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

parisc: Protect huge page pte changes with spinlocks

PA-RISC doesn't have atomic instructions to modify page table entries, so it
takes spinlock in the TLB handler and modifies the page table entry
non-atomically. If you modify the page table entry without the spinlock, you
may race with TLB handler on another CPU and your modification may be lost.
Protect against that with usage of purge_tlb_start() and purge_tlb_end() which
handles the TLB spinlock.

Signed-off-by: Helge Deller <deller@gmx.de>
Cc: stable@vger.kernel.org # v4.4

+52 -28
+4 -16
arch/parisc/include/asm/hugetlb.h
··· 54 54 return pte_wrprotect(pte); 55 55 } 56 56 57 - static inline void huge_ptep_set_wrprotect(struct mm_struct *mm, 58 - unsigned long addr, pte_t *ptep) 59 - { 60 - pte_t old_pte = *ptep; 61 - set_huge_pte_at(mm, addr, ptep, pte_wrprotect(old_pte)); 62 - } 57 + void huge_ptep_set_wrprotect(struct mm_struct *mm, 58 + unsigned long addr, pte_t *ptep); 63 59 64 - static inline int huge_ptep_set_access_flags(struct vm_area_struct *vma, 60 + int huge_ptep_set_access_flags(struct vm_area_struct *vma, 65 61 unsigned long addr, pte_t *ptep, 66 - pte_t pte, int dirty) 67 - { 68 - int changed = !pte_same(*ptep, pte); 69 - if (changed) { 70 - set_huge_pte_at(vma->vm_mm, addr, ptep, pte); 71 - flush_tlb_page(vma, addr); 72 - } 73 - return changed; 74 - } 62 + pte_t pte, int dirty); 75 63 76 64 static inline pte_t huge_ptep_get(pte_t *ptep) 77 65 {
+48 -12
arch/parisc/mm/hugetlbpage.c
··· 105 105 addr |= _HUGE_PAGE_SIZE_ENCODING_DEFAULT; 106 106 107 107 for (i = 0; i < (1 << (HPAGE_SHIFT-REAL_HPAGE_SHIFT)); i++) { 108 - mtsp(mm->context, 1); 109 - pdtlb(addr); 110 - if (unlikely(split_tlb)) 111 - pitlb(addr); 108 + purge_tlb_entries(mm, addr); 112 109 addr += (1UL << REAL_HPAGE_SHIFT); 113 110 } 114 111 } 115 112 116 - void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, 113 + /* __set_huge_pte_at() must be called holding the pa_tlb_lock. */ 114 + static void __set_huge_pte_at(struct mm_struct *mm, unsigned long addr, 117 115 pte_t *ptep, pte_t entry) 118 116 { 119 117 unsigned long addr_start; ··· 121 123 addr_start = addr; 122 124 123 125 for (i = 0; i < (1 << HUGETLB_PAGE_ORDER); i++) { 124 - /* Directly write pte entry. We could call set_pte_at(mm, addr, ptep, entry) 125 - * instead, but then we get double locking on pa_tlb_lock. */ 126 - *ptep = entry; 126 + set_pte(ptep, entry); 127 127 ptep++; 128 - 129 - /* Drop the PAGE_SIZE/non-huge tlb entry */ 130 - purge_tlb_entries(mm, addr); 131 128 132 129 addr += PAGE_SIZE; 133 130 pte_val(entry) += PAGE_SIZE; ··· 131 138 purge_tlb_entries_huge(mm, addr_start); 132 139 } 133 140 141 + void set_huge_pte_at(struct mm_struct *mm, unsigned long addr, 142 + pte_t *ptep, pte_t entry) 143 + { 144 + unsigned long flags; 145 + 146 + purge_tlb_start(flags); 147 + __set_huge_pte_at(mm, addr, ptep, entry); 148 + purge_tlb_end(flags); 149 + } 150 + 134 151 135 152 pte_t huge_ptep_get_and_clear(struct mm_struct *mm, unsigned long addr, 136 153 pte_t *ptep) 137 154 { 155 + unsigned long flags; 138 156 pte_t entry; 139 157 158 + purge_tlb_start(flags); 140 159 entry = *ptep; 141 - set_huge_pte_at(mm, addr, ptep, __pte(0)); 160 + __set_huge_pte_at(mm, addr, ptep, __pte(0)); 161 + purge_tlb_end(flags); 142 162 143 163 return entry; 144 164 } 165 + 166 + 167 + void huge_ptep_set_wrprotect(struct mm_struct *mm, 168 + unsigned long addr, pte_t *ptep) 169 + { 170 + unsigned long flags; 171 + pte_t old_pte; 172 + 173 + purge_tlb_start(flags); 174 + old_pte = *ptep; 175 + __set_huge_pte_at(mm, addr, ptep, pte_wrprotect(old_pte)); 176 + purge_tlb_end(flags); 177 + } 178 + 179 + int huge_ptep_set_access_flags(struct vm_area_struct *vma, 180 + unsigned long addr, pte_t *ptep, 181 + pte_t pte, int dirty) 182 + { 183 + unsigned long flags; 184 + int changed; 185 + 186 + purge_tlb_start(flags); 187 + changed = !pte_same(*ptep, pte); 188 + if (changed) { 189 + __set_huge_pte_at(vma->vm_mm, addr, ptep, pte); 190 + } 191 + purge_tlb_end(flags); 192 + return changed; 193 + } 194 + 145 195 146 196 int pmd_huge(pmd_t pmd) 147 197 {