Skip to content

Commit 850f14f

Browse files
Xu Yilunjgunthorpe
authored andcommitted
iommufd: Destroy vdevice on idevice destroy
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/[email protected] Originally-by: Nicolin Chen <[email protected]> Suggested-by: Jason Gunthorpe <[email protected]> Reviewed-by: Kevin Tian <[email protected]> Reviewed-by: Nicolin Chen <[email protected]> Reviewed-by: Jason Gunthorpe <[email protected]> Co-developed-by: "Aneesh Kumar K.V (Arm)" <[email protected]> Signed-off-by: "Aneesh Kumar K.V (Arm)" <[email protected]> Tested-by: Nicolin Chen <[email protected]> Signed-off-by: Xu Yilun <[email protected]> Signed-off-by: Jason Gunthorpe <[email protected]>
1 parent 7dc0e10 commit 850f14f

File tree

6 files changed

+119
-4
lines changed

6 files changed

+119
-4
lines changed

drivers/iommu/iommufd/device.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,57 @@ static struct iommufd_group *iommufd_get_group(struct iommufd_ctx *ictx,
137137
}
138138
}
139139

140+
static void iommufd_device_remove_vdev(struct iommufd_device *idev)
141+
{
142+
struct iommufd_vdevice *vdev;
143+
144+
mutex_lock(&idev->igroup->lock);
145+
/* prevent new references from vdev */
146+
idev->destroying = true;
147+
/* vdev has been completely destroyed by userspace */
148+
if (!idev->vdev)
149+
goto out_unlock;
150+
151+
vdev = iommufd_get_vdevice(idev->ictx, idev->vdev->obj.id);
152+
/*
153+
* An ongoing vdev destroy ioctl has removed the vdev from the object
154+
* xarray, but has not finished iommufd_vdevice_destroy() yet as it
155+
* needs the same mutex. We exit the locking then wait on short term
156+
* users for the vdev destruction.
157+
*/
158+
if (IS_ERR(vdev))
159+
goto out_unlock;
160+
161+
/* Should never happen */
162+
if (WARN_ON(vdev != idev->vdev)) {
163+
iommufd_put_object(idev->ictx, &vdev->obj);
164+
goto out_unlock;
165+
}
166+
167+
/*
168+
* vdev is still alive. Hold a users refcount to prevent racing with
169+
* userspace destruction, then use iommufd_object_tombstone_user() to
170+
* destroy it and leave a tombstone.
171+
*/
172+
refcount_inc(&vdev->obj.users);
173+
iommufd_put_object(idev->ictx, &vdev->obj);
174+
mutex_unlock(&idev->igroup->lock);
175+
iommufd_object_tombstone_user(idev->ictx, &vdev->obj);
176+
return;
177+
178+
out_unlock:
179+
mutex_unlock(&idev->igroup->lock);
180+
}
181+
182+
void iommufd_device_pre_destroy(struct iommufd_object *obj)
183+
{
184+
struct iommufd_device *idev =
185+
container_of(obj, struct iommufd_device, obj);
186+
187+
/* Release the short term users on this */
188+
iommufd_device_remove_vdev(idev);
189+
}
190+
140191
void iommufd_device_destroy(struct iommufd_object *obj)
141192
{
142193
struct iommufd_device *idev =

drivers/iommu/iommufd/iommufd_private.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,8 @@ struct iommufd_device {
489489
/* always the physical device */
490490
struct device *dev;
491491
bool enforce_cache_coherency;
492+
struct iommufd_vdevice *vdev;
493+
bool destroying;
492494
};
493495

494496
static inline struct iommufd_device *
@@ -499,6 +501,7 @@ iommufd_get_device(struct iommufd_ucmd *ucmd, u32 id)
499501
struct iommufd_device, obj);
500502
}
501503

504+
void iommufd_device_pre_destroy(struct iommufd_object *obj);
502505
void iommufd_device_destroy(struct iommufd_object *obj);
503506
int iommufd_get_hw_info(struct iommufd_ucmd *ucmd);
504507

@@ -687,9 +690,18 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd);
687690
void iommufd_viommu_destroy(struct iommufd_object *obj);
688691
int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd);
689692
void iommufd_vdevice_destroy(struct iommufd_object *obj);
693+
void iommufd_vdevice_abort(struct iommufd_object *obj);
690694
int iommufd_hw_queue_alloc_ioctl(struct iommufd_ucmd *ucmd);
691695
void iommufd_hw_queue_destroy(struct iommufd_object *obj);
692696

697+
static inline struct iommufd_vdevice *
698+
iommufd_get_vdevice(struct iommufd_ctx *ictx, u32 id)
699+
{
700+
return container_of(iommufd_get_object(ictx, id,
701+
IOMMUFD_OBJ_VDEVICE),
702+
struct iommufd_vdevice, obj);
703+
}
704+
693705
#ifdef CONFIG_IOMMUFD_TEST
694706
int iommufd_test(struct iommufd_ucmd *ucmd);
695707
void iommufd_selftest_destroy(struct iommufd_object *obj);

drivers/iommu/iommufd/main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
655655
.destroy = iommufd_access_destroy_object,
656656
},
657657
[IOMMUFD_OBJ_DEVICE] = {
658+
.pre_destroy = iommufd_device_pre_destroy,
658659
.destroy = iommufd_device_destroy,
659660
},
660661
[IOMMUFD_OBJ_FAULT] = {
@@ -676,6 +677,7 @@ static const struct iommufd_object_ops iommufd_object_ops[] = {
676677
},
677678
[IOMMUFD_OBJ_VDEVICE] = {
678679
.destroy = iommufd_vdevice_destroy,
680+
.abort = iommufd_vdevice_abort,
679681
},
680682
[IOMMUFD_OBJ_VEVENTQ] = {
681683
.destroy = iommufd_veventq_destroy,

drivers/iommu/iommufd/viommu.c

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,37 @@ int iommufd_viommu_alloc_ioctl(struct iommufd_ucmd *ucmd)
110110
return rc;
111111
}
112112

113-
void iommufd_vdevice_destroy(struct iommufd_object *obj)
113+
void iommufd_vdevice_abort(struct iommufd_object *obj)
114114
{
115115
struct iommufd_vdevice *vdev =
116116
container_of(obj, struct iommufd_vdevice, obj);
117117
struct iommufd_viommu *viommu = vdev->viommu;
118+
struct iommufd_device *idev = vdev->idev;
119+
120+
lockdep_assert_held(&idev->igroup->lock);
118121

119122
if (vdev->destroy)
120123
vdev->destroy(vdev);
121124
/* xa_cmpxchg is okay to fail if alloc failed xa_cmpxchg previously */
122125
xa_cmpxchg(&viommu->vdevs, vdev->virt_id, vdev, NULL, GFP_KERNEL);
123126
refcount_dec(&viommu->obj.users);
127+
idev->vdev = NULL;
124128
put_device(vdev->dev);
125129
}
126130

131+
void iommufd_vdevice_destroy(struct iommufd_object *obj)
132+
{
133+
struct iommufd_vdevice *vdev =
134+
container_of(obj, struct iommufd_vdevice, obj);
135+
struct iommufd_device *idev = vdev->idev;
136+
struct iommufd_ctx *ictx = idev->ictx;
137+
138+
mutex_lock(&idev->igroup->lock);
139+
iommufd_vdevice_abort(obj);
140+
mutex_unlock(&idev->igroup->lock);
141+
iommufd_put_object(ictx, &idev->obj);
142+
}
143+
127144
int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
128145
{
129146
struct iommu_vdevice_alloc *cmd = ucmd->cmd;
@@ -153,6 +170,17 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
153170
goto out_put_idev;
154171
}
155172

173+
mutex_lock(&idev->igroup->lock);
174+
if (idev->destroying) {
175+
rc = -ENOENT;
176+
goto out_unlock_igroup;
177+
}
178+
179+
if (idev->vdev) {
180+
rc = -EEXIST;
181+
goto out_unlock_igroup;
182+
}
183+
156184
if (viommu->ops && viommu->ops->vdevice_size) {
157185
/*
158186
* It is a driver bug for:
@@ -171,14 +199,27 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
171199
ucmd->ictx, vdev_size, IOMMUFD_OBJ_VDEVICE);
172200
if (IS_ERR(vdev)) {
173201
rc = PTR_ERR(vdev);
174-
goto out_put_idev;
202+
goto out_unlock_igroup;
175203
}
176204

177205
vdev->virt_id = virt_id;
178206
vdev->dev = idev->dev;
179207
get_device(idev->dev);
180208
vdev->viommu = viommu;
181209
refcount_inc(&viommu->obj.users);
210+
/*
211+
* A short term users reference is held on the idev so long as we have
212+
* the pointer. iommufd_device_pre_destroy() will revoke it before the
213+
* idev real destruction.
214+
*/
215+
vdev->idev = idev;
216+
217+
/*
218+
* iommufd_device_destroy() delays until idev->vdev is NULL before
219+
* freeing the idev, which only happens once the vdev is finished
220+
* destruction.
221+
*/
222+
idev->vdev = vdev;
182223

183224
curr = xa_cmpxchg(&viommu->vdevs, virt_id, NULL, vdev, GFP_KERNEL);
184225
if (curr) {
@@ -197,12 +238,15 @@ int iommufd_vdevice_alloc_ioctl(struct iommufd_ucmd *ucmd)
197238
if (rc)
198239
goto out_abort;
199240
iommufd_object_finalize(ucmd->ictx, &vdev->obj);
200-
goto out_put_idev;
241+
goto out_unlock_igroup;
201242

202243
out_abort:
203244
iommufd_object_abort_and_destroy(ucmd->ictx, &vdev->obj);
245+
out_unlock_igroup:
246+
mutex_unlock(&idev->igroup->lock);
204247
out_put_idev:
205-
iommufd_put_object(ucmd->ictx, &idev->obj);
248+
if (rc)
249+
iommufd_put_object(ucmd->ictx, &idev->obj);
206250
out_put_viommu:
207251
iommufd_put_object(ucmd->ictx, &viommu->obj);
208252
return rc;

include/linux/iommufd.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ struct iommufd_viommu {
108108
struct iommufd_vdevice {
109109
struct iommufd_object obj;
110110
struct iommufd_viommu *viommu;
111+
struct iommufd_device *idev;
111112
struct device *dev;
112113

113114
/*

include/uapi/linux/iommufd.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,11 @@ struct iommu_viommu_alloc {
10701070
*
10711071
* Allocate a virtual device instance (for a physical device) against a vIOMMU.
10721072
* This instance holds the device's information (related to its vIOMMU) in a VM.
1073+
* User should use IOMMU_DESTROY to destroy the virtual device before
1074+
* destroying the physical device (by closing vfio_cdev fd). Otherwise the
1075+
* virtual device would be forcibly destroyed on physical device destruction,
1076+
* its vdevice_id would be permanently leaked (unremovable & unreusable) until
1077+
* iommu fd closed.
10731078
*/
10741079
struct iommu_vdevice_alloc {
10751080
__u32 size;

0 commit comments

Comments
 (0)