| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/10/12 13:43 | flow | patch-triage |
8mArgs:null Results: {
"EnableConfigs": [
"BLK_DEV_ZONED",
"BLK_DEV_ZONED_LOOP",
"BLK_DEV_SD"
],
"FocusSymbols": [
"blkdev_zone_storage_elements_ioctl",
"bdev_report_storage_elements",
"bdev_remove_storage_element",
"bdev_restore_storage_elements",
"disk_zone_is_offline",
"zloop_report_elements",
"zloop_remove_element",
"zloop_restore_elements",
"zloop_degrade_element",
"zloop_ctl_degrade_element",
"sd_zbc_report_storage_elements",
"sd_zbc_remove_storage_element",
"sd_zbc_restore_storage_elements"
],
"KMSANReasoning": "The patch introduces storage element management for zoned block devices, including new ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) and support in the zloop and sd drivers.\n\n1. Kernel-to-User Information Leaks:\n - In `blkdev_get_nr_storage_elements_ioctl()`, `put_user` copies `disk-\u003enr_storage_elements`, which is an explicitly initialized scalar (`unsigned int`).\n - In `blkdev_report_storage_elements_ioctl()`, the report buffer is allocated with `kzalloc_flex()`, ensuring all payload bytes and potential padding are zeroed.\n - The UAPI structures `struct blk_se` (32 bytes) and `struct blk_se_report` (16 bytes header) are explicitly laid out with aligned fields and reserved byte padding (`reserved[13]` and `reserved`), leaving no compiler padding holes on 64-bit architectures.\n - In driver implementations: `sd_zbc_parse_storage_element()` starts with `memset(element, 0, sizeof(*element))`, and `zloop_create_storage_elements()` allocates descriptors via `kzalloc_objs()`.\n\n2. Uninitialized Memory Access in Control Flow:\n - All stack and heap buffers introduced by the patch (`opts` in `zloop_ctl_write`, `args` in `disk_revalidate_zones`, `elements` in `bdev_remove_storage_element` / `bdev_restore_storage_elements`) are zero-initialized (`{ }`, `kzalloc_objs`, etc.).\n - Variables controlling branching (e.g., `in_progress` in `disk_wait_for_se_mgmt_completion()`) are strictly set upon success (`ret == 0`) before being evaluated.\n\n3. KASAN vs KMSAN applicability:\n - Any potential risks introduced in the patch (such as out-of-bounds indexing in `zloop_remove_element` if `element_id == 0`, concurrency/locking with workqueues, or block queue state issues) fall under out-of-bounds, synchronization, or logic errors, which are detected by KASAN and LOCKDEP.\n - No uninitialized memory reads or information leak vulnerabilities are introduced that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false,
"Reasoning": "The patch series introduces storage element (depopulation/repopulation) management for zoned block devices. It adds new UAPI ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) handled in the block layer, implements storage element emulation and control operations (degrade_element) in the virtual zoned loop driver (zloop), and implements SCSI ZBC storage element operations. The modified code introduces reachable ioctl paths, device state transitions, queue freezing/unfreezing, delayed work handling, and error checking, making it an excellent target for fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/10/12 13:43 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 0f648578ec255b07dcf26e1f7c25cf9d70a5b080\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Mon Oct 12 13:43:15 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/Documentation/admin-guide/blockdev/zoned_loop.rst b/Documentation/admin-guide/blockdev/zoned_loop.rst\nindex 64277494fb36a..ec39f141af8d6 100644\n--- a/Documentation/admin-guide/blockdev/zoned_loop.rst\n+++ b/Documentation/admin-guide/blockdev/zoned_loop.rst\n@@ -61,7 +61,7 @@ The options available for the add command can be listed by reading the\n /dev/zloop-control device::\n \n \t$ cat /dev/zloop-control\n- 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\n+ 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\n remove id=%d\n \n In more details, the options that can be used with the \"add\" command are as\n@@ -113,6 +113,12 @@ discard_write_cache Discard all data that was not explicitly persisted using a\n each zone file to the size recorded during the last flush\n operation. This simulates power fail events where\n uncommitted data is lost.\n+stor_elements Control storage element emulation. The default value is 0,\n+ indicating no emulation. A value of 1 indicates that all\n+ access storage elements (equivalent to read+write head of\n+ a disk) are emulated. A value of 2 enables fractional\n+ access storage element (equivalent to pairs of read and\n+ write heads of a disk) emulation .\n =================== =========================================================\n \n 3) Deleting a Zoned Device\ndiff --git a/block/blk-zoned.c b/block/blk-zoned.c\nindex 19268afb8752e..8736e80b4080d 100644\n--- a/block/blk-zoned.c\n+++ b/block/blk-zoned.c\n@@ -18,6 +18,8 @@\n #include \u003clinux/mempool.h\u003e\n #include \u003clinux/kthread.h\u003e\n #include \u003clinux/freezer.h\u003e\n+#include \u003clinux/delay.h\u003e\n+#include \u003clinux/uaccess.h\u003e\n \n #include \u003ctrace/events/block.h\u003e\n \n@@ -292,6 +294,22 @@ static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector)\n \treturn !blk_zstate_is_conv(zs);\n }\n \n+/**\n+ * disk_zone_is_offline - check if a sector belongs to an offline zone\n+ * @disk:\tgendisk to check\n+ * @sector:\tsector number\n+ *\n+ * Check if @sector on @disk is contained in an offline zone.\n+ */\n+bool disk_zone_is_offline(struct gendisk *disk, sector_t sector)\n+{\n+\tif (!blk_queue_is_zoned(disk-\u003equeue))\n+\t\treturn false;\n+\n+\treturn disk_zone_get_cond(disk, sector) == BLK_ZONE_COND_OFFLINE;\n+}\n+EXPORT_SYMBOL_GPL(disk_zone_is_offline);\n+\n /**\n * bdev_zone_is_seq - check if a sector belongs to a sequential write zone\n * @bdev: block device to check\n@@ -2526,20 +2544,7 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,\n \treturn ret;\n }\n \n-/**\n- * blk_revalidate_disk_zones - (re)allocate and initialize zone write plugs\n- * @disk:\tTarget disk\n- *\n- * Helper function for low-level device drivers to check, (re) allocate and\n- * initialize resources used for managing zoned disks. This function should\n- * normally be called by blk-mq based drivers when a zoned gendisk is probed\n- * and when the zone configuration of the gendisk changes (e.g. after a format).\n- * Before calling this function, the device driver must already have set the\n- * device zone size (chunk_sector limit) and the max zone append limit.\n- * BIO based drivers can also use this function as long as the device queue\n- * can be safely frozen.\n- */\n-int blk_revalidate_disk_zones(struct gendisk *disk)\n+static int disk_revalidate_zones(struct gendisk *disk)\n {\n \tstruct blk_revalidate_zone_args args = { };\n \tstruct blk_report_zones_args rep_args = {\n@@ -2550,19 +2555,10 @@ int blk_revalidate_disk_zones(struct gendisk *disk)\n \tint retries = 2;\n \tint ret;\n \n-\tif (WARN_ON_ONCE(!blk_queue_is_zoned(disk-\u003equeue)))\n-\t\treturn -EIO;\n-\n-\t/*\n-\t * Serialize calls to this function so that we can safely look at and\n-\t * eventually change the disk zone information.\n-\t */\n-\tmutex_lock(\u0026disk-\u003ezone_revalidate_mutex);\n-\n again:\n \tret = disk_revalidate_capacity(disk, \u0026args);\n \tif (ret)\n-\t\tgoto unlock;\n+\t\treturn ret;\n \n \t/*\n \t * Allocate zone resources if they are needed and we have not done\n@@ -2575,14 +2571,14 @@ int blk_revalidate_disk_zones(struct gendisk *disk)\n \t\tret = disk_alloc_zone_resources(disk, args.capacity);\n \t\tif (ret) {\n \t\t\tmemalloc_noio_restore(noio_flag);\n-\t\t\tgoto unlock;\n+\t\t\treturn ret;\n \t\t}\n \t}\n \n \tret = disk_init_revalidate_args(disk, \u0026args);\n \tif (ret) {\n \t\tmemalloc_noio_restore(noio_flag);\n-\t\tgoto unlock;\n+\t\treturn ret;\n \t}\n \n \tret = disk-\u003efops-\u003ereport_zones(disk, 0, UINT_MAX, \u0026rep_args);\n@@ -2599,8 +2595,6 @@ int blk_revalidate_disk_zones(struct gendisk *disk)\n \tif (ret)\n \t\tgoto free_args;\n \n-\tmutex_unlock(\u0026disk-\u003ezone_revalidate_mutex);\n-\n \treturn 0;\n \n free_args:\n@@ -2616,7 +2610,36 @@ int blk_revalidate_disk_zones(struct gendisk *disk)\n \t}\n \n \tpr_warn(\"%s: failed to revalidate zones\\n\", disk-\u003edisk_name);\n-unlock:\n+\n+\treturn ret;\n+}\n+\n+/**\n+ * blk_revalidate_disk_zones - (re)allocate and initialize zone write plugs\n+ * @disk:\tTarget disk\n+ *\n+ * Helper function for low-level device drivers to check, (re) allocate and\n+ * initialize resources used for managing zoned disks. This function should\n+ * normally be called by blk-mq based drivers when a zoned gendisk is probed\n+ * and when the zone configuration of the gendisk changes (e.g. after a format).\n+ * Before calling this function, the device driver must already have set the\n+ * device zone size (chunk_sector limit) and the max zone append limit.\n+ * BIO based drivers can also use this function as long as the device queue\n+ * can be safely frozen.\n+ */\n+int blk_revalidate_disk_zones(struct gendisk *disk)\n+{\n+\tint ret;\n+\n+\tif (WARN_ON_ONCE(!blk_queue_is_zoned(disk-\u003equeue)))\n+\t\treturn -EIO;\n+\n+\t/*\n+\t * Serialize calls to this function so that we can safely look at and\n+\t * eventually change the disk zone information.\n+\t */\n+\tmutex_lock(\u0026disk-\u003ezone_revalidate_mutex);\n+\tret = disk_revalidate_zones(disk);\n \tmutex_unlock(\u0026disk-\u003ezone_revalidate_mutex);\n \n \treturn ret;\n@@ -2700,5 +2723,438 @@ int queue_zone_wplugs_show(void *data, struct seq_file *m)\n \n \treturn 0;\n }\n-\n #endif\n+\n+static int disk_se_mgmt_in_progress(struct gendisk *disk,\n+\t\t\t\t struct blk_se *elements,\n+\t\t\t\t bool *in_progress)\n+{\n+\tunsigned int i, nr_se = disk-\u003enr_storage_elements;\n+\tint ret;\n+\n+\t/*\n+\t * Check if we have storage elements being removed or restored.\n+\t */\n+\tret = disk-\u003efops-\u003ese_ops-\u003ereport_elements(disk, elements, \u0026nr_se);\n+\tif (ret) {\n+\t\tpr_err(\"Failed to get storage elements\\n\");\n+\t\treturn ret;\n+\t}\n+\n+\tfor (i = 0; i \u003c nr_se; i++) {\n+\t\tswitch (elements[i].status) {\n+\t\tcase BLK_SE_STS_REMOVE_IN_PROGRESS:\n+\t\tcase BLK_SE_STS_RESTORE_IN_PROGRESS:\n+\t\t\t*in_progress = true;\n+\t\t\treturn 0;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t*in_progress = false;\n+\n+\treturn 0;\n+}\n+\n+static int disk_check_remove_error(struct gendisk *disk,\n+\t\t\t\t struct blk_se *elements,\n+\t\t\t\t unsigned int element_id)\n+{\n+\tunsigned int i;\n+\n+\tfor (i = 0; i \u003c disk-\u003enr_storage_elements; i++) {\n+\t\tif (elements[i].id == element_id\n+\t\t \u0026\u0026 elements[i].status == BLK_SE_STS_REMOVE_ERROR)\n+\t\t\treturn -EIO;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int disk_wait_for_se_mgmt_completion(struct gendisk *disk,\n+\t\t\t\t\t struct blk_se *elements,\n+\t\t\t\t\t unsigned int element_id)\n+{\n+\tbool in_progress;\n+\tint ret;\n+\n+\t/*\n+\t * Poll the storage elements heath state to detect the end of a\n+\t * removal or restoration operation. Since we do not know how long the\n+\t * operation may take, we cannot have a timeout on this loop. So check\n+\t * for fatal signals so that the user can terminate this retry loop if\n+\t * the device misbehaves.\n+\t */\n+\twhile (1) {\n+\t\tret = disk_se_mgmt_in_progress(disk, elements, \u0026in_progress);\n+\t\tif (ret || !in_progress)\n+\t\t\tbreak;\n+\n+\t\tif (fatal_signal_pending(current)) {\n+\t\t\tret = -EINTR;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\t/* Not done yet: wait and retry. */\n+\t\tmsleep(1000);\n+\t}\n+\n+\t/* Check for remove errors. */\n+\tif (!ret \u0026\u0026 !in_progress \u0026\u0026 element_id)\n+\t\tret = disk_check_remove_error(disk, elements, element_id);\n+\n+\treturn ret;\n+}\n+\n+/**\n+ * bdev_report_storage_elements - report the storage elements of a zoned\n+ *\t\t\t\t block device\n+ * @bdev:\tthe zoned block device to check\n+ * @elements:\tarray of storage element descriptors to fill\n+ * @nr_elements: maximum size of @elements\n+ *\n+ * Fill at most @nr_elements storage element descriptors in the array\n+ * @elements. The number of storage elements filled in the array is returned\n+ * using @nr_elements. If @elements is NULL, only @nr_elements is returned to\n+ * indicate the maximum number of storage elements of @bdev.\n+ *\n+ * Returns 0 on success and a negative error code on failure.\n+ */\n+int bdev_report_storage_elements(struct block_device *bdev,\n+\t\t\t\t struct blk_se *elements,\n+\t\t\t\t unsigned int *nr_elements)\n+{\n+\tstruct gendisk *disk = bdev-\u003ebd_disk;\n+\n+\tif (!blk_queue_is_zoned(disk-\u003equeue) || !disk-\u003enr_storage_elements)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (!nr_elements)\n+\t\treturn -EINVAL;\n+\n+\tif (!elements) {\n+\t\t*nr_elements = disk-\u003enr_storage_elements;\n+\t\treturn 0;\n+\t}\n+\n+\t*nr_elements = min(*nr_elements, disk-\u003enr_storage_elements);\n+\tif (!*nr_elements)\n+\t\treturn -EINVAL;\n+\n+\treturn disk-\u003efops-\u003ese_ops-\u003ereport_elements(disk, elements,\n+\t\t\t\t\t\t nr_elements);\n+}\n+EXPORT_SYMBOL_GPL(bdev_report_storage_elements);\n+\n+static int blkdev_get_nr_storage_elements_ioctl(struct block_device *bdev,\n+\t\t\t\t\t\tvoid __user *argp)\n+{\n+\tstruct gendisk *disk = bdev-\u003ebd_disk;\n+\n+\tif (put_user(disk-\u003enr_storage_elements, (unsigned int __user *)argp))\n+\t\treturn -EFAULT;\n+\n+\treturn 0;\n+}\n+\n+static int blkdev_report_storage_elements_ioctl(struct block_device *bdev,\n+\t\t\t\t\t\tvoid __user *argp)\n+{\n+\tstruct gendisk *disk = bdev-\u003ebd_disk;\n+\tstruct blk_se_report *rep;\n+\tint ret;\n+\n+\tif (!argp)\n+\t\treturn -EINVAL;\n+\n+\trep = kzalloc_flex(*rep, elements, disk-\u003enr_storage_elements);\n+\tif (!rep)\n+\t\treturn -ENOMEM;\n+\n+\tif (copy_from_user(rep, argp, sizeof(struct blk_se_report))) {\n+\t\tret = -EFAULT;\n+\t\tgoto free_rep;\n+\t}\n+\n+\tif (rep-\u003enr_elements \u003c disk-\u003enr_storage_elements) {\n+\t\tret = -EINVAL;\n+\t\tgoto free_rep;\n+\t}\n+\n+\trep-\u003enr_reported_elements = disk-\u003enr_storage_elements;\n+\tret = bdev_report_storage_elements(bdev, rep-\u003eelements,\n+\t\t\t\t\t \u0026rep-\u003enr_reported_elements);\n+\tif (ret)\n+\t\tgoto free_rep;\n+\n+\tif (copy_to_user(argp, rep, struct_size_t(struct blk_se_report,\n+\t\t\t\t\telements, rep-\u003enr_reported_elements)))\n+\t\tret = -EFAULT;\n+\n+free_rep:\n+\tkfree(rep);\n+\treturn ret;\n+}\n+\n+/**\n+ * bdev_remove_storage_element - Remove (depopulate) a storage element of a\n+ *\t\t\t\t zoned block device\n+ * @bdev:\tblock device to operate on\n+ * @element_id:\tID of the storage element to remove\n+ *\n+ * Remove (depopulate) the storage element identified by @element_id from the\n+ * block device @bdev. The caller is responsible for taking care of any\n+ * necessary device write cache flush and invalidation of cached data for the\n+ * zones that will be offlined.\n+ *\n+ * Returns 0 on success and a negative error code on failure.\n+ */\n+int bdev_remove_storage_element(struct block_device *bdev,\n+\t\t\t\tunsigned int element_id)\n+{\n+\tstruct gendisk *disk = bdev-\u003ebd_disk;\n+\tunsigned int memflags, noio_flag;\n+\tstruct blk_se *elements;\n+\tint ret, err;\n+\n+\tif (!blk_queue_is_zoned(disk-\u003equeue) || !disk-\u003enr_storage_elements)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/* Zero is not a valid storage element ID. */\n+\tif (!element_id)\n+\t\treturn -EINVAL;\n+\n+\tnoio_flag = memalloc_noio_save();\n+\telements = kzalloc_objs(struct blk_se, disk-\u003enr_storage_elements);\n+\tmemalloc_noio_restore(noio_flag);\n+\tif (!elements)\n+\t\treturn -ENOMEM;\n+\n+\t/*\n+\t * Serialize against concurrent revalidation and concurrent storage\n+\t * element management operations so that we can see consistent zone\n+\t * condition changes.\n+\t */\n+\tmutex_lock(\u0026disk-\u003ezone_revalidate_mutex);\n+\n+\t/*\n+\t * Freeze and unfreeze the queue to flush any outstanding command.\n+\t * The caller is responsible for not queuing up more I/Os by higher\n+\t * level means.\n+\t */\n+\tmemflags = blk_mq_freeze_queue(disk-\u003equeue);\n+\tblk_mq_unfreeze_queue(disk-\u003equeue, memflags);\n+\n+\t/* Initiate the storage element removal and wait for its completion. */\n+\tret = disk-\u003efops-\u003ese_ops-\u003eremove_element(disk, element_id);\n+\tif (!ret)\n+\t\tret = disk_wait_for_se_mgmt_completion(disk, elements,\n+\t\t\t\t\t\t element_id);\n+\n+\t/*\n+\t * Always revalidate zones, even in the case of an error as we do not\n+\t * know what condition the drive zones are in.\n+\t */\n+\terr = disk_revalidate_zones(disk);\n+\tif (err \u0026\u0026 !ret)\n+\t\tret = err;\n+\n+\tmutex_unlock(\u0026disk-\u003ezone_revalidate_mutex);\n+\n+\tkfree(elements);\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(bdev_remove_storage_element);\n+\n+static int blkdev_flush_and_truncate(struct block_device *bdev,\n+\t\t\t\t blk_mode_t mode)\n+{\n+\tint ret;\n+\n+\tinode_lock(bdev-\u003ebd_mapping-\u003ehost);\n+\tfilemap_invalidate_lock(bdev-\u003ebd_mapping);\n+\tret = blkdev_issue_flush(bdev-\u003ebd_disk-\u003epart0);\n+\tif (!ret)\n+\t\tret = truncate_bdev_range(bdev, mode, 0,\n+\t\t\t\t\t bdev_nr_bytes(bdev) - 1);\n+\tfilemap_invalidate_unlock(bdev-\u003ebd_mapping);\n+\tinode_unlock(bdev-\u003ebd_mapping-\u003ehost);\n+\n+\treturn ret;\n+}\n+\n+static int blkdev_remove_storage_element_ioctl(struct block_device *bdev,\n+\t\t\t\t\tblk_mode_t mode, void __user *argp)\n+{\n+\tunsigned int element_id;\n+\tint ret;\n+\n+\tif (!(mode \u0026 BLK_OPEN_WRITE))\n+\t\treturn -EBADF;\n+\tif (bdev_read_only(bdev))\n+\t\treturn -EPERM;\n+\n+\tif (get_user(element_id, (unsigned int __user *)argp))\n+\t\treturn -EFAULT;\n+\n+\t/*\n+\t * Flush the device volatile write cache and invalidate all cached data\n+\t * so that reads do not return old data for zones that went offline.\n+\t * Since a user can only write to zoned devices using direct IOs, we\n+\t * should never have any dirty page invalidated and so no data loss as\n+\t * long as the device cache flush succeeds, that is, as long as the\n+\t * flush does not use degraded storage elements.\n+\t */\n+\tret = blkdev_flush_and_truncate(bdev, mode);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn bdev_remove_storage_element(bdev, element_id);\n+}\n+\n+/**\n+ * bdev_restore_storage_elements - Restore all depopulated storage elements of\n+ *\t\t\t\t a zoned block device\n+ * @bdev:\tblock device to operate on\n+ *\n+ * Restore all storage elements of @bdev that have been depopulated. Not all\n+ * elements may be restored by this operation.\n+ *\n+ * Returns 0 on success and a negative error code on failure.\n+ */\n+int bdev_restore_storage_elements(struct block_device *bdev)\n+{\n+\tstruct gendisk *disk = bdev-\u003ebd_disk;\n+\tunsigned int memflags, noio_flag;\n+\tstruct blk_se *elements;\n+\tint ret, err;\n+\n+\tif (!blk_queue_is_zoned(disk-\u003equeue) || !disk-\u003enr_storage_elements)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tnoio_flag = memalloc_noio_save();\n+\telements = kzalloc_objs(struct blk_se, disk-\u003enr_storage_elements);\n+\tmemalloc_noio_restore(noio_flag);\n+\tif (!elements)\n+\t\treturn -ENOMEM;\n+\n+\t/*\n+\t * Serialize against concurrent revalidation and concurrent storage\n+\t * element management operations so that we can see consistent zone\n+\t * condition changes.\n+\t */\n+\tmutex_lock(\u0026disk-\u003ezone_revalidate_mutex);\n+\n+\t/*\n+\t * Freeze and unfreeze the queue to flush any outstanding commands.\n+\t * The caller is responsible for not queuing up more I/O by higher\n+\t * level means.\n+\t */\n+\tmemflags = blk_mq_freeze_queue(disk-\u003equeue);\n+\tblk_mq_unfreeze_queue(disk-\u003equeue, memflags);\n+\n+\t/*\n+\t * Initiate the storage elements restoration and wait for its\n+\t * completion.\n+\t */\n+\tret = disk-\u003efops-\u003ese_ops-\u003erestore_elements(disk);\n+\tif (!ret)\n+\t\tret = disk_wait_for_se_mgmt_completion(disk, elements, 0);\n+\n+\t/*\n+\t * Always revalidate zones, even in the case of an error as we do not\n+\t * know what condition the drive zones are in.\n+\t */\n+\terr = disk_revalidate_zones(disk);\n+\tif (err \u0026\u0026 !ret)\n+\t\tret = err;\n+\n+\tmutex_unlock(\u0026disk-\u003ezone_revalidate_mutex);\n+\n+\tkfree(elements);\n+\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(bdev_restore_storage_elements);\n+\n+static int blkdev_restore_storage_elements_ioctl(struct block_device *bdev,\n+\t\t\t\t\t\t blk_mode_t mode)\n+{\n+\tint ret;\n+\n+\tif (!(mode \u0026 BLK_OPEN_WRITE))\n+\t\treturn -EBADF;\n+\tif (bdev_read_only(bdev))\n+\t\treturn -EPERM;\n+\n+\t/*\n+\t * Storage element restoration is a destructive operation that will\n+\t * reset all zones. So fflush the device volatile write cache and\n+\t * invalidate all cached data that we may have.\n+\t */\n+\tret = blkdev_flush_and_truncate(bdev, mode);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn bdev_restore_storage_elements(bdev);\n+}\n+\n+int disk_init_storage_element_mgmt(struct gendisk *disk)\n+{\n+\tint ret;\n+\n+\t/*\n+\t * Storage element operations are for zoned disks only and\n+\t * optional...\n+\t */\n+\tif (!blk_queue_is_zoned(disk-\u003equeue) || !disk-\u003efops-\u003ese_ops)\n+\t\treturn 0;\n+\n+\t/* ...but if supported, all operations must be defined. */\n+\tif (!disk-\u003efops-\u003ese_ops-\u003ereport_elements ||\n+\t !disk-\u003efops-\u003ese_ops-\u003eremove_element ||\n+\t !disk-\u003efops-\u003ese_ops-\u003erestore_elements) {\n+\t\tpr_err(\"%s: invalid storage element operations\\n\",\n+\t\t disk-\u003edisk_name);\n+\t\treturn -EINVAL;\n+\t}\n+\n+\t/*\n+\t * Get the number of storage elements of the disk. If this fails,\n+\t * do not report an error as the failure may be because the zoned disk\n+\t * does not support storage element management.\n+\t */\n+\tret = disk-\u003efops-\u003ese_ops-\u003ereport_elements(disk, NULL,\n+\t\t\t\t\t\t \u0026disk-\u003enr_storage_elements);\n+\tif (ret)\n+\t\tdisk-\u003enr_storage_elements = 0;\n+\n+\treturn 0;\n+}\n+\n+int blkdev_zone_storage_elements_ioctl(struct block_device *bdev,\n+\t\t\t\t blk_mode_t mode, unsigned int cmd,\n+\t\t\t\t void __user *argp)\n+{\n+\tstruct gendisk *disk = bdev-\u003ebd_disk;\n+\n+\tif (!disk-\u003enr_storage_elements)\n+\t\treturn -ENOTTY;\n+\n+\tswitch (cmd) {\n+\tcase BLKGETNRSTORELEMS:\n+\t\treturn blkdev_get_nr_storage_elements_ioctl(bdev, argp);\n+\tcase BLKREPORTSTORELEMS:\n+\t\treturn blkdev_report_storage_elements_ioctl(bdev, argp);\n+\tcase BLKREMOVESTORELEM:\n+\t\treturn blkdev_remove_storage_element_ioctl(bdev, mode, argp);\n+\tcase BLKRESTORESTORELEMS:\n+\t\treturn blkdev_restore_storage_elements_ioctl(bdev, mode);\n+\tdefault:\n+\t\tbreak;\n+\t}\n+\n+\treturn -ENOTTY;\n+}\ndiff --git a/block/blk.h b/block/blk.h\nindex 2cc03aa54c532..bbf523c9579cf 100644\n--- a/block/blk.h\n+++ b/block/blk.h\n@@ -527,6 +527,7 @@ static inline void ioc_clear_queue(struct request_queue *q)\n #endif /* CONFIG_BLK_ICQ */\n \n #ifdef CONFIG_BLK_DEV_ZONED\n+int disk_init_storage_element_mgmt(struct gendisk *disk);\n void disk_init_zone_resources(struct gendisk *disk);\n void disk_release_zone_resources(struct gendisk *disk);\n static inline bool bio_zone_write_plugging(struct bio *bio)\n@@ -578,7 +579,14 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd,\n int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode,\n \t\tunsigned int cmd, unsigned long arg);\n bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector);\n+int blkdev_zone_storage_elements_ioctl(struct block_device *bdev,\n+\t\t\t\t blk_mode_t mode, unsigned int cmd,\n+\t\t\t\t void __user *argp);\n #else /* CONFIG_BLK_DEV_ZONED */\n+static inline int disk_init_storage_element_mgmt(struct gendisk *disk)\n+{\n+\treturn 0;\n+}\n static inline void disk_init_zone_resources(struct gendisk *disk)\n {\n }\n@@ -625,6 +633,12 @@ static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,\n {\n \treturn false;\n }\n+static inline int blkdev_zone_storage_elements_ioctl(struct block_device *bdev,\n+\t\t\t\t\tblk_mode_t mode, unsigned int cmd,\n+\t\t\t\t\tvoid __user *argp)\n+{\n+\treturn -ENOTTY;\n+}\n #endif /* CONFIG_BLK_DEV_ZONED */\n \n struct block_device *bdev_alloc(struct gendisk *disk, u8 partno);\ndiff --git a/block/genhd.c b/block/genhd.c\nindex 10ca8b4d6eea7..e2cef791ac91a 100644\n--- a/block/genhd.c\n+++ b/block/genhd.c\n@@ -435,6 +435,10 @@ static int __add_disk(struct device *parent, struct gendisk *disk,\n \tif (WARN_ON_ONCE(bdev_nr_sectors(disk-\u003epart0) \u003e BLK_DEV_MAX_SECTORS))\n \t\treturn -EINVAL;\n \n+\tret = disk_init_storage_element_mgmt(disk);\n+\tif (ret)\n+\t\treturn ret;\n+\n \tif (queue_is_mq(disk-\u003equeue)) {\n \t\t/*\n \t\t * -\u003esubmit_bio and -\u003epoll_bio are bypassed for blk-mq drivers.\ndiff --git a/block/ioctl.c b/block/ioctl.c\nindex 64b4e6c0f6965..ccf806c2e37d1 100644\n--- a/block/ioctl.c\n+++ b/block/ioctl.c\n@@ -679,6 +679,11 @@ static int blkdev_common_ioctl(struct block_device *bdev, blk_mode_t mode,\n \t\treturn put_uint(argp, bdev_zone_sectors(bdev));\n \tcase BLKGETNRZONES:\n \t\treturn put_uint(argp, bdev_nr_zones(bdev));\n+\tcase BLKGETNRSTORELEMS:\n+\tcase BLKREPORTSTORELEMS:\n+\tcase BLKREMOVESTORELEM:\n+\tcase BLKRESTORESTORELEMS:\n+\t\treturn blkdev_zone_storage_elements_ioctl(bdev, mode, cmd, argp);\n \tcase BLKROGET:\n \t\treturn put_int(argp, bdev_read_only(bdev) != 0);\n \tcase BLKSSZGET: /* get block device logical block size */\ndiff --git a/drivers/block/zloop.c b/drivers/block/zloop.c\nindex 394dc2408ed73..26d8335508a16 100644\n--- a/drivers/block/zloop.c\n+++ b/drivers/block/zloop.c\n@@ -37,6 +37,8 @@ enum {\n \tZLOOP_OPT_ORDERED_ZONE_APPEND\t= (1 \u003c\u003c 10),\n \tZLOOP_OPT_DISCARD_WRITE_CACHE\t= (1 \u003c\u003c 11),\n \tZLOOP_OPT_MAX_OPEN_ZONES\t= (1 \u003c\u003c 12),\n+\tZLOOP_OPT_STOR_ELEMENTS\t\t= (1 \u003c\u003c 13),\n+\tZLOOP_OPT_ELEMENT_ID\t\t= (1 \u003c\u003c 14),\n };\n \n static const match_table_t zloop_opt_tokens = {\n@@ -51,11 +53,23 @@ static const match_table_t zloop_opt_tokens = {\n \t{ ZLOOP_OPT_BUFFERED_IO,\t\"buffered_io\"\t\t},\n \t{ ZLOOP_OPT_ZONE_APPEND,\t\"zone_append=%u\"\t},\n \t{ ZLOOP_OPT_ORDERED_ZONE_APPEND, \"ordered_zone_append\"\t},\n-\t{ ZLOOP_OPT_DISCARD_WRITE_CACHE, \"discard_write_cache\" },\n+\t{ ZLOOP_OPT_DISCARD_WRITE_CACHE, \"discard_write_cache\"\t},\n \t{ ZLOOP_OPT_MAX_OPEN_ZONES,\t\"max_open_zones=%u\"\t},\n+\t{ ZLOOP_OPT_STOR_ELEMENTS,\t\"stor_elements=%u\"\t},\n+\t{ ZLOOP_OPT_ELEMENT_ID,\t\t\"element_id=%u\"\t\t},\n \t{ ZLOOP_OPT_ERR,\t\tNULL\t\t\t}\n };\n \n+/* Storage elements emulation types. */\n+enum zloop_stor_elements {\n+\t/* No emulation. */\n+\tZLOOP_STOR_ELEMENTS_NONE,\n+\t/* Emulate read+write storage elements. */\n+\tZLOOP_STOR_ELEMENTS_RDWR,\n+\t/* Emulate pairs of associated read and write storage elements. */\n+\tZLOOP_STOR_ELEMENTS_PAIRS,\n+};\n+\n /* Default values for the \"add\" operation. */\n #define ZLOOP_DEF_ID\t\t\t-1\n #define ZLOOP_DEF_ZONE_SIZE\t\t((256ULL * SZ_1M) \u003e\u003e SECTOR_SHIFT)\n@@ -68,6 +82,8 @@ static const match_table_t zloop_opt_tokens = {\n #define ZLOOP_DEF_BUFFERED_IO\t\tfalse\n #define ZLOOP_DEF_ZONE_APPEND\t\ttrue\n #define ZLOOP_DEF_ORDERED_ZONE_APPEND\tfalse\n+#define ZLOOP_DEF_STOR_ELEMENTS\t\tZLOOP_STOR_ELEMENTS_NONE\n+#define ZLOOP_DEF_ELEMENT_ID\t\t0\n \n /* Arbitrary limit on the zone size (16GB). */\n #define ZLOOP_MAX_ZONE_SIZE_MB\t\t16384\n@@ -87,6 +103,8 @@ struct zloop_options {\n \tbool\t\t\tzone_append;\n \tbool\t\t\tordered_zone_append;\n \tbool\t\t\tdiscard_write_cache;\n+\tenum zloop_stor_elements stor_elements;\n+\tunsigned int\t\telement_id;\n };\n \n /*\n@@ -117,6 +135,8 @@ struct zloop_zone {\n \tenum blk_zone_cond\tcond;\n \tsector_t\t\tstart;\n \tsector_t\t\twp;\n+\tunsigned int\t\twr_se_id;\n+\tunsigned int\t\trd_se_id;\n \n \tgfp_t\t\t\told_gfp_mask;\n };\n@@ -133,6 +153,7 @@ struct zloop_device {\n \tbool\t\t\tzone_append;\n \tbool\t\t\tordered_zone_append;\n \tbool\t\t\tdiscard_write_cache;\n+\tenum zloop_stor_elements stor_elements;\n \n \tconst char\t\t*base_dir;\n \tstruct file\t\t*data_dir;\n@@ -150,6 +171,17 @@ struct zloop_device {\n \tstruct list_head\topen_zones_lru_list;\n \tunsigned int\t\tnr_open_zones;\n \n+\t/* For storage elements emulation. */\n+\tstruct mutex\t\tstor_elements_lock;\n+\tunsigned int\t\tnr_elements;\n+\tunsigned int\t\tmax_nr_removed_elements;\n+\tunsigned int\t\tnr_removed_elements;\n+\tstruct delayed_work\tremove_element_work;\n+\tunsigned int\t\tremove_element_id;\n+\tstruct delayed_work\trestore_elements_work;\n+\tbool\t\t\trestore_in_progress;\n+\tstruct blk_se\t\t*elements;\n+\n \tstruct zloop_zone\tzones[] __counted_by(nr_zones);\n };\n \n@@ -289,22 +321,43 @@ static bool zloop_do_open_zone(struct zloop_device *zlo,\n \t}\n }\n \n-static void zloop_mark_full(struct zloop_device *zlo, struct zloop_zone *zone)\n+static void zloop_set_zone_cond(struct zloop_device *zlo,\n+\t\t\t\tstruct zloop_zone *zone,\n+\t\t\t\tenum blk_zone_cond cond)\n {\n \tlockdep_assert_held(\u0026zone-\u003ewp_lock);\n \n \tzloop_lru_remove_open_zone(zlo, zone);\n-\tzone-\u003econd = BLK_ZONE_COND_FULL;\n-\tzone-\u003ewp = ULLONG_MAX;\n+\tzone-\u003econd = cond;\n+\tif (cond == BLK_ZONE_COND_EMPTY)\n+\t\tzone-\u003ewp = zone-\u003estart;\n+\telse\n+\t\tzone-\u003ewp = ULLONG_MAX;\n }\n \n-static void zloop_mark_empty(struct zloop_device *zlo, struct zloop_zone *zone)\n+static inline void zloop_set_zone_full(struct zloop_device *zlo,\n+\t\t\t\t struct zloop_zone *zone)\n {\n-\tlockdep_assert_held(\u0026zone-\u003ewp_lock);\n+\tzloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_FULL);\n+}\n \n-\tzloop_lru_remove_open_zone(zlo, zone);\n-\tzone-\u003econd = BLK_ZONE_COND_EMPTY;\n-\tzone-\u003ewp = zone-\u003estart;\n+static inline void zloop_set_zone_empty(struct zloop_device *zlo,\n+\t\t\t\t\tstruct zloop_zone *zone)\n+{\n+\tzloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_EMPTY);\n+}\n+\n+static bool zloop_zone_is_offline_or_readonly(struct zloop_device *zlo,\n+\t\t\t\t\t struct zloop_zone *zone)\n+{\n+\tbool ret;\n+\n+\tspin_lock(\u0026zone-\u003ewp_lock);\n+\tret = zone-\u003econd == BLK_ZONE_COND_OFFLINE ||\n+\t\tzone-\u003econd == BLK_ZONE_COND_READONLY;\n+\tspin_unlock(\u0026zone-\u003ewp_lock);\n+\n+\treturn ret;\n }\n \n static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)\n@@ -316,6 +369,13 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)\n \n \tlockdep_assert_held(\u0026zone-\u003elock);\n \n+\t/*\n+\t * If the zone was already changed to offline or read-only, we have\n+\t * nothing to do.\n+\t */\n+\tif (zloop_zone_is_offline_or_readonly(zlo, zone))\n+\t\treturn 0;\n+\n \tret = vfs_getattr(\u0026zone-\u003efile-\u003ef_path, \u0026stat, STATX_SIZE, 0);\n \tif (ret \u003c 0) {\n \t\tpr_err(\"Failed to get zone %u file stat (err=%d)\\n\",\n@@ -339,9 +399,9 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)\n \n \tspin_lock(\u0026zone-\u003ewp_lock);\n \tif (!file_sectors) {\n-\t\tzloop_mark_empty(zlo, zone);\n+\t\tzloop_set_zone_empty(zlo, zone);\n \t} else if (file_sectors == zlo-\u003ezone_capacity) {\n-\t\tzloop_mark_full(zlo, zone);\n+\t\tzloop_set_zone_full(zlo, zone);\n \t} else {\n \t\tif (zone-\u003econd != BLK_ZONE_COND_IMP_OPEN \u0026\u0026\n \t\t zone-\u003econd != BLK_ZONE_COND_EXP_OPEN)\n@@ -363,6 +423,11 @@ static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no)\n \n \tmutex_lock(\u0026zone-\u003elock);\n \n+\tif (zloop_zone_is_offline_or_readonly(zlo, zone)) {\n+\t\tret = -EIO;\n+\t\tgoto unlock;\n+\t}\n+\n \tif (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags)) {\n \t\tret = zloop_update_seq_zone(zlo, zone_no);\n \t\tif (ret)\n@@ -388,6 +453,11 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no)\n \n \tmutex_lock(\u0026zone-\u003elock);\n \n+\tif (zloop_zone_is_offline_or_readonly(zlo, zone)) {\n+\t\tret = -EIO;\n+\t\tgoto unlock;\n+\t}\n+\n \tif (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags)) {\n \t\tret = zloop_update_seq_zone(zlo, zone_no);\n \t\tif (ret)\n@@ -420,7 +490,22 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no)\n \treturn ret;\n }\n \n-static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no)\n+static int zloop_do_reset_zone(struct zloop_device *zlo,\n+\t\t\t struct zloop_zone *zone)\n+{\n+\tif (vfs_truncate(\u0026zone-\u003efile-\u003ef_path, 0))\n+\t\treturn -EIO;\n+\n+\tspin_lock(\u0026zone-\u003ewp_lock);\n+\tzloop_set_zone_empty(zlo, zone);\n+\tclear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags);\n+\tspin_unlock(\u0026zone-\u003ewp_lock);\n+\n+\treturn 0;\n+}\n+\n+static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no,\n+\t\t\t bool all_zones)\n {\n \tstruct zloop_zone *zone = \u0026zlo-\u003ezones[zone_no];\n \tint ret = 0;\n@@ -430,20 +515,19 @@ static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no)\n \n \tmutex_lock(\u0026zone-\u003elock);\n \n+\tif (zloop_zone_is_offline_or_readonly(zlo, zone)) {\n+\t\tif (!all_zones)\n+\t\t\tret = -EIO;\n+\t\tgoto unlock;\n+\t}\n+\n \tif (!test_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags) \u0026\u0026\n \t zone-\u003econd == BLK_ZONE_COND_EMPTY)\n \t\tgoto unlock;\n \n-\tif (vfs_truncate(\u0026zone-\u003efile-\u003ef_path, 0)) {\n+\tret = zloop_do_reset_zone(zlo, zone);\n+\tif (ret)\n \t\tset_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags);\n-\t\tret = -EIO;\n-\t\tgoto unlock;\n-\t}\n-\n-\tspin_lock(\u0026zone-\u003ewp_lock);\n-\tzloop_mark_empty(zlo, zone);\n-\tclear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags);\n-\tspin_unlock(\u0026zone-\u003ewp_lock);\n \n unlock:\n \tmutex_unlock(\u0026zone-\u003elock);\n@@ -457,7 +541,7 @@ static int zloop_reset_all_zones(struct zloop_device *zlo)\n \tint ret;\n \n \tfor (i = zlo-\u003enr_conv_zones; i \u003c zlo-\u003enr_zones; i++) {\n-\t\tret = zloop_reset_zone(zlo, i);\n+\t\tret = zloop_reset_zone(zlo, i, true);\n \t\tif (ret)\n \t\t\treturn ret;\n \t}\n@@ -475,6 +559,11 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no)\n \n \tmutex_lock(\u0026zone-\u003elock);\n \n+\tif (zloop_zone_is_offline_or_readonly(zlo, zone)) {\n+\t\tret = -EIO;\n+\t\tgoto unlock;\n+\t}\n+\n \tif (!test_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags) \u0026\u0026\n \t zone-\u003econd == BLK_ZONE_COND_FULL)\n \t\tgoto unlock;\n@@ -487,7 +576,7 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no)\n \t}\n \n \tspin_lock(\u0026zone-\u003ewp_lock);\n-\tzloop_mark_full(zlo, zone);\n+\tzloop_set_zone_full(zlo, zone);\n \tclear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags);\n \tspin_unlock(\u0026zone-\u003ewp_lock);\n \n@@ -624,7 +713,7 @@ static int zloop_seq_write_prep(struct zloop_cmd *cmd)\n \tif (!is_append || !zlo-\u003eordered_zone_append) {\n \t\tzone-\u003ewp += nr_sectors;\n \t\tif (zone-\u003ewp == zone_end)\n-\t\t\tzloop_mark_full(zlo, zone);\n+\t\t\tzloop_set_zone_full(zlo, zone);\n \t}\n out_unlock:\n \tspin_unlock(\u0026zone-\u003ewp_lock);\n@@ -766,7 +855,7 @@ static void zloop_handle_cmd(struct zloop_cmd *cmd)\n \t\tcmd-\u003eret = zloop_flush(zlo);\n \t\tbreak;\n \tcase REQ_OP_ZONE_RESET:\n-\t\tcmd-\u003eret = zloop_reset_zone(zlo, rq_zone_no(rq));\n+\t\tcmd-\u003eret = zloop_reset_zone(zlo, rq_zone_no(rq), false);\n \t\tbreak;\n \tcase REQ_OP_ZONE_RESET_ALL:\n \t\tcmd-\u003eret = zloop_reset_all_zones(zlo);\n@@ -875,30 +964,72 @@ static void zloop_complete_rq(struct request *rq)\n \tblk_mq_end_request(rq, sts);\n }\n \n-static bool zloop_set_zone_append_sector(struct request *rq)\n+static bool zloop_set_zone_append_sector(struct zloop_device *zlo,\n+\t\t\t\t\t struct zloop_zone *zone,\n+\t\t\t\t\t struct request *rq)\n {\n-\tstruct zloop_device *zlo = rq-\u003eq-\u003equeuedata;\n-\tunsigned int zone_no = rq_zone_no(rq);\n-\tstruct zloop_zone *zone = \u0026zlo-\u003ezones[zone_no];\n \tsector_t zone_end = zone-\u003estart + zlo-\u003ezone_capacity;\n \tsector_t nr_sectors = blk_rq_sectors(rq);\n \n-\tspin_lock(\u0026zone-\u003ewp_lock);\n-\n \tif (zone-\u003econd == BLK_ZONE_COND_FULL ||\n-\t zone-\u003ewp + nr_sectors \u003e zone_end) {\n-\t\tspin_unlock(\u0026zone-\u003ewp_lock);\n+\t zone-\u003ewp + nr_sectors \u003e zone_end)\n \t\treturn false;\n-\t}\n \n \trq-\u003e__sector = zone-\u003ewp;\n \tzone-\u003ewp += blk_rq_sectors(rq);\n \tif (zone-\u003ewp \u003e= zone_end)\n-\t\tzloop_mark_full(zlo, zone);\n+\t\tzloop_set_zone_full(zlo, zone);\n+\n+\treturn true;\n+}\n+\n+\n+static bool zloop_prep_rq(struct zloop_device *zlo, struct request *rq)\n+{\n+\tstruct zloop_zone *zone = \u0026zlo-\u003ezones[rq_zone_no(rq)];\n+\tbool is_write = op_is_write(req_op(rq));\n+\tbool ret = true;\n \n+\tspin_lock(\u0026zone-\u003ewp_lock);\n+\n+\tif (zlo-\u003enr_elements) {\n+\t\tstruct blk_se *se;\n+\n+\t\t/*\n+\t\t * Check the zone condition and the health state of the storage\n+\t\t * element serving the zone: do not allow any operation to\n+\t\t * offline zones or if the storage element serving the zone is\n+\t\t * being removed, and allow only reads to read-only zones.\n+\t\t */\n+\t\tif (is_write)\n+\t\t\tse = \u0026zlo-\u003eelements[zone-\u003ewr_se_id - 1];\n+\t\telse\n+\t\t\tse = \u0026zlo-\u003eelements[zone-\u003erd_se_id - 1];\n+\t\tif (READ_ONCE(se-\u003estatus) == BLK_SE_STS_DEGRADED ||\n+\t\t READ_ONCE(se-\u003estatus) == BLK_SE_STS_REMOVE_IN_PROGRESS) {\n+\t\t\tret = false;\n+\t\t\tgoto unlock;\n+\t\t}\n+\n+\t\tif (zone-\u003econd == BLK_ZONE_COND_OFFLINE ||\n+\t\t (zone-\u003econd == BLK_ZONE_COND_READONLY \u0026\u0026 is_write)) {\n+\t\t\tret = false;\n+\t\t\tgoto unlock;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * If we need to strongly order zone append operations, set the request\n+\t * sector to the zone write pointer location now instead of when the\n+\t * command work runs.\n+\t */\n+\tif (zlo-\u003eordered_zone_append \u0026\u0026 req_op(rq) == REQ_OP_ZONE_APPEND)\n+\t\tret = zloop_set_zone_append_sector(zlo, zone, rq);\n+\n+unlock:\n \tspin_unlock(\u0026zone-\u003ewp_lock);\n \n-\treturn true;\n+\treturn ret;\n }\n \n static blk_status_t zloop_queue_rq(struct blk_mq_hw_ctx *hctx,\n@@ -914,13 +1045,21 @@ static blk_status_t zloop_queue_rq(struct blk_mq_hw_ctx *hctx,\n \t}\n \n \t/*\n-\t * If we need to strongly order zone append operations, set the request\n-\t * sector to the zone write pointer location now instead of when the\n-\t * command work runs.\n+\t * Do not allow commands while the restoration of storage elements is\n+\t * in progress.\n \t */\n-\tif (zlo-\u003eordered_zone_append \u0026\u0026 req_op(rq) == REQ_OP_ZONE_APPEND) {\n-\t\tif (!zloop_set_zone_append_sector(rq))\n+\tif (READ_ONCE(zlo-\u003erestore_in_progress))\n+\t\treturn BLK_STS_IOERR;\n+\n+\tswitch (req_op(rq)) {\n+\tcase REQ_OP_READ:\n+\tcase REQ_OP_WRITE:\n+\tcase REQ_OP_ZONE_APPEND:\n+\t\tif (!zloop_prep_rq(zlo, rq))\n \t\t\treturn BLK_STS_IOERR;\n+\t\tbreak;\n+\tdefault:\n+\t\tbreak;\n \t}\n \n \tblk_mq_start_request(rq);\n@@ -1002,11 +1141,317 @@ static int zloop_report_zones(struct gendisk *disk, sector_t sector,\n \treturn nr_zones;\n }\n \n+static int zloop_report_elements(struct gendisk *disk,\n+\t\t\t\t struct blk_se *elements,\n+\t\t\t\t unsigned int *nr_elements)\n+{\n+\tstruct zloop_device *zlo = disk-\u003eprivate_data;\n+\tstruct blk_se *se;\n+\tunsigned int i;\n+\n+\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_NONE)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tmutex_lock(\u0026zlo-\u003estor_elements_lock);\n+\n+\tif (!elements) {\n+\t\t*nr_elements = zlo-\u003enr_elements;\n+\t\tgoto unlock;\n+\t}\n+\n+\t*nr_elements = min(*nr_elements, zlo-\u003enr_elements);\n+\tfor (i = 0, se = zlo-\u003eelements; i \u003c *nr_elements; i++, se++) {\n+\t\tswitch (READ_ONCE(se-\u003estatus)) {\n+\t\tcase BLK_SE_STS_REMOVED:\n+\t\tcase BLK_SE_STS_RESTORE_ERROR:\n+\t\t\tse-\u003erestore_allowed = 1;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tse-\u003erestore_allowed = 0;\n+\t\t}\n+\t\tmemcpy(\u0026elements[i], se, sizeof(struct blk_se));\n+\t}\n+\n+unlock:\n+\tmutex_unlock(\u0026zlo-\u003estor_elements_lock);\n+\n+\treturn 0;\n+}\n+\n+static int zloop_degrade_element(struct zloop_device *zlo,\n+\t\t\t\t unsigned int element_id)\n+{\n+\tstruct blk_se *se;\n+\tint ret = 0;\n+\n+\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_NONE)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (!element_id || element_id \u003e zlo-\u003enr_elements)\n+\t\treturn -EINVAL;\n+\n+\tmutex_lock(\u0026zlo-\u003estor_elements_lock);\n+\n+\tse = \u0026zlo-\u003eelements[element_id - 1];\n+\tif (READ_ONCE(se-\u003estatus) == BLK_SE_STS_OK)\n+\t\tWRITE_ONCE(se-\u003estatus, BLK_SE_STS_DEGRADED);\n+\telse\n+\t\tret = -EINVAL;\n+\n+\tmutex_unlock(\u0026zlo-\u003estor_elements_lock);\n+\n+\treturn ret;\n+}\n+\n+static void zloop_remove_element_work(struct work_struct *work)\n+{\n+\tstruct zloop_device *zlo = container_of(work, struct zloop_device,\n+\t\t\t\t\t\tremove_element_work.work);\n+\tstruct blk_se *se, *paired_se = NULL;\n+\tenum blk_zone_cond cond;\n+\tstruct zloop_zone *zone;\n+\tunsigned int memflags, i;\n+\n+\tmutex_lock(\u0026zlo-\u003estor_elements_lock);\n+\n+\t/* Wait for all IOs to complete. */\n+\tmemflags = blk_mq_freeze_queue(zlo-\u003edisk-\u003equeue);\n+\tblk_mq_unfreeze_queue(zlo-\u003edisk-\u003equeue, memflags);\n+\n+\t/*\n+\t * If the element to remove is a read element, zones must go offline and\n+\t * the associated write element is also removed.\n+\t */\n+\tse = \u0026zlo-\u003eelements[zlo-\u003eremove_element_id - 1];\n+\tswitch (se-\u003etype) {\n+\tcase BLK_SE_TYPE_RDWR:\n+\t\tcond = BLK_ZONE_COND_OFFLINE;\n+\t\tbreak;\n+\tcase BLK_SE_TYPE_READ:\n+\t\tcond = BLK_ZONE_COND_OFFLINE;\n+\t\tpaired_se = \u0026zlo-\u003eelements[se-\u003epaired_id - 1];\n+\t\tbreak;\n+\tcase BLK_SE_TYPE_WRITE:\n+\t\tcond = BLK_ZONE_COND_READONLY;\n+\t\tbreak;\n+\tdefault:\n+\t\tWARN_ON_ONCE(1);\n+\t}\n+\n+\t/*\n+\t * Change the condition of the zones owned by the (pair of) elements\n+\t * being removed and mark the elements removed.\n+\t */\n+\tfor (i = 0, zone = zlo-\u003ezones; i \u003c zlo-\u003enr_zones; i++, zone++) {\n+\t\tif (zone-\u003ewr_se_id != se-\u003eid \u0026\u0026 zone-\u003erd_se_id != se-\u003eid)\n+\t\t\tcontinue;\n+\t\tmutex_lock(\u0026zone-\u003elock);\n+\t\tspin_lock(\u0026zone-\u003ewp_lock);\n+\t\tclear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags);\n+\t\tzloop_set_zone_cond(zlo, zone, cond);\n+\t\tspin_unlock(\u0026zone-\u003ewp_lock);\n+\t\tmutex_unlock(\u0026zone-\u003elock);\n+\t}\n+\n+\tWRITE_ONCE(se-\u003estatus, BLK_SE_STS_REMOVED);\n+\tzlo-\u003enr_removed_elements++;\n+\tif (paired_se \u0026\u0026 READ_ONCE(paired_se-\u003estatus) != BLK_SE_STS_REMOVED) {\n+\t\tWRITE_ONCE(paired_se-\u003estatus, BLK_SE_STS_REMOVED);\n+\t\tzlo-\u003enr_removed_elements++;\n+\t}\n+\n+\tzlo-\u003eremove_element_id = 0;\n+\n+\tmutex_unlock(\u0026zlo-\u003estor_elements_lock);\n+}\n+\n+static int zloop_remove_element(struct gendisk *disk, unsigned int element_id)\n+{\n+\tstruct zloop_device *zlo = disk-\u003eprivate_data;\n+\tstruct blk_se *se, *paired_se = NULL;\n+\tunsigned int nr_remove = 1;\n+\tint ret = 0;\n+\n+\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_NONE)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tif (element_id \u003e zlo-\u003enr_elements)\n+\t\treturn -EINVAL;\n+\n+\tmutex_lock(\u0026zlo-\u003estor_elements_lock);\n+\n+\tif (zlo-\u003eremove_element_id || READ_ONCE(zlo-\u003erestore_in_progress)) {\n+\t\tret = -EBUSY;\n+\t\tgoto unlock;\n+\t}\n+\n+\t/*\n+\t * Get the element to remove. If it is already removed, we have nothing\n+\t * to do.\n+\t */\n+\tse = \u0026zlo-\u003eelements[element_id - 1];\n+\tif (READ_ONCE(se-\u003estatus) == BLK_SE_STS_REMOVED)\n+\t\tgoto unlock;\n+\n+\t/*\n+\t * If the element to remove is a read element, the associated write\n+\t * element must also be removed.\n+\t */\n+\tif (se-\u003etype == BLK_SE_TYPE_READ) {\n+\t\tpaired_se = \u0026zlo-\u003eelements[se-\u003epaired_id - 1];\n+\t\tif (READ_ONCE(paired_se-\u003estatus) != BLK_SE_STS_REMOVED)\n+\t\t\tnr_remove = 2;\n+\t}\n+\n+\tif (zlo-\u003enr_removed_elements + nr_remove \u003e\n+\t zlo-\u003emax_nr_removed_elements) {\n+\t\tret = -EBUSY;\n+\t\tgoto unlock;\n+\t}\n+\n+\t/*\n+\t * Schedule the element removal with a delay, to emulate the (generally\n+\t * short) time it takes for a real device to depopulate a head and\n+\t * modify the zones.\n+\t */\n+\tzlo-\u003eremove_element_id = element_id;\n+\tWRITE_ONCE(se-\u003estatus, BLK_SE_STS_REMOVE_IN_PROGRESS);\n+\tif (paired_se \u0026\u0026 READ_ONCE(paired_se-\u003estatus) != BLK_SE_STS_REMOVED)\n+\t\tWRITE_ONCE(paired_se-\u003estatus, BLK_SE_STS_REMOVE_IN_PROGRESS);\n+\n+\tschedule_delayed_work(\u0026zlo-\u003eremove_element_work,\n+\t\t\t msecs_to_jiffies(2000));\n+\n+unlock:\n+\tmutex_unlock(\u0026zlo-\u003estor_elements_lock);\n+\n+\treturn ret;\n+}\n+\n+static void zloop_restore_conv_zone(struct zloop_device *zlo,\n+\t\t\t\t struct zloop_zone *zone)\n+{\n+\tmutex_lock(\u0026zone-\u003elock);\n+\tspin_lock(\u0026zone-\u003ewp_lock);\n+\tif (zone-\u003econd == BLK_ZONE_COND_OFFLINE ||\n+\t zone-\u003econd == BLK_ZONE_COND_READONLY)\n+\t\tzloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_NOT_WP);\n+\tspin_unlock(\u0026zone-\u003ewp_lock);\n+\tmutex_unlock(\u0026zone-\u003elock);\n+}\n+\n+static int zloop_restore_seq_zone(struct zloop_device *zlo,\n+\t\t\t\t struct zloop_zone *zone)\n+{\n+\tint ret;\n+\n+\tmutex_lock(\u0026zone-\u003elock);\n+\tret = zloop_do_reset_zone(zlo, zone);\n+\tif (ret)\n+\t\tset_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags);\n+\tmutex_unlock(\u0026zone-\u003elock);\n+\n+\treturn ret;\n+}\n+\n+static void zloop_restore_elements_work(struct work_struct *work)\n+{\n+\tstruct zloop_device *zlo = container_of(work, struct zloop_device,\n+\t\t\t\t\t\trestore_elements_work.work);\n+\tstruct zloop_zone *zone;\n+\tstruct blk_se *se;\n+\tunsigned int memflags, i;\n+\tenum blk_se_status sts;\n+\tint ret = 0;\n+\n+\tmutex_lock(\u0026zlo-\u003estor_elements_lock);\n+\n+\t/* Wait for all IOs to complete. */\n+\tmemflags = blk_mq_freeze_queue(zlo-\u003edisk-\u003equeue);\n+\tblk_mq_unfreeze_queue(zlo-\u003edisk-\u003equeue, memflags);\n+\n+\t/* Reset all zones. */\n+\tfor (i = 0, zone = zlo-\u003ezones; i \u003c zlo-\u003enr_zones; i++, zone++) {\n+\t\tif (test_bit(ZLOOP_ZONE_CONV, \u0026zone-\u003eflags)) {\n+\t\t\tzloop_restore_conv_zone(zlo, zone);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tret = zloop_restore_seq_zone(zlo, zone);\n+\t\tif (ret)\n+\t\t\tbreak;\n+\t}\n+\n+\t/*\n+\t * If we had an error, maintain all elements as removed instead of\n+\t * using the restore error status.\n+\t */\n+\tif (ret)\n+\t\tsts = BLK_SE_STS_REMOVED;\n+\telse\n+\t\tsts = BLK_SE_STS_OK;\n+\n+\t/* Restore all removed elements. */\n+\tfor (i = 0, se = zlo-\u003eelements; i \u003c zlo-\u003enr_elements; i++, se++) {\n+\t\tif (READ_ONCE(se-\u003estatus) == BLK_SE_STS_RESTORE_IN_PROGRESS)\n+\t\t\tWRITE_ONCE(se-\u003estatus, sts);\n+\t}\n+\n+\tif (!ret)\n+\t\tzlo-\u003enr_removed_elements = 0;\n+\n+\tWRITE_ONCE(zlo-\u003erestore_in_progress, false);\n+\tmutex_unlock(\u0026zlo-\u003estor_elements_lock);\n+}\n+\n+static int zloop_restore_elements(struct gendisk *disk)\n+{\n+\tstruct zloop_device *zlo = disk-\u003eprivate_data;\n+\tstruct blk_se *se;\n+\tunsigned int i;\n+\tint ret = 0;\n+\n+\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_NONE)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tmutex_lock(\u0026zlo-\u003estor_elements_lock);\n+\n+\tif (zlo-\u003eremove_element_id || READ_ONCE(zlo-\u003erestore_in_progress)) {\n+\t\tret = -EBUSY;\n+\t\tgoto unlock;\n+\t}\n+\n+\t/* If we have no removed elements, we have nothing to do. */\n+\tif (!zlo-\u003enr_removed_elements)\n+\t\tgoto unlock;\n+\n+\t/*\n+\t * Schedule the elements restoration with a delay, to emulate the time\n+\t * it takes for a real device to restore all removed heads and reset\n+\t * all zones.\n+\t */\n+\tWRITE_ONCE(zlo-\u003erestore_in_progress, true);\n+\tfor (i = 0, se = zlo-\u003eelements; i \u003c zlo-\u003enr_elements; i++, se++) {\n+\t\tif (READ_ONCE(se-\u003estatus) == BLK_SE_STS_REMOVED)\n+\t\t\tWRITE_ONCE(se-\u003estatus, BLK_SE_STS_RESTORE_IN_PROGRESS);\n+\t}\n+\n+\tschedule_delayed_work(\u0026zlo-\u003erestore_elements_work,\n+\t\t\t msecs_to_jiffies(5000));\n+\n+unlock:\n+\tmutex_unlock(\u0026zlo-\u003estor_elements_lock);\n+\n+\treturn ret;\n+}\n+\n static void zloop_free_disk(struct gendisk *disk)\n {\n \tstruct zloop_device *zlo = disk-\u003eprivate_data;\n \tunsigned int i;\n \n+\tcancel_delayed_work_sync(\u0026zlo-\u003eremove_element_work);\n+\tcancel_delayed_work_sync(\u0026zlo-\u003erestore_elements_work);\n+\n \tblk_mq_free_tag_set(\u0026zlo-\u003etag_set);\n \n \tfor (i = 0; i \u003c zlo-\u003enr_zones; i++) {\n@@ -1019,15 +1464,24 @@ static void zloop_free_disk(struct gendisk *disk)\n \n \tfput(zlo-\u003edata_dir);\n \tdestroy_workqueue(zlo-\u003eworkqueue);\n+\tkfree(zlo-\u003eelements);\n \tkfree(zlo-\u003ebase_dir);\n \tkvfree(zlo);\n }\n \n+\n+static const struct blk_se_ops zloop_se_ops = {\n+\t.report_elements\t= zloop_report_elements,\n+\t.remove_element\t\t= zloop_remove_element,\n+\t.restore_elements\t= zloop_restore_elements,\n+};\n+\n static const struct block_device_operations zloop_fops = {\n \t.owner\t\t\t= THIS_MODULE,\n \t.open\t\t\t= zloop_open,\n \t.report_zones\t\t= zloop_report_zones,\n \t.free_disk\t\t= zloop_free_disk,\n+\t.se_ops\t\t\t= \u0026zloop_se_ops,\n };\n \n __printf(3, 4)\n@@ -1112,6 +1566,24 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts,\n \tif (!opts-\u003ebuffered_io)\n \t\toflags |= O_DIRECT;\n \n+\tif (zlo-\u003estor_elements != ZLOOP_STOR_ELEMENTS_NONE) {\n+\t\tunsigned int nr_elems = zlo-\u003enr_elements;\n+\t\tunsigned int se_idx;\n+\n+\t\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)\n+\t\t\tnr_elems /= 2;\n+\t\tse_idx = zone_no % nr_elems;\n+\t\tzlo-\u003eelements[se_idx].nr_zones++;\n+\n+\t\tzone-\u003ewr_se_id = se_idx + 1;\n+\t\tzone-\u003erd_se_id = zone-\u003ewr_se_id;\n+\t\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) {\n+\t\t\tzone-\u003erd_se_id += nr_elems;\n+\t\t\tzlo-\u003eelements[zone-\u003erd_se_id - 1].nr_zones =\n+\t\t\t\tzlo-\u003eelements[se_idx].nr_zones;\n+\t\t}\n+\t}\n+\n \tif (zone_no \u003c zlo-\u003enr_conv_zones) {\n \t\t/* Conventional zone file. */\n \t\tset_bit(ZLOOP_ZONE_CONV, \u0026zone-\u003eflags);\n@@ -1182,6 +1654,79 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts,\n \treturn ret;\n }\n \n+#define ZLOOP_MIN_STOR_ELEMENTS\t\t\t2\n+#define ZLOOP_MAX_STOR_ELEMENTS\t\t\t32\n+#define ZLOOP_MIN_ZONES_PER_STOR_ELEMENTS\t32\n+\n+static int zloop_create_storage_elements(struct zloop_device *zlo)\n+{\n+\tstruct blk_se *se, *paired_se;\n+\tunsigned int i, nr_elems, nr_elements;\n+\n+\t/*\n+\t * Calculate the number of storage elements we are going to emulate.\n+\t * To achieve a somewhat realistic emulation, we want at least 2 storage\n+\t * elements, and no more than 32, targeting at least 32 zones per\n+\t * element.\n+\t */\n+\tif (zlo-\u003enr_zones \u003c= 64)\n+\t\tnr_elements = 2;\n+\telse\n+\t\tnr_elements =\n+\t\t\tmin(ZLOOP_MAX_STOR_ELEMENTS,\n+\t\t\t zlo-\u003enr_zones / ZLOOP_MIN_ZONES_PER_STOR_ELEMENTS);\n+\n+\t/*\n+\t * If we are emulating pairs of read and write storage elements, we need\n+\t * double the number of storage element descriptors.\n+\t */\n+\tnr_elems = nr_elements;\n+\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)\n+\t\tnr_elems *= 2;\n+\tzlo-\u003eelements = kzalloc_objs(struct blk_se, nr_elems);\n+\tif (!zlo-\u003eelements)\n+\t\treturn -ENOMEM;\n+\n+\tfor (i = 0, se = zlo-\u003eelements; i \u003c nr_elements; i++, se++) {\n+\t\tse-\u003eid = i + 1;\n+\t\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)\n+\t\t\tse-\u003etype = BLK_SE_TYPE_WRITE;\n+\t\telse\n+\t\t\tse-\u003etype = BLK_SE_TYPE_RDWR;\n+\t\tse-\u003estatus = BLK_SE_STS_OK;\n+\t}\n+\n+\t/*\n+\t * If we are emulating pairs of read and write storage elements,\n+\t * initialize the read elements paired with the write elements we just\n+\t * initialized.\n+\t */\n+\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_PAIRS) {\n+\t\tfor (i = 0; i \u003c nr_elements; i++, se++) {\n+\t\t\tse-\u003eid = nr_elements + i + 1;\n+\t\t\tpaired_se = \u0026zlo-\u003eelements[i];\n+\t\t\tse-\u003epaired_id = paired_se-\u003eid;\n+\t\t\tpaired_se-\u003epaired_id = se-\u003eid;\n+\t\t\tse-\u003etype = BLK_SE_TYPE_READ;\n+\t\t\tse-\u003estatus = BLK_SE_STS_OK;\n+\t\t}\n+\t}\n+\n+\t/*\n+\t * Make sure we do not allow removing all storage elements as that does\n+\t * not make any sense. This is consistent with the device advertized\n+\t * limit of SCSI and ATA devices supporting the storage element\n+\t * depopulation feature.\n+\t */\n+\tzlo-\u003enr_elements = nr_elems;\n+\tif (zlo-\u003estor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)\n+\t\tzlo-\u003emax_nr_removed_elements = zlo-\u003enr_elements - 2;\n+\telse\n+\t\tzlo-\u003emax_nr_removed_elements = zlo-\u003enr_elements - 1;\n+\n+\treturn 0;\n+}\n+\n static bool zloop_dev_exists(struct zloop_device *zlo)\n {\n \tstruct file *cnv, *seq;\n@@ -1237,6 +1782,11 @@ static int zloop_ctl_add(struct zloop_options *opts)\n \tWRITE_ONCE(zlo-\u003estate, Zlo_creating);\n \tspin_lock_init(\u0026zlo-\u003eopen_zones_lock);\n \tINIT_LIST_HEAD(\u0026zlo-\u003eopen_zones_lru_list);\n+\tmutex_init(\u0026zlo-\u003estor_elements_lock);\n+\tINIT_DELAYED_WORK(\u0026zlo-\u003eremove_element_work,\n+\t\t\t zloop_remove_element_work);\n+\tINIT_DELAYED_WORK(\u0026zlo-\u003erestore_elements_work,\n+\t\t\t zloop_restore_elements_work);\n \n \tret = mutex_lock_killable(\u0026zloop_ctl_mutex);\n \tif (ret)\n@@ -1270,12 +1820,19 @@ static int zloop_ctl_add(struct zloop_options *opts)\n \tif (zlo-\u003ezone_append)\n \t\tzlo-\u003eordered_zone_append = opts-\u003eordered_zone_append;\n \tzlo-\u003ediscard_write_cache = opts-\u003ediscard_write_cache;\n+\tzlo-\u003estor_elements = opts-\u003estor_elements;\n+\n+\tif (zlo-\u003estor_elements != ZLOOP_STOR_ELEMENTS_NONE) {\n+\t\tret = zloop_create_storage_elements(zlo);\n+\t\tif (ret)\n+\t\t\tgoto out_free_idr;\n+\t}\n \n \tzlo-\u003eworkqueue = alloc_workqueue(\"zloop%d\", WQ_UNBOUND | WQ_FREEZABLE,\n \t\t\t\topts-\u003enr_queues * opts-\u003equeue_depth, zlo-\u003eid);\n \tif (!zlo-\u003eworkqueue) {\n \t\tret = -ENOMEM;\n-\t\tgoto out_free_idr;\n+\t\tgoto out_destroy_storage_elements;\n \t}\n \n \tif (opts-\u003ebase_dir)\n@@ -1361,6 +1918,10 @@ static int zloop_ctl_add(struct zloop_options *opts)\n \t\tzlo-\u003eid, zlo-\u003enr_zones,\n \t\t((sector_t)zlo-\u003ezone_size \u003c\u003c SECTOR_SHIFT) \u003e\u003e 20,\n \t\tzlo-\u003eblock_size);\n+\tif (zlo-\u003enr_elements)\n+\t\tpr_info(\"zloop%d: %d storage elements\\n\",\n+\t\t\tzlo-\u003eid, zlo-\u003enr_elements);\n+\n \tpr_info(\"zloop%d: using %s%s zone append\\n\",\n \t\tzlo-\u003eid,\n \t\tzlo-\u003eordered_zone_append ? \"ordered \" : \"\",\n@@ -1384,6 +1945,8 @@ static int zloop_ctl_add(struct zloop_options *opts)\n \tkfree(zlo-\u003ebase_dir);\n out_destroy_workqueue:\n \tdestroy_workqueue(zlo-\u003eworkqueue);\n+out_destroy_storage_elements:\n+\tkfree(zlo-\u003eelements);\n out_free_idr:\n \tmutex_lock(\u0026zloop_ctl_mutex);\n \tidr_remove(\u0026zloop_index_idr, zlo-\u003eid);\n@@ -1483,6 +2046,42 @@ static int zloop_ctl_remove(struct zloop_options *opts)\n \treturn 0;\n }\n \n+static int zloop_ctl_degrade_element(struct zloop_options *opts)\n+{\n+\tstruct zloop_device *zlo;\n+\tint ret = 0;\n+\n+\tif (!(opts-\u003emask \u0026 ZLOOP_OPT_ID)) {\n+\t\tpr_err(\"No ID specified for degrade_element\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (opts-\u003emask \u0026 ~(ZLOOP_OPT_ID | ZLOOP_OPT_ELEMENT_ID)) {\n+\t\tpr_err(\"Invalid option specified for degrade_element\\n\");\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tmutex_lock(\u0026zloop_ctl_mutex);\n+\n+\tzlo = idr_find(\u0026zloop_index_idr, opts-\u003eid);\n+\tif (!zlo || zlo-\u003estate == Zlo_creating)\n+\t\tret = -ENODEV;\n+\telse if (zlo-\u003estate == Zlo_deleting)\n+\t\tret = -EINVAL;\n+\tif (ret)\n+\t\tgoto unlock;\n+\n+\tret = zloop_degrade_element(zlo, opts-\u003eelement_id);\n+\tif (!ret)\n+\t\tpr_info(\"Degraded element %u of device %u\\n\",\n+\t\t\topts-\u003eelement_id, opts-\u003eid);\n+\n+unlock:\n+\tmutex_unlock(\u0026zloop_ctl_mutex);\n+\n+\treturn ret;\n+}\n+\n static int zloop_parse_options(struct zloop_options *opts, const char *buf)\n {\n \tsubstring_t args[MAX_OPT_ARGS];\n@@ -1502,6 +2101,8 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)\n \topts-\u003ebuffered_io = ZLOOP_DEF_BUFFERED_IO;\n \topts-\u003ezone_append = ZLOOP_DEF_ZONE_APPEND;\n \topts-\u003eordered_zone_append = ZLOOP_DEF_ORDERED_ZONE_APPEND;\n+\topts-\u003estor_elements = ZLOOP_DEF_STOR_ELEMENTS;\n+\topts-\u003eelement_id = ZLOOP_DEF_ELEMENT_ID;\n \n \tif (!buf)\n \t\treturn 0;\n@@ -1636,6 +2237,30 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)\n \t\tcase ZLOOP_OPT_DISCARD_WRITE_CACHE:\n \t\t\topts-\u003ediscard_write_cache = true;\n \t\t\tbreak;\n+\t\tcase ZLOOP_OPT_STOR_ELEMENTS:\n+\t\t\tif (match_uint(args, \u0026token)) {\n+\t\t\t\tret = -EINVAL;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tswitch (token) {\n+\t\t\tcase ZLOOP_STOR_ELEMENTS_NONE:\n+\t\t\tcase ZLOOP_STOR_ELEMENTS_RDWR:\n+\t\t\tcase ZLOOP_STOR_ELEMENTS_PAIRS:\n+\t\t\t\tbreak;\n+\t\t\tdefault:\n+\t\t\t\tpr_err(\"Invalid stor_elements value\\n\");\n+\t\t\t\tret = -EINVAL;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\topts-\u003estor_elements = token;\n+\t\t\tbreak;\n+\t\tcase ZLOOP_OPT_ELEMENT_ID:\n+\t\t\tif (match_uint(args, \u0026token)) {\n+\t\t\t\tret = -EINVAL;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\topts-\u003eelement_id = token;\n+\t\t\tbreak;\n \t\tcase ZLOOP_OPT_ERR:\n \t\tdefault:\n \t\t\tpr_warn(\"unknown parameter or missing value '%s'\\n\", p);\n@@ -1664,14 +2289,16 @@ static int zloop_parse_options(struct zloop_options *opts, const char *buf)\n enum {\n \tZLOOP_CTL_ADD,\n \tZLOOP_CTL_REMOVE,\n+\tZLOOP_CTL_DEGRADE_ELEMENT,\n };\n \n static struct zloop_ctl_op {\n \tint\t\tcode;\n \tconst char\t*name;\n } zloop_ctl_ops[] = {\n-\t{ ZLOOP_CTL_ADD,\t\"add\" },\n-\t{ ZLOOP_CTL_REMOVE,\t\"remove\" },\n+\t{ ZLOOP_CTL_ADD,\t\t\"add\" },\n+\t{ ZLOOP_CTL_REMOVE,\t\t\"remove\" },\n+\t{ ZLOOP_CTL_DEGRADE_ELEMENT,\t\"degrade_element\" },\n \t{ -1,\tNULL },\n };\n \n@@ -1719,6 +2346,9 @@ static ssize_t zloop_ctl_write(struct file *file, const char __user *ubuf,\n \tcase ZLOOP_CTL_REMOVE:\n \t\tret = zloop_ctl_remove(\u0026opts);\n \t\tbreak;\n+\tcase ZLOOP_CTL_DEGRADE_ELEMENT:\n+\t\tret = zloop_ctl_degrade_element(\u0026opts);\n+\t\tbreak;\n \tdefault:\n \t\tpr_err(\"Invalid operation\\n\");\n \t\tret = -EINVAL;\n@@ -1742,6 +2372,8 @@ static int zloop_ctl_show(struct seq_file *seq_file, void *private)\n \t\ttok = \u0026zloop_opt_tokens[i];\n \t\tif (!tok-\u003epattern)\n \t\t\tbreak;\n+\t\tif (tok-\u003etoken == ZLOOP_OPT_ELEMENT_ID)\n+\t\t\tcontinue;\n \t\tif (i)\n \t\t\tseq_putc(seq_file, ',');\n \t\tseq_puts(seq_file, tok-\u003epattern);\n@@ -1752,6 +2384,10 @@ static int zloop_ctl_show(struct seq_file *seq_file, void *private)\n \tseq_puts(seq_file, zloop_ctl_ops[1].name);\n \tseq_puts(seq_file, \" id=%d\\n\");\n \n+\t/* Degrade element operation */\n+\tseq_puts(seq_file, zloop_ctl_ops[2].name);\n+\tseq_puts(seq_file, \" id=%d,element_id=%d\\n\");\n+\n \treturn 0;\n }\n \ndiff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c\nindex a1b21ea14e549..48c46f4a1ace8 100644\n--- a/drivers/scsi/sd.c\n+++ b/drivers/scsi/sd.c\n@@ -1424,7 +1424,8 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd)\n {\n \tstruct request *rq = scsi_cmd_to_rq(cmd);\n \tstruct scsi_device *sdp = cmd-\u003edevice;\n-\tstruct scsi_disk *sdkp = scsi_disk(rq-\u003eq-\u003edisk);\n+\tstruct gendisk *disk = rq-\u003eq-\u003edisk;\n+\tstruct scsi_disk *sdkp = scsi_disk(disk);\n \tsector_t lba = sectors_to_logical(sdp, blk_rq_pos(rq));\n \tsector_t threshold;\n \tunsigned int nr_blocks = sectors_to_logical(sdp, blk_rq_sectors(rq));\n@@ -1456,6 +1457,11 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd)\n \t\tgoto fail;\n \t}\n \n+\t/* Prevent read/write to offline zones of ZBC disks. */\n+\tif (sdkp-\u003edevice-\u003etype == TYPE_ZBC \u0026\u0026\n+\t disk_zone_is_offline(disk, blk_rq_pos(rq)))\n+\t\tgoto fail;\n+\n \t/*\n \t * Some SD card readers can't handle accesses which touch the\n \t * last one or two logical blocks. Split accesses as needed.\n@@ -3937,6 +3943,9 @@ static const struct block_device_operations sd_fops = {\n \t.get_unique_id\t\t= sd_get_unique_id,\n \t.free_disk\t\t= scsi_disk_free_disk,\n \t.pr_ops\t\t\t= \u0026sd_pr_ops,\n+#ifdef CONFIG_BLK_DEV_ZONED\n+\t.se_ops\t\t\t= \u0026sd_zbc_se_ops,\n+#endif\n };\n \n /**\ndiff --git a/drivers/scsi/sd.h b/drivers/scsi/sd.h\nindex 574af82430169..fc8c49903811e 100644\n--- a/drivers/scsi/sd.h\n+++ b/drivers/scsi/sd.h\n@@ -156,6 +156,7 @@ struct scsi_disk {\n \tunsigned\tignore_medium_access_errors : 1;\n \tunsigned\trscs : 1; /* reduced stream control support */\n \tunsigned\tuse_atomic_write_boundary : 1;\n+\tunsigned\tmodify_zones_supported : 1;\n };\n #define to_scsi_disk(obj) container_of(obj, struct scsi_disk, disk_dev)\n \n@@ -242,6 +243,8 @@ unsigned int sd_zbc_complete(struct scsi_cmnd *cmd, unsigned int good_bytes,\n int sd_zbc_report_zones(struct gendisk *disk, sector_t sector,\n \t\tunsigned int nr_zones, struct blk_report_zones_args *args);\n \n+extern const struct blk_se_ops sd_zbc_se_ops;\n+\n #else /* CONFIG_BLK_DEV_ZONED */\n \n static inline int sd_zbc_read_zones(struct scsi_disk *sdkp,\ndiff --git a/drivers/scsi/sd_zbc.c b/drivers/scsi/sd_zbc.c\nindex 456beaf2e7690..39cd4f9fcce5c 100644\n--- a/drivers/scsi/sd_zbc.c\n+++ b/drivers/scsi/sd_zbc.c\n@@ -516,6 +516,21 @@ static int sd_zbc_check_capacity(struct scsi_disk *sdkp, unsigned char *buf,\n \treturn 0;\n }\n \n+/*\n+ * sd_zbc_check_modify_zones - Check if the device supports depopulation\n+ * @sdkp: Target disk\n+ * @buf: command buffer\n+ *\n+ * Check if the device supports the REMOVE ELEMENT AND MODIFY ZONES command.\n+ */\n+static inline bool sd_zbc_check_modify_zones(struct scsi_disk *sdkp,\n+\t\t\t\t\t unsigned char *buf)\n+{\n+\treturn scsi_report_opcode(sdkp-\u003edevice, buf, SD_BUF_SIZE,\n+\t\t\t\t SERVICE_ACTION_IN_16,\n+\t\t\t\t SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES) == 1;\n+}\n+\n static void sd_zbc_print_zones(struct scsi_disk *sdkp)\n {\n \tif (sdkp-\u003edevice-\u003etype != TYPE_ZBC || !sdkp-\u003ecapacity)\n@@ -533,6 +548,239 @@ static void sd_zbc_print_zones(struct scsi_disk *sdkp)\n \t\t\t sdkp-\u003ezone_info.zone_blocks);\n }\n \n+static void sd_zbc_parse_storage_element(struct scsi_disk *sdkp, u8 *desc,\n+\t\t\t\t\t struct blk_se *element)\n+{\n+\tstruct scsi_device *sdp = sdkp-\u003edevice;\n+\tsector_t zone_sectors = sd_zbc_zone_sectors(sdkp);\n+\tu64 capacity;\n+\n+\tmemset(element, 0, sizeof(*element));\n+\n+\telement-\u003eid = get_unaligned_be32(\u0026desc[4]);\n+\n+\tswitch (desc[14]) {\n+\tcase SCSI_PHYS_ELEM_TYPE_ALL_ACCESS_STORAGE:\n+\t\telement-\u003etype = BLK_SE_TYPE_RDWR;\n+\t\tcapacity = get_unaligned_be64(\u0026desc[16]);\n+\t\tif (!zone_sectors || capacity == ULLONG_MAX)\n+\t\t\telement-\u003enr_zones = 0;\n+\t\telse\n+\t\t\telement-\u003enr_zones =\n+\t\t\t\tlogical_to_sectors(sdp, capacity) \u003e\u003e\n+\t\t\t\t\tilog2(zone_sectors);\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_TYPE_FRAC_ACCESS_STORAGE:\n+\t\telement-\u003epaired_id = get_unaligned_be32(\u0026desc[16]);\n+\t\tif (desc[20] \u0026 0x01)\n+\t\t\telement-\u003etype = BLK_SE_TYPE_READ;\n+\t\telse\n+\t\t\telement-\u003etype = BLK_SE_TYPE_WRITE;\n+\t\telement-\u003enr_zones = get_unaligned_be64(\u0026desc[24]);\n+\t\tbreak;\n+\tdefault:\n+\t\telement-\u003etype = BLK_SE_TYPE_UNKNOWN;\n+\t}\n+\n+\tswitch (desc[15]) {\n+\tcase SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS:\n+\tcase SCSI_PHYS_ELEM_HEALTH_AT_SPEC_LIMITS:\n+\t\telement-\u003estatus = BLK_SE_STS_OK;\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS:\n+\t\telement-\u003estatus = BLK_SE_STS_DEGRADED;\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_ERR:\n+\t\telement-\u003estatus = BLK_SE_STS_RESTORE_ERROR;\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_HEALTH_DEPOP_REVOKE_IN_PROGRESS:\n+\t\telement-\u003estatus = BLK_SE_STS_RESTORE_IN_PROGRESS;\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_HEALTH_DEPOP_ERR:\n+\t\telement-\u003estatus = BLK_SE_STS_REMOVE_ERROR;\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_HEALTH_DEPOP_IN_PROGRESS:\n+\t\telement-\u003estatus = BLK_SE_STS_REMOVE_IN_PROGRESS;\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_HEALTH_DEPOP_OK:\n+\t\telement-\u003estatus = BLK_SE_STS_REMOVED;\n+\t\telement-\u003erestore_allowed = desc[13] \u0026 0x01;\n+\t\tbreak;\n+\tcase SCSI_PHYS_ELEM_HEALTH_NOT_REPORTED:\n+\tdefault:\n+\t\telement-\u003estatus = BLK_SE_STS_UNKNOWN;\n+\t\tbreak;\n+\t}\n+}\n+\n+/*\n+ * Large hard limit on the number of storage elements. This accommodates all\n+ * known devices today and likely forever :)\n+ */\n+#define SD_ZBC_MAX_STORAGE_ELEMENTS\t255\n+\n+static int sd_zbc_report_storage_elements(struct gendisk *disk,\n+\t\t\t\t\t struct blk_se *elements,\n+\t\t\t\t\t unsigned int *nr_elements)\n+{\n+\tstruct scsi_disk *sdkp = scsi_disk(disk);\n+\tstruct scsi_device *sdp = sdkp-\u003edevice;\n+\tconst int timeout = sdp-\u003erequest_queue-\u003erq_timeout;\n+\tstruct scsi_sense_hdr sshdr;\n+\tconst struct scsi_exec_args exec_args = {\n+\t\t.sshdr = \u0026sshdr,\n+\t};\n+\tunsigned char cmd[16];\n+\tunsigned int nr_descs, nr_se;\n+\tunsigned int buf_size;\n+\tint i, ret = 0, result;\n+\tu8 *desc, *buf;\n+\n+\tif (!sdkp-\u003emodify_zones_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\t/*\n+\t * We need at least 32B for the report header and 32B for each\n+\t * descriptor.\n+\t */\n+\tnr_se = min(SD_ZBC_MAX_STORAGE_ELEMENTS, *nr_elements);\n+\tbuf_size = ALIGN((nr_se + 1) * 32, SECTOR_SIZE);\n+\n+\tbuf = kzalloc(buf_size, GFP_NOIO);\n+\tif (!buf)\n+\t\treturn -ENOMEM;\n+\n+\tmemset(cmd, 0, 16);\n+\tcmd[0] = SERVICE_ACTION_IN_16;\n+\tcmd[1] = SAI_GET_PHYSICAL_ELEMENT_STATUS;\n+\tput_unaligned_be32(buf_size, \u0026cmd[10]);\n+\n+\tresult = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, buf, buf_size,\n+\t\t\t\t timeout, 1, \u0026exec_args);\n+\tif (result) {\n+\t\tsd_printk(KERN_ERR, sdkp,\n+\t\t\t \"GET PHYSICAL ELEMENT STATUS failed\\n\");\n+\t\tsd_print_result(sdkp, \"GET PHYSICAL ELEMENT STATUS\", result);\n+\t\tif (result \u003e 0 \u0026\u0026 scsi_sense_valid(\u0026sshdr))\n+\t\t\tsd_print_sense_hdr(sdkp, \u0026sshdr);\n+\t\tret = -EIO;\n+\t\tgoto free_buf;\n+\t}\n+\n+\tnr_descs = get_unaligned_be32(\u0026buf[0]);\n+\tif (!nr_descs) {\n+\t\tsd_printk(KERN_ERR, sdkp,\n+\t\t\t \"Invalid number of phys element descriptors\\n\");\n+\t\tret = -EIO;\n+\t\tgoto free_buf;\n+\t}\n+\tif (nr_descs \u003e SD_ZBC_MAX_STORAGE_ELEMENTS) {\n+\t\tsd_printk(KERN_ERR, sdkp,\n+\t\t\t \"Too many phys element descriptors\\n\");\n+\t\tret = -EIO;\n+\t\tgoto free_buf;\n+\t}\n+\n+\tif (!elements) {\n+\t\t*nr_elements = nr_descs;\n+\t\tgoto free_buf;\n+\t}\n+\n+\tnr_descs = get_unaligned_be32(\u0026buf[4]);\n+\tif (!nr_descs) {\n+\t\tsd_printk(KERN_ERR, sdkp,\n+\t\t\t\"Invalid number of reported phys element descriptors\\n\");\n+\t\tret = -EIO;\n+\t\tgoto free_buf;\n+\t}\n+\n+\tdesc = \u0026buf[32];\n+\tfor (i = 0; i \u003c min(nr_se, nr_descs); i++, desc += 32)\n+\t\tsd_zbc_parse_storage_element(sdkp, desc, \u0026elements[i]);\n+\t*nr_elements = i;\n+\n+free_buf:\n+\tkfree(buf);\n+\n+\treturn ret;\n+}\n+\n+static int sd_zbc_remove_storage_element(struct gendisk *disk,\n+\t\t\t\t\t unsigned int element_id)\n+{\n+\tstruct scsi_disk *sdkp = scsi_disk(disk);\n+\tstruct scsi_device *sdp = sdkp-\u003edevice;\n+\tconst int timeout = sdp-\u003erequest_queue-\u003erq_timeout;\n+\tstruct scsi_sense_hdr sshdr;\n+\tconst struct scsi_exec_args exec_args = {\n+\t\t.sshdr = \u0026sshdr,\n+\t};\n+\tunsigned char cmd[16];\n+\tint result;\n+\n+\tif (!sdkp-\u003emodify_zones_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tmemset(cmd, 0, 16);\n+\tcmd[0] = SERVICE_ACTION_IN_16;\n+\tcmd[1] = SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES;\n+\tput_unaligned_be32(element_id, \u0026cmd[10]);\n+\n+\tresult = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, NULL, 0,\n+\t\t\t\t timeout, 1, \u0026exec_args);\n+\tif (result) {\n+\t\tsd_printk(KERN_ERR, sdkp,\n+\t\t\t \"REMOVE ELEMENT AND MODIFY ZONES failed\\n\");\n+\t\tsd_print_result(sdkp,\n+\t\t\t\t\"REMOVE ELEMENT AND MODIFY ZONES\", result);\n+\t\tif (result \u003e 0 \u0026\u0026 scsi_sense_valid(\u0026sshdr))\n+\t\t\tsd_print_sense_hdr(sdkp, \u0026sshdr);\n+\t\treturn -EIO;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int sd_zbc_restore_storage_elements(struct gendisk *disk)\n+{\n+\tstruct scsi_disk *sdkp = scsi_disk(disk);\n+\tstruct scsi_device *sdp = sdkp-\u003edevice;\n+\tconst int timeout = sdp-\u003erequest_queue-\u003erq_timeout;\n+\tstruct scsi_sense_hdr sshdr;\n+\tconst struct scsi_exec_args exec_args = {\n+\t\t.sshdr = \u0026sshdr,\n+\t};\n+\tunsigned char cmd[16];\n+\tint result;\n+\n+\tif (!sdkp-\u003emodify_zones_supported)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tmemset(cmd, 0, 16);\n+\tcmd[0] = SERVICE_ACTION_IN_16;\n+\tcmd[1] = SAI_RESTORE_ELEMENTS_AND_REBUILD;\n+\n+\tresult = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, NULL, 0,\n+\t\t\t\t timeout, 1, \u0026exec_args);\n+\tif (result) {\n+\t\tsd_printk(KERN_ERR, sdkp,\n+\t\t\t \"RESTORE ELEMENTS AND REBUILD failed\\n\");\n+\t\tsd_print_result(sdkp,\n+\t\t\t\t\"RESTORE ELEMENTS AND REBUILD\", result);\n+\t\tif (result \u003e 0 \u0026\u0026 scsi_sense_valid(\u0026sshdr))\n+\t\t\tsd_print_sense_hdr(sdkp, \u0026sshdr);\n+\t\treturn -EIO;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+const struct blk_se_ops sd_zbc_se_ops = {\n+\t.report_elements\t= sd_zbc_report_storage_elements,\n+\t.remove_element\t\t= sd_zbc_remove_storage_element,\n+\t.restore_elements\t= sd_zbc_restore_storage_elements,\n+};\n+\n /*\n * Call blk_revalidate_disk_zones() if any of the zoned disk properties have\n * changed that make it necessary to call that function. Called by\n@@ -554,9 +802,15 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp)\n \tif (!blk_queue_is_zoned(q))\n \t\treturn 0;\n \n+\t/*\n+\t * If the zone size and number of zones has not changed, and the disk\n+\t * does not support depopulating heads, skip the rather slow call to\n+\t * blk_revalidate_disk_zones().\n+\t */\n \tif (sdkp-\u003ezone_info.zone_blocks == zone_blocks \u0026\u0026\n \t sdkp-\u003ezone_info.nr_zones == nr_zones \u0026\u0026\n-\t disk-\u003enr_zones == nr_zones)\n+\t disk-\u003enr_zones == nr_zones \u0026\u0026\n+\t !sdkp-\u003emodify_zones_supported)\n \t\treturn 0;\n \n \tsdkp-\u003ezone_info.zone_blocks = zone_blocks;\n@@ -620,6 +874,9 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim,\n \tif (ret != 0)\n \t\tgoto err;\n \n+\t/* Check if REMOVE ELEMENT AND MODIFY ZONES is supported. */\n+\tsdkp-\u003emodify_zones_supported = sd_zbc_check_modify_zones(sdkp, buf);\n+\n \tnr_zones = round_up(sdkp-\u003ecapacity, zone_blocks) \u003e\u003e ilog2(zone_blocks);\n \tif (nr_zones \u003e INT_MAX) {\n \t\tsd_printk(KERN_ERR, sdkp, \"Too many zones (%llu)\\n\",\ndiff --git a/include/linux/blkdev.h b/include/linux/blkdev.h\nindex d003a9d2d1f6c..20b77c6b451fc 100644\n--- a/include/linux/blkdev.h\n+++ b/include/linux/blkdev.h\n@@ -189,6 +189,11 @@ struct gendisk {\n \tstruct disk_events *ev;\n \n #ifdef CONFIG_BLK_DEV_ZONED\n+\t/*\n+\t * Total number of storage elements of the disk. This is always 0 for\n+\t * disks that do not support storage element management.\n+\t */\n+\tunsigned int\t\tnr_storage_elements;\n \t/*\n \t * Zoned block device information. Reads of this information must be\n \t * protected with blk_queue_enter() / blk_queue_exit() or by holding a\n@@ -942,6 +947,9 @@ static inline unsigned int disk_zone_capacity(struct gendisk *disk,\n \t\treturn disk-\u003elast_zone_capacity;\n \treturn disk-\u003ezone_capacity;\n }\n+\n+bool disk_zone_is_offline(struct gendisk *disk, sector_t sector);\n+\n static inline unsigned int bdev_zone_capacity(struct block_device *bdev,\n \t\t\t\t\t sector_t pos)\n {\n@@ -956,6 +964,12 @@ static inline unsigned int disk_nr_zones(struct gendisk *disk)\n \treturn 0;\n }\n \n+static inline bool disk_zone_is_offline(struct gendisk *disk,\n+\t\t\t\t\tsector_t sector)\n+{\n+\treturn false;\n+}\n+\n static inline bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)\n {\n \treturn false;\n@@ -1574,6 +1588,22 @@ enum blk_unique_id {\n \tBLK_UID_NAA\t= 3,\n };\n \n+/* Storage element management operations. */\n+struct blk_se_ops {\n+\tint (*report_elements)(struct gendisk *disk,\n+\t\t\t struct blk_se *elements,\n+\t\t\t unsigned int *nr_elements);\n+\tint (*remove_element)(struct gendisk *disk, unsigned int element_id);\n+\tint (*restore_elements)(struct gendisk *disk);\n+};\n+\n+int bdev_report_storage_elements(struct block_device *bdev,\n+\t\t\t\t struct blk_se *elements,\n+\t\t\t\t unsigned int *nr_elements);\n+int bdev_remove_storage_element(struct block_device *bdev,\n+\t\t\t\tunsigned int element_id);\n+int bdev_restore_storage_elements(struct block_device *bdev);\n+\n struct block_device_operations {\n \tvoid (*submit_bio)(struct bio *bio);\n \tint (*poll_bio)(struct bio *bio, struct io_comp_batch *iob,\n@@ -1601,6 +1631,7 @@ struct block_device_operations {\n \t\t\tenum blk_unique_id id_type);\n \tstruct module *owner;\n \tconst struct pr_ops *pr_ops;\n+\tconst struct blk_se_ops\t*se_ops;\n \n \t/*\n \t * Special callback for probing GPT entry at a given sector.\ndiff --git a/include/uapi/linux/blkzoned.h b/include/uapi/linux/blkzoned.h\nindex 6638361209667..56b8ea7c6a86d 100644\n--- a/include/uapi/linux/blkzoned.h\n+++ b/include/uapi/linux/blkzoned.h\n@@ -208,4 +208,87 @@ struct blk_zone_range {\n #define BLKFINISHZONE\t_IOW(0x12, 136, struct blk_zone_range)\n #define BLKREPORTZONEV2\t_IOWR(0x12, 142, struct blk_zone_report)\n \n+/**\n+ * enum blk_se_type - Type of a zoned device storage element.\n+ *\n+ * @BLK_SE_TYPE_RDWR: The storage element handles both reads and writes.\n+ * @BLK_SE_TYPE_READ: The storage element handles reads only.\n+ * @BLK_SE_TYPE_WRITE: The storage element handles writes only.\n+ * @BLK_SE_TYPE_UNKNOWN: The storage element type is not known.\n+ */\n+enum blk_se_type {\n+\tBLK_SE_TYPE_RDWR\t\t= 0x01,\n+\tBLK_SE_TYPE_READ\t\t= 0x02,\n+\tBLK_SE_TYPE_WRITE\t\t= 0x03,\n+\tBLK_SE_TYPE_UNKNOWN\t\t= 0xFF,\n+};\n+\n+/**\n+ * enum blk_se_status - Status of a zoned device storage element.\n+ *\n+ * @BLK_SE_STS_OK: The storage element is operating normally.\n+ * @BLK_SE_STS_DEGRADED: The storage element has degraded and is not operating\n+ *\t\t\t normally.\n+ * @BLK_SE_STS_REMOVE_IN_PROGRESS: The storage element is being removed.\n+ * @BLK_SE_STS_REMOVE_ERROR: The storage element removal failed.\n+ * @BLK_SE_STS_RESTORE_IN_PROGRESS: The storage element is being restored.\n+ * @BLK_SE_STS_RESTORE_ERROR: The storage element restoration failed.\n+ * @BLK_SE_STS_REMOVED: The storage element was removed.\n+ * @BLK_SE_STS_UNKNOWN: The storage element status is unknown.\n+ */\n+enum blk_se_status {\n+\tBLK_SE_STS_OK\t\t\t= 0x01,\n+\tBLK_SE_STS_DEGRADED\t\t= 0x02,\n+\tBLK_SE_STS_REMOVE_IN_PROGRESS\t= 0x03,\n+\tBLK_SE_STS_REMOVE_ERROR\t\t= 0x04,\n+\tBLK_SE_STS_RESTORE_IN_PROGRESS\t= 0x05,\n+\tBLK_SE_STS_RESTORE_ERROR\t= 0x06,\n+\tBLK_SE_STS_REMOVED\t\t= 0x07,\n+\tBLK_SE_STS_UNKNOWN\t\t= 0xFF,\n+};\n+\n+/**\n+ * struct blk_se - Zoned device storage element descriptor.\n+ *\n+ * @id: The ID of the element (cannot be 0).\n+ * @paired_id: The ID of the paired element for an element that is not\n+ *\t of type BLK_SE_TYPE_RDWR.\n+ * @nr_zones: The number of zones that the storage element handles.\n+ * @type: The type of the storage element (enum blk_se_type).\n+ * @status: The health status of the storage element\n+ *\t (enum blk_se_status).\n+ * @restore_allowed: Indicate if the storage element can be restored.\n+ * @reserved: unused field to align this structure size to 32B\n+ */\n+struct blk_se {\n+\t__u32\tid;\n+\t__u32\tpaired_id;\n+\t__u64\tnr_zones;\n+\t__u8\ttype;\n+\t__u8\tstatus;\n+\t__u8\trestore_allowed;\n+\t__u8\treserved[13];\n+};\n+\n+struct blk_se_report {\n+\t__u32\t\tnr_elements;\n+\t__u32\t\tnr_reported_elements;\n+\t__u64\t\treserved;\n+\tstruct blk_se\telements[];\n+};\n+\n+/**\n+ * Zoned block device storage element management ioctl's:\n+ *\n+ * @BLKGETNRSTORELEMS: Get the number of storage elements of the device.\n+ * @BLKREPORTSTORELEMS: Get the device storage elements.\n+ * @BLKREMOVESTORELEM: Remove (depopulate) one storage element of a device.\n+ * @BLKRESTORESTORELEM: Restore (repopulate if possible) all storage elements\n+ *\t\t\tthat have been removed.\n+ */\n+#define BLKGETNRSTORELEMS _IOR(0x12, 143, __u32)\n+#define BLKREPORTSTORELEMS _IOWR(0x12, 144, struct blk_se_report)\n+#define BLKREMOVESTORELEM _IOW(0x12, 145, __u32)\n+#define BLKRESTORESTORELEMS _IO(0x12, 146)\n+\n #endif /* _UAPI_BLKZONED_H */\ndiff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h\nindex 34c6f219462a5..8a979326aa7f0 100644\n--- a/include/uapi/linux/fs.h\n+++ b/include/uapi/linux/fs.h\n@@ -309,6 +309,7 @@ struct file_attr {\n /* 130-136 and 142 are used by zoned block device ioctls (uapi/linux/blkzoned.h) */\n /* 137-141 are used by blk-crypto ioctls (uapi/linux/blk-crypto.h) */\n #define BLKTRACESETUP2 _IOWR(0x12, 142, struct blk_user_trace_setup2)\n+/* 143-146 are used by storage element management for zoned block devices. */\n \n #define BMAP_IOCTL 1\t\t/* obsolete - kept for compatibility */\n #define FIBMAP\t _IO(0x00,1)\t/* bmap access */\n"
} |
| 2/1 | 2026/10/12 13:43 | agent | patch-evaluator |
3mModel:core Args: null Results: {
"EnableConfigs": [
"BLK_DEV_ZONED",
"BLK_DEV_ZONED_LOOP",
"BLK_DEV_SD"
],
"FocusSymbols": [
"blkdev_zone_storage_elements_ioctl",
"bdev_report_storage_elements",
"bdev_remove_storage_element",
"bdev_restore_storage_elements",
"disk_zone_is_offline",
"zloop_report_elements",
"zloop_remove_element",
"zloop_restore_elements",
"zloop_degrade_element",
"zloop_ctl_degrade_element",
"sd_zbc_report_storage_elements",
"sd_zbc_remove_storage_element",
"sd_zbc_restore_storage_elements"
],
"Reasoning": "The patch series introduces storage element (depopulation/repopulation) management for zoned block devices. It adds new UAPI ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) handled in the block layer, implements storage element emulation and control operations (degrade_element) in the virtual zoned loop driver (zloop), and implements SCSI ZBC storage element operations. The modified code introduces reachable ioctl paths, device state transitions, queue freezing/unfreezing, delayed work handling, and error checking, making it an excellent target for fuzzing.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.
IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.
================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.
- REACHABILITY IS THE PRIMARY GATE:
Fuzzing can only discover bugs in code that can actually execute in standard virtualized
environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
regardless of whether it adds assertions or complex logic.
- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
Patch authors routinely label changes as "cleanups", "refactorings", or state
"No functional change intended". Do NOT take these claims at face value.
Code refactorings that rearrange logic, introduce helper functions, or alter state management
in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
If reachable executable code is modified or refactored, it MUST be fuzzed.
- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
Even if the author believes the invariant always holds, fuzzing is essential to verify whether
an unusual sequence of operations can violate it.
================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:
- Non-kernel and non-executable changes:
* Modifications to Documentation/, comments, or spelling fixes.
* User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
that do not affect the compiled kernel image (vmlinux) or kernel modules.
* Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
that do not alter control flow or data structures.
* Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
* Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
* Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
physical PCI hot-unplug or manual sysfs driver unbinding.
* Code paths exclusive to architectures other than the target architecture.
================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
* Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
* Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
* Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
* Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).
================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:
- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
Do NOT list generic, ubiquitous functions called by almost every program in the corpus
(including, but not limited to: general memory allocators and deallocators, page fault
and trap handlers, or core synchronization primitives; this is not an exhaustive list).
Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
which severely dilutes fuzzing effort away from the actual changes.
- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
List functions that specifically implement the logic being added or altered, or direct API entrypoints
for the subsystem feature under review.
- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).
================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 0f648578ec255b07dcf26e1f7c25cf9d70a5b080
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Oct 12 13:43:15 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/blockdev/zoned_loop.rst b/Documentation/admin-guide/blockdev/zoned_loop.rst
index 64277494fb36a..ec39f141af8d6 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,12 @@ 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 all
+ access storage elements (equivalent to read+write head of
+ a disk) are emulated. A value of 2 enables fractional
+ access storage element (equivalent to pairs of read and
+ write heads of a disk) emulation .
=================== =========================================================
3) Deleting a Zoned Device
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 19268afb8752e..8736e80b4080d 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -18,6 +18,8 @@
#include <linux/mempool.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
#include <trace/events/block.h>
@@ -292,6 +294,22 @@ 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.
+ */
+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
@@ -2526,20 +2544,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 = {
@@ -2550,19 +2555,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
@@ -2575,14 +2571,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);
@@ -2599,8 +2595,6 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
if (ret)
goto free_args;
- mutex_unlock(&disk->zone_revalidate_mutex);
-
return 0;
free_args:
@@ -2616,7 +2610,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;
@@ -2700,5 +2723,438 @@ int queue_zone_wplugs_show(void *data, struct seq_file *m)
return 0;
}
-
#endif
+
+static int disk_se_mgmt_in_progress(struct gendisk *disk,
+ struct blk_se *elements,
+ bool *in_progress)
+{
+ unsigned int i, nr_se = disk->nr_storage_elements;
+ int ret;
+
+ /*
+ * Check if we have storage elements being removed or restored.
+ */
+ ret = disk->fops->se_ops->report_elements(disk, elements, &nr_se);
+ if (ret) {
+ pr_err("Failed to get storage elements\n");
+ return ret;
+ }
+
+ for (i = 0; i < nr_se; i++) {
+ switch (elements[i].status) {
+ case BLK_SE_STS_REMOVE_IN_PROGRESS:
+ case BLK_SE_STS_RESTORE_IN_PROGRESS:
+ *in_progress = true;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ *in_progress = false;
+
+ return 0;
+}
+
+static int disk_check_remove_error(struct gendisk *disk,
+ struct blk_se *elements,
+ unsigned int element_id)
+{
+ unsigned int i;
+
+ for (i = 0; i < disk->nr_storage_elements; i++) {
+ if (elements[i].id == element_id
+ && elements[i].status == BLK_SE_STS_REMOVE_ERROR)
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int disk_wait_for_se_mgmt_completion(struct gendisk *disk,
+ struct blk_se *elements,
+ unsigned int element_id)
+{
+ bool in_progress;
+ int ret;
+
+ /*
+ * Poll the storage elements heath state to detect the end of a
+ * removal or restoration operation. 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.
+ */
+ while (1) {
+ ret = disk_se_mgmt_in_progress(disk, elements, &in_progress);
+ if (ret || !in_progress)
+ break;
+
+ if (fatal_signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+
+ /* Not done yet: wait and retry. */
+ msleep(1000);
+ }
+
+ /* Check for remove errors. */
+ if (!ret && !in_progress && element_id)
+ ret = disk_check_remove_error(disk, elements, element_id);
+
+ return ret;
+}
+
+/**
+ * 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.
+ *
+ * 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 (!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);
+
+static int blkdev_get_nr_storage_elements_ioctl(struct block_device *bdev,
+ void __user *argp)
+{
+ struct gendisk *disk = bdev->bd_disk;
+
+ if (put_user(disk->nr_storage_elements, (unsigned int __user *)argp))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int blkdev_report_storage_elements_ioctl(struct block_device *bdev,
+ void __user *argp)
+{
+ struct gendisk *disk = bdev->bd_disk;
+ struct blk_se_report *rep;
+ int ret;
+
+ if (!argp)
+ return -EINVAL;
+
+ 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->nr_elements < disk->nr_storage_elements) {
+ ret = -EINVAL;
+ goto free_rep;
+ }
+
+ rep->nr_reported_elements = disk->nr_storage_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_t(struct blk_se_report,
+ 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
+ * @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 device write cache flush and invalidation of cached data for the
+ * zones that will be offlined.
+ *
+ * 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, noio_flag;
+ struct blk_se *elements;
+ int ret, err;
+
+ 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;
+
+ noio_flag = memalloc_noio_save();
+ elements = kzalloc_objs(struct blk_se, disk->nr_storage_elements);
+ memalloc_noio_restore(noio_flag);
+ if (!elements)
+ return -ENOMEM;
+
+ /*
+ * 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);
+ if (!ret)
+ ret = disk_wait_for_se_mgmt_completion(disk, elements,
+ element_id);
+
+ /*
+ * Always revalidate zones, even in the case of an error as we do not
+ * know what condition the drive zones are in.
+ */
+ err = disk_revalidate_zones(disk);
+ if (err && !ret)
+ ret = err;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
+
+ kfree(elements);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bdev_remove_storage_element);
+
+static int blkdev_flush_and_truncate(struct block_device *bdev,
+ blk_mode_t mode)
+{
+ int ret;
+
+ inode_lock(bdev->bd_mapping->host);
+ filemap_invalidate_lock(bdev->bd_mapping);
+ ret = blkdev_issue_flush(bdev->bd_disk->part0);
+ if (!ret)
+ ret = truncate_bdev_range(bdev, mode, 0,
+ bdev_nr_bytes(bdev) - 1);
+ filemap_invalidate_unlock(bdev->bd_mapping);
+ inode_unlock(bdev->bd_mapping->host);
+
+ return ret;
+}
+
+static int blkdev_remove_storage_element_ioctl(struct block_device *bdev,
+ blk_mode_t mode, void __user *argp)
+{
+ unsigned int element_id;
+ int ret;
+
+ if (!(mode & BLK_OPEN_WRITE))
+ return -EBADF;
+ if (bdev_read_only(bdev))
+ return -EPERM;
+
+ if (get_user(element_id, (unsigned int __user *)argp))
+ return -EFAULT;
+
+ /*
+ * Flush the device 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 write to zoned devices using direct IOs, we
+ * should never have any dirty page invalidated and so no data loss as
+ * long as the device cache flush succeeds, that is, as long as the
+ * flush does not use degraded storage elements.
+ */
+ ret = blkdev_flush_and_truncate(bdev, mode);
+ if (ret)
+ return ret;
+
+ return bdev_remove_storage_element(bdev, element_id);
+}
+
+/**
+ * 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.
+ *
+ * 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, noio_flag;
+ struct blk_se *elements;
+ int ret, err;
+
+ if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements)
+ return -EOPNOTSUPP;
+
+ noio_flag = memalloc_noio_save();
+ elements = kzalloc_objs(struct blk_se, disk->nr_storage_elements);
+ memalloc_noio_restore(noio_flag);
+ if (!elements)
+ return -ENOMEM;
+
+ /*
+ * 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);
+ if (!ret)
+ ret = disk_wait_for_se_mgmt_completion(disk, elements, 0);
+
+ /*
+ * Always revalidate zones, even in the case of an error as we do not
+ * know what condition the drive zones are in.
+ */
+ err = disk_revalidate_zones(disk);
+ if (err && !ret)
+ ret = err;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
+
+ kfree(elements);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bdev_restore_storage_elements);
+
+static int blkdev_restore_storage_elements_ioctl(struct block_device *bdev,
+ blk_mode_t mode)
+{
+ int ret;
+
+ if (!(mode & BLK_OPEN_WRITE))
+ return -EBADF;
+ if (bdev_read_only(bdev))
+ return -EPERM;
+
+ /*
+ * Storage element restoration is a destructive operation that will
+ * reset all zones. So fflush the device volatile write cache and
+ * invalidate all cached data that we may have.
+ */
+ ret = blkdev_flush_and_truncate(bdev, mode);
+ if (ret)
+ return ret;
+
+ return bdev_restore_storage_elements(bdev);
+}
+
+int disk_init_storage_element_mgmt(struct gendisk *disk)
+{
+ int ret;
+
+ /*
+ * Storage element operations are for zoned disks only and
+ * optional...
+ */
+ if (!blk_queue_is_zoned(disk->queue) || !disk->fops->se_ops)
+ return 0;
+
+ /* ...but if supported, all operations 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;
+}
+
+int blkdev_zone_storage_elements_ioctl(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 BLKGETNRSTORELEMS:
+ return blkdev_get_nr_storage_elements_ioctl(bdev, argp);
+ case BLKREPORTSTORELEMS:
+ return blkdev_report_storage_elements_ioctl(bdev, argp);
+ case BLKREMOVESTORELEM:
+ return blkdev_remove_storage_element_ioctl(bdev, mode, argp);
+ case BLKRESTORESTORELEMS:
+ return blkdev_restore_storage_elements_ioctl(bdev, mode);
+ default:
+ break;
+ }
+
+ return -ENOTTY;
+}
diff --git a/block/blk.h b/block/blk.h
index 2cc03aa54c532..bbf523c9579cf 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -527,6 +527,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)
@@ -578,7 +579,14 @@ 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 blkdev_zone_storage_elements_ioctl(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)
+{
+ return 0;
+}
static inline void disk_init_zone_resources(struct gendisk *disk)
{
}
@@ -625,6 +633,12 @@ static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,
{
return false;
}
+static inline int blkdev_zone_storage_elements_ioctl(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/genhd.c b/block/genhd.c
index 10ca8b4d6eea7..e2cef791ac91a 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/block/ioctl.c b/block/ioctl.c
index 64b4e6c0f6965..ccf806c2e37d1 100644
--- a/block/ioctl.c
+++ b/block/ioctl.c
@@ -679,6 +679,11 @@ 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 BLKGETNRSTORELEMS:
+ case BLKREPORTSTORELEMS:
+ case BLKREMOVESTORELEM:
+ case BLKRESTORESTORELEMS:
+ return blkdev_zone_storage_elements_ioctl(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/drivers/block/zloop.c b/drivers/block/zloop.c
index 394dc2408ed73..26d8335508a16 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -37,6 +37,8 @@ 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),
+ ZLOOP_OPT_ELEMENT_ID = (1 << 14),
};
static const match_table_t zloop_opt_tokens = {
@@ -51,11 +53,23 @@ 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_ELEMENT_ID, "element_id=%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 +82,8 @@ 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
+#define ZLOOP_DEF_ELEMENT_ID 0
/* Arbitrary limit on the zone size (16GB). */
#define ZLOOP_MAX_ZONE_SIZE_MB 16384
@@ -87,6 +103,8 @@ struct zloop_options {
bool zone_append;
bool ordered_zone_append;
bool discard_write_cache;
+ enum zloop_stor_elements stor_elements;
+ unsigned int element_id;
};
/*
@@ -117,6 +135,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 +153,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 +171,17 @@ 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 max_nr_removed_elements;
+ unsigned int nr_removed_elements;
+ struct delayed_work remove_element_work;
+ unsigned int remove_element_id;
+ struct delayed_work restore_elements_work;
+ bool restore_in_progress;
+ struct blk_se *elements;
+
struct zloop_zone zones[] __counted_by(nr_zones);
};
@@ -289,22 +321,43 @@ 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 void zloop_mark_empty(struct zloop_device *zlo, struct zloop_zone *zone)
+static inline void zloop_set_zone_full(struct zloop_device *zlo,
+ struct zloop_zone *zone)
{
- lockdep_assert_held(&zone->wp_lock);
+ zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_FULL);
+}
- zloop_lru_remove_open_zone(zlo, zone);
- zone->cond = BLK_ZONE_COND_EMPTY;
- zone->wp = zone->start;
+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 bool zloop_zone_is_offline_or_readonly(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ bool ret;
+
+ spin_lock(&zone->wp_lock);
+ 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 +369,13 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)
lockdep_assert_held(&zone->lock);
+ /*
+ * If the zone was already changed to offline or read-only, we have
+ * nothing to do.
+ */
+ if (zloop_zone_is_offline_or_readonly(zlo, zone))
+ return 0;
+
ret = vfs_getattr(&zone->file->f_path, &stat, STATX_SIZE, 0);
if (ret < 0) {
pr_err("Failed to get zone %u file stat (err=%d)\n",
@@ -339,9 +399,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)
@@ -363,6 +423,11 @@ static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(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)
@@ -388,6 +453,11 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(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)
@@ -420,7 +490,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;
@@ -430,20 +515,19 @@ static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(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);
@@ -457,7 +541,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;
}
@@ -475,6 +559,11 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(zlo, zone)) {
+ ret = -EIO;
+ goto unlock;
+ }
+
if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) &&
zone->cond == BLK_ZONE_COND_FULL)
goto unlock;
@@ -487,7 +576,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);
@@ -624,7 +713,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);
@@ -766,7 +855,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);
@@ -875,30 +964,72 @@ 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;
+ spin_lock(&zone->wp_lock);
+
+ if (zlo->nr_elements) {
+ struct blk_se *se;
+
+ /*
+ * Check the zone condition and the health state of the storage
+ * element serving the zone: do not allow any operation to
+ * offline zones or if the storage element serving the zone is
+ * being removed, and allow only reads to read-only zones.
+ */
+ if (is_write)
+ se = &zlo->elements[zone->wr_se_id - 1];
+ else
+ se = &zlo->elements[zone->rd_se_id - 1];
+ if (READ_ONCE(se->status) == BLK_SE_STS_DEGRADED ||
+ READ_ONCE(se->status) == BLK_SE_STS_REMOVE_IN_PROGRESS) {
+ ret = false;
+ goto unlock;
+ }
+
+ if (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,
@@ -914,13 +1045,21 @@ 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 (zlo->ordered_zone_append && req_op(rq) == REQ_OP_ZONE_APPEND) {
- if (!zloop_set_zone_append_sector(rq))
+ if (READ_ONCE(zlo->restore_in_progress))
+ return BLK_STS_IOERR;
+
+ 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);
@@ -1002,11 +1141,317 @@ 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 (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_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 void zloop_remove_element_work(struct work_struct *work)
+{
+ struct zloop_device *zlo = container_of(work, struct zloop_device,
+ remove_element_work.work);
+ struct blk_se *se, *paired_se = NULL;
+ enum blk_zone_cond cond;
+ struct zloop_zone *zone;
+ unsigned int memflags, i;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* Wait for all IOs to complete. */
+ memflags = blk_mq_freeze_queue(zlo->disk->queue);
+ blk_mq_unfreeze_queue(zlo->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);
+ }
+
+ /*
+ * 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);
+ clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ zloop_set_zone_cond(zlo, zone, cond);
+ 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++;
+ }
+
+ zlo->remove_element_id = 0;
+
+ mutex_unlock(&zlo->stor_elements_lock);
+}
+
+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 (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ if (element_id > zlo->nr_elements)
+ return -EINVAL;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ 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;
+ }
+
+ if (zlo->nr_removed_elements + nr_remove >
+ zlo->max_nr_removed_elements) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /*
+ * Schedule the element removal with a delay, to emulate the (generally
+ * short) time it takes for a real device to depopulate a head and
+ * modify the zones.
+ */
+ zlo->remove_element_id = element_id;
+ 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);
+
+ schedule_delayed_work(&zlo->remove_element_work,
+ msecs_to_jiffies(2000));
+
+unlock:
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return ret;
+}
+
+static void zloop_restore_conv_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ mutex_lock(&zone->lock);
+ spin_lock(&zone->wp_lock);
+ if (zone->cond == BLK_ZONE_COND_OFFLINE ||
+ zone->cond == BLK_ZONE_COND_READONLY)
+ zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_NOT_WP);
+ spin_unlock(&zone->wp_lock);
+ mutex_unlock(&zone->lock);
+}
+
+static int zloop_restore_seq_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ int ret;
+
+ mutex_lock(&zone->lock);
+ ret = zloop_do_reset_zone(zlo, zone);
+ if (ret)
+ set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ mutex_unlock(&zone->lock);
+
+ return ret;
+}
+
+static void zloop_restore_elements_work(struct work_struct *work)
+{
+ struct zloop_device *zlo = container_of(work, struct zloop_device,
+ restore_elements_work.work);
+ struct zloop_zone *zone;
+ struct blk_se *se;
+ unsigned int memflags, i;
+ enum blk_se_status sts;
+ int ret = 0;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* Wait for all IOs to complete. */
+ memflags = blk_mq_freeze_queue(zlo->disk->queue);
+ blk_mq_unfreeze_queue(zlo->disk->queue, memflags);
+
+ /* Reset all zones. */
+ for (i = 0, zone = zlo->zones; i < zlo->nr_zones; i++, zone++) {
+ if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) {
+ zloop_restore_conv_zone(zlo, zone);
+ continue;
+ }
+ ret = zloop_restore_seq_zone(zlo, zone);
+ if (ret)
+ break;
+ }
+
+ /*
+ * If we had an error, maintain all elements as removed instead of
+ * using the restore error status.
+ */
+ if (ret)
+ sts = BLK_SE_STS_REMOVED;
+ else
+ sts = BLK_SE_STS_OK;
+
+ /* Restore all removed elements. */
+ 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);
+ }
+
+ if (!ret)
+ zlo->nr_removed_elements = 0;
+
+ WRITE_ONCE(zlo->restore_in_progress, false);
+ mutex_unlock(&zlo->stor_elements_lock);
+}
+
+static int zloop_restore_elements(struct gendisk *disk)
+{
+ struct zloop_device *zlo = disk->private_data;
+ struct blk_se *se;
+ unsigned int i;
+ int ret = 0;
+
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ if (zlo->remove_element_id || READ_ONCE(zlo->restore_in_progress)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /* If we have no removed elements, we have nothing to do. */
+ if (!zlo->nr_removed_elements)
+ goto unlock;
+
+ /*
+ * Schedule the elements restoration with a delay, to emulate the time
+ * it takes for a real device to restore all removed heads and reset
+ * all zones.
+ */
+ WRITE_ONCE(zlo->restore_in_progress, true);
+ for (i = 0, se = zlo->elements; i < zlo->nr_elements; i++, se++) {
+ if (READ_ONCE(se->status) == BLK_SE_STS_REMOVED)
+ WRITE_ONCE(se->status, BLK_SE_STS_RESTORE_IN_PROGRESS);
+ }
+
+ schedule_delayed_work(&zlo->restore_elements_work,
+ msecs_to_jiffies(5000));
+
+unlock:
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return ret;
+}
+
static void zloop_free_disk(struct gendisk *disk)
{
struct zloop_device *zlo = disk->private_data;
unsigned int i;
+ cancel_delayed_work_sync(&zlo->remove_element_work);
+ cancel_delayed_work_sync(&zlo->restore_elements_work);
+
blk_mq_free_tag_set(&zlo->tag_set);
for (i = 0; i < zlo->nr_zones; i++) {
@@ -1019,15 +1464,24 @@ 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)
@@ -1112,6 +1566,24 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts,
if (!opts->buffered_io)
oflags |= O_DIRECT;
+ 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 (zone_no < zlo->nr_conv_zones) {
/* Conventional zone file. */
set_bit(ZLOOP_ZONE_CONV, &zone->flags);
@@ -1182,6 +1654,79 @@ 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 = 2;
+ 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;
+ }
+ }
+
+ /*
+ * 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.
+ */
+ zlo->nr_elements = nr_elems;
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)
+ zlo->max_nr_removed_elements = zlo->nr_elements - 2;
+ else
+ zlo->max_nr_removed_elements = zlo->nr_elements - 1;
+
+ return 0;
+}
+
static bool zloop_dev_exists(struct zloop_device *zlo)
{
struct file *cnv, *seq;
@@ -1237,6 +1782,11 @@ 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);
+ INIT_DELAYED_WORK(&zlo->remove_element_work,
+ zloop_remove_element_work);
+ INIT_DELAYED_WORK(&zlo->restore_elements_work,
+ zloop_restore_elements_work);
ret = mutex_lock_killable(&zloop_ctl_mutex);
if (ret)
@@ -1270,12 +1820,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)
@@ -1361,6 +1918,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: %d 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 " : "",
@@ -1384,6 +1945,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);
@@ -1483,6 +2046,42 @@ 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_ID | ZLOOP_OPT_ELEMENT_ID)) {
+ pr_err("Invalid option specified for degrade_element\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&zloop_ctl_mutex);
+
+ 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("Degraded element %u of device %u\n",
+ opts->element_id, opts->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];
@@ -1502,6 +2101,8 @@ 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;
+ opts->element_id = ZLOOP_DEF_ELEMENT_ID;
if (!buf)
return 0;
@@ -1636,6 +2237,30 @@ 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_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);
@@ -1664,14 +2289,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 },
};
@@ -1719,6 +2346,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;
@@ -1742,6 +2372,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);
@@ -1752,6 +2384,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=%d\n");
+
return 0;
}
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index a1b21ea14e549..48c46f4a1ace8 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,11 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd)
goto fail;
}
+ /* Prevent read/write to offline zones of ZBC disks. */
+ 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.
@@ -3937,6 +3943,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 574af82430169..fc8c49903811e 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -156,6 +156,7 @@ struct scsi_disk {
unsigned ignore_medium_access_errors : 1;
unsigned rscs : 1; /* reduced stream control support */
unsigned use_atomic_write_boundary : 1;
+ unsigned modify_zones_supported : 1;
};
#define to_scsi_disk(obj) container_of(obj, struct scsi_disk, disk_dev)
@@ -242,6 +243,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 456beaf2e7690..39cd4f9fcce5c 100644
--- a/drivers/scsi/sd_zbc.c
+++ b/drivers/scsi/sd_zbc.c
@@ -516,6 +516,21 @@ static int sd_zbc_check_capacity(struct scsi_disk *sdkp, unsigned char *buf,
return 0;
}
+/*
+ * sd_zbc_check_modify_zones - Check if the device supports depopulation
+ * @sdkp: Target disk
+ * @buf: command buffer
+ *
+ * Check if the device supports the REMOVE ELEMENT AND MODIFY ZONES command.
+ */
+static inline bool sd_zbc_check_modify_zones(struct scsi_disk *sdkp,
+ unsigned char *buf)
+{
+ return 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)
@@ -533,6 +548,239 @@ 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]);
+
+ 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;
+ }
+
+ switch (desc[15]) {
+ case SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS:
+ case SCSI_PHYS_ELEM_HEALTH_AT_SPEC_LIMITS:
+ element->status = BLK_SE_STS_OK;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS:
+ element->status = BLK_SE_STS_DEGRADED;
+ 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;
+ element->restore_allowed = desc[13] & 0x01;
+ 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
+
+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 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];
+ unsigned int nr_descs, nr_se;
+ unsigned int buf_size;
+ int i, ret = 0, result;
+ u8 *desc, *buf;
+
+ if (!sdkp->modify_zones_supported)
+ return -EOPNOTSUPP;
+
+ /*
+ * We need at least 32B for the report header and 32B for each
+ * descriptor.
+ */
+ nr_se = min(SD_ZBC_MAX_STORAGE_ELEMENTS, *nr_elements);
+ buf_size = ALIGN((nr_se + 1) * 32, SECTOR_SIZE);
+
+ buf = kzalloc(buf_size, GFP_NOIO);
+ if (!buf)
+ return -ENOMEM;
+
+ memset(cmd, 0, 16);
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = SAI_GET_PHYSICAL_ELEMENT_STATUS;
+ put_unaligned_be32(buf_size, &cmd[10]);
+
+ result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, buf, buf_size,
+ timeout, 1, &exec_args);
+ if (result) {
+ sd_printk(KERN_ERR, sdkp,
+ "GET PHYSICAL ELEMENT STATUS failed\n");
+ sd_print_result(sdkp, "GET PHYSICAL ELEMENT STATUS", result);
+ if (result > 0 && scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ ret = -EIO;
+ goto free_buf;
+ }
+
+ nr_descs = get_unaligned_be32(&buf[0]);
+ if (!nr_descs) {
+ sd_printk(KERN_ERR, sdkp,
+ "Invalid number of phys element descriptors\n");
+ ret = -EIO;
+ goto free_buf;
+ }
+ if (nr_descs > SD_ZBC_MAX_STORAGE_ELEMENTS) {
+ sd_printk(KERN_ERR, sdkp,
+ "Too many phys element descriptors\n");
+ ret = -EIO;
+ goto free_buf;
+ }
+
+ if (!elements) {
+ *nr_elements = nr_descs;
+ goto free_buf;
+ }
+
+ nr_descs = get_unaligned_be32(&buf[4]);
+ if (!nr_descs) {
+ sd_printk(KERN_ERR, sdkp,
+ "Invalid number of reported phys element descriptors\n");
+ ret = -EIO;
+ goto free_buf;
+ }
+
+ desc = &buf[32];
+ for (i = 0; i < min(nr_se, nr_descs); i++, desc += 32)
+ sd_zbc_parse_storage_element(sdkp, desc, &elements[i]);
+ *nr_elements = i;
+
+free_buf:
+ kfree(buf);
+
+ return ret;
+}
+
+static int sd_zbc_remove_storage_element(struct gendisk *disk,
+ unsigned int element_id)
+{
+ struct scsi_disk *sdkp = scsi_disk(disk);
+ 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];
+ int result;
+
+ if (!sdkp->modify_zones_supported)
+ return -EOPNOTSUPP;
+
+ memset(cmd, 0, 16);
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES;
+ put_unaligned_be32(element_id, &cmd[10]);
+
+ result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, NULL, 0,
+ timeout, 1, &exec_args);
+ if (result) {
+ sd_printk(KERN_ERR, sdkp,
+ "REMOVE ELEMENT AND MODIFY ZONES failed\n");
+ sd_print_result(sdkp,
+ "REMOVE ELEMENT AND MODIFY ZONES", result);
+ if (result > 0 && scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sd_zbc_restore_storage_elements(struct gendisk *disk)
+{
+ struct scsi_disk *sdkp = scsi_disk(disk);
+ 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];
+ int result;
+
+ if (!sdkp->modify_zones_supported)
+ return -EOPNOTSUPP;
+
+ memset(cmd, 0, 16);
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = SAI_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,
+ "RESTORE ELEMENTS AND REBUILD failed\n");
+ sd_print_result(sdkp,
+ "RESTORE ELEMENTS AND REBUILD", result);
+ if (result > 0 && scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+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
@@ -554,9 +802,15 @@ 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->modify_zones_supported)
return 0;
sdkp->zone_info.zone_blocks = zone_blocks;
@@ -620,6 +874,9 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim,
if (ret != 0)
goto err;
+ /* Check if REMOVE ELEMENT AND MODIFY ZONES is supported. */
+ sdkp->modify_zones_supported = sd_zbc_check_modify_zones(sdkp, buf);
+
nr_zones = round_up(sdkp->capacity, zone_blocks) >> ilog2(zone_blocks);
if (nr_zones > INT_MAX) {
sd_printk(KERN_ERR, sdkp, "Too many zones (%llu)\n",
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index d003a9d2d1f6c..20b77c6b451fc 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
@@ -942,6 +947,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)
{
@@ -956,6 +964,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;
@@ -1574,6 +1588,22 @@ enum blk_unique_id {
BLK_UID_NAA = 3,
};
+/* Storage element management operations. */
+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);
+};
+
+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);
+
struct block_device_operations {
void (*submit_bio)(struct bio *bio);
int (*poll_bio)(struct bio *bio, struct io_comp_batch *iob,
@@ -1601,6 +1631,7 @@ struct block_device_operations {
enum blk_unique_id id_type);
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 6638361209667..56b8ea7c6a86d 100644
--- a/include/uapi/linux/blkzoned.h
+++ b/include/uapi/linux/blkzoned.h
@@ -208,4 +208,87 @@ 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_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_DEGRADED = 0x02,
+ BLK_SE_STS_REMOVE_IN_PROGRESS = 0x03,
+ BLK_SE_STS_REMOVE_ERROR = 0x04,
+ BLK_SE_STS_RESTORE_IN_PROGRESS = 0x05,
+ BLK_SE_STS_RESTORE_ERROR = 0x06,
+ BLK_SE_STS_REMOVED = 0x07,
+ 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];
+};
+
+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:
+ *
+ * @BLKGETNRSTORELEMS: Get the number of storage elements of the device.
+ * @BLKREPORTSTORELEMS: Get the device storage elements.
+ * @BLKREMOVESTORELEM: Remove (depopulate) one storage element of a device.
+ * @BLKRESTORESTORELEM: Restore (repopulate if possible) all storage elements
+ * that have been removed.
+ */
+#define BLKGETNRSTORELEMS _IOR(0x12, 143, __u32)
+#define BLKREPORTSTORELEMS _IOWR(0x12, 144, struct blk_se_report)
+#define BLKREMOVESTORELEM _IOW(0x12, 145, __u32)
+#define BLKRESTORESTORELEMS _IO(0x12, 146)
+
#endif /* _UAPI_BLKZONED_H */
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 34c6f219462a5..8a979326aa7f0 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-146 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 */
|
| 3/2 | 2026/10/12 13:43 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 29836 output: 35 thoughts: 1349 Thoughts: **Patch Analysis: Storage Element Management for Zoned Block Devices** Alright, let's dive into this patch series. My initial thought is to understand the scope and impact of these changes, and whether they warrant further investigation (fuzzing). First, I see the core focus is on enhancing storage element management within the zoned block device framework. The changes span multiple areas: * **Documentation:** Updates to the documentation specifically for the zoned loop device to add the `stor_elements` option, which implies user-level configuration changes. * **Block Layer Core:** Significant modifications in `block/blk-zoned.c` introducing functions to handle the offline status of zones, and importantly, a whole suite of storage element management functions. This includes reporting, removing, restoring, and various IOCTLs for interaction. This immediately flags my interest because IOCTLs represent a direct interface to user space, a key vector for potential fuzzing targets. The `disk_init_storage_element_mgmt` function called in `genhd.c` is important, too. * **IOCTL Handling:** The `block/ioctl.c` changes are particularly noteworthy. We're seeing new IOCTLs (`BLKGETNRSTORELEMS`, `BLKREPORTSTORELEMS`, etc.) being added, and these are calling out to the new `blkdev_zone_storage_elements_ioctl` function. This solidifies the external-facing nature of these changes and their relevance to fuzzing. * **Zoned Loop Driver:** The addition of storage element emulation support in `drivers/block/zloop.c` is very interesting. This includes support for a new configuration option, `stor_elements`, and a whole host of new functions for managing storage elements within the loop device. The introduction of "degrade_element" commands in `zloop_ctl_write` and checks in `zloop_queue_rq` are likely candidates for fuzzing, given that this device can be controlled by userspace. I need to confirm if `zloop` is a real driver in the kernel or not, and how it is typically used in a normal system. * **SCSI Integration:** The inclusion of changes in `drivers/scsi/sd.c` with the `sd_fops.se_ops` and `sd_setup_read_write_cmnd` show that this new storage element management has been integrated into the SCSI layer for ZBC disks. This is important to note and might introduce new attack surfaces. * **Headers and Definitions:** The new structs and definitions in `include/linux/blkdev.h`, `include/uapi/linux/blkzoned.h`, and `include/uapi/linux/fs.h` solidify the user-space interaction and provide a clear picture of the data structures involved. Now, considering the fuzzing criteria, this looks *highly* promising. The changes touch core block layer code, IOCTLs, and the potentially user-accessible `zloop` driver. The restructuring of data structures and introduction of new IOCTLs definitely tick the boxes for "WorthFuzzing=true." Especially given that the `zloop` driver is likely accessible via a virtual bus, it seems ripe for investigation. I will need to check how it is used on a real system before I start fuzzing. |
| 4/2 | 2026/10/12 13:43 | tool | grepper |
0mArgs:{
"Expression": "CONFIG_BLK_DEV_ZLOOP|zloop",
"PathPrefixes": [
"drivers/block"
]
}Results: {
"Output": "\nFull output is too long, showing 200 out of 871 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (3 files in total):\ndrivers/block/Kconfig:1\ndrivers/block/Makefile:1\ndrivers/block/zloop.c:269\n\ndrivers/block/Kconfig=356=config BLK_DEV_ZONED_LOOP\n--\ndrivers/block/Kconfig-367-\t echo \"add id=0,zone_size_mb=256,capacity_mb=16384,conv_zones=11\" \u003e \\\ndrivers/block/Kconfig:368:\t\t/dev/zloop-control\ndrivers/block/Kconfig-369-\n--\ndrivers/block/Makefile=40=obj-$(CONFIG_BLK_DEV_UBLK)\t\t\t+= ublk_drv.o\ndrivers/block/Makefile:41:obj-$(CONFIG_BLK_DEV_ZONED_LOOP) += zloop.o\ndrivers/block/Makefile-42-\n--\ndrivers/block/zloop.c=25=enum {\n--\ndrivers/block/zloop.c-43-\ndrivers/block/zloop.c:44:static const match_table_t zloop_opt_tokens = {\ndrivers/block/zloop.c-45-\t{ ZLOOP_OPT_ID,\t\t\t\"id=%d\"\t},\n--\ndrivers/block/zloop.c-63-/* Storage elements emulation types. */\ndrivers/block/zloop.c:64:enum zloop_stor_elements {\ndrivers/block/zloop.c-65-\t/* No emulation. */\n--\ndrivers/block/zloop.c-78-#define ZLOOP_DEF_MAX_OPEN_ZONES\t0\ndrivers/block/zloop.c:79:#define ZLOOP_DEF_BASE_DIR\t\t\"/var/local/zloop\"\ndrivers/block/zloop.c-80-#define ZLOOP_DEF_NR_QUEUES\t\t1\n--\ndrivers/block/zloop.c-90-\ndrivers/block/zloop.c:91:struct zloop_options {\ndrivers/block/zloop.c-92-\tunsigned int\t\tmask;\n--\ndrivers/block/zloop.c-105-\tbool\t\t\tdiscard_write_cache;\ndrivers/block/zloop.c:106:\tenum zloop_stor_elements stor_elements;\ndrivers/block/zloop.c-107-\tunsigned int\t\telement_id;\n--\ndrivers/block/zloop.c=113=enum {\n--\ndrivers/block/zloop.c-118-\ndrivers/block/zloop.c:119:enum zloop_zone_flags {\ndrivers/block/zloop.c-120-\tZLOOP_ZONE_CONV = 0,\n--\ndrivers/block/zloop.c-127- */\ndrivers/block/zloop.c:128:struct zloop_zone {\ndrivers/block/zloop.c-129-\tstruct list_head\topen_zone_entry;\n--\ndrivers/block/zloop.c-143-\ndrivers/block/zloop.c:144:struct zloop_device {\ndrivers/block/zloop.c-145-\tunsigned int\t\tid;\n--\ndrivers/block/zloop.c-155-\tbool\t\t\tdiscard_write_cache;\ndrivers/block/zloop.c:156:\tenum zloop_stor_elements stor_elements;\ndrivers/block/zloop.c-157-\n--\ndrivers/block/zloop.c-184-\ndrivers/block/zloop.c:185:\tstruct zloop_zone\tzones[] __counted_by(nr_zones);\ndrivers/block/zloop.c-186-};\ndrivers/block/zloop.c-187-\ndrivers/block/zloop.c:188:struct zloop_cmd {\ndrivers/block/zloop.c-189-\tstruct work_struct\twork;\n--\ndrivers/block/zloop.c-197-\ndrivers/block/zloop.c:198:static DEFINE_IDR(zloop_index_idr);\ndrivers/block/zloop.c:199:static DEFINE_MUTEX(zloop_ctl_mutex);\ndrivers/block/zloop.c-200-\ndrivers/block/zloop.c=201=static unsigned int rq_zone_no(struct request *rq)\ndrivers/block/zloop.c-202-{\ndrivers/block/zloop.c:203:\tstruct zloop_device *zlo = rq-\u003eq-\u003equeuedata;\ndrivers/block/zloop.c-204-\n--\ndrivers/block/zloop.c-213- */\ndrivers/block/zloop.c:214:static inline void zloop_lru_rotate_open_zone(struct zloop_device *zlo,\ndrivers/block/zloop.c:215:\t\t\t\t\t struct zloop_zone *zone)\ndrivers/block/zloop.c-216-{\n--\ndrivers/block/zloop.c-224-\ndrivers/block/zloop.c:225:static inline void zloop_lru_remove_open_zone(struct zloop_device *zlo,\ndrivers/block/zloop.c:226:\t\t\t\t\t struct zloop_zone *zone)\ndrivers/block/zloop.c-227-{\n--\ndrivers/block/zloop.c-236-\ndrivers/block/zloop.c:237:static inline bool zloop_can_open_zone(struct zloop_device *zlo)\ndrivers/block/zloop.c-238-{\n--\ndrivers/block/zloop.c-246- */\ndrivers/block/zloop.c:247:static bool zloop_close_imp_open_zone(struct zloop_device *zlo)\ndrivers/block/zloop.c-248-{\ndrivers/block/zloop.c:249:\tstruct zloop_zone *zone;\ndrivers/block/zloop.c-250-\n--\ndrivers/block/zloop.c-252-\ndrivers/block/zloop.c:253:\tif (zloop_can_open_zone(zlo))\ndrivers/block/zloop.c-254-\t\treturn true;\n--\ndrivers/block/zloop.c-267-\ndrivers/block/zloop.c:268:static bool zloop_open_closed_or_empty_zone(struct zloop_device *zlo,\ndrivers/block/zloop.c:269:\t\t\t\t\t struct zloop_zone *zone,\ndrivers/block/zloop.c-270-\t\t\t\t\t bool explicit)\n--\ndrivers/block/zloop.c-278-\t\t */\ndrivers/block/zloop.c:279:\t\tif (!zloop_can_open_zone(zlo))\ndrivers/block/zloop.c-280-\t\t\tgoto fail;\n--\ndrivers/block/zloop.c-286-\t\t */\ndrivers/block/zloop.c:287:\t\tif (!zloop_close_imp_open_zone(zlo))\ndrivers/block/zloop.c-288-\t\t\tgoto fail;\n--\ndrivers/block/zloop.c-305-\ndrivers/block/zloop.c:306:static bool zloop_do_open_zone(struct zloop_device *zlo,\ndrivers/block/zloop.c:307:\t\t\t struct zloop_zone *zone, bool explicit)\ndrivers/block/zloop.c-308-{\n--\ndrivers/block/zloop.c-313-\t\t\tzone-\u003econd = BLK_ZONE_COND_EXP_OPEN;\ndrivers/block/zloop.c:314:\t\tzloop_lru_rotate_open_zone(zlo, zone);\ndrivers/block/zloop.c-315-\t\treturn true;\n--\ndrivers/block/zloop.c-317-\tcase BLK_ZONE_COND_CLOSED:\ndrivers/block/zloop.c:318:\t\treturn zloop_open_closed_or_empty_zone(zlo, zone, explicit);\ndrivers/block/zloop.c-319-\tdefault:\n--\ndrivers/block/zloop.c-323-\ndrivers/block/zloop.c:324:static void zloop_set_zone_cond(struct zloop_device *zlo,\ndrivers/block/zloop.c:325:\t\t\t\tstruct zloop_zone *zone,\ndrivers/block/zloop.c-326-\t\t\t\tenum blk_zone_cond cond)\n--\ndrivers/block/zloop.c-329-\ndrivers/block/zloop.c:330:\tzloop_lru_remove_open_zone(zlo, zone);\ndrivers/block/zloop.c-331-\tzone-\u003econd = cond;\n--\ndrivers/block/zloop.c-337-\ndrivers/block/zloop.c:338:static inline void zloop_set_zone_full(struct zloop_device *zlo,\ndrivers/block/zloop.c:339:\t\t\t\t struct zloop_zone *zone)\ndrivers/block/zloop.c-340-{\ndrivers/block/zloop.c:341:\tzloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_FULL);\ndrivers/block/zloop.c-342-}\ndrivers/block/zloop.c-343-\ndrivers/block/zloop.c:344:static inline void zloop_set_zone_empty(struct zloop_device *zlo,\ndrivers/block/zloop.c:345:\t\t\t\t\tstruct zloop_zone *zone)\ndrivers/block/zloop.c-346-{\ndrivers/block/zloop.c:347:\tzloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_EMPTY);\ndrivers/block/zloop.c-348-}\ndrivers/block/zloop.c-349-\ndrivers/block/zloop.c:350:static bool zloop_zone_is_offline_or_readonly(struct zloop_device *zlo,\ndrivers/block/zloop.c:351:\t\t\t\t\t struct zloop_zone *zone)\ndrivers/block/zloop.c-352-{\n--\ndrivers/block/zloop.c-362-\ndrivers/block/zloop.c:363:static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)\ndrivers/block/zloop.c-364-{\ndrivers/block/zloop.c:365:\tstruct zloop_zone *zone = \u0026zlo-\u003ezones[zone_no];\ndrivers/block/zloop.c-366-\tstruct kstat stat;\n--\ndrivers/block/zloop.c-375-\t */\ndrivers/block/zloop.c:376:\tif (zloop_zone_is_offline_or_readonly(zlo, zone))\ndrivers/block/zloop.c-377-\t\treturn 0;\n--\ndrivers/block/zloop.c-401-\tif (!file_sectors) {\ndrivers/block/zloop.c:402:\t\tzloop_set_zone_empty(zlo, zone);\ndrivers/block/zloop.c-403-\t} else if (file_sectors == zlo-\u003ezone_capacity) {\ndrivers/block/zloop.c:404:\t\tzloop_set_zone_full(zlo, zone);\ndrivers/block/zloop.c-405-\t} else {\n--\ndrivers/block/zloop.c-415-\ndrivers/block/zloop.c:416:static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no)\ndrivers/block/zloop.c-417-{\ndrivers/block/zloop.c:418:\tstruct zloop_zone *zone = \u0026zlo-\u003ezones[zone_no];\ndrivers/block/zloop.c-419-\tint ret = 0;\n--\ndrivers/block/zloop.c-425-\ndrivers/block/zloop.c:426:\tif (zloop_zone_is_offline_or_readonly(zlo, zone)) {\ndrivers/block/zloop.c-427-\t\tret = -EIO;\n--\ndrivers/block/zloop.c-431-\tif (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags)) {\ndrivers/block/zloop.c:432:\t\tret = zloop_update_seq_zone(zlo, zone_no);\ndrivers/block/zloop.c-433-\t\tif (ret)\n--\ndrivers/block/zloop.c-436-\ndrivers/block/zloop.c:437:\tif (!zloop_do_open_zone(zlo, zone, true))\ndrivers/block/zloop.c-438-\t\tret = -EIO;\n--\ndrivers/block/zloop.c-445-\ndrivers/block/zloop.c:446:static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no)\ndrivers/block/zloop.c-447-{\ndrivers/block/zloop.c:448:\tstruct zloop_zone *zone = \u0026zlo-\u003ezones[zone_no];\ndrivers/block/zloop.c-449-\tint ret = 0;\n--\ndrivers/block/zloop.c-455-\ndrivers/block/zloop.c:456:\tif (zloop_zone_is_offline_or_readonly(zlo, zone)) {\ndrivers/block/zloop.c-457-\t\tret = -EIO;\n--\ndrivers/block/zloop.c-461-\tif (test_and_clear_bit(ZLOOP_ZONE_SEQ_ERROR, \u0026zone-\u003eflags)) {\ndrivers/block/zloop.c:462:\t\tret = zloop_update_seq_zone(zlo, zone_no);\ndrivers/block/zloop.c-463-\t\tif (ret)\n--\ndrivers/block/zloop.c-472-\t\tspin_lock(\u0026zone-\u003ewp_lock);\ndrivers/block/zloop.c:473:\t\tzloop_lru_remove_open_zone(zlo, zone);\ndrivers/block/zloop.c-474-\t\tif (zone-\u003ewp == zone-\u003estart)\n--\ndrivers/block/zloop.c-492-\ndrivers/block/zloop.c:493:static int zloop_do_reset_zone(struct zloop_device *zlo,\ndrivers/block/zloop.c:494:\t\t\t struct zloop_zone *zone)\ndrivers/block/zloop.c-495-{\n--\ndrivers/block/zloop.c-499-\tspin_lock(\u0026zone-\u003ewp_lock);\n\n"
} |
| 5/2 | 2026/10/12 13:43 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 35721 output: 34 thoughts: 37 Thoughts: Okay, let's dive into this. Here's how I'd approach that kernel configuration question, assuming I'm already pretty familiar with the block device subsystems: **Investigating `CONFIG_BLK_DEV_ZONED_LOOP` Dependencies** Right, so someone's asking about `CONFIG_BLK_DEV_ZONED_LOOP`. I need to figure out what that thing depends on. My first instinct, and the quickest way to find that information, is to go directly to the source. Let's start by checking the `Kconfig` file, specifically the one for block devices – `drivers/block/Kconfig`. I already know my way around the kernel source, and I'm pretty certain I'll find the relevant configuration options within that file. I'd expect configuration options to be fairly well-organized, and `CONFIG_BLK_DEV_ZONED_LOOP` probably has its definition somewhere near line 356. I'll get that file open and take a look, assuming that's the more common and faster method to locate the specific line in question. I need to figure out what other things need to be enabled before I can build a kernel with that option. This is how I'll understand if it requires another piece of hardware, or depends on a particular filesystem, or something else. I want to build a better overall picture of the system. |
| 6/2 | 2026/10/12 13:43 | tool | read-file |
0mArgs:{
"File": "drivers/block/Kconfig",
"FirstLine": 350,
"LineCount": 40
}Results: {
"Contents": " 350:\t\t Say N if you don't want to support legacy command opcode. It is\n 351:\t\t suggested to enable N if your application(ublk server) switches to\n 352:\t\t ioctl command encoding.\n 353:\t\n 354:\tsource \"drivers/block/rnbd/Kconfig\"\n 355:\t\n 356:\tconfig BLK_DEV_ZONED_LOOP\n 357:\t\ttristate \"Zoned loopback device support\"\n 358:\t\tdepends on BLK_DEV_ZONED\n 359:\t\thelp\n 360:\t\t Saying Y here will allow you to use create a zoned block device using\n 361:\t\t regular files for zones (one file per zones). This is useful to test\n 362:\t\t file systems, device mapper and applications that support zoned block\n 363:\t\t devices. To create a zoned loop device, no user utility is needed, a\n 364:\t\t zoned loop device can be created (or re-started) using a command\n 365:\t\t like:\n 366:\t\n 367:\t\t echo \"add id=0,zone_size_mb=256,capacity_mb=16384,conv_zones=11\" \u003e \\\n 368:\t\t\t/dev/zloop-control\n 369:\t\n 370:\t\t See Documentation/admin-guide/blockdev/zoned_loop.rst for usage\n 371:\t\t details.\n 372:\t\n 373:\t\t If unsure, say N.\n 374:\t\n 375:\tendif # BLK_DEV\n"
} |
| 7/2 | 2026/10/12 13:43 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36524 output: 32 thoughts: 43 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience and presented in the first person: **Investigating the Dependencies of `BLK_DEV_ZONED_LOOP`** Alright, so it's clear that `BLK_DEV_ZONED_LOOP` has a dependency on `BLK_DEV_ZONED`. No surprises there, but it's always good to verify these things directly. My next step is to head over to `block/Kconfig`. I need to confirm the configuration logic in `BLK_DEV_ZONED` – specifically, what triggers its activation. I'm looking for the conditions that make `BLK_DEV_ZONED` available, and how that impacts `BLK_DEV_ZONED_LOOP`. I want to see if there are any subtle interactions I might be missing, or if there's any potential for conflict or unexpected behavior, especially in less-common configurations. Ultimately, I'm trying to ensure the intended hierarchy and dependencies are clean and robust. |
| 8/2 | 2026/10/12 13:43 | tool | grepper |
0mArgs:{
"Expression": "config BLK_DEV_ZONED",
"PathPrefixes": [
"block/"
]
}Results: {
"Output": "block/Kconfig=77=config BLK_DEV_WRITE_MOUNTED\n--\nblock/Kconfig-96-\nblock/Kconfig:97:config BLK_DEV_ZONED\nblock/Kconfig-98-\tbool \"Zoned block device support\"\n"
} |
| 9/2 | 2026/10/12 13:43 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 36897 output: 31 thoughts: 16 Thoughts: Okay, here's my interpretation of what's going on, formatted as you requested: **Navigating the Configuration Landscape** Alright, let's dive in. My immediate focus is on `block/Kconfig`, specifically around line 97. I'm guessing that this section likely houses configuration options related to the block device subsystem. Given my expertise, I'm expecting to see definitions for features, drivers, or behaviors that can be enabled or disabled at build time. Perhaps there are settings controlling things like caching, I/O schedulers, or specific device support. Knowing the line number is important because I'm looking for a specific section. This is not for a general overview, I'm interested in finding a specific configuration option and what its dependencies or implications are. The goal is to either modify the functionality, or understand the system fully. |
| 10/2 | 2026/10/12 13:43 | tool | read-file |
0mArgs:{
"File": "block/Kconfig",
"FirstLine": 95,
"LineCount": 25
}Results: {
"Contents": " 95:\t\twith the bdev_allow_write_mounted boot option.\n 96:\t\n 97:\tconfig BLK_DEV_ZONED\n 98:\t\tbool \"Zoned block device support\"\n 99:\t\thelp\n 100:\t\tBlock layer zoned block device support. This option enables\n 101:\t\tsupport for ZAC/ZBC/ZNS host-managed and host-aware zoned block\n 102:\t\tdevices.\n 103:\t\n 104:\t\tSay yes here if you have a ZAC, ZBC, or ZNS storage device.\n 105:\t\n 106:\tconfig BLK_DEV_THROTTLING\n 107:\t\tbool \"Block layer bio throttling support\"\n 108:\t\tdepends on BLK_CGROUP\n 109:\t\tselect BLK_CGROUP_RWSTAT\n 110:\t\thelp\n 111:\t\tBlock layer bio throttling support. It can be used to limit\n 112:\t\tthe IO rate to a device. IO rate policies are per cgroup and\n 113:\t\tone needs to mount and use blkio cgroup controller for creating\n 114:\t\tcgroups and specifying per device IO rate policies.\n 115:\t\n 116:\t\tSee Documentation/admin-guide/cgroup-v1/blkio-controller.rst for more information.\n 117:\t\n 118:\tconfig BLK_WBT\n 119:\t\tbool \"Enable support for block device writeback throttling\"\n"
} |
| 11/2 | 2026/10/12 13:43 | llm | patch-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37514 output: 36 thoughts: 37 Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue: **SCSI ZBC Configuration Review** Alright, let's see... the block layer has a `BLK_DEV_ZONED` boolean, that's step one. Now, to understand the SCSI side of things, I need to dive into `drivers/scsi/Kconfig`. That's where the rubber meets the road, the real configuration options for ZBC devices will be exposed. I expect to find dependencies, device type specifics, and potentially some advanced configurations for zoned block devices under the SCSI subsystem. Time to dig in and see what's what. |
| 12/2 | 2026/10/12 13:44 | tool | grepper |
0mArgs:{
"Expression": "sd_zbc|SCSI_ZBC",
"PathPrefixes": [
"drivers/scsi/"
]
}Results: {
"Output": "\nFull output is too long, showing 200 out of 271 lines.\nUse more precise expression or PathPrefixes if possible.\n\nNumber of matching lines per file (4 files in total):\ndrivers/scsi/Makefile:2\ndrivers/scsi/sd.c:10\ndrivers/scsi/sd.h:11\ndrivers/scsi/sd_zbc.c:49\n\ndrivers/scsi/Makefile=36=CONTEXT_ANALYSIS_sd_dif.o := y\ndrivers/scsi/Makefile:37:CONTEXT_ANALYSIS_sd_zbc.o := y\ndrivers/scsi/Makefile-38-CONTEXT_ANALYSIS_sr.o := y\n--\ndrivers/scsi/Makefile=199=sd_mod-$(CONFIG_BLK_DEV_INTEGRITY) += sd_dif.o\ndrivers/scsi/Makefile:200:sd_mod-$(CONFIG_BLK_DEV_ZONED) += sd_zbc.o\ndrivers/scsi/Makefile-201-\n--\ndrivers/scsi/sd.c=1543=static blk_status_t sd_init_command(struct scsi_cmnd *cmd)\n--\ndrivers/scsi/sd.c-1568-\tcase REQ_OP_ZONE_RESET:\ndrivers/scsi/sd.c:1569:\t\treturn sd_zbc_setup_zone_mgmt_cmnd(cmd, ZO_RESET_WRITE_POINTER,\ndrivers/scsi/sd.c-1570-\t\t\t\t\t\t false);\ndrivers/scsi/sd.c-1571-\tcase REQ_OP_ZONE_RESET_ALL:\ndrivers/scsi/sd.c:1572:\t\treturn sd_zbc_setup_zone_mgmt_cmnd(cmd, ZO_RESET_WRITE_POINTER,\ndrivers/scsi/sd.c-1573-\t\t\t\t\t\t true);\ndrivers/scsi/sd.c-1574-\tcase REQ_OP_ZONE_OPEN:\ndrivers/scsi/sd.c:1575:\t\treturn sd_zbc_setup_zone_mgmt_cmnd(cmd, ZO_OPEN_ZONE, false);\ndrivers/scsi/sd.c-1576-\tcase REQ_OP_ZONE_CLOSE:\ndrivers/scsi/sd.c:1577:\t\treturn sd_zbc_setup_zone_mgmt_cmnd(cmd, ZO_CLOSE_ZONE, false);\ndrivers/scsi/sd.c-1578-\tcase REQ_OP_ZONE_FINISH:\ndrivers/scsi/sd.c:1579:\t\treturn sd_zbc_setup_zone_mgmt_cmnd(cmd, ZO_FINISH_ZONE, false);\ndrivers/scsi/sd.c-1580-\tdefault:\n--\ndrivers/scsi/sd.c=2375=static int sd_done(struct scsi_cmnd *SCpnt)\n--\ndrivers/scsi/sd.c-2480-\tif (sdkp-\u003edevice-\u003etype == TYPE_ZBC)\ndrivers/scsi/sd.c:2481:\t\tgood_bytes = sd_zbc_complete(SCpnt, good_bytes, \u0026sshdr);\ndrivers/scsi/sd.c-2482-\n--\ndrivers/scsi/sd.c=3779=static void sd_revalidate_disk(struct gendisk *disk)\n--\ndrivers/scsi/sd.c-3836-\t\t\tsd_read_block_characteristics(sdkp, lim);\ndrivers/scsi/sd.c:3837:\t\t\tsd_zbc_read_zones(sdkp, lim, buffer);\ndrivers/scsi/sd.c-3838-\t\t}\n--\ndrivers/scsi/sd.c-3903-\t */\ndrivers/scsi/sd.c:3904:\tif (sd_zbc_revalidate_zones(sdkp))\ndrivers/scsi/sd.c-3905-\t\tset_capacity_and_notify(disk, 0);\n--\ndrivers/scsi/sd.c=3933=static const struct block_device_operations sd_fops = {\n--\ndrivers/scsi/sd.c-3941-\t.unlock_native_capacity\t= sd_unlock_native_capacity,\ndrivers/scsi/sd.c:3942:\t.report_zones\t\t= sd_zbc_report_zones,\ndrivers/scsi/sd.c-3943-\t.get_unique_id\t\t= sd_get_unique_id,\n--\ndrivers/scsi/sd.c-3946-#ifdef CONFIG_BLK_DEV_ZONED\ndrivers/scsi/sd.c:3947:\t.se_ops\t\t\t= \u0026sd_zbc_se_ops,\ndrivers/scsi/sd.c-3948-#endif\n--\ndrivers/scsi/sd.h=232=void sd_dif_config_host(struct scsi_disk *sdkp, struct queue_limits *lim);\n--\ndrivers/scsi/sd.h-235-\ndrivers/scsi/sd.h:236:int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim,\ndrivers/scsi/sd.h-237-\t\tu8 buf[SD_BUF_SIZE]);\ndrivers/scsi/sd.h:238:int sd_zbc_revalidate_zones(struct scsi_disk *sdkp);\ndrivers/scsi/sd.h:239:blk_status_t sd_zbc_setup_zone_mgmt_cmnd(struct scsi_cmnd *cmd,\ndrivers/scsi/sd.h-240-\t\t\t\t\t unsigned char op, bool all);\ndrivers/scsi/sd.h:241:unsigned int sd_zbc_complete(struct scsi_cmnd *cmd, unsigned int good_bytes,\ndrivers/scsi/sd.h-242-\t\t\t struct scsi_sense_hdr *sshdr);\ndrivers/scsi/sd.h:243:int sd_zbc_report_zones(struct gendisk *disk, sector_t sector,\ndrivers/scsi/sd.h-244-\t\tunsigned int nr_zones, struct blk_report_zones_args *args);\ndrivers/scsi/sd.h-245-\ndrivers/scsi/sd.h:246:extern const struct blk_se_ops sd_zbc_se_ops;\ndrivers/scsi/sd.h-247-\n--\ndrivers/scsi/sd.h-249-\ndrivers/scsi/sd.h:250:static inline int sd_zbc_read_zones(struct scsi_disk *sdkp,\ndrivers/scsi/sd.h-251-\t\tstruct queue_limits *lim, u8 buf[SD_BUF_SIZE])\n--\ndrivers/scsi/sd.h-255-\ndrivers/scsi/sd.h:256:static inline int sd_zbc_revalidate_zones(struct scsi_disk *sdkp)\ndrivers/scsi/sd.h-257-{\n--\ndrivers/scsi/sd.h-260-\ndrivers/scsi/sd.h:261:static inline blk_status_t sd_zbc_setup_zone_mgmt_cmnd(struct scsi_cmnd *cmd,\ndrivers/scsi/sd.h-262-\t\t\t\t\t\t unsigned char op,\n--\ndrivers/scsi/sd.h-267-\ndrivers/scsi/sd.h:268:static inline unsigned int sd_zbc_complete(struct scsi_cmnd *cmd,\ndrivers/scsi/sd.h-269-\t\t\tunsigned int good_bytes, struct scsi_sense_hdr *sshdr)\n--\ndrivers/scsi/sd.h-273-\ndrivers/scsi/sd.h:274:#define sd_zbc_report_zones NULL\ndrivers/scsi/sd.h-275-\n--\ndrivers/scsi/sd_zbc.c-26-/* Whether or not a SCSI zone descriptor describes a gap zone. */\ndrivers/scsi/sd_zbc.c:27:static bool sd_zbc_is_gap_zone(const u8 buf[64])\ndrivers/scsi/sd_zbc.c-28-{\n--\ndrivers/scsi/sd_zbc.c-32-/**\ndrivers/scsi/sd_zbc.c:33: * sd_zbc_parse_report - Parse a SCSI zone descriptor\ndrivers/scsi/sd_zbc.c-34- * @sdkp: SCSI disk pointer.\n--\ndrivers/scsi/sd_zbc.c-36- * @idx: Index of the zone relative to the first zone reported by the current\ndrivers/scsi/sd_zbc.c:37: *\tsd_zbc_report_zones() call.\ndrivers/scsi/sd_zbc.c-38- * @args: report zones arguments (callback, etc)\n--\ndrivers/scsi/sd_zbc.c-44- */\ndrivers/scsi/sd_zbc.c:45:static int sd_zbc_parse_report(struct scsi_disk *sdkp, const u8 buf[64],\ndrivers/scsi/sd_zbc.c-46-\t\t\tunsigned int idx, struct blk_report_zones_args *args)\n--\ndrivers/scsi/sd_zbc.c-51-\ndrivers/scsi/sd_zbc.c:52:\tif (WARN_ON_ONCE(sd_zbc_is_gap_zone(buf)))\ndrivers/scsi/sd_zbc.c-53-\t\treturn -EINVAL;\n--\ndrivers/scsi/sd_zbc.c-91-/**\ndrivers/scsi/sd_zbc.c:92: * sd_zbc_do_report_zones - Issue a REPORT ZONES scsi command.\ndrivers/scsi/sd_zbc.c-93- * @sdkp: The target disk\n--\ndrivers/scsi/sd_zbc.c-104- */\ndrivers/scsi/sd_zbc.c:105:static int sd_zbc_do_report_zones(struct scsi_disk *sdkp, unsigned char *buf,\ndrivers/scsi/sd_zbc.c-106-\t\t\t\t unsigned int buflen, sector_t lba,\n--\ndrivers/scsi/sd_zbc.c-149-/**\ndrivers/scsi/sd_zbc.c:150: * sd_zbc_alloc_report_buffer() - Allocate a buffer for report zones reply.\ndrivers/scsi/sd_zbc.c-151- * @sdkp: The target disk\n--\ndrivers/scsi/sd_zbc.c-161- */\ndrivers/scsi/sd_zbc.c:162:static void *sd_zbc_alloc_report_buffer(struct scsi_disk *sdkp,\ndrivers/scsi/sd_zbc.c-163-\t\t\t\t\tunsigned int nr_zones, size_t *buflen)\n--\ndrivers/scsi/sd_zbc.c-200-/**\ndrivers/scsi/sd_zbc.c:201: * sd_zbc_zone_sectors - Get the device zone size in number of 512B sectors.\ndrivers/scsi/sd_zbc.c-202- * @sdkp: The target disk\ndrivers/scsi/sd_zbc.c-203- */\ndrivers/scsi/sd_zbc.c:204:static inline sector_t sd_zbc_zone_sectors(struct scsi_disk *sdkp)\ndrivers/scsi/sd_zbc.c-205-{\n--\ndrivers/scsi/sd_zbc.c-209-/**\ndrivers/scsi/sd_zbc.c:210: * sd_zbc_report_zones - SCSI .report_zones() callback.\ndrivers/scsi/sd_zbc.c-211- * @disk: Disk to report zones for.\n--\ndrivers/scsi/sd_zbc.c-218- */\ndrivers/scsi/sd_zbc.c:219:int sd_zbc_report_zones(struct gendisk *disk, sector_t sector,\ndrivers/scsi/sd_zbc.c-220-\t\t\tunsigned int nr_zones,\n--\ndrivers/scsi/sd_zbc.c-239-\ndrivers/scsi/sd_zbc.c:240:\tbuf = sd_zbc_alloc_report_buffer(sdkp, nr_zones, \u0026buflen);\ndrivers/scsi/sd_zbc.c-241-\tif (!buf)\n--\ndrivers/scsi/sd_zbc.c-244-\twhile (zone_idx \u003c nr_zones \u0026\u0026 lba \u003c sdkp-\u003ecapacity) {\ndrivers/scsi/sd_zbc.c:245:\t\tret = sd_zbc_do_report_zones(sdkp, buf, buflen, lba, true);\ndrivers/scsi/sd_zbc.c-246-\t\tif (ret)\n--\ndrivers/scsi/sd_zbc.c-269-\t\t\tlba = start_lba + zone_length;\ndrivers/scsi/sd_zbc.c:270:\t\t\tif (sd_zbc_is_gap_zone(\u0026buf[offset])) {\ndrivers/scsi/sd_zbc.c-271-\t\t\t\tif (sdkp-\u003ezone_starting_lba_gran)\n--\ndrivers/scsi/sd_zbc.c-278-\ndrivers/scsi/sd_zbc.c:279:\t\t\tret = sd_zbc_parse_report(sdkp, buf + offset, zone_idx,\ndrivers/scsi/sd_zbc.c-280-\t\t\t\t\t\t args);\n--\ndrivers/scsi/sd_zbc.c-293-\ndrivers/scsi/sd_zbc.c:294:static blk_status_t sd_zbc_cmnd_checks(struct scsi_cmnd *cmd)\ndrivers/scsi/sd_zbc.c-295-{\n--\ndrivers/scsi/sd_zbc.c-306-\ndrivers/scsi/sd_zbc.c:307:\tif (sector \u0026 (sd_zbc_zone_sectors(sdkp) - 1))\ndrivers/scsi/sd_zbc.c-308-\t\t/* Unaligned request */\n--\ndrivers/scsi/sd_zbc.c-314-/**\ndrivers/scsi/sd_zbc.c:315: * sd_zbc_setup_zone_mgmt_cmnd - Prepare a zone ZBC_OUT command. The operations\ndrivers/scsi/sd_zbc.c-316- *\t\t\tcan be RESET WRITE POINTER, OPEN, CLOSE or FINISH.\n--\ndrivers/scsi/sd_zbc.c-323- */\ndrivers/scsi/sd_zbc.c:324:blk_status_t sd_zbc_setup_zone_mgmt_cmnd(struct scsi_cmnd *cmd,\ndrivers/scsi/sd_zbc.c-325-\t\t\t\t\t unsigned char op, bool all)\n--\ndrivers/scsi/sd_zbc.c-332-\ndrivers/scsi/sd_zbc.c:333:\tret = sd_zbc_cmnd_checks(cmd);\ndrivers/scsi/sd_zbc.c-334-\tif (ret != BLK_STS_OK)\n--\ndrivers/scsi/sd_zbc.c-354-/**\ndrivers/scsi/sd_zbc.c:355: * sd_zbc_complete - ZBC command post processing.\ndrivers/scsi/sd_zbc.c-356- * @cmd: Completed command\n--\ndrivers/scsi/sd_zbc.c-362- */\ndrivers/scsi/sd_zbc.c:363:unsigned int sd_zbc_complete(struct scsi_cmnd *cmd, unsigned int good_bytes,\ndrivers/scsi/sd_zbc.c-364-\t\t struct scsi_sense_hdr *sshdr)\n--\ndrivers/scsi/sd_zbc.c-384-/**\ndrivers/scsi/sd_zbc.c:385: * sd_zbc_check_zoned_characteristics - Check zoned block device characteristics\ndrivers/scsi/sd_zbc.c-386- * @sdkp: Target disk\n--\ndrivers/scsi/sd_zbc.c-390- */\ndrivers/scsi/sd_zbc.c:391:static int sd_zbc_check_zoned_characteristics(struct scsi_disk *sdkp,\ndrivers/scsi/sd_zbc.c-392-\t\t\t\t\t unsigned char *buf)\n--\ndrivers/scsi/sd_zbc.c-455-/**\ndrivers/scsi/sd_zbc.c:456: * sd_zbc_check_capacity - Check the device capacity\ndrivers/scsi/sd_zbc.c-457- * @sdkp: Target disk\n--\ndrivers/scsi/sd_zbc.c-466- */\ndrivers/scsi/sd_zbc.c:467:static int sd_zbc_check_capacity(struct scsi_disk *sdkp, unsigned char *buf,\ndrivers/scsi/sd_zbc.c-468-\t\t\t\t u32 *zblocks)\n--\ndrivers/scsi/sd_zbc.c-475-\t/* Do a report zone to get max_lba and the size of the first zone */\ndrivers/scsi/sd_zbc.c:476:\tret = sd_zbc_do_report_zones(sdkp, buf, SD_BUF_SIZE, 0, false);\ndrivers/scsi/sd_zbc.c-477-\tif (ret)\n\n"
} |
| 13/2 | 2026/10/12 13:44 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 42209 output: 27 thoughts: 2029 Thoughts: **Analysis of a Zoned Block Device Patch for Fuzzing** Alright, this is quite interesting. This patch introduces significant modifications to the block layer, specifically around zoned block devices and storage element management. I see several key areas of change. Firstly, the code adds functions and ioctls to manage storage elements directly. The `block/blk-zoned.c` file is heavily involved, introducing `disk_zone_is_offline()` and refactoring zone revalidation with mutex locking around storage element operations, suggesting critical region protection. The new ioctls (`BLKGETNRSTORELEMS`, `BLKREPORTSTORELEMS`, etc.) provide user-space access for reporting, removing, and restoring storage elements. The `zloop` driver in `drivers/block/zloop.c` is particularly compelling. It *fully* emulates storage elements, and the fact that it's a software device backed by files is a huge win. The ability to create, degrade, remove, and restore elements, plus the support for updating zone conditions (OFFLINE, READONLY), makes this a prime target for fuzzing. The `zloop` driver also provides an entry point for user space access through `/dev/zloop-control`. The patch also integrates storage element operations with SCSI ZBC in `drivers/scsi/sd.c` and `drivers/scsi/sd_zbc.c`, which makes things even more interesting in terms of a broader attack surface. `sd_zbc_se_ops` and `disk_zone_is_offline` are now part of the SCSI flow. The fact that `zloop` is a software device means that this code *is* absolutely reachable in standard virtualized environments like QEMU and GCE, which is ideal for a fuzzing setup. And, the ioctl interface on any zoned device (`BLKGETNRSTORELEMS`, `BLKREPORTSTORELEMS`, `BLKREMOVESTORELEM`, `BLKRESTORESTORELEMS`) is exposed via `blkdev_common_ioctl` on block devices. Diving into the core code, particularly `bdev_remove_storage_element` and `bdev_restore_storage_elements`, reveals critical points. The locking with `disk->zone_revalidate_mutex`, the queue freezing, the use of `msleep` in `disk_wait_for_se_mgmt_completion`, and the interactions with workqueues in `zloop_remove_element_work` show that we're dealing with potentially complex concurrency scenarios. This area is definitely worth a close look and careful fuzzing. The fact that the code freezes and unfreezes queues to flush outstanding commands during storage element removal/restoration is another significant point, which could introduce subtle race conditions. The truncate and flush operations, combined with invalidation and locking, also contribute to the complexity. Now, let's nail down the configuration requirements. We need `BLK_DEV_ZONED` and `BLK_DEV_ZONED_LOOP` to be enabled. I see that `sd_zbc.c` is compiled when `CONFIG_BLK_DEV_ZONED` is enabled and `sd.c` has `#ifdef CONFIG_BLK_DEV_ZONED`. So we don't need `SCSI_ZBC` to be explicitly enabled. So this is going on the *WorthFuzzing* list! |
| 14/2 | 2026/10/12 13:45 | tool | grepper |
0mArgs:{
"Expression": "blk-zoned",
"PathPrefixes": [
"block/Makefile"
]
}Results: {
"Output": "block/Makefile=31=obj-$(CONFIG_BLK_DEV_INTEGRITY) += bio-integrity.o blk-integrity.o t10-pi.o \\\nblock/Makefile-32-\t\t\t\t bio-integrity-auto.o bio-integrity-fs.o\nblock/Makefile:33:obj-$(CONFIG_BLK_DEV_ZONED)\t+= blk-zoned.o\nblock/Makefile-34-obj-$(CONFIG_BLK_WBT)\t\t+= blk-wbt.o\n"
} |
| 15/2 | 2026/10/12 13:45 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 45120 output: 35 thoughts: 109 Thoughts: **Zoned Block Device Configuration Deep Dive** Okay, so I'm looking at how these zoned block device modules are configured, specifically `blk-zoned.o`, `zloop.o`, and `sd_zbc.o`. It's clear that `blk-zoned.o` gets built when `CONFIG_BLK_DEV_ZONED` is enabled – that's straightforward enough. Then, `zloop.o` is also under its own configuration option, `CONFIG_BLK_DEV_ZONED_LOOP`. Interesting. And finally, `sd_zbc.o`, is also conditionally compiled under `CONFIG_BLK_DEV_ZONED`, but within the `sd_mod` framework which itself is governed by `CONFIG_BLK_DEV_SD`. It's likely related to ZBC-specific features for SCSI devices. Right, so to understand the dependencies a bit better, I need to check the Kconfig file for `BLK_DEV_SD` in `drivers/scsi/`. That'll give me a full picture of the configuration options and their interactions. I'm expecting to find out more about how `CONFIG_BLK_DEV_ZONED` is linked to the SCSI subsystem and how the zone-specific features relate to the standard SD module. Let's see what we can dig up. |
| 16/2 | 2026/10/12 13:45 | tool | grepper |
0mArgs:{
"Expression": "config BLK_DEV_SD",
"PathPrefixes": [
"drivers/scsi/Kconfig"
]
}Results: {
"Output": "drivers/scsi/Kconfig=79=comment \"SCSI support type (disk, tape, CD-ROM)\"\n--\ndrivers/scsi/Kconfig-81-\ndrivers/scsi/Kconfig:82:config BLK_DEV_SD\ndrivers/scsi/Kconfig-83-\ttristate \"SCSI disk support\"\n"
} |
| 17/2 | 2026/10/12 13:45 | llm | patch-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 45648 output: 337 thoughts: 2352 Thoughts: **Focus Symbol Determination for Zoned Block Device Feature Fuzzing** Alright, let's break this down systematically. My primary goal here is to identify the crucial configurations and functions (FocusSymbols) that will effectively guide fuzzing efforts for the zoned block device modifications. First, I need to make sure the core configurations are enabled. From the provided code, it's clear that `BLK_DEV_ZONED`, `BLK_DEV_ZONED_LOOP`, and `BLK_DEV_SD` are the linchpins. The first enables the overall zoned block device functionality and associated ioctls; the second enables the loop driver emulation aspects; and `BLK_DEV_SD` brings in the SCSI ZBC changes. This covers the main surface. Now, let's dive into the code changes to determine the feature-specific entry points and logic. I've gone through the modified files meticulously, focusing on `#ifdef` directives and `CONFIG_` checks within the diff. Looking at `block/blk.h`, the presence of `#ifdef CONFIG_BLK_DEV_ZONED` confirms that the zone-specific storage management is dependent on the `BLK_DEV_ZONED` config. The changes in `drivers/scsi/sd.c` and `drivers/scsi/sd.h`, particularly the `#ifdef CONFIG_BLK_DEV_ZONED` guarding the inclusion of the `sd_zbc_se_ops`, highlight that the SCSI ZBC support is also tied to `BLK_DEV_ZONED`. With the configurations identified, I can now carefully examine the functions to understand the scope and the feature. I am going to list the non-static and exported functions, as well as critical entry points. I can see the following functions in `block/blk-zoned.c` as key: * `disk_zone_is_offline` * `blk_revalidate_disk_zones` * `bdev_report_storage_elements` * `bdev_remove_storage_element` * `bdev_restore_storage_elements` * `disk_init_storage_element_mgmt` * `blkdev_zone_storage_elements_ioctl` I'll skip `blkdev_common_ioctl` as it's a general entrypoint and avoid those. Then, there's `drivers/block/zloop.c`: * `zloop_report_elements` * `zloop_remove_element` * `zloop_restore_elements` * `zloop_degrade_element` * `zloop_ctl_degrade_element` In `drivers/scsi/sd_zbc.c`: * `sd_zbc_report_storage_elements` * `sd_zbc_remove_storage_element` * `sd_zbc_restore_storage_elements` I need to confirm if these functions actually have symbols available during kernel compilation with coverage enabled. From my previous experience, non-static, unless inlined functions, have symbols in KCOV. The critical non-static ones are `blkdev_zone_storage_elements_ioctl`, `bdev_report_storage_elements`, `bdev_remove_storage_element`, `bdev_restore_storage_elements`, `disk_init_storage_element_mgmt`, `disk_zone_is_offline`, and `blk_revalidate_disk_zones`, `zloop_report_elements`, `zloop_remove_element`, `zloop_restore_elements`, `zloop_degrade_element`, `zloop_ctl_degrade_element` and the `sd_zbc` functions. I need to filter those that are truly specific to the new features. None of these functions are ubiquitous. They are all centered around zoned block device storage element management, its emulation, and related SCSI ZBC aspects. So, I'm defining the FocusSymbols as: * `blkdev_zone_storage_elements_ioctl` * `bdev_report_storage_elements` * `bdev_remove_storage_element` * `bdev_restore_storage_elements` * `disk_zone_is_offline` * `zloop_report_elements` * `zloop_remove_element` * `zloop_restore_elements` * `zloop_degrade_element` * `zloop_ctl_degrade_element` * `sd_zbc_report_storage_elements` * `sd_zbc_remove_storage |
| 18/2 | 2026/10/12 13:46 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BLK_DEV_ZONED",
"BLK_DEV_ZONED_LOOP",
"BLK_DEV_SD"
],
"FocusSymbols": [
"blkdev_zone_storage_elements_ioctl",
"bdev_report_storage_elements",
"bdev_remove_storage_element",
"bdev_restore_storage_elements",
"disk_zone_is_offline",
"zloop_report_elements",
"zloop_remove_element",
"zloop_restore_elements",
"zloop_degrade_element",
"zloop_ctl_degrade_element",
"sd_zbc_report_storage_elements",
"sd_zbc_remove_storage_element",
"sd_zbc_restore_storage_elements"
],
"Reasoning": "The patch series introduces storage element (depopulation/repopulation) management for zoned block devices. It adds new UAPI ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) handled in the block layer, implements storage element emulation and control operations (degrade_element) in the virtual zoned loop driver (zloop), and implements SCSI ZBC storage element operations. The modified code introduces reachable ioctl paths, device state transitions, queue freezing/unfreezing, delayed work handling, and error checking, making it an excellent target for fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BLK_DEV_ZONED",
"BLK_DEV_ZONED_LOOP",
"BLK_DEV_SD"
],
"FocusSymbols": [
"blkdev_zone_storage_elements_ioctl",
"bdev_report_storage_elements",
"bdev_remove_storage_element",
"bdev_restore_storage_elements",
"disk_zone_is_offline",
"zloop_report_elements",
"zloop_remove_element",
"zloop_restore_elements",
"zloop_degrade_element",
"zloop_ctl_degrade_element",
"sd_zbc_report_storage_elements",
"sd_zbc_remove_storage_element",
"sd_zbc_restore_storage_elements"
],
"Reasoning": "The patch series introduces storage element (depopulation/repopulation) management for zoned block devices. It adds new UAPI ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) handled in the block layer, implements storage element emulation and control operations (degrade_element) in the virtual zoned loop driver (zloop), and implements SCSI ZBC storage element operations. The modified code introduces reachable ioctl paths, device state transitions, queue freezing/unfreezing, delayed work handling, and error checking, making it an excellent target for fuzzing.",
"WorthFuzzing": true
} |
| 19/1 | 2026/10/12 13:46 | action | If |
4mArgs:{
"WorthFuzzing": true
}Results: null |
| 20/2 | 2026/10/12 13:46 | agent | kmsan-evaluator |
4mModel:core Args: null Results: {
"KMSANReasoning": "The patch introduces storage element management for zoned block devices, including new ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) and support in the zloop and sd drivers.\n\n1. Kernel-to-User Information Leaks:\n - In `blkdev_get_nr_storage_elements_ioctl()`, `put_user` copies `disk-\u003enr_storage_elements`, which is an explicitly initialized scalar (`unsigned int`).\n - In `blkdev_report_storage_elements_ioctl()`, the report buffer is allocated with `kzalloc_flex()`, ensuring all payload bytes and potential padding are zeroed.\n - The UAPI structures `struct blk_se` (32 bytes) and `struct blk_se_report` (16 bytes header) are explicitly laid out with aligned fields and reserved byte padding (`reserved[13]` and `reserved`), leaving no compiler padding holes on 64-bit architectures.\n - In driver implementations: `sd_zbc_parse_storage_element()` starts with `memset(element, 0, sizeof(*element))`, and `zloop_create_storage_elements()` allocates descriptors via `kzalloc_objs()`.\n\n2. Uninitialized Memory Access in Control Flow:\n - All stack and heap buffers introduced by the patch (`opts` in `zloop_ctl_write`, `args` in `disk_revalidate_zones`, `elements` in `bdev_remove_storage_element` / `bdev_restore_storage_elements`) are zero-initialized (`{ }`, `kzalloc_objs`, etc.).\n - Variables controlling branching (e.g., `in_progress` in `disk_wait_for_se_mgmt_completion()`) are strictly set upon success (`ret == 0`) before being evaluated.\n\n3. KASAN vs KMSAN applicability:\n - Any potential risks introduced in the patch (such as out-of-bounds indexing in `zloop_remove_element` if `element_id == 0`, concurrency/locking with workqueues, or block queue state issues) fall under out-of-bounds, synchronization, or logic errors, which are detected by KASAN and LOCKDEP.\n - No uninitialized memory reads or information leak vulnerabilities are introduced that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Instruction: You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.
CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
a comprehensive suite of debugging tools and sanitizers, including KASAN
(out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
(locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
or page allocations) and kernel-to-user memory info-leaks.
Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.
Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.
Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
attributes, ioctl output arguments, socket options, or BPF buffers) where fields
or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
uninitialized bytes of existing buffers.
Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
dereferences, locking deadlocks, or use-after-free bugs (these are already caught
by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.
Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt: Target architecture: amd64
For your convenience, here is the diff of the changes:
commit 0f648578ec255b07dcf26e1f7c25cf9d70a5b080
Author: syz-cluster <triage@syzkaller.com>
Date: Mon Oct 12 13:43:15 2026 +0000
syz-cluster: applied patch under review
diff --git a/Documentation/admin-guide/blockdev/zoned_loop.rst b/Documentation/admin-guide/blockdev/zoned_loop.rst
index 64277494fb36a..ec39f141af8d6 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,12 @@ 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 all
+ access storage elements (equivalent to read+write head of
+ a disk) are emulated. A value of 2 enables fractional
+ access storage element (equivalent to pairs of read and
+ write heads of a disk) emulation .
=================== =========================================================
3) Deleting a Zoned Device
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index 19268afb8752e..8736e80b4080d 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -18,6 +18,8 @@
#include <linux/mempool.h>
#include <linux/kthread.h>
#include <linux/freezer.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
#include <trace/events/block.h>
@@ -292,6 +294,22 @@ 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.
+ */
+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
@@ -2526,20 +2544,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 = {
@@ -2550,19 +2555,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
@@ -2575,14 +2571,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);
@@ -2599,8 +2595,6 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
if (ret)
goto free_args;
- mutex_unlock(&disk->zone_revalidate_mutex);
-
return 0;
free_args:
@@ -2616,7 +2610,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;
@@ -2700,5 +2723,438 @@ int queue_zone_wplugs_show(void *data, struct seq_file *m)
return 0;
}
-
#endif
+
+static int disk_se_mgmt_in_progress(struct gendisk *disk,
+ struct blk_se *elements,
+ bool *in_progress)
+{
+ unsigned int i, nr_se = disk->nr_storage_elements;
+ int ret;
+
+ /*
+ * Check if we have storage elements being removed or restored.
+ */
+ ret = disk->fops->se_ops->report_elements(disk, elements, &nr_se);
+ if (ret) {
+ pr_err("Failed to get storage elements\n");
+ return ret;
+ }
+
+ for (i = 0; i < nr_se; i++) {
+ switch (elements[i].status) {
+ case BLK_SE_STS_REMOVE_IN_PROGRESS:
+ case BLK_SE_STS_RESTORE_IN_PROGRESS:
+ *in_progress = true;
+ return 0;
+ default:
+ break;
+ }
+ }
+
+ *in_progress = false;
+
+ return 0;
+}
+
+static int disk_check_remove_error(struct gendisk *disk,
+ struct blk_se *elements,
+ unsigned int element_id)
+{
+ unsigned int i;
+
+ for (i = 0; i < disk->nr_storage_elements; i++) {
+ if (elements[i].id == element_id
+ && elements[i].status == BLK_SE_STS_REMOVE_ERROR)
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int disk_wait_for_se_mgmt_completion(struct gendisk *disk,
+ struct blk_se *elements,
+ unsigned int element_id)
+{
+ bool in_progress;
+ int ret;
+
+ /*
+ * Poll the storage elements heath state to detect the end of a
+ * removal or restoration operation. 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.
+ */
+ while (1) {
+ ret = disk_se_mgmt_in_progress(disk, elements, &in_progress);
+ if (ret || !in_progress)
+ break;
+
+ if (fatal_signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+
+ /* Not done yet: wait and retry. */
+ msleep(1000);
+ }
+
+ /* Check for remove errors. */
+ if (!ret && !in_progress && element_id)
+ ret = disk_check_remove_error(disk, elements, element_id);
+
+ return ret;
+}
+
+/**
+ * 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.
+ *
+ * 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 (!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);
+
+static int blkdev_get_nr_storage_elements_ioctl(struct block_device *bdev,
+ void __user *argp)
+{
+ struct gendisk *disk = bdev->bd_disk;
+
+ if (put_user(disk->nr_storage_elements, (unsigned int __user *)argp))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int blkdev_report_storage_elements_ioctl(struct block_device *bdev,
+ void __user *argp)
+{
+ struct gendisk *disk = bdev->bd_disk;
+ struct blk_se_report *rep;
+ int ret;
+
+ if (!argp)
+ return -EINVAL;
+
+ 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->nr_elements < disk->nr_storage_elements) {
+ ret = -EINVAL;
+ goto free_rep;
+ }
+
+ rep->nr_reported_elements = disk->nr_storage_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_t(struct blk_se_report,
+ 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
+ * @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 device write cache flush and invalidation of cached data for the
+ * zones that will be offlined.
+ *
+ * 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, noio_flag;
+ struct blk_se *elements;
+ int ret, err;
+
+ 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;
+
+ noio_flag = memalloc_noio_save();
+ elements = kzalloc_objs(struct blk_se, disk->nr_storage_elements);
+ memalloc_noio_restore(noio_flag);
+ if (!elements)
+ return -ENOMEM;
+
+ /*
+ * 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);
+ if (!ret)
+ ret = disk_wait_for_se_mgmt_completion(disk, elements,
+ element_id);
+
+ /*
+ * Always revalidate zones, even in the case of an error as we do not
+ * know what condition the drive zones are in.
+ */
+ err = disk_revalidate_zones(disk);
+ if (err && !ret)
+ ret = err;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
+
+ kfree(elements);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bdev_remove_storage_element);
+
+static int blkdev_flush_and_truncate(struct block_device *bdev,
+ blk_mode_t mode)
+{
+ int ret;
+
+ inode_lock(bdev->bd_mapping->host);
+ filemap_invalidate_lock(bdev->bd_mapping);
+ ret = blkdev_issue_flush(bdev->bd_disk->part0);
+ if (!ret)
+ ret = truncate_bdev_range(bdev, mode, 0,
+ bdev_nr_bytes(bdev) - 1);
+ filemap_invalidate_unlock(bdev->bd_mapping);
+ inode_unlock(bdev->bd_mapping->host);
+
+ return ret;
+}
+
+static int blkdev_remove_storage_element_ioctl(struct block_device *bdev,
+ blk_mode_t mode, void __user *argp)
+{
+ unsigned int element_id;
+ int ret;
+
+ if (!(mode & BLK_OPEN_WRITE))
+ return -EBADF;
+ if (bdev_read_only(bdev))
+ return -EPERM;
+
+ if (get_user(element_id, (unsigned int __user *)argp))
+ return -EFAULT;
+
+ /*
+ * Flush the device 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 write to zoned devices using direct IOs, we
+ * should never have any dirty page invalidated and so no data loss as
+ * long as the device cache flush succeeds, that is, as long as the
+ * flush does not use degraded storage elements.
+ */
+ ret = blkdev_flush_and_truncate(bdev, mode);
+ if (ret)
+ return ret;
+
+ return bdev_remove_storage_element(bdev, element_id);
+}
+
+/**
+ * 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.
+ *
+ * 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, noio_flag;
+ struct blk_se *elements;
+ int ret, err;
+
+ if (!blk_queue_is_zoned(disk->queue) || !disk->nr_storage_elements)
+ return -EOPNOTSUPP;
+
+ noio_flag = memalloc_noio_save();
+ elements = kzalloc_objs(struct blk_se, disk->nr_storage_elements);
+ memalloc_noio_restore(noio_flag);
+ if (!elements)
+ return -ENOMEM;
+
+ /*
+ * 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);
+ if (!ret)
+ ret = disk_wait_for_se_mgmt_completion(disk, elements, 0);
+
+ /*
+ * Always revalidate zones, even in the case of an error as we do not
+ * know what condition the drive zones are in.
+ */
+ err = disk_revalidate_zones(disk);
+ if (err && !ret)
+ ret = err;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
+
+ kfree(elements);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(bdev_restore_storage_elements);
+
+static int blkdev_restore_storage_elements_ioctl(struct block_device *bdev,
+ blk_mode_t mode)
+{
+ int ret;
+
+ if (!(mode & BLK_OPEN_WRITE))
+ return -EBADF;
+ if (bdev_read_only(bdev))
+ return -EPERM;
+
+ /*
+ * Storage element restoration is a destructive operation that will
+ * reset all zones. So fflush the device volatile write cache and
+ * invalidate all cached data that we may have.
+ */
+ ret = blkdev_flush_and_truncate(bdev, mode);
+ if (ret)
+ return ret;
+
+ return bdev_restore_storage_elements(bdev);
+}
+
+int disk_init_storage_element_mgmt(struct gendisk *disk)
+{
+ int ret;
+
+ /*
+ * Storage element operations are for zoned disks only and
+ * optional...
+ */
+ if (!blk_queue_is_zoned(disk->queue) || !disk->fops->se_ops)
+ return 0;
+
+ /* ...but if supported, all operations 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;
+}
+
+int blkdev_zone_storage_elements_ioctl(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 BLKGETNRSTORELEMS:
+ return blkdev_get_nr_storage_elements_ioctl(bdev, argp);
+ case BLKREPORTSTORELEMS:
+ return blkdev_report_storage_elements_ioctl(bdev, argp);
+ case BLKREMOVESTORELEM:
+ return blkdev_remove_storage_element_ioctl(bdev, mode, argp);
+ case BLKRESTORESTORELEMS:
+ return blkdev_restore_storage_elements_ioctl(bdev, mode);
+ default:
+ break;
+ }
+
+ return -ENOTTY;
+}
diff --git a/block/blk.h b/block/blk.h
index 2cc03aa54c532..bbf523c9579cf 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -527,6 +527,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)
@@ -578,7 +579,14 @@ 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 blkdev_zone_storage_elements_ioctl(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)
+{
+ return 0;
+}
static inline void disk_init_zone_resources(struct gendisk *disk)
{
}
@@ -625,6 +633,12 @@ static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,
{
return false;
}
+static inline int blkdev_zone_storage_elements_ioctl(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/genhd.c b/block/genhd.c
index 10ca8b4d6eea7..e2cef791ac91a 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/block/ioctl.c b/block/ioctl.c
index 64b4e6c0f6965..ccf806c2e37d1 100644
--- a/block/ioctl.c
+++ b/block/ioctl.c
@@ -679,6 +679,11 @@ 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 BLKGETNRSTORELEMS:
+ case BLKREPORTSTORELEMS:
+ case BLKREMOVESTORELEM:
+ case BLKRESTORESTORELEMS:
+ return blkdev_zone_storage_elements_ioctl(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/drivers/block/zloop.c b/drivers/block/zloop.c
index 394dc2408ed73..26d8335508a16 100644
--- a/drivers/block/zloop.c
+++ b/drivers/block/zloop.c
@@ -37,6 +37,8 @@ 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),
+ ZLOOP_OPT_ELEMENT_ID = (1 << 14),
};
static const match_table_t zloop_opt_tokens = {
@@ -51,11 +53,23 @@ 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_ELEMENT_ID, "element_id=%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 +82,8 @@ 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
+#define ZLOOP_DEF_ELEMENT_ID 0
/* Arbitrary limit on the zone size (16GB). */
#define ZLOOP_MAX_ZONE_SIZE_MB 16384
@@ -87,6 +103,8 @@ struct zloop_options {
bool zone_append;
bool ordered_zone_append;
bool discard_write_cache;
+ enum zloop_stor_elements stor_elements;
+ unsigned int element_id;
};
/*
@@ -117,6 +135,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 +153,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 +171,17 @@ 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 max_nr_removed_elements;
+ unsigned int nr_removed_elements;
+ struct delayed_work remove_element_work;
+ unsigned int remove_element_id;
+ struct delayed_work restore_elements_work;
+ bool restore_in_progress;
+ struct blk_se *elements;
+
struct zloop_zone zones[] __counted_by(nr_zones);
};
@@ -289,22 +321,43 @@ 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 void zloop_mark_empty(struct zloop_device *zlo, struct zloop_zone *zone)
+static inline void zloop_set_zone_full(struct zloop_device *zlo,
+ struct zloop_zone *zone)
{
- lockdep_assert_held(&zone->wp_lock);
+ zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_FULL);
+}
- zloop_lru_remove_open_zone(zlo, zone);
- zone->cond = BLK_ZONE_COND_EMPTY;
- zone->wp = zone->start;
+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 bool zloop_zone_is_offline_or_readonly(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ bool ret;
+
+ spin_lock(&zone->wp_lock);
+ 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 +369,13 @@ static int zloop_update_seq_zone(struct zloop_device *zlo, unsigned int zone_no)
lockdep_assert_held(&zone->lock);
+ /*
+ * If the zone was already changed to offline or read-only, we have
+ * nothing to do.
+ */
+ if (zloop_zone_is_offline_or_readonly(zlo, zone))
+ return 0;
+
ret = vfs_getattr(&zone->file->f_path, &stat, STATX_SIZE, 0);
if (ret < 0) {
pr_err("Failed to get zone %u file stat (err=%d)\n",
@@ -339,9 +399,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)
@@ -363,6 +423,11 @@ static int zloop_open_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(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)
@@ -388,6 +453,11 @@ static int zloop_close_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(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)
@@ -420,7 +490,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;
@@ -430,20 +515,19 @@ static int zloop_reset_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(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);
@@ -457,7 +541,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;
}
@@ -475,6 +559,11 @@ static int zloop_finish_zone(struct zloop_device *zlo, unsigned int zone_no)
mutex_lock(&zone->lock);
+ if (zloop_zone_is_offline_or_readonly(zlo, zone)) {
+ ret = -EIO;
+ goto unlock;
+ }
+
if (!test_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags) &&
zone->cond == BLK_ZONE_COND_FULL)
goto unlock;
@@ -487,7 +576,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);
@@ -624,7 +713,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);
@@ -766,7 +855,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);
@@ -875,30 +964,72 @@ 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;
+ spin_lock(&zone->wp_lock);
+
+ if (zlo->nr_elements) {
+ struct blk_se *se;
+
+ /*
+ * Check the zone condition and the health state of the storage
+ * element serving the zone: do not allow any operation to
+ * offline zones or if the storage element serving the zone is
+ * being removed, and allow only reads to read-only zones.
+ */
+ if (is_write)
+ se = &zlo->elements[zone->wr_se_id - 1];
+ else
+ se = &zlo->elements[zone->rd_se_id - 1];
+ if (READ_ONCE(se->status) == BLK_SE_STS_DEGRADED ||
+ READ_ONCE(se->status) == BLK_SE_STS_REMOVE_IN_PROGRESS) {
+ ret = false;
+ goto unlock;
+ }
+
+ if (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,
@@ -914,13 +1045,21 @@ 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 (zlo->ordered_zone_append && req_op(rq) == REQ_OP_ZONE_APPEND) {
- if (!zloop_set_zone_append_sector(rq))
+ if (READ_ONCE(zlo->restore_in_progress))
+ return BLK_STS_IOERR;
+
+ 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);
@@ -1002,11 +1141,317 @@ 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 (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_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 void zloop_remove_element_work(struct work_struct *work)
+{
+ struct zloop_device *zlo = container_of(work, struct zloop_device,
+ remove_element_work.work);
+ struct blk_se *se, *paired_se = NULL;
+ enum blk_zone_cond cond;
+ struct zloop_zone *zone;
+ unsigned int memflags, i;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* Wait for all IOs to complete. */
+ memflags = blk_mq_freeze_queue(zlo->disk->queue);
+ blk_mq_unfreeze_queue(zlo->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);
+ }
+
+ /*
+ * 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);
+ clear_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ zloop_set_zone_cond(zlo, zone, cond);
+ 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++;
+ }
+
+ zlo->remove_element_id = 0;
+
+ mutex_unlock(&zlo->stor_elements_lock);
+}
+
+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 (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ if (element_id > zlo->nr_elements)
+ return -EINVAL;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ 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;
+ }
+
+ if (zlo->nr_removed_elements + nr_remove >
+ zlo->max_nr_removed_elements) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /*
+ * Schedule the element removal with a delay, to emulate the (generally
+ * short) time it takes for a real device to depopulate a head and
+ * modify the zones.
+ */
+ zlo->remove_element_id = element_id;
+ 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);
+
+ schedule_delayed_work(&zlo->remove_element_work,
+ msecs_to_jiffies(2000));
+
+unlock:
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return ret;
+}
+
+static void zloop_restore_conv_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ mutex_lock(&zone->lock);
+ spin_lock(&zone->wp_lock);
+ if (zone->cond == BLK_ZONE_COND_OFFLINE ||
+ zone->cond == BLK_ZONE_COND_READONLY)
+ zloop_set_zone_cond(zlo, zone, BLK_ZONE_COND_NOT_WP);
+ spin_unlock(&zone->wp_lock);
+ mutex_unlock(&zone->lock);
+}
+
+static int zloop_restore_seq_zone(struct zloop_device *zlo,
+ struct zloop_zone *zone)
+{
+ int ret;
+
+ mutex_lock(&zone->lock);
+ ret = zloop_do_reset_zone(zlo, zone);
+ if (ret)
+ set_bit(ZLOOP_ZONE_SEQ_ERROR, &zone->flags);
+ mutex_unlock(&zone->lock);
+
+ return ret;
+}
+
+static void zloop_restore_elements_work(struct work_struct *work)
+{
+ struct zloop_device *zlo = container_of(work, struct zloop_device,
+ restore_elements_work.work);
+ struct zloop_zone *zone;
+ struct blk_se *se;
+ unsigned int memflags, i;
+ enum blk_se_status sts;
+ int ret = 0;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ /* Wait for all IOs to complete. */
+ memflags = blk_mq_freeze_queue(zlo->disk->queue);
+ blk_mq_unfreeze_queue(zlo->disk->queue, memflags);
+
+ /* Reset all zones. */
+ for (i = 0, zone = zlo->zones; i < zlo->nr_zones; i++, zone++) {
+ if (test_bit(ZLOOP_ZONE_CONV, &zone->flags)) {
+ zloop_restore_conv_zone(zlo, zone);
+ continue;
+ }
+ ret = zloop_restore_seq_zone(zlo, zone);
+ if (ret)
+ break;
+ }
+
+ /*
+ * If we had an error, maintain all elements as removed instead of
+ * using the restore error status.
+ */
+ if (ret)
+ sts = BLK_SE_STS_REMOVED;
+ else
+ sts = BLK_SE_STS_OK;
+
+ /* Restore all removed elements. */
+ 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);
+ }
+
+ if (!ret)
+ zlo->nr_removed_elements = 0;
+
+ WRITE_ONCE(zlo->restore_in_progress, false);
+ mutex_unlock(&zlo->stor_elements_lock);
+}
+
+static int zloop_restore_elements(struct gendisk *disk)
+{
+ struct zloop_device *zlo = disk->private_data;
+ struct blk_se *se;
+ unsigned int i;
+ int ret = 0;
+
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_NONE)
+ return -EOPNOTSUPP;
+
+ mutex_lock(&zlo->stor_elements_lock);
+
+ if (zlo->remove_element_id || READ_ONCE(zlo->restore_in_progress)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ /* If we have no removed elements, we have nothing to do. */
+ if (!zlo->nr_removed_elements)
+ goto unlock;
+
+ /*
+ * Schedule the elements restoration with a delay, to emulate the time
+ * it takes for a real device to restore all removed heads and reset
+ * all zones.
+ */
+ WRITE_ONCE(zlo->restore_in_progress, true);
+ for (i = 0, se = zlo->elements; i < zlo->nr_elements; i++, se++) {
+ if (READ_ONCE(se->status) == BLK_SE_STS_REMOVED)
+ WRITE_ONCE(se->status, BLK_SE_STS_RESTORE_IN_PROGRESS);
+ }
+
+ schedule_delayed_work(&zlo->restore_elements_work,
+ msecs_to_jiffies(5000));
+
+unlock:
+ mutex_unlock(&zlo->stor_elements_lock);
+
+ return ret;
+}
+
static void zloop_free_disk(struct gendisk *disk)
{
struct zloop_device *zlo = disk->private_data;
unsigned int i;
+ cancel_delayed_work_sync(&zlo->remove_element_work);
+ cancel_delayed_work_sync(&zlo->restore_elements_work);
+
blk_mq_free_tag_set(&zlo->tag_set);
for (i = 0; i < zlo->nr_zones; i++) {
@@ -1019,15 +1464,24 @@ 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)
@@ -1112,6 +1566,24 @@ static int zloop_init_zone(struct zloop_device *zlo, struct zloop_options *opts,
if (!opts->buffered_io)
oflags |= O_DIRECT;
+ 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 (zone_no < zlo->nr_conv_zones) {
/* Conventional zone file. */
set_bit(ZLOOP_ZONE_CONV, &zone->flags);
@@ -1182,6 +1654,79 @@ 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 = 2;
+ 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;
+ }
+ }
+
+ /*
+ * 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.
+ */
+ zlo->nr_elements = nr_elems;
+ if (zlo->stor_elements == ZLOOP_STOR_ELEMENTS_PAIRS)
+ zlo->max_nr_removed_elements = zlo->nr_elements - 2;
+ else
+ zlo->max_nr_removed_elements = zlo->nr_elements - 1;
+
+ return 0;
+}
+
static bool zloop_dev_exists(struct zloop_device *zlo)
{
struct file *cnv, *seq;
@@ -1237,6 +1782,11 @@ 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);
+ INIT_DELAYED_WORK(&zlo->remove_element_work,
+ zloop_remove_element_work);
+ INIT_DELAYED_WORK(&zlo->restore_elements_work,
+ zloop_restore_elements_work);
ret = mutex_lock_killable(&zloop_ctl_mutex);
if (ret)
@@ -1270,12 +1820,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)
@@ -1361,6 +1918,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: %d 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 " : "",
@@ -1384,6 +1945,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);
@@ -1483,6 +2046,42 @@ 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_ID | ZLOOP_OPT_ELEMENT_ID)) {
+ pr_err("Invalid option specified for degrade_element\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&zloop_ctl_mutex);
+
+ 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("Degraded element %u of device %u\n",
+ opts->element_id, opts->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];
@@ -1502,6 +2101,8 @@ 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;
+ opts->element_id = ZLOOP_DEF_ELEMENT_ID;
if (!buf)
return 0;
@@ -1636,6 +2237,30 @@ 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_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);
@@ -1664,14 +2289,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 },
};
@@ -1719,6 +2346,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;
@@ -1742,6 +2372,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);
@@ -1752,6 +2384,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=%d\n");
+
return 0;
}
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c
index a1b21ea14e549..48c46f4a1ace8 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,11 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd)
goto fail;
}
+ /* Prevent read/write to offline zones of ZBC disks. */
+ 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.
@@ -3937,6 +3943,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 574af82430169..fc8c49903811e 100644
--- a/drivers/scsi/sd.h
+++ b/drivers/scsi/sd.h
@@ -156,6 +156,7 @@ struct scsi_disk {
unsigned ignore_medium_access_errors : 1;
unsigned rscs : 1; /* reduced stream control support */
unsigned use_atomic_write_boundary : 1;
+ unsigned modify_zones_supported : 1;
};
#define to_scsi_disk(obj) container_of(obj, struct scsi_disk, disk_dev)
@@ -242,6 +243,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 456beaf2e7690..39cd4f9fcce5c 100644
--- a/drivers/scsi/sd_zbc.c
+++ b/drivers/scsi/sd_zbc.c
@@ -516,6 +516,21 @@ static int sd_zbc_check_capacity(struct scsi_disk *sdkp, unsigned char *buf,
return 0;
}
+/*
+ * sd_zbc_check_modify_zones - Check if the device supports depopulation
+ * @sdkp: Target disk
+ * @buf: command buffer
+ *
+ * Check if the device supports the REMOVE ELEMENT AND MODIFY ZONES command.
+ */
+static inline bool sd_zbc_check_modify_zones(struct scsi_disk *sdkp,
+ unsigned char *buf)
+{
+ return 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)
@@ -533,6 +548,239 @@ 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]);
+
+ 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;
+ }
+
+ switch (desc[15]) {
+ case SCSI_PHYS_ELEM_HEALTH_WITHIN_SPEC_LIMITS:
+ case SCSI_PHYS_ELEM_HEALTH_AT_SPEC_LIMITS:
+ element->status = BLK_SE_STS_OK;
+ break;
+ case SCSI_PHYS_ELEM_HEALTH_OUTSIDE_SPEC_LIMITS:
+ element->status = BLK_SE_STS_DEGRADED;
+ 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;
+ element->restore_allowed = desc[13] & 0x01;
+ 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
+
+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 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];
+ unsigned int nr_descs, nr_se;
+ unsigned int buf_size;
+ int i, ret = 0, result;
+ u8 *desc, *buf;
+
+ if (!sdkp->modify_zones_supported)
+ return -EOPNOTSUPP;
+
+ /*
+ * We need at least 32B for the report header and 32B for each
+ * descriptor.
+ */
+ nr_se = min(SD_ZBC_MAX_STORAGE_ELEMENTS, *nr_elements);
+ buf_size = ALIGN((nr_se + 1) * 32, SECTOR_SIZE);
+
+ buf = kzalloc(buf_size, GFP_NOIO);
+ if (!buf)
+ return -ENOMEM;
+
+ memset(cmd, 0, 16);
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = SAI_GET_PHYSICAL_ELEMENT_STATUS;
+ put_unaligned_be32(buf_size, &cmd[10]);
+
+ result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, buf, buf_size,
+ timeout, 1, &exec_args);
+ if (result) {
+ sd_printk(KERN_ERR, sdkp,
+ "GET PHYSICAL ELEMENT STATUS failed\n");
+ sd_print_result(sdkp, "GET PHYSICAL ELEMENT STATUS", result);
+ if (result > 0 && scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ ret = -EIO;
+ goto free_buf;
+ }
+
+ nr_descs = get_unaligned_be32(&buf[0]);
+ if (!nr_descs) {
+ sd_printk(KERN_ERR, sdkp,
+ "Invalid number of phys element descriptors\n");
+ ret = -EIO;
+ goto free_buf;
+ }
+ if (nr_descs > SD_ZBC_MAX_STORAGE_ELEMENTS) {
+ sd_printk(KERN_ERR, sdkp,
+ "Too many phys element descriptors\n");
+ ret = -EIO;
+ goto free_buf;
+ }
+
+ if (!elements) {
+ *nr_elements = nr_descs;
+ goto free_buf;
+ }
+
+ nr_descs = get_unaligned_be32(&buf[4]);
+ if (!nr_descs) {
+ sd_printk(KERN_ERR, sdkp,
+ "Invalid number of reported phys element descriptors\n");
+ ret = -EIO;
+ goto free_buf;
+ }
+
+ desc = &buf[32];
+ for (i = 0; i < min(nr_se, nr_descs); i++, desc += 32)
+ sd_zbc_parse_storage_element(sdkp, desc, &elements[i]);
+ *nr_elements = i;
+
+free_buf:
+ kfree(buf);
+
+ return ret;
+}
+
+static int sd_zbc_remove_storage_element(struct gendisk *disk,
+ unsigned int element_id)
+{
+ struct scsi_disk *sdkp = scsi_disk(disk);
+ 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];
+ int result;
+
+ if (!sdkp->modify_zones_supported)
+ return -EOPNOTSUPP;
+
+ memset(cmd, 0, 16);
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = SAI_REMOVE_ELEMENT_AND_MODIFY_ZONES;
+ put_unaligned_be32(element_id, &cmd[10]);
+
+ result = scsi_execute_cmd(sdp, cmd, REQ_OP_DRV_IN, NULL, 0,
+ timeout, 1, &exec_args);
+ if (result) {
+ sd_printk(KERN_ERR, sdkp,
+ "REMOVE ELEMENT AND MODIFY ZONES failed\n");
+ sd_print_result(sdkp,
+ "REMOVE ELEMENT AND MODIFY ZONES", result);
+ if (result > 0 && scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sd_zbc_restore_storage_elements(struct gendisk *disk)
+{
+ struct scsi_disk *sdkp = scsi_disk(disk);
+ 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];
+ int result;
+
+ if (!sdkp->modify_zones_supported)
+ return -EOPNOTSUPP;
+
+ memset(cmd, 0, 16);
+ cmd[0] = SERVICE_ACTION_IN_16;
+ cmd[1] = SAI_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,
+ "RESTORE ELEMENTS AND REBUILD failed\n");
+ sd_print_result(sdkp,
+ "RESTORE ELEMENTS AND REBUILD", result);
+ if (result > 0 && scsi_sense_valid(&sshdr))
+ sd_print_sense_hdr(sdkp, &sshdr);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+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
@@ -554,9 +802,15 @@ 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->modify_zones_supported)
return 0;
sdkp->zone_info.zone_blocks = zone_blocks;
@@ -620,6 +874,9 @@ int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim,
if (ret != 0)
goto err;
+ /* Check if REMOVE ELEMENT AND MODIFY ZONES is supported. */
+ sdkp->modify_zones_supported = sd_zbc_check_modify_zones(sdkp, buf);
+
nr_zones = round_up(sdkp->capacity, zone_blocks) >> ilog2(zone_blocks);
if (nr_zones > INT_MAX) {
sd_printk(KERN_ERR, sdkp, "Too many zones (%llu)\n",
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index d003a9d2d1f6c..20b77c6b451fc 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
@@ -942,6 +947,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)
{
@@ -956,6 +964,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;
@@ -1574,6 +1588,22 @@ enum blk_unique_id {
BLK_UID_NAA = 3,
};
+/* Storage element management operations. */
+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);
+};
+
+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);
+
struct block_device_operations {
void (*submit_bio)(struct bio *bio);
int (*poll_bio)(struct bio *bio, struct io_comp_batch *iob,
@@ -1601,6 +1631,7 @@ struct block_device_operations {
enum blk_unique_id id_type);
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 6638361209667..56b8ea7c6a86d 100644
--- a/include/uapi/linux/blkzoned.h
+++ b/include/uapi/linux/blkzoned.h
@@ -208,4 +208,87 @@ 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_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_DEGRADED = 0x02,
+ BLK_SE_STS_REMOVE_IN_PROGRESS = 0x03,
+ BLK_SE_STS_REMOVE_ERROR = 0x04,
+ BLK_SE_STS_RESTORE_IN_PROGRESS = 0x05,
+ BLK_SE_STS_RESTORE_ERROR = 0x06,
+ BLK_SE_STS_REMOVED = 0x07,
+ 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];
+};
+
+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:
+ *
+ * @BLKGETNRSTORELEMS: Get the number of storage elements of the device.
+ * @BLKREPORTSTORELEMS: Get the device storage elements.
+ * @BLKREMOVESTORELEM: Remove (depopulate) one storage element of a device.
+ * @BLKRESTORESTORELEM: Restore (repopulate if possible) all storage elements
+ * that have been removed.
+ */
+#define BLKGETNRSTORELEMS _IOR(0x12, 143, __u32)
+#define BLKREPORTSTORELEMS _IOWR(0x12, 144, struct blk_se_report)
+#define BLKREMOVESTORELEM _IOW(0x12, 145, __u32)
+#define BLKRESTORESTORELEMS _IO(0x12, 146)
+
#endif /* _UAPI_BLKZONED_H */
diff --git a/include/uapi/linux/fs.h b/include/uapi/linux/fs.h
index 34c6f219462a5..8a979326aa7f0 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-146 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 */
|
| 21/3 | 2026/10/12 13:46 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 29048 output: 40 thoughts: 5501 Thoughts: **KMSAN Analysis of Storage Element Management in Zoned Block Devices** Alright, let's break down this patch and see where KMSAN might raise a flag. The focus here is on the new storage element management introduced for zoned block devices. We're looking for uninitialized memory reads and kernel-to-user information leaks, as those are KMSAN's bread and butter. First, the patch introduces new ioctls to interact with storage elements. The core question is: are we carefully initializing all the data we're sending to user space or using in conditional logic? The code section `blkdev_report_storage_elements_ioctl` immediately catches my eye. We have `kzalloc_flex` allocating a `blk_se_report` structure. That's good, `kzalloc` zeroes the memory. Then, we copy data *from user space* into this structure. The size of the `blk_se_report` struct looks correct. The code then reports the elements, and finally, copies back to user space. A crucial point here is that the reported `nr_elements` could be less than `disk->nr_storage_elements`. The code looks sound because in `bdev_report_storage_elements`, the reported `nr_elements` is always capped at the device's actual number. Any elements past this would remain zero. The `memcpy` to user space will also only write back the valid number. Diving deeper into `bdev_report_storage_elements` itself, we have implementations for `zloop` and `sd_zbc` devices. In `zloop`, the `zlo->elements` are allocated with `kzalloc`. In `sd_zbc`, it's again allocated with `kzalloc`. This is a good sign. The `sd_zbc` implementation also calls `sd_zbc_parse_storage_element` which, *crucially*, explicitly `memset` the `blk_se` structure to zero *before* populating it. So no uninitialized bytes here. Next up, the `disk_se_mgmt_in_progress` and `disk_check_remove_error` functions are checked. Here we are checking the state of a removal/restoration operation. Again, the critical function here is `report_elements`, and again, it uses a zero-initialized memory block and doesn't read past the bounds of that. Padding in structures is another key consideration. Inspecting the `struct blk_se` structure, all data fields are packed. The `struct blk_se_report` also has no padding. Now, a very crucial and subtle detail: what happens if the `bdev_report_storage_elements` function returns with less elements than it was given? If the number returned from `bdev_report_storage_elements` is less than expected, `kzalloc_flex` ensures that there is no memory leak or data exposed because the trailing structure entries, where data was *not* written by the kernel function, would be zero. A quick look at `zloop_prep_rq` shows that `zone->wr_se_id - 1` could result in underflow. The `zloop_init_zone` function ensures that `wr_se_id` and `rd_se_id` are set to valid values, so that `zone->wr_se_id - 1` will *not* underflow. The `zloop_remove_element` function could lead to an out-of-bounds array access if given `element_id == 0`. However, that is checked at the higher layer. Finally, in `sd_zbc.c`, `sd_zbc_parse_storage_element` calls `memset` on the structure, so no uninitialized reads there. In summary, after this thorough analysis, I don't see any obvious KMSAN issues. The code consistently uses `kzalloc` or `memset` to ensure zero-initialization of the data passed to user space. The important thing is making sure the kernel structures are always completely initialized, and it looks like the developers have been meticulous in that regard. |
| 22/3 | 2026/10/12 13:47 | tool | grepper |
0mArgs:{
"Expression": "sd_zbc_read_zones",
"PathPrefixes": [
"drivers/scsi/sd_zbc.c"
]
}Results: {
"Output": "drivers/scsi/sd_zbc.c=789=int sd_zbc_revalidate_zones(struct scsi_disk *sdkp)\n--\ndrivers/scsi/sd_zbc.c-833-/**\ndrivers/scsi/sd_zbc.c:834: * sd_zbc_read_zones - Read zone information and update the request queue\ndrivers/scsi/sd_zbc.c-835- * @sdkp: SCSI disk pointer.\n--\ndrivers/scsi/sd_zbc.c-842- */\ndrivers/scsi/sd_zbc.c:843:int sd_zbc_read_zones(struct scsi_disk *sdkp, struct queue_limits *lim,\ndrivers/scsi/sd_zbc.c-844-\t\tu8 buf[SD_BUF_SIZE])\n"
} |
| 23/3 | 2026/10/12 13:47 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 35708 output: 20 thoughts: 18 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person: **Investigating the SCSI Report Opcode Functionality** Alright, let's dive into this. My initial thought is to examine the `scsi_report_opcode` function. I need to understand its specific behavior. I want to know precisely what it's responsible for, how it handles requests, what kind of information it returns, and any implications for the larger SCSI interaction. I’m thinking about potential issues with error handling and resource allocation within that function. I'll need to look at its underlying implementation, maybe trace its calls, and consider its integration into the broader SCSI command flow. I'm especially keen to see how it might be interacting with the device’s inquiry data or vital product data. Let's see what we can find. |
| 24/3 | 2026/10/12 13:47 | tool | grepper |
0mArgs:{
"Expression": "scsi_report_opcode"
}Results: {
"Output": "drivers/ata/libata-scsi.c=3986=static unsigned int ata_scsi_report_supported_opcodes(struct ata_device *dev,\n--\ndrivers/ata/libata-scsi.c-4038- *\ndrivers/ata/libata-scsi.c:4039: *\tYields a subset to satisfy scsi_report_opcode()\ndrivers/ata/libata-scsi.c-4040- *\n--\ndrivers/scsi/scsi.c=506=void scsi_attach_vpd(struct scsi_device *sdev)\n--\ndrivers/scsi/scsi.c-552-/**\ndrivers/scsi/scsi.c:553: * scsi_report_opcode - Find out if a given command is supported\ndrivers/scsi/scsi.c-554- * @sdev:\tscsi device to query\n--\ndrivers/scsi/scsi.c-565- */\ndrivers/scsi/scsi.c:566:int scsi_report_opcode(struct scsi_device *sdev, unsigned char *buffer,\ndrivers/scsi/scsi.c-567-\t\t unsigned int len, unsigned char opcode,\n--\ndrivers/scsi/scsi.c-616-}\ndrivers/scsi/scsi.c:617:EXPORT_SYMBOL(scsi_report_opcode);\ndrivers/scsi/scsi.c-618-\n--\ndrivers/scsi/scsi.c=621=static bool scsi_cdl_check_cmd(struct scsi_device *sdev, u8 opcode, u16 sa,\n--\ndrivers/scsi/scsi.c-627-\t/* Check operation code */\ndrivers/scsi/scsi.c:628:\tret = scsi_report_opcode(sdev, buf, SCSI_CDL_CHECK_BUF_LEN, opcode, sa);\ndrivers/scsi/scsi.c-629-\tif (ret \u003c= 0)\n--\ndrivers/scsi/sd.c=3548=static void sd_read_write_same(struct scsi_disk *sdkp, unsigned char *buffer)\n--\ndrivers/scsi/sd.c-3557-\ndrivers/scsi/sd.c:3558:\tif (scsi_report_opcode(sdev, buffer, SD_BUF_SIZE, INQUIRY, 0) \u003c 0) {\ndrivers/scsi/sd.c-3559-\t\tsdev-\u003eno_report_opcodes = 1;\n--\ndrivers/scsi/sd.c-3568-\ndrivers/scsi/sd.c:3569:\tif (scsi_report_opcode(sdev, buffer, SD_BUF_SIZE, WRITE_SAME_16, 0) == 1)\ndrivers/scsi/sd.c-3570-\t\tsdkp-\u003ews16 = 1;\ndrivers/scsi/sd.c-3571-\ndrivers/scsi/sd.c:3572:\tif (scsi_report_opcode(sdev, buffer, SD_BUF_SIZE, WRITE_SAME, 0) == 1)\ndrivers/scsi/sd.c-3573-\t\tsdkp-\u003ews10 = 1;\n--\ndrivers/scsi/sd.c=3576=static void sd_read_security(struct scsi_disk *sdkp, unsigned char *buffer)\n--\ndrivers/scsi/sd.c-3582-\ndrivers/scsi/sd.c:3583:\tif (scsi_report_opcode(sdev, buffer, SD_BUF_SIZE,\ndrivers/scsi/sd.c-3584-\t\t\tSECURITY_PROTOCOL_IN, 0) == 1 \u0026\u0026\ndrivers/scsi/sd.c:3585:\t scsi_report_opcode(sdev, buffer, SD_BUF_SIZE,\ndrivers/scsi/sd.c-3586-\t\t\tSECURITY_PROTOCOL_OUT, 0) == 1)\n--\ndrivers/scsi/sd_zbc.c=526=static inline bool sd_zbc_check_modify_zones(struct scsi_disk *sdkp,\n--\ndrivers/scsi/sd_zbc.c-528-{\ndrivers/scsi/sd_zbc.c:529:\treturn scsi_report_opcode(sdkp-\u003edevice, buf, SD_BUF_SIZE,\ndrivers/scsi/sd_zbc.c-530-\t\t\t\t SERVICE_ACTION_IN_16,\n--\ninclude/scsi/scsi_device.h=476=extern int scsi_get_vpd_page(struct scsi_device *, u8 page, unsigned char *buf,\ninclude/scsi/scsi_device.h-477-\t\t\t int buf_len);\ninclude/scsi/scsi_device.h:478:int scsi_report_opcode(struct scsi_device *sdev, unsigned char *buffer,\ninclude/scsi/scsi_device.h-479-\t\t unsigned int len, unsigned char opcode,\n"
} |
| 25/3 | 2026/10/12 13:47 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 37201 output: 37 thoughts: 24 Thoughts: Okay, let's dive into this. Here's my thought process laid out: **Checking the SCSI Report Opcode** Right, let's get down to brass tacks. I need to understand how the SCSI report opcode is handled. I immediately think to check the `scsi_report_opcode` function, and since I know it's a core SCSI function, I'll find it within the SCSI core code. That means I need to head over to `drivers/scsi/scsi.c`. Time to get my hands dirty. I'm going to navigate to that file and zero in on that specific function to understand its implementation and how it fits into the broader SCSI command processing flow. I suspect there'll be things like error handling, command parameter parsing, and potential interaction with the low-level hardware drivers. Let's see what's actually going on in there! |
| 26/3 | 2026/10/12 13:48 | tool | read-file |
0mArgs:{
"File": "drivers/scsi/scsi.c",
"FirstLine": 566,
"LineCount": 45
}Results: {
"Contents": " 566:\tint scsi_report_opcode(struct scsi_device *sdev, unsigned char *buffer,\n 567:\t\t\t unsigned int len, unsigned char opcode,\n 568:\t\t\t unsigned short sa)\n 569:\t{\n 570:\t\tunsigned char cmd[16];\n 571:\t\tstruct scsi_sense_hdr sshdr;\n 572:\t\tint result, request_len;\n 573:\t\tconst struct scsi_exec_args exec_args = {\n 574:\t\t\t.sshdr = \u0026sshdr,\n 575:\t\t};\n 576:\t\n 577:\t\tif (sdev-\u003eno_report_opcodes || sdev-\u003escsi_level \u003c SCSI_SPC_3)\n 578:\t\t\treturn -EINVAL;\n 579:\t\n 580:\t\t/* RSOC header + size of command we are asking about */\n 581:\t\trequest_len = 4 + COMMAND_SIZE(opcode);\n 582:\t\tif (request_len \u003e len) {\n 583:\t\t\tdev_warn_once(\u0026sdev-\u003esdev_gendev,\n 584:\t\t\t\t \"%s: len %u bytes, opcode 0x%02x needs %u\\n\",\n 585:\t\t\t\t __func__, len, opcode, request_len);\n 586:\t\t\treturn -EINVAL;\n 587:\t\t}\n 588:\t\n 589:\t\tmemset(cmd, 0, 16);\n 590:\t\tcmd[0] = MAINTENANCE_IN;\n 591:\t\tcmd[1] = MI_REPORT_SUPPORTED_OPERATION_CODES;\n 592:\t\tif (!sa) {\n 593:\t\t\tcmd[2] = 1;\t/* One command format */\n 594:\t\t\tcmd[3] = opcode;\n 595:\t\t} else {\n 596:\t\t\tcmd[2] = 3;\t/* One command format with service action */\n 597:\t\t\tcmd[3] = opcode;\n 598:\t\t\tput_unaligned_be16(sa, \u0026cmd[4]);\n 599:\t\t}\n 600:\t\tput_unaligned_be32(request_len, \u0026cmd[6]);\n 601:\t\tmemset(buffer, 0, len);\n 602:\t\n 603:\t\tresult = scsi_execute_cmd(sdev, cmd, REQ_OP_DRV_IN, buffer,\n 604:\t\t\t\t\t request_len, 30 * HZ, 3, \u0026exec_args);\n 605:\t\tif (result \u003c 0)\n 606:\t\t\treturn result;\n 607:\t\tif (result \u0026\u0026 scsi_sense_valid(\u0026sshdr) \u0026\u0026\n 608:\t\t sshdr.sense_key == ILLEGAL_REQUEST \u0026\u0026\n 609:\t\t (sshdr.asc == 0x20 || sshdr.asc == 0x24) \u0026\u0026 sshdr.ascq == 0x00)\n 610:\t\t\treturn -EINVAL;\n"
} |
| 27/3 | 2026/10/12 13:48 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 38244 output: 20 thoughts: 798 Thoughts: **Analyzing Storage Element Management in the Block Layer** Okay, so I'm looking at how storage elements are handled, specifically in relation to zoned block devices. First, I noticed that `scsi_report_opcode` calls `memset` on a buffer – a standard initialization. Now, I'm digging into the ioctls added in `block/blk-zoned.c` and `block/ioctl.c` to see how they're used. In `block/ioctl.c`, there's a switch statement handling several `BLK*STORELEMS` ioctls. Let's break down each one. * **`BLKGETNRSTORELEMS`:** This one looks straightforward. It uses `blkdev_get_nr_storage_elements_ioctl` to get the number of storage elements (`disk->nr_storage_elements`) from the `gendisk` structure and passes this value to user space. It appears `disk->nr_storage_elements` is an `unsigned int` field on the `gendisk` that is initialized in `disk_init_storage_element_mgmt` using a call to `disk->fops->se_ops->report_elements` where the number is set, or if an error occurs, it is set to zero. * **`BLKREPORTSTORELEMS`:** Okay, this is where it gets interesting. `blkdev_report_storage_elements_ioctl` allocates a `blk_se_report` structure. Critically, there's a `kzalloc_flex` call with `disk->nr_storage_elements`. It's a `struct blk_se_report` which contains an array of `elements` that are sized based on `disk->nr_storage_elements`. The code then copies data from user space, checks if the requested number of elements is valid, reports the elements, and finally copies the reported elements back to user space using `copy_to_user`. I'm going to have to make sure this is sized correctly. And hold on, look closely at that `copy_to_user`! It's using `struct_size_t(struct blk_se_report, elements, rep->nr_reported_elements)`. But how many bytes are *actually* allocated for `rep`? We have `kzalloc_flex(*rep, elements, disk->nr_storage_elements)` – it's allocating a flexible-sized array. I need to refresh my memory on how `kzalloc_flex` works to ensure we're not running into any potential buffer overflow issues when the data is copied back. I'll need to check the definition. |
| 28/3 | 2026/10/12 13:48 | tool | grepper |
0mArgs:{
"Expression": "#define kzalloc_flex"
}Results: {
"Output": "include/linux/slab.h=1073=void *kmalloc_nolock(size_t size, gfp_t gfp_flags, int node);\n--\ninclude/linux/slab.h-1158-\t__alloc_objs(kzalloc, default_gfp(__VA_ARGS__), typeof(P), COUNT)\ninclude/linux/slab.h:1159:#define kzalloc_flex(P, FAM, COUNT, ...)\t\t\\\ninclude/linux/slab.h-1160-\t__alloc_flex(kzalloc, default_gfp(__VA_ARGS__), typeof(P), FAM, COUNT)\n"
} |
| 29/3 | 2026/10/12 13:48 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 39790 output: 35 thoughts: 2222 Thoughts: **Analysis of Storage Element Management Code** Okay, let's break this down systematically. I'm looking at the `kzalloc_flex` call and how it's used with `struct blk_se_report`. I see that `kzalloc` is being used, combined with `struct_size`, which is typical and expected. The key thing is the `copy_from_user` call immediately afterward. I need to make sure I understand the sizes and potential for buffer overflows here. The `sizeof(struct blk_se_report)` is 16 bytes. `copy_from_user` is reading user-provided data into this buffer, so that's where a potential vulnerability might lie. If the copy succeeds, the first 16 bytes of the kernel buffer are overwritten. Then there's a check on `rep->nr_elements`. This is important because it validates a user-provided value. Subsequently, `nr_reported_elements` is set to the maximum value, and then `bdev_report_storage_elements` is called, likely to populate the `rep->elements` array. Now the `copy_to_user` call concerns me. It's the *output* operation where the kernel is copying data to user space. The critical point here is what happens if the user-space buffer `argp` is smaller than the calculated size from `struct_size(...)`. `copy_to_user` should return `-EFAULT` to signal a fault. And the kernel-side buffer, `rep`, was allocated via `kzalloc_flex` with a size determined by `struct_size`. Since `rep->nr_reported_elements` can't exceed `disk->nr_storage_elements`, and since `kzalloc` zero-initializes the allocated memory, I don't see an obvious uninitialized memory read here, assuming the logic to check `rep->nr_elements` holds. `kzalloc` guarantees that everything is initialized to zero, which is good. `bdev_report_storage_elements` populates the fields; in the examples I'm looking at (zloop, sd_zbc), this is happening safely. I need to confirm the structure layouts, especially the presence or absence of padding which could lead to vulnerabilities. I'm re-checking `struct blk_se`: it seems to be 32 bytes with no padding. Then I look at `struct blk_se_report`, and confirm that it's 16 bytes with no padding. Now I'm looking at the ioctl calls, specifically `BLKREMOVESTORELEM`. I see a `get_user` call to read `element_id`, which is a single `unsigned int`. The potential vulnerability here would be related to integer overflow. `BLKRESTORESTORELEMS` does no output to user memory. Now checking `bdev_remove_storage_element` and `bdev_restore_storage_elements`. Both use `kzalloc_objs` to allocate memory for the `elements` array. This also seems safe, assuming the usual `kzalloc` guarantees of zero-initialization. Finally, I'm checking `disk_se_mgmt_in_progress`. Here, I was concerned about a potential uninitialized read of the `in_progress` variable. However, I now see the code correctly handles the return value of `disk_se_mgmt_in_progress`. The code short-circuits to avoid checking `in_progress` if an error occurs. So, `in_progress` is always initialized before it's read. I'm now checking `zloop`, and checking the initialization of `zloop_options`. Both fields are initialized: `opts->stor_elements` and `opts->element_id`. In `zloop_ctl_degrade_element`, an instance `zloop_options opts` is declared and used. Overall, it seems the code has been written defensively. The use of `kzalloc` with zero-initialization, the checks on user-provided values, and the careful handling of memory copies seem to mitigate the risk of many common vulnerabilities. However, I would need to analyze the `bdev_report_storage_elements` function for any potential issues. Also, I need to investigate the integer overflow potential associated with `element_id`. |
| 30/3 | 2026/10/12 13:49 | tool | grepper |
0mArgs:{
"Expression": "zloop_ctl_write",
"PathPrefixes": [
"drivers/block/zloop.c"
]
}Results: {
"Output": "drivers/block/zloop.c=2295=static struct zloop_ctl_op {\n--\ndrivers/block/zloop.c-2304-\ndrivers/block/zloop.c:2305:static ssize_t zloop_ctl_write(struct file *file, const char __user *ubuf,\ndrivers/block/zloop.c-2306-\t\t\t size_t count, loff_t *pos)\n--\ndrivers/block/zloop.c=2405=static const struct file_operations zloop_ctl_fops = {\n--\ndrivers/block/zloop.c-2408-\t.release\t= zloop_ctl_release,\ndrivers/block/zloop.c:2409:\t.write\t\t= zloop_ctl_write,\ndrivers/block/zloop.c-2410-\t.read\t\t= seq_read,\n"
} |
| 31/3 | 2026/10/12 13:49 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 43219 output: 37 thoughts: 23 Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience: **Diving into zloop_ctl_write** Alright, let's get down to brass tacks. My initial instinct, given that context, is to immediately pull up `drivers/block/zloop.c` and zero in on the `zloop_ctl_write` function. This is where the magic (or potential headache) for user-space control of the zloop device likely resides. My expectation is that this function will be the entry point for handling commands or data sent through the control interface, typically accessed via a character device node or a sysfs entry. I’m anticipating the presence of a switch statement or series of if-else checks to parse and route the incoming data based on some defined control protocol. The real interest lies in how these commands are validated, what sanity checks are performed before making changes to internal data structures, and how it handles potential race conditions, concurrent access or interrupts. I will be looking out for any clues on how commands like device creation, deletion, configuration or read/write requests are handled, as this is all crucial for evaluating the zloop implementation and its overall reliability. Of course, the use of `copy_from_user` and the associated checks is the first thing I'll check to prevent security flaws. I'll also be watching for any usage of spinlocks, mutexes, or other synchronization primitives to ensure data integrity during potentially concurrent control operations. Let's see what we find. |
| 32/3 | 2026/10/12 13:49 | tool | read-file |
0mArgs:{
"File": "drivers/block/zloop.c",
"FirstLine": 2305,
"LineCount": 55
}Results: {
"Contents": "2305:\tstatic ssize_t zloop_ctl_write(struct file *file, const char __user *ubuf,\n2306:\t\t\t\t size_t count, loff_t *pos)\n2307:\t{\n2308:\t\tstruct zloop_options opts = { };\n2309:\t\tstruct zloop_ctl_op *op;\n2310:\t\tconst char *buf, *opts_buf;\n2311:\t\tint i, ret;\n2312:\t\n2313:\t\tif (count \u003e PAGE_SIZE)\n2314:\t\t\treturn -ENOMEM;\n2315:\t\n2316:\t\tbuf = memdup_user_nul(ubuf, count);\n2317:\t\tif (IS_ERR(buf))\n2318:\t\t\treturn PTR_ERR(buf);\n2319:\t\n2320:\t\tfor (i = 0; i \u003c ARRAY_SIZE(zloop_ctl_ops); i++) {\n2321:\t\t\top = \u0026zloop_ctl_ops[i];\n2322:\t\t\tif (!op-\u003ename) {\n2323:\t\t\t\tpr_err(\"Invalid operation\\n\");\n2324:\t\t\t\tret = -EINVAL;\n2325:\t\t\t\tgoto out;\n2326:\t\t\t}\n2327:\t\t\tif (!strncmp(buf, op-\u003ename, strlen(op-\u003ename)))\n2328:\t\t\t\tbreak;\n2329:\t\t}\n2330:\t\n2331:\t\tif (count \u003c= strlen(op-\u003ename))\n2332:\t\t\topts_buf = NULL;\n2333:\t\telse\n2334:\t\t\topts_buf = buf + strlen(op-\u003ename);\n2335:\t\n2336:\t\tret = zloop_parse_options(\u0026opts, opts_buf);\n2337:\t\tif (ret) {\n2338:\t\t\tpr_err(\"Failed to parse options\\n\");\n2339:\t\t\tgoto out;\n2340:\t\t}\n2341:\t\n2342:\t\tswitch (op-\u003ecode) {\n2343:\t\tcase ZLOOP_CTL_ADD:\n2344:\t\t\tret = zloop_ctl_add(\u0026opts);\n2345:\t\t\tbreak;\n2346:\t\tcase ZLOOP_CTL_REMOVE:\n2347:\t\t\tret = zloop_ctl_remove(\u0026opts);\n2348:\t\t\tbreak;\n2349:\t\tcase ZLOOP_CTL_DEGRADE_ELEMENT:\n2350:\t\t\tret = zloop_ctl_degrade_element(\u0026opts);\n2351:\t\t\tbreak;\n2352:\t\tdefault:\n2353:\t\t\tpr_err(\"Invalid operation\\n\");\n2354:\t\t\tret = -EINVAL;\n2355:\t\t\tgoto out;\n2356:\t\t}\n2357:\t\n2358:\tout:\n2359:\t\tkfree(opts.base_dir);\n"
} |
| 33/3 | 2026/10/12 13:49 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 44362 output: 21 thoughts: 1343 Thoughts: **Analyzing Storage Element Initialization and Usage** Okay, so I'm diving into this code. First, the `opts` struct is zero-initialized, and default values are set by `zloop_parse_options`. Now, let's look at `zloop_report_elements`. It seems pretty straightforward; it retrieves storage element information for a ZLOOP device. It acquires a mutex lock to protect `zlo->stor_elements_lock`, and then it copies data from `zlo->elements` into the provided `elements` buffer. The code checks the status of each element and sets `restore_allowed` based on its status. Seems solid. But, hold on. Where exactly is `zlo->elements` allocated and initialized? Let's check `zloop_create_storage_elements`. Ah, there it is! `zlo->elements` is allocated using `kzalloc_objs`, ensuring all the memory is zeroed out from the get-go. Nice, that avoids any potential uninitialized memory reads. The loop then initializes the `id`, `type`, and `status` fields of each element. In the `ZLOOP_STOR_ELEMENTS_PAIRS` case, it's setting up paired read/write elements. Seems like everything should be safe there. Alright, let's keep going. Looking at `zloop_init_zone`, I see that it indexes into `zlo->elements` to track the number of zones associated with each storage element. It appears we initialize the read elements and keep `nr_zones` in sync. Good. I see that everything in `zlo->elements` is initialized and `kzalloc_objs` zeroes everything, including `reserved`. Now, something catches my eye. Let's look at `sd_zbc_report_storage_elements`. We have this buffer, `buf`, allocated using `kzalloc`. OK, so this buffer will be zero initialized, so that's good. The code then executes a SCSI command, and the device is returning data into this `buf`. Now, look at how the code parses the returned data: `desc = &buf[32];` and `sd_zbc_parse_storage_element(sdkp, desc, &elements[i]);`. Inside `sd_zbc_parse_storage_element`, there's a `memset(element, 0, sizeof(*element));`. This confirms that *element* is fully zeroed, and that all data from `desc` is being safely written. Finally, the `disk_zone_is_offline` and its caller in `drivers/scsi/sd.c` checks if a zone is offline before allowing read/write operations. It calls `disk_zone_get_cond` to determine the zone's condition. Hmm, does `disk_zone_get_cond` potentially read uninitialized memory? No, looks safe, since the storage elements are initialized. Seems like everything is initialized and handled correctly. |
| 34/3 | 2026/10/12 13:50 | tool | grepper |
0mArgs:{
"Expression": "disk_zone_get_cond"
}Results: {
"Output": "block/blk-zoned.c=251=static inline u8 disk_zone_get_state(struct gendisk *disk, sector_t sector)\n--\nblock/blk-zoned.c-266-\nblock/blk-zoned.c:267:static enum blk_zone_cond disk_zone_get_cond(struct gendisk *disk,\nblock/blk-zoned.c-268-\t\t\t\t\t sector_t sector)\n--\nblock/blk-zoned.c=282=static inline bool disk_zone_is_offline_or_readonly(struct gendisk *disk,\n--\nblock/blk-zoned.c-284-{\nblock/blk-zoned.c:285:\tenum blk_zone_cond cond = disk_zone_get_cond(disk, sector);\nblock/blk-zoned.c-286-\n--\nblock/blk-zoned.c=304=bool disk_zone_is_offline(struct gendisk *disk, sector_t sector)\n--\nblock/blk-zoned.c-308-\nblock/blk-zoned.c:309:\treturn disk_zone_get_cond(disk, sector) == BLK_ZONE_COND_OFFLINE;\nblock/blk-zoned.c-310-}\n"
} |
| 35/3 | 2026/10/12 13:50 | llm | kmsan-evaluator |
0mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 46679 output: 35 thoughts: 402 Thoughts: **Analyzing Zone Revalidation and Conditional Logic**
Okay, let's break this down. My initial thought was to consider `disk_zone_get_cond`, since it seems like a relevant helper. But wait, I need to zoom out and look at the bigger picture with `disk_revalidate_zones`. The patch refactors `blk_revalidate_disk_zones` to call it. Now, within `blk-zoned.c`, I'm seeing `disk_revalidate_zones` using a `blk_revalidate_zone_args` struct. The critical point is that `args` is zero-initialized with `{ }`, which means no changes there from the previous implementation.
Now I need to ensure there are no unintended consequences when setting the zone condition, and to do this, let's move to `zloop_set_zone_cond`. Okay, so `zloop_set_zone_cond` explicitly assigns to all fields it touches. Good, that minimizes the chances of uninitialized variables causing issues. We remove a zone from the LRU, set the condition (`zone->cond`), and appropriately set `zone->wp` based on whether it is empty or not.
I'm now curious about the `zloop_zone` structure itself. Let's look at `struct zloop_zone`. Hmm, I see `wr_se_id` and `rd_se_id` - these seem relevant, but I need to understand the larger context.
Where is `struct zloop_device` allocated? This is crucial for understanding the lifetime and scope of these zones. I need to track down the allocation and initialization. Let's check `zloop_ctl_add` to see if that gives me the information I need.
|
| 36/3 | 2026/10/12 13:50 | tool | grepper |
0mArgs:{
"Expression": "kvzalloc.*zlo",
"PathPrefixes": [
"drivers/block/zloop.c"
]
}Results: {
"Output": "drivers/block/zloop.c=1749=static int zloop_ctl_add(struct zloop_options *opts)\n--\ndrivers/block/zloop.c-1776-\ndrivers/block/zloop.c:1777:\tzlo = kvzalloc_flex(*zlo, zones, nr_zones);\ndrivers/block/zloop.c-1778-\tif (!zlo) {\n"
} |
| 37/3 | 2026/10/12 13:50 | llm | kmsan-evaluator |
1mModel:gemini-3.8-flash Args: null Results: null Tokens: input: 47604 output: 550 thoughts: 5029 Thoughts: **Analysis of Storage Element Management Initialization and Data Flow** Okay, let's break this down. My primary concern is ensuring that we're not leaking uninitialized kernel memory to user space, or that there aren't any potential reads of uninitialized memory within the kernel itself. We're dealing with new storage element management functionality, so I need to be *thorough*. First, `zlo` and its `zones` are *completely* zeroed out during allocation with `kvzalloc_flex`. This includes all the new `zloop_device` fields. No surprises there, but I need to make *sure*. `zloop_queue_rq` doesn't have any uninitialized memory concerns, given the checks in place and the explicit allocation pattern. The `blkdev_get_nr_storage_elements_ioctl` uses `disk->nr_storage_elements` (unsigned int in `struct gendisk`). `struct gendisk` is allocated with `kzalloc`, so `disk->nr_storage_elements` starts at zero. `disk_init_storage_element_mgmt` is called and, if it succeeds or the disk isn't zoned or there are no suitable `se_ops`, we get a zero value for that field (either by kzalloc or by the function resetting it). That's critical, but expected. Now, the `report_elements` function is my main concern here. The kernel calls the disk driver's `report_elements` function. `zloop_report_elements` reads the count from already initialized fields. `sd_zbc_report_storage_elements` also reads from an explicitly initialized element. These are fine. The ioctl handler in `block/blk-zoned.c` checks `!disk->nr_storage_elements`. If it's zero, then the ioctl returns `-ENOTTY`, which is expected. Next is `blkdev_report_storage_elements_ioctl`. This is a *complex* flow. We allocate `rep` with `kzalloc_flex`. *Everything* in `rep` starts zeroed. We copy data *from* user space *into* it, and *then* we update the number of reported elements. Then we pass data *back* to user space. The key is that `kzalloc_flex` guarantees initialization to zero. Plus, it appears that any `copy_from_user` failure returns `-EFAULT`, meaning the program does not make it to writing values to `rep`. I need to confirm if there are any padding issues in `struct blk_se` or `struct blk_se_report` between 32-bit and 64-bit architectures, specifically x86-64. *No padding*. This is a critical detail. Zero is guaranteed. I need to make sure, so I will check `blk-zoned.c` too. Now, let's be *absolutely* sure. Is there any uninitialized memory read *anywhere* else? We check `zloop_remove_element_work`. The switch statement handling `se->type` has an uninitialized variable, `cond`. However, `se->type` is set in the `zloop_create_storage_elements` path and initialized. It's only modified in kernel space. No risk there. Okay, let's step back and consider the *KMSAN guidelines*. I have to be honest with myself. There is no *kernel memory* being leaked to userspace and the values are all explicitly initialized. There's no conditional logic that depends on uninitialized data. There are no allocations where uninitialized fields are read later. There are no bounds issues that might expose uninitialized data. All allocations use `kzalloc` or `kzalloc_flex`. There is the potential for an integer overflow in `zloop_remove_element` leading to out of bounds access, which is caught by KASAN. I need to check the patch files carefully. I see initialization to zero in several places. The structs are clean. No padding on x86-64. There's no evidence of a KMSAN issue here. I have *carefully* reviewed the entire patch set. All allocations are zeroed. There is no uninitialized kernel memory being leaked to user space or read within the kernel. KASAN is sufficient for the potential bugs. My conclusion: `NeedsKMSAN=false`. |
| 38/3 | 2026/10/12 13:51 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch introduces storage element management for zoned block devices, including new ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) and support in the zloop and sd drivers.\n\n1. Kernel-to-User Information Leaks:\n - In `blkdev_get_nr_storage_elements_ioctl()`, `put_user` copies `disk-\u003enr_storage_elements`, which is an explicitly initialized scalar (`unsigned int`).\n - In `blkdev_report_storage_elements_ioctl()`, the report buffer is allocated with `kzalloc_flex()`, ensuring all payload bytes and potential padding are zeroed.\n - The UAPI structures `struct blk_se` (32 bytes) and `struct blk_se_report` (16 bytes header) are explicitly laid out with aligned fields and reserved byte padding (`reserved[13]` and `reserved`), leaving no compiler padding holes on 64-bit architectures.\n - In driver implementations: `sd_zbc_parse_storage_element()` starts with `memset(element, 0, sizeof(*element))`, and `zloop_create_storage_elements()` allocates descriptors via `kzalloc_objs()`.\n\n2. Uninitialized Memory Access in Control Flow:\n - All stack and heap buffers introduced by the patch (`opts` in `zloop_ctl_write`, `args` in `disk_revalidate_zones`, `elements` in `bdev_remove_storage_element` / `bdev_restore_storage_elements`) are zero-initialized (`{ }`, `kzalloc_objs`, etc.).\n - Variables controlling branching (e.g., `in_progress` in `disk_wait_for_se_mgmt_completion()`) are strictly set upon success (`ret == 0`) before being evaluated.\n\n3. KASAN vs KMSAN applicability:\n - Any potential risks introduced in the patch (such as out-of-bounds indexing in `zloop_remove_element` if `element_id == 0`, concurrency/locking with workqueues, or block queue state issues) fall under out-of-bounds, synchronization, or logic errors, which are detected by KASAN and LOCKDEP.\n - No uninitialized memory reads or information leak vulnerabilities are introduced that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch introduces storage element management for zoned block devices, including new ioctls (BLKGETNRSTORELEMS, BLKREPORTSTORELEMS, BLKREMOVESTORELEM, BLKRESTORESTORELEMS) and support in the zloop and sd drivers.\n\n1. Kernel-to-User Information Leaks:\n - In `blkdev_get_nr_storage_elements_ioctl()`, `put_user` copies `disk-\u003enr_storage_elements`, which is an explicitly initialized scalar (`unsigned int`).\n - In `blkdev_report_storage_elements_ioctl()`, the report buffer is allocated with `kzalloc_flex()`, ensuring all payload bytes and potential padding are zeroed.\n - The UAPI structures `struct blk_se` (32 bytes) and `struct blk_se_report` (16 bytes header) are explicitly laid out with aligned fields and reserved byte padding (`reserved[13]` and `reserved`), leaving no compiler padding holes on 64-bit architectures.\n - In driver implementations: `sd_zbc_parse_storage_element()` starts with `memset(element, 0, sizeof(*element))`, and `zloop_create_storage_elements()` allocates descriptors via `kzalloc_objs()`.\n\n2. Uninitialized Memory Access in Control Flow:\n - All stack and heap buffers introduced by the patch (`opts` in `zloop_ctl_write`, `args` in `disk_revalidate_zones`, `elements` in `bdev_remove_storage_element` / `bdev_restore_storage_elements`) are zero-initialized (`{ }`, `kzalloc_objs`, etc.).\n - Variables controlling branching (e.g., `in_progress` in `disk_wait_for_se_mgmt_completion()`) are strictly set upon success (`ret == 0`) before being evaluated.\n\n3. KASAN vs KMSAN applicability:\n - Any potential risks introduced in the patch (such as out-of-bounds indexing in `zloop_remove_element` if `element_id == 0`, concurrency/locking with workqueues, or block queue state issues) fall under out-of-bounds, synchronization, or logic errors, which are detected by KASAN and LOCKDEP.\n - No uninitialized memory reads or information leak vulnerabilities are introduced that would require a dedicated KMSAN fuzzing session.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|