Files
lumia-emulator/qemu/saimaa.c
T

423 lines
16 KiB
C

/*
* Nokia Lumia 550 (Saimaa, MSM8909) emulation — WIP.
* Boots real SBL1 (img0.elf from Emergency Payload) on Cortex-A7.
* Most peripherals are trace-stubs; UART prints to stdio.
*/
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "qapi/error.h"
#include "hw/arm/boot.h"
#include "hw/boards.h"
#include "hw/loader.h"
#include "hw/sysbus.h"
#include "hw/sd/sdhci.h"
#include "hw/sd/sd.h"
#include "hw/char/serial.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "elf.h"
#include "qom/object.h"
#include "cpu.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
/* Real MSM8909 map (subset) */
#define SAIMAA_RPM_BASE 0x00400000
#define SAIMAA_RPM_SIZE 0x07C00000 /* 0x00400000..0x08000000 heap-ish */
#define SAIMAA_OCIMEM_BASE 0x00200000
#define SAIMAA_OCIMEM_SIZE 0x00100000
#define SAIMAA_DDRLOW_BASE 0x08000000
#define SAIMAA_DDRLOW_SIZE 0x00600000
#define SAIMAA_IMEM_BASE 0x08600000
#define SAIMAA_IMEM_SIZE 0x00010000
#define SAIMAA_DDRHIGH_BASE 0x80000000
#define SAIMAA_DDRHIGH_SIZE 0x20000000
#define SAIMAA_UART_BASE 0x078AF000
#define SAIMAA_SDHCI_BASE 0x07824900
#define SAIMAA_GICD_BASE 0x0B000000
#define SAIMAA_GICC_BASE 0x0B002000
#define SAIMAA_NCPUS 4
#define SAIMAA_MYSTROM_BASE 0x69000000
#define SAIMAA_MYSTROM_SIZE 0x00100000
#define SAIMAA_SMEM_BASE 0x87C00000
#define SAIMAA_SMEM_SIZE 0x00800000
/* ---- minimal MSM UARTDM stub: TX prints, status always ready ---- */
#define TYPE_SAIMAA_VEC "saimaa-vec"
#define TYPE_SAIMAA_UART "saimaa-uart"
OBJECT_DECLARE_SIMPLE_TYPE(SaimaaUartState, SAIMAA_UART)
struct SaimaaUartState {
SysBusDevice parent_obj;
MemoryRegion iomem;
/* WIP: plain host stdout, no chardev dance */
};
/* Low vectors / null-page trap: log faulting PC, return 0. */
static int saimaa_vec_n;
static uint64_t saimaa_vec_read(void *opaque, hwaddr off, unsigned size)
{
if (saimaa_vec_n < 24) {
ARMCPU *cpu = ARM_CPU(current_cpu);
uint64_t pc = cpu ? cpu->env.regs[15] : 0;
int th = cpu ? cpu->env.thumb : 0;
fprintf(stderr, "saimaa-vec: %s off=0x%x pc=0x%lx\n",
size == 2 ? "fetch?" : "read",
(unsigned)off, (unsigned long)pc - (th ? 4 : 8));
saimaa_vec_n++;
}
return 0;
}
static void saimaa_vec_write(void *opaque, hwaddr off, uint64_t val,
unsigned size)
{
}
static const MemoryRegionOps saimaa_vec_ops = {
.read = saimaa_vec_read,
.write = saimaa_vec_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static uint64_t saimaa_uart_read(void *opaque, hwaddr off, unsigned size)
{
switch (off) {
case 0x08: /* UARTDM_SR: pretend TX ready + empty */
return 0x000000A0;
case 0x70: /* UARTDM_TF: read returns 0 */
return 0;
default:
qemu_log_mask(LOG_GUEST_ERROR,
"saimaa-uart: unimp read @0x%x\n", (unsigned)off);
return 0;
}
}
static void saimaa_uart_write(void *opaque, hwaddr off, uint64_t val,
unsigned size)
{
SaimaaUartState *s = SAIMAA_UART(opaque);
(void)s;
if (off == 0x70) { /* UARTDM_TF */
putchar(val & 0xff);
fflush(stdout);
return;
}
qemu_log_mask(LOG_GUEST_ERROR,
"saimaa-uart: unimp write @0x%x = 0x%x\n",
(unsigned)off, (unsigned)val);
}
static const MemoryRegionOps saimaa_uart_ops = {
.read = saimaa_uart_read,
.write = saimaa_uart_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
static void saimaa_uart_init(Object *obj)
{
SaimaaUartState *s = SAIMAA_UART(obj);
SysBusDevice *dev = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &saimaa_uart_ops, s,
"saimaa-uart", 0x1000);
sysbus_init_mmio(dev, &s->iomem);
}
static void saimaa_uart_realize(DeviceState *dev, Error **errp)
{
/* backend wired directly in machine init; nothing to check */
}
/* ---- machine ---- */
struct SaimaaMachineState {
MachineState parent_obj;
char *sbl1;
char *emmc;
char *sblmain; /* hex: popped-PC override for PBL trampoline */
char *regs; /* PBL reg overrides: N=V,N=V (N=r1,r4,r6-r9,sl,fp) */
};
typedef struct SaimaaMachineState SaimaaMachineState;
#define TYPE_SAIMAA_MACHINE MACHINE_TYPE_NAME("saimaa")
OBJECT_DECLARE_SIMPLE_TYPE(SaimaaMachineState, SAIMAA_MACHINE)
static void saimaa_machine_init(MachineState *machine)
{
SaimaaMachineState *sms = SAIMAA_MACHINE(machine);
MemoryRegion *sysmem = get_system_memory();
int i;
/* RAM map */
const struct { const char *n; hwaddr b; hwaddr s; } rams[] = {
{ "saimaa.rpm", SAIMAA_RPM_BASE, SAIMAA_RPM_SIZE },
{ "saimaa.ocimem", SAIMAA_OCIMEM_BASE, SAIMAA_OCIMEM_SIZE },
{ "saimaa.ddrlow", SAIMAA_DDRLOW_BASE, SAIMAA_DDRLOW_SIZE },
{ "saimaa.imem", SAIMAA_IMEM_BASE, SAIMAA_IMEM_SIZE },
{ "saimaa.ddrhigh", SAIMAA_DDRHIGH_BASE, SAIMAA_DDRHIGH_SIZE },
{ "saimaa.mystrom", SAIMAA_MYSTROM_BASE, SAIMAA_MYSTROM_SIZE },
{ "saimaa.smem", SAIMAA_SMEM_BASE, SAIMAA_SMEM_SIZE },
};
for (i = 0; i < 7; i++) {
MemoryRegion *ram = g_new(MemoryRegion, 1);
memory_region_init_ram(ram, NULL, rams[i].n, rams[i].s,
&error_fatal);
memory_region_add_subregion(sysmem, rams[i].b, ram);
if (!strcmp(rams[i].n, "saimaa.mystrom")) {
/* Mystery handler ROM: ARM mov r0,#0; bx lr repeating,
* so unknown blx targets return 0 instead of faulting. */
uint32_t *p = memory_region_get_ram_ptr(ram);
size_t k, n = SAIMAA_MYSTROM_SIZE / 4;
for (k = 0; k < n; k++) {
p[k] = 0xe12fff1e; /* bx lr: any 4-aligned entry returns */
}
}
}
/* CPUs: 4x Cortex-A7, only cpu0 boots */
for (i = 0; i < SAIMAA_NCPUS; i++) {
Object *cpuobj = object_new("cortex-a7-" TYPE_ARM_CPU);
object_property_set_int(cpuobj, "mp-affinity", i, &error_fatal);
if (i > 0) {
object_property_set_bool(cpuobj, "start-powered-off", true,
&error_fatal);
}
object_property_set_bool(cpuobj, "realized", true, &error_fatal);
}
/* GICv2 (minimal: dist + cpuif, IRQs wired later) */
DeviceState *gic = qdev_new("arm_gic");
qdev_prop_set_uint32(gic, "num-cpu", SAIMAA_NCPUS);
qdev_prop_set_uint32(gic, "num-irq", 160);
sysbus_realize(SYS_BUS_DEVICE(gic), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(gic), 0, SAIMAA_GICD_BASE);
sysbus_mmio_map(SYS_BUS_DEVICE(gic), 1, SAIMAA_GICC_BASE);
/* UART at MSM address */
DeviceState *uart = qdev_new(TYPE_SAIMAA_UART);
sysbus_realize(SYS_BUS_DEVICE(uart), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(uart), 0, SAIMAA_UART_BASE);
/* Null-page trap at 0x0 (vectors live here; PBL normally maps ROM).
* Returns 0 for reads (data-0 invariant for SBL1 probing), logs the
* first faulting PCs for diagnosis. */
{
MemoryRegion *vec = g_new(MemoryRegion, 1);
memory_region_init_io(vec, NULL, &saimaa_vec_ops, NULL,
"saimaa-vec", 0x1000);
memory_region_add_subregion(sysmem, 0x0, vec);
}
/* SDHCI + eMMC image */
DeviceState *sdhci = qdev_new(TYPE_SYSBUS_SDHCI);
sysbus_realize(SYS_BUS_DEVICE(sdhci), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(sdhci), 0, SAIMAA_SDHCI_BASE);
DriveInfo *di = drive_get(IF_SD, 0, 0);
if (di) {
DeviceState *card = qdev_new(TYPE_SD_CARD);
qdev_prop_set_drive(card, "drive", blk_by_legacy_dinfo(di));
qdev_realize_and_unref(card, qdev_get_child_bus(sdhci, "sd-bus"),
&error_fatal);
}
/* Load real SBL1 ELF, jump to entry */
if (sms->sbl1) {
uint64_t entry = 0;
uint32_t pflags = 0;
ssize_t sz = load_elf(sms->sbl1, NULL, NULL, NULL, &entry, NULL,
NULL, &pflags, 0, EM_ARM, 1, 0);
if (sz < 0) {
error_report("saimaa: cannot load SBL1 %s: %s", sms->sbl1,
load_elf_strerror(sz));
exit(1);
}
if (sms->sblmain) {
/*
* Direct start at sbl_main: skip the PBL trampoline at
* entry (it BLs into zero padding and pop-zeroes r0-r12).
* Odd address = Thumb code -> set env.thumb (CPSR T).
*/
uint32_t tgt = strtoul(sms->sblmain, NULL, 0);
/* NOTE: pass bit0 through: arm_cpu_set_pc() sets
* regs[15]=tgt&~1 and env.thumb=tgt&1 itself. */
cpu_set_pc(first_cpu, tgt);
printf("saimaa: direct start -> 0x%x%s (entry 0x%lx skipped)\n",
tgt & ~1u, (tgt & 1) ? " thumb" : " arm",
(unsigned long)entry);
} else {
cpu_set_pc(first_cpu, entry);
}
/* PBL normally sets up SBL stack + r0=pbl_shared; fake it.
* All banked SPs: reset lands in SVC, regs[13] alone is USR. */
for (int b = 0; b < 8; b++) {
ARM_CPU(first_cpu)->env.banked_r13[b] = 0x085FFF00;
}
ARM_CPU(first_cpu)->env.regs[13] = 0x085FFF00;
ARM_CPU(first_cpu)->env.regs[0] = 0x00220000; /* fake pbl_shared */
/* sbl_main expects r5 = mem-table pointer (PBL trampoline does
* mov r5, r0 before jumping here); point it at pbl_shared too. */
ARM_CPU(first_cpu)->env.regs[5] = 0x00220000;
/* sl/fp = SBL1 table bases (PBL trampoline would pop these from its
* frame); point at free DDRLOW RAM so helper-built tables persist
* instead of vanishing into the null-page trap. */
ARM_CPU(first_cpu)->env.regs[10] = 0x08060000; /* sl */
ARM_CPU(first_cpu)->env.regs[11] = 0x08061000; /* fp */
if (sms->regs) {
char comma = 59;
char delims[2];
char *spec, *tok, *save = NULL;
delims[0] = comma;
delims[1] = 0;
spec = g_strdup(sms->regs);
for (tok = strtok_r(spec, delims, &save); tok;
tok = strtok_r(NULL, delims, &save)) {
char *eq = strchr(tok, 61);
if (eq) {
int n = strtoul(tok, NULL, 0);
*eq = 0;
eq++;
if (n >= 0 && n < 16) {
ARM_CPU(first_cpu)->env.regs[n] =
strtoul(eq, NULL, 0);
}
}
}
g_free(spec);
}
/* PBL enables VFP/NEON (CPACR CP10+CP11, FPEXC.EN) before SBL1. */
ARM_CPU(first_cpu)->env.cp15.cpacr_el1 = 0x00f00000;
ARM_CPU(first_cpu)->env.cp15.nsacr |= (3u << 10);
ARM_CPU(first_cpu)->env.vfp.xregs[ARM_VFP_FPEXC] |= (1u << 30);
/* Boot scaffolding for SBL1 BringUp (WIP stub-driven PBL):
* pop-compensation stublets at 0x08062000 pin sp and route onward,
* so repeated pops always hit valid slots. */
{
static const unsigned char stub_blob[] = { 0xf0, 0xb4, 0x2d, 0xe9, 0x00, 0x5f, 0xdf, 0xf8, 0x6c, 0xc0, 0xcd, 0xf8, 0x24, 0xc0, 0xdf, 0xf8, 0x68, 0xf0, 0x30, 0xb4, 0xdf, 0xf8, 0x64, 0xc0, 0xcd, 0xf8, 0x04, 0xc0, 0xdf, 0xf8, 0x60, 0xf0, 0xf0, 0xb4, 0x2d, 0xe9, 0x00, 0x5f, 0xdf, 0xf8, 0x5c, 0xc0, 0xcd, 0xf8, 0x24, 0xc0, 0xdf, 0xf8, 0x58, 0xf0, 0xf0, 0xb5, 0x01, 0xb4, 0xdf, 0xf8, 0x54, 0xc0, 0xcd, 0xf8, 0x14, 0xc0, 0xdf, 0xf8, 0x50, 0xf0, 0xf0, 0xb4, 0x2d, 0xe9, 0x00, 0x5f, 0xdf, 0xf8, 0x48, 0xc0, 0xcd, 0xf8, 0x24, 0xc0, 0xdf, 0xf8, 0x44, 0xf0, 0xf0, 0xb5, 0x01, 0xb4, 0xdf, 0xf8, 0x40, 0xc0, 0xcd, 0xf8, 0x14, 0xc0, 0xdf, 0xf8, 0x3c, 0xf0, 0xf0, 0xb5, 0x07, 0xb4, 0xdf, 0xf8, 0x38, 0xc0, 0xcd, 0xf8, 0x1c, 0xc0, 0xdf, 0xf8, 0x34, 0xf0, 0x01, 0x20, 0x06, 0x08, 0x5d, 0xf6, 0x02, 0x08, 0x13, 0x20, 0x06, 0x08, 0x65, 0xf6, 0x02, 0x08, 0x21, 0x20, 0x06, 0x08, 0xa5, 0x3b, 0x01, 0x08, 0x43, 0x20, 0x06, 0x08, 0x81, 0xf6, 0x02, 0x08, 0x33, 0x20, 0x06, 0x08, 0x83, 0x61, 0x01, 0x08, 0x55, 0x20, 0x06, 0x08, 0x8d, 0x8a, 0x01, 0x08, 0x65, 0x20, 0x06, 0x08, 0x7d, 0xf6, 0x02, 0x08 };
uint32_t magic = 0x00000000; /* IMEM poll spins WHILE equal */
uint32_t S = 0x08062100, STUB = 0x08062120;
uint32_t w;
uint16_t h0 = 0x2000, h1 = 0x4770; /* movs r0,#0; bx lr */
size_t k;
uint32_t v;
for (k = 0; k < sizeof(stub_blob); k += 4) {
uint32_t word;
__builtin_memcpy(&word, &stub_blob[k], 4);
cpu_physical_memory_write(0x08062000 + k, &word, 4);
}
/* pointer maze in owned RAM for SBL1 struct chains */
w = S + 8;
cpu_physical_memory_write(S, &w, 4);
cpu_physical_memory_write(S + 8, &STUB, 4);
cpu_physical_memory_write(STUB, &h0, 2);
cpu_physical_memory_write(STUB + 2, &h1, 2);
cpu_physical_memory_write(0x087c29d18, &S, 4);
cpu_physical_memory_write(0x08050b28, &S, 4);
cpu_physical_memory_write(0x08070000, &S, 4);
/* pop-site slots -> stublets (sp values from cpu traces) */
v = 0x08062001; /* S1 pop10 */
cpu_physical_memory_write(0x085FFF14, &v, 4);
v = 0x08062013; /* S2 pop2 */
cpu_physical_memory_write(0x085FFF1C, &v, 4);
v = 0x08062021; /* S3 pop10 */
cpu_physical_memory_write(0x085FFF44, &v, 4);
cpu_physical_memory_write(0x085FFF7C, &v, 4);
v = 0x08062033; /* S4 pop6 (stublet chains S5) */
cpu_physical_memory_write(0x085FFF34, &v, 4);
cpu_physical_memory_write(0x08600944, &magic, 4);
printf("saimaa: stub-scaffold live\n");
}
printf("saimaa: SBL1 %s loaded (%zd bytes), entry 0x%lx\n",
sms->sbl1, sz, (unsigned long)entry);
}
}
static void saimaa_uart_class_init(ObjectClass *oc, void *data)
{
(void)oc;
(void)data;
}
static char *saimaa_get_sbl1(Object *obj, Error **errp)
{
return g_strdup(SAIMAA_MACHINE(obj)->sbl1);
}
static void saimaa_set_sbl1(Object *obj, const char *value, Error **errp)
{
SaimaaMachineState *sms = SAIMAA_MACHINE(obj);
g_free(sms->sbl1);
sms->sbl1 = g_strdup(value);
}
static char *saimaa_get_sblmain(Object *obj, Error **errp)
{
return g_strdup(SAIMAA_MACHINE(obj)->sblmain);
}
static char *saimaa_get_regs(Object *obj, Error **errp)
{
return g_strdup(SAIMAA_MACHINE(obj)->regs);
}
static void saimaa_set_regs(Object *obj, const char *value, Error **errp)
{
SaimaaMachineState *sms = SAIMAA_MACHINE(obj);
g_free(sms->regs);
sms->regs = g_strdup(value);
}
static void saimaa_set_sblmain(Object *obj, const char *value, Error **errp)
{
SaimaaMachineState *sms = SAIMAA_MACHINE(obj);
g_free(sms->sblmain);
sms->sblmain = g_strdup(value);
}
static void saimaa_machine_instance_init(Object *obj)
{
SaimaaMachineState *sms = SAIMAA_MACHINE(obj);
sms->sbl1 = NULL;
sms->emmc = NULL;
sms->sblmain = NULL;
sms->regs = NULL;
object_property_add_str(obj, "sbl1", saimaa_get_sbl1, saimaa_set_sbl1);
object_property_add_str(obj, "regs", saimaa_get_regs,
saimaa_set_regs);
object_property_add_str(obj, "sblmain", saimaa_get_sblmain,
saimaa_set_sblmain);
}
static void saimaa_machine_class_init(ObjectClass *oc, void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
mc->desc = "Nokia Lumia 550 Saimaa (MSM8909) WIP";
mc->init = saimaa_machine_init;
mc->max_cpus = SAIMAA_NCPUS;
mc->default_cpus = SAIMAA_NCPUS;
mc->default_ram_size = 512 * MiB; /* only for -m accounting */
}
static const TypeInfo saimaa_machine_info = {
.name = TYPE_SAIMAA_MACHINE,
.parent = TYPE_MACHINE,
.instance_size = sizeof(SaimaaMachineState),
.instance_init = saimaa_machine_instance_init,
.class_init = saimaa_machine_class_init,
};
static const TypeInfo saimaa_uart_info = {
.name = TYPE_SAIMAA_UART,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(SaimaaUartState),
.instance_init = saimaa_uart_init,
.class_init = saimaa_uart_class_init,
};
static void saimaa_register_types(void)
{
type_register_static(&saimaa_machine_info);
type_register_static(&saimaa_uart_info);
}
type_init(saimaa_register_types)