162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#include <linux/blkdev.h>
362306a36Sopenharmony_ci#include <linux/slab.h>
462306a36Sopenharmony_ci
562306a36Sopenharmony_cistruct bd_holder_disk {
662306a36Sopenharmony_ci	struct list_head	list;
762306a36Sopenharmony_ci	struct kobject		*holder_dir;
862306a36Sopenharmony_ci	int			refcnt;
962306a36Sopenharmony_ci};
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_cistatic struct bd_holder_disk *bd_find_holder_disk(struct block_device *bdev,
1262306a36Sopenharmony_ci						  struct gendisk *disk)
1362306a36Sopenharmony_ci{
1462306a36Sopenharmony_ci	struct bd_holder_disk *holder;
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci	list_for_each_entry(holder, &disk->slave_bdevs, list)
1762306a36Sopenharmony_ci		if (holder->holder_dir == bdev->bd_holder_dir)
1862306a36Sopenharmony_ci			return holder;
1962306a36Sopenharmony_ci	return NULL;
2062306a36Sopenharmony_ci}
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_cistatic int add_symlink(struct kobject *from, struct kobject *to)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	return sysfs_create_link(from, to, kobject_name(to));
2562306a36Sopenharmony_ci}
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic void del_symlink(struct kobject *from, struct kobject *to)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	sysfs_remove_link(from, kobject_name(to));
3062306a36Sopenharmony_ci}
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci/**
3362306a36Sopenharmony_ci * bd_link_disk_holder - create symlinks between holding disk and slave bdev
3462306a36Sopenharmony_ci * @bdev: the claimed slave bdev
3562306a36Sopenharmony_ci * @disk: the holding disk
3662306a36Sopenharmony_ci *
3762306a36Sopenharmony_ci * DON'T USE THIS UNLESS YOU'RE ALREADY USING IT.
3862306a36Sopenharmony_ci *
3962306a36Sopenharmony_ci * This functions creates the following sysfs symlinks.
4062306a36Sopenharmony_ci *
4162306a36Sopenharmony_ci * - from "slaves" directory of the holder @disk to the claimed @bdev
4262306a36Sopenharmony_ci * - from "holders" directory of the @bdev to the holder @disk
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci * For example, if /dev/dm-0 maps to /dev/sda and disk for dm-0 is
4562306a36Sopenharmony_ci * passed to bd_link_disk_holder(), then:
4662306a36Sopenharmony_ci *
4762306a36Sopenharmony_ci *   /sys/block/dm-0/slaves/sda --> /sys/block/sda
4862306a36Sopenharmony_ci *   /sys/block/sda/holders/dm-0 --> /sys/block/dm-0
4962306a36Sopenharmony_ci *
5062306a36Sopenharmony_ci * The caller must have claimed @bdev before calling this function and
5162306a36Sopenharmony_ci * ensure that both @bdev and @disk are valid during the creation and
5262306a36Sopenharmony_ci * lifetime of these symlinks.
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * CONTEXT:
5562306a36Sopenharmony_ci * Might sleep.
5662306a36Sopenharmony_ci *
5762306a36Sopenharmony_ci * RETURNS:
5862306a36Sopenharmony_ci * 0 on success, -errno on failure.
5962306a36Sopenharmony_ci */
6062306a36Sopenharmony_ciint bd_link_disk_holder(struct block_device *bdev, struct gendisk *disk)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct bd_holder_disk *holder;
6362306a36Sopenharmony_ci	int ret = 0;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	if (WARN_ON_ONCE(!disk->slave_dir))
6662306a36Sopenharmony_ci		return -EINVAL;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	if (bdev->bd_disk == disk)
6962306a36Sopenharmony_ci		return -EINVAL;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	/*
7262306a36Sopenharmony_ci	 * del_gendisk drops the initial reference to bd_holder_dir, so we
7362306a36Sopenharmony_ci	 * need to keep our own here to allow for cleanup past that point.
7462306a36Sopenharmony_ci	 */
7562306a36Sopenharmony_ci	mutex_lock(&bdev->bd_disk->open_mutex);
7662306a36Sopenharmony_ci	if (!disk_live(bdev->bd_disk)) {
7762306a36Sopenharmony_ci		mutex_unlock(&bdev->bd_disk->open_mutex);
7862306a36Sopenharmony_ci		return -ENODEV;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci	kobject_get(bdev->bd_holder_dir);
8162306a36Sopenharmony_ci	mutex_unlock(&bdev->bd_disk->open_mutex);
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	mutex_lock(&disk->open_mutex);
8462306a36Sopenharmony_ci	WARN_ON_ONCE(!bdev->bd_holder);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	holder = bd_find_holder_disk(bdev, disk);
8762306a36Sopenharmony_ci	if (holder) {
8862306a36Sopenharmony_ci		kobject_put(bdev->bd_holder_dir);
8962306a36Sopenharmony_ci		holder->refcnt++;
9062306a36Sopenharmony_ci		goto out_unlock;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	holder = kzalloc(sizeof(*holder), GFP_KERNEL);
9462306a36Sopenharmony_ci	if (!holder) {
9562306a36Sopenharmony_ci		ret = -ENOMEM;
9662306a36Sopenharmony_ci		goto out_unlock;
9762306a36Sopenharmony_ci	}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	INIT_LIST_HEAD(&holder->list);
10062306a36Sopenharmony_ci	holder->refcnt = 1;
10162306a36Sopenharmony_ci	holder->holder_dir = bdev->bd_holder_dir;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	ret = add_symlink(disk->slave_dir, bdev_kobj(bdev));
10462306a36Sopenharmony_ci	if (ret)
10562306a36Sopenharmony_ci		goto out_free_holder;
10662306a36Sopenharmony_ci	ret = add_symlink(bdev->bd_holder_dir, &disk_to_dev(disk)->kobj);
10762306a36Sopenharmony_ci	if (ret)
10862306a36Sopenharmony_ci		goto out_del_symlink;
10962306a36Sopenharmony_ci	list_add(&holder->list, &disk->slave_bdevs);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	mutex_unlock(&disk->open_mutex);
11262306a36Sopenharmony_ci	return 0;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ciout_del_symlink:
11562306a36Sopenharmony_ci	del_symlink(disk->slave_dir, bdev_kobj(bdev));
11662306a36Sopenharmony_ciout_free_holder:
11762306a36Sopenharmony_ci	kfree(holder);
11862306a36Sopenharmony_ciout_unlock:
11962306a36Sopenharmony_ci	mutex_unlock(&disk->open_mutex);
12062306a36Sopenharmony_ci	if (ret)
12162306a36Sopenharmony_ci		kobject_put(bdev->bd_holder_dir);
12262306a36Sopenharmony_ci	return ret;
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(bd_link_disk_holder);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci/**
12762306a36Sopenharmony_ci * bd_unlink_disk_holder - destroy symlinks created by bd_link_disk_holder()
12862306a36Sopenharmony_ci * @bdev: the calimed slave bdev
12962306a36Sopenharmony_ci * @disk: the holding disk
13062306a36Sopenharmony_ci *
13162306a36Sopenharmony_ci * DON'T USE THIS UNLESS YOU'RE ALREADY USING IT.
13262306a36Sopenharmony_ci *
13362306a36Sopenharmony_ci * CONTEXT:
13462306a36Sopenharmony_ci * Might sleep.
13562306a36Sopenharmony_ci */
13662306a36Sopenharmony_civoid bd_unlink_disk_holder(struct block_device *bdev, struct gendisk *disk)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct bd_holder_disk *holder;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	if (WARN_ON_ONCE(!disk->slave_dir))
14162306a36Sopenharmony_ci		return;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	mutex_lock(&disk->open_mutex);
14462306a36Sopenharmony_ci	holder = bd_find_holder_disk(bdev, disk);
14562306a36Sopenharmony_ci	if (!WARN_ON_ONCE(holder == NULL) && !--holder->refcnt) {
14662306a36Sopenharmony_ci		del_symlink(disk->slave_dir, bdev_kobj(bdev));
14762306a36Sopenharmony_ci		del_symlink(holder->holder_dir, &disk_to_dev(disk)->kobj);
14862306a36Sopenharmony_ci		kobject_put(holder->holder_dir);
14962306a36Sopenharmony_ci		list_del_init(&holder->list);
15062306a36Sopenharmony_ci		kfree(holder);
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci	mutex_unlock(&disk->open_mutex);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(bd_unlink_disk_holder);
155