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)

