Commit dd530598 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull exfat updates from Namjae Jeon:

 - Improve error code handling and four cleanups

 - Reduce unnecessary valid_size extension during mmap write to avoid
   over-extending writes

 - Optimize consecutive FAT entry reads by caching buffer heads in
   __exfat_ent_get to significantly reduce sb_bread() calls

 - Add multi-cluster (contiguous cluster) support to exfat_get_cluster()
   and exfat_map_cluster() for better sequential read performance,
   especially on small cluster sizes

* tag 'exfat-for-7.0-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat:
  exfat: add blank line after declarations
  exfat: remove unnecessary else after return statement
  exfat: support multi-cluster for exfat_get_cluster
  exfat: return the start of next cache in exfat_cache_lookup
  exfat: tweak cluster cache to support zero offset
  exfat: support multi-cluster for exfat_map_cluster
  exfat: remove handling of non-file types in exfat_map_cluster
  exfat: reuse cache to improve exfat_get_cluster
  exfat: reduce the number of parameters for exfat_get_cluster()
  exfat: remove the unreachable warning for cache miss cases
  exfat: remove the check for infinite cluster chain loop
  exfat: improve exfat_find_last_cluster
  exfat: improve exfat_count_num_clusters
  exfat: support reuse buffer head for exfat_ent_get
  exfat: add cache option for __exfat_ent_get
  exfat: reduce unnecessary writes during mmap write
  exfat: improve error code handling in exfat_find_empty_entry()
parents 3e48a116 c1f57406
Loading
Loading
Loading
Loading
+96 −53
Original line number Diff line number Diff line
@@ -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,
@@ -234,15 +259,15 @@ 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 *count,
		unsigned int *last_dclus)
{
	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 buffer_head *bh = NULL;
	struct exfat_cache_id cid;
	unsigned int content;
	unsigned int content, fclus;
	unsigned int end = cluster + *count - 1;

	if (ei->start_clu == EXFAT_FREE_CLUSTER) {
		exfat_fs_error(sb,
@@ -251,64 +276,82 @@ int exfat_get_cluster(struct inode *inode, unsigned int cluster,
		return -EIO;
	}

	*fclus = 0;
	fclus = 0;
	*dclus = ei->start_clu;
	*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;
	}

	cache_init(&cid, EXFAT_EOF_CLUSTER, EXFAT_EOF_CLUSTER);
	/* If only the first cluster is needed, return now. */
	if (fclus == cluster && *count == 1)
		return 0;

	if (exfat_cache_lookup(inode, cluster, &cid, fclus, dclus) ==
			EXFAT_EOF_CLUSTER) {
	cache_init(&cid, fclus, *dclus);
	/*
		 * dummy, always not contiguous
		 * This is reinitialized by cache_init(), later.
	 * Update the 'end' to exclude the next cache range, as clusters in
	 * different cache are typically not contiguous.
	 */
		WARN_ON(cid.id != EXFAT_CACHE_VALID ||
			cid.fcluster != EXFAT_EOF_CLUSTER ||
			cid.dcluster != EXFAT_EOF_CLUSTER ||
			cid.nr_contig != 0);
	}
	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;

	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))
	/* Find the first cluster we need. */
	while (fclus < cluster) {
		if (exfat_ent_get(sb, *dclus, &content, &bh))
			return -EIO;

		*last_dclus = *dclus;
		*dclus = content;
		(*fclus)++;
		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;
		if (content == EXFAT_EOF_CLUSTER)
			break;

		if (!cache_contiguous(&cid, *dclus))
			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;

		if (!cache_contiguous(&cid, *dclus))
			cache_init(&cid, *fclus, *dclus);
		/*
		 * 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;
}
+3 −4
Original line number Diff line number Diff line
@@ -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,
@@ -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 *count, unsigned int *last_dclus);

/* dir.c */
extern const struct inode_operations exfat_dir_inode_operations;
+41 −21
Original line number Diff line number Diff line
@@ -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);

	if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) {
		brelse(bh);
		bh = sb_bread(sb, sec);
	if (!bh)
		if (last)
			*last = bh;
		if (unlikely(!bh))
			return -EIO;
	}

	*content = le32_to_cpu(*(__le32 *)(&bh->b_data[off]));

@@ -55,6 +60,7 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc,
	if (*content > EXFAT_BAD_CLUSTER)
		*content = EXFAT_EOF_CLUSTER;

	if (!last)
		brelse(bh);
	return 0;
}
@@ -82,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);
	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,
@@ -192,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;

@@ -281,6 +297,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;

@@ -293,10 +310,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))
		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))",
@@ -469,6 +487,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;
@@ -484,12 +503,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))
		if (exfat_ent_get(sb, clu, &clu, &bh))
			return -EIO;
		if (clu == EXFAT_EOF_CLUSTER)
			break;
	}

	brelse(bh);
	*ret_count = count;

	/*
+7 −9
Original line number Diff line number Diff line
@@ -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;
	}
@@ -708,21 +709,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);
+21 −36
Original line number Diff line number Diff line
@@ -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,39 +147,22 @@ 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
	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 if (ei->type == TYPE_FILE) {
		unsigned int fclus = 0;
	} else {
		int err = exfat_get_cluster(inode, clu_offset,
				&fclus, clu, &last_clu, 1);
				clu, count, &last_clu);
		if (err)
			return -EIO;

		clu_offset -= fclus;
	} 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) {
@@ -251,7 +234,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
				num_to_be_allocated--;
			}
		}

		*count = 1;
	}

	/* hint information */
@@ -270,7 +253,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;
@@ -283,8 +266,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,
@@ -300,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 = 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);
@@ -511,7 +495,8 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
			exfat_write_failed(mapping, size);

		return ret;
	} else
	}

	size = pos + ret;

	if (rw == WRITE) {
Loading