blob: 59e096530ddadf6f9d8853181bd0b5a078e11fed [file] [log] [blame]
/*
* 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 <http://www.gnu.org/licenses/>.
*/
/*
* 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 <linux/slab.h>
#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;
}