Skip to content

Add bare-metal flash agent with COBS binary protocol#10

Merged
widgetii merged 13 commits intomasterfrom
socket-transport
Mar 31, 2026
Merged

Add bare-metal flash agent with COBS binary protocol#10
widgetii merged 13 commits intomasterfrom
socket-transport

Conversation

@widgetii
Copy link
Copy Markdown
Member

Summary

Bare-metal ARM32 agent binary that runs on HiSilicon/Goke SoCs for fast flash operations over UART, replacing the slow md.b hex dump approach.

  • Agent binary: ARM32 bare-metal C code uploaded via existing boot protocol (SPL → agent)
  • COBS protocol: Binary framing with CRC32 per packet, ~99% line efficiency
  • Commands: INFO, READ, WRITE, CRC32, SELFUPDATE, REBOOT
  • Python host: recv_packet/send_packet with persistent buffering, wait_for_ready
  • Self-update: Agent can update itself without power cycle (staging + trampoline)
  • Disconnect survival: PL011 BREAK/framing error handling, periodic READY beacons

Performance (real hi3516ev300 hardware, 115200 baud)

Direction Throughput Efficiency ETA 16MB
Device→Host (READ) 11,394 B/s 98.9% 24.5 min
Host→Device (WRITE) 11,199 B/s 97.2% ~25 min
md.b baseline ~2,000 B/s ~17% ~2 hours

Files

  • agent/ — C source: startup.S, main.c, uart.c, protocol.c, cobs.c, spi_flash.c, Makefile, link.ld
  • src/defib/agent/ — Python host: protocol.py, client.py, cobs.py
  • src/defib/transport/socket.py — Unix socket transport for QEMU testing
  • tests/test_agent_protocol.py — 27 protocol tests (FakePort/FakeTransport)
  • tests/test_agent_cobs.py — 18 COBS/packet tests
  • agent/test_agent.c — 1604 C unit test assertions
  • .github/workflows/ci.yml — New agent-test CI job

Known Limitations

  • Host→Device WRITE reliable up to ~16KB per transfer (PL011 16-byte FIFO overflow on uncached DDR during COBS+CRC processing). Larger writes need host-side chunking.
  • SPI flash controller not yet activated (memory-mapped reads disabled — FMC state after SPL unknown)
  • UART fixed at 115200 baud (bootrom default)
  • Agent not yet wired into defib CLI/TUI

Test plan

  • 235 Python tests pass, 2 skipped
  • 1604 C agent test assertions pass
  • CI: agent-test job added (host gcc)
  • Real hardware: hi3516ev300 — upload, INFO, READ 256KB, WRITE 16KB, CRC32 verify, self-update ×2, disconnect survival ×5
  • CI check on PR

🤖 Generated with Claude Code

widgetii and others added 11 commits March 31, 2026 11:30
Bare-metal ARM32 agent (agent/):
- startup.S: entry point, MMU/cache disable, TLB invalidate, BSS clear
- uart.c: PL011 UART driver (per-SoC base addresses)
- spi_flash.c: HiSilicon FMC flash controller (memory-mapped read, register write/erase)
- protocol.c: COBS framing + CRC32 packet protocol
- cobs.c: COBS encode/decode
- main.c: command loop (INFO, READ, WRITE, ERASE, CRC32, REBOOT)
- Makefile: cross-compile for 8 SoCs, auto-pad to 32KB
- Compiles to ~4KB, runs without OS

Key fix: -mno-unaligned-access prevents ARM data abort on
non-word-aligned CRC32 byte writes (verified on real hi3516ev300).

Tested on real hardware:
- Uploaded via U-Boot loady + go at 0x41000000
- Agent prints debug output ('DF', 'XYZ') and COBS READY packet
- QEMU: uploads via boot protocol, PL011 writes confirmed via trace

Python host side (src/defib/agent/):
- cobs.py: COBS encode/decode
- protocol.py: packet framing, send/recv, command constants
- client.py: high-level FlashAgentClient (upload, read, write, verify)

Tests: 18 COBS + protocol tests (roundtrip, CRC, edge cases)

Known issues:
- QEMU chardev drops data between fastboot→PL011 handoff (socket timing)
- Boot protocol upload needs real DDR init + SPL (agent must be loaded
  via U-Boot loady, not directly via boot protocol on real hardware)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PL011 UART driver now handles BREAK/framing errors from host port
close: uart_getc_safe() returns -2 on error, proto_recv discards
partial frames and clears error flags. uart_putc has TX timeout to
prevent hanging. Serial transport disables DTR/RTS toggling.

Tested on real hi3516ev300: agent survived 5 consecutive disconnect
cycles with consistent beacon output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed raw uart_putc beacons ('*', '.', '[', 'R', 'r') that polluted
the COBS protocol stream. Agent now sends COBS-framed READY every ~2s
of idle time instead of suppressing it after first command — allows
host to detect agent after serial port reconnect.

Tested on hi3516ev300: survived 5 disconnect cycles, clean COBS only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New test_agent_protocol.py: 19 tests covering recv_packet, send_packet,
wait_for_ready, recv_response, and data streaming with FakePort/Transport.

Fixed _recv_packet_sync: uses per-port persistent buffer so bytes read
past the first COBS delimiter aren't lost between calls. Also uses
in_waiting for non-blocking reads instead of blocking port.read(256).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SELFUPDATE now requires host to include expected CRC32 in the command
payload: [addr:4LE] [size:4LE] [crc32:4LE]. After receiving all data,
agent verifies CRC32 of written region. On mismatch, sends ACK_CRC_ERROR
and stays alive instead of jumping to corrupt code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 new tests: packet format with CRC32, payload verification,
data transfer flow, CRC mismatch error handling, success flow.
42 total agent tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1604 assertions across COBS encode/decode, CRC32 known vectors,
protocol send/recv roundtrip, READY/ACK packets, bad CRC rejection,
max payload, multi-packet streams, and Python cross-compatibility.

Runs on host with `make -C agent test`. Added to CI as agent-test job.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New write handler: receives data packets to RAM with CRC32 verification,
without jumping (unlike SELFUPDATE). Same protocol as SELFUPDATE but
stays in command loop after completion. Needed for flash write path.

3 new Python tests, 1604 C tests still passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SELFUPDATE was overwriting its own running code, causing crash.
Now receives data to staging area (1MB above load addr), verifies
CRC32, then copies a position-independent ARM trampoline to a fixed
RAM location which performs the final copy and jump.

Also: CMD_WRITE handler reports received byte count on timeout and
actual CRC on mismatch for debugging.

Tested: 2 consecutive self-updates without power cycle, binary match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With MMU/caches disabled, COBS decode + CRC32 on uncached DDR takes
~1-2ms per packet. At 115200 baud, this overflows the 16-byte PL011
FIFO when packets arrive back-to-back.

Solution: software RX ring buffer (4KB) drains the hardware FIFO
before and after heavy processing in proto_recv. Bytes buffered in
software survive the processing gap.

Host should use ≤512 byte chunks for WRITE to stay within safe
timing margins. READ (device→host) unaffected — agent controls
transmit pacing.

Also: CMD_WRITE removed per-packet ACK (not needed with soft FIFO).
Debug reporting: timeout reports received byte count, CRC mismatch
reports actual CRC value.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- uart_init: always reconfigure to known-good state instead of trusting
  bootrom settings. Prevents issues from stale CR flags.
- Software RX ring buffer (4KB) in proto_recv drains PL011 FIFO before
  heavy COBS+CRC processing to prevent overflow on uncached DDR.
- CMD_WRITE works reliably up to 16KB per transfer. Larger writes need
  host-side chunking due to cumulative FIFO timing on uncached DDR.
- READ (device→host) unaffected: 98.9% efficiency at 256KB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
widgetii and others added 2 commits March 31, 2026 13:43
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@widgetii widgetii merged commit 149cad4 into master Mar 31, 2026
13 checks passed
@widgetii widgetii deleted the socket-transport branch March 31, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant