[PATCH 1/3] iommufd: Add devices_users to track the hw_pagetable usage by device

From: Nicolin Chen
Date: Fri Jan 27 2023 - 21:30:21 EST


From: Yi Liu <yi.l.liu@xxxxxxxxx>

Currently, hw_pagetable tracks the attached devices using a device list.
When attaching the first device to the kernel-managed hw_pagetable, it
should be linked to IOAS. When detaching the last device from this hwpt,
the link with IOAS should be removed too. And this first-or-last device
check is done with list_empty(hwpt->devices).

However, with a nested configuration, when a device is attached to the
user-managed stage-1 hw_pagetable, it will be added to this user-managed
hwpt's device list instead of the kernel-managed stage-2 hwpt's one. And
this breaks the logic for a kernel-managed hw_pagetable link/disconnect
to/from IOAS/IOPT. e.g. the stage-2 hw_pagetable would be linked to IOAS
multiple times if multiple device is attached, but it will become empty
as soon as one device detached.

Add a devices_users in struct iommufd_hw_pagetable to track the users of
hw_pagetable by the attached devices. Make this field as a pointer, only
allocate for a stage-2 hw_pagetable. A stage-1 hw_pagetable should reuse
the stage-2 hw_pagetable's devices_users, because when a device attaches
to a stage-1 hw_pagetable, linking the stage-2 hwpt to the IOAS is still
required. So, with a nested configuration, increase the devices_users on
the stage-2 (parent) hwpt, no matter a device is attached to the stage-1
or the stage-2 hwpt.

Adding this devices_users also reduces the dependency on the device list,
so it helps the following patch to remove the device list completely.

Signed-off-by: Yi Liu <yi.l.liu@xxxxxxxxx>
Co-developed-by: Nicolin Chen <nicolinc@xxxxxxxxxx>
Signed-off-by: Nicolin Chen <nicolinc@xxxxxxxxxx>
---
drivers/iommu/iommufd/device.c | 8 +++++---
drivers/iommu/iommufd/hw_pagetable.c | 11 +++++++++++
drivers/iommu/iommufd/iommufd_private.h | 1 +
3 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/iommufd/device.c b/drivers/iommu/iommufd/device.c
index 9f3b9674d72e..208757c39c90 100644
--- a/drivers/iommu/iommufd/device.c
+++ b/drivers/iommu/iommufd/device.c
@@ -212,7 +212,7 @@ static int iommufd_device_do_attach(struct iommufd_device *idev,
hwpt->domain->ops->enforce_cache_coherency(
hwpt->domain);
if (!hwpt->enforce_cache_coherency) {
- WARN_ON(list_empty(&hwpt->devices));
+ WARN_ON(refcount_read(hwpt->devices_users) == 1);
rc = -EINVAL;
goto out_unlock;
}
@@ -236,7 +236,7 @@ static int iommufd_device_do_attach(struct iommufd_device *idev,
if (rc)
goto out_iova;

- if (list_empty(&hwpt->devices)) {
+ if (refcount_read(hwpt->devices_users) == 1) {
rc = iopt_table_add_domain(&hwpt->ioas->iopt,
hwpt->domain);
if (rc)
@@ -246,6 +246,7 @@ static int iommufd_device_do_attach(struct iommufd_device *idev,

idev->hwpt = hwpt;
refcount_inc(&hwpt->obj.users);
+ refcount_inc(hwpt->devices_users);
list_add(&idev->devices_item, &hwpt->devices);
mutex_unlock(&hwpt->devices_lock);
return 0;
@@ -387,9 +388,10 @@ void iommufd_device_detach(struct iommufd_device *idev)

mutex_lock(&hwpt->ioas->mutex);
mutex_lock(&hwpt->devices_lock);
+ refcount_dec(hwpt->devices_users);
list_del(&idev->devices_item);
if (!iommufd_hw_pagetable_has_group(hwpt, idev->group)) {
- if (list_empty(&hwpt->devices)) {
+ if (refcount_read(hwpt->devices_users) == 1) {
iopt_table_remove_domain(&hwpt->ioas->iopt,
hwpt->domain);
list_del(&hwpt->hwpt_item);
diff --git a/drivers/iommu/iommufd/hw_pagetable.c b/drivers/iommu/iommufd/hw_pagetable.c
index 43d473989a06..910e759ffeac 100644
--- a/drivers/iommu/iommufd/hw_pagetable.c
+++ b/drivers/iommu/iommufd/hw_pagetable.c
@@ -16,6 +16,8 @@ void iommufd_hw_pagetable_destroy(struct iommufd_object *obj)
iommu_domain_free(hwpt->domain);
refcount_dec(&hwpt->ioas->obj.users);
mutex_destroy(&hwpt->devices_lock);
+ WARN_ON(!refcount_dec_if_one(hwpt->devices_users));
+ kfree(hwpt->devices_users);
}

/**
@@ -46,11 +48,20 @@ iommufd_hw_pagetable_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas,
INIT_LIST_HEAD(&hwpt->devices);
INIT_LIST_HEAD(&hwpt->hwpt_item);
mutex_init(&hwpt->devices_lock);
+ hwpt->devices_users = kzalloc(sizeof(*hwpt->devices_users), GFP_KERNEL);
+ if (!hwpt->devices_users) {
+ rc = -ENOMEM;
+ goto out_free_domain;
+ }
+ refcount_set(hwpt->devices_users, 1);
+
/* Pairs with iommufd_hw_pagetable_destroy() */
refcount_inc(&ioas->obj.users);
hwpt->ioas = ioas;
return hwpt;

+out_free_domain:
+ iommu_domain_free(hwpt->domain);
out_abort:
iommufd_object_abort(ictx, &hwpt->obj);
return ERR_PTR(rc);
diff --git a/drivers/iommu/iommufd/iommufd_private.h b/drivers/iommu/iommufd/iommufd_private.h
index 222e86591f8a..f128d77fb076 100644
--- a/drivers/iommu/iommufd/iommufd_private.h
+++ b/drivers/iommu/iommufd/iommufd_private.h
@@ -247,6 +247,7 @@ struct iommufd_hw_pagetable {
/* Head at iommufd_ioas::hwpt_list */
struct list_head hwpt_item;
struct mutex devices_lock;
+ refcount_t *devices_users;
struct list_head devices;
};

--
2.39.1