1 /*
2 * QTest testcase for VirtIO IOMMU
3 *
4 * Copyright (c) 2021 Red Hat, Inc.
5 *
6 * Authors:
7 * Eric Auger <eric.auger@redhat.com>
8 *
9 * This work is licensed under the terms of the GNU GPL, version 2 or (at your
10 * option) any later version. See the COPYING file in the top-level directory.
11 *
12 */
13
14 #include "qemu/osdep.h"
15 #include "libqtest-single.h"
16 #include "qemu/module.h"
17 #include "libqos/qgraph.h"
18 #include "libqos/virtio-iommu.h"
19 #include "hw/virtio/virtio-iommu.h"
20
21 #define PCI_SLOT_HP 0x06
22 #define QVIRTIO_IOMMU_TIMEOUT_US (30 * 1000 * 1000)
23
24 static QGuestAllocator *alloc;
25
pci_config(void * obj,void * data,QGuestAllocator * t_alloc)26 static void pci_config(void *obj, void *data, QGuestAllocator *t_alloc)
27 {
28 QVirtioIOMMU *v_iommu = obj;
29 QVirtioDevice *dev = v_iommu->vdev;
30 uint64_t input_range_start = qvirtio_config_readq(dev, 8);
31 uint64_t input_range_end = qvirtio_config_readq(dev, 16);
32 uint32_t domain_range_start = qvirtio_config_readl(dev, 24);
33 uint32_t domain_range_end = qvirtio_config_readl(dev, 28);
34 uint8_t bypass = qvirtio_config_readb(dev, 36);
35
36 g_assert_cmpint(input_range_start, ==, 0);
37 g_assert_cmphex(input_range_end, >=, UINT32_MAX);
38 g_assert_cmpint(domain_range_start, ==, 0);
39 g_assert_cmpint(domain_range_end, ==, UINT32_MAX);
40 g_assert_cmpint(bypass, ==, 1);
41 }
42
read_tail_status(struct virtio_iommu_req_tail * buffer)43 static int read_tail_status(struct virtio_iommu_req_tail *buffer)
44 {
45 int i;
46
47 for (i = 0; i < 3; i++) {
48 g_assert_cmpint(buffer->reserved[i], ==, 0);
49 }
50 return buffer->status;
51 }
52
53 /**
54 * send_attach_detach - Send an attach/detach command to the device
55 * @type: VIRTIO_IOMMU_T_ATTACH/VIRTIO_IOMMU_T_DETACH
56 * @domain: domain the endpoint is attached to
57 * @ep: endpoint
58 */
send_attach_detach(QTestState * qts,QVirtioIOMMU * v_iommu,uint8_t type,uint32_t domain,uint32_t ep)59 static int send_attach_detach(QTestState *qts, QVirtioIOMMU *v_iommu,
60 uint8_t type, uint32_t domain, uint32_t ep)
61 {
62 QVirtioDevice *dev = v_iommu->vdev;
63 QVirtQueue *vq = v_iommu->vq;
64 uint64_t ro_addr, wr_addr;
65 uint32_t free_head;
66 struct virtio_iommu_req_attach req = {}; /* same layout as detach */
67 size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
68 size_t wr_size = sizeof(struct virtio_iommu_req_tail);
69 struct virtio_iommu_req_tail buffer;
70 int ret;
71
72 req.head.type = type;
73 req.domain = cpu_to_le32(domain);
74 req.endpoint = cpu_to_le32(ep);
75
76 ro_addr = guest_alloc(alloc, ro_size);
77 wr_addr = guest_alloc(alloc, wr_size);
78
79 qtest_memwrite(qts, ro_addr, &req, ro_size);
80 free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
81 qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
82 qvirtqueue_kick(qts, dev, vq, free_head);
83 qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
84 QVIRTIO_IOMMU_TIMEOUT_US);
85 qtest_memread(qts, wr_addr, &buffer, wr_size);
86 ret = read_tail_status(&buffer);
87 guest_free(alloc, ro_addr);
88 guest_free(alloc, wr_addr);
89 return ret;
90 }
91
92 /**
93 * send_map - Send a map command to the device
94 * @domain: domain the new mapping is attached to
95 * @virt_start: iova start
96 * @virt_end: iova end
97 * @phys_start: base physical address
98 * @flags: mapping flags
99 */
send_map(QTestState * qts,QVirtioIOMMU * v_iommu,uint32_t domain,uint64_t virt_start,uint64_t virt_end,uint64_t phys_start,uint32_t flags)100 static int send_map(QTestState *qts, QVirtioIOMMU *v_iommu,
101 uint32_t domain, uint64_t virt_start, uint64_t virt_end,
102 uint64_t phys_start, uint32_t flags)
103 {
104 QVirtioDevice *dev = v_iommu->vdev;
105 QVirtQueue *vq = v_iommu->vq;
106 uint64_t ro_addr, wr_addr;
107 uint32_t free_head;
108 struct virtio_iommu_req_map req;
109 size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
110 size_t wr_size = sizeof(struct virtio_iommu_req_tail);
111 struct virtio_iommu_req_tail buffer;
112 int ret;
113
114 req.head.type = VIRTIO_IOMMU_T_MAP;
115 req.domain = cpu_to_le32(domain);
116 req.virt_start = cpu_to_le64(virt_start);
117 req.virt_end = cpu_to_le64(virt_end);
118 req.phys_start = cpu_to_le64(phys_start);
119 req.flags = cpu_to_le32(flags);
120
121 ro_addr = guest_alloc(alloc, ro_size);
122 wr_addr = guest_alloc(alloc, wr_size);
123
124 qtest_memwrite(qts, ro_addr, &req, ro_size);
125 free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
126 qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
127 qvirtqueue_kick(qts, dev, vq, free_head);
128 qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
129 QVIRTIO_IOMMU_TIMEOUT_US);
130 qtest_memread(qts, wr_addr, &buffer, wr_size);
131 ret = read_tail_status(&buffer);
132 guest_free(alloc, ro_addr);
133 guest_free(alloc, wr_addr);
134 return ret;
135 }
136
137 /**
138 * send_unmap - Send an unmap command to the device
139 * @domain: domain the new binding is attached to
140 * @virt_start: iova start
141 * @virt_end: iova end
142 */
send_unmap(QTestState * qts,QVirtioIOMMU * v_iommu,uint32_t domain,uint64_t virt_start,uint64_t virt_end)143 static int send_unmap(QTestState *qts, QVirtioIOMMU *v_iommu,
144 uint32_t domain, uint64_t virt_start, uint64_t virt_end)
145 {
146 QVirtioDevice *dev = v_iommu->vdev;
147 QVirtQueue *vq = v_iommu->vq;
148 uint64_t ro_addr, wr_addr;
149 uint32_t free_head;
150 struct virtio_iommu_req_unmap req;
151 size_t ro_size = sizeof(req) - sizeof(struct virtio_iommu_req_tail);
152 size_t wr_size = sizeof(struct virtio_iommu_req_tail);
153 struct virtio_iommu_req_tail buffer;
154 int ret;
155
156 req.head.type = VIRTIO_IOMMU_T_UNMAP;
157 req.domain = cpu_to_le32(domain);
158 req.virt_start = cpu_to_le64(virt_start);
159 req.virt_end = cpu_to_le64(virt_end);
160
161 ro_addr = guest_alloc(alloc, ro_size);
162 wr_addr = guest_alloc(alloc, wr_size);
163
164 qtest_memwrite(qts, ro_addr, &req, ro_size);
165 free_head = qvirtqueue_add(qts, vq, ro_addr, ro_size, false, true);
166 qvirtqueue_add(qts, vq, wr_addr, wr_size, true, false);
167 qvirtqueue_kick(qts, dev, vq, free_head);
168 qvirtio_wait_used_elem(qts, dev, vq, free_head, NULL,
169 QVIRTIO_IOMMU_TIMEOUT_US);
170 qtest_memread(qts, wr_addr, &buffer, wr_size);
171 ret = read_tail_status(&buffer);
172 guest_free(alloc, ro_addr);
173 guest_free(alloc, wr_addr);
174 return ret;
175 }
176
test_attach_detach(void * obj,void * data,QGuestAllocator * t_alloc)177 static void test_attach_detach(void *obj, void *data, QGuestAllocator *t_alloc)
178 {
179 QVirtioIOMMU *v_iommu = obj;
180 QTestState *qts = global_qtest;
181 int ret;
182
183 alloc = t_alloc;
184
185 /* type, domain, ep */
186
187 /* attach ep0 to domain 0 */
188 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 0);
189 g_assert_cmpint(ret, ==, 0);
190
191 /* attach a non existing device */
192 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 0, 444);
193 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
194
195 /* detach a non existing device (1) */
196 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 1);
197 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
198
199 /* move ep0 from domain 0 to domain 1 */
200 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
201 g_assert_cmpint(ret, ==, 0);
202
203 /* detach ep0 from domain 0 */
204 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 0, 0);
205 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL);
206
207 /* detach ep0 from domain 1 */
208 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
209 g_assert_cmpint(ret, ==, 0);
210
211 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
212 g_assert_cmpint(ret, ==, 0);
213 ret = send_map(qts, v_iommu, 1, 0x0, 0xFFF, 0xa1000,
214 VIRTIO_IOMMU_MAP_F_READ);
215 g_assert_cmpint(ret, ==, 0);
216 ret = send_map(qts, v_iommu, 1, 0x2000, 0x2FFF, 0xb1000,
217 VIRTIO_IOMMU_MAP_F_READ);
218 g_assert_cmpint(ret, ==, 0);
219 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_DETACH, 1, 0);
220 g_assert_cmpint(ret, ==, 0);
221 }
222
223 /* Test map/unmap scenari documented in the spec */
test_map_unmap(void * obj,void * data,QGuestAllocator * t_alloc)224 static void test_map_unmap(void *obj, void *data, QGuestAllocator *t_alloc)
225 {
226 QVirtioIOMMU *v_iommu = obj;
227 QTestState *qts = global_qtest;
228 int ret;
229
230 alloc = t_alloc;
231
232 /* attach ep0 to domain 1 */
233 ret = send_attach_detach(qts, v_iommu, VIRTIO_IOMMU_T_ATTACH, 1, 0);
234 g_assert_cmpint(ret, ==, 0);
235
236 ret = send_map(qts, v_iommu, 0, 0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
237 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
238
239 /* domain, virt start, virt end, phys start, flags */
240 ret = send_map(qts, v_iommu, 1, 0x0, 0xFFF, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
241 g_assert_cmpint(ret, ==, 0);
242
243 /* send a new mapping overlapping the previous one */
244 ret = send_map(qts, v_iommu, 1, 0, 0xFFFF, 0xb1000, VIRTIO_IOMMU_MAP_F_READ);
245 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL);
246
247 ret = send_unmap(qts, v_iommu, 4, 0x10, 0xFFF);
248 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_NOENT);
249
250 ret = send_unmap(qts, v_iommu, 1, 0x10, 0xFFF);
251 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE);
252
253 ret = send_unmap(qts, v_iommu, 1, 0, 0x1000);
254 g_assert_cmpint(ret, ==, 0); /* unmap everything */
255
256 /* Spec example sequence */
257
258 /* 1 */
259 ret = send_unmap(qts, v_iommu, 1, 0, 4);
260 g_assert_cmpint(ret, ==, 0); /* doesn't unmap anything */
261
262 /* 2 */
263 ret = send_map(qts, v_iommu, 1, 0, 9, 0xa1000, VIRTIO_IOMMU_MAP_F_READ);
264 g_assert_cmpint(ret, ==, 0);
265 ret = send_unmap(qts, v_iommu, 1, 0, 9);
266 g_assert_cmpint(ret, ==, 0); /* unmaps [0,9] */
267
268 /* 3 */
269 ret = send_map(qts, v_iommu, 1, 0, 4, 0xb1000, VIRTIO_IOMMU_MAP_F_READ);
270 g_assert_cmpint(ret, ==, 0);
271 ret = send_map(qts, v_iommu, 1, 5, 9, 0xb2000, VIRTIO_IOMMU_MAP_F_READ);
272 g_assert_cmpint(ret, ==, 0);
273 ret = send_unmap(qts, v_iommu, 1, 0, 9);
274 g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [5,9] */
275
276 /* 4 */
277 ret = send_map(qts, v_iommu, 1, 0, 9, 0xc1000, VIRTIO_IOMMU_MAP_F_READ);
278 g_assert_cmpint(ret, ==, 0);
279
280 ret = send_unmap(qts, v_iommu, 1, 0, 4);
281 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_RANGE); /* doesn't unmap anything */
282
283 ret = send_unmap(qts, v_iommu, 1, 0, 10);
284 g_assert_cmpint(ret, ==, 0);
285
286 /* 5 */
287 ret = send_map(qts, v_iommu, 1, 0, 4, 0xd1000, VIRTIO_IOMMU_MAP_F_READ);
288 g_assert_cmpint(ret, ==, 0);
289 ret = send_map(qts, v_iommu, 1, 5, 9, 0xd2000, VIRTIO_IOMMU_MAP_F_READ);
290 g_assert_cmpint(ret, ==, 0);
291 ret = send_unmap(qts, v_iommu, 1, 0, 4);
292 g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
293
294 ret = send_unmap(qts, v_iommu, 1, 5, 9);
295 g_assert_cmpint(ret, ==, 0);
296
297 /* 6 */
298 ret = send_map(qts, v_iommu, 1, 0, 4, 0xe2000, VIRTIO_IOMMU_MAP_F_READ);
299 g_assert_cmpint(ret, ==, 0);
300 ret = send_unmap(qts, v_iommu, 1, 0, 9);
301 g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] */
302
303 /* 7 */
304 ret = send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
305 g_assert_cmpint(ret, ==, 0);
306 ret = send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
307 g_assert_cmpint(ret, ==, 0);
308 ret = send_unmap(qts, v_iommu, 1, 0, 14);
309 g_assert_cmpint(ret, ==, 0); /* unmaps [0,4] and [10,14] */
310
311 ret = send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
312 g_assert_cmpint(ret, ==, 0);
313 ret = send_map(qts, v_iommu, 1, 0, 4, 0xf2000, VIRTIO_IOMMU_MAP_F_READ);
314 g_assert_cmpint(ret, ==, 0);
315 ret = send_unmap(qts, v_iommu, 1, 0, 4);
316 g_assert_cmpint(ret, ==, 0); /* only unmaps [0,4] */
317 ret = send_map(qts, v_iommu, 1, 10, 14, 0xf3000, VIRTIO_IOMMU_MAP_F_READ);
318 g_assert_cmpint(ret, ==, VIRTIO_IOMMU_S_INVAL); /* 10-14 still is mapped */
319 }
320
register_virtio_iommu_test(void)321 static void register_virtio_iommu_test(void)
322 {
323 qos_add_test("config", "virtio-iommu", pci_config, NULL);
324 qos_add_test("attach_detach", "virtio-iommu", test_attach_detach, NULL);
325 qos_add_test("map_unmap", "virtio-iommu", test_map_unmap, NULL);
326 }
327
328 libqos_init(register_virtio_iommu_test);
329