Files
pm3py/docs/plans/2026-07-05-firmware-flasher.md
michael 0ba5256d02 docs: Pi install + pm3flash firmware flashing (README, CLAUDE, roadmap)
- README: rewrite Install (from source, submodules, deps, Pi/aarch64); new
  'Flashing firmware (pm3flash)' section — CLI usage, --build, image resolution,
  cross-compile + PLATFORM note, Raspberry Pi bring-up, programmatic API; point
  Firmware Compatibility at it; add a License section (MIT).
- CLAUDE.md: flash.py/_firmware.py/flash_cli.py in the structure; a 'Firmware
  flashing' section with the maintainer gotchas (OLD frame, fullimage-only,
  page-merge, platform != is_rdv4, the pin).
- roadmap: new 2026-07-05-firmware-flasher plan doc + a Firmware-flashing row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 22:20:15 -07:00

2.8 KiB

Pure-Python firmware flasher + pm3flash

Status: Landed (hardware-validated on a PM3 Easy) · Date: 2026-07-05 Related: firmware-fork DT-Gitea migration

Goal

Let pm3py get the fork firmware onto a device by itself — no C pm3-flash/DFU/JTAG — so pip install pm3py + one command flashes the build this pm3py corresponds to. Supports the project "in the meantime", before tagged firmware releases exist on the DT Gitea.

What landed

  • Flasher (pm3py/core/flash.py) over the legacy 544-byte OLD frame (bootloader protocol, distinct from the NG frames the rest of the client speaks; OLD-frame plumbing in core/transport.py). Automatic OS→bootloader handover (CMD_START_FLASH, no button), CMD_FINISH_WRITE block loop with per-block ACK/NACK, reset, reopen-and-verify.
  • Safety: fullimage-only by default; the bootrom region is refused unless allow_bootrom=True. A bad OS write is recoverable (proven — recovered a corrupted-OS device); a bad bootrom write bricks to JTAG.
  • Firmware pin (pm3py/_firmware.py): FIRMWARE_PIN = the fork build pm3py corresponds to (currently the submodule SHA). pm3.firmware.matches_pin / .expected_firmware on connect.
  • pm3flash CLI (pm3py/flash_cli.py): detect → resolve/build image → flash → verify. --image (prebuilt), --build PLATFORM (make from the submodule; platform explicit — never inferred from is_rdv4, which reports the running firmware, not the board), --force, --allow-bootrom.

Lessons (found on hardware / by adversarial review)

  • Merge segments sharing a flash page. The SAM7 erase-programs whole pages; two adjacent PT_LOAD segments in one page must be written once with both segments' data, else 0xFF padding clobbers real bytes (bug: the flashed OS booted but had invalid version info).
  • --build must flash what it built, not gate on the pin — a dirty rebuild keeps the same base SHA, so matches_pin can't distinguish it (--build therefore implies --force).
  • No CWD-relative firmware discovery — running make on a stray ./firmware is arbitrary code execution; only an explicit arg / $PM3PY_FIRMWARE_SRC / the package repo root.

Open follow-ups

  • Auto-download of the pinned release asset (toolchain-free install) — needs a tagged firmware release to exist; rides on the DT-Gitea migration. Drops into resolve_image.
  • Read-back verify via CMD_READ_MEM_DOWNLOAD (v1 trusts per-block ACK/NACK, like the C flasher).

Provenance

Built + hardware-validated in one session; the --build layer was adversarially reviewed by a multi-agent workflow (correctness / subprocess-safety / CLI-UX lenses with a verify pass), which surfaced the pin-skip, make-missing, and CWD-Makefile issues fixed above.