Add a qtest that hot-adds an nec-usb-xhci controller, requests unplug,
resets the system to process the request, and waits for DEVICE_DELETED.
This covers the xHCI PCI host-link refcount cycle by verifying that
device_finalize() runs after unplug.

Signed-off-by: Xiangfeng Cai <[email protected]>
---
 tests/qtest/usb-hcd-xhci-test.c | 67 +++++++++++++++++++++++++++++++++
 1 file changed, 67 insertions(+)

diff --git a/tests/qtest/usb-hcd-xhci-test.c b/tests/qtest/usb-hcd-xhci-test.c
index 0cccfd85a6..b58fa1e2da 100644
--- a/tests/qtest/usb-hcd-xhci-test.c
+++ b/tests/qtest/usb-hcd-xhci-test.c
@@ -10,6 +10,72 @@
 #include "qemu/osdep.h"
 #include "libqtest-single.h"
 #include "libqos/usb.h"
+#include "qobject/qdict.h"
+
+static void wait_device_deleted_event(QTestState *qtest, const char *id)
+{
+    QDict *resp, *data;
+    const char *device;
+
+    /*
+     * Other devices might get removed along with the removed device. Skip
+     * these. The device of interest will be the last one.
+     */
+    for (;;) {
+        resp = qtest_qmp_eventwait_ref(qtest, "DEVICE_DELETED");
+        data = qdict_get_qdict(resp, "data");
+        device = data ? qdict_get_try_str(data, "device") : NULL;
+        if (device && !strcmp(device, id)) {
+            qobject_unref(resp);
+            break;
+        }
+        qobject_unref(resp);
+    }
+}
+
+/*
+ * Regression test for the xHCI-PCI "host" strong-link reference cycle.
+ *
+ * The xHCI PCI wrapper embeds an xhci-core child whose strong "host" link
+ * points back at the PCI device, forming a refcount cycle. If
+ * usb_xhci_pci_exit() does not break that cycle, the device's refcount never
+ * reaches 0 on unplug, device_finalize() never runs, and therefore the
+ * DEVICE_DELETED event (emitted from device_finalize()) is never sent.
+ *
+ * This test hot-plugs an xHCI controller into an ACPI-hotpluggable bus,
+ * requests its removal and waits for DEVICE_DELETED. Without the fix the event
+ * is never delivered (device_finalize() is blocked), so the test would
+ * hang/time out.
+ */
+static void test_xhci_unplug_finalize(void)
+{
+    QTestState *qtest;
+    const char *arch = qtest_get_arch();
+
+    if (strcmp(arch, "i386") != 0 && strcmp(arch, "x86_64") != 0) {
+        g_test_skip("Test only runs on x86 (ACPI PCI hotplug)");
+        return;
+    }
+    if (!qtest_has_device("nec-usb-xhci")) {
+        g_test_skip("Device nec-usb-xhci not available");
+        return;
+    }
+
+    qtest = qtest_initf("-machine pc");
+
+    qtest_qmp_device_add(qtest, "nec-usb-xhci", "xhci-finalize", "{}");
+
+    /*
+     * Request device removal. As the guest is not running, the unplug request
+     * won't be processed until the next system reset, which performs the
+     * removal and triggers device_finalize() (and thus DEVICE_DELETED).
+     */
+    qtest_qmp_device_del_send(qtest, "xhci-finalize");
+    qtest_system_reset_nowait(qtest);
+    wait_device_deleted_event(qtest, "xhci-finalize");
+
+    qtest_quit(qtest);
+}
 
 static void test_xhci_hotplug(void)
 {
@@ -50,6 +116,7 @@ int main(int argc, char **argv)
     g_test_init(&argc, &argv, NULL);
 
     qtest_add_func("/xhci/pci/hotplug", test_xhci_hotplug);
+    qtest_add_func("/xhci/pci/unplug/finalize", test_xhci_unplug_finalize);
     if (qtest_has_device("usb-uas")) {
         qtest_add_func("/xhci/pci/hotplug/usb-uas", test_usb_uas_hotplug);
     }
-- 
2.55.0.rc1

Reply via email to