mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Chao Yu <chao@kernel.org>
To: Daeho Jeong <daeho43@gmail.com>,
	linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net, kernel-team@android.com
Cc: chao@kernel.org, Daeho Jeong <daehojeong@google.com>
Subject: Re: [f2fs-dev] [PATCH 07/20] f2fs: support subpage blocks in data write path and writeback
Date: Fri, 6 Nov 2026 10:52:20 +0800	[thread overview]
Message-ID: <164909bf-39e8-421e-b9c6-f2bac0391f39@kernel.org> (raw)
In-Reply-To: <20261102193952.3002018-8-daeho43@gmail.com>

On 11/3/26 03:39, Daeho Jeong wrote:
> From: Daeho Jeong <daehojeong@google.com>
> 
> With blocks smaller than PAGE_SIZE, a folio holds several blocks, so
> write_begin, write_end and writeback have to handle individual blocks
> instead of the whole folio.
> 
> - Read, zero and reserve the blocks touched by a write per block in
>    write_begin, and mark only the written blocks dirty in write_end.
> - Write back only the dirty blocks of a multi-block folio and complete
>    write I/O per block. In subpage mode every inode is written back by
>    f2fs_write_cache_folios(), which now handles dentry and quota folios
>    and checkpoint errors as f2fs_write_single_data_page() does. Both use
>    the new f2fs_cp_controlled_inode() and f2fs_wb_folio_action() helpers
>    for these rules.
>    It also resets the error for each folio, so that the remaining folios
>    are still written back after an error, as writeback_iter() expects
>    for integrity writeback, instead of redirtying them.
>    f2fs_write_folio_dirty_range() cleans up each block where it is
>    written or skipped, and advances last_disk_size only up to the last
>    block it submitted. On an error, it clears the dirty state of the
>    blocks before the failed one, so that only the failed block and the
>    ones after it are written again.
> - Count inode dirty blocks per folio: add all blocks of a folio when
>    it is dirtied and remove them when it is cleaned, instead of counting
>    the dirty bits of the folio state. A multi-block folio can be dirtied
>    without a folio state, e.g. after a read of the whole folio or
>    through folio_mark_dirty(), and a state attached later starts with
>    dirty bits that were never counted, so the bit count cannot stay
>    balanced. A partially dirty folio is counted as fully dirty.

Sorry, still I don't understand why we can not track dirty block count in folio,
the count should be consistent w/ dirty bitmap in ffs (e.g. if there is x bits in
bitmap is 1, the count should be x).

IIUC, if large folio can track dirty page count, 16k-page should can track 4k
dirty block as well? or is there any difference in subpage block feature, can you
please explain more about this?

> - Compressed and atomic files are not supported yet in subpage mode:
>    write_begin returns -EOPNOTSUPP for them. Inline data is converted
>    first.
> 
> Some of these changes also apply to large folios when the block size
> equals PAGE_SIZE.
> 
> Signed-off-by: Daeho Jeong <daehojeong@google.com>
> ---
>   fs/f2fs/checkpoint.c |  39 +---
>   fs/f2fs/data.c       | 486 ++++++++++++++++++++++---------------------
>   fs/f2fs/dir.c        |   2 +-
>   fs/f2fs/f2fs.h       |   1 -
>   fs/f2fs/file.c       |  11 +-
>   fs/f2fs/gc.c         |  11 +-
>   fs/f2fs/inline.c     |   7 +-
>   fs/f2fs/node.c       |   2 +-
>   8 files changed, 275 insertions(+), 284 deletions(-)
> 
> diff --git a/fs/f2fs/checkpoint.c b/fs/f2fs/checkpoint.c
> index 1060ee651756..a9a2b9d93231 100644
> --- a/fs/f2fs/checkpoint.c
> +++ b/fs/f2fs/checkpoint.c
> @@ -1324,46 +1324,21 @@ static void __remove_dirty_inode(struct inode *inode, enum inode_type type)
>   	stat_dec_dirty_inode(F2FS_I_SB(inode), type);
>   }
>   
> -/*
> - * Account @nr dirty blocks (negative when dirtiness is cleared) for @inode.
> - */
> -void f2fs_account_dirty_blocks(struct inode *inode, int nr)
> +void f2fs_update_dirty_folio(struct inode *inode, struct folio *folio)
>   {
>   	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>   	enum inode_type type = S_ISDIR(inode->i_mode) ? DIR_INODE : FILE_INODE;
> +	unsigned int nr_blocks = f2fs_blocks_per_folio(sbi, folio);
>   
> -	if (!nr)
> -		return;
> -
> -	spin_lock(&sbi->inode_lock[type]);
> -	if (nr > 0) {
> -		if (type != FILE_INODE || test_opt(sbi, DATA_FLUSH))
> -			__add_dirty_inode(inode, type);
> -		inode_add_dirty_blocks(inode, nr);
> -	} else {
> -		inode_sub_dirty_blocks(inode, -nr);
> -		__remove_dirty_inode(inode, type);
> -	}
> -	spin_unlock(&sbi->inode_lock[type]);
> -}
> -
> -void f2fs_update_dirty_folio(struct inode *inode, struct folio *folio)
> -{
>   	if (!S_ISDIR(inode->i_mode) && !S_ISREG(inode->i_mode) &&
>   			!S_ISLNK(inode->i_mode))
>   		return;
>   
> -	/*
> -	 * Large folios have a subpage dirty bitmap, so defer their dirty accounting
> -	 * to the f2fs_ffs_*subrange_dirty*() helpers.  This avoids nesting
> -	 * ffs->state_lock with the inode dirty list lock.
> -	 */
> -	if (f2fs_folio_has_ffs(folio)) {
> -		folio_set_f2fs_reference(folio);
> -		return;
> -	}
> -
> -	f2fs_account_dirty_blocks(inode, 1);
> +	spin_lock(&sbi->inode_lock[type]);
> +	if (type != FILE_INODE || test_opt(sbi, DATA_FLUSH))
> +		__add_dirty_inode(inode, type);
> +	inode_add_dirty_blocks(inode, nr_blocks);
> +	spin_unlock(&sbi->inode_lock[type]);
>   
>   	folio_set_f2fs_reference(folio);
>   }
> diff --git a/fs/f2fs/data.c b/fs/f2fs/data.c
> index 07b7aeb94350..41246f83b31d 100644
> --- a/fs/f2fs/data.c
> +++ b/fs/f2fs/data.c
> @@ -420,8 +420,7 @@ static void f2fs_write_end_bio(struct bio *bio)
>   
>   	bio_for_each_folio_all(fi, bio) {
>   		struct folio *folio = fi.folio;
> -		unsigned int nr_pages = fi.length >> PAGE_SHIFT;
> -		unsigned int npages = nr_pages;
> +		unsigned int nr_blocks = fi.length >> sbi->log_blocksize;

F2FS_BYTES_TO_BLK(sbi, fi.length);

>   		enum count_type type;
>   
>   #ifdef CONFIG_F2FS_FS_COMPRESSION
> @@ -441,8 +440,7 @@ static void f2fs_write_end_bio(struct bio *bio)
>   			}
>   		}
>   
> -		while (nr_pages--)
> -			dec_cache_count(sbi, type);
> +		sub_cache_counts(sbi, type, nr_blocks);
>   
>   		/*
>   		 * we should access sbi before folio_end_writeback() to
> @@ -452,7 +450,7 @@ static void f2fs_write_end_bio(struct bio *bio)
>   				wq_has_sleeper(&sbi->cp_wait))
>   			wake_up(&sbi->cp_wait);
>   
> -		f2fs_ffs_put_write_pending(folio, npages);
> +		f2fs_ffs_put_write_pending(folio, nr_blocks);
>   	}
>   
>   	bio_put(bio);
> @@ -3023,7 +3021,7 @@ static void f2fs_clear_truncated_page_state(struct folio *folio, pgoff_t i)
>   {
>   	struct f2fs_sb_info *sbi = F2FS_F_SB(folio);
>   
> -	if (f2fs_folio_has_ffs(folio)) {
> +	if (f2fs_is_multiblock_folio(sbi, folio)) {
>   		f2fs_ffs_clear_subrange_uptodate(folio,
>   				F2FS_BLK_TO_BYTES(sbi, i), F2FS_BLKSIZE(sbi));
>   	} else {
> @@ -3051,21 +3049,6 @@ bool f2fs_ffs_test_blk_dirty(const struct folio *folio, pgoff_t index)
>   	return test_bit(nr_blocks + offset, ffs->state);
>   }
>   
> -static unsigned int __ffs_mark_subrange_dirty(struct folio *folio,
> -			struct f2fs_folio_state *ffs, size_t offset, size_t len)
> -{
> -	struct f2fs_sb_info *sbi = F2FS_F_SB(folio);
> -	unsigned int nr_blocks = f2fs_blocks_per_folio(sbi, folio);
> -	unsigned int start, end, before;
> -
> -	start = F2FS_BYTES_TO_BLK(sbi, offset);
> -	end = min_t(unsigned int, F2FS_BLK_ALIGN(sbi, offset + len), nr_blocks);
> -
> -	before = bitmap_weight(ffs->state, 2 * nr_blocks);
> -	bitmap_set(ffs->state, nr_blocks + start, end - start);
> -	return bitmap_weight(ffs->state, 2 * nr_blocks) - before;
> -}
> -
>   /*
>    * Mark the given range dirty for writeback.
>    *
> @@ -3078,7 +3061,8 @@ void f2fs_ffs_mark_subrange_dirty(struct folio *folio,
>   {
>   	struct f2fs_sb_info *sbi = F2FS_F_SB(folio);
>   	struct f2fs_folio_state *ffs;
> -	unsigned int set;
> +	unsigned int nr_blocks;
> +	unsigned int start, end;
>   	unsigned long flags;
>   
>   	f2fs_bug_on(F2FS_F_SB(folio), offset + len > folio_size(folio));
> @@ -3087,13 +3071,13 @@ void f2fs_ffs_mark_subrange_dirty(struct folio *folio,
>   		return;
>   
>   	ffs = f2fs_ffs_find_or_alloc(folio);
> +	nr_blocks = f2fs_blocks_per_folio(sbi, folio);
> +	start = F2FS_BYTES_TO_BLK(sbi, offset);
> +	end = min_t(unsigned int, F2FS_BLK_ALIGN(sbi, offset + len), nr_blocks);
>   
>   	spin_lock_irqsave(&ffs->state_lock, flags);
> -	set = __ffs_mark_subrange_dirty(folio, ffs, offset, len);
> +	bitmap_set(ffs->state, start + nr_blocks, end - start);
>   	spin_unlock_irqrestore(&ffs->state_lock, flags);
> -
> -	if (set)
> -		f2fs_account_dirty_blocks(folio->mapping->host, set);
>   }
>   
>   void f2fs_ffs_clear_subrange_uptodate(struct folio *folio,
> @@ -3129,19 +3113,16 @@ void f2fs_ffs_clear_subrange_uptodate(struct folio *folio,
>   }
>   
>   static bool __ffs_clear_subrange_dirty(struct folio *folio,
> -			struct f2fs_folio_state *ffs, size_t offset, size_t len,
> -			unsigned int *cleared)
> +			struct f2fs_folio_state *ffs, size_t offset, size_t len)
>   {
>   	struct f2fs_sb_info *sbi = F2FS_F_SB(folio);
>   	unsigned int nr_blocks = f2fs_blocks_per_folio(sbi, folio);
> -	unsigned int start, end, before;
> +	unsigned int start, end;
>   
>   	start = F2FS_BYTES_TO_BLK(sbi, offset);
>   	end = min_t(unsigned int, F2FS_BLK_ALIGN(sbi, offset + len), nr_blocks);
>   
> -	before = bitmap_weight(ffs->state, 2 * nr_blocks);
>   	bitmap_clear(ffs->state, nr_blocks + start, end - start);
> -	*cleared = before - bitmap_weight(ffs->state, 2 * nr_blocks);
>   	return find_next_bit(ffs->state, 2 * nr_blocks, nr_blocks) <
>   			2 * nr_blocks;
>   }
> @@ -3153,15 +3134,13 @@ static bool __ffs_clear_subrange_dirty(struct folio *folio,
>    * @len: byte length of the subrange
>    *
>    * Clear the dirty bits of the 4K subpages covered by [offset, offset + len),
> - * account the cleared subpages against the inode dirty counters, and return
> - * whether the folio still has any dirty subpage left.
> + * and return whether the folio still has any dirty subpage left.
>    */
>   bool f2fs_ffs_clear_subrange_dirty_and_test(struct folio *folio, size_t offset,
>   					    size_t len)
>   {
>   	struct f2fs_folio_state *ffs;
>   	unsigned long flags;
> -	unsigned int cleared = 0;
>   	bool dirty;
>   
>   	if (!f2fs_folio_has_ffs(folio)) {
> @@ -3178,12 +3157,9 @@ bool f2fs_ffs_clear_subrange_dirty_and_test(struct folio *folio, size_t offset,
>   
>   	ffs = f2fs_ffs_find_or_alloc(folio);
>   	spin_lock_irqsave(&ffs->state_lock, flags);
> -	dirty = __ffs_clear_subrange_dirty(folio, ffs, offset, len, &cleared);
> +	dirty = __ffs_clear_subrange_dirty(folio, ffs, offset, len);
>   	spin_unlock_irqrestore(&ffs->state_lock, flags);
>   
> -	if (cleared)
> -		f2fs_account_dirty_blocks(folio->mapping->host, -cleared);
> -
>   	return dirty;
>   }
>   
> @@ -3943,7 +3919,7 @@ int f2fs_do_write_data_page(struct f2fs_io_info *fio)
>   static int f2fs_write_folio_dirty_range(struct folio *folio, int *submitted,
>   					struct writeback_control *wbc,
>   					enum iostat_type io_type,
> -					u64 *pos, u64 len)
> +					u64 start, u64 end)
>   {
>   	struct inode *inode = folio->mapping->host;
>   	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
> @@ -3951,19 +3927,19 @@ static int f2fs_write_folio_dirty_range(struct folio *folio, int *submitted,
>   				folio_test_f2fs_atomic(folio);
>   	struct inode *dn_inode = atomic_commit ?
>   				F2FS_I(inode)->cow_inode : inode;
> -	struct f2fs_folio_state *ffs = NULL;
> -	u64 folio_start = folio_pos(folio);
> -	pgoff_t start_idx = (*pos - folio_start) >> PAGE_SHIFT;
> -	pgoff_t end_idx = (*pos - folio_start + len - 1) >> PAGE_SHIFT;
> +	u64 pos = folio_pos(folio);
> +	pgoff_t start_idx = (start - pos) >> sbi->log_blocksize;
> +	pgoff_t end_idx = (end - 1 - pos) >> sbi->log_blocksize;

F2FS_BYTES_TO_BLK(sbi, start - pos);
F2FS_BYTES_TO_BLK(sbi, end - 1 - pos);

> +	pgoff_t last_idx = 0;
> +	pgoff_t i;
> +	int local_submitted = 0;
>   	int err = 0;
>   
> -	for (pgoff_t i = start_idx; i <= end_idx && !err; i++) {
> +	for (i = start_idx; i <= end_idx; i++) {
>   		struct dnode_of_data dn;
>   		struct node_info ni;
> -		pgoff_t data_idx = folio->index + i;
> +		pgoff_t data_idx = f2fs_folio_lblk(sbi, folio) + i;
>   		bool ipu_force = false;
> -		bool dn_held = false;
> -		bool submitted_blk = false;
>   		struct f2fs_io_info fio = {
>   			.sbi = sbi,
>   			.ino = inode->i_ino,
> @@ -3983,7 +3959,12 @@ static int f2fs_write_folio_dirty_range(struct folio *folio, int *submitted,
>   		};
>   
>   		if (f2fs_folio_has_ffs(folio)) {
> -			ffs = (struct f2fs_folio_state *)folio->private;
> +			struct f2fs_folio_state *ffs =
> +				(struct f2fs_folio_state *)folio->private;
> +
> +			/* Write only dirty blocks */
> +			if (!f2fs_ffs_test_blk_dirty(folio, data_idx))
> +				continue;
>   			atomic_inc(&ffs->write_blocks_pending);
>   		}
>   
> @@ -3995,7 +3976,7 @@ static int f2fs_write_folio_dirty_range(struct folio *folio, int *submitted,
>   			if (!f2fs_is_valid_blkaddr(sbi, fio.old_blkaddr,
>   						   DATA_GENERIC_ENHANCE)) {
>   				err = -EFSCORRUPTED;
> -				goto block_done;
> +				goto skip_block;
>   			}
>   			ipu_force = true;
>   			goto got_it;
> @@ -4015,24 +3996,22 @@ static int f2fs_write_folio_dirty_range(struct folio *folio, int *submitted,
>   						     LOOKUP_NODE);
>   		}
>   		if (err && err != -ENOENT)
> -			goto block_done;
> -		dn_held = true;
> +			goto skip_block;
>   
> -		fio.old_blkaddr = dn.data_blkaddr;
> -
> -		/* This page is already truncated */
> -		if (err == -ENOENT || fio.old_blkaddr == NULL_ADDR) {
> -			f2fs_clear_truncated_page_state(folio, i);
> +		/* This block is already truncated */
> +		if (err || dn.data_blkaddr == NULL_ADDR) {
>   			err = 0;
> -			goto block_done;
> +			f2fs_clear_truncated_page_state(folio, i);
> +			goto skip_block;
>   		}
>   
> +		fio.old_blkaddr = dn.data_blkaddr;
>   got_it:
>   		if (__is_valid_data_blkaddr(fio.old_blkaddr) &&
>   		    !f2fs_is_valid_blkaddr(sbi, fio.old_blkaddr,
>   					   DATA_GENERIC_ENHANCE)) {
>   			err = -EFSCORRUPTED;
> -			goto block_done;
> +			goto skip_block;
>   		}
>   
>   		if (fio.meta_gc)
> @@ -4042,61 +4021,109 @@ static int f2fs_write_folio_dirty_range(struct folio *folio, int *submitted,
>   		    (__is_valid_data_blkaddr(fio.old_blkaddr) &&
>   					    need_inplace_update(&fio))) {
>   			f2fs_put_dnode(&dn);
> -			dn_held = false;
>   			err = f2fs_inplace_write_data(&fio);
> -			trace_f2fs_do_write_data_page(folio, IPU);
>   			if (err)
> -				goto block_done;
> +				goto skip_block_no_dnode;
>   
> -			if (submitted)
> -				(*submitted)++;
> -			submitted_blk = true;
> +			local_submitted++;
> +			last_idx = data_idx;
>   			set_inode_flag(inode, FI_UPDATE_WRITE);
> -			goto block_done;
> +			trace_f2fs_do_write_data_page(folio, IPU);
> +			continue;
>   		}
>   
>   		err = f2fs_get_node_info(sbi, dn.nid, &ni, false);
>   		if (err)
> -			goto block_done;
> +			goto skip_block;
>   
>   		fio.version = ni.version;
>   
>   		f2fs_outplace_write_data(&dn, &fio);
> -		/*
> -		 * The outplace write path failed to submit this block.
> -		 */
> +		f2fs_put_dnode(&dn);
> +		/* The outplace write path failed to submit this block. */
>   		if (!fio.submitted) {
>   			err = -EIO;
> -			goto block_done;
> +			goto skip_block_no_dnode;
>   		}
> -		if (submitted)
> -			(*submitted)++;
> -		submitted_blk = true;
> +		local_submitted++;
> +		last_idx = data_idx;
>   		set_inode_flag(inode, FI_APPEND_WRITE);
>   		trace_f2fs_do_write_data_page(folio, OPU);
> -block_done:
> -		if (dn_held)
> -			f2fs_put_dnode(&dn);
> +		continue;
> +
> +skip_block:
> +		f2fs_put_dnode(&dn);
> +skip_block_no_dnode:
> +		if (f2fs_folio_has_ffs(folio))
> +			f2fs_ffs_put_write_pending(folio, 1);
> +		if (err)
> +			break;
> +	}
> +
> +	if (err) {
>   		/*
> -		 * If this block was not submitted, roll back its
> -		 * write pages pending.
> +		 * The blocks before the failed one were written or skipped, so
> +		 * keep only the failed block and the ones after it dirty.
>   		 */
> -		if (!submitted_blk && ffs)
> -			f2fs_ffs_put_write_pending(folio, 1);
> -		if (!err) {
> -			*pos = ((loff_t)data_idx + 1) << PAGE_SHIFT;
> -			spin_lock(&F2FS_I(inode)->i_size_lock);
> -			if (F2FS_I(inode)->last_disk_size < *pos)
> -				F2FS_I(inode)->last_disk_size = *pos;
> -			spin_unlock(&F2FS_I(inode)->i_size_lock);
> -		} else {
> -			file_set_keep_isize(inode);
> +		if (i > start_idx) {
> +			size_t len = F2FS_BLK_TO_BYTES(sbi, i - start_idx);
> +
> +			f2fs_ffs_clear_subrange_dirty_and_test(folio,
> +							       start - pos, len);
>   		}
> +		file_set_keep_isize(inode);
> +	} else if (local_submitted) {
> +		loff_t psize = (loff_t)(last_idx + 1) << sbi->log_blocksize;

F2FS_BLK_TO_BYTES(sbi, last_idx + 1)

> +
> +		if (psize > i_size_read(inode))
> +			psize = i_size_read(inode);

It seems an improvement, to avoid unnecessary inode write during fsync, can we fix
f2fs_write_single_data_page as well? maybe it's better fix them all in a separated
patch?

> +
> +		spin_lock(&F2FS_I(inode)->i_size_lock);
> +		if (F2FS_I(inode)->last_disk_size < psize)
> +			F2FS_I(inode)->last_disk_size = psize;
> +		spin_unlock(&F2FS_I(inode)->i_size_lock);
>   	}
>   
> +	if (submitted)
> +		*submitted = local_submitted;
>   	return err;
>   }
>   
> +/* Dentry and quota blocks are controlled by checkpoint. */
> +static bool f2fs_cp_controlled_inode(struct inode *inode)
> +{
> +	return S_ISDIR(inode->i_mode) || IS_NOQUOTA(inode);
> +}
> +
> +enum f2fs_wb_action {
> +	F2FS_WB_WRITE,		/* write back the folio */
> +	F2FS_WB_REDIRTY,	/* keep the folio dirty */
> +	F2FS_WB_DROP,		/* drop the dirty data */
> +};
> +
> +/* Decide what to do with a dirty data folio before writing it back. */
> +static enum f2fs_wb_action f2fs_wb_folio_action(struct f2fs_sb_info *sbi,
> +						struct inode *inode)
> +{
> +	/* we should bypass data pages to proceed the kworker jobs */
> +	if (unlikely(f2fs_cp_error(sbi))) {
> +		mapping_set_error(inode->i_mapping, -EIO);
> +		/*
> +		 * Keep dirty dentry folios for the latest directory structure,
> +		 * and keep data folios in remount-ro mode.
> +		 */
> +		if ((S_ISDIR(inode->i_mode) &&
> +		     !is_sbi_flag_set(sbi, SBI_IS_CLOSE)) ||
> +		    F2FS_OPTION(sbi).errors == MOUNT_ERRORS_READONLY)
> +			return F2FS_WB_REDIRTY;
> +		return F2FS_WB_DROP;
> +	}
> +
> +	if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
> +		return F2FS_WB_REDIRTY;
> +	return F2FS_WB_WRITE;
> +}
> +
>   int f2fs_write_single_data_page(struct folio *folio, int *submitted,
>   				struct bio **bio,
>   				sector_t *last_block,
> @@ -4137,26 +4164,15 @@ int f2fs_write_single_data_page(struct folio *folio, int *submitted,
>   
>   	trace_f2fs_writepage(folio, DATA);
>   
> -	/* we should bypass data pages to proceed the kworker jobs */
> -	if (unlikely(f2fs_cp_error(sbi))) {
> -		mapping_set_error(folio->mapping, -EIO);
> -		/*
> -		 * don't drop any dirty dentry pages for keeping lastest
> -		 * directory structure.
> -		 */
> -		if (S_ISDIR(inode->i_mode) &&
> -				!is_sbi_flag_set(sbi, SBI_IS_CLOSE))
> -			goto redirty_out;
> -
> -		/* keep data pages in remount-ro mode */
> -		if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_READONLY)
> -			goto redirty_out;
> +	switch (f2fs_wb_folio_action(sbi, inode)) {
> +	case F2FS_WB_REDIRTY:
> +		goto redirty_out;
> +	case F2FS_WB_DROP:
>   		goto out;
> +	case F2FS_WB_WRITE:
> +		break;
>   	}
>   
> -	if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
> -		goto redirty_out;
> -
>   	if (folio->index < end_index ||
>   			f2fs_verity_in_progress(inode) ||
>   			compr_blocks)
> @@ -4172,8 +4188,7 @@ int f2fs_write_single_data_page(struct folio *folio, int *submitted,
>   
>   	folio_zero_segment(folio, offset, folio_size(folio));
>   write:
> -	/* Dentry/quota blocks are controlled by checkpoint */
> -	if (S_ISDIR(inode->i_mode) || quota_inode) {
> +	if (f2fs_cp_controlled_inode(inode)) {
>   		struct f2fs_lock_context lc;
>   
>   		/*
> @@ -4224,14 +4239,14 @@ int f2fs_write_single_data_page(struct folio *folio, int *submitted,
>   		goto redirty_out;
>   
>   out:
> -	inode_dec_dirty_block(inode);
> +	inode_sub_dirty_blocks(inode, f2fs_blocks_per_folio(sbi, folio));
>   	if (err) {
>   		folio_clear_uptodate(folio);
>   		folio_clear_f2fs_gcing(folio);
>   	}
>   	folio_unlock(folio);
> -	if (!S_ISDIR(inode->i_mode) && !IS_NOQUOTA(inode) &&
> -			!F2FS_I(inode)->wb_task && allow_balance)
> +	if (!f2fs_cp_controlled_inode(inode) &&
> +	    !F2FS_I(inode)->wb_task && allow_balance)
>   		f2fs_balance_fs(sbi, need_balance_fs);
>   
>   	if (unlikely(f2fs_cp_error(sbi))) {
> @@ -4560,6 +4575,7 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   	int submitted = 0;
>   	int nwritten = 0;
>   	bool op_locked = false;
> +	bool node_locked = false;
>   	bool retry = false;
>   
>   	if (get_dirty_blocks(inode) <= SM_I(sbi)->min_hot_blocks)
> @@ -4571,20 +4587,15 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   		struct f2fs_folio_state *ffs = NULL;
>   		u64 isize;
>   		size_t poff;
> -		size_t clear_len;
>   		pgoff_t end_index;
>   		bool verity_in_progress;
>   		int folio_submitted = 0;
>   		bool bias_added = false;
>   
>   		submitted = 0;
> +		err = 0;
>   		retry = false;
>   
> -		if (err) {
> -			folio_redirty_for_writepage(wbc, folio);
> -			goto retry_out;
> -		}
> -
>   		if (atomic_read(&sbi->wb_sync_req[DATA]) &&
>   		    wbc->sync_mode == WB_SYNC_NONE) {
>   			folio_redirty_for_writepage(wbc, folio);
> @@ -4597,11 +4608,22 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   			goto retry_out;
>   		}
>   
> +		if (f2fs_has_subpage_blocks(sbi) &&
> +		    (f2fs_compressed_file(inode) || f2fs_is_atomic_file(inode))) {
> +			WARN_ON_ONCE(1);
> +			mapping_set_error(mapping, -EOPNOTSUPP);
> +			folio_clear_dirty_for_io(folio);
> +			inode_sub_dirty_blocks(inode, f2fs_blocks_per_folio(sbi, folio));
> +			err = -EOPNOTSUPP;
> +			goto retry_out;
> +		}
> +
>   		/*
>   		 * Writing back one large folio performs multiple block allocations, so
>   		 * best-effort balance the filesystem before writing it back.
>   		 */
> -		if (!F2FS_I(inode)->wb_task && has_not_enough_free_secs(sbi, 0, 0)) {
> +		if (!f2fs_cp_controlled_inode(inode) &&
> +		    !F2FS_I(inode)->wb_task && has_not_enough_free_secs(sbi, 0, 0)) {
>   			folio_redirty_for_writepage(wbc, folio);
>   			retry = true;
>   			folio_unlock(folio);
> @@ -4636,31 +4658,39 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   		}
>   
>   		/*
> -		 * If cp_error is set, all subpages of this folio are not
> -		 * written back unless the filesystem is mounted with
> -		 * errors=remount-ro.  The check is placed after the retry
> -		 * block so it also covers cp_error being set while the folio
> -		 * lock was dropped for f2fs_balance_fs() or for the trylock_op()
> -		 * backoff.  PG_dirty is clear at this point, so both the
> -		 * redirty and the drop below leave a coherent state.
> +		 * Checked after the retry block, so that it also covers cp_error
> +		 * being set while the folio lock was dropped for f2fs_balance_fs()
> +		 * or for the trylock_op() backoff.
>   		 */
> -		if (unlikely(f2fs_cp_error(sbi))) {
> -			mapping_set_error(mapping, -EIO);
> -			if (F2FS_OPTION(sbi).errors == MOUNT_ERRORS_READONLY) {
> -				folio_redirty_for_writepage(wbc, folio);
> -				goto retry_out;
> -			}
> -			goto out;
> +		switch (f2fs_wb_folio_action(sbi, inode)) {
> +		case F2FS_WB_REDIRTY:
> +			folio_redirty_for_writepage(wbc, folio);
> +			goto retry_out;
> +		case F2FS_WB_DROP:
> +			f2fs_ffs_clear_subrange_dirty_and_test(folio, 0,
> +						folio_size(folio));
> +			inode_sub_dirty_blocks(inode, f2fs_blocks_per_folio(sbi, folio));
> +			goto retry_out;
> +		case F2FS_WB_WRITE:
> +			break;
>   		}
>   
> -		/* To avoid dealing with the complexity for one subrange is in bio
> -		 * while we trylock_op failed before writing another subrange.
> -		 * Try to lock_op before any subrange write for the folio.
> -		 */
> -		if (!op_locked) {
> +		if (f2fs_cp_controlled_inode(inode)) {
> +			/*
> +			 * Quota writes need node_write to avoid block
> +			 * allocation during checkpoint.
> +			 */
> +			if (IS_NOQUOTA(inode)) {
> +				f2fs_down_read_trace(&sbi->node_write, &lc);
> +				node_locked = true;
> +			}
> +		} else {
> +			/* To avoid dealing with the complexity for one subrange is in bio
> +			 * while we trylock_op failed before writing another subrange.
> +			 * Try to lock_op before any subrange write for the folio.
> +			 */
>   			if (!f2fs_trylock_op(sbi, &lc)) {
>   				folio_redirty_for_writepage(wbc, folio);
> -				err = 0;
>   				if (wbc->sync_mode != WB_SYNC_ALL)
>   					goto retry_out;
>   
> @@ -4690,59 +4720,34 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   		folio_start_writeback(folio);
>   
>   		/*
> -		 * PG_dirty was already cleared by writeback_iter(), and a
> -		 * single-block folio has no ffs dirty bitmap, so the dirty
> -		 * range cannot be derived. Write the whole folio.
> +		 * PG_dirty was already cleared by writeback_iter(). A single-block
> +		 * folio has no ffs, so its whole folio is written.
>   		 */
> -		if (!folio_test_large(folio)) {
> -			err = f2fs_write_folio_dirty_range(folio, &submitted,
> -					wbc, io_type, &pos, end_pos - pos);
> -			folio_submitted += submitted;
> -			if (err)
> -				goto out;
> -			nwritten += submitted;
> -			goto written;
> -		}
> -
> -		if (!f2fs_folio_has_ffs(folio)) {
> -			ffs = f2fs_ffs_find_or_alloc(folio);
> -			f2fs_ffs_mark_subrange_dirty(folio, 0, end_pos - pos);
> -		} else {
> +		if (f2fs_is_multiblock_folio(sbi, folio)) {
> +			if (!f2fs_folio_has_ffs(folio))
> +				f2fs_ffs_mark_subrange_dirty(folio, 0, end_pos - pos);
>   			ffs = (struct f2fs_folio_state *)folio->private;
> +			/*
> +			 * Multiblock folios can be submitted as multiple bios,
> +			 * which may complete while we are still submitting the
> +			 * remaining dirty ranges.  Hold a bias on
> +			 * write_blocks_pending to prevent folio_end_writeback()
> +			 * from being called early.
> +			 */
> +			if (!bias_added) {
> +				WARN_ON_ONCE(atomic_read(&ffs->write_blocks_pending) != 0);
> +				atomic_inc(&ffs->write_blocks_pending);
> +				bias_added = true;
> +			}
>   		}
> -		/*
> -		 * Large folios can be submitted as multiple bios, which may
> -		 * complete while we are still submitting the remaining dirty
> -		 * ranges.  Hold a bias on write_blocks_pending to prevent
> -		 * folio_end_writeback() from being called early.
> -		 */
> -		if (f2fs_folio_has_ffs(folio) && !bias_added) {
> -			WARN_ON_ONCE(atomic_read(&ffs->write_blocks_pending) != 0);
> -			atomic_inc(&ffs->write_blocks_pending);
> -			bias_added = true;
> -		}
> -
> -		unsigned int from = 0, start, count;
> -		unsigned int nr_blocks = f2fs_blocks_per_folio(sbi, folio);
> -		unsigned int to_blk = min_t(unsigned int,
> -				F2FS_BLK_ALIGN(sbi, end_pos - folio_pos(folio)),
> -				nr_blocks);
>   
> -		while (f2fs_ffs_dirty_blk_range(folio, from, to_blk,
> -						&start, &count)) {
> -			submitted = 0;
> -			pos = folio_pos(folio) + F2FS_BLK_TO_BYTES(sbi, start);
> -			err = f2fs_write_folio_dirty_range(folio, &submitted,
> -					wbc, io_type, &pos,
> -					F2FS_BLK_TO_BYTES(sbi, count));
> -			folio_submitted += submitted;
> -			nwritten += submitted;
> -			if (err)
> -				goto out;
> -			from = start + count;
> -		}
> +		err = f2fs_write_folio_dirty_range(folio, &submitted, wbc, io_type,
> +						   pos, end_pos);
> +		folio_submitted += submitted;
> +		nwritten += submitted;
> +		if (err)
> +			goto out;
>   
> -written:
>   		/* If none of the subpages of a folio is submitted, keep the atomic flag */
>   		if (folio_submitted &&
>   		    f2fs_is_atomic_file(inode) &&
> @@ -4750,26 +4755,12 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   			folio_clear_f2fs_atomic(folio);
>   
>   out:
> -		/*
> -		 * Clear the dirty state of the subranges that were written back.
> -		 * If some subranges are still dirty (e.g. the folio was only
> -		 * partially written back), or if the writeback failed, the
> -		 * folio must stay dirty so that the remaining subranges are
> -		 * written back later.
> -		 *
> -		 * On success, clear dirty bits of the whole folio rather than
> -		 * up to isize: subpages beyond EOF can still carry dirty bits
> -		 * (e.g. fill_zero() w/ FALLOC_FL_KEEP_SIZE, BG_GC on blocks
> -		 * beyond EOF, or f2fs_ffs_find_or_alloc() on a dirty folio),
> -		 * they will never be written back, and would keep the folio
> -		 * redirtied forever.
> -		 */
> -		clear_len = err ? pos - folio_pos(folio) : folio_size(folio);
> -		if (f2fs_ffs_clear_subrange_dirty_and_test(folio, 0,
> -						clear_len) || err) {
> +		if (!err) {
> +			f2fs_ffs_clear_subrange_dirty_and_test(folio, 0,
> +						folio_size(folio));
> +			inode_sub_dirty_blocks(inode, f2fs_blocks_per_folio(sbi, folio));
> +		} else {
>   			folio_redirty_for_writepage(wbc, folio);
> -		} else if (!f2fs_folio_has_ffs(folio)) {
> -			inode_dec_dirty_block(inode);
>   		}
>   
>   		/*
> @@ -4780,7 +4771,7 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   			f2fs_ffs_put_write_pending(folio, 1);
>   		} else if (!folio_submitted && folio_test_writeback(folio)) {
>   			/*
> -			 * Order-0 folios take no bias, so end the writeback
> +			 * Single-block folios take no bias, so end the writeback
>   			 * if nothing was submitted.
>   			 */
>   			folio_end_writeback(folio);
> @@ -4792,6 +4783,11 @@ static int f2fs_write_cache_folios(struct address_space *mapping,
>   			op_locked = false;
>   		}
>   
> +		if (node_locked) {
> +			f2fs_up_read_trace(&sbi->node_write, &lc);
> +			node_locked = false;
> +		}
> +
>   		folio_unlock(folio);
>   	}
>   
> @@ -4872,10 +4868,10 @@ static int __f2fs_write_data_pages(struct address_space *mapping,
>   	if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING)))
>   		goto skip_write;
>   
> -	if ((S_ISDIR(inode->i_mode) || IS_NOQUOTA(inode)) &&
> -			wbc->sync_mode == WB_SYNC_NONE &&
> -			get_dirty_blocks(inode) < nr_caches_to_skip(sbi, DATA) &&
> -			f2fs_available_free_memory(sbi, DIRTY_DENTS))
> +	if (f2fs_cp_controlled_inode(inode) &&
> +	    wbc->sync_mode == WB_SYNC_NONE &&
> +	    get_dirty_blocks(inode) < nr_caches_to_skip(sbi, DATA) &&
> +	    f2fs_available_free_memory(sbi, DIRTY_DENTS))
>   		goto skip_write;
>   
>   	/* skip writing in file defragment preparing stage */
> @@ -4902,7 +4898,7 @@ static int __f2fs_write_data_pages(struct address_space *mapping,
>   	account_writeback(inode, true);
>   
>   	blk_start_plug(&plug);
> -	if (mapping_large_folio_support(inode->i_mapping) &&
> +	if (f2fs_has_multiblock_folios(inode) &&
>   	    !f2fs_has_inline_data(inode))
>   		ret = f2fs_write_cache_folios(mapping, wbc, io_type);
>   	else
> @@ -5136,7 +5132,7 @@ static int prepare_atomic_write_begin(struct f2fs_sb_info *sbi,
>   	int err = 0;
>   	block_t ori_blk_addr = NULL_ADDR;
>   	bool cow_has_reserved_block = false;
> -	bool append = ((loff_t)index << PAGE_SHIFT) >= i_size_read(inode);
> +	bool append = F2FS_BLK_TO_BYTES(sbi, index) >= i_size_read(inode);
>   
>   	/*
>   	 * On the first attempt, an append write can skip the COW lookup and
> @@ -5226,7 +5222,7 @@ static bool f2fs_balance_fs_folio_locked(struct folio *folio,
>   
>   static int prepare_large_folio_write_begin(struct inode *inode,
>   					  struct folio *folio, loff_t pos,
> -					  unsigned int len)
> +					  unsigned int len, bool *need_balance)
>   {
>   	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
>   	struct f2fs_folio_state *ffs;
> @@ -5234,9 +5230,13 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>   	size_t need_off = ori_off;
>   	int err = 0;
>   
> +	if (f2fs_has_subpage_blocks(sbi) &&
> +	    (f2fs_compressed_file(inode) || f2fs_is_atomic_file(inode)))
> +		return -EOPNOTSUPP;
> +
>   	/*
> -	 * when the write did not come through f2fs_write_iter, we preallocate all
> -	 * blocks in the write range here.
> +	 * Reserve blocks without f2fs_balance_fs() since the folio is locked;
> +	 * the caller balances after unlocking it if *need_balance is set.

I think there is potential risk if we don't allow unlock -> f2fs_balance_fs, e.g.
multiple threads call write_begin to write high order large folio concurrently will
consume free blocks quickly, it may run out of free segments potentially w/o fggc.
thoughts?

Thanks,

>   	 */
>   	if (!is_inode_flag_set(inode, FI_PREALLOCATED_ALL) &&
>   	    !IS_DEVICE_ALIASING(inode)) {
> @@ -5252,9 +5252,7 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>   						   &node_changed);
>   			if (err)
>   				return err;
> -			if (f2fs_balance_fs_folio_locked(folio, folio->mapping,
> -						   node_changed))
> -				return -EAGAIN;
> +			*need_balance |= node_changed;
>   		}
>   	}
>   
> @@ -5266,8 +5264,12 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>   	if (folio_test_uptodate(folio) || len == folio_size(folio))
>   		return 0;
>   
> +	/*
> +	 * Skip read if the write starts at a block boundary and reaches EOF,
> +	 * and zero the rest of the folio instead.
> +	 */
>   	if (!f2fs_verity_in_progress(inode) &&
> -	    !(pos & (PAGE_SIZE - 1)) && (pos + len) >= i_size_read(inode)) {
> +	    !(pos & (F2FS_BLKSIZE(sbi) - 1)) && (pos + len) >= i_size_read(inode)) {
>   		size_t zoff = ori_off + len;
>   
>   		if (zoff < folio_size(folio))
> @@ -5281,7 +5283,8 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>   	while (find_next_valid_block(folio, ori_off,
>   				     &need_off, len)) {
>   		struct dnode_of_data dn;
> -		pgoff_t index = folio->index + (need_off >> PAGE_SHIFT);
> +		pgoff_t index = f2fs_folio_lblk(sbi, folio) +
> +				F2FS_BYTES_TO_BLK(sbi, need_off);
>   		block_t blkaddr;
>   
>   		if (!f2fs_lookup_read_extent_cache_block(inode, index,
> @@ -5297,12 +5300,13 @@ static int prepare_large_folio_write_begin(struct inode *inode,
>   			f2fs_put_dnode(&dn);
>   
>   			if (blkaddr == NEW_ADDR) {
> -				size_t off = offset_in_folio(folio,
> -							     (loff_t)index << PAGE_SHIFT);
> +				size_t off = f2fs_folio_byte_offset(sbi,
> +								folio, index);
>   
> -				folio_zero_segment(folio, off, off + PAGE_SIZE);
> +				folio_zero_segment(folio, off,
> +						   off + F2FS_BLKSIZE(sbi));
>   				f2fs_ffs_mark_subrange_uptodate(folio, off,
> -							       PAGE_SIZE);
> +							       F2FS_BLKSIZE(sbi));
>   				continue;
>   			}
>   		}
> @@ -5401,12 +5405,18 @@ static int f2fs_write_begin(const struct kiocb *iocb,
>   		goto fail;
>   	}
>   
> +	if (f2fs_has_subpage_blocks(sbi) &&
> +	    (f2fs_compressed_file(inode) || f2fs_is_atomic_file(inode))) {
> +		err = -EOPNOTSUPP;
> +		goto fail;
> +	}
> +
>   	/*
>   	 * We should check this at this moment to avoid deadlock on inode page
>   	 * and #0 page. The locking rule for inline_data conversion should be:
>   	 * folio_lock(folio #0) -> folio_lock(inode_page)
>   	 */
> -	if (index != 0) {
> +	if (index != 0 || f2fs_has_subpage_blocks(sbi)) {
>   		err = f2fs_convert_inline_inode(inode);
>   		if (err)
>   			goto fail;
> @@ -5469,17 +5479,19 @@ static int f2fs_write_begin(const struct kiocb *iocb,
>   	 */
>   	f2fs_folio_wait_writeback(folio, false, true);
>   
> -	if (f2fs_is_atomic_file(inode) && !folio_test_large(folio))
> +	if (f2fs_is_atomic_file(inode) && !f2fs_is_multiblock_folio(sbi, folio)) {
>   		err = prepare_atomic_write_begin(sbi, inode, folio->index,
>   					&blkaddr, &need_balance, retried);
> -	else if (!folio_test_large(folio))
> +	} else if (!f2fs_is_multiblock_folio(sbi, folio)) {
>   		err = prepare_write_begin(sbi, folio, pos, blen,
>   					&blkaddr, &need_balance);
> -	else if (f2fs_is_atomic_file(inode))
> +	} else if (f2fs_is_atomic_file(inode)) {
>   		err = prepare_large_folio_atomic_write_begin(inode,
>   					mapping, folio, pos, len, retried);
> -	else
> -		err = prepare_large_folio_write_begin(inode, folio, pos, blen);
> +	} else {
> +		err = prepare_large_folio_write_begin(inode, folio, pos, blen,
> +						      &need_balance);
> +	}
>   	if (err) {
>   		if (err == -EAGAIN) {
>   			retried = true;
> @@ -5495,7 +5507,7 @@ static int f2fs_write_begin(const struct kiocb *iocb,
>   		goto repeat;
>   	}
>   
> -	if (folio_test_large(folio))
> +	if (f2fs_is_multiblock_folio(sbi, folio))
>   		return 0;
>   
>   	if (blen == folio_size(folio) || folio_test_uptodate(folio))
> @@ -5565,7 +5577,7 @@ static int f2fs_write_end(const struct kiocb *iocb,
>   	if (!folio_test_uptodate(folio)) {
>   		if (unlikely(copied != len)) {
>   			copied = 0;
> -		} else if (folio_test_large(folio)) {
> +		} else if (f2fs_is_multiblock_folio(F2FS_I_SB(inode), folio)) {
>   			f2fs_ffs_mark_subrange_uptodate(folio,
>   					offset_in_folio(folio, pos), len);
>   		} else {
> @@ -5590,7 +5602,7 @@ static int f2fs_write_end(const struct kiocb *iocb,
>   	if (!copied)
>   		goto unlock_out;
>   
> -	if (folio_test_large(folio))
> +	if (f2fs_is_multiblock_folio(F2FS_I_SB(inode), folio))
>   		f2fs_ffs_mark_subrange_dirty(folio, offset_in_folio(folio, pos),
>   								   copied);
>   	folio_mark_dirty(folio);
> @@ -5642,6 +5654,9 @@ void f2fs_invalidate_folio(struct folio *folio, size_t offset, size_t length)
>   		 * dirty state.
>   		 */
>   		if (!dirty && folio_test_dirty(folio)) {
> +			inode_sub_dirty_blocks(inode,
> +					f2fs_blocks_per_folio(sbi, folio));
> +			f2fs_remove_dirty_inode(inode);
>   			f2fs_clear_page_cache_dirty_tag(folio);
>   			folio_cancel_dirty(folio);
>   		}
> @@ -5650,11 +5665,8 @@ void f2fs_invalidate_folio(struct folio *folio, size_t offset, size_t length)
>   		return;
>   	}
>   
> -	if (f2fs_folio_has_ffs(folio)) {
> -		f2fs_ffs_clear_subrange_dirty_and_test(folio, 0,
> -							folio_size(folio));
> -	} else if (folio_test_dirty(folio)) {
> -		inode_dec_dirty_block(inode);
> +	if (folio_test_dirty(folio)) {
> +		inode_sub_dirty_blocks(inode, f2fs_blocks_per_folio(sbi, folio));
>   		f2fs_remove_dirty_inode(inode);
>   	}
>   
> diff --git a/fs/f2fs/dir.c b/fs/f2fs/dir.c
> index aa3511c6f8fe..7f5dc2354a27 100644
> --- a/fs/f2fs/dir.c
> +++ b/fs/f2fs/dir.c
> @@ -979,9 +979,9 @@ void f2fs_delete_entry(struct f2fs_dir_entry *dentry, void *dentry_block,
>   		if (!f2fs_has_subpage_blocks(sbi)) {
>   			folio_clear_uptodate(folio);
>   			folio_detach_private(folio);
> -			inode_dec_dirty_block(dir);
>   		}
>   
> +		inode_sub_dirty_blocks(dir, f2fs_blocks_per_folio(sbi, folio));
>   		f2fs_remove_dirty_inode(dir);
>   	}
>   keep_dirty:
> diff --git a/fs/f2fs/f2fs.h b/fs/f2fs/f2fs.h
> index cf7294068415..aab1095ad5ff 100644
> --- a/fs/f2fs/f2fs.h
> +++ b/fs/f2fs/f2fs.h
> @@ -4457,7 +4457,6 @@ void f2fs_remove_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino);
>   int f2fs_recover_orphan_inodes(struct f2fs_sb_info *sbi);
>   int f2fs_get_valid_checkpoint(struct f2fs_sb_info *sbi);
>   void f2fs_update_dirty_folio(struct inode *inode, struct folio *folio);
> -void f2fs_account_dirty_blocks(struct inode *inode, int nr);
>   void f2fs_remove_dirty_inode(struct inode *inode);
>   int f2fs_sync_dirty_inodes(struct f2fs_sb_info *sbi, enum inode_type type,
>   								bool from_cp);
> diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c
> index 1ce4553b1d01..be548f39e33e 100644
> --- a/fs/f2fs/file.c
> +++ b/fs/f2fs/file.c
> @@ -5561,12 +5561,13 @@ static int f2fs_preallocate_blocks(struct kiocb *iocb, struct iov_iter *iter,
>   			return ret;
>   	}
>   
> -	if (!dio && mapping_large_folio_support(inode->i_mapping)) {
> +	if (!dio && f2fs_has_multiblock_folios(inode)) {
>   		/*
> -		 * Preallocate all blocks touched by a large-folio buffered write so
> -		 * the regular write_begin path does not need to unlock the folio for
> -		 * f2fs_balance_fs().  Rechecking large-folio state after unlock is
> -		 * unreliable since partial truncation can split the folio.
> +		 * Preallocate all blocks touched by a multiblock-folio buffered
> +		 * write so the regular write_begin path does not need to unlock
> +		 * the folio for f2fs_balance_fs().  Rechecking large-folio
> +		 * state after unlock is unreliable since partial truncation can
> +		 * split the folio.
>   		 */
>   		map.m_lblk = F2FS_BYTES_TO_BLK(sbi, pos);
>   		map.m_len = F2FS_BLK_ALIGN(sbi, pos + count);
> diff --git a/fs/f2fs/gc.c b/fs/f2fs/gc.c
> index 6c24001325b2..f041a036836e 100644
> --- a/fs/f2fs/gc.c
> +++ b/fs/f2fs/gc.c
> @@ -1608,13 +1608,12 @@ static int move_data_cache_block(struct inode *inode, block_t bidx, int gc_type,
>   			f2fs_ffs_mark_subrange_dirty(folio, foff, F2FS_BLKSIZE(sbi));
>   		folio_mark_dirty(folio);
>   		if (folio_clear_dirty_for_io(folio)) {
> -			if (!has_ffs) {
> -				inode_dec_dirty_block(inode);
> -				f2fs_remove_dirty_inode(inode);
> -			} else if (f2fs_ffs_clear_subrange_dirty_and_test(folio,
> -						foff, F2FS_BLKSIZE(sbi))) {
> +			inode_sub_dirty_blocks(inode, f2fs_blocks_per_folio(sbi, folio));
> +			f2fs_remove_dirty_inode(inode);
> +			if (has_ffs &&
> +			    f2fs_ffs_clear_subrange_dirty_and_test(folio, foff,
> +							F2FS_BLKSIZE(sbi)))
>   				folio_mark_dirty(folio);
> -			}
>   		}
>   
>   		folio_set_f2fs_gcing(folio);
> diff --git a/fs/f2fs/inline.c b/fs/f2fs/inline.c
> index ba0aff8e5643..757be9f8d069 100644
> --- a/fs/f2fs/inline.c
> +++ b/fs/f2fs/inline.c
> @@ -196,10 +196,15 @@ int f2fs_convert_inline_folio(struct dnode_of_data *dn, struct folio *folio)
>   	folio_start_writeback(folio);
>   	fio.old_blkaddr = dn->data_blkaddr;
>   	set_inode_flag(dn->inode, FI_HOT_DATA);
> +	if (f2fs_is_multiblock_folio(fio.sbi, folio)) {
> +		struct f2fs_folio_state *ffs = f2fs_ffs_find_or_alloc(folio);
> +
> +		atomic_inc(&ffs->write_blocks_pending);
> +	}
>   	f2fs_outplace_write_data(dn, &fio);
>   	f2fs_folio_wait_writeback(folio, true, true);
>   	if (dirty) {
> -		inode_dec_dirty_block(dn->inode);
> +		inode_sub_dirty_blocks(dn->inode, f2fs_blocks_per_folio(fio.sbi, folio));
>   		f2fs_remove_dirty_inode(dn->inode);
>   	}
>   
> diff --git a/fs/f2fs/node.c b/fs/f2fs/node.c
> index c48b12208e20..b95bb5c27b0b 100644
> --- a/fs/f2fs/node.c
> +++ b/fs/f2fs/node.c
> @@ -1700,7 +1700,7 @@ static void flush_inline_data(struct f2fs_sb_info *sbi, nid_t ino)
>   		goto folio_out;
>   
>   	ret = f2fs_write_inline_data(inode, folio);
> -	inode_dec_dirty_block(inode);
> +	inode_sub_dirty_blocks(inode, f2fs_blocks_per_folio(sbi, folio));
>   	f2fs_remove_dirty_inode(inode);
>   	if (ret)
>   		folio_mark_dirty(folio);


  reply	other threads:[~2026-11-06  2:52 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02 19:39 [PATCH 00/20] f2fs: support block size smaller than PAGE_SIZE Daeho Jeong
2026-11-02 19:39 ` [PATCH 01/20] f2fs: use block-based index and size calculations in remaining paths Daeho Jeong
2026-11-03  7:04   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 02/20] f2fs: support subpage block size superblock and mount Daeho Jeong
2026-11-03  9:26   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 03/20] f2fs: extend f2fs_folio_state for subpage blocks Daeho Jeong
2026-11-04  9:19   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 04/20] f2fs: support subpage blocks in directory operations Daeho Jeong
2026-11-04 11:13   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 05/20] f2fs: convert inode dirty page counters to dirty block counters Daeho Jeong
2026-11-04 11:24   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 06/20] f2fs: support subpage blocks in data read path and GC Daeho Jeong
2026-11-05  7:55   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 07/20] f2fs: support subpage blocks in data write path and writeback Daeho Jeong
2026-11-06  2:52   ` Chao Yu [this message]
2026-11-02 19:39 ` [PATCH 08/20] f2fs: adapt range operations and mmap for subpage blocks Daeho Jeong
2026-11-06  3:54   ` [f2fs-dev] " Chao Yu
2026-11-02 19:39 ` [PATCH 09/20] f2fs: support subpage blocks in pinned files Daeho Jeong
2026-11-02 19:39 ` [PATCH 10/20] f2fs: support subpage blocks in collapse and insert range Daeho Jeong
2026-11-02 19:39 ` [PATCH 11/20] f2fs: support subpage blocks in move range Daeho Jeong
2026-11-02 19:39 ` [PATCH 12/20] f2fs: support subpage blocks in defragmentation Daeho Jeong
2026-11-02 19:39 ` [PATCH 13/20] f2fs: support subpage blocks in swapfile activation Daeho Jeong
2026-11-02 19:39 ` [PATCH 14/20] f2fs: support subpage blocks in fs-verity enablement Daeho Jeong
2026-11-02 19:39 ` [PATCH 15/20] f2fs: support subpage blocks in atomic writes Daeho Jeong
2026-11-02 19:39 ` [PATCH 16/20] f2fs: preserve inline data in subpage block mode Daeho Jeong
2026-11-02 19:39 ` [PATCH 17/20] f2fs: support subpage blocks in online resize Daeho Jeong
2026-11-02 19:39 ` [PATCH 18/20] f2fs: support subpage blocks in compression Daeho Jeong
2026-11-02 19:39 ` [PATCH 19/20] f2fs: count compress cache entries in pages for its memory limit Daeho Jeong
2026-11-02 19:39 ` [PATCH 20/20] f2fs: enable subpage block size support Daeho Jeong

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=164909bf-39e8-421e-b9c6-f2bac0391f39@kernel.org \
    --to=chao@kernel.org \
    --cc=daeho43@gmail.com \
    --cc=daehojeong@google.com \
    --cc=kernel-team@android.com \
    --cc=linux-f2fs-devel@lists.sourceforge.net \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®