This commit was manufactured by cvs2svn to create tag
[linux-2.6.git] / fs / fat / cache.c
index dce337f..ad8ff5f 100644 (file)
 #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);
@@ -236,13 +45,13 @@ int __fat_access(struct super_block *sb, int nr, int new_value)
        }
        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)];
@@ -255,10 +64,10 @@ int __fat_access(struct super_block *sb, int nr, int new_value)
        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);
@@ -321,25 +130,170 @@ out:
        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);
@@ -349,14 +303,7 @@ int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
        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) {
@@ -375,15 +322,13 @@ int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)
                                     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;
 }