/* * Copyright (C) 2012-2013 Samsung Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ /* * 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. */ /************************************************************************/ /* */ /* PROJECT : exFAT & FAT12/16/32 File System */ /* FILE : extent.c */ /* PURPOSE : Improve the performance of traversing fat chain. */ /* */ /*----------------------------------------------------------------------*/ /* NOTES */ /* */ /* */ /************************************************************************/ #include #include "sdfat.h" #include "core.h" #define EXTENT_CACHE_VALID 0 /* this must be > 0. */ #define EXTENT_MAX_CACHE 16 struct extent_cache { struct list_head cache_list; u32 nr_contig; /* number of contiguous clusters */ u32 fcluster; /* cluster number in the file. */ u32 dcluster; /* cluster number on disk. */ }; struct extent_cache_id { u32 id; u32 nr_contig; u32 fcluster; u32 dcluster; }; static struct kmem_cache *extent_cache_cachep; static void init_once(void *c) { struct extent_cache *cache = (struct extent_cache *)c; INIT_LIST_HEAD(&cache->cache_list); } s32 extent_cache_init(void) { extent_cache_cachep = kmem_cache_create("sdfat_extent_cache", sizeof(struct extent_cache), 0, SLAB_RECLAIM_ACCOUNT|SLAB_MEM_SPREAD, init_once); if (!extent_cache_cachep) return -ENOMEM; return 0; } void extent_cache_shutdown(void) { if (!extent_cache_cachep) return; kmem_cache_destroy(extent_cache_cachep); } void extent_cache_init_inode(struct inode *inode) { EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); spin_lock_init(&extent->cache_lru_lock); extent->nr_caches = 0; extent->cache_valid_id = EXTENT_CACHE_VALID + 1; INIT_LIST_HEAD(&extent->cache_lru); } static inline struct extent_cache *extent_cache_alloc(void) { return kmem_cache_alloc(extent_cache_cachep, GFP_NOFS); } static inline void extent_cache_free(struct extent_cache *cache) { BUG_ON(!list_empty(&cache->cache_list)); kmem_cache_free(extent_cache_cachep, cache); } static inline void extent_cache_update_lru(struct inode *inode, struct extent_cache *cache) { EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); if (extent->cache_lru.next != &cache->cache_list) list_move(&cache->cache_list, &extent->cache_lru); } static u32 extent_cache_lookup(struct inode *inode, u32 fclus, struct extent_cache_id *cid, u32 *cached_fclus, u32 *cached_dclus) { EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); static struct extent_cache nohit = { .fcluster = 0, }; struct extent_cache *hit = &nohit, *p; u32 offset = CLUS_EOF; spin_lock(&extent->cache_lru_lock); list_for_each_entry(p, &extent->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) { extent_cache_update_lru(inode, hit); cid->id = extent->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(&extent->cache_lru_lock); return offset; } static struct extent_cache *extent_cache_merge(struct inode *inode, struct extent_cache_id *new) { EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); struct extent_cache *p; list_for_each_entry(p, &extent->cache_lru, cache_list) { /* Find the same part as "new" in cluster-chain. */ if (p->fcluster == new->fcluster) { ASSERT(p->dcluster == new->dcluster); if (new->nr_contig > p->nr_contig) p->nr_contig = new->nr_contig; return p; } } return NULL; } static void extent_cache_add(struct inode *inode, struct extent_cache_id *new) { EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); struct extent_cache *cache, *tmp; if (new->fcluster == -1) /* dummy cache */ return; spin_lock(&extent->cache_lru_lock); if (new->id != EXTENT_CACHE_VALID && new->id != extent->cache_valid_id) goto out; /* this cache was invalidated */ cache = extent_cache_merge(inode, new); if (cache == NULL) { if (extent->nr_caches < EXTENT_MAX_CACHE) { extent->nr_caches++; spin_unlock(&extent->cache_lru_lock); tmp = extent_cache_alloc(); if (!tmp) { spin_lock(&extent->cache_lru_lock); extent->nr_caches--; spin_unlock(&extent->cache_lru_lock); return; } spin_lock(&extent->cache_lru_lock); cache = extent_cache_merge(inode, new); if (cache != NULL) { extent->nr_caches--; extent_cache_free(tmp); goto out_update_lru; } cache = tmp; } else { struct list_head *p = extent->cache_lru.prev; cache = list_entry(p, struct extent_cache, cache_list); } cache->fcluster = new->fcluster; cache->dcluster = new->dcluster; cache->nr_contig = new->nr_contig; } out_update_lru: extent_cache_update_lru(inode, cache); out: spin_unlock(&extent->cache_lru_lock); } /* * Cache invalidation occurs rarely, thus the LRU chain is not updated. It * fixes itself after a while. */ static void __extent_cache_inval_inode(struct inode *inode) { EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); struct extent_cache *cache; while (!list_empty(&extent->cache_lru)) { cache = list_entry(extent->cache_lru.next, struct extent_cache, cache_list); list_del_init(&cache->cache_list); extent->nr_caches--; extent_cache_free(cache); } /* Update. The copy of caches before this id is discarded. */ extent->cache_valid_id++; if (extent->cache_valid_id == EXTENT_CACHE_VALID) extent->cache_valid_id++; } void extent_cache_inval_inode(struct inode *inode) { EXTENT_T *extent = &(SDFAT_I(inode)->fid.extent); spin_lock(&extent->cache_lru_lock); __extent_cache_inval_inode(inode); spin_unlock(&extent->cache_lru_lock); } static inline s32 cache_contiguous(struct extent_cache_id *cid, u32 dclus) { cid->nr_contig++; return ((cid->dcluster + cid->nr_contig) == dclus); } static inline void cache_init(struct extent_cache_id *cid, u32 fclus, u32 dclus) { cid->id = EXTENT_CACHE_VALID; cid->fcluster = fclus; cid->dcluster = dclus; cid->nr_contig = 0; } s32 extent_get_clus(struct inode *inode, u32 cluster, u32 *fclus, u32 *dclus, u32 *last_dclus, s32 allow_eof) { struct super_block *sb = inode->i_sb; FS_INFO_T *fsi = &(SDFAT_SB(sb)->fsi); u32 limit = fsi->num_clusters; FILE_ID_T *fid = &(SDFAT_I(inode)->fid); struct extent_cache_id cid; u32 content; /* FOR GRACEFUL ERROR HANDLING */ if (IS_CLUS_FREE(fid->start_clu)) { sdfat_fs_error(sb, "invalid access to " "extent cache (entry 0x%08x)", fid->start_clu); ASSERT(0); return -EIO; } *fclus = 0; *dclus = fid->start_clu; *last_dclus = *dclus; /* * Don`t use extent_cache if zero offset or non-cluster allocation */ if ((cluster == 0) || IS_CLUS_EOF(*dclus)) return 0; cache_init(&cid, CLUS_EOF, CLUS_EOF); if (extent_cache_lookup(inode, cluster, &cid, fclus, dclus) == CLUS_EOF) { /* * dummy, always not contiguous * This is reinitialized by cache_init(), later. */ ASSERT((cid.id == EXTENT_CACHE_VALID) && (cid.fcluster == CLUS_EOF) && (cid.dcluster == CLUS_EOF) && (cid.nr_contig == 0)); } if (*fclus == cluster) return 0; while (*fclus < cluster) { /* prevent the infinite loop of cluster chain */ if (*fclus > limit) { sdfat_fs_error(sb, "%s: detected the cluster chain loop" " (i_pos %u)", __func__, (*fclus)); return -EIO; } if (fat_ent_get_safe(sb, *dclus, &content)) return -EIO; *last_dclus = *dclus; *dclus = content; (*fclus)++; if (IS_CLUS_EOF(content)) { if (!allow_eof) { sdfat_fs_error(sb, "%s: invalid cluster chain (i_pos %u," "last_clus 0x%08x is EOF)", __func__, *fclus, (*last_dclus)); return -EIO; } break; } if (!cache_contiguous(&cid, *dclus)) cache_init(&cid, *fclus, *dclus); } extent_cache_add(inode, &cid); return 0; }