xref: /qemu/hw/intc/riscv_aclint.c (revision 3e80f6902c13f6edb6675c0f33edcbbf0163ec32)
11c77c410SMichael Clark /*
21c77c410SMichael Clark  * SiFive CLINT (Core Local Interruptor)
31c77c410SMichael Clark  *
41c77c410SMichael Clark  * Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu
51c77c410SMichael Clark  * Copyright (c) 2017 SiFive, Inc.
61c77c410SMichael Clark  *
71c77c410SMichael Clark  * This provides real-time clock, timer and interprocessor interrupts.
81c77c410SMichael Clark  *
91c77c410SMichael Clark  * This program is free software; you can redistribute it and/or modify it
101c77c410SMichael Clark  * under the terms and conditions of the GNU General Public License,
111c77c410SMichael Clark  * version 2 or later, as published by the Free Software Foundation.
121c77c410SMichael Clark  *
131c77c410SMichael Clark  * This program is distributed in the hope it will be useful, but WITHOUT
141c77c410SMichael Clark  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
151c77c410SMichael Clark  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
161c77c410SMichael Clark  * more details.
171c77c410SMichael Clark  *
181c77c410SMichael Clark  * You should have received a copy of the GNU General Public License along with
191c77c410SMichael Clark  * this program.  If not, see <http://www.gnu.org/licenses/>.
201c77c410SMichael Clark  */
211c77c410SMichael Clark 
221c77c410SMichael Clark #include "qemu/osdep.h"
23*3e80f690SMarkus Armbruster #include "qapi/error.h"
241c77c410SMichael Clark #include "qemu/error-report.h"
250b8fa32fSMarkus Armbruster #include "qemu/module.h"
261c77c410SMichael Clark #include "hw/sysbus.h"
271c77c410SMichael Clark #include "target/riscv/cpu.h"
28a27bd6c7SMarkus Armbruster #include "hw/qdev-properties.h"
291c77c410SMichael Clark #include "hw/riscv/sifive_clint.h"
301c77c410SMichael Clark #include "qemu/timer.h"
311c77c410SMichael Clark 
321c77c410SMichael Clark static uint64_t cpu_riscv_read_rtc(void)
331c77c410SMichael Clark {
342a8756edSMichael Clark     return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
352a8756edSMichael Clark         SIFIVE_CLINT_TIMEBASE_FREQ, NANOSECONDS_PER_SECOND);
361c77c410SMichael Clark }
371c77c410SMichael Clark 
381c77c410SMichael Clark /*
391c77c410SMichael Clark  * Called when timecmp is written to update the QEMU timer or immediately
401c77c410SMichael Clark  * trigger timer interrupt if mtimecmp <= current timer value.
411c77c410SMichael Clark  */
421c77c410SMichael Clark static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value)
431c77c410SMichael Clark {
441c77c410SMichael Clark     uint64_t next;
451c77c410SMichael Clark     uint64_t diff;
461c77c410SMichael Clark 
471c77c410SMichael Clark     uint64_t rtc_r = cpu_riscv_read_rtc();
481c77c410SMichael Clark 
491c77c410SMichael Clark     cpu->env.timecmp = value;
501c77c410SMichael Clark     if (cpu->env.timecmp <= rtc_r) {
511c77c410SMichael Clark         /* if we're setting an MTIMECMP value in the "past",
521c77c410SMichael Clark            immediately raise the timer interrupt */
5385ba724fSMichael Clark         riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(1));
541c77c410SMichael Clark         return;
551c77c410SMichael Clark     }
561c77c410SMichael Clark 
571c77c410SMichael Clark     /* otherwise, set up the future timer interrupt */
5885ba724fSMichael Clark     riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(0));
591c77c410SMichael Clark     diff = cpu->env.timecmp - rtc_r;
601c77c410SMichael Clark     /* back to ns (note args switched in muldiv64) */
611c77c410SMichael Clark     next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
622a8756edSMichael Clark         muldiv64(diff, NANOSECONDS_PER_SECOND, SIFIVE_CLINT_TIMEBASE_FREQ);
631c77c410SMichael Clark     timer_mod(cpu->env.timer, next);
641c77c410SMichael Clark }
651c77c410SMichael Clark 
661c77c410SMichael Clark /*
671c77c410SMichael Clark  * Callback used when the timer set using timer_mod expires.
681c77c410SMichael Clark  * Should raise the timer interrupt line
691c77c410SMichael Clark  */
701c77c410SMichael Clark static void sifive_clint_timer_cb(void *opaque)
711c77c410SMichael Clark {
721c77c410SMichael Clark     RISCVCPU *cpu = opaque;
7385ba724fSMichael Clark     riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(1));
741c77c410SMichael Clark }
751c77c410SMichael Clark 
761c77c410SMichael Clark /* CPU wants to read rtc or timecmp register */
771c77c410SMichael Clark static uint64_t sifive_clint_read(void *opaque, hwaddr addr, unsigned size)
781c77c410SMichael Clark {
791c77c410SMichael Clark     SiFiveCLINTState *clint = opaque;
801c77c410SMichael Clark     if (addr >= clint->sip_base &&
811c77c410SMichael Clark         addr < clint->sip_base + (clint->num_harts << 2)) {
821c77c410SMichael Clark         size_t hartid = (addr - clint->sip_base) >> 2;
831c77c410SMichael Clark         CPUState *cpu = qemu_get_cpu(hartid);
841c77c410SMichael Clark         CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
851c77c410SMichael Clark         if (!env) {
861c77c410SMichael Clark             error_report("clint: invalid timecmp hartid: %zu", hartid);
871c77c410SMichael Clark         } else if ((addr & 0x3) == 0) {
881c77c410SMichael Clark             return (env->mip & MIP_MSIP) > 0;
891c77c410SMichael Clark         } else {
901c77c410SMichael Clark             error_report("clint: invalid read: %08x", (uint32_t)addr);
911c77c410SMichael Clark             return 0;
921c77c410SMichael Clark         }
931c77c410SMichael Clark     } else if (addr >= clint->timecmp_base &&
941c77c410SMichael Clark         addr < clint->timecmp_base + (clint->num_harts << 3)) {
951c77c410SMichael Clark         size_t hartid = (addr - clint->timecmp_base) >> 3;
961c77c410SMichael Clark         CPUState *cpu = qemu_get_cpu(hartid);
971c77c410SMichael Clark         CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
981c77c410SMichael Clark         if (!env) {
991c77c410SMichael Clark             error_report("clint: invalid timecmp hartid: %zu", hartid);
1001c77c410SMichael Clark         } else if ((addr & 0x7) == 0) {
1011c77c410SMichael Clark             /* timecmp_lo */
1021c77c410SMichael Clark             uint64_t timecmp = env->timecmp;
1031c77c410SMichael Clark             return timecmp & 0xFFFFFFFF;
1041c77c410SMichael Clark         } else if ((addr & 0x7) == 4) {
1051c77c410SMichael Clark             /* timecmp_hi */
1061c77c410SMichael Clark             uint64_t timecmp = env->timecmp;
1071c77c410SMichael Clark             return (timecmp >> 32) & 0xFFFFFFFF;
1081c77c410SMichael Clark         } else {
1091c77c410SMichael Clark             error_report("clint: invalid read: %08x", (uint32_t)addr);
1101c77c410SMichael Clark             return 0;
1111c77c410SMichael Clark         }
1121c77c410SMichael Clark     } else if (addr == clint->time_base) {
1131c77c410SMichael Clark         /* time_lo */
1141c77c410SMichael Clark         return cpu_riscv_read_rtc() & 0xFFFFFFFF;
1151c77c410SMichael Clark     } else if (addr == clint->time_base + 4) {
1161c77c410SMichael Clark         /* time_hi */
1171c77c410SMichael Clark         return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF;
1181c77c410SMichael Clark     }
1191c77c410SMichael Clark 
1201c77c410SMichael Clark     error_report("clint: invalid read: %08x", (uint32_t)addr);
1211c77c410SMichael Clark     return 0;
1221c77c410SMichael Clark }
1231c77c410SMichael Clark 
1241c77c410SMichael Clark /* CPU wrote to rtc or timecmp register */
1251c77c410SMichael Clark static void sifive_clint_write(void *opaque, hwaddr addr, uint64_t value,
1261c77c410SMichael Clark         unsigned size)
1271c77c410SMichael Clark {
1281c77c410SMichael Clark     SiFiveCLINTState *clint = opaque;
1291c77c410SMichael Clark 
1301c77c410SMichael Clark     if (addr >= clint->sip_base &&
1311c77c410SMichael Clark         addr < clint->sip_base + (clint->num_harts << 2)) {
1321c77c410SMichael Clark         size_t hartid = (addr - clint->sip_base) >> 2;
1331c77c410SMichael Clark         CPUState *cpu = qemu_get_cpu(hartid);
1341c77c410SMichael Clark         CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
1351c77c410SMichael Clark         if (!env) {
1361c77c410SMichael Clark             error_report("clint: invalid timecmp hartid: %zu", hartid);
1371c77c410SMichael Clark         } else if ((addr & 0x3) == 0) {
13885ba724fSMichael Clark             riscv_cpu_update_mip(RISCV_CPU(cpu), MIP_MSIP, BOOL_TO_MASK(value));
1391c77c410SMichael Clark         } else {
1401c77c410SMichael Clark             error_report("clint: invalid sip write: %08x", (uint32_t)addr);
1411c77c410SMichael Clark         }
1421c77c410SMichael Clark         return;
1431c77c410SMichael Clark     } else if (addr >= clint->timecmp_base &&
1441c77c410SMichael Clark         addr < clint->timecmp_base + (clint->num_harts << 3)) {
1451c77c410SMichael Clark         size_t hartid = (addr - clint->timecmp_base) >> 3;
1461c77c410SMichael Clark         CPUState *cpu = qemu_get_cpu(hartid);
1471c77c410SMichael Clark         CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
1481c77c410SMichael Clark         if (!env) {
1491c77c410SMichael Clark             error_report("clint: invalid timecmp hartid: %zu", hartid);
1501c77c410SMichael Clark         } else if ((addr & 0x7) == 0) {
1511c77c410SMichael Clark             /* timecmp_lo */
152ef9e41dfSMichael Clark             uint64_t timecmp_hi = env->timecmp >> 32;
1531c77c410SMichael Clark             sifive_clint_write_timecmp(RISCV_CPU(cpu),
154ef9e41dfSMichael Clark                 timecmp_hi << 32 | (value & 0xFFFFFFFF));
1551c77c410SMichael Clark             return;
1561c77c410SMichael Clark         } else if ((addr & 0x7) == 4) {
1571c77c410SMichael Clark             /* timecmp_hi */
158ef9e41dfSMichael Clark             uint64_t timecmp_lo = env->timecmp;
1591c77c410SMichael Clark             sifive_clint_write_timecmp(RISCV_CPU(cpu),
160ef9e41dfSMichael Clark                 value << 32 | (timecmp_lo & 0xFFFFFFFF));
1611c77c410SMichael Clark         } else {
1621c77c410SMichael Clark             error_report("clint: invalid timecmp write: %08x", (uint32_t)addr);
1631c77c410SMichael Clark         }
1641c77c410SMichael Clark         return;
1651c77c410SMichael Clark     } else if (addr == clint->time_base) {
1661c77c410SMichael Clark         /* time_lo */
1671c77c410SMichael Clark         error_report("clint: time_lo write not implemented");
1681c77c410SMichael Clark         return;
1691c77c410SMichael Clark     } else if (addr == clint->time_base + 4) {
1701c77c410SMichael Clark         /* time_hi */
1711c77c410SMichael Clark         error_report("clint: time_hi write not implemented");
1721c77c410SMichael Clark         return;
1731c77c410SMichael Clark     }
1741c77c410SMichael Clark 
1751c77c410SMichael Clark     error_report("clint: invalid write: %08x", (uint32_t)addr);
1761c77c410SMichael Clark }
1771c77c410SMichael Clark 
1781c77c410SMichael Clark static const MemoryRegionOps sifive_clint_ops = {
1791c77c410SMichael Clark     .read = sifive_clint_read,
1801c77c410SMichael Clark     .write = sifive_clint_write,
1811c77c410SMichael Clark     .endianness = DEVICE_LITTLE_ENDIAN,
1821c77c410SMichael Clark     .valid = {
1831c77c410SMichael Clark         .min_access_size = 4,
1841c77c410SMichael Clark         .max_access_size = 4
1851c77c410SMichael Clark     }
1861c77c410SMichael Clark };
1871c77c410SMichael Clark 
1881c77c410SMichael Clark static Property sifive_clint_properties[] = {
1891c77c410SMichael Clark     DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState, num_harts, 0),
1901c77c410SMichael Clark     DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState, sip_base, 0),
1911c77c410SMichael Clark     DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState, timecmp_base, 0),
1921c77c410SMichael Clark     DEFINE_PROP_UINT32("time-base", SiFiveCLINTState, time_base, 0),
1931c77c410SMichael Clark     DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState, aperture_size, 0),
1941c77c410SMichael Clark     DEFINE_PROP_END_OF_LIST(),
1951c77c410SMichael Clark };
1961c77c410SMichael Clark 
1971c77c410SMichael Clark static void sifive_clint_realize(DeviceState *dev, Error **errp)
1981c77c410SMichael Clark {
1991c77c410SMichael Clark     SiFiveCLINTState *s = SIFIVE_CLINT(dev);
2001c77c410SMichael Clark     memory_region_init_io(&s->mmio, OBJECT(dev), &sifive_clint_ops, s,
2011c77c410SMichael Clark                           TYPE_SIFIVE_CLINT, s->aperture_size);
2021c77c410SMichael Clark     sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
2031c77c410SMichael Clark }
2041c77c410SMichael Clark 
2051c77c410SMichael Clark static void sifive_clint_class_init(ObjectClass *klass, void *data)
2061c77c410SMichael Clark {
2071c77c410SMichael Clark     DeviceClass *dc = DEVICE_CLASS(klass);
2081c77c410SMichael Clark     dc->realize = sifive_clint_realize;
2094f67d30bSMarc-André Lureau     device_class_set_props(dc, sifive_clint_properties);
2101c77c410SMichael Clark }
2111c77c410SMichael Clark 
2121c77c410SMichael Clark static const TypeInfo sifive_clint_info = {
2131c77c410SMichael Clark     .name          = TYPE_SIFIVE_CLINT,
2141c77c410SMichael Clark     .parent        = TYPE_SYS_BUS_DEVICE,
2151c77c410SMichael Clark     .instance_size = sizeof(SiFiveCLINTState),
2161c77c410SMichael Clark     .class_init    = sifive_clint_class_init,
2171c77c410SMichael Clark };
2181c77c410SMichael Clark 
2191c77c410SMichael Clark static void sifive_clint_register_types(void)
2201c77c410SMichael Clark {
2211c77c410SMichael Clark     type_register_static(&sifive_clint_info);
2221c77c410SMichael Clark }
2231c77c410SMichael Clark 
2241c77c410SMichael Clark type_init(sifive_clint_register_types)
2251c77c410SMichael Clark 
2261c77c410SMichael Clark 
2271c77c410SMichael Clark /*
2281c77c410SMichael Clark  * Create CLINT device.
2291c77c410SMichael Clark  */
2301c77c410SMichael Clark DeviceState *sifive_clint_create(hwaddr addr, hwaddr size, uint32_t num_harts,
2315f3616ccSAnup Patel     uint32_t sip_base, uint32_t timecmp_base, uint32_t time_base,
2325f3616ccSAnup Patel     bool provide_rdtime)
2331c77c410SMichael Clark {
2341c77c410SMichael Clark     int i;
2351c77c410SMichael Clark     for (i = 0; i < num_harts; i++) {
2361c77c410SMichael Clark         CPUState *cpu = qemu_get_cpu(i);
2371c77c410SMichael Clark         CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
2381c77c410SMichael Clark         if (!env) {
2391c77c410SMichael Clark             continue;
2401c77c410SMichael Clark         }
2415f3616ccSAnup Patel         if (provide_rdtime) {
2425f3616ccSAnup Patel             riscv_cpu_set_rdtime_fn(env, cpu_riscv_read_rtc);
2435f3616ccSAnup Patel         }
2441c77c410SMichael Clark         env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
2451c77c410SMichael Clark                                   &sifive_clint_timer_cb, cpu);
2461c77c410SMichael Clark         env->timecmp = 0;
2471c77c410SMichael Clark     }
2481c77c410SMichael Clark 
249*3e80f690SMarkus Armbruster     DeviceState *dev = qdev_new(TYPE_SIFIVE_CLINT);
2501c77c410SMichael Clark     qdev_prop_set_uint32(dev, "num-harts", num_harts);
2511c77c410SMichael Clark     qdev_prop_set_uint32(dev, "sip-base", sip_base);
2521c77c410SMichael Clark     qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base);
2531c77c410SMichael Clark     qdev_prop_set_uint32(dev, "time-base", time_base);
2541c77c410SMichael Clark     qdev_prop_set_uint32(dev, "aperture-size", size);
255*3e80f690SMarkus Armbruster     qdev_realize_and_unref(dev, NULL, &error_fatal);
2561c77c410SMichael Clark     sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
2571c77c410SMichael Clark     return dev;
2581c77c410SMichael Clark }
259