diff --git a/block/partition-generic.c b/block/partition-generic.c index 71d9ed9df8daea..d7beb6bbbf668f 100644 --- a/block/partition-generic.c +++ b/block/partition-generic.c @@ -430,6 +430,56 @@ static int drop_partitions(struct gendisk *disk, struct block_device *bdev) return 0; } +static bool part_zone_aligned(struct gendisk *disk, + struct block_device *bdev, + sector_t from, sector_t size) +{ + unsigned int zone_size = bdev_zone_size(bdev); + + /* + * If this function is called, then the disk is a zoned block device + * (host-aware or host-managed). This can be detected even if the + * zoned block device support is disabled (CONFIG_BLK_DEV_ZONED not + * set). In this case, however, only host-aware devices will be seen + * as a block device is not created for host-managed devices. Without + * zoned block device support, host-aware drives can still be used as + * regular block devices (no zone operation) and their zone size will + * be reported as 0. Allow this case. + */ + if (!zone_size) + return true; + + /* + * Check partition start and size alignement. If the drive has a + * smaller last runt zone, ignore it and allow the partition to + * use it. Check the zone size too: it should be a power of 2 number + * of sectors. + */ + if (WARN_ON_ONCE(!is_power_of_2(zone_size))) { + u32 rem; + + div_u64_rem(from, zone_size, &rem); + if (rem) + return false; + if ((from + size) < get_capacity(disk)) { + div_u64_rem(size, zone_size, &rem); + if (rem) + return false; + } + + } else { + + if (from & (zone_size - 1)) + return false; + if ((from + size) < get_capacity(disk) && + (size & (zone_size - 1))) + return false; + + } + + return true; +} + int rescan_partitions(struct gendisk *disk, struct block_device *bdev) { struct parsed_partitions *state = NULL; @@ -529,6 +579,21 @@ int rescan_partitions(struct gendisk *disk, struct block_device *bdev) } } + /* + * On a zoned block device, partitions should be aligned on the + * device zone size (i.e. zone boundary crossing not allowed). + * Otherwise, resetting the write pointer of the last zone of + * one partition may impact the following partition. + */ + if (bdev_is_zoned(bdev) && + !part_zone_aligned(disk, bdev, from, size)) { + printk(KERN_WARNING + "%s: p%d start %llu+%llu is not zone aligned\n", + disk->disk_name, p, (unsigned long long) from, + (unsigned long long) size); + continue; + } + part = add_partition(disk, p, from, size, state->parts[p].flags, &state->parts[p].info);