mmu_gather: move minimal range calculations into generic code
authorWill Deacon <will.deacon@arm.com>
Wed, 29 Oct 2014 10:03:09 +0000 (10:03 +0000)
committerWill Deacon <will.deacon@arm.com>
Mon, 17 Nov 2014 10:12:42 +0000 (10:12 +0000)
On architectures with hardware broadcasting of TLB invalidation messages
, it makes sense to reduce the range of the mmu_gather structure when
unmapping page ranges based on the dirty address information passed to
tlb_remove_tlb_entry.

arm64 already does this by directly manipulating the start/end fields
of the gather structure, but this confuses the generic code which
does not expect these fields to change and can end up calculating
invalid, negative ranges when forcing a flush in zap_pte_range.

This patch moves the minimal range calculation out of the arm64 code
and into the generic implementation, simplifying zap_pte_range in the
process (which no longer needs to care about start/end, since they will
point to the appropriate ranges already). With the range being tracked
by core code, the need_flush flag is dropped in favour of checking that
the end of the range has actually been set.

Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Russell King - ARM Linux <linux@arm.linux.org.uk>
Cc: Michal Simek <monstr@monstr.eu>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Will Deacon <will.deacon@arm.com>
arch/arm64/include/asm/tlb.h
arch/microblaze/include/asm/tlb.h
arch/powerpc/include/asm/pgalloc.h
arch/powerpc/include/asm/tlb.h
arch/powerpc/mm/hugetlbpage.c
include/asm-generic/tlb.h
mm/memory.c

index a82c0c5c8b521dc652fe9c4ca92de19f07018543..c028fe37456feade18c0c5737cf974899a8b6ee4 100644 (file)
 #ifndef __ASM_TLB_H
 #define __ASM_TLB_H
 
-#define  __tlb_remove_pmd_tlb_entry __tlb_remove_pmd_tlb_entry
-
-#include <asm-generic/tlb.h>
-
 #include <linux/pagemap.h>
 #include <linux/swap.h>
 
@@ -37,71 +33,22 @@ static inline void __tlb_remove_table(void *_table)
 #define tlb_remove_entry(tlb, entry)   tlb_remove_page(tlb, entry)
 #endif /* CONFIG_HAVE_RCU_TABLE_FREE */
 
-/*
- * There's three ways the TLB shootdown code is used:
- *  1. Unmapping a range of vmas.  See zap_page_range(), unmap_region().
- *     tlb->fullmm = 0, and tlb_start_vma/tlb_end_vma will be called.
- *  2. Unmapping all vmas.  See exit_mmap().
- *     tlb->fullmm = 1, and tlb_start_vma/tlb_end_vma will be called.
- *     Page tables will be freed.
- *  3. Unmapping argument pages.  See shift_arg_pages().
- *     tlb->fullmm = 0, but tlb_start_vma/tlb_end_vma will not be called.
- */
+#include <asm-generic/tlb.h>
+
 static inline void tlb_flush(struct mmu_gather *tlb)
 {
        if (tlb->fullmm) {
                flush_tlb_mm(tlb->mm);
-       } else if (tlb->end > 0) {
+       } else {
                struct vm_area_struct vma = { .vm_mm = tlb->mm, };
                flush_tlb_range(&vma, tlb->start, tlb->end);
-               tlb->start = TASK_SIZE;
-               tlb->end = 0;
-       }
-}
-
-static inline void tlb_add_flush(struct mmu_gather *tlb, unsigned long addr)
-{
-       if (!tlb->fullmm) {
-               tlb->start = min(tlb->start, addr);
-               tlb->end = max(tlb->end, addr + PAGE_SIZE);
-       }
-}
-
-/*
- * Memorize the range for the TLB flush.
- */
-static inline void __tlb_remove_tlb_entry(struct mmu_gather *tlb, pte_t *ptep,
-                                         unsigned long addr)
-{
-       tlb_add_flush(tlb, addr);
-}
-
-/*
- * In the case of tlb vma handling, we can optimise these away in the
- * case where we're doing a full MM flush.  When we're doing a munmap,
- * the vmas are adjusted to only cover the region to be torn down.
- */
-static inline void tlb_start_vma(struct mmu_gather *tlb,
-                                struct vm_area_struct *vma)
-{
-       if (!tlb->fullmm) {
-               tlb->start = TASK_SIZE;
-               tlb->end = 0;
        }
 }
 
-static inline void tlb_end_vma(struct mmu_gather *tlb,
-                              struct vm_area_struct *vma)
-{
-       if (!tlb->fullmm)
-               tlb_flush(tlb);
-}
-
 static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte,
                                  unsigned long addr)
 {
        pgtable_page_dtor(pte);
-       tlb_add_flush(tlb, addr);
        tlb_remove_entry(tlb, pte);
 }
 
@@ -109,7 +56,6 @@ static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t pte,
 static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmdp,
                                  unsigned long addr)
 {
-       tlb_add_flush(tlb, addr);
        tlb_remove_entry(tlb, virt_to_page(pmdp));
 }
 #endif
@@ -118,15 +64,8 @@ static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmdp,
 static inline void __pud_free_tlb(struct mmu_gather *tlb, pud_t *pudp,
                                  unsigned long addr)
 {
-       tlb_add_flush(tlb, addr);
        tlb_remove_entry(tlb, virt_to_page(pudp));
 }
 #endif
 
-static inline void __tlb_remove_pmd_tlb_entry(struct mmu_gather *tlb, pmd_t *pmdp,
-                                               unsigned long address)
-{
-       tlb_add_flush(tlb, address);
-}
-
 #endif
index 8aa97817cc8c6aabecc6e313a5db7d72d1aa9e99..99b6ded54849e2327e7593592c14aa73adee1429 100644 (file)
@@ -14,7 +14,6 @@
 #define tlb_flush(tlb) flush_tlb_mm((tlb)->mm)
 
 #include <linux/pagemap.h>
-#include <asm-generic/tlb.h>
 
 #ifdef CONFIG_MMU
 #define tlb_start_vma(tlb, vma)                do { } while (0)
@@ -22,4 +21,6 @@
 #define __tlb_remove_tlb_entry(tlb, pte, address) do { } while (0)
 #endif
 
+#include <asm-generic/tlb.h>
+
 #endif /* _ASM_MICROBLAZE_TLB_H */
index e9a9f60e596df4c4ef900965498e9f99565915ad..fc3ee06eab876ec12b20150aeffafe4e045260ae 100644 (file)
@@ -3,7 +3,6 @@
 #ifdef __KERNEL__
 
 #include <linux/mm.h>
-#include <asm-generic/tlb.h>
 
 #ifdef CONFIG_PPC_BOOK3E
 extern void tlb_flush_pgtable(struct mmu_gather *tlb, unsigned long address);
@@ -14,6 +13,8 @@ static inline void tlb_flush_pgtable(struct mmu_gather *tlb,
 }
 #endif /* !CONFIG_PPC_BOOK3E */
 
+extern void tlb_remove_table(struct mmu_gather *tlb, void *table);
+
 #ifdef CONFIG_PPC64
 #include <asm/pgalloc-64.h>
 #else
index e2b428b0f7babd3626e880be36b45a682a95b1ab..20733fa518ae135995abfeb161b227d9a105adce 100644 (file)
@@ -27,6 +27,7 @@
 
 #define tlb_start_vma(tlb, vma)        do { } while (0)
 #define tlb_end_vma(tlb, vma)  do { } while (0)
+#define __tlb_remove_tlb_entry __tlb_remove_tlb_entry
 
 extern void tlb_flush(struct mmu_gather *tlb);
 
index 7e70ae968e5f9df04cdf372e06ebea433be34d5b..6a4a5fcb9730877e0f089add23c6c522a41cc72c 100644 (file)
@@ -517,8 +517,6 @@ static void free_hugepd_range(struct mmu_gather *tlb, hugepd_t *hpdp, int pdshif
        for (i = 0; i < num_hugepd; i++, hpdp++)
                hpdp->pd = 0;
 
-       tlb->need_flush = 1;
-
 #ifdef CONFIG_PPC_FSL_BOOK3E
        hugepd_free(tlb, hugepte);
 #else
index 5672d7ea1fa066175b33c8f4c19457fcdc0614ed..08848050922e613f28a1d8f7e4b3ad4c34b463be 100644 (file)
@@ -96,10 +96,9 @@ struct mmu_gather {
 #endif
        unsigned long           start;
        unsigned long           end;
-       unsigned int            need_flush : 1, /* Did free PTEs */
        /* we are in the middle of an operation to clear
         * a full mm and can make some optimizations */
-                               fullmm : 1,
+       unsigned int            fullmm : 1,
        /* we have performed an operation which
         * requires a complete flush of the tlb */
                                need_flush_all : 1;
@@ -128,16 +127,54 @@ static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page)
                tlb_flush_mmu(tlb);
 }
 
+static inline void __tlb_adjust_range(struct mmu_gather *tlb,
+                                     unsigned long address)
+{
+       tlb->start = min(tlb->start, address);
+       tlb->end = max(tlb->end, address + PAGE_SIZE);
+}
+
+static inline void __tlb_reset_range(struct mmu_gather *tlb)
+{
+       tlb->start = TASK_SIZE;
+       tlb->end = 0;
+}
+
+/*
+ * In the case of tlb vma handling, we can optimise these away in the
+ * case where we're doing a full MM flush.  When we're doing a munmap,
+ * the vmas are adjusted to only cover the region to be torn down.
+ */
+#ifndef tlb_start_vma
+#define tlb_start_vma(tlb, vma) do { } while (0)
+#endif
+
+#define __tlb_end_vma(tlb, vma)                                        \
+       do {                                                    \
+               if (!tlb->fullmm && tlb->end) {                 \
+                       tlb_flush(tlb);                         \
+                       __tlb_reset_range(tlb);                 \
+               }                                               \
+       } while (0)
+
+#ifndef tlb_end_vma
+#define tlb_end_vma    __tlb_end_vma
+#endif
+
+#ifndef __tlb_remove_tlb_entry
+#define __tlb_remove_tlb_entry(tlb, ptep, address) do { } while (0)
+#endif
+
 /**
  * tlb_remove_tlb_entry - remember a pte unmapping for later tlb invalidation.
  *
- * Record the fact that pte's were really umapped in ->need_flush, so we can
- * later optimise away the tlb invalidate.   This helps when userspace is
- * unmapping already-unmapped pages, which happens quite a lot.
+ * Record the fact that pte's were really unmapped by updating the range,
+ * so we can later optimise away the tlb invalidate.   This helps when
+ * userspace is unmapping already-unmapped pages, which happens quite a lot.
  */
 #define tlb_remove_tlb_entry(tlb, ptep, address)               \
        do {                                                    \
-               tlb->need_flush = 1;                            \
+               __tlb_adjust_range(tlb, address);               \
                __tlb_remove_tlb_entry(tlb, ptep, address);     \
        } while (0)
 
@@ -151,27 +188,27 @@ static inline void tlb_remove_page(struct mmu_gather *tlb, struct page *page)
 
 #define tlb_remove_pmd_tlb_entry(tlb, pmdp, address)           \
        do {                                                    \
-               tlb->need_flush = 1;                            \
+               __tlb_adjust_range(tlb, address);               \
                __tlb_remove_pmd_tlb_entry(tlb, pmdp, address); \
        } while (0)
 
 #define pte_free_tlb(tlb, ptep, address)                       \
        do {                                                    \
-               tlb->need_flush = 1;                            \
+               __tlb_adjust_range(tlb, address);               \
                __pte_free_tlb(tlb, ptep, address);             \
        } while (0)
 
 #ifndef __ARCH_HAS_4LEVEL_HACK
 #define pud_free_tlb(tlb, pudp, address)                       \
        do {                                                    \
-               tlb->need_flush = 1;                            \
+               __tlb_adjust_range(tlb, address);               \
                __pud_free_tlb(tlb, pudp, address);             \
        } while (0)
 #endif
 
 #define pmd_free_tlb(tlb, pmdp, address)                       \
        do {                                                    \
-               tlb->need_flush = 1;                            \
+               __tlb_adjust_range(tlb, address);               \
                __pmd_free_tlb(tlb, pmdp, address);             \
        } while (0)
 
index 1cc6bfbd872ee17122b6e85859662696f595a363..c71edae9ba4473b44c83ba6f5b779601ace6c792 100644 (file)
@@ -220,9 +220,6 @@ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long
        /* Is it from 0 to ~0? */
        tlb->fullmm     = !(start | (end+1));
        tlb->need_flush_all = 0;
-       tlb->start      = start;
-       tlb->end        = end;
-       tlb->need_flush = 0;
        tlb->local.next = NULL;
        tlb->local.nr   = 0;
        tlb->local.max  = ARRAY_SIZE(tlb->__pages);
@@ -232,15 +229,20 @@ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, unsigned long
 #ifdef CONFIG_HAVE_RCU_TABLE_FREE
        tlb->batch = NULL;
 #endif
+
+       __tlb_reset_range(tlb);
 }
 
 static void tlb_flush_mmu_tlbonly(struct mmu_gather *tlb)
 {
-       tlb->need_flush = 0;
+       if (!tlb->end)
+               return;
+
        tlb_flush(tlb);
 #ifdef CONFIG_HAVE_RCU_TABLE_FREE
        tlb_table_flush(tlb);
 #endif
+       __tlb_reset_range(tlb);
 }
 
 static void tlb_flush_mmu_free(struct mmu_gather *tlb)
@@ -256,8 +258,6 @@ static void tlb_flush_mmu_free(struct mmu_gather *tlb)
 
 void tlb_flush_mmu(struct mmu_gather *tlb)
 {
-       if (!tlb->need_flush)
-               return;
        tlb_flush_mmu_tlbonly(tlb);
        tlb_flush_mmu_free(tlb);
 }
@@ -292,7 +292,7 @@ int __tlb_remove_page(struct mmu_gather *tlb, struct page *page)
 {
        struct mmu_gather_batch *batch;
 
-       VM_BUG_ON(!tlb->need_flush);
+       VM_BUG_ON(!tlb->end);
 
        batch = tlb->active;
        batch->pages[batch->nr++] = page;
@@ -359,8 +359,6 @@ void tlb_remove_table(struct mmu_gather *tlb, void *table)
 {
        struct mmu_table_batch **batch = &tlb->batch;
 
-       tlb->need_flush = 1;
-
        /*
         * When there's less then two users of this mm there cannot be a
         * concurrent page-table walk.
@@ -1185,20 +1183,8 @@ again:
        arch_leave_lazy_mmu_mode();
 
        /* Do the actual TLB flush before dropping ptl */
-       if (force_flush) {
-               unsigned long old_end;
-
-               /*
-                * Flush the TLB just for the previous segment,
-                * then update the range to be the remaining
-                * TLB range.
-                */
-               old_end = tlb->end;
-               tlb->end = addr;
+       if (force_flush)
                tlb_flush_mmu_tlbonly(tlb);
-               tlb->start = addr;
-               tlb->end = old_end;
-       }
        pte_unmap_unlock(start_pte, ptl);
 
        /*