/* * RISC-V ACLINT (Advanced Core Local Interruptor) * URL: https://github.com/riscv/riscv-aclint * * Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu * Copyright (c) 2017 SiFive, Inc. * Copyright (c) 2021 Western Digital Corporation or its affiliates. * * This provides real-time clock, timer and interprocessor interrupts. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2 or later, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ #include "qemu/osdep.h" #include "qapi/error.h" #include "qemu/error-report.h" #include "qemu/log.h" #include "qemu/module.h" #include "hw/sysbus.h" #include "target/riscv/cpu.h" #include "hw/qdev-properties.h" #include "hw/intc/riscv_aclint.h" #include "qemu/timer.h" #include "hw/irq.h" typedef struct riscv_aclint_mtimer_callback { RISCVAclintMTimerState *s; int num; } riscv_aclint_mtimer_callback; static uint64_t cpu_riscv_read_rtc_raw(uint32_t timebase_freq) { return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), timebase_freq, NANOSECONDS_PER_SECOND); } static uint64_t cpu_riscv_read_rtc(void *opaque) { RISCVAclintMTimerState *mtimer = opaque; return cpu_riscv_read_rtc_raw(mtimer->timebase_freq) + mtimer->time_delta; } /* * Called when timecmp is written to update the QEMU timer or immediately * trigger timer interrupt if mtimecmp <= current timer value. */ static void riscv_aclint_mtimer_write_timecmp(RISCVAclintMTimerState *mtimer, RISCVCPU *cpu, int hartid, uint64_t value) { uint32_t timebase_freq = mtimer->timebase_freq; uint64_t next; uint64_t diff; uint64_t rtc_r = cpu_riscv_read_rtc(mtimer); cpu->env.timecmp = value; if (cpu->env.timecmp <= rtc_r) { /* * If we're setting an MTIMECMP value in the "past", * immediately raise the timer interrupt */ qemu_irq_raise(mtimer->timer_irqs[hartid - mtimer->hartid_base]); return; } /* otherwise, set up the future timer interrupt */ qemu_irq_lower(mtimer->timer_irqs[hartid - mtimer->hartid_base]); diff = cpu->env.timecmp - rtc_r; /* back to ns (note args switched in muldiv64) */ uint64_t ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq); /* * check if ns_diff overflowed and check if the addition would potentially * overflow */ if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) || ns_diff > INT64_MAX) { next = INT64_MAX; } else { /* * as it is very unlikely qemu_clock_get_ns will return a value * greater than INT64_MAX, no additional check is needed for an * unsigned integer overflow. */ next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff; /* * if ns_diff is INT64_MAX next may still be outside the range * of a signed integer. */ next = MIN(next, INT64_MAX); } timer_mod(cpu->env.timer, next); } /* * Callback used when the timer set using timer_mod expires. * Should raise the timer interrupt line */ static void riscv_aclint_mtimer_cb(void *opaque) { riscv_aclint_mtimer_callback *state = opaque; qemu_irq_raise(state->s->timer_irqs[state->num]); } /* CPU read MTIMER register */ static uint64_t riscv_aclint_mtimer_read(void *opaque, hwaddr addr, unsigned size) { RISCVAclintMTimerState *mtimer = opaque; if (addr >= mtimer->timecmp_base && addr < (mtimer->timecmp_base + (mtimer->num_harts << 3))) { size_t hartid = mtimer->hartid_base + ((addr - mtimer->timecmp_base) >> 3); CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, "aclint-mtimer: invalid hartid: %zu", hartid); } else if ((addr & 0x7) == 0) { /* timecmp_lo for RV32/RV64 or timecmp for RV64 */ uint64_t timecmp = env->timecmp; return (size == 4) ? (timecmp & 0xFFFFFFFF) : timecmp; } else if ((addr & 0x7) == 4) { /* timecmp_hi */ uint64_t timecmp = env->timecmp; return (timecmp >> 32) & 0xFFFFFFFF; } else { qemu_log_mask(LOG_UNIMP, "aclint-mtimer: invalid read: %08x", (uint32_t)addr); return 0; } } else if (addr == mtimer->time_base) { /* time_lo for RV32/RV64 or timecmp for RV64 */ uint64_t rtc = cpu_riscv_read_rtc(mtimer); return (size == 4) ? (rtc & 0xFFFFFFFF) : rtc; } else if (addr == mtimer->time_base + 4) { /* time_hi */ return (cpu_riscv_read_rtc(mtimer) >> 32) & 0xFFFFFFFF; } qemu_log_mask(LOG_UNIMP, "aclint-mtimer: invalid read: %08x", (uint32_t)addr); return 0; } /* CPU write MTIMER register */ static void riscv_aclint_mtimer_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { RISCVAclintMTimerState *mtimer = opaque; int i; if (addr >= mtimer->timecmp_base && addr < (mtimer->timecmp_base + (mtimer->num_harts << 3))) { size_t hartid = mtimer->hartid_base + ((addr - mtimer->timecmp_base) >> 3); CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, "aclint-mtimer: invalid hartid: %zu", hartid); } else if ((addr & 0x7) == 0) { if (size == 4) { /* timecmp_lo for RV32/RV64 */ uint64_t timecmp_hi = env->timecmp >> 32; riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, timecmp_hi << 32 | (value & 0xFFFFFFFF)); } else { /* timecmp for RV64 */ riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, value); } } else if ((addr & 0x7) == 4) { if (size == 4) { /* timecmp_hi for RV32/RV64 */ uint64_t timecmp_lo = env->timecmp; riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, value << 32 | (timecmp_lo & 0xFFFFFFFF)); } else { qemu_log_mask(LOG_GUEST_ERROR, "aclint-mtimer: invalid timecmp_hi write: %08x", (uint32_t)addr); } } else { qemu_log_mask(LOG_UNIMP, "aclint-mtimer: invalid timecmp write: %08x", (uint32_t)addr); } return; } else if (addr == mtimer->time_base || addr == mtimer->time_base + 4) { uint64_t rtc_r = cpu_riscv_read_rtc_raw(mtimer->timebase_freq); if (addr == mtimer->time_base) { if (size == 4) { /* time_lo for RV32/RV64 */ mtimer->time_delta = ((rtc_r & ~0xFFFFFFFFULL) | value) - rtc_r; } else { /* time for RV64 */ mtimer->time_delta = value - rtc_r; } } else { if (size == 4) { /* time_hi for RV32/RV64 */ mtimer->time_delta = (value << 32 | (rtc_r & 0xFFFFFFFF)) - rtc_r; } else { qemu_log_mask(LOG_GUEST_ERROR, "aclint-mtimer: invalid time_hi write: %08x", (uint32_t)addr); return; } } /* Check if timer interrupt is triggered for each hart. */ for (i = 0; i < mtimer->num_harts; i++) { CPUState *cpu = qemu_get_cpu(mtimer->hartid_base + i); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { continue; } riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), i, env->timecmp); } return; } qemu_log_mask(LOG_UNIMP, "aclint-mtimer: invalid write: %08x", (uint32_t)addr); } static const MemoryRegionOps riscv_aclint_mtimer_ops = { .read = riscv_aclint_mtimer_read, .write = riscv_aclint_mtimer_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 8 }, .impl = { .min_access_size = 4, .max_access_size = 8, } }; static Property riscv_aclint_mtimer_properties[] = { DEFINE_PROP_UINT32("hartid-base", RISCVAclintMTimerState, hartid_base, 0), DEFINE_PROP_UINT32("num-harts", RISCVAclintMTimerState, num_harts, 1), DEFINE_PROP_UINT32("timecmp-base", RISCVAclintMTimerState, timecmp_base, RISCV_ACLINT_DEFAULT_MTIMECMP), DEFINE_PROP_UINT32("time-base", RISCVAclintMTimerState, time_base, RISCV_ACLINT_DEFAULT_MTIME), DEFINE_PROP_UINT32("aperture-size", RISCVAclintMTimerState, aperture_size, RISCV_ACLINT_DEFAULT_MTIMER_SIZE), DEFINE_PROP_UINT32("timebase-freq", RISCVAclintMTimerState, timebase_freq, 0), DEFINE_PROP_END_OF_LIST(), }; static void riscv_aclint_mtimer_realize(DeviceState *dev, Error **errp) { RISCVAclintMTimerState *s = RISCV_ACLINT_MTIMER(dev); int i; memory_region_init_io(&s->mmio, OBJECT(dev), &riscv_aclint_mtimer_ops, s, TYPE_RISCV_ACLINT_MTIMER, s->aperture_size); sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); s->timer_irqs = g_new(qemu_irq, s->num_harts); qdev_init_gpio_out(dev, s->timer_irqs, s->num_harts); /* Claim timer interrupt bits */ for (i = 0; i < s->num_harts; i++) { RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(s->hartid_base + i)); if (riscv_cpu_claim_interrupts(cpu, MIP_MTIP) < 0) { error_report("MTIP already claimed"); exit(1); } } } static void riscv_aclint_mtimer_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = riscv_aclint_mtimer_realize; device_class_set_props(dc, riscv_aclint_mtimer_properties); } static const TypeInfo riscv_aclint_mtimer_info = { .name = TYPE_RISCV_ACLINT_MTIMER, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(RISCVAclintMTimerState), .class_init = riscv_aclint_mtimer_class_init, }; /* * Create ACLINT MTIMER device. */ DeviceState *riscv_aclint_mtimer_create(hwaddr addr, hwaddr size, uint32_t hartid_base, uint32_t num_harts, uint32_t timecmp_base, uint32_t time_base, uint32_t timebase_freq, bool provide_rdtime) { int i; DeviceState *dev = qdev_new(TYPE_RISCV_ACLINT_MTIMER); assert(num_harts <= RISCV_ACLINT_MAX_HARTS); assert(!(addr & 0x7)); assert(!(timecmp_base & 0x7)); assert(!(time_base & 0x7)); qdev_prop_set_uint32(dev, "hartid-base", hartid_base); qdev_prop_set_uint32(dev, "num-harts", num_harts); qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base); qdev_prop_set_uint32(dev, "time-base", time_base); qdev_prop_set_uint32(dev, "aperture-size", size); qdev_prop_set_uint32(dev, "timebase-freq", timebase_freq); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); for (i = 0; i < num_harts; i++) { CPUState *cpu = qemu_get_cpu(hartid_base + i); RISCVCPU *rvcpu = RISCV_CPU(cpu); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; riscv_aclint_mtimer_callback *cb = g_new0(riscv_aclint_mtimer_callback, 1); if (!env) { g_free(cb); continue; } if (provide_rdtime) { riscv_cpu_set_rdtime_fn(env, cpu_riscv_read_rtc, dev); } cb->s = RISCV_ACLINT_MTIMER(dev); cb->num = i; env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_aclint_mtimer_cb, cb); env->timecmp = 0; qdev_connect_gpio_out(dev, i, qdev_get_gpio_in(DEVICE(rvcpu), IRQ_M_TIMER)); } return dev; } /* CPU read [M|S]SWI register */ static uint64_t riscv_aclint_swi_read(void *opaque, hwaddr addr, unsigned size) { RISCVAclintSwiState *swi = opaque; if (addr < (swi->num_harts << 2)) { size_t hartid = swi->hartid_base + (addr >> 2); CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, "aclint-swi: invalid hartid: %zu", hartid); } else if ((addr & 0x3) == 0) { return (swi->sswi) ? 0 : ((env->mip & MIP_MSIP) > 0); } } qemu_log_mask(LOG_UNIMP, "aclint-swi: invalid read: %08x", (uint32_t)addr); return 0; } /* CPU write [M|S]SWI register */ static void riscv_aclint_swi_write(void *opaque, hwaddr addr, uint64_t value, unsigned size) { RISCVAclintSwiState *swi = opaque; if (addr < (swi->num_harts << 2)) { size_t hartid = swi->hartid_base + (addr >> 2); CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { qemu_log_mask(LOG_GUEST_ERROR, "aclint-swi: invalid hartid: %zu", hartid); } else if ((addr & 0x3) == 0) { if (value & 0x1) { qemu_irq_raise(swi->soft_irqs[hartid - swi->hartid_base]); } else { if (!swi->sswi) { qemu_irq_lower(swi->soft_irqs[hartid - swi->hartid_base]); } } return; } } qemu_log_mask(LOG_UNIMP, "aclint-swi: invalid write: %08x", (uint32_t)addr); } static const MemoryRegionOps riscv_aclint_swi_ops = { .read = riscv_aclint_swi_read, .write = riscv_aclint_swi_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4 } }; static Property riscv_aclint_swi_properties[] = { DEFINE_PROP_UINT32("hartid-base", RISCVAclintSwiState, hartid_base, 0), DEFINE_PROP_UINT32("num-harts", RISCVAclintSwiState, num_harts, 1), DEFINE_PROP_UINT32("sswi", RISCVAclintSwiState, sswi, false), DEFINE_PROP_END_OF_LIST(), }; static void riscv_aclint_swi_realize(DeviceState *dev, Error **errp) { RISCVAclintSwiState *swi = RISCV_ACLINT_SWI(dev); int i; memory_region_init_io(&swi->mmio, OBJECT(dev), &riscv_aclint_swi_ops, swi, TYPE_RISCV_ACLINT_SWI, RISCV_ACLINT_SWI_SIZE); sysbus_init_mmio(SYS_BUS_DEVICE(dev), &swi->mmio); swi->soft_irqs = g_new(qemu_irq, swi->num_harts); qdev_init_gpio_out(dev, swi->soft_irqs, swi->num_harts); /* Claim software interrupt bits */ for (i = 0; i < swi->num_harts; i++) { RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(swi->hartid_base + i)); /* We don't claim mip.SSIP because it is writeable by software */ if (riscv_cpu_claim_interrupts(cpu, swi->sswi ? 0 : MIP_MSIP) < 0) { error_report("MSIP already claimed"); exit(1); } } } static void riscv_aclint_swi_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); dc->realize = riscv_aclint_swi_realize; device_class_set_props(dc, riscv_aclint_swi_properties); } static const TypeInfo riscv_aclint_swi_info = { .name = TYPE_RISCV_ACLINT_SWI, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(RISCVAclintSwiState), .class_init = riscv_aclint_swi_class_init, }; /* * Create ACLINT [M|S]SWI device. */ DeviceState *riscv_aclint_swi_create(hwaddr addr, uint32_t hartid_base, uint32_t num_harts, bool sswi) { int i; DeviceState *dev = qdev_new(TYPE_RISCV_ACLINT_SWI); assert(num_harts <= RISCV_ACLINT_MAX_HARTS); assert(!(addr & 0x3)); qdev_prop_set_uint32(dev, "hartid-base", hartid_base); qdev_prop_set_uint32(dev, "num-harts", num_harts); qdev_prop_set_uint32(dev, "sswi", sswi ? true : false); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); for (i = 0; i < num_harts; i++) { CPUState *cpu = qemu_get_cpu(hartid_base + i); RISCVCPU *rvcpu = RISCV_CPU(cpu); qdev_connect_gpio_out(dev, i, qdev_get_gpio_in(DEVICE(rvcpu), (sswi) ? IRQ_S_SOFT : IRQ_M_SOFT)); } return dev; } static void riscv_aclint_register_types(void) { type_register_static(&riscv_aclint_mtimer_info); type_register_static(&riscv_aclint_swi_info); } type_init(riscv_aclint_register_types)