From 8ffe56b104c5a9cdb88e3c7b2d84fa70d8866e64 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Fri, 5 Dec 2025 09:59:04 +0800 Subject: exfat: improve error code handling in exfat_find_empty_entry() Change the type of 'ret' from unsigned int to int in exfat_find_empty_entry(). Although the implicit type conversion (int -> unsigned int -> int) does not cause actual bugs in practice, using int directly is more appropriate for storing error codes returned by exfat_alloc_cluster(). This improves code clarity and consistency with standard error handling practices. Signed-off-by: Haotian Zhang Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- fs/exfat/namei.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index dfe957493d49..670116ae9ec8 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -304,8 +304,8 @@ int exfat_find_empty_entry(struct inode *inode, struct exfat_chain *p_dir, int num_entries, struct exfat_entry_set_cache *es) { - int dentry; - unsigned int ret, last_clu; + int dentry, ret; + unsigned int last_clu; loff_t size = 0; struct exfat_chain clu; struct super_block *sb = inode->i_sb; -- cgit v1.2.3 From 0914882bdda645e10cf536b474631e1a023b67c0 Mon Sep 17 00:00:00 2001 From: Yuling Dong Date: Thu, 15 Jan 2026 13:05:23 +0800 Subject: exfat: reduce unnecessary writes during mmap write During mmap write, exfat_page_mkwrite() currently extends valid_size to the end of the VMA range. For a large mapping, this can push valid_size far beyond the page that actually triggered the fault, resulting in unnecessary writes. valid_size only needs to extend to the end of the page being written. Signed-off-by: Yuling Dong Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/file.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/fs/exfat/file.c b/fs/exfat/file.c index b60ee0e1bec9..c0a19f8716e6 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -708,21 +708,18 @@ static ssize_t exfat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) { int err; - struct vm_area_struct *vma = vmf->vma; - struct file *file = vma->vm_file; - struct inode *inode = file_inode(file); + struct inode *inode = file_inode(vmf->vma->vm_file); struct exfat_inode_info *ei = EXFAT_I(inode); - loff_t start, end; + loff_t new_valid_size; if (!inode_trylock(inode)) return VM_FAULT_RETRY; - start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT); - end = min_t(loff_t, i_size_read(inode), - start + vma->vm_end - vma->vm_start); + new_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT; + new_valid_size = min(new_valid_size, i_size_read(inode)); - if (ei->valid_size < end) { - err = exfat_extend_valid_size(inode, end); + if (ei->valid_size < new_valid_size) { + err = exfat_extend_valid_size(inode, new_valid_size); if (err < 0) { inode_unlock(inode); return vmf_fs_error(err); -- cgit v1.2.3 From 967288e9a6f2ca88cf7d004d87bb3aa9db64fbe2 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:37 +0800 Subject: exfat: add cache option for __exfat_ent_get When multiple entries are obtained consecutively, these entries are mostly stored adjacent to each other. this patch introduces a "last" parameter to cache the last opened buffer head, and reuse it when possible, which reduces the number of sb_bread() calls. When the passed parameter "last" is NULL, it means cache option is disabled, the behavior unchanged as it was. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/fatent.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index c9c5f2e3a05e..0cfbc0b435bd 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -36,18 +36,23 @@ static int exfat_mirror_bh(struct super_block *sb, sector_t sec, } static int __exfat_ent_get(struct super_block *sb, unsigned int loc, - unsigned int *content) + unsigned int *content, struct buffer_head **last) { unsigned int off; sector_t sec; - struct buffer_head *bh; + struct buffer_head *bh = last ? *last : NULL; sec = FAT_ENT_OFFSET_SECTOR(sb, loc); off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc); - bh = sb_bread(sb, sec); - if (!bh) - return -EIO; + if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) { + brelse(bh); + bh = sb_bread(sb, sec); + if (last) + *last = bh; + if (unlikely(!bh)) + return -EIO; + } *content = le32_to_cpu(*(__le32 *)(&bh->b_data[off])); @@ -55,7 +60,8 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc, if (*content > EXFAT_BAD_CLUSTER) *content = EXFAT_EOF_CLUSTER; - brelse(bh); + if (!last) + brelse(bh); return 0; } @@ -95,7 +101,7 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc, return -EIO; } - err = __exfat_ent_get(sb, loc, content); + err = __exfat_ent_get(sb, loc, content, NULL); if (err) { exfat_fs_error_ratelimit(sb, "failed to access to FAT (entry 0x%08x, err:%d)", -- cgit v1.2.3 From 5cf0288f0da3ad1422690f9602149f023abffe9b Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:38 +0800 Subject: exfat: support reuse buffer head for exfat_ent_get This patch is part 2 of cached buffer head for exfat_ent_get, it introduces an argument for exfat_ent_get, and make sure this routine releases buffer head refcount when any error return. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 2 +- fs/exfat/exfat_fs.h | 4 ++-- fs/exfat/fatent.c | 39 ++++++++++++++++++++++++--------------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index d5ce0ae660ba..61af3fa05ab7 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -287,7 +287,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, return -EIO; } - if (exfat_ent_get(sb, *dclus, &content)) + if (exfat_ent_get(sb, *dclus, &content, NULL)) return -EIO; *last_dclus = *dclus; diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 176fef62574c..f7f25e0600c7 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -432,13 +432,13 @@ int exfat_set_volume_dirty(struct super_block *sb); int exfat_clear_volume_dirty(struct super_block *sb); /* fatent.c */ -#define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu) +#define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu, NULL) int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, struct exfat_chain *p_chain, bool sync_bmap); int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain); int exfat_ent_get(struct super_block *sb, unsigned int loc, - unsigned int *content); + unsigned int *content, struct buffer_head **last); int exfat_ent_set(struct super_block *sb, unsigned int loc, unsigned int content); int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index 0cfbc0b435bd..679688cfea01 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -88,49 +88,58 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, return 0; } +/* + * Caller must release the buffer_head if no error return. + */ int exfat_ent_get(struct super_block *sb, unsigned int loc, - unsigned int *content) + unsigned int *content, struct buffer_head **last) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - int err; if (!is_valid_cluster(sbi, loc)) { exfat_fs_error_ratelimit(sb, "invalid access to FAT (entry 0x%08x)", loc); - return -EIO; + goto err; } - err = __exfat_ent_get(sb, loc, content, NULL); - if (err) { + if (unlikely(__exfat_ent_get(sb, loc, content, last))) { exfat_fs_error_ratelimit(sb, - "failed to access to FAT (entry 0x%08x, err:%d)", - loc, err); - return err; + "failed to access to FAT (entry 0x%08x)", + loc); + goto err; } - if (*content == EXFAT_FREE_CLUSTER) { + if (unlikely(*content == EXFAT_FREE_CLUSTER)) { exfat_fs_error_ratelimit(sb, "invalid access to FAT free cluster (entry 0x%08x)", loc); - return -EIO; + goto err; } - if (*content == EXFAT_BAD_CLUSTER) { + if (unlikely(*content == EXFAT_BAD_CLUSTER)) { exfat_fs_error_ratelimit(sb, "invalid access to FAT bad cluster (entry 0x%08x)", loc); - return -EIO; + goto err; } if (*content != EXFAT_EOF_CLUSTER && !is_valid_cluster(sbi, *content)) { exfat_fs_error_ratelimit(sb, "invalid access to FAT (entry 0x%08x) bogus content (0x%08x)", loc, *content); - return -EIO; + goto err; } return 0; +err: + if (last) { + brelse(*last); + + /* Avoid double release */ + *last = NULL; + } + return -EIO; } int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, @@ -299,7 +308,7 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, do { count++; clu = next; - if (exfat_ent_get(sb, clu, &next)) + if (exfat_ent_get(sb, clu, &next, NULL)) return -EIO; } while (next != EXFAT_EOF_CLUSTER && count <= p_chain->size); @@ -490,7 +499,7 @@ int exfat_count_num_clusters(struct super_block *sb, count = 0; for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters; i++) { count++; - if (exfat_ent_get(sb, clu, &clu)) + if (exfat_ent_get(sb, clu, &clu, NULL)) return -EIO; if (clu == EXFAT_EOF_CLUSTER) break; -- cgit v1.2.3 From 06805f4c57840c6957b1c2064d3b453e66e943ab Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:39 +0800 Subject: exfat: improve exfat_count_num_clusters Since exfat_ent_get support cache buffer head, let's apply it to exfat_count_num_clusters. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/fatent.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index 679688cfea01..f060eab2f2f2 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -484,6 +484,7 @@ int exfat_count_num_clusters(struct super_block *sb, unsigned int i, count; unsigned int clu; struct exfat_sb_info *sbi = EXFAT_SB(sb); + struct buffer_head *bh = NULL; if (!p_chain->dir || p_chain->dir == EXFAT_EOF_CLUSTER) { *ret_count = 0; @@ -499,12 +500,13 @@ int exfat_count_num_clusters(struct super_block *sb, count = 0; for (i = EXFAT_FIRST_CLUSTER; i < sbi->num_clusters; i++) { count++; - if (exfat_ent_get(sb, clu, &clu, NULL)) + if (exfat_ent_get(sb, clu, &clu, &bh)) return -EIO; if (clu == EXFAT_EOF_CLUSTER) break; } + brelse(bh); *ret_count = count; /* -- cgit v1.2.3 From 5e205c484b1feb3297f78351f3866cb60afe004b Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:40 +0800 Subject: exfat: improve exfat_find_last_cluster Since exfat_ent_get support cache buffer head, let's apply it to exfat_find_last_cluster. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/fatent.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index f060eab2f2f2..71ee16479c43 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -296,6 +296,7 @@ int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain) int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, unsigned int *ret_clu) { + struct buffer_head *bh = NULL; unsigned int clu, next; unsigned int count = 0; @@ -308,10 +309,11 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, do { count++; clu = next; - if (exfat_ent_get(sb, clu, &next, NULL)) + if (exfat_ent_get(sb, clu, &next, &bh)) return -EIO; } while (next != EXFAT_EOF_CLUSTER && count <= p_chain->size); + brelse(bh); if (p_chain->size != count) { exfat_fs_error(sb, "bogus directory size (clus : ondisk(%d) != counted(%d))", -- cgit v1.2.3 From 2e21557d5422a24e0c2c12455ae8b9f10045e3d5 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:41 +0800 Subject: exfat: remove the check for infinite cluster chain loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The infinite cluster chain loop check is not work because the loop will terminate when fclus reaches the parameter cluster, and the parameter cluster value is never greater than ei->valid_size. The following relationship holds: 'fclus' < 'cluster' ≤ ei->valid_size ≤ sb->num_clusters The check would only be triggered if a cluster number greater than sb->num_clusters is passed, but no caller currently does this. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index 61af3fa05ab7..0ee4bff1cb35 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -238,8 +238,6 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, unsigned int *last_dclus, int allow_eof) { struct super_block *sb = inode->i_sb; - struct exfat_sb_info *sbi = EXFAT_SB(sb); - unsigned int limit = sbi->num_clusters; struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_cache_id cid; unsigned int content; @@ -279,14 +277,6 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, return 0; while (*fclus < cluster) { - /* prevent the infinite loop of cluster chain */ - if (*fclus > limit) { - exfat_fs_error(sb, - "detected the cluster chain loop (i_pos %u)", - (*fclus)); - return -EIO; - } - if (exfat_ent_get(sb, *dclus, &content, NULL)) return -EIO; -- cgit v1.2.3 From 5dc72a518137941852fc57b3cde97ff243791e57 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:42 +0800 Subject: exfat: remove the unreachable warning for cache miss cases The cache_id remains unchanged on a cache miss; its value is always exactly what was set by cache_init. Therefore, checking this value again is meaningless. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index 0ee4bff1cb35..d51737498ee4 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -260,18 +260,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, return 0; cache_init(&cid, EXFAT_EOF_CLUSTER, EXFAT_EOF_CLUSTER); - - if (exfat_cache_lookup(inode, cluster, &cid, fclus, dclus) == - EXFAT_EOF_CLUSTER) { - /* - * dummy, always not contiguous - * This is reinitialized by cache_init(), later. - */ - WARN_ON(cid.id != EXFAT_CACHE_VALID || - cid.fcluster != EXFAT_EOF_CLUSTER || - cid.dcluster != EXFAT_EOF_CLUSTER || - cid.nr_contig != 0); - } + exfat_cache_lookup(inode, cluster, &cid, fclus, dclus); if (*fclus == cluster) return 0; -- cgit v1.2.3 From afb6ffa33dab9622f7502c081b17dbc9bc9a97d0 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:43 +0800 Subject: exfat: reduce the number of parameters for exfat_get_cluster() Remove parameter 'fclus' and 'allow_eof': - The fclus parameter is changed to a local variable as it is not needed to be returned. - The passed allow_eof parameter was always 1, remove it and the associated error handling. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 27 +++++++++------------------ fs/exfat/exfat_fs.h | 3 +-- fs/exfat/inode.c | 5 +---- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index d51737498ee4..b806e7f5b00f 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -234,13 +234,12 @@ static inline void cache_init(struct exfat_cache_id *cid, } int exfat_get_cluster(struct inode *inode, unsigned int cluster, - unsigned int *fclus, unsigned int *dclus, - unsigned int *last_dclus, int allow_eof) + unsigned int *dclus, unsigned int *last_dclus) { struct super_block *sb = inode->i_sb; struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_cache_id cid; - unsigned int content; + unsigned int content, fclus; if (ei->start_clu == EXFAT_FREE_CLUSTER) { exfat_fs_error(sb, @@ -249,7 +248,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, return -EIO; } - *fclus = 0; + fclus = 0; *dclus = ei->start_clu; *last_dclus = *dclus; @@ -260,32 +259,24 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, return 0; cache_init(&cid, EXFAT_EOF_CLUSTER, EXFAT_EOF_CLUSTER); - exfat_cache_lookup(inode, cluster, &cid, fclus, dclus); + exfat_cache_lookup(inode, cluster, &cid, &fclus, dclus); - if (*fclus == cluster) + if (fclus == cluster) return 0; - while (*fclus < cluster) { + while (fclus < cluster) { if (exfat_ent_get(sb, *dclus, &content, NULL)) return -EIO; *last_dclus = *dclus; *dclus = content; - (*fclus)++; - - if (content == EXFAT_EOF_CLUSTER) { - if (!allow_eof) { - exfat_fs_error(sb, - "invalid cluster chain (i_pos %u, last_clus 0x%08x is EOF)", - *fclus, (*last_dclus)); - return -EIO; - } + fclus++; + if (content == EXFAT_EOF_CLUSTER) break; - } if (!cache_contiguous(&cid, *dclus)) - cache_init(&cid, *fclus, *dclus); + cache_init(&cid, fclus, *dclus); } exfat_cache_add(inode, &cid); diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index f7f25e0600c7..e58d8eed5495 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -486,8 +486,7 @@ int exfat_cache_init(void); void exfat_cache_shutdown(void); void exfat_cache_inval_inode(struct inode *inode); int exfat_get_cluster(struct inode *inode, unsigned int cluster, - unsigned int *fclus, unsigned int *dclus, - unsigned int *last_dclus, int allow_eof); + unsigned int *dclus, unsigned int *last_dclus); /* dir.c */ extern const struct inode_operations exfat_dir_inode_operations; diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index f9501c3a3666..55984585526e 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -157,13 +157,10 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, *clu += clu_offset; } } else if (ei->type == TYPE_FILE) { - unsigned int fclus = 0; int err = exfat_get_cluster(inode, clu_offset, - &fclus, clu, &last_clu, 1); + clu, &last_clu); if (err) return -EIO; - - clu_offset -= fclus; } else { /* hint information */ if (clu_offset > 0 && ei->hint_bmap.off != EXFAT_EOF_CLUSTER && -- cgit v1.2.3 From 6d0b7f873bbd153f6ce26a87ffa32238693e36a0 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:44 +0800 Subject: exfat: reuse cache to improve exfat_get_cluster Since exfat_ent_get supports cache buffer head, we can use this option to reduce sb_bread calls when fetching consecutive entries. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index b806e7f5b00f..025b39b7a9ac 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -238,6 +238,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, { struct super_block *sb = inode->i_sb; struct exfat_inode_info *ei = EXFAT_I(inode); + struct buffer_head *bh = NULL; struct exfat_cache_id cid; unsigned int content, fclus; @@ -265,7 +266,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, return 0; while (fclus < cluster) { - if (exfat_ent_get(sb, *dclus, &content, NULL)) + if (exfat_ent_get(sb, *dclus, &content, &bh)) return -EIO; *last_dclus = *dclus; @@ -279,6 +280,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, cache_init(&cid, fclus, *dclus); } + brelse(bh); exfat_cache_add(inode, &cid); return 0; } -- cgit v1.2.3 From 88a936b7a918e4f37296524d355505128c961d9c Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:45 +0800 Subject: exfat: remove handling of non-file types in exfat_map_cluster Yuezhang said: "exfat_map_cluster() is only used for files. The code in this 'else' block is never executed and can be cleaned up." Suggested-by: Yuezhang Mo Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/inode.c | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 55984585526e..b714d242b238 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -156,27 +156,11 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, else *clu += clu_offset; } - } else if (ei->type == TYPE_FILE) { + } else { int err = exfat_get_cluster(inode, clu_offset, clu, &last_clu); if (err) return -EIO; - } else { - /* hint information */ - if (clu_offset > 0 && ei->hint_bmap.off != EXFAT_EOF_CLUSTER && - ei->hint_bmap.off > 0 && clu_offset >= ei->hint_bmap.off) { - clu_offset -= ei->hint_bmap.off; - /* hint_bmap.clu should be valid */ - WARN_ON(ei->hint_bmap.clu < 2); - *clu = ei->hint_bmap.clu; - } - - while (clu_offset > 0 && *clu != EXFAT_EOF_CLUSTER) { - last_clu = *clu; - if (exfat_get_next_cluster(sb, clu)) - return -EIO; - clu_offset--; - } } if (*clu == EXFAT_EOF_CLUSTER) { -- cgit v1.2.3 From 256694b22d017edcc8a0ce82a8e40742b074d509 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:12:46 +0800 Subject: exfat: support multi-cluster for exfat_map_cluster This patch introduces a parameter 'count' to support fetching multiple clusters in exfat_map_cluster. The returned 'count' indicates the number of consecutive clusters, or 0 when the input cluster offset is past EOF. And the 'count' is also an input parameter for the caller to specify the required number of clusters. Only NO_FAT_CHAIN files enable multi-cluster fetching in this patch. After this patch, the time proportion of exfat_get_block has decreased, The performance data is as follows: Cluster size: 512 bytes Sequential read of a 30GB NO_FAT_CHAIN file: 2.4GB/s -> 2.5 GB/s proportion of exfat_get_block: 10.8% -> 0.02% Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/inode.c | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index b714d242b238..410f9c98b8dc 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -124,7 +124,7 @@ void exfat_sync_inode(struct inode *inode) * *clu = (~0), if it's unable to allocate a new cluster */ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, - unsigned int *clu, int create) + unsigned int *clu, unsigned int *count, int create) { int ret; unsigned int last_clu; @@ -147,20 +147,23 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, *clu = last_clu = ei->start_clu; - if (ei->flags == ALLOC_NO_FAT_CHAIN) { - if (clu_offset > 0 && *clu != EXFAT_EOF_CLUSTER) { - last_clu += clu_offset - 1; - - if (clu_offset == num_clusters) - *clu = EXFAT_EOF_CLUSTER; - else - *clu += clu_offset; + if (*clu == EXFAT_EOF_CLUSTER) { + *count = 0; + } else if (ei->flags == ALLOC_NO_FAT_CHAIN) { + last_clu += num_clusters - 1; + if (clu_offset < num_clusters) { + *clu += clu_offset; + *count = min(num_clusters - clu_offset, *count); + } else { + *clu = EXFAT_EOF_CLUSTER; + *count = 0; } } else { int err = exfat_get_cluster(inode, clu_offset, clu, &last_clu); if (err) return -EIO; + *count = (*clu == EXFAT_EOF_CLUSTER) ? 0 : 1; } if (*clu == EXFAT_EOF_CLUSTER) { @@ -232,7 +235,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_to_be_allocated--; } } - + *count = 1; } /* hint information */ @@ -251,7 +254,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits; int err = 0; unsigned long mapped_blocks = 0; - unsigned int cluster, sec_offset; + unsigned int cluster, sec_offset, count; sector_t last_block; sector_t phys = 0; sector_t valid_blks; @@ -264,8 +267,9 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, goto done; /* Is this block already allocated? */ + count = EXFAT_B_TO_CLU_ROUND_UP(bh_result->b_size, sbi); err = exfat_map_cluster(inode, iblock >> sbi->sect_per_clus_bits, - &cluster, create); + &cluster, &count, create); if (err) { if (err != -ENOSPC) exfat_fs_error_ratelimit(sb, @@ -281,7 +285,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock, sec_offset = iblock & (sbi->sect_per_clus - 1); phys = exfat_cluster_to_sector(sbi, cluster) + sec_offset; - mapped_blocks = sbi->sect_per_clus - sec_offset; + mapped_blocks = ((unsigned long)count << sbi->sect_per_clus_bits) - sec_offset; max_blocks = min(mapped_blocks, max_blocks); map_bh(bh_result, sb, phys); -- cgit v1.2.3 From 8c6bdce0e91425ca5e84f52819b7cf0e024fb129 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:17:47 +0800 Subject: exfat: tweak cluster cache to support zero offset The current cache mechanism does not support reading clusters starting from a file offset of zero. This patch enables that feature in preparation for subsequent reads of contiguous clusters from offset zero. 1. support finding clusters with zero offset. 2. allow clusters with zero offset to be cached. Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index 025b39b7a9ac..73147e153c2c 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -92,7 +92,7 @@ static unsigned int exfat_cache_lookup(struct inode *inode, spin_lock(&ei->cache_lru_lock); list_for_each_entry(p, &ei->cache_lru, cache_list) { /* Find the cache of "fclus" or nearest cache. */ - if (p->fcluster <= fclus && hit->fcluster < p->fcluster) { + if (p->fcluster <= fclus && hit->fcluster <= p->fcluster) { hit = p; if (hit->fcluster + hit->nr_contig < fclus) { offset = hit->nr_contig; @@ -259,7 +259,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, if (cluster == 0 || *dclus == EXFAT_EOF_CLUSTER) return 0; - cache_init(&cid, EXFAT_EOF_CLUSTER, EXFAT_EOF_CLUSTER); + cache_init(&cid, fclus, *dclus); exfat_cache_lookup(inode, cluster, &cid, &fclus, dclus); if (fclus == cluster) -- cgit v1.2.3 From 9fb696a10a6735a15a0d2b4370419702e99818d0 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:17:58 +0800 Subject: exfat: return the start of next cache in exfat_cache_lookup Change exfat_cache_lookup to return the cluster number of the last cluster before the next cache (i.e., the end of the current cache range) or the given 'end' if there is no next cache. This allows the caller to know whether the next cluster after the current cache is cached. The function signature is changed to accept an 'end' parameter, which is the upper bound of the search range. The function now stops early if it finds a cache that starts within the current cache's tail, meaning caches are contiguous. The return value is the cluster number at which the next cache starts (minus one) or the original 'end' if no next cache is found. The new behavior is illustrated as follows: cache: [ccccccc-------ccccccccc] search: [..................] return: ^ Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index 73147e153c2c..5cdeac014a3d 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -80,41 +80,66 @@ static inline void exfat_cache_update_lru(struct inode *inode, list_move(&cache->cache_list, &ei->cache_lru); } -static unsigned int exfat_cache_lookup(struct inode *inode, - unsigned int fclus, struct exfat_cache_id *cid, +/* + * Find the cache that covers or precedes 'fclus' and return the last + * cluster before the next cache range. + */ +static inline unsigned int +exfat_cache_lookup(struct inode *inode, struct exfat_cache_id *cid, + unsigned int fclus, unsigned int end, unsigned int *cached_fclus, unsigned int *cached_dclus) { struct exfat_inode_info *ei = EXFAT_I(inode); static struct exfat_cache nohit = { .fcluster = 0, }; struct exfat_cache *hit = &nohit, *p; - unsigned int offset = EXFAT_EOF_CLUSTER; + unsigned int tail = 0; /* End boundary of hit cache */ + /* + * Search range [fclus, end]. Stop early if: + * 1. Cache covers entire range, or + * 2. Next cache starts at current cache tail + */ spin_lock(&ei->cache_lru_lock); list_for_each_entry(p, &ei->cache_lru, cache_list) { /* Find the cache of "fclus" or nearest cache. */ - if (p->fcluster <= fclus && hit->fcluster <= p->fcluster) { + if (p->fcluster <= fclus) { + if (p->fcluster < hit->fcluster) + continue; + hit = p; - if (hit->fcluster + hit->nr_contig < fclus) { - offset = hit->nr_contig; - } else { - offset = fclus - hit->fcluster; + tail = hit->fcluster + hit->nr_contig; + + /* Current cache covers [fclus, end] completely */ + if (tail >= end) + break; + } else if (p->fcluster <= end) { + end = p->fcluster - 1; + + /* + * If we have a hit and next cache starts within/at + * its tail, caches are contiguous, stop searching. + */ + if (tail && tail >= end) break; - } } } if (hit != &nohit) { - exfat_cache_update_lru(inode, hit); + unsigned int offset; + exfat_cache_update_lru(inode, hit); cid->id = ei->cache_valid_id; cid->nr_contig = hit->nr_contig; cid->fcluster = hit->fcluster; cid->dcluster = hit->dcluster; + + offset = min(cid->nr_contig, fclus - cid->fcluster); *cached_fclus = cid->fcluster + offset; *cached_dclus = cid->dcluster + offset; } spin_unlock(&ei->cache_lru_lock); - return offset; + /* Return next cache start or 'end' if no more caches */ + return end; } static struct exfat_cache *exfat_cache_merge(struct inode *inode, @@ -260,7 +285,7 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, return 0; cache_init(&cid, fclus, *dclus); - exfat_cache_lookup(inode, cluster, &cid, &fclus, dclus); + exfat_cache_lookup(inode, &cid, cluster, cluster, &fclus, dclus); if (fclus == cluster) return 0; -- cgit v1.2.3 From 9276e330c17b6c926bdc5da90cbc58d6c4f26298 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Wed, 14 Jan 2026 20:18:08 +0800 Subject: exfat: support multi-cluster for exfat_get_cluster This patch introduces a count parameter to exfat_get_cluster, which serves as an input parameter for the caller to specify the desired number of clusters, and as an output parameter to store the length of consecutive clusters. This patch can improve read performance by reducing the number of get_block calls in sequential read scenarios. speacially in small cluster size. According to my test data, the performance improvement is approximately 10% when read FAT_CHAIN file with 512 bytes of cluster size. 454 MB/s -> 511 MB/s Suggested-by: Yuezhang Mo Signed-off-by: Chi Zhiling Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/cache.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++----- fs/exfat/exfat_fs.h | 2 +- fs/exfat/inode.c | 3 +-- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c index 5cdeac014a3d..7c8b4182f5de 100644 --- a/fs/exfat/cache.c +++ b/fs/exfat/cache.c @@ -259,13 +259,15 @@ static inline void cache_init(struct exfat_cache_id *cid, } int exfat_get_cluster(struct inode *inode, unsigned int cluster, - unsigned int *dclus, unsigned int *last_dclus) + unsigned int *dclus, unsigned int *count, + unsigned int *last_dclus) { struct super_block *sb = inode->i_sb; struct exfat_inode_info *ei = EXFAT_I(inode); struct buffer_head *bh = NULL; struct exfat_cache_id cid; unsigned int content, fclus; + unsigned int end = cluster + *count - 1; if (ei->start_clu == EXFAT_FREE_CLUSTER) { exfat_fs_error(sb, @@ -279,17 +281,33 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, *last_dclus = *dclus; /* - * Don`t use exfat_cache if zero offset or non-cluster allocation + * This case should not exist, as exfat_map_cluster function doesn't + * call this routine when start_clu == EXFAT_EOF_CLUSTER. + * This case is retained here for routine completeness. */ - if (cluster == 0 || *dclus == EXFAT_EOF_CLUSTER) + if (*dclus == EXFAT_EOF_CLUSTER) { + *count = 0; + return 0; + } + + /* If only the first cluster is needed, return now. */ + if (fclus == cluster && *count == 1) return 0; cache_init(&cid, fclus, *dclus); - exfat_cache_lookup(inode, &cid, cluster, cluster, &fclus, dclus); + /* + * Update the 'end' to exclude the next cache range, as clusters in + * different cache are typically not contiguous. + */ + end = exfat_cache_lookup(inode, &cid, cluster, end, &fclus, dclus); - if (fclus == cluster) + /* Return if the cache covers the entire range. */ + if (cid.fcluster + cid.nr_contig >= end) { + *count = end - cluster + 1; return 0; + } + /* Find the first cluster we need. */ while (fclus < cluster) { if (exfat_ent_get(sb, *dclus, &content, &bh)) return -EIO; @@ -305,6 +323,34 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster, cache_init(&cid, fclus, *dclus); } + /* + * Now the cid cache contains the first cluster requested, collect + * the remaining clusters of this contiguous extent. + */ + if (*dclus != EXFAT_EOF_CLUSTER) { + unsigned int clu = *dclus; + + while (fclus < end) { + if (exfat_ent_get(sb, clu, &content, &bh)) + return -EIO; + if (++clu != content) + break; + fclus++; + } + cid.nr_contig = fclus - cid.fcluster; + *count = fclus - cluster + 1; + + /* + * Cache this discontiguous cluster, we'll definitely need + * it later + */ + if (fclus < end && content != EXFAT_EOF_CLUSTER) { + exfat_cache_add(inode, &cid); + cache_init(&cid, fclus + 1, content); + } + } else { + *count = 0; + } brelse(bh); exfat_cache_add(inode, &cid); return 0; diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index e58d8eed5495..2dbed5f8ec26 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -486,7 +486,7 @@ int exfat_cache_init(void); void exfat_cache_shutdown(void); void exfat_cache_inval_inode(struct inode *inode); int exfat_get_cluster(struct inode *inode, unsigned int cluster, - unsigned int *dclus, unsigned int *last_dclus); + unsigned int *dclus, unsigned int *count, unsigned int *last_dclus); /* dir.c */ extern const struct inode_operations exfat_dir_inode_operations; diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 410f9c98b8dc..86bce7ea2725 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -160,10 +160,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, } } else { int err = exfat_get_cluster(inode, clu_offset, - clu, &last_clu); + clu, count, &last_clu); if (err) return -EIO; - *count = (*clu == EXFAT_EOF_CLUSTER) ? 0 : 1; } if (*clu == EXFAT_EOF_CLUSTER) { -- cgit v1.2.3 From 5e37a4577f95d93903c5754c6bac921ac39b557e Mon Sep 17 00:00:00 2001 From: William Hansen-Baird Date: Wed, 21 Jan 2026 19:04:33 -0500 Subject: exfat: remove unnecessary else after return statement Else-branch is unnecessary after return statement in if-branch. Remove to enhance readability and reduce indentation. Signed-off-by: William Hansen-Baird Signed-off-by: Namjae Jeon --- fs/exfat/inode.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 86bce7ea2725..2fb2d2d5d503 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -495,8 +495,9 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter) exfat_write_failed(mapping, size); return ret; - } else - size = pos + ret; + } + + size = pos + ret; if (rw == WRITE) { /* -- cgit v1.2.3 From c1f57406672ba6644ebc437b70a139115b68a0dc Mon Sep 17 00:00:00 2001 From: William Hansen-Baird Date: Wed, 21 Jan 2026 19:04:34 -0500 Subject: exfat: add blank line after declarations Add a blank line after variable declarations in fatent.c and file.c. This improves readability and makes code style more consistent across the exfat subsystem. Signed-off-by: William Hansen-Baird Signed-off-by: Namjae Jeon --- fs/exfat/fatent.c | 1 + fs/exfat/file.c | 1 + 2 files changed, 2 insertions(+) diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index 71ee16479c43..f87576ca7032 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -207,6 +207,7 @@ static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { int err; unsigned int last_cluster = p_chain->dir + p_chain->size - 1; + do { bool sync = false; diff --git a/fs/exfat/file.c b/fs/exfat/file.c index c0a19f8716e6..90cd540afeaa 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -683,6 +683,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter) if (iocb->ki_pos > pos) { ssize_t err = generic_write_sync(iocb, iocb->ki_pos - pos); + if (err < 0) return err; } -- cgit v1.2.3