# RK3588 DDR Init Blob Analysis ## Overview - **Blob:** `rk3588_ddr_lp4_2112MHz_lp5_2400MHz_v1.19.bin` (76,704 bytes) - **Architecture:** AArch64 (64-bit ARM), not Cortex-M0 as initially assumed - **Functions:** 118 decompiled, 17,308 assembly instructions - **Execution context:** Runs on A76/A55 cores during early boot (BL31/TPL stage) ## Key Findings ### 1. Fast vs Conservative: Only 14 bytes differ The "fast" (2112/2400 MHz) and "conservative" (1848/2112 MHz) blobs are identical code with only **6 bytes of timing data** changed: | Offset | Fast (2112/2400) | Conservative (1848/2112) | Purpose | |--------|-----------------|-------------------------|---------| | 0x11b8c | 0x0840 | 0x0738 | LP4 frequency param (repeated at 0x11bc0) | | 0x11bf4 | 0x6960 | 0x6840 | LP5 frequency param | The remaining 8 byte differences are in the ASCII version string at 0x10d83. ### 2. MMIO Register Regions (79 unique registers accessed) | Region | Count | Hardware Block | |--------|-------|---------------| | 0xFD58xxxx | 1 | GRF (General Register Files) | | 0xFD59xxxx | 1 | DDR GRF | | 0xFD5Fxxxx | 27 | Bus GRF (main DDR config) | | 0xFD8Cxxxx | 4 | PMU/CRU (clock/power) | | 0xFE01xxxx | 4 | MSCH (Memory Scheduler) | | 0xFE03xxxx | 1 | Firewall DDR | | 0xFE05xxxx | 9 | SGRF (Security) | | 0xFECCxxxx | 4 | DDR Controller | | 0xFF00xxxx | 1 | SRAM/Boot ROM | ### 3. Potential Issues in Decompiled Code - **Missing data section:** Offsets 0x0001xxxx and 0x001fxxxx are relative to the blob load address, not absolute MMIO. Ghidra treats them as MMIO which is incorrect — they are data tables within the binary. - **Timing loops:** Several `do {} while` patterns poll hardware registers without timeout, which could hang if hardware doesn't respond. - **Security registers:** The blob manipulates SGRF (0xFE05xxxx) to grant DDR access — this is the firewall configuration for memory regions. ### 4. Recompilation Status Direct recompilation of Ghidra's C output is not possible because: - Ghidra uses synthetic types (`undefined8`, `undefined4`) - Data section references are treated as absolute addresses - Inline data tables (timing params) need to be separated - The original compiler (likely ARM's armclang) produces different code patterns Assembly-level comparison is the correct approach — the disassembly from Ghidra exactly matches the original blob's machine code. ## Files - `ddr_decompiled.c` — Decompiled C (fast blob, 118 functions) - `ddr_conservative_decompiled.c` — Decompiled C (conservative blob) - `ddr_diff.txt` — Diff between the two - `ddr_fast_asm.s` — Full disassembly (fast) - `ddr_conservative_asm.s` — Full disassembly (conservative) - `rk3588_ddr.h` — Register definitions header - `rk3588_regs_auto.h` — Auto-extracted MMIO register map ## Conclusion The DDR init blobs are essentially a **single codebase with parameterized timing tables**. To create a custom frequency configuration, only 6 bytes of timing data need to be modified. The code itself handles DDR PHY training, calibration, and memory controller initialization for all 4 channels of the RK3588. ## Detailed Register Analysis (TRM-verified) ### MMIO Regions Accessed by DDR Init Blob | Address Range | Block | Registers | Purpose | |--------------|-------|-----------|---------| | 0xFD588xxx | PMU1_GRF | 1 | DDR training status | | 0xFD598xxx | DDR_GRF_CH2 | 1 | Channel 2 config | | 0xFD5F4xxx | BUS_GRF | 2 | Bus fabric base config | | 0xFD5F8xxx | BUS_GRF | 25 | DDR bus interconnect, AXI routing, QoS | | 0xFD8C8xxx | SCRU | 4 | DDR PLL (DPLL) clock gate/reset/config | | 0xFE010xxx | DDRC_CH0 | 4 | UMCTL2 controller (offsets 0xF0-0xFC) | | 0xFE030xxx | FW_DDR | 1 | Firewall access control | | 0xFE050xxx | SGRF | 9 | Security - DDR region access permissions | | 0xFECC0xxx | Unknown | 4 | Possibly DDR scramble/ECC | | 0xFF000xxx | SRAM | 1 | Boot mailbox/flag | ### Potential Bugs / Concerns 1. **No timeout on hardware polls:** FUN_000000e4 polls `_DAT_fe0500e0` (SGRF status) in a tight loop with no timeout. If SGRF doesn't respond, the system hangs permanently during boot. 2. **Single-channel DDRC access:** Only CH0 registers (0xFE01xxxx) are accessed directly. Channels 1-3 are likely configured via the dense BUS_GRF register block (0xFD5F8xxx) which may broadcast to all channels. 3. **Firewall opened wide:** `_DAT_fe030040 |= 0xffff` in FUN_000000e4 opens all DDR firewall masters — this grants full DDR access to all bus masters during init, which is expected but never re-restricted. 4. **0x001FE000 region:** This 0x001FExxxx area (5 registers) is likely a shared memory mailbox used to communicate between the DDR blob and BL31/TF-A. Not actual MMIO — it's SRAM at a fixed offset. ### Binary Comparison: Fast vs Conservative Both blobs share identical code (118 functions, 17,308 instructions). Only the timing data table differs: ``` Offset 0x11B8C: LP4 freq parameter Fast: 0x0840 (2112 MHz) Conservative: 0x0738 (1848 MHz) Offset 0x11BF4: LP5 freq parameter Fast: 0x6960 (2400 MHz) Conservative: 0x6840 (2112 MHz) ``` These values appear in the data section at the end of the blob and are loaded by the frequency setup function (likely FUN_000009fc or nearby). ### Recompilation Assessment The decompiled C cannot be directly recompiled because: 1. Ghidra's `undefined*` types need mapping to stdint types 2. Internal data references (0x0001xxxx) are blob-relative, not absolute 3. The blob is position-dependent — loaded at a fixed address by BL2 4. String/data tables are interleaved with code in the original binary However, **assembly-level patching is straightforward** — both the fast and conservative blobs prove that changing 6 bytes of timing data is all that's needed for frequency customization. A tool could: 1. Parse the blob header 2. Locate the timing table (at known offset 0x11B8C) 3. Patch frequency values 4. Recalculate any checksums (if present — not confirmed) ### Files Added - `rk3588_ddr.h` — Complete RK3588 DDR memory map header (TRM-verified) - `rk3588_regs_annotated.h` — All 79 MMIO registers with block annotations - `ddr_fast_asm.s` / `ddr_conservative_asm.s` — Full disassembly listings - `ddr_diff.txt` — Diff between fast and conservative decompiled output ## Blob Version Comparison (All Revisions) ### Size & Complexity Evolution | Version | Size | Functions | BL calls | LP5 MHz | Change from prev | |---------|------|-----------|----------|---------|-----------------| | v1.02 | 42 KB | ~80 | 258 | 2736 | Earliest available | | v1.03 | 45 KB | ~85 | 294 | 2736 | +2.9 KB code | | v1.04 | 49 KB | ~90 | 326 | 2736 | +4.1 KB code | | v1.07 | 60 KB | ~95 | 425 | 2736 | +11 KB major rewrite | | v1.08 | 64 KB | ~98 | 468 | 2736 | +4 KB code | | v1.09 | 71 KB | 101 | 484 | 2736 | +6 KB code | | v1.10 | 70 KB | 101 | 484 | 2736 | -0.6 KB refactor | | v1.11 | 72 KB | 102 | 493 | 2736 | +1.6 KB | | v1.12 | 73 KB | 105 | 513 | 2736 | +1.2 KB | | v1.14 | 73 KB | 105 | 519 | 2736 | +0.2 KB | | v1.15 | 73 KB | 105 | 521 | 2736 | +0.2 KB (last 2736) | | v1.16 | 75 KB | 105 | 528 | **2400** | +2.2 KB + freq downgrade | | v1.17 | 73 KB | 104 | 516 | 2400 | -2 KB refactor | | v1.18 | 75 KB | 106 | 544 | 2400 | +1.9 KB | | v1.19 | 77 KB | 118 | 560 | 2400 | +1.4 KB (current) | ### Code Changes Between Key Versions **Every version contains real code changes, not just timing adjustments.** | Transition | Identical funcs | Changed | New | Assessment | |-----------|----------------|---------|-----|------------| | v1.09 → v1.15 | 40 | 61 | 65 | Major: PHY training, ODT updates | | v1.15 → v1.16 | 64 | 41 | 40 | Major: 2736→2400 + code changes | | v1.16 → v1.19 | 31 | 73 | 82 | Major: new functions, expanded training | ### Conclusion The DDR blobs are under **active development** — each version has substantial code changes, not just parameter tweaks. The blob grew from 42 KB (v1.02) to 77 KB (v1.19), nearly doubling in size and function count. The v1.15→v1.16 transition (2736→2400 MHz) was **not just a frequency change** — it included 40+ function modifications alongside the frequency downgrade, suggesting Rockchip discovered bugs or instability at 2736 MHz and rewrote parts of the training algorithm. **Implication for using old blobs:** Running v1.15 (2736 MHz) means missing all bug fixes from v1.16-v1.19. The safest approach for higher frequencies is to use the current v1.19 blob with **rkddr** to patch the frequency parameter, getting both the latest code and custom timing.