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 <[email protected]>
---
 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)


Reply via email to