Add support to allocate and free a multipath gendisk. NVMe has almost like-for-like equivalents here: - mpath_alloc_head_disk() -> nvme_mpath_alloc_disk() - multipath_partition_scan_work() -> nvme_partition_scan_work() - mpath_remove_disk() -> nvme_remove_head() - mpath_device_set_live() -> nvme_mpath_set_live()
struct mpath_head_template is introduced as a method for drivers to provide custom multipath functionality. Signed-off-by: John Garry <[email protected]> --- include/linux/multipath.h | 41 ++++++++++++ lib/multipath.c | 129 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/include/linux/multipath.h b/include/linux/multipath.h index 18cd133b7ca21..be9dd9fb83345 100644 --- a/include/linux/multipath.h +++ b/include/linux/multipath.h @@ -5,11 +5,28 @@ #include <linux/blkdev.h> #include <linux/srcu.h> +extern const struct block_device_operations mpath_ops; + +struct mpath_disk { + struct gendisk *disk; + struct kref ref; + struct work_struct partition_scan_work; + struct mutex lock; + struct mpath_head *mpath_head; + struct device *parent; +}; + struct mpath_device { struct list_head siblings; struct gendisk *disk; }; +struct mpath_head_template { + const struct attribute_group **device_groups; +}; + +#define MPATH_HEAD_DISK_LIVE 0 + struct mpath_head { struct srcu_struct srcu; struct list_head dev_list; /* list of all mpath_devs */ @@ -17,12 +34,36 @@ struct mpath_head { struct kref ref; + unsigned long flags; struct mpath_device __rcu *current_path[MAX_NUMNODES]; + const struct mpath_head_template *mpdt; void *drvdata; }; +static inline struct mpath_disk *mpath_bd_device_to_disk(struct device *dev) +{ + return dev_get_drvdata(dev); +} + +static inline struct mpath_disk *mpath_gendisk_to_disk(struct gendisk *disk) +{ + return mpath_bd_device_to_disk(disk_to_dev(disk)); +} + 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); +void mpath_put_disk(struct mpath_disk *mpath_disk); +void mpath_remove_disk(struct mpath_disk *mpath_disk); +void mpath_unregister_disk(struct mpath_disk *mpath_disk); +struct mpath_disk *mpath_alloc_head_disk(struct queue_limits *lim, + int numa_node); +void mpath_device_set_live(struct mpath_disk *mpath_disk, + struct mpath_device *mpath_device); +void mpath_unregister_disk(struct mpath_disk *mpath_disk); +static inline bool is_mpath_head(struct gendisk *disk) +{ + return disk->fops == &mpath_ops; +} #endif // _LIBMULTIPATH_H diff --git a/lib/multipath.c b/lib/multipath.c index 15c495675d729..88efb0ae16acb 100644 --- a/lib/multipath.c +++ b/lib/multipath.c @@ -32,6 +32,135 @@ void mpath_put_head(struct mpath_head *mpath_head) } EXPORT_SYMBOL_GPL(mpath_put_head); +static void mpath_free_disk(struct kref *ref) +{ + struct mpath_disk *mpath_disk = + container_of(ref, struct mpath_disk, ref); + struct mpath_head *mpath_head = mpath_disk->mpath_head; + + put_disk(mpath_disk->disk); + mpath_put_head(mpath_head); + kfree(mpath_disk); +} + +void mpath_put_disk(struct mpath_disk *mpath_disk) +{ + kref_put(&mpath_disk->ref, mpath_free_disk); +} +EXPORT_SYMBOL_GPL(mpath_put_disk); + +static int mpath_get_disk(struct mpath_disk *mpath_disk) +{ + if (!kref_get_unless_zero(&mpath_disk->ref)) { + return -ENXIO; + } + return 0; +} + +static int mpath_bdev_open(struct gendisk *disk, blk_mode_t mode) +{ + struct mpath_disk *mpath_disk = disk->private_data; + + return mpath_get_disk(mpath_disk); +} + +static void mpath_bdev_release(struct gendisk *disk) +{ + struct mpath_disk *mpath_disk = disk->private_data; + + mpath_put_disk(mpath_disk); +} + +const struct block_device_operations mpath_ops = { + .owner = THIS_MODULE, + .open = mpath_bdev_open, + .release = mpath_bdev_release, +}; +EXPORT_SYMBOL_GPL(mpath_ops); + +static void multipath_partition_scan_work(struct work_struct *work) +{ + struct mpath_disk *mpath_disk = + container_of(work, struct mpath_disk, partition_scan_work); + + if (WARN_ON_ONCE(!test_and_clear_bit(GD_SUPPRESS_PART_SCAN, + &mpath_disk->disk->state))) + return; + + mutex_lock(&mpath_disk->disk->open_mutex); + bdev_disk_changed(mpath_disk->disk, false); + mutex_unlock(&mpath_disk->disk->open_mutex); +} + +void mpath_remove_disk(struct mpath_disk *mpath_disk) +{ + struct mpath_head *mpath_head = mpath_disk->mpath_head; + + if (test_and_clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) { + struct gendisk *disk = mpath_disk->disk; + + del_gendisk(disk); + } +} +EXPORT_SYMBOL_GPL(mpath_remove_disk); + +void mpath_unregister_disk(struct mpath_disk *mpath_disk) +{ + mpath_remove_disk(mpath_disk); + mpath_put_disk(mpath_disk); +} +EXPORT_SYMBOL_GPL(mpath_unregister_disk); + +struct mpath_disk *mpath_alloc_head_disk(struct queue_limits *lim, int numa_node) +{ + struct mpath_disk *mpath_disk; + + mpath_disk = kzalloc(sizeof(*mpath_disk), GFP_KERNEL); + if (!mpath_disk) + return NULL; + + INIT_WORK(&mpath_disk->partition_scan_work, + multipath_partition_scan_work); + mutex_init(&mpath_disk->lock); + kref_init(&mpath_disk->ref); + + mpath_disk->disk = blk_alloc_disk(lim, numa_node); + if (IS_ERR(mpath_disk->disk)) { + kfree(mpath_disk); + return NULL; + } + + mpath_disk->disk->private_data = mpath_disk; + mpath_disk->disk->fops = &mpath_ops; + + set_bit(GD_SUPPRESS_PART_SCAN, &mpath_disk->disk->state); + + return mpath_disk; +} +EXPORT_SYMBOL_GPL(mpath_alloc_head_disk); + +void mpath_device_set_live(struct mpath_disk *mpath_disk, + struct mpath_device *mpath_device) +{ + struct mpath_head *mpath_head = mpath_disk->mpath_head; + int ret; + + if (!mpath_disk) + return; + + if (!test_and_set_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags)) { + dev_set_drvdata(disk_to_dev(mpath_disk->disk), mpath_disk); + ret = device_add_disk(mpath_disk->parent, mpath_disk->disk, + mpath_head->mpdt->device_groups); + if (ret) { + clear_bit(MPATH_HEAD_DISK_LIVE, &mpath_head->flags); + return; + } + queue_work(mpath_wq, &mpath_disk->partition_scan_work); + } +} +EXPORT_SYMBOL_GPL(mpath_device_set_live); + struct mpath_head *mpath_alloc_head(void) { struct mpath_head *mpath_head; -- 2.43.5

