[RFC PATCH 5/7] iommu/riscv: Expose global QoS IDs in sysfs

Zhanpeng Zhang zhangzhanpeng.jasper at bytedance.com
Tue Jul 14 06:06:55 PDT 2026


The RISC-V IOMMU QoS extension provides iommu_qosid as a per-IOMMU
global default tag. It is used for IOMMU-originated DDT, CQ, FQ, PQ, and
MSI accesses, and for device-originated requests when DDTP is in BARE
mode.

Initialize iommu_qosid to RCID 0 and MCID 0 when the hardware advertises
QOSID support. Preserve reserved and WPRI bits with read-modify-write,
and use register readback to reject values which the WARL fields do not
retain.

Add a qosid attribute to the RISC-V IOMMU class device. Reading returns
the current RCID and MCID values. Writing the documented
'rcid=<rcid> mcid=<mcid>' form updates both fields while preserving the
other register bits.

Keep this interface separate from resctrl group QoS. The sysfs attribute
controls the IOMMU-wide default, while resctrl device assignment programs
per-device DC.ta in translated modes.

Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper at bytedance.com>
---
 .../ABI/testing/sysfs-class-iommu-riscv-iommu |  27 +++
 MAINTAINERS                                   |  10 ++
 drivers/iommu/riscv/iommu.c                   | 159 +++++++++++++++++-
 drivers/iommu/riscv/iommu.h                   |   9 +-
 4 files changed, 202 insertions(+), 3 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu

diff --git a/Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu b/Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
new file mode 100644
index 000000000000..b0cd68997f17
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
@@ -0,0 +1,27 @@
+What:		/sys/class/iommu/<iommu>/qosid
+Date:		June 2026
+KernelVersion:	6.18
+Contact:	Zhanpeng Zhang <zhangzhanpeng.jasper at bytedance.com>
+Description:
+		The RISC-V IOMMU global default QoS IDs for this IOMMU.
+		The file is present only when the IOMMU reports the QOSID
+		capability.
+
+		Reading the file returns the RCID and MCID fields from the
+		iommu_qosid register:
+
+		  rcid=<rcid> mcid=<mcid>
+
+		Writing the file updates the RCID and MCID fields while
+		preserving reserved/WPRI bits:
+
+		  rcid=<rcid> mcid=<mcid>
+
+		Writes fail with ERANGE when either value cannot be represented
+		by the IOMMU. A successful write is verified by reading the WARL
+		fields back from the register.
+
+		The iommu_qosid register is a per-IOMMU global default. It
+		tags IOMMU-originated DDT, CQ, FQ, PQ and MSI accesses, and
+		in BARE mode device-originated requests. It does not assign
+		per-device or per-IOMMU-group QoS IDs in translated modes.
diff --git a/MAINTAINERS b/MAINTAINERS
index 0b5d38b772e0..c59be02c8f02 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23279,6 +23279,16 @@ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/iommu/linux.git
 F:	Documentation/devicetree/bindings/iommu/riscv,iommu.yaml
 F:	drivers/iommu/riscv/
 
+RISC-V IOMMU QoS
+M:	Zhanpeng Zhang <zhangzhanpeng.jasper at bytedance.com>
+R:	Tomasz Jeznach <tomasz.jeznach at linux.dev>
+R:	Drew Fustini <fustini at kernel.org>
+R:	yunhui cui <cuiyunhui at bytedance.com>
+L:	iommu at lists.linux.dev
+L:	linux-riscv at lists.infradead.org
+S:	Maintained
+F:	Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
+
 RISC-V MICROCHIP SUPPORT
 M:	Conor Dooley <conor.dooley at microchip.com>
 M:	Daire McNamara <daire.mcnamara at microchip.com>
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index deab646bb1ea..e85da9eef58e 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -1658,6 +1658,7 @@ int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid, u32 mcid)
 	};
 	int ret;
 
+	/* Resctrl IDs are bounded by the system's reported controller counts. */
 	if (rcid > FIELD_MAX(RISCV_IOMMU_DC_TA_RCID) ||
 	    mcid > FIELD_MAX(RISCV_IOMMU_DC_TA_MCID))
 		return -ERANGE;
@@ -1688,9 +1689,154 @@ static const struct iommu_ops riscv_iommu_ops = {
 	.release_device	= riscv_iommu_release_device,
 };
 
+static int riscv_iommu_set_default_qosid(struct riscv_iommu_device *iommu,
+					 u32 rcid, u32 mcid)
+{
+	u32 old_qosid;
+	u32 qosid;
+	int ret = 0;
+
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return -EOPNOTSUPP;
+
+	if (rcid > FIELD_MAX(RISCV_IOMMU_IOMMU_QOSID_RCID) ||
+	    mcid > FIELD_MAX(RISCV_IOMMU_IOMMU_QOSID_MCID))
+		return -ERANGE;
+
+	/*
+	 * iommu_qosid is a per-IOMMU global default. It tags IOMMU-originated
+	 * DDT/CQ/FQ/PQ and MSI accesses, and in BARE mode device-originated
+	 * requests. Per-device group QoS is still handled separately through
+	 * DC.ta.
+	 */
+	mutex_lock(&iommu->qosid_lock);
+	old_qosid = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IOMMU_QOSID);
+	qosid = old_qosid & ~(RISCV_IOMMU_IOMMU_QOSID_RCID |
+				RISCV_IOMMU_IOMMU_QOSID_MCID);
+	qosid |= FIELD_PREP(RISCV_IOMMU_IOMMU_QOSID_RCID, rcid) |
+		 FIELD_PREP(RISCV_IOMMU_IOMMU_QOSID_MCID, mcid);
+	riscv_iommu_writel(iommu, RISCV_IOMMU_REG_IOMMU_QOSID, qosid);
+
+	qosid = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IOMMU_QOSID);
+	if (FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_RCID, qosid) != rcid ||
+	    FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_MCID, qosid) != mcid) {
+		riscv_iommu_writel(iommu, RISCV_IOMMU_REG_IOMMU_QOSID,
+				   old_qosid);
+		ret = -ERANGE;
+	}
+	mutex_unlock(&iommu->qosid_lock);
+	if (ret)
+		return ret;
+
+	dev_dbg(iommu->dev, "set global QoS IDs rcid=%u mcid=%u\n",
+		(u32)FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_RCID, qosid),
+		(u32)FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_MCID, qosid));
+
+	return ret;
+}
+
+static int riscv_iommu_get_default_qosid(struct riscv_iommu_device *iommu,
+					 u32 *rcid, u32 *mcid)
+{
+	u32 qosid;
+
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return -EOPNOTSUPP;
+
+	qosid = riscv_iommu_readl(iommu, RISCV_IOMMU_REG_IOMMU_QOSID);
+	if (rcid)
+		*rcid = FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_RCID, qosid);
+	if (mcid)
+		*mcid = FIELD_GET(RISCV_IOMMU_IOMMU_QOSID_MCID, qosid);
+
+	return 0;
+}
+
+static int riscv_iommu_init_default_qosid(struct riscv_iommu_device *iommu)
+{
+	if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+		return 0;
+
+	/* Avoid probing the live WARL fields with all-ones while in BARE mode. */
+	return riscv_iommu_set_default_qosid(iommu, 0, 0);
+}
+
+static struct riscv_iommu_device *dev_to_riscv_iommu(struct device *dev)
+{
+	struct iommu_device *iommu = dev_to_iommu_device(dev);
+
+	return iommu ? container_of(iommu, struct riscv_iommu_device, iommu) : NULL;
+}
+
+static ssize_t qosid_show(struct device *dev, struct device_attribute *attr,
+			  char *buf)
+{
+	struct riscv_iommu_device *iommu = dev_to_riscv_iommu(dev);
+	u32 rcid, mcid;
+	int ret;
+
+	if (!iommu)
+		return -ENODEV;
+
+	ret = riscv_iommu_get_default_qosid(iommu, &rcid, &mcid);
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "rcid=%u mcid=%u\n", rcid, mcid);
+}
+
+static ssize_t qosid_store(struct device *dev, struct device_attribute *attr,
+			   const char *buf, size_t count)
+{
+	struct riscv_iommu_device *iommu = dev_to_riscv_iommu(dev);
+	char *args, *key, *value;
+	char *input;
+	u32 rcid, mcid;
+	int ret = -EINVAL;
+
+	if (!iommu)
+		return -ENODEV;
+
+	input = kstrdup(buf, GFP_KERNEL);
+	if (!input)
+		return -ENOMEM;
+
+	args = strim(input);
+	args = next_arg(args, &key, &value);
+	if (!value || strcmp(key, "rcid") || kstrtou32(value, 10, &rcid))
+		goto out;
+
+	args = next_arg(args, &key, &value);
+	if (!value || strcmp(key, "mcid") || kstrtou32(value, 10, &mcid) ||
+	    *skip_spaces(args))
+		goto out;
+
+	ret = riscv_iommu_set_default_qosid(iommu, rcid, mcid);
+out:
+	kfree(input);
+	return ret ? ret : count;
+}
+
+static DEVICE_ATTR_RW(qosid);
+
+static struct attribute *riscv_iommu_attrs[] = {
+	&dev_attr_qosid.attr,
+	NULL,
+};
+
+static const struct attribute_group riscv_iommu_group = {
+	.attrs = riscv_iommu_attrs,
+};
+
+static const struct attribute_group *riscv_iommu_groups[] = {
+	&riscv_iommu_group,
+	NULL,
+};
+
 static int riscv_iommu_init_check(struct riscv_iommu_device *iommu)
 {
 	u64 ddtp;
+	int ret;
 
 	/*
 	 * Make sure the IOMMU is switched off or in pass-through mode during
@@ -1721,6 +1867,10 @@ static int riscv_iommu_init_check(struct riscv_iommu_device *iommu)
 			return -EINVAL;
 	}
 
+	ret = riscv_iommu_init_default_qosid(iommu);
+	if (ret)
+		return ret;
+
 	/*
 	 * Distribute interrupt vectors, always use first vector for CIV.
 	 * At least one interrupt is required. Read back and verify.
@@ -1753,10 +1903,12 @@ void riscv_iommu_remove(struct riscv_iommu_device *iommu)
 
 int riscv_iommu_init(struct riscv_iommu_device *iommu)
 {
+	const struct attribute_group **sysfs_groups = NULL;
 	int rc;
 
 	RISCV_IOMMU_QUEUE_INIT(&iommu->cmdq, CQ);
 	RISCV_IOMMU_QUEUE_INIT(&iommu->fltq, FQ);
+	mutex_init(&iommu->qosid_lock);
 
 	rc = riscv_iommu_init_check(iommu);
 	if (rc)
@@ -1788,8 +1940,11 @@ int riscv_iommu_init(struct riscv_iommu_device *iommu)
 	if (rc)
 		goto err_queue_disable;
 
-	rc = iommu_device_sysfs_add(&iommu->iommu, NULL, NULL, "riscv-iommu@%s",
-				    dev_name(iommu->dev));
+	if (iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID)
+		sysfs_groups = riscv_iommu_groups;
+
+	rc = iommu_device_sysfs_add(&iommu->iommu, NULL, sysfs_groups,
+				    "riscv-iommu@%s", dev_name(iommu->dev));
 	if (rc) {
 		dev_err_probe(iommu->dev, rc, "cannot register sysfs interface\n");
 		goto err_iodir_off;
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 2c57625637bf..13ea67b7e42d 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -12,8 +12,12 @@
 #define _RISCV_IOMMU_H_
 
 #include <linux/iommu.h>
-#include <linux/types.h>
 #include <linux/iopoll.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#ifdef CONFIG_RISCV_IOMMU_32BIT
+#include <linux/spinlock.h>
+#endif
 
 #include "iommu-bits.h"
 
@@ -50,6 +54,9 @@ struct riscv_iommu_device {
 	u64 caps;
 	u32 fctl;
 
+	/* Serializes QoS updates to iommu_qosid and device contexts. */
+	struct mutex qosid_lock;
+
 	/* available interrupt numbers, MSI or WSI */
 	unsigned int irqs[RISCV_IOMMU_INTR_COUNT];
 	unsigned int irqs_count;
-- 
2.50.1 (Apple Git-155)




More information about the linux-riscv mailing list