[PATCH v5 2/8] iommu/riscv: Add auxiliary bus framework and HPM device support

Lv Zheng lv.zheng at linux.spacemit.com
Sat Feb 28 06:44:14 PST 2026


From: Jingyu Li <joey.li at spacemit.com>

Introduces auxiliary bus support for RISC-V IOMMU to enable modular
extension of IOMMU capabilities. The framework allows creating auxiliary
devices that can be bound to separate drivers.

The IOMMU HPM featured PMU device ("iommu.riscv_iommu_hpm.0") is created
and registered as RISC-V IOMMU auxiliary device.

Signed-off-by: Jingyu Li <joey.li at spacemit.com>
Signed-off-by: Lv Zheng <lv.zheng at linux.spacemit.com>
Link: https://github.com/riscv-non-isa/riscv-iommu
Cc: Zong Li <zong.li at sifive.com>
Cc: Yaxing Guo <guoyaxing at bosc.ac.cn>
---
 drivers/iommu/riscv/Kconfig          |   1 +
 drivers/iommu/riscv/iommu-pci.c      |   8 +-
 drivers/iommu/riscv/iommu-platform.c |   3 +
 drivers/iommu/riscv/iommu.c          | 169 +++++++++++++++++++++++++++
 drivers/iommu/riscv/iommu.h          |  21 ++++
 include/linux/riscv_iommu.h          |  75 ++++++++++++
 6 files changed, 276 insertions(+), 1 deletion(-)
 create mode 100644 include/linux/riscv_iommu.h

diff --git a/drivers/iommu/riscv/Kconfig b/drivers/iommu/riscv/Kconfig
index c071816f59a6..26122a3a73d2 100644
--- a/drivers/iommu/riscv/Kconfig
+++ b/drivers/iommu/riscv/Kconfig
@@ -6,6 +6,7 @@ config RISCV_IOMMU
 	depends on RISCV && 64BIT
 	default y
 	select IOMMU_API
+	select AUXILIARY_BUS
 	help
 	  Support for implementations of the RISC-V IOMMU architecture that
 	  complements the RISC-V MMU capabilities, providing similar address
diff --git a/drivers/iommu/riscv/iommu-pci.c b/drivers/iommu/riscv/iommu-pci.c
index d82d2b00904c..478e72e9a285 100644
--- a/drivers/iommu/riscv/iommu-pci.c
+++ b/drivers/iommu/riscv/iommu-pci.c
@@ -34,6 +34,8 @@ static int riscv_iommu_pci_probe(struct pci_dev *pdev, const struct pci_device_i
 {
 	struct device *dev = &pdev->dev;
 	struct riscv_iommu_device *iommu;
+	phys_addr_t reg_phys;
+	resource_size_t reg_size;
 	int rc, vec;
 
 	rc = pcim_enable_device(pdev);
@@ -43,7 +45,9 @@ static int riscv_iommu_pci_probe(struct pci_dev *pdev, const struct pci_device_i
 	if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM))
 		return -ENODEV;
 
-	if (pci_resource_len(pdev, 0) < RISCV_IOMMU_REG_SIZE)
+	reg_phys = pci_resource_start(pdev, 0);
+	reg_size = pci_resource_len(pdev, 0);
+	if (reg_size < RISCV_IOMMU_REG_SIZE)
 		return -ENODEV;
 
 	rc = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev));
@@ -56,6 +60,8 @@ static int riscv_iommu_pci_probe(struct pci_dev *pdev, const struct pci_device_i
 
 	iommu->dev = dev;
 	iommu->reg = pcim_iomap_table(pdev)[0];
+	iommu->reg_phys = reg_phys;
+	iommu->reg_size = reg_size;
 
 	pci_set_master(pdev);
 	dev_set_drvdata(dev, iommu);
diff --git a/drivers/iommu/riscv/iommu-platform.c b/drivers/iommu/riscv/iommu-platform.c
index 83a28c83f991..e8e52bca8856 100644
--- a/drivers/iommu/riscv/iommu-platform.c
+++ b/drivers/iommu/riscv/iommu-platform.c
@@ -62,6 +62,9 @@ static int riscv_iommu_platform_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, PTR_ERR(iommu->reg),
 				     "could not map register region\n");
 
+	iommu->reg_phys = res->start;
+	iommu->reg_size = resource_size(res);
+
 	dev_set_drvdata(dev, iommu);
 
 	/* Check device reported capabilities / features. */
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index d9429097a2b5..1aa942486e3a 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -16,6 +16,7 @@
 #include <linux/acpi_rimt.h>
 #include <linux/compiler.h>
 #include <linux/crash_dump.h>
+#include <linux/idr.h>
 #include <linux/init.h>
 #include <linux/iommu.h>
 #include <linux/iopoll.h>
@@ -47,11 +48,28 @@
 static DEFINE_IDA(riscv_iommu_pscids);
 #define RISCV_IOMMU_MAX_PSCID		(BIT(20) - 1)
 
+static DEFINE_IDA(riscv_iommu_subdev_ida);
+
 /* Device resource-managed allocations */
 struct riscv_iommu_devres {
 	void *addr;
 };
 
+bool riscv_iommu_pmip_status(struct riscv_iommu_subdev *subdev)
+{
+	u32 ipsr = riscv_iommu_readl(subdev->iommu, RISCV_IOMMU_REG_IPSR);
+
+	return !!(ipsr & RISCV_IOMMU_IPSR_PMIP);
+}
+EXPORT_SYMBOL_GPL(riscv_iommu_pmip_status);
+
+void riscv_iommu_clear_pmip(struct riscv_iommu_subdev *subdev)
+{
+	riscv_iommu_writel(subdev->iommu, RISCV_IOMMU_REG_IPSR,
+			   RISCV_IOMMU_IPSR_PMIP);
+}
+EXPORT_SYMBOL_GPL(riscv_iommu_clear_pmip);
+
 static void riscv_iommu_devres_pages_release(struct device *dev, void *res)
 {
 	struct riscv_iommu_devres *devres = res;
@@ -1602,10 +1620,154 @@ static int riscv_iommu_init_check(struct riscv_iommu_device *iommu)
 	return 0;
 }
 
+static void riscv_iommu_subdev_release(struct device *dev)
+{
+	struct riscv_iommu_subdev *subdev = riscv_iommu_get_subdev(dev);
+
+	ida_free(&riscv_iommu_subdev_ida, subdev->auxdev.id);
+	kfree(subdev->info);
+	kfree(subdev);
+}
+
+static int riscv_iommu_subdev_add(struct riscv_iommu_device *iommu,
+				  const struct riscv_iommu_subdev_params *params)
+{
+	struct riscv_iommu_subdev *subdev;
+	struct auxiliary_device *auxdev;
+	int id, ret;
+
+	if (!params->info)
+		return -EINVAL;
+
+	id = ida_alloc(&riscv_iommu_subdev_ida, GFP_KERNEL);
+	if (id < 0)
+		return id;
+
+	subdev = kzalloc(sizeof(*subdev), GFP_KERNEL);
+	if (!subdev) {
+		ret = -ENOMEM;
+		goto err_free;
+	}
+
+	subdev->base = params->base;
+	subdev->iommu = iommu;
+	subdev->info = params->info;
+
+	auxdev = &subdev->auxdev;
+	auxdev->name = params->name;
+	auxdev->id = id;
+	auxdev->dev.parent = iommu->dev;
+	auxdev->dev.release = riscv_iommu_subdev_release;
+
+	ret = auxiliary_device_init(auxdev);
+	if (ret) {
+		dev_err(iommu->dev, "Failed to init %s auxiliary device: %d\n",
+			params->name, ret);
+		goto err_free;
+	}
+
+	ret = auxiliary_device_add(auxdev);
+	if (ret) {
+		dev_err(iommu->dev, "Failed to add %s auxiliary device: %d\n",
+			params->name, ret);
+		goto err_uninit;
+	}
+
+	spin_lock(&iommu->subdev_lock);
+	list_add_tail(&subdev->link, &iommu->subdev_list);
+	spin_unlock(&iommu->subdev_lock);
+	dev_info(iommu->dev, "%s auxiliary device created\n", params->name);
+	return 0;
+
+err_uninit:
+	auxiliary_device_uninit(auxdev);
+	return ret;
+
+err_free:
+	kfree(subdev);
+	ida_free(&riscv_iommu_subdev_ida, id);
+	return ret;
+}
+
+static void riscv_iommu_enumerate_hpm(struct riscv_iommu_device *iommu)
+{
+	struct riscv_iommu_hpm_info *hpm_info;
+	struct riscv_iommu_subdev_params params;
+	int irq;
+	int ret;
+
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_HPM))
+		return;
+
+	irq = iommu->irqs[riscv_iommu_queue_vec(iommu, RISCV_IOMMU_INTR_PM)];
+	if (irq <= 0) {
+		dev_err(iommu->dev, "HPM: No IRQ available\n");
+		return;
+	}
+
+	hpm_info = kzalloc(sizeof(*hpm_info), GFP_KERNEL);
+	if (!hpm_info)
+		return;
+
+	hpm_info->irq = irq;
+
+	params = (struct riscv_iommu_subdev_params) {
+		.name = "riscv_iommu_hpm",
+		.info = hpm_info,
+		.base = iommu->reg + RISCV_IOMMU_REG_IOCOUNTOVF,
+	};
+
+	ret = riscv_iommu_subdev_add(iommu, &params);
+	if (ret) {
+		kfree(hpm_info);
+		dev_warn(iommu->dev,
+			 "Failed to enumerate HPM auxiliary device: %d\n",
+			 ret);
+	}
+}
+
+/**
+ * riscv_iommu_subdev_setup - Enumerate auxiliary bus subdevices
+ *
+ * @iommu: RISC-V IOMMU device
+ *
+ * Enumerates HPM, or other extended subdevices via the auxiliary bus. To
+ * add new extended device types, implement an enumerate function and call
+ * it from here.
+ */
+void riscv_iommu_subdev_setup(struct riscv_iommu_device *iommu)
+{
+	riscv_iommu_enumerate_hpm(iommu);
+}
+
+/**
+ * riscv_iommu_subdev_cleanup - Remove all auxiliary bus subdevices
+ *
+ * @iommu: RISC-V IOMMU device
+ *
+ * Iterates over the subdev_list in reverse order, deletes each auxiliary
+ * device from the bus and uninitializes it.
+ */
+void riscv_iommu_subdev_cleanup(struct riscv_iommu_device *iommu)
+{
+	struct riscv_iommu_subdev *subdev, *next;
+
+	spin_lock(&iommu->subdev_lock);
+	list_for_each_entry_safe_reverse(subdev, next, &iommu->subdev_list, link) {
+		list_del_init(&subdev->link);
+		spin_unlock(&iommu->subdev_lock);
+		auxiliary_device_delete(&subdev->auxdev);
+		auxiliary_device_uninit(&subdev->auxdev);
+		spin_lock(&iommu->subdev_lock);
+	}
+	spin_unlock(&iommu->subdev_lock);
+}
+
 void riscv_iommu_remove(struct riscv_iommu_device *iommu)
 {
 	iommu_device_unregister(&iommu->iommu);
 	iommu_device_sysfs_remove(&iommu->iommu);
+	riscv_iommu_subdev_cleanup(iommu);
 	riscv_iommu_iodir_set_mode(iommu, RISCV_IOMMU_DDTP_IOMMU_MODE_OFF);
 	riscv_iommu_queue_disable(&iommu->cmdq);
 	riscv_iommu_queue_disable(&iommu->fltq);
@@ -1615,6 +1777,8 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu)
 {
 	int rc;
 
+	spin_lock_init(&iommu->subdev_lock);
+	INIT_LIST_HEAD(&iommu->subdev_list);
 	RISCV_IOMMU_QUEUE_INIT(&iommu->cmdq, CQ);
 	RISCV_IOMMU_QUEUE_INIT(&iommu->fltq, FQ);
 
@@ -1669,6 +1833,11 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu)
 		goto err_remove_sysfs;
 	}
 
+	/* Initialize auxiliary devices for extended features. These are not
+	 * critical to IOMMU operation, so failures are non-fatal.
+	 */
+	riscv_iommu_subdev_setup(iommu);
+
 	return 0;
 
 err_remove_sysfs:
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 46df79dd5495..1296625488ef 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -14,6 +14,7 @@
 #include <linux/iommu.h>
 #include <linux/types.h>
 #include <linux/iopoll.h>
+#include <linux/riscv_iommu.h>
 
 #include "iommu-bits.h"
 
@@ -42,6 +43,8 @@ struct riscv_iommu_device {
 
 	/* hardware control register space */
 	void __iomem *reg;
+	phys_addr_t reg_phys;
+	resource_size_t reg_size;
 
 	/* supported and enabled hardware capabilities */
 	u64 caps;
@@ -60,11 +63,29 @@ struct riscv_iommu_device {
 	unsigned int ddt_mode;
 	dma_addr_t ddt_phys;
 	u64 *ddt_root;
+
+	/* auxiliary subdevices */
+	spinlock_t subdev_lock;
+	struct list_head subdev_list;
+};
+
+/**
+ * struct riscv_iommu_subdev_params - params for adding auxiliary subdevice
+ * @name: auxiliary device name
+ * @info: device-specific info, freed in release
+ * @base: PMU register base
+ */
+struct riscv_iommu_subdev_params {
+	const char *name;
+	void *info;
+	void __iomem *base;
 };
 
 int riscv_iommu_init(struct riscv_iommu_device *iommu);
 void riscv_iommu_remove(struct riscv_iommu_device *iommu);
 void riscv_iommu_disable(struct riscv_iommu_device *iommu);
+void riscv_iommu_subdev_setup(struct riscv_iommu_device *iommu);
+void riscv_iommu_subdev_cleanup(struct riscv_iommu_device *iommu);
 
 #define riscv_iommu_readl(iommu, addr) \
 	readl_relaxed((iommu)->reg + (addr))
diff --git a/include/linux/riscv_iommu.h b/include/linux/riscv_iommu.h
new file mode 100644
index 000000000000..0447bc4d1fab
--- /dev/null
+++ b/include/linux/riscv_iommu.h
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * RISC-V IOMMU Common Interface
+ *
+ * This header provides a common interface for sharing resources between
+ * the RISC-V IOMMU driver and its auxiliary bus child drivers.
+ *
+ * Copyright (C) 2026 SpacemiT Technologies Inc.
+ *   Author: 2026 Jingyu Li <joey.li at spacemit.com>
+ *                Lv Zheng <lv.zheng at spacemit.com>
+ */
+
+#ifndef _LINUX_RISCV_IOMMU_H_
+#define _LINUX_RISCV_IOMMU_H_
+
+#include <linux/auxiliary_bus.h>
+
+struct riscv_iommu_device;
+
+/**
+ * struct riscv_iommu_subdev - RISC-V IOMMU auxiliary bus subdevice
+ * @link: list node for iommu->subdev_list
+ * @auxdev: auxiliary bus device ((use auxdev.id for unique id)
+ * @base: PMU register base
+ * @iommu: parent IOMMU (opaque)
+ * @info: subdevice-specific info, freed in release
+ */
+struct riscv_iommu_subdev {
+	struct list_head link;
+	struct auxiliary_device auxdev;
+	void __iomem *base;
+	struct riscv_iommu_device *iommu;
+	void *info;
+};
+
+/**
+ * struct riscv_iommu_hpm_info - HPM info for IOATS (main IOMMU HPM)
+ * @irq: interrupt number
+ */
+struct riscv_iommu_hpm_info {
+	unsigned int irq;
+};
+
+/**
+ * riscv_iommu_get_subdev - get riscv_iommu_subdev from device
+ *
+ * @dev: &device of the auxiliary device (auxdev->dev)
+ *
+ * Returns the riscv_iommu_subdev pointer, or NULL if @dev is NULL.
+ */
+static inline struct riscv_iommu_subdev *riscv_iommu_get_subdev(struct device *dev)
+{
+	if (!dev)
+		return NULL;
+	return container_of(container_of(dev, struct auxiliary_device, dev),
+			    struct riscv_iommu_subdev, auxdev);
+}
+
+/**
+ * riscv_iommu_pmip_status - test if PM interrupt is pending
+ *
+ * @subdev: subdevice with iommu
+ *
+ * Returns true if PM interrupt pending, false otherwise.
+ */
+bool riscv_iommu_pmip_status(struct riscv_iommu_subdev *subdev);
+
+/**
+ * riscv_iommu_clear_pmip - clear PMIP bit in IPSR to ack PMU interrupt
+ *
+ * @subdev: subdevice with iommu
+ */
+void riscv_iommu_clear_pmip(struct riscv_iommu_subdev *subdev);
+
+#endif /* _LINUX_RISCV_IOMMU_H_ */
-- 
2.43.0




More information about the linux-riscv mailing list