* [PATCH 01/25] block: export fs_bio_integrity_verify
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 02/25] iomap: add support for data checksumming Christoph Hellwig
` (23 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Damien Le Moal, Darrick J. Wong
From: Damien Le Moal <dlemoal@kernel.org>
XFS will need to call this in the iomap_write_ops.read_folio_range
implementation.
Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
block/bio-integrity-fs.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/block/bio-integrity-fs.c b/block/bio-integrity-fs.c
index c8e91ada8ca6..20d0a3d52932 100644
--- a/block/bio-integrity-fs.c
+++ b/block/bio-integrity-fs.c
@@ -74,6 +74,7 @@ int fs_bio_integrity_verify(struct bio *bio, struct bvec_iter *data_iter)
bio_integrity_bytes(bi, data_iter->bi_size >> SECTOR_SHIFT);
return blk_status_to_errno(bio_integrity_verify(bio, data_iter));
}
+EXPORT_SYMBOL_GPL(fs_bio_integrity_verify);
static int __init fs_bio_integrity_init(void)
{
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 02/25] iomap: add support for data checksumming
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
2026-11-04 8:57 ` [PATCH 01/25] block: export fs_bio_integrity_verify Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-11 11:21 ` Pankaj Raghav (Samsung)
2026-11-04 8:57 ` [PATCH 03/25] xfs: always call xfs_update_stable_writes in xfs_ioctl_setattr_xflags Christoph Hellwig
` (22 subsequent siblings)
24 siblings, 1 reply; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Add a new refcounted structure for a data checksum attached to
an iomap_ioend, and helpers to allocate, split and access it.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/iomap/bio.c | 2 +-
fs/iomap/direct-io.c | 2 +-
fs/iomap/internal.h | 27 ++++++++++-----
fs/iomap/ioend.c | 78 ++++++++++++++++++++++++++++++++++++++++---
include/linux/iomap.h | 42 ++++++++++++++++++++++-
5 files changed, 136 insertions(+), 15 deletions(-)
diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c
index 256564ebe238..3c13d85997a6 100644
--- a/fs/iomap/bio.c
+++ b/fs/iomap/bio.c
@@ -109,7 +109,7 @@ int iomap_bio_read_folio_range(const struct iomap_iter *iter,
if (!bio ||
bio_end_sector(bio) != iomap_sector(&iter->iomap, iter->pos) ||
- !iomap_bio_has_room(&iter->iomap, bio, plen) ||
+ !iomap_bio_has_room(iter->inode, &iter->iomap, bio, plen) ||
!bio_add_folio(bio, folio, plen, offset_in_folio(folio, iter->pos)))
iomap_read_alloc_bio(iter, ctx, plen);
return 0;
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index ad041ac3252a..36fb2e733b0f 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -342,7 +342,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 maxsize = iomap_max_bio_size(iter->inode, &iter->iomap);
unsigned int nr_vecs;
struct bio *bio;
ssize_t ret;
diff --git a/fs/iomap/internal.h b/fs/iomap/internal.h
index e69b3ac02cc2..b52a49345d68 100644
--- a/fs/iomap/internal.h
+++ b/fs/iomap/internal.h
@@ -4,24 +4,35 @@
#define IOEND_BATCH_SIZE 4096
+static inline unsigned int max_csum_io_size(const struct inode *inode,
+ const struct iomap *iomap)
+{
+ return IOMAP_CSUM_MAX_SIZE << (inode->i_blkbits - iomap->csum_shift);
+}
+
/*
* 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.
+ * But for checksum or integrity protected I/O we need to respect the maximum
+ * size of the single contiguous allocation for the checksum/integrity buffer.
*/
-static inline size_t iomap_max_bio_size(const struct iomap *iomap)
+static inline size_t iomap_max_bio_size(const struct inode *inode,
+ const struct iomap *iomap)
{
+ size_t max = BIO_MAX_SIZE;
+
if (iomap->flags & IOMAP_F_INTEGRITY)
- return max_integrity_io_size(bdev_limits(iomap->bdev));
- return BIO_MAX_SIZE;
+ max = min(max, max_integrity_io_size(bdev_limits(iomap->bdev)));
+ if (iomap->csum_shift)
+ max = min(max, max_csum_io_size(inode, iomap));
+ return max;
}
/* Can @len more bytes go into @bio? */
-static inline bool iomap_bio_has_room(const struct iomap *iomap,
- const struct bio *bio, size_t len)
+static inline bool iomap_bio_has_room(const struct inode *inode,
+ const struct iomap *iomap, const struct bio *bio, size_t len)
{
- size_t max_size = iomap_max_bio_size(iomap);
+ size_t max_size = iomap_max_bio_size(inode, iomap);
return len <= max_size && bio->bi_iter.bi_size <= max_size - len;
}
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 4da248ff99f8..a2283d5e1b8f 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -14,6 +14,7 @@
struct bio_set iomap_ioend_bioset;
EXPORT_SYMBOL_GPL(iomap_ioend_bioset);
static struct bio_set iomap_ioend_split_bioset;
+static mempool_t ioend_csum_pool;
struct iomap_ioend *iomap_init_ioend(struct inode *inode,
struct bio *bio, loff_t file_offset, u16 ioend_flags)
@@ -25,6 +26,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_csum_shift = 0;
ioend->io_bvec_offset = bio->bi_iter.bi_offset;
ioend->io_inode = inode;
ioend->io_offset = file_offset;
@@ -32,10 +34,55 @@ struct iomap_ioend *iomap_init_ioend(struct inode *inode,
ioend->io_sector = bio->bi_iter.bi_sector;
ioend->io_vi = NULL;
ioend->io_private = NULL;
+ ioend->io_csum = NULL;
return ioend;
}
EXPORT_SYMBOL_GPL(iomap_init_ioend);
+static void *__iomap_csum_alloc(struct iomap_ioend *ioend)
+{
+ size_t csum_size = iomap_csum_size(ioend);
+
+ WARN_ON_ONCE(csum_size > IOMAP_CSUM_MAX_SIZE);
+ if (csum_size <= sizeof(ioend->io_csum_inline)) {
+ ioend->io_flags |= IOMAP_IOEND_CSUM_INLINE;
+ return ioend->io_csum_inline;
+ }
+
+ ioend->io_csum_alloc = kmalloc(csum_size,
+ GFP_NOWAIT | __GFP_NOMEMALLOC | __GFP_NORETRY);
+ if (!ioend->io_csum_alloc) {
+ struct page *page = mempool_alloc(&ioend_csum_pool, GFP_NOFS);
+
+ ioend->io_flags |= IOMAP_IOEND_CSUM_MEMPOOL;
+ ioend->io_csum_alloc = page_address(page);
+ }
+ return ioend->io_csum_alloc;
+}
+
+void *iomap_csum_alloc(struct iomap_ioend *ioend, u8 csum_shift)
+{
+ ioend->io_csum_shift = csum_shift;
+ ioend->io_csum = __iomap_csum_alloc(ioend);
+ return ioend->io_csum;
+}
+EXPORT_SYMBOL_GPL(iomap_csum_alloc);
+
+void iomap_csum_free(struct iomap_ioend *ioend)
+{
+ if (ioend->io_flags & IOMAP_IOEND_CSUM_MEMPOOL) {
+ mempool_free(virt_to_page(ioend->io_csum_alloc),
+ &ioend_csum_pool);
+ } else if (!(ioend->io_flags & IOMAP_IOEND_CSUM_INLINE)) {
+ kfree(ioend->io_csum_alloc);
+ }
+
+ ioend->io_flags &=
+ ~(IOMAP_IOEND_CSUM_MEMPOOL | IOMAP_IOEND_CSUM_INLINE);
+ ioend->io_csum_shift = 0;
+}
+EXPORT_SYMBOL_GPL(iomap_csum_free);
+
/*
* We're now finished for good with this ioend structure. Update the folio
* state, release holds on bios, and finally free up memory. Do not use the
@@ -133,7 +180,8 @@ static bool iomap_can_add_to_ioend(struct iomap_writepage_ctx *wpc, loff_t pos,
{
struct iomap_ioend *ioend = wpc->wb_ctx;
- if (!iomap_bio_has_room(&wpc->iomap, &ioend->io_bio, map_len))
+ if (!iomap_bio_has_room(wpc->inode, &wpc->iomap, &ioend->io_bio,
+ map_len))
return false;
if (ioend_flags & IOMAP_IOEND_BOUNDARY)
return false;
@@ -284,7 +332,7 @@ EXPORT_SYMBOL_GPL(iomap_ioend_integrity_verify);
static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
{
- if (ioend->io_parent) {
+ if (ioend->io_flags & IOMAP_IOEND_CHAINED) {
struct bio *bio = &ioend->io_bio;
ioend = ioend->io_parent;
@@ -297,6 +345,9 @@ static u32 iomap_finish_ioend(struct iomap_ioend *ioend, int error)
if (!atomic_dec_and_test(&ioend->io_remaining))
return 0;
+ if (iomap_has_csum(ioend))
+ iomap_csum_free(ioend);
+
if (ioend->io_flags & IOMAP_IOEND_DIRECT)
return iomap_finish_ioend_direct(ioend);
if (bio_op(&ioend->io_bio) == REQ_OP_READ)
@@ -372,6 +423,8 @@ static bool iomap_ioend_can_merge(struct iomap_ioend *ioend,
if (ioend->io_sector + (ioend->io_size >> SECTOR_SHIFT) !=
next->io_sector)
return false;
+ if (ioend->io_csum || next->io_csum)
+ return false;
return true;
}
@@ -461,8 +514,9 @@ struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend,
split->bi_end_io = bio->bi_end_io;
split_ioend = iomap_init_ioend(ioend->io_inode, split, ioend->io_offset,
- ioend->io_flags);
+ ioend->io_flags | IOMAP_IOEND_CHAINED);
split_ioend->io_parent = ioend;
+ split_ioend->io_csum_shift = ioend->io_csum_shift;
atomic_inc(&ioend->io_remaining);
ioend->io_offset += split_ioend->io_size;
@@ -471,6 +525,12 @@ struct iomap_ioend *iomap_split_ioend(struct iomap_ioend *ioend,
split_ioend->io_sector = ioend->io_sector;
if (!is_append)
ioend->io_sector += (split_ioend->io_size >> SECTOR_SHIFT);
+
+ if (iomap_has_csum(ioend)) {
+ split_ioend->io_csum = ioend->io_csum;
+ ioend->io_csum += iomap_csum_size(split_ioend);
+ }
+
return split_ioend;
}
EXPORT_SYMBOL_GPL(iomap_split_ioend);
@@ -510,7 +570,9 @@ void iomap_bounce_read(struct iomap_ioend *orig_ioend, unsigned int minsize,
bio->bi_iter.bi_sector = sector;
ioend = iomap_init_ioend(inode, bio, file_offset,
- orig_ioend->io_flags);
+ orig_ioend->io_flags &
+ ~(IOMAP_IOEND_CSUM_MEMPOOL |
+ IOMAP_IOEND_CSUM_INLINE));
total_len -= bio->bi_iter.bi_size;
file_offset += bio->bi_iter.bi_size;
@@ -556,6 +618,8 @@ void iomap_bounce_read_end_io(struct iomap_ioend *ioend, struct bio *orig_bio,
else
iomap_ioend_unbounce(iomap_ioend_from_bio(orig_bio), ioend);
+ if (iomap_has_csum(ioend))
+ iomap_csum_free(ioend);
bio_free_folios(&ioend->io_bio);
if (bio_integrity(&ioend->io_bio))
fs_bio_integrity_free(&ioend->io_bio);
@@ -580,8 +644,14 @@ static int __init iomap_ioend_init(void)
BIOSET_NEED_BVECS);
if (error)
goto out_exit_ioend_bioset;
+ error = mempool_init_page_pool(&ioend_csum_pool, BIO_POOL_SIZE,
+ get_order(IOMAP_CSUM_MAX_SIZE));
+ if (error)
+ goto out_exit_ioend_split_bioset;
return 0;
+out_exit_ioend_split_bioset:
+ bioset_exit(&iomap_ioend_split_bioset);
out_exit_ioend_bioset:
bioset_exit(&iomap_ioend_bioset);
return error;
diff --git a/include/linux/iomap.h b/include/linux/iomap.h
index 5254430844b2..d85257880306 100644
--- a/include/linux/iomap.h
+++ b/include/linux/iomap.h
@@ -133,6 +133,7 @@ struct iomap {
u64 length; /* length of mapping, bytes */
u16 type; /* type of mapping */
u16 flags; /* flags for mapping */
+ u8 csum_shift; /* ilog() of csum size */
struct block_device *bdev; /* block device for I/O */
struct dax_device *dax_dev; /* dax_dev for dax operations */
void *inline_data;
@@ -489,6 +490,12 @@ sector_t iomap_bmap(struct address_space *mapping, sector_t bno,
#else
#define IOMAP_IOEND_INTEGRITY 0
#endif /* CONFIG_BLK_DEV_INTEGRITY */
+/* chained ioend that has io_parent */
+#define IOMAP_IOEND_CHAINED (1U << 6)
+/* using io_csum_inline */
+#define IOMAP_IOEND_CSUM_INLINE (1U << 7)
+/* io_csum is backed by a mempool */
+#define IOMAP_IOEND_CSUM_MEMPOOL (1U << 8)
/*
* Flags that if set on either ioend prevent the merge of two ioends.
@@ -522,14 +529,31 @@ 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_* */
+ u8 io_csum_shift; /* ilog(2) of csum size */
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 */
int io_error; /* stashed away status */
- struct iomap_ioend *io_parent; /* parent for completions */
+
+ /*
+ * There are two kinds of ioend, the original ones submitted by iomap,
+ * and thus split of for I/O size limitations. For the latter,
+ * io_parent points to the original ioend. For the original ioends,
+ * the space used by the parent pointer is reused by the checksum
+ * pointer when data checksum are used. Split ioends always point
+ * into the original allocation with their io_csum pointer. As a
+ * special case tiny checksum use the pointer as actual checksum
+ * storage if that fits.
+ */
+ union {
+ struct iomap_ioend *io_parent; /* parent for completions */
+ void *io_csum_alloc; /* original csum allocation. */
+ u8 io_csum_inline[sizeof(void *)];
+ };
loff_t io_offset; /* offset in the file */
sector_t io_sector; /* start sector of ioend */
+ void *io_csum; /* data checksum */
void *io_private; /* file system private data */
struct fsverity_info *io_vi; /* fsverity info */
struct bio io_bio; /* MUST BE LAST! */
@@ -547,6 +571,22 @@ static inline struct iomap_ioend *iomap_ioend_from_bio(struct bio *bio)
.bi_offset = (_ioend)->io_bvec_offset, \
}
+#define IOMAP_CSUM_MAX_SIZE SZ_64K
+
+static inline bool iomap_has_csum(const struct iomap_ioend *ioend)
+{
+ return ioend->io_csum_shift > 0;
+}
+
+static inline size_t iomap_csum_size(const struct iomap_ioend *ioend)
+{
+ return DIV_ROUND_UP(ioend->io_size, i_blocksize(ioend->io_inode)) <<
+ ioend->io_csum_shift;
+}
+
+void *iomap_csum_alloc(struct iomap_ioend *ioend, u8 csum_shift);
+void iomap_csum_free(struct iomap_ioend *ioend);
+
struct iomap_writeback_ops {
/*
* Performs writeback on the passed in range
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [PATCH 02/25] iomap: add support for data checksumming
2026-11-04 8:57 ` [PATCH 02/25] iomap: add support for data checksumming Christoph Hellwig
@ 2026-11-11 11:21 ` Pankaj Raghav (Samsung)
2026-11-11 17:38 ` Christoph Hellwig
0 siblings, 1 reply; 34+ messages in thread
From: Pankaj Raghav (Samsung) @ 2026-11-11 11:21 UTC (permalink / raw)
To: Christoph Hellwig; +Cc: Carlos Maiolino, linux-xfs, linux-fsdevel
On Wed, Nov 04, 2026 at 09:57:06AM +0100, Christoph Hellwig wrote:
> Add a new refcounted structure for a data checksum attached to
I don't see any new refcounted structure. Probably the commit message is
stale?
> an iomap_ioend, and helpers to allocate, split and access it.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
>
> /*
> * Flags that if set on either ioend prevent the merge of two ioends.
> @@ -522,14 +529,31 @@ 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_* */
> + u8 io_csum_shift; /* ilog(2) of csum size */
> 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 */
> int io_error; /* stashed away status */
> - struct iomap_ioend *io_parent; /* parent for completions */
> +
> + /*
> + * There are two kinds of ioend, the original ones submitted by iomap,
> + * and thus split of for I/O size limitations. For the latter,
s/thus split of/those split off
> + * io_parent points to the original ioend. For the original ioends,
> + * the space used by the parent pointer is reused by the checksum
> + * pointer when data checksum are used. Split ioends always point
> + * into the original allocation with their io_csum pointer. As a
> + * special case tiny checksum use the pointer as actual checksum
> + * storage if that fits.
> + */
> + union {
> + struct iomap_ioend *io_parent; /* parent for completions */
> + void *io_csum_alloc; /* original csum allocation. */
> + u8 io_csum_inline[sizeof(void *)];
> + };
> loff_t io_offset; /* offset in the file */
> sector_t io_sector; /* start sector of ioend */
> + void *io_csum; /* data checksum */
> void *io_private; /* file system private data */
> struct fsverity_info *io_vi; /* fsverity info */
> struct bio io_bio; /* MUST BE LAST! */
> @@ -547,6 +571,22 @@ static inline struct iomap_ioend *iomap_ioend_from_bio(struct bio *bio)
> .bi_offset = (_ioend)->io_bvec_offset, \
> }
>
--
Pankaj
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [PATCH 02/25] iomap: add support for data checksumming
2026-11-11 11:21 ` Pankaj Raghav (Samsung)
@ 2026-11-11 17:38 ` Christoph Hellwig
0 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-11 17:38 UTC (permalink / raw)
To: Pankaj Raghav (Samsung)
Cc: Christoph Hellwig, Carlos Maiolino, linux-xfs, linux-fsdevel
On Wed, Nov 11, 2026 at 12:21:07PM +0100, Pankaj Raghav (Samsung) wrote:
> On Wed, Nov 04, 2026 at 09:57:06AM +0100, Christoph Hellwig wrote:
> > Add a new refcounted structure for a data checksum attached to
>
> I don't see any new refcounted structure. Probably the commit message is
> stale?
Yes, I'll fix it up.
^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH 03/25] xfs: always call xfs_update_stable_writes in xfs_ioctl_setattr_xflags
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
2026-11-04 8:57 ` [PATCH 01/25] block: export fs_bio_integrity_verify Christoph Hellwig
2026-11-04 8:57 ` [PATCH 02/25] iomap: add support for data checksumming Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-11 11:11 ` Pankaj Raghav (Samsung)
2026-11-04 8:57 ` [PATCH 04/25] xfs: use a separate lockdep class for all rmap inode locks Christoph Hellwig
` (21 subsequent siblings)
24 siblings, 1 reply; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Wilfred Mallawa
Currently xfs_update_stable_writes() is never called when the RT flag is
flipped because xfs_ioctl_setattr_xflags() updates ip->i_diflags before
comparing the rtflag variable to XFS_IS_REALTIME_INODE(ip), and thus
the stable flag can be set incorretly when flipping the realtime bit
on an existing file.
Fix this by unconditionally calling xfs_update_stable_writes for regular
files.
Fixes: d528a6c1d4b6 ("xfs: data checksums require stable writes")
Reported-by: Wilfred Mallawa <wilfred.opensource@gmail.com>
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_ioctl.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
index f81b6e52ac40..36ba609a2593 100644
--- a/fs/xfs/xfs_ioctl.c
+++ b/fs/xfs/xfs_ioctl.c
@@ -595,7 +595,7 @@ xfs_ioctl_setattr_xflags(
* Make the stable writes flag match that of the device the inode
* resides on when flipping the RT flag.
*/
- if (rtflag != XFS_IS_REALTIME_INODE(ip) && S_ISREG(VFS_I(ip)->i_mode))
+ if (S_ISREG(VFS_I(ip)->i_mode))
xfs_update_stable_writes(ip);
xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [PATCH 03/25] xfs: always call xfs_update_stable_writes in xfs_ioctl_setattr_xflags
2026-11-04 8:57 ` [PATCH 03/25] xfs: always call xfs_update_stable_writes in xfs_ioctl_setattr_xflags Christoph Hellwig
@ 2026-11-11 11:11 ` Pankaj Raghav (Samsung)
2026-11-11 17:37 ` Christoph Hellwig
0 siblings, 1 reply; 34+ messages in thread
From: Pankaj Raghav (Samsung) @ 2026-11-11 11:11 UTC (permalink / raw)
To: Christoph Hellwig
Cc: Carlos Maiolino, linux-xfs, linux-fsdevel, Wilfred Mallawa
On Wed, Nov 04, 2026 at 09:57:07AM +0100, Christoph Hellwig wrote:
> Currently xfs_update_stable_writes() is never called when the RT flag is
> flipped because xfs_ioctl_setattr_xflags() updates ip->i_diflags before
> comparing the rtflag variable to XFS_IS_REALTIME_INODE(ip), and thus
> the stable flag can be set incorretly when flipping the realtime bit
> on an existing file.
>
> Fix this by unconditionally calling xfs_update_stable_writes for regular
> files.
>
> Fixes: d528a6c1d4b6 ("xfs: data checksums require stable writes")
Shouldn't this patch be folded in to the stable writes patches?
> Reported-by: Wilfred Mallawa <wilfred.opensource@gmail.com>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
> fs/xfs/xfs_ioctl.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c
> index f81b6e52ac40..36ba609a2593 100644
> --- a/fs/xfs/xfs_ioctl.c
> +++ b/fs/xfs/xfs_ioctl.c
> @@ -595,7 +595,7 @@ xfs_ioctl_setattr_xflags(
> * Make the stable writes flag match that of the device the inode
> * resides on when flipping the RT flag.
> */
> - if (rtflag != XFS_IS_REALTIME_INODE(ip) && S_ISREG(VFS_I(ip)->i_mode))
> + if (S_ISREG(VFS_I(ip)->i_mode))
> xfs_update_stable_writes(ip);
>
> xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
> --
> 2.53.0
>
--
Pankaj
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [PATCH 03/25] xfs: always call xfs_update_stable_writes in xfs_ioctl_setattr_xflags
2026-11-11 11:11 ` Pankaj Raghav (Samsung)
@ 2026-11-11 17:37 ` Christoph Hellwig
0 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-11 17:37 UTC (permalink / raw)
To: Pankaj Raghav (Samsung)
Cc: Christoph Hellwig, Carlos Maiolino, linux-xfs, linux-fsdevel,
Wilfred Mallawa
On Wed, Nov 11, 2026 at 12:11:51PM +0100, Pankaj Raghav (Samsung) wrote:
> On Wed, Nov 04, 2026 at 09:57:07AM +0100, Christoph Hellwig wrote:
> > Currently xfs_update_stable_writes() is never called when the RT flag is
> > flipped because xfs_ioctl_setattr_xflags() updates ip->i_diflags before
> > comparing the rtflag variable to XFS_IS_REALTIME_INODE(ip), and thus
> > the stable flag can be set incorretly when flipping the realtime bit
> > on an existing file.
> >
> > Fix this by unconditionally calling xfs_update_stable_writes for regular
> > files.
> >
> > Fixes: d528a6c1d4b6 ("xfs: data checksums require stable writes")
>
> Shouldn't this patch be folded in to the stable writes patches?
Not really, this is a fix for patches already in mainline. Carlos
has queued this one up already, so it will be gone from the next
round.
^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH 04/25] xfs: use a separate lockdep class for all rmap inode locks
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (2 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 03/25] xfs: always call xfs_update_stable_writes in xfs_ioctl_setattr_xflags Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 05/25] xfs: add a xfs_buf_read_async buffer cache API Christoph Hellwig
` (20 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
We'll soon use the rmap inode iolock for additional zone reset
synchronization, so assign a differnet lock class to it. And while
we're at it do the same for the invalidate lock.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/libxfs/xfs_rtgroup.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/fs/xfs/libxfs/xfs_rtgroup.c b/fs/xfs/libxfs/xfs_rtgroup.c
index c6981f566860..91961fb85aff 100644
--- a/fs/xfs/libxfs/xfs_rtgroup.c
+++ b/fs/xfs/libxfs/xfs_rtgroup.c
@@ -282,6 +282,8 @@ xfs_rtgroup_get_geometry(
#ifdef CONFIG_PROVE_LOCKING
static struct lock_class_key xfs_rtginode_lock_class;
+static struct lock_class_key xfs_rtginode_iolock_class;
+static struct lock_class_key xfs_rtginode_invalidatelock_class;
static int
xfs_rtginode_ilock_cmp_fn(
@@ -328,6 +330,10 @@ xfs_rtginode_lockdep_setup(
type);
lock_set_cmp_fn(&ip->i_lock, xfs_rtginode_ilock_cmp_fn,
xfs_rtginode_ilock_print_fn);
+ lockdep_set_class_and_subclass(&VFS_I(ip)->i_rwsem,
+ &xfs_rtginode_iolock_class, type);
+ lockdep_set_class_and_subclass(&VFS_I(ip)->i_mapping->invalidate_lock,
+ &xfs_rtginode_invalidatelock_class, type);
}
#else
#define xfs_rtginode_lockdep_setup(ip, rgno, type) do { } while (0)
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 05/25] xfs: add a xfs_buf_read_async buffer cache API
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (3 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 04/25] xfs: use a separate lockdep class for all rmap inode locks Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 06/25] xfs: add xfs_daddr_to_rgno and xfs_daddr_to_rgbno helpers Christoph Hellwig
` (19 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Add a new helper that reads a buffer asynchronously, and one that waits
for this read. The xfs_buf_read_async call is somehwat similar to
readahead, but returns a buffered reference to the caller and does not
become a no-op under memory or I/O congestion. This means the callers
can use xfs_buf_read_async_wait instead of doing another read call
and will never issue I/O.
The intended use is to kick off a read of data checksum buffers at
roughly the same time as the data read so that they are available
in the I/O completion handler.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_buf.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_buf.h | 4 ++
fs/xfs/xfs_trace.h | 3 ++
3 files changed, 103 insertions(+)
diff --git a/fs/xfs/xfs_buf.c b/fs/xfs/xfs_buf.c
index 6c93b4f5629c..39dd3650b425 100644
--- a/fs/xfs/xfs_buf.c
+++ b/fs/xfs/xfs_buf.c
@@ -734,6 +734,102 @@ xfs_buf_read_map(
return error;
}
+static int
+xfs_buf_read_async_wait_lock(
+ struct xfs_buf *bp)
+{
+ int error = 0;
+
+ trace_xfs_buf_read_async_lock(bp, 0, _RET_IP_);
+
+ /* xfs_buf_find_lock can't return an error with 0 flags */
+ xfs_buf_find_lock(bp, 0);
+ if (bp->b_error) {
+ if (!xlog_is_shutdown(bp->b_mount->m_log))
+ xfs_buf_ioerror_alert(bp, __builtin_return_address(0));
+ xfs_buf_clear_flags(bp, XBF_DONE);
+ xfs_buf_stale(bp);
+ error = bp->b_error;
+ /* bad CRC means corrupted metadata */
+ if (error == -EFSBADCRC)
+ error = -EFSCORRUPTED;
+ } else {
+ ASSERT(bp->b_ops);
+ xfs_buf_clear_flags(bp, XBF_READ);
+ }
+ xfs_buf_unlock(bp);
+ return error;
+}
+
+/*
+ * Wait for a buffer read kicked off by xfs_buf_read_async().
+ *
+ * Do a full lock cycle to wait for unfinished I/O and for buffers with errors
+ * or which are stale. For successfully read buffers nothing needs to be done.
+ */
+int
+xfs_buf_read_async_wait(
+ struct xfs_buf *bp)
+{
+ trace_xfs_buf_read_async_wait(bp, 0, _RET_IP_);
+ if (bp->b_error ||
+ (READ_ONCE(bp->b_flags) & (XBF_DONE | XBF_STALE)) != XBF_DONE)
+ return xfs_buf_read_async_wait_lock(bp);
+ return 0;
+}
+
+/*
+ * Kick off an asynchronous read. Unlike xfs_buf_readahead, this returns a
+ * reference to the buffer, and reliably reads the data instead of skipping the
+ * read on memory pressure.
+ *
+ * The buffer may be locked when I/O is kicked off, but the I/O completion
+ * handler will unlock it. The caller needs to lock itself if need to prevent
+ * concurrent access or to synchronize with I/O completion.
+ */
+int
+xfs_buf_read_async(
+ struct xfs_buftarg *btp,
+ xfs_daddr_t daddr,
+ size_t numblks,
+ const struct xfs_buf_ops *ops,
+ struct xfs_buf **bpp)
+{
+ DEFINE_SINGLE_BUF_MAP(map, daddr, numblks);
+ struct xfs_buf *bp;
+ int error;
+
+ ASSERT(!xfs_buftarg_is_mem(btp));
+
+ error = xfs_find_get_buf(btp, &map, 1, XBF_READ, &bp);
+ if (error)
+ return error;
+
+ /*
+ * Do a lockless fast path check for a valid uptodate buffer and avoid
+ * locking entirely in this case.
+ */
+ if ((READ_ONCE(bp->b_flags) & (XBF_DONE | XBF_STALE)) == XBF_DONE)
+ goto done;
+
+ /* xfs_buf_find_lock can't return an error with 0 flags */
+ xfs_buf_find_lock(bp, 0);
+ if (bp->b_flags & XBF_DONE) {
+ xfs_buf_unlock(bp);
+ goto done;
+ }
+ trace_xfs_buf_read_async(bp, 0, _RET_IP_);
+ XFS_STATS_INC(btp->bt_mount, xb_get_read);
+ xfs_buf_hold(bp);
+ bp->b_ops = ops;
+ xfs_buf_clear_flags(bp, XBF_WRITE);
+ xfs_buf_set_flags(bp, XBF_READ | XBF_ASYNC);
+ xfs_buf_submit(bp);
+done:
+ *bpp = bp;
+ return 0;
+}
+
/*
* If we are not low on memory then do the readahead in a deadlock
* safe manner.
diff --git a/fs/xfs/xfs_buf.h b/fs/xfs/xfs_buf.h
index a4729253b56f..1b352ec91aa6 100644
--- a/fs/xfs/xfs_buf.h
+++ b/fs/xfs/xfs_buf.h
@@ -255,6 +255,10 @@ xfs_buf_readahead(
return xfs_buf_readahead_map(target, &map, 1, ops);
}
+int xfs_buf_read_async(struct xfs_buftarg *btp, xfs_daddr_t daddr,
+ size_t numblks, const struct xfs_buf_ops *ops,
+ struct xfs_buf **bpp);
+int xfs_buf_read_async_wait(struct xfs_buf *bp);
int xfs_buf_get_uncached(struct xfs_buftarg *target, size_t numblks,
struct xfs_buf **bpp);
int xfs_buf_read_uncached(struct xfs_buftarg *target, xfs_daddr_t daddr,
diff --git a/fs/xfs/xfs_trace.h b/fs/xfs/xfs_trace.h
index 28d49f158b22..aca4e7b03052 100644
--- a/fs/xfs/xfs_trace.h
+++ b/fs/xfs/xfs_trace.h
@@ -840,6 +840,9 @@ DEFINE_EVENT(xfs_buf_flags_class, name, \
TP_ARGS(bp, flags, caller_ip))
DEFINE_BUF_FLAGS_EVENT(xfs_buf_get);
DEFINE_BUF_FLAGS_EVENT(xfs_buf_read);
+DEFINE_BUF_FLAGS_EVENT(xfs_buf_read_async);
+DEFINE_BUF_FLAGS_EVENT(xfs_buf_read_async_wait);
+DEFINE_BUF_FLAGS_EVENT(xfs_buf_read_async_lock);
DEFINE_BUF_FLAGS_EVENT(xfs_buf_readahead);
TRACE_EVENT(xfs_buf_ioerror,
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 06/25] xfs: add xfs_daddr_to_rgno and xfs_daddr_to_rgbno helpers
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (4 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 05/25] xfs: add a xfs_buf_read_async buffer cache API Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 07/25] xfs: prepare xfs_rtfile_initialize_blocks for larger than FSB blocks Christoph Hellwig
` (18 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Darrick J. Wong
Translate from a disk address to the realtime group and group-relative
block numbers. This will be needed by the data checksumming code.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_rtgroup.h | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/fs/xfs/libxfs/xfs_rtgroup.h b/fs/xfs/libxfs/xfs_rtgroup.h
index fca2eb74908c..f26e324f0de3 100644
--- a/fs/xfs/libxfs/xfs_rtgroup.h
+++ b/fs/xfs/libxfs/xfs_rtgroup.h
@@ -390,4 +390,24 @@ xfs_rtgroup_raw_size(
return g->blocks;
}
+static inline xfs_rgnumber_t
+xfs_daddr_to_rgno(struct xfs_mount *mp, xfs_daddr_t d)
+{
+ struct xfs_groups *g = &mp->m_groups[XG_TYPE_RTG];
+ xfs_rfsblock_t rbno = XFS_BB_TO_FSBT(mp, d) - g->start_fsb;
+
+ ASSERT(xfs_has_rtgroups(mp));
+ return div_u64(rbno, xfs_rtgroup_raw_size(mp));
+}
+
+static inline xfs_rgblock_t
+xfs_daddr_to_rgbno(struct xfs_mount *mp, xfs_daddr_t d)
+{
+ struct xfs_groups *g = &mp->m_groups[XG_TYPE_RTG];
+ xfs_rfsblock_t rbno = XFS_BB_TO_FSBT(mp, d) - g->start_fsb;
+
+ ASSERT(xfs_has_rtgroups(mp));
+ return do_div(rbno, xfs_rtgroup_raw_size(mp));
+}
+
#endif /* __LIBXFS_RTGROUP_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 07/25] xfs: prepare xfs_rtfile_initialize_blocks for larger than FSB blocks
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (5 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 06/25] xfs: add xfs_daddr_to_rgno and xfs_daddr_to_rgbno helpers Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 08/25] xfs: relase zi_open_zones_lock over xfs_open_zone_put on unmount Christoph Hellwig
` (17 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
The upcoming RT data checksum feature will use larger than FSB blocks.
Prepare xfs_rtfile_initialize_blocks to pass the number of FSBs per
RT blocks, and to pass bmapi_flags to ask for contiguous allocation.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/libxfs/xfs_rtbitmap.c | 47 ++++++++++++++++++++++--------------
fs/xfs/libxfs/xfs_rtbitmap.h | 3 ++-
fs/xfs/xfs_rtalloc.c | 4 +--
3 files changed, 33 insertions(+), 21 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_rtbitmap.c b/fs/xfs/libxfs/xfs_rtbitmap.c
index 7fbfca2a5edd..c83d7f961f40 100644
--- a/fs/xfs/libxfs/xfs_rtbitmap.c
+++ b/fs/xfs/libxfs/xfs_rtbitmap.c
@@ -1345,6 +1345,7 @@ xfs_rtfile_alloc_blocks(
struct xfs_inode *ip,
xfs_fileoff_t offset_fsb,
xfs_filblks_t count_fsb,
+ uint32_t bmapi_flags,
struct xfs_bmbt_irec *map)
{
struct xfs_mount *mp = ip->i_mount;
@@ -1366,7 +1367,7 @@ xfs_rtfile_alloc_blocks(
goto out_trans_cancel;
error = xfs_bmapi_write(tp, ip, offset_fsb, count_fsb,
- XFS_BMAPI_METADATA, 0, map, &nmap);
+ XFS_BMAPI_METADATA | bmapi_flags, 0, map, &nmap);
if (error)
goto out_trans_cancel;
@@ -1404,34 +1405,46 @@ xfs_rtfile_initialize_block(
struct xfs_rtgroup *rtg,
enum xfs_rtg_inodes type,
xfs_fsblock_t fsbno,
- void *data)
+ xfs_filblks_t nblks,
+ void **data)
{
struct xfs_mount *mp = rtg_mount(rtg);
struct xfs_inode *ip = rtg->rtg_inodes[type];
+ size_t len = XFS_FSB_TO_B(mp, nblks);
+ size_t copylen = len;
+ struct xfs_trans_res tres = M_RES(mp)->tr_growrtzero;
struct xfs_trans *tp;
struct xfs_buf *bp;
- const size_t copylen = mp->m_blockwsize << XFS_WORDLOG;
int error;
- error = xfs_trans_alloc(mp, &M_RES(mp)->tr_growrtzero, 0, 0, 0, &tp);
+ /* The log should always be able to handle 64k of logged buffers */
+ ASSERT(nblks <= XFS_B_TO_FSB(mp, SZ_64K));
+
+ tres.tr_logres *= nblks;
+ error = xfs_trans_alloc(mp, &tres, 0, 0, 0, &tp);
if (error)
return error;
xfs_ilock(ip, XFS_ILOCK_EXCL);
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
error = xfs_trans_get_buf(tp, mp->m_ddev_targp,
- XFS_FSB_TO_DADDR(mp, fsbno), mp->m_bsize, 0, &bp);
+ XFS_FSB_TO_DADDR(mp, fsbno), BTOBB(len), 0, &bp);
if (error) {
xfs_trans_cancel(tp);
return error;
}
+ if (xfs_has_rtgroups(mp))
+ copylen -= sizeof(struct xfs_rtbuf_blkinfo);
+
xfs_rtfile_initialize_buf(rtg, type, bp, tp);
- if (data)
- memcpy(xfs_rtblock_payload(bp), data, copylen);
- else
+ if (*data) {
+ memcpy(xfs_rtblock_payload(bp), *data, copylen);
+ *data += copylen;
+ } else {
memset(xfs_rtblock_payload(bp), 0, copylen);
- xfs_trans_log_buf(tp, bp, 0, mp->m_sb.sb_blocksize - 1);
+ }
+ xfs_trans_log_buf(tp, bp, 0, len - 1);
return xfs_trans_commit(tp);
}
@@ -1446,33 +1459,31 @@ xfs_rtfile_initialize_blocks(
enum xfs_rtg_inodes type,
xfs_fileoff_t offset_fsb, /* offset to start from */
xfs_fileoff_t end_fsb, /* offset to allocate to */
+ xfs_filblks_t bsize,
+ uint32_t bmapi_flags,
void *data) /* data to fill the blocks */
{
- struct xfs_mount *mp = rtg_mount(rtg);
- const size_t copylen = mp->m_blockwsize << XFS_WORDLOG;
-
while (offset_fsb < end_fsb) {
struct xfs_bmbt_irec map;
xfs_filblks_t i;
int error;
error = xfs_rtfile_alloc_blocks(rtg->rtg_inodes[type],
- offset_fsb, end_fsb - offset_fsb, &map);
+ offset_fsb, end_fsb - offset_fsb, bmapi_flags,
+ &map);
if (error)
return error;
/*
- * Now we need to clear the allocated blocks.
+ * Now we need to clear or initialize the allocated blocks.
*
* Do this one block per transaction, to keep it simple.
*/
- for (i = 0; i < map.br_blockcount; i++) {
+ for (i = 0; i < map.br_blockcount; i += bsize) {
error = xfs_rtfile_initialize_block(rtg, type,
- map.br_startblock + i, data);
+ map.br_startblock + i, bsize, &data);
if (error)
return error;
- if (data)
- data += copylen;
}
offset_fsb = map.br_startoff + map.br_blockcount;
diff --git a/fs/xfs/libxfs/xfs_rtbitmap.h b/fs/xfs/libxfs/xfs_rtbitmap.h
index 750d74fbf4ed..e9e3378d15aa 100644
--- a/fs/xfs/libxfs/xfs_rtbitmap.h
+++ b/fs/xfs/libxfs/xfs_rtbitmap.h
@@ -410,7 +410,8 @@ void xfs_rtfile_initialize_buf(struct xfs_rtgroup *rtg,
struct xfs_trans *tp);
int xfs_rtfile_initialize_blocks(struct xfs_rtgroup *rtg,
enum xfs_rtg_inodes type, xfs_fileoff_t offset_fsb,
- xfs_fileoff_t end_fsb, void *data);
+ xfs_fileoff_t end_fsb, xfs_filblks_t bsize,
+ uint32_t bmapi_flags, void *data);
int xfs_rtbitmap_create(struct xfs_rtgroup *rtg, struct xfs_inode *ip,
struct xfs_trans *tp, bool init);
int xfs_rtsummary_create(struct xfs_rtgroup *rtg, struct xfs_inode *ip,
diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c
index 84efe5a8fb11..78a1c066c7eb 100644
--- a/fs/xfs/xfs_rtalloc.c
+++ b/fs/xfs/xfs_rtalloc.c
@@ -1191,11 +1191,11 @@ xfs_growfs_rt_alloc_blocks(
}
error = xfs_rtfile_initialize_blocks(rtg, XFS_RTGI_BITMAP, orbmblocks,
- nmp->m_sb.sb_rbmblocks, NULL);
+ nmp->m_sb.sb_rbmblocks, 1, 0, NULL);
if (error)
goto out_free;
error = xfs_rtfile_initialize_blocks(rtg, XFS_RTGI_SUMMARY, orsumblocks,
- nmp->m_rsumblocks, NULL);
+ nmp->m_rsumblocks, 1, 0, NULL);
out_free:
kfree(nmp);
return error;
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 08/25] xfs: relase zi_open_zones_lock over xfs_open_zone_put on unmount
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (6 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 07/25] xfs: prepare xfs_rtfile_initialize_blocks for larger than FSB blocks Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 09/25] xfs: put the open zone outside RCU protection in xfs_get_cached_zone Christoph Hellwig
` (16 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
xfs_open_zone_put will soon grow code that could sleep. The only
caller that currently doesn't allow that is xfs_free_open_zones, but
that can easily drop zi_open_zones_lock because at this point nothing
else is looking at the open zone list.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_zone_alloc.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c
index f827ea3d899a..8909c8d0f5fc 100644
--- a/fs/xfs/xfs_zone_alloc.c
+++ b/fs/xfs/xfs_zone_alloc.c
@@ -1037,7 +1037,10 @@ xfs_free_open_zones(
while ((oz = list_first_entry_or_null(&zi->zi_open_zones,
struct xfs_open_zone, oz_entry))) {
list_del(&oz->oz_entry);
+ spin_unlock(&zi->zi_open_zones_lock);
+
xfs_open_zone_put(oz);
+ spin_lock(&zi->zi_open_zones_lock);
}
spin_unlock(&zi->zi_open_zones_lock);
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 09/25] xfs: put the open zone outside RCU protection in xfs_get_cached_zone
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (7 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 08/25] xfs: relase zi_open_zones_lock over xfs_open_zone_put on unmount Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 10/25] xfs: define the RT data checksum on-disk format Christoph Hellwig
` (15 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
With the RT data checksum support, xfs_open_zone_put will need a
sleepable context, so move it out of the RCU critical section.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_zone_alloc.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c
index 8909c8d0f5fc..509420c63e49 100644
--- a/fs/xfs/xfs_zone_alloc.c
+++ b/fs/xfs/xfs_zone_alloc.c
@@ -842,9 +842,9 @@ xfs_get_cached_zone(
if (oz && oz->oz_allocated == rtg_blocks(oz->oz_rtg)) {
VFS_I(ip)->i_private = NULL;
spin_unlock(&ip->i_flags_lock);
+ rcu_read_unlock();
xfs_open_zone_put(oz);
- oz = NULL;
- goto out_unlock;
+ return NULL;
}
spin_unlock(&ip->i_flags_lock);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 10/25] xfs: define the RT data checksum on-disk format
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (8 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 09/25] xfs: put the open zone outside RCU protection in xfs_get_cached_zone Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 11/25] xfs: add support for per-RTG csum files Christoph Hellwig
` (14 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Add the on-disk format for the new RT data checksum format.
Keyed off a new read-only compat feature flag, this adds new fields to
the superblock to indicate the checksum algorithm used and the size of
the blocks containing the checksums. These new fields reuse the
previously reserved padding to make efficient use of the space in the
on-disk superblock.
Data checksums are only supported on zoned RT devices, because they
require out of places writes to safely update the checksums for file
overwrites and a data/metadata split to be able to store the checksums
for a group in a file without causing recursion. This means they can't
be supported directly on the data device at all, and only when using
the always_cow mode on regular RT devices, but that has no benefit
over the zoned allocator which is designed for out of place writes.
The initially supported data checksum algorithms are crc32c and crc64 as
specified by NVMe. Both have extremely fast kernel implementations and
the strong data protection guarantees offered by CRC-style algorithms.
Both also happen to be support by NVMe for protection information so that
the userspace PI passthrough support (once extended to files on file
systems) can be reused to expose the checksums to applications and thus
provide true end-to-end data integrity.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/libxfs/xfs_format.h | 43 +++++++++++++++--
fs/xfs/libxfs/xfs_log_format.h | 1 +
fs/xfs/libxfs/xfs_ondisk.h | 4 +-
fs/xfs/libxfs/xfs_sb.c | 86 ++++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_sb.h | 1 +
| 5 ++
fs/xfs/xfs_mount.h | 4 ++
7 files changed, 140 insertions(+), 4 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index 469e879149ac..3d794a2eb497 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -179,7 +179,9 @@ typedef struct xfs_sb {
xfs_rgnumber_t sb_rgcount; /* number of realtime groups */
xfs_rtxlen_t sb_rgextents; /* size of a realtime group in rtx */
uint8_t sb_rgblklog; /* rt group number shift */
- uint8_t sb_pad[7]; /* zeroes */
+ uint8_t sb_rtcsum_type; /* RT device data checksum type */
+ uint8_t sb_rtcsum_blklog; /* log2 of rtcsum bsize */
+ uint8_t sb_pad[5]; /* zero */
xfs_rfsblock_t sb_rtstart; /* start of internal RT section (FSB) */
xfs_filblks_t sb_rtreserved; /* reserved (zoned) RT blocks */
@@ -272,7 +274,9 @@ struct xfs_dsb {
__be32 sb_rgcount; /* # of realtime groups */
__be32 sb_rgextents; /* size of rtgroup in rtx */
__u8 sb_rgblklog; /* rt group number shift */
- __u8 sb_pad[7]; /* zeroes */
+ __u8 sb_rtcsum_type; /* RT device data checksum type */
+ __u8 sb_rtcsum_blklog; /* log2 of rtcsum bsize */
+ __u8 sb_pad[5]; /* zero */
__be64 sb_rtstart; /* start of internal RT section (FSB) */
__be64 sb_rtreserved; /* reserved (zoned) RT blocks */
@@ -374,6 +378,7 @@ xfs_sb_has_compat_feature(
#define XFS_SB_FEAT_RO_COMPAT_RMAPBT (1 << 1) /* reverse map btree */
#define XFS_SB_FEAT_RO_COMPAT_REFLINK (1 << 2) /* reflinked files */
#define XFS_SB_FEAT_RO_COMPAT_INOBTCNT (1 << 3) /* inobt block counts */
+
#define XFS_SB_FEAT_RO_COMPAT_ALL \
(XFS_SB_FEAT_RO_COMPAT_FINOBT | \
XFS_SB_FEAT_RO_COMPAT_RMAPBT | \
@@ -399,6 +404,7 @@ xfs_sb_has_ro_compat_feature(
#define XFS_SB_FEAT_INCOMPAT_METADIR (1 << 8) /* metadata dir tree */
#define XFS_SB_FEAT_INCOMPAT_ZONED (1 << 9) /* zoned RT allocator */
#define XFS_SB_FEAT_INCOMPAT_ZONE_GAPS (1 << 10) /* RTGs have LBA gaps */
+#define XFS_SB_FEAT_INCOMPAT_RTCSUM (1 << 11) /* RT data checksums */
#define XFS_SB_FEAT_INCOMPAT_ALL \
(XFS_SB_FEAT_INCOMPAT_FTYPE | \
@@ -866,6 +872,7 @@ enum xfs_metafile_type {
XFS_METAFILE_RTSUMMARY, /* rt summary */
XFS_METAFILE_RTRMAP, /* rt rmap */
XFS_METAFILE_RTREFCOUNT, /* rt refcount */
+ XFS_METAFILE_RTCSUM, /* rt data checksums */
XFS_METAFILE_MAX
} __packed;
@@ -879,7 +886,8 @@ enum xfs_metafile_type {
{ XFS_METAFILE_RTBITMAP, "rtbitmap" }, \
{ XFS_METAFILE_RTSUMMARY, "rtsummary" }, \
{ XFS_METAFILE_RTRMAP, "rtrmap" }, \
- { XFS_METAFILE_RTREFCOUNT, "rtrefcount" }
+ { XFS_METAFILE_RTREFCOUNT, "rtrefcount" }, \
+ { XFS_METAFILE_RTCSUM, "rtcsum", }
/*
* On-disk inode structure.
@@ -1322,6 +1330,7 @@ static inline bool xfs_dinode_is_metadir(const struct xfs_dinode *dip)
*/
#define XFS_RTBITMAP_MAGIC 0x424D505A /* BMPZ */
#define XFS_RTSUMMARY_MAGIC 0x53554D59 /* SUMY */
+#define XFS_RTCSUM_MAGIC 0x4353554D /* CSUM */
struct xfs_rtbuf_blkinfo {
__be32 rt_magic; /* validity check on block */
@@ -2031,4 +2040,32 @@ struct xfs_acl {
#define SGI_ACL_FILE_SIZE (sizeof(SGI_ACL_FILE)-1)
#define SGI_ACL_DEFAULT_SIZE (sizeof(SGI_ACL_DEFAULT)-1)
+/*
+ * Size of a RT data checksum block. Data reads must be contained in a single
+ * block, so this should be fairly large.
+ *
+ * The default is 32k, matching the default inode cluster size and the maximum
+ * memory allocation the Linux MM can handle in the fast path. 64k is primarily
+ * there so that this value never needs to be below the FSB size, even for 64k
+ * blocks.
+ */
+#define XFS_RTCSUM_BSIZE_LOG_MIN 15
+#define XFS_RTCSUM_BSIZE_LOG_MAX 16
+
+/*
+ * Data checksum types.
+ */
+#define XFS_CSUM_TYPE_NONE 0u
+#define XFS_CSUM_TYPE_CRC32C 1u
+#define XFS_CSUM_TYPE_CRC64 2u
+#define XFS_CSUM_TYPE_MAX 3u
+
+/*
+ * On-disk data checksums.
+ */
+union xfs_disk_csum {
+ __le32 crc32c;
+ __le64 crc64;
+};
+
#endif /* __XFS_FORMAT_H__ */
diff --git a/fs/xfs/libxfs/xfs_log_format.h b/fs/xfs/libxfs/xfs_log_format.h
index 6e620380d5dc..c6f0edb4bf0a 100644
--- a/fs/xfs/libxfs/xfs_log_format.h
+++ b/fs/xfs/libxfs/xfs_log_format.h
@@ -581,6 +581,7 @@ enum xfs_blft {
XFS_BLFT_SB_BUF,
XFS_BLFT_RTBITMAP_BUF,
XFS_BLFT_RTSUMMARY_BUF,
+ XFS_BLFT_RTCSUM_BUF,
XFS_BLFT_MAX_BUF = (1 << XFS_BLFT_BITS),
};
diff --git a/fs/xfs/libxfs/xfs_ondisk.h b/fs/xfs/libxfs/xfs_ondisk.h
index 23cde1248f01..17ab9366b3b9 100644
--- a/fs/xfs/libxfs/xfs_ondisk.h
+++ b/fs/xfs/libxfs/xfs_ondisk.h
@@ -284,7 +284,9 @@ xfs_check_ondisk_structs(void)
XFS_CHECK_SB_OFFSET(sb_rgcount, 272);
XFS_CHECK_SB_OFFSET(sb_rgextents, 276);
XFS_CHECK_SB_OFFSET(sb_rgblklog, 280);
- XFS_CHECK_SB_OFFSET(sb_pad, 281);
+ XFS_CHECK_SB_OFFSET(sb_rtcsum_type, 281);
+ XFS_CHECK_SB_OFFSET(sb_rtcsum_blklog, 282);
+ XFS_CHECK_SB_OFFSET(sb_pad, 283);
XFS_CHECK_SB_OFFSET(sb_rtstart, 288);
XFS_CHECK_SB_OFFSET(sb_rtreserved, 296);
diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c
index ce44123c98ce..8096706ed78a 100644
--- a/fs/xfs/libxfs/xfs_sb.c
+++ b/fs/xfs/libxfs/xfs_sb.c
@@ -184,6 +184,9 @@ xfs_sb_version_to_features(
features |= XFS_FEAT_METADIR;
if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_ZONED)
features |= XFS_FEAT_ZONED;
+ if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_RTCSUM)
+ features |= XFS_FEAT_RTCSUM;
+
return features;
}
@@ -486,6 +489,42 @@ xfs_validate_sb_zoned(
return 0;
}
+static int
+xfs_validate_sb_csum(
+ struct xfs_mount *mp,
+ struct xfs_sb *sbp)
+{
+ if (!(sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_ZONED)) {
+ xfs_warn(mp, "data checksum required the zone allocator");
+ return -EINVAL;
+ }
+ if (sbp->sb_rtcsum_blklog < XFS_RTCSUM_BSIZE_LOG_MIN ||
+ sbp->sb_rtcsum_blklog > XFS_RTCSUM_BSIZE_LOG_MAX) {
+ xfs_warn(mp,
+"invalid data checksum block log: %u (min %u/max %u)",
+ sbp->sb_rtcsum_blklog,
+ XFS_RTCSUM_BSIZE_LOG_MIN,
+ XFS_RTCSUM_BSIZE_LOG_MAX);
+ return -EINVAL;
+ }
+ if ((1U << sbp->sb_rtcsum_blklog) < sbp->sb_blocksize) {
+ xfs_warn(mp,
+"checksum block size must not be smaller than file system block size: %u/%u",
+ 1U << sbp->sb_rtcsum_blklog, sbp->sb_blocksize);
+ return -EINVAL;
+ }
+ switch (sbp->sb_rtcsum_type) {
+ case XFS_CSUM_TYPE_CRC32C:
+ case XFS_CSUM_TYPE_CRC64:
+ break;
+ default:
+ xfs_warn(mp, "invalid data checksum type: 0x%x",
+ sbp->sb_rtcsum_type);
+ return -EINVAL;
+ }
+ return 0;
+}
+
/* Check the validity of the SB. */
STATIC int
xfs_validate_sb_common(
@@ -579,6 +618,17 @@ xfs_validate_sb_common(
if (error)
return error;
}
+ if (sbp->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_RTCSUM) {
+ error = xfs_validate_sb_csum(mp, sbp);
+ if (error)
+ return error;
+ } else {
+ if (sbp->sb_rtcsum_type || sbp->sb_rtcsum_blklog) {
+ xfs_warn(mp,
+"rtcsum superblock fields must be zero for non-RTCSUM file systems.");
+ return -EINVAL;
+ }
+ }
} else if (sbp->sb_qflags & (XFS_PQUOTA_ENFD | XFS_GQUOTA_ENFD |
XFS_PQUOTA_CHKD | XFS_GQUOTA_CHKD)) {
xfs_notice(mp,
@@ -886,10 +936,20 @@ __xfs_sb_from_disk(
to->sb_rgextents = be32_to_cpu(from->sb_rgextents);
to->sb_rbmino = NULLFSINO;
to->sb_rsumino = NULLFSINO;
+
+ /*
+ * The checksum fields are in space that was padding originally
+ * added with the metadir feature. Always copy the values and
+ * later validate that they are zero for non-csum file systems.
+ */
+ to->sb_rtcsum_type = from->sb_rtcsum_type;
+ to->sb_rtcsum_blklog = from->sb_rtcsum_blklog;
} else {
to->sb_metadirino = NULLFSINO;
to->sb_rgcount = 1;
to->sb_rgextents = 0;
+ to->sb_rtcsum_type = XFS_CSUM_TYPE_NONE;
+ to->sb_rtcsum_blklog = 0;
}
if (to->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_ZONED) {
@@ -1066,6 +1126,15 @@ xfs_sb_to_disk(
to->sb_rsumino = cpu_to_be64(0);
}
+ if (from->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_RTCSUM) {
+ to->sb_rtcsum_type = from->sb_rtcsum_type;
+ to->sb_rtcsum_blklog = from->sb_rtcsum_blklog;
+ } else {
+ /* these are in previou padding and must always be written */
+ to->sb_rtcsum_type = 0;
+ to->sb_rtcsum_blklog = 0;
+ }
+
if (from->sb_features_incompat & XFS_SB_FEAT_INCOMPAT_ZONED) {
to->sb_rtstart = cpu_to_be64(from->sb_rtstart);
to->sb_rtreserved = cpu_to_be64(from->sb_rtreserved);
@@ -1242,6 +1311,20 @@ xfs_mount_sb_set_rextsize(
xfs_sb_mount_rextsize(mp, sbp);
}
+uint8_t
+xfs_data_csum_shift(
+ uint8_t csum)
+{
+ switch (csum) {
+ case XFS_CSUM_TYPE_CRC32C:
+ return 2;
+ case XFS_CSUM_TYPE_CRC64:
+ return 3;
+ default:
+ return 0;
+ }
+}
+
/*
* xfs_mount_common
*
@@ -1310,6 +1393,9 @@ xfs_sb_mount_common(
mp->m_bsize = XFS_FSB_TO_BB(mp, 1);
mp->m_alloc_set_aside = xfs_alloc_set_aside(mp);
mp->m_ag_max_usable = xfs_alloc_ag_max_usable(mp);
+
+ mp->m_rtcsum_shift = xfs_data_csum_shift(mp->m_sb.sb_rtcsum_type);
+ mp->m_rtcsum_bsize = 1U << mp->m_sb.sb_rtcsum_blklog;
}
/*
diff --git a/fs/xfs/libxfs/xfs_sb.h b/fs/xfs/libxfs/xfs_sb.h
index 77de65922213..298d70f2a72e 100644
--- a/fs/xfs/libxfs/xfs_sb.h
+++ b/fs/xfs/libxfs/xfs_sb.h
@@ -19,6 +19,7 @@ extern void xfs_sb_mount_common(struct xfs_mount *mp, struct xfs_sb *sbp);
void xfs_sb_mount_rextsize(struct xfs_mount *mp, struct xfs_sb *sbp);
void xfs_mount_sb_set_rextsize(struct xfs_mount *mp,
struct xfs_sb *sbp, xfs_agblock_t rextsize);
+uint8_t xfs_data_csum_shift(uint8_t csum);
extern void xfs_sb_from_disk(struct xfs_sb *to, struct xfs_dsb *from);
extern void xfs_sb_to_disk(struct xfs_dsb *to, struct xfs_sb *from);
extern void xfs_sb_quota_from_disk(struct xfs_sb *sbp);
--git a/fs/xfs/scrub/agheader.c b/fs/xfs/scrub/agheader.c
index be59b0b49547..b074212fbfff 100644
--- a/fs/xfs/scrub/agheader.c
+++ b/fs/xfs/scrub/agheader.c
@@ -445,6 +445,11 @@ xchk_superblock(
if (memchr_inv(sb->sb_pad, 0, sizeof(sb->sb_pad)))
xchk_block_set_corrupt(sc, bp);
+
+ if (sb->sb_rtcsum_type != mp->m_sb.sb_rtcsum_type)
+ xchk_block_set_corrupt(sc, bp);
+ if (sb->sb_rtcsum_blklog != mp->m_sb.sb_rtcsum_blklog)
+ xchk_block_set_corrupt(sc, bp);
}
if (xfs_has_zoned(mp)) {
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index cbcfb89c67a4..0e60d5c29c9d 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -191,6 +191,8 @@ typedef struct xfs_mount {
uint8_t m_agno_log; /* log #ag's */
uint8_t m_sectbb_log; /* sectlog - BBSHIFT */
int8_t m_rtxblklog; /* log2 of rextsize, if possible */
+ uint8_t m_rtcsum_shift; /* log2 of RT data csum size */
+ uint32_t m_rtcsum_bsize; /* rtcsum block size in bytes */
uint m_blockmask; /* sb_blocksize-1 */
uint m_blockwsize; /* sb_blocksize in words */
@@ -403,6 +405,7 @@ typedef struct xfs_mount {
#define XFS_FEAT_EXCHANGE_RANGE (1ULL << 27) /* exchange range */
#define XFS_FEAT_METADIR (1ULL << 28) /* metadata directory tree */
#define XFS_FEAT_ZONED (1ULL << 29) /* zoned RT device */
+#define XFS_FEAT_RTCSUM (1ULL << 30) /* RT data checksums */
/* Mount features */
#define XFS_FEAT_NOLIFETIME (1ULL << 47) /* disable lifetime hints */
@@ -460,6 +463,7 @@ __XFS_HAS_FEAT(exchange_range, EXCHANGE_RANGE)
__XFS_HAS_FEAT(metadir, METADIR)
__XFS_HAS_FEAT(zoned, ZONED)
__XFS_HAS_FEAT(nolifetime, NOLIFETIME)
+__XFS_HAS_FEAT(rtcsum, RTCSUM)
static inline bool xfs_has_rtgroups(const struct xfs_mount *mp)
{
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 11/25] xfs: add support for per-RTG csum files
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (9 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 10/25] xfs: define the RT data checksum on-disk format Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 12/25] xfs: calculate the log reservation for logging data checksum buffers Christoph Hellwig
` (13 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Add the definitions for another per-RTG file that stores data checksums
for the RTG. The file is fully preallocated at mkfs/growfs time, and
thus bmap lookups for it can be performed without taking locks.
Checksums are organized in fixed size large (initially 32Kib or 64KiB)
blocks to reduce the lookup and read overhead compare to using the
smaller file system block size.
Each block uses the standard RT file header for self-describing metadata
and can thus reuse the buf_ops including the verifier.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/Makefile | 1 +
fs/xfs/libxfs/xfs_cksum.h | 7 +-
fs/xfs/libxfs/xfs_fs.h | 1 +
fs/xfs/libxfs/xfs_health.h | 4 +-
fs/xfs/libxfs/xfs_rtbitmap.c | 10 +++
fs/xfs/libxfs/xfs_rtcsumfile.c | 95 +++++++++++++++++++++
fs/xfs/libxfs/xfs_rtcsumfile.h | 150 +++++++++++++++++++++++++++++++++
fs/xfs/libxfs/xfs_rtgroup.c | 10 +++
fs/xfs/libxfs/xfs_rtgroup.h | 6 ++
fs/xfs/libxfs/xfs_shared.h | 1 +
fs/xfs/xfs_buf_item_recover.c | 10 ++-
fs/xfs/xfs_health.c | 1 +
fs/xfs/xfs_platform.h | 1 +
fs/xfs/xfs_rtalloc.c | 7 ++
14 files changed, 301 insertions(+), 3 deletions(-)
create mode 100644 fs/xfs/libxfs/xfs_rtcsumfile.c
create mode 100644 fs/xfs/libxfs/xfs_rtcsumfile.h
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 399a207f2d0e..79ea4136fbba 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -64,6 +64,7 @@ xfs-y += $(addprefix libxfs/, \
xfs-$(CONFIG_XFS_RT) += $(addprefix libxfs/, \
xfs_rtbitmap.o \
xfs_rtgroup.o \
+ xfs_rtcsumfile.o \
xfs_zones.o \
)
diff --git a/fs/xfs/libxfs/xfs_cksum.h b/fs/xfs/libxfs/xfs_cksum.h
index 999a290cfd72..315b85ff78ae 100644
--- a/fs/xfs/libxfs/xfs_cksum.h
+++ b/fs/xfs/libxfs/xfs_cksum.h
@@ -2,7 +2,12 @@
#ifndef _XFS_CKSUM_H
#define _XFS_CKSUM_H 1
-#define XFS_CRC_SEED (~(uint32_t)0)
+/*
+ * crc32c() does not include the inversion at the beginning and end, while
+ * crc64_nvme() does.
+ */
+#define XFS_CRC_SEED (~(uint32_t)0)
+#define XFS_CRC64_SEED 0
/*
* Calculate the intermediate checksum for a buffer that has the CRC field
diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h
index 185f09f327c0..226cb96dfae8 100644
--- a/fs/xfs/libxfs/xfs_fs.h
+++ b/fs/xfs/libxfs/xfs_fs.h
@@ -1003,6 +1003,7 @@ struct xfs_rtgroup_geometry {
#define XFS_RTGROUP_GEOM_SICK_SUMMARY (1U << 2) /* rtsummary */
#define XFS_RTGROUP_GEOM_SICK_RMAPBT (1U << 3) /* reverse mappings */
#define XFS_RTGROUP_GEOM_SICK_REFCNTBT (1U << 4) /* reference counts */
+#define XFS_RTGROUP_GEOM_SICK_CSUM (1U << 5) /* data csums */
#define XFS_RTGROUP_GEOM_WRITEPOINTER (1U << 0) /* write pointer */
diff --git a/fs/xfs/libxfs/xfs_health.h b/fs/xfs/libxfs/xfs_health.h
index 1d45cf5789e8..349093e75268 100644
--- a/fs/xfs/libxfs/xfs_health.h
+++ b/fs/xfs/libxfs/xfs_health.h
@@ -72,6 +72,7 @@ struct xfs_rtgroup;
#define XFS_SICK_RG_SUMMARY (1 << 2) /* rt groups summary */
#define XFS_SICK_RG_RMAPBT (1 << 3) /* reverse mappings */
#define XFS_SICK_RG_REFCNTBT (1 << 4) /* reference counts */
+#define XFS_SICK_RG_CSUM (1 << 5) /* data checksums */
/* Observable health issues for AG metadata. */
#define XFS_SICK_AG_SB (1 << 0) /* superblock */
@@ -119,7 +120,8 @@ struct xfs_rtgroup;
XFS_SICK_RG_BITMAP | \
XFS_SICK_RG_SUMMARY | \
XFS_SICK_RG_RMAPBT | \
- XFS_SICK_RG_REFCNTBT)
+ XFS_SICK_RG_REFCNTBT | \
+ XFS_SICK_RG_CSUM)
#define XFS_SICK_AG_PRIMARY (XFS_SICK_AG_SB | \
XFS_SICK_AG_AGF | \
diff --git a/fs/xfs/libxfs/xfs_rtbitmap.c b/fs/xfs/libxfs/xfs_rtbitmap.c
index c83d7f961f40..edf7b6e88c7c 100644
--- a/fs/xfs/libxfs/xfs_rtbitmap.c
+++ b/fs/xfs/libxfs/xfs_rtbitmap.c
@@ -124,9 +124,18 @@ const struct xfs_buf_ops xfs_rtsummary_buf_ops = {
.verify_struct = xfs_rtbuf_verify,
};
+const struct xfs_buf_ops xfs_rtcsum_buf_ops = {
+ .name = "xfs_rtcsum",
+ .magic = { 0, cpu_to_be32(XFS_RTCSUM_MAGIC) },
+ .verify_read = xfs_rtbuf_verify_read,
+ .verify_write = xfs_rtbuf_verify_write,
+ .verify_struct = xfs_rtbuf_verify,
+};
+
static const struct xfs_buf_ops *xfs_rtblock_buf_ops[XFS_RTGI_MAX] = {
[XFS_RTGI_SUMMARY] = &xfs_rtsummary_buf_ops,
[XFS_RTGI_BITMAP] = &xfs_rtbitmap_buf_ops,
+ [XFS_RTGI_CSUM] = &xfs_rtcsum_buf_ops,
};
const struct xfs_buf_ops *
@@ -142,6 +151,7 @@ xfs_rtblock_ops(
static enum xfs_blft xfs_rtblock_buf_types[XFS_RTGI_MAX] = {
[XFS_RTGI_SUMMARY] = XFS_BLFT_RTSUMMARY_BUF,
[XFS_RTGI_BITMAP] = XFS_BLFT_RTBITMAP_BUF,
+ [XFS_RTGI_CSUM] = XFS_BLFT_RTCSUM_BUF,
};
/* Release cached rt bitmap and summary buffers. */
diff --git a/fs/xfs/libxfs/xfs_rtcsumfile.c b/fs/xfs/libxfs/xfs_rtcsumfile.c
new file mode 100644
index 000000000000..076ff12ed5cd
--- /dev/null
+++ b/fs/xfs/libxfs/xfs_rtcsumfile.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Christoph Hellwig.
+ */
+#include "xfs_platform.h"
+#include "xfs_fs.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_shared.h"
+#include "xfs_trans_resv.h"
+#include "xfs_bit.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_bmap.h"
+#include "xfs_rtbitmap.h"
+#include "xfs_bmap_btree.h"
+#include "xfs_trans.h"
+#include "xfs_error.h"
+#include "xfs_health.h"
+#include "xfs_rtcsumfile.h"
+
+int
+xfs_rtcsum_bmap(
+ struct xfs_rtgroup *rtg,
+ xfs_rgblock_t rgbno,
+ xfs_daddr_t *daddr)
+{
+ struct xfs_mount *mp = rtg_mount(rtg);
+ struct xfs_inode *csumip = rtg_csum(rtg);
+ struct xfs_ifork *ifp = &csumip->i_df;
+ unsigned int csum_block =
+ xfs_rgbno_to_rtcsumblock(mp, rgbno);
+ xfs_fileoff_t start_fsb =
+ XFS_B_TO_FSB(mp, mp->m_rtcsum_bsize) * csum_block;
+ struct xfs_iext_cursor icur;
+ struct xfs_bmbt_irec got;
+
+ ASSERT(!xfs_need_iread_extents(ifp));
+
+ if (XFS_IS_CORRUPT(mp, ifp->if_nextents != 1))
+ goto sick;
+
+ /*
+ * We can do an unlocked lookup here because the bmap btree for the
+ * csum files is immutable once created.
+ */
+ if (XFS_IS_CORRUPT(mp, !xfs_iext_lookup_extent(csumip, ifp, start_fsb,
+ &icur, &got)))
+ goto sick;
+ if (XFS_IS_CORRUPT(mp, got.br_startoff > start_fsb))
+ goto sick;
+
+ start_fsb -= got.br_startoff;
+ *daddr = XFS_FSB_TO_DADDR(mp, got.br_startblock + start_fsb);
+ return 0;
+sick:
+ xfs_rtginode_mark_sick(rtg, XFS_RTGI_CSUM);
+ return -EFSCORRUPTED;
+}
+
+xfs_off_t
+xfs_rtcsum_file_size(
+ struct xfs_rtgroup *rtg)
+{
+ struct xfs_mount *mp = rtg_mount(rtg);
+ uint64_t raw_size;
+
+ raw_size = (xfs_off_t)rtg_blocks(rtg) << mp->m_rtcsum_shift;
+ return DIV_ROUND_UP_ULL(raw_size, xfs_rtcsum_payload_size(mp)) *
+ mp->m_rtcsum_bsize;
+}
+
+int
+xfs_rtcsum_alloc_blocks(
+ struct xfs_rtgroup *rtg)
+{
+ struct xfs_mount *mp = rtg_mount(rtg);
+
+ return xfs_rtfile_initialize_blocks(rtg, XFS_RTGI_CSUM, 0,
+ XFS_B_TO_FSB(mp, rtg_csum(rtg)->i_disk_size),
+ XFS_B_TO_FSB(mp, mp->m_rtcsum_bsize),
+ XFS_BMAPI_CONTIG, NULL);
+}
+
+int
+xfs_rtcsum_create(
+ struct xfs_rtgroup *rtg,
+ struct xfs_inode *ip,
+ struct xfs_trans *tp,
+ bool init)
+{
+ ip->i_disk_size = xfs_rtcsum_file_size(rtg);
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
+ return 0;
+}
diff --git a/fs/xfs/libxfs/xfs_rtcsumfile.h b/fs/xfs/libxfs/xfs_rtcsumfile.h
new file mode 100644
index 000000000000..a01121761faf
--- /dev/null
+++ b/fs/xfs/libxfs/xfs_rtcsumfile.h
@@ -0,0 +1,150 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _XFS_RTCSUMFILE_H
+#define _XFS_RTCSUMFILE_H
+
+#include "xfs_rtgroup.h"
+
+/*
+ * Maximum size of a checksum buffer for writes, used for the log reservation.
+ */
+#define XFS_RTCSUM_MAX_WRITE SZ_64K
+
+/*
+ * Size of the actual payload in the RT data checksum block. This excludes the
+ * self-describing metadata header.
+ */
+static inline unsigned int
+xfs_rtcsum_payload_size(
+ struct xfs_mount *mp)
+{
+ return mp->m_rtcsum_bsize - sizeof(struct xfs_rtbuf_blkinfo);
+}
+
+/* Convert data length in logical blocks to checksum length in bytes. */
+static inline unsigned int
+xfs_extlen_to_rtcsum_len(
+ struct xfs_mount *mp,
+ xfs_extlen_t nb)
+{
+ return nb << mp->m_rtcsum_shift;
+}
+
+/* Convert checksum length in bytes to data length in logical blocks. */
+static inline xfs_extlen_t
+xfs_rtcsum_len_to_extlen(
+ struct xfs_mount *mp,
+ unsigned int csum_len)
+{
+ return csum_len >> mp->m_rtcsum_shift;
+}
+
+/* Convert an rgbno to the csum byte position in the csum file. */
+static inline xfs_off_t
+xfs_rgbno_to_rtcsumpos(
+ struct xfs_mount *mp,
+ xfs_rgblock_t rgbno)
+{
+ return (xfs_off_t)rgbno << mp->m_rtcsum_shift;
+}
+
+/* Convert an rgbno to the csum block index in the csum file. */
+static inline unsigned int
+xfs_rgbno_to_rtcsumblock(
+ struct xfs_mount *mp,
+ xfs_rgblock_t rgbno)
+{
+ return div_u64(xfs_rgbno_to_rtcsumpos(mp, rgbno),
+ xfs_rtcsum_payload_size(mp));
+}
+
+/* Convert an rgbno to a the checksum offset within an rt csum block. */
+static inline unsigned int
+xfs_rgbno_to_rtcsumoff(
+ struct xfs_mount *mp,
+ xfs_rgblock_t rgbno)
+{
+ uint32_t off;
+
+ div_u64_rem(xfs_rgbno_to_rtcsumpos(mp, rgbno),
+ xfs_rtcsum_payload_size(mp), &off);
+ return sizeof(struct xfs_rtbuf_blkinfo) + off;
+}
+
+/* Convert an rtbno to a the checksum offset within an rt csum block. */
+static inline unsigned int
+xfs_rtb_to_rtcsumoff(
+ struct xfs_mount *mp,
+ xfs_rtblock_t fsbno)
+{
+ return xfs_rgbno_to_rtcsumoff(mp, xfs_rtb_to_rgbno(mp, fsbno));
+}
+
+int xfs_rtcsum_bmap(struct xfs_rtgroup *rtg, xfs_rgblock_t rgbno,
+ xfs_daddr_t *daddr);
+xfs_off_t xfs_rtcsum_file_size(struct xfs_rtgroup *rtg);
+int xfs_rtcsum_alloc_blocks(struct xfs_rtgroup *rtg);
+int xfs_rtcsum_create(struct xfs_rtgroup *rtg, struct xfs_inode *ip,
+ struct xfs_trans *tp, bool init);
+
+static inline unsigned int
+xfs_rtcsum_bufs_per_rtg(
+ struct xfs_rtgroup *rtg)
+{
+ struct xfs_mount *mp = rtg_mount(rtg);
+
+ if (xfs_has_rtcsum(mp))
+ return div_u64(xfs_rtcsum_file_size(rtg), mp->m_rtcsum_bsize);
+ return 0;
+}
+
+static inline xfs_filblks_t
+xfs_rtcsum_max_len(
+ struct xfs_mount *mp,
+ xfs_fsblock_t fsbno)
+{
+ return xfs_rtcsum_len_to_extlen(mp,
+ mp->m_rtcsum_bsize - xfs_rtb_to_rtcsumoff(mp, fsbno));
+}
+
+union xfs_csum {
+ uint32_t crc32c;
+ uint64_t crc64;
+};
+
+static __always_inline void
+xfs_csum_seed(
+ struct xfs_mount *mp,
+ union xfs_csum *csum)
+{
+ if (mp->m_sb.sb_rtcsum_type == XFS_CSUM_TYPE_CRC32C)
+ csum->crc32c = XFS_CRC_SEED;
+ else
+ csum->crc64 = XFS_CRC64_SEED;
+}
+
+static __always_inline void
+xfs_csum_gen(
+ struct xfs_mount *mp,
+ void *data,
+ unsigned int len,
+ union xfs_csum *csum)
+{
+ if (mp->m_sb.sb_rtcsum_type == XFS_CSUM_TYPE_CRC32C)
+ csum->crc32c = crc32c(csum->crc32c, data, len);
+ else
+ csum->crc64 = crc64_nvme(csum->crc64, data, len);
+}
+
+static __always_inline void
+xfs_csum_finalize(
+ struct xfs_mount *mp,
+ union xfs_disk_csum *to,
+ union xfs_csum *csum)
+{
+ if (mp->m_sb.sb_rtcsum_type == XFS_CSUM_TYPE_CRC32C)
+ to->crc32c = cpu_to_le32(~csum->crc32c);
+ else
+ to->crc64 = cpu_to_le64(csum->crc64);
+}
+
+#endif /* _XFS_RTCSUMFILE_H */
diff --git a/fs/xfs/libxfs/xfs_rtgroup.c b/fs/xfs/libxfs/xfs_rtgroup.c
index 91961fb85aff..4864adf6f59e 100644
--- a/fs/xfs/libxfs/xfs_rtgroup.c
+++ b/fs/xfs/libxfs/xfs_rtgroup.c
@@ -34,6 +34,7 @@
#include "xfs_metadir.h"
#include "xfs_rtrmap_btree.h"
#include "xfs_rtrefcount_btree.h"
+#include "xfs_rtcsumfile.h"
/* Find the first usable fsblock in this rtgroup. */
static inline uint32_t
@@ -399,6 +400,15 @@ static const struct xfs_rtginode_ops xfs_rtginode_ops[XFS_RTGI_MAX] = {
.enabled = xfs_has_reflink,
.create = xfs_rtrefcountbt_create,
},
+ [XFS_RTGI_CSUM] = {
+ .name = "csum",
+ .metafile_type = XFS_METAFILE_RTCSUM,
+ .sick = XFS_SICK_RG_CSUM,
+ .fmt_mask = (1U << XFS_DINODE_FMT_EXTENTS) |
+ (1U << XFS_DINODE_FMT_BTREE),
+ .enabled = xfs_has_rtcsum,
+ .create = xfs_rtcsum_create,
+ },
};
/* Return the shortname of this rtgroup inode. */
diff --git a/fs/xfs/libxfs/xfs_rtgroup.h b/fs/xfs/libxfs/xfs_rtgroup.h
index f26e324f0de3..5cca0f4dd25a 100644
--- a/fs/xfs/libxfs/xfs_rtgroup.h
+++ b/fs/xfs/libxfs/xfs_rtgroup.h
@@ -16,6 +16,7 @@ enum xfs_rtg_inodes {
XFS_RTGI_SUMMARY, /* allocation summary */
XFS_RTGI_RMAP, /* rmap btree inode */
XFS_RTGI_REFCOUNT, /* refcount btree inode */
+ XFS_RTGI_CSUM, /* data checksum inode */
XFS_RTGI_MAX,
};
@@ -109,6 +110,11 @@ static inline struct xfs_inode *rtg_refcount(const struct xfs_rtgroup *rtg)
return rtg->rtg_inodes[XFS_RTGI_REFCOUNT];
}
+static inline struct xfs_inode *rtg_csum(const struct xfs_rtgroup *rtg)
+{
+ return rtg->rtg_inodes[XFS_RTGI_CSUM];
+}
+
/* Passive rtgroup references */
static inline struct xfs_rtgroup *
xfs_rtgroup_get(
diff --git a/fs/xfs/libxfs/xfs_shared.h b/fs/xfs/libxfs/xfs_shared.h
index b1e0d9bc1f7d..8a60f2c56fe7 100644
--- a/fs/xfs/libxfs/xfs_shared.h
+++ b/fs/xfs/libxfs/xfs_shared.h
@@ -40,6 +40,7 @@ extern const struct xfs_buf_ops xfs_refcountbt_buf_ops;
extern const struct xfs_buf_ops xfs_rmapbt_buf_ops;
extern const struct xfs_buf_ops xfs_rtbitmap_buf_ops;
extern const struct xfs_buf_ops xfs_rtsummary_buf_ops;
+extern const struct xfs_buf_ops xfs_rtcsum_buf_ops;
extern const struct xfs_buf_ops xfs_rtbuf_ops;
extern const struct xfs_buf_ops xfs_rtsb_buf_ops;
extern const struct xfs_buf_ops xfs_rtrefcountbt_buf_ops;
diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c
index 57929f115055..28fd1f358ba2 100644
--- a/fs/xfs/xfs_buf_item_recover.c
+++ b/fs/xfs/xfs_buf_item_recover.c
@@ -414,6 +414,13 @@ xlog_recover_validate_buf_type(
}
bp->b_ops = xfs_rtblock_ops(mp, XFS_RTGI_SUMMARY);
break;
+ case XFS_BLFT_RTCSUM_BUF:
+ if (xfs_has_rtgroups(mp) && magic32 != XFS_RTCSUM_MAGIC) {
+ warnmsg = "Bad rtcsum magic!";
+ break;
+ }
+ bp->b_ops = &xfs_rtcsum_buf_ops;
+ break;
#endif /* CONFIG_XFS_RT */
default:
xfs_warn(mp, "Unknown buffer type %d!",
@@ -869,7 +876,8 @@ xlog_recover_get_buf_lsn(
magic32 = be32_to_cpu(*(__be32 *)blk);
switch (magic32) {
case XFS_RTSUMMARY_MAGIC:
- case XFS_RTBITMAP_MAGIC: {
+ case XFS_RTBITMAP_MAGIC:
+ case XFS_RTCSUM_MAGIC: {
struct xfs_rtbuf_blkinfo *hdr = blk;
lsn = be64_to_cpu(hdr->rt_lsn);
diff --git a/fs/xfs/xfs_health.c b/fs/xfs/xfs_health.c
index 239b843e83d4..dec452b5b15f 100644
--- a/fs/xfs/xfs_health.c
+++ b/fs/xfs/xfs_health.c
@@ -571,6 +571,7 @@ static const struct ioctl_sick_map rtgroup_map[] = {
{ XFS_SICK_RG_SUMMARY, XFS_RTGROUP_GEOM_SICK_SUMMARY },
{ XFS_SICK_RG_RMAPBT, XFS_RTGROUP_GEOM_SICK_RMAPBT },
{ XFS_SICK_RG_REFCNTBT, XFS_RTGROUP_GEOM_SICK_REFCNTBT },
+ { XFS_SICK_RG_CSUM, XFS_RTGROUP_GEOM_SICK_CSUM },
};
/* Fill out rtgroup geometry health info. */
diff --git a/fs/xfs/xfs_platform.h b/fs/xfs/xfs_platform.h
index 981488778ccc..2c52b004e82b 100644
--- a/fs/xfs/xfs_platform.h
+++ b/fs/xfs/xfs_platform.h
@@ -16,6 +16,7 @@
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/crc32c.h>
+#include <linux/crc64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/file.h>
diff --git a/fs/xfs/xfs_rtalloc.c b/fs/xfs/xfs_rtalloc.c
index 78a1c066c7eb..e2b6113772b6 100644
--- a/fs/xfs/xfs_rtalloc.c
+++ b/fs/xfs/xfs_rtalloc.c
@@ -32,6 +32,7 @@
#include "xfs_error.h"
#include "xfs_trace.h"
#include "xfs_rtrefcount_btree.h"
+#include "xfs_rtcsumfile.h"
#include "xfs_reflink.h"
#include "xfs_zone_alloc.h"
@@ -898,6 +899,12 @@ xfs_growfs_rt_zoned(
xfs_rtbxlen_t freed_rtx;
int error;
+ if (xfs_has_rtcsum(mp)) {
+ error = xfs_rtcsum_alloc_blocks(rtg);
+ if (error)
+ return error;
+ }
+
/*
* Calculate new sb and mount fields for this round. Also ensure the
* rtg_extents value is uptodate as the rtbitmap code relies on it.
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 12/25] xfs: calculate the log reservation for logging data checksum buffers
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (10 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 11/25] xfs: add support for per-RTG csum files Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 13/25] xfs: core RT data checksum support Christoph Hellwig
` (12 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Darrick J. Wong
The data checksum is logged in its own transaction, which only logs the
checksum buffers. While the maximum size of a checksummed data write
is the same as that of a single checksum buffer, the Zone Append based
zoned write path can't guarantee alignment, so a reservation for an
extra slack buffer is needed.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_trans_resv.c | 23 +++++++++++++++++++++++
fs/xfs/libxfs/xfs_trans_resv.h | 4 ++++
2 files changed, 27 insertions(+)
diff --git a/fs/xfs/libxfs/xfs_trans_resv.c b/fs/xfs/libxfs/xfs_trans_resv.c
index b09c88aec185..25f7581ea90c 100644
--- a/fs/xfs/libxfs/xfs_trans_resv.c
+++ b/fs/xfs/libxfs/xfs_trans_resv.c
@@ -11,6 +11,7 @@
#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
#include "xfs_mount.h"
+#include "xfs_rtcsumfile.h"
#include "xfs_da_format.h"
#include "xfs_da_btree.h"
#include "xfs_inode.h"
@@ -1235,6 +1236,24 @@ xfs_calc_qm_dqalloc_reservation_minlogsize(
return xfs_calc_qm_dqalloc_reservation(mp, true);
}
+/*
+ * Log data checksums for a write.
+ *
+ * Writes do not have to be aligned to checksum file blocks and thus can
+ * spill into an extra buffer that needs to be accounted for.
+ */
+unsigned int
+xfs_calc_csum_reservation(
+ struct xfs_mount *mp,
+ unsigned int csum_len)
+{
+ unsigned int payload_size = xfs_rtcsum_payload_size(mp);
+
+ return xfs_calc_buf_res(
+ howmany(csum_len + payload_size, payload_size),
+ mp->m_rtcsum_bsize);
+}
+
/*
* Syncing the incore super block changes to disk.
* the super block to reflect the changes: sector size
@@ -1355,6 +1374,10 @@ xfs_trans_resv_calc(
xfs_calc_namespace_reservations(mp, resp);
+ resp->tr_csum.tr_logres =
+ xfs_calc_csum_reservation(mp, XFS_RTCSUM_MAX_WRITE);
+ resp->tr_csum.tr_logcount = XFS_DEFAULT_LOG_COUNT;
+
/*
* The following transactions are logged in logical format with
* a default log count.
diff --git a/fs/xfs/libxfs/xfs_trans_resv.h b/fs/xfs/libxfs/xfs_trans_resv.h
index 336279e0fc61..fbd978a91cc8 100644
--- a/fs/xfs/libxfs/xfs_trans_resv.h
+++ b/fs/xfs/libxfs/xfs_trans_resv.h
@@ -49,6 +49,7 @@ struct xfs_trans_resv {
struct xfs_trans_res tr_sb; /* modify superblock */
struct xfs_trans_res tr_fsyncts; /* update timestamps on fsync */
struct xfs_trans_res tr_atomic_ioend; /* untorn write completion */
+ struct xfs_trans_res tr_csum; /* data checksums in metafile */
};
/* shorthand way of accessing reservation structure */
@@ -121,6 +122,9 @@ unsigned int xfs_calc_itruncate_reservation_minlogsize(struct xfs_mount *mp);
unsigned int xfs_calc_write_reservation_minlogsize(struct xfs_mount *mp);
unsigned int xfs_calc_qm_dqalloc_reservation_minlogsize(struct xfs_mount *mp);
+unsigned int xfs_calc_csum_reservation(struct xfs_mount *mp,
+ unsigned int csum_len);
+
xfs_extlen_t xfs_calc_max_atomic_write_fsblocks(struct xfs_mount *mp);
xfs_extlen_t xfs_calc_atomic_write_log_geometry(struct xfs_mount *mp,
xfs_extlen_t blockcount, unsigned int *new_logres);
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 13/25] xfs: core RT data checksum support
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (11 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 12/25] xfs: calculate the log reservation for logging data checksum buffers Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 14/25] xfs: data checksums require stable writes Christoph Hellwig
` (11 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Support reading and writing of data checksum buffers, and generating
and verifying the checksum.
All data checksum buffers for an open zone are pre-allocated at zone open
time, so that we never have to read in a partially written buffer as part
of a data write, which would otherwise impose very expensive seeks and
stall writes.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/Kconfig | 1 +
fs/xfs/Makefile | 1 +
fs/xfs/xfs_inode.h | 5 +
fs/xfs/xfs_rtcsum.c | 314 ++++++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_rtcsum.h | 23 +++
fs/xfs/xfs_super.c | 14 ++
fs/xfs/xfs_sysfs.c | 2 +
fs/xfs/xfs_zone_alloc.c | 32 +++-
fs/xfs/xfs_zone_priv.h | 8 +
9 files changed, 396 insertions(+), 4 deletions(-)
create mode 100644 fs/xfs/xfs_rtcsum.c
create mode 100644 fs/xfs/xfs_rtcsum.h
diff --git a/fs/xfs/Kconfig b/fs/xfs/Kconfig
index b99da294e9a3..424a04b507a6 100644
--- a/fs/xfs/Kconfig
+++ b/fs/xfs/Kconfig
@@ -106,6 +106,7 @@ config XFS_RT
bool "XFS Realtime subvolume support"
depends on XFS_FS
default BLK_DEV_ZONED
+ select CRC64
help
If you say Y here you will be able to mount and use XFS filesystems
which contain a realtime subvolume. The realtime subvolume is a
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 79ea4136fbba..0d57bf0701ec 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -142,6 +142,7 @@ xfs-$(CONFIG_XFS_QUOTA) += xfs_dquot.o \
# xfs_rtbitmap is shared with libxfs
xfs-$(CONFIG_XFS_RT) += xfs_rtalloc.o \
+ xfs_rtcsum.o \
xfs_zone_alloc.o \
xfs_zone_gc.o \
xfs_zone_info.o \
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index b16eee99b567..56d5a670ba75 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -385,6 +385,11 @@ static inline bool xfs_inode_can_sw_atomic_write(const struct xfs_inode *ip)
return xfs_can_sw_atomic_write(ip->i_mount);
}
+static inline bool xfs_is_rtcsum_inode(const struct xfs_inode *ip)
+{
+ return xfs_has_rtcsum(ip->i_mount) && XFS_IS_REALTIME_INODE(ip);
+}
+
/*
* In-core inode flags.
*/
diff --git a/fs/xfs/xfs_rtcsum.c b/fs/xfs/xfs_rtcsum.c
new file mode 100644
index 000000000000..64fbf6886f8f
--- /dev/null
+++ b/fs/xfs/xfs_rtcsum.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Christoph Hellwig.
+ */
+#include "xfs_platform.h"
+#include "xfs_fs.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_shared.h"
+#include "xfs_trans_resv.h"
+#include "xfs_bit.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_bmap.h"
+#include "xfs_rtgroup.h"
+#include "xfs_rtbitmap.h"
+#include "xfs_bmap_btree.h"
+#include "xfs_trans.h"
+#include "xfs_buf_item.h"
+#include "xfs_trans_space.h"
+#include "xfs_error.h"
+#include "xfs_health.h"
+#include "xfs_rtcsum.h"
+#include "xfs_zone_priv.h"
+#include <linux/iomap.h>
+
+static_assert(IOMAP_CSUM_MAX_SIZE <= XFS_RTCSUM_MAX_WRITE);
+
+static const char *xfs_data_csum_names[XFS_CSUM_TYPE_MAX] = {
+ [XFS_CSUM_TYPE_CRC32C] = "crc32c",
+ [XFS_CSUM_TYPE_CRC64] = "crc64",
+};
+
+int
+xfs_csum_verify(
+ struct xfs_mount *mp,
+ struct bio *bio,
+ struct bvec_iter *iter,
+ void *csum_buf,
+ xfs_fsblock_t bno,
+ bool verbose)
+{
+ unsigned int bsize = mp->m_sb.sb_blocksize;
+ unsigned int csum_size = 1u << mp->m_rtcsum_shift;
+ unsigned int offset = 0;
+ union xfs_csum csum;
+ union xfs_disk_csum dsum;
+
+ do {
+ struct bio_vec bv = mp_bvec_iter_bvec(bio->bi_io_vec, *iter);
+
+ if (offset == 0)
+ xfs_csum_seed(mp, &csum);
+ bv.bv_len = min(bv.bv_len, bsize - offset);
+ xfs_csum_gen(mp, bvec_virt(&bv), bv.bv_len, &csum);
+ offset += bv.bv_len;
+ if (offset == bsize) {
+ xfs_csum_finalize(mp, &dsum, &csum);
+ if (unlikely(memcmp(&dsum, csum_buf, csum_size) != 0))
+ goto mismatch;
+ bno++;
+ csum_buf += csum_size;
+ offset = 0;
+ }
+ bio_advance_iter_single(bio, iter, bv.bv_len);
+ } while (iter->bi_size);
+
+ return 0;
+
+mismatch:
+ if (verbose) {
+ xfs_warn_ratelimited(mp,
+"data csum mismatch for rtblock 0x%llx: 0x%*phN (expected 0x%*phN)",
+ bno, csum_size, &dsum, csum_size, csum_buf);
+ }
+ return -EIO;
+}
+
+void
+xfs_csum_generate(
+ struct xfs_mount *mp,
+ struct bio *bio,
+ void *csum_buf)
+{
+ struct bvec_iter iter = bio->bi_iter;
+ unsigned int bsize = mp->m_sb.sb_blocksize;
+ unsigned int csum_size = 1u << mp->m_rtcsum_shift;
+ unsigned int offset = 0;
+ union xfs_csum csum;
+
+ do {
+ struct bio_vec bv = mp_bvec_iter_bvec(bio->bi_io_vec, iter);
+
+ if (offset == 0)
+ xfs_csum_seed(mp, &csum);
+ bv.bv_len = min(bv.bv_len, bsize - offset);
+ xfs_csum_gen(mp, bvec_virt(&bv), bv.bv_len, &csum);
+ offset += bv.bv_len;
+ if (offset == bsize) {
+ xfs_csum_finalize(mp, csum_buf, &csum);
+ csum_buf += csum_size;
+ offset = 0;
+ }
+ bio_advance_iter_single(bio, &iter, bv.bv_len);
+ } while (iter.bi_size);
+}
+
+/*
+ * Read the checksum buffer for @fsbno and return it unlocked.
+ *
+ * We don't need to lock read access to the buffer because checksums will not
+ * change until the @rtg is reset.
+ */
+int
+xfs_rtcsum_read_async(
+ struct xfs_mount *mp,
+ xfs_rtblock_t fsbno,
+ struct xfs_buf **bpp)
+{
+ xfs_daddr_t csum_daddr;
+ struct xfs_rtgroup *rtg;
+ int error;
+
+ rtg = xfs_rtgroup_get(mp, xfs_rtb_to_rgno(mp, fsbno));
+ if (!rtg)
+ return -EFSCORRUPTED;
+ error = xfs_rtcsum_bmap(rtg, xfs_rtb_to_rgbno(mp, fsbno), &csum_daddr);
+ if (!error)
+ error = xfs_buf_read_async(mp->m_ddev_targp, csum_daddr,
+ BTOBB(mp->m_rtcsum_bsize), &xfs_rtcsum_buf_ops,
+ bpp);
+ xfs_rtgroup_put(rtg);
+ return error;
+}
+
+/*
+ * When opening a zone for writing, do a speculative buf_get for each csum
+ * buffer. This ensures we usually have a buffer in-memory when we actually
+ * start writing to it.
+ *
+ * Without this we'd have to read the buffer from disk, as we don't know if
+ * anyone has already written to it by the time we get to the buffer due to
+ * completion reordering.
+ *
+ * When reopening a partially written zone at mount time, just read ahead
+ * the entire csums for the zone.
+ */
+int
+xfs_rtcsum_open_zone(
+ struct xfs_open_zone *oz)
+{
+ struct xfs_rtgroup *rtg = oz->oz_rtg;
+ struct xfs_mount *mp = rtg_mount(rtg);
+ xfs_rgblock_t rgbno = 0;
+ int i, error;
+
+ for (i = 0; i < oz->oz_nr_csum_bufs; i++) {
+ xfs_daddr_t csum_daddr;
+ struct xfs_buf *bp;
+
+ error = xfs_rtcsum_bmap(rtg, rgbno, &csum_daddr);
+ if (error)
+ goto out_error;
+
+ if (oz->oz_allocated) {
+ error = xfs_buf_read_async(mp->m_ddev_targp, csum_daddr,
+ BTOBB(mp->m_rtcsum_bsize),
+ &xfs_rtcsum_buf_ops, &bp);
+ if (error)
+ goto out_error;
+ } else {
+ error = xfs_buf_get(mp->m_ddev_targp, csum_daddr,
+ BTOBB(mp->m_rtcsum_bsize), &bp);
+ if (error)
+ goto out_error;
+ xfs_buf_set_uptodate(bp);
+ xfs_rtfile_initialize_buf(rtg, XFS_RTGI_CSUM, bp, NULL);
+ xfs_buf_unlock(bp);
+ }
+ if (error)
+ goto out_error;
+ oz->oz_csum_bufs[i] = bp;
+ rgbno += (xfs_rtcsum_payload_size(mp) /
+ (1u << mp->m_rtcsum_shift));
+ }
+
+ return 0;
+
+out_error:
+ while (--i >= 0) {
+ xfs_buf_rele(oz->oz_csum_bufs[i]);
+ oz->oz_csum_bufs[i] = NULL;
+ }
+ return error;
+}
+
+void
+xfs_rtcsum_free_zone(
+ struct xfs_open_zone *oz)
+{
+ unsigned int i;
+
+ for (i = 0; i < oz->oz_nr_csum_bufs; i++)
+ xfs_buf_rele(oz->oz_csum_bufs[i]);
+}
+
+static int
+xfs_rtcsum_log_one(
+ struct xfs_open_zone *oz,
+ xfs_rgblock_t rgbno,
+ const void *csum,
+ xfs_extlen_t nfsb,
+ struct xfs_trans *tp,
+ xfs_extlen_t *nr)
+{
+ struct xfs_rtgroup *rtg = oz->oz_rtg;
+ struct xfs_mount *mp = rtg_mount(rtg);
+ unsigned int boff = xfs_rgbno_to_rtcsumoff(mp, rgbno);
+ struct xfs_buf *bp;
+ unsigned int len;
+ int error;
+
+ bp = oz->oz_csum_bufs[xfs_rgbno_to_rtcsumblock(mp, rgbno)];
+ error = xfs_buf_read_async_wait(bp);
+ if (error) {
+ if (xfs_metadata_is_sick(error))
+ xfs_rtginode_mark_sick(rtg, XFS_RTGI_CSUM);
+ return error;
+ }
+
+ xfs_buf_hold(bp);
+ xfs_buf_lock(bp);
+ xfs_trans_bjoin(tp, bp);
+ xfs_trans_buf_set_type(tp, bp, XFS_BLFT_RTCSUM_BUF);
+
+ *nr = min(nfsb,
+ xfs_rtcsum_len_to_extlen(mp, mp->m_rtcsum_bsize - boff));
+ len = xfs_extlen_to_rtcsum_len(mp, *nr);
+ memcpy(bp->b_addr + boff, csum, len);
+
+ xfs_trans_log_buf(tp, bp, boff, boff + len - 1);
+ return 0;
+}
+
+int
+xfs_rtcsum_log(
+ struct xfs_open_zone *oz,
+ xfs_daddr_t daddr,
+ unsigned int data_len,
+ const void *csum)
+{
+ struct xfs_mount *mp = rtg_mount(oz->oz_rtg);
+ xfs_rgblock_t rgbno = xfs_daddr_to_rgbno(mp, daddr);
+ xfs_extlen_t nfsb = XFS_B_TO_FSB(mp, data_len);
+ struct xfs_trans_res tres = M_RES(mp)->tr_csum;
+ struct xfs_trans *tp;
+ int error;
+
+ tres.tr_logres = xfs_calc_csum_reservation(mp,
+ xfs_extlen_to_rtcsum_len(mp, nfsb));
+ ASSERT(tres.tr_logres <= M_RES(mp)->tr_csum.tr_logres);
+
+ error = xfs_trans_alloc(mp, &tres, 0, 0, 0, &tp);
+ if (error)
+ return error;
+
+ do {
+ xfs_extlen_t nr;
+
+ error = xfs_rtcsum_log_one(oz, rgbno, csum, nfsb, tp, &nr);
+ if (error)
+ goto out_trans_cancel;
+ rgbno += nr;
+ nfsb -= nr;
+ csum += xfs_extlen_to_rtcsum_len(mp, nr);
+ } while (nfsb);
+
+ return xfs_trans_commit(tp);
+
+out_trans_cancel:
+ xfs_trans_cancel(tp);
+ return error;
+}
+
+int
+xfs_rtcsum_mount(
+ struct xfs_mount *mp)
+{
+ /*
+ * Supporting highmem would require less efficient loops for the
+ * checksumming. We could do that with a separate code path, but
+ * there isn't much of a point in the extra test coverage required just
+ * for obsolete 32-bit platforms.
+ */
+ if (IS_ENABLED(CONFIG_HIGHMEM)) {
+ xfs_alert(mp,
+"CONFIG_HIGHMEM not supported with data checksums.");
+ return -EINVAL;
+ }
+
+ /*
+ * We can't support data checksums on DAX because the direct access
+ * doesn't allow computing and verifying the checksums.
+ */
+ if (xfs_has_dax_always(mp)) {
+ xfs_warn(mp, "dax=always not supported with data checksums");
+ return -EINVAL;
+ }
+ mp->m_features |= XFS_FEAT_DAX_NEVER;
+
+ xfs_info(mp, "using %s for RT device data checksums",
+ xfs_data_csum_names[mp->m_sb.sb_rtcsum_type]);
+ return 0;
+}
diff --git a/fs/xfs/xfs_rtcsum.h b/fs/xfs/xfs_rtcsum.h
new file mode 100644
index 000000000000..bd16f5a99451
--- /dev/null
+++ b/fs/xfs/xfs_rtcsum.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _XFS_RTCSUM_H
+#define _XFS_RTCSUM_H
+
+#include "xfs_rtcsumfile.h"
+
+void xfs_csum_generate(struct xfs_mount *mp, struct bio *bio,
+ void *csum_buf);
+int xfs_csum_verify(struct xfs_mount *mp, struct bio *bio,
+ struct bvec_iter *iter, void *csum_buf,
+ xfs_fsblock_t bno, bool verbose);
+
+int xfs_rtcsum_read_async(struct xfs_mount *mp, xfs_rtblock_t fsbno,
+ struct xfs_buf **bpp);
+int xfs_rtcsum_log(struct xfs_open_zone *oz, xfs_daddr_t daddr,
+ unsigned int data_len, const void *csum);
+
+int xfs_rtcsum_open_zone(struct xfs_open_zone *oz);
+void xfs_rtcsum_free_zone(struct xfs_open_zone *oz);
+
+int xfs_rtcsum_mount(struct xfs_mount *mp);
+
+#endif /* _XFS_RTCSUM_H */
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index 5a06132aa384..fc99167938e5 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -50,6 +50,7 @@
#include "xfs_rtalloc.h"
#include "xfs_zone_alloc.h"
#include "xfs_healthmon.h"
+#include "xfs_rtcsum.h"
#include "scrub/stats.h"
#include "scrub/rcbag_btree.h"
@@ -1953,6 +1954,19 @@ xfs_fs_fill_super(
}
}
+ if (xfs_has_rtcsum(mp)) {
+ if (!IS_ENABLED(CONFIG_XFS_RT) || !xfs_has_zoned(mp)) {
+ xfs_alert(mp,
+ "zoned allocator required for RT data checksums");
+ error = -EINVAL;
+ goto out_filestream_unmount;
+ }
+
+ error = xfs_rtcsum_mount(mp);
+ if (error)
+ goto out_filestream_unmount;
+ }
+
if (xfs_has_reflink(mp)) {
if (xfs_has_realtime(mp) &&
!xfs_reflink_supports_rextsize(mp, mp->m_sb.sb_rextsize)) {
diff --git a/fs/xfs/xfs_sysfs.c b/fs/xfs/xfs_sysfs.c
index 4a6a8c92c3a1..4a451a414312 100644
--- a/fs/xfs/xfs_sysfs.c
+++ b/fs/xfs/xfs_sysfs.c
@@ -401,6 +401,8 @@ static bool
xfs_has_read_bounce(
struct xfs_mount *mp)
{
+ if (xfs_has_rtcsum(mp))
+ return true;
if (bdev_has_integrity_csum(mp->m_ddev_targp->bt_bdev))
return true;
if (mp->m_rtdev_targp &&
diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c
index 509420c63e49..0dbc08c58817 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 "xfs_rtcsum.h"
#include <linux/bio-integrity.h>
static void
@@ -42,8 +43,10 @@ void
xfs_open_zone_put(
struct xfs_open_zone *oz)
{
- if (atomic_dec_and_test(&oz->oz_ref))
+ if (atomic_dec_and_test(&oz->oz_ref)) {
+ xfs_rtcsum_free_zone(oz);
call_rcu(&oz->oz_rcu, xfs_open_zone_free_rcu);
+ }
}
static inline uint32_t
@@ -416,9 +419,12 @@ xfs_init_open_zone(
enum rw_hint write_hint,
bool is_gc)
{
+ unsigned int nr_csum_bufs = xfs_rtcsum_bufs_per_rtg(rtg);
struct xfs_open_zone *oz;
+ int error;
- oz = kzalloc_obj(*oz, GFP_NOFS | __GFP_NOFAIL);
+ oz = kzalloc_flex(*oz, oz_csum_bufs, nr_csum_bufs,
+ GFP_NOFS | __GFP_NOFAIL);
spin_lock_init(&oz->oz_alloc_lock);
atomic_set(&oz->oz_ref, 1);
oz->oz_rtg = rtg;
@@ -426,6 +432,14 @@ xfs_init_open_zone(
oz->oz_written = write_pointer;
oz->oz_write_hint = write_hint;
oz->oz_is_gc = is_gc;
+ oz->oz_nr_csum_bufs = nr_csum_bufs;
+ if (nr_csum_bufs) {
+ error = xfs_rtcsum_open_zone(oz);
+ if (error) {
+ kfree(oz);
+ return ERR_PTR(error);
+ }
+ }
/*
* All dereferences of rtg->rtg_open_zone hold the ILOCK for the rmap
@@ -469,6 +483,7 @@ xfs_open_zone(
{
struct xfs_zone_info *zi = mp->m_zone_info;
XA_STATE (xas, &mp->m_groups[XG_TYPE_RTG].xa, 0);
+ struct xfs_open_zone *oz;
struct xfs_group *xg;
if (!xfs_claim_free_zone(zi, is_gc))
@@ -500,7 +515,14 @@ xfs_open_zone(
xas_unlock(&xas);
set_current_state(TASK_RUNNING);
- return xfs_init_open_zone(to_rtg(xg), 0, write_hint, is_gc);
+ oz = xfs_init_open_zone(to_rtg(xg), 0, write_hint, is_gc);
+ if (IS_ERR(oz)) {
+ xfs_rtgroup_rele(to_rtg(xg));
+ xfs_force_shutdown(mp, SHUTDOWN_CORRUPT_ONDISK);
+ return NULL;
+ }
+
+ return oz;
}
static struct xfs_open_zone *
@@ -1174,9 +1196,11 @@ xfs_init_zone(
/* zone is open */
struct xfs_open_zone *oz;
- atomic_inc(&rtg_group(rtg)->xg_active_ref);
oz = xfs_init_open_zone(rtg, write_pointer, WRITE_LIFE_NOT_SET,
false);
+ if (IS_ERR(oz))
+ return PTR_ERR(oz);
+ atomic_inc(&rtg_group(rtg)->xg_active_ref);
list_add_tail(&oz->oz_entry, &zi->zi_open_zones);
zi->zi_nr_open_zones++;
diff --git a/fs/xfs/xfs_zone_priv.h b/fs/xfs/xfs_zone_priv.h
index fcb57506d8e6..322a031e6ba0 100644
--- a/fs/xfs/xfs_zone_priv.h
+++ b/fs/xfs/xfs_zone_priv.h
@@ -42,6 +42,14 @@ struct xfs_open_zone {
struct xfs_rtgroup *oz_rtg;
struct rcu_head oz_rcu;
+
+ /*
+ * Checksum buffers. All checksum buffers for an open zone are pinned
+ * into memory so that we never need to read them in to log checksums
+ * for a write.
+ */
+ unsigned int oz_nr_csum_bufs;
+ struct xfs_buf *oz_csum_bufs[] __counted_by(oz_nr_csum_bufs);
};
/*
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 14/25] xfs: data checksums require stable writes
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (12 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 13/25] xfs: core RT data checksum support Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 15/25] xfs: require file system block size alignment when using data checksums Christoph Hellwig
` (10 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Darrick J. Wong
Call mapping_set_stable_writes based on the data checksum flag
so that the page cache doesn't change data in-flight as that could
corrupt the checksum.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/xfs_inode.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index 56d5a670ba75..169208e1c7da 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -628,7 +628,8 @@ int xfs_break_layouts(struct inode *inode, uint *iolock,
static inline void xfs_update_stable_writes(struct xfs_inode *ip)
{
- if (bdev_stable_writes(xfs_inode_buftarg(ip)->bt_bdev))
+ if (xfs_is_rtcsum_inode(ip) ||
+ bdev_stable_writes(xfs_inode_buftarg(ip)->bt_bdev))
mapping_set_stable_writes(VFS_I(ip)->i_mapping);
else
mapping_clear_stable_writes(VFS_I(ip)->i_mapping);
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 15/25] xfs: require file system block size alignment when using data checksums
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (13 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 14/25] xfs: data checksums require stable writes Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 16/25] xfs: add support for reading with " Christoph Hellwig
` (9 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
The checksums cover a whole block, so we can't read or update parts of a
block. Report the requirement and enforce it for direct I/O reads.
Direct I/O writes already require file system block size alignment when
using the zoned allocator, and buffered I/O never does sub-block I/O.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_file.c | 14 +++++++++++++-
fs/xfs/xfs_inode.h | 14 ++++++++++++++
fs/xfs/xfs_ioend.c | 4 ++--
fs/xfs/xfs_iops.c | 8 +++++++-
4 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 396aca8c66a9..97b0a976058a 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -29,6 +29,7 @@
#include "xfs_zone_alloc.h"
#include "xfs_error.h"
#include "xfs_errortag.h"
+#include "xfs_rtcsum.h"
#include <linux/dax.h>
#include <linux/falloc.h>
@@ -208,9 +209,20 @@ xfs_file_dio_read(
if (ret)
return ret;
if (mapping_stable_writes(iocb->ki_filp->f_mapping)) {
+ unsigned int dio_flags = 0;
+
+ /*
+ * Each checksums covers a whole file system block, and thus
+ * sub-fsblock reads are not supported for file systems using
+ * data checksums.
+ */
+ if (xfs_is_rtcsum_inode(ip))
+ dio_flags |= IOMAP_DIO_FSBLOCK_ALIGNED;
ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops,
- &xfs_dio_read_bounce_ops, 0, NULL, 0);
+ &xfs_dio_read_bounce_ops, dio_flags, NULL, 0);
} else {
+ ASSERT(!xfs_is_rtcsum_inode(ip));
+
ret = iomap_dio_read_simple(iocb, to, xfs_read_iomap_begin);
if (ret == -ENOTBLK)
ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops, NULL,
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index 169208e1c7da..7b75483bb43d 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -390,6 +390,20 @@ static inline bool xfs_is_rtcsum_inode(const struct xfs_inode *ip)
return xfs_has_rtcsum(ip->i_mount) && XFS_IS_REALTIME_INODE(ip);
}
+/*
+ * Each checksums covers a whole file system block, and thus sub-fsblock
+ * reads are not supported for file systems using data checksums.
+ */
+static inline unsigned int
+xfs_min_diosize(
+ struct xfs_inode *ip,
+ struct block_device *bdev)
+{
+ if (xfs_is_rtcsum_inode(ip))
+ return i_blocksize(VFS_I(ip));
+ return bdev_logical_block_size(bdev);
+}
+
/*
* In-core inode flags.
*/
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index 7f4a4a95afe5..05ab91dd4343 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -86,7 +86,7 @@ xfs_read_bounce_and_resubmit(
.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),
+ iomap_bounce_read(ioend, xfs_min_diosize(ip, bio->bi_bdev),
xfs_bounce_submit_ioend);
memalloc_nofs_restore(nofs_flag);
}
@@ -134,7 +134,7 @@ xfs_ioend_submit_read(
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),
+ iomap_bounce_read(ioend, xfs_min_diosize(ip, bio->bi_bdev),
xfs_bounce_submit_ioend);
return;
}
diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c
index d307ec97fac8..11952caad5e6 100644
--- a/fs/xfs/xfs_iops.c
+++ b/fs/xfs/xfs_iops.c
@@ -581,6 +581,13 @@ xfs_report_dioalign(
stat->result_mask |= STATX_DIOALIGN | STATX_DIO_READ_ALIGN;
stat->dio_mem_align = bdev_dma_alignment(bdev) + 1;
+ /*
+ * The minimum read size is generally the sector size of the underlying
+ * device, but when using checksums that whole FSB is needed to verify
+ * the checksum.
+ */
+ stat->dio_read_offset_align = xfs_min_diosize(ip, bdev);
+
/*
* For COW inodes, we can only perform out of place writes of entire
* allocation units (blocks or RT extents).
@@ -591,7 +598,6 @@ xfs_report_dioalign(
* alignment in dio_offset_align, and the smaller read alignment in
* dio_read_offset_align.
*/
- stat->dio_read_offset_align = bdev_logical_block_size(bdev);
if (xfs_is_cow_inode(ip))
stat->dio_offset_align = xfs_inode_alloc_unitsize(ip);
else
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 16/25] xfs: add support for reading with data checksums
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (14 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 15/25] xfs: require file system block size alignment when using data checksums Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-11 11:31 ` Pankaj Raghav (Samsung)
2026-11-04 8:57 ` [PATCH 17/25] xfs: add support for writing " Christoph Hellwig
` (8 subsequent siblings)
24 siblings, 1 reply; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
All reads from files with data checksums have the returned iomaps for
data blocks limited to be inside a single RT csum file block, so that
each data read only needs to deal with a single checksum buffer.
All reads on checksummed files need to use ioends so that the checksum
can be verified from process context. The ioend submission path looks
up the checksum buffer and kicks of an asynchronous read of it. The
completion path waits for the buffer if needed and verifies the checksum.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_aops.c | 3 +-
fs/xfs/xfs_ioend.c | 120 +++++++++++++++++++++++++++++++++++++++------
fs/xfs/xfs_iomap.c | 23 +++++++--
3 files changed, 126 insertions(+), 20 deletions(-)
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index c30e688cfc9f..db834e5e60c1 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -601,7 +601,8 @@ xfs_get_iomap_read_ops(
{
struct xfs_inode *ip = XFS_I(mapping->host);
- if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
+ if (xfs_has_rtcsum(ip->i_mount) ||
+ bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
return &xfs_iomap_read_ops;
return &iomap_bio_read_ops;
}
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index 05ab91dd4343..1066f262ba98 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -14,12 +14,80 @@
#include "xfs_trace.h"
#include "xfs_bmap_util.h"
#include "xfs_reflink.h"
+#include "xfs_rtcsum.h"
#include "xfs_zone_alloc.h"
#include "xfs_ioend.h"
#include "xfs_error.h"
#include "xfs_errortag.h"
#include <linux/bio-integrity.h>
+static bool
+xfs_rtcsum_prepare_read(
+ struct iomap_ioend *ioend)
+{
+ struct xfs_inode *ip = XFS_I(ioend->io_inode);
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_buf *bp;
+ int error;
+
+ /*
+ * The main I/O end bio is either freshly build by iomap, and has both
+ * bi_idx and bi_offset set to zero, or comes from a bvec iter for
+ * direct I/O, in which case bi_offset can be non-zero, but bi_idx still
+ * must be zero as the bio always points to the first actually used
+ * bio_vec in the iov_iter.
+ */
+ error = -EIO;
+ if (WARN_ON_ONCE(ioend->io_bio.bi_iter.bi_idx))
+ goto fail;
+
+ error = xfs_rtcsum_read_async(mp,
+ xfs_daddr_to_rtb(mp, ioend->io_sector), &bp);
+ if (error)
+ goto fail;
+ ioend->io_private = bp;
+ return true;
+
+fail:
+ ioend->io_bio.bi_status = errno_to_blk_status(error);
+ bio_endio(&ioend->io_bio);
+ return false;
+}
+
+static int
+xfs_rtcsum_verify_ioend(
+ struct iomap_ioend *ioend,
+ int error)
+{
+ struct xfs_inode *ip = XFS_I(ioend->io_inode);
+ struct xfs_mount *mp = ip->i_mount;
+ xfs_rtblock_t bno = xfs_daddr_to_rtb(mp, ioend->io_sector);
+ unsigned int bsize = mp->m_sb.sb_blocksize;
+ struct xfs_buf *bp = ioend->io_private;
+ struct bvec_iter iter = {
+ .bi_size = roundup(ioend->io_size, bsize),
+ .bi_offset = ioend->io_bvec_offset,
+ };
+
+ /* Early xfs_rtcsum_prepare_read failures do not have a bp yet. */
+ if (!bp) {
+ ASSERT(error);
+ return error;
+ }
+
+ if (error)
+ goto out_rele;
+ error = xfs_buf_read_async_wait(bp);
+ if (error)
+ goto out_rele;
+
+ error = xfs_csum_verify(mp, &ioend->io_bio, &iter,
+ bp->b_addr + xfs_rtb_to_rtcsumoff(mp, bno), bno, true);
+out_rele:
+ xfs_buf_rele(bp);
+ return error;
+}
+
static void
xfs_dio_bounce_end_io(
struct bio *bio)
@@ -30,6 +98,9 @@ xfs_dio_bounce_end_io(
if ((ioend->io_flags & IOMAP_IOEND_INTEGRITY) && !bio->bi_status)
error = iomap_ioend_integrity_verify(ioend);
+ if (xfs_is_rtcsum_inode(XFS_I(ioend->io_inode)))
+ error = xfs_rtcsum_verify_ioend(ioend, error);
+
iomap_bounce_read_end_io(ioend, orig_bio, error);
}
@@ -39,7 +110,12 @@ xfs_bounce_submit_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;
+ if (xfs_is_rtcsum_inode(XFS_I(ioend->io_inode)) &&
+ !xfs_rtcsum_prepare_read(ioend))
+ return;
+
bio_set_flag(&ioend->io_bio, BIO_COMPLETE_IN_TASK);
submit_bio(&ioend->io_bio);
}
@@ -99,25 +175,36 @@ xfs_end_io_read(
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);
+ bool is_csum_error = false;
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_TEST_ERROR(mp, XFS_ERRTAG_BOUNCE_REREAD)) {
- xfs_read_bounce_and_resubmit(ioend);
- return;
- }
- }
+ /*
+ * 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)
+ is_csum_error = true;
}
- iomap_finish_ioends(ioend, error);
+ if (xfs_is_rtcsum_inode(ip)) {
+ error = xfs_rtcsum_verify_ioend(ioend, error);
+ if (error && !bio->bi_status)
+ is_csum_error = true;
+ }
+
+ /*
+ * If we saw a checksum failure on a direct I/O read that uses lazy
+ * bouncing, resubmit the read using a bounce buffer so that we can
+ * guarantee this was not caused by the user corrupting the buffer.
+ */
+ if ((ioend->io_flags & IOMAP_IOEND_DIRECT) &&
+ READ_ONCE(mp->m_read_bounce) == XFS_READ_BOUNCE_LAZY &&
+ (is_csum_error ||
+ (!error && XFS_TEST_ERROR(mp, XFS_ERRTAG_BOUNCE_REREAD))))
+ xfs_read_bounce_and_resubmit(ioend);
+ else
+ iomap_finish_ioends(ioend, error);
}
void
@@ -139,9 +226,12 @@ xfs_ioend_submit_read(
return;
}
+ bio->bi_end_io = xfs_end_io_read;
+ if (xfs_is_rtcsum_inode(ip) && !xfs_rtcsum_prepare_read(ioend))
+ return;
+
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);
}
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index c3465204b8d5..80b7c9df7c14 100644
--- a/fs/xfs/xfs_iomap.c
+++ b/fs/xfs/xfs_iomap.c
@@ -32,6 +32,7 @@
#include "xfs_rtbitmap.h"
#include "xfs_icache.h"
#include "xfs_zone_alloc.h"
+#include "xfs_rtcsum.h"
#define XFS_ALLOC_ALIGN(mp, off) \
(((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
@@ -166,6 +167,8 @@ xfs_bmbt_to_iomap(
}
iomap->validity_cookie = sequence_cookie;
+ if (xfs_is_rtcsum_inode(ip))
+ iomap->csum_shift = mp->m_rtcsum_shift;
return 0;
}
@@ -2249,16 +2252,28 @@ xfs_read_iomap_begin(
return error;
error = xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb, &imap,
&nimaps, 0);
- if (!error && ((flags & IOMAP_REPORT) || IS_DAX(inode)))
+ if (error)
+ goto out_unlock;
+
+ if ((flags & IOMAP_REPORT) || IS_DAX(inode)) {
error = xfs_reflink_trim_around_shared(ip, &imap, &shared);
+ if (error)
+ goto out_unlock;
+ } else if (!isnullstartblock(imap.br_startblock) &&
+ xfs_is_rtcsum_inode(ip)) {
+ imap.br_blockcount = min(imap.br_blockcount,
+ xfs_rtcsum_max_len(mp, imap.br_startblock));
+ }
+
seq = xfs_iomap_inode_sequence(ip, shared ? IOMAP_F_SHARED : 0);
xfs_iunlock(ip, lockmode);
-
- if (error)
- return error;
trace_xfs_iomap_found(ip, offset, length, XFS_DATA_FORK, &imap);
return xfs_bmbt_to_iomap(ip, iomap, &imap, flags,
shared ? IOMAP_F_SHARED : 0, seq);
+
+out_unlock:
+ xfs_iunlock(ip, lockmode);
+ return error;
}
static DEFINE_IOMAP_ITER_NEXT(xfs_read_iomap_next, xfs_read_iomap_begin);
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [PATCH 16/25] xfs: add support for reading with data checksums
2026-11-04 8:57 ` [PATCH 16/25] xfs: add support for reading with " Christoph Hellwig
@ 2026-11-11 11:31 ` Pankaj Raghav (Samsung)
2026-11-11 17:38 ` Christoph Hellwig
0 siblings, 1 reply; 34+ messages in thread
From: Pankaj Raghav (Samsung) @ 2026-11-11 11:31 UTC (permalink / raw)
To: Christoph Hellwig; +Cc: Carlos Maiolino, linux-xfs, linux-fsdevel
On Wed, Nov 04, 2026 at 09:57:20AM +0100, Christoph Hellwig wrote:
> All reads from files with data checksums have the returned iomaps for
> data blocks limited to be inside a single RT csum file block, so that
> each data read only needs to deal with a single checksum buffer.
>
> All reads on checksummed files need to use ioends so that the checksum
> can be verified from process context. The ioend submission path looks
> up the checksum buffer and kicks of an asynchronous read of it. The
> completion path waits for the buffer if needed and verifies the checksum.
>
> Signed-off-by: Christoph Hellwig <hch@lst.de>
> ---
> fs/xfs/xfs_aops.c | 3 +-
> fs/xfs/xfs_ioend.c | 120 +++++++++++++++++++++++++++++++++++++++------
> fs/xfs/xfs_iomap.c | 23 +++++++--
> 3 files changed, 126 insertions(+), 20 deletions(-)
>
> diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
> index c30e688cfc9f..db834e5e60c1 100644
> --- a/fs/xfs/xfs_aops.c
> +++ b/fs/xfs/xfs_aops.c
> @@ -601,7 +601,8 @@ xfs_get_iomap_read_ops(
> {
> struct xfs_inode *ip = XFS_I(mapping->host);
>
> - if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
> + if (xfs_has_rtcsum(ip->i_mount) ||
Shouldn't this be xfs_is_rtcsum_inode(ip) instead of a per mount option?
> + bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
> return &xfs_iomap_read_ops;
> return &iomap_bio_read_ops;
> }
--
Pankaj
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [PATCH 16/25] xfs: add support for reading with data checksums
2026-11-11 11:31 ` Pankaj Raghav (Samsung)
@ 2026-11-11 17:38 ` Christoph Hellwig
0 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-11 17:38 UTC (permalink / raw)
To: Pankaj Raghav (Samsung)
Cc: Christoph Hellwig, Carlos Maiolino, linux-xfs, linux-fsdevel
On Wed, Nov 11, 2026 at 12:31:52PM +0100, Pankaj Raghav (Samsung) wrote:
> > diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
> > index c30e688cfc9f..db834e5e60c1 100644
> > --- a/fs/xfs/xfs_aops.c
> > +++ b/fs/xfs/xfs_aops.c
> > @@ -601,7 +601,8 @@ xfs_get_iomap_read_ops(
> > {
> > struct xfs_inode *ip = XFS_I(mapping->host);
> >
> > - if (bdev_has_integrity_csum(xfs_inode_buftarg(ip)->bt_bdev))
> > + if (xfs_has_rtcsum(ip->i_mount) ||
>
> Shouldn't this be xfs_is_rtcsum_inode(ip) instead of a per mount option?
Yes.
^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH 17/25] xfs: add support for writing with data checksums
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (15 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 16/25] xfs: add support for reading with " Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 18/25] xfs: add data checksum support to zoned garbage collection Christoph Hellwig
` (7 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Darrick J. Wong
All write to checksummed files need to use ioends so that the checksum
can be verified from process context.
The ioend submission path allocates the csum buffer and attaches it to
the ioend before generating the checksum from the file data. The I/O
completion then logs the checksums into the buffers for the LBAs that
were written.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/xfs_file.c | 7 +++---
fs/xfs/xfs_ioend.c | 51 +++++++++++++++++++++++++++++++++++++++++
fs/xfs/xfs_ioend.h | 2 ++
fs/xfs/xfs_iomap.c | 49 +++++++++++++++++++++++++++++++++++----
fs/xfs/xfs_iomap.h | 4 +++-
fs/xfs/xfs_reflink.c | 2 +-
fs/xfs/xfs_zone_alloc.c | 4 ++++
7 files changed, 109 insertions(+), 10 deletions(-)
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 97b0a976058a..c522913309c3 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -1012,7 +1012,8 @@ xfs_file_buffered_write(
trace_xfs_file_buffered_write(iocb, from);
ret = iomap_file_buffered_write(iocb, from,
- &xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops,
+ &xfs_buffered_write_iomap_ops,
+ xfs_get_iomap_write_ops(ip),
NULL);
/*
@@ -1093,8 +1094,8 @@ xfs_file_buffered_write_zoned(
retry:
trace_xfs_file_buffered_write(iocb, from);
ret = iomap_file_buffered_write(iocb, from,
- &xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops,
- &ac);
+ &xfs_buffered_write_iomap_ops,
+ xfs_get_iomap_write_ops(ip), &ac);
if (ret == -ENOSPC && !cleared_space) {
/*
* Kick off writeback to convert delalloc space and release the
diff --git a/fs/xfs/xfs_ioend.c b/fs/xfs/xfs_ioend.c
index 1066f262ba98..3a2d18e82822 100644
--- a/fs/xfs/xfs_ioend.c
+++ b/fs/xfs/xfs_ioend.c
@@ -236,6 +236,37 @@ xfs_ioend_submit_read(
submit_bio(bio);
}
+int
+xfs_ioend_submit_read_sync(
+ struct bio *bio,
+ struct inode *inode,
+ loff_t file_offset,
+ u16 ioend_flags)
+{
+ struct xfs_inode *ip = XFS_I(inode);
+ struct iomap_ioend *ioend;
+ struct bvec_iter saved_iter;
+ int error;
+
+ ASSERT(!(ioend_flags & IOMAP_IOEND_DIRECT));
+
+ ioend = iomap_init_ioend(inode, bio, file_offset, ioend_flags);
+ if (xfs_is_rtcsum_inode(ip) && !xfs_rtcsum_prepare_read(ioend))
+ return blk_status_to_errno(bio->bi_status);
+ if (ioend_flags & IOMAP_IOEND_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, &saved_iter);
+ fs_bio_integrity_free(bio);
+ }
+ if (xfs_is_rtcsum_inode(ip))
+ error = xfs_rtcsum_verify_ioend(ioend, error);
+ return error;
+}
+
static void
xfs_end_ioend_write_zoned(
struct iomap_ioend *ioend)
@@ -262,6 +293,26 @@ xfs_end_ioend_write_zoned(
goto done;
}
+ /*
+ * Log the checksum updates before remapping the newly written extents
+ * into the data fork. We must commit the csum update before the remap
+ * transaction to satisfy an ordering requirement that any read after a
+ * write must be able to find the new data and new checksum; or the old
+ * data and the old checksum.
+ *
+ * It's harmless if the system fails after the csum update but before
+ * the remap because nobody will ever see the newly written extent until
+ * the next GC cycle.
+ */
+ if (xfs_is_rtcsum_inode(ip)) {
+ error = xfs_rtcsum_log(oz, ioend->io_sector, ioend->io_size,
+ ioend->io_csum);
+ if (error) {
+ xfs_force_shutdown(ip->i_mount, SHUTDOWN_META_IO_ERROR);
+ goto done;
+ }
+ }
+
error = xfs_zoned_end_io(ip, ioend->io_offset, ioend->io_size,
ioend->io_sector, oz, NULLFSBLOCK);
if (error)
diff --git a/fs/xfs/xfs_ioend.h b/fs/xfs/xfs_ioend.h
index 7c2a1ea3e6ed..f01aa208c61f 100644
--- a/fs/xfs/xfs_ioend.h
+++ b/fs/xfs/xfs_ioend.h
@@ -14,5 +14,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);
+int xfs_ioend_submit_read_sync(struct bio *bio, struct inode *inode,
+ loff_t file_offset, u16 ioend_flags);
#endif /* __XFS_IOEND_H */
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index 80b7c9df7c14..ee45eb1baf3d 100644
--- a/fs/xfs/xfs_iomap.c
+++ b/fs/xfs/xfs_iomap.c
@@ -33,6 +33,7 @@
#include "xfs_icache.h"
#include "xfs_zone_alloc.h"
#include "xfs_rtcsum.h"
+#include "xfs_ioend.h"
#define XFS_ALLOC_ALIGN(mp, off) \
(((off) >> mp->m_allocsize_log) << mp->m_allocsize_log)
@@ -93,10 +94,45 @@ xfs_iomap_valid(
return true;
}
-const struct iomap_write_ops xfs_iomap_write_ops = {
+static const struct iomap_write_ops xfs_iomap_write_ops = {
.iomap_valid = xfs_iomap_valid,
};
+static int
+xfs_csum_read_folio_range(
+ const struct iomap_iter *iter,
+ struct folio *folio,
+ loff_t pos,
+ size_t len)
+{
+ const struct iomap *srcmap = iomap_iter_srcmap(iter);
+ unsigned int ioend_flags = iomap_ioend_flags(&iter->iomap);
+ struct bio *bio;
+ int error;
+
+ bio = bio_alloc_bioset(srcmap->bdev, 1, REQ_OP_READ, GFP_NOFS,
+ &iomap_ioend_bioset);
+ bio->bi_iter.bi_sector = iomap_sector(srcmap, pos);
+ bio_add_folio_nofail(bio, folio, len, offset_in_folio(folio, pos));
+ error = xfs_ioend_submit_read_sync(bio, iter->inode, pos, ioend_flags);
+ bio_put(bio);
+ return error;
+}
+
+static const struct iomap_write_ops xfs_iomap_csum_write_ops = {
+ .iomap_valid = xfs_iomap_valid,
+ .read_folio_range = xfs_csum_read_folio_range,
+};
+
+const struct iomap_write_ops *
+xfs_get_iomap_write_ops(
+ struct xfs_inode *ip)
+{
+ if (xfs_is_rtcsum_inode(ip))
+ return &xfs_iomap_csum_write_ops;
+ return &xfs_iomap_write_ops;
+}
+
int
xfs_bmbt_to_iomap(
struct xfs_inode *ip,
@@ -1639,6 +1675,9 @@ xfs_zoned_fill_srcmap(
* There is a data fork mapping, only map until the end of it.
*/
xfs_trim_extent(&smap, offset_fsb, *end_fsb - offset_fsb);
+ if (xfs_is_rtcsum_inode(ip))
+ smap.br_blockcount = min(smap.br_blockcount,
+ xfs_rtcsum_max_len(ip->i_mount, smap.br_startblock));
*end_fsb = min(*end_fsb, smap.br_startoff + smap.br_blockcount);
return xfs_bmbt_to_iomap(ip, srcmap, &smap, flags, 0,
xfs_iomap_inode_sequence(ip, 0));
@@ -2437,8 +2476,8 @@ xfs_zero_range(
return dax_zero_range(inode, pos, len, did_zero,
&xfs_dax_write_iomap_ops);
return iomap_zero_range(inode, pos, len, did_zero,
- &xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops,
- ac);
+ &xfs_buffered_write_iomap_ops,
+ xfs_get_iomap_write_ops(ip), ac);
}
int
@@ -2454,6 +2493,6 @@ xfs_truncate_page(
return dax_truncate_page(inode, pos, did_zero,
&xfs_dax_write_iomap_ops);
return iomap_truncate_page(inode, pos, did_zero,
- &xfs_buffered_write_iomap_ops, &xfs_iomap_write_ops,
- ac);
+ &xfs_buffered_write_iomap_ops,
+ xfs_get_iomap_write_ops(ip), ac);
}
diff --git a/fs/xfs/xfs_iomap.h b/fs/xfs/xfs_iomap.h
index 8fc0c6fc8bea..81fff7cad59b 100644
--- a/fs/xfs/xfs_iomap.h
+++ b/fs/xfs/xfs_iomap.h
@@ -44,6 +44,7 @@ xfs_iomap_set_anon_write(
iomap->flags = IOMAP_F_ANON_WRITE | IOMAP_F_DIRTY;
if (bdev_has_integrity_csum(iomap->bdev))
iomap->flags |= IOMAP_F_INTEGRITY;
+ iomap->csum_shift = ip->i_mount->m_rtcsum_shift;
}
static inline xfs_filblks_t
@@ -70,6 +71,8 @@ int xfs_read_iomap_begin(struct inode *inode, loff_t offset,
loff_t length, unsigned flags, struct iomap *iomap,
struct iomap *srcmap);
+const struct iomap_write_ops *xfs_get_iomap_write_ops(struct xfs_inode *ip);
+
extern const struct iomap_ops xfs_buffered_write_iomap_ops;
extern const struct iomap_ops xfs_direct_write_iomap_ops;
extern const struct iomap_ops xfs_zoned_direct_write_iomap_ops;
@@ -78,6 +81,5 @@ extern const struct iomap_ops xfs_seek_iomap_ops;
extern const struct iomap_ops xfs_xattr_iomap_ops;
extern const struct iomap_ops xfs_dax_write_iomap_ops;
extern const struct iomap_ops xfs_atomic_write_cow_iomap_ops;
-extern const struct iomap_write_ops xfs_iomap_write_ops;
#endif /* __XFS_IOMAP_H__*/
diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c
index 104542b10d9e..5653aa08ce1c 100644
--- a/fs/xfs/xfs_reflink.c
+++ b/fs/xfs/xfs_reflink.c
@@ -1923,7 +1923,7 @@ xfs_reflink_unshare(
else
error = iomap_file_unshare(inode, offset, len,
&xfs_buffered_write_iomap_ops,
- &xfs_iomap_write_ops);
+ xfs_get_iomap_write_ops(ip));
if (error)
goto out;
diff --git a/fs/xfs/xfs_zone_alloc.c b/fs/xfs/xfs_zone_alloc.c
index 0dbc08c58817..5c5f42f3262c 100644
--- a/fs/xfs/xfs_zone_alloc.c
+++ b/fs/xfs/xfs_zone_alloc.c
@@ -964,6 +964,10 @@ xfs_zone_alloc_and_submit(
if (ioend->io_flags & IOMAP_IOEND_INTEGRITY)
fs_bio_integrity_generate(&ioend->io_bio);
+ if (xfs_is_rtcsum_inode(ip)) {
+ xfs_csum_generate(mp, &ioend->io_bio,
+ iomap_csum_alloc(ioend, mp->m_rtcsum_shift));
+ }
/*
* If we don't have a locally cached zone in this write context, see if
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 18/25] xfs: add data checksum support to zoned garbage collection
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (16 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 17/25] xfs: add support for writing " Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 19/25] xfs: simplify range tracking in xfs_verify_media Christoph Hellwig
` (6 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Transfer the checksum from the old location to the new one. The
implementation mirrors that of user data reads and writes.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_zone_gc.c | 66 ++++++++++++++++++++++++++++++++++++--------
1 file changed, 54 insertions(+), 12 deletions(-)
diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c
index cea5447a886d..9296f584282a 100644
--- a/fs/xfs/xfs_zone_gc.c
+++ b/fs/xfs/xfs_zone_gc.c
@@ -21,6 +21,7 @@
#include "xfs_zone_alloc.h"
#include "xfs_zone_priv.h"
#include "xfs_zones.h"
+#include "xfs_rtcsum.h"
#include "xfs_trace.h"
/*
@@ -106,6 +107,9 @@ struct xfs_gc_bio {
/* Realtime group currently being reclaimed */
struct xfs_rtgroup *victim_rtg;
+ /* Buffer for data checksums */
+ struct xfs_buf *csum_bp;
+
/* Bio used for reads and writes, including the bvec used by it */
struct bio bio; /* must be last */
};
@@ -660,6 +664,20 @@ xfs_zone_gc_alloc_blocks(
return true;
}
+static void
+xfs_zone_gc_free_chunk(
+ struct xfs_gc_bio *chunk)
+{
+ atomic_dec(&chunk->victim_rtg->rtg_gccount);
+ xfs_rtgroup_rele(chunk->victim_rtg);
+ list_del(&chunk->entry);
+ xfs_open_zone_put(chunk->oz);
+ if (chunk->csum_bp)
+ xfs_buf_rele(chunk->csum_bp);
+ xfs_irele(chunk->ip);
+ bio_put(&chunk->bio);
+}
+
static void
xfs_zone_gc_add_data(
struct xfs_gc_bio *chunk)
@@ -725,6 +743,11 @@ xfs_zone_gc_start_chunk(
if (!xfs_zone_gc_iter_irec(mp, iter, &irec, &ip))
return false;
+ if (xfs_has_rtcsum(mp)) {
+ irec.rm_blockcount = min(irec.rm_blockcount,
+ xfs_rtcsum_max_len(mp, irec.rm_startblock));
+ }
+
if (!xfs_zone_gc_alloc_blocks(data, &irec.rm_blockcount, &daddr,
&is_seq)) {
xfs_irele(ip);
@@ -748,6 +771,7 @@ xfs_zone_gc_start_chunk(
chunk->data = data;
chunk->oz = data->oz;
chunk->victim_rtg = iter->victim_rtg;
+ chunk->csum_bp = NULL;
atomic_inc(&rtg_group(chunk->victim_rtg)->xg_active_ref);
atomic_inc(&chunk->victim_rtg->rtg_gccount);
@@ -764,22 +788,22 @@ xfs_zone_gc_start_chunk(
list_add_tail(&chunk->entry, &data->reading);
xfs_zone_gc_iter_advance(iter, irec.rm_blockcount);
+ if (xfs_is_rtcsum_inode(ip)) {
+ int error;
+
+ error = xfs_rtcsum_read_async(mp, chunk->old_startblock,
+ &chunk->csum_bp);
+ if (error) {
+ xfs_force_shutdown(mp, SHUTDOWN_META_IO_ERROR);
+ xfs_zone_gc_free_chunk(chunk);
+ return false;
+ }
+ }
+
submit_bio(bio);
return true;
}
-static void
-xfs_zone_gc_free_chunk(
- struct xfs_gc_bio *chunk)
-{
- atomic_dec(&chunk->victim_rtg->rtg_gccount);
- xfs_rtgroup_rele(chunk->victim_rtg);
- list_del(&chunk->entry);
- xfs_open_zone_put(chunk->oz);
- xfs_irele(chunk->ip);
- bio_put(&chunk->bio);
-}
-
static void
xfs_zone_gc_submit_write(
struct xfs_zone_gc_data *data,
@@ -832,6 +856,9 @@ xfs_zone_gc_split_write(
split_chunk->old_startblock = chunk->old_startblock;
split_chunk->new_daddr = chunk->new_daddr;
split_chunk->oz = chunk->oz;
+ split_chunk->csum_bp = chunk->csum_bp;
+ if (split_chunk->csum_bp)
+ xfs_buf_hold(split_chunk->csum_bp);
atomic_inc(&chunk->oz->oz_ref);
split_chunk->victim_rtg = chunk->victim_rtg;
@@ -919,6 +946,21 @@ xfs_zone_gc_finish_chunk(
if (chunk->is_seq)
chunk->new_daddr = chunk->bio.bi_iter.bi_sector;
+
+ if (xfs_is_rtcsum_inode(ip)) {
+ unsigned int boff;
+
+ error = xfs_buf_read_async_wait(chunk->csum_bp);
+ if (error)
+ goto free;
+
+ boff = xfs_rtb_to_rtcsumoff(mp, chunk->old_startblock);
+ error = xfs_rtcsum_log(chunk->oz, chunk->new_daddr, chunk->len,
+ chunk->csum_bp->b_addr + boff);
+ if (error)
+ goto free;
+ }
+
error = xfs_zoned_end_io(ip, chunk->offset, chunk->len,
chunk->new_daddr, chunk->oz, chunk->old_startblock);
free:
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 19/25] xfs: simplify range tracking in xfs_verify_media
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (17 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 18/25] xfs: add data checksum support to zoned garbage collection Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 20/25] xfs: verify data checksums during media verification Christoph Hellwig
` (5 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Remove the bbcount variable tracking the remainining basic blocks in
the current verification request, and instead always recalculated it
from the daddr and the end, reducing the number of moving pieces.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_verify_media.c | 42 +++++++++++----------------------------
1 file changed, 12 insertions(+), 30 deletions(-)
diff --git a/fs/xfs/xfs_verify_media.c b/fs/xfs/xfs_verify_media.c
index 516763219d3f..382c46cdfb6a 100644
--- a/fs/xfs/xfs_verify_media.c
+++ b/fs/xfs/xfs_verify_media.c
@@ -267,12 +267,11 @@ xfs_verify_media(
struct xfs_mount *mp,
struct xfs_verify_media *me)
{
+ xfs_daddr_t daddr = me->me_start_daddr;
struct xfs_buftarg *btp = NULL;
struct folio *folio;
xfs_daddr_t dev_start = 0;
xfs_daddr_t dev_end = 0;
- xfs_daddr_t daddr;
- uint64_t bbcount;
int error = 0;
me->me_ioerror = 0;
@@ -305,11 +304,11 @@ xfs_verify_media(
*/
if (me->me_end_daddr > dev_end)
me->me_end_daddr = dev_end;
- if (me->me_start_daddr < dev_start)
- me->me_start_daddr = dev_start;
+ if (daddr < dev_start)
+ daddr = dev_start;
/* start and end have to be aligned to the lba size */
- if (!IS_ALIGNED(BBTOB(me->me_start_daddr | me->me_end_daddr),
+ if (!IS_ALIGNED(BBTOB(daddr | me->me_end_daddr),
btp->bt_logical_sectorsize))
return -EINVAL;
@@ -317,36 +316,22 @@ xfs_verify_media(
* end_daddr is the exclusive end of the range, so if start_daddr
* reaches there (or beyond), there's no work to be done.
*/
- if (me->me_start_daddr >= me->me_end_daddr)
+ if (daddr >= me->me_end_daddr)
return 0;
- /*
- * There are three ranges involved here:
- *
- * - [me->me_start_daddr, me->me_end_daddr) is the range that the
- * user wants to verify. end_daddr can be beyond the end of the
- * disk; we'll constrain it to the end if necessary.
- *
- * - [daddr, me->me_end_daddr) is the range that we have not yet
- * verified. We update daddr after each successful read.
- * me->me_start_daddr is set to daddr before returning.
- *
- * - [daddr, daddr + bio_bbcount) is the range that we're currently
- * verifying.
- */
- daddr = me->me_start_daddr;
- bbcount = me->me_end_daddr - me->me_start_daddr;
-
- folio = xfs_verify_alloc_folio(xfs_verify_iosize(me, btp, bbcount));
+ folio = xfs_verify_alloc_folio(xfs_verify_iosize(me, btp,
+ me->me_end_daddr - daddr));
if (!folio)
return -ENOMEM;
- trace_xfs_verify_media(mp, me, btp->bt_dev, daddr, bbcount, folio);
+ trace_xfs_verify_media(mp, me, btp->bt_dev, daddr,
+ me->me_end_daddr - daddr, folio);
for (;;) {
unsigned int bio_bbcount;
- bio_bbcount = min(bbcount, folio_size(folio) >> SECTOR_SHIFT);
+ bio_bbcount = min(me->me_end_daddr - daddr,
+ folio_size(folio) >> SECTOR_SHIFT);
error = bdev_rw_virt(btp->bt_bdev, daddr, folio_address(folio),
bio_bbcount << SECTOR_SHIFT,
REQ_OP_READ);
@@ -356,11 +341,8 @@ xfs_verify_media(
error = 0;
break;
}
-
daddr += bio_bbcount;
- bbcount -= bio_bbcount;
-
- if (bbcount == 0)
+ if (daddr >= me->me_end_daddr)
break;
if (me->me_rest_us) {
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 20/25] xfs: verify data checksums during media verification
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (18 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 19/25] xfs: simplify range tracking in xfs_verify_media Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-11 11:57 ` Pankaj Raghav (Samsung)
2026-11-04 8:57 ` [PATCH 21/25] xfs: don't try to verify checksums on empty zones Christoph Hellwig
` (4 subsequent siblings)
24 siblings, 1 reply; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Wire up reading and verifying data checksums during media verification.
This is very similar to the file read path in that it kicks of an async
read for the checksum buffer before reading the data, and then validating
once both are read in.
To support this, split the per-read logic in xfs_verify_media into a
separate helpers for the data checksums vs no checksum cases.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_verify_media.c | 116 ++++++++++++++++++++++++++++++++++----
1 file changed, 105 insertions(+), 11 deletions(-)
diff --git a/fs/xfs/xfs_verify_media.c b/fs/xfs/xfs_verify_media.c
index 382c46cdfb6a..bafaac2f3ea8 100644
--- a/fs/xfs/xfs_verify_media.c
+++ b/fs/xfs/xfs_verify_media.c
@@ -22,6 +22,7 @@
#include "xfs_rtrmap_btree.h"
#include "xfs_health.h"
#include "xfs_healthmon.h"
+#include "xfs_rtcsum.h"
#include "xfs_trace.h"
#include "xfs_verify_media.h"
@@ -261,6 +262,99 @@ xfs_verify_media_error(
}
}
+static int
+xfs_submit_verify_bio(
+ struct xfs_mount *mp,
+ struct xfs_verify_media *me,
+ struct xfs_buftarg *btp,
+ struct folio *folio,
+ xfs_daddr_t *daddr)
+{
+ unsigned int bbcount;
+ int error;
+
+ bbcount = min(me->me_end_daddr - *daddr,
+ folio_size(folio) >> SECTOR_SHIFT);
+ error = bdev_rw_virt(btp->bt_bdev, *daddr, folio_address(folio),
+ bbcount << SECTOR_SHIFT, REQ_OP_READ);
+ if (error) {
+ xfs_verify_media_error(mp, me, btp, *daddr, bbcount, error);
+ return 1;
+ }
+
+ *daddr += bbcount;
+ return 0;
+}
+
+/*
+ * Data checksums are per-FSB, so we can only validate FSB-aligned regions.
+ *
+ * When the alignment leads to an empty range, give up.
+ */
+static int
+xfs_submit_verify_bio_csum(
+ struct xfs_mount *mp,
+ struct xfs_verify_media *me,
+ struct xfs_buftarg *btp,
+ struct folio *folio,
+ xfs_daddr_t *daddr)
+{
+ xfs_fsblock_t bno = xfs_daddr_to_rtb(mp, *daddr);
+ xfs_fsblock_t end = xfs_daddr_to_rtb(mp, me->me_end_daddr);
+ struct xfs_buf *csum_bp = NULL;
+ struct bvec_iter saved_iter;
+ unsigned int bbcount;
+ xfs_filblks_t len;
+ struct bio bio;
+ struct bio_vec bv;
+ int error;
+
+ len = min3(end - bno, xfs_rtcsum_max_len(mp, bno),
+ XFS_B_TO_FSBT(mp, folio_size(folio)));
+ if (!len) {
+ *daddr = me->me_end_daddr;
+ return 0;
+ }
+
+ error = xfs_rtcsum_read_async(mp, bno, &csum_bp);
+ if (error)
+ return error;
+
+ *daddr = xfs_rtb_to_daddr(mp, bno);
+ bbcount = XFS_FSB_TO_BB(mp, len);
+
+ bio_init(&bio, btp->bt_bdev, &bv, 1, REQ_OP_READ);
+ bio.bi_iter.bi_sector = *daddr;
+ bio_add_folio_nofail(&bio, folio, bbcount << SECTOR_SHIFT, 0);
+
+ saved_iter = bio.bi_iter;
+ error = submit_bio_wait(&bio);
+ if (error) {
+ xfs_verify_media_error(mp, me, btp, *daddr, bbcount, error);
+ error = 1;
+ goto out_buf_rele;
+ }
+
+ error = xfs_buf_read_async_wait(csum_bp);
+ if (error)
+ goto out_buf_rele;
+
+ error = xfs_csum_verify(mp, &bio, &saved_iter,
+ csum_bp->b_addr + xfs_rtb_to_rtcsumoff(mp, bno), bno,
+ false);
+ if (error) {
+ xfs_verify_media_error(mp, me, btp, *daddr, bbcount, error);
+ error = 1;
+ goto out_buf_rele;
+ }
+
+ *daddr = xfs_rtb_to_daddr(mp, bno + len);
+out_buf_rele:
+ xfs_buf_rele(csum_bp);
+ bio_uninit(&bio);
+ return error;
+}
+
/* Verify the media of an xfs device by submitting read requests to the disk. */
static int
xfs_verify_media(
@@ -328,20 +422,20 @@ xfs_verify_media(
me->me_end_daddr - daddr, folio);
for (;;) {
- unsigned int bio_bbcount;
-
- bio_bbcount = min(me->me_end_daddr - daddr,
- folio_size(folio) >> SECTOR_SHIFT);
- error = bdev_rw_virt(btp->bt_bdev, daddr, folio_address(folio),
- bio_bbcount << SECTOR_SHIFT,
- REQ_OP_READ);
+ if (IS_ENABLED(CONFIG_XFS_RT) &&
+ me->me_dev == XFS_DEV_RT &&
+ xfs_has_rtcsum(mp)) {
+ error = xfs_submit_verify_bio_csum(mp, me, btp, folio,
+ &daddr);
+ } else {
+ error = xfs_submit_verify_bio(mp, me, btp, folio,
+ &daddr);
+ }
if (error) {
- xfs_verify_media_error(mp, me, btp, daddr, bio_bbcount,
- error);
- error = 0;
+ if (error == 1)
+ error = 0;
break;
}
- daddr += bio_bbcount;
if (daddr >= me->me_end_daddr)
break;
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* Re: [PATCH 20/25] xfs: verify data checksums during media verification
2026-11-04 8:57 ` [PATCH 20/25] xfs: verify data checksums during media verification Christoph Hellwig
@ 2026-11-11 11:57 ` Pankaj Raghav (Samsung)
2026-11-11 17:39 ` Christoph Hellwig
0 siblings, 1 reply; 34+ messages in thread
From: Pankaj Raghav (Samsung) @ 2026-11-11 11:57 UTC (permalink / raw)
To: Christoph Hellwig; +Cc: Carlos Maiolino, linux-xfs, linux-fsdevel
On Wed, Nov 04, 2026 at 09:57:24AM +0100, Christoph Hellwig wrote:
> Wire up reading and verifying data checksums during media verification.
> This is very similar to the file read path in that it kicks of an async
> read for the checksum buffer before reading the data, and then validating
> once both are read in.
>
> To support this, split the per-read logic in xfs_verify_media into a
> separate helpers for the data checksums vs no checksum cases.
>
> +
> +/*
> + * Data checksums are per-FSB, so we can only validate FSB-aligned regions.
> + *
> + * When the alignment leads to an empty range, give up.
> + */
A comment here would be helpful about the return values.
0 for success,
1 for media errors
errno for other errors.
> +static int
> +xfs_submit_verify_bio_csum(
> + struct xfs_mount *mp,
> + struct xfs_verify_media *me,
> + struct xfs_buftarg *btp,
> + struct folio *folio,
> + xfs_daddr_t *daddr)
> +{
> + xfs_fsblock_t bno = xfs_daddr_to_rtb(mp, *daddr);
> + xfs_fsblock_t end = xfs_daddr_to_rtb(mp, me->me_end_daddr);
> + struct xfs_buf *csum_bp = NULL;
> + struct bvec_iter saved_iter;
> + unsigned int bbcount;
> + xfs_filblks_t len;
> + struct bio bio;
> + struct bio_vec bv;
> + int error;
> +
> + len = min3(end - bno, xfs_rtcsum_max_len(mp, bno),
> + XFS_B_TO_FSBT(mp, folio_size(folio)));
For large block size systems, if we allocate a single page in the folio
due to fragmentation, len will always be zero (4096 >> (blocklog > 12)).
> + if (!len) {
> + *daddr = me->me_end_daddr;
> + return 0;
> + }
And we skip those ranges without verifying anything with a success.
I think we should raise the iosize to a single FSB in
xfs_verify_alloc_folio and should return ENOMEM if we cannot allocate
one?
> +
> + error = xfs_rtcsum_read_async(mp, bno, &csum_bp);
> + if (error)
> + return error;
> +
> + *daddr = xfs_rtb_to_daddr(mp, bno);
> + bbcount = XFS_FSB_TO_BB(mp, len);
> +
> + bio_init(&bio, btp->bt_bdev, &bv, 1, REQ_OP_READ);
> + bio.bi_iter.bi_sector = *daddr;
> + bio_add_folio_nofail(&bio, folio, bbcount << SECTOR_SHIFT, 0);
> +
> + saved_iter = bio.bi_iter;
> + error = submit_bio_wait(&bio);
> + if (error) {
> + xfs_verify_media_error(mp, me, btp, *daddr, bbcount, error);
> + error = 1;
> + goto out_buf_rele;
> + }
> +
> + error = xfs_buf_read_async_wait(csum_bp);
> + if (error)
> + goto out_buf_rele;
> +
> + error = xfs_csum_verify(mp, &bio, &saved_iter,
> + csum_bp->b_addr + xfs_rtb_to_rtcsumoff(mp, bno), bno,
> + false);
> + if (error) {
> + xfs_verify_media_error(mp, me, btp, *daddr, bbcount, error);
> + error = 1;
> + goto out_buf_rele;
> + }
> +
> + *daddr = xfs_rtb_to_daddr(mp, bno + len);
> +out_buf_rele:
> + xfs_buf_rele(csum_bp);
> + bio_uninit(&bio);
> + return error;
> +}
> +
--
Pankaj
^ permalink raw reply [flat|nested] 34+ messages in thread* Re: [PATCH 20/25] xfs: verify data checksums during media verification
2026-11-11 11:57 ` Pankaj Raghav (Samsung)
@ 2026-11-11 17:39 ` Christoph Hellwig
0 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-11 17:39 UTC (permalink / raw)
To: Pankaj Raghav (Samsung)
Cc: Christoph Hellwig, Carlos Maiolino, linux-xfs, linux-fsdevel
On Wed, Nov 11, 2026 at 12:57:39PM +0100, Pankaj Raghav (Samsung) wrote:
> > +/*
> > + * Data checksums are per-FSB, so we can only validate FSB-aligned regions.
> > + *
> > + * When the alignment leads to an empty range, give up.
> > + */
>
> A comment here would be helpful about the return values.
> 0 for success,
> 1 for media errors
> errno for other errors.
Ok, I'll add it.
> > + unsigned int bbcount;
> > + xfs_filblks_t len;
> > + struct bio bio;
> > + struct bio_vec bv;
> > + int error;
> > +
> > + len = min3(end - bno, xfs_rtcsum_max_len(mp, bno),
> > + XFS_B_TO_FSBT(mp, folio_size(folio)));
>
> For large block size systems, if we allocate a single page in the folio
> due to fragmentation, len will always be zero (4096 >> (blocklog > 12)).
>
> > + if (!len) {
> > + *daddr = me->me_end_daddr;
> > + return 0;
> > + }
>
> And we skip those ranges without verifying anything with a success.
>
> I think we should raise the iosize to a single FSB in
> xfs_verify_alloc_folio and should return ENOMEM if we cannot allocate
> one?
Yes, I'll do that.
^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH 21/25] xfs: don't try to verify checksums on empty zones
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (19 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 20/25] xfs: verify data checksums during media verification Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 22/25] xfs: report RT data checksum information via XFS_FSOP_GEOM Christoph Hellwig
` (3 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
xfs_scrub can sometimes send XFS_IOC_VERIFY_MEDIA ioctls for ranges
that have never been written since the last zone reset, which will
lead to checksum verification failures.
Protect against this by checking that the range is valid. To make the
range-check non-racy hold the IOLOCK of the rmap inode over the entire
media verification and cycle through it before resetting a zone, as that
is the only way to rewrite data in a zone.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/xfs_verify_media.c | 46 ++++++++++++++++++++++++++++++++++++---
fs/xfs/xfs_zone_gc.c | 7 ++++++
2 files changed, 50 insertions(+), 3 deletions(-)
diff --git a/fs/xfs/xfs_verify_media.c b/fs/xfs/xfs_verify_media.c
index bafaac2f3ea8..d94bee0b9b6b 100644
--- a/fs/xfs/xfs_verify_media.c
+++ b/fs/xfs/xfs_verify_media.c
@@ -25,6 +25,7 @@
#include "xfs_rtcsum.h"
#include "xfs_trace.h"
#include "xfs_verify_media.h"
+#include "xfs_zone_alloc.h"
#include <linux/fserror.h>
@@ -291,6 +292,46 @@ xfs_submit_verify_bio(
*
* When the alignment leads to an empty range, give up.
*/
+static int
+xfs_csum_verify_metafile(
+ struct xfs_mount *mp,
+ struct bio *bio,
+ struct bvec_iter *saved_iter,
+ void *csum_buf,
+ xfs_fsblock_t bno)
+{
+ struct xfs_rtgroup *rtg;
+ bool is_valid;
+ int error = 0;
+
+ rtg = xfs_rtgroup_get(mp, xfs_rtb_to_rgno(mp, bno));
+ if (!rtg)
+ return -EFSCORRUPTED;
+
+ /*
+ * Only validate the checksums for valid data, as data never written
+ * will not have valid checksums. We need to hold the iolock on the
+ * rmap inode over the actual verification to prevent freeing of blocks
+ * and thus a zone reset to happen underneath us.
+ *
+ * Note that this still relies on cooperating userspace, as there also
+ * can be blocks that were written but never recorded after an unclean
+ * shutdown, which this check does not catch. It purely tries to deal
+ * with races vs the previous FSMAP output used by xfs_scrub.
+ */
+ xfs_ilock(rtg_rmap(rtg), XFS_IOLOCK_SHARED);
+ xfs_ilock(rtg_rmap(rtg), XFS_ILOCK_SHARED);
+ is_valid = xfs_zone_rgbno_is_valid(rtg, xfs_rtb_to_rgbno(mp, bno));
+ xfs_iunlock(rtg_rmap(rtg), XFS_ILOCK_SHARED);
+
+ if (is_valid)
+ error = xfs_csum_verify(mp, bio, saved_iter, csum_buf, bno,
+ false);
+ xfs_iunlock(rtg_rmap(rtg), XFS_IOLOCK_SHARED);
+ xfs_rtgroup_put(rtg);
+ return error;
+}
+
static int
xfs_submit_verify_bio_csum(
struct xfs_mount *mp,
@@ -339,9 +380,8 @@ xfs_submit_verify_bio_csum(
if (error)
goto out_buf_rele;
- error = xfs_csum_verify(mp, &bio, &saved_iter,
- csum_bp->b_addr + xfs_rtb_to_rtcsumoff(mp, bno), bno,
- false);
+ error = xfs_csum_verify_metafile(mp, &bio, &saved_iter,
+ csum_bp->b_addr + xfs_rtb_to_rtcsumoff(mp, bno), bno);
if (error) {
xfs_verify_media_error(mp, me, btp, *daddr, bbcount, error);
error = 1;
diff --git a/fs/xfs/xfs_zone_gc.c b/fs/xfs/xfs_zone_gc.c
index 9296f584282a..7e04693fd573 100644
--- a/fs/xfs/xfs_zone_gc.c
+++ b/fs/xfs/xfs_zone_gc.c
@@ -1001,6 +1001,13 @@ xfs_submit_zone_reset_bio(
trace_xfs_zone_reset(rtg);
+ /*
+ * Cycle through the rmap inode iolock to synchronize against
+ * xfs_csum_verify_metafile.
+ */
+ xfs_ilock(rtg_rmap(rtg), XFS_IOLOCK_SHARED);
+ xfs_iunlock(rtg_rmap(rtg), XFS_IOLOCK_SHARED);
+
ASSERT(rtg_rmap(rtg)->i_used_blocks == 0);
if (XFS_TEST_ERROR(mp, XFS_ERRTAG_ZONE_RESET)) {
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 22/25] xfs: report RT data checksum information via XFS_FSOP_GEOM
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (20 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 21/25] xfs: don't try to verify checksums on empty zones Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 23/25] xfs: add an experimental feature warning for RT data checksums Christoph Hellwig
` (2 subsequent siblings)
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Darrick J. Wong
Report the RT data checksum flag and the checksum algorithm to userspace.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_fs.h | 6 +++++-
fs/xfs/libxfs/xfs_sb.c | 5 +++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/libxfs/xfs_fs.h b/fs/xfs/libxfs/xfs_fs.h
index 226cb96dfae8..ef644cfe26d6 100644
--- a/fs/xfs/libxfs/xfs_fs.h
+++ b/fs/xfs/libxfs/xfs_fs.h
@@ -191,7 +191,10 @@ struct xfs_fsop_geom {
__u32 rgcount; /* number of realtime groups */
__u64 rtstart; /* start of internal rt section */
__u64 rtreserved; /* RT (zoned) reserved blocks */
- __u64 reserved[14]; /* reserved space */
+ __u8 rtcsum_type; /* RT data checksum type */
+ __u8 rtcsum_blklog; /* log2 of rtcsum bsize */
+ __u8 reserved_pad[6];/* reserved space */
+ __u64 reserved[13]; /* reserved space */
};
#define XFS_FSOP_GEOM_SICK_COUNTERS (1 << 0) /* summary counters */
@@ -250,6 +253,7 @@ typedef struct xfs_fsop_resblks {
#define XFS_FSOP_GEOM_FLAGS_PARENT (1 << 25) /* linux parent pointers */
#define XFS_FSOP_GEOM_FLAGS_METADIR (1 << 26) /* metadata directories */
#define XFS_FSOP_GEOM_FLAGS_ZONED (1 << 27) /* zoned rt device */
+#define XFS_FSOP_GEOM_FLAGS_DATA_CSUM (1 << 28) /* data checksums */
/*
* Minimum and maximum sizes need for growth checks.
diff --git a/fs/xfs/libxfs/xfs_sb.c b/fs/xfs/libxfs/xfs_sb.c
index 8096706ed78a..a1aebf398423 100644
--- a/fs/xfs/libxfs/xfs_sb.c
+++ b/fs/xfs/libxfs/xfs_sb.c
@@ -1630,6 +1630,11 @@ xfs_fs_geometry(
geo->flags |= XFS_FSOP_GEOM_FLAGS_METADIR;
if (xfs_has_zoned(mp))
geo->flags |= XFS_FSOP_GEOM_FLAGS_ZONED;
+ if (xfs_has_rtcsum(mp)) {
+ geo->flags |= XFS_FSOP_GEOM_FLAGS_DATA_CSUM;
+ geo->rtcsum_type = mp->m_sb.sb_rtcsum_type;
+ geo->rtcsum_blklog = mp->m_sb.sb_rtcsum_blklog;
+ }
geo->rtsectsize = sbp->sb_blocksize;
geo->dirblocksize = xfs_dir2_dirblock_bytes(sbp);
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 23/25] xfs: add an experimental feature warning for RT data checksums
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (21 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 22/25] xfs: report RT data checksum information via XFS_FSOP_GEOM Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 24/25] xfs: enable " Christoph Hellwig
2026-11-04 8:57 ` [PATCH 25/25] xfs: add a RT checksum log item Christoph Hellwig
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Darrick J. Wong
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/xfs_message.c | 4 ++++
fs/xfs/xfs_message.h | 1 +
fs/xfs/xfs_mount.h | 2 ++
fs/xfs/xfs_rtcsum.c | 1 +
4 files changed, 8 insertions(+)
diff --git a/fs/xfs/xfs_message.c b/fs/xfs/xfs_message.c
index 0243e509a468..2924e5c2b8fc 100644
--- a/fs/xfs/xfs_message.c
+++ b/fs/xfs/xfs_message.c
@@ -149,6 +149,10 @@ xfs_warn_experimental(
.opstate = XFS_OPSTATE_WARNED_LARP,
.name = "logged extended attributes",
},
+ [XFS_EXPERIMENTAL_CSUM] = {
+ .opstate = XFS_OPSTATE_WARNED_CSUM,
+ .name = "data checksums",
+ },
};
ASSERT(feat >= 0 && feat < XFS_EXPERIMENTAL_MAX);
BUILD_BUG_ON(ARRAY_SIZE(features) != XFS_EXPERIMENTAL_MAX);
diff --git a/fs/xfs/xfs_message.h b/fs/xfs/xfs_message.h
index bfae8dddc9a6..23b178ea4976 100644
--- a/fs/xfs/xfs_message.h
+++ b/fs/xfs/xfs_message.h
@@ -94,6 +94,7 @@ void xfs_buf_alert_ratelimited(struct xfs_buf *bp, const char *rlmsg,
enum xfs_experimental_feat {
XFS_EXPERIMENTAL_SHRINK,
XFS_EXPERIMENTAL_LARP,
+ XFS_EXPERIMENTAL_CSUM,
XFS_EXPERIMENTAL_MAX,
};
diff --git a/fs/xfs/xfs_mount.h b/fs/xfs/xfs_mount.h
index 0e60d5c29c9d..70f7563cde61 100644
--- a/fs/xfs/xfs_mount.h
+++ b/fs/xfs/xfs_mount.h
@@ -583,6 +583,8 @@ __XFS_HAS_FEAT(nouuid, NOUUID)
*/
#define XFS_OPSTATE_BLOCKGC_ENABLED 6
+/* Kernel has logged a warning about checksums */
+#define XFS_OPSTATE_WARNED_CSUM 8
/* Kernel has logged a warning about shrink being used on this fs. */
#define XFS_OPSTATE_WARNED_SHRINK 9
/* Kernel has logged a warning about logged xattr updates being used. */
diff --git a/fs/xfs/xfs_rtcsum.c b/fs/xfs/xfs_rtcsum.c
index 64fbf6886f8f..93e9792207c6 100644
--- a/fs/xfs/xfs_rtcsum.c
+++ b/fs/xfs/xfs_rtcsum.c
@@ -308,6 +308,7 @@ xfs_rtcsum_mount(
}
mp->m_features |= XFS_FEAT_DAX_NEVER;
+ xfs_warn_experimental(mp, XFS_EXPERIMENTAL_CSUM);
xfs_info(mp, "using %s for RT device data checksums",
xfs_data_csum_names[mp->m_sb.sb_rtcsum_type]);
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 24/25] xfs: enable RT data checksums
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (22 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 23/25] xfs: add an experimental feature warning for RT data checksums Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
2026-11-04 8:57 ` [PATCH 25/25] xfs: add a RT checksum log item Christoph Hellwig
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel, Darrick J. Wong
All mandatory pieces are in place now, allow mounting.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: "Darrick J. Wong" <djwong@kernel.org>
---
fs/xfs/libxfs/xfs_format.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/xfs/libxfs/xfs_format.h b/fs/xfs/libxfs/xfs_format.h
index 3d794a2eb497..4ed938d710ac 100644
--- a/fs/xfs/libxfs/xfs_format.h
+++ b/fs/xfs/libxfs/xfs_format.h
@@ -417,7 +417,8 @@ xfs_sb_has_ro_compat_feature(
XFS_SB_FEAT_INCOMPAT_PARENT | \
XFS_SB_FEAT_INCOMPAT_METADIR | \
XFS_SB_FEAT_INCOMPAT_ZONED | \
- XFS_SB_FEAT_INCOMPAT_ZONE_GAPS)
+ XFS_SB_FEAT_INCOMPAT_ZONE_GAPS | \
+ XFS_SB_FEAT_INCOMPAT_RTCSUM)
#define XFS_SB_FEAT_INCOMPAT_UNKNOWN ~XFS_SB_FEAT_INCOMPAT_ALL
static inline bool
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread* [PATCH 25/25] xfs: add a RT checksum log item
2026-11-04 8:57 support for RT data checksums v2 Christoph Hellwig
` (23 preceding siblings ...)
2026-11-04 8:57 ` [PATCH 24/25] xfs: enable " Christoph Hellwig
@ 2026-11-04 8:57 ` Christoph Hellwig
24 siblings, 0 replies; 34+ messages in thread
From: Christoph Hellwig @ 2026-11-04 8:57 UTC (permalink / raw)
To: Carlos Maiolino; +Cc: linux-xfs, linux-fsdevel
Use a one-shot log item to log RT checksum log items instead of the
buffer log item, and just update the buffer as an ordered buffer.
This reduces the amount of reallocs of the shadow buffers compared
to the buffer item, with the downside of needing more code and creating
more overhead for lots of small non-data integrity writes.
Still needs full benchmarking.
Signed-off-by: Christoph Hellwig <hch@lst.de>
---
fs/xfs/Makefile | 1 +
fs/xfs/libxfs/xfs_log_format.h | 12 +-
fs/xfs/libxfs/xfs_log_recover.h | 1 +
fs/xfs/libxfs/xfs_ondisk.h | 1 +
fs/xfs/libxfs/xfs_trans_resv.c | 9 +-
fs/xfs/xfs_log.h | 3 +-
fs/xfs/xfs_log_recover.c | 3 +
fs/xfs/xfs_rtcsum.c | 4 +-
fs/xfs/xfs_rtcsum.h | 7 +
fs/xfs/xfs_rtcsum_item.c | 218 ++++++++++++++++++++++++++++++++
fs/xfs/xfs_super.c | 7 +
11 files changed, 259 insertions(+), 7 deletions(-)
create mode 100644 fs/xfs/xfs_rtcsum_item.c
diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 0d57bf0701ec..1891fc3edf39 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -143,6 +143,7 @@ xfs-$(CONFIG_XFS_QUOTA) += xfs_dquot.o \
# xfs_rtbitmap is shared with libxfs
xfs-$(CONFIG_XFS_RT) += xfs_rtalloc.o \
xfs_rtcsum.o \
+ xfs_rtcsum_item.o \
xfs_zone_alloc.o \
xfs_zone_gc.o \
xfs_zone_info.o \
diff --git a/fs/xfs/libxfs/xfs_log_format.h b/fs/xfs/libxfs/xfs_log_format.h
index c6f0edb4bf0a..ae6476a01b11 100644
--- a/fs/xfs/libxfs/xfs_log_format.h
+++ b/fs/xfs/libxfs/xfs_log_format.h
@@ -249,6 +249,7 @@ struct xfs_trans_header {
#define XFS_LI_RUD_RT 0x124d /* realtime rmap update done */
#define XFS_LI_CUI_RT 0x124e /* realtime refcount update intent */
#define XFS_LI_CUD_RT 0x124f /* realtime refcount update done */
+#define XFS_LI_RTCSUM 0x1250 /* realtime checksum update */
#define XFS_LI_TYPE_DESC \
{ XFS_LI_EFI, "XFS_LI_EFI" }, \
@@ -274,7 +275,8 @@ struct xfs_trans_header {
{ XFS_LI_RUI_RT, "XFS_LI_RUI_RT" }, \
{ XFS_LI_RUD_RT, "XFS_LI_RUD_RT" }, \
{ XFS_LI_CUI_RT, "XFS_LI_CUI_RT" }, \
- { XFS_LI_CUD_RT, "XFS_LI_CUD_RT" }
+ { XFS_LI_CUD_RT, "XFS_LI_CUD_RT" }, \
+ { XFS_LI_RTCSUM, "XFS_LI_RTCSUM" }
/*
* Inode Log Item Format definitions.
@@ -1082,4 +1084,12 @@ struct xfs_attrd_log_format {
uint64_t alfd_alf_id; /* id of corresponding attri */
};
+#define XFS_RTCSUM_NR_VECS 1
+struct xfs_rtcsum_log_format {
+ uint16_t rlf_type; /* type of log format structure */
+ uint16_t rlf_size; /* size of log format structure */
+ uint32_t rlf_buf_offset; /* offset into the rtcsum buf */
+ uint64_t rlf_buf_daddr; /* daddr of the rtcsum buf */
+};
+
#endif /* __XFS_LOG_FORMAT_H__ */
diff --git a/fs/xfs/libxfs/xfs_log_recover.h b/fs/xfs/libxfs/xfs_log_recover.h
index 9e712e62369c..67ec495f2ff3 100644
--- a/fs/xfs/libxfs/xfs_log_recover.h
+++ b/fs/xfs/libxfs/xfs_log_recover.h
@@ -83,6 +83,7 @@ extern const struct xlog_recover_item_ops xlog_rtrui_item_ops;
extern const struct xlog_recover_item_ops xlog_rtrud_item_ops;
extern const struct xlog_recover_item_ops xlog_rtcui_item_ops;
extern const struct xlog_recover_item_ops xlog_rtcud_item_ops;
+extern const struct xlog_recover_item_ops xlog_rtcsum_item_ops;
/*
* Macros, structures, prototypes for internal log manager use.
diff --git a/fs/xfs/libxfs/xfs_ondisk.h b/fs/xfs/libxfs/xfs_ondisk.h
index 17ab9366b3b9..cbfdd865a00b 100644
--- a/fs/xfs/libxfs/xfs_ondisk.h
+++ b/fs/xfs/libxfs/xfs_ondisk.h
@@ -163,6 +163,7 @@ xfs_check_ondisk_structs(void)
XFS_CHECK_STRUCT_SIZE(struct xfs_cud_log_format, 16);
XFS_CHECK_STRUCT_SIZE(struct xfs_rui_log_format, 16);
XFS_CHECK_STRUCT_SIZE(struct xfs_rud_log_format, 16);
+ XFS_CHECK_STRUCT_SIZE(struct xfs_rtcsum_log_format, 16);
XFS_CHECK_STRUCT_SIZE(struct xfs_map_extent, 32);
XFS_CHECK_STRUCT_SIZE(struct xfs_phys_extent, 16);
XFS_CHECK_STRUCT_SIZE(struct xlog_rec_header, 512);
diff --git a/fs/xfs/libxfs/xfs_trans_resv.c b/fs/xfs/libxfs/xfs_trans_resv.c
index 25f7581ea90c..f9c811db0761 100644
--- a/fs/xfs/libxfs/xfs_trans_resv.c
+++ b/fs/xfs/libxfs/xfs_trans_resv.c
@@ -1248,10 +1248,13 @@ xfs_calc_csum_reservation(
unsigned int csum_len)
{
unsigned int payload_size = xfs_rtcsum_payload_size(mp);
+ unsigned int nbufs =
+ howmany(csum_len + payload_size, payload_size);
- return xfs_calc_buf_res(
- howmany(csum_len + payload_size, payload_size),
- mp->m_rtcsum_bsize);
+ return nbufs * (sizeof(struct xlog_op_header) +
+ sizeof(struct xfs_rtcsum_log_format) +
+ sizeof(struct xlog_op_header) +
+ payload_size);
}
/*
diff --git a/fs/xfs/xfs_log.h b/fs/xfs/xfs_log.h
index f715695e8fcb..ff6df9d51046 100644
--- a/fs/xfs/xfs_log.h
+++ b/fs/xfs/xfs_log.h
@@ -44,7 +44,8 @@ struct xfs_cil_ctx;
#define XLOG_REG_TYPE_XMD_FORMAT 32
#define XLOG_REG_TYPE_ATTR_NEWNAME 33
#define XLOG_REG_TYPE_ATTR_NEWVALUE 34
-#define XLOG_REG_TYPE_MAX 34
+#define XLOG_REG_TYPE_RTCSUM_FORMAT 35
+#define XLOG_REG_TYPE_MAX 36
#define XFS_LOG_VEC_ORDERED (-1)
diff --git a/fs/xfs/xfs_log_recover.c b/fs/xfs/xfs_log_recover.c
index 457617979668..d812ee5e6752 100644
--- a/fs/xfs/xfs_log_recover.c
+++ b/fs/xfs/xfs_log_recover.c
@@ -1825,6 +1825,9 @@ static const struct xlog_recover_item_ops *xlog_recover_item_ops[] = {
&xlog_rtrud_item_ops,
&xlog_rtcui_item_ops,
&xlog_rtcud_item_ops,
+#ifdef CONFIG_XFS_RT
+ &xlog_rtcsum_item_ops,
+#endif
};
static const struct xlog_recover_item_ops *
diff --git a/fs/xfs/xfs_rtcsum.c b/fs/xfs/xfs_rtcsum.c
index 93e9792207c6..c6daa32c71c7 100644
--- a/fs/xfs/xfs_rtcsum.c
+++ b/fs/xfs/xfs_rtcsum.c
@@ -237,8 +237,8 @@ xfs_rtcsum_log_one(
xfs_rtcsum_len_to_extlen(mp, mp->m_rtcsum_bsize - boff));
len = xfs_extlen_to_rtcsum_len(mp, *nr);
memcpy(bp->b_addr + boff, csum, len);
-
- xfs_trans_log_buf(tp, bp, boff, boff + len - 1);
+ xfs_trans_ordered_buf(tp, bp);
+ xfs_rtcsum_item_log(tp, xfs_buf_daddr(bp), boff, *nr, csum);
return 0;
}
diff --git a/fs/xfs/xfs_rtcsum.h b/fs/xfs/xfs_rtcsum.h
index bd16f5a99451..6cab812c9d9b 100644
--- a/fs/xfs/xfs_rtcsum.h
+++ b/fs/xfs/xfs_rtcsum.h
@@ -15,9 +15,16 @@ int xfs_rtcsum_read_async(struct xfs_mount *mp, xfs_rtblock_t fsbno,
int xfs_rtcsum_log(struct xfs_open_zone *oz, xfs_daddr_t daddr,
unsigned int data_len, const void *csum);
+void xfs_rtcsum_item_log(struct xfs_trans *tp, xfs_daddr_t buf_daddr,
+ unsigned int buf_offset, xfs_extlen_t count,
+ const void *csum_buf);
+
int xfs_rtcsum_open_zone(struct xfs_open_zone *oz);
void xfs_rtcsum_free_zone(struct xfs_open_zone *oz);
int xfs_rtcsum_mount(struct xfs_mount *mp);
+int __init xfs_rtcsum_init(void);
+void xfs_rtcsum_exit(void);
+
#endif /* _XFS_RTCSUM_H */
diff --git a/fs/xfs/xfs_rtcsum_item.c b/fs/xfs/xfs_rtcsum_item.c
new file mode 100644
index 000000000000..feee39846d32
--- /dev/null
+++ b/fs/xfs/xfs_rtcsum_item.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2026 Christoph Hellwig.
+ */
+#include "xfs_platform.h"
+#include "xfs_fs.h"
+#include "xfs_shared.h"
+#include "xfs_format.h"
+#include "xfs_log_format.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_trans.h"
+#include "xfs_trans_priv.h"
+#include "xfs_log.h"
+#include "xfs_log_priv.h"
+#include "xfs_log_recover.h"
+#include "xfs_rtcsum.h"
+
+struct xfs_rtcsum_item {
+ struct xfs_log_item ri_item;
+ struct xfs_rtcsum_log_format ri_format;
+
+ /*
+ * ri_csum_buf is used to pass the callers csum buffer to
+ * xfs_rtcsum_item_format, and is not valid after transaction
+ * commit.
+ */
+ const void *ri_csum_buf;
+ xfs_extlen_t ri_count;
+};
+
+static struct kmem_cache *xfs_rtcsum_item_cache;
+
+static inline struct xfs_rtcsum_item *RTCSUM_ITEM(struct xfs_log_item *lip)
+{
+ return container_of(lip, struct xfs_rtcsum_item, ri_item);
+}
+
+static inline unsigned int
+xfs_rtcsum_item_buf_size(
+ struct xfs_rtcsum_item *rip)
+{
+ return xfs_extlen_to_rtcsum_len(rip->ri_item.li_log->l_mp,
+ rip->ri_count);
+}
+
+static void
+xfs_rtcsum_item_size(
+ struct xfs_log_item *lip,
+ int *nvecs,
+ int *nbytes)
+{
+ *nvecs += XFS_RTCSUM_NR_VECS;
+ *nbytes += sizeof(struct xfs_rtcsum_log_format);
+ *nbytes += xfs_rtcsum_item_buf_size(RTCSUM_ITEM(lip));
+}
+
+/*
+ * Format both the log format and the array of checksums into the same
+ * region to reduce the overhead for small writes.
+ */
+static void
+xfs_rtcsum_item_format(
+ struct xfs_log_item *lip,
+ struct xlog_format_buf *lfb)
+{
+ struct xfs_rtcsum_item *rip = RTCSUM_ITEM(lip);
+ void *dst, *p;
+
+ dst = p = xlog_format_start(lfb, XLOG_REG_TYPE_RTCSUM_FORMAT);
+ memcpy(p, &rip->ri_format, sizeof(rip->ri_format));
+ p += sizeof(rip->ri_format);
+ memcpy(p, rip->ri_csum_buf, xfs_rtcsum_item_buf_size(rip));
+ p += xfs_rtcsum_item_buf_size(rip);
+ xlog_format_commit(lfb, p - dst);
+}
+
+static void
+xfs_rtcsum_item_release(
+ struct xfs_log_item *lip)
+{
+ kvfree(RTCSUM_ITEM(lip)->ri_item.li_lv_shadow);
+ kmem_cache_free(xfs_rtcsum_item_cache, RTCSUM_ITEM(lip));
+}
+
+static const struct xfs_item_ops xfs_rtcsum_item_ops = {
+ .flags = XFS_ITEM_RELEASE_WHEN_COMMITTED,
+ .iop_size = xfs_rtcsum_item_size,
+ .iop_format = xfs_rtcsum_item_format,
+ .iop_release = xfs_rtcsum_item_release,
+};
+
+void
+xfs_rtcsum_item_log(
+ struct xfs_trans *tp,
+ xfs_daddr_t buf_daddr,
+ unsigned int buf_offset,
+ xfs_extlen_t count,
+ const void *csum_buf)
+{
+ struct xfs_rtcsum_item *rip;
+
+ rip = kmem_cache_zalloc(xfs_rtcsum_item_cache,
+ GFP_KERNEL | __GFP_NOFAIL);
+ xfs_log_item_init(tp->t_mountp, &rip->ri_item, XFS_LI_RTCSUM,
+ &xfs_rtcsum_item_ops);
+ rip->ri_count = count;
+ rip->ri_csum_buf = csum_buf;
+ rip->ri_format.rlf_type = XFS_LI_RTCSUM;
+ rip->ri_format.rlf_size = XFS_RTCSUM_NR_VECS;
+ rip->ri_format.rlf_buf_daddr = buf_daddr;
+ rip->ri_format.rlf_buf_offset = buf_offset;
+
+ xfs_trans_add_item(tp, &rip->ri_item);
+ tp->t_flags |= XFS_TRANS_DIRTY;
+ set_bit(XFS_LI_DIRTY, &rip->ri_item.li_flags);
+}
+
+/*
+ * Checksum buffers must be recovered before inode items can reference the data
+ * covered by the checksums.
+ */
+static enum xlog_recover_reorder
+xlog_recover_rtcsum_reorder(
+ struct xlog_recover_item *item)
+{
+ return XLOG_REORDER_BUFFER_LIST;
+}
+
+static void
+xlog_recover_rtcsum_ra_pass2(
+ struct xlog *log,
+ struct xlog_recover_item *item)
+{
+ struct xfs_rtcsum_log_format *rlf = item->ri_buf[0].iov_base;
+
+ xlog_buf_readahead(log, rlf->rlf_buf_daddr,
+ BTOBB(log->l_mp->m_rtcsum_bsize), NULL);
+}
+
+static int
+xlog_recover_rtcsum_commit_pass2(
+ struct xlog *log,
+ struct list_head *buffer_list,
+ struct xlog_recover_item *item,
+ xfs_lsn_t lsn)
+{
+ struct xfs_mount *mp = log->l_mp;
+ struct xfs_rtcsum_log_format *rlf = item->ri_buf[0].iov_base;
+ void *csum = rlf + 1;
+ unsigned int buf_len;
+ struct xfs_buf *bp;
+ int error;
+
+ if (rlf->rlf_type != XFS_LI_RTCSUM) {
+ xfs_warn(mp, "%s: bad type", __func__);
+ return -EINVAL;
+ }
+ if (rlf->rlf_size != XFS_RTCSUM_NR_VECS) {
+ xfs_warn(mp, "%s: bad number of iovecs: %u",
+ __func__, rlf->rlf_size);
+ return -EINVAL;
+ }
+
+ if (item->ri_buf[0].iov_len < sizeof(*rlf)) {
+ xfs_warn(mp, "%s: bad len: %zu",
+ __func__, item->ri_buf[0].iov_len);
+ return -EINVAL;
+ }
+
+ buf_len = item->ri_buf[0].iov_len - sizeof(*rlf);
+ if (buf_len == 0 ||
+ buf_len % (1u << mp->m_rtcsum_shift) ||
+ buf_len > mp->m_rtcsum_bsize) {
+ xfs_warn(mp, "%s: bad rtcsum buf size: %u",
+ __func__, buf_len);
+ return -EINVAL;
+ }
+
+ if (rlf->rlf_buf_offset > mp->m_rtcsum_bsize ||
+ rlf->rlf_buf_offset > mp->m_rtcsum_bsize - buf_len) {
+ xfs_warn(mp, "%s: bad rtcsum buf offset: %u",
+ __func__, buf_len);
+ return -EINVAL;
+ }
+
+ error = xfs_buf_read(mp->m_ddev_targp, rlf->rlf_buf_daddr,
+ BTOBB(mp->m_rtcsum_bsize), 0, &bp, &xfs_rtcsum_buf_ops);
+ if (error)
+ return error;
+ memcpy(bp->b_addr + rlf->rlf_buf_offset, csum, buf_len);
+ xfs_buf_set_uptodate(bp);
+ xfs_buf_delwri_queue(bp, buffer_list);
+ xfs_buf_relse(bp);
+ return 0;
+}
+
+const struct xlog_recover_item_ops xlog_rtcsum_item_ops = {
+ .item_type = XFS_LI_RTCSUM,
+ .reorder = xlog_recover_rtcsum_reorder,
+ .ra_pass2 = xlog_recover_rtcsum_ra_pass2,
+ .commit_pass2 = xlog_recover_rtcsum_commit_pass2,
+};
+
+int __init xfs_rtcsum_init(void)
+{
+ xfs_rtcsum_item_cache = kmem_cache_create("xfs_rtcsum_item",
+ sizeof(struct xfs_rtcsum_item), 0, 0, NULL);
+ if (!xfs_rtcsum_item_cache)
+ return -ENOMEM;
+ return 0;
+}
+
+void xfs_rtcsum_exit(void)
+{
+ kmem_cache_destroy(xfs_rtcsum_item_cache);
+}
diff --git a/fs/xfs/xfs_super.c b/fs/xfs/xfs_super.c
index fc99167938e5..960f1e9693f6 100644
--- a/fs/xfs/xfs_super.c
+++ b/fs/xfs/xfs_super.c
@@ -2530,8 +2530,13 @@ xfs_init_caches(void)
if (!xfs_parent_args_cache)
goto out_destroy_xmi_cache;
+ if (IS_ENABLED(CONFIG_XFS_RT) && xfs_rtcsum_init() < 0)
+ goto out_destroy_parent_args_cache;
+
return 0;
+ out_destroy_parent_args_cache:
+ kmem_cache_destroy(xfs_parent_args_cache);
out_destroy_xmi_cache:
kmem_cache_destroy(xfs_xmi_cache);
out_destroy_xmd_cache:
@@ -2594,6 +2599,8 @@ xfs_destroy_caches(void)
* destroy caches.
*/
rcu_barrier();
+ if (IS_ENABLED(CONFIG_XFS_RT))
+ xfs_rtcsum_exit();
kmem_cache_destroy(xfs_parent_args_cache);
kmem_cache_destroy(xfs_xmd_cache);
kmem_cache_destroy(xfs_xmi_cache);
--
2.53.0
^ permalink raw reply related [flat|nested] 34+ messages in thread