This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / lib / genalloc.c
diff --git a/lib/genalloc.c b/lib/genalloc.c
new file mode 100644 (file)
index 0000000..9ce0a6a
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Basic general purpose allocator for managing special purpose memory
+ * not managed by the regular kmalloc/kfree interface.
+ * Uses for this includes on-device special memory, uncached memory
+ * etc.
+ *
+ * This code is based on the buddy allocator found in the sym53c8xx_2
+ * driver Copyright (C) 1999-2001  Gerard Roudier <groudier@free.fr>,
+ * and adapted for general purpose use.
+ *
+ * Copyright 2005 (C) Jes Sorensen <jes@trained-monkey.org>
+ *
+ * This source code is licensed under the GNU General Public License,
+ * Version 2.  See the file COPYING for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/spinlock.h>
+#include <linux/genalloc.h>
+
+#include <asm/page.h>
+
+
+struct gen_pool *gen_pool_create(int nr_chunks, int max_chunk_shift,
+                                unsigned long (*fp)(struct gen_pool *),
+                                unsigned long data)
+{
+       struct gen_pool *poolp;
+       unsigned long tmp;
+       int i;
+
+       /*
+        * This is really an arbitrary limit, +10 is enough for
+        * IA64_GRANULE_SHIFT, aka 16MB. If anyone needs a large limit
+        * this can be increased without problems.
+        */
+       if ((max_chunk_shift > (PAGE_SHIFT + 10)) ||
+           ((max_chunk_shift < ALLOC_MIN_SHIFT) && max_chunk_shift))
+               return NULL;
+
+       if (!max_chunk_shift)
+               max_chunk_shift = PAGE_SHIFT;
+
+       poolp = kmalloc(sizeof(struct gen_pool), GFP_KERNEL);
+       if (!poolp)
+               return NULL;
+       memset(poolp, 0, sizeof(struct gen_pool));
+       poolp->h = kmalloc(sizeof(struct gen_pool_link) *
+                          (max_chunk_shift - ALLOC_MIN_SHIFT + 1),
+                          GFP_KERNEL);
+       if (!poolp->h) {
+               printk(KERN_WARNING "gen_pool_alloc() failed to allocate\n");
+               kfree(poolp);
+               return NULL;
+       }
+       memset(poolp->h, 0, sizeof(struct gen_pool_link) *
+              (max_chunk_shift - ALLOC_MIN_SHIFT + 1));
+
+       spin_lock_init(&poolp->lock);
+       poolp->get_new_chunk = fp;
+       poolp->max_chunk_shift = max_chunk_shift;
+       poolp->private = data;
+
+       for (i = 0; i < nr_chunks; i++) {
+               tmp = poolp->get_new_chunk(poolp);
+               printk(KERN_INFO "allocated %lx\n", tmp);
+               if (!tmp)
+                       break;
+               gen_pool_free(poolp, tmp, (1 << poolp->max_chunk_shift));
+       }
+
+       return poolp;
+}
+EXPORT_SYMBOL(gen_pool_create);
+
+
+/*
+ *  Simple power of two buddy-like generic allocator.
+ *  Provides naturally aligned memory chunks.
+ */
+unsigned long gen_pool_alloc(struct gen_pool *poolp, int size)
+{
+       int j, i, s, max_chunk_size;
+       unsigned long a, flags;
+       struct gen_pool_link *h = poolp->h;
+
+       max_chunk_size = 1 << poolp->max_chunk_shift;
+
+       if (size > max_chunk_size)
+               return 0;
+
+       size = max(size, 1 << ALLOC_MIN_SHIFT);
+       i = fls(size - 1);
+       s = 1 << i;
+       j = i -= ALLOC_MIN_SHIFT;
+
+       spin_lock_irqsave(&poolp->lock, flags);
+       while (!h[j].next) {
+               if (s == max_chunk_size) {
+                       struct gen_pool_link *ptr;
+                       spin_unlock_irqrestore(&poolp->lock, flags);
+                       ptr = (struct gen_pool_link *)poolp->get_new_chunk(poolp);
+                       spin_lock_irqsave(&poolp->lock, flags);
+                       h[j].next = ptr;
+                       if (h[j].next)
+                               h[j].next->next = NULL;
+                       break;
+               }
+               j++;
+               s <<= 1;
+       }
+       a = (unsigned long) h[j].next;
+       if (a) {
+               h[j].next = h[j].next->next;
+               /*
+                * This should be split into a seperate function doing
+                * the chunk split in order to support custom
+                * handling memory not physically accessible by host
+                */
+               while (j > i) {
+                       j -= 1;
+                       s >>= 1;
+                       h[j].next = (struct gen_pool_link *) (a + s);
+                       h[j].next->next = NULL;
+               }
+       }
+       spin_unlock_irqrestore(&poolp->lock, flags);
+       return a;
+}
+EXPORT_SYMBOL(gen_pool_alloc);
+
+
+/*
+ *  Counter-part of the generic allocator.
+ */
+void gen_pool_free(struct gen_pool *poolp, unsigned long ptr, int size)
+{
+       struct gen_pool_link *q;
+       struct gen_pool_link *h = poolp->h;
+       unsigned long a, b, flags;
+       int i, s, max_chunk_size;
+
+       max_chunk_size = 1 << poolp->max_chunk_shift;
+
+       if (size > max_chunk_size)
+               return;
+
+       size = max(size, 1 << ALLOC_MIN_SHIFT);
+       i = fls(size - 1);
+       s = 1 << i;
+       i -= ALLOC_MIN_SHIFT;
+
+       a = ptr;
+
+       spin_lock_irqsave(&poolp->lock, flags);
+       while (1) {
+               if (s == max_chunk_size) {
+                       ((struct gen_pool_link *)a)->next = h[i].next;
+                       h[i].next = (struct gen_pool_link *)a;
+                       break;
+               }
+               b = a ^ s;
+               q = &h[i];
+
+               while (q->next && q->next != (struct gen_pool_link *)b)
+                       q = q->next;
+
+               if (!q->next) {
+                       ((struct gen_pool_link *)a)->next = h[i].next;
+                       h[i].next = (struct gen_pool_link *)a;
+                       break;
+               }
+               q->next = q->next->next;
+               a = a & b;
+               s <<= 1;
+               i++;
+       }
+       spin_unlock_irqrestore(&poolp->lock, flags);
+}
+EXPORT_SYMBOL(gen_pool_free);