#include <linux/mm.h>
 #include <linux/mman.h>
 #include <linux/module.h>
+#include <linux/personality.h>
 #include <linux/random.h>
 #include <linux/sched.h>
 
 
 EXPORT_SYMBOL(shm_align_mask);
 
+/* gap between mmap and stack */
+#define MIN_GAP (128*1024*1024UL)
+#define MAX_GAP        ((TASK_SIZE)/6*5)
+
+static int mmap_is_legacy(void)
+{
+       if (current->personality & ADDR_COMPAT_LAYOUT)
+               return 1;
+
+       if (rlimit(RLIMIT_STACK) == RLIM_INFINITY)
+               return 1;
+
+       return sysctl_legacy_va_layout;
+}
+
+static unsigned long mmap_base(unsigned long rnd)
+{
+       unsigned long gap = rlimit(RLIMIT_STACK);
+
+       if (gap < MIN_GAP)
+               gap = MIN_GAP;
+       else if (gap > MAX_GAP)
+               gap = MAX_GAP;
+
+       return PAGE_ALIGN(TASK_SIZE - gap - rnd);
+}
+
+static inline unsigned long COLOUR_ALIGN_DOWN(unsigned long addr,
+                                             unsigned long pgoff)
+{
+       unsigned long base = addr & ~shm_align_mask;
+       unsigned long off = (pgoff << PAGE_SHIFT) & shm_align_mask;
+
+       if (base + off <= addr)
+               return base + off;
+
+       return base - off;
+}
+
 #define COLOUR_ALIGN(addr,pgoff)                               \
        ((((addr) + shm_align_mask) & ~shm_align_mask) +        \
         (((pgoff) << PAGE_SHIFT) & shm_align_mask))
 
-unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr,
-       unsigned long len, unsigned long pgoff, unsigned long flags)
+enum mmap_allocation_direction {UP, DOWN};
+
+static unsigned long arch_get_unmapped_area_foo(struct file *filp,
+       unsigned long addr0, unsigned long len, unsigned long pgoff,
+       unsigned long flags, enum mmap_allocation_direction dir)
 {
-       struct vm_area_struct * vmm;
+       struct mm_struct *mm = current->mm;
+       struct vm_area_struct *vma;
+       unsigned long addr = addr0;
        int do_color_align;
 
-       if (len > TASK_SIZE)
+       if (unlikely(len > TASK_SIZE))
                return -ENOMEM;
 
        if (flags & MAP_FIXED) {
-               /* Even MAP_FIXED mappings must reside within TASK_SIZE.  */
+               /* Even MAP_FIXED mappings must reside within TASK_SIZE */
                if (TASK_SIZE - len < addr)
                        return -EINVAL;
 
        do_color_align = 0;
        if (filp || (flags & MAP_SHARED))
                do_color_align = 1;
+
+       /* requesting a specific address */
        if (addr) {
                if (do_color_align)
                        addr = COLOUR_ALIGN(addr, pgoff);
                else
                        addr = PAGE_ALIGN(addr);
-               vmm = find_vma(current->mm, addr);
+
+               vma = find_vma(mm, addr);
                if (TASK_SIZE - len >= addr &&
-                   (!vmm || addr + len <= vmm->vm_start))
+                  (!vma || addr + len <= vma->vm_start))
                        return addr;
        }
-       addr = current->mm->mmap_base;
-       if (do_color_align)
-               addr = COLOUR_ALIGN(addr, pgoff);
-       else
-               addr = PAGE_ALIGN(addr);
 
-       for (vmm = find_vma(current->mm, addr); ; vmm = vmm->vm_next) {
-               /* At this point:  (!vmm || addr < vmm->vm_end). */
-               if (TASK_SIZE - len < addr)
-                       return -ENOMEM;
-               if (!vmm || addr + len <= vmm->vm_start)
-                       return addr;
-               addr = vmm->vm_end;
+       if (dir == UP) {
+               addr = mm->mmap_base;
+                       if (do_color_align)
+                               addr = COLOUR_ALIGN(addr, pgoff);
+                       else
+                               addr = PAGE_ALIGN(addr);
+
+               for (vma = find_vma(current->mm, addr); ; vma = vma->vm_next) {
+                       /* At this point:  (!vma || addr < vma->vm_end). */
+                       if (TASK_SIZE - len < addr)
+                               return -ENOMEM;
+                       if (!vma || addr + len <= vma->vm_start)
+                               return addr;
+                       addr = vma->vm_end;
+                       if (do_color_align)
+                               addr = COLOUR_ALIGN(addr, pgoff);
+                }
+        } else {
+               /* check if free_area_cache is useful for us */
+               if (len <= mm->cached_hole_size) {
+                       mm->cached_hole_size = 0;
+                       mm->free_area_cache = mm->mmap_base;
+               }
+
+               /* either no address requested or can't fit in requested address hole */
+               addr = mm->free_area_cache;
+                       if (do_color_align) {
+                               unsigned long base =
+                                       COLOUR_ALIGN_DOWN(addr - len, pgoff);
+
+                       addr = base + len;
+                }
+
+               /* make sure it can fit in the remaining address space */
+               if (likely(addr > len)) {
+                       vma = find_vma(mm, addr - len);
+                       if (!vma || addr <= vma->vm_start) {
+                               /* remember the address as a hint for next time */
+                               return mm->free_area_cache = addr-len;
+                       }
+               }
+
+               if (unlikely(mm->mmap_base < len))
+                       goto bottomup;
+
+               addr = mm->mmap_base-len;
                if (do_color_align)
-                       addr = COLOUR_ALIGN(addr, pgoff);
+                       addr = COLOUR_ALIGN_DOWN(addr, pgoff);
+
+               do {
+                       /*
+                        * Lookup failure means no vma is above this address,
+                        * else if new region fits below vma->vm_start,
+                        * return with success:
+                        */
+                       vma = find_vma(mm, addr);
+                       if (likely(!vma || addr+len <= vma->vm_start)) {
+                               /* remember the address as a hint for next time */
+                               return mm->free_area_cache = addr;
+                       }
+
+                       /* remember the largest hole we saw so far */
+                       if (addr + mm->cached_hole_size < vma->vm_start)
+                               mm->cached_hole_size = vma->vm_start - addr;
+
+                       /* try just below the current vma->vm_start */
+                       addr = vma->vm_start-len;
+                       if (do_color_align)
+                               addr = COLOUR_ALIGN_DOWN(addr, pgoff);
+               } while (likely(len < vma->vm_start));
+
+bottomup:
+               /*
+                * A failed mmap() very likely causes application failure,
+                * so fall back to the bottom-up function here. This scenario
+                * can happen with large stack limits and large mmap()
+                * allocations.
+                */
+               mm->cached_hole_size = ~0UL;
+               mm->free_area_cache = TASK_UNMAPPED_BASE;
+               addr = arch_get_unmapped_area(filp, addr0, len, pgoff, flags);
+               /*
+                * Restore the topdown base:
+                */
+               mm->free_area_cache = mm->mmap_base;
+               mm->cached_hole_size = ~0UL;
+
+               return addr;
        }
 }
 
+unsigned long arch_get_unmapped_area(struct file *filp, unsigned long addr0,
+       unsigned long len, unsigned long pgoff, unsigned long flags)
+{
+       return arch_get_unmapped_area_foo(filp,
+                       addr0, len, pgoff, flags, UP);
+}
+
+/*
+ * There is no need to export this but sched.h declares the function as
+ * extern so making it static here results in an error.
+ */
+unsigned long arch_get_unmapped_area_topdown(struct file *filp,
+       unsigned long addr0, unsigned long len, unsigned long pgoff,
+       unsigned long flags)
+{
+       return arch_get_unmapped_area_foo(filp,
+                       addr0, len, pgoff, flags, DOWN);
+}
+
 void arch_pick_mmap_layout(struct mm_struct *mm)
 {
        unsigned long random_factor = 0UL;
                        random_factor &= 0xffffffful;
        }
 
-       mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
-       mm->get_unmapped_area = arch_get_unmapped_area;
-       mm->unmap_area = arch_unmap_area;
+       if (mmap_is_legacy()) {
+               mm->mmap_base = TASK_UNMAPPED_BASE + random_factor;
+               mm->get_unmapped_area = arch_get_unmapped_area;
+               mm->unmap_area = arch_unmap_area;
+       } else {
+               mm->mmap_base = mmap_base(random_factor);
+               mm->get_unmapped_area = arch_get_unmapped_area_topdown;
+               mm->unmap_area = arch_unmap_area_topdown;
+       }
 }
 
 static inline unsigned long brk_rnd(void)