mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Chao Yu <chao@kernel.org>
To: Daeho Jeong <daeho43@gmail.com>
Cc: chao@kernel.org, linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net, kernel-team@android.com,
	Daeho Jeong <daehojeong@google.com>
Subject: Re: [f2fs-dev] [PATCH 07/20] f2fs: support subpage blocks in data write path and writeback
Date: Wed, 11 Nov 2026 14:40:25 +0800	[thread overview]
Message-ID: <31dfb7c7-5730-4ac5-b9ea-790ef42e14af@kernel.org> (raw)
In-Reply-To: <CACOAw_wu7bXCocK-jOeAe61r0z7FMhsFeQWsN6VeQSUXkKDaQA@mail.gmail.com>

On 11/11/26 03:50, Daeho Jeong wrote:
> On Tue, Nov 10, 2026 at 3:19 AM Chao Yu <chao@kernel.org> wrote:
>>
>> On 11/8/26 07:14, Daeho Jeong wrote:
>>> On Thu, Nov 5, 2026 at 6:52 PM Chao Yu <chao@kernel.org> wrote:
>>>>
>>>> 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?
>>>
>>> Hi Chao,
>>
>> Hi Daeho,
>>
>> Thanks for the detailed explanation.
>>
>>>
>>> Counting dirty bits needs the folio state on every dirty multi-block
>>> folio, but a folio can be dirtied through ->dirty_folio(), which cannot
>>> allocate it: ->dirty_folio() must not block and can be called under
>>> the page table lock without the folio lock, and it has no range, so the
>>> whole folio becomes dirty. E.g. a folio read by a single bio has no
>>> folio state, and it can then be dirtied by GUP or mmap. When the folio
>>> state is attached later, its dirty bits are set from PG_dirty, and
>>> these bits were never counted, so clearing them unbalances the counter.
>>
>> I think for such case that GUP or mmap set folio dirty, since the entire folio
>> is dirty:
>> 1. w/o ffs, we can just increase reference w/ blks_per_folio.
>> 2. w/ ffs, we can update fss dirty bitmap, and increase reference w/ blks count
>> that they are not dirty previously.
>>
>>>
>>> Large folios worked because the folio state was attached up front, but
>>> in subpage mode every order-0 folio is a multi-block folio, and we
>>> cannot attach it in every path that creates or dirties a folio.
>>> Counting per folio adds and removes the same number when the folio is
>>> dirtied and cleaned, so it always stays balanced. A partially dirty
>>> folio is counted as fully dirty, but the counters are only compared
>>> with thresholds such as min_fsync_blocks, and order-0 folios were
>>> counted per page before, so this is enough.
>>
>> However there is a side-effect, F2FS_DIRTY_DENTS will not match the real
>> dirty dentry block count? result in has_not_enough_free_secs() return
>> true earlier and trigger unnecessary fggc?
>>
> 
> Hi Chao,
> 
> In v1, I tried a simpler way to make it work first. Now, we could take the way

Hi Daeho,

Sure, it's doable to improve it this step by step.

> you say. If a folio is counted as blks_per_folio without a folio state, and
> the count is adjusted to its dirty bits when a folio state is attached, the
> counter stays balanced.
> 
> In v2, I'll count the dirty bits again:
> - ->dirty_folio() marks the uptodate blocks within EOF dirty and counts the
>   newly dirty ones for a folio with a folio state, and counts
>   blks_per_folio for a folio without it.
> - The updates made by f2fs itself mark and count only the blocks they
>   modify, and do not go through ->dirty_folio().
> - Attaching a folio state to a dirty folio converts its count to the dirty
>   bits.

Looks good to me.

> 
>>>
>>>>
>>>>> - 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?
>>>
>>> Agreed. f2fs_write_single_data_page() and the compressed write path also
>>> set last_disk_size beyond i_size. Besides the extra inode write, if
>>> i_size grows to the recorded value later, f2fs_skip_inode_update() can
>>> see last_disk_size == i_size even though the inode on disk still has
>>> the old size. I will fix all of them in a separate 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?
>>>
>>> f2fs_balance_fs() is still called. f2fs_write_begin() calls
>>> f2fs_balance_fs_folio_locked() right after this, which unlocks the
>>> folio, calls f2fs_balance_fs() and locks it again, under the same
>>> condition (node_changed) as before. It is only moved to the caller, so
>>> that the order-0 and multi-block paths balance in one place. I will
>>> make the comment clearer in v2.
>>
>> Actually, what I meant is it changed
>> from a. one f2fs_balance_fs() after one __reserve_data_block()
>> to b. one f2fs_balance_fs() after in-batch __reserve_data_block()
>>
>> So in high order large folio, in prepare_large_folio_write_begin() we may reserve
>> hundreds of blocks, and then a following f2fs_balance_fs(), which may be late,
>> especially in a small size image, mutliple threads write high order folio concurrently,
>> it may run out of free section, due to we missed to call f2fs_balance_fs() in time?
>>
>> Thanks,
>>
> 
> I see, you're right. It was changed because f2fs_vm_page_mkwrite() also
> calls prepare_large_folio_write_begin() with subpage blocks, and it can
> neither unlock the folio nor return -EAGAIN there. It calls
> f2fs_balance_fs() before locking the folio, as before.

Alright, will we suffer any issue once we unlock folio -> balance_fs -> lock folio
-> continue?

Maybe we need to do some tests on this mkwrite case w/ below scenario to check whether
there is potential ENOSPC issue as well:
- 4k block + 16k page + maybe enable large folio and unlimit max order
- small sized image
- less free section
- multiple threads call mkwrite concurrently

> 
> But there is no reason to change it for write_begin. In v2, I'll call
> f2fs_balance_fs_folio_locked() after each __reserve_data_block() again when
> it is called from f2fs_write_begin(), and skip it only for page_mkwrite.

It makes sense to me.

Thanks,

> 
> Thanks,
> 
>>>
>>> Thanks,
>>>
>>>>
>>>> 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-11  6:40 UTC|newest]

Thread overview: 44+ 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-06 20:53     ` Daeho Jeong
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-06 21:29     ` Daeho Jeong
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   ` [f2fs-dev] " Chao Yu
2026-11-07 23:14     ` Daeho Jeong
2026-11-10 11:19       ` Chao Yu
2026-11-10 19:50         ` Daeho Jeong
2026-11-11  6:40           ` 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-07 23:24     ` Daeho Jeong
2026-11-10 11:35       ` 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-09  2:56   ` [f2fs-dev] " Chao Yu
2026-11-10 11:49   ` Chao Yu
2026-11-10 21:28     ` Daeho Jeong
2026-11-02 19:39 ` [PATCH 16/20] f2fs: preserve inline data in subpage block mode Daeho Jeong
2026-11-09  3:18   ` [f2fs-dev] " Chao Yu
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-10  3:04   ` [f2fs-dev] " Chao Yu
2026-11-10 18:45     ` Daeho Jeong
2026-11-11  2:07       ` Chao Yu
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=31dfb7c7-5730-4ac5-b9ea-790ef42e14af@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®