Skip to content

Commit b1de0d6

Browse files
committed
Support emulating memory management unit(MMU)
The purpose of this commit is to boot 32-bit RISC-V Linux in the future. The virtual memory scheme to support is Sv32. There are one change to original code base to adapt the MMU: The prototype of riscv_io_t interface needs to be changed. Particularly, add a RISC-V instance(riscv_t) as the first parameter. MMU related callbacks require to access the satp CSR to perform a page table walk during virtual memory translation but satp CSR is stored in RISC-V instance(riscv_t), thus it should have a way to access the satp CSR. The trivial solution is adding RISC-V instance(riscv_t) to the prototype of riscv_io_t interface. After this change, we can reuse riscv_io_t for system emulation afterward. The rest of changes are implementing the Sv32 virtual memory scheme. For every memory access, it has to walk through the page table to get the corresponding PTE. Depends on the retrieval of PTE, there are several page faults to be handled if necessary, so there are three exceptions handlers have been introduced which are insn_pgfault, load_pgfault, and store_pgfault and they are used in MMU_CHECK_FAULT. In this commit, the access fault are not handled well since they are related to PMA and PMP and they might not the must to boot 32-bit RISC-V Linux (tested on semu). More PTE, S-mode, M-mode CSR helper macro are introduced as well. Related: #310
1 parent 8351106 commit b1de0d6

File tree

6 files changed

+394
-56
lines changed

6 files changed

+394
-56
lines changed

src/common.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
#define ARRAYS_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
2727

28+
#define MASK(n) (~(1 << n))
29+
2830
/* Alignment macro */
2931
#if defined(__GNUC__) || defined(__clang__)
3032
#define __ALIGNED(x) __attribute__((aligned(x)))

src/emulate.c

Lines changed: 266 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extern struct target_ops gdbstub_ops;
2424
#endif
2525

2626
#include "decode.h"
27+
#include "io.h"
2728
#include "mpool.h"
2829
#include "riscv.h"
2930
#include "riscv_private.h"
@@ -51,7 +52,10 @@ extern struct target_ops gdbstub_ops;
5152
_(breakpoint, 3) /* Breakpoint */ \
5253
_(load_misaligned, 4) /* Load address misaligned */ \
5354
_(store_misaligned, 6) /* Store/AMO address misaligned */ \
54-
_(ecall_M, 11) /* Environment call from M-mode */
55+
_(ecall_M, 11) /* Environment call from M-mode */ \
56+
_(insn_pgfault, 12) /* Instruction page fault */ \
57+
_(load_pgfault, 13) /* Load page fault */ \
58+
_(store_pgfault, 15) /* Store page fault */
5559
/* clang-format on */
5660

5761
enum {
@@ -196,6 +200,8 @@ static uint32_t *csr_get_ptr(riscv_t *rv, uint32_t csr)
196200
case CSR_FCSR:
197201
return (uint32_t *) (&rv->csr_fcsr);
198202
#endif
203+
case CSR_SATP:
204+
return (uint32_t *) (&rv->csr_satp);
199205
default:
200206
return NULL;
201207
}
@@ -220,7 +226,16 @@ static uint32_t csr_csrrw(riscv_t *rv, uint32_t csr, uint32_t val)
220226
out &= FFLAG_MASK;
221227
#endif
222228

223-
*c = val;
229+
if (c == &rv->csr_satp) {
230+
const uint8_t mode_sv32 = val >> 31;
231+
if (mode_sv32)
232+
*c = val & MASK(22); /* store ppn */
233+
else /* bare mode */
234+
*c = 0; /* virtual mem addr maps to same physical mem addr directly
235+
*/
236+
} else {
237+
*c = val;
238+
}
224239

225240
return out;
226241
}
@@ -456,7 +471,7 @@ static bool do_fuse3(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
456471
for (int i = 0; i < ir->imm2; i++) {
457472
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
458473
RV_EXC_MISALIGN_HANDLER(3, store, false, 1);
459-
rv->io.mem_write_w(addr, rv->X[fuse[i].rs2]);
474+
rv->io.mem_write_w(rv, addr, rv->X[fuse[i].rs2]);
460475
}
461476
PC += ir->imm2 * 4;
462477
if (unlikely(RVOP_NO_NEXT(ir))) {
@@ -480,7 +495,7 @@ static bool do_fuse4(riscv_t *rv, rv_insn_t *ir, uint64_t cycle, uint32_t PC)
480495
for (int i = 0; i < ir->imm2; i++) {
481496
uint32_t addr = rv->X[fuse[i].rs1] + fuse[i].imm;
482497
RV_EXC_MISALIGN_HANDLER(3, load, false, 1);
483-
rv->X[fuse[i].rd] = rv->io.mem_read_w(addr);
498+
rv->X[fuse[i].rd] = rv->io.mem_read_w(rv, addr);
484499
}
485500
PC += ir->imm2 * 4;
486501
if (unlikely(RVOP_NO_NEXT(ir))) {
@@ -613,7 +628,8 @@ static void block_translate(riscv_t *rv, block_t *block)
613628
prev_ir->next = ir;
614629

615630
/* fetch the next instruction */
616-
const uint32_t insn = rv->io.mem_ifetch(block->pc_end);
631+
const uint32_t insn = rv->io.mem_ifetch(rv, block->pc_end);
632+
printf("insn: %d\n", insn);
617633

618634
/* decode the instruction */
619635
if (!rv_decode(ir, insn)) {
@@ -691,7 +707,7 @@ static bool detect_memset(riscv_t *rv, size_t type)
691707

692708
uint32_t tmp_pc = rv->PC;
693709
for (uint32_t i = 0; i < memset_len; i++) {
694-
const uint32_t insn = rv->io.mem_ifetch(tmp_pc);
710+
const uint32_t insn = rv->io.mem_ifetch(rv, tmp_pc);
695711
if (unlikely(insn != memset_insn[i]))
696712
return false;
697713
tmp_pc += 4;
@@ -712,7 +728,7 @@ static bool detect_memcpy(riscv_t *rv, size_t type)
712728

713729
uint32_t tmp_pc = rv->PC;
714730
for (uint32_t i = 0; i < memcpy_len; i++) {
715-
const uint32_t insn = rv->io.mem_ifetch(tmp_pc);
731+
const uint32_t insn = rv->io.mem_ifetch(rv, tmp_pc);
716732
if (unlikely(insn != memcpy_insn[i]))
717733
return false;
718734
tmp_pc += 4;
@@ -1178,6 +1194,230 @@ void rv_step(void *arg)
11781194
#endif
11791195
}
11801196

1197+
static bool ppn_is_valid(riscv_t *rv, uint32_t ppn)
1198+
{
1199+
vm_attr_t *attr = PRIV(rv);
1200+
const uint32_t nr_pg_max = attr->mem_size / RV_PG_SIZE;
1201+
return ppn < nr_pg_max;
1202+
}
1203+
1204+
#define PAGE_TABLE(ppn) \
1205+
ppn_is_valid(rv, ppn) ? (uint32_t *) &attr->mem[ppn << (RV_PG_SHIFT - 2)] \
1206+
: NULL
1207+
1208+
/* Walk through page tables and get the corresponding PTE by virtual address if
1209+
* exists
1210+
* @rv: RISC-V emulator
1211+
* @addr: virtual address
1212+
* @return: NULL if a not found or fault else the corresponding PTE
1213+
*/
1214+
static uint32_t *mmu_walk(riscv_t *rv, const uint32_t addr)
1215+
{
1216+
vm_attr_t *attr = PRIV(rv);
1217+
uint32_t ppn = rv->csr_satp;
1218+
if (ppn == 0) /* Bare mode */
1219+
return NULL;
1220+
1221+
/* start from root page table */
1222+
uint32_t *page_table = PAGE_TABLE(ppn);
1223+
if (!page_table)
1224+
return NULL;
1225+
1226+
for (int level = 1; level >= 0; level--) {
1227+
uint32_t vpn = addr >> RV_PG_SHIFT >> (level * (RV_PG_SHIFT - 2));
1228+
uint32_t *pte = page_table + vpn;
1229+
1230+
/* PTE XWRV bit in order */
1231+
uint8_t XWRV_bit = (*pte & MASK(4));
1232+
switch (XWRV_bit) {
1233+
case 0b0001: /* next level of the page table */
1234+
page_table = PAGE_TABLE(ppn);
1235+
if (!page_table)
1236+
return NULL;
1237+
break;
1238+
case 0b0011:
1239+
case 0b0111:
1240+
case 0b1001:
1241+
case 0b1011:
1242+
case 0b1111:
1243+
ppn = (*pte >> (RV_PG_SHIFT - 2));
1244+
if (unlikely(ppn) & MASK(10)) /* misaligned superpage */
1245+
return NULL;
1246+
return pte; /* leaf PTE */
1247+
case 0b0101:
1248+
case 0b1101:
1249+
return NULL;
1250+
}
1251+
}
1252+
1253+
return NULL;
1254+
}
1255+
1256+
/* Verify the PTE and generate corresponding faults if needed
1257+
* @op: the operation
1258+
* @rv: RISC-V emulator
1259+
* @pte: to be verified pte
1260+
* @addr: the corresponding virtual address to cause fault
1261+
* @return: false if a corresponding fault is generated else true
1262+
*/
1263+
/* FIXME: handle access fault */
1264+
#define MMU_FAULT_CHECK(op, rv, pte, addr, access_bits) \
1265+
mmu_##op##_fault_check(rv, pte, addr, access_bits)
1266+
#define MMU_FAULT_CHECK_IMPL(op, pgfault) \
1267+
static bool mmu_##op##_fault_check(riscv_t *rv, uint32_t *pte, \
1268+
uint32_t addr, uint32_t access_bits) \
1269+
{ \
1270+
if (!pte && rv->csr_satp) { /* not found */ \
1271+
rv_except_##pgfault(rv, addr); \
1272+
return false; \
1273+
} else if (pte && \
1274+
(!(*pte & PTE_V) || (!(*pte & PTE_R) && (*pte & PTE_W)))) { \
1275+
rv_except_##pgfault(rv, addr); \
1276+
return false; \
1277+
} else if (pte && (!(*pte & PTE_X) && (access_bits & PTE_X))) { \
1278+
rv_except_##pgfault(rv, addr); \
1279+
return false; \
1280+
} else if (pte && (!(!(MSTATUS_MXR & rv->csr_mstatus) && \
1281+
!(*pte & PTE_R) && (access_bits & PTE_R)) && \
1282+
!((MSTATUS_MXR & rv->csr_mstatus) && \
1283+
!((*pte & PTE_R) | (*pte & PTE_X)) && \
1284+
(access_bits & PTE_R)))) { \
1285+
rv_except_##pgfault(rv, addr); \
1286+
return false; \
1287+
} else if (pte && ((MSTATUS_MPRV & rv->csr_mstatus) && \
1288+
!(MSTATUS_MPPH & \
1289+
rv->csr_mstatus) && /* MPP=01 means S-mode */ \
1290+
(MSTATUS_MPPL & rv->csr_mstatus))) { \
1291+
if (!(MSTATUS_SUM & rv->csr_mstatus) && (*pte & PTE_U)) { \
1292+
rv_except_##pgfault(rv, addr); \
1293+
return false; \
1294+
} \
1295+
} \
1296+
return true; \
1297+
}
1298+
1299+
MMU_FAULT_CHECK_IMPL(ifetch, insn_pgfault)
1300+
MMU_FAULT_CHECK_IMPL(read, load_pgfault)
1301+
MMU_FAULT_CHECK_IMPL(write, store_pgfault)
1302+
1303+
#define get_ppn_and_offset(ppn, offset) \
1304+
do { \
1305+
ppn = *pte << RV_PG_SHIFT; \
1306+
offset = addr & MASK(RV_PG_SHIFT); \
1307+
} while (0)
1308+
1309+
uint32_t mmu_ifetch(riscv_t *rv, const uint32_t addr)
1310+
{
1311+
uint32_t *pte = mmu_walk(rv, addr);
1312+
bool ok = MMU_FAULT_CHECK(ifetch, rv, pte, addr, PTE_X);
1313+
if (unlikely(!ok))
1314+
return 0;
1315+
1316+
if (rv->csr_satp) {
1317+
uint32_t ppn;
1318+
uint32_t offset;
1319+
get_ppn_and_offset(ppn, offset);
1320+
return memory_ifetch(ppn | offset);
1321+
}
1322+
return memory_ifetch(addr);
1323+
}
1324+
1325+
uint32_t mmu_read_w(riscv_t *rv, const uint32_t addr)
1326+
{
1327+
uint32_t *pte = mmu_walk(rv, addr);
1328+
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
1329+
if (unlikely(!ok))
1330+
return 0;
1331+
1332+
if (rv->csr_satp) {
1333+
uint32_t ppn;
1334+
uint32_t offset;
1335+
get_ppn_and_offset(ppn, offset);
1336+
return memory_read_w(ppn | offset);
1337+
}
1338+
return memory_read_w(addr);
1339+
}
1340+
1341+
uint16_t mmu_read_s(riscv_t *rv, const uint32_t addr)
1342+
{
1343+
uint32_t *pte = mmu_walk(rv, addr);
1344+
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
1345+
if (unlikely(!ok))
1346+
return 0;
1347+
1348+
if (rv->csr_satp) {
1349+
uint32_t ppn;
1350+
uint32_t offset;
1351+
get_ppn_and_offset(ppn, offset);
1352+
return memory_read_s(ppn | offset);
1353+
}
1354+
return memory_read_s(addr);
1355+
}
1356+
1357+
uint8_t mmu_read_b(riscv_t *rv, const uint32_t addr)
1358+
{
1359+
uint32_t *pte = mmu_walk(rv, addr);
1360+
bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R);
1361+
if (unlikely(!ok))
1362+
return 0;
1363+
1364+
if (rv->csr_satp) {
1365+
uint32_t ppn;
1366+
uint32_t offset;
1367+
get_ppn_and_offset(ppn, offset);
1368+
return memory_read_b(ppn | offset);
1369+
}
1370+
return memory_read_b(addr);
1371+
}
1372+
1373+
void mmu_write_w(riscv_t *rv, const uint32_t addr, const uint32_t val)
1374+
{
1375+
uint32_t *pte = mmu_walk(rv, addr);
1376+
bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W);
1377+
if (unlikely(!ok))
1378+
return;
1379+
1380+
if (rv->csr_satp) {
1381+
uint32_t ppn;
1382+
uint32_t offset;
1383+
get_ppn_and_offset(ppn, offset);
1384+
return memory_write_w(ppn | offset, (uint8_t *) &val);
1385+
}
1386+
return memory_write_w(addr, (uint8_t *) &val);
1387+
}
1388+
1389+
void mmu_write_s(riscv_t *rv, const uint32_t addr, const uint16_t val)
1390+
{
1391+
uint32_t *pte = mmu_walk(rv, addr);
1392+
bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W);
1393+
if (unlikely(!ok))
1394+
return;
1395+
1396+
if (rv->csr_satp) {
1397+
uint32_t ppn;
1398+
uint32_t offset;
1399+
get_ppn_and_offset(ppn, offset);
1400+
return memory_write_s(ppn | offset, (uint8_t *) &val);
1401+
}
1402+
return memory_write_s(addr, (uint8_t *) &val);
1403+
}
1404+
1405+
void mmu_write_b(riscv_t *rv, const uint32_t addr, const uint8_t val)
1406+
{
1407+
uint32_t *pte = mmu_walk(rv, addr);
1408+
bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W);
1409+
if (unlikely(!ok))
1410+
return;
1411+
1412+
if (rv->csr_satp) {
1413+
uint32_t ppn;
1414+
uint32_t offset;
1415+
get_ppn_and_offset(ppn, offset);
1416+
return memory_write_b(ppn | offset, (uint8_t *) &val);
1417+
}
1418+
return memory_write_b(addr, (uint8_t *) &val);
1419+
}
1420+
11811421
void ebreak_handler(riscv_t *rv)
11821422
{
11831423
assert(rv);
@@ -1225,3 +1465,22 @@ void dump_registers(riscv_t *rv, char *out_file_path)
12251465
if (out_file_path[0] != '-')
12261466
fclose(f);
12271467
}
1468+
1469+
riscv_io_t mmu_io = {
1470+
/* memory read interface */
1471+
.mem_ifetch = mmu_ifetch,
1472+
.mem_read_w = mmu_read_w,
1473+
.mem_read_s = mmu_read_s,
1474+
.mem_read_b = mmu_read_b,
1475+
1476+
/* memory write interface */
1477+
.mem_write_w = mmu_write_w,
1478+
.mem_write_s = mmu_write_s,
1479+
.mem_write_b = mmu_write_b,
1480+
1481+
/* system services or essential routines */
1482+
.on_ecall = ecall_handler,
1483+
.on_ebreak = ebreak_handler,
1484+
.on_memcpy = memcpy_handler,
1485+
.on_memset = memset_handler,
1486+
};

src/riscv.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,8 +162,10 @@ void rv_remap_stdstream(riscv_t *rv, fd_stream_pair_t *fsp, uint32_t fsp_size)
162162
#define MEMIO(op) on_mem_##op
163163
#define IO_HANDLER_IMPL(type, op, RW) \
164164
static IIF(RW)( \
165-
/* W */ void MEMIO(op)(riscv_word_t addr, riscv_##type##_t data), \
166-
/* R */ riscv_##type##_t MEMIO(op)(riscv_word_t addr)) \
165+
/* W */ void MEMIO(op)(UNUSED riscv_t * rv, riscv_word_t addr, \
166+
riscv_##type##_t data), \
167+
/* R */ riscv_##type##_t MEMIO(op)(UNUSED riscv_t * rv, \
168+
riscv_word_t addr)) \
167169
{ \
168170
IIF(RW) \
169171
(memory_##op(addr, (uint8_t *) &data), return memory_##op(addr)); \
@@ -239,6 +241,10 @@ riscv_t *rv_create(riscv_user_t rv_attr)
239241
memcpy(&rv->io, &io, sizeof(riscv_io_t));
240242
} else {
241243
/* TODO: system emulator */
244+
245+
/* this variable has external linkage to mmu_io defined in emulate.c */
246+
extern riscv_io_t mmu_io;
247+
memcpy(&rv->io, &mmu_io, sizeof(riscv_io_t));
242248
}
243249

244250
/* default standard stream.

0 commit comments

Comments
 (0)