diff --git a/main.c b/main.c index b6238bf..ced5eb8 100644 --- a/main.c +++ b/main.c @@ -15,15 +15,15 @@ /* Define fetch separately since it is simpler (fixed width, already checked * alignment, only main RAM is executable). */ -static void mem_fetch(vm_t *vm, uint32_t addr, uint32_t *value) +static void mem_fetch(vm_t *vm, uint32_t n_pages, uint32_t **page_addr) { emu_state_t *data = (emu_state_t *) vm->priv; - if (unlikely(addr >= RAM_SIZE)) { + if (unlikely(n_pages >= RAM_SIZE / RV_PAGE_SIZE)) { /* TODO: check for other regions */ vm_set_exception(vm, RV_EXC_FETCH_FAULT, vm->exc_val); return; } - *value = data->ram[addr >> 2]; + *page_addr = &data->ram[n_pages << (RV_PAGE_SHIFT - 2)]; } /* Similarly, only main memory pages can be used as page tables. */ @@ -369,6 +369,7 @@ static int semu_start(int argc, char **argv) .mem_store = mem_store, .mem_page_table = mem_page_table, }; + vm_init(&vm); /* Set up RAM */ emu.ram = mmap(NULL, RAM_SIZE, PROT_READ | PROT_WRITE, diff --git a/riscv.c b/riscv.c index 3c65a1b..785b2fc 100644 --- a/riscv.c +++ b/riscv.c @@ -167,11 +167,17 @@ static inline uint32_t read_rs2(const vm_t *vm, uint32_t insn) /* virtual addressing */ +static void mmu_invalidate(vm_t *vm) +{ + vm->cache_fetch.n_pages = 0xFFFFFFFF; +} + /* Pre-verify the root page table to minimize page table access during * translation time. */ static void mmu_set(vm_t *vm, uint32_t satp) { + mmu_invalidate(vm); if (satp >> 31) { uint32_t *page_table = vm->mem_page_table(vm, satp & MASK(22)); if (!page_table) @@ -267,18 +273,27 @@ static void mmu_translate(vm_t *vm, *addr = ((*addr) & MASK(RV_PAGE_SHIFT)) | (ppn << RV_PAGE_SHIFT); } -static void mmu_fence(vm_t *vm UNUSED, uint32_t insn UNUSED) +static void mmu_fence(vm_t *vm, uint32_t insn UNUSED) { - /* no-op for now */ + mmu_invalidate(vm); } static void mmu_fetch(vm_t *vm, uint32_t addr, uint32_t *value) { - mmu_translate(vm, &addr, (1 << 3), (1 << 6), false, RV_EXC_FETCH_FAULT, - RV_EXC_FETCH_PFAULT); - if (vm->error) - return; - vm->mem_fetch(vm, addr, value); + uint32_t vpn = addr >> RV_PAGE_SHIFT; + if (unlikely(vpn != vm->cache_fetch.n_pages)) { + mmu_translate(vm, &addr, (1 << 3), (1 << 6), false, RV_EXC_FETCH_FAULT, + RV_EXC_FETCH_PFAULT); + if (vm->error) + return; + uint32_t *page_addr; + vm->mem_fetch(vm, addr >> RV_PAGE_SHIFT, &page_addr); + if (vm->error) + return; + vm->cache_fetch.n_pages = vpn; + vm->cache_fetch.page_addr = page_addr; + } + *value = vm->cache_fetch.page_addr[(addr >> 2) & MASK(RV_PAGE_SHIFT - 2)]; } static void mmu_load(vm_t *vm, @@ -347,6 +362,7 @@ void vm_trap(vm_t *vm) /* Set */ vm->sstatus_sie = false; + mmu_invalidate(vm); vm->s_mode = true; vm->pc = vm->stvec_addr; if (vm->stvec_vectored) @@ -359,6 +375,7 @@ static void op_sret(vm_t *vm) { /* Restore from stack */ vm->pc = vm->sepc; + mmu_invalidate(vm); vm->s_mode = vm->sstatus_spp; vm->sstatus_sie = vm->sstatus_spie; @@ -763,6 +780,11 @@ static void op_amo(vm_t *vm, uint32_t insn) } } +void vm_init(vm_t *vm) +{ + mmu_invalidate(vm); +} + void vm_step(vm_t *vm) { if (unlikely(vm->error)) diff --git a/riscv.h b/riscv.h index c416384..04b60b8 100644 --- a/riscv.h +++ b/riscv.h @@ -29,10 +29,15 @@ typedef enum { ERR_USER, /**< user-specific error */ } vm_error_t; -/* To use the emulator, start by initializing a "vm_t" struct with zero values - * and set the required environment-supplied callbacks. You may also set other - * necessary fields such as argument registers and s_mode, ensuring that all - * field restrictions are met to avoid undefined behavior. +typedef struct { + uint32_t n_pages; + uint32_t *page_addr; +} mmu_cache_t; + +/* To use the emulator, start by initializing a vm_t object with zero values, + * invoke vm_init(), and set the required environment-supplied callbacks. You + * may also set other necessary fields such as argument registers and s_mode, + * ensuring that all field restrictions are met to avoid undefined behavior. * * Once the emulator is set up, execute the emulation loop by calling * "vm_step()" repeatedly. Each call attempts to execute a single instruction. @@ -77,6 +82,8 @@ struct __vm_internal { */ uint32_t exc_cause, exc_val; + mmu_cache_t cache_fetch; + /* Supervisor state */ bool s_mode; bool sstatus_spp; /**< state saved at trap */ @@ -101,7 +108,7 @@ struct __vm_internal { /* Memory access sets the vm->error to indicate failure. On successful * access, it reads or writes the specified "value". */ - void (*mem_fetch)(vm_t *vm, uint32_t addr, uint32_t *value); + void (*mem_fetch)(vm_t *vm, uint32_t n_pages, uint32_t **page_addr); void (*mem_load)(vm_t *vm, uint32_t addr, uint8_t width, uint32_t *value); void (*mem_store)(vm_t *vm, uint32_t addr, uint8_t width, uint32_t value); @@ -112,6 +119,8 @@ struct __vm_internal { uint32_t *(*mem_page_table)(const vm_t *vm, uint32_t ppn); }; +void vm_init(vm_t *vm); + /* Emulate the next instruction. This is a no-op if the error is already set. */ void vm_step(vm_t *vm);