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

Configure Feed

Select the types of activity you want to include in your feed.

arm64: mm: __ptep_set_access_flags must hint correct TTL

It has been reported that since commit 752a0d1d483e9 ("arm64: mm:
Provide level hint for flush_tlb_page()"), the arm64
check_hugetlb_options selftest has been locking up while running "Check
child hugetlb memory with private mapping, sync error mode and mmap
memory".

This is due to hugetlb (and THP) helpers casting their PMD/PUD entries
to PTE and calling __ptep_set_access_flags(), which issues a
__flush_tlb_page(). Now that this is hinted for level 3, in this case,
the TLB entry does not get evicted and we end up in a spurious fault
loop.

Fix this by creating a __ptep_set_access_flags_anysz() function which
takes the pgsize of the entry. It can then add the appropriate hint. The
"_anysz" approach is the established pattern for problems of this class.

Reported-by: Aishwarya TCV <Aishwarya.TCV@arm.com>
Fixes: 752a0d1d483e ("arm64: mm: Provide level hint for flush_tlb_page()")
Signed-off-by: Ryan Roberts <ryan.roberts@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>

authored by

Ryan Roberts and committed by
Catalin Marinas
b7d9d2e3 752a0d1d

+42 -13
+14 -5
arch/arm64/include/asm/pgtable.h
··· 1248 1248 return pte_pmd(pte_modify(pmd_pte(pmd), newprot)); 1249 1249 } 1250 1250 1251 - extern int __ptep_set_access_flags(struct vm_area_struct *vma, 1252 - unsigned long address, pte_t *ptep, 1253 - pte_t entry, int dirty); 1251 + extern int __ptep_set_access_flags_anysz(struct vm_area_struct *vma, 1252 + unsigned long address, pte_t *ptep, 1253 + pte_t entry, int dirty, 1254 + unsigned long pgsize); 1255 + 1256 + static inline int __ptep_set_access_flags(struct vm_area_struct *vma, 1257 + unsigned long address, pte_t *ptep, 1258 + pte_t entry, int dirty) 1259 + { 1260 + return __ptep_set_access_flags_anysz(vma, address, ptep, entry, dirty, 1261 + PAGE_SIZE); 1262 + } 1254 1263 1255 1264 #ifdef CONFIG_TRANSPARENT_HUGEPAGE 1256 1265 #define __HAVE_ARCH_PMDP_SET_ACCESS_FLAGS ··· 1267 1258 unsigned long address, pmd_t *pmdp, 1268 1259 pmd_t entry, int dirty) 1269 1260 { 1270 - return __ptep_set_access_flags(vma, address, (pte_t *)pmdp, 1271 - pmd_pte(entry), dirty); 1261 + return __ptep_set_access_flags_anysz(vma, address, (pte_t *)pmdp, 1262 + pmd_pte(entry), dirty, PMD_SIZE); 1272 1263 } 1273 1264 #endif 1274 1265
+25 -5
arch/arm64/mm/fault.c
··· 204 204 * 205 205 * Returns whether or not the PTE actually changed. 206 206 */ 207 - int __ptep_set_access_flags(struct vm_area_struct *vma, 208 - unsigned long address, pte_t *ptep, 209 - pte_t entry, int dirty) 207 + int __ptep_set_access_flags_anysz(struct vm_area_struct *vma, 208 + unsigned long address, pte_t *ptep, 209 + pte_t entry, int dirty, unsigned long pgsize) 210 210 { 211 211 pteval_t old_pteval, pteval; 212 212 pte_t pte = __ptep_get(ptep); 213 + int level; 213 214 214 215 if (pte_same(pte, entry)) 215 216 return 0; ··· 239 238 * may still cause page faults and be invalidated via 240 239 * flush_tlb_fix_spurious_fault(). 241 240 */ 242 - if (dirty) 243 - __flush_tlb_page(vma, address, TLBF_NOBROADCAST); 241 + if (dirty) { 242 + switch (pgsize) { 243 + case PAGE_SIZE: 244 + level = 3; 245 + break; 246 + case PMD_SIZE: 247 + level = 2; 248 + break; 249 + #ifndef __PAGETABLE_PMD_FOLDED 250 + case PUD_SIZE: 251 + level = 1; 252 + break; 253 + #endif 254 + default: 255 + level = TLBI_TTL_UNKNOWN; 256 + WARN_ON(1); 257 + } 258 + 259 + __flush_tlb_range(vma, address, address + pgsize, pgsize, level, 260 + TLBF_NOWALKCACHE | TLBF_NOBROADCAST); 261 + } 244 262 return 1; 245 263 } 246 264
+3 -3
arch/arm64/mm/hugetlbpage.c
··· 427 427 pte_t orig_pte; 428 428 429 429 VM_WARN_ON(!pte_present(pte)); 430 + ncontig = num_contig_ptes(huge_page_size(hstate_vma(vma)), &pgsize); 430 431 431 432 if (!pte_cont(pte)) 432 - return __ptep_set_access_flags(vma, addr, ptep, pte, dirty); 433 - 434 - ncontig = num_contig_ptes(huge_page_size(hstate_vma(vma)), &pgsize); 433 + return __ptep_set_access_flags_anysz(vma, addr, ptep, pte, 434 + dirty, pgsize); 435 435 436 436 if (!__cont_access_flags_changed(ptep, pte, ncontig)) 437 437 return 0;