1 /* 2 * QEMU device plug/unplug handling 3 * 4 * Copyright (C) 2019 Red Hat Inc. 5 * 6 * Authors: 7 * David Hildenbrand <david@redhat.com> 8 * 9 * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 * See the COPYING file in the top-level directory. 11 */ 12 13 #include "qemu/osdep.h" 14 #include "libqtest.h" 15 #include "qobject/qdict.h" 16 #include "qobject/qstring.h" 17 18 static void wait_device_deleted_event(QTestState *qtest, const char *id) 19 { 20 QDict *resp, *data; 21 QString *qstr; 22 23 /* 24 * Other devices might get removed along with the removed device. Skip 25 * these. The device of interest will be the last one. 26 */ 27 for (;;) { 28 resp = qtest_qmp_eventwait_ref(qtest, "DEVICE_DELETED"); 29 data = qdict_get_qdict(resp, "data"); 30 if (!data || !qdict_get(data, "device")) { 31 qobject_unref(resp); 32 continue; 33 } 34 qstr = qobject_to(QString, qdict_get(data, "device")); 35 g_assert(qstr); 36 if (!strcmp(qstring_get_str(qstr), id)) { 37 qobject_unref(resp); 38 break; 39 } 40 qobject_unref(resp); 41 } 42 } 43 44 static void process_device_remove(QTestState *qtest, const char *id) 45 { 46 /* 47 * Request device removal. As the guest is not running, the request won't 48 * be processed. However during system reset, the removal will be 49 * handled, removing the device. 50 */ 51 qtest_qmp_device_del_send(qtest, id); 52 qtest_system_reset_nowait(qtest); 53 wait_device_deleted_event(qtest, id); 54 } 55 56 static void test_pci_unplug_request(void) 57 { 58 QTestState *qtest; 59 const char *arch = qtest_get_arch(); 60 const char *machine_addition = ""; 61 62 if (!qtest_has_device("virtio-mouse-pci")) { 63 g_test_skip("Device virtio-mouse-pci not available"); 64 return; 65 } 66 67 if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { 68 machine_addition = "-machine pc"; 69 } 70 71 qtest = qtest_initf("%s -device virtio-mouse-pci,id=dev0", 72 machine_addition); 73 74 process_device_remove(qtest, "dev0"); 75 76 qtest_quit(qtest); 77 } 78 79 static void test_q35_pci_unplug_request(void) 80 { 81 QTestState *qtest; 82 83 if (!qtest_has_device("virtio-mouse-pci")) { 84 g_test_skip("Device virtio-mouse-pci not available"); 85 return; 86 } 87 88 qtest = qtest_initf("-machine q35 " 89 "-device pcie-root-port,id=p1 " 90 "-device pcie-pci-bridge,bus=p1,id=b1 " 91 "-device virtio-mouse-pci,bus=b1,id=dev0"); 92 93 process_device_remove(qtest, "dev0"); 94 95 qtest_quit(qtest); 96 } 97 98 static void test_pci_unplug_json_request(void) 99 { 100 QTestState *qtest; 101 const char *arch = qtest_get_arch(); 102 const char *machine_addition = ""; 103 104 if (!qtest_has_device("virtio-mouse-pci")) { 105 g_test_skip("Device virtio-mouse-pci not available"); 106 return; 107 } 108 109 if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { 110 machine_addition = "-machine pc"; 111 } 112 113 qtest = qtest_initf( 114 "%s -device \"{'driver': 'virtio-mouse-pci', 'id': 'dev0'}\"", 115 machine_addition); 116 117 process_device_remove(qtest, "dev0"); 118 119 qtest_quit(qtest); 120 } 121 122 static void test_q35_pci_unplug_json_request(void) 123 { 124 QTestState *qtest; 125 const char *port = "-device \"{'driver': 'pcie-root-port', " 126 "'id': 'p1'}\""; 127 128 const char *bridge = "-device \"{'driver': 'pcie-pci-bridge', " 129 "'id': 'b1', " 130 "'bus': 'p1'}\""; 131 132 const char *device = "-device \"{'driver': 'virtio-mouse-pci', " 133 "'bus': 'b1', " 134 "'id': 'dev0'}\""; 135 136 if (!qtest_has_device("virtio-mouse-pci")) { 137 g_test_skip("Device virtio-mouse-pci not available"); 138 return; 139 } 140 141 qtest = qtest_initf("-machine q35 %s %s %s", port, bridge, device); 142 143 process_device_remove(qtest, "dev0"); 144 145 qtest_quit(qtest); 146 } 147 148 static void test_ccw_unplug(void) 149 { 150 QTestState *qtest; 151 152 if (!qtest_has_device("virtio-balloon-ccw")) { 153 g_test_skip("Device virtio-balloon-ccw not available"); 154 return; 155 } 156 157 qtest = qtest_initf("-device virtio-balloon-ccw,id=dev0"); 158 159 qtest_qmp_device_del_send(qtest, "dev0"); 160 wait_device_deleted_event(qtest, "dev0"); 161 162 qtest_quit(qtest); 163 } 164 165 static void test_spapr_cpu_unplug_request(void) 166 { 167 QTestState *qtest; 168 169 qtest = qtest_initf("-cpu power9_v2.2 -smp 1,maxcpus=2 " 170 "-device power9_v2.2-spapr-cpu-core,core-id=1,id=dev0"); 171 172 /* similar to test_pci_unplug_request */ 173 process_device_remove(qtest, "dev0"); 174 175 qtest_quit(qtest); 176 } 177 178 static void test_spapr_memory_unplug_request(void) 179 { 180 QTestState *qtest; 181 182 qtest = qtest_initf("-m 256M,slots=1,maxmem=768M " 183 "-object memory-backend-ram,id=mem0,size=512M " 184 "-device pc-dimm,id=dev0,memdev=mem0"); 185 186 /* similar to test_pci_unplug_request */ 187 process_device_remove(qtest, "dev0"); 188 189 qtest_quit(qtest); 190 } 191 192 static void test_spapr_phb_unplug_request(void) 193 { 194 QTestState *qtest; 195 196 qtest = qtest_initf("-device spapr-pci-host-bridge,index=1,id=dev0"); 197 198 /* similar to test_pci_unplug_request */ 199 process_device_remove(qtest, "dev0"); 200 201 qtest_quit(qtest); 202 } 203 204 int main(int argc, char **argv) 205 { 206 const char *arch = qtest_get_arch(); 207 208 g_test_init(&argc, &argv, NULL); 209 210 /* 211 * We need a system that will process unplug requests during system resets 212 * and does not do PCI surprise removal. This holds for x86 ACPI, 213 * s390x and spapr. 214 */ 215 qtest_add_func("/device-plug/pci-unplug-request", 216 test_pci_unplug_request); 217 qtest_add_func("/device-plug/pci-unplug-json-request", 218 test_pci_unplug_json_request); 219 220 if (!strcmp(arch, "s390x")) { 221 qtest_add_func("/device-plug/ccw-unplug", 222 test_ccw_unplug); 223 } 224 225 if (!strcmp(arch, "ppc64")) { 226 qtest_add_func("/device-plug/spapr-cpu-unplug-request", 227 test_spapr_cpu_unplug_request); 228 qtest_add_func("/device-plug/spapr-memory-unplug-request", 229 test_spapr_memory_unplug_request); 230 qtest_add_func("/device-plug/spapr-phb-unplug-request", 231 test_spapr_phb_unplug_request); 232 } 233 234 if (!strcmp(arch, "x86_64") && qtest_has_machine("q35")) { 235 qtest_add_func("/device-plug/q35-pci-unplug-request", 236 test_q35_pci_unplug_request); 237 qtest_add_func("/device-plug/q35-pci-unplug-json-request", 238 test_q35_pci_unplug_json_request); 239 } 240 241 return g_test_run(); 242 } 243