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.
258 lines
6.4 KiB
C
258 lines
6.4 KiB
C
#include "qemu/osdep.h"
|
|
#include "hw/core/sysbus.h"
|
|
#include "hw/core/irq.h"
|
|
#include "hw/core/qdev-properties-system.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/module.h"
|
|
#include "chardev/char-fe.h"
|
|
|
|
#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;
|
|
|
|
MemoryRegion iomem;
|
|
CharFrontend chr;
|
|
|
|
uint32_t ulcon;
|
|
uint32_t ucon;
|
|
uint32_t ufcon;
|
|
uint32_t umcon;
|
|
uint32_t ubrdiv;
|
|
uint32_t ufracval;
|
|
|
|
uint8_t rx_fifo[S5L8950X_UART_RX_FIFO_SIZE];
|
|
int rx_count;
|
|
qemu_irq irq;
|
|
};
|
|
|
|
#define UART_ULCON 0x00
|
|
#define UART_UCON 0x04
|
|
#define UART_UFCON 0x08
|
|
#define UART_UMCON 0x0C
|
|
#define UART_UTRSTAT 0x10
|
|
#define UART_UERSTAT 0x14
|
|
#define UART_UFSTAT 0x18
|
|
#define UART_UMSTAT 0x1C
|
|
#define UART_UTXH 0x20
|
|
#define UART_URXH 0x24
|
|
#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) {
|
|
qemu_irq_raise(s->irq);
|
|
} else {
|
|
qemu_irq_lower(s->irq);
|
|
}
|
|
}
|
|
|
|
static uint64_t s5l8950x_uart_read(void *opaque, hwaddr offset, unsigned size)
|
|
{
|
|
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
|
uint32_t val = 0;
|
|
|
|
switch (offset) {
|
|
case UART_ULCON:
|
|
val = s->ulcon;
|
|
break;
|
|
case UART_UCON:
|
|
val = s->ucon;
|
|
break;
|
|
case UART_UFCON:
|
|
val = s->ufcon;
|
|
break;
|
|
case UART_UMCON:
|
|
val = s->umcon;
|
|
break;
|
|
case UART_UTRSTAT:
|
|
/* Bit 0: rx ready, Bit 1: tx buf empty, Bit 2: tx empty */
|
|
val = 0x6 | (s->rx_count > 0 ? 1 : 0);
|
|
break;
|
|
case UART_UERSTAT:
|
|
val = 0;
|
|
break;
|
|
case UART_UFSTAT:
|
|
/* Return rx count in low bits, no tx fifo used here */
|
|
val = s->rx_count;
|
|
break;
|
|
case UART_UMSTAT:
|
|
val = 0;
|
|
break;
|
|
case UART_UTXH:
|
|
break;
|
|
case UART_URXH:
|
|
if (s->rx_count > 0) {
|
|
val = s->rx_fifo[0];
|
|
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:
|
|
val = s->ubrdiv;
|
|
break;
|
|
case UART_UFRACVAL:
|
|
val = s->ufracval;
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
static void s5l8950x_uart_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
|
|
{
|
|
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
|
unsigned char c;
|
|
|
|
switch (offset) {
|
|
case UART_ULCON:
|
|
s->ulcon = val;
|
|
break;
|
|
case UART_UCON:
|
|
s->ucon = val;
|
|
break;
|
|
case UART_UFCON:
|
|
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;
|
|
break;
|
|
case UART_UTRSTAT:
|
|
case UART_UERSTAT:
|
|
case UART_UFSTAT:
|
|
case UART_UMSTAT:
|
|
/* Read-only registers */
|
|
break;
|
|
case UART_UTXH:
|
|
c = val & 0xFF;
|
|
qemu_chr_fe_write_all(&s->chr, &c, 1);
|
|
break;
|
|
case UART_URXH:
|
|
break;
|
|
case UART_UBRDIV:
|
|
s->ubrdiv = val;
|
|
break;
|
|
case UART_UFRACVAL:
|
|
s->ufracval = val;
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps s5l8950x_uart_ops = {
|
|
.read = s5l8950x_uart_read,
|
|
.write = s5l8950x_uart_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 4,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 4,
|
|
},
|
|
};
|
|
|
|
static int s5l8950x_uart_can_receive(void *opaque)
|
|
{
|
|
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
|
|
|
return S5L8950X_UART_RX_FIFO_SIZE - s->rx_count;
|
|
}
|
|
|
|
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 < S5L8950X_UART_RX_FIFO_SIZE; i++) {
|
|
s->rx_fifo[s->rx_count++] = buf[i];
|
|
}
|
|
s5l8950x_uart_update_irq(s);
|
|
}
|
|
|
|
static void s5l8950x_uart_reset(DeviceState *dev)
|
|
{
|
|
S5L8950XUartState *s = S5L8950X_UART(dev);
|
|
|
|
s->ulcon = 0;
|
|
s->ucon = 0;
|
|
s->ufcon = 0;
|
|
s->umcon = 0;
|
|
s->ubrdiv = 0;
|
|
s->ufracval = 0;
|
|
s->rx_count = 0;
|
|
s5l8950x_uart_update_irq(s);
|
|
}
|
|
|
|
static void s5l8950x_uart_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
S5L8950XUartState *s = S5L8950X_UART(dev);
|
|
|
|
qemu_chr_fe_set_handlers(&s->chr, s5l8950x_uart_can_receive,
|
|
s5l8950x_uart_receive, NULL, NULL,
|
|
s, NULL, true);
|
|
}
|
|
|
|
static void s5l8950x_uart_init(Object *obj)
|
|
{
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
|
S5L8950XUartState *s = S5L8950X_UART(obj);
|
|
|
|
memory_region_init_io(&s->iomem, obj, &s5l8950x_uart_ops, s,
|
|
TYPE_S5L8950X_UART, 0x100);
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
}
|
|
|
|
static const Property s5l8950x_uart_properties[] = {
|
|
DEFINE_PROP_CHR("chardev", S5L8950XUartState, chr),
|
|
};
|
|
|
|
static void s5l8950x_uart_class_init(ObjectClass *klass, const void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
device_class_set_legacy_reset(dc, s5l8950x_uart_reset);
|
|
dc->realize = s5l8950x_uart_realize;
|
|
device_class_set_props(dc, s5l8950x_uart_properties);
|
|
}
|
|
|
|
static const TypeInfo s5l8950x_uart_info = {
|
|
.name = TYPE_S5L8950X_UART,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(S5L8950XUartState),
|
|
.instance_init = s5l8950x_uart_init,
|
|
.class_init = s5l8950x_uart_class_init,
|
|
};
|
|
|
|
static void s5l8950x_uart_register_types(void)
|
|
{
|
|
type_register_static(&s5l8950x_uart_info);
|
|
}
|
|
|
|
type_init(s5l8950x_uart_register_types)
|