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" 233e80f690SMarkus 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)) { 82*3bf03f08SAnup Patel size_t hartid = clint->hartid_base + ((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)) { 95*3bf03f08SAnup Patel size_t hartid = clint->hartid_base + 96*3bf03f08SAnup Patel ((addr - clint->timecmp_base) >> 3); 971c77c410SMichael Clark CPUState *cpu = qemu_get_cpu(hartid); 981c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 991c77c410SMichael Clark if (!env) { 1001c77c410SMichael Clark error_report("clint: invalid timecmp hartid: %zu", hartid); 1011c77c410SMichael Clark } else if ((addr & 0x7) == 0) { 1021c77c410SMichael Clark /* timecmp_lo */ 1031c77c410SMichael Clark uint64_t timecmp = env->timecmp; 1041c77c410SMichael Clark return timecmp & 0xFFFFFFFF; 1051c77c410SMichael Clark } else if ((addr & 0x7) == 4) { 1061c77c410SMichael Clark /* timecmp_hi */ 1071c77c410SMichael Clark uint64_t timecmp = env->timecmp; 1081c77c410SMichael Clark return (timecmp >> 32) & 0xFFFFFFFF; 1091c77c410SMichael Clark } else { 1101c77c410SMichael Clark error_report("clint: invalid read: %08x", (uint32_t)addr); 1111c77c410SMichael Clark return 0; 1121c77c410SMichael Clark } 1131c77c410SMichael Clark } else if (addr == clint->time_base) { 1141c77c410SMichael Clark /* time_lo */ 1151c77c410SMichael Clark return cpu_riscv_read_rtc() & 0xFFFFFFFF; 1161c77c410SMichael Clark } else if (addr == clint->time_base + 4) { 1171c77c410SMichael Clark /* time_hi */ 1181c77c410SMichael Clark return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF; 1191c77c410SMichael Clark } 1201c77c410SMichael Clark 1211c77c410SMichael Clark error_report("clint: invalid read: %08x", (uint32_t)addr); 1221c77c410SMichael Clark return 0; 1231c77c410SMichael Clark } 1241c77c410SMichael Clark 1251c77c410SMichael Clark /* CPU wrote to rtc or timecmp register */ 1261c77c410SMichael Clark static void sifive_clint_write(void *opaque, hwaddr addr, uint64_t value, 1271c77c410SMichael Clark unsigned size) 1281c77c410SMichael Clark { 1291c77c410SMichael Clark SiFiveCLINTState *clint = opaque; 1301c77c410SMichael Clark 1311c77c410SMichael Clark if (addr >= clint->sip_base && 1321c77c410SMichael Clark addr < clint->sip_base + (clint->num_harts << 2)) { 133*3bf03f08SAnup Patel size_t hartid = clint->hartid_base + ((addr - clint->sip_base) >> 2); 1341c77c410SMichael Clark CPUState *cpu = qemu_get_cpu(hartid); 1351c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 1361c77c410SMichael Clark if (!env) { 1371c77c410SMichael Clark error_report("clint: invalid timecmp hartid: %zu", hartid); 1381c77c410SMichael Clark } else if ((addr & 0x3) == 0) { 13985ba724fSMichael Clark riscv_cpu_update_mip(RISCV_CPU(cpu), MIP_MSIP, BOOL_TO_MASK(value)); 1401c77c410SMichael Clark } else { 1411c77c410SMichael Clark error_report("clint: invalid sip write: %08x", (uint32_t)addr); 1421c77c410SMichael Clark } 1431c77c410SMichael Clark return; 1441c77c410SMichael Clark } else if (addr >= clint->timecmp_base && 1451c77c410SMichael Clark addr < clint->timecmp_base + (clint->num_harts << 3)) { 146*3bf03f08SAnup Patel size_t hartid = clint->hartid_base + 147*3bf03f08SAnup Patel ((addr - clint->timecmp_base) >> 3); 1481c77c410SMichael Clark CPUState *cpu = qemu_get_cpu(hartid); 1491c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 1501c77c410SMichael Clark if (!env) { 1511c77c410SMichael Clark error_report("clint: invalid timecmp hartid: %zu", hartid); 1521c77c410SMichael Clark } else if ((addr & 0x7) == 0) { 1531c77c410SMichael Clark /* timecmp_lo */ 154ef9e41dfSMichael Clark uint64_t timecmp_hi = env->timecmp >> 32; 1551c77c410SMichael Clark sifive_clint_write_timecmp(RISCV_CPU(cpu), 156ef9e41dfSMichael Clark timecmp_hi << 32 | (value & 0xFFFFFFFF)); 1571c77c410SMichael Clark return; 1581c77c410SMichael Clark } else if ((addr & 0x7) == 4) { 1591c77c410SMichael Clark /* timecmp_hi */ 160ef9e41dfSMichael Clark uint64_t timecmp_lo = env->timecmp; 1611c77c410SMichael Clark sifive_clint_write_timecmp(RISCV_CPU(cpu), 162ef9e41dfSMichael Clark value << 32 | (timecmp_lo & 0xFFFFFFFF)); 1631c77c410SMichael Clark } else { 1641c77c410SMichael Clark error_report("clint: invalid timecmp write: %08x", (uint32_t)addr); 1651c77c410SMichael Clark } 1661c77c410SMichael Clark return; 1671c77c410SMichael Clark } else if (addr == clint->time_base) { 1681c77c410SMichael Clark /* time_lo */ 1691c77c410SMichael Clark error_report("clint: time_lo write not implemented"); 1701c77c410SMichael Clark return; 1711c77c410SMichael Clark } else if (addr == clint->time_base + 4) { 1721c77c410SMichael Clark /* time_hi */ 1731c77c410SMichael Clark error_report("clint: time_hi write not implemented"); 1741c77c410SMichael Clark return; 1751c77c410SMichael Clark } 1761c77c410SMichael Clark 1771c77c410SMichael Clark error_report("clint: invalid write: %08x", (uint32_t)addr); 1781c77c410SMichael Clark } 1791c77c410SMichael Clark 1801c77c410SMichael Clark static const MemoryRegionOps sifive_clint_ops = { 1811c77c410SMichael Clark .read = sifive_clint_read, 1821c77c410SMichael Clark .write = sifive_clint_write, 1831c77c410SMichael Clark .endianness = DEVICE_LITTLE_ENDIAN, 1841c77c410SMichael Clark .valid = { 1851c77c410SMichael Clark .min_access_size = 4, 18670b78d4eSAlistair Francis .max_access_size = 8 1871c77c410SMichael Clark } 1881c77c410SMichael Clark }; 1891c77c410SMichael Clark 1901c77c410SMichael Clark static Property sifive_clint_properties[] = { 191*3bf03f08SAnup Patel DEFINE_PROP_UINT32("hartid-base", SiFiveCLINTState, hartid_base, 0), 1921c77c410SMichael Clark DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState, num_harts, 0), 1931c77c410SMichael Clark DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState, sip_base, 0), 1941c77c410SMichael Clark DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState, timecmp_base, 0), 1951c77c410SMichael Clark DEFINE_PROP_UINT32("time-base", SiFiveCLINTState, time_base, 0), 1961c77c410SMichael Clark DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState, aperture_size, 0), 1971c77c410SMichael Clark DEFINE_PROP_END_OF_LIST(), 1981c77c410SMichael Clark }; 1991c77c410SMichael Clark 2001c77c410SMichael Clark static void sifive_clint_realize(DeviceState *dev, Error **errp) 2011c77c410SMichael Clark { 2021c77c410SMichael Clark SiFiveCLINTState *s = SIFIVE_CLINT(dev); 2031c77c410SMichael Clark memory_region_init_io(&s->mmio, OBJECT(dev), &sifive_clint_ops, s, 2041c77c410SMichael Clark TYPE_SIFIVE_CLINT, s->aperture_size); 2051c77c410SMichael Clark sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); 2061c77c410SMichael Clark } 2071c77c410SMichael Clark 2081c77c410SMichael Clark static void sifive_clint_class_init(ObjectClass *klass, void *data) 2091c77c410SMichael Clark { 2101c77c410SMichael Clark DeviceClass *dc = DEVICE_CLASS(klass); 2111c77c410SMichael Clark dc->realize = sifive_clint_realize; 2124f67d30bSMarc-André Lureau device_class_set_props(dc, sifive_clint_properties); 2131c77c410SMichael Clark } 2141c77c410SMichael Clark 2151c77c410SMichael Clark static const TypeInfo sifive_clint_info = { 2161c77c410SMichael Clark .name = TYPE_SIFIVE_CLINT, 2171c77c410SMichael Clark .parent = TYPE_SYS_BUS_DEVICE, 2181c77c410SMichael Clark .instance_size = sizeof(SiFiveCLINTState), 2191c77c410SMichael Clark .class_init = sifive_clint_class_init, 2201c77c410SMichael Clark }; 2211c77c410SMichael Clark 2221c77c410SMichael Clark static void sifive_clint_register_types(void) 2231c77c410SMichael Clark { 2241c77c410SMichael Clark type_register_static(&sifive_clint_info); 2251c77c410SMichael Clark } 2261c77c410SMichael Clark 2271c77c410SMichael Clark type_init(sifive_clint_register_types) 2281c77c410SMichael Clark 2291c77c410SMichael Clark 2301c77c410SMichael Clark /* 2311c77c410SMichael Clark * Create CLINT device. 2321c77c410SMichael Clark */ 233*3bf03f08SAnup Patel DeviceState *sifive_clint_create(hwaddr addr, hwaddr size, 234*3bf03f08SAnup Patel uint32_t hartid_base, uint32_t num_harts, uint32_t sip_base, 235*3bf03f08SAnup Patel uint32_t timecmp_base, uint32_t time_base, bool provide_rdtime) 2361c77c410SMichael Clark { 2371c77c410SMichael Clark int i; 2381c77c410SMichael Clark for (i = 0; i < num_harts; i++) { 239*3bf03f08SAnup Patel CPUState *cpu = qemu_get_cpu(hartid_base + i); 2401c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 2411c77c410SMichael Clark if (!env) { 2421c77c410SMichael Clark continue; 2431c77c410SMichael Clark } 2445f3616ccSAnup Patel if (provide_rdtime) { 2455f3616ccSAnup Patel riscv_cpu_set_rdtime_fn(env, cpu_riscv_read_rtc); 2465f3616ccSAnup Patel } 2471c77c410SMichael Clark env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, 2481c77c410SMichael Clark &sifive_clint_timer_cb, cpu); 2491c77c410SMichael Clark env->timecmp = 0; 2501c77c410SMichael Clark } 2511c77c410SMichael Clark 2523e80f690SMarkus Armbruster DeviceState *dev = qdev_new(TYPE_SIFIVE_CLINT); 253*3bf03f08SAnup Patel qdev_prop_set_uint32(dev, "hartid-base", hartid_base); 2541c77c410SMichael Clark qdev_prop_set_uint32(dev, "num-harts", num_harts); 2551c77c410SMichael Clark qdev_prop_set_uint32(dev, "sip-base", sip_base); 2561c77c410SMichael Clark qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base); 2571c77c410SMichael Clark qdev_prop_set_uint32(dev, "time-base", time_base); 2581c77c410SMichael Clark qdev_prop_set_uint32(dev, "aperture-size", size); 2593c6ef471SMarkus Armbruster sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); 2601c77c410SMichael Clark sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); 2611c77c410SMichael Clark return dev; 2621c77c410SMichael Clark } 263