diff --git a/test/sim/sw_testcases/pmp_u_x.c b/test/sim/sw_testcases/pmp_u_x.c new file mode 100644 index 0000000..932a3bf --- /dev/null +++ b/test/sim/sw_testcases/pmp_u_x.c @@ -0,0 +1,179 @@ +#include "tb_cxxrtl_io.h" +#include "hazard3_csr.h" +#include "pmp.h" + +// Check that PMP correctly controls U-mode X permissions, with precedence to +// the lowest-numbered matching region. Check that partial region matches +// cause failure no matter the permission, unless there is a lower-numbered +// fully matching region. + +/*EXPECTED-OUTPUT*************************************************************** + +Initial +mcause = 1 +4-byte region +mcause = 11 +Large region +mcause = 11 +X under !X +mcause = 11 +!X under X +mcause = 1 +Full match under partial match, positive overhang +mcause = 11 +Partial match under full match, positive overhang +mcause = 1 +Full match under partial match, negative overhang +mcause = 11 +Partial match under full match, negative overhang +mcause = 1 + +*******************************************************************************/ + +static inline void enter_umode(void (*f)(void)) { + clear_csr(mstatus, 0x1800u); + write_csr(mepc, f); + // This function assumes that the U-mode callee will trap rather than + // returning via ra. + asm ( + "la s0, 1f\n" + "csrrw s0, mtvec, s0\n" + "mret\n" + ".p2align 2\n" + "1:\n" + "csrw mtvec, s0\n" + : : : "s0" + ); +} + +void __attribute__((aligned(4), naked)) do_ecall() { + asm ("ecall"); +} + +// A 2-byte nop followed by a 4-byte nop, so that the second nop is only +// 2-byte-aligned. Useful for checking partial match detection. +void __attribute__((aligned(4), naked)) do_nops() { + asm ( + "c.nop\n" + ".option push\n" + ".option norvc\n" + "nop\n" + ".option pop\n" + "ecall\n" + ); +} + +#define MCAUSE_INSTR_FAULT 1 +#define MCAUSE_ECALL 11 + +int main() { + // Initially, there are no permissions active in the PMP, so we should get + // an instruction access fault at the entry point. + tb_puts("Initial\n"); + enter_umode(&do_ecall); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_INSTR_FAULT, "Should get instruction fault when no X permission\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_ecall, "Bad mepc\n"); + + // Setting a 4-byte X region on the ecall should let us call it in U mode. + tb_puts("4-byte region\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(0, (uint32_t)&do_ecall >> 2); + + enter_umode(&do_ecall); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_ECALL, "Should successfully execute ecall\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_ecall, "Bad mepc\n"); + + // Make the region larger (all of memory) -- should still pass. + tb_puts("Large region\n"); + write_pmpcfg(0, PMPCFG_A_NAPOT << PMPCFG_A_LSB |PMPCFG_X_BITS); + write_pmpaddr(0, -1u); + + enter_umode(&do_ecall); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_ECALL, "Should successfully execute ecall\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_ecall, "Bad mepc\n"); + + // Put another region on top, with no permissions -- should still pass + // because lower-numbered regions take precedence. + tb_puts("X under !X\n"); + write_pmpcfg(0, PMPCFG_A_NAPOT << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(0, -1u); + write_pmpcfg(1, PMPCFG_A_NAPOT << PMPCFG_A_LSB); + write_pmpaddr(1, -1u); + + enter_umode(&do_ecall); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_ECALL, "Should successfully execute ecall\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_ecall, "Bad mepc\n"); + + // Swap the two regions. Should now fail, because lower-numbered region + // revokes permissions of higher-numbered region. + tb_puts("!X under X\n"); + write_pmpcfg(1, PMPCFG_A_NAPOT << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpcfg(0, PMPCFG_A_NAPOT << PMPCFG_A_LSB); + + enter_umode(&do_ecall); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_INSTR_FAULT, "Should get instruction fault when no X permission\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_ecall, "Bad mepc\n"); + + // Now we'll use two regions, both with X permission, one matching all of + // memory and one matching the first 4 bytes of a function. The function + // is crafted to have a 4-byte instruction starting 2 bytes into the + // function, so will partially match the small region. + + // First of all: small region at higher region number. Should pass, + // because the lower-numbered region overrides the bad match. + tb_puts("Full match under partial match, positive overhang\n"); + write_pmpcfg(0, PMPCFG_A_NAPOT << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(0, -1u); + write_pmpcfg(1, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(1, (uint32_t)&do_nops >> 2); + + enter_umode(&do_nops); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_ECALL, "Should successfully execute ecall\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_nops + 6, "Bad mepc\n"); + + // Now swap the order of the regions. The partial match of the + // lower-numbered region should override its permissions and the + // permissions of the higher-numbered region. + tb_puts("Partial match under full match, positive overhang\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(0, (uint32_t)&do_nops >> 2); + write_pmpcfg(1, PMPCFG_A_NAPOT << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(1, -1u); + + enter_umode(&do_nops); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_INSTR_FAULT, "Should get instruction fault on partial match\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_nops + 2, "Bad mepc\n"); + + // Now do the same two tests, but with the partial match on the second + // half of the instruction rather than the first half + tb_puts("Full match under partial match, negative overhang\n"); + write_pmpcfg(0, PMPCFG_A_NAPOT << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(0, -1u); + write_pmpcfg(1, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(1, ((uint32_t)&do_nops + 4) >> 2); + + enter_umode(&do_nops); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_ECALL, "Should successfully execute ecall\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_nops + 6, "Bad mepc\n"); + + tb_puts("Partial match under full match, negative overhang\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(0, ((uint32_t)&do_nops + 4) >> 2); + write_pmpcfg(1, PMPCFG_A_NAPOT << PMPCFG_A_LSB | PMPCFG_X_BITS); + write_pmpaddr(1, -1u); + + enter_umode(&do_nops); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mcause) == MCAUSE_INSTR_FAULT, "Should get instruction fault on partial match\n"); + tb_assert(read_csr(mepc) == (uint32_t)&do_nops + 2, "Bad mepc\n"); + + return 0; +} diff --git a/test/sim/sw_testcases/runtests b/test/sim/sw_testcases/runtests index 1f2b852..564cd86 100755 --- a/test/sim/sw_testcases/runtests +++ b/test/sim/sw_testcases/runtests @@ -13,6 +13,10 @@ if "--vcd" in args: if len(args) > 0: testlist = args + # This happens a lot when autocomplete is used: + for i, n in enumerate(testlist): + if n.endswith(".c"): + testlist[i] = n[:-2] else: testlist = [] for path in os.listdir():