]>
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 | ||
11 | #include <glib.h> | |
12 | #include <string.h> | |
13 | #include "libqtest.h" | |
14 | #include "qemu/osdep.h" | |
397c767b | 15 | #include <stdio.h> |
4bb7b0da | 16 | #include "block/scsi.h" |
397c767b FZ |
17 | #include "libqos/virtio.h" |
18 | #include "libqos/virtio-pci.h" | |
19 | #include "libqos/pci-pc.h" | |
20 | #include "libqos/malloc.h" | |
21 | #include "libqos/malloc-pc.h" | |
22 | #include "libqos/malloc-generic.h" | |
23 | ||
24 | #define PCI_SLOT 0x02 | |
25 | #define PCI_FN 0x00 | |
26 | #define QVIRTIO_SCSI_TIMEOUT_US (1 * 1000 * 1000) | |
27 | #define CDB_SIZE 32 | |
28 | ||
29 | #define MAX_NUM_QUEUES 64 | |
30 | ||
31 | typedef struct { | |
32 | QVirtioDevice *dev; | |
33 | QGuestAllocator *alloc; | |
34 | QPCIBus *bus; | |
35 | int num_queues; | |
36 | QVirtQueue *vq[MAX_NUM_QUEUES + 2]; | |
37 | } QVirtIOSCSI; | |
38 | ||
39 | typedef struct { | |
40 | uint8_t lun[8]; | |
41 | int64_t tag; | |
42 | uint8_t task_attr; | |
43 | uint8_t prio; | |
44 | uint8_t crn; | |
45 | uint8_t cdb[CDB_SIZE]; | |
46 | } QEMU_PACKED QVirtIOSCSICmdReq; | |
47 | ||
48 | typedef struct { | |
49 | uint32_t sense_len; | |
50 | uint32_t resid; | |
51 | uint16_t status_qualifier; | |
52 | uint8_t status; | |
53 | uint8_t response; | |
54 | uint8_t sense[96]; | |
55 | } QEMU_PACKED QVirtIOSCSICmdResp; | |
26c9a015 | 56 | |
06b008d9 FZ |
57 | static void qvirtio_scsi_start(const char *extra_opts) |
58 | { | |
59 | char *cmdline; | |
60 | ||
61 | cmdline = g_strdup_printf( | |
62 | "-drive id=drv0,if=none,file=/dev/null,format=raw " | |
63 | "-device virtio-scsi-pci,id=vs0 " | |
64 | "-device scsi-hd,bus=vs0.0,drive=drv0 %s", | |
65 | extra_opts ? : ""); | |
66 | qtest_start(cmdline); | |
67 | g_free(cmdline); | |
68 | } | |
69 | ||
70 | static void qvirtio_scsi_stop(void) | |
71 | { | |
72 | qtest_end(); | |
73 | } | |
74 | ||
397c767b FZ |
75 | static void qvirtio_scsi_pci_free(QVirtIOSCSI *vs) |
76 | { | |
77 | int i; | |
78 | ||
79 | for (i = 0; i < vs->num_queues + 2; i++) { | |
80 | guest_free(vs->alloc, vs->vq[i]->desc); | |
81 | } | |
82 | pc_alloc_uninit(vs->alloc); | |
83 | qvirtio_pci_device_disable(container_of(vs->dev, QVirtioPCIDevice, vdev)); | |
84 | g_free(vs->dev); | |
85 | qpci_free_pc(vs->bus); | |
86 | } | |
87 | ||
88 | static uint64_t qvirtio_scsi_alloc(QVirtIOSCSI *vs, size_t alloc_size, | |
89 | const void *data) | |
90 | { | |
91 | uint64_t addr; | |
92 | ||
93 | addr = guest_alloc(vs->alloc, alloc_size); | |
94 | if (data) { | |
95 | memwrite(addr, data, alloc_size); | |
96 | } | |
97 | ||
98 | return addr; | |
99 | } | |
100 | ||
101 | static uint8_t virtio_scsi_do_command(QVirtIOSCSI *vs, const uint8_t *cdb, | |
102 | const uint8_t *data_in, | |
103 | size_t data_in_len, | |
4bb7b0da SH |
104 | uint8_t *data_out, size_t data_out_len, |
105 | QVirtIOSCSICmdResp *resp_out) | |
397c767b FZ |
106 | { |
107 | QVirtQueue *vq; | |
108 | QVirtIOSCSICmdReq req = { { 0 } }; | |
109 | QVirtIOSCSICmdResp resp = { .response = 0xff, .status = 0xff }; | |
110 | uint64_t req_addr, resp_addr, data_in_addr = 0, data_out_addr = 0; | |
111 | uint8_t response; | |
112 | uint32_t free_head; | |
113 | ||
114 | vq = vs->vq[2]; | |
115 | ||
116 | req.lun[0] = 1; /* Select LUN */ | |
117 | req.lun[1] = 1; /* Select target 1 */ | |
118 | memcpy(req.cdb, cdb, CDB_SIZE); | |
119 | ||
120 | /* XXX: Fix endian if any multi-byte field in req/resp is used */ | |
121 | ||
122 | /* Add request header */ | |
123 | req_addr = qvirtio_scsi_alloc(vs, sizeof(req), &req); | |
124 | free_head = qvirtqueue_add(vq, req_addr, sizeof(req), false, true); | |
125 | ||
126 | if (data_out_len) { | |
127 | data_out_addr = qvirtio_scsi_alloc(vs, data_out_len, data_out); | |
128 | qvirtqueue_add(vq, data_out_addr, data_out_len, false, true); | |
129 | } | |
130 | ||
131 | /* Add response header */ | |
132 | resp_addr = qvirtio_scsi_alloc(vs, sizeof(resp), &resp); | |
133 | qvirtqueue_add(vq, resp_addr, sizeof(resp), true, !!data_in_len); | |
134 | ||
135 | if (data_in_len) { | |
136 | data_in_addr = qvirtio_scsi_alloc(vs, data_in_len, data_in); | |
137 | qvirtqueue_add(vq, data_in_addr, data_in_len, true, false); | |
138 | } | |
139 | ||
140 | qvirtqueue_kick(&qvirtio_pci, vs->dev, vq, free_head); | |
141 | qvirtio_wait_queue_isr(&qvirtio_pci, vs->dev, vq, QVIRTIO_SCSI_TIMEOUT_US); | |
142 | ||
143 | response = readb(resp_addr + offsetof(QVirtIOSCSICmdResp, response)); | |
144 | ||
4bb7b0da SH |
145 | if (resp_out) { |
146 | memread(resp_addr, resp_out, sizeof(*resp_out)); | |
147 | } | |
148 | ||
397c767b FZ |
149 | guest_free(vs->alloc, req_addr); |
150 | guest_free(vs->alloc, resp_addr); | |
151 | guest_free(vs->alloc, data_in_addr); | |
152 | guest_free(vs->alloc, data_out_addr); | |
153 | return response; | |
154 | } | |
155 | ||
4bb7b0da SH |
156 | static QVirtIOSCSI *qvirtio_scsi_pci_init(int slot) |
157 | { | |
158 | const uint8_t test_unit_ready_cdb[CDB_SIZE] = {}; | |
159 | QVirtIOSCSI *vs; | |
160 | QVirtioPCIDevice *dev; | |
161 | QVirtIOSCSICmdResp resp; | |
162 | void *addr; | |
163 | int i; | |
164 | ||
165 | vs = g_new0(QVirtIOSCSI, 1); | |
166 | vs->alloc = pc_alloc_init(); | |
167 | vs->bus = qpci_init_pc(); | |
168 | ||
169 | dev = qvirtio_pci_device_find(vs->bus, QVIRTIO_SCSI_DEVICE_ID); | |
170 | vs->dev = (QVirtioDevice *)dev; | |
171 | g_assert(dev != NULL); | |
172 | g_assert_cmphex(vs->dev->device_type, ==, QVIRTIO_SCSI_DEVICE_ID); | |
173 | ||
174 | qvirtio_pci_device_enable(dev); | |
175 | qvirtio_reset(&qvirtio_pci, vs->dev); | |
176 | qvirtio_set_acknowledge(&qvirtio_pci, vs->dev); | |
177 | qvirtio_set_driver(&qvirtio_pci, vs->dev); | |
178 | ||
179 | addr = dev->addr + QVIRTIO_PCI_DEVICE_SPECIFIC_NO_MSIX; | |
180 | vs->num_queues = qvirtio_config_readl(&qvirtio_pci, vs->dev, | |
181 | (uint64_t)(uintptr_t)addr); | |
182 | ||
183 | g_assert_cmpint(vs->num_queues, <, MAX_NUM_QUEUES); | |
184 | ||
185 | for (i = 0; i < vs->num_queues + 2; i++) { | |
186 | vs->vq[i] = qvirtqueue_setup(&qvirtio_pci, vs->dev, vs->alloc, i); | |
187 | } | |
188 | ||
189 | /* Clear the POWER ON OCCURRED unit attention */ | |
190 | g_assert_cmpint(virtio_scsi_do_command(vs, test_unit_ready_cdb, | |
191 | NULL, 0, NULL, 0, &resp), | |
192 | ==, 0); | |
193 | g_assert_cmpint(resp.status, ==, CHECK_CONDITION); | |
194 | g_assert_cmpint(resp.sense[0], ==, 0x70); /* Fixed format sense buffer */ | |
195 | g_assert_cmpint(resp.sense[2], ==, UNIT_ATTENTION); | |
196 | g_assert_cmpint(resp.sense[12], ==, 0x29); /* POWER ON */ | |
197 | g_assert_cmpint(resp.sense[13], ==, 0x00); | |
198 | ||
199 | return vs; | |
200 | } | |
201 | ||
26c9a015 AF |
202 | /* Tests only initialization so far. TODO: Replace with functional tests */ |
203 | static void pci_nop(void) | |
204 | { | |
06b008d9 FZ |
205 | qvirtio_scsi_start(NULL); |
206 | qvirtio_scsi_stop(); | |
26c9a015 AF |
207 | } |
208 | ||
ac2c4946 IM |
209 | static void hotplug(void) |
210 | { | |
211 | QDict *response; | |
212 | ||
06b008d9 | 213 | qvirtio_scsi_start("-drive id=drv1,if=none,file=/dev/null,format=raw"); |
ac2c4946 IM |
214 | response = qmp("{\"execute\": \"device_add\"," |
215 | " \"arguments\": {" | |
216 | " \"driver\": \"scsi-hd\"," | |
217 | " \"id\": \"scsi-hd\"," | |
218 | " \"drive\": \"drv1\"" | |
219 | "}}"); | |
220 | ||
221 | g_assert(response); | |
222 | g_assert(!qdict_haskey(response, "error")); | |
223 | QDECREF(response); | |
224 | ||
225 | response = qmp("{\"execute\": \"device_del\"," | |
226 | " \"arguments\": {" | |
227 | " \"id\": \"scsi-hd\"" | |
228 | "}}"); | |
229 | ||
230 | g_assert(response); | |
231 | g_assert(!qdict_haskey(response, "error")); | |
232 | g_assert(qdict_haskey(response, "event")); | |
233 | g_assert(!strcmp(qdict_get_str(response, "event"), "DEVICE_DELETED")); | |
234 | QDECREF(response); | |
06b008d9 | 235 | qvirtio_scsi_stop(); |
ac2c4946 IM |
236 | } |
237 | ||
397c767b FZ |
238 | /* Test WRITE SAME with the lba not aligned */ |
239 | static void test_unaligned_write_same(void) | |
240 | { | |
241 | QVirtIOSCSI *vs; | |
975b6655 FZ |
242 | uint8_t buf1[512] = { 0 }; |
243 | uint8_t buf2[512] = { 1 }; | |
244 | const uint8_t write_same_cdb_1[CDB_SIZE] = { 0x41, 0x00, 0x00, 0x00, 0x00, | |
397c767b | 245 | 0x01, 0x00, 0x00, 0x02, 0x00 }; |
975b6655 FZ |
246 | const uint8_t write_same_cdb_2[CDB_SIZE] = { 0x41, 0x00, 0x00, 0x00, 0x00, |
247 | 0x01, 0x00, 0x33, 0x00, 0x00 }; | |
397c767b FZ |
248 | |
249 | qvirtio_scsi_start("-drive file=blkdebug::null-co://,if=none,id=dr1" | |
250 | ",format=raw,file.align=4k " | |
251 | "-device scsi-disk,drive=dr1,lun=0,scsi-id=1"); | |
252 | vs = qvirtio_scsi_pci_init(PCI_SLOT); | |
253 | ||
254 | g_assert_cmphex(0, ==, | |
975b6655 FZ |
255 | virtio_scsi_do_command(vs, write_same_cdb_1, NULL, 0, buf1, 512, NULL)); |
256 | ||
257 | g_assert_cmphex(0, ==, | |
258 | virtio_scsi_do_command(vs, write_same_cdb_2, NULL, 0, buf2, 512, NULL)); | |
397c767b FZ |
259 | |
260 | qvirtio_scsi_pci_free(vs); | |
261 | qvirtio_scsi_stop(); | |
262 | } | |
263 | ||
26c9a015 AF |
264 | int main(int argc, char **argv) |
265 | { | |
266 | int ret; | |
267 | ||
268 | g_test_init(&argc, &argv, NULL); | |
269 | qtest_add_func("/virtio/scsi/pci/nop", pci_nop); | |
ac2c4946 | 270 | qtest_add_func("/virtio/scsi/pci/hotplug", hotplug); |
397c767b FZ |
271 | qtest_add_func("/virtio/scsi/pci/scsi-disk/unaligned-write-same", |
272 | test_unaligned_write_same); | |
26c9a015 | 273 | |
26c9a015 AF |
274 | ret = g_test_run(); |
275 | ||
26c9a015 AF |
276 | return ret; |
277 | } |