[RFC PATCH 1/7] iommu: Add group lookup by ID
From: Zhanpeng Zhang
Date: Tue Jul 14 2026 - 09:15:09 EST
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@xxxxxxxxxxxxx>
---
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)