mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block.
@ 2026-01-08  7:49 Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 01/13] exfat: add cache option for __exfat_ent_get Chi Zhiling
                   ` (10 more replies)
  0 siblings, 11 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

This patch series significantly improves exFAT read performance
by adding multi-cluster mapping support.
The changes reduce get_block calls during sequential reads,
particularly benefiting small cluster sizes.

- Extends exfat_get_cluster() and exfat_map_cluster() to handle multiple contiguous clusters
- Adds buffer head caching for FAT table reads

Performance results show ~10% improvement for 512-byte clusters (454->511 MB/s)
and reduced get_block overhead from 10.8% to 0.02% for NO_FAT_CHAIN files.

All criticism and suggestions are welcome :)


Changes:
- Cache the last dis-continuous cluster
- Continue collect clusters after cache hit
- Some cleanup.

V1:
https://lore.kernel.org/linux-fsdevel/20251226094440.455563-1-chizhiling@163.com/T/#u
rfc:
https://lore.kernel.org/linux-fsdevel/20251118082208.1034186-1-chizhiling@163.com/T/#u


Chi Zhiling (13):
  exfat: add cache option for __exfat_ent_get
  exfat: support reuse buffer head for exfat_ent_get
  exfat: improve exfat_count_num_clusters
  exfat: improve exfat_find_last_cluster
  exfat: remove the check for infinite cluster chain loop
  exfat: remove the unreachable warning for cache miss cases
  exfat: reduce the number of parameters for exfat_get_cluster()
  exfat: reuse cache to improve exfat_get_cluster
  exfat: remove handling of non-file types in exfat_map_cluster
  exfat: support multi-cluster for exfat_map_cluster
  exfat: tweak cluster cache to support zero offset
  exfat: return the start of next cache in exfat_cache_lookup
  exfat: support multi-cluster for exfat_get_cluster

 fs/exfat/cache.c    | 149 ++++++++++++++++++++++++++++----------------
 fs/exfat/exfat_fs.h |   7 +--
 fs/exfat/fatent.c   |  61 +++++++++++-------
 fs/exfat/inode.c    |  52 ++++++----------
 4 files changed, 157 insertions(+), 112 deletions(-)


base-commit: 8f0b4cce4481fb22653697cced8d0d04027cb1e8
prerequisite-patch-id: e5f54b5b252924be0d139310a81d57eda55bead3
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 01/13] exfat: add cache option for __exfat_ent_get
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 02/13] exfat: support reuse buffer head for exfat_ent_get Chi Zhiling
                   ` (9 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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)",
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 02/13] exfat: support reuse buffer head for exfat_ent_get
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 01/13] exfat: add cache option for __exfat_ent_get Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 03/13] exfat: improve exfat_count_num_clusters Chi Zhiling
                   ` (8 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 03/13] exfat: improve exfat_count_num_clusters
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 01/13] exfat: add cache option for __exfat_ent_get Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 02/13] exfat: support reuse buffer head for exfat_ent_get Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 04/13] exfat: improve exfat_find_last_cluster Chi Zhiling
                   ` (7 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

Since exfat_ent_get support cache buffer head, let's apply it to
exfat_count_num_clusters.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 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;
 
 	/*
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 04/13] exfat: improve exfat_find_last_cluster
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (2 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 03/13] exfat: improve exfat_count_num_clusters Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 05/13] exfat: remove the check for infinite cluster chain loop Chi Zhiling
                   ` (6 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

Since exfat_ent_get support cache buffer head, let's apply it to
exfat_find_last_cluster.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 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))",
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 05/13] exfat: remove the check for infinite cluster chain loop
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (3 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 04/13] exfat: improve exfat_find_last_cluster Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 06/13] exfat: remove the unreachable warning for cache miss cases Chi Zhiling
                   ` (5 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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;
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 06/13] exfat: remove the unreachable warning for cache miss cases
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (4 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 05/13] exfat: remove the check for infinite cluster chain loop Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 07/13] exfat: reduce the number of parameters for exfat_get_cluster() Chi Zhiling
                   ` (4 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 07/13] exfat: reduce the number of parameters for exfat_get_cluster()
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (5 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 06/13] exfat: remove the unreachable warning for cache miss cases Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 08/13] exfat: reuse cache to improve exfat_get_cluster Chi Zhiling
                   ` (3 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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 &&
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 08/13] exfat: reuse cache to improve exfat_get_cluster
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (6 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 07/13] exfat: reduce the number of parameters for exfat_get_cluster() Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 09/13] exfat: remove handling of non-file types in exfat_map_cluster Chi Zhiling
                   ` (2 subsequent siblings)
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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;
 }
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 09/13] exfat: remove handling of non-file types in exfat_map_cluster
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (7 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 08/13] exfat: reuse cache to improve exfat_get_cluster Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-08  7:49 ` [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster Chi Zhiling
  2026-01-08  7:59 ` [PATCH v2 11/13] exfat: tweak cluster cache to support zero offset Chi Zhiling
  10 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling, Yuezhang Mo

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <Yuezhang.Mo@sony.com>
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 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) {
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (8 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 09/13] exfat: remove handling of non-file types in exfat_map_cluster Chi Zhiling
@ 2026-01-08  7:49 ` Chi Zhiling
  2026-01-13  6:37   ` Yuezhang.Mo
  2026-01-08  7:59 ` [PATCH v2 11/13] exfat: tweak cluster cache to support zero offset Chi Zhiling
  10 siblings, 1 reply; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:49 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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..00ff6c7ed935 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 = num_clusters - clu_offset;
+		} 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 = (count << sbi->sect_per_clus_bits) - sec_offset;
 	max_blocks = min(mapped_blocks, max_blocks);
 
 	map_bh(bh_result, sb, phys);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 11/13] exfat: tweak cluster cache to support zero offset
  2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
                   ` (9 preceding siblings ...)
  2026-01-08  7:49 ` [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster Chi Zhiling
@ 2026-01-08  7:59 ` Chi Zhiling
  2026-01-08  7:59   ` [PATCH v2 12/13] exfat: return the start of next cache in exfat_cache_lookup Chi Zhiling
  2026-01-08  7:59   ` [PATCH v2 13/13] exfat: support multi-cluster for exfat_get_cluster Chi Zhiling
  10 siblings, 2 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:59 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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. modify exfat_cache_lookup() to find clusters with offset zero.
2. allow clusters with zero offset to be cached.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 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)
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 12/13] exfat: return the start of next cache in exfat_cache_lookup
  2026-01-08  7:59 ` [PATCH v2 11/13] exfat: tweak cluster cache to support zero offset Chi Zhiling
@ 2026-01-08  7:59   ` Chi Zhiling
  2026-01-08  7:59   ` [PATCH v2 13/13] exfat: support multi-cluster for exfat_get_cluster Chi Zhiling
  1 sibling, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:59 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <chizhiling@kylinos.cn>
---
 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;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH v2 13/13] exfat: support multi-cluster for exfat_get_cluster
  2026-01-08  7:59 ` [PATCH v2 11/13] exfat: tweak cluster cache to support zero offset Chi Zhiling
  2026-01-08  7:59   ` [PATCH v2 12/13] exfat: return the start of next cache in exfat_cache_lookup Chi Zhiling
@ 2026-01-08  7:59   ` Chi Zhiling
  1 sibling, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-08  7:59 UTC (permalink / raw)
  To: linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling, Yuezhang Mo

From: Chi Zhiling <chizhiling@kylinos.cn>

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 <Yuezhang.Mo@sony.com>
Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 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..18d304d1d4cc 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 = cid.fcluster + cid.nr_contig - 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 00ff6c7ed935..e8b74185b0ad 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) {
-- 
2.43.0


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster
  2026-01-08  7:49 ` [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster Chi Zhiling
@ 2026-01-13  6:37   ` Yuezhang.Mo
  2026-01-13  8:53     ` Chi Zhiling
  0 siblings, 1 reply; 18+ messages in thread
From: Yuezhang.Mo @ 2026-01-13  6:37 UTC (permalink / raw)
  To: Chi Zhiling, linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Chi Zhiling

> @@ -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 = (count << sbi->sect_per_clus_bits) - sec_offset;

This left shift will cause an overflow if the file is larger than 2TB 
and the clusters are contiguous.

The others look good.
Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster
  2026-01-13  6:37   ` Yuezhang.Mo
@ 2026-01-13  8:53     ` Chi Zhiling
  2026-01-13 10:26       ` Yuezhang.Mo
  0 siblings, 1 reply; 18+ messages in thread
From: Chi Zhiling @ 2026-01-13  8:53 UTC (permalink / raw)
  To: Yuezhang.Mo, linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Chi Zhiling

On 1/13/26 14:37, Yuezhang.Mo@sony.com wrote:
>> @@ -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 = (count << sbi->sect_per_clus_bits) - sec_offset;
> 
> This left shift will cause an overflow if the file is larger than 2TB
> and the clusters are contiguous.

Thank you for pointing this out, I will change the type to blkcnt_t in 
v3 to fix this bug.

diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c
index e8b74185b0ad..9c1e00f5b011 100644
--- a/fs/exfat/inode.c
+++ b/fs/exfat/inode.c
@@ -250,9 +250,9 @@ static int exfat_get_block(struct inode *inode, 
sector_t iblock,
         struct exfat_inode_info *ei = EXFAT_I(inode);
         struct super_block *sb = inode->i_sb;
         struct exfat_sb_info *sbi = EXFAT_SB(sb);
-       unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
+       blkcnt_t max_blocks = EXFAT_B_TO_BLK(bh_result->b_size, sb);
+       blkcnt_t mapped_blocks = 0;
         int err = 0;
-       unsigned long mapped_blocks = 0;
         unsigned int cluster, sec_offset, count;
         sector_t last_block;
         sector_t phys = 0;

> 
> The others look good.
> Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>


Thanks,


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster
  2026-01-13  8:53     ` Chi Zhiling
@ 2026-01-13 10:26       ` Yuezhang.Mo
  2026-01-14  3:07         ` Chi Zhiling
  0 siblings, 1 reply; 18+ messages in thread
From: Yuezhang.Mo @ 2026-01-13 10:26 UTC (permalink / raw)
  To: Chi Zhiling, linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Chi Zhiling

> On 1/13/26 14:37, Yuezhang.Mo@sony.com wrote:
>>> @@ -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 = (count << sbi->sect_per_clus_bits) - sec_offset;
>>
>> This left shift will cause an overflow if the file is larger than 2TB
>> and the clusters are contiguous.
>
> Thank you for pointing this out, I will change the type to blkcnt_t in
> v3 to fix this bug.

I don't think it's necessary to change the type to blkcnt_t, because the type
of bh_result->b_size is size_t (aka unsigned long int).

The overflow was caused by '*count' being increased in exfat_map_cluster().
I think it should be fixed as below.

--- a/fs/exfat/inode.c
+++ b/fs/exfat/inode.c
@@ -153,7 +153,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
                last_clu += num_clusters - 1;
                if (clu_offset < num_clusters) {
                        *clu += clu_offset;
-                       *count = num_clusters - clu_offset;
+                       *count = min(num_clusters - clu_offset, *count);
                } else {
                        *clu = EXFAT_EOF_CLUSTER;
                        *count = 0;
@@ -284,7 +284,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 = (count << sbi->sect_per_clus_bits) - 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);

> 
> diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c
> index e8b74185b0ad..9c1e00f5b011 100644
> --- a/fs/exfat/inode.c
> +++ b/fs/exfat/inode.c
> @@ -250,9 +250,9 @@ static int exfat_get_block(struct inode *inode,
> sector_t iblock,
>         struct exfat_inode_info *ei = EXFAT_I(inode);
>         struct super_block *sb = inode->i_sb;
>         struct exfat_sb_info *sbi = EXFAT_SB(sb);
>-       unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
>+       blkcnt_t max_blocks = EXFAT_B_TO_BLK(bh_result->b_size, sb);
>+       blkcnt_t mapped_blocks = 0;
>         int err = 0;
>-       unsigned long mapped_blocks = 0;
>         unsigned int cluster, sec_offset, count;
>         sector_t last_block;
>         sector_t phys = 0;
>
>>
>> The others look good.
>> Reviewed-by: Yuezhang Mo <Yuezhang.Mo@sony.com>
>
>
> Thanks,



^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster
  2026-01-13 10:26       ` Yuezhang.Mo
@ 2026-01-14  3:07         ` Chi Zhiling
  0 siblings, 0 replies; 18+ messages in thread
From: Chi Zhiling @ 2026-01-14  3:07 UTC (permalink / raw)
  To: Yuezhang.Mo, linux-fsdevel, linux-kernel
  Cc: Namjae Jeon, Sungjong Seo, Chi Zhiling

On 1/13/26 18:26, Yuezhang.Mo@sony.com wrote:
>> On 1/13/26 14:37, Yuezhang.Mo@sony.com wrote:
>>>> @@ -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 = (count << sbi->sect_per_clus_bits) - sec_offset;
>>>
>>> This left shift will cause an overflow if the file is larger than 2TB
>>> and the clusters are contiguous.
>>
>> Thank you for pointing this out, I will change the type to blkcnt_t in
>> v3 to fix this bug.
> 
> I don't think it's necessary to change the type to blkcnt_t, because the type
> of bh_result->b_size is size_t (aka unsigned long int).
> 
> The overflow was caused by '*count' being increased in exfat_map_cluster().
> I think it should be fixed as below.
> 
> --- a/fs/exfat/inode.c
> +++ b/fs/exfat/inode.c
> @@ -153,7 +153,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
>                  last_clu += num_clusters - 1;
>                  if (clu_offset < num_clusters) {
>                          *clu += clu_offset;
> -                       *count = num_clusters - clu_offset;
> +                       *count = min(num_clusters - clu_offset, *count);
>                  } else {
>                          *clu = EXFAT_EOF_CLUSTER;
>                          *count = 0;
> @@ -284,7 +284,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 = (count << sbi->sect_per_clus_bits) - 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);
> 

Okay, I got it, so the exfat_get_cluster should be updated as well.


diff --git a/fs/exfat/cache.c b/fs/exfat/cache.c
index 18d304d1d4cc..7c8b4182f5de 100644
--- a/fs/exfat/cache.c
+++ b/fs/exfat/cache.c
@@ -303,7 +303,7 @@ int exfat_get_cluster(struct inode *inode, unsigned 
int cluster,

         /* Return if the cache covers the entire range. */
         if (cid.fcluster + cid.nr_contig >= end) {
-               *count = cid.fcluster + cid.nr_contig - cluster + 1;
+               *count = end - cluster + 1;
                 return 0;
         }


Thanks,


^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2026-01-14  3:07 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-01-08  7:49 [PATCH v2 00/13] Enable multi-cluster fetching for exfat_get_block Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 01/13] exfat: add cache option for __exfat_ent_get Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 02/13] exfat: support reuse buffer head for exfat_ent_get Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 03/13] exfat: improve exfat_count_num_clusters Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 04/13] exfat: improve exfat_find_last_cluster Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 05/13] exfat: remove the check for infinite cluster chain loop Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 06/13] exfat: remove the unreachable warning for cache miss cases Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 07/13] exfat: reduce the number of parameters for exfat_get_cluster() Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 08/13] exfat: reuse cache to improve exfat_get_cluster Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 09/13] exfat: remove handling of non-file types in exfat_map_cluster Chi Zhiling
2026-01-08  7:49 ` [PATCH v2 10/13] exfat: support multi-cluster for exfat_map_cluster Chi Zhiling
2026-01-13  6:37   ` Yuezhang.Mo
2026-01-13  8:53     ` Chi Zhiling
2026-01-13 10:26       ` Yuezhang.Mo
2026-01-14  3:07         ` Chi Zhiling
2026-01-08  7:59 ` [PATCH v2 11/13] exfat: tweak cluster cache to support zero offset Chi Zhiling
2026-01-08  7:59   ` [PATCH v2 12/13] exfat: return the start of next cache in exfat_cache_lookup Chi Zhiling
2026-01-08  7:59   ` [PATCH v2 13/13] exfat: support multi-cluster for exfat_get_cluster Chi Zhiling

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®