Files
QEMU-S5L8950X/hw/gpio/s5l8950x-gpio.c
T
Yaya48 5d9a60a926 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.
2026-09-01 09:51:49 -07:00

151 lines
4.7 KiB
C

/*
* Apple S5L8950X GPIO controller (minimal SecureROM-facing model)
*
* The A6 SecureROM addresses pins as (bank << 8) | pin. Each pin maps to
* one 32-bit register at ((bank * 8) + pin) * 4. Bit 0 is the sampled input
* level; the remaining fields configure muxing and pull state.
*/
#include "qemu/osdep.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "qemu/log.h"
#include "qom/object.h"
#define S5L8950X_GPIO_REGION_SIZE 0x10000
#define S5L8950X_GPIO_NUM_REGS (S5L8950X_GPIO_REGION_SIZE / 4)
/* 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)
struct S5L8950XGPIOState {
SysBusDevice parent_obj;
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,
unsigned size)
{
S5L8950XGPIOState *s = opaque;
uint32_t value;
if (offset >= S5L8950X_GPIO_REGION_SIZE) {
return 0;
}
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;
}
static void s5l8950x_gpio_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XGPIOState *s = opaque;
if (offset < S5L8950X_GPIO_REGION_SIZE) {
s->regs[offset / sizeof(uint32_t)] = value;
} else {
qemu_log_mask(LOG_GUEST_ERROR,
"%s: invalid write at offset 0x%" HWADDR_PRIx "\n",
__func__, offset);
}
}
static const MemoryRegionOps s5l8950x_gpio_ops = {
.read = s5l8950x_gpio_read,
.write = s5l8950x_gpio_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
.impl = {
.min_access_size = 4,
.max_access_size = 4,
},
};
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)
{
S5L8950XGPIOState *s = S5L8950X_GPIO(dev);
memset(s->regs, 0, sizeof(s->regs));
}
static void s5l8950x_gpio_init(Object *obj)
{
S5L8950XGPIOState *s = S5L8950X_GPIO(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_gpio_ops, s,
TYPE_S5L8950X_GPIO, S5L8950X_GPIO_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_gpio_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_gpio_reset);
device_class_set_props(dc, s5l8950x_gpio_properties);
}
static const TypeInfo s5l8950x_gpio_info = {
.name = TYPE_S5L8950X_GPIO,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XGPIOState),
.instance_init = s5l8950x_gpio_init,
.class_init = s5l8950x_gpio_class_init,
};
static void s5l8950x_gpio_register_types(void)
{
type_register_static(&s5l8950x_gpio_info);
}
type_init(s5l8950x_gpio_register_types)