@samitouri / QOSamiQemu / commits / ba54d7e66e

tests/qtest: add xhci-pci unplug finalize regression test

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 <caixiangfeng@bytedance.com> Message-ID: <20260618110119.3084296-3-caixiangfeng@bytedance.com> Signed-off-by: Thomas Huth <thuth@redhat.com>

Xiangfeng Cai committed Jun 18, 2026 at 19:01 UTC ba54d7e66e251764f06d6231c5832d7c7239aa8b
1 file changed +67
tests/qtest/usb-hcd-xhci-test.c
+67
@@ -10,6 +10,72 @@
10 #include "qemu/osdep.h"
11 #include "libqtest-single.h"
12 #include "libqos/usb.h"
13 +#include "qobject/qdict.h"
14 +
15 +static void wait_device_deleted_event(QTestState *qtest, const char *id)
16 +{
17 + QDict *resp, *data;
18 + const char *device;
19 +
20 + /*
21 + * Other devices might get removed along with the removed device. Skip
22 + * these. The device of interest will be the last one.
23 + */
24 + for (;;) {
25 + resp = qtest_qmp_eventwait_ref(qtest, "DEVICE_DELETED");
26 + data = qdict_get_qdict(resp, "data");
27 + device = data ? qdict_get_try_str(data, "device") : NULL;
28 + if (device && !strcmp(device, id)) {
29 + qobject_unref(resp);
30 + break;
31 + }
32 + qobject_unref(resp);
33 + }
34 +}
35 +
36 +/*
37 + * Regression test for the xHCI-PCI "host" strong-link reference cycle.
38 + *
39 + * The xHCI PCI wrapper embeds an xhci-core child whose strong "host" link
40 + * points back at the PCI device, forming a refcount cycle. If
41 + * usb_xhci_pci_exit() does not break that cycle, the device's refcount never
42 + * reaches 0 on unplug, device_finalize() never runs, and therefore the
43 + * DEVICE_DELETED event (emitted from device_finalize()) is never sent.
44 + *
45 + * This test hot-plugs an xHCI controller into an ACPI-hotpluggable bus,
46 + * requests its removal and waits for DEVICE_DELETED. Without the fix the event
47 + * is never delivered (device_finalize() is blocked), so the test would
48 + * hang/time out.
49 + */
50 +static void test_xhci_unplug_finalize(void)
51 +{
52 + QTestState *qtest;
53 + const char *arch = qtest_get_arch();
54 +
55 + if (strcmp(arch, "i386") != 0 && strcmp(arch, "x86_64") != 0) {
56 + g_test_skip("Test only runs on x86 (ACPI PCI hotplug)");
57 + return;
58 + }
59 + if (!qtest_has_device("nec-usb-xhci")) {
60 + g_test_skip("Device nec-usb-xhci not available");
61 + return;
62 + }
63 +
64 + qtest = qtest_initf("-machine pc");
65 +
66 + qtest_qmp_device_add(qtest, "nec-usb-xhci", "xhci-finalize", "{}");
67 +
68 + /*
69 + * Request device removal. As the guest is not running, the unplug request
70 + * won't be processed until the next system reset, which performs the
71 + * removal and triggers device_finalize() (and thus DEVICE_DELETED).
72 + */
73 + qtest_qmp_device_del_send(qtest, "xhci-finalize");
74 + qtest_system_reset_nowait(qtest);
75 + wait_device_deleted_event(qtest, "xhci-finalize");
76 +
77 + qtest_quit(qtest);
78 +}
79
80 static void test_xhci_hotplug(void)
81 {
@@ -50,6 +116,7 @@ int main(int argc, char **argv)
116 g_test_init(&argc, &argv, NULL);
117
118 qtest_add_func("/xhci/pci/hotplug", test_xhci_hotplug);
119 + qtest_add_func("/xhci/pci/unplug/finalize", test_xhci_unplug_finalize);
120 if (qtest_has_device("usb-uas")) {
121 qtest_add_func("/xhci/pci/hotplug/usb-uas", test_usb_uas_hotplug);
122 }