Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 1 | /* |
| 2 | * QTest testcase for VirtIO SCSI |
| 3 | * |
| 4 | * Copyright (c) 2014 SUSE LINUX Products GmbH |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 5 | * Copyright (c) 2015 Red Hat Inc. |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 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" |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 15 | #include <stdio.h> |
Stefan Hajnoczi | 4bb7b0d | 2015-07-30 14:16:13 +0100 | [diff] [blame] | 16 | #include "block/scsi.h" |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 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; |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 56 | |
Fam Zheng | 06b008d9 | 2015-04-24 19:35:19 +0800 | [diff] [blame] | 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 | |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 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, |
Stefan Hajnoczi | 4bb7b0d | 2015-07-30 14:16:13 +0100 | [diff] [blame] | 104 | uint8_t *data_out, size_t data_out_len, |
| 105 | QVirtIOSCSICmdResp *resp_out) |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 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 | |
Stefan Hajnoczi | 4bb7b0d | 2015-07-30 14:16:13 +0100 | [diff] [blame] | 145 | if (resp_out) { |
| 146 | memread(resp_addr, resp_out, sizeof(*resp_out)); |
| 147 | } |
| 148 | |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 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 | |
Stefan Hajnoczi | 4bb7b0d | 2015-07-30 14:16:13 +0100 | [diff] [blame] | 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 | |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 202 | /* Tests only initialization so far. TODO: Replace with functional tests */ |
| 203 | static void pci_nop(void) |
| 204 | { |
Fam Zheng | 06b008d9 | 2015-04-24 19:35:19 +0800 | [diff] [blame] | 205 | qvirtio_scsi_start(NULL); |
| 206 | qvirtio_scsi_stop(); |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 207 | } |
| 208 | |
Igor Mammedov | ac2c494 | 2014-09-26 09:28:06 +0000 | [diff] [blame] | 209 | static void hotplug(void) |
| 210 | { |
| 211 | QDict *response; |
| 212 | |
Fam Zheng | 06b008d9 | 2015-04-24 19:35:19 +0800 | [diff] [blame] | 213 | qvirtio_scsi_start("-drive id=drv1,if=none,file=/dev/null,format=raw"); |
Igor Mammedov | ac2c494 | 2014-09-26 09:28:06 +0000 | [diff] [blame] | 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); |
Fam Zheng | 06b008d9 | 2015-04-24 19:35:19 +0800 | [diff] [blame] | 235 | qvirtio_scsi_stop(); |
Igor Mammedov | ac2c494 | 2014-09-26 09:28:06 +0000 | [diff] [blame] | 236 | } |
| 237 | |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 238 | /* Test WRITE SAME with the lba not aligned */ |
| 239 | static void test_unaligned_write_same(void) |
| 240 | { |
| 241 | QVirtIOSCSI *vs; |
Fam Zheng | 975b665 | 2015-07-29 16:45:12 +0800 | [diff] [blame] | 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, |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 245 | 0x01, 0x00, 0x00, 0x02, 0x00 }; |
Fam Zheng | 975b665 | 2015-07-29 16:45:12 +0800 | [diff] [blame] | 246 | const uint8_t write_same_cdb_2[CDB_SIZE] = { 0x41, 0x00, 0x00, 0x00, 0x00, |
| 247 | 0x01, 0x00, 0x33, 0x00, 0x00 }; |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 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, ==, |
Fam Zheng | 975b665 | 2015-07-29 16:45:12 +0800 | [diff] [blame] | 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)); |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 259 | |
| 260 | qvirtio_scsi_pci_free(vs); |
| 261 | qvirtio_scsi_stop(); |
| 262 | } |
| 263 | |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 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); |
Igor Mammedov | ac2c494 | 2014-09-26 09:28:06 +0000 | [diff] [blame] | 270 | qtest_add_func("/virtio/scsi/pci/hotplug", hotplug); |
Fam Zheng | 397c767 | 2015-04-24 19:35:20 +0800 | [diff] [blame] | 271 | qtest_add_func("/virtio/scsi/pci/scsi-disk/unaligned-write-same", |
| 272 | test_unaligned_write_same); |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 273 | |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 274 | ret = g_test_run(); |
| 275 | |
Andreas Färber | 26c9a01 | 2014-02-21 16:42:15 +0100 | [diff] [blame] | 276 | return ret; |
| 277 | } |