diff options
author | James Morse <james.morse@arm.com> | 2020-06-16 14:07:28 +0100 |
---|---|---|
committer | James Morse <james.morse@arm.com> | 2020-07-22 11:56:38 +0000 |
commit | 442601b6f098ddc565b6e6384a7bcc33575baf23 (patch) | |
tree | 748dda77426bfaefd0f780009dc4612e8a7ec184 | |
parent | a3307eb5dbdba6b657a0d9b19eb9bc0c05d04add (diff) | |
download | kvm-unit-tests-nv/at/wip.tar.gz |
arm64: Add tests for emulation of Address Translation instructionsnv/at/wip
KVM's nested-virt has to emulate AT instructions on behalf of guests.
It's really hairy. Add some tests.
Signed-off-by: James Morse <james.morse@arm.com>
CC: Alexandru Elisei <alexandru.elisei@arm.com>
-rw-r--r-- | arm/Makefile.arm64 | 1 | ||||
-rw-r--r-- | arm/at.c | 202 | ||||
-rw-r--r-- | lib/arm/mmu.c | 6 | ||||
-rw-r--r-- | lib/arm64/asm/esr.h | 7 | ||||
-rw-r--r-- | lib/arm64/asm/mmu.h | 1 | ||||
-rw-r--r-- | lib/libcflat.h | 1 | ||||
-rw-r--r-- | lib/vmalloc.h | 3 |
7 files changed, 218 insertions, 3 deletions
diff --git a/arm/Makefile.arm64 b/arm/Makefile.arm64 index dfd0c56..78a2f2d 100644 --- a/arm/Makefile.arm64 +++ b/arm/Makefile.arm64 @@ -27,6 +27,7 @@ OBJDIRS += lib/arm64 tests = $(TEST_DIR)/timer.flat tests += $(TEST_DIR)/micro-bench.flat tests += $(TEST_DIR)/cache.flat +tests += $(TEST_DIR)/at.flat include $(SRCDIR)/$(TEST_DIR)/Makefile.common diff --git a/arm/at.c b/arm/at.c new file mode 100644 index 0000000..1c008c6 --- /dev/null +++ b/arm/at.c @@ -0,0 +1,202 @@ +/* + * Tests for AT instructions, as emulated by KVM nested virt or Qemu TCG + * + * Copyright (C) 2020, ARM Ltd + * + * This work is licensed under the terms of the GNU GPL, version 2. + */ +#include <libcflat.h> + +#include <alloc_page.h> +#include <bitops.h> +#include <vmalloc.h> + +#include <asm/esr.h> +#include <asm/mmu.h> +#include <asm/mmu-api.h> +#include <asm/page.h> +#include <asm/pgtable.h> +#include <asm/processor.h> + +/* AKA: outer shareable */ +#define DEVICE_SHAREABILITY 0b10 +#define INNER_SHAREABLE 0b11 +#define DEVICE_nGnRE 0x04 +#define NORMAL_WB 0xff + +#define PAR_EL1_F BIT(0) +#define PAR_EL1_RES0 (GENMASK_ULL(55, 52) | GENMASK_ULL(6, 1)) +#define PAR_EL1_ATTR_SHIFT 56 +#define PAR_EL1_ATTR GENMASK_ULL(63, 56) +#define PAR_EL1_PA GENMASK_ULL(51, 12) +#define PAR_EL1_RES1 BIT(11) +#define PAR_EL1_NS BIT(9) +#define PAR_EL1_SH_SHIFT 7 +#define PAR_EL1_SH GENMASK_ULL(8, 7) + +#define PAR_EL1_FAULT_RES0 (GENMASK_ULL(12, 47) | BIT(10) | BIT(7)) +#define PAR_EL1_FAULT_STAGE BIT(9) +#define PAR_EL1_FAULT_PTW BIT(8) +#define PAR_EL1_FAULT_FST_SHIFT 1 +#define PAR_EL1_FAULT_FST GENMASK_ULL(6, 1) + +static u64 perform_at(u64 addr, bool read) +{ + u64 par; + + if (read) + asm volatile("at s1e1r, %0" : : "r" (addr)); + else + asm volatile("at s1e1w, %0" : : "r" (addr)); + isb(); + + par = read_sysreg(par_el1); + report_info("AT S1E1%s, 0x%lx = 0x%lx", (read ? "R" : "W"), + addr, par); + + return par; +} + +static void expect_translation(u64 par, u8 shareability, u8 memattr, u64 pa) +{ + u8 sh, attr; + bool fault; + + fault = par & PAR_EL1_F; + report(!fault, "PAR_EL1 Did not report a fault"); + + if (fault) + return; + + if (par & PAR_EL1_RES0) + report_info("PAR_EL1 RES0 bits set"); + + if (!(par & PAR_EL1_RES1)) + report_info("PAR_EL1 RES1 bit not set"); + + report(par & PAR_EL1_NS, "PAR_EL1 reports NS"); + + sh = (par & PAR_EL1_SH) >> PAR_EL1_SH_SHIFT; + report(sh == shareability, "PAR_EL1 Sharability"); + if (sh != shareability) + report_info("Expected %x, got %x", shareability, sh); + + report((par & PAR_EL1_PA) == pa, "Correct translation"); + + attr = (par & PAR_EL1_ATTR) >> PAR_EL1_ATTR_SHIFT; + report(attr == memattr, "PAR_EL1 Memory-Attributes"); + if (attr != memattr) + report_info("Expected %x, got %x", memattr, attr); +} + +static void expect_fault(u64 par, u8 expected_fst, u8 level) +{ + u8 fst; + bool fault; + + fault = par & PAR_EL1_F; + report(fault, "PAR_EL1 Reported a fault"); + + if (!fault) + return; + + if (par & PAR_EL1_FAULT_RES0) + report_info("PAR_EL1 Faulting RES0 bits set"); + + if (!(par & PAR_EL1_RES1)) + report_info("PAR_EL1 RES1 bit not set"); + + report(!(par & PAR_EL1_FAULT_STAGE), "PAR_EL1 reports stage1 fault"); + report(!(par & PAR_EL1_FAULT_PTW), "PAR_EL1 doesn't report PTW fault"); + + fst = (par & PAR_EL1_FAULT_FST ) >> PAR_EL1_FAULT_FST_SHIFT; + report(fst == (expected_fst | level), "PAR_EL1 reports expected fault"); + if (fst != (expected_fst | level)) + report_info("Expected %x@%x, got %x@%x", expected_fst, level, fst & 0xc, fst & 0x3); +} + + +static void do_static_tests(void) +{ + u64 par, addr; + + report_info("***** Translate current function *****"); + addr = (u64)&do_static_tests; + par = perform_at(addr, true); + expect_translation(par, INNER_SHAREABLE, NORMAL_WB, + __pa(addr) & PAR_EL1_PA); + + + report_info("***** Translate IO *****"); + par = perform_at(0, true); + expect_translation(par, DEVICE_SHAREABILITY, DEVICE_nGnRE, 0); + + /* + * kvm-unit-tests setup code is shared with 32bit, there shouldn't be + * anything above 4G. + */ + addr = ((1ULL << VA_BITS) - 1) & PAGE_MASK; + report_info("***** Fault unmapped address *****"); + par = perform_at(addr, true); + /* + * kvm unit tests uses 64K/42, there are two levels of page tables, + * expect faults at level 2, not level 3. + */ + expect_fault(par, ESR_EL1_FSC_FAULT, 2); +} + +static void do_dynamic_tests(void *junk_page, phys_addr_t pa_junk_page) +{ + u64 par, addr; + + /* 3G is the start of the vmalloc region */ + addr = (u64)SZ_2G + (u64)SZ_1G; + install_page_prot(mmu_idmap, pa_junk_page, addr, + __pgprot(PTE_WBWA | PTE_RDONLY)); + report_info("***** Fault write to read-only page *****"); + par = perform_at(addr, false); + expect_fault(par, ESR_EL1_FSC_PERM, 3); + + /* Now unmap it! */ + install_pte(mmu_idmap, addr, 0); + report_info("***** Fault unmapped address *****"); + par = perform_at(addr, false); + expect_fault(par, ESR_EL1_FSC_FAULT, 3); + + install_page_prot(mmu_idmap, ~0, addr, + __pgprot(PTE_WBWA | PTE_RDONLY)); + report_info("***** Fault Insane PA *****"); + par = perform_at(addr, false); + expect_fault(par, ESR_EL1_FSC_SIZE, 3); + + install_pte(mmu_idmap, addr, (pa_junk_page | PTE_TYPE_PAGE | + PTE_SHARED | PTE_WBWA)); + report_info("***** Access Flag Fault *****"); + par = perform_at(addr, false); + expect_fault(par, ESR_EL1_FSC_ACCESS, 3); + + install_pte(mmu_idmap, addr, 0); + +} + +int main(int argc, char **argv) +{ + void *junk_page = alloc_vpage(); + phys_addr_t pa_junk_page; + + do_static_tests(); + + if (!junk_page) { + // skip + return report_summary(); + } + + /* + *__virt_to_phys() walks the page tables, and finds junk_page + * unmapped + */ + pa_junk_page = (long)junk_page & PAGE_MASK; + do_dynamic_tests(junk_page, pa_junk_page); + + return report_summary(); +} diff --git a/lib/arm/mmu.c b/lib/arm/mmu.c index 540a1e8..813e09b 100644 --- a/lib/arm/mmu.c +++ b/lib/arm/mmu.c @@ -87,7 +87,7 @@ static pteval_t *get_pte(pgd_t *pgtable, uintptr_t vaddr) return &pte_val(*pte); } -static pteval_t *install_pte(pgd_t *pgtable, uintptr_t vaddr, pteval_t pte) +pteval_t *install_pte(pgd_t *pgtable, uintptr_t vaddr, pteval_t pte) { pteval_t *p_pte = get_pte(pgtable, vaddr); @@ -96,8 +96,8 @@ static pteval_t *install_pte(pgd_t *pgtable, uintptr_t vaddr, pteval_t pte) return p_pte; } -static pteval_t *install_page_prot(pgd_t *pgtable, phys_addr_t phys, - uintptr_t vaddr, pgprot_t prot) +pteval_t *install_page_prot(pgd_t *pgtable, phys_addr_t phys, + uintptr_t vaddr, pgprot_t prot) { pteval_t pte = phys; pte |= PTE_TYPE_PAGE | PTE_AF | PTE_SHARED; diff --git a/lib/arm64/asm/esr.h b/lib/arm64/asm/esr.h index 8c35163..265c4e6 100644 --- a/lib/arm64/asm/esr.h +++ b/lib/arm64/asm/esr.h @@ -46,5 +46,12 @@ #define ESR_EL1_FSC_MASK (0x3F) #define ESR_EL1_FSC_EXTABT (0x10) +#define ESR_EL1_FSC_TYPE (0x3C) +#define ESR_EL1_FSC_EXTABT (0x10) +#define ESR_EL1_FSC_SERROR (0x11) +#define ESR_EL1_FSC_ACCESS (0x08) +#define ESR_EL1_FSC_FAULT (0x04) +#define ESR_EL1_FSC_PERM (0x0C) +#define ESR_EL1_FSC_SIZE (0x00) #endif /* _ASMARM64_ESR_H_ */ diff --git a/lib/arm64/asm/mmu.h b/lib/arm64/asm/mmu.h index 72d75ea..a3f0fe3 100644 --- a/lib/arm64/asm/mmu.h +++ b/lib/arm64/asm/mmu.h @@ -6,6 +6,7 @@ * This work is licensed under the terms of the GNU LGPL, version 2. */ #include <asm/barrier.h> +#include <asm/pgtable-hwdef.h> #define PMD_SECT_UNCACHED PMD_ATTRINDX(MT_DEVICE_nGnRE) #define PTE_WBWA PTE_ATTRINDX(MT_NORMAL) diff --git a/lib/libcflat.h b/lib/libcflat.h index 7092af2..323b4fe 100644 --- a/lib/libcflat.h +++ b/lib/libcflat.h @@ -43,6 +43,7 @@ #define SZ_64K (1 << 16) #define SZ_2M (1 << 21) #define SZ_1G (1 << 30) +#define SZ_2G (1U << 31) #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) diff --git a/lib/vmalloc.h b/lib/vmalloc.h index 3658b80..84598ae 100644 --- a/lib/vmalloc.h +++ b/lib/vmalloc.h @@ -10,6 +10,9 @@ extern void setup_vm(void); extern void *setup_mmu(phys_addr_t top); extern phys_addr_t virt_to_pte_phys(pgd_t *pgtable, void *virt); +extern pteval_t *install_pte(pgd_t *pgtable, uintptr_t vaddr, pteval_t pte); +extern pteval_t *install_page_prot(pgd_t *pgtable, phys_addr_t phys, + uintptr_t vaddr, pgprot_t prot); extern pteval_t *install_page(pgd_t *pgtable, phys_addr_t phys, void *virt); void *vmap(phys_addr_t phys, size_t size); |