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

From: Lv Zheng

Date: Sat Feb 28 2026 - 09:52:25 EST


From: Jingyu Li <joey.li@xxxxxxxxxxxx>

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@xxxxxxxxxxxx>
Signed-off-by: Lv Zheng <lv.zheng@xxxxxxxxxxxxxxxxxx>
Link: https://github.com/riscv-non-isa/riscv-iommu
Cc: Zong Li <zong.li@xxxxxxxxxx>
Cc: Yaxing Guo <guoyaxing@xxxxxxxxxx>
---
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@xxxxxxxxxxxx>
+ * Lv Zheng <lv.zheng@xxxxxxxxxxxx>
+ */
+
+#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