| 1 | /* |
| 2 | * Rasperry Pi 2 emulation ARM control logic module. |
| 3 | * Copyright (c) 2015, Microsoft |
| 4 | * Written by Andrew Baumann |
| 5 | * |
| 6 | * Based on bcm2835_ic.c (Raspberry Pi emulation) (c) 2012 Gregory Estrade |
| 7 | * |
| 8 | * At present, only implements interrupt routing, and mailboxes (i.e., |
| 9 | * not PMU interrupt, or AXI counters). |
| 10 | * |
| 11 | * ARM Local Timer IRQ Copyright (c) 2019. Zoltán Baldaszti |
| 12 | * |
| 13 | * Ref: |
| 14 | * https://www.raspberrypi.org/documentation/hardware/raspberrypi/bcm2836/QA7_rev3.4.pdf |
| 15 | * |
| 16 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| 17 | * See the COPYING file in the top-level directory. |
| 18 | */ |
| 19 | |
| 20 | #include "qemu/osdep.h" |
| 21 | #include "hw/intc/bcm2836_control.h" |
| 22 | #include "hw/core/irq.h" |
| 23 | #include "migration/vmstate.h" |
| 24 | #include "qemu/log.h" |
| 25 | #include "qemu/module.h" |
| 26 | |
| 27 | #define REG_GPU_ROUTE 0x0c |
| 28 | #define REG_LOCALTIMERROUTING 0x24 |
| 29 | #define REG_LOCALTIMERCONTROL 0x34 |
| 30 | #define REG_LOCALTIMERACK 0x38 |
| 31 | #define REG_TIMERCONTROL 0x40 |
| 32 | #define REG_MBOXCONTROL 0x50 |
| 33 | #define REG_IRQSRC 0x60 |
| 34 | #define REG_FIQSRC 0x70 |
| 35 | #define REG_MBOX0_WR 0x80 |
| 36 | #define REG_MBOX0_RDCLR 0xc0 |
| 37 | #define REG_LIMIT 0x100 |
| 38 | |
| 39 | #define IRQ_BIT(cntrl, num) (((cntrl) & (1 << (num))) != 0) |
| 40 | #define FIQ_BIT(cntrl, num) (((cntrl) & (1 << ((num) + 4))) != 0) |
| 41 | |
| 42 | #define IRQ_CNTPSIRQ 0 |
| 43 | #define IRQ_CNTPNSIRQ 1 |
| 44 | #define IRQ_CNTHPIRQ 2 |
| 45 | #define IRQ_CNTVIRQ 3 |
| 46 | #define IRQ_MAILBOX0 4 |
| 47 | #define IRQ_MAILBOX1 5 |
| 48 | #define IRQ_MAILBOX2 6 |
| 49 | #define IRQ_MAILBOX3 7 |
| 50 | #define IRQ_GPU 8 |
| 51 | #define IRQ_PMU 9 |
| 52 | #define IRQ_AXI 10 |
| 53 | #define IRQ_TIMER 11 |
| 54 | #define IRQ_MAX IRQ_TIMER |
| 55 | |
| 56 | #define LOCALTIMER_FREQ 38400000 |
| 57 | #define LOCALTIMER_INTFLAG (1 << 31) |
| 58 | #define LOCALTIMER_RELOAD (1 << 30) |
| 59 | #define LOCALTIMER_INTENABLE (1 << 29) |
| 60 | #define LOCALTIMER_ENABLE (1 << 28) |
| 61 | #define LOCALTIMER_VALUE(x) ((x) & 0xfffffff) |
| 62 | |
| 63 | static void deliver_local(BCM2836ControlState *s, uint8_t core, uint8_t irq, |
| 64 | uint32_t controlreg, uint8_t controlidx) |
| 65 | { |
| 66 | if (FIQ_BIT(controlreg, controlidx)) { |
| 67 | /* deliver a FIQ */ |
| 68 | s->fiqsrc[core] |= (uint32_t)1 << irq; |
| 69 | } else if (IRQ_BIT(controlreg, controlidx)) { |
| 70 | /* deliver an IRQ */ |
| 71 | s->irqsrc[core] |= (uint32_t)1 << irq; |
| 72 | } else { |
| 73 | /* the interrupt is masked */ |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | /* Update interrupts. */ |
| 78 | static void bcm2836_control_update(BCM2836ControlState *s) |
| 79 | { |
| 80 | int i, j; |
| 81 | |
| 82 | /* reset pending IRQs/FIQs */ |
| 83 | for (i = 0; i < BCM2836_NCORES; i++) { |
| 84 | s->irqsrc[i] = s->fiqsrc[i] = 0; |
| 85 | } |
| 86 | |
| 87 | /* apply routing logic, update status regs */ |
| 88 | if (s->gpu_irq) { |
| 89 | assert(s->route_gpu_irq < BCM2836_NCORES); |
| 90 | s->irqsrc[s->route_gpu_irq] |= (uint32_t)1 << IRQ_GPU; |
| 91 | } |
| 92 | |
| 93 | if (s->gpu_fiq) { |
| 94 | assert(s->route_gpu_fiq < BCM2836_NCORES); |
| 95 | s->fiqsrc[s->route_gpu_fiq] |= (uint32_t)1 << IRQ_GPU; |
| 96 | } |
| 97 | |
| 98 | /* |
| 99 | * handle the control module 'local timer' interrupt for one of the |
| 100 | * cores' IRQ/FIQ; this is distinct from the per-CPU timer |
| 101 | * interrupts handled below. |
| 102 | */ |
| 103 | if ((s->local_timer_control & LOCALTIMER_INTENABLE) && |
| 104 | (s->local_timer_control & LOCALTIMER_INTFLAG)) { |
| 105 | if (s->route_localtimer & 4) { |
| 106 | s->fiqsrc[(s->route_localtimer & 3)] |= (uint32_t)1 << IRQ_TIMER; |
| 107 | } else { |
| 108 | s->irqsrc[(s->route_localtimer & 3)] |= (uint32_t)1 << IRQ_TIMER; |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | for (i = 0; i < BCM2836_NCORES; i++) { |
| 113 | /* handle local timer interrupts for this core */ |
| 114 | if (s->timerirqs[i]) { |
| 115 | assert(s->timerirqs[i] < (1 << (IRQ_CNTVIRQ + 1))); /* sane mask? */ |
| 116 | for (j = 0; j <= IRQ_CNTVIRQ; j++) { |
| 117 | if ((s->timerirqs[i] & (1 << j)) != 0) { |
| 118 | /* local interrupt j is set */ |
| 119 | deliver_local(s, i, j, s->timercontrol[i], j); |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | /* handle mailboxes for this core */ |
| 125 | for (j = 0; j < BCM2836_MBPERCORE; j++) { |
| 126 | if (s->mailboxes[i * BCM2836_MBPERCORE + j] != 0) { |
| 127 | /* mailbox j is set */ |
| 128 | deliver_local(s, i, j + IRQ_MAILBOX0, s->mailboxcontrol[i], j); |
| 129 | } |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /* call set_irq appropriately for each output */ |
| 134 | for (i = 0; i < BCM2836_NCORES; i++) { |
| 135 | qemu_set_irq(s->irq[i], s->irqsrc[i] != 0); |
| 136 | qemu_set_irq(s->fiq[i], s->fiqsrc[i] != 0); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | static void bcm2836_control_set_local_irq(void *opaque, int core, int local_irq, |
| 141 | int level) |
| 142 | { |
| 143 | BCM2836ControlState *s = opaque; |
| 144 | |
| 145 | assert(core >= 0 && core < BCM2836_NCORES); |
| 146 | assert(local_irq >= 0 && local_irq <= IRQ_CNTVIRQ); |
| 147 | |
| 148 | s->timerirqs[core] = deposit32(s->timerirqs[core], local_irq, 1, !!level); |
| 149 | |
| 150 | bcm2836_control_update(s); |
| 151 | } |
| 152 | |
| 153 | /* XXX: the following wrapper functions are a kludgy workaround, |
| 154 | * needed because I can't seem to pass useful information in the "irq" |
| 155 | * parameter when using named interrupts. Feel free to clean this up! |
| 156 | */ |
| 157 | |
| 158 | static void bcm2836_control_set_local_irq0(void *opaque, int core, int level) |
| 159 | { |
| 160 | bcm2836_control_set_local_irq(opaque, core, IRQ_CNTPSIRQ, level); |
| 161 | } |
| 162 | |
| 163 | static void bcm2836_control_set_local_irq1(void *opaque, int core, int level) |
| 164 | { |
| 165 | bcm2836_control_set_local_irq(opaque, core, IRQ_CNTPNSIRQ, level); |
| 166 | } |
| 167 | |
| 168 | static void bcm2836_control_set_local_irq2(void *opaque, int core, int level) |
| 169 | { |
| 170 | bcm2836_control_set_local_irq(opaque, core, IRQ_CNTHPIRQ, level); |
| 171 | } |
| 172 | |
| 173 | static void bcm2836_control_set_local_irq3(void *opaque, int core, int level) |
| 174 | { |
| 175 | bcm2836_control_set_local_irq(opaque, core, IRQ_CNTVIRQ, level); |
| 176 | } |
| 177 | |
| 178 | static void bcm2836_control_set_gpu_irq(void *opaque, int irq, int level) |
| 179 | { |
| 180 | BCM2836ControlState *s = opaque; |
| 181 | |
| 182 | s->gpu_irq = level; |
| 183 | |
| 184 | bcm2836_control_update(s); |
| 185 | } |
| 186 | |
| 187 | static void bcm2836_control_set_gpu_fiq(void *opaque, int irq, int level) |
| 188 | { |
| 189 | BCM2836ControlState *s = opaque; |
| 190 | |
| 191 | s->gpu_fiq = level; |
| 192 | |
| 193 | bcm2836_control_update(s); |
| 194 | } |
| 195 | |
| 196 | static void bcm2836_control_local_timer_set_next(void *opaque) |
| 197 | { |
| 198 | BCM2836ControlState *s = opaque; |
| 199 | uint64_t next_event; |
| 200 | uint64_t reload_value = LOCALTIMER_VALUE(s->local_timer_control); |
| 201 | |
| 202 | if (reload_value == 0) { |
| 203 | /* |
| 204 | * Spec doesn't say what happens in this case; treat as a |
| 205 | * guest error and stop the timer running. |
| 206 | */ |
| 207 | qemu_log_mask(LOG_GUEST_ERROR, "%s: local timer reload value is 0\n", |
| 208 | __func__); |
| 209 | timer_del(&s->timer); |
| 210 | return; |
| 211 | } |
| 212 | |
| 213 | next_event = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + |
| 214 | muldiv64(reload_value, NANOSECONDS_PER_SECOND, LOCALTIMER_FREQ); |
| 215 | timer_mod(&s->timer, next_event); |
| 216 | } |
| 217 | |
| 218 | static void bcm2836_control_local_timer_tick(void *opaque) |
| 219 | { |
| 220 | BCM2836ControlState *s = opaque; |
| 221 | |
| 222 | bcm2836_control_local_timer_set_next(s); |
| 223 | |
| 224 | s->local_timer_control |= LOCALTIMER_INTFLAG; |
| 225 | bcm2836_control_update(s); |
| 226 | } |
| 227 | |
| 228 | static void bcm2836_control_local_timer_control(void *opaque, uint32_t val) |
| 229 | { |
| 230 | BCM2836ControlState *s = opaque; |
| 231 | |
| 232 | s->local_timer_control = val; |
| 233 | if (val & LOCALTIMER_ENABLE) { |
| 234 | bcm2836_control_local_timer_set_next(s); |
| 235 | } else { |
| 236 | timer_del(&s->timer); |
| 237 | } |
| 238 | } |
| 239 | |
| 240 | static void bcm2836_control_local_timer_ack(void *opaque, uint32_t val) |
| 241 | { |
| 242 | BCM2836ControlState *s = opaque; |
| 243 | |
| 244 | if (val & LOCALTIMER_INTFLAG) { |
| 245 | s->local_timer_control &= ~LOCALTIMER_INTFLAG; |
| 246 | } |
| 247 | if ((val & LOCALTIMER_RELOAD) && |
| 248 | (s->local_timer_control & LOCALTIMER_ENABLE)) { |
| 249 | bcm2836_control_local_timer_set_next(s); |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | static uint64_t bcm2836_control_read(void *opaque, hwaddr offset, unsigned size) |
| 254 | { |
| 255 | BCM2836ControlState *s = opaque; |
| 256 | |
| 257 | if (offset == REG_GPU_ROUTE) { |
| 258 | assert(s->route_gpu_fiq < BCM2836_NCORES |
| 259 | && s->route_gpu_irq < BCM2836_NCORES); |
| 260 | return ((uint32_t)s->route_gpu_fiq << 2) | s->route_gpu_irq; |
| 261 | } else if (offset == REG_LOCALTIMERROUTING) { |
| 262 | return s->route_localtimer; |
| 263 | } else if (offset == REG_LOCALTIMERCONTROL) { |
| 264 | return s->local_timer_control; |
| 265 | } else if (offset == REG_LOCALTIMERACK) { |
| 266 | return 0; |
| 267 | } else if (offset >= REG_TIMERCONTROL && offset < REG_MBOXCONTROL) { |
| 268 | return s->timercontrol[(offset - REG_TIMERCONTROL) >> 2]; |
| 269 | } else if (offset >= REG_MBOXCONTROL && offset < REG_IRQSRC) { |
| 270 | return s->mailboxcontrol[(offset - REG_MBOXCONTROL) >> 2]; |
| 271 | } else if (offset >= REG_IRQSRC && offset < REG_FIQSRC) { |
| 272 | return s->irqsrc[(offset - REG_IRQSRC) >> 2]; |
| 273 | } else if (offset >= REG_FIQSRC && offset < REG_MBOX0_WR) { |
| 274 | return s->fiqsrc[(offset - REG_FIQSRC) >> 2]; |
| 275 | } else if (offset >= REG_MBOX0_RDCLR && offset < REG_LIMIT) { |
| 276 | return s->mailboxes[(offset - REG_MBOX0_RDCLR) >> 2]; |
| 277 | } else { |
| 278 | qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx"\n", |
| 279 | __func__, offset); |
| 280 | return 0; |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | static void bcm2836_control_write(void *opaque, hwaddr offset, |
| 285 | uint64_t val, unsigned size) |
| 286 | { |
| 287 | BCM2836ControlState *s = opaque; |
| 288 | |
| 289 | if (offset == REG_GPU_ROUTE) { |
| 290 | s->route_gpu_irq = val & 0x3; |
| 291 | s->route_gpu_fiq = (val >> 2) & 0x3; |
| 292 | } else if (offset == REG_LOCALTIMERROUTING) { |
| 293 | s->route_localtimer = val & 7; |
| 294 | } else if (offset == REG_LOCALTIMERCONTROL) { |
| 295 | bcm2836_control_local_timer_control(s, val); |
| 296 | } else if (offset == REG_LOCALTIMERACK) { |
| 297 | bcm2836_control_local_timer_ack(s, val); |
| 298 | } else if (offset >= REG_TIMERCONTROL && offset < REG_MBOXCONTROL) { |
| 299 | s->timercontrol[(offset - REG_TIMERCONTROL) >> 2] = val & 0xff; |
| 300 | } else if (offset >= REG_MBOXCONTROL && offset < REG_IRQSRC) { |
| 301 | s->mailboxcontrol[(offset - REG_MBOXCONTROL) >> 2] = val & 0xff; |
| 302 | } else if (offset >= REG_MBOX0_WR && offset < REG_MBOX0_RDCLR) { |
| 303 | s->mailboxes[(offset - REG_MBOX0_WR) >> 2] |= val; |
| 304 | } else if (offset >= REG_MBOX0_RDCLR && offset < REG_LIMIT) { |
| 305 | s->mailboxes[(offset - REG_MBOX0_RDCLR) >> 2] &= ~val; |
| 306 | } else { |
| 307 | qemu_log_mask(LOG_UNIMP, "%s: Unsupported offset 0x%"HWADDR_PRIx |
| 308 | " value 0x%"PRIx64"\n", |
| 309 | __func__, offset, val); |
| 310 | return; |
| 311 | } |
| 312 | |
| 313 | bcm2836_control_update(s); |
| 314 | } |
| 315 | |
| 316 | static const MemoryRegionOps bcm2836_control_ops = { |
| 317 | .read = bcm2836_control_read, |
| 318 | .write = bcm2836_control_write, |
| 319 | .endianness = DEVICE_NATIVE_ENDIAN, |
| 320 | .valid.min_access_size = 4, |
| 321 | .valid.max_access_size = 4, |
| 322 | }; |
| 323 | |
| 324 | static void bcm2836_control_reset(DeviceState *d) |
| 325 | { |
| 326 | BCM2836ControlState *s = BCM2836_CONTROL(d); |
| 327 | int i; |
| 328 | |
| 329 | s->route_gpu_irq = s->route_gpu_fiq = 0; |
| 330 | |
| 331 | timer_del(&s->timer); |
| 332 | s->route_localtimer = 0; |
| 333 | s->local_timer_control = 0; |
| 334 | |
| 335 | for (i = 0; i < BCM2836_NCORES; i++) { |
| 336 | s->timercontrol[i] = 0; |
| 337 | s->mailboxcontrol[i] = 0; |
| 338 | } |
| 339 | |
| 340 | for (i = 0; i < BCM2836_NCORES * BCM2836_MBPERCORE; i++) { |
| 341 | s->mailboxes[i] = 0; |
| 342 | } |
| 343 | } |
| 344 | |
| 345 | static void bcm2836_control_init(Object *obj) |
| 346 | { |
| 347 | BCM2836ControlState *s = BCM2836_CONTROL(obj); |
| 348 | DeviceState *dev = DEVICE(obj); |
| 349 | |
| 350 | memory_region_init_io(&s->iomem, obj, &bcm2836_control_ops, s, |
| 351 | TYPE_BCM2836_CONTROL, REG_LIMIT); |
| 352 | sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); |
| 353 | |
| 354 | /* inputs from each CPU core */ |
| 355 | qdev_init_gpio_in_named(dev, bcm2836_control_set_local_irq0, "cntpsirq", |
| 356 | BCM2836_NCORES); |
| 357 | qdev_init_gpio_in_named(dev, bcm2836_control_set_local_irq1, "cntpnsirq", |
| 358 | BCM2836_NCORES); |
| 359 | qdev_init_gpio_in_named(dev, bcm2836_control_set_local_irq2, "cnthpirq", |
| 360 | BCM2836_NCORES); |
| 361 | qdev_init_gpio_in_named(dev, bcm2836_control_set_local_irq3, "cntvirq", |
| 362 | BCM2836_NCORES); |
| 363 | |
| 364 | /* IRQ and FIQ inputs from upstream bcm2835 controller */ |
| 365 | qdev_init_gpio_in_named(dev, bcm2836_control_set_gpu_irq, "gpu-irq", 1); |
| 366 | qdev_init_gpio_in_named(dev, bcm2836_control_set_gpu_fiq, "gpu-fiq", 1); |
| 367 | |
| 368 | /* outputs to CPU cores */ |
| 369 | qdev_init_gpio_out_named(dev, s->irq, "irq", BCM2836_NCORES); |
| 370 | qdev_init_gpio_out_named(dev, s->fiq, "fiq", BCM2836_NCORES); |
| 371 | |
| 372 | /* create a qemu virtual timer */ |
| 373 | timer_init_ns(&s->timer, QEMU_CLOCK_VIRTUAL, |
| 374 | bcm2836_control_local_timer_tick, s); |
| 375 | } |
| 376 | |
| 377 | static const VMStateDescription vmstate_bcm2836_control = { |
| 378 | .name = TYPE_BCM2836_CONTROL, |
| 379 | .version_id = 2, |
| 380 | .minimum_version_id = 1, |
| 381 | .fields = (const VMStateField[]) { |
| 382 | VMSTATE_UINT32_ARRAY(mailboxes, BCM2836ControlState, |
| 383 | BCM2836_NCORES * BCM2836_MBPERCORE), |
| 384 | VMSTATE_UINT8(route_gpu_irq, BCM2836ControlState), |
| 385 | VMSTATE_UINT8(route_gpu_fiq, BCM2836ControlState), |
| 386 | VMSTATE_UINT32_ARRAY(timercontrol, BCM2836ControlState, BCM2836_NCORES), |
| 387 | VMSTATE_UINT32_ARRAY(mailboxcontrol, BCM2836ControlState, |
| 388 | BCM2836_NCORES), |
| 389 | VMSTATE_TIMER_V(timer, BCM2836ControlState, 2), |
| 390 | VMSTATE_UINT32_V(local_timer_control, BCM2836ControlState, 2), |
| 391 | VMSTATE_UINT8_V(route_localtimer, BCM2836ControlState, 2), |
| 392 | VMSTATE_END_OF_LIST() |
| 393 | } |
| 394 | }; |
| 395 | |
| 396 | static void bcm2836_control_class_init(ObjectClass *klass, const void *data) |
| 397 | { |
| 398 | DeviceClass *dc = DEVICE_CLASS(klass); |
| 399 | |
| 400 | device_class_set_legacy_reset(dc, bcm2836_control_reset); |
| 401 | dc->vmsd = &vmstate_bcm2836_control; |
| 402 | } |
| 403 | |
| 404 | static const TypeInfo bcm2836_control_info = { |
| 405 | .name = TYPE_BCM2836_CONTROL, |
| 406 | .parent = TYPE_SYS_BUS_DEVICE, |
| 407 | .instance_size = sizeof(BCM2836ControlState), |
| 408 | .class_init = bcm2836_control_class_init, |
| 409 | .instance_init = bcm2836_control_init, |
| 410 | }; |
| 411 | |
| 412 | static void bcm2836_control_register_types(void) |
| 413 | { |
| 414 | type_register_static(&bcm2836_control_info); |
| 415 | } |
| 416 | |
| 417 | type_init(bcm2836_control_register_types) |