Skip to content

Commit

Permalink
Preliminary support for MMU emulation
Browse files Browse the repository at this point in the history
To boot a 32-bit RISC-V Linux with MMU, MMU emulation support is essential.
The virtual memory scheme required is SV32. Major changes in this commit
include implementing the MMU-related riscv_io_t interface and binding it
during RISC-V instance initialization. To reuse the riscv_io_t interface,
its prototype is modified to allow access to the RISC-V core instance as
the first parameter, since MMU-enabled I/O requires access to the SATP CSR.
Additionally, a trap_handler callback is added to the riscv_io_t
interface to route the actual trap handler. This approach keeps the
dispatch_table and TRAP_HANDLER_IMPL static within emulate.c, aligning
the schema with other handlers like ebreak_handler and ecall_handler.
The SET_CAUSE_AND_TVAL_THEN_TRAP macro is introduced to simplify the
dispatch process when invoking a trap.

For each memory access, the page table is walked to get the
corresponding PTE. Depending on the PTE retrieval, several page faults
may need handling. Thus, three exception handlers have been introduced:
insn_pgfault, load_pgfault, and store_pgfault, used in MMU_CHECK_FAULT.
This commit does not fully handle access faults since they are related
to PMA and PMP, which may not be necessary for booting 32-bit RISC-V
Linux (possibly supported in the future).

Since Linux has not been booted yet, a test suite is needed to test the
MMU emulation. This commit includes a test suite that implements a
simple kernel space supervisor and a user space application. The
supervisor prepares the page table and then passes control to the user
space application to test the three aforementioned page faults.

Related: sysprog21#310
  • Loading branch information
ChinYikMing committed Oct 27, 2024
1 parent edb5a1b commit 3b1f10d
Show file tree
Hide file tree
Showing 17 changed files with 1,508 additions and 160 deletions.
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ CFLAGS += $(CFLAGS_NO_CET)

OBJS_EXT :=

ifeq ($(call has, SYSTEM), 1)
OBJS_EXT += system.o
endif

# Integer Multiplication and Division instructions
ENABLE_EXT_M ?= 1
$(call set-feature, EXT_M)
Expand Down
5 changes: 2 additions & 3 deletions src/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -907,9 +907,8 @@ static inline bool op_system(rv_insn_t *ir, const uint32_t insn)
default: /* illegal instruction */
return false;
}
if (!csr_is_writable(ir->imm) && ir->rs1 != rv_reg_zero)
return false;
return true;

return csr_is_writable(ir->imm) || (ir->rs1 == rv_reg_zero);
}

/* MISC-MEM: I-type
Expand Down
150 changes: 94 additions & 56 deletions src/emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include <emscripten.h>
#endif

#if RV32_HAS(SYSTEM)
#include "system.h"
#endif /* RV32_HAS(SYSTEM) */

#if RV32_HAS(EXT_F)
#include <math.h>
#include "softfloat.h"
Expand Down Expand Up @@ -41,47 +45,33 @@ extern struct target_ops gdbstub_ops;
#define IF_rs2(i, r) (i->rs2 == rv_reg_##r)
#define IF_imm(i, v) (i->imm == v)

/* RISC-V exception code list */
/* RISC-V trap code list */
/* clang-format off */
#define RV_TRAP_LIST \
IIF(RV32_HAS(EXT_C))(, \
_(insn_misaligned, 0) /* Instruction address misaligned */ \
) \
_(illegal_insn, 2) /* Illegal instruction */ \
_(breakpoint, 3) /* Breakpoint */ \
_(load_misaligned, 4) /* Load address misaligned */ \
_(store_misaligned, 6) /* Store/AMO address misaligned */ \
IIF(RV32_HAS(SYSTEM))(, \
_(ecall_M, 11) /* Environment call from M-mode */ \
#define RV_TRAP_LIST \
IIF(RV32_HAS(EXT_C))(, \
_(insn_misaligned) /* Instruction address misaligned */ \
) \
_(illegal_insn) /* Illegal instruction */ \
_(breakpoint) /* Breakpoint */ \
_(load_misaligned) /* Load address misaligned */ \
_(store_misaligned) /* Store/AMO address misaligned */ \
IIF(RV32_HAS(SYSTEM))( \
_(pagefault_insn) /* Instruction page fault */ \
_(pagefault_load) /* Load page fault */ \
_(pagefault_store), /* Store page fault */ \
_(ecall_M) /* Environment call from M-mode */ \
)
/* clang-format on */

enum {
#define _(type, code) rv_trap_code_##type = code,
RV_TRAP_LIST
#undef _
};

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.
*
* Since the system emulation has not yet included in rv32emu, the page
* fault is not practical in current test suite. Instead, we try to
* emulate the misaligned handling in the test suite.
*/
#if RV32_HAS(SYSTEM)
static void trap_handler(riscv_t *rv);
#endif
static void __trap_handler(riscv_t *rv);
#endif /* SYSTEM */

/* When a trap occurs in M-mode/S-mode, m/stval is either initialized to zero or
* populated with exception-specific details to assist software in managing
Expand All @@ -99,11 +89,9 @@ static void trap_handler(riscv_t *rv);
* it is worth noting that a future standard could redefine how m/stval is
* handled for different types of traps.
*
* For simplicity and clarity, abstracting stval and mtval into a single
* identifier called tval, as both are handled by TRAP_HANDLER_IMPL.
*/
#define TRAP_HANDLER_IMPL(type, code) \
static void rv_trap_##type(riscv_t *rv, uint32_t tval) \
#define TRAP_HANDLER_IMPL(type) \
static void rv_trap_##type(riscv_t *rv) \
{ \
/* m/stvec (Machine/Supervisor Trap-Vector Base Address Register) \
* m/stvec[MXLEN-1:2]: vector base address \
Expand All @@ -113,9 +101,12 @@ static void trap_handler(riscv_t *rv);
* 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 \
* \
* m/stval and m/scause are set in SET_CAUSE_AND_TVAL_THEN_TRAP \
*/ \
uint32_t base; \
uint32_t mode; \
uint32_t cause; \
/* user or supervisor */ \
if (RV_PRIV_IS_U_OR_S_MODE()) { \
const uint32_t sstatus_sie = \
Expand All @@ -126,9 +117,8 @@ static void trap_handler(riscv_t *rv);
rv->priv_mode = RV_PRIV_S_MODE; \
base = rv->csr_stvec & ~0x3; \
mode = rv->csr_stvec & 0x3; \
cause = rv->csr_scause; \
rv->csr_sepc = rv->PC; \
rv->csr_stval = tval; \
rv->csr_scause = code; \
} else { /* machine */ \
const uint32_t mstatus_mie = \
(rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT; \
Expand All @@ -138,9 +128,8 @@ static void trap_handler(riscv_t *rv);
rv->priv_mode = RV_PRIV_M_MODE; \
base = rv->csr_mtvec & ~0x3; \
mode = rv->csr_mtvec & 0x3; \
cause = rv->csr_mcause; \
rv->csr_mepc = rv->PC; \
rv->csr_mtval = tval; \
rv->csr_mcause = code; \
if (!rv->csr_mtvec) { /* in case CSR is not configured */ \
rv_trap_default_handler(rv); \
return; \
Expand All @@ -155,14 +144,14 @@ static void trap_handler(riscv_t *rv);
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)); \
rv->PC = base + 4 * (cause & MASK(31)); \
break; \
} \
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) trap_handler(rv);, ) \
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) __trap_handler(rv);, ) \
}

/* RISC-V exception handlers */
#define _(type, code) TRAP_HANDLER_IMPL(type, code)
#define _(type) TRAP_HANDLER_IMPL(type)
RV_TRAP_LIST
#undef _

Expand All @@ -180,8 +169,8 @@ RV_TRAP_LIST
rv->compressed = compress; \
rv->csr_cycle = cycle; \
rv->PC = PC; \
IIF(RV32_HAS(SYSTEM))(rv->is_trapped = true, ); \
rv_trap_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, type##_MISALIGNED, \
IIF(IO)(addr, mask_or_pc)); \
return false; \
}

Expand Down Expand Up @@ -531,8 +520,8 @@ static bool do_fuse3(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
*/
for (int i = 0; i < ir->imm2; i++) {
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
RV_EXC_MISALIGN_HANDLER(3, store, false, 1);
rv->io.mem_write_w(addr, rv->X[fuse[i].rs2]);
RV_EXC_MISALIGN_HANDLER(3, STORE, false, 1);
rv->io.mem_write_w(rv, addr, rv->X[fuse[i].rs2]);
}
PC += ir->imm2 * 4;
if (unlikely(RVOP_NO_NEXT(ir))) {
Expand All @@ -555,8 +544,8 @@ static bool do_fuse4(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
*/
for (int i = 0; i < ir->imm2; i++) {
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
RV_EXC_MISALIGN_HANDLER(3, load, false, 1);
rv->X[fuse[i].rd] = rv->io.mem_read_w(addr);
RV_EXC_MISALIGN_HANDLER(3, LOAD, false, 1);
rv->X[fuse[i].rd] = rv->io.mem_read_w(rv, addr);
}
PC += ir->imm2 * 4;
if (unlikely(RVOP_NO_NEXT(ir))) {
Expand Down Expand Up @@ -666,12 +655,12 @@ static void block_translate(riscv_t *rv, block_t *block)
prev_ir->next = ir;

/* fetch the next instruction */
const uint32_t insn = rv->io.mem_ifetch(block->pc_end);
const uint32_t insn = rv->io.mem_ifetch(rv, block->pc_end);

/* decode the instruction */
if (!rv_decode(ir, insn)) {
rv->compressed = is_compressed(insn);
rv_trap_illegal_insn(rv, insn);
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ILLEGAL_INSN, insn);
break;
}
ir->impl = dispatch_table[ir->opcode];
Expand Down Expand Up @@ -1122,15 +1111,14 @@ void rv_step(void *arg)
}

#if RV32_HAS(SYSTEM)
static void trap_handler(riscv_t *rv)
static void __trap_handler(riscv_t *rv)
{
rv_insn_t *ir = mpool_alloc(rv->block_ir_mp);
assert(ir);

/* set to false by sret/mret implementation */
uint32_t insn;
/* set to false by sret implementation */
while (rv->is_trapped && !rv_has_halted(rv)) {
insn = rv->io.mem_ifetch(rv->PC);
uint32_t insn = rv->io.mem_ifetch(rv, rv->PC);
assert(insn);

rv_decode(ir, insn);
Expand All @@ -1139,12 +1127,62 @@ static void trap_handler(riscv_t *rv)
ir->impl(rv, ir, rv->csr_cycle, rv->PC);
}
}
#endif
#endif /* SYSTEM */

static void _trap_handler(riscv_t *rv)
{
uint32_t cause = RV_PRIV_IS_U_OR_S_MODE() ? rv->csr_scause : rv->csr_mcause;

switch (cause) {
#if !RV32_HAS(EXT_C)
case INSN_MISALIGNED:
rv_trap_insn_misaligned(rv);
break;
#endif /* EXT_C */
case ILLEGAL_INSN:
rv_trap_illegal_insn(rv);
break;
case BREAKPOINT:
rv_trap_breakpoint(rv);
break;
case LOAD_MISALIGNED:
rv_trap_load_misaligned(rv);
break;
case STORE_MISALIGNED:
rv_trap_store_misaligned(rv);
break;
#if RV32_HAS(SYSTEM)
case PAGEFAULT_INSN:
rv_trap_pagefault_insn(rv);
break;
case PAGEFAULT_LOAD:
rv_trap_pagefault_load(rv);
break;
case PAGEFAULT_STORE:
rv_trap_pagefault_store(rv);
break;
#endif /* SYSTEM */
#if !RV32_HAS(SYSTEM)
case ECALL_M:
rv_trap_ecall_M(rv);
break;
#endif /* SYSTEM */
default:
__UNREACHABLE;
break;
}
}

void trap_handler(riscv_t *rv)
{
assert(rv);
_trap_handler(rv);
}

void ebreak_handler(riscv_t *rv)
{
assert(rv);
rv_trap_breakpoint(rv, rv->PC);
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, BREAKPOINT, rv->PC);
}

void ecall_handler(riscv_t *rv)
Expand All @@ -1154,7 +1192,7 @@ void ecall_handler(riscv_t *rv)
syscall_handler(rv);
rv->PC += 4;
#else
rv_trap_ecall_M(rv, 0);
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ECALL_M, 0);
syscall_handler(rv);
#endif
}
Expand Down
5 changes: 5 additions & 0 deletions src/feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,10 @@
#define RV32_FEATURE_T2C 0
#endif

/* System */
#ifndef RV32_FEATURE_SYSTEM
#define RV32_FEATURE_SYSTEM 0
#endif

/* Feature test macro */
#define RV32_HAS(x) RV32_FEATURE_##x
4 changes: 2 additions & 2 deletions src/gdbstub.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ static int rv_read_mem(void *args, size_t addr, size_t len, void *val)
* an invalid address. We may have to do error handling in the
* mem_read_* function directly.
*/
*((uint8_t *) val + i) = rv->io.mem_read_b(addr + i);
*((uint8_t *) val + i) = rv->io.mem_read_b(rv, addr + i);
}

return err;
Expand All @@ -66,7 +66,7 @@ static int rv_write_mem(void *args, size_t addr, size_t len, void *val)
riscv_t *rv = (riscv_t *) args;

for (size_t i = 0; i < len; i++)
rv->io.mem_write_b(addr + i, *((uint8_t *) val + i));
rv->io.mem_write_b(rv, addr + i, *((uint8_t *) val + i));

return 0;
}
Expand Down
9 changes: 9 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -217,12 +217,21 @@ int main(int argc, char **args)
.log_level = 0,
.run_flag = run_flag,
.profile_output_file = prof_out_file,
#if RV32_HAS(SYSTEM)
.data.system = malloc(sizeof(vm_system_t)),
#else
.data.user = malloc(sizeof(vm_user_t)),
#endif
.cycle_per_step = CYCLE_PER_STEP,
.allow_misalign = opt_misaligned,
};
#if RV32_HAS(SYSTEM)
assert(attr.data.system);
attr.data.system->elf_program = opt_prog_name;
#else
assert(attr.data.user);
attr.data.user->elf_program = opt_prog_name;
#endif

/* create the RISC-V runtime */
rv = rv_create(&attr);
Expand Down
Loading

0 comments on commit 3b1f10d

Please sign in to comment.