pureboot: a loader in the staging slot is the staging copy — leave it there

The update flow's first step wrote the staging content over whatever the
staging slot held; with a loader running right there (programmed by hand
onto erased flash), that write met the copy's own running-slot guard on
the composed through-word and the tool stopped at its verify — although
the copy is exactly an installed staging copy, able to stream the new
resident like any other. The install is now skipped when the slot holds a
complete loader: its info block where every image carries it, matching
the device's byte for byte, and the slot unchanged since the update began
(the state file's snapshot) — so a resumed half-written install still
differs from its snapshot and takes the install path, which completes it.
pbrehome gains the staging-slot position (an older build at stage
streaming a newer resident in); the README's wrong "cannot re-home from
the staging slot" claim is corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 21:55:15 +02:00
parent 5f9e736d66
commit 76f4576fe0
3 changed files with 94 additions and 49 deletions

View File

@@ -725,15 +725,33 @@ def op_update_loader(loader, wait, path, state_path, fuse_bytes):
state = UpdateState(state_path)
state.load_or_save(loader)
# Install the staging copy. On a chip whose staging slot starts at
# address 0 (the 1 KB tiny13A), its first page carries the reset vector:
# written last, so any earlier interruption still resets into the old
# resident, and from then on resets enter the staging copy.
order = list(range(0, info.slot, page))
if info.stage == 0:
order = order[1:] + [0]
if write_differing(loader, info.stage, staged, order):
print(f"staging copy installed at {info.stage:#06x}")
# Install the staging copy — unless a loader already sits whole in the
# staging slot (a build programmed there by hand): that copy IS the
# installed staging copy, and rewriting it would only trip its own
# running-slot guard on the composed through-word. Any pureboot with
# the device's own info block serves — the staged copy just streams
# pages, so an older build installs a newer resident all the same. Two
# checks make "already a loader" mean a *complete* one: the block must
# sit where every image carries it (within the slot's first 256 bytes
# — the build's position lint), matching the device's block byte for
# byte, and the slot must be unchanged since this update began (the
# state file's snapshot) — a resumed, half-written install differs
# from its snapshot and takes the install path below, which completes
# it page by page.
current = loader.read_flash(info.stage, info.slot)
staged_loader = image_info(current[:268])
if staged_loader is not None and staged_loader.raw == info.raw and current == state.staging:
print(f"staging slot already holds a loader — left in place")
else:
# On a chip whose staging slot starts at address 0 (the 1 KB
# tiny13s), its first page carries the reset vector: written last,
# so any earlier interruption still resets into the old resident,
# and from then on resets enter the staging copy.
order = list(range(0, info.slot, page))
if info.stage == 0:
order = order[1:] + [0]
if write_differing(loader, info.stage, staged, order):
print(f"staging copy installed at {info.stage:#06x}")
# Enter it and let it rewrite the resident slot. Where a patched reset
# vector routes through the resident (a tiny with the staging slot away