816848a474
- 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>
122 lines
4.7 KiB
C
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;
|
|
}
|