Add test for U-mode X permissions
This commit is contained in:
		
							parent
							
								
									e2b9a3b2f9
								
							
						
					
					
						commit
						399dcf2cb9
					
				|  | @ -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; | ||||
| } | ||||
|  | @ -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(): | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue