]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
btrfs: make read verification handle bs > ps cases without large folios
authorQu Wenruo <wqu@suse.com>
Mon, 10 Nov 2025 22:42:00 +0000 (09:12 +1030)
committerDavid Sterba <dsterba@suse.com>
Mon, 24 Nov 2025 21:42:24 +0000 (22:42 +0100)
The current read verification is also relying on large folios to support
bs > ps cases, but that introduced quite some limits.

To enhance read-repair to support bs > ps without large folios:

- Make btrfs_data_csum_ok() to accept an array of paddrs
  Which can pass the paddrs[] direct into
  btrfs_calculate_block_csum_pages().

- Make repair_one_sector() to accept an array of paddrs
  So that it can submit a repair bio backed by regular pages, not only
  large folios.
  This requires us to allocate more slots at bio allocation time though.

  Also since the caller may have only partially advanced the saved_iter
  for bs > ps cases, we can not directly trust the logical bytenr from
  saved_iter (can be unaligned), thus a manual round down is necessary
  for the logical bytenr.

- Make btrfs_check_read_bio() to build an array of paddrs
  The tricky part is that we can only call btrfs_data_csum_ok() after
  all involved pages are assembled.

  This means at the call time of btrfs_check_read_bio(), our offset
  inside the bio is already at the end of the fs block.
  Thus we must re-calculate @bio_offset for btrfs_data_csum_ok() and
  repair_one_sector().

Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/bio.c
fs/btrfs/btrfs_inode.h
fs/btrfs/inode.c

index 383ea6731b35e27c0493a948d155ca130af6c73a..fcd28eb682471e57bc83af99fa6569ce9c5864ab 100644 (file)
@@ -171,7 +171,6 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
        struct btrfs_failed_bio *fbio = repair_bbio->private;
        struct btrfs_inode *inode = repair_bbio->inode;
        struct btrfs_fs_info *fs_info = inode->root->fs_info;
-       struct bio_vec *bv = bio_first_bvec_all(&repair_bbio->bio);
        /*
         * We can not move forward the saved_iter, as it will be later
         * utilized by repair_bbio again.
@@ -188,8 +187,14 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
        /* Repair bbio should be eaxctly one block sized. */
        ASSERT(repair_bbio->saved_iter.bi_size == fs_info->sectorsize);
 
+       btrfs_bio_for_each_block(paddr, &repair_bbio->bio, &saved_iter, step) {
+               ASSERT(slot < nr_steps);
+               paddrs[slot] = paddr;
+               slot++;
+       }
+
        if (repair_bbio->bio.bi_status ||
-           !btrfs_data_csum_ok(repair_bbio, dev, 0, bvec_phys(bv))) {
+           !btrfs_data_csum_ok(repair_bbio, dev, 0, paddrs)) {
                bio_reset(&repair_bbio->bio, NULL, REQ_OP_READ);
                repair_bbio->bio.bi_iter = repair_bbio->saved_iter;
 
@@ -204,12 +209,6 @@ static void btrfs_end_repair_bio(struct btrfs_bio *repair_bbio,
                return;
        }
 
-       btrfs_bio_for_each_block(paddr, &repair_bbio->bio, &saved_iter, step) {
-               ASSERT(slot < nr_steps);
-               paddrs[slot] = paddr;
-               slot++;
-       }
-
        do {
                mirror = prev_repair_mirror(fbio, mirror);
                btrfs_repair_io_failure(fs_info, btrfs_ino(inode),
@@ -231,21 +230,25 @@ done:
  */
 static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
                                                  u32 bio_offset,
-                                                 phys_addr_t paddr,
+                                                 phys_addr_t paddrs[],
                                                  struct btrfs_failed_bio *fbio)
 {
        struct btrfs_inode *inode = failed_bbio->inode;
        struct btrfs_fs_info *fs_info = inode->root->fs_info;
-       struct folio *folio = page_folio(phys_to_page(paddr));
        const u32 sectorsize = fs_info->sectorsize;
-       const u32 foff = offset_in_folio(folio, paddr);
-       const u64 logical = (failed_bbio->saved_iter.bi_sector << SECTOR_SHIFT);
+       const u32 step = min(fs_info->sectorsize, PAGE_SIZE);
+       const u32 nr_steps = sectorsize / step;
+       /*
+        * For bs > ps cases, the saved_iter can be partially moved forward.
+        * In that case we should round it down to the block boundary.
+        */
+       const u64 logical = round_down(failed_bbio->saved_iter.bi_sector << SECTOR_SHIFT,
+                                      sectorsize);
        struct btrfs_bio *repair_bbio;
        struct bio *repair_bio;
        int num_copies;
        int mirror;
 
-       ASSERT(foff + sectorsize <= folio_size(folio));
        btrfs_debug(fs_info, "repair read error: read error at %llu",
                    failed_bbio->file_offset + bio_offset);
 
@@ -265,10 +268,18 @@ static struct btrfs_failed_bio *repair_one_sector(struct btrfs_bio *failed_bbio,
 
        atomic_inc(&fbio->repair_count);
 
-       repair_bio = bio_alloc_bioset(NULL, 1, REQ_OP_READ, GFP_NOFS,
+       repair_bio = bio_alloc_bioset(NULL, nr_steps, REQ_OP_READ, GFP_NOFS,
                                      &btrfs_repair_bioset);
-       repair_bio->bi_iter.bi_sector = failed_bbio->saved_iter.bi_sector;
-       bio_add_folio_nofail(repair_bio, folio, sectorsize, foff);
+       repair_bio->bi_iter.bi_sector = logical >> SECTOR_SHIFT;
+       for (int i = 0; i < nr_steps; i++) {
+               int ret;
+
+               ASSERT(offset_in_page(paddrs[i]) + step <= PAGE_SIZE);
+
+               ret = bio_add_page(repair_bio, phys_to_page(paddrs[i]), step,
+                                  offset_in_page(paddrs[i]));
+               ASSERT(ret == step);
+       }
 
        repair_bbio = btrfs_bio(repair_bio);
        btrfs_bio_init(repair_bbio, failed_bbio->inode, failed_bbio->file_offset + bio_offset,
@@ -284,10 +295,13 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de
 {
        struct btrfs_inode *inode = bbio->inode;
        struct btrfs_fs_info *fs_info = inode->root->fs_info;
-       u32 sectorsize = fs_info->sectorsize;
+       const u32 sectorsize = fs_info->sectorsize;
+       const u32 step = min(sectorsize, PAGE_SIZE);
+       const u32 nr_steps = sectorsize / step;
        struct bvec_iter *iter = &bbio->saved_iter;
        blk_status_t status = bbio->bio.bi_status;
        struct btrfs_failed_bio *fbio = NULL;
+       phys_addr_t paddrs[BTRFS_MAX_BLOCKSIZE / PAGE_SIZE];
        phys_addr_t paddr;
        u32 offset = 0;
 
@@ -306,10 +320,16 @@ static void btrfs_check_read_bio(struct btrfs_bio *bbio, struct btrfs_device *de
        /* Clear the I/O error. A failed repair will reset it. */
        bbio->bio.bi_status = BLK_STS_OK;
 
-       btrfs_bio_for_each_block(paddr, &bbio->bio, iter, fs_info->sectorsize) {
-               if (status || !btrfs_data_csum_ok(bbio, dev, offset, paddr))
-                       fbio = repair_one_sector(bbio, offset, paddr, fbio);
-               offset += sectorsize;
+       btrfs_bio_for_each_block(paddr, &bbio->bio, iter, step) {
+               paddrs[(offset / step) % nr_steps] = paddr;
+               offset += step;
+
+               if (IS_ALIGNED(offset, sectorsize)) {
+                       if (status ||
+                           !btrfs_data_csum_ok(bbio, dev, offset - sectorsize, paddrs))
+                               fbio = repair_one_sector(bbio, offset - sectorsize,
+                                                        paddrs, fbio);
+               }
        }
        if (bbio->csum != bbio->csum_inline)
                kvfree(bbio->csum);
index 00671b72407901666ce50eb75015e8d0818baede..73602ee8de3f2e3e5ef8b87feaceb1e1ee4c9bce 100644 (file)
@@ -550,7 +550,7 @@ void btrfs_calculate_block_csum_pages(struct btrfs_fs_info *fs_info,
 int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8 *csum,
                           const u8 * const csum_expected);
 bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev,
-                       u32 bio_offset, phys_addr_t paddr);
+                       u32 bio_offset, const phys_addr_t paddrs[]);
 noinline int can_nocow_extent(struct btrfs_inode *inode, u64 offset, u64 *len,
                              struct btrfs_file_extent *file_extent,
                              bool nowait);
index c083a67d0091751884a6f0372b91468bb68a1a15..1a0c380ef464260a9d2d02a0b18bb934b6b4e80e 100644 (file)
@@ -3420,12 +3420,13 @@ int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8
 }
 
 /*
- * Verify the checksum of a single data sector.
+ * Verify the checksum of a single data sector, which can be scattered at
+ * different noncontiguous pages.
  *
  * @bbio:      btrfs_io_bio which contains the csum
  * @dev:       device the sector is on
  * @bio_offset:        offset to the beginning of the bio (in bytes)
- * @bv:                bio_vec to check
+ * @paddrs:    physical addresses which back the fs block
  *
  * Check if the checksum on a data block is valid.  When a checksum mismatch is
  * detected, report the error and fill the corrupted range with zero.
@@ -3433,12 +3434,13 @@ int btrfs_check_block_csum(struct btrfs_fs_info *fs_info, phys_addr_t paddr, u8
  * Return %true if the sector is ok or had no checksum to start with, else %false.
  */
 bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev,
-                       u32 bio_offset, phys_addr_t paddr)
+                       u32 bio_offset, const phys_addr_t paddrs[])
 {
        struct btrfs_inode *inode = bbio->inode;
        struct btrfs_fs_info *fs_info = inode->root->fs_info;
        const u32 blocksize = fs_info->sectorsize;
-       struct folio *folio;
+       const u32 step = min(blocksize, PAGE_SIZE);
+       const u32 nr_steps = blocksize / step;
        u64 file_offset = bbio->file_offset + bio_offset;
        u64 end = file_offset + blocksize - 1;
        u8 *csum_expected;
@@ -3458,7 +3460,8 @@ bool btrfs_data_csum_ok(struct btrfs_bio *bbio, struct btrfs_device *dev,
 
        csum_expected = bbio->csum + (bio_offset >> fs_info->sectorsize_bits) *
                                fs_info->csum_size;
-       if (btrfs_check_block_csum(fs_info, paddr, csum, csum_expected))
+       btrfs_calculate_block_csum_pages(fs_info, paddrs, csum);
+       if (unlikely(memcmp(csum, csum_expected, fs_info->csum_size) != 0))
                goto zeroit;
        return true;
 
@@ -3467,9 +3470,8 @@ zeroit:
                                    bbio->mirror_num);
        if (dev)
                btrfs_dev_stat_inc_and_print(dev, BTRFS_DEV_STAT_CORRUPTION_ERRS);
-       folio = page_folio(phys_to_page(paddr));
-       ASSERT(offset_in_folio(folio, paddr) + blocksize <= folio_size(folio));
-       folio_zero_range(folio, offset_in_folio(folio, paddr), blocksize);
+       for (int i = 0; i < nr_steps; i++)
+               memzero_page(phys_to_page(paddrs[i]), offset_in_page(paddrs[i]), step);
        return false;
 }