diff options
author | Tim Newsome <tim@sifive.com> | 2020-01-09 15:42:05 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-09 15:42:05 -0800 |
commit | 6db6ab8680ea493bacbdd58adb2227894615afdc (patch) | |
tree | e03767f450ba2db4b4ea422fc6d19a7619a8c045 | |
parent | d081949d96d374949e3ac5b849608dda251825d0 (diff) | |
download | riscv-tests-6db6ab8680ea493bacbdd58adb2227894615afdc.zip riscv-tests-6db6ab8680ea493bacbdd58adb2227894615afdc.tar.gz riscv-tests-6db6ab8680ea493bacbdd58adb2227894615afdc.tar.bz2 |
Smoke test virtual address translation support. (#233)
* WIP
* Smoke test virtual address support.
Tests sv32, sv39, and sv48. Only explicitly tests 4K pages, but uses as
large as possible pages to 1:1 map the rest of RAM so those sizes do get
minimal coverage as well.
-rwxr-xr-x | debug/gdbserver.py | 49 | ||||
-rw-r--r-- | debug/programs/init.c | 4 | ||||
-rw-r--r-- | debug/programs/init.h | 8 | ||||
-rw-r--r-- | debug/programs/interrupt.c | 4 | ||||
-rw-r--r-- | debug/programs/multicore.c | 3 | ||||
-rw-r--r-- | debug/programs/translate.c | 176 |
6 files changed, 231 insertions, 13 deletions
diff --git a/debug/gdbserver.py b/debug/gdbserver.py index 55cfb21..3b61529 100755 --- a/debug/gdbserver.py +++ b/debug/gdbserver.py @@ -1259,6 +1259,55 @@ class CheckMisa(GdbTest): misa = self.gdb.p("$misa") assertEqual(misa, hart.misa) +class TranslateTest(GdbTest): + compile_args = ("programs/translate.c", ) + + def setup(self): + # TODO: If we use a random hart, then we get into trouble because + # gdb_read_memory_packet() ignores which hart is currently selected, so + # we end up reading satp from hart 0 when the address translation might + # be set up on hart 1 only. + self.gdb.select_hart(self.target.harts[0]) + self.gdb.load() + self.gdb.b("main") + output = self.gdb.c() + assertIn(" main ", output) + + def test_translation(self): + self.gdb.b("error") + self.gdb.b("handle_trap") + self.gdb.b("main:active") + output = self.gdb.c() + assertIn(" main ", output) + assertEqual(0xdeadbeef, self.gdb.p("physical[0]")) + assertEqual(0x55667788, self.gdb.p("physical[1]")) + assertEqual(0xdeadbeef, self.gdb.p("virtual[0]")) + assertEqual(0x55667788, self.gdb.p("virtual[1]")) + +class Sv32Test(TranslateTest): + def early_applicable(self): + return self.hart.xlen == 32 + + def test(self): + self.gdb.p("vms=&sv32") + self.test_translation() + +class Sv39Test(TranslateTest): + def early_applicable(self): + return self.hart.xlen > 32 + + def test(self): + self.gdb.p("vms=&sv39") + self.test_translation() + +class Sv48Test(TranslateTest): + def early_applicable(self): + return self.hart.xlen > 32 + + def test(self): + self.gdb.p("vms=&sv48") + self.test_translation() + parsed = None def main(): parser = argparse.ArgumentParser( diff --git a/debug/programs/init.c b/debug/programs/init.c index 8b047de..e3efc8e 100644 --- a/debug/programs/init.c +++ b/debug/programs/init.c @@ -7,7 +7,7 @@ trap_handler_t trap_handler[NHARTS] = {0}; void set_trap_handler(trap_handler_t handler) { - unsigned hartid = csr_read(mhartid); + unsigned hartid = read_csr(mhartid); trap_handler[hartid] = handler; } @@ -19,7 +19,7 @@ void enable_timer_interrupts() void handle_trap(unsigned int mcause, void *mepc, void *sp) { - unsigned hartid = csr_read(mhartid); + unsigned hartid = read_csr(mhartid); if (trap_handler[hartid]) { trap_handler[hartid](hartid, mcause, mepc, sp); return; diff --git a/debug/programs/init.h b/debug/programs/init.h index 9aaa398..06d5384 100644 --- a/debug/programs/init.h +++ b/debug/programs/init.h @@ -4,14 +4,6 @@ #define MTIME (*(volatile long long *)(0x02000000 + 0xbff8)) #define MTIMECMP ((volatile long long *)(0x02000000 + 0x4000)) -#define csr_read(csr) \ -({ \ - register unsigned long __v; \ - __asm__ __volatile__ ("csrr %0, " #csr \ - : "=r" (__v)); \ - __v; \ -}) - typedef void* (*trap_handler_t)(unsigned hartid, unsigned mcause, void *mepc, void *sp); void set_trap_handler(trap_handler_t handler); diff --git a/debug/programs/interrupt.c b/debug/programs/interrupt.c index c2dd5ec..8378576 100644 --- a/debug/programs/interrupt.c +++ b/debug/programs/interrupt.c @@ -10,7 +10,7 @@ void *increment_count(unsigned hartid, unsigned mcause, void *mepc, void *sp) interrupt_count++; // There is no guarantee that the interrupt is cleared immediately when // MTIMECMP is written, so stick around here until that happens. - while (csr_read(mip) & MIP_MTIP) { + while (read_csr(mip) & MIP_MTIP) { MTIMECMP[hartid] = MTIME + delta; } return mepc; @@ -20,7 +20,7 @@ int main() { interrupt_count = 0; local = 0; - unsigned hartid = csr_read(mhartid); + unsigned hartid = read_csr(mhartid); set_trap_handler(increment_count); MTIMECMP[hartid] = MTIME - 1; diff --git a/debug/programs/multicore.c b/debug/programs/multicore.c index 272baea..a51ee4a 100644 --- a/debug/programs/multicore.c +++ b/debug/programs/multicore.c @@ -1,6 +1,7 @@ #include <stdint.h> #include "init.h" +#include "encoding.h" typedef struct { int counter; @@ -51,7 +52,7 @@ void *increment_count(unsigned hartid, unsigned mcause, void *mepc, void *sp) int main() { - uint32_t hartid = csr_read(mhartid); + uint32_t hartid = read_csr(mhartid); hart_count[hartid] = 0; interrupt_count[hartid] = 0; diff --git a/debug/programs/translate.c b/debug/programs/translate.c new file mode 100644 index 0000000..ebeb92d --- /dev/null +++ b/debug/programs/translate.c @@ -0,0 +1,176 @@ +#include <stdint.h> + +#include "encoding.h" + +#define get_field(reg, mask) (((reg) & (mask)) / ((mask) & ~((mask) << 1))) +#define set_field(reg, mask, val) (((reg) & ~(mask)) | (((val) * ((mask) & ~((mask) << 1))) & (mask))) + +#if __riscv_xlen == 64 +# define SATP_PPN SATP64_PPN +typedef uint64_t reg_t; +#else +# define SATP_PPN SATP32_PPN +typedef uint32_t reg_t; +#endif + +static char page_buffer[4096 * 8]; +static char *page_buffer_next = page_buffer; + +typedef struct { + unsigned mode; + unsigned levels; + unsigned ppn_width_bits[5]; + unsigned ppn_offset_bits[5]; + unsigned entry_width_bytes; + unsigned vpn_width_bits; +} virtual_memory_system_t; + +static virtual_memory_system_t sv32 = { + .mode = SATP_MODE_SV32, + .levels = 2, + .ppn_width_bits = {12, 10, 10}, + .ppn_offset_bits = {0, 12, 22}, + .entry_width_bytes = 4, + .vpn_width_bits = 10 +}; + +static virtual_memory_system_t sv39 = { + .mode = SATP_MODE_SV39, + .levels = 3, + .ppn_width_bits = {12, 9, 9, 26}, + .ppn_offset_bits = {0, 12, 21, 30}, + .entry_width_bytes = 8, + .vpn_width_bits = 9 +}; + +static virtual_memory_system_t sv48 = { + .mode = SATP_MODE_SV48, + .levels = 4, + .ppn_width_bits = {12, 9, 9, 9, 26}, + .ppn_offset_bits = {0, 12, 21, 30, 39}, + .entry_width_bytes = 8, + .vpn_width_bits = 9 +}; + +static virtual_memory_system_t *vms; + +void error() +{ + while (1) + ; +} + +void assert(int condition) +{ + if (!condition) + error(); +} + +// Return a 4Kb, aligned, page. +void *get_page() +{ + page_buffer_next = (char *) (((unsigned long) page_buffer_next + 4095) & ~0xfff); + while (page_buffer_next + 4096 >= page_buffer + sizeof(page_buffer)) + ; + void *result = page_buffer_next; + page_buffer_next += 4096; + return result; +} + +reg_t entry(char *table, unsigned index) +{ + if (vms->entry_width_bytes == 4) + return ((uint32_t *) table)[index]; + else if (vms->entry_width_bytes == 8) + return ((uint64_t *) table)[index]; + else + assert(0); +} + +void entry_set(char *table, unsigned index, uint64_t value) +{ + if (vms->entry_width_bytes == 4) + ((uint32_t *) table)[index] = value; + else if (vms->entry_width_bytes == 8) + ((uint64_t *) table)[index] = value; + else + assert(0); +} + +// Set up 1-to-1 for this entire table. +void setup_page_table(char *table, unsigned level, uint64_t physical) +{ + for (unsigned i = 0; i < (1<<vms->vpn_width_bits); i++) { + uint64_t pte = PTE_V | PTE_R | PTE_W | PTE_X | PTE_U | PTE_A | PTE_D; + // Add in portion of physical address. + pte |= physical & (((1LL<<vms->vpn_width_bits)-1) << + (PTE_PPN_SHIFT + (level+1) * vms->vpn_width_bits)); + // Add in the index. + pte |= ((reg_t) i) << (PTE_PPN_SHIFT + level * vms->vpn_width_bits); + entry_set(table, i, pte); + } +} + +// Return contents of vpn field for the given virtual address and level. +unsigned vpn(uint64_t virtual, unsigned level) +{ + virtual >>= 12 + vms->vpn_width_bits * level; + return virtual & ((1<<vms->vpn_width_bits)-1); +} + +// Add an entry to the given table, at the given level (0 for 4Kb page). +void add_entry(char *table, unsigned level, uint64_t virtual, uint64_t physical) +{ + unsigned current_level = vms->levels - 1; + while (1) { + unsigned index = vpn(virtual, current_level); + if (current_level <= level) { + // Add the new entry. + entry_set(table, index, PTE_V | PTE_R | PTE_W | PTE_X | PTE_U | PTE_A | PTE_D | + ((physical >> 2) & ~((1 << + (PTE_PPN_SHIFT + current_level * vms->vpn_width_bits)) - 1))); + return; + } + reg_t pte = entry(table, index); + if (!(pte & PTE_V) || + ((pte & PTE_R) && (pte & PTE_W))) { + // Create a new page + void *new_page = get_page(); + setup_page_table(new_page, current_level - 1, virtual); + entry_set(table, index, PTE_V | PTE_U | PTE_A | PTE_D | + ((((reg_t) new_page) >> 2) & ~((1 << 10) - 1))); + table = new_page; + } else { + table = (char *) (pte & ~0xfff); + } + current_level--; + } +} + +int main() +{ + void *master_table = get_page(); + setup_page_table(master_table, vms->levels-1, 0); + uint32_t *physical = get_page(); + uint32_t *virtual = (uint32_t *) (((reg_t) physical) ^ ((reg_t) 0x40000000)); + add_entry(master_table, 0, (reg_t) virtual, (reg_t) physical); + + unsigned long satp = set_field(0, SATP_MODE, vms->mode); + satp = set_field(satp, SATP_PPN, ((unsigned long) master_table) >> 12); + write_csr(satp, satp); + + reg_t mstatus = read_csr(mstatus); + mstatus |= MSTATUS_MPRV; + write_csr(mstatus, mstatus); + + // Address translation is enabled. + physical[0] = 0xdeadbeef; + assert(virtual[0] == physical[0]); + virtual[1] = 0x55667788; + assert(virtual[1] == physical[1]); + +active: +end: + while (1) + ; +} |