mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:56:38 +00:00
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:
@@ -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
|
||||
|
||||
@@ -37,6 +37,7 @@ tests_x86_64_system_thorough = [
|
||||
'linux_initrd',
|
||||
'multiprocess',
|
||||
'netdev_ethtool',
|
||||
'nvme_migration',
|
||||
'replay',
|
||||
'reverse_debug',
|
||||
'tuxrun',
|
||||
|
||||
172
tests/functional/x86_64/test_nvme_migration.py
Executable file
172
tests/functional/x86_64/test_nvme_migration.py
Executable 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()
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user