Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
[linux-2.6-block.git] / mm / vmalloc.c
index 72043d6c88c09c16b581c571cb6b79ee12a34b68..b12fd8612604f26bfca3006fd07bcbad27d73040 100644 (file)
 #include <linux/pfn.h>
 #include <linux/kmemleak.h>
 #include <linux/atomic.h>
+#include <linux/llist.h>
 #include <asm/uaccess.h>
 #include <asm/tlbflush.h>
 #include <asm/shmparam.h>
 
+struct vfree_deferred {
+       struct llist_head list;
+       struct work_struct wq;
+};
+static DEFINE_PER_CPU(struct vfree_deferred, vfree_deferred);
+
+static void __vunmap(const void *, int);
+
+static void free_work(struct work_struct *w)
+{
+       struct vfree_deferred *p = container_of(w, struct vfree_deferred, wq);
+       struct llist_node *llnode = llist_del_all(&p->list);
+       while (llnode) {
+               void *p = llnode;
+               llnode = llist_next(llnode);
+               __vunmap(p, 1);
+       }
+}
+
 /*** Page table manipulation functions ***/
 
 static void vunmap_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end)
@@ -1175,10 +1195,14 @@ void __init vmalloc_init(void)
 
        for_each_possible_cpu(i) {
                struct vmap_block_queue *vbq;
+               struct vfree_deferred *p;
 
                vbq = &per_cpu(vmap_block_queue, i);
                spin_lock_init(&vbq->lock);
                INIT_LIST_HEAD(&vbq->free);
+               p = &per_cpu(vfree_deferred, i);
+               init_llist_head(&p->list);
+               INIT_WORK(&p->wq, free_work);
        }
 
        /* Import existing vmlist entries. */
@@ -1486,7 +1510,7 @@ static void __vunmap(const void *addr, int deallocate_pages)
        kfree(area);
        return;
 }
-
 /**
  *     vfree  -  release memory allocated by vmalloc()
  *     @addr:          memory base address
@@ -1495,15 +1519,25 @@ static void __vunmap(const void *addr, int deallocate_pages)
  *     obtained from vmalloc(), vmalloc_32() or __vmalloc(). If @addr is
  *     NULL, no operation is performed.
  *
- *     Must not be called in interrupt context.
+ *     Must not be called in NMI context (strictly speaking, only if we don't
+ *     have CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG, but making the calling
+ *     conventions for vfree() arch-depenedent would be a really bad idea)
+ *     
  */
 void vfree(const void *addr)
 {
-       BUG_ON(in_interrupt());
+       BUG_ON(in_nmi());
 
        kmemleak_free(addr);
 
-       __vunmap(addr, 1);
+       if (!addr)
+               return;
+       if (unlikely(in_interrupt())) {
+               struct vfree_deferred *p = &__get_cpu_var(vfree_deferred);
+               llist_add((struct llist_node *)addr, &p->list);
+               schedule_work(&p->wq);
+       } else
+               __vunmap(addr, 1);
 }
 EXPORT_SYMBOL(vfree);
 
@@ -1520,7 +1554,8 @@ void vunmap(const void *addr)
 {
        BUG_ON(in_interrupt());
        might_sleep();
-       __vunmap(addr, 0);
+       if (addr)
+               __vunmap(addr, 0);
 }
 EXPORT_SYMBOL(vunmap);