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);