From de48e61caf6e750ecac02310ce6a9e5338a54137 Mon Sep 17 00:00:00 2001 From: chiangkd Date: Mon, 27 May 2024 02:17:30 +0800 Subject: [PATCH] Implement basic CLINT support mtimer device In this commit, replace the `timer` defined in the `emu_state_t` structure, which originally compared with the instruction counter and triggered software timer interrupts. Further, make 64-bit unsigned integer `mtime` with Real time counter (RTC) to prevent mtime overflow. Replacing timer with MTIMER perpheral also triggers timer interrupts normally. Clarify that the MTIMER peripheral can also trigger timer interrupts within a scheduled period by monitoring `/proc/interrupts` reveals an increase in timer interrupts triggered by `riscv-timer`: ``` 1: 595 SiFive PLIC 1 Edge ttyS0 2: 19 SiFive PLIC 3 Edge virtio1 3: 0 SiFive PLIC 2 Edge virtio0 5: 58985 RISC-V INTC 5 Edge riscv-timer ... 1: 610 SiFive PLIC 1 Edge ttyS0 2: 19 SiFive PLIC 3 Edge virtio1 3: 0 SiFive PLIC 2 Edge virtio0 5: 59196 RISC-V INTC 5 Edge riscv-timer ... 1: 625 SiFive PLIC 1 Edge ttyS0 2: 19 SiFive PLIC 3 Edge virtio1 3: 0 SiFive PLIC 2 Edge virtio0 5: 59296 RISC-V INTC 5 Edge riscv-timer ``` Additionally, the `vm_timer_t` structure is use to retrieve system-wide clock through `clock_gettime()`. The clock frequency is configured to 65MHz, which matches the `timebase-frequency` specified in `minimal.dts`. --- Makefile | 2 + aclint.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++++ device.h | 49 +++++++++++++++++- main.c | 23 ++++++--- minimal.dts | 6 +++ plic.c | 20 ++++---- riscv.c | 20 ++++++-- riscv.h | 13 +++++ riscv_private.h | 7 +++ timer.c | 41 ++++++++++++++++ 10 files changed, 287 insertions(+), 22 deletions(-) create mode 100644 aclint.c create mode 100644 timer.c diff --git a/Makefile b/Makefile index f3ce149..489767c 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,8 @@ all: $(BIN) minimal.dtb OBJS := \ riscv.o \ ram.o \ + timer.o \ + aclint.o \ plic.o \ uart.o \ main.o \ diff --git a/aclint.c b/aclint.c new file mode 100644 index 0000000..67156dc --- /dev/null +++ b/aclint.c @@ -0,0 +1,128 @@ +#include "device.h" +#include "riscv.h" +#include "riscv_private.h" + +void aclint_timer_interrupts(vm_t *vm, aclint_state_t *aclint) +{ + uint64_t time_delta = aclint->mtimecmp - vm_timer_gettime(&vm->timer); + if ((time_delta & 0x8000000000000000) || time_delta == 0) + vm->sip |= RV_INT_STI_BIT; + else + vm->sip &= ~RV_INT_STI_BIT; +} + +void aclint_update_interrupts(vm_t *vm, aclint_state_t *aclint) +{ + if (aclint->setssip) + vm->sip |= RV_INT_SSI_BIT; + else + vm->sip &= ~RV_INT_SSI_BIT; +} + +static bool aclint_reg_read(aclint_state_t *aclint, + uint32_t addr, + uint32_t *value) +{ +#define _(reg) ACLINT_##reg + switch (addr) { + case _(SSWI): + /* sswi */ + *value = aclint->setssip; + return true; + case _(MTIMECMP_LO): + /* mtimecmp */ + *value = aclint->mtimecmp & 0xFFFFFFFF; + return true; + case _(MTIMECMP_HI): + /* mtimecmph */ + *value = (aclint->mtimecmp >> 32) & 0xFFFFFFFF; + return true; + case _(MTIME_LO): + /* mtime */ + *value = (uint32_t) (vm_timer_gettime(&aclint->mtimer) & 0xFFFFFFFF); + return true; + case _(MTIME_HI): + /* mtimeh */ + *value = + (uint32_t) (vm_timer_gettime(&aclint->mtimer) >> 32) & 0xFFFFFFFF; + return true; + default: + return false; + } +#undef _ +} + +static bool aclint_reg_write(aclint_state_t *aclint, + uint32_t addr, + uint32_t value) +{ +#define _(reg) ACLINT_##reg + switch (addr) { + case _(SSWI): + /* sswi */ + aclint->setssip = value; + return true; + case _(MTIMECMP_LO): + /* mtimecmp */ + aclint->mtimecmp |= value; + return true; + case _(MTIMECMP_HI): + /* mtimecmph */ + aclint->mtimecmp |= ((uint64_t) value) << 32; + return true; + case _(MTIME_LO): + /* mtime */ + vm_timer_rebase(&aclint->mtimer, value); + return true; + case _(MTIME_HI): + /* mtimeh */ + vm_timer_rebase(&aclint->mtimer, ((uint64_t) value) << 32); + return true; + default: + return false; + } +#undef _ +} + +void aclint_read(vm_t *vm, + aclint_state_t *aclint, + uint32_t addr, + uint8_t width, + uint32_t *value) +{ + switch (width) { + case RV_MEM_LW: + if (!aclint_reg_read(aclint, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); + break; + case RV_MEM_LBU: + case RV_MEM_LB: + case RV_MEM_LHU: + case RV_MEM_LH: + vm_set_exception(vm, RV_EXC_LOAD_MISALIGN, vm->exc_val); + return; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + return; + } +} +void aclint_write(vm_t *vm, + aclint_state_t *aclint, + uint32_t addr, + uint8_t width, + uint32_t value) +{ + switch (width) { + case RV_MEM_SW: + if (!aclint_reg_write(aclint, addr >> 2, value)) + vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); + break; + case RV_MEM_SB: + case RV_MEM_SH: + vm_set_exception(vm, RV_EXC_STORE_MISALIGN, vm->exc_val); + return; + default: + vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); + return; + } +} diff --git a/device.h b/device.h index 754065f..8c0dae8 100644 --- a/device.h +++ b/device.h @@ -21,7 +21,54 @@ void ram_write(vm_t *core, const uint8_t width, const uint32_t value); +/* ACLINT */ +#define ACLINT_REG_LIST \ + _(SSWI, 0x0000) \ + _(MTIMECMP_LO, 0x4000) \ + _(MTIMECMP_HI, 0x4004) \ + _(MTIME_LO, 0x7FF8) \ + _(MTIME_HI, 0x7FFC) + +enum { +#define _(reg, addr) ACLINT_##reg = addr >> 2, + ACLINT_REG_LIST +#undef _ +}; + +typedef struct { + vm_t *vm; + vm_timer_t mtimer; + uint64_t mtimecmp; + uint32_t setssip; +} aclint_state_t; + +void aclint_update_interrupts(vm_t *core, aclint_state_t *aclint); +void aclint_timer_interrupts(vm_t *core, aclint_state_t *aclint); +void aclint_read(vm_t *core, + aclint_state_t *aclint, + uint32_t addr, + uint8_t width, + uint32_t *value); +void aclint_write(vm_t *core, + aclint_state_t *aclint, + uint32_t addr, + uint8_t width, + uint32_t value); +void aclint_send_ipi(vm_t *vm, aclint_state_t *aclint, uint32_t target_hart); + /* PLIC */ +#define PLIC_REG_LIST \ + _(InterruptPending, 0x1000) \ + _(InterruptEnable, 0x2000) \ + _(PriorityThresholds, 0x200000) \ + _(InterruptClaim, 0x200004) \ + _(InterruptCompletion, 0x200004) + +enum { +#define _(reg, addr) PLIC_##reg = addr >> 2, + PLIC_REG_LIST +#undef _ +}; typedef struct { uint32_t masked; @@ -177,6 +224,7 @@ typedef struct { bool stopped; uint32_t *ram; uint32_t *disk; + aclint_state_t aclint; plic_state_t plic; u8250_state_t uart; #if SEMU_HAS(VIRTIONET) @@ -185,5 +233,4 @@ typedef struct { #if SEMU_HAS(VIRTIOBLK) virtio_blk_state_t vblk; #endif - uint64_t timer; } emu_state_t; diff --git a/main.c b/main.c index ba1b6d0..138c495 100644 --- a/main.c +++ b/main.c @@ -105,6 +105,10 @@ static void mem_load(vm_t *vm, uint32_t addr, uint8_t width, uint32_t *value) emu_update_vblk_interrupts(vm); return; #endif + case 0x44: /* CLINT */ + aclint_read(vm, &data->aclint, addr & 0xFFFF, width, value); + aclint_update_interrupts(vm, &data->aclint); + return; } } vm_set_exception(vm, RV_EXC_LOAD_FAULT, vm->exc_val); @@ -143,6 +147,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 0x44: /* CLINT */ + aclint_write(vm, &data->aclint, addr & 0xFFFF, width, value); + aclint_update_interrupts(vm, &data->aclint); + return; } } vm_set_exception(vm, RV_EXC_STORE_FAULT, vm->exc_val); @@ -162,8 +170,8 @@ 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->aclint.mtimecmp = (((uint64_t) vm->x_regs[RV_R_A1])) << 32 | + (uint64_t) vm->x_regs[RV_R_A0]; return (sbi_ret_t){SBI_SUCCESS, 0}; default: return (sbi_ret_t){SBI_ERR_NOT_SUPPORTED, 0}; @@ -361,6 +369,8 @@ static int semu_start(int argc, char **argv) /* Initialize the emulator */ emu_state_t emu; memset(&emu, 0, sizeof(emu)); + emu.aclint.mtimer.freq = 65000000; + emu.aclint.mtimecmp = 0xFFFFFFFFFFFFFFFF; vm_t vm = { .priv = &emu, @@ -406,8 +416,8 @@ static int semu_start(int argc, char **argv) atexit(unmap_files); /* Set up RISC-V hart */ - emu.timer = 0xFFFFFFFFFFFFFFFF; vm.s_mode = true; + vm.timer = emu.aclint.mtimer; vm.x_regs[RV_R_A0] = 0; /* hart ID. i.e., cpuid */ vm.x_regs[RV_R_A1] = dtb_addr; @@ -446,10 +456,9 @@ 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; + aclint_timer_interrupts(&vm, &emu.aclint); + aclint_update_interrupts(&vm, &emu.aclint); + vm_step(&vm); if (likely(!vm.error)) diff --git a/minimal.dts b/minimal.dts index 0e631d3..d8d4144 100644 --- a/minimal.dts +++ b/minimal.dts @@ -81,5 +81,11 @@ interrupts = <3>; }; #endif + clint0: clint@4400000 { + #interrupt-cells = <1>; + compatible = "sifive,clint0"; + reg = <0x4400000 0x000C000>; + interrupts-extended = <&cpu0_intc 1 &cpu0_intc 5>; + }; }; }; diff --git a/plic.c b/plic.c index 0801125..724a95b 100644 --- a/plic.c +++ b/plic.c @@ -21,19 +21,19 @@ static bool plic_reg_read(plic_state_t *plic, uint32_t addr, uint32_t *value) /* no priority support: source priority hardwired to 1 */ if (1 <= addr && addr <= 31) return true; - +#define _(reg) PLIC_##reg switch (addr) { - case 0x400: + case _(InterruptPending): *value = plic->ip; return true; - case 0x800: + case _(InterruptEnable): *value = plic->ie; return true; - case 0x80000: + case _(PriorityThresholds): *value = 0; /* no priority support: target priority threshold hardwired to 0 */ return true; - case 0x80001: + case _(InterruptClaim): /* claim */ *value = 0; uint32_t candidates = plic->ip & plic->ie; @@ -45,6 +45,7 @@ static bool plic_reg_read(plic_state_t *plic, uint32_t addr, uint32_t *value) default: return false; } +#undef _ } static bool plic_reg_write(plic_state_t *plic, uint32_t addr, uint32_t value) @@ -52,16 +53,16 @@ static bool plic_reg_write(plic_state_t *plic, uint32_t addr, uint32_t value) /* no priority support: source priority hardwired to 1 */ if (1 <= addr && addr <= 31) return true; - +#define _(reg) PLIC_##reg switch (addr) { - case 0x800: + case _(InterruptEnable): value &= ~1; plic->ie = value; return true; - case 0x80000: + case _(PriorityThresholds): /* no priority support: target priority threshold hardwired to 0 */ return true; - case 0x80001: + case _(InterruptCompletion): /* completion */ if (plic->ie & (1 << value)) plic->masked &= ~(1 << value); @@ -69,6 +70,7 @@ static bool plic_reg_write(plic_state_t *plic, uint32_t addr, uint32_t value) default: return false; } +#undef _ } void plic_read(vm_t *vm, diff --git a/riscv.c b/riscv.c index 699846f..17652d8 100644 --- a/riscv.c +++ b/riscv.c @@ -434,12 +434,22 @@ static void csr_read(vm_t *vm, uint16_t addr, uint32_t *value) if (idx >= 0x20 || !(vm->s_mode || ((vm->scounteren >> idx) & 1))) vm_set_exception(vm, RV_EXC_ILLEGAL_INSN, 0); else { - /* Use the instruction counter for all of the counters. - * Ideally, reads should return the value before the increment, - * and writes should set the value after the increment. However, - * we do not expose any way to write the counters. + /* + * We do not expose any way to write the counters. */ - *value = vm->insn_count >> ((addr & (1 << 7)) ? 32 : 0); + switch (addr) { + case RV_CSR_INSTRET: + case RV_CSR_INSTRETH: + *value = vm->insn_count >> ((addr & (1 << 7)) ? 32 : 0); + break; + case RV_CSR_TIME: + case RV_CSR_TIMEH: + *value = + vm_timer_gettime(&vm->timer) >> ((addr & 1 << 7) ? 32 : 0); + break; + default: + break; + } } return; } diff --git a/riscv.h b/riscv.h index d318c31..140a13e 100644 --- a/riscv.h +++ b/riscv.h @@ -34,6 +34,16 @@ typedef struct { uint32_t *page_addr; } mmu_cache_t; +/* TIMER */ +typedef struct { + uint64_t begin; + uint64_t freq; +} vm_timer_t; + +uint64_t vm_timer_clocksource(uint64_t freq); +uint64_t vm_timer_gettime(vm_timer_t *timer); +void vm_timer_rebase(vm_timer_t *timer, uint64_t time); + /* 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, @@ -103,6 +113,9 @@ struct __vm_internal { uint32_t satp; /**< MMU */ uint32_t *page_table; + /* Timer */ + vm_timer_t timer; + void *priv; /**< environment supplied */ /* Memory access sets the vm->error to indicate failure. On successful diff --git a/riscv_private.h b/riscv_private.h index 94a3007..15b189f 100644 --- a/riscv_private.h +++ b/riscv_private.h @@ -56,6 +56,13 @@ enum { /* S-mode (Supervisor Protection and Translation) */ RV_CSR_SATP = 0x180, /**< Supervisor address translation and protection */ + + /* Unprivileged Timers */ + RV_CSR_TIME = 0xC01, /**< Timer for RDTIME instruction */ + RV_CSR_INSTRET = + 0xC02, /**< Instructions-retired counter for RDINSTRET instruction */ + RV_CSR_TIMEH = 0xC81, /**< Upper 32 bits of time, RV32 only.*/ + RV_CSR_INSTRETH = 0xC82, /**< Upper 32 bits of instret, RV32 only */ }; /* privileged ISA: exception causes */ diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..4e5eba9 --- /dev/null +++ b/timer.c @@ -0,0 +1,41 @@ +#include + +#include "riscv.h" + +#if defined(__APPLE__) +#define HAVE_MACH_TIMER +#include +#elif !defined(_WIN32) && !defined(_WIN64) +#define HAVE_POSIX_TIMER +#ifdef CLOCK_MONOTONIC +#define CLOCKID CLOCK_MONOTONIC +#else +#define CLOCKID CLOCK_REALTIME +#endif +#endif + +uint64_t vm_timer_clocksource(uint64_t freq) +{ +#if defined(HAVE_POSIX_TIMER) + struct timespec t; + clock_gettime(CLOCKID, &t); + return (t.tv_sec * freq) + (t.tv_nsec * freq / 1e9); +#elif defined(HAVE_MACH_TIMER) + static mach_timebase_info_data_t t; + if (mach_clk.denom == 0) + (void) mach_timebase_info(&t); + return mach_absolute_time() * freq / t.denom * t.numer; +#else + return time(0) * freq; +#endif +} + +uint64_t vm_timer_gettime(vm_timer_t *timer) +{ + return vm_timer_clocksource(timer->freq) - timer->begin; +} + +void vm_timer_rebase(vm_timer_t *timer, uint64_t time) +{ + timer->begin = vm_timer_clocksource(timer->freq) - time; +} \ No newline at end of file