From: Alexander Mikhalitsyn <alexander@mihalicyn.com>
To: qemu-devel@nongnu.org
Cc: "Alexander Mikhalitsyn" <alexander@mihalicyn.com>,
"Peter Xu" <peterx@redhat.com>, "Fabiano Rosas" <farosas@suse.de>,
"Jesper Devantier" <foss@defmacro.it>,
"Klaus Jensen" <its@irrelevant.dk>,
"Stéphane Graber" <stgraber@stgraber.org>,
qemu-block@nongnu.org, "Stefan Hajnoczi" <stefanha@redhat.com>,
"Hanna Reitz" <hreitz@redhat.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Keith Busch" <kbusch@kernel.org>, "Fam Zheng" <fam@euphon.net>,
"Philippe Mathieu-Daudé" <philmd@linaro.org>,
"Zhao Liu" <zhao1.liu@intel.com>, "Kevin Wolf" <kwolf@redhat.com>,
"Alexander Mikhalitsyn" <aleksandr.mikhalitsyn@futurfusion.io>
Subject: [PATCH v5 5/8] hw/nvme: add migration blockers for non-supported cases
Date: Tue, 17 Mar 2026 11:27:05 +0100 [thread overview]
Message-ID: <20260317102708.126725-6-alexander@mihalicyn.com> (raw)
In-Reply-To: <20260317102708.126725-1-alexander@mihalicyn.com>
From: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Let's block migration for cases we don't support:
- SR-IOV
- CMB
- PMR
- SPDM
No functional changes here, because NVMe migration is
not supported at all as of this commit.
Signed-off-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
---
hw/nvme/ctrl.c | 206 +++++++++++++++++++++++++++++++++++++++++++
hw/nvme/nvme.h | 3 +
include/block/nvme.h | 12 +++
3 files changed, 221 insertions(+)
diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c
index cc4593cd427..9f9c9bcbead 100644
--- a/hw/nvme/ctrl.c
+++ b/hw/nvme/ctrl.c
@@ -207,6 +207,7 @@
#include "hw/pci/msix.h"
#include "hw/pci/pcie_sriov.h"
#include "system/spdm-socket.h"
+#include "migration/blocker.h"
#include "migration/vmstate.h"
#include "nvme.h"
@@ -250,6 +251,7 @@ static const bool nvme_feature_support[NVME_FID_MAX] = {
[NVME_COMMAND_SET_PROFILE] = true,
[NVME_FDP_MODE] = true,
[NVME_FDP_EVENTS] = true,
+ /* if you add something here, please update nvme_set_migration_blockers() */
};
static const uint32_t nvme_feature_cap[NVME_FID_MAX] = {
@@ -4601,6 +4603,7 @@ static uint16_t nvme_io_mgmt_send(NvmeCtrl *n, NvmeRequest *req)
return 0;
case NVME_IOMS_MO_RUH_UPDATE:
return nvme_io_mgmt_send_ruh_update(n, req);
+ /* if you add something here, please update nvme_set_migration_blockers() */
default:
return NVME_INVALID_FIELD | NVME_DNR;
};
@@ -7518,6 +7521,10 @@ static uint16_t nvme_security_receive(NvmeCtrl *n, NvmeRequest *req)
static uint16_t nvme_directive_send(NvmeCtrl *n, NvmeRequest *req)
{
+ /*
+ * When adding a new dtype handling here,
+ * please also update nvme_set_migration_blockers().
+ */
return NVME_INVALID_FIELD | NVME_DNR;
}
@@ -9208,6 +9215,199 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev)
}
}
+#define BLOCKER_FEATURES_MAX_LEN 256
+
+static inline void nvme_add_blocker_feature(char *blocker_features, const char *feature)
+{
+ if (strlen(blocker_features) > 0) {
+ g_strlcat(blocker_features, ", ", BLOCKER_FEATURES_MAX_LEN);
+ }
+ g_strlcat(blocker_features, feature, BLOCKER_FEATURES_MAX_LEN);
+}
+
+static bool nvme_set_migration_blockers(NvmeCtrl *n, PCIDevice *pci_dev, Error **errp)
+{
+ uint64_t unsupported_cap, cap = ldq_le_p(&n->bar.cap);
+ char blocker_features[BLOCKER_FEATURES_MAX_LEN] = "";
+ bool adm_cmd_security_checked = false;
+ bool cmd_io_mgmt_checked = false;
+ bool cmd_zone_checked = false;
+
+ /*
+ * Idea of this function is simple, we iterate over all Command Sets and
+ * for each supported command we provide a special handling logic to
+ * determine if we should block migration or not.
+ *
+ * For instance, we have NVME_ADM_CMD_NS_ATTACHMENT and it is always
+ * available to the guest, but if there is only 1 namespace, then it is
+ * safe to allow migration, but if there are more, then we need to block
+ * migration because we don't handle this in migration code yet.
+ */
+ for (int opcode = 0; opcode < sizeof(n->cse.acs) / sizeof(n->cse.acs[0]); opcode++) {
+ /* Is command supported? */
+ if (!n->cse.acs[opcode]) {
+ continue;
+ }
+
+ switch (opcode) {
+ case NVME_ADM_CMD_DELETE_SQ:
+ case NVME_ADM_CMD_CREATE_SQ:
+ case NVME_ADM_CMD_GET_LOG_PAGE:
+ case NVME_ADM_CMD_DELETE_CQ:
+ case NVME_ADM_CMD_CREATE_CQ:
+ case NVME_ADM_CMD_IDENTIFY:
+ case NVME_ADM_CMD_ABORT:
+ case NVME_ADM_CMD_SET_FEATURES:
+ case NVME_ADM_CMD_GET_FEATURES:
+ case NVME_ADM_CMD_ASYNC_EV_REQ:
+ case NVME_ADM_CMD_DBBUF_CONFIG:
+ case NVME_ADM_CMD_FORMAT_NVM:
+ case NVME_ADM_CMD_DIRECTIVE_SEND:
+ case NVME_ADM_CMD_DIRECTIVE_RECV:
+ break;
+ case NVME_ADM_CMD_NS_ATTACHMENT:
+ int namespaces_num = 0;
+ for (int i = 1; i <= NVME_MAX_NAMESPACES; i++) {
+ NvmeNamespace *ns = nvme_subsys_ns(n->subsys, i);
+ if (!ns) {
+ continue;
+ }
+
+ namespaces_num++;
+ }
+
+ if (namespaces_num > 1) {
+ nvme_add_blocker_feature(blocker_features, "Namespace Attachment");
+ }
+
+ break;
+ case NVME_ADM_CMD_VIRT_MNGMT:
+ if (n->params.sriov_max_vfs) {
+ nvme_add_blocker_feature(blocker_features, "SR-IOV");
+ }
+
+ break;
+ case NVME_ADM_CMD_SECURITY_SEND:
+ case NVME_ADM_CMD_SECURITY_RECV:
+ if (adm_cmd_security_checked) {
+ break;
+ }
+
+ if (pci_dev->spdm_port) {
+ nvme_add_blocker_feature(blocker_features, "SPDM");
+ }
+
+ adm_cmd_security_checked = true;
+
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ for (int opcode = 0; opcode < sizeof(n->cse.iocs.nvm) / sizeof(n->cse.iocs.nvm[0]); opcode++) {
+ if (!n->cse.iocs.nvm[opcode]) {
+ continue;
+ }
+
+ switch (opcode) {
+ case NVME_CMD_FLUSH:
+ case NVME_CMD_WRITE:
+ case NVME_CMD_READ:
+ case NVME_CMD_COMPARE:
+ case NVME_CMD_WRITE_ZEROES:
+ case NVME_CMD_DSM:
+ case NVME_CMD_VERIFY:
+ case NVME_CMD_COPY:
+ break;
+ case NVME_CMD_IO_MGMT_RECV:
+ case NVME_CMD_IO_MGMT_SEND:
+ if (cmd_io_mgmt_checked) {
+ break;
+ }
+
+ /* check for NVME_IOMS_MO_RUH_UPDATE */
+ if (n->subsys->params.fdp.enabled) {
+ nvme_add_blocker_feature(blocker_features, "FDP");
+ }
+
+ cmd_io_mgmt_checked = true;
+
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ for (int opcode = 0; opcode < sizeof(n->cse.iocs.zoned) / sizeof(n->cse.iocs.zoned[0]); opcode++) {
+ /*
+ * If command isn't supported or we have the same command
+ * in n->cse.iocs.nvm, then we can skip it here.
+ */
+ if (!n->cse.iocs.zoned[opcode] || n->cse.iocs.nvm[opcode]) {
+ continue;
+ }
+
+ switch (opcode) {
+ case NVME_CMD_ZONE_APPEND:
+ case NVME_CMD_ZONE_MGMT_SEND:
+ case NVME_CMD_ZONE_MGMT_RECV:
+ if (cmd_zone_checked) {
+ break;
+ }
+
+ for (int i = 1; i <= NVME_MAX_NAMESPACES; i++) {
+ NvmeNamespace *ns = nvme_subsys_ns(n->subsys, i);
+ if (!ns) {
+ continue;
+ }
+
+ if (ns->params.zoned) {
+ nvme_add_blocker_feature(blocker_features, "Zoned Namespace");
+ break;
+ }
+ }
+
+ cmd_zone_checked = true;
+
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ }
+
+ /*
+ * Try our best to explicitly detect all not supported caps,
+ * to let users know what features cause migration to be blocked,
+ * but in case we miss handling here, everything else will be
+ * covered by unsupported_cap check.
+ */
+ if (NVME_CAP_CMBS(cap)) {
+ nvme_add_blocker_feature(blocker_features, "CMB");
+ cap &= ~((uint64_t)CAP_CMBS_MASK << CAP_CMBS_SHIFT);
+ }
+
+ if (NVME_CAP_PMRS(cap)) {
+ nvme_add_blocker_feature(blocker_features, "PMR");
+ cap &= ~((uint64_t)CAP_PMRS_MASK << CAP_PMRS_SHIFT);
+ }
+
+ unsupported_cap = cap & ~NVME_MIGRATION_SUPPORTED_CAP_BITS;
+ if (unsupported_cap) {
+ nvme_add_blocker_feature(blocker_features, "unknown capability");
+ }
+
+ assert(n->migration_blocker == NULL);
+ if (strlen(blocker_features) > 0) {
+ error_setg(&n->migration_blocker, "Migration is not supported for %s", blocker_features);
+ if (migrate_add_blocker(&n->migration_blocker, errp) < 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
static int nvme_init_subsys(NvmeCtrl *n, Error **errp)
{
int cntlid;
@@ -9313,6 +9513,10 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
n->subsys->namespaces[ns->params.nsid] = ns;
}
+
+ if (!nvme_set_migration_blockers(n, pci_dev, errp)) {
+ return;
+ }
}
static void nvme_exit(PCIDevice *pci_dev)
@@ -9365,6 +9569,8 @@ static void nvme_exit(PCIDevice *pci_dev)
}
memory_region_del_subregion(&n->bar0, &n->iomem);
+
+ migrate_del_blocker(&n->migration_blocker);
}
static const Property nvme_props[] = {
diff --git a/hw/nvme/nvme.h b/hw/nvme/nvme.h
index d66f7dc82d5..457b6637249 100644
--- a/hw/nvme/nvme.h
+++ b/hw/nvme/nvme.h
@@ -666,6 +666,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 {
diff --git a/include/block/nvme.h b/include/block/nvme.h
index 9d7159ed7a7..a7f586fc801 100644
--- a/include/block/nvme.h
+++ b/include/block/nvme.h
@@ -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,
--
2.47.3
next prev parent reply other threads:[~2026-03-17 10:30 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-17 10:27 [PATCH v5 0/8] hw/nvme: add basic live migration support Alexander Mikhalitsyn
2026-03-17 10:27 ` [PATCH v5 1/8] migration/vmstate: export vmstate_{load, save}_field helpers Alexander Mikhalitsyn
2026-03-17 10:27 ` [PATCH v5 2/8] migration: add VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT{8, 32}_ALLOC Alexander Mikhalitsyn
2026-03-17 21:39 ` Peter Xu
2026-03-17 23:30 ` Peter Xu
2026-03-18 10:06 ` Alexander Mikhalitsyn
2026-03-18 10:05 ` Alexander Mikhalitsyn
2026-03-17 10:27 ` [PATCH v5 3/8] tests/unit/test-vmstate: add tests for VMS_ARRAY_OF_POINTER_ALLOW_NULL Alexander Mikhalitsyn
2026-03-17 10:27 ` [PATCH v5 4/8] tests/functional/migration: add VM launch/configure hooks Alexander Mikhalitsyn
2026-03-17 10:27 ` Alexander Mikhalitsyn [this message]
2026-04-07 15:34 ` [PATCH v5 5/8] hw/nvme: add migration blockers for non-supported cases Stefan Hajnoczi
2026-04-07 18:39 ` Alexander Mikhalitsyn
2026-04-08 6:32 ` Klaus Jensen
2026-04-08 11:47 ` Stefan Hajnoczi
2026-04-08 11:50 ` Alexander Mikhalitsyn
2026-03-17 10:27 ` [PATCH v5 6/8] hw/nvme: split nvme_init_sq/nvme_init_cq into helpers Alexander Mikhalitsyn
2026-03-17 10:27 ` [PATCH v5 7/8] hw/nvme: add basic live migration support Alexander Mikhalitsyn
2026-04-07 15:48 ` Stefan Hajnoczi
2026-04-07 19:02 ` Alexander Mikhalitsyn
2026-04-08 6:41 ` Klaus Jensen
2026-04-08 11:31 ` Alexander Mikhalitsyn
2026-04-08 18:35 ` Stefan Hajnoczi
2026-04-08 19:59 ` Alexander Mikhalitsyn
2026-04-08 18:27 ` Stefan Hajnoczi
2026-04-08 19:55 ` Alexander Mikhalitsyn
2026-04-09 13:36 ` Stefan Hajnoczi
2026-03-17 10:27 ` [PATCH v5 8/8] tests/functional/x86_64: add migration test for NVMe device Alexander Mikhalitsyn
2026-03-30 11:38 ` [PATCH v5 0/8] hw/nvme: add basic live migration support Alexander Mikhalitsyn
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260317102708.126725-6-alexander@mihalicyn.com \
--to=alexander@mihalicyn.com \
--cc=aleksandr.mikhalitsyn@futurfusion.io \
--cc=fam@euphon.net \
--cc=farosas@suse.de \
--cc=foss@defmacro.it \
--cc=hreitz@redhat.com \
--cc=its@irrelevant.dk \
--cc=kbusch@kernel.org \
--cc=kwolf@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peterx@redhat.com \
--cc=philmd@linaro.org \
--cc=qemu-block@nongnu.org \
--cc=qemu-devel@nongnu.org \
--cc=stefanha@redhat.com \
--cc=stgraber@stgraber.org \
--cc=zhao1.liu@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.