| 1 | /* |
| 2 | * QTest testcase for NVMe |
| 3 | * |
| 4 | * Copyright (c) 2014 SUSE LINUX Products GmbH |
| 5 | * |
| 6 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 7 | * See the COPYING file in the top-level directory. |
| 8 | */ |
| 9 | |
| 10 | #include "qemu/osdep.h" |
| 11 | #include <glib/gstdio.h> |
| 12 | #include "qemu/bswap.h" |
| 13 | #include "qemu/module.h" |
| 14 | #include "qemu/units.h" |
| 15 | #include "libqtest.h" |
| 16 | #include "libqtest-single.h" |
| 17 | #include "libqos/qgraph.h" |
| 18 | #include "libqos/pci.h" |
| 19 | #include "block/nvme.h" |
| 20 | |
| 21 | typedef struct QNvme QNvme; |
| 22 | |
| 23 | struct QNvme { |
| 24 | QOSGraphObject obj; |
| 25 | QPCIDevice dev; |
| 26 | }; |
| 27 | |
| 28 | static void *nvme_get_driver(void *obj, const char *interface) |
| 29 | { |
| 30 | QNvme *nvme = obj; |
| 31 | |
| 32 | if (!g_strcmp0(interface, "pci-device")) { |
| 33 | return &nvme->dev; |
| 34 | } |
| 35 | |
| 36 | fprintf(stderr, "%s not present in nvme\n", interface); |
| 37 | g_assert_not_reached(); |
| 38 | } |
| 39 | |
| 40 | static void *nvme_create(void *pci_bus, QGuestAllocator *alloc, void *addr) |
| 41 | { |
| 42 | QNvme *nvme = g_new0(QNvme, 1); |
| 43 | QPCIBus *bus = pci_bus; |
| 44 | |
| 45 | qpci_device_init(&nvme->dev, bus, addr); |
| 46 | nvme->obj.get_driver = nvme_get_driver; |
| 47 | |
| 48 | return &nvme->obj; |
| 49 | } |
| 50 | |
| 51 | /* This used to cause a NULL pointer dereference. */ |
| 52 | static void nvmetest_oob_cmb_test(void *obj, void *data, QGuestAllocator *alloc) |
| 53 | { |
| 54 | const int cmb_bar_size = 2 * MiB; |
| 55 | QNvme *nvme = obj; |
| 56 | QPCIDevice *pdev = &nvme->dev; |
| 57 | QPCIBar bar; |
| 58 | |
| 59 | qpci_device_enable(pdev); |
| 60 | bar = qpci_iomap(pdev, 2, NULL); |
| 61 | |
| 62 | qpci_io_writel(pdev, bar, 0, 0xccbbaa99); |
| 63 | g_assert_cmpint(qpci_io_readb(pdev, bar, 0), ==, 0x99); |
| 64 | g_assert_cmpint(qpci_io_readw(pdev, bar, 0), ==, 0xaa99); |
| 65 | |
| 66 | /* Test partially out-of-bounds accesses. */ |
| 67 | qpci_io_writel(pdev, bar, cmb_bar_size - 1, 0x44332211); |
| 68 | g_assert_cmpint(qpci_io_readb(pdev, bar, cmb_bar_size - 1), ==, 0x11); |
| 69 | g_assert_cmpint(qpci_io_readw(pdev, bar, cmb_bar_size - 1), !=, 0x2211); |
| 70 | g_assert_cmpint(qpci_io_readl(pdev, bar, cmb_bar_size - 1), !=, 0x44332211); |
| 71 | } |
| 72 | |
| 73 | static void nvmetest_reg_read_test(void *obj, void *data, QGuestAllocator *alloc) |
| 74 | { |
| 75 | QNvme *nvme = obj; |
| 76 | QPCIDevice *pdev = &nvme->dev; |
| 77 | QPCIBar bar; |
| 78 | uint32_t cap_lo, cap_hi; |
| 79 | uint64_t cap; |
| 80 | |
| 81 | qpci_device_enable(pdev); |
| 82 | bar = qpci_iomap(pdev, 0, NULL); |
| 83 | |
| 84 | cap_lo = qpci_io_readl(pdev, bar, 0x0); |
| 85 | g_assert_cmpint(NVME_CAP_MQES(cap_lo), ==, 0x7ff); |
| 86 | |
| 87 | cap_hi = qpci_io_readl(pdev, bar, 0x4); |
| 88 | g_assert_cmpint(NVME_CAP_MPSMAX((uint64_t)cap_hi << 32), ==, 0x4); |
| 89 | |
| 90 | cap = qpci_io_readq(pdev, bar, 0x0); |
| 91 | g_assert_cmpint(NVME_CAP_MQES(cap), ==, 0x7ff); |
| 92 | g_assert_cmpint(NVME_CAP_MPSMAX(cap), ==, 0x4); |
| 93 | |
| 94 | qpci_iounmap(pdev, bar); |
| 95 | } |
| 96 | |
| 97 | static void nvmetest_pmr_reg_test(void *obj, void *data, QGuestAllocator *alloc) |
| 98 | { |
| 99 | QNvme *nvme = obj; |
| 100 | QPCIDevice *pdev = &nvme->dev; |
| 101 | QPCIBar pmr_bar, nvme_bar; |
| 102 | uint32_t pmrcap, pmrsts; |
| 103 | |
| 104 | qpci_device_enable(pdev); |
| 105 | pmr_bar = qpci_iomap(pdev, 4, NULL); |
| 106 | |
| 107 | /* Without Enabling PMRCTL check bar enablemet */ |
| 108 | qpci_io_writel(pdev, pmr_bar, 0, 0xccbbaa99); |
| 109 | g_assert_cmpint(qpci_io_readb(pdev, pmr_bar, 0), !=, 0x99); |
| 110 | g_assert_cmpint(qpci_io_readw(pdev, pmr_bar, 0), !=, 0xaa99); |
| 111 | |
| 112 | /* Map NVMe Bar Register to Enable the Mem Region */ |
| 113 | nvme_bar = qpci_iomap(pdev, 0, NULL); |
| 114 | |
| 115 | pmrcap = qpci_io_readl(pdev, nvme_bar, 0xe00); |
| 116 | g_assert_cmpint(NVME_PMRCAP_RDS(pmrcap), ==, 0x1); |
| 117 | g_assert_cmpint(NVME_PMRCAP_WDS(pmrcap), ==, 0x1); |
| 118 | g_assert_cmpint(NVME_PMRCAP_BIR(pmrcap), ==, 0x4); |
| 119 | g_assert_cmpint(NVME_PMRCAP_PMRWBM(pmrcap), ==, 0x2); |
| 120 | g_assert_cmpint(NVME_PMRCAP_CMSS(pmrcap), ==, 0x1); |
| 121 | |
| 122 | /* Enable PMRCTRL */ |
| 123 | qpci_io_writel(pdev, nvme_bar, 0xe04, 0x1); |
| 124 | |
| 125 | qpci_io_writel(pdev, pmr_bar, 0, 0x44332211); |
| 126 | g_assert_cmpint(qpci_io_readb(pdev, pmr_bar, 0), ==, 0x11); |
| 127 | g_assert_cmpint(qpci_io_readw(pdev, pmr_bar, 0), ==, 0x2211); |
| 128 | g_assert_cmpint(qpci_io_readl(pdev, pmr_bar, 0), ==, 0x44332211); |
| 129 | |
| 130 | pmrsts = qpci_io_readl(pdev, nvme_bar, 0xe08); |
| 131 | g_assert_cmpint(NVME_PMRSTS_NRDY(pmrsts), ==, 0x0); |
| 132 | |
| 133 | /* Disable PMRCTRL */ |
| 134 | qpci_io_writel(pdev, nvme_bar, 0xe04, 0x0); |
| 135 | |
| 136 | qpci_io_writel(pdev, pmr_bar, 0, 0x88776655); |
| 137 | g_assert_cmpint(qpci_io_readb(pdev, pmr_bar, 0), !=, 0x55); |
| 138 | g_assert_cmpint(qpci_io_readw(pdev, pmr_bar, 0), !=, 0x6655); |
| 139 | g_assert_cmpint(qpci_io_readl(pdev, pmr_bar, 0), !=, 0x88776655); |
| 140 | |
| 141 | pmrsts = qpci_io_readl(pdev, nvme_bar, 0xe08); |
| 142 | g_assert_cmpint(NVME_PMRSTS_NRDY(pmrsts), ==, 0x1); |
| 143 | |
| 144 | qpci_iounmap(pdev, nvme_bar); |
| 145 | qpci_iounmap(pdev, pmr_bar); |
| 146 | } |
| 147 | |
| 148 | typedef struct nvme_ctrl nvme_ctrl; |
| 149 | |
| 150 | typedef struct nvme_queue { |
| 151 | nvme_ctrl *ctrl; |
| 152 | uint64_t doorbell; |
| 153 | uint32_t size; |
| 154 | } nvme_queue; |
| 155 | |
| 156 | typedef struct nvme_cq { |
| 157 | nvme_queue common; |
| 158 | uint64_t phys_cqe; /* NvmeCqe* */ |
| 159 | uint16_t head; |
| 160 | uint8_t phase; |
| 161 | } nvme_cq; |
| 162 | |
| 163 | typedef struct nvme_sq { |
| 164 | nvme_queue common; |
| 165 | uint64_t phys_sqe; /* NvmeCmd* */ |
| 166 | nvme_cq *cq; |
| 167 | uint16_t head; |
| 168 | uint16_t tail; |
| 169 | } nvme_sq; |
| 170 | |
| 171 | struct nvme_ctrl { |
| 172 | QGuestAllocator *alloc; |
| 173 | QPCIDevice *pdev; |
| 174 | QPCIBar bar; |
| 175 | |
| 176 | uint32_t db_stride; |
| 177 | |
| 178 | nvme_sq admin_sq; |
| 179 | nvme_cq admin_cq; |
| 180 | }; |
| 181 | |
| 182 | #define PHYS_ADDR_OF_FIELD(T, base_phys_addr, field) \ |
| 183 | ((uint64_t)&((T *)(base_phys_addr))->field) |
| 184 | |
| 185 | #define PHYS_ADDR_OF(T, base_phys_addr, accessor) \ |
| 186 | ((uint64_t)&((T *)(base_phys_addr))accessor) |
| 187 | |
| 188 | static void nvme_init_queue_common(nvme_ctrl *ctrl, nvme_queue *q, |
| 189 | uint16_t db_idx, uint32_t size) |
| 190 | { |
| 191 | q->ctrl = ctrl; |
| 192 | q->doorbell = (sizeof(NvmeBar) + db_idx * ctrl->db_stride); |
| 193 | g_test_message(" q %p db_idx %u doorbell 0x%" PRIx64, q, db_idx, q->doorbell); |
| 194 | q->size = size; |
| 195 | } |
| 196 | |
| 197 | static void nvme_init_sq(nvme_ctrl *ctrl, nvme_sq *sq, uint16_t db_idx, |
| 198 | uint32_t size, nvme_cq *cq) |
| 199 | { |
| 200 | nvme_init_queue_common(ctrl, &sq->common, db_idx, size); |
| 201 | |
| 202 | sq->phys_sqe = guest_alloc(ctrl->alloc, sizeof(NvmeCmd) * size); |
| 203 | g_assert(sq->phys_sqe); |
| 204 | |
| 205 | g_test_message("sq %p db_idx %u sqe 0x%" PRIx64, sq, db_idx, sq->phys_sqe); |
| 206 | sq->cq = cq; |
| 207 | sq->head = 0; |
| 208 | sq->tail = 0; |
| 209 | } |
| 210 | |
| 211 | static void nvme_init_cq(nvme_ctrl *ctrl, nvme_cq *cq, uint16_t db_idx, |
| 212 | uint32_t size) |
| 213 | { |
| 214 | nvme_init_queue_common(ctrl, &cq->common, db_idx, size); |
| 215 | |
| 216 | cq->phys_cqe = guest_alloc(ctrl->alloc, sizeof(NvmeCqe) * size); |
| 217 | g_assert(cq->phys_cqe); |
| 218 | |
| 219 | g_test_message("cq %p db_idx %u cqe 0x%" PRIx64, cq, db_idx, cq->phys_cqe); |
| 220 | cq->head = 0; |
| 221 | cq->phase = 1; |
| 222 | } |
| 223 | |
| 224 | static int nvme_cqe_pending(nvme_cq *cq) |
| 225 | { |
| 226 | uint16_t status = qtest_readw( |
| 227 | cq->common.ctrl->pdev->bus->qts, |
| 228 | PHYS_ADDR_OF(NvmeCqe, cq->phys_cqe, [cq->head].status)); |
| 229 | return (status & 1) == cq->phase; |
| 230 | } |
| 231 | |
| 232 | static int nvme_is_cqe_success(NvmeCqe *cqe) |
| 233 | { |
| 234 | return (le16_to_cpu(cqe->status) >> 1) == NVME_SUCCESS; |
| 235 | } |
| 236 | |
| 237 | static NvmeCqe nvme_handle_cqe(nvme_sq *sq) |
| 238 | { |
| 239 | nvme_cq *cq = sq->cq; |
| 240 | uint64_t phys_cqe = PHYS_ADDR_OF( |
| 241 | NvmeCqe, cq->phys_cqe, [cq->head]); /* NvmeCqe* */ |
| 242 | NvmeCqe cqe; |
| 243 | uint16_t cq_next_head; |
| 244 | |
| 245 | g_assert(nvme_cqe_pending(cq)); |
| 246 | |
| 247 | qtest_memread(sq->common.ctrl->pdev->bus->qts, phys_cqe, &cqe, sizeof(cqe)); |
| 248 | |
| 249 | cq_next_head = (cq->head + 1) % cq->common.size; |
| 250 | g_test_message("cq %p head %u -> %u", cq, cq->head, cq_next_head); |
| 251 | if (cq_next_head < cq->head) { |
| 252 | cq->phase ^= 1; |
| 253 | } |
| 254 | cq->head = cq_next_head; |
| 255 | |
| 256 | if (cqe.sq_head != sq->head) { |
| 257 | sq->head = cqe.sq_head; |
| 258 | g_test_message("sq %p head = %u", sq, sq->head); |
| 259 | } |
| 260 | |
| 261 | qpci_io_writel(cq->common.ctrl->pdev, cq->common.ctrl->bar, |
| 262 | cq->common.doorbell, cq->head); |
| 263 | |
| 264 | return cqe; |
| 265 | } |
| 266 | |
| 267 | static NvmeCqe nvme_wait(nvme_sq *sq) |
| 268 | { |
| 269 | int i; |
| 270 | bool ready = false; |
| 271 | |
| 272 | for (i = 0; i < 10; i++) { |
| 273 | if (nvme_cqe_pending(sq->cq)) { |
| 274 | ready = true; |
| 275 | break; |
| 276 | } |
| 277 | |
| 278 | g_usleep(1000); |
| 279 | } |
| 280 | |
| 281 | g_assert(ready); |
| 282 | |
| 283 | return nvme_handle_cqe(sq); |
| 284 | } |
| 285 | |
| 286 | static uint64_t nvme_get_next_sqe(nvme_sq *sq, uint8_t opcode, |
| 287 | uint16_t cid, uint64_t prp1) |
| 288 | { |
| 289 | uint64_t phys_sqe = PHYS_ADDR_OF(NvmeCmd, sq->phys_sqe, [sq->tail]); |
| 290 | |
| 291 | if (((sq->tail + 1) % sq->common.size) == sq->head) { |
| 292 | /* no space in SQ */ |
| 293 | g_test_message("%s head %d tail %d", __func__, sq->head, sq->tail); |
| 294 | g_assert_not_reached(); |
| 295 | return 0; |
| 296 | } |
| 297 | |
| 298 | qtest_memset(sq->common.ctrl->pdev->bus->qts, |
| 299 | phys_sqe, 0, sizeof(NvmeCmd)); |
| 300 | |
| 301 | #define GUEST_MEM_WRITE(fn, phys_addr, val) \ |
| 302 | fn(sq->common.ctrl->pdev->bus->qts, phys_addr, (val)) |
| 303 | |
| 304 | GUEST_MEM_WRITE(qtest_writeb, |
| 305 | PHYS_ADDR_OF_FIELD(NvmeCmd, phys_sqe, opcode), opcode); |
| 306 | GUEST_MEM_WRITE(qtest_writew, |
| 307 | PHYS_ADDR_OF_FIELD(NvmeCmd, phys_sqe, cid), cid); |
| 308 | GUEST_MEM_WRITE(qtest_writeq, |
| 309 | PHYS_ADDR_OF_FIELD(NvmeCmd, phys_sqe, dptr.prp1), prp1); |
| 310 | |
| 311 | #undef GUEST_MEM_WRITE |
| 312 | |
| 313 | g_test_message("sq %p next_sqe %u sqe 0x%" PRIx64, sq, sq->tail, phys_sqe); |
| 314 | return phys_sqe; |
| 315 | } |
| 316 | |
| 317 | static void nvme_commit_sqe(nvme_sq *sq) |
| 318 | { |
| 319 | g_test_message("sq %p commit sqe tail %u", sq, sq->tail); |
| 320 | sq->tail = (sq->tail + 1) % sq->common.size; |
| 321 | qpci_io_writel(sq->common.ctrl->pdev, sq->common.ctrl->bar, |
| 322 | sq->common.doorbell, sq->tail); |
| 323 | } |
| 324 | |
| 325 | static uint64_t nvme_admin_identify_ctrl(nvme_ctrl *ctrl, |
| 326 | uint16_t cid, bool no_wait) |
| 327 | { |
| 328 | uint64_t phys_cmd_identify; /* NvmeCmd* */ |
| 329 | uint64_t phys_identify; /* NvmeIdCtrl* */ |
| 330 | NvmeCqe cqe; |
| 331 | |
| 332 | g_test_message("sending req cid %u no_wait %d", cid, no_wait); |
| 333 | |
| 334 | phys_identify = guest_alloc(ctrl->alloc, sizeof(NvmeIdCtrl)); |
| 335 | g_assert(phys_identify); |
| 336 | |
| 337 | phys_cmd_identify = nvme_get_next_sqe(&ctrl->admin_sq, |
| 338 | NVME_ADM_CMD_IDENTIFY, cid, |
| 339 | phys_identify); |
| 340 | g_assert(phys_cmd_identify); |
| 341 | |
| 342 | #define GUEST_MEM_WRITE(fn, phys_addr, val) \ |
| 343 | fn(ctrl->pdev->bus->qts, phys_addr, (val)) |
| 344 | |
| 345 | GUEST_MEM_WRITE(qtest_writel, |
| 346 | PHYS_ADDR_OF_FIELD(NvmeCmd, phys_cmd_identify, nsid), 0); |
| 347 | GUEST_MEM_WRITE(qtest_writel, |
| 348 | PHYS_ADDR_OF_FIELD(NvmeIdentify, phys_cmd_identify, cns), |
| 349 | NVME_ID_CNS_CTRL); |
| 350 | |
| 351 | #undef GUEST_MEM_WRITE |
| 352 | |
| 353 | nvme_commit_sqe(&ctrl->admin_sq); |
| 354 | |
| 355 | if (no_wait) { |
| 356 | return phys_identify; |
| 357 | } |
| 358 | |
| 359 | cqe = nvme_wait(&ctrl->admin_sq); |
| 360 | g_assert(nvme_is_cqe_success(&cqe)); |
| 361 | g_assert(le16_to_cpu(cqe.cid) == cid); |
| 362 | |
| 363 | return phys_identify; |
| 364 | } |
| 365 | |
| 366 | static void nvme_wait_ready(nvme_ctrl *ctrl, int val) |
| 367 | { |
| 368 | int i; |
| 369 | |
| 370 | for (i = 0; i < 10; i++) { |
| 371 | uint32_t csts = qpci_io_readl(ctrl->pdev, ctrl->bar, NVME_REG_CSTS); |
| 372 | g_test_message("%s: csts %x", __func__, csts); |
| 373 | |
| 374 | if (NVME_CSTS_RDY(csts) == val) { |
| 375 | return; |
| 376 | } |
| 377 | |
| 378 | g_usleep(1000); |
| 379 | } |
| 380 | |
| 381 | g_assert_not_reached(); |
| 382 | } |
| 383 | |
| 384 | static void test_migrate_setup_nvme_ctrl(nvme_ctrl *ctrl) |
| 385 | { |
| 386 | uint64_t cap; |
| 387 | |
| 388 | /* disable controller */ |
| 389 | qpci_io_writel(ctrl->pdev, ctrl->bar, NVME_REG_CC, 0); |
| 390 | nvme_wait_ready(ctrl, 0); |
| 391 | |
| 392 | cap = qpci_io_readq(ctrl->pdev, ctrl->bar, NVME_REG_CAP); |
| 393 | ctrl->db_stride = 4 << NVME_CAP_DSTRD(cap); |
| 394 | |
| 395 | nvme_init_cq(ctrl, &ctrl->admin_cq, 1, 2 /* CQEs num */); |
| 396 | nvme_init_sq(ctrl, &ctrl->admin_sq, 0, 4 /* SQEs num */, &ctrl->admin_cq); |
| 397 | |
| 398 | qpci_io_writel(ctrl->pdev, ctrl->bar, NVME_REG_AQA, |
| 399 | ((ctrl->admin_cq.common.size - 1) << AQA_ACQS_SHIFT) | |
| 400 | ((ctrl->admin_sq.common.size - 1) << AQA_ASQS_SHIFT) |
| 401 | ); |
| 402 | |
| 403 | qpci_io_writeq(ctrl->pdev, ctrl->bar, |
| 404 | NVME_REG_ASQ, (uint64_t)ctrl->admin_sq.phys_sqe); |
| 405 | qpci_io_writeq(ctrl->pdev, ctrl->bar, |
| 406 | NVME_REG_ACQ, (uint64_t)ctrl->admin_cq.phys_cqe); |
| 407 | |
| 408 | /* enable controller */ |
| 409 | { |
| 410 | uint32_t cc = 0; |
| 411 | NVME_SET_CC_EN(cc, 1); |
| 412 | qpci_io_writel(ctrl->pdev, ctrl->bar, NVME_REG_CC, cc); |
| 413 | } |
| 414 | |
| 415 | nvme_wait_ready(ctrl, 1); |
| 416 | } |
| 417 | |
| 418 | typedef struct test_migrate_req { |
| 419 | uint16_t cid; |
| 420 | bool handle_cqe; |
| 421 | uint64_t phys_identify; /* NvmeIdCtrl* */ |
| 422 | } test_migrate_req; |
| 423 | |
| 424 | static void test_migrate_send_nvme_reqs(nvme_ctrl *ctrl, test_migrate_req *reqs, |
| 425 | int num) |
| 426 | { |
| 427 | int i; |
| 428 | |
| 429 | for (i = 0; i < num; i++) { |
| 430 | reqs[i].phys_identify = nvme_admin_identify_ctrl(ctrl, reqs[i].cid, |
| 431 | !reqs[i].handle_cqe); |
| 432 | g_assert(reqs[i].phys_identify); |
| 433 | |
| 434 | if (reqs[i].handle_cqe) { |
| 435 | guest_free(ctrl->alloc, reqs[i].phys_identify); |
| 436 | } |
| 437 | } |
| 438 | } |
| 439 | |
| 440 | static void test_migrate_check_nvme(nvme_ctrl *ctrl, |
| 441 | test_migrate_req *reqs, int num) |
| 442 | { |
| 443 | int i; |
| 444 | |
| 445 | for (i = 0; i < num; i++) { |
| 446 | NvmeCqe cqe; |
| 447 | |
| 448 | if (reqs[i].handle_cqe) { |
| 449 | continue; |
| 450 | } |
| 451 | |
| 452 | cqe = nvme_wait(&ctrl->admin_sq); |
| 453 | g_assert(nvme_is_cqe_success(&cqe)); |
| 454 | |
| 455 | g_assert_cmpint(le16_to_cpu(cqe.cid), ==, reqs[i].cid); |
| 456 | |
| 457 | #define GUEST_MEM_READB(phys_addr) \ |
| 458 | qtest_readb(ctrl->pdev->bus->qts, (phys_addr)) |
| 459 | |
| 460 | g_assert_cmpint(GUEST_MEM_READB( |
| 461 | PHYS_ADDR_OF_FIELD(NvmeIdCtrl, reqs[i].phys_identify, ieee[0])), |
| 462 | ==, 0x0); |
| 463 | g_assert_cmpint(GUEST_MEM_READB( |
| 464 | PHYS_ADDR_OF_FIELD(NvmeIdCtrl, reqs[i].phys_identify, ieee[1])), |
| 465 | ==, 0x54); |
| 466 | g_assert_cmpint(GUEST_MEM_READB( |
| 467 | PHYS_ADDR_OF_FIELD(NvmeIdCtrl, reqs[i].phys_identify, ieee[2])), |
| 468 | ==, 0x52); |
| 469 | |
| 470 | #undef GUEST_MEM_READB |
| 471 | |
| 472 | guest_free(ctrl->alloc, reqs[i].phys_identify); |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | static void test_migrate(void *obj, void *data, QGuestAllocator *alloc) |
| 477 | { |
| 478 | g_autofree gchar *tmpfs = NULL; |
| 479 | GError *err = NULL; |
| 480 | g_autofree gchar *mig_path = NULL; |
| 481 | g_autofree gchar *uri = NULL; |
| 482 | GString *dest_cmdline; |
| 483 | QTestState *to; |
| 484 | QDict *rsp; |
| 485 | QNvme *nvme = obj; |
| 486 | QPCIDevice *pdev = &nvme->dev; |
| 487 | g_autofree nvme_ctrl *ctrl = NULL; |
| 488 | test_migrate_req test_reqs[] = { |
| 489 | { 123, true }, |
| 490 | { 456, false }, |
| 491 | { 300, false }, |
| 492 | { 333, false } |
| 493 | }; |
| 494 | |
| 495 | if (qpci_check_buggy_msi(pdev)) { |
| 496 | return; |
| 497 | } |
| 498 | |
| 499 | /* create temporary dir and prepare unix socket path for migration */ |
| 500 | tmpfs = g_dir_make_tmp("nvme-test-XXXXXX", &err); |
| 501 | if (!tmpfs) { |
| 502 | g_test_message("Can't create temporary directory in %s: %s", |
| 503 | g_get_tmp_dir(), err->message); |
| 504 | g_error_free(err); |
| 505 | } |
| 506 | g_assert(tmpfs); |
| 507 | |
| 508 | mig_path = g_strdup_printf("%s/socket.mig", tmpfs); |
| 509 | uri = g_strdup_printf("unix:%s", mig_path); |
| 510 | |
| 511 | /* enable NVMe PCI device */ |
| 512 | qpci_device_enable(pdev); |
| 513 | |
| 514 | ctrl = g_malloc0(sizeof(*ctrl)); |
| 515 | ctrl->alloc = alloc; |
| 516 | ctrl->pdev = pdev; |
| 517 | ctrl->bar = qpci_iomap(ctrl->pdev, 0, NULL); |
| 518 | g_assert(pdev->bus->qts == global_qtest); |
| 519 | |
| 520 | test_migrate_setup_nvme_ctrl(ctrl); |
| 521 | test_migrate_send_nvme_reqs(ctrl, test_reqs, ARRAY_SIZE(test_reqs)); |
| 522 | |
| 523 | qpci_iounmap(ctrl->pdev, ctrl->bar); |
| 524 | |
| 525 | dest_cmdline = g_string_new(qos_get_current_command_line()); |
| 526 | g_string_append_printf(dest_cmdline, " -incoming %s", uri); |
| 527 | |
| 528 | /* Create destination VM */ |
| 529 | to = qtest_init(dest_cmdline->str); |
| 530 | |
| 531 | /* Get access to PCI device from destination VM */ |
| 532 | nvme = qos_allocate_objects(to, &ctrl->alloc); |
| 533 | pdev = &nvme->dev; |
| 534 | ctrl->pdev = pdev; |
| 535 | ctrl->bar = qpci_iomap(ctrl->pdev, 0, NULL); |
| 536 | g_assert(pdev->bus->qts == to); |
| 537 | |
| 538 | /* Migrate VM */ |
| 539 | rsp = qmp("{ 'execute': 'migrate', 'arguments': { 'uri': %s } }", uri); |
| 540 | g_assert(qdict_haskey(rsp, "return")); |
| 541 | qobject_unref(rsp); |
| 542 | |
| 543 | /* Wait when source VM is stopped */ |
| 544 | qmp_eventwait("STOP"); |
| 545 | |
| 546 | /* Copy guest physical memory allocator state */ |
| 547 | migrate_allocator(alloc, ctrl->alloc); |
| 548 | |
| 549 | /* Wait for destination VM to become alive */ |
| 550 | qtest_qmp_eventwait(to, "RESUME"); |
| 551 | |
| 552 | test_migrate_check_nvme(ctrl, test_reqs, ARRAY_SIZE(test_reqs)); |
| 553 | |
| 554 | qpci_iounmap(ctrl->pdev, ctrl->bar); |
| 555 | |
| 556 | qtest_quit(to); |
| 557 | g_unlink(mig_path); |
| 558 | g_rmdir(tmpfs); |
| 559 | g_string_free(dest_cmdline, true); |
| 560 | } |
| 561 | |
| 562 | static void nvme_register_nodes(void) |
| 563 | { |
| 564 | QOSGraphEdgeOptions opts = { |
| 565 | .extra_device_opts = "addr=04.0,drive=drv0,serial=foo", |
| 566 | .before_cmd_line = "-drive id=drv0,if=none,file=null-co://," |
| 567 | "file.read-zeroes=on,format=raw " |
| 568 | "-object memory-backend-ram,id=pmr0," |
| 569 | "share=on,size=16", |
| 570 | }; |
| 571 | |
| 572 | add_qpci_address(&opts, &(QPCIAddress) { .devfn = QPCI_DEVFN(4, 0) }); |
| 573 | |
| 574 | qos_node_create_driver("nvme", nvme_create); |
| 575 | qos_node_consumes("nvme", "pci-bus", &opts); |
| 576 | qos_node_produces("nvme", "pci-device"); |
| 577 | |
| 578 | qos_add_test("oob-cmb-access", "nvme", nvmetest_oob_cmb_test, &(QOSGraphTestOptions) { |
| 579 | .edge.extra_device_opts = "cmb_size_mb=2" |
| 580 | }); |
| 581 | |
| 582 | qos_add_test("pmr-test-access", "nvme", nvmetest_pmr_reg_test, |
| 583 | &(QOSGraphTestOptions) { |
| 584 | .edge.extra_device_opts = "pmrdev=pmr0" |
| 585 | }); |
| 586 | |
| 587 | qos_add_test("reg-read", "nvme", nvmetest_reg_read_test, NULL); |
| 588 | |
| 589 | qos_add_test("migrate", "nvme", test_migrate, NULL); |
| 590 | } |
| 591 | |
| 592 | libqos_init(nvme_register_nodes); |