Zone write plugging
Subsystems (9)
Damien Le Moal
maintainerv5 cover Zone write plugging
The patch series introduces zone write plugging (ZWP) as the new mechanism to control the ordering of writes to zoned block devices. ZWP replaces zone write locking (ZWL) which is implemented only by mq-deadline today. ZWP also allows emulating zone append operations using regular writes for zoned devices that do not natively support this operation (e.g. SMR HDDs). This patch series removes the scsi disk driver and device mapper zone append emulation to use ZWP emulation.
Unlike ZWL which operates on requests, ZWP operates on BIOs. A zone write plug is simply a BIO list that is atomically manipulated using a spinlock and a kblockd submission work. A write BIO to a zone is "plugged" to delay its execution if a write BIO for the same zone was already issued, that is, if a write request for the same zone is being executed. The next plugged BIO is unplugged and issued once the write request completes.
This mechanism allows to:
- Untangle zone write ordering from the block IO schedulers. This allows removing the restriction on using only mq-deadline for zoned block devices. Any block IO scheduler, including "none" can be used.
- Zone write plugging operates on BIOs instead of requests. Plugged BIOs waiting for execution thus do not hold scheduling tags and thus do not prevent other BIOs from being submitted to the device (reads or writes to other zones). Depending on the workload, this can significantly improve the device use and the performance.
- Both blk-mq (request) based zoned devices and BIO-based devices (e.g. device mapper) can use ZWP. It is mandatory for the former but optional for the latter: BIO-based driver can use zone write plugging to implement write ordering guarantees, or the drivers can implement their own if needed.
- The code is less invasive in the block layer and in device drivers. ZWP implementation is mostly limited to blk-zoned.c, with some small changes in blk-mq.c, blk-merge.c and bio.c.
Performance evaluation results are shown below.
The series is based on block/for-next and organized as follows:
- Patch 1 to 6 are preparatory changes for patch 7.
- Patch 7 and 8 introduce ZWP
- Patch 9 and 10 add zone append emulation to ZWP.
- Patch 11 to 18 modify zoned block device drivers to use ZWP and prepare for the removal of ZWL.
- Patch 19 to 28 remove zone write locking
Overall, these changes do not significantly increase the amount of code (the higher number of addition shown by diff-stat is in fact due to a larger amount of comments in the code).
Many thanks must go to Christoph Hellwig for comments and suggestions he provided on earlier versions of these patches.
Performance evaluation results
==============================
Environments:
- Intel Xeon 16-cores/32-threads, 128GB of RAM
- Kernel:
- ZWL (baseline): block/for-next (based on 6.9.0-rc2)
- ZWP: block/for-next patched kernel to add zone write plugging (both kernels were compiled with the same configuration turning off most heavy debug features)
Workoads:
- seqw4K1: 4KB sequential write, qd=1
- seqw4K16: 4KB sequential write, qd=16
- seqw1M16: 1MB sequential write, qd=16
- rndw4K16: 4KB random write, qd=16
- rndw128K16: 128KB random write, qd=16
- btrfs workoad: Single fio job writing 128 MB files using 128 KB direct IOs at qd=16.
Devices:
- nullblk (zoned): 4096 zones of 256 MB, 128 max open zones.
- NVMe ZNS drive: 1 TB ZNS drive with 2GB zone size, 14 max open and active zones.
- SMR HDD: 20 TB disk with 256MB zone size, 128 max open zones.
For ZWP, the result show the performance percentage increase (or decrease) against ZWL (baseline) case.
1) null_blk zoned device:
+--------+--------+-------+--------+---------+---------+
|seqw4K1 |seqw4K16|seqw1M1|seqw1M16|rndw4K16|rndw128K16|
|(MB/s) | (MB/s) |(MB/s) | (MB/s) | (KIOPS)| (KIOPS) |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWL | 940 | 840 | 18550 | 14400 | 424 | 167 |
|mq-deadline| | | | | | |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWP | 943 | 845 | 18660 | 14770 | 461 | 165 |
|mq-deadline| (+0%) | (+0%) | (+0%) | (+1%) | (+8%) | (-1%) |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWP | 756 | 668 | 16020 | 12980 | 135 | 101 |
| bfq | (-19%) | (-20%) | (-13%)| (-9%) | (-68%) | (-39%) |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWP | 2639 | 1715 | 28190 | 19760 | 344 | 150 |
| none | (+180%)| (+104%)| (+51%)| (+37%) | (-18%) | (-10%) |
+-----------+--------+--------+-------+--------+--------+----------+ZWP with mq-deadline gives performance very similar to zone write locking, showing that zone write plugging overhead is acceptable. But ZWP ability to run fast block devices with the none scheduler shows brings all the benefits of zone write plugging and results in significant performance increase for all workloads. The exception to this are random write workloads with multiple jobs: for these, the faster request submission rate achieved by zone write plugging results in higher contention on null-blk zone spinlock, which degrades performance.
2) NVMe ZNS drive:
+--------+--------+-------+--------+--------+----------+
|seqw4K1 |seqw4K16|seqw1M1|seqw1M16|rndw4K16|rndw128K16|
|(MB/s) | (MB/s) |(MB/s) | (MB/s) | (KIOPS)| (KIOPS) |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWL | 183 | 702 | 1066 | 1103 | 53.5 | 14.5 |
|mq-deadline| | | | | | |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWP | 183 | 719 | 1086 | 1108 | 55.6 | 14.7 |
|mq-deadline| (-0%) | (+1%) | (+0%) | (+0%) | (+3%) | (+1%) |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWP | 178 | 691 | 1082 | 1106 | 30.8 | 11.5 |
| bfq | (-3%) | (-2%) | (-0%) | (+0%) | (-42%) | (-20%) |
+-----------+--------+--------+-------+--------+--------+----------+
| ZWP | 190 | 666 | 1083 | 1108 | 51.4 | 14.7 |
| none | (+4%) | (-5%) | (+0%) | (+0%) | (-4%) | (+0%) |
+-----------+--------+--------+-------+--------+--------+----------+Zone write plugging overhead does not significantly impact performance. Similar to nullblk, using the none scheduler leads to performance increase for most workloads.
3) SMR SATA HDD:
+-------+--------+-------+--------+--------+----------+
|seqw4K1|seqw4K16|seqw1M1|seqw1M16|rndw4K16|rndw128K16|
|(MB/s) | (MB/s) |(MB/s) | (MB/s) | (KIOPS)| (KIOPS) |
+-----------+-------+--------+-------+--------+--------+----------+
| ZWL | 107 | 243 | 245 | 246 | 2.2 | 0.763 |
|mq-deadline| | | | | | |
+-----------+-------+--------+-------+--------+--------+----------+
| ZWP | 107 | 242 | 245 | 245 | 2.2 | 0.772 |
|mq-deadline| (+0%) | (-0%) | (+0%) | (-0%) | (+0%) | (+0%) |
+-----------+-------+--------+-------+--------+--------+----------+
| ZWP | 104 | 241 | 246 | 242 | 2.2 | 0.765 |
| bfq | (-2%) | (-0%) | (+0%) | (-0%) | (+0%) | (+0%) |
+-----------+-------+--------+-------+--------+--------+----------+
| ZWP | 115 | 235 | 249 | 242 | 2.2 | 0.763 |
| none | (+7%) | (-3%) | (+1%) | (-1%) | (+0%) | (+0%) |
+-----------+-------+--------+-------+--------+--------+----------+Performance with purely sequential write workloads at high queue depth somewhat decrease a little when using zone write plugging. This is due to the different IO pattern that ZWP generates where the first writes to a zone start being issued when the end of the previous zone are still being written. Depending on how the disk handles queued commands, seek may be generated, slightly impacting the throughput achieved. Such pure sequential write workloads are however rare with SMR drives.
4) Zone append tests using btrfs:
+-------------+-------------+-----------+-------------+
| null-blk | null_blk | ZNS | SMR |
| native ZA | emulated ZA | native ZA | emulated ZA |
| (MB/s) | (MB/s) | (MB/s) | (MB/s) |
+-----------+-------------+-------------+-----------+-------------+
| ZWL | 2441 | N/A | 1081 | 243 |
|mq-deadline| | | | |
+-----------+-------------+-------------+-----------+-------------+
| ZWP | 2361 | 2999 | 1085 | 239 |
|mq-deadline| (-1%) | | (+0%) | (-2%) |
+-----------+-------------+-------------+-----------+-------------+
| ZWP | 2299 | 2730 | 1080 | 240 |
| bfq | (-4%) | | (+0%) | (-2%) |
+-----------+-------------+-------------+-----------+-------------+
| ZWP | 2443 | 3152 | 1083 | 240 |
| none | (+0%) | | (+0%) | (-1%) |
+-----------+-------------+-------------+-----------+-------------+With a more realistic use of the device though a file system, ZWP does not introduce significant performance differences, except for SMR for the same reason as with the fio sequential workloads at high queue depth.
Changes from v4:
- Modified patch 7 to:
- Fix a compilation warning
- Integrate disk_lookup_zone_wplug() into disk_get_zone_wplug() so that all users of a zone write plug do a get+put when referencing the plug.
- Introduced inline functions blk_zone_write_complete_request() and blk_zone_write_bio_endio() to integrate the "if (plugging)" test and avoid repeating it for all calls.
- Added review tags
Changes from v3:
- Rebase on block/for-next
- Removed old patch 1 as it is already applied
- Addressed Bart and Christoph comment in patch 4
- Merged former patch 8 and 9 together and changed the zone write plug allocation to use a mempool
- Removed the zone_wplugs_mempool_size filed from patch 8 and instead directly reference mempool->min_nr
- Added review tags
Changes from v2:
- Added Patch 1 (Christoph's comment)
- Fixed error code setup in Patch 3 (Bart's comment)
- Split former patch 26 into patches 27 and 28
- Modified patch 8 (zone write plugging) introduction to remove the kmem_cache use and address Bart's and Christoph comments.
- Changed from using a mempool of zone write plugs to using a simple free-list (patch 9)
- Simplified patch 10 as suggested by Christoph
- Moved common code to a helper in patch 13 as suggested by Christoph
Changes from v1:
- Added patch 6
- Rewrite of patch 7 to use a hash table of dynamically allocated zone write plugs. This results in changes in patch 11 and the addition of patch 8 and 9.
- Rebased everything on 6.9.0-rc1
- Added review tags for patches that did not change
Damien Le Moal (28):
block: Restore sector of flush requests
block: Remove req_bio_endio()
block: Introduce blk_zone_update_request_bio()
block: Introduce bio_straddles_zones() and bio_offset_from_zone_start()
block: Allow using bio_attempt_back_merge() internally
block: Remember zone capacity when revalidating zones
block: Introduce zone write plugging
block: Fake max open zones limit when there is no limit
block: Allow zero value of max_zone_append_sectors queue limit
block: Implement zone append emulation
block: Allow BIO-based drivers to use blk_revalidate_disk_zones()
dm: Use the block layer zone append emulation
scsi: sd: Use the block layer zone append emulation
ublk_drv: Do not request ELEVATOR_F_ZBD_SEQ_WRITE elevator feature
null_blk: Do not request ELEVATOR_F_ZBD_SEQ_WRITE elevator feature
null_blk: Introduce zone_append_max_sectors attribute
null_blk: Introduce fua attribute
nvmet: zns: Do not reference the gendisk conv_zones_bitmap
block: Remove BLK_STS_ZONE_RESOURCE
block: Simplify blk_revalidate_disk_zones() interface
block: mq-deadline: Remove support for zone write locking
block: Remove elevator required features
block: Do not check zone type in blk_check_zone_append()
block: Move zone related debugfs attribute to blk-zoned.c
block: Replace zone_wlock debugfs entry with zone_wplugs entry
block: Remove zone write locking
block: Do not force select mq-deadline with CONFIG_BLK_DEV_ZONED
block: Do not special-case plugging of zone write operations
block/Kconfig | 5 - block/Makefile | 1 - block/bio.c | 6 + block/blk-core.c | 11 +- block/blk-flush.c | 1 + block/blk-merge.c | 22 +- block/blk-mq-debugfs-zoned.c | 22 - block/blk-mq-debugfs.c | 3 +- block/blk-mq-debugfs.h | 6 +- block/blk-mq.c | 125 ++- block/blk-mq.h | 31 - block/blk-settings.c | 46 +- block/blk-sysfs.c | 2 +- block/blk-zoned.c | 1336 +++++++++++++++++++++++++++-- block/blk.h | 80 +- block/elevator.c | 46 +- block/elevator.h | 1 - block/genhd.c | 3 +- block/mq-deadline.c | 176 +--- drivers/block/null_blk/main.c | 22 +- drivers/block/null_blk/null_blk.h | 2 + drivers/block/null_blk/zoned.c | 23 +- drivers/block/ublk_drv.c | 5 +- drivers/block/virtio_blk.c | 2 +- drivers/md/dm-core.h | 2 +- drivers/md/dm-zone.c | 476 +--------- drivers/md/dm.c | 75 +- drivers/md/dm.h | 4 +- drivers/nvme/host/core.c | 2 +- drivers/nvme/target/zns.c | 10 +- drivers/scsi/scsi_lib.c | 1 - drivers/scsi/sd.c | 8 - drivers/scsi/sd.h | 19 - drivers/scsi/sd_zbc.c | 335 +------- include/linux/blk-mq.h | 85 +- include/linux/blk_types.h | 30 +- include/linux/blkdev.h | 103 ++- 37 files changed, 1663 insertions(+), 1464 deletions(-) delete mode 100644 block/blk-mq-debugfs-zoned.c
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 1/28 block: Restore sector of flush requests
On completion of a flush sequence, blk_flush_restore_request() restores the bio of a request to the original submitted BIO. However, the last use of the request in the flush sequence may have been for a POSTFLUSH which does not have a sector. So make sure to restore the request sector using the iter sector of the original BIO. This BIO has not changed yet since the completions of the flush sequence intermediate steps use requeueing of the request until all steps are completed.
Restoring the request sector ensures that blk_mq_end_request() will see a valid sector as originally set when the flush BIO was submitted.
--- block/blk-flush.c | 1 + 1 file changed, 1 insertion(+)
block/blk-flush.c +1 -0
@@ -130,6 +130,7 @@ static void blk_flush_restore_request(struct request *rq)* original @rq->bio. Restore it. */ rq->bio = rq->biotail; + rq->__sector = rq->bio->bi_iter.bi_sector;/* make @rq a normal request */ rq->rq_flags &= ~RQF_FLUSH_SEQ ;
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 2/28 block: Remove req_bio_endio()
In reply to Damien Le MoalMoving req_bio_endio() code into its only caller, blk_update_request(), allows reducing accesses to and tests of bio and request fields. Also, given that partial completions of zone append operations is not possible and that zone append operations cannot be merged, the update of the BIO sector using the request sector for these operations can be moved directly before the call to bio_endio().
--- block/blk-mq.c | 58 ++++++++++++++++++++++++-------------------------- 1 file changed, 28 insertions(+), 30 deletions(-)
block/blk-mq.c +28 -30
@@ -762,31 +762,6 @@ void blk_dump_rq_flags(struct request *rq, char *msg) }EXPORT_SYMBOL (blk_dump_rq_flags); -static void req_bio_endio (struct request *rq,struct bio *bio, -unsigned int nbytes,blk_status_t error) -{ -if (unlikely (error)) { - bio->bi_status = error; - }else if (req_op (rq) ==REQ_OP_ZONE_APPEND ) { -/* -* Partial zone append completions cannot be supported as the -* BIO fragments may end up not being written sequentially. -*/ -if (bio->bi_iter.bi_size != nbytes) - bio->bi_status =BLK_STS_IOERR ; -else - bio->bi_iter.bi_sector = rq->__sector; - } - -bio_advance (bio, nbytes); - -if (unlikely (rq->rq_flags &RQF_QUIET )) -bio_set_flag (bio,BIO_QUIET ); -/* don't actually finish bio if it's part of flush sequence */ -if (bio->bi_iter.bi_size ==0 && !(rq->rq_flags &RQF_FLUSH_SEQ )) -bio_endio (bio); -} -static void blk_account_io_completion (struct request *req,unsigned int bytes) {if (req->part &&blk_do_io_stat (req)) {
@@ -890,6 +865,8 @@ static void blk_complete_request(struct request *req)bool blk_update_request (struct request *req,blk_status_t error,unsigned int nr_bytes) { +bool is_flush = req->rq_flags &RQF_FLUSH_SEQ ; +bool quiet = req->rq_flags &RQF_QUIET ;int total_bytes;trace_block_rq_complete (req, error, nr_bytes);
@@ -910,9 +887,8 @@ bool blk_update_request(struct request *req, blk_status_t error,if (blk_crypto_rq_has_keyslot (req) && nr_bytes >=blk_rq_bytes (req))__blk_crypto_rq_put_keyslot (req); -if (unlikely (error && !blk_rq_is_passthrough (req) && - !(req->rq_flags &RQF_QUIET )) && - !test_bit (GD_DEAD , &req->q->disk->state)) { +if (unlikely (error && !blk_rq_is_passthrough (req) && !quiet) && + !test_bit (GD_DEAD , &req->q->disk->state)) {blk_print_req_error (req, error);trace_block_rq_error (req, error, nr_bytes); }
@@ -924,12 +900,34 @@ bool blk_update_request(struct request *req, blk_status_t error,struct bio *bio = req->bio;unsigned bio_bytes =min (bio->bi_iter.bi_size, nr_bytes); -if (bio_bytes == bio->bi_iter.bi_size) +if (unlikely (error)) + bio->bi_status = error; + +if (bio_bytes == bio->bi_iter.bi_size) { req->bio = bio->bi_next; + }else if (req_op (req) ==REQ_OP_ZONE_APPEND && + error ==BLK_STS_OK ) { +/* +* Partial zone append completions cannot be supported +* as the BIO fragments may end up not being written +* sequentially. +*/ + bio->bi_status =BLK_STS_IOERR ; + }/* Completion has already been traced */ bio_clear_flag (bio,BIO_TRACE_COMPLETION ); -req_bio_endio (req, bio, bio_bytes, error); +if (unlikely (quiet)) +bio_set_flag (bio,BIO_QUIET ); + +bio_advance (bio, bio_bytes); + +/* Don't actually finish bio if it's part of flush sequence */ +if (!bio->bi_iter.bi_size && !is_flush) { +if (req_op (req) ==REQ_OP_ZONE_APPEND ) + bio->bi_iter.bi_sector = req->__sector; +bio_endio (bio); + } total_bytes += bio_bytes; nr_bytes -= bio_bytes;
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 3/28 block: Introduce blk_zone_update_request_bio()
In reply to Damien Le MoalOn completion of a zone append request, the request sector indicates the location of the written data. This value must be returned to the user through the BIO iter sector. This is done in 2 places: in blk_complete_request() and in blk_update_request(). Introduce the inline helper function blk_zone_update_request_bio() to avoid duplicating this BIO update for zone append requests, and to compile out this helper call when CONFIG_BLK_DEV_ZONED is not enabled.
--- block/blk-mq.c | 11 +++++------ block/blk.h | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 7 deletions(-)
block/blk-mq.c +5 -6
@@ -821,8 +821,7 @@ static void blk_complete_request(struct request *req)/* Completion has already been traced */ bio_clear_flag (bio,BIO_TRACE_COMPLETION ); -if (req_op (req) ==REQ_OP_ZONE_APPEND ) - bio->bi_iter.bi_sector = req->__sector; +blk_zone_update_request_bio (req, bio);if (!is_flush)bio_endio (bio);
@@ -923,10 +922,10 @@ bool blk_update_request(struct request *req, blk_status_t error,bio_advance (bio, bio_bytes);/* Don't actually finish bio if it's part of flush sequence */ -if (!bio->bi_iter.bi_size && !is_flush) { -if (req_op (req) ==REQ_OP_ZONE_APPEND ) - bio->bi_iter.bi_sector = req->__sector; -bio_endio (bio); +if (!bio->bi_iter.bi_size) { +blk_zone_update_request_bio (req, bio); +if (!is_flush) +bio_endio (bio); } total_bytes += bio_bytes;
block/blk.h +18 -1
@@ -408,12 +408,29 @@ static inline struct bio *blk_queue_bounce(struct bio *bio,#ifdef CONFIG_BLK_DEV_ZONED void disk_free_zone_bitmaps (struct gendisk *disk); +static inline void blk_zone_update_request_bio (struct request *rq, +struct bio *bio) +{ +/* +* For zone append requests, the request sector indicates the location +* at which the BIO data was written. Return this value to the BIO +* issuer through the BIO iter sector. +*/ +if (req_op (rq) ==REQ_OP_ZONE_APPEND ) + bio->bi_iter.bi_sector = rq->__sector; +}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);#else /* CONFIG_BLK_DEV_ZONED */ -static inline void disk_free_zone_bitmaps (struct gendisk *disk) {} +static inline void disk_free_zone_bitmaps (struct gendisk *disk) +{ +} +static inline void blk_zone_update_request_bio (struct request *rq, +struct bio *bio) +{ +}static inline int blkdev_report_zones_ioctl (struct block_device *bdev,unsigned int cmd,unsigned long arg) {
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 4/28 block: Introduce bio_straddles_zones() and bio_offset_from_zone_start()
In reply to Damien Le MoalImplement the inline helper functions bio_straddles_zones() and bio_offset_from_zone_start() to respectively test if a BIO crosses a zone boundary (the start sector and last sector belong to different zones) and to obtain the offset of a BIO from the start sector of its target zone.
--- include/linux/blkdev.h | 13 +++++++++++++ 1 file changed, 13 insertions(+)
include/linux/blkdev.h +13 -0
@@ -853,6 +853,13 @@ static inline unsigned int bio_zone_no(struct bio *bio)return disk_zone_no (bio->bi_bdev->bd_disk, bio->bi_iter.bi_sector); } +static inline bool bio_straddles_zones (struct bio *bio) +{ +return bio_sectors (bio) && +bio_zone_no (bio) != +disk_zone_no (bio->bi_bdev->bd_disk,bio_end_sector (bio) -1 ); +} +static inline unsigned int bio_zone_is_seq (struct bio *bio) {return disk_zone_is_seq (bio->bi_bdev->bd_disk, bio->bi_iter.bi_sector);
@@ -1328,6 +1335,12 @@ static inline sector_t bdev_offset_from_zone_start(struct block_device *bdev,return sector & (bdev_zone_sectors (bdev) -1 ); } +static inline sector_t bio_offset_from_zone_start (struct bio *bio) +{ +return bdev_offset_from_zone_start (bio->bi_bdev, + bio->bi_iter.bi_sector); +} +static inline bool bdev_is_zone_start (struct block_device *bdev,sector_t sector) {
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 5/28 block: Allow using bio_attempt_back_merge() internally
In reply to Damien Le MoalRemove "static" from the definition of bio_attempt_back_merge() and declare this function in block/blk.h to allow using it internally from other block layer files. The definition of enum bio_merge_status is also moved to block/blk.h.
--- block/blk-merge.c | 8 +------- block/blk.h | 8 ++++++++ 2 files changed, 9 insertions(+), 7 deletions(-)
block/blk-merge.c +1 -7
@@ -972,13 +972,7 @@ static void blk_account_io_merge_bio(struct request *req)part_stat_unlock (); } -enum bio_merge_status { -BIO_MERGE_OK , -BIO_MERGE_NONE , -BIO_MERGE_FAILED , -}; - -static enum bio_merge_status bio_attempt_back_merge (struct request *req, +enum bio_merge_status bio_attempt_back_merge (struct request *req,struct bio *bio,unsigned int nr_segs) {const blk_opf_t ff =bio_failfast (bio);
block/blk.h +8 -0
@@ -269,6 +269,14 @@ static inline void bio_integrity_free(struct bio *bio)unsigned long blk_rq_timeout (unsigned long timeout);void blk_add_timer (struct request *req); +enum bio_merge_status { +BIO_MERGE_OK , +BIO_MERGE_NONE , +BIO_MERGE_FAILED , +}; + +enum bio_merge_status bio_attempt_back_merge (struct request *req, +struct bio *bio,unsigned int nr_segs);bool blk_attempt_plug_merge (struct request_queue *q,struct bio *bio,unsigned int nr_segs); bool blk_bio_list_merge(struct request_queue *q, struct list_head *list,
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 6/28 block: Remember zone capacity when revalidating zones
In reply to Damien Le MoalIn preparation for adding zone write plugging, modify blk_revalidate_disk_zones() to get the capacity of zones of a zoned block device. This capacity value as a number of 512B sectors is stored in the gendisk zone_capacity field.
Given that host-managed SMR disks (including zoned UFS drives) and all known NVMe ZNS devices have the same zone capacity for all zones blk_revalidate_disk_zones() returns an error if different capacities are detected for different zones.
This also adds check to verify that the values reported by the device for zone capacities are correct, that is, that the zone capacity is never 0, does not exceed the zone size and is equal to the zone size for conventional zones.
--- block/blk-zoned.c | 26 ++++++++++++++++++++++++++ include/linux/blkdev.h | 1 + 2 files changed, 27 insertions(+)
block/blk-zoned.c +26 -0
@@ -438,6 +438,7 @@ struct blk_revalidate_zone_args {unsigned long *conv_zones_bitmap;unsigned long *seq_zones_wlock;unsigned int nr_zones; +unsigned int zone_capacity;sector_t sector; };
@@ -482,9 +483,20 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,return -ENODEV ; } +if (!zone->capacity || zone->capacity > zone->len) { +pr_warn ("%s: Invalid zone capacity\n" , + disk->disk_name); +return -ENODEV ; + } +/* Check zone type */ switch (zone->type) {case BLK_ZONE_TYPE_CONVENTIONAL : +if (zone->capacity != zone->len) { +pr_warn ("%s: Invalid conventional zone capacity\n" , + disk->disk_name); +return -ENODEV ; + }if (!args->conv_zones_bitmap) { args->conv_zones_bitmap =blk_alloc_zone_bitmap (q->node, args->nr_zones);
@@ -500,6 +512,18 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,if (!args->seq_zones_wlock)return -ENOMEM ; } + +/* +* Remember the capacity of the first sequential zone and check +* if it is constant for all zones. +*/ +if (!args->zone_capacity) + args->zone_capacity = zone->capacity; +if (zone->capacity != args->zone_capacity) { +pr_warn ("%s: Invalid variable zone capacity\n" , + disk->disk_name); +return -ENODEV ; + }break ;case BLK_ZONE_TYPE_SEQWRITE_PREF :default :
@@ -595,6 +619,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk,blk_mq_freeze_queue (q);if (ret >0 ) { disk->nr_zones = args.nr_zones; + disk->zone_capacity = args.zone_capacity;swap (disk->seq_zones_wlock, args.seq_zones_wlock);swap (disk->conv_zones_bitmap, args.conv_zones_bitmap);if (update_driver_data)
@@ -608,6 +633,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk,kfree (args.seq_zones_wlock);kfree (args.conv_zones_bitmap); +return ret; }EXPORT_SYMBOL_GPL (blk_revalidate_disk_zones);
include/linux/blkdev.h +1 -0
@@ -191,6 +191,7 @@ struct gendisk {* blk_mq_unfreeze_queue(). */ unsigned int nr_zones; +unsigned int zone_capacity;unsigned long *conv_zones_bitmap;unsigned long *seq_zones_wlock;#endif /* CONFIG_BLK_DEV_ZONED */
Signature · 1 line
2.44.0
Re: [PATCH v5 06/28] block: Remember zone capacity when revalidating zones
Quoted 10 lines · Damien Le Moal
In preparation for adding zone write plugging, modify
blk_revalidate_disk_zones()to get the capacity of zones of a zoned block device. This capacity value as a number of 512B sectors is stored in the gendiskzone_capacityfield.Given that host-managed SMR disks (including zoned UFS drives) and all known NVMe ZNS devices have the same zone capacity for all zones
blk_revalidate_disk_zones()returns an error if different capacities are detected for different zones.
Quoted 4 lines
This also adds check to verify that the values reported by the device for zone capacities are correct, that is, that the zone capacity is never 0, does not exceed the zone size and is equal to the zone size for conventional zones.
Damien Le Moal
maintainerPATCH v5 7/28 block: Introduce zone write plugging
In reply to Damien Le MoalZone write plugging implements a per-zone "plug" for write operations to control the submission and execution order of write operations to sequential write required zones of a zoned block device. Per-zone plugging guarantees that at any time there is at most only one write request per zone being executed. This mechanism is intended to replace zone write locking which implements a similar per-zone write throttling at the scheduler level, but is implemented only by mq-deadline.
Unlike zone write locking which operates on requests, zone write plugging operates on BIOs. A zone write plug is simply a BIO list that is atomically manipulated using a spinlock and a kblockd submission work. A write BIO to a zone is "plugged" to delay its execution if a write BIO for the same zone was already issued, that is, if a write request for the same zone is being executed. The next plugged BIO is unplugged and issued once the write request completes.
This mechanism allows to:
- Untangle zone write ordering from block IO schedulers. This allows removing the restriction on using mq-deadline for writing to zoned block devices. Any block IO scheduler, including "none" can be used.
- Zone write plugging operates on BIOs instead of requests. Plugged BIOs waiting for execution thus do not hold scheduling tags and thus are not preventing other BIOs from executing (reads or writes to other zones). Depending on the workload, this can significantly improve the device use (higher queue depth operation) and performance.
- Both blk-mq (request based) zoned devices and BIO-based zoned devices (e.g. device mapper) can use zone write plugging. It is mandatory for the former but optional for the latter. BIO-based drivers can use zone write plugging to implement write ordering guarantees, or the drivers can implement their own if needed.
- The code is less invasive in the block layer and is mostly limited to blk-zoned.c with some small changes in blk-mq.c, blk-merge.c and bio.c.
Zone write plugging is implemented using struct blk_zone_wplug. This structure includes a spinlock, a BIO list and a work structure to handle the submission of plugged BIOs. Zone write plugs structures are managed using a per-disk hash table.
Plugging of zone write BIOs is done using the function blk_zone_write_plug_bio() which returns false if a BIO execution does not need to be delayed and true otherwise. This function is called from blk_mq_submit_bio() after a BIO is split to avoid large BIOs spanning multiple zones which would cause mishandling of zone write plugs. This ichange enables by default zone write plugging for any mq request-based block device. BIO-based device drivers can also use zone write plugging by expliclty calling blk_zone_write_plug_bio() in their ->submit_bio method. For such devices, the driver must ensure that a BIO passed to blk_zone_write_plug_bio() is already split and not straddling zone boundaries.
Only write and write zeroes BIOs are plugged. Zone write plugging does not introduce any significant overhead for other operations. A BIO that is being handled through zone write plugging is flagged using the new BIO flag BIO_ZONE_WRITE_PLUGGING. A request handling a BIO flagged with this new flag is flagged with the new RQF_ZONE_WRITE_PLUGGING flag. The completion of BIOs and requests flagged trigger respectively calls to the functions blk_zone_write_bio_endio() and blk_zone_write_complete_request(). The latter function is used to trigger submission of the next plugged BIO using the zone plug work. blk_zone_write_bio_endio() does the same for BIO-based devices. This ensures that at any time, at most one request (blk-mq devices) or one BIO (BIO-based devices) is being executed for any zone. The handling of zone write plugs using a per-zone plug spinlock maximizes parallelism and device usage by allowing multiple zones to be writen simultaneously without lock contention.
Zone write plugging ignores flush BIOs without data. Hovever, any flush BIO that has data is always plugged so that the write part of the flush sequence is serialized with other regular writes.
Given that any BIO handled through zone write plugging will be the only BIO in flight for the target zone when it is executed, the unplugging and submission of a BIO will have no chance of successfully merging with plugged requests or requests in the scheduler. To overcome this potential performance degradation, blk_mq_submit_bio() calls the function blk_zone_write_plug_attempt_merge() to try to merge other plugged BIOs with the one just unplugged and submitted. Successful merging is signaled using blk_zone_write_plug_bio_merged(), called from bio_attempt_back_merge(). Furthermore, to avoid recalculating the number of segments of plugged BIOs to attempt merging, the number of segments of a plugged BIO is saved using the new struct bio field __bi_nr_segments. To avoid growing the size of struct bio, this field is added as a union with the bio_cookie field. This is safe to do as polling is always disabled for plugged BIOs.
When BIOs are plugged in a zone write plug, the device request queue usage counter is always incremented. This reference is kept and reused for blk-mq devices when the plugged BIO is unplugged and submitted again using submit_bio_noacct_nocheck(). For this case, the unplugged BIO is already flagged with BIO_ZONE_WRITE_PLUGGING and blk_mq_submit_bio() proceeds directly to allocating a new request for the BIO, re-using the usage reference count taken when the BIO was plugged. This extra reference count is dropped in blk_zone_write_plug_attempt_merge() for any plugged BIO that is successfully merged. Given that BIO-based devices will not take this path, the extra reference is dropped after a plugged BIO is unplugged and submitted.
Zone write plugs are dynamically allocated and managed using a hash table (an array of struct hlist_head) with RCU protection. A zone write plug is allocated when a write BIO is received for the zone and not freed until the zone is fully written, reset or finished. To detect when a zone write plug can be freed, the write state of each zone is tracked using a write pointer offset which corresponds to the offset of a zone write pointer relative to the zone start. Write operations always increment this write pointer offset. Zone reset operations set it to 0 and zone finish operations set it to the zone size.
If a write error happens, the wp_offset value of a zone write plug may become incorrect and out of sync with the device managed write pointer. This is handled using the zone write plug flag BLK_ZONE_WPLUG_ERROR. The function blk_zone_wplug_handle_error() is called from the new disk zone write plug work when this flag is set. This function executes a report zone to update the zone write pointer offset to the current value as indicated by the device. The disk zone write plug work is scheduled whenever a BIO flagged with BIO_ZONE_WRITE_PLUGGING completes with an error or when bio_zone_wplug_prepare_bio() detects an unaligned write. Once scheduled, the disk zone write plugs work keeps running until all zone errors are handled.
To match the new data structures used for zoned disks, the function disk_free_zone_bitmaps() is renamed to the more generic disk_free_zone_resources(). The function disk_init_zone_resources() is also introduced to initialize zone write plugs resources when a gendisk is allocated.
In order to guarantee that the user can simultaneously write up to a number of zones equal to a device max active zone limit or max open zone limit, zone write plugs are allocated using a mempool sized to the maximum of these 2 device limits. For a device that does not have active and open zone limits, 128 is used as the default mempool size.
If a change to the device active and open zone limits is detected, the disk mempool is resized when blk_revalidate_disk_zones() is executed.
This commit contains contributions from Christoph Hellwig <[email protected]>.
--- block/bio.c | 6 + block/blk-merge.c | 11 + block/blk-mq.c | 32 +- block/blk-zoned.c | 1090 ++++++++++++++++++++++++++++++++++++- block/blk.h | 47 +- block/genhd.c | 3 +- include/linux/blk-mq.h | 2 + include/linux/blk_types.h | 8 +- include/linux/blkdev.h | 12 + 9 files changed, 1200 insertions(+), 11 deletions(-)
block/bio.c +6 -0
@@ -1576,6 +1576,12 @@ void bio_endio(struct bio *bio)if (!bio_integrity_endio (bio))return ; +/* +* For write BIOs to zoned devices, signal the completion of the BIO so +* that the next write BIO can be submitted by zone write plugging. +*/ +blk_zone_write_bio_endio (bio); +rq_qos_done_bio (bio);if (bio->bi_bdev &&bio_flagged (bio,BIO_TRACE_COMPLETION )) {
block/blk-merge.c +11 -0
@@ -377,6 +377,7 @@ struct bio *__bio_split_to_limits(struct bio *bio,blkcg_bio_issue_init (split);bio_chain (split, bio);trace_block_split (split, bio->bi_iter.bi_sector); +WARN_ON_ONCE (bio_zone_write_plugging (bio));submit_bio_noacct (bio);return split; }
@@ -988,6 +989,9 @@ enum bio_merge_status bio_attempt_back_merge(struct request *req,blk_update_mixed_merge (req, bio, false); +if (req->rq_flags &RQF_ZONE_WRITE_PLUGGING ) +blk_zone_write_plug_bio_merged (bio); + req->biotail->bi_next = bio; req->biotail = bio; req->__data_len += bio->bi_iter.bi_size;
@@ -1003,6 +1007,13 @@ static enum bio_merge_status bio_attempt_front_merge(struct request *req, {const blk_opf_t ff =bio_failfast (bio); +/* +* A front merge for zone writes can happen only if the user submitted +* writes out of order. Do not attempt this to let the write fail. +*/ +if (req->rq_flags &RQF_ZONE_WRITE_PLUGGING ) +return BIO_MERGE_FAILED ; +if (!ll_front_merge_fn (req, bio, nr_segs))return BIO_MERGE_FAILED ;
block/blk-mq.c +29 -3
@@ -828,6 +828,8 @@ static void blk_complete_request(struct request *req) bio = next; }while (bio); +blk_zone_write_complete_request (req); +/* * Reset counters so that the request stacking driver * can find how many bytes remain in the request
@@ -939,6 +941,7 @@ bool blk_update_request(struct request *req, blk_status_t error,* completely done */ if (!req->bio) { +blk_zone_write_complete_request (req);/* * Reset counters so that the request stacking driver * can find how many bytes remain in the request
@@ -2963,15 +2966,30 @@ void blk_mq_submit_bio(struct bio *bio)struct request *rq;blk_status_t ret; +/* +* If the plug has a cached request for this queue, try use it. +*/ + rq =blk_mq_peek_cached_request (plug, q, bio->bi_opf); + +/* +* A BIO that was released from a zone write plug has already been +* through the preparation in this function, already holds a reference +* on the queue usage counter, and is the only write BIO in-flight for +* the target zone. Go straight to preparing a request for it. +*/ +if (bio_zone_write_plugging (bio)) { + nr_segs = bio->__bi_nr_segments; +if (rq) +blk_queue_exit (q); + goto new_request; + } + bio =blk_queue_bounce (bio, q);/* -* If the plug has a cached request for this queue, try use it. -* * The cached request already holds a q_usage_counter reference and we * don't have to acquire a new one if we use it. */ - rq =blk_mq_peek_cached_request (plug, q, bio->bi_opf);if (!rq) {if (unlikely (bio_queue_enter (bio)))return ;
@@ -2988,6 +3006,10 @@ void blk_mq_submit_bio(struct bio *bio)if (blk_mq_attempt_bio_merge (q, bio, nr_segs)) goto queue_exit; +if (blk_queue_is_zoned (q) &&blk_zone_write_plug_bio (bio, nr_segs)) + goto queue_exit; + +new_request:if (!rq) { rq =blk_mq_get_new_requests (q, plug, bio, nr_segs);if (unlikely (!rq))
@@ -3006,6 +3028,7 @@ void blk_mq_submit_bio(struct bio *bio)if (ret !=BLK_STS_OK ) { bio->bi_status = ret;bio_endio (bio); +blk_zone_write_complete_request (rq);blk_mq_free_request (rq);return ; }
@@ -3013,6 +3036,9 @@ void blk_mq_submit_bio(struct bio *bio)if (op_is_flush (bio->bi_opf) &&blk_insert_flush (rq))return ; +if (bio_zone_write_plugging (bio)) +blk_zone_write_plug_attempt_merge (rq); +if (plug) {blk_add_rq_to_plug (plug, rq);return ;
block/blk-zoned.c +1087 -3
1,151 lines not on this page · raw message
block/blk.h +44 -3
73 lines not on this page · raw message
block/genhd.c +2 -1
@@ -1182,7 +1182,7 @@ static void disk_release(struct device *dev)disk_release_events (disk);kfree (disk->random); -disk_free_zone_bitmaps (disk); +disk_free_zone_resources (disk);xa_destroy (&disk->part_tbl); disk->queue->disk =NULL ;
@@ -1364,6 +1364,7 @@ struct gendisk *__alloc_disk_node(struct request_queue *q, int node_id,if (blkcg_init_disk (disk)) goto out_erase_part0; +disk_init_zone_resources (disk);rand_initialize_disk (disk);disk_to_dev (disk)->class = &block_class;disk_to_dev (disk)->type = &disk_type;
include/linux/blk-mq.h +2 -0
@@ -56,6 +56,8 @@ typedef __u32 __bitwise req_flags_t;#define RQF_SPECIAL_PAYLOAD ((__force req_flags_t)(1 << 18))/* The per-zone write lock is held for this request */ #define RQF_ZONE_WRITE_LOCKED ((__force req_flags_t)(1 << 19)) +/* The request completion needs to be signaled to zone write pluging. */ +#define RQF_ZONE_WRITE_PLUGGING ((__force req_flags_t)(1 << 20))/* ->timeout has been called, don't expire again */ #define RQF_TIMED_OUT ((__force req_flags_t)(1 << 21))#define RQF_RESV ((__force req_flags_t)(1 << 23))
include/linux/blk_types.h +7 -1
@@ -234,7 +234,12 @@ struct bio {struct bvec_iter bi_iter; -blk_qc_t bi_cookie; +union { +/* for polled bios: */ +blk_qc_t bi_cookie; +/* for plugged zoned writes only: */ +unsigned int __bi_nr_segments; + };bio_end_io_t *bi_end_io;void *bi_private;#ifdef CONFIG_BLK_CGROUP
@@ -305,6 +310,7 @@ enum {BIO_QOS_MERGED ,/* but went through rq_qos merge path */ BIO_REMAPPED ,BIO_ZONE_WRITE_LOCKED ,/* Owns a zoned device zone write lock */ +BIO_ZONE_WRITE_PLUGGING ,/* bio handled through zone write plugging */ BIO_FLAG_LAST };
include/linux/blkdev.h +12 -0
@@ -194,6 +194,12 @@ struct gendisk {unsigned int zone_capacity;unsigned long *conv_zones_bitmap;unsigned long *seq_zones_wlock; +unsigned int zone_wplugs_hash_bits; +spinlock_t zone_wplugs_lock; +struct mempool_s *zone_wplugs_pool; +struct hlist_head *zone_wplugs_hash; +struct list_head zone_wplugs_err_list; +struct work_struct zone_wplugs_work;#endif /* CONFIG_BLK_DEV_ZONED */ #if IS_ENABLED (CONFIG_CDROM )
@@ -663,6 +669,7 @@ static inline unsigned int bdev_max_active_zones(struct block_device *bdev) return bdev->bd_disk->queue->limits.max_active_zones; } +bool blk_zone_write_plug_bio (struct bio *bio,unsigned int nr_segs);#else /* CONFIG_BLK_DEV_ZONED */ static inline unsigned int bdev_nr_zones (struct block_device *bdev) {
@@ -690,6 +697,11 @@ static inline unsigned int bdev_max_active_zones(struct block_device *bdev) {return 0 ; } +static inline bool blk_zone_write_plug_bio (struct bio *bio, +unsigned int nr_segs) +{ +return false; +}#endif /* CONFIG_BLK_DEV_ZONED */ static inline unsigned int blk_queue_depth (struct request_queue *q)
Signature · 1 line
2.44.0
Re: [PATCH v5 07/28] block: Introduce zone write plugging
Looks good:
Re: [PATCH v5 07/28] block: Introduce zone write plugging
In reply to Damien Le MoalQuoted 141 lines · Damien Le Moal
Zone write plugging implements a per-zone "plug" for write operations to control the submission and execution order of write operations to sequential write required zones of a zoned block device. Per-zone plugging guarantees that at any time there is at most only one write request per zone being executed. This mechanism is intended to replace zone write locking which implements a similar per-zone write throttling at the scheduler level, but is implemented only by mq-deadline.
Unlike zone write locking which operates on requests, zone write plugging operates on BIOs. A zone write plug is simply a BIO list that is atomically manipulated using a spinlock and a kblockd submission work. A write BIO to a zone is "plugged" to delay its execution if a write BIO for the same zone was already issued, that is, if a write request for the same zone is being executed. The next plugged BIO is unplugged and issued once the write request completes.
This mechanism allows to:
- Untangle zone write ordering from block IO schedulers. This allows removing the restriction on using mq-deadline for writing to zoned block devices. Any block IO scheduler, including "none" can be used.
- Zone write plugging operates on BIOs instead of requests. Plugged BIOs waiting for execution thus do not hold scheduling tags and thus are not preventing other BIOs from executing (reads or writes to other zones). Depending on the workload, this can significantly improve the device use (higher queue depth operation) and performance.
- Both blk-mq (request based) zoned devices and BIO-based zoned devices (e.g. device mapper) can use zone write plugging. It is mandatory for the former but optional for the latter. BIO-based drivers can use zone write plugging to implement write ordering guarantees, or the drivers can implement their own if needed.
- The code is less invasive in the block layer and is mostly limited toblk-zoned.cwith some small changes inblk-mq.c,blk-merge.candbio.c.Zone write plugging is implemented using struct
blk_zone_wplug. This structure includes a spinlock, a BIO list and a work structure to handle the submission of plugged BIOs. Zone write plugs structures are managed using a per-disk hash table.Plugging of zone write BIOs is done using the function
blk_zone_write_plug_bio()which returns false if a BIO execution does not need to be delayed and true otherwise. This function is called fromblk_mq_submit_bio()after a BIO is split to avoid large BIOs spanning multiple zones which would cause mishandling of zone write plugs. This ichange enables by default zone write plugging for any mq request-based block device. BIO-based device drivers can also use zone write plugging by expliclty callingblk_zone_write_plug_bio()in their ->submit_bio method. For such devices, the driver must ensure that a BIO passed toblk_zone_write_plug_bio()is already split and not straddling zone boundaries.Only write and write zeroes BIOs are plugged. Zone write plugging does not introduce any significant overhead for other operations. A BIO that is being handled through zone write plugging is flagged using the new BIO flag
BIO_ZONE_WRITE_PLUGGING. A request handling a BIO flagged with this new flag is flagged with the newRQF_ZONE_WRITE_PLUGGINGflag. The completion of BIOs and requests flagged trigger respectively calls to the functionsblk_zone_write_bio_endio()andblk_zone_write_complete_request(). The latter function is used to trigger submission of the next plugged BIO using the zone plug work.blk_zone_write_bio_endio()does the same for BIO-based devices. This ensures that at any time, at most one request (blk-mq devices) or one BIO (BIO-based devices) is being executed for any zone. The handling of zone write plugs using a per-zone plug spinlock maximizes parallelism and device usage by allowing multiple zones to be writen simultaneously without lock contention.Zone write plugging ignores flush BIOs without data. Hovever, any flush BIO that has data is always plugged so that the write part of the flush sequence is serialized with other regular writes.
Given that any BIO handled through zone write plugging will be the only BIO in flight for the target zone when it is executed, the unplugging and submission of a BIO will have no chance of successfully merging with plugged requests or requests in the scheduler. To overcome this potential performance degradation,
blk_mq_submit_bio()calls the functionblk_zone_write_plug_attempt_merge()to try to merge other plugged BIOs with the one just unplugged and submitted. Successful merging is signaled usingblk_zone_write_plug_bio_merged(), called frombio_attempt_back_merge(). Furthermore, to avoid recalculating the number of segments of plugged BIOs to attempt merging, the number of segments of a plugged BIO is saved using the new struct bio field__bi_nr_segments. To avoid growing the size of struct bio, this field is added as a union with thebio_cookiefield. This is safe to do as polling is always disabled for plugged BIOs.When BIOs are plugged in a zone write plug, the device request queue usage counter is always incremented. This reference is kept and reused for blk-mq devices when the plugged BIO is unplugged and submitted again using
submit_bio_noacct_nocheck(). For this case, the unplugged BIO is already flagged withBIO_ZONE_WRITE_PLUGGINGandblk_mq_submit_bio()proceeds directly to allocating a new request for the BIO, re-using the usage reference count taken when the BIO was plugged. This extra reference count is dropped inblk_zone_write_plug_attempt_merge()for any plugged BIO that is successfully merged. Given that BIO-based devices will not take this path, the extra reference is dropped after a plugged BIO is unplugged and submitted.Zone write plugs are dynamically allocated and managed using a hash table (an array of struct
hlist_head) with RCU protection. A zone write plug is allocated when a write BIO is received for the zone and not freed until the zone is fully written, reset or finished. To detect when a zone write plug can be freed, the write state of each zone is tracked using a write pointer offset which corresponds to the offset of a zone write pointer relative to the zone start. Write operations always increment this write pointer offset. Zone reset operations set it to 0 and zone finish operations set it to the zone size.If a write error happens, the
wp_offsetvalue of a zone write plug may become incorrect and out of sync with the device managed write pointer. This is handled using the zone write plug flagBLK_ZONE_WPLUG_ERROR. The functionblk_zone_wplug_handle_error()is called from the new disk zone write plug work when this flag is set. This function executes a report zone to update the zone write pointer offset to the current value as indicated by the device. The disk zone write plug work is scheduled whenever a BIO flagged withBIO_ZONE_WRITE_PLUGGINGcompletes with an error or whenbio_zone_wplug_prepare_bio()detects an unaligned write. Once scheduled, the disk zone write plugs work keeps running until all zone errors are handled.To match the new data structures used for zoned disks, the function
disk_free_zone_bitmaps()is renamed to the more genericdisk_free_zone_resources(). The functiondisk_init_zone_resources()is also introduced to initialize zone write plugs resources when a gendisk is allocated.In order to guarantee that the user can simultaneously write up to a number of zones equal to a device max active zone limit or max open zone limit, zone write plugs are allocated using a mempool sized to the maximum of these 2 device limits. For a device that does not have active and open zone limits, 128 is used as the default mempool size.
If a change to the device active and open zone limits is detected, the disk mempool is resized when
blk_revalidate_disk_zones()is executed.This commit contains contributions from Christoph Hellwig <[email protected]>.
Signed-off-by Damien Le Moal <[email protected]>
Quoted 12 lines
--- block/bio.c | 6 + block/blk-merge.c | 11 + block/blk-mq.c | 32 +- block/blk-zoned.c | 1090 ++++++++++++++++++++++++++++++++++++- block/blk.h | 47 +- block/genhd.c | 3 +- include/linux/blk-mq.h | 2 + include/linux/blk_types.h | 8 +- include/linux/blkdev.h | 12 + 9 files changed, 1200 insertions(+), 11 deletions(-)
Cheers,
Hannes
Signature · 4 lines
Dr. Hannes Reinecke Kernel Storage Architect [email protected] +49 911 74053 688 SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
Re: [PATCH v5 07/28] block: Introduce zone write plugging
In reply to Damien Le MoalQuoted 5 lines · Damien Le Moal
block/bio.c +0
-0
block/bio.c void bio_endio(struct bio *bio) · line 1585
Quoted 10 lines
block/bio.c +6
-0@@ -1576,6 +1576,12 @@ void bio_endio(struct bio *bio)if (!bio_integrity_endio (bio))return ; +/* +* For write BIOs to zoned devices, signal the completion of the BIO so +* that the next write BIO can be submitted by zone write plugging. +*/ +blk_zone_write_bio_endio (bio); +rq_qos_done_bio (bio);
The function name "blk_zone_write_bio_endio()" is misleading since that
function is called for all bio operation types and not only for zoned
write bios. How about renaming blk_zone_write_bio_endio() into blk_zone_bio_endio()? If that function is renamed the above comment is no longer necessary in bio_endio() and can be moved to above theblk_zone_bio_endio() definition.
block/bio.c static enum bio_merge_status bio_attempt_front_merge(struct request *req, · line 1010
Quoted 5 lines
block/bio.c +4
-0@@ -1003,6 +1007,13 @@ static enum bio_merge_status bio_attempt_front_merge(struct request *req, +/* +* A front merge for zone writes can happen only if the user submitted +* writes out of order. Do not attempt this to let the write fail. +*/
"zone writes" -> "zoned writes"?
block/blk-mq.c static void blk_complete_request(struct request *req) · line 831
Quoted 9 lines
block/blk-mq.c +1
-0@@ -828,6 +828,8 @@ static void blk_complete_request(struct request *req) bio = next; }while (bio); +blk_zone_write_complete_request (req);
Same comment here as above: the function name blk_zone_write_complete_request() is misleading since that function is called for all request types and not only for zoned writes. Please
rename blk_zone_write_complete_request() intoblk_zone_complete_request().
block/blk-mq.c void blk_mq_submit_bio(struct bio *bio) · line 2971
Quoted 3 lines
block/blk-mq.c +3
-0+/* +* If the plug has a cached request for this queue, try use it. +*/
try use it -> try to use it (I know this comes from upstream code).
block/blk-mq.c void blk_mq_submit_bio(struct bio *bio) · line 3010
Quoted 2 lines
block/blk-mq.c +2
-0+if (blk_queue_is_zoned (q) &&blk_zone_write_plug_bio (bio, nr_segs)) + goto queue_exit;
The order of words in the blk_zone_write_plug_bio() function name seems unusual to me. How about renaming that function into blk_zone_plug_write_bio()?
Quoted 2 lines
+/*
+ * Zone write plug flags bits:
Zone write -> zoned write
Quoted 1 line
+ * -
BLK_ZONE_WPLUG_PLUGGED: Indicate that the zone write plug is plugged,
Indicate -> Indicates
Quoted 15 lines
block/blk-mq.c +15
-0+static bool disk_insert_zone_wplug (struct gendisk *disk, +struct blk_zone_wplug *zwplug) +{ +struct blk_zone_wplug *zwplg; +unsigned long flags; +unsigned int idx = +hash_32 (zwplug->zone_no, disk->zone_wplugs_hash_bits); + +/* +* Add the new zone write plug to the hash table, but carefully as we +* are racing with other submission context, so we may already have a +* zone write plug for the same zone. +*/ +spin_lock_irqsave (&disk->zone_wplugs_lock, flags); +hlist_for_each_entry_rcu (zwplg, &disk->zone_wplugs_hash[idx], node) {
block/blk-mq.c
Quoted 10 lines
block/blk-mq.c +10
-0+if (zwplg->zone_no == zwplug->zone_no) { +spin_unlock_irqrestore (&disk->zone_wplugs_lock, flags); +return false; + } + } +hlist_add_head_rcu (&zwplug->node, &disk->zone_wplugs_hash[idx]); +spin_unlock_irqrestore (&disk->zone_wplugs_lock, flags); + +return true; +}
The above code can be made easier to read and more compact by using guard(spinlock_irqsave)(...) instead of spin_lock_irqsave() + spin_unlock_irqrestore().
Quoted 11 lines
block/blk-mq.c +11
-0+static struct blk_zone_wplug *disk_get_zone_wplug (struct gendisk *disk, +sector_t sector) +{ +unsigned int zno =disk_zone_no (disk, sector); +unsigned int idx =hash_32 (zno, disk->zone_wplugs_hash_bits); +struct blk_zone_wplug *zwplug; + +rcu_read_lock (); + +hlist_for_each_entry_rcu (zwplug, &disk->zone_wplugs_hash[idx], node) { + if (zwplug->zone_no == zno &&
block/blk-mq.c
Quoted 10 lines
block/blk-mq.c +10
-0+ atomic_inc_not_zero(&zwplug->ref)) { +rcu_read_unlock (); +return zwplug; + } + } + +rcu_read_unlock (); + +return NULL ; +}
The above code can also be made more compact by using guard(rcu)() instead of rcu_read_lock() + rcu_read_unlock().
Quoted 5 lines
+/*
+ * Get a reference on the write plug for the zone containing @sector.
+ * If the plug does not exist, it is allocated and hashed.
+ * Return a pointer to the zone write plug with the plug spinlock held.
+ */
Holding a spinlock upon return is not my favorite approach. Has the following alternative been considered?
- Remove the spinlock flags argument from this function and instead add two other arguments: prev_plug_flags and new_plug_flags.
- If a zone plug is found or allocated, copy the existing zone plug flags into *prev_plug_flags and set the zone plug flags that have been passed in new_plug_flags (logical or).
- From blk_revalidate_zone_cb(), pass 0 as the new_plug_flags argument.
- From blk_zone_wplug_handle_write, pass BLK_ZONE_WPLUG_PLUGGED as the new_plug_flags argument.
Thanks,
Bart.
Damien Le Moal
maintainerRe: [PATCH v5 07/28] block: Introduce zone write plugging
Quoted 9 lines · Bart Van Assche
Quoted 8 lines · Damien Le Moal
block/bio.c +0
-0@@ -1576,6 +1576,12 @@ void bio_endio(struct bio *bio)if (!bio_integrity_endio (bio))return ;
Quoted 14 lines
block/bio.c void bio_endio(struct bio *bio) · line 1585
Quoted 7 lines
block/bio.c +6
-0+/* +* For write BIOs to zoned devices, signal the completion of the BIO so +* that the next write BIO can be submitted by zone write plugging. +*/ +blk_zone_write_bio_endio (bio); +rq_qos_done_bio (bio);The function name "
blk_zone_write_bio_endio()" is misleading since that function is called for all bio operation types and not only for zoned write bios. How about renamingblk_zone_write_bio_endio()intoblk_zone_bio_endio()? If that function is renamed the above comment is no longer necessary inbio_endio()and can be moved to above theblk_zone_bio_endio()definition.
OK.
Quoted 8 lines
block/bio.c static enum bio_merge_status bio_attempt_front_merge(struct request *req, · line 1010
Quoted 6 lines
block/bio.c +4
-0@@ -1003,6 +1007,13 @@ static enum bio_merge_status bio_attempt_front_merge(struct request *req, +/* +* A front merge for zone writes can happen only if the user submitted +* writes out of order. Do not attempt this to let the write fail. +*/ "zone writes" -> "zoned writes"?
Well, "writes to zones of a zoned block device" would be the correct way to describe this. The shortcut is "zone writes" but can be "zoned write" too.
Quoted 4 lines
block/bio.c static enum bio_merge_status bio_attempt_front_merge(struct request *req, · line 1012
Quoted 2 lines
block/bio.c +2
-0+/* +* Zone write plug flags bits: Zone write -> zoned write
Nope. This is flags for zone write plugs (as in struct blk_zone_wplug). Not talking about flags for writes to zones.
Quoted 21 lines
Quoted 21 lines
block/bio.c +21
-0+static bool disk_insert_zone_wplug (struct gendisk *disk, +struct blk_zone_wplug *zwplug) +{ +struct blk_zone_wplug *zwplg; +unsigned long flags; +unsigned int idx = +hash_32 (zwplug->zone_no, disk->zone_wplugs_hash_bits); + +/* +* Add the new zone write plug to the hash table, but carefully as we +* are racing with other submission context, so we may already have a +* zone write plug for the same zone. +*/ +spin_lock_irqsave (&disk->zone_wplugs_lock, flags); +hlist_for_each_entry_rcu (zwplg, &disk->zone_wplugs_hash[idx], node) { +if (zwplg->zone_no == zwplug->zone_no) { +spin_unlock_irqrestore (&disk->zone_wplugs_lock, flags); +return false; + } + } +hlist_add_head_rcu (&zwplug->node, &disk->zone_wplugs_hash[idx]);
Quoted 8 lines
block/bio.c
Quoted 4 lines
block/bio.c +4
-0+spin_unlock_irqrestore (&disk->zone_wplugs_lock, flags); + +return true; +}The above code can be made easier to read and more compact by using guard(spinlock_irqsave)(...) instead of
spin_lock_irqsave()+spin_unlock_irqrestore().
I am not familiar with this annotation and I do not see it used in the block layer. So I would rather not do that to be clear about locking/unlocking. Such change can come as cleanups later.
Quoted 17 lines
Quoted 17 lines
block/bio.c +16
-0+static struct blk_zone_wplug *disk_get_zone_wplug (struct gendisk *disk, +sector_t sector) +{ +unsigned int zno =disk_zone_no (disk, sector); +unsigned int idx =hash_32 (zno, disk->zone_wplugs_hash_bits); +struct blk_zone_wplug *zwplug; + +rcu_read_lock (); + +hlist_for_each_entry_rcu (zwplug, &disk->zone_wplugs_hash[idx], node) { +if (zwplug->zone_no == zno && +atomic_inc_not_zero (&zwplug->ref)) { +rcu_read_unlock (); +return zwplug; + } + }
Quoted 8 lines
block/bio.c
Quoted 5 lines
block/bio.c +5
-0+ +rcu_read_unlock (); + +return NULL ; +}The above code can also be made more compact by using guard(rcu)() instead of
rcu_read_lock()+rcu_read_unlock().
Same comment as above.
Quoted 6 lines
Quoted 5 lines
+/*
+ * Get a reference on the write plug for the zone containing @sector.
+ * If the plug does not exist, it is allocated and hashed.
+ * Return a pointer to the zone write plug with the plug spinlock held.
+ */
Quoted 10 lines
Holding a spinlock upon return is not my favorite approach. Has the following alternative been considered?
- Remove the spinlock flags argument from this function and instead add two other arguments:prev_plug_flagsandnew_plug_flags.
- If a zone plug is found or allocated, copy the existing zone plug flags into*prev_plug_flagsand set the zone plug flags that have been passed innew_plug_flags(logical or).
- Fromblk_revalidate_zone_cb(), pass 0 as thenew_plug_flagsargument.
- Fromblk_zone_wplug_handle_write, passBLK_ZONE_WPLUG_PLUGGEDas thenew_plug_flagsargument.
I would rather not do this because this is a lot more complicated on the caller sites as all callers would need to check for the UNHASHED case and potentially retry again. As it is, zone write plugging is already not trivial, so I am trying to keep things as simple as I can for now ? The function name makes it *very* clear that it takes the plug spinlock, and that avoids needing the caller to worry about the state of the plug they will get.
Quoted 4 lines
Thanks,
Bart.
Signature · 2 lines
Damien Le Moal Western Digital Research
Damien Le Moal
maintainerRe: [PATCH v5 07/28] block: Introduce zone write plugging
In reply to Bart Van AsscheQuoted 22 lines · Bart Van Assche
block/blk-mq.c static void blk_complete_request(struct request *req) · line 831
Quoted 9 lines
block/blk-mq.c +1
-0@@ -828,6 +828,8 @@ static void blk_complete_request(struct request *req) bio = next; }while (bio); +blk_zone_write_complete_request (req);Same comment here as above: the function name
blk_zone_write_complete_request()is misleading since that function is called for all request types and not only for zoned writes. Please renameblk_zone_write_complete_request()intoblk_zone_complete_request().block/blk-mq.c void blk_mq_submit_bio(struct bio *bio) · line 2971
Quoted 3 lines
block/blk-mq.c +3
-0+/* +* If the plug has a cached request for this queue, try use it. +*/ try use it -> try to use it (I know this comes from upstream code).
Quoted 6 lines
block/blk-mq.c void blk_mq_submit_bio(struct bio *bio) · line 3010
Quoted 2 lines
block/blk-mq.c +2
-0+if (blk_queue_is_zoned (q) &&blk_zone_write_plug_bio (bio, nr_segs)) + goto queue_exit;The order of words in the
blk_zone_write_plug_bio()function name seems unusual to me. How about renaming that function intoblk_zone_plug_write_bio()?
To be consistent with your other renaming ideas, what about "blk_zone_plug_bio()" ?
Signature · 2 lines
Damien Le Moal Western Digital Research
Re: [PATCH v5 07/28] block: Introduce zone write plugging
Quoted 6 lines · Damien Le Moal
Quoted 3 lines · Bart Van Assche
The order of words in the
blk_zone_write_plug_bio()function name seems unusual to me. How about renaming that function intoblk_zone_plug_write_bio()?To be consistent with your other renaming ideas, what about "
blk_zone_plug_bio()" ?
That name is fine with me. I hadn't suggested this yet because I wasn't sure whether that name would be correct.
Thanks,
Bart.
Damien Le Moal
maintainerRe: [PATCH v5 07/28] block: Introduce zone write plugging
Quoted 10 lines · Bart Van Assche
Quoted 6 lines · Damien Le Moal
Quoted 3 lines · Bart Van Assche
The order of words in the
blk_zone_write_plug_bio()function name seems unusual to me. How about renaming that function intoblk_zone_plug_write_bio()?To be consistent with your other renaming ideas, what about "
blk_zone_plug_bio()" ?That name is fine with me. I hadn't suggested this yet because I wasn't sure whether that name would be correct.
That function is also called for all BIOs to a zoned block device. So the change will make things consistent.
Quoted 5 lines
Thanks,
Bart.
Signature · 2 lines
Damien Le Moal Western Digital Research
Damien Le Moal
maintainerPATCH v5 8/28 block: Fake max open zones limit when there is no limit
In reply to Damien Le MoalFor a zoned block device that has no limit on the number of open zones and no limit on the number of active zones, the zone write plug mempool is created with a size of 128 zone write plugs. For such case, set the device max_open_zones queue limit to this value to indicate to the user the potential performance penalty that may happen when writing simultaneously to more zones than the mempool size.
--- block/blk-zoned.c | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-)
block/blk-zoned.c +35 -6
@@ -1502,6 +1502,38 @@ struct blk_revalidate_zone_args {sector_t sector; }; +/* +* Update the disk zone resources information and device queue limits. +* The disk queue is frozen when this is executed. +*/ +static int disk_update_zone_resources (struct gendisk *disk, +struct blk_revalidate_zone_args *args) +{ +struct request_queue *q = disk->queue; +struct queue_limits lim; + + disk->nr_zones = args->nr_zones; + disk->zone_capacity = args->zone_capacity; +swap (disk->seq_zones_wlock, args->seq_zones_wlock); +swap (disk->conv_zones_bitmap, args->conv_zones_bitmap); + +/* +* If the device has no limit on the maximum number of open and active +* zones, set its max open zone limit to the mempool size to indicate +* to the user that there is a potential performance impact due to +* dynamic zone write plug allocation when simultaneously writing to +* more zones than the size of the mempool. +*/ +if (disk->zone_wplugs_pool) { + lim =queue_limits_start_update (q); +if (!lim.max_open_zones && !lim.max_active_zones) + lim.max_open_zones = disk->zone_wplugs_pool->min_nr; +return queue_limits_commit_update (q, &lim); + } + +return 0 ; +} +/* * Helper function to check the validity of zones of a zoned block device. */
@@ -1702,17 +1734,14 @@ int blk_revalidate_disk_zones(struct gendisk *disk,*/ blk_mq_freeze_queue (q);if (ret >0 ) { - disk->nr_zones = args.nr_zones; - disk->zone_capacity = args.zone_capacity; -swap (disk->seq_zones_wlock, args.seq_zones_wlock); -swap (disk->conv_zones_bitmap, args.conv_zones_bitmap); + ret =disk_update_zone_resources (disk, &args);if (update_driver_data)update_driver_data (disk); - ret =0 ; }else {pr_warn ("%s: failed to revalidate zones\n" , disk->disk_name); -disk_free_zone_resources (disk); } +if (ret) +disk_free_zone_resources (disk);blk_mq_unfreeze_queue (q);kfree (args.seq_zones_wlock);
Signature · 1 line
2.44.0
Re: [PATCH v5 08/28] block: Fake max open zones limit when there is no limit
Looks good:
Re: [PATCH v5 08/28] block: Fake max open zones limit when there is no limit
In reply to Damien Le MoalQuoted 7 lines · Damien Le Moal
For a zoned block device that has no limit on the number of open zones and no limit on the number of active zones, the zone write plug mempool is created with a size of 128 zone write plugs. For such case, set the device
max_open_zonesqueue limit to this value to indicate to the user the potential performance penalty that may happen when writing simultaneously to more zones than the mempool size.
Quoted 5 lines
Signed-off-by Damien Le Moal <[email protected]>--- block/blk-zoned.c | 41 +++++++++++++++++++++++++++++++++++------ 1 file changed, 35 insertions(+), 6 deletions(-)
Cheers,
Hannes
Signature · 4 lines
Dr. Hannes Reinecke Kernel Storage Architect [email protected] +49 911 74053 688 SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
Re: [PATCH v5 08/28] block: Fake max open zones limit when there is no limit
In reply to Damien Le MoalQuoted 6 lines · Damien Le Moal
For a zoned block device that has no limit on the number of open zones and no limit on the number of active zones, the zone write plug mempool is created with a size of 128 zone write plugs. For such case, set the device
max_open_zonesqueue limit to this value to indicate to the user the potential performance penalty that may happen when writing simultaneously to more zones than the mempool size.
Damien Le Moal
maintainerPATCH v5 9/28 block: Allow zero value of max_zone_append_sectors queue limit
In reply to Damien Le MoalIn preparation for adding a generic zone append emulation using zone write plugging, allow device drivers supporting zoned block device to set a the max_zone_append_sectors queue limit of a device to 0 to indicate the lack of native support for zone append operations and that the block layer should emulate these operations using regular write operations.
blk_queue_max_zone_append_sectors() is modified to allow passing 0 as the max_zone_append_sectors argument. The function queue_max_zone_append_sectors() is also modified to ensure that the minimum of the max_hw_sectors and chunk_sectors limit is used whenever the max_zone_append_sectors limit is 0. This minimum is consistent with the value set for the max_zone_append_sectors limit by the function blk_validate_zoned_limits() when limits for a queue are validated.
The helper functions queue_emulates_zone_append() and bdev_emulates_zone_append() are added to test if a queue (or block device) emulates zone append operations.
In order for blk_revalidate_disk_zones() to accept zoned block devices relying on zone append emulation, the direct check to the max_zone_append_sectors queue limit of the disk is replaced by a check using the value returned by queue_max_zone_append_sectors(). Similarly, queue_zone_append_max_show() is modified to use the same accessor so that the sysfs attribute advertizes the non-zero limit that will be used, regardless if it is for native or emulated commands.
For stacking drivers, a top device should not need to care if the underlying devices have native or emulated zone append operations. blk_stack_limits() is thus modified to set the top device max_zone_append_sectors limit using the new accessor queue_limits_max_zone_append_sectors(). queue_max_zone_append_sectors() is modified to use this function as well. Stacking drivers that require zone append emulation, e.g. dm-crypt, can still request this feature by calling blk_queue_max_zone_append_sectors() with a 0 limit.
--- block/blk-core.c | 2 +- block/blk-settings.c | 30 +++++++++++++++++++----------- block/blk-sysfs.c | 2 +- block/blk-zoned.c | 2 +- include/linux/blkdev.h | 23 ++++++++++++++++++++--- 5 files changed, 42 insertions(+), 17 deletions(-)
block/blk-core.c +1 -1
@@ -602,7 +602,7 @@ static inline blk_status_t blk_check_zone_append(struct request_queue *q,return BLK_STS_IOERR ;/* Make sure the BIO is small enough and will not get split */ -if (nr_sectors > q->limits.max_zone_append_sectors) +if (nr_sectors >queue_max_zone_append_sectors (q))return BLK_STS_IOERR ; bio->bi_opf |=REQ_NOMERGE ;
block/blk-settings.c +19 -11
@@ -412,24 +412,32 @@ EXPORT_SYMBOL(blk_queue_max_write_zeroes_sectors);* blk_queue_max_zone_append_sectors - set max sectors for a single zone append * @q: the request queue for the device * @max_zone_append_sectors: maximum number of sectors to write per command +* +* Sets the maximum number of sectors allowed for zone append commands. If +* Specifying 0 for @max_zone_append_sectors indicates that the queue does +* not natively support zone append operations and that the block layer must +* emulate these operations using regular writes. **/ void blk_queue_max_zone_append_sectors (struct request_queue *q,unsigned int max_zone_append_sectors) { -unsigned int max_sectors; +unsigned int max_sectors =0 ;if (WARN_ON (!blk_queue_is_zoned (q)))return ; - max_sectors =min (q->limits.max_hw_sectors, max_zone_append_sectors); - max_sectors =min (q->limits.chunk_sectors, max_sectors); +if (max_zone_append_sectors) { + max_sectors =min (q->limits.max_hw_sectors, + max_zone_append_sectors); + max_sectors =min (q->limits.chunk_sectors, max_sectors); -/* -* Signal eventual driver bugs resulting in the max_zone_append sectors limit -* being 0 due to a 0 argument, the chunk_sectors limit (zone size) not set, -* or the max_hw_sectors limit not set. -*/ -WARN_ON (!max_sectors); +/* +* Signal eventual driver bugs resulting in the max_zone_append +* sectors limit being 0 due to the chunk_sectors limit (zone +* size) not set or the max_hw_sectors limit not set. +*/ +WARN_ON_ONCE (!max_sectors); + } q->limits.max_zone_append_sectors = max_sectors; }
@@ -756,8 +764,8 @@ int blk_stack_limits(struct queue_limits *t, struct queue_limits *b, t->max_dev_sectors =min_not_zero (t->max_dev_sectors, b->max_dev_sectors); t->max_write_zeroes_sectors =min (t->max_write_zeroes_sectors, b->max_write_zeroes_sectors); - t->max_zone_append_sectors =min (t->max_zone_append_sectors, - b->max_zone_append_sectors); + t->max_zone_append_sectors =min (queue_limits_max_zone_append_sectors (t), +queue_limits_max_zone_append_sectors (b)); t->bounce =max (t->bounce, b->bounce); t->seg_boundary_mask = min_not_zero(t->seg_boundary_mask,
block/blk-sysfs.c +1 -1
@@ -224,7 +224,7 @@ static ssize_t queue_zone_write_granularity_show(struct request_queue *q,static ssize_t queue_zone_append_max_show (struct request_queue *q,char *page) { -unsigned long long max_sectors = q->limits.max_zone_append_sectors; +unsigned long long max_sectors =queue_max_zone_append_sectors (q);return sprintf (page,"%llu\n" , max_sectors <<SECTOR_SHIFT ); }
block/blk-zoned.c +1 -1
@@ -1691,7 +1691,7 @@ int blk_revalidate_disk_zones(struct gendisk *disk,return -ENODEV ; } -if (!q->limits.max_zone_append_sectors) { +if (!queue_max_zone_append_sectors (q)) {pr_warn ("%s: Invalid 0 maximum zone append limit\n" , disk->disk_name);return -ENODEV ;
include/linux/blkdev.h +20 -3
@@ -1174,12 +1174,29 @@ static inline unsigned int queue_max_segment_size(const struct request_queue *q)return q->limits.max_segment_size; } -static inline unsigned int queue_max_zone_append_sectors (const struct request_queue *q) +static inline unsigned int queue_limits_max_zone_append_sectors (struct queue_limits *l) { +unsigned int max_sectors =min (l->chunk_sectors, l->max_hw_sectors); -const struct queue_limits *l = &q->limits; +return min_not_zero (l->max_zone_append_sectors, max_sectors); +} + +static inline unsigned int queue_max_zone_append_sectors (struct request_queue *q) +{ +if (!blk_queue_is_zoned (q)) +return 0 ; -return min (l->max_zone_append_sectors, l->max_sectors); +return queue_limits_max_zone_append_sectors (&q->limits); +} + +static inline bool queue_emulates_zone_append (struct request_queue *q) +{ +return blk_queue_is_zoned (q) && !q->limits.max_zone_append_sectors; +} + +static inline bool bdev_emulates_zone_append (struct block_device *bdev) +{ +return queue_emulates_zone_append (bdev_get_queue (bdev)); } static inline unsigned int
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 10/28 block: Implement zone append emulation
In reply to Damien Le MoalGiven that zone write plugging manages all writes to zones of a zoned block device and tracks the write pointer position of all zones that are not full nor empty, emulating zone append operations using regular writes can be implemented generically, without relying on the underlying device driver to implement such emulation. This is needed for devices that do not natively support the zone append command (e.g. SMR hard-disks).
A device may request zone append emulation by setting its max_zone_append_sectors queue limit to 0. For such device, the function blk_zone_wplug_prepare_bio() changes zone append BIOs into non-mergeable regular write BIOs. Modified zone append BIOs are flagged with the new BIO flag BIO_EMULATES_ZONE_APPEND. This flag is checked on completion of the BIO in blk_zone_write_plug_bio_endio() to restore the original REQ_OP_ZONE_APPEND operation code of the BIO.
The block layer internal inline helper function bio_is_zone_append() is added to test if a BIO is either a native zone append operation (REQ_OP_ZONE_APPEND operation code) or if it is flagged with BIO_EMULATES_ZONE_APPEND. Given that both native and emulated zone append BIO completion handling should be similar, The functions blk_update_request() and blk_zone_complete_request_bio() are modified to use bio_is_zone_append() to execute blk_zone_update_request_bio() for both native and emulated zone append operations.
This commit contains contributions from Christoph Hellwig <[email protected]>.
--- block/blk-mq.c | 3 +- block/blk-zoned.c | 69 +++++++++++++++++++++++++++++++-------- block/blk.h | 14 ++++++-- include/linux/blk_types.h | 1 + 4 files changed, 69 insertions(+), 18 deletions(-)
block/blk-mq.c +1 -2
@@ -906,8 +906,7 @@ bool blk_update_request(struct request *req, blk_status_t error,if (bio_bytes == bio->bi_iter.bi_size) { req->bio = bio->bi_next; - }else if (req_op (req) ==REQ_OP_ZONE_APPEND && - error ==BLK_STS_OK ) { + }else if (bio_is_zone_append (bio) && error ==BLK_STS_OK ) {/* * Partial zone append completions cannot be supported * as the BIO fragments may end up not being written
block/blk-zoned.c +55 -14
118 lines not on this page · raw message
block/blk.h +12 -2
@@ -421,6 +421,11 @@ static inline bool bio_zone_write_plugging(struct bio *bio) {return bio_flagged (bio,BIO_ZONE_WRITE_PLUGGING ); } +static inline bool bio_is_zone_append (struct bio *bio) +{ +return bio_op (bio) ==REQ_OP_ZONE_APPEND || +bio_flagged (bio,BIO_EMULATES_ZONE_APPEND ); +}void blk_zone_write_plug_bio_merged (struct bio *bio);void blk_zone_write_plug_attempt_merge (struct request *rq); static inline void blk_zone_update_request_bio(struct request *rq,
@@ -430,8 +435,9 @@ static inline void blk_zone_update_request_bio(struct request *rq,* For zone append requests, the request sector indicates the location * at which the BIO data was written. Return this value to the BIO * issuer through the BIO iter sector. -* For plugged zone writes, we need the original BIO sector so -* that blk_zone_write_plug_bio_endio() can lookup the zone write plug. +* For plugged zone writes, which include emulated zone append, we need +* the original BIO sector so that blk_zone_write_plug_bio_endio() can +* lookup the zone write plug. */ if (req_op (rq) ==REQ_OP_ZONE_APPEND ||bio_zone_write_plugging (bio)) bio->bi_iter.bi_sector = rq->__sector;
@@ -464,6 +470,10 @@ static inline bool bio_zone_write_plugging(struct bio *bio) {return false; } +static inline bool bio_is_zone_append (struct bio *bio) +{ +return false; +}static inline void blk_zone_write_plug_bio_merged (struct bio *bio) { }
include/linux/blk_types.h +1 -0
@@ -311,6 +311,7 @@ enum {BIO_REMAPPED ,BIO_ZONE_WRITE_LOCKED ,/* Owns a zoned device zone write lock */ BIO_ZONE_WRITE_PLUGGING ,/* bio handled through zone write plugging */ +BIO_EMULATES_ZONE_APPEND ,/* bio emulates a zone append operation */ BIO_FLAG_LAST };
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 11/28 block: Allow BIO-based drivers to use blk_revalidate_disk_zones()
In reply to Damien Le MoalIn preparation for allowing BIO based device drivers to use zone write plugging and its zone append emulation, allow these drivers to call blk_revalidate_disk_zones() so that all zone resources necessary to zone write plugging can be initialized.
To do so, remove the check in blk_revalidate_disk_zones() restricting the use of this function to mq request-based drivers to allow also BIO-based drivers to use it. This is safe to do as long as the BIO-based block device queue is already setup and usable, as it should, and can be safely frozen.
The helper function disk_need_zone_resources() is added to control the allocation and initialization of the zone write plug hash table and of the conventional zone bitmap only for mq devices and for BIO-based devices that require zone append emulation.
--- block/blk-zoned.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-)
block/blk-zoned.c +24 -6
@@ -1510,12 +1510,28 @@ void disk_free_zone_resources(struct gendisk *disk) disk->nr_zones =0 ; } +static inline bool disk_need_zone_resources (struct gendisk *disk) +{ +/* +* All mq zoned devices need zone resources so that the block layer +* can automatically handle write BIO plugging. BIO-based device drivers +* (e.g. DM devices) are normally responsible for handling zone write +* ordering and do not need zone resources, unless the driver requires +* zone append emulation. +*/ +return queue_is_mq (disk->queue) || +queue_emulates_zone_append (disk->queue); +} +static int disk_revalidate_zone_resources (struct gendisk *disk,unsigned int nr_zones) {struct queue_limits *lim = &disk->queue->limits;unsigned int pool_size; +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.
@@ -1633,6 +1649,9 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx, disk->disk_name);return -ENODEV ; } + +if (!disk_need_zone_resources (disk)) +break ;if (!args->conv_zones_bitmap) { args->conv_zones_bitmap =blk_alloc_zone_bitmap (q->node, args->nr_zones);
@@ -1664,10 +1683,11 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,/* * We need to track the write pointer of all zones that are not * empty nor full. So make sure we have a zone write plug for -* such zone. +* such zone if the device has a zone write plug hash table. */ wp_offset =blk_zone_wp_offset (zone); -if (wp_offset && wp_offset < zone_sectors) { +if (disk->zone_wplugs_hash && + wp_offset && wp_offset < zone_sectors) { zwplug =disk_get_and_lock_zone_wplug (disk, zone->start,GFP_NOIO , &flags);if (!zwplug)
@@ -1698,8 +1718,8 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,* be called within the disk ->revalidate method for blk-mq based drivers. * Before calling this function, the device driver must already have set the * device zone size (chunk_sector limit) and the max zone append limit. -* For BIO based drivers, this function cannot be used. BIO based device drivers -* only need to set disk->nr_zones so that the sysfs exposed value is correct. +* BIO based drivers can also use this function as long as the device queue +* can be safely frozen. * If the @update_driver_data callback function is not NULL, the callback is * executed with the device request queue frozen after all zones have been * checked.
@@ -1716,8 +1736,6 @@ int blk_revalidate_disk_zones(struct gendisk *disk,if (WARN_ON_ONCE (!blk_queue_is_zoned (q)))return -EIO ; -if (WARN_ON_ONCE (!queue_is_mq (q))) -return -EIO ;if (!capacity)return -ENODEV ;
Signature · 1 line
2.44.0
Re: [PATCH v5 11/28] block: Allow BIO-based drivers to use blk_revalidate_disk_zones()
Quoted 11 lines · Damien Le Moal
In preparation for allowing BIO based device drivers to use zone write plugging and its zone append emulation, allow these drivers to call
blk_revalidate_disk_zones()so that all zone resources necessary to zone write plugging can be initialized.To do so, remove the check in
blk_revalidate_disk_zones()restricting the use of this function to mq request-based drivers to allow also BIO-based drivers to use it. This is safe to do as long as the BIO-based block device queue is already setup and usable, as it should, and can be safely frozen.
Quoted 4 lines
The helper function
disk_need_zone_resources()is added to control the allocation and initialization of the zone write plug hash table and of the conventional zone bitmap only for mq devices and for BIO-based devices that require zone append emulation.
Damien Le Moal
maintainerPATCH v5 12/28 dm: Use the block layer zone append emulation
In reply to Damien Le MoalFor targets requiring zone append operation emulation with regular writes (e.g. dm-crypt), we can use the block layer emulation provided by zone write plugging. Remove DM implemented zone append emulation and enable the block layer one.
This is done by setting the max_zone_append_sectors limit of the mapped device queue to 0 for mapped devices that have a target table that cannot support native zone append operations (e.g. dm-crypt). Such mapped devices are flagged with the DMF_EMULATE_ZONE_APPEND flag. dm_split_and_process_bio() is modified to execute blk_zone_write_plug_bio() for such device to let the block layer transform zone append operations into regular writes. This is done after ensuring that the submitted BIO is split if it straddles zone boundaries. Both changes are implemented unsing the inline helpers dm_zone_write_plug_bio() and dm_zone_bio_needs_split() respectively.
dm_revalidate_zones() is also modified to use the block layer provided function blk_revalidate_disk_zones() so that all zone resources needed for zone append emulation are initialized by the block layer without DM core needing to do anything. Since the device table is not yet live when dm_revalidate_zones() is executed, enabling the use of blk_revalidate_disk_zones() requires adding a pointer to the device table in struct mapped_device. This avoids errors in dm_blk_report_zones() trying to get the table with dm_get_live_table(). The mapped device table pointer is set to the table passed as argument to dm_revalidate_zones() before calling blk_revalidate_disk_zones() and reset to NULL after this function returns to restore the live table handling for user call of report zones.
All the code related to zone append emulation is removed from dm-zone.c. This leads to simplifications of the functions __map_bio() and dm_zone_endio(). This later function now only needs to deal with completions of real zone append operations for targets that support it.
--- drivers/md/dm-core.h | 2 +- drivers/md/dm-zone.c | 476 ++++--------------------------------------- drivers/md/dm.c | 75 ++++--- drivers/md/dm.h | 4 +- 4 files changed, 97 insertions(+), 460 deletions(-)
drivers/md/dm-core.h +1 -1
@@ -140,7 +140,7 @@ struct mapped_device {#ifdef CONFIG_BLK_DEV_ZONED unsigned int nr_zones; -unsigned int *zwp_offset; +void *zone_revalidate_map;#endif #ifdef CONFIG_IMA
drivers/md/dm-zone.c +43 -433
538 lines not on this page · raw message
drivers/md/dm.c +52 -23
109 lines not on this page · raw message
drivers/md/dm.h +1 -3
@@ -104,14 +104,12 @@ int dm_setup_md_queue(struct mapped_device *md, struct dm_table *t);int dm_set_zones_restrictions (struct dm_table *t,struct request_queue *q);void dm_zone_endio (struct dm_io *io,struct bio *clone);#ifdef CONFIG_BLK_DEV_ZONED -void dm_cleanup_zoned_dev (struct mapped_device *md);int dm_blk_report_zones (struct gendisk *disk,sector_t sector,unsigned int nr_zones,report_zones_cb cb,void *data);bool dm_is_zone_write (struct mapped_device *md,struct bio *bio);int dm_zone_map_bio (struct dm_target_io *io);#else -static inline void dm_cleanup_zoned_dev (struct mapped_device *md) {} -#define dm_blk_report_zones NULL +#define dm_blk_report_zones NULLstatic inline bool dm_is_zone_write (struct mapped_device *md,struct bio *bio) {return false;
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 13/28 scsi: sd: Use the block layer zone append emulation
In reply to Damien Le MoalSet the request queue of a TYPE_ZBC device as needing zone append emulation by setting the device queue max_zone_append_sectors limit to
0. This enables the block layer generic implementation provided by zone write plugging. With this, the sd driver will never see a REQ_OP_ZONE_APPEND request and the zone append emulation code implemented in sd_zbc.c can be removed.
--- drivers/scsi/sd.c | 8 - drivers/scsi/sd.h | 19 --- drivers/scsi/sd_zbc.c | 335 ++---------------------------------------- 3 files changed, 10 insertions(+), 352 deletions(-)
drivers/scsi/sd.c +0 -8
@@ -1260,12 +1260,6 @@ static blk_status_t sd_setup_read_write_cmnd(struct scsi_cmnd *cmd) } } -if (req_op (rq) ==REQ_OP_ZONE_APPEND ) { - ret =sd_zbc_prepare_zone_append (cmd, &lba, nr_blocks); -if (ret) - goto fail; - } - fua = rq->cmd_flags &REQ_FUA ?0x8 :0 ; dix =scsi_prot_sg_count (cmd); dif =scsi_host_dif_capable (cmd->device->host, sdkp->protection_type);
@@ -1348,7 +1342,6 @@ static blk_status_t sd_init_command(struct scsi_cmnd *cmd)return sd_setup_flush_cmnd (cmd);case REQ_OP_READ :case REQ_OP_WRITE : -case REQ_OP_ZONE_APPEND :return sd_setup_read_write_cmnd (cmd);case REQ_OP_ZONE_RESET : return sd_zbc_setup_zone_mgmt_cmnd(cmd, ZO_RESET_WRITE_POINTER,
@@ -3979,7 +3972,6 @@ static void scsi_disk_release(struct device *dev)struct scsi_disk *sdkp =to_scsi_disk (dev);ida_free (&sd_index_ida, sdkp->index); -sd_zbc_free_zone_info (sdkp);put_device (&sdkp->device->sdev_gendev);free_opal_dev (sdkp->opal_dev);
drivers/scsi/sd.h +0 -19
@@ -104,12 +104,6 @@ struct scsi_disk {* between zone starting LBAs is constant. */ u32 zone_starting_lba_gran; -u32 *zones_wp_offset; -spinlock_t zones_wp_offset_lock; -u32 *rev_wp_offset; -struct mutex rev_mutex; -struct work_struct zone_wp_offset_work; -char *zone_wp_update_buf;#endif atomic_t openers;sector_t capacity;/* size in logical blocks */
@@ -245,7 +239,6 @@ static inline int sd_is_zoned(struct scsi_disk *sdkp)#ifdef CONFIG_BLK_DEV_ZONED -void sd_zbc_free_zone_info (struct scsi_disk *sdkp);int sd_zbc_read_zones (struct scsi_disk *sdkp,u8 buf[SD_BUF_SIZE ]);int sd_zbc_revalidate_zones (struct scsi_disk *sdkp); blk_status_t sd_zbc_setup_zone_mgmt_cmnd(struct scsi_cmnd *cmd,
@@ -255,13 +248,8 @@ unsigned int sd_zbc_complete(struct scsi_cmnd *cmd, unsigned int good_bytes,int sd_zbc_report_zones (struct gendisk *disk,sector_t sector,unsigned int nr_zones,report_zones_cb cb,void *data); -blk_status_t sd_zbc_prepare_zone_append (struct scsi_cmnd *cmd,sector_t *lba, -unsigned int nr_blocks); -#else /* CONFIG_BLK_DEV_ZONED */ -static inline void sd_zbc_free_zone_info (struct scsi_disk *sdkp) {} -static inline int sd_zbc_read_zones (struct scsi_disk *sdkp,u8 buf[SD_BUF_SIZE ]) {return 0 ;
@@ -285,13 +273,6 @@ static inline unsigned int sd_zbc_complete(struct scsi_cmnd *cmd, return good_bytes; } -static inline blk_status_t sd_zbc_prepare_zone_append (struct scsi_cmnd *cmd, -sector_t *lba, -unsigned int nr_blocks) -{ -return BLK_STS_TARGET ; -} -#define sd_zbc_report_zones NULL#endif /* CONFIG_BLK_DEV_ZONED */
drivers/scsi/sd_zbc.c +10 -325
416 lines not on this page · raw message
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 14/28 ublk_drv: Do not request ELEVATOR_F_ZBD_SEQ_WRITE elevator feature
In reply to Damien Le MoalWith zone write plugging enabled at the block layer level, any zone device can only ever see at most a single write operation per zone. There is thus no need to request a block scheduler with strick per-zone sequential write ordering control through the ELEVATOR_F_ZBD_SEQ_WRITE feature. Removing this allows using a zoned ublk device with any scheduler, including "none".
--- drivers/block/ublk_drv.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/block/ublk_drv.c +1 -2
@@ -249,8 +249,7 @@ static int ublk_dev_param_zoned_validate(const struct ublk_device *ub)static void ublk_dev_param_zoned_apply (struct ublk_device *ub) {blk_queue_flag_set (QUEUE_FLAG_ZONE_RESETALL , ub->ub_disk->queue); -blk_queue_required_elevator_features (ub->ub_disk->queue, -ELEVATOR_F_ZBD_SEQ_WRITE ); + ub->ub_disk->nr_zones =ublk_get_nr_zones (ub); }
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 15/28 null_blk: Do not request ELEVATOR_F_ZBD_SEQ_WRITE elevator feature
In reply to Damien Le MoalWith zone write plugging enabled at the block layer level, a zoned device can only ever see at most a single write operation per zone. There is thus no need to request a block scheduler with strick per-zone sequential write ordering control through the ELEVATOR_F_ZBD_SEQ_WRITE feature. Removing this allows using a zoned null_blk device with any scheduler, including "none".
--- drivers/block/null_blk/zoned.c | 1 - 1 file changed, 1 deletion(-)
drivers/block/null_blk/zoned.c +0 -1
@@ -165,7 +165,6 @@ int null_register_zoned_dev(struct nullb *nullb)struct request_queue *q = nullb->q;blk_queue_flag_set (QUEUE_FLAG_ZONE_RESETALL , q); -blk_queue_required_elevator_features (q,ELEVATOR_F_ZBD_SEQ_WRITE ); nullb->disk->nr_zones =bdev_nr_zones (nullb->disk->part0);return blk_revalidate_disk_zones (nullb->disk,NULL ); }
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 16/28 null_blk: Introduce zone_append_max_sectors attribute
In reply to Damien Le MoalAdd the zone_append_max_sectors configfs attribute and module parameter to allow configuring the maximum number of 512B sectors of zone append operations. This attribute is meaningful only for zoned null block devices.
If not specified, the default is unchanged and the zoned device max append sectors limit is set to the device max sectors limit. If a non 0 value is used for this attribute, which is the default, then native support for zone append operations is enabled. Setting a 0 value disables native zone append operations support to instead use the block layer emulation.
--- drivers/block/null_blk/main.c | 10 +++++++++- drivers/block/null_blk/null_blk.h | 1 + drivers/block/null_blk/zoned.c | 22 +++++++++++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-)
drivers/block/null_blk/main.c +9 -1
@@ -253,6 +253,11 @@ static unsigned int g_zone_max_active;module_param_named (zone_max_active, g_zone_max_active, uint,0444 );MODULE_PARM_DESC (zone_max_active,"Maximum number of active zones when block device is zoned. Default: 0 (no limit)" ); +static int g_zone_append_max_sectors =INT_MAX ; +module_param_named (zone_append_max_sectors, g_zone_append_max_sectors, int,0444 ); +MODULE_PARM_DESC (zone_append_max_sectors, +"Maximum size of a zone append command (in 512B sectors). Specify 0 for zone append emulation" ); +static struct nullb_device *null_alloc_dev (void );static void null_free_dev (struct nullb_device *dev);static void null_del_dev (struct nullb *nullb);
@@ -436,6 +441,7 @@ NULLB_DEVICE_ATTR(zone_capacity, ulong, NULL);NULLB_DEVICE_ATTR (zone_nr_conv, uint,NULL );NULLB_DEVICE_ATTR (zone_max_open, uint,NULL );NULLB_DEVICE_ATTR (zone_max_active, uint,NULL ); +NULLB_DEVICE_ATTR (zone_append_max_sectors, uint,NULL );NULLB_DEVICE_ATTR (virt_boundary, bool,NULL );NULLB_DEVICE_ATTR (no_sched, bool,NULL );NULLB_DEVICE_ATTR (shared_tags, bool,NULL );
@@ -580,6 +586,7 @@ static struct configfs_attribute *nullb_device_attrs[] = { &nullb_device_attr_zone_nr_conv, &nullb_device_attr_zone_max_open, &nullb_device_attr_zone_max_active, + &nullb_device_attr_zone_append_max_sectors, &nullb_device_attr_zone_readonly, &nullb_device_attr_zone_offline, &nullb_device_attr_virt_boundary,
@@ -671,7 +678,7 @@ static ssize_t memb_group_features_show(struct config_item *item, char *page)"shared_tags,size,submit_queues,use_per_node_hctx," "virt_boundary,zoned,zone_capacity,zone_max_active," "zone_max_open,zone_nr_conv,zone_offline,zone_readonly," - "zone_size\n"); + "zone_size,zone_append_max_sectors\n"); }CONFIGFS_ATTR_RO (memb_group_, features);
@@ -751,6 +758,7 @@ static struct nullb_device *null_alloc_dev(void) dev->zone_nr_conv = g_zone_nr_conv; dev->zone_max_open = g_zone_max_open; dev->zone_max_active = g_zone_max_active; + dev->zone_append_max_sectors = g_zone_append_max_sectors; dev->virt_boundary = g_virt_boundary; dev->no_sched = g_no_sched; dev->shared_tags = g_shared_tags;
drivers/block/null_blk/null_blk.h +1 -0
@@ -82,6 +82,7 @@ struct nullb_device {unsigned int zone_nr_conv;/* number of conventional zones */ unsigned int zone_max_open;/* max number of open zones */ unsigned int zone_max_active;/* max number of active zones */ +unsigned int zone_append_max_sectors;/* Max sectors per zone append command */ unsigned int submit_queues;/* number of submission queues */ unsigned int prev_submit_queues;/* number of submission queues before change */ unsigned int poll_queues;/* number of IOPOLL submission queues */
drivers/block/null_blk/zoned.c +19 -3
@@ -62,6 +62,7 @@ int null_init_zoned_dev(struct nullb_device *dev, struct queue_limits *lim) {sector_t dev_capacity_sects, zone_capacity_sects; +sector_t zone_append_max_bytes;struct nullb_zone *zone;sector_t sector =0 ;unsigned int i;
@@ -103,6 +104,12 @@ int null_init_zoned_dev(struct nullb_device *dev, dev->zone_nr_conv); } + zone_append_max_bytes = +ALIGN_DOWN (dev->zone_append_max_sectors <<SECTOR_SHIFT , + dev->blocksize); + dev->zone_append_max_sectors = +min (zone_append_max_bytes >>SECTOR_SHIFT , zone_capacity_sects); +/* Max active zones has to be < nbr of seq zones in order to be enforceable */ if (dev->zone_max_active >= dev->nr_zones - dev->zone_nr_conv) { dev->zone_max_active =0 ;
@@ -154,7 +161,7 @@ int null_init_zoned_dev(struct nullb_device *dev, lim->zoned = true; lim->chunk_sectors = dev->zone_size_sects; - lim->max_zone_append_sectors = dev->zone_size_sects; + lim->max_zone_append_sectors = dev->zone_append_max_sectors; lim->max_open_zones = dev->zone_max_open; lim->max_active_zones = dev->zone_max_active;return 0 ;
@@ -163,10 +170,16 @@ int null_init_zoned_dev(struct nullb_device *dev,int null_register_zoned_dev (struct nullb *nullb) {struct request_queue *q = nullb->q; +struct gendisk *disk = nullb->disk;blk_queue_flag_set (QUEUE_FLAG_ZONE_RESETALL , q); - nullb->disk->nr_zones =bdev_nr_zones (nullb->disk->part0); -return blk_revalidate_disk_zones (nullb->disk,NULL ); + disk->nr_zones =bdev_nr_zones (disk->part0); + +pr_info ("%s: using %s zone append\n" , + disk->disk_name, +queue_emulates_zone_append (q) ?"emulated" :"native" ); + +return blk_revalidate_disk_zones (disk,NULL ); }void null_free_zoned_dev (struct nullb_device *dev)
@@ -365,6 +378,9 @@ static blk_status_t null_zone_write(struct nullb_cmd *cmd, sector_t sector,trace_nullb_zone_op (cmd, zno, zone->cond); +if (WARN_ON_ONCE (append && !dev->zone_append_max_sectors)) +return BLK_STS_IOERR ; +if (zone->type ==BLK_ZONE_TYPE_CONVENTIONAL ) {if (append)return BLK_STS_IOERR ;
Signature · 1 line
2.44.0
Re: [PATCH v5 16/28] null_blk: Introduce zone_append_max_sectors attribute
drivers/block/null_blk/zoned.c int null_init_zoned_dev(struct nullb_device *dev, · line 111
Quoted 5 lines · Damien Le Moal
drivers/block/null_blk/zoned.c +5
-0+ zone_append_max_bytes = +ALIGN_DOWN (dev->zone_append_max_sectors <<SECTOR_SHIFT , + dev->blocksize); + dev->zone_append_max_sectors = +min (zone_append_max_bytes >>SECTOR_SHIFT , zone_capacity_sects);
I think the above code can be simplified to the following:
dev->zone_append_max_sectors =
min(ALIGN_DOWN(dev->zone_append_max_sectors,
dev->blocksize >> SECTOR_SHIFT),
zone_capacity_sects);Anyway:
Damien Le Moal
maintainerPATCH v5 17/28 null_blk: Introduce fua attribute
In reply to Damien Le MoalAdd the fua configfs attribute and module parameter to allow configuring if the device supports FUA or not. Using this attribute has an effect on the null_blk device only if memory backing is enabled together with a write cache (cache_size option).
This new attribute allows configuring a null_blk device with a write cache but without FUA support. This is convenient to test the block layer flush machinery.
--- drivers/block/null_blk/main.c | 12 ++++++++++-- drivers/block/null_blk/null_blk.h | 1 + 2 files changed, 11 insertions(+), 2 deletions(-)
drivers/block/null_blk/main.c +10 -2
@@ -225,6 +225,10 @@ static unsigned long g_cache_size;module_param_named (cache_size, g_cache_size, ulong,0444 );MODULE_PARM_DESC (mbps,"Cache size in MiB for memory-backed device. Default: 0 (none)" ); +static bool g_fua = true; +module_param_named (fua, g_fua, bool,0444 ); +MODULE_PARM_DESC (zoned,"Enable/disable FUA support when cache_size is used. Default: true" ); +static unsigned int g_mbps;module_param_named (mbps, g_mbps, uint,0444 );MODULE_PARM_DESC (mbps,"Limit maximum bandwidth (in MiB/s). Default: 0 (no limit)" );
@@ -446,6 +450,7 @@ NULLB_DEVICE_ATTR(virt_boundary, bool, NULL);NULLB_DEVICE_ATTR (no_sched, bool,NULL );NULLB_DEVICE_ATTR (shared_tags, bool,NULL );NULLB_DEVICE_ATTR (shared_tag_bitmap, bool,NULL ); +NULLB_DEVICE_ATTR (fua, bool,NULL );static ssize_t nullb_device_power_show (struct config_item *item,char *page) {
@@ -593,6 +598,7 @@ static struct configfs_attribute *nullb_device_attrs[] = { &nullb_device_attr_no_sched, &nullb_device_attr_shared_tags, &nullb_device_attr_shared_tag_bitmap, + &nullb_device_attr_fua, NULL, };
@@ -671,7 +677,7 @@ nullb_group_drop_item(struct config_group *group, struct config_item *item)static ssize_t memb_group_features_show (struct config_item *item,char *page) {return snprintf (page,PAGE_SIZE , -"badblocks,blocking,blocksize,cache_size," +"badblocks,blocking,blocksize,cache_size,fua," "completion_nsec,discard,home_node,hw_queue_depth," "irqmode,max_sectors,mbps,memory_backed,no_sched," "poll_queues,power,queue_mode,shared_tag_bitmap,"
@@ -763,6 +769,8 @@ static struct nullb_device *null_alloc_dev(void) dev->no_sched = g_no_sched; dev->shared_tags = g_shared_tags; dev->shared_tag_bitmap = g_shared_tag_bitmap; + dev->fua = g_fua; +return dev; }
@@ -1920,7 +1928,7 @@ static int null_add_dev(struct nullb_device *dev)if (dev->cache_size >0 ) {set_bit (NULLB_DEV_FL_CACHE , &nullb->dev->flags); -blk_queue_write_cache (nullb->q, true, true); +blk_queue_write_cache (nullb->q, true, dev->fua); } nullb->q->queuedata = nullb;
drivers/block/null_blk/null_blk.h +1 -0
@@ -105,6 +105,7 @@ struct nullb_device {bool no_sched;/* no IO scheduler for the device */ bool shared_tags;/* share tag set between devices for blk-mq */ bool shared_tag_bitmap;/* use hostwide shared tags */ +bool fua;/* Support FUA */ };struct nullb {
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 18/28 nvmet: zns: Do not reference the gendisk conv_zones_bitmap
In reply to Damien Le MoalThe gendisk conventional zone bitmap is going away. So to check for the presence of conventional zones on a zoned target device, always use report zones.
--- drivers/nvme/target/zns.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-)
drivers/nvme/target/zns.c +3 -7
@@ -52,14 +52,10 @@ bool nvmet_bdev_zns_enable(struct nvmet_ns *ns)if (get_capacity (bd_disk) & (bdev_zone_sectors (ns->bdev) -1 ))return false;/* -* ZNS does not define a conventional zone type. If the underlying -* device has a bitmap set indicating the existence of conventional -* zones, reject the device. Otherwise, use report zones to detect if -* the device has conventional zones. +* ZNS does not define a conventional zone type. Use report zones +* to detect if the device has conventional zones and reject it if +* it does. */ -if (ns->bdev->bd_disk->conv_zones_bitmap) -return false; - ret =blkdev_report_zones (ns->bdev,0 ,bdev_nr_zones (ns->bdev), validate_conv_zones_cb,NULL );if (ret <0 )
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 19/28 block: Remove BLK_STS_ZONE_RESOURCE
In reply to Damien Le MoalThe zone append emulation of the scsi disk driver was the only driver using BLK_STS_ZONE_RESOURCE. With this code removed, BLK_STS_ZONE_RESOURCE is now unused. Remove this macro definition and simplify blk_mq_dispatch_rq_list() where this status code was handled.
--- block/blk-mq.c | 26 -------------------------- drivers/scsi/scsi_lib.c | 1 - include/linux/blk_types.h | 20 ++++---------------- 3 files changed, 4 insertions(+), 43 deletions(-)
block/blk-mq.c +0 -26
@@ -1921,19 +1921,6 @@ static void blk_mq_handle_dev_resource(struct request *rq,__blk_mq_requeue_request (rq); } -static void blk_mq_handle_zone_resource (struct request *rq, -struct list_head *zone_list) -{ -/* -* If we end up here it is because we cannot dispatch a request to a -* specific zone due to LLD level zone-write locking or other zone -* related resource not being available. In this case, set the request -* aside in zone_list for retrying it later. -*/ -list_add (&rq->queuelist, zone_list); -__blk_mq_requeue_request (rq); -} -enum prep_dispatch {PREP_DISPATCH_OK ,PREP_DISPATCH_NO_TAG ,
@@ -2019,7 +2006,6 @@ bool blk_mq_dispatch_rq_list(struct blk_mq_hw_ctx *hctx, struct list_head *list,struct request *rq;int queued;blk_status_t ret =BLK_STS_OK ; -LIST_HEAD (zone_list);bool needs_resource = false;if (list_empty (list))
@@ -2061,23 +2047,11 @@ bool blk_mq_dispatch_rq_list(struct blk_mq_hw_ctx *hctx, struct list_head *list,case BLK_STS_DEV_RESOURCE :blk_mq_handle_dev_resource (rq, list); goto out; -case BLK_STS_ZONE_RESOURCE : -/* -* Move the request to zone_list and keep going through -* the dispatch list to find more requests the drive can -* accept. -*/ -blk_mq_handle_zone_resource (rq, &zone_list); - needs_resource = true; -break ;default :blk_mq_end_request (rq, ret); } }while (!list_empty (list)); out: -if (!list_empty (&zone_list)) -list_splice_tail_init (&zone_list, list); -/* If we didn't flush the entire list, we could have told the driver * there was more coming, but that turned out to be a lie. */
drivers/scsi/scsi_lib.c +0 -1
@@ -1870,7 +1870,6 @@ static blk_status_t scsi_queue_rq(struct blk_mq_hw_ctx *hctx,case BLK_STS_OK :break ;case BLK_STS_RESOURCE : -case BLK_STS_ZONE_RESOURCE :if (scsi_device_blocked (sdev)) ret =BLK_STS_DEV_RESOURCE ;break ;
include/linux/blk_types.h +4 -16
@@ -136,18 +136,6 @@ typedef u16 blk_short_t;*/ #define BLK_STS_DEV_RESOURCE ((__force blk_status_t)13) -/* -* BLK_STS_ZONE_RESOURCE is returned from the driver to the block layer if zone -* related resources are unavailable, but the driver can guarantee the queue -* will be rerun in the future once the resources become available again. -* -* This is different from BLK_STS_DEV_RESOURCE in that it explicitly references -* a zone specific resource and IO to a different zone on the same device could -* still be served. Examples of that are zones that are write-locked, but a read -* to the same zone could be served. -*/ -#define BLK_STS_ZONE_RESOURCE ((__force blk_status_t)14) -/* * BLK_STS_ZONE_OPEN_RESOURCE is returned from the driver in the completion * path if the device returns a status indicating that too many zone resources
@@ -155,7 +143,7 @@ typedef u16 blk_short_t;* after the number of open zones decreases below the device's limits, which is * reported in the request_queue's max_open_zones. */ -#define BLK_STS_ZONE_OPEN_RESOURCE ((__force blk_status_t)15) +#define BLK_STS_ZONE_OPEN_RESOURCE ((__force blk_status_t)14)/* * BLK_STS_ZONE_ACTIVE_RESOURCE is returned from the driver in the completion
@@ -164,20 +152,20 @@ typedef u16 blk_short_t;* after the number of active zones decreases below the device's limits, which * is reported in the request_queue's max_active_zones. */ -#define BLK_STS_ZONE_ACTIVE_RESOURCE ((__force blk_status_t)16) +#define BLK_STS_ZONE_ACTIVE_RESOURCE ((__force blk_status_t)15)/* * BLK_STS_OFFLINE is returned from the driver when the target device is offline * or is being taken offline. This could help differentiate the case where a * device is intentionally being shut down from a real I/O error. */ -#define BLK_STS_OFFLINE ((__force blk_status_t)17) +#define BLK_STS_OFFLINE ((__force blk_status_t)16)/* * BLK_STS_DURATION_LIMIT is returned from the driver when the target device * aborted the command because it exceeded one of its Command Duration Limits. */ -#define BLK_STS_DURATION_LIMIT ((__force blk_status_t)18) +#define BLK_STS_DURATION_LIMIT ((__force blk_status_t)17)/** * blk_path_error - returns true if error may be path related
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 20/28 block: Simplify blk_revalidate_disk_zones() interface
In reply to Damien Le MoalThe only user of blk_revalidate_disk_zones() second argument was the SCSI disk driver (sd). Now that this driver does not require this update_driver_data argument, remove it to simplify the interface of blk_revalidate_disk_zones(). Also update the function kdoc comment to be more accurate (i.e. there is no gendisk ->revalidate method).
--- block/blk-zoned.c | 21 +++++++-------------- drivers/block/null_blk/zoned.c | 2 +- drivers/block/ublk_drv.c | 2 +- drivers/block/virtio_blk.c | 2 +- drivers/md/dm-zone.c | 2 +- drivers/nvme/host/core.c | 2 +- drivers/scsi/sd_zbc.c | 2 +- include/linux/blkdev.h | 3 +-- 8 files changed, 14 insertions(+), 22 deletions(-)
block/blk-zoned.c +7 -14
@@ -1711,21 +1711,17 @@ static int blk_revalidate_zone_cb(struct blk_zone *zone, unsigned int idx,/** * blk_revalidate_disk_zones - (re)allocate and initialize zone bitmaps * @disk: Target disk -* @update_driver_data: Callback to update driver data on the frozen disk * -* Helper function for low-level device drivers to check and (re) allocate and -* initialize a disk request queue zone bitmaps. This functions should normally -* be called within the disk ->revalidate method for blk-mq based drivers. +* Helper function for low-level device drivers to check, (re) allocate and +* initialize resources used for managing zoned disks. This function should +* normally be called by blk-mq based drivers when a zoned gendisk is probed +* and when the zone configuration of the gendisk changes (e.g. after a format). * Before calling this function, the device driver must already have set the * device zone size (chunk_sector limit) and the max zone append limit. * BIO based drivers can also use this function as long as the device queue * can be safely frozen. -* If the @update_driver_data callback function is not NULL, the callback is -* executed with the device request queue frozen after all zones have been -* checked. */ -int blk_revalidate_disk_zones (struct gendisk *disk, -void (*update_driver_data)(struct gendisk *disk)) +int blk_revalidate_disk_zones (struct gendisk *disk) {struct request_queue *q = disk->queue;sector_t zone_sectors = q->limits.chunk_sectors;
@@ -1792,13 +1788,10 @@ int blk_revalidate_disk_zones(struct gendisk *disk,* referencing the bitmaps). */ blk_mq_freeze_queue (q); -if (ret >0 ) { +if (ret >0 ) ret =disk_update_zone_resources (disk, &args); -if (update_driver_data) -update_driver_data (disk); - }else { +else pr_warn ("%s: failed to revalidate zones\n" , disk->disk_name); - }if (ret)disk_free_zone_resources (disk);blk_mq_unfreeze_queue (q);
drivers/block/null_blk/zoned.c +1 -1
@@ -179,7 +179,7 @@ int null_register_zoned_dev(struct nullb *nullb) disk->disk_name, queue_emulates_zone_append(q) ? "emulated" : "native"); -return blk_revalidate_disk_zones (disk,NULL ); +return blk_revalidate_disk_zones (disk); }void null_free_zoned_dev (struct nullb_device *dev)
drivers/block/ublk_drv.c +1 -1
@@ -221,7 +221,7 @@ static int ublk_get_nr_zones(const struct ublk_device *ub)static int ublk_revalidate_disk_zones (struct ublk_device *ub) { -return blk_revalidate_disk_zones (ub->ub_disk,NULL ); +return blk_revalidate_disk_zones (ub->ub_disk); }static int ublk_dev_param_zoned_validate (const struct ublk_device *ub)
drivers/block/virtio_blk.c +1 -1
@@ -1543,7 +1543,7 @@ static int virtblk_probe(struct virtio_device *vdev)*/ if (IS_ENABLED (CONFIG_BLK_DEV_ZONED ) && lim.zoned) {blk_queue_flag_set (QUEUE_FLAG_ZONE_RESETALL , vblk->disk->queue); - err =blk_revalidate_disk_zones (vblk->disk,NULL ); + err =blk_revalidate_disk_zones (vblk->disk);if (err) goto out_cleanup_disk; }
drivers/md/dm-zone.c +1 -1
@@ -169,7 +169,7 @@ static int dm_revalidate_zones(struct mapped_device *md, struct dm_table *t)* our table for dm_blk_report_zones() to use directly. */ md->zone_revalidate_map = t; - ret =blk_revalidate_disk_zones (disk,NULL ); + ret =blk_revalidate_disk_zones (disk); md->zone_revalidate_map =NULL ;if (ret) {
drivers/nvme/host/core.c +1 -1
@@ -2150,7 +2150,7 @@ static int nvme_update_ns_info_block(struct nvme_ns *ns,blk_mq_unfreeze_queue (ns->disk->queue);if (blk_queue_is_zoned (ns->queue)) { - ret =blk_revalidate_disk_zones (ns->disk,NULL ); + ret =blk_revalidate_disk_zones (ns->disk);if (ret && !nvme_first_scan (ns->disk)) goto out; }
drivers/scsi/sd_zbc.c +1 -1
@@ -572,7 +572,7 @@ int sd_zbc_revalidate_zones(struct scsi_disk *sdkp)blk_queue_max_zone_append_sectors (q,0 ); flags =memalloc_noio_save (); - ret =blk_revalidate_disk_zones (disk,NULL ); + ret =blk_revalidate_disk_zones (disk);memalloc_noio_restore (flags);if (ret) { sdkp->zone_info = (struct zoned_disk_info ){ };
include/linux/blkdev.h +1 -2
@@ -336,8 +336,7 @@ int blkdev_report_zones(struct block_device *bdev, sector_t sector, unsigned int nr_zones, report_zones_cb cb, void *data);int blkdev_zone_mgmt (struct block_device *bdev,enum req_op op,sector_t sectors,sector_t nr_sectors); -int blk_revalidate_disk_zones (struct gendisk *disk, -void (*update_driver_data)(struct gendisk *disk)); +int blk_revalidate_disk_zones (struct gendisk *disk);/* * Independent access ranges: struct blk_independent_access_range describes
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 21/28 block: mq-deadline: Remove support for zone write locking
In reply to Damien Le MoalWith the block layer generic plugging of write operations for zoned block devices, mq-deadline, or any other scheduler, can only ever see at most one write operation per zone at any time. There is thus no sequentiality requirements for these writes and thus no need to tightly control the dispatching of write requests using zone write locking.
Remove all the code that implement this control in the mq-deadline scheduler and remove advertizing support for the ELEVATOR_F_ZBD_SEQ_WRITE elevator feature.
--- block/mq-deadline.c | 176 ++------------------------------------------ 1 file changed, 6 insertions(+), 170 deletions(-)
block/mq-deadline.c +6 -170
@@ -102,7 +102,6 @@ struct deadline_data {int prio_aging_expire;spinlock_t lock; -spinlock_t zone_lock; };/* Maps an I/O priority class to a deadline scheduler priority. */
@@ -157,8 +156,7 @@ deadline_latter_request(struct request *rq) }/* -* Return the first request for which blk_rq_pos() >= @pos. For zoned devices, -* return the first request after the start of the zone containing @pos. +* Return the first request for which blk_rq_pos() >= @pos. */ static inline struct request *deadline_from_pos (struct dd_per_prio *per_prio,enum dd_data_dir data_dir,sector_t pos)
@@ -170,14 +168,6 @@ static inline struct request *deadline_from_pos(struct dd_per_prio *per_prio,return NULL ; rq =rb_entry_rq (node); -/* -* A zoned write may have been requeued with a starting position that -* is below that of the most recently dispatched request. Hence, for -* zoned writes, start searching from the start of a zone. -*/ -if (blk_rq_is_seq_zoned_write (rq)) - pos =round_down (pos, rq->q->limits.chunk_sectors); -while (node) { rq =rb_entry_rq (node);if (blk_rq_pos (rq) >= pos) {
@@ -308,36 +298,6 @@ static inline bool deadline_check_fifo(struct dd_per_prio *per_prio,return time_is_before_eq_jiffies ((unsigned long )rq->fifo_time); } -/* -* Check if rq has a sequential request preceding it. -*/ -static bool deadline_is_seq_write (struct deadline_data *dd,struct request *rq) -{ -struct request *prev =deadline_earlier_request (rq); - -if (!prev) -return false; - -return blk_rq_pos (prev) +blk_rq_sectors (prev) ==blk_rq_pos (rq); -} - -/* -* Skip all write requests that are sequential from @rq, even if we cross -* a zone boundary. -*/ -static struct request *deadline_skip_seq_writes (struct deadline_data *dd, -struct request *rq) -{ -sector_t pos =blk_rq_pos (rq); - -do { - pos +=blk_rq_sectors (rq); - rq =deadline_latter_request (rq); - }while (rq &&blk_rq_pos (rq) == pos); - -return rq; -} -/* * For the specified data direction, return the next request to * dispatch using arrival ordered lists.
@@ -346,40 +306,10 @@ static struct request * deadline_fifo_request(struct deadline_data *dd, struct dd_per_prio *per_prio, enum dd_data_dir data_dir) { - struct request *rq, *rb_rq, *next; -unsigned long flags; - if (list_empty(&per_prio->fifo_list[data_dir])) return NULL; - rq =rq_entry_fifo (per_prio->fifo_list[data_dir].next); -if (data_dir ==DD_READ || !blk_queue_is_zoned (rq->q)) -return rq; - -/* -* Look for a write request that can be dispatched, that is one with -* an unlocked target zone. For some HDDs, breaking a sequential -* write stream can lead to lower throughput, so make sure to preserve -* sequential write streams, even if that stream crosses into the next -* zones and these zones are unlocked. -*/ -spin_lock_irqsave (&dd->zone_lock, flags); -list_for_each_entry_safe (rq, next, &per_prio->fifo_list[DD_WRITE ], - queuelist) { -/* Check whether a prior request exists for the same zone. */ - rb_rq =deadline_from_pos (per_prio, data_dir,blk_rq_pos (rq)); -if (rb_rq &&blk_rq_pos (rb_rq) <blk_rq_pos (rq)) - rq = rb_rq; -if (blk_req_can_dispatch_to_zone (rq) && - (blk_queue_nonrot (rq->q) || - !deadline_is_seq_write (dd, rq))) - goto out; - } - rq =NULL ; -out: -spin_unlock_irqrestore (&dd->zone_lock, flags); - -return rq; + return rq_entry_fifo(per_prio->fifo_list[data_dir].next); }/*
@@ -390,36 +320,8 @@ static struct request * deadline_next_request(struct deadline_data *dd, struct dd_per_prio *per_prio, enum dd_data_dir data_dir) { - struct request *rq; -unsigned long flags; - - rq =deadline_from_pos (per_prio, data_dir, - per_prio->latest_pos[data_dir]); -if (!rq) -return NULL ; - -if (data_dir ==DD_READ || !blk_queue_is_zoned (rq->q)) -return rq; - -/* -* Look for a write request that can be dispatched, that is one with -* an unlocked target zone. For some HDDs, breaking a sequential -* write stream can lead to lower throughput, so make sure to preserve -* sequential write streams, even if that stream crosses into the next -* zones and these zones are unlocked. -*/ -spin_lock_irqsave (&dd->zone_lock, flags); -while (rq) { -if (blk_req_can_dispatch_to_zone (rq)) -break ; -if (blk_queue_nonrot (rq->q)) - rq =deadline_latter_request (rq); -else - rq =deadline_skip_seq_writes (dd, rq); - } -spin_unlock_irqrestore (&dd->zone_lock, flags); - -return rq; + return deadline_from_pos(per_prio, data_dir, + per_prio->latest_pos[data_dir]); }/*
@@ -525,10 +427,6 @@ static struct request *__dd_dispatch_request(struct deadline_data *dd, rq = next_rq; } -/* -* For a zoned block device, if we only have writes queued and none of -* them can be dispatched, rq will be NULL. -*/ if (!rq)return NULL ;
@@ -549,10 +447,6 @@ static struct request *__dd_dispatch_request(struct deadline_data *dd, prio = ioprio_class_to_prio[ioprio_class]; dd->per_prio[prio].latest_pos[data_dir] =blk_rq_pos (rq); dd->per_prio[prio].stats.dispatched++; -/* -* If the request needs its target zone locked, do it. -*/ -blk_req_zone_write_lock (rq); rq->rq_flags |=RQF_STARTED ;return rq; }
@@ -722,7 +616,6 @@ static int dd_init_sched(struct request_queue *q, struct elevator_type *e) dd->fifo_batch = fifo_batch; dd->prio_aging_expire = prio_aging_expire;spin_lock_init (&dd->lock); -spin_lock_init (&dd->zone_lock);/* We dispatch from request queue wide instead of hw queue */ blk_queue_flag_set (QUEUE_FLAG_SQ_SCHED , q);
@@ -804,12 +697,6 @@ static void dd_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq,lockdep_assert_held (&dd->lock); -/* -* This may be a requeue of a write request that has locked its -* target zone. If it is the case, this releases the zone lock. -*/ -blk_req_zone_write_unlock (rq); - prio = ioprio_class_to_prio[ioprio_class]; per_prio = &dd->per_prio[prio];if (!rq->elv.priv[0 ]) {
@@ -841,18 +728,6 @@ static void dd_insert_request(struct blk_mq_hw_ctx *hctx, struct request *rq,*/ rq->fifo_time = jiffies + dd->fifo_expire[data_dir]; insert_before = &per_prio->fifo_list[data_dir]; -#ifdef CONFIG_BLK_DEV_ZONED -/* -* Insert zoned writes such that requests are sorted by -* position per zone. -*/ -if (blk_rq_is_seq_zoned_write (rq)) { -struct request *rq2 =deadline_latter_request (rq); - -if (rq2 &&blk_rq_zone_no (rq2) ==blk_rq_zone_no (rq)) - insert_before = &rq2->queuelist; - } -#endif list_add_tail (&rq->queuelist, insert_before); } }
@@ -887,33 +762,8 @@ static void dd_prepare_request(struct request *rq) rq->elv.priv[0 ] =NULL ; } -static bool dd_has_write_work (struct blk_mq_hw_ctx *hctx) -{ -struct deadline_data *dd = hctx->queue->elevator->elevator_data; -enum dd_prio p; - -for (p =0 ; p <=DD_PRIO_MAX ; p++) -if (!list_empty_careful (&dd->per_prio[p].fifo_list[DD_WRITE ])) -return true; - -return false; -} -/* * Callback from inside blk_mq_free_request(). -* -* For zoned block devices, write unlock the target zone of -* completed write requests. Do this while holding the zone lock -* spinlock so that the zone is never unlocked while deadline_fifo_request() -* or deadline_next_request() are executing. This function is called for -* all requests, whether or not these requests complete successfully. -* -* For a zoned block device, __dd_dispatch_request() may have stopped -* dispatching requests if all the queued requests are write requests directed -* at zones that are already locked due to on-going write requests. To ensure -* write request dispatch progress in this case, mark the queue as needing a -* restart to ensure that the queue is run again after completion of the -* request and zones being unlocked. */ static void dd_finish_request (struct request *rq) {
@@ -928,21 +778,8 @@ static void dd_finish_request(struct request *rq)* called dd_insert_requests(). Skip requests that bypassed I/O * scheduling. See also blk_mq_request_bypass_insert(). */ -if (!rq->elv.priv[0 ]) -return ; - -atomic_inc (&per_prio->stats.completed); - -if (blk_queue_is_zoned (q)) { -unsigned long flags; - -spin_lock_irqsave (&dd->zone_lock, flags); -blk_req_zone_write_unlock (rq); -spin_unlock_irqrestore (&dd->zone_lock, flags); - -if (dd_has_write_work (rq->mq_hctx)) -blk_mq_sched_mark_restart_hctx (rq->mq_hctx); - } +if (rq->elv.priv[0 ]) +atomic_inc (&per_prio->stats.completed); }static bool dd_has_work_for_prio (struct dd_per_prio *per_prio)
@@ -1266,7 +1103,6 @@ static struct elevator_type mq_deadline = { .elevator_attrs = deadline_attrs, .elevator_name = "mq-deadline", .elevator_alias = "deadline", - .elevator_features = ELEVATOR_F_ZBD_SEQ_WRITE, .elevator_owner = THIS_MODULE, };MODULE_ALIAS ("mq-deadline-iosched" );
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 22/28 block: Remove elevator required features
In reply to Damien Le MoalThe only elevator feature ever implemented is ELEVATOR_F_ZBD_SEQ_WRITE for signaling that a scheduler implements zone write locking to tightly control the dispatching order of write operations to zoned block devices. With the removal of zone write locking support in mq-deadline and the reliance of all block device drivers on the block layer zone write plugging to control ordering of write operations to zones, the elevator feature ELEVATOR_F_ZBD_SEQ_WRITE is completely unused. Remove it, and also remove the now unused code for filtering the possible schedulers for a block device based on required features.
--- block/blk-settings.c | 16 --------------- block/elevator.c | 46 +++++------------------------------------- block/elevator.h | 1 - include/linux/blkdev.h | 10 --------- 4 files changed, 5 insertions(+), 68 deletions(-)
block/blk-settings.c +0 -16
@@ -1052,22 +1052,6 @@ void blk_queue_write_cache(struct request_queue *q, bool wc, bool fua) }EXPORT_SYMBOL_GPL (blk_queue_write_cache); -/** -* blk_queue_required_elevator_features - Set a queue required elevator features -* @q: the request queue for the target device -* @features: Required elevator features OR'ed together -* -* Tell the block layer that for the device controlled through @q, only the -* only elevators that can be used are those that implement at least the set of -* features specified by @features. -*/ -void blk_queue_required_elevator_features (struct request_queue *q, -unsigned int features) -{ - q->required_elevator_features = features; -} -EXPORT_SYMBOL_GPL (blk_queue_required_elevator_features); -/** * blk_queue_can_use_dma_map_merging - configure queue for merging segments. * @q: the request queue for the device
block/elevator.c +5 -41
76 lines not on this page · raw message
block/elevator.h +0 -1
@@ -74,7 +74,6 @@ struct elevator_typestruct elv_fs_entry *elevator_attrs;const char *elevator_name;const char *elevator_alias; -const unsigned int elevator_features;struct module *elevator_owner;#ifdef CONFIG_BLK_DEBUG_FS const struct blk_mq_debugfs_attr *queue_debugfs_attrs;
include/linux/blkdev.h +0 -10
@@ -453,8 +453,6 @@ struct request_queue {atomic_t nr_active_requests_shared_tags; -unsigned int required_elevator_features; -struct blk_mq_tags *sched_shared_tags;struct list_head icq_list;
@@ -959,14 +957,6 @@ disk_alloc_independent_access_ranges(struct gendisk *disk, int nr_ia_ranges);void disk_set_independent_access_ranges (struct gendisk *disk,struct blk_independent_access_ranges *iars); -/* -* Elevator features for blk_queue_required_elevator_features: -*/ -/* Supports zoned block devices sequential write constraint */ -#define ELEVATOR_F_ZBD_SEQ_WRITE (1U << 0) - -extern void blk_queue_required_elevator_features (struct request_queue *q, -unsigned int features);extern bool blk_queue_can_use_dma_map_merging (struct request_queue *q,struct device *dev);
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 23/28 block: Do not check zone type in blk_check_zone_append()
In reply to Damien Le MoalZone append operations are only allowed to target sequential write required zones. blk_check_zone_append() uses bio_zone_is_seq() to check this. However, this check is not necessary because:
1) For NVMe ZNS namespace devices, only sequential write required zones exist, making the zone type check useless.
2) For null_blk, the driver will fail the request anyway, thus notifying the user that a conventional zone was targeted.
3) For all other zoned devices, zone append is now emulated using zone write plugging, which checks that a zone append operation does not target a conventional zone.
In preparation for the removal of zone write locking and its conventional zone bitmap (used by bio_zone_is_seq()), remove the bio_zone_is_seq() call from blk_check_zone_append().
--- block/blk-core.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
block/blk-core.c +1 -2
@@ -589,8 +589,7 @@ static inline blk_status_t blk_check_zone_append(struct request_queue *q,return BLK_STS_NOTSUPP ;/* The bio sector must point to the start of a sequential zone */ -if (!bdev_is_zone_start (bio->bi_bdev, bio->bi_iter.bi_sector) || - !bio_zone_is_seq (bio)) +if (!bdev_is_zone_start (bio->bi_bdev, bio->bi_iter.bi_sector))return BLK_STS_IOERR ;/*
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 24/28 block: Move zone related debugfs attribute to blk-zoned.c
In reply to Damien Le Moalblock/blk-mq-debugfs-zone.c contains a single debugfs attribute function. Defining this outside of block/blk-zoned.c does not really help in any way, so move this zone related debugfs attribute to block/blk-zoned.c and delete block/blk-mq-debugfs-zone.c.
--- block/Kconfig | 4 ---- block/Makefile | 1 - block/blk-mq-debugfs-zoned.c | 22 ---------------------- block/blk-mq-debugfs.h | 2 +- block/blk-zoned.c | 20 ++++++++++++++++++++ 5 files changed, 21 insertions(+), 28 deletions(-) delete mode 100644 block/blk-mq-debugfs-zoned.c
block/Kconfig +0 -4
@@ -198,10 +198,6 @@ config BLK_DEBUG_FS Unless you are building a kernel for a tiny system, you should say Y here. -config BLK_DEBUG_FS_ZONED - bool - default BLK_DEBUG_FS && BLK_DEV_ZONED - config BLK_SED_OPAL bool "Logic for interfacing with Opal enabled SEDs" depends on KEYS
block/Makefile +0 -1
@@ -33,7 +33,6 @@ obj-$(CONFIG_BLK_MQ_VIRTIO) += blk-mq-virtio.o obj-$(CONFIG_BLK_DEV_ZONED) += blk-zoned.o obj-$(CONFIG_BLK_WBT) += blk-wbt.o obj-$(CONFIG_BLK_DEBUG_FS) += blk-mq-debugfs.o -obj-$(CONFIG_BLK_DEBUG_FS_ZONED)+= blk-mq-debugfs-zoned.o obj-$(CONFIG_BLK_SED_OPAL) += sed-opal.o obj-$(CONFIG_BLK_PM) += blk-pm.o obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \
block/blk-mq-debugfs-zoned.c deleted +0 -22
@@ -1,22 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* -* Copyright (C) 2017 Western Digital Corporation or its affiliates. -*/ - -#include <linux/blkdev.h> -#include "blk-mq-debugfs.h" - -int queue_zone_wlock_show (void *data,struct seq_file *m) -{ -struct request_queue *q = data; -unsigned int i; - -if (!q->disk->seq_zones_wlock) -return 0 ; - -for (i =0 ; i < q->disk->nr_zones; i++) -if (test_bit (i, q->disk->seq_zones_wlock)) -seq_printf (m,"%u\n" , i); - -return 0 ; -}
block/blk-mq-debugfs.h +1 -1
@@ -83,7 +83,7 @@ static inline void blk_mq_debugfs_unregister_rqos(struct rq_qos *rqos) }#endif -#ifdef CONFIG_BLK_DEBUG_FS_ZONED +#if defined(CONFIG_BLK_DEV_ZONED ) && defined(CONFIG_BLK_DEBUG_FS )int queue_zone_wlock_show (void *data,struct seq_file *m);#else static inline int queue_zone_wlock_show (void *data,struct seq_file *m)
block/blk-zoned.c +20 -0
@@ -23,6 +23,7 @@#include "blk.h" #include "blk-mq-sched.h" +#include "blk-mq-debugfs.h" #define ZONE_COND_NAME (name) [BLK_ZONE_COND_##name] = #namestatic const char *const zone_cond_name[] = {
@@ -1802,3 +1803,22 @@ int blk_revalidate_disk_zones(struct gendisk *disk)return ret; }EXPORT_SYMBOL_GPL (blk_revalidate_disk_zones); + +#ifdef CONFIG_BLK_DEBUG_FS + +int queue_zone_wlock_show (void *data,struct seq_file *m) +{ +struct request_queue *q = data; +unsigned int i; + +if (!q->disk->seq_zones_wlock) +return 0 ; + +for (i =0 ; i < q->disk->nr_zones; i++) +if (test_bit (i, q->disk->seq_zones_wlock)) +seq_printf (m,"%u\n" , i); + +return 0 ; +} + +#endif
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 25/28 block: Replace zone_wlock debugfs entry with zone_wplugs entry
In reply to Damien Le MoalIn preparation to completely remove zone write locking, replace the "zone_wlock" mq-debugfs entry that was listing zones that are write-locked with the zone_wplugs entry which lists the zones that currently have a write plug allocated.
The write plug information provided is: the zone number, the zone write plug flags, the zone write plug write pointer offset and the number of BIOs currently waiting for execution in the zone write plug BIO list.
--- block/blk-mq-debugfs.c | 2 +- block/blk-mq-debugfs.h | 4 ++-- block/blk-zoned.c | 31 ++++++++++++++++++++++++------- 3 files changed, 27 insertions(+), 10 deletions(-)
block/blk-mq-debugfs.c +1 -1
@@ -160,7 +160,7 @@ static const struct blk_mq_debugfs_attr blk_mq_debugfs_queue_attrs[] = { { "requeue_list", 0400, .seq_ops = &queue_requeue_list_seq_ops }, { "pm_only", 0600, queue_pm_only_show, NULL }, { "state", 0600, queue_state_show, queue_state_write }, - { "zone_wlock", 0400, queue_zone_wlock_show, NULL }, + { "zone_wplugs", 0400, queue_zone_wplugs_show, NULL }, { }, };
block/blk-mq-debugfs.h +2 -2
@@ -84,9 +84,9 @@ static inline void blk_mq_debugfs_unregister_rqos(struct rq_qos *rqos)#endif #if defined(CONFIG_BLK_DEV_ZONED ) && defined(CONFIG_BLK_DEBUG_FS ) -int queue_zone_wlock_show (void *data,struct seq_file *m); +int queue_zone_wplugs_show (void *data,struct seq_file *m);#else -static inline int queue_zone_wlock_show (void *data,struct seq_file *m) +static inline int queue_zone_wplugs_show (void *data,struct seq_file *m) {return 0 ; }
block/blk-zoned.c +24 -7
@@ -1806,17 +1806,34 @@ EXPORT_SYMBOL_GPL(blk_revalidate_disk_zones);#ifdef CONFIG_BLK_DEBUG_FS -int queue_zone_wlock_show (void *data,struct seq_file *m) +int queue_zone_wplugs_show (void *data,struct seq_file *m) {struct request_queue *q = data; -unsigned int i; +struct gendisk *disk = q->disk; +struct blk_zone_wplug *zwplug; +unsigned int zwp_wp_offset, zwp_flags; +unsigned int zwp_zone_no, zwp_ref; +unsigned int zwp_bio_list_size, i; +unsigned long flags; -if (!q->disk->seq_zones_wlock) -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) { +spin_lock_irqsave (&zwplug->lock, flags); + zwp_zone_no = zwplug->zone_no; + zwp_flags = zwplug->flags; + zwp_ref =atomic_read (&zwplug->ref); + zwp_wp_offset = zwplug->wp_offset; + zwp_bio_list_size =bio_list_size (&zwplug->bio_list); +spin_unlock_irqrestore (&zwplug->lock, flags); -for (i =0 ; i < q->disk->nr_zones; i++) -if (test_bit (i, q->disk->seq_zones_wlock)) -seq_printf (m,"%u\n" , i); +seq_printf (m,"%u 0x%x %u %u %u\n" , + zwp_zone_no, zwp_flags, zwp_ref, + zwp_wp_offset, zwp_bio_list_size); + } + } +rcu_read_unlock ();return 0 ; }
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 26/28 block: Remove zone write locking
In reply to Damien Le MoalZone write locking is now unused and replaced with zone write plugging. Remove all code that was implementing zone write locking, that is, the various helper functions controlling request zone write locking and the gendisk attached zone bitmaps.
--- block/blk-mq-debugfs.c | 1 - block/blk-zoned.c | 66 ++----------------------------- include/linux/blk-mq.h | 83 --------------------------------------- include/linux/blk_types.h | 1 - include/linux/blkdev.h | 35 ++--------------- 5 files changed, 7 insertions(+), 179 deletions(-)
block/blk-mq-debugfs.c +0 -1
@@ -256,7 +256,6 @@ static const char *const rqf_name[] = {RQF_NAME (HASHED ),RQF_NAME (STATS ),RQF_NAME (SPECIAL_PAYLOAD ), -RQF_NAME (ZONE_WRITE_LOCKED ),RQF_NAME (TIMED_OUT ), RQF_NAME(RESV), };
block/blk-zoned.c +3 -63
114 lines not on this page · raw message
include/linux/blk-mq.h +0 -83
93 lines not on this page · raw message
include/linux/blk_types.h +0 -1
@@ -297,7 +297,6 @@ enum {BIO_QOS_THROTTLED ,/* bio went through rq_qos throttle path */ BIO_QOS_MERGED ,/* but went through rq_qos merge path */ BIO_REMAPPED , -BIO_ZONE_WRITE_LOCKED ,/* Owns a zoned device zone write lock */ BIO_ZONE_WRITE_PLUGGING ,/* bio handled through zone write plugging */ BIO_EMULATES_ZONE_APPEND ,/* bio emulates a zone append operation */ BIO_FLAG_LAST
include/linux/blkdev.h +4 -31
@@ -177,23 +177,14 @@ struct gendisk {#ifdef CONFIG_BLK_DEV_ZONED /* -* Zoned block device information for request dispatch control. -* nr_zones is the total number of zones of the device. This is always -* 0 for regular block devices. conv_zones_bitmap is a bitmap of nr_zones -* bits which indicates if a zone is conventional (bit set) or -* sequential (bit clear). seq_zones_wlock is a bitmap of nr_zones -* bits which indicates if a zone is write locked, that is, if a write -* request targeting the zone was dispatched. -* -* 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(). +* 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(). */ unsigned int nr_zones;unsigned int zone_capacity;unsigned long *conv_zones_bitmap; -unsigned long *seq_zones_wlock;unsigned int zone_wplugs_hash_bits;spinlock_t zone_wplugs_lock;struct mempool_s *zone_wplugs_pool;
@@ -635,15 +626,6 @@ static inline unsigned int disk_zone_no(struct gendisk *disk, sector_t sector)return sector >>ilog2 (disk->queue->limits.chunk_sectors); } -static inline bool disk_zone_is_seq (struct gendisk *disk,sector_t sector) -{ -if (!blk_queue_is_zoned (disk->queue)) -return false; -if (!disk->conv_zones_bitmap) -return true; -return !test_bit (disk_zone_no (disk, sector), disk->conv_zones_bitmap); -} -static inline void disk_set_max_open_zones (struct gendisk *disk,unsigned int max_open_zones) {
@@ -677,10 +659,6 @@ static inline unsigned int disk_nr_zones(struct gendisk *disk) {return 0 ; } -static inline bool disk_zone_is_seq (struct gendisk *disk,sector_t sector) -{ -return false; -}static inline unsigned int disk_zone_no (struct gendisk *disk,sector_t sector) {return 0 ;
@@ -870,11 +848,6 @@ static inline bool bio_straddles_zones(struct bio *bio)disk_zone_no (bio->bi_bdev->bd_disk,bio_end_sector (bio) -1 ); } -static inline unsigned int bio_zone_is_seq (struct bio *bio) -{ -return disk_zone_is_seq (bio->bi_bdev->bd_disk, bio->bi_iter.bi_sector); -} -/* * Return how much of the chunk is left to be used for I/O at a given offset. */
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 27/28 block: Do not force select mq-deadline with CONFIG_BLK_DEV_ZONED
In reply to Damien Le MoalNow that zone block device write ordering control does not depend anymore on mq-deadline and zone write locking, there is no need to force select the mq-deadline scheduler when CONFIG_BLK_DEV_ZONED is enabled.
--- block/Kconfig | 1 - 1 file changed, 1 deletion(-)
block/Kconfig +0 -1
@@ -100,7 +100,6 @@ config BLK_DEV_WRITE_MOUNTED config BLK_DEV_ZONED bool "Zoned block device support" - select MQ_IOSCHED_DEADLINE help Block layer zoned block device support. This option enables support for ZAC/ZBC/ZNS host-managed and host-aware zoned block
Signature · 1 line
2.44.0
Damien Le Moal
maintainerPATCH v5 28/28 block: Do not special-case plugging of zone write operations
In reply to Damien Le MoalWith the block layer zone write plugging being automatically done for any write operation to a zone of a zoned block device, a regular request plugging handled through current->plug can only ever see at most a single write request per zone. In such case, any potential reordering of the plugged requests will be harmless. We can thus remove the special casing for write operations to zones and have these requests plugged as well. This allows removing the function blk_mq_plug and instead directly using current->plug where needed.
--- block/blk-core.c | 6 ------ block/blk-merge.c | 3 +-- block/blk-mq.c | 7 +------ block/blk-mq.h | 31 ------------------------------- include/linux/blkdev.h | 12 ------------ 5 files changed, 2 insertions(+), 57 deletions(-)
block/blk-core.c +0 -6
@@ -907,12 +907,6 @@ int bio_poll(struct bio *bio, struct io_comp_batch *iob, unsigned int flags) !test_bit(QUEUE_FLAG_POLL, &q->queue_flags))return 0 ; -/* -* As the requests that require a zone lock are not plugged in the -* first place, directly accessing the plug instead of using -* blk_mq_plug() should not have any consequences during flushing for -* zoned devices. -*/ blk_flush_plug (current->plug, false);/*
block/blk-merge.c +1 -2
@@ -1112,10 +1112,9 @@ static enum bio_merge_status blk_attempt_bio_merge(struct request_queue *q,bool blk_attempt_plug_merge (struct request_queue *q,struct bio *bio,unsigned int nr_segs) { -struct blk_plug *plug; +struct blk_plug *plug = current->plug;struct request *rq; - plug =blk_mq_plug (bio);if (!plug ||rq_list_empty (plug->mq_list))return false;
block/blk-mq.c +1 -6
@@ -1330,11 +1330,6 @@ void blk_execute_rq_nowait(struct request *rq, bool at_head)blk_account_io_start (rq); -/* -* As plugging can be enabled for passthrough requests on a zoned -* device, directly accessing the plug instead of using blk_mq_plug() -* should not have any consequences. -*/ if (current->plug && !at_head) {blk_add_rq_to_plug (current->plug, rq);return ;
@@ -2932,7 +2927,7 @@ static void blk_mq_use_cached_rq(struct request *rq, struct blk_plug *plug,void blk_mq_submit_bio (struct bio *bio) {struct request_queue *q =bdev_get_queue (bio->bi_bdev); -struct blk_plug *plug =blk_mq_plug (bio); +struct blk_plug *plug = current->plug;const int is_sync =op_is_sync (bio->bi_opf);struct blk_mq_hw_ctx *hctx;unsigned int nr_segs =1 ;
block/blk-mq.h +0 -31
@@ -365,37 +365,6 @@ static inline void blk_mq_clear_mq_map(struct blk_mq_queue_map *qmap) qmap->mq_map[cpu] =0 ; } -/* -* blk_mq_plug() - Get caller context plug -* @bio : the bio being submitted by the caller context -* -* Plugging, by design, may delay the insertion of BIOs into the elevator in -* order to increase BIO merging opportunities. This however can cause BIO -* insertion order to change from the order in which submit_bio() is being -* executed in the case of multiple contexts concurrently issuing BIOs to a -* device, even if these context are synchronized to tightly control BIO issuing -* order. While this is not a problem with regular block devices, this ordering -* change can cause write BIO failures with zoned block devices as these -* require sequential write patterns to zones. Prevent this from happening by -* ignoring the plug state of a BIO issuing context if it is for a zoned block -* device and the BIO to plug is a write operation. -* -* Return current->plug if the bio can be plugged and NULL otherwise -*/ -static inline struct blk_plug *blk_mq_plug (struct bio *bio) -{ -/* Zoned block device write operation case: do not plug the BIO */ -if (IS_ENABLED (CONFIG_BLK_DEV_ZONED ) && -bdev_op_is_zoned_write (bio->bi_bdev,bio_op (bio))) -return NULL ; - -/* -* For regular block devices or read operations, use the context plug -* which may be NULL if blk_start_plug() was not executed. -*/ -return current->plug; -} -/* Free all requests on the list */ static inline void blk_mq_free_requests (struct list_head *list) {
include/linux/blkdev.h +0 -12
@@ -1300,18 +1300,6 @@ static inline unsigned int bdev_zone_no(struct block_device *bdev, sector_t sec)return disk_zone_no (bdev->bd_disk, sec); } -/* Whether write serialization is required for @op on zoned devices. */ -static inline bool op_needs_zoned_write_locking (enum req_op op) -{ -return op ==REQ_OP_WRITE || op ==REQ_OP_WRITE_ZEROES ; -} - -static inline bool bdev_op_is_zoned_write (struct block_device *bdev, -enum req_op op) -{ -return bdev_is_zoned (bdev) &&op_needs_zoned_write_locking (op); -} -static inline sector_t bdev_zone_sectors (struct block_device *bdev) {struct request_queue *q =bdev_get_queue (bdev);
Signature · 1 line
2.44.0
Quoted 180 lines · Damien Le Moal
The patch series introduces zone write plugging (ZWP) as the new mechanism to control the ordering of writes to zoned block devices. ZWP replaces zone write locking (ZWL) which is implemented only by mq-deadline today. ZWP also allows emulating zone append operations using regular writes for zoned devices that do not natively support this operation (e.g. SMR HDDs). This patch series removes the scsi disk driver and device mapper zone append emulation to use ZWP emulation.
Unlike ZWL which operates on requests, ZWP operates on BIOs. A zone write plug is simply a BIO list that is atomically manipulated using a spinlock and a kblockd submission work. A write BIO to a zone is "plugged" to delay its execution if a write BIO for the same zone was already issued, that is, if a write request for the same zone is being executed. The next plugged BIO is unplugged and issued once the write request completes.
This mechanism allows to:
- Untangle zone write ordering from the block IO schedulers. This allows removing the restriction on using only mq-deadline for zoned block devices. Any block IO scheduler, including "none" can be used.
- Zone write plugging operates on BIOs instead of requests. Plugged BIOs waiting for execution thus do not hold scheduling tags and thus do not prevent other BIOs from being submitted to the device (reads or writes to other zones). Depending on the workload, this can significantly improve the device use and the performance.
- Both blk-mq (request) based zoned devices and BIO-based devices (e.g. device mapper) can use ZWP. It is mandatory for the former but optional for the latter: BIO-based driver can use zone write plugging to implement write ordering guarantees, or the drivers can implement their own if needed.
- The code is less invasive in the block layer and in device drivers. ZWP implementation is mostly limited toblk-zoned.c, with some small changes inblk-mq.c,blk-merge.candbio.c.Performance evaluation results are shown below.
The series is based on
block/for-nextand organized as follows:- Patch 1 to 6 are preparatory changes for patch 7.
- Patch 7 and 8 introduce ZWP
- Patch 9 and 10 add zone append emulation to ZWP.
- Patch 11 to 18 modify zoned block device drivers to use ZWP and prepare for the removal of ZWL.
- Patch 19 to 28 remove zone write lockingOverall, these changes do not significantly increase the amount of code (the higher number of addition shown by diff-stat is in fact due to a larger amount of comments in the code).
Many thanks must go to Christoph Hellwig for comments and suggestions he provided on earlier versions of these patches.
Performance evaluation results
==============================Environments:
- Intel Xeon 16-cores/32-threads, 128GB of RAM
- Kernel:
- ZWL (baseline):block/for-next(based on 6.9.0-rc2)
- ZWP:block/for-nextpatched kernel to add zone write plugging (both kernels were compiled with the same configuration turning off most heavy debug features)Workoads:
- seqw4K1: 4KB sequential write, qd=1
- seqw4K16: 4KB sequential write, qd=16
- seqw1M16: 1MB sequential write, qd=16
- rndw4K16: 4KB random write, qd=16
- rndw128K16: 128KB random write, qd=16
- btrfs workoad: Single fio job writing 128 MB files using 128 KB direct IOs at qd=16.Devices:
- nullblk (zoned): 4096 zones of 256 MB, 128 max open zones.
- NVMe ZNS drive: 1 TB ZNS drive with 2GB zone size, 14 max open and active zones.
- SMR HDD: 20 TB disk with 256MB zone size, 128 max open zones.For ZWP, the result show the performance percentage increase (or decrease) against ZWL (baseline) case.
1)
null_blkzoned device:+--------+--------+-------+--------+---------+---------+ |seqw4K1 |seqw4K16|seqw1M1|seqw1M16|rndw4K16|rndw128K16| |(MB/s) | (MB/s) |(MB/s) | (MB/s) | (KIOPS)| (KIOPS) | +-----------+--------+--------+-------+--------+--------+----------+ | ZWL | 940 | 840 | 18550 | 14400 | 424 | 167 | |mq-deadline| | | | | | | +-----------+--------+--------+-------+--------+--------+----------+ | ZWP | 943 | 845 | 18660 | 14770 | 461 | 165 | |mq-deadline| (+0%) | (+0%) | (+0%) | (+1%) | (+8%) | (-1%) | +-----------+--------+--------+-------+--------+--------+----------+ | ZWP | 756 | 668 | 16020 | 12980 | 135 | 101 | | bfq | (-19%) | (-20%) | (-13%)| (-9%) | (-68%) | (-39%) | +-----------+--------+--------+-------+--------+--------+----------+ | ZWP | 2639 | 1715 | 28190 | 19760 | 344 | 150 | | none | (+180%)| (+104%)| (+51%)| (+37%) | (-18%) | (-10%) | +-----------+--------+--------+-------+--------+--------+----------+ZWP with mq-deadline gives performance very similar to zone write locking, showing that zone write plugging overhead is acceptable. But ZWP ability to run fast block devices with the none scheduler shows brings all the benefits of zone write plugging and results in significant performance increase for all workloads. The exception to this are random write workloads with multiple jobs: for these, the faster request submission rate achieved by zone write plugging results in higher contention on null-blk zone spinlock, which degrades performance.
2) NVMe ZNS drive:
+--------+--------+-------+--------+--------+----------+ |seqw4K1 |seqw4K16|seqw1M1|seqw1M16|rndw4K16|rndw128K16| |(MB/s) | (MB/s) |(MB/s) | (MB/s) | (KIOPS)| (KIOPS) | +-----------+--------+--------+-------+--------+--------+----------+ | ZWL | 183 | 702 | 1066 | 1103 | 53.5 | 14.5 | |mq-deadline| | | | | | | +-----------+--------+--------+-------+--------+--------+----------+ | ZWP | 183 | 719 | 1086 | 1108 | 55.6 | 14.7 | |mq-deadline| (-0%) | (+1%) | (+0%) | (+0%) | (+3%) | (+1%) | +-----------+--------+--------+-------+--------+--------+----------+ | ZWP | 178 | 691 | 1082 | 1106 | 30.8 | 11.5 | | bfq | (-3%) | (-2%) | (-0%) | (+0%) | (-42%) | (-20%) | +-----------+--------+--------+-------+--------+--------+----------+ | ZWP | 190 | 666 | 1083 | 1108 | 51.4 | 14.7 | | none | (+4%) | (-5%) | (+0%) | (+0%) | (-4%) | (+0%) | +-----------+--------+--------+-------+--------+--------+----------+Zone write plugging overhead does not significantly impact performance. Similar to nullblk, using the none scheduler leads to performance increase for most workloads.
3) SMR SATA HDD:
+-------+--------+-------+--------+--------+----------+ |seqw4K1|seqw4K16|seqw1M1|seqw1M16|rndw4K16|rndw128K16| |(MB/s) | (MB/s) |(MB/s) | (MB/s) | (KIOPS)| (KIOPS) | +-----------+-------+--------+-------+--------+--------+----------+ | ZWL | 107 | 243 | 245 | 246 | 2.2 | 0.763 | |mq-deadline| | | | | | | +-----------+-------+--------+-------+--------+--------+----------+ | ZWP | 107 | 242 | 245 | 245 | 2.2 | 0.772 | |mq-deadline| (+0%) | (-0%) | (+0%) | (-0%) | (+0%) | (+0%) | +-----------+-------+--------+-------+--------+--------+----------+ | ZWP | 104 | 241 | 246 | 242 | 2.2 | 0.765 | | bfq | (-2%) | (-0%) | (+0%) | (-0%) | (+0%) | (+0%) | +-----------+-------+--------+-------+--------+--------+----------+ | ZWP | 115 | 235 | 249 | 242 | 2.2 | 0.763 | | none | (+7%) | (-3%) | (+1%) | (-1%) | (+0%) | (+0%) | +-----------+-------+--------+-------+--------+--------+----------+Performance with purely sequential write workloads at high queue depth somewhat decrease a little when using zone write plugging. This is due to the different IO pattern that ZWP generates where the first writes to a zone start being issued when the end of the previous zone are still being written. Depending on how the disk handles queued commands, seek may be generated, slightly impacting the throughput achieved. Such pure sequential write workloads are however rare with SMR drives.
4) Zone append tests using btrfs:
+-------------+-------------+-----------+-------------+ | null-blk | null_blk | ZNS | SMR | | native ZA | emulated ZA | native ZA | emulated ZA | | (MB/s) | (MB/s) | (MB/s) | (MB/s) | +-----------+-------------+-------------+-----------+-------------+ | ZWL | 2441 | N/A | 1081 | 243 | |mq-deadline| | | | | +-----------+-------------+-------------+-----------+-------------+ | ZWP | 2361 | 2999 | 1085 | 239 | |mq-deadline| (-1%) | | (+0%) | (-2%) | +-----------+-------------+-------------+-----------+-------------+ | ZWP | 2299 | 2730 | 1080 | 240 | | bfq | (-4%) | | (+0%) | (-2%) | +-----------+-------------+-------------+-----------+-------------+ | ZWP | 2443 | 3152 | 1083 | 240 | | none | (+0%) | | (+0%) | (-1%) | +-----------+-------------+-------------+-----------+-------------+
Quoted 4 lines
With a more realistic use of the device though a file system, ZWP does not introduce significant performance differences, except for SMR for the same reason as with the fio sequential workloads at high queue depth.
I re-ran the same RocksDB testing I did for v4 on this patch series. Same results.
Setup
-----
Workloads: filluniquerandom followed by three iterations of overwrites, readrandom, fwdrange, revrange and varieties of readwhilewriting,
Config: Direct IO, 400M key/values
Userspace: db_bench / RocksDB 7.7.3, using ZenFS with libzbd as backend
IO Scheduler: [none]
NVMe ZNS drive: 1 TB ZNS drive with 2GB zone size, 14 max open and active zones.
System: 16-cores (AMD Epyc 7302P) with 128 GB of DRAM
Results:
--------
iostat: 33.6T read, 6.5T written. No errors/failures.
Comparing performance with the baseline requiring [mq-deadline] to avoid write reordering, we can see a ~8-10% throughput improvement for read heavy workloads.
No regressions seen for these workloads, db_bench write throughput and read and write tail latencies look unaffected.
Quoted 110 lines
Changes from v4:
- Modified patch 7 to:
- Fix a compilation warning
- Integratedisk_lookup_zone_wplug()intodisk_get_zone_wplug()so that all users of a zone write plug do a get+put when referencing the plug.
- Introduced inline functionsblk_zone_write_complete_request()andblk_zone_write_bio_endio()to integrate the "if (plugging)" test and avoid repeating it for all calls.
- Added review tagsChanges from v3:
- Rebase onblock/for-next
- Removed old patch 1 as it is already applied
- Addressed Bart and Christoph comment in patch 4
- Merged former patch 8 and 9 together and changed the zone write plug allocation to use a mempool
- Removed thezone_wplugs_mempool_sizefiled from patch 8 and insteaddirectly reference mempool->min_nr- Added review tags
Changes from v2:
- Added Patch 1 (Christoph's comment)
- Fixed error code setup in Patch 3 (Bart's comment)
- Split former patch 26 into patches 27 and 28
- Modified patch 8 (zone write plugging) introduction to remove thekmem_cacheuse and address Bart's and Christoph comments.
- Changed from using a mempool of zone write plugs to using a simple free-list (patch 9)
- Simplified patch 10 as suggested by Christoph
- Moved common code to a helper in patch 13 as suggested by ChristophChanges from v1:
- Added patch 6
- Rewrite of patch 7 to use a hash table of dynamically allocated zone write plugs. This results in changes in patch 11 and the addition of patch 8 and 9.
- Rebased everything on 6.9.0-rc1
- Added review tags for patches that did not changeDamien Le Moal (28):
block: Restore sector of flush requests
block: Removereq_bio_endio()
block: Introduceblk_zone_update_request_bio()
block: Introducebio_straddles_zones()andbio_offset_from_zone_start()
block: Allow usingbio_attempt_back_merge()internally
block: Remember zone capacity when revalidating zones
block: Introduce zone write plugging
block: Fake max open zones limit when there is no limit
block: Allow zero value ofmax_zone_append_sectorsqueue limit
block: Implement zone append emulation
block: Allow BIO-based drivers to useblk_revalidate_disk_zones()
dm: Use the block layer zone append emulation
scsi: sd: Use the block layer zone append emulation
ublk_drv: Do not requestELEVATOR_F_ZBD_SEQ_WRITEelevator feature
null_blk: Do not requestELEVATOR_F_ZBD_SEQ_WRITEelevator feature
null_blk: Introducezone_append_max_sectorsattribute
null_blk: Introduce fua attribute
nvmet: zns: Do not reference the gendiskconv_zones_bitmap
block: RemoveBLK_STS_ZONE_RESOURCE
block: Simplifyblk_revalidate_disk_zones()interface
block: mq-deadline: Remove support for zone write locking
block: Remove elevator required features
block: Do not check zone type inblk_check_zone_append()
block: Move zone related debugfs attribute toblk-zoned.c
block: Replacezone_wlockdebugfs entry withzone_wplugsentry
block: Remove zone write locking
block: Do not force select mq-deadline withCONFIG_BLK_DEV_ZONED
block: Do not special-case plugging of zone write operationsblock/Kconfig | 5 - block/Makefile | 1 - block/bio.c | 6 + block/blk-core.c | 11 +- block/blk-flush.c | 1 + block/blk-merge.c | 22 +- block/blk-mq-debugfs-zoned.c | 22 - block/blk-mq-debugfs.c | 3 +- block/blk-mq-debugfs.h | 6 +- block/blk-mq.c | 125 ++- block/blk-mq.h | 31 - block/blk-settings.c | 46 +- block/blk-sysfs.c | 2 +- block/blk-zoned.c | 1336 +++++++++++++++++++++++++++-- block/blk.h | 80 +- block/elevator.c | 46 +- block/elevator.h | 1 - block/genhd.c | 3 +- block/mq-deadline.c | 176 +--- drivers/block/null_blk/main.c | 22 +- drivers/block/null_blk/null_blk.h | 2 + drivers/block/null_blk/zoned.c | 23 +- drivers/block/ublk_drv.c | 5 +- drivers/block/virtio_blk.c | 2 +- drivers/md/dm-core.h | 2 +- drivers/md/dm-zone.c | 476 +--------- drivers/md/dm.c | 75 +- drivers/md/dm.h | 4 +- drivers/nvme/host/core.c | 2 +- drivers/nvme/target/zns.c | 10 +- drivers/scsi/scsi_lib.c | 1 - drivers/scsi/sd.c | 8 - drivers/scsi/sd.h | 19 - drivers/scsi/sd_zbc.c | 335 +------- include/linux/blk-mq.h | 85 +- include/linux/blk_types.h | 30 +- include/linux/blkdev.h | 103 ++- 37 files changed, 1663 insertions(+), 1464 deletions(-)delete mode 100644
block/blk-mq-debugfs-zoned.c