@samitouri / QOSamiQemu / commits / e5830e0ec1

tests/qtest/nvme-test: add migration test with full CQ

As suggested by Stefan [1], let's add a migration test to cover rare scenario when CQ is full of non-processed CQEs and migration happens. To run this test: $ meson test -C build 'qtest-x86_64/qos-test' Link: https://lore.kernel.org/qemu-devel/20260408183529.GB319710@fedora/ [1] Suggested-by: Stefan Hajnoczi <stefanha@redhat.com> Acked-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io> Acked-by: Fabiano Rosas <farosas@suse.de> Signed-off-by: Klaus Jensen <k.jensen@samsung.com>

Alexander Mikhalitsyn committed Jun 11, 2026 at 20:08 UTC e5830e0ec19a864da3dd40f7b62cad95d3b4866a
1 file changed +419
tests/qtest/nvme-test.c
+419
@@ -8,9 +8,12 @@
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"
@@ -142,6 +145,420 @@ static void nvmetest_pmr_reg_test(void *obj, void *data, QGuestAllocator *alloc)
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 = {
@@ -168,6 +585,8 @@ static void nvme_register_nodes(void)
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);