Commit 7ab03f5485 for qemu.org

commit 7ab03f54856139d9e55ed4cd369ef77616422ded
Author: Zhenzhong Duan <zhenzhong.duan@intel.com>
Date:   Tue Oct 20 17:19:54 2026 +0800

    intel_iommu_accel: Teardown FAULTQ resources in bottom half

    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 <skolothumtho@nvidia.com>
    Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
    Reviewed-by: Yi Liu <yi.l.liu@intel.com>
    Tested-by: Xudong Hao <xudong.hao@intel.com>
    Reviewed-by: Clement Mathieu--Drif <clement.mathieu--drif@bull.com>
    Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
    Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
    Message-ID: <20261020091955.1607161-7-zhenzhong.duan@intel.com>

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,