]>
Commit | Line | Data |
---|---|---|
b815ec5e AF |
1 | /* |
2 | * QTest testcase for VirtIO NIC | |
3 | * | |
4 | * Copyright (c) 2014 SUSE LINUX Products GmbH | |
5 | * | |
6 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
7 | * See the COPYING file in the top-level directory. | |
8 | */ | |
9 | ||
681c28a3 | 10 | #include "qemu/osdep.h" |
b815ec5e | 11 | #include "libqtest.h" |
2af40254 JW |
12 | #include "qemu-common.h" |
13 | #include "qemu/sockets.h" | |
2af40254 | 14 | #include "qemu/iov.h" |
a980f7f2 | 15 | #include "libqos/libqos-pc.h" |
30ca440e | 16 | #include "libqos/libqos-spapr.h" |
2af40254 JW |
17 | #include "libqos/virtio.h" |
18 | #include "libqos/virtio-pci.h" | |
452fcdbc | 19 | #include "qapi/qmp/qdict.h" |
2af40254 JW |
20 | #include "qemu/bswap.h" |
21 | #include "hw/virtio/virtio-net.h" | |
8ac9e205 | 22 | #include "standard-headers/linux/virtio_ids.h" |
ee3b850a | 23 | #include "standard-headers/linux/virtio_ring.h" |
9224709b IM |
24 | |
25 | #define PCI_SLOT_HP 0x06 | |
2af40254 | 26 | #define PCI_SLOT 0x04 |
b815ec5e | 27 | |
2af40254 JW |
28 | #define QVIRTIO_NET_TIMEOUT_US (30 * 1000 * 1000) |
29 | #define VNET_HDR_SIZE sizeof(struct virtio_net_hdr_mrg_rxbuf) | |
30 | ||
31 | static void test_end(void) | |
32 | { | |
33 | qtest_end(); | |
34 | } | |
35 | ||
36 | #ifndef _WIN32 | |
37 | ||
38 | static QVirtioPCIDevice *virtio_net_pci_init(QPCIBus *bus, int slot) | |
39 | { | |
40 | QVirtioPCIDevice *dev; | |
41 | ||
8ac9e205 | 42 | dev = qvirtio_pci_device_find(bus, VIRTIO_ID_NET); |
2af40254 | 43 | g_assert(dev != NULL); |
8ac9e205 | 44 | g_assert_cmphex(dev->vdev.device_type, ==, VIRTIO_ID_NET); |
2af40254 JW |
45 | |
46 | qvirtio_pci_device_enable(dev); | |
6b9cdf4c LV |
47 | qvirtio_reset(&dev->vdev); |
48 | qvirtio_set_acknowledge(&dev->vdev); | |
49 | qvirtio_set_driver(&dev->vdev); | |
2af40254 JW |
50 | |
51 | return dev; | |
52 | } | |
53 | ||
ae4c445c JW |
54 | GCC_FMT_ATTR(1, 2) |
55 | static QOSState *pci_test_start(const char *cmd, ...) | |
2af40254 | 56 | { |
3d95fb97 | 57 | QOSState *qs; |
ae4c445c | 58 | va_list ap; |
30ca440e | 59 | const char *arch = qtest_get_arch(); |
2af40254 | 60 | |
30ca440e | 61 | if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { |
ae4c445c JW |
62 | va_start(ap, cmd); |
63 | qs = qtest_pc_vboot(cmd, ap); | |
64 | va_end(ap); | |
3d95fb97 | 65 | } else if (strcmp(arch, "ppc64") == 0) { |
ae4c445c JW |
66 | va_start(ap, cmd); |
67 | qs = qtest_spapr_vboot(cmd, ap); | |
68 | va_end(ap); | |
3d95fb97 EB |
69 | } else { |
70 | g_printerr("virtio-net tests are only available on x86 or ppc64\n"); | |
71 | exit(EXIT_FAILURE); | |
30ca440e | 72 | } |
3d95fb97 EB |
73 | global_qtest = qs->qts; |
74 | return qs; | |
2af40254 JW |
75 | } |
76 | ||
6b9cdf4c | 77 | static void driver_init(QVirtioDevice *dev) |
2af40254 JW |
78 | { |
79 | uint32_t features; | |
80 | ||
6b9cdf4c | 81 | features = qvirtio_get_features(dev); |
2af40254 | 82 | features = features & ~(QVIRTIO_F_BAD_FEATURE | |
ee3b850a SH |
83 | (1u << VIRTIO_RING_F_INDIRECT_DESC) | |
84 | (1u << VIRTIO_RING_F_EVENT_IDX)); | |
6b9cdf4c | 85 | qvirtio_set_features(dev, features); |
2af40254 | 86 | |
6b9cdf4c | 87 | qvirtio_set_driver_ok(dev); |
2af40254 JW |
88 | } |
89 | ||
6b9cdf4c | 90 | static void rx_test(QVirtioDevice *dev, |
2af40254 JW |
91 | QGuestAllocator *alloc, QVirtQueue *vq, |
92 | int socket) | |
93 | { | |
94 | uint64_t req_addr; | |
95 | uint32_t free_head; | |
96 | char test[] = "TEST"; | |
97 | char buffer[64]; | |
98 | int len = htonl(sizeof(test)); | |
99 | struct iovec iov[] = { | |
100 | { | |
101 | .iov_base = &len, | |
102 | .iov_len = sizeof(len), | |
103 | }, { | |
104 | .iov_base = test, | |
105 | .iov_len = sizeof(test), | |
106 | }, | |
107 | }; | |
108 | int ret; | |
109 | ||
110 | req_addr = guest_alloc(alloc, 64); | |
111 | ||
112 | free_head = qvirtqueue_add(vq, req_addr, 64, true, false); | |
6b9cdf4c | 113 | qvirtqueue_kick(dev, vq, free_head); |
2af40254 JW |
114 | |
115 | ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test)); | |
116 | g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len)); | |
117 | ||
be3a6781 | 118 | qvirtio_wait_used_elem(dev, vq, free_head, NULL, QVIRTIO_NET_TIMEOUT_US); |
2af40254 JW |
119 | memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test)); |
120 | g_assert_cmpstr(buffer, ==, "TEST"); | |
121 | ||
122 | guest_free(alloc, req_addr); | |
123 | } | |
124 | ||
6b9cdf4c | 125 | static void tx_test(QVirtioDevice *dev, |
2af40254 JW |
126 | QGuestAllocator *alloc, QVirtQueue *vq, |
127 | int socket) | |
128 | { | |
129 | uint64_t req_addr; | |
130 | uint32_t free_head; | |
131 | uint32_t len; | |
132 | char buffer[64]; | |
133 | int ret; | |
134 | ||
135 | req_addr = guest_alloc(alloc, 64); | |
136 | memwrite(req_addr + VNET_HDR_SIZE, "TEST", 4); | |
137 | ||
138 | free_head = qvirtqueue_add(vq, req_addr, 64, false, false); | |
6b9cdf4c | 139 | qvirtqueue_kick(dev, vq, free_head); |
2af40254 | 140 | |
be3a6781 | 141 | qvirtio_wait_used_elem(dev, vq, free_head, NULL, QVIRTIO_NET_TIMEOUT_US); |
2af40254 JW |
142 | guest_free(alloc, req_addr); |
143 | ||
144 | ret = qemu_recv(socket, &len, sizeof(len), 0); | |
145 | g_assert_cmpint(ret, ==, sizeof(len)); | |
146 | len = ntohl(len); | |
147 | ||
148 | ret = qemu_recv(socket, buffer, len, 0); | |
149 | g_assert_cmpstr(buffer, ==, "TEST"); | |
150 | } | |
151 | ||
6b9cdf4c | 152 | static void rx_stop_cont_test(QVirtioDevice *dev, |
8887f84c JW |
153 | QGuestAllocator *alloc, QVirtQueue *vq, |
154 | int socket) | |
155 | { | |
156 | uint64_t req_addr; | |
157 | uint32_t free_head; | |
158 | char test[] = "TEST"; | |
159 | char buffer[64]; | |
160 | int len = htonl(sizeof(test)); | |
1ec3b71c | 161 | QDict *rsp; |
8887f84c JW |
162 | struct iovec iov[] = { |
163 | { | |
164 | .iov_base = &len, | |
165 | .iov_len = sizeof(len), | |
166 | }, { | |
167 | .iov_base = test, | |
168 | .iov_len = sizeof(test), | |
169 | }, | |
170 | }; | |
171 | int ret; | |
172 | ||
173 | req_addr = guest_alloc(alloc, 64); | |
174 | ||
175 | free_head = qvirtqueue_add(vq, req_addr, 64, true, false); | |
6b9cdf4c | 176 | qvirtqueue_kick(dev, vq, free_head); |
8887f84c | 177 | |
1ec3b71c | 178 | rsp = qmp("{ 'execute' : 'stop'}"); |
cb3e7f08 | 179 | qobject_unref(rsp); |
8887f84c JW |
180 | |
181 | ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test)); | |
182 | g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len)); | |
183 | ||
184 | /* We could check the status, but this command is more importantly to | |
185 | * ensure the packet data gets queued in QEMU, before we do 'cont'. | |
186 | */ | |
1ec3b71c | 187 | rsp = qmp("{ 'execute' : 'query-status'}"); |
cb3e7f08 | 188 | qobject_unref(rsp); |
1ec3b71c | 189 | rsp = qmp("{ 'execute' : 'cont'}"); |
cb3e7f08 | 190 | qobject_unref(rsp); |
8887f84c | 191 | |
be3a6781 | 192 | qvirtio_wait_used_elem(dev, vq, free_head, NULL, QVIRTIO_NET_TIMEOUT_US); |
8887f84c JW |
193 | memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test)); |
194 | g_assert_cmpstr(buffer, ==, "TEST"); | |
195 | ||
196 | guest_free(alloc, req_addr); | |
197 | } | |
198 | ||
6b9cdf4c | 199 | static void send_recv_test(QVirtioDevice *dev, |
2af40254 JW |
200 | QGuestAllocator *alloc, QVirtQueue *rvq, |
201 | QVirtQueue *tvq, int socket) | |
b815ec5e | 202 | { |
6b9cdf4c LV |
203 | rx_test(dev, alloc, rvq, socket); |
204 | tx_test(dev, alloc, tvq, socket); | |
b815ec5e AF |
205 | } |
206 | ||
6b9cdf4c | 207 | static void stop_cont_test(QVirtioDevice *dev, |
8887f84c JW |
208 | QGuestAllocator *alloc, QVirtQueue *rvq, |
209 | QVirtQueue *tvq, int socket) | |
210 | { | |
6b9cdf4c | 211 | rx_stop_cont_test(dev, alloc, rvq, socket); |
8887f84c JW |
212 | } |
213 | ||
2af40254 JW |
214 | static void pci_basic(gconstpointer data) |
215 | { | |
216 | QVirtioPCIDevice *dev; | |
a980f7f2 | 217 | QOSState *qs; |
2af40254 | 218 | QVirtQueuePCI *tx, *rx; |
6b9cdf4c | 219 | void (*func) (QVirtioDevice *dev, |
2af40254 JW |
220 | QGuestAllocator *alloc, |
221 | QVirtQueue *rvq, | |
222 | QVirtQueue *tvq, | |
223 | int socket) = data; | |
224 | int sv[2], ret; | |
225 | ||
226 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv); | |
227 | g_assert_cmpint(ret, !=, -1); | |
228 | ||
ae4c445c JW |
229 | qs = pci_test_start("-netdev socket,fd=%d,id=hs0 -device " |
230 | "virtio-net-pci,netdev=hs0", sv[1]); | |
a980f7f2 | 231 | dev = virtio_net_pci_init(qs->pcibus, PCI_SLOT); |
2af40254 | 232 | |
a980f7f2 LV |
233 | rx = (QVirtQueuePCI *)qvirtqueue_setup(&dev->vdev, qs->alloc, 0); |
234 | tx = (QVirtQueuePCI *)qvirtqueue_setup(&dev->vdev, qs->alloc, 1); | |
2af40254 | 235 | |
6b9cdf4c | 236 | driver_init(&dev->vdev); |
a980f7f2 | 237 | func(&dev->vdev, qs->alloc, &rx->vq, &tx->vq, sv[0]); |
2af40254 JW |
238 | |
239 | /* End test */ | |
240 | close(sv[0]); | |
a980f7f2 LV |
241 | qvirtqueue_cleanup(dev->vdev.bus, &tx->vq, qs->alloc); |
242 | qvirtqueue_cleanup(dev->vdev.bus, &rx->vq, qs->alloc); | |
2af40254 | 243 | qvirtio_pci_device_disable(dev); |
1ec3b71c | 244 | g_free(dev->pdev); |
2af40254 | 245 | g_free(dev); |
a980f7f2 | 246 | qtest_shutdown(qs); |
2af40254 | 247 | } |
118cafff JW |
248 | |
249 | static void large_tx(gconstpointer data) | |
250 | { | |
251 | QVirtioPCIDevice *dev; | |
252 | QOSState *qs; | |
253 | QVirtQueuePCI *tx, *rx; | |
254 | QVirtQueue *vq; | |
255 | uint64_t req_addr; | |
256 | uint32_t free_head; | |
257 | size_t alloc_size = (size_t)data / 64; | |
258 | int i; | |
259 | ||
260 | qs = pci_test_start("-netdev hubport,id=hp0,hubid=0 " | |
261 | "-device virtio-net-pci,netdev=hp0"); | |
262 | dev = virtio_net_pci_init(qs->pcibus, PCI_SLOT); | |
263 | ||
264 | rx = (QVirtQueuePCI *)qvirtqueue_setup(&dev->vdev, qs->alloc, 0); | |
265 | tx = (QVirtQueuePCI *)qvirtqueue_setup(&dev->vdev, qs->alloc, 1); | |
266 | ||
267 | driver_init(&dev->vdev); | |
268 | vq = &tx->vq; | |
269 | ||
270 | /* Bypass the limitation by pointing several descriptors to a single | |
271 | * smaller area */ | |
272 | req_addr = guest_alloc(qs->alloc, alloc_size); | |
273 | free_head = qvirtqueue_add(vq, req_addr, alloc_size, false, true); | |
274 | ||
275 | for (i = 0; i < 64; i++) { | |
276 | qvirtqueue_add(vq, req_addr, alloc_size, false, i != 63); | |
277 | } | |
278 | qvirtqueue_kick(&dev->vdev, vq, free_head); | |
279 | ||
280 | qvirtio_wait_used_elem(&dev->vdev, vq, free_head, NULL, | |
281 | QVIRTIO_NET_TIMEOUT_US); | |
282 | ||
283 | qvirtqueue_cleanup(dev->vdev.bus, &tx->vq, qs->alloc); | |
284 | qvirtqueue_cleanup(dev->vdev.bus, &rx->vq, qs->alloc); | |
285 | qvirtio_pci_device_disable(dev); | |
286 | g_free(dev->pdev); | |
287 | g_free(dev); | |
288 | qtest_shutdown(qs); | |
289 | } | |
2af40254 JW |
290 | #endif |
291 | ||
9224709b IM |
292 | static void hotplug(void) |
293 | { | |
30ca440e LV |
294 | const char *arch = qtest_get_arch(); |
295 | ||
2af40254 JW |
296 | qtest_start("-device virtio-net-pci"); |
297 | ||
82cab70b MA |
298 | qtest_qmp_device_add("virtio-net-pci", "net1", |
299 | "{'addr': %s}", stringify(PCI_SLOT_HP)); | |
30ca440e LV |
300 | |
301 | if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { | |
302 | qpci_unplug_acpi_device_test("net1", PCI_SLOT_HP); | |
303 | } | |
2af40254 JW |
304 | |
305 | test_end(); | |
9224709b IM |
306 | } |
307 | ||
b815ec5e AF |
308 | int main(int argc, char **argv) |
309 | { | |
b815ec5e | 310 | g_test_init(&argc, &argv, NULL); |
2af40254 JW |
311 | #ifndef _WIN32 |
312 | qtest_add_data_func("/virtio/net/pci/basic", send_recv_test, pci_basic); | |
8887f84c JW |
313 | qtest_add_data_func("/virtio/net/pci/rx_stop_cont", |
314 | stop_cont_test, pci_basic); | |
118cafff JW |
315 | qtest_add_data_func("/virtio/net/pci/large_tx_uint_max", |
316 | (gconstpointer)UINT_MAX, large_tx); | |
317 | qtest_add_data_func("/virtio/net/pci/large_tx_net_bufsize", | |
318 | (gconstpointer)NET_BUFSIZE, large_tx); | |
2af40254 | 319 | #endif |
9224709b | 320 | qtest_add_func("/virtio/net/pci/hotplug", hotplug); |
b815ec5e | 321 | |
9be38598 | 322 | return g_test_run(); |
b815ec5e | 323 | } |