162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Block device concurrent positioning ranges.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2021 Western Digital Corporation or its Affiliates.
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/blkdev.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include "blk.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistatic ssize_t
1562306a36Sopenharmony_ciblk_ia_range_sector_show(struct blk_independent_access_range *iar,
1662306a36Sopenharmony_ci			 char *buf)
1762306a36Sopenharmony_ci{
1862306a36Sopenharmony_ci	return sprintf(buf, "%llu\n", iar->sector);
1962306a36Sopenharmony_ci}
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic ssize_t
2262306a36Sopenharmony_ciblk_ia_range_nr_sectors_show(struct blk_independent_access_range *iar,
2362306a36Sopenharmony_ci			     char *buf)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	return sprintf(buf, "%llu\n", iar->nr_sectors);
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct blk_ia_range_sysfs_entry {
2962306a36Sopenharmony_ci	struct attribute attr;
3062306a36Sopenharmony_ci	ssize_t (*show)(struct blk_independent_access_range *iar, char *buf);
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic struct blk_ia_range_sysfs_entry blk_ia_range_sector_entry = {
3462306a36Sopenharmony_ci	.attr = { .name = "sector", .mode = 0444 },
3562306a36Sopenharmony_ci	.show = blk_ia_range_sector_show,
3662306a36Sopenharmony_ci};
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic struct blk_ia_range_sysfs_entry blk_ia_range_nr_sectors_entry = {
3962306a36Sopenharmony_ci	.attr = { .name = "nr_sectors", .mode = 0444 },
4062306a36Sopenharmony_ci	.show = blk_ia_range_nr_sectors_show,
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic struct attribute *blk_ia_range_attrs[] = {
4462306a36Sopenharmony_ci	&blk_ia_range_sector_entry.attr,
4562306a36Sopenharmony_ci	&blk_ia_range_nr_sectors_entry.attr,
4662306a36Sopenharmony_ci	NULL,
4762306a36Sopenharmony_ci};
4862306a36Sopenharmony_ciATTRIBUTE_GROUPS(blk_ia_range);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic ssize_t blk_ia_range_sysfs_show(struct kobject *kobj,
5162306a36Sopenharmony_ci				      struct attribute *attr, char *buf)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	struct blk_ia_range_sysfs_entry *entry =
5462306a36Sopenharmony_ci		container_of(attr, struct blk_ia_range_sysfs_entry, attr);
5562306a36Sopenharmony_ci	struct blk_independent_access_range *iar =
5662306a36Sopenharmony_ci		container_of(kobj, struct blk_independent_access_range, kobj);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	return entry->show(iar, buf);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_cistatic const struct sysfs_ops blk_ia_range_sysfs_ops = {
6262306a36Sopenharmony_ci	.show	= blk_ia_range_sysfs_show,
6362306a36Sopenharmony_ci};
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/*
6662306a36Sopenharmony_ci * Independent access range entries are not freed individually, but alltogether
6762306a36Sopenharmony_ci * with struct blk_independent_access_ranges and its array of ranges. Since
6862306a36Sopenharmony_ci * kobject_add() takes a reference on the parent kobject contained in
6962306a36Sopenharmony_ci * struct blk_independent_access_ranges, the array of independent access range
7062306a36Sopenharmony_ci * entries cannot be freed until kobject_del() is called for all entries.
7162306a36Sopenharmony_ci * So we do not need to do anything here, but still need this no-op release
7262306a36Sopenharmony_ci * operation to avoid complaints from the kobject code.
7362306a36Sopenharmony_ci */
7462306a36Sopenharmony_cistatic void blk_ia_range_sysfs_nop_release(struct kobject *kobj)
7562306a36Sopenharmony_ci{
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic const struct kobj_type blk_ia_range_ktype = {
7962306a36Sopenharmony_ci	.sysfs_ops	= &blk_ia_range_sysfs_ops,
8062306a36Sopenharmony_ci	.default_groups	= blk_ia_range_groups,
8162306a36Sopenharmony_ci	.release	= blk_ia_range_sysfs_nop_release,
8262306a36Sopenharmony_ci};
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci/*
8562306a36Sopenharmony_ci * This will be executed only after all independent access range entries are
8662306a36Sopenharmony_ci * removed with kobject_del(), at which point, it is safe to free everything,
8762306a36Sopenharmony_ci * including the array of ranges.
8862306a36Sopenharmony_ci */
8962306a36Sopenharmony_cistatic void blk_ia_ranges_sysfs_release(struct kobject *kobj)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct blk_independent_access_ranges *iars =
9262306a36Sopenharmony_ci		container_of(kobj, struct blk_independent_access_ranges, kobj);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	kfree(iars);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic const struct kobj_type blk_ia_ranges_ktype = {
9862306a36Sopenharmony_ci	.release	= blk_ia_ranges_sysfs_release,
9962306a36Sopenharmony_ci};
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci/**
10262306a36Sopenharmony_ci * disk_register_independent_access_ranges - register with sysfs a set of
10362306a36Sopenharmony_ci *		independent access ranges
10462306a36Sopenharmony_ci * @disk:	Target disk
10562306a36Sopenharmony_ci *
10662306a36Sopenharmony_ci * Register with sysfs a set of independent access ranges for @disk.
10762306a36Sopenharmony_ci */
10862306a36Sopenharmony_ciint disk_register_independent_access_ranges(struct gendisk *disk)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct blk_independent_access_ranges *iars = disk->ia_ranges;
11162306a36Sopenharmony_ci	struct request_queue *q = disk->queue;
11262306a36Sopenharmony_ci	int i, ret;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	lockdep_assert_held(&q->sysfs_dir_lock);
11562306a36Sopenharmony_ci	lockdep_assert_held(&q->sysfs_lock);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	if (!iars)
11862306a36Sopenharmony_ci		return 0;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/*
12162306a36Sopenharmony_ci	 * At this point, iars is the new set of sector access ranges that needs
12262306a36Sopenharmony_ci	 * to be registered with sysfs.
12362306a36Sopenharmony_ci	 */
12462306a36Sopenharmony_ci	WARN_ON(iars->sysfs_registered);
12562306a36Sopenharmony_ci	ret = kobject_init_and_add(&iars->kobj, &blk_ia_ranges_ktype,
12662306a36Sopenharmony_ci				   &disk->queue_kobj, "%s",
12762306a36Sopenharmony_ci				   "independent_access_ranges");
12862306a36Sopenharmony_ci	if (ret) {
12962306a36Sopenharmony_ci		disk->ia_ranges = NULL;
13062306a36Sopenharmony_ci		kobject_put(&iars->kobj);
13162306a36Sopenharmony_ci		return ret;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	for (i = 0; i < iars->nr_ia_ranges; i++) {
13562306a36Sopenharmony_ci		ret = kobject_init_and_add(&iars->ia_range[i].kobj,
13662306a36Sopenharmony_ci					   &blk_ia_range_ktype, &iars->kobj,
13762306a36Sopenharmony_ci					   "%d", i);
13862306a36Sopenharmony_ci		if (ret) {
13962306a36Sopenharmony_ci			while (--i >= 0)
14062306a36Sopenharmony_ci				kobject_del(&iars->ia_range[i].kobj);
14162306a36Sopenharmony_ci			kobject_del(&iars->kobj);
14262306a36Sopenharmony_ci			kobject_put(&iars->kobj);
14362306a36Sopenharmony_ci			return ret;
14462306a36Sopenharmony_ci		}
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	iars->sysfs_registered = true;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	return 0;
15062306a36Sopenharmony_ci}
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_civoid disk_unregister_independent_access_ranges(struct gendisk *disk)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct request_queue *q = disk->queue;
15562306a36Sopenharmony_ci	struct blk_independent_access_ranges *iars = disk->ia_ranges;
15662306a36Sopenharmony_ci	int i;
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	lockdep_assert_held(&q->sysfs_dir_lock);
15962306a36Sopenharmony_ci	lockdep_assert_held(&q->sysfs_lock);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	if (!iars)
16262306a36Sopenharmony_ci		return;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	if (iars->sysfs_registered) {
16562306a36Sopenharmony_ci		for (i = 0; i < iars->nr_ia_ranges; i++)
16662306a36Sopenharmony_ci			kobject_del(&iars->ia_range[i].kobj);
16762306a36Sopenharmony_ci		kobject_del(&iars->kobj);
16862306a36Sopenharmony_ci		kobject_put(&iars->kobj);
16962306a36Sopenharmony_ci	} else {
17062306a36Sopenharmony_ci		kfree(iars);
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	disk->ia_ranges = NULL;
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cistatic struct blk_independent_access_range *
17762306a36Sopenharmony_cidisk_find_ia_range(struct blk_independent_access_ranges *iars,
17862306a36Sopenharmony_ci		  sector_t sector)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	struct blk_independent_access_range *iar;
18162306a36Sopenharmony_ci	int i;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	for (i = 0; i < iars->nr_ia_ranges; i++) {
18462306a36Sopenharmony_ci		iar = &iars->ia_range[i];
18562306a36Sopenharmony_ci		if (sector >= iar->sector &&
18662306a36Sopenharmony_ci		    sector < iar->sector + iar->nr_sectors)
18762306a36Sopenharmony_ci			return iar;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	return NULL;
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic bool disk_check_ia_ranges(struct gendisk *disk,
19462306a36Sopenharmony_ci				struct blk_independent_access_ranges *iars)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci	struct blk_independent_access_range *iar, *tmp;
19762306a36Sopenharmony_ci	sector_t capacity = get_capacity(disk);
19862306a36Sopenharmony_ci	sector_t sector = 0;
19962306a36Sopenharmony_ci	int i;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	if (WARN_ON_ONCE(!iars->nr_ia_ranges))
20262306a36Sopenharmony_ci		return false;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	/*
20562306a36Sopenharmony_ci	 * While sorting the ranges in increasing LBA order, check that the
20662306a36Sopenharmony_ci	 * ranges do not overlap, that there are no sector holes and that all
20762306a36Sopenharmony_ci	 * sectors belong to one range.
20862306a36Sopenharmony_ci	 */
20962306a36Sopenharmony_ci	for (i = 0; i < iars->nr_ia_ranges; i++) {
21062306a36Sopenharmony_ci		tmp = disk_find_ia_range(iars, sector);
21162306a36Sopenharmony_ci		if (!tmp || tmp->sector != sector) {
21262306a36Sopenharmony_ci			pr_warn("Invalid non-contiguous independent access ranges\n");
21362306a36Sopenharmony_ci			return false;
21462306a36Sopenharmony_ci		}
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci		iar = &iars->ia_range[i];
21762306a36Sopenharmony_ci		if (tmp != iar) {
21862306a36Sopenharmony_ci			swap(iar->sector, tmp->sector);
21962306a36Sopenharmony_ci			swap(iar->nr_sectors, tmp->nr_sectors);
22062306a36Sopenharmony_ci		}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		sector += iar->nr_sectors;
22362306a36Sopenharmony_ci	}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (sector != capacity) {
22662306a36Sopenharmony_ci		pr_warn("Independent access ranges do not match disk capacity\n");
22762306a36Sopenharmony_ci		return false;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	return true;
23162306a36Sopenharmony_ci}
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_cistatic bool disk_ia_ranges_changed(struct gendisk *disk,
23462306a36Sopenharmony_ci				   struct blk_independent_access_ranges *new)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	struct blk_independent_access_ranges *old = disk->ia_ranges;
23762306a36Sopenharmony_ci	int i;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (!old)
24062306a36Sopenharmony_ci		return true;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (old->nr_ia_ranges != new->nr_ia_ranges)
24362306a36Sopenharmony_ci		return true;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	for (i = 0; i < old->nr_ia_ranges; i++) {
24662306a36Sopenharmony_ci		if (new->ia_range[i].sector != old->ia_range[i].sector ||
24762306a36Sopenharmony_ci		    new->ia_range[i].nr_sectors != old->ia_range[i].nr_sectors)
24862306a36Sopenharmony_ci			return true;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	return false;
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci/**
25562306a36Sopenharmony_ci * disk_alloc_independent_access_ranges - Allocate an independent access ranges
25662306a36Sopenharmony_ci *                                        data structure
25762306a36Sopenharmony_ci * @disk:		target disk
25862306a36Sopenharmony_ci * @nr_ia_ranges:	Number of independent access ranges
25962306a36Sopenharmony_ci *
26062306a36Sopenharmony_ci * Allocate a struct blk_independent_access_ranges structure with @nr_ia_ranges
26162306a36Sopenharmony_ci * access range descriptors.
26262306a36Sopenharmony_ci */
26362306a36Sopenharmony_cistruct blk_independent_access_ranges *
26462306a36Sopenharmony_cidisk_alloc_independent_access_ranges(struct gendisk *disk, int nr_ia_ranges)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	struct blk_independent_access_ranges *iars;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	iars = kzalloc_node(struct_size(iars, ia_range, nr_ia_ranges),
26962306a36Sopenharmony_ci			    GFP_KERNEL, disk->queue->node);
27062306a36Sopenharmony_ci	if (iars)
27162306a36Sopenharmony_ci		iars->nr_ia_ranges = nr_ia_ranges;
27262306a36Sopenharmony_ci	return iars;
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(disk_alloc_independent_access_ranges);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci/**
27762306a36Sopenharmony_ci * disk_set_independent_access_ranges - Set a disk independent access ranges
27862306a36Sopenharmony_ci * @disk:	target disk
27962306a36Sopenharmony_ci * @iars:	independent access ranges structure
28062306a36Sopenharmony_ci *
28162306a36Sopenharmony_ci * Set the independent access ranges information of the request queue
28262306a36Sopenharmony_ci * of @disk to @iars. If @iars is NULL and the independent access ranges
28362306a36Sopenharmony_ci * structure already set is cleared. If there are no differences between
28462306a36Sopenharmony_ci * @iars and the independent access ranges structure already set, @iars
28562306a36Sopenharmony_ci * is freed.
28662306a36Sopenharmony_ci */
28762306a36Sopenharmony_civoid disk_set_independent_access_ranges(struct gendisk *disk,
28862306a36Sopenharmony_ci				struct blk_independent_access_ranges *iars)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	struct request_queue *q = disk->queue;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	mutex_lock(&q->sysfs_dir_lock);
29362306a36Sopenharmony_ci	mutex_lock(&q->sysfs_lock);
29462306a36Sopenharmony_ci	if (iars && !disk_check_ia_ranges(disk, iars)) {
29562306a36Sopenharmony_ci		kfree(iars);
29662306a36Sopenharmony_ci		iars = NULL;
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci	if (iars && !disk_ia_ranges_changed(disk, iars)) {
29962306a36Sopenharmony_ci		kfree(iars);
30062306a36Sopenharmony_ci		goto unlock;
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	/*
30462306a36Sopenharmony_ci	 * This may be called for a registered queue. E.g. during a device
30562306a36Sopenharmony_ci	 * revalidation. If that is the case, we need to unregister the old
30662306a36Sopenharmony_ci	 * set of independent access ranges and register the new set. If the
30762306a36Sopenharmony_ci	 * queue is not registered, registration of the device request queue
30862306a36Sopenharmony_ci	 * will register the independent access ranges.
30962306a36Sopenharmony_ci	 */
31062306a36Sopenharmony_ci	disk_unregister_independent_access_ranges(disk);
31162306a36Sopenharmony_ci	disk->ia_ranges = iars;
31262306a36Sopenharmony_ci	if (blk_queue_registered(q))
31362306a36Sopenharmony_ci		disk_register_independent_access_ranges(disk);
31462306a36Sopenharmony_ciunlock:
31562306a36Sopenharmony_ci	mutex_unlock(&q->sysfs_lock);
31662306a36Sopenharmony_ci	mutex_unlock(&q->sysfs_dir_lock);
31762306a36Sopenharmony_ci}
31862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(disk_set_independent_access_ranges);
319