diff options
Diffstat (limited to 'hw/intc/riscv_aclint.c')
-rw-r--r-- | hw/intc/riscv_aclint.c | 460 |
1 files changed, 460 insertions, 0 deletions
diff --git a/hw/intc/riscv_aclint.c b/hw/intc/riscv_aclint.c new file mode 100644 index 000000000..f1a5d3d28 --- /dev/null +++ b/hw/intc/riscv_aclint.c @@ -0,0 +1,460 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#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(uint32_t timebase_freq) +{ + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), + timebase_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 void riscv_aclint_mtimer_write_timecmp(RISCVAclintMTimerState *mtimer, + RISCVCPU *cpu, + int hartid, + uint64_t value, + uint32_t timebase_freq) +{ + uint64_t next; + uint64_t diff; + + uint64_t rtc_r = cpu_riscv_read_rtc(timebase_freq); + + 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 */ + uint64_t timecmp = env->timecmp; + return timecmp & 0xFFFFFFFF; + } 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 */ + return cpu_riscv_read_rtc(mtimer->timebase_freq) & 0xFFFFFFFF; + } else if (addr == mtimer->time_base + 4) { + /* time_hi */ + return (cpu_riscv_read_rtc(mtimer->timebase_freq) >> 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; + + 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 */ + uint64_t timecmp_hi = env->timecmp >> 32; + riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, + timecmp_hi << 32 | (value & 0xFFFFFFFF), + mtimer->timebase_freq); + return; + } else if ((addr & 0x7) == 4) { + /* timecmp_hi */ + uint64_t timecmp_lo = env->timecmp; + riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid, + value << 32 | (timecmp_lo & 0xFFFFFFFF), + mtimer->timebase_freq); + } else { + qemu_log_mask(LOG_UNIMP, + "aclint-mtimer: invalid timecmp write: %08x", + (uint32_t)addr); + } + return; + } else if (addr == mtimer->time_base) { + /* time_lo */ + qemu_log_mask(LOG_UNIMP, + "aclint-mtimer: time_lo write not implemented"); + return; + } else if (addr == mtimer->time_base + 4) { + /* time_hi */ + qemu_log_mask(LOG_UNIMP, + "aclint-mtimer: time_hi write not implemented"); + 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 + } +}; + +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_malloc(sizeof(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_malloc0(sizeof(riscv_aclint_mtimer_callback)); + + if (!env) { + g_free(cb); + continue; + } + if (provide_rdtime) { + riscv_cpu_set_rdtime_fn(env, cpu_riscv_read_rtc, timebase_freq); + } + + 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_malloc(sizeof(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) |