From 947f3a26feedd5244a7b9ef59773b30449f0cbee Mon Sep 17 00:00:00 2001 From: ChinYikMing Date: Mon, 24 Jun 2024 09:07:05 +0800 Subject: [PATCH] Support trap handling during block emulation Trap might occurs during block emulation, for example, page fault. In order to handle trap, we have to temporarily escape from block and jump to the trap handler to handle the trap PC by PC. Once trap is handled, resume the previous execution flow where cause the trap. To support this, we leverage setjmp function to store the state and a flag called is_trapped to indicate the emulation is in a trap handling mode currently. The sret and mret implementation are refactored to control the corresponding CSR before get out from trap. Some S-mode CSRs are added to riscv_internal to support S-mode. S-mode, and M-mode CSR helper macros are also introduced, so that the CSR can be manipulated in an easier manner. A test suite shall be introduced with this PR, please do not merge yet. Related: #310 --- src/common.h | 2 + src/emulate.c | 178 +++++++++++++++++++++++++++++++++----------- src/riscv.c | 3 + src/riscv.h | 54 ++++++++++++++ src/riscv_private.h | 92 ++++++++++++++++------- src/rv32_template.c | 23 +++++- 6 files changed, 277 insertions(+), 75 deletions(-) diff --git a/src/common.h b/src/common.h index f6337f8d..da84ebdb 100644 --- a/src/common.h +++ b/src/common.h @@ -25,6 +25,8 @@ #define ARRAYS_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) +#define MASK(n) (~((~0U << (n)))) + /* Alignment macro */ #if defined(__GNUC__) || defined(__clang__) #define __ALIGNED(x) __attribute__((aligned(x))) diff --git a/src/emulate.c b/src/emulate.c index a8856afb..f89ef134 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -43,7 +44,7 @@ extern struct target_ops gdbstub_ops; /* RISC-V exception code list */ /* clang-format off */ -#define RV_EXCEPTION_LIST \ +#define RV_TRAP_LIST \ IIF(RV32_HAS(EXT_C))(, \ _(insn_misaligned, 0) /* Instruction address misaligned */ \ ) \ @@ -55,17 +56,32 @@ extern struct target_ops gdbstub_ops; /* clang-format on */ enum { -#define _(type, code) rv_exception_code##type = code, - RV_EXCEPTION_LIST +#define _(type, code) rv_trap_code_##type = code, + RV_TRAP_LIST #undef _ }; -static void rv_exception_default_handler(riscv_t *rv) +static void rv_trap_default_handler(riscv_t *rv) { rv->csr_mepc += rv->compressed ? 2 : 4; rv->PC = rv->csr_mepc; /* mret */ } +/* Trap might occurs during block emulation. For instance, page fault. + * In order to handle trap, we have to escape from block and execute + * registered trap handler. This trap_handler function helps to execute + * the registered trap handler, PC by PC. Once the trap is handled, + * resume the previous execution flow where cause the trap. + */ +#if RV32_HAS(SYSTEM) +static void trap_handler(riscv_t *rv); +#else +/* should not be called in non-SYSTEM mode since default trap handler is capable + * to handle traps + */ +static void trap_handler(riscv_t *rv UNUSED) {} +#endif + /* When a trap occurs in M-mode, mtval is either initialized to zero or * populated with exception-specific details to assist software in managing * the trap. Otherwise, the implementation never modifies mtval, although @@ -82,43 +98,80 @@ static void rv_exception_default_handler(riscv_t *rv) * it is worth noting that a future standard could redefine how mtval is * handled for different types of traps. */ -#define EXCEPTION_HANDLER_IMPL(type, code) \ - static void rv_except_##type(riscv_t *rv, uint32_t mtval) \ - { \ - /* mtvec (Machine Trap-Vector Base Address Register) \ - * mtvec[MXLEN-1:2]: vector base address \ - * mtvec[1:0] : vector mode \ - */ \ - const uint32_t base = rv->csr_mtvec & ~0x3; \ - const uint32_t mode = rv->csr_mtvec & 0x3; \ - /* mepc (Machine Exception Program Counter) \ - * mtval (Machine Trap Value Register) \ - * mcause (Machine Cause Register): store exception code \ - * mstatus (Machine Status Register): keep track of and controls the \ - * hart’s current operating state \ - */ \ - rv->csr_mepc = rv->PC; \ - rv->csr_mtval = mtval; \ - rv->csr_mcause = code; \ - rv->csr_mstatus = MSTATUS_MPP; /* set privilege mode */ \ - if (!rv->csr_mtvec) { /* in case CSR is not configured */ \ - rv_exception_default_handler(rv); \ - return; \ - } \ - switch (mode) { \ - case 0: /* DIRECT: All exceptions set PC to base */ \ - rv->PC = base; \ - break; \ - /* VECTORED: Asynchronous interrupts set PC to base + 4 * code */ \ - case 1: \ - rv->PC = base + 4 * code; \ - break; \ - } \ +static jmp_buf env; +#define TRAP_HANDLER_IMPL(type, code) \ + static void rv_trap_##type(riscv_t *rv, uint32_t mtval) \ + { \ + /* m/stvec (Machine/Supervisor Trap-Vector Base Address Register) \ + * m/stvec[MXLEN-1:2]: vector base address \ + * m/stvec[1:0] : vector mode \ + */ \ + uint32_t base; \ + uint32_t mode; \ + /* m/sepc (Machine/Supervisor Exception Program Counter) \ + * m/stval (Machine/Supervisor Trap Value Register) \ + * m/scause (Machine/Supervisor Cause Register): store exception code \ + * m/sstatus (Machine/Supervisor Status Register): keep track of and \ + * controls the hart’s current operating state \ + */ \ + /* supervisor */ \ + if (rv->csr_medeleg & (1U << code) || \ + rv->csr_mideleg & (1U << code)) { \ + const uint32_t sstatus_sie = \ + (rv->csr_sstatus & SSTATUS_SIE) >> SSTATUS_SIE_SHIFT; \ + rv->csr_sstatus |= (sstatus_sie << SSTATUS_SPIE_SHIFT); \ + rv->csr_sstatus &= ~(SSTATUS_SIE); \ + rv->csr_sstatus |= (rv->priv_mode << SSTATUS_SPP_SHIFT); \ + rv->priv_mode = RV_PRIV_S_MODE; \ + base = rv->csr_stvec & ~0x3; \ + mode = rv->csr_stvec & 0x3; \ + rv->csr_sepc = rv->PC; \ + rv->csr_stval = mtval; \ + rv->csr_scause = code; \ + rv->csr_sstatus |= SSTATUS_SPP; /* set privilege mode */ \ + } else { /* machine */ \ + const uint32_t mstatus_mie = \ + (rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT; \ + rv->csr_mstatus |= (mstatus_mie << MSTATUS_MPIE_SHIFT); \ + rv->csr_mstatus &= ~(MSTATUS_MIE); \ + rv->csr_mstatus |= (rv->priv_mode << MSTATUS_MPP_SHIFT); \ + rv->priv_mode = RV_PRIV_M_MODE; \ + base = rv->csr_mtvec & ~0x3; \ + mode = rv->csr_mtvec & 0x3; \ + rv->csr_mepc = rv->PC; \ + rv->csr_mtval = mtval; \ + rv->csr_mcause = code; \ + rv->csr_mstatus |= MSTATUS_MPP; /* set privilege mode */ \ + if (!rv->csr_mtvec) { /* in case CSR is not configured */ \ + rv_trap_default_handler(rv); \ + return; \ + } \ + } \ + switch (mode) { \ + /* DIRECT: All traps set PC to base */ \ + case 0: \ + rv->PC = base; \ + break; \ + /* VECTORED: Asynchronous traps set PC to base + 4 * code */ \ + case 1: \ + /* MSB of code is used to indicate whether the trap is interrupt \ + * or exception, so it is not considered as the 'real' code */ \ + rv->PC = base + 4 * (code & MASK(31)); \ + break; \ + } \ + /* block escaping for trap handling */ \ + if (rv->is_trapped) { \ + if (setjmp(env) == 0) { \ + trap_handler(rv); \ + } else { \ + fprintf(stderr, "setjmp failed"); \ + } \ + } \ } /* RISC-V exception handlers */ -#define _(type, code) EXCEPTION_HANDLER_IMPL(type, code) -RV_EXCEPTION_LIST +#define _(type, code) TRAP_HANDLER_IMPL(type, code) +RV_TRAP_LIST #undef _ /* wrap load/store and insn misaligned handler @@ -135,7 +188,7 @@ RV_EXCEPTION_LIST rv->compressed = compress; \ rv->csr_cycle = cycle; \ rv->PC = PC; \ - rv_except_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \ + rv_trap_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \ return false; \ } @@ -196,6 +249,26 @@ static uint32_t *csr_get_ptr(riscv_t *rv, uint32_t csr) case CSR_FCSR: return (uint32_t *) (&rv->csr_fcsr); #endif + case CSR_SSTATUS: + return (uint32_t *) (&rv->csr_sstatus); + case CSR_SIE: + return (uint32_t *) (&rv->csr_sie); + case CSR_STVEC: + return (uint32_t *) (&rv->csr_stvec); + case CSR_SCOUNTEREN: + return (uint32_t *) (&rv->csr_scounteren); + case CSR_SSCRATCH: + return (uint32_t *) (&rv->csr_sscratch); + case CSR_SEPC: + return (uint32_t *) (&rv->csr_sepc); + case CSR_SCAUSE: + return (uint32_t *) (&rv->csr_scause); + case CSR_STVAL: + return (uint32_t *) (&rv->csr_stval); + case CSR_SIP: + return (uint32_t *) (&rv->csr_sip); + case CSR_SATP: + return (uint32_t *) (&rv->csr_satp); default: return NULL; } @@ -377,9 +450,9 @@ enum { }; #if RV32_HAS(GDBSTUB) -#define RVOP_NO_NEXT(ir) (!ir->next | rv->debug_mode) +#define RVOP_NO_NEXT(ir) (!ir->next | rv->debug_mode | rv->is_trapped) #else -#define RVOP_NO_NEXT(ir) (!ir->next) +#define RVOP_NO_NEXT(ir) (!ir->next | rv->is_trapped) #endif /* record whether the branch is taken or not during emulation */ @@ -598,7 +671,7 @@ static void block_translate(riscv_t *rv, block_t *block) /* decode the instruction */ if (!rv_decode(ir, insn)) { rv->compressed = is_compressed(insn); - rv_except_illegal_insn(rv, insn); + rv_trap_illegal_insn(rv, insn); break; } ir->impl = dispatch_table[ir->opcode]; @@ -1048,16 +1121,33 @@ void rv_step(void *arg) #endif } +#if RV32_HAS(SYSTEM) +static void trap_handler(riscv_t *rv) +{ + rv_insn_t *ir = mpool_alloc(rv->block_ir_mp); + assert(ir); + + uint32_t insn; + while (rv->is_trapped) { /* set to false by sret/mret implementation */ + insn = rv->io.mem_ifetch(rv, rv->PC); + rv_decode(ir, insn); + ir->impl = dispatch_table[ir->opcode]; + rv->compressed = is_compressed(insn); + ir->impl(rv, ir, rv->csr_cycle, rv->PC); + } +} +#endif + void ebreak_handler(riscv_t *rv) { assert(rv); - rv_except_breakpoint(rv, rv->PC); + rv_trap_breakpoint(rv, rv->PC); } void ecall_handler(riscv_t *rv) { assert(rv); - rv_except_ecall_M(rv, 0); + rv_trap_ecall_M(rv, 0); syscall_handler(rv); } diff --git a/src/riscv.c b/src/riscv.c index f163e854..ba35b192 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -221,6 +221,9 @@ riscv_t *rv_create(riscv_user_t rv_attr) attr->mem = memory_new(attr->mem_size); assert(attr->mem); + /* not being trapped */ + rv->is_trapped = false; + /* reset */ rv_reset(rv, 0U); diff --git a/src/riscv.h b/src/riscv.h index c4b7091b..97f245b3 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -96,6 +96,60 @@ enum { #define MSTATUS_MPIE (1 << MSTATUS_MPIE_SHIFT) #define MSTATUS_MPP (3 << MSTATUS_MPP_SHIFT) +/* The mstatus register keeps track of and controls the hart’s current operating + * state */ +#define MSTATUS_SIE_SHIFT 1 +#define MSTATUS_MIE_SHIFT 3 +#define MSTATUS_SPIE_SHIFT 5 +#define MSTATUS_UBE_SHIFT 6 +#define MSTATUS_MPIE_SHIFT 7 +#define MSTATUS_SPP_SHIFT 8 +#define MSTATUS_MPP_SHIFT 11 +#define MSTATUS_MPRV_SHIFT 17 +#define MSTATUS_SUM_SHIFT 18 +#define MSTATUS_MXR_SHIFT 18 +#define MSTATUS_TVM_SHIFT 20 +#define MSTATUS_TW_SHIFT 21 +#define MSTATUS_TSR_SHIFT 22 +#define MSTATUS_SIE (1 << MSTATUS_SIE_SHIFT) +#define MSTATUS_MIE (1 << MSTATUS_MIE_SHIFT) +#define MSTATUS_SPIE (1 << MSTATUS_SPIE_SHIFT) +#define MSTATUS_UBE (1 << MSTATUS_UBE_SHIFT) +#define MSTATUS_MPIE (1 << MSTATUS_MPIE_SHIFT) +#define MSTATUS_SPP (1 << MSTATUS_SPP_SHIFT) +#define MSTATUS_MPP (3 << MSTATUS_MPP_SHIFT) +#define MSTATUS_MPRV (1 << MSTATUS_MPRV_SHIFT) +#define MSTATUS_SUM (1 << MSTATUS_SUM_SHIFT) +#define MSTATUS_MXR (1 << MSTATUS_MXR_SHIFT) +#define MSTATUS_TVM (1 << MSTATUS_TVM_SHIFT) +#define MSTATUS_TW (1 << MSTATUS_TW_SHIFT) +#define MSTATUS_TSR (1 << MSTATUS_TSR_SHIFT) + +/* A restricted view of mstatus */ +#define SSTATUS_SIE_SHIFT 1 +#define SSTATUS_SPIE_SHIFT 5 +#define SSTATUS_UBE_SHIFT 6 +#define SSTATUS_SPP_SHIFT 8 +#define SSTATUS_SUM_SHIFT 18 +#define SSTATUS_MXR_SHIFT 19 +#define SSTATUS_SIE (1 << SSTATUS_SIE_SHIFT) +#define SSTATUS_SPIE (1 << SSTATUS_SPIE_SHIFT) +#define SSTATUS_UBE (1 << SSTATUS_UBE_SHIFT) +#define SSTATUS_SPP (1 << SSTATUS_SPP_SHIFT) +#define SSTATUS_SUM (1 << SSTATUS_SUM_SHIFT) +#define SSTATUS_MXR (1 << SSTATUS_MXR_SHIFT) + +#define SIP_SSIP_SHIFT 1 +#define SIP_STIP_SHIFT 5 +#define SIP_SEIP_SHIFT 9 +#define SIP_SSIP (1 << SIP_SSIP_SHIFT) +#define SIP_STIP (1 << SIP_STIP_SHIFT) +#define SIP_SEIP (1 << SIP_SEIP_SHIFT) + +#define RV_PRIV_U_MODE 0 +#define RV_PRIV_S_MODE 1 +#define RV_PRIV_M_MODE 3 + /* * SBI functions must return a pair of values: * diff --git a/src/riscv_private.h b/src/riscv_private.h index 5ca740a1..ca2a3295 100644 --- a/src/riscv_private.h +++ b/src/riscv_private.h @@ -25,9 +25,31 @@ /* CSRs */ enum { /* floating point */ - CSR_FFLAGS = 0x001, /* Floating-point accrued exceptions */ - CSR_FRM = 0x002, /* Floating-point dynamic rounding mode */ - CSR_FCSR = 0x003, /* Floating-point control and status register */ + CSR_FFLAGS = 0x001, /* Floating-point accrued exceptions */ + CSR_FRM = 0x002, /* Floating-point dynamic rounding mode */ + CSR_FCSR = 0x003, /* Floating-point control and status register */ + + /* Supervisor trap setup */ + CSR_SSTATUS = 0x100, /* Supervisor status register */ + CSR_SIE = 0x104, /* Supervisor interrupt-enable register */ + CSR_STVEC = 0x105, /* Supervisor trap-handler base address */ + CSR_SCOUNTEREN = 0x106, /* Supervisor counter enable */ + + /* Supervisor trap handling */ + CSR_SSCRATCH = 0x140, /* Supervisor register for machine trap handlers */ + CSR_SEPC = 0x141, /* Supervisor exception program counter */ + CSR_SCAUSE = 0x142, /* Supervisor trap cause */ + CSR_STVAL = 0x143, /* Supervisor bad address or instruction */ + CSR_SIP = 0x144, /* Supervisor interrupt pending */ + + /* Supervisor protection and translation */ + CSR_SATP = 0x180, /* Supervisor address translation and protection */ + + /* Machine information registers */ + CSR_MVENDORID = 0xF11, /* Vendor ID */ + CSR_MARCHID = 0xF12, /* Architecture ID */ + CSR_MIMPID = 0xF13, /* Implementation ID */ + CSR_MHARTID = 0xF14, /* Hardware thread ID */ /* Machine trap setup */ CSR_MSTATUS = 0x300, /* Machine status register */ @@ -39,26 +61,21 @@ enum { CSR_MCOUNTEREN = 0x306, /* Machine counter enable */ /* machine trap handling */ - CSR_MSCRATCH = 0x340, /* Scratch register for machine trap handlers */ - CSR_MEPC = 0x341, /* Machine exception program counter */ - CSR_MCAUSE = 0x342, /* Machine trap cause */ - CSR_MTVAL = 0x343, /* Machine bad address or instruction */ - CSR_MIP = 0x344, /* Machine interrupt pending */ + CSR_MSCRATCH = 0x340, /* Scratch register for machine trap handlers */ + CSR_MEPC = 0x341, /* Machine exception program counter */ + CSR_MCAUSE = 0x342, /* Machine trap cause */ + CSR_MTVAL = 0x343, /* Machine bad address or instruction */ + CSR_MIP = 0x344, /* Machine interrupt pending */ /* low words */ - CSR_CYCLE = 0xC00, /* Cycle counter for RDCYCLE instruction */ - CSR_TIME = 0xC01, /* Timer for RDTIME instruction */ + CSR_CYCLE = 0xC00, /* Cycle counter for RDCYCLE instruction */ + CSR_TIME = 0xC01, /* Timer for RDTIME instruction */ CSR_INSTRET = 0xC02, /* high words */ CSR_CYCLEH = 0xC80, CSR_TIMEH = 0xC81, CSR_INSTRETH = 0xC82, - - CSR_MVENDORID = 0xF11, /* Vendor ID */ - CSR_MARCHID = 0xF12, /* Architecture ID */ - CSR_MIMPID = 0xF13, /* Implementation ID */ - CSR_MHARTID = 0xF14, /* Hardware thread ID */ }; /* translated basic block */ @@ -125,21 +142,37 @@ struct riscv_internal { #endif /* csr registers */ - uint64_t csr_cycle; /* Machine cycle counter */ - uint32_t csr_time[2]; /* Performance counter */ - uint32_t csr_mstatus; /* Machine status register */ - uint32_t csr_mtvec; /* Machine trap-handler base address */ - uint32_t csr_misa; /* ISA and extensions */ - uint32_t csr_mtval; /* Machine bad address or instruction */ - uint32_t csr_mcause; /* Machine trap cause */ - uint32_t csr_mscratch; /* Scratch register for machine trap handler */ - uint32_t csr_mepc; /* Machine exception program counter */ - uint32_t csr_mip; /* Machine interrupt pending */ - uint32_t csr_mvendorid; /* vendor ID */ - uint32_t csr_marchid; /* Architecture ID */ - uint32_t csr_mimpid; /* Implementation ID */ + uint64_t csr_cycle; /* Machine cycle counter */ + uint32_t csr_time[2]; /* Performance counter */ + uint32_t csr_mstatus; /* Machine status register */ + uint32_t csr_mtvec; /* Machine trap-handler base address */ + uint32_t csr_misa; /* ISA and extensions */ + uint32_t csr_mtval; /* Machine bad address or instruction */ + uint32_t csr_mcause; /* Machine trap cause */ + uint32_t csr_mscratch; /* Scratch register for machine trap handler */ + uint32_t csr_mepc; /* Machine exception program counter */ + uint32_t csr_mip; /* Machine interrupt pending */ + uint32_t csr_mie; /* Machine interrupt enable */ + uint32_t csr_mideleg; /* Machine interrupt delegation register */ + uint32_t csr_medeleg; /* Machine exception delegation register */ + uint32_t csr_mvendorid; /* vendor ID */ + uint32_t csr_marchid; /* Architecture ID */ + uint32_t csr_mimpid; /* Implementation ID */ uint32_t csr_mbadaddr; + uint32_t csr_sstatus; /* supervisor status register */ + uint32_t csr_stvec; /* supervisor trap vector base address register */ + uint32_t csr_sip; /* supervisor interrupt pending register */ + uint32_t csr_sie; /* supervisor interrupt enable register */ + uint32_t csr_scounteren; /* supervisor counter-enable register */ + uint32_t csr_sscratch; /* supervisor scratch register */ + uint32_t csr_sepc; /* supervisor exception program counter */ + uint32_t csr_scause; /* supervisor cause register */ + uint32_t csr_stval; /* supervisor trap value register */ + uint32_t csr_satp; /* supervisor address translation and protection */ + + uint32_t priv_mode; /* U-mode or S-mode or M-mode */ + bool compressed; /**< current instruction is compressed or not */ #if !RV32_HAS(JIT) block_map_t block_map; /**< basic block map */ @@ -170,6 +203,9 @@ struct riscv_internal { */ bool is_interrupted; #endif + + /* The flag to indicate the current emulation is in a trap */ + bool is_trapped; }; /* sign extend a 16 bit value */ diff --git a/src/rv32_template.c b/src/rv32_template.c index c021ae0e..93ca0e58 100644 --- a/src/rv32_template.c +++ b/src/rv32_template.c @@ -1007,8 +1007,17 @@ RVOP( RVOP( sret, { - /* FIXME: Implement */ - return false; + rv->is_trapped = false; + rv->priv_mode = (rv->csr_sstatus & MSTATUS_SPP) >> MSTATUS_SPP_SHIFT; + rv->csr_sstatus &= ~(MSTATUS_SPP); + + const uint32_t sstatus_spie = + (rv->csr_sstatus & SSTATUS_SPIE) >> SSTATUS_SPIE_SHIFT; + rv->csr_sstatus |= (sstatus_spie << SSTATUS_SIE_SHIFT); + rv->csr_sstatus |= SSTATUS_SPIE; + + rv->PC = rv->csr_sepc; + return true; }, GEN({ assert; /* FIXME: Implement */ @@ -1029,7 +1038,15 @@ RVOP( RVOP( mret, { - rv->csr_mstatus = MSTATUS_MPIE; + rv->is_trapped = false; + rv->priv_mode = (rv->csr_mstatus & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT; + rv->csr_mstatus &= ~(MSTATUS_MPP); + + const uint32_t mstatus_mpie = + (rv->csr_mstatus & MSTATUS_MPIE) >> MSTATUS_MPIE_SHIFT; + rv->csr_mstatus |= (mstatus_mpie << MSTATUS_MIE_SHIFT); + rv->csr_mstatus |= MSTATUS_MPIE; + rv->PC = rv->csr_mepc; return true; },