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 <[email protected]>
---
 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


Reply via email to