Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

powerpc/mm/radix: Flush page walk cache when freeing page table

Even though a tlb_flush() does a flush with invalidate all cache,
we can end up doing an RCU page table free before calling tlb_flush().
That means we can have page walk cache entries even after we free the
page table pages. This can result in us doing wrong page table walk.

Avoid this by doing pwc flush on every page table free. We can't batch
the pwc flush, because the rcu call back function where we free the
page table pages doesn't have information of the mmu gather. Thus we
have to do a pwc on every page table page freed.

Note: I also removed the dummy tlb_flush_pgtable call functions for
hash 32.

Fixes: 1a472c9dba6b ("powerpc/mm/radix: Add tlbflush routines")
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>

authored by

Aneesh Kumar K.V and committed by
Michael Ellerman
a145abf1 36194812

+73 -7
-1
arch/powerpc/include/asm/book3s/32/pgalloc.h
··· 102 102 static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t table, 103 103 unsigned long address) 104 104 { 105 - tlb_flush_pgtable(tlb, address); 106 105 pgtable_page_dtor(table); 107 106 pgtable_free_tlb(tlb, page_address(table), 0); 108 107 }
+15 -1
arch/powerpc/include/asm/book3s/64/pgalloc.h
··· 110 110 static inline void __pud_free_tlb(struct mmu_gather *tlb, pud_t *pud, 111 111 unsigned long address) 112 112 { 113 + /* 114 + * By now all the pud entries should be none entries. So go 115 + * ahead and flush the page walk cache 116 + */ 117 + flush_tlb_pgtable(tlb, address); 113 118 pgtable_free_tlb(tlb, pud, PUD_INDEX_SIZE); 114 119 } 115 120 ··· 132 127 static inline void __pmd_free_tlb(struct mmu_gather *tlb, pmd_t *pmd, 133 128 unsigned long address) 134 129 { 130 + /* 131 + * By now all the pud entries should be none entries. So go 132 + * ahead and flush the page walk cache 133 + */ 134 + flush_tlb_pgtable(tlb, address); 135 135 return pgtable_free_tlb(tlb, pmd, PMD_CACHE_INDEX); 136 136 } 137 137 ··· 208 198 static inline void __pte_free_tlb(struct mmu_gather *tlb, pgtable_t table, 209 199 unsigned long address) 210 200 { 211 - tlb_flush_pgtable(tlb, address); 201 + /* 202 + * By now all the pud entries should be none entries. So go 203 + * ahead and flush the page walk cache 204 + */ 205 + flush_tlb_pgtable(tlb, address); 212 206 pgtable_free_tlb(tlb, table, 0); 213 207 } 214 208
+3
arch/powerpc/include/asm/book3s/64/tlbflush-radix.h
··· 18 18 extern void radix__local_flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr); 19 19 extern void radix___local_flush_tlb_page(struct mm_struct *mm, unsigned long vmaddr, 20 20 unsigned long ap, int nid); 21 + extern void radix__local_flush_tlb_pwc(struct mmu_gather *tlb, unsigned long addr); 21 22 extern void radix__tlb_flush(struct mmu_gather *tlb); 22 23 #ifdef CONFIG_SMP 23 24 extern void radix__flush_tlb_mm(struct mm_struct *mm); 24 25 extern void radix__flush_tlb_page(struct vm_area_struct *vma, unsigned long vmaddr); 25 26 extern void radix___flush_tlb_page(struct mm_struct *mm, unsigned long vmaddr, 26 27 unsigned long ap, int nid); 28 + extern void radix__flush_tlb_pwc(struct mmu_gather *tlb, unsigned long addr); 27 29 #else 28 30 #define radix__flush_tlb_mm(mm) radix__local_flush_tlb_mm(mm) 29 31 #define radix__flush_tlb_page(vma,addr) radix__local_flush_tlb_page(vma,addr) 30 32 #define radix___flush_tlb_page(mm,addr,p,i) radix___local_flush_tlb_page(mm,addr,p,i) 33 + #define radix__flush_tlb_pwc(tlb, addr) radix__local_flush_tlb_pwc(tlb, addr) 31 34 #endif 32 35 33 36 #endif
+14
arch/powerpc/include/asm/book3s/64/tlbflush.h
··· 72 72 #define flush_tlb_mm(mm) local_flush_tlb_mm(mm) 73 73 #define flush_tlb_page(vma, addr) local_flush_tlb_page(vma, addr) 74 74 #endif /* CONFIG_SMP */ 75 + /* 76 + * flush the page walk cache for the address 77 + */ 78 + static inline void flush_tlb_pgtable(struct mmu_gather *tlb, unsigned long address) 79 + { 80 + /* 81 + * Flush the page table walk cache on freeing a page table. We already 82 + * have marked the upper/higher level page table entry none by now. 83 + * So it is safe to flush PWC here. 84 + */ 85 + if (!radix_enabled()) 86 + return; 75 87 88 + radix__flush_tlb_pwc(tlb, address); 89 + } 76 90 #endif /* _ASM_POWERPC_BOOK3S_64_TLBFLUSH_H */
-5
arch/powerpc/include/asm/book3s/pgalloc.h
··· 4 4 #include <linux/mm.h> 5 5 6 6 extern void tlb_remove_table(struct mmu_gather *tlb, void *table); 7 - static inline void tlb_flush_pgtable(struct mmu_gather *tlb, 8 - unsigned long address) 9 - { 10 - 11 - } 12 7 13 8 #ifdef CONFIG_PPC64 14 9 #include <asm/book3s/64/pgalloc.h>
+41
arch/powerpc/mm/tlb-radix.c
··· 128 128 } 129 129 EXPORT_SYMBOL(radix__local_flush_tlb_mm); 130 130 131 + void radix__local_flush_tlb_pwc(struct mmu_gather *tlb, unsigned long addr) 132 + { 133 + unsigned long pid; 134 + struct mm_struct *mm = tlb->mm; 135 + 136 + preempt_disable(); 137 + 138 + pid = mm->context.id; 139 + if (pid != MMU_NO_CONTEXT) 140 + _tlbiel_pid(pid, RIC_FLUSH_PWC); 141 + 142 + preempt_enable(); 143 + } 144 + EXPORT_SYMBOL(radix__local_flush_tlb_pwc); 145 + 131 146 void radix___local_flush_tlb_page(struct mm_struct *mm, unsigned long vmaddr, 132 147 unsigned long ap, int nid) 133 148 { ··· 197 182 preempt_enable(); 198 183 } 199 184 EXPORT_SYMBOL(radix__flush_tlb_mm); 185 + 186 + void radix__flush_tlb_pwc(struct mmu_gather *tlb, unsigned long addr) 187 + { 188 + unsigned long pid; 189 + struct mm_struct *mm = tlb->mm; 190 + 191 + preempt_disable(); 192 + 193 + pid = mm->context.id; 194 + if (unlikely(pid == MMU_NO_CONTEXT)) 195 + goto no_context; 196 + 197 + if (!mm_is_core_local(mm)) { 198 + int lock_tlbie = !mmu_has_feature(MMU_FTR_LOCKLESS_TLBIE); 199 + 200 + if (lock_tlbie) 201 + raw_spin_lock(&native_tlbie_lock); 202 + _tlbie_pid(pid, RIC_FLUSH_PWC); 203 + if (lock_tlbie) 204 + raw_spin_unlock(&native_tlbie_lock); 205 + } else 206 + _tlbiel_pid(pid, RIC_FLUSH_PWC); 207 + no_context: 208 + preempt_enable(); 209 + } 210 + EXPORT_SYMBOL(radix__flush_tlb_pwc); 200 211 201 212 void radix___flush_tlb_page(struct mm_struct *mm, unsigned long vmaddr, 202 213 unsigned long ap, int nid)