ALSA: hda - Quirk for Acer Aspire 6530G
[pandora-kernel.git] / mm / vmalloc.c
index 1ddb77b..c5db9a7 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/highmem.h>
 #include <linux/slab.h>
 #include <linux/spinlock.h>
+#include <linux/mutex.h>
 #include <linux/interrupt.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
@@ -151,11 +152,12 @@ static int vmap_pud_range(pgd_t *pgd, unsigned long addr,
  *
  * Ie. pte at addr+N*PAGE_SIZE shall point to pfn corresponding to pages[N]
  */
-static int vmap_page_range(unsigned long addr, unsigned long end,
+static int vmap_page_range(unsigned long start, unsigned long end,
                                pgprot_t prot, struct page **pages)
 {
        pgd_t *pgd;
        unsigned long next;
+       unsigned long addr = start;
        int err = 0;
        int nr = 0;
 
@@ -167,7 +169,7 @@ static int vmap_page_range(unsigned long addr, unsigned long end,
                if (err)
                        break;
        } while (pgd++, addr = next, addr != end);
-       flush_cache_vmap(addr, end);
+       flush_cache_vmap(start, end);
 
        if (unlikely(err))
                return err;
@@ -380,8 +382,9 @@ found:
                        goto retry;
                }
                if (printk_ratelimit())
-                       printk(KERN_WARNING "vmap allocation failed: "
-                                "use vmalloc=<size> to increase size.\n");
+                       printk(KERN_WARNING
+                               "vmap allocation for size %lu failed: "
+                               "use vmalloc=<size> to increase size.\n", size);
                return ERR_PTR(-EBUSY);
        }
 
@@ -431,6 +434,27 @@ static void unmap_vmap_area(struct vmap_area *va)
        vunmap_page_range(va->va_start, va->va_end);
 }
 
+static void vmap_debug_free_range(unsigned long start, unsigned long end)
+{
+       /*
+        * Unmap page tables and force a TLB flush immediately if
+        * CONFIG_DEBUG_PAGEALLOC is set. This catches use after free
+        * bugs similarly to those in linear kernel virtual address
+        * space after a page has been freed.
+        *
+        * All the lazy freeing logic is still retained, in order to
+        * minimise intrusiveness of this debugging feature.
+        *
+        * This is going to be *slow* (linear kernel virtual address
+        * debugging doesn't do a broadcast TLB flush so it is a lot
+        * faster).
+        */
+#ifdef CONFIG_DEBUG_PAGEALLOC
+       vunmap_page_range(start, end);
+       flush_tlb_kernel_range(start, end);
+#endif
+}
+
 /*
  * lazy_max_pages is the maximum amount of virtual address space we gather up
  * before attempting to purge with a TLB flush.
@@ -471,7 +495,7 @@ static atomic_t vmap_lazy_nr = ATOMIC_INIT(0);
 static void __purge_vmap_area_lazy(unsigned long *start, unsigned long *end,
                                        int sync, int force_flush)
 {
-       static DEFINE_SPINLOCK(purge_lock);
+       static DEFINE_MUTEX(purge_lock);
        LIST_HEAD(valist);
        struct vmap_area *va;
        int nr = 0;
@@ -482,10 +506,10 @@ static void __purge_vmap_area_lazy(unsigned long *start, unsigned long *end,
         * the case that isn't actually used at the moment anyway.
         */
        if (!sync && !force_flush) {
-               if (!spin_trylock(&purge_lock))
+               if (!mutex_trylock(&purge_lock))
                        return;
        } else
-               spin_lock(&purge_lock);
+               mutex_lock(&purge_lock);
 
        rcu_read_lock();
        list_for_each_entry_rcu(va, &vmap_area_list, list) {
@@ -517,7 +541,7 @@ static void __purge_vmap_area_lazy(unsigned long *start, unsigned long *end,
                        __free_vmap_area(va);
                spin_unlock(&vmap_area_lock);
        }
-       spin_unlock(&purge_lock);
+       mutex_unlock(&purge_lock);
 }
 
 /*
@@ -911,6 +935,7 @@ void vm_unmap_ram(const void *mem, unsigned int count)
        BUG_ON(addr & (PAGE_SIZE-1));
 
        debug_check_no_locks_freed(mem, size);
+       vmap_debug_free_range(addr, addr+size);
 
        if (likely(count <= VMAP_MAX_ALLOC))
                vb_free(mem, size);
@@ -1127,6 +1152,8 @@ struct vm_struct *remove_vm_area(const void *addr)
        if (va && va->flags & VM_VM_AREA) {
                struct vm_struct *vm = va->private;
                struct vm_struct *tmp, **p;
+
+               vmap_debug_free_range(va->va_start, va->va_end);
                free_unmap_vmap_area(va);
                vm->size -= PAGE_SIZE;
 
@@ -1374,7 +1401,8 @@ void *vmalloc_user(unsigned long size)
        struct vm_struct *area;
        void *ret;
 
-       ret = __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO, PAGE_KERNEL);
+       ret = __vmalloc_node(size, GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
+                            PAGE_KERNEL, -1, __builtin_return_address(0));
        if (ret) {
                area = find_vm_area(ret);
                area->flags |= VM_USERMAP;
@@ -1419,7 +1447,8 @@ EXPORT_SYMBOL(vmalloc_node);
 
 void *vmalloc_exec(unsigned long size)
 {
-       return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC);
+       return __vmalloc_node(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL_EXEC,
+                             -1, __builtin_return_address(0));
 }
 
 #if defined(CONFIG_64BIT) && defined(CONFIG_ZONE_DMA32)
@@ -1439,7 +1468,8 @@ void *vmalloc_exec(unsigned long size)
  */
 void *vmalloc_32(unsigned long size)
 {
-       return __vmalloc(size, GFP_VMALLOC32, PAGE_KERNEL);
+       return __vmalloc_node(size, GFP_VMALLOC32, PAGE_KERNEL,
+                             -1, __builtin_return_address(0));
 }
 EXPORT_SYMBOL(vmalloc_32);
 
@@ -1455,7 +1485,8 @@ void *vmalloc_32_user(unsigned long size)
        struct vm_struct *area;
        void *ret;
 
-       ret = __vmalloc(size, GFP_VMALLOC32 | __GFP_ZERO, PAGE_KERNEL);
+       ret = __vmalloc_node(size, GFP_VMALLOC32 | __GFP_ZERO, PAGE_KERNEL,
+                            -1, __builtin_return_address(0));
        if (ret) {
                area = find_vm_area(ret);
                area->flags |= VM_USERMAP;