pureboot: a version number for the loader, not for the protocol

The info block's third byte was a protocol version that never moved in the
loader's lifetime. It is the pureboot version now, and this change is
version 2: the one number that says what a deployed loader is. Every loader
already in the field answers 1.

The protocol keeps no number of its own — a pureboot version implies it, and
the host tool is what holds that map. pureboot.py states the loader-version
window it speaks (OLDEST_LOADER/NEWEST_LOADER; a version that changes the
protocol becomes the new floor there), so a loader newer than the tool is
refused by name rather than decoded on the assumption nothing moved, while an
older one is read, identified and installed like any other. The tool carries
its own version, free to drift from the loader's: --version prints it and the
window, --info leads with the device's, --update-loader names the version it
installs.

Tests: the planner unit pins the window — every version in it decodes, one
above it is refused, an older loader's image is still found — and the live
suite pins the built loader against the tool beside it, so a bump that reaches
only one of them fails. The image is byte-identical to the previous build but
for that byte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 17:19:27 +02:00
parent a39a3c7f46
commit 6f3eb06233
5 changed files with 95 additions and 22 deletions

View File

@@ -170,7 +170,7 @@ The info block (`b`):
| Offset | Content |
|---|---|
| 02 | `'P'`, `'B'`, protocol version (1) |
| 02 | `'P'`, `'B'`, pureboot version (2) |
| 35 | device signature |
| 6 | SPM page size in bytes (0 means 256) |
| 78 | loader base — application flash ends here (a word address when bit 1 is set) |
@@ -180,6 +180,21 @@ The info block (`b`):
Composites are the host's job: verify = read back and compare, erase =
write `0xff` (per page for flash, per byte for EEPROM).
## Version
The third byte of the info block is the **pureboot version** — the loader's
one identity number, and the only way to tell what a deployed loader is.
Nothing else is numbered: the wire protocol has no version of its own, a
pureboot version implies its protocol, and the host tool is what holds that
map. It states the window of loader versions it speaks
(`OLDEST_LOADER`/`NEWEST_LOADER` in `pureboot.py`); a version that changes
the protocol becomes the new floor there. So far none has: pureboot 1 and 2
speak the identical session, and a loader newer than the tool is refused by
name rather than decoded on the assumption that nothing moved.
The tool carries its own version, free to drift from the loader's:
`--version` prints both it and the window.
## Deployment
The build leaves three artifacts per chip. The ELF is a container for the
@@ -245,7 +260,7 @@ BOOTRST does not exist there.
## Updating the loader
`pureboot.py --update-loader new_pureboot.bin` replaces the resident loader
with any pureboot build — a re-timed window, a newer protocol — using the
with any pureboot build — a re-timed window, a newer version — using the
loader itself as its own staging loader. The image is the loader's own 512
bytes as a raw binary, or the Intel HEX the build emits beside it, which
links the loader at its base inside an otherwise blank flash image:
@@ -312,11 +327,12 @@ Intel HEX by extension. `--force` overrides the refusable safety checks (today:
application data into a mega's reset walk region).
Readouts come one fact per line: `--info` prints the decoded info block
field by field, `--fuses` each fuse byte on its own line — plus, on a
boot-sectioned mega, the decoded meaning (where the BOOTSZ section starts,
what BOOTRST does to reset). Transfers that take wire time — programming,
reading, erasing, verifying, the update phases — draw a transient progress
bar on stderr when it is a tty; logs and pipes see only the summary lines.
field by field, the loader's version first; `--fuses` each fuse byte on its
own line — plus, on a boot-sectioned mega, the decoded meaning (where the
BOOTSZ section starts, what BOOTRST does to reset). Transfers that take
wire time — programming, reading, erasing, verifying, the update phases —
draw a transient progress bar on stderr when it is a tty; logs and pipes
see only the summary lines.
`-v`/`--verbose` adds the decisions as they happen: knock counts, the
programming plan (vector-surgery targets, skipped blank pages), update
state handling and per-phase page counts.