Add hotplug support for nvme-ns devices on the NvmeBus. This enables
namespace-level hot-swap without removing the NVMe controller, which
is how physical NVMe drives behave when hot-swapped in the same PCIe
slot.
Mark nvme-ns devices as hotpluggable and register the NvmeBus as a
hotplug handler with proper plug and unplug callbacks:
- plug: attach namespace to all started controllers and send an
Asynchronous Event Notification (AEN) with NS_ATTR_CHANGED so
the guest kernel rescans namespaces
- unplug: detach from all controllers, send AEN, remove from
subsystem, then unrealize the device
The plug handler skips controllers that haven't started yet
(qs_created == false) to avoid interfering with boot-time namespace
attachment in nvme_start_ctrl().
Both the controller bus and subsystem bus are configured as hotplug
handlers via qbus_set_bus_hotplug_handler() since nvme-ns devices
may reparent to the subsystem bus during realize.
Signed-off-by: Matthieu Receveur <matthieu@min.io>
---
hw/nvme/ctrl.c | 85 ++++++++++++++++++++++++++++++++++++++++++++++++
hw/nvme/ns.c | 1 +
hw/nvme/subsys.c | 2 ++
3 files changed, 88 insertions(+)
diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c
index be6c7028cb..5502e4ea2b 100644
--- a/hw/nvme/ctrl.c
+++ b/hw/nvme/ctrl.c
@@ -206,6 +206,7 @@
#include "system/hostmem.h"
#include "hw/pci/msix.h"
#include "hw/pci/pcie_sriov.h"
+#include "hw/core/qdev.h"
#include "system/spdm-socket.h"
#include "migration/vmstate.h"
@@ -9293,6 +9294,7 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
}
qbus_init(&n->bus, sizeof(NvmeBus), TYPE_NVME_BUS, dev, dev->id);
+ qbus_set_bus_hotplug_handler(BUS(&n->bus));
if (nvme_init_subsys(n, errp)) {
return;
@@ -9553,10 +9555,93 @@ static const TypeInfo nvme_info = {
},
};
+static void nvme_ns_hot_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ NvmeNamespace *ns = NVME_NS(dev);
+ NvmeSubsystem *subsys = ns->subsys;
+ uint32_t nsid = ns->params.nsid;
+ int i;
+
+ /*
+ * Attach to all started controllers and notify via AEN.
+ * Skip controllers that haven't started yet (boot-time realize) —
+ * nvme_start_ctrl() will attach namespaces during controller init.
+ */
+ for (i = 0; i < NVME_MAX_CONTROLLERS; i++) {
+ NvmeCtrl *ctrl = nvme_subsys_ctrl(subsys, i);
+ if (!ctrl || !ctrl->qs_created) {
+ continue;
+ }
+
+ if (nvme_csi_supported(ctrl, ns->csi) && !ns->params.detached) {
+ nvme_attach_ns(ctrl, ns);
+ nvme_update_dsm_limits(ctrl, ns);
+
+ if (!test_and_set_bit(nsid, ctrl->changed_nsids)) {
+ nvme_enqueue_event(ctrl, NVME_AER_TYPE_NOTICE,
+ NVME_AER_INFO_NOTICE_NS_ATTR_CHANGED,
+ NVME_LOG_CHANGED_NSLIST);
+ }
+ }
+ }
+}
+
+static void nvme_ns_hot_unplug(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ NvmeNamespace *ns = NVME_NS(dev);
+ NvmeSubsystem *subsys = ns->subsys;
+ uint32_t nsid = ns->params.nsid;
+ int i;
+
+ /*
+ * Detach from all controllers and notify the guest via AEN.
+ * Must happen before unrealize to avoid use-after-free when the
+ * guest sends I/O to a freed namespace.
+ */
+ for (i = 0; i < NVME_MAX_CONTROLLERS; i++) {
+ NvmeCtrl *ctrl = nvme_subsys_ctrl(subsys, i);
+ if (!ctrl || !nvme_ns(ctrl, nsid)) {
+ continue;
+ }
+
+ nvme_detach_ns(ctrl, ns);
+ nvme_update_dsm_limits(ctrl, NULL);
+
+ if (!test_and_set_bit(nsid, ctrl->changed_nsids)) {
+ nvme_enqueue_event(ctrl, NVME_AER_TYPE_NOTICE,
+ NVME_AER_INFO_NOTICE_NS_ATTR_CHANGED,
+ NVME_LOG_CHANGED_NSLIST);
+ }
+ }
+
+ /* Remove from subsystem namespace list. */
+ subsys->namespaces[nsid] = NULL;
+
+ /*
+ * Unrealize: drain I/O, flush, cleanup structures, remove from QOM.
+ * nvme_ns_unrealize() handles drain/shutdown/cleanup internally.
+ */
+ qdev_unrealize(dev);
+}
+
+static void nvme_bus_class_init(ObjectClass *klass, const void *data)
+{
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+ hc->plug = nvme_ns_hot_plug;
+ hc->unplug = nvme_ns_hot_unplug;
+}
+
static const TypeInfo nvme_bus_info = {
.name = TYPE_NVME_BUS,
.parent = TYPE_BUS,
.instance_size = sizeof(NvmeBus),
+ .class_init = nvme_bus_class_init,
+ .interfaces = (const InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { }
+ },
};
static void nvme_register_types(void)
diff --git a/hw/nvme/ns.c b/hw/nvme/ns.c
index b0106eaa5c..eb628c0734 100644
--- a/hw/nvme/ns.c
+++ b/hw/nvme/ns.c
@@ -937,6 +937,7 @@ 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;
device_class_set_props(dc, nvme_ns_props);
dc->desc = "Virtual NVMe namespace";
}
diff --git a/hw/nvme/subsys.c b/hw/nvme/subsys.c
index 777e1c620f..fa35055d3c 100644
--- a/hw/nvme/subsys.c
+++ b/hw/nvme/subsys.c
@@ -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);
}
--
2.50.1 (Apple Git-155)
© 2016 - 2026 Red Hat, Inc.