diff --git a/Makefile b/Makefile index f3ce149..54a9565 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ OBJS := \ plic.o \ uart.o \ main.o \ + clint.o \ $(OBJS_EXTRA) deps := $(OBJS:%.o=.%.o.d) diff --git a/clint.c b/clint.c new file mode 100644 index 0000000..27d6db1 --- /dev/null +++ b/clint.c @@ -0,0 +1,87 @@ +#include +#include "device.h" +#include "riscv.h" +#include "riscv_private.h" + +void clint_update_interrupts(vm_t *hart, clint_state_t *clint) +{ + if (clint->mtime > clint->mtimecmp[hart->mhartid]) + hart->sip |= RV_INT_STI_BIT; + else + hart->sip &= ~RV_INT_STI_BIT; + + if (clint->msip[hart->mhartid]) { + hart->sip |= RV_INT_SSI_BIT; + } else + hart->sip &= ~RV_INT_SSI_BIT; +} + +static bool clint_reg_read(clint_state_t *clint, uint32_t addr, uint32_t *value) +{ + if (addr < 0x4000) { + *value = clint->msip[addr >> 2]; + return true; + } else if (addr < 0xBFF8) { + addr -= 0x4000; + *value = + (uint32_t) (clint->mtimecmp[addr >> 3] >> (32 & -!!(addr & 0b100))); + return true; + } else if (addr < 0xBFFF) { + *value = clint->mtime >> (32 & -!!(addr & 0b100)); + return true; + } + return false; +} + +static bool clint_reg_write(clint_state_t *clint, uint32_t addr, uint32_t value) +{ + if (addr < 0x4000) { + clint->msip[addr >> 2] = value; + return true; + } else if (addr < 0xBFF8) { + addr -= 0x4000; + int32_t upper = clint->mtimecmp[addr >> 3] >> 32; + int32_t lowwer = clint->mtimecmp[addr >> 3]; + if (addr & 0b100) + upper = value; + else + lowwer = value; + + clint->mtimecmp[addr >> 3] = (uint64_t) upper << 32 | lowwer; + return true; + } else if (addr < 0xBFFF) { + int32_t upper = clint->mtime >> 32; + int32_t lowwer = clint->mtime; + if (addr & 0b100) + upper = value; + else + lowwer = value; + + clint->mtime = (uint64_t) upper << 32 | lowwer; + return true; + } + return false; +} + +void clint_read(vm_t *hart, + clint_state_t *clint, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + if (!clint_reg_read(clint, addr, value)) + vm_set_exception(hart, RV_EXC_LOAD_FAULT, hart->exc_val); + *value = (*value) >> (RV_MEM_SW - width); + return; +} + +void clint_write(vm_t *hart, + clint_state_t *clint, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + if (!clint_reg_write(clint, addr, value >> (RV_MEM_SW - width))) + vm_set_exception(hart, RV_EXC_STORE_FAULT, hart->exc_val); + return; +} \ No newline at end of file diff --git a/device.h b/device.h index 885f86b..50596d6 100644 --- a/device.h +++ b/device.h @@ -171,6 +171,26 @@ void virtio_blk_write(vm_t *vm, uint32_t *virtio_blk_init(virtio_blk_state_t *vblk, char *disk_file); #endif /* SEMU_HAS(VIRTIOBLK) */ +/* clint */ +typedef struct { + uint32_t msip[4096]; + uint64_t mtimecmp[4095]; + uint64_t mtime; +} clint_state_t; + +void clint_update_interrupts(vm_t *vm, clint_state_t *clint); +void clint_read(vm_t *vm, + clint_state_t *clint, + uint32_t addr, + uint8_t width, + uint32_t *value); + +void clint_write(vm_t *vm, + clint_state_t *clint, + uint32_t addr, + uint8_t width, + uint32_t value); + /* memory mapping */ typedef struct { @@ -185,5 +205,5 @@ typedef struct { #if SEMU_HAS(VIRTIOBLK) virtio_blk_state_t vblk; #endif - uint64_t timer; + clint_state_t clint; } emu_state_t; diff --git a/main.c b/main.c index 021b707..6662ead 100644 --- a/main.c +++ b/main.c @@ -72,6 +72,21 @@ static void emu_update_vblk_interrupts(vm_t *vm) } #endif +static void emu_update_timer_interrupt(vm_t *vm) +{ + emu_state_t *data = PRIV(vm); + vm->time = data->clint.mtime; + clint_update_interrupts(vm, &data->clint); +} + +static void emu_update_global_timer(vm_t *vm) +{ + emu_state_t *data = PRIV(vm); + data->clint.mtime++; + return; +} + + static void mem_load(vm_t *vm, uint32_t addr, uint8_t width, uint32_t *value) { emu_state_t *data = PRIV(vm); @@ -105,6 +120,9 @@ static void mem_load(vm_t *vm, uint32_t addr, uint8_t width, uint32_t *value) emu_update_vblk_interrupts(vm); return; #endif + case 0x43: /* clint */ + clint_read(vm, &data->clint, addr & 0xFFFFF, width, value); + clint_update_interrupts(vm, &data->clint); } } vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); @@ -143,6 +161,10 @@ static void mem_store(vm_t *vm, uint32_t addr, uint8_t width, uint32_t value) emu_update_vblk_interrupts(vm); return; #endif + case 0x43: /* clint */ + clint_write(vm, &data->clint, addr & 0xFFFFF, width, value); + clint_update_interrupts(vm, &data->clint); + return; } } vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); @@ -162,8 +184,9 @@ static inline sbi_ret_t handle_sbi_ecall_TIMER(vm_t *vm, int32_t fid) emu_state_t *data = PRIV(vm); switch (fid) { case SBI_TIMER__SET_TIMER: - data->timer = (((uint64_t) vm->x_regs[RV_R_A1]) << 32) | - (uint64_t) (vm->x_regs[RV_R_A0]); + data->clint.mtimecmp[0] = (((uint64_t) vm->x_regs[RV_R_A1]) << 32) | + (uint64_t) (vm->x_regs[RV_R_A0]); + vm->sip &= ~RV_INT_STI_BIT; return (sbi_ret_t){SBI_SUCCESS, 0}; default: return (sbi_ret_t){SBI_ERR_NOT_SUPPORTED, 0}; @@ -369,6 +392,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 */ @@ -406,7 +430,6 @@ static int semu_start(int argc, char **argv) atexit(unmap_files); /* Set up RISC-V hart */ - emu.timer = 0xFFFFFFFFFFFFFFFF; vm.s_mode = true; vm.x_regs[RV_R_A0] = 0; /* hart ID. i.e., cpuid */ vm.x_regs[RV_R_A1] = dtb_addr; @@ -428,6 +451,7 @@ static int semu_start(int argc, char **argv) /* Emulate */ uint32_t peripheral_update_ctr = 0; while (!emu.stopped) { + emu_update_global_timer(&vm); if (peripheral_update_ctr-- == 0) { peripheral_update_ctr = 64; @@ -447,10 +471,7 @@ static int semu_start(int argc, char **argv) #endif } - if (vm.insn_count > emu.timer) - vm.sip |= RV_INT_STI_BIT; - else - vm.sip &= ~RV_INT_STI_BIT; + emu_update_timer_interrupt(&vm); vm_step(&vm); if (likely(!vm.error)) diff --git a/minimal.dts b/minimal.dts index 0e631d3..693c00d 100644 --- a/minimal.dts +++ b/minimal.dts @@ -81,5 +81,12 @@ interrupts = <3>; }; #endif + clint0: clint@4300000 { + compatible = "riscv,clint0"; + interrupt-controller; + interrupts-extended = + <&cpu0_intc 3 &cpu0_intc 7>; + reg = <0x4300000 0x10000>; + }; }; }; diff --git a/riscv.h b/riscv.h index 7019a57..fda402e 100644 --- a/riscv.h +++ b/riscv.h @@ -69,6 +69,7 @@ struct __vm_internal { * resets. */ uint64_t insn_count; + uint64_t time; /* Instruction execution state must be set to "NONE" for instruction * execution to continue. If the state is not "NONE," the vm_step()