#include <asm-generic/tlb.h>
 
+/*
+ * get the tlbi levels in arm64.  Default value is 0 if more than one
+ * of cleared_* is set or neither is set.
+ * Arm64 doesn't support p4ds now.
+ */
+static inline int tlb_get_level(struct mmu_gather *tlb)
+{
+       if (tlb->cleared_ptes && !(tlb->cleared_pmds ||
+                                  tlb->cleared_puds ||
+                                  tlb->cleared_p4ds))
+               return 3;
+
+       if (tlb->cleared_pmds && !(tlb->cleared_ptes ||
+                                  tlb->cleared_puds ||
+                                  tlb->cleared_p4ds))
+               return 2;
+
+       if (tlb->cleared_puds && !(tlb->cleared_ptes ||
+                                  tlb->cleared_pmds ||
+                                  tlb->cleared_p4ds))
+               return 1;
+
+       return 0;
+}
+
 static inline void tlb_flush(struct mmu_gather *tlb)
 {
        struct vm_area_struct vma = TLB_FLUSH_VMA(tlb->mm, 0);
        bool last_level = !tlb->freed_tables;
        unsigned long stride = tlb_get_unmap_size(tlb);
+       int tlb_level = tlb_get_level(tlb);
 
        /*
         * If we're tearing down the address space then we only care about
                return;
        }
 
-       __flush_tlb_range(&vma, tlb->start, tlb->end, stride, last_level);
+       __flush_tlb_range(&vma, tlb->start, tlb->end, stride,
+                         last_level, tlb_level);
 }
 
 static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte,
 
 
 static inline void __flush_tlb_range(struct vm_area_struct *vma,
                                     unsigned long start, unsigned long end,
-                                    unsigned long stride, bool last_level)
+                                    unsigned long stride, bool last_level,
+                                    int tlb_level)
 {
        unsigned long asid = ASID(vma->vm_mm);
        unsigned long addr;
        dsb(ishst);
        for (addr = start; addr < end; addr += stride) {
                if (last_level) {
-                       __tlbi_level(vale1is, addr, 0);
-                       __tlbi_user_level(vale1is, addr, 0);
+                       __tlbi_level(vale1is, addr, tlb_level);
+                       __tlbi_user_level(vale1is, addr, tlb_level);
                } else {
-                       __tlbi_level(vae1is, addr, 0);
-                       __tlbi_user_level(vae1is, addr, 0);
+                       __tlbi_level(vae1is, addr, tlb_level);
+                       __tlbi_user_level(vae1is, addr, tlb_level);
                }
        }
        dsb(ish);
        /*
         * We cannot use leaf-only invalidation here, since we may be invalidating
         * table entries as part of collapsing hugepages or moving page tables.
+        * Set the tlb_level to 0 because we can not get enough information here.
         */
-       __flush_tlb_range(vma, start, end, PAGE_SIZE, false);
+       __flush_tlb_range(vma, start, end, PAGE_SIZE, false, 0);
 }
 
 static inline void flush_tlb_kernel_range(unsigned long start, unsigned long end)