From: Boris Burkov Date: Mon, 27 Jul 2026 22:23:30 +0000 (-0700) Subject: btrfs: trigger cow fixup via dirty_folio() X-Git-Url: http://git.ipfire.org/index.cgi?a=commitdiff_plain;h=0680cbbf39ca61c70be16141b5259f822e7cdb3b;p=thirdparty%2Fkernel%2Flinux.git btrfs: trigger cow fixup via dirty_folio() The problem scenario: If we have a folio mmapped shared and then somebody does a dio read with that folio as the read destination, then it is possible that the dio will see a dirty destination page when it starts (and thus skip dirtying and just GUP pin it) but then while it is doing the read, btrfs finishes writing it back and by the endio, the folio is clean. In that case, the dio read must re-dirty the folio with aops->dirty_folio(): btrfs_check_read_bio() |- __iomap_dio_bio_end_io() from btrfs_bio_end_io() |- bio_check_pages_dirty() |- bio_dirty_fn() |- bio_release_pages(bio, true) |- __bio_release_pages(bio, mark_dirty == true) |- folio_lock() |- folio_mark_dirty() |- aops->dirty_folio() |- folio_unlock() A data block normally moves through writeback as follows: TASK folio_lock write clean -> dirty bit + delalloc folio_unlock WRITEBACK for-each-dirty-folio: folio_lock run_delalloc delalloc consumed -> dirty bit + OE submission dirty bit consumed -> writeback bit + OE folio_unlock ENDIO endio OE bytes accounted OE finish writeback -> clean; destroy OE Three critical invariants that this path maintains are: I1. Any dirty block is covered by delalloc xor an ordered extent I2. Any dirty block covered by an OE will be submitted into that OE I3. Any dirty block already submitted into an OE will not be submitted again into the same OE. These ensure that the block will be written exactly once. It is clear that not reserving delalloc for the re-dirty case violates I1. This situation, even without bs < folio_size, has long required btrfs to fixup such dirty pages during writeback with an asynchronous worker that is allowed to do this expensive work and writeback does not proceed for a folio while it is doing this work. Commit 247e743cbe6e ("Btrfs: Use async helpers to deal with pages that have been improperly dirtied") introduced the COW fixup to catch exactly this class at writeback, way back in 2008. Since then, there have been many advances to prevent most of the causes of such re-dirtying and we thought we could get away with removing the annoying cow-fixup in the hope of simplifying writeback for large folio support. Commit b2a9f217ad3f ("btrfs: remove the COW fixup mechanism") Commit 4927b141877c ("btrfs: remove folio ordered flag and subpage bitmap") Since it turns out this assumption was incorrect, as evidenced by the report and attendant reproducers, we must reintroduce the fixup concept. This is of course critically further complicated by bs < folio_size. In that case, rather than just a folio dirty bit, we have a bitmap for the dirty blocks in the folio. And the (also broken) invariant is: I4. folio dirty IFF at least one block bitmap dirty. The original report of a stall on a misinterpreted empty bitmap is exactly evidence of a violation of I4. It is exactly because of bs < folio_size we don't want to simply revert the removal patches. The original fixup was not properly bs < folio_size aware, which motivated removal in the first place. So we wish to build a bs < folio_size aware fixup. One other important detail from the old design, any normal write that happens after a re-dirty but before a fixup is racing with the cow fixup to do the delalloc reservation, therefore it must cancel the fixup state. If it arrives after the reservation exists, it will be a normal dirty overwrite. This critically informs the design in a pretty clear way. fixup requiring re-dirty has folio granularity, while cancellation has delalloc (block) granularity so while we only ever produce fixup in chunks of folios, we must be able to clear it in blocks. Therefore we must track the blocks needing fixup at block granularity. The obvious way to do this is with a new bitmap in btrfs_folio_state, but it is desirable to avoid that if possible. Unfortunately, I don't think it is possible and the reason is subtle and leans on a sort of extreme reproducer, but I think can be explained relatively succinctly. Consider a folio whose two halves will land in different ordered extents (can be accomplished with tricks using nodatasum) and a dio read is running with it as the shared mmap destination. 1. The front half: a. folio comes clean on a normal write b. dio read completes into the folio marking it fixup. c. a write comes for the previous folio for a range extending into this folio, this is a cancellation of the fixup which reserves space. d. writeback runs on the range *not* overlapping the folio. This half remains dirty but is now covered by an OE and is awaiting writeback running on its range to be submitted and finish the OE. 2. The back half: a. the folio is part of an OE that gets far enough along to clear writeback. b. dio read completes into the folio marking it fixup. After this, the folio's front half is dirty in the "normal" sense, it needs to be submitted to the OE waiting for it. It's a cancelled fixup. Meanwhile, the second half is a true fresh fixup. So at this point if we run writeback on this folio, we genuinely can't know what to do without block level information. If we submit it, we submit unreserved dirty from the back half. If we don't, we will never finish the OE waiting for it. So it's either a corruption or a deadlock. Thus, the full high level design picture: - btrfs_data_dirty_folio(): For out of band non-reserving dirties, mark still-clean blocks inside EOF dirty and set their fixup bits (the event carries no range, so every clean block is suspect). Already-dirty blocks are covered or pending and are left alone. - Writeback: skip fixup blocks and enqueue work for them - writepage_fixup(): for each fixup block do the fixup reservation in a worker, after which the blocks can be written back normally. - Typical reserving write paths cancel fixup state for the ranges they cover with btrfs_folio_cancel_fixup() Link: https://lore.kernel.org/linux-btrfs/20260721191152.101118-1-borntraeger@linux.ibm.com/ Assisted-by: LLM Reviewed-by: Qu Wenruo Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h index 7fdc6c3fd0666..1082fa92c1457 100644 --- a/fs/btrfs/btrfs_inode.h +++ b/fs/btrfs/btrfs_inode.h @@ -600,6 +600,7 @@ int btrfs_prealloc_file_range_trans(struct inode *inode, loff_t actual_len, u64 *alloc_hint); int btrfs_run_delalloc_range(struct btrfs_inode *inode, struct folio *locked_folio, u64 start, u64 end, struct writeback_control *wbc); +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio); int btrfs_encoded_io_compression_from_extent(struct btrfs_fs_info *fs_info, int compress_type); int btrfs_encoded_read_regular_fill_pages(struct btrfs_inode *inode, diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 36332df9a0f1c..6bb70c43a63f6 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -1760,6 +1760,8 @@ static int read_backup_root(struct btrfs_fs_info *fs_info, u8 priority) /* helper to cleanup workers */ static void btrfs_stop_all_workers(struct btrfs_fs_info *fs_info) { + if (fs_info->fixup_workers) + destroy_workqueue(fs_info->fixup_workers); btrfs_destroy_workqueue(fs_info->delalloc_workers); btrfs_destroy_workqueue(fs_info->workers); if (fs_info->endio_workers) @@ -1967,6 +1969,9 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->caching_workers = btrfs_alloc_workqueue(fs_info, "cache", flags, max_active, 0); + fs_info->fixup_workers = + alloc_ordered_workqueue("btrfs-fixup", ordered_flags); + fs_info->endio_workers = alloc_workqueue("btrfs-endio", flags, max_active); fs_info->endio_meta_workers = @@ -1992,7 +1997,7 @@ static int btrfs_init_workqueues(struct btrfs_fs_info *fs_info) fs_info->endio_workers && fs_info->endio_meta_workers && fs_info->endio_write_workers && fs_info->endio_freespace_worker && fs_info->rmw_workers && - fs_info->caching_workers && + fs_info->caching_workers && fs_info->fixup_workers && fs_info->delayed_workers && fs_info->qgroup_rescan_workers && fs_info->discard_ctl.discard_workers)) { return -ENOMEM; diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c index de5785117a47f..f032f0858f40f 100644 --- a/fs/btrfs/extent_io.c +++ b/fs/btrfs/extent_io.c @@ -1440,6 +1440,115 @@ static bool find_next_delalloc_bitmap(struct folio *folio, return true; } +/* + * Debug checks for fixup selection logic to help ensure the invariants + * we expect for fixup marking hold in practice. + * + * - A dirty block without a fixup bit is covered by delalloc or a running + * ordered extent (it was dirtied by a reserving write path). + * - A block with a fixup bit is never covered by delalloc: every delalloc + * setter holds the folio lock and cancels the fixup state of the blocks + * it covers (btrfs_folio_set_dirty()) before releasing it. + */ +static void debug_check_writepage_fixup(struct btrfs_inode *inode, u64 start, + u32 len, bool needs_fixup) +{ + struct btrfs_ordered_extent *ordered; + bool delalloc; + + if (!IS_ENABLED(CONFIG_BTRFS_DEBUG)) + return; + + delalloc = btrfs_test_range_bit_exists(&inode->io_tree, start, + start + len - 1, EXTENT_DELALLOC); + if (needs_fixup) { + if (unlikely(delalloc)) + DEBUG_WARN("writeback: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + } else { + if (delalloc) + return; + + ordered = btrfs_lookup_ordered_range(inode, start, len); + if (unlikely(!ordered)) + DEBUG_WARN("dirty block, no delalloc, fixup, ordered. ino %llu start %llu", + btrfs_ino(inode), start); + else + btrfs_put_ordered_extent(ordered); + } +} + +/* + * Handle folios dirtied without a delalloc reservation, e.g. + * O_DIRECT read into a MAP_SHARED mapping dirtying via set_page_dirty_lock(). + * + * btrfs_data_dirty_folio() records the affected blocks in the fixup bitmap + * and the folio fixup flag and we check them here in writeback. + * + * Don't submit such blocks and queue work for the fixup worker to reserve + * space for them so that they can be submitted properly by writeback. + * + * Return 1 if the folio needed fixup, 0 if not, and a negative error code + * on error. + */ +static noinline_for_stack int writepage_fixup(struct btrfs_inode *inode, + struct folio *folio, + struct btrfs_bio_ctrl *bio_ctrl) +{ + struct btrfs_fs_info *fs_info = inode_to_fs_info(&inode->vfs_inode); + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + bool found_fixup = false; + unsigned int bit; + + /* + * A folio was dirtied without calling aops->dirty_folio() which we + * explicitly assert is not allowed. + */ + if (unlikely(bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio))) { + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is dirty with an empty dirty bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + return -EUCLEAN; + } + + /* Cheap check on the folio flag. Set iff the fixup bitmap is non-empty. */ + if (likely(!folio_test_fixup_pending(folio))) + return 0; + + for_each_set_bit(bit, bio_ctrl->submit_bitmap, blocks_per_folio) { + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + const bool needs_fixup = btrfs_folio_test_fixup(fs_info, folio, + start, sectorsize); + + debug_check_writepage_fixup(inode, start, sectorsize, needs_fixup); + if (needs_fixup) { + bitmap_clear(bio_ctrl->submit_bitmap, bit, 1); + found_fixup = true; + } + } + if (likely(found_fixup)) { + btrfs_queue_writepage_fixup(inode, folio); + folio_redirty_for_writepage(bio_ctrl->wbc, folio); + if (bitmap_empty(bio_ctrl->submit_bitmap, blocks_per_folio)) { + folio_unlock(folio); + return 1; + } + return 0; + } + /* We should always find fixup if the folio fixup flag was set. */ + DEBUG_WARN(); + btrfs_err_rl(fs_info, + "root %lld ino %llu folio %llu is fixup with an empty fixup bitmap", + btrfs_root_id(inode->root), btrfs_ino(inode), + folio_pos(folio)); + + return -EUCLEAN; +} + /* * Do all of the delayed allocation setup. * @@ -1492,6 +1601,10 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode, /* Save the dirty bitmap as our submission bitmap will be a subset of it. */ btrfs_copy_subpage_dirty_bitmap(fs_info, folio, bio_ctrl->submit_bitmap); + ret = writepage_fixup(inode, folio, bio_ctrl); + if (ret) + return ret; + for_each_set_bitrange(start_bit, end_bit, bio_ctrl->submit_bitmap, blocks_per_folio) { u64 start = page_start + (start_bit << fs_info->sectorsize_bits); diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 7ee9ec2b0efba..f7f343fbe732f 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -713,6 +713,8 @@ struct btrfs_fs_info { struct btrfs_workqueue *endio_write_workers; struct btrfs_workqueue *endio_freespace_worker; struct btrfs_workqueue *caching_workers; + + struct workqueue_struct *fixup_workers; struct btrfs_workqueue *delayed_workers; struct task_struct *transaction_kthread; @@ -1200,6 +1202,16 @@ static inline void btrfs_wake_unfinished_drop(struct btrfs_fs_info *fs_info) clear_and_wake_up_bit(BTRFS_FS_UNFINISHED_DROPS, &fs_info->flags); } +/* + * We use the folio owner_2 flag to indicate the folio has blocks that were + * dirtied without a space reservation and need the writepage fixup before + * writeback. For bs < folio_size the fixup bitmap tracks the affected + * blocks. + */ +#define folio_test_fixup_pending(folio) folio_test_owner_2(folio) +#define folio_set_fixup_pending(folio) folio_set_owner_2(folio) +#define folio_clear_fixup_pending(folio) folio_clear_owner_2(folio) + #define BTRFS_FS_ERROR(fs_info) (READ_ONCE((fs_info)->fs_error)) #define BTRFS_FS_LOG_CLEANUP_ERROR(fs_info) \ diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 04ea10b61bbb7..98b31a090626e 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -2812,6 +2812,163 @@ int btrfs_set_extent_delalloc(struct btrfs_inode *inode, u64 start, u64 end, EXTENT_DELALLOC | extra_bits, cached_state); } +struct btrfs_writepage_fixup { + struct folio *folio; + struct btrfs_inode *inode; + struct work_struct work; +}; + +/* + * Do the real fixup work of reserving space for the blocks a folio's fixup + * state records. Queued by writepage_fixup() when writeback found the bits set. + * + * Since the fixup can be cancelled by a task dirtying with a reservation, we must + * re-check the state of fixup under the folio lock. + */ +static void btrfs_writepage_fixup_worker(struct work_struct *work) +{ + struct btrfs_writepage_fixup *fixup = + container_of(work, struct btrfs_writepage_fixup, work); + struct extent_state *cached_state = NULL; + struct extent_changeset *data_reserved = NULL; + unsigned long delalloc_bitmap[BITS_TO_LONGS(BTRFS_MAX_BLOCKS_PER_FOLIO)] = { 0 }; + struct folio *folio = fixup->folio; + struct btrfs_inode *inode = fixup->inode; + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const unsigned int blocks_per_folio = btrfs_blocks_per_folio(fs_info, folio); + const u32 sectorsize = fs_info->sectorsize; + const u64 page_start = folio_pos(folio); + const u64 page_end = folio_next_pos(folio) - 1; + unsigned int start_bit; + unsigned int end_bit; + unsigned int bit; + bool reserved; + int ret; + + /* + * We would prefer to reserve under the folio lock when we know exactly + * which blocks need a reservation. Unfortunately, since the reservation + * can go into flushers which can go into writeback, which takes folio + * locks, that is not possible. Therefore, we have to reserve for the + * whole folio here, then release what we didn't end up needing once we + * figure it out. + * + * Also note the slightly strange error checking. If fixup is actually + * not set, we don't need to mark an error on the mapping. So hang on to + * ret until after we lock and find out if we actually care. + */ + ret = btrfs_delalloc_reserve_space(inode, &data_reserved, page_start, + folio_size(folio)); + reserved = (ret == 0); +again: + folio_lock(folio); + + if (!folio->mapping || !folio_test_fixup_pending(folio)) { + ret = 0; + goto out; + } + if (ret) + goto out; + + btrfs_lock_extent(&inode->io_tree, page_start, page_end, &cached_state); + + for (bit = 0; bit < blocks_per_folio; bit++) { + struct btrfs_ordered_extent *ordered; + const u64 start = page_start + (bit << fs_info->sectorsize_bits); + + if (test_bit(bit, delalloc_bitmap)) + continue; + if (!btrfs_folio_test_fixup(fs_info, folio, start, sectorsize)) + continue; + /* + * Any task that sets EXTENT_DELALLOC clears the fixup bits + * under the folio lock, so it should be impossible to observe + * both under the lock. Setting delalloc twice would wrongly + * double account the space. + */ + if (IS_ENABLED(CONFIG_BTRFS_DEBUG) && + unlikely(btrfs_test_range_bit_exists(&inode->io_tree, start, + start + sectorsize - 1, + EXTENT_DELALLOC))) { + DEBUG_WARN("fixup worker: delalloc and fixup conflict. ino %llu start %llu", + btrfs_ino(inode), start); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + continue; + } + ordered = btrfs_lookup_ordered_range(inode, start, sectorsize); + if (ordered) { + trace_btrfs_writepage_fixup_defer(inode, ordered); + btrfs_unlock_extent(&inode->io_tree, page_start, + page_end, &cached_state); + folio_unlock(folio); + btrfs_start_ordered_extent(ordered); + btrfs_put_ordered_extent(ordered); + goto again; + } + ret = btrfs_set_extent_delalloc(inode, start, + start + sectorsize - 1, 0, + &cached_state); + if (ret) + break; + trace_btrfs_writepage_fixup_reserve(inode, start, sectorsize); + btrfs_folio_clear_fixup(fs_info, folio, start, sectorsize); + set_bit(bit, delalloc_bitmap); + } + + btrfs_unlock_extent(&inode->io_tree, page_start, page_end, &cached_state); +out: + if (ret < 0) { + /* Failure here is analogous to failure in writeback. */ + mapping_set_error(folio->mapping, ret); + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start, + folio_size(folio)); + } + if (reserved) { + btrfs_delalloc_release_extents(inode, folio_size(folio)); + for_each_clear_bitrange(start_bit, end_bit, delalloc_bitmap, + blocks_per_folio) + btrfs_delalloc_release_space(inode, data_reserved, + page_start + (start_bit << fs_info->sectorsize_bits), + (end_bit - start_bit) << fs_info->sectorsize_bits, + true); + } + folio_unlock(folio); + folio_put(folio); + kfree(fixup); + extent_changeset_free(data_reserved); + btrfs_add_delayed_iput(inode); +} + +/* + * Queue space reservation fixup work for blocks dirtied without a space reservation. + * + * Should be used by writeback while holding the folio locked. + * + * If we fail to queue fixup, then the folio state is unchanged and a future + * writeback pass will still see it. + */ +void btrfs_queue_writepage_fixup(struct btrfs_inode *inode, struct folio *folio) +{ + struct btrfs_fs_info *fs_info = inode->root->fs_info; + struct btrfs_writepage_fixup *fixup; + + fixup = kzalloc_obj(*fixup, GFP_NOFS); + if (!fixup) + return; + + /* + * This is called from within extent_write_cache_pages() which + * has successfully done an igrab(). But that will be released at the + * end of the writeback pass. We need to extend it for the worker as well. + */ + ihold(&inode->vfs_inode); + folio_get(folio); + INIT_WORK(&fixup->work, btrfs_writepage_fixup_worker); + fixup->folio = folio; + fixup->inode = inode; + queue_work(fs_info->fixup_workers, &fixup->work); +} + /* * Clear the old accounting flags and set EXTENT_DELALLOC for the range. * @@ -7507,6 +7664,12 @@ static void btrfs_invalidate_folio(struct folio *folio, size_t offset, folio_wait_writeback(folio); wait_subpage_spinlock(folio); + /* + * The invalidated blocks are going away; drop any fixup blocks among + * them, data included, as they have no space reservation. + */ + btrfs_folio_clear_fixup_dirty(fs_info, folio, page_start + offset, length); + /* * For subpage case, we have call sites like * btrfs_punch_hole_lock_range() which passes range not aligned to @@ -10548,6 +10711,41 @@ static const struct file_operations btrfs_dir_file_operations = { .setlease = generic_setlease, }; +/* + * The folio is going dirty without a btrfs delalloc space reservation. + * This requires a fixup before writeback which we might sleep so cannot + * run in this context, so we merely set state on the folio indicating it + * needs fixup before writeback. + * + * Note that there is no range in the input, so the whole folio is marked + * dirty and fixup. + * + * We believe that all callers of dirty_folio either: + * - take the folio lock (e.g. pinned folio release notification). + * - take the pte lock but must be running on a dirty pte which means + * page_mkwrite() ran on it and reserved the space. zap_pte_range() cannot + * race with writeback cleaning the folio because writeback runs + * folio_mkclean() which also uses the pte lock and revokes outstanding + * writable mappings. + * Therefore, an additional folio private lock (a la bfs->lock for all cases, + * not just subpage) is not necessary. + */ +static bool btrfs_data_dirty_folio(struct address_space *mapping, + struct folio *folio) +{ + struct btrfs_inode *inode = BTRFS_I(mapping->host); + struct btrfs_fs_info *fs_info = inode->root->fs_info; + const u64 page_start = folio_pos(folio); + const u64 range_end = min_t(u64, folio_next_pos(folio), + round_up(i_size_read(&inode->vfs_inode), + fs_info->sectorsize)); + + if (range_end > page_start) + btrfs_folio_set_fixup_dirty(fs_info, folio, page_start, + range_end - page_start); + return filemap_dirty_folio(mapping, folio); +} + /* * btrfs doesn't support the bmap operation because swapfiles * use bmap to make a mapping of extents in the file. They assume @@ -10568,7 +10766,7 @@ static const struct address_space_operations btrfs_aops = { .launder_folio = btrfs_launder_folio, .release_folio = btrfs_release_folio, .migrate_folio = btrfs_migrate_folio, - .dirty_folio = filemap_dirty_folio, + .dirty_folio = btrfs_data_dirty_folio, .error_remove_folio = generic_error_remove_folio, .swap_activate = btrfs_swap_activate, .swap_deactivate = btrfs_swap_deactivate, diff --git a/fs/btrfs/subpage.c b/fs/btrfs/subpage.c index 2a9397be8116c..27dd677ca687d 100644 --- a/fs/btrfs/subpage.c +++ b/fs/btrfs/subpage.c @@ -345,18 +345,57 @@ void btrfs_subpage_clear_uptodate(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +/* + * folio_mark_dirty() for a folio we are dirtying with a space reservation. + * + * Dirtiers without a reservation use btrfs_data_dirty_folio(). + */ +static void btrfs_folio_mark_dirty(struct folio *folio) +{ + struct address_space *mapping = folio_mapping(folio); + + if (!mapping || !mapping->host || !is_data_inode(BTRFS_I(mapping->host))) { + folio_mark_dirty(folio); + return; + } + if (folio_test_reclaim(folio)) + folio_clear_reclaim(folio); + filemap_dirty_folio(mapping, folio); +} + +/* + * The set helper of the dirty ops, so it only runs for folios without a + * fixup bitmap: for those the folio flag is the whole fixup state, and this + * reserving write covers the block, so retire it. Metadata never has the + * flag set and only pays the test. + */ +static void btrfs_folio_mark_dirty_reserved(struct folio *folio) +{ + if (folio_test_fixup_pending(folio)) + folio_clear_fixup_pending(folio); + btrfs_folio_mark_dirty(folio); +} + void btrfs_subpage_set_dirty(const struct btrfs_fs_info *fs_info, struct folio *folio, u64 start, u32 len) { struct btrfs_folio_state *bfs = folio_get_private(folio); - unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; unsigned long flags; spin_lock_irqsave(&bfs->lock, flags); - bitmap_set(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + bitmap_set(bfs->bitmaps, dirty_bit, nbits); + /* Proper dirtying obviates the need for fixup. */ + bitmap_clear(bfs->bitmaps, fixup_bit, nbits); + if (folio_test_fixup_pending(folio) && + subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); spin_unlock_irqrestore(&bfs->lock, flags); - folio_mark_dirty(folio); + btrfs_folio_mark_dirty(folio); } static void folio_clear_tags(struct folio *folio) @@ -457,6 +496,172 @@ void btrfs_subpage_clear_writeback(const struct btrfs_fs_info *fs_info, spin_unlock_irqrestore(&bfs->lock, flags); } +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int start_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + unsigned long flags; + + spin_lock_irqsave(&bfs->lock, flags); + bitmap_clear(bfs->bitmaps, start_bit, len >> fs_info->sectorsize_bits); + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * In one pass under bfs->lock, mark every block with a clear dirty bit in the + * range both dirty and needing fixup. + * + * Only called from the dirty_folio callback, which owns the folio-level + * dirty flag; calling folio_mark_dirty() here would recurse. + * + * The folio fixup flag and bits are both set under bfs->lock so that a + * writeback pass observing the new bits also observes the flag. + */ +static void btrfs_subpage_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool marked = false; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (test_bit(dirty_bit + i, bfs->bitmaps)) + continue; + set_bit(dirty_bit + i, bfs->bitmaps); + set_bit(fixup_bit + i, bfs->bitmaps); + marked = true; + } + if (marked) + folio_set_fixup_pending(folio); + spin_unlock_irqrestore(&bfs->lock, flags); +} + +/* + * Mark the still-clean blocks of a folio dirty and needing fixup, for + * btrfs_data_dirty_folio(). + * + * A subpage block size folio that is not uptodate is left alone: its clean + * blocks may hold content that was never read in, which must not be marked + * dirty. + */ +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + if (!folio_test_dirty(folio)) + folio_set_fixup_pending(folio); + return; + } + if (!folio_test_uptodate(folio)) + return; + btrfs_subpage_set_fixup_dirty(fs_info, folio, start, len); +} + +/* + * Drop the fixup blocks inside the range: clear both their fixup and dirty + * bits. + * + * Fixup blocks carry no space reservation, so their fixup and dirty bits + * must be dropped together. Clearing only the fixup bit would leave a + * dirty block without a reservation which is not a valid state. + * + * Returns true if the folio has no dirty blocks left. + */ +static bool btrfs_subpage_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + struct btrfs_folio_state *bfs = folio_get_private(folio); + unsigned int dirty_bit = subpage_calc_start_bit(fs_info, folio, + dirty, start, len); + unsigned int fixup_bit = subpage_calc_start_bit(fs_info, folio, + fixup, start, len); + const unsigned int nbits = len >> fs_info->sectorsize_bits; + unsigned long flags; + bool last; + + spin_lock_irqsave(&bfs->lock, flags); + for (unsigned int i = 0; i < nbits; i++) { + if (!test_bit(fixup_bit + i, bfs->bitmaps)) + continue; + clear_bit(fixup_bit + i, bfs->bitmaps); + clear_bit(dirty_bit + i, bfs->bitmaps); + } + if (subpage_test_bitmap_all_zero(fs_info, folio, fixup)) + folio_clear_fixup_pending(folio); + last = subpage_test_bitmap_all_zero(fs_info, folio, dirty); + spin_unlock_irqrestore(&bfs->lock, flags); + return last; +} + +/* + * Drop the fixup blocks inside the range, for callers discarding their data: + * btrfs_invalidate_folio() and the writepage fixup worker's error path. + * + * Callers that have just reserved space for a block want + * btrfs_folio_clear_fixup() instead - there the block stays dirty and gets + * written. + * + * The range can be byte-granular (an unaligned truncate through + * btrfs_invalidate_folio()); only blocks fully inside it are dropped, as a + * partially covered block still holds live data outside the range. For + * single-block folios the folio flag is the fixup state, so it is dropped + * only when the range covers the whole folio. + */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + u64 aligned_start; + u64 aligned_end; + + /* The folio flag is set whenever any fixup bitmap bit is. */ + if (!folio_test_fixup_pending(folio)) + return; + if (!btrfs_is_subpage(fs_info, folio)) { + if (start <= folio_pos(folio) && + start + len >= folio_next_pos(folio)) { + folio_clear_fixup_pending(folio); + folio_clear_dirty_for_io(folio); + } + return; + } + btrfs_subpage_clamp_range(folio, &start, &len); + aligned_start = round_up(start, fs_info->sectorsize); + aligned_end = round_down(start + len, fs_info->sectorsize); + if (aligned_end <= aligned_start) + return; + if (btrfs_subpage_clear_fixup_dirty(fs_info, folio, aligned_start, + aligned_end - aligned_start)) + folio_clear_dirty_for_io(folio); +} + +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) + return folio_test_fixup_pending(folio); + return btrfs_subpage_test_fixup(fs_info, folio, start, len); +} + +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len) +{ + if (!btrfs_is_subpage(fs_info, folio)) { + folio_clear_fixup_pending(folio); + return; + } + btrfs_subpage_clear_fixup(fs_info, folio, start, len); +} + /* * Unlike set/clear which is dependent on each page status, for test all bits * are tested in the same way. @@ -480,6 +685,7 @@ bool btrfs_subpage_test_##name(const struct btrfs_fs_info *fs_info, \ IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(uptodate); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(dirty); IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(writeback); +IMPLEMENT_BTRFS_SUBPAGE_TEST_OP(fixup); /* * Note that, in selftests (extent-io-tests), we can have empty fs_info passed @@ -571,8 +777,8 @@ bool btrfs_meta_folio_test_##name(struct folio *folio, const struct extent_buffe } IMPLEMENT_BTRFS_PAGE_OPS(uptodate, folio_mark_uptodate, folio_clear_uptodate, folio_test_uptodate); -IMPLEMENT_BTRFS_PAGE_OPS(dirty, folio_mark_dirty, folio_clear_dirty_for_io, - folio_test_dirty); +IMPLEMENT_BTRFS_PAGE_OPS(dirty, btrfs_folio_mark_dirty_reserved, + folio_clear_dirty_for_io, folio_test_dirty); IMPLEMENT_BTRFS_PAGE_OPS(writeback, folio_start_writeback, folio_end_writeback, folio_test_writeback); diff --git a/fs/btrfs/subpage.h b/fs/btrfs/subpage.h index c6d7394e6418a..9aceba93c818f 100644 --- a/fs/btrfs/subpage.h +++ b/fs/btrfs/subpage.h @@ -14,15 +14,15 @@ struct folio; /* * Extra info for subpage bitmap. * - * For subpage we pack all uptodate/dirty/writeback bitmaps into + * For subpage we pack all uptodate/dirty/writeback/fixup bitmaps into * one larger bitmap. * * This structure records how they are organized in the bitmap: * - * /- uptodate /- dirty /- writeback - * | | | - * v v v - * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.......|w|w| + * /- uptodate /- dirty /- writeback /- fixup + * | | | | + * v v v v + * |u|u|u|u|........|u|u|d|d|.......|d|d|w|w|.....|w|w|f|f|.....|f|f| * |< sectors_per_page >| * * Unlike regular macro-like enums, here we do not go upper-case names, as @@ -40,6 +40,14 @@ enum { */ btrfs_bitmap_nr_writeback, + /* + * Blocks dirtied by the dirty_folio callback instead of a reserving + * write path (e.g. set_page_dirty_lock() on a GUP pin). They have + * no space reservation and need the writepage fixup before they can + * be submitted. + */ + btrfs_bitmap_nr_fixup, + btrfs_bitmap_nr_max }; @@ -165,6 +173,29 @@ DECLARE_BTRFS_SUBPAGE_OPS(uptodate); DECLARE_BTRFS_SUBPAGE_OPS(dirty); DECLARE_BTRFS_SUBPAGE_OPS(writeback); +/* + * Fixup bit helpers. + * + * The fixup bit is data-only and has no plain set helper (setting happens + * together with dirtying in btrfs_subpage_set_fixup_dirty()), so it does not + * go through DECLARE_BTRFS_SUBPAGE_OPS(). For single-block folios the + * folio_*_fixup_pending() flag takes the place of the bitmap. + */ +void btrfs_subpage_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_subpage_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +bool btrfs_folio_test_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +void btrfs_folio_set_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For a block that just got its space reserved; it stays dirty. */ +void btrfs_folio_clear_fixup(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); +/* For callers discarding the data; clears the dirty bits too. */ +void btrfs_folio_clear_fixup_dirty(const struct btrfs_fs_info *fs_info, + struct folio *folio, u64 start, u32 len); + /* * Helper for error cleanup, where a folio will have its dirty flag cleared, * with writeback started and finished. diff --git a/include/trace/events/btrfs.h b/include/trace/events/btrfs.h index 4c5c47c5edb74..6c1438f6a4d38 100644 --- a/include/trace/events/btrfs.h +++ b/include/trace/events/btrfs.h @@ -689,6 +689,41 @@ DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_lookup_first, TP_ARGS(inode, ordered) ); +/* + * The writepage fixup worker deferred a block because this still-running + * ordered extent covers it. + */ +DEFINE_EVENT(btrfs__ordered_extent, btrfs_writepage_fixup_defer, + + TP_PROTO(const struct btrfs_inode *inode, + const struct btrfs_ordered_extent *ordered), + + TP_ARGS(inode, ordered) +); + +/* The writepage fixup worker reserved space for a block and set delalloc. */ +TRACE_EVENT(btrfs_writepage_fixup_reserve, + + TP_PROTO(const struct btrfs_inode *inode, u64 start, u32 len), + + TP_ARGS(inode, start, len), + + TP_STRUCT__entry_btrfs( + __field( u64, ino ) + __field( u64, start ) + __field( u32, len ) + ), + + TP_fast_assign_btrfs(inode->root->fs_info, + __entry->ino = btrfs_ino(inode); + __entry->start = start; + __entry->len = len; + ), + + TP_printk_btrfs("ino=%llu start=%llu len=%u", + __entry->ino, __entry->start, __entry->len) +); + DEFINE_EVENT(btrfs__ordered_extent, btrfs_ordered_extent_split, TP_PROTO(const struct btrfs_inode *inode,