diff --git a/fs/xfs/Makefile b/fs/xfs/Makefile
index 7afa51e41427..aa8749d640e7 100644
--- a/fs/xfs/Makefile
+++ b/fs/xfs/Makefile
@@ -73,6 +73,7 @@ xfs-y				+= xfs_aops.o \
 				   xfs_bmap_util.o \
 				   xfs_bio_io.o \
 				   xfs_buf.o \
+				   xfs_data_csum.o \
 				   xfs_dahash_test.o \
 				   xfs_dir2_readdir.o \
 				   xfs_discard.o \
diff --git a/fs/xfs/xfs_aops.c b/fs/xfs/xfs_aops.c
index 3e42a684cce1..291f5d4dbce6 100644
--- a/fs/xfs/xfs_aops.c
+++ b/fs/xfs/xfs_aops.c
@@ -19,6 +19,7 @@
 #include "xfs_reflink.h"
 #include "xfs_errortag.h"
 #include "xfs_error.h"
+#include "xfs_data_csum.h"
 
 struct xfs_writepage_ctx {
 	struct iomap_writepage_ctx ctx;
@@ -122,6 +123,11 @@ xfs_end_ioend(
 		goto done;
 	}
 
+	if (bio_op(&ioend->io_bio) == REQ_OP_READ) {
+		error = xfs_data_csum_verify(ioend);
+		goto done;
+	}
+
 	/*
 	 * Success: commit the COW or unwritten blocks if needed.
 	 */
@@ -175,7 +181,7 @@ xfs_end_io(
 	}
 }
 
-STATIC void
+void
 xfs_end_bio(
 	struct bio		*bio)
 {
@@ -417,6 +423,8 @@ xfs_submit_ioend(
 
 	memalloc_nofs_restore(nofs_flag);
 
+	xfs_data_csum_generate(&ioend->io_bio);
+
 	/* send ioends that might require a transaction to the completion wq */
 	if (xfs_ioend_is_append(ioend) ||
 	    (ioend->io_flags & (IOMAP_IOEND_UNWRITTEN | IOMAP_IOEND_SHARED)))
@@ -517,19 +525,34 @@ xfs_vm_bmap(
 	return iomap_bmap(mapping, block, &xfs_read_iomap_ops);
 }
 
+static void xfs_buffered_read_submit_io(struct inode *inode,
+		struct bio *bio, loff_t file_offset)
+{
+	xfs_data_csum_alloc(bio);
+	iomap_init_ioend(inode, bio, file_offset, 0);
+	bio->bi_end_io = xfs_end_bio;
+	submit_bio(bio);
+}
+
+static const struct iomap_read_folio_ops xfs_iomap_read_ops = {
+	.bio_set	= &iomap_ioend_bioset,
+	.submit_io	= xfs_buffered_read_submit_io,
+};
+
 STATIC int
 xfs_vm_read_folio(
 	struct file		*unused,
 	struct folio		*folio)
 {
-	return iomap_read_folio(folio, &xfs_read_iomap_ops, NULL);
+	return iomap_read_folio(folio, &xfs_read_iomap_ops,
+			&xfs_iomap_read_ops);
 }
 
 STATIC void
 xfs_vm_readahead(
 	struct readahead_control	*rac)
 {
-	iomap_readahead(rac, &xfs_read_iomap_ops, NULL);
+	iomap_readahead(rac, &xfs_read_iomap_ops, &xfs_iomap_read_ops);
 }
 
 static int
diff --git a/fs/xfs/xfs_aops.h b/fs/xfs/xfs_aops.h
index e0bd68419764..efed1ab59dbf 100644
--- a/fs/xfs/xfs_aops.h
+++ b/fs/xfs/xfs_aops.h
@@ -10,5 +10,6 @@ extern const struct address_space_operations xfs_address_space_operations;
 extern const struct address_space_operations xfs_dax_aops;
 
 int	xfs_setfilesize(struct xfs_inode *ip, xfs_off_t offset, size_t size);
+void	xfs_end_bio(struct bio *bio);
 
 #endif /* __XFS_AOPS_H__ */
diff --git a/fs/xfs/xfs_data_csum.c b/fs/xfs/xfs_data_csum.c
new file mode 100644
index 000000000000..d9d3620654b1
--- /dev/null
+++ b/fs/xfs/xfs_data_csum.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2022-2025 Christoph Hellwig.
+ */
+#include "xfs.h"
+#include "xfs_format.h"
+#include "xfs_shared.h"
+#include "xfs_trans_resv.h"
+#include "xfs_mount.h"
+#include "xfs_inode.h"
+#include "xfs_cksum.h"
+#include "xfs_data_csum.h"
+#include <linux/iomap.h>
+#include <linux/blk-integrity.h>
+#include <linux/bio-integrity.h>
+
+void *
+xfs_data_csum_alloc(
+	struct bio		*bio)
+{
+	struct blk_integrity	*bi = blk_get_integrity(bio->bi_bdev->bd_disk);
+	struct bio_integrity_payload *bip;
+	unsigned int		buf_size;
+	void			*buf;
+
+	if (!bi)
+		return NULL;
+
+	buf_size = bio_integrity_bytes(bi, bio_sectors(bio));
+	/* XXX: this needs proper mempools */
+	/* XXX: needs (partial) zeroing if tuple_size > csum_size */
+	buf = kmalloc(buf_size, GFP_NOFS | __GFP_NOFAIL);
+	bip = bio_integrity_alloc(bio, GFP_NOFS | __GFP_NOFAIL, 1);
+	if (!bio_integrity_add_page(bio, virt_to_page(buf), buf_size,
+			offset_in_page(buf)))
+		WARN_ON_ONCE(1);
+
+	if (bi->csum_type) {
+		if (bi->csum_type == BLK_INTEGRITY_CSUM_IP)
+			bip->bip_flags |= BIP_IP_CHECKSUM;
+		bip->bip_flags |= BIP_CHECK_GUARD;
+	}
+	if (bi->flags & BLK_INTEGRITY_REF_TAG)
+		bip->bip_flags |= BIP_CHECK_REFTAG;
+	bip_set_seed(bip, bio->bi_iter.bi_sector);
+
+	return buf;
+}
+
+void
+xfs_data_csum_generate(
+	struct bio		*bio)
+{
+	struct blk_integrity	*bi = blk_get_integrity(bio->bi_bdev->bd_disk);
+
+	if (!bi || !bi->csum_type)
+		return;
+
+	xfs_data_csum_alloc(bio);
+	blk_integrity_generate(bio);
+}
+
+int
+xfs_data_csum_verify(
+	struct iomap_ioend	*ioend)
+{
+	struct bio		*bio = &ioend->io_bio;
+	struct blk_integrity	*bi = blk_get_integrity(bio->bi_bdev->bd_disk);
+
+	if (!bi || !bi->csum_type)
+		return 0;
+	return blk_integrity_verify_all(bio, ioend->io_sector);
+}
diff --git a/fs/xfs/xfs_data_csum.h b/fs/xfs/xfs_data_csum.h
new file mode 100644
index 000000000000..f32215e8f46c
--- /dev/null
+++ b/fs/xfs/xfs_data_csum.h
@@ -0,0 +1,7 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+struct iomap_ioend;
+
+void *xfs_data_csum_alloc(struct bio *bio);
+void xfs_data_csum_generate(struct bio *bio);
+int xfs_data_csum_verify(struct iomap_ioend *ioend);
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index f7a7d89c345e..0d64c54017f0 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -25,6 +25,8 @@
 #include "xfs_iomap.h"
 #include "xfs_reflink.h"
 #include "xfs_file.h"
+#include "xfs_data_csum.h"
+#include "xfs_aops.h"
 
 #include <linux/dax.h>
 #include <linux/falloc.h>
@@ -227,6 +229,20 @@ xfs_ilock_iocb_for_write(
 	return 0;
 }
 
+static void xfs_dio_read_submit_io(const struct iomap_iter *iter,
+		struct bio *bio, loff_t file_offset)
+{
+	xfs_data_csum_alloc(bio);
+	iomap_init_ioend(iter->inode, bio, file_offset, IOMAP_IOEND_DIRECT);
+	bio->bi_end_io = xfs_end_bio;
+	submit_bio(bio);
+}
+
+static const struct iomap_dio_ops xfs_dio_read_ops = {
+	.bio_set	= &iomap_ioend_bioset,
+	.submit_io	= xfs_dio_read_submit_io,
+};
+
 STATIC ssize_t
 xfs_file_dio_read(
 	struct kiocb		*iocb,
@@ -245,7 +261,8 @@ xfs_file_dio_read(
 	ret = xfs_ilock_iocb(iocb, XFS_IOLOCK_SHARED);
 	if (ret)
 		return ret;
-	ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops, NULL, 0, NULL, 0);
+	ret = iomap_dio_rw(iocb, to, &xfs_read_iomap_ops, &xfs_dio_read_ops, 0,
+			NULL, 0);
 	xfs_iunlock(ip, XFS_IOLOCK_SHARED);
 
 	return ret;
@@ -578,8 +595,16 @@ xfs_dio_write_end_io(
 	return error;
 }
 
+static void xfs_dio_write_submit_io(const struct iomap_iter *iter,
+		struct bio *bio, loff_t file_offset)
+{
+	xfs_data_csum_generate(bio);
+	submit_bio(bio);
+}
+
 static const struct iomap_dio_ops xfs_dio_write_ops = {
 	.end_io		= xfs_dio_write_end_io,
+	.submit_io	= xfs_dio_write_submit_io,
 };
 
 /*
diff --git a/fs/xfs/xfs_inode.h b/fs/xfs/xfs_inode.h
index c08093a65352..ff346bbe454c 100644
--- a/fs/xfs/xfs_inode.h
+++ b/fs/xfs/xfs_inode.h
@@ -609,10 +609,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))
-		mapping_set_stable_writes(VFS_I(ip)->i_mapping);
-	else
-		mapping_clear_stable_writes(VFS_I(ip)->i_mapping);
+	/* XXX: unconditional for now */
+	mapping_set_stable_writes(VFS_I(ip)->i_mapping);
 }
 
 /*
