* [PATCH v2 00/13] Improve handling of offline and read-only zones
@ 2026-08-06 16:04 Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 01/13] block: remove disk_free_zone_resources() Damien Le Moal
` (13 more replies)
0 siblings, 14 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Jens,
This patch series refactor some zone related code and improves the
caching iof zone information and the management of zone write plugs in
preparation for fully supporting storage element depopulation.
Most of the functional changes here are around the management of zone
write plugs to better handle read-only and offline zones as these
conditions can become more common with storage element being depopulated.
Changes from v1:
- Fixup error path change in patch 1
- Use WARN_ON_ONCE in patch 4
- Dropped patch 14
- Applied review tags
Damien Le Moal (13):
block: remove disk_free_zone_resources()
block: refactor disk_revalidate_zone_resources()
block: refactor disk_update_zone_resources()
block: remember a zone type regardless of its condition
block: refactor bdev_zone_is_seq()
block: introduce disk_for_all_zone_wplugs()
block: drop all zone write plugs on capacity changes
block: propagate readonly and offline conditions to zone write plugs
block: always treat offline and read-only zones as dead
block: fail zone management operations to read-only and offline zones
block: allow read-only and offline conventional zones
block: simplify disk_zone_set_cond()
block: flag zoned disks with GENHD_FL_NO_PART
block/blk-core.c | 7 +-
block/blk-zoned.c | 778 +++++++++++++++++++++++++----------------
block/blk.h | 6 +
block/genhd.c | 7 +
include/linux/blkdev.h | 2 +-
5 files changed, 486 insertions(+), 314 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v2 01/13] block: remove disk_free_zone_resources()
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 02/13] block: refactor disk_revalidate_zone_resources() Damien Le Moal
` (12 subsequent siblings)
13 siblings, 0 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
In the rare event when revalidating the zones of a zoned block device
fails, the device capacity will be dropped to 0. In such case, the zoned
block device will either be rescanned and restored or dropped entirely and
its gendisk destroyed. So calling disk_free_zone_resources() from
blk_revalidate_disk_zones() in case of error does not make much sense. We
can keep the zone resources in case the device is rescanned and restored
or simply free all resources in disk_release_zone_resources() when the
gendick is destroyed.
Remove the call to disk_free_zone_resources() from
blk_revalidate_disk_zones() and squash disk_free_zone_resources() inside
disk_release_zone_resources(). With this change, the conditional creation
of the zone write plugs work queue is not necessary anymore as the
workqueue will keep existing together with all other resources until the
disk is released. This simplifies disk_alloc_zone_resources().
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Hannes Reinecke <hare@suse.de>
---
block/blk-zoned.c | 56 +++++++++++++++++------------------------------
1 file changed, 20 insertions(+), 36 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index a5afb842bf35..b47e32bf4602 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -1893,21 +1893,6 @@ static int disk_alloc_zone_resources(struct gendisk *disk,
if (!disk->zone_wplugs_pool)
goto free_hash;
- /*
- * We may already have a zone write plug workqueue as this function may
- * be called after disk_free_zone_resources(), which does not destroy
- * the workqueue (the zone write plugs workqueue is destroyed at
- * disk_release() time).
- */
- if (!disk->zone_wplugs_wq) {
- disk->zone_wplugs_wq =
- alloc_workqueue("%s_zwplugs",
- WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,
- pool_size, disk->disk_name);
- if (!disk->zone_wplugs_wq)
- goto destroy_pool;
- }
-
disk->zone_wplugs_worker =
kthread_create(disk_zone_wplugs_worker, disk,
"%s_zwplugs_worker", disk->disk_name);
@@ -1918,8 +1903,18 @@ static int disk_alloc_zone_resources(struct gendisk *disk,
}
wake_up_process(disk->zone_wplugs_worker);
+ disk->zone_wplugs_wq =
+ alloc_workqueue("%s_zwplugs",
+ WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,
+ pool_size, disk->disk_name);
+ if (!disk->zone_wplugs_wq)
+ goto stop_worker;
+
return 0;
+stop_worker:
+ kthread_stop(disk->zone_wplugs_worker);
+ disk->zone_wplugs_worker = NULL;
destroy_pool:
mempool_destroy(disk->zone_wplugs_pool);
disk->zone_wplugs_pool = NULL;
@@ -1975,7 +1970,7 @@ static void disk_set_zones_cond_array(struct gendisk *disk, u8 *zones_cond)
kfree_rcu_mightsleep(zones_cond);
}
-static void disk_free_zone_resources(struct gendisk *disk)
+void disk_release_zone_resources(struct gendisk *disk)
{
if (disk->zone_wplugs_worker) {
kthread_stop(disk->zone_wplugs_worker);
@@ -1983,8 +1978,10 @@ static void disk_free_zone_resources(struct gendisk *disk)
}
WARN_ON_ONCE(!list_empty(&disk->zone_wplugs_list));
- if (disk->zone_wplugs_wq)
- drain_workqueue(disk->zone_wplugs_wq);
+ if (disk->zone_wplugs_wq) {
+ destroy_workqueue(disk->zone_wplugs_wq);
+ disk->zone_wplugs_wq = NULL;
+ }
disk_destroy_zone_wplugs_hash_table(disk);
@@ -1994,16 +1991,6 @@ static void disk_free_zone_resources(struct gendisk *disk)
disk->nr_zones = 0;
}
-void disk_release_zone_resources(struct gendisk *disk)
-{
- if (disk->zone_wplugs_wq) {
- destroy_workqueue(disk->zone_wplugs_wq);
- disk->zone_wplugs_wq = NULL;
- }
-
- disk_free_zone_resources(disk);
-}
-
struct blk_revalidate_zone_args {
struct gendisk *disk;
u8 *zones_cond;
@@ -2317,11 +2304,11 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
sector_t zone_sectors = q->limits.chunk_sectors;
sector_t capacity = get_capacity(disk);
struct blk_revalidate_zone_args args = { };
- unsigned int memflags, noio_flag;
struct blk_report_zones_args rep_args = {
.cb = blk_revalidate_zone_cb,
.data = &args,
};
+ unsigned int noio_flag;
int ret = -ENOMEM;
if (WARN_ON_ONCE(!blk_queue_is_zoned(q)))
@@ -2359,7 +2346,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
memalloc_noio_restore(noio_flag);
if (ret <= 0)
- goto free_resources;
+ goto free_args;
/*
* If zones where reported, make sure that the entire disk capacity
@@ -2369,22 +2356,19 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
pr_warn("%s: Missing zones from sector %llu\n",
disk->disk_name, args.sector);
ret = -ENODEV;
- goto free_resources;
+ goto free_args;
}
ret = disk_update_zone_resources(disk, &args);
if (ret)
- goto free_resources;
+ goto free_args;
return 0;
-free_resources:
+free_args:
pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
kfree(args.zones_cond);
- memflags = blk_mq_freeze_queue(q);
- disk_free_zone_resources(disk);
- blk_mq_unfreeze_queue(q, memflags);
return ret;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 02/13] block: refactor disk_revalidate_zone_resources()
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 01/13] block: remove disk_free_zone_resources() Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 03/13] block: refactor disk_update_zone_resources() Damien Le Moal
` (11 subsequent siblings)
13 siblings, 0 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
The function disk_revalidate_zone_resources() is misnamed as it does not
revalidate anything but rather allocates the revalidation arguments and
then calls disk_alloc_zone_resources() to initialize the disk zone
resources if they are needed and not already allocated.
Make this function less confusing by renaming it
disk_init_revalidate_args() and moving the call to
disk_alloc_zone_resources() into blk_revalidate_disk_zones(). As before,
this function is only called if the zone resources are needed and not yet
allocated.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Hannes Reinecke <hare@suse.de>
---
block/blk-zoned.c | 69 ++++++++++++++++++++++++-----------------------
1 file changed, 36 insertions(+), 33 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index b47e32bf4602..0580f0bc333c 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -1854,12 +1854,20 @@ static int disk_zone_wplugs_worker(void *data)
void disk_init_zone_resources(struct gendisk *disk)
{
+ atomic_set(&disk->nr_zone_wplugs, 0);
spin_lock_init(&disk->zone_wplugs_hash_lock);
spin_lock_init(&disk->zone_wplugs_list_lock);
INIT_LIST_HEAD(&disk->zone_wplugs_list);
init_completion(&disk->zone_wplugs_worker_bio_done);
}
+static unsigned int disk_get_nr_zones(struct gendisk *disk)
+{
+ struct queue_limits *lim = &disk->queue->limits;
+
+ return DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors);
+}
+
/*
* For the size of a disk zone write plug hash table, use the size of the
* zone write plug mempool, which is the maximum of the disk open zones and
@@ -1869,13 +1877,21 @@ void disk_init_zone_resources(struct gendisk *disk)
#define BLK_ZONE_WPLUG_MAX_HASH_BITS 9
#define BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE 128
-static int disk_alloc_zone_resources(struct gendisk *disk,
- unsigned int pool_size)
+static int disk_alloc_zone_resources(struct gendisk *disk)
{
- unsigned int i;
+ struct queue_limits *lim = &disk->queue->limits;
+ unsigned int nr_zones = disk_get_nr_zones(disk);
+ unsigned int pool_size, i;
int ret = -ENOMEM;
- atomic_set(&disk->nr_zone_wplugs, 0);
+ /*
+ * If the device has no limit on the maximum number of open and active
+ * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.
+ */
+ pool_size = max(lim->max_open_zones, lim->max_active_zones);
+ if (!pool_size)
+ pool_size = min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, nr_zones);
+
disk->zone_wplugs_hash_bits =
min(ilog2(pool_size) + 1, BLK_ZONE_WPLUG_MAX_HASH_BITS);
@@ -2001,41 +2017,18 @@ struct blk_revalidate_zone_args {
sector_t sector;
};
-static int disk_revalidate_zone_resources(struct gendisk *disk,
+static int disk_init_revalidate_args(struct gendisk *disk,
struct blk_revalidate_zone_args *args)
{
- struct queue_limits *lim = &disk->queue->limits;
- unsigned int pool_size;
- int ret = 0;
-
args->disk = disk;
- args->nr_zones =
- DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors);
+ args->nr_zones = disk_get_nr_zones(disk);
/* Cached zone conditions: 1 byte per zone */
args->zones_cond = kzalloc(args->nr_zones, GFP_NOIO);
if (!args->zones_cond)
return -ENOMEM;
- if (!disk_need_zone_resources(disk))
- return 0;
-
- /*
- * If the device has no limit on the maximum number of open and active
- * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.
- */
- pool_size = max(lim->max_open_zones, lim->max_active_zones);
- if (!pool_size)
- pool_size =
- min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, args->nr_zones);
-
- if (!disk->zone_wplugs_hash) {
- ret = disk_alloc_zone_resources(disk, pool_size);
- if (ret)
- kfree(args->zones_cond);
- }
-
- return ret;
+ return 0;
}
/*
@@ -2328,11 +2321,21 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
}
/*
- * Ensure that all memory allocations in this context are done as if
- * GFP_NOIO was specified.
+ * Allocate zone resources if they are needed and we have not done
+ * so yet, and initialize the revalidation arguments passed to report
+ * zones. Ensure that all memory allocations in this context are done as
+ * if GFP_NOIO was specified.
*/
noio_flag = memalloc_noio_save();
- ret = disk_revalidate_zone_resources(disk, &args);
+ if (disk_need_zone_resources(disk) && !disk->zone_wplugs_hash) {
+ ret = disk_alloc_zone_resources(disk);
+ if (ret) {
+ memalloc_noio_restore(noio_flag);
+ return ret;
+ }
+ }
+
+ ret = disk_init_revalidate_args(disk, &args);
if (ret) {
memalloc_noio_restore(noio_flag);
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 03/13] block: refactor disk_update_zone_resources()
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 01/13] block: remove disk_free_zone_resources() Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 02/13] block: refactor disk_revalidate_zone_resources() Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 9:03 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 04/13] block: remember a zone type regardless of its condition Damien Le Moal
` (10 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Since the function disk_update_zone_resources() does a lot of checks
beside updating a zoned disk limits and resources, rename this function
to disk_revalidate_zone_resources(). To keep all checks together, move
the capacity check in blk_revalidate_disk_zones() to this function and
simplify it by moving the check on the number of zones before the call to
queue_limits_start_update().
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 46 +++++++++++++++++++---------------------------
1 file changed, 19 insertions(+), 27 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 0580f0bc333c..b6970e635671 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -2032,11 +2032,11 @@ static int disk_init_revalidate_args(struct gendisk *disk,
}
/*
- * Update the disk zone resources information and device queue limits.
- * The disk queue is frozen when this is executed.
+ * Revalidate and update the disk zone resources information and device queue
+ * limits.
*/
-static int disk_update_zone_resources(struct gendisk *disk,
- struct blk_revalidate_zone_args *args)
+static int disk_revalidate_zone_resources(struct gendisk *disk,
+ struct blk_revalidate_zone_args *args)
{
struct request_queue *q = disk->queue;
unsigned int nr_seq_zones;
@@ -2044,19 +2044,24 @@ static int disk_update_zone_resources(struct gendisk *disk,
struct queue_limits lim;
int ret = 0;
+ /* Make sure that the entire disk capacity has been checked. */
+ if (args->sector != get_capacity(disk)) {
+ pr_warn("%s: Missing zones from sector %llu\n",
+ disk->disk_name, args->sector);
+ return -ENODEV;
+ }
+
+ if (args->nr_conv_zones >= args->nr_zones) {
+ pr_warn("%s: Invalid number of conventional zones %u / %u\n",
+ disk->disk_name, args->nr_conv_zones, args->nr_zones);
+ return -ENODEV;
+ }
+
lim = queue_limits_start_update(q);
memflags = blk_mq_freeze_queue(q);
disk->nr_zones = args->nr_zones;
- if (args->nr_conv_zones >= disk->nr_zones) {
- queue_limits_cancel_update(q);
- pr_warn("%s: Invalid number of conventional zones %u / %u\n",
- disk->disk_name, args->nr_conv_zones, disk->nr_zones);
- ret = -ENODEV;
- goto unfreeze;
- }
-
disk->zone_capacity = args->zone_capacity;
disk->last_zone_capacity = args->last_zone_capacity;
disk_set_zones_cond_array(disk, args->zones_cond);
@@ -2100,7 +2105,6 @@ static int disk_update_zone_resources(struct gendisk *disk,
commit:
ret = queue_limits_commit_update(q, &lim);
-unfreeze:
blk_mq_unfreeze_queue(q, memflags);
return ret;
@@ -2295,7 +2299,6 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
{
struct request_queue *q = disk->queue;
sector_t zone_sectors = q->limits.chunk_sectors;
- sector_t capacity = get_capacity(disk);
struct blk_revalidate_zone_args args = { };
struct blk_report_zones_args rep_args = {
.cb = blk_revalidate_zone_cb,
@@ -2307,7 +2310,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
if (WARN_ON_ONCE(!blk_queue_is_zoned(q)))
return -EIO;
- if (!capacity)
+ if (!get_capacity(disk))
return -ENODEV;
/*
@@ -2351,18 +2354,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
if (ret <= 0)
goto free_args;
- /*
- * If zones where reported, make sure that the entire disk capacity
- * has been checked.
- */
- if (args.sector != capacity) {
- pr_warn("%s: Missing zones from sector %llu\n",
- disk->disk_name, args.sector);
- ret = -ENODEV;
- goto free_args;
- }
-
- ret = disk_update_zone_resources(disk, &args);
+ ret = disk_revalidate_zone_resources(disk, &args);
if (ret)
goto free_args;
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 04/13] block: remember a zone type regardless of its condition
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (2 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 03/13] block: refactor disk_update_zone_resources() Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 9:11 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 05/13] block: refactor bdev_zone_is_seq() Damien Le Moal
` (9 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Currently, bdev_zone_is_seq() identifies a sequential zone by looking at
the zone condition, assuming that any zone that does not have the
condition BLK_ZONE_COND_NOT_WP is a sequential write required zone. That
is correct only as long as the target zoned device does not support
storage element depopulation (aka HDD head depopulation), which is a
feature that can transition conventional zones to the read-only or offline
condition. For such device, we cannot distinguish anymore between
conventional and sequential zones using the zone condition as both zone
types can have the same conditions.
Prepare for fully supporting storage element depopulation and restoration
by caching the type of a zone in addition to its condition. This is
implemented by reformating the zones_cond array using a more compact zone
condition representation with the new enum blk_zstate. This zone condition
representation only uses the lower 4 bits of a byte for the condition
values, thus leaving the high order 4 bits of each byte entry of the
array for flags. The flag BLK_ZFLAG_CONV is defined to indicate
conventional zones.
The helper functions blk_zstate_to_zone_cond() and
blk_zone_cond_to_zstate() are implemented using lookup tables to convert
between enum blk_zone_condition values and enum blk_zstate zone condition
values. The helper blk_zstate_set() can be used to prepare a zones_state
entry for a disk with a zone condition and zone type flags. This helper is
used in disk_zone_set_cond() to update a zone condition.
bdev_zone_is_seq() is modified to use the flags of the zones_state array
entries to identify sequential zones. The zones_state array initialization
and revalidation is unchanged from the former zones_cond array.
Overall, any zone condition that is being considered outside of the
zones_state array always uses the enum blk_zone_condition values as
before, thus minimizing the number of changes.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 202 +++++++++++++++++++++++++++++------------
include/linux/blkdev.h | 2 +-
2 files changed, 143 insertions(+), 61 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index b6970e635671..50b98c394b50 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -39,6 +39,103 @@ static const char *const zone_cond_name[] = {
};
#undef ZONE_COND_NAME
+/*
+ * Internal and compact representation of enum blk_zone_cond values for zone
+ * conditions. All these values fit into 4-bits, allowing using the high order
+ * bits as the zone type.
+ */
+enum blk_zstate {
+ BLK_ZSTATE_NOT_WP = 0x00,
+ BLK_ZSTATE_EMPTY = 0x01,
+ BLK_ZSTATE_IMP_OPEN = 0x02,
+ BLK_ZSTATE_EXP_OPEN = 0x03,
+ BLK_ZSTATE_CLOSED = 0x04,
+ BLK_ZSTATE_READONLY = 0x05,
+ BLK_ZSTATE_FULL = 0x06,
+ BLK_ZSTATE_OFFLINE = 0x07,
+ BLK_ZSTATE_ACTIVE = 0x08,
+
+ BLK_ZSTATE_COND_MASK = 0x0F,
+
+ /* Conventional zone. */
+ BLK_ZFLAG_CONV = 0x80,
+ BLK_ZSTATE_FLAGS_MASK = ~BLK_ZSTATE_COND_MASK,
+};
+
+/*
+ * Lookup table and helper to convert enum blk_zstate conditions into enum
+ * blk_zone_condition values.
+ */
+static const u8 blk_zstate2zcond[] = {
+ [BLK_ZSTATE_NOT_WP] = BLK_ZONE_COND_NOT_WP,
+ [BLK_ZSTATE_EMPTY] = BLK_ZONE_COND_EMPTY,
+ [BLK_ZSTATE_IMP_OPEN] = BLK_ZONE_COND_IMP_OPEN,
+ [BLK_ZSTATE_EXP_OPEN] = BLK_ZONE_COND_EXP_OPEN,
+ [BLK_ZSTATE_CLOSED] = BLK_ZONE_COND_CLOSED,
+ [BLK_ZSTATE_READONLY] = BLK_ZONE_COND_READONLY,
+ [BLK_ZSTATE_FULL] = BLK_ZONE_COND_FULL,
+ [BLK_ZSTATE_OFFLINE] = BLK_ZONE_COND_OFFLINE,
+ [BLK_ZSTATE_ACTIVE] = BLK_ZONE_COND_ACTIVE,
+};
+
+static inline enum blk_zone_cond blk_zstate_to_zone_cond(enum blk_zstate zs)
+{
+ u8 idx = zs & BLK_ZSTATE_COND_MASK;
+
+ if (WARN_ON_ONCE(idx >= ARRAY_SIZE(blk_zstate2zcond)))
+ return 0;
+
+ return blk_zstate2zcond[idx];
+}
+
+/*
+ * Lookup table and helper to convert an enum blk_zone_condition into an enum
+ * blk_zstate condition value. To keep the lookup table small, the
+ * BLK_ZONE_COND_ACTIVE condition is not added and handled separately.
+ */
+static const u8 blk_zcond2zstate[] = {
+ [BLK_ZONE_COND_NOT_WP] = BLK_ZSTATE_NOT_WP,
+ [BLK_ZONE_COND_EMPTY] = BLK_ZSTATE_EMPTY,
+ [BLK_ZONE_COND_IMP_OPEN] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_EXP_OPEN] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_CLOSED] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_READONLY] = BLK_ZSTATE_READONLY,
+ [BLK_ZONE_COND_FULL] = BLK_ZSTATE_FULL,
+ [BLK_ZONE_COND_OFFLINE] = BLK_ZSTATE_OFFLINE,
+};
+
+static inline enum blk_zstate blk_zone_cond_to_zstate(enum blk_zone_cond cond)
+{
+ if (cond == BLK_ZONE_COND_ACTIVE)
+ return BLK_ZSTATE_ACTIVE;
+
+ if (WARN_ON_ONCE(cond >= ARRAY_SIZE(blk_zcond2zstate)))
+ return 0;
+
+ return blk_zcond2zstate[cond];
+}
+
+/*
+ * Combine an enum blk_zone_condition and zone flags into a zones_state array
+ * entry.
+ */
+static inline void blk_zstate_set(u8 *zones_state, unsigned int zno,
+ enum blk_zone_cond cond, u8 flags)
+{
+ if (zones_state)
+ zones_state[zno] = flags | blk_zone_cond_to_zstate(cond);
+}
+
+static inline u8 blk_zstate_flags(enum blk_zstate zs)
+{
+ return zs & BLK_ZSTATE_FLAGS_MASK;
+}
+
+static inline bool blk_zstate_is_conv(enum blk_zstate zs)
+{
+ return blk_zstate_flags(zs) & BLK_ZFLAG_CONV;
+}
+
/*
* Per-zone write plug.
* @node: hlist_node structure for managing the plug using a hash table.
@@ -135,37 +232,14 @@ const char *blk_zone_cond_str(enum blk_zone_cond zone_cond)
}
EXPORT_SYMBOL_GPL(blk_zone_cond_str);
-static void blk_zone_set_cond(u8 *zones_cond, unsigned int zno,
- enum blk_zone_cond cond)
-{
- if (!zones_cond)
- return;
-
- switch (cond) {
- case BLK_ZONE_COND_IMP_OPEN:
- case BLK_ZONE_COND_EXP_OPEN:
- case BLK_ZONE_COND_CLOSED:
- zones_cond[zno] = BLK_ZONE_COND_ACTIVE;
- return;
- case BLK_ZONE_COND_NOT_WP:
- case BLK_ZONE_COND_EMPTY:
- case BLK_ZONE_COND_FULL:
- case BLK_ZONE_COND_OFFLINE:
- case BLK_ZONE_COND_READONLY:
- default:
- zones_cond[zno] = cond;
- return;
- }
-}
-
static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
enum blk_zone_cond cond)
{
- u8 *zones_cond;
+ u8 *zones_state;
rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (zones_cond) {
+ zones_state = rcu_dereference(disk->zones_state);
+ if (zones_state) {
unsigned int zno = disk_zone_no(disk, sector);
/*
@@ -173,13 +247,14 @@ static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
* never changes, so do nothing if the target zone is in one of
* these conditions.
*/
- switch (zones_cond[zno]) {
- case BLK_ZONE_COND_NOT_WP:
- case BLK_ZONE_COND_READONLY:
- case BLK_ZONE_COND_OFFLINE:
+ switch (zones_state[zno] & BLK_ZSTATE_COND_MASK) {
+ case BLK_ZSTATE_NOT_WP:
+ case BLK_ZSTATE_READONLY:
+ case BLK_ZSTATE_OFFLINE:
break;
default:
- blk_zone_set_cond(zones_cond, zno, cond);
+ blk_zstate_set(zones_state, zno, cond,
+ blk_zstate_flags(zones_state[zno]));
break;
}
}
@@ -198,15 +273,15 @@ bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)
struct gendisk *disk = bdev->bd_disk;
unsigned int zno = disk_zone_no(disk, sector);
bool is_seq = false;
- u8 *zones_cond;
+ u8 *zones_state;
if (!bdev_is_zoned(bdev))
return false;
rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (zones_cond && zno < disk->nr_zones)
- is_seq = zones_cond[zno] != BLK_ZONE_COND_NOT_WP;
+ zones_state = rcu_dereference(disk->zones_state);
+ if (zones_state && zno < disk->nr_zones)
+ is_seq = !blk_zstate_is_conv(zones_state[zno]);
rcu_read_unlock();
return is_seq;
@@ -505,7 +580,7 @@ static bool disk_insert_zone_wplug(struct gendisk *disk,
{
struct blk_zone_wplug *zwplg;
unsigned long flags;
- u8 *zones_cond;
+ u8 *zones_state;
unsigned int idx =
hash_32(zwplug->zone_no, disk->zone_wplugs_hash_bits);
@@ -524,15 +599,16 @@ static bool disk_insert_zone_wplug(struct gendisk *disk,
}
/*
- * Set the zone condition: if we do not yet have a zones_cond array
+ * Set the zone condition: if we do not yet have a zones_state array
* attached to the disk, then this is a zone write plug insert from the
* first call to blk_revalidate_disk_zones(), in which case the zone is
* necessarilly in the active condition.
*/
- zones_cond = rcu_dereference_check(disk->zones_cond,
+ zones_state = rcu_dereference_check(disk->zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock));
- if (zones_cond)
- zwplug->cond = zones_cond[zwplug->zone_no];
+ if (zones_state)
+ zwplug->cond =
+ blk_zstate_to_zone_cond(zones_state[zwplug->zone_no]);
else
zwplug->cond = BLK_ZONE_COND_ACTIVE;
@@ -592,9 +668,9 @@ static void disk_free_zone_wplug(struct blk_zone_wplug *zwplug)
WARN_ON_ONCE(!bio_list_empty(&zwplug->bio_list));
spin_lock_irqsave(&disk->zone_wplugs_hash_lock, flags);
- blk_zone_set_cond(rcu_dereference_check(disk->zones_cond,
+ blk_zstate_set(rcu_dereference_check(disk->zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock)),
- zwplug->zone_no, zwplug->cond);
+ zwplug->zone_no, zwplug->cond, 0);
hlist_del_init_rcu(&zwplug->node);
atomic_dec(&disk->nr_zone_wplugs);
spin_unlock_irqrestore(&disk->zone_wplugs_hash_lock, flags);
@@ -940,7 +1016,7 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
sector_t zone_sectors = bdev_zone_sectors(bdev);
struct blk_zone_wplug *zwplug;
unsigned long flags;
- u8 *zones_cond;
+ u8 *zones_state, zs;
if (!bdev_is_zoned(bdev))
return -EOPNOTSUPP;
@@ -955,12 +1031,18 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
return blkdev_report_zone_fallback(bdev, sector, zone);
rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (!disk->zone_wplugs_hash || !zones_cond) {
+ zones_state = rcu_dereference(disk->zones_state);
+ if (!disk->zone_wplugs_hash || !zones_state) {
rcu_read_unlock();
return blkdev_report_zone_fallback(bdev, sector, zone);
}
- zone->cond = zones_cond[disk_zone_no(disk, sector)];
+
+ zs = zones_state[disk_zone_no(disk, sector)];
+ zone->cond = blk_zstate_to_zone_cond(zs);
+ if (blk_zstate_is_conv(zs))
+ zone->type = BLK_ZONE_TYPE_CONVENTIONAL;
+ else
+ zone->type = BLK_ZONE_TYPE_SEQWRITE_REQ;
rcu_read_unlock();
zone->start = sector;
@@ -970,8 +1052,7 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
* If this is a conventional zone, we do not have a zone write plug and
* can report the zone immediately.
*/
- if (zone->cond == BLK_ZONE_COND_NOT_WP) {
- zone->type = BLK_ZONE_TYPE_CONVENTIONAL;
+ if (zone->type == BLK_ZONE_TYPE_CONVENTIONAL) {
zone->capacity = zone_sectors;
zone->wp = ULLONG_MAX;
return 0;
@@ -982,7 +1063,6 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
* offline, only set the zone write pointer to an invalid value and
* report the zone.
*/
- zone->type = BLK_ZONE_TYPE_SEQWRITE_REQ;
if (disk_zone_is_last(disk, zone))
zone->capacity = disk->last_zone_capacity;
else
@@ -1974,16 +2054,16 @@ static void disk_destroy_zone_wplugs_hash_table(struct gendisk *disk)
disk->zone_wplugs_pool = NULL;
}
-static void disk_set_zones_cond_array(struct gendisk *disk, u8 *zones_cond)
+static void disk_set_zones_state_array(struct gendisk *disk, u8 *zones_state)
{
unsigned long flags;
spin_lock_irqsave(&disk->zone_wplugs_hash_lock, flags);
- zones_cond = rcu_replace_pointer(disk->zones_cond, zones_cond,
+ zones_state = rcu_replace_pointer(disk->zones_state, zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock));
spin_unlock_irqrestore(&disk->zone_wplugs_hash_lock, flags);
- kfree_rcu_mightsleep(zones_cond);
+ kfree_rcu_mightsleep(zones_state);
}
void disk_release_zone_resources(struct gendisk *disk)
@@ -2001,7 +2081,7 @@ void disk_release_zone_resources(struct gendisk *disk)
disk_destroy_zone_wplugs_hash_table(disk);
- disk_set_zones_cond_array(disk, NULL);
+ disk_set_zones_state_array(disk, NULL);
disk->zone_capacity = 0;
disk->last_zone_capacity = 0;
disk->nr_zones = 0;
@@ -2009,7 +2089,7 @@ void disk_release_zone_resources(struct gendisk *disk)
struct blk_revalidate_zone_args {
struct gendisk *disk;
- u8 *zones_cond;
+ u8 *zones_state;
unsigned int nr_zones;
unsigned int nr_conv_zones;
unsigned int zone_capacity;
@@ -2024,8 +2104,8 @@ static int disk_init_revalidate_args(struct gendisk *disk,
args->nr_zones = disk_get_nr_zones(disk);
/* Cached zone conditions: 1 byte per zone */
- args->zones_cond = kzalloc(args->nr_zones, GFP_NOIO);
- if (!args->zones_cond)
+ args->zones_state = kzalloc(args->nr_zones, GFP_NOIO);
+ if (!args->zones_state)
return -ENOMEM;
return 0;
@@ -2064,8 +2144,8 @@ static int disk_revalidate_zone_resources(struct gendisk *disk,
disk->nr_zones = args->nr_zones;
disk->zone_capacity = args->zone_capacity;
disk->last_zone_capacity = args->last_zone_capacity;
- disk_set_zones_cond_array(disk, args->zones_cond);
- args->zones_cond = NULL;
+ disk_set_zones_state_array(disk, args->zones_state);
+ args->zones_state = NULL;
/*
* Some devices can advertise zone resource limits that are larger than
@@ -2114,12 +2194,14 @@ static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx,
struct blk_revalidate_zone_args *args)
{
enum blk_zone_cond cond = zone->cond;
+ u8 flags = 0;
/* Check that the zone condition is consistent with the zone type. */
switch (cond) {
case BLK_ZONE_COND_NOT_WP:
if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL)
goto invalid_condition;
+ flags = BLK_ZFLAG_CONV;
break;
case BLK_ZONE_COND_IMP_OPEN:
case BLK_ZONE_COND_EXP_OPEN:
@@ -2137,7 +2219,7 @@ static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx,
return -ENODEV;
}
- blk_zone_set_cond(args->zones_cond, idx, cond);
+ blk_zstate_set(args->zones_state, idx, cond, flags);
return 0;
@@ -2363,7 +2445,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
free_args:
pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
- kfree(args.zones_cond);
+ kfree(args.zones_state);
return ret;
}
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 20cb8ed7d987..4fa00757527d 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -200,7 +200,7 @@ struct gendisk {
unsigned int nr_zones;
unsigned int zone_capacity;
unsigned int last_zone_capacity;
- u8 __rcu *zones_cond;
+ u8 __rcu *zones_state;
unsigned int zone_wplugs_hash_bits;
atomic_t nr_zone_wplugs;
spinlock_t zone_wplugs_hash_lock;
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 05/13] block: refactor bdev_zone_is_seq()
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (3 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 04/13] block: remember a zone type regardless of its condition Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 9:12 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 06/13] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
` (8 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Define the helper function disk_zone_is_seq() and use it to refactor
bdev_zone_is_seq(). disk_zone_is_seq() is also used in
blk_zone_wplug_handle_write().
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 38 +++++++++++++++++++++++++-------------
1 file changed, 25 insertions(+), 13 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 50b98c394b50..c1090155d961 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -261,6 +261,29 @@ static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
rcu_read_unlock();
}
+static inline u8 disk_zone_get_state(struct gendisk *disk, sector_t sector)
+{
+ unsigned int zno = disk_zone_no(disk, sector);
+ u8 *zones_state, zs;
+
+ rcu_read_lock();
+ zones_state = rcu_dereference(disk->zones_state);
+ if (likely(zones_state && zno < disk->nr_zones))
+ zs = zones_state[zno];
+ else
+ zs = 0;
+ rcu_read_unlock();
+
+ return zs;
+}
+
+static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector)
+{
+ u8 zs = disk_zone_get_state(disk, sector);
+
+ return !blk_zstate_is_conv(zs);
+}
+
/**
* bdev_zone_is_seq - check if a sector belongs to a sequential write zone
* @bdev: block device to check
@@ -270,21 +293,10 @@ static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
*/
bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)
{
- struct gendisk *disk = bdev->bd_disk;
- unsigned int zno = disk_zone_no(disk, sector);
- bool is_seq = false;
- u8 *zones_state;
-
if (!bdev_is_zoned(bdev))
return false;
- rcu_read_lock();
- zones_state = rcu_dereference(disk->zones_state);
- if (zones_state && zno < disk->nr_zones)
- is_seq = !blk_zstate_is_conv(zones_state[zno]);
- rcu_read_unlock();
-
- return is_seq;
+ return disk_zone_is_seq(bdev->bd_disk, sector);
}
EXPORT_SYMBOL_GPL(bdev_zone_is_seq);
@@ -1522,7 +1534,7 @@ static bool blk_zone_wplug_handle_write(struct bio *bio, unsigned int nr_segs)
}
/* Conventional zones do not need write plugging. */
- if (!bdev_zone_is_seq(bio->bi_bdev, sector)) {
+ if (!disk_zone_is_seq(disk, sector)) {
/* Zone append to conventional zones is not allowed. */
if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
bio_io_error(bio);
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 06/13] block: introduce disk_for_all_zone_wplugs()
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (4 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 05/13] block: refactor bdev_zone_is_seq() Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 10:20 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 07/13] block: drop all zone write plugs on capacity changes Damien Le Moal
` (7 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Introduce the helper function disk_for_all_zone_wplugs() to allow
executing an actor function on all hashed zone write plugs. This helper
is used to simplify the implementation of blk_zone_reset_all_bio_endio()
and queue_zone_wplugs_show().
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
---
block/blk-zoned.c | 68 +++++++++++++++++++++++++----------------------
1 file changed, 36 insertions(+), 32 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index c1090155d961..53c43a9413a1 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -662,6 +662,26 @@ static inline struct blk_zone_wplug *disk_get_zone_wplug(struct gendisk *disk,
return disk_get_hashed_zone_wplug(disk, sector);
}
+static void disk_for_all_zone_wplugs(struct gendisk *disk,
+ void (*actor)(struct blk_zone_wplug *,
+ void *),
+ void *data)
+{
+ struct blk_zone_wplug *zwplug;
+ unsigned int i;
+
+ if (!disk->zone_wplugs_hash)
+ return;
+
+ rcu_read_lock();
+ for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
+ hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i],
+ node)
+ actor(zwplug, data);
+ }
+ rcu_read_unlock();
+}
+
static void disk_free_zone_wplug_rcu(struct rcu_head *rcu_head)
{
struct blk_zone_wplug *zwplug =
@@ -1200,32 +1220,26 @@ static void blk_zone_reset_bio_endio(struct bio *bio)
}
}
+static void disk_zone_wplug_reset_wp(struct blk_zone_wplug *zwplug, void *data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&zwplug->lock, flags);
+ disk_zone_wplug_set_wp_offset(zwplug->disk, zwplug, 0);
+ spin_unlock_irqrestore(&zwplug->lock, flags);
+}
+
static void blk_zone_reset_all_bio_endio(struct bio *bio)
{
struct gendisk *disk = bio->bi_bdev->bd_disk;
- sector_t capacity = get_capacity(disk);
- struct blk_zone_wplug *zwplug;
- unsigned long flags;
sector_t sector;
- unsigned int i;
- if (atomic_read(&disk->nr_zone_wplugs)) {
- /* Update the condition of all zone write plugs. */
- rcu_read_lock();
- for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
- hlist_for_each_entry_rcu(zwplug,
- &disk->zone_wplugs_hash[i],
- node) {
- spin_lock_irqsave(&zwplug->lock, flags);
- disk_zone_wplug_set_wp_offset(disk, zwplug, 0);
- spin_unlock_irqrestore(&zwplug->lock, flags);
- }
- }
- rcu_read_unlock();
- }
+ /* Update the condition of all zone write plugs. */
+ if (atomic_read(&disk->nr_zone_wplugs))
+ disk_for_all_zone_wplugs(disk, disk_zone_wplug_reset_wp, NULL);
/* Update the cached zone conditions. */
- for (sector = 0; sector < capacity;
+ for (sector = 0; sector < get_capacity(disk);
sector += bdev_zone_sectors(bio->bi_bdev))
disk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY);
clear_bit(GD_ZONE_APPEND_USED, &disk->state);
@@ -2508,8 +2522,9 @@ EXPORT_SYMBOL_GPL(blk_zone_issue_zeroout);
#ifdef CONFIG_BLK_DEBUG_FS
static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,
- struct seq_file *m)
+ void *data)
{
+ struct seq_file *m = data;
unsigned int zwp_wp_offset, zwp_flags;
unsigned int zwp_zone_no, zwp_ref;
unsigned int zwp_bio_list_size;
@@ -2534,19 +2549,8 @@ static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,
int queue_zone_wplugs_show(void *data, struct seq_file *m)
{
struct request_queue *q = data;
- struct gendisk *disk = q->disk;
- struct blk_zone_wplug *zwplug;
- unsigned int i;
-
- if (!disk->zone_wplugs_hash)
- return 0;
- rcu_read_lock();
- for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++)
- hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i],
- node)
- queue_zone_wplug_show(zwplug, m);
- rcu_read_unlock();
+ disk_for_all_zone_wplugs(q->disk, queue_zone_wplug_show, m);
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 07/13] block: drop all zone write plugs on capacity changes
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (5 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 06/13] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 10:24 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 08/13] block: propagate readonly and offline conditions to zone write plugs Damien Le Moal
` (6 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
If during revalidation, we detect a capacity change for a zoned block
device, e.g. due to a storage element removal on an HDD, we can assume
that the device was reformatted, which implies that all sequential zones
are empty. For such case, we can remove and free all zone write plugs in
the gendisk hash table by marking them as dead, thus avoiding also to
leave zone write plugs for zones that are beyond the new device capacity
in the disk hash table.
Introduce the function disk_revalidate_capacity() to do this and call this
new function at the beginning of blk_revalidate_disk_zones(), so that the
zone revalidation process can re-create, if needed, any zone write plug
for sequential zones that are not empty.
The checks on the capacity and zone size that were in
blk_revalidate_disk_zones() are moved to disk_revalidate_capacity() and
if true, also trigger dropping all zone write plugs.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 71 ++++++++++++++++++++++++++++++++++++-----------
1 file changed, 55 insertions(+), 16 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 53c43a9413a1..e2cbeecb5f03 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -2216,6 +2216,56 @@ static int disk_revalidate_zone_resources(struct gendisk *disk,
return ret;
}
+static void disk_drop_zone_wplug(struct blk_zone_wplug *zwplug, void *data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&zwplug->lock, flags);
+ disk_zone_wplug_abort(zwplug);
+ disk_mark_zone_wplug_dead(zwplug);
+ spin_unlock_irqrestore(&zwplug->lock, flags);
+}
+
+static int disk_revalidate_capacity(struct gendisk *disk)
+{
+ struct queue_limits *lim = &disk->queue->limits;
+ sector_t zone_sectors = lim->chunk_sectors;
+ unsigned int nr_zones = disk_get_nr_zones(disk);
+ int ret = -ENODEV;
+
+ if (!get_capacity(disk))
+ goto drop_all_zwplugs;
+
+ /*
+ * 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);
+ goto drop_all_zwplugs;
+ }
+
+ /*
+ * Check if the capacity has changed. If it did, assume that the device
+ * was reformatted and that all sequential zones are now empty. So drop
+ * all zone write plug.
+ */
+ if (disk->nr_zones && disk->nr_zones != nr_zones) {
+ pr_warn("%s: Number of zones changed (%u -> %u)\n",
+ disk->disk_name, disk->nr_zones, nr_zones);
+ ret = 0;
+ goto drop_all_zwplugs;
+ }
+
+ return 0;
+
+drop_all_zwplugs:
+ disk_for_all_zone_wplugs(disk, disk_drop_zone_wplug, NULL);
+
+ return ret;
+}
+
static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx,
struct blk_revalidate_zone_args *args)
{
@@ -2405,31 +2455,20 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
*/
int blk_revalidate_disk_zones(struct gendisk *disk)
{
- struct request_queue *q = disk->queue;
- sector_t zone_sectors = q->limits.chunk_sectors;
struct blk_revalidate_zone_args args = { };
struct blk_report_zones_args rep_args = {
.cb = blk_revalidate_zone_cb,
.data = &args,
};
unsigned int noio_flag;
- int ret = -ENOMEM;
+ int ret;
- if (WARN_ON_ONCE(!blk_queue_is_zoned(q)))
+ if (WARN_ON_ONCE(!blk_queue_is_zoned(disk->queue)))
return -EIO;
- if (!get_capacity(disk))
- return -ENODEV;
-
- /*
- * 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);
- return -ENODEV;
- }
+ ret = disk_revalidate_capacity(disk);
+ if (ret)
+ return ret;
/*
* Allocate zone resources if they are needed and we have not done
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 08/13] block: propagate readonly and offline conditions to zone write plugs
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (6 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 07/13] block: drop all zone write plugs on capacity changes Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 10:32 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 09/13] block: always treat offline and read-only zones as dead Damien Le Moal
` (5 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
When revalidating the zones of a zoned block device, or when handling a
zone which has a zone write plug flagged with
BLK_ZONE_WPLUG_NEED_WP_UPDATE, the function
disk_zone_wplug_sync_wp_offset() is used to update the write pointer
offset of a zone write plug. However, this does not take into account the
condition of the zone, which may have changed to readonly or offline,
which in itself will always cause errors.
In order to catch such errors, rename disk_zone_wplug_sync_wp_offset() to
disk_zone_wplug_sync_state() and in addition to the zone write pointer,
also update the zone write plug condition if the zone is readonly or
offline.
This change also requires changes to how a reset all zones operation
(REQ_OP_ZONE_RESET_ALL) is handled so that the offline or read-only
condition of zone write plugs is not overwritten with an erroneous empty
condition. To do so, introduce the helper function
disk_zone_is_offline_or_readonly() to skip updating the condition of zones
that are read-only or offline and that do not have a zone write plug.
For zones that have a zone write plug, the helper function
disk_zone_wplug_is_offline_or_readonly() is used in
disk_zone_wplug_set_wp_offset() to not update a zone write plug write
pointer and condition for read-only and offline zones.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 52 +++++++++++++++++++++++++++++++++++++++--------
1 file changed, 43 insertions(+), 9 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index e2cbeecb5f03..cb34d0beaf43 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -277,6 +277,29 @@ static inline u8 disk_zone_get_state(struct gendisk *disk, sector_t sector)
return zs;
}
+static enum blk_zone_cond disk_zone_get_cond(struct gendisk *disk,
+ sector_t sector)
+{
+ u8 zs = disk_zone_get_state(disk, sector);
+
+ return blk_zstate_to_zone_cond(zs);
+}
+
+static inline bool
+disk_zone_cond_is_offline_or_readonly(enum blk_zone_cond cond)
+{
+ return cond == BLK_ZONE_COND_READONLY ||
+ cond == BLK_ZONE_COND_OFFLINE;
+}
+
+static inline bool disk_zone_is_offline_or_readonly(struct gendisk *disk,
+ sector_t sector)
+{
+ enum blk_zone_cond cond = disk_zone_get_cond(disk, sector);
+
+ return disk_zone_cond_is_offline_or_readonly(cond);
+}
+
static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector)
{
u8 zs = disk_zone_get_state(disk, sector);
@@ -587,6 +610,11 @@ static bool disk_zone_wplug_is_full(struct gendisk *disk,
return zwplug->wp_offset >= disk->last_zone_capacity;
}
+static bool disk_zone_wplug_is_offline_or_readonly(struct blk_zone_wplug *zwplug)
+{
+ return disk_zone_cond_is_offline_or_readonly(zwplug->cond);
+}
+
static bool disk_insert_zone_wplug(struct gendisk *disk,
struct blk_zone_wplug *zwplug)
{
@@ -893,8 +921,10 @@ static void disk_zone_wplug_set_wp_offset(struct gendisk *disk,
/* Update the zone write pointer and abort all plugged BIOs. */
zwplug->flags &= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE;
- zwplug->wp_offset = wp_offset;
- disk_zone_wplug_update_cond(disk, zwplug);
+ if (!disk_zone_wplug_is_offline_or_readonly(zwplug)) {
+ zwplug->wp_offset = wp_offset;
+ disk_zone_wplug_update_cond(disk, zwplug);
+ }
disk_zone_wplug_abort(zwplug);
if (!zwplug->wp_offset || disk_zone_wplug_is_full(disk, zwplug))
@@ -924,8 +954,8 @@ static unsigned int blk_zone_wp_offset(struct blk_zone *zone)
}
}
-static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,
- struct blk_zone *zone)
+static unsigned int disk_zone_wplug_sync_state(struct gendisk *disk,
+ struct blk_zone *zone)
{
struct blk_zone_wplug *zwplug;
unsigned int wp_offset = blk_zone_wp_offset(zone);
@@ -937,6 +967,8 @@ static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,
spin_lock_irqsave(&zwplug->lock, flags);
if (zwplug->flags & BLK_ZONE_WPLUG_NEED_WP_UPDATE)
disk_zone_wplug_set_wp_offset(disk, zwplug, wp_offset);
+ if (disk_zone_cond_is_offline_or_readonly(zone->cond))
+ zwplug->cond = zone->cond;
spin_unlock_irqrestore(&zwplug->lock, flags);
disk_put_zone_wplug(zwplug);
}
@@ -979,7 +1011,7 @@ int disk_report_zone(struct gendisk *disk, struct blk_zone *zone,
}
if (disk->zone_wplugs_hash)
- disk_zone_wplug_sync_wp_offset(disk, zone);
+ disk_zone_wplug_sync_state(disk, zone);
if (args && args->cb)
return args->cb(zone, idx, args->data);
@@ -1100,8 +1132,7 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
else
zone->capacity = disk->zone_capacity;
- if (zone->cond == BLK_ZONE_COND_READONLY ||
- zone->cond == BLK_ZONE_COND_OFFLINE) {
+ if (disk_zone_cond_is_offline_or_readonly(zone->cond)) {
zone->wp = ULLONG_MAX;
return 0;
}
@@ -1240,8 +1271,11 @@ static void blk_zone_reset_all_bio_endio(struct bio *bio)
/* Update the cached zone conditions. */
for (sector = 0; sector < get_capacity(disk);
- sector += bdev_zone_sectors(bio->bi_bdev))
+ sector += bdev_zone_sectors(bio->bi_bdev)) {
+ if (disk_zone_is_offline_or_readonly(disk, sector))
+ continue;
disk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY);
+ }
clear_bit(GD_ZONE_APPEND_USED, &disk->state);
}
@@ -2356,7 +2390,7 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,
if (!disk->zone_wplugs_hash)
return 0;
- wp_offset = disk_zone_wplug_sync_wp_offset(disk, zone);
+ wp_offset = disk_zone_wplug_sync_state(disk, zone);
if (!wp_offset || wp_offset >= zone->capacity)
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 09/13] block: always treat offline and read-only zones as dead
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (7 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 08/13] block: propagate readonly and offline conditions to zone write plugs Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 10:46 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 10/13] block: fail zone management operations to read-only and offline zones Damien Le Moal
` (4 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Since any write BIO targeting an offline or a read-only zone will fail,
there is no point in keeping zone write plugs for these zones.
So for any offline or read-only zone, the zone write plug should always
be treated as dead.
Do this by modifying disk_check_zone_wplug_dead() to always mark read-only
and offline zones as dead to force a removal of the zone write plug from
the disk hash table on BIO submission. blk_zone_wplug_prepare_bio() is
also modified to have the same checks to immediately fail a write BIO
targeting a read-only or offline zone. With these two changes, any newly
issued or unplugged write BIO targeting a read-only or offline zone is
immediately failed.
Finally, disk_zone_wplug_sync_state() is modified to add a call to
disk_mark_zone_wplug_dead() for the zone write plug of any read-only or
offline zone found during zone revalidation or a report zones.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 113 +++++++++++++++++++++++++---------------------
1 file changed, 61 insertions(+), 52 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index cb34d0beaf43..0f0ffe832008 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -744,6 +744,53 @@ static inline void disk_put_zone_wplug(struct blk_zone_wplug *zwplug)
disk_free_zone_wplug(zwplug);
}
+static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,
+ struct bio *bio)
+{
+ struct request_queue *q = zwplug->disk->queue;
+
+ bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
+ bio_io_error(bio);
+ disk_put_zone_wplug(zwplug);
+ /* Drop the reference taken by disk_zone_wplug_add_bio(). */
+ blk_queue_exit(q);
+}
+
+/*
+ * Abort (fail) all plugged BIOs of a zone write plug.
+ */
+static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)
+{
+ struct gendisk *disk = zwplug->disk;
+ struct bio *bio;
+
+ lockdep_assert_held(&zwplug->lock);
+
+ if (bio_list_empty(&zwplug->bio_list))
+ return;
+
+ pr_warn_ratelimited("%s: zone %u: Aborting plugged BIOs\n",
+ zwplug->disk->disk_name, zwplug->zone_no);
+ while ((bio = bio_list_pop(&zwplug->bio_list)))
+ blk_zone_wplug_bio_io_error(zwplug, bio);
+
+ zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
+
+ /*
+ * If we are using the per disk zone write plugs worker thread, remove
+ * the zone write plug from the work list and drop the reference we
+ * took when the zone write plug was added to that list.
+ */
+ if (blk_queue_zoned_qd1_writes(disk->queue)) {
+ spin_lock(&disk->zone_wplugs_list_lock);
+ if (!list_empty(&zwplug->entry)) {
+ list_del_init(&zwplug->entry);
+ disk_put_zone_wplug(zwplug);
+ }
+ spin_unlock(&disk->zone_wplugs_list_lock);
+ }
+}
+
/*
* Flag the zone write plug as dead and drop the initial reference we got when
* the zone write plug was added to the hash table. The zone write plug will be
@@ -761,6 +808,12 @@ static void disk_mark_zone_wplug_dead(struct blk_zone_wplug *zwplug)
static inline bool disk_check_zone_wplug_dead(struct blk_zone_wplug *zwplug)
{
+ if (disk_zone_wplug_is_offline_or_readonly(zwplug)) {
+ disk_zone_wplug_abort(zwplug);
+ disk_mark_zone_wplug_dead(zwplug);
+ return true;
+ }
+
if (!(zwplug->flags & BLK_ZONE_WPLUG_DEAD))
return false;
@@ -844,53 +897,6 @@ static struct blk_zone_wplug *disk_get_or_alloc_zone_wplug(struct gendisk *disk,
return zwplug;
}
-static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,
- struct bio *bio)
-{
- struct request_queue *q = zwplug->disk->queue;
-
- bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
- bio_io_error(bio);
- disk_put_zone_wplug(zwplug);
- /* Drop the reference taken by disk_zone_wplug_add_bio(). */
- blk_queue_exit(q);
-}
-
-/*
- * Abort (fail) all plugged BIOs of a zone write plug.
- */
-static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)
-{
- struct gendisk *disk = zwplug->disk;
- struct bio *bio;
-
- lockdep_assert_held(&zwplug->lock);
-
- if (bio_list_empty(&zwplug->bio_list))
- return;
-
- pr_warn_ratelimited("%s: zone %u: Aborting plugged BIOs\n",
- zwplug->disk->disk_name, zwplug->zone_no);
- while ((bio = bio_list_pop(&zwplug->bio_list)))
- blk_zone_wplug_bio_io_error(zwplug, bio);
-
- zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
-
- /*
- * If we are using the per disk zone write plugs worker thread, remove
- * the zone write plug from the work list and drop the reference we
- * took when the zone write plug was added to that list.
- */
- if (blk_queue_zoned_qd1_writes(disk->queue)) {
- spin_lock(&disk->zone_wplugs_list_lock);
- if (!list_empty(&zwplug->entry)) {
- list_del_init(&zwplug->entry);
- disk_put_zone_wplug(zwplug);
- }
- spin_unlock(&disk->zone_wplugs_list_lock);
- }
-}
-
/*
* Update a zone write plug condition based on the write pointer offset.
*/
@@ -967,8 +973,10 @@ static unsigned int disk_zone_wplug_sync_state(struct gendisk *disk,
spin_lock_irqsave(&zwplug->lock, flags);
if (zwplug->flags & BLK_ZONE_WPLUG_NEED_WP_UPDATE)
disk_zone_wplug_set_wp_offset(disk, zwplug, wp_offset);
- if (disk_zone_cond_is_offline_or_readonly(zone->cond))
+ if (disk_zone_cond_is_offline_or_readonly(zone->cond)) {
zwplug->cond = zone->cond;
+ disk_mark_zone_wplug_dead(zwplug);
+ }
spin_unlock_irqrestore(&zwplug->lock, flags);
disk_put_zone_wplug(zwplug);
}
@@ -1521,11 +1529,12 @@ static bool blk_zone_wplug_prepare_bio(struct blk_zone_wplug *zwplug,
return false;
/*
- * Check that the user is not attempting to write to a full zone.
- * We know such BIO will fail, and that would potentially overflow our
- * write pointer offset beyond the end of the zone.
+ * Check that the user is not attempting to write to a full, read-only
+ * or offline zone. We know such BIOs will fail, so there is no point
+ * in issuing them.
*/
- if (disk_zone_wplug_is_full(disk, zwplug))
+ if (disk_zone_wplug_is_full(disk, zwplug) ||
+ disk_zone_wplug_is_offline_or_readonly(zwplug))
return false;
if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 10/13] block: fail zone management operations to read-only and offline zones
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (8 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 09/13] block: always treat offline and read-only zones as dead Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 11:42 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 11/13] block: allow read-only and offline conventional zones Damien Le Moal
` (3 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Any zone management operation targeting a zone that is in the read-only
or offline condition will fail. So there is no point in issuing such
BIO. Modify the check in submit_bio_noacct() to use the new helper
function bdev_check_zone_mgmt() to check that a zone is sequential (as was
checked before) and also that the zone is not offline nor read-only.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-core.c | 7 +++++--
block/blk-zoned.c | 24 ++++++++++++++++++++++++
block/blk.h | 6 ++++++
3 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/block/blk-core.c b/block/blk-core.c
index 196bccf27f58..80a4e2bcc3f3 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -901,8 +901,11 @@ void submit_bio_noacct(struct bio *bio)
case REQ_OP_ZONE_CLOSE:
case REQ_OP_ZONE_RESET:
case REQ_OP_ZONE_FINISH:
- /* Zone management operations require sequential zones. */
- if (!bdev_zone_is_seq(bio->bi_bdev, bio->bi_iter.bi_sector))
+ /*
+ * Zone management operations require sequential zones that are
+ * not offline nor read-only.
+ */
+ if (!bdev_check_zone_mgmt(bdev, bio->bi_iter.bi_sector))
goto end_io;
break;
case REQ_OP_ZONE_RESET_ALL:
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 0f0ffe832008..d13a6edc7362 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -323,6 +323,30 @@ bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)
}
EXPORT_SYMBOL_GPL(bdev_zone_is_seq);
+/**
+ * bdev_check_zone_mgmt - check if a sector belongs to a valid sequential zone
+ * @bdev: block device to check
+ * @sector: sector number
+ *
+ * Check if @sector on @bdev is contained in a sequential write required zone
+ * that is not offline nor read-only.
+ */
+bool bdev_check_zone_mgmt(struct block_device *bdev, sector_t sector)
+{
+ enum blk_zone_cond cond;
+ u8 zs;
+
+ if (!bdev_is_zoned(bdev))
+ return false;
+
+ zs = disk_zone_get_state(bdev->bd_disk, sector);
+ if (blk_zstate_is_conv(zs))
+ return false;
+
+ cond = blk_zstate_to_zone_cond(zs);
+ return !disk_zone_cond_is_offline_or_readonly(cond);
+}
+
/*
* Zone report arguments for block device drivers report_zones operation.
* @cb: report_zones_cb callback for each reported zone.
diff --git a/block/blk.h b/block/blk.h
index 50abfd932886..cfe8d4185e25 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -577,6 +577,7 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd,
unsigned long arg);
int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg);
+bool bdev_check_zone_mgmt(struct block_device *bdev, sector_t sector);
#else /* CONFIG_BLK_DEV_ZONED */
static inline void disk_init_zone_resources(struct gendisk *disk)
{
@@ -619,6 +620,11 @@ static inline int blkdev_zone_mgmt_ioctl(struct block_device *bdev,
{
return -ENOTTY;
}
+static inline bool bdev_check_zone_mgmt(struct block_device *bdev,
+ sector_t sector)
+{
+ return false;
+}
#endif /* CONFIG_BLK_DEV_ZONED */
struct block_device *bdev_alloc(struct gendisk *disk, u8 partno);
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 11/13] block: allow read-only and offline conventional zones
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (9 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 10/13] block: fail zone management operations to read-only and offline zones Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 11:48 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 12/13] block: simplify disk_zone_set_cond() Damien Le Moal
` (2 subsequent siblings)
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
With SCSI and ATA SMR HDDs, the storage element depopulation feature can
change the condition of conventional zones to read-only (if a write head
is depopulated) or to offline (if a read head is depopulated).
However, the function blk_revalidate_zone_cond() currently does not allow
these conditions for conventional zones, causing a zone revalidation
failure.
Remove blk_revalidate_zone_cond() and move the zone condition checks for
conventional zones to blk_revalidate_conv_zone(), allowing the regular
BLK_ZONE_COND_NOT_WP condition as well as the BLK_ZONE_COND_OFFLINE and
BLK_ZONE_COND_READONLY conditions to match the conditions that can be
seen from a zoned device with depopulated storage elements.
The zone condition checks for sequential write required zones are moved
to blk_revalidate_seq_zone() without any change to the conditions allowed.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 65 +++++++++++++++++++----------------------------
1 file changed, 26 insertions(+), 39 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index d13a6edc7362..d0bdef1a5774 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -2333,57 +2333,31 @@ static int disk_revalidate_capacity(struct gendisk *disk)
return ret;
}
-static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx,
+static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,
struct blk_revalidate_zone_args *args)
{
- enum blk_zone_cond cond = zone->cond;
- u8 flags = 0;
+ struct gendisk *disk = args->disk;
- /* Check that the zone condition is consistent with the zone type. */
- switch (cond) {
+ /* Check the zone condition. */
+ switch (zone->cond) {
case BLK_ZONE_COND_NOT_WP:
- if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL)
- goto invalid_condition;
- flags = BLK_ZFLAG_CONV;
- break;
- case BLK_ZONE_COND_IMP_OPEN:
- case BLK_ZONE_COND_EXP_OPEN:
- case BLK_ZONE_COND_CLOSED:
- case BLK_ZONE_COND_EMPTY:
- case BLK_ZONE_COND_FULL:
case BLK_ZONE_COND_OFFLINE:
case BLK_ZONE_COND_READONLY:
- if (zone->type != BLK_ZONE_TYPE_SEQWRITE_REQ)
- goto invalid_condition;
break;
default:
- pr_warn("%s: Invalid zone condition 0x%X\n",
- args->disk->disk_name, cond);
+ pr_warn("%s: Invalid conv. zone condition 0x%X at sector %llu\n",
+ disk->disk_name, zone->cond, zone->start);
return -ENODEV;
}
- blk_zstate_set(args->zones_state, idx, cond, flags);
-
- return 0;
-
-invalid_condition:
- pr_warn("%s: Invalid zone condition 0x%x for type 0x%x\n",
- args->disk->disk_name, cond, zone->type);
-
- return -ENODEV;
-}
-
-static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,
- struct blk_revalidate_zone_args *args)
-{
- struct gendisk *disk = args->disk;
-
if (zone->capacity != zone->len) {
pr_warn("%s: Invalid conventional zone capacity\n",
disk->disk_name);
return -ENODEV;
}
+ blk_zstate_set(args->zones_state, idx, zone->cond, BLK_ZFLAG_CONV);
+
if (disk_zone_is_last(disk, zone))
args->last_zone_capacity = zone->capacity;
@@ -2399,6 +2373,24 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,
struct blk_zone_wplug *zwplug;
unsigned int wp_offset;
+ /* Check the zone condition. */
+ switch (zone->cond) {
+ case BLK_ZONE_COND_IMP_OPEN:
+ case BLK_ZONE_COND_EXP_OPEN:
+ case BLK_ZONE_COND_CLOSED:
+ case BLK_ZONE_COND_EMPTY:
+ case BLK_ZONE_COND_FULL:
+ case BLK_ZONE_COND_OFFLINE:
+ case BLK_ZONE_COND_READONLY:
+ break;
+ default:
+ pr_warn("%s: Invalid seq. zone condition 0x%X at sector %llu\n",
+ disk->disk_name, zone->cond, zone->start);
+ return -ENODEV;
+ }
+
+ blk_zstate_set(args->zones_state, idx, zone->cond, 0);
+
/*
* Remember the capacity of the first sequential zone and check
* if it is constant for all zones, ignoring the last zone as it can be
@@ -2481,11 +2473,6 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return -ENODEV;
}
- /* Check zone condition */
- ret = blk_revalidate_zone_cond(zone, idx, args);
- if (ret)
- return ret;
-
/* Check zone type */
switch (zone->type) {
case BLK_ZONE_TYPE_CONVENTIONAL:
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 12/13] block: simplify disk_zone_set_cond()
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (10 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 11/13] block: allow read-only and offline conventional zones Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 11:51 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 13/13] block: flag zoned disks with GENHD_FL_NO_PART Damien Le Moal
2026-08-07 15:34 ` [PATCH v2 00/13] Improve handling of offline and read-only zones Bart Van Assche
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
disk_zone_set_cond() is used to set a zone condition afer a reset, a
finish or a reset all operation. For a single zone reset or finish, we are
guaranteed that the target zone is a sequential one that is not offline
nor read-only (otherwise, the operation would have failed). For a reset
all operation, there is no point in calling this function for offline and
read-only zones since the condition checks in disk_zone_set_cond() will
result in nothing being done.
Simplify all this using disk_zone_is_offline_or_readonly() in
blk_zone_reset_all_bio_endio() to skip zones that are offline or
read-only. This change allows simplifying disk_zone_set_cond() by removing
the zone condition checks. This change is also consistent with the fact
that conventional zones can now have the offline or read-only condition.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 26 ++++++--------------------
1 file changed, 6 insertions(+), 20 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index d0bdef1a5774..f17069309ac9 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -235,29 +235,14 @@ EXPORT_SYMBOL_GPL(blk_zone_cond_str);
static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
enum blk_zone_cond cond)
{
+ unsigned int zno = disk_zone_no(disk, sector);
u8 *zones_state;
rcu_read_lock();
zones_state = rcu_dereference(disk->zones_state);
- if (zones_state) {
- unsigned int zno = disk_zone_no(disk, sector);
-
- /*
- * The condition of a conventional, readonly and offline zones
- * never changes, so do nothing if the target zone is in one of
- * these conditions.
- */
- switch (zones_state[zno] & BLK_ZSTATE_COND_MASK) {
- case BLK_ZSTATE_NOT_WP:
- case BLK_ZSTATE_READONLY:
- case BLK_ZSTATE_OFFLINE:
- break;
- default:
- blk_zstate_set(zones_state, zno, cond,
- blk_zstate_flags(zones_state[zno]));
- break;
- }
- }
+ if (likely(zones_state && zno < disk->nr_zones))
+ blk_zstate_set(zones_state, zno, cond,
+ blk_zstate_flags(zones_state[zno]));
rcu_read_unlock();
}
@@ -1304,7 +1289,8 @@ static void blk_zone_reset_all_bio_endio(struct bio *bio)
/* Update the cached zone conditions. */
for (sector = 0; sector < get_capacity(disk);
sector += bdev_zone_sectors(bio->bi_bdev)) {
- if (disk_zone_is_offline_or_readonly(disk, sector))
+ if (!disk_zone_is_seq(disk, sector) ||
+ disk_zone_is_offline_or_readonly(disk, sector))
continue;
disk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v2 13/13] block: flag zoned disks with GENHD_FL_NO_PART
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (11 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 12/13] block: simplify disk_zone_set_cond() Damien Le Moal
@ 2026-08-06 16:04 ` Damien Le Moal
2026-08-07 11:53 ` Hannes Reinecke
2026-08-07 15:34 ` [PATCH v2 00/13] Improve handling of offline and read-only zones Bart Van Assche
13 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-08-06 16:04 UTC (permalink / raw)
To: Jens Axboe, linux-block; +Cc: Christoph Hellwig
Zoned block devices do not support partitions. However, the partition
table is nevertheless still inspected, and any partition found ignored
with a warning in add_partition(). While this is generally not a problem,
and in fact beneficial to the user as it indicates an invalid use of a
zoned block device, scanning for a partition table on the device may
result in issuing read operations to offline zones (e.g. after a disk head
is depopulated for disks that support head management operations).
Since partitions are ignored anyway, completely disable partition scanning
for zoned gendisks by setting the flag GENHD_FL_NO_PART in __add_disk().
The existing check in add_partition() is left as-is to ensure that we
still get a warning if for whatever reason, despite GENHD_FL_NO_PART, we
still endup trying to add partitions.
Flagging zoned disks with GENHD_FL_NO_PART also has the benefit to expose
through sysfs the ext_range attribute with the value of 1 instead of the
default DISK_MAX_PARTS, thus correctly advertizing the fact that zoned
disks do not support partitions.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/genhd.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/block/genhd.c b/block/genhd.c
index e8ce0cabf392..34a64077ba02 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -447,6 +447,13 @@ static int __add_disk(struct device *parent, struct gendisk *disk,
bdev_set_flag(disk->part0, BD_HAS_SUBMIT_BIO);
}
+ /*
+ * We do not support partitions with zoned block devices, so do not try
+ * to scan the partitions table.
+ */
+ if (blk_queue_is_zoned(disk->queue))
+ disk->flags |= GENHD_FL_NO_PART;
+
/*
* If the driver provides an explicit major number it also must provide
* the number of minors numbers supported, and those will be used to
--
2.55.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v2 03/13] block: refactor disk_update_zone_resources()
2026-08-06 16:04 ` [PATCH v2 03/13] block: refactor disk_update_zone_resources() Damien Le Moal
@ 2026-08-07 9:03 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 9:03 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> Since the function disk_update_zone_resources() does a lot of checks
> beside updating a zoned disk limits and resources, rename this function
> to disk_revalidate_zone_resources(). To keep all checks together, move
> the capacity check in blk_revalidate_disk_zones() to this function and
> simplify it by moving the check on the number of zones before the call to
> queue_limits_start_update().
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 46 +++++++++++++++++++---------------------------
> 1 file changed, 19 insertions(+), 27 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 04/13] block: remember a zone type regardless of its condition
2026-08-06 16:04 ` [PATCH v2 04/13] block: remember a zone type regardless of its condition Damien Le Moal
@ 2026-08-07 9:11 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 9:11 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> Currently, bdev_zone_is_seq() identifies a sequential zone by looking at
> the zone condition, assuming that any zone that does not have the
> condition BLK_ZONE_COND_NOT_WP is a sequential write required zone. That
> is correct only as long as the target zoned device does not support
> storage element depopulation (aka HDD head depopulation), which is a
> feature that can transition conventional zones to the read-only or offline
> condition. For such device, we cannot distinguish anymore between
> conventional and sequential zones using the zone condition as both zone
> types can have the same conditions.
>
> Prepare for fully supporting storage element depopulation and restoration
> by caching the type of a zone in addition to its condition. This is
> implemented by reformating the zones_cond array using a more compact zone
> condition representation with the new enum blk_zstate. This zone condition
> representation only uses the lower 4 bits of a byte for the condition
> values, thus leaving the high order 4 bits of each byte entry of the
> array for flags. The flag BLK_ZFLAG_CONV is defined to indicate
> conventional zones.
>
> The helper functions blk_zstate_to_zone_cond() and
> blk_zone_cond_to_zstate() are implemented using lookup tables to convert
> between enum blk_zone_condition values and enum blk_zstate zone condition
> values. The helper blk_zstate_set() can be used to prepare a zones_state
> entry for a disk with a zone condition and zone type flags. This helper is
> used in disk_zone_set_cond() to update a zone condition.
> bdev_zone_is_seq() is modified to use the flags of the zones_state array
> entries to identify sequential zones. The zones_state array initialization
> and revalidation is unchanged from the former zones_cond array.
>
> Overall, any zone condition that is being considered outside of the
> zones_state array always uses the enum blk_zone_condition values as
> before, thus minimizing the number of changes.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 202 +++++++++++++++++++++++++++++------------
> include/linux/blkdev.h | 2 +-
> 2 files changed, 143 insertions(+), 61 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 05/13] block: refactor bdev_zone_is_seq()
2026-08-06 16:04 ` [PATCH v2 05/13] block: refactor bdev_zone_is_seq() Damien Le Moal
@ 2026-08-07 9:12 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 9:12 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> Define the helper function disk_zone_is_seq() and use it to refactor
> bdev_zone_is_seq(). disk_zone_is_seq() is also used in
> blk_zone_wplug_handle_write().
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 38 +++++++++++++++++++++++++-------------
> 1 file changed, 25 insertions(+), 13 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 06/13] block: introduce disk_for_all_zone_wplugs()
2026-08-06 16:04 ` [PATCH v2 06/13] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
@ 2026-08-07 10:20 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 10:20 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> Introduce the helper function disk_for_all_zone_wplugs() to allow
> executing an actor function on all hashed zone write plugs. This helper
> is used to simplify the implementation of blk_zone_reset_all_bio_endio()
> and queue_zone_wplugs_show().
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> Reviewed-by: Bart Van Assche <bvanassche@acm.org>
> ---
> block/blk-zoned.c | 68 +++++++++++++++++++++++++----------------------
> 1 file changed, 36 insertions(+), 32 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 07/13] block: drop all zone write plugs on capacity changes
2026-08-06 16:04 ` [PATCH v2 07/13] block: drop all zone write plugs on capacity changes Damien Le Moal
@ 2026-08-07 10:24 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 10:24 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> If during revalidation, we detect a capacity change for a zoned block
> device, e.g. due to a storage element removal on an HDD, we can assume
> that the device was reformatted, which implies that all sequential zones
> are empty. For such case, we can remove and free all zone write plugs in
> the gendisk hash table by marking them as dead, thus avoiding also to
> leave zone write plugs for zones that are beyond the new device capacity
> in the disk hash table.
>
> Introduce the function disk_revalidate_capacity() to do this and call this
> new function at the beginning of blk_revalidate_disk_zones(), so that the
> zone revalidation process can re-create, if needed, any zone write plug
> for sequential zones that are not empty.
>
> The checks on the capacity and zone size that were in
> blk_revalidate_disk_zones() are moved to disk_revalidate_capacity() and
> if true, also trigger dropping all zone write plugs.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 71 ++++++++++++++++++++++++++++++++++++-----------
> 1 file changed, 55 insertions(+), 16 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 08/13] block: propagate readonly and offline conditions to zone write plugs
2026-08-06 16:04 ` [PATCH v2 08/13] block: propagate readonly and offline conditions to zone write plugs Damien Le Moal
@ 2026-08-07 10:32 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 10:32 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> When revalidating the zones of a zoned block device, or when handling a
> zone which has a zone write plug flagged with
> BLK_ZONE_WPLUG_NEED_WP_UPDATE, the function
> disk_zone_wplug_sync_wp_offset() is used to update the write pointer
> offset of a zone write plug. However, this does not take into account the
> condition of the zone, which may have changed to readonly or offline,
> which in itself will always cause errors.
>
> In order to catch such errors, rename disk_zone_wplug_sync_wp_offset() to
> disk_zone_wplug_sync_state() and in addition to the zone write pointer,
> also update the zone write plug condition if the zone is readonly or
> offline.
>
> This change also requires changes to how a reset all zones operation
> (REQ_OP_ZONE_RESET_ALL) is handled so that the offline or read-only
> condition of zone write plugs is not overwritten with an erroneous empty
> condition. To do so, introduce the helper function
> disk_zone_is_offline_or_readonly() to skip updating the condition of zones
> that are read-only or offline and that do not have a zone write plug.
> For zones that have a zone write plug, the helper function
> disk_zone_wplug_is_offline_or_readonly() is used in
> disk_zone_wplug_set_wp_offset() to not update a zone write plug write
> pointer and condition for read-only and offline zones.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 52 +++++++++++++++++++++++++++++++++++++++--------
> 1 file changed, 43 insertions(+), 9 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 09/13] block: always treat offline and read-only zones as dead
2026-08-06 16:04 ` [PATCH v2 09/13] block: always treat offline and read-only zones as dead Damien Le Moal
@ 2026-08-07 10:46 ` Hannes Reinecke
2026-08-07 15:48 ` Damien Le Moal
0 siblings, 1 reply; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 10:46 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> Since any write BIO targeting an offline or a read-only zone will fail,
> there is no point in keeping zone write plugs for these zones.
> So for any offline or read-only zone, the zone write plug should always
> be treated as dead.
>
> Do this by modifying disk_check_zone_wplug_dead() to always mark read-only
> and offline zones as dead to force a removal of the zone write plug from
> the disk hash table on BIO submission. blk_zone_wplug_prepare_bio() is
> also modified to have the same checks to immediately fail a write BIO
> targeting a read-only or offline zone. With these two changes, any newly
> issued or unplugged write BIO targeting a read-only or offline zone is
> immediately failed.
>
> Finally, disk_zone_wplug_sync_state() is modified to add a call to
> disk_mark_zone_wplug_dead() for the zone write plug of any read-only or
> offline zone found during zone revalidation or a report zones.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 113 +++++++++++++++++++++++++---------------------
> 1 file changed, 61 insertions(+), 52 deletions(-)
>
Makes one feeling sorry for filesystems having to deal with this ..
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 10/13] block: fail zone management operations to read-only and offline zones
2026-08-06 16:04 ` [PATCH v2 10/13] block: fail zone management operations to read-only and offline zones Damien Le Moal
@ 2026-08-07 11:42 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 11:42 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> Any zone management operation targeting a zone that is in the read-only
> or offline condition will fail. So there is no point in issuing such
> BIO. Modify the check in submit_bio_noacct() to use the new helper
> function bdev_check_zone_mgmt() to check that a zone is sequential (as was
> checked before) and also that the zone is not offline nor read-only.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-core.c | 7 +++++--
> block/blk-zoned.c | 24 ++++++++++++++++++++++++
> block/blk.h | 6 ++++++
> 3 files changed, 35 insertions(+), 2 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 11/13] block: allow read-only and offline conventional zones
2026-08-06 16:04 ` [PATCH v2 11/13] block: allow read-only and offline conventional zones Damien Le Moal
@ 2026-08-07 11:48 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 11:48 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> With SCSI and ATA SMR HDDs, the storage element depopulation feature can
> change the condition of conventional zones to read-only (if a write head
> is depopulated) or to offline (if a read head is depopulated).
> However, the function blk_revalidate_zone_cond() currently does not allow
> these conditions for conventional zones, causing a zone revalidation
> failure.
>
> Remove blk_revalidate_zone_cond() and move the zone condition checks for
> conventional zones to blk_revalidate_conv_zone(), allowing the regular
> BLK_ZONE_COND_NOT_WP condition as well as the BLK_ZONE_COND_OFFLINE and
> BLK_ZONE_COND_READONLY conditions to match the conditions that can be
> seen from a zoned device with depopulated storage elements.
>
> The zone condition checks for sequential write required zones are moved
> to blk_revalidate_seq_zone() without any change to the conditions allowed.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 65 +++++++++++++++++++----------------------------
> 1 file changed, 26 insertions(+), 39 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 12/13] block: simplify disk_zone_set_cond()
2026-08-06 16:04 ` [PATCH v2 12/13] block: simplify disk_zone_set_cond() Damien Le Moal
@ 2026-08-07 11:51 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 11:51 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> disk_zone_set_cond() is used to set a zone condition afer a reset, a
afer -> after
> finish or a reset all operation. For a single zone reset or finish, we are
> guaranteed that the target zone is a sequential one that is not offline
> nor read-only (otherwise, the operation would have failed). For a reset
> all operation, there is no point in calling this function for offline and
> read-only zones since the condition checks in disk_zone_set_cond() will
> result in nothing being done.
>
> Simplify all this using disk_zone_is_offline_or_readonly() in
> blk_zone_reset_all_bio_endio() to skip zones that are offline or
> read-only. This change allows simplifying disk_zone_set_cond() by removing
> the zone condition checks. This change is also consistent with the fact
> that conventional zones can now have the offline or read-only condition.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 26 ++++++--------------------
> 1 file changed, 6 insertions(+), 20 deletions(-)
>
Otherwise:
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 13/13] block: flag zoned disks with GENHD_FL_NO_PART
2026-08-06 16:04 ` [PATCH v2 13/13] block: flag zoned disks with GENHD_FL_NO_PART Damien Le Moal
@ 2026-08-07 11:53 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-08-07 11:53 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 6:04 PM, Damien Le Moal wrote:
> Zoned block devices do not support partitions. However, the partition
> table is nevertheless still inspected, and any partition found ignored
> with a warning in add_partition(). While this is generally not a problem,
> and in fact beneficial to the user as it indicates an invalid use of a
> zoned block device, scanning for a partition table on the device may
> result in issuing read operations to offline zones (e.g. after a disk head
> is depopulated for disks that support head management operations).
>
> Since partitions are ignored anyway, completely disable partition scanning
> for zoned gendisks by setting the flag GENHD_FL_NO_PART in __add_disk().
> The existing check in add_partition() is left as-is to ensure that we
> still get a warning if for whatever reason, despite GENHD_FL_NO_PART, we
> still endup trying to add partitions.
>
> Flagging zoned disks with GENHD_FL_NO_PART also has the benefit to expose
> through sysfs the ext_range attribute with the value of 1 instead of the
> default DISK_MAX_PARTS, thus correctly advertizing the fact that zoned
> disks do not support partitions.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/genhd.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
Long overdue, if you ask me, and arguably not directly related to this
patchset. But anyway.
Reviewed-by: Hannes Reinecke <hare@suse.de>
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 00/13] Improve handling of offline and read-only zones
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
` (12 preceding siblings ...)
2026-08-06 16:04 ` [PATCH v2 13/13] block: flag zoned disks with GENHD_FL_NO_PART Damien Le Moal
@ 2026-08-07 15:34 ` Bart Van Assche
13 siblings, 0 replies; 27+ messages in thread
From: Bart Van Assche @ 2026-08-07 15:34 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 8/6/26 9:04 AM, Damien Le Moal wrote:
> This patch series refactor some zone related code and improves the
> caching iof zone information and the management of zone write plugs in
> preparation for fully supporting storage element depopulation.
> Most of the functional changes here are around the management of zone
> write plugs to better handle read-only and offline zones as these
> conditions can become more common with storage element being depopulated.
For the series:
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v2 09/13] block: always treat offline and read-only zones as dead
2026-08-07 10:46 ` Hannes Reinecke
@ 2026-08-07 15:48 ` Damien Le Moal
0 siblings, 0 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-08-07 15:48 UTC (permalink / raw)
To: Hannes Reinecke, Jens Axboe, linux-block; +Cc: Christoph Hellwig
On 2026/08/07 3:46, Hannes Reinecke wrote:
> On 8/6/26 6:04 PM, Damien Le Moal wrote:
>> Since any write BIO targeting an offline or a read-only zone will fail,
>> there is no point in keeping zone write plugs for these zones.
>> So for any offline or read-only zone, the zone write plug should always
>> be treated as dead.
>>
>> Do this by modifying disk_check_zone_wplug_dead() to always mark read-only
>> and offline zones as dead to force a removal of the zone write plug from
>> the disk hash table on BIO submission. blk_zone_wplug_prepare_bio() is
>> also modified to have the same checks to immediately fail a write BIO
>> targeting a read-only or offline zone. With these two changes, any newly
>> issued or unplugged write BIO targeting a read-only or offline zone is
>> immediately failed.
>>
>> Finally, disk_zone_wplug_sync_state() is modified to add a call to
>> disk_mark_zone_wplug_dead() for the zone write plug of any read-only or
>> offline zone found during zone revalidation or a report zones.
>>
>> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
>> ---
>> block/blk-zoned.c | 113 +++++++++++++++++++++++++---------------------
>> 1 file changed, 61 insertions(+), 52 deletions(-)
>>
> Makes one feeling sorry for filesystems having to deal with this ..
Yeah. If you depop a head under a mounted FS, the FS will not be happy :)
For file systems that can support it (e.g. XFS with metadata & log on a
different device), the plan is to execute depop going through the file system,
so that the FS can synchronize itself with zones going offline so that it does
not uselessly trip on IO errors and also can notify the user of affected files.
--
Damien Le Moal
Western Digital Research
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-08-07 15:48 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06 16:04 [PATCH v2 00/13] Improve handling of offline and read-only zones Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 01/13] block: remove disk_free_zone_resources() Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 02/13] block: refactor disk_revalidate_zone_resources() Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 03/13] block: refactor disk_update_zone_resources() Damien Le Moal
2026-08-07 9:03 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 04/13] block: remember a zone type regardless of its condition Damien Le Moal
2026-08-07 9:11 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 05/13] block: refactor bdev_zone_is_seq() Damien Le Moal
2026-08-07 9:12 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 06/13] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
2026-08-07 10:20 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 07/13] block: drop all zone write plugs on capacity changes Damien Le Moal
2026-08-07 10:24 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 08/13] block: propagate readonly and offline conditions to zone write plugs Damien Le Moal
2026-08-07 10:32 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 09/13] block: always treat offline and read-only zones as dead Damien Le Moal
2026-08-07 10:46 ` Hannes Reinecke
2026-08-07 15:48 ` Damien Le Moal
2026-08-06 16:04 ` [PATCH v2 10/13] block: fail zone management operations to read-only and offline zones Damien Le Moal
2026-08-07 11:42 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 11/13] block: allow read-only and offline conventional zones Damien Le Moal
2026-08-07 11:48 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 12/13] block: simplify disk_zone_set_cond() Damien Le Moal
2026-08-07 11:51 ` Hannes Reinecke
2026-08-06 16:04 ` [PATCH v2 13/13] block: flag zoned disks with GENHD_FL_NO_PART Damien Le Moal
2026-08-07 11:53 ` Hannes Reinecke
2026-08-07 15:34 ` [PATCH v2 00/13] Improve handling of offline and read-only zones Bart Van Assche
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.