From: Zhenzhong Duan <[email protected]> When a PASID entry becomes invalid, all resources allocated for that entry, including the FAULTQ object and fault_fd, must be released.
Calling qemu_set_fd_handler() detaches the fault_fd I/O read handler and wakes up the main loop thread from poll(). However, a race window exists where iommufd_backend_free_id(fault_id) can be invoked before poll() exits and releases its reference to the fault_fd file. In this scenario, freeing the FAULTQ object returns -EBUSY because the opened fault_fd file still holds an active reference to the FAULTQ object. Defer FAULTQ resource teardown to a bottom half. This ensures poll() has fully exited and dropped the file reference before the fault_fd is closed and the FAULTQ object ID is freed. Additionally, keep the IOMMUFD backend FD open via iommufd_backend_connect() across the asynchronous bottom half execution to ensure FAULTQ object freed successfully. Suggested-by: Shameer Kolothum <[email protected]> Signed-off-by: Zhenzhong Duan <[email protected]> Reviewed-by: Yi Liu <[email protected]> Tested-by: Xudong Hao <[email protected]> Reviewed-by: Clement Mathieu--Drif <[email protected]> Reviewed-by: Michael S. Tsirkin <[email protected]> Signed-off-by: Michael S. Tsirkin <[email protected]> Message-ID: <[email protected]> --- hw/i386/intel_iommu_accel.c | 59 +++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/hw/i386/intel_iommu_accel.c b/hw/i386/intel_iommu_accel.c index 243501f137..7db8319ad4 100644 --- a/hw/i386/intel_iommu_accel.c +++ b/hw/i386/intel_iommu_accel.c @@ -271,17 +271,70 @@ free_faultq: return false; } +typedef struct IOMMUFaultQueue { + IOMMUFDBackend *iommufd; + uint32_t id; + int fd; + QLIST_HEAD(, VTDPRQEntry) vtd_prq_list; +} IOMMUFaultQueue; + +static void faultq_teardown_bh(void *opaque) +{ + IOMMUFaultQueue *fq = opaque; + VTDPRQEntry *prqe, *next; + + QLIST_FOREACH_SAFE(prqe, &fq->vtd_prq_list, next, next) { + QLIST_REMOVE(prqe, next); + g_free(prqe); + } + + qemu_set_fd_handler(fq->fd, NULL, NULL, NULL); + close(fq->fd); + iommufd_backend_free_id(fq->iommufd, fq->id); + iommufd_backend_disconnect(fq->iommufd); + + g_free(fq); +} + static void vtd_destroy_old_fs_faultq(VTDAccelPASIDCacheEntry *vtd_pce) { + HostIOMMUDeviceIOMMUFD *idev = + HOST_IOMMU_DEVICE_IOMMUFD(vtd_pce->vtd_hiod->hiod); + IOMMUFaultQueue *fq; + if (vtd_pce->fault_fd < 0) { return; } - qemu_set_fd_handler(vtd_pce->fault_fd, NULL, NULL, NULL); - vtd_destroy_fs_faultq(vtd_pce->vtd_hiod, vtd_pce->fault_id, - vtd_pce->fault_fd); + fq = g_malloc(sizeof(*fq)); + fq->iommufd = idev->iommufd; + fq->fd = vtd_pce->fault_fd; + fq->id = vtd_pce->fault_id; vtd_pce->fault_id = 0; vtd_pce->fault_fd = -1; + fq->vtd_prq_list.lh_first = vtd_pce->vtd_prq_list.lh_first; + QLIST_INIT(&vtd_pce->vtd_prq_list); + + /* + * During a VFIO device hot-unplug, if this is the last device connected + * to the IOMMUFDBackend, the backend user count (iommufd->users) will + * drop to zero, triggering iommufd_backend_disconnect() which closes + * the backend's opened container FD. + * + * Since the teardown of this fault queue (faultq_teardown_bh) is + * scheduled asynchronously as a bottom half, we must temporarily + * increment the connection user count of the iommufd backend here. + * This guarantees the backend connection remains open and its opened + * FD remains active until the async BH completes. + * + * Incrementing the user count is guaranteed to succeed here because the + * VFIO device is still registered as an active user of the backend at + * this point in the teardown sequence. + */ + iommufd_backend_connect(fq->iommufd, &error_abort); + + aio_bh_schedule_oneshot(iohandler_get_aio_context(), + faultq_teardown_bh, fq); } static void vtd_setup_fs_faultq(VTDAccelPASIDCacheEntry *vtd_pce, -- MST
