tests: Add a test for the MMU radix page table walks
authorPaul Mackerras <paulus@ozlabs.org>
Thu, 23 Apr 2020 05:33:36 +0000 (15:33 +1000)
committerPaul Mackerras <paulus@ozlabs.org>
Fri, 8 May 2020 02:12:02 +0000 (12:12 +1000)
This adds tests to check that the MMU and dTLB are translating
addresses and checking permissions correctly.

We use a simple 2-level radix tree.  The radix tree maps 2GB of
address space and has a 1024-entry page directory pointing to
512-entry page table pages.

Signed-off-by: Paul Mackerras <paulus@ozlabs.org>
tests/mmu/Makefile [new file with mode: 0644]
tests/mmu/head.S [new file with mode: 0644]
tests/mmu/mmu.c [new file with mode: 0644]
tests/mmu/powerpc.lds [new file with mode: 0644]
tests/test_mmu.bin [new file with mode: 0755]
tests/test_mmu.console_out [new file with mode: 0644]
tests/update_console_tests

diff --git a/tests/mmu/Makefile b/tests/mmu/Makefile
new file mode 100644 (file)
index 0000000..84f7ff2
--- /dev/null
@@ -0,0 +1,3 @@
+TEST=mmu
+
+include ../Makefile.test
diff --git a/tests/mmu/head.S b/tests/mmu/head.S
new file mode 100644 (file)
index 0000000..3627cff
--- /dev/null
@@ -0,0 +1,112 @@
+/* Copyright 2013-2014 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define STACK_TOP 0x4000
+
+/* Load an immediate 64-bit value into a register */
+#define LOAD_IMM64(r, e)                       \
+       lis     r,(e)@highest;                  \
+       ori     r,r,(e)@higher;                 \
+       rldicr  r,r, 32, 31;                    \
+       oris    r,r, (e)@h;                     \
+       ori     r,r, (e)@l;
+
+       .section ".head","ax"
+
+       /*
+        * Microwatt currently enters in LE mode at 0x0, so we don't need to
+        * do any endian fix ups
+        */
+       . = 0
+.global _start
+_start:
+       b       boot_entry
+
+.global boot_entry
+boot_entry:
+       /* setup stack */
+       LOAD_IMM64(%r1, STACK_TOP - 0x100)
+       LOAD_IMM64(%r12, main)
+       mtctr   %r12
+       bctrl
+       attn // terminate on exit
+       b .
+
+       /* Read a location with translation on */
+       .globl  test_read
+test_read:
+       mfmsr   %r9
+       ori     %r8,%r9,0x10    /* set MSR_DR */
+       mtmsrd  %r8,0
+       mr      %r6,%r3
+       li      %r3,0
+       ld      %r5,0(%r6)
+       li      %r3,1
+       /* land here if DSI occurred */
+       mtmsrd  %r9,0
+       std     %r5,0(%r4)
+       blr
+
+       /* Write a location with translation on */
+       .globl  test_write
+test_write:
+       mfmsr   %r9
+       ori     %r8,%r9,0x10    /* set MSR_DR */
+       mtmsrd  %r8,0
+       mr      %r6,%r3
+       li      %r3,0
+       std     %r4,0(%r6)
+       li      %r3,1
+       /* land here if DSI occurred */
+       mtmsrd  %r9,0
+       blr
+
+#define EXCEPTION(nr)          \
+       .= nr                   ;\
+       attn
+
+       /* DSI vector - skip the failing instruction + the next one */
+       . = 0x300
+       mtsprg0 %r10
+       mfsrr0  %r10
+       addi    %r10,%r10,8
+       mtsrr0  %r10
+       rfid
+
+       /* More exception stubs */
+       EXCEPTION(0x380)
+       EXCEPTION(0x400)
+       EXCEPTION(0x480)
+       EXCEPTION(0x500)
+       EXCEPTION(0x600)
+       EXCEPTION(0x700)
+       EXCEPTION(0x800)
+       EXCEPTION(0x900)
+       EXCEPTION(0x980)
+       EXCEPTION(0xa00)
+       EXCEPTION(0xb00)
+       EXCEPTION(0xc00)
+       EXCEPTION(0xd00)
+       EXCEPTION(0xe00)
+       EXCEPTION(0xe20)
+       EXCEPTION(0xe40)
+       EXCEPTION(0xe60)
+       EXCEPTION(0xe80)
+       EXCEPTION(0xf00)
+       EXCEPTION(0xf20)
+       EXCEPTION(0xf40)
+       EXCEPTION(0xf60)
+       EXCEPTION(0xf80)
diff --git a/tests/mmu/mmu.c b/tests/mmu/mmu.c
new file mode 100644 (file)
index 0000000..0a717c7
--- /dev/null
@@ -0,0 +1,468 @@
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "console.h"
+
+extern int test_read(long *addr, long *ret, long init);
+extern int test_write(long *addr, long val);
+
+static inline void do_tlbie(unsigned long rb, unsigned long rs)
+{
+       __asm__ volatile("tlbie %0,%1" : : "r" (rb), "r" (rs) : "memory");
+}
+
+static inline unsigned long mfspr(int sprnum)
+{
+       long val;
+
+       __asm__ volatile("mfspr %0,%1" : "=r" (val) : "i" (sprnum));
+       return val;
+}
+
+static inline void mtspr(int sprnum, unsigned long val)
+{
+       __asm__ volatile("mtspr %0,%1" : : "i" (sprnum), "r" (val));
+}
+
+static inline void store_pte(unsigned long *p, unsigned long pte)
+{
+       __asm__ volatile("stdbrx %1,0,%0" : : "r" (p), "r" (pte) : "memory");
+}
+
+void print_string(const char *str)
+{
+       for (; *str; ++str)
+               putchar(*str);
+}
+
+void print_hex(unsigned long val)
+{
+       int i, x;
+
+       for (i = 60; i >= 0; i -= 4) {
+               x = (val >> i) & 0xf;
+               if (x >= 10)
+                       putchar(x + 'a' - 10);
+               else
+                       putchar(x + '0');
+       }
+}
+
+// i < 100
+void print_test_number(int i)
+{
+       print_string("test ");
+       putchar(48 + i/10);
+       putchar(48 + i%10);
+       putchar(':');
+}
+
+#define CACHE_LINE_SIZE        64
+
+void zero_memory(void *ptr, unsigned long nbytes)
+{
+       unsigned long nb, i, nl;
+       void *p;
+
+       for (; nbytes != 0; nbytes -= nb, ptr += nb) {
+               nb = -((unsigned long)ptr) & (CACHE_LINE_SIZE - 1);
+               if (nb == 0 && nbytes >= CACHE_LINE_SIZE) {
+                       nl = nbytes / CACHE_LINE_SIZE;
+                       p = ptr;
+                       for (i = 0; i < nl; ++i) {
+                               __asm__ volatile("dcbz 0,%0" : : "r" (p) : "memory");
+                               p += CACHE_LINE_SIZE;
+                       }
+                       nb = nl * CACHE_LINE_SIZE;
+               } else {
+                       if (nb > nbytes)
+                               nb = nbytes;
+                       for (i = 0; i < nb; ++i)
+                               ((unsigned char *)ptr)[i] = 0;
+               }
+       }
+}
+
+#define PERM_EX                0x001
+#define PERM_WR                0x002
+#define PERM_RD                0x004
+#define PERM_PRIV      0x008
+#define ATTR_NC                0x020
+#define CHG            0x080
+#define REF            0x100
+
+#define DFLT_PERM      (PERM_WR | PERM_RD | REF | CHG)
+
+/*
+ * Set up an MMU translation tree using memory starting at the 64k point.
+ * We use 2 levels, mapping 2GB (the minimum size possible), with a
+ * 8kB PGD level pointing to 4kB PTE pages.
+ */
+unsigned long *pgdir = (unsigned long *) 0x10000;
+unsigned long free_ptr = 0x12000;
+void *eas_mapped[4];
+int neas_mapped;
+
+void init_mmu(void)
+{
+       zero_memory(pgdir, 1024 * sizeof(unsigned long));
+       /* RTS = 0 (2GB address space), RPDS = 10 (1024-entry top level) */
+       mtspr(720, (unsigned long) pgdir | 10);
+       do_tlbie(0xc00, 0);     /* invalidate all TLB entries */
+}
+
+static unsigned long *read_pgd(unsigned long i)
+{
+       unsigned long ret;
+
+       __asm__ volatile("ldbrx %0,%1,%2" : "=r" (ret) : "b" (pgdir),
+                        "r" (i * sizeof(unsigned long)));
+       return (unsigned long *) (ret & 0x00ffffffffffff00);
+}
+
+void map(void *ea, void *pa, unsigned long perm_attr)
+{
+       unsigned long epn = (unsigned long) ea >> 12;
+       unsigned long i, j;
+       unsigned long *ptep;
+
+       i = (epn >> 9) & 0x3ff;
+       j = epn & 0x1ff;
+       if (pgdir[i] == 0) {
+               zero_memory((void *)free_ptr, 512 * sizeof(unsigned long));
+               store_pte(&pgdir[i], 0x8000000000000000 | free_ptr | 9);
+               free_ptr += 512 * sizeof(unsigned long);
+       }
+       ptep = read_pgd(i);
+       store_pte(&ptep[j], 0xc000000000000000 | ((unsigned long)pa & 0x00fffffffffff000) | perm_attr);
+       eas_mapped[neas_mapped++] = ea;
+}
+
+void unmap(void *ea)
+{
+       unsigned long epn = (unsigned long) ea >> 12;
+       unsigned long i, j;
+       unsigned long *ptep;
+
+       i = (epn >> 9) & 0x3ff;
+       j = epn & 0x1ff;
+       if (pgdir[i] == 0)
+               return;
+       ptep = read_pgd(i);
+       ptep[j] = 0;
+       do_tlbie(((unsigned long)ea & ~0xfff), 0);
+}
+
+void unmap_all(void)
+{
+       int i;
+
+       for (i = 0; i < neas_mapped; ++i)
+               unmap(eas_mapped[i]);
+       neas_mapped = 0;
+}
+
+int mmu_test_1(void)
+{
+       long *ptr = (long *) 0x123000;
+       long val;
+
+       /* this should fail */
+       if (test_read(ptr, &val, 0xdeadbeefd00d))
+               return 1;
+       /* dest reg of load should be unchanged */
+       if (val != 0xdeadbeefd00d)
+               return 2;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long) ptr || mfspr(18) != 0x40000000)
+               return 3;
+       return 0;
+}
+
+int mmu_test_2(void)
+{
+       long *mem = (long *) 0x4000;
+       long *ptr = (long *) 0x124000;
+       long *ptr2 = (long *) 0x1124000;
+       long val;
+
+       /* create PTE */
+       map(ptr, mem, DFLT_PERM);
+       /* initialize the memory content */
+       mem[33] = 0xbadc0ffee;
+       /* this should succeed and be a cache miss */
+       if (!test_read(&ptr[33], &val, 0xdeadbeefd00d))
+               return 1;
+       /* dest reg of load should have the value written */
+       if (val != 0xbadc0ffee)
+               return 2;
+       /* load a second TLB entry in the same set as the first */
+       map(ptr2, mem, DFLT_PERM);
+       /* this should succeed and be a cache hit */
+       if (!test_read(&ptr2[33], &val, 0xdeadbeefd00d))
+               return 3;
+       /* dest reg of load should have the value written */
+       if (val != 0xbadc0ffee)
+               return 4;
+       /* check that the first entry still works */
+       if (!test_read(&ptr[33], &val, 0xdeadbeefd00d))
+               return 5;
+       if (val != 0xbadc0ffee)
+               return 6;
+       return 0;
+}
+
+int mmu_test_3(void)
+{
+       long *mem = (long *) 0x5000;
+       long *ptr = (long *) 0x149000;
+       long val;
+
+       /* create PTE */
+       map(ptr, mem, DFLT_PERM);
+       /* initialize the memory content */
+       mem[45] = 0xfee1800d4ea;
+       /* this should succeed and be a cache miss */
+       if (!test_read(&ptr[45], &val, 0xdeadbeefd0d0))
+               return 1;
+       /* dest reg of load should have the value written */
+       if (val != 0xfee1800d4ea)
+               return 2;
+       /* remove the PTE */
+       unmap(ptr);
+       /* this should fail */
+       if (test_read(&ptr[45], &val, 0xdeadbeefd0d0))
+               return 3;
+       /* dest reg of load should be unchanged */
+       if (val != 0xdeadbeefd0d0)
+               return 4;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long) &ptr[45] || mfspr(18) != 0x40000000)
+               return 5;
+       return 0;
+}
+
+int mmu_test_4(void)
+{
+       long *mem = (long *) 0x6000;
+       long *ptr = (long *) 0x10a000;
+       long *ptr2 = (long *) 0x110a000;
+       long val;
+
+       /* create PTE */
+       map(ptr, mem, DFLT_PERM);
+       /* initialize the memory content */
+       mem[27] = 0xf00f00f00f00;
+       /* this should succeed and be a cache miss */
+       if (!test_write(&ptr[27], 0xe44badc0ffee))
+               return 1;
+       /* memory should now have the value written */
+       if (mem[27] != 0xe44badc0ffee)
+               return 2;
+       /* load a second TLB entry in the same set as the first */
+       map(ptr2, mem, DFLT_PERM);
+       /* this should succeed and be a cache hit */
+       if (!test_write(&ptr2[27], 0x6e11ae))
+               return 3;
+       /* memory should have the value written */
+       if (mem[27] != 0x6e11ae)
+               return 4;
+       /* check that the first entry still exists */
+       /* (assumes TLB is 2-way associative or more) */
+       if (!test_read(&ptr[27], &val, 0xdeadbeefd00d))
+               return 5;
+       if (val != 0x6e11ae)
+               return 6;
+       return 0;
+}
+
+int mmu_test_5(void)
+{
+       long *mem = (long *) 0x7ffd;
+       long *ptr = (long *) 0x39fffd;
+       long val;
+
+       /* create PTE */
+       map(ptr, mem, DFLT_PERM);
+       /* this should fail */
+       if (test_read(ptr, &val, 0xdeadbeef0dd0))
+               return 1;
+       /* dest reg of load should be unchanged */
+       if (val != 0xdeadbeef0dd0)
+               return 2;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != ((long)ptr & ~0xfff) + 0x1000 || mfspr(18) != 0x40000000)
+               return 3;
+       return 0;
+}
+
+int mmu_test_6(void)
+{
+       long *mem = (long *) 0x7ffd;
+       long *ptr = (long *) 0x39fffd;
+
+       /* create PTE */
+       map(ptr, mem, DFLT_PERM);
+       /* initialize memory */
+       *mem = 0x123456789abcdef0;
+       /* this should fail */
+       if (test_write(ptr, 0xdeadbeef0dd0))
+               return 1;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != ((long)ptr & ~0xfff) + 0x1000 || mfspr(18) != 0x42000000)
+               return 2;
+       return 0;
+}
+
+int mmu_test_7(void)
+{
+       long *mem = (long *) 0x4000;
+       long *ptr = (long *) 0x124000;
+       long val;
+
+       *mem = 0x123456789abcdef0;
+       /* create PTE without R or C */
+       map(ptr, mem, PERM_RD | PERM_WR);
+       /* this should fail */
+       if (test_read(ptr, &val, 0xdeadd00dbeef))
+               return 1;
+       /* dest reg of load should be unchanged */
+       if (val != 0xdeadd00dbeef)
+               return 2;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long) ptr || mfspr(18) != 0x00040000)
+               return 3;
+       /* this should fail */
+       if (test_write(ptr, 0xdeadbeef0dd0))
+               return 4;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long)ptr || mfspr(18) != 0x02040000)
+               return 5;
+       /* memory should be unchanged */
+       if (*mem != 0x123456789abcdef0)
+               return 6;
+       return 0;
+}
+
+int mmu_test_8(void)
+{
+       long *mem = (long *) 0x4000;
+       long *ptr = (long *) 0x124000;
+       long val;
+
+       *mem = 0x123456789abcdef0;
+       /* create PTE with R but not C */
+       map(ptr, mem, REF | PERM_RD | PERM_WR);
+       /* this should succeed */
+       if (!test_read(ptr, &val, 0xdeadd00dbeef))
+               return 1;
+       /* this should fail */
+       if (test_write(ptr, 0xdeadbeef0dd1))
+               return 2;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long)ptr || mfspr(18) != 0x02040000)
+               return 3;
+       /* memory should be unchanged */
+       if (*mem != 0x123456789abcdef0)
+               return 4;
+       return 0;
+}
+
+int mmu_test_9(void)
+{
+       long *mem = (long *) 0x4000;
+       long *ptr = (long *) 0x124000;
+       long val;
+
+       *mem = 0x123456789abcdef0;
+       /* create PTE without read or write permission */
+       map(ptr, mem, REF);
+       /* this should fail */
+       if (test_read(ptr, &val, 0xdeadd00dbeef))
+               return 1;
+       /* dest reg of load should be unchanged */
+       if (val != 0xdeadd00dbeef)
+               return 2;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long) ptr || mfspr(18) != 0x08000000)
+               return 3;
+       /* this should fail */
+       if (test_write(ptr, 0xdeadbeef0dd1))
+               return 4;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long)ptr || mfspr(18) != 0x0a000000)
+               return 5;
+       /* memory should be unchanged */
+       if (*mem != 0x123456789abcdef0)
+               return 6;
+       return 0;
+}
+
+int mmu_test_10(void)
+{
+       long *mem = (long *) 0x4000;
+       long *ptr = (long *) 0x124000;
+       long val;
+
+       *mem = 0x123456789abcdef0;
+       /* create PTE with read but not write permission */
+       map(ptr, mem, REF | PERM_RD);
+       /* this should succeed */
+       if (!test_read(ptr, &val, 0xdeadd00dbeef))
+               return 1;
+       /* this should fail */
+       if (test_write(ptr, 0xdeadbeef0dd1))
+               return 2;
+       /* DAR and DSISR should be set correctly */
+       if (mfspr(19) != (long)ptr || mfspr(18) != 0x0a000000)
+               return 3;
+       /* memory should be unchanged */
+       if (*mem != 0x123456789abcdef0)
+               return 4;
+       return 0;
+}
+
+int fail = 0;
+
+void do_test(int num, int (*test)(void))
+{
+       int ret;
+
+       mtspr(18, 0);
+       mtspr(19, 0);
+       unmap_all();
+       print_test_number(num);
+       ret = test();
+       if (ret == 0) {
+               print_string("PASS\r\n");
+       } else {
+               fail = 1;
+               print_string("FAIL ");
+               putchar(ret + '0');
+               print_string(" DAR=");
+               print_hex(mfspr(19));
+               print_string(" DSISR=");
+               print_hex(mfspr(18));
+               print_string("\r\n");
+       }
+}
+
+int main(void)
+{
+       potato_uart_init();
+       init_mmu();
+
+       do_test(1, mmu_test_1);
+       do_test(2, mmu_test_2);
+       do_test(3, mmu_test_3);
+       do_test(4, mmu_test_4);
+       do_test(5, mmu_test_5);
+       do_test(6, mmu_test_6);
+       do_test(7, mmu_test_7);
+       do_test(8, mmu_test_8);
+       do_test(9, mmu_test_9);
+       do_test(10, mmu_test_10);
+
+       return fail;
+}
diff --git a/tests/mmu/powerpc.lds b/tests/mmu/powerpc.lds
new file mode 100644 (file)
index 0000000..c4bff13
--- /dev/null
@@ -0,0 +1,13 @@
+SECTIONS
+{
+       _start = .;
+       . = 0;
+       .head : {
+               KEEP(*(.head))
+       }
+       . = 0x1000;
+       .text : { *(.text) }
+       . = 0x3000;
+       .data : { *(.data) }
+       .bss : { *(.bss) }
+}
diff --git a/tests/test_mmu.bin b/tests/test_mmu.bin
new file mode 100755 (executable)
index 0000000..961e2df
Binary files /dev/null and b/tests/test_mmu.bin differ
diff --git a/tests/test_mmu.console_out b/tests/test_mmu.console_out
new file mode 100644 (file)
index 0000000..3e84260
--- /dev/null
@@ -0,0 +1,10 @@
+test 01:PASS\r
+test 02:PASS\r
+test 03:PASS\r
+test 04:PASS\r
+test 05:PASS\r
+test 06:PASS\r
+test 07:PASS\r
+test 08:PASS\r
+test 09:PASS\r
+test 10:PASS\r
index 94e74d10f3a91814d897ad3a0e26b0d817d66f13..d8fb44e7c6d608e1703d7f0e9f3ea8f1e0380bdf 100755 (executable)
@@ -3,7 +3,7 @@
 # Script to update console related tests from source
 #
 
-for i in sc illegal decrementer xics privileged ; do
+for i in sc illegal decrementer xics privileged mmu ; do
     cd $i
     make
     cd -