hw/nvme: add migration blockers for non-supported cases
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. Reviewed-by: Klaus Jensen <k.jensen@samsung.com> Acked-by: Stefan Hajnoczi <stefanha@redhat.com> Signed-off-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io> Signed-off-by: Klaus Jensen <k.jensen@samsung.com>
Alexander Mikhalitsyn committed
Jun 11, 2026 at 20:08 UTC
5320d335fb8d254d3c2efa4aad9d112c52f4ba4c
3 files changed
+227
hw/nvme/ctrl.c
+212
@@ -209,6 +209,7 @@
209
#include "hw/pci/msix.h"
210
#include "hw/pci/pcie_sriov.h"
211
#include "system/spdm-socket.h"
212
+#include "migration/blocker.h"
213
#include "migration/vmstate.h"
214
215
#include "nvme.h"
@@ -252,6 +253,7 @@ static const bool nvme_feature_support[NVME_FID_MAX] = {
253
[NVME_COMMAND_SET_PROFILE] = true,
254
[NVME_FDP_MODE] = true,
255
[NVME_FDP_EVENTS] = true,
256
+ /* if you add something here, please update nvme_set_migration_blockers() */
257
};
258
259
static const uint32_t nvme_feature_cap[NVME_FID_MAX] = {
@@ -4616,6 +4618,7 @@ static uint16_t nvme_io_mgmt_send(NvmeCtrl *n, NvmeRequest *req)
4618
return 0;
4619
case NVME_IOMS_MO_RUH_UPDATE:
4620
return nvme_io_mgmt_send_ruh_update(n, req);
4621
+ /* if you add something here, please update nvme_set_migration_blockers() */
4622
default:
4623
return NVME_INVALID_FIELD | NVME_DNR;
4624
};
@@ -7545,6 +7548,10 @@ static uint16_t nvme_security_receive(NvmeCtrl *n, NvmeRequest *req)
7548
7549
static uint16_t nvme_directive_send(NvmeCtrl *n, NvmeRequest *req)
7550
{
7551
+ /*
7552
+ * When adding a new dtype handling here,
7553
+ * please also update nvme_set_migration_blockers().
7554
+ */
7555
return NVME_INVALID_FIELD | NVME_DNR;
7556
}
7557
@@ -9256,6 +9263,205 @@ static void nvme_init_ctrl(NvmeCtrl *n, PCIDevice *pci_dev)
9263
}
9264
}
9265
9266
+#define BLOCKER_FEATURES_MAX_LEN 256
9267
+
9268
+static inline void nvme_add_blocker_feature(char *blocker_features,
9269
+ const char *feature)
9270
+{
9271
+ if (strlen(blocker_features) > 0) {
9272
+ g_strlcat(blocker_features, ", ", BLOCKER_FEATURES_MAX_LEN);
9273
+ }
9274
+ g_strlcat(blocker_features, feature, BLOCKER_FEATURES_MAX_LEN);
9275
+}
9276
+
9277
+static bool nvme_set_migration_blockers(NvmeCtrl *n, PCIDevice *pci_dev,
9278
+ Error **errp)
9279
+{
9280
+ uint64_t unsupported_cap, cap = ldq_le_p(&n->bar.cap);
9281
+ char blocker_features[BLOCKER_FEATURES_MAX_LEN] = "";
9282
+ bool adm_cmd_security_checked = false;
9283
+ bool cmd_io_mgmt_checked = false;
9284
+ bool cmd_zone_checked = false;
9285
+
9286
+ /*
9287
+ * Idea of this function is simple, we iterate over all Command Sets and
9288
+ * for each supported command we provide a special handling logic to
9289
+ * determine if we should block migration or not.
9290
+ *
9291
+ * For instance, we have NVME_ADM_CMD_NS_ATTACHMENT and it is always
9292
+ * available to the guest, but if there is only 1 namespace, then it is
9293
+ * safe to allow migration, but if there are more, then we need to block
9294
+ * migration because we don't handle this in migration code yet.
9295
+ */
9296
+ for (int opcode = 0; opcode < ARRAY_SIZE(n->cse.acs); opcode++) {
9297
+ /* Is command supported? */
9298
+ if (!n->cse.acs[opcode]) {
9299
+ continue;
9300
+ }
9301
+
9302
+ switch (opcode) {
9303
+ case NVME_ADM_CMD_DELETE_SQ:
9304
+ case NVME_ADM_CMD_CREATE_SQ:
9305
+ case NVME_ADM_CMD_GET_LOG_PAGE:
9306
+ case NVME_ADM_CMD_DELETE_CQ:
9307
+ case NVME_ADM_CMD_CREATE_CQ:
9308
+ case NVME_ADM_CMD_IDENTIFY:
9309
+ case NVME_ADM_CMD_ABORT:
9310
+ case NVME_ADM_CMD_SET_FEATURES:
9311
+ case NVME_ADM_CMD_GET_FEATURES:
9312
+ case NVME_ADM_CMD_ASYNC_EV_REQ:
9313
+ case NVME_ADM_CMD_DBBUF_CONFIG:
9314
+ case NVME_ADM_CMD_FORMAT_NVM:
9315
+ case NVME_ADM_CMD_DIRECTIVE_SEND:
9316
+ case NVME_ADM_CMD_DIRECTIVE_RECV:
9317
+ break;
9318
+ case NVME_ADM_CMD_NS_ATTACHMENT: {
9319
+ int namespaces_num = 0;
9320
+ for (int i = 1; i <= NVME_MAX_NAMESPACES; i++) {
9321
+ NvmeNamespace *ns = nvme_subsys_ns(n->subsys, i);
9322
+ if (!ns) {
9323
+ continue;
9324
+ }
9325
+
9326
+ namespaces_num++;
9327
+ }
9328
+
9329
+ if (namespaces_num > 1) {
9330
+ nvme_add_blocker_feature(blocker_features,
9331
+ "Namespace Attachment");
9332
+ }
9333
+
9334
+ break;
9335
+ }
9336
+ case NVME_ADM_CMD_VIRT_MNGMT:
9337
+ if (n->params.sriov_max_vfs) {
9338
+ nvme_add_blocker_feature(blocker_features, "SR-IOV");
9339
+ }
9340
+
9341
+ break;
9342
+ case NVME_ADM_CMD_SECURITY_SEND:
9343
+ case NVME_ADM_CMD_SECURITY_RECV:
9344
+ if (adm_cmd_security_checked) {
9345
+ break;
9346
+ }
9347
+
9348
+ if (pci_dev->spdm_port) {
9349
+ nvme_add_blocker_feature(blocker_features, "SPDM");
9350
+ }
9351
+
9352
+ adm_cmd_security_checked = true;
9353
+
9354
+ break;
9355
+ default:
9356
+ g_assert_not_reached();
9357
+ }
9358
+ }
9359
+
9360
+ for (int opcode = 0; opcode < ARRAY_SIZE(n->cse.iocs.nvm); opcode++) {
9361
+ if (!n->cse.iocs.nvm[opcode]) {
9362
+ continue;
9363
+ }
9364
+
9365
+ switch (opcode) {
9366
+ case NVME_CMD_FLUSH:
9367
+ case NVME_CMD_WRITE:
9368
+ case NVME_CMD_READ:
9369
+ case NVME_CMD_COMPARE:
9370
+ case NVME_CMD_WRITE_ZEROES:
9371
+ case NVME_CMD_DSM:
9372
+ case NVME_CMD_VERIFY:
9373
+ case NVME_CMD_COPY:
9374
+ break;
9375
+ case NVME_CMD_IO_MGMT_RECV:
9376
+ case NVME_CMD_IO_MGMT_SEND:
9377
+ if (cmd_io_mgmt_checked) {
9378
+ break;
9379
+ }
9380
+
9381
+ /* check for NVME_IOMS_MO_RUH_UPDATE */
9382
+ if (n->subsys->params.fdp.enabled) {
9383
+ nvme_add_blocker_feature(blocker_features, "FDP");
9384
+ }
9385
+
9386
+ cmd_io_mgmt_checked = true;
9387
+
9388
+ break;
9389
+ default:
9390
+ g_assert_not_reached();
9391
+ }
9392
+ }
9393
+
9394
+ for (int opcode = 0; opcode < ARRAY_SIZE(n->cse.iocs.zoned); opcode++) {
9395
+ /*
9396
+ * If command isn't supported or we have the same command
9397
+ * in n->cse.iocs.nvm, then we can skip it here.
9398
+ */
9399
+ if (!n->cse.iocs.zoned[opcode] || n->cse.iocs.nvm[opcode]) {
9400
+ continue;
9401
+ }
9402
+
9403
+ switch (opcode) {
9404
+ case NVME_CMD_ZONE_APPEND:
9405
+ case NVME_CMD_ZONE_MGMT_SEND:
9406
+ case NVME_CMD_ZONE_MGMT_RECV:
9407
+ if (cmd_zone_checked) {
9408
+ break;
9409
+ }
9410
+
9411
+ for (int i = 1; i <= NVME_MAX_NAMESPACES; i++) {
9412
+ NvmeNamespace *ns = nvme_subsys_ns(n->subsys, i);
9413
+ if (!ns) {
9414
+ continue;
9415
+ }
9416
+
9417
+ if (ns->params.zoned) {
9418
+ nvme_add_blocker_feature(blocker_features,
9419
+ "Zoned Namespace");
9420
+ break;
9421
+ }
9422
+ }
9423
+
9424
+ cmd_zone_checked = true;
9425
+
9426
+ break;
9427
+ default:
9428
+ g_assert_not_reached();
9429
+ }
9430
+ }
9431
+
9432
+ /*
9433
+ * Try our best to explicitly detect all not supported caps,
9434
+ * to let users know what features cause migration to be blocked,
9435
+ * but in case we miss handling here, everything else will be
9436
+ * covered by unsupported_cap check.
9437
+ */
9438
+ if (NVME_CAP_CMBS(cap)) {
9439
+ nvme_add_blocker_feature(blocker_features, "CMB");
9440
+ cap &= ~((uint64_t)CAP_CMBS_MASK << CAP_CMBS_SHIFT);
9441
+ }
9442
+
9443
+ if (NVME_CAP_PMRS(cap)) {
9444
+ nvme_add_blocker_feature(blocker_features, "PMR");
9445
+ cap &= ~((uint64_t)CAP_PMRS_MASK << CAP_PMRS_SHIFT);
9446
+ }
9447
+
9448
+ unsupported_cap = cap & ~NVME_MIGRATION_SUPPORTED_CAP_BITS;
9449
+ if (unsupported_cap) {
9450
+ nvme_add_blocker_feature(blocker_features, "unknown capability");
9451
+ }
9452
+
9453
+ assert(n->migration_blocker == NULL);
9454
+ if (strlen(blocker_features) > 0) {
9455
+ error_setg(&n->migration_blocker,
9456
+ "Migration is not supported for %s", blocker_features);
9457
+ if (migrate_add_blocker(&n->migration_blocker, errp) < 0) {
9458
+ return false;
9459
+ }
9460
+ }
9461
+
9462
+ return true;
9463
+}
9464
+
9465
static int nvme_init_subsys(NvmeCtrl *n, Error **errp)
9466
{
9467
int cntlid;
@@ -9361,6 +9567,10 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
9567
9568
n->subsys->namespaces[ns->params.nsid] = ns;
9569
}
9570
+
9571
+ if (!nvme_set_migration_blockers(n, pci_dev, errp)) {
9572
+ return;
9573
+ }
9574
}
9575
9576
static void nvme_exit(PCIDevice *pci_dev)
@@ -9413,6 +9623,8 @@ static void nvme_exit(PCIDevice *pci_dev)
9623
}
9624
9625
memory_region_del_subregion(&n->bar0, &n->iomem);
9626
+
9627
+ migrate_del_blocker(&n->migration_blocker);
9628
}
9629
9630
static const Property nvme_props[] = {
hw/nvme/nvme.h
+3
@@ -675,6 +675,9 @@ typedef struct NvmeCtrl {
675
676
/* Socket mapping to SPDM over NVMe Security In/Out commands */
677
int spdm_socket;
678
+
679
+ /* Migration-related stuff */
680
+ Error *migration_blocker;
681
} NvmeCtrl;
682
683
typedef enum NvmeResetType {
include/block/nvme.h
+12
@@ -141,6 +141,18 @@ enum NvmeCapMask {
141
#define NVME_CAP_SET_CMBS(cap, val) \
142
((cap) |= (uint64_t)((val) & CAP_CMBS_MASK) << CAP_CMBS_SHIFT)
143
144
+#define NVME_MIGRATION_SUPPORTED_CAP_BITS ( \
145
+ ((uint64_t)CAP_MQES_MASK << CAP_MQES_SHIFT) \
146
+ | ((uint64_t)CAP_CQR_MASK << CAP_CQR_SHIFT) \
147
+ | ((uint64_t)CAP_AMS_MASK << CAP_AMS_SHIFT) \
148
+ | ((uint64_t)CAP_TO_MASK << CAP_TO_SHIFT) \
149
+ | ((uint64_t)CAP_DSTRD_MASK << CAP_DSTRD_SHIFT) \
150
+ | ((uint64_t)CAP_NSSRS_MASK << CAP_NSSRS_SHIFT) \
151
+ | ((uint64_t)CAP_CSS_MASK << CAP_CSS_SHIFT) \
152
+ | ((uint64_t)CAP_MPSMIN_MASK << CAP_MPSMIN_SHIFT) \
153
+ | ((uint64_t)CAP_MPSMAX_MASK << CAP_MPSMAX_SHIFT) \
154
+)
155
+
156
enum NvmeCapCss {
157
NVME_CAP_CSS_NCSS = 1 << 0,
158
NVME_CAP_CSS_IOCSS = 1 << 6,