diff options
author | Dave Hansen <dave@sr71.net> | 2013-04-12 19:23:54 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2013-04-12 19:56:47 -0400 |
commit | 1de14c3c5cbc9bb17e9dcc648cda51c0c85d54b9 (patch) | |
tree | ab6010ad78195651c39a8ca3829ba1b95451762f | |
parent | bf81710c4b6e2df2cc047f7c8e1f342511904b74 (diff) |
x86-32: Fix possible incomplete TLB invalidate with PAE pagetables
This patch attempts to fix:
https://bugzilla.kernel.org/show_bug.cgi?id=56461
The symptom is a crash and messages like this:
chrome: Corrupted page table at address 34a03000
*pdpt = 0000000000000000 *pde = 0000000000000000
Bad pagetable: 000f [#1] PREEMPT SMP
Ingo guesses this got introduced by commit 611ae8e3f520 ("x86/tlb:
enable tlb flush range support for x86") since that code started to free
unused pagetables.
On x86-32 PAE kernels, that new code has the potential to free an entire
PMD page and will clear one of the four page-directory-pointer-table
(aka pgd_t entries).
The hardware aggressively "caches" these top-level entries and invlpg
does not actually affect the CPU's copy. If we clear one we *HAVE* to
do a full TLB flush, otherwise we might continue using a freed pmd page.
(note, we do this properly on the population side in pud_populate()).
This patch tracks whenever we clear one of these entries in the 'struct
mmu_gather', and ensures that we follow up with a full tlb flush.
BTW, I disassembled and checked that:
if (tlb->fullmm == 0)
and
if (!tlb->fullmm && !tlb->need_flush_all)
generate essentially the same code, so there should be zero impact there
to the !PAE case.
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Cc: Peter Anvin <hpa@zytor.com>
Cc: Ingo Molnar <mingo@kernel.org>
Cc: Artem S Tashkinov <t.artem@mailcity.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | arch/x86/include/asm/tlb.h | 2 | ||||
-rw-r--r-- | arch/x86/mm/pgtable.c | 7 | ||||
-rw-r--r-- | include/asm-generic/tlb.h | 7 | ||||
-rw-r--r-- | mm/memory.c | 1 |
4 files changed, 15 insertions, 2 deletions
diff --git a/arch/x86/include/asm/tlb.h b/arch/x86/include/asm/tlb.h index 4fef20773b8f..c7797307fc2b 100644 --- a/arch/x86/include/asm/tlb.h +++ b/arch/x86/include/asm/tlb.h | |||
@@ -7,7 +7,7 @@ | |||
7 | 7 | ||
8 | #define tlb_flush(tlb) \ | 8 | #define tlb_flush(tlb) \ |
9 | { \ | 9 | { \ |
10 | if (tlb->fullmm == 0) \ | 10 | if (!tlb->fullmm && !tlb->need_flush_all) \ |
11 | flush_tlb_mm_range(tlb->mm, tlb->start, tlb->end, 0UL); \ | 11 | flush_tlb_mm_range(tlb->mm, tlb->start, tlb->end, 0UL); \ |
12 | else \ | 12 | else \ |
13 | flush_tlb_mm_range(tlb->mm, 0UL, TLB_FLUSH_ALL, 0UL); \ | 13 | flush_tlb_mm_range(tlb->mm, 0UL, TLB_FLUSH_ALL, 0UL); \ |
diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c index 193350b51f90..17fda6a8b3c2 100644 --- a/arch/x86/mm/pgtable.c +++ b/arch/x86/mm/pgtable.c | |||
@@ -58,6 +58,13 @@ void ___pte_free_tlb(struct mmu_gather *tlb, struct page *pte) | |||
58 | void ___pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd) | 58 | void ___pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd) |
59 | { | 59 | { |
60 | paravirt_release_pmd(__pa(pmd) >> PAGE_SHIFT); | 60 | paravirt_release_pmd(__pa(pmd) >> PAGE_SHIFT); |
61 | /* | ||
62 | * NOTE! For PAE, any changes to the top page-directory-pointer-table | ||
63 | * entries need a full cr3 reload to flush. | ||
64 | */ | ||
65 | #ifdef CONFIG_X86_PAE | ||
66 | tlb->need_flush_all = 1; | ||
67 | #endif | ||
61 | tlb_remove_page(tlb, virt_to_page(pmd)); | 68 | tlb_remove_page(tlb, virt_to_page(pmd)); |
62 | } | 69 | } |
63 | 70 | ||
diff --git a/include/asm-generic/tlb.h b/include/asm-generic/tlb.h index 25f01d0bc149..b1b1fa6ffffe 100644 --- a/include/asm-generic/tlb.h +++ b/include/asm-generic/tlb.h | |||
@@ -99,7 +99,12 @@ struct mmu_gather { | |||
99 | unsigned int need_flush : 1, /* Did free PTEs */ | 99 | unsigned int need_flush : 1, /* Did free PTEs */ |
100 | fast_mode : 1; /* No batching */ | 100 | fast_mode : 1; /* No batching */ |
101 | 101 | ||
102 | unsigned int fullmm; | 102 | /* we are in the middle of an operation to clear |
103 | * a full mm and can make some optimizations */ | ||
104 | unsigned int fullmm : 1, | ||
105 | /* we have performed an operation which | ||
106 | * requires a complete flush of the tlb */ | ||
107 | need_flush_all : 1; | ||
103 | 108 | ||
104 | struct mmu_gather_batch *active; | 109 | struct mmu_gather_batch *active; |
105 | struct mmu_gather_batch local; | 110 | struct mmu_gather_batch local; |
diff --git a/mm/memory.c b/mm/memory.c index 494526ae024a..13cbc420fead 100644 --- a/mm/memory.c +++ b/mm/memory.c | |||
@@ -216,6 +216,7 @@ void tlb_gather_mmu(struct mmu_gather *tlb, struct mm_struct *mm, bool fullmm) | |||
216 | tlb->mm = mm; | 216 | tlb->mm = mm; |
217 | 217 | ||
218 | tlb->fullmm = fullmm; | 218 | tlb->fullmm = fullmm; |
219 | tlb->need_flush_all = 0; | ||
219 | tlb->start = -1UL; | 220 | tlb->start = -1UL; |
220 | tlb->end = 0; | 221 | tlb->end = 0; |
221 | tlb->need_flush = 0; | 222 | tlb->need_flush = 0; |