| // SPDX-License-Identifier: GPL-2.0-or-later | 
 | /* | 
 |  *  linux/fs/fat/cache.c | 
 |  * | 
 |  *  Written 1992,1993 by Werner Almesberger | 
 |  * | 
 |  *  Mar 1999. AV. Changed cache, so that it uses the starting cluster instead | 
 |  *	of inode number. | 
 |  *  May 1999. AV. Fixed the bogosity with FAT32 (read "FAT28"). Fscking lusers. | 
 |  *  Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. | 
 |  */ | 
 |  | 
 | #include <linux/slab.h> | 
 | #include <asm/unaligned.h> | 
 | #include <linux/buffer_head.h> | 
 |  | 
 | #include "exfat_raw.h" | 
 | #include "exfat_fs.h" | 
 |  | 
 | #define EXFAT_MAX_CACHE		16 | 
 |  | 
 | struct exfat_cache { | 
 | 	struct list_head cache_list; | 
 | 	unsigned int nr_contig;	/* number of contiguous clusters */ | 
 | 	unsigned int fcluster;	/* cluster number in the file. */ | 
 | 	unsigned int dcluster;	/* cluster number on disk. */ | 
 | }; | 
 |  | 
 | struct exfat_cache_id { | 
 | 	unsigned int id; | 
 | 	unsigned int nr_contig; | 
 | 	unsigned int fcluster; | 
 | 	unsigned int dcluster; | 
 | }; | 
 |  | 
 | static struct kmem_cache *exfat_cachep; | 
 |  | 
 | static void exfat_cache_init_once(void *c) | 
 | { | 
 | 	struct exfat_cache *cache = (struct exfat_cache *)c; | 
 |  | 
 | 	INIT_LIST_HEAD(&cache->cache_list); | 
 | } | 
 |  | 
 | int exfat_cache_init(void) | 
 | { | 
 | 	exfat_cachep = kmem_cache_create("exfat_cache", | 
 | 				sizeof(struct exfat_cache), | 
 | 				0, SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, | 
 | 				exfat_cache_init_once); | 
 | 	if (!exfat_cachep) | 
 | 		return -ENOMEM; | 
 | 	return 0; | 
 | } | 
 |  | 
 | void exfat_cache_shutdown(void) | 
 | { | 
 | 	if (!exfat_cachep) | 
 | 		return; | 
 | 	kmem_cache_destroy(exfat_cachep); | 
 | } | 
 |  | 
 | static inline struct exfat_cache *exfat_cache_alloc(void) | 
 | { | 
 | 	return kmem_cache_alloc(exfat_cachep, GFP_NOFS); | 
 | } | 
 |  | 
 | static inline void exfat_cache_free(struct exfat_cache *cache) | 
 | { | 
 | 	WARN_ON(!list_empty(&cache->cache_list)); | 
 | 	kmem_cache_free(exfat_cachep, cache); | 
 | } | 
 |  | 
 | static inline void exfat_cache_update_lru(struct inode *inode, | 
 | 		struct exfat_cache *cache) | 
 | { | 
 | 	struct exfat_inode_info *ei = EXFAT_I(inode); | 
 |  | 
 | 	if (ei->cache_lru.next != &cache->cache_list) | 
 | 		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, | 
 | 		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; | 
 |  | 
 | 	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) { | 
 | 			hit = p; | 
 | 			if (hit->fcluster + hit->nr_contig < fclus) { | 
 | 				offset = hit->nr_contig; | 
 | 			} else { | 
 | 				offset = fclus - hit->fcluster; | 
 | 				break; | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	if (hit != &nohit) { | 
 | 		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; | 
 | 		*cached_fclus = cid->fcluster + offset; | 
 | 		*cached_dclus = cid->dcluster + offset; | 
 | 	} | 
 | 	spin_unlock(&ei->cache_lru_lock); | 
 |  | 
 | 	return offset; | 
 | } | 
 |  | 
 | static struct exfat_cache *exfat_cache_merge(struct inode *inode, | 
 | 		struct exfat_cache_id *new) | 
 | { | 
 | 	struct exfat_inode_info *ei = EXFAT_I(inode); | 
 | 	struct exfat_cache *p; | 
 |  | 
 | 	list_for_each_entry(p, &ei->cache_lru, cache_list) { | 
 | 		/* Find the same part as "new" in cluster-chain. */ | 
 | 		if (p->fcluster == new->fcluster) { | 
 | 			if (new->nr_contig > p->nr_contig) | 
 | 				p->nr_contig = new->nr_contig; | 
 | 			return p; | 
 | 		} | 
 | 	} | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static void exfat_cache_add(struct inode *inode, | 
 | 		struct exfat_cache_id *new) | 
 | { | 
 | 	struct exfat_inode_info *ei = EXFAT_I(inode); | 
 | 	struct exfat_cache *cache, *tmp; | 
 |  | 
 | 	if (new->fcluster == EXFAT_EOF_CLUSTER) /* dummy cache */ | 
 | 		return; | 
 |  | 
 | 	spin_lock(&ei->cache_lru_lock); | 
 | 	if (new->id != EXFAT_CACHE_VALID && | 
 | 	    new->id != ei->cache_valid_id) | 
 | 		goto unlock;	/* this cache was invalidated */ | 
 |  | 
 | 	cache = exfat_cache_merge(inode, new); | 
 | 	if (cache == NULL) { | 
 | 		if (ei->nr_caches < EXFAT_MAX_CACHE) { | 
 | 			ei->nr_caches++; | 
 | 			spin_unlock(&ei->cache_lru_lock); | 
 |  | 
 | 			tmp = exfat_cache_alloc(); | 
 | 			if (!tmp) { | 
 | 				spin_lock(&ei->cache_lru_lock); | 
 | 				ei->nr_caches--; | 
 | 				spin_unlock(&ei->cache_lru_lock); | 
 | 				return; | 
 | 			} | 
 |  | 
 | 			spin_lock(&ei->cache_lru_lock); | 
 | 			cache = exfat_cache_merge(inode, new); | 
 | 			if (cache != NULL) { | 
 | 				ei->nr_caches--; | 
 | 				exfat_cache_free(tmp); | 
 | 				goto out_update_lru; | 
 | 			} | 
 | 			cache = tmp; | 
 | 		} else { | 
 | 			struct list_head *p = ei->cache_lru.prev; | 
 |  | 
 | 			cache = list_entry(p, | 
 | 					struct exfat_cache, cache_list); | 
 | 		} | 
 | 		cache->fcluster = new->fcluster; | 
 | 		cache->dcluster = new->dcluster; | 
 | 		cache->nr_contig = new->nr_contig; | 
 | 	} | 
 | out_update_lru: | 
 | 	exfat_cache_update_lru(inode, cache); | 
 | unlock: | 
 | 	spin_unlock(&ei->cache_lru_lock); | 
 | } | 
 |  | 
 | /* | 
 |  * Cache invalidation occurs rarely, thus the LRU chain is not updated. It | 
 |  * fixes itself after a while. | 
 |  */ | 
 | static void __exfat_cache_inval_inode(struct inode *inode) | 
 | { | 
 | 	struct exfat_inode_info *ei = EXFAT_I(inode); | 
 | 	struct exfat_cache *cache; | 
 |  | 
 | 	while (!list_empty(&ei->cache_lru)) { | 
 | 		cache = list_entry(ei->cache_lru.next, | 
 | 				   struct exfat_cache, cache_list); | 
 | 		list_del_init(&cache->cache_list); | 
 | 		ei->nr_caches--; | 
 | 		exfat_cache_free(cache); | 
 | 	} | 
 | 	/* Update. The copy of caches before this id is discarded. */ | 
 | 	ei->cache_valid_id++; | 
 | 	if (ei->cache_valid_id == EXFAT_CACHE_VALID) | 
 | 		ei->cache_valid_id++; | 
 | } | 
 |  | 
 | void exfat_cache_inval_inode(struct inode *inode) | 
 | { | 
 | 	struct exfat_inode_info *ei = EXFAT_I(inode); | 
 |  | 
 | 	spin_lock(&ei->cache_lru_lock); | 
 | 	__exfat_cache_inval_inode(inode); | 
 | 	spin_unlock(&ei->cache_lru_lock); | 
 | } | 
 |  | 
 | static inline int cache_contiguous(struct exfat_cache_id *cid, | 
 | 		unsigned int dclus) | 
 | { | 
 | 	cid->nr_contig++; | 
 | 	return cid->dcluster + cid->nr_contig == dclus; | 
 | } | 
 |  | 
 | static inline void cache_init(struct exfat_cache_id *cid, | 
 | 		unsigned int fclus, unsigned int dclus) | 
 | { | 
 | 	cid->id = EXFAT_CACHE_VALID; | 
 | 	cid->fcluster = fclus; | 
 | 	cid->dcluster = dclus; | 
 | 	cid->nr_contig = 0; | 
 | } | 
 |  | 
 | int exfat_get_cluster(struct inode *inode, unsigned int cluster, | 
 | 		unsigned int *fclus, unsigned int *dclus, | 
 | 		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; | 
 |  | 
 | 	if (ei->start_clu == EXFAT_FREE_CLUSTER) { | 
 | 		exfat_fs_error(sb, | 
 | 			"invalid access to exfat cache (entry 0x%08x)", | 
 | 			ei->start_clu); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	*fclus = 0; | 
 | 	*dclus = ei->start_clu; | 
 | 	*last_dclus = *dclus; | 
 |  | 
 | 	/* | 
 | 	 * Don`t use exfat_cache if zero offset or non-cluster allocation | 
 | 	 */ | 
 | 	if (cluster == 0 || *dclus == EXFAT_EOF_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); | 
 | 	} | 
 |  | 
 | 	if (*fclus == 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)) | 
 | 			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; | 
 | 			} | 
 |  | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		if (!cache_contiguous(&cid, *dclus)) | 
 | 			cache_init(&cid, *fclus, *dclus); | 
 | 	} | 
 |  | 
 | 	exfat_cache_add(inode, &cid); | 
 | 	return 0; | 
 | } |