|  | /* | 
|  | * 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 = (const 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); |