From 0ba55d02e4f1a0cf9bb45c4f907ce480fa159213 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin Date: Sat, 4 Apr 2026 19:39:08 +0300 Subject: [PATCH 1/4] Add backpressure to handle_flash_write, use _write_block in write_flash handle_flash_write now sends per-packet ACK (matching handle_write). write_flash reuses _write_block for consistent backpressure behavior. Flash write still fails at ~1MB due to FMC soft reset accumulation during page programming. RAM write (16MB) verified at 921600 baud. Flash write needs further debugging of fmc_enter_boot impact. Co-Authored-By: Claude Opus 4.6 (1M context) --- agent/main.c | 2 ++ src/defib/agent/client.py | 43 ++++++++++++++++----------------------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/agent/main.c b/agent/main.c index b440bff..ba5323c 100644 --- a/agent/main.c +++ b/agent/main.c @@ -333,6 +333,8 @@ static void handle_flash_write(const uint8_t *data, uint32_t len) { uint32_t chunk = pkt_len - 2; for (uint32_t i = 0; i < chunk && received < size; i++) staging[received++] = pkt[2 + i]; + /* Backpressure ACK */ + proto_send_ack(ACK_OK); } else if (cmd == 0) { uint8_t err[5]; err[0] = ACK_FLASH_ERROR; diff --git a/src/defib/agent/client.py b/src/defib/agent/client.py index 6e39653..dffa6ed 100644 --- a/src/defib/agent/client.py +++ b/src/defib/agent/client.py @@ -383,41 +383,32 @@ async def write_flash( ) -> bool: """Write data to flash (assumes sectors already erased). - Splits into WRITE_MAX_TRANSFER blocks to avoid FIFO overflow. - Each block: host sends CMD_WRITE with flash address, agent - receives to RAM, verifies CRC, programs page-by-page, verifies. + Uses _write_block with per-packet backpressure for reliable + transfer. Agent receives to RAM staging, verifies CRC, programs + flash page-by-page, verifies readback. """ total = len(data) offset = 0 + max_retries = 3 while offset < total: block_size = min(WRITE_MAX_TRANSFER, total - offset) block = data[offset:offset + block_size] - block_crc = zlib.crc32(block) & 0xFFFFFFFF - payload = struct.pack(" Date: Mon, 6 Apr 2026 15:24:01 +0300 Subject: [PATCH 2/4] Fix agent CLI progress output, CRC32 timeout, and misleading labels - Fix progress display: use plain print() instead of Rich console.print() which was adding newlines instead of overwriting in-place via \r - Scale CRC32 verification timeout with data size (~1s/MB) to avoid timeout on large reads (16MB was timing out at 10s hard limit) - Show "Sending Agent" instead of "Sending U-Boot" during agent upload - Document flash agent commands in main README Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 28 ++++++++++++++++++++++++ src/defib/agent/client.py | 3 ++- src/defib/cli/app.py | 5 +++-- src/defib/protocol/hisilicon_standard.py | 11 ++++++---- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f052e64..d1717e3 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,34 @@ Tested on real hardware with CRS112-8P-4S: | IVGHP203Y-AF | hi3516cv300 | `/dev/uart-IVGHP203Y-AF` | | IVG85HG50PYA-S | hi3516ev300 | `/dev/uart-IVG85HG50PYA-S` | +## Flash Agent (High-Speed Flash Dump) + +Defib includes a bare-metal flash agent that runs directly on the SoC, +replacing U-Boot in the boot chain. It communicates over a COBS binary +protocol at 921600 baud — ~5x faster than U-Boot's `md.b` hex dump. + +```bash +# 1. Upload the agent (power-cycle the camera when prompted) +defib agent upload -c hi3516ev300 -p /dev/ttyUSB0 + +# 2. Dump the entire flash (16MB, ~3 min, with CRC32 verification) +defib agent read -p /dev/ttyUSB0 -a 0x14000000 -s 16MB -o flash_dump.bin + +# Query device info (flash size, RAM base, JEDEC ID) +defib agent info -p /dev/ttyUSB0 + +# Write data back to flash +defib agent write -p /dev/ttyUSB0 -a 0x14000000 -i flash_dump.bin + +# Scan flash health (bad sectors, stuck bits) +defib agent scan -p /dev/ttyUSB0 +``` + +The flash base address is `0x14000000` for all supported SoCs. Use +`--no-verify` to skip the CRC32 check, or `--output-mode json` for +automation. See [agent/README.md](agent/README.md) for protocol details +and supported chips. + ## Testing with QEMU Defib can be tested end-to-end against the diff --git a/src/defib/agent/client.py b/src/defib/agent/client.py index dffa6ed..e048437 100644 --- a/src/defib/agent/client.py +++ b/src/defib/agent/client.py @@ -423,7 +423,8 @@ async def crc32(self, addr: int, size: int) -> int: payload = struct.pack(" None: result = await protocol.send_firmware( transport, agent_data, on_progress, spl_override=spl_data, + payload_label="Agent", ) if not result.success: if output == "json": @@ -973,7 +974,7 @@ async def _agent_read_async( t0 = time.time() data = await client.read_memory(address, size, on_progress=lambda d, t: ( - console.print(f"\r {d}/{t} ({d*100//t}%)", end="") if output == "human" else None + print(f"\r {d}/{t} ({d*100//t}%)", end="", flush=True) if output == "human" else None )) elapsed = time.time() - t0 @@ -1042,7 +1043,7 @@ async def _agent_write_async( t0 = time.time() ok = await client.write_memory(address, data, on_progress=lambda d, t: ( - console.print(f"\r {d}/{t} ({d*100//t}%)", end="") if output == "human" else None + print(f"\r {d}/{t} ({d*100//t}%)", end="", flush=True) if output == "human" else None )) elapsed = time.time() - t0 diff --git a/src/defib/protocol/hisilicon_standard.py b/src/defib/protocol/hisilicon_standard.py index ff60af9..6582bb5 100644 --- a/src/defib/protocol/hisilicon_standard.py +++ b/src/defib/protocol/hisilicon_standard.py @@ -262,13 +262,14 @@ async def _send_uboot( firmware: bytes, profile: SoCProfile, on_progress: Callable[[ProgressEvent], None] | None = None, + label: str = "U-Boot", ) -> bool: - """Send U-Boot image to DDR.""" + """Send U-Boot (or agent) image to DDR.""" total = len(firmware) _emit(on_progress, ProgressEvent( stage=Stage.UBOOT, bytes_sent=0, bytes_total=total, - message="Sending U-Boot", + message=f"Sending {label}", )) if not await self._send_head(transport, total, profile.uboot_address): @@ -289,7 +290,7 @@ async def _send_uboot( _emit(on_progress, ProgressEvent( stage=Stage.UBOOT, bytes_sent=total, bytes_total=total, - message="U-Boot complete", + message=f"{label} complete", )) return True @@ -299,6 +300,7 @@ async def send_firmware( firmware: bytes, on_progress: Callable[[ProgressEvent], None] | None = None, spl_override: bytes | None = None, + payload_label: str = "U-Boot", ) -> RecoveryResult: if self._profile is None: return RecoveryResult(success=False, error="No profile loaded") @@ -321,7 +323,8 @@ async def send_firmware( ) stages.append(Stage.SPL) - if not await self._send_uboot(transport, firmware, profile, on_progress): + if not await self._send_uboot(transport, firmware, profile, on_progress, + label=payload_label): return RecoveryResult( success=False, stages_completed=stages, error="Failed to send U-Boot", From 503fe51395d85ecbdd4b67d25d7af826a0800ebf Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin Date: Mon, 6 Apr 2026 15:26:04 +0300 Subject: [PATCH 3/4] Make agent read/write default to flash base and auto-detect size - `agent read` no longer requires -a and -s; defaults to 0x14000000 and queries flash size from the device via get_info() - `agent write` defaults -a to 0x14000000 - `agent read` -o defaults to flash_dump.bin - Update README examples to show the simpler invocation Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 14 +++++++------- src/defib/cli/app.py | 23 ++++++++++++++++------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index d1717e3..c499bdc 100644 --- a/README.md +++ b/README.md @@ -115,23 +115,23 @@ protocol at 921600 baud — ~5x faster than U-Boot's `md.b` hex dump. # 1. Upload the agent (power-cycle the camera when prompted) defib agent upload -c hi3516ev300 -p /dev/ttyUSB0 -# 2. Dump the entire flash (16MB, ~3 min, with CRC32 verification) -defib agent read -p /dev/ttyUSB0 -a 0x14000000 -s 16MB -o flash_dump.bin +# 2. Dump the entire flash (address and size auto-detected) +defib agent read -p /dev/ttyUSB0 -o flash_dump.bin # Query device info (flash size, RAM base, JEDEC ID) defib agent info -p /dev/ttyUSB0 # Write data back to flash -defib agent write -p /dev/ttyUSB0 -a 0x14000000 -i flash_dump.bin +defib agent write -p /dev/ttyUSB0 -i flash_dump.bin # Scan flash health (bad sectors, stuck bits) defib agent scan -p /dev/ttyUSB0 ``` -The flash base address is `0x14000000` for all supported SoCs. Use -`--no-verify` to skip the CRC32 check, or `--output-mode json` for -automation. See [agent/README.md](agent/README.md) for protocol details -and supported chips. +Address defaults to flash base (`0x14000000`) and size is auto-detected +from the device. Override with `-a` and `-s` if needed. Use `--no-verify` +to skip the CRC32 check, or `--output-mode json` for automation. See +[agent/README.md](agent/README.md) for protocol details and supported chips. ## Testing with QEMU diff --git a/src/defib/cli/app.py b/src/defib/cli/app.py index 715c88e..4b72610 100644 --- a/src/defib/cli/app.py +++ b/src/defib/cli/app.py @@ -935,9 +935,9 @@ async def _agent_info_async(port: str, output: str) -> None: @agent_app.command("read") def agent_read( port: str = typer.Option("/dev/ttyUSB0", "-p", "--port", help="Serial port"), - addr: str = typer.Option(..., "-a", "--addr", help="Start address (hex)"), - size: str = typer.Option(..., "-s", "--size", help="Size in bytes (or 1KB, 16MB, etc)"), - output_file: str = typer.Option(..., "-o", "--output", help="Output binary file"), + addr: str = typer.Option(None, "-a", "--addr", help="Start address (hex, default: flash base 0x14000000)"), + size: str = typer.Option(None, "-s", "--size", help="Size in bytes (or 1KB, 16MB, etc; default: auto-detect)"), + output_file: str = typer.Option("flash_dump.bin", "-o", "--output", help="Output binary file"), verify: bool = typer.Option(True, "--verify/--no-verify", help="CRC32 verify after read"), output: str = typer.Option("human", "--output-mode", help="Output mode: human, json"), ) -> None: @@ -947,7 +947,7 @@ def agent_read( async def _agent_read_async( - port: str, addr_str: str, size_str: str, output_file: str, verify: bool, output: str, + port: str, addr_str: str | None, size_str: str | None, output_file: str, verify: bool, output: str, ) -> None: import json as json_mod import time @@ -959,8 +959,6 @@ async def _agent_read_async( from defib.transport.serial import SerialTransport console = Console() - address = int(addr_str, 0) - size = _parse_size(size_str) transport = await SerialTransport.create(port) client = FlashAgentClient(transport) @@ -969,6 +967,17 @@ async def _agent_read_async( await transport.close() raise typer.Exit(1) + # Default to full flash dump when address/size not specified + if addr_str is None or size_str is None: + info = await client.get_info() + if not info: + console.print("[red]Failed to get device info[/red]") + await transport.close() + raise typer.Exit(1) + + address = int(addr_str, 0) if addr_str is not None else 0x14000000 + size = _parse_size(size_str) if size_str is not None else info["flash_size"] + if output == "human": console.print(f"Reading 0x{address:08x} + {size} bytes...") @@ -1006,7 +1015,7 @@ async def _agent_read_async( @agent_app.command("write") def agent_write( port: str = typer.Option("/dev/ttyUSB0", "-p", "--port", help="Serial port"), - addr: str = typer.Option(..., "-a", "--addr", help="Start address (hex)"), + addr: str = typer.Option("0x14000000", "-a", "--addr", help="Start address (hex, default: flash base)"), input_file: str = typer.Option(..., "-i", "--input", help="Input binary file"), verify: bool = typer.Option(True, "--verify/--no-verify", help="CRC32 verify after write"), output: str = typer.Option("human", "--output", help="Output mode: human, json"), From b60e81a762940d22a2b1797c781bf22e43cff28b Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin Date: Mon, 6 Apr 2026 15:27:20 +0300 Subject: [PATCH 4/4] Fix mypy: cast flash_size to int for read_memory arg Co-Authored-By: Claude Opus 4.6 (1M context) --- src/defib/cli/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defib/cli/app.py b/src/defib/cli/app.py index 4b72610..1bccb2e 100644 --- a/src/defib/cli/app.py +++ b/src/defib/cli/app.py @@ -976,7 +976,7 @@ async def _agent_read_async( raise typer.Exit(1) address = int(addr_str, 0) if addr_str is not None else 0x14000000 - size = _parse_size(size_str) if size_str is not None else info["flash_size"] + size = _parse_size(size_str) if size_str is not None else int(info["flash_size"]) if output == "human": console.print(f"Reading 0x{address:08x} + {size} bytes...")