]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
iommufd: Destroy vdevice on idevice destroy
authorXu Yilun <yilun.xu@linux.intel.com>
Wed, 16 Jul 2025 07:03:45 +0000 (15:03 +0800)
committerJason Gunthorpe <jgg@nvidia.com>
Fri, 18 Jul 2025 20:33:08 +0000 (17:33 -0300)
Destroy iommufd_vdevice (vdev) on iommufd_idevice (idev) destruction so
that vdev can't outlive idev.

idev represents the physical device bound to iommufd, while the vdev
represents the virtual instance of the physical device in the VM. The
lifecycle of the vdev should not be longer than idev. This doesn't
cause real problem on existing use cases cause vdev doesn't impact the
physical device, only provides virtualization information. But to
extend vdev for Confidential Computing (CC), there are needs to do
secure configuration for the vdev, e.g. TSM Bind/Unbind. These
configurations should be rolled back on idev destroy, or the external
driver (VFIO) functionality may be impact.

The idev is created by external driver so its destruction can't fail.
The idev implements pre_destroy() op to actively remove its associated
vdev before destroying itself. There are 3 cases on idev pre_destroy():

  1. vdev is already destroyed by userspace. No extra handling needed.
  2. vdev is still alive. Use iommufd_object_tombstone_user() to
     destroy vdev and tombstone the vdev ID.
  3. vdev is being destroyed by userspace. The vdev ID is already
     freed, but vdev destroy handler is not completed. This requires
     multi-threads syncing - vdev holds idev's short term users
     reference until vdev destruction completes, idev leverages
     existing wait_shortterm mechanism for syncing.

idev should also block any new reference to it after pre_destroy(),
or the following wait shortterm would timeout. Introduce a 'destroying'
flag, set it to true on idev pre_destroy(). Any attempt to reference
idev should honor this flag under the protection of
idev->igroup->lock.

Link: https://patch.msgid.link/r/20250716070349.1807226-5-yilun.xu@linux.intel.com
Originally-by: Nicolin Chen <nicolinc@nvidia.com>
Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Reviewed-by: Kevin Tian <kevin.tian@intel.com>
Reviewed-by: Nicolin Chen <nicolinc@nvidia.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Co-developed-by: "Aneesh Kumar K.V (Arm)" <aneesh.kumar@kernel.org>
Signed-off-by: "Aneesh Kumar K.V (Arm)" <aneesh.kumar@kernel.org>
Tested-by: Nicolin Chen <nicolinc@nvidia.com>
Signed-off-by: Xu Yilun <yilun.xu@linux.intel.com>
Signed-off-by: Jason Gunthorpe <jgg@nvidia.com>
drivers/iommu/iommufd/device.c
drivers/iommu/iommufd/iommufd_private.h
drivers/iommu/iommufd/main.c
drivers/iommu/iommufd/viommu.c
include/linux/iommufd.h
include/uapi/linux/iommufd.h

index e2ba21c43ad24e4098f9ec88081d44f5d7171f53..ee6ff4caf3982fee05ba8a018eec7e474c695097 100644 (file)
@@ -137,6 +137,57 @@ static struct iommufd_group *iommufd_get_group(struct iommufd_ctx *ictx,
        }
 }
 
+static void iommufd_device_remove_vdev(struct iommufd_device *idev)
+{
+       struct iommufd_vdevice *vdev;
+
+       mutex_lock(&idev->igroup->lock);
+       /* prevent new references from vdev */
+       idev->destroying = true;
+       /* vdev has been completely destroyed by userspace */
+       if (!idev->vdev)
+               goto out_unlock;
+
+       vdev = iommufd_get_vdevice(idev->ictx, idev->vdev->obj.id);
+       /*
+        * An ongoing vdev destroy ioctl has removed the vdev from the object
+        * xarray, but has not finished iommufd_vdevice_destroy() yet as it
+        * needs the same mutex. We exit the locking then wait on short term
+        * users for the vdev destruction.
+        */
+       if (IS_ERR(vdev))
+               goto out_unlock;
+
+       /* Should never happen */
+       if (WARN_ON(vdev != idev->vdev)) {
+               iommufd_put_object(idev->ictx, &vdev->obj);
+               goto out_unlock;
+       }
+
+       /*
+        * vdev is still alive. Hold a users refcount to prevent racing with
+        * userspace destruction, then use iommufd_object_tombstone_user() to
+        * destroy it and leave a tombstone.
+        */
+       refcount_inc(&vdev->obj.users);
+       iommufd_put_object(idev->ictx, &vdev->obj);
+       mutex_unlock(&idev->igroup->lock);
+       iommufd_object_tombstone_user(idev->ictx, &vdev->obj);
+       return;
+
+out_unlock:
+       mutex_unlock(&idev->igroup->lock);
+}
+
+void iommufd_device_pre_destroy(struct iommufd_object *obj)
+{
+       struct iommufd_device *idev =
+               container_of(obj, struct iommufd_device, obj);
+
+       /* Release the short term users on this */
+       iommufd_device_remove_vdev(idev);
+}
+
 void iommufd_device_destroy(struct iommufd_object *obj)
 {
        struct iommufd_device *idev =
index 1495450600293305b99b9787aa91d93fc5ad9334..5d6ea5395cfea6a661d01470ef6259fa2e956077 100644 (file)
@@ -489,6 +489,8 @@ struct iommufd_device {
        /* always the physical device */
        struct device *dev;
        bool enforce_cache_coherency;
+       struct iommufd_vdevice *vdev;
+       bool destroying;
 };
 
 static inline struct iommufd_device *
@@ -499,6 +501,7 @@ iommufd_get_device(struct iommufd_ucmd *ucmd, u32 id)
                            struct iommufd_device, obj);
 }
 
+void iommufd_device_pre_destroy(struct iommufd_object *obj);
 void iommufd_device_destroy(struct iommufd_object *obj);
 int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
 
@@ -687,9 +690,18 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd);
 void iommufd_viommu_destroy(struct iommufd_object *obj);
 int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd);
 void iommufd_vdevice_destroy(struct iommufd_object *obj);
+void iommufd_vdevice_abort(struct iommufd_object *obj);
 int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd);
 void iommufd_hw_queue_destroy(struct iommufd_object *obj);
 
+static inline struct iommufd_vdevice *
+iommufd_get_vdevice(struct iommufd_ctx *ictx, u32 id)
+{
+       return container_of(iommufd_get_object(ictx, id,
+                                              IOMMUFD_OBJ_VDEVICE),
+                           struct iommufd_vdevice, obj);
+}
+
 #ifdef CONFIG_IOMMUFD_TEST
 int iommufd_test(struct iommufd_ucmd *ucmd);
 void iommufd_selftest_destroy(struct iommufd_object *obj);
index 53085d24ce4a9874ad053062877c5063c24165fc..99c1aab3d396dddebdd1cf04323b0c10a662f668 100644 (file)
@@ -655,6 +655,7 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
                .destroy = iommufd_access_destroy_object,
        },
        [IOMMUFD_OBJ_DEVICE] = {
+               .pre_destroy = iommufd_device_pre_destroy,
                .destroy = iommufd_device_destroy,
        },
        [IOMMUFD_OBJ_FAULT] = {
@@ -676,6 +677,7 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
        },
        [IOMMUFD_OBJ_VDEVICE] = {
                .destroy = iommufd_vdevice_destroy,
+               .abort = iommufd_vdevice_abort,
        },
        [IOMMUFD_OBJ_VEVENTQ] = {
                .destroy = iommufd_veventq_destroy,
index dcf8a85b9f6ec2f217ca8bdaa2e2486f94cf3db0..ecbae5091ffe1fb8ee2fd0c8c39634677334f209 100644 (file)
@@ -110,20 +110,37 @@ out_put_idev:
        return rc;
 }
 
-void iommufd_vdevice_destroy(struct iommufd_object *obj)
+void iommufd_vdevice_abort(struct iommufd_object *obj)
 {
        struct iommufd_vdevice *vdev =
                container_of(obj, struct iommufd_vdevice, obj);
        struct iommufd_viommu *viommu = vdev->viommu;
+       struct iommufd_device *idev = vdev->idev;
+
+       lockdep_assert_held(&idev->igroup->lock);
 
        if (vdev->destroy)
                vdev->destroy(vdev);
        /* xa_cmpxchg is okay to fail if alloc failed xa_cmpxchg previously */
        xa_cmpxchg(&viommu->vdevs, vdev->virt_id, vdev, NULL, GFP_KERNEL);
        refcount_dec(&viommu->obj.users);
+       idev->vdev = NULL;
        put_device(vdev->dev);
 }
 
+void iommufd_vdevice_destroy(struct iommufd_object *obj)
+{
+       struct iommufd_vdevice *vdev =
+               container_of(obj, struct iommufd_vdevice, obj);
+       struct iommufd_device *idev = vdev->idev;
+       struct iommufd_ctx *ictx = idev->ictx;
+
+       mutex_lock(&idev->igroup->lock);
+       iommufd_vdevice_abort(obj);
+       mutex_unlock(&idev->igroup->lock);
+       iommufd_put_object(ictx, &idev->obj);
+}
+
 int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
 {
        struct iommu_vdevice_alloc *cmd = ucmd->cmd;
@@ -153,6 +170,17 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
                goto out_put_idev;
        }
 
+       mutex_lock(&idev->igroup->lock);
+       if (idev->destroying) {
+               rc = -ENOENT;
+               goto out_unlock_igroup;
+       }
+
+       if (idev->vdev) {
+               rc = -EEXIST;
+               goto out_unlock_igroup;
+       }
+
        if (viommu->ops && viommu->ops->vdevice_size) {
                /*
                 * It is a driver bug for:
@@ -171,7 +199,7 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
                ucmd->ictx, vdev_size, IOMMUFD_OBJ_VDEVICE);
        if (IS_ERR(vdev)) {
                rc = PTR_ERR(vdev);
-               goto out_put_idev;
+               goto out_unlock_igroup;
        }
 
        vdev->virt_id = virt_id;
@@ -179,6 +207,19 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
        get_device(idev->dev);
        vdev->viommu = viommu;
        refcount_inc(&viommu->obj.users);
+       /*
+        * A short term users reference is held on the idev so long as we have
+        * the pointer. iommufd_device_pre_destroy() will revoke it before the
+        * idev real destruction.
+        */
+       vdev->idev = idev;
+
+       /*
+        * iommufd_device_destroy() delays until idev->vdev is NULL before
+        * freeing the idev, which only happens once the vdev is finished
+        * destruction.
+        */
+       idev->vdev = vdev;
 
        curr = xa_cmpxchg(&viommu->vdevs, virt_id, NULL, vdev, GFP_KERNEL);
        if (curr) {
@@ -197,12 +238,15 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
        if (rc)
                goto out_abort;
        iommufd_object_finalize(ucmd->ictx, &vdev->obj);
-       goto out_put_idev;
+       goto out_unlock_igroup;
 
 out_abort:
        iommufd_object_abort_and_destroy(ucmd->ictx, &vdev->obj);
+out_unlock_igroup:
+       mutex_unlock(&idev->igroup->lock);
 out_put_idev:
-       iommufd_put_object(ucmd->ictx, &idev->obj);
+       if (rc)
+               iommufd_put_object(ucmd->ictx, &idev->obj);
 out_put_viommu:
        iommufd_put_object(ucmd->ictx, &viommu->obj);
        return rc;
index e3a0cd47384d07efb21bafd9ada5bd64c96bfd87..b88911026bc412f01661bcdf27a7ccd9fd245775 100644 (file)
@@ -108,6 +108,7 @@ struct iommufd_viommu {
 struct iommufd_vdevice {
        struct iommufd_object obj;
        struct iommufd_viommu *viommu;
+       struct iommufd_device *idev;
        struct device *dev;
 
        /*
index 554aacf89ea7b45bdb79bb7e0f02bbca81e232ab..c218c89e0e2ebd4ede6e1b5e8f3b8504ee6f26e2 100644 (file)
@@ -1070,6 +1070,11 @@ struct iommu_viommu_alloc {
  *
  * Allocate a virtual device instance (for a physical device) against a vIOMMU.
  * This instance holds the device's information (related to its vIOMMU) in a VM.
+ * User should use IOMMU_DESTROY to destroy the virtual device before
+ * destroying the physical device (by closing vfio_cdev fd). Otherwise the
+ * virtual device would be forcibly destroyed on physical device destruction,
+ * its vdevice_id would be permanently leaked (unremovable & unreusable) until
+ * iommu fd closed.
  */
 struct iommu_vdevice_alloc {
        __u32 size;