[PATCH 05/13] libmultipath: Add support for mpath_device management

From: John Garry

Date: Wed Feb 25 2026 - 10:34:38 EST


Add support to add or remove a mpath_device as a path.

NVMe has almost like-for-like equivalents here:
- nvme_mpath_clear_current_path() -> mpath_clear_current_path()
- nvme_mpath_add_sysfs_link() -> mpath_add_sysfs_link()
- nvme_mpath_remove_sysfs_link() -> mpath_remove_sysfs_link()
- nvme_mpath_revalidate_paths() -> mpath_revalidate_paths()

mpath_revalidate_paths() has a CB arg for NVMe specific handling.

The functionality in mpath_clear_paths() and mpath_synchronize() have the
same pattern which is frequently used in the NVMe code.

Helper mpath_call_for_device() is added to allow a driver run a callback
on any path available. It is intended to be used for occasions when the
NVMe drivers accesses the list of paths outside its multipath code, like
NVMe sysfs.c

Signed-off-by: John Garry <john.g.garry@xxxxxxxxxx>
---
include/linux/multipath.h | 18 ++++
lib/multipath.c | 169 +++++++++++++++++++++++++++++++++++++-
2 files changed, 186 insertions(+), 1 deletion(-)

diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index d557fb9bab4c9..4255b73de56b2 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -33,10 +33,13 @@ struct mpath_disk {
struct device *parent;
};

+#define MPATH_DEVICE_SYSFS_ATTR_LINK 0
+
struct mpath_device {
struct list_head siblings;
atomic_t nr_active;
struct gendisk *disk;
+ unsigned long flags;
int numa_node;
};

@@ -94,6 +97,21 @@ static inline enum mpath_iopolicy_e mpath_read_iopolicy(
void mpath_synchronize(struct mpath_head *mpath_head);
int mpath_set_iopolicy(const char *val, int *iopolicy);
int mpath_get_iopolicy(char *buf, int iopolicy);
+bool mpath_clear_current_path(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device);
+void mpath_synchronize(struct mpath_head *mpath_head);
+void mpath_add_device(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device);
+void mpath_delete_device(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device);
+int mpath_call_for_device(struct mpath_head *mpath_head,
+ int (*cb)(struct mpath_device *mpath_device));
+void mpath_clear_paths(struct mpath_head *mpath_head);
+void mpath_revalidate_paths(struct mpath_disk *mpath_disk,
+ void (*cb)(struct mpath_device *mpath_device, sector_t capacity));
+void mpath_add_sysfs_link(struct mpath_disk *mpath_disk);
+void mpath_remove_sysfs_link(struct mpath_disk *mpath_disk,
+ struct mpath_device *mpath_device);
int mpath_get_head(struct mpath_head *mpath_head);
void mpath_put_head(struct mpath_head *mpath_head);
void mpath_requeue_work(struct work_struct *work);
diff --git a/lib/multipath.c b/lib/multipath.c
index b494b35e8dccc..7f3b0cccf053b 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -40,13 +40,108 @@ int mpath_get_iopolicy(char *buf, int iopolicy)
}
EXPORT_SYMBOL_GPL(mpath_get_iopolicy);

-
void mpath_synchronize(struct mpath_head *mpath_head)
{
synchronize_srcu(&mpath_head->srcu);
}
EXPORT_SYMBOL_GPL(mpath_synchronize);

+void mpath_add_device(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device)
+{
+ mutex_lock(&mpath_head->lock);
+ list_add_tail_rcu(&mpath_device->siblings, &mpath_head->dev_list);
+ mutex_unlock(&mpath_head->lock);
+}
+EXPORT_SYMBOL_GPL(mpath_add_device);
+
+void mpath_delete_device(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device)
+{
+ mutex_lock(&mpath_head->lock);
+ list_del_rcu(&mpath_device->siblings);
+ mutex_unlock(&mpath_head->lock);
+}
+EXPORT_SYMBOL_GPL(mpath_delete_device);
+
+int mpath_call_for_device(struct mpath_head *mpath_head,
+ int (*cb)(struct mpath_device *mpath_device))
+{
+ struct mpath_device *mpath_device;
+ int ret = -EWOULDBLOCK, srcu_idx;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ mpath_device = mpath_find_path(mpath_head);
+ if (mpath_device)
+ ret = cb(mpath_device);
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mpath_call_for_device);
+
+bool mpath_clear_current_path(struct mpath_head *mpath_head,
+ struct mpath_device *mpath_device)
+{
+ bool changed = false;
+ int node;
+
+ if (!mpath_head)
+ goto out;
+
+ for_each_node(node) {
+ if (mpath_device ==
+ rcu_access_pointer(mpath_head->current_path[node])) {
+ rcu_assign_pointer(mpath_head->current_path[node],
+ NULL);
+ changed = true;
+ }
+ }
+out:
+ return changed;
+}
+EXPORT_SYMBOL_GPL(mpath_clear_current_path);
+
+static void mpath_revalidate_paths_iter(struct mpath_disk *mpath_disk,
+ void (*cb)(struct mpath_device *mpath_device, sector_t capacity))
+{
+ struct mpath_head *mpath_head = mpath_disk->mpath_head;
+ sector_t capacity = get_capacity(mpath_disk->disk);
+ struct mpath_device *mpath_device;
+ int srcu_idx;
+
+ if (!cb)
+ return;
+
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ cb(mpath_device, capacity);
+ }
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+}
+
+void mpath_clear_paths(struct mpath_head *mpath_head)
+{
+ int node;
+
+ for_each_node(node)
+ rcu_assign_pointer(mpath_head->current_path[node], NULL);
+}
+EXPORT_SYMBOL_GPL(mpath_clear_paths);
+
+void mpath_revalidate_paths(struct mpath_disk *mpath_disk,
+ void (*cb)(struct mpath_device *mpath_device, sector_t capacity))
+{
+ struct mpath_head *mpath_head = mpath_disk->mpath_head;
+
+ mpath_revalidate_paths_iter(mpath_disk, cb);
+ mpath_clear_paths(mpath_head);
+
+ kblockd_schedule_work(&mpath_head->requeue_work);
+}
+EXPORT_SYMBOL_GPL(mpath_revalidate_paths);
+
static bool mpath_path_is_disabled(struct mpath_head *mpath_head,
struct mpath_device *mpath_device)
{
@@ -480,6 +575,8 @@ void mpath_device_set_live(struct mpath_disk *mpath_disk,
queue_work(mpath_wq, &mpath_disk->partition_scan_work);
}

+ mpath_add_sysfs_link(mpath_disk);
+
mutex_lock(&mpath_head->lock);
if (mpath_path_is_optimized(mpath_head, mpath_device)) {
int node, srcu_idx;
@@ -498,6 +595,76 @@ void mpath_device_set_live(struct mpath_disk *mpath_disk,
}
EXPORT_SYMBOL_GPL(mpath_device_set_live);

+void mpath_add_sysfs_link(struct mpath_disk *mpath_disk)
+{
+ struct mpath_head *mpath_head = mpath_disk->mpath_head;
+ struct device *target;
+ struct device *source;
+ int rc, srcu_idx;
+ struct kobject *mpath_gd_kobj;
+ struct mpath_device *mpath_device;
+
+ /*
+ * Ensure head disk node is already added otherwise we may get invalid
+ * kobj for head disk node
+ */
+ if (!test_bit(GD_ADDED, &mpath_disk->disk->state))
+ return;
+
+ mpath_gd_kobj = &disk_to_dev(mpath_disk->disk)->kobj;
+ srcu_idx = srcu_read_lock(&mpath_head->srcu);
+
+ list_for_each_entry_srcu(mpath_device, &mpath_head->dev_list, siblings,
+ srcu_read_lock_held(&mpath_head->srcu)) {
+ if (!test_bit(GD_ADDED, &mpath_device->disk->state))
+ continue;
+
+ if (test_and_set_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags))
+ continue;
+
+ target = disk_to_dev(mpath_device->disk);
+ source = disk_to_dev(mpath_disk->disk);
+ /*
+ * Create sysfs link from head gendisk kobject @kobj to the
+ * ns path gendisk kobject @target->kobj.
+ */
+ rc = sysfs_add_link_to_group(mpath_gd_kobj, "multipath",
+ &target->kobj, dev_name(target));
+
+ if (unlikely(rc)) {
+ dev_err(disk_to_dev(mpath_disk->disk),
+ "failed to create link to %s rc=%d\n",
+ dev_name(target), rc);
+ clear_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags);
+ } else {
+ dev_info(source, "Created multipath sysfs link to %s\n",
+ mpath_device->disk->disk_name);
+ }
+ }
+
+ srcu_read_unlock(&mpath_head->srcu, srcu_idx);
+}
+EXPORT_SYMBOL_GPL(mpath_add_sysfs_link);
+
+void mpath_remove_sysfs_link(struct mpath_disk *mpath_disk,
+ struct mpath_device *mpath_device)
+{
+ struct device *target;
+ struct kobject *mpath_gd_kobj;
+
+ if (!test_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags))
+ return;
+
+ target = disk_to_dev(mpath_device->disk);
+ mpath_gd_kobj = &disk_to_dev(mpath_disk->disk)->kobj;
+
+ sysfs_remove_link_from_group(mpath_gd_kobj, "multipath",
+ dev_name(target));
+
+ clear_bit(MPATH_DEVICE_SYSFS_ATTR_LINK, &mpath_device->flags);
+}
+EXPORT_SYMBOL_GPL(mpath_remove_sysfs_link);
+
struct mpath_head *mpath_alloc_head(void)
{
struct mpath_head *mpath_head;
--
2.43.5