]>
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" |
26c9a015 | 12 | #include "libqtest.h" |
08e2c9f1 | 13 | #include "scsi/constants.h" |
a980f7f2 | 14 | #include "libqos/libqos-pc.h" |
30ca440e | 15 | #include "libqos/libqos-spapr.h" |
397c767b FZ |
16 | #include "libqos/virtio.h" |
17 | #include "libqos/virtio-pci.h" | |
8ac9e205 | 18 | #include "standard-headers/linux/virtio_ids.h" |
c75f4c06 | 19 | #include "standard-headers/linux/virtio_pci.h" |
74f079a7 | 20 | #include "standard-headers/linux/virtio_scsi.h" |
397c767b FZ |
21 | |
22 | #define PCI_SLOT 0x02 | |
23 | #define PCI_FN 0x00 | |
24 | #define QVIRTIO_SCSI_TIMEOUT_US (1 * 1000 * 1000) | |
397c767b FZ |
25 | |
26 | #define MAX_NUM_QUEUES 64 | |
27 | ||
28 | typedef struct { | |
29 | QVirtioDevice *dev; | |
a980f7f2 | 30 | QOSState *qs; |
397c767b FZ |
31 | int num_queues; |
32 | QVirtQueue *vq[MAX_NUM_QUEUES + 2]; | |
33 | } QVirtIOSCSI; | |
34 | ||
a980f7f2 | 35 | static QOSState *qvirtio_scsi_start(const char *extra_opts) |
06b008d9 | 36 | { |
3d95fb97 | 37 | QOSState *qs; |
30ca440e | 38 | const char *arch = qtest_get_arch(); |
2420d369 | 39 | const char *cmd = "-drive id=drv0,if=none,file=null-co://,format=raw " |
a980f7f2 LV |
40 | "-device virtio-scsi-pci,id=vs0 " |
41 | "-device scsi-hd,bus=vs0.0,drive=drv0 %s"; | |
42 | ||
30ca440e | 43 | if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { |
3d95fb97 EB |
44 | qs = qtest_pc_boot(cmd, extra_opts ? : ""); |
45 | } else if (strcmp(arch, "ppc64") == 0) { | |
46 | qs = qtest_spapr_boot(cmd, extra_opts ? : ""); | |
47 | } else { | |
48 | g_printerr("virtio-scsi tests are only available on x86 or ppc64\n"); | |
49 | exit(EXIT_FAILURE); | |
30ca440e | 50 | } |
3d95fb97 EB |
51 | global_qtest = qs->qts; |
52 | return qs; | |
06b008d9 FZ |
53 | } |
54 | ||
a980f7f2 | 55 | static void qvirtio_scsi_stop(QOSState *qs) |
06b008d9 | 56 | { |
a980f7f2 | 57 | qtest_shutdown(qs); |
06b008d9 FZ |
58 | } |
59 | ||
397c767b FZ |
60 | static void qvirtio_scsi_pci_free(QVirtIOSCSI *vs) |
61 | { | |
62 | int i; | |
63 | ||
64 | for (i = 0; i < vs->num_queues + 2; i++) { | |
a980f7f2 | 65 | qvirtqueue_cleanup(vs->dev->bus, vs->vq[i], vs->qs->alloc); |
397c767b | 66 | } |
397c767b | 67 | qvirtio_pci_device_disable(container_of(vs->dev, QVirtioPCIDevice, vdev)); |
3caab54d | 68 | qvirtio_pci_device_free((QVirtioPCIDevice *)vs->dev); |
a980f7f2 | 69 | qvirtio_scsi_stop(vs->qs); |
f62e0bbb | 70 | g_free(vs); |
397c767b FZ |
71 | } |
72 | ||
73 | static uint64_t qvirtio_scsi_alloc(QVirtIOSCSI *vs, size_t alloc_size, | |
74 | const void *data) | |
75 | { | |
76 | uint64_t addr; | |
77 | ||
a980f7f2 | 78 | addr = guest_alloc(vs->qs->alloc, alloc_size); |
397c767b FZ |
79 | if (data) { |
80 | memwrite(addr, data, alloc_size); | |
81 | } | |
82 | ||
83 | return addr; | |
84 | } | |
85 | ||
86 | static uint8_t virtio_scsi_do_command(QVirtIOSCSI *vs, const uint8_t *cdb, | |
87 | const uint8_t *data_in, | |
88 | size_t data_in_len, | |
4bb7b0da | 89 | uint8_t *data_out, size_t data_out_len, |
74f079a7 | 90 | struct virtio_scsi_cmd_resp *resp_out) |
397c767b FZ |
91 | { |
92 | QVirtQueue *vq; | |
74f079a7 SH |
93 | struct virtio_scsi_cmd_req req = { { 0 } }; |
94 | struct virtio_scsi_cmd_resp resp = { .response = 0xff, .status = 0xff }; | |
397c767b FZ |
95 | uint64_t req_addr, resp_addr, data_in_addr = 0, data_out_addr = 0; |
96 | uint8_t response; | |
97 | uint32_t free_head; | |
98 | ||
99 | vq = vs->vq[2]; | |
100 | ||
101 | req.lun[0] = 1; /* Select LUN */ | |
102 | req.lun[1] = 1; /* Select target 1 */ | |
74f079a7 | 103 | memcpy(req.cdb, cdb, VIRTIO_SCSI_CDB_SIZE); |
397c767b FZ |
104 | |
105 | /* XXX: Fix endian if any multi-byte field in req/resp is used */ | |
106 | ||
107 | /* Add request header */ | |
108 | req_addr = qvirtio_scsi_alloc(vs, sizeof(req), &req); | |
109 | free_head = qvirtqueue_add(vq, req_addr, sizeof(req), false, true); | |
110 | ||
111 | if (data_out_len) { | |
112 | data_out_addr = qvirtio_scsi_alloc(vs, data_out_len, data_out); | |
113 | qvirtqueue_add(vq, data_out_addr, data_out_len, false, true); | |
114 | } | |
115 | ||
116 | /* Add response header */ | |
117 | resp_addr = qvirtio_scsi_alloc(vs, sizeof(resp), &resp); | |
118 | qvirtqueue_add(vq, resp_addr, sizeof(resp), true, !!data_in_len); | |
119 | ||
120 | if (data_in_len) { | |
121 | data_in_addr = qvirtio_scsi_alloc(vs, data_in_len, data_in); | |
122 | qvirtqueue_add(vq, data_in_addr, data_in_len, true, false); | |
123 | } | |
124 | ||
6b9cdf4c | 125 | qvirtqueue_kick(vs->dev, vq, free_head); |
be3a6781 GK |
126 | qvirtio_wait_used_elem(vs->dev, vq, free_head, NULL, |
127 | QVIRTIO_SCSI_TIMEOUT_US); | |
397c767b | 128 | |
74f079a7 SH |
129 | response = readb(resp_addr + |
130 | offsetof(struct virtio_scsi_cmd_resp, response)); | |
397c767b | 131 | |
4bb7b0da SH |
132 | if (resp_out) { |
133 | memread(resp_addr, resp_out, sizeof(*resp_out)); | |
134 | } | |
135 | ||
a980f7f2 LV |
136 | guest_free(vs->qs->alloc, req_addr); |
137 | guest_free(vs->qs->alloc, resp_addr); | |
138 | guest_free(vs->qs->alloc, data_in_addr); | |
139 | guest_free(vs->qs->alloc, data_out_addr); | |
397c767b FZ |
140 | return response; |
141 | } | |
142 | ||
4bb7b0da SH |
143 | static QVirtIOSCSI *qvirtio_scsi_pci_init(int slot) |
144 | { | |
74f079a7 | 145 | const uint8_t test_unit_ready_cdb[VIRTIO_SCSI_CDB_SIZE] = {}; |
4bb7b0da SH |
146 | QVirtIOSCSI *vs; |
147 | QVirtioPCIDevice *dev; | |
74f079a7 | 148 | struct virtio_scsi_cmd_resp resp; |
4bb7b0da SH |
149 | int i; |
150 | ||
151 | vs = g_new0(QVirtIOSCSI, 1); | |
4bb7b0da | 152 | |
a980f7f2 LV |
153 | vs->qs = qvirtio_scsi_start("-drive file=blkdebug::null-co://," |
154 | "if=none,id=dr1,format=raw,file.align=4k " | |
8ee47a88 | 155 | "-device scsi-hd,drive=dr1,lun=0,scsi-id=1"); |
a980f7f2 | 156 | dev = qvirtio_pci_device_find(vs->qs->pcibus, VIRTIO_ID_SCSI); |
4bb7b0da SH |
157 | vs->dev = (QVirtioDevice *)dev; |
158 | g_assert(dev != NULL); | |
8ac9e205 | 159 | g_assert_cmphex(vs->dev->device_type, ==, VIRTIO_ID_SCSI); |
4bb7b0da SH |
160 | |
161 | qvirtio_pci_device_enable(dev); | |
6b9cdf4c LV |
162 | qvirtio_reset(vs->dev); |
163 | qvirtio_set_acknowledge(vs->dev); | |
164 | qvirtio_set_driver(vs->dev); | |
4bb7b0da | 165 | |
246fc0fb | 166 | vs->num_queues = qvirtio_config_readl(vs->dev, 0); |
4bb7b0da SH |
167 | |
168 | g_assert_cmpint(vs->num_queues, <, MAX_NUM_QUEUES); | |
169 | ||
170 | for (i = 0; i < vs->num_queues + 2; i++) { | |
a980f7f2 | 171 | vs->vq[i] = qvirtqueue_setup(vs->dev, vs->qs->alloc, i); |
4bb7b0da SH |
172 | } |
173 | ||
174 | /* Clear the POWER ON OCCURRED unit attention */ | |
175 | g_assert_cmpint(virtio_scsi_do_command(vs, test_unit_ready_cdb, | |
176 | NULL, 0, NULL, 0, &resp), | |
177 | ==, 0); | |
178 | g_assert_cmpint(resp.status, ==, CHECK_CONDITION); | |
179 | g_assert_cmpint(resp.sense[0], ==, 0x70); /* Fixed format sense buffer */ | |
180 | g_assert_cmpint(resp.sense[2], ==, UNIT_ATTENTION); | |
181 | g_assert_cmpint(resp.sense[12], ==, 0x29); /* POWER ON */ | |
182 | g_assert_cmpint(resp.sense[13], ==, 0x00); | |
183 | ||
184 | return vs; | |
185 | } | |
186 | ||
26c9a015 AF |
187 | /* Tests only initialization so far. TODO: Replace with functional tests */ |
188 | static void pci_nop(void) | |
189 | { | |
a980f7f2 LV |
190 | QOSState *qs; |
191 | ||
192 | qs = qvirtio_scsi_start(NULL); | |
193 | qvirtio_scsi_stop(qs); | |
26c9a015 AF |
194 | } |
195 | ||
ac2c4946 IM |
196 | static void hotplug(void) |
197 | { | |
a980f7f2 | 198 | QOSState *qs; |
ac2c4946 | 199 | |
2420d369 FZ |
200 | qs = qvirtio_scsi_start( |
201 | "-drive id=drv1,if=none,file=null-co://,format=raw"); | |
82cab70b | 202 | qtest_qmp_device_add("scsi-hd", "scsihd", "{'drive': 'drv1'}"); |
acd80015 | 203 | qtest_qmp_device_del("scsihd"); |
a980f7f2 | 204 | qvirtio_scsi_stop(qs); |
ac2c4946 IM |
205 | } |
206 | ||
397c767b FZ |
207 | /* Test WRITE SAME with the lba not aligned */ |
208 | static void test_unaligned_write_same(void) | |
209 | { | |
210 | QVirtIOSCSI *vs; | |
975b6655 FZ |
211 | uint8_t buf1[512] = { 0 }; |
212 | uint8_t buf2[512] = { 1 }; | |
74f079a7 SH |
213 | const uint8_t write_same_cdb_1[VIRTIO_SCSI_CDB_SIZE] = { |
214 | 0x41, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0x00 | |
215 | }; | |
216 | const uint8_t write_same_cdb_2[VIRTIO_SCSI_CDB_SIZE] = { | |
217 | 0x41, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0x00, 0x00 | |
218 | }; | |
4397a018 PB |
219 | const uint8_t write_same_cdb_ndob[VIRTIO_SCSI_CDB_SIZE] = { |
220 | 0x41, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x33, 0x00, 0x00 | |
221 | }; | |
397c767b | 222 | |
397c767b FZ |
223 | vs = qvirtio_scsi_pci_init(PCI_SLOT); |
224 | ||
225 | g_assert_cmphex(0, ==, | |
975b6655 FZ |
226 | virtio_scsi_do_command(vs, write_same_cdb_1, NULL, 0, buf1, 512, NULL)); |
227 | ||
228 | g_assert_cmphex(0, ==, | |
229 | virtio_scsi_do_command(vs, write_same_cdb_2, NULL, 0, buf2, 512, NULL)); | |
397c767b | 230 | |
4397a018 PB |
231 | g_assert_cmphex(0, ==, |
232 | virtio_scsi_do_command(vs, write_same_cdb_ndob, NULL, 0, NULL, 0, NULL)); | |
233 | ||
397c767b | 234 | qvirtio_scsi_pci_free(vs); |
397c767b FZ |
235 | } |
236 | ||
26c9a015 AF |
237 | int main(int argc, char **argv) |
238 | { | |
26c9a015 AF |
239 | g_test_init(&argc, &argv, NULL); |
240 | qtest_add_func("/virtio/scsi/pci/nop", pci_nop); | |
ac2c4946 | 241 | qtest_add_func("/virtio/scsi/pci/hotplug", hotplug); |
397c767b FZ |
242 | qtest_add_func("/virtio/scsi/pci/scsi-disk/unaligned-write-same", |
243 | test_unaligned_write_same); | |
26c9a015 | 244 | |
9be38598 | 245 | return g_test_run(); |
26c9a015 | 246 | } |