* [RFC PATCH 1/7] iommu: Add group lookup by ID
2026-07-14 13:06 [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment Zhanpeng Zhang
@ 2026-07-14 13:06 ` Zhanpeng Zhang
2026-07-14 14:03 ` Jason Gunthorpe
2026-07-14 13:06 ` [RFC PATCH 2/7] iommu: Add checked group device update helper Zhanpeng Zhang
` (5 subsequent siblings)
6 siblings, 1 reply; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
linux-kselftest, x86
Add iommu_group_get_by_id() so callers can resolve an IOMMU group from
the numeric ID used in /sys/kernel/iommu_groups.
An ID lookup must keep the group object alive without also keeping an
otherwise empty group active. Embed the devices kobject in struct
iommu_group so its address remains valid until the parent group is
released, and return a reference on the parent kobject to ID lookup
callers. Add iommu_group_put_by_id() to release that reference and
iommu_group_is_active() to detect when the devices kobject has become
inactive.
Serialize lookup against group teardown with iommu_group_kset_mutex and
only return groups whose devices kobject still has a live reference.
This prevents a concurrent lookup from dereferencing a stale child
kobject while allowing external users to discard bindings to empty
groups.
Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
drivers/iommu/iommu.c | 107 +++++++++++++++++++++++++++++++++++++-----
include/linux/iommu.h | 17 +++++++
2 files changed, 113 insertions(+), 11 deletions(-)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index e8f13dcebbde..da269d10f6bf 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -40,6 +40,7 @@
#include "iommu-priv.h"
static struct kset *iommu_group_kset;
+static DEFINE_MUTEX(iommu_group_kset_mutex);
static DEFINE_IDA(iommu_group_ida);
static DEFINE_IDA(iommu_global_pasid_ida);
@@ -52,7 +53,8 @@ enum { IOMMU_PASID_ARRAY_DOMAIN = 0, IOMMU_PASID_ARRAY_HANDLE = 1 };
struct iommu_group {
struct kobject kobj;
- struct kobject *devices_kobj;
+ /* Embedded so it remains addressable until the parent group is released. */
+ struct kobject devices_kobj;
struct list_head devices;
struct xarray pasid_array;
struct mutex mutex;
@@ -729,7 +731,7 @@ static void __iommu_group_free_device(struct iommu_group *group,
{
struct device *dev = grp_dev->dev;
- sysfs_remove_link(group->devices_kobj, grp_dev->name);
+ sysfs_remove_link(&group->devices_kobj, grp_dev->name);
sysfs_remove_link(&dev->kobj, "iommu_group");
trace_remove_device_from_group(group->id, dev);
@@ -1058,6 +1060,14 @@ static const struct kobj_type iommu_group_ktype = {
.release = iommu_group_release,
};
+static void iommu_group_devices_release(struct kobject *kobj)
+{
+}
+
+static const struct kobj_type iommu_group_devices_ktype = {
+ .release = iommu_group_devices_release,
+};
+
/**
* iommu_group_alloc - Allocate a new group
*
@@ -1091,17 +1101,22 @@ struct iommu_group *iommu_group_alloc(void)
}
group->id = ret;
+ mutex_lock(&iommu_group_kset_mutex);
ret = kobject_init_and_add(&group->kobj, &iommu_group_ktype,
NULL, "%d", group->id);
if (ret) {
kobject_put(&group->kobj);
+ mutex_unlock(&iommu_group_kset_mutex);
return ERR_PTR(ret);
}
- group->devices_kobj = kobject_create_and_add("devices", &group->kobj);
- if (!group->devices_kobj) {
+ kobject_init(&group->devices_kobj, &iommu_group_devices_ktype);
+ ret = kobject_add(&group->devices_kobj, &group->kobj, "devices");
+ if (ret) {
+ kobject_put(&group->devices_kobj);
kobject_put(&group->kobj); /* triggers .release & free */
- return ERR_PTR(-ENOMEM);
+ mutex_unlock(&iommu_group_kset_mutex);
+ return ERR_PTR(ret);
}
/*
@@ -1114,15 +1129,18 @@ struct iommu_group *iommu_group_alloc(void)
ret = iommu_group_create_file(group,
&iommu_group_attr_reserved_regions);
if (ret) {
- kobject_put(group->devices_kobj);
+ kobject_put(&group->devices_kobj);
+ mutex_unlock(&iommu_group_kset_mutex);
return ERR_PTR(ret);
}
ret = iommu_group_create_file(group, &iommu_group_attr_type);
if (ret) {
- kobject_put(group->devices_kobj);
+ kobject_put(&group->devices_kobj);
+ mutex_unlock(&iommu_group_kset_mutex);
return ERR_PTR(ret);
}
+ mutex_unlock(&iommu_group_kset_mutex);
pr_debug("Allocated group %d\n", group->id);
@@ -1286,7 +1304,7 @@ static struct group_device *iommu_group_alloc_device(struct iommu_group *group,
goto err_remove_link;
}
- ret = sysfs_create_link_nowarn(group->devices_kobj,
+ ret = sysfs_create_link_nowarn(&group->devices_kobj,
&dev->kobj, device->name);
if (ret) {
if (ret == -EEXIST && i >= 0) {
@@ -1431,7 +1449,7 @@ struct iommu_group *iommu_group_get(struct device *dev)
struct iommu_group *group = dev->iommu_group;
if (group)
- kobject_get(group->devices_kobj);
+ kobject_get(&group->devices_kobj);
return group;
}
@@ -1446,7 +1464,7 @@ EXPORT_SYMBOL_GPL(iommu_group_get);
*/
struct iommu_group *iommu_group_ref_get(struct iommu_group *group)
{
- kobject_get(group->devices_kobj);
+ kobject_get(&group->devices_kobj);
return group;
}
EXPORT_SYMBOL_GPL(iommu_group_ref_get);
@@ -1461,10 +1479,77 @@ EXPORT_SYMBOL_GPL(iommu_group_ref_get);
void iommu_group_put(struct iommu_group *group)
{
if (group)
- kobject_put(group->devices_kobj);
+ kobject_put(&group->devices_kobj);
}
EXPORT_SYMBOL_GPL(iommu_group_put);
+/**
+ * iommu_group_get_by_id - Lookup an IOMMU group by its sysfs ID
+ * @id: group ID matching /sys/kernel/iommu_groups/<id>
+ *
+ * Return a group with a reference on its parent kobject, or NULL if no active
+ * group exists for @id. The caller must release the returned group with
+ * iommu_group_put_by_id(). Keeping this reference does not keep an empty
+ * group's devices kobject active.
+ */
+struct iommu_group *iommu_group_get_by_id(int id)
+{
+ struct kobject *group_kobj;
+ struct iommu_group *group = NULL;
+ char name[12];
+
+ if (!iommu_group_kset || id < 0)
+ return NULL;
+
+ snprintf(name, sizeof(name), "%d", id);
+ mutex_lock(&iommu_group_kset_mutex);
+ group_kobj = kset_find_obj(iommu_group_kset, name);
+ if (!group_kobj)
+ goto unlock;
+
+ group = container_of(group_kobj, struct iommu_group, kobj);
+ if (!kobject_get_unless_zero(&group->devices_kobj)) {
+ kobject_put(group_kobj);
+ group = NULL;
+ goto unlock;
+ }
+
+ kobject_put(&group->devices_kobj);
+unlock:
+ mutex_unlock(&iommu_group_kset_mutex);
+ return group;
+}
+EXPORT_SYMBOL_GPL(iommu_group_get_by_id);
+
+/**
+ * iommu_group_put_by_id - Release a group returned by ID lookup
+ * @group: group returned by iommu_group_get_by_id()
+ */
+void iommu_group_put_by_id(struct iommu_group *group)
+{
+ if (group)
+ kobject_put(&group->kobj);
+}
+EXPORT_SYMBOL_GPL(iommu_group_put_by_id);
+
+/**
+ * iommu_group_is_active - Test whether a referenced group can accept devices
+ * @group: referenced IOMMU group
+ *
+ * Return true while the devices kobject still has a live reference. Once the
+ * group loses its last device and external device reference, it cannot become
+ * active again.
+ */
+bool iommu_group_is_active(struct iommu_group *group)
+{
+ if (!group || !kobject_get_unless_zero(&group->devices_kobj))
+ return false;
+
+ kobject_put(&group->devices_kobj);
+ return true;
+}
+EXPORT_SYMBOL_GPL(iommu_group_is_active);
+
/**
* iommu_group_id - Return ID for a group
* @group: the group to ID
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index d20aa6f6863a..e771b4a92f5b 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -989,6 +989,9 @@ extern void iommu_group_remove_device(struct device *dev);
extern int iommu_group_for_each_dev(struct iommu_group *group, void *data,
int (*fn)(struct device *, void *));
extern struct iommu_group *iommu_group_get(struct device *dev);
+struct iommu_group *iommu_group_get_by_id(int id);
+void iommu_group_put_by_id(struct iommu_group *group);
+bool iommu_group_is_active(struct iommu_group *group);
extern struct iommu_group *iommu_group_ref_get(struct iommu_group *group);
extern void iommu_group_put(struct iommu_group *group);
@@ -1401,6 +1404,20 @@ static inline struct iommu_group *iommu_group_get(struct device *dev)
return NULL;
}
+static inline struct iommu_group *iommu_group_get_by_id(int id)
+{
+ return NULL;
+}
+
+static inline void iommu_group_put_by_id(struct iommu_group *group)
+{
+}
+
+static inline bool iommu_group_is_active(struct iommu_group *group)
+{
+ return false;
+}
+
static inline void iommu_group_put(struct iommu_group *group)
{
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [RFC PATCH 1/7] iommu: Add group lookup by ID
2026-07-14 13:06 ` [RFC PATCH 1/7] iommu: Add group lookup by ID Zhanpeng Zhang
@ 2026-07-14 14:03 ` Jason Gunthorpe
2026-07-15 2:54 ` Zhanpeng Zhang
2026-07-15 2:55 ` Zhanpeng Zhang
0 siblings, 2 replies; 11+ messages in thread
From: Jason Gunthorpe @ 2026-07-14 14:03 UTC (permalink / raw)
To: Zhanpeng Zhang
Cc: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach, will,
robin.murphy, fustini, pjw, aou, alex, Dave.Martin, james.morse,
babu.moger, corbet, shuah, kevin.tian, cuiyunhui, yuanzhu, iommu,
linux-riscv, linux-kernel, linux-doc, linux-kselftest, x86
On Tue, Jul 14, 2026 at 09:06:51PM +0800, Zhanpeng Zhang wrote:
> Add iommu_group_get_by_id() so callers can resolve an IOMMU group from
> the numeric ID used in /sys/kernel/iommu_groups.
>
> An ID lookup must keep the group object alive without also keeping an
> otherwise empty group active. Embed the devices kobject in struct
> iommu_group so its address remains valid until the parent group is
> released, and return a reference on the parent kobject to ID lookup
> callers. Add iommu_group_put_by_id() to release that reference and
> iommu_group_is_active() to detect when the devices kobject has become
> inactive.
>
> Serialize lookup against group teardown with iommu_group_kset_mutex and
> only return groups whose devices kobject still has a live reference.
> This prevents a concurrent lookup from dereferencing a stale child
> kobject while allowing external users to discard bindings to empty
> groups.
>
> Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
> ---
> drivers/iommu/iommu.c | 107 +++++++++++++++++++++++++++++++++++++-----
> include/linux/iommu.h | 17 +++++++
> 2 files changed, 113 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
> index e8f13dcebbde..da269d10f6bf 100644
> --- a/drivers/iommu/iommu.c
> +++ b/drivers/iommu/iommu.c
> @@ -40,6 +40,7 @@
> #include "iommu-priv.h"
>
> static struct kset *iommu_group_kset;
> +static DEFINE_MUTEX(iommu_group_kset_mutex);
> static DEFINE_IDA(iommu_group_ida);
I think it would be better to change the ida to an xarray than to use
a string search on a kset..
Jason
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 1/7] iommu: Add group lookup by ID
2026-07-14 14:03 ` Jason Gunthorpe
@ 2026-07-15 2:54 ` Zhanpeng Zhang
2026-07-15 2:55 ` Zhanpeng Zhang
1 sibling, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-15 2:54 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach, will,
robin.murphy, fustini, pjw, aou, alex, Dave.Martin, james.morse,
babu.moger, corbet, shuah, kevin.tian, cuiyunhui, yuanzhu, iommu,
linux-riscv, linux-kernel, linux-doc, linux-kselftest, x86
Hi Jason,
On 7/14/26 10:03 PM, Jason Gunthorpe wrote:
> On Tue, Jul 14, 2026 at 09:06:51PM +0800, Zhanpeng Zhang wrote:
>> Add iommu_group_get_by_id() so callers can resolve an IOMMU group from
>> the numeric ID used in /sys/kernel/iommu_groups.
>>
>> An ID lookup must keep the group object alive without also keeping an
>> otherwise empty group active. Embed the devices kobject in struct
>> iommu_group so its address remains valid until the parent group is
>> released, and return a reference on the parent kobject to ID lookup
>> callers. Add iommu_group_put_by_id() to release that reference and
>> iommu_group_is_active() to detect when the devices kobject has become
>> inactive.
>>
>> Serialize lookup against group teardown with iommu_group_kset_mutex and
>> only return groups whose devices kobject still has a live reference.
>> This prevents a concurrent lookup from dereferencing a stale child
>> kobject while allowing external users to discard bindings to empty
>> groups.
>>
>> Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
>> ---
>> drivers/iommu/iommu.c | 107 +++++++++++++++++++++++++++++++++++++-----
>> include/linux/iommu.h | 17 +++++++
>> 2 files changed, 113 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
>> index e8f13dcebbde..da269d10f6bf 100644
>> --- a/drivers/iommu/iommu.c
>> +++ b/drivers/iommu/iommu.c
>> @@ -40,6 +40,7 @@
>> #include "iommu-priv.h"
>>
>> static struct kset *iommu_group_kset;
>> +static DEFINE_MUTEX(iommu_group_kset_mutex);
>> static DEFINE_IDA(iommu_group_ida);
>
> I think it would be better to change the ida to an xarray than to use
> a string search on a kset..
>
> Jason
Agreed. Using an XArray as both the ID allocator and the group lookup
table avoids the string-based kset lookup and the additional mutex.
I will refactor it in the next revision.
Thanks,
Zhanpeng
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH 1/7] iommu: Add group lookup by ID
2026-07-14 14:03 ` Jason Gunthorpe
2026-07-15 2:54 ` Zhanpeng Zhang
@ 2026-07-15 2:55 ` Zhanpeng Zhang
1 sibling, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-15 2:55 UTC (permalink / raw)
To: Jason Gunthorpe
Cc: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach, will,
robin.murphy, fustini, pjw, aou, alex, Dave.Martin, james.morse,
babu.moger, corbet, shuah, kevin.tian, cuiyunhui, yuanzhu, iommu,
linux-riscv, linux-kernel, linux-doc, linux-kselftest, x86
Hi Jason,
On 7/14/26 10:03 PM, Jason Gunthorpe wrote:
> On Tue, Jul 14, 2026 at 09:06:51PM +0800, Zhanpeng Zhang wrote:
>> Add iommu_group_get_by_id() so callers can resolve an IOMMU group from
>> the numeric ID used in /sys/kernel/iommu_groups.
>>
>> An ID lookup must keep the group object alive without also keeping an
>> otherwise empty group active. Embed the devices kobject in struct
>> iommu_group so its address remains valid until the parent group is
>> released, and return a reference on the parent kobject to ID lookup
>> callers. Add iommu_group_put_by_id() to release that reference and
>> iommu_group_is_active() to detect when the devices kobject has become
>> inactive.
>>
>> Serialize lookup against group teardown with iommu_group_kset_mutex and
>> only return groups whose devices kobject still has a live reference.
>> This prevents a concurrent lookup from dereferencing a stale child
>> kobject while allowing external users to discard bindings to empty
>> groups.
>>
>> Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
>> ---
>> drivers/iommu/iommu.c | 107 +++++++++++++++++++++++++++++++++++++-----
>> include/linux/iommu.h | 17 +++++++
>> 2 files changed, 113 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
>> index e8f13dcebbde..da269d10f6bf 100644
>> --- a/drivers/iommu/iommu.c
>> +++ b/drivers/iommu/iommu.c
>> @@ -40,6 +40,7 @@
>> #include "iommu-priv.h"
>>
>> static struct kset *iommu_group_kset;
>> +static DEFINE_MUTEX(iommu_group_kset_mutex);
>> static DEFINE_IDA(iommu_group_ida);
>
> I think it would be better to change the ida to an xarray than to use
> a string search on a kset..
>
> Jason
Agreed. Using an XArray as both the ID allocator and the group lookup
table avoids the string-based kset lookup and the additional mutex.
I will refactor it in the next revision.
Thanks,
Zhanpeng
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH 2/7] iommu: Add checked group device update helper
2026-07-14 13:06 [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 1/7] iommu: Add group lookup by ID Zhanpeng Zhang
@ 2026-07-14 13:06 ` Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 3/7] resctrl: Add a devices file for external requester assignment Zhanpeng Zhang
` (4 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
linux-kselftest, x86
Some group-wide operations must validate every member before changing
any device. Separate iommu_group_for_each_dev() calls cannot provide
that guarantee because group membership may change between traversals.
Add iommu_group_update_devices() to keep the group membership mutex held
across a validation pass and a non-failing update pass. This provides
all-or-none validation without exposing IOMMU group internals to
callers.
Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
drivers/iommu/iommu.c | 33 +++++++++++++++++++++++++++++++++
include/linux/iommu.h | 13 +++++++++++++
2 files changed, 46 insertions(+)
diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index da269d10f6bf..9a6c4a7e7df6 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -1436,6 +1436,39 @@ int iommu_group_for_each_dev(struct iommu_group *group, void *data,
}
EXPORT_SYMBOL_GPL(iommu_group_for_each_dev);
+/**
+ * iommu_group_update_devices - Check and update every device in a group
+ * @group: the group
+ * @data: caller data passed to both callbacks
+ * @check: validates whether one device can be updated
+ * @update: updates one device after every check has succeeded
+ *
+ * Keep group membership stable while first checking every device and then
+ * applying an update which cannot fail. No device is updated if a check fails.
+ */
+int iommu_group_update_devices(struct iommu_group *group, void *data,
+ int (*check)(struct device *, void *),
+ void (*update)(struct device *, void *))
+{
+ struct group_device *device;
+ int ret = 0;
+
+ mutex_lock(&group->mutex);
+ for_each_group_device(group, device) {
+ ret = check(device->dev, data);
+ if (ret)
+ goto unlock;
+ }
+
+ for_each_group_device(group, device)
+ update(device->dev, data);
+
+unlock:
+ mutex_unlock(&group->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(iommu_group_update_devices);
+
/**
* iommu_group_get - Return the group for a device and increment reference
* @dev: get the group that this device belongs to
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index e771b4a92f5b..befba0683e06 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -988,6 +988,9 @@ extern int iommu_group_add_device(struct iommu_group *group,
extern void iommu_group_remove_device(struct device *dev);
extern int iommu_group_for_each_dev(struct iommu_group *group, void *data,
int (*fn)(struct device *, void *));
+int iommu_group_update_devices(struct iommu_group *group, void *data,
+ int (*check)(struct device *, void *),
+ void (*update)(struct device *, void *));
extern struct iommu_group *iommu_group_get(struct device *dev);
struct iommu_group *iommu_group_get_by_id(int id);
void iommu_group_put_by_id(struct iommu_group *group);
@@ -1399,6 +1402,16 @@ static inline int iommu_group_for_each_dev(struct iommu_group *group,
return -ENODEV;
}
+static inline int iommu_group_update_devices(struct iommu_group *group,
+ void *data,
+ int (*check)(struct device *,
+ void *),
+ void (*update)(struct device *,
+ void *))
+{
+ return -ENODEV;
+}
+
static inline struct iommu_group *iommu_group_get(struct device *dev)
{
return NULL;
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC PATCH 3/7] resctrl: Add a devices file for external requester assignment
2026-07-14 13:06 [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 1/7] iommu: Add group lookup by ID Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 2/7] iommu: Add checked group device update helper Zhanpeng Zhang
@ 2026-07-14 13:06 ` Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 4/7] iommu/riscv: Program QoS IDs for assigned groups Zhanpeng Zhang
` (3 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
linux-kselftest, x86
Do not overload the resctrl tasks file with architecture-specific
non-PID tokens. The tasks ABI remains a list of task IDs, while the new
devices file carries external requesters assigned to a resctrl group.
Add architecture hooks for assigning and showing those external
objects, and reject group removal, reparenting, or pseudo-lock setup
while devices are still assigned. The teardown path performs a
best-effort reset to the default group.
The devices file is an assignment interface only. It does not describe
a new resource schema or domain; resource allocation and monitoring
policy remain described by the existing schemata and info files.
Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
Documentation/filesystems/resctrl.rst | 26 ++++
arch/Kconfig | 6 +
fs/resctrl/rdtgroup.c | 206 +++++++++++++++++++++++++-
include/linux/resctrl.h | 45 ++++++
4 files changed, 280 insertions(+), 3 deletions(-)
diff --git a/Documentation/filesystems/resctrl.rst b/Documentation/filesystems/resctrl.rst
index e4b66af55ffb..fce61d019114 100644
--- a/Documentation/filesystems/resctrl.rst
+++ b/Documentation/filesystems/resctrl.rst
@@ -581,6 +581,32 @@ All groups contain the following files:
idle tasks. Instead, a CPU's idle task is always considered as a
member of the group owning the CPU.
+"devices":
+ On architectures that support external requester assignment through
+ resctrl, reading this file shows the devices or device groups assigned
+ to this resource group. Writing an architecture-specific device token
+ moves that external requester to the group. Multiple tokens can be
+ separated by commas and are processed sequentially. A failure aborts the
+ write, but requesters moved before the failure remain in their new groups.
+
+ On RISC-V, an IOMMU group is identified by the following token::
+
+ iommu_group:<group-id>
+
+ Each assigned IOMMU group is reported on a separate line when the file is
+ read. Writing the token to the root control group's devices file restores
+ the IOMMU group to the reserved default QoS IDs. Default assignments are
+ not listed in the root devices file.
+
+ This file only controls external requester membership. Resource
+ allocation and monitoring policy remains described by schemata and
+ info files.
+
+ Resource groups with assigned devices cannot be removed or reparented.
+ Move devices to another group first.
+
+ Failures will be logged to /sys/fs/resctrl/info/last_cmd_status.
+
"cpus":
Reading this file shows a bitmask of the logical CPUs owned by
this group. Writing a mask to this file will add and remove
diff --git a/arch/Kconfig b/arch/Kconfig
index fa7507ac8e13..36a4f4fbb164 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -1615,6 +1615,12 @@ config ARCH_HAS_CPU_RESCTRL
monitoring and control interfaces provided by the 'resctrl'
filesystem (see RESCTRL_FS).
+config ARCH_HAS_RESCTRL_DEVICES
+ bool
+ help
+ An architecture selects this option to indicate that external
+ device objects can be attached to resctrl resource groups.
+
config HAVE_ARCH_COMPILER_H
bool
help
diff --git a/fs/resctrl/rdtgroup.c b/fs/resctrl/rdtgroup.c
index af2cbab14497..2e424c911049 100644
--- a/fs/resctrl/rdtgroup.c
+++ b/fs/resctrl/rdtgroup.c
@@ -99,13 +99,32 @@ void rdt_last_cmd_puts(const char *s)
seq_buf_puts(&last_cmd_status, s);
}
+void resctrl_last_cmd_puts(const char *s)
+{
+ rdt_last_cmd_puts(s);
+}
+
+static void rdt_last_cmd_vprintf(const char *fmt, va_list ap)
+{
+ lockdep_assert_held(&rdtgroup_mutex);
+ seq_buf_vprintf(&last_cmd_status, fmt, ap);
+}
+
void rdt_last_cmd_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
- lockdep_assert_held(&rdtgroup_mutex);
- seq_buf_vprintf(&last_cmd_status, fmt, ap);
+ rdt_last_cmd_vprintf(fmt, ap);
+ va_end(ap);
+}
+
+void resctrl_last_cmd_printf(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ rdt_last_cmd_vprintf(fmt, ap);
va_end(ap);
}
@@ -766,6 +785,151 @@ static int rdtgroup_move_task(pid_t pid, struct rdtgroup *rdtgrp,
return ret;
}
+static bool rdtgroup_effective_ids(struct rdtgroup *r,
+ struct resctrl_group_ids *ids)
+{
+ if (!r || !ids)
+ return false;
+
+ if (r->type == RDTMON_GROUP)
+ ids->closid = r->mon.parent->closid;
+ else if (r->type == RDTCTRL_GROUP)
+ ids->closid = r->closid;
+ else
+ return false;
+
+ ids->rmid = r->mon.rmid;
+ return true;
+}
+
+#ifdef CONFIG_ARCH_HAS_RESCTRL_DEVICES
+static void show_rdt_devices(struct rdtgroup *r, struct seq_file *s)
+{
+ struct resctrl_group_ids ids;
+
+ if (rdtgroup_effective_ids(r, &ids))
+ resctrl_arch_devices_show(s, ids);
+}
+
+static int rdtgroup_reset_all_devices(struct rdtgroup *to)
+{
+ struct resctrl_group_ids default_ids;
+
+ if (!to || !rdtgroup_effective_ids(to, &default_ids))
+ return 0;
+
+ return resctrl_arch_devices_reset_all(default_ids);
+}
+
+static bool rdtgroup_arch_devices_assigned(struct rdtgroup *rdtgrp)
+{
+ struct resctrl_group_ids ids;
+
+ if (!rdtgroup_effective_ids(rdtgrp, &ids))
+ return false;
+
+ return resctrl_arch_devices_assigned(ids);
+}
+
+static bool rdtgroup_child_arch_devices_assigned(struct rdtgroup *rdtgrp)
+{
+ struct rdtgroup *crgrp;
+
+ list_for_each_entry(crgrp, &rdtgrp->mon.crdtgrp_list, mon.crdtgrp_list) {
+ if (rdtgroup_arch_devices_assigned(crgrp))
+ return true;
+ }
+
+ return false;
+}
+
+static int rdtgroup_reject_assigned_devices(struct rdtgroup *rdtgrp,
+ bool include_children,
+ const char *operation)
+{
+ if (!rdtgroup_arch_devices_assigned(rdtgrp) &&
+ (!include_children || !rdtgroup_child_arch_devices_assigned(rdtgrp)))
+ return 0;
+
+ rdt_last_cmd_printf("Move devices out before %s group\n", operation);
+ return -EBUSY;
+}
+
+static ssize_t rdtgroup_devices_write(struct kernfs_open_file *of,
+ char *buf, size_t nbytes, loff_t off)
+{
+ struct resctrl_group_ids ids;
+ struct rdtgroup *rdtgrp;
+ char *tok;
+ int ret = 0;
+
+ rdtgrp = rdtgroup_kn_lock_live(of->kn);
+ if (!rdtgrp) {
+ rdtgroup_kn_unlock(of->kn);
+ return -ENOENT;
+ }
+ rdt_last_cmd_clear();
+
+ if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED ||
+ rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP) {
+ ret = -EINVAL;
+ rdt_last_cmd_puts("Pseudo-locking in progress\n");
+ goto unlock;
+ }
+
+ if (!rdtgroup_effective_ids(rdtgrp, &ids)) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ while ((tok = strsep(&buf, ","))) {
+ tok = strim(tok);
+ if (!*tok) {
+ rdt_last_cmd_puts("Device list parsing error\n");
+ ret = -EINVAL;
+ break;
+ }
+
+ ret = resctrl_arch_devices_write(tok, ids);
+ if (ret)
+ break;
+ }
+
+unlock:
+ rdtgroup_kn_unlock(of->kn);
+
+ return ret ?: nbytes;
+}
+
+static int rdtgroup_devices_show(struct kernfs_open_file *of,
+ struct seq_file *s, void *v)
+{
+ struct rdtgroup *rdtgrp;
+ int ret = 0;
+
+ rdtgrp = rdtgroup_kn_lock_live(of->kn);
+ if (rdtgrp)
+ show_rdt_devices(rdtgrp, s);
+ else
+ ret = -ENOENT;
+ rdtgroup_kn_unlock(of->kn);
+
+ return ret;
+}
+#else
+static inline int rdtgroup_reset_all_devices(struct rdtgroup *to)
+{
+ return 0;
+}
+
+static inline int rdtgroup_reject_assigned_devices(struct rdtgroup *rdtgrp,
+ bool include_children,
+ const char *operation)
+{
+ return 0;
+}
+#endif
+
static ssize_t rdtgroup_tasks_write(struct kernfs_open_file *of,
char *buf, size_t nbytes, loff_t off)
{
@@ -1491,6 +1655,11 @@ static ssize_t rdtgroup_mode_write(struct kernfs_open_file *of,
rdtgrp->mode = RDT_MODE_EXCLUSIVE;
} else if (IS_ENABLED(CONFIG_RESCTRL_FS_PSEUDO_LOCK) &&
!strcmp(buf, "pseudo-locksetup")) {
+ ret = rdtgroup_reject_assigned_devices(rdtgrp, true,
+ "entering pseudo-locksetup");
+ if (ret)
+ goto out;
+
ret = rdtgroup_locksetup_enter(rdtgrp);
if (ret)
goto out;
@@ -2067,6 +2236,16 @@ static struct rftype res_common_files[] = {
.seq_show = rdtgroup_tasks_show,
.fflags = RFTYPE_BASE,
},
+#ifdef CONFIG_ARCH_HAS_RESCTRL_DEVICES
+ {
+ .name = "devices",
+ .mode = 0644,
+ .kf_ops = &rdtgroup_kf_single_ops,
+ .write = rdtgroup_devices_write,
+ .seq_show = rdtgroup_devices_show,
+ .fflags = RFTYPE_BASE,
+ },
+#endif
{
.name = "mon_hw_id",
.mode = 0444,
@@ -3061,6 +3240,8 @@ static void rmdir_all_sub(void)
/* Move all tasks to the default resource group */
rdt_move_group_tasks(NULL, &rdtgroup_default, NULL);
+ WARN_ON_ONCE(rdtgroup_reset_all_devices(&rdtgroup_default));
+
list_for_each_entry_safe(rdtgrp, tmp, &rdt_all_groups, rdtgroup_list) {
/* Free any child rmids */
free_all_child_rdtgrp(rdtgrp);
@@ -3970,6 +4151,11 @@ static int rdtgroup_rmdir_mon(struct rdtgroup *rdtgrp, cpumask_var_t tmpmask)
struct rdtgroup *prdtgrp = rdtgrp->mon.parent;
u32 closid, rmid;
int cpu;
+ int ret;
+
+ ret = rdtgroup_reject_assigned_devices(rdtgrp, false, "removing");
+ if (ret)
+ return ret;
/* Give any tasks back to the parent group */
rdt_move_group_tasks(rdtgrp, prdtgrp, tmpmask);
@@ -4020,6 +4206,11 @@ static int rdtgroup_rmdir_ctrl(struct rdtgroup *rdtgrp, cpumask_var_t tmpmask)
{
u32 closid, rmid;
int cpu;
+ int ret;
+
+ ret = rdtgroup_reject_assigned_devices(rdtgrp, true, "removing");
+ if (ret)
+ return ret;
/* Give any tasks back to the default group */
rdt_move_group_tasks(rdtgrp, &rdtgroup_default, tmpmask);
@@ -4093,7 +4284,10 @@ static int rdtgroup_rmdir(struct kernfs_node *kn)
rdtgrp != &rdtgroup_default) {
if (rdtgrp->mode == RDT_MODE_PSEUDO_LOCKSETUP ||
rdtgrp->mode == RDT_MODE_PSEUDO_LOCKED) {
- ret = rdtgroup_ctrl_remove(rdtgrp);
+ ret = rdtgroup_reject_assigned_devices(rdtgrp, true,
+ "removing");
+ if (!ret)
+ ret = rdtgroup_ctrl_remove(rdtgrp);
} else {
ret = rdtgroup_rmdir_ctrl(rdtgrp, tmpmask);
}
@@ -4210,6 +4404,12 @@ static int rdtgroup_rename(struct kernfs_node *kn,
goto out;
}
+ if (rdtgrp->mon.parent != new_prdtgrp) {
+ ret = rdtgroup_reject_assigned_devices(rdtgrp, false, "reparenting");
+ if (ret)
+ goto out;
+ }
+
/*
* Allocate the cpumask for use in mongrp_reparent() to avoid the
* possibility of failing to allocate it after kernfs_rename() has
diff --git a/include/linux/resctrl.h b/include/linux/resctrl.h
index 73ff522448a0..a4b5c2d5e814 100644
--- a/include/linux/resctrl.h
+++ b/include/linux/resctrl.h
@@ -8,6 +8,8 @@
#include <linux/pid.h>
#include <linux/resctrl_types.h>
+struct seq_file;
+
#ifdef CONFIG_ARCH_HAS_CPU_RESCTRL
#include <asm/resctrl.h>
#endif
@@ -18,6 +20,49 @@
#define RESCTRL_PICK_ANY_CPU -1
+/**
+ * struct resctrl_group_ids - resctrl control and monitoring IDs
+ * @closid: resource control class ID
+ * @rmid: resource monitoring ID
+ */
+struct resctrl_group_ids {
+ u32 closid;
+ u32 rmid;
+};
+
+void resctrl_last_cmd_puts(const char *s);
+void resctrl_last_cmd_printf(const char *fmt, ...) __printf(1, 2);
+
+#ifdef CONFIG_ARCH_HAS_RESCTRL_DEVICES
+int resctrl_arch_devices_write(char *tok, struct resctrl_group_ids ids);
+void resctrl_arch_devices_show(struct seq_file *s,
+ struct resctrl_group_ids ids);
+bool resctrl_arch_devices_assigned(struct resctrl_group_ids ids);
+int resctrl_arch_devices_reset_all(struct resctrl_group_ids default_ids);
+#else
+static inline int resctrl_arch_devices_write(char *tok,
+ struct resctrl_group_ids ids)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void resctrl_arch_devices_show(struct seq_file *s,
+ struct resctrl_group_ids ids)
+{
+}
+
+static inline bool resctrl_arch_devices_assigned(struct resctrl_group_ids ids)
+{
+ return false;
+}
+
+static inline int
+resctrl_arch_devices_reset_all(struct resctrl_group_ids default_ids)
+{
+ return 0;
+}
+#endif
+
#ifdef CONFIG_PROC_CPU_RESCTRL
int proc_resctrl_show(struct seq_file *m,
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC PATCH 4/7] iommu/riscv: Program QoS IDs for assigned groups
2026-07-14 13:06 [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment Zhanpeng Zhang
` (2 preceding siblings ...)
2026-07-14 13:06 ` [RFC PATCH 3/7] resctrl: Add a devices file for external requester assignment Zhanpeng Zhang
@ 2026-07-14 13:06 ` Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 5/7] iommu/riscv: Expose global QoS IDs in sysfs Zhanpeng Zhang
` (2 subsequent siblings)
6 siblings, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
linux-kselftest, x86
Program RCID and MCID for RISC-V IOMMU groups through the device context
TA fields. The resctrl group assignment is per device group, so reject
BARE mode where only the per-IOMMU iommu_qosid global default is
available.
Validate every group member, firmware ID, device context, field value,
and QoS ID capability before changing hardware. Then update all members
through the checked IOMMU group helper so a validation failure leaves the
group unchanged.
Serialize DC.ta changes with context setup under qosid_lock. Change only
the RCID and MCID fields with ordinary accesses so fixed DDT mappings are
not subject to atomic LR/SC operations, invalidate active device contexts
after an update, and clear the IDs when a device is released.
Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
arch/riscv/include/asm/qos.h | 16 +++
drivers/iommu/riscv/iommu-bits.h | 15 +++
drivers/iommu/riscv/iommu.c | 200 ++++++++++++++++++++++++++++++-
drivers/iommu/riscv/iommu.h | 3 +
4 files changed, 232 insertions(+), 2 deletions(-)
diff --git a/arch/riscv/include/asm/qos.h b/arch/riscv/include/asm/qos.h
index cf19e8438bb9..daa758d4efff 100644
--- a/arch/riscv/include/asm/qos.h
+++ b/arch/riscv/include/asm/qos.h
@@ -2,7 +2,23 @@
#ifndef _ASM_RISCV_QOS_H
#define _ASM_RISCV_QOS_H
+#include <linux/errno.h>
#include <linux/percpu-defs.h>
+#include <linux/types.h>
+
+struct iommu_group;
+
+#ifdef CONFIG_RISCV_IOMMU
+int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
+ u32 mcid);
+#else
+static inline int riscv_iommu_group_set_qosid(struct iommu_group *group,
+ u32 rcid, u32 mcid)
+{
+ return -EOPNOTSUPP;
+}
+
+#endif
#ifdef CONFIG_RISCV_ISA_SSQOSID
diff --git a/drivers/iommu/riscv/iommu-bits.h b/drivers/iommu/riscv/iommu-bits.h
index f2ef9bd3cde9..782de5c92727 100644
--- a/drivers/iommu/riscv/iommu-bits.h
+++ b/drivers/iommu/riscv/iommu-bits.h
@@ -63,6 +63,7 @@
#define RISCV_IOMMU_CAPABILITIES_PD8 BIT_ULL(38)
#define RISCV_IOMMU_CAPABILITIES_PD17 BIT_ULL(39)
#define RISCV_IOMMU_CAPABILITIES_PD20 BIT_ULL(40)
+#define RISCV_IOMMU_CAPABILITIES_QOSID BIT_ULL(41)
#define RISCV_IOMMU_CAPABILITIES_NL BIT_ULL(42)
#define RISCV_IOMMU_CAPABILITIES_S BIT_ULL(43)
@@ -274,6 +275,14 @@ enum riscv_iommu_hpmevent_id {
#define RISCV_IOMMU_TR_RESPONSE_SZ BIT_ULL(9)
#define RISCV_IOMMU_TR_RESPONSE_PPN RISCV_IOMMU_PPN_FIELD
+/* 6.27 IOMMU QoS IDs for IOMMU-initiated requests (32bits) */
+#define RISCV_IOMMU_REG_IOMMU_QOSID 0x0270
+#define RISCV_IOMMU_IOMMU_QOSID_RCID GENMASK(11, 0)
+#define RISCV_IOMMU_IOMMU_QOSID_MCID GENMASK(27, 16)
+
+#define RISCV_IOMMU_IOMMU_QOSID_RCID_SHIFT 0
+#define RISCV_IOMMU_IOMMU_QOSID_MCID_SHIFT 16
+
/* 5.27 Interrupt cause to vector (64bits) */
#define RISCV_IOMMU_REG_ICVEC 0x02F8
#define RISCV_IOMMU_ICVEC_CIV GENMASK_ULL(3, 0)
@@ -371,6 +380,12 @@ enum riscv_iommu_dc_iohgatp_modes {
/* Translation attributes fields */
#define RISCV_IOMMU_DC_TA_PSCID GENMASK_ULL(31, 12)
+/*
+ * QoS IDs for translated device requests and IOMMU accesses with a
+ * device context (when capabilities.QOSID == 1).
+ */
+#define RISCV_IOMMU_DC_TA_RCID GENMASK_ULL(51, 40)
+#define RISCV_IOMMU_DC_TA_MCID GENMASK_ULL(63, 52)
/* First-stage context fields */
#define RISCV_IOMMU_DC_FSC_PPN RISCV_IOMMU_ATP_PPN_FIELD
diff --git a/drivers/iommu/riscv/iommu.c b/drivers/iommu/riscv/iommu.c
index cec3ddd7ab10..deab646bb1ea 100644
--- a/drivers/iommu/riscv/iommu.c
+++ b/drivers/iommu/riscv/iommu.c
@@ -48,6 +48,8 @@
static DEFINE_IDA(riscv_iommu_pscids);
#define RISCV_IOMMU_MAX_PSCID (BIT(20) - 1)
+static const struct iommu_ops riscv_iommu_ops;
+
/* Device resource-managed allocations */
struct riscv_iommu_devres {
void *addr;
@@ -1091,6 +1093,28 @@ static void riscv_iommu_iotlb_inval(struct riscv_iommu_domain *domain,
}
#define RISCV_IOMMU_FSC_BARE 0
+#define RISCV_IOMMU_DC_TA_QOSID \
+ (RISCV_IOMMU_DC_TA_RCID | RISCV_IOMMU_DC_TA_MCID)
+
+static u64 riscv_iommu_qosid_ta(u32 rcid, u32 mcid)
+{
+ return FIELD_PREP(RISCV_IOMMU_DC_TA_RCID, rcid) |
+ FIELD_PREP(RISCV_IOMMU_DC_TA_MCID, mcid);
+}
+
+static void riscv_iommu_dc_update_qosid(struct riscv_iommu_device *iommu,
+ struct riscv_iommu_dc *dc,
+ u32 rcid, u32 mcid)
+{
+ u64 qos_ta = riscv_iommu_qosid_ta(rcid, mcid);
+ u64 ta;
+
+ lockdep_assert_held(&iommu->qosid_lock);
+ ta = READ_ONCE(dc->ta);
+ ta = (ta & ~RISCV_IOMMU_DC_TA_QOSID) | qos_ta;
+ WRITE_ONCE(dc->ta, ta);
+}
+
/*
* This function sends IOTINVAL commands as required by the RISC-V
* IOMMU specification (Section 6.3.1 and 6.3.2 in 1.0 spec version)
@@ -1202,12 +1226,23 @@ static void riscv_iommu_iodir_update(struct riscv_iommu_device *iommu,
* is stored as DC_TC_V bit (both sharing the same location at BIT(0)).
*/
for (i = 0; i < fwspec->num_ids; i++) {
+ u64 dc_ta;
+ u64 ta_mask = RISCV_IOMMU_PC_TA_PSCID;
+
dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
tc = READ_ONCE(dc->tc);
- tc |= ta & RISCV_IOMMU_DC_TC_V;
+ 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;
+ }
+ tc |= dc_ta & RISCV_IOMMU_DC_TC_V;
WRITE_ONCE(dc->fsc, fsc);
- WRITE_ONCE(dc->ta, ta & RISCV_IOMMU_PC_TA_PSCID);
+ WRITE_ONCE(dc->ta, dc_ta & ta_mask);
/* Update device context, write TC.V as the last step. */
dma_wmb();
WRITE_ONCE(dc->tc, tc);
@@ -1474,13 +1509,174 @@ static struct iommu_device *riscv_iommu_probe_device(struct device *dev)
return &iommu->iommu;
}
+static void riscv_iommu_qosid_invalidate_did(struct riscv_iommu_device *iommu,
+ unsigned int did)
+{
+ struct riscv_iommu_command cmd;
+
+ riscv_iommu_cmd_iodir_inval_ddt(&cmd);
+ riscv_iommu_cmd_iodir_set_did(&cmd, did);
+ riscv_iommu_cmd_send(iommu, &cmd);
+}
+
static void riscv_iommu_release_device(struct device *dev)
{
struct riscv_iommu_info *info = dev_iommu_priv_get(dev);
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct riscv_iommu_device *iommu = dev_to_iommu(dev);
+ bool sync_required = false;
+ unsigned int i;
+
+ if (iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID) {
+ mutex_lock(&iommu->qosid_lock);
+ for (i = 0; fwspec && i < fwspec->num_ids; i++) {
+ struct riscv_iommu_dc *dc;
+ u64 tc;
+
+ dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
+ if (!dc)
+ continue;
+
+ tc = READ_ONCE(dc->tc);
+ riscv_iommu_dc_update_qosid(iommu, dc, 0, 0);
+ if (!(tc & RISCV_IOMMU_DC_TC_V))
+ continue;
+
+ dma_wmb();
+ riscv_iommu_qosid_invalidate_did(iommu, fwspec->ids[i]);
+ riscv_iommu_iodir_iotinval(iommu, false, dc->iohgatp,
+ dc, NULL);
+ sync_required = true;
+ }
+
+ if (sync_required)
+ riscv_iommu_cmd_sync(iommu,
+ RISCV_IOMMU_IOTINVAL_TIMEOUT);
+ mutex_unlock(&iommu->qosid_lock);
+ }
kfree_rcu_mightsleep(info);
}
+struct riscv_iommu_qosid_hw_ctx {
+ u32 rcid;
+ u32 mcid;
+ bool has_devices;
+ bool has_qosid;
+ bool reset;
+};
+
+static int riscv_iommu_qosid_validate_dev(struct device *dev, void *data)
+{
+ struct riscv_iommu_qosid_hw_ctx *ctx = data;
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct riscv_iommu_device *iommu;
+ unsigned int i;
+
+ ctx->has_devices = true;
+
+ if (!dev->iommu || !dev->iommu->iommu_dev ||
+ dev->iommu->iommu_dev->ops != &riscv_iommu_ops)
+ return -EOPNOTSUPP;
+
+ if (!fwspec || !fwspec->num_ids)
+ return -ENODEV;
+
+ iommu = dev_to_iommu(dev);
+
+ if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+ return ctx->reset ? 0 : -EOPNOTSUPP;
+
+ ctx->has_qosid = true;
+
+ /*
+ * IOMMU group QoS is a per-device assignment. BARE mode only has the
+ * per-IOMMU iommu_qosid register, which is a global default rather
+ * than a safe target for moving an individual group between resctrl
+ * groups.
+ */
+ if (iommu->ddt_mode <= RISCV_IOMMU_DDTP_IOMMU_MODE_BARE)
+ return -EOPNOTSUPP;
+
+ for (i = 0; i < fwspec->num_ids; i++) {
+ if (!riscv_iommu_get_dc(iommu, fwspec->ids[i]))
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void riscv_iommu_qosid_apply_dev(struct device *dev, void *data)
+{
+ struct riscv_iommu_qosid_hw_ctx *ctx = data;
+ struct iommu_fwspec *fwspec = dev_iommu_fwspec_get(dev);
+ struct riscv_iommu_device *iommu;
+ bool sync_required = false;
+ unsigned int i;
+
+ iommu = dev_to_iommu(dev);
+ if (!(iommu->caps & RISCV_IOMMU_CAPABILITIES_QOSID))
+ return;
+
+ mutex_lock(&iommu->qosid_lock);
+ for (i = 0; i < fwspec->num_ids; i++) {
+ struct riscv_iommu_dc *dc;
+ bool dc_is_valid;
+ u64 tc;
+
+ dc = riscv_iommu_get_dc(iommu, fwspec->ids[i]);
+ if (WARN_ON_ONCE(!dc))
+ continue;
+
+ tc = READ_ONCE(dc->tc);
+ dc_is_valid = tc & RISCV_IOMMU_DC_TC_V;
+
+ riscv_iommu_dc_update_qosid(iommu, dc, ctx->rcid, ctx->mcid);
+ dev_dbg(dev, "set QoS ID DC.ta did=%u rcid=%u mcid=%u\n",
+ fwspec->ids[i], ctx->rcid, ctx->mcid);
+
+ if (dc_is_valid) {
+ dma_wmb();
+ riscv_iommu_qosid_invalidate_did(iommu, fwspec->ids[i]);
+ riscv_iommu_iodir_iotinval(iommu, false, dc->iohgatp,
+ dc, NULL);
+ sync_required = true;
+ }
+ }
+
+ if (sync_required)
+ riscv_iommu_cmd_sync(iommu, RISCV_IOMMU_IOTINVAL_TIMEOUT);
+ mutex_unlock(&iommu->qosid_lock);
+}
+
+int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid, u32 mcid)
+{
+ struct riscv_iommu_qosid_hw_ctx hw = {
+ .rcid = rcid,
+ .mcid = mcid,
+ .reset = !rcid && !mcid,
+ };
+ int ret;
+
+ if (rcid > FIELD_MAX(RISCV_IOMMU_DC_TA_RCID) ||
+ mcid > FIELD_MAX(RISCV_IOMMU_DC_TA_MCID))
+ return -ERANGE;
+
+ ret = iommu_group_update_devices(group, &hw,
+ riscv_iommu_qosid_validate_dev,
+ riscv_iommu_qosid_apply_dev);
+ if (ret)
+ return ret;
+ if (!hw.has_devices)
+ return -ENODATA;
+ if (!hw.has_qosid && !hw.reset)
+ return -EOPNOTSUPP;
+
+ pr_debug("set qosid: group=%d rcid=%u mcid=%u\n",
+ iommu_group_id(group), rcid, mcid);
+ return 0;
+}
+
static const struct iommu_ops riscv_iommu_ops = {
.of_xlate = riscv_iommu_of_xlate,
.identity_domain = &riscv_iommu_identity_domain,
diff --git a/drivers/iommu/riscv/iommu.h b/drivers/iommu/riscv/iommu.h
index 46df79dd5495..2c57625637bf 100644
--- a/drivers/iommu/riscv/iommu.h
+++ b/drivers/iommu/riscv/iommu.h
@@ -19,6 +19,9 @@
struct riscv_iommu_device;
+int riscv_iommu_group_set_qosid(struct iommu_group *group, u32 rcid,
+ u32 mcid);
+
struct riscv_iommu_queue {
atomic_t prod; /* unbounded producer allocation index */
atomic_t head; /* unbounded shadow ring buffer consumer index */
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC PATCH 5/7] iommu/riscv: Expose global QoS IDs in sysfs
2026-07-14 13:06 [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment Zhanpeng Zhang
` (3 preceding siblings ...)
2026-07-14 13:06 ` [RFC PATCH 4/7] iommu/riscv: Program QoS IDs for assigned groups Zhanpeng Zhang
@ 2026-07-14 13:06 ` Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 6/7] riscv_cbqri: Assign IOMMU groups to resource groups Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 7/7] selftests/iommu: Add RISC-V IOMMU QoS smoke test Zhanpeng Zhang
6 siblings, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
linux-kselftest, x86
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@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@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@bytedance.com>
+R: Tomasz Jeznach <tomasz.jeznach@linux.dev>
+R: Drew Fustini <fustini@kernel.org>
+R: yunhui cui <cuiyunhui@bytedance.com>
+L: iommu@lists.linux.dev
+L: linux-riscv@lists.infradead.org
+S: Maintained
+F: Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
+
RISC-V MICROCHIP SUPPORT
M: Conor Dooley <conor.dooley@microchip.com>
M: Daire McNamara <daire.mcnamara@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)
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC PATCH 6/7] riscv_cbqri: Assign IOMMU groups to resource groups
2026-07-14 13:06 [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment Zhanpeng Zhang
` (4 preceding siblings ...)
2026-07-14 13:06 ` [RFC PATCH 5/7] iommu/riscv: Expose global QoS IDs in sysfs Zhanpeng Zhang
@ 2026-07-14 13:06 ` Zhanpeng Zhang
2026-07-14 13:06 ` [RFC PATCH 7/7] selftests/iommu: Add RISC-V IOMMU QoS smoke test Zhanpeng Zhang
6 siblings, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
linux-kselftest, x86
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@bytedance.com>
---
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@lists.linux.dev
L: linux-riscv@lists.infradead.org
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@microchip.com>
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);
+ }
+
+ 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)
^ permalink raw reply related [flat|nested] 11+ messages in thread* [RFC PATCH 7/7] selftests/iommu: Add RISC-V IOMMU QoS smoke test
2026-07-14 13:06 [RFC PATCH 0/7] riscv: iommu: Add QoS ID support for resctrl device assignment Zhanpeng Zhang
` (5 preceding siblings ...)
2026-07-14 13:06 ` [RFC PATCH 6/7] riscv_cbqri: Assign IOMMU groups to resource groups Zhanpeng Zhang
@ 2026-07-14 13:06 ` Zhanpeng Zhang
6 siblings, 0 replies; 11+ messages in thread
From: Zhanpeng Zhang @ 2026-07-14 13:06 UTC (permalink / raw)
To: joro, palmer, tony.luck, reinette.chatre, tomasz.jeznach
Cc: will, robin.murphy, fustini, pjw, aou, alex, Dave.Martin,
james.morse, babu.moger, corbet, shuah, jgg, kevin.tian,
cuiyunhui, yuanzhu, iommu, linux-riscv, linux-kernel, linux-doc,
linux-kselftest, x86
Add a RISC-V IOMMU QoS smoke test for the resctrl devices assignment ABI
and the per-IOMMU global qosid sysfs attribute.
Probe for a usable IOMMU group with a non-default child-group assignment
without disturbing groups already assigned by the system. Cover valid
assignment and reset, devices-file readback, malformed and missing group
IDs, strict decimal parsing, tasks-file rejection, group removal and
pseudo-lock protection, and cleanup before child-group removal.
When the global qosid attribute is present, cover its readback format,
valid rewrite and restore, malformed input, missing separators, range
checking, and integer overflow. Keep child-group coverage optional so an
environment without allocatable resctrl resources can still exercise
the root IOMMU QoS paths.
Signed-off-by: Zhanpeng Zhang <zhangzhanpeng.jasper@bytedance.com>
---
MAINTAINERS | 1 +
tools/testing/selftests/iommu/Makefile | 2 +
.../selftests/iommu/iommu_qos_smoke.sh | 649 ++++++++++++++++++
3 files changed, 652 insertions(+)
create mode 100755 tools/testing/selftests/iommu/iommu_qos_smoke.sh
diff --git a/MAINTAINERS b/MAINTAINERS
index 162ad5a1780f..9facf8ac79a2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -23289,6 +23289,7 @@ L: linux-riscv@lists.infradead.org
S: Maintained
F: Documentation/ABI/testing/sysfs-class-iommu-riscv-iommu
F: drivers/resctrl/cbqri_iommu.c
+F: tools/testing/selftests/iommu/iommu_qos_smoke.sh
RISC-V MICROCHIP SUPPORT
M: Conor Dooley <conor.dooley@microchip.com>
diff --git a/tools/testing/selftests/iommu/Makefile b/tools/testing/selftests/iommu/Makefile
index 84abeb2f0949..2d22a10c17fc 100644
--- a/tools/testing/selftests/iommu/Makefile
+++ b/tools/testing/selftests/iommu/Makefile
@@ -7,4 +7,6 @@ TEST_GEN_PROGS :=
TEST_GEN_PROGS += iommufd
TEST_GEN_PROGS += iommufd_fail_nth
+TEST_PROGS += iommu_qos_smoke.sh
+
include ../lib.mk
diff --git a/tools/testing/selftests/iommu/iommu_qos_smoke.sh b/tools/testing/selftests/iommu/iommu_qos_smoke.sh
new file mode 100755
index 000000000000..1a81bca39460
--- /dev/null
+++ b/tools/testing/selftests/iommu/iommu_qos_smoke.sh
@@ -0,0 +1,649 @@
+#!/bin/sh
+# SPDX-License-Identifier: GPL-2.0-only
+# Smoke test for RISC-V IOMMU QoS resctrl integration.
+#
+# This intentionally tests the IOMMU group QoS control path:
+# resctrl/devices -> iommu_group:<id> parser -> IOMMU group QoS assignment
+# explicit reset to the default group before resctrl group removal
+# and the per-IOMMU global default QoS ID sysfs path used by BARE mode.
+
+KSFT_PASS=0
+KSFT_FAIL=1
+KSFT_SKIP=4
+
+RESCTRL=/sys/fs/resctrl
+RESCTRL_FS=resctrl
+IOMMU_GROUPS=/sys/kernel/iommu_groups
+LAST_STATUS=$RESCTRL/info/last_cmd_status
+ROOT_DEVICES=$RESCTRL/devices
+IOMMU_CLASS=/sys/class/iommu
+
+pass_count=0
+fail_count=0
+skip_count=0
+group=""
+child_group=""
+qosid_file=""
+tmp_dir=""
+lock_dir=""
+lock_taken=0
+mounted_by_test=0
+qosid_rcid=""
+qosid_mcid=""
+
+log()
+{
+ printf '%s\n' "$*"
+}
+
+pass()
+{
+ pass_count=$((pass_count + 1))
+ log "PASS: $*"
+}
+
+fail()
+{
+ fail_count=$((fail_count + 1))
+ log "FAIL: $*"
+}
+
+skip()
+{
+ log "SKIP: $*"
+ exit $KSFT_SKIP
+}
+
+skip_optional()
+{
+ skip_count=$((skip_count + 1))
+ log "SKIP: $*"
+}
+
+cleanup()
+{
+ if [ -n "$child_group" ] && [ -d "$child_group" ]; then
+ if [ -n "$group" ]; then
+ printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" 2>/dev/null || true
+ fi
+ rmdir "$child_group" 2>/dev/null || true
+ fi
+
+ if [ "$mounted_by_test" = "1" ]; then
+ umount "$RESCTRL" 2>/dev/null || true
+ fi
+
+ if [ "$lock_taken" = "1" ] && [ -n "$lock_dir" ]; then
+ rmdir "$lock_dir" 2>/dev/null || true
+ fi
+
+ if [ -n "$tmp_dir" ]; then
+ rm -rf "$tmp_dir"
+ fi
+}
+
+need_root()
+{
+ [ "$(id -u)" = "0" ] || skip "requires root"
+}
+
+init_tmp_dir()
+{
+ tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/iommu_qos_smoke.XXXXXX") ||
+ skip "failed to create temporary directory"
+}
+
+take_global_lock()
+{
+ lock_dir=${TMPDIR:-/tmp}/iommu_qos_smoke.lock
+
+ if mkdir "$lock_dir" 2>"$tmp_dir/lock.err"; then
+ lock_taken=1
+ pass "acquired global IOMMU QoS test lock"
+ return
+ fi
+
+ skip "another IOMMU QoS smoke test instance is running"
+}
+
+ensure_resctrl_mounted()
+{
+ if ! grep -qw resctrl /proc/filesystems; then
+ skip "resctrl filesystem is not available"
+ fi
+
+ mkdir -p "$RESCTRL" 2>/dev/null || true
+
+ if grep -qs " $RESCTRL resctrl " /proc/mounts; then
+ pass "resctrl already mounted"
+ return
+ fi
+
+ if mount -t resctrl "$RESCTRL_FS" "$RESCTRL" 2>"$tmp_dir/mount.err"; then
+ mounted_by_test=1
+ pass "mounted resctrl"
+ else
+ log "mount error: $(cat "$tmp_dir/mount.err" 2>/dev/null)"
+ skip "failed to mount resctrl"
+ fi
+}
+
+ensure_devices_file()
+{
+ [ -e "$ROOT_DEVICES" ] || skip "resctrl devices file is not available"
+ [ -w "$ROOT_DEVICES" ] || skip "$ROOT_DEVICES is not writable"
+}
+
+create_child_group()
+{
+ child_group=$RESCTRL/iommu_qos_test_$$
+
+ if mkdir "$child_group" 2>"$tmp_dir/mkdir.err"; then
+ pass "created child resctrl group"
+ return
+ fi
+
+ log "mkdir error: $(cat "$tmp_dir/mkdir.err" 2>/dev/null)"
+ child_group=""
+ skip "failed to create a child resctrl group"
+}
+
+iommu_group_is_assigned()
+{
+ candidate=$1
+
+ find "$RESCTRL" -type f -name devices \
+ -exec grep -qx "iommu_group:$candidate" {} \; -print \
+ 2>/dev/null | grep -q .
+}
+
+pick_iommu_group()
+{
+ populated=0
+ unsupported=0
+
+ [ -d "$IOMMU_GROUPS" ] || skip "no IOMMU groups sysfs directory"
+
+ for path in "$IOMMU_GROUPS"/*; do
+ [ -d "$path" ] || continue
+ case "${path##*/}" in
+ *[!0-9]*|'')
+ continue
+ ;;
+ esac
+
+ [ -d "$path/devices" ] || continue
+ if ! find "$path/devices" -mindepth 1 -maxdepth 1 2>/dev/null |
+ grep -q .; then
+ continue
+ fi
+
+ populated=$((populated + 1))
+ candidate=${path##*/}
+ if iommu_group_is_assigned "$candidate"; then
+ log "skip IOMMU group $candidate with an existing resctrl assignment"
+ continue
+ fi
+
+ if printf '%s\n' "iommu_group:$candidate" > "$child_group/devices" \
+ 2>"$tmp_dir/group_$candidate.err"; then
+ group=$candidate
+ if ! printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/group_${candidate}_reset.err"; then
+ log "reset error:"
+ cat "$tmp_dir/group_${candidate}_reset.err" 2>/dev/null
+ fail "failed to restore probed IOMMU group $group"
+ return 1
+ fi
+ pass "selected group $group using non-default QoS assignment"
+ return 0
+ fi
+
+ if last_status_is_iommu_qos_unsupported; then
+ unsupported=$((unsupported + 1))
+ continue
+ fi
+
+ log "group $candidate write error:"
+ cat "$tmp_dir/group_$candidate.err" 2>/dev/null
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "IOMMU group $candidate failed QoS capability probing"
+ done
+
+ [ "$populated" -gt 0 ] || skip "no populated IOMMU group found"
+ if [ "$unsupported" -eq "$populated" ]; then
+ skip "no populated IOMMU group supports QoS ID programming"
+ fi
+
+ return 1
+}
+
+last_status_contains()
+{
+ pattern=$1
+
+ [ -f "$LAST_STATUS" ] || return 1
+ grep -qi "$pattern" "$LAST_STATUS"
+}
+
+last_status_is_iommu_qos_unsupported()
+{
+ [ -f "$LAST_STATUS" ] || return 1
+ grep -Eiq \
+ 'IOMMU group .* QoS \(-95\)|IOMMU QoSID.*not supported|not supported' \
+ "$LAST_STATUS"
+}
+
+pick_missing_iommu_group()
+{
+ max=0
+
+ for path in "$IOMMU_GROUPS"/*; do
+ [ -d "$path" ] || continue
+ id=${path##*/}
+ case "$id" in
+ *[!0-9]*|'')
+ continue
+ ;;
+ esac
+ if [ "$id" -gt "$max" ]; then
+ max=$id
+ fi
+ done
+
+ if [ "$max" -ge 2147483647 ]; then
+ skip "cannot choose a missing IOMMU group id"
+ fi
+
+ missing=$((max + 1))
+ while [ -e "$IOMMU_GROUPS/$missing" ]; do
+ if [ "$missing" -ge 2147483647 ]; then
+ skip "cannot choose a missing IOMMU group id"
+ fi
+ missing=$((missing + 1))
+ done
+}
+
+pick_qosid_file()
+{
+ for file in "$IOMMU_CLASS"/*/qosid; do
+ [ -e "$file" ] || continue
+ qosid_file=$file
+ return 0
+ done
+
+ return 1
+}
+
+parse_qosid()
+{
+ line=$1
+
+ case "$line" in
+ rcid=[0-9]*" "mcid=[0-9]*)
+ qosid_rcid=${line%% *}
+ qosid_rcid=${qosid_rcid#rcid=}
+ qosid_mcid=${line##* }
+ qosid_mcid=${qosid_mcid#mcid=}
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+
+ case "$qosid_rcid" in
+ *[!0-9]*|'')
+ return 1
+ ;;
+ esac
+ case "$qosid_mcid" in
+ *[!0-9]*|'')
+ return 1
+ ;;
+ esac
+
+ return 0
+}
+
+test_malformed_group_rejected()
+{
+ if printf '%s\n' 'iommu_group:bad' > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/badtoken.err"; then
+ fail "malformed IOMMU group token was accepted"
+ return
+ fi
+
+ if last_status_contains "IOMMU group parsing error"; then
+ pass "malformed IOMMU group token is rejected with useful status"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "malformed IOMMU group rejection status is unclear"
+ fi
+}
+
+test_trailing_separator_rejected()
+{
+ if printf '%s\n' "iommu_group:$group," > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/trailing_separator.err"; then
+ fail "IOMMU group token with trailing separator was accepted"
+ return
+ fi
+
+ if last_status_contains "Device list parsing error"; then
+ pass "trailing device-list separator is rejected"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "trailing separator rejection status is unclear"
+ fi
+}
+
+test_missing_group_rejected()
+{
+ pick_missing_iommu_group
+
+ if printf '%s\n' "iommu_group:$missing" > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/missing.err"; then
+ fail "missing IOMMU group was accepted"
+ return
+ fi
+
+ if last_status_contains "No IOMMU group $missing"; then
+ pass "missing IOMMU group is rejected with useful status"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "missing IOMMU group rejection status is unclear"
+ fi
+}
+
+test_non_decimal_group_rejected()
+{
+ if printf '%s\n' "iommu_group:0x$group" > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/non_decimal.err"; then
+ fail "non-decimal IOMMU group ID was accepted"
+ return
+ fi
+
+ if last_status_contains "IOMMU group parsing error"; then
+ pass "non-decimal IOMMU group ID is rejected"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "non-decimal IOMMU group rejection status is unclear"
+ fi
+}
+
+test_tasks_rejects_iommu_token()
+{
+ if printf '%s\n' "iommu_group:$group" > "$RESCTRL/tasks" \
+ 2>"$tmp_dir/tasks_token.err"; then
+ fail "tasks accepted an IOMMU group token"
+ return
+ fi
+
+ if last_status_contains "Task list parsing error pid iommu_group:$group"; then
+ pass "tasks rejects IOMMU group tokens"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "tasks rejection status is unclear"
+ fi
+}
+
+test_global_qosid_sysfs()
+{
+ if ! pick_qosid_file; then
+ skip_optional "no RISC-V IOMMU qosid sysfs file is available"
+ return
+ fi
+
+ if ! line=$(cat "$qosid_file" 2>"$tmp_dir/qosid_read.err"); then
+ log "read error: $(cat "$tmp_dir/qosid_read.err" 2>/dev/null)"
+ fail "failed to read IOMMU global qosid"
+ return
+ fi
+
+ if parse_qosid "$line"; then
+ pass "read IOMMU global qosid from sysfs"
+ else
+ log "qosid content: $line"
+ fail "IOMMU global qosid has unexpected format"
+ return
+ fi
+
+ if printf '%s\n' "$line" > "$qosid_file" \
+ 2>"$tmp_dir/qosid_rewrite.err"; then
+ pass "IOMMU global qosid accepts readback format"
+ else
+ log "write error: $(cat "$tmp_dir/qosid_rewrite.err" 2>/dev/null)"
+ fail "failed to rewrite IOMMU global qosid readback"
+ return
+ fi
+
+ if printf '%s\n' "bad" > "$qosid_file" 2>"$tmp_dir/qosid_bad.err"; then
+ fail "IOMMU global qosid accepted malformed input"
+ return
+ fi
+ pass "IOMMU global qosid rejects malformed input"
+
+ if printf '%s\n' "rcid=0mcid=1" > "$qosid_file" \
+ 2>"$tmp_dir/qosid_separator.err"; then
+ fail "IOMMU global qosid accepted missing token separator"
+ return
+ fi
+ pass "IOMMU global qosid requires a token separator"
+
+ if printf '%s\n' "rcid=4096 mcid=0" > "$qosid_file" \
+ 2>"$tmp_dir/qosid_range.err"; then
+ fail "IOMMU global qosid accepted out-of-range RCID"
+ return
+ fi
+ pass "IOMMU global qosid rejects out-of-range RCID"
+
+ if printf '%s\n' "rcid=4294967296 mcid=0" > "$qosid_file" \
+ 2>"$tmp_dir/qosid_overflow.err"; then
+ fail "IOMMU global qosid accepted overflowed RCID"
+ return
+ fi
+ pass "IOMMU global qosid rejects overflowed RCID"
+
+ if printf '%s\n' "rcid=$qosid_rcid mcid=$qosid_mcid" > "$qosid_file" \
+ 2>"$tmp_dir/qosid_restore.err"; then
+ pass "IOMMU global qosid accepts current RCID/MCID"
+ else
+ log "write error: $(cat "$tmp_dir/qosid_restore.err" 2>/dev/null)"
+ fail "failed to write current IOMMU global qosid"
+ return
+ fi
+
+ if line=$(cat "$qosid_file" 2>/dev/null) &&
+ [ "$line" = "rcid=$qosid_rcid mcid=$qosid_mcid" ]; then
+ pass "IOMMU global qosid readback matches restored value"
+ else
+ log "qosid content: $line"
+ fail "IOMMU global qosid readback mismatch"
+ fi
+}
+
+test_valid_group_write()
+{
+ if printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/valid.err"; then
+ pass "write iommu_group:$group to root resctrl devices"
+ else
+ log "write error: $(cat "$tmp_dir/valid.err" 2>/dev/null)"
+ fail "valid IOMMU group write failed"
+ return
+ fi
+
+ if last_status_contains ^ok; then
+ pass "last_cmd_status reports ok"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "last_cmd_status is not ok after valid write"
+ fi
+
+ # The root control group represents the default QoS IDs. The kernel
+ # applies the reset but intentionally does not keep a software binding
+ # for default assignments, so the root devices file should stay empty.
+ if grep -qx "iommu_group:$group" "$ROOT_DEVICES"; then
+ log "devices content:"
+ cat "$ROOT_DEVICES" 2>/dev/null
+ fail "root devices unexpectedly shows default iommu_group:$group"
+ else
+ pass "root devices omits default iommu_group:$group"
+ fi
+
+}
+
+test_child_group_write()
+{
+ if printf '%s\n' "iommu_group:$group" > "$child_group/devices" \
+ 2>"$tmp_dir/child.err"; then
+ pass "write iommu_group:$group to child resctrl group"
+ else
+ log "write error: $(cat "$tmp_dir/child.err" 2>/dev/null)"
+ fail "valid child IOMMU group write failed"
+ return
+ fi
+
+ if last_status_contains ^ok; then
+ pass "last_cmd_status reports ok after child group write"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "last_cmd_status is not ok after child group write"
+ fi
+
+ if grep -qx "iommu_group:$group" "$child_group/devices"; then
+ pass "child devices shows iommu_group:$group after set/get"
+ else
+ log "child devices content:"
+ cat "$child_group/devices" 2>/dev/null
+ fail "child devices does not show iommu_group:$group"
+ fi
+
+ if grep -qx "iommu_group:$group" "$ROOT_DEVICES"; then
+ log "root devices content:"
+ cat "$ROOT_DEVICES" 2>/dev/null
+ fail "root devices still shows iommu_group:$group after child move"
+ else
+ pass "root devices no longer shows iommu_group:$group after child move"
+ fi
+
+}
+
+test_child_pseudo_locksetup_rejected()
+{
+ [ -d "$child_group" ] || return
+
+ if [ ! -e "$child_group/mode" ]; then
+ skip_optional "resctrl mode file is not available"
+ return
+ fi
+
+ if printf '%s\n' "pseudo-locksetup" > "$child_group/mode" \
+ 2>"$tmp_dir/pseudo_locksetup.err"; then
+ printf '%s\n' "shareable" > "$child_group/mode" 2>/dev/null || true
+ fail "pseudo-locksetup accepted a group with assigned devices"
+ return
+ fi
+
+ if last_status_contains \
+ "Move devices out before entering pseudo-locksetup group"; then
+ pass "pseudo-locksetup rejects group with assigned devices"
+ elif last_status_contains "Unknown or unsupported mode"; then
+ skip_optional "pseudo-locksetup mode is not supported"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "pseudo-locksetup rejection status is unclear"
+ fi
+}
+
+test_child_group_rmdir()
+{
+ [ -d "$child_group" ] || return
+
+ if rmdir "$child_group" 2>"$tmp_dir/rmdir_busy.err"; then
+ fail "rmdir child resctrl group with IOMMU group was accepted"
+ # The child group no longer exists, so cleanup() cannot reset this.
+ if ! printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/rmdir_unexpected_move_root.err"; then
+ log "move error after unexpected rmdir:"
+ cat "$tmp_dir/rmdir_unexpected_move_root.err" 2>/dev/null
+ fi
+ child_group=""
+ return
+ fi
+
+ if last_status_contains "Move devices out before removing group"; then
+ pass "rmdir rejects child group while IOMMU group is assigned"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "rmdir rejection status is unclear"
+ fi
+
+ if printf '%s\n' "iommu_group:$group" > "$ROOT_DEVICES" \
+ 2>"$tmp_dir/rmdir_move_root.err"; then
+ pass "move iommu_group:$group back to root before rmdir"
+ else
+ log "move error: $(cat "$tmp_dir/rmdir_move_root.err" 2>/dev/null)"
+ fail "failed to move iommu_group:$group back to root"
+ return
+ fi
+
+ if rmdir "$child_group" 2>"$tmp_dir/rmdir.err"; then
+ pass "rmdir child resctrl group after moving IOMMU group out"
+ else
+ log "rmdir error: $(cat "$tmp_dir/rmdir.err" 2>/dev/null)"
+ fail "failed to rmdir child resctrl group after moving IOMMU group out"
+ return
+ fi
+ child_group=""
+
+ if last_status_contains ^ok; then
+ pass "last_cmd_status reports ok after child group rmdir"
+ else
+ log "last_cmd_status: $(cat "$LAST_STATUS" 2>/dev/null)"
+ fail "last_cmd_status is not ok after child group rmdir"
+ fi
+
+ if grep -qx "iommu_group:$group" "$ROOT_DEVICES"; then
+ log "root devices content:"
+ cat "$ROOT_DEVICES" 2>/dev/null
+ fail "root devices still shows iommu_group:$group after default move"
+ else
+ pass "root devices does not show iommu_group:$group after default move"
+ fi
+
+}
+
+trap cleanup EXIT
+need_root
+init_tmp_dir
+take_global_lock
+ensure_resctrl_mounted
+ensure_devices_file
+create_child_group
+if ! pick_iommu_group; then
+ log "FAIL: $fail_count failure(s), $pass_count pass(es)"
+ exit $KSFT_FAIL
+fi
+test_malformed_group_rejected
+test_trailing_separator_rejected
+test_missing_group_rejected
+test_non_decimal_group_rejected
+test_tasks_rejects_iommu_token
+test_global_qosid_sysfs
+test_valid_group_write
+test_child_group_write
+test_child_pseudo_locksetup_rejected
+test_child_group_rmdir
+
+if [ "$fail_count" -gt 0 ]; then
+ log "FAIL: $fail_count failure(s), $pass_count pass(es)"
+ exit $KSFT_FAIL
+fi
+
+if [ "$skip_count" -gt 0 ]; then
+ log "PASS: $pass_count checks passed, $skip_count optional check(s) skipped"
+else
+ log "PASS: $pass_count checks passed"
+fi
+exit $KSFT_PASS
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 11+ messages in thread