| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/09/08 09:18 | flow | patch-triage |
0mArgs:null Results: {
"EnableConfigs": [
"BLK_DEV_ZONED"
],
"FocusSymbols": [
"blk_revalidate_disk_zones",
"bdev_zone_mgmt_allowed",
"bdev_zone_is_seq",
"blkdev_get_zone_info",
"disk_zone_wplug_sync_state",
"disk_zone_wplug_abort",
"disk_revalidate_zone_resources",
"disk_revalidate_capacity",
"disk_alloc_zone_resources",
"blk_revalidate_seq_zone",
"blk_revalidate_conv_zone"
],
"KMSANReasoning": "The patch refactors zoned block device condition and state tracking (introducing enum blk_zstate to pack zone condition and conventional zone flag into a single u8 array per disk), adds mutex synchronization around zone revalidation, and updates zone management operation checks.\n\nAll allocated memory and data structures touched by the patch are fully initialized:\n1. The dynamic `zones_state` array is allocated using `kzalloc()`.\n2. Stack structures such as `struct blk_revalidate_zone_args` and `struct blk_zone` are explicitly zero-initialized (`{ }` / `memset`).\n3. Return paths and condition/state helper functions (`disk_zone_get_state`, `blk_zstate_to_zone_cond`, etc.) assign all outputs and default cases cleanly.\n4. No uninitialized memory or structure padding is exposed or copied to user space.\n\nAny potential memory safety issues (e.g. array index bounds, concurrency, use-after-free during zone write plug teardown, or lock ordering with `zone_revalidate_mutex`) are covered by KASAN and LOCKDEP. KMSAN is not required.",
"NeedsKMSAN": false,
"Reasoning": "The patch modifies the block layer's zoned device handling, including zone condition and state tracking (switching from zones_cond to zones_state), revalidation of zoned devices with synchronization under zone_revalidate_mutex, handling of offline/read-only zones, zone write plug aborts and dead state checks, and zone management validation during bio submission. These reachable core block layer changes warrant fuzzing.",
"WorthFuzzing": true
} |
| 1/1 | 2026/09/08 09:18 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 9514449fcfb821c5f2bb5c88be27acd256a43195\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Tue Sep 8 09:18:29 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/block/blk-core.c b/block/blk-core.c\nindex 196bccf27f58d..13dc70e8f55d9 100644\n--- a/block/blk-core.c\n+++ b/block/blk-core.c\n@@ -901,8 +901,11 @@ void submit_bio_noacct(struct bio *bio)\n \tcase REQ_OP_ZONE_CLOSE:\n \tcase REQ_OP_ZONE_RESET:\n \tcase REQ_OP_ZONE_FINISH:\n-\t\t/* Zone management operations require sequential zones. */\n-\t\tif (!bdev_zone_is_seq(bio-\u003ebi_bdev, bio-\u003ebi_iter.bi_sector))\n+\t\t/*\n+\t\t * Zone management operations require sequential zones that are\n+\t\t * not offline nor read-only.\n+\t\t */\n+\t\tif (!bdev_zone_mgmt_allowed(bdev, bio-\u003ebi_iter.bi_sector))\n \t\t\tgoto end_io;\n \t\tbreak;\n \tcase REQ_OP_ZONE_RESET_ALL:\ndiff --git a/block/blk-zoned.c b/block/blk-zoned.c\nindex a5afb842bf35e..ec510d66dfe2b 100644\n--- a/block/blk-zoned.c\n+++ b/block/blk-zoned.c\n@@ -39,6 +39,103 @@ static const char *const zone_cond_name[] = {\n };\n #undef ZONE_COND_NAME\n \n+/*\n+ * Internal and compact representation of enum blk_zone_cond values for zone\n+ * conditions. All these values fit into 4-bits, allowing using the high order\n+ * bits as the zone type.\n+ */\n+enum blk_zstate {\n+\tBLK_ZSTATE_NOT_WP\t= 0x00,\n+\tBLK_ZSTATE_EMPTY\t= 0x01,\n+\tBLK_ZSTATE_IMP_OPEN\t= 0x02,\n+\tBLK_ZSTATE_EXP_OPEN\t= 0x03,\n+\tBLK_ZSTATE_CLOSED\t= 0x04,\n+\tBLK_ZSTATE_READONLY\t= 0x05,\n+\tBLK_ZSTATE_FULL\t\t= 0x06,\n+\tBLK_ZSTATE_OFFLINE\t= 0x07,\n+\tBLK_ZSTATE_ACTIVE\t= 0x08,\n+\n+\tBLK_ZSTATE_COND_MASK\t= 0x0F,\n+\n+\t/* Conventional zone. */\n+\tBLK_ZFLAG_CONV\t\t= 0x80,\n+\tBLK_ZSTATE_FLAGS_MASK\t= ~BLK_ZSTATE_COND_MASK,\n+};\n+\n+/*\n+ * Lookup table and helper to convert enum blk_zstate conditions into enum\n+ * blk_zone_condition values.\n+ */\n+static const u8 blk_zstate2zcond[] = {\n+\t[BLK_ZSTATE_NOT_WP]\t= BLK_ZONE_COND_NOT_WP,\n+\t[BLK_ZSTATE_EMPTY]\t= BLK_ZONE_COND_EMPTY,\n+\t[BLK_ZSTATE_IMP_OPEN]\t= BLK_ZONE_COND_IMP_OPEN,\n+\t[BLK_ZSTATE_EXP_OPEN]\t= BLK_ZONE_COND_EXP_OPEN,\n+\t[BLK_ZSTATE_CLOSED]\t= BLK_ZONE_COND_CLOSED,\n+\t[BLK_ZSTATE_READONLY]\t= BLK_ZONE_COND_READONLY,\n+\t[BLK_ZSTATE_FULL]\t= BLK_ZONE_COND_FULL,\n+\t[BLK_ZSTATE_OFFLINE]\t= BLK_ZONE_COND_OFFLINE,\n+\t[BLK_ZSTATE_ACTIVE]\t= BLK_ZONE_COND_ACTIVE,\n+};\n+\n+static inline enum blk_zone_cond blk_zstate_to_zone_cond(enum blk_zstate zs)\n+{\n+\tu8 idx = zs \u0026 BLK_ZSTATE_COND_MASK;\n+\n+\tif (WARN_ON_ONCE(idx \u003e= ARRAY_SIZE(blk_zstate2zcond)))\n+\t\treturn 0;\n+\n+\treturn blk_zstate2zcond[idx];\n+}\n+\n+/*\n+ * Lookup table and helper to convert an enum blk_zone_condition into an enum\n+ * blk_zstate condition value. To keep the lookup table small, the\n+ * BLK_ZONE_COND_ACTIVE condition is not added and handled separately.\n+ */\n+static const u8 blk_zcond2zstate[] = {\n+\t[BLK_ZONE_COND_NOT_WP]\t\t= BLK_ZSTATE_NOT_WP,\n+\t[BLK_ZONE_COND_EMPTY]\t\t= BLK_ZSTATE_EMPTY,\n+\t[BLK_ZONE_COND_IMP_OPEN]\t= BLK_ZSTATE_ACTIVE,\n+\t[BLK_ZONE_COND_EXP_OPEN]\t= BLK_ZSTATE_ACTIVE,\n+\t[BLK_ZONE_COND_CLOSED]\t\t= BLK_ZSTATE_ACTIVE,\n+\t[BLK_ZONE_COND_READONLY]\t= BLK_ZSTATE_READONLY,\n+\t[BLK_ZONE_COND_FULL]\t\t= BLK_ZSTATE_FULL,\n+\t[BLK_ZONE_COND_OFFLINE]\t\t= BLK_ZSTATE_OFFLINE,\n+};\n+\n+static inline enum blk_zstate blk_zone_cond_to_zstate(enum blk_zone_cond cond)\n+{\n+\tif (cond == BLK_ZONE_COND_ACTIVE)\n+\t\treturn BLK_ZSTATE_ACTIVE;\n+\n+\tif (WARN_ON_ONCE(cond \u003e= ARRAY_SIZE(blk_zcond2zstate)))\n+\t\treturn 0;\n+\n+\treturn blk_zcond2zstate[cond];\n+}\n+\n+/*\n+ * Combine an enum blk_zone_condition and zone flags into a zones_state array\n+ * entry.\n+ */\n+static inline void blk_zstate_set(u8 *zones_state, unsigned int nr_zones,\n+\t\t\tunsigned int zno, enum blk_zone_cond cond, u8 flags)\n+{\n+\tif (zones_state \u0026\u0026 zno \u003c nr_zones)\n+\t\tzones_state[zno] = flags | blk_zone_cond_to_zstate(cond);\n+}\n+\n+static inline u8 blk_zstate_flags(enum blk_zstate zs)\n+{\n+\treturn zs \u0026 BLK_ZSTATE_FLAGS_MASK;\n+}\n+\n+static inline bool blk_zstate_is_conv(enum blk_zstate zs)\n+{\n+\treturn blk_zstate_flags(zs) \u0026 BLK_ZFLAG_CONV;\n+}\n+\n /*\n * Per-zone write plug.\n * @node: hlist_node structure for managing the plug using a hash table.\n@@ -135,55 +232,64 @@ const char *blk_zone_cond_str(enum blk_zone_cond zone_cond)\n }\n EXPORT_SYMBOL_GPL(blk_zone_cond_str);\n \n-static void blk_zone_set_cond(u8 *zones_cond, unsigned int zno,\n-\t\t\t enum blk_zone_cond cond)\n+static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,\n+\t\t\t enum blk_zone_cond cond)\n {\n-\tif (!zones_cond)\n-\t\treturn;\n+\tunsigned int zno = disk_zone_no(disk, sector);\n+\tu8 *zones_state;\n \n-\tswitch (cond) {\n-\tcase BLK_ZONE_COND_IMP_OPEN:\n-\tcase BLK_ZONE_COND_EXP_OPEN:\n-\tcase BLK_ZONE_COND_CLOSED:\n-\t\tzones_cond[zno] = BLK_ZONE_COND_ACTIVE;\n-\t\treturn;\n-\tcase BLK_ZONE_COND_NOT_WP:\n-\tcase BLK_ZONE_COND_EMPTY:\n-\tcase BLK_ZONE_COND_FULL:\n-\tcase BLK_ZONE_COND_OFFLINE:\n-\tcase BLK_ZONE_COND_READONLY:\n-\tdefault:\n-\t\tzones_cond[zno] = cond;\n-\t\treturn;\n-\t}\n+\trcu_read_lock();\n+\tzones_state = rcu_dereference(disk-\u003ezones_state);\n+\tif (likely(zones_state \u0026\u0026 zno \u003c disk-\u003enr_zones))\n+\t\tblk_zstate_set(zones_state, disk-\u003enr_zones, zno, cond,\n+\t\t\t blk_zstate_flags(zones_state[zno]));\n+\trcu_read_unlock();\n }\n \n-static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,\n-\t\t\t enum blk_zone_cond cond)\n+static inline u8 disk_zone_get_state(struct gendisk *disk, sector_t sector)\n {\n-\tu8 *zones_cond;\n+\tunsigned int zno = disk_zone_no(disk, sector);\n+\tu8 *zones_state, zs;\n \n \trcu_read_lock();\n-\tzones_cond = rcu_dereference(disk-\u003ezones_cond);\n-\tif (zones_cond) {\n-\t\tunsigned int zno = disk_zone_no(disk, sector);\n-\n-\t\t/*\n-\t\t * The condition of a conventional, readonly and offline zones\n-\t\t * never changes, so do nothing if the target zone is in one of\n-\t\t * these conditions.\n-\t\t */\n-\t\tswitch (zones_cond[zno]) {\n-\t\tcase BLK_ZONE_COND_NOT_WP:\n-\t\tcase BLK_ZONE_COND_READONLY:\n-\t\tcase BLK_ZONE_COND_OFFLINE:\n-\t\t\tbreak;\n-\t\tdefault:\n-\t\t\tblk_zone_set_cond(zones_cond, zno, cond);\n-\t\t\tbreak;\n-\t\t}\n-\t}\n+\tzones_state = rcu_dereference(disk-\u003ezones_state);\n+\tif (likely(zones_state \u0026\u0026 zno \u003c disk-\u003enr_zones))\n+\t\tzs = zones_state[zno];\n+\telse\n+\t\tzs = BLK_ZFLAG_CONV;\n \trcu_read_unlock();\n+\n+\treturn zs;\n+}\n+\n+static enum blk_zone_cond disk_zone_get_cond(struct gendisk *disk,\n+\t\t\t\t\t sector_t sector)\n+{\n+\tu8 zs = disk_zone_get_state(disk, sector);\n+\n+\treturn blk_zstate_to_zone_cond(zs);\n+}\n+\n+static inline bool\n+disk_zone_cond_is_offline_or_readonly(enum blk_zone_cond cond)\n+{\n+\treturn cond == BLK_ZONE_COND_READONLY ||\n+\t\tcond == BLK_ZONE_COND_OFFLINE;\n+}\n+\n+static inline bool disk_zone_is_offline_or_readonly(struct gendisk *disk,\n+\t\t\t\t\t\t sector_t sector)\n+{\n+\tenum blk_zone_cond cond = disk_zone_get_cond(disk, sector);\n+\n+\treturn disk_zone_cond_is_offline_or_readonly(cond);\n+}\n+\n+static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector)\n+{\n+\tu8 zs = disk_zone_get_state(disk, sector);\n+\n+\treturn !blk_zstate_is_conv(zs);\n }\n \n /**\n@@ -195,23 +301,37 @@ static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,\n */\n bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)\n {\n-\tstruct gendisk *disk = bdev-\u003ebd_disk;\n-\tunsigned int zno = disk_zone_no(disk, sector);\n-\tbool is_seq = false;\n-\tu8 *zones_cond;\n+\tif (!bdev_is_zoned(bdev))\n+\t\treturn false;\n+\n+\treturn disk_zone_is_seq(bdev-\u003ebd_disk, sector);\n+}\n+EXPORT_SYMBOL_GPL(bdev_zone_is_seq);\n+\n+/**\n+ * bdev_zone_mgmt_allowed - check if management operations are allowed on a zone\n+ * @bdev: block device to check\n+ * @sector: sector number\n+ *\n+ * Check if the zone containing @sector on @bdev can be a target for a zone\n+ * management operation, that is, if the zone is a sequential write required\n+ * zone that is not offline nor read-only.\n+ */\n+bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector)\n+{\n+\tenum blk_zone_cond cond;\n+\tu8 zs;\n \n \tif (!bdev_is_zoned(bdev))\n \t\treturn false;\n \n-\trcu_read_lock();\n-\tzones_cond = rcu_dereference(disk-\u003ezones_cond);\n-\tif (zones_cond \u0026\u0026 zno \u003c disk-\u003enr_zones)\n-\t\tis_seq = zones_cond[zno] != BLK_ZONE_COND_NOT_WP;\n-\trcu_read_unlock();\n+\tzs = disk_zone_get_state(bdev-\u003ebd_disk, sector);\n+\tif (blk_zstate_is_conv(zs))\n+\t\treturn false;\n \n-\treturn is_seq;\n+\tcond = blk_zstate_to_zone_cond(zs);\n+\treturn !disk_zone_cond_is_offline_or_readonly(cond);\n }\n-EXPORT_SYMBOL_GPL(bdev_zone_is_seq);\n \n /*\n * Zone report arguments for block device drivers report_zones operation.\n@@ -500,12 +620,17 @@ static bool disk_zone_wplug_is_full(struct gendisk *disk,\n \treturn zwplug-\u003ewp_offset \u003e= disk-\u003elast_zone_capacity;\n }\n \n+static bool disk_zone_wplug_is_offline_or_readonly(struct blk_zone_wplug *zwplug)\n+{\n+\treturn disk_zone_cond_is_offline_or_readonly(zwplug-\u003econd);\n+}\n+\n static bool disk_insert_zone_wplug(struct gendisk *disk,\n \t\t\t\t struct blk_zone_wplug *zwplug)\n {\n \tstruct blk_zone_wplug *zwplg;\n \tunsigned long flags;\n-\tu8 *zones_cond;\n+\tu8 *zones_state;\n \tunsigned int idx =\n \t\thash_32(zwplug-\u003ezone_no, disk-\u003ezone_wplugs_hash_bits);\n \n@@ -524,15 +649,16 @@ static bool disk_insert_zone_wplug(struct gendisk *disk,\n \t}\n \n \t/*\n-\t * Set the zone condition: if we do not yet have a zones_cond array\n+\t * Set the zone condition: if we do not yet have a zones_state array\n \t * attached to the disk, then this is a zone write plug insert from the\n \t * first call to blk_revalidate_disk_zones(), in which case the zone is\n \t * necessarilly in the active condition.\n \t */\n-\tzones_cond = rcu_dereference_check(disk-\u003ezones_cond,\n+\tzones_state = rcu_dereference_check(disk-\u003ezones_state,\n \t\t\t\tlockdep_is_held(\u0026disk-\u003ezone_wplugs_hash_lock));\n-\tif (zones_cond)\n-\t\tzwplug-\u003econd = zones_cond[zwplug-\u003ezone_no];\n+\tif (zones_state)\n+\t\tzwplug-\u003econd =\n+\t\t\tblk_zstate_to_zone_cond(zones_state[zwplug-\u003ezone_no]);\n \telse\n \t\tzwplug-\u003econd = BLK_ZONE_COND_ACTIVE;\n \n@@ -574,6 +700,26 @@ static inline struct blk_zone_wplug *disk_get_zone_wplug(struct gendisk *disk,\n \treturn disk_get_hashed_zone_wplug(disk, sector);\n }\n \n+static void disk_for_all_zone_wplugs(struct gendisk *disk,\n+\t\t\t\t void (*actor)(struct blk_zone_wplug *,\n+\t\t\t\t\t\t void *),\n+\t\t\t\t void *data)\n+{\n+\tstruct blk_zone_wplug *zwplug;\n+\tunsigned int i;\n+\n+\tif (!disk-\u003ezone_wplugs_hash)\n+\t\treturn;\n+\n+\trcu_read_lock();\n+\tfor (i = 0; i \u003c disk_zone_wplugs_hash_size(disk); i++) {\n+\t\thlist_for_each_entry_rcu(zwplug, \u0026disk-\u003ezone_wplugs_hash[i],\n+\t\t\t\t\t node)\n+\t\t\tactor(zwplug, data);\n+\t}\n+\trcu_read_unlock();\n+}\n+\n static void disk_free_zone_wplug_rcu(struct rcu_head *rcu_head)\n {\n \tstruct blk_zone_wplug *zwplug =\n@@ -592,9 +738,9 @@ static void disk_free_zone_wplug(struct blk_zone_wplug *zwplug)\n \tWARN_ON_ONCE(!bio_list_empty(\u0026zwplug-\u003ebio_list));\n \n \tspin_lock_irqsave(\u0026disk-\u003ezone_wplugs_hash_lock, flags);\n-\tblk_zone_set_cond(rcu_dereference_check(disk-\u003ezones_cond,\n+\tblk_zstate_set(rcu_dereference_check(disk-\u003ezones_state,\n \t\t\t\tlockdep_is_held(\u0026disk-\u003ezone_wplugs_hash_lock)),\n-\t\t\t zwplug-\u003ezone_no, zwplug-\u003econd);\n+\t\t disk-\u003enr_zones, zwplug-\u003ezone_no, zwplug-\u003econd, 0);\n \thlist_del_init_rcu(\u0026zwplug-\u003enode);\n \tatomic_dec(\u0026disk-\u003enr_zone_wplugs);\n \tspin_unlock_irqrestore(\u0026disk-\u003ezone_wplugs_hash_lock, flags);\n@@ -608,6 +754,53 @@ static inline void disk_put_zone_wplug(struct blk_zone_wplug *zwplug)\n \t\tdisk_free_zone_wplug(zwplug);\n }\n \n+static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,\n+\t\t\t\t\t struct bio *bio)\n+{\n+\tstruct request_queue *q = zwplug-\u003edisk-\u003equeue;\n+\n+\tbio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);\n+\tbio_io_error(bio);\n+\tdisk_put_zone_wplug(zwplug);\n+\t/* Drop the reference taken by disk_zone_wplug_add_bio(). */\n+\tblk_queue_exit(q);\n+}\n+\n+/*\n+ * Abort (fail) all plugged BIOs of a zone write plug.\n+ */\n+static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)\n+{\n+\tstruct gendisk *disk = zwplug-\u003edisk;\n+\tstruct bio *bio;\n+\n+\tlockdep_assert_held(\u0026zwplug-\u003elock);\n+\n+\tif (bio_list_empty(\u0026zwplug-\u003ebio_list))\n+\t\treturn;\n+\n+\tpr_warn_ratelimited(\"%s: zone %u: Aborting plugged BIOs\\n\",\n+\t\t\t zwplug-\u003edisk-\u003edisk_name, zwplug-\u003ezone_no);\n+\twhile ((bio = bio_list_pop(\u0026zwplug-\u003ebio_list)))\n+\t\tblk_zone_wplug_bio_io_error(zwplug, bio);\n+\n+\tzwplug-\u003eflags \u0026= ~BLK_ZONE_WPLUG_PLUGGED;\n+\n+\t/*\n+\t * If we are using the per disk zone write plugs worker thread, remove\n+\t * the zone write plug from the work list and drop the reference we\n+\t * took when the zone write plug was added to that list.\n+\t */\n+\tif (blk_queue_zoned_qd1_writes(disk-\u003equeue)) {\n+\t\tspin_lock(\u0026disk-\u003ezone_wplugs_list_lock);\n+\t\tif (!list_empty(\u0026zwplug-\u003eentry)) {\n+\t\t\tlist_del_init(\u0026zwplug-\u003eentry);\n+\t\t\tdisk_put_zone_wplug(zwplug);\n+\t\t}\n+\t\tspin_unlock(\u0026disk-\u003ezone_wplugs_list_lock);\n+\t}\n+}\n+\n /*\n * Flag the zone write plug as dead and drop the initial reference we got when\n * the zone write plug was added to the hash table. The zone write plug will be\n@@ -625,6 +818,12 @@ static void disk_mark_zone_wplug_dead(struct blk_zone_wplug *zwplug)\n \n static inline bool disk_check_zone_wplug_dead(struct blk_zone_wplug *zwplug)\n {\n+\tif (disk_zone_wplug_is_offline_or_readonly(zwplug)) {\n+\t\tdisk_zone_wplug_abort(zwplug);\n+\t\tdisk_mark_zone_wplug_dead(zwplug);\n+\t\treturn true;\n+\t}\n+\n \tif (!(zwplug-\u003eflags \u0026 BLK_ZONE_WPLUG_DEAD))\n \t\treturn false;\n \n@@ -708,53 +907,6 @@ static struct blk_zone_wplug *disk_get_or_alloc_zone_wplug(struct gendisk *disk,\n \treturn zwplug;\n }\n \n-static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,\n-\t\t\t\t\t struct bio *bio)\n-{\n-\tstruct request_queue *q = zwplug-\u003edisk-\u003equeue;\n-\n-\tbio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);\n-\tbio_io_error(bio);\n-\tdisk_put_zone_wplug(zwplug);\n-\t/* Drop the reference taken by disk_zone_wplug_add_bio(). */\n-\tblk_queue_exit(q);\n-}\n-\n-/*\n- * Abort (fail) all plugged BIOs of a zone write plug.\n- */\n-static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)\n-{\n-\tstruct gendisk *disk = zwplug-\u003edisk;\n-\tstruct bio *bio;\n-\n-\tlockdep_assert_held(\u0026zwplug-\u003elock);\n-\n-\tif (bio_list_empty(\u0026zwplug-\u003ebio_list))\n-\t\treturn;\n-\n-\tpr_warn_ratelimited(\"%s: zone %u: Aborting plugged BIOs\\n\",\n-\t\t\t zwplug-\u003edisk-\u003edisk_name, zwplug-\u003ezone_no);\n-\twhile ((bio = bio_list_pop(\u0026zwplug-\u003ebio_list)))\n-\t\tblk_zone_wplug_bio_io_error(zwplug, bio);\n-\n-\tzwplug-\u003eflags \u0026= ~BLK_ZONE_WPLUG_PLUGGED;\n-\n-\t/*\n-\t * If we are using the per disk zone write plugs worker thread, remove\n-\t * the zone write plug from the work list and drop the reference we\n-\t * took when the zone write plug was added to that list.\n-\t */\n-\tif (blk_queue_zoned_qd1_writes(disk-\u003equeue)) {\n-\t\tspin_lock(\u0026disk-\u003ezone_wplugs_list_lock);\n-\t\tif (!list_empty(\u0026zwplug-\u003eentry)) {\n-\t\t\tlist_del_init(\u0026zwplug-\u003eentry);\n-\t\t\tdisk_put_zone_wplug(zwplug);\n-\t\t}\n-\t\tspin_unlock(\u0026disk-\u003ezone_wplugs_list_lock);\n-\t}\n-}\n-\n /*\n * Update a zone write plug condition based on the write pointer offset.\n */\n@@ -785,8 +937,10 @@ static void disk_zone_wplug_set_wp_offset(struct gendisk *disk,\n \n \t/* Update the zone write pointer and abort all plugged BIOs. */\n \tzwplug-\u003eflags \u0026= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE;\n-\tzwplug-\u003ewp_offset = wp_offset;\n-\tdisk_zone_wplug_update_cond(disk, zwplug);\n+\tif (!disk_zone_wplug_is_offline_or_readonly(zwplug)) {\n+\t\tzwplug-\u003ewp_offset = wp_offset;\n+\t\tdisk_zone_wplug_update_cond(disk, zwplug);\n+\t}\n \n \tdisk_zone_wplug_abort(zwplug);\n \tif (!zwplug-\u003ewp_offset || disk_zone_wplug_is_full(disk, zwplug))\n@@ -816,8 +970,8 @@ static unsigned int blk_zone_wp_offset(struct blk_zone *zone)\n \t}\n }\n \n-static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,\n-\t\t\t\t\t\t struct blk_zone *zone)\n+static unsigned int disk_zone_wplug_sync_state(struct gendisk *disk,\n+\t\t\t\t\t struct blk_zone *zone)\n {\n \tstruct blk_zone_wplug *zwplug;\n \tunsigned int wp_offset = blk_zone_wp_offset(zone);\n@@ -827,6 +981,12 @@ static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,\n \t\tunsigned long flags;\n \n \t\tspin_lock_irqsave(\u0026zwplug-\u003elock, flags);\n+\t\tif (disk_zone_cond_is_offline_or_readonly(zone-\u003econd)) {\n+\t\t\tzwplug-\u003eflags \u0026= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE;\n+\t\t\tzwplug-\u003econd = zone-\u003econd;\n+\t\t\tzwplug-\u003ewp_offset = UINT_MAX;\n+\t\t\tdisk_mark_zone_wplug_dead(zwplug);\n+\t\t}\n \t\tif (zwplug-\u003eflags \u0026 BLK_ZONE_WPLUG_NEED_WP_UPDATE)\n \t\t\tdisk_zone_wplug_set_wp_offset(disk, zwplug, wp_offset);\n \t\tspin_unlock_irqrestore(\u0026zwplug-\u003elock, flags);\n@@ -871,7 +1031,7 @@ int disk_report_zone(struct gendisk *disk, struct blk_zone *zone,\n \t}\n \n \tif (disk-\u003ezone_wplugs_hash)\n-\t\tdisk_zone_wplug_sync_wp_offset(disk, zone);\n+\t\tdisk_zone_wplug_sync_state(disk, zone);\n \n \tif (args \u0026\u0026 args-\u003ecb)\n \t\treturn args-\u003ecb(zone, idx, args-\u003edata);\n@@ -938,29 +1098,36 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,\n {\n \tstruct gendisk *disk = bdev-\u003ebd_disk;\n \tsector_t zone_sectors = bdev_zone_sectors(bdev);\n+\tunsigned int zno = disk_zone_no(disk, sector);\n \tstruct blk_zone_wplug *zwplug;\n \tunsigned long flags;\n-\tu8 *zones_cond;\n+\tu8 *zones_state, zs;\n \n \tif (!bdev_is_zoned(bdev))\n \t\treturn -EOPNOTSUPP;\n \n-\tif (sector \u003e= get_capacity(disk))\n+\tif (sector \u003e= get_capacity(disk) || zno \u003e= disk-\u003enr_zones)\n \t\treturn -EINVAL;\n \n \tmemset(zone, 0, sizeof(*zone));\n \tsector = bdev_zone_start(bdev, sector);\n \n \tif (!blkdev_has_cached_report_zones(bdev))\n-\t\treturn blkdev_report_zone_fallback(bdev, sector, zone);\n+\t\tgoto fallback;\n \n \trcu_read_lock();\n-\tzones_cond = rcu_dereference(disk-\u003ezones_cond);\n-\tif (!disk-\u003ezone_wplugs_hash || !zones_cond) {\n+\tzones_state = rcu_dereference(disk-\u003ezones_state);\n+\tif (!disk-\u003ezone_wplugs_hash || !zones_state) {\n \t\trcu_read_unlock();\n-\t\treturn blkdev_report_zone_fallback(bdev, sector, zone);\n+\t\tgoto fallback;\n \t}\n-\tzone-\u003econd = zones_cond[disk_zone_no(disk, sector)];\n+\n+\tzs = zones_state[zno];\n+\tzone-\u003econd = blk_zstate_to_zone_cond(zs);\n+\tif (blk_zstate_is_conv(zs))\n+\t\tzone-\u003etype = BLK_ZONE_TYPE_CONVENTIONAL;\n+\telse\n+\t\tzone-\u003etype = BLK_ZONE_TYPE_SEQWRITE_REQ;\n \trcu_read_unlock();\n \n \tzone-\u003estart = sector;\n@@ -970,8 +1137,7 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,\n \t * If this is a conventional zone, we do not have a zone write plug and\n \t * can report the zone immediately.\n \t */\n-\tif (zone-\u003econd == BLK_ZONE_COND_NOT_WP) {\n-\t\tzone-\u003etype = BLK_ZONE_TYPE_CONVENTIONAL;\n+\tif (zone-\u003etype == BLK_ZONE_TYPE_CONVENTIONAL) {\n \t\tzone-\u003ecapacity = zone_sectors;\n \t\tzone-\u003ewp = ULLONG_MAX;\n \t\treturn 0;\n@@ -982,14 +1148,12 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,\n \t * offline, only set the zone write pointer to an invalid value and\n \t * report the zone.\n \t */\n-\tzone-\u003etype = BLK_ZONE_TYPE_SEQWRITE_REQ;\n \tif (disk_zone_is_last(disk, zone))\n \t\tzone-\u003ecapacity = disk-\u003elast_zone_capacity;\n \telse\n \t\tzone-\u003ecapacity = disk-\u003ezone_capacity;\n \n-\tif (zone-\u003econd == BLK_ZONE_COND_READONLY ||\n-\t zone-\u003econd == BLK_ZONE_COND_OFFLINE) {\n+\tif (disk_zone_cond_is_offline_or_readonly(zone-\u003econd)) {\n \t\tzone-\u003ewp = ULLONG_MAX;\n \t\treturn 0;\n \t}\n@@ -1022,6 +1186,9 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,\n \tdisk_put_zone_wplug(zwplug);\n \n \treturn 0;\n+\n+fallback:\n+\treturn blkdev_report_zone_fallback(bdev, sector, zone);\n }\n EXPORT_SYMBOL_GPL(blkdev_get_zone_info);\n \n@@ -1108,34 +1275,32 @@ static void blk_zone_reset_bio_endio(struct bio *bio)\n \t}\n }\n \n+static void disk_zone_wplug_reset_wp(struct blk_zone_wplug *zwplug, void *data)\n+{\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026zwplug-\u003elock, flags);\n+\tdisk_zone_wplug_set_wp_offset(zwplug-\u003edisk, zwplug, 0);\n+\tspin_unlock_irqrestore(\u0026zwplug-\u003elock, flags);\n+}\n+\n static void blk_zone_reset_all_bio_endio(struct bio *bio)\n {\n \tstruct gendisk *disk = bio-\u003ebi_bdev-\u003ebd_disk;\n-\tsector_t capacity = get_capacity(disk);\n-\tstruct blk_zone_wplug *zwplug;\n-\tunsigned long flags;\n \tsector_t sector;\n-\tunsigned int i;\n \n-\tif (atomic_read(\u0026disk-\u003enr_zone_wplugs)) {\n-\t\t/* Update the condition of all zone write plugs. */\n-\t\trcu_read_lock();\n-\t\tfor (i = 0; i \u003c disk_zone_wplugs_hash_size(disk); i++) {\n-\t\t\thlist_for_each_entry_rcu(zwplug,\n-\t\t\t\t\t\t \u0026disk-\u003ezone_wplugs_hash[i],\n-\t\t\t\t\t\t node) {\n-\t\t\t\tspin_lock_irqsave(\u0026zwplug-\u003elock, flags);\n-\t\t\t\tdisk_zone_wplug_set_wp_offset(disk, zwplug, 0);\n-\t\t\t\tspin_unlock_irqrestore(\u0026zwplug-\u003elock, flags);\n-\t\t\t}\n-\t\t}\n-\t\trcu_read_unlock();\n-\t}\n+\t/* Update the condition of all zone write plugs. */\n+\tif (atomic_read(\u0026disk-\u003enr_zone_wplugs))\n+\t\tdisk_for_all_zone_wplugs(disk, disk_zone_wplug_reset_wp, NULL);\n \n \t/* Update the cached zone conditions. */\n-\tfor (sector = 0; sector \u003c capacity;\n-\t sector += bdev_zone_sectors(bio-\u003ebi_bdev))\n+\tfor (sector = 0; sector \u003c get_capacity(disk);\n+\t sector += bdev_zone_sectors(bio-\u003ebi_bdev)) {\n+\t\tif (!disk_zone_is_seq(disk, sector) ||\n+\t\t disk_zone_is_offline_or_readonly(disk, sector))\n+\t\t\tcontinue;\n \t\tdisk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY);\n+\t}\n \tclear_bit(GD_ZONE_APPEND_USED, \u0026disk-\u003estate);\n }\n \n@@ -1381,11 +1546,12 @@ static bool blk_zone_wplug_prepare_bio(struct blk_zone_wplug *zwplug,\n \t\treturn false;\n \n \t/*\n-\t * Check that the user is not attempting to write to a full zone.\n-\t * We know such BIO will fail, and that would potentially overflow our\n-\t * write pointer offset beyond the end of the zone.\n+\t * Check that the user is not attempting to write to a full, read-only\n+\t * or offline zone. We know such BIOs will fail, so there is no point\n+\t * in issuing them.\n \t */\n-\tif (disk_zone_wplug_is_full(disk, zwplug))\n+\tif (disk_zone_wplug_is_full(disk, zwplug) ||\n+\t disk_zone_wplug_is_offline_or_readonly(zwplug))\n \t\treturn false;\n \n \tif (bio_op(bio) == REQ_OP_ZONE_APPEND) {\n@@ -1442,7 +1608,7 @@ static bool blk_zone_wplug_handle_write(struct bio *bio, unsigned int nr_segs)\n \t}\n \n \t/* Conventional zones do not need write plugging. */\n-\tif (!bdev_zone_is_seq(bio-\u003ebi_bdev, sector)) {\n+\tif (!disk_zone_is_seq(disk, sector)) {\n \t\t/* Zone append to conventional zones is not allowed. */\n \t\tif (bio_op(bio) == REQ_OP_ZONE_APPEND) {\n \t\t\tbio_io_error(bio);\n@@ -1854,12 +2020,24 @@ static int disk_zone_wplugs_worker(void *data)\n \n void disk_init_zone_resources(struct gendisk *disk)\n {\n+\tmutex_init(\u0026disk-\u003ezone_revalidate_mutex);\n+\tatomic_set(\u0026disk-\u003enr_zone_wplugs, 0);\n \tspin_lock_init(\u0026disk-\u003ezone_wplugs_hash_lock);\n \tspin_lock_init(\u0026disk-\u003ezone_wplugs_list_lock);\n \tINIT_LIST_HEAD(\u0026disk-\u003ezone_wplugs_list);\n \tinit_completion(\u0026disk-\u003ezone_wplugs_worker_bio_done);\n }\n \n+static unsigned int disk_get_nr_zones(struct gendisk *disk, sector_t capacity)\n+{\n+\tstruct queue_limits *lim = \u0026disk-\u003equeue-\u003elimits;\n+\n+\tif (!capacity || !lim-\u003echunk_sectors)\n+\t\treturn 0;\n+\n+\treturn DIV_ROUND_UP_ULL(capacity, lim-\u003echunk_sectors);\n+}\n+\n /*\n * For the size of a disk zone write plug hash table, use the size of the\n * zone write plug mempool, which is the maximum of the disk open zones and\n@@ -1869,13 +2047,24 @@ void disk_init_zone_resources(struct gendisk *disk)\n #define BLK_ZONE_WPLUG_MAX_HASH_BITS\t\t9\n #define BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE\t128\n \n-static int disk_alloc_zone_resources(struct gendisk *disk,\n-\t\t\t\t unsigned int pool_size)\n+static int disk_alloc_zone_resources(struct gendisk *disk, sector_t capacity)\n {\n-\tunsigned int i;\n+\tstruct queue_limits *lim = \u0026disk-\u003equeue-\u003elimits;\n+\tunsigned int nr_zones, pool_size, i;\n \tint ret = -ENOMEM;\n \n-\tatomic_set(\u0026disk-\u003enr_zone_wplugs, 0);\n+\tnr_zones = disk_get_nr_zones(disk, capacity);\n+\tif (!nr_zones)\n+\t\treturn -ENODEV;\n+\n+\t/*\n+\t * If the device has no limit on the maximum number of open and active\n+\t * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.\n+\t */\n+\tpool_size = max(lim-\u003emax_open_zones, lim-\u003emax_active_zones);\n+\tif (!pool_size)\n+\t\tpool_size = min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, nr_zones);\n+\n \tdisk-\u003ezone_wplugs_hash_bits =\n \t\tmin(ilog2(pool_size) + 1, BLK_ZONE_WPLUG_MAX_HASH_BITS);\n \n@@ -1893,21 +2082,6 @@ static int disk_alloc_zone_resources(struct gendisk *disk,\n \tif (!disk-\u003ezone_wplugs_pool)\n \t\tgoto free_hash;\n \n-\t/*\n-\t * We may already have a zone write plug workqueue as this function may\n-\t * be called after disk_free_zone_resources(), which does not destroy\n-\t * the workqueue (the zone write plugs workqueue is destroyed at\n-\t * disk_release() time).\n-\t */\n-\tif (!disk-\u003ezone_wplugs_wq) {\n-\t\tdisk-\u003ezone_wplugs_wq =\n-\t\t\talloc_workqueue(\"%s_zwplugs\",\n-\t\t\t\t\tWQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,\n-\t\t\t\t\tpool_size, disk-\u003edisk_name);\n-\t\tif (!disk-\u003ezone_wplugs_wq)\n-\t\t\tgoto destroy_pool;\n-\t}\n-\n \tdisk-\u003ezone_wplugs_worker =\n \t\tkthread_create(disk_zone_wplugs_worker, disk,\n \t\t\t \"%s_zwplugs_worker\", disk-\u003edisk_name);\n@@ -1918,8 +2092,18 @@ static int disk_alloc_zone_resources(struct gendisk *disk,\n \t}\n \twake_up_process(disk-\u003ezone_wplugs_worker);\n \n+\tdisk-\u003ezone_wplugs_wq =\n+\t\talloc_workqueue(\"%s_zwplugs\",\n+\t\t\t\tWQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,\n+\t\t\t\tpool_size, disk-\u003edisk_name);\n+\tif (!disk-\u003ezone_wplugs_wq)\n+\t\tgoto stop_worker;\n+\n \treturn 0;\n \n+stop_worker:\n+\tkthread_stop(disk-\u003ezone_wplugs_worker);\n+\tdisk-\u003ezone_wplugs_worker = NULL;\n destroy_pool:\n \tmempool_destroy(disk-\u003ezone_wplugs_pool);\n \tdisk-\u003ezone_wplugs_pool = NULL;\n@@ -1963,19 +2147,19 @@ static void disk_destroy_zone_wplugs_hash_table(struct gendisk *disk)\n \tdisk-\u003ezone_wplugs_pool = NULL;\n }\n \n-static void disk_set_zones_cond_array(struct gendisk *disk, u8 *zones_cond)\n+static void disk_set_zones_state_array(struct gendisk *disk, u8 *zones_state)\n {\n \tunsigned long flags;\n \n \tspin_lock_irqsave(\u0026disk-\u003ezone_wplugs_hash_lock, flags);\n-\tzones_cond = rcu_replace_pointer(disk-\u003ezones_cond, zones_cond,\n+\tzones_state = rcu_replace_pointer(disk-\u003ezones_state, zones_state,\n \t\t\t\tlockdep_is_held(\u0026disk-\u003ezone_wplugs_hash_lock));\n \tspin_unlock_irqrestore(\u0026disk-\u003ezone_wplugs_hash_lock, flags);\n \n-\tkfree_rcu_mightsleep(zones_cond);\n+\tkfree_rcu_mightsleep(zones_state);\n }\n \n-static void disk_free_zone_resources(struct gendisk *disk)\n+void disk_release_zone_resources(struct gendisk *disk)\n {\n \tif (disk-\u003ezone_wplugs_worker) {\n \t\tkthread_stop(disk-\u003ezone_wplugs_worker);\n@@ -1983,30 +2167,24 @@ static void disk_free_zone_resources(struct gendisk *disk)\n \t}\n \tWARN_ON_ONCE(!list_empty(\u0026disk-\u003ezone_wplugs_list));\n \n-\tif (disk-\u003ezone_wplugs_wq)\n-\t\tdrain_workqueue(disk-\u003ezone_wplugs_wq);\n+\tif (disk-\u003ezone_wplugs_wq) {\n+\t\tdestroy_workqueue(disk-\u003ezone_wplugs_wq);\n+\t\tdisk-\u003ezone_wplugs_wq = NULL;\n+\t}\n \n \tdisk_destroy_zone_wplugs_hash_table(disk);\n \n-\tdisk_set_zones_cond_array(disk, NULL);\n+\tdisk_set_zones_state_array(disk, NULL);\n \tdisk-\u003ezone_capacity = 0;\n \tdisk-\u003elast_zone_capacity = 0;\n \tdisk-\u003enr_zones = 0;\n-}\n-\n-void disk_release_zone_resources(struct gendisk *disk)\n-{\n-\tif (disk-\u003ezone_wplugs_wq) {\n-\t\tdestroy_workqueue(disk-\u003ezone_wplugs_wq);\n-\t\tdisk-\u003ezone_wplugs_wq = NULL;\n-\t}\n-\n-\tdisk_free_zone_resources(disk);\n+\tmutex_destroy(\u0026disk-\u003ezone_revalidate_mutex);\n }\n \n struct blk_revalidate_zone_args {\n \tstruct gendisk\t*disk;\n-\tu8\t\t*zones_cond;\n+\tsector_t\tcapacity;\n+\tu8\t\t*zones_state;\n \tunsigned int\tnr_zones;\n \tunsigned int\tnr_conv_zones;\n \tunsigned int\tzone_capacity;\n@@ -2014,73 +2192,74 @@ struct blk_revalidate_zone_args {\n \tsector_t\tsector;\n };\n \n-static int disk_revalidate_zone_resources(struct gendisk *disk,\n-\t\t\t\tstruct blk_revalidate_zone_args *args)\n+static int disk_init_revalidate_args(struct gendisk *disk,\n+\t\t\t\t struct blk_revalidate_zone_args *args)\n {\n-\tstruct queue_limits *lim = \u0026disk-\u003equeue-\u003elimits;\n-\tunsigned int pool_size;\n-\tint ret = 0;\n-\n \targs-\u003edisk = disk;\n-\targs-\u003enr_zones =\n-\t\tDIV_ROUND_UP_ULL(get_capacity(disk), lim-\u003echunk_sectors);\n+\targs-\u003enr_zones = disk_get_nr_zones(disk, args-\u003ecapacity);\n \n \t/* Cached zone conditions: 1 byte per zone */\n-\targs-\u003ezones_cond = kzalloc(args-\u003enr_zones, GFP_NOIO);\n-\tif (!args-\u003ezones_cond)\n+\targs-\u003ezones_state = kzalloc(args-\u003enr_zones, GFP_NOIO);\n+\tif (!args-\u003ezones_state)\n \t\treturn -ENOMEM;\n \n-\tif (!disk_need_zone_resources(disk))\n-\t\treturn 0;\n-\n-\t/*\n-\t * If the device has no limit on the maximum number of open and active\n-\t * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.\n-\t */\n-\tpool_size = max(lim-\u003emax_open_zones, lim-\u003emax_active_zones);\n-\tif (!pool_size)\n-\t\tpool_size =\n-\t\t\tmin(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, args-\u003enr_zones);\n-\n-\tif (!disk-\u003ezone_wplugs_hash) {\n-\t\tret = disk_alloc_zone_resources(disk, pool_size);\n-\t\tif (ret)\n-\t\t\tkfree(args-\u003ezones_cond);\n-\t}\n-\n-\treturn ret;\n+\treturn 0;\n }\n \n /*\n- * Update the disk zone resources information and device queue limits.\n- * The disk queue is frozen when this is executed.\n+ * Revalidate and update the disk zone resources information and device queue\n+ * limits.\n */\n-static int disk_update_zone_resources(struct gendisk *disk,\n-\t\t\t\t struct blk_revalidate_zone_args *args)\n+static int disk_revalidate_zone_resources(struct gendisk *disk,\n+\t\t\t\t\t struct blk_revalidate_zone_args *args)\n {\n \tstruct request_queue *q = disk-\u003equeue;\n \tunsigned int nr_seq_zones;\n \tunsigned int pool_size, memflags;\n \tstruct queue_limits lim;\n+\tsector_t capacity;\n \tint ret = 0;\n \n \tlim = queue_limits_start_update(q);\n \n \tmemflags = blk_mq_freeze_queue(q);\n \n-\tdisk-\u003enr_zones = args-\u003enr_zones;\n-\tif (args-\u003enr_conv_zones \u003e= disk-\u003enr_zones) {\n-\t\tqueue_limits_cancel_update(q);\n+\t/*\n+\t * Using the re-evaluated disk capacity, make sure that the entire disk\n+\t * has been checked.\n+\t */\n+\tcapacity = get_capacity(disk);\n+\tif (args-\u003ecapacity != capacity) {\n+\t\tpr_warn(\"%s: Capacity has changed (%llu -\u003e %llu)\\n\",\n+\t\t\tdisk-\u003edisk_name, args-\u003ecapacity, capacity);\n+\t\t/* Force a retry if we have a valid (non-zero) capacity. */\n+\t\tif (capacity)\n+\t\t\tret = -EAGAIN;\n+\t\telse\n+\t\t\tret = -ENODEV;\n+\t\tgoto unfreeze;\n+\t}\n+\n+\t/* Make sure that all zones have been checked. */\n+\tif (args-\u003esector != capacity) {\n+\t\tpr_warn(\"%s: last zone and capacity mismatch (%llu != %llu)\\n\",\n+\t\t\tdisk-\u003edisk_name, args-\u003esector, capacity);\n+\t\tret = -ENODEV;\n+\t\tgoto unfreeze;\n+\t}\n+\n+\tif (args-\u003enr_conv_zones \u003e= args-\u003enr_zones) {\n \t\tpr_warn(\"%s: Invalid number of conventional zones %u / %u\\n\",\n-\t\t\tdisk-\u003edisk_name, args-\u003enr_conv_zones, disk-\u003enr_zones);\n+\t\t\tdisk-\u003edisk_name, args-\u003enr_conv_zones, args-\u003enr_zones);\n \t\tret = -ENODEV;\n \t\tgoto unfreeze;\n \t}\n \n+\tdisk-\u003enr_zones = args-\u003enr_zones;\n \tdisk-\u003ezone_capacity = args-\u003ezone_capacity;\n \tdisk-\u003elast_zone_capacity = args-\u003elast_zone_capacity;\n-\tdisk_set_zones_cond_array(disk, args-\u003ezones_cond);\n-\targs-\u003ezones_cond = NULL;\n+\tdisk_set_zones_state_array(disk, args-\u003ezones_state);\n+\targs-\u003ezones_state = NULL;\n \n \t/*\n \t * Some devices can advertise zone resource limits that are larger than\n@@ -2095,7 +2274,7 @@ static int disk_update_zone_resources(struct gendisk *disk,\n \t\tlim.max_active_zones = 0;\n \n \tif (!disk-\u003ezone_wplugs_pool)\n-\t\tgoto commit;\n+\t\tgoto unfreeze;\n \n \t/*\n \t * If the device has no limit on the maximum number of open and active\n@@ -2117,51 +2296,65 @@ static int disk_update_zone_resources(struct gendisk *disk,\n \t\t\tlim.max_open_zones = 0;\n \t}\n \n-commit:\n-\tret = queue_limits_commit_update(q, \u0026lim);\n-\n unfreeze:\n+\tif (ret)\n+\t\tqueue_limits_cancel_update(q);\n+\telse\n+\t\tret = queue_limits_commit_update(q, \u0026lim);\n+\n \tblk_mq_unfreeze_queue(q, memflags);\n \n \treturn ret;\n }\n \n-static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx,\n+static void disk_drop_zone_wplug(struct blk_zone_wplug *zwplug, void *data)\n+{\n+\tunsigned long flags;\n+\n+\tspin_lock_irqsave(\u0026zwplug-\u003elock, flags);\n+\tdisk_zone_wplug_abort(zwplug);\n+\tdisk_mark_zone_wplug_dead(zwplug);\n+\tspin_unlock_irqrestore(\u0026zwplug-\u003elock, flags);\n+}\n+\n+static int disk_revalidate_capacity(struct gendisk *disk,\n \t\t\t\t struct blk_revalidate_zone_args *args)\n {\n-\tenum blk_zone_cond cond = zone-\u003econd;\n+\tstruct queue_limits *lim = \u0026disk-\u003equeue-\u003elimits;\n+\tsector_t zone_sectors = lim-\u003echunk_sectors;\n+\tunsigned int nr_zones;\n+\tint ret = -ENODEV;\n \n-\t/* Check that the zone condition is consistent with the zone type. */\n-\tswitch (cond) {\n-\tcase BLK_ZONE_COND_NOT_WP:\n-\t\tif (zone-\u003etype != BLK_ZONE_TYPE_CONVENTIONAL)\n-\t\t\tgoto invalid_condition;\n-\t\tbreak;\n-\tcase BLK_ZONE_COND_IMP_OPEN:\n-\tcase BLK_ZONE_COND_EXP_OPEN:\n-\tcase BLK_ZONE_COND_CLOSED:\n-\tcase BLK_ZONE_COND_EMPTY:\n-\tcase BLK_ZONE_COND_FULL:\n-\tcase BLK_ZONE_COND_OFFLINE:\n-\tcase BLK_ZONE_COND_READONLY:\n-\t\tif (zone-\u003etype != BLK_ZONE_TYPE_SEQWRITE_REQ)\n-\t\t\tgoto invalid_condition;\n-\t\tbreak;\n-\tdefault:\n-\t\tpr_warn(\"%s: Invalid zone condition 0x%X\\n\",\n-\t\t\targs-\u003edisk-\u003edisk_name, cond);\n-\t\treturn -ENODEV;\n+\t/* Checks that the device driver indicated a valid zone size. */\n+\tif (!zone_sectors || !is_power_of_2(zone_sectors)) {\n+\t\tpr_warn(\"%s: Invalid non power of two zone size (%llu)\\n\",\n+\t\t\tdisk-\u003edisk_name, zone_sectors);\n+\t\tgoto drop_all_zwplugs;\n \t}\n \n-\tblk_zone_set_cond(args-\u003ezones_cond, idx, cond);\n+\targs-\u003ecapacity = get_capacity(disk);\n+\tnr_zones = disk_get_nr_zones(disk, args-\u003ecapacity);\n+\tif (!args-\u003ecapacity || !nr_zones)\n+\t\tgoto drop_all_zwplugs;\n+\n+\t/*\n+\t * Check if the capacity has changed. If it did, assume that the device\n+\t * was reformatted and that all sequential zones are now empty. So drop\n+\t * all zone write plug.\n+\t */\n+\tif (disk-\u003enr_zones \u0026\u0026 disk-\u003enr_zones != nr_zones) {\n+\t\tpr_warn(\"%s: Number of zones changed (%u -\u003e %u)\\n\",\n+\t\t\tdisk-\u003edisk_name, disk-\u003enr_zones, nr_zones);\n+\t\tret = 0;\n+\t\tgoto drop_all_zwplugs;\n+\t}\n \n \treturn 0;\n \n-invalid_condition:\n-\tpr_warn(\"%s: Invalid zone condition 0x%x for type 0x%x\\n\",\n-\t\targs-\u003edisk-\u003edisk_name, cond, zone-\u003etype);\n+drop_all_zwplugs:\n+\tdisk_for_all_zone_wplugs(disk, disk_drop_zone_wplug, NULL);\n \n-\treturn -ENODEV;\n+\treturn ret;\n }\n \n static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,\n@@ -2169,12 +2362,27 @@ static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,\n {\n \tstruct gendisk *disk = args-\u003edisk;\n \n+\t/* Check the zone condition. */\n+\tswitch (zone-\u003econd) {\n+\tcase BLK_ZONE_COND_NOT_WP:\n+\tcase BLK_ZONE_COND_OFFLINE:\n+\tcase BLK_ZONE_COND_READONLY:\n+\t\tbreak;\n+\tdefault:\n+\t\tpr_warn(\"%s: Invalid conv. zone condition 0x%X at sector %llu\\n\",\n+\t\t\tdisk-\u003edisk_name, zone-\u003econd, zone-\u003estart);\n+\t\treturn -ENODEV;\n+\t}\n+\n \tif (zone-\u003ecapacity != zone-\u003elen) {\n \t\tpr_warn(\"%s: Invalid conventional zone capacity\\n\",\n \t\t\tdisk-\u003edisk_name);\n \t\treturn -ENODEV;\n \t}\n \n+\tblk_zstate_set(args-\u003ezones_state, args-\u003enr_zones, idx,\n+\t\t zone-\u003econd, BLK_ZFLAG_CONV);\n+\n \tif (disk_zone_is_last(disk, zone))\n \t\targs-\u003elast_zone_capacity = zone-\u003ecapacity;\n \n@@ -2190,6 +2398,24 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,\n \tstruct blk_zone_wplug *zwplug;\n \tunsigned int wp_offset;\n \n+\t/* Check the zone condition. */\n+\tswitch (zone-\u003econd) {\n+\tcase BLK_ZONE_COND_IMP_OPEN:\n+\tcase BLK_ZONE_COND_EXP_OPEN:\n+\tcase BLK_ZONE_COND_CLOSED:\n+\tcase BLK_ZONE_COND_EMPTY:\n+\tcase BLK_ZONE_COND_FULL:\n+\tcase BLK_ZONE_COND_OFFLINE:\n+\tcase BLK_ZONE_COND_READONLY:\n+\t\tbreak;\n+\tdefault:\n+\t\tpr_warn(\"%s: Invalid seq. zone condition 0x%X at sector %llu\\n\",\n+\t\t\tdisk-\u003edisk_name, zone-\u003econd, zone-\u003estart);\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tblk_zstate_set(args-\u003ezones_state, args-\u003enr_zones, idx, zone-\u003econd, 0);\n+\n \t/*\n \t * Remember the capacity of the first sequential zone and check\n \t * if it is constant for all zones, ignoring the last zone as it can be\n@@ -2214,7 +2440,7 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,\n \tif (!disk-\u003ezone_wplugs_hash)\n \t\treturn 0;\n \n-\twp_offset = disk_zone_wplug_sync_wp_offset(disk, zone);\n+\twp_offset = disk_zone_wplug_sync_state(disk, zone);\n \tif (!wp_offset || wp_offset \u003e= zone-\u003ecapacity)\n \t\treturn 0;\n \n@@ -2244,7 +2470,7 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,\n \t\treturn -ENODEV;\n \t}\n \n-\tif (zone-\u003estart \u003e= get_capacity(disk) || !zone-\u003elen) {\n+\tif (zone-\u003estart \u003e= args-\u003ecapacity || !zone-\u003elen) {\n \t\tpr_warn(\"%s: Invalid zone start %llu, length %llu\\n\",\n \t\t\tdisk-\u003edisk_name, zone-\u003estart, zone-\u003elen);\n \t\treturn -ENODEV;\n@@ -2272,11 +2498,6 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,\n \t\treturn -ENODEV;\n \t}\n \n-\t/* Check zone condition */\n-\tret = blk_revalidate_zone_cond(zone, idx, args);\n-\tif (ret)\n-\t\treturn ret;\n-\n \t/* Check zone type */\n \tswitch (zone-\u003etype) {\n \tcase BLK_ZONE_TYPE_CONVENTIONAL:\n@@ -2313,42 +2534,48 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,\n */\n int blk_revalidate_disk_zones(struct gendisk *disk)\n {\n-\tstruct request_queue *q = disk-\u003equeue;\n-\tsector_t zone_sectors = q-\u003elimits.chunk_sectors;\n-\tsector_t capacity = get_capacity(disk);\n \tstruct blk_revalidate_zone_args args = { };\n-\tunsigned int memflags, noio_flag;\n \tstruct blk_report_zones_args rep_args = {\n \t\t.cb = blk_revalidate_zone_cb,\n \t\t.data = \u0026args,\n \t};\n-\tint ret = -ENOMEM;\n+\tunsigned int noio_flag;\n+\tint retries = 2;\n+\tint ret;\n \n-\tif (WARN_ON_ONCE(!blk_queue_is_zoned(q)))\n+\tif (WARN_ON_ONCE(!blk_queue_is_zoned(disk-\u003equeue)))\n \t\treturn -EIO;\n \n-\tif (!capacity)\n-\t\treturn -ENODEV;\n-\n \t/*\n-\t * Checks that the device driver indicated a valid zone size and that\n-\t * the max zone append limit is set.\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-\tif (!zone_sectors || !is_power_of_2(zone_sectors)) {\n-\t\tpr_warn(\"%s: Invalid non power of two zone size (%llu)\\n\",\n-\t\t\tdisk-\u003edisk_name, zone_sectors);\n-\t\treturn -ENODEV;\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 \n \t/*\n-\t * Ensure that all memory allocations in this context are done as if\n-\t * GFP_NOIO was specified.\n+\t * Allocate zone resources if they are needed and we have not done\n+\t * so yet, and initialize the revalidation arguments passed to report\n+\t * zones. Ensure that all memory allocations in this context are done as\n+\t * if GFP_NOIO was specified.\n \t */\n \tnoio_flag = memalloc_noio_save();\n-\tret = disk_revalidate_zone_resources(disk, \u0026args);\n+\tif (disk_need_zone_resources(disk) \u0026\u0026 !disk-\u003ezone_wplugs_hash) {\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}\n+\t}\n+\n+\tret = disk_init_revalidate_args(disk, \u0026args);\n \tif (ret) {\n \t\tmemalloc_noio_restore(noio_flag);\n-\t\treturn ret;\n+\t\tgoto unlock;\n \t}\n \n \tret = disk-\u003efops-\u003ereport_zones(disk, 0, UINT_MAX, \u0026rep_args);\n@@ -2358,33 +2585,32 @@ int blk_revalidate_disk_zones(struct gendisk *disk)\n \t}\n \tmemalloc_noio_restore(noio_flag);\n \n-\tif (ret \u003c= 0)\n-\t\tgoto free_resources;\n-\n-\t/*\n-\t * If zones where reported, make sure that the entire disk capacity\n-\t * has been checked.\n-\t */\n-\tif (args.sector != capacity) {\n-\t\tpr_warn(\"%s: Missing zones from sector %llu\\n\",\n-\t\t\tdisk-\u003edisk_name, args.sector);\n-\t\tret = -ENODEV;\n-\t\tgoto free_resources;\n-\t}\n+\tif (ret \u003c 0)\n+\t\tgoto free_args;\n \n-\tret = disk_update_zone_resources(disk, \u0026args);\n+\tret = disk_revalidate_zone_resources(disk, \u0026args);\n \tif (ret)\n-\t\tgoto free_resources;\n+\t\tgoto free_args;\n+\n+\tmutex_unlock(\u0026disk-\u003ezone_revalidate_mutex);\n \n \treturn 0;\n \n-free_resources:\n-\tpr_warn(\"%s: failed to revalidate zones\\n\", disk-\u003edisk_name);\n+free_args:\n+\tkfree(args.zones_state);\n \n-\tkfree(args.zones_cond);\n-\tmemflags = blk_mq_freeze_queue(q);\n-\tdisk_free_zone_resources(disk);\n-\tblk_mq_unfreeze_queue(q, memflags);\n+\tif (ret == -EAGAIN) {\n+\t\tif (retries) {\n+\t\t\tmemset(\u0026args, 0, sizeof(args));\n+\t\t\tretries--;\n+\t\t\tgoto again;\n+\t\t}\n+\t\tret = -ENODEV;\n+\t}\n+\n+\tpr_warn(\"%s: failed to revalidate zones\\n\", disk-\u003edisk_name);\n+unlock:\n+\tmutex_unlock(\u0026disk-\u003ezone_revalidate_mutex);\n \n \treturn ret;\n }\n@@ -2435,8 +2661,9 @@ EXPORT_SYMBOL_GPL(blk_zone_issue_zeroout);\n \n #ifdef CONFIG_BLK_DEBUG_FS\n static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,\n-\t\t\t\t struct seq_file *m)\n+\t\t\t\t void *data)\n {\n+\tstruct seq_file *m = data;\n \tunsigned int zwp_wp_offset, zwp_flags;\n \tunsigned int zwp_zone_no, zwp_ref;\n \tunsigned int zwp_bio_list_size;\n@@ -2461,19 +2688,8 @@ static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,\n int queue_zone_wplugs_show(void *data, struct seq_file *m)\n {\n \tstruct request_queue *q = data;\n-\tstruct gendisk *disk = q-\u003edisk;\n-\tstruct blk_zone_wplug *zwplug;\n-\tunsigned int i;\n \n-\tif (!disk-\u003ezone_wplugs_hash)\n-\t\treturn 0;\n-\n-\trcu_read_lock();\n-\tfor (i = 0; i \u003c disk_zone_wplugs_hash_size(disk); i++)\n-\t\thlist_for_each_entry_rcu(zwplug, \u0026disk-\u003ezone_wplugs_hash[i],\n-\t\t\t\t\t node)\n-\t\t\tqueue_zone_wplug_show(zwplug, m);\n-\trcu_read_unlock();\n+\tdisk_for_all_zone_wplugs(q-\u003edisk, queue_zone_wplug_show, m);\n \n \treturn 0;\n }\ndiff --git a/block/blk.h b/block/blk.h\nindex 50abfd9328861..2cc03aa54c532 100644\n--- a/block/blk.h\n+++ b/block/blk.h\n@@ -577,6 +577,7 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd,\n \t\tunsigned long arg);\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 #else /* CONFIG_BLK_DEV_ZONED */\n static inline void disk_init_zone_resources(struct gendisk *disk)\n {\n@@ -619,6 +620,11 @@ static inline int blkdev_zone_mgmt_ioctl(struct block_device *bdev,\n {\n \treturn -ENOTTY;\n }\n+static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,\n+\t\t\t\t\t sector_t sector)\n+{\n+\treturn false;\n+}\n #endif /* CONFIG_BLK_DEV_ZONED */\n \n struct block_device *bdev_alloc(struct gendisk *disk, u8 partno);\ndiff --git a/include/linux/blkdev.h b/include/linux/blkdev.h\nindex 4f7905c3412b8..8252c896e3eac 100644\n--- a/include/linux/blkdev.h\n+++ b/include/linux/blkdev.h\n@@ -191,14 +191,17 @@ struct gendisk {\n #ifdef CONFIG_BLK_DEV_ZONED\n \t/*\n \t * Zoned block device information. Reads of this information must be\n-\t * protected with blk_queue_enter() / blk_queue_exit(). Modifying this\n-\t * information is only allowed while no requests are being processed.\n-\t * See also blk_mq_freeze_queue() and blk_mq_unfreeze_queue().\n+\t * protected with blk_queue_enter() / blk_queue_exit() or by holding a\n+\t * lock on zone_revalidate_mutex. blk_revalidate_disk_zones() may modify\n+\t * this information while no requests are being processed (disk queue\n+\t * frozen with blk_mq_freeze_queue()) and while holding a lock on\n+\t * zone_revalidate_mutex.\n \t */\n+\tstruct mutex\t\tzone_revalidate_mutex;\n \tunsigned int\t\tnr_zones;\n \tunsigned int\t\tzone_capacity;\n \tunsigned int\t\tlast_zone_capacity;\n-\tu8 __rcu\t\t*zones_cond;\n+\tu8 __rcu\t\t*zones_state;\n \tunsigned int\t\tzone_wplugs_hash_bits;\n \tatomic_t\t\tnr_zone_wplugs;\n \tspinlock_t\t\tzone_wplugs_hash_lock;\n"
} |
| 2/1 | 2026/09/08 09:18 | agent | patch-evaluator |
0mModel:core Args: null Results: {
"EnableConfigs": [
"BLK_DEV_ZONED"
],
"FocusSymbols": [
"blk_revalidate_disk_zones",
"bdev_zone_mgmt_allowed",
"bdev_zone_is_seq",
"blkdev_get_zone_info",
"disk_zone_wplug_sync_state",
"disk_zone_wplug_abort",
"disk_revalidate_zone_resources",
"disk_revalidate_capacity",
"disk_alloc_zone_resources",
"blk_revalidate_seq_zone",
"blk_revalidate_conv_zone"
],
"Reasoning": "The patch modifies the block layer's zoned device handling, including zone condition and state tracking (switching from zones_cond to zones_state), revalidation of zoned devices with synchronization under zone_revalidate_mutex, handling of offline/read-only zones, zone write plug aborts and dead state checks, and zone management validation during bio submission. These reachable core block layer changes warrant 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 9514449fcfb821c5f2bb5c88be27acd256a43195
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 8 09:18:29 2026 +0000
syz-cluster: applied patch under review
diff --git a/block/blk-core.c b/block/blk-core.c
index 196bccf27f58d..13dc70e8f55d9 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -901,8 +901,11 @@ void submit_bio_noacct(struct bio *bio)
case REQ_OP_ZONE_CLOSE:
case REQ_OP_ZONE_RESET:
case REQ_OP_ZONE_FINISH:
- /* Zone management operations require sequential zones. */
- if (!bdev_zone_is_seq(bio->bi_bdev, bio->bi_iter.bi_sector))
+ /*
+ * Zone management operations require sequential zones that are
+ * not offline nor read-only.
+ */
+ if (!bdev_zone_mgmt_allowed(bdev, bio->bi_iter.bi_sector))
goto end_io;
break;
case REQ_OP_ZONE_RESET_ALL:
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index a5afb842bf35e..ec510d66dfe2b 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -39,6 +39,103 @@ static const char *const zone_cond_name[] = {
};
#undef ZONE_COND_NAME
+/*
+ * Internal and compact representation of enum blk_zone_cond values for zone
+ * conditions. All these values fit into 4-bits, allowing using the high order
+ * bits as the zone type.
+ */
+enum blk_zstate {
+ BLK_ZSTATE_NOT_WP = 0x00,
+ BLK_ZSTATE_EMPTY = 0x01,
+ BLK_ZSTATE_IMP_OPEN = 0x02,
+ BLK_ZSTATE_EXP_OPEN = 0x03,
+ BLK_ZSTATE_CLOSED = 0x04,
+ BLK_ZSTATE_READONLY = 0x05,
+ BLK_ZSTATE_FULL = 0x06,
+ BLK_ZSTATE_OFFLINE = 0x07,
+ BLK_ZSTATE_ACTIVE = 0x08,
+
+ BLK_ZSTATE_COND_MASK = 0x0F,
+
+ /* Conventional zone. */
+ BLK_ZFLAG_CONV = 0x80,
+ BLK_ZSTATE_FLAGS_MASK = ~BLK_ZSTATE_COND_MASK,
+};
+
+/*
+ * Lookup table and helper to convert enum blk_zstate conditions into enum
+ * blk_zone_condition values.
+ */
+static const u8 blk_zstate2zcond[] = {
+ [BLK_ZSTATE_NOT_WP] = BLK_ZONE_COND_NOT_WP,
+ [BLK_ZSTATE_EMPTY] = BLK_ZONE_COND_EMPTY,
+ [BLK_ZSTATE_IMP_OPEN] = BLK_ZONE_COND_IMP_OPEN,
+ [BLK_ZSTATE_EXP_OPEN] = BLK_ZONE_COND_EXP_OPEN,
+ [BLK_ZSTATE_CLOSED] = BLK_ZONE_COND_CLOSED,
+ [BLK_ZSTATE_READONLY] = BLK_ZONE_COND_READONLY,
+ [BLK_ZSTATE_FULL] = BLK_ZONE_COND_FULL,
+ [BLK_ZSTATE_OFFLINE] = BLK_ZONE_COND_OFFLINE,
+ [BLK_ZSTATE_ACTIVE] = BLK_ZONE_COND_ACTIVE,
+};
+
+static inline enum blk_zone_cond blk_zstate_to_zone_cond(enum blk_zstate zs)
+{
+ u8 idx = zs & BLK_ZSTATE_COND_MASK;
+
+ if (WARN_ON_ONCE(idx >= ARRAY_SIZE(blk_zstate2zcond)))
+ return 0;
+
+ return blk_zstate2zcond[idx];
+}
+
+/*
+ * Lookup table and helper to convert an enum blk_zone_condition into an enum
+ * blk_zstate condition value. To keep the lookup table small, the
+ * BLK_ZONE_COND_ACTIVE condition is not added and handled separately.
+ */
+static const u8 blk_zcond2zstate[] = {
+ [BLK_ZONE_COND_NOT_WP] = BLK_ZSTATE_NOT_WP,
+ [BLK_ZONE_COND_EMPTY] = BLK_ZSTATE_EMPTY,
+ [BLK_ZONE_COND_IMP_OPEN] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_EXP_OPEN] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_CLOSED] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_READONLY] = BLK_ZSTATE_READONLY,
+ [BLK_ZONE_COND_FULL] = BLK_ZSTATE_FULL,
+ [BLK_ZONE_COND_OFFLINE] = BLK_ZSTATE_OFFLINE,
+};
+
+static inline enum blk_zstate blk_zone_cond_to_zstate(enum blk_zone_cond cond)
+{
+ if (cond == BLK_ZONE_COND_ACTIVE)
+ return BLK_ZSTATE_ACTIVE;
+
+ if (WARN_ON_ONCE(cond >= ARRAY_SIZE(blk_zcond2zstate)))
+ return 0;
+
+ return blk_zcond2zstate[cond];
+}
+
+/*
+ * Combine an enum blk_zone_condition and zone flags into a zones_state array
+ * entry.
+ */
+static inline void blk_zstate_set(u8 *zones_state, unsigned int nr_zones,
+ unsigned int zno, enum blk_zone_cond cond, u8 flags)
+{
+ if (zones_state && zno < nr_zones)
+ zones_state[zno] = flags | blk_zone_cond_to_zstate(cond);
+}
+
+static inline u8 blk_zstate_flags(enum blk_zstate zs)
+{
+ return zs & BLK_ZSTATE_FLAGS_MASK;
+}
+
+static inline bool blk_zstate_is_conv(enum blk_zstate zs)
+{
+ return blk_zstate_flags(zs) & BLK_ZFLAG_CONV;
+}
+
/*
* Per-zone write plug.
* @node: hlist_node structure for managing the plug using a hash table.
@@ -135,55 +232,64 @@ const char *blk_zone_cond_str(enum blk_zone_cond zone_cond)
}
EXPORT_SYMBOL_GPL(blk_zone_cond_str);
-static void blk_zone_set_cond(u8 *zones_cond, unsigned int zno,
- enum blk_zone_cond cond)
+static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
+ enum blk_zone_cond cond)
{
- if (!zones_cond)
- return;
+ unsigned int zno = disk_zone_no(disk, sector);
+ u8 *zones_state;
- switch (cond) {
- case BLK_ZONE_COND_IMP_OPEN:
- case BLK_ZONE_COND_EXP_OPEN:
- case BLK_ZONE_COND_CLOSED:
- zones_cond[zno] = BLK_ZONE_COND_ACTIVE;
- return;
- case BLK_ZONE_COND_NOT_WP:
- case BLK_ZONE_COND_EMPTY:
- case BLK_ZONE_COND_FULL:
- case BLK_ZONE_COND_OFFLINE:
- case BLK_ZONE_COND_READONLY:
- default:
- zones_cond[zno] = cond;
- return;
- }
+ rcu_read_lock();
+ zones_state = rcu_dereference(disk->zones_state);
+ if (likely(zones_state && zno < disk->nr_zones))
+ blk_zstate_set(zones_state, disk->nr_zones, zno, cond,
+ blk_zstate_flags(zones_state[zno]));
+ rcu_read_unlock();
}
-static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
- enum blk_zone_cond cond)
+static inline u8 disk_zone_get_state(struct gendisk *disk, sector_t sector)
{
- u8 *zones_cond;
+ unsigned int zno = disk_zone_no(disk, sector);
+ u8 *zones_state, zs;
rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (zones_cond) {
- unsigned int zno = disk_zone_no(disk, sector);
-
- /*
- * The condition of a conventional, readonly and offline zones
- * never changes, so do nothing if the target zone is in one of
- * these conditions.
- */
- switch (zones_cond[zno]) {
- case BLK_ZONE_COND_NOT_WP:
- case BLK_ZONE_COND_READONLY:
- case BLK_ZONE_COND_OFFLINE:
- break;
- default:
- blk_zone_set_cond(zones_cond, zno, cond);
- break;
- }
- }
+ zones_state = rcu_dereference(disk->zones_state);
+ if (likely(zones_state && zno < disk->nr_zones))
+ zs = zones_state[zno];
+ else
+ zs = BLK_ZFLAG_CONV;
rcu_read_unlock();
+
+ return zs;
+}
+
+static enum blk_zone_cond disk_zone_get_cond(struct gendisk *disk,
+ sector_t sector)
+{
+ u8 zs = disk_zone_get_state(disk, sector);
+
+ return blk_zstate_to_zone_cond(zs);
+}
+
+static inline bool
+disk_zone_cond_is_offline_or_readonly(enum blk_zone_cond cond)
+{
+ return cond == BLK_ZONE_COND_READONLY ||
+ cond == BLK_ZONE_COND_OFFLINE;
+}
+
+static inline bool disk_zone_is_offline_or_readonly(struct gendisk *disk,
+ sector_t sector)
+{
+ enum blk_zone_cond cond = disk_zone_get_cond(disk, sector);
+
+ return disk_zone_cond_is_offline_or_readonly(cond);
+}
+
+static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector)
+{
+ u8 zs = disk_zone_get_state(disk, sector);
+
+ return !blk_zstate_is_conv(zs);
}
/**
@@ -195,23 +301,37 @@ static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
*/
bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)
{
- struct gendisk *disk = bdev->bd_disk;
- unsigned int zno = disk_zone_no(disk, sector);
- bool is_seq = false;
- u8 *zones_cond;
+ if (!bdev_is_zoned(bdev))
+ return false;
+
+ return disk_zone_is_seq(bdev->bd_disk, sector);
+}
+EXPORT_SYMBOL_GPL(bdev_zone_is_seq);
+
+/**
+ * bdev_zone_mgmt_allowed - check if management operations are allowed on a zone
+ * @bdev: block device to check
+ * @sector: sector number
+ *
+ * Check if the zone containing @sector on @bdev can be a target for a zone
+ * management operation, that is, if the zone is a sequential write required
+ * zone that is not offline nor read-only.
+ */
+bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector)
+{
+ enum blk_zone_cond cond;
+ u8 zs;
if (!bdev_is_zoned(bdev))
return false;
- rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (zones_cond && zno < disk->nr_zones)
- is_seq = zones_cond[zno] != BLK_ZONE_COND_NOT_WP;
- rcu_read_unlock();
+ zs = disk_zone_get_state(bdev->bd_disk, sector);
+ if (blk_zstate_is_conv(zs))
+ return false;
- return is_seq;
+ cond = blk_zstate_to_zone_cond(zs);
+ return !disk_zone_cond_is_offline_or_readonly(cond);
}
-EXPORT_SYMBOL_GPL(bdev_zone_is_seq);
/*
* Zone report arguments for block device drivers report_zones operation.
@@ -500,12 +620,17 @@ static bool disk_zone_wplug_is_full(struct gendisk *disk,
return zwplug->wp_offset >= disk->last_zone_capacity;
}
+static bool disk_zone_wplug_is_offline_or_readonly(struct blk_zone_wplug *zwplug)
+{
+ return disk_zone_cond_is_offline_or_readonly(zwplug->cond);
+}
+
static bool disk_insert_zone_wplug(struct gendisk *disk,
struct blk_zone_wplug *zwplug)
{
struct blk_zone_wplug *zwplg;
unsigned long flags;
- u8 *zones_cond;
+ u8 *zones_state;
unsigned int idx =
hash_32(zwplug->zone_no, disk->zone_wplugs_hash_bits);
@@ -524,15 +649,16 @@ static bool disk_insert_zone_wplug(struct gendisk *disk,
}
/*
- * Set the zone condition: if we do not yet have a zones_cond array
+ * Set the zone condition: if we do not yet have a zones_state array
* attached to the disk, then this is a zone write plug insert from the
* first call to blk_revalidate_disk_zones(), in which case the zone is
* necessarilly in the active condition.
*/
- zones_cond = rcu_dereference_check(disk->zones_cond,
+ zones_state = rcu_dereference_check(disk->zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock));
- if (zones_cond)
- zwplug->cond = zones_cond[zwplug->zone_no];
+ if (zones_state)
+ zwplug->cond =
+ blk_zstate_to_zone_cond(zones_state[zwplug->zone_no]);
else
zwplug->cond = BLK_ZONE_COND_ACTIVE;
@@ -574,6 +700,26 @@ static inline struct blk_zone_wplug *disk_get_zone_wplug(struct gendisk *disk,
return disk_get_hashed_zone_wplug(disk, sector);
}
+static void disk_for_all_zone_wplugs(struct gendisk *disk,
+ void (*actor)(struct blk_zone_wplug *,
+ void *),
+ void *data)
+{
+ struct blk_zone_wplug *zwplug;
+ unsigned int i;
+
+ if (!disk->zone_wplugs_hash)
+ return;
+
+ rcu_read_lock();
+ for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
+ hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i],
+ node)
+ actor(zwplug, data);
+ }
+ rcu_read_unlock();
+}
+
static void disk_free_zone_wplug_rcu(struct rcu_head *rcu_head)
{
struct blk_zone_wplug *zwplug =
@@ -592,9 +738,9 @@ static void disk_free_zone_wplug(struct blk_zone_wplug *zwplug)
WARN_ON_ONCE(!bio_list_empty(&zwplug->bio_list));
spin_lock_irqsave(&disk->zone_wplugs_hash_lock, flags);
- blk_zone_set_cond(rcu_dereference_check(disk->zones_cond,
+ blk_zstate_set(rcu_dereference_check(disk->zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock)),
- zwplug->zone_no, zwplug->cond);
+ disk->nr_zones, zwplug->zone_no, zwplug->cond, 0);
hlist_del_init_rcu(&zwplug->node);
atomic_dec(&disk->nr_zone_wplugs);
spin_unlock_irqrestore(&disk->zone_wplugs_hash_lock, flags);
@@ -608,6 +754,53 @@ static inline void disk_put_zone_wplug(struct blk_zone_wplug *zwplug)
disk_free_zone_wplug(zwplug);
}
+static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,
+ struct bio *bio)
+{
+ struct request_queue *q = zwplug->disk->queue;
+
+ bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
+ bio_io_error(bio);
+ disk_put_zone_wplug(zwplug);
+ /* Drop the reference taken by disk_zone_wplug_add_bio(). */
+ blk_queue_exit(q);
+}
+
+/*
+ * Abort (fail) all plugged BIOs of a zone write plug.
+ */
+static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)
+{
+ struct gendisk *disk = zwplug->disk;
+ struct bio *bio;
+
+ lockdep_assert_held(&zwplug->lock);
+
+ if (bio_list_empty(&zwplug->bio_list))
+ return;
+
+ pr_warn_ratelimited("%s: zone %u: Aborting plugged BIOs\n",
+ zwplug->disk->disk_name, zwplug->zone_no);
+ while ((bio = bio_list_pop(&zwplug->bio_list)))
+ blk_zone_wplug_bio_io_error(zwplug, bio);
+
+ zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
+
+ /*
+ * If we are using the per disk zone write plugs worker thread, remove
+ * the zone write plug from the work list and drop the reference we
+ * took when the zone write plug was added to that list.
+ */
+ if (blk_queue_zoned_qd1_writes(disk->queue)) {
+ spin_lock(&disk->zone_wplugs_list_lock);
+ if (!list_empty(&zwplug->entry)) {
+ list_del_init(&zwplug->entry);
+ disk_put_zone_wplug(zwplug);
+ }
+ spin_unlock(&disk->zone_wplugs_list_lock);
+ }
+}
+
/*
* Flag the zone write plug as dead and drop the initial reference we got when
* the zone write plug was added to the hash table. The zone write plug will be
@@ -625,6 +818,12 @@ static void disk_mark_zone_wplug_dead(struct blk_zone_wplug *zwplug)
static inline bool disk_check_zone_wplug_dead(struct blk_zone_wplug *zwplug)
{
+ if (disk_zone_wplug_is_offline_or_readonly(zwplug)) {
+ disk_zone_wplug_abort(zwplug);
+ disk_mark_zone_wplug_dead(zwplug);
+ return true;
+ }
+
if (!(zwplug->flags & BLK_ZONE_WPLUG_DEAD))
return false;
@@ -708,53 +907,6 @@ static struct blk_zone_wplug *disk_get_or_alloc_zone_wplug(struct gendisk *disk,
return zwplug;
}
-static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,
- struct bio *bio)
-{
- struct request_queue *q = zwplug->disk->queue;
-
- bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
- bio_io_error(bio);
- disk_put_zone_wplug(zwplug);
- /* Drop the reference taken by disk_zone_wplug_add_bio(). */
- blk_queue_exit(q);
-}
-
-/*
- * Abort (fail) all plugged BIOs of a zone write plug.
- */
-static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)
-{
- struct gendisk *disk = zwplug->disk;
- struct bio *bio;
-
- lockdep_assert_held(&zwplug->lock);
-
- if (bio_list_empty(&zwplug->bio_list))
- return;
-
- pr_warn_ratelimited("%s: zone %u: Aborting plugged BIOs\n",
- zwplug->disk->disk_name, zwplug->zone_no);
- while ((bio = bio_list_pop(&zwplug->bio_list)))
- blk_zone_wplug_bio_io_error(zwplug, bio);
-
- zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
-
- /*
- * If we are using the per disk zone write plugs worker thread, remove
- * the zone write plug from the work list and drop the reference we
- * took when the zone write plug was added to that list.
- */
- if (blk_queue_zoned_qd1_writes(disk->queue)) {
- spin_lock(&disk->zone_wplugs_list_lock);
- if (!list_empty(&zwplug->entry)) {
- list_del_init(&zwplug->entry);
- disk_put_zone_wplug(zwplug);
- }
- spin_unlock(&disk->zone_wplugs_list_lock);
- }
-}
-
/*
* Update a zone write plug condition based on the write pointer offset.
*/
@@ -785,8 +937,10 @@ static void disk_zone_wplug_set_wp_offset(struct gendisk *disk,
/* Update the zone write pointer and abort all plugged BIOs. */
zwplug->flags &= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE;
- zwplug->wp_offset = wp_offset;
- disk_zone_wplug_update_cond(disk, zwplug);
+ if (!disk_zone_wplug_is_offline_or_readonly(zwplug)) {
+ zwplug->wp_offset = wp_offset;
+ disk_zone_wplug_update_cond(disk, zwplug);
+ }
disk_zone_wplug_abort(zwplug);
if (!zwplug->wp_offset || disk_zone_wplug_is_full(disk, zwplug))
@@ -816,8 +970,8 @@ static unsigned int blk_zone_wp_offset(struct blk_zone *zone)
}
}
-static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,
- struct blk_zone *zone)
+static unsigned int disk_zone_wplug_sync_state(struct gendisk *disk,
+ struct blk_zone *zone)
{
struct blk_zone_wplug *zwplug;
unsigned int wp_offset = blk_zone_wp_offset(zone);
@@ -827,6 +981,12 @@ static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,
unsigned long flags;
spin_lock_irqsave(&zwplug->lock, flags);
+ if (disk_zone_cond_is_offline_or_readonly(zone->cond)) {
+ zwplug->flags &= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE;
+ zwplug->cond = zone->cond;
+ zwplug->wp_offset = UINT_MAX;
+ disk_mark_zone_wplug_dead(zwplug);
+ }
if (zwplug->flags & BLK_ZONE_WPLUG_NEED_WP_UPDATE)
disk_zone_wplug_set_wp_offset(disk, zwplug, wp_offset);
spin_unlock_irqrestore(&zwplug->lock, flags);
@@ -871,7 +1031,7 @@ int disk_report_zone(struct gendisk *disk, struct blk_zone *zone,
}
if (disk->zone_wplugs_hash)
- disk_zone_wplug_sync_wp_offset(disk, zone);
+ disk_zone_wplug_sync_state(disk, zone);
if (args && args->cb)
return args->cb(zone, idx, args->data);
@@ -938,29 +1098,36 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
{
struct gendisk *disk = bdev->bd_disk;
sector_t zone_sectors = bdev_zone_sectors(bdev);
+ unsigned int zno = disk_zone_no(disk, sector);
struct blk_zone_wplug *zwplug;
unsigned long flags;
- u8 *zones_cond;
+ u8 *zones_state, zs;
if (!bdev_is_zoned(bdev))
return -EOPNOTSUPP;
- if (sector >= get_capacity(disk))
+ if (sector >= get_capacity(disk) || zno >= disk->nr_zones)
return -EINVAL;
memset(zone, 0, sizeof(*zone));
sector = bdev_zone_start(bdev, sector);
if (!blkdev_has_cached_report_zones(bdev))
- return blkdev_report_zone_fallback(bdev, sector, zone);
+ goto fallback;
rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (!disk->zone_wplugs_hash || !zones_cond) {
+ zones_state = rcu_dereference(disk->zones_state);
+ if (!disk->zone_wplugs_hash || !zones_state) {
rcu_read_unlock();
- return blkdev_report_zone_fallback(bdev, sector, zone);
+ goto fallback;
}
- zone->cond = zones_cond[disk_zone_no(disk, sector)];
+
+ zs = zones_state[zno];
+ zone->cond = blk_zstate_to_zone_cond(zs);
+ if (blk_zstate_is_conv(zs))
+ zone->type = BLK_ZONE_TYPE_CONVENTIONAL;
+ else
+ zone->type = BLK_ZONE_TYPE_SEQWRITE_REQ;
rcu_read_unlock();
zone->start = sector;
@@ -970,8 +1137,7 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
* If this is a conventional zone, we do not have a zone write plug and
* can report the zone immediately.
*/
- if (zone->cond == BLK_ZONE_COND_NOT_WP) {
- zone->type = BLK_ZONE_TYPE_CONVENTIONAL;
+ if (zone->type == BLK_ZONE_TYPE_CONVENTIONAL) {
zone->capacity = zone_sectors;
zone->wp = ULLONG_MAX;
return 0;
@@ -982,14 +1148,12 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
* offline, only set the zone write pointer to an invalid value and
* report the zone.
*/
- zone->type = BLK_ZONE_TYPE_SEQWRITE_REQ;
if (disk_zone_is_last(disk, zone))
zone->capacity = disk->last_zone_capacity;
else
zone->capacity = disk->zone_capacity;
- if (zone->cond == BLK_ZONE_COND_READONLY ||
- zone->cond == BLK_ZONE_COND_OFFLINE) {
+ if (disk_zone_cond_is_offline_or_readonly(zone->cond)) {
zone->wp = ULLONG_MAX;
return 0;
}
@@ -1022,6 +1186,9 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
disk_put_zone_wplug(zwplug);
return 0;
+
+fallback:
+ return blkdev_report_zone_fallback(bdev, sector, zone);
}
EXPORT_SYMBOL_GPL(blkdev_get_zone_info);
@@ -1108,34 +1275,32 @@ static void blk_zone_reset_bio_endio(struct bio *bio)
}
}
+static void disk_zone_wplug_reset_wp(struct blk_zone_wplug *zwplug, void *data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&zwplug->lock, flags);
+ disk_zone_wplug_set_wp_offset(zwplug->disk, zwplug, 0);
+ spin_unlock_irqrestore(&zwplug->lock, flags);
+}
+
static void blk_zone_reset_all_bio_endio(struct bio *bio)
{
struct gendisk *disk = bio->bi_bdev->bd_disk;
- sector_t capacity = get_capacity(disk);
- struct blk_zone_wplug *zwplug;
- unsigned long flags;
sector_t sector;
- unsigned int i;
- if (atomic_read(&disk->nr_zone_wplugs)) {
- /* Update the condition of all zone write plugs. */
- rcu_read_lock();
- for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
- hlist_for_each_entry_rcu(zwplug,
- &disk->zone_wplugs_hash[i],
- node) {
- spin_lock_irqsave(&zwplug->lock, flags);
- disk_zone_wplug_set_wp_offset(disk, zwplug, 0);
- spin_unlock_irqrestore(&zwplug->lock, flags);
- }
- }
- rcu_read_unlock();
- }
+ /* Update the condition of all zone write plugs. */
+ if (atomic_read(&disk->nr_zone_wplugs))
+ disk_for_all_zone_wplugs(disk, disk_zone_wplug_reset_wp, NULL);
/* Update the cached zone conditions. */
- for (sector = 0; sector < capacity;
- sector += bdev_zone_sectors(bio->bi_bdev))
+ for (sector = 0; sector < get_capacity(disk);
+ sector += bdev_zone_sectors(bio->bi_bdev)) {
+ if (!disk_zone_is_seq(disk, sector) ||
+ disk_zone_is_offline_or_readonly(disk, sector))
+ continue;
disk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY);
+ }
clear_bit(GD_ZONE_APPEND_USED, &disk->state);
}
@@ -1381,11 +1546,12 @@ static bool blk_zone_wplug_prepare_bio(struct blk_zone_wplug *zwplug,
return false;
/*
- * Check that the user is not attempting to write to a full zone.
- * We know such BIO will fail, and that would potentially overflow our
- * write pointer offset beyond the end of the zone.
+ * Check that the user is not attempting to write to a full, read-only
+ * or offline zone. We know such BIOs will fail, so there is no point
+ * in issuing them.
*/
- if (disk_zone_wplug_is_full(disk, zwplug))
+ if (disk_zone_wplug_is_full(disk, zwplug) ||
+ disk_zone_wplug_is_offline_or_readonly(zwplug))
return false;
if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
@@ -1442,7 +1608,7 @@ static bool blk_zone_wplug_handle_write(struct bio *bio, unsigned int nr_segs)
}
/* Conventional zones do not need write plugging. */
- if (!bdev_zone_is_seq(bio->bi_bdev, sector)) {
+ if (!disk_zone_is_seq(disk, sector)) {
/* Zone append to conventional zones is not allowed. */
if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
bio_io_error(bio);
@@ -1854,12 +2020,24 @@ static int disk_zone_wplugs_worker(void *data)
void disk_init_zone_resources(struct gendisk *disk)
{
+ mutex_init(&disk->zone_revalidate_mutex);
+ atomic_set(&disk->nr_zone_wplugs, 0);
spin_lock_init(&disk->zone_wplugs_hash_lock);
spin_lock_init(&disk->zone_wplugs_list_lock);
INIT_LIST_HEAD(&disk->zone_wplugs_list);
init_completion(&disk->zone_wplugs_worker_bio_done);
}
+static unsigned int disk_get_nr_zones(struct gendisk *disk, sector_t capacity)
+{
+ struct queue_limits *lim = &disk->queue->limits;
+
+ if (!capacity || !lim->chunk_sectors)
+ return 0;
+
+ return DIV_ROUND_UP_ULL(capacity, lim->chunk_sectors);
+}
+
/*
* For the size of a disk zone write plug hash table, use the size of the
* zone write plug mempool, which is the maximum of the disk open zones and
@@ -1869,13 +2047,24 @@ void disk_init_zone_resources(struct gendisk *disk)
#define BLK_ZONE_WPLUG_MAX_HASH_BITS 9
#define BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE 128
-static int disk_alloc_zone_resources(struct gendisk *disk,
- unsigned int pool_size)
+static int disk_alloc_zone_resources(struct gendisk *disk, sector_t capacity)
{
- unsigned int i;
+ struct queue_limits *lim = &disk->queue->limits;
+ unsigned int nr_zones, pool_size, i;
int ret = -ENOMEM;
- atomic_set(&disk->nr_zone_wplugs, 0);
+ nr_zones = disk_get_nr_zones(disk, capacity);
+ if (!nr_zones)
+ return -ENODEV;
+
+ /*
+ * If the device has no limit on the maximum number of open and active
+ * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.
+ */
+ pool_size = max(lim->max_open_zones, lim->max_active_zones);
+ if (!pool_size)
+ pool_size = min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, nr_zones);
+
disk->zone_wplugs_hash_bits =
min(ilog2(pool_size) + 1, BLK_ZONE_WPLUG_MAX_HASH_BITS);
@@ -1893,21 +2082,6 @@ static int disk_alloc_zone_resources(struct gendisk *disk,
if (!disk->zone_wplugs_pool)
goto free_hash;
- /*
- * We may already have a zone write plug workqueue as this function may
- * be called after disk_free_zone_resources(), which does not destroy
- * the workqueue (the zone write plugs workqueue is destroyed at
- * disk_release() time).
- */
- if (!disk->zone_wplugs_wq) {
- disk->zone_wplugs_wq =
- alloc_workqueue("%s_zwplugs",
- WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,
- pool_size, disk->disk_name);
- if (!disk->zone_wplugs_wq)
- goto destroy_pool;
- }
-
disk->zone_wplugs_worker =
kthread_create(disk_zone_wplugs_worker, disk,
"%s_zwplugs_worker", disk->disk_name);
@@ -1918,8 +2092,18 @@ static int disk_alloc_zone_resources(struct gendisk *disk,
}
wake_up_process(disk->zone_wplugs_worker);
+ disk->zone_wplugs_wq =
+ alloc_workqueue("%s_zwplugs",
+ WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,
+ pool_size, disk->disk_name);
+ if (!disk->zone_wplugs_wq)
+ goto stop_worker;
+
return 0;
+stop_worker:
+ kthread_stop(disk->zone_wplugs_worker);
+ disk->zone_wplugs_worker = NULL;
destroy_pool:
mempool_destroy(disk->zone_wplugs_pool);
disk->zone_wplugs_pool = NULL;
@@ -1963,19 +2147,19 @@ static void disk_destroy_zone_wplugs_hash_table(struct gendisk *disk)
disk->zone_wplugs_pool = NULL;
}
-static void disk_set_zones_cond_array(struct gendisk *disk, u8 *zones_cond)
+static void disk_set_zones_state_array(struct gendisk *disk, u8 *zones_state)
{
unsigned long flags;
spin_lock_irqsave(&disk->zone_wplugs_hash_lock, flags);
- zones_cond = rcu_replace_pointer(disk->zones_cond, zones_cond,
+ zones_state = rcu_replace_pointer(disk->zones_state, zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock));
spin_unlock_irqrestore(&disk->zone_wplugs_hash_lock, flags);
- kfree_rcu_mightsleep(zones_cond);
+ kfree_rcu_mightsleep(zones_state);
}
-static void disk_free_zone_resources(struct gendisk *disk)
+void disk_release_zone_resources(struct gendisk *disk)
{
if (disk->zone_wplugs_worker) {
kthread_stop(disk->zone_wplugs_worker);
@@ -1983,30 +2167,24 @@ static void disk_free_zone_resources(struct gendisk *disk)
}
WARN_ON_ONCE(!list_empty(&disk->zone_wplugs_list));
- if (disk->zone_wplugs_wq)
- drain_workqueue(disk->zone_wplugs_wq);
+ if (disk->zone_wplugs_wq) {
+ destroy_workqueue(disk->zone_wplugs_wq);
+ disk->zone_wplugs_wq = NULL;
+ }
disk_destroy_zone_wplugs_hash_table(disk);
- disk_set_zones_cond_array(disk, NULL);
+ disk_set_zones_state_array(disk, NULL);
disk->zone_capacity = 0;
disk->last_zone_capacity = 0;
disk->nr_zones = 0;
-}
-
-void disk_release_zone_resources(struct gendisk *disk)
-{
- if (disk->zone_wplugs_wq) {
- destroy_workqueue(disk->zone_wplugs_wq);
- disk->zone_wplugs_wq = NULL;
- }
-
- disk_free_zone_resources(disk);
+ mutex_destroy(&disk->zone_revalidate_mutex);
}
struct blk_revalidate_zone_args {
struct gendisk *disk;
- u8 *zones_cond;
+ sector_t capacity;
+ u8 *zones_state;
unsigned int nr_zones;
unsigned int nr_conv_zones;
unsigned int zone_capacity;
@@ -2014,73 +2192,74 @@ struct blk_revalidate_zone_args {
sector_t sector;
};
-static int disk_revalidate_zone_resources(struct gendisk *disk,
- struct blk_revalidate_zone_args *args)
+static int disk_init_revalidate_args(struct gendisk *disk,
+ struct blk_revalidate_zone_args *args)
{
- struct queue_limits *lim = &disk->queue->limits;
- unsigned int pool_size;
- int ret = 0;
-
args->disk = disk;
- args->nr_zones =
- DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors);
+ args->nr_zones = disk_get_nr_zones(disk, args->capacity);
/* Cached zone conditions: 1 byte per zone */
- args->zones_cond = kzalloc(args->nr_zones, GFP_NOIO);
- if (!args->zones_cond)
+ args->zones_state = kzalloc(args->nr_zones, GFP_NOIO);
+ if (!args->zones_state)
return -ENOMEM;
- if (!disk_need_zone_resources(disk))
- return 0;
-
- /*
- * If the device has no limit on the maximum number of open and active
- * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.
- */
- pool_size = max(lim->max_open_zones, lim->max_active_zones);
- if (!pool_size)
- pool_size =
- min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, args->nr_zones);
-
- if (!disk->zone_wplugs_hash) {
- ret = disk_alloc_zone_resources(disk, pool_size);
- if (ret)
- kfree(args->zones_cond);
- }
-
- return ret;
+ return 0;
}
/*
- * Update the disk zone resources information and device queue limits.
- * The disk queue is frozen when this is executed.
+ * Revalidate and update the disk zone resources information and device queue
+ * limits.
*/
-static int disk_update_zone_resources(struct gendisk *disk,
- struct blk_revalidate_zone_args *args)
+static int disk_revalidate_zone_resources(struct gendisk *disk,
+ struct blk_revalidate_zone_args *args)
{
struct request_queue *q = disk->queue;
unsigned int nr_seq_zones;
unsigned int pool_size, memflags;
struct queue_limits lim;
+ sector_t capacity;
int ret = 0;
lim = queue_limits_start_update(q);
memflags = blk_mq_freeze_queue(q);
- disk->nr_zones = args->nr_zones;
- if (args->nr_conv_zones >= disk->nr_zones) {
- queue_limits_cancel_update(q);
+ /*
+ * Using the re-evaluated disk capacity, make sure that the entire disk
+ * has been checked.
+ */
+ capacity = get_capacity(disk);
+ if (args->capacity != capacity) {
+ pr_warn("%s: Capacity has changed (%llu -> %llu)\n",
+ disk->disk_name, args->capacity, capacity);
+ /* Force a retry if we have a valid (non-zero) capacity. */
+ if (capacity)
+ ret = -EAGAIN;
+ else
+ ret = -ENODEV;
+ goto unfreeze;
+ }
+
+ /* Make sure that all zones have been checked. */
+ if (args->sector != capacity) {
+ pr_warn("%s: last zone and capacity mismatch (%llu != %llu)\n",
+ disk->disk_name, args->sector, capacity);
+ ret = -ENODEV;
+ goto unfreeze;
+ }
+
+ if (args->nr_conv_zones >= args->nr_zones) {
pr_warn("%s: Invalid number of conventional zones %u / %u\n",
- disk->disk_name, args->nr_conv_zones, disk->nr_zones);
+ disk->disk_name, args->nr_conv_zones, args->nr_zones);
ret = -ENODEV;
goto unfreeze;
}
+ disk->nr_zones = args->nr_zones;
disk->zone_capacity = args->zone_capacity;
disk->last_zone_capacity = args->last_zone_capacity;
- disk_set_zones_cond_array(disk, args->zones_cond);
- args->zones_cond = NULL;
+ disk_set_zones_state_array(disk, args->zones_state);
+ args->zones_state = NULL;
/*
* Some devices can advertise zone resource limits that are larger than
@@ -2095,7 +2274,7 @@ static int disk_update_zone_resources(struct gendisk *disk,
lim.max_active_zones = 0;
if (!disk->zone_wplugs_pool)
- goto commit;
+ goto unfreeze;
/*
* If the device has no limit on the maximum number of open and active
@@ -2117,51 +2296,65 @@ static int disk_update_zone_resources(struct gendisk *disk,
lim.max_open_zones = 0;
}
-commit:
- ret = queue_limits_commit_update(q, &lim);
-
unfreeze:
+ if (ret)
+ queue_limits_cancel_update(q);
+ else
+ ret = queue_limits_commit_update(q, &lim);
+
blk_mq_unfreeze_queue(q, memflags);
return ret;
}
-static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx,
+static void disk_drop_zone_wplug(struct blk_zone_wplug *zwplug, void *data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&zwplug->lock, flags);
+ disk_zone_wplug_abort(zwplug);
+ disk_mark_zone_wplug_dead(zwplug);
+ spin_unlock_irqrestore(&zwplug->lock, flags);
+}
+
+static int disk_revalidate_capacity(struct gendisk *disk,
struct blk_revalidate_zone_args *args)
{
- enum blk_zone_cond cond = zone->cond;
+ struct queue_limits *lim = &disk->queue->limits;
+ sector_t zone_sectors = lim->chunk_sectors;
+ unsigned int nr_zones;
+ int ret = -ENODEV;
- /* Check that the zone condition is consistent with the zone type. */
- switch (cond) {
- case BLK_ZONE_COND_NOT_WP:
- if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL)
- goto invalid_condition;
- break;
- case BLK_ZONE_COND_IMP_OPEN:
- case BLK_ZONE_COND_EXP_OPEN:
- case BLK_ZONE_COND_CLOSED:
- case BLK_ZONE_COND_EMPTY:
- case BLK_ZONE_COND_FULL:
- case BLK_ZONE_COND_OFFLINE:
- case BLK_ZONE_COND_READONLY:
- if (zone->type != BLK_ZONE_TYPE_SEQWRITE_REQ)
- goto invalid_condition;
- break;
- default:
- pr_warn("%s: Invalid zone condition 0x%X\n",
- args->disk->disk_name, cond);
- return -ENODEV;
+ /* Checks that the device driver indicated a valid zone size. */
+ if (!zone_sectors || !is_power_of_2(zone_sectors)) {
+ pr_warn("%s: Invalid non power of two zone size (%llu)\n",
+ disk->disk_name, zone_sectors);
+ goto drop_all_zwplugs;
}
- blk_zone_set_cond(args->zones_cond, idx, cond);
+ args->capacity = get_capacity(disk);
+ nr_zones = disk_get_nr_zones(disk, args->capacity);
+ if (!args->capacity || !nr_zones)
+ goto drop_all_zwplugs;
+
+ /*
+ * Check if the capacity has changed. If it did, assume that the device
+ * was reformatted and that all sequential zones are now empty. So drop
+ * all zone write plug.
+ */
+ if (disk->nr_zones && disk->nr_zones != nr_zones) {
+ pr_warn("%s: Number of zones changed (%u -> %u)\n",
+ disk->disk_name, disk->nr_zones, nr_zones);
+ ret = 0;
+ goto drop_all_zwplugs;
+ }
return 0;
-invalid_condition:
- pr_warn("%s: Invalid zone condition 0x%x for type 0x%x\n",
- args->disk->disk_name, cond, zone->type);
+drop_all_zwplugs:
+ disk_for_all_zone_wplugs(disk, disk_drop_zone_wplug, NULL);
- return -ENODEV;
+ return ret;
}
static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,
@@ -2169,12 +2362,27 @@ static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,
{
struct gendisk *disk = args->disk;
+ /* Check the zone condition. */
+ switch (zone->cond) {
+ case BLK_ZONE_COND_NOT_WP:
+ case BLK_ZONE_COND_OFFLINE:
+ case BLK_ZONE_COND_READONLY:
+ break;
+ default:
+ pr_warn("%s: Invalid conv. zone condition 0x%X at sector %llu\n",
+ disk->disk_name, zone->cond, zone->start);
+ return -ENODEV;
+ }
+
if (zone->capacity != zone->len) {
pr_warn("%s: Invalid conventional zone capacity\n",
disk->disk_name);
return -ENODEV;
}
+ blk_zstate_set(args->zones_state, args->nr_zones, idx,
+ zone->cond, BLK_ZFLAG_CONV);
+
if (disk_zone_is_last(disk, zone))
args->last_zone_capacity = zone->capacity;
@@ -2190,6 +2398,24 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,
struct blk_zone_wplug *zwplug;
unsigned int wp_offset;
+ /* Check the zone condition. */
+ switch (zone->cond) {
+ case BLK_ZONE_COND_IMP_OPEN:
+ case BLK_ZONE_COND_EXP_OPEN:
+ case BLK_ZONE_COND_CLOSED:
+ case BLK_ZONE_COND_EMPTY:
+ case BLK_ZONE_COND_FULL:
+ case BLK_ZONE_COND_OFFLINE:
+ case BLK_ZONE_COND_READONLY:
+ break;
+ default:
+ pr_warn("%s: Invalid seq. zone condition 0x%X at sector %llu\n",
+ disk->disk_name, zone->cond, zone->start);
+ return -ENODEV;
+ }
+
+ blk_zstate_set(args->zones_state, args->nr_zones, idx, zone->cond, 0);
+
/*
* Remember the capacity of the first sequential zone and check
* if it is constant for all zones, ignoring the last zone as it can be
@@ -2214,7 +2440,7 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,
if (!disk->zone_wplugs_hash)
return 0;
- wp_offset = disk_zone_wplug_sync_wp_offset(disk, zone);
+ wp_offset = disk_zone_wplug_sync_state(disk, zone);
if (!wp_offset || wp_offset >= zone->capacity)
return 0;
@@ -2244,7 +2470,7 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return -ENODEV;
}
- if (zone->start >= get_capacity(disk) || !zone->len) {
+ if (zone->start >= args->capacity || !zone->len) {
pr_warn("%s: Invalid zone start %llu, length %llu\n",
disk->disk_name, zone->start, zone->len);
return -ENODEV;
@@ -2272,11 +2498,6 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return -ENODEV;
}
- /* Check zone condition */
- ret = blk_revalidate_zone_cond(zone, idx, args);
- if (ret)
- return ret;
-
/* Check zone type */
switch (zone->type) {
case BLK_ZONE_TYPE_CONVENTIONAL:
@@ -2313,42 +2534,48 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
*/
int blk_revalidate_disk_zones(struct gendisk *disk)
{
- struct request_queue *q = disk->queue;
- sector_t zone_sectors = q->limits.chunk_sectors;
- sector_t capacity = get_capacity(disk);
struct blk_revalidate_zone_args args = { };
- unsigned int memflags, noio_flag;
struct blk_report_zones_args rep_args = {
.cb = blk_revalidate_zone_cb,
.data = &args,
};
- int ret = -ENOMEM;
+ unsigned int noio_flag;
+ int retries = 2;
+ int ret;
- if (WARN_ON_ONCE(!blk_queue_is_zoned(q)))
+ if (WARN_ON_ONCE(!blk_queue_is_zoned(disk->queue)))
return -EIO;
- if (!capacity)
- return -ENODEV;
-
/*
- * Checks that the device driver indicated a valid zone size and that
- * the max zone append limit is set.
+ * Serialize calls to this function so that we can safely look at and
+ * eventually change the disk zone information.
*/
- if (!zone_sectors || !is_power_of_2(zone_sectors)) {
- pr_warn("%s: Invalid non power of two zone size (%llu)\n",
- disk->disk_name, zone_sectors);
- return -ENODEV;
- }
+ mutex_lock(&disk->zone_revalidate_mutex);
+
+again:
+ ret = disk_revalidate_capacity(disk, &args);
+ if (ret)
+ goto unlock;
/*
- * Ensure that all memory allocations in this context are done as if
- * GFP_NOIO was specified.
+ * Allocate zone resources if they are needed and we have not done
+ * so yet, and initialize the revalidation arguments passed to report
+ * zones. Ensure that all memory allocations in this context are done as
+ * if GFP_NOIO was specified.
*/
noio_flag = memalloc_noio_save();
- ret = disk_revalidate_zone_resources(disk, &args);
+ if (disk_need_zone_resources(disk) && !disk->zone_wplugs_hash) {
+ ret = disk_alloc_zone_resources(disk, args.capacity);
+ if (ret) {
+ memalloc_noio_restore(noio_flag);
+ goto unlock;
+ }
+ }
+
+ ret = disk_init_revalidate_args(disk, &args);
if (ret) {
memalloc_noio_restore(noio_flag);
- return ret;
+ goto unlock;
}
ret = disk->fops->report_zones(disk, 0, UINT_MAX, &rep_args);
@@ -2358,33 +2585,32 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
}
memalloc_noio_restore(noio_flag);
- if (ret <= 0)
- goto free_resources;
-
- /*
- * If zones where reported, make sure that the entire disk capacity
- * has been checked.
- */
- if (args.sector != capacity) {
- pr_warn("%s: Missing zones from sector %llu\n",
- disk->disk_name, args.sector);
- ret = -ENODEV;
- goto free_resources;
- }
+ if (ret < 0)
+ goto free_args;
- ret = disk_update_zone_resources(disk, &args);
+ ret = disk_revalidate_zone_resources(disk, &args);
if (ret)
- goto free_resources;
+ goto free_args;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
return 0;
-free_resources:
- pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
+free_args:
+ kfree(args.zones_state);
- kfree(args.zones_cond);
- memflags = blk_mq_freeze_queue(q);
- disk_free_zone_resources(disk);
- blk_mq_unfreeze_queue(q, memflags);
+ if (ret == -EAGAIN) {
+ if (retries) {
+ memset(&args, 0, sizeof(args));
+ retries--;
+ goto again;
+ }
+ ret = -ENODEV;
+ }
+
+ pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
+unlock:
+ mutex_unlock(&disk->zone_revalidate_mutex);
return ret;
}
@@ -2435,8 +2661,9 @@ EXPORT_SYMBOL_GPL(blk_zone_issue_zeroout);
#ifdef CONFIG_BLK_DEBUG_FS
static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,
- struct seq_file *m)
+ void *data)
{
+ struct seq_file *m = data;
unsigned int zwp_wp_offset, zwp_flags;
unsigned int zwp_zone_no, zwp_ref;
unsigned int zwp_bio_list_size;
@@ -2461,19 +2688,8 @@ static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,
int queue_zone_wplugs_show(void *data, struct seq_file *m)
{
struct request_queue *q = data;
- struct gendisk *disk = q->disk;
- struct blk_zone_wplug *zwplug;
- unsigned int i;
- if (!disk->zone_wplugs_hash)
- return 0;
-
- rcu_read_lock();
- for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++)
- hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i],
- node)
- queue_zone_wplug_show(zwplug, m);
- rcu_read_unlock();
+ disk_for_all_zone_wplugs(q->disk, queue_zone_wplug_show, m);
return 0;
}
diff --git a/block/blk.h b/block/blk.h
index 50abfd9328861..2cc03aa54c532 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -577,6 +577,7 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd,
unsigned long arg);
int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg);
+bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector);
#else /* CONFIG_BLK_DEV_ZONED */
static inline void disk_init_zone_resources(struct gendisk *disk)
{
@@ -619,6 +620,11 @@ static inline int blkdev_zone_mgmt_ioctl(struct block_device *bdev,
{
return -ENOTTY;
}
+static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,
+ sector_t sector)
+{
+ return false;
+}
#endif /* CONFIG_BLK_DEV_ZONED */
struct block_device *bdev_alloc(struct gendisk *disk, u8 partno);
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 4f7905c3412b8..8252c896e3eac 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -191,14 +191,17 @@ struct gendisk {
#ifdef CONFIG_BLK_DEV_ZONED
/*
* Zoned block device information. Reads of this information must be
- * protected with blk_queue_enter() / blk_queue_exit(). Modifying this
- * information is only allowed while no requests are being processed.
- * See also blk_mq_freeze_queue() and blk_mq_unfreeze_queue().
+ * protected with blk_queue_enter() / blk_queue_exit() or by holding a
+ * lock on zone_revalidate_mutex. blk_revalidate_disk_zones() may modify
+ * this information while no requests are being processed (disk queue
+ * frozen with blk_mq_freeze_queue()) and while holding a lock on
+ * zone_revalidate_mutex.
*/
+ struct mutex zone_revalidate_mutex;
unsigned int nr_zones;
unsigned int zone_capacity;
unsigned int last_zone_capacity;
- u8 __rcu *zones_cond;
+ u8 __rcu *zones_state;
unsigned int zone_wplugs_hash_bits;
atomic_t nr_zone_wplugs;
spinlock_t zone_wplugs_hash_lock;
|
| 3/2 | 2026/09/08 09:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 19649 output: 30 thoughts: 698 Thoughts: **Analyzing the Block Layer Patch for Fuzzing Potential** Okay, let's break this down. My initial assessment is that this patch, touching `block/blk-core.c`, `block/blk-zoned.c`, `block/blk.h`, and `include/linux/blkdev.h`, specifically targets the block layer and, more precisely, zoned block devices (`CONFIG_BLK_DEV_ZONED`). My internal checklist is already ticking: worth fuzzing? Necessary configs? Key symbols to target? Let's figure it out. First, the diff itself. The main change in `blk-core.c` replaces a condition within `submit_bio_noacct`. It's swapping `!bdev_zone_is_seq(bio->bi_bdev, bio->bi_iter.bi_sector)` with `!bdev_zone_mgmt_allowed(bdev, bio->bi_iter.bi_sector)`. Hmm, slightly unusual with `bio->bi_bdev` versus the standalone `bdev` in the new function. I should double-check the surrounding context of `submit_bio_noacct` around line 901 just to make sure that's kosher. `blk-zoned.c` looks significantly more involved. This is where things get interesting. Extensive refactoring is happening around zone management. They're introducing `enum blk_zstate`, lookup tables (`blk_zstate2zcond`, `blk_zcond2zstate`), shifting `zones_cond` to `zones_state` within `struct gendisk`, introducing `bdev_zone_mgmt_allowed` (which, again, is probably important), and updating handling for dead zone plugs, especially when zones are offline or read-only. I'm also seeing a bunch of revalidation and resource management functions being refactored with retry loops and `zone_revalidate_mutex`, which immediately draws my attention as potential points of vulnerability. Furthermore, they are also introducing RCU usage with `zones_state` and related functions. There are updates for `blkdev_get_zone_info`, `blk_zone_reset_all_bio_endio` as well, plus a new helper function, `disk_for_all_zone_wplugs`. This is a *lot* of churn, which suggests a significant redesign and, consequently, a higher probability of introducing subtle bugs. This has my attention. The next thing I need to confirm is whether zoned block devices are even reachable in a standard fuzzing environment. The good news is, yes, it seems like it. I can use `null_blk` (with zoned mode), or loop/dm-zoned, or even virtio-blk/scsi/nvme. This is important to ensure these functionalities are within easy reach and I can trigger code paths for the new code. Syzkaller is already testing zoned devices, which means standard syscalls and ioctls, like `BLKREPORTZONE`, `BLKRESETZONE`, etc., are likely already working. I could save time and build on their existing configurations. Now, back to that `submit_bio_noacct` check. Let me just use `read-file` or `grepper` to inspect it more closely. I need to understand that subtle change and if there's anything fishy going on with the `bdev` variable. |
| 4/2 | 2026/09/08 09:18 | tool | grepper |
0mArgs:{
"Expression": "bdev_zone_mgmt_allowed",
"PathPrefix": "block/"
}Results: {
"Output": "block/blk-core.c=817=void submit_bio_noacct(struct bio *bio)\n--\nblock/blk-core.c-907-\t\t */\nblock/blk-core.c:908:\t\tif (!bdev_zone_mgmt_allowed(bdev, bio-\u003ebi_iter.bi_sector))\nblock/blk-core.c-909-\t\t\tgoto end_io;\n--\nblock/blk-zoned.c=309=EXPORT_SYMBOL_GPL(bdev_zone_is_seq);\n--\nblock/blk-zoned.c-311-/**\nblock/blk-zoned.c:312: * bdev_zone_mgmt_allowed - check if management operations are allowed on a zone\nblock/blk-zoned.c-313- * @bdev: block device to check\n--\nblock/blk-zoned.c-319- */\nblock/blk-zoned.c:320:bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector)\nblock/blk-zoned.c-321-{\n--\nblock/blk.h=578=int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode,\nblock/blk.h-579-\t\tunsigned int cmd, unsigned long arg);\nblock/blk.h:580:bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector);\nblock/blk.h-581-#else /* CONFIG_BLK_DEV_ZONED */\n--\nblock/blk.h=618=static inline int blkdev_zone_mgmt_ioctl(struct block_device *bdev,\n--\nblock/blk.h-622-}\nblock/blk.h:623:static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,\nblock/blk.h-624-\t\t\t\t\t sector_t sector)\n"
} |
| 5/2 | 2026/09/08 09:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 21592 output: 31 thoughts: 29 Thoughts: Here's my take on the thought process, as requested: **Diving into `block/Kconfig` for Zoned Block Device Configuration** Alright, time to get this straight. I need to figure out the essential configuration options required to enable the Zoned Block Device support, which means a deep dive into the `block/Kconfig` file. I'm assuming that's where the relevant configuration variables and dependencies are defined. I need to be thorough; missing a crucial option could lead to a host of problems down the line. Let's see what the Kconfig file holds for `CONFIG_BLK_DEV_ZONED` and what it depends on. I'll be looking for things like: * Any core ZBD-related features that *must* be enabled or disabled * Dependencies on specific kernel subsystems or drivers, like specific block layer features, hardware access, or even filesystem support. * Relevant default values and help text to understand the rationale behind each configuration option. * Any relationships between the ZBD option and other, possibly more general, block device options. This initial assessment will give me the building blocks to understand the overall configuration landscape for zoned block devices. It's the foundation for any deeper work or debugging that might be needed later. |
| 6/2 | 2026/09/08 09:18 | tool | grepper |
0mArgs:{
"Expression": "config BLK_DEV_ZONED",
"PathPrefix": "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"
} |
| 7/2 | 2026/09/08 09:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22001 output: 31 thoughts: 18 Thoughts: Here's a summary of my thought process, as if I were analyzing that Kconfig entry: **Analyzing the BLK_DEV_ZONED Kconfig Entry** Okay, time to dive into the `BLK_DEV_ZONED` Kconfig entry. Let's see what we're dealing with here. My initial thought is to quickly identify what this pertains to. The name immediately suggests zoned block devices, which of course I'm familiar with – they're the new generation of storage devices that require software to respect zones, as opposed to random access. So, the question is, what's the configuration option like, what are the dependencies, and how does it relate to the rest of the block layer? First, I need to understand its context. Where does this live within the overall Kconfig structure? Is it nested deeply within some subsystem, or is it more top-level? Understanding the hierarchy will give me insight into its scope. Next, I'll be looking for dependencies. What other options *must* be enabled or *cannot* be enabled alongside this? Are there hardware dependencies? Driver prerequisites? This tells me about the prerequisites for enabling zoned block device support. Beyond that, I'll look for any "help" text. Good Kconfig entries provide a clear explanation of what the option does, and under what circumstances it should be enabled or disabled. Does it explain the implications of enabling this from a performance or power consumption perspective? Are there any specific use cases highlighted? Ideally, there would be cross-references to relevant documentation or design documents, though that isn't always the case. Finally, I'll scan the surrounding options. Does this entry provide a general toggle or does it enable various sub-features? What other related options are present? Understanding the connections to other related configurations will give me the best holistic understanding of the role of this configuration setting in my domain of expertise. Based on this, I'll have a good idea of whether this configuration option has changed, how this functionality aligns with current kernel design and what's next. |
| 8/2 | 2026/09/08 09:18 | 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"
} |
| 9/2 | 2026/09/08 09:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22873 output: 25 thoughts: 97 Thoughts: **Investigating Zoned Block Device Support** Okay, let's dive in and see what's what with zoned block device configurations. My first thought is to double-check for related configs, perhaps anything around `null_blk` and zoned support. Hmm, wait a second... `BLK_DEV_ZONED` seems to be the main configuration key. It's the primary guard for `blk-zoned.c` and also seems to gate the zoned block features sprinkled throughout `blk-core.c` and `blkdev.h`. Now, to get a better handle on how this stuff gets built, I'll need to poke around in `block/Makefile`. Let's see how they handle `blk-zoned.c` in the build process. |
| 10/2 | 2026/09/08 09:18 | tool | grepper |
0mArgs:{
"Expression": "blk-zoned",
"PathPrefix": "block/"
}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"
} |
| 11/2 | 2026/09/08 09:18 | llm | patch-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 23290 output: 240 thoughts: 1334 Thoughts: **Analysis of Zoned Block Device Support for Fuzzing** Alright, let's break this down. First, the dependency: `obj-$(CONFIG_BLK_DEV_ZONED) += blk-zoned.o`. So, `BLK_DEV_ZONED` is the primary configuration option. Now, let's examine the code modifications in this patch. I've identified several key functions that warrant closer inspection, noting new functions and those that have been modified: 1. `bdev_zone_mgmt_allowed` - newly exported function, indicating potential external interaction. 2. `blk_revalidate_disk_zones` - Likely involved in device state validation, a common source of bugs. 3. `blkdev_get_zone_info` - Modified, and exported, so likely part of the external interface, or at least a public API. 4. `disk_revalidate_zone_resources`, `disk_alloc_zone_resources` - Resource management functions that are modified, which means they are important for fuzzing. 5. `disk_zone_wplug_sync_state`, `disk_zone_wplug_abort` - Modified write-plugging state management; these are critical for ensuring data integrity and concurrency, and are a good focus. 6. `disk_revalidate_capacity` - A new function focused on disk capacity revalidation. 7. `blk_zone_reset_all_bio_endio` - I suspect there's a good reason that this is being modified, since endio handlers are a common source of errors. 8. `blk_revalidate_conv_zone`, `blk_revalidate_seq_zone` - Focuses on zone validation; there's probably a lot of state. 9. `bdev_zone_is_seq` - another exported function, indicating that this is externally visible, and worth fuzzing. Next, I've categorized the symbols, identifying the externally visible ones, which are the primary focus of fuzzing efforts: * `bdev_zone_mgmt_allowed` * `bdev_zone_is_seq` * `blkdev_get_zone_info` * `blk_revalidate_disk_zones` * `disk_zone_wplug_sync_state` * `disk_zone_wplug_abort` * `disk_revalidate_zone_resources` * `disk_revalidate_capacity` * `disk_alloc_zone_resources` * `blk_revalidate_seq_zone` * `blk_revalidate_conv_zone` I've examined the scope of these symbols. While some are static, the non-static ones are the key focus. I have also verified that `BLK_DEV_ZONED` is the only config dependency. The question of whether this is worth fuzzing has been determined to be "true". This code is reachable and exercised by common operations on zoned block devices, including I/O control operations, write and append operations, and disk revalidation tasks. More importantly, it fundamentally alters the state management for zoned block devices, with an updated state management system, offline/readonly zone handling, locking with `zone_revalidate_mutex` and RCU, and zone write plugging changes, all significant areas for potential vulnerabilities. Therefore, the set-results parameters are straightforward: `WorthFuzzing: true`, and the Reasoning is described as above. EnableConfigs includes "BLK_DEV_ZONED", and FocusSymbols are the list of non-ubiquitous functions I've just highlighted. This should be a strong signal for the fuzzer. |
| 12/2 | 2026/09/08 09:18 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"BLK_DEV_ZONED"
],
"FocusSymbols": [
"blk_revalidate_disk_zones",
"bdev_zone_mgmt_allowed",
"bdev_zone_is_seq",
"blkdev_get_zone_info",
"disk_zone_wplug_sync_state",
"disk_zone_wplug_abort",
"disk_revalidate_zone_resources",
"disk_revalidate_capacity",
"disk_alloc_zone_resources",
"blk_revalidate_seq_zone",
"blk_revalidate_conv_zone"
],
"Reasoning": "The patch modifies the block layer's zoned device handling, including zone condition and state tracking (switching from zones_cond to zones_state), revalidation of zoned devices with synchronization under zone_revalidate_mutex, handling of offline/read-only zones, zone write plug aborts and dead state checks, and zone management validation during bio submission. These reachable core block layer changes warrant fuzzing.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"BLK_DEV_ZONED"
],
"FocusSymbols": [
"blk_revalidate_disk_zones",
"bdev_zone_mgmt_allowed",
"bdev_zone_is_seq",
"blkdev_get_zone_info",
"disk_zone_wplug_sync_state",
"disk_zone_wplug_abort",
"disk_revalidate_zone_resources",
"disk_revalidate_capacity",
"disk_alloc_zone_resources",
"blk_revalidate_seq_zone",
"blk_revalidate_conv_zone"
],
"Reasoning": "The patch modifies the block layer's zoned device handling, including zone condition and state tracking (switching from zones_cond to zones_state), revalidation of zoned devices with synchronization under zone_revalidate_mutex, handling of offline/read-only zones, zone write plug aborts and dead state checks, and zone management validation during bio submission. These reachable core block layer changes warrant fuzzing.",
"WorthFuzzing": true
} |
| 13/1 | 2026/09/08 09:18 | action | If |
0mArgs:{
"WorthFuzzing": true
}Results: null |
| 14/2 | 2026/09/08 09:18 | agent | kmsan-evaluator |
0mModel:core Args: null Results: {
"KMSANReasoning": "The patch refactors zoned block device condition and state tracking (introducing enum blk_zstate to pack zone condition and conventional zone flag into a single u8 array per disk), adds mutex synchronization around zone revalidation, and updates zone management operation checks.\n\nAll allocated memory and data structures touched by the patch are fully initialized:\n1. The dynamic `zones_state` array is allocated using `kzalloc()`.\n2. Stack structures such as `struct blk_revalidate_zone_args` and `struct blk_zone` are explicitly zero-initialized (`{ }` / `memset`).\n3. Return paths and condition/state helper functions (`disk_zone_get_state`, `blk_zstate_to_zone_cond`, etc.) assign all outputs and default cases cleanly.\n4. No uninitialized memory or structure padding is exposed or copied to user space.\n\nAny potential memory safety issues (e.g. array index bounds, concurrency, use-after-free during zone write plug teardown, or lock ordering with `zone_revalidate_mutex`) are covered by KASAN and LOCKDEP. KMSAN is not required.",
"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 9514449fcfb821c5f2bb5c88be27acd256a43195
Author: syz-cluster <triage@syzkaller.com>
Date: Tue Sep 8 09:18:29 2026 +0000
syz-cluster: applied patch under review
diff --git a/block/blk-core.c b/block/blk-core.c
index 196bccf27f58d..13dc70e8f55d9 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -901,8 +901,11 @@ void submit_bio_noacct(struct bio *bio)
case REQ_OP_ZONE_CLOSE:
case REQ_OP_ZONE_RESET:
case REQ_OP_ZONE_FINISH:
- /* Zone management operations require sequential zones. */
- if (!bdev_zone_is_seq(bio->bi_bdev, bio->bi_iter.bi_sector))
+ /*
+ * Zone management operations require sequential zones that are
+ * not offline nor read-only.
+ */
+ if (!bdev_zone_mgmt_allowed(bdev, bio->bi_iter.bi_sector))
goto end_io;
break;
case REQ_OP_ZONE_RESET_ALL:
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index a5afb842bf35e..ec510d66dfe2b 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -39,6 +39,103 @@ static const char *const zone_cond_name[] = {
};
#undef ZONE_COND_NAME
+/*
+ * Internal and compact representation of enum blk_zone_cond values for zone
+ * conditions. All these values fit into 4-bits, allowing using the high order
+ * bits as the zone type.
+ */
+enum blk_zstate {
+ BLK_ZSTATE_NOT_WP = 0x00,
+ BLK_ZSTATE_EMPTY = 0x01,
+ BLK_ZSTATE_IMP_OPEN = 0x02,
+ BLK_ZSTATE_EXP_OPEN = 0x03,
+ BLK_ZSTATE_CLOSED = 0x04,
+ BLK_ZSTATE_READONLY = 0x05,
+ BLK_ZSTATE_FULL = 0x06,
+ BLK_ZSTATE_OFFLINE = 0x07,
+ BLK_ZSTATE_ACTIVE = 0x08,
+
+ BLK_ZSTATE_COND_MASK = 0x0F,
+
+ /* Conventional zone. */
+ BLK_ZFLAG_CONV = 0x80,
+ BLK_ZSTATE_FLAGS_MASK = ~BLK_ZSTATE_COND_MASK,
+};
+
+/*
+ * Lookup table and helper to convert enum blk_zstate conditions into enum
+ * blk_zone_condition values.
+ */
+static const u8 blk_zstate2zcond[] = {
+ [BLK_ZSTATE_NOT_WP] = BLK_ZONE_COND_NOT_WP,
+ [BLK_ZSTATE_EMPTY] = BLK_ZONE_COND_EMPTY,
+ [BLK_ZSTATE_IMP_OPEN] = BLK_ZONE_COND_IMP_OPEN,
+ [BLK_ZSTATE_EXP_OPEN] = BLK_ZONE_COND_EXP_OPEN,
+ [BLK_ZSTATE_CLOSED] = BLK_ZONE_COND_CLOSED,
+ [BLK_ZSTATE_READONLY] = BLK_ZONE_COND_READONLY,
+ [BLK_ZSTATE_FULL] = BLK_ZONE_COND_FULL,
+ [BLK_ZSTATE_OFFLINE] = BLK_ZONE_COND_OFFLINE,
+ [BLK_ZSTATE_ACTIVE] = BLK_ZONE_COND_ACTIVE,
+};
+
+static inline enum blk_zone_cond blk_zstate_to_zone_cond(enum blk_zstate zs)
+{
+ u8 idx = zs & BLK_ZSTATE_COND_MASK;
+
+ if (WARN_ON_ONCE(idx >= ARRAY_SIZE(blk_zstate2zcond)))
+ return 0;
+
+ return blk_zstate2zcond[idx];
+}
+
+/*
+ * Lookup table and helper to convert an enum blk_zone_condition into an enum
+ * blk_zstate condition value. To keep the lookup table small, the
+ * BLK_ZONE_COND_ACTIVE condition is not added and handled separately.
+ */
+static const u8 blk_zcond2zstate[] = {
+ [BLK_ZONE_COND_NOT_WP] = BLK_ZSTATE_NOT_WP,
+ [BLK_ZONE_COND_EMPTY] = BLK_ZSTATE_EMPTY,
+ [BLK_ZONE_COND_IMP_OPEN] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_EXP_OPEN] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_CLOSED] = BLK_ZSTATE_ACTIVE,
+ [BLK_ZONE_COND_READONLY] = BLK_ZSTATE_READONLY,
+ [BLK_ZONE_COND_FULL] = BLK_ZSTATE_FULL,
+ [BLK_ZONE_COND_OFFLINE] = BLK_ZSTATE_OFFLINE,
+};
+
+static inline enum blk_zstate blk_zone_cond_to_zstate(enum blk_zone_cond cond)
+{
+ if (cond == BLK_ZONE_COND_ACTIVE)
+ return BLK_ZSTATE_ACTIVE;
+
+ if (WARN_ON_ONCE(cond >= ARRAY_SIZE(blk_zcond2zstate)))
+ return 0;
+
+ return blk_zcond2zstate[cond];
+}
+
+/*
+ * Combine an enum blk_zone_condition and zone flags into a zones_state array
+ * entry.
+ */
+static inline void blk_zstate_set(u8 *zones_state, unsigned int nr_zones,
+ unsigned int zno, enum blk_zone_cond cond, u8 flags)
+{
+ if (zones_state && zno < nr_zones)
+ zones_state[zno] = flags | blk_zone_cond_to_zstate(cond);
+}
+
+static inline u8 blk_zstate_flags(enum blk_zstate zs)
+{
+ return zs & BLK_ZSTATE_FLAGS_MASK;
+}
+
+static inline bool blk_zstate_is_conv(enum blk_zstate zs)
+{
+ return blk_zstate_flags(zs) & BLK_ZFLAG_CONV;
+}
+
/*
* Per-zone write plug.
* @node: hlist_node structure for managing the plug using a hash table.
@@ -135,55 +232,64 @@ const char *blk_zone_cond_str(enum blk_zone_cond zone_cond)
}
EXPORT_SYMBOL_GPL(blk_zone_cond_str);
-static void blk_zone_set_cond(u8 *zones_cond, unsigned int zno,
- enum blk_zone_cond cond)
+static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
+ enum blk_zone_cond cond)
{
- if (!zones_cond)
- return;
+ unsigned int zno = disk_zone_no(disk, sector);
+ u8 *zones_state;
- switch (cond) {
- case BLK_ZONE_COND_IMP_OPEN:
- case BLK_ZONE_COND_EXP_OPEN:
- case BLK_ZONE_COND_CLOSED:
- zones_cond[zno] = BLK_ZONE_COND_ACTIVE;
- return;
- case BLK_ZONE_COND_NOT_WP:
- case BLK_ZONE_COND_EMPTY:
- case BLK_ZONE_COND_FULL:
- case BLK_ZONE_COND_OFFLINE:
- case BLK_ZONE_COND_READONLY:
- default:
- zones_cond[zno] = cond;
- return;
- }
+ rcu_read_lock();
+ zones_state = rcu_dereference(disk->zones_state);
+ if (likely(zones_state && zno < disk->nr_zones))
+ blk_zstate_set(zones_state, disk->nr_zones, zno, cond,
+ blk_zstate_flags(zones_state[zno]));
+ rcu_read_unlock();
}
-static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
- enum blk_zone_cond cond)
+static inline u8 disk_zone_get_state(struct gendisk *disk, sector_t sector)
{
- u8 *zones_cond;
+ unsigned int zno = disk_zone_no(disk, sector);
+ u8 *zones_state, zs;
rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (zones_cond) {
- unsigned int zno = disk_zone_no(disk, sector);
-
- /*
- * The condition of a conventional, readonly and offline zones
- * never changes, so do nothing if the target zone is in one of
- * these conditions.
- */
- switch (zones_cond[zno]) {
- case BLK_ZONE_COND_NOT_WP:
- case BLK_ZONE_COND_READONLY:
- case BLK_ZONE_COND_OFFLINE:
- break;
- default:
- blk_zone_set_cond(zones_cond, zno, cond);
- break;
- }
- }
+ zones_state = rcu_dereference(disk->zones_state);
+ if (likely(zones_state && zno < disk->nr_zones))
+ zs = zones_state[zno];
+ else
+ zs = BLK_ZFLAG_CONV;
rcu_read_unlock();
+
+ return zs;
+}
+
+static enum blk_zone_cond disk_zone_get_cond(struct gendisk *disk,
+ sector_t sector)
+{
+ u8 zs = disk_zone_get_state(disk, sector);
+
+ return blk_zstate_to_zone_cond(zs);
+}
+
+static inline bool
+disk_zone_cond_is_offline_or_readonly(enum blk_zone_cond cond)
+{
+ return cond == BLK_ZONE_COND_READONLY ||
+ cond == BLK_ZONE_COND_OFFLINE;
+}
+
+static inline bool disk_zone_is_offline_or_readonly(struct gendisk *disk,
+ sector_t sector)
+{
+ enum blk_zone_cond cond = disk_zone_get_cond(disk, sector);
+
+ return disk_zone_cond_is_offline_or_readonly(cond);
+}
+
+static bool disk_zone_is_seq(struct gendisk *disk, sector_t sector)
+{
+ u8 zs = disk_zone_get_state(disk, sector);
+
+ return !blk_zstate_is_conv(zs);
}
/**
@@ -195,23 +301,37 @@ static void disk_zone_set_cond(struct gendisk *disk, sector_t sector,
*/
bool bdev_zone_is_seq(struct block_device *bdev, sector_t sector)
{
- struct gendisk *disk = bdev->bd_disk;
- unsigned int zno = disk_zone_no(disk, sector);
- bool is_seq = false;
- u8 *zones_cond;
+ if (!bdev_is_zoned(bdev))
+ return false;
+
+ return disk_zone_is_seq(bdev->bd_disk, sector);
+}
+EXPORT_SYMBOL_GPL(bdev_zone_is_seq);
+
+/**
+ * bdev_zone_mgmt_allowed - check if management operations are allowed on a zone
+ * @bdev: block device to check
+ * @sector: sector number
+ *
+ * Check if the zone containing @sector on @bdev can be a target for a zone
+ * management operation, that is, if the zone is a sequential write required
+ * zone that is not offline nor read-only.
+ */
+bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector)
+{
+ enum blk_zone_cond cond;
+ u8 zs;
if (!bdev_is_zoned(bdev))
return false;
- rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (zones_cond && zno < disk->nr_zones)
- is_seq = zones_cond[zno] != BLK_ZONE_COND_NOT_WP;
- rcu_read_unlock();
+ zs = disk_zone_get_state(bdev->bd_disk, sector);
+ if (blk_zstate_is_conv(zs))
+ return false;
- return is_seq;
+ cond = blk_zstate_to_zone_cond(zs);
+ return !disk_zone_cond_is_offline_or_readonly(cond);
}
-EXPORT_SYMBOL_GPL(bdev_zone_is_seq);
/*
* Zone report arguments for block device drivers report_zones operation.
@@ -500,12 +620,17 @@ static bool disk_zone_wplug_is_full(struct gendisk *disk,
return zwplug->wp_offset >= disk->last_zone_capacity;
}
+static bool disk_zone_wplug_is_offline_or_readonly(struct blk_zone_wplug *zwplug)
+{
+ return disk_zone_cond_is_offline_or_readonly(zwplug->cond);
+}
+
static bool disk_insert_zone_wplug(struct gendisk *disk,
struct blk_zone_wplug *zwplug)
{
struct blk_zone_wplug *zwplg;
unsigned long flags;
- u8 *zones_cond;
+ u8 *zones_state;
unsigned int idx =
hash_32(zwplug->zone_no, disk->zone_wplugs_hash_bits);
@@ -524,15 +649,16 @@ static bool disk_insert_zone_wplug(struct gendisk *disk,
}
/*
- * Set the zone condition: if we do not yet have a zones_cond array
+ * Set the zone condition: if we do not yet have a zones_state array
* attached to the disk, then this is a zone write plug insert from the
* first call to blk_revalidate_disk_zones(), in which case the zone is
* necessarilly in the active condition.
*/
- zones_cond = rcu_dereference_check(disk->zones_cond,
+ zones_state = rcu_dereference_check(disk->zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock));
- if (zones_cond)
- zwplug->cond = zones_cond[zwplug->zone_no];
+ if (zones_state)
+ zwplug->cond =
+ blk_zstate_to_zone_cond(zones_state[zwplug->zone_no]);
else
zwplug->cond = BLK_ZONE_COND_ACTIVE;
@@ -574,6 +700,26 @@ static inline struct blk_zone_wplug *disk_get_zone_wplug(struct gendisk *disk,
return disk_get_hashed_zone_wplug(disk, sector);
}
+static void disk_for_all_zone_wplugs(struct gendisk *disk,
+ void (*actor)(struct blk_zone_wplug *,
+ void *),
+ void *data)
+{
+ struct blk_zone_wplug *zwplug;
+ unsigned int i;
+
+ if (!disk->zone_wplugs_hash)
+ return;
+
+ rcu_read_lock();
+ for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
+ hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i],
+ node)
+ actor(zwplug, data);
+ }
+ rcu_read_unlock();
+}
+
static void disk_free_zone_wplug_rcu(struct rcu_head *rcu_head)
{
struct blk_zone_wplug *zwplug =
@@ -592,9 +738,9 @@ static void disk_free_zone_wplug(struct blk_zone_wplug *zwplug)
WARN_ON_ONCE(!bio_list_empty(&zwplug->bio_list));
spin_lock_irqsave(&disk->zone_wplugs_hash_lock, flags);
- blk_zone_set_cond(rcu_dereference_check(disk->zones_cond,
+ blk_zstate_set(rcu_dereference_check(disk->zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock)),
- zwplug->zone_no, zwplug->cond);
+ disk->nr_zones, zwplug->zone_no, zwplug->cond, 0);
hlist_del_init_rcu(&zwplug->node);
atomic_dec(&disk->nr_zone_wplugs);
spin_unlock_irqrestore(&disk->zone_wplugs_hash_lock, flags);
@@ -608,6 +754,53 @@ static inline void disk_put_zone_wplug(struct blk_zone_wplug *zwplug)
disk_free_zone_wplug(zwplug);
}
+static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,
+ struct bio *bio)
+{
+ struct request_queue *q = zwplug->disk->queue;
+
+ bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
+ bio_io_error(bio);
+ disk_put_zone_wplug(zwplug);
+ /* Drop the reference taken by disk_zone_wplug_add_bio(). */
+ blk_queue_exit(q);
+}
+
+/*
+ * Abort (fail) all plugged BIOs of a zone write plug.
+ */
+static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)
+{
+ struct gendisk *disk = zwplug->disk;
+ struct bio *bio;
+
+ lockdep_assert_held(&zwplug->lock);
+
+ if (bio_list_empty(&zwplug->bio_list))
+ return;
+
+ pr_warn_ratelimited("%s: zone %u: Aborting plugged BIOs\n",
+ zwplug->disk->disk_name, zwplug->zone_no);
+ while ((bio = bio_list_pop(&zwplug->bio_list)))
+ blk_zone_wplug_bio_io_error(zwplug, bio);
+
+ zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
+
+ /*
+ * If we are using the per disk zone write plugs worker thread, remove
+ * the zone write plug from the work list and drop the reference we
+ * took when the zone write plug was added to that list.
+ */
+ if (blk_queue_zoned_qd1_writes(disk->queue)) {
+ spin_lock(&disk->zone_wplugs_list_lock);
+ if (!list_empty(&zwplug->entry)) {
+ list_del_init(&zwplug->entry);
+ disk_put_zone_wplug(zwplug);
+ }
+ spin_unlock(&disk->zone_wplugs_list_lock);
+ }
+}
+
/*
* Flag the zone write plug as dead and drop the initial reference we got when
* the zone write plug was added to the hash table. The zone write plug will be
@@ -625,6 +818,12 @@ static void disk_mark_zone_wplug_dead(struct blk_zone_wplug *zwplug)
static inline bool disk_check_zone_wplug_dead(struct blk_zone_wplug *zwplug)
{
+ if (disk_zone_wplug_is_offline_or_readonly(zwplug)) {
+ disk_zone_wplug_abort(zwplug);
+ disk_mark_zone_wplug_dead(zwplug);
+ return true;
+ }
+
if (!(zwplug->flags & BLK_ZONE_WPLUG_DEAD))
return false;
@@ -708,53 +907,6 @@ static struct blk_zone_wplug *disk_get_or_alloc_zone_wplug(struct gendisk *disk,
return zwplug;
}
-static inline void blk_zone_wplug_bio_io_error(struct blk_zone_wplug *zwplug,
- struct bio *bio)
-{
- struct request_queue *q = zwplug->disk->queue;
-
- bio_clear_flag(bio, BIO_ZONE_WRITE_PLUGGING);
- bio_io_error(bio);
- disk_put_zone_wplug(zwplug);
- /* Drop the reference taken by disk_zone_wplug_add_bio(). */
- blk_queue_exit(q);
-}
-
-/*
- * Abort (fail) all plugged BIOs of a zone write plug.
- */
-static void disk_zone_wplug_abort(struct blk_zone_wplug *zwplug)
-{
- struct gendisk *disk = zwplug->disk;
- struct bio *bio;
-
- lockdep_assert_held(&zwplug->lock);
-
- if (bio_list_empty(&zwplug->bio_list))
- return;
-
- pr_warn_ratelimited("%s: zone %u: Aborting plugged BIOs\n",
- zwplug->disk->disk_name, zwplug->zone_no);
- while ((bio = bio_list_pop(&zwplug->bio_list)))
- blk_zone_wplug_bio_io_error(zwplug, bio);
-
- zwplug->flags &= ~BLK_ZONE_WPLUG_PLUGGED;
-
- /*
- * If we are using the per disk zone write plugs worker thread, remove
- * the zone write plug from the work list and drop the reference we
- * took when the zone write plug was added to that list.
- */
- if (blk_queue_zoned_qd1_writes(disk->queue)) {
- spin_lock(&disk->zone_wplugs_list_lock);
- if (!list_empty(&zwplug->entry)) {
- list_del_init(&zwplug->entry);
- disk_put_zone_wplug(zwplug);
- }
- spin_unlock(&disk->zone_wplugs_list_lock);
- }
-}
-
/*
* Update a zone write plug condition based on the write pointer offset.
*/
@@ -785,8 +937,10 @@ static void disk_zone_wplug_set_wp_offset(struct gendisk *disk,
/* Update the zone write pointer and abort all plugged BIOs. */
zwplug->flags &= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE;
- zwplug->wp_offset = wp_offset;
- disk_zone_wplug_update_cond(disk, zwplug);
+ if (!disk_zone_wplug_is_offline_or_readonly(zwplug)) {
+ zwplug->wp_offset = wp_offset;
+ disk_zone_wplug_update_cond(disk, zwplug);
+ }
disk_zone_wplug_abort(zwplug);
if (!zwplug->wp_offset || disk_zone_wplug_is_full(disk, zwplug))
@@ -816,8 +970,8 @@ static unsigned int blk_zone_wp_offset(struct blk_zone *zone)
}
}
-static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,
- struct blk_zone *zone)
+static unsigned int disk_zone_wplug_sync_state(struct gendisk *disk,
+ struct blk_zone *zone)
{
struct blk_zone_wplug *zwplug;
unsigned int wp_offset = blk_zone_wp_offset(zone);
@@ -827,6 +981,12 @@ static unsigned int disk_zone_wplug_sync_wp_offset(struct gendisk *disk,
unsigned long flags;
spin_lock_irqsave(&zwplug->lock, flags);
+ if (disk_zone_cond_is_offline_or_readonly(zone->cond)) {
+ zwplug->flags &= ~BLK_ZONE_WPLUG_NEED_WP_UPDATE;
+ zwplug->cond = zone->cond;
+ zwplug->wp_offset = UINT_MAX;
+ disk_mark_zone_wplug_dead(zwplug);
+ }
if (zwplug->flags & BLK_ZONE_WPLUG_NEED_WP_UPDATE)
disk_zone_wplug_set_wp_offset(disk, zwplug, wp_offset);
spin_unlock_irqrestore(&zwplug->lock, flags);
@@ -871,7 +1031,7 @@ int disk_report_zone(struct gendisk *disk, struct blk_zone *zone,
}
if (disk->zone_wplugs_hash)
- disk_zone_wplug_sync_wp_offset(disk, zone);
+ disk_zone_wplug_sync_state(disk, zone);
if (args && args->cb)
return args->cb(zone, idx, args->data);
@@ -938,29 +1098,36 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
{
struct gendisk *disk = bdev->bd_disk;
sector_t zone_sectors = bdev_zone_sectors(bdev);
+ unsigned int zno = disk_zone_no(disk, sector);
struct blk_zone_wplug *zwplug;
unsigned long flags;
- u8 *zones_cond;
+ u8 *zones_state, zs;
if (!bdev_is_zoned(bdev))
return -EOPNOTSUPP;
- if (sector >= get_capacity(disk))
+ if (sector >= get_capacity(disk) || zno >= disk->nr_zones)
return -EINVAL;
memset(zone, 0, sizeof(*zone));
sector = bdev_zone_start(bdev, sector);
if (!blkdev_has_cached_report_zones(bdev))
- return blkdev_report_zone_fallback(bdev, sector, zone);
+ goto fallback;
rcu_read_lock();
- zones_cond = rcu_dereference(disk->zones_cond);
- if (!disk->zone_wplugs_hash || !zones_cond) {
+ zones_state = rcu_dereference(disk->zones_state);
+ if (!disk->zone_wplugs_hash || !zones_state) {
rcu_read_unlock();
- return blkdev_report_zone_fallback(bdev, sector, zone);
+ goto fallback;
}
- zone->cond = zones_cond[disk_zone_no(disk, sector)];
+
+ zs = zones_state[zno];
+ zone->cond = blk_zstate_to_zone_cond(zs);
+ if (blk_zstate_is_conv(zs))
+ zone->type = BLK_ZONE_TYPE_CONVENTIONAL;
+ else
+ zone->type = BLK_ZONE_TYPE_SEQWRITE_REQ;
rcu_read_unlock();
zone->start = sector;
@@ -970,8 +1137,7 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
* If this is a conventional zone, we do not have a zone write plug and
* can report the zone immediately.
*/
- if (zone->cond == BLK_ZONE_COND_NOT_WP) {
- zone->type = BLK_ZONE_TYPE_CONVENTIONAL;
+ if (zone->type == BLK_ZONE_TYPE_CONVENTIONAL) {
zone->capacity = zone_sectors;
zone->wp = ULLONG_MAX;
return 0;
@@ -982,14 +1148,12 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
* offline, only set the zone write pointer to an invalid value and
* report the zone.
*/
- zone->type = BLK_ZONE_TYPE_SEQWRITE_REQ;
if (disk_zone_is_last(disk, zone))
zone->capacity = disk->last_zone_capacity;
else
zone->capacity = disk->zone_capacity;
- if (zone->cond == BLK_ZONE_COND_READONLY ||
- zone->cond == BLK_ZONE_COND_OFFLINE) {
+ if (disk_zone_cond_is_offline_or_readonly(zone->cond)) {
zone->wp = ULLONG_MAX;
return 0;
}
@@ -1022,6 +1186,9 @@ int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,
disk_put_zone_wplug(zwplug);
return 0;
+
+fallback:
+ return blkdev_report_zone_fallback(bdev, sector, zone);
}
EXPORT_SYMBOL_GPL(blkdev_get_zone_info);
@@ -1108,34 +1275,32 @@ static void blk_zone_reset_bio_endio(struct bio *bio)
}
}
+static void disk_zone_wplug_reset_wp(struct blk_zone_wplug *zwplug, void *data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&zwplug->lock, flags);
+ disk_zone_wplug_set_wp_offset(zwplug->disk, zwplug, 0);
+ spin_unlock_irqrestore(&zwplug->lock, flags);
+}
+
static void blk_zone_reset_all_bio_endio(struct bio *bio)
{
struct gendisk *disk = bio->bi_bdev->bd_disk;
- sector_t capacity = get_capacity(disk);
- struct blk_zone_wplug *zwplug;
- unsigned long flags;
sector_t sector;
- unsigned int i;
- if (atomic_read(&disk->nr_zone_wplugs)) {
- /* Update the condition of all zone write plugs. */
- rcu_read_lock();
- for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++) {
- hlist_for_each_entry_rcu(zwplug,
- &disk->zone_wplugs_hash[i],
- node) {
- spin_lock_irqsave(&zwplug->lock, flags);
- disk_zone_wplug_set_wp_offset(disk, zwplug, 0);
- spin_unlock_irqrestore(&zwplug->lock, flags);
- }
- }
- rcu_read_unlock();
- }
+ /* Update the condition of all zone write plugs. */
+ if (atomic_read(&disk->nr_zone_wplugs))
+ disk_for_all_zone_wplugs(disk, disk_zone_wplug_reset_wp, NULL);
/* Update the cached zone conditions. */
- for (sector = 0; sector < capacity;
- sector += bdev_zone_sectors(bio->bi_bdev))
+ for (sector = 0; sector < get_capacity(disk);
+ sector += bdev_zone_sectors(bio->bi_bdev)) {
+ if (!disk_zone_is_seq(disk, sector) ||
+ disk_zone_is_offline_or_readonly(disk, sector))
+ continue;
disk_zone_set_cond(disk, sector, BLK_ZONE_COND_EMPTY);
+ }
clear_bit(GD_ZONE_APPEND_USED, &disk->state);
}
@@ -1381,11 +1546,12 @@ static bool blk_zone_wplug_prepare_bio(struct blk_zone_wplug *zwplug,
return false;
/*
- * Check that the user is not attempting to write to a full zone.
- * We know such BIO will fail, and that would potentially overflow our
- * write pointer offset beyond the end of the zone.
+ * Check that the user is not attempting to write to a full, read-only
+ * or offline zone. We know such BIOs will fail, so there is no point
+ * in issuing them.
*/
- if (disk_zone_wplug_is_full(disk, zwplug))
+ if (disk_zone_wplug_is_full(disk, zwplug) ||
+ disk_zone_wplug_is_offline_or_readonly(zwplug))
return false;
if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
@@ -1442,7 +1608,7 @@ static bool blk_zone_wplug_handle_write(struct bio *bio, unsigned int nr_segs)
}
/* Conventional zones do not need write plugging. */
- if (!bdev_zone_is_seq(bio->bi_bdev, sector)) {
+ if (!disk_zone_is_seq(disk, sector)) {
/* Zone append to conventional zones is not allowed. */
if (bio_op(bio) == REQ_OP_ZONE_APPEND) {
bio_io_error(bio);
@@ -1854,12 +2020,24 @@ static int disk_zone_wplugs_worker(void *data)
void disk_init_zone_resources(struct gendisk *disk)
{
+ mutex_init(&disk->zone_revalidate_mutex);
+ atomic_set(&disk->nr_zone_wplugs, 0);
spin_lock_init(&disk->zone_wplugs_hash_lock);
spin_lock_init(&disk->zone_wplugs_list_lock);
INIT_LIST_HEAD(&disk->zone_wplugs_list);
init_completion(&disk->zone_wplugs_worker_bio_done);
}
+static unsigned int disk_get_nr_zones(struct gendisk *disk, sector_t capacity)
+{
+ struct queue_limits *lim = &disk->queue->limits;
+
+ if (!capacity || !lim->chunk_sectors)
+ return 0;
+
+ return DIV_ROUND_UP_ULL(capacity, lim->chunk_sectors);
+}
+
/*
* For the size of a disk zone write plug hash table, use the size of the
* zone write plug mempool, which is the maximum of the disk open zones and
@@ -1869,13 +2047,24 @@ void disk_init_zone_resources(struct gendisk *disk)
#define BLK_ZONE_WPLUG_MAX_HASH_BITS 9
#define BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE 128
-static int disk_alloc_zone_resources(struct gendisk *disk,
- unsigned int pool_size)
+static int disk_alloc_zone_resources(struct gendisk *disk, sector_t capacity)
{
- unsigned int i;
+ struct queue_limits *lim = &disk->queue->limits;
+ unsigned int nr_zones, pool_size, i;
int ret = -ENOMEM;
- atomic_set(&disk->nr_zone_wplugs, 0);
+ nr_zones = disk_get_nr_zones(disk, capacity);
+ if (!nr_zones)
+ return -ENODEV;
+
+ /*
+ * If the device has no limit on the maximum number of open and active
+ * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.
+ */
+ pool_size = max(lim->max_open_zones, lim->max_active_zones);
+ if (!pool_size)
+ pool_size = min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, nr_zones);
+
disk->zone_wplugs_hash_bits =
min(ilog2(pool_size) + 1, BLK_ZONE_WPLUG_MAX_HASH_BITS);
@@ -1893,21 +2082,6 @@ static int disk_alloc_zone_resources(struct gendisk *disk,
if (!disk->zone_wplugs_pool)
goto free_hash;
- /*
- * We may already have a zone write plug workqueue as this function may
- * be called after disk_free_zone_resources(), which does not destroy
- * the workqueue (the zone write plugs workqueue is destroyed at
- * disk_release() time).
- */
- if (!disk->zone_wplugs_wq) {
- disk->zone_wplugs_wq =
- alloc_workqueue("%s_zwplugs",
- WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,
- pool_size, disk->disk_name);
- if (!disk->zone_wplugs_wq)
- goto destroy_pool;
- }
-
disk->zone_wplugs_worker =
kthread_create(disk_zone_wplugs_worker, disk,
"%s_zwplugs_worker", disk->disk_name);
@@ -1918,8 +2092,18 @@ static int disk_alloc_zone_resources(struct gendisk *disk,
}
wake_up_process(disk->zone_wplugs_worker);
+ disk->zone_wplugs_wq =
+ alloc_workqueue("%s_zwplugs",
+ WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,
+ pool_size, disk->disk_name);
+ if (!disk->zone_wplugs_wq)
+ goto stop_worker;
+
return 0;
+stop_worker:
+ kthread_stop(disk->zone_wplugs_worker);
+ disk->zone_wplugs_worker = NULL;
destroy_pool:
mempool_destroy(disk->zone_wplugs_pool);
disk->zone_wplugs_pool = NULL;
@@ -1963,19 +2147,19 @@ static void disk_destroy_zone_wplugs_hash_table(struct gendisk *disk)
disk->zone_wplugs_pool = NULL;
}
-static void disk_set_zones_cond_array(struct gendisk *disk, u8 *zones_cond)
+static void disk_set_zones_state_array(struct gendisk *disk, u8 *zones_state)
{
unsigned long flags;
spin_lock_irqsave(&disk->zone_wplugs_hash_lock, flags);
- zones_cond = rcu_replace_pointer(disk->zones_cond, zones_cond,
+ zones_state = rcu_replace_pointer(disk->zones_state, zones_state,
lockdep_is_held(&disk->zone_wplugs_hash_lock));
spin_unlock_irqrestore(&disk->zone_wplugs_hash_lock, flags);
- kfree_rcu_mightsleep(zones_cond);
+ kfree_rcu_mightsleep(zones_state);
}
-static void disk_free_zone_resources(struct gendisk *disk)
+void disk_release_zone_resources(struct gendisk *disk)
{
if (disk->zone_wplugs_worker) {
kthread_stop(disk->zone_wplugs_worker);
@@ -1983,30 +2167,24 @@ static void disk_free_zone_resources(struct gendisk *disk)
}
WARN_ON_ONCE(!list_empty(&disk->zone_wplugs_list));
- if (disk->zone_wplugs_wq)
- drain_workqueue(disk->zone_wplugs_wq);
+ if (disk->zone_wplugs_wq) {
+ destroy_workqueue(disk->zone_wplugs_wq);
+ disk->zone_wplugs_wq = NULL;
+ }
disk_destroy_zone_wplugs_hash_table(disk);
- disk_set_zones_cond_array(disk, NULL);
+ disk_set_zones_state_array(disk, NULL);
disk->zone_capacity = 0;
disk->last_zone_capacity = 0;
disk->nr_zones = 0;
-}
-
-void disk_release_zone_resources(struct gendisk *disk)
-{
- if (disk->zone_wplugs_wq) {
- destroy_workqueue(disk->zone_wplugs_wq);
- disk->zone_wplugs_wq = NULL;
- }
-
- disk_free_zone_resources(disk);
+ mutex_destroy(&disk->zone_revalidate_mutex);
}
struct blk_revalidate_zone_args {
struct gendisk *disk;
- u8 *zones_cond;
+ sector_t capacity;
+ u8 *zones_state;
unsigned int nr_zones;
unsigned int nr_conv_zones;
unsigned int zone_capacity;
@@ -2014,73 +2192,74 @@ struct blk_revalidate_zone_args {
sector_t sector;
};
-static int disk_revalidate_zone_resources(struct gendisk *disk,
- struct blk_revalidate_zone_args *args)
+static int disk_init_revalidate_args(struct gendisk *disk,
+ struct blk_revalidate_zone_args *args)
{
- struct queue_limits *lim = &disk->queue->limits;
- unsigned int pool_size;
- int ret = 0;
-
args->disk = disk;
- args->nr_zones =
- DIV_ROUND_UP_ULL(get_capacity(disk), lim->chunk_sectors);
+ args->nr_zones = disk_get_nr_zones(disk, args->capacity);
/* Cached zone conditions: 1 byte per zone */
- args->zones_cond = kzalloc(args->nr_zones, GFP_NOIO);
- if (!args->zones_cond)
+ args->zones_state = kzalloc(args->nr_zones, GFP_NOIO);
+ if (!args->zones_state)
return -ENOMEM;
- if (!disk_need_zone_resources(disk))
- return 0;
-
- /*
- * If the device has no limit on the maximum number of open and active
- * zones, use BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE.
- */
- pool_size = max(lim->max_open_zones, lim->max_active_zones);
- if (!pool_size)
- pool_size =
- min(BLK_ZONE_WPLUG_DEFAULT_POOL_SIZE, args->nr_zones);
-
- if (!disk->zone_wplugs_hash) {
- ret = disk_alloc_zone_resources(disk, pool_size);
- if (ret)
- kfree(args->zones_cond);
- }
-
- return ret;
+ return 0;
}
/*
- * Update the disk zone resources information and device queue limits.
- * The disk queue is frozen when this is executed.
+ * Revalidate and update the disk zone resources information and device queue
+ * limits.
*/
-static int disk_update_zone_resources(struct gendisk *disk,
- struct blk_revalidate_zone_args *args)
+static int disk_revalidate_zone_resources(struct gendisk *disk,
+ struct blk_revalidate_zone_args *args)
{
struct request_queue *q = disk->queue;
unsigned int nr_seq_zones;
unsigned int pool_size, memflags;
struct queue_limits lim;
+ sector_t capacity;
int ret = 0;
lim = queue_limits_start_update(q);
memflags = blk_mq_freeze_queue(q);
- disk->nr_zones = args->nr_zones;
- if (args->nr_conv_zones >= disk->nr_zones) {
- queue_limits_cancel_update(q);
+ /*
+ * Using the re-evaluated disk capacity, make sure that the entire disk
+ * has been checked.
+ */
+ capacity = get_capacity(disk);
+ if (args->capacity != capacity) {
+ pr_warn("%s: Capacity has changed (%llu -> %llu)\n",
+ disk->disk_name, args->capacity, capacity);
+ /* Force a retry if we have a valid (non-zero) capacity. */
+ if (capacity)
+ ret = -EAGAIN;
+ else
+ ret = -ENODEV;
+ goto unfreeze;
+ }
+
+ /* Make sure that all zones have been checked. */
+ if (args->sector != capacity) {
+ pr_warn("%s: last zone and capacity mismatch (%llu != %llu)\n",
+ disk->disk_name, args->sector, capacity);
+ ret = -ENODEV;
+ goto unfreeze;
+ }
+
+ if (args->nr_conv_zones >= args->nr_zones) {
pr_warn("%s: Invalid number of conventional zones %u / %u\n",
- disk->disk_name, args->nr_conv_zones, disk->nr_zones);
+ disk->disk_name, args->nr_conv_zones, args->nr_zones);
ret = -ENODEV;
goto unfreeze;
}
+ disk->nr_zones = args->nr_zones;
disk->zone_capacity = args->zone_capacity;
disk->last_zone_capacity = args->last_zone_capacity;
- disk_set_zones_cond_array(disk, args->zones_cond);
- args->zones_cond = NULL;
+ disk_set_zones_state_array(disk, args->zones_state);
+ args->zones_state = NULL;
/*
* Some devices can advertise zone resource limits that are larger than
@@ -2095,7 +2274,7 @@ static int disk_update_zone_resources(struct gendisk *disk,
lim.max_active_zones = 0;
if (!disk->zone_wplugs_pool)
- goto commit;
+ goto unfreeze;
/*
* If the device has no limit on the maximum number of open and active
@@ -2117,51 +2296,65 @@ static int disk_update_zone_resources(struct gendisk *disk,
lim.max_open_zones = 0;
}
-commit:
- ret = queue_limits_commit_update(q, &lim);
-
unfreeze:
+ if (ret)
+ queue_limits_cancel_update(q);
+ else
+ ret = queue_limits_commit_update(q, &lim);
+
blk_mq_unfreeze_queue(q, memflags);
return ret;
}
-static int blk_revalidate_zone_cond(struct blk_zone *zone, unsigned int idx,
+static void disk_drop_zone_wplug(struct blk_zone_wplug *zwplug, void *data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&zwplug->lock, flags);
+ disk_zone_wplug_abort(zwplug);
+ disk_mark_zone_wplug_dead(zwplug);
+ spin_unlock_irqrestore(&zwplug->lock, flags);
+}
+
+static int disk_revalidate_capacity(struct gendisk *disk,
struct blk_revalidate_zone_args *args)
{
- enum blk_zone_cond cond = zone->cond;
+ struct queue_limits *lim = &disk->queue->limits;
+ sector_t zone_sectors = lim->chunk_sectors;
+ unsigned int nr_zones;
+ int ret = -ENODEV;
- /* Check that the zone condition is consistent with the zone type. */
- switch (cond) {
- case BLK_ZONE_COND_NOT_WP:
- if (zone->type != BLK_ZONE_TYPE_CONVENTIONAL)
- goto invalid_condition;
- break;
- case BLK_ZONE_COND_IMP_OPEN:
- case BLK_ZONE_COND_EXP_OPEN:
- case BLK_ZONE_COND_CLOSED:
- case BLK_ZONE_COND_EMPTY:
- case BLK_ZONE_COND_FULL:
- case BLK_ZONE_COND_OFFLINE:
- case BLK_ZONE_COND_READONLY:
- if (zone->type != BLK_ZONE_TYPE_SEQWRITE_REQ)
- goto invalid_condition;
- break;
- default:
- pr_warn("%s: Invalid zone condition 0x%X\n",
- args->disk->disk_name, cond);
- return -ENODEV;
+ /* Checks that the device driver indicated a valid zone size. */
+ if (!zone_sectors || !is_power_of_2(zone_sectors)) {
+ pr_warn("%s: Invalid non power of two zone size (%llu)\n",
+ disk->disk_name, zone_sectors);
+ goto drop_all_zwplugs;
}
- blk_zone_set_cond(args->zones_cond, idx, cond);
+ args->capacity = get_capacity(disk);
+ nr_zones = disk_get_nr_zones(disk, args->capacity);
+ if (!args->capacity || !nr_zones)
+ goto drop_all_zwplugs;
+
+ /*
+ * Check if the capacity has changed. If it did, assume that the device
+ * was reformatted and that all sequential zones are now empty. So drop
+ * all zone write plug.
+ */
+ if (disk->nr_zones && disk->nr_zones != nr_zones) {
+ pr_warn("%s: Number of zones changed (%u -> %u)\n",
+ disk->disk_name, disk->nr_zones, nr_zones);
+ ret = 0;
+ goto drop_all_zwplugs;
+ }
return 0;
-invalid_condition:
- pr_warn("%s: Invalid zone condition 0x%x for type 0x%x\n",
- args->disk->disk_name, cond, zone->type);
+drop_all_zwplugs:
+ disk_for_all_zone_wplugs(disk, disk_drop_zone_wplug, NULL);
- return -ENODEV;
+ return ret;
}
static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,
@@ -2169,12 +2362,27 @@ static int blk_revalidate_conv_zone(struct blk_zone *zone, unsigned int idx,
{
struct gendisk *disk = args->disk;
+ /* Check the zone condition. */
+ switch (zone->cond) {
+ case BLK_ZONE_COND_NOT_WP:
+ case BLK_ZONE_COND_OFFLINE:
+ case BLK_ZONE_COND_READONLY:
+ break;
+ default:
+ pr_warn("%s: Invalid conv. zone condition 0x%X at sector %llu\n",
+ disk->disk_name, zone->cond, zone->start);
+ return -ENODEV;
+ }
+
if (zone->capacity != zone->len) {
pr_warn("%s: Invalid conventional zone capacity\n",
disk->disk_name);
return -ENODEV;
}
+ blk_zstate_set(args->zones_state, args->nr_zones, idx,
+ zone->cond, BLK_ZFLAG_CONV);
+
if (disk_zone_is_last(disk, zone))
args->last_zone_capacity = zone->capacity;
@@ -2190,6 +2398,24 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,
struct blk_zone_wplug *zwplug;
unsigned int wp_offset;
+ /* Check the zone condition. */
+ switch (zone->cond) {
+ case BLK_ZONE_COND_IMP_OPEN:
+ case BLK_ZONE_COND_EXP_OPEN:
+ case BLK_ZONE_COND_CLOSED:
+ case BLK_ZONE_COND_EMPTY:
+ case BLK_ZONE_COND_FULL:
+ case BLK_ZONE_COND_OFFLINE:
+ case BLK_ZONE_COND_READONLY:
+ break;
+ default:
+ pr_warn("%s: Invalid seq. zone condition 0x%X at sector %llu\n",
+ disk->disk_name, zone->cond, zone->start);
+ return -ENODEV;
+ }
+
+ blk_zstate_set(args->zones_state, args->nr_zones, idx, zone->cond, 0);
+
/*
* Remember the capacity of the first sequential zone and check
* if it is constant for all zones, ignoring the last zone as it can be
@@ -2214,7 +2440,7 @@ static int blk_revalidate_seq_zone(struct blk_zone *zone, unsigned int idx,
if (!disk->zone_wplugs_hash)
return 0;
- wp_offset = disk_zone_wplug_sync_wp_offset(disk, zone);
+ wp_offset = disk_zone_wplug_sync_state(disk, zone);
if (!wp_offset || wp_offset >= zone->capacity)
return 0;
@@ -2244,7 +2470,7 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return -ENODEV;
}
- if (zone->start >= get_capacity(disk) || !zone->len) {
+ if (zone->start >= args->capacity || !zone->len) {
pr_warn("%s: Invalid zone start %llu, length %llu\n",
disk->disk_name, zone->start, zone->len);
return -ENODEV;
@@ -2272,11 +2498,6 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
return -ENODEV;
}
- /* Check zone condition */
- ret = blk_revalidate_zone_cond(zone, idx, args);
- if (ret)
- return ret;
-
/* Check zone type */
switch (zone->type) {
case BLK_ZONE_TYPE_CONVENTIONAL:
@@ -2313,42 +2534,48 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,
*/
int blk_revalidate_disk_zones(struct gendisk *disk)
{
- struct request_queue *q = disk->queue;
- sector_t zone_sectors = q->limits.chunk_sectors;
- sector_t capacity = get_capacity(disk);
struct blk_revalidate_zone_args args = { };
- unsigned int memflags, noio_flag;
struct blk_report_zones_args rep_args = {
.cb = blk_revalidate_zone_cb,
.data = &args,
};
- int ret = -ENOMEM;
+ unsigned int noio_flag;
+ int retries = 2;
+ int ret;
- if (WARN_ON_ONCE(!blk_queue_is_zoned(q)))
+ if (WARN_ON_ONCE(!blk_queue_is_zoned(disk->queue)))
return -EIO;
- if (!capacity)
- return -ENODEV;
-
/*
- * Checks that the device driver indicated a valid zone size and that
- * the max zone append limit is set.
+ * Serialize calls to this function so that we can safely look at and
+ * eventually change the disk zone information.
*/
- if (!zone_sectors || !is_power_of_2(zone_sectors)) {
- pr_warn("%s: Invalid non power of two zone size (%llu)\n",
- disk->disk_name, zone_sectors);
- return -ENODEV;
- }
+ mutex_lock(&disk->zone_revalidate_mutex);
+
+again:
+ ret = disk_revalidate_capacity(disk, &args);
+ if (ret)
+ goto unlock;
/*
- * Ensure that all memory allocations in this context are done as if
- * GFP_NOIO was specified.
+ * Allocate zone resources if they are needed and we have not done
+ * so yet, and initialize the revalidation arguments passed to report
+ * zones. Ensure that all memory allocations in this context are done as
+ * if GFP_NOIO was specified.
*/
noio_flag = memalloc_noio_save();
- ret = disk_revalidate_zone_resources(disk, &args);
+ if (disk_need_zone_resources(disk) && !disk->zone_wplugs_hash) {
+ ret = disk_alloc_zone_resources(disk, args.capacity);
+ if (ret) {
+ memalloc_noio_restore(noio_flag);
+ goto unlock;
+ }
+ }
+
+ ret = disk_init_revalidate_args(disk, &args);
if (ret) {
memalloc_noio_restore(noio_flag);
- return ret;
+ goto unlock;
}
ret = disk->fops->report_zones(disk, 0, UINT_MAX, &rep_args);
@@ -2358,33 +2585,32 @@ int blk_revalidate_disk_zones(struct gendisk *disk)
}
memalloc_noio_restore(noio_flag);
- if (ret <= 0)
- goto free_resources;
-
- /*
- * If zones where reported, make sure that the entire disk capacity
- * has been checked.
- */
- if (args.sector != capacity) {
- pr_warn("%s: Missing zones from sector %llu\n",
- disk->disk_name, args.sector);
- ret = -ENODEV;
- goto free_resources;
- }
+ if (ret < 0)
+ goto free_args;
- ret = disk_update_zone_resources(disk, &args);
+ ret = disk_revalidate_zone_resources(disk, &args);
if (ret)
- goto free_resources;
+ goto free_args;
+
+ mutex_unlock(&disk->zone_revalidate_mutex);
return 0;
-free_resources:
- pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
+free_args:
+ kfree(args.zones_state);
- kfree(args.zones_cond);
- memflags = blk_mq_freeze_queue(q);
- disk_free_zone_resources(disk);
- blk_mq_unfreeze_queue(q, memflags);
+ if (ret == -EAGAIN) {
+ if (retries) {
+ memset(&args, 0, sizeof(args));
+ retries--;
+ goto again;
+ }
+ ret = -ENODEV;
+ }
+
+ pr_warn("%s: failed to revalidate zones\n", disk->disk_name);
+unlock:
+ mutex_unlock(&disk->zone_revalidate_mutex);
return ret;
}
@@ -2435,8 +2661,9 @@ EXPORT_SYMBOL_GPL(blk_zone_issue_zeroout);
#ifdef CONFIG_BLK_DEBUG_FS
static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,
- struct seq_file *m)
+ void *data)
{
+ struct seq_file *m = data;
unsigned int zwp_wp_offset, zwp_flags;
unsigned int zwp_zone_no, zwp_ref;
unsigned int zwp_bio_list_size;
@@ -2461,19 +2688,8 @@ static void queue_zone_wplug_show(struct blk_zone_wplug *zwplug,
int queue_zone_wplugs_show(void *data, struct seq_file *m)
{
struct request_queue *q = data;
- struct gendisk *disk = q->disk;
- struct blk_zone_wplug *zwplug;
- unsigned int i;
- if (!disk->zone_wplugs_hash)
- return 0;
-
- rcu_read_lock();
- for (i = 0; i < disk_zone_wplugs_hash_size(disk); i++)
- hlist_for_each_entry_rcu(zwplug, &disk->zone_wplugs_hash[i],
- node)
- queue_zone_wplug_show(zwplug, m);
- rcu_read_unlock();
+ disk_for_all_zone_wplugs(q->disk, queue_zone_wplug_show, m);
return 0;
}
diff --git a/block/blk.h b/block/blk.h
index 50abfd9328861..2cc03aa54c532 100644
--- a/block/blk.h
+++ b/block/blk.h
@@ -577,6 +577,7 @@ int blkdev_report_zones_ioctl(struct block_device *bdev, unsigned int cmd,
unsigned long arg);
int blkdev_zone_mgmt_ioctl(struct block_device *bdev, blk_mode_t mode,
unsigned int cmd, unsigned long arg);
+bool bdev_zone_mgmt_allowed(struct block_device *bdev, sector_t sector);
#else /* CONFIG_BLK_DEV_ZONED */
static inline void disk_init_zone_resources(struct gendisk *disk)
{
@@ -619,6 +620,11 @@ static inline int blkdev_zone_mgmt_ioctl(struct block_device *bdev,
{
return -ENOTTY;
}
+static inline bool bdev_zone_mgmt_allowed(struct block_device *bdev,
+ sector_t sector)
+{
+ return false;
+}
#endif /* CONFIG_BLK_DEV_ZONED */
struct block_device *bdev_alloc(struct gendisk *disk, u8 partno);
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index 4f7905c3412b8..8252c896e3eac 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -191,14 +191,17 @@ struct gendisk {
#ifdef CONFIG_BLK_DEV_ZONED
/*
* Zoned block device information. Reads of this information must be
- * protected with blk_queue_enter() / blk_queue_exit(). Modifying this
- * information is only allowed while no requests are being processed.
- * See also blk_mq_freeze_queue() and blk_mq_unfreeze_queue().
+ * protected with blk_queue_enter() / blk_queue_exit() or by holding a
+ * lock on zone_revalidate_mutex. blk_revalidate_disk_zones() may modify
+ * this information while no requests are being processed (disk queue
+ * frozen with blk_mq_freeze_queue()) and while holding a lock on
+ * zone_revalidate_mutex.
*/
+ struct mutex zone_revalidate_mutex;
unsigned int nr_zones;
unsigned int zone_capacity;
unsigned int last_zone_capacity;
- u8 __rcu *zones_cond;
+ u8 __rcu *zones_state;
unsigned int zone_wplugs_hash_bits;
atomic_t nr_zone_wplugs;
spinlock_t zone_wplugs_hash_lock;
|
| 15/3 | 2026/09/08 09:18 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 18861 output: 22 thoughts: 614 Thoughts: **KMSAN Fuzzing Evaluation - Zoned Block Device Handling** Okay, so the task is to figure out if this patch touching zoned block devices warrants a dedicated KMSAN fuzzing session, or if standard KASAN/sanitizers will be sufficient. Let's dig in. I see changes across `blk-core.c`, `blk-zoned.c`, `blk.h`, and `blkdev.h`. The core of the patch seems to revolve around refactoring the zoned block device management. First, the `bdev_zone_mgmt_allowed` check is replacing `bdev_zone_is_seq` which sounds like it is more flexible, which can be an interesting point of failure. Then I see a shift from `disk->zones_cond` to `disk->zones_state`, which is intriguing. Compacting the state and flags into a single array element is probably for efficiency. Looks like new enums, lookups, and helper functions, like `blk_zstate_to_zone_cond`, are added for handling the state transitions. Interesting. The code modifies `disk_zone_set_cond`, `disk_zone_get_state`, `disk_zone_get_cond`, `disk_zone_is_seq`, and `bdev_zone_mgmt_allowed`. This seems like the heart of the changes in this patch. Crucially, the changes to `blkdev_get_zone_info` have my attention. I need to know how these zone states are exposed to userspace. I see checks for `zno >= disk->nr_zones`, and that the `zone->cond` and `zone->type` are populated. The `memset` already being there is fine. I'm focusing on the user interface here. There's some refactoring in the zone revalidation logic with `disk_revalidate_*` functions, also `args->zones_state = kzalloc(...)` which means it's zero-initialized. Good practice, but I need to watch how this interacts with the new state. Also, locking and serialization, with `disk->zone_revalidate_mutex`. That’s important for concurrency, so I need to know where that is happening. The primary thing to do is find out if there's any copying of `struct blk_zone` to userspace, or related ioctls, or anything along those lines where userspace data is set from kernel structures. Given the changes in state and conditions, that's where any potential sanitizer problems might surface. I should search for `blkdev_get_zone_info` and how zones interact with userspace. This seems like a reasonable candidate for needing KMSAN. If we are passing state to userspace without sanitization, this will likely require KMSAN. |
| 16/3 | 2026/09/08 09:19 | tool | grepper |
0mArgs:{
"Expression": "blkdev_get_zone_info"
}Results: {
"Output": "block/blk-zoned.c=1074=static inline bool blkdev_has_cached_report_zones(struct block_device *bdev)\n--\nblock/blk-zoned.c-1081-/**\nblock/blk-zoned.c:1082: * blkdev_get_zone_info - Get a single zone information from cached data\nblock/blk-zoned.c-1083- * @bdev: Target block device\n--\nblock/blk-zoned.c-1095- */\nblock/blk-zoned.c:1096:int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,\nblock/blk-zoned.c-1097-\t\t\t struct blk_zone *zone)\n--\nblock/blk-zoned.c-1192-}\nblock/blk-zoned.c:1193:EXPORT_SYMBOL_GPL(blkdev_get_zone_info);\nblock/blk-zoned.c-1194-\n--\nblock/blk-zoned.c-1205- * device driver to get the zone report from the device, use\nblock/blk-zoned.c:1206: * blkdev_get_zone_info() to generate the report from the disk zone write\nblock/blk-zoned.c-1207- * plugs and zones condition array. Since calling this function without a\n--\nblock/blk-zoned.c=1210=int blkdev_report_zones_cached(struct block_device *bdev, sector_t sector,\n--\nblock/blk-zoned.c-1239-\t sector += zone_sectors, idx++) {\nblock/blk-zoned.c:1240:\t\tret = blkdev_get_zone_info(bdev, sector, \u0026zone);\nblock/blk-zoned.c-1241-\t\tif (ret)\n--\nfs/xfs/xfs_zone_alloc.c=996=xfs_query_write_pointer(\n--\nfs/xfs/xfs_zone_alloc.c-1008-\tif (bdev_is_zoned(bdev)) {\nfs/xfs/xfs_zone_alloc.c:1009:\t\terror = blkdev_get_zone_info(bdev, start, \u0026zone);\nfs/xfs/xfs_zone_alloc.c-1010-\t\tif (error)\n--\ninclude/linux/blkdev.h=449=int disk_report_zone(struct gendisk *disk, struct blk_zone *zone,\n--\ninclude/linux/blkdev.h-451-\ninclude/linux/blkdev.h:452:int blkdev_get_zone_info(struct block_device *bdev, sector_t sector,\ninclude/linux/blkdev.h-453-\t\t\t struct blk_zone *zone);\n"
} |
| 17/3 | 2026/09/08 09:19 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 20810 output: 37 thoughts: 25 Thoughts: Okay, here's my take on those functions, as if I were thinking it: **Diving into Block Device Zone Information** Alright, let's get down to brass tacks. I need to understand how zone information is being handled within the block device layer, specifically the interaction between `blkdev_report_zones_cached` and `blkdev_get_zone_info`. I assume these are the core functions related to retrieving and potentially caching details about zoned block devices. My expectation is that `blkdev_report_zones_cached` likely leverages a cache, perhaps holding zone metadata to avoid repeated calls to the underlying storage hardware. `blkdev_get_zone_info`, I'm guessing, is the fundamental function responsible for querying the actual device. I'll need to figure out the caching strategy if it exists, how cache invalidation is managed, and how these two functions work together. Hopefully, by understanding their relationship, I can get a good grasp of the block device zone management implementation. |
| 18/3 | 2026/09/08 09:19 | tool | read-file |
0mArgs:{
"File": "block/blk-zoned.c",
"FirstLine": 1095,
"LineCount": 100
}Results: {
"Contents": "1095:\t */\n1096:\tint blkdev_get_zone_info(struct block_device *bdev, sector_t sector,\n1097:\t\t\t\t struct blk_zone *zone)\n1098:\t{\n1099:\t\tstruct gendisk *disk = bdev-\u003ebd_disk;\n1100:\t\tsector_t zone_sectors = bdev_zone_sectors(bdev);\n1101:\t\tunsigned int zno = disk_zone_no(disk, sector);\n1102:\t\tstruct blk_zone_wplug *zwplug;\n1103:\t\tunsigned long flags;\n1104:\t\tu8 *zones_state, zs;\n1105:\t\n1106:\t\tif (!bdev_is_zoned(bdev))\n1107:\t\t\treturn -EOPNOTSUPP;\n1108:\t\n1109:\t\tif (sector \u003e= get_capacity(disk) || zno \u003e= disk-\u003enr_zones)\n1110:\t\t\treturn -EINVAL;\n1111:\t\n1112:\t\tmemset(zone, 0, sizeof(*zone));\n1113:\t\tsector = bdev_zone_start(bdev, sector);\n1114:\t\n1115:\t\tif (!blkdev_has_cached_report_zones(bdev))\n1116:\t\t\tgoto fallback;\n1117:\t\n1118:\t\trcu_read_lock();\n1119:\t\tzones_state = rcu_dereference(disk-\u003ezones_state);\n1120:\t\tif (!disk-\u003ezone_wplugs_hash || !zones_state) {\n1121:\t\t\trcu_read_unlock();\n1122:\t\t\tgoto fallback;\n1123:\t\t}\n1124:\t\n1125:\t\tzs = zones_state[zno];\n1126:\t\tzone-\u003econd = blk_zstate_to_zone_cond(zs);\n1127:\t\tif (blk_zstate_is_conv(zs))\n1128:\t\t\tzone-\u003etype = BLK_ZONE_TYPE_CONVENTIONAL;\n1129:\t\telse\n1130:\t\t\tzone-\u003etype = BLK_ZONE_TYPE_SEQWRITE_REQ;\n1131:\t\trcu_read_unlock();\n1132:\t\n1133:\t\tzone-\u003estart = sector;\n1134:\t\tzone-\u003elen = zone_sectors;\n1135:\t\n1136:\t\t/*\n1137:\t\t * If this is a conventional zone, we do not have a zone write plug and\n1138:\t\t * can report the zone immediately.\n1139:\t\t */\n1140:\t\tif (zone-\u003etype == BLK_ZONE_TYPE_CONVENTIONAL) {\n1141:\t\t\tzone-\u003ecapacity = zone_sectors;\n1142:\t\t\tzone-\u003ewp = ULLONG_MAX;\n1143:\t\t\treturn 0;\n1144:\t\t}\n1145:\t\n1146:\t\t/*\n1147:\t\t * This is a sequential write required zone. If the zone is read-only or\n1148:\t\t * offline, only set the zone write pointer to an invalid value and\n1149:\t\t * report the zone.\n1150:\t\t */\n1151:\t\tif (disk_zone_is_last(disk, zone))\n1152:\t\t\tzone-\u003ecapacity = disk-\u003elast_zone_capacity;\n1153:\t\telse\n1154:\t\t\tzone-\u003ecapacity = disk-\u003ezone_capacity;\n1155:\t\n1156:\t\tif (disk_zone_cond_is_offline_or_readonly(zone-\u003econd)) {\n1157:\t\t\tzone-\u003ewp = ULLONG_MAX;\n1158:\t\t\treturn 0;\n1159:\t\t}\n1160:\t\n1161:\t\t/*\n1162:\t\t * If the zone does not have a zone write plug, it is either full or\n1163:\t\t * empty, as we otherwise would have a zone write plug for it. In this\n1164:\t\t * case, set the write pointer accordingly and report the zone.\n1165:\t\t * Otherwise, if we have a zone write plug, use it.\n1166:\t\t */\n1167:\t\tzwplug = disk_get_zone_wplug(disk, sector);\n1168:\t\tif (!zwplug) {\n1169:\t\t\tif (zone-\u003econd == BLK_ZONE_COND_FULL)\n1170:\t\t\t\tzone-\u003ewp = ULLONG_MAX;\n1171:\t\t\telse\n1172:\t\t\t\tzone-\u003ewp = sector;\n1173:\t\t\treturn 0;\n1174:\t\t}\n1175:\t\n1176:\t\tspin_lock_irqsave(\u0026zwplug-\u003elock, flags);\n1177:\t\tif (zwplug-\u003eflags \u0026 BLK_ZONE_WPLUG_NEED_WP_UPDATE) {\n1178:\t\t\tspin_unlock_irqrestore(\u0026zwplug-\u003elock, flags);\n1179:\t\t\tdisk_put_zone_wplug(zwplug);\n1180:\t\t\treturn blkdev_report_zone_fallback(bdev, sector, zone);\n1181:\t\t}\n1182:\t\tzone-\u003econd = zwplug-\u003econd;\n1183:\t\tzone-\u003ewp = sector + zwplug-\u003ewp_offset;\n1184:\t\tspin_unlock_irqrestore(\u0026zwplug-\u003elock, flags);\n1185:\t\n1186:\t\tdisk_put_zone_wplug(zwplug);\n1187:\t\n1188:\t\treturn 0;\n1189:\t\n1190:\tfallback:\n1191:\t\treturn blkdev_report_zone_fallback(bdev, sector, zone);\n1192:\t}\n1193:\tEXPORT_SYMBOL_GPL(blkdev_get_zone_info);\n1194:\t\n"
} |
| 19/3 | 2026/09/08 09:19 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 22608 output: 36 thoughts: 22 Thoughts: Here's a summary of my thought process, as requested: **Diving into the Zone Report Caching Logic** Okay, so I'm digging into this `blkdev_report_zones_cached` function, and my immediate focus is around line 1205. I'm guessing that's where the core logic of the caching implementation lives. I need to understand how the block device's zone reporting is being optimized, potentially with some kind of local cache. Specifically, I want to see how the code handles caching the zone information. Are we talking about a simple in-memory cache, or something more sophisticated like a persistent cache? Are there any potential concurrency issues I should anticipate? I'm expecting to find the key read/write paths for the cached data around that line. The function's name hints that it might be cached, and that's my starting point. Now, let's see what's really happening. |
| 20/3 | 2026/09/08 09:19 | tool | read-file |
0mArgs:{
"File": "block/blk-zoned.c",
"FirstLine": 1210,
"LineCount": 60
}Results: {
"Contents": "1210:\tint blkdev_report_zones_cached(struct block_device *bdev, sector_t sector,\n1211:\t\t\t\tunsigned int nr_zones, report_zones_cb cb, void *data)\n1212:\t{\n1213:\t\tstruct gendisk *disk = bdev-\u003ebd_disk;\n1214:\t\tsector_t capacity = get_capacity(disk);\n1215:\t\tsector_t zone_sectors = bdev_zone_sectors(bdev);\n1216:\t\tunsigned int idx = 0;\n1217:\t\tstruct blk_zone zone;\n1218:\t\tint ret;\n1219:\t\n1220:\t\tif (!cb || !bdev_is_zoned(bdev) ||\n1221:\t\t WARN_ON_ONCE(!disk-\u003efops-\u003ereport_zones))\n1222:\t\t\treturn -EOPNOTSUPP;\n1223:\t\n1224:\t\tif (!nr_zones || sector \u003e= capacity)\n1225:\t\t\treturn 0;\n1226:\t\n1227:\t\tif (!blkdev_has_cached_report_zones(bdev)) {\n1228:\t\t\tstruct blk_report_zones_args args = {\n1229:\t\t\t\t.cb = cb,\n1230:\t\t\t\t.data = data,\n1231:\t\t\t\t.report_active = true,\n1232:\t\t\t};\n1233:\t\n1234:\t\t\treturn blkdev_do_report_zones(bdev, sector, nr_zones, \u0026args);\n1235:\t\t}\n1236:\t\n1237:\t\tfor (sector = bdev_zone_start(bdev, sector);\n1238:\t\t sector \u003c capacity \u0026\u0026 idx \u003c nr_zones;\n1239:\t\t sector += zone_sectors, idx++) {\n1240:\t\t\tret = blkdev_get_zone_info(bdev, sector, \u0026zone);\n1241:\t\t\tif (ret)\n1242:\t\t\t\treturn ret;\n1243:\t\n1244:\t\t\tret = cb(\u0026zone, idx, data);\n1245:\t\t\tif (ret)\n1246:\t\t\t\treturn ret;\n1247:\t\t}\n1248:\t\n1249:\t\treturn idx;\n1250:\t}\n1251:\tEXPORT_SYMBOL_GPL(blkdev_report_zones_cached);\n1252:\t\n1253:\tstatic void blk_zone_reset_bio_endio(struct bio *bio)\n1254:\t{\n1255:\t\tstruct gendisk *disk = bio-\u003ebi_bdev-\u003ebd_disk;\n1256:\t\tsector_t sector = bio-\u003ebi_iter.bi_sector;\n1257:\t\tstruct blk_zone_wplug *zwplug;\n1258:\t\n1259:\t\t/*\n1260:\t\t * If we have a zone write plug, set its write pointer offset to 0.\n1261:\t\t * This will abort all BIOs plugged for the target zone. It is fine as\n1262:\t\t * resetting zones while writes are still in-flight will result in the\n1263:\t\t * writes failing anyway.\n1264:\t\t */\n1265:\t\tzwplug = disk_get_zone_wplug(disk, sector);\n1266:\t\tif (zwplug) {\n1267:\t\t\tunsigned long flags;\n1268:\t\n1269:\t\t\tspin_lock_irqsave(\u0026zwplug-\u003elock, flags);\n"
} |
| 21/3 | 2026/09/08 09:19 | llm | kmsan-evaluator |
0mModel:gemini-3.7-flash Args: null Results: null Tokens: input: 23786 output: 268 thoughts: 1863 Thoughts: **KMSAN Assessment: No Uninitialized Memory Risks Introduced**
Okay, let's break this down. My goal here is to determine if this code introduces any new risks related to uninitialized memory. I'm focusing on KMSAN, and I'm looking for anything that could lead to information leaks or undefined behavior due to uninitialized reads.
First, I examined how `blkdev_get_zone_info` uses the `zone` struct. I confirmed that it's explicitly initialized to zero using `memset` *before* any data is written to its fields. This means there's no risk of reading uninitialized values from this struct. This initialization happens consistently, even in fallback paths.
Next, I looked into the zone revalidation process: `blk_revalidate_conv_zone`, `blk_revalidate_seq_zone`, and how the `zones_state` array is managed. I saw that `disk_init_revalidate_args` uses `kzalloc` to allocate `zones_state`, which guarantees zero-initialization. Then I traced how the `blk_zstate_set` function is used to update the `zones_state` array, with `blk_zstate_set` itself being bounds checked. The array indices are consistently validated to ensure that no out-of-bounds writes or reads occur. Both `blk_revalidate_conv_zone` and `blk_revalidate_seq_zone` use this to populate this array, so I'm happy this is handled.
Now let's check `blk_zstate_set`: This function itself is pretty straightforward, and its bounds checking further reinforces safety.
I carefully analyzed the `disk_zone_get_state` function. Critically, if the `zones_state` pointer is `NULL` or the zone number `zno` is out of bounds, the code explicitly assigns a default value (`BLK_ZFLAG_CONV`) to the return value `zs`. This is important: no matter the condition, `zs` is always initialized before use, avoiding any chance of reading uninitialized memory.
Moving on to `bdev_zone_mgmt_allowed`, everything is computed from return values and clearly initialized before use.
I checked `disk_init_zone_resources`: this code only initializes mutexes, atomic variables, spinlocks, and lists, and doesn't deal with memory allocation or potential uninitialized data. This is good to see as this is also a hot path for the kernel.
I then went over `blk_revalidate_disk_zones`. Here, `args` is initialized with `{ }`, a zero-initialization, which effectively ensures that all its fields are set to a known state.
Finally, I checked the seemingly benign `queue_zone_wplug_show` in `CONFIG_BLK_DEBUG_FS`. This change was merely a parameter signature update and not related to the memory initialization process.
So, to summarize my overall assessment:
1. `zones_state` is allocated via `kzalloc`. This ensures it's zero-initialized.
2. `blk_zone` is explicitly `memset` before use.
3. `args` in `blk_revalidate_disk_zones` is initialized with `{ }`.
4. `zs` in `disk_zone_get_state` is always assigned a value, even in error paths.
5. No new data structures with uninitialized padding or potentially leaked information have been added.
6. These changes focus on zone condition tracking, revalidation serialization, and management permission checks.
I'm confident that these changes do *not* introduce any new KMSAN-relevant risks. While KASAN/LOCKDEP/UBSAN are relevant for catching other classes of bugs like overflows, use-after-free or lock issues, they are separate from KMSAN. All allocated memory is properly initialized, and all reads are from initialized variables or memory regions. Therefore, this patch does not require KMSAN.
|
| 22/3 | 2026/09/08 09:19 | tool | set-results |
0mArgs:{
"KMSANReasoning": "The patch refactors zoned block device condition and state tracking (introducing enum blk_zstate to pack zone condition and conventional zone flag into a single u8 array per disk), adds mutex synchronization around zone revalidation, and updates zone management operation checks.\n\nAll allocated memory and data structures touched by the patch are fully initialized:\n1. The dynamic `zones_state` array is allocated using `kzalloc()`.\n2. Stack structures such as `struct blk_revalidate_zone_args` and `struct blk_zone` are explicitly zero-initialized (`{ }` / `memset`).\n3. Return paths and condition/state helper functions (`disk_zone_get_state`, `blk_zstate_to_zone_cond`, etc.) assign all outputs and default cases cleanly.\n4. No uninitialized memory or structure padding is exposed or copied to user space.\n\nAny potential memory safety issues (e.g. array index bounds, concurrency, use-after-free during zone write plug teardown, or lock ordering with `zone_revalidate_mutex`) are covered by KASAN and LOCKDEP. KMSAN is not required.",
"NeedsKMSAN": false
}Results: {
"KMSANReasoning": "The patch refactors zoned block device condition and state tracking (introducing enum blk_zstate to pack zone condition and conventional zone flag into a single u8 array per disk), adds mutex synchronization around zone revalidation, and updates zone management operation checks.\n\nAll allocated memory and data structures touched by the patch are fully initialized:\n1. The dynamic `zones_state` array is allocated using `kzalloc()`.\n2. Stack structures such as `struct blk_revalidate_zone_args` and `struct blk_zone` are explicitly zero-initialized (`{ }` / `memset`).\n3. Return paths and condition/state helper functions (`disk_zone_get_state`, `blk_zstate_to_zone_cond`, etc.) assign all outputs and default cases cleanly.\n4. No uninitialized memory or structure padding is exposed or copied to user space.\n\nAny potential memory safety issues (e.g. array index bounds, concurrency, use-after-free during zone write plug teardown, or lock ordering with `zone_revalidate_mutex`) are covered by KASAN and LOCKDEP. KMSAN is not required.",
"NeedsKMSAN": false
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|