From b02b9ba585d5bcc2c0e1de6f625a753b665c016f Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 7 Apr 2026 16:05:24 +0200 Subject: [PATCH 1/5] xfs: fix number of GC bvecs GC scratch allocations can wrap around and use the same buffer twice, and the current code fails to account for that. So far this worked due to rounding in the block layer, but changes to the bio allocator drop the over-provisioning and generic/256 or generic/361 will now usually fail when running against the current block tree. Simplify the allocation to always pass the maximum value that is easier to verify, as a saving of up to one bvec per allocation isn't worth the effort to verify a complicated calculated value. Fixes: 102f444b57b3 ("xfs: rework zone GC buffer management") Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Hans Holmberg --- fs/xfs/xfs_zone_gc.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index 309f70098524..eebe6f4fee1f 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -670,7 +670,6 @@ xfs_zone_gc_start_chunk( struct xfs_inode *ip; struct bio *bio; xfs_daddr_t daddr; - unsigned int len; bool is_seq; if (xfs_is_shutdown(mp)) @@ -685,15 +684,16 @@ xfs_zone_gc_start_chunk( return false; } - len = XFS_FSB_TO_B(mp, irec.rm_blockcount); - bio = bio_alloc_bioset(bdev, - min(howmany(len, XFS_GC_BUF_SIZE) + 1, XFS_GC_NR_BUFS), - REQ_OP_READ, GFP_NOFS, &data->bio_set); - + /* + * Scratch allocation can wrap around to the same buffer again, + * provision an extra bvec for that case. + */ + bio = bio_alloc_bioset(bdev, XFS_GC_NR_BUFS + 1, REQ_OP_READ, GFP_NOFS, + &data->bio_set); chunk = container_of(bio, struct xfs_gc_bio, bio); chunk->ip = ip; chunk->offset = XFS_FSB_TO_B(mp, irec.rm_offset); - chunk->len = len; + chunk->len = XFS_FSB_TO_B(mp, irec.rm_blockcount); chunk->old_startblock = xfs_rgbno_to_rtb(iter->victim_rtg, irec.rm_startblock); chunk->new_daddr = daddr; @@ -707,8 +707,9 @@ xfs_zone_gc_start_chunk( bio->bi_iter.bi_sector = xfs_rtb_to_daddr(mp, chunk->old_startblock); bio->bi_end_io = xfs_zone_gc_end_io; xfs_zone_gc_add_data(chunk); - data->scratch_head = (data->scratch_head + len) % data->scratch_size; - data->scratch_available -= len; + data->scratch_head = + (data->scratch_head + chunk->len) % data->scratch_size; + data->scratch_available -= chunk->len; XFS_STATS_INC(mp, xs_gc_read_calls); From 3282ea409cdc14a1cba26e3d5ed0690a8009650e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 7 Apr 2026 16:05:25 +0200 Subject: [PATCH 2/5] block: unify the synchronous bi_end_io callbacks Put the bio in bio_await_chain after waiting for the completion, and share the now identical callbacks between submit_bio_wait and bio_await_chain. Signed-off-by: Christoph Hellwig Reviewed-by: Bart Van Assche Reviewed-by: Damien Le Moal Reviewed-by: Chaitanya Kulkarni --- block/bio.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/block/bio.c b/block/bio.c index d80d5d26804e..a59d20999832 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1481,7 +1481,7 @@ void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty) bio_iov_iter_unbounce_read(bio, is_error, mark_dirty); } -static void submit_bio_wait_endio(struct bio *bio) +static void bio_wait_end_io(struct bio *bio) { complete(bio->bi_private); } @@ -1503,7 +1503,7 @@ int submit_bio_wait(struct bio *bio) bio->bi_bdev->bd_disk->lockdep_map); bio->bi_private = &done; - bio->bi_end_io = submit_bio_wait_endio; + bio->bi_end_io = bio_wait_end_io; bio->bi_opf |= REQ_SYNC; submit_bio(bio); blk_wait_io(&done); @@ -1542,12 +1542,6 @@ int bdev_rw_virt(struct block_device *bdev, sector_t sector, void *data, } EXPORT_SYMBOL_GPL(bdev_rw_virt); -static void bio_wait_end_io(struct bio *bio) -{ - complete(bio->bi_private); - bio_put(bio); -} - /* * bio_await_chain - ends @bio and waits for every chained bio to complete */ @@ -1560,6 +1554,7 @@ void bio_await_chain(struct bio *bio) bio->bi_end_io = bio_wait_end_io; bio_endio(bio); blk_wait_io(&done); + bio_put(bio); } void __bio_advance(struct bio *bio, unsigned bytes) From effebad2b83a785c5a2e973f232cada4c0fe8d51 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 7 Apr 2026 16:05:26 +0200 Subject: [PATCH 3/5] block: factor out a bio_await helper Add a new helper to wait for a bio and anything chained off it to complete synchronously after submitting it. This factors common code out of submit_bio_wait and bio_await_chain and will also be useful for file system code and thus is exported. Note that this will now set REQ_SYNC also for the bio_await case for consistency. Nothing should look at the flag in the end_io handler, but if something does having the flag set makes more sense. Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal --- block/bio.c | 53 +++++++++++++++++++++++++++++++-------------- include/linux/bio.h | 2 ++ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/block/bio.c b/block/bio.c index a59d20999832..373795491287 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1487,17 +1487,20 @@ static void bio_wait_end_io(struct bio *bio) } /** - * submit_bio_wait - submit a bio, and wait until it completes - * @bio: The &struct bio which describes the I/O + * bio_await - call a function on a bio, and wait until it completes + * @bio: the bio which describes the I/O + * @submit: function called to submit the bio + * @priv: private data passed to @submit * - * Simple wrapper around submit_bio(). Returns 0 on success, or the error from - * bio_endio() on failure. + * Wait for the bio as well as any bio chained off it after executing the + * passed in callback @submit. The wait for the bio is set up before calling + * @submit to ensure that the completion is captured. If @submit is %NULL, + * submit_bio() is used instead to submit the bio. * - * WARNING: Unlike to how submit_bio() is usually used, this function does not - * result in bio reference to be consumed. The caller must drop the reference - * on his own. + * Note: this overrides the bi_private and bi_end_io fields in the bio. */ -int submit_bio_wait(struct bio *bio) +void bio_await(struct bio *bio, void *priv, + void (*submit)(struct bio *bio, void *priv)) { DECLARE_COMPLETION_ONSTACK_MAP(done, bio->bi_bdev->bd_disk->lockdep_map); @@ -1505,13 +1508,37 @@ int submit_bio_wait(struct bio *bio) bio->bi_private = &done; bio->bi_end_io = bio_wait_end_io; bio->bi_opf |= REQ_SYNC; - submit_bio(bio); + if (submit) + submit(bio, priv); + else + submit_bio(bio); blk_wait_io(&done); +} +EXPORT_SYMBOL_GPL(bio_await); +/** + * submit_bio_wait - submit a bio, and wait until it completes + * @bio: The &struct bio which describes the I/O + * + * Simple wrapper around submit_bio(). Returns 0 on success, or the error from + * bio_endio() on failure. + * + * WARNING: Unlike to how submit_bio() is usually used, this function does not + * result in bio reference to be consumed. The caller must drop the reference + * on his own. + */ +int submit_bio_wait(struct bio *bio) +{ + bio_await(bio, NULL, NULL); return blk_status_to_errno(bio->bi_status); } EXPORT_SYMBOL(submit_bio_wait); +static void bio_endio_cb(struct bio *bio, void *priv) +{ + bio_endio(bio); +} + /** * bdev_rw_virt - synchronously read into / write from kernel mapping * @bdev: block device to access @@ -1547,13 +1574,7 @@ EXPORT_SYMBOL_GPL(bdev_rw_virt); */ void bio_await_chain(struct bio *bio) { - DECLARE_COMPLETION_ONSTACK_MAP(done, - bio->bi_bdev->bd_disk->lockdep_map); - - bio->bi_private = &done; - bio->bi_end_io = bio_wait_end_io; - bio_endio(bio); - blk_wait_io(&done); + bio_await(bio, NULL, bio_endio_cb); bio_put(bio); } diff --git a/include/linux/bio.h b/include/linux/bio.h index 36a3f2275ecd..3059dcaf6d5e 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -433,6 +433,8 @@ extern void bio_uninit(struct bio *); void bio_reset(struct bio *bio, struct block_device *bdev, blk_opf_t opf); void bio_reuse(struct bio *bio, blk_opf_t opf); void bio_chain(struct bio *, struct bio *); +void bio_await(struct bio *bio, void *priv, + void (*submit)(struct bio *bio, void *priv)); int __must_check bio_add_page(struct bio *bio, struct page *page, unsigned len, unsigned off); From e375dc540ad84fdb85b303c11a253fe0c465f39e Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 7 Apr 2026 16:05:27 +0200 Subject: [PATCH 4/5] block: add a bio_submit_or_kill helper Factor the common logic for the ioctl helpers to either submit a bio or end if the process is being killed. As this is now the only user of bio_await_chain, open code that. Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Bart Van Assche --- block/bio.c | 23 ++++++++++++++--------- block/blk-lib.c | 16 ++-------------- block/blk.h | 2 +- block/ioctl.c | 11 ++--------- 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/block/bio.c b/block/bio.c index 373795491287..ba4691905d34 100644 --- a/block/bio.c +++ b/block/bio.c @@ -1539,6 +1539,20 @@ static void bio_endio_cb(struct bio *bio, void *priv) bio_endio(bio); } +/* + * Submit @bio synchronously, or call bio_endio on it if the current process + * is being killed. + */ +int bio_submit_or_kill(struct bio *bio, unsigned int flags) +{ + if ((flags & BLKDEV_ZERO_KILLABLE) && fatal_signal_pending(current)) { + bio_await(bio, NULL, bio_endio_cb); + return -EINTR; + } + + return submit_bio_wait(bio); +} + /** * bdev_rw_virt - synchronously read into / write from kernel mapping * @bdev: block device to access @@ -1569,15 +1583,6 @@ int bdev_rw_virt(struct block_device *bdev, sector_t sector, void *data, } EXPORT_SYMBOL_GPL(bdev_rw_virt); -/* - * bio_await_chain - ends @bio and waits for every chained bio to complete - */ -void bio_await_chain(struct bio *bio) -{ - bio_await(bio, NULL, bio_endio_cb); - bio_put(bio); -} - void __bio_advance(struct bio *bio, unsigned bytes) { if (bio_integrity(bio)) diff --git a/block/blk-lib.c b/block/blk-lib.c index 3213afc7f0d5..688bc67cbf73 100644 --- a/block/blk-lib.c +++ b/block/blk-lib.c @@ -155,13 +155,7 @@ static int blkdev_issue_write_zeroes(struct block_device *bdev, sector_t sector, __blkdev_issue_write_zeroes(bdev, sector, nr_sects, gfp, &bio, flags, limit); if (bio) { - if ((flags & BLKDEV_ZERO_KILLABLE) && - fatal_signal_pending(current)) { - bio_await_chain(bio); - blk_finish_plug(&plug); - return -EINTR; - } - ret = submit_bio_wait(bio); + ret = bio_submit_or_kill(bio, flags); bio_put(bio); } blk_finish_plug(&plug); @@ -236,13 +230,7 @@ static int blkdev_issue_zero_pages(struct block_device *bdev, sector_t sector, blk_start_plug(&plug); __blkdev_issue_zero_pages(bdev, sector, nr_sects, gfp, &bio, flags); if (bio) { - if ((flags & BLKDEV_ZERO_KILLABLE) && - fatal_signal_pending(current)) { - bio_await_chain(bio); - blk_finish_plug(&plug); - return -EINTR; - } - ret = submit_bio_wait(bio); + ret = bio_submit_or_kill(bio, flags); bio_put(bio); } blk_finish_plug(&plug); diff --git a/block/blk.h b/block/blk.h index f6053e9dd2aa..c69beafa1786 100644 --- a/block/blk.h +++ b/block/blk.h @@ -55,7 +55,7 @@ bool __blk_freeze_queue_start(struct request_queue *q, struct task_struct *owner); int __bio_queue_enter(struct request_queue *q, struct bio *bio); void submit_bio_noacct_nocheck(struct bio *bio, bool split); -void bio_await_chain(struct bio *bio); +int bio_submit_or_kill(struct bio *bio, unsigned int flags); static inline bool blk_try_enter_queue(struct request_queue *q, bool pm) { diff --git a/block/ioctl.c b/block/ioctl.c index 0b04661ac809..fc3be0549aa7 100644 --- a/block/ioctl.c +++ b/block/ioctl.c @@ -153,13 +153,7 @@ static int blk_ioctl_discard(struct block_device *bdev, blk_mode_t mode, nr_sects = len >> SECTOR_SHIFT; blk_start_plug(&plug); - while (1) { - if (fatal_signal_pending(current)) { - if (prev) - bio_await_chain(prev); - err = -EINTR; - goto out_unplug; - } + while (!fatal_signal_pending(current)) { bio = blk_alloc_discard_bio(bdev, §or, &nr_sects, GFP_KERNEL); if (!bio) @@ -167,12 +161,11 @@ static int blk_ioctl_discard(struct block_device *bdev, blk_mode_t mode, prev = bio_chain_and_submit(prev, bio); } if (prev) { - err = submit_bio_wait(prev); + err = bio_submit_or_kill(prev, BLKDEV_ZERO_KILLABLE); if (err == -EOPNOTSUPP) err = 0; bio_put(prev); } -out_unplug: blk_finish_plug(&plug); fail: filemap_invalidate_unlock(bdev->bd_mapping); From 24a5ec87d81f47fd0ca5877625b8e53fb864e365 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 7 Apr 2026 16:05:28 +0200 Subject: [PATCH 5/5] xfs: use bio_await in xfs_zone_gc_reset_sync Replace the open-coded bio wait logic with the new bio_await helper. Signed-off-by: Christoph Hellwig Reviewed-by: Damien Le Moal Reviewed-by: Chaitanya Kulkarni --- fs/xfs/xfs_zone_gc.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c index eebe6f4fee1f..b2626a482563 100644 --- a/fs/xfs/xfs_zone_gc.c +++ b/fs/xfs/xfs_zone_gc.c @@ -900,9 +900,10 @@ xfs_zone_gc_finish_reset( static void xfs_submit_zone_reset_bio( - struct xfs_rtgroup *rtg, - struct bio *bio) + struct bio *bio, + void *priv) { + struct xfs_rtgroup *rtg = priv; struct xfs_mount *mp = rtg_mount(rtg); trace_xfs_zone_reset(rtg); @@ -934,26 +935,16 @@ xfs_submit_zone_reset_bio( submit_bio(bio); } -static void xfs_bio_wait_endio(struct bio *bio) -{ - complete(bio->bi_private); -} - int xfs_zone_gc_reset_sync( struct xfs_rtgroup *rtg) { - DECLARE_COMPLETION_ONSTACK(done); struct bio bio; int error; bio_init(&bio, rtg_mount(rtg)->m_rtdev_targp->bt_bdev, NULL, 0, REQ_OP_ZONE_RESET | REQ_SYNC); - bio.bi_private = &done; - bio.bi_end_io = xfs_bio_wait_endio; - xfs_submit_zone_reset_bio(rtg, &bio); - wait_for_completion_io(&done); - + bio_await(&bio, rtg, xfs_submit_zone_reset_bio); error = blk_status_to_errno(bio.bi_status); bio_uninit(&bio); return error; @@ -990,7 +981,7 @@ xfs_zone_gc_reset_zones( chunk->data = data; WRITE_ONCE(chunk->state, XFS_GC_BIO_NEW); list_add_tail(&chunk->entry, &data->resetting); - xfs_submit_zone_reset_bio(rtg, bio); + xfs_submit_zone_reset_bio(bio, rtg); } while (next); }