aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Newsome <tim@sifive.com>2020-01-09 15:42:05 -0800
committerGitHub <noreply@github.com>2020-01-09 15:42:05 -0800
commit6db6ab8680ea493bacbdd58adb2227894615afdc (patch)
treee03767f450ba2db4b4ea422fc6d19a7619a8c045
parentd081949d96d374949e3ac5b849608dda251825d0 (diff)
downloadriscv-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-xdebug/gdbserver.py49
-rw-r--r--debug/programs/init.c4
-rw-r--r--debug/programs/init.h8
-rw-r--r--debug/programs/interrupt.c4
-rw-r--r--debug/programs/multicore.c3
-rw-r--r--debug/programs/translate.c176
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)
+ ;
+}