e20563e2ef
Two extensions that finally get the emu producing useful output:
1. Catch UC_ERR_EXCEPTION on MSR/MRS access, decode the instruction,
stub the destination register to 0 (for MRS) or silently accept
(for MSR), advance PC, resume. Opaque sysregs the blob touches
(CNTFRQ_EL0 etc.) no longer halt the emu.
2. Map UART2 (0xFEB50000), hook writes to THR (offset 0), collect
printable bytes. Stub LSR (+0x14 = 0x60 THRE|TEMT) and USR (+0x7C
= 0x02 TFE) in ABS_STUB so the blob|s putc polling loops resolve.
Result: stock AND patched v3fb blob each emit the full 52-byte
cold-boot banner under stub=0x00 --
DDR ff1a08bde6 typ 25/04/21-14:31.26,fwver: v1.19
-- byte-identical to what comes out of the GenBook|s real UART.
Under stub=0xFF both progress further, also identically:
DDR ff1a08bde6 typ 25/04/21-14:31.26,fwver: v1.19
pd/pu vd_ddr
Patched matches stock in both stub regimes. That|s the regression
gate we wanted: a patcher change that breaks the DDR blob|s visible
behavior now shows up as banner-divergence before any hardware flash.
244 lines
10 KiB
Python
Executable File
244 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""DDR blob emulator — runs a raw (plaintext) RK3588 DDR init blob
|
|
in Unicorn, stubs MMIO reads to a constant byte. Intent: smoke-test a
|
|
patched blob to catch crashes/hangs before committing to a real flash.
|
|
|
|
Usage: blob_emu.py <blob.bin> [--stub 0x00|0xFF] [--max N] [--trace]
|
|
"""
|
|
import argparse, sys
|
|
from unicorn import *
|
|
from unicorn.arm64_const import *
|
|
|
|
# The blob is position-dependent: it expects to be loaded at 0xFF001000
|
|
# (the RK3588 bootrom's TPL SRAM slot). An integrity check at 0x14-0x1c
|
|
# ANDs the BL return address with 0xFFFFFF00 and compares to 0xFF001000.
|
|
SRAM_BASE = 0xFF000000
|
|
SRAM_SIZE = 0x00100000 # 1 MB SRAM window (covers 0xFF000000..0xFF100000)
|
|
BLOB_BASE = 0xFF001000 # where the blob actually lives
|
|
STACK_BASE = 0x00400000
|
|
STACK_SIZE = 0x00100000
|
|
RET_STUB = 0x00800000
|
|
RET_SIZE = 0x00001000
|
|
|
|
MMIO = [
|
|
(0xFD580000, 0x00020000, "GRF"),
|
|
(0xFD5F0000, 0x00010000, "BUS_GRF"),
|
|
(0xFD7C0000, 0x00040000, "CRU"),
|
|
(0xFD8C0000, 0x00010000, "SCRU"),
|
|
(0xFE010000, 0x00020000, "DDRC"),
|
|
(0xFE030000, 0x00010000, "FW_DDR"),
|
|
(0xFE050000, 0x00010000, "SGRF"),
|
|
(0xFE0C0000, 0x00040000, "DDRPHY"),
|
|
(0xFE400000, 0x00010000, "PMU"),
|
|
(0xFECC0000, 0x00010000, "SCRAMBLE"),
|
|
(0xFEB50000, 0x00010000, "UART2"), # debug UART — capture TX writes
|
|
# SRAM_BOOT (0xFF000000..0xFF100000) is regular RWX memory where
|
|
# the blob lives, NOT MMIO — not listed here.
|
|
]
|
|
|
|
# Per-address stub values — ported from ddr_emu2.c.
|
|
# Each entry: address → value to return on read.
|
|
# Use base + offset; the MMIO read hook checks exact match first,
|
|
# then falls back to region-specific patterns, then to the constant stub.
|
|
ABS_STUB = {
|
|
# SGRF (0xFE050000)
|
|
0xFE0500E0: 0x00000000, # status = ready
|
|
0xFE050054: 0x00000001, # CON21 = done
|
|
0xFE0500E4: 0x00000000, # enable
|
|
# UART2 (0xFEB50000) — DesignWare DW_apb_uart + 8250-compatible
|
|
0xFEB50014: 0x00000060, # LSR: THRE + TEMT (8250 legacy TX-ready)
|
|
0xFEB5007C: 0x00000002, # USR: TFE (Transmit FIFO Empty, DW-specific)
|
|
}
|
|
# Region-offset patterns: (region_base, region_end, offset_mask, offset_value, return_value)
|
|
REGION_OFF = [
|
|
# DDRPHY (0xFE0C0000..0xFE100000), offset masked to lower 12 bits
|
|
(0xFE0C0000, 0xFE100000, 0xFFF, 0xA24, 0x00000002), # DfiStatus ready
|
|
(0xFE0C0000, 0xFE100000, 0xFFF, 0x684, 0x00000000), # CalBusy idle
|
|
(0xFE0C0000, 0xFE100000, 0xFFF, 0x090, 0x00000000), # MicroContMux
|
|
(0xFE0C0000, 0xFE100000, 0xFFF, 0x080, 0x00000000), # MicroReset done
|
|
(0xFE0C0000, 0xFE100000, 0xFFF, 0x514, 0x00000000), # training done
|
|
]
|
|
# Region constants: entire region returns this by default
|
|
REGION_CONST = [
|
|
(0xFD8C0000, 0xFD8D0000, 0x00000001), # SCRU — PLL locked everywhere
|
|
]
|
|
|
|
def run(blob_path, stub_byte, max_insn, trace, entry=0):
|
|
blob = open(blob_path, "rb").read()
|
|
print(f"blob: {blob_path} size=0x{len(blob):x} stub=0x{stub_byte:02x}")
|
|
|
|
uc = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
|
uc.mem_map(SRAM_BASE, SRAM_SIZE, UC_PROT_ALL) # SRAM window
|
|
uc.mem_write(BLOB_BASE, blob) # blob at 0xFF001000
|
|
uc.mem_map(STACK_BASE, STACK_SIZE, UC_PROT_ALL)
|
|
uc.mem_map(RET_STUB, RET_SIZE, UC_PROT_ALL)
|
|
uc.mem_write(RET_STUB, b"\x00\x00\x20\xd4") # brk #0
|
|
|
|
for base, sz, _ in MMIO:
|
|
uc.mem_map(base, sz, UC_PROT_ALL)
|
|
|
|
state = {"count": 0, "last_pc": 0, "same_pc": 0, "max_pc": 0}
|
|
stub_word = int.from_bytes(bytes([stub_byte]) * 8, "little")
|
|
|
|
def hook_code(uc, addr, size, ud):
|
|
state["count"] += 1
|
|
if addr == state["last_pc"]:
|
|
state["same_pc"] += 1
|
|
if state["same_pc"] > 5000:
|
|
print(f"HANG at PC=0x{addr:x} after {state['count']} insns")
|
|
uc.emu_stop()
|
|
else:
|
|
state["same_pc"] = 0
|
|
state["last_pc"] = addr
|
|
if addr > state["max_pc"]:
|
|
state["max_pc"] = addr
|
|
if state["count"] >= max_insn:
|
|
print(f"LIMIT at PC=0x{addr:x} (max_pc=0x{state['max_pc']:x}, {state['count']} insns)")
|
|
uc.emu_stop()
|
|
if trace and state["count"] % 10000 == 0:
|
|
print(f"[{state['count']}] PC=0x{addr:x}")
|
|
|
|
def stub_value(addr):
|
|
# Exact-address overrides first
|
|
if addr in ABS_STUB:
|
|
return ABS_STUB[addr]
|
|
# Region + offset patterns
|
|
for rbase, rend, mask, off_val, ret_val in REGION_OFF:
|
|
if rbase <= addr < rend and (addr & mask) == off_val:
|
|
return ret_val
|
|
# Whole-region constants
|
|
for rbase, rend, ret_val in REGION_CONST:
|
|
if rbase <= addr < rend:
|
|
return ret_val
|
|
# Fall through to constant-byte stub
|
|
return stub_word
|
|
|
|
mmio_reads = []
|
|
def hook_mmio_read(uc, typ, addr, size, val, ud):
|
|
v = stub_value(addr) & ((1 << (size * 8)) - 1)
|
|
uc.mem_write(addr, v.to_bytes(size, "little"))
|
|
if len(mmio_reads) < 50:
|
|
mmio_reads.append((uc.reg_read(UC_ARM64_REG_PC), addr, v))
|
|
|
|
uc.hook_add(UC_HOOK_CODE, hook_code, begin=SRAM_BASE, end=SRAM_BASE + SRAM_SIZE)
|
|
for base, sz, _ in MMIO:
|
|
uc.hook_add(UC_HOOK_MEM_READ, hook_mmio_read, begin=base, end=base + sz)
|
|
|
|
# UART2 TX capture: writes to 0xFEB50000 (THR/DR register) are characters
|
|
uart_buf = bytearray()
|
|
def hook_uart_write(uc, typ, addr, size, val, ud):
|
|
if addr == 0xFEB50000:
|
|
c = val & 0xFF
|
|
uart_buf.append(c)
|
|
# Echo printable characters immediately so we see live trace
|
|
if 0x20 <= c < 0x7F or c in (0x09, 0x0A, 0x0D):
|
|
sys.stdout.write(chr(c))
|
|
sys.stdout.flush()
|
|
uc.hook_add(UC_HOOK_MEM_WRITE, hook_uart_write, begin=0xFEB50000, end=0xFEB50000 + 0x10000)
|
|
|
|
# (UART UART2 status registers are in ABS_STUB so they get served
|
|
# by the single hook_mmio_read path — avoids hook-ordering races.)
|
|
|
|
# Catch any unmapped read/write and log what the blob wanted
|
|
unmapped = []
|
|
def hook_unmapped(uc, typ, addr, size, val, ud):
|
|
pc = uc.reg_read(UC_ARM64_REG_PC)
|
|
unmapped.append((pc, typ, addr, size, val))
|
|
# Lazily map a 64KB page containing the address and stub it
|
|
page = addr & ~0xFFFF
|
|
try:
|
|
uc.mem_map(page, 0x10000, UC_PROT_ALL)
|
|
except UcError:
|
|
pass
|
|
v = stub_value(addr) & ((1 << (size * 8)) - 1)
|
|
uc.mem_write(addr, v.to_bytes(size, "little"))
|
|
return True # tell Unicorn to continue
|
|
uc.hook_add(UC_HOOK_MEM_UNMAPPED, hook_unmapped)
|
|
|
|
uc.reg_write(UC_ARM64_REG_SP, STACK_BASE + STACK_SIZE - 16)
|
|
uc.reg_write(UC_ARM64_REG_X30, RET_STUB)
|
|
|
|
# Register-index → Unicorn constant table (X0..X30)
|
|
XREG = [getattr(__import__("unicorn.arm64_const", fromlist=["X"]),
|
|
f"UC_ARM64_REG_X{i}") for i in range(31)]
|
|
|
|
sysreg_log = []
|
|
def is_mrs(insn): # 1101_0101_0011 … MRS Xt, sysreg
|
|
return (insn >> 20) == 0xD53
|
|
def is_msr_reg(insn): # 1101_0101_0001 … MSR sysreg, Xt
|
|
return (insn >> 20) == 0xD51
|
|
def is_msr_imm(insn): # 1101_0101_0000 … MSR pstate / HINT-like
|
|
return (insn >> 20) == 0xD50
|
|
def decode_sysreg(insn):
|
|
# bits 20..5 are op0:op1:CRn:CRm:op2
|
|
op0 = ((insn >> 19) & 0x3) | 2 # MRS/MSR implies op0 in {2,3}
|
|
op1 = (insn >> 16) & 0x7
|
|
crn = (insn >> 12) & 0xF
|
|
crm = (insn >> 8) & 0xF
|
|
op2 = (insn >> 5) & 0x7
|
|
return (op0, op1, crn, crm, op2)
|
|
|
|
pc = BLOB_BASE + entry
|
|
remaining = max_insn
|
|
while remaining > 0:
|
|
try:
|
|
uc.emu_start(pc, RET_STUB, count=remaining)
|
|
break # normal return
|
|
except UcError as e:
|
|
pc = uc.reg_read(UC_ARM64_REG_PC)
|
|
try:
|
|
insn = int.from_bytes(uc.mem_read(pc, 4), "little")
|
|
except UcError:
|
|
print(f"EXC at PC=0x{pc:x}: {e} (couldn't fetch insn) (max_pc=0x{state['max_pc']:x}, {state['count']} insns)")
|
|
return 1
|
|
if is_mrs(insn):
|
|
rt = insn & 0x1F
|
|
sr = decode_sysreg(insn)
|
|
sysreg_log.append(("MRS", pc, sr, rt))
|
|
if rt < 31:
|
|
uc.reg_write(XREG[rt], 0) # stub = 0
|
|
pc += 4
|
|
uc.reg_write(UC_ARM64_REG_PC, pc)
|
|
remaining -= 1
|
|
continue
|
|
if is_msr_reg(insn) or is_msr_imm(insn):
|
|
sr = decode_sysreg(insn) if is_msr_reg(insn) else None
|
|
sysreg_log.append(("MSR", pc, sr, insn & 0x1F))
|
|
pc += 4
|
|
uc.reg_write(UC_ARM64_REG_PC, pc)
|
|
remaining -= 1
|
|
continue
|
|
print(f"EXC at PC=0x{pc:x}: {e} (insn=0x{insn:08x}) (max_pc=0x{state['max_pc']:x}, {state['count']} insns)")
|
|
return 1
|
|
|
|
print(f"HALT insns={state['count']} max_pc=0x{state['max_pc']:x}")
|
|
print(f"--- first MMIO reads ({len(mmio_reads)}) ---")
|
|
for pc, addr, v in mmio_reads[:20]:
|
|
print(f" PC=0x{pc:x} RD 0x{addr:x} -> 0x{v:x}")
|
|
if unmapped:
|
|
print(f"--- unmapped accesses ({len(unmapped)}) ---")
|
|
for pc, typ, addr, size, val in unmapped[:20]:
|
|
print(f" PC=0x{pc:x} type={typ} 0x{addr:x} size={size}")
|
|
if sysreg_log:
|
|
print(f"--- sysreg accesses stubbed ({len(sysreg_log)}) ---")
|
|
for kind, pc, sr, rt in sysreg_log[:20]:
|
|
srstr = f"S{sr[0]}_{sr[1]}_C{sr[2]}_C{sr[3]}_{sr[4]}" if sr else "?"
|
|
print(f" PC=0x{pc:x} {kind} {srstr} Rt=x{rt}")
|
|
if uart_buf:
|
|
print(f"\n--- UART capture ({len(uart_buf)} bytes) ---")
|
|
try:
|
|
print(uart_buf.decode("utf-8", errors="replace"))
|
|
except Exception:
|
|
print(uart_buf.hex())
|
|
return 0
|
|
|
|
if __name__ == "__main__":
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("blob")
|
|
ap.add_argument("--stub", default="0x00")
|
|
ap.add_argument("--max", type=int, default=200_000)
|
|
ap.add_argument("--trace", action="store_true")
|
|
ap.add_argument("--entry", default="0x0", help="start PC (0x40 skips version check)")
|
|
a = ap.parse_args()
|
|
sys.exit(run(a.blob, int(a.stub, 0), a.max, a.trace, int(a.entry, 0)))
|