* lazy bounce buffering for checksummed reads v3
@ 2026-09-09 6:08 Christoph Hellwig
2026-09-09 6:08 ` [PATCH 01/16] block: split bio_iov_iter_bounce_write Christoph Hellwig
` (16 more replies)
0 siblings, 17 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Hi all,
this series improves performance and resource usage for reads from
devices that require stable pages due to checksumming on XFS.
Currently XFS unconditionally bounce buffers reads on such devices to
prevent user modifications to the buffer from corrupting the data,
leading to checksum failures.
This uses DRAM bandwidth and CPU cycles for copies that are not needed
most of the time, and due to the use of a bio_vec for the bounce
buffer to smaller than wanted and unaligned I/O sizes when using 4k
user pages (i.e. 1MB-4k I/O).
This series addresses this by reading without the bounce buffer first,
and then only allocating a buffer and reading into that again on an
initial checksum failure. To accommodate for rare (or hypothetical?)
applications that have legitimate needs to frequently modify in-flight
buffers, a sysfs know is provided to revert to the old behavior.
NOTE/QUESTION TO SUBSYSTEM MAINTAINERS: The patches in this series are
split over 3 subsystems, and I'd love to hear from the maintainers
about their preferences for merging this.
The baseline of this series is mainline with the
"misc block PI / bounce buffering fixes" series.
A git tree is available to help with the review here:
git://git.infradead.org/users/hch/misc.git lazy-bounce
Gitweb:
https://git.infradead.org/?p=users/hch/misc.git;a=shortlog;h=refs/heads/lazy-bounce
Changes since v2:
- break out of bio_iov_iter_get_pages when bi_size reaches maxlen
- various fixes for pre-existing issues pointed out by Sashiko
- generate zone PI a bit earlier
- initialize the csum dir in sysfs unconditionally to protect against the
rather theoretical case of the flag changing underneath us
- redo sysfs initialization order to avoid a cleanup bug
- use NOFS allocations in xfs_read_bounce_and_resubmit
- rework bio setup for reissue to not leave land mines for other uses
- fix commit message typos
- fix comment typos
Changes since v1:
- rebase on 7.3rc-1 and xfs for-next, which has the bio complete in
task support merged
- remove the now unused IOMAP_DIO_BOUNCE support for reads
- fix compilation with integrity disabled
- make I/O size limitation actually work
- clear REQ_POLLED when bounce buffering
- use sysfs string match helpers to allow non -n echo
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 01/16] block: split bio_iov_iter_bounce_write
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:08 ` [PATCH 02/16] block: export fs_bio_integrity_{alloc,free} Christoph Hellwig
` (15 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Factor out a bio_alloc_bounce_folios helper that we'll use for a
different take on read-side bounce buffering soon.
For that make it and also bio_free_folios available to callers outside of
bio.c.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
block/bio.c | 58 ++++++++++++++++++++++++++-------------------
include/linux/bio.h | 2 ++
2 files changed, 35 insertions(+), 25 deletions(-)
diff --git a/block/bio.c b/block/bio.c
index b89c02f363ce..5792a059ef2a 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -1356,7 +1356,7 @@ static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size,
return folio_alloc(gfp, get_order(*size));
}
-static void bio_free_folios(struct bio *bio)
+void bio_free_folios(struct bio *bio)
{
struct bio_vec *bv;
int i;
@@ -1369,11 +1369,8 @@ static void bio_free_folios(struct bio *bio)
}
}
-static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
- size_t maxlen, size_t minsize)
+int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize)
{
- size_t total_len = min(maxlen, iov_iter_count(iter));
-
if (WARN_ON_ONCE(bio_flagged(bio, BIO_CLONED)))
return -EINVAL;
if (WARN_ON_ONCE(bio->bi_iter.bi_size))
@@ -1383,7 +1380,6 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
do {
size_t this_len = min(total_len, SZ_1M);
- size_t copied;
struct folio *folio;
if (this_len > minsize * 2)
@@ -1404,32 +1400,44 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
*/
this_len &= ~(minsize - 1);
bio_add_folio_nofail(bio, folio, this_len, 0);
+ total_len -= this_len;
+ } while (total_len && bio->bi_vcnt < bio->bi_max_vecs);
+
+ if (!bio->bi_iter.bi_size)
+ return -ENOMEM;
+ return 0;
+}
+
+static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
+ size_t maxlen, size_t minsize)
+{
+ size_t total_len = min(maxlen, iov_iter_count(iter));
+ size_t total_copied = 0;
+ struct bio_vec *bv;
+ int i, error;
+
+ error = bio_alloc_bounce_folios(bio, total_len, minsize);
+ if (error)
+ return error;
+
+ bio_for_each_bvec_all(bv, bio, i) {
+ struct folio *folio = page_folio(bv->bv_page);
+ size_t copied;
if (iter->nofault)
- copied = copy_folio_from_iter_atomic(folio, 0, this_len,
- iter);
+ copied = copy_folio_from_iter_atomic(folio, 0,
+ bv->bv_len, iter);
else
- copied = copy_folio_from_iter(folio, 0, this_len, iter);
- if (copied < this_len) {
- /*
- * Need to revert the iov iter for all bytes we have
- * copied.
- *
- * However the bio size differs from the real copied
- * bytes as @this_len is queued but only advanced
- * less than that.
- * Need to compensate that for the revert.
- */
- iov_iter_revert(iter, bio->bi_iter.bi_size - this_len +
- copied);
+ copied = copy_folio_from_iter(folio, 0, bv->bv_len,
+ iter);
+ total_copied += copied;
+ if (copied < bv->bv_len) {
+ iov_iter_revert(iter, total_copied);
bio_free_folios(bio);
return -EFAULT;
}
- total_len -= this_len;
- } while (total_len && bio->bi_vcnt < bio->bi_max_vecs);
+ }
- if (!bio->bi_iter.bi_size)
- return -ENOMEM;
return 0;
}
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 0070be355181..584b6abf6baf 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -527,6 +527,8 @@ extern void bio_check_pages_dirty(struct bio *bio);
int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen,
size_t minsize);
void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty);
+int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize);
+void bio_free_folios(struct bio *bio);
extern void bio_copy_data(struct bio *dst, struct bio *src);
extern void bio_free_pages(struct bio *bio);
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 02/16] block: export fs_bio_integrity_{alloc,free}
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
2026-09-09 6:08 ` [PATCH 01/16] block: split bio_iov_iter_bounce_write Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:08 ` [PATCH 03/16] block: add a bio_prepare_reissue helper Christoph Hellwig
` (14 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
We'll move integrity generation and verification into the iomap
submission helpers, which means they will be needed in modular file
system code.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
block/bio-integrity-fs.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/block/bio-integrity-fs.c b/block/bio-integrity-fs.c
index 692403dfa047..4f67b34bf289 100644
--- a/block/bio-integrity-fs.c
+++ b/block/bio-integrity-fs.c
@@ -31,6 +31,7 @@ unsigned int fs_bio_integrity_alloc(struct bio *bio)
bio_integrity_setup_default(bio);
return action;
}
+EXPORT_SYMBOL_GPL(fs_bio_integrity_alloc);
void fs_bio_integrity_free(struct bio *bio)
{
@@ -43,6 +44,7 @@ void fs_bio_integrity_free(struct bio *bio)
bio->bi_integrity = NULL;
bio->bi_opf &= ~REQ_INTEGRITY;
}
+EXPORT_SYMBOL_GPL(fs_bio_integrity_free);
void fs_bio_integrity_generate(struct bio *bio)
{
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 03/16] block: add a bio_prepare_reissue helper
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
2026-09-09 6:08 ` [PATCH 01/16] block: split bio_iov_iter_bounce_write Christoph Hellwig
2026-09-09 6:08 ` [PATCH 02/16] block: export fs_bio_integrity_{alloc,free} Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 16:05 ` Darrick J. Wong
2026-09-09 6:08 ` [PATCH 04/16] iomap: respect maximum I/O size in iomap_dio_bio_iter_one Christoph Hellwig
` (13 subsequent siblings)
16 siblings, 1 reply; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Add a helper to a clear a bio for reissue without a lot of the
pointless clearing and reinitializing done by bio_reset and bio_reuse,
and keeping the page pinning flag intact.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/bio.c | 20 ++++++++++++++++++++
include/linux/bio.h | 1 +
2 files changed, 21 insertions(+)
diff --git a/block/bio.c b/block/bio.c
index 5792a059ef2a..7f7654e60dd4 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -320,6 +320,26 @@ void bio_reuse(struct bio *bio, blk_opf_t opf)
}
EXPORT_SYMBOL_GPL(bio_reuse);
+/**
+ * bio_prepare_reissue - prepare a bio for reuissing the original I/O
+ * @bio: bio to reuse
+ * @bdev: block device to use the bio for
+ *
+ * Prepare @bio to be resubmitted to retry the original operation.
+ * The caller must reset bio->bi_iter to the original state.
+ */
+void bio_prepare_reissue(struct bio *bio, struct block_device *bdev)
+{
+ bio->bi_bdev = bdev;
+ bio_associate_blkg(bio);
+ bio->bi_flags &=
+ (BIO_PAGE_PINNED | BIO_CLONED | BIO_QUIET | BIO_REFFED);
+ bio->bi_status = BLK_STS_OK;
+ bio->bi_bvec_gap_bit = 0;
+ atomic_set(&bio->__bi_remaining, 1);
+}
+EXPORT_SYMBOL_GPL(bio_prepare_reissue);
+
static struct bio *__bio_chain_endio(struct bio *bio)
{
struct bio *parent = bio->bi_private;
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 584b6abf6baf..bfa3c0e97b6f 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -479,6 +479,7 @@ static inline void bio_init_inline(struct bio *bio, struct block_device *bdev,
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_prepare_reissue(struct bio *bio, struct block_device *bdev);
void bio_chain(struct bio *, struct bio *);
void bio_await(struct bio *bio, void *priv,
void (*submit)(struct bio *bio, void *priv));
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 04/16] iomap: respect maximum I/O size in iomap_dio_bio_iter_one
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (2 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 03/16] block: add a bio_prepare_reissue helper Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:08 ` [PATCH 05/16] iomap: add a iomap_ioend_flags helper Christoph Hellwig
` (12 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Respect the maximum I/O size set for PI-enabled I/O in
iomap_dio_bio_iter_one, otherwise bio_integrity_alloc_buf could
under-allocate the integrity buffer when the initial kmalloc fails.
Currently this should not be triggered as file systems that limit the
size for PI always use bounce buffering, but this will change soon.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/iomap/direct-io.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index bc726d47b7dc..91f5b67718a5 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -336,6 +336,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
struct iomap_dio *dio, loff_t pos, unsigned int alignment,
blk_opf_t op)
{
+ unsigned int maxsize = iomap_max_bio_size(&iter->iomap);
unsigned int nr_vecs;
struct bio *bio;
ssize_t ret;
@@ -353,14 +354,12 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
bio->bi_private = dio;
bio->bi_end_io = iomap_dio_bio_end_io;
-
if (dio->flags & IOMAP_DIO_BOUNCE)
- ret = bio_iov_iter_bounce(bio, dio->submit.iter,
- iomap_max_bio_size(&iter->iomap), alignment);
+ ret = bio_iov_iter_bounce(bio, dio->submit.iter, maxsize,
+ alignment);
else
- ret = bio_iov_iter_get_pages(bio, dio->submit.iter,
- BIO_MAX_SIZE, bdev_dma_alignment(bio->bi_bdev),
- alignment - 1);
+ ret = bio_iov_iter_get_pages(bio, dio->submit.iter, maxsize,
+ bdev_dma_alignment(bio->bi_bdev), alignment - 1);
if (unlikely(ret))
goto out_put_bio;
ret = bio->bi_iter.bi_size;
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 05/16] iomap: add a iomap_ioend_flags helper
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (3 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 04/16] iomap: respect maximum I/O size in iomap_dio_bio_iter_one Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:08 ` [PATCH 06/16] iomap: add a IOMAP_IOEND_INTEGRITY flag Christoph Hellwig
` (11 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Add a helper to initialize the ioend flags to the values that can be
directly derived from the iomap.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/iomap/ioend.c | 10 +++-------
include/linux/iomap.h | 12 ++++++++++++
2 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 7bbbb417f915..2d77c11e3c18 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -215,7 +215,7 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
{
struct iomap_ioend *ioend = wpc->wb_ctx;
size_t poff = offset_in_folio(folio, pos);
- unsigned int ioend_flags = 0;
+ unsigned int ioend_flags = iomap_ioend_flags(&wpc->iomap);
unsigned int map_len = min_t(u64, dirty_len,
wpc->iomap.offset + wpc->iomap.length - pos);
int error;
@@ -225,20 +225,16 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
WARN_ON_ONCE(!folio->private && map_len < dirty_len);
switch (wpc->iomap.type) {
+ case IOMAP_HOLE:
+ return map_len;
case IOMAP_UNWRITTEN:
- ioend_flags |= IOMAP_IOEND_UNWRITTEN;
- break;
case IOMAP_MAPPED:
break;
- case IOMAP_HOLE:
- return map_len;
default:
WARN_ON_ONCE(1);
return -EIO;
}
- if (wpc->iomap.flags & IOMAP_F_SHARED)
- ioend_flags |= IOMAP_IOEND_SHARED;
if (pos == wpc->iomap.offset && (wpc->iomap.flags & IOMAP_F_BOUNDARY))
ioend_flags |= IOMAP_IOEND_BOUNDARY;
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index bc7ae6327dbf..8e09b2d4eda0 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -491,6 +491,18 @@ sector_t iomap_bmap(struct address_space *mapping, sector_t bno,
#define IOMAP_IOEND_NOMERGE_FLAGS \
(IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT)
+/* ioend flags directly implied by iomap flags */
+static inline u16 iomap_ioend_flags(const struct iomap *iomap)
+{
+ unsigned int flags = 0;
+
+ if (iomap->type == IOMAP_UNWRITTEN)
+ flags |= IOMAP_IOEND_UNWRITTEN;
+ if (iomap->flags & IOMAP_F_SHARED)
+ flags |= IOMAP_IOEND_SHARED;
+ return flags;
+}
+
/*
* Structure for writeback I/O completions.
*
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 06/16] iomap: add a IOMAP_IOEND_INTEGRITY flag
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (4 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 05/16] iomap: add a iomap_ioend_flags helper Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:08 ` [PATCH 07/16] iomap,xfs: move T10 PI handling for direct I/O into ->submit_io Christoph Hellwig
` (10 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
This will make it easier to share code for the various submit callback
provided by the file system to iomap.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/iomap/ioend.c | 2 +-
fs/xfs/xfs_aops.c | 5 +++--
fs/xfs/xfs_file.c | 5 +++--
include/linux/iomap.h | 12 +++++++++++-
4 files changed, 18 insertions(+), 6 deletions(-)
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 2d77c11e3c18..573fa89c1632 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -149,7 +149,7 @@ int iomap_ioend_writeback_submit(struct iomap_writepage_ctx *wpc, int error)
return error;
}
- if (wpc->iomap.flags & IOMAP_F_INTEGRITY)
+ if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
fs_bio_integrity_generate(&ioend->io_bio);
submit_bio(&ioend->io_bio);
return 0;
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 8b6119776fb3..4e862406eee9 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -498,7 +498,7 @@ xfs_zoned_writeback_submit(
bio_endio(&ioend->io_bio);
return error;
}
- if (wpc->iomap.flags & IOMAP_F_INTEGRITY)
+ if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
fs_bio_integrity_generate(&ioend->io_bio);
xfs_zone_alloc_and_submit(ioend, &XFS_ZWPC(wpc)->open_zone);
return 0;
@@ -588,7 +588,8 @@ xfs_bio_submit_read(
struct bio *bio = ctx->read_ctx;
/* defer read completions to the ioend workqueue */
- iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset, 0);
+ iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset,
+ iomap_ioend_flags(&iter->iomap));
iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio);
}
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 426a67b813a7..4d7a284fcff9 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -241,7 +241,8 @@ xfs_dio_read_bounce_submit_io(
struct bio *bio,
loff_t file_offset)
{
- iomap_init_ioend(iter->inode, bio, file_offset, IOMAP_IOEND_DIRECT);
+ iomap_init_ioend(iter->inode, bio, file_offset,
+ iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
bio->bi_end_io = xfs_end_bio;
submit_bio(bio);
}
@@ -732,7 +733,7 @@ xfs_dio_zoned_submit_io(
bio->bi_end_io = xfs_end_bio;
ioend = iomap_init_ioend(iter->inode, bio, file_offset,
- IOMAP_IOEND_DIRECT);
+ iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
xfs_zone_alloc_and_submit(ioend, &ac->open_zone);
}
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 8e09b2d4eda0..1cc9a35fd5cc 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -483,13 +483,20 @@ sector_t iomap_bmap(struct address_space *mapping, sector_t bno,
#define IOMAP_IOEND_BOUNDARY (1U << 2)
/* is direct I/O */
#define IOMAP_IOEND_DIRECT (1U << 3)
+/* generate integrity (PI) information */
+#ifdef CONFIG_BLK_DEV_INTEGRITY
+#define IOMAP_IOEND_INTEGRITY (1U << 4)
+#else
+#define IOMAP_IOEND_INTEGRITY 0
+#endif /* CONFIG_BLK_DEV_INTEGRITY */
/*
* Flags that if set on either ioend prevent the merge of two ioends.
* (IOMAP_IOEND_BOUNDARY also prevents merges, but only one-way)
*/
#define IOMAP_IOEND_NOMERGE_FLAGS \
- (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT)
+ (IOMAP_IOEND_SHARED | IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_DIRECT | \
+ IOMAP_IOEND_INTEGRITY)
/* ioend flags directly implied by iomap flags */
static inline u16 iomap_ioend_flags(const struct iomap *iomap)
@@ -500,6 +507,9 @@ static inline u16 iomap_ioend_flags(const struct iomap *iomap)
flags |= IOMAP_IOEND_UNWRITTEN;
if (iomap->flags & IOMAP_F_SHARED)
flags |= IOMAP_IOEND_SHARED;
+ if (iomap->flags & IOMAP_F_INTEGRITY)
+ flags |= IOMAP_IOEND_INTEGRITY;
+
return flags;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 07/16] iomap,xfs: move T10 PI handling for direct I/O into ->submit_io
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (5 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 06/16] iomap: add a IOMAP_IOEND_INTEGRITY flag Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:08 ` [PATCH 08/16] xfs: move PI generation into xfs_submit_zoned_bio Christoph Hellwig
` (9 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Currently the buffered I/O code defers PI handling into the submit
handlers, while direct I/O does it in common code. Move the direct I/O
side into the file system callbacks or their generic implementations to
be consistent, and to allow file systems to shared helpers for submission
of buffered and direct bios.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/iomap/direct-io.c | 22 ++++++++++++----------
fs/xfs/xfs_file.c | 13 ++++++++++---
2 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index 91f5b67718a5..4154717a09de 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -76,10 +76,19 @@ static void iomap_dio_submit_bio(const struct iomap_iter *iter,
if (dio->dops && dio->dops->submit_io) {
dio->dops->submit_io(iter, bio, pos);
- } else {
- WARN_ON_ONCE(iter->iomap.flags & IOMAP_F_ANON_WRITE);
- blk_crypto_submit_bio(bio);
+ return;
+ }
+
+ WARN_ON_ONCE(iter->iomap.flags & IOMAP_F_ANON_WRITE);
+
+ if (iter->iomap.flags & IOMAP_F_INTEGRITY) {
+ if (dio->flags & IOMAP_DIO_WRITE)
+ fs_bio_integrity_generate(bio);
+ else
+ fs_bio_integrity_alloc(bio);
}
+
+ blk_crypto_submit_bio(bio);
}
static inline enum fserror_type iomap_dio_err_type(const struct iomap_dio *dio)
@@ -373,13 +382,6 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
goto out_bio_release_pages;
}
- if (iter->iomap.flags & IOMAP_F_INTEGRITY) {
- if (dio->flags & IOMAP_DIO_WRITE)
- fs_bio_integrity_generate(bio);
- else
- fs_bio_integrity_alloc(bio);
- }
-
if (dio->flags & IOMAP_DIO_WRITE)
task_io_account_write(ret);
else if ((dio->flags & IOMAP_DIO_USER_BACKED) &&
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 4d7a284fcff9..acbfd55f56a3 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -37,6 +37,7 @@
#include <linux/fadvise.h>
#include <linux/mount.h>
#include <linux/filelock.h>
+#include <linux/bio-integrity.h>
static const struct vm_operations_struct xfs_file_vm_ops;
@@ -241,8 +242,12 @@ xfs_dio_read_bounce_submit_io(
struct bio *bio,
loff_t file_offset)
{
- iomap_init_ioend(iter->inode, bio, file_offset,
- iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
+ struct iomap_ioend *ioend;
+
+ ioend = iomap_init_ioend(iter->inode, bio, file_offset,
+ iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
+ if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
+ fs_bio_integrity_alloc(bio);
bio->bi_end_io = xfs_end_bio;
submit_bio(bio);
}
@@ -733,7 +738,9 @@ xfs_dio_zoned_submit_io(
bio->bi_end_io = xfs_end_bio;
ioend = iomap_init_ioend(iter->inode, bio, file_offset,
- iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
+ iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
+ if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
+ fs_bio_integrity_generate(bio);
xfs_zone_alloc_and_submit(ioend, &ac->open_zone);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 08/16] xfs: move PI generation into xfs_submit_zoned_bio
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (6 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 07/16] iomap,xfs: move T10 PI handling for direct I/O into ->submit_io Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 16:06 ` Darrick J. Wong
2026-09-09 6:08 ` [PATCH 09/16] block,iomap: fix protection information verification with initial bvec offset Christoph Hellwig
` (8 subsequent siblings)
16 siblings, 1 reply; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Keep the code in one place.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_aops.c | 4 +---
fs/xfs/xfs_file.c | 2 --
fs/xfs/xfs_zone_alloc.c | 4 ++++
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 4e862406eee9..d2cdb7cffa6b 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -23,7 +23,6 @@
#include "xfs_ioend.h"
#include "xfs_zone_alloc.h"
#include "xfs_rtgroup.h"
-#include <linux/bio-integrity.h>
struct xfs_writepage_ctx {
struct iomap_writepage_ctx ctx;
@@ -498,8 +497,7 @@ xfs_zoned_writeback_submit(
bio_endio(&ioend->io_bio);
return error;
}
- if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
- fs_bio_integrity_generate(&ioend->io_bio);
+
xfs_zone_alloc_and_submit(ioend, &XFS_ZWPC(wpc)->open_zone);
return 0;
}
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index acbfd55f56a3..0e029db2e85b 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -739,8 +739,6 @@ xfs_dio_zoned_submit_io(
bio->bi_end_io = xfs_end_bio;
ioend = iomap_init_ioend(iter->inode, bio, file_offset,
iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
- if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
- fs_bio_integrity_generate(bio);
xfs_zone_alloc_and_submit(ioend, &ac->open_zone);
}
diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c
index bdbb60cc5d5b..bede79859671 100644
--- a/fs/xfs/xfs_zone_alloc.c
+++ b/fs/xfs/xfs_zone_alloc.c
@@ -26,6 +26,7 @@
#include "xfs_zones.h"
#include "xfs_trace.h"
#include "xfs_mru_cache.h"
+#include <linux/bio-integrity.h>
static void
xfs_open_zone_free_rcu(
@@ -909,6 +910,9 @@ xfs_zone_alloc_and_submit(
if (xfs_is_shutdown(mp))
goto out_error;
+ if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
+ fs_bio_integrity_generate(&ioend->io_bio);
+
/*
* If we don't have a locally cached zone in this write context, see if
* the inode is still associated with a zone and use that if so.
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 09/16] block,iomap: fix protection information verification with initial bvec offset
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (7 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 08/16] xfs: move PI generation into xfs_submit_zoned_bio Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:08 ` [PATCH 10/16] iomap: better read bounce buffering support Christoph Hellwig
` (7 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
When reconstructing a bvec_iter from an ioend for protection information
verification, iomap currently ignores the offset into the initial
bio_vec.
This can't happen for buffered I/O or direct I/O to user addresses, but
is exercised by splice on O_DIRECT file descriptors or when using the
loop driver in direct I/O mode.
Fortunately the only file system PI user (XFS) currently always bounce
buffers, so this can't actually be triggered yet. But we'll want to make
the bounce buffering conditional soon, for which this needs to be fixed.
Store the initial offset in struct iomap_ioend, and pass a
pre-constructed bvec_iter to fs_bio_integrity_verify. For the
synchronous read case the fix is even simpler as this path can
simply stash away the original bvec_iter.
Fixes: 0bde8a12b554 ("block: add fs_bio_integrity helpers")
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
block/bio-integrity-fs.c | 13 +++++--------
fs/iomap/bio.c | 4 +++-
fs/iomap/ioend.c | 14 ++++++++++----
include/linux/bio-integrity.h | 3 +--
include/linux/iomap.h | 8 ++++++++
5 files changed, 27 insertions(+), 15 deletions(-)
diff --git a/block/bio-integrity-fs.c b/block/bio-integrity-fs.c
index 4f67b34bf289..c8e91ada8ca6 100644
--- a/block/bio-integrity-fs.c
+++ b/block/bio-integrity-fs.c
@@ -54,14 +54,10 @@ void fs_bio_integrity_generate(struct bio *bio)
}
EXPORT_SYMBOL_GPL(fs_bio_integrity_generate);
-int fs_bio_integrity_verify(struct bio *bio, sector_t sector, unsigned int size)
+int fs_bio_integrity_verify(struct bio *bio, struct bvec_iter *data_iter)
{
struct blk_integrity *bi = blk_get_integrity(bio->bi_bdev->bd_disk);
struct bio_integrity_payload *bip = bio_integrity(bio);
- struct bvec_iter data_iter = {
- .bi_sector = sector,
- .bi_size = size,
- };
if (!bip || !(bip->bip_flags & BIP_CHECK_FLAGS))
return 0;
@@ -73,9 +69,10 @@ int fs_bio_integrity_verify(struct bio *bio, sector_t sector, unsigned int size)
* bio. Requires the submitter to remember the sector and the size.
*/
memset(&bip->bip_iter, 0, sizeof(bip->bip_iter));
- bip->bip_iter.bi_sector = sector;
- bip->bip_iter.bi_size = bio_integrity_bytes(bi, size >> SECTOR_SHIFT);
- return blk_status_to_errno(bio_integrity_verify(bio, &data_iter));
+ bip->bip_iter.bi_sector = data_iter->bi_sector;
+ bip->bip_iter.bi_size =
+ bio_integrity_bytes(bi, data_iter->bi_size >> SECTOR_SHIFT);
+ return blk_status_to_errno(bio_integrity_verify(bio, data_iter));
}
static int __init fs_bio_integrity_init(void)
diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c
index 48100c614431..d46c2f8ea18c 100644
--- a/fs/iomap/bio.c
+++ b/fs/iomap/bio.c
@@ -169,6 +169,7 @@ int iomap_bio_read_folio_range_sync(const struct iomap_iter *iter,
{
const struct iomap *srcmap = iomap_iter_srcmap(iter);
sector_t sector = iomap_sector(srcmap, pos);
+ struct bvec_iter saved_iter;
struct bio_vec bvec;
struct bio bio;
int error;
@@ -178,10 +179,11 @@ int iomap_bio_read_folio_range_sync(const struct iomap_iter *iter,
bio_add_folio_nofail(&bio, folio, len, offset_in_folio(folio, pos));
if (srcmap->flags & IOMAP_F_INTEGRITY)
fs_bio_integrity_alloc(&bio);
+ saved_iter = bio.bi_iter;
error = submit_bio_wait(&bio);
if (bio_integrity(&bio)) {
if (!error)
- error = fs_bio_integrity_verify(&bio, sector, len);
+ error = fs_bio_integrity_verify(&bio, &saved_iter);
fs_bio_integrity_free(&bio);
}
bio_uninit(&bio);
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 573fa89c1632..332dbfb2230f 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -25,6 +25,7 @@ struct iomap_ioend *iomap_init_ioend(struct inode *inode,
ioend->io_parent = NULL;
INIT_LIST_HEAD(&ioend->io_list);
ioend->io_flags = ioend_flags;
+ ioend->io_bvec_offset = bio->bi_iter.bi_offset;
ioend->io_inode = inode;
ioend->io_offset = file_offset;
ioend->io_size = bio->bi_iter.bi_size;
@@ -308,6 +309,13 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
}
EXPORT_SYMBOL_GPL(iomap_add_to_ioend);
+static int iomap_ioend_integrity_verify(struct iomap_ioend *ioend)
+{
+ struct bvec_iter data_iter = BVEC_ITER_IOEND(ioend);
+
+ return fs_bio_integrity_verify(&ioend->io_bio, &data_iter);
+}
+
static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
{
if (ioend->io_parent) {
@@ -325,10 +333,8 @@ static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
if (!ioend->io_error &&
bio_integrity(&ioend->io_bio) &&
- bio_op(&ioend->io_bio) == REQ_OP_READ) {
- ioend->io_error = fs_bio_integrity_verify(&ioend->io_bio,
- ioend->io_sector, ioend->io_size);
- }
+ bio_op(&ioend->io_bio) == REQ_OP_READ)
+ ioend->io_error = iomap_ioend_integrity_verify(ioend);
if (ioend->io_flags & IOMAP_IOEND_DIRECT)
return iomap_finish_ioend_direct(ioend);
diff --git a/include/linux/bio-integrity.h b/include/linux/bio-integrity.h
index 0ea2a8bf7efb..a954c97be0b3 100644
--- a/include/linux/bio-integrity.h
+++ b/include/linux/bio-integrity.h
@@ -151,7 +151,6 @@ void bio_integrity_setup_default(struct bio *bio);
unsigned int fs_bio_integrity_alloc(struct bio *bio);
void fs_bio_integrity_free(struct bio *bio);
void fs_bio_integrity_generate(struct bio *bio);
-int fs_bio_integrity_verify(struct bio *bio, sector_t sector,
- unsigned int size);
+int fs_bio_integrity_verify(struct bio *bio, struct bvec_iter *data_iter);
#endif /* _LINUX_BIO_INTEGRITY_H */
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 1cc9a35fd5cc..bffdc217ad85 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -522,6 +522,7 @@ static inline u16 iomap_ioend_flags(const struct iomap *iomap)
struct iomap_ioend {
struct list_head io_list; /* next ioend in chain */
u16 io_flags; /* IOMAP_IOEND_* */
+ u32 io_bvec_offset; /* offset into first bvec */
struct inode *io_inode; /* file being written to */
size_t io_size; /* size of the extent */
atomic_t io_remaining; /* completetion defer count */
@@ -539,6 +540,13 @@ static inline struct iomap_ioend *iomap_ioend_from_bio(struct bio *bio)
return container_of(bio, struct iomap_ioend, io_bio);
}
+#define BVEC_ITER_IOEND(_ioend) \
+{ \
+ .bi_sector = (_ioend)->io_sector, \
+ .bi_size = (_ioend)->io_size, \
+ .bi_offset = (_ioend)->io_bvec_offset, \
+}
+
struct iomap_writeback_ops {
/*
* Performs writeback on the passed in range
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 10/16] iomap: better read bounce buffering support
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (8 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 09/16] block,iomap: fix protection information verification with initial bvec offset Christoph Hellwig
@ 2026-09-09 6:08 ` Christoph Hellwig
2026-09-09 6:09 ` [PATCH 11/16] xfs: use BIO_COMPLETE_IN_TASK for bounce buffered read I/Os Christoph Hellwig
` (6 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:08 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Add helpers to bounce buffer an upper bio into one or more lower bios
using bounce buffers, and to copy the data back on completion.
Compared to the existing IOMAP_DIO_BOUNCE support for read bios, this
has two advantages: by removing the special bounce bio_vec it allows
to the full and "round" size of a single bio, i.e., 1MiB when using
4k pages. This is important for good performance on HDD. Additionally
it allows to bounce buffer a bio from completion conext, and thus
implement a "lazy" bounce buffering scheme, where the data is only
read into a bounce buffer after an initial checksum validation failure,
thus avoiding the bounce buffering I/O for most I/O.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/iomap/ioend.c | 90 +++++++++++++++++++++++++++++++++++++++++++
include/linux/iomap.h | 5 +++
2 files changed, 95 insertions(+)
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 332dbfb2230f..ba97eec4ee2c 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -514,6 +514,96 @@ struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend,
}
EXPORT_SYMBOL_GPL(iomap_split_ioend);
+void iomap_bounce_read(struct iomap_ioend *orig_ioend, unsigned int minsize,
+ void (*submit_ioend)(struct iomap_ioend *ioend))
+{
+ struct inode *inode = orig_ioend->io_inode;
+ struct bio *orig_bio = &orig_ioend->io_bio;
+ loff_t file_offset = orig_ioend->io_offset;
+ sector_t sector = orig_ioend->io_sector;
+ size_t total_len = round_up(orig_ioend->io_size, minsize);
+
+ WARN_ON_ONCE(!(orig_ioend->io_flags & IOMAP_IOEND_DIRECT));
+
+ /* We can't poll a bio that is not passed on to hardware */
+ orig_bio->bi_opf &= ~REQ_POLLED;
+
+ do {
+ struct iomap_ioend *ioend;
+ struct bio *bio;
+ int error;
+
+ bio = bio_alloc_bioset(orig_bio->bi_bdev,
+ min(total_len / minsize, BIO_MAX_VECS),
+ orig_bio->bi_opf, GFP_KERNEL,
+ &iomap_ioend_split_bioset);
+ error = bio_alloc_bounce_folios(bio, total_len, minsize);
+ if (error) {
+ bio_put(bio);
+ orig_bio->bi_status = errno_to_blk_status(error);
+ break;
+ }
+ bio->bi_ioprio = orig_bio->bi_ioprio;
+ bio->bi_write_hint = orig_bio->bi_write_hint;
+ bio->bi_write_stream = orig_bio->bi_write_stream;
+ bio->bi_iter.bi_sector = sector;
+
+ ioend = iomap_init_ioend(inode, bio, file_offset,
+ orig_ioend->io_flags);
+
+ total_len -= bio->bi_iter.bi_size;
+ file_offset += bio->bi_iter.bi_size;
+ sector += (bio->bi_iter.bi_size >> SECTOR_SHIFT);
+
+ bio->bi_private = orig_bio;
+ bio_inc_remaining(orig_bio);
+ submit_ioend(ioend);
+ } while (total_len > 0);
+
+ bio_endio(&orig_ioend->io_bio);
+}
+EXPORT_SYMBOL_GPL(iomap_bounce_read);
+
+static void iomap_ioend_unbounce(struct iomap_ioend *orig_ioend,
+ struct iomap_ioend *ioend)
+{
+ struct bio *orig_bio = &orig_ioend->io_bio;
+ struct iov_iter to;
+ struct bio_vec *bv;
+ int i;
+
+ iov_iter_bvec(&to, ITER_DEST, orig_bio->bi_io_vec, orig_bio->bi_vcnt,
+ orig_ioend->io_size);
+ to.iov_offset = orig_ioend->io_bvec_offset;
+
+ if (ioend->io_offset != orig_ioend->io_offset) {
+ WARN_ON_ONCE(ioend->io_offset < orig_ioend->io_offset);
+ iov_iter_advance(&to, ioend->io_offset - orig_ioend->io_offset);
+ }
+
+ /* copying to pinned pages should always work */
+ bio_for_each_bvec_all(bv, &ioend->io_bio, i)
+ WARN_ON_ONCE(copy_to_iter(bvec_virt(bv), bv->bv_len, &to) !=
+ bv->bv_len);
+}
+
+void iomap_bounce_read_end_io(struct iomap_ioend *ioend, struct bio *orig_bio,
+ int error)
+{
+ if (error)
+ orig_bio->bi_status = errno_to_blk_status(error);
+ else
+ iomap_ioend_unbounce(iomap_ioend_from_bio(orig_bio), ioend);
+
+ bio_free_folios(&ioend->io_bio);
+ if (bio_integrity(&ioend->io_bio))
+ fs_bio_integrity_free(&ioend->io_bio);
+ bio_put(&ioend->io_bio);
+
+ bio_endio(orig_bio);
+}
+EXPORT_SYMBOL_GPL(iomap_bounce_read_end_io);
+
static int __init iomap_ioend_init(void)
{
const unsigned int nr_mempool_entries = 4 * (PAGE_SIZE / SECTOR_SIZE);
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index bffdc217ad85..fbe051f0b032 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -607,6 +607,11 @@ void iomap_finish_folio_write(struct inode *inode, struct folio *folio,
int iomap_writeback_folio(struct iomap_writepage_ctx *wpc, struct folio *folio);
int iomap_writepages(struct iomap_writepage_ctx *wpc);
+void iomap_bounce_read(struct iomap_ioend *orig_ioend, unsigned int minsize,
+ void (*submit_ioend)(struct iomap_ioend *ioend));
+void iomap_bounce_read_end_io(struct iomap_ioend *ioend, struct bio *orig_bio,
+ int error);
+
struct iomap_read_folio_ctx {
const struct iomap_read_ops *ops;
struct folio *cur_folio;
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 11/16] xfs: use BIO_COMPLETE_IN_TASK for bounce buffered read I/Os
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (9 preceding siblings ...)
2026-09-09 6:08 ` [PATCH 10/16] iomap: better read bounce buffering support Christoph Hellwig
@ 2026-09-09 6:09 ` Christoph Hellwig
2026-09-09 6:09 ` [PATCH 12/16] iomap,xfs: move integrity verification to the file system Christoph Hellwig
` (5 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:09 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Stop using the xfs per-inode work struct for completing read bios, as
unlike writes we don't want to serialize reads on a single inode as
there is no exclusive resource contention for them.
Factor the code for kicking off a read that needs and ioend and the
task context completion into a single helper so that it is split off
the xfs_end_bio machinery, which is not only used for writes.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/xfs_aops.c | 10 ++++------
fs/xfs/xfs_file.c | 8 +-------
fs/xfs/xfs_ioend.c | 32 +++++++++++++++++++++++++++-----
fs/xfs/xfs_ioend.h | 2 ++
4 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index d2cdb7cffa6b..c30e688cfc9f 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -583,12 +583,10 @@ xfs_bio_submit_read(
const struct iomap_iter *iter,
struct iomap_read_folio_ctx *ctx)
{
- struct bio *bio = ctx->read_ctx;
-
- /* defer read completions to the ioend workqueue */
- iomap_init_ioend(iter->inode, bio, ctx->read_ctx_file_offset,
- iomap_ioend_flags(&iter->iomap));
- iomap_bio_submit_read_endio(iter, ctx, xfs_end_bio);
+ xfs_ioend_submit_read(iter->inode, ctx->read_ctx,
+ ctx->read_ctx_file_offset,
+ iomap_ioend_flags(&iter->iomap));
+ ctx->read_ctx = NULL;
}
static const struct iomap_read_ops xfs_iomap_read_ops = {
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 0e029db2e85b..077744b3f6c3 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -242,14 +242,8 @@ xfs_dio_read_bounce_submit_io(
struct bio *bio,
loff_t file_offset)
{
- struct iomap_ioend *ioend;
-
- ioend = iomap_init_ioend(iter->inode, bio, file_offset,
+ xfs_ioend_submit_read(iter->inode, bio, file_offset,
iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
- if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
- fs_bio_integrity_alloc(bio);
- bio->bi_end_io = xfs_end_bio;
- submit_bio(bio);
}
static const struct iomap_dio_ops xfs_dio_read_bounce_ops = {
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index 40695d18dac0..37a3ae8066e9 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -16,6 +16,32 @@
#include "xfs_reflink.h"
#include "xfs_zone_alloc.h"
#include "xfs_ioend.h"
+#include <linux/bio-integrity.h>
+
+static void
+xfs_end_io_read(
+ struct bio *bio)
+{
+ struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
+ int error = blk_status_to_errno(bio->bi_status);
+
+ iomap_finish_ioends(ioend, error);
+}
+
+void
+xfs_ioend_submit_read(
+ struct inode *inode,
+ struct bio *bio,
+ loff_t file_offset,
+ u16 ioend_flags)
+{
+ iomap_init_ioend(inode, bio, file_offset, ioend_flags);
+ if (ioend_flags & IOMAP_IOEND_INTEGRITY)
+ fs_bio_integrity_alloc(bio);
+ bio->bi_end_io = xfs_end_io_read;
+ bio_set_flag(bio, BIO_COMPLETE_IN_TASK);
+ submit_bio(bio);
+}
static void
xfs_ioend_put_open_zones(
@@ -148,11 +174,7 @@ xfs_end_io(
io_list))) {
list_del_init(&ioend->io_list);
iomap_ioend_try_merge(ioend, &tmp);
- if (bio_op(&ioend->io_bio) == REQ_OP_READ)
- iomap_finish_ioends(ioend,
- blk_status_to_errno(ioend->io_bio.bi_status));
- else
- xfs_end_ioend_write(ioend);
+ xfs_end_ioend_write(ioend);
cond_resched();
}
}
diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h
index 525865767fca..7c2a1ea3e6ed 100644
--- a/fs/xfs/xfs_ioend.h
+++ b/fs/xfs/xfs_ioend.h
@@ -12,5 +12,7 @@ static inline bool xfs_ioend_is_append(struct iomap_ioend *ioend)
}
void xfs_end_bio(struct bio *bio);
+void xfs_ioend_submit_read(struct inode *inode, struct bio *bio,
+ loff_t file_offset, u16 ioend_flags);
#endif /* __XFS_IOEND_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 12/16] iomap,xfs: move integrity verification to the file system
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (10 preceding siblings ...)
2026-09-09 6:09 ` [PATCH 11/16] xfs: use BIO_COMPLETE_IN_TASK for bounce buffered read I/Os Christoph Hellwig
@ 2026-09-09 6:09 ` Christoph Hellwig
2026-09-09 6:09 ` [PATCH 13/16] xfs: add support for lazy direct read bounce buffering Christoph Hellwig
` (4 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:09 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Integrity support in file systems already requires file system-specific
completion handling because it must be run in process context.
Move the actual verification to the file system so that it can better
handle errors in file system specific ways, and to support lazy bounce
buffering.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/iomap/ioend.c | 10 ++++------
fs/xfs/xfs_ioend.c | 3 +++
include/linux/iomap.h | 1 +
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index ba97eec4ee2c..bbebecc31670 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -309,12 +309,15 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
}
EXPORT_SYMBOL_GPL(iomap_add_to_ioend);
-static int iomap_ioend_integrity_verify(struct iomap_ioend *ioend)
+#ifdef CONFIG_BLK_DEV_INTEGRITY
+int iomap_ioend_integrity_verify(struct iomap_ioend *ioend)
{
struct bvec_iter data_iter = BVEC_ITER_IOEND(ioend);
return fs_bio_integrity_verify(&ioend->io_bio, &data_iter);
}
+EXPORT_SYMBOL_GPL(iomap_ioend_integrity_verify);
+#endif /* CONFIG_BLK_DEV_INTEGRITY */
static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
{
@@ -331,11 +334,6 @@ static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
if (!atomic_dec_and_test(&ioend->io_remaining))
return 0;
- if (!ioend->io_error &&
- bio_integrity(&ioend->io_bio) &&
- bio_op(&ioend->io_bio) == REQ_OP_READ)
- ioend->io_error = iomap_ioend_integrity_verify(ioend);
-
if (ioend->io_flags & IOMAP_IOEND_DIRECT)
return iomap_finish_ioend_direct(ioend);
if (bio_op(&ioend->io_bio) == REQ_OP_READ)
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index 37a3ae8066e9..a095cf217863 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -25,6 +25,9 @@ xfs_end_io_read(
struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
int error = blk_status_to_errno(bio->bi_status);
+ if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY))
+ error = iomap_ioend_integrity_verify(ioend);
+
iomap_finish_ioends(ioend, error);
}
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index fbe051f0b032..59718f73c15a 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -595,6 +595,7 @@ void iomap_finish_ioends(struct iomap_ioend *ioend, int error);
void iomap_ioend_try_merge(struct iomap_ioend *ioend,
struct list_head *more_ioends);
void iomap_sort_ioends(struct list_head *ioend_list);
+int iomap_ioend_integrity_verify(struct iomap_ioend *ioend);
ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx *wpc, struct folio *folio,
loff_t pos, loff_t end_pos, unsigned int dirty_len);
int iomap_ioend_writeback_submit(struct iomap_writepage_ctx *wpc, int error);
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 13/16] xfs: add support for lazy direct read bounce buffering
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (11 preceding siblings ...)
2026-09-09 6:09 ` [PATCH 12/16] iomap,xfs: move integrity verification to the file system Christoph Hellwig
@ 2026-09-09 6:09 ` Christoph Hellwig
2026-09-09 6:09 ` [PATCH 14/16] xfs: add error injection for lazy " Christoph Hellwig
` (3 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:09 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Currently direct I/O reads always bounce buffer the I/O to deal with
the case where userspace is modifying the buffer in-flight while
reading data into it.
This is a very expensive countermeasure for something no sane application
should do, so try to avoid it by reading without a bounce buffer first,
and retrying the read on a checksum failure. This avoids the cost of
bounce buffering for sanely behave applications. For the rare case of
an application regularly modifying in-flight buffers, allow forcing the
always bounce buffer behavior through sysfs. And now that we have that
knob, allow disabling read-side bounce buffering entirely for those who
live fast and dangerous.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/xfs_file.c | 3 +-
fs/xfs/xfs_ioend.c | 103 +++++++++++++++++++++++++++++++++++++++++++--
fs/xfs/xfs_mount.h | 8 ++++
fs/xfs/xfs_super.c | 1 +
fs/xfs/xfs_sysfs.c | 78 +++++++++++++++++++++++++++++++++-
fs/xfs/xfs_trace.h | 1 +
6 files changed, 187 insertions(+), 7 deletions(-)
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 077744b3f6c3..6f25879b6510 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -271,8 +271,7 @@ xfs_file_dio_read(
return ret;
if (mapping_stable_writes(iocb->ki_filp->f_mapping)) {
ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops,
- &xfs_dio_read_bounce_ops, IOMAP_DIO_BOUNCE,
- NULL, 0);
+ &xfs_dio_read_bounce_ops, 0, NULL, 0);
} else {
ret = iomap_dio_read_simple(iocb, to, xfs_read_iomap_begin);
if (ret == -ENOTBLK)
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index a095cf217863..4fba8558d8d2 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/*
- * Copyright (c) 2016-2025 Christoph Hellwig.
+ * Copyright (c) 2016-2026 Christoph Hellwig.
* All Rights Reserved.
*/
#include "xfs_platform.h"
@@ -18,15 +18,101 @@
#include "xfs_ioend.h"
#include <linux/bio-integrity.h>
+static void
+xfs_dio_bounce_end_io(
+ struct bio *bio)
+{
+ struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
+ int error = blk_status_to_errno(bio->bi_status);
+ struct bio *orig_bio = bio->bi_private;
+
+ if ((ioend->io_flags & IOMAP_IOEND_INTEGRITY) && !bio->bi_status)
+ error = iomap_ioend_integrity_verify(ioend);
+ iomap_bounce_read_end_io(ioend, orig_bio, error);
+}
+
+static void
+xfs_bounce_submit_ioend(
+ struct iomap_ioend *ioend)
+{
+ if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
+ fs_bio_integrity_alloc(&ioend->io_bio);
+ ioend->io_bio.bi_end_io = xfs_dio_bounce_end_io;
+ bio_set_flag(&ioend->io_bio, BIO_COMPLETE_IN_TASK);
+ submit_bio(&ioend->io_bio);
+}
+
+static void
+xfs_end_bio_bounced(
+ struct bio *bio)
+{
+ /*
+ * Just complete the original ioends as all verification is done by the
+ * end_io handlers for the clone bio(s).
+ */
+ iomap_finish_ioends(iomap_ioend_from_bio(bio),
+ blk_status_to_errno(bio->bi_status));
+}
+
+static void
+xfs_read_bounce_and_resubmit(
+ struct iomap_ioend *ioend)
+{
+ struct bio *bio = &ioend->io_bio;
+ struct xfs_inode *ip = XFS_I(ioend->io_inode);
+ unsigned int nofs_flag = memalloc_nofs_save();
+
+ trace_xfs_bounce_reread(ip, ioend->io_offset, ioend->io_size);
+
+ /*
+ * Free the bio integrity data for the original bio, as we'll allocate
+ * a new one for each sub-I/O, which could deadlock if we keep the
+ * integrity data for the original bio around.
+ */
+ if (bio_integrity(bio))
+ fs_bio_integrity_free(bio);
+
+ /*
+ * Resubmit the bio through the iomap bounce machinery. The original
+ * bio itself is not resubmitted to the block layer, but just used to
+ * track I/O completion of the cloned bios.
+ */
+ bio_prepare_reissue(bio, xfs_inode_buftarg(ip)->bt_bdev);
+ bio->bi_iter = (struct bvec_iter) {
+ .bi_sector = ioend->io_sector,
+ .bi_size = ioend->io_size,
+ .bi_offset = ioend->io_bvec_offset,
+ };
+ bio->bi_end_io = xfs_end_bio_bounced;
+ iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev),
+ xfs_bounce_submit_ioend);
+ memalloc_nofs_restore(nofs_flag);
+}
+
static void
xfs_end_io_read(
struct bio *bio)
{
struct iomap_ioend *ioend = iomap_ioend_from_bio(bio);
+ struct xfs_inode *ip = XFS_I(ioend->io_inode);
+ struct xfs_mount *mp = ip->i_mount;
int error = blk_status_to_errno(bio->bi_status);
- if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY))
+ if (!error && (ioend->io_flags & IOMAP_IOEND_INTEGRITY)) {
error = iomap_ioend_integrity_verify(ioend);
+ if ((ioend->io_flags & IOMAP_IOEND_DIRECT) &&
+ READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_LAZY) {
+ /*
+ * We only really need to retry for guard tag errors,
+ * but right now we can't distinguish them from other
+ * (i.e, reftag) errors.
+ */
+ if (error) {
+ xfs_read_bounce_and_resubmit(ioend);
+ return;
+ }
+ }
+ }
iomap_finish_ioends(ioend, error);
}
@@ -38,7 +124,18 @@ xfs_ioend_submit_read(
loff_t file_offset,
u16 ioend_flags)
{
- iomap_init_ioend(inode, bio, file_offset, ioend_flags);
+ struct xfs_inode *ip = XFS_I(inode);
+ struct xfs_mount *mp = ip->i_mount;
+ struct iomap_ioend *ioend;
+
+ ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags);
+ if ((ioend_flags & IOMAP_IOEND_DIRECT) &&
+ READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_ALWAYS) {
+ iomap_bounce_read(ioend, bdev_logical_block_size(bio->bi_bdev),
+ xfs_bounce_submit_ioend);
+ return;
+ }
+
if (ioend_flags & IOMAP_IOEND_INTEGRITY)
fs_bio_integrity_alloc(bio);
bio->bi_end_io = xfs_end_io_read;
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index 216a38a354e7..894ff2f4ecbd 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -142,6 +142,12 @@ struct xfs_freecounter {
uint64_t res_saved;
};
+enum xfs_read_bounce {
+ XFS_READ_BOUNCE_NEVER,
+ XFS_READ_BOUNCE_ALWAYS,
+ XFS_READ_BOUNCE_LAZY,
+};
+
/*
* The struct xfsmount layout is optimised to separate read-mostly variables
* from variables that are frequently modified. We put the read-mostly variables
@@ -177,6 +183,7 @@ typedef struct xfs_mount {
struct workqueue_struct *m_sync_workqueue;
struct workqueue_struct *m_blockgc_wq;
struct workqueue_struct *m_inodegc_wq;
+ enum xfs_read_bounce m_read_bounce;
int m_bsize; /* fs logical block size */
uint8_t m_blkbit_log; /* blocklog + NBBY */
@@ -291,6 +298,7 @@ typedef struct xfs_mount {
struct xfs_zone_info *m_zone_info; /* zone allocator information */
struct dentry *m_debugfs; /* debugfs parent */
struct xfs_kobj m_kobj;
+ struct xfs_kobj m_csum_kobj;
struct xfs_kobj m_error_kobj;
struct xfs_kobj m_error_meta_kobj;
struct xfs_error_cfg m_error_cfg[XFS_ERR_CLASS_MAX][XFS_ERR_ERRNO_MAX];
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index b24db75eaedc..fce1d2905c94 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -2317,6 +2317,7 @@ xfs_init_fs_context(
mp->m_logbufs = -1;
mp->m_logbsize = -1;
mp->m_allocsize_log = 16; /* 64k */
+ mp->m_read_bounce = XFS_READ_BOUNCE_LAZY;
xfs_hooks_init(&mp->m_dir_update_hooks);
diff --git a/fs/xfs/xfs_sysfs.c b/fs/xfs/xfs_sysfs.c
index b62712187324..e77917ac179d 100644
--- a/fs/xfs/xfs_sysfs.c
+++ b/fs/xfs/xfs_sysfs.c
@@ -392,6 +392,71 @@ const struct kobj_type xfs_stats_ktype = {
.default_groups = xfs_stats_groups,
};
+static inline struct xfs_mount *csum_to_mp(struct kobject *kobj)
+{
+ return container_of(to_kobj(kobj), struct xfs_mount, m_csum_kobj);
+}
+
+static bool
+xfs_has_read_bounce(
+ struct xfs_mount *mp)
+{
+ if (bdev_has_integrity_csum(mp->m_ddev_targp->bt_bdev))
+ return true;
+ if (mp->m_rtdev_targp &&
+ bdev_has_integrity_csum(mp->m_rtdev_targp->bt_bdev))
+ return true;
+ return false;
+}
+
+static const char * const bounce_modes[] = {
+ [XFS_READ_BOUNCE_NEVER] = "never",
+ [XFS_READ_BOUNCE_ALWAYS] = "always",
+ [XFS_READ_BOUNCE_LAZY] = "lazy",
+};
+
+static ssize_t
+read_bounce_show(
+ struct kobject *kobj,
+ char *buf)
+{
+ struct xfs_mount *mp = csum_to_mp(kobj);
+
+ return sysfs_emit(buf, "%s\n",
+ bounce_modes[READ_ONCE(mp->m_read_bounce)]);
+}
+
+static ssize_t
+read_bounce_store(
+ struct kobject *kobj,
+ const char *buf,
+ size_t count)
+{
+ struct xfs_mount *mp = csum_to_mp(kobj);
+ int ret;
+
+ if (!xfs_has_read_bounce(mp))
+ return -EINVAL;
+ ret = sysfs_match_string(bounce_modes, buf);
+ if (ret < 0)
+ return ret;
+ WRITE_ONCE(mp->m_read_bounce, ret);
+ return count;
+}
+XFS_SYSFS_ATTR_RW(read_bounce);
+
+static struct attribute *xfs_csum_attrs[] = {
+ ATTR_LIST(read_bounce),
+ NULL,
+};
+ATTRIBUTE_GROUPS(xfs_csum);
+
+static const struct kobj_type xfs_csum_ktype = {
+ .release = xfs_sysfs_release,
+ .sysfs_ops = &xfs_sysfs_ops,
+ .default_groups = xfs_csum_groups,
+};
+
/* xlog */
static inline struct xlog *
@@ -817,11 +882,17 @@ xfs_mount_sysfs_init(
if (error)
goto out_remove_fsdir;
+ /* .../xfs/<dev>/csum/ */
+ error = xfs_sysfs_init(&mp->m_csum_kobj, &xfs_csum_ktype, &mp->m_kobj,
+ "csum");
+ if (error)
+ goto out_remove_stats_dir;
+
/* .../xfs/<dev>/error/ */
error = xfs_sysfs_init(&mp->m_error_kobj, &xfs_error_ktype,
&mp->m_kobj, "error");
if (error)
- goto out_remove_stats_dir;
+ goto out_remove_csum_dir;
/* .../xfs/<dev>/error/fail_at_unmount */
error = sysfs_create_file(&mp->m_error_kobj.kobject,
@@ -835,12 +906,14 @@ xfs_mount_sysfs_init(
"metadata", &mp->m_error_meta_kobj,
xfs_error_meta_init);
if (error)
- goto out_remove_error_dir;
+ goto out_remove_csum_dir;
return 0;
out_remove_error_dir:
xfs_sysfs_del(&mp->m_error_kobj);
+out_remove_csum_dir:
+ xfs_sysfs_del(&mp->m_csum_kobj);
out_remove_stats_dir:
xfs_sysfs_del(&mp->m_stats.xs_kobj);
out_remove_fsdir:
@@ -864,6 +937,7 @@ xfs_mount_sysfs_del(
}
xfs_sysfs_del(&mp->m_error_meta_kobj);
xfs_sysfs_del(&mp->m_error_kobj);
+ xfs_sysfs_del(&mp->m_csum_kobj);
xfs_sysfs_del(&mp->m_stats.xs_kobj);
xfs_sysfs_del(&mp->m_kobj);
}
diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h
index f333c938fbd9..2af9a1429ae9 100644
--- a/fs/xfs/xfs_trace.h
+++ b/fs/xfs/xfs_trace.h
@@ -1896,6 +1896,7 @@ DEFINE_SIMPLE_IO_EVENT(xfs_zero_eof);
DEFINE_SIMPLE_IO_EVENT(xfs_end_io_direct_write);
DEFINE_SIMPLE_IO_EVENT(xfs_file_splice_read);
DEFINE_SIMPLE_IO_EVENT(xfs_zoned_map_blocks);
+DEFINE_SIMPLE_IO_EVENT(xfs_bounce_reread);
DECLARE_EVENT_CLASS(xfs_itrunc_class,
TP_PROTO(struct xfs_inode *ip, xfs_fsize_t new_size),
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 14/16] xfs: add error injection for lazy bounce buffering
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (12 preceding siblings ...)
2026-09-09 6:09 ` [PATCH 13/16] xfs: add support for lazy direct read bounce buffering Christoph Hellwig
@ 2026-09-09 6:09 ` Christoph Hellwig
2026-09-09 6:09 ` [PATCH 15/16] xfs: log a message at mount time when using integrity protection Christoph Hellwig
` (2 subsequent siblings)
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:09 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Add an error injection knob to exercise the lazy bounce buffering
code path, i.e. to inject direct I/O re-read using the bounce buffer.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_errortag.h | 6 ++++--
fs/xfs/xfs_ioend.c | 5 ++++-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_errortag.h b/fs/xfs/libxfs/xfs_errortag.h
index 6de207fed2d8..2dc441da0333 100644
--- a/fs/xfs/libxfs/xfs_errortag.h
+++ b/fs/xfs/libxfs/xfs_errortag.h
@@ -75,7 +75,8 @@
#define XFS_ERRTAG_METAFILE_RESV_CRITICAL 45
#define XFS_ERRTAG_FORCE_ZERO_RANGE 46
#define XFS_ERRTAG_ZONE_RESET 47
-#define XFS_ERRTAG_MAX 48
+#define XFS_ERRTAG_BOUNCE_REREAD 48
+#define XFS_ERRTAG_MAX 49
/*
* Random factors for above tags, 1 means always, 2 means 1/2 time, etc.
@@ -137,7 +138,8 @@ XFS_ERRTAG(WRITE_DELAY_MS, write_delay_ms, 3000) \
XFS_ERRTAG(EXCHMAPS_FINISH_ONE, exchmaps_finish_one, 1) \
XFS_ERRTAG(METAFILE_RESV_CRITICAL, metafile_resv_crit, 4) \
XFS_ERRTAG(FORCE_ZERO_RANGE, force_zero_range, 4) \
-XFS_ERRTAG(ZONE_RESET, zone_reset, 1)
+XFS_ERRTAG(ZONE_RESET, zone_reset, 1) \
+XFS_ERRTAG(BOUNCE_REREAD, bounce_reread, XFS_RANDOM_DEFAULT)
#endif /* XFS_ERRTAG */
#endif /* __XFS_ERRORTAG_H_ */
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index 4fba8558d8d2..e70be5b86f0b 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -16,6 +16,8 @@
#include "xfs_reflink.h"
#include "xfs_zone_alloc.h"
#include "xfs_ioend.h"
+#include "xfs_error.h"
+#include "xfs_errortag.h"
#include <linux/bio-integrity.h>
static void
@@ -107,7 +109,8 @@ xfs_end_io_read(
* but right now we can't distinguish them from other
* (i.e, reftag) errors.
*/
- if (error) {
+ if (error ||
+ XFS_TEST_ERROR(mp, XFS_ERRTAG_BOUNCE_REREAD)) {
xfs_read_bounce_and_resubmit(ioend);
return;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 15/16] xfs: log a message at mount time when using integrity protection
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (13 preceding siblings ...)
2026-09-09 6:09 ` [PATCH 14/16] xfs: add error injection for lazy " Christoph Hellwig
@ 2026-09-09 6:09 ` Christoph Hellwig
2026-09-09 6:09 ` [PATCH 16/16] block,iomap: remove the old read side bounce buffering support Christoph Hellwig
2026-09-10 20:50 ` lazy bounce buffering for checksummed reads v3 Jens Axboe
16 siblings, 0 replies; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:09 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
Log a message in the kernel when using T10 protection information.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/xfs_buf.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index ee7c2e9c0340..eee491c01d8c 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -5,6 +5,7 @@
*/
#include "xfs_platform.h"
#include <linux/backing-dev.h>
+#include <linux/blk-integrity.h>
#include <linux/dax.h>
#include "xfs_shared.h"
@@ -1694,6 +1695,7 @@ xfs_configure_buftarg(
struct xfs_mount *mp = btp->bt_mount;
if (btp->bt_bdev) {
+ struct blk_integrity *bi = bdev_get_integrity(btp->bt_bdev);
int error;
error = bdev_validate_blocksize(btp->bt_bdev, sectorsize);
@@ -1706,6 +1708,15 @@ xfs_configure_buftarg(
if (bdev_can_atomic_write(btp->bt_bdev))
xfs_configure_buftarg_atomic_writes(btp);
+
+ if (!bi)
+ ;
+ else if (btp->bt_bdev == btp->bt_mount->m_super->s_bdev)
+ xfs_info(mp, "using %s integrity profile",
+ blk_integrity_profile_name(bi));
+ else
+ xfs_info(mp, "using %s integrity profile for %pg",
+ blk_integrity_profile_name(bi), btp->bt_bdev);
}
btp->bt_meta_sectorsize = sectorsize;
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH 16/16] block,iomap: remove the old read side bounce buffering support
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (14 preceding siblings ...)
2026-09-09 6:09 ` [PATCH 15/16] xfs: log a message at mount time when using integrity protection Christoph Hellwig
@ 2026-09-09 6:09 ` Christoph Hellwig
2026-09-09 16:15 ` Darrick J. Wong
2026-09-10 20:50 ` lazy bounce buffering for checksummed reads v3 Jens Axboe
16 siblings, 1 reply; 22+ messages in thread
From: Christoph Hellwig @ 2026-09-09 6:09 UTC (permalink / raw)
To: Jens Axboe, Christian Brauner, Darrick J. Wong, Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
bio_iov_iter_bounce_read turned to generate suboptimal I/O sizes and
isn't usable for lazy bounce buffering. Now that is has been replaced
with the iomap side implementation that requires extra bounce bios, it
is unused and can be removed. Change the interface so that the
previously hidden write-side implementation is directly exposed.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
block/bio.c | 150 ++++++-------------------------------------
fs/iomap/direct-io.c | 7 +-
include/linux/bio.h | 5 +-
3 files changed, 23 insertions(+), 139 deletions(-)
diff --git a/block/bio.c b/block/bio.c
index 7f7654e60dd4..987082e0f0ef 100644
--- a/block/bio.c
+++ b/block/bio.c
@@ -1223,8 +1223,9 @@ bool bio_iov_iter_set(struct bio *bio, const struct iov_iter *iter)
* for the next iteration.
*/
static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter,
- struct bio_vec *bv, unsigned len_align_mask)
+ unsigned len_align_mask)
{
+ struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
size_t nbytes = bio->bi_iter.bi_size & len_align_mask;
if (!nbytes)
@@ -1356,8 +1357,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
if (is_pci_p2pdma_page(bio->bi_io_vec->bv_page))
bio->bi_opf |= REQ_NOMERGE;
- return bio_iov_iter_align_down(bio, iter,
- &bio->bi_io_vec[bio->bi_vcnt - 1], len_align_mask);
+ return bio_iov_iter_align_down(bio, iter, len_align_mask);
}
static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size,
@@ -1428,7 +1428,20 @@ int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize)
return 0;
}
-static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
+/**
+ * bio_iov_iter_bounce_write - bounce buffer data from an iter into a bio
+ * @bio: bio to send
+ * @iter: iter to read from
+ * @maxlen: maximum size to bounce
+ * @minsize: minimum folio allocation size
+ *
+ * Helper for direct I/O write implementations that need to bounce buffer
+ * because they need need to checksum the data or perform other operations that
+ * require consistency. Allocates folios to back the bounce buffer, and copies
+ * the data into it. Needs to be paired with bio_free_folios() called on
+ * completion.
+ */
+int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
size_t maxlen, size_t minsize)
{
size_t total_len = min(maxlen, iov_iter_count(iter));
@@ -1460,134 +1473,7 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
return 0;
}
-
-static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter,
- size_t maxlen, size_t minsize)
-{
- size_t len = min3(iov_iter_count(iter), maxlen, SZ_1M);
- struct folio *folio;
- ssize_t ret;
-
- folio = folio_alloc_greedy(GFP_KERNEL, &len, minsize);
- if (!folio)
- return -ENOMEM;
-
- do {
- ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec + 1, len,
- &bio->bi_vcnt, bio->bi_max_vecs - 1, 0, 0);
- if (ret <= 0) {
- if (!bio->bi_vcnt)
- goto out_folio_put;
- break;
- }
- len -= ret;
- bio->bi_iter.bi_size += ret;
- } while (len && bio->bi_vcnt < bio->bi_max_vecs - 1);
-
- /*
- * Set the folio directly here. The above loop has already calculated
- * the correct bi_size, and we use bi_vcnt for the user buffers. That
- * is safe as bi_vcnt is only used by the submitter and not the actual
- * I/O path.
- */
- bvec_set_folio(&bio->bi_io_vec[0], folio, bio->bi_iter.bi_size, 0);
- if (iov_iter_extract_will_pin(iter))
- bio_set_flag(bio, BIO_PAGE_PINNED);
-
- /* The first vec stores the bounce buffer, so do not subtract 1 here. */
- ret = bio_iov_iter_align_down(bio, iter,
- &bio->bi_io_vec[bio->bi_vcnt], minsize - 1);
- if (ret)
- goto out_folio_put;
-
- /* Update the bounc buffer bv_len to the aligned down size. */
- bio->bi_io_vec[0].bv_len = bio->bi_iter.bi_size;
- return 0;
-
-out_folio_put:
- folio_put(folio);
- return ret;
-}
-
-/**
- * bio_iov_iter_bounce - bounce buffer data from an iter into a bio
- * @bio: bio to send
- * @iter: iter to read from / write into
- * @maxlen: maximum size to bounce
- * @minsize: minimum folio allocation size
- *
- * Helper for direct I/O implementations that need to bounce buffer because
- * we need to checksum the data or perform other operations that require
- * consistency. Allocates folios to back the bounce buffer, and for writes
- * copies the data into it. Needs to be paired with bio_iov_iter_unbounce()
- * called on completion.
- */
-int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen,
- size_t minsize)
-{
- if (op_is_write(bio_op(bio)))
- return bio_iov_iter_bounce_write(bio, iter, maxlen, minsize);
- return bio_iov_iter_bounce_read(bio, iter, maxlen, minsize);
-}
-
-static void bvec_unpin(struct bio_vec *bv, bool mark_dirty)
-{
- struct folio *folio = bvec_folio(bv);
- size_t nr_pages = (bv->bv_offset + bv->bv_len - 1) / PAGE_SIZE -
- bv->bv_offset / PAGE_SIZE + 1;
-
- if (mark_dirty)
- folio_mark_dirty_lock(folio);
- unpin_user_folio(folio, nr_pages);
-}
-
-static void bio_iov_iter_unbounce_read(struct bio *bio, bool is_error,
- bool mark_dirty)
-{
- unsigned int len = bio->bi_io_vec[0].bv_len;
-
- if (likely(!is_error)) {
- void *buf = bvec_virt(&bio->bi_io_vec[0]);
- struct iov_iter to;
-
- iov_iter_bvec(&to, ITER_DEST, bio->bi_io_vec + 1, bio->bi_vcnt,
- len);
- /* copying to pinned pages should always work */
- WARN_ON_ONCE(copy_to_iter(buf, len, &to) != len);
- } else {
- /* No need to mark folios dirty if never copied to them */
- mark_dirty = false;
- }
-
- if (bio_flagged(bio, BIO_PAGE_PINNED)) {
- int i;
-
- for (i = 0; i < bio->bi_vcnt; i++)
- bvec_unpin(&bio->bi_io_vec[1 + i], mark_dirty);
- }
-
- folio_put(bvec_folio(&bio->bi_io_vec[0]));
-}
-
-/**
- * bio_iov_iter_unbounce - finish a bounce buffer operation
- * @bio: completed bio
- * @is_error: %true if an I/O error occurred and data should not be copied
- * @mark_dirty: If %true, folios will be marked dirty.
- *
- * Helper for direct I/O implementations that need to bounce buffer because
- * we need to checksum the data or perform other operations that require
- * consistency. Called to complete a bio set up by bio_iov_iter_bounce().
- * Copies data back for reads, and marks the original folios dirty if
- * requested and then frees the bounce buffer.
- */
-void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty)
-{
- if (op_is_write(bio_op(bio)))
- bio_free_folios(bio);
- else
- bio_iov_iter_unbounce_read(bio, is_error, mark_dirty);
-}
+EXPORT_SYMBOL_GPL(bio_iov_iter_bounce_write);
static void bio_wait_end_io(struct bio *bio)
{
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index 4154717a09de..41fdc90a9094 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -255,8 +255,7 @@ static void __iomap_dio_bio_end_io(struct bio *bio, bool inline_completion)
fs_bio_integrity_free(bio);
if (dio->flags & IOMAP_DIO_BOUNCE) {
- bio_iov_iter_unbounce(bio, !!dio->error,
- dio->flags & IOMAP_DIO_USER_BACKED);
+ bio_free_folios(bio);
bio_put(bio);
} else if (dio->flags & IOMAP_DIO_USER_BACKED) {
bio_check_pages_dirty(bio);
@@ -364,7 +363,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
bio->bi_end_io = iomap_dio_bio_end_io;
if (dio->flags & IOMAP_DIO_BOUNCE)
- ret = bio_iov_iter_bounce(bio, dio->submit.iter, maxsize,
+ ret = bio_iov_iter_bounce_write(bio, dio->submit.iter, maxsize,
alignment);
else
ret = bio_iov_iter_get_pages(bio, dio->submit.iter, maxsize,
@@ -398,7 +397,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
out_bio_release_pages:
if (dio->flags & IOMAP_DIO_BOUNCE)
- bio_iov_iter_unbounce(bio, true, false);
+ bio_free_folios(bio);
else
bio_release_pages(bio, false);
out_put_bio:
diff --git a/include/linux/bio.h b/include/linux/bio.h
index bfa3c0e97b6f..17944e44b584 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -525,11 +525,10 @@ void __bio_release_pages(struct bio *bio, bool mark_dirty);
extern void bio_set_pages_dirty(struct bio *bio);
extern void bio_check_pages_dirty(struct bio *bio);
-int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen,
- size_t minsize);
-void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty);
int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize);
void bio_free_folios(struct bio *bio);
+int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
+ size_t maxlen, size_t minsize);
extern void bio_copy_data(struct bio *dst, struct bio *src);
extern void bio_free_pages(struct bio *bio);
--
2.53.0
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH 03/16] block: add a bio_prepare_reissue helper
2026-09-09 6:08 ` [PATCH 03/16] block: add a bio_prepare_reissue helper Christoph Hellwig
@ 2026-09-09 16:05 ` Darrick J. Wong
0 siblings, 0 replies; 22+ messages in thread
From: Darrick J. Wong @ 2026-09-09 16:05 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Jens Axboe, Christian Brauner, Carlos Maiolino, Tal Zussman,
Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
On Wed, Sep 09, 2026 at 09:08:52AM +0300, Christoph Hellwig wrote:
> Add a helper to a clear a bio for reissue without a lot of the
> pointless clearing and reinitializing done by bio_reset and bio_reuse,
> and keeping the page pinning flag intact.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
Looks good to me,
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> ---
> block/bio.c | 20 ++++++++++++++++++++
> include/linux/bio.h | 1 +
> 2 files changed, 21 insertions(+)
>
> diff --git a/block/bio.c b/block/bio.c
> index 5792a059ef2a..7f7654e60dd4 100644
> --- a/block/bio.c
> +++ b/block/bio.c
> @@ -320,6 +320,26 @@ void bio_reuse(struct bio *bio, blk_opf_t opf)
> }
> EXPORT_SYMBOL_GPL(bio_reuse);
>
> +/**
> + * bio_prepare_reissue - prepare a bio for reuissing the original I/O
> + * @bio: bio to reuse
> + * @bdev: block device to use the bio for
> + *
> + * Prepare @bio to be resubmitted to retry the original operation.
> + * The caller must reset bio->bi_iter to the original state.
> + */
> +void bio_prepare_reissue(struct bio *bio, struct block_device *bdev)
> +{
> + bio->bi_bdev = bdev;
> + bio_associate_blkg(bio);
> + bio->bi_flags &=
> + (BIO_PAGE_PINNED | BIO_CLONED | BIO_QUIET | BIO_REFFED);
> + bio->bi_status = BLK_STS_OK;
> + bio->bi_bvec_gap_bit = 0;
> + atomic_set(&bio->__bi_remaining, 1);
> +}
> +EXPORT_SYMBOL_GPL(bio_prepare_reissue);
> +
> static struct bio *__bio_chain_endio(struct bio *bio)
> {
> struct bio *parent = bio->bi_private;
> diff --git a/include/linux/bio.h b/include/linux/bio.h
> index 584b6abf6baf..bfa3c0e97b6f 100644
> --- a/include/linux/bio.h
> +++ b/include/linux/bio.h
> @@ -479,6 +479,7 @@ static inline void bio_init_inline(struct bio *bio, struct block_device *bdev,
> 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_prepare_reissue(struct bio *bio, struct block_device *bdev);
> void bio_chain(struct bio *, struct bio *);
> void bio_await(struct bio *bio, void *priv,
> void (*submit)(struct bio *bio, void *priv));
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 08/16] xfs: move PI generation into xfs_submit_zoned_bio
2026-09-09 6:08 ` [PATCH 08/16] xfs: move PI generation into xfs_submit_zoned_bio Christoph Hellwig
@ 2026-09-09 16:06 ` Darrick J. Wong
0 siblings, 0 replies; 22+ messages in thread
From: Darrick J. Wong @ 2026-09-09 16:06 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Jens Axboe, Christian Brauner, Carlos Maiolino, Tal Zussman,
Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
On Wed, Sep 09, 2026 at 09:08:57AM +0300, Christoph Hellwig wrote:
> Keep the code in one place.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
Seems reasonable to move this to a common function.
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> ---
> fs/xfs/xfs_aops.c | 4 +---
> fs/xfs/xfs_file.c | 2 --
> fs/xfs/xfs_zone_alloc.c | 4 ++++
> 3 files changed, 5 insertions(+), 5 deletions(-)
>
> diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
> index 4e862406eee9..d2cdb7cffa6b 100644
> --- a/fs/xfs/xfs_aops.c
> +++ b/fs/xfs/xfs_aops.c
> @@ -23,7 +23,6 @@
> #include "xfs_ioend.h"
> #include "xfs_zone_alloc.h"
> #include "xfs_rtgroup.h"
> -#include <linux/bio-integrity.h>
>
> struct xfs_writepage_ctx {
> struct iomap_writepage_ctx ctx;
> @@ -498,8 +497,7 @@ xfs_zoned_writeback_submit(
> bio_endio(&ioend->io_bio);
> return error;
> }
> - if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
> - fs_bio_integrity_generate(&ioend->io_bio);
> +
> xfs_zone_alloc_and_submit(ioend, &XFS_ZWPC(wpc)->open_zone);
> return 0;
> }
> diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
> index acbfd55f56a3..0e029db2e85b 100644
> --- a/fs/xfs/xfs_file.c
> +++ b/fs/xfs/xfs_file.c
> @@ -739,8 +739,6 @@ xfs_dio_zoned_submit_io(
> bio->bi_end_io = xfs_end_bio;
> ioend = iomap_init_ioend(iter->inode, bio, file_offset,
> iomap_ioend_flags(&iter->iomap) | IOMAP_IOEND_DIRECT);
> - if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
> - fs_bio_integrity_generate(bio);
> xfs_zone_alloc_and_submit(ioend, &ac->open_zone);
> }
>
> diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c
> index bdbb60cc5d5b..bede79859671 100644
> --- a/fs/xfs/xfs_zone_alloc.c
> +++ b/fs/xfs/xfs_zone_alloc.c
> @@ -26,6 +26,7 @@
> #include "xfs_zones.h"
> #include "xfs_trace.h"
> #include "xfs_mru_cache.h"
> +#include <linux/bio-integrity.h>
>
> static void
> xfs_open_zone_free_rcu(
> @@ -909,6 +910,9 @@ xfs_zone_alloc_and_submit(
> if (xfs_is_shutdown(mp))
> goto out_error;
>
> + if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
> + fs_bio_integrity_generate(&ioend->io_bio);
> +
> /*
> * If we don't have a locally cached zone in this write context, see if
> * the inode is still associated with a zone and use that if so.
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 16/16] block,iomap: remove the old read side bounce buffering support
2026-09-09 6:09 ` [PATCH 16/16] block,iomap: remove the old read side bounce buffering support Christoph Hellwig
@ 2026-09-09 16:15 ` Darrick J. Wong
0 siblings, 0 replies; 22+ messages in thread
From: Darrick J. Wong @ 2026-09-09 16:15 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Jens Axboe, Christian Brauner, Carlos Maiolino, Tal Zussman,
Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
On Wed, Sep 09, 2026 at 09:09:05AM +0300, Christoph Hellwig wrote:
> bio_iov_iter_bounce_read turned to generate suboptimal I/O sizes and
> isn't usable for lazy bounce buffering. Now that is has been replaced
> with the iomap side implementation that requires extra bounce bios, it
> is unused and can be removed. Change the interface so that the
> previously hidden write-side implementation is directly exposed.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
Seems reasonable...
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
--D
> ---
> block/bio.c | 150 ++++++-------------------------------------
> fs/iomap/direct-io.c | 7 +-
> include/linux/bio.h | 5 +-
> 3 files changed, 23 insertions(+), 139 deletions(-)
>
> diff --git a/block/bio.c b/block/bio.c
> index 7f7654e60dd4..987082e0f0ef 100644
> --- a/block/bio.c
> +++ b/block/bio.c
> @@ -1223,8 +1223,9 @@ bool bio_iov_iter_set(struct bio *bio, const struct iov_iter *iter)
> * for the next iteration.
> */
> static int bio_iov_iter_align_down(struct bio *bio, struct iov_iter *iter,
> - struct bio_vec *bv, unsigned len_align_mask)
> + unsigned len_align_mask)
> {
> + struct bio_vec *bv = &bio->bi_io_vec[bio->bi_vcnt - 1];
> size_t nbytes = bio->bi_iter.bi_size & len_align_mask;
>
> if (!nbytes)
> @@ -1356,8 +1357,7 @@ int bio_iov_iter_get_pages(struct bio *bio, struct iov_iter *iter,
>
> if (is_pci_p2pdma_page(bio->bi_io_vec->bv_page))
> bio->bi_opf |= REQ_NOMERGE;
> - return bio_iov_iter_align_down(bio, iter,
> - &bio->bi_io_vec[bio->bi_vcnt - 1], len_align_mask);
> + return bio_iov_iter_align_down(bio, iter, len_align_mask);
> }
>
> static struct folio *folio_alloc_greedy(gfp_t gfp, size_t *size,
> @@ -1428,7 +1428,20 @@ int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize)
> return 0;
> }
>
> -static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
> +/**
> + * bio_iov_iter_bounce_write - bounce buffer data from an iter into a bio
> + * @bio: bio to send
> + * @iter: iter to read from
> + * @maxlen: maximum size to bounce
> + * @minsize: minimum folio allocation size
> + *
> + * Helper for direct I/O write implementations that need to bounce buffer
> + * because they need need to checksum the data or perform other operations that
> + * require consistency. Allocates folios to back the bounce buffer, and copies
> + * the data into it. Needs to be paired with bio_free_folios() called on
> + * completion.
> + */
> +int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
> size_t maxlen, size_t minsize)
> {
> size_t total_len = min(maxlen, iov_iter_count(iter));
> @@ -1460,134 +1473,7 @@ static int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
>
> return 0;
> }
> -
> -static int bio_iov_iter_bounce_read(struct bio *bio, struct iov_iter *iter,
> - size_t maxlen, size_t minsize)
> -{
> - size_t len = min3(iov_iter_count(iter), maxlen, SZ_1M);
> - struct folio *folio;
> - ssize_t ret;
> -
> - folio = folio_alloc_greedy(GFP_KERNEL, &len, minsize);
> - if (!folio)
> - return -ENOMEM;
> -
> - do {
> - ret = iov_iter_extract_bvecs(iter, bio->bi_io_vec + 1, len,
> - &bio->bi_vcnt, bio->bi_max_vecs - 1, 0, 0);
> - if (ret <= 0) {
> - if (!bio->bi_vcnt)
> - goto out_folio_put;
> - break;
> - }
> - len -= ret;
> - bio->bi_iter.bi_size += ret;
> - } while (len && bio->bi_vcnt < bio->bi_max_vecs - 1);
> -
> - /*
> - * Set the folio directly here. The above loop has already calculated
> - * the correct bi_size, and we use bi_vcnt for the user buffers. That
> - * is safe as bi_vcnt is only used by the submitter and not the actual
> - * I/O path.
> - */
> - bvec_set_folio(&bio->bi_io_vec[0], folio, bio->bi_iter.bi_size, 0);
> - if (iov_iter_extract_will_pin(iter))
> - bio_set_flag(bio, BIO_PAGE_PINNED);
> -
> - /* The first vec stores the bounce buffer, so do not subtract 1 here. */
> - ret = bio_iov_iter_align_down(bio, iter,
> - &bio->bi_io_vec[bio->bi_vcnt], minsize - 1);
> - if (ret)
> - goto out_folio_put;
> -
> - /* Update the bounc buffer bv_len to the aligned down size. */
> - bio->bi_io_vec[0].bv_len = bio->bi_iter.bi_size;
> - return 0;
> -
> -out_folio_put:
> - folio_put(folio);
> - return ret;
> -}
> -
> -/**
> - * bio_iov_iter_bounce - bounce buffer data from an iter into a bio
> - * @bio: bio to send
> - * @iter: iter to read from / write into
> - * @maxlen: maximum size to bounce
> - * @minsize: minimum folio allocation size
> - *
> - * Helper for direct I/O implementations that need to bounce buffer because
> - * we need to checksum the data or perform other operations that require
> - * consistency. Allocates folios to back the bounce buffer, and for writes
> - * copies the data into it. Needs to be paired with bio_iov_iter_unbounce()
> - * called on completion.
> - */
> -int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen,
> - size_t minsize)
> -{
> - if (op_is_write(bio_op(bio)))
> - return bio_iov_iter_bounce_write(bio, iter, maxlen, minsize);
> - return bio_iov_iter_bounce_read(bio, iter, maxlen, minsize);
> -}
> -
> -static void bvec_unpin(struct bio_vec *bv, bool mark_dirty)
> -{
> - struct folio *folio = bvec_folio(bv);
> - size_t nr_pages = (bv->bv_offset + bv->bv_len - 1) / PAGE_SIZE -
> - bv->bv_offset / PAGE_SIZE + 1;
> -
> - if (mark_dirty)
> - folio_mark_dirty_lock(folio);
> - unpin_user_folio(folio, nr_pages);
> -}
> -
> -static void bio_iov_iter_unbounce_read(struct bio *bio, bool is_error,
> - bool mark_dirty)
> -{
> - unsigned int len = bio->bi_io_vec[0].bv_len;
> -
> - if (likely(!is_error)) {
> - void *buf = bvec_virt(&bio->bi_io_vec[0]);
> - struct iov_iter to;
> -
> - iov_iter_bvec(&to, ITER_DEST, bio->bi_io_vec + 1, bio->bi_vcnt,
> - len);
> - /* copying to pinned pages should always work */
> - WARN_ON_ONCE(copy_to_iter(buf, len, &to) != len);
> - } else {
> - /* No need to mark folios dirty if never copied to them */
> - mark_dirty = false;
> - }
> -
> - if (bio_flagged(bio, BIO_PAGE_PINNED)) {
> - int i;
> -
> - for (i = 0; i < bio->bi_vcnt; i++)
> - bvec_unpin(&bio->bi_io_vec[1 + i], mark_dirty);
> - }
> -
> - folio_put(bvec_folio(&bio->bi_io_vec[0]));
> -}
> -
> -/**
> - * bio_iov_iter_unbounce - finish a bounce buffer operation
> - * @bio: completed bio
> - * @is_error: %true if an I/O error occurred and data should not be copied
> - * @mark_dirty: If %true, folios will be marked dirty.
> - *
> - * Helper for direct I/O implementations that need to bounce buffer because
> - * we need to checksum the data or perform other operations that require
> - * consistency. Called to complete a bio set up by bio_iov_iter_bounce().
> - * Copies data back for reads, and marks the original folios dirty if
> - * requested and then frees the bounce buffer.
> - */
> -void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty)
> -{
> - if (op_is_write(bio_op(bio)))
> - bio_free_folios(bio);
> - else
> - bio_iov_iter_unbounce_read(bio, is_error, mark_dirty);
> -}
> +EXPORT_SYMBOL_GPL(bio_iov_iter_bounce_write);
>
> static void bio_wait_end_io(struct bio *bio)
> {
> diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
> index 4154717a09de..41fdc90a9094 100644
> --- a/fs/iomap/direct-io.c
> +++ b/fs/iomap/direct-io.c
> @@ -255,8 +255,7 @@ static void __iomap_dio_bio_end_io(struct bio *bio, bool inline_completion)
> fs_bio_integrity_free(bio);
>
> if (dio->flags & IOMAP_DIO_BOUNCE) {
> - bio_iov_iter_unbounce(bio, !!dio->error,
> - dio->flags & IOMAP_DIO_USER_BACKED);
> + bio_free_folios(bio);
> bio_put(bio);
> } else if (dio->flags & IOMAP_DIO_USER_BACKED) {
> bio_check_pages_dirty(bio);
> @@ -364,7 +363,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
> bio->bi_end_io = iomap_dio_bio_end_io;
>
> if (dio->flags & IOMAP_DIO_BOUNCE)
> - ret = bio_iov_iter_bounce(bio, dio->submit.iter, maxsize,
> + ret = bio_iov_iter_bounce_write(bio, dio->submit.iter, maxsize,
> alignment);
> else
> ret = bio_iov_iter_get_pages(bio, dio->submit.iter, maxsize,
> @@ -398,7 +397,7 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter *iter,
>
> out_bio_release_pages:
> if (dio->flags & IOMAP_DIO_BOUNCE)
> - bio_iov_iter_unbounce(bio, true, false);
> + bio_free_folios(bio);
> else
> bio_release_pages(bio, false);
> out_put_bio:
> diff --git a/include/linux/bio.h b/include/linux/bio.h
> index bfa3c0e97b6f..17944e44b584 100644
> --- a/include/linux/bio.h
> +++ b/include/linux/bio.h
> @@ -525,11 +525,10 @@ void __bio_release_pages(struct bio *bio, bool mark_dirty);
> extern void bio_set_pages_dirty(struct bio *bio);
> extern void bio_check_pages_dirty(struct bio *bio);
>
> -int bio_iov_iter_bounce(struct bio *bio, struct iov_iter *iter, size_t maxlen,
> - size_t minsize);
> -void bio_iov_iter_unbounce(struct bio *bio, bool is_error, bool mark_dirty);
> int bio_alloc_bounce_folios(struct bio *bio, size_t total_len, size_t minsize);
> void bio_free_folios(struct bio *bio);
> +int bio_iov_iter_bounce_write(struct bio *bio, struct iov_iter *iter,
> + size_t maxlen, size_t minsize);
>
> extern void bio_copy_data(struct bio *dst, struct bio *src);
> extern void bio_free_pages(struct bio *bio);
> --
> 2.53.0
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: lazy bounce buffering for checksummed reads v3
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
` (15 preceding siblings ...)
2026-09-09 6:09 ` [PATCH 16/16] block,iomap: remove the old read side bounce buffering support Christoph Hellwig
@ 2026-09-10 20:50 ` Jens Axboe
2026-09-10 20:51 ` Jens Axboe
16 siblings, 1 reply; 22+ messages in thread
From: Jens Axboe @ 2026-09-10 20:50 UTC (permalink / raw)
To: Christoph Hellwig, Christian Brauner, Darrick J. Wong,
Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
On 9/9/26 12:08 AM, Christoph Hellwig wrote:
> NOTE/QUESTION TO SUBSYSTEM MAINTAINERS: The patches in this series are
> split over 3 subsystems, and I'd love to hear from the maintainers
> about their preferences for merging this.
It's a little hard to split as you have eg block patches both at the
start and beginning... In the spirit of getting this some runtime, I'm
going to queue it up in a separate branch,
for-7.4/lazy-bounce-buffering for now.
--
Jens Axboe
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: lazy bounce buffering for checksummed reads v3
2026-09-10 20:50 ` lazy bounce buffering for checksummed reads v3 Jens Axboe
@ 2026-09-10 20:51 ` Jens Axboe
0 siblings, 0 replies; 22+ messages in thread
From: Jens Axboe @ 2026-09-10 20:51 UTC (permalink / raw)
To: Christoph Hellwig, Christian Brauner, Darrick J. Wong,
Carlos Maiolino
Cc: Tal Zussman, Anuj Gupta, linux-block, linux-xfs, linux-fsdevel
On 9/10/26 2:50 PM, Jens Axboe wrote:
> On 9/9/26 12:08 AM, Christoph Hellwig wrote:
>> NOTE/QUESTION TO SUBSYSTEM MAINTAINERS: The patches in this series are
>> split over 3 subsystems, and I'd love to hear from the maintainers
>> about their preferences for merging this.
>
> It's a little hard to split as you have eg block patches both at the
> start and beginning... In the spirit of getting this some runtime, I'm
> going to queue it up in a separate branch,
> for-7.4/lazy-bounce-buffering for now.
On 2nd thought since this already relies on stuff in the XFS tree
apparently, you can just add my
Reviewed-by: Jens Axboe <axboe@kernel.dk>
to the block patches. Would probably make everybody's life easier.
--
Jens Axboe
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-09-10 20:51 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-09 6:08 lazy bounce buffering for checksummed reads v3 Christoph Hellwig
2026-09-09 6:08 ` [PATCH 01/16] block: split bio_iov_iter_bounce_write Christoph Hellwig
2026-09-09 6:08 ` [PATCH 02/16] block: export fs_bio_integrity_{alloc,free} Christoph Hellwig
2026-09-09 6:08 ` [PATCH 03/16] block: add a bio_prepare_reissue helper Christoph Hellwig
2026-09-09 16:05 ` Darrick J. Wong
2026-09-09 6:08 ` [PATCH 04/16] iomap: respect maximum I/O size in iomap_dio_bio_iter_one Christoph Hellwig
2026-09-09 6:08 ` [PATCH 05/16] iomap: add a iomap_ioend_flags helper Christoph Hellwig
2026-09-09 6:08 ` [PATCH 06/16] iomap: add a IOMAP_IOEND_INTEGRITY flag Christoph Hellwig
2026-09-09 6:08 ` [PATCH 07/16] iomap,xfs: move T10 PI handling for direct I/O into ->submit_io Christoph Hellwig
2026-09-09 6:08 ` [PATCH 08/16] xfs: move PI generation into xfs_submit_zoned_bio Christoph Hellwig
2026-09-09 16:06 ` Darrick J. Wong
2026-09-09 6:08 ` [PATCH 09/16] block,iomap: fix protection information verification with initial bvec offset Christoph Hellwig
2026-09-09 6:08 ` [PATCH 10/16] iomap: better read bounce buffering support Christoph Hellwig
2026-09-09 6:09 ` [PATCH 11/16] xfs: use BIO_COMPLETE_IN_TASK for bounce buffered read I/Os Christoph Hellwig
2026-09-09 6:09 ` [PATCH 12/16] iomap,xfs: move integrity verification to the file system Christoph Hellwig
2026-09-09 6:09 ` [PATCH 13/16] xfs: add support for lazy direct read bounce buffering Christoph Hellwig
2026-09-09 6:09 ` [PATCH 14/16] xfs: add error injection for lazy " Christoph Hellwig
2026-09-09 6:09 ` [PATCH 15/16] xfs: log a message at mount time when using integrity protection Christoph Hellwig
2026-09-09 6:09 ` [PATCH 16/16] block,iomap: remove the old read side bounce buffering support Christoph Hellwig
2026-09-09 16:15 ` Darrick J. Wong
2026-09-10 20:50 ` lazy bounce buffering for checksummed reads v3 Jens Axboe
2026-09-10 20:51 ` Jens Axboe
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox