@samitouri / QOSamiQemu / commits / cfb6c95706

hw/nvme: add namespace hotplug support

Add hotplug support for nvme-ns devices on the NvmeBus. This enables NVMe namespace-level hot-add and hot-remove via device_add and device_del with proper Asynchronous Event Notification (AEN), so the guest kernel can react to namespace topology changes. 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 and adds the block device - unplug: drain in-flight I/O, detach from all controllers, send AEN, then unrealize the device. The guest kernel rescans and removes the block 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(). The unplug handler drains in-flight I/O via nvme_ns_drain() before detaching the namespace from controllers, so pending requests can complete normally without touching freed state. For symmetry with nvme_ns_realize() which sets subsys->namespaces[nsid], nvme_ns_unrealize() now clears that slot too making the namespace lifecycle complete. 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. Example hot-swap sequence using the NVMe subsystem model: # Boot with: -device nvme-subsys,id=subsys0 # -device nvme,id=ctrl0,subsys=subsys0 # -device nvme-ns,id=ns0,drive=drv0,bus=ctrl0,nsid=1 device_del ns0 # guest receives AEN, removes /dev/nvme0n1 drive_del drv0 drive_add 0 file=disk.qcow2,format=qcow2,id=drv0,if=none device_add nvme-ns,id=ns0,drive=drv0,bus=ctrl0,nsid=1 # guest receives AEN, adds /dev/nvme0n1 Tested with Linux 6.1 guest (NVMe driver processes AEN and rescans namespace list automatically). Signed-off-by: Matthieu <matthieu@min.io> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Reviewed-by: Klaus Jensen <k.jensen@samsung.com> Signed-off-by: Klaus Jensen <k.jensen@samsung.com>

mr-083 committed Apr 15, 2026 at 19:38 UTC cfb6c95706998be9a6606d80e03f088c0f5b7b42
3 files changed +97
hw/nvme/ctrl.c
+87
@@ -9634,6 +9634,7 @@ static void nvme_realize(PCIDevice *pci_dev, Error **errp)
9634 }
9635
9636 qbus_init(&n->bus, sizeof(NvmeBus), TYPE_NVME_BUS, dev, dev->id);
9637 + qbus_set_bus_hotplug_handler(BUS(&n->bus));
9638
9639 if (nvme_init_subsys(n, errp)) {
9640 return;
@@ -10571,10 +10572,96 @@ static const TypeInfo nvme_info = {
10572 },
10573 };
10574
10575 +static void nvme_ns_hot_plug(HotplugHandler *hotplug_dev, DeviceState *dev,
10576 + Error **errp)
10577 +{
10578 + NvmeNamespace *ns = NVME_NS(dev);
10579 + NvmeSubsystem *subsys = ns->subsys;
10580 + uint32_t nsid = ns->params.nsid;
10581 + int i;
10582 +
10583 + /*
10584 + * Attach to all started controllers and notify via AEN.
10585 + * Skip controllers that haven't started yet (boot-time realize) —
10586 + * nvme_start_ctrl() will attach namespaces during controller init.
10587 + */
10588 + for (i = 0; i < NVME_MAX_CONTROLLERS; i++) {
10589 + NvmeCtrl *ctrl = nvme_subsys_ctrl(subsys, i);
10590 + if (!ctrl || !ctrl->qs_created) {
10591 + continue;
10592 + }
10593 +
10594 + if (nvme_csi_supported(ctrl, ns->csi) && !ns->params.detached) {
10595 + nvme_attach_ns(ctrl, ns);
10596 + nvme_update_dsm_limits(ctrl, ns);
10597 +
10598 + if (!test_and_set_bit(nsid, ctrl->changed_nsids)) {
10599 + nvme_enqueue_event(ctrl, NVME_AER_TYPE_NOTICE,
10600 + NVME_AER_INFO_NOTICE_NS_ATTR_CHANGED,
10601 + NVME_LOG_CHANGED_NSLIST);
10602 + }
10603 + }
10604 + }
10605 +}
10606 +
10607 +static void nvme_ns_hot_unplug(HotplugHandler *hotplug_dev, DeviceState *dev,
10608 + Error **errp)
10609 +{
10610 + NvmeNamespace *ns = NVME_NS(dev);
10611 + NvmeSubsystem *subsys = ns->subsys;
10612 + uint32_t nsid = ns->params.nsid;
10613 + int i;
10614 +
10615 + /*
10616 + * Drain in-flight I/O before tearing down the namespace.
10617 + * This must happen while the namespace is still attached to the
10618 + * controllers so any pending requests can complete normally.
10619 + */
10620 + nvme_ns_drain(ns);
10621 +
10622 + /*
10623 + * Detach from all controllers and notify the guest via AEN.
10624 + * The guest kernel will rescan namespaces and remove the block device.
10625 + */
10626 + for (i = 0; i < NVME_MAX_CONTROLLERS; i++) {
10627 + NvmeCtrl *ctrl = nvme_subsys_ctrl(subsys, i);
10628 + if (!ctrl || !nvme_ns(ctrl, nsid)) {
10629 + continue;
10630 + }
10631 +
10632 + nvme_detach_ns(ctrl, ns);
10633 + nvme_update_dsm_limits(ctrl, NULL);
10634 +
10635 + if (!test_and_set_bit(nsid, ctrl->changed_nsids)) {
10636 + nvme_enqueue_event(ctrl, NVME_AER_TYPE_NOTICE,
10637 + NVME_AER_INFO_NOTICE_NS_ATTR_CHANGED,
10638 + NVME_LOG_CHANGED_NSLIST);
10639 + }
10640 + }
10641 +
10642 + /*
10643 + * Unrealize: removes from subsystem (in nvme_ns_unrealize), flushes,
10644 + * cleans up structures, and removes from QOM.
10645 + */
10646 + qdev_unrealize(dev);
10647 +}
10648 +
10649 +static void nvme_bus_class_init(ObjectClass *klass, const void *data)
10650 +{
10651 + HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
10652 + hc->plug = nvme_ns_hot_plug;
10653 + hc->unplug = nvme_ns_hot_unplug;
10654 +}
10655 +
10656 static const TypeInfo nvme_bus_info = {
10657 .name = TYPE_NVME_BUS,
10658 .parent = TYPE_BUS,
10659 .instance_size = sizeof(NvmeBus),
10660 + .class_init = nvme_bus_class_init,
10661 + .interfaces = (const InterfaceInfo[]) {
10662 + { TYPE_HOTPLUG_HANDLER },
10663 + { }
10664 + },
10665 };
10666
10667 static void nvme_register_types(void)
hw/nvme/ns.c
+8
@@ -720,10 +720,17 @@ void nvme_ns_cleanup(NvmeNamespace *ns)
720 static void nvme_ns_unrealize(DeviceState *dev)
721 {
722 NvmeNamespace *ns = NVME_NS(dev);
723 + NvmeSubsystem *subsys = ns->subsys;
724 + uint32_t nsid = ns->params.nsid;
725
726 nvme_ns_drain(ns);
727 nvme_ns_shutdown(ns);
728 nvme_ns_cleanup(ns);
729 +
730 + /* Symmetric with nvme_ns_realize() which sets subsys->namespaces[nsid]. */
731 + if (subsys && nsid && subsys->namespaces[nsid] == ns) {
732 + subsys->namespaces[nsid] = NULL;
733 + }
734 }
735
736 void nvme_ns_atomic_configure_boundary(bool dn, uint16_t nabsn,
@@ -1100,6 +1107,7 @@ static void nvme_ns_class_init(ObjectClass *oc, const void *data)
1107 dc->bus_type = TYPE_NVME_BUS;
1108 dc->realize = nvme_ns_realize;
1109 dc->unrealize = nvme_ns_unrealize;
1110 + dc->hotpluggable = true;
1111 dc->vmsd = &nvme_vmstate_ns;
1112 device_class_set_props(dc, nvme_ns_props);
1113 dc->desc = "Virtual NVMe namespace";
hw/nvme/subsys.c
+2
@@ -9,6 +9,7 @@
9 #include "qemu/osdep.h"
10 #include "qemu/units.h"
11 #include "qapi/error.h"
12 +#include "hw/core/qdev.h"
13
14 #include "nvme.h"
15
@@ -205,6 +206,7 @@ static void nvme_subsys_realize(DeviceState *dev, Error **errp)
206 NvmeSubsystem *subsys = NVME_SUBSYS(dev);
207
208 qbus_init(&subsys->bus, sizeof(NvmeBus), TYPE_NVME_BUS, dev, dev->id);
209 + qbus_set_bus_hotplug_handler(BUS(&subsys->bus));
210
211 nvme_subsys_setup(subsys, errp);
212 }