This repository has been archived by the owner on Apr 14, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
T8030: basic Apple Interrupt Controller emulation
- Loading branch information
1 parent
899c97e
commit 063c038
Showing
3 changed files
with
524 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); |
Oops, something went wrong.