#include <linux/msdos_fs.h>
#include <linux/buffer_head.h>
-/* this must be > 0. */
-#define FAT_MAX_CACHE 8
-
-struct fat_cache {
- struct list_head cache_list;
- int nr_contig; /* number of contiguous clusters */
- int fcluster; /* cluster number in the file. */
- int dcluster; /* cluster number on disk. */
-};
-
-struct fat_cache_id {
- unsigned int id;
- int nr_contig;
- int fcluster;
- int dcluster;
-};
-
-static inline int fat_max_cache(struct inode *inode)
-{
- return FAT_MAX_CACHE;
-}
-
-static kmem_cache_t *fat_cache_cachep;
-
-static void init_once(void *foo, kmem_cache_t *cachep, unsigned long flags)
-{
- struct fat_cache *cache = (struct fat_cache *)foo;
-
- if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
- SLAB_CTOR_CONSTRUCTOR)
- INIT_LIST_HEAD(&cache->cache_list);
-}
-
-int __init fat_cache_init(void)
-{
- fat_cache_cachep = kmem_cache_create("fat_cache",
- sizeof(struct fat_cache),
- 0, SLAB_RECLAIM_ACCOUNT,
- init_once, NULL);
- if (fat_cache_cachep == NULL)
- return -ENOMEM;
- return 0;
-}
-
-void __exit fat_cache_destroy(void)
-{
- if (kmem_cache_destroy(fat_cache_cachep))
- printk(KERN_INFO "fat_cache: not all structures were freed\n");
-}
-
-static inline struct fat_cache *fat_cache_alloc(struct inode *inode)
-{
- return kmem_cache_alloc(fat_cache_cachep, SLAB_KERNEL);
-}
-
-static inline void fat_cache_free(struct fat_cache *cache)
-{
- BUG_ON(!list_empty(&cache->cache_list));
- kmem_cache_free(fat_cache_cachep, cache);
-}
-
-static inline void fat_cache_update_lru(struct inode *inode,
- struct fat_cache *cache)
-{
- if (MSDOS_I(inode)->cache_lru.next != &cache->cache_list)
- list_move(&cache->cache_list, &MSDOS_I(inode)->cache_lru);
-}
-
-static int fat_cache_lookup(struct inode *inode, int fclus,
- struct fat_cache_id *cid,
- int *cached_fclus, int *cached_dclus)
-{
- static struct fat_cache nohit = { .fcluster = 0, };
-
- struct fat_cache *hit = &nohit, *p;
- int offset = -1;
-
- spin_lock(&MSDOS_I(inode)->cache_lru_lock);
- list_for_each_entry(p, &MSDOS_I(inode)->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) {
- fat_cache_update_lru(inode, hit);
-
- cid->id = MSDOS_I(inode)->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(&MSDOS_I(inode)->cache_lru_lock);
-
- return offset;
-}
-
-static struct fat_cache *fat_cache_merge(struct inode *inode,
- struct fat_cache_id *new)
-{
- struct fat_cache *p;
-
- list_for_each_entry(p, &MSDOS_I(inode)->cache_lru, cache_list) {
- /* Find the same part as "new" in cluster-chain. */
- if (p->fcluster == new->fcluster) {
- BUG_ON(p->dcluster != new->dcluster);
- if (new->nr_contig > p->nr_contig)
- p->nr_contig = new->nr_contig;
- return p;
- }
- }
- return NULL;
-}
-
-static void fat_cache_add(struct inode *inode, struct fat_cache_id *new)
-{
- struct fat_cache *cache, *tmp;
-
- if (new->fcluster == -1) /* dummy cache */
- return;
-
- spin_lock(&MSDOS_I(inode)->cache_lru_lock);
- if (new->id != FAT_CACHE_VALID &&
- new->id != MSDOS_I(inode)->cache_valid_id)
- goto out; /* this cache was invalidated */
-
- cache = fat_cache_merge(inode, new);
- if (cache == NULL) {
- if (MSDOS_I(inode)->nr_caches < fat_max_cache(inode)) {
- MSDOS_I(inode)->nr_caches++;
- spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
-
- tmp = fat_cache_alloc(inode);
- spin_lock(&MSDOS_I(inode)->cache_lru_lock);
- cache = fat_cache_merge(inode, new);
- if (cache != NULL) {
- MSDOS_I(inode)->nr_caches--;
- fat_cache_free(tmp);
- goto out_update_lru;
- }
- cache = tmp;
- } else {
- struct list_head *p = MSDOS_I(inode)->cache_lru.prev;
- cache = list_entry(p, struct fat_cache, cache_list);
- }
- cache->fcluster = new->fcluster;
- cache->dcluster = new->dcluster;
- cache->nr_contig = new->nr_contig;
- }
-out_update_lru:
- fat_cache_update_lru(inode, cache);
-out:
- spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
-}
-
-/*
- * Cache invalidation occurs rarely, thus the LRU chain is not updated. It
- * fixes itself after a while.
- */
-static void __fat_cache_inval_inode(struct inode *inode)
-{
- struct msdos_inode_info *i = MSDOS_I(inode);
- struct fat_cache *cache;
-
- while (!list_empty(&i->cache_lru)) {
- cache = list_entry(i->cache_lru.next, struct fat_cache, cache_list);
- list_del_init(&cache->cache_list);
- i->nr_caches--;
- fat_cache_free(cache);
- }
- /* Update. The copy of caches before this id is discarded. */
- i->cache_valid_id++;
- if (i->cache_valid_id == FAT_CACHE_VALID)
- i->cache_valid_id++;
-}
-
-void fat_cache_inval_inode(struct inode *inode)
-{
- spin_lock(&MSDOS_I(inode)->cache_lru_lock);
- __fat_cache_inval_inode(inode);
- spin_unlock(&MSDOS_I(inode)->cache_lru_lock);
-}
-
int __fat_access(struct super_block *sb, int nr, int new_value)
{
struct msdos_sb_info *sbi = MSDOS_SB(sb);
}
if (sbi->fat_bits == 32) {
p_first = p_last = NULL; /* GCC needs that stuff */
- next = le32_to_cpu(((__le32 *) bh->b_data)[(first &
+ next = CF_LE_L(((__le32 *) bh->b_data)[(first &
(sb->s_blocksize - 1)) >> 2]);
/* Fscking Microsoft marketing department. Their "32" is 28. */
next &= 0x0fffffff;
} else if (sbi->fat_bits == 16) {
p_first = p_last = NULL; /* GCC needs that stuff */
- next = le16_to_cpu(((__le16 *) bh->b_data)[(first &
+ next = CF_LE_W(((__le16 *) bh->b_data)[(first &
(sb->s_blocksize - 1)) >> 1]);
} else {
p_first = &((__u8 *)bh->b_data)[first & (sb->s_blocksize - 1)];
if (new_value != -1) {
if (sbi->fat_bits == 32) {
((__le32 *)bh->b_data)[(first & (sb->s_blocksize - 1)) >> 2]
- = cpu_to_le32(new_value);
+ = CT_LE_L(new_value);
} else if (sbi->fat_bits == 16) {
((__le16 *)bh->b_data)[(first & (sb->s_blocksize - 1)) >> 1]
- = cpu_to_le16(new_value);
+ = CT_LE_W(new_value);
} else {
if (nr & 1) {
*p_first = (*p_first & 0xf) | (new_value << 4);
return next;
}
-static inline int cache_contiguous(struct fat_cache_id *cid, int dclus)
+void fat_cache_init(struct super_block *sb)
{
- cid->nr_contig++;
- return ((cid->dcluster + cid->nr_contig) == dclus);
+ struct msdos_sb_info *sbi = MSDOS_SB(sb);
+ int count;
+
+ spin_lock_init(&sbi->cache_lock);
+
+ for (count = 0; count < FAT_CACHE_NR - 1; count++) {
+ sbi->cache_array[count].start_cluster = 0;
+ sbi->cache_array[count].next = &sbi->cache_array[count + 1];
+ }
+ sbi->cache_array[count].start_cluster = 0;
+ sbi->cache_array[count].next = NULL;
+ sbi->cache = sbi->cache_array;
}
-static inline void cache_init(struct fat_cache_id *cid, int fclus, int dclus)
+static void
+fat_cache_lookup(struct inode *inode, int cluster, int *f_clu, int *d_clu)
{
- cid->id = FAT_CACHE_VALID;
- cid->fcluster = fclus;
- cid->dcluster = dclus;
- cid->nr_contig = 0;
+ struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+ struct fat_cache *walk;
+ int first;
+
+ BUG_ON(cluster == 0);
+
+ first = MSDOS_I(inode)->i_start;
+ if (!first)
+ return;
+
+ spin_lock(&sbi->cache_lock);
+
+ if (MSDOS_I(inode)->disk_cluster &&
+ MSDOS_I(inode)->file_cluster <= cluster) {
+ *d_clu = MSDOS_I(inode)->disk_cluster;
+ *f_clu = MSDOS_I(inode)->file_cluster;
+ }
+
+ for (walk = sbi->cache; walk; walk = walk->next) {
+ if (walk->start_cluster == first
+ && walk->file_cluster <= cluster
+ && walk->file_cluster > *f_clu) {
+ *d_clu = walk->disk_cluster;
+ *f_clu = walk->file_cluster;
+#ifdef DEBUG
+ printk("cache hit: %d (%d)\n", *f_clu, *d_clu);
+#endif
+ if (*f_clu == cluster)
+ goto out;
+ }
+ }
+#ifdef DEBUG
+ printk("cache miss\n");
+#endif
+out:
+ spin_unlock(&sbi->cache_lock);
+}
+
+#ifdef DEBUG
+static void list_cache(struct super_block *sb)
+{
+ struct msdos_sb_info *sbi = MSDOS_SB(sb);
+ struct fat_cache *walk;
+
+ for (walk = sbi->cache; walk; walk = walk->next) {
+ if (walk->start_cluster)
+ printk("<%s,%d>(%d,%d) ", sb->s_id,
+ walk->start_cluster, walk->file_cluster,
+ walk->disk_cluster);
+ else
+ printk("-- ");
+ }
+ printk("\n");
+}
+#endif
+
+/*
+ * Cache invalidation occurs rarely, thus the LRU chain is not updated. It
+ * fixes itself after a while.
+ */
+static void __fat_cache_inval_inode(struct inode *inode)
+{
+ struct fat_cache *walk;
+ int first = MSDOS_I(inode)->i_start;
+ MSDOS_I(inode)->file_cluster = MSDOS_I(inode)->disk_cluster = 0;
+ for (walk = MSDOS_SB(inode->i_sb)->cache; walk; walk = walk->next)
+ if (walk->start_cluster == first)
+ walk->start_cluster = 0;
+}
+
+void fat_cache_inval_inode(struct inode *inode)
+{
+ struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+ spin_lock(&sbi->cache_lock);
+ __fat_cache_inval_inode(inode);
+ spin_unlock(&sbi->cache_lock);
+}
+
+void fat_cache_add(struct inode *inode, int f_clu, int d_clu)
+{
+ struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb);
+ struct fat_cache *walk, *last;
+ int first, prev_f_clu, prev_d_clu;
+
+ if (f_clu == 0)
+ return;
+ first = MSDOS_I(inode)->i_start;
+ if (!first)
+ return;
+
+ last = NULL;
+ spin_lock(&sbi->cache_lock);
+
+ if (MSDOS_I(inode)->file_cluster == f_clu)
+ goto out;
+ else {
+ prev_f_clu = MSDOS_I(inode)->file_cluster;
+ prev_d_clu = MSDOS_I(inode)->disk_cluster;
+ MSDOS_I(inode)->file_cluster = f_clu;
+ MSDOS_I(inode)->disk_cluster = d_clu;
+ if (prev_f_clu == 0)
+ goto out;
+ f_clu = prev_f_clu;
+ d_clu = prev_d_clu;
+ }
+
+ for (walk = sbi->cache; walk->next; walk = (last = walk)->next) {
+ if (walk->start_cluster == first &&
+ walk->file_cluster == f_clu) {
+ if (walk->disk_cluster != d_clu) {
+ printk(KERN_ERR "FAT: cache corruption "
+ "(i_pos %lld)\n", MSDOS_I(inode)->i_pos);
+ __fat_cache_inval_inode(inode);
+ goto out;
+ }
+ if (last == NULL)
+ goto out;
+
+ /* update LRU */
+ last->next = walk->next;
+ walk->next = sbi->cache;
+ sbi->cache = walk;
+#ifdef DEBUG
+ list_cache();
+#endif
+ goto out;
+ }
+ }
+ walk->start_cluster = first;
+ walk->file_cluster = f_clu;
+ walk->disk_cluster = d_clu;
+ last->next = NULL;
+ walk->next = sbi->cache;
+ sbi->cache = walk;
+#ifdef DEBUG
+ list_cache();
+#endif
+out:
+ spin_unlock(&sbi->cache_lock);
}
int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
{
struct super_block *sb = inode->i_sb;
const int limit = sb->s_maxbytes >> MSDOS_SB(sb)->cluster_bits;
- struct fat_cache_id cid;
int nr;
BUG_ON(MSDOS_I(inode)->i_start == 0);
if (cluster == 0)
return 0;
- if (fat_cache_lookup(inode, cluster, &cid, fclus, dclus) < 0) {
- /*
- * dummy, always not contiguous
- * This is reinitialized by cache_init(), later.
- */
- cache_init(&cid, -1, -1);
- }
-
+ fat_cache_lookup(inode, cluster, fclus, dclus);
while (*fclus < cluster) {
/* prevent the infinite loop of cluster chain */
if (*fclus > limit) {
MSDOS_I(inode)->i_pos);
return -EIO;
} else if (nr == FAT_ENT_EOF) {
- fat_cache_add(inode, &cid);
+ fat_cache_add(inode, *fclus, *dclus);
return FAT_ENT_EOF;
}
(*fclus)++;
*dclus = nr;
- if (!cache_contiguous(&cid, *dclus))
- cache_init(&cid, *fclus, *dclus);
}
- fat_cache_add(inode, &cid);
+ fat_cache_add(inode, *fclus, *dclus);
return 0;
}