[PATCH 1/2] nvme: keep transport module referenced while head node is open
Nilay Shroff
nilay at linux.ibm.com
Mon Aug 31 08:19:54 PDT 2026
When a user opens an NVMe multipath head node, we take a reference to
the head node, but this does not prevent the transport module backing
its paths from being unloaded. This can result in the multipath head
remaining open while its underlying transport module is unloaded.
Fix this by taking a reference to the transport module for each active
path when the multipath head node is opened. Keep track of the number
of active head node openers so that dynamically added paths acquire the
same number of transport module references.
Similarly, when a path is removed while the head node is open, release
the transport module reference once for each active head node opener.
When the head node is closed, release the transport module reference
held for each remaining active path.
This ensures that a transport module cannot be unloaded while it is
still referenced by an open multipath head node.
Signed-off-by: Nilay Shroff <nilay at linux.ibm.com>
---
drivers/nvme/host/core.c | 6 ++++++
drivers/nvme/host/multipath.c | 37 +++++++++++++++++++++++++++++++++--
drivers/nvme/host/nvme.h | 32 ++++++++++++++++++++++++++++++
3 files changed, 73 insertions(+), 2 deletions(-)
diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index 1322c678f4eb..fdf760c57e02 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -4173,6 +4173,9 @@ static int nvme_init_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info)
list_add_tail_rcu(&ns->siblings, &head->list);
ns->head = head;
+ ret = nvme_module_get(ns, head->nr_openers);
+ if (ret)
+ goto out_err_module_get;
mutex_unlock(&ctrl->subsys->lock);
#ifdef CONFIG_NVME_MULTIPATH
@@ -4181,6 +4184,8 @@ static int nvme_init_ns_head(struct nvme_ns *ns, struct nvme_ns_info *info)
#endif
return 0;
+out_err_module_get:
+ list_del_rcu(&ns->siblings);
out_put_ns_head:
nvme_put_ns_head(head);
out_unlock:
@@ -4370,6 +4375,7 @@ static void nvme_ns_remove(struct nvme_ns *ns)
list_del_init(&ns->head->entry);
last_path = true;
}
+ nvme_module_put(ns, ns->head->nr_openers);
mutex_unlock(&ns->ctrl->subsys->lock);
/* guarantee not available in head->list */
diff --git a/drivers/nvme/host/multipath.c b/drivers/nvme/host/multipath.c
index 75dbb58286a3..822cd0d23e1d 100644
--- a/drivers/nvme/host/multipath.c
+++ b/drivers/nvme/host/multipath.c
@@ -579,14 +579,47 @@ static void nvme_ns_head_submit_bio(struct bio *bio)
static int nvme_ns_head_open(struct gendisk *disk, blk_mode_t mode)
{
- if (!nvme_tryget_ns_head(disk->private_data))
+ struct nvme_ns_head *head = disk->private_data;
+ struct nvme_subsystem *subsys = head->subsys;
+ struct nvme_ns *ns;
+ int ret;
+
+ if (!nvme_tryget_ns_head(head))
return -ENXIO;
+
+ mutex_lock(&subsys->lock);
+ list_for_each_entry(ns, &head->list, siblings) {
+ ret = nvme_module_get(ns, 1);
+ if (ret)
+ goto out_unwind;
+ }
+ head->nr_openers++;
+ mutex_unlock(&subsys->lock);
+
return 0;
+
+out_unwind:
+ list_for_each_entry_continue_reverse(ns, &head->list, siblings)
+ nvme_module_put(ns, 1);
+ mutex_unlock(&subsys->lock);
+
+ nvme_put_ns_head(head);
+ return ret;
}
static void nvme_ns_head_release(struct gendisk *disk)
{
- nvme_put_ns_head(disk->private_data);
+ struct nvme_ns_head *head = disk->private_data;
+ struct nvme_subsystem *subsys = head->subsys;
+ struct nvme_ns *ns;
+
+ mutex_lock(&subsys->lock);
+ list_for_each_entry(ns, &head->list, siblings)
+ nvme_module_put(ns, 1);
+ head->nr_openers--;
+ mutex_unlock(&subsys->lock);
+
+ nvme_put_ns_head(head);
}
static int nvme_ns_head_get_unique_id(struct gendisk *disk, u8 id[16],
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 75e5d5a8a77c..db08f4618f92 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -569,6 +569,8 @@ struct nvme_ns_head {
struct gendisk *disk;
+ unsigned int nr_openers;
+
u16 nr_plids;
u16 *plids;
#ifdef CONFIG_NVME_MULTIPATH
@@ -1109,6 +1111,28 @@ static inline bool nvme_mpath_queue_if_no_path(struct nvme_ns_head *head)
return true;
return false;
}
+
+static inline int nvme_module_get(struct nvme_ns *ns, unsigned int count)
+{
+ unsigned int i;
+
+ for (i = 0; i < count; i++) {
+ if (!try_module_get(ns->ctrl->ops->module))
+ goto out_unwind;
+ }
+
+ return 0;
+out_unwind:
+ while (i--)
+ module_put(ns->ctrl->ops->module);
+ return -ENXIO;
+}
+
+static inline void nvme_module_put(struct nvme_ns *ns, unsigned int count)
+{
+ while (count--)
+ module_put(ns->ctrl->ops->module);
+}
#else
#define multipath false
static inline bool nvme_ctrl_use_ana(struct nvme_ctrl *ctrl)
@@ -1199,6 +1223,14 @@ static inline bool nvme_disk_is_ns_head(struct gendisk *disk)
static inline bool nvme_mpath_queue_if_no_path(struct nvme_ns_head *head)
{
return false;
+}
+static inline int nvme_module_get(struct nvme_ns *ns, unsigned int count)
+{
+ return 0;
+}
+static inline void nvme_module_put(struct nvme_ns *ns, unsigned int count)
+{
+
}
#endif /* CONFIG_NVME_MULTIPATH */
--
2.53.0
More information about the Linux-nvme
mailing list