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 |  16 ++++
 lib/multipath.c           | 182 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 198 insertions(+)

diff --git a/include/linux/multipath.h b/include/linux/multipath.h
index 2a5a9236480f7..72186ab220083 100644
--- a/include/linux/multipath.h
+++ b/include/linux/multipath.h
@@ -24,10 +24,13 @@ enum mpath_access_state {
        MPATH_STATE_OTHER
 };
 
+#define MPATH_DEVICE_SYSFS_ATTR_LINK      0
+
 struct mpath_device {
        struct mpath_head       *mpath_head;
        struct list_head        siblings;
        struct gendisk          *disk;
+       unsigned long           flags;
        int                     numa_node;
        enum mpath_access_state access_state;
 };
@@ -90,6 +93,19 @@ 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_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);
+bool mpath_delete_device(struct mpath_device *mpath_device);
+bool mpath_head_devices_empty(struct mpath_head *mpath_head);
+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_head *mpath_head,
+       void (*not_ready_cb)(struct mpath_device *mpath_device));
+void mpath_add_sysfs_link(struct mpath_head *mpath_head);
+void mpath_remove_sysfs_link(struct mpath_device *mpath_device);
 int mpath_get_head(struct mpath_head *mpath_head);
 void mpath_put_head(struct mpath_head *mpath_head);
 struct mpath_head *mpath_alloc_head(void);
diff --git a/lib/multipath.c b/lib/multipath.c
index eabf1347d9acc..1232e057199ae 100644
--- a/lib/multipath.c
+++ b/lib/multipath.c
@@ -46,6 +46,115 @@ void mpath_synchronize(struct mpath_head *mpath_head)
 }
 EXPORT_SYMBOL_GPL(mpath_synchronize);
 
+void mpath_add_device(struct mpath_head *mpath_head,
+                       struct mpath_device *mpath_device)
+{
+       mpath_device->mpath_head = mpath_head;
+       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);
+
+bool mpath_delete_device(struct mpath_device *mpath_device)
+{
+       bool empty;
+
+       mutex_lock(&mpath_device->mpath_head->lock);
+       list_del_rcu(&mpath_device->siblings);
+       empty = list_empty(&mpath_device->mpath_head->dev_list);
+       mutex_unlock(&mpath_device->mpath_head->lock);
+
+       return empty;
+}
+EXPORT_SYMBOL_GPL(mpath_delete_device);
+
+bool mpath_head_devices_empty(struct mpath_head *mpath_head)
+{
+       bool empty;
+
+       mutex_lock(&mpath_head->lock);
+       empty = list_empty(&mpath_head->dev_list);
+       mutex_unlock(&mpath_head->lock);
+
+       return empty;
+}
+EXPORT_SYMBOL_GPL(mpath_head_devices_empty);
+
+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_device *mpath_device)
+{
+       struct mpath_head *mpath_head = mpath_device->mpath_head;
+       bool changed = false;
+       int node;
+
+       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;
+               }
+       }
+
+       return changed;
+}
+EXPORT_SYMBOL_GPL(mpath_clear_current_path);
+
+static void mpath_revalidate_paths_iter(struct mpath_head *mpath_head,
+       void (*not_ready_cb)(struct mpath_device *mpath_device))
+{
+       sector_t capacity = get_capacity(mpath_head->disk);
+       struct mpath_device *mpath_device;
+       int srcu_idx;
+
+       if (!not_ready_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)) {
+               if (capacity != get_capacity(mpath_device->disk))
+                       not_ready_cb(mpath_device);
+       }
+       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_head *mpath_head,
+       void (*not_ready_cb)(struct mpath_device *mpath_device))
+{
+
+       mpath_revalidate_paths_iter(mpath_head, not_ready_cb);
+       mpath_clear_paths(mpath_head);
+
+       mpath_schedule_requeue_work(mpath_head);
+}
+EXPORT_SYMBOL_GPL(mpath_revalidate_paths);
+
 static bool mpath_path_is_disabled(struct mpath_head *mpath_head,
                                struct mpath_device *mpath_device)
 {
@@ -449,6 +558,8 @@ void mpath_device_set_live(struct mpath_device 
*mpath_device)
                queue_work(mpath_wq, &mpath_head->partition_scan_work);
        }
 
+       mpath_add_sysfs_link(mpath_head);
+
        mutex_lock(&mpath_head->lock);
        if (mpath_path_is_optimized(mpath_head, mpath_device)) {
                int node, srcu_idx;
@@ -465,6 +576,77 @@ void mpath_device_set_live(struct mpath_device 
*mpath_device)
 }
 EXPORT_SYMBOL_GPL(mpath_device_set_live);
 
+void mpath_add_sysfs_link(struct mpath_head *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_head->disk->state))
+               return;
+
+       mpath_gd_kobj = &disk_to_dev(mpath_head->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_head->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_head->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_device *mpath_device)
+{
+       struct device *target;
+       struct kobject *mpath_gd_kobj;
+       struct mpath_head *mpath_head = mpath_device->mpath_head;
+
+       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_head->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