mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:17 +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:
@@ -2680,6 +2680,7 @@ S: Supported
|
||||
F: hw/nvme/*
|
||||
F: include/block/nvme.h
|
||||
F: tests/qtest/nvme-test.c
|
||||
F: tests/functional/x86_64/test_nvme_migration.py
|
||||
F: docs/system/devices/nvme.rst
|
||||
T: git git://git.infradead.org/qemu-nvme.git nvme-next
|
||||
|
||||
|
||||
1158
hw/nvme/ctrl.c
1158
hw/nvme/ctrl.c
File diff suppressed because it is too large
Load Diff
172
hw/nvme/ns.c
172
hw/nvme/ns.c
@@ -20,6 +20,7 @@
|
||||
#include "qemu/bitops.h"
|
||||
#include "system/system.h"
|
||||
#include "system/block-backend.h"
|
||||
#include "migration/vmstate.h"
|
||||
|
||||
#include "nvme.h"
|
||||
#include "trace.h"
|
||||
@@ -719,10 +720,17 @@ void nvme_ns_cleanup(NvmeNamespace *ns)
|
||||
static void nvme_ns_unrealize(DeviceState *dev)
|
||||
{
|
||||
NvmeNamespace *ns = NVME_NS(dev);
|
||||
NvmeSubsystem *subsys = ns->subsys;
|
||||
uint32_t nsid = ns->params.nsid;
|
||||
|
||||
nvme_ns_drain(ns);
|
||||
nvme_ns_shutdown(ns);
|
||||
nvme_ns_cleanup(ns);
|
||||
|
||||
/* Symmetric with nvme_ns_realize() which sets subsys->namespaces[nsid]. */
|
||||
if (subsys && nsid && subsys->namespaces[nsid] == ns) {
|
||||
subsys->namespaces[nsid] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void nvme_ns_atomic_configure_boundary(bool dn, uint16_t nabsn,
|
||||
@@ -886,6 +894,168 @@ static void nvme_ns_realize(DeviceState *dev, Error **errp)
|
||||
}
|
||||
}
|
||||
|
||||
static const VMStateDescription nvme_vmstate_lbaf = {
|
||||
.name = "nvme_lbaf",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT16(ms, NvmeLBAF),
|
||||
VMSTATE_UINT8(ds, NvmeLBAF),
|
||||
VMSTATE_UINT8(rp, NvmeLBAF),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription nvme_vmstate_id_ns = {
|
||||
.name = "nvme_id_ns",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT64(nsze, NvmeIdNs),
|
||||
VMSTATE_UINT64(ncap, NvmeIdNs),
|
||||
VMSTATE_UINT64(nuse, NvmeIdNs),
|
||||
VMSTATE_UINT8(nsfeat, NvmeIdNs),
|
||||
VMSTATE_UINT8(nlbaf, NvmeIdNs),
|
||||
VMSTATE_UINT8(flbas, NvmeIdNs),
|
||||
VMSTATE_UINT8(mc, NvmeIdNs),
|
||||
VMSTATE_UINT8(dpc, NvmeIdNs),
|
||||
VMSTATE_UINT8(dps, NvmeIdNs),
|
||||
VMSTATE_UINT8(nmic, NvmeIdNs),
|
||||
VMSTATE_UINT8(rescap, NvmeIdNs),
|
||||
VMSTATE_UINT8(fpi, NvmeIdNs),
|
||||
VMSTATE_UINT8(dlfeat, NvmeIdNs),
|
||||
VMSTATE_UINT16(nawun, NvmeIdNs),
|
||||
VMSTATE_UINT16(nawupf, NvmeIdNs),
|
||||
VMSTATE_UINT16(nacwu, NvmeIdNs),
|
||||
VMSTATE_UINT16(nabsn, NvmeIdNs),
|
||||
VMSTATE_UINT16(nabo, NvmeIdNs),
|
||||
VMSTATE_UINT16(nabspf, NvmeIdNs),
|
||||
VMSTATE_UINT16(noiob, NvmeIdNs),
|
||||
VMSTATE_UINT8_ARRAY(nvmcap, NvmeIdNs, 16),
|
||||
VMSTATE_UINT16(npwg, NvmeIdNs),
|
||||
VMSTATE_UINT16(npwa, NvmeIdNs),
|
||||
VMSTATE_UINT16(npdg, NvmeIdNs),
|
||||
VMSTATE_UINT16(npda, NvmeIdNs),
|
||||
VMSTATE_UINT16(nows, NvmeIdNs),
|
||||
VMSTATE_UINT16(mssrl, NvmeIdNs),
|
||||
VMSTATE_UINT32(mcl, NvmeIdNs),
|
||||
VMSTATE_UINT8(msrc, NvmeIdNs),
|
||||
VMSTATE_UINT8_ARRAY(rsvd81, NvmeIdNs, 18),
|
||||
VMSTATE_UINT8(nsattr, NvmeIdNs),
|
||||
VMSTATE_UINT16(nvmsetid, NvmeIdNs),
|
||||
VMSTATE_UINT16(endgid, NvmeIdNs),
|
||||
VMSTATE_UINT8_ARRAY(nguid, NvmeIdNs, 16),
|
||||
VMSTATE_UINT64(eui64, NvmeIdNs),
|
||||
VMSTATE_STRUCT_ARRAY(lbaf, NvmeIdNs, NVME_MAX_NLBAF, 1,
|
||||
nvme_vmstate_lbaf, NvmeLBAF),
|
||||
VMSTATE_UINT8_ARRAY(vs, NvmeIdNs, 3712),
|
||||
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription nvme_vmstate_id_ns_nvm = {
|
||||
.name = "nvme_id_ns_nvm",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT64(lbstm, NvmeIdNsNvm),
|
||||
VMSTATE_UINT8(pic, NvmeIdNsNvm),
|
||||
VMSTATE_UINT8_ARRAY(rsvd9, NvmeIdNsNvm, 3),
|
||||
VMSTATE_UINT32_ARRAY(elbaf, NvmeIdNsNvm, NVME_MAX_NLBAF),
|
||||
VMSTATE_UINT32(npdgl, NvmeIdNsNvm),
|
||||
VMSTATE_UINT32(nprg, NvmeIdNsNvm),
|
||||
VMSTATE_UINT32(npra, NvmeIdNsNvm),
|
||||
VMSTATE_UINT32(nors, NvmeIdNsNvm),
|
||||
VMSTATE_UINT32(npdal, NvmeIdNsNvm),
|
||||
VMSTATE_UINT8_ARRAY(rsvd288, NvmeIdNsNvm, 3808),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const VMStateDescription nvme_vmstate_id_ns_ind = {
|
||||
.name = "nvme_id_ns_ind",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT8(nsfeat, NvmeIdNsInd),
|
||||
VMSTATE_UINT8(nmic, NvmeIdNsInd),
|
||||
VMSTATE_UINT8(rescap, NvmeIdNsInd),
|
||||
VMSTATE_UINT8(fpi, NvmeIdNsInd),
|
||||
VMSTATE_UINT32(anagrpid, NvmeIdNsInd),
|
||||
VMSTATE_UINT8(nsattr, NvmeIdNsInd),
|
||||
VMSTATE_UINT8(rsvd9, NvmeIdNsInd),
|
||||
VMSTATE_UINT16(nvmsetid, NvmeIdNsInd),
|
||||
VMSTATE_UINT16(endgrpid, NvmeIdNsInd),
|
||||
VMSTATE_UINT8(nstat, NvmeIdNsInd),
|
||||
VMSTATE_UINT8_ARRAY(rsvd15, NvmeIdNsInd, 4081),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
typedef struct TmpNvmeNamespace {
|
||||
NvmeNamespace *parent;
|
||||
bool enable_write_cache;
|
||||
} TmpNvmeNamespace;
|
||||
|
||||
static bool nvme_ns_tmp_pre_save(void *opaque, Error **errp)
|
||||
{
|
||||
struct TmpNvmeNamespace *tns = opaque;
|
||||
|
||||
tns->enable_write_cache = blk_enable_write_cache(tns->parent->blkconf.blk);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool nvme_ns_tmp_post_load(void *opaque, int version_id, Error **errp)
|
||||
{
|
||||
struct TmpNvmeNamespace *tns = opaque;
|
||||
|
||||
blk_set_enable_write_cache(tns->parent->blkconf.blk,
|
||||
tns->enable_write_cache);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const VMStateDescription nvme_vmstate_ns_tmp = {
|
||||
.name = "nvme_ns_tmp",
|
||||
.pre_save_errp = nvme_ns_tmp_pre_save,
|
||||
.post_load_errp = nvme_ns_tmp_post_load,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_BOOL(enable_write_cache, TmpNvmeNamespace),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
const VMStateDescription nvme_vmstate_ns = {
|
||||
.name = "nvme_ns",
|
||||
.version_id = 1,
|
||||
.minimum_version_id = 1,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_WITH_TMP(NvmeNamespace, TmpNvmeNamespace, nvme_vmstate_ns_tmp),
|
||||
|
||||
VMSTATE_STRUCT(id_ns, NvmeNamespace, 0, nvme_vmstate_id_ns, NvmeIdNs),
|
||||
VMSTATE_STRUCT(id_ns_nvm, NvmeNamespace, 0,
|
||||
nvme_vmstate_id_ns_nvm, NvmeIdNsNvm),
|
||||
VMSTATE_STRUCT(id_ns_ind, NvmeNamespace, 0,
|
||||
nvme_vmstate_id_ns_ind, NvmeIdNsInd),
|
||||
VMSTATE_STRUCT(lbaf, NvmeNamespace, 0, nvme_vmstate_lbaf, NvmeLBAF),
|
||||
VMSTATE_UINT32(nlbaf, NvmeNamespace),
|
||||
VMSTATE_UINT8(csi, NvmeNamespace),
|
||||
VMSTATE_UINT16(status, NvmeNamespace),
|
||||
VMSTATE_UINT8(pif, NvmeNamespace),
|
||||
|
||||
VMSTATE_UINT16(zns.zrwas, NvmeNamespace),
|
||||
VMSTATE_UINT16(zns.zrwafg, NvmeNamespace),
|
||||
VMSTATE_UINT32(zns.numzrwa, NvmeNamespace),
|
||||
|
||||
VMSTATE_UINT32(features.err_rec, NvmeNamespace),
|
||||
VMSTATE_STRUCT(atomic, NvmeNamespace, 0,
|
||||
nvme_vmstate_atomic, NvmeAtomic),
|
||||
VMSTATE_END_OF_LIST()
|
||||
}
|
||||
};
|
||||
|
||||
static const Property nvme_ns_props[] = {
|
||||
DEFINE_BLOCK_PROPERTIES(NvmeNamespace, blkconf),
|
||||
DEFINE_PROP_BOOL("detached", NvmeNamespace, params.detached, false),
|
||||
@@ -937,6 +1107,8 @@ static void nvme_ns_class_init(ObjectClass *oc, const void *data)
|
||||
dc->bus_type = TYPE_NVME_BUS;
|
||||
dc->realize = nvme_ns_realize;
|
||||
dc->unrealize = nvme_ns_unrealize;
|
||||
dc->hotpluggable = true;
|
||||
dc->vmsd = &nvme_vmstate_ns;
|
||||
device_class_set_props(dc, nvme_ns_props);
|
||||
dc->desc = "Virtual NVMe namespace";
|
||||
}
|
||||
|
||||
@@ -160,7 +160,14 @@ typedef struct NvmeZone {
|
||||
#define NVME_FDP_MAX_NS_RUHS 32u
|
||||
#define FDPVSS 0
|
||||
|
||||
static const uint8_t nvme_fdp_evf_shifts[FDP_EVT_MAX] = {
|
||||
/*
|
||||
* NOTE: Apart from event type 0, any event type with a shift value of 0 is
|
||||
* considered unsupported and thus skipped in get/set features calls.
|
||||
*
|
||||
* NOTE: NvmeRuHandle uses a 64bit event mask - refactor to support event types
|
||||
* of 63 or greater.
|
||||
*/
|
||||
static const uint8_t nvme_fdp_evf_shifts[FDP_EVT_MAX + 1] = {
|
||||
/* Host events */
|
||||
[FDP_EVT_RU_NOT_FULLY_WRITTEN] = 0,
|
||||
[FDP_EVT_RU_ATL_EXCEEDED] = 1,
|
||||
@@ -444,6 +451,11 @@ typedef struct NvmeRequest {
|
||||
NvmeSg sg;
|
||||
bool atomic_write;
|
||||
QTAILQ_ENTRY(NvmeRequest)entry;
|
||||
/*
|
||||
* If you add a new field here, please make sure to update
|
||||
* nvme_vmstate_request, pre_save_validate_aer_req() and
|
||||
* pre_save_validate_cq_req().
|
||||
*/
|
||||
} NvmeRequest;
|
||||
|
||||
typedef struct NvmeBounceContext {
|
||||
@@ -640,6 +652,7 @@ typedef struct NvmeCtrl {
|
||||
|
||||
NvmeNamespace namespace;
|
||||
NvmeNamespace *namespaces[NVME_MAX_NAMESPACES + 1];
|
||||
uint32_t num_queues;
|
||||
NvmeSQueue **sq;
|
||||
NvmeCQueue **cq;
|
||||
NvmeSQueue admin_sq;
|
||||
@@ -668,6 +681,9 @@ typedef struct NvmeCtrl {
|
||||
|
||||
/* Socket mapping to SPDM over NVMe Security In/Out commands */
|
||||
int spdm_socket;
|
||||
|
||||
/* Migration-related stuff */
|
||||
Error *migration_blocker;
|
||||
} NvmeCtrl;
|
||||
|
||||
typedef enum NvmeResetType {
|
||||
@@ -748,4 +764,7 @@ void nvme_atomic_configure_max_write_size(bool dn, uint16_t awun,
|
||||
void nvme_ns_atomic_configure_boundary(bool dn, uint16_t nabsn,
|
||||
uint16_t nabspf, NvmeAtomic *atomic);
|
||||
|
||||
extern const VMStateDescription nvme_vmstate_atomic;
|
||||
extern const VMStateDescription nvme_vmstate_ns;
|
||||
|
||||
#endif /* HW_NVME_NVME_H */
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/units.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/core/qdev.h"
|
||||
|
||||
#include "nvme.h"
|
||||
|
||||
@@ -205,6 +206,7 @@ static void nvme_subsys_realize(DeviceState *dev, Error **errp)
|
||||
NvmeSubsystem *subsys = NVME_SUBSYS(dev);
|
||||
|
||||
qbus_init(&subsys->bus, sizeof(NvmeBus), TYPE_NVME_BUS, dev, dev->id);
|
||||
qbus_set_bus_hotplug_handler(BUS(&subsys->bus));
|
||||
|
||||
nvme_subsys_setup(subsys, errp);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,16 @@ pci_nvme_dbbuf_config(uint64_t dbs_addr, uint64_t eis_addr) "dbs_addr=0x%"PRIx64
|
||||
pci_nvme_map_addr(uint64_t addr, uint64_t len) "addr 0x%"PRIx64" len %"PRIu64""
|
||||
pci_nvme_map_addr_cmb(uint64_t addr, uint64_t len) "addr 0x%"PRIx64" len %"PRIu64""
|
||||
pci_nvme_map_prp(uint64_t trans_len, uint32_t len, uint64_t prp1, uint64_t prp2, int num_prps) "trans_len %"PRIu64" len %"PRIu32" prp1 0x%"PRIx64" prp2 0x%"PRIx64" num_prps %d"
|
||||
pci_nvme_pre_save_enter(void *n) "n=%p"
|
||||
pci_nvme_pre_save_ns_drain(void *n, int i) "n=%p i=%d"
|
||||
pci_nvme_pre_save_sq_out_req_check(void *n, int i, uint32_t head, uint32_t tail, uint32_t size) "n=%p i=%d head=0x%"PRIx32" tail=0x%"PRIx32" size=0x%"PRIx32""
|
||||
pci_nvme_pre_save_cq_req_check(void *n, int i, uint32_t head, uint32_t tail, uint32_t size) "n=%p i=%d head=0x%"PRIx32" tail=0x%"PRIx32" size=0x%"PRIx32""
|
||||
pci_nvme_pre_save_cq_unposted_cqe(void *n, int i, uint16_t cid, uint32_t nsid, uint32_t dw0, uint32_t dw1, uint16_t status, uint8_t opc) "n=%p i=%d cid %"PRIu16" nsid %"PRIu32" dw0 0x%"PRIx32" dw1 0x%"PRIx32" status 0x%"PRIx16" opc 0x%"PRIx8""
|
||||
pci_nvme_pre_save_aer(uint8_t typ, uint8_t info, uint8_t log_page) "type 0x%"PRIx8" info 0x%"PRIx8" lid 0x%"PRIx8""
|
||||
pci_nvme_post_load_enter(void *n) "n=%p"
|
||||
pci_nvme_post_load_restore_cq(void *n, int i, uint32_t head, uint32_t tail, uint32_t size) "n=%p i=%d head=0x%"PRIx32" tail=0x%"PRIx32" size=0x%"PRIx32""
|
||||
pci_nvme_post_load_restore_sq(void *n, int i, uint32_t head, uint32_t tail, uint32_t size) "n=%p i=%d head=0x%"PRIx32" tail=0x%"PRIx32" size=0x%"PRIx32""
|
||||
pci_nvme_post_load_aer(uint8_t typ, uint8_t info, uint8_t log_page) "type 0x%"PRIx8" info 0x%"PRIx8" lid 0x%"PRIx8""
|
||||
pci_nvme_map_sgl(uint8_t typ, uint64_t len) "type 0x%"PRIx8" len %"PRIu64""
|
||||
pci_nvme_io_cmd(uint16_t cid, uint32_t nsid, uint16_t sqid, uint8_t opcode, const char *opname) "cid %"PRIu16" nsid 0x%"PRIx32" sqid %"PRIu16" opc 0x%"PRIx8" opname '%s'"
|
||||
pci_nvme_admin_cmd(uint16_t cid, uint16_t sqid, uint8_t opcode, const char *opname) "cid %"PRIu16" sqid %"PRIu16" opc 0x%"PRIx8" opname '%s'"
|
||||
|
||||
@@ -141,6 +141,18 @@ enum NvmeCapMask {
|
||||
#define NVME_CAP_SET_CMBS(cap, val) \
|
||||
((cap) |= (uint64_t)((val) & CAP_CMBS_MASK) << CAP_CMBS_SHIFT)
|
||||
|
||||
#define NVME_MIGRATION_SUPPORTED_CAP_BITS ( \
|
||||
((uint64_t)CAP_MQES_MASK << CAP_MQES_SHIFT) \
|
||||
| ((uint64_t)CAP_CQR_MASK << CAP_CQR_SHIFT) \
|
||||
| ((uint64_t)CAP_AMS_MASK << CAP_AMS_SHIFT) \
|
||||
| ((uint64_t)CAP_TO_MASK << CAP_TO_SHIFT) \
|
||||
| ((uint64_t)CAP_DSTRD_MASK << CAP_DSTRD_SHIFT) \
|
||||
| ((uint64_t)CAP_NSSRS_MASK << CAP_NSSRS_SHIFT) \
|
||||
| ((uint64_t)CAP_CSS_MASK << CAP_CSS_SHIFT) \
|
||||
| ((uint64_t)CAP_MPSMIN_MASK << CAP_MPSMIN_SHIFT) \
|
||||
| ((uint64_t)CAP_MPSMAX_MASK << CAP_MPSMAX_SHIFT) \
|
||||
)
|
||||
|
||||
enum NvmeCapCss {
|
||||
NVME_CAP_CSS_NCSS = 1 << 0,
|
||||
NVME_CAP_CSS_IOCSS = 1 << 6,
|
||||
|
||||
@@ -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