#include "compression.h"
 #include "extent_io.h"
 #include "extent_map.h"
+#include "subpage.h"
 #include "zoned.h"
 
 static const char* const btrfs_compress_types[] = { "", "zlib", "lzo", "zstd" };
        return page_offset(last->bv_page) + last->bv_len + last->bv_offset;
 }
 
+/*
+ * Add extra pages in the same compressed file extent so that we don't need to
+ * re-read the same extent again and again.
+ *
+ * NOTE: this won't work well for subpage, as for subpage read, we lock the
+ * full page then submit bio for each compressed/regular extents.
+ *
+ * This means, if we have several sectors in the same page points to the same
+ * on-disk compressed data, we will re-read the same extent many times and
+ * this function can only help for the next page.
+ */
 static noinline int add_ra_bio_pages(struct inode *inode,
                                     u64 compressed_end,
                                     struct compressed_bio *cb)
 {
+       struct btrfs_fs_info *fs_info = btrfs_sb(inode->i_sb);
        unsigned long end_index;
-       unsigned long pg_index;
-       u64 last_offset;
+       u64 cur = bio_end_offset(cb->orig_bio);
        u64 isize = i_size_read(inode);
        int ret;
        struct page *page;
        struct address_space *mapping = inode->i_mapping;
        struct extent_map_tree *em_tree;
        struct extent_io_tree *tree;
-       u64 end;
-       int misses = 0;
+       int sectors_missed = 0;
 
-       last_offset = bio_end_offset(cb->orig_bio);
        em_tree = &BTRFS_I(inode)->extent_tree;
        tree = &BTRFS_I(inode)->io_tree;
 
 
        end_index = (i_size_read(inode) - 1) >> PAGE_SHIFT;
 
-       while (last_offset < compressed_end) {
-               pg_index = last_offset >> PAGE_SHIFT;
+       while (cur < compressed_end) {
+               u64 page_end;
+               u64 pg_index = cur >> PAGE_SHIFT;
+               u32 add_size;
 
                if (pg_index > end_index)
                        break;
 
                page = xa_load(&mapping->i_pages, pg_index);
                if (page && !xa_is_value(page)) {
-                       misses++;
-                       if (misses > 4)
+                       sectors_missed += (PAGE_SIZE - offset_in_page(cur)) >>
+                                         fs_info->sectorsize_bits;
+
+                       /* Beyond threshold, no need to continue */
+                       if (sectors_missed > 4)
                                break;
-                       goto next;
+
+                       /*
+                        * Jump to next page start as we already have page for
+                        * current offset.
+                        */
+                       cur = (pg_index << PAGE_SHIFT) + PAGE_SIZE;
+                       continue;
                }
 
                page = __page_cache_alloc(mapping_gfp_constraint(mapping,
 
                if (add_to_page_cache_lru(page, mapping, pg_index, GFP_NOFS)) {
                        put_page(page);
-                       goto next;
+                       /* There is already a page, skip to page end */
+                       cur = (pg_index << PAGE_SHIFT) + PAGE_SIZE;
+                       continue;
                }
 
-               /*
-                * at this point, we have a locked page in the page cache
-                * for these bytes in the file.  But, we have to make
-                * sure they map to this compressed extent on disk.
-                */
                ret = set_page_extent_mapped(page);
                if (ret < 0) {
                        unlock_page(page);
                        break;
                }
 
-               end = last_offset + PAGE_SIZE - 1;
-               lock_extent(tree, last_offset, end);
+               page_end = (pg_index << PAGE_SHIFT) + PAGE_SIZE - 1;
+               lock_extent(tree, cur, page_end);
                read_lock(&em_tree->lock);
-               em = lookup_extent_mapping(em_tree, last_offset,
-                                          PAGE_SIZE);
+               em = lookup_extent_mapping(em_tree, cur, page_end + 1 - cur);
                read_unlock(&em_tree->lock);
 
-               if (!em || last_offset < em->start ||
-                   (last_offset + PAGE_SIZE > extent_map_end(em)) ||
+               /*
+                * At this point, we have a locked page in the page cache for
+                * these bytes in the file.  But, we have to make sure they map
+                * to this compressed extent on disk.
+                */
+               if (!em || cur < em->start ||
+                   (cur + fs_info->sectorsize > extent_map_end(em)) ||
                    (em->block_start >> 9) != cb->orig_bio->bi_iter.bi_sector) {
                        free_extent_map(em);
-                       unlock_extent(tree, last_offset, end);
+                       unlock_extent(tree, cur, page_end);
                        unlock_page(page);
                        put_page(page);
                        break;
                        }
                }
 
-               ret = bio_add_page(cb->orig_bio, page,
-                                  PAGE_SIZE, 0);
-
-               if (ret == PAGE_SIZE) {
-                       put_page(page);
-               } else {
-                       unlock_extent(tree, last_offset, end);
+               add_size = min(em->start + em->len, page_end + 1) - cur;
+               ret = bio_add_page(cb->orig_bio, page, add_size, offset_in_page(cur));
+               if (ret != add_size) {
+                       unlock_extent(tree, cur, page_end);
                        unlock_page(page);
                        put_page(page);
                        break;
                }
-next:
-               last_offset += PAGE_SIZE;
+               /*
+                * If it's subpage, we also need to increase its
+                * subpage::readers number, as at endio we will decrease
+                * subpage::readers and to unlock the page.
+                */
+               if (fs_info->sectorsize < PAGE_SIZE)
+                       btrfs_subpage_start_reader(fs_info, page, cur, add_size);
+               put_page(page);
+               cur += add_size;
        }
        return 0;
 }