@samitouri / QOSamiQemu / commits / 5320d335fb

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,