1049 lines · 29.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * This kernel test validates architecture page table helpers and * accessors and helps in verifying their continued compliance with * expected generic MM semantics. * * Copyright (C) 2019 ARM Ltd. * * Author: Anshuman Khandual <anshuman.khandual@arm.com> */#define pr_fmt(fmt) "debug_vm_pgtable: [%-25s]: " fmt, __func__ #include <linux/gfp.h>#include <linux/highmem.h>#include <linux/hugetlb.h>#include <linux/kernel.h>#include <linux/kconfig.h>#include <linux/mm.h>#include <linux/mman.h>#include <linux/mm_types.h>#include <linux/module.h>#include <linux/pfn_t.h>#include <linux/printk.h>#include <linux/pgtable.h>#include <linux/random.h>#include <linux/spinlock.h>#include <linux/swap.h>#include <linux/swapops.h>#include <linux/start_kernel.h>#include <linux/sched/mm.h>#include <asm/pgalloc.h>#include <asm/tlbflush.h> /* * Please refer Documentation/vm/arch_pgtable_helpers.rst for the semantics * expectations that are being validated here. All future changes in here * or the documentation need to be in sync. */ #define VMFLAGS	(VM_READ|VM_WRITE|VM_EXEC) /* * On s390 platform, the lower 4 bits are used to identify given page table * entry type. But these bits might affect the ability to clear entries with * pxx_clear() because of how dynamic page table folding works on s390. So * while loading up the entries do not change the lower 4 bits. It does not * have affect any other platform. */#define S390_MASK_BITS	4#define RANDOM_ORVALUE	GENMASK(BITS_PER_LONG - 1, S390_MASK_BITS)#define RANDOM_NZVALUE	GENMASK(7, 0) static void __init pte_basic_tests(unsigned long pfn, pgprot_t prot){	pte_t pte = pfn_pte(pfn, prot); 	pr_debug("Validating PTE basic\n");	WARN_ON(!pte_same(pte, pte));	WARN_ON(!pte_young(pte_mkyoung(pte_mkold(pte))));	WARN_ON(!pte_dirty(pte_mkdirty(pte_mkclean(pte))));	WARN_ON(!pte_write(pte_mkwrite(pte_wrprotect(pte))));	WARN_ON(pte_young(pte_mkold(pte_mkyoung(pte))));	WARN_ON(pte_dirty(pte_mkclean(pte_mkdirty(pte))));	WARN_ON(pte_write(pte_wrprotect(pte_mkwrite(pte))));} static void __init pte_advanced_tests(struct mm_struct *mm,				      struct vm_area_struct *vma, pte_t *ptep,				      unsigned long pfn, unsigned long vaddr,				      pgprot_t prot){	pte_t pte = pfn_pte(pfn, prot); 	pr_debug("Validating PTE advanced\n");	pte = pfn_pte(pfn, prot);	set_pte_at(mm, vaddr, ptep, pte);	ptep_set_wrprotect(mm, vaddr, ptep);	pte = ptep_get(ptep);	WARN_ON(pte_write(pte)); 	pte = pfn_pte(pfn, prot);	set_pte_at(mm, vaddr, ptep, pte);	ptep_get_and_clear(mm, vaddr, ptep);	pte = ptep_get(ptep);	WARN_ON(!pte_none(pte)); 	pte = pfn_pte(pfn, prot);	pte = pte_wrprotect(pte);	pte = pte_mkclean(pte);	set_pte_at(mm, vaddr, ptep, pte);	pte = pte_mkwrite(pte);	pte = pte_mkdirty(pte);	ptep_set_access_flags(vma, vaddr, ptep, pte, 1);	pte = ptep_get(ptep);	WARN_ON(!(pte_write(pte) && pte_dirty(pte))); 	pte = pfn_pte(pfn, prot);	set_pte_at(mm, vaddr, ptep, pte);	ptep_get_and_clear_full(mm, vaddr, ptep, 1);	pte = ptep_get(ptep);	WARN_ON(!pte_none(pte)); 	pte = pte_mkyoung(pte);	set_pte_at(mm, vaddr, ptep, pte);	ptep_test_and_clear_young(vma, vaddr, ptep);	pte = ptep_get(ptep);	WARN_ON(pte_young(pte));} static void __init pte_savedwrite_tests(unsigned long pfn, pgprot_t prot){	pte_t pte = pfn_pte(pfn, prot); 	pr_debug("Validating PTE saved write\n");	WARN_ON(!pte_savedwrite(pte_mk_savedwrite(pte_clear_savedwrite(pte))));	WARN_ON(pte_savedwrite(pte_clear_savedwrite(pte_mk_savedwrite(pte))));}#ifdef CONFIG_TRANSPARENT_HUGEPAGEstatic void __init pmd_basic_tests(unsigned long pfn, pgprot_t prot){	pmd_t pmd = pfn_pmd(pfn, prot); 	if (!has_transparent_hugepage())		return; 	pr_debug("Validating PMD basic\n");	WARN_ON(!pmd_same(pmd, pmd));	WARN_ON(!pmd_young(pmd_mkyoung(pmd_mkold(pmd))));	WARN_ON(!pmd_dirty(pmd_mkdirty(pmd_mkclean(pmd))));	WARN_ON(!pmd_write(pmd_mkwrite(pmd_wrprotect(pmd))));	WARN_ON(pmd_young(pmd_mkold(pmd_mkyoung(pmd))));	WARN_ON(pmd_dirty(pmd_mkclean(pmd_mkdirty(pmd))));	WARN_ON(pmd_write(pmd_wrprotect(pmd_mkwrite(pmd))));	/*	 * A huge page does not point to next level page table	 * entry. Hence this must qualify as pmd_bad().	 */	WARN_ON(!pmd_bad(pmd_mkhuge(pmd)));} static void __init pmd_advanced_tests(struct mm_struct *mm,				      struct vm_area_struct *vma, pmd_t *pmdp,				      unsigned long pfn, unsigned long vaddr,				      pgprot_t prot){	pmd_t pmd = pfn_pmd(pfn, prot); 	if (!has_transparent_hugepage())		return; 	pr_debug("Validating PMD advanced\n");	/* Align the address wrt HPAGE_PMD_SIZE */	vaddr = (vaddr & HPAGE_PMD_MASK) + HPAGE_PMD_SIZE; 	pmd = pfn_pmd(pfn, prot);	set_pmd_at(mm, vaddr, pmdp, pmd);	pmdp_set_wrprotect(mm, vaddr, pmdp);	pmd = READ_ONCE(*pmdp);	WARN_ON(pmd_write(pmd)); 	pmd = pfn_pmd(pfn, prot);	set_pmd_at(mm, vaddr, pmdp, pmd);	pmdp_huge_get_and_clear(mm, vaddr, pmdp);	pmd = READ_ONCE(*pmdp);	WARN_ON(!pmd_none(pmd)); 	pmd = pfn_pmd(pfn, prot);	pmd = pmd_wrprotect(pmd);	pmd = pmd_mkclean(pmd);	set_pmd_at(mm, vaddr, pmdp, pmd);	pmd = pmd_mkwrite(pmd);	pmd = pmd_mkdirty(pmd);	pmdp_set_access_flags(vma, vaddr, pmdp, pmd, 1);	pmd = READ_ONCE(*pmdp);	WARN_ON(!(pmd_write(pmd) && pmd_dirty(pmd))); 	pmd = pmd_mkhuge(pfn_pmd(pfn, prot));	set_pmd_at(mm, vaddr, pmdp, pmd);	pmdp_huge_get_and_clear_full(vma, vaddr, pmdp, 1);	pmd = READ_ONCE(*pmdp);	WARN_ON(!pmd_none(pmd)); 	pmd = pmd_mkyoung(pmd);	set_pmd_at(mm, vaddr, pmdp, pmd);	pmdp_test_and_clear_young(vma, vaddr, pmdp);	pmd = READ_ONCE(*pmdp);	WARN_ON(pmd_young(pmd));} static void __init pmd_leaf_tests(unsigned long pfn, pgprot_t prot){	pmd_t pmd = pfn_pmd(pfn, prot); 	pr_debug("Validating PMD leaf\n");	/*	 * PMD based THP is a leaf entry.	 */	pmd = pmd_mkhuge(pmd);	WARN_ON(!pmd_leaf(pmd));} static void __init pmd_huge_tests(pmd_t *pmdp, unsigned long pfn, pgprot_t prot){	pmd_t pmd; 	if (!IS_ENABLED(CONFIG_HAVE_ARCH_HUGE_VMAP))		return; 	pr_debug("Validating PMD huge\n");	/*	 * X86 defined pmd_set_huge() verifies that the given	 * PMD is not a populated non-leaf entry.	 */	WRITE_ONCE(*pmdp, __pmd(0));	WARN_ON(!pmd_set_huge(pmdp, __pfn_to_phys(pfn), prot));	WARN_ON(!pmd_clear_huge(pmdp));	pmd = READ_ONCE(*pmdp);	WARN_ON(!pmd_none(pmd));} static void __init pmd_savedwrite_tests(unsigned long pfn, pgprot_t prot){	pmd_t pmd = pfn_pmd(pfn, prot); 	pr_debug("Validating PMD saved write\n");	WARN_ON(!pmd_savedwrite(pmd_mk_savedwrite(pmd_clear_savedwrite(pmd))));	WARN_ON(pmd_savedwrite(pmd_clear_savedwrite(pmd_mk_savedwrite(pmd))));} #ifdef CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUDstatic void __init pud_basic_tests(unsigned long pfn, pgprot_t prot){	pud_t pud = pfn_pud(pfn, prot); 	if (!has_transparent_hugepage())		return; 	pr_debug("Validating PUD basic\n");	WARN_ON(!pud_same(pud, pud));	WARN_ON(!pud_young(pud_mkyoung(pud_mkold(pud))));	WARN_ON(!pud_write(pud_mkwrite(pud_wrprotect(pud))));	WARN_ON(pud_write(pud_wrprotect(pud_mkwrite(pud))));	WARN_ON(pud_young(pud_mkold(pud_mkyoung(pud)))); 	if (mm_pmd_folded(mm))		return; 	/*	 * A huge page does not point to next level page table	 * entry. Hence this must qualify as pud_bad().	 */	WARN_ON(!pud_bad(pud_mkhuge(pud)));} static void __init pud_advanced_tests(struct mm_struct *mm,				      struct vm_area_struct *vma, pud_t *pudp,				      unsigned long pfn, unsigned long vaddr,				      pgprot_t prot){	pud_t pud = pfn_pud(pfn, prot); 	if (!has_transparent_hugepage())		return; 	pr_debug("Validating PUD advanced\n");	/* Align the address wrt HPAGE_PUD_SIZE */	vaddr = (vaddr & HPAGE_PUD_MASK) + HPAGE_PUD_SIZE; 	set_pud_at(mm, vaddr, pudp, pud);	pudp_set_wrprotect(mm, vaddr, pudp);	pud = READ_ONCE(*pudp);	WARN_ON(pud_write(pud)); #ifndef __PAGETABLE_PMD_FOLDED	pud = pfn_pud(pfn, prot);	set_pud_at(mm, vaddr, pudp, pud);	pudp_huge_get_and_clear(mm, vaddr, pudp);	pud = READ_ONCE(*pudp);	WARN_ON(!pud_none(pud)); 	pud = pfn_pud(pfn, prot);	set_pud_at(mm, vaddr, pudp, pud);	pudp_huge_get_and_clear_full(mm, vaddr, pudp, 1);	pud = READ_ONCE(*pudp);	WARN_ON(!pud_none(pud));#endif /* __PAGETABLE_PMD_FOLDED */	pud = pfn_pud(pfn, prot);	pud = pud_wrprotect(pud);	pud = pud_mkclean(pud);	set_pud_at(mm, vaddr, pudp, pud);	pud = pud_mkwrite(pud);	pud = pud_mkdirty(pud);	pudp_set_access_flags(vma, vaddr, pudp, pud, 1);	pud = READ_ONCE(*pudp);	WARN_ON(!(pud_write(pud) && pud_dirty(pud))); 	pud = pud_mkyoung(pud);	set_pud_at(mm, vaddr, pudp, pud);	pudp_test_and_clear_young(vma, vaddr, pudp);	pud = READ_ONCE(*pudp);	WARN_ON(pud_young(pud));} static void __init pud_leaf_tests(unsigned long pfn, pgprot_t prot){	pud_t pud = pfn_pud(pfn, prot); 	pr_debug("Validating PUD leaf\n");	/*	 * PUD based THP is a leaf entry.	 */	pud = pud_mkhuge(pud);	WARN_ON(!pud_leaf(pud));} static void __init pud_huge_tests(pud_t *pudp, unsigned long pfn, pgprot_t prot){	pud_t pud; 	if (!IS_ENABLED(CONFIG_HAVE_ARCH_HUGE_VMAP))		return; 	pr_debug("Validating PUD huge\n");	/*	 * X86 defined pud_set_huge() verifies that the given	 * PUD is not a populated non-leaf entry.	 */	WRITE_ONCE(*pudp, __pud(0));	WARN_ON(!pud_set_huge(pudp, __pfn_to_phys(pfn), prot));	WARN_ON(!pud_clear_huge(pudp));	pud = READ_ONCE(*pudp);	WARN_ON(!pud_none(pud));}#else  /* !CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD */static void __init pud_basic_tests(unsigned long pfn, pgprot_t prot) { }static void __init pud_advanced_tests(struct mm_struct *mm,				      struct vm_area_struct *vma, pud_t *pudp,				      unsigned long pfn, unsigned long vaddr,				      pgprot_t prot){}static void __init pud_leaf_tests(unsigned long pfn, pgprot_t prot) { }static void __init pud_huge_tests(pud_t *pudp, unsigned long pfn, pgprot_t prot){}#endif /* CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD */#else  /* !CONFIG_TRANSPARENT_HUGEPAGE */static void __init pmd_basic_tests(unsigned long pfn, pgprot_t prot) { }static void __init pud_basic_tests(unsigned long pfn, pgprot_t prot) { }static void __init pmd_advanced_tests(struct mm_struct *mm,				      struct vm_area_struct *vma, pmd_t *pmdp,				      unsigned long pfn, unsigned long vaddr,				      pgprot_t prot){}static void __init pud_advanced_tests(struct mm_struct *mm,				      struct vm_area_struct *vma, pud_t *pudp,				      unsigned long pfn, unsigned long vaddr,				      pgprot_t prot){}static void __init pmd_leaf_tests(unsigned long pfn, pgprot_t prot) { }static void __init pud_leaf_tests(unsigned long pfn, pgprot_t prot) { }static void __init pmd_huge_tests(pmd_t *pmdp, unsigned long pfn, pgprot_t prot){}static void __init pud_huge_tests(pud_t *pudp, unsigned long pfn, pgprot_t prot){}static void __init pmd_savedwrite_tests(unsigned long pfn, pgprot_t prot) { }#endif /* CONFIG_TRANSPARENT_HUGEPAGE */ static void __init p4d_basic_tests(unsigned long pfn, pgprot_t prot){	p4d_t p4d; 	pr_debug("Validating P4D basic\n");	memset(&p4d, RANDOM_NZVALUE, sizeof(p4d_t));	WARN_ON(!p4d_same(p4d, p4d));} static void __init pgd_basic_tests(unsigned long pfn, pgprot_t prot){	pgd_t pgd; 	pr_debug("Validating PGD basic\n");	memset(&pgd, RANDOM_NZVALUE, sizeof(pgd_t));	WARN_ON(!pgd_same(pgd, pgd));} #ifndef __PAGETABLE_PUD_FOLDEDstatic void __init pud_clear_tests(struct mm_struct *mm, pud_t *pudp){	pud_t pud = READ_ONCE(*pudp); 	if (mm_pmd_folded(mm))		return; 	pr_debug("Validating PUD clear\n");	pud = __pud(pud_val(pud) | RANDOM_ORVALUE);	WRITE_ONCE(*pudp, pud);	pud_clear(pudp);	pud = READ_ONCE(*pudp);	WARN_ON(!pud_none(pud));} static void __init pud_populate_tests(struct mm_struct *mm, pud_t *pudp,				      pmd_t *pmdp){	pud_t pud; 	if (mm_pmd_folded(mm))		return; 	pr_debug("Validating PUD populate\n");	/*	 * This entry points to next level page table page.	 * Hence this must not qualify as pud_bad().	 */	pmd_clear(pmdp);	pud_clear(pudp);	pud_populate(mm, pudp, pmdp);	pud = READ_ONCE(*pudp);	WARN_ON(pud_bad(pud));}#else  /* !__PAGETABLE_PUD_FOLDED */static void __init pud_clear_tests(struct mm_struct *mm, pud_t *pudp) { }static void __init pud_populate_tests(struct mm_struct *mm, pud_t *pudp,				      pmd_t *pmdp){}#endif /* PAGETABLE_PUD_FOLDED */ #ifndef __PAGETABLE_P4D_FOLDEDstatic void __init p4d_clear_tests(struct mm_struct *mm, p4d_t *p4dp){	p4d_t p4d = READ_ONCE(*p4dp); 	if (mm_pud_folded(mm))		return; 	pr_debug("Validating P4D clear\n");	p4d = __p4d(p4d_val(p4d) | RANDOM_ORVALUE);	WRITE_ONCE(*p4dp, p4d);	p4d_clear(p4dp);	p4d = READ_ONCE(*p4dp);	WARN_ON(!p4d_none(p4d));} static void __init p4d_populate_tests(struct mm_struct *mm, p4d_t *p4dp,				      pud_t *pudp){	p4d_t p4d; 	if (mm_pud_folded(mm))		return; 	pr_debug("Validating P4D populate\n");	/*	 * This entry points to next level page table page.	 * Hence this must not qualify as p4d_bad().	 */	pud_clear(pudp);	p4d_clear(p4dp);	p4d_populate(mm, p4dp, pudp);	p4d = READ_ONCE(*p4dp);	WARN_ON(p4d_bad(p4d));} static void __init pgd_clear_tests(struct mm_struct *mm, pgd_t *pgdp){	pgd_t pgd = READ_ONCE(*pgdp); 	if (mm_p4d_folded(mm))		return; 	pr_debug("Validating PGD clear\n");	pgd = __pgd(pgd_val(pgd) | RANDOM_ORVALUE);	WRITE_ONCE(*pgdp, pgd);	pgd_clear(pgdp);	pgd = READ_ONCE(*pgdp);	WARN_ON(!pgd_none(pgd));} static void __init pgd_populate_tests(struct mm_struct *mm, pgd_t *pgdp,				      p4d_t *p4dp){	pgd_t pgd; 	if (mm_p4d_folded(mm))		return; 	pr_debug("Validating PGD populate\n");	/*	 * This entry points to next level page table page.	 * Hence this must not qualify as pgd_bad().	 */	p4d_clear(p4dp);	pgd_clear(pgdp);	pgd_populate(mm, pgdp, p4dp);

Lines 1–500 of 1049.