Compare commits
1 Commits
main
...
feature/em
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e84839932a |
@@ -15,14 +15,22 @@
|
|||||||
#define EMMC_CMD_ERASE 0x05 /* Erase a range of sectors (forces reallocation) */
|
#define EMMC_CMD_ERASE 0x05 /* Erase a range of sectors (forces reallocation) */
|
||||||
#define EMMC_CMD_EXIT 0xFF
|
#define EMMC_CMD_EXIT 0xFF
|
||||||
|
|
||||||
/* Transfer chunk sizes basically the amount much data is sent/received per USB transfer was not multiple of 0x1000
|
/* Boot partition encoding for EMMC_CMD_READ / EMMC_CMD_WRITE start_sector:
|
||||||
*
|
* 0x00000000 | sector -> User Data Area (normal)
|
||||||
* Reads: 8 sectors (4KB) = 1.1 MB/s - OPTIMIZED
|
* 0x80000000 | sector -> Boot Partition 1 (boot0)
|
||||||
* Writes: 1 sector (512B) - ORIGINAL (safe)
|
* 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_READ 8
|
||||||
#define EMMC_CHUNK_SECTORS_WRITE 1
|
#define EMMC_CHUNK_SECTORS_WRITE 8
|
||||||
#define EMMC_CHUNK_SECTORS 1 /* Default - single sector for compatibility */
|
#define EMMC_CHUNK_SECTORS 8
|
||||||
#define EMMC_SECTOR_SIZE 512
|
#define EMMC_SECTOR_SIZE 512
|
||||||
#define EMMC_CHUNK_BYTES (EMMC_CHUNK_SECTORS * EMMC_SECTOR_SIZE)
|
#define EMMC_CHUNK_BYTES (EMMC_CHUNK_SECTORS * EMMC_SECTOR_SIZE)
|
||||||
|
|
||||||
|
|||||||
@@ -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_CMD18 0x123A /* READ_MULTIPLE_BLOCK: R1, data, CRC+index check */
|
||||||
#define MMC_CMD24 0x183A /* WRITE_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_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_CMD35 0x233A /* ERASE_GROUP_START: R1, CRC+index check */
|
||||||
#define MMC_CMD36 0x243A /* ERASE_GROUP_END: R1, CRC+index check */
|
#define MMC_CMD36 0x243A /* ERASE_GROUP_END: R1, CRC+index check */
|
||||||
#define MMC_CMD38 0x263B /* ERASE: R1b, 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 sdmmc4_initialized = 0;
|
||||||
static u32 init_error = 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 */
|
/* 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 */
|
/* Write a single 512-byte sector to eMMC */
|
||||||
static int write_emmc_sector(u32 sector, u32 *buffer) {
|
static int write_emmc_sector(u32 sector, u32 *buffer) {
|
||||||
u32 status;
|
u32 status;
|
||||||
@@ -715,6 +781,19 @@ static int erase_emmc_sectors(u32 start_sector, u32 end_sector) {
|
|||||||
return 0;
|
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")))
|
__attribute__((section(".init")))
|
||||||
void entry() {
|
void entry() {
|
||||||
|
|
||||||
@@ -776,9 +855,15 @@ void entry() {
|
|||||||
|
|
||||||
if (cmd.op == EMMC_CMD_READ) {
|
if (cmd.op == EMMC_CMD_READ) {
|
||||||
init_sdmmc4();
|
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 remaining = cmd.num_sectors;
|
||||||
|
|
||||||
|
if (part_sel != BOOT_PART_UDA)
|
||||||
|
switch_partition_access((part_sel == BOOT_PART_BOOT0) ? 1 : 2);
|
||||||
|
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
u32 batch = remaining > EMMC_CHUNK_SECTORS_READ ? EMMC_CHUNK_SECTORS_READ : remaining;
|
u32 batch = remaining > EMMC_CHUNK_SECTORS_READ ? EMMC_CHUNK_SECTORS_READ : remaining;
|
||||||
u32 batch_bytes = batch * EMMC_SECTOR_SIZE;
|
u32 batch_bytes = batch * EMMC_SECTOR_SIZE;
|
||||||
@@ -795,15 +880,25 @@ void entry() {
|
|||||||
sector += batch;
|
sector += batch;
|
||||||
remaining -= batch;
|
remaining -= batch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (part_sel != BOOT_PART_UDA)
|
||||||
|
switch_partition_access(0); /* restore UDA access */
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cmd.op == EMMC_CMD_WRITE) {
|
if (cmd.op == EMMC_CMD_WRITE) {
|
||||||
init_sdmmc4();
|
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 remaining = cmd.num_sectors;
|
||||||
u32 write_result = 0;
|
u32 write_result = 0;
|
||||||
|
|
||||||
|
if (part_sel != BOOT_PART_UDA)
|
||||||
|
switch_partition_access((part_sel == BOOT_PART_BOOT0) ? 1 : 2);
|
||||||
|
|
||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
u32 batch = remaining > EMMC_CHUNK_SECTORS_WRITE ? EMMC_CHUNK_SECTORS_WRITE : remaining;
|
u32 batch = remaining > EMMC_CHUNK_SECTORS_WRITE ? EMMC_CHUNK_SECTORS_WRITE : remaining;
|
||||||
u32 batch_bytes = batch * EMMC_SECTOR_SIZE;
|
u32 batch_bytes = batch * EMMC_SECTOR_SIZE;
|
||||||
@@ -824,6 +919,9 @@ void entry() {
|
|||||||
remaining -= batch;
|
remaining -= batch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (part_sel != BOOT_PART_UDA)
|
||||||
|
switch_partition_access(0); /* restore UDA access */
|
||||||
|
|
||||||
ep1_in_write_imm(&write_result, 4, &num_xfer);
|
ep1_in_write_imm(&write_result, 4, &num_xfer);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
182
dump_boot_partitions.py
Normal file
182
dump_boot_partitions.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user