/* * Apple Interrupt Controller v2 as used by the S5L8950X. * * The A6 SecureROM uses the same register layout later documented for AICv2: * event acknowledge at 0x2004, per-source destinations at 0x3000 and mask * set/clear banks at 0x4100/0x4180. */ #include "qemu/osdep.h" #include "qemu/log.h" #include "qemu/timer.h" #include "hw/core/cpu.h" #include "hw/core/sysbus.h" #include "hw/core/irq.h" #include "qom/object.h" #include "target/arm/cpu.h" #define TYPE_S5L8950X_AIC "s5l8950x-aic" OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XAicState, S5L8950X_AIC) #define S5L8950X_AIC_NUM_IRQS 256 #define S5L8950X_AIC_NUM_BANKS (S5L8950X_AIC_NUM_IRQS / 32) #define AIC_REV 0x0000 #define AIC_CAP0 0x0004 #define AIC_CAP1 0x0008 #define AIC_RST 0x000c #define AIC_GLB_CFG 0x0010 #define AIC_TIME_LO 0x0020 #define AIC_TIME_HI 0x0028 #define AIC_WHOAMI 0x2000 #define AIC_ACK 0x2004 #define AIC_IPI_SET 0x2008 #define AIC_IPI_CLR 0x200c #define AIC_TIMER_CTRL 0x2010 #define AIC_TIMER_COMPARE 0x2014 #define AIC_TIMER_STATE 0x2018 #define AIC_TIMER_MASK_SET 0x201c #define AIC_TIMER_MASK_CLR 0x2020 #define AIC_SRC_CFG_BASE 0x3000 #define AIC_MASK_SET_BASE 0x4100 #define AIC_MASK_CLR_BASE 0x4180 #define AIC_INT_STATE_BASE 0x4200 #define AIC_EVENT_EXT 0x00010000 #define AIC_EVENT_TIMER 0x00070001 #define AIC_CLOCK_HZ 24000000 struct S5L8950XAicState { SysBusDevice parent_obj; MemoryRegion iomem; uint32_t mask[S5L8950X_AIC_NUM_BANKS]; uint32_t pending[S5L8950X_AIC_NUM_BANKS]; uint32_t src_cfg[S5L8950X_AIC_NUM_IRQS]; uint32_t glb_cfg; uint32_t timer_ctrl; uint32_t timer_compare; uint32_t timer_state; bool timer_irq_enabled; bool timer_pending; bool timer_has_fired; QEMUTimer *timer; qemu_irq irq; qemu_irq fiq; }; static bool s5l8950x_aic_irq_is_active(S5L8950XAicState *s, int irq) { int bank = irq / 32; uint32_t bit = BIT(irq % 32); return (s->pending[bank] & bit) && !(s->mask[bank] & bit) && (s->src_cfg[irq] & 1); } static void s5l8950x_aic_update(S5L8950XAicState *s) { bool active = s->timer_pending && s->timer_irq_enabled; int irq; for (irq = 0; irq < S5L8950X_AIC_NUM_IRQS; irq++) { if (s5l8950x_aic_irq_is_active(s, irq)) { active = true; break; } } qemu_set_irq(s->irq, active); qemu_irq_lower(s->fiq); } static uint64_t s5l8950x_aic_ticks(void) { return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), AIC_CLOCK_HZ, NANOSECONDS_PER_SECOND); } static void s5l8950x_aic_timer_reschedule(S5L8950XAicState *s); static void s5l8950x_aic_timer_expired(void *opaque) { S5L8950XAicState *s = opaque; CPUState *cs = first_cpu; vaddr pc = cs ? ARM_CPU(cs)->env.regs[15] : 0; /* * iBoot initially shares the top of its bootstrap stack with the IRQ * stack. On A6 the CPU reaches WFE well before the first 10 ms timer * deadline, while a non-icount TCG run can take longer in host time. * Do not inject that first local event until the bootstrap code has * actually reached WFE; later events retain their normal timing and may * preempt scheduler tasks. */ if (!s->timer_has_fired && cs && (!cs->halted || pc < 0x10000854 || pc > 0x10000860)) { timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + NANOSECONDS_PER_SECOND / 1000); return; } if (!s->timer_has_fired) { qemu_log_mask(LOG_GUEST_ERROR, "s5l8950x-aic: first local timer event at " "PC=0x%" VADDR_PRIx "\n", pc); } s->timer_has_fired = true; s->timer_state |= 1; s->timer_pending = true; s5l8950x_aic_update(s); } static void s5l8950x_aic_timer_reschedule(S5L8950XAicState *s) { uint32_t delta = s->timer_compare; int64_t delta_ns; timer_del(s->timer); /* AICv2's compare register is a relative countdown in 24 MHz ticks. */ if (!(s->timer_ctrl & 1) || delta == UINT32_MAX) { return; } if (!delta) { delta = 1; } delta_ns = muldiv64(delta, NANOSECONDS_PER_SECOND, AIC_CLOCK_HZ); if (!delta_ns) { delta_ns = 1; } timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delta_ns); } static uint32_t s5l8950x_aic_ack(S5L8950XAicState *s) { int irq; /* * The per-CPU timer is reported by AICv2 as local event 1, not as an * external source. iBoot maps 0x00070001 to its internal vector 0xc1. * Acknowledge also masks it until iBoot writes TIMER_MASK_CLR at the end * of the handler, mirroring the external-source acknowledge behaviour. */ if (s->timer_pending && s->timer_irq_enabled) { s->timer_irq_enabled = false; s5l8950x_aic_update(s); return AIC_EVENT_TIMER; } for (irq = 0; irq < S5L8950X_AIC_NUM_IRQS; irq++) { if (s5l8950x_aic_irq_is_active(s, irq)) { s->mask[irq / 32] |= BIT(irq % 32); s5l8950x_aic_update(s); return AIC_EVENT_EXT | irq; } } s5l8950x_aic_update(s); return 0; } static void s5l8950x_aic_set_irq(void *opaque, int irq, int level) { S5L8950XAicState *s = opaque; uint32_t bit = BIT(irq % 32); if (level) { s->pending[irq / 32] |= bit; } else { s->pending[irq / 32] &= ~bit; } s5l8950x_aic_update(s); } static uint64_t s5l8950x_aic_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XAicState *s = opaque; uint64_t ticks; ticks = s5l8950x_aic_ticks(); switch (offset) { case AIC_REV: return 2; case AIC_CAP0: return S5L8950X_AIC_NUM_IRQS; case AIC_CAP1: return 0; case AIC_RST: return 0; case AIC_GLB_CFG: return s->glb_cfg; case AIC_TIME_LO: return (uint32_t)ticks; case AIC_TIME_HI: return (uint32_t)(ticks >> 32); case AIC_WHOAMI: return 0; case AIC_ACK: return s5l8950x_aic_ack(s); case AIC_TIMER_CTRL: return s->timer_ctrl; case AIC_TIMER_COMPARE: return s->timer_compare; case AIC_TIMER_STATE: return s->timer_state; case AIC_TIMER_MASK_SET: case AIC_TIMER_MASK_CLR: return 0; case AIC_SRC_CFG_BASE ... AIC_SRC_CFG_BASE + S5L8950X_AIC_NUM_IRQS * 4 - 4: return s->src_cfg[(offset - AIC_SRC_CFG_BASE) / 4]; case AIC_MASK_SET_BASE ... AIC_MASK_SET_BASE + S5L8950X_AIC_NUM_BANKS * 4 - 4: return s->mask[(offset - AIC_MASK_SET_BASE) / 4]; case AIC_MASK_CLR_BASE ... AIC_MASK_CLR_BASE + S5L8950X_AIC_NUM_BANKS * 4 - 4: return s->mask[(offset - AIC_MASK_CLR_BASE) / 4]; case AIC_INT_STATE_BASE ... AIC_INT_STATE_BASE + S5L8950X_AIC_NUM_BANKS * 4 - 4: return s->pending[(offset - AIC_INT_STATE_BASE) / 4]; default: qemu_log_mask(LOG_UNIMP, "%s: unimplemented read at 0x%" HWADDR_PRIx "\n", __func__, offset); return 0; } } static void s5l8950x_aic_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { S5L8950XAicState *s = opaque; switch (offset) { case AIC_RST: memset(s->mask, 0xff, sizeof(s->mask)); memset(s->pending, 0, sizeof(s->pending)); s5l8950x_aic_update(s); break; case AIC_GLB_CFG: s->glb_cfg = value; break; case AIC_IPI_SET: case AIC_IPI_CLR: break; case AIC_TIMER_CTRL: s->timer_ctrl = value; if (!(s->timer_ctrl & 1)) { timer_del(s->timer); } break; case AIC_TIMER_COMPARE: s->timer_compare = value; s5l8950x_aic_timer_reschedule(s); break; case AIC_TIMER_STATE: s->timer_state &= ~value; if (value & 1) { s->timer_pending = false; s5l8950x_aic_update(s); } break; case AIC_TIMER_MASK_SET: if (value & 2) { s->timer_irq_enabled = false; s5l8950x_aic_update(s); } break; case AIC_TIMER_MASK_CLR: if (value & 2) { s->timer_irq_enabled = true; s5l8950x_aic_update(s); } break; case AIC_SRC_CFG_BASE ... AIC_SRC_CFG_BASE + S5L8950X_AIC_NUM_IRQS * 4 - 4: s->src_cfg[(offset - AIC_SRC_CFG_BASE) / 4] = value; s5l8950x_aic_update(s); break; case AIC_MASK_SET_BASE ... AIC_MASK_SET_BASE + S5L8950X_AIC_NUM_BANKS * 4 - 4: s->mask[(offset - AIC_MASK_SET_BASE) / 4] |= value; s5l8950x_aic_update(s); break; case AIC_MASK_CLR_BASE ... AIC_MASK_CLR_BASE + S5L8950X_AIC_NUM_BANKS * 4 - 4: s->mask[(offset - AIC_MASK_CLR_BASE) / 4] &= ~value; s5l8950x_aic_update(s); break; default: qemu_log_mask(LOG_UNIMP, "%s: unimplemented write at 0x%" HWADDR_PRIx " = 0x%08" PRIx64 "\n", __func__, offset, value); break; } } static const MemoryRegionOps s5l8950x_aic_ops = { .read = s5l8950x_aic_read, .write = s5l8950x_aic_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, }, .impl = { .min_access_size = 4, .max_access_size = 4, }, }; static void s5l8950x_aic_reset(DeviceState *dev) { S5L8950XAicState *s = S5L8950X_AIC(dev); memset(s->mask, 0xff, sizeof(s->mask)); memset(s->pending, 0, sizeof(s->pending)); memset(s->src_cfg, 0, sizeof(s->src_cfg)); s->glb_cfg = 0; s->timer_ctrl = 0; s->timer_compare = UINT32_MAX; s->timer_state = 0; s->timer_irq_enabled = false; s->timer_pending = false; s->timer_has_fired = false; timer_del(s->timer); s5l8950x_aic_update(s); } static void s5l8950x_aic_init(Object *obj) { S5L8950XAicState *s = S5L8950X_AIC(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, s5l8950x_aic_timer_expired, s); memory_region_init_io(&s->iomem, obj, &s5l8950x_aic_ops, s, TYPE_S5L8950X_AIC, 0x8000); sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->irq); sysbus_init_irq(sbd, &s->fiq); qdev_init_gpio_in(DEVICE(obj), s5l8950x_aic_set_irq, S5L8950X_AIC_NUM_IRQS); } static void s5l8950x_aic_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, s5l8950x_aic_reset); } static const TypeInfo s5l8950x_aic_info = { .name = TYPE_S5L8950X_AIC, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(S5L8950XAicState), .instance_init = s5l8950x_aic_init, .class_init = s5l8950x_aic_class_init, }; static void s5l8950x_aic_register_types(void) { type_register_static(&s5l8950x_aic_info); } type_init(s5l8950x_aic_register_types)