summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJens Axboe <axboe@kernel.dk>2026-09-15 06:53:17 -0600
committerJens Axboe <axboe@kernel.dk>2026-09-15 06:53:17 -0600
commit2dd9768a37dbb99398c5e332485dbb107ac397c8 (patch)
tree36be9d1c74e9b109b70265f46f6ba564c65a6e35
parent4953f86cbbdd202104a696ac837a6692bca5e995 (diff)
parent85bffad4d2fea373a72d68bed8012e8cbccb8ee7 (diff)
downloadlinux-next-2dd9768a37dbb99398c5e332485dbb107ac397c8.tar.gz
linux-next-2dd9768a37dbb99398c5e332485dbb107ac397c8.zip
Merge branch 'for-7.4/block' into for-next
* for-7.4/block: block: simplify disk_zone_set_cond() block: allow read-only and offline conventional zones block: fail zone management operations to read-only and offline zones block: always treat offline and read-only zones as dead block: propagate readonly and offline conditions to zone write plugs block: retry zone revalidation on capacity change block: drop all zone write plugs on capacity changes block: serialize zone revalidation block: introduce disk_for_all_zone_wplugs() block: improve blkdev_get_zone_info() block: refactor bdev_zone_is_seq() block: remember a zone type regardless of its condition block: refactor disk_update_zone_resources() block: refactor disk_revalidate_zone_resources() block: improve capacity handling during zone revalidation block: remove disk_free_zone_resources() block: cap atomic write size by PI buffer size constraints block: Fix the thaw_bdev reference in bdev_freeze() kernel-doc
-rw-r--r--block/bdev.c2
-rw-r--r--block/blk-core.c7
-rw-r--r--block/blk-settings.c18
-rw-r--r--block/blk-zoned.c848
-rw-r--r--block/blk.h6
-rw-r--r--include/linux/blkdev.h11
6 files changed, 561 insertions, 331 deletions
diff --git a/block/bdev.c b/block/bdev.c
index cd8323083740..fac74319e9fb 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -292,7 +292,7 @@ EXPORT_SYMBOL(sync_blockdev_range);
* The reference counter (bd_fsfreeze_count) guarantees that only the last
* unfreeze process can unfreeze the frozen filesystem actually when multiple
* freeze requests arrive simultaneously. It counts up in bdev_freeze() and
- * count down in bdev_thaw(). When it becomes 0, thaw_bdev() will unfreeze
+ * count down in bdev_thaw(). When it becomes 0, bdev_thaw() will unfreeze
* actually.
*
* Return: On success zero is returned, negative error code on failure.
diff --git a/block/blk-core.c b/block/blk-core.c
index 196bccf27f58..13dc70e8f55d 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-settings.c b/block/blk-settings.c
index e469baa1f08b..1f5ee2453269 100644
--- a/block/blk-settings.c
+++ b/block/blk-settings.c
@@ -206,12 +206,6 @@ static int blk_validate_integrity_limits(struct queue_limits *lim)
lim->max_sectors = min(lim->max_sectors,
max_integrity_io_size(lim) >> SECTOR_SHIFT);
- if (lim->features & BLK_FEAT_ATOMIC_WRITES) {
- lim->atomic_write_max_sectors =
- min(lim->atomic_write_max_sectors,
- max_integrity_io_size(lim) >> SECTOR_SHIFT);
- }
-
return 0;
}
@@ -236,14 +230,20 @@ static unsigned int blk_queue_max_guaranteed_bio(struct queue_limits *lim)
static void blk_atomic_writes_update_limits(struct queue_limits *lim)
{
+ unsigned int integrity_max = lim->integrity.metadata_size ?
+ max_integrity_io_size(lim) : 0;
unsigned int unit_limit = min(lim->max_hw_sectors << SECTOR_SHIFT,
blk_queue_max_guaranteed_bio(lim));
+ unit_limit = min_not_zero(unit_limit, integrity_max);
unit_limit = rounddown_pow_of_two(unit_limit);
lim->atomic_write_max_sectors =
min(lim->atomic_write_hw_max >> SECTOR_SHIFT,
lim->max_hw_sectors);
+ lim->atomic_write_max_sectors =
+ min_not_zero(lim->atomic_write_max_sectors,
+ integrity_max >> SECTOR_SHIFT);
lim->atomic_write_unit_min =
min(lim->atomic_write_hw_unit_min, unit_limit);
lim->atomic_write_unit_max =
@@ -511,11 +511,13 @@ int blk_validate_limits(struct queue_limits *lim)
if (!(lim->features & BLK_FEAT_WRITE_CACHE))
lim->features &= ~BLK_FEAT_FUA;
- blk_validate_atomic_write_limits(lim);
-
err = blk_validate_integrity_limits(lim);
if (err)
return err;
+
+ /* atomics limits depend on integrity limits */
+ blk_validate_atomic_write_limits(lim);
+
return blk_validate_zoned_limits(lim);
}
EXPORT_SYMBOL_GPL(blk_validate_limits);
diff --git a/block/blk-zoned.c b/block/blk-zoned.c
index a5afb842bf35..ec510d66dfe2 100644
--- a/block/blk-zoned.c
+++ b/block/blk-zoned.c
@@ -40,6 +40,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.
* @entry: list_head structure for listing the plug in the disk list of active
@@ -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 @@ again:
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 50abfd932886..2cc03aa54c53 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 098a65f3e48b..d003a9d2d1f6 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;