]>
Commit | Line | Data |
---|---|---|
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 | ||
10 | #include "qemu/osdep.h" | |
11 | #include "libqtest.h" | |
12 | #include "qemu/iov.h" | |
13 | #include "qapi/qmp/qdict.h" | |
14 | #include "hw/virtio/virtio-net.h" | |
15 | #include "libqos/qgraph.h" | |
16 | #include "libqos/virtio-net.h" | |
17 | ||
18 | #ifndef ETH_P_RARP | |
19 | #define ETH_P_RARP 0x8035 | |
20 | #endif | |
21 | ||
22 | #define PCI_SLOT_HP 0x06 | |
23 | #define PCI_SLOT 0x04 | |
24 | ||
25 | #define QVIRTIO_NET_TIMEOUT_US (30 * 1000 * 1000) | |
26 | #define VNET_HDR_SIZE sizeof(struct virtio_net_hdr_mrg_rxbuf) | |
27 | ||
28 | #ifndef _WIN32 | |
29 | ||
30 | static void rx_test(QVirtioDevice *dev, | |
31 | QGuestAllocator *alloc, QVirtQueue *vq, | |
32 | int socket) | |
33 | { | |
34 | uint64_t req_addr; | |
35 | uint32_t free_head; | |
36 | char test[] = "TEST"; | |
37 | char buffer[64]; | |
38 | int len = htonl(sizeof(test)); | |
39 | struct iovec iov[] = { | |
40 | { | |
41 | .iov_base = &len, | |
42 | .iov_len = sizeof(len), | |
43 | }, { | |
44 | .iov_base = test, | |
45 | .iov_len = sizeof(test), | |
46 | }, | |
47 | }; | |
48 | int ret; | |
49 | ||
50 | req_addr = guest_alloc(alloc, 64); | |
51 | ||
52 | free_head = qvirtqueue_add(vq, req_addr, 64, true, false); | |
53 | qvirtqueue_kick(dev, vq, free_head); | |
54 | ||
55 | ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test)); | |
56 | g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len)); | |
57 | ||
58 | qvirtio_wait_used_elem(dev, vq, free_head, NULL, QVIRTIO_NET_TIMEOUT_US); | |
59 | memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test)); | |
60 | g_assert_cmpstr(buffer, ==, "TEST"); | |
61 | ||
62 | guest_free(alloc, req_addr); | |
63 | } | |
64 | ||
65 | static void tx_test(QVirtioDevice *dev, | |
66 | QGuestAllocator *alloc, QVirtQueue *vq, | |
67 | int socket) | |
68 | { | |
69 | uint64_t req_addr; | |
70 | uint32_t free_head; | |
71 | uint32_t len; | |
72 | char buffer[64]; | |
73 | int ret; | |
74 | ||
75 | req_addr = guest_alloc(alloc, 64); | |
76 | memwrite(req_addr + VNET_HDR_SIZE, "TEST", 4); | |
77 | ||
78 | free_head = qvirtqueue_add(vq, req_addr, 64, false, false); | |
79 | qvirtqueue_kick(dev, vq, free_head); | |
80 | ||
81 | qvirtio_wait_used_elem(dev, vq, free_head, NULL, QVIRTIO_NET_TIMEOUT_US); | |
82 | guest_free(alloc, req_addr); | |
83 | ||
84 | ret = qemu_recv(socket, &len, sizeof(len), 0); | |
85 | g_assert_cmpint(ret, ==, sizeof(len)); | |
86 | len = ntohl(len); | |
87 | ||
88 | ret = qemu_recv(socket, buffer, len, 0); | |
89 | g_assert_cmpstr(buffer, ==, "TEST"); | |
90 | } | |
91 | ||
92 | static void rx_stop_cont_test(QVirtioDevice *dev, | |
93 | QGuestAllocator *alloc, QVirtQueue *vq, | |
94 | int socket) | |
95 | { | |
96 | uint64_t req_addr; | |
97 | uint32_t free_head; | |
98 | char test[] = "TEST"; | |
99 | char buffer[64]; | |
100 | int len = htonl(sizeof(test)); | |
101 | QDict *rsp; | |
102 | struct iovec iov[] = { | |
103 | { | |
104 | .iov_base = &len, | |
105 | .iov_len = sizeof(len), | |
106 | }, { | |
107 | .iov_base = test, | |
108 | .iov_len = sizeof(test), | |
109 | }, | |
110 | }; | |
111 | int ret; | |
112 | ||
113 | req_addr = guest_alloc(alloc, 64); | |
114 | ||
115 | free_head = qvirtqueue_add(vq, req_addr, 64, true, false); | |
116 | qvirtqueue_kick(dev, vq, free_head); | |
117 | ||
118 | rsp = qmp("{ 'execute' : 'stop'}"); | |
119 | qobject_unref(rsp); | |
120 | ||
121 | ret = iov_send(socket, iov, 2, 0, sizeof(len) + sizeof(test)); | |
122 | g_assert_cmpint(ret, ==, sizeof(test) + sizeof(len)); | |
123 | ||
124 | /* We could check the status, but this command is more importantly to | |
125 | * ensure the packet data gets queued in QEMU, before we do 'cont'. | |
126 | */ | |
127 | rsp = qmp("{ 'execute' : 'query-status'}"); | |
128 | qobject_unref(rsp); | |
129 | rsp = qmp("{ 'execute' : 'cont'}"); | |
130 | qobject_unref(rsp); | |
131 | ||
132 | qvirtio_wait_used_elem(dev, vq, free_head, NULL, QVIRTIO_NET_TIMEOUT_US); | |
133 | memread(req_addr + VNET_HDR_SIZE, buffer, sizeof(test)); | |
134 | g_assert_cmpstr(buffer, ==, "TEST"); | |
135 | ||
136 | guest_free(alloc, req_addr); | |
137 | } | |
138 | ||
139 | static void send_recv_test(void *obj, void *data, QGuestAllocator *t_alloc) | |
140 | { | |
141 | QVirtioNet *net_if = obj; | |
142 | QVirtioDevice *dev = net_if->vdev; | |
143 | QVirtQueue *rx = net_if->queues[0]; | |
144 | QVirtQueue *tx = net_if->queues[1]; | |
145 | int *sv = data; | |
146 | ||
147 | rx_test(dev, t_alloc, rx, sv[0]); | |
148 | tx_test(dev, t_alloc, tx, sv[0]); | |
149 | } | |
150 | ||
151 | static void stop_cont_test(void *obj, void *data, QGuestAllocator *t_alloc) | |
152 | { | |
153 | QVirtioNet *net_if = obj; | |
154 | QVirtioDevice *dev = net_if->vdev; | |
155 | QVirtQueue *rx = net_if->queues[0]; | |
156 | int *sv = data; | |
157 | ||
158 | rx_stop_cont_test(dev, t_alloc, rx, sv[0]); | |
159 | } | |
160 | ||
161 | #endif | |
162 | ||
163 | static void hotplug(void *obj, void *data, QGuestAllocator *t_alloc) | |
164 | { | |
165 | const char *arch = qtest_get_arch(); | |
166 | ||
167 | qtest_qmp_device_add("virtio-net-pci", "net1", | |
168 | "{'addr': %s}", stringify(PCI_SLOT_HP)); | |
169 | ||
170 | if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { | |
171 | qpci_unplug_acpi_device_test("net1", PCI_SLOT_HP); | |
172 | } | |
173 | } | |
174 | ||
175 | static void announce_self(void *obj, void *data, QGuestAllocator *t_alloc) | |
176 | { | |
177 | int *sv = data; | |
178 | char buffer[60]; | |
179 | int len; | |
180 | QDict *rsp; | |
181 | int ret; | |
182 | uint16_t *proto = (uint16_t *)&buffer[12]; | |
183 | ||
184 | rsp = qmp("{ 'execute' : 'announce-self', " | |
185 | " 'arguments': {" | |
186 | " 'initial': 50, 'max': 550," | |
187 | " 'rounds': 10, 'step': 50 } }"); | |
188 | assert(!qdict_haskey(rsp, "error")); | |
189 | qobject_unref(rsp); | |
190 | ||
191 | /* Catch the packet and make sure it's a RARP */ | |
192 | ret = qemu_recv(sv[0], &len, sizeof(len), 0); | |
193 | g_assert_cmpint(ret, ==, sizeof(len)); | |
194 | len = ntohl(len); | |
195 | ||
196 | ret = qemu_recv(sv[0], buffer, len, 0); | |
197 | g_assert_cmpint(*proto, ==, htons(ETH_P_RARP)); | |
198 | } | |
199 | ||
200 | static void virtio_net_test_cleanup(void *sockets) | |
201 | { | |
202 | int *sv = sockets; | |
203 | ||
204 | close(sv[0]); | |
205 | qos_invalidate_command_line(); | |
206 | close(sv[1]); | |
207 | g_free(sv); | |
208 | } | |
209 | ||
210 | static void *virtio_net_test_setup(GString *cmd_line, void *arg) | |
211 | { | |
212 | int ret; | |
213 | int *sv = g_new(int, 2); | |
214 | ||
215 | ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv); | |
216 | g_assert_cmpint(ret, !=, -1); | |
217 | ||
218 | g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ", sv[1]); | |
219 | ||
220 | g_test_queue_destroy(virtio_net_test_cleanup, sv); | |
221 | return sv; | |
222 | } | |
223 | ||
224 | static void large_tx(void *obj, void *data, QGuestAllocator *t_alloc) | |
225 | { | |
226 | QVirtioNet *dev = obj; | |
227 | QVirtQueue *vq = dev->queues[1]; | |
228 | uint64_t req_addr; | |
229 | uint32_t free_head; | |
230 | size_t alloc_size = (size_t)data / 64; | |
231 | int i; | |
232 | ||
233 | /* Bypass the limitation by pointing several descriptors to a single | |
234 | * smaller area */ | |
235 | req_addr = guest_alloc(t_alloc, alloc_size); | |
236 | free_head = qvirtqueue_add(vq, req_addr, alloc_size, false, true); | |
237 | ||
238 | for (i = 0; i < 64; i++) { | |
239 | qvirtqueue_add(vq, req_addr, alloc_size, false, i != 63); | |
240 | } | |
241 | qvirtqueue_kick(dev->vdev, vq, free_head); | |
242 | ||
243 | qvirtio_wait_used_elem(dev->vdev, vq, free_head, NULL, | |
244 | QVIRTIO_NET_TIMEOUT_US); | |
245 | guest_free(t_alloc, req_addr); | |
246 | } | |
247 | ||
248 | static void *virtio_net_test_setup_nosocket(GString *cmd_line, void *arg) | |
249 | { | |
250 | g_string_append(cmd_line, " -netdev hubport,hubid=0,id=hs0 "); | |
251 | return arg; | |
252 | } | |
253 | ||
254 | static void register_virtio_net_test(void) | |
255 | { | |
256 | QOSGraphTestOptions opts = { | |
257 | .before = virtio_net_test_setup, | |
258 | }; | |
259 | ||
260 | qos_add_test("hotplug", "virtio-pci", hotplug, &opts); | |
261 | #ifndef _WIN32 | |
262 | qos_add_test("basic", "virtio-net", send_recv_test, &opts); | |
263 | qos_add_test("rx_stop_cont", "virtio-net", stop_cont_test, &opts); | |
264 | #endif | |
265 | qos_add_test("announce-self", "virtio-net", announce_self, &opts); | |
266 | ||
267 | /* These tests do not need a loopback backend. */ | |
268 | opts.before = virtio_net_test_setup_nosocket; | |
269 | opts.arg = (gpointer)UINT_MAX; | |
270 | qos_add_test("large_tx/uint_max", "virtio-net", large_tx, &opts); | |
271 | opts.arg = (gpointer)NET_BUFSIZE; | |
272 | qos_add_test("large_tx/net_bufsize", "virtio-net", large_tx, &opts); | |
273 | } | |
274 | ||
275 | libqos_init(register_virtio_net_test); |