Commit b7d9d2e3 authored by Ryan Roberts's avatar Ryan Roberts Committed by Catalin Marinas
Browse files

arm64: mm: __ptep_set_access_flags must hint correct TTL



It has been reported that since commit 752a0d1d ("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: default avatarAishwarya TCV <Aishwarya.TCV@arm.com>
Fixes: 752a0d1d ("arm64: mm: Provide level hint for flush_tlb_page()")
Signed-off-by: default avatarRyan Roberts <ryan.roberts@arm.com>
Signed-off-by: default avatarCatalin Marinas <catalin.marinas@arm.com>
parent 752a0d1d
Loading
Loading
Loading
Loading
+14 −5
Original line number Diff line number Diff line
@@ -1248,9 +1248,18 @@ static inline pmd_t pmd_modify(pmd_t pmd, pgprot_t newprot)
	return pte_pmd(pte_modify(pmd_pte(pmd), newprot));
}

extern int __ptep_set_access_flags(struct vm_area_struct *vma,
extern int __ptep_set_access_flags_anysz(struct vm_area_struct *vma,
					 unsigned long address, pte_t *ptep,
				 pte_t entry, int dirty);
					 pte_t entry, int dirty,
					 unsigned long pgsize);

static inline int __ptep_set_access_flags(struct vm_area_struct *vma,
					  unsigned long address, pte_t *ptep,
					  pte_t entry, int dirty)
{
	return __ptep_set_access_flags_anysz(vma, address, ptep, entry, dirty,
					     PAGE_SIZE);
}

#ifdef CONFIG_TRANSPARENT_HUGEPAGE
#define __HAVE_ARCH_PMDP_SET_ACCESS_FLAGS
@@ -1258,8 +1267,8 @@ static inline int pmdp_set_access_flags(struct vm_area_struct *vma,
					unsigned long address, pmd_t *pmdp,
					pmd_t entry, int dirty)
{
	return __ptep_set_access_flags(vma, address, (pte_t *)pmdp,
							pmd_pte(entry), dirty);
	return __ptep_set_access_flags_anysz(vma, address, (pte_t *)pmdp,
					     pmd_pte(entry), dirty, PMD_SIZE);
}
#endif

+25 −5
Original line number Diff line number Diff line
@@ -204,12 +204,13 @@ static void show_pte(unsigned long addr)
 *
 * Returns whether or not the PTE actually changed.
 */
int __ptep_set_access_flags(struct vm_area_struct *vma,
int __ptep_set_access_flags_anysz(struct vm_area_struct *vma,
				  unsigned long address, pte_t *ptep,
			    pte_t entry, int dirty)
				  pte_t entry, int dirty, unsigned long pgsize)
{
	pteval_t old_pteval, pteval;
	pte_t pte = __ptep_get(ptep);
	int level;

	if (pte_same(pte, entry))
		return 0;
@@ -238,8 +239,27 @@ int __ptep_set_access_flags(struct vm_area_struct *vma,
	 * may still cause page faults and be invalidated via
	 * flush_tlb_fix_spurious_fault().
	 */
	if (dirty)
		__flush_tlb_page(vma, address, TLBF_NOBROADCAST);
	if (dirty) {
		switch (pgsize) {
		case PAGE_SIZE:
			level = 3;
			break;
		case PMD_SIZE:
			level = 2;
			break;
#ifndef __PAGETABLE_PMD_FOLDED
		case PUD_SIZE:
			level = 1;
			break;
#endif
		default:
			level = TLBI_TTL_UNKNOWN;
			WARN_ON(1);
		}

		__flush_tlb_range(vma, address, address + pgsize, pgsize, level,
				  TLBF_NOWALKCACHE | TLBF_NOBROADCAST);
	}
	return 1;
}

+3 −3
Original line number Diff line number Diff line
@@ -427,11 +427,11 @@ int huge_ptep_set_access_flags(struct vm_area_struct *vma,
	pte_t orig_pte;

	VM_WARN_ON(!pte_present(pte));
	ncontig = num_contig_ptes(huge_page_size(hstate_vma(vma)), &pgsize);

	if (!pte_cont(pte))
		return __ptep_set_access_flags(vma, addr, ptep, pte, dirty);

	ncontig = num_contig_ptes(huge_page_size(hstate_vma(vma)), &pgsize);
		return __ptep_set_access_flags_anysz(vma, addr, ptep, pte,
						     dirty, pgsize);

	if (!__cont_access_flags_changed(ptep, pte, ncontig))
		return 0;