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 <[email protected]> --- .../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 <[email protected]> +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 <[email protected]> +R: Tomasz Jeznach <[email protected]> +R: Drew Fustini <[email protected]> +R: yunhui cui <[email protected]> +L: [email protected] +L: [email protected] +S: Maintained +F: Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu + RISC-V MICROCHIP SUPPORT M: Conor Dooley <[email protected]> M: Daire McNamara <[email protected]> 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)

