Merge tag 'pull-nvme-20260707' of https://gitlab.com/birkelund/qemu into staging

nvme queue

# -----BEGIN PGP SIGNATURE-----
#
# iQEzBAABCgAdFiEEUigzqnXi3OaiR2bATeGvMW1PDekFAmpML5AACgkQTeGvMW1P
# DekLeAf+JsOqQZ8rowk6ysUG4bp7Yy72vyIs05mOKrXsIEb1N3KpYAL10qs3Psl/
# /P37sBw51mbkgovpUgEV+J3kamCF/+8li1lgpsMV8HwbiO0QqQUMXPMGHWwA4CmF
# 3dkZWYP9xKjU/o9tdzSJ5F8hX372leu+z8FHslhK/XIlopRWeRaH/HxrEjZvKuIr
# ETPpVwffuAgM2hpi7/ekQhIxWz9nBvP/+fz57AHnj70O2Cl01JQm2PZrVX976C54
# o8j+Uz0DQup/lXunk83t1snJcYNNB/vbVPlUlF0nX/oQCiZ85SN8GqXblZPvELB9
# AzeYXmhxMWYrgf2eiqrqPl8xw1QvGg==
# =wZBE
# -----END PGP SIGNATURE-----
# gpg: Signature made Tue 07 Jul 2026 00:43:28 CEST
# gpg:                using RSA key 522833AA75E2DCE6A24766C04DE1AF316D4F0DE9
# gpg: Good signature from "Klaus Jensen <its@irrelevant.dk>" [unknown]
# gpg:                 aka "Klaus Jensen <k.jensen@samsung.com>" [unknown]
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: DDCA 4D9C 9EF9 31CC 3468  4272 63D5 6FC5 E55D A838
#      Subkey fingerprint: 5228 33AA 75E2 DCE6 A247  66C0 4DE1 AF31 6D4F 0DE9

* tag 'pull-nvme-20260707' of https://gitlab.com/birkelund/qemu:
  hw/nvme: add namespace hotplug support
  tests/qtest/nvme-test: add migration test with full CQ
  tests/functional/x86_64: add migration test for NVMe device
  hw/nvme: add basic live migration support
  hw/nvme: unmap req->sg earlier in nvme_enqueue_req_completion
  hw/nvme: set CQE.sq_id earlier in nvme_process_sq
  hw/nvme: split nvme_init_sq/nvme_init_cq into helpers
  hw/nvme: add migration blockers for non-supported cases
  tests/functional/migration: add VM launch/configure hooks
  hw/nvme: ensure sgl forward progress
  hw/nvme: fix FDP set FDP events

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi
2026-07-07 07:09:37 +02:00
11 changed files with 1948 additions and 43 deletions

View File

@@ -40,19 +40,36 @@ class MigrationTest(QemuSystemTest):
self.assertEqual(dst_vm.cmd('query-status')['status'], 'running')
self.assertEqual(src_vm.cmd('query-status')['status'],'postmigrate')
# Can be overridden by subclasses to configure both source/dest VMs.
def configure_machine(self, vm):
vm.add_args('-nodefaults')
# Can be overridden by subclasses to prepare the source VM before
# migration, e.g. by running some workload inside the source VM
# to see if it continues to run properly after migration.
def launch_source_vm(self, vm):
vm.launch()
# Can be overridden by subclasses to check the destination VM after
# migration, e.g. by checking if the workload is still running after
# migration.
def assert_dest_vm(self, vm):
pass
def migrate_vms(self, dst_uri, src_uri, dst_vm, src_vm):
dst_vm.qmp('migrate-incoming', uri=dst_uri)
src_vm.qmp('migrate', uri=src_uri)
self.assert_migration(src_vm, dst_vm)
self.assert_dest_vm(dst_vm)
def migrate(self, dst_uri, src_uri=None):
dst_vm = self.get_vm('-incoming', 'defer', name="dst-qemu")
dst_vm.add_args('-nodefaults')
self.configure_machine(dst_vm)
dst_vm.launch()
src_vm = self.get_vm(name="src-qemu")
src_vm.add_args('-nodefaults')
src_vm.launch()
self.configure_machine(src_vm)
self.launch_source_vm(src_vm)
if src_uri is None:
src_uri = dst_uri

View File

@@ -37,6 +37,7 @@ tests_x86_64_system_thorough = [
'linux_initrd',
'multiprocess',
'netdev_ethtool',
'nvme_migration',
'replay',
'reverse_debug',
'tuxrun',

View File

@@ -0,0 +1,172 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# x86_64 NVMe migration test
from migration import MigrationTest
from qemu_test import QemuSystemTest, Asset
from qemu_test import wait_for_console_pattern
from qemu_test import exec_command, exec_command_and_wait_for_pattern
class X8664NVMeMigrationTest(MigrationTest):
ASSET_KERNEL = Asset(
('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases'
'/31/Server/x86_64/os/images/pxeboot/vmlinuz'),
'd4738d03dbbe083ca610d0821d0a8f1488bebbdccef54ce33e3adb35fda00129')
ASSET_INITRD = Asset(
('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases'
'/31/Server/x86_64/os/images/pxeboot/initrd.img'),
'277cd6c7adf77c7e63d73bbb2cded8ef9e2d3a2f100000e92ff1f8396513cd8b')
ASSET_DISKIMAGE = Asset(
('https://archives.fedoraproject.org/pub/archive/fedora/linux/releases'
'/31/Cloud/x86_64/images/Fedora-Cloud-Base-31-1.9.x86_64.qcow2'),
'e3c1b309d9203604922d6e255c2c5d098a309c2d46215d8fc026954f3c5c27a0')
DEFAULT_KERNEL_PARAMS = ('root=/dev/nvme0n1p1 console=ttyS0 net.ifnames=0 '
'rd.rescue quiet')
def wait_for_console_pattern(self, success_message, vm):
wait_for_console_pattern(
self,
success_message,
failure_message="Kernel panic - not syncing",
vm=vm,
)
def exec_command_and_check(self, command, vm):
prompt = '# '
exec_command_and_wait_for_pattern(self,
f"{command} && echo OK || echo FAIL",
'FAIL', vm=vm)
# Note, that commands we send to the console are echo-ed back,
# so if we have a word "FAIL" in the command itself, we should
# expect to see it once.
wait_for_console_pattern(self, 'OK', failure_message="FAIL", vm=vm)
self.wait_for_console_pattern(prompt, vm)
def configure_machine(self, vm):
kernel_path = self.ASSET_KERNEL.fetch()
initrd_path = self.ASSET_INITRD.fetch()
diskimage_path = self.ASSET_DISKIMAGE.fetch()
vm.set_console()
vm.add_args("-cpu", "max")
vm.add_args("-m", "2G")
vm.add_args("-accel", "kvm")
vm.add_args('-drive',
f'file={diskimage_path},if=none,id=drv0,snapshot=on')
vm.add_args('-device', 'nvme,bus=pcie.0,' +
'drive=drv0,id=nvme-disk0,serial=nvmemigtest,bootindex=1')
vm.add_args(
"-kernel",
kernel_path,
"-initrd",
initrd_path,
"-append",
self.DEFAULT_KERNEL_PARAMS
)
def launch_source_vm(self, vm):
vm.launch()
self.wait_for_console_pattern('Entering emergency mode.', vm)
prompt = '# '
self.wait_for_console_pattern(prompt, vm)
# Synchronize on NVMe driver creating the root device
exec_command_and_wait_for_pattern(self,
"while ! (dmesg -c | grep nvme0n1:) ; do sleep 1 ; done",
"nvme0n1", vm=vm)
self.wait_for_console_pattern(prompt, vm)
# prepare system
exec_command_and_wait_for_pattern(self, 'mount /dev/nvme0n1p1 /sysroot',
prompt, vm=vm)
exec_command_and_wait_for_pattern(self, 'chroot /sysroot',
prompt, vm=vm)
exec_command_and_wait_for_pattern(self, 'mount -t proc proc /proc',
prompt, vm=vm)
exec_command_and_wait_for_pattern(self, 'mount -t sysfs sysfs /sys',
prompt, vm=vm)
# Run workload before migration to check if it continues
# to run properly after migration.
#
# Workload is simple: it continuously calculates checksums of
# all files in /usr/bin to generate some I/O load on
# the NVMe disk and at the same time it drops caches to
# make sure that we have some read I/O on the disk as well.
# If there are any issues with the migration of the NVMe device,
# we should see errors in dmesg and consequently in the workload log.
exec_command_and_wait_for_pattern(self,
"(while [ ! -f /tmp/test_nvme_mig_workload.stop ]; do \
rm -f /tmp/test_nvme_mig_workload.iter_finished; \
echo 3 > /proc/sys/vm/drop_caches; \
find /usr/bin -type f -exec cksum {} \\;; \
touch /tmp/test_nvme_mig_workload.iter_finished; \
done) > /dev/null 2> /tmp/test_nvme_mig_workload.errors &",
prompt, vm=vm)
exec_command_and_wait_for_pattern(self,
'echo $! > /tmp/test_nvme_mig_workload.pid',
prompt, vm=vm)
# check if process is alive and running
self.exec_command_and_check(
"kill -0 $(cat /tmp/test_nvme_mig_workload.pid)", vm)
def assert_dest_vm(self, vm):
prompt = '# '
# check if process is alive and running after migration,
# if not - fail the test
self.exec_command_and_check(
"kill -0 $(cat /tmp/test_nvme_mig_workload.pid)", vm)
# signal workload to stop
exec_command_and_wait_for_pattern(self,
'touch /tmp/test_nvme_mig_workload.stop',
prompt, vm=vm)
# wait workload to finish, because we want to examine log
# to see if there are any errors
exec_command_and_wait_for_pattern(self,
"while [ ! -f /tmp/test_nvme_mig_workload.iter_finished ]; do \
sleep 1; \
done;",
prompt, vm=vm)
exec_command_and_wait_for_pattern(self,
'cat /tmp/test_nvme_mig_workload.errors',
prompt, vm=vm)
# fail the test if non-empty
self.exec_command_and_check(
"[ ! -s /tmp/test_nvme_mig_workload.errors ]", vm)
def test_migration_with_tcp_localhost(self):
self.set_machine('q35')
self.require_accelerator("kvm")
self.migration_with_tcp_localhost()
def test_migration_with_unix(self):
self.set_machine('q35')
self.require_accelerator("kvm")
self.migration_with_unix()
def test_migration_with_exec(self):
self.set_machine('q35')
self.require_accelerator("kvm")
self.migration_with_exec()
if __name__ == '__main__':
MigrationTest.main()

View File

@@ -8,9 +8,12 @@
*/
#include "qemu/osdep.h"
#include <glib/gstdio.h>
#include "qemu/bswap.h"
#include "qemu/module.h"
#include "qemu/units.h"
#include "libqtest.h"
#include "libqtest-single.h"
#include "libqos/qgraph.h"
#include "libqos/pci.h"
#include "block/nvme.h"
@@ -142,6 +145,420 @@ static void nvmetest_pmr_reg_test(void *obj, void *data, QGuestAllocator *alloc)
qpci_iounmap(pdev, pmr_bar);
}
typedef struct nvme_ctrl nvme_ctrl;
typedef struct nvme_queue {
nvme_ctrl *ctrl;
uint64_t doorbell;
uint32_t size;
} nvme_queue;
typedef struct nvme_cq {
nvme_queue common;
uint64_t phys_cqe; /* NvmeCqe* */
uint16_t head;
uint8_t phase;
} nvme_cq;
typedef struct nvme_sq {
nvme_queue common;
uint64_t phys_sqe; /* NvmeCmd* */
nvme_cq *cq;
uint16_t head;
uint16_t tail;
} nvme_sq;
struct nvme_ctrl {
QGuestAllocator *alloc;
QPCIDevice *pdev;
QPCIBar bar;
uint32_t db_stride;
nvme_sq admin_sq;
nvme_cq admin_cq;
};
#define PHYS_ADDR_OF_FIELD(T, base_phys_addr, field) \
((uint64_t)&((T *)(base_phys_addr))->field)
#define PHYS_ADDR_OF(T, base_phys_addr, accessor) \
((uint64_t)&((T *)(base_phys_addr))accessor)
static void nvme_init_queue_common(nvme_ctrl *ctrl, nvme_queue *q,
uint16_t db_idx, uint32_t size)
{
q->ctrl = ctrl;
q->doorbell = (sizeof(NvmeBar) + db_idx * ctrl->db_stride);
g_test_message(" q %p db_idx %u doorbell 0x%" PRIx64, q, db_idx, q->doorbell);
q->size = size;
}
static void nvme_init_sq(nvme_ctrl *ctrl, nvme_sq *sq, uint16_t db_idx,
uint32_t size, nvme_cq *cq)
{
nvme_init_queue_common(ctrl, &sq->common, db_idx, size);
sq->phys_sqe = guest_alloc(ctrl->alloc, sizeof(NvmeCmd) * size);
g_assert(sq->phys_sqe);
g_test_message("sq %p db_idx %u sqe 0x%" PRIx64, sq, db_idx, sq->phys_sqe);
sq->cq = cq;
sq->head = 0;
sq->tail = 0;
}
static void nvme_init_cq(nvme_ctrl *ctrl, nvme_cq *cq, uint16_t db_idx,
uint32_t size)
{
nvme_init_queue_common(ctrl, &cq->common, db_idx, size);
cq->phys_cqe = guest_alloc(ctrl->alloc, sizeof(NvmeCqe) * size);
g_assert(cq->phys_cqe);
g_test_message("cq %p db_idx %u cqe 0x%" PRIx64, cq, db_idx, cq->phys_cqe);
cq->head = 0;
cq->phase = 1;
}
static int nvme_cqe_pending(nvme_cq *cq)
{
uint16_t status = qtest_readw(
cq->common.ctrl->pdev->bus->qts,
PHYS_ADDR_OF(NvmeCqe, cq->phys_cqe, [cq->head].status));
return (status & 1) == cq->phase;
}
static int nvme_is_cqe_success(NvmeCqe *cqe)
{
return (le16_to_cpu(cqe->status) >> 1) == NVME_SUCCESS;
}
static NvmeCqe nvme_handle_cqe(nvme_sq *sq)
{
nvme_cq *cq = sq->cq;
uint64_t phys_cqe = PHYS_ADDR_OF(
NvmeCqe, cq->phys_cqe, [cq->head]); /* NvmeCqe* */
NvmeCqe cqe;
uint16_t cq_next_head;
g_assert(nvme_cqe_pending(cq));
qtest_memread(sq->common.ctrl->pdev->bus->qts, phys_cqe, &cqe, sizeof(cqe));
cq_next_head = (cq->head + 1) % cq->common.size;
g_test_message("cq %p head %u -> %u", cq, cq->head, cq_next_head);
if (cq_next_head < cq->head) {
cq->phase ^= 1;
}
cq->head = cq_next_head;
if (cqe.sq_head != sq->head) {
sq->head = cqe.sq_head;
g_test_message("sq %p head = %u", sq, sq->head);
}
qpci_io_writel(cq->common.ctrl->pdev, cq->common.ctrl->bar,
cq->common.doorbell, cq->head);
return cqe;
}
static NvmeCqe nvme_wait(nvme_sq *sq)
{
int i;
bool ready = false;
for (i = 0; i < 10; i++) {
if (nvme_cqe_pending(sq->cq)) {
ready = true;
break;
}
g_usleep(1000);
}
g_assert(ready);
return nvme_handle_cqe(sq);
}
static uint64_t nvme_get_next_sqe(nvme_sq *sq, uint8_t opcode,
uint16_t cid, uint64_t prp1)
{
uint64_t phys_sqe = PHYS_ADDR_OF(NvmeCmd, sq->phys_sqe, [sq->tail]);
if (((sq->tail + 1) % sq->common.size) == sq->head) {
/* no space in SQ */
g_test_message("%s head %d tail %d", __func__, sq->head, sq->tail);
g_assert_not_reached();
return 0;
}
qtest_memset(sq->common.ctrl->pdev->bus->qts,
phys_sqe, 0, sizeof(NvmeCmd));
#define GUEST_MEM_WRITE(fn, phys_addr, val) \
fn(sq->common.ctrl->pdev->bus->qts, phys_addr, (val))
GUEST_MEM_WRITE(qtest_writeb,
PHYS_ADDR_OF_FIELD(NvmeCmd, phys_sqe, opcode), opcode);
GUEST_MEM_WRITE(qtest_writew,
PHYS_ADDR_OF_FIELD(NvmeCmd, phys_sqe, cid), cid);
GUEST_MEM_WRITE(qtest_writeq,
PHYS_ADDR_OF_FIELD(NvmeCmd, phys_sqe, dptr.prp1), prp1);
#undef GUEST_MEM_WRITE
g_test_message("sq %p next_sqe %u sqe 0x%" PRIx64, sq, sq->tail, phys_sqe);
return phys_sqe;
}
static void nvme_commit_sqe(nvme_sq *sq)
{
g_test_message("sq %p commit sqe tail %u", sq, sq->tail);
sq->tail = (sq->tail + 1) % sq->common.size;
qpci_io_writel(sq->common.ctrl->pdev, sq->common.ctrl->bar,
sq->common.doorbell, sq->tail);
}
static uint64_t nvme_admin_identify_ctrl(nvme_ctrl *ctrl,
uint16_t cid, bool no_wait)
{
uint64_t phys_cmd_identify; /* NvmeCmd* */
uint64_t phys_identify; /* NvmeIdCtrl* */
NvmeCqe cqe;
g_test_message("sending req cid %u no_wait %d", cid, no_wait);
phys_identify = guest_alloc(ctrl->alloc, sizeof(NvmeIdCtrl));
g_assert(phys_identify);
phys_cmd_identify = nvme_get_next_sqe(&ctrl->admin_sq,
NVME_ADM_CMD_IDENTIFY, cid,
phys_identify);
g_assert(phys_cmd_identify);
#define GUEST_MEM_WRITE(fn, phys_addr, val) \
fn(ctrl->pdev->bus->qts, phys_addr, (val))
GUEST_MEM_WRITE(qtest_writel,
PHYS_ADDR_OF_FIELD(NvmeCmd, phys_cmd_identify, nsid), 0);
GUEST_MEM_WRITE(qtest_writel,
PHYS_ADDR_OF_FIELD(NvmeIdentify, phys_cmd_identify, cns),
NVME_ID_CNS_CTRL);
#undef GUEST_MEM_WRITE
nvme_commit_sqe(&ctrl->admin_sq);
if (no_wait) {
return phys_identify;
}
cqe = nvme_wait(&ctrl->admin_sq);
g_assert(nvme_is_cqe_success(&cqe));
g_assert(le16_to_cpu(cqe.cid) == cid);
return phys_identify;
}
static void nvme_wait_ready(nvme_ctrl *ctrl, int val)
{
int i;
for (i = 0; i < 10; i++) {
uint32_t csts = qpci_io_readl(ctrl->pdev, ctrl->bar, NVME_REG_CSTS);
g_test_message("%s: csts %x", __func__, csts);
if (NVME_CSTS_RDY(csts) == val) {
return;
}
g_usleep(1000);
}
g_assert_not_reached();
}
static void test_migrate_setup_nvme_ctrl(nvme_ctrl *ctrl)
{
uint64_t cap;
/* disable controller */
qpci_io_writel(ctrl->pdev, ctrl->bar, NVME_REG_CC, 0);
nvme_wait_ready(ctrl, 0);
cap = qpci_io_readq(ctrl->pdev, ctrl->bar, NVME_REG_CAP);
ctrl->db_stride = 4 << NVME_CAP_DSTRD(cap);
nvme_init_cq(ctrl, &ctrl->admin_cq, 1, 2 /* CQEs num */);
nvme_init_sq(ctrl, &ctrl->admin_sq, 0, 4 /* SQEs num */, &ctrl->admin_cq);
qpci_io_writel(ctrl->pdev, ctrl->bar, NVME_REG_AQA,
((ctrl->admin_cq.common.size - 1) << AQA_ACQS_SHIFT) |
((ctrl->admin_sq.common.size - 1) << AQA_ASQS_SHIFT)
);
qpci_io_writeq(ctrl->pdev, ctrl->bar,
NVME_REG_ASQ, (uint64_t)ctrl->admin_sq.phys_sqe);
qpci_io_writeq(ctrl->pdev, ctrl->bar,
NVME_REG_ACQ, (uint64_t)ctrl->admin_cq.phys_cqe);
/* enable controller */
{
uint32_t cc = 0;
NVME_SET_CC_EN(cc, 1);
qpci_io_writel(ctrl->pdev, ctrl->bar, NVME_REG_CC, cc);
}
nvme_wait_ready(ctrl, 1);
}
typedef struct test_migrate_req {
uint16_t cid;
bool handle_cqe;
uint64_t phys_identify; /* NvmeIdCtrl* */
} test_migrate_req;
static void test_migrate_send_nvme_reqs(nvme_ctrl *ctrl, test_migrate_req *reqs,
int num)
{
int i;
for (i = 0; i < num; i++) {
reqs[i].phys_identify = nvme_admin_identify_ctrl(ctrl, reqs[i].cid,
!reqs[i].handle_cqe);
g_assert(reqs[i].phys_identify);
if (reqs[i].handle_cqe) {
guest_free(ctrl->alloc, reqs[i].phys_identify);
}
}
}
static void test_migrate_check_nvme(nvme_ctrl *ctrl,
test_migrate_req *reqs, int num)
{
int i;
for (i = 0; i < num; i++) {
NvmeCqe cqe;
if (reqs[i].handle_cqe) {
continue;
}
cqe = nvme_wait(&ctrl->admin_sq);
g_assert(nvme_is_cqe_success(&cqe));
g_assert_cmpint(le16_to_cpu(cqe.cid), ==, reqs[i].cid);
#define GUEST_MEM_READB(phys_addr) \
qtest_readb(ctrl->pdev->bus->qts, (phys_addr))
g_assert_cmpint(GUEST_MEM_READB(
PHYS_ADDR_OF_FIELD(NvmeIdCtrl, reqs[i].phys_identify, ieee[0])),
==, 0x0);
g_assert_cmpint(GUEST_MEM_READB(
PHYS_ADDR_OF_FIELD(NvmeIdCtrl, reqs[i].phys_identify, ieee[1])),
==, 0x54);
g_assert_cmpint(GUEST_MEM_READB(
PHYS_ADDR_OF_FIELD(NvmeIdCtrl, reqs[i].phys_identify, ieee[2])),
==, 0x52);
#undef GUEST_MEM_READB
guest_free(ctrl->alloc, reqs[i].phys_identify);
}
}
static void test_migrate(void *obj, void *data, QGuestAllocator *alloc)
{
g_autofree gchar *tmpfs = NULL;
GError *err = NULL;
g_autofree gchar *mig_path = NULL;
g_autofree gchar *uri = NULL;
GString *dest_cmdline;
QTestState *to;
QDict *rsp;
QNvme *nvme = obj;
QPCIDevice *pdev = &nvme->dev;
g_autofree nvme_ctrl *ctrl = NULL;
test_migrate_req test_reqs[] = {
{ 123, true },
{ 456, false },
{ 300, false },
{ 333, false }
};
if (qpci_check_buggy_msi(pdev)) {
return;
}
/* create temporary dir and prepare unix socket path for migration */
tmpfs = g_dir_make_tmp("nvme-test-XXXXXX", &err);
if (!tmpfs) {
g_test_message("Can't create temporary directory in %s: %s",
g_get_tmp_dir(), err->message);
g_error_free(err);
}
g_assert(tmpfs);
mig_path = g_strdup_printf("%s/socket.mig", tmpfs);
uri = g_strdup_printf("unix:%s", mig_path);
/* enable NVMe PCI device */
qpci_device_enable(pdev);
ctrl = g_malloc0(sizeof(*ctrl));
ctrl->alloc = alloc;
ctrl->pdev = pdev;
ctrl->bar = qpci_iomap(ctrl->pdev, 0, NULL);
g_assert(pdev->bus->qts == global_qtest);
test_migrate_setup_nvme_ctrl(ctrl);
test_migrate_send_nvme_reqs(ctrl, test_reqs, ARRAY_SIZE(test_reqs));
qpci_iounmap(ctrl->pdev, ctrl->bar);
dest_cmdline = g_string_new(qos_get_current_command_line());
g_string_append_printf(dest_cmdline, " -incoming %s", uri);
/* Create destination VM */
to = qtest_init(dest_cmdline->str);
/* Get access to PCI device from destination VM */
nvme = qos_allocate_objects(to, &ctrl->alloc);
pdev = &nvme->dev;
ctrl->pdev = pdev;
ctrl->bar = qpci_iomap(ctrl->pdev, 0, NULL);
g_assert(pdev->bus->qts == to);
/* Migrate VM */
rsp = qmp("{ 'execute': 'migrate', 'arguments': { 'uri': %s } }", uri);
g_assert(qdict_haskey(rsp, "return"));
qobject_unref(rsp);
/* Wait when source VM is stopped */
qmp_eventwait("STOP");
/* Copy guest physical memory allocator state */
migrate_allocator(alloc, ctrl->alloc);
/* Wait for destination VM to become alive */
qtest_qmp_eventwait(to, "RESUME");
test_migrate_check_nvme(ctrl, test_reqs, ARRAY_SIZE(test_reqs));
qpci_iounmap(ctrl->pdev, ctrl->bar);
qtest_quit(to);
g_unlink(mig_path);
g_rmdir(tmpfs);
g_string_free(dest_cmdline, true);
}
static void nvme_register_nodes(void)
{
QOSGraphEdgeOptions opts = {
@@ -168,6 +585,8 @@ static void nvme_register_nodes(void)
});
qos_add_test("reg-read", "nvme", nvmetest_reg_read_test, NULL);
qos_add_test("migrate", "nvme", test_migrate, NULL);
}
libqos_init(nvme_register_nodes);