pureboot: one position-independent binary — its own staging loader
The image now runs from any 512-byte slot with every command intact: control flow stays PC-relative, the write guard keys on the running slot (the return-address anchor, computed once), the info block is addressed from that same anchor as a byte pair (no absolute 16-bit address in the image), and the application jump is an indirect call through a noipa- laundered pointer to the absolute entry. 'J' — jump to a wire word address, the one transfer primitive — replaces 'G': the host knows the application entry from the info block, and moving between loader copies needs arbitrary targets. The activation window is a compile-time 8 s (PUREBOOT_TIMEOUT overrides), counted as a single calibrated poll loop. A refused page no longer poisons the write-once temporary buffer (a real silicon trap: the next write would program the drained data): every page write discards the buffer first — CTPB on the tinies, on the mega the same RWWSRE store that re-enables RWW after programming. The tinies' post-op busy-waits go with it: their CPU halts through page erase and write. 488 / 502 / 504 B on t13a / t85 / mega — under the tinies' 510-byte budget, whose last slot word is the host-managed trampoline: the resident's holds the application entry, a staging copy's the jump through which an abandoned update still times out into a loader. The host tool updates the loader with itself: --update-loader installs the identical image one slot below the resident, jumps into it, lets it rewrite the resident, and restores the staging region from a state file — each phase idempotent off the flash state, resumable after any interruption (t13a: the staging slot carries the reset vector, written last in and first out; t85: word 0 redirected around the resident rewrite; mega: fuse-matrix preflight with a hard BOOTSZ gate and --assume-fuses for simulators). Application flashing recovers by reset from any interruption: patched page 0 and trampoline first, erase descending, and a walk-region refusal behind --force on BOOTRST-below-loader megas. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""End-to-end pureboot protocol test: spawn the simavr device, then drive it
|
||||
with the real host tool (pureboot.py, as a subprocess over the device's pty)
|
||||
through flash + EEPROM + timeout + fuse + hand-over scenarios, and cross-check
|
||||
through flash + EEPROM + fuse + hand-over scenarios, and cross-check
|
||||
the tool's view against the simulator's ground-truth memory dumps.
|
||||
|
||||
Usage: pbtest.py <device_bin> <pureboot_elf> <mcu> <hz> <base_hex> <page>
|
||||
@@ -101,8 +101,8 @@ def main():
|
||||
try:
|
||||
# Session 1: knock from reset, identify, program everything, stay.
|
||||
out = run_tool(tool, device.pty, baud, "--info", "--fuses", "--flash", app_bin,
|
||||
"--eeprom", ee_path, "--timeout", "8", "--stay")
|
||||
for needed in ("device: signature", "fuses:", "verify:", "activation timeout: 8 s", "stays"):
|
||||
"--eeprom", ee_path, "--stay")
|
||||
for needed in ("device: signature", "fuses:", "verify:", "stays"):
|
||||
if needed not in out:
|
||||
fail(f"session 1 output lacks {needed!r}")
|
||||
|
||||
@@ -116,8 +116,6 @@ def main():
|
||||
eeprom_back = open(read_eeprom, "rb").read()
|
||||
if eeprom_back[: len(ee_image)] != ee_image:
|
||||
fail("EEPROM read-back mismatch")
|
||||
if eeprom_back[-1] != 8:
|
||||
fail(f"timeout cell reads {eeprom_back[-1]}, expected 8")
|
||||
|
||||
# The expected post-surgery flash, straight from the tool's planner.
|
||||
pages = pb.plan_flash(open(app_bin, "rb").read(), info)
|
||||
@@ -128,16 +126,14 @@ def main():
|
||||
|
||||
# An external reset re-enters through the patched word 0 (tinies; the
|
||||
# runner resets them to address 0 like silicon) or BOOTRST (mega).
|
||||
# The loader must answer a fresh knock, and 'G' must land in the
|
||||
# application, which banners on the same link.
|
||||
# The loader must answer a fresh knock, and the 'J' hand-over must
|
||||
# land in the application, which banners on the same link.
|
||||
device.proc.send_signal(signal.SIGUSR1)
|
||||
port = pb.Port(device.pty, baud)
|
||||
try:
|
||||
loader = pb.Loader(port)
|
||||
loader.connect(15)
|
||||
port.write(b"G")
|
||||
if port.read_exact(1, 5.0) != pb.PROMPT:
|
||||
fail("no ack for G")
|
||||
loader.run_application()
|
||||
banner = port.read_exact(3, 5.0)
|
||||
if banner != b"APP":
|
||||
fail(f"application banner was {banner!r}")
|
||||
@@ -168,7 +164,7 @@ def main():
|
||||
ee_true_path = dump + ".eeprom"
|
||||
if os.path.exists(ee_true_path):
|
||||
ee_true = open(ee_true_path, "rb").read()
|
||||
if ee_true[: len(ee_image)] != ee_image or ee_true[-1] != 8:
|
||||
if ee_true[: len(ee_image)] != ee_image:
|
||||
fail("ground-truth EEPROM does not match what was programmed")
|
||||
|
||||
print("pbtest: all scenarios pass")
|
||||
|
||||
Reference in New Issue
Block a user