Re: [RFC PATCH 6/7] riscv_cbqri: Assign IOMMU groups to resource groups
From: yunhui cui
Date: Sun Aug 30 2026 - 22:23:06 EST
Hi Zhanpeng,
On Tue, Jul 14, 2026 at 9:08 PM Zhanpeng Zhang
<zhangzhanpeng.jasper@xxxxxxxxxxxxx> wrote:
>
> Allow the resctrl devices file to accept iommu_group:<id> tokens on
> RISC-V and map the target resource group's closid and rmid values to
> RCID and MCID.
>
> Record non-default assignments in an RCU-protected binding list rather
> than inferring membership from hardware IDs, which may be shared. Keep a
> parent reference obtained by numeric group lookup so the binding does not
> keep an empty group's devices kobject active, and prune bindings after
> their group becomes inactive.
>
> Publish an explicit UPDATING state while hardware changes. Paging-domain
> attachment rejects that transient state. Static FSC=Bare transitions
> preserve the current IDs so a mandatory release-domain attachment cannot
> fail. After the checked group update succeeds, publish the packed IDs. A
> validation failure restores the previous active state without partially
> changing hardware.
>
> Moving an IOMMU group to the default resource group resets its hardware
> state and removes the software binding. Device contexts created later for
> an assigned group inherit the IDs through the RCU lookup path.
>
> Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@xxxxxxxxxxxxx>
> ---
> MAINTAINERS | 1 +
> arch/riscv/include/asm/qos.h | 12 ++
> drivers/iommu/riscv/iommu.c | 70 +++++++--
> drivers/iommu/riscv/iommu.h | 16 +-
> drivers/resctrl/Kconfig | 6 +
> drivers/resctrl/Makefile | 1 +
> drivers/resctrl/cbqri_iommu.c | 276 ++++++++++++++++++++++++++++++++++
> 7 files changed, 369 insertions(+), 13 deletions(-)
> create mode 100644 drivers/resctrl/cbqri_iommu.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index c59be02c8f02..162ad5a1780f 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -23288,6 +23288,7 @@ L: iommu@xxxxxxxxxxxxxxx
> L: linux-riscv@xxxxxxxxxxxxxxxxxxx
> S: Maintained
> F: Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
> +F: drivers/resctrl/cbqri_iommu.c
>
> RISC-V MICROCHIP SUPPORT
> M: Conor Dooley <conor.dooley@xxxxxxxxxxxxx>
> diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
> index daa758d4efff..84a93739c6ff 100644
> --- a/arch/riscv/include/asm/qos.h
> +++ b/arch/riscv/include/asm/qos.h
> @@ -8,6 +8,18 @@
>
> struct iommu_group;
>
> +#ifdef CONFIG_RISCV_CBQRI_IOMMU
> +int riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group,
> + u32 *rcid, u32 *mcid);
> +#else
> +static inline int
> +riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group, u32 *rcid,
> + u32 *mcid)
> +{
> + return -ENOENT;
> +}
> +#endif
> +
> #ifdef CONFIG_RISCV_IOMMU
> int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
> u32 mcid);
> diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
> index e85da9eef58e..1a7d8a582959 100644
> --- a/drivers/iommu/riscv/iommu.c
> +++ b/drivers/iommu/riscv/iommu.c
> @@ -1188,16 +1188,50 @@ static void riscv_iommu_iodir_iotinval(struct riscv_iommu_device *iommu,
> * device is not quiesced might be disruptive, potentially causing
> * interim translation faults.
> */
> -static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
> - struct device *dev, u64 fsc, u64 ta)
> +static int riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
> + struct device *dev, u64 fsc, u64 ta)
> {
> struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
> struct riscv_iommu_dc *dc;
> struct riscv_iommu_command cmd;
> bool sync_required = false;
> + u64 group_qos_ta = 0;
> + bool has_group_qos;
> + u32 group_rcid;
> + u32 group_mcid;
> u64 tc;
> + int ret = 0;
> + int qos_ret;
> int i;
>
> + /* Serialize DC.ta updates with resctrl IOMMU group QoS changes. */
> + mutex_lock(&iommu->qosid_lock);
> +
> + qos_ret = riscv_resctrl_iommu_group_get_qosid(dev->iommu_group,
> + &group_rcid, &group_mcid);
> + if (qos_ret == -EAGAIN) {
> + /* Static domain transitions must not fail during device release. */
> + if (fsc != RISCV_IOMMU_FSC_BARE) {
> + ret = -EBUSY;
> + goto unlock;
> + }
> + qos_ret = -ENOENT;
> + }
> + has_group_qos = !qos_ret;
> + if (has_group_qos) {
> + if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID) ||
> + iommu->ddt_mode <= RISCV_IOMMU_DDTP_IOMMU_MODE_BARE) {
> + ret = -EOPNOTSUPP;
> + goto unlock;
> + }
> + if (group_rcid > FIELD_MAX(RISCV_IOMMU_DC_TA_RCID) ||
> + group_mcid > FIELD_MAX(RISCV_IOMMU_DC_TA_MCID)) {
> + ret = -ERANGE;
> + goto unlock;
> + }
> + group_qos_ta = riscv_iommu_qosid_ta(group_rcid, group_mcid);
> + }
> +
> for (i = 0; i < fwspec->num_ids; i++) {
> dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
> tc = READ_ONCE(dc->tc);
> @@ -1233,11 +1267,12 @@ static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
> tc = READ_ONCE(dc->tc);
> dc_ta = ta;
> if (iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID) {
> - dc_ta |= READ_ONCE(dc->ta) &
> - (RISCV_IOMMU_DC_TA_RCID |
> - RISCV_IOMMU_DC_TA_MCID);
> - ta_mask |= RISCV_IOMMU_DC_TA_RCID |
> - RISCV_IOMMU_DC_TA_MCID;
> + if (has_group_qos)
> + dc_ta |= group_qos_ta;
> + else
> + dc_ta |= READ_ONCE(dc->ta) &
> + RISCV_IOMMU_DC_TA_QOSID;
> + ta_mask |= RISCV_IOMMU_DC_TA_QOSID;
> }
> tc |= dc_ta & RISCV_IOMMU_DC_TC_V;
>
> @@ -1259,6 +1294,9 @@ static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
> }
>
> riscv_iommu_cmd_sync(iommu, RISCV_IOMMU_IOTINVAL_TIMEOUT);
> +unlock:
> + mutex_unlock(&iommu->qosid_lock);
> + return ret;
> }
>
> /*
> @@ -1324,6 +1362,7 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
> struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
> struct pt_iommu_riscv_64_hw_info pt_info;
> u64 fsc, ta;
> + int ret;
>
> pt_iommu_riscv_64_hw_info(&domain->riscvpt, &pt_info);
>
> @@ -1338,7 +1377,11 @@ static int riscv_iommu_attach_paging_domain(struct iommu_domain *iommu_domain,
> if (riscv_iommu_bond_link(domain, dev))
> return -ENOMEM;
>
> - riscv_iommu_iodir_update(iommu, dev, fsc, ta);
> + ret = riscv_iommu_iodir_update(iommu, dev, fsc, ta);
> + if (ret) {
> + riscv_iommu_bond_unlink(domain, dev);
> + return ret;
> + }
> riscv_iommu_bond_unlink(info->domain, dev);
> info->domain = domain;
>
> @@ -1413,9 +1456,12 @@ static int riscv_iommu_attach_blocking_domain(struct iommu_domain *iommu_domain,
> {
> struct riscv_iommu_device *iommu = dev_to_iommu(dev);
> struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
> + int ret;
>
> /* Make device context invalid, translation requests will fault w/ #258 */
> - riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, 0);
> + ret = riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, 0);
> + if (ret)
> + return ret;
> riscv_iommu_bond_unlink(info->domain, dev);
> info->domain = NULL;
>
> @@ -1435,8 +1481,12 @@ static int riscv_iommu_attach_identity_domain(struct iommu_domain *iommu_domain,
> {
> struct riscv_iommu_device *iommu = dev_to_iommu(dev);
> struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
> + u64 ta = RISCV_IOMMU_PC_TA_V;
> + int ret;
>
> - riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, RISCV_IOMMU_PC_TA_V);
> + ret = riscv_iommu_iodir_update(iommu, dev, RISCV_IOMMU_FSC_BARE, ta);
> + if (ret)
> + return ret;
> riscv_iommu_bond_unlink(info->domain, dev);
> info->domain = NULL;
>
> diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
> index 13ea67b7e42d..e7804ec65647 100644
> --- a/drivers/iommu/riscv/iommu.h
> +++ b/drivers/iommu/riscv/iommu.h
> @@ -11,13 +11,11 @@
> #ifndef _RISCV_IOMMU_H_
> #define _RISCV_IOMMU_H_
>
> +#include <linux/errno.h>
> #include <linux/iommu.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"
>
> @@ -26,6 +24,18 @@ struct riscv_iommu_device;
> int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
> u32 mcid);
>
> +#ifdef CONFIG_RISCV_CBQRI_IOMMU
> +int riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group,
> + u32 *rcid, u32 *mcid);
> +#else
> +static inline int
> +riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group, u32 *rcid,
> + u32 *mcid)
> +{
> + return -ENOENT;
> +}
> +#endif
> +
> struct riscv_iommu_queue {
> atomic_t prod; /* unbounded producer allocation index */
> atomic_t head; /* unbounded shadow ring buffer consumer index */
> diff --git a/drivers/resctrl/Kconfig b/drivers/resctrl/Kconfig
> index b7db6ff9d054..4e10c813e647 100644
> --- a/drivers/resctrl/Kconfig
> +++ b/drivers/resctrl/Kconfig
> @@ -60,3 +60,9 @@ endif
> config RISCV_CBQRI_RESCTRL_FS
> bool
> default y if RISCV_CBQRI && RESCTRL_FS
> +
> +config RISCV_CBQRI_IOMMU
> + bool
> + depends on RISCV_CBQRI_RESCTRL_FS && RISCV_IOMMU
> + select ARCH_HAS_RESCTRL_DEVICES
> + default y
> diff --git a/drivers/resctrl/Makefile b/drivers/resctrl/Makefile
> index c8339113ef1f..148b2e28c6a3 100644
> --- a/drivers/resctrl/Makefile
> +++ b/drivers/resctrl/Makefile
> @@ -8,3 +8,4 @@ obj-$(CONFIG_RISCV_CBQRI) += cbqri.o
> cbqri-y += cbqri_devices.o
> cbqri-$(CONFIG_RISCV_CBQRI_RESCTRL_FS) += cbqri_resctrl.o
> cbqri-$(CONFIG_RISCV_CBQRI_CAPACITY) += cbqri_capacity.o
> +cbqri-$(CONFIG_RISCV_CBQRI_IOMMU) += cbqri_iommu.o
> diff --git a/drivers/resctrl/cbqri_iommu.c b/drivers/resctrl/cbqri_iommu.c
> new file mode 100644
> index 000000000000..4086c32546bd
> --- /dev/null
> +++ b/drivers/resctrl/cbqri_iommu.c
> @@ -0,0 +1,276 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +#include <linux/capability.h>
> +#include <linux/errno.h>
> +#include <linux/iommu.h>
> +#include <linux/kernel.h>
> +#include <linux/limits.h>
> +#include <linux/list.h>
> +#include <linux/rculist.h>
> +#include <linux/rcupdate.h>
> +#include <linux/resctrl.h>
> +#include <linux/seq_file.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>
> +#include <linux/types.h>
> +
> +#include <asm/qos.h>
> +
> +#define IOMMU_GROUP_TOKEN "iommu_group:"
> +
> +/*
> + * RISC-V IOMMU RCID and MCID fields are 12 bits each, so an active binding
> + * cannot contain U64_MAX.
> + */
> +#define QOS_IOMMU_BINDING_UPDATING U64_MAX
> +
> +/* Records the resctrl QoS ID assignment for one IOMMU group. */
> +struct qos_iommu_group_binding {
> + struct list_head node;
> + struct rcu_head rcu;
> + struct iommu_group *group;
> + int group_id;
> + /* Packed IDs, or QOS_IOMMU_BINDING_UPDATING during a hardware update. */
> + u64 state;
> +};
> +
> +/*
> + * resctrl owns IOMMU group membership bookkeeping. Do not infer membership by
> + * reading RCID/MCID back from hardware: the same IDs may be shared by multiple
> + * resctrl users. Writers are serialized by rdtgroup_mutex. IOMMU attach paths
> + * read the list under RCU so newly attached devices can inherit an existing
> + * group assignment without taking locks in the opposite order to resctrl.
> + */
> +static LIST_HEAD(qos_iommu_bindings);
> +
> +static u64 qos_iommu_group_ids_pack(struct resctrl_group_ids ids)
> +{
> + return (u64)ids.closid << 32 | ids.rmid;
> +}
> +
> +static struct resctrl_group_ids qos_iommu_group_ids_unpack(u64 ids)
> +{
> + return (struct resctrl_group_ids) {
> + .closid = upper_32_bits(ids),
> + .rmid = lower_32_bits(ids),
> + };
> +}
> +
> +static bool qos_iommu_group_ids_match(u64 packed, struct resctrl_group_ids ids)
> +{
> + return packed != QOS_IOMMU_BINDING_UPDATING &&
> + packed == qos_iommu_group_ids_pack(ids);
> +}
> +
> +static bool qos_iommu_group_ids_default(struct resctrl_group_ids ids)
> +{
> + return ids.closid == RESCTRL_RESERVED_CLOSID &&
> + ids.rmid == RESCTRL_RESERVED_RMID;
> +}
> +
> +static struct qos_iommu_group_binding *
> +qos_iommu_group_find_binding(int group_id)
> +{
> + struct qos_iommu_group_binding *binding;
> +
> + list_for_each_entry(binding, &qos_iommu_bindings, node) {
> + if (binding->group_id == group_id)
> + return binding;
> + }
> +
> + return NULL;
> +}
> +
> +static void qos_iommu_group_free_binding(struct qos_iommu_group_binding *binding)
> +{
> + list_del_rcu(&binding->node);
> + iommu_group_put_by_id(binding->group);
> + kfree_rcu(binding, rcu);
> +}
> +
> +static void qos_iommu_group_prune_inactive(void)
> +{
> + struct qos_iommu_group_binding *binding;
> + struct qos_iommu_group_binding *tmp;
> +
> + list_for_each_entry_safe(binding, tmp, &qos_iommu_bindings, node) {
> + if (!iommu_group_is_active(binding->group))
> + qos_iommu_group_free_binding(binding);
> + }
> +}
> +
> +static int qos_iommu_group_set(struct iommu_group *group,
> + struct resctrl_group_ids ids)
> +{
> + /* RISC-V IOMMU QoS maps closid->rcid and rmid->mcid 1:1. */
> + return riscv_iommu_group_set_qosid(group, ids.closid, ids.rmid);
> +}
> +
> +int resctrl_arch_devices_write(char *tok, struct resctrl_group_ids ids)
> +{
> + struct qos_iommu_group_binding *binding;
> + struct qos_iommu_group_binding *new_binding = NULL;
> + struct iommu_group *group;
> + u64 old_state = QOS_IOMMU_BINDING_UPDATING;
> + bool default_ids;
> + int group_id;
> + int ret;
> +
> + if (strncmp(tok, IOMMU_GROUP_TOKEN, strlen(IOMMU_GROUP_TOKEN))) {
> + resctrl_last_cmd_printf("Unsupported device token %s\n", tok);
> + return -EOPNOTSUPP;
> + }
> +
> + tok += strlen(IOMMU_GROUP_TOKEN);
> + if (kstrtoint(tok, 10, &group_id) || group_id < 0) {
> + resctrl_last_cmd_printf("IOMMU group parsing error %s%s\n",
> + IOMMU_GROUP_TOKEN, tok);
> + return -EINVAL;
> + }
> +
> + if (!capable(CAP_SYS_ADMIN)) {
> + resctrl_last_cmd_puts("No permission to move IOMMU group\n");
> + return -EPERM;
> + }
> +
> + default_ids = qos_iommu_group_ids_default(ids);
> + qos_iommu_group_prune_inactive();
> + binding = qos_iommu_group_find_binding(group_id);
> + if (!binding) {
> + group = iommu_group_get_by_id(group_id);
> + if (!group) {
> + resctrl_last_cmd_printf("No IOMMU group %d\n", group_id);
> + return -ENOENT;
> + }
> +
> + if (default_ids) {
> + ret = qos_iommu_group_set(group, ids);
> + iommu_group_put_by_id(group);
> + if (ret)
> + resctrl_last_cmd_printf("Failed to reset IOMMU group %d QoS (%d)\n",
> + group_id, ret);
> + return ret;
> + }
> +
> + new_binding = kzalloc_obj(*new_binding, GFP_KERNEL);
> + if (!new_binding) {
> + iommu_group_put_by_id(group);
> + resctrl_last_cmd_puts("Unable to allocate IOMMU group QoS binding\n");
> + return -ENOMEM;
> + }
> + binding = new_binding;
> + binding->group = group;
> + binding->group_id = group_id;
> + binding->state = QOS_IOMMU_BINDING_UPDATING;
> + list_add_tail_rcu(&binding->node, &qos_iommu_bindings);
> + } else {
> + group = binding->group;
> + old_state = READ_ONCE(binding->state);
> + WRITE_ONCE(binding->state, QOS_IOMMU_BINDING_UPDATING);
> + }
Could this make a concurrent paging-domain attachment fail
unnecessarily?
resctrl_arch_devices_write()
iommu_attach_group()
--------------------------------
--------------------------------
WRITE_ONCE(binding->state,
QOS_IOMMU_BINDING_UPDATING);
mutex_lock(&group->mutex);
__iommu_attach_group()
__iommu_group_set_domain()
__iommu_device_set_domain()
riscv_iommu_attach_paging_domain()
riscv_iommu_iodir_update()
riscv_resctrl_iommu_group_get_qosid()
return -EAGAIN
return -EBUSY
mutex_unlock(&group->mutex);
qos_iommu_group_set()
riscv_iommu_group_set_qosid()
iommu_group_update_devices()
mutex_lock(&group->mutex);
riscv_iommu_qosid_validate_dev()
riscv_iommu_qosid_apply_dev()
mutex_unlock(&group->mutex);
Since QOS_IOMMU_BINDING_UPDATING is published before
iommu_group_update_devices() acquires group->mutex, the attachment can
acquire group->mutex first, but still fail with -EBUSY after observing
the transient state.
Should the binding state transition be serialized with the group update
under group->mutex instead?
Thanks,
Yunhui
> +
> + ret = qos_iommu_group_set(group, ids);
> + if (ret) {
> + if (new_binding)
> + qos_iommu_group_free_binding(binding);
> + else
> + WRITE_ONCE(binding->state, old_state);
> + resctrl_last_cmd_printf("Failed to move IOMMU group %d QoS (%d)\n",
> + group_id, ret);
> + return ret;
> + }
> +
> + if (default_ids)
> + qos_iommu_group_free_binding(binding);
> + else
> + WRITE_ONCE(binding->state, qos_iommu_group_ids_pack(ids));
> +
> + return 0;
> +}
> +
> +int riscv_resctrl_iommu_group_get_qosid(struct iommu_group *group, u32 *rcid,
> + u32 *mcid)
> +{
> + struct qos_iommu_group_binding *binding;
> + int ret = -ENOENT;
> +
> + if (!group)
> + return -ENODEV;
> +
> + rcu_read_lock();
> + list_for_each_entry_rcu(binding, &qos_iommu_bindings, node) {
> + struct resctrl_group_ids ids;
> + u64 state;
> +
> + if (binding->group != group)
> + continue;
> + state = READ_ONCE(binding->state);
> + if (state == QOS_IOMMU_BINDING_UPDATING) {
> + ret = -EAGAIN;
> + break;
> + }
> +
> + ids = qos_iommu_group_ids_unpack(state);
> + if (rcid)
> + *rcid = ids.closid;
> + if (mcid)
> + *mcid = ids.rmid;
> + ret = 0;
> + break;
> + }
> + rcu_read_unlock();
> +
> + return ret;
> +}
> +
> +void resctrl_arch_devices_show(struct seq_file *s, struct resctrl_group_ids ids)
> +{
> + struct qos_iommu_group_binding *binding;
> +
> + qos_iommu_group_prune_inactive();
> + list_for_each_entry(binding, &qos_iommu_bindings, node) {
> + if (qos_iommu_group_ids_match(READ_ONCE(binding->state), ids))
> + seq_printf(s, "iommu_group:%d\n", binding->group_id);
> + }
> +}
> +
> +bool resctrl_arch_devices_assigned(struct resctrl_group_ids ids)
> +{
> + struct qos_iommu_group_binding *binding;
> +
> + qos_iommu_group_prune_inactive();
> + list_for_each_entry(binding, &qos_iommu_bindings, node) {
> + if (qos_iommu_group_ids_match(READ_ONCE(binding->state), ids))
> + return true;
> + }
> +
> + return false;
> +}
> +
> +int resctrl_arch_devices_reset_all(struct resctrl_group_ids default_ids)
> +{
> + struct qos_iommu_group_binding *binding;
> + struct qos_iommu_group_binding *tmp;
> + u64 old_state;
> + int first_ret = 0;
> + int ret;
> +
> + qos_iommu_group_prune_inactive();
> + list_for_each_entry_safe(binding, tmp, &qos_iommu_bindings, node) {
> + old_state = READ_ONCE(binding->state);
> + WRITE_ONCE(binding->state, QOS_IOMMU_BINDING_UPDATING);
> + ret = qos_iommu_group_set(binding->group, default_ids);
> + if (ret) {
> + WRITE_ONCE(binding->state, old_state);
> + if (!first_ret)
> + first_ret = ret;
> + resctrl_last_cmd_printf("Failed to reset IOMMU group %d QoS (%d)\n",
> + binding->group_id, ret);
> + continue;
> + }
> +
> + qos_iommu_group_free_binding(binding);
> + }
> +
> + return first_ret;
> +}
> --
> 2.50.1 (Apple Git-155)
>