On Wed, Jan 21, 2026 at 07:43:22AM +0100, Christoph Hellwig wrote: > Add support for generating / verifying protection information in iomap. > This is done by hooking into the bio submission and then using the > generic PI helpers. Compared to just using the block layer auto PI > this extends the protection envelope and also prepares for eventually > passing through PI from userspace at least for direct I/O. > > To generate or verify PI, the file system needs to set the > IOMAP_F_INTEGRITY flag on the iomap for the request, and ensure the > ioends are used for all integrity I/O. Additionally the file system > should defer read I/O completions to user context so that the guard
must ? > tag validation isn't run from interrupt context. > > Signed-off-by: Christoph Hellwig <[email protected]> > --- > fs/iomap/bio.c | 24 +++++++++++++++++++++--- > fs/iomap/direct-io.c | 15 ++++++++++++++- > fs/iomap/internal.h | 13 +++++++++++++ > fs/iomap/ioend.c | 20 ++++++++++++++++++-- > include/linux/iomap.h | 7 +++++++ > 5 files changed, 73 insertions(+), 6 deletions(-) > > diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c > index b4de67bdd513..f989ffcaac96 100644 > --- a/fs/iomap/bio.c > +++ b/fs/iomap/bio.c > @@ -3,6 +3,7 @@ > * Copyright (C) 2010 Red Hat, Inc. > * Copyright (C) 2016-2023 Christoph Hellwig. > */ > +#include <linux/bio-integrity.h> > #include <linux/iomap.h> > #include <linux/pagemap.h> > #include "internal.h" > @@ -17,6 +18,8 @@ static u32 __iomap_read_end_io(struct bio *bio, int error) > iomap_finish_folio_read(fi.folio, fi.offset, fi.length, error); > folio_count++; > } > + if (bio_integrity(bio)) > + fs_bio_integrity_free(bio); > bio_put(bio); > return folio_count; > } > @@ -34,7 +37,11 @@ u32 iomap_finish_ioend_buffered_read(struct iomap_ioend > *ioend) > static void iomap_bio_submit_read(const struct iomap_iter *iter, > struct iomap_read_folio_ctx *ctx) > { > - submit_bio(ctx->read_ctx); > + struct bio *bio = ctx->read_ctx; > + > + if (iter->iomap.flags & IOMAP_F_INTEGRITY) > + fs_bio_integrity_alloc(bio); > + submit_bio(bio); > } > > static struct bio_set *iomap_read_bio_set(struct iomap_read_folio_ctx *ctx) > @@ -91,6 +98,7 @@ int iomap_bio_read_folio_range(const struct iomap_iter > *iter, > > if (!bio || > bio_end_sector(bio) != iomap_sector(&iter->iomap, iter->pos) || > + bio->bi_iter.bi_size > iomap_max_bio_size(&iter->iomap) - plen || > !bio_add_folio(bio, folio, plen, offset_in_folio(folio, iter->pos))) > iomap_read_alloc_bio(iter, ctx, plen); > return 0; > @@ -107,11 +115,21 @@ int iomap_bio_read_folio_range_sync(const struct > iomap_iter *iter, > struct folio *folio, loff_t pos, size_t len) > { > const struct iomap *srcmap = iomap_iter_srcmap(iter); > + sector_t sector = iomap_sector(srcmap, pos); > struct bio_vec bvec; > struct bio bio; > + int error; > > bio_init(&bio, srcmap->bdev, &bvec, 1, REQ_OP_READ); > - bio.bi_iter.bi_sector = iomap_sector(srcmap, pos); > + bio.bi_iter.bi_sector = sector; > bio_add_folio_nofail(&bio, folio, len, offset_in_folio(folio, pos)); > - return submit_bio_wait(&bio); > + if (srcmap->flags & IOMAP_F_INTEGRITY) > + fs_bio_integrity_alloc(&bio); > + error = submit_bio_wait(&bio); > + if (srcmap->flags & IOMAP_F_INTEGRITY) { > + if (!error) > + error = fs_bio_integrity_verify(&bio, sector, len); > + fs_bio_integrity_free(&bio); > + } > + return error; > } > diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c > index 842fc7fecb2d..831378a6ced4 100644 > --- a/fs/iomap/direct-io.c > +++ b/fs/iomap/direct-io.c > @@ -3,6 +3,7 @@ > * Copyright (C) 2010 Red Hat, Inc. > * Copyright (c) 2016-2025 Christoph Hellwig. > */ > +#include <linux/bio-integrity.h> > #include <linux/blk-crypto.h> > #include <linux/fscrypt.h> > #include <linux/pagemap.h> > @@ -215,6 +216,9 @@ static void __iomap_dio_bio_end_io(struct bio *bio, bool > inline_completion) > { > struct iomap_dio *dio = bio->bi_private; > > + if (bio_integrity(bio)) > + fs_bio_integrity_free(bio); > + > if (dio->flags & IOMAP_DIO_BOUNCE) { > bio_iov_iter_unbounce(bio, !!dio->error, > dio->flags & IOMAP_DIO_USER_BACKED); > @@ -325,8 +329,10 @@ 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, UINT_MAX); > + ret = bio_iov_iter_bounce(bio, dio->submit.iter, > + iomap_max_bio_size(&iter->iomap)); > else > ret = bio_iov_iter_get_pages(bio, dio->submit.iter, > alignment - 1); > @@ -343,6 +349,13 @@ static ssize_t iomap_dio_bio_iter_one(struct iomap_iter > *iter, > goto out_put_bio; > } > > + 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/iomap/internal.h b/fs/iomap/internal.h > index b39dbc17e3f0..cfe63de9e5c7 100644 > --- a/fs/iomap/internal.h > +++ b/fs/iomap/internal.h > @@ -4,6 +4,19 @@ > > #define IOEND_BATCH_SIZE 4096 > > +/* > + * Normally we can build bios as big as the data structure supports. > + * > + * But for integrity protected I/O we need to respect the maximum size of the > + * single contiguous allocation for the integrity buffer. > + */ > +static inline size_t iomap_max_bio_size(const struct iomap *iomap) > +{ > + if (iomap->flags & IOMAP_F_INTEGRITY) > + return max_integrity_io_size(bdev_limits(iomap->bdev)); > + return SIZE_MAX; > +} > + > u32 iomap_finish_ioend_buffered_read(struct iomap_ioend *ioend); > u32 iomap_finish_ioend_direct(struct iomap_ioend *ioend); > > diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c > index 72f20e8c8893..a2931f8c454c 100644 > --- a/fs/iomap/ioend.c > +++ b/fs/iomap/ioend.c > @@ -2,6 +2,7 @@ > /* > * Copyright (c) 2016-2025 Christoph Hellwig. > */ > +#include <linux/bio-integrity.h> > #include <linux/iomap.h> > #include <linux/list_sort.h> > #include <linux/pagemap.h> > @@ -59,6 +60,8 @@ static u32 iomap_finish_ioend_buffered_write(struct > iomap_ioend *ioend) > folio_count++; > } > > + if (bio_integrity(bio)) > + fs_bio_integrity_free(bio); > bio_put(bio); /* frees the ioend */ > return folio_count; > } > @@ -92,6 +95,8 @@ int iomap_ioend_writeback_submit(struct iomap_writepage_ctx > *wpc, int error) > return error; > } > > + if (wpc->iomap.flags & IOMAP_F_INTEGRITY) > + fs_bio_integrity_generate(&ioend->io_bio); > submit_bio(&ioend->io_bio); > return 0; > } > @@ -113,10 +118,13 @@ static struct iomap_ioend *iomap_alloc_ioend(struct > iomap_writepage_ctx *wpc, > } > > static bool iomap_can_add_to_ioend(struct iomap_writepage_ctx *wpc, loff_t > pos, > - u16 ioend_flags) > + unsigned int map_len, u16 ioend_flags) > { > struct iomap_ioend *ioend = wpc->wb_ctx; > > + if (ioend->io_bio.bi_iter.bi_size > > + iomap_max_bio_size(&wpc->iomap) - map_len) > + return false; > if (ioend_flags & IOMAP_IOEND_BOUNDARY) > return false; > if ((ioend_flags & IOMAP_IOEND_NOMERGE_FLAGS) != Unrelated question: should iomap_can_add_to_ioend return false if it did an IOMAP_F_ANON_WRITE and the bdevs aren't the same, even if the sectors match? Currently not a problem for XFS, but some day we might want to have a file that maps to zones on different devices. The changes here otherwise look ok to me. --D > @@ -181,7 +189,7 @@ ssize_t iomap_add_to_ioend(struct iomap_writepage_ctx > *wpc, struct folio *folio, > if (pos == wpc->iomap.offset && (wpc->iomap.flags & IOMAP_F_BOUNDARY)) > ioend_flags |= IOMAP_IOEND_BOUNDARY; > > - if (!ioend || !iomap_can_add_to_ioend(wpc, pos, ioend_flags)) { > + if (!ioend || !iomap_can_add_to_ioend(wpc, pos, map_len, ioend_flags)) { > new_ioend: > if (ioend) { > error = wpc->ops->writeback_submit(wpc, 0); > @@ -258,6 +266,14 @@ 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 = fs_bio_integrity_verify(&ioend->io_bio, > + ioend->io_sector, ioend->io_size); > + } > + > 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/include/linux/iomap.h b/include/linux/iomap.h > index 24f884b6b0c4..bde16d619654 100644 > --- a/include/linux/iomap.h > +++ b/include/linux/iomap.h > @@ -65,6 +65,8 @@ struct vm_fault; > * > * IOMAP_F_ATOMIC_BIO indicates that (write) I/O will be issued as an atomic > * bio, i.e. set REQ_ATOMIC. > + * > + * IOMAP_F_INTEGRITY indicates that the filesystems handles integrity > metadata. > */ > #define IOMAP_F_NEW (1U << 0) > #define IOMAP_F_DIRTY (1U << 1) > @@ -79,6 +81,11 @@ struct vm_fault; > #define IOMAP_F_BOUNDARY (1U << 6) > #define IOMAP_F_ANON_WRITE (1U << 7) > #define IOMAP_F_ATOMIC_BIO (1U << 8) > +#ifdef CONFIG_BLK_DEV_INTEGRITY > +#define IOMAP_F_INTEGRITY (1U << 9) > +#else > +#define IOMAP_F_INTEGRITY 0 > +#endif /* CONFIG_BLK_DEV_INTEGRITY */ > > /* > * Flag reserved for file system specific usage > -- > 2.47.3 > >
