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, we must first support MMU
emulation. The virtual memory scheme to be supported is Sv32. The 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, we modified its prototype.

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.

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 aforementioned page faults.

Some S-mode CSRs are added to riscv_internal to support S-mode. PTE,
S-mode, and M-mode CSR helper macros are also introduced.

Related: sysprog21#310
  • Loading branch information
ChinYikMing committed Jun 21, 2024
1 parent 4bb8593 commit 785ca82
Show file tree
Hide file tree
Showing 20 changed files with 1,677 additions and 169 deletions.
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ CFLAGS = -std=gnu99 -O2 -Wall -Wextra
CFLAGS += -Wno-unused-label
CFLAGS += -include src/common.h

ENABLE_SYSTEM ?= 0
ifeq ($(call has, SYSTEM), 1)
$(call set-feature, SYSTEM)
endif

# Enable link-time optimization (LTO)
ENABLE_LTO ?= 1
ifeq ($(call has, LTO), 1)
Expand Down
12 changes: 12 additions & 0 deletions src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@

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

#define MASK(n) (~((~0U << (n))))

/* Floor log base 2 */
#if defined(__GNUC__) || defined(__clang__)
#define ilog2(x) (31 - __builtin_clz(x | 1))
#elif defined(_MSC_VER)
/* FIXME */
#define ilog2(x)
#else /* unsupported compilers */
#define ilog2(x)
#endif

/* Alignment macro */
#if defined(__GNUC__) || defined(__clang__)
#define __ALIGNED(x) __attribute__((aligned(x)))
Expand Down
8 changes: 5 additions & 3 deletions src/decode.c
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,9 @@ static inline bool op_system(rv_insn_t *ir, const uint32_t insn)
break;
case 0x105: /* WFI: Wait for Interrupt */
case 0x002: /* URET: return from traps in U-mode */
case 0x102: /* SRET: return from traps in S-mode */
ir->opcode = rv_insn_sret;
break;
case 0x202: /* HRET: return from traps in H-mode */
/* illegal instruction */
return false;
Expand Down Expand Up @@ -903,9 +906,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);
}

#if RV32_HAS(Zifencei)
Expand Down
634 changes: 563 additions & 71 deletions src/emulate.c

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/feature.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
#if !RV32_FEATURE_JIT
#undef RV32_FEATURE_T2C
#define RV32_FEATURE_T2C 0

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

/* Feature test macro */
Expand Down
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
5 changes: 5 additions & 0 deletions src/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@

#include "io.h"

plic_t *plic_new()
{
return malloc(sizeof(plic_t));
}

static uint8_t *data_memory_base;

memory_t *memory_new(uint32_t size)
Expand Down
11 changes: 11 additions & 0 deletions src/io.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
#include <stdint.h>
#include <string.h>

typedef struct {
uint32_t masked;
uint32_t ip;
uint32_t ie;
/* state of input interrupt lines (level-triggered), set by environment */
uint32_t active;
} plic_t;

/* create a PLIC core */
plic_t *plic_new();

typedef struct {
uint8_t *mem_base;
uint64_t mem_size;
Expand Down
18 changes: 18 additions & 0 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,23 @@ int main(int argc, char **args)
#endif
run_flag |= opt_prof_data << 2;

#if RV32_HAS(SYSTEM)
vm_attr_t attr = {
.mem_size = MEM_SIZE,
.stack_size = STACK_SIZE,
.args_offset_size = ARGS_OFFSET_SIZE,
.argc = prog_argc,
.argv = prog_args,
.log_level = 0,
.run_flag = run_flag,
.profile_output_file = prof_out_file,
.data.system = malloc(sizeof(vm_system_t)),
.cycle_per_step = CYCLE_PER_STEP,
.allow_misalign = opt_misaligned,
};
assert(attr.data.system);
attr.data.system->elf_program = opt_prog_name;
#else
vm_attr_t attr = {
.mem_size = MEM_SIZE,
.stack_size = STACK_SIZE,
Expand All @@ -222,6 +239,7 @@ int main(int argc, char **args)
};
assert(attr.data.user);
attr.data.user->elf_program = opt_prog_name;
#endif

/* create the RISC-V runtime */
rv = rv_create(&attr);
Expand Down
34 changes: 30 additions & 4 deletions src/riscv.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,10 @@ void rv_remap_stdstream(riscv_t *rv, fd_stream_pair_t *fsp, uint32_t fsp_size)
#define MEMIO(op) on_mem_##op
#define IO_HANDLER_IMPL(type, op, RW) \
static IIF(RW)( \
/* W */ void MEMIO(op)(riscv_word_t addr, riscv_##type##_t data), \
/* R */ riscv_##type##_t MEMIO(op)(riscv_word_t addr)) \
/* W */ void MEMIO(op)(UNUSED riscv_t * rv, riscv_word_t addr, \
riscv_##type##_t data), \
/* R */ riscv_##type##_t MEMIO(op)(UNUSED riscv_t * rv, \
riscv_word_t addr)) \
{ \
IIF(RW) \
(memory_##op(addr, (uint8_t *) &data), return memory_##op(addr)); \
Expand Down Expand Up @@ -221,6 +223,10 @@ riscv_t *rv_create(riscv_user_t rv_attr)
vm_attr_t *attr = PRIV(rv);
attr->mem = memory_new(attr->mem_size);
assert(attr->mem);
attr->plic = plic_new();
assert(attr->plic);

rv->is_interrupted = false;

/* reset */
rv_reset(rv, 0U);
Expand Down Expand Up @@ -263,6 +269,24 @@ riscv_t *rv_create(riscv_user_t rv_attr)
memcpy(&rv->io, &io, sizeof(riscv_io_t));
} else {
/* TODO: system emulator */
elf_t *elf = elf_new();
assert(elf && elf_open(elf, (attr->data.system)->elf_program));

const struct Elf32_Sym *end;
if ((end = elf_get_symbol(elf, "_end")))
attr->break_addr = end->st_value;

assert(elf_load(elf, attr->mem));

/* set the entry pc */
const struct Elf32_Ehdr *hdr = get_elf_header(elf);
assert(rv_set_pc(rv, hdr->e_entry));

elf_delete(elf);

/* this variable has external linkage to mmu_io defined in emulate.c */
extern riscv_io_t mmu_io;
memcpy(&rv->io, &mmu_io, sizeof(riscv_io_t));
}

/* default standard stream.
Expand Down Expand Up @@ -342,7 +366,7 @@ void rv_run(riscv_t *rv)
assert(rv);

vm_attr_t *attr = PRIV(rv);
assert(attr && attr->data.user && attr->data.user->elf_program);
//assert(attr && attr->data.user && attr->data.user->elf_program);

if (attr->run_flag & RV_RUN_TRACE)
rv_run_and_trace(rv);
Expand Down Expand Up @@ -501,6 +525,9 @@ void rv_reset(riscv_t *rv, riscv_word_t pc)
/* reset sp pointing to argc */
rv->X[rv_reg_sp] = stack_top;

/* reset privilege mode */
rv->priv_mode = RV_PRIV_M_MODE;

/* reset the csrs */
rv->csr_mtvec = 0;
rv->csr_cycle = 0;
Expand All @@ -526,7 +553,6 @@ void rv_reset(riscv_t *rv, riscv_word_t pc)
rv->csr_misa |= MISA_M;
#endif


rv->halt = false;
}

Expand Down
136 changes: 129 additions & 7 deletions src/riscv.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,117 @@ enum {
#define MISA_A (1 << ('A' - 'A'))
#define MISA_F (1 << ('F' - 'A'))
#define MISA_C (1 << ('C' - 'A'))

/* 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_PG_SHIFT 12
#define RV_PG_SIZE (1 << RV_PG_SHIFT)

#define RV_PRIV_U_MODE 0
#define RV_PRIV_S_MODE 1
#define RV_PRIV_M_MODE 3

#define PTE_V (1U)
#define PTE_R (1U << 1)
#define PTE_W (1U << 2)
#define PTE_X (1U << 3)
#define PTE_U (1U << 4)
#define PTE_G (1U << 5)
#define PTE_A (1U << 6)
#define PTE_D (1U << 7)

/*
* SBI functions must return a pair of values:
*
* struct sbiret {
* long error;
* long value;
* };
*
* The error and value field will be set to register a0 and a1 respectively
* after the SBI function return. The error field indicate whether the
* SBI call is success or not. SBI_SUCCESS indicates success and
* SBI_ERR_NOT_SUPPORTED indicates not supported failure. The value field is
* the information based on the extension ID(EID) and SBI function ID(FID).
*
* SBI reference: https://github.com/riscv-non-isa/riscv-sbi-doc
*
*/
#define SBI_SUCCESS 0
#define SBI_ERR_NOT_SUPPORTED -2

/*
* All of the functions in the base extension must be supported by
* all SBI implementations.
*/
#define SBI_EID_BASE 0x10
#define SBI_BASE_GET_SBI_SPEC_VERSION 0
#define SBI_BASE_GET_SBI_IMPL_ID 1
#define SBI_BASE_GET_SBI_IMPL_VERSION 2
#define SBI_BASE_PROBE_EXTENSION 3
#define SBI_BASE_GET_MVENDORID 4
#define SBI_BASE_GET_MARCHID 5
#define SBI_BASE_GET_MIMPID 6

/* Make supervisor to schedule the clock for next timer event. */
#define SBI_EID_TIMER 0x54494D45
#define SBI_TIMER_SET_TIMER 0

/* Allows the supervisor to request system-level reboot or shutdown. */
#define SBI_EID_RST 0x53525354
#define SBI_RST_SYSTEM_RESET 0

#define RV_MVENDORID 0x12345678
#define RV_MARCHID ((1ULL << 31) | 1)
#define RV_MIMPID 1

/*
* SBI functions must return a pair of values:
Expand Down Expand Up @@ -156,15 +263,21 @@ typedef softfloat_float32_t riscv_float_t;
#endif

/* memory read handlers */
typedef riscv_word_t (*riscv_mem_ifetch)(riscv_word_t addr);
typedef riscv_word_t (*riscv_mem_read_w)(riscv_word_t addr);
typedef riscv_half_t (*riscv_mem_read_s)(riscv_word_t addr);
typedef riscv_byte_t (*riscv_mem_read_b)(riscv_word_t addr);
typedef riscv_word_t (*riscv_mem_ifetch)(riscv_t *rv, riscv_word_t addr);
typedef riscv_word_t (*riscv_mem_read_w)(riscv_t *rv, riscv_word_t addr);
typedef riscv_half_t (*riscv_mem_read_s)(riscv_t *rv, riscv_word_t addr);
typedef riscv_byte_t (*riscv_mem_read_b)(riscv_t *rv, riscv_word_t addr);

/* memory write handlers */
typedef void (*riscv_mem_write_w)(riscv_word_t addr, riscv_word_t data);
typedef void (*riscv_mem_write_s)(riscv_word_t addr, riscv_half_t data);
typedef void (*riscv_mem_write_b)(riscv_word_t addr, riscv_byte_t data);
typedef void (*riscv_mem_write_w)(riscv_t *rv,
riscv_word_t addr,
riscv_word_t data);
typedef void (*riscv_mem_write_s)(riscv_t *rv,
riscv_word_t addr,
riscv_half_t data);
typedef void (*riscv_mem_write_b)(riscv_t *rv,
riscv_word_t addr,
riscv_byte_t data);

/* system instruction handlers */
typedef void (*riscv_on_ecall)(riscv_t *rv);
Expand Down Expand Up @@ -273,12 +386,21 @@ typedef struct {
char *elf_program;
} vm_user_t;

/* FIXME: replace with kernel image, dtb, etc */
typedef struct {
char *elf_program;
} vm_system_t;

typedef struct {
vm_user_t *user;
vm_system_t *system;
/* TODO: system emulator stuff */
} vm_data_t;

typedef struct {
/* PLIC core */
plic_t *plic;

/* vm memory object */
memory_t *mem;

Expand Down
Loading

0 comments on commit 785ca82

Please sign in to comment.