hw/arm: add authenticated A6 IMG3 boot lab

Model the A6 crypto, interrupt, USB, and platform blocks needed to boot SecureROM through iBSS into iBEC Recovery.

Add local lab identity, IMG3, and APTicket tooling, patched macOS recovery utilities, UART and GDB access, and English end-user documentation.
This commit is contained in:
2026-09-01 09:51:49 -07:00
parent 47977dd34a
commit 5d9a60a926
45 changed files with 6002 additions and 265 deletions
+138 -3
View File
@@ -9,9 +9,11 @@
#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)
@@ -30,12 +32,19 @@ OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XAicState, S5L8950X_AIC)
#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;
@@ -44,6 +53,13 @@ struct S5L8950XAicState {
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;
};
@@ -59,7 +75,7 @@ static bool s5l8950x_aic_irq_is_active(S5L8950XAicState *s, int irq)
static void s5l8950x_aic_update(S5L8950XAicState *s)
{
bool active = false;
bool active = s->timer_pending && s->timer_irq_enabled;
int irq;
for (irq = 0; irq < S5L8950X_AIC_NUM_IRQS; irq++) {
@@ -72,10 +88,83 @@ static void s5l8950x_aic_update(S5L8950XAicState *s)
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);
@@ -105,8 +194,7 @@ static uint64_t s5l8950x_aic_read(void *opaque, hwaddr offset, unsigned size)
S5L8950XAicState *s = opaque;
uint64_t ticks;
ticks = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24000000,
NANOSECONDS_PER_SECOND);
ticks = s5l8950x_aic_ticks();
switch (offset) {
case AIC_REV:
return 2;
@@ -126,6 +214,15 @@ static uint64_t s5l8950x_aic_read(void *opaque, hwaddr offset, unsigned size)
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];
@@ -163,6 +260,35 @@ static void s5l8950x_aic_write(void *opaque, hwaddr offset,
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;
@@ -209,6 +335,13 @@ static void s5l8950x_aic_reset(DeviceState *dev)
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);
}
@@ -217,6 +350,8 @@ 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);