From: Paul Mackerras Date: Thu, 23 Apr 2020 05:33:36 +0000 (+1000) Subject: tests: Add a test for the MMU radix page table walks X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=882a5a0dc06add8f91b747e8032e044708a32318;p=microwatt.git tests: Add a test for the MMU radix page table walks 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 --- diff --git a/tests/mmu/Makefile b/tests/mmu/Makefile new file mode 100644 index 0000000..84f7ff2 --- /dev/null +++ b/tests/mmu/Makefile @@ -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 index 0000000..3627cff --- /dev/null +++ b/tests/mmu/head.S @@ -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 index 0000000..0a717c7 --- /dev/null +++ b/tests/mmu/mmu.c @@ -0,0 +1,468 @@ +#include +#include +#include + +#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 index 0000000..c4bff13 --- /dev/null +++ b/tests/mmu/powerpc.lds @@ -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 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 index 0000000..3e84260 --- /dev/null +++ b/tests/test_mmu.console_out @@ -0,0 +1,10 @@ +test 01:PASS +test 02:PASS +test 03:PASS +test 04:PASS +test 05:PASS +test 06:PASS +test 07:PASS +test 08:PASS +test 09:PASS +test 10:PASS diff --git a/tests/update_console_tests b/tests/update_console_tests index 94e74d1..d8fb44e 100755 --- a/tests/update_console_tests +++ b/tests/update_console_tests @@ -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 -