// 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__
/* * Please refer Documentation/mm/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 RANDOM_NZVALUE GENMASK(7, 0)
/* * This test needs to be executed after the given page table entry * is created with pfn_pte() to make sure that vm_get_page_prot(idx) * does not have the dirty bit enabled from the beginning. This is * important for platforms like arm64 where (!PTE_RDONLY) indicate * dirty bit being set.
*/
WARN_ON(pte_dirty(pte_wrprotect(pte)));
/* * Architectures optimize set_pte_at by avoiding TLB flush. * This requires set_pte_at to be not used to update an * existing pte entry. Clear pte before we do set_pte_at * * flush_dcache_page() is called after set_pte_at() to clear * PG_arch_1 for the page on ARM64. The page flag isn't cleared * when it's released and page allocation check will fail when * the page is allocated again. For architectures other than ARM64, * the unexpected overhead of cache flushing is acceptable.
*/
page = (args->pte_pfn != ULONG_MAX) ? pfn_to_page(args->pte_pfn) : NULL; if (!page) return;
pr_debug("Validating PTE advanced\n"); if (WARN_ON(!args->ptep)) return;
/* * This test needs to be executed after the given page table entry * is created with pfn_pmd() to make sure that vm_get_page_prot(idx) * does not have the dirty bit enabled from the beginning. This is * important for platforms like arm64 where (!PTE_RDONLY) indicate * dirty bit being set.
*/
WARN_ON(pmd_dirty(pmd_wrprotect(pmd)));
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), args->vma)));
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, args->vma))));
WARN_ON(pmd_dirty(pmd_wrprotect(pmd_mkclean(pmd))));
WARN_ON(!pmd_dirty(pmd_wrprotect(pmd_mkdirty(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)));
}
/* * flush_dcache_page() is called after set_pmd_at() to clear * PG_arch_1 for the page on ARM64. The page flag isn't cleared * when it's released and page allocation check will fail when * the page is allocated again. For architectures other than ARM64, * the unexpected overhead of cache flushing is acceptable.
*/
pr_debug("Validating PMD advanced\n"); /* Align the address wrt HPAGE_PMD_SIZE */
vaddr &= HPAGE_PMD_MASK;
/* * This test needs to be executed after the given page table entry * is created with pfn_pud() to make sure that vm_get_page_prot(idx) * does not have the dirty bit enabled from the beginning. This is * important for platforms like arm64 where (!PTE_RDONLY) indicate * dirty bit being set.
*/
WARN_ON(pud_dirty(pud_wrprotect(pud)));
/* * flush_dcache_page() is called after set_pud_at() to clear * PG_arch_1 for the page on ARM64. The page flag isn't cleared * when it's released and page allocation check will fail when * the page is allocated again. For architectures other than ARM64, * the unexpected overhead of cache flushing is acceptable.
*/
pr_debug("Validating PUD advanced\n"); /* Align the address wrt HPAGE_PUD_SIZE */
vaddr &= HPAGE_PUD_MASK;
if (!arch_vmap_pmd_supported(args->page_prot) ||
args->fixed_alignment < PMD_SIZE) 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(*args->pmdp, __pmd(0));
WARN_ON(!pmd_set_huge(args->pmdp, __pfn_to_phys(args->fixed_pmd_pfn), args->page_prot));
WARN_ON(!pmd_clear_huge(args->pmdp));
pmd = pmdp_get(args->pmdp);
WARN_ON(!pmd_none(pmd));
}
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(args->pudp);
p4d_clear(args->p4dp);
p4d_populate(args->mm, args->p4dp, args->start_pudp);
p4d = p4dp_get(args->p4dp);
WARN_ON(p4d_bad(p4d));
}
/* * flush_dcache_page() is called after set_pte_at() to clear * PG_arch_1 for the page on ARM64. The page flag isn't cleared * when it's released and page allocation check will fail when * the page is allocated again. For architectures other than ARM64, * the unexpected overhead of cache flushing is acceptable.
*/
pr_debug("Validating PTE clear\n"); if (WARN_ON(!args->ptep)) return;
pr_debug("Validating PMD populate\n"); /* * This entry points to next level page table page. * Hence this must not qualify as pmd_bad().
*/
pmd_populate(args->mm, args->pmdp, args->start_ptep);
pmd = pmdp_get(args->pmdp);
WARN_ON(pmd_bad(pmd));
}
/* * swap_migration_tests() requires a dedicated page as it needs to * be locked before creating a migration entry from it. Locking the * page that actually maps kernel text ('start_kernel') can be real * problematic. Lets use the allocated page explicitly for this * purpose.
*/
page = (args->pte_pfn != ULONG_MAX) ? pfn_to_page(args->pte_pfn) : NULL; if (!page) return;
pr_debug("Validating swap migration\n");
/* * make_[readable|writable]_migration_entry() expects given page to * be locked, otherwise it stumbles upon a BUG_ON().
*/
__SetPageLocked(page);
swp = make_writable_migration_entry(page_to_pfn(page));
WARN_ON(!is_migration_entry(swp));
WARN_ON(!is_writable_migration_entry(swp));
pr_debug("Validating PMD based THP\n"); /* * pmd_trans_huge() and pmd_present() must return positive after * MMU invalidation with pmd_mkinvalid(). This behavior is an * optimization for transparent huge page. pmd_trans_huge() must * be true if pmd_page() returns a valid THP to avoid taking the * pmd_lock when others walk over non transhuge pmds (i.e. there * are no THP allocated). Especially when splitting a THP and * removing the present bit from the pmd, pmd_trans_huge() still * needs to return true. pmd_present() should be true whenever * pmd_trans_huge() returns true.
*/
pmd = pfn_pmd(args->fixed_pmd_pfn, args->page_prot);
WARN_ON(!pmd_trans_huge(pmd_mkhuge(pmd)));
if (order <= MAX_PAGE_ORDER)
page = alloc_pages(GFP_KERNEL, order);
return page;
}
/* * Check if a physical memory range described by <pstart, pend> contains * an area that is of size psize, and aligned to psize. * * Don't use address 0, an all-zeroes physical address might mask bugs, and * it's not used on x86.
*/ staticvoid __init phys_align_check(phys_addr_t pstart,
phys_addr_t pend, unsignedlong psize,
phys_addr_t *physp, unsignedlong *alignp)
{
phys_addr_t aligned_start, aligned_end;
/* * Initialize the fixed pfns. To do this, try to find a * valid physical range, preferably aligned to PUD_SIZE, * but settling for aligned to PMD_SIZE as a fallback. If * neither of those is found, use the physical address of * the start_kernel symbol. * * The memory doesn't need to be allocated, it just needs to exist * as usable memory. It won't be touched. * * The alignment is recorded, and can be checked to see if we * can run the tests that require an actual valid physical * address range on some architectures ({pmd,pud}_huge_test * on x86).
*/
for_each_mem_range(idx, &pstart, &pend) { /* First check for a PUD-aligned area */
phys_align_check(pstart, pend, PUD_SIZE, &phys,
&args->fixed_alignment);
/* If a PUD-aligned area is found, we're done */ if (args->fixed_alignment == PUD_SIZE) break;
/* * If no PMD-aligned area found yet, check for one, * but continue the loop to look for a PUD-aligned area.
*/ if (args->fixed_alignment < PMD_SIZE)
phys_align_check(pstart, pend, PMD_SIZE, &phys,
&args->fixed_alignment);
}
staticint __init init_args(struct pgtable_debug_args *args)
{ unsignedlong max_swap_offset; struct page *page = NULL; int ret = 0;
/* * Initialize the debugging data. * * vm_get_page_prot(VM_NONE) or vm_get_page_prot(VM_SHARED|VM_NONE) * will help create page table entries with PROT_NONE permission as * required for pxx_protnone_tests().
*/
memset(args, 0, sizeof(*args));
args->vaddr = get_random_vaddr();
args->page_prot = vm_get_page_prot(VM_ACCESS_FLAGS);
args->page_prot_none = vm_get_page_prot(VM_NONE);
args->is_contiguous_page = false;
args->pud_pfn = ULONG_MAX;
args->pmd_pfn = ULONG_MAX;
args->pte_pfn = ULONG_MAX;
args->fixed_pgd_pfn = ULONG_MAX;
args->fixed_p4d_pfn = ULONG_MAX;
args->fixed_pud_pfn = ULONG_MAX;
args->fixed_pmd_pfn = ULONG_MAX;
args->fixed_pte_pfn = ULONG_MAX;
/* Allocate mm and vma */
args->mm = mm_alloc(); if (!args->mm) {
pr_err("Failed to allocate mm struct\n");
ret = -ENOMEM; goto error;
}
args->vma = vm_area_alloc(args->mm); if (!args->vma) {
pr_err("Failed to allocate vma\n");
ret = -ENOMEM; goto error;
}
/* * Allocate page table entries. They will be modified in the tests. * Lets save the page table entries so that they can be released * when the tests are completed.
*/
args->pgdp = pgd_offset(args->mm, args->vaddr);
args->p4dp = p4d_alloc(args->mm, args->pgdp, args->vaddr); if (!args->p4dp) {
pr_err("Failed to allocate p4d entries\n");
ret = -ENOMEM; goto error;
}
args->start_p4dp = p4d_offset(args->pgdp, 0UL);
WARN_ON(!args->start_p4dp);
args->pudp = pud_alloc(args->mm, args->p4dp, args->vaddr); if (!args->pudp) {
pr_err("Failed to allocate pud entries\n");
ret = -ENOMEM; goto error;
}
args->start_pudp = pud_offset(args->p4dp, 0UL);
WARN_ON(!args->start_pudp);
args->pmdp = pmd_alloc(args->mm, args->pudp, args->vaddr); if (!args->pmdp) {
pr_err("Failed to allocate pmd entries\n");
ret = -ENOMEM; goto error;
}
args->start_pmdp = pmd_offset(args->pudp, 0UL);
WARN_ON(!args->start_pmdp);
if (pte_alloc(args->mm, args->pmdp)) {
pr_err("Failed to allocate pte entries\n");
ret = -ENOMEM; goto error;
}
args->start_ptep = pmd_pgtable(pmdp_get(args->pmdp));
WARN_ON(!args->start_ptep);
init_fixed_pfns(args);
/* See generic_max_swapfile_size(): probe the maximum offset */
max_swap_offset = swp_offset(pte_to_swp_entry(swp_entry_to_pte(swp_entry(0, ~0UL)))); /* Create a swp entry with all possible bits set */
args->swp_entry = swp_entry((1 << MAX_SWAPFILES_SHIFT) - 1, max_swap_offset);
/* * Allocate (huge) pages because some of the tests need to access * the data in the pages. The corresponding tests will be skipped * if we fail to allocate (huge) pages.
*/ if (IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE) &&
has_transparent_pud_hugepage()) {
page = debug_vm_pgtable_alloc_huge_page(args,
HPAGE_PUD_SHIFT - PAGE_SHIFT); if (page) {
args->pud_pfn = page_to_pfn(page);
args->pmd_pfn = args->pud_pfn;
args->pte_pfn = args->pud_pfn; return 0;
}
}
pr_info("Validating architecture page table helpers\n");
ret = init_args(&args); if (ret) return ret;
/* * Iterate over each possible vm_flags to make sure that all * the basic page table transformation validations just hold * true irrespective of the starting protection value for a * given page table entry. * * Protection based vm_flags combinations are always linear * and increasing i.e starting from VM_NONE and going up to * (VM_SHARED | READ | WRITE | EXEC).
*/ #define VM_FLAGS_START (VM_NONE) #define VM_FLAGS_END (VM_SHARED | VM_EXEC | VM_WRITE | VM_READ)
/* * Both P4D and PGD level tests are very basic which do not * involve creating page table entries from the protection * value and the given pfn. Hence just keep them out from * the above iteration for now to save some test execution * time.
*/
p4d_basic_tests(&args);
pgd_basic_tests(&args);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.