Files
rk3588-ddr-analysis/ddr_emu2.c
T
test0r 816848a474 RK3588 DDR init blob reverse engineering
- Ghidra decompilation of v1.02-v1.19 blobs (118 functions)
- 53 functions renamed, 79 MMIO registers mapped to TRM
- 45 timeout-less poll loops identified and patched
- Production patcher (patch_prod.py) and QEMU emulator
- Comprehensive analysis, frequency tables, community research

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:06:47 +02:00

122 lines
4.7 KiB
C

/* RK3588 DDR blob emulator v2 - with proper entry stub */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unicorn/unicorn.h>
#define BLOB_BASE 0x00000000
#define BLOB_SIZE 0x20000
#define STACK_BASE 0x00100000
#define STACK_SIZE 0x10000
#define SRAM_BASE 0x001F0000
#define SRAM_SIZE 0x10000
typedef struct { uint64_t base; uint64_t size; const char *name; } mmio_t;
static mmio_t mmio[] = {
{0xFD580000, 0x20000, "GRF"}, {0xFD5F0000, 0x10000, "BUS_GRF"},
{0xFD8C0000, 0x10000, "SCRU"}, {0xFE010000, 0x20000, "DDRC"},
{0xFE030000, 0x10000, "FW_DDR"}, {0xFE050000, 0x10000, "SGRF"},
{0xFE0C0000, 0x40000, "DDRPHY"}, {0xFECC0000, 0x10000, "SCRAMBLE"},
{0xFF000000, 0x100000, "SRAM_BOOT"}, {0, 0, NULL}
};
static int instr_count = 0, max_instr = 50000, mmio_count = 0;
static int verbose = 0;
static void mmio_read(uc_engine *uc, uc_mem_type type,
uint64_t addr, int size, int64_t val, void *ud) {
uint32_t ret = 0;
uint32_t off = addr & 0xFFFF;
/* SGRF: return ready */
if (addr >= 0xFE050000 && addr < 0xFE060000) {
if (off == 0x00E0) ret = 0; /* status = ready */
if (off == 0x0054) ret = 1; /* CON21 = done */
if (off == 0x00E4) ret = 0; /* enable */
}
/* DDRPHY: return ready/not-busy */
else if (addr >= 0xFE0C0000 && addr < 0xFE100000) {
if ((off & 0xFFF) == 0xA24) ret = 0x02; /* DfiStatus = ready */
if ((off & 0xFFF) == 0x684) ret = 0; /* CalBusy = idle */
if ((off & 0xFFF) == 0x090) ret = 0; /* MicroContMux */
if ((off & 0xFFF) == 0x080) ret = 0; /* MicroReset done */
if ((off & 0xFFF) == 0x514) ret = 0; /* training done */
}
/* SCRU: PLL locked */
else if (addr >= 0xFD8C0000 && addr < 0xFD8D0000) {
ret = 0x01; /* PLL locked */
}
uc_mem_write(uc, addr, &ret, 4);
mmio_count++;
if (verbose || mmio_count <= 100)
printf(" MMIO RD 0x%lx = 0x%x\n", addr, ret);
}
static void hook_code(uc_engine *uc, uint64_t addr, uint32_t size, void *ud) {
instr_count++;
if (instr_count >= max_instr) {
printf("LIMIT at PC=0x%lx (%d instrs, %d MMIO)\n",
addr, instr_count, mmio_count);
uc_emu_stop(uc);
}
if (instr_count <= 20 || instr_count % 5000 == 0)
printf("[%d] PC=0x%lx\n", instr_count, addr);
}
int main(int argc, char **argv) {
if (argc < 2) { printf("Usage: %s <blob.bin> [max_instr] [verbose]\n", argv[0]); return 1; }
if (argc > 2) max_instr = atoi(argv[2]);
if (argc > 3) verbose = 1;
FILE *f = fopen(argv[1], "rb");
fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET);
uint8_t *blob = malloc(sz); fread(blob, 1, sz, f); fclose(f);
printf("Loaded %ld bytes\n", sz);
uc_engine *uc; uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc);
uc_mem_map(uc, BLOB_BASE, BLOB_SIZE, UC_PROT_ALL);
uc_mem_write(uc, BLOB_BASE, blob, sz);
uc_mem_map(uc, STACK_BASE, STACK_SIZE, UC_PROT_ALL);
uint64_t sp = STACK_BASE + STACK_SIZE - 16;
uc_reg_write(uc, UC_ARM64_REG_SP, &sp);
uc_mem_map(uc, SRAM_BASE, SRAM_SIZE, UC_PROT_ALL);
for (int i = 0; mmio[i].name; i++) {
uc_mem_map(uc, mmio[i].base, mmio[i].size, UC_PROT_ALL);
uc_hook hh;
uc_hook_add(uc, &hh, UC_HOOK_MEM_READ, mmio_read, NULL,
mmio[i].base, mmio[i].base + mmio[i].size);
}
uc_hook hh;
uc_hook_add(uc, &hh, UC_HOOK_CODE, hook_code, NULL, BLOB_BASE, BLOB_BASE + BLOB_SIZE);
/* Skip the entry version check loop - start at the real init (0x40)
The entry at 0x0 is a version check gate that requires a specific
return value from the PMU status check. On real hardware this is
set by BL2. We skip it and go directly to the DDR init code. */
uint64_t start_pc = 0x00000040; /* FUN_00000040 = first real init function */
/* But FUN_40 takes parameters. The main orchestrator is at the thunk
target. Let's find where Reset would jump after the version check. */
/* Reset flow: 0x0 -> check version -> thunk_FUN_00010978
FUN_00010978 is the main orchestrator at offset 0x10978 */
start_pc = 0x00010978;
printf("Starting at PC=0x%lx (skipping entry version check)\n\n", start_pc);
uc_err err = uc_emu_start(uc, start_pc, BLOB_BASE + sz, 0, max_instr);
uint64_t pc, x0;
uc_reg_read(uc, UC_ARM64_REG_PC, &pc);
uc_reg_read(uc, UC_ARM64_REG_X0, &x0);
printf("\nStopped: %s PC=0x%lx X0=0x%lx (%d instrs, %d MMIO)\n",
uc_strerror(err), pc, x0, instr_count, mmio_count);
uc_close(uc); free(blob);
return 0;
}