Introduce a scsi_device head structure - scsi_mpath_head - to manage multipathing for a scsi_device. This is similar to nvme_ns_head structure.
A list of scsi_mpath_head structures is managed to lookup for matching multipathed scsi_device's. Matching is done through the scsi_device unique id. A new class for multipathed devices is added, scsi_mpath_device_class. The purpose of this class is for managing the scsi_mpath_head.dev member. The naming for the scsi_device structure is in form H:C:I:L, where H is host, C is channel, I is ID, and L is lun. However, for a multipathed scsi_device, all the naming members may be different between member scsi_device's. As such, just use a simple single-number naming index for each scsi_mpath_head. The sysfs device folder will have links to the scsi_device's so, it will be possible to lookup the member scsi_device's. An example sysfs entry is as follows: # ls -l /sys/class/scsi_mpath_device/scsi_mpath_device0/ total 0 drwxr-xr-x 2 root root 0 Apr 13 15:48 power lrwxrwxrwx 1 root root 0 Apr 13 15:48 subsystem -> ../../../../class/scsi_mpath_device -rw-r--r-- 1 root root 4096 Apr 13 15:48 uevent -r--r--r-- 1 root root 4096 Apr 13 15:48 vpd_id Signed-off-by: John Garry <[email protected]> --- drivers/scsi/scsi_multipath.c | 209 +++++++++++++++++++++++++++++++++- drivers/scsi/scsi_sysfs.c | 3 + include/scsi/scsi_multipath.h | 29 +++++ 3 files changed, 239 insertions(+), 2 deletions(-) diff --git a/drivers/scsi/scsi_multipath.c b/drivers/scsi/scsi_multipath.c index ff37cfdf2f9d1..18d50d051b3ba 100644 --- a/drivers/scsi/scsi_multipath.c +++ b/drivers/scsi/scsi_multipath.c @@ -27,6 +27,10 @@ static const char *scsi_multipath_modes[] = { static int scsi_multipath = SCSI_MULTIPATH_OFF; +static LIST_HEAD(scsi_mpath_heads_list); +static DEFINE_MUTEX(scsi_mpath_heads_lock); +static DEFINE_IDA(scsi_multipath_dev_ida); + static int scsi_multipath_param_set(const char *val, const struct kernel_param *kp) { if (!val) @@ -69,6 +73,60 @@ static int scsi_mpath_unique_lun_id(struct scsi_device *sdev) return 0; } +static void scsi_mpath_head_release(struct device *dev) +{ + struct scsi_mpath_head *scsi_mpath_head = + container_of(dev, struct scsi_mpath_head, dev); + struct mpath_head *mpath_head = scsi_mpath_head->mpath_head; + + ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index); + mpath_put_head(mpath_head); + kfree(scsi_mpath_head); +} + +static ssize_t scsi_mpath_device_vpd_id_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct scsi_mpath_head *scsi_mpath_head = + container_of(dev, struct scsi_mpath_head, dev); + + return sysfs_emit(buf, "%s\n", scsi_mpath_head->vpd_id); +} +static DEVICE_ATTR(vpd_id, S_IRUGO, scsi_mpath_device_vpd_id_show, NULL); + +static struct attribute *scsi_mpath_device_attrs[] = { + &dev_attr_vpd_id.attr, + NULL +}; + +static const struct attribute_group scsi_mpath_device_attrs_group = { + .attrs = scsi_mpath_device_attrs, +}; + +static bool scsi_multipath_sysfs_group_visible(struct kobject *kobj) +{ + return true; +} + +static bool scsi_multipath_sysfs_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + return false; +} +DEFINE_SYSFS_GROUP_VISIBLE(scsi_multipath_sysfs) + +const struct attribute_group *scsi_mpath_device_groups[] = { + &scsi_mpath_device_attrs_group, + NULL +}; + +static const struct class scsi_mpath_device_class = { + .name = "scsi_mpath_device", + .dev_groups = scsi_mpath_device_groups, + .dev_release = scsi_mpath_head_release, +}; + static int scsi_multipath_sdev_init(struct scsi_device *sdev) { struct Scsi_Host *shost = sdev->host; @@ -88,6 +146,73 @@ static int scsi_multipath_sdev_init(struct scsi_device *sdev) return 0; } +struct mpath_head_template smpdt = { +}; + +static struct scsi_mpath_head *scsi_mpath_alloc_head(void) +{ + struct scsi_mpath_head *scsi_mpath_head; + int ret; + + scsi_mpath_head = kzalloc(sizeof(*scsi_mpath_head), GFP_KERNEL); + if (!scsi_mpath_head) + return NULL; + + ida_init(&scsi_mpath_head->ida); + + scsi_mpath_head->mpath_head = mpath_alloc_head(); + if (IS_ERR(scsi_mpath_head->mpath_head)) + goto out_free; + scsi_mpath_head->mpath_head->mpdt = &smpdt; + scsi_mpath_head->mpath_head->drvdata = scsi_mpath_head; + + scsi_mpath_head->index = ida_alloc(&scsi_multipath_dev_ida, GFP_KERNEL); + if (scsi_mpath_head->index < 0) + goto out_put_head; + kref_init(&scsi_mpath_head->ref); + + device_initialize(&scsi_mpath_head->dev); + scsi_mpath_head->dev.class = &scsi_mpath_device_class; + ret = dev_set_name(&scsi_mpath_head->dev, "scsi_mpath_device%d", + scsi_mpath_head->index); + if (ret) { + put_device(&scsi_mpath_head->dev); + goto out_free_ida; + } + + return scsi_mpath_head; + +out_free_ida: + ida_free(&scsi_multipath_dev_ida, scsi_mpath_head->index); +out_put_head: + mpath_put_head(scsi_mpath_head->mpath_head); +out_free: + kfree(scsi_mpath_head); + return NULL; +} + +static struct scsi_mpath_head *scsi_mpath_find_head( + struct scsi_mpath_device *scsi_mpath_dev) +{ + struct scsi_mpath_head *scsi_mpath_head; + int ret; + + list_for_each_entry(scsi_mpath_head, &scsi_mpath_heads_list, entry) { + ret = scsi_mpath_get_head(scsi_mpath_head); + if (ret) + continue; + if (strncmp(scsi_mpath_head->vpd_id, + scsi_mpath_dev->device_id_str, + SCSI_MPATH_DEVICE_ID_LEN) == 0) { + + return scsi_mpath_head; + } + scsi_mpath_put_head(scsi_mpath_head); + } + + return NULL; +} + static void scsi_multipath_sdev_uninit(struct scsi_device *sdev) { kfree(sdev->scsi_mpath_dev); @@ -96,6 +221,7 @@ static void scsi_multipath_sdev_uninit(struct scsi_device *sdev) int scsi_mpath_dev_alloc(struct scsi_device *sdev) { + struct scsi_mpath_head *scsi_mpath_head; int ret; if (scsi_multipath == SCSI_MULTIPATH_OFF) @@ -116,13 +242,58 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev) goto out_uninit; } - return 0; + mutex_lock(&scsi_mpath_heads_lock); + scsi_mpath_head = scsi_mpath_find_head(sdev->scsi_mpath_dev); + if (scsi_mpath_head) + goto found; + scsi_mpath_head = scsi_mpath_alloc_head(); + if (!scsi_mpath_head) { + sdev_printk(KERN_NOTICE, sdev, "could not allocate multipath head, device multipathing disabled\n"); + mutex_unlock(&scsi_mpath_heads_lock); + goto out_uninit; + } + strscpy(scsi_mpath_head->vpd_id, sdev->scsi_mpath_dev->device_id_str, + SCSI_MPATH_DEVICE_ID_LEN); + + ret = device_add(&scsi_mpath_head->dev); + if (ret) { + mutex_unlock(&scsi_mpath_heads_lock); + goto out_put_head; + } + + list_add_tail(&scsi_mpath_head->entry, &scsi_mpath_heads_list); +found: + mutex_unlock(&scsi_mpath_heads_lock); + ret = ida_alloc(&scsi_mpath_head->ida, GFP_KERNEL); + if (ret < 0) + goto out_put_head; + sdev->scsi_mpath_dev->index = ret; + + sdev->scsi_mpath_dev->scsi_mpath_head = scsi_mpath_head; + return 0; +out_put_head: + scsi_mpath_put_head(scsi_mpath_head); out_uninit: scsi_multipath_sdev_uninit(sdev); return ret; } +static void scsi_mpath_remove_head(struct scsi_mpath_device *scsi_mpath_dev) +{ + scsi_mpath_put_head(scsi_mpath_dev->scsi_mpath_head); + scsi_mpath_dev->scsi_mpath_head = NULL; +} + +void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev) +{ + struct scsi_mpath_head *scsi_mpath_head = scsi_mpath_dev->scsi_mpath_head; + + ida_free(&scsi_mpath_head->ida, scsi_mpath_dev->index); + + scsi_mpath_remove_head(scsi_mpath_dev); +} + void scsi_mpath_dev_release(struct scsi_device *sdev) { struct scsi_mpath_device *scsi_mpath_dev = sdev->scsi_mpath_dev; @@ -133,13 +304,47 @@ void scsi_mpath_dev_release(struct scsi_device *sdev) scsi_multipath_sdev_uninit(sdev); } -int __init scsi_multipath_init(void) +int scsi_mpath_get_head(struct scsi_mpath_head *scsi_mpath_head) { + if (!kref_get_unless_zero(&scsi_mpath_head->ref)) + return -ENXIO; return 0; } +EXPORT_SYMBOL_GPL(scsi_mpath_get_head); + +static void scsi_mpath_free_head(struct kref *ref) +{ + struct scsi_mpath_head *scsi_mpath_head = + container_of(ref, struct scsi_mpath_head, ref); + + /* + * If we race with scsi_mpath_find_head(), then that function may + * find this scsi_mpath_head in the heads list; however we would fail + * to take a reference to this scsi_mpath_head and continue the search. + * As such, it is safe to call device_unregister (and free + * scsi_mpath_head) after we delete this head from the list. + */ + mutex_lock(&scsi_mpath_heads_lock); + list_del_init(&scsi_mpath_head->entry); + mutex_unlock(&scsi_mpath_heads_lock); + + device_unregister(&scsi_mpath_head->dev); +} + +void scsi_mpath_put_head(struct scsi_mpath_head *scsi_mpath_head) +{ + kref_put(&scsi_mpath_head->ref, scsi_mpath_free_head); +} +EXPORT_SYMBOL_GPL(scsi_mpath_put_head); + +int __init scsi_multipath_init(void) +{ + return class_register(&scsi_mpath_device_class); +} void __exit scsi_multipath_exit(void) { + class_unregister(&scsi_mpath_device_class); } MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/scsi_sysfs.c b/drivers/scsi/scsi_sysfs.c index 47534d9f2cf9b..043fd2d9cc417 100644 --- a/drivers/scsi/scsi_sysfs.c +++ b/drivers/scsi/scsi_sysfs.c @@ -1485,6 +1485,9 @@ void __scsi_remove_device(struct scsi_device *sdev) } else put_device(&sdev->sdev_dev); + if (sdev->scsi_mpath_dev) + scsi_mpath_remove_device(sdev->scsi_mpath_dev); + /* * Stop accepting new requests and wait until all queuecommand() and * scsi_run_queue() invocations have finished before tearing down the diff --git a/include/scsi/scsi_multipath.h b/include/scsi/scsi_multipath.h index d3d410dafd17a..b3e0b98f39c56 100644 --- a/include/scsi/scsi_multipath.h +++ b/include/scsi/scsi_multipath.h @@ -19,12 +19,25 @@ #ifdef CONFIG_SCSI_MULTIPATH #define SCSI_MPATH_DEVICE_ID_LEN 256 +struct scsi_mpath_head { + char vpd_id[SCSI_MPATH_DEVICE_ID_LEN]; + struct list_head entry; + struct ida ida; + struct kref ref; + struct mpath_head *mpath_head; + struct device dev; + int index; +}; + struct scsi_mpath_device { struct mpath_device mpath_device; struct scsi_device *sdev; + int index; + struct scsi_mpath_head *scsi_mpath_head; char device_id_str[SCSI_MPATH_DEVICE_ID_LEN]; }; + #define to_scsi_mpath_device(d) \ container_of(d, struct scsi_mpath_device, mpath_device) @@ -32,8 +45,13 @@ int scsi_mpath_dev_alloc(struct scsi_device *sdev); void scsi_mpath_dev_release(struct scsi_device *sdev); int scsi_multipath_init(void); void scsi_multipath_exit(void); +void scsi_mpath_remove_device(struct scsi_mpath_device *scsi_mpath_dev); +int scsi_mpath_get_head(struct scsi_mpath_head *); +void scsi_mpath_put_head(struct scsi_mpath_head *); #else /* CONFIG_SCSI_MULTIPATH */ +struct scsi_mpath_head { +}; struct scsi_mpath_device { }; @@ -51,5 +69,16 @@ static inline int scsi_multipath_init(void) static inline void scsi_multipath_exit(void) { } +static inline void scsi_mpath_remove_device(struct scsi_mpath_device + *scsi_mpath_dev) +{ +} +static inline int scsi_mpath_get_head(struct scsi_mpath_head *) +{ + return 0; +} +static inline void scsi_mpath_put_head(struct scsi_mpath_head *) +{ +} #endif /* CONFIG_SCSI_MULTIPATH */ #endif /* _SCSI_SCSI_MULTIPATH_H */ -- 2.43.5

