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" 29*cc63a182SAnup Patel #include "hw/intc/riscv_aclint.h" 301c77c410SMichael Clark #include "qemu/timer.h" 31a714b8aaSAlistair Francis #include "hw/irq.h" 32a714b8aaSAlistair Francis 33a714b8aaSAlistair Francis typedef struct sifive_clint_callback { 34a714b8aaSAlistair Francis SiFiveCLINTState *s; 35a714b8aaSAlistair Francis int num; 36a714b8aaSAlistair Francis } sifive_clint_callback; 371c77c410SMichael Clark 38a47ef6e9SBin Meng static uint64_t cpu_riscv_read_rtc(uint32_t timebase_freq) 391c77c410SMichael Clark { 402a8756edSMichael Clark return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 41a47ef6e9SBin Meng timebase_freq, NANOSECONDS_PER_SECOND); 421c77c410SMichael Clark } 431c77c410SMichael Clark 441c77c410SMichael Clark /* 451c77c410SMichael Clark * Called when timecmp is written to update the QEMU timer or immediately 461c77c410SMichael Clark * trigger timer interrupt if mtimecmp <= current timer value. 471c77c410SMichael Clark */ 48a714b8aaSAlistair Francis static void sifive_clint_write_timecmp(SiFiveCLINTState *s, RISCVCPU *cpu, 49a714b8aaSAlistair Francis int hartid, 50a714b8aaSAlistair Francis uint64_t value, 51a47ef6e9SBin Meng uint32_t timebase_freq) 521c77c410SMichael Clark { 531c77c410SMichael Clark uint64_t next; 541c77c410SMichael Clark uint64_t diff; 551c77c410SMichael Clark 56a47ef6e9SBin Meng uint64_t rtc_r = cpu_riscv_read_rtc(timebase_freq); 571c77c410SMichael Clark 581c77c410SMichael Clark cpu->env.timecmp = value; 591c77c410SMichael Clark if (cpu->env.timecmp <= rtc_r) { 601c77c410SMichael Clark /* if we're setting an MTIMECMP value in the "past", 611c77c410SMichael Clark immediately raise the timer interrupt */ 62a714b8aaSAlistair Francis qemu_irq_raise(s->timer_irqs[hartid - s->hartid_base]); 631c77c410SMichael Clark return; 641c77c410SMichael Clark } 651c77c410SMichael Clark 661c77c410SMichael Clark /* otherwise, set up the future timer interrupt */ 67a714b8aaSAlistair Francis qemu_irq_lower(s->timer_irqs[hartid - s->hartid_base]); 681c77c410SMichael Clark diff = cpu->env.timecmp - rtc_r; 691c77c410SMichael Clark /* back to ns (note args switched in muldiv64) */ 704dc06bb8SDavid Hoppenbrouwers uint64_t ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq); 714dc06bb8SDavid Hoppenbrouwers 724dc06bb8SDavid Hoppenbrouwers /* 734dc06bb8SDavid Hoppenbrouwers * check if ns_diff overflowed and check if the addition would potentially 744dc06bb8SDavid Hoppenbrouwers * overflow 754dc06bb8SDavid Hoppenbrouwers */ 764dc06bb8SDavid Hoppenbrouwers if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) || 774dc06bb8SDavid Hoppenbrouwers ns_diff > INT64_MAX) { 784dc06bb8SDavid Hoppenbrouwers next = INT64_MAX; 794dc06bb8SDavid Hoppenbrouwers } else { 804dc06bb8SDavid Hoppenbrouwers /* 814dc06bb8SDavid Hoppenbrouwers * as it is very unlikely qemu_clock_get_ns will return a value 824dc06bb8SDavid Hoppenbrouwers * greater than INT64_MAX, no additional check is needed for an 834dc06bb8SDavid Hoppenbrouwers * unsigned integer overflow. 844dc06bb8SDavid Hoppenbrouwers */ 854dc06bb8SDavid Hoppenbrouwers next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff; 864dc06bb8SDavid Hoppenbrouwers /* 874dc06bb8SDavid Hoppenbrouwers * if ns_diff is INT64_MAX next may still be outside the range 884dc06bb8SDavid Hoppenbrouwers * of a signed integer. 894dc06bb8SDavid Hoppenbrouwers */ 904dc06bb8SDavid Hoppenbrouwers next = MIN(next, INT64_MAX); 914dc06bb8SDavid Hoppenbrouwers } 924dc06bb8SDavid Hoppenbrouwers 931c77c410SMichael Clark timer_mod(cpu->env.timer, next); 941c77c410SMichael Clark } 951c77c410SMichael Clark 961c77c410SMichael Clark /* 971c77c410SMichael Clark * Callback used when the timer set using timer_mod expires. 981c77c410SMichael Clark * Should raise the timer interrupt line 991c77c410SMichael Clark */ 1001c77c410SMichael Clark static void sifive_clint_timer_cb(void *opaque) 1011c77c410SMichael Clark { 102a714b8aaSAlistair Francis sifive_clint_callback *state = opaque; 103a714b8aaSAlistair Francis 104a714b8aaSAlistair Francis qemu_irq_raise(state->s->timer_irqs[state->num]); 1051c77c410SMichael Clark } 1061c77c410SMichael Clark 1071c77c410SMichael Clark /* CPU wants to read rtc or timecmp register */ 1081c77c410SMichael Clark static uint64_t sifive_clint_read(void *opaque, hwaddr addr, unsigned size) 1091c77c410SMichael Clark { 1101c77c410SMichael Clark SiFiveCLINTState *clint = opaque; 1111c77c410SMichael Clark if (addr >= clint->sip_base && 1121c77c410SMichael Clark addr < clint->sip_base + (clint->num_harts << 2)) { 1133bf03f08SAnup Patel size_t hartid = clint->hartid_base + ((addr - clint->sip_base) >> 2); 1141c77c410SMichael Clark CPUState *cpu = qemu_get_cpu(hartid); 1151c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 1161c77c410SMichael Clark if (!env) { 1171c77c410SMichael Clark error_report("clint: invalid timecmp hartid: %zu", hartid); 1181c77c410SMichael Clark } else if ((addr & 0x3) == 0) { 1191c77c410SMichael Clark return (env->mip & MIP_MSIP) > 0; 1201c77c410SMichael Clark } else { 1211c77c410SMichael Clark error_report("clint: invalid read: %08x", (uint32_t)addr); 1221c77c410SMichael Clark return 0; 1231c77c410SMichael Clark } 1241c77c410SMichael Clark } else if (addr >= clint->timecmp_base && 1251c77c410SMichael Clark addr < clint->timecmp_base + (clint->num_harts << 3)) { 1263bf03f08SAnup Patel size_t hartid = clint->hartid_base + 1273bf03f08SAnup Patel ((addr - clint->timecmp_base) >> 3); 1281c77c410SMichael Clark CPUState *cpu = qemu_get_cpu(hartid); 1291c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 1301c77c410SMichael Clark if (!env) { 1311c77c410SMichael Clark error_report("clint: invalid timecmp hartid: %zu", hartid); 1321c77c410SMichael Clark } else if ((addr & 0x7) == 0) { 1331c77c410SMichael Clark /* timecmp_lo */ 1341c77c410SMichael Clark uint64_t timecmp = env->timecmp; 1351c77c410SMichael Clark return timecmp & 0xFFFFFFFF; 1361c77c410SMichael Clark } else if ((addr & 0x7) == 4) { 1371c77c410SMichael Clark /* timecmp_hi */ 1381c77c410SMichael Clark uint64_t timecmp = env->timecmp; 1391c77c410SMichael Clark return (timecmp >> 32) & 0xFFFFFFFF; 1401c77c410SMichael Clark } else { 1411c77c410SMichael Clark error_report("clint: invalid read: %08x", (uint32_t)addr); 1421c77c410SMichael Clark return 0; 1431c77c410SMichael Clark } 1441c77c410SMichael Clark } else if (addr == clint->time_base) { 1451c77c410SMichael Clark /* time_lo */ 146a47ef6e9SBin Meng return cpu_riscv_read_rtc(clint->timebase_freq) & 0xFFFFFFFF; 1471c77c410SMichael Clark } else if (addr == clint->time_base + 4) { 1481c77c410SMichael Clark /* time_hi */ 149a47ef6e9SBin Meng return (cpu_riscv_read_rtc(clint->timebase_freq) >> 32) & 0xFFFFFFFF; 1501c77c410SMichael Clark } 1511c77c410SMichael Clark 1521c77c410SMichael Clark error_report("clint: invalid read: %08x", (uint32_t)addr); 1531c77c410SMichael Clark return 0; 1541c77c410SMichael Clark } 1551c77c410SMichael Clark 1561c77c410SMichael Clark /* CPU wrote to rtc or timecmp register */ 1571c77c410SMichael Clark static void sifive_clint_write(void *opaque, hwaddr addr, uint64_t value, 1581c77c410SMichael Clark unsigned size) 1591c77c410SMichael Clark { 1601c77c410SMichael Clark SiFiveCLINTState *clint = opaque; 1611c77c410SMichael Clark 1621c77c410SMichael Clark if (addr >= clint->sip_base && 1631c77c410SMichael Clark addr < clint->sip_base + (clint->num_harts << 2)) { 1643bf03f08SAnup Patel size_t hartid = clint->hartid_base + ((addr - clint->sip_base) >> 2); 1651c77c410SMichael Clark CPUState *cpu = qemu_get_cpu(hartid); 1661c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 1671c77c410SMichael Clark if (!env) { 1681c77c410SMichael Clark error_report("clint: invalid timecmp hartid: %zu", hartid); 1691c77c410SMichael Clark } else if ((addr & 0x3) == 0) { 170a714b8aaSAlistair Francis qemu_set_irq(clint->soft_irqs[hartid - clint->hartid_base], value); 1711c77c410SMichael Clark } else { 1721c77c410SMichael Clark error_report("clint: invalid sip write: %08x", (uint32_t)addr); 1731c77c410SMichael Clark } 1741c77c410SMichael Clark return; 1751c77c410SMichael Clark } else if (addr >= clint->timecmp_base && 1761c77c410SMichael Clark addr < clint->timecmp_base + (clint->num_harts << 3)) { 1773bf03f08SAnup Patel size_t hartid = clint->hartid_base + 1783bf03f08SAnup Patel ((addr - clint->timecmp_base) >> 3); 1791c77c410SMichael Clark CPUState *cpu = qemu_get_cpu(hartid); 1801c77c410SMichael Clark CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 1811c77c410SMichael Clark if (!env) { 1821c77c410SMichael Clark error_report("clint: invalid timecmp hartid: %zu", hartid); 1831c77c410SMichael Clark } else if ((addr & 0x7) == 0) { 1841c77c410SMichael Clark /* timecmp_lo */ 185ef9e41dfSMichael Clark uint64_t timecmp_hi = env->timecmp >> 32; 186a714b8aaSAlistair Francis sifive_clint_write_timecmp(clint, RISCV_CPU(cpu), hartid, 187a47ef6e9SBin Meng timecmp_hi << 32 | (value & 0xFFFFFFFF), clint->timebase_freq); 1881c77c410SMichael Clark return; 1891c77c410SMichael Clark } else if ((addr & 0x7) == 4) { 1901c77c410SMichael Clark /* timecmp_hi */ 191ef9e41dfSMichael Clark uint64_t timecmp_lo = env->timecmp; 192a714b8aaSAlistair Francis sifive_clint_write_timecmp(clint, RISCV_CPU(cpu), hartid, 193a47ef6e9SBin Meng value << 32 | (timecmp_lo & 0xFFFFFFFF), clint->timebase_freq); 1941c77c410SMichael Clark } else { 1951c77c410SMichael Clark error_report("clint: invalid timecmp write: %08x", (uint32_t)addr); 1961c77c410SMichael Clark } 1971c77c410SMichael Clark return; 1981c77c410SMichael Clark } else if (addr == clint->time_base) { 1991c77c410SMichael Clark /* time_lo */ 2001c77c410SMichael Clark error_report("clint: time_lo write not implemented"); 2011c77c410SMichael Clark return; 2021c77c410SMichael Clark } else if (addr == clint->time_base + 4) { 2031c77c410SMichael Clark /* time_hi */ 2041c77c410SMichael Clark error_report("clint: time_hi write not implemented"); 2051c77c410SMichael Clark return; 2061c77c410SMichael Clark } 2071c77c410SMichael Clark 2081c77c410SMichael Clark error_report("clint: invalid write: %08x", (uint32_t)addr); 2091c77c410SMichael Clark } 2101c77c410SMichael Clark 2111c77c410SMichael Clark static const MemoryRegionOps sifive_clint_ops = { 2121c77c410SMichael Clark .read = sifive_clint_read, 2131c77c410SMichael Clark .write = sifive_clint_write, 2141c77c410SMichael Clark .endianness = DEVICE_LITTLE_ENDIAN, 2151c77c410SMichael Clark .valid = { 2161c77c410SMichael Clark .min_access_size = 4, 21770b78d4eSAlistair Francis .max_access_size = 8 2181c77c410SMichael Clark } 2191c77c410SMichael Clark }; 2201c77c410SMichael Clark 2211c77c410SMichael Clark static Property sifive_clint_properties[] = { 2223bf03f08SAnup Patel DEFINE_PROP_UINT32("hartid-base", SiFiveCLINTState, hartid_base, 0), 2231c77c410SMichael Clark DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState, num_harts, 0), 2241c77c410SMichael Clark DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState, sip_base, 0), 2251c77c410SMichael Clark DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState, timecmp_base, 0), 2261c77c410SMichael Clark DEFINE_PROP_UINT32("time-base", SiFiveCLINTState, time_base, 0), 2271c77c410SMichael Clark DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState, aperture_size, 0), 228a47ef6e9SBin Meng DEFINE_PROP_UINT32("timebase-freq", SiFiveCLINTState, timebase_freq, 0), 2291c77c410SMichael Clark DEFINE_PROP_END_OF_LIST(), 2301c77c410SMichael Clark }; 2311c77c410SMichael Clark 2321c77c410SMichael Clark static void sifive_clint_realize(DeviceState *dev, Error **errp) 2331c77c410SMichael Clark { 2341c77c410SMichael Clark SiFiveCLINTState *s = SIFIVE_CLINT(dev); 2351c77c410SMichael Clark memory_region_init_io(&s->mmio, OBJECT(dev), &sifive_clint_ops, s, 2361c77c410SMichael Clark TYPE_SIFIVE_CLINT, s->aperture_size); 2371c77c410SMichael Clark sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); 238a714b8aaSAlistair Francis 239a714b8aaSAlistair Francis s->timer_irqs = g_malloc(sizeof(qemu_irq) * s->num_harts); 240a714b8aaSAlistair Francis qdev_init_gpio_out(dev, s->timer_irqs, s->num_harts); 241a714b8aaSAlistair Francis 242a714b8aaSAlistair Francis s->soft_irqs = g_malloc(sizeof(qemu_irq) * s->num_harts); 243a714b8aaSAlistair Francis qdev_init_gpio_out(dev, s->soft_irqs, s->num_harts); 2441c77c410SMichael Clark } 2451c77c410SMichael Clark 2461c77c410SMichael Clark static void sifive_clint_class_init(ObjectClass *klass, void *data) 2471c77c410SMichael Clark { 2481c77c410SMichael Clark DeviceClass *dc = DEVICE_CLASS(klass); 2491c77c410SMichael Clark dc->realize = sifive_clint_realize; 2504f67d30bSMarc-André Lureau device_class_set_props(dc, sifive_clint_properties); 2511c77c410SMichael Clark } 2521c77c410SMichael Clark 2531c77c410SMichael Clark static const TypeInfo sifive_clint_info = { 2541c77c410SMichael Clark .name = TYPE_SIFIVE_CLINT, 2551c77c410SMichael Clark .parent = TYPE_SYS_BUS_DEVICE, 2561c77c410SMichael Clark .instance_size = sizeof(SiFiveCLINTState), 2571c77c410SMichael Clark .class_init = sifive_clint_class_init, 2581c77c410SMichael Clark }; 2591c77c410SMichael Clark 2601c77c410SMichael Clark static void sifive_clint_register_types(void) 2611c77c410SMichael Clark { 2621c77c410SMichael Clark type_register_static(&sifive_clint_info); 2631c77c410SMichael Clark } 2641c77c410SMichael Clark 2651c77c410SMichael Clark type_init(sifive_clint_register_types) 2661c77c410SMichael Clark 2671c77c410SMichael Clark /* 2681c77c410SMichael Clark * Create CLINT device. 2691c77c410SMichael Clark */ 2703bf03f08SAnup Patel DeviceState *sifive_clint_create(hwaddr addr, hwaddr size, 2713bf03f08SAnup Patel uint32_t hartid_base, uint32_t num_harts, uint32_t sip_base, 272a47ef6e9SBin Meng uint32_t timecmp_base, uint32_t time_base, uint32_t timebase_freq, 273a47ef6e9SBin Meng bool provide_rdtime) 2741c77c410SMichael Clark { 2751c77c410SMichael Clark int i; 2761c77c410SMichael Clark 2773e80f690SMarkus Armbruster DeviceState *dev = qdev_new(TYPE_SIFIVE_CLINT); 2783bf03f08SAnup Patel qdev_prop_set_uint32(dev, "hartid-base", hartid_base); 2791c77c410SMichael Clark qdev_prop_set_uint32(dev, "num-harts", num_harts); 2801c77c410SMichael Clark qdev_prop_set_uint32(dev, "sip-base", sip_base); 2811c77c410SMichael Clark qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base); 2821c77c410SMichael Clark qdev_prop_set_uint32(dev, "time-base", time_base); 2831c77c410SMichael Clark qdev_prop_set_uint32(dev, "aperture-size", size); 284a47ef6e9SBin Meng qdev_prop_set_uint32(dev, "timebase-freq", timebase_freq); 2853c6ef471SMarkus Armbruster sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); 2861c77c410SMichael Clark sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); 287a714b8aaSAlistair Francis 288a714b8aaSAlistair Francis for (i = 0; i < num_harts; i++) { 289a714b8aaSAlistair Francis CPUState *cpu = qemu_get_cpu(hartid_base + i); 290a714b8aaSAlistair Francis RISCVCPU *rvcpu = RISCV_CPU(cpu); 291a714b8aaSAlistair Francis CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 292a714b8aaSAlistair Francis sifive_clint_callback *cb = g_malloc0(sizeof(sifive_clint_callback)); 293a714b8aaSAlistair Francis 294a714b8aaSAlistair Francis if (!env) { 295a714b8aaSAlistair Francis g_free(cb); 296a714b8aaSAlistair Francis continue; 297a714b8aaSAlistair Francis } 298a714b8aaSAlistair Francis if (provide_rdtime) { 299a714b8aaSAlistair Francis riscv_cpu_set_rdtime_fn(env, cpu_riscv_read_rtc, timebase_freq); 300a714b8aaSAlistair Francis } 301a714b8aaSAlistair Francis 302a714b8aaSAlistair Francis cb->s = SIFIVE_CLINT(dev); 303a714b8aaSAlistair Francis cb->num = i; 304a714b8aaSAlistair Francis env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, 305a714b8aaSAlistair Francis &sifive_clint_timer_cb, cb); 306a714b8aaSAlistair Francis env->timecmp = 0; 307a714b8aaSAlistair Francis 308a714b8aaSAlistair Francis qdev_connect_gpio_out(dev, i, 309a714b8aaSAlistair Francis qdev_get_gpio_in(DEVICE(rvcpu), IRQ_M_TIMER)); 310a714b8aaSAlistair Francis qdev_connect_gpio_out(dev, num_harts + i, 311a714b8aaSAlistair Francis qdev_get_gpio_in(DEVICE(rvcpu), IRQ_M_SOFT)); 312a714b8aaSAlistair Francis } 313a714b8aaSAlistair Francis 3141c77c410SMichael Clark return dev; 3151c77c410SMichael Clark } 316