1
2
3
4
5
6
7
8
9
10
11#include "qemu/osdep.h"
12#include "libqtest-single.h"
13#include "qemu/module.h"
14#include "scsi/constants.h"
15#include "libqos/libqos-pc.h"
16#include "libqos/libqos-spapr.h"
17#include "libqos/virtio.h"
18#include "libqos/virtio-pci.h"
19#include "standard-headers/linux/virtio_ids.h"
20#include "standard-headers/linux/virtio_pci.h"
21#include "standard-headers/linux/virtio_scsi.h"
22#include "libqos/virtio-scsi.h"
23#include "libqos/qgraph.h"
24
25#define PCI_SLOT 0x02
26#define PCI_FN 0x00
27#define QVIRTIO_SCSI_TIMEOUT_US (1 * 1000 * 1000)
28
29#define MAX_NUM_QUEUES 64
30
31typedef struct {
32 QVirtioDevice *dev;
33 int num_queues;
34 QVirtQueue *vq[MAX_NUM_QUEUES + 2];
35} QVirtioSCSIQueues;
36
37static QGuestAllocator *alloc;
38
39static void qvirtio_scsi_pci_free(QVirtioSCSIQueues *vs)
40{
41 int i;
42
43 for (i = 0; i < vs->num_queues + 2; i++) {
44 qvirtqueue_cleanup(vs->dev->bus, vs->vq[i], alloc);
45 }
46 g_free(vs);
47}
48
49static uint64_t qvirtio_scsi_alloc(QVirtioSCSIQueues *vs, size_t alloc_size,
50 const void *data)
51{
52 uint64_t addr;
53
54 addr = guest_alloc(alloc, alloc_size);
55 if (data) {
56 memwrite(addr, data, alloc_size);
57 }
58
59 return addr;
60}
61
62static uint8_t virtio_scsi_do_command(QVirtioSCSIQueues *vs,
63 const uint8_t *cdb,
64 const uint8_t *data_in,
65 size_t data_in_len,
66 uint8_t *data_out, size_t data_out_len,
67 struct virtio_scsi_cmd_resp *resp_out)
68{
69 QVirtQueue *vq;
70 struct virtio_scsi_cmd_req req = { { 0 } };
71 struct virtio_scsi_cmd_resp resp = { .response = 0xff, .status = 0xff };
72 uint64_t req_addr, resp_addr, data_in_addr = 0, data_out_addr = 0;
73 uint8_t response;
74 uint32_t free_head;
75 QTestState *qts = global_qtest;
76
77 vq = vs->vq[2];
78
79 req.lun[0] = 1;
80 req.lun[1] = 1;
81 memcpy(req.cdb, cdb, VIRTIO_SCSI_CDB_SIZE);
82
83
84
85
86 req_addr = qvirtio_scsi_alloc(vs, sizeof(req), &req);
87 free_head = qvirtqueue_add(qts, vq, req_addr, sizeof(req), false, true);
88
89 if (data_out_len) {
90 data_out_addr = qvirtio_scsi_alloc(vs, data_out_len, data_out);
91 qvirtqueue_add(qts, vq, data_out_addr, data_out_len, false, true);
92 }
93
94
95 resp_addr = qvirtio_scsi_alloc(vs, sizeof(resp), &resp);
96 qvirtqueue_add(qts, vq, resp_addr, sizeof(resp), true, !!data_in_len);
97
98 if (data_in_len) {
99 data_in_addr = qvirtio_scsi_alloc(vs, data_in_len, data_in);
100 qvirtqueue_add(qts, vq, data_in_addr, data_in_len, true, false);
101 }
102
103 qvirtqueue_kick(qts, vs->dev, vq, free_head);
104 qvirtio_wait_used_elem(qts, vs->dev, vq, free_head, NULL,
105 QVIRTIO_SCSI_TIMEOUT_US);
106
107 response = readb(resp_addr +
108 offsetof(struct virtio_scsi_cmd_resp, response));
109
110 if (resp_out) {
111 memread(resp_addr, resp_out, sizeof(*resp_out));
112 }
113
114 guest_free(alloc, req_addr);
115 guest_free(alloc, resp_addr);
116 guest_free(alloc, data_in_addr);
117 guest_free(alloc, data_out_addr);
118 return response;
119}
120
121static QVirtioSCSIQueues *qvirtio_scsi_init(QVirtioDevice *dev)
122{
123 QVirtioSCSIQueues *vs;
124 const uint8_t test_unit_ready_cdb[VIRTIO_SCSI_CDB_SIZE] = {};
125 struct virtio_scsi_cmd_resp resp;
126 uint64_t features;
127 int i;
128
129 vs = g_new0(QVirtioSCSIQueues, 1);
130 vs->dev = dev;
131
132 features = qvirtio_get_features(dev);
133 features &= ~(QVIRTIO_F_BAD_FEATURE | (1ull << VIRTIO_RING_F_EVENT_IDX));
134 qvirtio_set_features(dev, features);
135
136 vs->num_queues = qvirtio_config_readl(dev, 0);
137
138 g_assert_cmpint(vs->num_queues, <, MAX_NUM_QUEUES);
139
140 for (i = 0; i < vs->num_queues + 2; i++) {
141 vs->vq[i] = qvirtqueue_setup(dev, alloc, i);
142 }
143
144 qvirtio_set_driver_ok(dev);
145
146
147 g_assert_cmpint(virtio_scsi_do_command(vs, test_unit_ready_cdb,
148 NULL, 0, NULL, 0, &resp),
149 ==, 0);
150 g_assert_cmpint(resp.status, ==, CHECK_CONDITION);
151 g_assert_cmpint(resp.sense[0], ==, 0x70);
152 g_assert_cmpint(resp.sense[2], ==, UNIT_ATTENTION);
153 g_assert_cmpint(resp.sense[12], ==, 0x29);
154 g_assert_cmpint(resp.sense[13], ==, 0x00);
155
156 return vs;
157}
158
159static void hotplug(void *obj, void *data, QGuestAllocator *alloc)
160{
161 QTestState *qts = global_qtest;
162
163 qtest_qmp_device_add(qts, "scsi-hd", "scsihd", "{'drive': 'drv1'}");
164 qtest_qmp_device_del(qts, "scsihd");
165}
166
167
168static void test_unaligned_write_same(void *obj, void *data,
169 QGuestAllocator *t_alloc)
170{
171 QVirtioSCSI *scsi = obj;
172 QVirtioSCSIQueues *vs;
173 uint8_t buf1[512] = { 0 };
174 uint8_t buf2[512] = { 1 };
175 const uint8_t write_same_cdb_1[VIRTIO_SCSI_CDB_SIZE] = {
176 0x41, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00
177 };
178 const uint8_t write_same_cdb_2[VIRTIO_SCSI_CDB_SIZE] = {
179 0x41, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0x00, 0x00
180 };
181 const uint8_t write_same_cdb_ndob[VIRTIO_SCSI_CDB_SIZE] = {
182 0x41, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0x00, 0x00
183 };
184
185 alloc = t_alloc;
186 vs = qvirtio_scsi_init(scsi->vdev);
187
188 g_assert_cmphex(0, ==,
189 virtio_scsi_do_command(vs, write_same_cdb_1, NULL, 0, buf1, 512,
190 NULL));
191
192 g_assert_cmphex(0, ==,
193 virtio_scsi_do_command(vs, write_same_cdb_2, NULL, 0, buf2, 512,
194 NULL));
195
196 g_assert_cmphex(0, ==,
197 virtio_scsi_do_command(vs, write_same_cdb_ndob, NULL, 0, NULL, 0,
198 NULL));
199
200 qvirtio_scsi_pci_free(vs);
201}
202
203
204static void test_unmap_large_lba(void *obj, void *data,
205 QGuestAllocator *t_alloc)
206{
207 QVirtioSCSI *scsi = obj;
208 QVirtioSCSIQueues *vs;
209 const uint8_t unmap[VIRTIO_SCSI_CDB_SIZE] = {
210 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00
211 };
212
213
214
215
216
217
218 uint8_t unmap_params[0x18] = {
219 0x00, 0x16,
220 0x00, 0x10,
221 0x00, 0x00, 0x00, 0x00,
222 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff,
223 0x00, 0x00, 0x03, 0xff,
224 0x00, 0x00, 0x00, 0x00,
225 };
226 struct virtio_scsi_cmd_resp resp;
227
228 alloc = t_alloc;
229 vs = qvirtio_scsi_init(scsi->vdev);
230
231 virtio_scsi_do_command(vs, unmap, NULL, 0, unmap_params,
232 sizeof(unmap_params), &resp);
233 g_assert_cmphex(resp.response, ==, 0);
234 g_assert_cmphex(resp.status, !=, CHECK_CONDITION);
235
236 qvirtio_scsi_pci_free(vs);
237}
238
239static void test_write_to_cdrom(void *obj, void *data,
240 QGuestAllocator *t_alloc)
241{
242 QVirtioSCSI *scsi = obj;
243 QVirtioSCSIQueues *vs;
244 uint8_t buf[2048] = { 0 };
245 const uint8_t write_cdb[VIRTIO_SCSI_CDB_SIZE] = {
246
247 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00
248 };
249 struct virtio_scsi_cmd_resp resp;
250
251 alloc = t_alloc;
252 vs = qvirtio_scsi_init(scsi->vdev);
253
254 virtio_scsi_do_command(vs, write_cdb, NULL, 0, buf, 2048, &resp);
255 g_assert_cmphex(resp.response, ==, 0);
256 g_assert_cmphex(resp.status, ==, CHECK_CONDITION);
257 g_assert_cmphex(resp.sense[0], ==, 0x70);
258 g_assert_cmphex(resp.sense[2], ==, DATA_PROTECT);
259 g_assert_cmphex(resp.sense[12], ==, 0x27);
260 g_assert_cmphex(resp.sense[13], ==, 0x00);
261
262 qvirtio_scsi_pci_free(vs);
263}
264
265static void test_iothread_attach_node(void *obj, void *data,
266 QGuestAllocator *t_alloc)
267{
268 QVirtioSCSIPCI *scsi_pci = obj;
269 QVirtioSCSI *scsi = &scsi_pci->scsi;
270 QVirtioSCSIQueues *vs;
271 char tmp_path[] = "/tmp/qtest.XXXXXX";
272 int fd;
273 int ret;
274
275 uint8_t buf[512] = { 0 };
276 const uint8_t write_cdb[VIRTIO_SCSI_CDB_SIZE] = {
277
278 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00
279 };
280
281 alloc = t_alloc;
282 vs = qvirtio_scsi_init(scsi->vdev);
283
284
285 fd = mkstemp(tmp_path);
286 g_assert(fd >= 0);
287 close(fd);
288
289 if (!have_qemu_img()) {
290 g_test_message("QTEST_QEMU_IMG not set or qemu-img missing; "
291 "skipping snapshot test");
292 goto fail;
293 }
294
295 mkqcow2(tmp_path, 64);
296
297
298 qtest_qmp_assert_success(scsi_pci->pci_vdev.pdev->bus->qts,
299 "{'execute': 'blockdev-add', 'arguments': {"
300 " 'driver': 'qcow2', 'node-name': 'overlay',"
301 " 'backing': 'null0', 'file': {"
302 " 'driver': 'file', 'filename': %s}}}",
303 tmp_path);
304
305
306 ret = virtio_scsi_do_command(vs, write_cdb, NULL, 0, buf, 512, NULL);
307 g_assert_cmphex(ret, ==, 0);
308
309fail:
310 qvirtio_scsi_pci_free(vs);
311 unlink(tmp_path);
312}
313
314static void *virtio_scsi_hotplug_setup(GString *cmd_line, void *arg)
315{
316 g_string_append(cmd_line,
317 " -drive id=drv1,if=none,file=null-co://,"
318 "file.read-zeroes=on,format=raw");
319 return arg;
320}
321
322static void *virtio_scsi_setup(GString *cmd_line, void *arg)
323{
324 g_string_append(cmd_line,
325 " -drive file=blkdebug::null-co://,"
326 "file.image.read-zeroes=on,"
327 "if=none,id=dr1,format=raw,file.align=4k "
328 "-device scsi-hd,drive=dr1,lun=0,scsi-id=1");
329 return arg;
330}
331
332static void *virtio_scsi_setup_4k(GString *cmd_line, void *arg)
333{
334 g_string_append(cmd_line,
335 " -drive file=blkdebug::null-co://,"
336 "file.image.read-zeroes=on,"
337 "if=none,id=dr1,format=raw "
338 "-device scsi-hd,drive=dr1,lun=0,scsi-id=1"
339 ",logical_block_size=4k,physical_block_size=4k");
340 return arg;
341}
342
343static void *virtio_scsi_setup_cd(GString *cmd_line, void *arg)
344{
345 g_string_append(cmd_line,
346 " -drive file=null-co://,"
347 "file.read-zeroes=on,"
348 "if=none,id=dr1,format=raw "
349 "-device scsi-cd,drive=dr1,lun=0,scsi-id=1");
350 return arg;
351}
352
353static void *virtio_scsi_setup_iothread(GString *cmd_line, void *arg)
354{
355 g_string_append(cmd_line,
356 " -object iothread,id=thread0"
357 " -blockdev driver=null-co,read-zeroes=on,node-name=null0"
358 " -device scsi-hd,drive=null0");
359 return arg;
360}
361
362static void register_virtio_scsi_test(void)
363{
364 QOSGraphTestOptions opts = { };
365
366 opts.before = virtio_scsi_hotplug_setup;
367 qos_add_test("hotplug", "virtio-scsi", hotplug, &opts);
368
369 opts.before = virtio_scsi_setup;
370 qos_add_test("unaligned-write-same", "virtio-scsi",
371 test_unaligned_write_same, &opts);
372
373 opts.before = virtio_scsi_setup_4k;
374 qos_add_test("large-lba-unmap", "virtio-scsi",
375 test_unmap_large_lba, &opts);
376
377 opts.before = virtio_scsi_setup_cd;
378 qos_add_test("write-to-cdrom", "virtio-scsi", test_write_to_cdrom, &opts);
379
380 opts.before = virtio_scsi_setup_iothread;
381 opts.edge = (QOSGraphEdgeOptions) {
382 .extra_device_opts = "iothread=thread0",
383 };
384 qos_add_test("iothread-attach-node", "virtio-scsi-pci",
385 test_iothread_attach_node, &opts);
386}
387
388libqos_init(register_virtio_scsi_test);
389