diff --git a/Makefile b/Makefile index d8359110..94a6a33e 100644 --- a/Makefile +++ b/Makefile @@ -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) diff --git a/src/common.h b/src/common.h index f6337f8d..da84ebdb 100644 --- a/src/common.h +++ b/src/common.h @@ -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))) diff --git a/src/decode.c b/src/decode.c index ac65d3db..a27686a0 100644 --- a/src/decode.c +++ b/src/decode.c @@ -903,9 +903,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) diff --git a/src/emulate.c b/src/emulate.c index e156aaba..720c8a0c 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -24,6 +25,7 @@ extern struct target_ops gdbstub_ops; #endif #include "decode.h" +#include "io.h" #include "mpool.h" #include "riscv.h" #include "riscv_private.h" @@ -41,31 +43,49 @@ 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_EXCEPTION_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 */ \ - _(ecall_M, 11) /* Environment call from M-mode */ +#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 */ \ + _(ecall_M, 11) /* Environment call from M-mode */ \ + IIF(RV32_HAS(SYSTEM))( \ + _(pagefault_insn, 12) /* Instruction page fault */ \ + _(pagefault_load, 13) /* Load page fault */ \ + _(pagefault_store, 15) /* Store page fault */ \ + ) /* 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 */ } +typedef enum { + EXCEPTION, + INTERRUPT, +} trap_t; + +#if RV32_HAS(SYSTEM) +static void trap_handler(riscv_t *rv, trap_t type UNUSED); +#else /* should not be called in non-SYSTEM mode as default trap handler is \ + * capable to handle traps \ + */ +static void trap_handler(riscv_t *rv UNUSED, trap_t type 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 @@ -76,49 +96,95 @@ static void rv_exception_default_handler(riscv_t *rv) * When a hardware breakpoint is triggered or an exception like address * misalignment, access fault, or page fault occurs during an instruction * fetch, load, or store operation, mtval is updated with the virtual address - * that caused the fault. In the case of an illegal instruction trap, mtval - * might be updated with the first XLEN or ILEN bits of the offending - * instruction. For all other traps, mtval is simply set to zero. However, - * it is worth noting that a future standard could redefine how mtval is - * handled for different types of traps. + * that caused the fault. In the case of an illegal instruction trap, mtval * + * might be updated with the first XLEN or ILEN bits of the offending * + * instruction. For all other traps, mtval is simply set to zero. However, 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; \ + } \ + } \ + /* FIXME: ecall is not emulated in current test suite, so temporarily \ + * add this. When Linux kernel boots, the system call table is \ + * provided by it. At that time, trap_handler shall be called to \ + * handle the trap \ + */ \ + if (code == rv_trap_code_ecall_M) { \ + 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, code & ~MASK(31)); \ + } else { \ + fprintf(stderr, "setjmp failed"); \ + } \ + } \ } -/* RISC-V exception handlers */ -#define _(type, code) EXCEPTION_HANDLER_IMPL(type, code) -RV_EXCEPTION_LIST +/* RISC-V trap handlers */ +#define _(type, code) TRAP_HANDLER_IMPL(type, code) +RV_TRAP_LIST #undef _ /* wrap load/store and insn misaligned handler @@ -135,10 +201,11 @@ 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; \ } + /* get current time in microsecnds and update csr_time register */ static inline void update_time(riscv_t *rv) { @@ -164,6 +231,10 @@ static uint32_t *csr_get_ptr(riscv_t *rv, uint32_t csr) return (uint32_t *) (&rv->csr_misa); /* Machine Trap Handling */ + case CSR_MEDELEG: /* Machine Exception Delegation Register */ + return (uint32_t *) (&rv->csr_medeleg); + case CSR_MIDELEG: /* Machine Interrupt Delegation Register */ + return (uint32_t *) (&rv->csr_mideleg); case CSR_MSCRATCH: /* Machine Scratch Register */ return (uint32_t *) (&rv->csr_mscratch); case CSR_MEPC: /* Machine Exception Program Counter */ @@ -196,6 +267,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; } @@ -220,7 +311,16 @@ static uint32_t csr_csrrw(riscv_t *rv, uint32_t csr, uint32_t val) out &= FFLAG_MASK; #endif - *c = val; + if (c == &rv->csr_satp) { + const uint8_t mode_sv32 = val >> 31; + if (mode_sv32) + *c = val & MASK(22); /* store ppn */ + else /* bare mode */ + *c = 0; /* virtual mem addr maps to same physical mem addr directly + */ + } else { + *c = val; + } return out; } @@ -253,6 +353,7 @@ static uint32_t csr_csrrc(riscv_t *rv, uint32_t csr, uint32_t val) if (!c) return 0; + uint32_t out = *c; #if RV32_HAS(EXT_F) if (csr == CSR_FFLAGS) @@ -377,9 +478,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 */ @@ -461,7 +562,7 @@ 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->io.mem_write_w(rv, addr, rv->X[fuse[i].rs2]); } PC += ir->imm2 * 4; if (unlikely(RVOP_NO_NEXT(ir))) { @@ -485,7 +586,7 @@ 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->X[fuse[i].rd] = rv->io.mem_read_w(rv, addr); } PC += ir->imm2 * 4; if (unlikely(RVOP_NO_NEXT(ir))) { @@ -584,21 +685,25 @@ static void block_translate(riscv_t *rv, block_t *block) block->pc_start = block->pc_end = rv->PC; rv_insn_t *prev_ir = NULL; - rv_insn_t *ir = mpool_calloc(rv->block_ir_mp); + rv_insn_t *ir = mpool_alloc(rv->block_ir_mp); block->ir_head = ir; /* translate the basic block */ while (true) { + memset(ir, 0, sizeof(rv_insn_t)); + if (prev_ir) 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); + // printf("next insn addr: 0x%x, next insn: 0x%x, PC: 0x%x\n", + // block->pc_end, insn, rv->PC); /* 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]; @@ -626,7 +731,7 @@ static void block_translate(riscv_t *rv, block_t *block) break; } - ir = mpool_calloc(rv->block_ir_mp); + ir = mpool_alloc(rv->block_ir_mp); } assert(prev_ir); @@ -1048,16 +1153,324 @@ void rv_step(void *arg) #endif } +#if RV32_HAS(SYSTEM) +static void trap_handler(riscv_t *rv, trap_t type UNUSED) +{ + 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); + } +} + +static bool ppn_is_valid(riscv_t *rv, uint32_t ppn) +{ + vm_attr_t *attr = PRIV(rv); + const uint32_t nr_pg_max = attr->mem_size / RV_PG_SIZE; + return ppn < nr_pg_max; +} + +#define PAGE_TABLE(ppn) \ + ppn_is_valid(rv, ppn) \ + ? (uint32_t *) (attr->mem->mem_base + (ppn << (RV_PG_SHIFT))) \ + : NULL + +/* Walk through page tables and get the corresponding PTE by virtual address if + * exists + * @rv: RISC-V emulator + * @addr: virtual address + * @return: NULL if a not found or fault else the corresponding PTE + */ +static uint32_t *mmu_walk(riscv_t *rv, + const uint32_t addr, + uint32_t *level, + uint32_t **pte_ref) +{ + vm_attr_t *attr = PRIV(rv); + uint32_t ppn = rv->csr_satp; + if (ppn == 0) /* Bare mode */ + return NULL; + + /* root page table */ + uint32_t *page_table = PAGE_TABLE(ppn); + if (!page_table) + return NULL; + + for (int i = 1; i >= 0; i--) { + *level = 2 - i; + uint32_t vpn = + (addr >> RV_PG_SHIFT >> (i * (RV_PG_SHIFT - 2))) & MASK(10); + uint32_t *pte = page_table + vpn; + *pte_ref = pte; + + /* PTE XWRV bit in order */ + uint8_t XWRV_bit = (*pte & MASK(4)); + switch (XWRV_bit) { + case 0b0001: /* next level of the page table */ + ppn = (*pte >> (RV_PG_SHIFT - 2)); + page_table = PAGE_TABLE(ppn); + if (!page_table) + return NULL; + break; + case 0b0011: + case 0b0111: + case 0b1001: + case 0b1011: + case 0b1111: + ppn = (*pte >> (RV_PG_SHIFT - 2)); + if (*level == 1 && + unlikely(ppn & MASK(10))) /* misaligned superpage */ + return NULL; + return pte; /* leaf PTE */ + case 0b0101: + case 0b1101: + return NULL; + } + } + + return NULL; +} + +/* Verify the PTE and generate corresponding faults if needed + * @op: the operation + * @rv: RISC-V emulator + * @pte: to be verified pte + * @addr: the corresponding virtual address to cause fault + * @return: false if a any fault is generated which caused by violating the + * access permission else true + */ +/* FIXME: handle access fault, addr out of range check */ +#define MMU_FAULT_CHECK(op, rv, pte, addr, access_bits) \ + mmu_##op##_fault_check(rv, pte, addr, access_bits) +#define MMU_FAULT_CHECK_IMPL(op, pgfault) \ + static bool mmu_##op##_fault_check(riscv_t *rv, uint32_t *pte, \ + uint32_t addr, uint32_t access_bits) \ + { \ + if (pte && (!(*pte & PTE_V) || (!(*pte & PTE_R) && (*pte & PTE_W)))) { \ + rv->is_trapped = true; \ + rv_trap_##pgfault(rv, addr); \ + return false; \ + } \ + if (pte && (!(*pte & PTE_X) && (access_bits & PTE_X))) { \ + rv->is_trapped = true; \ + rv_trap_##pgfault(rv, addr); \ + return false; \ + } \ + if (pte && \ + ((!(SSTATUS_MXR & rv->csr_sstatus) && !(*pte & PTE_R) && \ + (access_bits & PTE_R)) || \ + ((SSTATUS_MXR & rv->csr_sstatus) && \ + !((*pte & PTE_R) | (*pte & PTE_X)) && (access_bits & PTE_R)))) { \ + rv->is_trapped = true; \ + rv_trap_##pgfault(rv, addr); \ + return false; \ + } \ + if (pte && (MSTATUS_MPRV & rv->csr_mstatus && \ + rv->priv_mode == RV_PRIV_S_MODE)) { \ + if (!(MSTATUS_SUM & rv->csr_mstatus) && (*pte & PTE_U)) { \ + rv->is_trapped = true; \ + rv_trap_##pgfault(rv, addr); \ + return false; \ + } \ + } \ + /* PTE not found, map it in handler */ \ + if (!pte && rv->csr_satp) { \ + rv->is_trapped = true; \ + rv_trap_##pgfault(rv, addr); \ + return true; \ + } \ + /* valid PTE */ \ + return true; \ + } + +MMU_FAULT_CHECK_IMPL(ifetch, pagefault_insn) +MMU_FAULT_CHECK_IMPL(read, pagefault_load) +MMU_FAULT_CHECK_IMPL(write, pagefault_store) + +#define get_ppn_and_offset(ppn, offset) \ + do { \ + ppn = *pte >> (RV_PG_SHIFT - 2) << RV_PG_SHIFT; \ + offset = level == 1 ? addr & MASK((RV_PG_SHIFT + 10)) \ + : addr & MASK(RV_PG_SHIFT); \ + } while (0) + +uint32_t mmu_ifetch(riscv_t *rv, const uint32_t addr) +{ + uint32_t level; + uint32_t *pte_ref; + uint32_t *pte = mmu_walk(rv, addr, &level, &pte_ref); + bool ok = MMU_FAULT_CHECK(ifetch, rv, pte, addr, PTE_X); + if (unlikely(!ok)) + return 0; + + pte = pte_ref; /* PTE should be valid now */ + + if (rv->csr_satp) { + uint32_t ppn; + uint32_t offset; + get_ppn_and_offset(ppn, offset); + return memory_ifetch(ppn | offset); + } + return memory_ifetch(addr); +} + +uint32_t mmu_read_w(riscv_t *rv, const uint32_t addr) +{ + uint32_t level; + uint32_t *pte_ref; + uint32_t *pte = mmu_walk(rv, addr, &level, &pte_ref); + bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R); + if (unlikely(!ok)) + return 0; + + pte = pte_ref; /* PTE should be valid now */ + + if (rv->csr_satp) { + uint32_t ppn; + uint32_t offset; + get_ppn_and_offset(ppn, offset); + return memory_read_w(ppn | offset); + } + return memory_read_w(addr); +} + +uint16_t mmu_read_s(riscv_t *rv, const uint32_t addr) +{ + uint32_t level; + uint32_t *pte_ref; + uint32_t *pte = mmu_walk(rv, addr, &level, &pte_ref); + bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R); + if (unlikely(!ok)) + return 0; + + pte = pte_ref; /* PTE should be valid now */ + + if (rv->csr_satp) { + uint32_t ppn; + uint32_t offset; + get_ppn_and_offset(ppn, offset); + return memory_read_s(ppn | offset); + } + return memory_read_s(addr); +} + +uint8_t mmu_read_b(riscv_t *rv, const uint32_t addr) +{ + uint32_t level; + uint32_t *pte_ref; + uint32_t *pte = mmu_walk(rv, addr, &level, &pte_ref); + bool ok = MMU_FAULT_CHECK(read, rv, pte, addr, PTE_R); + if (unlikely(!ok)) + return 0; + + pte = pte_ref; /* PTE should be valid now */ + + if (rv->csr_satp) { + uint32_t ppn; + uint32_t offset; + get_ppn_and_offset(ppn, offset); + return memory_read_b(ppn | offset); + } + return memory_read_b(addr); +} + +void mmu_write_w(riscv_t *rv, const uint32_t addr, const uint32_t val) +{ + uint32_t level; + uint32_t *pte_ref; + uint32_t *pte = mmu_walk(rv, addr, &level, &pte_ref); + bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W); + if (unlikely(!ok)) + return; + + pte = pte_ref; /* PTE should be valid now */ + + if (rv->csr_satp) { + uint32_t ppn; + uint32_t offset; + get_ppn_and_offset(ppn, offset); + return memory_write_w(ppn | offset, (uint8_t *) &val); + } + return memory_write_w(addr, (uint8_t *) &val); +} + +void mmu_write_s(riscv_t *rv, const uint32_t addr, const uint16_t val) +{ + uint32_t level; + uint32_t *pte_ref; + uint32_t *pte = mmu_walk(rv, addr, &level, &pte_ref); + bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W); + if (unlikely(!ok)) + return; + + pte = pte_ref; /* PTE should be valid now */ + + if (rv->csr_satp) { + uint32_t ppn; + uint32_t offset; + get_ppn_and_offset(ppn, offset); + return memory_write_s(ppn | offset, (uint8_t *) &val); + } + return memory_write_s(addr, (uint8_t *) &val); +} + +void mmu_write_b(riscv_t *rv, const uint32_t addr, const uint8_t val) +{ + uint32_t level; + uint32_t *pte_ref; + uint32_t *pte = mmu_walk(rv, addr, &level, &pte_ref); + bool ok = MMU_FAULT_CHECK(write, rv, pte, addr, PTE_W); + if (unlikely(!ok)) + return; + + pte = pte_ref; /* PTE should be valid now */ + + + if (rv->csr_satp) { + uint32_t ppn; + uint32_t offset; + get_ppn_and_offset(ppn, offset); + return memory_write_b(ppn | offset, (uint8_t *) &val); + } + return memory_write_b(addr, (uint8_t *) &val); +} + +riscv_io_t mmu_io = { + /* memory read interface */ + .mem_ifetch = mmu_ifetch, + .mem_read_w = mmu_read_w, + .mem_read_s = mmu_read_s, + .mem_read_b = mmu_read_b, + + /* memory write interface */ + .mem_write_w = mmu_write_w, + .mem_write_s = mmu_write_s, + .mem_write_b = mmu_write_b, + + /* system services or essential routines */ + .on_ecall = ecall_handler, + .on_ebreak = ebreak_handler, + .on_memcpy = memcpy_handler, + .on_memset = memset_handler, +}; +#endif /* SYSTEM */ + 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); } diff --git a/src/feature.h b/src/feature.h index ee27936a..93eb6160 100644 --- a/src/feature.h +++ b/src/feature.h @@ -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 diff --git a/src/gdbstub.c b/src/gdbstub.c index 663458d6..d9d635b7 100644 --- a/src/gdbstub.c +++ b/src/gdbstub.c @@ -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; @@ -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; } diff --git a/src/main.c b/src/main.c index d1a85a90..67cd01c1 100644 --- a/src/main.c +++ b/src/main.c @@ -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, @@ -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); diff --git a/src/riscv.c b/src/riscv.c index a0d385cc..9ac77ac5 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -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)); \ @@ -222,6 +224,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); @@ -261,9 +266,30 @@ riscv_t *rv_create(riscv_user_t rv_attr) .on_memset = memset_handler, }; memcpy(&rv->io, &io, sizeof(riscv_io_t)); - } else { + } +#if RV32_HAS(SYSTEM) + 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)); } +#endif /* SYSTEM */ /* default standard stream. * rv_remap_stdstream can be called to overwrite them @@ -342,7 +368,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); @@ -501,6 +527,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; @@ -526,7 +555,6 @@ void rv_reset(riscv_t *rv, riscv_word_t pc) rv->csr_misa |= MISA_M; #endif - rv->halt = false; } diff --git a/src/riscv.h b/src/riscv.h index c4b7091b..b8183c8c 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -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: @@ -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); @@ -273,9 +386,20 @@ typedef struct { char *elf_program; } vm_user_t; +/* FIXME: replace with kernel image, dtb, etc */ +#if RV32_HAS(SYSTEM) +typedef struct { + char *elf_program; +} vm_system_t; +#endif /* SYSTEM */ + typedef struct { vm_user_t *user; - /* TODO: system emulator stuff */ + +#if RV32_HAS(SYSTEM) + vm_system_t *system; +#endif /* SYSTEM */ + } vm_data_t; typedef struct { diff --git a/src/riscv_private.h b/src/riscv_private.h index 33d8cb3e..8a74e62c 100644 --- a/src/riscv_private.h +++ b/src/riscv_private.h @@ -29,6 +29,28 @@ enum { CSR_FRM = 0x002, /* Floating-point dynamic rounding mode */ CSR_FCSR = 0x003, /* Floating-point control and status register */ + /* Supervisor trap setup */ + CSR_SSTATUS = 0x100, /* Supervisor status register */ + CSR_SIE = 0x104, /* Supervisor interrupt-enable register */ + CSR_STVEC = 0x105, /* Supervisor trap-handler base address */ + CSR_SCOUNTEREN = 0x106, /* Supervisor counter enable */ + + /* Supervisor trap handling */ + CSR_SSCRATCH = 0x140, /* Supervisor register for machine trap handlers */ + CSR_SEPC = 0x141, /* Supervisor exception program counter */ + CSR_SCAUSE = 0x142, /* Supervisor trap cause */ + CSR_STVAL = 0x143, /* Supervisor bad address or instruction */ + CSR_SIP = 0x144, /* Supervisor interrupt pending */ + + /* Supervisor protection and translation */ + CSR_SATP = 0x180, /* Supervisor address translation and protection */ + + /* Machine information registers */ + CSR_MVENDORID = 0xF11, /* Vendor ID */ + CSR_MARCHID = 0xF12, /* Architecture ID */ + CSR_MIMPID = 0xF13, /* Implementation ID */ + CSR_MHARTID = 0xF14, /* Hardware thread ID */ + /* Machine trap setup */ CSR_MSTATUS = 0x300, /* Machine status register */ CSR_MISA = 0x301, /* ISA and extensions */ @@ -54,11 +76,6 @@ enum { CSR_CYCLEH = 0xC80, CSR_TIMEH = 0xC81, CSR_INSTRETH = 0xC82, - - CSR_MVENDORID = 0xF11, /* Vendor ID */ - CSR_MARCHID = 0xF12, /* Architecture ID */ - CSR_MIMPID = 0xF13, /* Implementation ID */ - CSR_MHARTID = 0xF14, /* Hardware thread ID */ }; /* translated basic block */ @@ -135,11 +152,27 @@ struct riscv_internal { uint32_t csr_mscratch; /* Scratch register for machine trap handler */ uint32_t csr_mepc; /* Machine exception program counter */ uint32_t csr_mip; /* Machine interrupt pending */ + uint32_t csr_mie; /* Machine interrupt enable */ + uint32_t csr_mideleg; /* Machine interrupt delegation register */ + uint32_t csr_medeleg; /* Machine exception delegation register */ uint32_t csr_mvendorid; /* vendor ID */ uint32_t csr_marchid; /* Architecture ID */ uint32_t csr_mimpid; /* Implementation ID */ uint32_t csr_mbadaddr; + uint32_t csr_sstatus; /* supervisor status register */ + uint32_t csr_stvec; /* supervisor trap vector base address register */ + uint32_t csr_sip; /* supervisor interrupt pending register */ + uint32_t csr_sie; /* supervisor interrupt enable register */ + uint32_t csr_scounteren; /* supervisor counter-enable register */ + uint32_t csr_sscratch; /* supervisor scratch register */ + uint32_t csr_sepc; /* supervisor exception program counter */ + uint32_t csr_scause; /* supervisor cause register */ + uint32_t csr_stval; /* supervisor trap value register */ + uint32_t csr_satp; /* supervisor address translation and protection */ + + uint32_t priv_mode; /* U-mode or S-mode or M-mode */ + bool compressed; /**< current instruction is compressed or not */ #if !RV32_HAS(JIT) block_map_t block_map; /**< basic block map */ @@ -169,6 +202,9 @@ struct riscv_internal { */ bool is_interrupted; #endif + + /* The flag to indicate the current emulation is in a trap */ + bool is_trapped; }; /* sign extend a 16 bit value */ diff --git a/src/rv32_template.c b/src/rv32_template.c index cf66ced6..54b8abc8 100644 --- a/src/rv32_template.c +++ b/src/rv32_template.c @@ -119,10 +119,8 @@ */ /* Internal */ -RVOP( - nop, - { rv->X[rv_reg_zero] = 0; }, - GEN({/* no operation */})) +#include "decode.h" +RVOP(nop, { rv->X[rv_reg_zero] = 0; }, GEN({/* no operation */})) /* LUI is used to build 32-bit constants and uses the U-type format. LUI * places the U-immediate value in the top 20 bits of the destination @@ -158,8 +156,10 @@ RVOP( jal, { const uint32_t pc = PC; + /* Jump */ PC += ir->imm; + /* link with return address */ if (ir->rd) rv->X[ir->rd] = pc + 4; @@ -202,19 +202,23 @@ RVOP( #if !RV32_HAS(JIT) #define LOOKUP_OR_UPDATE_BRANCH_HISTORY_TABLE() \ /* lookup branch history table */ \ - for (int i = 0; i < HISTORY_SIZE; i++) { \ - if (ir->branch_table->PC[i] == PC) { \ - MUST_TAIL return ir->branch_table->target[i]->impl( \ - rv, ir->branch_table->target[i], cycle, PC); \ + if (!rv->is_trapped) { \ + for (int i = 0; i < HISTORY_SIZE; i++) { \ + if (ir->branch_table->PC[i] == PC) { \ + MUST_TAIL return ir->branch_table->target[i]->impl( \ + rv, ir->branch_table->target[i], cycle, PC); \ + } \ + } \ + block_t *block = block_find(&rv->block_map, PC); \ + if (block) { \ + /* update branch history table */ \ + ir->branch_table->PC[ir->branch_table->idx] = PC; \ + ir->branch_table->target[ir->branch_table->idx] = block->ir_head; \ + ir->branch_table->idx = \ + (ir->branch_table->idx + 1) % HISTORY_SIZE; \ + MUST_TAIL return block->ir_head->impl(rv, block->ir_head, cycle, \ + PC); \ } \ - } \ - block_t *block = block_find(&rv->block_map, PC); \ - if (block) { \ - /* update branch history table */ \ - ir->branch_table->PC[ir->branch_table->idx] = PC; \ - ir->branch_table->target[ir->branch_table->idx] = block->ir_head; \ - ir->branch_table->idx = (ir->branch_table->idx + 1) % HISTORY_SIZE; \ - MUST_TAIL return block->ir_head->impl(rv, block->ir_head, cycle, PC); \ } #else #define LOOKUP_OR_UPDATE_BRANCH_HISTORY_TABLE() \ @@ -257,11 +261,14 @@ RVOP( jalr, { const uint32_t pc = PC; + /* jump */ PC = (rv->X[ir->rs1] + ir->imm) & ~1U; + /* link */ if (ir->rd) rv->X[ir->rd] = pc + 4; + /* check instruction misaligned */ #if !RV32_HAS(EXT_C) RV_EXC_MISALIGN_HANDLER(pc, insn, false, 0); @@ -506,7 +513,7 @@ RVOP( lb, { rv->X[ir->rd] = - sign_extend_b(rv->io.mem_read_b(rv->X[ir->rs1] + ir->imm)); + sign_extend_b(rv->io.mem_read_b(rv, rv->X[ir->rs1] + ir->imm)); }, GEN({ mem; @@ -523,7 +530,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + ir->imm; RV_EXC_MISALIGN_HANDLER(1, load, false, 1); - rv->X[ir->rd] = sign_extend_h(rv->io.mem_read_s(addr)); + rv->X[ir->rd] = sign_extend_h(rv->io.mem_read_s(rv, addr)); }, GEN({ mem; @@ -540,7 +547,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - rv->X[ir->rd] = rv->io.mem_read_w(addr); + rv->X[ir->rd] = rv->io.mem_read_w(rv, addr); }, GEN({ mem; @@ -554,7 +561,7 @@ RVOP( /* LBU: Load Byte Unsigned */ RVOP( lbu, - { rv->X[ir->rd] = rv->io.mem_read_b(rv->X[ir->rs1] + ir->imm); }, + { rv->X[ir->rd] = rv->io.mem_read_b(rv, rv->X[ir->rs1] + ir->imm); }, GEN({ mem; rald, VR0, rs1; @@ -570,7 +577,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + ir->imm; RV_EXC_MISALIGN_HANDLER(1, load, false, 1); - rv->X[ir->rd] = rv->io.mem_read_s(addr); + rv->X[ir->rd] = rv->io.mem_read_s(rv, addr); }, GEN({ mem; @@ -590,7 +597,7 @@ RVOP( /* SB: Store Byte */ RVOP( sb, - { rv->io.mem_write_b(rv->X[ir->rs1] + ir->imm, rv->X[ir->rs2]); }, + { rv->io.mem_write_b(rv, rv->X[ir->rs1] + ir->imm, rv->X[ir->rs2]); }, GEN({ mem; rald, VR0, rs1; @@ -606,7 +613,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + ir->imm; RV_EXC_MISALIGN_HANDLER(1, store, false, 1); - rv->io.mem_write_s(addr, rv->X[ir->rs2]); + rv->io.mem_write_s(rv, addr, rv->X[ir->rs2]); }, GEN({ mem; @@ -623,7 +630,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, store, false, 1); - rv->io.mem_write_w(addr, rv->X[ir->rs2]); + rv->io.mem_write_w(rv, addr, rv->X[ir->rs2]); }, GEN({ mem; @@ -989,8 +996,17 @@ RVOP( RVOP( sret, { - /* FIXME: Implement */ - return false; + rv->is_trapped = false; + rv->priv_mode = (rv->csr_sstatus & MSTATUS_SPP) >> MSTATUS_SPP_SHIFT; + rv->csr_sstatus &= ~(MSTATUS_SPP); + + const uint32_t sstatus_spie = + (rv->csr_sstatus & SSTATUS_SPIE) >> SSTATUS_SPIE_SHIFT; + rv->csr_sstatus |= (sstatus_spie << SSTATUS_SIE_SHIFT); + rv->csr_sstatus |= SSTATUS_SPIE; + + rv->PC = rv->csr_sepc; + return true; }, GEN({ assert; /* FIXME: Implement */ @@ -1007,11 +1023,19 @@ RVOP( assert; /* FIXME: Implement */ })) -/* MRET: return from traps in U-mode */ +/* MRET: return from traps in M-mode */ RVOP( mret, { - rv->csr_mstatus = MSTATUS_MPIE; + rv->is_trapped = false; + rv->priv_mode = (rv->csr_mstatus & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT; + rv->csr_mstatus &= ~(MSTATUS_MPP); + + const uint32_t mstatus_mpie = + (rv->csr_mstatus & MSTATUS_MPIE) >> MSTATUS_MPIE_SHIFT; + rv->csr_mstatus |= (mstatus_mpie << MSTATUS_MIE_SHIFT); + rv->csr_mstatus |= MSTATUS_MPIE; + rv->PC = rv->csr_mepc; return true; }, @@ -1332,7 +1356,7 @@ RVOP( const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); if (ir->rd) - rv->X[ir->rd] = rv->io.mem_read_w(addr); + rv->X[ir->rd] = rv->io.mem_read_w(rv, addr); /* skip registration of the 'reservation set' * FIXME: unimplemented */ @@ -1350,7 +1374,7 @@ RVOP( */ const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, store, false, 1); - rv->io.mem_write_w(addr, rv->X[ir->rs2]); + rv->io.mem_write_w(rv, addr, rv->X[ir->rs2]); rv->X[ir->rd] = 0; }, GEN({ @@ -1363,11 +1387,11 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; - rv->io.mem_write_w(addr, value2); + rv->io.mem_write_w(rv, addr, value2); }, GEN({ assert; /* FIXME: Implement */ @@ -1379,12 +1403,12 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const uint32_t res = value1 + value2; - rv->io.mem_write_w(addr, res); + rv->io.mem_write_w(rv, addr, res); }, GEN({ assert; /* FIXME: Implement */ @@ -1396,12 +1420,12 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const uint32_t res = value1 ^ value2; - rv->io.mem_write_w(addr, res); + rv->io.mem_write_w(rv, addr, res); }, GEN({ assert; /* FIXME: Implement */ @@ -1413,12 +1437,12 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const uint32_t res = value1 & value2; - rv->io.mem_write_w(addr, res); + rv->io.mem_write_w(rv, addr, res); }, GEN({ assert; /* FIXME: Implement */ @@ -1430,12 +1454,12 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const uint32_t res = value1 | value2; - rv->io.mem_write_w(addr, res); + rv->io.mem_write_w(rv, addr, res); }, GEN({ assert; /* FIXME: Implement */ @@ -1447,14 +1471,14 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const int32_t a = value1; const int32_t b = value2; const uint32_t res = a < b ? value1 : value2; - rv->io.mem_write_w(addr, res); + rv->io.mem_write_w(rv, addr, res); }, GEN({ assert; /* FIXME: Implement */ @@ -1466,14 +1490,14 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const int32_t a = value1; const int32_t b = value2; const uint32_t res = a > b ? value1 : value2; - rv->io.mem_write_w(addr, res); + rv->io.mem_write_w(rv, addr, res); }, GEN({ assert; /* FIXME: Implement */ @@ -1485,12 +1509,12 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const uint32_t ures = value1 < value2 ? value1 : value2; - rv->io.mem_write_w(addr, ures); + rv->io.mem_write_w(rv, addr, ures); }, GEN({ assert; /* FIXME: Implement */ @@ -1502,12 +1526,12 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1]; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - const uint32_t value1 = rv->io.mem_read_w(addr); + const uint32_t value1 = rv->io.mem_read_w(rv, addr); const uint32_t value2 = rv->X[ir->rs2]; if (ir->rd) rv->X[ir->rd] = value1; const uint32_t ures = value1 > value2 ? value1 : value2; - rv->io.mem_write_w(addr, ures); + rv->io.mem_write_w(rv, addr, ures); }, GEN({ assert; /* FIXME: Implement */ @@ -1524,7 +1548,7 @@ RVOP( /* copy into the float register */ const uint32_t addr = rv->X[ir->rs1] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - rv->F[ir->rd].v = rv->io.mem_read_w(addr); + rv->F[ir->rd].v = rv->io.mem_read_w(rv, addr); }, GEN({ assert; /* FIXME: Implement */ @@ -1537,7 +1561,7 @@ RVOP( /* copy from float registers */ const uint32_t addr = rv->X[ir->rs1] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, store, false, 1); - rv->io.mem_write_w(addr, rv->F[ir->rs2].v); + rv->io.mem_write_w(rv, addr, rv->F[ir->rs2].v); }, GEN({ assert; /* FIXME: Implement */ @@ -1898,7 +1922,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + (uint32_t) ir->imm; RV_EXC_MISALIGN_HANDLER(3, load, true, 1); - rv->X[ir->rd] = rv->io.mem_read_w(addr); + rv->X[ir->rd] = rv->io.mem_read_w(rv, addr); }, GEN({ mem; @@ -1919,7 +1943,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + (uint32_t) ir->imm; RV_EXC_MISALIGN_HANDLER(3, store, true, 1); - rv->io.mem_write_w(addr, rv->X[ir->rs2]); + rv->io.mem_write_w(rv, addr, rv->X[ir->rs2]); }, GEN({ mem; @@ -2272,7 +2296,7 @@ RVOP( { const uint32_t addr = rv->X[rv_reg_sp] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, load, true, 1); - rv->X[ir->rd] = rv->io.mem_read_w(addr); + rv->X[ir->rd] = rv->io.mem_read_w(rv, addr); }, GEN({ mem; @@ -2378,7 +2402,7 @@ RVOP( { const uint32_t addr = rv->X[rv_reg_sp] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, store, true, 1); - rv->io.mem_write_w(addr, rv->X[ir->rs2]); + rv->io.mem_write_w(rv, addr, rv->X[ir->rs2]); }, GEN({ mem; @@ -2397,7 +2421,7 @@ RVOP( { const uint32_t addr = rv->X[rv_reg_sp] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - rv->F[ir->rd].v = rv->io.mem_read_w(addr); + rv->F[ir->rd].v = rv->io.mem_read_w(rv, addr); }, GEN({ assert; /* FIXME: Implement */ @@ -2409,7 +2433,7 @@ RVOP( { const uint32_t addr = rv->X[rv_reg_sp] + ir->imm; RV_EXC_MISALIGN_HANDLER(3, store, false, 1); - rv->io.mem_write_w(addr, rv->F[ir->rs2].v); + rv->io.mem_write_w(rv, addr, rv->F[ir->rs2].v); }, GEN({ assert; /* FIXME: Implement */ @@ -2421,7 +2445,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + (uint32_t) ir->imm; RV_EXC_MISALIGN_HANDLER(3, load, false, 1); - rv->F[ir->rd].v = rv->io.mem_read_w(addr); + rv->F[ir->rd].v = rv->io.mem_read_w(rv, addr); }, GEN({ assert; /* FIXME: Implement */ @@ -2433,7 +2457,7 @@ RVOP( { const uint32_t addr = rv->X[ir->rs1] + (uint32_t) ir->imm; RV_EXC_MISALIGN_HANDLER(3, store, false, 1); - rv->io.mem_write_w(addr, rv->F[ir->rs2].v); + rv->io.mem_write_w(rv, addr, rv->F[ir->rs2].v); }, GEN({ assert; /* FIXME: Implement */ diff --git a/src/syscall.c b/src/syscall.c index 1c25408a..01021218 100644 --- a/src/syscall.c +++ b/src/syscall.c @@ -34,9 +34,11 @@ _(brk, 214) \ _(clock_gettime, 403) \ _(open, 1024) \ - _(sbi_base, 0x10) \ - _(sbi_timer, 0x54494D45) \ - _(sbi_rst, 0x53525354) \ + IIF(RV32_HAS(SYSTEM))( \ + _(sbi_base, 0x10) \ + _(sbi_timer, 0x54494D45) \ + _(sbi_rst, 0x53525354) \ + ) \ IIF(RV32_HAS(SDL))( \ _(draw_frame, 0xBEEF) \ _(setup_queue, 0xC0DE) \ @@ -372,6 +374,8 @@ extern void syscall_setup_audio(riscv_t *rv); extern void syscall_control_audio(riscv_t *rv); #endif +#if RV32_HAS(SYSTEM) + /* SBI related system calls */ static void syscall_sbi_timer(riscv_t *rv) { @@ -459,6 +463,7 @@ static void syscall_sbi_rst(riscv_t *rv) break; } } +#endif /* SYSTEM */ void syscall_handler(riscv_t *rv) { diff --git a/tests/system/Makefile b/tests/system/Makefile new file mode 100644 index 00000000..7ed3ddef --- /dev/null +++ b/tests/system/Makefile @@ -0,0 +1,27 @@ +PREFIX ?= riscv32-unknown-elf- +ARCH = -march=rv32izicsr +LINKER_SCRIPT = linker.ld + +DEBUG_CFLAGS = -g +CFLAGS = -c +LDFLAGS = -T +EXEC = vm.elf + +CC = $(PREFIX)gcc +AS = $(PREFIX)as +LD = $(PREFIX)ld +OBJDUMP = $(PREFIX)objdump + +deps = main.o setup.o vm_setup.o + +all: + $(CC) $(DEBUG_CLAGS) $(CFLAGS) main.c + $(CC) $(DEBUG_CLAGS) $(CFLAGS) vm_setup.c + $(AS) $(DEBUG_CLAGS) $(ARCH) setup.S -o setup.o + $(LD) $(LDFLAGS) $(LINKER_SCRIPT) -o $(EXEC) $(deps) + +dump: + $(OBJDUMP) -Ds $(EXEC) | less + +clean: + rm $(EXEC) $(deps) diff --git a/tests/system/entry.S b/tests/system/entry.S new file mode 100644 index 00000000..738dd37d --- /dev/null +++ b/tests/system/entry.S @@ -0,0 +1,11 @@ +.globl foo +foo: + li x2, 32 + +STACK_START = .end + 4096 + +.globl _start +_start: + li a0, 100 + la sp, STACK_START + j main diff --git a/tests/system/linker.ld b/tests/system/linker.ld new file mode 100644 index 00000000..50e3934c --- /dev/null +++ b/tests/system/linker.ld @@ -0,0 +1,31 @@ +OUTPUT_ARCH( "riscv" ) + +ENTRY(_start) + +SECTIONS +{ + . = 0x00000004; + .text.main : { *(.text.main) } + . = ALIGN(0x1000); + .mystring : { *(.mystring) } + .data.main : { *(.data.main) } + .bss.main : { *(.bss.main) } + + .text : { + . = 0x7fffeffc; + *(.text.setup) + *(.text.vm_setup) + } + . = ALIGN(0x1000); + .data : { + *(.data.setup) + *(.data.vm_setup) + } + .bss : { + *(.bss.setup) + *(.bss.vm_setup) + } + + . = ALIGN(0x1000); + _end = .; +} diff --git a/tests/system/main.c b/tests/system/main.c new file mode 100644 index 00000000..98142a9e --- /dev/null +++ b/tests/system/main.c @@ -0,0 +1,98 @@ +#define SECTION_TEXT_MAIN __attribute__((section(".text.main"))) +#define SECTION_DATA_MAIN __attribute__((section(".data.main"))) +#define SECTION_BSS_MAIN __attribute__((section(".bss.main"))) + +#define printstr(ptr, length) \ + do { \ + asm volatile( \ + "add a7, x0, 0x40;" \ + "add a0, x0, 0x1;" /* stdout */ \ + "add a1, x0, %0;" \ + "mv a2, %1;" /* length character */ \ + "ecall;" \ + : \ + : "r"(ptr), "r"(length) \ + : "a0", "a1", "a2", "a7"); \ + } while (0) + +#define TEST_OUTPUT(msg, length) printstr(msg, length) + +#define TEST_LOGGER(msg) \ + { \ + char _msg[] = msg; \ + TEST_OUTPUT(_msg, sizeof(_msg)); \ + } + +#define SUCCESS 0 +#define FAIL 1 + +__attribute__((section(".mystring"))) const char pagefault_load_str[] = + "rv32emu"; + +int SECTION_TEXT_MAIN main() +{ + /* instruction fetch page fault test */ + int x = 100; /* trigger instruction page fault */ + int y = 200; + int z = x + y; + TEST_LOGGER("INSTRUCTION FETCH PAGE FAULT TEST PASSED!\n"); + + /* data load page fault test */ + char buf[8]; + /* Clear buffer */ + for (int i = 0; i < 8; i++) { + buf[i] = 0; + } + + char *qtr = 0x1000; /* first data page */ + /* should trigger load page fault and load pagefault_load_str */ + for (int i = 0; i < 8; i++) { + qtr = 0x1000; /* FIXME: weird result without this */ + buf[i] = *(qtr + i); + } + + for (int i = 0; i < 8; i++) { /* should not trigger load page fault */ + if (buf[i] != *(qtr + i)) { + TEST_LOGGER("[LOAD PAGE FAULT TEST] rv32emu string not match\n") + _exit(FAIL); + } + } + TEST_LOGGER("LOAD PAGE FAULT TEST PASSED!\n"); + + /* data store page fault test */ + /* Clear buffer */ + for (int i = 0; i < 8; i++) { + buf[i] = 0; + } + + char *ptr = 0x2000; /* second data page */ + *ptr = 'r'; /* trigger store page fault */ + ptr = 0x2000; /* FIXME: weird result without this */ + *(ptr + 1) = 'v'; /* should not trigger store page fault */ + ptr = 0x2000; /* FIXME: weird result without this */ + *(ptr + 2) = '3'; /* should not trigger store page fault */ + ptr = 0x2000; /* FIXME: weird result without this */ + *(ptr + 3) = '2'; /* should not trigger store page fault */ + ptr = 0x2000; /* FIXME: weird result without this */ + *(ptr + 4) = 'e'; /* should not trigger store page fault */ + ptr = 0x2000; /* FIXME: weird result without this */ + *(ptr + 5) = 'm'; /* should not trigger store page fault */ + ptr = 0x2000; /* FIXME: weird result without this */ + *(ptr + 6) = 'u'; /* should not trigger store page fault */ + ptr = 0x2000; /* FIXME: weird result without this */ + *(ptr + 7) = '\0'; /* should not trigger store page fault */ + + for (int i = 0; i < 8; i++) { /* should not trigger load page fault */ + buf[i] = *(ptr + i); + } + + for (int i = 0; i < 8; i++) { /* should not trigger load page fault */ + if (buf[i] != *(ptr + i)) { + TEST_LOGGER("[STORE PAGE FAULT TEST] rv32emu string not match\n") + _exit(FAIL); + } + } + TEST_LOGGER("STORE PAGE FAULT TEST PASSED!\n"); + + _exit(SUCCESS); +} diff --git a/tests/system/setup.S b/tests/system/setup.S new file mode 100644 index 00000000..3f5effdc --- /dev/null +++ b/tests/system/setup.S @@ -0,0 +1,162 @@ +.section .text.setup +TRAPFRAME_SIZE = 35 * 4 +PG_SIZE = 4096 +STACK_TOP = _end + PG_SIZE + +# FIXME: implement proper machine trap vector +# Since I assume that all interrupts and exceptions are +# delegated to S-mode software, so the machine trap +# vector is not that much important in here +machine_trap_vector: + j _exit; + +.globl _start +_start: + # init regs + li x1, 0 + li x2, 0 + li x3, 0 + li x4, 0 + li x5, 0 + li x6, 0 + li x7, 0 + li x8, 0 + li x9, 0 + li x10, 0 + li x11, 0 + li x12, 0 + li x13, 0 + li x14, 0 + li x15, 0 + li x16, 0 + li x17, 0 + li x18, 0 + li x19, 0 + li x20, 0 + li x21, 0 + li x22, 0 + li x23, 0 + li x24, 0 + li x25, 0 + li x26, 0 + li x27, 0 + li x28, 0 + li x29, 0 + li x30, 0 + li x31, 0 + + la sp, STACK_TOP - TRAPFRAME_SIZE + csrw mscratch, sp; + + la t0, machine_trap_vector + csrw mtvec, t0 + csrr t0, mtvec # for debugging + + # init virtual memory + j vm_boot + +.globl _exit +_exit: + li a7, 93 + ecall + +.globl user_entry +user_entry: + la sp, STACK_TOP - TRAPFRAME_SIZE + jalr x0, 0x4 # jump to user space main + +.globl supervisor_trap_entry +supervisor_trap_entry: + # get trapframe pointer (save a0 into scratch) + csrrw a0, sscratch, a0; + + # push regs into trapframe + sw x1, 0*4(a0); + sw x2, 1*4(a0); + sw x3, 2*4(a0); + sw x4, 3*4(a0); + sw x5, 4*4(a0); + sw x6, 5*4(a0); + sw x7, 6*4(a0); + sw x8, 7*4(a0); + sw x9, 8*4(a0); + sw x11, 10*4(a0); + sw x12, 11*4(a0); + sw x13, 12*4(a0); + sw x14, 13*4(a0); + sw x15, 14*4(a0); + sw x16, 15*4(a0); + sw x17, 16*4(a0); + sw x18, 17*4(a0); + sw x19, 18*4(a0); + sw x20, 19*4(a0); + sw x21, 20*4(a0); + sw x22, 21*4(a0); + sw x23, 22*4(a0); + sw x24, 23*4(a0); + sw x25, 24*4(a0); + sw x26, 25*4(a0); + sw x27, 26*4(a0); + sw x28, 27*4(a0); + sw x29, 28*4(a0); + sw x20, 29*4(a0); + sw x31, 30*4(a0); + + # load stack pointer and save trapframe pointer into scratch + csrrw t0, sscratch, a0; + sw t0, 9*4(a0); + + # push status, epc, badaddr, cause + csrr t0, sstatus; + sw t0, 31*4(a0); + csrr t0, sepc; + sw t0, 32*4(a0); + csrr t0, stval; + sw t0, 33*4(a0); + csrr t0, scause; + sw t0, 34*4(a0); + + csrr sp, sscratch; + j handle_trap; + +.globl pop_tf +pop_tf: + # a0 need to save trapframe pointer + # pop epc and regs from trapframe + lw t0, 32*4(a0) + csrw sepc, t0 + lw x1, 0*4(a0) + lw x2, 1*4(a0) + lw x3, 2*4(a0) + lw x4, 3*4(a0) + lw x5, 4*4(a0) + lw x6, 5*4(a0) + lw x7, 6*4(a0) + lw x8, 7*4(a0) + lw x9, 8*4(a0) + lw x11, 10*4(a0) + lw x12, 11*4(a0) + lw x13, 12*4(a0) + lw x14, 13*4(a0) + lw x15, 14*4(a0) + lw x16, 15*4(a0) + lw x17, 16*4(a0) + lw x18, 17*4(a0) + lw x19, 18*4(a0) + lw x20, 19*4(a0) + lw x21, 20*4(a0) + lw x22, 21*4(a0) + lw x23, 22*4(a0) + lw x24, 23*4(a0) + lw x25, 24*4(a0) + lw x26, 25*4(a0) + lw x27, 26*4(a0) + lw x28, 27*4(a0) + lw x29, 28*4(a0) + lw x20, 29*4(a0) + lw x31, 30*4(a0) + + # save trapframe pointer to sscratch + csrrw a0, sscratch, a0; + + sret diff --git a/tests/system/vm_setup.c b/tests/system/vm_setup.c new file mode 100644 index 00000000..9af7124a --- /dev/null +++ b/tests/system/vm_setup.c @@ -0,0 +1,413 @@ +#include +#include +#include + +#if !defined(__GNUC__) || !defined(__riscv) || 32 != __riscv_xlen +#error "32-bit RISC-V GNU toolchain is required" +#endif + +#define SECTION_TEXT_VMSETUP __attribute__((section(".text.vm_setup"))) +#define SECTION_DATA_VMSETUP __attribute__((section(".data.vm_setup"))) +#define SECTION_BSS_VMSETUP __attribute__((section(".bss.vm_setup"))) + +#define assert(x) \ + do { \ + if (!x) { \ + printf("Assertion failed '%s' at line %d of '%s'\n", #x); \ + _exit(1); \ + } \ + } while (0) + +#define read_csr(reg) \ + ({ \ + uint32_t __tmp; \ + asm volatile("csrr %0, " #reg : "=r"(__tmp)); \ + __tmp; \ + }) + +#define write_csr(reg, val) ({ asm volatile("csrw " #reg ", %0" ::"rK"(val)); }) + +#define swap_csr(reg, val) \ + ({ \ + uint32_t __tmp; \ + asm volatile("csrrw %0, " #reg ", %1" : "=r"(__tmp) : "rK"(val)); \ + __tmp; \ + }) + +#define set_csr(reg, bit) \ + ({ \ + uint32_t __tmp; \ + asm volatile("csrrs %0, " #reg ", %1" : "=r"(__tmp) : "rK"(bit)); \ + __tmp; \ + }) + +#define clear_csr(reg, bit) \ + ({ \ + uint32_t __tmp; \ + asm volatile("csrrc %0, " #reg ", %1" : "=r"(__tmp) : "rK"(bit)); \ + __tmp; \ + }) + +typedef struct { + uint32_t ra; + uint32_t sp; + uint32_t gp; + uint32_t tp; + uint32_t t0; + uint32_t t1; + uint32_t t2; + uint32_t s0; + uint32_t s1; + uint32_t a0; + uint32_t a1; + uint32_t a2; + uint32_t a3; + uint32_t a4; + uint32_t a5; + uint32_t a6; + uint32_t a7; + uint32_t s2; + uint32_t s3; + uint32_t s4; + uint32_t s5; + uint32_t s6; + uint32_t s7; + uint32_t s8; + uint32_t s9; + uint32_t s10; + uint32_t s11; + uint32_t t3; + uint32_t t4; + uint32_t t5; + uint32_t t6; + uint32_t status; + uint32_t epc; + uint32_t badvaddr; + uint32_t cause; +} trapframe_t; + +#define SV32_MODE 0x80000000 +#define BARE_MODE 0x00000000 +#define PG_SHIFT 12 +#define PG_SIZE (1U << 12) +#define FREE_FRAME_BASE 0x400000 +#define MAX_TEST_PG 32 /* FREE_FRAME_BASE - 0x41ffff */ +#define MEGA_PG (PG_SIZE << 10) /* 4 MiB */ + +#define CAUSE_USER_ECALL (1U << 8) +#define CAUSE_SUPERVISOR_ECALL (1U << 9) +#define CAUSE_FETCH_PAGE_FAULT (1U << 12) +#define CAUSE_LOAD_PAGE_FAULT (1U << 13) +#define CAUSE_STORE_PAGE_FAULT (1U << 15) + +#define SSTATUS_SUM (1U << 18) + +#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) + +extern void main(); +extern void _start(); +extern int user_entry(); +extern void supervisor_trap_entry(); +extern void pop_tf(trapframe_t *); +extern void _exit(int status); + +typedef uint32_t pte_t; +typedef struct { + pte_t addr; + void *next; +} freelist_t; + +freelist_t freelist_nodes[MAX_TEST_PG] SECTION_BSS_VMSETUP; +freelist_t *SECTION_BSS_VMSETUP freelist_head; +freelist_t *SECTION_BSS_VMSETUP freelist_tail; + +#define l1pt pt[0] +#define user_l1pt pt[1] +#define NPT 2 +#define PTES_PER_PT (1U << 10) +#define PTE_PPN_SHIFT 10 +pte_t pt[NPT][PTES_PER_PT] SECTION_BSS_VMSETUP + __attribute__((aligned(PG_SIZE))); + +#define MASK(n) (~((~0U << (n)))) +#define pa2kva(x) ((pte_t) (x) - (pte_t) (_start) - MEGA_PG) +#define uva2kva(x) ((pte_t) (x) - MEGA_PG) + +void *SECTION_TEXT_VMSETUP memcpy(void *ptr, void *src, size_t len) +{ + uint32_t *word_ptr = ptr; + uint32_t *word_src = src; + + while (len >= 4) { + *word_ptr++ = *word_src++; + len -= 4; + } + + char *byte_ptr = (char *) word_ptr; + char *byte_src = (char *) word_src; + while (len--) { + *byte_ptr++ = *byte_src++; + } + + return ptr; +} + +void *SECTION_TEXT_VMSETUP memset(void *ptr, int val, size_t len) +{ + uint32_t *word_ptr = ptr; + uint32_t write_word = (char) val; + write_word |= write_word << 8; + write_word |= write_word << 16; + + while (len >= 4) { + *word_ptr++ = write_word; + len -= 4; + } + + char *byte_ptr = (char *) word_ptr; + while (len--) { + *byte_ptr++ = val; + } + + return ptr; +} + +#define SYSCALL_WRITE 64 +int SECTION_TEXT_VMSETUP putchar(int ch) +{ + int syscall_nr = SYSCALL_WRITE; + asm volatile( + "mv a7, %0;" + "li a0, 0x1;" /* stdout */ + "add a1, x0, %1;" + "li a2, 0x1;" /* one character */ + "ecall;" + : + : "r"(syscall_nr), "r"(&ch) + : "a0", "a1", "a2", "a7"); + + return 0; +} + +int SECTION_TEXT_VMSETUP _puts(const char *s) +{ + int res = 0; + while (*s) { + putchar(*(s++)); + ++res; + } + + return res; +} + +int SECTION_TEXT_VMSETUP puts(const char *s) +{ + int res = 1; + res += _puts(s); + putchar('\n'); + + return res; +} + +char *SECTION_TEXT_VMSETUP itoa(uint32_t value, + int base, + int min_len, + char fill_char) +{ + static char digitals[16] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + static char str[64]; + char *ptr = str + 63; + int tmp; + *ptr = 0; + do { + if (!value) + *--ptr = fill_char; + else + *--ptr = digitals[value % base]; + value /= base; + } while (--min_len > 0 || value); + + return ptr; +} + +int SECTION_TEXT_VMSETUP printf(const char *format, ...) +{ + const char *ptr = format; + int res = 0; + + va_list va; + va_start(va, format); + + while (*ptr) { + if (*ptr == '%') { + int min_len = 0; + char fill_char = 0; + loop1: + switch (*(++ptr)) { + case 'c': + assert(!(min_len || fill_char)); + ++res; + putchar(va_arg(va, int)); + break; + case 's': + assert(!(min_len || fill_char)); + res += _puts(va_arg(va, const char *)); + break; + case 'x': { + assert(!(!fill_char ^ !min_len)); + uint32_t n = va_arg(va, uint32_t); + res += _puts(itoa(n, 16, min_len, min_len ? fill_char : '0')); + } break; + case 'd': { + assert(!(!fill_char ^ !min_len)); + int64_t n = va_arg(va, int64_t); + if (n < 0) { + ++res; + putchar('-'); + n = -n; + } + res += _puts(itoa(n, 10, min_len, min_len ? fill_char : '0')); + } break; + case '%': + ++res; + putchar('%'); + break; + case '1': + assert(fill_char); + min_len *= 10; + min_len += 1; + goto loop1; + case '2': + assert(fill_char); + min_len *= 10; + min_len += 2; + goto loop1; + case '3': + assert(fill_char); + min_len *= 10; + min_len += 3; + goto loop1; + case '4': + assert(fill_char); + min_len *= 10; + min_len += 4; + goto loop1; + case '5': + assert(fill_char); + min_len *= 10; + min_len += 5; + goto loop1; + case '6': + assert(fill_char); + min_len *= 10; + min_len += 6; + goto loop1; + case '7': + assert(fill_char); + min_len *= 10; + min_len += 7; + goto loop1; + case '8': + assert(fill_char); + min_len *= 10; + min_len += 8; + goto loop1; + case '9': + assert(fill_char); + min_len *= 10; + min_len += 9; + goto loop1; + default: + assert(!min_len); + fill_char = *ptr; + goto loop1; + } + } else { + ++res; + putchar(*ptr); + } + ++ptr; + } + return res; +} + +void SECTION_TEXT_VMSETUP handle_fault(uint32_t addr, uint32_t cause) +{ + addr = addr >> PG_SHIFT << PG_SHIFT; /* round down page */ + pte_t *pte = user_l1pt + ((addr >> PG_SHIFT) & MASK(10)); + + /* create a new pte */ + assert(freelist_head); + pte_t pa = freelist_head->addr; + freelist_head = freelist_head->next; + *pte = (pa >> PG_SHIFT << PTE_PPN_SHIFT) | PTE_A | PTE_U | PTE_R | PTE_W | + PTE_X | PTE_V; + if ((1U << cause) == CAUSE_STORE_PAGE_FAULT) + *pte |= PTE_D; + + /* temporarily allow kernel to access user memory to copy data */ + set_csr(sstatus, SSTATUS_SUM); + /* page table is updated, so main should not cause trap here */ + memcpy((uint32_t *) addr, (uint32_t *) uva2kva(addr), PG_SIZE); + /* disallow kernel to access user memory */ + clear_csr(sstatus, SSTATUS_SUM); +} + +void SECTION_TEXT_VMSETUP handle_trap(trapframe_t *tf) +{ + if ((1U << tf->cause) == CAUSE_FETCH_PAGE_FAULT || + (1U << tf->cause) == CAUSE_LOAD_PAGE_FAULT || + (1U << tf->cause) == CAUSE_STORE_PAGE_FAULT) { + handle_fault(tf->badvaddr, tf->cause); + } else + assert(!"Unknown exception"); + + pop_tf(tf); +} + +void SECTION_TEXT_VMSETUP vm_boot() +{ + /* map first page table entry to a next level page table for user */ + l1pt[0] = ((pte_t) user_l1pt >> PG_SHIFT << PTE_PPN_SHIFT) | PTE_V; + /* map last page table leaf entry for kernel which direct maps for user + * virtual memory, note that this is a trick of 2's complement, e.g., 0 - + * MEGA_PG = 0b1111111111xxx...x when page fault occurs after entering main + */ + l1pt[PTES_PER_PT - 1] = ((pte_t) main >> PG_SHIFT << PTE_PPN_SHIFT) | + PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D; + + /* direct map kernel virtual memory */ + l1pt[PTES_PER_PT >> 1] = ((pte_t) _start >> PG_SHIFT << PTE_PPN_SHIFT) | + PTE_V | PTE_R | PTE_W | PTE_X | PTE_A | PTE_D; + + /* Enable paging */ + uintptr_t satp_val = ((pte_t) &l1pt >> PG_SHIFT) | SV32_MODE; + write_csr(satp, satp_val); + + /* set up supervisor trap handler */ + write_csr(stvec, supervisor_trap_entry); + write_csr(sscratch, read_csr(mscratch)); + write_csr(medeleg, CAUSE_FETCH_PAGE_FAULT | CAUSE_LOAD_PAGE_FAULT | + CAUSE_STORE_PAGE_FAULT); + + freelist_head = (void *) &freelist_nodes[0]; + freelist_tail = &freelist_nodes[MAX_TEST_PG - 1]; + for (uint32_t i = 0; i < MAX_TEST_PG; i++) { + freelist_nodes[i].addr = FREE_FRAME_BASE + i * PG_SIZE; + freelist_nodes[i].next = &freelist_nodes[i + 1]; + } + freelist_nodes[MAX_TEST_PG - 1].next = 0; + + trapframe_t tf; + memset(&tf, 0, sizeof(tf)); + tf.epc = &user_entry; + pop_tf(&tf); +}