Signed-off-by: Sagar Karandikar <sag...@eecs.berkeley.edu> --- hw/riscv/Makefile.objs | 1 + hw/riscv/cpudevs.h | 17 +++ hw/riscv/riscv_rtc.c | 230 ++++++++++++++++++++++++++++++++++ include/hw/riscv/riscv_rtc.h | 25 ++++ include/hw/riscv/riscv_rtc_internal.h | 3 + target-riscv/op_helper.c | 4 +- 6 files changed, 278 insertions(+), 2 deletions(-) create mode 100644 hw/riscv/Makefile.objs create mode 100644 hw/riscv/cpudevs.h create mode 100644 hw/riscv/riscv_rtc.c create mode 100644 include/hw/riscv/riscv_rtc.h create mode 100644 include/hw/riscv/riscv_rtc_internal.h
diff --git a/hw/riscv/Makefile.objs b/hw/riscv/Makefile.objs new file mode 100644 index 0000000..79b4553 --- /dev/null +++ b/hw/riscv/Makefile.objs @@ -0,0 +1 @@ +obj-y += riscv_rtc.o diff --git a/hw/riscv/cpudevs.h b/hw/riscv/cpudevs.h new file mode 100644 index 0000000..f2f4195 --- /dev/null +++ b/hw/riscv/cpudevs.h @@ -0,0 +1,17 @@ +#ifndef HW_RISCV_CPUDEVS_H +#define HW_RISCV_CPUDEVS_H + +#include "target-riscv/cpu.h" + +/* Definitions for RISCV CPU internal devices. */ + +/* riscv_board.c */ +uint64_t identity_translate(void *opaque, uint64_t addr); + +/* riscv_int.c */ +void cpu_riscv_irq_init_cpu(CPURISCVState *env); + +/* cputimer.c */ +void cpu_riscv_clock_init(CPURISCVState *); + +#endif diff --git a/hw/riscv/riscv_rtc.c b/hw/riscv/riscv_rtc.c new file mode 100644 index 0000000..178c7ff --- /dev/null +++ b/hw/riscv/riscv_rtc.c @@ -0,0 +1,230 @@ +/* + * QEMU RISC-V timer, instret counter support + * + * Author: Sagar Karandikar, sag...@eecs.berkeley.edu + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu/osdep.h" +#include "hw/hw.h" +#include "hw/riscv/cpudevs.h" +#include "hw/riscv/riscv_rtc_internal.h" +#include "hw/riscv/riscv_rtc.h" +#include "qemu/timer.h" + +/*#define TIMER_DEBUGGING_RISCV */ + +/* this is the "right value" for defaults in pk/linux + see pk/sbi_entry.S and arch/riscv/kernel/time.c call to + clockevents_config_and_register */ +#define TIMER_FREQ (10 * 1000 * 1000) +/* CPU_FREQ is for instret approximation - say we're running at 1 BIPS */ +#define CPU_FREQ (1000 * 1000 * 1000) + +inline uint64_t rtc_read(CPURISCVState *env) +{ + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), TIMER_FREQ, + NANOSECONDS_PER_SECOND); +} + +inline uint64_t instret_read(CPURISCVState *env) +{ + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), CPU_FREQ, + NANOSECONDS_PER_SECOND); +} + +/* + * Called when timecmp is written to update the QEMU timer or immediately + * trigger timer interrupt if mtimecmp <= current timer value. + */ +static inline void cpu_riscv_timer_update(CPURISCVState *env) +{ + uint64_t next; + uint64_t diff; + + uint64_t rtc_r = rtc_read(env); + + #ifdef TIMER_DEBUGGING_RISCV + printf("timer update: mtimecmp %016lx, timew %016lx\n", + env->timecmp, rtc_r); + #endif + + if (env->timecmp <= rtc_r) { + /* if we're setting an MTIMECMP value in the "past", + immediately raise the timer interrupt */ + env->csr[CSR_MIP] |= MIP_MTIP; + qemu_irq_raise(env->irq[3]); + return; + } + + /* otherwise, set up the future timer interrupt */ + diff = env->timecmp - rtc_r; + /* back to ns (note args switched in muldiv64) */ + next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + muldiv64(diff, NANOSECONDS_PER_SECOND, TIMER_FREQ); + timer_mod(env->timer, next); +} + +/* + * Called by the callback used when the timer set using timer_mod expires. + * Should raise the timer interrupt line + */ +static inline void cpu_riscv_timer_expire(CPURISCVState *env) +{ + /* do not call update here */ + env->csr[CSR_MIP] |= MIP_MTIP; + qemu_irq_raise(env->irq[3]); +} + +/* used in op_helper.c */ +inline uint64_t cpu_riscv_read_instret(CPURISCVState *env) +{ + uint64_t retval = instret_read(env); + return retval; +} + +inline void write_timecmp(CPURISCVState *env, uint64_t value) +{ + #ifdef TIMER_DEBUGGING_RISCV + uint64_t rtc_r = rtc_read_with_delta(env); + printf("wrote mtimecmp %016lx, timew %016lx\n", value, rtc_r); + #endif + + env->timecmp = value; + env->csr[CSR_MIP] &= ~MIP_MTIP; + cpu_riscv_timer_update(env); +} + +/* + * Callback used when the timer set using timer_mod expires. + */ +static void riscv_timer_cb(void *opaque) +{ + CPURISCVState *env; + env = opaque; + cpu_riscv_timer_expire(env); +} + +/* + * Initialize clock mechanism. + */ +void cpu_riscv_clock_init(CPURISCVState *env) +{ + env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_timer_cb, env); + env->timecmp = 0; +} + + +#include "target-riscv/cpu.h" + +static void timer_pre_save(void *opaque) +{ + return; +} + +static int timer_post_load(void *opaque, int version_id) +{ + return 0; +} + +const VMStateDescription vmstate_timer_rv = { + .name = "rvtimer", + .version_id = 1, + .minimum_version_id = 1, + .pre_save = timer_pre_save, + .post_load = timer_post_load, + .fields = (VMStateField []) { /* TODO what */ + VMSTATE_END_OF_LIST() + }, +}; + +/* CPU wants to read rtc or timecmp register */ +static uint64_t timer_mm_read(void *opaque, hwaddr addr, unsigned size) +{ + TIMERState *timerstate = opaque; + if (addr == 0) { + /* rtc */ + timerstate->temp_rtc_val = rtc_read(timerstate->env); + return timerstate->temp_rtc_val & 0xFFFFFFFF; + } else if (addr == 4) { + /* rtc */ + return (timerstate->temp_rtc_val >> 32) & 0xFFFFFFFF; + } else if (addr == 8) { + /* timecmp */ + printf("TIMECMP READ NOT IMPL\n"); + exit(1); + } else if (addr == 0xc) { + /* timecmp */ + printf("TIMECMP READ NOT IMPL\n"); + exit(1); + } else { + printf("Invalid timer register address %016lx\n", (uint64_t)addr); + exit(1); + } +} + +/* CPU wrote to rtc or timecmp register */ +static void timer_mm_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + TIMERState *timerstate = opaque; + if (addr == 0) { + /*rtc */ + printf("RTC WRITE NOT IMPL\n"); + exit(1); + } else if (addr == 4) { + /*rtc */ + printf("RTC WRITE NOT IMPL\n"); + exit(1); + } else if (addr == 8) { + /* timecmp */ + timerstate->timecmp_lower = value & 0xFFFFFFFF; + } else if (addr == 0xc) { + /* timecmp */ + write_timecmp(timerstate->env, value << 32 | + timerstate->timecmp_lower); + } else { + printf("Invalid timer register address %016lx\n", (uint64_t)addr); + exit(1); + } +} + +static const MemoryRegionOps timer_mm_ops[3] = { + [DEVICE_LITTLE_ENDIAN] = { + .read = timer_mm_read, + .write = timer_mm_write, + .endianness = DEVICE_LITTLE_ENDIAN, + }, +}; + +TIMERState *timer_mm_init(MemoryRegion *address_space, hwaddr base, + CPURISCVState *env) +{ + TIMERState *timerstate; + timerstate = g_malloc0(sizeof(TIMERState)); + timerstate->env = env; + timerstate->temp_rtc_val = 0; + vmstate_register(NULL, base, &vmstate_timer_rv, timerstate); + memory_region_init_io(&timerstate->io, NULL, + &timer_mm_ops[DEVICE_LITTLE_ENDIAN], + timerstate, "timer", 16 /* 2 64-bit registers */); + memory_region_add_subregion(address_space, base, &timerstate->io); + return timerstate; +} diff --git a/include/hw/riscv/riscv_rtc.h b/include/hw/riscv/riscv_rtc.h new file mode 100644 index 0000000..d5c422c --- /dev/null +++ b/include/hw/riscv/riscv_rtc.h @@ -0,0 +1,25 @@ +#ifndef HW_RISCV_TIMER_H +#define HW_RISCV_TIMER_H 1 + +#include "hw/hw.h" +#include "sysemu/sysemu.h" +#include "exec/memory.h" +#include "target-riscv/cpu.h" + +typedef struct TIMERState TIMERState; + +struct TIMERState { + CPURISCVState *env; + MemoryRegion io; + uint32_t timecmp_lower; + uint64_t temp_rtc_val; +}; + +extern const VMStateDescription vmstate_timer_rv; +extern const MemoryRegionOps timer_io_ops; + +/* legacy pre qom */ +TIMERState *timer_mm_init(MemoryRegion *address_space, hwaddr base, + CPURISCVState *env); + +#endif diff --git a/include/hw/riscv/riscv_rtc_internal.h b/include/hw/riscv/riscv_rtc_internal.h new file mode 100644 index 0000000..d13e104 --- /dev/null +++ b/include/hw/riscv/riscv_rtc_internal.h @@ -0,0 +1,3 @@ +uint64_t rtc_read(CPURISCVState *env); +uint64_t instret_read(CPURISCVState *env); +void write_timecmp(CPURISCVState *env, uint64_t value); diff --git a/target-riscv/op_helper.c b/target-riscv/op_helper.c index 1ecdef2..54d7538 100644 --- a/target-riscv/op_helper.c +++ b/target-riscv/op_helper.c @@ -335,10 +335,10 @@ inline target_ulong csr_read_helper(CPURISCVState *env, target_ulong csrno) /* notice the lack of CSR_MTIME - this is handled by throwing an exception and letting the handler read from the RTC */ case CSR_MCYCLE: - /* Read instret here */ + return cpu_riscv_read_instret(env); break; case CSR_MINSTRET: - /* Read instret here */ + return cpu_riscv_read_instret(env); break; case CSR_MCYCLEH: printf("CSR 0x%x unsupported on RV64\n", csrno2); -- 2.9.3