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>
This commit is contained in:
michael
2026-07-05 22:20:15 -07:00
parent c0ae7b90c8
commit 0ba5256d02
4 changed files with 142 additions and 2 deletions

View File

@@ -30,6 +30,9 @@ pm3py/
hf_iso15.py # ISO 15693 — scan, rdbl, wrbl, thin sniff cmd
hf_mfc.py # MIFARE Classic — rdbl, wrbl, rdsc, chk, nested, cident
lf.py # LF commands + T55xxCommands + LFSearchResult
flash.py # pure-Python firmware flasher (OLD-frame bootloader protocol)
_firmware.py # firmware pin (matches_pin) — the fork build pm3py corresponds to
flash_cli.py # `pm3flash` console-script (detect → build/resolve → flash → verify)
trace/ # firmware trace infrastructure (shared by sniff, sim, reader)
trace.py # parse_tracelog, TRACELOG_HDR_SIZE
decode_iso15.py # ISO 15693 command/response decoders
@@ -137,6 +140,25 @@ Design doc: `docs/PYTHON_SIM_DESIGN.md`. Firmware patch in `firmware/` submodule
Firmware maintenance: atomic single-file commits for easy rebase against upstream PM3. See design doc for CI workflow.
## Firmware flashing
pm3py flashes the fork firmware itself (`pm3.flasher`, `pm3flash` CLI) — no C `pm3-flash`.
Key facts for maintainers:
- **OLD frame, not NG.** The bootloader speaks the legacy 544-byte fixed frame (no
magic/CRC); `core/transport.py` has `send_old`/`reopen`, `core/flash.py` the `Flasher`.
- **fullimage-only by default.** The bootrom region is refused unless `allow_bootrom=True`
a bad OS write is recoverable (proven on hardware); a bad bootrom write bricks to JTAG.
- **Merge segments sharing a flash page** (`build_blocks`): the SAM7 erase-programs whole
pages, so two adjacent PT_LOAD segments landing in one page must be written once with both
segments' data (else 0xFF padding clobbers real bytes — the on-hardware bug we hit).
- **Platform ≠ is_rdv4.** `is_rdv4` is the running firmware's compile-time flag, not the
board, so `--build`/`build_firmware` require an explicit `PLATFORM` (`PM3GENERIC` = Easy,
`PM3RDV4`). Build target: `make -C firmware PLATFORM=... armsrc/all`.
- **The pin.** `pm3py/_firmware.py` `FIRMWARE_PIN` is the fork build pm3py corresponds to
(currently the submodule SHA); bump it when you bump the submodule. `matches()` does a
7-char SHA-prefix test on the device version string. Auto-download of the pinned release
is the open follow-up (see the DT-Gitea migration plan).
## Adding new commands
1. Find the `CMD_*` constant in `include/pm3_cmd.h` and add to `Cmd` enum in `core/protocol.py`

View File

@@ -10,12 +10,18 @@ Pure-Python async client for the [Proxmark3](https://github.com/RfidResearchGrou
## Install
Not on PyPI yet — install from a clone. Use `--recurse-submodules` so the firmware fork
(`firmware/`) comes with it (needed for flashing and the live-sim features):
```bash
git clone --recurse-submodules <repo-url> pm3py
cd pm3py
pip install -e .
pip install -e . # or `pip install .`
```
Requires Python 3.10+ and `pyserial-asyncio>=0.6` (installed automatically).
Requires **Python 3.10+**; dependencies (`pyserial-asyncio`, `bitarray`, `pycryptodome`)
install automatically. It's pure Python + pyserial, so it also runs on a **Raspberry Pi**
(aarch64) — see [Flashing firmware](#flashing-firmware-pm3flash) for the Pi bring-up.
## Quick Start
@@ -502,6 +508,58 @@ trace card simulation) require our firmware fork, tracked as the `firmware/` git
submodule (`proxmark3-pm3py`, rebased onto upstream RfidResearchGroup/proxmark3).
Those add the `CMD_HF_SNIFF_STREAM` live-frame path and the BigBuf response-table +
WTX relay used by `SimSession`. Everything else degrades gracefully on stock firmware.
To put the fork on your device, use [`pm3flash`](#flashing-firmware-pm3flash) (below).
## Flashing firmware (`pm3flash`)
The live features (streaming sniff, response-table/trace card sim, `hw.led`) need the
**firmware fork** (`proxmark3-pm3py`, the `firmware/` submodule). pm3py flashes it for you
over USB — a pure-Python flasher, no C `pm3-flash`/DFU/JTAG. Each pm3py release is pinned to
a firmware build, and `pm3.firmware.matches_pin` tells you whether your device runs it.
```bash
pm3flash # detect the device; flash the pinned firmware if it differs
pm3flash --image fullimage.elf # flash a specific prebuilt image
pm3flash --build PM3GENERIC # build from the submodule (Easy), then flash
pm3flash --force # flash even if the device already matches the pin
```
`pm3flash` auto-detects the port, compares the device firmware to the pin, then (fullimage
only) flashes and verifies. It's **bootrom-safe** — it never writes the bootloader, so a bad
flash is recoverable, not a brick.
Image resolution order: `--image`, then `$PM3PY_FIRMWARE`, then a local build at
`firmware/armsrc/obj/fullimage.elf`. Build one with:
```bash
make -C firmware PLATFORM=PM3GENERIC armsrc/all # PM3 Easy / generic
make -C firmware PLATFORM=PM3RDV4 armsrc/all # RDV4
```
The firmware is an ARM **cross-compile** (needs `gcc-arm-none-eabi` + `make`; the FPGA
bitstream is committed, so no FPGA toolchain), and the `fullimage.elf` it produces is the
same regardless of build host. **Choose `PLATFORM` yourself** — pm3py can't infer it, because
a device's `is_rdv4` flag reports the firmware it's running, not the physical board.
**On a Raspberry Pi:** `pip install` pm3py on the Pi (works on aarch64), then either
cross-build on another Linux box and copy the image over —
```bash
make -C firmware PLATFORM=PM3GENERIC armsrc/all # dev box
scp firmware/armsrc/obj/fullimage.elf pi@rpi:~/
pm3flash --image ~/fullimage.elf # on the Pi
```
— or build on the Pi itself (with `gcc-arm-none-eabi` + `make`): `pm3flash --build PM3GENERIC`.
**Programmatic** (the CLI wraps `pm3.flasher`):
```python
pm3 = Proxmark3.sync()
pm3.firmware.matches_pin # False -> not on the pinned build
pm3.firmware.expected_firmware # the pinned firmware id
pm3.flasher.flash("firmware/armsrc/obj/fullimage.elf") # enter bootloader, write, reset, verify
```
## Architecture
@@ -551,3 +609,7 @@ python -m pytest tests/ -v
```
All tests use mock transports — **no hardware required**.
## License
MIT — see [LICENSE](LICENSE).

View File

@@ -0,0 +1,50 @@
# 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.

View File

@@ -49,6 +49,12 @@ approach, concrete steps, tests, risks.
| [2026-07-03-firmware-upstream-rebase-plan](2026-07-03-firmware-upstream-rebase-plan.md) | 📎 | Rebase the `proxmark3-pm3py` fork against upstream PM3 (atomic single-file-commit workflow). |
| [2026-07-05-firmware-fork-dt-migration](2026-07-05-firmware-fork-dt-migration.md) | 📋 | Migrate the firmware fork + pm3py to the DT Gitea; clean `master`(RRG-mirror)/`pm3py`(patches) rebase-fork model. |
## Firmware flashing
| Plan | Status | Summary |
|------|--------|---------|
| [2026-07-05-firmware-flasher](2026-07-05-firmware-flasher.md) | ✅ | Pure-Python flasher (OLD-frame bootloader protocol) + `pm3flash` CLI + firmware pin; hardware-validated on a PM3 Easy. Auto-download of the pinned release is the open follow-up (rides on the DT-Gitea migration). |
---
### Conventions