xref: /qemu/hw/intc/riscv_aclint.c (revision cc63a18282d8e8cd96d8bf26c29cad2e879ff9f6)
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