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
wait_device_deleted_event(QTestState * qtest,const char * id)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
process_device_remove(QTestState * qtest,const char * id)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
test_pci_unplug_request(void)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
test_q35_pci_unplug_request(void)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
test_pci_unplug_json_request(void)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
test_q35_pci_unplug_json_request(void)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
test_ccw_unplug(void)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
test_spapr_cpu_unplug_request(void)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
test_spapr_memory_unplug_request(void)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
test_spapr_phb_unplug_request(void)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
main(int argc,char ** argv)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