Linux block layer
 help / color / mirror / Atom feed
* [PATCH 00/14] Improve handling of offline and read-only zones
@ 2026-08-05  2:27 Damien Le Moal
  2026-08-05  2:27 ` [PATCH 01/14] block: remove disk_free_zone_resources() Damien Le Moal
                   ` (13 more replies)
  0 siblings, 14 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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.

Damien Le Moal (14):
  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: fail reads to offline zones early

 block/blk-core.c       |  12 +-
 block/blk-zoned.c      | 795 +++++++++++++++++++++++++----------------
 block/blk.h            |  12 +
 block/genhd.c          |   7 +
 include/linux/blkdev.h |   2 +-
 5 files changed, 514 insertions(+), 314 deletions(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 22+ messages in thread

* [PATCH 01/14] block: remove disk_free_zone_resources()
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05 11:17   ` Hannes Reinecke
  2026-08-05 20:53   ` Bart Van Assche
  2026-08-05  2:27 ` [PATCH 02/14] block: refactor disk_revalidate_zone_resources() Damien Le Moal
                   ` (12 subsequent siblings)
  13 siblings, 2 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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>
---
 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..fd9d8878c15c 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,11 +1903,21 @@ 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;
 
 destroy_pool:
 	mempool_destroy(disk->zone_wplugs_pool);
 	disk->zone_wplugs_pool = NULL;
+stop_worker:
+	kthread_stop(disk->zone_wplugs_worker);
+	disk->zone_wplugs_worker = NULL;
 free_hash:
 	kfree(disk->zone_wplugs_hash);
 	disk->zone_wplugs_hash = 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] 22+ messages in thread

* [PATCH 02/14] block: refactor disk_revalidate_zone_resources()
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
  2026-08-05  2:27 ` [PATCH 01/14] block: remove disk_free_zone_resources() Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05 11:23   ` Hannes Reinecke
  2026-08-05  2:27 ` [PATCH 03/14] block: refactor disk_update_zone_resources() Damien Le Moal
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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>
---
 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 fd9d8878c15c..ba51e600ae92 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] 22+ messages in thread

* [PATCH 03/14] block: refactor disk_update_zone_resources()
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
  2026-08-05  2:27 ` [PATCH 01/14] block: remove disk_free_zone_resources() Damien Le Moal
  2026-08-05  2:27 ` [PATCH 02/14] block: refactor disk_revalidate_zone_resources() Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 04/14] block: remember a zone type regardless of its condition Damien Le Moal
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 ba51e600ae92..bfcd3cd99ec9 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] 22+ messages in thread

* [PATCH 04/14] block: remember a zone type regardless of its condition
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (2 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 03/14] block: refactor disk_update_zone_resources() Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05 21:20   ` Bart Van Assche
  2026-08-05  2:27 ` [PATCH 05/14] block: refactor bdev_zone_is_seq() Damien Le Moal
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 bfcd3cd99ec9..e5afef2bd7f1 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(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(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] 22+ messages in thread

* [PATCH 05/14] block: refactor bdev_zone_is_seq()
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (3 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 04/14] block: remember a zone type regardless of its condition Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 06/14] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 e5afef2bd7f1..60adc9a4c853 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] 22+ messages in thread

* [PATCH 06/14] block: introduce disk_for_all_zone_wplugs()
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (4 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 05/14] block: refactor bdev_zone_is_seq() Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05 21:25   ` Bart Van Assche
  2026-08-05  2:27 ` [PATCH 07/14] block: drop all zone write plugs on capacity changes Damien Le Moal
                   ` (7 subsequent siblings)
  13 siblings, 1 reply; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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>
---
 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 60adc9a4c853..3bca7b0437d5 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] 22+ messages in thread

* [PATCH 07/14] block: drop all zone write plugs on capacity changes
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (5 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 06/14] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 08/14] block: propagate readonly and offline conditions to zone write plugs Damien Le Moal
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 3bca7b0437d5..f065febbae97 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] 22+ messages in thread

* [PATCH 08/14] block: propagate readonly and offline conditions to zone write plugs
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (6 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 07/14] block: drop all zone write plugs on capacity changes Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 09/14] block: always treat offline and read-only zones as dead Damien Le Moal
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 f065febbae97..1560b000fb08 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] 22+ messages in thread

* [PATCH 09/14] block: always treat offline and read-only zones as dead
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (7 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 08/14] block: propagate readonly and offline conditions to zone write plugs Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 10/14] block: fail zone management operations to read-only and offline zones Damien Le Moal
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 1560b000fb08..069251171154 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] 22+ messages in thread

* [PATCH 10/14] block: fail zone management operations to read-only and offline zones
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (8 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 09/14] block: always treat offline and read-only zones as dead Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 11/14] block: allow read-only and offline conventional zones Damien Le Moal
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 069251171154..1d1175638763 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 eaac05815cb0..4024bd9251d4 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] 22+ messages in thread

* [PATCH 11/14] block: allow read-only and offline conventional zones
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (9 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 10/14] block: fail zone management operations to read-only and offline zones Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 12/14] block: simplify disk_zone_set_cond() Damien Le Moal
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 1d1175638763..3494f06ec3f1 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] 22+ messages in thread

* [PATCH 12/14] block: simplify disk_zone_set_cond()
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (10 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 11/14] block: allow read-only and offline conventional zones Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 13/14] block: flag zoned disks with GENHD_FL_NO_PART Damien Le Moal
  2026-08-05  2:27 ` [PATCH 14/14] block: fail reads to offline zones early Damien Le Moal
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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 3494f06ec3f1..e900c5d14414 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] 22+ messages in thread

* [PATCH 13/14] block: flag zoned disks with GENHD_FL_NO_PART
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (11 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 12/14] block: simplify disk_zone_set_cond() Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05  2:27 ` [PATCH 14/14] block: fail reads to offline zones early Damien Le Moal
  13 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 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] 22+ messages in thread

* [PATCH 14/14] block: fail reads to offline zones early
  2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
                   ` (12 preceding siblings ...)
  2026-08-05  2:27 ` [PATCH 13/14] block: flag zoned disks with GENHD_FL_NO_PART Damien Le Moal
@ 2026-08-05  2:27 ` Damien Le Moal
  2026-08-05 21:29   ` Bart Van Assche
  13 siblings, 1 reply; 22+ messages in thread
From: Damien Le Moal @ 2026-08-05  2:27 UTC (permalink / raw)
  To: Jens Axboe, linux-block; +Cc: Christoph Hellwig

We know that any operation targeting a zone that is offline will fail,
including read commands. So there is no point in issuing such reads.
Fail them early if we ever see one, and be quiet about the error.

Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
 block/blk-core.c  |  5 +++++
 block/blk-zoned.c | 17 +++++++++++++++++
 block/blk.h       |  6 ++++++
 3 files changed, 28 insertions(+)

diff --git a/block/blk-core.c b/block/blk-core.c
index 80a4e2bcc3f3..a5c4fcd210bb 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -866,6 +866,11 @@ void submit_bio_noacct(struct bio *bio)
 
 	switch (bio_op(bio)) {
 	case REQ_OP_READ:
+		if (bdev_is_zoned(bdev) &&
+		    bdev_zone_is_offline(bdev, bio->bi_iter.bi_sector)) {
+			bio_set_flag(bio, BIO_QUIET);
+			goto end_io;
+		}
 		break;
 	case REQ_OP_WRITE:
 		if (bio->bi_opf & REQ_ATOMIC) {
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index e900c5d14414..83185fc0cc4d 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -308,6 +308,23 @@ bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)
 }
 EXPORT_SYMBOL_GPL(bdev_zone_is_seq);
 
+/**
+ * bdev_zone_is_offline - check if a sector belongs to an offline zone
+ * @bdev:       block device to check
+ * @sector:     sector number
+ *
+ * Check if @sector on @bdev is contained in an offline zone.
+ */
+bool bdev_zone_is_offline(struct block_device *bdev, sector_t sector)
+{
+	enum blk_zone_cond cond = disk_zone_get_cond(bdev->bd_disk, sector);
+
+	if (!bdev_is_zoned(bdev))
+		return false;
+
+	return cond == BLK_ZONE_COND_OFFLINE;
+}
+
 /**
  * bdev_check_zone_mgmt - check if a sector belongs to a valid sequential zone
  * @bdev:       block device to check
diff --git a/block/blk.h b/block/blk.h
index 4024bd9251d4..636884424873 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -578,6 +578,7 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd,
 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);
+bool bdev_zone_is_offline(struct block_device *bdev, sector_t sector);
 #else /* CONFIG_BLK_DEV_ZONED */
 static inline void disk_init_zone_resources(struct gendisk *disk)
 {
@@ -625,6 +626,11 @@ static inline bool bdev_check_zone_mgmt(struct block_device *bdev,
 {
 	return false;
 }
+static inline bool bdev_zone_is_offline(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] 22+ messages in thread

* Re: [PATCH 01/14] block: remove disk_free_zone_resources()
  2026-08-05  2:27 ` [PATCH 01/14] block: remove disk_free_zone_resources() Damien Le Moal
@ 2026-08-05 11:17   ` Hannes Reinecke
  2026-08-05 20:53   ` Bart Van Assche
  1 sibling, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2026-08-05 11:17 UTC (permalink / raw)
  To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig

On 8/5/26 4:27 AM, Damien Le Moal wrote:
> 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>
> ---
>   block/blk-zoned.c | 56 +++++++++++++++++------------------------------
>   1 file changed, 20 insertions(+), 36 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] 22+ messages in thread

* Re: [PATCH 02/14] block: refactor disk_revalidate_zone_resources()
  2026-08-05  2:27 ` [PATCH 02/14] block: refactor disk_revalidate_zone_resources() Damien Le Moal
@ 2026-08-05 11:23   ` Hannes Reinecke
  0 siblings, 0 replies; 22+ messages in thread
From: Hannes Reinecke @ 2026-08-05 11:23 UTC (permalink / raw)
  To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig

On 8/5/26 4:27 AM, Damien Le Moal wrote:
> 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>
> ---
>   block/blk-zoned.c | 69 ++++++++++++++++++++++++-----------------------
>   1 file changed, 36 insertions(+), 33 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] 22+ messages in thread

* Re: [PATCH 01/14] block: remove disk_free_zone_resources()
  2026-08-05  2:27 ` [PATCH 01/14] block: remove disk_free_zone_resources() Damien Le Moal
  2026-08-05 11:17   ` Hannes Reinecke
@ 2026-08-05 20:53   ` Bart Van Assche
  1 sibling, 0 replies; 22+ messages in thread
From: Bart Van Assche @ 2026-08-05 20:53 UTC (permalink / raw)
  To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig

On 8/4/26 7:27 PM, Damien Le Moal wrote:
>   destroy_pool:
>   	mempool_destroy(disk->zone_wplugs_pool);
>   	disk->zone_wplugs_pool = NULL;
> +stop_worker:
> +	kthread_stop(disk->zone_wplugs_worker);
> +	disk->zone_wplugs_worker = NULL;
>   free_hash:
>   	kfree(disk->zone_wplugs_hash);
>   	disk->zone_wplugs_hash = NULL;

With this patch applied the kernel thread and workqueue are created
after the mempool has been created. Shouldn't the kthread_stop() call
occur before the mempool_destroy() call such that the mempool is
destroyed if workqueue allocation fails?

Thanks,

Bart.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 04/14] block: remember a zone type regardless of its condition
  2026-08-05  2:27 ` [PATCH 04/14] block: remember a zone type regardless of its condition Damien Le Moal
@ 2026-08-05 21:20   ` Bart Van Assche
  0 siblings, 0 replies; 22+ messages in thread
From: Bart Van Assche @ 2026-08-05 21:20 UTC (permalink / raw)
  To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig

On 8/4/26 7:27 PM, Damien Le Moal wrote:
> +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(idx >= ARRAY_SIZE(blk_zstate2zcond)))
> +		return 0;
> +
> +	return blk_zstate2zcond[idx];
> +}

Isn't WARN_ON_ONCE() preferred over WARN_ON()?

> +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(cond >= ARRAY_SIZE(blk_zcond2zstate)))
> +		return 0;
> +
> +	return blk_zcond2zstate[cond];
> +}

Same question here - isn't WARN_ON_ONCE() preferred over WARN_ON()?

>   		default:
> -			blk_zone_set_cond(zones_cond, zno, cond);
> +			blk_zstate_set(zones_state, zno, cond,
> +				       blk_zstate_flags(zones_state[zno]));

This is an open-coded implementation of FIELD_MODIFY(), isn't it?

Thanks,

Bart.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 06/14] block: introduce disk_for_all_zone_wplugs()
  2026-08-05  2:27 ` [PATCH 06/14] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
@ 2026-08-05 21:25   ` Bart Van Assche
  0 siblings, 0 replies; 22+ messages in thread
From: Bart Van Assche @ 2026-08-05 21:25 UTC (permalink / raw)
  To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig

On 8/4/26 7:27 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().

Reviewed-by: Bart Van Assche <bvanassche@acm.org>

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 14/14] block: fail reads to offline zones early
  2026-08-05  2:27 ` [PATCH 14/14] block: fail reads to offline zones early Damien Le Moal
@ 2026-08-05 21:29   ` Bart Van Assche
  2026-08-06 15:52     ` Damien Le Moal
  0 siblings, 1 reply; 22+ messages in thread
From: Bart Van Assche @ 2026-08-05 21:29 UTC (permalink / raw)
  To: Damien Le Moal, Jens Axboe, linux-block; +Cc: Christoph Hellwig

On 8/4/26 7:27 PM, Damien Le Moal wrote:
> diff --git a/block/blk-core.c b/block/blk-core.c
> index 80a4e2bcc3f3..a5c4fcd210bb 100644
> --- a/block/blk-core.c
> +++ b/block/blk-core.c
> @@ -866,6 +866,11 @@ void submit_bio_noacct(struct bio *bio)
>   
>   	switch (bio_op(bio)) {
>   	case REQ_OP_READ:
> +		if (bdev_is_zoned(bdev) &&
> +		    bdev_zone_is_offline(bdev, bio->bi_iter.bi_sector)) {
> +			bio_set_flag(bio, BIO_QUIET);
> +			goto end_io;
> +		}
>   		break;
>   	case REQ_OP_WRITE:
>   		if (bio->bi_opf & REQ_ATOMIC) {

This change might have a slight negative impact on the performance of 
the hot path for all block devices. Is this change really necessary?

Thanks,

Bart.

^ permalink raw reply	[flat|nested] 22+ messages in thread

* Re: [PATCH 14/14] block: fail reads to offline zones early
  2026-08-05 21:29   ` Bart Van Assche
@ 2026-08-06 15:52     ` Damien Le Moal
  0 siblings, 0 replies; 22+ messages in thread
From: Damien Le Moal @ 2026-08-06 15:52 UTC (permalink / raw)
  To: Bart Van Assche, Jens Axboe, linux-block; +Cc: Christoph Hellwig

On 2026/08/05 14:29, Bart Van Assche wrote:
> On 8/4/26 7:27 PM, Damien Le Moal wrote:
>> diff --git a/block/blk-core.c b/block/blk-core.c
>> index 80a4e2bcc3f3..a5c4fcd210bb 100644
>> --- a/block/blk-core.c
>> +++ b/block/blk-core.c
>> @@ -866,6 +866,11 @@ void submit_bio_noacct(struct bio *bio)
>>   
>>   	switch (bio_op(bio)) {
>>   	case REQ_OP_READ:
>> +		if (bdev_is_zoned(bdev) &&
>> +		    bdev_zone_is_offline(bdev, bio->bi_iter.bi_sector)) {
>> +			bio_set_flag(bio, BIO_QUIET);
>> +			goto end_io;
>> +		}
>>   		break;
>>   	case REQ_OP_WRITE:
>>   		if (bio->bi_opf & REQ_ATOMIC) {
> 
> This change might have a slight negative impact on the performance of 
> the hot path for all block devices. Is this change really necessary?

It is mostly intended to avoid command failures with SATA SMR drives, because
NCQ failures with SATA are costly for performance (queue drain and retries).
But given that this sort of invalid access should be rare, I can drop this for
now and revisit later if necessary.


-- 
Damien Le Moal
Western Digital Research

^ permalink raw reply	[flat|nested] 22+ messages in thread

end of thread, other threads:[~2026-08-06 15:52 UTC | newest]

Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05  2:27 [PATCH 00/14] Improve handling of offline and read-only zones Damien Le Moal
2026-08-05  2:27 ` [PATCH 01/14] block: remove disk_free_zone_resources() Damien Le Moal
2026-08-05 11:17   ` Hannes Reinecke
2026-08-05 20:53   ` Bart Van Assche
2026-08-05  2:27 ` [PATCH 02/14] block: refactor disk_revalidate_zone_resources() Damien Le Moal
2026-08-05 11:23   ` Hannes Reinecke
2026-08-05  2:27 ` [PATCH 03/14] block: refactor disk_update_zone_resources() Damien Le Moal
2026-08-05  2:27 ` [PATCH 04/14] block: remember a zone type regardless of its condition Damien Le Moal
2026-08-05 21:20   ` Bart Van Assche
2026-08-05  2:27 ` [PATCH 05/14] block: refactor bdev_zone_is_seq() Damien Le Moal
2026-08-05  2:27 ` [PATCH 06/14] block: introduce disk_for_all_zone_wplugs() Damien Le Moal
2026-08-05 21:25   ` Bart Van Assche
2026-08-05  2:27 ` [PATCH 07/14] block: drop all zone write plugs on capacity changes Damien Le Moal
2026-08-05  2:27 ` [PATCH 08/14] block: propagate readonly and offline conditions to zone write plugs Damien Le Moal
2026-08-05  2:27 ` [PATCH 09/14] block: always treat offline and read-only zones as dead Damien Le Moal
2026-08-05  2:27 ` [PATCH 10/14] block: fail zone management operations to read-only and offline zones Damien Le Moal
2026-08-05  2:27 ` [PATCH 11/14] block: allow read-only and offline conventional zones Damien Le Moal
2026-08-05  2:27 ` [PATCH 12/14] block: simplify disk_zone_set_cond() Damien Le Moal
2026-08-05  2:27 ` [PATCH 13/14] block: flag zoned disks with GENHD_FL_NO_PART Damien Le Moal
2026-08-05  2:27 ` [PATCH 14/14] block: fail reads to offline zones early Damien Le Moal
2026-08-05 21:29   ` Bart Van Assche
2026-08-06 15:52     ` Damien Le Moal

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox