1 /* 2 * SiFive CLINT (Core Local Interruptor) 3 * 4 * Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu 5 * Copyright (c) 2017 SiFive, Inc. 6 * 7 * This provides real-time clock, timer and interprocessor interrupts. 8 * 9 * This program is free software; you can redistribute it and/or modify it 10 * under the terms and conditions of the GNU General Public License, 11 * version 2 or later, as published by the Free Software Foundation. 12 * 13 * This program is distributed in the hope it will be useful, but WITHOUT 14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 16 * more details. 17 * 18 * You should have received a copy of the GNU General Public License along with 19 * this program. If not, see <http://www.gnu.org/licenses/>. 20 */ 21 22 #include "qemu/osdep.h" 23 #include "qapi/error.h" 24 #include "qemu/error-report.h" 25 #include "qemu/module.h" 26 #include "hw/sysbus.h" 27 #include "target/riscv/cpu.h" 28 #include "hw/qdev-properties.h" 29 #include "hw/intc/riscv_aclint.h" 30 #include "qemu/timer.h" 31 #include "hw/irq.h" 32 33 typedef struct sifive_clint_callback { 34 SiFiveCLINTState *s; 35 int num; 36 } sifive_clint_callback; 37 38 static uint64_t cpu_riscv_read_rtc(uint32_t timebase_freq) 39 { 40 return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 41 timebase_freq, NANOSECONDS_PER_SECOND); 42 } 43 44 /* 45 * Called when timecmp is written to update the QEMU timer or immediately 46 * trigger timer interrupt if mtimecmp <= current timer value. 47 */ 48 static void sifive_clint_write_timecmp(SiFiveCLINTState *s, RISCVCPU *cpu, 49 int hartid, 50 uint64_t value, 51 uint32_t timebase_freq) 52 { 53 uint64_t next; 54 uint64_t diff; 55 56 uint64_t rtc_r = cpu_riscv_read_rtc(timebase_freq); 57 58 cpu->env.timecmp = value; 59 if (cpu->env.timecmp <= rtc_r) { 60 /* if we're setting an MTIMECMP value in the "past", 61 immediately raise the timer interrupt */ 62 qemu_irq_raise(s->timer_irqs[hartid - s->hartid_base]); 63 return; 64 } 65 66 /* otherwise, set up the future timer interrupt */ 67 qemu_irq_lower(s->timer_irqs[hartid - s->hartid_base]); 68 diff = cpu->env.timecmp - rtc_r; 69 /* back to ns (note args switched in muldiv64) */ 70 uint64_t ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq); 71 72 /* 73 * check if ns_diff overflowed and check if the addition would potentially 74 * overflow 75 */ 76 if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) || 77 ns_diff > INT64_MAX) { 78 next = INT64_MAX; 79 } else { 80 /* 81 * as it is very unlikely qemu_clock_get_ns will return a value 82 * greater than INT64_MAX, no additional check is needed for an 83 * unsigned integer overflow. 84 */ 85 next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff; 86 /* 87 * if ns_diff is INT64_MAX next may still be outside the range 88 * of a signed integer. 89 */ 90 next = MIN(next, INT64_MAX); 91 } 92 93 timer_mod(cpu->env.timer, next); 94 } 95 96 /* 97 * Callback used when the timer set using timer_mod expires. 98 * Should raise the timer interrupt line 99 */ 100 static void sifive_clint_timer_cb(void *opaque) 101 { 102 sifive_clint_callback *state = opaque; 103 104 qemu_irq_raise(state->s->timer_irqs[state->num]); 105 } 106 107 /* CPU wants to read rtc or timecmp register */ 108 static uint64_t sifive_clint_read(void *opaque, hwaddr addr, unsigned size) 109 { 110 SiFiveCLINTState *clint = opaque; 111 if (addr >= clint->sip_base && 112 addr < clint->sip_base + (clint->num_harts << 2)) { 113 size_t hartid = clint->hartid_base + ((addr - clint->sip_base) >> 2); 114 CPUState *cpu = qemu_get_cpu(hartid); 115 CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 116 if (!env) { 117 error_report("clint: invalid timecmp hartid: %zu", hartid); 118 } else if ((addr & 0x3) == 0) { 119 return (env->mip & MIP_MSIP) > 0; 120 } else { 121 error_report("clint: invalid read: %08x", (uint32_t)addr); 122 return 0; 123 } 124 } else if (addr >= clint->timecmp_base && 125 addr < clint->timecmp_base + (clint->num_harts << 3)) { 126 size_t hartid = clint->hartid_base + 127 ((addr - clint->timecmp_base) >> 3); 128 CPUState *cpu = qemu_get_cpu(hartid); 129 CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 130 if (!env) { 131 error_report("clint: invalid timecmp hartid: %zu", hartid); 132 } else if ((addr & 0x7) == 0) { 133 /* timecmp_lo */ 134 uint64_t timecmp = env->timecmp; 135 return timecmp & 0xFFFFFFFF; 136 } else if ((addr & 0x7) == 4) { 137 /* timecmp_hi */ 138 uint64_t timecmp = env->timecmp; 139 return (timecmp >> 32) & 0xFFFFFFFF; 140 } else { 141 error_report("clint: invalid read: %08x", (uint32_t)addr); 142 return 0; 143 } 144 } else if (addr == clint->time_base) { 145 /* time_lo */ 146 return cpu_riscv_read_rtc(clint->timebase_freq) & 0xFFFFFFFF; 147 } else if (addr == clint->time_base + 4) { 148 /* time_hi */ 149 return (cpu_riscv_read_rtc(clint->timebase_freq) >> 32) & 0xFFFFFFFF; 150 } 151 152 error_report("clint: invalid read: %08x", (uint32_t)addr); 153 return 0; 154 } 155 156 /* CPU wrote to rtc or timecmp register */ 157 static void sifive_clint_write(void *opaque, hwaddr addr, uint64_t value, 158 unsigned size) 159 { 160 SiFiveCLINTState *clint = opaque; 161 162 if (addr >= clint->sip_base && 163 addr < clint->sip_base + (clint->num_harts << 2)) { 164 size_t hartid = clint->hartid_base + ((addr - clint->sip_base) >> 2); 165 CPUState *cpu = qemu_get_cpu(hartid); 166 CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 167 if (!env) { 168 error_report("clint: invalid timecmp hartid: %zu", hartid); 169 } else if ((addr & 0x3) == 0) { 170 qemu_set_irq(clint->soft_irqs[hartid - clint->hartid_base], value); 171 } else { 172 error_report("clint: invalid sip write: %08x", (uint32_t)addr); 173 } 174 return; 175 } else if (addr >= clint->timecmp_base && 176 addr < clint->timecmp_base + (clint->num_harts << 3)) { 177 size_t hartid = clint->hartid_base + 178 ((addr - clint->timecmp_base) >> 3); 179 CPUState *cpu = qemu_get_cpu(hartid); 180 CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 181 if (!env) { 182 error_report("clint: invalid timecmp hartid: %zu", hartid); 183 } else if ((addr & 0x7) == 0) { 184 /* timecmp_lo */ 185 uint64_t timecmp_hi = env->timecmp >> 32; 186 sifive_clint_write_timecmp(clint, RISCV_CPU(cpu), hartid, 187 timecmp_hi << 32 | (value & 0xFFFFFFFF), clint->timebase_freq); 188 return; 189 } else if ((addr & 0x7) == 4) { 190 /* timecmp_hi */ 191 uint64_t timecmp_lo = env->timecmp; 192 sifive_clint_write_timecmp(clint, RISCV_CPU(cpu), hartid, 193 value << 32 | (timecmp_lo & 0xFFFFFFFF), clint->timebase_freq); 194 } else { 195 error_report("clint: invalid timecmp write: %08x", (uint32_t)addr); 196 } 197 return; 198 } else if (addr == clint->time_base) { 199 /* time_lo */ 200 error_report("clint: time_lo write not implemented"); 201 return; 202 } else if (addr == clint->time_base + 4) { 203 /* time_hi */ 204 error_report("clint: time_hi write not implemented"); 205 return; 206 } 207 208 error_report("clint: invalid write: %08x", (uint32_t)addr); 209 } 210 211 static const MemoryRegionOps sifive_clint_ops = { 212 .read = sifive_clint_read, 213 .write = sifive_clint_write, 214 .endianness = DEVICE_LITTLE_ENDIAN, 215 .valid = { 216 .min_access_size = 4, 217 .max_access_size = 8 218 } 219 }; 220 221 static Property sifive_clint_properties[] = { 222 DEFINE_PROP_UINT32("hartid-base", SiFiveCLINTState, hartid_base, 0), 223 DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState, num_harts, 0), 224 DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState, sip_base, 0), 225 DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState, timecmp_base, 0), 226 DEFINE_PROP_UINT32("time-base", SiFiveCLINTState, time_base, 0), 227 DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState, aperture_size, 0), 228 DEFINE_PROP_UINT32("timebase-freq", SiFiveCLINTState, timebase_freq, 0), 229 DEFINE_PROP_END_OF_LIST(), 230 }; 231 232 static void sifive_clint_realize(DeviceState *dev, Error **errp) 233 { 234 SiFiveCLINTState *s = SIFIVE_CLINT(dev); 235 memory_region_init_io(&s->mmio, OBJECT(dev), &sifive_clint_ops, s, 236 TYPE_SIFIVE_CLINT, s->aperture_size); 237 sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio); 238 239 s->timer_irqs = g_malloc(sizeof(qemu_irq) * s->num_harts); 240 qdev_init_gpio_out(dev, s->timer_irqs, s->num_harts); 241 242 s->soft_irqs = g_malloc(sizeof(qemu_irq) * s->num_harts); 243 qdev_init_gpio_out(dev, s->soft_irqs, s->num_harts); 244 } 245 246 static void sifive_clint_class_init(ObjectClass *klass, void *data) 247 { 248 DeviceClass *dc = DEVICE_CLASS(klass); 249 dc->realize = sifive_clint_realize; 250 device_class_set_props(dc, sifive_clint_properties); 251 } 252 253 static const TypeInfo sifive_clint_info = { 254 .name = TYPE_SIFIVE_CLINT, 255 .parent = TYPE_SYS_BUS_DEVICE, 256 .instance_size = sizeof(SiFiveCLINTState), 257 .class_init = sifive_clint_class_init, 258 }; 259 260 static void sifive_clint_register_types(void) 261 { 262 type_register_static(&sifive_clint_info); 263 } 264 265 type_init(sifive_clint_register_types) 266 267 /* 268 * Create CLINT device. 269 */ 270 DeviceState *sifive_clint_create(hwaddr addr, hwaddr size, 271 uint32_t hartid_base, uint32_t num_harts, uint32_t sip_base, 272 uint32_t timecmp_base, uint32_t time_base, uint32_t timebase_freq, 273 bool provide_rdtime) 274 { 275 int i; 276 277 DeviceState *dev = qdev_new(TYPE_SIFIVE_CLINT); 278 qdev_prop_set_uint32(dev, "hartid-base", hartid_base); 279 qdev_prop_set_uint32(dev, "num-harts", num_harts); 280 qdev_prop_set_uint32(dev, "sip-base", sip_base); 281 qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base); 282 qdev_prop_set_uint32(dev, "time-base", time_base); 283 qdev_prop_set_uint32(dev, "aperture-size", size); 284 qdev_prop_set_uint32(dev, "timebase-freq", timebase_freq); 285 sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); 286 sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); 287 288 for (i = 0; i < num_harts; i++) { 289 CPUState *cpu = qemu_get_cpu(hartid_base + i); 290 RISCVCPU *rvcpu = RISCV_CPU(cpu); 291 CPURISCVState *env = cpu ? cpu->env_ptr : NULL; 292 sifive_clint_callback *cb = g_malloc0(sizeof(sifive_clint_callback)); 293 294 if (!env) { 295 g_free(cb); 296 continue; 297 } 298 if (provide_rdtime) { 299 riscv_cpu_set_rdtime_fn(env, cpu_riscv_read_rtc, timebase_freq); 300 } 301 302 cb->s = SIFIVE_CLINT(dev); 303 cb->num = i; 304 env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, 305 &sifive_clint_timer_cb, cb); 306 env->timecmp = 0; 307 308 qdev_connect_gpio_out(dev, i, 309 qdev_get_gpio_in(DEVICE(rvcpu), IRQ_M_TIMER)); 310 qdev_connect_gpio_out(dev, num_harts + i, 311 qdev_get_gpio_in(DEVICE(rvcpu), IRQ_M_SOFT)); 312 } 313 314 return dev; 315 } 316