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
+89 -7
View File
@@ -5,6 +5,9 @@
#include "hw/core/boards.h"
#include "hw/core/loader.h"
#include "hw/misc/unimp.h"
#include "hw/misc/s5l8950x-aes.h"
#include "hw/misc/s5l8950x-pke.h"
#include "hw/misc/s5l8950x-sha1.h"
#include "hw/core/sysbus.h"
#include "system/address-spaces.h"
#include "target/arm/cpu.h"
@@ -30,6 +33,39 @@ static const ARMCPRegInfo s5l8950x_cp_reginfo[] = {
.access = PL1_W,
.type = ARM_CP_NOP,
},
{
.name = "APPLE_SWIFT_AUX_CFG",
.cp = 15,
.opc1 = 1,
.crn = 15,
.crm = 2,
.opc2 = 4,
.access = PL1_RW,
.type = ARM_CP_CONST,
.resetvalue = 0,
},
{
.name = "APPLE_SWIFT_AUX_CFG2",
.cp = 15,
.opc1 = 1,
.crn = 15,
.crm = 2,
.opc2 = 5,
.access = PL1_RW,
.type = ARM_CP_CONST,
.resetvalue = 0,
},
{
.name = "APPLE_SWIFT_AUX_CFG3",
.cp = 15,
.opc1 = 1,
.crn = 15,
.crm = 2,
.opc2 = 7,
.access = PL1_RW,
.type = ARM_CP_CONST,
.resetvalue = 0,
},
};
/* Addresses */
@@ -40,21 +76,30 @@ static const ARMCPRegInfo s5l8950x_cp_reginfo[] = {
#define S5L8950X_SDIO_BASE 0x20000000
#define S5L8950X_USB_PHY_BASE 0x36000000
#define S5L8950X_USB_BASE 0x36100000
#define S5L8950X_SHA1_BASE 0x3C500000
#define S5L8950X_SHA1_BASE 0x30200000
#define S5L8950X_SPI_BASE 0x3D200000
#define S5L8950X_AES_BASE 0x3E000000
#define S5L8950X_AES_DMA_BASE 0x37000000
#define S5L8950X_AES_BASE 0x37800000
#define S5L8950X_IIC_BASE 0x3E100000
#define S5L8950X_PKE_BASE 0x3E200000
#define S5L8950X_PKE_BASE 0x33100000
#define S5L8950X_WDT_BASE 0x3E300000
#define S5L8950X_CHIPID_BASE 0x3F500000
#define S5L8950X_TIMER_BASE 0x3F200000
#define S5L8950X_PMGR_BASE 0x3F100000
#define S5L8950X_CLOCK_BASE 0x3F800000
#define S5L8950X_MEMCTL_BASE 0x39500000
#define S5L8950X_AMC_BASE 0x40000000
#define S5L8950X_AIC_BASE 0x3F200000
#define S5L8950X_UART_BASE 0x44300000
#define S5L8950X_UART_BASE 0x32B00000
#define S5L8950X_GPIO_BASE 0x3FA00000
#define S5L8950X_DRAM_BASE 0x80000000
static const hwaddr s5l8950x_i2c_base[] = {
0x33200000,
0x33300000,
0x33400000,
};
static void s5l8950x_init(Object *obj)
{
S5L8950XState *s = S5L8950X(obj);
@@ -84,11 +129,8 @@ static void s5l8950x_realize(DeviceState *dev, Error **errp)
/* Create unimplemented devices */
create_unimplemented_device("s5l8950x.sdio", S5L8950X_SDIO_BASE, 0x10000);
create_unimplemented_device("s5l8950x.sha1", S5L8950X_SHA1_BASE, 0x1000);
create_unimplemented_device("s5l8950x.spi", S5L8950X_SPI_BASE, 0x1000);
create_unimplemented_device("s5l8950x.aes", S5L8950X_AES_BASE, 0x1000);
create_unimplemented_device("s5l8950x.iic", S5L8950X_IIC_BASE, 0x1000);
create_unimplemented_device("s5l8950x.pke", S5L8950X_PKE_BASE, 0x1000);
create_unimplemented_device("s5l8950x.wdt", S5L8950X_WDT_BASE, 0x1000);
create_unimplemented_device("s5l8950x.amc", S5L8950X_AMC_BASE, 0x1000);
@@ -98,6 +140,22 @@ static void s5l8950x_realize(DeviceState *dev, Error **errp)
sysbus_realize_and_unref(s->chipid, &error_fatal);
sysbus_mmio_map(s->chipid, 0, S5L8950X_CHIPID_BASE);
DeviceState *dev_aes = qdev_new(TYPE_S5L8950X_AES);
s->aes = SYS_BUS_DEVICE(dev_aes);
sysbus_realize_and_unref(s->aes, &error_fatal);
sysbus_mmio_map(s->aes, 0, S5L8950X_AES_DMA_BASE);
sysbus_mmio_map(s->aes, 1, S5L8950X_AES_BASE);
DeviceState *dev_pke = qdev_new(TYPE_S5L8950X_PKE);
s->pke = SYS_BUS_DEVICE(dev_pke);
sysbus_realize_and_unref(s->pke, &error_fatal);
sysbus_mmio_map(s->pke, 0, S5L8950X_PKE_BASE);
DeviceState *dev_sha1 = qdev_new(TYPE_S5L8950X_SHA1);
s->sha1 = SYS_BUS_DEVICE(dev_sha1);
sysbus_realize_and_unref(s->sha1, &error_fatal);
sysbus_mmio_map(s->sha1, 0, S5L8950X_SHA1_BASE);
DeviceState *dev_aic = qdev_new(TYPE_S5L8950X_AIC);
s->aic = SYS_BUS_DEVICE(dev_aic);
sysbus_realize_and_unref(s->aic, &error_fatal);
@@ -112,14 +170,38 @@ static void s5l8950x_realize(DeviceState *dev, Error **errp)
s->uart = SYS_BUS_DEVICE(dev_uart);
sysbus_realize_and_unref(s->uart, &error_fatal);
sysbus_mmio_map(s->uart, 0, S5L8950X_UART_BASE);
/* iBoot's A6 UART table maps 0x32b00000 to external IRQ 0x44. */
sysbus_connect_irq(s->uart, 0,
qdev_get_gpio_in(dev_aic, 0x44));
DeviceState *dev_pmgr = qdev_new(TYPE_S5L8950X_PMGR);
s->pmgr = SYS_BUS_DEVICE(dev_pmgr);
sysbus_realize_and_unref(s->pmgr, &error_fatal);
sysbus_mmio_map(s->pmgr, 0, S5L8950X_PMGR_BASE);
DeviceState *dev_clock = qdev_new(TYPE_S5L8950X_CLOCK);
s->clock = SYS_BUS_DEVICE(dev_clock);
sysbus_realize_and_unref(s->clock, &error_fatal);
sysbus_mmio_map(s->clock, 0, S5L8950X_CLOCK_BASE);
DeviceState *dev_memctl = qdev_new(TYPE_S5L8950X_MEMCTL);
s->memctl = SYS_BUS_DEVICE(dev_memctl);
sysbus_realize_and_unref(s->memctl, &error_fatal);
sysbus_mmio_map(s->memctl, 0, S5L8950X_MEMCTL_BASE);
for (size_t i = 0; i < ARRAY_SIZE(s5l8950x_i2c_base); i++) {
DeviceState *dev_i2c = qdev_new(TYPE_S5L8950X_I2C);
s->i2c[i] = SYS_BUS_DEVICE(dev_i2c);
sysbus_realize_and_unref(s->i2c[i], &error_fatal);
sysbus_mmio_map(s->i2c[i], 0, s5l8950x_i2c_base[i]);
sysbus_connect_irq(s->i2c[i], 0,
qdev_get_gpio_in(dev_aic, 0x46 + i));
}
DeviceState *dev_gpio = qdev_new(TYPE_S5L8950X_GPIO);
qdev_prop_set_bit(dev_gpio, "force-dfu", true);
qdev_prop_set_uint8(dev_gpio, "board-straps", 2);
s->gpio = SYS_BUS_DEVICE(dev_gpio);
sysbus_realize_and_unref(s->gpio, &error_fatal);
sysbus_mmio_map(s->gpio, 0, S5L8950X_GPIO_BASE);
+25 -7
View File
@@ -9,6 +9,8 @@
#define TYPE_S5L8950X_UART "s5l8950x-uart"
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUartState, S5L8950X_UART)
#define S5L8950X_UART_RX_FIFO_SIZE 16
struct S5L8950XUartState {
SysBusDevice parent_obj;
@@ -22,7 +24,7 @@ struct S5L8950XUartState {
uint32_t ubrdiv;
uint32_t ufracval;
uint8_t rx_fifo[16];
uint8_t rx_fifo[S5L8950X_UART_RX_FIFO_SIZE];
int rx_count;
qemu_irq irq;
};
@@ -40,6 +42,9 @@ struct S5L8950XUartState {
#define UART_UBRDIV 0x28
#define UART_UFRACVAL 0x2C
#define UART_UFCON_RX_FIFO_RESET BIT(1)
#define UART_UFCON_TX_FIFO_RESET BIT(2)
static void s5l8950x_uart_update_irq(S5L8950XUartState *s)
{
if (s->rx_count > 0) {
@@ -76,7 +81,7 @@ static uint64_t s5l8950x_uart_read(void *opaque, hwaddr offset, unsigned size)
break;
case UART_UFSTAT:
/* Return rx count in low bits, no tx fifo used here */
val = s->rx_count & 0xf;
val = s->rx_count;
break;
case UART_UMSTAT:
val = 0;
@@ -89,6 +94,7 @@ static uint64_t s5l8950x_uart_read(void *opaque, hwaddr offset, unsigned size)
s->rx_count--;
memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_count);
s5l8950x_uart_update_irq(s);
qemu_chr_fe_accept_input(&s->chr);
}
break;
case UART_UBRDIV:
@@ -118,7 +124,14 @@ static void s5l8950x_uart_write(void *opaque, hwaddr offset, uint64_t val, unsig
s->ucon = val;
break;
case UART_UFCON:
s->ufcon = val;
if (val & UART_UFCON_RX_FIFO_RESET) {
s->rx_count = 0;
s5l8950x_uart_update_irq(s);
qemu_chr_fe_accept_input(&s->chr);
}
/* FIFO reset bits are self-clearing; TX is unbuffered. */
s->ufcon = val & ~(UART_UFCON_RX_FIFO_RESET |
UART_UFCON_TX_FIFO_RESET);
break;
case UART_UMCON:
s->umcon = val;
@@ -151,9 +164,13 @@ static void s5l8950x_uart_write(void *opaque, hwaddr offset, uint64_t val, unsig
static const MemoryRegionOps s5l8950x_uart_ops = {
.read = s5l8950x_uart_read,
.write = s5l8950x_uart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.min_access_size = 1,
.max_access_size = 4,
},
.impl = {
.min_access_size = 1,
.max_access_size = 4,
},
};
@@ -162,7 +179,7 @@ static int s5l8950x_uart_can_receive(void *opaque)
{
S5L8950XUartState *s = S5L8950X_UART(opaque);
return sizeof(s->rx_fifo) - s->rx_count;
return S5L8950X_UART_RX_FIFO_SIZE - s->rx_count;
}
static void s5l8950x_uart_receive(void *opaque, const uint8_t *buf, int size)
@@ -170,7 +187,8 @@ static void s5l8950x_uart_receive(void *opaque, const uint8_t *buf, int size)
S5L8950XUartState *s = S5L8950X_UART(opaque);
int i;
for (i = 0; i < size && s->rx_count < sizeof(s->rx_fifo); i++) {
for (i = 0; i < size &&
s->rx_count < S5L8950X_UART_RX_FIFO_SIZE; i++) {
s->rx_fifo[s->rx_count++] = buf[i];
}
s5l8950x_uart_update_irq(s);
+31
View File
@@ -18,6 +18,17 @@
/* Recovery/DFU button sampled by SecureROM as GPIO 0x1906. */
#define S5L8950X_GPIO_DFU_OFFSET (((0x19 * 8) + 6) * sizeof(uint32_t))
/*
* SecureROM packs these four board straps into bits [11:8] of the
* boot-policy word at 0x3f106000. Profile 2 is the authenticated A6
* production path used by this n41ap machine.
*/
#define S5L8950X_GPIO_STRAP_0_OFFSET (((0x02 * 8) + 2) * sizeof(uint32_t))
#define S5L8950X_GPIO_STRAP_1_OFFSET (((0x19 * 8) + 5) * sizeof(uint32_t))
#define S5L8950X_GPIO_STRAP_2_OFFSET (((0x1a * 8) + 0) * sizeof(uint32_t))
#define S5L8950X_GPIO_STRAP_3_OFFSET (((0x1a * 8) + 1) * sizeof(uint32_t))
/* iBEC samples GPIO 1 as an active-low boot/power button. */
#define S5L8950X_GPIO_BOOT_BUTTON_OFFSET sizeof(uint32_t)
#define S5L8950X_GPIO_INPUT_LEVEL BIT(0)
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XGPIOState, S5L8950X_GPIO)
@@ -27,6 +38,7 @@ struct S5L8950XGPIOState {
MemoryRegion iomem;
uint32_t regs[S5L8950X_GPIO_NUM_REGS];
bool force_dfu;
uint8_t board_straps;
};
static uint64_t s5l8950x_gpio_read(void *opaque, hwaddr offset,
@@ -40,9 +52,27 @@ static uint64_t s5l8950x_gpio_read(void *opaque, hwaddr offset,
}
value = s->regs[offset / sizeof(uint32_t)];
if (offset == S5L8950X_GPIO_BOOT_BUTTON_OFFSET) {
/*
* No host button is currently wired up: expose the released level so
* iBEC does not mistake the zero-filled register file for a button
* held continuously and power the device off after its timeout.
*/
value |= S5L8950X_GPIO_INPUT_LEVEL;
}
if (s->force_dfu && offset == S5L8950X_GPIO_DFU_OFFSET) {
value |= S5L8950X_GPIO_INPUT_LEVEL;
}
if ((offset == S5L8950X_GPIO_STRAP_0_OFFSET &&
(s->board_straps & BIT(0))) ||
(offset == S5L8950X_GPIO_STRAP_1_OFFSET &&
(s->board_straps & BIT(1))) ||
(offset == S5L8950X_GPIO_STRAP_2_OFFSET &&
(s->board_straps & BIT(2))) ||
(offset == S5L8950X_GPIO_STRAP_3_OFFSET &&
(s->board_straps & BIT(3)))) {
value |= S5L8950X_GPIO_INPUT_LEVEL;
}
return value;
}
@@ -76,6 +106,7 @@ static const MemoryRegionOps s5l8950x_gpio_ops = {
static const Property s5l8950x_gpio_properties[] = {
DEFINE_PROP_BOOL("force-dfu", S5L8950XGPIOState, force_dfu, false),
DEFINE_PROP_UINT8("board-straps", S5L8950XGPIOState, board_straps, 0),
};
static void s5l8950x_gpio_reset(DeviceState *dev)
+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);
+10 -1
View File
@@ -16,7 +16,16 @@ system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c'))
system_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_debug.c'))
system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-chipid.c', 's5l8950x-pmgr.c'))
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files(
's5l8950x-aes.c',
's5l8950x-pke.c',
's5l8950x-sha1.c',
's5l8950x-chipid.c',
's5l8950x-clock.c',
's5l8950x-i2c.c',
's5l8950x-memctl.c',
's5l8950x-pmgr.c',
))
system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
+589
View File
@@ -0,0 +1,589 @@
/*
* Apple A6 (S5L8950X) AES accelerator and its two-channel DMA front-end.
*
* This models the register protocol used by the dumped A6 SecureROM for
* Image3 KBAG unwrap and DATA encryption/decryption. The built-in GID slot
* is populated only from an explicit lab key file; the key is never exposed
* through MMIO or logs.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "crypto/aes.h"
#include "hw/misc/s5l8950x-aes.h"
#include "hw/core/cpu.h"
#include "hw/core/sysbus.h"
#include "hw/core/qdev-properties.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/units.h"
#include "system/address-spaces.h"
#include "system/dma.h"
#include "target/arm/cpu.h"
#define A6_AES_DMA_MMIO_SIZE 0x3000
#define A6_AES_MMIO_SIZE 0x2000
#define A6_AES_DMA_CHANNELS 3
#define A6_DMA_CONTROL 0x000
#define A6_DMA_CONFIG 0x004
#define A6_DMA_TRANSFER_SIZE 0x00c
#define A6_DMA_DESCRIPTOR 0x014
#define A6_DMA_CHANNEL_SIZE 0x1000
#define A6_AES_CAPABILITIES 0x0000
#define A6_AES_CONTROL 0x1000
#define A6_AES_IV_BASE 0x1010
#define A6_AES_IV_END 0x1020
#define A6_AES_KEY_BASE 0x1020
#define A6_AES_KEY_END 0x1040
#define A6_AES_CTL_ENCRYPT BIT(16)
#define A6_AES_CTL_CBC BIT(17)
#define A6_AES_CTL_KEY_BITS_MASK (3u << 18)
#define A6_AES_CTL_EXPLICIT_KEY BIT(20)
#define A6_AES_CTL_KEY_SELECT_MASK (3u << 21)
#define A6_AES_KEY_SELECT_GID 1
#define A6_AES_MAX_TRANSFER (16 * MiB)
#define A6_IBEC_LOAD_BASE 0xbff00000u
#define A6_IBEC_MIN_SIZE (256 * KiB)
#define A6_IBOOT_RESET_VECTOR 0xea00000eu
typedef struct S5L8950XIBECHandoff {
uint8_t *payload;
uint32_t length;
} S5L8950XIBECHandoff;
struct S5L8950XAESState {
SysBusDevice parent_obj;
MemoryRegion dma_mmio;
MemoryRegion aes_mmio;
char *gid_key_path;
bool force_debug_uarts;
bool authenticated_ibec_handoff;
bool ibec_handoff_queued;
uint8_t gid_key[32];
bool gid_key_loaded;
uint32_t dma_control[A6_AES_DMA_CHANNELS];
uint32_t dma_config[A6_AES_DMA_CHANNELS];
uint32_t dma_transfer_size[A6_AES_DMA_CHANNELS];
uint32_t dma_descriptor[A6_AES_DMA_CHANNELS];
bool dma_started[A6_AES_DMA_CHANNELS];
uint32_t capabilities;
uint32_t control;
uint8_t iv[16];
uint8_t explicit_key[32];
};
static void s5l8950x_aes_handoff_ibec_on_cpu(CPUState *cpu,
run_on_cpu_data data)
{
S5L8950XIBECHandoff *handoff = data.host_ptr;
if (address_space_write_rom(&address_space_memory, A6_IBEC_LOAD_BASE,
MEMTXATTRS_UNSPECIFIED, handoff->payload,
handoff->length) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: iBEC handoff write failed at 0x%08x\n",
A6_IBEC_LOAD_BASE);
goto out;
}
/*
* iBSS has already completed the IMG3, ticket, nonce and AES work before
* this callback is queued. The remaining physical operation is a warm
* CPU transfer into the linked iBEC address; resetting the whole machine
* would incorrectly re-enter SecureROM and consume another core-entry
* slot in its retained SRAM state.
*/
cpu_reset(cpu);
ARM_CPU(cpu)->env.regs[0] = 1;
cpu->halted = 0;
cpu_set_pc(cpu, A6_IBEC_LOAD_BASE);
cpu_exit(cpu);
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: transferred authenticated iBEC (%u bytes) "
"to PC=0x%08x\n",
handoff->length, A6_IBEC_LOAD_BASE);
out:
g_free(handoff->payload);
g_free(handoff);
}
static void s5l8950x_aes_queue_ibec_handoff(S5L8950XAESState *s,
const uint8_t *payload,
uint32_t length)
{
S5L8950XIBECHandoff *handoff;
CPUState *cpu = first_cpu;
if (!cpu || s->ibec_handoff_queued || length < A6_IBEC_MIN_SIZE ||
ldl_le_p(payload) != A6_IBOOT_RESET_VECTOR) {
return;
}
handoff = g_new(S5L8950XIBECHandoff, 1);
handoff->payload = g_memdup2(payload, length);
handoff->length = length;
s->ibec_handoff_queued = true;
async_run_on_cpu(cpu, s5l8950x_aes_handoff_ibec_on_cpu,
RUN_ON_CPU_HOST_PTR(handoff));
}
static bool s5l8950x_aes_enable_bootloader_uarts(uint8_t *image,
size_t image_length,
hwaddr load_base)
{
static const uint16_t helper_tail[] = {
0x680a, /* ldr r2, [r1] */
0x4310, /* orrs r0, r2 */
0x6008, /* str r0, [r1] */
0x4770, /* bx lr */
};
static const uint8_t movs_r0_3[] = { 0x03, 0x20 };
for (size_t offset = 0; offset + 10 <= image_length; offset += 2) {
uint16_t ldr_literal = lduw_le_p(image + offset);
size_t i;
if ((ldr_literal & 0xff00) != 0x4900) {
continue;
}
for (i = 0; i < ARRAY_SIZE(helper_tail); i++) {
if (lduw_le_p(image + offset + 2 + i * 2) != helper_tail[i]) {
break;
}
}
if (i != ARRAY_SIZE(helper_tail)) {
continue;
}
memcpy(image + offset + 4, movs_r0_3, sizeof(movs_r0_3));
qemu_log_mask(LOG_UNIMP,
"s5l8950x.uart: forced debug-uarts=3 after native "
"IMG3 decrypt at 0x%08" HWADDR_PRIx "\n",
load_base + offset + 4);
return true;
}
return false;
}
static bool s5l8950x_aes_read_descriptor(uint32_t address,
hwaddr *buffer,
uint32_t *length)
{
uint8_t descriptor[16];
if (!address ||
dma_memory_read(&address_space_memory, address, descriptor,
sizeof(descriptor),
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
return false;
}
/*
* A6 DMA addresses are 32-bit physical addresses. ldl_le_p() returns a
* signed target-endian value on this host, so widening it directly to
* hwaddr sign-extends SRAM addresses such as 0xbfe00b00. Preserve the
* descriptor's raw 32-bit bit pattern before widening it.
*/
*buffer = (uint32_t)ldl_le_p(descriptor + 8);
*length = ldl_le_p(descriptor + 12);
return true;
}
static size_t s5l8950x_aes_key_length(uint32_t control)
{
switch ((control & A6_AES_CTL_KEY_BITS_MASK) >> 18) {
case 0:
return 16;
case 1:
return 24;
case 2:
return 32;
default:
return 0;
}
}
static bool s5l8950x_aes_crypt(const uint8_t *source, uint8_t *destination,
size_t length, const uint8_t *key,
size_t key_length, const uint8_t iv[16],
bool cbc, bool encrypt)
{
AES_KEY expanded_key;
uint8_t chain[AES_BLOCK_SIZE];
uint8_t block[AES_BLOCK_SIZE];
if ((encrypt ? AES_set_encrypt_key(key, key_length * 8, &expanded_key) :
AES_set_decrypt_key(key, key_length * 8, &expanded_key))) {
return false;
}
memcpy(chain, iv, sizeof(chain));
for (size_t offset = 0; offset < length; offset += AES_BLOCK_SIZE) {
if (encrypt) {
for (size_t i = 0; i < AES_BLOCK_SIZE; i++) {
block[i] = source[offset + i] ^ (cbc ? chain[i] : 0);
}
AES_encrypt(block, destination + offset, &expanded_key);
if (cbc) {
memcpy(chain, destination + offset, sizeof(chain));
}
} else {
AES_decrypt(source + offset, block, &expanded_key);
for (size_t i = 0; i < AES_BLOCK_SIZE; i++) {
destination[offset + i] = block[i] ^ (cbc ? chain[i] : 0);
}
if (cbc) {
memcpy(chain, source + offset, sizeof(chain));
}
}
}
return true;
}
static bool s5l8950x_aes_process(S5L8950XAESState *s)
{
g_autoptr(GByteArray) source = NULL;
g_autoptr(GByteArray) destination = NULL;
const uint8_t *key;
const char *key_name;
hwaddr source_address;
hwaddr destination_address;
uint32_t source_length;
uint32_t destination_length;
uint32_t key_select;
size_t key_length;
bool cbc;
bool encrypt;
if (!s5l8950x_aes_read_descriptor(s->dma_descriptor[1],
&source_address, &source_length) ||
!s5l8950x_aes_read_descriptor(s->dma_descriptor[2],
&destination_address,
&destination_length)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: invalid DMA descriptor\n");
return false;
}
if (!source_length || source_length != destination_length ||
source_length > A6_AES_MAX_TRANSFER || source_length % 16) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: invalid DMA lengths %u/%u\n",
source_length, destination_length);
return false;
}
key_length = s5l8950x_aes_key_length(s->control);
if (!key_length) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: unsupported key-size selector\n");
return false;
}
key_select = (s->control & A6_AES_CTL_KEY_SELECT_MASK) >> 21;
if (key_select == A6_AES_KEY_SELECT_GID) {
if (!s->gid_key_loaded || key_length != sizeof(s->gid_key)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: GID operation requested without "
"a matching 256-bit lab key\n");
return false;
}
key = s->gid_key;
key_name = "lab-GID";
} else if (key_select == 0 &&
(s->control & A6_AES_CTL_EXPLICIT_KEY)) {
key = s->explicit_key;
key_name = "explicit";
} else {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: unsupported built-in key slot %u\n",
key_select);
return false;
}
cbc = (s->control & A6_AES_CTL_CBC) != 0;
encrypt = (s->control & A6_AES_CTL_ENCRYPT) != 0;
source = g_byte_array_sized_new(source_length);
g_byte_array_set_size(source, source_length);
destination = g_byte_array_sized_new(destination_length);
g_byte_array_set_size(destination, destination_length);
if (dma_memory_read(&address_space_memory, source_address, source->data,
source_length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: DMA source read failed at 0x%08"
HWADDR_PRIx "\n", source_address);
return false;
}
if (!s5l8950x_aes_crypt(source->data, destination->data, source_length,
key, key_length, s->iv, cbc, encrypt)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: AES operation failed\n");
return false;
}
if (s->force_debug_uarts && !encrypt &&
key_select == 0 && (s->control & A6_AES_CTL_EXPLICIT_KEY) &&
!s5l8950x_aes_enable_bootloader_uarts(destination->data,
destination_length,
destination_address)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.uart: debug_enable_uarts helper not found "
"after native IMG3 decrypt\n");
}
if (dma_memory_write(&address_space_memory, destination_address,
destination->data, destination_length,
MEMTXATTRS_UNSPECIFIED) != MEMTX_OK) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.aes: DMA destination write failed at 0x%08"
HWADDR_PRIx "\n", destination_address);
return false;
}
if (s->authenticated_ibec_handoff && !encrypt && key_select == 0 &&
(s->control & A6_AES_CTL_EXPLICIT_KEY)) {
s5l8950x_aes_queue_ibec_handoff(s, destination->data,
destination_length);
}
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: %s AES-%zu-%s key=%s bytes=%u "
"src=0x%08" HWADDR_PRIx " dst=0x%08" HWADDR_PRIx "\n",
encrypt ? "encrypt" : "decrypt", key_length * 8,
cbc ? "CBC" : "ECB",
key_name, source_length, source_address,
destination_address);
return true;
}
static uint64_t s5l8950x_aes_dma_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XAESState *s = opaque;
unsigned channel = offset / A6_DMA_CHANNEL_SIZE;
hwaddr reg = offset % A6_DMA_CHANNEL_SIZE;
if (channel >= A6_AES_DMA_CHANNELS) {
return 0;
}
switch (reg) {
case A6_DMA_CONTROL:
return s->dma_control[channel];
case A6_DMA_CONFIG:
return s->dma_config[channel];
case A6_DMA_TRANSFER_SIZE:
return s->dma_transfer_size[channel];
case A6_DMA_DESCRIPTOR:
return s->dma_descriptor[channel];
default:
return 0;
}
}
static void s5l8950x_aes_dma_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XAESState *s = opaque;
unsigned channel = offset / A6_DMA_CHANNEL_SIZE;
hwaddr reg = offset % A6_DMA_CHANNEL_SIZE;
if (channel >= A6_AES_DMA_CHANNELS) {
return;
}
switch (reg) {
case A6_DMA_CONTROL:
s->dma_control[channel] = value;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: DMA%u control=0x%08" PRIx64 "\n",
channel, value);
if (value == 2) {
s->dma_started[channel] = false;
} else if (channel && (value & 1)) {
s->dma_started[channel] = true;
if (s->dma_started[1] && s->dma_started[2]) {
s5l8950x_aes_process(s);
s->dma_started[1] = false;
s->dma_started[2] = false;
/* Status 0 is idle/complete; SecureROM polls bits 16..17. */
s->dma_control[1] &= ~(3u << 16);
s->dma_control[2] &= ~(3u << 16);
}
}
break;
case A6_DMA_CONFIG:
s->dma_config[channel] = value;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: DMA%u config=0x%08" PRIx64 "\n",
channel, value);
break;
case A6_DMA_TRANSFER_SIZE:
s->dma_transfer_size[channel] = value;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: DMA%u length=0x%08" PRIx64 "\n",
channel, value);
break;
case A6_DMA_DESCRIPTOR:
s->dma_descriptor[channel] = value;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: DMA%u descriptor=0x%08" PRIx64 "\n",
channel, value);
break;
default:
break;
}
}
static uint64_t s5l8950x_aes_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XAESState *s = opaque;
switch (offset) {
case A6_AES_CAPABILITIES:
return s->capabilities;
case A6_AES_CONTROL:
return s->control;
case A6_AES_IV_BASE ... A6_AES_IV_END - 4:
return ldl_le_p(s->iv + offset - A6_AES_IV_BASE);
case A6_AES_KEY_BASE ... A6_AES_KEY_END - 4:
/* Explicit key registers are write-only on the real device. */
return 0;
default:
return 0;
}
}
static void s5l8950x_aes_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XAESState *s = opaque;
switch (offset) {
case A6_AES_CAPABILITIES:
s->capabilities = value;
break;
case A6_AES_CONTROL:
s->control = value;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: control=0x%08" PRIx64 "\n", value);
break;
case A6_AES_IV_BASE ... A6_AES_IV_END - 4:
stl_le_p(s->iv + offset - A6_AES_IV_BASE, value);
break;
case A6_AES_KEY_BASE ... A6_AES_KEY_END - 4:
stl_le_p(s->explicit_key + offset - A6_AES_KEY_BASE, value);
break;
default:
break;
}
}
static const MemoryRegionOps s5l8950x_aes_dma_ops = {
.read = s5l8950x_aes_dma_read,
.write = s5l8950x_aes_dma_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static const MemoryRegionOps s5l8950x_aes_ops = {
.read = s5l8950x_aes_read,
.write = s5l8950x_aes_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static void s5l8950x_aes_reset(DeviceState *dev)
{
S5L8950XAESState *s = S5L8950X_AES(dev);
memset(s->dma_control, 0, sizeof(s->dma_control));
memset(s->dma_config, 0, sizeof(s->dma_config));
memset(s->dma_transfer_size, 0, sizeof(s->dma_transfer_size));
memset(s->dma_descriptor, 0, sizeof(s->dma_descriptor));
memset(s->dma_started, 0, sizeof(s->dma_started));
s->capabilities = 0;
s->control = 0;
memset(s->iv, 0, sizeof(s->iv));
memset(s->explicit_key, 0, sizeof(s->explicit_key));
s->ibec_handoff_queued = false;
}
static void s5l8950x_aes_realize(DeviceState *dev, Error **errp)
{
S5L8950XAESState *s = S5L8950X_AES(dev);
g_autofree gchar *contents = NULL;
gsize length = 0;
g_autoptr(GError) error = NULL;
if (!s->gid_key_path) {
return;
}
if (!g_file_get_contents(s->gid_key_path, &contents, &length, &error)) {
error_setg(errp, "could not load A6 lab GID key '%s': %s",
s->gid_key_path, error->message);
return;
}
if (length != sizeof(s->gid_key)) {
error_setg(errp, "A6 lab GID key '%s' must be exactly 32 bytes",
s->gid_key_path);
return;
}
memcpy(s->gid_key, contents, sizeof(s->gid_key));
s->gid_key_loaded = true;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.aes: loaded private 256-bit lab GID key\n");
}
static void s5l8950x_aes_init(Object *object)
{
S5L8950XAESState *s = S5L8950X_AES(object);
SysBusDevice *sbd = SYS_BUS_DEVICE(object);
memory_region_init_io(&s->dma_mmio, object, &s5l8950x_aes_dma_ops, s,
TYPE_S5L8950X_AES ".dma",
A6_AES_DMA_MMIO_SIZE);
sysbus_init_mmio(sbd, &s->dma_mmio);
memory_region_init_io(&s->aes_mmio, object, &s5l8950x_aes_ops, s,
TYPE_S5L8950X_AES, A6_AES_MMIO_SIZE);
sysbus_init_mmio(sbd, &s->aes_mmio);
}
static const Property s5l8950x_aes_properties[] = {
DEFINE_PROP_STRING("gid-key-file", S5L8950XAESState, gid_key_path),
DEFINE_PROP_BOOL("force-debug-uarts", S5L8950XAESState,
force_debug_uarts, false),
DEFINE_PROP_BOOL("authenticated-ibec-handoff", S5L8950XAESState,
authenticated_ibec_handoff, false),
};
static void s5l8950x_aes_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = s5l8950x_aes_realize;
device_class_set_legacy_reset(dc, s5l8950x_aes_reset);
device_class_set_props(dc, s5l8950x_aes_properties);
}
static const TypeInfo s5l8950x_aes_type_info = {
.name = TYPE_S5L8950X_AES,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XAESState),
.instance_init = s5l8950x_aes_init,
.class_init = s5l8950x_aes_class_init,
};
static void s5l8950x_aes_register_types(void)
{
type_register_static(&s5l8950x_aes_type_info);
}
type_init(s5l8950x_aes_register_types)
+5 -1
View File
@@ -18,7 +18,11 @@ static uint64_t s5l8950x_chipid_read(void *opaque, hwaddr offset, unsigned size)
/* n41ap: CPFM 0x03, SCEP 0x10, BDID 0x00, IBFL 0x00. */
return 0x200D;
case 0x04:
return 0x00;
/*
* SecureROM leaves the high handoff bit asserted before entering
* iBSS. Early iBoot aborts immediately when this latch is clear.
*/
return 0x80000000;
case 0x08:
return 0xDEADBEEF;
case 0x0C:
+137
View File
@@ -0,0 +1,137 @@
/*
* Apple S5L8950X clock/PLL parameter controller.
*
* iBoot programs the ordinary clock registers directly and uses the command
* ports at 0x480..0x4a4 to query PLL parameters. The corresponding status
* ports contain two result bytes plus busy bits. Clock changes complete
* synchronously here; returning a non-zero divider is enough for iBoot to
* derive its early clock tree without inventing asynchronous hardware.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/sysbus.h"
#include "qom/object.h"
#define S5L8950X_CLOCK_REGION_SIZE 0x1000
#define S5L8950X_CLOCK_NUM_REGS \
(S5L8950X_CLOCK_REGION_SIZE / sizeof(uint32_t))
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XClockState, S5L8950X_CLOCK)
struct S5L8950XClockState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t regs[S5L8950X_CLOCK_NUM_REGS];
};
static uint64_t s5l8950x_clock_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XClockState *s = opaque;
return s->regs[offset / sizeof(uint32_t)];
}
static void s5l8950x_clock_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XClockState *s = opaque;
uint32_t reg = value;
uint32_t selector = (reg >> 8) & 0xff;
uint32_t result = 1;
/*
* Parameter 8 describes the divider layout used by iBoot's early clock
* setup. Its bits 2..5 must encode a value in the accepted 4..6 range;
* the other parameters are ordinary non-zero divisors.
*/
if (selector == 8) {
result = 0x10;
}
s->regs[offset / sizeof(uint32_t)] = reg;
/* Read-command ports: publish the result and leave the busy bit clear. */
switch (offset) {
case 0x480:
s->regs[0x490 / 4] =
(s->regs[0x490 / 4] & ~0x0000ff01u) | (result << 8);
break;
case 0x488:
s->regs[0x490 / 4] =
(s->regs[0x490 / 4] & ~0x00ff0010u) | (result << 16);
break;
case 0x498:
s->regs[0x4a8 / 4] =
(s->regs[0x4a8 / 4] & ~0x0000ff01u) | (result << 8);
break;
case 0x4a0:
s->regs[0x4a8 / 4] =
(s->regs[0x4a8 / 4] & ~0x00ff0010u) | (result << 16);
break;
case 0x484:
case 0x48c:
case 0x49c:
case 0x4a4:
/* Write commands are consumed by the clock engine immediately. */
s->regs[offset / 4] = 0;
break;
default:
break;
}
}
static const MemoryRegionOps s5l8950x_clock_ops = {
.read = s5l8950x_clock_read,
.write = s5l8950x_clock_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_clock_reset(DeviceState *dev)
{
S5L8950XClockState *s = S5L8950X_CLOCK(dev);
memset(s->regs, 0, sizeof(s->regs));
s->regs[0x490 / 4] = 0x00010100;
s->regs[0x4a8 / 4] = 0x00010100;
}
static void s5l8950x_clock_init(Object *obj)
{
S5L8950XClockState *s = S5L8950X_CLOCK(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_clock_ops, s,
TYPE_S5L8950X_CLOCK,
S5L8950X_CLOCK_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_clock_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_clock_reset);
}
static const TypeInfo s5l8950x_clock_info = {
.name = TYPE_S5L8950X_CLOCK,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XClockState),
.instance_init = s5l8950x_clock_init,
.class_init = s5l8950x_clock_class_init,
};
static void s5l8950x_clock_register_types(void)
{
type_register_static(&s5l8950x_clock_info);
}
type_init(s5l8950x_clock_register_types)
+115
View File
@@ -0,0 +1,115 @@
/*
* Apple S5L8950X I2C controller (minimal polled-transfer model)
*
* Early iBoot uses the three controllers while bringing up board devices.
* The target devices are not modelled yet, so reads return zero, but command
* FIFO writes complete synchronously and without a NAK. This is sufficient
* to preserve the firmware's normal timeout/error paths while allowing the
* boot chain to continue through board discovery.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/irq.h"
#include "hw/core/sysbus.h"
#include "qom/object.h"
#define S5L8950X_I2C_REGION_SIZE 0x1000
#define S5L8950X_I2C_STATUS 0x14
#define S5L8950X_I2C_STATUS_DONE BIT(27)
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XI2CState, S5L8950X_I2C)
struct S5L8950XI2CState {
SysBusDevice parent_obj;
MemoryRegion iomem;
qemu_irq irq;
uint32_t regs[S5L8950X_I2C_REGION_SIZE / sizeof(uint32_t)];
};
static uint64_t s5l8950x_i2c_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XI2CState *s = opaque;
uint32_t value = s->regs[offset / sizeof(uint32_t)];
if (offset == S5L8950X_I2C_STATUS) {
value |= S5L8950X_I2C_STATUS_DONE;
}
return value;
}
static void s5l8950x_i2c_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XI2CState *s = opaque;
if (offset == S5L8950X_I2C_STATUS) {
s->regs[offset / sizeof(uint32_t)] &= ~value;
if (!(s->regs[offset / sizeof(uint32_t)] &
S5L8950X_I2C_STATUS_DONE)) {
qemu_irq_lower(s->irq);
}
return;
}
s->regs[offset / sizeof(uint32_t)] = value;
if (offset == 0) {
s->regs[S5L8950X_I2C_STATUS / sizeof(uint32_t)] |=
S5L8950X_I2C_STATUS_DONE;
qemu_irq_raise(s->irq);
}
}
static const MemoryRegionOps s5l8950x_i2c_ops = {
.read = s5l8950x_i2c_read,
.write = s5l8950x_i2c_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_i2c_reset(DeviceState *dev)
{
S5L8950XI2CState *s = S5L8950X_I2C(dev);
memset(s->regs, 0, sizeof(s->regs));
qemu_irq_lower(s->irq);
}
static void s5l8950x_i2c_init(Object *obj)
{
S5L8950XI2CState *s = S5L8950X_I2C(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_i2c_ops, s,
TYPE_S5L8950X_I2C, S5L8950X_I2C_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
sysbus_init_irq(sbd, &s->irq);
}
static void s5l8950x_i2c_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_i2c_reset);
}
static const TypeInfo s5l8950x_i2c_info = {
.name = TYPE_S5L8950X_I2C,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XI2CState),
.instance_init = s5l8950x_i2c_init,
.class_init = s5l8950x_i2c_class_init,
};
static void s5l8950x_i2c_register_types(void)
{
type_register_static(&s5l8950x_i2c_info);
}
type_init(s5l8950x_i2c_register_types)
+130
View File
@@ -0,0 +1,130 @@
/*
* Apple S5L8950X DRAM controller training/status model.
*
* iBEC programs the timing registers and then waits for the controller and
* PHY training state in register zero. QEMU's RAM is already usable, so the
* training phases complete synchronously while ordinary register values are
* retained for debugger inspection.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/sysbus.h"
#include "qom/object.h"
#define S5L8950X_MEMCTL_REGION_SIZE 0x1000
#define S5L8950X_MEMCTL_NUM_REGS \
(S5L8950X_MEMCTL_REGION_SIZE / sizeof(uint32_t))
/* Controller ready, PHY ready and all low training stages complete. */
#define S5L8950X_MEMCTL_READY 0x801003ffu
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XMemctlState, S5L8950X_MEMCTL)
struct S5L8950XMemctlState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t regs[S5L8950X_MEMCTL_NUM_REGS];
};
static uint64_t s5l8950x_memctl_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XMemctlState *s = opaque;
if (offset == 0) {
uint32_t status = s->regs[0] | S5L8950X_MEMCTL_READY;
/*
* The 0b101 command starts a training phase whose lane bits are
* active-low. iBEC clears the command before waiting for them to
* return to the completed state.
*/
if ((s->regs[0x14 / 4] & 5) == 5) {
status &= ~0x000003f0u;
}
/*
* Register 0x08 bit 31 requests a controller state transition. The
* corresponding completion state is exposed in status bit 10 and is
* cleared again when iBEC drops the request.
*/
if (s->regs[0x08 / 4] & BIT(31)) {
status |= BIT(10);
} else {
status &= ~BIT(10);
}
return status;
}
/*
* The PHY command/status block starts at 0x34. Its status register at
* 0x44 reports both command-complete stages once the programmed timing
* values have been accepted.
*/
if (offset == 0x44) {
return s->regs[offset / sizeof(uint32_t)] | 0x00500000u;
}
return s->regs[offset / sizeof(uint32_t)];
}
static void s5l8950x_memctl_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XMemctlState *s = opaque;
s->regs[offset / sizeof(uint32_t)] = value;
}
static const MemoryRegionOps s5l8950x_memctl_ops = {
.read = s5l8950x_memctl_read,
.write = s5l8950x_memctl_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_memctl_reset(DeviceState *dev)
{
S5L8950XMemctlState *s = S5L8950X_MEMCTL(dev);
memset(s->regs, 0, sizeof(s->regs));
}
static void s5l8950x_memctl_init(Object *obj)
{
S5L8950XMemctlState *s = S5L8950X_MEMCTL(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_memctl_ops, s,
TYPE_S5L8950X_MEMCTL,
S5L8950X_MEMCTL_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_memctl_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_memctl_reset);
}
static const TypeInfo s5l8950x_memctl_info = {
.name = TYPE_S5L8950X_MEMCTL,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XMemctlState),
.instance_init = s5l8950x_memctl_init,
.class_init = s5l8950x_memctl_class_init,
};
static void s5l8950x_memctl_register_types(void)
{
type_register_static(&s5l8950x_memctl_info);
}
type_init(s5l8950x_memctl_register_types)
+285
View File
@@ -0,0 +1,285 @@
/*
* Apple A6 (S5L8950X) public-key accelerator.
*
* The A6 SecureROM uses this block as a modular-arithmetic coprocessor while
* validating the RSA-2048/RSA-1024 certificate chain embedded in Image3.
* Its software driver stages little-endian operands in the 2 KiB operand
* window and drives a fixed sequence of commands for the public exponent
* 65537. This functional model completes that sequence atomically when all
* operands have been staged; the remaining commands retain the computed
* result in operand slot 1, exactly where the ROM reads it back.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/misc/s5l8950x-pke.h"
#include "hw/core/sysbus.h"
#include "qemu/log.h"
#include "qemu/module.h"
#define A6_PKE_MMIO_SIZE 0x1000
#define A6_PKE_OPERAND_BASE 0x0800
#define A6_PKE_OPERAND_SIZE 0x0800
#define A6_PKE_CONFIG 0x0000
#define A6_PKE_START 0x0008
#define A6_PKE_COMMAND 0x000c
#define A6_PKE_STATUS 0x0010
#define A6_PKE_CONTROL 0x0014
#define A6_PKE_RSA_2048_STAGE 0x01050003
#define A6_PKE_RSA_1024_STAGE 0x010e0003
#define A6_PKE_RSA_STAGE_START 9
#define A6_PKE_MAX_BYTES 256
#define A6_PKE_MAX_LIMBS (A6_PKE_MAX_BYTES / sizeof(uint32_t))
#define A6_PKE_PUBLIC_EXPONENT 65537u
struct S5L8950XPKEState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t config;
uint32_t start;
uint32_t command;
uint32_t status;
uint32_t control;
uint8_t operands[A6_PKE_OPERAND_SIZE];
bool rsa_result_ready;
};
static int s5l8950x_pke_compare(const uint32_t *a, const uint32_t *b,
size_t limbs)
{
for (size_t i = limbs; i-- > 0;) {
if (a[i] != b[i]) {
return a[i] > b[i] ? 1 : -1;
}
}
return 0;
}
static void s5l8950x_pke_subtract(uint32_t *a, const uint32_t *b,
size_t limbs)
{
uint64_t borrow = 0;
for (size_t i = 0; i < limbs; i++) {
uint64_t subtrahend = (uint64_t)b[i] + borrow;
uint64_t minuend = a[i];
a[i] = minuend - subtrahend;
borrow = minuend < subtrahend;
}
}
/* Both operands must be reduced. Their sum then needs at most one subtract. */
static void s5l8950x_pke_add_mod(uint32_t *a, const uint32_t *b,
const uint32_t *modulus, size_t limbs)
{
uint64_t carry = 0;
for (size_t i = 0; i < limbs; i++) {
uint64_t sum = (uint64_t)a[i] + b[i] + carry;
a[i] = sum;
carry = sum >> 32;
}
if (carry || s5l8950x_pke_compare(a, modulus, limbs) >= 0) {
s5l8950x_pke_subtract(a, modulus, limbs);
}
}
static void s5l8950x_pke_mod_mul(uint32_t *out, const uint32_t *a,
const uint32_t *b,
const uint32_t *modulus, size_t limbs)
{
uint32_t result[A6_PKE_MAX_LIMBS] = { 0 };
uint32_t current[A6_PKE_MAX_LIMBS] = { 0 };
memcpy(current, a, limbs * sizeof(*a));
while (s5l8950x_pke_compare(current, modulus, limbs) >= 0) {
s5l8950x_pke_subtract(current, modulus, limbs);
}
for (size_t bit = 0; bit < limbs * 32; bit++) {
if (b[bit / 32] & BIT(bit % 32)) {
s5l8950x_pke_add_mod(result, current, modulus, limbs);
}
s5l8950x_pke_add_mod(current, current, modulus, limbs);
}
memcpy(out, result, limbs * sizeof(*out));
}
static bool s5l8950x_pke_rsa_public(S5L8950XPKEState *s)
{
uint32_t modulus[A6_PKE_MAX_LIMBS] = { 0 };
uint32_t base[A6_PKE_MAX_LIMBS] = { 0 };
uint32_t result[A6_PKE_MAX_LIMBS] = { 0 };
uint32_t squared[A6_PKE_MAX_LIMBS] = { 0 };
size_t width = 64 * ((s->config & 3) + 1);
size_t limbs;
uint32_t exponent = A6_PKE_PUBLIC_EXPONENT;
if (width > A6_PKE_MAX_BYTES || width * 2 > sizeof(s->operands)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.pke: unsupported operand width %zu\n",
width);
return false;
}
limbs = width / sizeof(uint32_t);
for (size_t i = 0; i < limbs; i++) {
modulus[i] = ldl_le_p(s->operands + i * sizeof(uint32_t));
base[i] = ldl_le_p(s->operands + width +
i * sizeof(uint32_t));
}
if (!(modulus[0] & 1) ||
!s5l8950x_pke_compare(modulus, result, limbs)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.pke: invalid RSA modulus\n");
return false;
}
result[0] = 1;
while (exponent) {
if (exponent & 1) {
s5l8950x_pke_mod_mul(result, result, base, modulus, limbs);
}
exponent >>= 1;
if (exponent) {
s5l8950x_pke_mod_mul(squared, base, base, modulus, limbs);
memcpy(base, squared, limbs * sizeof(*base));
}
}
for (size_t i = 0; i < limbs; i++) {
stl_le_p(s->operands + width + i * sizeof(uint32_t), result[i]);
}
s->rsa_result_ready = true;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.pke: completed RSA-%zu public operation\n",
width * 8);
return true;
}
static uint64_t s5l8950x_pke_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XPKEState *s = opaque;
if (offset >= A6_PKE_OPERAND_BASE &&
offset <= A6_PKE_MMIO_SIZE - sizeof(uint32_t)) {
return ldl_le_p(s->operands + offset - A6_PKE_OPERAND_BASE);
}
switch (offset) {
case A6_PKE_CONFIG:
return s->config;
case A6_PKE_START:
return s->start;
case A6_PKE_COMMAND:
return s->command;
case A6_PKE_STATUS:
return s->status;
case A6_PKE_CONTROL:
return s->control;
default:
return 0;
}
}
static void s5l8950x_pke_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XPKEState *s = opaque;
if (offset >= A6_PKE_OPERAND_BASE &&
offset <= A6_PKE_MMIO_SIZE - sizeof(uint32_t)) {
stl_le_p(s->operands + offset - A6_PKE_OPERAND_BASE, value);
return;
}
switch (offset) {
case A6_PKE_CONFIG:
s->config = value;
s->rsa_result_ready = false;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.pke: config=0x%08" PRIx64 "\n", value);
break;
case A6_PKE_START:
s->start = value;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.pke: command=0x%08x start=0x%08" PRIx64
" config=0x%08x\n",
s->command, value, s->config);
if (!s->rsa_result_ready && value == A6_PKE_RSA_STAGE_START &&
(s->command == A6_PKE_RSA_2048_STAGE ||
s->command == A6_PKE_RSA_1024_STAGE)) {
s5l8950x_pke_rsa_public(s);
}
/* The ROM polls bit zero until the command has completed. */
s->start &= ~1u;
break;
case A6_PKE_COMMAND:
s->command = value;
break;
case A6_PKE_STATUS:
s->status = value;
break;
case A6_PKE_CONTROL:
s->control = value;
break;
default:
break;
}
}
static const MemoryRegionOps s5l8950x_pke_ops = {
.read = s5l8950x_pke_read,
.write = s5l8950x_pke_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static void s5l8950x_pke_reset(DeviceState *dev)
{
S5L8950XPKEState *s = S5L8950X_PKE(dev);
s->config = 0;
s->start = 0;
s->command = 0;
s->status = 0;
s->control = 0;
s->rsa_result_ready = false;
memset(s->operands, 0, sizeof(s->operands));
}
static void s5l8950x_pke_init(Object *object)
{
S5L8950XPKEState *s = S5L8950X_PKE(object);
SysBusDevice *sbd = SYS_BUS_DEVICE(object);
memory_region_init_io(&s->iomem, object, &s5l8950x_pke_ops, s,
TYPE_S5L8950X_PKE, A6_PKE_MMIO_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_pke_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_pke_reset);
}
static const TypeInfo s5l8950x_pke_type_info = {
.name = TYPE_S5L8950X_PKE,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XPKEState),
.instance_init = s5l8950x_pke_init,
.class_init = s5l8950x_pke_class_init,
};
static void s5l8950x_pke_register_types(void)
{
type_register_static(&s5l8950x_pke_type_info);
}
type_init(s5l8950x_pke_register_types)
+8 -1
View File
@@ -17,6 +17,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XPmgrState, S5L8950X_PMGR)
#define PMGR_SPECIAL_DOMAIN_CTRL 0x100
#define PMGR_SPECIAL_DOMAIN_STATUS 0x2034
#define PMGR_DOMAIN_ACTIVE BIT(30)
#define PMGR_TRANSITION_DONE BIT(29)
#define PMGR_PLL0_CTRL 0x60
#define PMGR_PLL_LOCKED BIT(29)
@@ -57,7 +58,6 @@ static void s5l8950x_pmgr_write(void *opaque, hwaddr offset,
if (offset == PMGR_PLL0_CTRL) {
reg |= PMGR_PLL_LOCKED;
}
s->regs[idx] = reg;
/* Power-domain transitions also complete immediately. */
if (offset < PMGR_DOMAIN_CTRL_STRIDE * PMGR_DOMAIN_COUNT &&
@@ -65,14 +65,21 @@ static void s5l8950x_pmgr_write(void *opaque, hwaddr offset,
uint32_t domain = offset / PMGR_DOMAIN_CTRL_STRIDE;
uint32_t status = (PMGR_DOMAIN_STATUS_BASE / 4) + domain;
if (reg & BIT(31)) {
reg |= PMGR_TRANSITION_DONE;
}
s->regs[status] = deposit32(s->regs[status], 30, 1,
!!(reg & PMGR_DOMAIN_ACTIVE));
} else if (offset == PMGR_SPECIAL_DOMAIN_CTRL) {
uint32_t status = PMGR_SPECIAL_DOMAIN_STATUS / 4;
if (reg & BIT(31)) {
reg |= PMGR_TRANSITION_DONE;
}
s->regs[status] = deposit32(s->regs[status], 30, 1,
!!(reg & PMGR_DOMAIN_ACTIVE));
}
s->regs[idx] = reg;
} else {
qemu_log_mask(LOG_UNIMP, "%s: Out of bounds write to offset 0x%" HWADDR_PRIx
" with value 0x%" PRIx64 "\n", __func__, offset, val);
+223
View File
@@ -0,0 +1,223 @@
/*
* Apple A6 (S5L8950X) SHA-1 accelerator used by the SecureROM.
*
* The ROM supplies already padded 64-byte SHA-1 blocks through the input
* window. QEMU's hash API expects the original message, so this model keeps
* the submitted blocks, validates/removes the standard SHA-1 padding, and
* exposes the resulting five digest words through the hardware registers.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "crypto/hash.h"
#include "hw/core/cpu.h"
#include "hw/misc/s5l8950x-sha1.h"
#include "hw/core/sysbus.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "qemu/units.h"
#define A6_SHA1_MMIO_SIZE 0x100
#define A6_SHA1_RESET 0x008
#define A6_SHA1_COMMAND 0x00c
#define A6_SHA1_STATUS 0x010
#define A6_SHA1_DIGEST_BASE 0x040
#define A6_SHA1_DIGEST_SIZE QCRYPTO_HASH_DIGEST_LEN_SHA1
#define A6_SHA1_INPUT_BASE 0x080
#define A6_SHA1_BLOCK_SIZE 64
#define A6_SHA1_MAX_MESSAGE (16 * MiB)
struct S5L8950XSHA1State {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t reset;
uint32_t command;
uint32_t status;
uint8_t input[A6_SHA1_BLOCK_SIZE];
uint8_t digest[A6_SHA1_DIGEST_SIZE];
GByteArray *padded;
bool digest_valid;
};
static bool s5l8950x_sha1_finalize(S5L8950XSHA1State *s)
{
uint64_t bit_length;
uint64_t message_length;
uint8_t *digest = s->digest;
size_t digest_length = sizeof(s->digest);
if (!s->padded->len || s->padded->len % A6_SHA1_BLOCK_SIZE ||
s->padded->len < 9) {
return false;
}
bit_length = ldq_be_p(s->padded->data + s->padded->len - 8);
if (bit_length & 7) {
return false;
}
message_length = bit_length / 8;
if (message_length > A6_SHA1_MAX_MESSAGE ||
message_length >= s->padded->len - 8 ||
s->padded->data[message_length] != 0x80) {
return false;
}
for (size_t i = message_length + 1; i < s->padded->len - 8; i++) {
if (s->padded->data[i]) {
return false;
}
}
if (qcrypto_hash_bytes(QCRYPTO_HASH_ALGO_SHA1, s->padded->data,
message_length, &digest, &digest_length,
NULL) < 0 || digest_length != sizeof(s->digest)) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.sha1: SHA-1 calculation failed\n");
return false;
}
s->digest_valid = true;
qemu_log_mask(LOG_UNIMP,
"s5l8950x.sha1: PC=0x%" VADDR_PRIx
" hashed %" PRIu64 " guest bytes\n",
current_cpu ? current_cpu->cc->get_pc(current_cpu) : 0,
message_length);
return true;
}
static void s5l8950x_sha1_submit(S5L8950XSHA1State *s)
{
if (s->padded->len > A6_SHA1_MAX_MESSAGE + 2 * A6_SHA1_BLOCK_SIZE) {
qemu_log_mask(LOG_GUEST_ERROR,
"s5l8950x.sha1: guest message is too large\n");
return;
}
g_byte_array_append(s->padded, s->input, sizeof(s->input));
s->digest_valid = false;
}
static uint64_t s5l8950x_sha1_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XSHA1State *s = opaque;
if (offset >= A6_SHA1_INPUT_BASE &&
offset <= A6_SHA1_MMIO_SIZE - sizeof(uint32_t)) {
return ldl_le_p(s->input + offset - A6_SHA1_INPUT_BASE);
}
if (offset >= A6_SHA1_DIGEST_BASE &&
offset < A6_SHA1_DIGEST_BASE + A6_SHA1_DIGEST_SIZE) {
if (!s->digest_valid) {
s5l8950x_sha1_finalize(s);
}
return ldl_le_p(s->digest + offset - A6_SHA1_DIGEST_BASE);
}
switch (offset) {
case A6_SHA1_RESET:
return s->reset;
case A6_SHA1_COMMAND:
return s->command;
case A6_SHA1_STATUS:
return s->status;
default:
return 0;
}
}
static void s5l8950x_sha1_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XSHA1State *s = opaque;
if (offset >= A6_SHA1_INPUT_BASE &&
offset <= A6_SHA1_MMIO_SIZE - sizeof(uint32_t)) {
stl_le_p(s->input + offset - A6_SHA1_INPUT_BASE, value);
return;
}
switch (offset) {
case A6_SHA1_RESET:
s->reset = value;
if (value & 0x10) {
g_byte_array_set_size(s->padded, 0);
memset(s->input, 0, sizeof(s->input));
memset(s->digest, 0, sizeof(s->digest));
s->digest_valid = false;
}
break;
case A6_SHA1_COMMAND:
s->command = value;
if (value & 1) {
s5l8950x_sha1_submit(s);
}
/* The SecureROM waits for bit zero to clear after each block. */
s->command &= ~1u;
break;
case A6_SHA1_STATUS:
s->status = value;
break;
default:
break;
}
}
static const MemoryRegionOps s5l8950x_sha1_ops = {
.read = s5l8950x_sha1_read,
.write = s5l8950x_sha1_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid.min_access_size = 4,
.valid.max_access_size = 4,
};
static void s5l8950x_sha1_reset(DeviceState *dev)
{
S5L8950XSHA1State *s = S5L8950X_SHA1(dev);
s->reset = 0;
s->command = 0;
s->status = 0;
s->digest_valid = false;
memset(s->input, 0, sizeof(s->input));
memset(s->digest, 0, sizeof(s->digest));
if (s->padded) {
g_byte_array_set_size(s->padded, 0);
}
}
static void s5l8950x_sha1_init(Object *object)
{
S5L8950XSHA1State *s = S5L8950X_SHA1(object);
SysBusDevice *sbd = SYS_BUS_DEVICE(object);
s->padded = g_byte_array_new();
memory_region_init_io(&s->iomem, object, &s5l8950x_sha1_ops, s,
TYPE_S5L8950X_SHA1, A6_SHA1_MMIO_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_sha1_finalize_instance(Object *object)
{
S5L8950XSHA1State *s = S5L8950X_SHA1(object);
g_clear_pointer(&s->padded, g_byte_array_unref);
}
static void s5l8950x_sha1_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_sha1_reset);
}
static const TypeInfo s5l8950x_sha1_type_info = {
.name = TYPE_S5L8950X_SHA1,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XSHA1State),
.instance_init = s5l8950x_sha1_init,
.instance_finalize = s5l8950x_sha1_finalize_instance,
.class_init = s5l8950x_sha1_class_init,
};
static void s5l8950x_sha1_register_types(void)
{
type_register_static(&s5l8950x_sha1_type_info);
}
type_init(s5l8950x_sha1_register_types)
+1077 -72
View File
File diff suppressed because it is too large Load Diff