Skip to content

Commit 3ccd202

Browse files
committed
Preliminary support for MMU emulation
To boot a 32-bit RISC-V Linux with MMU, MMU emulation support is crucial, using the SV32 virtual memory scheme. This commit’s main changes include implementing the MMU-related riscv_io_t interface and binding it during RISC-V instance initialization. To enable reuse of riscv_io_t, its prototype is modified to accept the RISC-V core instance as the first parameter, allowing MMU-enabled I/O to access the SATP CSR. A trap_handler callback is also added to the riscv_io_t interface. This keeps the dispatch_table and _trap_handler static within emulate.c, aligning the schema with other handlers, like ebreak_handler and ecall_handler. With m/scause and m/stval set in SET_CAUSE_AND_TVAL_THEN_TRAP, _trap_handler can immediately dispatch without rechecking scause, avoiding the need for additional call of specific type of trap handler, thus no more need of TRAP_HANDLER_IMPL. 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: #310
1 parent edb5a1b commit 3ccd202

File tree

16 files changed

+1512
-236
lines changed

16 files changed

+1512
-236
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ CFLAGS += $(CFLAGS_NO_CET)
4545

4646
OBJS_EXT :=
4747

48+
ifeq ($(call has, SYSTEM), 1)
49+
OBJS_EXT += system.o
50+
endif
51+
4852
# Integer Multiplication and Division instructions
4953
ENABLE_EXT_M ?= 1
5054
$(call set-feature, EXT_M)

src/decode.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -907,9 +907,8 @@ static inline bool op_system(rv_insn_t *ir, const uint32_t insn)
907907
default: /* illegal instruction */
908908
return false;
909909
}
910-
if (!csr_is_writable(ir->imm) && ir->rs1 != rv_reg_zero)
911-
return false;
912-
return true;
910+
911+
return csr_is_writable(ir->imm) || (ir->rs1 == rv_reg_zero);
913912
}
914913

915914
/* MISC-MEM: I-type

src/emulate.c

Lines changed: 98 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -41,130 +41,15 @@ extern struct target_ops gdbstub_ops;
4141
#define IF_rs2(i, r) (i->rs2 == rv_reg_##r)
4242
#define IF_imm(i, v) (i->imm == v)
4343

44-
/* RISC-V exception code list */
45-
/* clang-format off */
46-
#define RV_TRAP_LIST \
47-
IIF(RV32_HAS(EXT_C))(, \
48-
_(insn_misaligned, 0) /* Instruction address misaligned */ \
49-
) \
50-
_(illegal_insn, 2) /* Illegal instruction */ \
51-
_(breakpoint, 3) /* Breakpoint */ \
52-
_(load_misaligned, 4) /* Load address misaligned */ \
53-
_(store_misaligned, 6) /* Store/AMO address misaligned */ \
54-
IIF(RV32_HAS(SYSTEM))(, \
55-
_(ecall_M, 11) /* Environment call from M-mode */ \
56-
)
57-
/* clang-format on */
58-
59-
enum {
60-
#define _(type, code) rv_trap_code_##type = code,
61-
RV_TRAP_LIST
62-
#undef _
63-
};
64-
6544
static void rv_trap_default_handler(riscv_t *rv)
6645
{
6746
rv->csr_mepc += rv->compressed ? 2 : 4;
6847
rv->PC = rv->csr_mepc; /* mret */
6948
}
7049

71-
/*
72-
* Trap might occurs during block emulation. For instance, page fault.
73-
* In order to handle trap, we have to escape from block and execute
74-
* registered trap handler. This trap_handler function helps to execute
75-
* the registered trap handler, PC by PC. Once the trap is handled,
76-
* resume the previous execution flow where cause the trap.
77-
*
78-
* Since the system emulation has not yet included in rv32emu, the page
79-
* fault is not practical in current test suite. Instead, we try to
80-
* emulate the misaligned handling in the test suite.
81-
*/
8250
#if RV32_HAS(SYSTEM)
83-
static void trap_handler(riscv_t *rv);
84-
#endif
85-
86-
/* When a trap occurs in M-mode/S-mode, m/stval is either initialized to zero or
87-
* populated with exception-specific details to assist software in managing
88-
* the trap. Otherwise, the implementation never modifies m/stval, although
89-
* software can explicitly write to it. The hardware platform will define
90-
* which exceptions are required to informatively set mtval and which may
91-
* consistently set it to zero.
92-
*
93-
* When a hardware breakpoint is triggered or an exception like address
94-
* misalignment, access fault, or page fault occurs during an instruction
95-
* fetch, load, or store operation, m/stval is updated with the virtual address
96-
* that caused the fault. In the case of an illegal instruction trap, m/stval
97-
* might be updated with the first XLEN or ILEN bits of the offending
98-
* instruction. For all other traps, m/stval is simply set to zero. However,
99-
* it is worth noting that a future standard could redefine how m/stval is
100-
* handled for different types of traps.
101-
*
102-
* For simplicity and clarity, abstracting stval and mtval into a single
103-
* identifier called tval, as both are handled by TRAP_HANDLER_IMPL.
104-
*/
105-
#define TRAP_HANDLER_IMPL(type, code) \
106-
static void rv_trap_##type(riscv_t *rv, uint32_t tval) \
107-
{ \
108-
/* m/stvec (Machine/Supervisor Trap-Vector Base Address Register) \
109-
* m/stvec[MXLEN-1:2]: vector base address \
110-
* m/stvec[1:0] : vector mode \
111-
* m/sepc (Machine/Supervisor Exception Program Counter) \
112-
* m/stval (Machine/Supervisor Trap Value Register) \
113-
* m/scause (Machine/Supervisor Cause Register): store exception code \
114-
* m/sstatus (Machine/Supervisor Status Register): keep track of and \
115-
* controls the hart’s current operating state \
116-
*/ \
117-
uint32_t base; \
118-
uint32_t mode; \
119-
/* user or supervisor */ \
120-
if (RV_PRIV_IS_U_OR_S_MODE()) { \
121-
const uint32_t sstatus_sie = \
122-
(rv->csr_sstatus & SSTATUS_SIE) >> SSTATUS_SIE_SHIFT; \
123-
rv->csr_sstatus |= (sstatus_sie << SSTATUS_SPIE_SHIFT); \
124-
rv->csr_sstatus &= ~(SSTATUS_SIE); \
125-
rv->csr_sstatus |= (rv->priv_mode << SSTATUS_SPP_SHIFT); \
126-
rv->priv_mode = RV_PRIV_S_MODE; \
127-
base = rv->csr_stvec & ~0x3; \
128-
mode = rv->csr_stvec & 0x3; \
129-
rv->csr_sepc = rv->PC; \
130-
rv->csr_stval = tval; \
131-
rv->csr_scause = code; \
132-
} else { /* machine */ \
133-
const uint32_t mstatus_mie = \
134-
(rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT; \
135-
rv->csr_mstatus |= (mstatus_mie << MSTATUS_MPIE_SHIFT); \
136-
rv->csr_mstatus &= ~(MSTATUS_MIE); \
137-
rv->csr_mstatus |= (rv->priv_mode << MSTATUS_MPP_SHIFT); \
138-
rv->priv_mode = RV_PRIV_M_MODE; \
139-
base = rv->csr_mtvec & ~0x3; \
140-
mode = rv->csr_mtvec & 0x3; \
141-
rv->csr_mepc = rv->PC; \
142-
rv->csr_mtval = tval; \
143-
rv->csr_mcause = code; \
144-
if (!rv->csr_mtvec) { /* in case CSR is not configured */ \
145-
rv_trap_default_handler(rv); \
146-
return; \
147-
} \
148-
} \
149-
switch (mode) { \
150-
/* DIRECT: All traps set PC to base */ \
151-
case 0: \
152-
rv->PC = base; \
153-
break; \
154-
/* VECTORED: Asynchronous traps set PC to base + 4 * code */ \
155-
case 1: \
156-
/* MSB of code is used to indicate whether the trap is interrupt \
157-
* or exception, so it is not considered as the 'real' code */ \
158-
rv->PC = base + 4 * (code & MASK(31)); \
159-
break; \
160-
} \
161-
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) trap_handler(rv);, ) \
162-
}
163-
164-
/* RISC-V exception handlers */
165-
#define _(type, code) TRAP_HANDLER_IMPL(type, code)
166-
RV_TRAP_LIST
167-
#undef _
51+
static void __trap_handler(riscv_t *rv);
52+
#endif /* RV32_HAS(SYSTEM) */
16853

16954
/* wrap load/store and insn misaligned handler
17055
* @mask_or_pc: mask for load/store and pc for insn misaligned handler.
@@ -180,8 +65,8 @@ RV_TRAP_LIST
18065
rv->compressed = compress; \
18166
rv->csr_cycle = cycle; \
18267
rv->PC = PC; \
183-
IIF(RV32_HAS(SYSTEM))(rv->is_trapped = true, ); \
184-
rv_trap_##type##_misaligned(rv, IIF(IO)(addr, mask_or_pc)); \
68+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, type##_MISALIGNED, \
69+
IIF(IO)(addr, mask_or_pc)); \
18570
return false; \
18671
}
18772

@@ -531,8 +416,8 @@ static bool do_fuse3(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
531416
*/
532417
for (int i = 0; i < ir->imm2; i++) {
533418
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
534-
RV_EXC_MISALIGN_HANDLER(3, store, false, 1);
535-
rv->io.mem_write_w(addr, rv->X[fuse[i].rs2]);
419+
RV_EXC_MISALIGN_HANDLER(3, STORE, false, 1);
420+
rv->io.mem_write_w(rv, addr, rv->X[fuse[i].rs2]);
536421
}
537422
PC += ir->imm2 * 4;
538423
if (unlikely(RVOP_NO_NEXT(ir))) {
@@ -555,8 +440,8 @@ static bool do_fuse4(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
555440
*/
556441
for (int i = 0; i < ir->imm2; i++) {
557442
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
558-
RV_EXC_MISALIGN_HANDLER(3, load, false, 1);
559-
rv->X[fuse[i].rd] = rv->io.mem_read_w(addr);
443+
RV_EXC_MISALIGN_HANDLER(3, LOAD, false, 1);
444+
rv->X[fuse[i].rd] = rv->io.mem_read_w(rv, addr);
560445
}
561446
PC += ir->imm2 * 4;
562447
if (unlikely(RVOP_NO_NEXT(ir))) {
@@ -666,12 +551,12 @@ static void block_translate(riscv_t *rv, block_t *block)
666551
prev_ir->next = ir;
667552

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

671556
/* decode the instruction */
672557
if (!rv_decode(ir, insn)) {
673558
rv->compressed = is_compressed(insn);
674-
rv_trap_illegal_insn(rv, insn);
559+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ILLEGAL_INSN, insn);
675560
break;
676561
}
677562
ir->impl = dispatch_table[ir->opcode];
@@ -1122,15 +1007,14 @@ void rv_step(void *arg)
11221007
}
11231008

11241009
#if RV32_HAS(SYSTEM)
1125-
static void trap_handler(riscv_t *rv)
1010+
static void __trap_handler(riscv_t *rv)
11261011
{
11271012
rv_insn_t *ir = mpool_alloc(rv->block_ir_mp);
11281013
assert(ir);
11291014

1130-
/* set to false by sret/mret implementation */
1131-
uint32_t insn;
1015+
/* set to false by sret implementation */
11321016
while (rv->is_trapped && !rv_has_halted(rv)) {
1133-
insn = rv->io.mem_ifetch(rv->PC);
1017+
uint32_t insn = rv->io.mem_ifetch(rv, rv->PC);
11341018
assert(insn);
11351019

11361020
rv_decode(ir, insn);
@@ -1139,12 +1023,94 @@ static void trap_handler(riscv_t *rv)
11391023
ir->impl(rv, ir, rv->csr_cycle, rv->PC);
11401024
}
11411025
}
1142-
#endif
1026+
#endif /* RV32_HAS(SYSTEM) */
1027+
1028+
/* When a trap occurs in M-mode/S-mode, m/stval is either initialized to zero or
1029+
* populated with exception-specific details to assist software in managing
1030+
* the trap. Otherwise, the implementation never modifies m/stval, although
1031+
* software can explicitly write to it. The hardware platform will define
1032+
* which exceptions are required to informatively set mtval and which may
1033+
* consistently set it to zero.
1034+
*
1035+
* When a hardware breakpoint is triggered or an exception like address
1036+
* misalignment, access fault, or page fault occurs during an instruction
1037+
* fetch, load, or store operation, m/stval is updated with the virtual address
1038+
* that caused the fault. In the case of an illegal instruction trap, m/stval
1039+
* might be updated with the first XLEN or ILEN bits of the offending
1040+
* instruction. For all other traps, m/stval is simply set to zero. However,
1041+
* it is worth noting that a future standard could redefine how m/stval is
1042+
* handled for different types of traps.
1043+
*
1044+
*/
1045+
static void _trap_handler(riscv_t *rv)
1046+
{
1047+
/* m/stvec (Machine/Supervisor Trap-Vector Base Address Register)
1048+
* m/stvec[MXLEN-1:2]: vector base address
1049+
* m/stvec[1:0] : vector mode
1050+
* m/sepc (Machine/Supervisor Exception Program Counter)
1051+
* m/stval (Machine/Supervisor Trap Value Register)
1052+
* m/scause (Machine/Supervisor Cause Register): store exception code
1053+
* m/sstatus (Machine/Supervisor Status Register): keep track of and
1054+
* controls the hart’s current operating state
1055+
*
1056+
* m/stval and m/scause are set in SET_CAUSE_AND_TVAL_THEN_TRAP
1057+
*/
1058+
uint32_t base;
1059+
uint32_t mode;
1060+
uint32_t cause;
1061+
/* user or supervisor */
1062+
if (RV_PRIV_IS_U_OR_S_MODE()) {
1063+
const uint32_t sstatus_sie =
1064+
(rv->csr_sstatus & SSTATUS_SIE) >> SSTATUS_SIE_SHIFT;
1065+
rv->csr_sstatus |= (sstatus_sie << SSTATUS_SPIE_SHIFT);
1066+
rv->csr_sstatus &= ~(SSTATUS_SIE);
1067+
rv->csr_sstatus |= (rv->priv_mode << SSTATUS_SPP_SHIFT);
1068+
rv->priv_mode = RV_PRIV_S_MODE;
1069+
base = rv->csr_stvec & ~0x3;
1070+
mode = rv->csr_stvec & 0x3;
1071+
cause = rv->csr_scause;
1072+
rv->csr_sepc = rv->PC;
1073+
} else { /* machine */
1074+
const uint32_t mstatus_mie =
1075+
(rv->csr_mstatus & MSTATUS_MIE) >> MSTATUS_MIE_SHIFT;
1076+
rv->csr_mstatus |= (mstatus_mie << MSTATUS_MPIE_SHIFT);
1077+
rv->csr_mstatus &= ~(MSTATUS_MIE);
1078+
rv->csr_mstatus |= (rv->priv_mode << MSTATUS_MPP_SHIFT);
1079+
rv->priv_mode = RV_PRIV_M_MODE;
1080+
base = rv->csr_mtvec & ~0x3;
1081+
mode = rv->csr_mtvec & 0x3;
1082+
cause = rv->csr_mcause;
1083+
rv->csr_mepc = rv->PC;
1084+
if (!rv->csr_mtvec) { /* in case CSR is not configured */
1085+
rv_trap_default_handler(rv);
1086+
return;
1087+
}
1088+
}
1089+
switch (mode) {
1090+
/* DIRECT: All traps set PC to base */
1091+
case 0:
1092+
rv->PC = base;
1093+
break;
1094+
/* VECTORED: Asynchronous traps set PC to base + 4 * code */
1095+
case 1:
1096+
/* MSB of code is used to indicate whether the trap is interrupt
1097+
* or exception, so it is not considered as the 'real' code */
1098+
rv->PC = base + 4 * (cause & MASK(31));
1099+
break;
1100+
}
1101+
IIF(RV32_HAS(SYSTEM))(if (rv->is_trapped) __trap_handler(rv);, )
1102+
}
1103+
1104+
void trap_handler(riscv_t *rv)
1105+
{
1106+
assert(rv);
1107+
_trap_handler(rv);
1108+
}
11431109

11441110
void ebreak_handler(riscv_t *rv)
11451111
{
11461112
assert(rv);
1147-
rv_trap_breakpoint(rv, rv->PC);
1113+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, BREAKPOINT, rv->PC);
11481114
}
11491115

11501116
void ecall_handler(riscv_t *rv)
@@ -1154,7 +1120,7 @@ void ecall_handler(riscv_t *rv)
11541120
syscall_handler(rv);
11551121
rv->PC += 4;
11561122
#else
1157-
rv_trap_ecall_M(rv, 0);
1123+
SET_CAUSE_AND_TVAL_THEN_TRAP(rv, ECALL_M, 0);
11581124
syscall_handler(rv);
11591125
#endif
11601126
}

src/feature.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,10 @@
6363
#define RV32_FEATURE_T2C 0
6464
#endif
6565

66+
/* System */
67+
#ifndef RV32_FEATURE_SYSTEM
68+
#define RV32_FEATURE_SYSTEM 0
69+
#endif
70+
6671
/* Feature test macro */
6772
#define RV32_HAS(x) RV32_FEATURE_##x

src/gdbstub.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ static int rv_read_mem(void *args, size_t addr, size_t len, void *val)
5555
* an invalid address. We may have to do error handling in the
5656
* mem_read_* function directly.
5757
*/
58-
*((uint8_t *) val + i) = rv->io.mem_read_b(addr + i);
58+
*((uint8_t *) val + i) = rv->io.mem_read_b(rv, addr + i);
5959
}
6060

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

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

7171
return 0;
7272
}

src/main.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,12 +217,21 @@ int main(int argc, char **args)
217217
.log_level = 0,
218218
.run_flag = run_flag,
219219
.profile_output_file = prof_out_file,
220+
#if RV32_HAS(SYSTEM)
221+
.data.system = malloc(sizeof(vm_system_t)),
222+
#else
220223
.data.user = malloc(sizeof(vm_user_t)),
224+
#endif
221225
.cycle_per_step = CYCLE_PER_STEP,
222226
.allow_misalign = opt_misaligned,
223227
};
228+
#if RV32_HAS(SYSTEM)
229+
assert(attr.data.system);
230+
attr.data.system->elf_program = opt_prog_name;
231+
#else
224232
assert(attr.data.user);
225233
attr.data.user->elf_program = opt_prog_name;
234+
#endif
226235

227236
/* create the RISC-V runtime */
228237
rv = rv_create(&attr);

0 commit comments

Comments
 (0)