* [PATCH v8 00/12] Add support for storage element depopulation
@ 2026-11-09 9:51 Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 01/12] block: introduce disk_zone_is_offline() Damien Le Moal
` (11 more replies)
0 siblings, 12 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Jens,
These patches define a new set of block device operations for generically
using from the block layer the storage element depopulation feature of
zoned block devices. Support for this feature is added to the SCSI disk
driver and an emulation of this feature added to the zloop driver.
The management operations can be accessed using block layer API, which is
intended for file systems (e.g. zonefs and XFS), as well as using ioctls
for users using zoned disks directly from user space.
Changes from v7:
A significant amount of changes in this version to address most of the
Sashiko review comments and to simplify the code. In more details, the
changes are:
- Added patches 3, 6, and 9.
- Dropped former patch 5 as the use of irq-disabling spinlocks in zloop
is not necessary.
- Reworked patch 4 by migrating the idepop operation wait loops to the
device drivers. This simplifies the implementation in zloop without
making the sd driver implementation more difficult. Also fixed problems
with errors during restoration and zone write plugs state.
- Reworked patch 7 and 11 to be consistent with the changes in patch 4.
Changes from v6:
- Addressed the nit pointed out by Christoph in patch 4
- Added review tags
Changes from v5:
- Rebased on latest block/for-next
- Addressed Christoph's comments on patch 3
- Added patch 5 to address a potential deadlock in zloop (signaled by
sashiko)
- Reworked the restoration of storage elements in patch 6 to avoid an
inconsistent state of storage elements in case of error. Also added
protection against disk removal during storage element removal and
restoration execution.
- Added review tags
Changes from v4:
- Rebased on latest block/for-next
- Added review tags to patch 1 and 2
- Reworked patch 3, 4 and 5 to address Christoph's and sashiko comments
Changes from v3:
- Reworked patch 1 to only provide the disk_zone_is_offline() helper
function. Failing of read operations to offline zones is handled
directly by the zloop driver and the new patch 7 is added to handle
failing early read operations to offline zones of ZBC SCSI disks.
- Added patch 2
- Addressed further valid comments from Sashiko
The biggest changes are in patch 2 to save the number of storage
elements of a disk in struct gendisk and to serialize the execution of
the storage element management operations using the zone revalidation
mutex.
- Rebased on latest block/for-next
Changes from v2:
- Addressed further valid comments from Sashiko
- Rebased on latest block/for-next
Changes from v1:
- Addressed the many valid comments that Sashiko had
- Rebased on latest block/for-next
Damien Le Moal (12):
block: introduce disk_zone_is_offline()
block: refactor blk_revalidate_disk_zones()
block: allow dropping all zone write plugs on revalidation
block: introduce storage element management
block: add storage element management ioctls
zloop: fix zone reporting consistency
zloop: add storage element emulation
zloop: add degrade_element control command
scsi: scsi_proto: refine the definition of storage element health
scsi: sd_zbc: always revalidate zones for disks supporting head
depopulation
scsi: sd_zbc: define storage element management operations
scsi: sd: fail read commands to offline zones early
.../admin-guide/blockdev/zoned_loop.rst | 33 +-
block/blk-zoned.c | 450 +++++++++-
block/blk.h | 14 +
block/genhd.c | 4 +
block/ioctl.c | 4 +
drivers/block/zloop.c | 786 +++++++++++++++++-
drivers/scsi/sd.c | 23 +-
drivers/scsi/sd.h | 5 +
drivers/scsi/sd_zbc.c | 516 +++++++++++-
include/linux/blkdev.h | 64 ++
include/scsi/scsi_proto.h | 6 +-
include/uapi/linux/blkzoned.h | 106 +++
include/uapi/linux/fs.h | 1 +
13 files changed, 1933 insertions(+), 79 deletions(-)
--
2.56.0
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH v8 01/12] block: introduce disk_zone_is_offline()
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 02/12] block: refactor blk_revalidate_disk_zones() Damien Le Moal
` (10 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Introduce the helper function disk_zone_is_offline() for testing if a zone
of a gendisk has the offline condition. The zone to test is identified
using a sector number that must belong to the target zone to check.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Martin K. Petersen <mkp@kernel.org>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
---
block/blk-zoned.c | 18 ++++++++++++++++++
include/linux/blkdev.h | 9 +++++++++
2 files changed, 27 insertions(+)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 4c5889296191..07d504287024 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -292,6 +292,24 @@ static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector)
return !blk_zstate_is_conv(zs);
}
+/**
+ * disk_zone_is_offline - check if a sector belongs to an offline zone
+ * @disk: gendisk to check
+ * @sector: sector number
+ *
+ * Check if @sector on @disk is contained in an offline zone.
+ * This function can be used only when the bdev queue was entered (BIOs
+ * submitted but not yet completed) or when holding zone_revalidate_mutex.
+ */
+bool disk_zone_is_offline(struct gendisk *disk, sector_t sector)
+{
+ if (!blk_queue_is_zoned(disk->queue))
+ return false;
+
+ return disk_zone_get_cond(disk, sector) == BLK_ZONE_COND_OFFLINE;
+}
+EXPORT_SYMBOL_GPL(disk_zone_is_offline);
+
/**
* bdev_zone_is_seq - check if a sector belongs to a sequential write zone
* @bdev: block device to check
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 215753171cef..a2c36b4e6308 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -940,6 +940,9 @@ static inline unsigned int disk_zone_capacity(struct gendisk *disk,
return disk->last_zone_capacity;
return disk->zone_capacity;
}
+
+bool disk_zone_is_offline(struct gendisk *disk, sector_t sector);
+
static inline unsigned int bdev_zone_capacity(struct block_device *bdev,
sector_t pos)
{
@@ -954,6 +957,12 @@ static inline unsigned int disk_nr_zones(struct gendisk *disk)
return 0;
}
+static inline bool disk_zone_is_offline(struct gendisk *disk,
+ sector_t sector)
+{
+ return false;
+}
+
static inline bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)
{
return false;
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 02/12] block: refactor blk_revalidate_disk_zones()
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 01/12] block: introduce disk_zone_is_offline() Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 03/12] block: allow dropping all zone write plugs on revalidation Damien Le Moal
` (9 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Split out most of the code of blk_revalidate_disk_zones() into the
internal function disk_revalidate_zones(). This will allow calling this
new helper together with other code under the zone revalidation mutex.
No functional change.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Martin K. Petersen <mkp@kernel.org>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
---
block/blk-zoned.c | 63 +++++++++++++++++++++++++----------------------
1 file changed, 34 insertions(+), 29 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 07d504287024..600d6bd008ae 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -2552,20 +2552,7 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return ret;
}
-/**
- * blk_revalidate_disk_zones - (re)allocate and initialize zone write plugs
- * @disk: Target disk
- *
- * Helper function for low-level device drivers to check, (re) allocate and
- * initialize resources used for managing zoned disks. This function should
- * normally be called by blk-mq based drivers when a zoned gendisk is probed
- * and when the zone configuration of the gendisk changes (e.g. after a format).
- * Before calling this function, the device driver must already have set the
- * device zone size (chunk_sector limit) and the max zone append limit.
- * BIO based drivers can also use this function as long as the device queue
- * can be safely frozen.
- */
-int blk_revalidate_disk_zones(struct gendisk *disk)
+static int disk_revalidate_zones(struct gendisk *disk)
{
struct blk_revalidate_zone_args args = { };
struct blk_report_zones_args rep_args = {
@@ -2576,19 +2563,10 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
int retries = 2;
int ret;
- if (WARN_ON_ONCE(!blk_queue_is_zoned(disk->queue)))
- return -EIO;
-
- /*
- * Serialize calls to this function so that we can safely look at and
- * eventually change the disk zone information.
- */
- mutex_lock(&disk->zone_revalidate_mutex);
-
again:
ret = disk_revalidate_capacity(disk, &args);
if (ret)
- goto unlock;
+ return ret;
/*
* Allocate zone resources if they are needed and we have not done
@@ -2601,14 +2579,14 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
ret = disk_alloc_zone_resources(disk, args.capacity);
if (ret) {
memalloc_noio_restore(noio_flag);
- goto unlock;
+ return ret;
}
}
ret = disk_init_revalidate_args(disk, &args);
if (ret) {
memalloc_noio_restore(noio_flag);
- goto unlock;
+ return ret;
}
ret = disk->fops->report_zones(disk, 0, UINT_MAX, &rep_args);
@@ -2625,8 +2603,6 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
if (ret)
goto free_args;
- mutex_unlock(&disk->zone_revalidate_mutex);
-
return 0;
free_args:
@@ -2642,7 +2618,36 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
}
pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
-unlock:
+
+ return ret;
+}
+
+/**
+ * blk_revalidate_disk_zones - (re)allocate and initialize zone write plugs
+ * @disk: Target disk
+ *
+ * Helper function for low-level device drivers to check, (re) allocate and
+ * initialize resources used for managing zoned disks. This function should
+ * normally be called by blk-mq based drivers when a zoned gendisk is probed
+ * and when the zone configuration of the gendisk changes (e.g. after a format).
+ * Before calling this function, the device driver must already have set the
+ * device zone size (chunk_sector limit) and the max zone append limit.
+ * BIO based drivers can also use this function as long as the device queue
+ * can be safely frozen.
+ */
+int blk_revalidate_disk_zones(struct gendisk *disk)
+{
+ int ret;
+
+ if (WARN_ON_ONCE(!blk_queue_is_zoned(disk->queue)))
+ return -EIO;
+
+ /*
+ * Serialize calls to this function so that we can safely look at and
+ * eventually change the disk zone information.
+ */
+ mutex_lock(&disk->zone_revalidate_mutex);
+ ret = disk_revalidate_zones(disk);
mutex_unlock(&disk->zone_revalidate_mutex);
return ret;
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 03/12] block: allow dropping all zone write plugs on revalidation
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 01/12] block: introduce disk_zone_is_offline() Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 02/12] block: refactor blk_revalidate_disk_zones() Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:13 ` Hannes Reinecke
2026-11-11 15:23 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 04/12] block: introduce storage element management Damien Le Moal
` (8 subsequent siblings)
11 siblings, 2 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
In preparation for supporting storage element depopulation, modify the
function disk_revalidate_zones() to take an additional boolean parameter
drop_all_zwplugs to force removing all zone write plugs from a disk hash
table. Removing all zone write plugs is done in the same manner as for a
capacity change, using the function disk_drop_zone_wplug() applied to all
hashed zone write plugs.
Calls to disk_revalidate_zones() with the argument drop_all_zwplugs set to
true will be used when restoring depopulated storage elements as such
operation clears all sequential zones, modifying them to be empty.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 600d6bd008ae..3761e634650e 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -2552,7 +2552,7 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return ret;
}
-static int disk_revalidate_zones(struct gendisk *disk)
+static int disk_revalidate_zones(struct gendisk *disk, bool drop_all_zwplugs)
{
struct blk_revalidate_zone_args args = { };
struct blk_report_zones_args rep_args = {
@@ -2563,6 +2563,9 @@ static int disk_revalidate_zones(struct gendisk *disk)
int retries = 2;
int ret;
+ if (drop_all_zwplugs)
+ disk_for_all_zone_wplugs(disk, disk_drop_zone_wplug, NULL);
+
again:
ret = disk_revalidate_capacity(disk, &args);
if (ret)
@@ -2647,7 +2650,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
* eventually change the disk zone information.
*/
mutex_lock(&disk->zone_revalidate_mutex);
- ret = disk_revalidate_zones(disk);
+ ret = disk_revalidate_zones(disk, false);
mutex_unlock(&disk->zone_revalidate_mutex);
return ret;
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 04/12] block: introduce storage element management
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (2 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 03/12] block: allow dropping all zone write plugs on revalidation Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:14 ` Hannes Reinecke
2026-11-11 15:24 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 05/12] block: add storage element management ioctls Damien Le Moal
` (7 subsequent siblings)
11 siblings, 2 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Recent SCSI (SBC) and ATA (ACS) standards define the storage element
depopulation feature. This feature is intended for managing hard-disks
heads, either combined read-write heads or pairs of read and write heads,
allowing to keep disks with defective heads longer in production by
allowing "depopulating" (removing) defective heads.
This feature comes in two different flavors:
- A destructive version which removes a head and reformats the disk at a
lower capacity point, restoring a fully functional contiguous LBA
address space.
- A data preserving version restricted to host-managed zoned disks, which
marks the zones served by a removed head as offline or read-only.
In preparation for using the data-preserving flavor of the depopulation
feature in file systems natively supporting zoned block devices, introduce
a set of storage element management operations and functions to define
generic calls into block device drivers for managing storage elements.
The set of operations is defined with struct blk_se_ops and includes three
operations:
- report_elements: get information on a device storage elements state
reported with the new struct blk_se.
- remove_element: depopulate a defective storage element, blocking until
the operation completes.
- restore_elements: restore depopulated storage elements, blocking until
the operation completes.
Each operation is called from the functions
bdev_report_storage_elements(), bdev_remove_storage_element() and
bdev_restore_storage_elements() respectively.
Removing a healthy storage element from a device is possible and useful
for testing. The storage element restoration operation restore_elements
allows repopulating such healthy element. Repopulating defective storage
elements is generally not allowed by devices.
The remove_element and restore_elements operations are executed under the
zone revalidation lock so that they are serialized against each other and
also serialized against user initiated device revalidation, thus allowing
to always have a consistent view of the device zone conditions after
executing these operations. In case of failure, zone revalidation is still
executed, which may give an unstable view of the zone states. In such
case, stable zone states will be recovered with zone access error handling
or later zone revalidation.
The device drivers of zoned block devices can indicate support for the
storage element depopulation feature by specifying the storage element
management operations with the se_ops field of struct
block_device_operations.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 205 ++++++++++++++++++++++++++++++++++
block/blk.h | 5 +
block/genhd.c | 4 +
include/linux/blkdev.h | 55 +++++++++
include/uapi/linux/blkzoned.h | 65 +++++++++++
5 files changed, 334 insertions(+)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 3761e634650e..30e61dbd89d6 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -2736,3 +2736,208 @@ int queue_zone_wplugs_show(void *data, struct seq_file *m)
}
#endif
+
+/**
+ * bdev_report_storage_elements - report the storage elements of a zoned
+ * block device
+ * @bdev: the zoned block device to check
+ * @elements: array of storage element descriptors to fill
+ * @nr_elements: maximum size of @elements
+ *
+ * Fill at most @nr_elements storage element descriptors in the array
+ * @elements. The number of storage elements filled in the array is returned
+ * using @nr_elements. If @elements is NULL, only @nr_elements is returned to
+ * indicate the maximum number of storage elements of @bdev. This function may
+ * sleep.
+ *
+ * Returns 0 on success and a negative error code on failure.
+ */
+int bdev_report_storage_elements(struct block_device *bdev,
+ struct blk_se *elements,
+ unsigned int *nr_elements)
+{
+ struct gendisk *disk = bdev->bd_disk;
+
+ if (test_bit(GD_DEAD, &disk->state))
+ return -ENODEV;
+
+ if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements)
+ return -EOPNOTSUPP;
+
+ if (!nr_elements)
+ return -EINVAL;
+
+ if (!elements) {
+ *nr_elements = disk->nr_storage_elements;
+ return 0;
+ }
+
+ *nr_elements = min(*nr_elements, disk->nr_storage_elements);
+ if (!*nr_elements)
+ return -EINVAL;
+
+ return disk->fops->se_ops->report_elements(disk, elements,
+ nr_elements);
+}
+EXPORT_SYMBOL_GPL(bdev_report_storage_elements);
+
+/**
+ * bdev_remove_storage_element - Remove (depopulate) a storage element of a
+ * zoned block device
+ * @bdev: block device to operate on
+ * @element_id: ID of the storage element to remove
+ *
+ * Remove (depopulate) the storage element identified by @element_id from the
+ * block device @bdev. The caller is responsible for taking care of any
+ * necessary dirty page and device write cache flush and invalidation of cached
+ * data. This function will block until the storage element is removed or an
+ * error occurs.
+ *
+ * Returns 0 on success and a negative error code on failure.
+ */
+int bdev_remove_storage_element(struct block_device *bdev,
+ unsigned int element_id)
+{
+ struct gendisk *disk = bdev->bd_disk;
+ unsigned int memflags;
+ int ret, err;
+
+ if (test_bit(GD_DEAD, &disk->state))
+ return -ENODEV;
+
+ if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements)
+ return -EOPNOTSUPP;
+
+ /* Zero is not a valid storage element ID. */
+ if (!element_id)
+ return -EINVAL;
+
+ /*
+ * Serialize against concurrent revalidation and concurrent storage
+ * element management operations so that we can see consistent zone
+ * condition changes.
+ */
+ mutex_lock(&disk->zone_revalidate_mutex);
+
+ /*
+ * Freeze and unfreeze the queue to flush any outstanding command.
+ * The caller is responsible for not queuing up more I/Os by higher
+ * level means.
+ */
+ memflags = blk_mq_freeze_queue(disk->queue);
+ blk_mq_unfreeze_queue(disk->queue, memflags);
+
+ /* Initiate the storage element removal and wait for its completion. */
+ ret = disk->fops->se_ops->remove_element(disk, element_id);
+
+ /*
+ * Always revalidate zones, even in the case of an error as we do not
+ * know what condition the zones of the block device are in.
+ */
+ err = disk_revalidate_zones(disk, false);
+ if (err && !ret)
+ ret = err;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bdev_remove_storage_element);
+
+/**
+ * bdev_restore_storage_elements - Restore all depopulated storage elements of
+ * a zoned block device
+ * @bdev: block device to operate on
+ *
+ * Restore all storage elements of @bdev that have been depopulated. Not all
+ * elements may be restored by this operation. This operation is destructive and
+ * will result in all sequential zones to be transitioned to the empty condition.
+ * The caller is responsible for taking care of any necessary invalidation of
+ * cached data. This function will block until the storage elements are restored
+ * or an error occurs.
+ *
+ * Returns 0 on success and a negative error code on failure.
+ */
+int bdev_restore_storage_elements(struct block_device *bdev)
+{
+ struct gendisk *disk = bdev->bd_disk;
+ unsigned int memflags;
+ int ret, err;
+
+ if (test_bit(GD_DEAD, &disk->state))
+ return -ENODEV;
+
+ if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements)
+ return -EOPNOTSUPP;
+
+ /*
+ * Serialize against concurrent revalidation and concurrent storage
+ * element management operations so that we can see consistent zone
+ * condition changes.
+ */
+ mutex_lock(&disk->zone_revalidate_mutex);
+
+ /*
+ * Freeze and unfreeze the queue to flush any outstanding commands.
+ * The caller is responsible for not queuing up more I/O by higher
+ * level means.
+ */
+ memflags = blk_mq_freeze_queue(disk->queue);
+ blk_mq_unfreeze_queue(disk->queue, memflags);
+
+ /*
+ * Initiate the storage elements restoration and wait for its
+ * completion.
+ */
+ ret = disk->fops->se_ops->restore_elements(disk);
+
+ /*
+ * Always revalidate zones, even in the case of an error as we do not
+ * know what condition the zones of the block device are in. Note that
+ * since all sequential zones should have been transitioned to the empty
+ * condition, we can drop all zone write plugs. If an error occured and
+ * not all sequential zones are empty, the revalidation will reinstate
+ * the write plugs for these zones.
+ */
+ err = disk_revalidate_zones(disk, true);
+ if (err && !ret)
+ ret = err;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bdev_restore_storage_elements);
+
+int disk_init_storage_element_mgmt(struct gendisk *disk)
+{
+ int ret;
+
+ /*
+ * Storage element operations are for zoned disks only and are
+ * optional...
+ */
+ if (!blk_queue_is_zoned(disk->queue) || !disk->fops->se_ops)
+ return 0;
+
+ /* ...but if defined, all operation callbacks must be defined. */
+ if (!disk->fops->se_ops->report_elements ||
+ !disk->fops->se_ops->remove_element ||
+ !disk->fops->se_ops->restore_elements) {
+ pr_err("%s: invalid storage element operations\n",
+ disk->disk_name);
+ return -EINVAL;
+ }
+
+ /*
+ * Get the number of storage elements of the disk. If this fails,
+ * do not report an error as the failure may be because the zoned disk
+ * does not support storage element management.
+ */
+ ret = disk->fops->se_ops->report_elements(disk, NULL,
+ &disk->nr_storage_elements);
+ if (ret)
+ disk->nr_storage_elements = 0;
+
+ return 0;
+}
diff --git a/block/blk.h b/block/blk.h
index 0e38e4b88cf5..05e2692b80d5 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -543,6 +543,7 @@ static inline void ioc_clear_queue(struct request_queue *q)
#endif /* CONFIG_BLK_ICQ */
#ifdef CONFIG_BLK_DEV_ZONED
+int disk_init_storage_element_mgmt(struct gendisk *disk);
void disk_init_zone_resources(struct gendisk *disk);
void disk_release_zone_resources(struct gendisk *disk);
static inline bool bio_zone_write_plugging(struct bio *bio)
@@ -595,6 +596,10 @@ int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg);
bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector);
#else /* CONFIG_BLK_DEV_ZONED */
+static inline int disk_init_storage_element_mgmt(struct gendisk *disk)
+{
+ return 0;
+}
static inline void disk_init_zone_resources(struct gendisk *disk)
{
}
diff --git a/block/genhd.c b/block/genhd.c
index 69204d44adae..251a5b07baff 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -435,6 +435,10 @@ static int __add_disk(struct device *parent, struct gendisk *disk,
if (WARN_ON_ONCE(bdev_nr_sectors(disk->part0) > BLK_DEV_MAX_SECTORS))
return -EINVAL;
+ ret = disk_init_storage_element_mgmt(disk);
+ if (ret)
+ return ret;
+
if (queue_is_mq(disk->queue)) {
/*
* ->submit_bio and ->poll_bio are bypassed for blk-mq drivers.
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index a2c36b4e6308..63c6e43ecdda 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -189,6 +189,11 @@ struct gendisk {
struct disk_events *ev;
#ifdef CONFIG_BLK_DEV_ZONED
+ /*
+ * Total number of storage elements of the disk. This is always 0 for
+ * disks that do not support storage element management.
+ */
+ unsigned int nr_storage_elements;
/*
* Zoned block device information. Reads of this information must be
* protected with blk_queue_enter() / blk_queue_exit() or by holding a
@@ -951,6 +956,13 @@ static inline unsigned int bdev_zone_capacity(struct block_device *bdev,
bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector);
+int bdev_report_storage_elements(struct block_device *bdev,
+ struct blk_se *elements,
+ unsigned int *nr_elements);
+int bdev_remove_storage_element(struct block_device *bdev,
+ unsigned int element_id);
+int bdev_restore_storage_elements(struct block_device *bdev);
+
#else /* CONFIG_BLK_DEV_ZONED */
static inline unsigned int disk_nr_zones(struct gendisk *disk)
{
@@ -977,6 +989,25 @@ static inline bool blk_zone_plug_bio(struct bio *bio, unsigned int nr_segs)
{
return false;
}
+
+static inline int bdev_report_storage_elements(struct block_device *bdev,
+ struct blk_se *elements,
+ unsigned int *nr_elements)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int bdev_remove_storage_element(struct block_device *bdev,
+ unsigned int element_id)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int bdev_restore_storage_elements(struct block_device *bdev)
+{
+ return -EOPNOTSUPP;
+}
+
#endif /* CONFIG_BLK_DEV_ZONED */
static inline unsigned int bdev_nr_zones(struct block_device *bdev)
@@ -1581,6 +1612,29 @@ enum blk_unique_id {
BLK_UID_NAA = 3,
};
+/**
+ * struct blk_se_ops - Storage element management operations.
+ * @report_elements: Report at most nr_elements storage elements of the
+ * specified disk. If the elements argument is NULL, only
+ * report the number of storage elements of the target disk.
+ * @remove_element: Remove (depopulate) the specified storage element from the
+ * target disk. This is a blocking operation called with
+ * zone_revalidate_mutex held. This callback must return only
+ * after the remove operation completes or an error occurs.
+ * @restore_elements: Restore (re-populate), if possible, all removed storage
+ * elements of the target disk. This operation is called with
+ * zone_revalidate_mutex held. This callback must return only
+ * after the restoration operation completes or an error
+ * occurs.
+ */
+struct blk_se_ops {
+ int (*report_elements)(struct gendisk *disk,
+ struct blk_se *elements,
+ unsigned int *nr_elements);
+ int (*remove_element)(struct gendisk *disk, unsigned int element_id);
+ int (*restore_elements)(struct gendisk *disk);
+};
+
struct block_device_operations {
void (*submit_bio)(struct bio *bio);
int (*poll_bio)(struct bio *bio, struct io_comp_batch *iob,
@@ -1610,6 +1664,7 @@ struct block_device_operations {
struct dma_buf_io_ctx *);
struct module *owner;
const struct pr_ops *pr_ops;
+ const struct blk_se_ops *se_ops;
/*
* Special callback for probing GPT entry at a given sector.
diff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h
index 663836120966..9d7f59da3efc 100644
--- a/include/uapi/linux/blkzoned.h
+++ b/include/uapi/linux/blkzoned.h
@@ -208,4 +208,69 @@ struct blk_zone_range {
#define BLKFINISHZONE _IOW(0x12, 136, struct blk_zone_range)
#define BLKREPORTZONEV2 _IOWR(0x12, 142, struct blk_zone_report)
+/**
+ * enum blk_se_type - Type of a zoned device storage element.
+ *
+ * @BLK_SE_TYPE_RDWR: The storage element handles both reads and writes.
+ * @BLK_SE_TYPE_READ: The storage element handles reads only.
+ * @BLK_SE_TYPE_WRITE: The storage element handles writes only.
+ * @BLK_SE_TYPE_UNKNOWN: The storage element type is not known.
+ */
+enum blk_se_type {
+ BLK_SE_TYPE_RDWR = 0x01,
+ BLK_SE_TYPE_READ = 0x02,
+ BLK_SE_TYPE_WRITE = 0x03,
+ BLK_SE_TYPE_UNKNOWN = 0xFF,
+};
+
+/**
+ * enum blk_se_status - Status of a zoned device storage element.
+ *
+ * @BLK_SE_STS_OK: The storage element is operating normally.
+ * @BLK_SE_STS_OK_LIMIT: The storage element is operating at the manufacturer
+ * specifications limit.
+ * @BLK_SE_STS_DEGRADED: The storage element has degraded and is not operating
+ * normally.
+ * @BLK_SE_STS_REMOVE_IN_PROGRESS: The storage element is being removed.
+ * @BLK_SE_STS_REMOVE_ERROR: The storage element removal failed.
+ * @BLK_SE_STS_RESTORE_IN_PROGRESS: The storage element is being restored.
+ * @BLK_SE_STS_RESTORE_ERROR: The storage element restoration failed.
+ * @BLK_SE_STS_REMOVED: The storage element was removed.
+ * @BLK_SE_STS_UNKNOWN: The storage element status is unknown.
+ */
+enum blk_se_status {
+ BLK_SE_STS_OK = 0x01,
+ BLK_SE_STS_OK_LIMIT = 0x02,
+ BLK_SE_STS_DEGRADED = 0x03,
+ BLK_SE_STS_REMOVE_IN_PROGRESS = 0x04,
+ BLK_SE_STS_REMOVE_ERROR = 0x05,
+ BLK_SE_STS_RESTORE_IN_PROGRESS = 0x06,
+ BLK_SE_STS_RESTORE_ERROR = 0x07,
+ BLK_SE_STS_REMOVED = 0x08,
+ BLK_SE_STS_UNKNOWN = 0xFF,
+};
+
+/**
+ * struct blk_se - Zoned device storage element descriptor.
+ *
+ * @id: The ID of the element (cannot be 0).
+ * @paired_id: The ID of the paired element for an element that is not
+ * of type BLK_SE_TYPE_RDWR.
+ * @nr_zones: The number of zones that the storage element handles.
+ * @type: The type of the storage element (enum blk_se_type).
+ * @status: The health status of the storage element
+ * (enum blk_se_status).
+ * @restore_allowed: Indicate if the storage element can be restored.
+ * @reserved: unused field to align this structure size to 32B
+ */
+struct blk_se {
+ __u32 id;
+ __u32 paired_id;
+ __u64 nr_zones;
+ __u8 type;
+ __u8 status;
+ __u8 restore_allowed;
+ __u8 reserved[13];
+};
+
#endif /* _UAPI_BLKZONED_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 05/12] block: add storage element management ioctls
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (3 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 04/12] block: introduce storage element management Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:15 ` Hannes Reinecke
2026-11-11 15:25 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 06/12] zloop: fix zone reporting consistency Damien Le Moal
` (6 subsequent siblings)
11 siblings, 2 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Define the new ioctl commands BLKREPORTSTORELEMS, BLKREMOVESTORELEM, and
BLKRESTORESTORELEMS to provide users accessing zoned block devices
directly with an interface to the storage elements management operations
of the block layer.
The ioctl BLKREPORTSTORELEMS allows to get the total number of storage
elements of a zoned block device and to get an array of storage element
descriptors (struct blk_se) describing the current state of the device
storage elements.
The ioctl BLKREMOVESTORELEM can be used to remove (depopulate) a storage
element, in particular storage elements that have a degraded status
(i.e. BLK_SE_STS_DEGRADED). But healthy elements (BLK_SE_STS_OK) can also
be removed, and later restored.
Finally, the BLKRESTORESTORELEMS ioctl interfaces with
bdev_restore_storage_elements() to restore the depopulated storage
elements of a zoned device.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
block/blk-zoned.c | 163 ++++++++++++++++++++++++++++++++++
block/blk.h | 9 ++
block/ioctl.c | 4 +
include/uapi/linux/blkzoned.h | 41 +++++++++
include/uapi/linux/fs.h | 1 +
5 files changed, 218 insertions(+)
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 30e61dbd89d6..d56afbeb436a 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -18,6 +18,7 @@
#include <linux/mempool.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
+#include <linux/uaccess.h>
#include <trace/events/block.h>
@@ -2781,6 +2782,53 @@ int bdev_report_storage_elements(struct block_device *bdev,
}
EXPORT_SYMBOL_GPL(bdev_report_storage_elements);
+static int blkdev_ioctl_report_storage_elements(struct block_device *bdev,
+ void __user *argp)
+{
+ struct gendisk *disk = bdev->bd_disk;
+ struct blk_se_report *rep;
+ int ret = 0;
+
+ /*
+ * Allocate the report header with an array of elements, assuming that
+ * we need the maximum number of storage elements. In practice, we may
+ * need less than that, but this potentially unnecessary preallocation
+ * simplifies the code.
+ */
+ rep = kzalloc_flex(*rep, elements, disk->nr_storage_elements);
+ if (!rep)
+ return -ENOMEM;
+
+ if (copy_from_user(rep, argp, sizeof(struct blk_se_report))) {
+ ret = -EFAULT;
+ goto free_rep;
+ }
+
+ if (rep->reserved) {
+ ret = -EINVAL;
+ goto free_rep;
+ }
+
+ rep->nr_reported_elements =
+ min(rep->nr_elements, disk->nr_storage_elements);
+ rep->nr_elements = disk->nr_storage_elements;
+
+ if (rep->nr_reported_elements) {
+ ret = bdev_report_storage_elements(bdev, rep->elements,
+ &rep->nr_reported_elements);
+ if (ret)
+ goto free_rep;
+ }
+
+ if (copy_to_user(argp, rep,
+ struct_size(rep, elements, rep->nr_reported_elements)))
+ ret = -EFAULT;
+
+free_rep:
+ kfree(rep);
+ return ret;
+}
+
/**
* bdev_remove_storage_element - Remove (depopulate) a storage element of a
* zoned block device
@@ -2844,6 +2892,62 @@ int bdev_remove_storage_element(struct block_device *bdev,
}
EXPORT_SYMBOL_GPL(bdev_remove_storage_element);
+static int blkdev_ioctl_remove_storage_element(struct block_device *bdev,
+ blk_mode_t mode,
+ unsigned int __user *argp)
+{
+ unsigned int element_id;
+ int ret;
+
+ /*
+ * Note: we require an O_EXCL open here so that a user cannot trigger
+ * a storage element removal from user space under a mounted file system.
+ * An FS that wants to manage storage element can do so using the
+ * function bdev_remove_storage_element() from kernel context.
+ */
+ if (!(mode & BLK_OPEN_EXCL))
+ return -EBADF;
+ if (!(mode & BLK_OPEN_WRITE))
+ return -EBADF;
+ if (bdev_read_only(bdev))
+ return -EPERM;
+
+ if (get_user(element_id, argp))
+ return -EFAULT;
+
+ /*
+ * Sync the device dirty data, flush the volatile write cache, and
+ * invalidate all cached data so that reads do not return old data for
+ * zones that went offline.
+ * Since a user can only successfully write to sequential zones of a
+ * zoned devices using direct IOs, the sync should only handle dirty
+ * data from buffered writes to conventional zones, for devices that
+ * have such zones.
+ */
+ ret = sync_blockdev(bdev);
+ if (ret)
+ return ret;
+
+ inode_lock(bdev->bd_mapping->host);
+ filemap_invalidate_lock(bdev->bd_mapping);
+
+ ret = blkdev_issue_flush(bdev->bd_disk->part0);
+ if (ret)
+ goto unlock;
+
+ ret = truncate_bdev_range(bdev, mode, 0, bdev_nr_bytes(bdev) - 1);
+ if (ret)
+ goto unlock;
+
+ ret = bdev_remove_storage_element(bdev, element_id);
+
+unlock:
+ filemap_invalidate_unlock(bdev->bd_mapping);
+ inode_unlock(bdev->bd_mapping->host);
+
+ return ret;
+}
+
/**
* bdev_restore_storage_elements - Restore all depopulated storage elements of
* a zoned block device
@@ -2909,6 +3013,42 @@ int bdev_restore_storage_elements(struct block_device *bdev)
}
EXPORT_SYMBOL_GPL(bdev_restore_storage_elements);
+static int blkdev_ioctl_restore_storage_elements(struct block_device *bdev,
+ blk_mode_t mode)
+{
+ int ret;
+
+ /*
+ * Note: because restoring storage elements is a destructive operation
+ * equivalent to reformatting the device, we require an O_EXCL open here
+ * so that a user cannot trigger with another active user (e.g. a
+ * mounted file system).
+ */
+ if (!(mode & BLK_OPEN_EXCL))
+ return -EBADF;
+ if (!(mode & BLK_OPEN_WRITE))
+ return -EBADF;
+ if (bdev_read_only(bdev))
+ return -EPERM;
+
+ inode_lock(bdev->bd_mapping->host);
+ filemap_invalidate_lock(bdev->bd_mapping);
+
+ /*
+ * Storage element restoration is a destructive operation that will
+ * reset all zones. So invalidate all cached data that we may have so
+ * that old data cannot be read once the operation completes.
+ */
+ ret = truncate_bdev_range(bdev, mode, 0, bdev_nr_bytes(bdev) - 1);
+ if (!ret)
+ ret = bdev_restore_storage_elements(bdev);
+
+ filemap_invalidate_unlock(bdev->bd_mapping);
+ inode_unlock(bdev->bd_mapping->host);
+
+ return ret;
+}
+
int disk_init_storage_element_mgmt(struct gendisk *disk)
{
int ret;
@@ -2941,3 +3081,26 @@ int disk_init_storage_element_mgmt(struct gendisk *disk)
return 0;
}
+
+int blk_ioctl_zone_storage_elements(struct block_device *bdev,
+ blk_mode_t mode, unsigned int cmd,
+ void __user *argp)
+{
+ struct gendisk *disk = bdev->bd_disk;
+
+ if (!disk->nr_storage_elements)
+ return -ENOTTY;
+
+ switch (cmd) {
+ case BLKREPORTSTORELEMS:
+ return blkdev_ioctl_report_storage_elements(bdev, argp);
+ case BLKREMOVESTORELEM:
+ return blkdev_ioctl_remove_storage_element(bdev, mode, argp);
+ case BLKRESTORESTORELEMS:
+ return blkdev_ioctl_restore_storage_elements(bdev, mode);
+ default:
+ break;
+ }
+
+ return -ENOTTY;
+}
diff --git a/block/blk.h b/block/blk.h
index 05e2692b80d5..44876b148237 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -595,6 +595,9 @@ 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_zone_mgmt_allowed(struct block_device *bdev, sector_t sector);
+int blk_ioctl_zone_storage_elements(struct block_device *bdev,
+ blk_mode_t mode, unsigned int cmd,
+ void __user *argp);
#else /* CONFIG_BLK_DEV_ZONED */
static inline int disk_init_storage_element_mgmt(struct gendisk *disk)
{
@@ -646,6 +649,12 @@ static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,
{
return false;
}
+static inline int blk_ioctl_zone_storage_elements(struct block_device *bdev,
+ blk_mode_t mode, unsigned int cmd,
+ void __user *argp)
+{
+ return -ENOTTY;
+}
#endif /* CONFIG_BLK_DEV_ZONED */
struct block_device *bdev_alloc(struct gendisk *disk, u8 partno);
diff --git a/block/ioctl.c b/block/ioctl.c
index fb2f4b4d2505..0f043363048c 100644
--- a/block/ioctl.c
+++ b/block/ioctl.c
@@ -679,6 +679,10 @@ static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode,
return put_uint(argp, bdev_zone_sectors(bdev));
case BLKGETNRZONES:
return put_uint(argp, bdev_nr_zones(bdev));
+ case BLKREPORTSTORELEMS:
+ case BLKREMOVESTORELEM:
+ case BLKRESTORESTORELEMS:
+ return blk_ioctl_zone_storage_elements(bdev, mode, cmd, argp);
case BLKROGET:
return put_int(argp, bdev_read_only(bdev) != 0);
case BLKSSZGET: /* get block device logical block size */
diff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h
index 9d7f59da3efc..6463cdec8e86 100644
--- a/include/uapi/linux/blkzoned.h
+++ b/include/uapi/linux/blkzoned.h
@@ -273,4 +273,45 @@ struct blk_se {
__u8 reserved[13];
};
+/**
+ * struct blk_se_report - Zoned device storage element descriptor report for the
+ * BLKREPORTSTORELEMS ioctl.
+ *
+ * @nr_elements: on input, this indicates the size of the @elements array. On
+ * output, this indicates the maximum number of storage elements
+ * of the device. If this field specifies 0 on input, the array
+ * @elements is not filled and the maximum number of storage
+ * elements of the device returned.
+ * @nr_reported_elements: output only to indicate the number of storage element
+ * descriptors filled in the array @elements.
+ * @reserved: reserved space for future use and structure field alignment.
+ * @elements: array of storage element descriptors with a maximum number of
+ * entries specified by @nr_elements on input and by
+ * @nr_reported_elements on output.
+ */
+struct blk_se_report {
+ __u32 nr_elements;
+ __u32 nr_reported_elements;
+ __u64 reserved;
+ struct blk_se elements[];
+};
+
+/**
+ * Zoned block device storage element management ioctl's:
+ *
+ * @BLKREPORTSTORELEMS: Get the number of storage elements of a device and
+ * the device storage element descriptors.
+ * @BLKREMOVESTORELEM: Remove (depopulate) one storage element of a device. This
+ * operation will flush the target device write cache and
+ * invalidate all cached data. So the caller must ensure
+ * that all dirty data written with buffered IOs are written
+ * back first to avoid any data loss.
+ * @BLKRESTORESTORELEMS: Restore (repopulate if possible) all storage elements
+ * that have been removed. This is a destructive operation
+ * equivalent to reformatting the target device.
+ */
+#define BLKREPORTSTORELEMS _IOWR(0x12, 143, struct blk_se_report)
+#define BLKREMOVESTORELEM _IOW(0x12, 144, __u32)
+#define BLKRESTORESTORELEMS _IO(0x12, 145)
+
#endif /* _UAPI_BLKZONED_H */
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index a46c33692aa2..5b6f8248a479 100644
--- a/include/uapi/linux/fs.h
+++ b/include/uapi/linux/fs.h
@@ -309,6 +309,7 @@ struct file_attr {
/* 130-136 and 142 are used by zoned block device ioctls (uapi/linux/blkzoned.h) */
/* 137-141 are used by blk-crypto ioctls (uapi/linux/blk-crypto.h) */
#define BLKTRACESETUP2 _IOWR(0x12, 142, struct blk_user_trace_setup2)
+/* 143-145 are used by storage element management for zoned block devices. */
#define BMAP_IOCTL 1 /* obsolete - kept for compatibility */
#define FIBMAP _IO(0x00,1) /* bmap access */
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 06/12] zloop: fix zone reporting consistency
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (4 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 05/12] block: add storage element management ioctls Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:16 ` Hannes Reinecke
2026-11-11 15:27 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 07/12] zloop: add storage element emulation Damien Le Moal
` (5 subsequent siblings)
11 siblings, 2 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
In zloop_report_zones(), filling the write pointer and condition of a zone
descriptor separately outside of the zone wp_lock can lead to an
inconsistent zone report with a write pointer potentially not being
consistent with the condition being reported. Avoid such inconsistency by
filling both fields while holding the zone wp_lock.
Fixes: fcc6eaa3a03a ("zloop: introduce the ordered_zone_append configuration parameter")
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
drivers/block/zloop.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index 96666742523b..844a9f5db6a4 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -992,8 +992,8 @@ static int zloop_report_zones(struct gendisk *disk, sector_t sector,
blkz.len = zlo->zone_size;
spin_lock(&zone->wp_lock);
blkz.wp = zone->wp;
- spin_unlock(&zone->wp_lock);
blkz.cond = zone->cond;
+ spin_unlock(&zone->wp_lock);
if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) {
blkz.type = BLK_ZONE_TYPE_CONVENTIONAL;
blkz.capacity = zlo->zone_size;
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 07/12] zloop: add storage element emulation
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (5 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 06/12] zloop: fix zone reporting consistency Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:17 ` Hannes Reinecke
2026-11-09 9:51 ` [PATCH v8 08/12] zloop: add degrade_element control command Damien Le Moal
` (4 subsequent siblings)
11 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Emulate the storage element depopulation feature of SMR disks in the
zloop driver. This emulation is controlled using the new stor_elements
option. This option can take several values:
- ZLOOP_STOR_ELEMENTS_NONE (0): no emulation (default)
- ZLOOP_STOR_ELEMENTS_RDWR (1): emulate all access storage elements
(e.g. read+write heads)
- ZLOOP_STOR_ELEMENTS_PAIRS (2): emulate fractional access storage
elements (e.g. pairs of read and write heads)
If enabled with the value 1 or 2, the number of storage elements, or of
pairs of fractional access storage elements, is automatically calculated
based on the number of zones of the device so that we have at least 2 and
at most 32 storage elements or pairs of fractional access storage
elements.
The mapping of zones to storage elements is defined simply as the modulo
of a zone number with the the number of storage elements for the all
access storage elements emulation and with the number of storage element
pairs for the fractional access storage element emulation case.
E.g, removing a storage element from a set of 4 all access storage
elements will offline 1 zone every 4 zones.
The storage element management operations are specified with the
zloop_se_ops (struct blk_se_ops).
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
.../admin-guide/blockdev/zoned_loop.rst | 11 +-
drivers/block/zloop.c | 684 ++++++++++++++++--
2 files changed, 653 insertions(+), 42 deletions(-)
diff --git a/Documentation/admin-guide/blockdev/zoned_loop.rst b/Documentation/admin-guide/blockdev/zoned_loop.rst
index 64277494fb36..80a564836c1b 100644
--- a/Documentation/admin-guide/blockdev/zoned_loop.rst
+++ b/Documentation/admin-guide/blockdev/zoned_loop.rst
@@ -61,7 +61,7 @@ The options available for the add command can be listed by reading the
/dev/zloop-control device::
$ cat /dev/zloop-control
- add id=%d,capacity_mb=%u,zone_size_mb=%u,zone_capacity_mb=%u,conv_zones=%u,max_open_zones=%u,base_dir=%s,nr_queues=%u,queue_depth=%u,buffered_io,zone_append=%u,ordered_zone_append,discard_write_cache
+ add id=%d,capacity_mb=%u,zone_size_mb=%u,zone_capacity_mb=%u,conv_zones=%u,max_open_zones=%u,base_dir=%s,nr_queues=%u,queue_depth=%u,buffered_io,zone_append=%u,ordered_zone_append,discard_write_cache,stor_elements=%u
remove id=%d
In more details, the options that can be used with the "add" command are as
@@ -113,6 +113,15 @@ discard_write_cache Discard all data that was not explicitly persisted using a
each zone file to the size recorded during the last flush
operation. This simulates power fail events where
uncommitted data is lost.
+stor_elements Control storage element emulation. The default value is
+ 0, indicating no emulation. A value of 1 indicates that
+ storage elements are emulated as full access physical
+ elements, which is equivalent to combined read+write heads
+ of a hard-disk. A value of 2 enables storage element
+ emulation as fractional access physical storage elements,
+ which is equivalent to pairs of read and write heads of a
+ hard-disk. The health state of storage elements is not
+ preserved across a zloop device remove and add.
=================== =========================================================
3) Deleting a Zoned Device
diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index 844a9f5db6a4..75506e6a4d75 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -18,6 +18,7 @@
#include <linux/parser.h>
#include <linux/seq_file.h>
#include <linux/xattr.h>
+#include <linux/delay.h>
/*
* Options for adding (and removing) a device.
@@ -37,6 +38,7 @@ enum {
ZLOOP_OPT_ORDERED_ZONE_APPEND = (1 << 10),
ZLOOP_OPT_DISCARD_WRITE_CACHE = (1 << 11),
ZLOOP_OPT_MAX_OPEN_ZONES = (1 << 12),
+ ZLOOP_OPT_STOR_ELEMENTS = (1 << 13),
};
static const match_table_t zloop_opt_tokens = {
@@ -51,11 +53,22 @@ static const match_table_t zloop_opt_tokens = {
{ ZLOOP_OPT_BUFFERED_IO, "buffered_io" },
{ ZLOOP_OPT_ZONE_APPEND, "zone_append=%u" },
{ ZLOOP_OPT_ORDERED_ZONE_APPEND, "ordered_zone_append" },
- { ZLOOP_OPT_DISCARD_WRITE_CACHE, "discard_write_cache" },
+ { ZLOOP_OPT_DISCARD_WRITE_CACHE, "discard_write_cache" },
{ ZLOOP_OPT_MAX_OPEN_ZONES, "max_open_zones=%u" },
+ { ZLOOP_OPT_STOR_ELEMENTS, "stor_elements=%u" },
{ ZLOOP_OPT_ERR, NULL }
};
+/* Storage elements emulation types. */
+enum zloop_stor_elements {
+ /* No emulation. */
+ ZLOOP_STOR_ELEMENTS_NONE,
+ /* Emulate read+write storage elements. */
+ ZLOOP_STOR_ELEMENTS_RDWR,
+ /* Emulate pairs of associated read and write storage elements. */
+ ZLOOP_STOR_ELEMENTS_PAIRS,
+};
+
/* Default values for the "add" operation. */
#define ZLOOP_DEF_ID -1
#define ZLOOP_DEF_ZONE_SIZE ((256ULL * SZ_1M) >> SECTOR_SHIFT)
@@ -68,6 +81,7 @@ static const match_table_t zloop_opt_tokens = {
#define ZLOOP_DEF_BUFFERED_IO false
#define ZLOOP_DEF_ZONE_APPEND true
#define ZLOOP_DEF_ORDERED_ZONE_APPEND false
+#define ZLOOP_DEF_STOR_ELEMENTS ZLOOP_STOR_ELEMENTS_NONE
/* Arbitrary limit on the zone size (16GB). */
#define ZLOOP_MAX_ZONE_SIZE_MB 16384
@@ -87,6 +101,7 @@ struct zloop_options {
bool zone_append;
bool ordered_zone_append;
bool discard_write_cache;
+ enum zloop_stor_elements stor_elements;
};
/*
@@ -117,6 +132,8 @@ struct zloop_zone {
enum blk_zone_cond cond;
sector_t start;
sector_t wp;
+ unsigned int wr_se_id;
+ unsigned int rd_se_id;
gfp_t old_gfp_mask;
};
@@ -133,6 +150,7 @@ struct zloop_device {
bool zone_append;
bool ordered_zone_append;
bool discard_write_cache;
+ enum zloop_stor_elements stor_elements;
const char *base_dir;
struct file *data_dir;
@@ -150,6 +168,14 @@ struct zloop_device {
struct list_head open_zones_lru_list;
unsigned int nr_open_zones;
+ /* For storage elements emulation. */
+ struct mutex stor_elements_lock;
+ unsigned int nr_elements;
+ unsigned int nr_removed_elements;
+ unsigned int remove_element_id;
+ bool restore_in_progress;
+ struct blk_se *elements;
+
struct zloop_zone zones[] __counted_by(nr_zones);
};
@@ -289,22 +315,64 @@ static bool zloop_do_open_zone(struct zloop_device *zlo,
}
}
-static void zloop_mark_full(struct zloop_device *zlo, struct zloop_zone *zone)
+static void zloop_set_zone_cond(struct zloop_device *zlo,
+ struct zloop_zone *zone,
+ enum blk_zone_cond cond)
{
lockdep_assert_held(&zone->wp_lock);
zloop_lru_remove_open_zone(zlo, zone);
- zone->cond = BLK_ZONE_COND_FULL;
- zone->wp = ULLONG_MAX;
+ zone->cond = cond;
+ if (cond == BLK_ZONE_COND_EMPTY)
+ zone->wp = zone->start;
+ else
+ zone->wp = ULLONG_MAX;
+}
+
+static inline void zloop_set_zone_full(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_FULL);
+}
+
+static inline void zloop_set_zone_empty(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_EMPTY);
}
-static void zloop_mark_empty(struct zloop_device *zlo, struct zloop_zone *zone)
+static bool zloop_zone_element_is_ok(struct zloop_device *zlo,
+ struct zloop_zone *zone, bool for_write)
{
+ struct blk_se *se;
+
lockdep_assert_held(&zone->wp_lock);
- zloop_lru_remove_open_zone(zlo, zone);
- zone->cond = BLK_ZONE_COND_EMPTY;
- zone->wp = zone->start;
+ if (!zlo->nr_elements)
+ return true;
+
+ if (for_write)
+ se = &zlo->elements[zone->wr_se_id - 1];
+ else
+ se = &zlo->elements[zone->rd_se_id - 1];
+
+ return READ_ONCE(se->status) == BLK_SE_STS_OK;
+}
+
+static bool zloop_zone_is_ok(struct zloop_device *zlo, struct zloop_zone *zone)
+{
+ bool ret;
+
+ spin_lock(&zone->wp_lock);
+
+ ret = zloop_zone_element_is_ok(zlo, zone, true);
+ if (ret)
+ ret = zone->cond != BLK_ZONE_COND_OFFLINE &&
+ zone->cond != BLK_ZONE_COND_READONLY;
+
+ spin_unlock(&zone->wp_lock);
+
+ return ret;
}
static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)
@@ -316,6 +384,15 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)
lockdep_assert_held(&zone->lock);
+ /*
+ * If the zone is served by an element that is not in a good state, fail
+ * the operation.
+ */
+ if (!zloop_zone_is_ok(zlo, zone)) {
+ set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ return -EIO;
+ }
+
ret = vfs_getattr(&zone->file->f_path, &stat, STATX_SIZE, 0);
if (ret < 0) {
pr_err("%pg: Failed to get zone %u file stat (err=%d)\n",
@@ -340,9 +417,9 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)
spin_lock(&zone->wp_lock);
if (!file_sectors) {
- zloop_mark_empty(zlo, zone);
+ zloop_set_zone_empty(zlo, zone);
} else if (file_sectors == zlo->zone_capacity) {
- zloop_mark_full(zlo, zone);
+ zloop_set_zone_full(zlo, zone);
} else {
if (zone->cond != BLK_ZONE_COND_IMP_OPEN &&
zone->cond != BLK_ZONE_COND_EXP_OPEN)
@@ -364,6 +441,11 @@ static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (!zloop_zone_is_ok(zlo, zone)) {
+ ret = -EIO;
+ goto unlock;
+ }
+
if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) {
ret = zloop_update_seq_zone(zlo, zone_no);
if (ret)
@@ -389,6 +471,11 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (!zloop_zone_is_ok(zlo, zone)) {
+ ret = -EIO;
+ goto unlock;
+ }
+
if (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags)) {
ret = zloop_update_seq_zone(zlo, zone_no);
if (ret)
@@ -421,7 +508,22 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no)
return ret;
}
-static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no)
+static int zloop_do_reset_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ if (vfs_truncate(&zone->file->f_path, 0))
+ return -EIO;
+
+ spin_lock(&zone->wp_lock);
+ zloop_set_zone_empty(zlo, zone);
+ clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ spin_unlock(&zone->wp_lock);
+
+ return 0;
+}
+
+static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no,
+ bool all_zones)
{
struct zloop_zone *zone = &zlo->zones[zone_no];
int ret = 0;
@@ -431,20 +533,19 @@ static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (!zloop_zone_is_ok(zlo, zone)) {
+ if (!all_zones)
+ ret = -EIO;
+ goto unlock;
+ }
+
if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) &&
zone->cond == BLK_ZONE_COND_EMPTY)
goto unlock;
- if (vfs_truncate(&zone->file->f_path, 0)) {
+ ret = zloop_do_reset_zone(zlo, zone);
+ if (ret)
set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
- ret = -EIO;
- goto unlock;
- }
-
- spin_lock(&zone->wp_lock);
- zloop_mark_empty(zlo, zone);
- clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
- spin_unlock(&zone->wp_lock);
unlock:
mutex_unlock(&zone->lock);
@@ -458,7 +559,7 @@ static int zloop_reset_all_zones(struct zloop_device *zlo)
int ret;
for (i = zlo->nr_conv_zones; i < zlo->nr_zones; i++) {
- ret = zloop_reset_zone(zlo, i);
+ ret = zloop_reset_zone(zlo, i, true);
if (ret)
return ret;
}
@@ -476,6 +577,11 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (!zloop_zone_is_ok(zlo, zone)) {
+ ret = -EIO;
+ goto unlock;
+ }
+
if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) &&
zone->cond == BLK_ZONE_COND_FULL)
goto unlock;
@@ -488,7 +594,7 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no)
}
spin_lock(&zone->wp_lock);
- zloop_mark_full(zlo, zone);
+ zloop_set_zone_full(zlo, zone);
clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
spin_unlock(&zone->wp_lock);
@@ -632,7 +738,7 @@ static int zloop_seq_write_prep(struct zloop_cmd *cmd)
if (!is_append || !zlo->ordered_zone_append) {
zone->wp += nr_sectors;
if (zone->wp == zone_end)
- zloop_mark_full(zlo, zone);
+ zloop_set_zone_full(zlo, zone);
}
out_unlock:
spin_unlock(&zone->wp_lock);
@@ -774,7 +880,7 @@ static void zloop_handle_cmd(struct zloop_cmd *cmd)
cmd->ret = zloop_flush(zlo);
break;
case REQ_OP_ZONE_RESET:
- cmd->ret = zloop_reset_zone(zlo, rq_zone_no(rq));
+ cmd->ret = zloop_reset_zone(zlo, rq_zone_no(rq), false);
break;
case REQ_OP_ZONE_RESET_ALL:
cmd->ret = zloop_reset_all_zones(zlo);
@@ -885,30 +991,65 @@ static void zloop_complete_rq(struct request *rq)
blk_mq_end_request(rq, sts);
}
-static bool zloop_set_zone_append_sector(struct request *rq)
+static bool zloop_set_zone_append_sector(struct zloop_device *zlo,
+ struct zloop_zone *zone,
+ struct request *rq)
{
- struct zloop_device *zlo = rq->q->queuedata;
- unsigned int zone_no = rq_zone_no(rq);
- struct zloop_zone *zone = &zlo->zones[zone_no];
sector_t zone_end = zone->start + zlo->zone_capacity;
sector_t nr_sectors = blk_rq_sectors(rq);
- spin_lock(&zone->wp_lock);
-
if (zone->cond == BLK_ZONE_COND_FULL ||
- zone->wp + nr_sectors > zone_end) {
- spin_unlock(&zone->wp_lock);
+ zone->wp + nr_sectors > zone_end)
return false;
- }
rq->__sector = zone->wp;
zone->wp += blk_rq_sectors(rq);
if (zone->wp >= zone_end)
- zloop_mark_full(zlo, zone);
+ zloop_set_zone_full(zlo, zone);
+
+ return true;
+}
+
+static bool zloop_prep_rq(struct zloop_device *zlo, struct request *rq)
+{
+ struct zloop_zone *zone = &zlo->zones[rq_zone_no(rq)];
+ bool is_write = op_is_write(req_op(rq));
+ bool ret = true;
+ if (!zlo->nr_elements &&
+ (!zlo->ordered_zone_append || req_op(rq) != REQ_OP_ZONE_APPEND))
+ return true;
+
+ spin_lock(&zone->wp_lock);
+
+ /*
+ * If storage element emulation is enabled, check the health state of
+ * the storage element serving the zone and the zone condition: do not
+ * allow any operation to zones served by an unusable storage element,
+ * operations to offline zones, and allow only read operations to
+ * read-only zones.
+ */
+ if (zlo->nr_elements) {
+ if (!zloop_zone_element_is_ok(zlo, zone, is_write) ||
+ zone->cond == BLK_ZONE_COND_OFFLINE ||
+ (zone->cond == BLK_ZONE_COND_READONLY && is_write)) {
+ ret = false;
+ goto unlock;
+ }
+ }
+
+ /*
+ * If we need to strongly order zone append operations, set the request
+ * sector to the zone write pointer location now instead of when the
+ * command work runs.
+ */
+ if (zlo->ordered_zone_append && req_op(rq) == REQ_OP_ZONE_APPEND)
+ ret = zloop_set_zone_append_sector(zlo, zone, rq);
+
+unlock:
spin_unlock(&zone->wp_lock);
- return true;
+ return ret;
}
static blk_status_t zloop_queue_rq(struct blk_mq_hw_ctx *hctx,
@@ -924,13 +1065,27 @@ static blk_status_t zloop_queue_rq(struct blk_mq_hw_ctx *hctx,
}
/*
- * If we need to strongly order zone append operations, set the request
- * sector to the zone write pointer location now instead of when the
- * command work runs.
+ * Do not allow commands while the restoration of storage elements is
+ * in progress.
+ */
+ if (READ_ONCE(zlo->restore_in_progress))
+ return BLK_STS_IOERR;
+
+ /*
+ * Check if we can accept data access commands and prepare zone append.
+ * If storage element emulation is enabled, this will check if the data
+ * access can be allowed based on the health state of the storage
+ * element assigned to the zone being accessed.
*/
- if (zlo->ordered_zone_append && req_op(rq) == REQ_OP_ZONE_APPEND) {
- if (!zloop_set_zone_append_sector(rq))
+ switch (req_op(rq)) {
+ case REQ_OP_READ:
+ case REQ_OP_WRITE:
+ case REQ_OP_ZONE_APPEND:
+ if (!zloop_prep_rq(zlo, rq))
return BLK_STS_IOERR;
+ break;
+ default:
+ break;
}
blk_mq_start_request(rq);
@@ -1012,6 +1167,331 @@ static int zloop_report_zones(struct gendisk *disk, sector_t sector,
return nr_zones;
}
+static int zloop_report_elements(struct gendisk *disk,
+ struct blk_se *elements,
+ unsigned int *nr_elements)
+{
+ struct zloop_device *zlo = disk->private_data;
+ struct blk_se *se;
+ unsigned int i;
+
+ if (test_bit(GD_DEAD, &disk->state) ||
+ data_race(READ_ONCE(zlo->state)) == Zlo_deleting)
+ return -ENODEV;
+
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ if (!elements) {
+ *nr_elements = zlo->nr_elements;
+ goto unlock;
+ }
+
+ *nr_elements = min(*nr_elements, zlo->nr_elements);
+ for (i = 0, se = zlo->elements; i < *nr_elements; i++, se++) {
+ switch (READ_ONCE(se->status)) {
+ case BLK_SE_STS_REMOVED:
+ case BLK_SE_STS_RESTORE_ERROR:
+ se->restore_allowed = 1;
+ break;
+ default:
+ se->restore_allowed = 0;
+ }
+ memcpy(&elements[i], se, sizeof(struct blk_se));
+ }
+
+unlock:
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return 0;
+}
+
+static int zloop_execute_remove_element(struct zloop_device *zlo)
+{
+ struct gendisk *disk = zlo->disk;
+ struct blk_se *se, *paired_se = NULL;
+ enum blk_zone_cond cond;
+ struct zloop_zone *zone;
+ unsigned int memflags, i;
+ int ret = 0;
+
+ /* Wait for all IOs to complete. */
+ memflags = blk_mq_freeze_queue(disk->queue);
+ blk_mq_unfreeze_queue(disk->queue, memflags);
+
+ /*
+ * If the element to remove is a read element, zones must go offline and
+ * the associated write element is also removed.
+ */
+ se = &zlo->elements[zlo->remove_element_id - 1];
+ switch (se->type) {
+ case BLK_SE_TYPE_RDWR:
+ cond = BLK_ZONE_COND_OFFLINE;
+ break;
+ case BLK_SE_TYPE_READ:
+ cond = BLK_ZONE_COND_OFFLINE;
+ paired_se = &zlo->elements[se->paired_id - 1];
+ break;
+ case BLK_SE_TYPE_WRITE:
+ cond = BLK_ZONE_COND_READONLY;
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
+ /*
+ * Change the condition of the zones owned by the (pair of) elements
+ * being removed and mark the elements removed.
+ */
+ for (i = 0, zone = zlo->zones; i < zlo->nr_zones; i++, zone++) {
+ if (zone->wr_se_id != se->id && zone->rd_se_id != se->id)
+ continue;
+ mutex_lock(&zone->lock);
+ spin_lock(&zone->wp_lock);
+ zloop_set_zone_cond(zlo, zone, cond);
+ clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ spin_unlock(&zone->wp_lock);
+ mutex_unlock(&zone->lock);
+ }
+
+ WRITE_ONCE(se->status, BLK_SE_STS_REMOVED);
+ zlo->nr_removed_elements++;
+ if (paired_se && READ_ONCE(paired_se->status) != BLK_SE_STS_REMOVED) {
+ WRITE_ONCE(paired_se->status, BLK_SE_STS_REMOVED);
+ zlo->nr_removed_elements++;
+ }
+
+ return ret;
+}
+
+static int zloop_remove_element(struct gendisk *disk, unsigned int element_id)
+{
+ struct zloop_device *zlo = disk->private_data;
+ struct blk_se *se, *paired_se = NULL;
+ unsigned int nr_remove = 1;
+ int ret = 0;
+
+ if (test_bit(GD_DEAD, &disk->state) ||
+ data_race(READ_ONCE(zlo->state)) == Zlo_deleting)
+ return -ENODEV;
+
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ if (!element_id || element_id > zlo->nr_elements)
+ return -EINVAL;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* If a remove or restore operation is already in progress, bail out. */
+ if (zlo->remove_element_id || READ_ONCE(zlo->restore_in_progress)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /*
+ * Get the element to remove. If it is already removed, we have nothing
+ * to do.
+ */
+ se = &zlo->elements[element_id - 1];
+ if (READ_ONCE(se->status) == BLK_SE_STS_REMOVED)
+ goto unlock;
+
+ /*
+ * If the element to remove is a read element, the associated write
+ * element must also be removed.
+ */
+ if (se->type == BLK_SE_TYPE_READ) {
+ paired_se = &zlo->elements[se->paired_id - 1];
+ if (READ_ONCE(paired_se->status) != BLK_SE_STS_REMOVED)
+ nr_remove = 2;
+ }
+
+ /*
+ * Make sure we do not allow removing all storage elements as that does
+ * not make any sense. This is consistent with the device advertized
+ * limit of SCSI and ATA devices supporting the storage element
+ * depopulation feature.
+ */
+ if (zlo->nr_removed_elements + nr_remove >= zlo->nr_elements) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ /*
+ * Change the elements to be removed to the remove in progress state so
+ * that concurrent report elements can see the operation on-going.
+ */
+ WRITE_ONCE(se->status, BLK_SE_STS_REMOVE_IN_PROGRESS);
+ if (paired_se && READ_ONCE(paired_se->status) != BLK_SE_STS_REMOVED)
+ WRITE_ONCE(paired_se->status, BLK_SE_STS_REMOVE_IN_PROGRESS);
+ zlo->remove_element_id = element_id;
+
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ /*
+ * Wait a little so that this looks like a real device taking time to
+ * operate.
+ */
+ msleep(2000);
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* Remove the element(s). */
+ ret = zloop_execute_remove_element(zlo);
+ zlo->remove_element_id = 0;
+
+unlock:
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return ret;
+}
+
+static int zloop_restore_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ int ret = 0;
+
+ mutex_lock(&zone->lock);
+
+ if (!test_bit(ZLOOP_ZONE_CONV, &zone->flags)) {
+ ret = vfs_truncate(&zone->file->f_path, 0);
+ if (ret) {
+ set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ ret = -EIO;
+ goto unlock;
+ }
+ }
+
+ spin_lock(&zone->wp_lock);
+ if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) {
+ zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_NOT_WP);
+ } else {
+ zloop_set_zone_empty(zlo, zone);
+ clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ }
+ spin_unlock(&zone->wp_lock);
+
+unlock:
+ mutex_unlock(&zone->lock);
+
+ return ret;
+}
+
+static int zloop_execute_restore_elements(struct zloop_device *zlo)
+{
+ struct gendisk *disk = zlo->disk;
+ struct zloop_zone *zone;
+ struct blk_se *se;
+ enum blk_se_status sts = BLK_SE_STS_OK;
+ unsigned int memflags, i;
+ int ret = 0;
+
+ /* Wait for all IOs to complete. */
+ memflags = blk_mq_freeze_queue(disk->queue);
+ blk_mq_unfreeze_queue(disk->queue, memflags);
+
+ /*
+ * Truncate all files for sequential zones and restore the condition of
+ * all zones. If this fails, we will propagate the failure as a
+ * restoration error.
+ */
+ for (i = 0, zone = zlo->zones; i < zlo->nr_zones; i++, zone++) {
+ ret = zloop_restore_zone(zlo, zone);
+ if (ret) {
+ pr_err("zloop%d: restore zone %u failed %d\n",
+ zlo->id, i, ret);
+ sts = BLK_SE_STS_RESTORE_ERROR;
+ break;
+ }
+ }
+
+ /*
+ * Update the status of all elements being restored based on the zone
+ * restoration result.
+ */
+ for (i = 0, se = zlo->elements; i < zlo->nr_elements; i++, se++) {
+ if (READ_ONCE(se->status) == BLK_SE_STS_RESTORE_IN_PROGRESS)
+ WRITE_ONCE(se->status, sts);
+ }
+
+ /*
+ * Clear the counter of removed elements so that even if we had an error
+ * and set the elements health to BLK_SE_STS_RESTORE_ERROR, then can be
+ * removed again.
+ */
+ zlo->nr_removed_elements = 0;
+
+ return ret;
+}
+
+static int zloop_restore_elements(struct gendisk *disk)
+{
+ struct zloop_device *zlo = disk->private_data;
+ struct blk_se *se;
+ unsigned int i, n = 0;
+ int ret = 0;
+
+ if (test_bit(GD_DEAD, &disk->state) ||
+ data_race(READ_ONCE(zlo->state)) == Zlo_deleting)
+ return -ENODEV;
+
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* If a remove or restore operation is already in progress, bail out. */
+ if (zlo->remove_element_id || READ_ONCE(zlo->restore_in_progress)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /*
+ * Set the restore in progress state for elements that will be
+ * restored.
+ */
+ for (i = 0, se = zlo->elements; i < zlo->nr_elements; i++, se++) {
+ switch (READ_ONCE(se->status)) {
+ case BLK_SE_STS_REMOVED:
+ case BLK_SE_STS_RESTORE_ERROR:
+ WRITE_ONCE(se->status, BLK_SE_STS_RESTORE_IN_PROGRESS);
+ n++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* If we have no removed or in error elements, we have nothing to do. */
+ if (!n)
+ goto unlock;
+
+ WRITE_ONCE(zlo->restore_in_progress, true);
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ /*
+ * Wait a little so that this looks like a real device taking time to
+ * operate.
+ */
+ msleep(5000);
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* Restore the elements. */
+ ret = zloop_execute_restore_elements(zlo);
+
+ WRITE_ONCE(zlo->restore_in_progress, false);
+
+unlock:
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return ret;
+}
+
static void zloop_free_disk(struct gendisk *disk)
{
struct zloop_device *zlo = disk->private_data;
@@ -1029,15 +1509,23 @@ static void zloop_free_disk(struct gendisk *disk)
fput(zlo->data_dir);
destroy_workqueue(zlo->workqueue);
+ kfree(zlo->elements);
kfree(zlo->base_dir);
kvfree(zlo);
}
+static const struct blk_se_ops zloop_se_ops = {
+ .report_elements = zloop_report_elements,
+ .remove_element = zloop_remove_element,
+ .restore_elements = zloop_restore_elements,
+};
+
static const struct block_device_operations zloop_fops = {
.owner = THIS_MODULE,
.open = zloop_open,
.report_zones = zloop_report_zones,
.free_disk = zloop_free_disk,
+ .se_ops = &zloop_se_ops,
};
__printf(3, 4)
@@ -1114,6 +1602,25 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts,
spin_lock_init(&zone->wp_lock);
zone->start = (sector_t)zone_no << zlo->zone_shift;
+ /* If we are emulating storage elements, assign one to the zone. */
+ if (zlo->stor_elements != ZLOOP_STOR_ELEMENTS_NONE) {
+ unsigned int nr_elems = zlo->nr_elements;
+ unsigned int se_idx;
+
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)
+ nr_elems /= 2;
+ se_idx = zone_no % nr_elems;
+ zlo->elements[se_idx].nr_zones++;
+
+ zone->wr_se_id = se_idx + 1;
+ zone->rd_se_id = zone->wr_se_id;
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) {
+ zone->rd_se_id += nr_elems;
+ zlo->elements[zone->rd_se_id - 1].nr_zones =
+ zlo->elements[se_idx].nr_zones;
+ }
+ }
+
if (!restore)
oflags |= O_CREAT;
@@ -1189,6 +1696,69 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts,
return ret;
}
+#define ZLOOP_MIN_STOR_ELEMENTS 2
+#define ZLOOP_MAX_STOR_ELEMENTS 32
+#define ZLOOP_MIN_ZONES_PER_STOR_ELEMENTS 32
+
+static int zloop_create_storage_elements(struct zloop_device *zlo)
+{
+ struct blk_se *se, *paired_se;
+ unsigned int i, nr_elems, nr_elements;
+
+ /*
+ * Calculate the number of storage elements we are going to emulate.
+ * To achieve a somewhat realistic emulation, we want at least 2 storage
+ * elements, and no more than 32, targeting at least 32 zones per
+ * element.
+ */
+ if (zlo->nr_zones <= 64)
+ nr_elements = ZLOOP_MIN_STOR_ELEMENTS;
+ else
+ nr_elements =
+ min(ZLOOP_MAX_STOR_ELEMENTS,
+ zlo->nr_zones / ZLOOP_MIN_ZONES_PER_STOR_ELEMENTS);
+
+ /*
+ * If we are emulating pairs of read and write storage elements, we need
+ * double the number of storage element descriptors.
+ */
+ nr_elems = nr_elements;
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)
+ nr_elems *= 2;
+ zlo->elements = kzalloc_objs(struct blk_se, nr_elems);
+ if (!zlo->elements)
+ return -ENOMEM;
+
+ for (i = 0, se = zlo->elements; i < nr_elements; i++, se++) {
+ se->id = i + 1;
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)
+ se->type = BLK_SE_TYPE_WRITE;
+ else
+ se->type = BLK_SE_TYPE_RDWR;
+ se->status = BLK_SE_STS_OK;
+ }
+
+ /*
+ * If we are emulating pairs of read and write storage elements,
+ * initialize the read elements paired with the write elements we just
+ * initialized.
+ */
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) {
+ for (i = 0; i < nr_elements; i++, se++) {
+ se->id = nr_elements + i + 1;
+ paired_se = &zlo->elements[i];
+ se->paired_id = paired_se->id;
+ paired_se->paired_id = se->id;
+ se->type = BLK_SE_TYPE_READ;
+ se->status = BLK_SE_STS_OK;
+ }
+ }
+
+ zlo->nr_elements = nr_elems;
+
+ return 0;
+}
+
static bool zloop_dev_exists(struct zloop_device *zlo)
{
struct file *cnv, *seq;
@@ -1244,6 +1814,7 @@ static int zloop_ctl_add(struct zloop_options *opts)
WRITE_ONCE(zlo->state, Zlo_creating);
spin_lock_init(&zlo->open_zones_lock);
INIT_LIST_HEAD(&zlo->open_zones_lru_list);
+ mutex_init(&zlo->stor_elements_lock);
ret = mutex_lock_killable(&zloop_ctl_mutex);
if (ret)
@@ -1277,12 +1848,19 @@ static int zloop_ctl_add(struct zloop_options *opts)
if (zlo->zone_append)
zlo->ordered_zone_append = opts->ordered_zone_append;
zlo->discard_write_cache = opts->discard_write_cache;
+ zlo->stor_elements = opts->stor_elements;
+
+ if (zlo->stor_elements != ZLOOP_STOR_ELEMENTS_NONE) {
+ ret = zloop_create_storage_elements(zlo);
+ if (ret)
+ goto out_free_idr;
+ }
zlo->workqueue = alloc_workqueue("zloop%d", WQ_UNBOUND | WQ_FREEZABLE,
opts->nr_queues * opts->queue_depth, zlo->id);
if (!zlo->workqueue) {
ret = -ENOMEM;
- goto out_free_idr;
+ goto out_destroy_storage_elements;
}
if (opts->base_dir)
@@ -1368,6 +1946,10 @@ static int zloop_ctl_add(struct zloop_options *opts)
zlo->id, zlo->nr_zones,
((sector_t)zlo->zone_size << SECTOR_SHIFT) >> 20,
zlo->block_size);
+ if (zlo->nr_elements)
+ pr_info("zloop%d: %u storage elements\n",
+ zlo->id, zlo->nr_elements);
+
pr_info("zloop%d: using %s%s zone append\n",
zlo->id,
zlo->ordered_zone_append ? "ordered " : "",
@@ -1391,6 +1973,8 @@ static int zloop_ctl_add(struct zloop_options *opts)
kfree(zlo->base_dir);
out_destroy_workqueue:
destroy_workqueue(zlo->workqueue);
+out_destroy_storage_elements:
+ kfree(zlo->elements);
out_free_idr:
mutex_lock(&zloop_ctl_mutex);
idr_remove(&zloop_index_idr, zlo->id);
@@ -1509,6 +2093,7 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)
opts->buffered_io = ZLOOP_DEF_BUFFERED_IO;
opts->zone_append = ZLOOP_DEF_ZONE_APPEND;
opts->ordered_zone_append = ZLOOP_DEF_ORDERED_ZONE_APPEND;
+ opts->stor_elements = ZLOOP_DEF_STOR_ELEMENTS;
if (!buf)
return 0;
@@ -1643,6 +2228,23 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)
case ZLOOP_OPT_DISCARD_WRITE_CACHE:
opts->discard_write_cache = true;
break;
+ case ZLOOP_OPT_STOR_ELEMENTS:
+ if (match_uint(args, &token)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ switch (token) {
+ case ZLOOP_STOR_ELEMENTS_NONE:
+ case ZLOOP_STOR_ELEMENTS_RDWR:
+ case ZLOOP_STOR_ELEMENTS_PAIRS:
+ break;
+ default:
+ pr_err("Invalid stor_elements value\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ opts->stor_elements = token;
+ break;
case ZLOOP_OPT_ERR:
default:
pr_warn("unknown parameter or missing value '%s'\n", p);
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 08/12] zloop: add degrade_element control command
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (6 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 07/12] zloop: add storage element emulation Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 09/12] scsi: scsi_proto: refine the definition of storage element health Damien Le Moal
` (3 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Allow users to mark storage elements of a zloop device as degraded using
the new "degrade_element" control command. The element to degrade is
indicated using the element_id option. Example:
echo "degrade_element id=0,element_id=2" > /dev/zloop-control
If the element ID identifies an all access storage element, both read and
write operations targeting a zone served by the degraded element are
failed. In the case of a partial access storage element, any write access
to a zone whose write element is degraded will fail, and read accesses
to a zone whose read element is degraded will result in an error.
The zoned_loop.rst documentation file is also updated to explain this new
feature.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Martin K. Petersen <mkp@kernel.org>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
---
.../admin-guide/blockdev/zoned_loop.rst | 22 +++-
drivers/block/zloop.c | 100 +++++++++++++++++-
2 files changed, 119 insertions(+), 3 deletions(-)
diff --git a/Documentation/admin-guide/blockdev/zoned_loop.rst b/Documentation/admin-guide/blockdev/zoned_loop.rst
index 80a564836c1b..d378fbdc1983 100644
--- a/Documentation/admin-guide/blockdev/zoned_loop.rst
+++ b/Documentation/admin-guide/blockdev/zoned_loop.rst
@@ -63,6 +63,7 @@ The options available for the add command can be listed by reading the
$ cat /dev/zloop-control
add id=%d,capacity_mb=%u,zone_size_mb=%u,zone_capacity_mb=%u,conv_zones=%u,max_open_zones=%u,base_dir=%s,nr_queues=%u,queue_depth=%u,buffered_io,zone_append=%u,ordered_zone_append,discard_write_cache,stor_elements=%u
remove id=%d
+ degrade_element id=%d,element_id=%u
In more details, the options that can be used with the "add" command are as
follows.
@@ -144,7 +145,26 @@ the zoned device will not be created.
To fully delete a zoned device, after executing the remove operation, the device
base directory containing the backing files of the device zones must be deleted.
-4) Example
+4) Degrading a storage element
+------------------------------
+
+When storage element emulation is enabled, storage elements can be set in a
+degraded state to emulate defective storage elements (e.g. defective heads of a
+hard disk). This is done by issuing the "degrade_element" command to
+/dev/zloop-control, specifying the ID of the target device and the ID of the
+storage element to degrade::
+
+ $ echo "degrade_element id=X,element_id=Y" > /dev/zloop-control
+
+Depending on the type of the storage element specified, a read or write access
+to sectors of a zone served by a degraded storage element will result in a
+failure. For partial access storage elements, write accesses are always failed
+if the write element serving the zone is degraded. Likewise, read accesses are
+failed if the read element serving the zone is degraded. When storage elements
+are emulated as all access physical elements, both read and write operations are
+failed when the storage element serving the zones being accessed is degraded.
+
+5) Example
----------
The following sequence of commands creates a 2GB zoned device with zones of 64
diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
index 75506e6a4d75..c4a0a845a096 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -39,6 +39,7 @@ enum {
ZLOOP_OPT_DISCARD_WRITE_CACHE = (1 << 11),
ZLOOP_OPT_MAX_OPEN_ZONES = (1 << 12),
ZLOOP_OPT_STOR_ELEMENTS = (1 << 13),
+ ZLOOP_OPT_ELEMENT_ID = (1 << 14),
};
static const match_table_t zloop_opt_tokens = {
@@ -56,6 +57,7 @@ static const match_table_t zloop_opt_tokens = {
{ ZLOOP_OPT_DISCARD_WRITE_CACHE, "discard_write_cache" },
{ ZLOOP_OPT_MAX_OPEN_ZONES, "max_open_zones=%u" },
{ ZLOOP_OPT_STOR_ELEMENTS, "stor_elements=%u" },
+ { ZLOOP_OPT_ELEMENT_ID, "element_id=%u" },
{ ZLOOP_OPT_ERR, NULL }
};
@@ -82,6 +84,7 @@ enum zloop_stor_elements {
#define ZLOOP_DEF_ZONE_APPEND true
#define ZLOOP_DEF_ORDERED_ZONE_APPEND false
#define ZLOOP_DEF_STOR_ELEMENTS ZLOOP_STOR_ELEMENTS_NONE
+#define ZLOOP_DEF_ELEMENT_ID 0
/* Arbitrary limit on the zone size (16GB). */
#define ZLOOP_MAX_ZONE_SIZE_MB 16384
@@ -102,6 +105,7 @@ struct zloop_options {
bool ordered_zone_append;
bool discard_write_cache;
enum zloop_stor_elements stor_elements;
+ unsigned int element_id;
};
/*
@@ -1208,6 +1212,31 @@ static int zloop_report_elements(struct gendisk *disk,
return 0;
}
+static int zloop_degrade_element(struct zloop_device *zlo,
+ unsigned int element_id)
+{
+ struct blk_se *se;
+ int ret = 0;
+
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ if (!element_id || element_id > zlo->nr_elements)
+ return -EINVAL;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ se = &zlo->elements[element_id - 1];
+ if (READ_ONCE(se->status) == BLK_SE_STS_OK)
+ WRITE_ONCE(se->status, BLK_SE_STS_DEGRADED);
+ else
+ ret = -EINVAL;
+
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return ret;
+}
+
static int zloop_execute_remove_element(struct zloop_device *zlo)
{
struct gendisk *disk = zlo->disk;
@@ -1791,6 +1820,11 @@ static int zloop_ctl_add(struct zloop_options *opts)
int ret = -EINVAL;
bool restore;
+ if (opts->mask & ZLOOP_OPT_ELEMENT_ID) {
+ pr_err("Invalid option specified for add\n");
+ return -EINVAL;
+ }
+
__module_get(THIS_MODULE);
nr_zones = opts->capacity >> ilog2(opts->zone_size);
@@ -2074,6 +2108,49 @@ static int zloop_ctl_remove(struct zloop_options *opts)
return 0;
}
+static int zloop_ctl_degrade_element(struct zloop_options *opts)
+{
+ struct zloop_device *zlo;
+ int ret = 0;
+
+ if (!(opts->mask & ZLOOP_OPT_ID)) {
+ pr_err("No ID specified for degrade_element\n");
+ return -EINVAL;
+ }
+
+ if (!(opts->mask & ZLOOP_OPT_ELEMENT_ID)) {
+ pr_err("No element ID specified for degrade_element\n");
+ return -EINVAL;
+ }
+
+ if (opts->mask & ~(ZLOOP_OPT_ID | ZLOOP_OPT_ELEMENT_ID)) {
+ pr_err("Invalid option specified for degrade_element\n");
+ return -EINVAL;
+ }
+
+ ret = mutex_lock_killable(&zloop_ctl_mutex);
+ if (ret)
+ return ret;
+
+ zlo = idr_find(&zloop_index_idr, opts->id);
+ if (!zlo || zlo->state == Zlo_creating)
+ ret = -ENODEV;
+ else if (zlo->state == Zlo_deleting)
+ ret = -EINVAL;
+ if (ret)
+ goto unlock;
+
+ ret = zloop_degrade_element(zlo, opts->element_id);
+ if (!ret)
+ pr_info("%pg: degraded element %u\n",
+ zlo->disk->part0, opts->element_id);
+
+unlock:
+ mutex_unlock(&zloop_ctl_mutex);
+
+ return ret;
+}
+
static int zloop_parse_options(struct zloop_options *opts, const char *buf)
{
substring_t args[MAX_OPT_ARGS];
@@ -2094,6 +2171,7 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)
opts->zone_append = ZLOOP_DEF_ZONE_APPEND;
opts->ordered_zone_append = ZLOOP_DEF_ORDERED_ZONE_APPEND;
opts->stor_elements = ZLOOP_DEF_STOR_ELEMENTS;
+ opts->element_id = ZLOOP_DEF_ELEMENT_ID;
if (!buf)
return 0;
@@ -2245,6 +2323,13 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)
}
opts->stor_elements = token;
break;
+ case ZLOOP_OPT_ELEMENT_ID:
+ if (match_uint(args, &token)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ opts->element_id = token;
+ break;
case ZLOOP_OPT_ERR:
default:
pr_warn("unknown parameter or missing value '%s'\n", p);
@@ -2273,14 +2358,16 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)
enum {
ZLOOP_CTL_ADD,
ZLOOP_CTL_REMOVE,
+ ZLOOP_CTL_DEGRADE_ELEMENT,
};
static struct zloop_ctl_op {
int code;
const char *name;
} zloop_ctl_ops[] = {
- { ZLOOP_CTL_ADD, "add" },
- { ZLOOP_CTL_REMOVE, "remove" },
+ { ZLOOP_CTL_ADD, "add" },
+ { ZLOOP_CTL_REMOVE, "remove" },
+ { ZLOOP_CTL_DEGRADE_ELEMENT, "degrade_element" },
{ -1, NULL },
};
@@ -2328,6 +2415,9 @@ static ssize_t zloop_ctl_write(struct file *file, const char __user *ubuf,
case ZLOOP_CTL_REMOVE:
ret = zloop_ctl_remove(&opts);
break;
+ case ZLOOP_CTL_DEGRADE_ELEMENT:
+ ret = zloop_ctl_degrade_element(&opts);
+ break;
default:
pr_err("Invalid operation\n");
ret = -EINVAL;
@@ -2351,6 +2441,8 @@ static int zloop_ctl_show(struct seq_file *seq_file, void *private)
tok = &zloop_opt_tokens[i];
if (!tok->pattern)
break;
+ if (tok->token == ZLOOP_OPT_ELEMENT_ID)
+ continue;
if (i)
seq_putc(seq_file, ',');
seq_puts(seq_file, tok->pattern);
@@ -2361,6 +2453,10 @@ static int zloop_ctl_show(struct seq_file *seq_file, void *private)
seq_puts(seq_file, zloop_ctl_ops[1].name);
seq_puts(seq_file, " id=%d\n");
+ /* Degrade element operation */
+ seq_puts(seq_file, zloop_ctl_ops[2].name);
+ seq_puts(seq_file, " id=%d,element_id=%u\n");
+
return 0;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 09/12] scsi: scsi_proto: refine the definition of storage element health
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (7 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 08/12] zloop: add degrade_element control command Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:17 ` Hannes Reinecke
2026-11-11 15:28 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation Damien Le Moal
` (2 subsequent siblings)
11 siblings, 2 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
A disk may report the health of a storage element as being within or
outside of the manufacturer's specification limits using a range of values
for the health field. However, in scsi_proto.h, we only define the
start of each range using the SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS and
SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS enum value.
Define the full range by renaming SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS
to SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MIN and by defining the
healthy range end with SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MAX. The
same treatment is done for the outside range with the renaming of
SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS to
SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MIN, and the definition of the
range upper value with SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MAX.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
include/scsi/scsi_proto.h | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/include/scsi/scsi_proto.h b/include/scsi/scsi_proto.h
index 9c7d31aa6bae..f290a214e3e0 100644
--- a/include/scsi/scsi_proto.h
+++ b/include/scsi/scsi_proto.h
@@ -408,9 +408,11 @@ enum scsi_phys_element_type {
/* SCSI physical element health. */
enum scsi_phys_element_health {
SCSI_PHYS_ELEM_HEALTH_NOT_REPORTED = 0x00,
- SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS = 0x01,
+ SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MIN = 0x01,
+ SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MAX = 0x63,
SCSI_PHYS_ELEM_HEALTH_AT_SPEC_LIMITS = 0x64,
- SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS = 0x65,
+ SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MIN = 0x65,
+ SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MAX = 0xCF,
SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_ERR = 0xFB,
SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_IN_PROGRESS = 0xFC,
SCSI_PHYS_ELEM_HEALTH_DEPOP_ERR = 0xFD,
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (8 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 09/12] scsi: scsi_proto: refine the definition of storage element health Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:18 ` Hannes Reinecke
2026-11-11 15:29 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 11/12] scsi: sd_zbc: define storage element management operations Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 12/12] scsi: sd: fail read commands to offline zones early Damien Le Moal
11 siblings, 2 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
sd_zbc_revalidate_zones() skips revalidating the zones of a ZBC device if
the zone size and total number of zones of the disk has not changed. This
is to avoid the sometimes slow report zones for the entire disk that
blk_revalidate_disk_zones() will generate.
For ZBC devices that support the storage element depopulation feature, a
revalidation after executing a passthrough REMOVE ELEMENT AND TRUNCATE
command will thus correctly trigger a zone revalidation as the disk
capacity and number of zones changed.
However, for devices supporting the data preserving head depopulation
variant of the depopulation feature (REMOVE ELEMENT AND MODIFY ZONES
command), a disk capacity and number of zones does not change after a head
is depopulated. Only the conditions of the zones served by the head that
was depopulated change. These zones are transitioned to either read-only
or offline.
While the block layer storage element management functions always call
blk_revalidate_disk_zones() to catch these changes, a user scan, libata
device rescan after EH or a depopulation or restoration action executed
through SG_IO would not. In these cases, not calling
blk_revalidate_disk_zones() prevents the block layer from taking
appropriate actions on the zone write plugs of the disk.
Avoid any issue with the block layer view of the zone conditions by not
skipping the call to blk_revalidate_disk_zones() for disks that support
both the GET PHYSICAL ELEMENTS STATUS and REMOVE ELEMENT AND MODIFY ZONES
commands. This check is done from sd_zbc_read_zones() using the helper
function sd_zbc_check_depop_supported(). The new SCSI disk flags
depop_report_supported and depop_modify_zones_supported are defined to
remember the result of this check.
This change will slow down a little SMR disk revalidation. However, user
forced revalidations are normally rare, and other revalidations triggered
by udev only happen when the disk device files is not in use anymore
(closed). So this slow down should in practice not be an issue.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
drivers/scsi/sd.h | 2 ++
drivers/scsi/sd_zbc.c | 47 +++++++++++++++++++++++++++++++++++++++++--
2 files changed, 47 insertions(+), 2 deletions(-)
diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
index 574af8243016..84c87d0f05d8 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -156,6 +156,8 @@ struct scsi_disk {
unsigned ignore_medium_access_errors : 1;
unsigned rscs : 1; /* reduced stream control support */
unsigned use_atomic_write_boundary : 1;
+ unsigned depop_report_supported : 1;
+ unsigned depop_modify_zones_supported : 1;
};
#define to_scsi_disk(obj) container_of(obj, struct scsi_disk, disk_dev)
diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c
index 8cbf171e6fab..bf2e60071d57 100644
--- a/drivers/scsi/sd_zbc.c
+++ b/drivers/scsi/sd_zbc.c
@@ -516,6 +516,33 @@ static int sd_zbc_check_capacity(struct scsi_disk *sdkp, unsigned char *buf,
return 0;
}
+/*
+ * sd_zbc_check_depop_supported - Check if the device supports storage element
+ * depopulation
+ * @sdkp: Target SCSI disk
+ * @buf: command buffer
+ *
+ * Check if the device supports the REMOVE ELEMENT AND MODIFY ZONES command.
+ */
+static void sd_zbc_check_depop_supported(struct scsi_disk *sdkp,
+ unsigned char *buf)
+{
+ /* Mandate support for reporting storage elements health. */
+ sdkp->depop_report_supported =
+ scsi_report_opcode(sdkp->device, buf, SD_BUF_SIZE,
+ SERVICE_ACTION_IN_16,
+ SAI_GET_PHYSICAL_ELEMENT_STATUS) == 1;
+ if (!sdkp->depop_report_supported) {
+ sdkp->depop_modify_zones_supported = false;
+ return;
+ }
+
+ sdkp->depop_modify_zones_supported =
+ scsi_report_opcode(sdkp->device, buf, SD_BUF_SIZE,
+ SERVICE_ACTION_IN_16,
+ SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES) == 1;
+}
+
static void sd_zbc_print_zones(struct scsi_disk *sdkp)
{
if (sdkp->device->type != TYPE_ZBC || !sdkp->capacity)
@@ -545,6 +572,7 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp)
u32 zone_blocks = sdkp->early_zone_info.zone_blocks;
unsigned int nr_zones = sdkp->early_zone_info.nr_zones;
unsigned int flags;
+ bool print_zones;
int ret;
/*
@@ -554,11 +582,22 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp)
if (!blk_queue_is_zoned(q))
return 0;
+ /*
+ * If the zone size and number of zones has not changed, and the disk
+ * does not support depopulating heads, skip the rather slow call to
+ * blk_revalidate_disk_zones().
+ */
if (sdkp->zone_info.zone_blocks == zone_blocks &&
sdkp->zone_info.nr_zones == nr_zones &&
- disk->nr_zones == nr_zones)
+ disk->nr_zones == nr_zones &&
+ !sdkp->depop_modify_zones_supported)
return 0;
+ print_zones =
+ sdkp->zone_info.zone_blocks != zone_blocks ||
+ sdkp->zone_info.nr_zones != nr_zones ||
+ disk->nr_zones != nr_zones;
+
sdkp->zone_info.zone_blocks = zone_blocks;
sdkp->zone_info.nr_zones = nr_zones;
@@ -571,7 +610,8 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp)
return ret;
}
- sd_zbc_print_zones(sdkp);
+ if (print_zones)
+ sd_zbc_print_zones(sdkp);
return 0;
}
@@ -610,6 +650,9 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim,
sdkp->device->use_10_for_rw = 0;
sdkp->device->use_16_for_sync = 1;
+ /* Check if storage element depopulation is supported. */
+ sd_zbc_check_depop_supported(sdkp, buf);
+
/* Check zoned block device characteristics (unconstrained reads) */
ret = sd_zbc_check_zoned_characteristics(sdkp, buf);
if (ret)
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 11/12] scsi: sd_zbc: define storage element management operations
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (9 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
2026-11-09 11:21 ` Hannes Reinecke
2026-11-09 9:51 ` [PATCH v8 12/12] scsi: sd: fail read commands to offline zones early Damien Le Moal
11 siblings, 1 reply; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Define the storage element management operations using struct blk_se_ops.
These operations are valid only on SMR disks supporting the storage
element depopulation featu, with support for the GET PHYSICAL ELEMENTS
STATUS command being mandatory. The support for the modify_zones operation
is already indicated by the depop_modify_zones_supported SCSI disk flag.
sd_zbc_check_depop_supported() is modified to add detection for the
command RESTORE ELEMENTS AND REBUILD, which is indicated with the new
SCSI disk flag depop_restore_supported.
Both the remove_element and restore_elements operations will block
until the operation completes or an error is detected.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
---
drivers/scsi/sd.c | 3 +
drivers/scsi/sd.h | 3 +
drivers/scsi/sd_zbc.c | 471 +++++++++++++++++++++++++++++++++++++++++-
3 files changed, 476 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index dc45df819c42..34b44d72b2b7 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -3939,6 +3939,9 @@ static const struct block_device_operations sd_fops = {
.get_unique_id = sd_get_unique_id,
.free_disk = scsi_disk_free_disk,
.pr_ops = &sd_pr_ops,
+#ifdef CONFIG_BLK_DEV_ZONED
+ .se_ops = &sd_zbc_se_ops,
+#endif
};
/**
diff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h
index 84c87d0f05d8..8a32e5f771d8 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -158,6 +158,7 @@ struct scsi_disk {
unsigned use_atomic_write_boundary : 1;
unsigned depop_report_supported : 1;
unsigned depop_modify_zones_supported : 1;
+ unsigned depop_restore_supported : 1;
};
#define to_scsi_disk(obj) container_of(obj, struct scsi_disk, disk_dev)
@@ -244,6 +245,8 @@ unsigned int sd_zbc_complete(struct scsi_cmnd *cmd, unsigned int good_bytes,
int sd_zbc_report_zones(struct gendisk *disk, sector_t sector,
unsigned int nr_zones, struct blk_report_zones_args *args);
+extern const struct blk_se_ops sd_zbc_se_ops;
+
#else /* CONFIG_BLK_DEV_ZONED */
static inline int sd_zbc_read_zones(struct scsi_disk *sdkp,
diff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c
index bf2e60071d57..3bad1392f0e2 100644
--- a/drivers/scsi/sd_zbc.c
+++ b/drivers/scsi/sd_zbc.c
@@ -12,6 +12,7 @@
#include <linux/vmalloc.h>
#include <linux/sched/mm.h>
#include <linux/mutex.h>
+#include <linux/delay.h>
#include <linux/unaligned.h>
@@ -522,7 +523,8 @@ static int sd_zbc_check_capacity(struct scsi_disk *sdkp, unsigned char *buf,
* @sdkp: Target SCSI disk
* @buf: command buffer
*
- * Check if the device supports the REMOVE ELEMENT AND MODIFY ZONES command.
+ * Check if the device supports the storage elements depopulation management
+ * commands.
*/
static void sd_zbc_check_depop_supported(struct scsi_disk *sdkp,
unsigned char *buf)
@@ -534,6 +536,7 @@ static void sd_zbc_check_depop_supported(struct scsi_disk *sdkp,
SAI_GET_PHYSICAL_ELEMENT_STATUS) == 1;
if (!sdkp->depop_report_supported) {
sdkp->depop_modify_zones_supported = false;
+ sdkp->depop_restore_supported = false;
return;
}
@@ -541,6 +544,11 @@ static void sd_zbc_check_depop_supported(struct scsi_disk *sdkp,
scsi_report_opcode(sdkp->device, buf, SD_BUF_SIZE,
SERVICE_ACTION_IN_16,
SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES) == 1;
+
+ sdkp->depop_restore_supported =
+ scsi_report_opcode(sdkp->device, buf, SD_BUF_SIZE,
+ SERVICE_ACTION_IN_16,
+ SAI_RESTORE_ELEMENTS_AND_REBUILD) == 1;
}
static void sd_zbc_print_zones(struct scsi_disk *sdkp)
@@ -548,6 +556,11 @@ static void sd_zbc_print_zones(struct scsi_disk *sdkp)
if (sdkp->device->type != TYPE_ZBC || !sdkp->capacity)
return;
+ if (sdkp->depop_modify_zones_supported)
+ sd_printk(KERN_NOTICE, sdkp, "Depop modify zones supported\n");
+ if (sdkp->depop_restore_supported)
+ sd_printk(KERN_NOTICE, sdkp, "Depop restoration supported\n");
+
if (sdkp->capacity & (sdkp->zone_info.zone_blocks - 1))
sd_printk(KERN_NOTICE, sdkp,
"%u zones of %u logical blocks + 1 runt zone\n",
@@ -560,6 +573,462 @@ static void sd_zbc_print_zones(struct scsi_disk *sdkp)
sdkp->zone_info.zone_blocks);
}
+static void sd_zbc_parse_storage_element(struct scsi_disk *sdkp, u8 *desc,
+ struct blk_se *element)
+{
+ struct scsi_device *sdp = sdkp->device;
+ sector_t zone_sectors = sd_zbc_zone_sectors(sdkp);
+ u64 capacity;
+
+ memset(element, 0, sizeof(*element));
+
+ element->id = get_unaligned_be32(&desc[4]);
+ element->restore_allowed = desc[13] & 0x01;
+
+ switch (desc[14]) {
+ case SCSI_PHYS_ELEM_TYPE_ALL_ACCESS_STORAGE:
+ element->type = BLK_SE_TYPE_RDWR;
+ capacity = get_unaligned_be64(&desc[16]);
+ if (!zone_sectors || capacity == ULLONG_MAX)
+ element->nr_zones = 0;
+ else
+ element->nr_zones =
+ logical_to_sectors(sdp, capacity) >>
+ ilog2(zone_sectors);
+ break;
+ case SCSI_PHYS_ELEM_TYPE_FRAC_ACCESS_STORAGE:
+ element->paired_id = get_unaligned_be32(&desc[16]);
+ if (desc[20] & 0x01)
+ element->type = BLK_SE_TYPE_READ;
+ else
+ element->type = BLK_SE_TYPE_WRITE;
+ element->nr_zones = get_unaligned_be64(&desc[24]);
+ break;
+ default:
+ element->type = BLK_SE_TYPE_UNKNOWN;
+ }
+
+ if (desc[15] >= SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MIN &&
+ desc[15] <= SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MAX) {
+ element->status = BLK_SE_STS_OK;
+ return;
+ }
+
+ if (desc[15] >= SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MIN &&
+ desc[15] <= SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MAX) {
+ element->status = BLK_SE_STS_DEGRADED;
+ return;
+ }
+
+ switch (desc[15]) {
+ case SCSI_PHYS_ELEM_HEALTH_AT_SPEC_LIMITS:
+ element->status = BLK_SE_STS_OK_LIMIT;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_ERR:
+ element->status = BLK_SE_STS_RESTORE_ERROR;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_IN_PROGRESS:
+ element->status = BLK_SE_STS_RESTORE_IN_PROGRESS;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_DEPOP_ERR:
+ element->status = BLK_SE_STS_REMOVE_ERROR;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_DEPOP_IN_PROGRESS:
+ element->status = BLK_SE_STS_REMOVE_IN_PROGRESS;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_DEPOP_OK:
+ element->status = BLK_SE_STS_REMOVED;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_NOT_REPORTED:
+ default:
+ element->status = BLK_SE_STS_UNKNOWN;
+ break;
+ }
+}
+
+/*
+ * Large hard limit on the number of storage elements. This accommodates all
+ * known devices today and likely forever :)
+ */
+#define SD_ZBC_MAX_STORAGE_ELEMENTS 255
+
+struct sd_zbc_se_report {
+ struct blk_se *elements;
+ unsigned int nr_elements;
+ unsigned int nr_reported_elements;
+ bool need_free_elements;
+ u8 *buf;
+ unsigned int buf_size;
+};
+
+static int sd_zbc_alloc_se_report(struct blk_se *elements,
+ unsigned int nr_elements,
+ bool alloc_elements,
+ struct sd_zbc_se_report *rep)
+{
+ /*
+ * We need at least 32B for the report header and 32B for each
+ * descriptor. Note that we use GFP_KERNEL here, which may block. That
+ * is fine when called from ioctl context. When called from a file
+ * system, the FS can use memalloc_noio_save()/restore() or
+ * memalloc_nofs_save()/restore() if GFP_NOFS is required to avoid I/Os
+ * and/or FS re-entry.
+ */
+ rep->need_free_elements = alloc_elements;
+ rep->nr_elements = min(SD_ZBC_MAX_STORAGE_ELEMENTS, nr_elements);
+ rep->buf_size = ALIGN((rep->nr_elements + 1) * 32, SECTOR_SIZE);
+ rep->buf = kzalloc(rep->buf_size, GFP_KERNEL);
+ if (!rep->buf)
+ return -ENOMEM;
+
+ if (!alloc_elements) {
+ rep->elements = elements;
+ return 0;
+ }
+
+ rep->elements =
+ kzalloc_objs(struct blk_se, rep->nr_elements, GFP_KERNEL);
+ if (!rep->elements) {
+ kfree(rep->buf);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void sd_zbc_free_se_report(struct sd_zbc_se_report *rep)
+{
+ kfree(rep->buf);
+ if (rep->need_free_elements)
+ kfree(rep->elements);
+}
+
+static int sd_zbc_report_se(struct scsi_disk *sdkp,
+ struct sd_zbc_se_report *rep)
+{
+ struct scsi_device *sdp = sdkp->device;
+ const int timeout = sdp->request_queue->rq_timeout;
+ struct scsi_sense_hdr sshdr;
+ struct scsi_failure failure_defs[] = {
+ /* Allow up to retry 3 times */
+ {
+ .result = SCMD_FAILURE_RESULT_ANY,
+ .allowed = 3,
+ },
+ {}
+ };
+ struct scsi_failures failures = {
+ .failure_definitions = failure_defs,
+ };
+ const struct scsi_exec_args exec_args = {
+ .sshdr = &sshdr,
+ .failures = &failures,
+ };
+ unsigned char cmd[16];
+ unsigned int nr_descs;
+ int i, result;
+ u8 *desc;
+
+ memset(cmd, 0, 16);
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = SAI_GET_PHYSICAL_ELEMENT_STATUS;
+ put_unaligned_be32(rep->buf_size, &cmd[10]);
+
+ result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN,
+ rep->buf, rep->buf_size, timeout, 1, &exec_args);
+ if (result) {
+ if (result > 0) {
+ sd_print_result(sdkp, "GET PHYSICAL ELEMENT STATUS",
+ result);
+ if (scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ return -EIO;
+ }
+ return result;
+ }
+
+ nr_descs = get_unaligned_be32(&rep->buf[0]);
+ if (!nr_descs) {
+ sd_printk(KERN_ERR, sdkp,
+ "Invalid number of phys element descriptors\n");
+ return -EIO;
+ }
+ if (nr_descs > SD_ZBC_MAX_STORAGE_ELEMENTS) {
+ sd_printk(KERN_ERR, sdkp,
+ "Too many phys element descriptors\n");
+ return -EIO;
+ }
+
+ if (!rep->elements) {
+ rep->nr_reported_elements = nr_descs;
+ return 0;
+ }
+
+ nr_descs = get_unaligned_be32(&rep->buf[4]);
+ if (!nr_descs) {
+ sd_printk(KERN_ERR, sdkp,
+ "Invalid number of reported phys element descriptors\n");
+ return -EIO;
+ }
+
+ desc = &rep->buf[32];
+ for (i = 0; i < min(rep->nr_elements, nr_descs); i++, desc += 32)
+ sd_zbc_parse_storage_element(sdkp, desc, &rep->elements[i]);
+ rep->nr_reported_elements = i;
+
+ return 0;
+}
+
+static int sd_zbc_report_storage_elements(struct gendisk *disk,
+ struct blk_se *elements,
+ unsigned int *nr_elements)
+{
+ struct scsi_disk *sdkp = scsi_disk(disk);
+ struct sd_zbc_se_report rep;
+ int ret;
+
+ if (!sdkp->depop_report_supported)
+ return -EOPNOTSUPP;
+
+ ret = sd_zbc_alloc_se_report(elements, *nr_elements, false, &rep);
+ if (ret)
+ return ret;
+
+ ret = sd_zbc_report_se(sdkp, &rep);
+ if (!ret)
+ *nr_elements = rep.nr_reported_elements;
+
+ sd_zbc_free_se_report(&rep);
+
+ return ret;
+}
+
+static int sd_zbc_execute_storage_element_cmd(struct scsi_disk *sdkp,
+ u8 action, u32 element_id)
+{
+ struct scsi_device *sdp = sdkp->device;
+ const int timeout = sdp->request_queue->rq_timeout;
+ struct scsi_sense_hdr sshdr;
+ const struct scsi_exec_args exec_args = {
+ .sshdr = &sshdr,
+ };
+ unsigned char cmd[16];
+ const char *cmd_name;
+ int result;
+
+ memset(cmd, 0, sizeof(cmd));
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = action;
+ if (action == SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES) {
+ put_unaligned_be32(element_id, &cmd[10]);
+ cmd_name = "REMOVE ELEMENT AND MODIFY ZONES";
+ } else {
+ cmd_name = "RESTORE ELEMENTS AND REBUILD";
+ }
+
+ result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, NULL, 0,
+ timeout, 1, &exec_args);
+ if (result) {
+ sd_printk(KERN_ERR, sdkp, "%s failed\n", cmd_name);
+ if (result > 0) {
+ if (scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ result = -EIO;
+ }
+ return result;
+ }
+
+ return 0;
+}
+
+/*
+ * Wait for the completion of a storage element remove operation. Note that the
+ * standards advise to use pollable sense data with the REQUEST SENSE command to
+ * detect if a remove operation is in progress. However, since REQUEST SENSE
+ * will return sense data only for the last executed command, and since we are
+ * not blocking commands, this method would be unreliable as we may end up
+ * seeing sense data (or no sense data) for a command that is not the REMOVE
+ * ELEMENT AND MODIFY ZONES we issued.
+ */
+static int sd_zbc_wait_for_se_removal(struct scsi_disk *sdkp,
+ struct sd_zbc_se_report *rep,
+ unsigned int element_id)
+{
+ bool element_checked, element_removed;
+ int ret, in_progress;
+ unsigned int i;
+
+ /*
+ * Poll the storage elements health to see that we get the element we
+ * are interested in and that its removal completed. Since we do not
+ * know how long the operation may take, we cannot have a timeout on
+ * this loop. So check for fatal signals so that the user can terminate
+ * this loop if the device misbehaves.
+ */
+ do {
+ /* Let the operation proceed and check. */
+ msleep(1000);
+
+ ret = sd_zbc_report_se(sdkp, rep);
+ if (ret)
+ return ret;
+
+ element_checked = false;
+ element_removed = false;
+ in_progress = 0;
+ for (i = 0; i < rep->nr_elements; i++) {
+ if (rep->elements[i].id == element_id)
+ element_checked = true;
+ switch (rep->elements[i].status) {
+ case BLK_SE_STS_REMOVED:
+ if (rep->elements[i].id == element_id)
+ element_removed = true;
+ break;
+ case BLK_SE_STS_REMOVE_IN_PROGRESS:
+ in_progress++;
+ break;
+ case BLK_SE_STS_REMOVE_ERROR:
+ if (rep->elements[i].id == element_id)
+ return -EIO;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!element_checked)
+ return -EIO;
+ if (element_removed && !in_progress)
+ return 0;
+ } while (!fatal_signal_pending(current));
+
+ return -EINTR;
+}
+
+static int sd_zbc_remove_storage_element(struct gendisk *disk,
+ unsigned int element_id)
+{
+ struct scsi_disk *sdkp = scsi_disk(disk);
+ struct sd_zbc_se_report rep;
+ int ret;
+
+ if (!sdkp->depop_modify_zones_supported)
+ return -EOPNOTSUPP;
+
+ /*
+ * Get the resources we need to prepare for waiting for the operation to
+ * complete.
+ */
+ ret = sd_zbc_alloc_se_report(NULL, disk->nr_storage_elements,
+ true, &rep);
+ if (ret)
+ return ret;
+
+ /*
+ * Execute the remove command and wait for the element removal operation
+ * to complete.
+ */
+ ret = sd_zbc_execute_storage_element_cmd(sdkp,
+ SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES, element_id);
+ if (!ret)
+ ret = sd_zbc_wait_for_se_removal(sdkp, &rep, element_id);
+ if (!ret)
+ sd_printk(KERN_NOTICE, sdkp,
+ "Removed storage element %u\n", element_id);
+
+ sd_zbc_free_se_report(&rep);
+
+ return ret;
+}
+
+/*
+ * Wait for the completion of the restoration of removed storage elements.
+ * See the note for sd_zbc_wait_for_se_removal().
+ */
+static int sd_zbc_wait_for_se_restore(struct scsi_disk *sdkp,
+ struct sd_zbc_se_report *rep)
+{
+ int ret, in_progress;
+ unsigned int i;
+
+ /*
+ * Poll the storage elements health to see if we have any element still
+ * being restored or if we got errors. Since we do not know how long the
+ * operation may take, we cannot have a timeout on this loop. So check
+ * for fatal signals so that the user can terminate this retry loop if
+ * the device misbehaves.
+ */
+ do {
+ /* Let the operation proceed and check. */
+ msleep(1000);
+
+ ret = sd_zbc_report_se(sdkp, rep);
+ if (ret)
+ return ret;
+
+ in_progress = 0;
+ for (i = 0; i < rep->nr_elements; i++) {
+ switch (rep->elements[i].status) {
+ case BLK_SE_STS_RESTORE_IN_PROGRESS:
+ in_progress++;
+ break;
+ case BLK_SE_STS_REMOVED:
+ case BLK_SE_STS_RESTORE_ERROR:
+ if (rep->elements[i].restore_allowed)
+ ret = -EIO;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (!in_progress)
+ return ret;
+ } while (!fatal_signal_pending(current));
+
+ return -EINTR;
+}
+
+static int sd_zbc_restore_storage_elements(struct gendisk *disk)
+{
+ struct scsi_disk *sdkp = scsi_disk(disk);
+ struct sd_zbc_se_report rep;
+ int ret;
+
+ if (!sdkp->depop_restore_supported)
+ return -EOPNOTSUPP;
+
+ /*
+ * Get the resources we need to prepare for waiting for the operation to
+ * complete.
+ */
+ ret = sd_zbc_alloc_se_report(NULL, disk->nr_storage_elements,
+ true, &rep);
+ if (ret)
+ return ret;
+
+ /*
+ * Execute the restore command and wait for the elements restoration
+ * operation to complete.
+ */
+ ret = sd_zbc_execute_storage_element_cmd(sdkp,
+ SAI_RESTORE_ELEMENTS_AND_REBUILD, 0);
+ if (!ret)
+ ret = sd_zbc_wait_for_se_restore(sdkp, &rep);
+ if (!ret)
+ sd_printk(KERN_NOTICE, sdkp, "Restored storage elements\n");
+
+ sd_zbc_free_se_report(&rep);
+
+ return ret;
+}
+
+const struct blk_se_ops sd_zbc_se_ops = {
+ .report_elements = sd_zbc_report_storage_elements,
+ .remove_element = sd_zbc_remove_storage_element,
+ .restore_elements = sd_zbc_restore_storage_elements,
+};
+
/*
* Call blk_revalidate_disk_zones() if any of the zoned disk properties have
* changed that make it necessary to call that function. Called by
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* [PATCH v8 12/12] scsi: sd: fail read commands to offline zones early
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
` (10 preceding siblings ...)
2026-11-09 9:51 ` [PATCH v8 11/12] scsi: sd_zbc: define storage element management operations Damien Le Moal
@ 2026-11-09 9:51 ` Damien Le Moal
11 siblings, 0 replies; 27+ messages in thread
From: Damien Le Moal @ 2026-11-09 9:51 UTC (permalink / raw)
To: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Issuing read commands to offline zones of a ZBC disk will result in
failures. So do not issue such command and fail them early in
sd_setup_read_write_cmnd(). This change also checks write commands, which
is fine but should actually never happen as the block layer zone write
plugging code already fails write BIOs to offline zones early in the
submission path.
As the code comment explains, since disk_zone_is_offline() uses the block
layer cached zone condition, this is inherently racy with zone
revalidation updating the zone condition. However, this will never prevent
valid read operations as the only case where we could proceed is if the
read is received after a restoration of the disk storage element completes
but before the zones are revalidated. And in that case, all zones are
empty and thus have no data to read.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Martin K. Petersen <mkp@kernel.org>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
---
drivers/scsi/sd.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index 34b44d72b2b7..8b1c05de541d 100644
--- a/drivers/scsi/sd.c
+++ b/drivers/scsi/sd.c
@@ -1424,7 +1424,8 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd)
{
struct request *rq = scsi_cmd_to_rq(cmd);
struct scsi_device *sdp = cmd->device;
- struct scsi_disk *sdkp = scsi_disk(rq->q->disk);
+ struct gendisk *disk = rq->q->disk;
+ struct scsi_disk *sdkp = scsi_disk(disk);
sector_t lba = sectors_to_logical(sdp, blk_rq_pos(rq));
sector_t threshold;
unsigned int nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));
@@ -1456,6 +1457,23 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd)
goto fail;
}
+ /*
+ * The block layer already prevents writes to offline zones of zoned
+ * block devices. But there are no checks for reads, so check this here.
+ * Since disk_zone_is_offline() uses the block layer cached zone
+ * condition, this is inherently racy with zone revalidation updating
+ * the zone condition. However, this will never prevent valid read
+ * operations as the only case where we could proceed is if the read is
+ * received after a restoration of the disk storage element completes
+ * but before the zones are revalidated. And in that case, all zones are
+ * empty and thus have no data to read.
+ * Note that we are quiet about the error (no message) to avoid flooding
+ * the kernel log.
+ */
+ if (sdkp->device->type == TYPE_ZBC &&
+ disk_zone_is_offline(disk, blk_rq_pos(rq)))
+ goto fail;
+
/*
* Some SD card readers can't handle accesses which touch the
* last one or two logical blocks. Split accesses as needed.
--
2.56.0
^ permalink raw reply related [flat|nested] 27+ messages in thread
* Re: [PATCH v8 03/12] block: allow dropping all zone write plugs on revalidation
2026-11-09 9:51 ` [PATCH v8 03/12] block: allow dropping all zone write plugs on revalidation Damien Le Moal
@ 2026-11-09 11:13 ` Hannes Reinecke
2026-11-11 15:23 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:13 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> In preparation for supporting storage element depopulation, modify the
> function disk_revalidate_zones() to take an additional boolean parameter
> drop_all_zwplugs to force removing all zone write plugs from a disk hash
> table. Removing all zone write plugs is done in the same manner as for a
> capacity change, using the function disk_drop_zone_wplug() applied to all
> hashed zone write plugs.
>
> Calls to disk_revalidate_zones() with the argument drop_all_zwplugs set to
> true will be used when restoring depopulated storage elements as such
> operation clears all sequential zones, modifying them to be empty.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 v8 04/12] block: introduce storage element management
2026-11-09 9:51 ` [PATCH v8 04/12] block: introduce storage element management Damien Le Moal
@ 2026-11-09 11:14 ` Hannes Reinecke
2026-11-11 15:24 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:14 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> Recent SCSI (SBC) and ATA (ACS) standards define the storage element
> depopulation feature. This feature is intended for managing hard-disks
> heads, either combined read-write heads or pairs of read and write heads,
> allowing to keep disks with defective heads longer in production by
> allowing "depopulating" (removing) defective heads.
>
> This feature comes in two different flavors:
> - A destructive version which removes a head and reformats the disk at a
> lower capacity point, restoring a fully functional contiguous LBA
> address space.
> - A data preserving version restricted to host-managed zoned disks, which
> marks the zones served by a removed head as offline or read-only.
>
> In preparation for using the data-preserving flavor of the depopulation
> feature in file systems natively supporting zoned block devices, introduce
> a set of storage element management operations and functions to define
> generic calls into block device drivers for managing storage elements.
>
> The set of operations is defined with struct blk_se_ops and includes three
> operations:
> - report_elements: get information on a device storage elements state
> reported with the new struct blk_se.
> - remove_element: depopulate a defective storage element, blocking until
> the operation completes.
> - restore_elements: restore depopulated storage elements, blocking until
> the operation completes.
>
> Each operation is called from the functions
> bdev_report_storage_elements(), bdev_remove_storage_element() and
> bdev_restore_storage_elements() respectively.
>
> Removing a healthy storage element from a device is possible and useful
> for testing. The storage element restoration operation restore_elements
> allows repopulating such healthy element. Repopulating defective storage
> elements is generally not allowed by devices.
>
> The remove_element and restore_elements operations are executed under the
> zone revalidation lock so that they are serialized against each other and
> also serialized against user initiated device revalidation, thus allowing
> to always have a consistent view of the device zone conditions after
> executing these operations. In case of failure, zone revalidation is still
> executed, which may give an unstable view of the zone states. In such
> case, stable zone states will be recovered with zone access error handling
> or later zone revalidation.
>
> The device drivers of zoned block devices can indicate support for the
> storage element depopulation feature by specifying the storage element
> management operations with the se_ops field of struct
> block_device_operations.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 205 ++++++++++++++++++++++++++++++++++
> block/blk.h | 5 +
> block/genhd.c | 4 +
> include/linux/blkdev.h | 55 +++++++++
> include/uapi/linux/blkzoned.h | 65 +++++++++++
> 5 files changed, 334 insertions(+)
>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 v8 05/12] block: add storage element management ioctls
2026-11-09 9:51 ` [PATCH v8 05/12] block: add storage element management ioctls Damien Le Moal
@ 2026-11-09 11:15 ` Hannes Reinecke
2026-11-11 15:25 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:15 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> Define the new ioctl commands BLKREPORTSTORELEMS, BLKREMOVESTORELEM, and
> BLKRESTORESTORELEMS to provide users accessing zoned block devices
> directly with an interface to the storage elements management operations
> of the block layer.
>
> The ioctl BLKREPORTSTORELEMS allows to get the total number of storage
> elements of a zoned block device and to get an array of storage element
> descriptors (struct blk_se) describing the current state of the device
> storage elements.
>
> The ioctl BLKREMOVESTORELEM can be used to remove (depopulate) a storage
> element, in particular storage elements that have a degraded status
> (i.e. BLK_SE_STS_DEGRADED). But healthy elements (BLK_SE_STS_OK) can also
> be removed, and later restored.
>
> Finally, the BLKRESTORESTORELEMS ioctl interfaces with
> bdev_restore_storage_elements() to restore the depopulated storage
> elements of a zoned device.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> block/blk-zoned.c | 163 ++++++++++++++++++++++++++++++++++
> block/blk.h | 9 ++
> block/ioctl.c | 4 +
> include/uapi/linux/blkzoned.h | 41 +++++++++
> include/uapi/linux/fs.h | 1 +
> 5 files changed, 218 insertions(+)
>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 v8 06/12] zloop: fix zone reporting consistency
2026-11-09 9:51 ` [PATCH v8 06/12] zloop: fix zone reporting consistency Damien Le Moal
@ 2026-11-09 11:16 ` Hannes Reinecke
2026-11-11 15:27 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:16 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> In zloop_report_zones(), filling the write pointer and condition of a zone
> descriptor separately outside of the zone wp_lock can lead to an
> inconsistent zone report with a write pointer potentially not being
> consistent with the condition being reported. Avoid such inconsistency by
> filling both fields while holding the zone wp_lock.
>
> Fixes: fcc6eaa3a03a ("zloop: introduce the ordered_zone_append configuration parameter")
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> drivers/block/zloop.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/drivers/block/zloop.c b/drivers/block/zloop.c
> index 96666742523b..844a9f5db6a4 100644
> --- a/drivers/block/zloop.c
> +++ b/drivers/block/zloop.c
> @@ -992,8 +992,8 @@ static int zloop_report_zones(struct gendisk *disk, sector_t sector,
> blkz.len = zlo->zone_size;
> spin_lock(&zone->wp_lock);
> blkz.wp = zone->wp;
> - spin_unlock(&zone->wp_lock);
> blkz.cond = zone->cond;
> + spin_unlock(&zone->wp_lock);
> if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) {
> blkz.type = BLK_ZONE_TYPE_CONVENTIONAL;
> blkz.capacity = zlo->zone_size;
Reviewed=by: Hannes REinecke <hare@kernel.org>
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 v8 07/12] zloop: add storage element emulation
2026-11-09 9:51 ` [PATCH v8 07/12] zloop: add storage element emulation Damien Le Moal
@ 2026-11-09 11:17 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:17 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> Emulate the storage element depopulation feature of SMR disks in the
> zloop driver. This emulation is controlled using the new stor_elements
> option. This option can take several values:
> - ZLOOP_STOR_ELEMENTS_NONE (0): no emulation (default)
> - ZLOOP_STOR_ELEMENTS_RDWR (1): emulate all access storage elements
> (e.g. read+write heads)
> - ZLOOP_STOR_ELEMENTS_PAIRS (2): emulate fractional access storage
> elements (e.g. pairs of read and write heads)
>
> If enabled with the value 1 or 2, the number of storage elements, or of
> pairs of fractional access storage elements, is automatically calculated
> based on the number of zones of the device so that we have at least 2 and
> at most 32 storage elements or pairs of fractional access storage
> elements.
>
> The mapping of zones to storage elements is defined simply as the modulo
> of a zone number with the the number of storage elements for the all
> access storage elements emulation and with the number of storage element
> pairs for the fractional access storage element emulation case.
> E.g, removing a storage element from a set of 4 all access storage
> elements will offline 1 zone every 4 zones.
>
> The storage element management operations are specified with the
> zloop_se_ops (struct blk_se_ops).
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> .../admin-guide/blockdev/zoned_loop.rst | 11 +-
> drivers/block/zloop.c | 684 ++++++++++++++++--
> 2 files changed, 653 insertions(+), 42 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 v8 09/12] scsi: scsi_proto: refine the definition of storage element health
2026-11-09 9:51 ` [PATCH v8 09/12] scsi: scsi_proto: refine the definition of storage element health Damien Le Moal
@ 2026-11-09 11:17 ` Hannes Reinecke
2026-11-11 15:28 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:17 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> A disk may report the health of a storage element as being within or
> outside of the manufacturer's specification limits using a range of values
> for the health field. However, in scsi_proto.h, we only define the
> start of each range using the SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS and
> SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS enum value.
>
> Define the full range by renaming SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS
> to SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MIN and by defining the
> healthy range end with SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MAX. The
> same treatment is done for the outside range with the renaming of
> SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS to
> SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MIN, and the definition of the
> range upper value with SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MAX.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> include/scsi/scsi_proto.h | 6 ++++--
> 1 file changed, 4 insertions(+), 2 deletions(-)
>
> diff --git a/include/scsi/scsi_proto.h b/include/scsi/scsi_proto.h
> index 9c7d31aa6bae..f290a214e3e0 100644
> --- a/include/scsi/scsi_proto.h
> +++ b/include/scsi/scsi_proto.h
> @@ -408,9 +408,11 @@ enum scsi_phys_element_type {
> /* SCSI physical element health. */
> enum scsi_phys_element_health {
> SCSI_PHYS_ELEM_HEALTH_NOT_REPORTED = 0x00,
> - SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS = 0x01,
> + SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MIN = 0x01,
> + SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS_MAX = 0x63,
> SCSI_PHYS_ELEM_HEALTH_AT_SPEC_LIMITS = 0x64,
> - SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS = 0x65,
> + SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MIN = 0x65,
> + SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS_MAX = 0xCF,
> SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_ERR = 0xFB,
> SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_IN_PROGRESS = 0xFC,
> SCSI_PHYS_ELEM_HEALTH_DEPOP_ERR = 0xFD,
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation
2026-11-09 9:51 ` [PATCH v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation Damien Le Moal
@ 2026-11-09 11:18 ` Hannes Reinecke
2026-11-11 15:29 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:18 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> sd_zbc_revalidate_zones() skips revalidating the zones of a ZBC device if
> the zone size and total number of zones of the disk has not changed. This
> is to avoid the sometimes slow report zones for the entire disk that
> blk_revalidate_disk_zones() will generate.
>
> For ZBC devices that support the storage element depopulation feature, a
> revalidation after executing a passthrough REMOVE ELEMENT AND TRUNCATE
> command will thus correctly trigger a zone revalidation as the disk
> capacity and number of zones changed.
>
> However, for devices supporting the data preserving head depopulation
> variant of the depopulation feature (REMOVE ELEMENT AND MODIFY ZONES
> command), a disk capacity and number of zones does not change after a head
> is depopulated. Only the conditions of the zones served by the head that
> was depopulated change. These zones are transitioned to either read-only
> or offline.
>
> While the block layer storage element management functions always call
> blk_revalidate_disk_zones() to catch these changes, a user scan, libata
> device rescan after EH or a depopulation or restoration action executed
> through SG_IO would not. In these cases, not calling
> blk_revalidate_disk_zones() prevents the block layer from taking
> appropriate actions on the zone write plugs of the disk.
>
> Avoid any issue with the block layer view of the zone conditions by not
> skipping the call to blk_revalidate_disk_zones() for disks that support
> both the GET PHYSICAL ELEMENTS STATUS and REMOVE ELEMENT AND MODIFY ZONES
> commands. This check is done from sd_zbc_read_zones() using the helper
> function sd_zbc_check_depop_supported(). The new SCSI disk flags
> depop_report_supported and depop_modify_zones_supported are defined to
> remember the result of this check.
>
> This change will slow down a little SMR disk revalidation. However, user
> forced revalidations are normally rare, and other revalidations triggered
> by udev only happen when the disk device files is not in use anymore
> (closed). So this slow down should in practice not be an issue.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> drivers/scsi/sd.h | 2 ++
> drivers/scsi/sd_zbc.c | 47 +++++++++++++++++++++++++++++++++++++++++--
> 2 files changed, 47 insertions(+), 2 deletions(-)
>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 v8 11/12] scsi: sd_zbc: define storage element management operations
2026-11-09 9:51 ` [PATCH v8 11/12] scsi: sd_zbc: define storage element management operations Damien Le Moal
@ 2026-11-09 11:21 ` Hannes Reinecke
0 siblings, 0 replies; 27+ messages in thread
From: Hannes Reinecke @ 2026-11-09 11:21 UTC (permalink / raw)
To: Damien Le Moal, Jens Axboe, linux-block, Christoph Hellwig,
linux-scsi, Martin K . Petersen
On 11/9/26 10:51 AM, Damien Le Moal wrote:
> Define the storage element management operations using struct blk_se_ops.
> These operations are valid only on SMR disks supporting the storage
> element depopulation featu, with support for the GET PHYSICAL ELEMENTS
> STATUS command being mandatory. The support for the modify_zones operation
> is already indicated by the depop_modify_zones_supported SCSI disk flag.
> sd_zbc_check_depop_supported() is modified to add detection for the
> command RESTORE ELEMENTS AND REBUILD, which is indicated with the new
> SCSI disk flag depop_restore_supported.
>
> Both the remove_element and restore_elements operations will block
> until the operation completes or an error is detected.
>
> Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
> ---
> drivers/scsi/sd.c | 3 +
> drivers/scsi/sd.h | 3 +
> drivers/scsi/sd_zbc.c | 471 +++++++++++++++++++++++++++++++++++++++++-
> 3 files changed, 476 insertions(+), 1 deletion(-)
>
I am not sure if I particularly like the .ops method, but I'm also
not seeing an alternative approach.
So:
Reviewed-by: Hannes Reinecke <hare@kernel.org>
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 v8 03/12] block: allow dropping all zone write plugs on revalidation
2026-11-09 9:51 ` [PATCH v8 03/12] block: allow dropping all zone write plugs on revalidation Damien Le Moal
2026-11-09 11:13 ` Hannes Reinecke
@ 2026-11-11 15:23 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2026-11-11 15:23 UTC (permalink / raw)
To: Damien Le Moal
Cc: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v8 04/12] block: introduce storage element management
2026-11-09 9:51 ` [PATCH v8 04/12] block: introduce storage element management Damien Le Moal
2026-11-09 11:14 ` Hannes Reinecke
@ 2026-11-11 15:24 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2026-11-11 15:24 UTC (permalink / raw)
To: Damien Le Moal
Cc: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v8 05/12] block: add storage element management ioctls
2026-11-09 9:51 ` [PATCH v8 05/12] block: add storage element management ioctls Damien Le Moal
2026-11-09 11:15 ` Hannes Reinecke
@ 2026-11-11 15:25 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2026-11-11 15:25 UTC (permalink / raw)
To: Damien Le Moal
Cc: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v8 06/12] zloop: fix zone reporting consistency
2026-11-09 9:51 ` [PATCH v8 06/12] zloop: fix zone reporting consistency Damien Le Moal
2026-11-09 11:16 ` Hannes Reinecke
@ 2026-11-11 15:27 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2026-11-11 15:27 UTC (permalink / raw)
To: Damien Le Moal
Cc: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
I think the Sashiko comment on this is correct, so more work
to do here..
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v8 09/12] scsi: scsi_proto: refine the definition of storage element health
2026-11-09 9:51 ` [PATCH v8 09/12] scsi: scsi_proto: refine the definition of storage element health Damien Le Moal
2026-11-09 11:17 ` Hannes Reinecke
@ 2026-11-11 15:28 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2026-11-11 15:28 UTC (permalink / raw)
To: Damien Le Moal
Cc: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation
2026-11-09 9:51 ` [PATCH v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation Damien Le Moal
2026-11-09 11:18 ` Hannes Reinecke
@ 2026-11-11 15:29 ` Christoph Hellwig
1 sibling, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2026-11-11 15:29 UTC (permalink / raw)
To: Damien Le Moal
Cc: Jens Axboe, linux-block, Christoph Hellwig, linux-scsi,
Martin K . Petersen
Looks good:
Reviewed-by: Christoph Hellwig <hch@lst.de>
^ permalink raw reply [flat|nested] 27+ messages in thread
end of thread, other threads:[~2026-11-11 15:29 UTC | newest]
Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-09 9:51 [PATCH v8 00/12] Add support for storage element depopulation Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 01/12] block: introduce disk_zone_is_offline() Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 02/12] block: refactor blk_revalidate_disk_zones() Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 03/12] block: allow dropping all zone write plugs on revalidation Damien Le Moal
2026-11-09 11:13 ` Hannes Reinecke
2026-11-11 15:23 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 04/12] block: introduce storage element management Damien Le Moal
2026-11-09 11:14 ` Hannes Reinecke
2026-11-11 15:24 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 05/12] block: add storage element management ioctls Damien Le Moal
2026-11-09 11:15 ` Hannes Reinecke
2026-11-11 15:25 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 06/12] zloop: fix zone reporting consistency Damien Le Moal
2026-11-09 11:16 ` Hannes Reinecke
2026-11-11 15:27 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 07/12] zloop: add storage element emulation Damien Le Moal
2026-11-09 11:17 ` Hannes Reinecke
2026-11-09 9:51 ` [PATCH v8 08/12] zloop: add degrade_element control command Damien Le Moal
2026-11-09 9:51 ` [PATCH v8 09/12] scsi: scsi_proto: refine the definition of storage element health Damien Le Moal
2026-11-09 11:17 ` Hannes Reinecke
2026-11-11 15:28 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 10/12] scsi: sd_zbc: always revalidate zones for disks supporting head depopulation Damien Le Moal
2026-11-09 11:18 ` Hannes Reinecke
2026-11-11 15:29 ` Christoph Hellwig
2026-11-09 9:51 ` [PATCH v8 11/12] scsi: sd_zbc: define storage element management operations Damien Le Moal
2026-11-09 11:21 ` Hannes Reinecke
2026-11-09 9:51 ` [PATCH v8 12/12] scsi: sd: fail read commands to offline zones early 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