aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Morse <james.morse@arm.com>2020-06-16 14:07:28 +0100
committerJames Morse <james.morse@arm.com>2020-07-22 11:56:38 +0000
commit442601b6f098ddc565b6e6384a7bcc33575baf23 (patch)
tree748dda77426bfaefd0f780009dc4612e8a7ec184
parenta3307eb5dbdba6b657a0d9b19eb9bc0c05d04add (diff)
downloadkvm-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.arm641
-rw-r--r--arm/at.c202
-rw-r--r--lib/arm/mmu.c6
-rw-r--r--lib/arm64/asm/esr.h7
-rw-r--r--lib/arm64/asm/mmu.h1
-rw-r--r--lib/libcflat.h1
-rw-r--r--lib/vmalloc.h3
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);