diff options
Diffstat (limited to 'hw/intc')
-rw-r--r-- | hw/intc/Makefile.objs | 18 | ||||
-rw-r--r-- | hw/intc/apic.c | 911 | ||||
-rw-r--r-- | hw/intc/apic_common.c | 402 | ||||
-rw-r--r-- | hw/intc/arm_gic.c | 723 | ||||
-rw-r--r-- | hw/intc/arm_gic_common.c | 176 | ||||
-rw-r--r-- | hw/intc/arm_gic_kvm.c | 167 | ||||
-rw-r--r-- | hw/intc/armv7m_nvic.c | 553 | ||||
-rw-r--r-- | hw/intc/etraxfs_pic.c | 180 | ||||
-rw-r--r-- | hw/intc/exynos4210_combiner.c | 455 | ||||
-rw-r--r-- | hw/intc/exynos4210_gic.c | 462 | ||||
-rw-r--r-- | hw/intc/grlib_irqmp.c | 385 | ||||
-rw-r--r-- | hw/intc/imx_avic.c | 408 | ||||
-rw-r--r-- | hw/intc/ioapic.c | 258 | ||||
-rw-r--r-- | hw/intc/ioapic_common.c | 120 | ||||
-rw-r--r-- | hw/intc/lm32_pic.c | 199 | ||||
-rw-r--r-- | hw/intc/omap_intc.c | 649 | ||||
-rw-r--r-- | hw/intc/openpic.c | 1661 | ||||
-rw-r--r-- | hw/intc/realview_gic.c | 74 | ||||
-rw-r--r-- | hw/intc/sbi.c | 156 | ||||
-rw-r--r-- | hw/intc/sh_intc.c | 513 | ||||
-rw-r--r-- | hw/intc/slavio_intctl.c | 471 | ||||
-rw-r--r-- | hw/intc/sun4c_intctl.c | 208 |
22 files changed, 9149 insertions, 0 deletions
diff --git a/hw/intc/Makefile.objs b/hw/intc/Makefile.objs index 2813adb3e7..718d97ae8a 100644 --- a/hw/intc/Makefile.objs +++ b/hw/intc/Makefile.objs @@ -3,3 +3,21 @@ common-obj-$(CONFIG_I8259) += i8259_common.o i8259.o common-obj-$(CONFIG_PL190) += pl190.o common-obj-$(CONFIG_PUV3) += puv3_intc.o common-obj-$(CONFIG_XILINX) += xilinx_intc.o +common-obj-$(CONFIG_ETRAXFS) += etraxfs_pic.o +common-obj-$(CONFIG_IMX) += imx_avic.o +common-obj-$(CONFIG_LM32) += lm32_pic.o +common-obj-$(CONFIG_REALVIEW) += realview_gic.o +common-obj-$(CONFIG_SLAVIO) += sbi.o slavio_intctl.o sun4c_intctl.o +common-obj-$(CONFIG_IOAPIC) += ioapic_common.o +common-obj-$(CONFIG_ARM_GIC) += arm_gic_common.o + +obj-$(CONFIG_APIC) += apic.o apic_common.o +obj-$(CONFIG_ARM_GIC) += arm_gic.o +obj-$(CONFIG_ARM_GIC_KVM) += arm_gic_kvm.o +obj-$(CONFIG_STELLARIS) += armv7m_nvic.o +obj-$(CONFIG_EXYNOS4) += exynos4210_gic.o exynos4210_combiner.o +obj-$(CONFIG_GRLIB) += grlib_irqmp.o +obj-$(CONFIG_IOAPIC) += ioapic.o +obj-$(CONFIG_OMAP) += omap_intc.o +obj-$(CONFIG_OPENPIC) += openpic.o +obj-$(CONFIG_SH4) += sh_intc.o diff --git a/hw/intc/apic.c b/hw/intc/apic.c new file mode 100644 index 0000000000..2d79a9ea7f --- /dev/null +++ b/hw/intc/apic.c @@ -0,0 +1,911 @@ +/* + * APIC support + * + * Copyright (c) 2004-2005 Fabrice Bellard + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ +#include "qemu/thread.h" +#include "hw/i386/apic_internal.h" +#include "hw/i386/apic.h" +#include "hw/i386/ioapic.h" +#include "hw/pci/msi.h" +#include "qemu/host-utils.h" +#include "trace.h" +#include "hw/i386/pc.h" +#include "hw/i386/apic-msidef.h" + +#define MAX_APIC_WORDS 8 + +#define SYNC_FROM_VAPIC 0x1 +#define SYNC_TO_VAPIC 0x2 +#define SYNC_ISR_IRR_TO_VAPIC 0x4 + +static APICCommonState *local_apics[MAX_APICS + 1]; + +static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode); +static void apic_update_irq(APICCommonState *s); +static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask, + uint8_t dest, uint8_t dest_mode); + +/* Find first bit starting from msb */ +static int fls_bit(uint32_t value) +{ + return 31 - clz32(value); +} + +/* Find first bit starting from lsb */ +static int ffs_bit(uint32_t value) +{ + return ctz32(value); +} + +static inline void set_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + tab[i] |= mask; +} + +static inline void reset_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + tab[i] &= ~mask; +} + +static inline int get_bit(uint32_t *tab, int index) +{ + int i, mask; + i = index >> 5; + mask = 1 << (index & 0x1f); + return !!(tab[i] & mask); +} + +/* return -1 if no bit is set */ +static int get_highest_priority_int(uint32_t *tab) +{ + int i; + for (i = 7; i >= 0; i--) { + if (tab[i] != 0) { + return i * 32 + fls_bit(tab[i]); + } + } + return -1; +} + +static void apic_sync_vapic(APICCommonState *s, int sync_type) +{ + VAPICState vapic_state; + size_t length; + off_t start; + int vector; + + if (!s->vapic_paddr) { + return; + } + if (sync_type & SYNC_FROM_VAPIC) { + cpu_physical_memory_rw(s->vapic_paddr, (void *)&vapic_state, + sizeof(vapic_state), 0); + s->tpr = vapic_state.tpr; + } + if (sync_type & (SYNC_TO_VAPIC | SYNC_ISR_IRR_TO_VAPIC)) { + start = offsetof(VAPICState, isr); + length = offsetof(VAPICState, enabled) - offsetof(VAPICState, isr); + + if (sync_type & SYNC_TO_VAPIC) { + assert(qemu_cpu_is_self(CPU(s->cpu))); + + vapic_state.tpr = s->tpr; + vapic_state.enabled = 1; + start = 0; + length = sizeof(VAPICState); + } + + vector = get_highest_priority_int(s->isr); + if (vector < 0) { + vector = 0; + } + vapic_state.isr = vector & 0xf0; + + vapic_state.zero = 0; + + vector = get_highest_priority_int(s->irr); + if (vector < 0) { + vector = 0; + } + vapic_state.irr = vector & 0xff; + + cpu_physical_memory_write_rom(s->vapic_paddr + start, + ((void *)&vapic_state) + start, length); + } +} + +static void apic_vapic_base_update(APICCommonState *s) +{ + apic_sync_vapic(s, SYNC_TO_VAPIC); +} + +static void apic_local_deliver(APICCommonState *s, int vector) +{ + uint32_t lvt = s->lvt[vector]; + int trigger_mode; + + trace_apic_local_deliver(vector, (lvt >> 8) & 7); + + if (lvt & APIC_LVT_MASKED) + return; + + switch ((lvt >> 8) & 7) { + case APIC_DM_SMI: + cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_SMI); + break; + + case APIC_DM_NMI: + cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_NMI); + break; + + case APIC_DM_EXTINT: + cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_HARD); + break; + + case APIC_DM_FIXED: + trigger_mode = APIC_TRIGGER_EDGE; + if ((vector == APIC_LVT_LINT0 || vector == APIC_LVT_LINT1) && + (lvt & APIC_LVT_LEVEL_TRIGGER)) + trigger_mode = APIC_TRIGGER_LEVEL; + apic_set_irq(s, lvt & 0xff, trigger_mode); + } +} + +void apic_deliver_pic_intr(DeviceState *d, int level) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + + if (level) { + apic_local_deliver(s, APIC_LVT_LINT0); + } else { + uint32_t lvt = s->lvt[APIC_LVT_LINT0]; + + switch ((lvt >> 8) & 7) { + case APIC_DM_FIXED: + if (!(lvt & APIC_LVT_LEVEL_TRIGGER)) + break; + reset_bit(s->irr, lvt & 0xff); + /* fall through */ + case APIC_DM_EXTINT: + cpu_reset_interrupt(CPU(s->cpu), CPU_INTERRUPT_HARD); + break; + } + } +} + +static void apic_external_nmi(APICCommonState *s) +{ + apic_local_deliver(s, APIC_LVT_LINT1); +} + +#define foreach_apic(apic, deliver_bitmask, code) \ +{\ + int __i, __j, __mask;\ + for(__i = 0; __i < MAX_APIC_WORDS; __i++) {\ + __mask = deliver_bitmask[__i];\ + if (__mask) {\ + for(__j = 0; __j < 32; __j++) {\ + if (__mask & (1 << __j)) {\ + apic = local_apics[__i * 32 + __j];\ + if (apic) {\ + code;\ + }\ + }\ + }\ + }\ + }\ +} + +static void apic_bus_deliver(const uint32_t *deliver_bitmask, + uint8_t delivery_mode, uint8_t vector_num, + uint8_t trigger_mode) +{ + APICCommonState *apic_iter; + + switch (delivery_mode) { + case APIC_DM_LOWPRI: + /* XXX: search for focus processor, arbitration */ + { + int i, d; + d = -1; + for(i = 0; i < MAX_APIC_WORDS; i++) { + if (deliver_bitmask[i]) { + d = i * 32 + ffs_bit(deliver_bitmask[i]); + break; + } + } + if (d >= 0) { + apic_iter = local_apics[d]; + if (apic_iter) { + apic_set_irq(apic_iter, vector_num, trigger_mode); + } + } + } + return; + + case APIC_DM_FIXED: + break; + + case APIC_DM_SMI: + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(CPU(apic_iter->cpu), CPU_INTERRUPT_SMI) + ); + return; + + case APIC_DM_NMI: + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(CPU(apic_iter->cpu), CPU_INTERRUPT_NMI) + ); + return; + + case APIC_DM_INIT: + /* normal INIT IPI sent to processors */ + foreach_apic(apic_iter, deliver_bitmask, + cpu_interrupt(CPU(apic_iter->cpu), + CPU_INTERRUPT_INIT) + ); + return; + + case APIC_DM_EXTINT: + /* handled in I/O APIC code */ + break; + + default: + return; + } + + foreach_apic(apic_iter, deliver_bitmask, + apic_set_irq(apic_iter, vector_num, trigger_mode) ); +} + +void apic_deliver_irq(uint8_t dest, uint8_t dest_mode, uint8_t delivery_mode, + uint8_t vector_num, uint8_t trigger_mode) +{ + uint32_t deliver_bitmask[MAX_APIC_WORDS]; + + trace_apic_deliver_irq(dest, dest_mode, delivery_mode, vector_num, + trigger_mode); + + apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode); + apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, trigger_mode); +} + +static void apic_set_base(APICCommonState *s, uint64_t val) +{ + s->apicbase = (val & 0xfffff000) | + (s->apicbase & (MSR_IA32_APICBASE_BSP | MSR_IA32_APICBASE_ENABLE)); + /* if disabled, cannot be enabled again */ + if (!(val & MSR_IA32_APICBASE_ENABLE)) { + s->apicbase &= ~MSR_IA32_APICBASE_ENABLE; + cpu_clear_apic_feature(&s->cpu->env); + s->spurious_vec &= ~APIC_SV_ENABLE; + } +} + +static void apic_set_tpr(APICCommonState *s, uint8_t val) +{ + /* Updates from cr8 are ignored while the VAPIC is active */ + if (!s->vapic_paddr) { + s->tpr = val << 4; + apic_update_irq(s); + } +} + +static uint8_t apic_get_tpr(APICCommonState *s) +{ + apic_sync_vapic(s, SYNC_FROM_VAPIC); + return s->tpr >> 4; +} + +static int apic_get_ppr(APICCommonState *s) +{ + int tpr, isrv, ppr; + + tpr = (s->tpr >> 4); + isrv = get_highest_priority_int(s->isr); + if (isrv < 0) + isrv = 0; + isrv >>= 4; + if (tpr >= isrv) + ppr = s->tpr; + else + ppr = isrv << 4; + return ppr; +} + +static int apic_get_arb_pri(APICCommonState *s) +{ + /* XXX: arbitration */ + return 0; +} + + +/* + * <0 - low prio interrupt, + * 0 - no interrupt, + * >0 - interrupt number + */ +static int apic_irq_pending(APICCommonState *s) +{ + int irrv, ppr; + irrv = get_highest_priority_int(s->irr); + if (irrv < 0) { + return 0; + } + ppr = apic_get_ppr(s); + if (ppr && (irrv & 0xf0) <= (ppr & 0xf0)) { + return -1; + } + + return irrv; +} + +/* signal the CPU if an irq is pending */ +static void apic_update_irq(APICCommonState *s) +{ + CPUState *cpu; + + if (!(s->spurious_vec & APIC_SV_ENABLE)) { + return; + } + cpu = CPU(s->cpu); + if (!qemu_cpu_is_self(cpu)) { + cpu_interrupt(cpu, CPU_INTERRUPT_POLL); + } else if (apic_irq_pending(s) > 0) { + cpu_interrupt(cpu, CPU_INTERRUPT_HARD); + } +} + +void apic_poll_irq(DeviceState *d) +{ + APICCommonState *s = APIC_COMMON(d); + + apic_sync_vapic(s, SYNC_FROM_VAPIC); + apic_update_irq(s); +} + +static void apic_set_irq(APICCommonState *s, int vector_num, int trigger_mode) +{ + apic_report_irq_delivered(!get_bit(s->irr, vector_num)); + + set_bit(s->irr, vector_num); + if (trigger_mode) + set_bit(s->tmr, vector_num); + else + reset_bit(s->tmr, vector_num); + if (s->vapic_paddr) { + apic_sync_vapic(s, SYNC_ISR_IRR_TO_VAPIC); + /* + * The vcpu thread needs to see the new IRR before we pull its current + * TPR value. That way, if we miss a lowering of the TRP, the guest + * has the chance to notice the new IRR and poll for IRQs on its own. + */ + smp_wmb(); + apic_sync_vapic(s, SYNC_FROM_VAPIC); + } + apic_update_irq(s); +} + +static void apic_eoi(APICCommonState *s) +{ + int isrv; + isrv = get_highest_priority_int(s->isr); + if (isrv < 0) + return; + reset_bit(s->isr, isrv); + if (!(s->spurious_vec & APIC_SV_DIRECTED_IO) && get_bit(s->tmr, isrv)) { + ioapic_eoi_broadcast(isrv); + } + apic_sync_vapic(s, SYNC_FROM_VAPIC | SYNC_TO_VAPIC); + apic_update_irq(s); +} + +static int apic_find_dest(uint8_t dest) +{ + APICCommonState *apic = local_apics[dest]; + int i; + + if (apic && apic->id == dest) + return dest; /* shortcut in case apic->id == apic->idx */ + + for (i = 0; i < MAX_APICS; i++) { + apic = local_apics[i]; + if (apic && apic->id == dest) + return i; + if (!apic) + break; + } + + return -1; +} + +static void apic_get_delivery_bitmask(uint32_t *deliver_bitmask, + uint8_t dest, uint8_t dest_mode) +{ + APICCommonState *apic_iter; + int i; + + if (dest_mode == 0) { + if (dest == 0xff) { + memset(deliver_bitmask, 0xff, MAX_APIC_WORDS * sizeof(uint32_t)); + } else { + int idx = apic_find_dest(dest); + memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t)); + if (idx >= 0) + set_bit(deliver_bitmask, idx); + } + } else { + /* XXX: cluster mode */ + memset(deliver_bitmask, 0x00, MAX_APIC_WORDS * sizeof(uint32_t)); + for(i = 0; i < MAX_APICS; i++) { + apic_iter = local_apics[i]; + if (apic_iter) { + if (apic_iter->dest_mode == 0xf) { + if (dest & apic_iter->log_dest) + set_bit(deliver_bitmask, i); + } else if (apic_iter->dest_mode == 0x0) { + if ((dest & 0xf0) == (apic_iter->log_dest & 0xf0) && + (dest & apic_iter->log_dest & 0x0f)) { + set_bit(deliver_bitmask, i); + } + } + } else { + break; + } + } + } +} + +static void apic_startup(APICCommonState *s, int vector_num) +{ + s->sipi_vector = vector_num; + cpu_interrupt(CPU(s->cpu), CPU_INTERRUPT_SIPI); +} + +void apic_sipi(DeviceState *d) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + + cpu_reset_interrupt(CPU(s->cpu), CPU_INTERRUPT_SIPI); + + if (!s->wait_for_sipi) + return; + cpu_x86_load_seg_cache_sipi(s->cpu, s->sipi_vector); + s->wait_for_sipi = 0; +} + +static void apic_deliver(DeviceState *d, uint8_t dest, uint8_t dest_mode, + uint8_t delivery_mode, uint8_t vector_num, + uint8_t trigger_mode) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + uint32_t deliver_bitmask[MAX_APIC_WORDS]; + int dest_shorthand = (s->icr[0] >> 18) & 3; + APICCommonState *apic_iter; + + switch (dest_shorthand) { + case 0: + apic_get_delivery_bitmask(deliver_bitmask, dest, dest_mode); + break; + case 1: + memset(deliver_bitmask, 0x00, sizeof(deliver_bitmask)); + set_bit(deliver_bitmask, s->idx); + break; + case 2: + memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask)); + break; + case 3: + memset(deliver_bitmask, 0xff, sizeof(deliver_bitmask)); + reset_bit(deliver_bitmask, s->idx); + break; + } + + switch (delivery_mode) { + case APIC_DM_INIT: + { + int trig_mode = (s->icr[0] >> 15) & 1; + int level = (s->icr[0] >> 14) & 1; + if (level == 0 && trig_mode == 1) { + foreach_apic(apic_iter, deliver_bitmask, + apic_iter->arb_id = apic_iter->id ); + return; + } + } + break; + + case APIC_DM_SIPI: + foreach_apic(apic_iter, deliver_bitmask, + apic_startup(apic_iter, vector_num) ); + return; + } + + apic_bus_deliver(deliver_bitmask, delivery_mode, vector_num, trigger_mode); +} + +static bool apic_check_pic(APICCommonState *s) +{ + if (!apic_accept_pic_intr(&s->busdev.qdev) || !pic_get_output(isa_pic)) { + return false; + } + apic_deliver_pic_intr(&s->busdev.qdev, 1); + return true; +} + +int apic_get_interrupt(DeviceState *d) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + int intno; + + /* if the APIC is installed or enabled, we let the 8259 handle the + IRQs */ + if (!s) + return -1; + if (!(s->spurious_vec & APIC_SV_ENABLE)) + return -1; + + apic_sync_vapic(s, SYNC_FROM_VAPIC); + intno = apic_irq_pending(s); + + if (intno == 0) { + apic_sync_vapic(s, SYNC_TO_VAPIC); + return -1; + } else if (intno < 0) { + apic_sync_vapic(s, SYNC_TO_VAPIC); + return s->spurious_vec & 0xff; + } + reset_bit(s->irr, intno); + set_bit(s->isr, intno); + apic_sync_vapic(s, SYNC_TO_VAPIC); + + /* re-inject if there is still a pending PIC interrupt */ + apic_check_pic(s); + + apic_update_irq(s); + + return intno; +} + +int apic_accept_pic_intr(DeviceState *d) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + uint32_t lvt0; + + if (!s) + return -1; + + lvt0 = s->lvt[APIC_LVT_LINT0]; + + if ((s->apicbase & MSR_IA32_APICBASE_ENABLE) == 0 || + (lvt0 & APIC_LVT_MASKED) == 0) + return 1; + + return 0; +} + +static uint32_t apic_get_current_count(APICCommonState *s) +{ + int64_t d; + uint32_t val; + d = (qemu_get_clock_ns(vm_clock) - s->initial_count_load_time) >> + s->count_shift; + if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) { + /* periodic */ + val = s->initial_count - (d % ((uint64_t)s->initial_count + 1)); + } else { + if (d >= s->initial_count) + val = 0; + else + val = s->initial_count - d; + } + return val; +} + +static void apic_timer_update(APICCommonState *s, int64_t current_time) +{ + if (apic_next_timer(s, current_time)) { + qemu_mod_timer(s->timer, s->next_time); + } else { + qemu_del_timer(s->timer); + } +} + +static void apic_timer(void *opaque) +{ + APICCommonState *s = opaque; + + apic_local_deliver(s, APIC_LVT_TIMER); + apic_timer_update(s, s->next_time); +} + +static uint32_t apic_mem_readb(void *opaque, hwaddr addr) +{ + return 0; +} + +static uint32_t apic_mem_readw(void *opaque, hwaddr addr) +{ + return 0; +} + +static void apic_mem_writeb(void *opaque, hwaddr addr, uint32_t val) +{ +} + +static void apic_mem_writew(void *opaque, hwaddr addr, uint32_t val) +{ +} + +static uint32_t apic_mem_readl(void *opaque, hwaddr addr) +{ + DeviceState *d; + APICCommonState *s; + uint32_t val; + int index; + + d = cpu_get_current_apic(); + if (!d) { + return 0; + } + s = DO_UPCAST(APICCommonState, busdev.qdev, d); + + index = (addr >> 4) & 0xff; + switch(index) { + case 0x02: /* id */ + val = s->id << 24; + break; + case 0x03: /* version */ + val = 0x11 | ((APIC_LVT_NB - 1) << 16); /* version 0x11 */ + break; + case 0x08: + apic_sync_vapic(s, SYNC_FROM_VAPIC); + if (apic_report_tpr_access) { + cpu_report_tpr_access(&s->cpu->env, TPR_ACCESS_READ); + } + val = s->tpr; + break; + case 0x09: + val = apic_get_arb_pri(s); + break; + case 0x0a: + /* ppr */ + val = apic_get_ppr(s); + break; + case 0x0b: + val = 0; + break; + case 0x0d: + val = s->log_dest << 24; + break; + case 0x0e: + val = s->dest_mode << 28; + break; + case 0x0f: + val = s->spurious_vec; + break; + case 0x10 ... 0x17: + val = s->isr[index & 7]; + break; + case 0x18 ... 0x1f: + val = s->tmr[index & 7]; + break; + case 0x20 ... 0x27: + val = s->irr[index & 7]; + break; + case 0x28: + val = s->esr; + break; + case 0x30: + case 0x31: + val = s->icr[index & 1]; + break; + case 0x32 ... 0x37: + val = s->lvt[index - 0x32]; + break; + case 0x38: + val = s->initial_count; + break; + case 0x39: + val = apic_get_current_count(s); + break; + case 0x3e: + val = s->divide_conf; + break; + default: + s->esr |= ESR_ILLEGAL_ADDRESS; + val = 0; + break; + } + trace_apic_mem_readl(addr, val); + return val; +} + +static void apic_send_msi(hwaddr addr, uint32_t data) +{ + uint8_t dest = (addr & MSI_ADDR_DEST_ID_MASK) >> MSI_ADDR_DEST_ID_SHIFT; + uint8_t vector = (data & MSI_DATA_VECTOR_MASK) >> MSI_DATA_VECTOR_SHIFT; + uint8_t dest_mode = (addr >> MSI_ADDR_DEST_MODE_SHIFT) & 0x1; + uint8_t trigger_mode = (data >> MSI_DATA_TRIGGER_SHIFT) & 0x1; + uint8_t delivery = (data >> MSI_DATA_DELIVERY_MODE_SHIFT) & 0x7; + /* XXX: Ignore redirection hint. */ + apic_deliver_irq(dest, dest_mode, delivery, vector, trigger_mode); +} + +static void apic_mem_writel(void *opaque, hwaddr addr, uint32_t val) +{ + DeviceState *d; + APICCommonState *s; + int index = (addr >> 4) & 0xff; + if (addr > 0xfff || !index) { + /* MSI and MMIO APIC are at the same memory location, + * but actually not on the global bus: MSI is on PCI bus + * APIC is connected directly to the CPU. + * Mapping them on the global bus happens to work because + * MSI registers are reserved in APIC MMIO and vice versa. */ + apic_send_msi(addr, val); + return; + } + + d = cpu_get_current_apic(); + if (!d) { + return; + } + s = DO_UPCAST(APICCommonState, busdev.qdev, d); + + trace_apic_mem_writel(addr, val); + + switch(index) { + case 0x02: + s->id = (val >> 24); + break; + case 0x03: + break; + case 0x08: + if (apic_report_tpr_access) { + cpu_report_tpr_access(&s->cpu->env, TPR_ACCESS_WRITE); + } + s->tpr = val; + apic_sync_vapic(s, SYNC_TO_VAPIC); + apic_update_irq(s); + break; + case 0x09: + case 0x0a: + break; + case 0x0b: /* EOI */ + apic_eoi(s); + break; + case 0x0d: + s->log_dest = val >> 24; + break; + case 0x0e: + s->dest_mode = val >> 28; + break; + case 0x0f: + s->spurious_vec = val & 0x1ff; + apic_update_irq(s); + break; + case 0x10 ... 0x17: + case 0x18 ... 0x1f: + case 0x20 ... 0x27: + case 0x28: + break; + case 0x30: + s->icr[0] = val; + apic_deliver(d, (s->icr[1] >> 24) & 0xff, (s->icr[0] >> 11) & 1, + (s->icr[0] >> 8) & 7, (s->icr[0] & 0xff), + (s->icr[0] >> 15) & 1); + break; + case 0x31: + s->icr[1] = val; + break; + case 0x32 ... 0x37: + { + int n = index - 0x32; + s->lvt[n] = val; + if (n == APIC_LVT_TIMER) { + apic_timer_update(s, qemu_get_clock_ns(vm_clock)); + } else if (n == APIC_LVT_LINT0 && apic_check_pic(s)) { + apic_update_irq(s); + } + } + break; + case 0x38: + s->initial_count = val; + s->initial_count_load_time = qemu_get_clock_ns(vm_clock); + apic_timer_update(s, s->initial_count_load_time); + break; + case 0x39: + break; + case 0x3e: + { + int v; + s->divide_conf = val & 0xb; + v = (s->divide_conf & 3) | ((s->divide_conf >> 1) & 4); + s->count_shift = (v + 1) & 7; + } + break; + default: + s->esr |= ESR_ILLEGAL_ADDRESS; + break; + } +} + +static void apic_pre_save(APICCommonState *s) +{ + apic_sync_vapic(s, SYNC_FROM_VAPIC); +} + +static void apic_post_load(APICCommonState *s) +{ + if (s->timer_expiry != -1) { + qemu_mod_timer(s->timer, s->timer_expiry); + } else { + qemu_del_timer(s->timer); + } +} + +static const MemoryRegionOps apic_io_ops = { + .old_mmio = { + .read = { apic_mem_readb, apic_mem_readw, apic_mem_readl, }, + .write = { apic_mem_writeb, apic_mem_writew, apic_mem_writel, }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void apic_init(APICCommonState *s) +{ + memory_region_init_io(&s->io_memory, &apic_io_ops, s, "apic-msi", + MSI_SPACE_SIZE); + + s->timer = qemu_new_timer_ns(vm_clock, apic_timer, s); + local_apics[s->idx] = s; + + msi_supported = true; +} + +static void apic_class_init(ObjectClass *klass, void *data) +{ + APICCommonClass *k = APIC_COMMON_CLASS(klass); + + k->init = apic_init; + k->set_base = apic_set_base; + k->set_tpr = apic_set_tpr; + k->get_tpr = apic_get_tpr; + k->vapic_base_update = apic_vapic_base_update; + k->external_nmi = apic_external_nmi; + k->pre_save = apic_pre_save; + k->post_load = apic_post_load; +} + +static const TypeInfo apic_info = { + .name = "apic", + .instance_size = sizeof(APICCommonState), + .parent = TYPE_APIC_COMMON, + .class_init = apic_class_init, +}; + +static void apic_register_types(void) +{ + type_register_static(&apic_info); +} + +type_init(apic_register_types) diff --git a/hw/intc/apic_common.c b/hw/intc/apic_common.c new file mode 100644 index 0000000000..e0ae07afd5 --- /dev/null +++ b/hw/intc/apic_common.c @@ -0,0 +1,402 @@ +/* + * APIC support - common bits of emulated and KVM kernel model + * + * Copyright (c) 2004-2005 Fabrice Bellard + * Copyright (c) 2011 Jan Kiszka, Siemens AG + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/> + */ +#include "hw/i386/apic.h" +#include "hw/i386/apic_internal.h" +#include "trace.h" +#include "sysemu/kvm.h" + +static int apic_irq_delivered; +bool apic_report_tpr_access; + +void cpu_set_apic_base(DeviceState *d, uint64_t val) +{ + trace_cpu_set_apic_base(val); + + if (d) { + APICCommonState *s = APIC_COMMON(d); + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + info->set_base(s, val); + } +} + +uint64_t cpu_get_apic_base(DeviceState *d) +{ + if (d) { + APICCommonState *s = APIC_COMMON(d); + trace_cpu_get_apic_base((uint64_t)s->apicbase); + return s->apicbase; + } else { + trace_cpu_get_apic_base(MSR_IA32_APICBASE_BSP); + return MSR_IA32_APICBASE_BSP; + } +} + +void cpu_set_apic_tpr(DeviceState *d, uint8_t val) +{ + APICCommonState *s; + APICCommonClass *info; + + if (!d) { + return; + } + + s = APIC_COMMON(d); + info = APIC_COMMON_GET_CLASS(s); + + info->set_tpr(s, val); +} + +uint8_t cpu_get_apic_tpr(DeviceState *d) +{ + APICCommonState *s; + APICCommonClass *info; + + if (!d) { + return 0; + } + + s = APIC_COMMON(d); + info = APIC_COMMON_GET_CLASS(s); + + return info->get_tpr(s); +} + +void apic_enable_tpr_access_reporting(DeviceState *d, bool enable) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + + apic_report_tpr_access = enable; + if (info->enable_tpr_reporting) { + info->enable_tpr_reporting(s, enable); + } +} + +void apic_enable_vapic(DeviceState *d, hwaddr paddr) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + + s->vapic_paddr = paddr; + info->vapic_base_update(s); +} + +void apic_handle_tpr_access_report(DeviceState *d, target_ulong ip, + TPRAccess access) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + + vapic_report_tpr_access(s->vapic, CPU(s->cpu), ip, access); +} + +void apic_report_irq_delivered(int delivered) +{ + apic_irq_delivered += delivered; + + trace_apic_report_irq_delivered(apic_irq_delivered); +} + +void apic_reset_irq_delivered(void) +{ + trace_apic_reset_irq_delivered(apic_irq_delivered); + + apic_irq_delivered = 0; +} + +int apic_get_irq_delivered(void) +{ + trace_apic_get_irq_delivered(apic_irq_delivered); + + return apic_irq_delivered; +} + +void apic_deliver_nmi(DeviceState *d) +{ + APICCommonState *s = APIC_COMMON(d); + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + + info->external_nmi(s); +} + +bool apic_next_timer(APICCommonState *s, int64_t current_time) +{ + int64_t d; + + /* We need to store the timer state separately to support APIC + * implementations that maintain a non-QEMU timer, e.g. inside the + * host kernel. This open-coded state allows us to migrate between + * both models. */ + s->timer_expiry = -1; + + if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_MASKED) { + return false; + } + + d = (current_time - s->initial_count_load_time) >> s->count_shift; + + if (s->lvt[APIC_LVT_TIMER] & APIC_LVT_TIMER_PERIODIC) { + if (!s->initial_count) { + return false; + } + d = ((d / ((uint64_t)s->initial_count + 1)) + 1) * + ((uint64_t)s->initial_count + 1); + } else { + if (d >= s->initial_count) { + return false; + } + d = (uint64_t)s->initial_count + 1; + } + s->next_time = s->initial_count_load_time + (d << s->count_shift); + s->timer_expiry = s->next_time; + return true; +} + +void apic_init_reset(DeviceState *d) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + int i; + + if (!s) { + return; + } + s->tpr = 0; + s->spurious_vec = 0xff; + s->log_dest = 0; + s->dest_mode = 0xf; + memset(s->isr, 0, sizeof(s->isr)); + memset(s->tmr, 0, sizeof(s->tmr)); + memset(s->irr, 0, sizeof(s->irr)); + for (i = 0; i < APIC_LVT_NB; i++) { + s->lvt[i] = APIC_LVT_MASKED; + } + s->esr = 0; + memset(s->icr, 0, sizeof(s->icr)); + s->divide_conf = 0; + s->count_shift = 0; + s->initial_count = 0; + s->initial_count_load_time = 0; + s->next_time = 0; + s->wait_for_sipi = 1; + + if (s->timer) { + qemu_del_timer(s->timer); + } + s->timer_expiry = -1; +} + +void apic_designate_bsp(DeviceState *d) +{ + if (d == NULL) { + return; + } + + APICCommonState *s = APIC_COMMON(d); + s->apicbase |= MSR_IA32_APICBASE_BSP; +} + +static void apic_reset_common(DeviceState *d) +{ + APICCommonState *s = DO_UPCAST(APICCommonState, busdev.qdev, d); + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + bool bsp; + + bsp = cpu_is_bsp(s->cpu); + s->apicbase = APIC_DEFAULT_ADDRESS | + (bsp ? MSR_IA32_APICBASE_BSP : 0) | MSR_IA32_APICBASE_ENABLE; + + s->vapic_paddr = 0; + info->vapic_base_update(s); + + apic_init_reset(d); + + if (bsp) { + /* + * LINT0 delivery mode on CPU #0 is set to ExtInt at initialization + * time typically by BIOS, so PIC interrupt can be delivered to the + * processor when local APIC is enabled. + */ + s->lvt[APIC_LVT_LINT0] = 0x700; + } +} + +/* This function is only used for old state version 1 and 2 */ +static int apic_load_old(QEMUFile *f, void *opaque, int version_id) +{ + APICCommonState *s = opaque; + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + int i; + + if (version_id > 2) { + return -EINVAL; + } + + /* XXX: what if the base changes? (registered memory regions) */ + qemu_get_be32s(f, &s->apicbase); + qemu_get_8s(f, &s->id); + qemu_get_8s(f, &s->arb_id); + qemu_get_8s(f, &s->tpr); + qemu_get_be32s(f, &s->spurious_vec); + qemu_get_8s(f, &s->log_dest); + qemu_get_8s(f, &s->dest_mode); + for (i = 0; i < 8; i++) { + qemu_get_be32s(f, &s->isr[i]); + qemu_get_be32s(f, &s->tmr[i]); + qemu_get_be32s(f, &s->irr[i]); + } + for (i = 0; i < APIC_LVT_NB; i++) { + qemu_get_be32s(f, &s->lvt[i]); + } + qemu_get_be32s(f, &s->esr); + qemu_get_be32s(f, &s->icr[0]); + qemu_get_be32s(f, &s->icr[1]); + qemu_get_be32s(f, &s->divide_conf); + s->count_shift = qemu_get_be32(f); + qemu_get_be32s(f, &s->initial_count); + s->initial_count_load_time = qemu_get_be64(f); + s->next_time = qemu_get_be64(f); + + if (version_id >= 2) { + s->timer_expiry = qemu_get_be64(f); + } + + if (info->post_load) { + info->post_load(s); + } + return 0; +} + +static int apic_init_common(SysBusDevice *dev) +{ + APICCommonState *s = APIC_COMMON(dev); + APICCommonClass *info; + static DeviceState *vapic; + static int apic_no; + + if (apic_no >= MAX_APICS) { + return -1; + } + s->idx = apic_no++; + + info = APIC_COMMON_GET_CLASS(s); + info->init(s); + + sysbus_init_mmio(dev, &s->io_memory); + + /* Note: We need at least 1M to map the VAPIC option ROM */ + if (!vapic && s->vapic_control & VAPIC_ENABLE_MASK && + ram_size >= 1024 * 1024) { + vapic = sysbus_create_simple("kvmvapic", -1, NULL); + } + s->vapic = vapic; + if (apic_report_tpr_access && info->enable_tpr_reporting) { + info->enable_tpr_reporting(s, true); + } + + return 0; +} + +static void apic_dispatch_pre_save(void *opaque) +{ + APICCommonState *s = APIC_COMMON(opaque); + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + + if (info->pre_save) { + info->pre_save(s); + } +} + +static int apic_dispatch_post_load(void *opaque, int version_id) +{ + APICCommonState *s = APIC_COMMON(opaque); + APICCommonClass *info = APIC_COMMON_GET_CLASS(s); + + if (info->post_load) { + info->post_load(s); + } + return 0; +} + +static const VMStateDescription vmstate_apic_common = { + .name = "apic", + .version_id = 3, + .minimum_version_id = 3, + .minimum_version_id_old = 1, + .load_state_old = apic_load_old, + .pre_save = apic_dispatch_pre_save, + .post_load = apic_dispatch_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT32(apicbase, APICCommonState), + VMSTATE_UINT8(id, APICCommonState), + VMSTATE_UINT8(arb_id, APICCommonState), + VMSTATE_UINT8(tpr, APICCommonState), + VMSTATE_UINT32(spurious_vec, APICCommonState), + VMSTATE_UINT8(log_dest, APICCommonState), + VMSTATE_UINT8(dest_mode, APICCommonState), + VMSTATE_UINT32_ARRAY(isr, APICCommonState, 8), + VMSTATE_UINT32_ARRAY(tmr, APICCommonState, 8), + VMSTATE_UINT32_ARRAY(irr, APICCommonState, 8), + VMSTATE_UINT32_ARRAY(lvt, APICCommonState, APIC_LVT_NB), + VMSTATE_UINT32(esr, APICCommonState), + VMSTATE_UINT32_ARRAY(icr, APICCommonState, 2), + VMSTATE_UINT32(divide_conf, APICCommonState), + VMSTATE_INT32(count_shift, APICCommonState), + VMSTATE_UINT32(initial_count, APICCommonState), + VMSTATE_INT64(initial_count_load_time, APICCommonState), + VMSTATE_INT64(next_time, APICCommonState), + VMSTATE_INT64(timer_expiry, + APICCommonState), /* open-coded timer state */ + VMSTATE_END_OF_LIST() + } +}; + +static Property apic_properties_common[] = { + DEFINE_PROP_UINT8("id", APICCommonState, id, -1), + DEFINE_PROP_BIT("vapic", APICCommonState, vapic_control, VAPIC_ENABLE_BIT, + true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void apic_common_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_apic_common; + dc->reset = apic_reset_common; + dc->no_user = 1; + dc->props = apic_properties_common; + sc->init = apic_init_common; +} + +static const TypeInfo apic_common_type = { + .name = TYPE_APIC_COMMON, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(APICCommonState), + .class_size = sizeof(APICCommonClass), + .class_init = apic_common_class_init, + .abstract = true, +}; + +static void register_types(void) +{ + type_register_static(&apic_common_type); +} + +type_init(register_types) diff --git a/hw/intc/arm_gic.c b/hw/intc/arm_gic.c new file mode 100644 index 0000000000..bcb072bbcf --- /dev/null +++ b/hw/intc/arm_gic.c @@ -0,0 +1,723 @@ +/* + * ARM Generic/Distributed Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +/* This file contains implementation code for the RealView EB interrupt + * controller, MPCore distributed interrupt controller and ARMv7-M + * Nested Vectored Interrupt Controller. + * It is compiled in two ways: + * (1) as a standalone file to produce a sysbus device which is a GIC + * that can be used on the realview board and as one of the builtin + * private peripherals for the ARM MP CPUs (11MPCore, A9, etc) + * (2) by being directly #included into armv7m_nvic.c to produce the + * armv7m_nvic device. + */ + +#include "hw/sysbus.h" +#include "hw/arm_gic_internal.h" + +//#define DEBUG_GIC + +#ifdef DEBUG_GIC +#define DPRINTF(fmt, ...) \ +do { fprintf(stderr, "arm_gic: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while(0) +#endif + +static const uint8_t gic_id[] = { + 0x90, 0x13, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 +}; + +#define NUM_CPU(s) ((s)->num_cpu) + +static inline int gic_get_current_cpu(GICState *s) +{ + if (s->num_cpu > 1) { + CPUState *cpu = ENV_GET_CPU(cpu_single_env); + return cpu->cpu_index; + } + return 0; +} + +/* TODO: Many places that call this routine could be optimized. */ +/* Update interrupt status after enabled or pending bits have been changed. */ +void gic_update(GICState *s) +{ + int best_irq; + int best_prio; + int irq; + int level; + int cpu; + int cm; + + for (cpu = 0; cpu < NUM_CPU(s); cpu++) { + cm = 1 << cpu; + s->current_pending[cpu] = 1023; + if (!s->enabled || !s->cpu_enabled[cpu]) { + qemu_irq_lower(s->parent_irq[cpu]); + return; + } + best_prio = 0x100; + best_irq = 1023; + for (irq = 0; irq < s->num_irq; irq++) { + if (GIC_TEST_ENABLED(irq, cm) && GIC_TEST_PENDING(irq, cm)) { + if (GIC_GET_PRIORITY(irq, cpu) < best_prio) { + best_prio = GIC_GET_PRIORITY(irq, cpu); + best_irq = irq; + } + } + } + level = 0; + if (best_prio < s->priority_mask[cpu]) { + s->current_pending[cpu] = best_irq; + if (best_prio < s->running_priority[cpu]) { + DPRINTF("Raised pending IRQ %d (cpu %d)\n", best_irq, cpu); + level = 1; + } + } + qemu_set_irq(s->parent_irq[cpu], level); + } +} + +void gic_set_pending_private(GICState *s, int cpu, int irq) +{ + int cm = 1 << cpu; + + if (GIC_TEST_PENDING(irq, cm)) + return; + + DPRINTF("Set %d pending cpu %d\n", irq, cpu); + GIC_SET_PENDING(irq, cm); + gic_update(s); +} + +/* Process a change in an external IRQ input. */ +static void gic_set_irq(void *opaque, int irq, int level) +{ + /* Meaning of the 'irq' parameter: + * [0..N-1] : external interrupts + * [N..N+31] : PPI (internal) interrupts for CPU 0 + * [N+32..N+63] : PPI (internal interrupts for CPU 1 + * ... + */ + GICState *s = (GICState *)opaque; + int cm, target; + if (irq < (s->num_irq - GIC_INTERNAL)) { + /* The first external input line is internal interrupt 32. */ + cm = ALL_CPU_MASK; + irq += GIC_INTERNAL; + target = GIC_TARGET(irq); + } else { + int cpu; + irq -= (s->num_irq - GIC_INTERNAL); + cpu = irq / GIC_INTERNAL; + irq %= GIC_INTERNAL; + cm = 1 << cpu; + target = cm; + } + + if (level == GIC_TEST_LEVEL(irq, cm)) { + return; + } + + if (level) { + GIC_SET_LEVEL(irq, cm); + if (GIC_TEST_TRIGGER(irq) || GIC_TEST_ENABLED(irq, cm)) { + DPRINTF("Set %d pending mask %x\n", irq, target); + GIC_SET_PENDING(irq, target); + } + } else { + GIC_CLEAR_LEVEL(irq, cm); + } + gic_update(s); +} + +static void gic_set_running_irq(GICState *s, int cpu, int irq) +{ + s->running_irq[cpu] = irq; + if (irq == 1023) { + s->running_priority[cpu] = 0x100; + } else { + s->running_priority[cpu] = GIC_GET_PRIORITY(irq, cpu); + } + gic_update(s); +} + +uint32_t gic_acknowledge_irq(GICState *s, int cpu) +{ + int new_irq; + int cm = 1 << cpu; + new_irq = s->current_pending[cpu]; + if (new_irq == 1023 + || GIC_GET_PRIORITY(new_irq, cpu) >= s->running_priority[cpu]) { + DPRINTF("ACK no pending IRQ\n"); + return 1023; + } + s->last_active[new_irq][cpu] = s->running_irq[cpu]; + /* Clear pending flags for both level and edge triggered interrupts. + Level triggered IRQs will be reasserted once they become inactive. */ + GIC_CLEAR_PENDING(new_irq, GIC_TEST_MODEL(new_irq) ? ALL_CPU_MASK : cm); + gic_set_running_irq(s, cpu, new_irq); + DPRINTF("ACK %d\n", new_irq); + return new_irq; +} + +void gic_complete_irq(GICState *s, int cpu, int irq) +{ + int update = 0; + int cm = 1 << cpu; + DPRINTF("EOI %d\n", irq); + if (irq >= s->num_irq) { + /* This handles two cases: + * 1. If software writes the ID of a spurious interrupt [ie 1023] + * to the GICC_EOIR, the GIC ignores that write. + * 2. If software writes the number of a non-existent interrupt + * this must be a subcase of "value written does not match the last + * valid interrupt value read from the Interrupt Acknowledge + * register" and so this is UNPREDICTABLE. We choose to ignore it. + */ + return; + } + if (s->running_irq[cpu] == 1023) + return; /* No active IRQ. */ + /* Mark level triggered interrupts as pending if they are still + raised. */ + if (!GIC_TEST_TRIGGER(irq) && GIC_TEST_ENABLED(irq, cm) + && GIC_TEST_LEVEL(irq, cm) && (GIC_TARGET(irq) & cm) != 0) { + DPRINTF("Set %d pending mask %x\n", irq, cm); + GIC_SET_PENDING(irq, cm); + update = 1; + } + if (irq != s->running_irq[cpu]) { + /* Complete an IRQ that is not currently running. */ + int tmp = s->running_irq[cpu]; + while (s->last_active[tmp][cpu] != 1023) { + if (s->last_active[tmp][cpu] == irq) { + s->last_active[tmp][cpu] = s->last_active[irq][cpu]; + break; + } + tmp = s->last_active[tmp][cpu]; + } + if (update) { + gic_update(s); + } + } else { + /* Complete the current running IRQ. */ + gic_set_running_irq(s, cpu, s->last_active[s->running_irq[cpu]][cpu]); + } +} + +static uint32_t gic_dist_readb(void *opaque, hwaddr offset) +{ + GICState *s = (GICState *)opaque; + uint32_t res; + int irq; + int i; + int cpu; + int cm; + int mask; + + cpu = gic_get_current_cpu(s); + cm = 1 << cpu; + if (offset < 0x100) { + if (offset == 0) + return s->enabled; + if (offset == 4) + return ((s->num_irq / 32) - 1) | ((NUM_CPU(s) - 1) << 5); + if (offset < 0x08) + return 0; + if (offset >= 0x80) { + /* Interrupt Security , RAZ/WI */ + return 0; + } + goto bad_reg; + } else if (offset < 0x200) { + /* Interrupt Set/Clear Enable. */ + if (offset < 0x180) + irq = (offset - 0x100) * 8; + else + irq = (offset - 0x180) * 8; + irq += GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + res = 0; + for (i = 0; i < 8; i++) { + if (GIC_TEST_ENABLED(irq + i, cm)) { + res |= (1 << i); + } + } + } else if (offset < 0x300) { + /* Interrupt Set/Clear Pending. */ + if (offset < 0x280) + irq = (offset - 0x200) * 8; + else + irq = (offset - 0x280) * 8; + irq += GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + res = 0; + mask = (irq < GIC_INTERNAL) ? cm : ALL_CPU_MASK; + for (i = 0; i < 8; i++) { + if (GIC_TEST_PENDING(irq + i, mask)) { + res |= (1 << i); + } + } + } else if (offset < 0x400) { + /* Interrupt Active. */ + irq = (offset - 0x300) * 8 + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + res = 0; + mask = (irq < GIC_INTERNAL) ? cm : ALL_CPU_MASK; + for (i = 0; i < 8; i++) { + if (GIC_TEST_ACTIVE(irq + i, mask)) { + res |= (1 << i); + } + } + } else if (offset < 0x800) { + /* Interrupt Priority. */ + irq = (offset - 0x400) + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + res = GIC_GET_PRIORITY(irq, cpu); + } else if (offset < 0xc00) { + /* Interrupt CPU Target. */ + if (s->num_cpu == 1 && s->revision != REV_11MPCORE) { + /* For uniprocessor GICs these RAZ/WI */ + res = 0; + } else { + irq = (offset - 0x800) + GIC_BASE_IRQ; + if (irq >= s->num_irq) { + goto bad_reg; + } + if (irq >= 29 && irq <= 31) { + res = cm; + } else { + res = GIC_TARGET(irq); + } + } + } else if (offset < 0xf00) { + /* Interrupt Configuration. */ + irq = (offset - 0xc00) * 2 + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + res = 0; + for (i = 0; i < 4; i++) { + if (GIC_TEST_MODEL(irq + i)) + res |= (1 << (i * 2)); + if (GIC_TEST_TRIGGER(irq + i)) + res |= (2 << (i * 2)); + } + } else if (offset < 0xfe0) { + goto bad_reg; + } else /* offset >= 0xfe0 */ { + if (offset & 3) { + res = 0; + } else { + res = gic_id[(offset - 0xfe0) >> 2]; + } + } + return res; +bad_reg: + qemu_log_mask(LOG_GUEST_ERROR, + "gic_dist_readb: Bad offset %x\n", (int)offset); + return 0; +} + +static uint32_t gic_dist_readw(void *opaque, hwaddr offset) +{ + uint32_t val; + val = gic_dist_readb(opaque, offset); + val |= gic_dist_readb(opaque, offset + 1) << 8; + return val; +} + +static uint32_t gic_dist_readl(void *opaque, hwaddr offset) +{ + uint32_t val; + val = gic_dist_readw(opaque, offset); + val |= gic_dist_readw(opaque, offset + 2) << 16; + return val; +} + +static void gic_dist_writeb(void *opaque, hwaddr offset, + uint32_t value) +{ + GICState *s = (GICState *)opaque; + int irq; + int i; + int cpu; + + cpu = gic_get_current_cpu(s); + if (offset < 0x100) { + if (offset == 0) { + s->enabled = (value & 1); + DPRINTF("Distribution %sabled\n", s->enabled ? "En" : "Dis"); + } else if (offset < 4) { + /* ignored. */ + } else if (offset >= 0x80) { + /* Interrupt Security Registers, RAZ/WI */ + } else { + goto bad_reg; + } + } else if (offset < 0x180) { + /* Interrupt Set Enable. */ + irq = (offset - 0x100) * 8 + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + if (irq < 16) + value = 0xff; + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + int mask = + (irq < GIC_INTERNAL) ? (1 << cpu) : GIC_TARGET(irq + i); + int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK; + + if (!GIC_TEST_ENABLED(irq + i, cm)) { + DPRINTF("Enabled IRQ %d\n", irq + i); + } + GIC_SET_ENABLED(irq + i, cm); + /* If a raised level triggered IRQ enabled then mark + is as pending. */ + if (GIC_TEST_LEVEL(irq + i, mask) + && !GIC_TEST_TRIGGER(irq + i)) { + DPRINTF("Set %d pending mask %x\n", irq + i, mask); + GIC_SET_PENDING(irq + i, mask); + } + } + } + } else if (offset < 0x200) { + /* Interrupt Clear Enable. */ + irq = (offset - 0x180) * 8 + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + if (irq < 16) + value = 0; + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + int cm = (irq < GIC_INTERNAL) ? (1 << cpu) : ALL_CPU_MASK; + + if (GIC_TEST_ENABLED(irq + i, cm)) { + DPRINTF("Disabled IRQ %d\n", irq + i); + } + GIC_CLEAR_ENABLED(irq + i, cm); + } + } + } else if (offset < 0x280) { + /* Interrupt Set Pending. */ + irq = (offset - 0x200) * 8 + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + if (irq < 16) + irq = 0; + + for (i = 0; i < 8; i++) { + if (value & (1 << i)) { + GIC_SET_PENDING(irq + i, GIC_TARGET(irq + i)); + } + } + } else if (offset < 0x300) { + /* Interrupt Clear Pending. */ + irq = (offset - 0x280) * 8 + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + for (i = 0; i < 8; i++) { + /* ??? This currently clears the pending bit for all CPUs, even + for per-CPU interrupts. It's unclear whether this is the + corect behavior. */ + if (value & (1 << i)) { + GIC_CLEAR_PENDING(irq + i, ALL_CPU_MASK); + } + } + } else if (offset < 0x400) { + /* Interrupt Active. */ + goto bad_reg; + } else if (offset < 0x800) { + /* Interrupt Priority. */ + irq = (offset - 0x400) + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + if (irq < GIC_INTERNAL) { + s->priority1[irq][cpu] = value; + } else { + s->priority2[irq - GIC_INTERNAL] = value; + } + } else if (offset < 0xc00) { + /* Interrupt CPU Target. RAZ/WI on uniprocessor GICs, with the + * annoying exception of the 11MPCore's GIC. + */ + if (s->num_cpu != 1 || s->revision == REV_11MPCORE) { + irq = (offset - 0x800) + GIC_BASE_IRQ; + if (irq >= s->num_irq) { + goto bad_reg; + } + if (irq < 29) { + value = 0; + } else if (irq < GIC_INTERNAL) { + value = ALL_CPU_MASK; + } + s->irq_target[irq] = value & ALL_CPU_MASK; + } + } else if (offset < 0xf00) { + /* Interrupt Configuration. */ + irq = (offset - 0xc00) * 4 + GIC_BASE_IRQ; + if (irq >= s->num_irq) + goto bad_reg; + if (irq < GIC_INTERNAL) + value |= 0xaa; + for (i = 0; i < 4; i++) { + if (value & (1 << (i * 2))) { + GIC_SET_MODEL(irq + i); + } else { + GIC_CLEAR_MODEL(irq + i); + } + if (value & (2 << (i * 2))) { + GIC_SET_TRIGGER(irq + i); + } else { + GIC_CLEAR_TRIGGER(irq + i); + } + } + } else { + /* 0xf00 is only handled for 32-bit writes. */ + goto bad_reg; + } + gic_update(s); + return; +bad_reg: + qemu_log_mask(LOG_GUEST_ERROR, + "gic_dist_writeb: Bad offset %x\n", (int)offset); +} + +static void gic_dist_writew(void *opaque, hwaddr offset, + uint32_t value) +{ + gic_dist_writeb(opaque, offset, value & 0xff); + gic_dist_writeb(opaque, offset + 1, value >> 8); +} + +static void gic_dist_writel(void *opaque, hwaddr offset, + uint32_t value) +{ + GICState *s = (GICState *)opaque; + if (offset == 0xf00) { + int cpu; + int irq; + int mask; + + cpu = gic_get_current_cpu(s); + irq = value & 0x3ff; + switch ((value >> 24) & 3) { + case 0: + mask = (value >> 16) & ALL_CPU_MASK; + break; + case 1: + mask = ALL_CPU_MASK ^ (1 << cpu); + break; + case 2: + mask = 1 << cpu; + break; + default: + DPRINTF("Bad Soft Int target filter\n"); + mask = ALL_CPU_MASK; + break; + } + GIC_SET_PENDING(irq, mask); + gic_update(s); + return; + } + gic_dist_writew(opaque, offset, value & 0xffff); + gic_dist_writew(opaque, offset + 2, value >> 16); +} + +static const MemoryRegionOps gic_dist_ops = { + .old_mmio = { + .read = { gic_dist_readb, gic_dist_readw, gic_dist_readl, }, + .write = { gic_dist_writeb, gic_dist_writew, gic_dist_writel, }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint32_t gic_cpu_read(GICState *s, int cpu, int offset) +{ + switch (offset) { + case 0x00: /* Control */ + return s->cpu_enabled[cpu]; + case 0x04: /* Priority mask */ + return s->priority_mask[cpu]; + case 0x08: /* Binary Point */ + /* ??? Not implemented. */ + return 0; + case 0x0c: /* Acknowledge */ + return gic_acknowledge_irq(s, cpu); + case 0x14: /* Running Priority */ + return s->running_priority[cpu]; + case 0x18: /* Highest Pending Interrupt */ + return s->current_pending[cpu]; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "gic_cpu_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void gic_cpu_write(GICState *s, int cpu, int offset, uint32_t value) +{ + switch (offset) { + case 0x00: /* Control */ + s->cpu_enabled[cpu] = (value & 1); + DPRINTF("CPU %d %sabled\n", cpu, s->cpu_enabled[cpu] ? "En" : "Dis"); + break; + case 0x04: /* Priority mask */ + s->priority_mask[cpu] = (value & 0xff); + break; + case 0x08: /* Binary Point */ + /* ??? Not implemented. */ + break; + case 0x10: /* End Of Interrupt */ + return gic_complete_irq(s, cpu, value & 0x3ff); + default: + qemu_log_mask(LOG_GUEST_ERROR, + "gic_cpu_write: Bad offset %x\n", (int)offset); + return; + } + gic_update(s); +} + +/* Wrappers to read/write the GIC CPU interface for the current CPU */ +static uint64_t gic_thiscpu_read(void *opaque, hwaddr addr, + unsigned size) +{ + GICState *s = (GICState *)opaque; + return gic_cpu_read(s, gic_get_current_cpu(s), addr); +} + +static void gic_thiscpu_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + GICState *s = (GICState *)opaque; + gic_cpu_write(s, gic_get_current_cpu(s), addr, value); +} + +/* Wrappers to read/write the GIC CPU interface for a specific CPU. + * These just decode the opaque pointer into GICState* + cpu id. + */ +static uint64_t gic_do_cpu_read(void *opaque, hwaddr addr, + unsigned size) +{ + GICState **backref = (GICState **)opaque; + GICState *s = *backref; + int id = (backref - s->backref); + return gic_cpu_read(s, id, addr); +} + +static void gic_do_cpu_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + GICState **backref = (GICState **)opaque; + GICState *s = *backref; + int id = (backref - s->backref); + gic_cpu_write(s, id, addr, value); +} + +static const MemoryRegionOps gic_thiscpu_ops = { + .read = gic_thiscpu_read, + .write = gic_thiscpu_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const MemoryRegionOps gic_cpu_ops = { + .read = gic_do_cpu_read, + .write = gic_do_cpu_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +void gic_init_irqs_and_distributor(GICState *s, int num_irq) +{ + int i; + + i = s->num_irq - GIC_INTERNAL; + /* For the GIC, also expose incoming GPIO lines for PPIs for each CPU. + * GPIO array layout is thus: + * [0..N-1] SPIs + * [N..N+31] PPIs for CPU 0 + * [N+32..N+63] PPIs for CPU 1 + * ... + */ + if (s->revision != REV_NVIC) { + i += (GIC_INTERNAL * s->num_cpu); + } + qdev_init_gpio_in(&s->busdev.qdev, gic_set_irq, i); + for (i = 0; i < NUM_CPU(s); i++) { + sysbus_init_irq(&s->busdev, &s->parent_irq[i]); + } + memory_region_init_io(&s->iomem, &gic_dist_ops, s, "gic_dist", 0x1000); +} + +static void arm_gic_realize(DeviceState *dev, Error **errp) +{ + /* Device instance realize function for the GIC sysbus device */ + int i; + GICState *s = ARM_GIC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + ARMGICClass *agc = ARM_GIC_GET_CLASS(s); + + agc->parent_realize(dev, errp); + if (error_is_set(errp)) { + return; + } + + gic_init_irqs_and_distributor(s, s->num_irq); + + /* Memory regions for the CPU interfaces (NVIC doesn't have these): + * a region for "CPU interface for this core", then a region for + * "CPU interface for core 0", "for core 1", ... + * NB that the memory region size of 0x100 applies for the 11MPCore + * and also cores following the GIC v1 spec (ie A9). + * GIC v2 defines a larger memory region (0x1000) so this will need + * to be extended when we implement A15. + */ + memory_region_init_io(&s->cpuiomem[0], &gic_thiscpu_ops, s, + "gic_cpu", 0x100); + for (i = 0; i < NUM_CPU(s); i++) { + s->backref[i] = s; + memory_region_init_io(&s->cpuiomem[i+1], &gic_cpu_ops, &s->backref[i], + "gic_cpu", 0x100); + } + /* Distributor */ + sysbus_init_mmio(sbd, &s->iomem); + /* cpu interfaces (one for "current cpu" plus one per cpu) */ + for (i = 0; i <= NUM_CPU(s); i++) { + sysbus_init_mmio(sbd, &s->cpuiomem[i]); + } +} + +static void arm_gic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ARMGICClass *agc = ARM_GIC_CLASS(klass); + + dc->no_user = 1; + agc->parent_realize = dc->realize; + dc->realize = arm_gic_realize; +} + +static const TypeInfo arm_gic_info = { + .name = TYPE_ARM_GIC, + .parent = TYPE_ARM_GIC_COMMON, + .instance_size = sizeof(GICState), + .class_init = arm_gic_class_init, + .class_size = sizeof(ARMGICClass), +}; + +static void arm_gic_register_types(void) +{ + type_register_static(&arm_gic_info); +} + +type_init(arm_gic_register_types) diff --git a/hw/intc/arm_gic_common.c b/hw/intc/arm_gic_common.c new file mode 100644 index 0000000000..71594f1f87 --- /dev/null +++ b/hw/intc/arm_gic_common.c @@ -0,0 +1,176 @@ +/* + * ARM GIC support - common bits of emulated and KVM kernel model + * + * Copyright (c) 2012 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/arm_gic_internal.h" + +static void gic_pre_save(void *opaque) +{ + GICState *s = (GICState *)opaque; + ARMGICCommonClass *c = ARM_GIC_COMMON_GET_CLASS(s); + + if (c->pre_save) { + c->pre_save(s); + } +} + +static int gic_post_load(void *opaque, int version_id) +{ + GICState *s = (GICState *)opaque; + ARMGICCommonClass *c = ARM_GIC_COMMON_GET_CLASS(s); + + if (c->post_load) { + c->post_load(s); + } + return 0; +} + +static const VMStateDescription vmstate_gic_irq_state = { + .name = "arm_gic_irq_state", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(enabled, gic_irq_state), + VMSTATE_UINT8(pending, gic_irq_state), + VMSTATE_UINT8(active, gic_irq_state), + VMSTATE_UINT8(level, gic_irq_state), + VMSTATE_BOOL(model, gic_irq_state), + VMSTATE_BOOL(trigger, gic_irq_state), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_gic = { + .name = "arm_gic", + .version_id = 4, + .minimum_version_id = 4, + .pre_save = gic_pre_save, + .post_load = gic_post_load, + .fields = (VMStateField[]) { + VMSTATE_BOOL(enabled, GICState), + VMSTATE_BOOL_ARRAY(cpu_enabled, GICState, NCPU), + VMSTATE_STRUCT_ARRAY(irq_state, GICState, GIC_MAXIRQ, 1, + vmstate_gic_irq_state, gic_irq_state), + VMSTATE_UINT8_ARRAY(irq_target, GICState, GIC_MAXIRQ), + VMSTATE_UINT8_2DARRAY(priority1, GICState, GIC_INTERNAL, NCPU), + VMSTATE_UINT8_ARRAY(priority2, GICState, GIC_MAXIRQ - GIC_INTERNAL), + VMSTATE_UINT16_2DARRAY(last_active, GICState, GIC_MAXIRQ, NCPU), + VMSTATE_UINT16_ARRAY(priority_mask, GICState, NCPU), + VMSTATE_UINT16_ARRAY(running_irq, GICState, NCPU), + VMSTATE_UINT16_ARRAY(running_priority, GICState, NCPU), + VMSTATE_UINT16_ARRAY(current_pending, GICState, NCPU), + VMSTATE_END_OF_LIST() + } +}; + +static void arm_gic_common_realize(DeviceState *dev, Error **errp) +{ + GICState *s = ARM_GIC_COMMON(dev); + int num_irq = s->num_irq; + + if (s->num_cpu > NCPU) { + error_setg(errp, "requested %u CPUs exceeds GIC maximum %d", + s->num_cpu, NCPU); + return; + } + s->num_irq += GIC_BASE_IRQ; + if (s->num_irq > GIC_MAXIRQ) { + error_setg(errp, + "requested %u interrupt lines exceeds GIC maximum %d", + num_irq, GIC_MAXIRQ); + return; + } + /* ITLinesNumber is represented as (N / 32) - 1 (see + * gic_dist_readb) so this is an implementation imposed + * restriction, not an architectural one: + */ + if (s->num_irq < 32 || (s->num_irq % 32)) { + error_setg(errp, + "%d interrupt lines unsupported: not divisible by 32", + num_irq); + return; + } +} + +static void arm_gic_common_reset(DeviceState *dev) +{ + GICState *s = FROM_SYSBUS(GICState, SYS_BUS_DEVICE(dev)); + int i; + memset(s->irq_state, 0, GIC_MAXIRQ * sizeof(gic_irq_state)); + for (i = 0 ; i < s->num_cpu; i++) { + if (s->revision == REV_11MPCORE) { + s->priority_mask[i] = 0xf0; + } else { + s->priority_mask[i] = 0; + } + s->current_pending[i] = 1023; + s->running_irq[i] = 1023; + s->running_priority[i] = 0x100; + s->cpu_enabled[i] = false; + } + for (i = 0; i < 16; i++) { + GIC_SET_ENABLED(i, ALL_CPU_MASK); + GIC_SET_TRIGGER(i); + } + if (s->num_cpu == 1) { + /* For uniprocessor GICs all interrupts always target the sole CPU */ + for (i = 0; i < GIC_MAXIRQ; i++) { + s->irq_target[i] = 1; + } + } + s->enabled = false; +} + +static Property arm_gic_common_properties[] = { + DEFINE_PROP_UINT32("num-cpu", GICState, num_cpu, 1), + DEFINE_PROP_UINT32("num-irq", GICState, num_irq, 32), + /* Revision can be 1 or 2 for GIC architecture specification + * versions 1 or 2, or 0 to indicate the legacy 11MPCore GIC. + * (Internally, 0xffffffff also indicates "not a GIC but an NVIC".) + */ + DEFINE_PROP_UINT32("revision", GICState, revision, 1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void arm_gic_common_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->reset = arm_gic_common_reset; + dc->realize = arm_gic_common_realize; + dc->props = arm_gic_common_properties; + dc->vmsd = &vmstate_gic; + dc->no_user = 1; +} + +static const TypeInfo arm_gic_common_type = { + .name = TYPE_ARM_GIC_COMMON, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(GICState), + .class_size = sizeof(ARMGICCommonClass), + .class_init = arm_gic_common_class_init, + .abstract = true, +}; + +static void register_types(void) +{ + type_register_static(&arm_gic_common_type); +} + +type_init(register_types) diff --git a/hw/intc/arm_gic_kvm.c b/hw/intc/arm_gic_kvm.c new file mode 100644 index 0000000000..22b40b4f84 --- /dev/null +++ b/hw/intc/arm_gic_kvm.c @@ -0,0 +1,167 @@ +/* + * ARM Generic Interrupt Controller using KVM in-kernel support + * + * Copyright (c) 2012 Linaro Limited + * Written by Peter Maydell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/sysbus.h" +#include "sysemu/kvm.h" +#include "kvm_arm.h" +#include "hw/arm_gic_internal.h" + +#define TYPE_KVM_ARM_GIC "kvm-arm-gic" +#define KVM_ARM_GIC(obj) \ + OBJECT_CHECK(GICState, (obj), TYPE_KVM_ARM_GIC) +#define KVM_ARM_GIC_CLASS(klass) \ + OBJECT_CLASS_CHECK(KVMARMGICClass, (klass), TYPE_KVM_ARM_GIC) +#define KVM_ARM_GIC_GET_CLASS(obj) \ + OBJECT_GET_CLASS(KVMARMGICClass, (obj), TYPE_KVM_ARM_GIC) + +typedef struct KVMARMGICClass { + ARMGICCommonClass parent_class; + DeviceRealize parent_realize; + void (*parent_reset)(DeviceState *dev); +} KVMARMGICClass; + +static void kvm_arm_gic_set_irq(void *opaque, int irq, int level) +{ + /* Meaning of the 'irq' parameter: + * [0..N-1] : external interrupts + * [N..N+31] : PPI (internal) interrupts for CPU 0 + * [N+32..N+63] : PPI (internal interrupts for CPU 1 + * ... + * Convert this to the kernel's desired encoding, which + * has separate fields in the irq number for type, + * CPU number and interrupt number. + */ + GICState *s = (GICState *)opaque; + int kvm_irq, irqtype, cpu; + + if (irq < (s->num_irq - GIC_INTERNAL)) { + /* External interrupt. The kernel numbers these like the GIC + * hardware, with external interrupt IDs starting after the + * internal ones. + */ + irqtype = KVM_ARM_IRQ_TYPE_SPI; + cpu = 0; + irq += GIC_INTERNAL; + } else { + /* Internal interrupt: decode into (cpu, interrupt id) */ + irqtype = KVM_ARM_IRQ_TYPE_PPI; + irq -= (s->num_irq - GIC_INTERNAL); + cpu = irq / GIC_INTERNAL; + irq %= GIC_INTERNAL; + } + kvm_irq = (irqtype << KVM_ARM_IRQ_TYPE_SHIFT) + | (cpu << KVM_ARM_IRQ_VCPU_SHIFT) | irq; + + kvm_set_irq(kvm_state, kvm_irq, !!level); +} + +static void kvm_arm_gic_put(GICState *s) +{ + /* TODO: there isn't currently a kernel interface to set the GIC state */ +} + +static void kvm_arm_gic_get(GICState *s) +{ + /* TODO: there isn't currently a kernel interface to get the GIC state */ +} + +static void kvm_arm_gic_reset(DeviceState *dev) +{ + GICState *s = ARM_GIC_COMMON(dev); + KVMARMGICClass *kgc = KVM_ARM_GIC_GET_CLASS(s); + + kgc->parent_reset(dev); + kvm_arm_gic_put(s); +} + +static void kvm_arm_gic_realize(DeviceState *dev, Error **errp) +{ + int i; + GICState *s = KVM_ARM_GIC(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + KVMARMGICClass *kgc = KVM_ARM_GIC_GET_CLASS(s); + + kgc->parent_realize(dev, errp); + if (error_is_set(errp)) { + return; + } + + i = s->num_irq - GIC_INTERNAL; + /* For the GIC, also expose incoming GPIO lines for PPIs for each CPU. + * GPIO array layout is thus: + * [0..N-1] SPIs + * [N..N+31] PPIs for CPU 0 + * [N+32..N+63] PPIs for CPU 1 + * ... + */ + i += (GIC_INTERNAL * s->num_cpu); + qdev_init_gpio_in(dev, kvm_arm_gic_set_irq, i); + /* We never use our outbound IRQ lines but provide them so that + * we maintain the same interface as the non-KVM GIC. + */ + for (i = 0; i < s->num_cpu; i++) { + sysbus_init_irq(sbd, &s->parent_irq[i]); + } + /* Distributor */ + memory_region_init_reservation(&s->iomem, "kvm-gic_dist", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + kvm_arm_register_device(&s->iomem, + (KVM_ARM_DEVICE_VGIC_V2 << KVM_ARM_DEVICE_ID_SHIFT) + | KVM_VGIC_V2_ADDR_TYPE_DIST); + /* CPU interface for current core. Unlike arm_gic, we don't + * provide the "interface for core #N" memory regions, because + * cores with a VGIC don't have those. + */ + memory_region_init_reservation(&s->cpuiomem[0], "kvm-gic_cpu", 0x1000); + sysbus_init_mmio(sbd, &s->cpuiomem[0]); + kvm_arm_register_device(&s->cpuiomem[0], + (KVM_ARM_DEVICE_VGIC_V2 << KVM_ARM_DEVICE_ID_SHIFT) + | KVM_VGIC_V2_ADDR_TYPE_CPU); +} + +static void kvm_arm_gic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ARMGICCommonClass *agcc = ARM_GIC_COMMON_CLASS(klass); + KVMARMGICClass *kgc = KVM_ARM_GIC_CLASS(klass); + + agcc->pre_save = kvm_arm_gic_get; + agcc->post_load = kvm_arm_gic_put; + kgc->parent_realize = dc->realize; + kgc->parent_reset = dc->reset; + dc->realize = kvm_arm_gic_realize; + dc->reset = kvm_arm_gic_reset; + dc->no_user = 1; +} + +static const TypeInfo kvm_arm_gic_info = { + .name = TYPE_KVM_ARM_GIC, + .parent = TYPE_ARM_GIC_COMMON, + .instance_size = sizeof(GICState), + .class_init = kvm_arm_gic_class_init, + .class_size = sizeof(KVMARMGICClass), +}; + +static void kvm_arm_gic_register_types(void) +{ + type_register_static(&kvm_arm_gic_info); +} + +type_init(kvm_arm_gic_register_types) diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c new file mode 100644 index 0000000000..75742601cb --- /dev/null +++ b/hw/intc/armv7m_nvic.c @@ -0,0 +1,553 @@ +/* + * ARM Nested Vectored Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + * + * The ARMv7M System controller is fairly tightly tied in with the + * NVIC. Much of that is also implemented here. + */ + +#include "hw/sysbus.h" +#include "qemu/timer.h" +#include "hw/arm.h" +#include "exec/address-spaces.h" +#include "hw/arm_gic_internal.h" + +typedef struct { + GICState gic; + struct { + uint32_t control; + uint32_t reload; + int64_t tick; + QEMUTimer *timer; + } systick; + MemoryRegion sysregmem; + MemoryRegion gic_iomem_alias; + MemoryRegion container; + uint32_t num_irq; +} nvic_state; + +#define TYPE_NVIC "armv7m_nvic" +/** + * NVICClass: + * @parent_reset: the parent class' reset handler. + * + * A model of the v7M NVIC and System Controller + */ +typedef struct NVICClass { + /*< private >*/ + ARMGICClass parent_class; + /*< public >*/ + DeviceRealize parent_realize; + void (*parent_reset)(DeviceState *dev); +} NVICClass; + +#define NVIC_CLASS(klass) \ + OBJECT_CLASS_CHECK(NVICClass, (klass), TYPE_NVIC) +#define NVIC_GET_CLASS(obj) \ + OBJECT_GET_CLASS(NVICClass, (obj), TYPE_NVIC) +#define NVIC(obj) \ + OBJECT_CHECK(nvic_state, (obj), TYPE_NVIC) + +static const uint8_t nvic_id[] = { + 0x00, 0xb0, 0x1b, 0x00, 0x0d, 0xe0, 0x05, 0xb1 +}; + +/* qemu timers run at 1GHz. We want something closer to 1MHz. */ +#define SYSTICK_SCALE 1000ULL + +#define SYSTICK_ENABLE (1 << 0) +#define SYSTICK_TICKINT (1 << 1) +#define SYSTICK_CLKSOURCE (1 << 2) +#define SYSTICK_COUNTFLAG (1 << 16) + +int system_clock_scale; + +/* Conversion factor from qemu timer to SysTick frequencies. */ +static inline int64_t systick_scale(nvic_state *s) +{ + if (s->systick.control & SYSTICK_CLKSOURCE) + return system_clock_scale; + else + return 1000; +} + +static void systick_reload(nvic_state *s, int reset) +{ + if (reset) + s->systick.tick = qemu_get_clock_ns(vm_clock); + s->systick.tick += (s->systick.reload + 1) * systick_scale(s); + qemu_mod_timer(s->systick.timer, s->systick.tick); +} + +static void systick_timer_tick(void * opaque) +{ + nvic_state *s = (nvic_state *)opaque; + s->systick.control |= SYSTICK_COUNTFLAG; + if (s->systick.control & SYSTICK_TICKINT) { + /* Trigger the interrupt. */ + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + } + if (s->systick.reload == 0) { + s->systick.control &= ~SYSTICK_ENABLE; + } else { + systick_reload(s, 0); + } +} + +static void systick_reset(nvic_state *s) +{ + s->systick.control = 0; + s->systick.reload = 0; + s->systick.tick = 0; + qemu_del_timer(s->systick.timer); +} + +/* The external routines use the hardware vector numbering, ie. the first + IRQ is #16. The internal GIC routines use #32 as the first IRQ. */ +void armv7m_nvic_set_pending(void *opaque, int irq) +{ + nvic_state *s = (nvic_state *)opaque; + if (irq >= 16) + irq += 16; + gic_set_pending_private(&s->gic, 0, irq); +} + +/* Make pending IRQ active. */ +int armv7m_nvic_acknowledge_irq(void *opaque) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t irq; + + irq = gic_acknowledge_irq(&s->gic, 0); + if (irq == 1023) + hw_error("Interrupt but no vector\n"); + if (irq >= 32) + irq -= 16; + return irq; +} + +void armv7m_nvic_complete_irq(void *opaque, int irq) +{ + nvic_state *s = (nvic_state *)opaque; + if (irq >= 16) + irq += 16; + gic_complete_irq(&s->gic, 0, irq); +} + +static uint32_t nvic_readl(nvic_state *s, uint32_t offset) +{ + uint32_t val; + int irq; + + switch (offset) { + case 4: /* Interrupt Control Type. */ + return (s->num_irq / 32) - 1; + case 0x10: /* SysTick Control and Status. */ + val = s->systick.control; + s->systick.control &= ~SYSTICK_COUNTFLAG; + return val; + case 0x14: /* SysTick Reload Value. */ + return s->systick.reload; + case 0x18: /* SysTick Current Value. */ + { + int64_t t; + if ((s->systick.control & SYSTICK_ENABLE) == 0) + return 0; + t = qemu_get_clock_ns(vm_clock); + if (t >= s->systick.tick) + return 0; + val = ((s->systick.tick - (t + 1)) / systick_scale(s)) + 1; + /* The interrupt in triggered when the timer reaches zero. + However the counter is not reloaded until the next clock + tick. This is a hack to return zero during the first tick. */ + if (val > s->systick.reload) + val = 0; + return val; + } + case 0x1c: /* SysTick Calibration Value. */ + return 10000; + case 0xd00: /* CPUID Base. */ + return cpu_single_env->cp15.c0_cpuid; + case 0xd04: /* Interrypt Control State. */ + /* VECTACTIVE */ + val = s->gic.running_irq[0]; + if (val == 1023) { + val = 0; + } else if (val >= 32) { + val -= 16; + } + /* RETTOBASE */ + if (s->gic.running_irq[0] == 1023 + || s->gic.last_active[s->gic.running_irq[0]][0] == 1023) { + val |= (1 << 11); + } + /* VECTPENDING */ + if (s->gic.current_pending[0] != 1023) + val |= (s->gic.current_pending[0] << 12); + /* ISRPENDING */ + for (irq = 32; irq < s->num_irq; irq++) { + if (s->gic.irq_state[irq].pending) { + val |= (1 << 22); + break; + } + } + /* PENDSTSET */ + if (s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending) + val |= (1 << 26); + /* PENDSVSET */ + if (s->gic.irq_state[ARMV7M_EXCP_PENDSV].pending) + val |= (1 << 28); + /* NMIPENDSET */ + if (s->gic.irq_state[ARMV7M_EXCP_NMI].pending) + val |= (1 << 31); + return val; + case 0xd08: /* Vector Table Offset. */ + return cpu_single_env->v7m.vecbase; + case 0xd0c: /* Application Interrupt/Reset Control. */ + return 0xfa05000; + case 0xd10: /* System Control. */ + /* TODO: Implement SLEEPONEXIT. */ + return 0; + case 0xd14: /* Configuration Control. */ + /* TODO: Implement Configuration Control bits. */ + return 0; + case 0xd24: /* System Handler Status. */ + val = 0; + if (s->gic.irq_state[ARMV7M_EXCP_MEM].active) val |= (1 << 0); + if (s->gic.irq_state[ARMV7M_EXCP_BUS].active) val |= (1 << 1); + if (s->gic.irq_state[ARMV7M_EXCP_USAGE].active) val |= (1 << 3); + if (s->gic.irq_state[ARMV7M_EXCP_SVC].active) val |= (1 << 7); + if (s->gic.irq_state[ARMV7M_EXCP_DEBUG].active) val |= (1 << 8); + if (s->gic.irq_state[ARMV7M_EXCP_PENDSV].active) val |= (1 << 10); + if (s->gic.irq_state[ARMV7M_EXCP_SYSTICK].active) val |= (1 << 11); + if (s->gic.irq_state[ARMV7M_EXCP_USAGE].pending) val |= (1 << 12); + if (s->gic.irq_state[ARMV7M_EXCP_MEM].pending) val |= (1 << 13); + if (s->gic.irq_state[ARMV7M_EXCP_BUS].pending) val |= (1 << 14); + if (s->gic.irq_state[ARMV7M_EXCP_SVC].pending) val |= (1 << 15); + if (s->gic.irq_state[ARMV7M_EXCP_MEM].enabled) val |= (1 << 16); + if (s->gic.irq_state[ARMV7M_EXCP_BUS].enabled) val |= (1 << 17); + if (s->gic.irq_state[ARMV7M_EXCP_USAGE].enabled) val |= (1 << 18); + return val; + case 0xd28: /* Configurable Fault Status. */ + /* TODO: Implement Fault Status. */ + qemu_log_mask(LOG_UNIMP, "Configurable Fault Status unimplemented\n"); + return 0; + case 0xd2c: /* Hard Fault Status. */ + case 0xd30: /* Debug Fault Status. */ + case 0xd34: /* Mem Manage Address. */ + case 0xd38: /* Bus Fault Address. */ + case 0xd3c: /* Aux Fault Status. */ + /* TODO: Implement fault status registers. */ + qemu_log_mask(LOG_UNIMP, "Fault status registers unimplemented\n"); + return 0; + case 0xd40: /* PFR0. */ + return 0x00000030; + case 0xd44: /* PRF1. */ + return 0x00000200; + case 0xd48: /* DFR0. */ + return 0x00100000; + case 0xd4c: /* AFR0. */ + return 0x00000000; + case 0xd50: /* MMFR0. */ + return 0x00000030; + case 0xd54: /* MMFR1. */ + return 0x00000000; + case 0xd58: /* MMFR2. */ + return 0x00000000; + case 0xd5c: /* MMFR3. */ + return 0x00000000; + case 0xd60: /* ISAR0. */ + return 0x01141110; + case 0xd64: /* ISAR1. */ + return 0x02111000; + case 0xd68: /* ISAR2. */ + return 0x21112231; + case 0xd6c: /* ISAR3. */ + return 0x01111110; + case 0xd70: /* ISAR4. */ + return 0x01310102; + /* TODO: Implement debug registers. */ + default: + qemu_log_mask(LOG_GUEST_ERROR, "NVIC: Bad read offset 0x%x\n", offset); + return 0; + } +} + +static void nvic_writel(nvic_state *s, uint32_t offset, uint32_t value) +{ + uint32_t oldval; + switch (offset) { + case 0x10: /* SysTick Control and Status. */ + oldval = s->systick.control; + s->systick.control &= 0xfffffff8; + s->systick.control |= value & 7; + if ((oldval ^ value) & SYSTICK_ENABLE) { + int64_t now = qemu_get_clock_ns(vm_clock); + if (value & SYSTICK_ENABLE) { + if (s->systick.tick) { + s->systick.tick += now; + qemu_mod_timer(s->systick.timer, s->systick.tick); + } else { + systick_reload(s, 1); + } + } else { + qemu_del_timer(s->systick.timer); + s->systick.tick -= now; + if (s->systick.tick < 0) + s->systick.tick = 0; + } + } else if ((oldval ^ value) & SYSTICK_CLKSOURCE) { + /* This is a hack. Force the timer to be reloaded + when the reference clock is changed. */ + systick_reload(s, 1); + } + break; + case 0x14: /* SysTick Reload Value. */ + s->systick.reload = value; + break; + case 0x18: /* SysTick Current Value. Writes reload the timer. */ + systick_reload(s, 1); + s->systick.control &= ~SYSTICK_COUNTFLAG; + break; + case 0xd04: /* Interrupt Control State. */ + if (value & (1 << 31)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_NMI); + } + if (value & (1 << 28)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_PENDSV); + } else if (value & (1 << 27)) { + s->gic.irq_state[ARMV7M_EXCP_PENDSV].pending = 0; + gic_update(&s->gic); + } + if (value & (1 << 26)) { + armv7m_nvic_set_pending(s, ARMV7M_EXCP_SYSTICK); + } else if (value & (1 << 25)) { + s->gic.irq_state[ARMV7M_EXCP_SYSTICK].pending = 0; + gic_update(&s->gic); + } + break; + case 0xd08: /* Vector Table Offset. */ + cpu_single_env->v7m.vecbase = value & 0xffffff80; + break; + case 0xd0c: /* Application Interrupt/Reset Control. */ + if ((value >> 16) == 0x05fa) { + if (value & 2) { + qemu_log_mask(LOG_UNIMP, "VECTCLRACTIVE unimplemented\n"); + } + if (value & 5) { + qemu_log_mask(LOG_UNIMP, "AIRCR system reset unimplemented\n"); + } + } + break; + case 0xd10: /* System Control. */ + case 0xd14: /* Configuration Control. */ + /* TODO: Implement control registers. */ + qemu_log_mask(LOG_UNIMP, "NVIC: SCR and CCR unimplemented\n"); + break; + case 0xd24: /* System Handler Control. */ + /* TODO: Real hardware allows you to set/clear the active bits + under some circumstances. We don't implement this. */ + s->gic.irq_state[ARMV7M_EXCP_MEM].enabled = (value & (1 << 16)) != 0; + s->gic.irq_state[ARMV7M_EXCP_BUS].enabled = (value & (1 << 17)) != 0; + s->gic.irq_state[ARMV7M_EXCP_USAGE].enabled = (value & (1 << 18)) != 0; + break; + case 0xd28: /* Configurable Fault Status. */ + case 0xd2c: /* Hard Fault Status. */ + case 0xd30: /* Debug Fault Status. */ + case 0xd34: /* Mem Manage Address. */ + case 0xd38: /* Bus Fault Address. */ + case 0xd3c: /* Aux Fault Status. */ + qemu_log_mask(LOG_UNIMP, + "NVIC: fault status registers unimplemented\n"); + break; + case 0xf00: /* Software Triggered Interrupt Register */ + if ((value & 0x1ff) < s->num_irq) { + gic_set_pending_private(&s->gic, 0, value & 0x1ff); + } + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "NVIC: Bad write offset 0x%x\n", offset); + } +} + +static uint64_t nvic_sysreg_read(void *opaque, hwaddr addr, + unsigned size) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t offset = addr; + int i; + uint32_t val; + + switch (offset) { + case 0xd18 ... 0xd23: /* System Handler Priority. */ + val = 0; + for (i = 0; i < size; i++) { + val |= s->gic.priority1[(offset - 0xd14) + i][0] << (i * 8); + } + return val; + case 0xfe0 ... 0xfff: /* ID. */ + if (offset & 3) { + return 0; + } + return nvic_id[(offset - 0xfe0) >> 2]; + } + if (size == 4) { + return nvic_readl(s, offset); + } + qemu_log_mask(LOG_GUEST_ERROR, + "NVIC: Bad read of size %d at offset 0x%x\n", size, offset); + return 0; +} + +static void nvic_sysreg_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + nvic_state *s = (nvic_state *)opaque; + uint32_t offset = addr; + int i; + + switch (offset) { + case 0xd18 ... 0xd23: /* System Handler Priority. */ + for (i = 0; i < size; i++) { + s->gic.priority1[(offset - 0xd14) + i][0] = + (value >> (i * 8)) & 0xff; + } + gic_update(&s->gic); + return; + } + if (size == 4) { + nvic_writel(s, offset, value); + return; + } + qemu_log_mask(LOG_GUEST_ERROR, + "NVIC: Bad write of size %d at offset 0x%x\n", size, offset); +} + +static const MemoryRegionOps nvic_sysreg_ops = { + .read = nvic_sysreg_read, + .write = nvic_sysreg_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_nvic = { + .name = "armv7m_nvic", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(systick.control, nvic_state), + VMSTATE_UINT32(systick.reload, nvic_state), + VMSTATE_INT64(systick.tick, nvic_state), + VMSTATE_TIMER(systick.timer, nvic_state), + VMSTATE_END_OF_LIST() + } +}; + +static void armv7m_nvic_reset(DeviceState *dev) +{ + nvic_state *s = NVIC(dev); + NVICClass *nc = NVIC_GET_CLASS(s); + nc->parent_reset(dev); + /* Common GIC reset resets to disabled; the NVIC doesn't have + * per-CPU interfaces so mark our non-existent CPU interface + * as enabled by default, and with a priority mask which allows + * all interrupts through. + */ + s->gic.cpu_enabled[0] = true; + s->gic.priority_mask[0] = 0x100; + /* The NVIC as a whole is always enabled. */ + s->gic.enabled = true; + systick_reset(s); +} + +static void armv7m_nvic_realize(DeviceState *dev, Error **errp) +{ + nvic_state *s = NVIC(dev); + NVICClass *nc = NVIC_GET_CLASS(s); + + /* The NVIC always has only one CPU */ + s->gic.num_cpu = 1; + /* Tell the common code we're an NVIC */ + s->gic.revision = 0xffffffff; + s->num_irq = s->gic.num_irq; + nc->parent_realize(dev, errp); + if (error_is_set(errp)) { + return; + } + gic_init_irqs_and_distributor(&s->gic, s->num_irq); + /* The NVIC and system controller register area looks like this: + * 0..0xff : system control registers, including systick + * 0x100..0xcff : GIC-like registers + * 0xd00..0xfff : system control registers + * We use overlaying to put the GIC like registers + * over the top of the system control register region. + */ + memory_region_init(&s->container, "nvic", 0x1000); + /* The system register region goes at the bottom of the priority + * stack as it covers the whole page. + */ + memory_region_init_io(&s->sysregmem, &nvic_sysreg_ops, s, + "nvic_sysregs", 0x1000); + memory_region_add_subregion(&s->container, 0, &s->sysregmem); + /* Alias the GIC region so we can get only the section of it + * we need, and layer it on top of the system register region. + */ + memory_region_init_alias(&s->gic_iomem_alias, "nvic-gic", &s->gic.iomem, + 0x100, 0xc00); + memory_region_add_subregion_overlap(&s->container, 0x100, + &s->gic_iomem_alias, 1); + /* Map the whole thing into system memory at the location required + * by the v7M architecture. + */ + memory_region_add_subregion(get_system_memory(), 0xe000e000, &s->container); + s->systick.timer = qemu_new_timer_ns(vm_clock, systick_timer_tick, s); +} + +static void armv7m_nvic_instance_init(Object *obj) +{ + /* We have a different default value for the num-irq property + * than our superclass. This function runs after qdev init + * has set the defaults from the Property array and before + * any user-specified property setting, so just modify the + * value in the GICState struct. + */ + GICState *s = ARM_GIC_COMMON(obj); + /* The ARM v7m may have anything from 0 to 496 external interrupt + * IRQ lines. We default to 64. Other boards may differ and should + * set the num-irq property appropriately. + */ + s->num_irq = 64; +} + +static void armv7m_nvic_class_init(ObjectClass *klass, void *data) +{ + NVICClass *nc = NVIC_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + nc->parent_reset = dc->reset; + nc->parent_realize = dc->realize; + dc->vmsd = &vmstate_nvic; + dc->reset = armv7m_nvic_reset; + dc->realize = armv7m_nvic_realize; +} + +static const TypeInfo armv7m_nvic_info = { + .name = TYPE_NVIC, + .parent = TYPE_ARM_GIC_COMMON, + .instance_init = armv7m_nvic_instance_init, + .instance_size = sizeof(nvic_state), + .class_init = armv7m_nvic_class_init, + .class_size = sizeof(NVICClass), +}; + +static void armv7m_nvic_register_types(void) +{ + type_register_static(&armv7m_nvic_info); +} + +type_init(armv7m_nvic_register_types) diff --git a/hw/intc/etraxfs_pic.c b/hw/intc/etraxfs_pic.c new file mode 100644 index 0000000000..635103c001 --- /dev/null +++ b/hw/intc/etraxfs_pic.c @@ -0,0 +1,180 @@ +/* + * QEMU ETRAX Interrupt Controller. + * + * Copyright (c) 2008 Edgar E. Iglesias, Axis Communications AB. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sysbus.h" +#include "hw/hw.h" +//#include "pc.h" +//#include "etraxfs.h" + +#define D(x) + +#define R_RW_MASK 0 +#define R_R_VECT 1 +#define R_R_MASKED_VECT 2 +#define R_R_NMI 3 +#define R_R_GURU 4 +#define R_MAX 5 + +struct etrax_pic +{ + SysBusDevice busdev; + MemoryRegion mmio; + void *interrupt_vector; + qemu_irq parent_irq; + qemu_irq parent_nmi; + uint32_t regs[R_MAX]; +}; + +static void pic_update(struct etrax_pic *fs) +{ + uint32_t vector = 0; + int i; + + fs->regs[R_R_MASKED_VECT] = fs->regs[R_R_VECT] & fs->regs[R_RW_MASK]; + + /* The ETRAX interrupt controller signals interrupts to the core + through an interrupt request wire and an irq vector bus. If + multiple interrupts are simultaneously active it chooses vector + 0x30 and lets the sw choose the priorities. */ + if (fs->regs[R_R_MASKED_VECT]) { + uint32_t mv = fs->regs[R_R_MASKED_VECT]; + for (i = 0; i < 31; i++) { + if (mv & 1) { + vector = 0x31 + i; + /* Check for multiple interrupts. */ + if (mv > 1) + vector = 0x30; + break; + } + mv >>= 1; + } + } + + if (fs->interrupt_vector) { + /* hack alert: ptr property */ + *(uint32_t*)(fs->interrupt_vector) = vector; + } + qemu_set_irq(fs->parent_irq, !!vector); +} + +static uint64_t +pic_read(void *opaque, hwaddr addr, unsigned int size) +{ + struct etrax_pic *fs = opaque; + uint32_t rval; + + rval = fs->regs[addr >> 2]; + D(printf("%s %x=%x\n", __func__, addr, rval)); + return rval; +} + +static void pic_write(void *opaque, hwaddr addr, + uint64_t value, unsigned int size) +{ + struct etrax_pic *fs = opaque; + D(printf("%s addr=%x val=%x\n", __func__, addr, value)); + + if (addr == R_RW_MASK) { + fs->regs[R_RW_MASK] = value; + pic_update(fs); + } +} + +static const MemoryRegionOps pic_ops = { + .read = pic_read, + .write = pic_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4 + } +}; + +static void nmi_handler(void *opaque, int irq, int level) +{ + struct etrax_pic *fs = (void *)opaque; + uint32_t mask; + + mask = 1 << irq; + if (level) + fs->regs[R_R_NMI] |= mask; + else + fs->regs[R_R_NMI] &= ~mask; + + qemu_set_irq(fs->parent_nmi, !!fs->regs[R_R_NMI]); +} + +static void irq_handler(void *opaque, int irq, int level) +{ + struct etrax_pic *fs = (void *)opaque; + + if (irq >= 30) + return nmi_handler(opaque, irq, level); + + irq -= 1; + fs->regs[R_R_VECT] &= ~(1 << irq); + fs->regs[R_R_VECT] |= (!!level << irq); + pic_update(fs); +} + +static int etraxfs_pic_init(SysBusDevice *dev) +{ + struct etrax_pic *s = FROM_SYSBUS(typeof (*s), dev); + + qdev_init_gpio_in(&dev->qdev, irq_handler, 32); + sysbus_init_irq(dev, &s->parent_irq); + sysbus_init_irq(dev, &s->parent_nmi); + + memory_region_init_io(&s->mmio, &pic_ops, s, "etraxfs-pic", R_MAX * 4); + sysbus_init_mmio(dev, &s->mmio); + return 0; +} + +static Property etraxfs_pic_properties[] = { + DEFINE_PROP_PTR("interrupt_vector", struct etrax_pic, interrupt_vector), + DEFINE_PROP_END_OF_LIST(), +}; + +static void etraxfs_pic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = etraxfs_pic_init; + dc->props = etraxfs_pic_properties; +} + +static const TypeInfo etraxfs_pic_info = { + .name = "etraxfs,pic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct etrax_pic), + .class_init = etraxfs_pic_class_init, +}; + +static void etraxfs_pic_register_types(void) +{ + type_register_static(&etraxfs_pic_info); +} + +type_init(etraxfs_pic_register_types) diff --git a/hw/intc/exynos4210_combiner.c b/hw/intc/exynos4210_combiner.c new file mode 100644 index 0000000000..6874287302 --- /dev/null +++ b/hw/intc/exynos4210_combiner.c @@ -0,0 +1,455 @@ +/* + * Samsung exynos4210 Interrupt Combiner + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * + * Evgeny Voevodin <e.voevodin@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +/* + * Exynos4210 Combiner represents an OR gate for SOC's IRQ lines. It combines + * IRQ sources into groups and provides signal output to GIC from each group. It + * is driven by common mask and enable/disable logic. Take a note that not all + * IRQs are passed to GIC through Combiner. + */ + +#include "hw/sysbus.h" + +#include "hw/arm/exynos4210.h" + +//#define DEBUG_COMBINER + +#ifdef DEBUG_COMBINER +#define DPRINTF(fmt, ...) \ + do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \ + ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#define IIC_NGRP 64 /* Internal Interrupt Combiner + Groups number */ +#define IIC_NIRQ (IIC_NGRP * 8)/* Internal Interrupt Combiner + Interrupts number */ +#define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */ +#define IIC_REGSET_SIZE 0x41 + +/* + * State for each output signal of internal combiner + */ +typedef struct CombinerGroupState { + uint8_t src_mask; /* 1 - source enabled, 0 - disabled */ + uint8_t src_pending; /* Pending source interrupts before masking */ +} CombinerGroupState; + +typedef struct Exynos4210CombinerState { + SysBusDevice busdev; + MemoryRegion iomem; + + struct CombinerGroupState group[IIC_NGRP]; + uint32_t reg_set[IIC_REGSET_SIZE]; + uint32_t icipsr[2]; + uint32_t external; /* 1 means that this combiner is external */ + + qemu_irq output_irq[IIC_NGRP]; +} Exynos4210CombinerState; + +static const VMStateDescription vmstate_exynos4210_combiner_group_state = { + .name = "exynos4210.combiner.groupstate", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(src_mask, CombinerGroupState), + VMSTATE_UINT8(src_pending, CombinerGroupState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_exynos4210_combiner = { + .name = "exynos4210.combiner", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0, + vmstate_exynos4210_combiner_group_state, CombinerGroupState), + VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState, + IIC_REGSET_SIZE), + VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2), + VMSTATE_UINT32(external, Exynos4210CombinerState), + VMSTATE_END_OF_LIST() + } +}; + +/* + * Get Combiner input GPIO into irqs structure + */ +void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev, + int ext) +{ + int n; + int bit; + int max; + qemu_irq *irq; + + max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ : + EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; + irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq; + + /* + * Some IRQs of Int/External Combiner are going to two Combiners groups, + * so let split them. + */ + for (n = 0; n < max; n++) { + + bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n); + + switch (n) { + /* MDNIE_LCD1 INTG1 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]); + continue; + + /* TMU INTG3 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]); + continue; + + /* LCD1 INTG12 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]); + continue; + + /* Multi-Core Timer INTG12 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + + /* Multi-Core Timer INTG35 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + + /* Multi-Core Timer INTG51 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + + /* Multi-Core Timer INTG53 */ + case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ... + EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8): + irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n), + irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]); + continue; + } + + irq[n] = qdev_get_gpio_in(dev, n); + } +} + +static uint64_t +exynos4210_combiner_read(void *opaque, hwaddr offset, unsigned size) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and + get a start of corresponding group quad */ + uint32_t grp_quad_base_n; /* Base of group quad */ + uint32_t reg_n; /* Register number inside the quad */ + uint32_t val; + + req_quad_base_n = offset >> 4; + grp_quad_base_n = req_quad_base_n << 2; + reg_n = (offset - (req_quad_base_n << 4)) >> 2; + + if (req_quad_base_n >= IIC_NGRP) { + /* Read of ICIPSR register */ + return s->icipsr[reg_n]; + } + + val = 0; + + switch (reg_n) { + /* IISTR */ + case 2: + val |= s->group[grp_quad_base_n].src_pending; + val |= s->group[grp_quad_base_n + 1].src_pending << 8; + val |= s->group[grp_quad_base_n + 2].src_pending << 16; + val |= s->group[grp_quad_base_n + 3].src_pending << 24; + break; + /* IIMSR */ + case 3: + val |= s->group[grp_quad_base_n].src_mask & + s->group[grp_quad_base_n].src_pending; + val |= (s->group[grp_quad_base_n + 1].src_mask & + s->group[grp_quad_base_n + 1].src_pending) << 8; + val |= (s->group[grp_quad_base_n + 2].src_mask & + s->group[grp_quad_base_n + 2].src_pending) << 16; + val |= (s->group[grp_quad_base_n + 3].src_mask & + s->group[grp_quad_base_n + 3].src_pending) << 24; + break; + default: + if (offset >> 2 >= IIC_REGSET_SIZE) { + hw_error("exynos4210.combiner: overflow of reg_set by 0x" + TARGET_FMT_plx "offset\n", offset); + } + val = s->reg_set[offset >> 2]; + return 0; + } + return val; +} + +static void exynos4210_combiner_update(void *opaque, uint8_t group_n) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + + /* Send interrupt if needed */ + if (s->group[group_n].src_mask & s->group[group_n].src_pending) { +#ifdef DEBUG_COMBINER + if (group_n != 26) { + /* skip uart */ + DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); + } +#endif + + /* Set Combiner interrupt pending status after masking */ + if (group_n >= 32) { + s->icipsr[1] |= 1 << (group_n - 32); + } else { + s->icipsr[0] |= 1 << group_n; + } + + qemu_irq_raise(s->output_irq[group_n]); + } else { +#ifdef DEBUG_COMBINER + if (group_n != 26) { + /* skip uart */ + DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n); + } +#endif + + /* Set Combiner interrupt pending status after masking */ + if (group_n >= 32) { + s->icipsr[1] &= ~(1 << (group_n - 32)); + } else { + s->icipsr[0] &= ~(1 << group_n); + } + + qemu_irq_lower(s->output_irq[group_n]); + } +} + +static void exynos4210_combiner_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and + get a start of corresponding group quad */ + uint32_t grp_quad_base_n; /* Base of group quad */ + uint32_t reg_n; /* Register number inside the quad */ + + req_quad_base_n = offset >> 4; + grp_quad_base_n = req_quad_base_n << 2; + reg_n = (offset - (req_quad_base_n << 4)) >> 2; + + if (req_quad_base_n >= IIC_NGRP) { + hw_error("exynos4210.combiner: unallowed write access at offset 0x" + TARGET_FMT_plx "\n", offset); + return; + } + + if (reg_n > 1) { + hw_error("exynos4210.combiner: unallowed write access at offset 0x" + TARGET_FMT_plx "\n", offset); + return; + } + + if (offset >> 2 >= IIC_REGSET_SIZE) { + hw_error("exynos4210.combiner: overflow of reg_set by 0x" + TARGET_FMT_plx "offset\n", offset); + } + s->reg_set[offset >> 2] = val; + + switch (reg_n) { + /* IIESR */ + case 0: + /* FIXME: what if irq is pending, allowed by mask, and we allow it + * again. Interrupt will rise again! */ + + DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n", + s->external ? "EXT" : "INT", + grp_quad_base_n, + grp_quad_base_n + 1, + grp_quad_base_n + 2, + grp_quad_base_n + 3); + + /* Enable interrupt sources */ + s->group[grp_quad_base_n].src_mask |= val & 0xFF; + s->group[grp_quad_base_n + 1].src_mask |= (val & 0xFF00) >> 8; + s->group[grp_quad_base_n + 2].src_mask |= (val & 0xFF0000) >> 16; + s->group[grp_quad_base_n + 3].src_mask |= (val & 0xFF000000) >> 24; + + exynos4210_combiner_update(s, grp_quad_base_n); + exynos4210_combiner_update(s, grp_quad_base_n + 1); + exynos4210_combiner_update(s, grp_quad_base_n + 2); + exynos4210_combiner_update(s, grp_quad_base_n + 3); + break; + /* IIECR */ + case 1: + DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n", + s->external ? "EXT" : "INT", + grp_quad_base_n, + grp_quad_base_n + 1, + grp_quad_base_n + 2, + grp_quad_base_n + 3); + + /* Disable interrupt sources */ + s->group[grp_quad_base_n].src_mask &= ~(val & 0xFF); + s->group[grp_quad_base_n + 1].src_mask &= ~((val & 0xFF00) >> 8); + s->group[grp_quad_base_n + 2].src_mask &= ~((val & 0xFF0000) >> 16); + s->group[grp_quad_base_n + 3].src_mask &= ~((val & 0xFF000000) >> 24); + + exynos4210_combiner_update(s, grp_quad_base_n); + exynos4210_combiner_update(s, grp_quad_base_n + 1); + exynos4210_combiner_update(s, grp_quad_base_n + 2); + exynos4210_combiner_update(s, grp_quad_base_n + 3); + break; + default: + hw_error("exynos4210.combiner: unallowed write access at offset 0x" + TARGET_FMT_plx "\n", offset); + break; + } +} + +/* Get combiner group and bit from irq number */ +static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit) +{ + *bit = irq - ((irq >> 3) << 3); + return irq >> 3; +} + +/* Process a change in an external IRQ input. */ +static void exynos4210_combiner_handler(void *opaque, int irq, int level) +{ + struct Exynos4210CombinerState *s = + (struct Exynos4210CombinerState *)opaque; + uint8_t bit_n, group_n; + + group_n = get_combiner_group_and_bit(irq, &bit_n); + + if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) { + DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT" + , group_n); + return; + } + + if (level) { + s->group[group_n].src_pending |= 1 << bit_n; + } else { + s->group[group_n].src_pending &= ~(1 << bit_n); + } + + exynos4210_combiner_update(s, group_n); +} + +static void exynos4210_combiner_reset(DeviceState *d) +{ + struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d; + + memset(&s->group, 0, sizeof(s->group)); + memset(&s->reg_set, 0, sizeof(s->reg_set)); + + s->reg_set[0xC0 >> 2] = 0x01010101; + s->reg_set[0xC4 >> 2] = 0x01010101; + s->reg_set[0xD0 >> 2] = 0x01010101; + s->reg_set[0xD4 >> 2] = 0x01010101; +} + +static const MemoryRegionOps exynos4210_combiner_ops = { + .read = exynos4210_combiner_read, + .write = exynos4210_combiner_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* + * Internal Combiner initialization. + */ +static int exynos4210_combiner_init(SysBusDevice *dev) +{ + unsigned int i; + struct Exynos4210CombinerState *s = + FROM_SYSBUS(struct Exynos4210CombinerState, dev); + + /* Allocate general purpose input signals and connect a handler to each of + * them */ + qdev_init_gpio_in(&s->busdev.qdev, exynos4210_combiner_handler, IIC_NIRQ); + + /* Connect SysBusDev irqs to device specific irqs */ + for (i = 0; i < IIC_NIRQ; i++) { + sysbus_init_irq(dev, &s->output_irq[i]); + } + + memory_region_init_io(&s->iomem, &exynos4210_combiner_ops, s, + "exynos4210-combiner", IIC_REGION_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static Property exynos4210_combiner_properties[] = { + DEFINE_PROP_UINT32("external", Exynos4210CombinerState, external, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_combiner_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_combiner_init; + dc->reset = exynos4210_combiner_reset; + dc->props = exynos4210_combiner_properties; + dc->vmsd = &vmstate_exynos4210_combiner; +} + +static const TypeInfo exynos4210_combiner_info = { + .name = "exynos4210.combiner", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210CombinerState), + .class_init = exynos4210_combiner_class_init, +}; + +static void exynos4210_combiner_register_types(void) +{ + type_register_static(&exynos4210_combiner_info); +} + +type_init(exynos4210_combiner_register_types) diff --git a/hw/intc/exynos4210_gic.c b/hw/intc/exynos4210_gic.c new file mode 100644 index 0000000000..bad6dde652 --- /dev/null +++ b/hw/intc/exynos4210_gic.c @@ -0,0 +1,462 @@ +/* + * Samsung exynos4210 GIC implementation. Based on hw/arm_gic.c + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * + * Evgeny Voevodin <e.voevodin@samsung.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/sysbus.h" +#include "qemu-common.h" +#include "hw/irq.h" +#include "hw/arm/exynos4210.h" + +enum ExtGicId { + EXT_GIC_ID_MDMA_LCD0 = 66, + EXT_GIC_ID_PDMA0, + EXT_GIC_ID_PDMA1, + EXT_GIC_ID_TIMER0, + EXT_GIC_ID_TIMER1, + EXT_GIC_ID_TIMER2, + EXT_GIC_ID_TIMER3, + EXT_GIC_ID_TIMER4, + EXT_GIC_ID_MCT_L0, + EXT_GIC_ID_WDT, + EXT_GIC_ID_RTC_ALARM, + EXT_GIC_ID_RTC_TIC, + EXT_GIC_ID_GPIO_XB, + EXT_GIC_ID_GPIO_XA, + EXT_GIC_ID_MCT_L1, + EXT_GIC_ID_IEM_APC, + EXT_GIC_ID_IEM_IEC, + EXT_GIC_ID_NFC, + EXT_GIC_ID_UART0, + EXT_GIC_ID_UART1, + EXT_GIC_ID_UART2, + EXT_GIC_ID_UART3, + EXT_GIC_ID_UART4, + EXT_GIC_ID_MCT_G0, + EXT_GIC_ID_I2C0, + EXT_GIC_ID_I2C1, + EXT_GIC_ID_I2C2, + EXT_GIC_ID_I2C3, + EXT_GIC_ID_I2C4, + EXT_GIC_ID_I2C5, + EXT_GIC_ID_I2C6, + EXT_GIC_ID_I2C7, + EXT_GIC_ID_SPI0, + EXT_GIC_ID_SPI1, + EXT_GIC_ID_SPI2, + EXT_GIC_ID_MCT_G1, + EXT_GIC_ID_USB_HOST, + EXT_GIC_ID_USB_DEVICE, + EXT_GIC_ID_MODEMIF, + EXT_GIC_ID_HSMMC0, + EXT_GIC_ID_HSMMC1, + EXT_GIC_ID_HSMMC2, + EXT_GIC_ID_HSMMC3, + EXT_GIC_ID_SDMMC, + EXT_GIC_ID_MIPI_CSI_4LANE, + EXT_GIC_ID_MIPI_DSI_4LANE, + EXT_GIC_ID_MIPI_CSI_2LANE, + EXT_GIC_ID_MIPI_DSI_2LANE, + EXT_GIC_ID_ONENAND_AUDI, + EXT_GIC_ID_ROTATOR, + EXT_GIC_ID_FIMC0, + EXT_GIC_ID_FIMC1, + EXT_GIC_ID_FIMC2, + EXT_GIC_ID_FIMC3, + EXT_GIC_ID_JPEG, + EXT_GIC_ID_2D, + EXT_GIC_ID_PCIe, + EXT_GIC_ID_MIXER, + EXT_GIC_ID_HDMI, + EXT_GIC_ID_HDMI_I2C, + EXT_GIC_ID_MFC, + EXT_GIC_ID_TVENC, +}; + +enum ExtInt { + EXT_GIC_ID_EXTINT0 = 48, + EXT_GIC_ID_EXTINT1, + EXT_GIC_ID_EXTINT2, + EXT_GIC_ID_EXTINT3, + EXT_GIC_ID_EXTINT4, + EXT_GIC_ID_EXTINT5, + EXT_GIC_ID_EXTINT6, + EXT_GIC_ID_EXTINT7, + EXT_GIC_ID_EXTINT8, + EXT_GIC_ID_EXTINT9, + EXT_GIC_ID_EXTINT10, + EXT_GIC_ID_EXTINT11, + EXT_GIC_ID_EXTINT12, + EXT_GIC_ID_EXTINT13, + EXT_GIC_ID_EXTINT14, + EXT_GIC_ID_EXTINT15 +}; + +/* + * External GIC sources which are not from External Interrupt Combiner or + * External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ, + * which is INTG16 in Internal Interrupt Combiner. + */ + +static uint32_t +combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = { + /* int combiner groups 16-19 */ + { }, { }, { }, { }, + /* int combiner group 20 */ + { 0, EXT_GIC_ID_MDMA_LCD0 }, + /* int combiner group 21 */ + { EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1 }, + /* int combiner group 22 */ + { EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2, + EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4 }, + /* int combiner group 23 */ + { EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC }, + /* int combiner group 24 */ + { EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA }, + /* int combiner group 25 */ + { EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC }, + /* int combiner group 26 */ + { EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3, + EXT_GIC_ID_UART4 }, + /* int combiner group 27 */ + { EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3, + EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6, + EXT_GIC_ID_I2C7 }, + /* int combiner group 28 */ + { EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2 , EXT_GIC_ID_USB_HOST}, + /* int combiner group 29 */ + { EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2, + EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC }, + /* int combiner group 30 */ + { EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE }, + /* int combiner group 31 */ + { EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE }, + /* int combiner group 32 */ + { EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1 }, + /* int combiner group 33 */ + { EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3 }, + /* int combiner group 34 */ + { EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC }, + /* int combiner group 35 */ + { 0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 }, + /* int combiner group 36 */ + { EXT_GIC_ID_MIXER }, + /* int combiner group 37 */ + { EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6, + EXT_GIC_ID_EXTINT7 }, + /* groups 38-50 */ + { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, + /* int combiner group 51 */ + { EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 }, + /* group 52 */ + { }, + /* int combiner group 53 */ + { EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 }, + /* groups 54-63 */ + { }, { }, { }, { }, { }, { }, { }, { }, { }, { } +}; + +#define EXYNOS4210_GIC_NIRQ 160 + +#define EXYNOS4210_EXT_GIC_CPU_REGION_SIZE 0x10000 +#define EXYNOS4210_EXT_GIC_DIST_REGION_SIZE 0x10000 + +#define EXYNOS4210_EXT_GIC_PER_CPU_OFFSET 0x8000 +#define EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(n) \ + ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET) +#define EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(n) \ + ((n) * EXYNOS4210_EXT_GIC_PER_CPU_OFFSET) + +#define EXYNOS4210_GIC_CPU_REGION_SIZE 0x100 +#define EXYNOS4210_GIC_DIST_REGION_SIZE 0x1000 + +static void exynos4210_irq_handler(void *opaque, int irq, int level) +{ + Exynos4210Irq *s = (Exynos4210Irq *)opaque; + + /* Bypass */ + qemu_set_irq(s->board_irqs[irq], level); +} + +/* + * Initialize exynos4210 IRQ subsystem stub. + */ +qemu_irq *exynos4210_init_irq(Exynos4210Irq *s) +{ + return qemu_allocate_irqs(exynos4210_irq_handler, s, + EXYNOS4210_MAX_INT_COMBINER_IN_IRQ); +} + +/* + * Initialize board IRQs. + * These IRQs contain splitted Int/External Combiner and External Gic IRQs. + */ +void exynos4210_init_board_irqs(Exynos4210Irq *s) +{ + uint32_t grp, bit, irq_id, n; + + for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) { + s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n], + s->ext_combiner_irq[n]); + + irq_id = 0; + if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) || + n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) { + /* MCT_G0 is passed to External GIC */ + irq_id = EXT_GIC_ID_MCT_G0; + } + if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) || + n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 5)) { + /* MCT_G1 is passed to External and GIC */ + irq_id = EXT_GIC_ID_MCT_G1; + } + if (irq_id) { + s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n], + s->ext_gic_irq[irq_id-32]); + } + + } + for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) { + /* these IDs are passed to Internal Combiner and External GIC */ + grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n); + bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n); + irq_id = combiner_grp_to_gic_id[grp - + EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit]; + + if (irq_id) { + s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n], + s->ext_gic_irq[irq_id-32]); + } + } +} + +/* + * Get IRQ number from exynos4210 IRQ subsystem stub. + * To identify IRQ source use internal combiner group and bit number + * grp - group number + * bit - bit number inside group + */ +uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit) +{ + return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit); +} + +/********* GIC part *********/ + +typedef struct { + SysBusDevice busdev; + MemoryRegion cpu_container; + MemoryRegion dist_container; + MemoryRegion cpu_alias[EXYNOS4210_NCPUS]; + MemoryRegion dist_alias[EXYNOS4210_NCPUS]; + uint32_t num_cpu; + DeviceState *gic; +} Exynos4210GicState; + +static void exynos4210_gic_set_irq(void *opaque, int irq, int level) +{ + Exynos4210GicState *s = (Exynos4210GicState *)opaque; + qemu_set_irq(qdev_get_gpio_in(s->gic, irq), level); +} + +static int exynos4210_gic_init(SysBusDevice *dev) +{ + Exynos4210GicState *s = FROM_SYSBUS(Exynos4210GicState, dev); + uint32_t i; + const char cpu_prefix[] = "exynos4210-gic-alias_cpu"; + const char dist_prefix[] = "exynos4210-gic-alias_dist"; + char cpu_alias_name[sizeof(cpu_prefix) + 3]; + char dist_alias_name[sizeof(cpu_prefix) + 3]; + SysBusDevice *busdev; + + s->gic = qdev_create(NULL, "arm_gic"); + qdev_prop_set_uint32(s->gic, "num-cpu", s->num_cpu); + qdev_prop_set_uint32(s->gic, "num-irq", EXYNOS4210_GIC_NIRQ); + qdev_init_nofail(s->gic); + busdev = SYS_BUS_DEVICE(s->gic); + + /* Pass through outbound IRQ lines from the GIC */ + sysbus_pass_irq(dev, busdev); + + /* Pass through inbound GPIO lines to the GIC */ + qdev_init_gpio_in(&s->busdev.qdev, exynos4210_gic_set_irq, + EXYNOS4210_GIC_NIRQ - 32); + + memory_region_init(&s->cpu_container, "exynos4210-cpu-container", + EXYNOS4210_EXT_GIC_CPU_REGION_SIZE); + memory_region_init(&s->dist_container, "exynos4210-dist-container", + EXYNOS4210_EXT_GIC_DIST_REGION_SIZE); + + for (i = 0; i < s->num_cpu; i++) { + /* Map CPU interface per SMP Core */ + sprintf(cpu_alias_name, "%s%x", cpu_prefix, i); + memory_region_init_alias(&s->cpu_alias[i], + cpu_alias_name, + sysbus_mmio_get_region(busdev, 1), + 0, + EXYNOS4210_GIC_CPU_REGION_SIZE); + memory_region_add_subregion(&s->cpu_container, + EXYNOS4210_EXT_GIC_CPU_GET_OFFSET(i), &s->cpu_alias[i]); + + /* Map Distributor per SMP Core */ + sprintf(dist_alias_name, "%s%x", dist_prefix, i); + memory_region_init_alias(&s->dist_alias[i], + dist_alias_name, + sysbus_mmio_get_region(busdev, 0), + 0, + EXYNOS4210_GIC_DIST_REGION_SIZE); + memory_region_add_subregion(&s->dist_container, + EXYNOS4210_EXT_GIC_DIST_GET_OFFSET(i), &s->dist_alias[i]); + } + + sysbus_init_mmio(dev, &s->cpu_container); + sysbus_init_mmio(dev, &s->dist_container); + + return 0; +} + +static Property exynos4210_gic_properties[] = { + DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_gic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_gic_init; + dc->props = exynos4210_gic_properties; +} + +static const TypeInfo exynos4210_gic_info = { + .name = "exynos4210.gic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210GicState), + .class_init = exynos4210_gic_class_init, +}; + +static void exynos4210_gic_register_types(void) +{ + type_register_static(&exynos4210_gic_info); +} + +type_init(exynos4210_gic_register_types) + +/* IRQ OR Gate struct. + * + * This device models an OR gate. There are n_in input qdev gpio lines and one + * output sysbus IRQ line. The output IRQ level is formed as OR between all + * gpio inputs. + */ +typedef struct { + SysBusDevice busdev; + + uint32_t n_in; /* inputs amount */ + uint32_t *level; /* input levels */ + qemu_irq out; /* output IRQ */ +} Exynos4210IRQGateState; + +static Property exynos4210_irq_gate_properties[] = { + DEFINE_PROP_UINT32("n_in", Exynos4210IRQGateState, n_in, 1), + DEFINE_PROP_END_OF_LIST(), +}; + +static const VMStateDescription vmstate_exynos4210_irq_gate = { + .name = "exynos4210.irq_gate", + .version_id = 2, + .minimum_version_id = 2, + .minimum_version_id_old = 2, + .fields = (VMStateField[]) { + VMSTATE_VBUFFER_UINT32(level, Exynos4210IRQGateState, 1, NULL, 0, n_in), + VMSTATE_END_OF_LIST() + } +}; + +/* Process a change in IRQ input. */ +static void exynos4210_irq_gate_handler(void *opaque, int irq, int level) +{ + Exynos4210IRQGateState *s = (Exynos4210IRQGateState *)opaque; + uint32_t i; + + assert(irq < s->n_in); + + s->level[irq] = level; + + for (i = 0; i < s->n_in; i++) { + if (s->level[i] >= 1) { + qemu_irq_raise(s->out); + return; + } + } + + qemu_irq_lower(s->out); +} + +static void exynos4210_irq_gate_reset(DeviceState *d) +{ + Exynos4210IRQGateState *s = + DO_UPCAST(Exynos4210IRQGateState, busdev.qdev, d); + + memset(s->level, 0, s->n_in * sizeof(*s->level)); +} + +/* + * IRQ Gate initialization. + */ +static int exynos4210_irq_gate_init(SysBusDevice *dev) +{ + Exynos4210IRQGateState *s = FROM_SYSBUS(Exynos4210IRQGateState, dev); + + /* Allocate general purpose input signals and connect a handler to each of + * them */ + qdev_init_gpio_in(&s->busdev.qdev, exynos4210_irq_gate_handler, s->n_in); + + s->level = g_malloc0(s->n_in * sizeof(*s->level)); + + sysbus_init_irq(dev, &s->out); + + return 0; +} + +static void exynos4210_irq_gate_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_irq_gate_init; + dc->reset = exynos4210_irq_gate_reset; + dc->vmsd = &vmstate_exynos4210_irq_gate; + dc->props = exynos4210_irq_gate_properties; +} + +static const TypeInfo exynos4210_irq_gate_info = { + .name = "exynos4210.irq_gate", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210IRQGateState), + .class_init = exynos4210_irq_gate_class_init, +}; + +static void exynos4210_irq_gate_register_types(void) +{ + type_register_static(&exynos4210_irq_gate_info); +} + +type_init(exynos4210_irq_gate_register_types) diff --git a/hw/intc/grlib_irqmp.c b/hw/intc/grlib_irqmp.c new file mode 100644 index 0000000000..68dfe6a4bb --- /dev/null +++ b/hw/intc/grlib_irqmp.c @@ -0,0 +1,385 @@ +/* + * QEMU GRLIB IRQMP Emulator + * + * (Multiprocessor and extended interrupt not supported) + * + * Copyright (c) 2010-2011 AdaCore + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sysbus.h" +#include "cpu.h" + +#include "hw/sparc/grlib.h" + +#include "trace.h" + +#define IRQMP_MAX_CPU 16 +#define IRQMP_REG_SIZE 256 /* Size of memory mapped registers */ + +/* Memory mapped register offsets */ +#define LEVEL_OFFSET 0x00 +#define PENDING_OFFSET 0x04 +#define FORCE0_OFFSET 0x08 +#define CLEAR_OFFSET 0x0C +#define MP_STATUS_OFFSET 0x10 +#define BROADCAST_OFFSET 0x14 +#define MASK_OFFSET 0x40 +#define FORCE_OFFSET 0x80 +#define EXTENDED_OFFSET 0xC0 + +typedef struct IRQMPState IRQMPState; + +typedef struct IRQMP { + SysBusDevice busdev; + MemoryRegion iomem; + + void *set_pil_in; + void *set_pil_in_opaque; + + IRQMPState *state; +} IRQMP; + +struct IRQMPState { + uint32_t level; + uint32_t pending; + uint32_t clear; + uint32_t broadcast; + + uint32_t mask[IRQMP_MAX_CPU]; + uint32_t force[IRQMP_MAX_CPU]; + uint32_t extended[IRQMP_MAX_CPU]; + + IRQMP *parent; +}; + +static void grlib_irqmp_check_irqs(IRQMPState *state) +{ + uint32_t pend = 0; + uint32_t level0 = 0; + uint32_t level1 = 0; + set_pil_in_fn set_pil_in; + + assert(state != NULL); + assert(state->parent != NULL); + + /* IRQ for CPU 0 (no SMP support) */ + pend = (state->pending | state->force[0]) + & state->mask[0]; + + level0 = pend & ~state->level; + level1 = pend & state->level; + + trace_grlib_irqmp_check_irqs(state->pending, state->force[0], + state->mask[0], level1, level0); + + set_pil_in = (set_pil_in_fn)state->parent->set_pil_in; + + /* Trigger level1 interrupt first and level0 if there is no level1 */ + if (level1 != 0) { + set_pil_in(state->parent->set_pil_in_opaque, level1); + } else { + set_pil_in(state->parent->set_pil_in_opaque, level0); + } +} + +void grlib_irqmp_ack(DeviceState *dev, int intno) +{ + SysBusDevice *sdev; + IRQMP *irqmp; + IRQMPState *state; + uint32_t mask; + + assert(dev != NULL); + + sdev = SYS_BUS_DEVICE(dev); + assert(sdev != NULL); + + irqmp = FROM_SYSBUS(typeof(*irqmp), sdev); + assert(irqmp != NULL); + + state = irqmp->state; + assert(state != NULL); + + intno &= 15; + mask = 1 << intno; + + trace_grlib_irqmp_ack(intno); + + /* Clear registers */ + state->pending &= ~mask; + state->force[0] &= ~mask; /* Only CPU 0 (No SMP support) */ + + grlib_irqmp_check_irqs(state); +} + +void grlib_irqmp_set_irq(void *opaque, int irq, int level) +{ + IRQMP *irqmp; + IRQMPState *s; + int i = 0; + + assert(opaque != NULL); + + irqmp = FROM_SYSBUS(typeof(*irqmp), SYS_BUS_DEVICE(opaque)); + assert(irqmp != NULL); + + s = irqmp->state; + assert(s != NULL); + assert(s->parent != NULL); + + + if (level) { + trace_grlib_irqmp_set_irq(irq); + + if (s->broadcast & 1 << irq) { + /* Broadcasted IRQ */ + for (i = 0; i < IRQMP_MAX_CPU; i++) { + s->force[i] |= 1 << irq; + } + } else { + s->pending |= 1 << irq; + } + grlib_irqmp_check_irqs(s); + + } +} + +static uint64_t grlib_irqmp_read(void *opaque, hwaddr addr, + unsigned size) +{ + IRQMP *irqmp = opaque; + IRQMPState *state; + + assert(irqmp != NULL); + state = irqmp->state; + assert(state != NULL); + + addr &= 0xff; + + /* global registers */ + switch (addr) { + case LEVEL_OFFSET: + return state->level; + + case PENDING_OFFSET: + return state->pending; + + case FORCE0_OFFSET: + /* This register is an "alias" for the force register of CPU 0 */ + return state->force[0]; + + case CLEAR_OFFSET: + case MP_STATUS_OFFSET: + /* Always read as 0 */ + return 0; + + case BROADCAST_OFFSET: + return state->broadcast; + + default: + break; + } + + /* mask registers */ + if (addr >= MASK_OFFSET && addr < FORCE_OFFSET) { + int cpu = (addr - MASK_OFFSET) / 4; + assert(cpu >= 0 && cpu < IRQMP_MAX_CPU); + + return state->mask[cpu]; + } + + /* force registers */ + if (addr >= FORCE_OFFSET && addr < EXTENDED_OFFSET) { + int cpu = (addr - FORCE_OFFSET) / 4; + assert(cpu >= 0 && cpu < IRQMP_MAX_CPU); + + return state->force[cpu]; + } + + /* extended (not supported) */ + if (addr >= EXTENDED_OFFSET && addr < IRQMP_REG_SIZE) { + int cpu = (addr - EXTENDED_OFFSET) / 4; + assert(cpu >= 0 && cpu < IRQMP_MAX_CPU); + + return state->extended[cpu]; + } + + trace_grlib_irqmp_readl_unknown(addr); + return 0; +} + +static void grlib_irqmp_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + IRQMP *irqmp = opaque; + IRQMPState *state; + + assert(irqmp != NULL); + state = irqmp->state; + assert(state != NULL); + + addr &= 0xff; + + /* global registers */ + switch (addr) { + case LEVEL_OFFSET: + value &= 0xFFFF << 1; /* clean up the value */ + state->level = value; + return; + + case PENDING_OFFSET: + /* Read Only */ + return; + + case FORCE0_OFFSET: + /* This register is an "alias" for the force register of CPU 0 */ + + value &= 0xFFFE; /* clean up the value */ + state->force[0] = value; + grlib_irqmp_check_irqs(irqmp->state); + return; + + case CLEAR_OFFSET: + value &= ~1; /* clean up the value */ + state->pending &= ~value; + return; + + case MP_STATUS_OFFSET: + /* Read Only (no SMP support) */ + return; + + case BROADCAST_OFFSET: + value &= 0xFFFE; /* clean up the value */ + state->broadcast = value; + return; + + default: + break; + } + + /* mask registers */ + if (addr >= MASK_OFFSET && addr < FORCE_OFFSET) { + int cpu = (addr - MASK_OFFSET) / 4; + assert(cpu >= 0 && cpu < IRQMP_MAX_CPU); + + value &= ~1; /* clean up the value */ + state->mask[cpu] = value; + grlib_irqmp_check_irqs(irqmp->state); + return; + } + + /* force registers */ + if (addr >= FORCE_OFFSET && addr < EXTENDED_OFFSET) { + int cpu = (addr - FORCE_OFFSET) / 4; + assert(cpu >= 0 && cpu < IRQMP_MAX_CPU); + + uint32_t force = value & 0xFFFE; + uint32_t clear = (value >> 16) & 0xFFFE; + uint32_t old = state->force[cpu]; + + state->force[cpu] = (old | force) & ~clear; + grlib_irqmp_check_irqs(irqmp->state); + return; + } + + /* extended (not supported) */ + if (addr >= EXTENDED_OFFSET && addr < IRQMP_REG_SIZE) { + int cpu = (addr - EXTENDED_OFFSET) / 4; + assert(cpu >= 0 && cpu < IRQMP_MAX_CPU); + + value &= 0xF; /* clean up the value */ + state->extended[cpu] = value; + return; + } + + trace_grlib_irqmp_writel_unknown(addr, value); +} + +static const MemoryRegionOps grlib_irqmp_ops = { + .read = grlib_irqmp_read, + .write = grlib_irqmp_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void grlib_irqmp_reset(DeviceState *d) +{ + IRQMP *irqmp = container_of(d, IRQMP, busdev.qdev); + assert(irqmp != NULL); + assert(irqmp->state != NULL); + + memset(irqmp->state, 0, sizeof *irqmp->state); + irqmp->state->parent = irqmp; +} + +static int grlib_irqmp_init(SysBusDevice *dev) +{ + IRQMP *irqmp = FROM_SYSBUS(typeof(*irqmp), dev); + + assert(irqmp != NULL); + + /* Check parameters */ + if (irqmp->set_pil_in == NULL) { + return -1; + } + + memory_region_init_io(&irqmp->iomem, &grlib_irqmp_ops, irqmp, + "irqmp", IRQMP_REG_SIZE); + + irqmp->state = g_malloc0(sizeof *irqmp->state); + + sysbus_init_mmio(dev, &irqmp->iomem); + + return 0; +} + +static Property grlib_irqmp_properties[] = { + DEFINE_PROP_PTR("set_pil_in", IRQMP, set_pil_in), + DEFINE_PROP_PTR("set_pil_in_opaque", IRQMP, set_pil_in_opaque), + DEFINE_PROP_END_OF_LIST(), +}; + +static void grlib_irqmp_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = grlib_irqmp_init; + dc->reset = grlib_irqmp_reset; + dc->props = grlib_irqmp_properties; +} + +static const TypeInfo grlib_irqmp_info = { + .name = "grlib,irqmp", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IRQMP), + .class_init = grlib_irqmp_class_init, +}; + +static void grlib_irqmp_register_types(void) +{ + type_register_static(&grlib_irqmp_info); +} + +type_init(grlib_irqmp_register_types) diff --git a/hw/intc/imx_avic.c b/hw/intc/imx_avic.c new file mode 100644 index 0000000000..4e280b6ab9 --- /dev/null +++ b/hw/intc/imx_avic.c @@ -0,0 +1,408 @@ +/* + * i.MX31 Vectored Interrupt Controller + * + * Note this is NOT the PL192 provided by ARM, but + * a custom implementation by Freescale. + * + * Copyright (c) 2008 OKL + * Copyright (c) 2011 NICTA Pty Ltd + * Originally written by Hans Jiang + * + * This code is licensed under the GPL version 2 or later. See + * the COPYING file in the top-level directory. + * + * TODO: implement vectors. + */ + +#include "hw/hw.h" +#include "hw/sysbus.h" +#include "qemu/host-utils.h" + +#define DEBUG_INT 1 +#undef DEBUG_INT /* comment out for debugging */ + +#ifdef DEBUG_INT +#define DPRINTF(fmt, args...) \ +do { printf("imx_avic: " fmt , ##args); } while (0) +#else +#define DPRINTF(fmt, args...) do {} while (0) +#endif + +/* + * Define to 1 for messages about attempts to + * access unimplemented registers or similar. + */ +#define DEBUG_IMPLEMENTATION 1 +#if DEBUG_IMPLEMENTATION +# define IPRINTF(fmt, args...) \ + do { fprintf(stderr, "imx_avic: " fmt, ##args); } while (0) +#else +# define IPRINTF(fmt, args...) do {} while (0) +#endif + +#define IMX_AVIC_NUM_IRQS 64 + +/* Interrupt Control Bits */ +#define ABFLAG (1<<25) +#define ABFEN (1<<24) +#define NIDIS (1<<22) /* Normal Interrupt disable */ +#define FIDIS (1<<21) /* Fast interrupt disable */ +#define NIAD (1<<20) /* Normal Interrupt Arbiter Rise ARM level */ +#define FIAD (1<<19) /* Fast Interrupt Arbiter Rise ARM level */ +#define NM (1<<18) /* Normal interrupt mode */ + + +#define PRIO_PER_WORD (sizeof(uint32_t) * 8 / 4) +#define PRIO_WORDS (IMX_AVIC_NUM_IRQS/PRIO_PER_WORD) + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + uint64_t pending; + uint64_t enabled; + uint64_t is_fiq; + uint32_t intcntl; + uint32_t intmask; + qemu_irq irq; + qemu_irq fiq; + uint32_t prio[PRIO_WORDS]; /* Priorities are 4-bits each */ +} IMXAVICState; + +static const VMStateDescription vmstate_imx_avic = { + .name = "imx-avic", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(pending, IMXAVICState), + VMSTATE_UINT64(enabled, IMXAVICState), + VMSTATE_UINT64(is_fiq, IMXAVICState), + VMSTATE_UINT32(intcntl, IMXAVICState), + VMSTATE_UINT32(intmask, IMXAVICState), + VMSTATE_UINT32_ARRAY(prio, IMXAVICState, PRIO_WORDS), + VMSTATE_END_OF_LIST() + }, +}; + + + +static inline int imx_avic_prio(IMXAVICState *s, int irq) +{ + uint32_t word = irq / PRIO_PER_WORD; + uint32_t part = 4 * (irq % PRIO_PER_WORD); + return 0xf & (s->prio[word] >> part); +} + +static inline void imx_avic_set_prio(IMXAVICState *s, int irq, int prio) +{ + uint32_t word = irq / PRIO_PER_WORD; + uint32_t part = 4 * (irq % PRIO_PER_WORD); + uint32_t mask = ~(0xf << part); + s->prio[word] &= mask; + s->prio[word] |= prio << part; +} + +/* Update interrupts. */ +static void imx_avic_update(IMXAVICState *s) +{ + int i; + uint64_t new = s->pending & s->enabled; + uint64_t flags; + + flags = new & s->is_fiq; + qemu_set_irq(s->fiq, !!flags); + + flags = new & ~s->is_fiq; + if (!flags || (s->intmask == 0x1f)) { + qemu_set_irq(s->irq, !!flags); + return; + } + + /* + * Take interrupt if there's a pending interrupt with + * priority higher than the value of intmask + */ + for (i = 0; i < IMX_AVIC_NUM_IRQS; i++) { + if (flags & (1UL << i)) { + if (imx_avic_prio(s, i) > s->intmask) { + qemu_set_irq(s->irq, 1); + return; + } + } + } + qemu_set_irq(s->irq, 0); +} + +static void imx_avic_set_irq(void *opaque, int irq, int level) +{ + IMXAVICState *s = (IMXAVICState *)opaque; + + if (level) { + DPRINTF("Raising IRQ %d, prio %d\n", + irq, imx_avic_prio(s, irq)); + s->pending |= (1ULL << irq); + } else { + DPRINTF("Clearing IRQ %d, prio %d\n", + irq, imx_avic_prio(s, irq)); + s->pending &= ~(1ULL << irq); + } + + imx_avic_update(s); +} + + +static uint64_t imx_avic_read(void *opaque, + hwaddr offset, unsigned size) +{ + IMXAVICState *s = (IMXAVICState *)opaque; + + + DPRINTF("read(offset = 0x%x)\n", offset >> 2); + switch (offset >> 2) { + case 0: /* INTCNTL */ + return s->intcntl; + + case 1: /* Normal Interrupt Mask Register, NIMASK */ + return s->intmask; + + case 2: /* Interrupt Enable Number Register, INTENNUM */ + case 3: /* Interrupt Disable Number Register, INTDISNUM */ + return 0; + + case 4: /* Interrupt Enabled Number Register High */ + return s->enabled >> 32; + + case 5: /* Interrupt Enabled Number Register Low */ + return s->enabled & 0xffffffffULL; + + case 6: /* Interrupt Type Register High */ + return s->is_fiq >> 32; + + case 7: /* Interrupt Type Register Low */ + return s->is_fiq & 0xffffffffULL; + + case 8: /* Normal Interrupt Priority Register 7 */ + case 9: /* Normal Interrupt Priority Register 6 */ + case 10:/* Normal Interrupt Priority Register 5 */ + case 11:/* Normal Interrupt Priority Register 4 */ + case 12:/* Normal Interrupt Priority Register 3 */ + case 13:/* Normal Interrupt Priority Register 2 */ + case 14:/* Normal Interrupt Priority Register 1 */ + case 15:/* Normal Interrupt Priority Register 0 */ + return s->prio[15-(offset>>2)]; + + case 16: /* Normal interrupt vector and status register */ + { + /* + * This returns the highest priority + * outstanding interrupt. Where there is more than + * one pending IRQ with the same priority, + * take the highest numbered one. + */ + uint64_t flags = s->pending & s->enabled & ~s->is_fiq; + int i; + int prio = -1; + int irq = -1; + for (i = 63; i >= 0; --i) { + if (flags & (1ULL<<i)) { + int irq_prio = imx_avic_prio(s, i); + if (irq_prio > prio) { + irq = i; + prio = irq_prio; + } + } + } + if (irq >= 0) { + imx_avic_set_irq(s, irq, 0); + return irq << 16 | prio; + } + return 0xffffffffULL; + } + case 17:/* Fast Interrupt vector and status register */ + { + uint64_t flags = s->pending & s->enabled & s->is_fiq; + int i = ctz64(flags); + if (i < 64) { + imx_avic_set_irq(opaque, i, 0); + return i; + } + return 0xffffffffULL; + } + case 18:/* Interrupt source register high */ + return s->pending >> 32; + + case 19:/* Interrupt source register low */ + return s->pending & 0xffffffffULL; + + case 20:/* Interrupt Force Register high */ + case 21:/* Interrupt Force Register low */ + return 0; + + case 22:/* Normal Interrupt Pending Register High */ + return (s->pending & s->enabled & ~s->is_fiq) >> 32; + + case 23:/* Normal Interrupt Pending Register Low */ + return (s->pending & s->enabled & ~s->is_fiq) & 0xffffffffULL; + + case 24: /* Fast Interrupt Pending Register High */ + return (s->pending & s->enabled & s->is_fiq) >> 32; + + case 25: /* Fast Interrupt Pending Register Low */ + return (s->pending & s->enabled & s->is_fiq) & 0xffffffffULL; + + case 0x40: /* AVIC vector 0, use for WFI WAR */ + return 0x4; + + default: + IPRINTF("imx_avic_read: Bad offset 0x%x\n", (int)offset); + return 0; + } +} + +static void imx_avic_write(void *opaque, hwaddr offset, + uint64_t val, unsigned size) +{ + IMXAVICState *s = (IMXAVICState *)opaque; + + /* Vector Registers not yet supported */ + if (offset >= 0x100 && offset <= 0x2fc) { + IPRINTF("imx_avic_write to vector register %d ignored\n", + (unsigned int)((offset - 0x100) >> 2)); + return; + } + + DPRINTF("imx_avic_write(0x%x) = %x\n", + (unsigned int)offset>>2, (unsigned int)val); + switch (offset >> 2) { + case 0: /* Interrupt Control Register, INTCNTL */ + s->intcntl = val & (ABFEN | NIDIS | FIDIS | NIAD | FIAD | NM); + if (s->intcntl & ABFEN) { + s->intcntl &= ~(val & ABFLAG); + } + break; + + case 1: /* Normal Interrupt Mask Register, NIMASK */ + s->intmask = val & 0x1f; + break; + + case 2: /* Interrupt Enable Number Register, INTENNUM */ + DPRINTF("enable(%d)\n", (int)val); + val &= 0x3f; + s->enabled |= (1ULL << val); + break; + + case 3: /* Interrupt Disable Number Register, INTDISNUM */ + DPRINTF("disable(%d)\n", (int)val); + val &= 0x3f; + s->enabled &= ~(1ULL << val); + break; + + case 4: /* Interrupt Enable Number Register High */ + s->enabled = (s->enabled & 0xffffffffULL) | (val << 32); + break; + + case 5: /* Interrupt Enable Number Register Low */ + s->enabled = (s->enabled & 0xffffffff00000000ULL) | val; + break; + + case 6: /* Interrupt Type Register High */ + s->is_fiq = (s->is_fiq & 0xffffffffULL) | (val << 32); + break; + + case 7: /* Interrupt Type Register Low */ + s->is_fiq = (s->is_fiq & 0xffffffff00000000ULL) | val; + break; + + case 8: /* Normal Interrupt Priority Register 7 */ + case 9: /* Normal Interrupt Priority Register 6 */ + case 10:/* Normal Interrupt Priority Register 5 */ + case 11:/* Normal Interrupt Priority Register 4 */ + case 12:/* Normal Interrupt Priority Register 3 */ + case 13:/* Normal Interrupt Priority Register 2 */ + case 14:/* Normal Interrupt Priority Register 1 */ + case 15:/* Normal Interrupt Priority Register 0 */ + s->prio[15-(offset>>2)] = val; + break; + + /* Read-only registers, writes ignored */ + case 16:/* Normal Interrupt Vector and Status register */ + case 17:/* Fast Interrupt vector and status register */ + case 18:/* Interrupt source register high */ + case 19:/* Interrupt source register low */ + return; + + case 20:/* Interrupt Force Register high */ + s->pending = (s->pending & 0xffffffffULL) | (val << 32); + break; + + case 21:/* Interrupt Force Register low */ + s->pending = (s->pending & 0xffffffff00000000ULL) | val; + break; + + case 22:/* Normal Interrupt Pending Register High */ + case 23:/* Normal Interrupt Pending Register Low */ + case 24: /* Fast Interrupt Pending Register High */ + case 25: /* Fast Interrupt Pending Register Low */ + return; + + default: + IPRINTF("imx_avic_write: Bad offset %x\n", (int)offset); + } + imx_avic_update(s); +} + +static const MemoryRegionOps imx_avic_ops = { + .read = imx_avic_read, + .write = imx_avic_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void imx_avic_reset(DeviceState *dev) +{ + IMXAVICState *s = container_of(dev, IMXAVICState, busdev.qdev); + s->pending = 0; + s->enabled = 0; + s->is_fiq = 0; + s->intmask = 0x1f; + s->intcntl = 0; + memset(s->prio, 0, sizeof s->prio); +} + +static int imx_avic_init(SysBusDevice *dev) +{ + IMXAVICState *s = FROM_SYSBUS(IMXAVICState, dev);; + + memory_region_init_io(&s->iomem, &imx_avic_ops, s, "imx_avic", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + + qdev_init_gpio_in(&dev->qdev, imx_avic_set_irq, IMX_AVIC_NUM_IRQS); + sysbus_init_irq(dev, &s->irq); + sysbus_init_irq(dev, &s->fiq); + + return 0; +} + + +static void imx_avic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + k->init = imx_avic_init; + dc->vmsd = &vmstate_imx_avic; + dc->reset = imx_avic_reset; + dc->desc = "i.MX Advanced Vector Interrupt Controller"; +} + +static const TypeInfo imx_avic_info = { + .name = "imx_avic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMXAVICState), + .class_init = imx_avic_class_init, +}; + +static void imx_avic_register_types(void) +{ + type_register_static(&imx_avic_info); +} + +type_init(imx_avic_register_types) diff --git a/hw/intc/ioapic.c b/hw/intc/ioapic.c new file mode 100644 index 0000000000..c6f09f4331 --- /dev/null +++ b/hw/intc/ioapic.c @@ -0,0 +1,258 @@ +/* + * ioapic.c IOAPIC emulation logic + * + * Copyright (c) 2004-2005 Fabrice Bellard + * + * Split the ioapic logic from apic.c + * Xiantao Zhang <xiantao.zhang@intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/hw.h" +#include "hw/i386/pc.h" +#include "hw/i386/ioapic.h" +#include "hw/i386/ioapic_internal.h" + +//#define DEBUG_IOAPIC + +#ifdef DEBUG_IOAPIC +#define DPRINTF(fmt, ...) \ + do { printf("ioapic: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) +#endif + +static IOAPICCommonState *ioapics[MAX_IOAPICS]; + +static void ioapic_service(IOAPICCommonState *s) +{ + uint8_t i; + uint8_t trig_mode; + uint8_t vector; + uint8_t delivery_mode; + uint32_t mask; + uint64_t entry; + uint8_t dest; + uint8_t dest_mode; + + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + mask = 1 << i; + if (s->irr & mask) { + entry = s->ioredtbl[i]; + if (!(entry & IOAPIC_LVT_MASKED)) { + trig_mode = ((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1); + dest = entry >> IOAPIC_LVT_DEST_SHIFT; + dest_mode = (entry >> IOAPIC_LVT_DEST_MODE_SHIFT) & 1; + delivery_mode = + (entry >> IOAPIC_LVT_DELIV_MODE_SHIFT) & IOAPIC_DM_MASK; + if (trig_mode == IOAPIC_TRIGGER_EDGE) { + s->irr &= ~mask; + } else { + s->ioredtbl[i] |= IOAPIC_LVT_REMOTE_IRR; + } + if (delivery_mode == IOAPIC_DM_EXTINT) { + vector = pic_read_irq(isa_pic); + } else { + vector = entry & IOAPIC_VECTOR_MASK; + } + apic_deliver_irq(dest, dest_mode, delivery_mode, + vector, trig_mode); + } + } + } +} + +static void ioapic_set_irq(void *opaque, int vector, int level) +{ + IOAPICCommonState *s = opaque; + + /* ISA IRQs map to GSI 1-1 except for IRQ0 which maps + * to GSI 2. GSI maps to ioapic 1-1. This is not + * the cleanest way of doing it but it should work. */ + + DPRINTF("%s: %s vec %x\n", __func__, level ? "raise" : "lower", vector); + if (vector == 0) { + vector = 2; + } + if (vector >= 0 && vector < IOAPIC_NUM_PINS) { + uint32_t mask = 1 << vector; + uint64_t entry = s->ioredtbl[vector]; + + if (entry & (1 << IOAPIC_LVT_POLARITY_SHIFT)) { + level = !level; + } + if (((entry >> IOAPIC_LVT_TRIGGER_MODE_SHIFT) & 1) == + IOAPIC_TRIGGER_LEVEL) { + /* level triggered */ + if (level) { + s->irr |= mask; + ioapic_service(s); + } else { + s->irr &= ~mask; + } + } else { + /* According to the 82093AA manual, we must ignore edge requests + * if the input pin is masked. */ + if (level && !(entry & IOAPIC_LVT_MASKED)) { + s->irr |= mask; + ioapic_service(s); + } + } + } +} + +void ioapic_eoi_broadcast(int vector) +{ + IOAPICCommonState *s; + uint64_t entry; + int i, n; + + for (i = 0; i < MAX_IOAPICS; i++) { + s = ioapics[i]; + if (!s) { + continue; + } + for (n = 0; n < IOAPIC_NUM_PINS; n++) { + entry = s->ioredtbl[n]; + if ((entry & IOAPIC_LVT_REMOTE_IRR) + && (entry & IOAPIC_VECTOR_MASK) == vector) { + s->ioredtbl[n] = entry & ~IOAPIC_LVT_REMOTE_IRR; + if (!(entry & IOAPIC_LVT_MASKED) && (s->irr & (1 << n))) { + ioapic_service(s); + } + } + } + } +} + +static uint64_t +ioapic_mem_read(void *opaque, hwaddr addr, unsigned int size) +{ + IOAPICCommonState *s = opaque; + int index; + uint32_t val = 0; + + switch (addr & 0xff) { + case IOAPIC_IOREGSEL: + val = s->ioregsel; + break; + case IOAPIC_IOWIN: + if (size != 4) { + break; + } + switch (s->ioregsel) { + case IOAPIC_REG_ID: + val = s->id << IOAPIC_ID_SHIFT; + break; + case IOAPIC_REG_VER: + val = IOAPIC_VERSION | + ((IOAPIC_NUM_PINS - 1) << IOAPIC_VER_ENTRIES_SHIFT); + break; + case IOAPIC_REG_ARB: + val = 0; + break; + default: + index = (s->ioregsel - IOAPIC_REG_REDTBL_BASE) >> 1; + if (index >= 0 && index < IOAPIC_NUM_PINS) { + if (s->ioregsel & 1) { + val = s->ioredtbl[index] >> 32; + } else { + val = s->ioredtbl[index] & 0xffffffff; + } + } + } + DPRINTF("read: %08x = %08x\n", s->ioregsel, val); + break; + } + return val; +} + +static void +ioapic_mem_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + IOAPICCommonState *s = opaque; + int index; + + switch (addr & 0xff) { + case IOAPIC_IOREGSEL: + s->ioregsel = val; + break; + case IOAPIC_IOWIN: + if (size != 4) { + break; + } + DPRINTF("write: %08x = %08" PRIx64 "\n", s->ioregsel, val); + switch (s->ioregsel) { + case IOAPIC_REG_ID: + s->id = (val >> IOAPIC_ID_SHIFT) & IOAPIC_ID_MASK; + break; + case IOAPIC_REG_VER: + case IOAPIC_REG_ARB: + break; + default: + index = (s->ioregsel - IOAPIC_REG_REDTBL_BASE) >> 1; + if (index >= 0 && index < IOAPIC_NUM_PINS) { + if (s->ioregsel & 1) { + s->ioredtbl[index] &= 0xffffffff; + s->ioredtbl[index] |= (uint64_t)val << 32; + } else { + s->ioredtbl[index] &= ~0xffffffffULL; + s->ioredtbl[index] |= val; + } + ioapic_service(s); + } + } + break; + } +} + +static const MemoryRegionOps ioapic_io_ops = { + .read = ioapic_mem_read, + .write = ioapic_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void ioapic_init(IOAPICCommonState *s, int instance_no) +{ + memory_region_init_io(&s->io_memory, &ioapic_io_ops, s, "ioapic", 0x1000); + + qdev_init_gpio_in(&s->busdev.qdev, ioapic_set_irq, IOAPIC_NUM_PINS); + + ioapics[instance_no] = s; +} + +static void ioapic_class_init(ObjectClass *klass, void *data) +{ + IOAPICCommonClass *k = IOAPIC_COMMON_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + k->init = ioapic_init; + dc->reset = ioapic_reset_common; +} + +static const TypeInfo ioapic_info = { + .name = "ioapic", + .parent = TYPE_IOAPIC_COMMON, + .instance_size = sizeof(IOAPICCommonState), + .class_init = ioapic_class_init, +}; + +static void ioapic_register_types(void) +{ + type_register_static(&ioapic_info); +} + +type_init(ioapic_register_types) diff --git a/hw/intc/ioapic_common.c b/hw/intc/ioapic_common.c new file mode 100644 index 0000000000..42c7adc691 --- /dev/null +++ b/hw/intc/ioapic_common.c @@ -0,0 +1,120 @@ +/* + * IOAPIC emulation logic - common bits of emulated and KVM kernel model + * + * Copyright (c) 2004-2005 Fabrice Bellard + * Copyright (c) 2009 Xiantao Zhang, Intel + * Copyright (c) 2011 Jan Kiszka, Siemens AG + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "hw/i386/ioapic.h" +#include "hw/i386/ioapic_internal.h" +#include "hw/sysbus.h" + +void ioapic_reset_common(DeviceState *dev) +{ + IOAPICCommonState *s = IOAPIC_COMMON(dev); + int i; + + s->id = 0; + s->ioregsel = 0; + s->irr = 0; + for (i = 0; i < IOAPIC_NUM_PINS; i++) { + s->ioredtbl[i] = 1 << IOAPIC_LVT_MASKED_SHIFT; + } +} + +static void ioapic_dispatch_pre_save(void *opaque) +{ + IOAPICCommonState *s = IOAPIC_COMMON(opaque); + IOAPICCommonClass *info = IOAPIC_COMMON_GET_CLASS(s); + + if (info->pre_save) { + info->pre_save(s); + } +} + +static int ioapic_dispatch_post_load(void *opaque, int version_id) +{ + IOAPICCommonState *s = IOAPIC_COMMON(opaque); + IOAPICCommonClass *info = IOAPIC_COMMON_GET_CLASS(s); + + if (info->post_load) { + info->post_load(s); + } + return 0; +} + +static int ioapic_init_common(SysBusDevice *dev) +{ + IOAPICCommonState *s = FROM_SYSBUS(IOAPICCommonState, dev); + IOAPICCommonClass *info; + static int ioapic_no; + + if (ioapic_no >= MAX_IOAPICS) { + return -1; + } + + info = IOAPIC_COMMON_GET_CLASS(s); + info->init(s, ioapic_no); + + sysbus_init_mmio(&s->busdev, &s->io_memory); + ioapic_no++; + + return 0; +} + +static const VMStateDescription vmstate_ioapic_common = { + .name = "ioapic", + .version_id = 3, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .pre_save = ioapic_dispatch_pre_save, + .post_load = ioapic_dispatch_post_load, + .fields = (VMStateField[]) { + VMSTATE_UINT8(id, IOAPICCommonState), + VMSTATE_UINT8(ioregsel, IOAPICCommonState), + VMSTATE_UNUSED_V(2, 8), /* to account for qemu-kvm's v2 format */ + VMSTATE_UINT32_V(irr, IOAPICCommonState, 2), + VMSTATE_UINT64_ARRAY(ioredtbl, IOAPICCommonState, IOAPIC_NUM_PINS), + VMSTATE_END_OF_LIST() + } +}; + +static void ioapic_common_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sc = SYS_BUS_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + sc->init = ioapic_init_common; + dc->vmsd = &vmstate_ioapic_common; + dc->no_user = 1; +} + +static const TypeInfo ioapic_common_type = { + .name = TYPE_IOAPIC_COMMON, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IOAPICCommonState), + .class_size = sizeof(IOAPICCommonClass), + .class_init = ioapic_common_class_init, + .abstract = true, +}; + +static void register_types(void) +{ + type_register_static(&ioapic_common_type); +} + +type_init(register_types) diff --git a/hw/intc/lm32_pic.c b/hw/intc/lm32_pic.c new file mode 100644 index 0000000000..b4e80c8d8c --- /dev/null +++ b/hw/intc/lm32_pic.c @@ -0,0 +1,199 @@ +/* + * LatticeMico32 CPU interrupt controller logic. + * + * Copyright (c) 2010 Michael Walle <michael@walle.cc> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include "hw/hw.h" +#include "hw/i386/pc.h" +#include "monitor/monitor.h" +#include "hw/sysbus.h" +#include "trace.h" +#include "hw/lm32/lm32_pic.h" + +struct LM32PicState { + SysBusDevice busdev; + qemu_irq parent_irq; + uint32_t im; /* interrupt mask */ + uint32_t ip; /* interrupt pending */ + uint32_t irq_state; + + /* statistics */ + uint32_t stats_irq_count[32]; +}; +typedef struct LM32PicState LM32PicState; + +static LM32PicState *pic; +void lm32_do_pic_info(Monitor *mon, const QDict *qdict) +{ + if (pic == NULL) { + return; + } + + monitor_printf(mon, "lm32-pic: im=%08x ip=%08x irq_state=%08x\n", + pic->im, pic->ip, pic->irq_state); +} + +void lm32_irq_info(Monitor *mon, const QDict *qdict) +{ + int i; + uint32_t count; + + if (pic == NULL) { + return; + } + + monitor_printf(mon, "IRQ statistics:\n"); + for (i = 0; i < 32; i++) { + count = pic->stats_irq_count[i]; + if (count > 0) { + monitor_printf(mon, "%2d: %u\n", i, count); + } + } +} + +static void update_irq(LM32PicState *s) +{ + s->ip |= s->irq_state; + + if (s->ip & s->im) { + trace_lm32_pic_raise_irq(); + qemu_irq_raise(s->parent_irq); + } else { + trace_lm32_pic_lower_irq(); + qemu_irq_lower(s->parent_irq); + } +} + +static void irq_handler(void *opaque, int irq, int level) +{ + LM32PicState *s = opaque; + + assert(irq < 32); + trace_lm32_pic_interrupt(irq, level); + + if (level) { + s->irq_state |= (1 << irq); + s->stats_irq_count[irq]++; + } else { + s->irq_state &= ~(1 << irq); + } + + update_irq(s); +} + +void lm32_pic_set_im(DeviceState *d, uint32_t im) +{ + LM32PicState *s = container_of(d, LM32PicState, busdev.qdev); + + trace_lm32_pic_set_im(im); + s->im = im; + + update_irq(s); +} + +void lm32_pic_set_ip(DeviceState *d, uint32_t ip) +{ + LM32PicState *s = container_of(d, LM32PicState, busdev.qdev); + + trace_lm32_pic_set_ip(ip); + + /* ack interrupt */ + s->ip &= ~ip; + + update_irq(s); +} + +uint32_t lm32_pic_get_im(DeviceState *d) +{ + LM32PicState *s = container_of(d, LM32PicState, busdev.qdev); + + trace_lm32_pic_get_im(s->im); + return s->im; +} + +uint32_t lm32_pic_get_ip(DeviceState *d) +{ + LM32PicState *s = container_of(d, LM32PicState, busdev.qdev); + + trace_lm32_pic_get_ip(s->ip); + return s->ip; +} + +static void pic_reset(DeviceState *d) +{ + LM32PicState *s = container_of(d, LM32PicState, busdev.qdev); + int i; + + s->im = 0; + s->ip = 0; + s->irq_state = 0; + for (i = 0; i < 32; i++) { + s->stats_irq_count[i] = 0; + } +} + +static int lm32_pic_init(SysBusDevice *dev) +{ + LM32PicState *s = FROM_SYSBUS(typeof(*s), dev); + + qdev_init_gpio_in(&dev->qdev, irq_handler, 32); + sysbus_init_irq(dev, &s->parent_irq); + + pic = s; + + return 0; +} + +static const VMStateDescription vmstate_lm32_pic = { + .name = "lm32-pic", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(im, LM32PicState), + VMSTATE_UINT32(ip, LM32PicState), + VMSTATE_UINT32(irq_state, LM32PicState), + VMSTATE_UINT32_ARRAY(stats_irq_count, LM32PicState, 32), + VMSTATE_END_OF_LIST() + } +}; + +static void lm32_pic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = lm32_pic_init; + dc->reset = pic_reset; + dc->vmsd = &vmstate_lm32_pic; +} + +static const TypeInfo lm32_pic_info = { + .name = "lm32-pic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(LM32PicState), + .class_init = lm32_pic_class_init, +}; + +static void lm32_pic_register_types(void) +{ + type_register_static(&lm32_pic_info); +} + +type_init(lm32_pic_register_types) diff --git a/hw/intc/omap_intc.c b/hw/intc/omap_intc.c new file mode 100644 index 0000000000..875eba4e3e --- /dev/null +++ b/hw/intc/omap_intc.c @@ -0,0 +1,649 @@ +/* + * TI OMAP interrupt controller emulation. + * + * Copyright (C) 2006-2008 Andrzej Zaborowski <balrog@zabor.org> + * Copyright (C) 2007-2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ +#include "hw/hw.h" +#include "hw/arm/omap.h" +#include "hw/sysbus.h" + +/* Interrupt Handlers */ +struct omap_intr_handler_bank_s { + uint32_t irqs; + uint32_t inputs; + uint32_t mask; + uint32_t fiq; + uint32_t sens_edge; + uint32_t swi; + unsigned char priority[32]; +}; + +struct omap_intr_handler_s { + SysBusDevice busdev; + qemu_irq *pins; + qemu_irq parent_intr[2]; + MemoryRegion mmio; + void *iclk; + void *fclk; + unsigned char nbanks; + int level_only; + uint32_t size; + + uint8_t revision; + + /* state */ + uint32_t new_agr[2]; + int sir_intr[2]; + int autoidle; + uint32_t mask; + struct omap_intr_handler_bank_s bank[3]; +}; + +static void omap_inth_sir_update(struct omap_intr_handler_s *s, int is_fiq) +{ + int i, j, sir_intr, p_intr, p, f; + uint32_t level; + sir_intr = 0; + p_intr = 255; + + /* Find the interrupt line with the highest dynamic priority. + * Note: 0 denotes the hightest priority. + * If all interrupts have the same priority, the default order is IRQ_N, + * IRQ_N-1,...,IRQ_0. */ + for (j = 0; j < s->nbanks; ++j) { + level = s->bank[j].irqs & ~s->bank[j].mask & + (is_fiq ? s->bank[j].fiq : ~s->bank[j].fiq); + for (f = ffs(level), i = f - 1, level >>= f - 1; f; i += f, + level >>= f) { + p = s->bank[j].priority[i]; + if (p <= p_intr) { + p_intr = p; + sir_intr = 32 * j + i; + } + f = ffs(level >> 1); + } + } + s->sir_intr[is_fiq] = sir_intr; +} + +static inline void omap_inth_update(struct omap_intr_handler_s *s, int is_fiq) +{ + int i; + uint32_t has_intr = 0; + + for (i = 0; i < s->nbanks; ++i) + has_intr |= s->bank[i].irqs & ~s->bank[i].mask & + (is_fiq ? s->bank[i].fiq : ~s->bank[i].fiq); + + if (s->new_agr[is_fiq] & has_intr & s->mask) { + s->new_agr[is_fiq] = 0; + omap_inth_sir_update(s, is_fiq); + qemu_set_irq(s->parent_intr[is_fiq], 1); + } +} + +#define INT_FALLING_EDGE 0 +#define INT_LOW_LEVEL 1 + +static void omap_set_intr(void *opaque, int irq, int req) +{ + struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque; + uint32_t rise; + + struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5]; + int n = irq & 31; + + if (req) { + rise = ~bank->irqs & (1 << n); + if (~bank->sens_edge & (1 << n)) + rise &= ~bank->inputs; + + bank->inputs |= (1 << n); + if (rise) { + bank->irqs |= rise; + omap_inth_update(ih, 0); + omap_inth_update(ih, 1); + } + } else { + rise = bank->sens_edge & bank->irqs & (1 << n); + bank->irqs &= ~rise; + bank->inputs &= ~(1 << n); + } +} + +/* Simplified version with no edge detection */ +static void omap_set_intr_noedge(void *opaque, int irq, int req) +{ + struct omap_intr_handler_s *ih = (struct omap_intr_handler_s *) opaque; + uint32_t rise; + + struct omap_intr_handler_bank_s *bank = &ih->bank[irq >> 5]; + int n = irq & 31; + + if (req) { + rise = ~bank->inputs & (1 << n); + if (rise) { + bank->irqs |= bank->inputs |= rise; + omap_inth_update(ih, 0); + omap_inth_update(ih, 1); + } + } else + bank->irqs = (bank->inputs &= ~(1 << n)) | bank->swi; +} + +static uint64_t omap_inth_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque; + int i, offset = addr; + int bank_no = offset >> 8; + int line_no; + struct omap_intr_handler_bank_s *bank = &s->bank[bank_no]; + offset &= 0xff; + + switch (offset) { + case 0x00: /* ITR */ + return bank->irqs; + + case 0x04: /* MIR */ + return bank->mask; + + case 0x10: /* SIR_IRQ_CODE */ + case 0x14: /* SIR_FIQ_CODE */ + if (bank_no != 0) + break; + line_no = s->sir_intr[(offset - 0x10) >> 2]; + bank = &s->bank[line_no >> 5]; + i = line_no & 31; + if (((bank->sens_edge >> i) & 1) == INT_FALLING_EDGE) + bank->irqs &= ~(1 << i); + return line_no; + + case 0x18: /* CONTROL_REG */ + if (bank_no != 0) + break; + return 0; + + case 0x1c: /* ILR0 */ + case 0x20: /* ILR1 */ + case 0x24: /* ILR2 */ + case 0x28: /* ILR3 */ + case 0x2c: /* ILR4 */ + case 0x30: /* ILR5 */ + case 0x34: /* ILR6 */ + case 0x38: /* ILR7 */ + case 0x3c: /* ILR8 */ + case 0x40: /* ILR9 */ + case 0x44: /* ILR10 */ + case 0x48: /* ILR11 */ + case 0x4c: /* ILR12 */ + case 0x50: /* ILR13 */ + case 0x54: /* ILR14 */ + case 0x58: /* ILR15 */ + case 0x5c: /* ILR16 */ + case 0x60: /* ILR17 */ + case 0x64: /* ILR18 */ + case 0x68: /* ILR19 */ + case 0x6c: /* ILR20 */ + case 0x70: /* ILR21 */ + case 0x74: /* ILR22 */ + case 0x78: /* ILR23 */ + case 0x7c: /* ILR24 */ + case 0x80: /* ILR25 */ + case 0x84: /* ILR26 */ + case 0x88: /* ILR27 */ + case 0x8c: /* ILR28 */ + case 0x90: /* ILR29 */ + case 0x94: /* ILR30 */ + case 0x98: /* ILR31 */ + i = (offset - 0x1c) >> 2; + return (bank->priority[i] << 2) | + (((bank->sens_edge >> i) & 1) << 1) | + ((bank->fiq >> i) & 1); + + case 0x9c: /* ISR */ + return 0x00000000; + + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap_inth_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque; + int i, offset = addr; + int bank_no = offset >> 8; + struct omap_intr_handler_bank_s *bank = &s->bank[bank_no]; + offset &= 0xff; + + switch (offset) { + case 0x00: /* ITR */ + /* Important: ignore the clearing if the IRQ is level-triggered and + the input bit is 1 */ + bank->irqs &= value | (bank->inputs & bank->sens_edge); + return; + + case 0x04: /* MIR */ + bank->mask = value; + omap_inth_update(s, 0); + omap_inth_update(s, 1); + return; + + case 0x10: /* SIR_IRQ_CODE */ + case 0x14: /* SIR_FIQ_CODE */ + OMAP_RO_REG(addr); + break; + + case 0x18: /* CONTROL_REG */ + if (bank_no != 0) + break; + if (value & 2) { + qemu_set_irq(s->parent_intr[1], 0); + s->new_agr[1] = ~0; + omap_inth_update(s, 1); + } + if (value & 1) { + qemu_set_irq(s->parent_intr[0], 0); + s->new_agr[0] = ~0; + omap_inth_update(s, 0); + } + return; + + case 0x1c: /* ILR0 */ + case 0x20: /* ILR1 */ + case 0x24: /* ILR2 */ + case 0x28: /* ILR3 */ + case 0x2c: /* ILR4 */ + case 0x30: /* ILR5 */ + case 0x34: /* ILR6 */ + case 0x38: /* ILR7 */ + case 0x3c: /* ILR8 */ + case 0x40: /* ILR9 */ + case 0x44: /* ILR10 */ + case 0x48: /* ILR11 */ + case 0x4c: /* ILR12 */ + case 0x50: /* ILR13 */ + case 0x54: /* ILR14 */ + case 0x58: /* ILR15 */ + case 0x5c: /* ILR16 */ + case 0x60: /* ILR17 */ + case 0x64: /* ILR18 */ + case 0x68: /* ILR19 */ + case 0x6c: /* ILR20 */ + case 0x70: /* ILR21 */ + case 0x74: /* ILR22 */ + case 0x78: /* ILR23 */ + case 0x7c: /* ILR24 */ + case 0x80: /* ILR25 */ + case 0x84: /* ILR26 */ + case 0x88: /* ILR27 */ + case 0x8c: /* ILR28 */ + case 0x90: /* ILR29 */ + case 0x94: /* ILR30 */ + case 0x98: /* ILR31 */ + i = (offset - 0x1c) >> 2; + bank->priority[i] = (value >> 2) & 0x1f; + bank->sens_edge &= ~(1 << i); + bank->sens_edge |= ((value >> 1) & 1) << i; + bank->fiq &= ~(1 << i); + bank->fiq |= (value & 1) << i; + return; + + case 0x9c: /* ISR */ + for (i = 0; i < 32; i ++) + if (value & (1 << i)) { + omap_set_intr(s, 32 * bank_no + i, 1); + return; + } + return; + } + OMAP_BAD_REG(addr); +} + +static const MemoryRegionOps omap_inth_mem_ops = { + .read = omap_inth_read, + .write = omap_inth_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void omap_inth_reset(DeviceState *dev) +{ + struct omap_intr_handler_s *s = FROM_SYSBUS(struct omap_intr_handler_s, + SYS_BUS_DEVICE(dev)); + int i; + + for (i = 0; i < s->nbanks; ++i){ + s->bank[i].irqs = 0x00000000; + s->bank[i].mask = 0xffffffff; + s->bank[i].sens_edge = 0x00000000; + s->bank[i].fiq = 0x00000000; + s->bank[i].inputs = 0x00000000; + s->bank[i].swi = 0x00000000; + memset(s->bank[i].priority, 0, sizeof(s->bank[i].priority)); + + if (s->level_only) + s->bank[i].sens_edge = 0xffffffff; + } + + s->new_agr[0] = ~0; + s->new_agr[1] = ~0; + s->sir_intr[0] = 0; + s->sir_intr[1] = 0; + s->autoidle = 0; + s->mask = ~0; + + qemu_set_irq(s->parent_intr[0], 0); + qemu_set_irq(s->parent_intr[1], 0); +} + +static int omap_intc_init(SysBusDevice *dev) +{ + struct omap_intr_handler_s *s; + s = FROM_SYSBUS(struct omap_intr_handler_s, dev); + if (!s->iclk) { + hw_error("omap-intc: clk not connected\n"); + } + s->nbanks = 1; + sysbus_init_irq(dev, &s->parent_intr[0]); + sysbus_init_irq(dev, &s->parent_intr[1]); + qdev_init_gpio_in(&dev->qdev, omap_set_intr, s->nbanks * 32); + memory_region_init_io(&s->mmio, &omap_inth_mem_ops, s, + "omap-intc", s->size); + sysbus_init_mmio(dev, &s->mmio); + return 0; +} + +static Property omap_intc_properties[] = { + DEFINE_PROP_UINT32("size", struct omap_intr_handler_s, size, 0x100), + DEFINE_PROP_PTR("clk", struct omap_intr_handler_s, iclk), + DEFINE_PROP_END_OF_LIST(), +}; + +static void omap_intc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = omap_intc_init; + dc->reset = omap_inth_reset; + dc->props = omap_intc_properties; +} + +static const TypeInfo omap_intc_info = { + .name = "omap-intc", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct omap_intr_handler_s), + .class_init = omap_intc_class_init, +}; + +static uint64_t omap2_inth_read(void *opaque, hwaddr addr, + unsigned size) +{ + struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque; + int offset = addr; + int bank_no, line_no; + struct omap_intr_handler_bank_s *bank = NULL; + + if ((offset & 0xf80) == 0x80) { + bank_no = (offset & 0x60) >> 5; + if (bank_no < s->nbanks) { + offset &= ~0x60; + bank = &s->bank[bank_no]; + } else { + OMAP_BAD_REG(addr); + return 0; + } + } + + switch (offset) { + case 0x00: /* INTC_REVISION */ + return s->revision; + + case 0x10: /* INTC_SYSCONFIG */ + return (s->autoidle >> 2) & 1; + + case 0x14: /* INTC_SYSSTATUS */ + return 1; /* RESETDONE */ + + case 0x40: /* INTC_SIR_IRQ */ + return s->sir_intr[0]; + + case 0x44: /* INTC_SIR_FIQ */ + return s->sir_intr[1]; + + case 0x48: /* INTC_CONTROL */ + return (!s->mask) << 2; /* GLOBALMASK */ + + case 0x4c: /* INTC_PROTECTION */ + return 0; + + case 0x50: /* INTC_IDLE */ + return s->autoidle & 3; + + /* Per-bank registers */ + case 0x80: /* INTC_ITR */ + return bank->inputs; + + case 0x84: /* INTC_MIR */ + return bank->mask; + + case 0x88: /* INTC_MIR_CLEAR */ + case 0x8c: /* INTC_MIR_SET */ + return 0; + + case 0x90: /* INTC_ISR_SET */ + return bank->swi; + + case 0x94: /* INTC_ISR_CLEAR */ + return 0; + + case 0x98: /* INTC_PENDING_IRQ */ + return bank->irqs & ~bank->mask & ~bank->fiq; + + case 0x9c: /* INTC_PENDING_FIQ */ + return bank->irqs & ~bank->mask & bank->fiq; + + /* Per-line registers */ + case 0x100 ... 0x300: /* INTC_ILR */ + bank_no = (offset - 0x100) >> 7; + if (bank_no > s->nbanks) + break; + bank = &s->bank[bank_no]; + line_no = (offset & 0x7f) >> 2; + return (bank->priority[line_no] << 2) | + ((bank->fiq >> line_no) & 1); + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap2_inth_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + struct omap_intr_handler_s *s = (struct omap_intr_handler_s *) opaque; + int offset = addr; + int bank_no, line_no; + struct omap_intr_handler_bank_s *bank = NULL; + + if ((offset & 0xf80) == 0x80) { + bank_no = (offset & 0x60) >> 5; + if (bank_no < s->nbanks) { + offset &= ~0x60; + bank = &s->bank[bank_no]; + } else { + OMAP_BAD_REG(addr); + return; + } + } + + switch (offset) { + case 0x10: /* INTC_SYSCONFIG */ + s->autoidle &= 4; + s->autoidle |= (value & 1) << 2; + if (value & 2) /* SOFTRESET */ + omap_inth_reset(&s->busdev.qdev); + return; + + case 0x48: /* INTC_CONTROL */ + s->mask = (value & 4) ? 0 : ~0; /* GLOBALMASK */ + if (value & 2) { /* NEWFIQAGR */ + qemu_set_irq(s->parent_intr[1], 0); + s->new_agr[1] = ~0; + omap_inth_update(s, 1); + } + if (value & 1) { /* NEWIRQAGR */ + qemu_set_irq(s->parent_intr[0], 0); + s->new_agr[0] = ~0; + omap_inth_update(s, 0); + } + return; + + case 0x4c: /* INTC_PROTECTION */ + /* TODO: Make a bitmap (or sizeof(char)map) of access privileges + * for every register, see Chapter 3 and 4 for privileged mode. */ + if (value & 1) + fprintf(stderr, "%s: protection mode enable attempt\n", + __FUNCTION__); + return; + + case 0x50: /* INTC_IDLE */ + s->autoidle &= ~3; + s->autoidle |= value & 3; + return; + + /* Per-bank registers */ + case 0x84: /* INTC_MIR */ + bank->mask = value; + omap_inth_update(s, 0); + omap_inth_update(s, 1); + return; + + case 0x88: /* INTC_MIR_CLEAR */ + bank->mask &= ~value; + omap_inth_update(s, 0); + omap_inth_update(s, 1); + return; + + case 0x8c: /* INTC_MIR_SET */ + bank->mask |= value; + return; + + case 0x90: /* INTC_ISR_SET */ + bank->irqs |= bank->swi |= value; + omap_inth_update(s, 0); + omap_inth_update(s, 1); + return; + + case 0x94: /* INTC_ISR_CLEAR */ + bank->swi &= ~value; + bank->irqs = bank->swi & bank->inputs; + return; + + /* Per-line registers */ + case 0x100 ... 0x300: /* INTC_ILR */ + bank_no = (offset - 0x100) >> 7; + if (bank_no > s->nbanks) + break; + bank = &s->bank[bank_no]; + line_no = (offset & 0x7f) >> 2; + bank->priority[line_no] = (value >> 2) & 0x3f; + bank->fiq &= ~(1 << line_no); + bank->fiq |= (value & 1) << line_no; + return; + + case 0x00: /* INTC_REVISION */ + case 0x14: /* INTC_SYSSTATUS */ + case 0x40: /* INTC_SIR_IRQ */ + case 0x44: /* INTC_SIR_FIQ */ + case 0x80: /* INTC_ITR */ + case 0x98: /* INTC_PENDING_IRQ */ + case 0x9c: /* INTC_PENDING_FIQ */ + OMAP_RO_REG(addr); + return; + } + OMAP_BAD_REG(addr); +} + +static const MemoryRegionOps omap2_inth_mem_ops = { + .read = omap2_inth_read, + .write = omap2_inth_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static int omap2_intc_init(SysBusDevice *dev) +{ + struct omap_intr_handler_s *s; + s = FROM_SYSBUS(struct omap_intr_handler_s, dev); + if (!s->iclk) { + hw_error("omap2-intc: iclk not connected\n"); + } + if (!s->fclk) { + hw_error("omap2-intc: fclk not connected\n"); + } + s->level_only = 1; + s->nbanks = 3; + sysbus_init_irq(dev, &s->parent_intr[0]); + sysbus_init_irq(dev, &s->parent_intr[1]); + qdev_init_gpio_in(&dev->qdev, omap_set_intr_noedge, s->nbanks * 32); + memory_region_init_io(&s->mmio, &omap2_inth_mem_ops, s, + "omap2-intc", 0x1000); + sysbus_init_mmio(dev, &s->mmio); + return 0; +} + +static Property omap2_intc_properties[] = { + DEFINE_PROP_UINT8("revision", struct omap_intr_handler_s, + revision, 0x21), + DEFINE_PROP_PTR("iclk", struct omap_intr_handler_s, iclk), + DEFINE_PROP_PTR("fclk", struct omap_intr_handler_s, fclk), + DEFINE_PROP_END_OF_LIST(), +}; + +static void omap2_intc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = omap2_intc_init; + dc->reset = omap_inth_reset; + dc->props = omap2_intc_properties; +} + +static const TypeInfo omap2_intc_info = { + .name = "omap2-intc", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(struct omap_intr_handler_s), + .class_init = omap2_intc_class_init, +}; + +static void omap_intc_register_types(void) +{ + type_register_static(&omap_intc_info); + type_register_static(&omap2_intc_info); +} + +type_init(omap_intc_register_types) diff --git a/hw/intc/openpic.c b/hw/intc/openpic.c new file mode 100644 index 0000000000..c78871445b --- /dev/null +++ b/hw/intc/openpic.c @@ -0,0 +1,1661 @@ +/* + * OpenPIC emulation + * + * Copyright (c) 2004 Jocelyn Mayer + * 2011 Alexander Graf + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/* + * + * Based on OpenPic implementations: + * - Intel GW80314 I/O companion chip developer's manual + * - Motorola MPC8245 & MPC8540 user manuals. + * - Motorola MCP750 (aka Raven) programmer manual. + * - Motorola Harrier programmer manuel + * + * Serial interrupts, as implemented in Raven chipset are not supported yet. + * + */ +#include "hw/hw.h" +#include "hw/ppc/mac.h" +#include "hw/pci/pci.h" +#include "hw/ppc/openpic.h" +#include "hw/sysbus.h" +#include "hw/pci/msi.h" +#include "qemu/bitops.h" +#include "hw/ppc/ppc.h" + +//#define DEBUG_OPENPIC + +#ifdef DEBUG_OPENPIC +static const int debug_openpic = 1; +#else +static const int debug_openpic = 0; +#endif + +#define DPRINTF(fmt, ...) do { \ + if (debug_openpic) { \ + printf(fmt , ## __VA_ARGS__); \ + } \ + } while (0) + +#define MAX_CPU 32 +#define MAX_SRC 256 +#define MAX_TMR 4 +#define MAX_IPI 4 +#define MAX_MSI 8 +#define MAX_IRQ (MAX_SRC + MAX_IPI + MAX_TMR) +#define VID 0x03 /* MPIC version ID */ + +/* OpenPIC capability flags */ +#define OPENPIC_FLAG_IDR_CRIT (1 << 0) +#define OPENPIC_FLAG_ILR (2 << 0) + +/* OpenPIC address map */ +#define OPENPIC_GLB_REG_START 0x0 +#define OPENPIC_GLB_REG_SIZE 0x10F0 +#define OPENPIC_TMR_REG_START 0x10F0 +#define OPENPIC_TMR_REG_SIZE 0x220 +#define OPENPIC_MSI_REG_START 0x1600 +#define OPENPIC_MSI_REG_SIZE 0x200 +#define OPENPIC_SUMMARY_REG_START 0x3800 +#define OPENPIC_SUMMARY_REG_SIZE 0x800 +#define OPENPIC_SRC_REG_START 0x10000 +#define OPENPIC_SRC_REG_SIZE (MAX_SRC * 0x20) +#define OPENPIC_CPU_REG_START 0x20000 +#define OPENPIC_CPU_REG_SIZE 0x100 + ((MAX_CPU - 1) * 0x1000) + +/* Raven */ +#define RAVEN_MAX_CPU 2 +#define RAVEN_MAX_EXT 48 +#define RAVEN_MAX_IRQ 64 +#define RAVEN_MAX_TMR MAX_TMR +#define RAVEN_MAX_IPI MAX_IPI + +/* Interrupt definitions */ +#define RAVEN_FE_IRQ (RAVEN_MAX_EXT) /* Internal functional IRQ */ +#define RAVEN_ERR_IRQ (RAVEN_MAX_EXT + 1) /* Error IRQ */ +#define RAVEN_TMR_IRQ (RAVEN_MAX_EXT + 2) /* First timer IRQ */ +#define RAVEN_IPI_IRQ (RAVEN_TMR_IRQ + RAVEN_MAX_TMR) /* First IPI IRQ */ +/* First doorbell IRQ */ +#define RAVEN_DBL_IRQ (RAVEN_IPI_IRQ + (RAVEN_MAX_CPU * RAVEN_MAX_IPI)) + +typedef struct FslMpicInfo { + int max_ext; +} FslMpicInfo; + +static FslMpicInfo fsl_mpic_20 = { + .max_ext = 12, +}; + +static FslMpicInfo fsl_mpic_42 = { + .max_ext = 12, +}; + +#define FRR_NIRQ_SHIFT 16 +#define FRR_NCPU_SHIFT 8 +#define FRR_VID_SHIFT 0 + +#define VID_REVISION_1_2 2 +#define VID_REVISION_1_3 3 + +#define VIR_GENERIC 0x00000000 /* Generic Vendor ID */ + +#define GCR_RESET 0x80000000 +#define GCR_MODE_PASS 0x00000000 +#define GCR_MODE_MIXED 0x20000000 +#define GCR_MODE_PROXY 0x60000000 + +#define TBCR_CI 0x80000000 /* count inhibit */ +#define TCCR_TOG 0x80000000 /* toggles when decrement to zero */ + +#define IDR_EP_SHIFT 31 +#define IDR_EP_MASK (1 << IDR_EP_SHIFT) +#define IDR_CI0_SHIFT 30 +#define IDR_CI1_SHIFT 29 +#define IDR_P1_SHIFT 1 +#define IDR_P0_SHIFT 0 + +#define ILR_INTTGT_MASK 0x000000ff +#define ILR_INTTGT_INT 0x00 +#define ILR_INTTGT_CINT 0x01 /* critical */ +#define ILR_INTTGT_MCP 0x02 /* machine check */ + +/* The currently supported INTTGT values happen to be the same as QEMU's + * openpic output codes, but don't depend on this. The output codes + * could change (unlikely, but...) or support could be added for + * more INTTGT values. + */ +static const int inttgt_output[][2] = { + { ILR_INTTGT_INT, OPENPIC_OUTPUT_INT }, + { ILR_INTTGT_CINT, OPENPIC_OUTPUT_CINT }, + { ILR_INTTGT_MCP, OPENPIC_OUTPUT_MCK }, +}; + +static int inttgt_to_output(int inttgt) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(inttgt_output); i++) { + if (inttgt_output[i][0] == inttgt) { + return inttgt_output[i][1]; + } + } + + fprintf(stderr, "%s: unsupported inttgt %d\n", __func__, inttgt); + return OPENPIC_OUTPUT_INT; +} + +static int output_to_inttgt(int output) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(inttgt_output); i++) { + if (inttgt_output[i][1] == output) { + return inttgt_output[i][0]; + } + } + + abort(); +} + +#define MSIIR_OFFSET 0x140 +#define MSIIR_SRS_SHIFT 29 +#define MSIIR_SRS_MASK (0x7 << MSIIR_SRS_SHIFT) +#define MSIIR_IBS_SHIFT 24 +#define MSIIR_IBS_MASK (0x1f << MSIIR_IBS_SHIFT) + +static int get_current_cpu(void) +{ + CPUState *cpu_single_cpu; + + if (!cpu_single_env) { + return -1; + } + + cpu_single_cpu = ENV_GET_CPU(cpu_single_env); + return cpu_single_cpu->cpu_index; +} + +static uint32_t openpic_cpu_read_internal(void *opaque, hwaddr addr, + int idx); +static void openpic_cpu_write_internal(void *opaque, hwaddr addr, + uint32_t val, int idx); + +typedef enum IRQType { + IRQ_TYPE_NORMAL = 0, + IRQ_TYPE_FSLINT, /* FSL internal interrupt -- level only */ + IRQ_TYPE_FSLSPECIAL, /* FSL timer/IPI interrupt, edge, no polarity */ +} IRQType; + +typedef struct IRQQueue { + /* Round up to the nearest 64 IRQs so that the queue length + * won't change when moving between 32 and 64 bit hosts. + */ + unsigned long queue[BITS_TO_LONGS((MAX_IRQ + 63) & ~63)]; + int next; + int priority; +} IRQQueue; + +typedef struct IRQSource { + uint32_t ivpr; /* IRQ vector/priority register */ + uint32_t idr; /* IRQ destination register */ + uint32_t destmask; /* bitmap of CPU destinations */ + int last_cpu; + int output; /* IRQ level, e.g. OPENPIC_OUTPUT_INT */ + int pending; /* TRUE if IRQ is pending */ + IRQType type; + bool level:1; /* level-triggered */ + bool nomask:1; /* critical interrupts ignore mask on some FSL MPICs */ +} IRQSource; + +#define IVPR_MASK_SHIFT 31 +#define IVPR_MASK_MASK (1 << IVPR_MASK_SHIFT) +#define IVPR_ACTIVITY_SHIFT 30 +#define IVPR_ACTIVITY_MASK (1 << IVPR_ACTIVITY_SHIFT) +#define IVPR_MODE_SHIFT 29 +#define IVPR_MODE_MASK (1 << IVPR_MODE_SHIFT) +#define IVPR_POLARITY_SHIFT 23 +#define IVPR_POLARITY_MASK (1 << IVPR_POLARITY_SHIFT) +#define IVPR_SENSE_SHIFT 22 +#define IVPR_SENSE_MASK (1 << IVPR_SENSE_SHIFT) + +#define IVPR_PRIORITY_MASK (0xF << 16) +#define IVPR_PRIORITY(_ivprr_) ((int)(((_ivprr_) & IVPR_PRIORITY_MASK) >> 16)) +#define IVPR_VECTOR(opp, _ivprr_) ((_ivprr_) & (opp)->vector_mask) + +/* IDR[EP/CI] are only for FSL MPIC prior to v4.0 */ +#define IDR_EP 0x80000000 /* external pin */ +#define IDR_CI 0x40000000 /* critical interrupt */ + +typedef struct IRQDest { + int32_t ctpr; /* CPU current task priority */ + IRQQueue raised; + IRQQueue servicing; + qemu_irq *irqs; + + /* Count of IRQ sources asserting on non-INT outputs */ + uint32_t outputs_active[OPENPIC_OUTPUT_NB]; +} IRQDest; + +typedef struct OpenPICState { + SysBusDevice busdev; + MemoryRegion mem; + + /* Behavior control */ + FslMpicInfo *fsl; + uint32_t model; + uint32_t flags; + uint32_t nb_irqs; + uint32_t vid; + uint32_t vir; /* Vendor identification register */ + uint32_t vector_mask; + uint32_t tfrr_reset; + uint32_t ivpr_reset; + uint32_t idr_reset; + uint32_t brr1; + uint32_t mpic_mode_mask; + + /* Sub-regions */ + MemoryRegion sub_io_mem[6]; + + /* Global registers */ + uint32_t frr; /* Feature reporting register */ + uint32_t gcr; /* Global configuration register */ + uint32_t pir; /* Processor initialization register */ + uint32_t spve; /* Spurious vector register */ + uint32_t tfrr; /* Timer frequency reporting register */ + /* Source registers */ + IRQSource src[MAX_IRQ]; + /* Local registers per output pin */ + IRQDest dst[MAX_CPU]; + uint32_t nb_cpus; + /* Timer registers */ + struct { + uint32_t tccr; /* Global timer current count register */ + uint32_t tbcr; /* Global timer base count register */ + } timers[MAX_TMR]; + /* Shared MSI registers */ + struct { + uint32_t msir; /* Shared Message Signaled Interrupt Register */ + } msi[MAX_MSI]; + uint32_t max_irq; + uint32_t irq_ipi0; + uint32_t irq_tim0; + uint32_t irq_msi; +} OpenPICState; + +static inline void IRQ_setbit(IRQQueue *q, int n_IRQ) +{ + set_bit(n_IRQ, q->queue); +} + +static inline void IRQ_resetbit(IRQQueue *q, int n_IRQ) +{ + clear_bit(n_IRQ, q->queue); +} + +static inline int IRQ_testbit(IRQQueue *q, int n_IRQ) +{ + return test_bit(n_IRQ, q->queue); +} + +static void IRQ_check(OpenPICState *opp, IRQQueue *q) +{ + int irq = -1; + int next = -1; + int priority = -1; + + for (;;) { + irq = find_next_bit(q->queue, opp->max_irq, irq + 1); + if (irq == opp->max_irq) { + break; + } + + DPRINTF("IRQ_check: irq %d set ivpr_pr=%d pr=%d\n", + irq, IVPR_PRIORITY(opp->src[irq].ivpr), priority); + + if (IVPR_PRIORITY(opp->src[irq].ivpr) > priority) { + next = irq; + priority = IVPR_PRIORITY(opp->src[irq].ivpr); + } + } + + q->next = next; + q->priority = priority; +} + +static int IRQ_get_next(OpenPICState *opp, IRQQueue *q) +{ + /* XXX: optimize */ + IRQ_check(opp, q); + + return q->next; +} + +static void IRQ_local_pipe(OpenPICState *opp, int n_CPU, int n_IRQ, + bool active, bool was_active) +{ + IRQDest *dst; + IRQSource *src; + int priority; + + dst = &opp->dst[n_CPU]; + src = &opp->src[n_IRQ]; + + DPRINTF("%s: IRQ %d active %d was %d\n", + __func__, n_IRQ, active, was_active); + + if (src->output != OPENPIC_OUTPUT_INT) { + DPRINTF("%s: output %d irq %d active %d was %d count %d\n", + __func__, src->output, n_IRQ, active, was_active, + dst->outputs_active[src->output]); + + /* On Freescale MPIC, critical interrupts ignore priority, + * IACK, EOI, etc. Before MPIC v4.1 they also ignore + * masking. + */ + if (active) { + if (!was_active && dst->outputs_active[src->output]++ == 0) { + DPRINTF("%s: Raise OpenPIC output %d cpu %d irq %d\n", + __func__, src->output, n_CPU, n_IRQ); + qemu_irq_raise(dst->irqs[src->output]); + } + } else { + if (was_active && --dst->outputs_active[src->output] == 0) { + DPRINTF("%s: Lower OpenPIC output %d cpu %d irq %d\n", + __func__, src->output, n_CPU, n_IRQ); + qemu_irq_lower(dst->irqs[src->output]); + } + } + + return; + } + + priority = IVPR_PRIORITY(src->ivpr); + + /* Even if the interrupt doesn't have enough priority, + * it is still raised, in case ctpr is lowered later. + */ + if (active) { + IRQ_setbit(&dst->raised, n_IRQ); + } else { + IRQ_resetbit(&dst->raised, n_IRQ); + } + + IRQ_check(opp, &dst->raised); + + if (active && priority <= dst->ctpr) { + DPRINTF("%s: IRQ %d priority %d too low for ctpr %d on CPU %d\n", + __func__, n_IRQ, priority, dst->ctpr, n_CPU); + active = 0; + } + + if (active) { + if (IRQ_get_next(opp, &dst->servicing) >= 0 && + priority <= dst->servicing.priority) { + DPRINTF("%s: IRQ %d is hidden by servicing IRQ %d on CPU %d\n", + __func__, n_IRQ, dst->servicing.next, n_CPU); + } else { + DPRINTF("%s: Raise OpenPIC INT output cpu %d irq %d/%d\n", + __func__, n_CPU, n_IRQ, dst->raised.next); + qemu_irq_raise(opp->dst[n_CPU].irqs[OPENPIC_OUTPUT_INT]); + } + } else { + IRQ_get_next(opp, &dst->servicing); + if (dst->raised.priority > dst->ctpr && + dst->raised.priority > dst->servicing.priority) { + DPRINTF("%s: IRQ %d inactive, IRQ %d prio %d above %d/%d, CPU %d\n", + __func__, n_IRQ, dst->raised.next, dst->raised.priority, + dst->ctpr, dst->servicing.priority, n_CPU); + /* IRQ line stays asserted */ + } else { + DPRINTF("%s: IRQ %d inactive, current prio %d/%d, CPU %d\n", + __func__, n_IRQ, dst->ctpr, dst->servicing.priority, n_CPU); + qemu_irq_lower(opp->dst[n_CPU].irqs[OPENPIC_OUTPUT_INT]); + } + } +} + +/* update pic state because registers for n_IRQ have changed value */ +static void openpic_update_irq(OpenPICState *opp, int n_IRQ) +{ + IRQSource *src; + bool active, was_active; + int i; + + src = &opp->src[n_IRQ]; + active = src->pending; + + if ((src->ivpr & IVPR_MASK_MASK) && !src->nomask) { + /* Interrupt source is disabled */ + DPRINTF("%s: IRQ %d is disabled\n", __func__, n_IRQ); + active = false; + } + + was_active = !!(src->ivpr & IVPR_ACTIVITY_MASK); + + /* + * We don't have a similar check for already-active because + * ctpr may have changed and we need to withdraw the interrupt. + */ + if (!active && !was_active) { + DPRINTF("%s: IRQ %d is already inactive\n", __func__, n_IRQ); + return; + } + + if (active) { + src->ivpr |= IVPR_ACTIVITY_MASK; + } else { + src->ivpr &= ~IVPR_ACTIVITY_MASK; + } + + if (src->destmask == 0) { + /* No target */ + DPRINTF("%s: IRQ %d has no target\n", __func__, n_IRQ); + return; + } + + if (src->destmask == (1 << src->last_cpu)) { + /* Only one CPU is allowed to receive this IRQ */ + IRQ_local_pipe(opp, src->last_cpu, n_IRQ, active, was_active); + } else if (!(src->ivpr & IVPR_MODE_MASK)) { + /* Directed delivery mode */ + for (i = 0; i < opp->nb_cpus; i++) { + if (src->destmask & (1 << i)) { + IRQ_local_pipe(opp, i, n_IRQ, active, was_active); + } + } + } else { + /* Distributed delivery mode */ + for (i = src->last_cpu + 1; i != src->last_cpu; i++) { + if (i == opp->nb_cpus) { + i = 0; + } + if (src->destmask & (1 << i)) { + IRQ_local_pipe(opp, i, n_IRQ, active, was_active); + src->last_cpu = i; + break; + } + } + } +} + +static void openpic_set_irq(void *opaque, int n_IRQ, int level) +{ + OpenPICState *opp = opaque; + IRQSource *src; + + if (n_IRQ >= MAX_IRQ) { + fprintf(stderr, "%s: IRQ %d out of range\n", __func__, n_IRQ); + abort(); + } + + src = &opp->src[n_IRQ]; + DPRINTF("openpic: set irq %d = %d ivpr=0x%08x\n", + n_IRQ, level, src->ivpr); + if (src->level) { + /* level-sensitive irq */ + src->pending = level; + openpic_update_irq(opp, n_IRQ); + } else { + /* edge-sensitive irq */ + if (level) { + src->pending = 1; + openpic_update_irq(opp, n_IRQ); + } + + if (src->output != OPENPIC_OUTPUT_INT) { + /* Edge-triggered interrupts shouldn't be used + * with non-INT delivery, but just in case, + * try to make it do something sane rather than + * cause an interrupt storm. This is close to + * what you'd probably see happen in real hardware. + */ + src->pending = 0; + openpic_update_irq(opp, n_IRQ); + } + } +} + +static void openpic_reset(DeviceState *d) +{ + OpenPICState *opp = FROM_SYSBUS(typeof(*opp), SYS_BUS_DEVICE(d)); + int i; + + opp->gcr = GCR_RESET; + /* Initialise controller registers */ + opp->frr = ((opp->nb_irqs - 1) << FRR_NIRQ_SHIFT) | + ((opp->nb_cpus - 1) << FRR_NCPU_SHIFT) | + (opp->vid << FRR_VID_SHIFT); + + opp->pir = 0; + opp->spve = -1 & opp->vector_mask; + opp->tfrr = opp->tfrr_reset; + /* Initialise IRQ sources */ + for (i = 0; i < opp->max_irq; i++) { + opp->src[i].ivpr = opp->ivpr_reset; + opp->src[i].idr = opp->idr_reset; + + switch (opp->src[i].type) { + case IRQ_TYPE_NORMAL: + opp->src[i].level = !!(opp->ivpr_reset & IVPR_SENSE_MASK); + break; + + case IRQ_TYPE_FSLINT: + opp->src[i].ivpr |= IVPR_POLARITY_MASK; + break; + + case IRQ_TYPE_FSLSPECIAL: + break; + } + } + /* Initialise IRQ destinations */ + for (i = 0; i < MAX_CPU; i++) { + opp->dst[i].ctpr = 15; + memset(&opp->dst[i].raised, 0, sizeof(IRQQueue)); + opp->dst[i].raised.next = -1; + memset(&opp->dst[i].servicing, 0, sizeof(IRQQueue)); + opp->dst[i].servicing.next = -1; + } + /* Initialise timers */ + for (i = 0; i < MAX_TMR; i++) { + opp->timers[i].tccr = 0; + opp->timers[i].tbcr = TBCR_CI; + } + /* Go out of RESET state */ + opp->gcr = 0; +} + +static inline uint32_t read_IRQreg_idr(OpenPICState *opp, int n_IRQ) +{ + return opp->src[n_IRQ].idr; +} + +static inline uint32_t read_IRQreg_ilr(OpenPICState *opp, int n_IRQ) +{ + if (opp->flags & OPENPIC_FLAG_ILR) { + return output_to_inttgt(opp->src[n_IRQ].output); + } + + return 0xffffffff; +} + +static inline uint32_t read_IRQreg_ivpr(OpenPICState *opp, int n_IRQ) +{ + return opp->src[n_IRQ].ivpr; +} + +static inline void write_IRQreg_idr(OpenPICState *opp, int n_IRQ, uint32_t val) +{ + IRQSource *src = &opp->src[n_IRQ]; + uint32_t normal_mask = (1UL << opp->nb_cpus) - 1; + uint32_t crit_mask = 0; + uint32_t mask = normal_mask; + int crit_shift = IDR_EP_SHIFT - opp->nb_cpus; + int i; + + if (opp->flags & OPENPIC_FLAG_IDR_CRIT) { + crit_mask = mask << crit_shift; + mask |= crit_mask | IDR_EP; + } + + src->idr = val & mask; + DPRINTF("Set IDR %d to 0x%08x\n", n_IRQ, src->idr); + + if (opp->flags & OPENPIC_FLAG_IDR_CRIT) { + if (src->idr & crit_mask) { + if (src->idr & normal_mask) { + DPRINTF("%s: IRQ configured for multiple output types, using " + "critical\n", __func__); + } + + src->output = OPENPIC_OUTPUT_CINT; + src->nomask = true; + src->destmask = 0; + + for (i = 0; i < opp->nb_cpus; i++) { + int n_ci = IDR_CI0_SHIFT - i; + + if (src->idr & (1UL << n_ci)) { + src->destmask |= 1UL << i; + } + } + } else { + src->output = OPENPIC_OUTPUT_INT; + src->nomask = false; + src->destmask = src->idr & normal_mask; + } + } else { + src->destmask = src->idr; + } +} + +static inline void write_IRQreg_ilr(OpenPICState *opp, int n_IRQ, uint32_t val) +{ + if (opp->flags & OPENPIC_FLAG_ILR) { + IRQSource *src = &opp->src[n_IRQ]; + + src->output = inttgt_to_output(val & ILR_INTTGT_MASK); + DPRINTF("Set ILR %d to 0x%08x, output %d\n", n_IRQ, src->idr, + src->output); + + /* TODO: on MPIC v4.0 only, set nomask for non-INT */ + } +} + +static inline void write_IRQreg_ivpr(OpenPICState *opp, int n_IRQ, uint32_t val) +{ + uint32_t mask; + + /* NOTE when implementing newer FSL MPIC models: starting with v4.0, + * the polarity bit is read-only on internal interrupts. + */ + mask = IVPR_MASK_MASK | IVPR_PRIORITY_MASK | IVPR_SENSE_MASK | + IVPR_POLARITY_MASK | opp->vector_mask; + + /* ACTIVITY bit is read-only */ + opp->src[n_IRQ].ivpr = + (opp->src[n_IRQ].ivpr & IVPR_ACTIVITY_MASK) | (val & mask); + + /* For FSL internal interrupts, The sense bit is reserved and zero, + * and the interrupt is always level-triggered. Timers and IPIs + * have no sense or polarity bits, and are edge-triggered. + */ + switch (opp->src[n_IRQ].type) { + case IRQ_TYPE_NORMAL: + opp->src[n_IRQ].level = !!(opp->src[n_IRQ].ivpr & IVPR_SENSE_MASK); + break; + + case IRQ_TYPE_FSLINT: + opp->src[n_IRQ].ivpr &= ~IVPR_SENSE_MASK; + break; + + case IRQ_TYPE_FSLSPECIAL: + opp->src[n_IRQ].ivpr &= ~(IVPR_POLARITY_MASK | IVPR_SENSE_MASK); + break; + } + + openpic_update_irq(opp, n_IRQ); + DPRINTF("Set IVPR %d to 0x%08x -> 0x%08x\n", n_IRQ, val, + opp->src[n_IRQ].ivpr); +} + +static void openpic_gcr_write(OpenPICState *opp, uint64_t val) +{ + bool mpic_proxy = false; + + if (val & GCR_RESET) { + openpic_reset(&opp->busdev.qdev); + return; + } + + opp->gcr &= ~opp->mpic_mode_mask; + opp->gcr |= val & opp->mpic_mode_mask; + + /* Set external proxy mode */ + if ((val & opp->mpic_mode_mask) == GCR_MODE_PROXY) { + mpic_proxy = true; + } + + ppce500_set_mpic_proxy(mpic_proxy); +} + +static void openpic_gbl_write(void *opaque, hwaddr addr, uint64_t val, + unsigned len) +{ + OpenPICState *opp = opaque; + IRQDest *dst; + int idx; + + DPRINTF("%s: addr %#" HWADDR_PRIx " <= %08" PRIx64 "\n", + __func__, addr, val); + if (addr & 0xF) { + return; + } + switch (addr) { + case 0x00: /* Block Revision Register1 (BRR1) is Readonly */ + break; + case 0x40: + case 0x50: + case 0x60: + case 0x70: + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + openpic_cpu_write_internal(opp, addr, val, get_current_cpu()); + break; + case 0x1000: /* FRR */ + break; + case 0x1020: /* GCR */ + openpic_gcr_write(opp, val); + break; + case 0x1080: /* VIR */ + break; + case 0x1090: /* PIR */ + for (idx = 0; idx < opp->nb_cpus; idx++) { + if ((val & (1 << idx)) && !(opp->pir & (1 << idx))) { + DPRINTF("Raise OpenPIC RESET output for CPU %d\n", idx); + dst = &opp->dst[idx]; + qemu_irq_raise(dst->irqs[OPENPIC_OUTPUT_RESET]); + } else if (!(val & (1 << idx)) && (opp->pir & (1 << idx))) { + DPRINTF("Lower OpenPIC RESET output for CPU %d\n", idx); + dst = &opp->dst[idx]; + qemu_irq_lower(dst->irqs[OPENPIC_OUTPUT_RESET]); + } + } + opp->pir = val; + break; + case 0x10A0: /* IPI_IVPR */ + case 0x10B0: + case 0x10C0: + case 0x10D0: + { + int idx; + idx = (addr - 0x10A0) >> 4; + write_IRQreg_ivpr(opp, opp->irq_ipi0 + idx, val); + } + break; + case 0x10E0: /* SPVE */ + opp->spve = val & opp->vector_mask; + break; + default: + break; + } +} + +static uint64_t openpic_gbl_read(void *opaque, hwaddr addr, unsigned len) +{ + OpenPICState *opp = opaque; + uint32_t retval; + + DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr); + retval = 0xFFFFFFFF; + if (addr & 0xF) { + return retval; + } + switch (addr) { + case 0x1000: /* FRR */ + retval = opp->frr; + break; + case 0x1020: /* GCR */ + retval = opp->gcr; + break; + case 0x1080: /* VIR */ + retval = opp->vir; + break; + case 0x1090: /* PIR */ + retval = 0x00000000; + break; + case 0x00: /* Block Revision Register1 (BRR1) */ + retval = opp->brr1; + break; + case 0x40: + case 0x50: + case 0x60: + case 0x70: + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + retval = openpic_cpu_read_internal(opp, addr, get_current_cpu()); + break; + case 0x10A0: /* IPI_IVPR */ + case 0x10B0: + case 0x10C0: + case 0x10D0: + { + int idx; + idx = (addr - 0x10A0) >> 4; + retval = read_IRQreg_ivpr(opp, opp->irq_ipi0 + idx); + } + break; + case 0x10E0: /* SPVE */ + retval = opp->spve; + break; + default: + break; + } + DPRINTF("%s: => 0x%08x\n", __func__, retval); + + return retval; +} + +static void openpic_tmr_write(void *opaque, hwaddr addr, uint64_t val, + unsigned len) +{ + OpenPICState *opp = opaque; + int idx; + + addr += 0x10f0; + + DPRINTF("%s: addr %#" HWADDR_PRIx " <= %08" PRIx64 "\n", + __func__, addr, val); + if (addr & 0xF) { + return; + } + + if (addr == 0x10f0) { + /* TFRR */ + opp->tfrr = val; + return; + } + + idx = (addr >> 6) & 0x3; + addr = addr & 0x30; + + switch (addr & 0x30) { + case 0x00: /* TCCR */ + break; + case 0x10: /* TBCR */ + if ((opp->timers[idx].tccr & TCCR_TOG) != 0 && + (val & TBCR_CI) == 0 && + (opp->timers[idx].tbcr & TBCR_CI) != 0) { + opp->timers[idx].tccr &= ~TCCR_TOG; + } + opp->timers[idx].tbcr = val; + break; + case 0x20: /* TVPR */ + write_IRQreg_ivpr(opp, opp->irq_tim0 + idx, val); + break; + case 0x30: /* TDR */ + write_IRQreg_idr(opp, opp->irq_tim0 + idx, val); + break; + } +} + +static uint64_t openpic_tmr_read(void *opaque, hwaddr addr, unsigned len) +{ + OpenPICState *opp = opaque; + uint32_t retval = -1; + int idx; + + DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr); + if (addr & 0xF) { + goto out; + } + idx = (addr >> 6) & 0x3; + if (addr == 0x0) { + /* TFRR */ + retval = opp->tfrr; + goto out; + } + switch (addr & 0x30) { + case 0x00: /* TCCR */ + retval = opp->timers[idx].tccr; + break; + case 0x10: /* TBCR */ + retval = opp->timers[idx].tbcr; + break; + case 0x20: /* TIPV */ + retval = read_IRQreg_ivpr(opp, opp->irq_tim0 + idx); + break; + case 0x30: /* TIDE (TIDR) */ + retval = read_IRQreg_idr(opp, opp->irq_tim0 + idx); + break; + } + +out: + DPRINTF("%s: => 0x%08x\n", __func__, retval); + + return retval; +} + +static void openpic_src_write(void *opaque, hwaddr addr, uint64_t val, + unsigned len) +{ + OpenPICState *opp = opaque; + int idx; + + DPRINTF("%s: addr %#" HWADDR_PRIx " <= %08" PRIx64 "\n", + __func__, addr, val); + + addr = addr & 0xffff; + idx = addr >> 5; + + switch (addr & 0x1f) { + case 0x00: + write_IRQreg_ivpr(opp, idx, val); + break; + case 0x10: + write_IRQreg_idr(opp, idx, val); + break; + case 0x18: + write_IRQreg_ilr(opp, idx, val); + break; + } +} + +static uint64_t openpic_src_read(void *opaque, uint64_t addr, unsigned len) +{ + OpenPICState *opp = opaque; + uint32_t retval; + int idx; + + DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr); + retval = 0xFFFFFFFF; + + addr = addr & 0xffff; + idx = addr >> 5; + + switch (addr & 0x1f) { + case 0x00: + retval = read_IRQreg_ivpr(opp, idx); + break; + case 0x10: + retval = read_IRQreg_idr(opp, idx); + break; + case 0x18: + retval = read_IRQreg_ilr(opp, idx); + break; + } + + DPRINTF("%s: => 0x%08x\n", __func__, retval); + return retval; +} + +static void openpic_msi_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + OpenPICState *opp = opaque; + int idx = opp->irq_msi; + int srs, ibs; + + DPRINTF("%s: addr %#" HWADDR_PRIx " <= 0x%08" PRIx64 "\n", + __func__, addr, val); + if (addr & 0xF) { + return; + } + + switch (addr) { + case MSIIR_OFFSET: + srs = val >> MSIIR_SRS_SHIFT; + idx += srs; + ibs = (val & MSIIR_IBS_MASK) >> MSIIR_IBS_SHIFT; + opp->msi[srs].msir |= 1 << ibs; + openpic_set_irq(opp, idx, 1); + break; + default: + /* most registers are read-only, thus ignored */ + break; + } +} + +static uint64_t openpic_msi_read(void *opaque, hwaddr addr, unsigned size) +{ + OpenPICState *opp = opaque; + uint64_t r = 0; + int i, srs; + + DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr); + if (addr & 0xF) { + return -1; + } + + srs = addr >> 4; + + switch (addr) { + case 0x00: + case 0x10: + case 0x20: + case 0x30: + case 0x40: + case 0x50: + case 0x60: + case 0x70: /* MSIRs */ + r = opp->msi[srs].msir; + /* Clear on read */ + opp->msi[srs].msir = 0; + openpic_set_irq(opp, opp->irq_msi + srs, 0); + break; + case 0x120: /* MSISR */ + for (i = 0; i < MAX_MSI; i++) { + r |= (opp->msi[i].msir ? 1 : 0) << i; + } + break; + } + + return r; +} + +static uint64_t openpic_summary_read(void *opaque, hwaddr addr, unsigned size) +{ + uint64_t r = 0; + + DPRINTF("%s: addr %#" HWADDR_PRIx "\n", __func__, addr); + + /* TODO: EISR/EIMR */ + + return r; +} + +static void openpic_summary_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + DPRINTF("%s: addr %#" HWADDR_PRIx " <= 0x%08" PRIx64 "\n", + __func__, addr, val); + + /* TODO: EISR/EIMR */ +} + +static void openpic_cpu_write_internal(void *opaque, hwaddr addr, + uint32_t val, int idx) +{ + OpenPICState *opp = opaque; + IRQSource *src; + IRQDest *dst; + int s_IRQ, n_IRQ; + + DPRINTF("%s: cpu %d addr %#" HWADDR_PRIx " <= 0x%08x\n", __func__, idx, + addr, val); + + if (idx < 0) { + return; + } + + if (addr & 0xF) { + return; + } + dst = &opp->dst[idx]; + addr &= 0xFF0; + switch (addr) { + case 0x40: /* IPIDR */ + case 0x50: + case 0x60: + case 0x70: + idx = (addr - 0x40) >> 4; + /* we use IDE as mask which CPUs to deliver the IPI to still. */ + opp->src[opp->irq_ipi0 + idx].destmask |= val; + openpic_set_irq(opp, opp->irq_ipi0 + idx, 1); + openpic_set_irq(opp, opp->irq_ipi0 + idx, 0); + break; + case 0x80: /* CTPR */ + dst->ctpr = val & 0x0000000F; + + DPRINTF("%s: set CPU %d ctpr to %d, raised %d servicing %d\n", + __func__, idx, dst->ctpr, dst->raised.priority, + dst->servicing.priority); + + if (dst->raised.priority <= dst->ctpr) { + DPRINTF("%s: Lower OpenPIC INT output cpu %d due to ctpr\n", + __func__, idx); + qemu_irq_lower(dst->irqs[OPENPIC_OUTPUT_INT]); + } else if (dst->raised.priority > dst->servicing.priority) { + DPRINTF("%s: Raise OpenPIC INT output cpu %d irq %d\n", + __func__, idx, dst->raised.next); + qemu_irq_raise(dst->irqs[OPENPIC_OUTPUT_INT]); + } + + break; + case 0x90: /* WHOAMI */ + /* Read-only register */ + break; + case 0xA0: /* IACK */ + /* Read-only register */ + break; + case 0xB0: /* EOI */ + DPRINTF("EOI\n"); + s_IRQ = IRQ_get_next(opp, &dst->servicing); + + if (s_IRQ < 0) { + DPRINTF("%s: EOI with no interrupt in service\n", __func__); + break; + } + + IRQ_resetbit(&dst->servicing, s_IRQ); + /* Set up next servicing IRQ */ + s_IRQ = IRQ_get_next(opp, &dst->servicing); + /* Check queued interrupts. */ + n_IRQ = IRQ_get_next(opp, &dst->raised); + src = &opp->src[n_IRQ]; + if (n_IRQ != -1 && + (s_IRQ == -1 || + IVPR_PRIORITY(src->ivpr) > dst->servicing.priority)) { + DPRINTF("Raise OpenPIC INT output cpu %d irq %d\n", + idx, n_IRQ); + qemu_irq_raise(opp->dst[idx].irqs[OPENPIC_OUTPUT_INT]); + } + break; + default: + break; + } +} + +static void openpic_cpu_write(void *opaque, hwaddr addr, uint64_t val, + unsigned len) +{ + openpic_cpu_write_internal(opaque, addr, val, (addr & 0x1f000) >> 12); +} + + +static uint32_t openpic_iack(OpenPICState *opp, IRQDest *dst, int cpu) +{ + IRQSource *src; + int retval, irq; + + DPRINTF("Lower OpenPIC INT output\n"); + qemu_irq_lower(dst->irqs[OPENPIC_OUTPUT_INT]); + + irq = IRQ_get_next(opp, &dst->raised); + DPRINTF("IACK: irq=%d\n", irq); + + if (irq == -1) { + /* No more interrupt pending */ + return opp->spve; + } + + src = &opp->src[irq]; + if (!(src->ivpr & IVPR_ACTIVITY_MASK) || + !(IVPR_PRIORITY(src->ivpr) > dst->ctpr)) { + fprintf(stderr, "%s: bad raised IRQ %d ctpr %d ivpr 0x%08x\n", + __func__, irq, dst->ctpr, src->ivpr); + openpic_update_irq(opp, irq); + retval = opp->spve; + } else { + /* IRQ enter servicing state */ + IRQ_setbit(&dst->servicing, irq); + retval = IVPR_VECTOR(opp, src->ivpr); + } + + if (!src->level) { + /* edge-sensitive IRQ */ + src->ivpr &= ~IVPR_ACTIVITY_MASK; + src->pending = 0; + IRQ_resetbit(&dst->raised, irq); + } + + if ((irq >= opp->irq_ipi0) && (irq < (opp->irq_ipi0 + MAX_IPI))) { + src->destmask &= ~(1 << cpu); + if (src->destmask && !src->level) { + /* trigger on CPUs that didn't know about it yet */ + openpic_set_irq(opp, irq, 1); + openpic_set_irq(opp, irq, 0); + /* if all CPUs knew about it, set active bit again */ + src->ivpr |= IVPR_ACTIVITY_MASK; + } + } + + return retval; +} + +static uint32_t openpic_cpu_read_internal(void *opaque, hwaddr addr, + int idx) +{ + OpenPICState *opp = opaque; + IRQDest *dst; + uint32_t retval; + + DPRINTF("%s: cpu %d addr %#" HWADDR_PRIx "\n", __func__, idx, addr); + retval = 0xFFFFFFFF; + + if (idx < 0) { + return retval; + } + + if (addr & 0xF) { + return retval; + } + dst = &opp->dst[idx]; + addr &= 0xFF0; + switch (addr) { + case 0x80: /* CTPR */ + retval = dst->ctpr; + break; + case 0x90: /* WHOAMI */ + retval = idx; + break; + case 0xA0: /* IACK */ + retval = openpic_iack(opp, dst, idx); + break; + case 0xB0: /* EOI */ + retval = 0; + break; + default: + break; + } + DPRINTF("%s: => 0x%08x\n", __func__, retval); + + return retval; +} + +static uint64_t openpic_cpu_read(void *opaque, hwaddr addr, unsigned len) +{ + return openpic_cpu_read_internal(opaque, addr, (addr & 0x1f000) >> 12); +} + +static const MemoryRegionOps openpic_glb_ops_le = { + .write = openpic_gbl_write, + .read = openpic_gbl_read, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_glb_ops_be = { + .write = openpic_gbl_write, + .read = openpic_gbl_read, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_tmr_ops_le = { + .write = openpic_tmr_write, + .read = openpic_tmr_read, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_tmr_ops_be = { + .write = openpic_tmr_write, + .read = openpic_tmr_read, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_cpu_ops_le = { + .write = openpic_cpu_write, + .read = openpic_cpu_read, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_cpu_ops_be = { + .write = openpic_cpu_write, + .read = openpic_cpu_read, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_src_ops_le = { + .write = openpic_src_write, + .read = openpic_src_read, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_src_ops_be = { + .write = openpic_src_write, + .read = openpic_src_read, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_msi_ops_be = { + .read = openpic_msi_read, + .write = openpic_msi_write, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps openpic_summary_ops_be = { + .read = openpic_summary_read, + .write = openpic_summary_write, + .endianness = DEVICE_BIG_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void openpic_save_IRQ_queue(QEMUFile* f, IRQQueue *q) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(q->queue); i++) { + /* Always put the lower half of a 64-bit long first, in case we + * restore on a 32-bit host. The least significant bits correspond + * to lower IRQ numbers in the bitmap. + */ + qemu_put_be32(f, (uint32_t)q->queue[i]); +#if LONG_MAX > 0x7FFFFFFF + qemu_put_be32(f, (uint32_t)(q->queue[i] >> 32)); +#endif + } + + qemu_put_sbe32s(f, &q->next); + qemu_put_sbe32s(f, &q->priority); +} + +static void openpic_save(QEMUFile* f, void *opaque) +{ + OpenPICState *opp = (OpenPICState *)opaque; + unsigned int i; + + qemu_put_be32s(f, &opp->gcr); + qemu_put_be32s(f, &opp->vir); + qemu_put_be32s(f, &opp->pir); + qemu_put_be32s(f, &opp->spve); + qemu_put_be32s(f, &opp->tfrr); + + qemu_put_be32s(f, &opp->nb_cpus); + + for (i = 0; i < opp->nb_cpus; i++) { + qemu_put_sbe32s(f, &opp->dst[i].ctpr); + openpic_save_IRQ_queue(f, &opp->dst[i].raised); + openpic_save_IRQ_queue(f, &opp->dst[i].servicing); + qemu_put_buffer(f, (uint8_t *)&opp->dst[i].outputs_active, + sizeof(opp->dst[i].outputs_active)); + } + + for (i = 0; i < MAX_TMR; i++) { + qemu_put_be32s(f, &opp->timers[i].tccr); + qemu_put_be32s(f, &opp->timers[i].tbcr); + } + + for (i = 0; i < opp->max_irq; i++) { + qemu_put_be32s(f, &opp->src[i].ivpr); + qemu_put_be32s(f, &opp->src[i].idr); + qemu_get_be32s(f, &opp->src[i].destmask); + qemu_put_sbe32s(f, &opp->src[i].last_cpu); + qemu_put_sbe32s(f, &opp->src[i].pending); + } +} + +static void openpic_load_IRQ_queue(QEMUFile* f, IRQQueue *q) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(q->queue); i++) { + unsigned long val; + + val = qemu_get_be32(f); +#if LONG_MAX > 0x7FFFFFFF + val <<= 32; + val |= qemu_get_be32(f); +#endif + + q->queue[i] = val; + } + + qemu_get_sbe32s(f, &q->next); + qemu_get_sbe32s(f, &q->priority); +} + +static int openpic_load(QEMUFile* f, void *opaque, int version_id) +{ + OpenPICState *opp = (OpenPICState *)opaque; + unsigned int i; + + if (version_id != 1) { + return -EINVAL; + } + + qemu_get_be32s(f, &opp->gcr); + qemu_get_be32s(f, &opp->vir); + qemu_get_be32s(f, &opp->pir); + qemu_get_be32s(f, &opp->spve); + qemu_get_be32s(f, &opp->tfrr); + + qemu_get_be32s(f, &opp->nb_cpus); + + for (i = 0; i < opp->nb_cpus; i++) { + qemu_get_sbe32s(f, &opp->dst[i].ctpr); + openpic_load_IRQ_queue(f, &opp->dst[i].raised); + openpic_load_IRQ_queue(f, &opp->dst[i].servicing); + qemu_get_buffer(f, (uint8_t *)&opp->dst[i].outputs_active, + sizeof(opp->dst[i].outputs_active)); + } + + for (i = 0; i < MAX_TMR; i++) { + qemu_get_be32s(f, &opp->timers[i].tccr); + qemu_get_be32s(f, &opp->timers[i].tbcr); + } + + for (i = 0; i < opp->max_irq; i++) { + uint32_t val; + + val = qemu_get_be32(f); + write_IRQreg_idr(opp, i, val); + val = qemu_get_be32(f); + write_IRQreg_ivpr(opp, i, val); + + qemu_get_be32s(f, &opp->src[i].ivpr); + qemu_get_be32s(f, &opp->src[i].idr); + qemu_get_be32s(f, &opp->src[i].destmask); + qemu_get_sbe32s(f, &opp->src[i].last_cpu); + qemu_get_sbe32s(f, &opp->src[i].pending); + } + + return 0; +} + +typedef struct MemReg { + const char *name; + MemoryRegionOps const *ops; + hwaddr start_addr; + ram_addr_t size; +} MemReg; + +static void fsl_common_init(OpenPICState *opp) +{ + int i; + int virq = MAX_SRC; + + opp->vid = VID_REVISION_1_2; + opp->vir = VIR_GENERIC; + opp->vector_mask = 0xFFFF; + opp->tfrr_reset = 0; + opp->ivpr_reset = IVPR_MASK_MASK; + opp->idr_reset = 1 << 0; + opp->max_irq = MAX_IRQ; + + opp->irq_ipi0 = virq; + virq += MAX_IPI; + opp->irq_tim0 = virq; + virq += MAX_TMR; + + assert(virq <= MAX_IRQ); + + opp->irq_msi = 224; + + msi_supported = true; + for (i = 0; i < opp->fsl->max_ext; i++) { + opp->src[i].level = false; + } + + /* Internal interrupts, including message and MSI */ + for (i = 16; i < MAX_SRC; i++) { + opp->src[i].type = IRQ_TYPE_FSLINT; + opp->src[i].level = true; + } + + /* timers and IPIs */ + for (i = MAX_SRC; i < virq; i++) { + opp->src[i].type = IRQ_TYPE_FSLSPECIAL; + opp->src[i].level = false; + } +} + +static void map_list(OpenPICState *opp, const MemReg *list, int *count) +{ + while (list->name) { + assert(*count < ARRAY_SIZE(opp->sub_io_mem)); + + memory_region_init_io(&opp->sub_io_mem[*count], list->ops, opp, + list->name, list->size); + + memory_region_add_subregion(&opp->mem, list->start_addr, + &opp->sub_io_mem[*count]); + + (*count)++; + list++; + } +} + +static int openpic_init(SysBusDevice *dev) +{ + OpenPICState *opp = FROM_SYSBUS(typeof (*opp), dev); + int i, j; + int list_count = 0; + static const MemReg list_le[] = { + {"glb", &openpic_glb_ops_le, + OPENPIC_GLB_REG_START, OPENPIC_GLB_REG_SIZE}, + {"tmr", &openpic_tmr_ops_le, + OPENPIC_TMR_REG_START, OPENPIC_TMR_REG_SIZE}, + {"src", &openpic_src_ops_le, + OPENPIC_SRC_REG_START, OPENPIC_SRC_REG_SIZE}, + {"cpu", &openpic_cpu_ops_le, + OPENPIC_CPU_REG_START, OPENPIC_CPU_REG_SIZE}, + {NULL} + }; + static const MemReg list_be[] = { + {"glb", &openpic_glb_ops_be, + OPENPIC_GLB_REG_START, OPENPIC_GLB_REG_SIZE}, + {"tmr", &openpic_tmr_ops_be, + OPENPIC_TMR_REG_START, OPENPIC_TMR_REG_SIZE}, + {"src", &openpic_src_ops_be, + OPENPIC_SRC_REG_START, OPENPIC_SRC_REG_SIZE}, + {"cpu", &openpic_cpu_ops_be, + OPENPIC_CPU_REG_START, OPENPIC_CPU_REG_SIZE}, + {NULL} + }; + static const MemReg list_fsl[] = { + {"msi", &openpic_msi_ops_be, + OPENPIC_MSI_REG_START, OPENPIC_MSI_REG_SIZE}, + {"summary", &openpic_summary_ops_be, + OPENPIC_SUMMARY_REG_START, OPENPIC_SUMMARY_REG_SIZE}, + {NULL} + }; + + memory_region_init(&opp->mem, "openpic", 0x40000); + + switch (opp->model) { + case OPENPIC_MODEL_FSL_MPIC_20: + default: + opp->fsl = &fsl_mpic_20; + opp->brr1 = 0x00400200; + opp->flags |= OPENPIC_FLAG_IDR_CRIT; + opp->nb_irqs = 80; + opp->mpic_mode_mask = GCR_MODE_MIXED; + + fsl_common_init(opp); + map_list(opp, list_be, &list_count); + map_list(opp, list_fsl, &list_count); + + break; + + case OPENPIC_MODEL_FSL_MPIC_42: + opp->fsl = &fsl_mpic_42; + opp->brr1 = 0x00400402; + opp->flags |= OPENPIC_FLAG_ILR; + opp->nb_irqs = 196; + opp->mpic_mode_mask = GCR_MODE_PROXY; + + fsl_common_init(opp); + map_list(opp, list_be, &list_count); + map_list(opp, list_fsl, &list_count); + + break; + + case OPENPIC_MODEL_RAVEN: + opp->nb_irqs = RAVEN_MAX_EXT; + opp->vid = VID_REVISION_1_3; + opp->vir = VIR_GENERIC; + opp->vector_mask = 0xFF; + opp->tfrr_reset = 4160000; + opp->ivpr_reset = IVPR_MASK_MASK | IVPR_MODE_MASK; + opp->idr_reset = 0; + opp->max_irq = RAVEN_MAX_IRQ; + opp->irq_ipi0 = RAVEN_IPI_IRQ; + opp->irq_tim0 = RAVEN_TMR_IRQ; + opp->brr1 = -1; + opp->mpic_mode_mask = GCR_MODE_MIXED; + + /* Only UP supported today */ + if (opp->nb_cpus != 1) { + return -EINVAL; + } + + map_list(opp, list_le, &list_count); + break; + } + + for (i = 0; i < opp->nb_cpus; i++) { + opp->dst[i].irqs = g_new(qemu_irq, OPENPIC_OUTPUT_NB); + for (j = 0; j < OPENPIC_OUTPUT_NB; j++) { + sysbus_init_irq(dev, &opp->dst[i].irqs[j]); + } + } + + register_savevm(&opp->busdev.qdev, "openpic", 0, 2, + openpic_save, openpic_load, opp); + + sysbus_init_mmio(dev, &opp->mem); + qdev_init_gpio_in(&dev->qdev, openpic_set_irq, opp->max_irq); + + return 0; +} + +static Property openpic_properties[] = { + DEFINE_PROP_UINT32("model", OpenPICState, model, OPENPIC_MODEL_FSL_MPIC_20), + DEFINE_PROP_UINT32("nb_cpus", OpenPICState, nb_cpus, 1), + DEFINE_PROP_END_OF_LIST(), +}; + +static void openpic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = openpic_init; + dc->props = openpic_properties; + dc->reset = openpic_reset; +} + +static const TypeInfo openpic_info = { + .name = "openpic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(OpenPICState), + .class_init = openpic_class_init, +}; + +static void openpic_register_types(void) +{ + type_register_static(&openpic_info); +} + +type_init(openpic_register_types) diff --git a/hw/intc/realview_gic.c b/hw/intc/realview_gic.c new file mode 100644 index 0000000000..0ec30caa06 --- /dev/null +++ b/hw/intc/realview_gic.c @@ -0,0 +1,74 @@ +/* + * ARM RealView Emulation Baseboard Interrupt Controller + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "hw/sysbus.h" + +typedef struct { + SysBusDevice busdev; + DeviceState *gic; + MemoryRegion container; +} RealViewGICState; + +static void realview_gic_set_irq(void *opaque, int irq, int level) +{ + RealViewGICState *s = (RealViewGICState *)opaque; + qemu_set_irq(qdev_get_gpio_in(s->gic, irq), level); +} + +static int realview_gic_init(SysBusDevice *dev) +{ + RealViewGICState *s = FROM_SYSBUS(RealViewGICState, dev); + SysBusDevice *busdev; + /* The GICs on the RealView boards have a fixed nonconfigurable + * number of interrupt lines, so we don't need to expose this as + * a qdev property. + */ + int numirq = 96; + + s->gic = qdev_create(NULL, "arm_gic"); + qdev_prop_set_uint32(s->gic, "num-cpu", 1); + qdev_prop_set_uint32(s->gic, "num-irq", numirq); + qdev_init_nofail(s->gic); + busdev = SYS_BUS_DEVICE(s->gic); + + /* Pass through outbound IRQ lines from the GIC */ + sysbus_pass_irq(dev, busdev); + + /* Pass through inbound GPIO lines to the GIC */ + qdev_init_gpio_in(&s->busdev.qdev, realview_gic_set_irq, numirq - 32); + + memory_region_init(&s->container, "realview-gic-container", 0x2000); + memory_region_add_subregion(&s->container, 0, + sysbus_mmio_get_region(busdev, 1)); + memory_region_add_subregion(&s->container, 0x1000, + sysbus_mmio_get_region(busdev, 0)); + sysbus_init_mmio(dev, &s->container); + return 0; +} + +static void realview_gic_class_init(ObjectClass *klass, void *data) +{ + SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass); + + sdc->init = realview_gic_init; +} + +static const TypeInfo realview_gic_info = { + .name = "realview_gic", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RealViewGICState), + .class_init = realview_gic_class_init, +}; + +static void realview_gic_register_types(void) +{ + type_register_static(&realview_gic_info); +} + +type_init(realview_gic_register_types) diff --git a/hw/intc/sbi.c b/hw/intc/sbi.c new file mode 100644 index 0000000000..8795749de8 --- /dev/null +++ b/hw/intc/sbi.c @@ -0,0 +1,156 @@ +/* + * QEMU Sparc SBI interrupt controller emulation + * + * Based on slavio_intctl, copyright (c) 2003-2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sysbus.h" + +//#define DEBUG_IRQ + +#ifdef DEBUG_IRQ +#define DPRINTF(fmt, ...) \ + do { printf("IRQ: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) +#endif + +#define MAX_CPUS 16 + +#define SBI_NREGS 16 + +typedef struct SBIState { + SysBusDevice busdev; + MemoryRegion iomem; + uint32_t regs[SBI_NREGS]; + uint32_t intreg_pending[MAX_CPUS]; + qemu_irq cpu_irqs[MAX_CPUS]; + uint32_t pil_out[MAX_CPUS]; +} SBIState; + +#define SBI_SIZE (SBI_NREGS * 4) + +static void sbi_set_irq(void *opaque, int irq, int level) +{ +} + +static uint64_t sbi_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + SBIState *s = opaque; + uint32_t saddr, ret; + + saddr = addr >> 2; + switch (saddr) { + default: + ret = s->regs[saddr]; + break; + } + DPRINTF("read system reg 0x" TARGET_FMT_plx " = %x\n", addr, ret); + + return ret; +} + +static void sbi_mem_write(void *opaque, hwaddr addr, + uint64_t val, unsigned dize) +{ + SBIState *s = opaque; + uint32_t saddr; + + saddr = addr >> 2; + DPRINTF("write system reg 0x" TARGET_FMT_plx " = %x\n", addr, (int)val); + switch (saddr) { + default: + s->regs[saddr] = val; + break; + } +} + +static const MemoryRegionOps sbi_mem_ops = { + .read = sbi_mem_read, + .write = sbi_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const VMStateDescription vmstate_sbi = { + .name ="sbi", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT32_ARRAY(intreg_pending, SBIState, MAX_CPUS), + VMSTATE_END_OF_LIST() + } +}; + +static void sbi_reset(DeviceState *d) +{ + SBIState *s = container_of(d, SBIState, busdev.qdev); + unsigned int i; + + for (i = 0; i < MAX_CPUS; i++) { + s->intreg_pending[i] = 0; + } +} + +static int sbi_init1(SysBusDevice *dev) +{ + SBIState *s = FROM_SYSBUS(SBIState, dev); + unsigned int i; + + qdev_init_gpio_in(&dev->qdev, sbi_set_irq, 32 + MAX_CPUS); + for (i = 0; i < MAX_CPUS; i++) { + sysbus_init_irq(dev, &s->cpu_irqs[i]); + } + + memory_region_init_io(&s->iomem, &sbi_mem_ops, s, "sbi", SBI_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void sbi_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = sbi_init1; + dc->reset = sbi_reset; + dc->vmsd = &vmstate_sbi; +} + +static const TypeInfo sbi_info = { + .name = "sbi", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SBIState), + .class_init = sbi_class_init, +}; + +static void sbi_register_types(void) +{ + type_register_static(&sbi_info); +} + +type_init(sbi_register_types) diff --git a/hw/intc/sh_intc.c b/hw/intc/sh_intc.c new file mode 100644 index 0000000000..050bfb63ce --- /dev/null +++ b/hw/intc/sh_intc.c @@ -0,0 +1,513 @@ +/* + * SuperH interrupt controller module + * + * Copyright (c) 2007 Magnus Damm + * Based on sh_timer.c and arm_timer.c by Paul Brook + * Copyright (c) 2005-2006 CodeSourcery. + * + * This code is licensed under the GPL. + */ + +#include "hw/sh4/sh_intc.h" +#include "hw/hw.h" +#include "hw/sh4/sh.h" + +//#define DEBUG_INTC +//#define DEBUG_INTC_SOURCES + +#define INTC_A7(x) ((x) & 0x1fffffff) + +void sh_intc_toggle_source(struct intc_source *source, + int enable_adj, int assert_adj) +{ + int enable_changed = 0; + int pending_changed = 0; + int old_pending; + + if ((source->enable_count == source->enable_max) && (enable_adj == -1)) + enable_changed = -1; + + source->enable_count += enable_adj; + + if (source->enable_count == source->enable_max) + enable_changed = 1; + + source->asserted += assert_adj; + + old_pending = source->pending; + source->pending = source->asserted && + (source->enable_count == source->enable_max); + + if (old_pending != source->pending) + pending_changed = 1; + + if (pending_changed) { + CPUState *cpu = CPU(sh_env_get_cpu(first_cpu)); + if (source->pending) { + source->parent->pending++; + if (source->parent->pending == 1) { + cpu_interrupt(cpu, CPU_INTERRUPT_HARD); + } + } else { + source->parent->pending--; + if (source->parent->pending == 0) { + cpu_reset_interrupt(cpu, CPU_INTERRUPT_HARD); + } + } + } + + if (enable_changed || assert_adj || pending_changed) { +#ifdef DEBUG_INTC_SOURCES + printf("sh_intc: (%d/%d/%d/%d) interrupt source 0x%x %s%s%s\n", + source->parent->pending, + source->asserted, + source->enable_count, + source->enable_max, + source->vect, + source->asserted ? "asserted " : + assert_adj ? "deasserted" : "", + enable_changed == 1 ? "enabled " : + enable_changed == -1 ? "disabled " : "", + source->pending ? "pending" : ""); +#endif + } +} + +static void sh_intc_set_irq (void *opaque, int n, int level) +{ + struct intc_desc *desc = opaque; + struct intc_source *source = &(desc->sources[n]); + + if (level && !source->asserted) + sh_intc_toggle_source(source, 0, 1); + else if (!level && source->asserted) + sh_intc_toggle_source(source, 0, -1); +} + +int sh_intc_get_pending_vector(struct intc_desc *desc, int imask) +{ + unsigned int i; + + /* slow: use a linked lists of pending sources instead */ + /* wrong: take interrupt priority into account (one list per priority) */ + + if (imask == 0x0f) { + return -1; /* FIXME, update code to include priority per source */ + } + + for (i = 0; i < desc->nr_sources; i++) { + struct intc_source *source = desc->sources + i; + + if (source->pending) { +#ifdef DEBUG_INTC_SOURCES + printf("sh_intc: (%d) returning interrupt source 0x%x\n", + desc->pending, source->vect); +#endif + return source->vect; + } + } + + abort(); +} + +#define INTC_MODE_NONE 0 +#define INTC_MODE_DUAL_SET 1 +#define INTC_MODE_DUAL_CLR 2 +#define INTC_MODE_ENABLE_REG 3 +#define INTC_MODE_MASK_REG 4 +#define INTC_MODE_IS_PRIO 8 + +static unsigned int sh_intc_mode(unsigned long address, + unsigned long set_reg, unsigned long clr_reg) +{ + if ((address != INTC_A7(set_reg)) && + (address != INTC_A7(clr_reg))) + return INTC_MODE_NONE; + + if (set_reg && clr_reg) { + if (address == INTC_A7(set_reg)) + return INTC_MODE_DUAL_SET; + else + return INTC_MODE_DUAL_CLR; + } + + if (set_reg) + return INTC_MODE_ENABLE_REG; + else + return INTC_MODE_MASK_REG; +} + +static void sh_intc_locate(struct intc_desc *desc, + unsigned long address, + unsigned long **datap, + intc_enum **enums, + unsigned int *first, + unsigned int *width, + unsigned int *modep) +{ + unsigned int i, mode; + + /* this is slow but works for now */ + + if (desc->mask_regs) { + for (i = 0; i < desc->nr_mask_regs; i++) { + struct intc_mask_reg *mr = desc->mask_regs + i; + + mode = sh_intc_mode(address, mr->set_reg, mr->clr_reg); + if (mode == INTC_MODE_NONE) + continue; + + *modep = mode; + *datap = &mr->value; + *enums = mr->enum_ids; + *first = mr->reg_width - 1; + *width = 1; + return; + } + } + + if (desc->prio_regs) { + for (i = 0; i < desc->nr_prio_regs; i++) { + struct intc_prio_reg *pr = desc->prio_regs + i; + + mode = sh_intc_mode(address, pr->set_reg, pr->clr_reg); + if (mode == INTC_MODE_NONE) + continue; + + *modep = mode | INTC_MODE_IS_PRIO; + *datap = &pr->value; + *enums = pr->enum_ids; + *first = (pr->reg_width / pr->field_width) - 1; + *width = pr->field_width; + return; + } + } + + abort(); +} + +static void sh_intc_toggle_mask(struct intc_desc *desc, intc_enum id, + int enable, int is_group) +{ + struct intc_source *source = desc->sources + id; + + if (!id) + return; + + if (!source->next_enum_id && (!source->enable_max || !source->vect)) { +#ifdef DEBUG_INTC_SOURCES + printf("sh_intc: reserved interrupt source %d modified\n", id); +#endif + return; + } + + if (source->vect) + sh_intc_toggle_source(source, enable ? 1 : -1, 0); + +#ifdef DEBUG_INTC + else { + printf("setting interrupt group %d to %d\n", id, !!enable); + } +#endif + + if ((is_group || !source->vect) && source->next_enum_id) { + sh_intc_toggle_mask(desc, source->next_enum_id, enable, 1); + } + +#ifdef DEBUG_INTC + if (!source->vect) { + printf("setting interrupt group %d to %d - done\n", id, !!enable); + } +#endif +} + +static uint64_t sh_intc_read(void *opaque, hwaddr offset, + unsigned size) +{ + struct intc_desc *desc = opaque; + intc_enum *enum_ids = NULL; + unsigned int first = 0; + unsigned int width = 0; + unsigned int mode = 0; + unsigned long *valuep; + +#ifdef DEBUG_INTC + printf("sh_intc_read 0x%lx\n", (unsigned long) offset); +#endif + + sh_intc_locate(desc, (unsigned long)offset, &valuep, + &enum_ids, &first, &width, &mode); + return *valuep; +} + +static void sh_intc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + struct intc_desc *desc = opaque; + intc_enum *enum_ids = NULL; + unsigned int first = 0; + unsigned int width = 0; + unsigned int mode = 0; + unsigned int k; + unsigned long *valuep; + unsigned long mask; + +#ifdef DEBUG_INTC + printf("sh_intc_write 0x%lx 0x%08x\n", (unsigned long) offset, value); +#endif + + sh_intc_locate(desc, (unsigned long)offset, &valuep, + &enum_ids, &first, &width, &mode); + + switch (mode) { + case INTC_MODE_ENABLE_REG | INTC_MODE_IS_PRIO: break; + case INTC_MODE_DUAL_SET: value |= *valuep; break; + case INTC_MODE_DUAL_CLR: value = *valuep & ~value; break; + default: abort(); + } + + for (k = 0; k <= first; k++) { + mask = ((1 << width) - 1) << ((first - k) * width); + + if ((*valuep & mask) == (value & mask)) + continue; +#if 0 + printf("k = %d, first = %d, enum = %d, mask = 0x%08x\n", + k, first, enum_ids[k], (unsigned int)mask); +#endif + sh_intc_toggle_mask(desc, enum_ids[k], value & mask, 0); + } + + *valuep = value; + +#ifdef DEBUG_INTC + printf("sh_intc_write 0x%lx -> 0x%08x\n", (unsigned long) offset, value); +#endif +} + +static const MemoryRegionOps sh_intc_ops = { + .read = sh_intc_read, + .write = sh_intc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +struct intc_source *sh_intc_source(struct intc_desc *desc, intc_enum id) +{ + if (id) + return desc->sources + id; + + return NULL; +} + +static unsigned int sh_intc_register(MemoryRegion *sysmem, + struct intc_desc *desc, + const unsigned long address, + const char *type, + const char *action, + const unsigned int index) +{ + char name[60]; + MemoryRegion *iomem, *iomem_p4, *iomem_a7; + + if (!address) { + return 0; + } + + iomem = &desc->iomem; + iomem_p4 = desc->iomem_aliases + index; + iomem_a7 = iomem_p4 + 1; + +#define SH_INTC_IOMEM_FORMAT "interrupt-controller-%s-%s-%s" + snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "p4"); + memory_region_init_alias(iomem_p4, name, iomem, INTC_A7(address), 4); + memory_region_add_subregion(sysmem, P4ADDR(address), iomem_p4); + + snprintf(name, sizeof(name), SH_INTC_IOMEM_FORMAT, type, action, "a7"); + memory_region_init_alias(iomem_a7, name, iomem, INTC_A7(address), 4); + memory_region_add_subregion(sysmem, A7ADDR(address), iomem_a7); +#undef SH_INTC_IOMEM_FORMAT + + /* used to increment aliases index */ + return 2; +} + +static void sh_intc_register_source(struct intc_desc *desc, + intc_enum source, + struct intc_group *groups, + int nr_groups) +{ + unsigned int i, k; + struct intc_source *s; + + if (desc->mask_regs) { + for (i = 0; i < desc->nr_mask_regs; i++) { + struct intc_mask_reg *mr = desc->mask_regs + i; + + for (k = 0; k < ARRAY_SIZE(mr->enum_ids); k++) { + if (mr->enum_ids[k] != source) + continue; + + s = sh_intc_source(desc, mr->enum_ids[k]); + if (s) + s->enable_max++; + } + } + } + + if (desc->prio_regs) { + for (i = 0; i < desc->nr_prio_regs; i++) { + struct intc_prio_reg *pr = desc->prio_regs + i; + + for (k = 0; k < ARRAY_SIZE(pr->enum_ids); k++) { + if (pr->enum_ids[k] != source) + continue; + + s = sh_intc_source(desc, pr->enum_ids[k]); + if (s) + s->enable_max++; + } + } + } + + if (groups) { + for (i = 0; i < nr_groups; i++) { + struct intc_group *gr = groups + i; + + for (k = 0; k < ARRAY_SIZE(gr->enum_ids); k++) { + if (gr->enum_ids[k] != source) + continue; + + s = sh_intc_source(desc, gr->enum_ids[k]); + if (s) + s->enable_max++; + } + } + } + +} + +void sh_intc_register_sources(struct intc_desc *desc, + struct intc_vect *vectors, + int nr_vectors, + struct intc_group *groups, + int nr_groups) +{ + unsigned int i, k; + struct intc_source *s; + + for (i = 0; i < nr_vectors; i++) { + struct intc_vect *vect = vectors + i; + + sh_intc_register_source(desc, vect->enum_id, groups, nr_groups); + s = sh_intc_source(desc, vect->enum_id); + if (s) { + s->vect = vect->vect; + +#ifdef DEBUG_INTC_SOURCES + printf("sh_intc: registered source %d -> 0x%04x (%d/%d)\n", + vect->enum_id, s->vect, s->enable_count, s->enable_max); +#endif + } + } + + if (groups) { + for (i = 0; i < nr_groups; i++) { + struct intc_group *gr = groups + i; + + s = sh_intc_source(desc, gr->enum_id); + s->next_enum_id = gr->enum_ids[0]; + + for (k = 1; k < ARRAY_SIZE(gr->enum_ids); k++) { + if (!gr->enum_ids[k]) + continue; + + s = sh_intc_source(desc, gr->enum_ids[k - 1]); + s->next_enum_id = gr->enum_ids[k]; + } + +#ifdef DEBUG_INTC_SOURCES + printf("sh_intc: registered group %d (%d/%d)\n", + gr->enum_id, s->enable_count, s->enable_max); +#endif + } + } +} + +int sh_intc_init(MemoryRegion *sysmem, + struct intc_desc *desc, + int nr_sources, + struct intc_mask_reg *mask_regs, + int nr_mask_regs, + struct intc_prio_reg *prio_regs, + int nr_prio_regs) +{ + unsigned int i, j; + + desc->pending = 0; + desc->nr_sources = nr_sources; + desc->mask_regs = mask_regs; + desc->nr_mask_regs = nr_mask_regs; + desc->prio_regs = prio_regs; + desc->nr_prio_regs = nr_prio_regs; + /* Allocate 4 MemoryRegions per register (2 actions * 2 aliases). + **/ + desc->iomem_aliases = g_new0(MemoryRegion, + (nr_mask_regs + nr_prio_regs) * 4); + + j = 0; + i = sizeof(struct intc_source) * nr_sources; + desc->sources = g_malloc0(i); + + for (i = 0; i < desc->nr_sources; i++) { + struct intc_source *source = desc->sources + i; + + source->parent = desc; + } + + desc->irqs = qemu_allocate_irqs(sh_intc_set_irq, desc, nr_sources); + + memory_region_init_io(&desc->iomem, &sh_intc_ops, desc, + "interrupt-controller", 0x100000000ULL); + +#define INT_REG_PARAMS(reg_struct, type, action, j) \ + reg_struct->action##_reg, #type, #action, j + if (desc->mask_regs) { + for (i = 0; i < desc->nr_mask_regs; i++) { + struct intc_mask_reg *mr = desc->mask_regs + i; + + j += sh_intc_register(sysmem, desc, + INT_REG_PARAMS(mr, mask, set, j)); + j += sh_intc_register(sysmem, desc, + INT_REG_PARAMS(mr, mask, clr, j)); + } + } + + if (desc->prio_regs) { + for (i = 0; i < desc->nr_prio_regs; i++) { + struct intc_prio_reg *pr = desc->prio_regs + i; + + j += sh_intc_register(sysmem, desc, + INT_REG_PARAMS(pr, prio, set, j)); + j += sh_intc_register(sysmem, desc, + INT_REG_PARAMS(pr, prio, clr, j)); + } + } +#undef INT_REG_PARAMS + + return 0; +} + +/* Assert level <n> IRL interrupt. + 0:deassert. 1:lowest priority,... 15:highest priority. */ +void sh_intc_set_irl(void *opaque, int n, int level) +{ + struct intc_source *s = opaque; + int i, irl = level ^ 15; + for (i = 0; (s = sh_intc_source(s->parent, s->next_enum_id)); i++) { + if (i == irl) + sh_intc_toggle_source(s, s->enable_count?0:1, s->asserted?0:1); + else + if (s->asserted) + sh_intc_toggle_source(s, 0, -1); + } +} diff --git a/hw/intc/slavio_intctl.c b/hw/intc/slavio_intctl.c new file mode 100644 index 0000000000..b367752fd3 --- /dev/null +++ b/hw/intc/slavio_intctl.c @@ -0,0 +1,471 @@ +/* + * QEMU Sparc SLAVIO interrupt controller emulation + * + * Copyright (c) 2003-2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/sparc/sun4m.h" +#include "monitor/monitor.h" +#include "hw/sysbus.h" +#include "trace.h" + +//#define DEBUG_IRQ_COUNT + +/* + * Registers of interrupt controller in sun4m. + * + * This is the interrupt controller part of chip STP2001 (Slave I/O), also + * produced as NCR89C105. See + * http://www.ibiblio.org/pub/historic-linux/early-ports/Sparc/NCR/NCR89C105.txt + * + * There is a system master controller and one for each cpu. + * + */ + +#define MAX_CPUS 16 +#define MAX_PILS 16 + +struct SLAVIO_INTCTLState; + +typedef struct SLAVIO_CPUINTCTLState { + MemoryRegion iomem; + struct SLAVIO_INTCTLState *master; + uint32_t intreg_pending; + uint32_t cpu; + uint32_t irl_out; +} SLAVIO_CPUINTCTLState; + +typedef struct SLAVIO_INTCTLState { + SysBusDevice busdev; + MemoryRegion iomem; +#ifdef DEBUG_IRQ_COUNT + uint64_t irq_count[32]; +#endif + qemu_irq cpu_irqs[MAX_CPUS][MAX_PILS]; + SLAVIO_CPUINTCTLState slaves[MAX_CPUS]; + uint32_t intregm_pending; + uint32_t intregm_disabled; + uint32_t target_cpu; +} SLAVIO_INTCTLState; + +#define INTCTL_MAXADDR 0xf +#define INTCTL_SIZE (INTCTL_MAXADDR + 1) +#define INTCTLM_SIZE 0x14 +#define MASTER_IRQ_MASK ~0x0fa2007f +#define MASTER_DISABLE 0x80000000 +#define CPU_SOFTIRQ_MASK 0xfffe0000 +#define CPU_IRQ_INT15_IN (1 << 15) +#define CPU_IRQ_TIMER_IN (1 << 14) + +static void slavio_check_interrupts(SLAVIO_INTCTLState *s, int set_irqs); + +// per-cpu interrupt controller +static uint64_t slavio_intctl_mem_readl(void *opaque, hwaddr addr, + unsigned size) +{ + SLAVIO_CPUINTCTLState *s = opaque; + uint32_t saddr, ret; + + saddr = addr >> 2; + switch (saddr) { + case 0: + ret = s->intreg_pending; + break; + default: + ret = 0; + break; + } + trace_slavio_intctl_mem_readl(s->cpu, addr, ret); + + return ret; +} + +static void slavio_intctl_mem_writel(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SLAVIO_CPUINTCTLState *s = opaque; + uint32_t saddr; + + saddr = addr >> 2; + trace_slavio_intctl_mem_writel(s->cpu, addr, val); + switch (saddr) { + case 1: // clear pending softints + val &= CPU_SOFTIRQ_MASK | CPU_IRQ_INT15_IN; + s->intreg_pending &= ~val; + slavio_check_interrupts(s->master, 1); + trace_slavio_intctl_mem_writel_clear(s->cpu, val, s->intreg_pending); + break; + case 2: // set softint + val &= CPU_SOFTIRQ_MASK; + s->intreg_pending |= val; + slavio_check_interrupts(s->master, 1); + trace_slavio_intctl_mem_writel_set(s->cpu, val, s->intreg_pending); + break; + default: + break; + } +} + +static const MemoryRegionOps slavio_intctl_mem_ops = { + .read = slavio_intctl_mem_readl, + .write = slavio_intctl_mem_writel, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +// master system interrupt controller +static uint64_t slavio_intctlm_mem_readl(void *opaque, hwaddr addr, + unsigned size) +{ + SLAVIO_INTCTLState *s = opaque; + uint32_t saddr, ret; + + saddr = addr >> 2; + switch (saddr) { + case 0: + ret = s->intregm_pending & ~MASTER_DISABLE; + break; + case 1: + ret = s->intregm_disabled & MASTER_IRQ_MASK; + break; + case 4: + ret = s->target_cpu; + break; + default: + ret = 0; + break; + } + trace_slavio_intctlm_mem_readl(addr, ret); + + return ret; +} + +static void slavio_intctlm_mem_writel(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + SLAVIO_INTCTLState *s = opaque; + uint32_t saddr; + + saddr = addr >> 2; + trace_slavio_intctlm_mem_writel(addr, val); + switch (saddr) { + case 2: // clear (enable) + // Force clear unused bits + val &= MASTER_IRQ_MASK; + s->intregm_disabled &= ~val; + trace_slavio_intctlm_mem_writel_enable(val, s->intregm_disabled); + slavio_check_interrupts(s, 1); + break; + case 3: // set (disable; doesn't affect pending) + // Force clear unused bits + val &= MASTER_IRQ_MASK; + s->intregm_disabled |= val; + slavio_check_interrupts(s, 1); + trace_slavio_intctlm_mem_writel_disable(val, s->intregm_disabled); + break; + case 4: + s->target_cpu = val & (MAX_CPUS - 1); + slavio_check_interrupts(s, 1); + trace_slavio_intctlm_mem_writel_target(s->target_cpu); + break; + default: + break; + } +} + +static const MemoryRegionOps slavio_intctlm_mem_ops = { + .read = slavio_intctlm_mem_readl, + .write = slavio_intctlm_mem_writel, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +void slavio_pic_info(Monitor *mon, DeviceState *dev) +{ + SysBusDevice *sd; + SLAVIO_INTCTLState *s; + int i; + + sd = SYS_BUS_DEVICE(dev); + s = FROM_SYSBUS(SLAVIO_INTCTLState, sd); + for (i = 0; i < MAX_CPUS; i++) { + monitor_printf(mon, "per-cpu %d: pending 0x%08x\n", i, + s->slaves[i].intreg_pending); + } + monitor_printf(mon, "master: pending 0x%08x, disabled 0x%08x\n", + s->intregm_pending, s->intregm_disabled); +} + +void slavio_irq_info(Monitor *mon, DeviceState *dev) +{ +#ifndef DEBUG_IRQ_COUNT + monitor_printf(mon, "irq statistic code not compiled.\n"); +#else + SysBusDevice *sd; + SLAVIO_INTCTLState *s; + int i; + int64_t count; + + sd = SYS_BUS_DEVICE(dev); + s = FROM_SYSBUS(SLAVIO_INTCTLState, sd); + monitor_printf(mon, "IRQ statistics:\n"); + for (i = 0; i < 32; i++) { + count = s->irq_count[i]; + if (count > 0) + monitor_printf(mon, "%2d: %" PRId64 "\n", i, count); + } +#endif +} + +static const uint32_t intbit_to_level[] = { + 2, 3, 5, 7, 9, 11, 13, 2, 3, 5, 7, 9, 11, 13, 12, 12, + 6, 13, 4, 10, 8, 9, 11, 0, 0, 0, 0, 15, 15, 15, 15, 0, +}; + +static void slavio_check_interrupts(SLAVIO_INTCTLState *s, int set_irqs) +{ + uint32_t pending = s->intregm_pending, pil_pending; + unsigned int i, j; + + pending &= ~s->intregm_disabled; + + trace_slavio_check_interrupts(pending, s->intregm_disabled); + for (i = 0; i < MAX_CPUS; i++) { + pil_pending = 0; + + /* If we are the current interrupt target, get hard interrupts */ + if (pending && !(s->intregm_disabled & MASTER_DISABLE) && + (i == s->target_cpu)) { + for (j = 0; j < 32; j++) { + if ((pending & (1 << j)) && intbit_to_level[j]) { + pil_pending |= 1 << intbit_to_level[j]; + } + } + } + + /* Calculate current pending hard interrupts for display */ + s->slaves[i].intreg_pending &= CPU_SOFTIRQ_MASK | CPU_IRQ_INT15_IN | + CPU_IRQ_TIMER_IN; + if (i == s->target_cpu) { + for (j = 0; j < 32; j++) { + if ((s->intregm_pending & (1 << j)) && intbit_to_level[j]) { + s->slaves[i].intreg_pending |= 1 << intbit_to_level[j]; + } + } + } + + /* Level 15 and CPU timer interrupts are only masked when + the MASTER_DISABLE bit is set */ + if (!(s->intregm_disabled & MASTER_DISABLE)) { + pil_pending |= s->slaves[i].intreg_pending & + (CPU_IRQ_INT15_IN | CPU_IRQ_TIMER_IN); + } + + /* Add soft interrupts */ + pil_pending |= (s->slaves[i].intreg_pending & CPU_SOFTIRQ_MASK) >> 16; + + if (set_irqs) { + /* Since there is not really an interrupt 0 (and pil_pending + * and irl_out bit zero are thus always zero) there is no need + * to do anything with cpu_irqs[i][0] and it is OK not to do + * the j=0 iteration of this loop. + */ + for (j = MAX_PILS-1; j > 0; j--) { + if (pil_pending & (1 << j)) { + if (!(s->slaves[i].irl_out & (1 << j))) { + qemu_irq_raise(s->cpu_irqs[i][j]); + } + } else { + if (s->slaves[i].irl_out & (1 << j)) { + qemu_irq_lower(s->cpu_irqs[i][j]); + } + } + } + } + s->slaves[i].irl_out = pil_pending; + } +} + +/* + * "irq" here is the bit number in the system interrupt register to + * separate serial and keyboard interrupts sharing a level. + */ +static void slavio_set_irq(void *opaque, int irq, int level) +{ + SLAVIO_INTCTLState *s = opaque; + uint32_t mask = 1 << irq; + uint32_t pil = intbit_to_level[irq]; + unsigned int i; + + trace_slavio_set_irq(s->target_cpu, irq, pil, level); + if (pil > 0) { + if (level) { +#ifdef DEBUG_IRQ_COUNT + s->irq_count[pil]++; +#endif + s->intregm_pending |= mask; + if (pil == 15) { + for (i = 0; i < MAX_CPUS; i++) { + s->slaves[i].intreg_pending |= 1 << pil; + } + } + } else { + s->intregm_pending &= ~mask; + if (pil == 15) { + for (i = 0; i < MAX_CPUS; i++) { + s->slaves[i].intreg_pending &= ~(1 << pil); + } + } + } + slavio_check_interrupts(s, 1); + } +} + +static void slavio_set_timer_irq_cpu(void *opaque, int cpu, int level) +{ + SLAVIO_INTCTLState *s = opaque; + + trace_slavio_set_timer_irq_cpu(cpu, level); + + if (level) { + s->slaves[cpu].intreg_pending |= CPU_IRQ_TIMER_IN; + } else { + s->slaves[cpu].intreg_pending &= ~CPU_IRQ_TIMER_IN; + } + + slavio_check_interrupts(s, 1); +} + +static void slavio_set_irq_all(void *opaque, int irq, int level) +{ + if (irq < 32) { + slavio_set_irq(opaque, irq, level); + } else { + slavio_set_timer_irq_cpu(opaque, irq - 32, level); + } +} + +static int vmstate_intctl_post_load(void *opaque, int version_id) +{ + SLAVIO_INTCTLState *s = opaque; + + slavio_check_interrupts(s, 0); + return 0; +} + +static const VMStateDescription vmstate_intctl_cpu = { + .name ="slavio_intctl_cpu", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT32(intreg_pending, SLAVIO_CPUINTCTLState), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_intctl = { + .name ="slavio_intctl", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = vmstate_intctl_post_load, + .fields = (VMStateField []) { + VMSTATE_STRUCT_ARRAY(slaves, SLAVIO_INTCTLState, MAX_CPUS, 1, + vmstate_intctl_cpu, SLAVIO_CPUINTCTLState), + VMSTATE_UINT32(intregm_pending, SLAVIO_INTCTLState), + VMSTATE_UINT32(intregm_disabled, SLAVIO_INTCTLState), + VMSTATE_UINT32(target_cpu, SLAVIO_INTCTLState), + VMSTATE_END_OF_LIST() + } +}; + +static void slavio_intctl_reset(DeviceState *d) +{ + SLAVIO_INTCTLState *s = container_of(d, SLAVIO_INTCTLState, busdev.qdev); + int i; + + for (i = 0; i < MAX_CPUS; i++) { + s->slaves[i].intreg_pending = 0; + s->slaves[i].irl_out = 0; + } + s->intregm_disabled = ~MASTER_IRQ_MASK; + s->intregm_pending = 0; + s->target_cpu = 0; + slavio_check_interrupts(s, 0); +} + +static int slavio_intctl_init1(SysBusDevice *dev) +{ + SLAVIO_INTCTLState *s = FROM_SYSBUS(SLAVIO_INTCTLState, dev); + unsigned int i, j; + char slave_name[45]; + + qdev_init_gpio_in(&dev->qdev, slavio_set_irq_all, 32 + MAX_CPUS); + memory_region_init_io(&s->iomem, &slavio_intctlm_mem_ops, s, + "master-interrupt-controller", INTCTLM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + for (i = 0; i < MAX_CPUS; i++) { + snprintf(slave_name, sizeof(slave_name), + "slave-interrupt-controller-%i", i); + for (j = 0; j < MAX_PILS; j++) { + sysbus_init_irq(dev, &s->cpu_irqs[i][j]); + } + memory_region_init_io(&s->slaves[i].iomem, &slavio_intctl_mem_ops, + &s->slaves[i], slave_name, INTCTL_SIZE); + sysbus_init_mmio(dev, &s->slaves[i].iomem); + s->slaves[i].cpu = i; + s->slaves[i].master = s; + } + + return 0; +} + +static void slavio_intctl_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = slavio_intctl_init1; + dc->reset = slavio_intctl_reset; + dc->vmsd = &vmstate_intctl; +} + +static const TypeInfo slavio_intctl_info = { + .name = "slavio_intctl", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SLAVIO_INTCTLState), + .class_init = slavio_intctl_class_init, +}; + +static void slavio_intctl_register_types(void) +{ + type_register_static(&slavio_intctl_info); +} + +type_init(slavio_intctl_register_types) diff --git a/hw/intc/sun4c_intctl.c b/hw/intc/sun4c_intctl.c new file mode 100644 index 0000000000..1096375670 --- /dev/null +++ b/hw/intc/sun4c_intctl.c @@ -0,0 +1,208 @@ +/* + * QEMU Sparc Sun4c interrupt controller emulation + * + * Based on slavio_intctl, copyright (c) 2003-2005 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "hw/hw.h" +#include "hw/sparc/sun4m.h" +#include "monitor/monitor.h" +#include "hw/sysbus.h" + +//#define DEBUG_IRQ_COUNT +//#define DEBUG_IRQ + +#ifdef DEBUG_IRQ +#define DPRINTF(fmt, ...) \ + do { printf("IRQ: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) +#endif + +/* + * Registers of interrupt controller in sun4c. + * + */ + +#define MAX_PILS 16 + +typedef struct Sun4c_INTCTLState { + SysBusDevice busdev; + MemoryRegion iomem; +#ifdef DEBUG_IRQ_COUNT + uint64_t irq_count; +#endif + qemu_irq cpu_irqs[MAX_PILS]; + const uint32_t *intbit_to_level; + uint32_t pil_out; + uint8_t reg; + uint8_t pending; +} Sun4c_INTCTLState; + +#define INTCTL_SIZE 1 + +static void sun4c_check_interrupts(void *opaque); + +static uint64_t sun4c_intctl_mem_read(void *opaque, hwaddr addr, + unsigned size) +{ + Sun4c_INTCTLState *s = opaque; + uint32_t ret; + + ret = s->reg; + DPRINTF("read reg 0x" TARGET_FMT_plx " = %x\n", addr, ret); + + return ret; +} + +static void sun4c_intctl_mem_write(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + Sun4c_INTCTLState *s = opaque; + + DPRINTF("write reg 0x" TARGET_FMT_plx " = %x\n", addr, (unsigned)val); + val &= 0xbf; + s->reg = val; + sun4c_check_interrupts(s); +} + +static const MemoryRegionOps sun4c_intctl_mem_ops = { + .read = sun4c_intctl_mem_read, + .write = sun4c_intctl_mem_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 1, + }, +}; + +static const uint32_t intbit_to_level[] = { 0, 1, 4, 6, 8, 10, 0, 14, }; + +static void sun4c_check_interrupts(void *opaque) +{ + Sun4c_INTCTLState *s = opaque; + uint32_t pil_pending; + unsigned int i; + + pil_pending = 0; + if (s->pending && !(s->reg & 0x80000000)) { + for (i = 0; i < 8; i++) { + if (s->pending & (1 << i)) + pil_pending |= 1 << intbit_to_level[i]; + } + } + + for (i = 0; i < MAX_PILS; i++) { + if (pil_pending & (1 << i)) { + if (!(s->pil_out & (1 << i))) + qemu_irq_raise(s->cpu_irqs[i]); + } else { + if (s->pil_out & (1 << i)) + qemu_irq_lower(s->cpu_irqs[i]); + } + } + s->pil_out = pil_pending; +} + +/* + * "irq" here is the bit number in the system interrupt register + */ +static void sun4c_set_irq(void *opaque, int irq, int level) +{ + Sun4c_INTCTLState *s = opaque; + uint32_t mask = 1 << irq; + uint32_t pil = intbit_to_level[irq]; + + DPRINTF("Set irq %d -> pil %d level %d\n", irq, pil, + level); + if (pil > 0) { + if (level) { +#ifdef DEBUG_IRQ_COUNT + s->irq_count++; +#endif + s->pending |= mask; + } else { + s->pending &= ~mask; + } + sun4c_check_interrupts(s); + } +} + +static const VMStateDescription vmstate_sun4c_intctl = { + .name ="sun4c_intctl", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField []) { + VMSTATE_UINT8(reg, Sun4c_INTCTLState), + VMSTATE_UINT8(pending, Sun4c_INTCTLState), + VMSTATE_END_OF_LIST() + } +}; + +static void sun4c_intctl_reset(DeviceState *d) +{ + Sun4c_INTCTLState *s = container_of(d, Sun4c_INTCTLState, busdev.qdev); + + s->reg = 1; + s->pending = 0; +} + +static int sun4c_intctl_init1(SysBusDevice *dev) +{ + Sun4c_INTCTLState *s = FROM_SYSBUS(Sun4c_INTCTLState, dev); + unsigned int i; + + memory_region_init_io(&s->iomem, &sun4c_intctl_mem_ops, s, + "intctl", INTCTL_SIZE); + sysbus_init_mmio(dev, &s->iomem); + qdev_init_gpio_in(&dev->qdev, sun4c_set_irq, 8); + + for (i = 0; i < MAX_PILS; i++) { + sysbus_init_irq(dev, &s->cpu_irqs[i]); + } + + return 0; +} + +static void sun4c_intctl_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = sun4c_intctl_init1; + dc->reset = sun4c_intctl_reset; + dc->vmsd = &vmstate_sun4c_intctl; +} + +static const TypeInfo sun4c_intctl_info = { + .name = "sun4c_intctl", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Sun4c_INTCTLState), + .class_init = sun4c_intctl_class_init, +}; + +static void sun4c_intctl_register_types(void) +{ + type_register_static(&sun4c_intctl_info); +} + +type_init(sun4c_intctl_register_types) |