diff --git a/hw/intc/apple-aic.c b/hw/intc/apple-aic.c new file mode 100644 index 00000000000..baf915f4ec7 --- /dev/null +++ b/hw/intc/apple-aic.c @@ -0,0 +1,397 @@ +#include "qemu/osdep.h" +#include "hw/intc/apple-aic.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "qemu/bitops.h" +#include "qemu/lockable.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "hw/arm/xnu_dtb.h" + +//cpu is getting next interrupt to process, find one +static unsigned int apple_aic_find_irq_cpu(AppleAICState* s, unsigned int cpu_id){ + // check for IPI + for(int i = 0; i < s->numCPU; i++){ /* source */ + int j = cpu_id; /* target */ + if(!s->cpus[j]->interrupted && s->deferredIPI[i][j]){ + bool isMasked = s->ipi_mask[j] & (i == j ? REG_IPI_FLAG_SELF : REG_IPI_FLAG_OTHER); + if(!isMasked){ + s->cpus[j]->interrupted = true; + s->cpus[j]->is_ipi = true; + s->cpus[j]->ipi_source = i; + s->cpus[j]->ack = (i == j ? REG_ACK_IPI_SELF : REG_ACK_IPI_OTHER); + s->deferredIPI[i][j] = 0; + return s->cpus[j]->ack; + } + } + } + // check for IRQ + for(int i = 0; i < s->numIRQ;i++) + if(s->ext_irq_state[i]){ + if(!test_bit(i, s->ipid_mask)){ + //find a cpu to interrupt + int cpu = -1; + if(!s->cpus[cpu_id]->interrupted && test_bit(cpu_id, &s->irq_affinity[i])){ + cpu = cpu_id; + } + if(cpu == -1) continue; + s->cpus[cpu]->interrupted = true; + s->cpus[cpu]->is_ipi = false; + s->cpus[cpu]->irq_source = i; + s->cpus[cpu]->ack = i | REG_ACK_TYPE_IRQ; + s->ext_irq_state[i] = 0; + return s->cpus[cpu]->ack; + } + } + return REG_ACK_TYPE_NONE; +} +//update aic and dispatch, call with mutex locked +static void apple_aic_update(AppleAICState* s){ + // This is not the best way to handle this + // Interrupts should be grouped in order for one CPU to handle it all at once + + // check for IPI + for(int i = 0; i < s->numCPU; i++) /* source */ + for(int j = 0; j < s->numCPU; j++) /* target */ + if(!s->cpus[j]->interrupted && s->deferredIPI[i][j]){ + bool isMasked = s->ipi_mask[j] & (i == j ? REG_IPI_FLAG_SELF : REG_IPI_FLAG_OTHER); + if(!isMasked){ + s->cpus[j]->interrupted = true; + s->cpus[j]->is_ipi = true; + s->cpus[j]->ipi_source = i; + s->cpus[j]->ack = (i == j ? REG_ACK_IPI_SELF : REG_ACK_IPI_OTHER); + s->deferredIPI[i][j] = 0; + qemu_irq_raise(s->cpu_irqs[j]); + } + } + // check for IRQ + for(int i = 0; i < s->numIRQ;i++) + if(s->ext_irq_state[i]){ + if(!test_bit(i, s->ipid_mask)){ + //find a cpu to interrupt + int cpu = -1; + for(int j = 0; j < s->numCPU; j++) + if(!s->cpus[j]->interrupted && test_bit(j, &s->irq_affinity[i])){ + cpu = j; + break; + } + if(cpu == -1) continue; + s->cpus[cpu]->interrupted = true; + s->cpus[cpu]->is_ipi = false; + s->cpus[cpu]->irq_source = i; + s->cpus[cpu]->ack = i; + s->ext_irq_state[i] = false; + qemu_irq_raise(s->cpu_irqs[cpu]); + } + } +} +static void apple_aic_set_irq(void *opaque, int irq, int level){ + AppleAICState* s = APPLE_AIC(opaque); + WITH_QEMU_LOCK_GUARD(&s->mutex){ + s->ext_irq_state[irq] = level & 1; + if(level == 1) { + apple_aic_update(s); + } + } +} +static void apple_aic_tick(void *opaque) { + AppleAICState* s = APPLE_AIC(opaque); + WITH_QEMU_LOCK_GUARD(&s->mutex){ + apple_aic_update(s); + } + timer_mod_ns(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + kDeferredIPITimerDefault); +} +static void apple_aic_write(void *opaque, + hwaddr addr, + uint64_t data, + unsigned size){ + AppleAICOpaque* o = (AppleAICOpaque*)opaque; + AppleAICState* s = APPLE_AIC(o->aic); + WITH_QEMU_LOCK_GUARD(&s->mutex){ + if (addr >= 0x6000) { /* REG_TSTAMP */ + //TODO: implement + fprintf(stderr, "AIC: Write REG_TSTAMP\n"); + return; + } else if (addr >= 0x5000) { /* REG_PERCPU(r,c) */ + unsigned int cpu_id = extract32(addr, 7, 5); + unsigned int op = extract32(addr, 0, 7); + switch (op){ + case REG_IPI_DISABLE: + s->ipi_mask[cpu_id] = (~data) & (REG_IPI_FLAG_SELF | REG_IPI_FLAG_OTHER); + return; + case REG_IPI_ENABLE: + s->ipi_mask[cpu_id] = ((~data) & REG_IPI_FLAG_SELF) | ((~data) & REG_IPI_FLAG_OTHER); + return; + case REG_IPI_DEFER_CLEAR: + s->deferredIPI[cpu_id][o->cpu_id] = 0; + return; + default: + break; + } + } else if (addr >= 0x4280) { + //Unknown + } else if (addr >= 0x4100){ /* REG_IRQ_DISABLE; REG_IRQ_ENABLE, REG_IRQ_STAT */ + unsigned int ipid = extract32(addr, 2, 6); + if(ipid < (s->numIRQ >> 5)){ + if (addr >= 0x4200) { /* REG_IRQ_STAT */ + s->ipid_mask[ipid] = data; + return; + } else if (addr >= 0x4180){ /* REG_IRQ_ENABLE */ + for(int i = 0; i < 32; i++) + if(data & (1 << i)) { + s->ipid_mask[ipid] &= ~(1 << i); + } + return; + } else { /* REG_IRQ_DISABLE */ + for(int i = 0; i < 32; i++) + if(data & (1 << i)) { + s->ipid_mask[ipid] |= (1 << i); + } + return; + } + } + } else if (addr >= 0x4080){ + //IRQ ACK + //if the vector-th bit is set in ipid-mask, [0, 4) in most dtree, this will be sent + //on t8030, only wdt (watch dog timer) is affected by this + //for wdt device, when IRQ 0 is raised, the device panics + if(!o->interrupted) return; + o->interrupted = false; + fprintf(stderr, "AIC: Received IRQ ack"); + return; + } else if (addr >= 0x3000) { /* REG_IRQ_AFFINITY */ + unsigned int vectorNumber = extract32(addr, 2, 10); + data &= ~(-1 << s->numCPU); + if (data == 0){ + data = ~(-1 << s->numCPU); //any CPU + } + s->irq_affinity[vectorNumber] = data; + return; + } else { + bool set = false; + switch (addr){ + case REG_GLOBAL_CFG: + s->global_cfg = data; + return; + case REG_IPI_SET: + if (data == REG_IPI_FLAG_SELF){ + data = 1 << o->cpu_id; + } + for (int i = 0; i < s->numCPU; i++) + if(test_bit(i, &data)) + { + s->deferredIPI[o->cpu_id][i] = 0; + set = true; + } + if (!set){ + fprintf(stderr, "AIC: Write REG_IPI_SET = 0x%x not set any IPI\n", data); + break; + } + apple_aic_update(s); + return; + case REG_IPI_DEFER_SET: + if (data == REG_IPI_FLAG_SELF){ + data = 1 << o->cpu_id; + } + set = false; + for (int i = 0; i < s->numCPU; i++) + if(test_bit(i, &data)) + { + s->deferredIPI[o->cpu_id][i] = 1; + set = true; + } + if (!set){ + fprintf(stderr, "AIC: Write REG_IPI_DEFER_SET = 0x%x not set any IPI\n", data); + break; + } + apple_aic_update(s); + return; + case REG_IPI_CLEAR: + //TODO: Implement + o->interrupted = false; + return; + default: + break; + } + } + fprintf(stderr, "AIC: Write to unspported reg 0x%x", addr); + } +} +static uint64_t apple_aic_read(void *opaque, + hwaddr addr, + unsigned size){ + AppleAICOpaque* o = (AppleAICOpaque*)opaque; + AppleAICState* s = APPLE_AIC(o->aic); + WITH_QEMU_LOCK_GUARD(&s->mutex){ + if (addr >= 0x6000) { /* REG_TSTAMP */ + //TODO: implement + fprintf(stderr, "AIC: Read REG_TSTAMP\n"); + return 0; + } else if (addr >= 0x5000) { /* REG_PERCPU(r,c) */ + unsigned int cpu_id = extract32(addr, 7, 5); + unsigned int op = extract32(addr, 0, 7); + switch (op){ + case REG_IPI_DISABLE: + return s->ipi_mask[cpu_id]; + case REG_IPI_ENABLE: + return ((~s->ipi_mask[cpu_id]) & REG_IPI_FLAG_SELF) | ((~s->ipi_mask[cpu_id]) & REG_IPI_FLAG_OTHER); + default: + break; + } + } else if (addr >= 0x4280) { + //Unknown + } else if (addr >= 0x4100){ /* REG_IRQ_DISABLE; REG_IRQ_ENABLE, REG_IRQ_STAT */ + unsigned int ipid = extract32(addr, 2, 6); + if(ipid < (s->numIRQ >> 5)){ + if (addr >= 0x4200) { /* REG_IRQ_STAT */ + return s->ipid_mask[ipid]; + } else if (addr >= 0x4180){ /* REG_IRQ_ENABLE */ + return ~s->ipid_mask[ipid]; + } else { /* REG_IRQ_DISABLE */ + return s->ipid_mask[ipid]; + } + } + } else if (addr >= 0x3000) { /* REG_IRQ_AFFINITY */ + unsigned int vectorNumber = extract32(addr, 2, 10); + return s->irq_affinity[vectorNumber]; + } else { + switch (addr){ + case REG_ID_REVISION: + return 2; + case REG_ID_CONFIG: + return (((uint64_t)s->numCPU - 1) << 16) | (s->numIRQ); + case REG_GLOBAL_CFG: + return s->global_cfg; + case REG_ID_CPUID: + return o->cpu_id; + case REG_TSTAMP_LO: + return (uint32_t)s->tick; + case REG_TSTAMP_HI: + return (uint32_t)((s->tick)>>32); + case REG_ACK: + //TODO : implement + if(!o->interrupted){ + int ack = apple_aic_find_irq_cpu(s, o->cpu_id); + if(o->interrupted){ + if(!o->is_ipi && o->irq_source > 3){ + o->interrupted = false; + } + } + return ack; + } + if(!o->is_ipi && o->irq_source > 3){ + o->interrupted = false; + } + qemu_irq_lower(s->cpu_irqs[o->cpu_id]); + return o->ack; + default: + break; + } + } + } + fprintf(stderr, "AIC: Read from unspported reg 0x%x", addr); + return -1; +} + +static const MemoryRegionOps apple_aic_ops = { + .read = apple_aic_read, + .write = apple_aic_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, + .valid.unaligned = false, +}; + +static void apple_aic_init(Object *obj) +{ + SysBusDevice *sbd = SYS_BUS_DEVICE(obj); + AppleAICState *s = APPLE_AIC(obj); + qemu_mutex_init(&s->mutex); + s->cpus = g_malloc0(sizeof(AppleAICOpaque*) * s->numCPU); + s->iomems = g_malloc0(sizeof(MemoryRegion*) * s->numCPU); + for(int i=0; i < s->numCPU; i++){ + s->iomems[i] = g_new(MemoryRegion, 1); + AppleAICOpaque *opaque = g_malloc0(sizeof(AppleAICOpaque)); + opaque->aic = s; + opaque->cpu_id = i; + memory_region_init_io(s->iomems[i], obj, &apple_aic_ops, opaque, + TYPE_APPLE_AIC, s->base_size); + s->cpus[i] = opaque; + } + + qdev_init_gpio_in(DEVICE(obj), apple_aic_set_irq, s->numIRQ); + assert(s->numCPU > 0); + s->ipid_mask = g_malloc0(sizeof(unsigned int) * s->numIPID); + s->ipi_mask = g_malloc0(sizeof(unsigned int) * s->numCPU); + s->irq_affinity = g_malloc0(sizeof(unsigned int) * s->numIRQ); + s->cpu_irqs = g_malloc0(sizeof(qemu_irq) * s->numCPU); + qdev_init_gpio_out(DEVICE(obj), s->cpu_irqs, s->numCPU); + s->deferredIPI = g_malloc0(sizeof(unsigned int*) * s->numCPU); + for(int i = 0; i < s->numCPU; i++){ + sysbus_init_irq(sbd, &s->cpu_irqs[i]); + s->deferredIPI[i] = g_malloc0(sizeof(bool) * s->numCPU); + } + s->ext_irq_state = g_malloc0(sizeof(bool) * s->numIRQ); +} +static void apple_aic_realize(DeviceState *dev, Error **errp){ + AppleAICState *s = APPLE_AIC(dev); + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + apple_aic_tick, dev); + timer_mod_ns(s->timer, kDeferredIPITimerDefault); +} +static void apple_aic_reset(DeviceState *dev){ + AppleAICState *s = APPLE_AIC(dev); + //mask all IRQs + memset(s->ipid_mask, 0xff, sizeof(unsigned int)*s->numIPID); + //Affinity default to 0 + memset(s->irq_affinity, 0, sizeof(unsigned int)*s->numIRQ); + for(int i=0;i < s->numCPU; i++) + { + // mask all IPI + s->ipi_mask[i] = REG_IPI_FLAG_SELF | REG_IPI_FLAG_OTHER; + } +} +AppleAICState* apple_aic_create(hwaddr soc_base, unsigned int numCPU, DTBNode* node){ + DeviceState *dev; + SysBusDevice *bus; + AppleAICState *s; + + dev = qdev_new(TYPE_APPLE_AIC); + s = APPLE_AIC(dev); + DTBProp* prop = get_dtb_prop(node, "reg"); + assert(prop != NULL); + hwaddr* reg = (hwaddr*)prop->value; + s->base = soc_base + reg[0]; + s->base_size = reg[1]; + prop = get_dtb_prop(node, "ipid-mask"); + s->numIPID = prop->length / 4; + s->numIRQ = s->numIPID * 32; + + s->numCPU = numCPU; + overwrite_dtb_prop(node, "#main-cpus", 4, &s->numCPU); + apple_aic_init(OBJECT(dev)); + + return s; +} +static void apple_aic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + dc->realize = apple_aic_realize; + dc->reset = apple_aic_reset; + dc->desc = "Apple Interrupt Controller"; +} + +static const TypeInfo apple_aic_info = { + .name = TYPE_APPLE_AIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AppleAICState), + .class_init = apple_aic_class_init, +}; + +static void apple_aic_register_types(void) +{ + type_register_static(&apple_aic_info); +} + +type_init(apple_aic_register_types); \ No newline at end of file diff --git a/hw/intc/meson.build b/hw/intc/meson.build index 3f82cc230ad..d27f562ca15 100644 --- a/hw/intc/meson.build +++ b/hw/intc/meson.build @@ -1,4 +1,5 @@ softmmu_ss.add(files('intc.c')) +softmmu_ss.add(files('apple-aic.c')) softmmu_ss.add(when: 'CONFIG_ARM_GIC', if_true: files( 'arm_gic.c', 'arm_gic_common.c', diff --git a/include/hw/intc/apple-aic.h b/include/hw/intc/apple-aic.h new file mode 100644 index 00000000000..36eeef7c04a --- /dev/null +++ b/include/hw/intc/apple-aic.h @@ -0,0 +1,126 @@ +#ifndef APPLE_AIC_H +#define APPLE_AIC_H + +#include "hw/sysbus.h" +#include "qom/object.h" +#include "hw/arm/xnu_dtb.h" + +#define TYPE_APPLE_AIC "apple.aic" +OBJECT_DECLARE_SIMPLE_TYPE(AppleAICState, APPLE_AIC) + +#define REG_ID_REVISION 0x0000 +#define REG_ID_CONFIG 0x0004 +#define REG_GLOBAL_CFG 0x0010 +#define REG_TIME_LO 0x0020 +#define REG_TIME_HI 0x0028 +#define REG_ID_CPUID 0x2000 +#define REG_ACK 0x2004 +#define REG_ACK_TYPE_MASK (15 << 16) +#define REG_ACK_TYPE_NONE (0 << 16) +#define REG_ACK_TYPE_IRQ (1 << 16) +#define REG_ACK_TYPE_IPI (4 << 16) +#define REG_ACK_IPI_OTHER 0x40001 +#define REG_ACK_IPI_SELF 0x40002 +#define REG_ACK_NUM_MASK (4095) + +#define REG_IPI_SET 0x2008 +#define REG_IPI_FLAG_SELF (1 << 31) +#define REG_IPI_FLAG_OTHER (1 << 0) +#define REG_IPI_CLEAR 0x200C +#define REG_IPI_DEFER_SET 0x202C +#define REG_IPI_DEFER_CLEAR 0x2030 + +#define REG_IPI_DISABLE 0x0024 +#define REG_IPI_ENABLE 0x0028 + +#define REG_TSTAMP_CTRL 0x2040 +#define REG_TSTAMP_LO 0x2048 +#define REG_TSTAMP_HI 0x204C + +#define REG_TSTAMP(i) (0x6000 + ((i) << 4)) + +#define REG_IRQ_AFFINITY(i) (0x3000 + ((i) << 2)) +#define REG_IRQ_DISABLE(i) (0x4100 + (((i) >> 5) << 2)) +#define REG_IRQ_xABLE_MASK(i) (1 << ((i) & 31)) +#define REG_IRQ_ENABLE(i) (0x4180 + (((i) >> 5) << 2)) +#define REG_IRQ_STAT(i) (0x4200 + (((i) >> 5) << 2)) +#define REG_CPU_REGION 0x5000 +#define REG_CPU_LOCAL 0x2000 +#define REG_CPU_SHIFT 7 +#define REG_PERCPU(r,c) ((r)+REG_CPU_REGION+((c)<vtable->AppleInterruptController._readReg32(this, 4u) & 0x3FF; +this->AppleInterruptController._aicNumExtInts = v22; +v23 = (v22 + 31) >> 5; +this->AppleInterruptController._aicNumIPID = v23; +if ( v11 != 4 * v23 ) + panic( + "\"AppleInterruptController::start: device tree ipid-mask property length is %d but should be %d\"", + v11, + (4 * v23)); +} + +In t8030, we have ipid_length = 72 +=> IRQ(extInts) max nr = 0x240 -> max num IPID = (0x240 + 31)>>5 = 18 (domains) +conviniently, 0x240/18 = 32 (bits) +*/ + +// ((len(ipid_mask)>>2)<<5) +#define kDeferredIPITimerDefault 1536 + +/* +Apparently, t8030 uses fast IPI, which does not rely on AIC but cluster to do IPIs +*/ + +typedef struct { + void *aic; + unsigned int cpu_id; + unsigned int interrupted; + unsigned int ack; + unsigned int is_ipi; + unsigned int ipi_source; + + unsigned int irq_source; +} AppleAICOpaque; + +struct AppleAICState { + SysBusDevice parent_obj; + //reg region per cpu + MemoryRegion** iomems; + //timer + QEMUTimer* timer; + //mutex + QemuMutex mutex; + //reg base address + hwaddr base; + unsigned long base_size; + size_t numIPID; + size_t numIRQ; + size_t numCPU; + //mask of IRQ in domains of 32 + unsigned int *ipid_mask; + //whether IPI i is disabled (bit 31 set: self masked, bit 0 set: other masked) + unsigned int *ipi_mask; + //for IRQ i, if bit x is set, that IRQ should be sent to cpu x (there might be multiple bits set) + unsigned int *irq_affinity; + //cpu opaques + AppleAICOpaque** cpus; + //cpu irqs + qemu_irq *cpu_irqs; + //ext irqs state + bool *ext_irq_state; + //deferred IPIs: 1: set; 0: unset + bool **deferredIPI; + //global cfg + unsigned int global_cfg; + //tick counter + unsigned long tick; + +}; + + +AppleAICState* apple_aic_create(hwaddr soc_base, unsigned int numCPU, DTBNode* node); + +#endif /* APPLE_AIC_H */ \ No newline at end of file