123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 |
- /*
- * ASPEED Interrupt Controller (New)
- *
- * Andrew Jeffery <andrew@aj.id.au>
- *
- * Copyright 2015, 2016 IBM Corp.
- *
- * This code is licensed under the GPL version 2 or later. See
- * the COPYING file in the top-level directory.
- */
- /* The hardware exposes two register sets, a legacy set and a 'new' set. The
- * model implements the 'new' register set, and logs warnings on accesses to
- * the legacy IO space.
- *
- * The hardware uses 32bit registers to manage 51 IRQs, with low and high
- * registers for each conceptual register. The device model's implementation
- * uses 64bit data types to store both low and high register values (in the one
- * member), but must cope with access offset values in multiples of 4 passed to
- * the callbacks. As such the read() and write() implementations process the
- * provided offset to understand whether the access is requesting the lower or
- * upper 32 bits of the 64bit member.
- *
- * Additionally, the "Interrupt Enable", "Edge Status" and "Software Interrupt"
- * fields have separate "enable"/"status" and "clear" registers, where set bits
- * are written to one or the other to change state (avoiding a
- * read-modify-write sequence).
- */
- #include "qemu/osdep.h"
- #include "hw/intc/aspeed_vic.h"
- #include "hw/irq.h"
- #include "migration/vmstate.h"
- #include "qemu/bitops.h"
- #include "qemu/log.h"
- #include "qemu/module.h"
- #include "trace.h"
- #define AVIC_NEW_BASE_OFFSET 0x80
- #define AVIC_L_MASK 0xFFFFFFFFU
- #define AVIC_H_MASK 0x0007FFFFU
- #define AVIC_EVENT_W_MASK (0x78000ULL << 32)
- static void aspeed_vic_update(AspeedVICState *s)
- {
- uint64_t new = (s->raw & s->enable);
- uint64_t flags;
- flags = new & s->select;
- trace_aspeed_vic_update_fiq(!!flags);
- qemu_set_irq(s->fiq, !!flags);
- flags = new & ~s->select;
- trace_aspeed_vic_update_irq(!!flags);
- qemu_set_irq(s->irq, !!flags);
- }
- static void aspeed_vic_set_irq(void *opaque, int irq, int level)
- {
- uint64_t irq_mask;
- bool raise;
- AspeedVICState *s = (AspeedVICState *)opaque;
- if (irq > ASPEED_VIC_NR_IRQS) {
- qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid interrupt number: %d\n",
- __func__, irq);
- return;
- }
- trace_aspeed_vic_set_irq(irq, level);
- irq_mask = BIT(irq);
- if (s->sense & irq_mask) {
- /* level-triggered */
- if (s->event & irq_mask) {
- /* high-sensitive */
- raise = level;
- } else {
- /* low-sensitive */
- raise = !level;
- }
- s->raw = deposit64(s->raw, irq, 1, raise);
- } else {
- uint64_t old_level = s->level & irq_mask;
- /* edge-triggered */
- if (s->dual_edge & irq_mask) {
- raise = (!!old_level) != (!!level);
- } else {
- if (s->event & irq_mask) {
- /* rising-sensitive */
- raise = !old_level && level;
- } else {
- /* falling-sensitive */
- raise = old_level && !level;
- }
- }
- if (raise) {
- s->raw = deposit64(s->raw, irq, 1, raise);
- }
- }
- s->level = deposit64(s->level, irq, 1, level);
- aspeed_vic_update(s);
- }
- static uint64_t aspeed_vic_read(void *opaque, hwaddr offset, unsigned size)
- {
- AspeedVICState *s = (AspeedVICState *)opaque;
- hwaddr n_offset;
- uint64_t val;
- bool high;
- if (offset < AVIC_NEW_BASE_OFFSET) {
- high = false;
- n_offset = offset;
- } else {
- high = !!(offset & 0x4);
- n_offset = (offset & ~0x4);
- }
- switch (n_offset) {
- case 0x80: /* IRQ Status */
- case 0x00:
- val = s->raw & ~s->select & s->enable;
- break;
- case 0x88: /* FIQ Status */
- case 0x04:
- val = s->raw & s->select & s->enable;
- break;
- case 0x90: /* Raw Interrupt Status */
- case 0x08:
- val = s->raw;
- break;
- case 0x98: /* Interrupt Selection */
- case 0x0c:
- val = s->select;
- break;
- case 0xa0: /* Interrupt Enable */
- case 0x10:
- val = s->enable;
- break;
- case 0xb0: /* Software Interrupt */
- case 0x18:
- val = s->trigger;
- break;
- case 0xc0: /* Interrupt Sensitivity */
- case 0x24:
- val = s->sense;
- break;
- case 0xc8: /* Interrupt Both Edge Trigger Control */
- case 0x28:
- val = s->dual_edge;
- break;
- case 0xd0: /* Interrupt Event */
- case 0x2c:
- val = s->event;
- break;
- case 0xe0: /* Edge Triggered Interrupt Status */
- val = s->raw & ~s->sense;
- break;
- /* Illegal */
- case 0xa8: /* Interrupt Enable Clear */
- case 0xb8: /* Software Interrupt Clear */
- case 0xd8: /* Edge Triggered Interrupt Clear */
- qemu_log_mask(LOG_GUEST_ERROR,
- "%s: Read of write-only register with offset 0x%"
- HWADDR_PRIx "\n", __func__, offset);
- val = 0;
- break;
- default:
- qemu_log_mask(LOG_GUEST_ERROR,
- "%s: Bad register at offset 0x%" HWADDR_PRIx "\n",
- __func__, offset);
- val = 0;
- break;
- }
- if (high) {
- val = extract64(val, 32, 19);
- } else {
- val = extract64(val, 0, 32);
- }
- trace_aspeed_vic_read(offset, size, val);
- return val;
- }
- static void aspeed_vic_write(void *opaque, hwaddr offset, uint64_t data,
- unsigned size)
- {
- AspeedVICState *s = (AspeedVICState *)opaque;
- hwaddr n_offset;
- bool high;
- if (offset < AVIC_NEW_BASE_OFFSET) {
- high = false;
- n_offset = offset;
- } else {
- high = !!(offset & 0x4);
- n_offset = (offset & ~0x4);
- }
- trace_aspeed_vic_write(offset, size, data);
- /* Given we have members using separate enable/clear registers, deposit64()
- * isn't quite the tool for the job. Instead, relocate the incoming bits to
- * the required bit offset based on the provided access address
- */
- if (high) {
- data &= AVIC_H_MASK;
- data <<= 32;
- } else {
- data &= AVIC_L_MASK;
- }
- switch (n_offset) {
- case 0x98: /* Interrupt Selection */
- case 0x0c:
- /* Register has deposit64() semantics - overwrite requested 32 bits */
- if (high) {
- s->select &= AVIC_L_MASK;
- } else {
- s->select &= ((uint64_t) AVIC_H_MASK) << 32;
- }
- s->select |= data;
- break;
- case 0xa0: /* Interrupt Enable */
- case 0x10:
- s->enable |= data;
- break;
- case 0xa8: /* Interrupt Enable Clear */
- case 0x14:
- s->enable &= ~data;
- break;
- case 0xb0: /* Software Interrupt */
- case 0x18:
- qemu_log_mask(LOG_UNIMP, "%s: Software interrupts unavailable. "
- "IRQs requested: 0x%016" PRIx64 "\n", __func__, data);
- break;
- case 0xb8: /* Software Interrupt Clear */
- case 0x1c:
- qemu_log_mask(LOG_UNIMP, "%s: Software interrupts unavailable. "
- "IRQs to be cleared: 0x%016" PRIx64 "\n", __func__, data);
- break;
- case 0xd0: /* Interrupt Event */
- /* Register has deposit64() semantics - overwrite the top four valid
- * IRQ bits, as only the top four IRQs (GPIOs) can change their event
- * type */
- if (high) {
- s->event &= ~AVIC_EVENT_W_MASK;
- s->event |= (data & AVIC_EVENT_W_MASK);
- } else {
- qemu_log_mask(LOG_GUEST_ERROR,
- "Ignoring invalid write to interrupt event register");
- }
- break;
- case 0xd8: /* Edge Triggered Interrupt Clear */
- case 0x38:
- s->raw &= ~(data & ~s->sense);
- break;
- case 0x80: /* IRQ Status */
- case 0x00:
- case 0x88: /* FIQ Status */
- case 0x04:
- case 0x90: /* Raw Interrupt Status */
- case 0x08:
- case 0xc0: /* Interrupt Sensitivity */
- case 0x24:
- case 0xc8: /* Interrupt Both Edge Trigger Control */
- case 0x28:
- case 0xe0: /* Edge Triggered Interrupt Status */
- qemu_log_mask(LOG_GUEST_ERROR,
- "%s: Write of read-only register with offset 0x%"
- HWADDR_PRIx "\n", __func__, offset);
- break;
- default:
- qemu_log_mask(LOG_GUEST_ERROR,
- "%s: Bad register at offset 0x%" HWADDR_PRIx "\n",
- __func__, offset);
- break;
- }
- aspeed_vic_update(s);
- }
- static const MemoryRegionOps aspeed_vic_ops = {
- .read = aspeed_vic_read,
- .write = aspeed_vic_write,
- .endianness = DEVICE_LITTLE_ENDIAN,
- .valid.min_access_size = 4,
- .valid.max_access_size = 4,
- .valid.unaligned = false,
- };
- static void aspeed_vic_reset(DeviceState *dev)
- {
- AspeedVICState *s = ASPEED_VIC(dev);
- s->level = 0;
- s->raw = 0;
- s->select = 0;
- s->enable = 0;
- s->trigger = 0;
- s->sense = 0x1F07FFF8FFFFULL;
- s->dual_edge = 0xF800070000ULL;
- s->event = 0x5F07FFF8FFFFULL;
- }
- #define AVIC_IO_REGION_SIZE 0x20000
- static void aspeed_vic_realize(DeviceState *dev, Error **errp)
- {
- SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
- AspeedVICState *s = ASPEED_VIC(dev);
- memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_vic_ops, s,
- TYPE_ASPEED_VIC, AVIC_IO_REGION_SIZE);
- sysbus_init_mmio(sbd, &s->iomem);
- qdev_init_gpio_in(dev, aspeed_vic_set_irq, ASPEED_VIC_NR_IRQS);
- sysbus_init_irq(sbd, &s->irq);
- sysbus_init_irq(sbd, &s->fiq);
- }
- static const VMStateDescription vmstate_aspeed_vic = {
- .name = "aspeed.new-vic",
- .version_id = 1,
- .minimum_version_id = 1,
- .fields = (VMStateField[]) {
- VMSTATE_UINT64(level, AspeedVICState),
- VMSTATE_UINT64(raw, AspeedVICState),
- VMSTATE_UINT64(select, AspeedVICState),
- VMSTATE_UINT64(enable, AspeedVICState),
- VMSTATE_UINT64(trigger, AspeedVICState),
- VMSTATE_UINT64(sense, AspeedVICState),
- VMSTATE_UINT64(dual_edge, AspeedVICState),
- VMSTATE_UINT64(event, AspeedVICState),
- VMSTATE_END_OF_LIST()
- }
- };
- static void aspeed_vic_class_init(ObjectClass *klass, void *data)
- {
- DeviceClass *dc = DEVICE_CLASS(klass);
- dc->realize = aspeed_vic_realize;
- dc->reset = aspeed_vic_reset;
- dc->desc = "ASPEED Interrupt Controller (New)";
- dc->vmsd = &vmstate_aspeed_vic;
- }
- static const TypeInfo aspeed_vic_info = {
- .name = TYPE_ASPEED_VIC,
- .parent = TYPE_SYS_BUS_DEVICE,
- .instance_size = sizeof(AspeedVICState),
- .class_init = aspeed_vic_class_init,
- };
- static void aspeed_vic_register_types(void)
- {
- type_register_static(&aspeed_vic_info);
- }
- type_init(aspeed_vic_register_types);
|