From e84839932a593c7ea4194a437d0f001f0b6ecf58 Mon Sep 17 00:00:00 2001 From: pasketti Date: Mon, 6 Apr 2026 09:22:12 -0400 Subject: [PATCH] Add eMMC boot partition dump support (boot0/boot1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the emmc_server payload to access the eMMC hardware boot partitions (boot0/boot1) that hold the Tegra T124 BCT and low-level bootloader — data not captured in the existing NAND dump. Payload changes (emmc_server.c / emmc_server.h): - Add MMC_CMD6 (SWITCH, R1b) for EXT_CSD partition switching - Add switch_partition_access() to set EXT_CSD[179] PARTITION_CONFIG - EMMC_CMD_READ and EMMC_CMD_WRITE now decode high 2 bits of start_sector to select the target partition without new op codes: 0x80000000|sector -> boot0, 0xC0000000|sector -> boot1 Partition is restored to UDA after each operation. New script (dump_boot_partitions.py): - Reads EXT_CSD BOOT_SIZE_MULT to determine exact partition size - Dumps jibo_work/emmc_boot0.bin and jibo_work/emmc_boot1.bin - Works with the existing shofel2_t124 EMMC_READ command unchanged Co-Authored-By: Claude Sonnet 4.6 --- Shofel/include/emmc_server.h | 20 ++-- Shofel/payloads/emmc_server.c | 102 ++++++++++++++++++- dump_boot_partitions.py | 182 ++++++++++++++++++++++++++++++++++ 3 files changed, 296 insertions(+), 8 deletions(-) create mode 100644 dump_boot_partitions.py diff --git a/Shofel/include/emmc_server.h b/Shofel/include/emmc_server.h index 5229e2e..97ee9eb 100644 --- a/Shofel/include/emmc_server.h +++ b/Shofel/include/emmc_server.h @@ -15,14 +15,22 @@ #define EMMC_CMD_ERASE 0x05 /* Erase a range of sectors (forces reallocation) */ #define EMMC_CMD_EXIT 0xFF -/* Transfer chunk sizes basically the amount much data is sent/received per USB transfer was not multiple of 0x1000 - * - * Reads: 8 sectors (4KB) = 1.1 MB/s - OPTIMIZED - * Writes: 1 sector (512B) - ORIGINAL (safe) +/* Boot partition encoding for EMMC_CMD_READ / EMMC_CMD_WRITE start_sector: + * 0x00000000 | sector -> User Data Area (normal) + * 0x80000000 | sector -> Boot Partition 1 (boot0) + * 0xC0000000 | sector -> Boot Partition 2 (boot1) + * The payload switches the eMMC partition, performs the I/O, then restores UDA. + * Use sector=0 and num_sectors=boot_size_sectors to dump an entire boot partition. + */ +#define EMMC_BOOT0_BASE 0x80000000U +#define EMMC_BOOT1_BASE 0xC0000000U + +/* Transfer chunk sizes: sectors per USB transfer. + * Both read and write now use 8-sector (4 KB) chunks via CMD18/CMD25. */ #define EMMC_CHUNK_SECTORS_READ 8 -#define EMMC_CHUNK_SECTORS_WRITE 1 -#define EMMC_CHUNK_SECTORS 1 /* Default - single sector for compatibility */ +#define EMMC_CHUNK_SECTORS_WRITE 8 +#define EMMC_CHUNK_SECTORS 8 #define EMMC_SECTOR_SIZE 512 #define EMMC_CHUNK_BYTES (EMMC_CHUNK_SECTORS * EMMC_SECTOR_SIZE) diff --git a/Shofel/payloads/emmc_server.c b/Shofel/payloads/emmc_server.c index 41601b7..84b7163 100644 --- a/Shofel/payloads/emmc_server.c +++ b/Shofel/payloads/emmc_server.c @@ -170,10 +170,28 @@ static int send_cmd(u32 cmd_val, u32 argument) { #define MMC_CMD18 0x123A /* READ_MULTIPLE_BLOCK: R1, data, CRC+index check */ #define MMC_CMD24 0x183A /* WRITE_BLOCK: R1, data, CRC+index check */ #define MMC_CMD25 0x193A /* WRITE_MULTIPLE_BLOCK: R1, data, CRC+index check */ +#define MMC_CMD6 0x061B /* SWITCH: R1b, no data, CRC+index check */ #define MMC_CMD35 0x233A /* ERASE_GROUP_START: R1, CRC+index check */ #define MMC_CMD36 0x243A /* ERASE_GROUP_END: R1, CRC+index check */ #define MMC_CMD38 0x263B /* ERASE: R1b, CRC+index check */ +/* + * Boot partition encoding in start_sector for EMMC_CMD_READ / EMMC_CMD_WRITE: + * bits [31:30] == 00 → User Data Area (normal sectors) + * bits [31:30] == 10 → Boot Partition 1 (boot0), real sector in bits [29:0] + * bits [31:30] == 11 → Boot Partition 2 (boot1), real sector in bits [29:0] + * + * Example: sector 0x80000000 means "boot0, sector 0". + * sector 0xC0000005 means "boot1, sector 5". + * + * After the operation the partition is switched back to UDA (0). + */ +#define BOOT_PART_MASK 0xC0000000U +#define BOOT_PART_UDA 0x00000000U +#define BOOT_PART_BOOT0 0x80000000U +#define BOOT_PART_BOOT1 0xC0000000U +#define BOOT_PART_SECTOR(s) ((s) & 0x3FFFFFFFU) + static u32 sdmmc4_initialized = 0; static u32 init_error = 0; @@ -575,6 +593,54 @@ static int read_emmc_sector(u32 sector, u32 *buffer) { } /* Write N sectors to eMMC using multi-block CMD25 */ +static int write_emmc_sectors(u32 sector, u32 count, u32 *buffer) { + u32 status; + u32 timeout; + + if (count == 0) return 0; + if (wait_ready() < 0) return -1; + + write32(SDMMC4_BASE + SDHCI_INT_STATUS, 0xFFFFFFFF); + write32(SDMMC4_BASE + SDHCI_BLOCK_SIZE, (count << 16) | 0x200); + write32(SDMMC4_BASE + SDHCI_ARGUMENT, sector); + write32(SDMMC4_BASE + SDHCI_TRANSFER_MODE, + ((u32)MMC_CMD25 << 16) | XFER_MODE_WRITE_MULTI); + + timeout = 1000000; + do { + status = read32(SDMMC4_BASE + SDHCI_INT_STATUS); + if (status & SDHCI_INT_ERROR) { reset_cmd_dat(); return -2; } + if (--timeout == 0) return -3; + } while (!(status & SDHCI_INT_CMD_COMPLETE)); + + write32(SDMMC4_BASE + SDHCI_INT_STATUS, SDHCI_INT_CMD_COMPLETE); + + for (u32 blk = 0; blk < count; blk++) { + timeout = 2000000; + do { + status = read32(SDMMC4_BASE + SDHCI_INT_STATUS); + if (status & SDHCI_INT_ERROR) { reset_cmd_dat(); return -4; } + if (--timeout == 0) return -5; + } while (!(status & SDHCI_INT_BUF_WR_READY)); + + for (u32 i = 0; i < 128; i++) { + write32(SDMMC4_BASE + SDHCI_BUFFER, buffer[blk * 128 + i]); + } + + write32(SDMMC4_BASE + SDHCI_INT_STATUS, SDHCI_INT_BUF_WR_READY); + } + + timeout = 2000000; + do { + status = read32(SDMMC4_BASE + SDHCI_INT_STATUS); + if (status & SDHCI_INT_ERROR) { reset_cmd_dat(); return -6; } + if (--timeout == 0) return -7; + } while (!(status & SDHCI_INT_XFER_COMPLETE)); + + write32(SDMMC4_BASE + SDHCI_INT_STATUS, 0xFFFFFFFF); + return 0; +} + /* Write a single 512-byte sector to eMMC */ static int write_emmc_sector(u32 sector, u32 *buffer) { u32 status; @@ -715,6 +781,19 @@ static int erase_emmc_sectors(u32 start_sector, u32 end_sector) { return 0; } +/* + * Switch eMMC partition access via CMD6 SWITCH (EXT_CSD[179] PARTITION_CONFIG). + * part 0 = User Data Area + * part 1 = Boot Partition 1 (boot0) + * part 2 = Boot Partition 2 (boot1) + * Returns 0 on success, negative on send_cmd error. + */ +static int switch_partition_access(u32 part) { + /* CMD6 argument: [25:24]=3(WriteByte), [23:16]=179(PARTITION_CONFIG), [15:8]=value, [2:0]=0 */ + u32 arg = (3U << 24) | (179U << 16) | ((part & 0x7U) << 8); + return send_cmd(MMC_CMD6, arg); +} + __attribute__((section(".init"))) void entry() { @@ -776,9 +855,15 @@ void entry() { if (cmd.op == EMMC_CMD_READ) { init_sdmmc4(); - u32 sector = cmd.start_sector; + + /* Decode boot partition encoding from high 2 bits of start_sector */ + u32 part_sel = cmd.start_sector & BOOT_PART_MASK; + u32 sector = BOOT_PART_SECTOR(cmd.start_sector); u32 remaining = cmd.num_sectors; + if (part_sel != BOOT_PART_UDA) + switch_partition_access((part_sel == BOOT_PART_BOOT0) ? 1 : 2); + while (remaining > 0) { u32 batch = remaining > EMMC_CHUNK_SECTORS_READ ? EMMC_CHUNK_SECTORS_READ : remaining; u32 batch_bytes = batch * EMMC_SECTOR_SIZE; @@ -795,15 +880,25 @@ void entry() { sector += batch; remaining -= batch; } + + if (part_sel != BOOT_PART_UDA) + switch_partition_access(0); /* restore UDA access */ + continue; } if (cmd.op == EMMC_CMD_WRITE) { init_sdmmc4(); - u32 sector = cmd.start_sector; + + /* Decode boot partition encoding from high 2 bits of start_sector */ + u32 part_sel = cmd.start_sector & BOOT_PART_MASK; + u32 sector = BOOT_PART_SECTOR(cmd.start_sector); u32 remaining = cmd.num_sectors; u32 write_result = 0; + if (part_sel != BOOT_PART_UDA) + switch_partition_access((part_sel == BOOT_PART_BOOT0) ? 1 : 2); + while (remaining > 0) { u32 batch = remaining > EMMC_CHUNK_SECTORS_WRITE ? EMMC_CHUNK_SECTORS_WRITE : remaining; u32 batch_bytes = batch * EMMC_SECTOR_SIZE; @@ -824,6 +919,9 @@ void entry() { remaining -= batch; } + if (part_sel != BOOT_PART_UDA) + switch_partition_access(0); /* restore UDA access */ + ep1_in_write_imm(&write_result, 4, &num_xfer); continue; } diff --git a/dump_boot_partitions.py b/dump_boot_partitions.py new file mode 100644 index 0000000..63882fe --- /dev/null +++ b/dump_boot_partitions.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +""" +dump_boot_partitions.py — dump eMMC boot0 and boot1 hardware partitions + +These partitions are NOT in the main NAND dump (jibo_full_dump.bin). +They hold the Tegra T124 BCT (Boot Configuration Table) and the +low-level bootloader, which the SoC ROM reads before any GPT exists. + +Output files (written to jibo_work/): + emmc_boot0.bin — Boot Partition 1 (~4–8 MB) + emmc_boot1.bin — Boot Partition 2 (~4–8 MB) + +Requires: + - Jibo in RCM mode (NVIDIA APX, USB 0955:7740) + - Shofel/shofel2_t124 built + - emmc_server.bin rebuilt with boot-partition support (make -C Shofel) + +Usage: + python3 dump_boot_partitions.py +""" + +import os +import sys +import subprocess +import platform +import struct +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent.resolve() +SHOFEL_DIR = SCRIPT_DIR / "Shofel" +WORK_DIR = SCRIPT_DIR / "jibo_work" + +SECTOR_SIZE = 512 + +# Magic sector bases that tell the payload which partition to access +BOOT0_BASE = 0x80000000 +BOOT1_BASE = 0xC0000000 + + +def shofel_exe() -> Path: + if platform.system() == "Windows": + return SHOFEL_DIR / "shofel2_t124.exe" + return SHOFEL_DIR / "shofel2_t124" + + +def run_shofel(args: list, capture: bool = True, timeout: int = 300): + exe = shofel_exe() + cmd = [str(exe)] + args + if platform.system() == "Linux": + cmd = ["sudo"] + cmd + result = subprocess.run(cmd, cwd=SHOFEL_DIR, capture_output=capture, timeout=timeout) + return result.returncode, result.stdout, result.stderr + + +def device_present() -> bool: + try: + r = subprocess.run(["lsusb"], capture_output=True, text=True, timeout=5) + return "0955:7740" in r.stdout or "NVIDIA Corp. APX" in r.stdout + except Exception: + return False + + +def get_boot_partition_size() -> int: + """Read EXT_CSD to determine the boot partition size in sectors. + EXT_CSD byte 226 (BOOT_SIZE_MULT): boot partition size = BOOT_SIZE_MULT * 128 KB. + Returns sector count, or a safe default of 8192 (4 MB) on failure.""" + tmp = WORK_DIR / "_ext_csd_tmp.bin" + try: + rc, _, _ = run_shofel(["EMMC_READ_EXT_CSD", str(tmp)]) + if rc != 0 or not tmp.exists(): + print("[!] EXT_CSD read failed — using default boot partition size (4 MB)") + return 4 * 1024 * 1024 // SECTOR_SIZE # 8192 sectors + + data = tmp.read_bytes() + if len(data) < 227: + print("[!] EXT_CSD too short — using default boot partition size (4 MB)") + return 4 * 1024 * 1024 // SECTOR_SIZE + + boot_size_mult = data[226] # BOOT_SIZE_MULT + if boot_size_mult == 0: + print("[!] BOOT_SIZE_MULT is 0 — using default boot partition size (4 MB)") + return 4 * 1024 * 1024 // SECTOR_SIZE + + size_bytes = boot_size_mult * 128 * 1024 + size_sectors = size_bytes // SECTOR_SIZE + print(f"[+] EXT_CSD BOOT_SIZE_MULT={boot_size_mult} → boot partition = {size_bytes//1024} KB ({size_sectors} sectors)") + return size_sectors + finally: + tmp.unlink(missing_ok=True) + + +def dump_partition(label: str, base: int, num_sectors: int, out_path: Path) -> bool: + """Dump one boot partition by passing the encoded base sector to EMMC_READ.""" + size_kb = num_sectors * SECTOR_SIZE // 1024 + encoded_start = f"0x{base:08x}" # e.g. 0x80000000 for boot0 + + print(f"\n[*] Dumping {label} ({size_kb} KB, {num_sectors} sectors)...") + print(f" encoded start sector: {encoded_start}") + print(f" output: {out_path}") + + rc, _, stderr = run_shofel( + ["EMMC_READ", encoded_start, f"0x{num_sectors:x}", str(out_path)], + capture=False, + timeout=120, + ) + + if rc != 0: + print(f"[!] {label} dump FAILED (rc={rc})") + if stderr: + print(f" stderr: {stderr.decode(errors='replace')}") + return False + + if not out_path.exists(): + print(f"[!] {label}: output file not created") + return False + + actual = out_path.stat().st_size + expected = num_sectors * SECTOR_SIZE + print(f"[+] {label} written: {actual:,} bytes (expected {expected:,})") + + if actual != expected: + print(f"[!] Size mismatch — dump may be incomplete") + return False + + # Quick sanity: check if it's all zeros + data = out_path.read_bytes() + nz = sum(1 for b in data if b != 0) + if nz == 0: + print(f"[!] WARNING: {label} is entirely zeros — partition may be empty or switch failed") + else: + print(f"[+] {label} contains data ({nz:,} non-zero bytes)") + + return True + + +def main(): + print("=" * 60) + print("Jibo eMMC Boot Partition Dumper") + print("=" * 60) + + # Prerequisites + exe = shofel_exe() + if not exe.exists(): + print(f"[!] shofel2_t124 not found: {exe}") + print(" Build first: make -C Shofel") + sys.exit(1) + + WORK_DIR.mkdir(exist_ok=True) + + print("\n[*] Checking for Jibo in RCM mode...") + if not device_present(): + print("[!] Device not detected!") + print(" Hold RCM button, press reset, then check: lsusb | grep NVIDIA") + sys.exit(1) + print("[+] Device detected") + + # Get boot partition size from EXT_CSD + boot_sectors = get_boot_partition_size() + + # Dump boot0 and boot1 + boot0_path = WORK_DIR / "emmc_boot0.bin" + boot1_path = WORK_DIR / "emmc_boot1.bin" + + ok0 = dump_partition("boot0", BOOT0_BASE, boot_sectors, boot0_path) + ok1 = dump_partition("boot1", BOOT1_BASE, boot_sectors, boot1_path) + + print("\n" + "=" * 60) + if ok0 and ok1: + print("[+] Both boot partitions dumped successfully.") + print(f" {boot0_path}") + print(f" {boot1_path}") + print("\nThese files, combined with jibo_full_dump.bin, give you a") + print("complete backup of all eMMC data including the bootloader.") + else: + print("[!] One or more dumps failed.") + print("=" * 60) + + sys.exit(0 if (ok0 and ok1) else 1) + + +if __name__ == "__main__": + main()