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

51 lines
2.8 KiB
Markdown

# Pure-Python firmware flasher + `pm3flash`
**Status:** ✅ Landed (hardware-validated on a PM3 Easy) · **Date:** 2026-07-05
Related: [firmware-fork DT-Gitea migration](2026-07-05-firmware-fork-dt-migration.md)
## 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.