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: | if len(args) > 0: | ||||||
| 	testlist = args | 	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: | else: | ||||||
| 	testlist = [] | 	testlist = [] | ||||||
| 	for path in os.listdir(): | 	for path in os.listdir(): | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue