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

