Skip to content

Commit

Permalink
Support trap handling during block emulation
Browse files Browse the repository at this point in the history
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. A test suite shall be introduced with this PR, please do not
merge yet.

Related: sysprog21#310
  • Loading branch information
ChinYikMing committed Jun 24, 2024
1 parent 4bb8593 commit 06b8ef0
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 75 deletions.
2 changes: 2 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down
178 changes: 134 additions & 44 deletions src/emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

#include <assert.h>
#include <setjmp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
Expand Down Expand Up @@ -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 */ \
) \
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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; \
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);
}

Expand Down
3 changes: 3 additions & 0 deletions src/riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,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);

Expand Down
54 changes: 54 additions & 0 deletions src/riscv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
*
Expand Down
Loading

0 comments on commit 06b8ef0

Please sign in to comment.