From 30ce6652eed121c64ed0df8db3ca9d9d0167eaae Mon Sep 17 00:00:00 2001
From: Pankaj Raghav
Date: Fri, 23 Sep 2022 19:36:08 +0200
Subject: [PATCH] ANDROID: block: Support npo2 zone sizes
Checking if a given sector is aligned to a zone is a common operation
that is performed for zoned devices. Add bdev_is_zone_start helper to
check for this instead of opencoding it everywhere.
Convert the calculations on zone size to be generic instead of relying on
power-of-2(po2) based arithmetic in the block layer using the helpers
wherever possible.
The only hot path affected by this change for zoned devices with po2
zone size is in blk_check_zone_append() but bdev_is_zone_start() helper
is used to optimize the calculation for po2 zone sizes.
Finally, allow zoned devices with non po2 zone sizes provided that their
zone capacity and zone size are equal. The main motivation to allow
zoned devices with non po2 zone size is to remove the unmapped LBA
between zone capacity and zone size for devices that cannot have a po2
zone capacity.
Signed-off-by: Pankaj Raghav
Bug: 269471019
Bug: 415836627
Link: https://lore.kernel.org/linux-block/20220923173618.6899-4-p.raghav@samsung.com/
Change-Id: I2ecc186d7b14f5508b6abfe9821526d39a21d7e4
[ bvanassche: ported this patch to kernel 6.12 ]
Signed-off-by: Bart Van Assche
---
block/blk-zoned.c | 26 ++++++++++++++++++++++----
include/linux/blkdev.h | 18 ++++++++++++++++--
2 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index dfe39222c935..33eeed0fdadb 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -1673,6 +1673,25 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return -ENODEV;
}
+ if (zone->start == 0) {
+ if (zone->len == 0) {
+ pr_warn("%s: Invalid zero zone size", disk->disk_name);
+ return -ENODEV;
+ }
+
+ /*
+ * Non power-of-2 zone size support was added to remove the gap
+ * between zone capacity and zone size. Though it is technically
+ * possible to have gaps in a non power-of-2 device, Linux
+ * requires the zone size to be equal to zone capacity for non
+ * power-of-2 zoned devices.
+ */
+ if (!is_power_of_2(zone->len) && zone->capacity < zone->len) {
+ pr_err("%s: Invalid zone capacity %lld with non power-of-2 zone size %lld",
+ disk->disk_name, zone->capacity, zone->len);
+ return -ENODEV;
+ }
+ }
/*
* All zones must have the same size, with the exception on an eventual
* smaller last zone.
@@ -1748,9 +1767,8 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
* Checks that the device driver indicated a valid zone size and that
* the max zone append limit is set.
*/
- if (!zone_sectors || !is_power_of_2(zone_sectors)) {
- pr_warn("%s: Invalid non power of two zone size (%llu)\n",
- disk->disk_name, zone_sectors);
+ if (!zone_sectors) {
+ pr_warn("%s: Invalid zone size\n", disk->disk_name);
return -ENODEV;
}
@@ -1765,7 +1783,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
* GFP_NOIO was specified.
*/
args.disk = disk;
- args.nr_zones = (capacity + zone_sectors - 1) >> ilog2(zone_sectors);
+ args.nr_zones = div64_u64(capacity + zone_sectors - 1, zone_sectors);
noio_flag = memalloc_noio_save();
ret = disk_revalidate_zone_resources(disk, args.nr_zones);
if (ret) {
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 1aad2ee18f4e..ee99ca51539e 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -723,9 +723,13 @@ static inline bool blk_zone_plug_bio(struct bio *bio, unsigned int nr_segs)
static inline unsigned int disk_zone_no(struct gendisk *disk, sector_t sector)
{
+ const sector_t zone_sectors = disk->queue->limits.chunk_sectors;
+
if (!blk_queue_is_zoned(disk->queue))
return 0;
- return sector >> ilog2(disk->queue->limits.chunk_sectors);
+ if (is_power_of_2(zone_sectors))
+ return sector >> ilog2(zone_sectors);
+ return div64_u64(sector, zone_sectors);
}
static inline unsigned int bdev_nr_zones(struct block_device *bdev)
@@ -1404,7 +1408,17 @@ static inline sector_t bdev_zone_sectors(struct block_device *bdev)
static inline sector_t bdev_offset_from_zone_start(struct block_device *bdev,
sector_t sector)
{
- return sector & (bdev_zone_sectors(bdev) - 1);
+ sector_t zone_sectors = bdev_zone_sectors(bdev);
+ u64 remainder = 0;
+
+ if (!bdev_is_zoned(bdev))
+ return 0;
+
+ if (is_power_of_2(zone_sectors))
+ return sector & (zone_sectors - 1);
+
+ div64_u64_rem(sector, zone_sectors, &remainder);
+ return remainder;
}
static inline sector_t bio_offset_from_zone_start(struct bio *bio)