[RFC PATCH 1/4] buffer: writeback: extract shared bio build/complete helpers

From: Lukas Sichert

Date: Fri Oct 09 2026 - 05:59:46 EST


Split the bio allocation out of __bh_submit() and the folio writeback
completion out of bh_end_async_write() into helpers, so that other
submit and completion paths can share them. This prevents code
duplication and code drift.

As this is a pure refactor the behavior of __bh_submit() and
bh_end_async_write() is unchanged.

Signed-off-by: Lukas Sichert <l.sichert@xxxxxxxxxxx>
---
fs/buffer.c | 89 +++++++++++++++++++++++++++++++----------------------
1 file changed, 53 insertions(+), 36 deletions(-)

diff --git a/fs/buffer.c b/fs/buffer.c
index ed966fa73b1b..3389d866a85c 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -386,27 +386,10 @@ static void bh_end_async_read(struct bio *bio)
end_buffer_async_read(bh, uptodate);
}

-/**
- * bh_end_async_write - I/O end handler for async folio writes
- * @bio: The bio being completed.
- *
- * Pass this function to bh_submit() if you're doing the equivalent of
- * block_write_full_folio(). That is, the folio is unlocked, and will
- * have its writeback flag cleared once all async write buffers have
- * completed.
- */
-void bh_end_async_write(struct bio *bio)
+static void bh_complete_write(struct buffer_head *bh, bool success)
{
- struct buffer_head *bh;
- bool success = bio_endio_bh(bio, &bh);
- unsigned long flags;
- struct buffer_head *first;
- struct buffer_head *tmp;
- struct folio *folio;
-
BUG_ON(!buffer_async_write(bh));

- folio = bh->b_folio;
if (success) {
set_buffer_uptodate(bh);
} else {
@@ -414,8 +397,14 @@ void bh_end_async_write(struct bio *bio)
mark_buffer_write_io_error(bh);
clear_buffer_uptodate(bh);
}
+}
+
+static void bh_complete_folio_write(struct folio *folio, struct buffer_head *bh)
+{
+ struct buffer_head *first = folio_buffers(folio);
+ struct buffer_head *tmp;
+ unsigned long flags;

- first = folio_buffers(folio);
spin_lock_irqsave(&first->b_uptodate_lock, flags);

clear_buffer_async_write(bh);
@@ -435,6 +424,25 @@ void bh_end_async_write(struct bio *bio)
still_busy:
spin_unlock_irqrestore(&first->b_uptodate_lock, flags);
}
+
+/**
+ * bh_end_async_write - I/O end handler for async folio writes
+ * @bio: The bio being completed.
+ *
+ * Pass this function to bh_submit() if you're doing the equivalent of
+ * block_write_full_folio(). That is, the folio is unlocked, and will
+ * have its writeback flag cleared once all async write buffers have
+ * completed.
+ */
+void bh_end_async_write(struct bio *bio)
+{
+ struct buffer_head *bh;
+ bool success = bio_endio_bh(bio, &bh);
+ struct folio *folio = bh->b_folio;
+
+ bh_complete_write(bh, success);
+ bh_complete_folio_write(folio, bh);
+}
EXPORT_SYMBOL(bh_end_async_write);


@@ -1082,24 +1090,11 @@ static void buffer_set_crypto_ctx(struct bio *bio, const struct buffer_head *bh,
folio_pos(bh->b_folio) + bh_offset(bh), gfp_mask);
}

-static void __bh_submit(struct buffer_head *bh, blk_opf_t opf,
- enum rw_hint write_hint, struct writeback_control *wbc,
- bio_end_io_t end_bio)
+static struct bio *bh_bio_alloc(struct buffer_head *bh, blk_opf_t opf,
+ enum rw_hint write_hint, bio_end_io_t end_bio)
{
- const enum req_op op = opf & REQ_OP_MASK;
struct bio *bio;

- BUG_ON(!buffer_locked(bh));
- BUG_ON(!buffer_mapped(bh));
- BUG_ON(buffer_delay(bh));
- BUG_ON(buffer_unwritten(bh));
-
- /*
- * Only clear out a write error when rewriting
- */
- if (test_set_buffer_req(bh) && (op == REQ_OP_WRITE))
- clear_buffer_write_io_error(bh);
-
if (buffer_meta(bh))
opf |= REQ_META;
if (buffer_prio(bh))
@@ -1115,10 +1110,32 @@ static void __bh_submit(struct buffer_head *bh, blk_opf_t opf,

bio->bi_iter.bi_sector = bh->b_blocknr * (bh->b_size >> 9);
bio->bi_write_hint = write_hint;
+ bio->bi_end_io = end_bio;

- bio_add_folio_nofail(bio, bh->b_folio, bh->b_size, bh_offset(bh));
+ return bio;
+}

- bio->bi_end_io = end_bio;
+static void __bh_submit(struct buffer_head *bh, blk_opf_t opf,
+ enum rw_hint write_hint, struct writeback_control *wbc,
+ bio_end_io_t end_bio)
+{
+ const enum req_op op = opf & REQ_OP_MASK;
+ struct bio *bio;
+
+ BUG_ON(!buffer_locked(bh));
+ BUG_ON(!buffer_mapped(bh));
+ BUG_ON(buffer_delay(bh));
+ BUG_ON(buffer_unwritten(bh));
+
+ /*
+ * Only clear out a write error when rewriting
+ */
+ if (test_set_buffer_req(bh) && (op == REQ_OP_WRITE))
+ clear_buffer_write_io_error(bh);
+
+ bio = bh_bio_alloc(bh, opf, write_hint, end_bio);
+
+ bio_add_folio_nofail(bio, bh->b_folio, bh->b_size, bh_offset(bh));
bio->bi_private = bh;

/* Take care of bh's that straddle the end of the device */