]>
Commit | Line | Data |
---|---|---|
26c9a015 AF |
1 | /* |
2 | * QTest testcase for VirtIO SCSI | |
3 | * | |
4 | * Copyright (c) 2014 SUSE LINUX Products GmbH | |
397c767b | 5 | * Copyright (c) 2015 Red Hat Inc. |
26c9a015 AF |
6 | * |
7 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
8 | * See the COPYING file in the top-level directory. | |
9 | */ | |
10 | ||
681c28a3 | 11 | #include "qemu/osdep.h" |
dd210749 | 12 | #include "libqtest-single.h" |
0b8fa32f | 13 | #include "qemu/module.h" |
08e2c9f1 | 14 | #include "scsi/constants.h" |
a980f7f2 | 15 | #include "libqos/libqos-pc.h" |
30ca440e | 16 | #include "libqos/libqos-spapr.h" |
397c767b FZ |
17 | #include "libqos/virtio.h" |
18 | #include "libqos/virtio-pci.h" | |
8ac9e205 | 19 | #include "standard-headers/linux/virtio_ids.h" |
c75f4c06 | 20 | #include "standard-headers/linux/virtio_pci.h" |
74f079a7 | 21 | #include "standard-headers/linux/virtio_scsi.h" |
4e200798 EGE |
22 | #include "libqos/virtio-scsi.h" |
23 | #include "libqos/qgraph.h" | |
397c767b FZ |
24 | |
25 | #define PCI_SLOT 0x02 | |
26 | #define PCI_FN 0x00 | |
27 | #define QVIRTIO_SCSI_TIMEOUT_US (1 * 1000 * 1000) | |
397c767b FZ |
28 | |
29 | #define MAX_NUM_QUEUES 64 | |
30 | ||
31 | typedef struct { | |
32 | QVirtioDevice *dev; | |
397c767b FZ |
33 | int num_queues; |
34 | QVirtQueue *vq[MAX_NUM_QUEUES + 2]; | |
4e200798 | 35 | } QVirtioSCSIQueues; |
397c767b | 36 | |
4e200798 | 37 | static QGuestAllocator *alloc; |
06b008d9 | 38 | |
4e200798 | 39 | static void qvirtio_scsi_pci_free(QVirtioSCSIQueues *vs) |
397c767b FZ |
40 | { |
41 | int i; | |
42 | ||
43 | for (i = 0; i < vs->num_queues + 2; i++) { | |
4e200798 | 44 | qvirtqueue_cleanup(vs->dev->bus, vs->vq[i], alloc); |
397c767b | 45 | } |
f62e0bbb | 46 | g_free(vs); |
397c767b FZ |
47 | } |
48 | ||
4e200798 | 49 | static uint64_t qvirtio_scsi_alloc(QVirtioSCSIQueues *vs, size_t alloc_size, |
397c767b FZ |
50 | const void *data) |
51 | { | |
52 | uint64_t addr; | |
53 | ||
4e200798 | 54 | addr = guest_alloc(alloc, alloc_size); |
397c767b FZ |
55 | if (data) { |
56 | memwrite(addr, data, alloc_size); | |
57 | } | |
58 | ||
59 | return addr; | |
60 | } | |
61 | ||
4e200798 EGE |
62 | static uint8_t virtio_scsi_do_command(QVirtioSCSIQueues *vs, |
63 | const uint8_t *cdb, | |
397c767b FZ |
64 | const uint8_t *data_in, |
65 | size_t data_in_len, | |
4bb7b0da | 66 | uint8_t *data_out, size_t data_out_len, |
74f079a7 | 67 | struct virtio_scsi_cmd_resp *resp_out) |
397c767b FZ |
68 | { |
69 | QVirtQueue *vq; | |
74f079a7 SH |
70 | struct virtio_scsi_cmd_req req = { { 0 } }; |
71 | struct virtio_scsi_cmd_resp resp = { .response = 0xff, .status = 0xff }; | |
397c767b FZ |
72 | uint64_t req_addr, resp_addr, data_in_addr = 0, data_out_addr = 0; |
73 | uint8_t response; | |
74 | uint32_t free_head; | |
1999a70a | 75 | QTestState *qts = global_qtest; |
397c767b FZ |
76 | |
77 | vq = vs->vq[2]; | |
78 | ||
79 | req.lun[0] = 1; /* Select LUN */ | |
80 | req.lun[1] = 1; /* Select target 1 */ | |
74f079a7 | 81 | memcpy(req.cdb, cdb, VIRTIO_SCSI_CDB_SIZE); |
397c767b FZ |
82 | |
83 | /* XXX: Fix endian if any multi-byte field in req/resp is used */ | |
84 | ||
85 | /* Add request header */ | |
86 | req_addr = qvirtio_scsi_alloc(vs, sizeof(req), &req); | |
1999a70a | 87 | free_head = qvirtqueue_add(qts, vq, req_addr, sizeof(req), false, true); |
397c767b FZ |
88 | |
89 | if (data_out_len) { | |
90 | data_out_addr = qvirtio_scsi_alloc(vs, data_out_len, data_out); | |
1999a70a | 91 | qvirtqueue_add(qts, vq, data_out_addr, data_out_len, false, true); |
397c767b FZ |
92 | } |
93 | ||
94 | /* Add response header */ | |
95 | resp_addr = qvirtio_scsi_alloc(vs, sizeof(resp), &resp); | |
1999a70a | 96 | qvirtqueue_add(qts, vq, resp_addr, sizeof(resp), true, !!data_in_len); |
397c767b FZ |
97 | |
98 | if (data_in_len) { | |
99 | data_in_addr = qvirtio_scsi_alloc(vs, data_in_len, data_in); | |
1999a70a | 100 | qvirtqueue_add(qts, vq, data_in_addr, data_in_len, true, false); |
397c767b FZ |
101 | } |
102 | ||
1999a70a TH |
103 | qvirtqueue_kick(qts, vs->dev, vq, free_head); |
104 | qvirtio_wait_used_elem(qts, vs->dev, vq, free_head, NULL, | |
be3a6781 | 105 | QVIRTIO_SCSI_TIMEOUT_US); |
397c767b | 106 | |
74f079a7 SH |
107 | response = readb(resp_addr + |
108 | offsetof(struct virtio_scsi_cmd_resp, response)); | |
397c767b | 109 | |
4bb7b0da SH |
110 | if (resp_out) { |
111 | memread(resp_addr, resp_out, sizeof(*resp_out)); | |
112 | } | |
113 | ||
4e200798 EGE |
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); | |
397c767b FZ |
118 | return response; |
119 | } | |
120 | ||
4e200798 | 121 | static QVirtioSCSIQueues *qvirtio_scsi_init(QVirtioDevice *dev) |
4bb7b0da | 122 | { |
4e200798 | 123 | QVirtioSCSIQueues *vs; |
74f079a7 | 124 | const uint8_t test_unit_ready_cdb[VIRTIO_SCSI_CDB_SIZE] = {}; |
74f079a7 | 125 | struct virtio_scsi_cmd_resp resp; |
e73255be | 126 | uint64_t features; |
4bb7b0da SH |
127 | int i; |
128 | ||
4e200798 EGE |
129 | vs = g_new0(QVirtioSCSIQueues, 1); |
130 | vs->dev = dev; | |
e73255be SH |
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 | ||
4e200798 | 136 | vs->num_queues = qvirtio_config_readl(dev, 0); |
4bb7b0da SH |
137 | |
138 | g_assert_cmpint(vs->num_queues, <, MAX_NUM_QUEUES); | |
139 | ||
140 | for (i = 0; i < vs->num_queues + 2; i++) { | |
4e200798 | 141 | vs->vq[i] = qvirtqueue_setup(dev, alloc, i); |
4bb7b0da SH |
142 | } |
143 | ||
e73255be SH |
144 | qvirtio_set_driver_ok(dev); |
145 | ||
4bb7b0da SH |
146 | /* Clear the POWER ON OCCURRED unit attention */ |
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); /* Fixed format sense buffer */ | |
152 | g_assert_cmpint(resp.sense[2], ==, UNIT_ATTENTION); | |
153 | g_assert_cmpint(resp.sense[12], ==, 0x29); /* POWER ON */ | |
154 | g_assert_cmpint(resp.sense[13], ==, 0x00); | |
155 | ||
156 | return vs; | |
157 | } | |
158 | ||
4e200798 | 159 | static void hotplug(void *obj, void *data, QGuestAllocator *alloc) |
ac2c4946 | 160 | { |
e5758de4 TH |
161 | QTestState *qts = global_qtest; |
162 | ||
163 | qtest_qmp_device_add(qts, "scsi-hd", "scsihd", "{'drive': 'drv1'}"); | |
164 | qtest_qmp_device_del(qts, "scsihd"); | |
ac2c4946 IM |
165 | } |
166 | ||
397c767b | 167 | /* Test WRITE SAME with the lba not aligned */ |
4e200798 EGE |
168 | static void test_unaligned_write_same(void *obj, void *data, |
169 | QGuestAllocator *t_alloc) | |
397c767b | 170 | { |
4e200798 EGE |
171 | QVirtioSCSI *scsi = obj; |
172 | QVirtioSCSIQueues *vs; | |
975b6655 FZ |
173 | uint8_t buf1[512] = { 0 }; |
174 | uint8_t buf2[512] = { 1 }; | |
74f079a7 SH |
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 | }; | |
4397a018 PB |
181 | const uint8_t write_same_cdb_ndob[VIRTIO_SCSI_CDB_SIZE] = { |
182 | 0x41, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0x00, 0x00 | |
183 | }; | |
397c767b | 184 | |
4e200798 EGE |
185 | alloc = t_alloc; |
186 | vs = qvirtio_scsi_init(scsi->vdev); | |
397c767b FZ |
187 | |
188 | g_assert_cmphex(0, ==, | |
4e200798 EGE |
189 | virtio_scsi_do_command(vs, write_same_cdb_1, NULL, 0, buf1, 512, |
190 | NULL)); | |
975b6655 FZ |
191 | |
192 | g_assert_cmphex(0, ==, | |
4e200798 EGE |
193 | virtio_scsi_do_command(vs, write_same_cdb_2, NULL, 0, buf2, 512, |
194 | NULL)); | |
397c767b | 195 | |
4397a018 | 196 | g_assert_cmphex(0, ==, |
4e200798 EGE |
197 | virtio_scsi_do_command(vs, write_same_cdb_ndob, NULL, 0, NULL, 0, |
198 | NULL)); | |
4397a018 | 199 | |
397c767b | 200 | qvirtio_scsi_pci_free(vs); |
397c767b FZ |
201 | } |
202 | ||
edbe36ad KW |
203 | static void test_iothread_attach_node(void *obj, void *data, |
204 | QGuestAllocator *t_alloc) | |
205 | { | |
206 | QVirtioSCSIPCI *scsi_pci = obj; | |
207 | QVirtioSCSI *scsi = &scsi_pci->scsi; | |
208 | QVirtioSCSIQueues *vs; | |
209 | char tmp_path[] = "/tmp/qtest.XXXXXX"; | |
210 | int fd; | |
211 | int ret; | |
212 | ||
213 | uint8_t buf[512] = { 0 }; | |
214 | const uint8_t write_cdb[VIRTIO_SCSI_CDB_SIZE] = { | |
215 | /* WRITE(10) to LBA 0, transfer length 1 */ | |
216 | 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00 | |
217 | }; | |
218 | ||
219 | alloc = t_alloc; | |
220 | vs = qvirtio_scsi_init(scsi->vdev); | |
221 | ||
222 | /* Create a temporary qcow2 overlay*/ | |
223 | fd = mkstemp(tmp_path); | |
224 | g_assert(fd >= 0); | |
225 | close(fd); | |
226 | ||
227 | if (!have_qemu_img()) { | |
228 | g_test_message("QTEST_QEMU_IMG not set or qemu-img missing; " | |
229 | "skipping snapshot test"); | |
230 | goto fail; | |
231 | } | |
232 | ||
233 | mkqcow2(tmp_path, 64); | |
234 | ||
235 | /* Attach the overlay to the null0 node */ | |
6fc9f3d3 TH |
236 | qtest_qmp_assert_success(scsi_pci->pci_vdev.pdev->bus->qts, |
237 | "{'execute': 'blockdev-add', 'arguments': {" | |
238 | " 'driver': 'qcow2', 'node-name': 'overlay'," | |
239 | " 'backing': 'null0', 'file': {" | |
240 | " 'driver': 'file', 'filename': %s}}}", | |
241 | tmp_path); | |
edbe36ad KW |
242 | |
243 | /* Send a request to see if the AioContext is still right */ | |
244 | ret = virtio_scsi_do_command(vs, write_cdb, NULL, 0, buf, 512, NULL); | |
245 | g_assert_cmphex(ret, ==, 0); | |
246 | ||
247 | fail: | |
248 | qvirtio_scsi_pci_free(vs); | |
249 | unlink(tmp_path); | |
250 | } | |
251 | ||
4e200798 | 252 | static void *virtio_scsi_hotplug_setup(GString *cmd_line, void *arg) |
26c9a015 | 253 | { |
4e200798 | 254 | g_string_append(cmd_line, |
ca1ef1e6 AS |
255 | " -drive id=drv1,if=none,file=null-co://," |
256 | "file.read-zeroes=on,format=raw"); | |
4e200798 EGE |
257 | return arg; |
258 | } | |
26c9a015 | 259 | |
4e200798 EGE |
260 | static void *virtio_scsi_setup(GString *cmd_line, void *arg) |
261 | { | |
262 | g_string_append(cmd_line, | |
263 | " -drive file=blkdebug::null-co://," | |
ca1ef1e6 | 264 | "file.image.read-zeroes=on," |
4e200798 EGE |
265 | "if=none,id=dr1,format=raw,file.align=4k " |
266 | "-device scsi-hd,drive=dr1,lun=0,scsi-id=1"); | |
267 | return arg; | |
26c9a015 | 268 | } |
4e200798 | 269 | |
edbe36ad KW |
270 | static void *virtio_scsi_setup_iothread(GString *cmd_line, void *arg) |
271 | { | |
272 | g_string_append(cmd_line, | |
273 | " -object iothread,id=thread0" | |
ca1ef1e6 | 274 | " -blockdev driver=null-co,read-zeroes=on,node-name=null0" |
edbe36ad KW |
275 | " -device scsi-hd,drive=null0"); |
276 | return arg; | |
277 | } | |
278 | ||
4e200798 EGE |
279 | static void register_virtio_scsi_test(void) |
280 | { | |
281 | QOSGraphTestOptions opts = { }; | |
282 | ||
283 | opts.before = virtio_scsi_hotplug_setup; | |
284 | qos_add_test("hotplug", "virtio-scsi", hotplug, &opts); | |
285 | ||
286 | opts.before = virtio_scsi_setup; | |
287 | qos_add_test("unaligned-write-same", "virtio-scsi", | |
288 | test_unaligned_write_same, &opts); | |
edbe36ad KW |
289 | |
290 | opts.before = virtio_scsi_setup_iothread; | |
291 | opts.edge = (QOSGraphEdgeOptions) { | |
292 | .extra_device_opts = "iothread=thread0", | |
293 | }; | |
294 | qos_add_test("iothread-attach-node", "virtio-scsi-pci", | |
295 | test_iothread_attach_node, &opts); | |
4e200798 EGE |
296 | } |
297 | ||
298 | libqos_init(register_virtio_scsi_test); |