diff --git a/test/sim/sw_testcases/pmp_u_rw.c b/test/sim/sw_testcases/pmp_u_rw.c new file mode 100644 index 0000000..4b691d4 --- /dev/null +++ b/test/sim/sw_testcases/pmp_u_rw.c @@ -0,0 +1,220 @@ +#include "tb_cxxrtl_io.h" +#include "hazard3_csr.h" +#include "pmp.h" + +// Check that PMP correctly controls U-mode R/W permissions, for all three +// access sizes. + +#define MCAUSE_LOAD_FAULT 5 +#define MCAUSE_STORE_FAULT 7 +#define MCAUSE_ECALL_UMODE 8 + +/*EXPECTED-OUTPUT*************************************************************** + +Initial word read/write check +mcause = 8 +mcause = 8 +Remove read permission, word read/write +mcause = 8 +mcause = 5 +Restore read permission, remove write permission, word read/write +mcause = 7 +mcause = 8 +Remove read permission, halfword read/write +mcause = 8 +mcause = 5 +Restore read permission, remove write permission, halfword read/write +mcause = 7 +mcause = 8 +Remove read permission, byte read/write +mcause = 8 +mcause = 5 +Restore read permission, remove write permission, byte read/write +mcause = 7 +mcause = 8 + +*******************************************************************************/ + +typedef uint32_t uxlen_t; +typedef uxlen_t (*umode_func_2a_1r)(uxlen_t, uxlen_t); + +// /!\ Unconventional control flow ahead + +// Call a function in U-mode, from M-mode, with two register arguments and one +// register result. If the function traps, mcause/mepc indicate the trap +// cause. Otherwise mcause is set to U-mode ecall (= 8). + +uxlen_t __attribute__((naked)) call_umode(umode_func_2a_1r f, uxlen_t a0, uxlen_t a1) { + asm ( + "addi sp, sp, -16 \n" + "sw s0, 0(sp) \n" + "sw ra, 4(sp) \n" + // Set up mret target address and mode + "csrw mepc, a0 \n" + "li a0, 0x1800 \n" + "csrc mstatus, a0 \n" + // Set up arguments + "mv a0, a1 \n" + "mv a1, a2 \n" + // Set up two return paths: trap -> mtvec, or ret -> ecall -> mtvec + "la s0, 1f \n" + "addi ra, s0, -4 \n" + "csrrw s0, mtvec, s0 \n" + // Call the function + "mret \n" + // Join return paths and restore mtvec + ".p2align 2 \n" + "ecall \n" + "1: \n" + "csrw mtvec, s0 \n" + // Then return via saved ra + "lw s0, 0(sp) \n" + "lw ra, 4(sp) \n" + "addi sp, sp, 16 \n" + "ret \n" + ); +} + +void __attribute__((naked)) do_sw(uxlen_t a, uxlen_t d) { + asm volatile ( + "sw a1, (a0)\n" + "ecall\n" + ); +} + +void __attribute__((naked)) do_sh(uxlen_t a, uxlen_t d) { + asm volatile ( + "sh a1, (a0)\n" + "ecall\n" + ); +} + +void __attribute__((naked)) do_sb(uxlen_t a, uxlen_t d) { + asm volatile ( + "sb a1, (a0)\n" + "ecall\n" + ); +} + +uxlen_t __attribute__((naked)) do_lw(uxlen_t a) { + asm volatile ( + "lw a0, (a0)\n" + "ecall" + ); +} + +uxlen_t __attribute__((naked)) do_lhu(uxlen_t a) { + asm volatile ( + "lhu a0, (a0)\n" + "ecall" + ); +} + +uxlen_t __attribute__((naked)) do_lbu(uxlen_t a) { + asm volatile ( + "lbu a0, (a0)\n" + "ecall" + ); +} + +volatile uint32_t scratch_word; + +int main() { + // We will keep PMP region 1 as an all-permission grant on all of memory, + // and then use PMP region 0 to alter the permissions of only the + // targeted scratch word. + write_pmpcfg(1, PMPCFG_A_NAPOT << PMPCFG_A_LSB | PMPCFG_R_BITS | PMPCFG_W_BITS | PMPCFG_X_BITS); + write_pmpaddr(1, -1u); + write_pmpaddr(0, (uint32_t)&scratch_word >> 2); + + // Region 1 is not yet active, so we should have full permissions on this word. + tb_puts("Initial word read/write check\n"); + scratch_word = 0; + (void)call_umode((umode_func_2a_1r)&do_sw, (uintptr_t)&scratch_word, 0x12345678); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(scratch_word == 0x12345678, "Failed to write\n"); + + uint32_t readback = call_umode((umode_func_2a_1r)&do_lw, (uintptr_t)&scratch_word, 0); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(readback == scratch_word, "Failed to read\n"); + + // Change permissions to WX, and repeat previous test, making sure we get + // the correct traps from the correct places, and trapped instructions + // have their side effects suppressed. + tb_puts("Remove read permission, word read/write\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_W_BITS | PMPCFG_X_BITS); + scratch_word = 0; + (void)call_umode((umode_func_2a_1r)&do_sw, (uintptr_t)&scratch_word, 0x12345678); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(scratch_word == 0x12345678, "Failed to write\n"); + + readback = call_umode((umode_func_2a_1r)&do_lw, (uintptr_t)&scratch_word, 0); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mepc) == (uint32_t)&do_lw, "Trap should come from load instruction\n"); + tb_assert(readback == (uintptr_t)&scratch_word, "Load instruction should not have written back\n"); + + // Now again, with permissions set to RX. + tb_puts("Restore read permission, remove write permission, word read/write\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_R_BITS | PMPCFG_X_BITS); + scratch_word = 0xdeadbeefu; + (void)call_umode((umode_func_2a_1r)&do_sw, (uintptr_t)&scratch_word, 0x12345678); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mepc) == (uint32_t)&do_sw, "Trap should come from store instruction\n"); + tb_assert(scratch_word == 0xdeadbeefu, "Should not have written to memory\n"); + + readback = call_umode((umode_func_2a_1r)&do_lw, (uintptr_t)&scratch_word, 0); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(readback == scratch_word, "Failed to read\n"); + + // Repeat previous two tests with halfword access at top of region + tb_puts("Remove read permission, halfword read/write\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_W_BITS | PMPCFG_X_BITS); + scratch_word = 0; + (void)call_umode((umode_func_2a_1r)&do_sh, (uintptr_t)&scratch_word + 2, 0x12345678); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(scratch_word == 0x56780000u, "Failed to write\n"); + + readback = call_umode((umode_func_2a_1r)&do_lhu, (uintptr_t)&scratch_word + 2, 0); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mepc) == (uint32_t)&do_lhu, "Trap should come from load instruction\n"); + tb_assert(readback == (uintptr_t)&scratch_word + 2, "Load instruction should not have written back\n"); + + tb_puts("Restore read permission, remove write permission, halfword read/write\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_R_BITS | PMPCFG_X_BITS); + scratch_word = 0xdeadbeefu; + (void)call_umode((umode_func_2a_1r)&do_sh, (uintptr_t)&scratch_word + 2, 0x12345678); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mepc) == (uint32_t)&do_sh, "Trap should come from store instruction\n"); + tb_assert(scratch_word == 0xdeadbeefu, "Should not have written to memory\n"); + + readback = call_umode((umode_func_2a_1r)&do_lhu, (uintptr_t)&scratch_word + 2, 0); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(readback == scratch_word >> 16, "Failed to read\n"); + + // Repeat previous two tests with byte access at top of region + tb_puts("Remove read permission, byte read/write\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_W_BITS | PMPCFG_X_BITS); + scratch_word = 0; + (void)call_umode((umode_func_2a_1r)&do_sb, (uintptr_t)&scratch_word + 3, 0x12345678); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(scratch_word == 0x78000000u, "Failed to write\n"); + + readback = call_umode((umode_func_2a_1r)&do_lbu, (uintptr_t)&scratch_word + 3, 0); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mepc) == (uint32_t)&do_lbu, "Trap should come from load instruction\n"); + tb_assert(readback == (uintptr_t)&scratch_word + 3, "Load instruction should not have written back\n"); + + tb_puts("Restore read permission, remove write permission, byte read/write\n"); + write_pmpcfg(0, PMPCFG_A_NA4 << PMPCFG_A_LSB | PMPCFG_R_BITS | PMPCFG_X_BITS); + scratch_word = 0xdeadbeefu; + (void)call_umode((umode_func_2a_1r)&do_sb, (uintptr_t)&scratch_word + 3, 0x12345678); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(read_csr(mepc) == (uint32_t)&do_sb, "Trap should come from store instruction\n"); + tb_assert(scratch_word == 0xdeadbeefu, "Should not have written to memory\n"); + + readback = call_umode((umode_func_2a_1r)&do_lbu, (uintptr_t)&scratch_word + 3, 0); + tb_printf("mcause = %u\n", read_csr(mcause)); + tb_assert(readback == scratch_word >> 24, "Failed to read\n"); + + return 0; +} diff --git a/test/sim/sw_testcases/umode_wfi.c b/test/sim/sw_testcases/umode_wfi.c index d00f79f..ac8b324 100644 --- a/test/sim/sw_testcases/umode_wfi.c +++ b/test/sim/sw_testcases/umode_wfi.c @@ -3,8 +3,9 @@ // Check that U-mode execution of a wfi causes an illegal opcode exception if // and only if the mstatus timeout wait bit is set. -// Also check that a U-mode WFI which fails PMP X check does not stall the -// procesor. +// +// Also check that a U-mode WFI which fails PMP X check or TW=1 check does not +// stall the processor. /*EXPECTED-OUTPUT***************************************************************