The temporary page buffer is write-once per word, so a page filled over
one an earlier writer left dirty programs the stale words. The same
datasheet clause carries the cure: the buffer auto-erases after a page
write (§26.2.1; §19.2 on the tinies), so the corruption clears itself by
happening, and rewriting the page programs correctly.
The loader therefore clears the buffer nowhere. The tinies' CTPB and the
m48s' RWWSRE discard are gone; the boot-sectioned megas keep only the
trailing RWWSRE they need anyway to re-enable the RWW section for
read-back, which discards the buffer as a side effect and keeps them off
the path entirely. 434 B on the tiny13s, 438-442 on the tiny25/45/85,
430 on the m48s; the megas are unchanged, the 1284s still 506.
The host takes over the guarantee: a flash page that reads back wrong is
rewritten up to RETRIES times before the run stops. Both read-back paths
repair — verify_pages for programming, and write_differing, which is the
loader-update path where a page left wrong is a half-written loader slot.
That one is not hypothetical: deleting the discard made attiny85
pureboot.rehome fail deterministically there, the only flow still
assuming the old contract.
Protocol-visible, so README's W command says it: one W may program the
wrong bytes after a refused page, or after an application that
self-programmed entered without a reset, and a host that programs without
reading back cannot trust it.
Tests: pureboot.dirty drives the case the loader declines to guard — the
fixture application dirties every buffer word and jumps in with no reset
(hardware forbids that on a boot-sectioned mega, but simavr dispatches SPM
from anywhere, which is what makes it constructible) — and asserts a bare
verify sees the corruption, the repairing verify fixes it in one rewrite,
and it stays fixed. pbreloc asserts the same shape after a refusal.
test_planner covers the bound against a fake device: one bad write
repaired in a single rewrite, a page that never comes good stopping after
exactly three.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The far machinery (ELPM reads, RAMPZ page commands, wire-word math)
costs ~46 B over the m328P's 504, and the tsb-calibrated C++-to-asm
gap says no implementation of this feature set reaches 512 on this
chip — a boundary its hardware does not have anyway: the 1284P's
smallest boot sector is 1 KiB. The slot therefore becomes
per-geometry (512 B, or 1 KiB past 64 KiB), which the host derives
from the word-addressing flag; slot arithmetic unifies (the index is
the wire high byte with its low bit dropped in either unit), the
update preflight demands a two-slot boot section in the chip's own
terms, and pbapp's hand-back jumps to the real slot base. libavr's
far primitives split their RAMPZ/Z asm operands (a page never
crosses 64 KiB, so callers keep a byte and a 16-bit cursor — the
32-bit address folds away; flash_load_far's byte form becomes the
out-RAMPZ+elpm pair avr-libc's pgm_read_byte_far rebuilds per call),
and the host splits reads at 64 KiB boundaries. All ten chips pass
the full suite — the 1284P at 558 B including protocol, relocation,
and the power-fail self-update — with pureboot byte-identical across
generated and reflect modes everywhere, and the original three
chips' images unchanged to the byte (488/502/504).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
check_pi.py asserts the two link-time facts position independence rests on
(no absolute jmp/call; the info block within the image's first 256 bytes);
the size gates drop to 510 on the tinies for the trampoline word.
New per-chip tests beside the reworked protocol test: the planner units
(programming orders and their recovery properties, the surgery, staging
composition, boot-fuse decode, and the update preflight's error/warning
matrix over synthetic fuse bytes), the relocated-copy sweep (the identical
image installed one slot lower serves the full command set — the PI
acceptance test, and the one that caught the temporary-buffer trap), and
the self-update end-to-end: --update-loader to a re-timed build
(pureboot9, byte-different by PUREBOOT_TIMEOUT alone), then every
power-fail phase killed mid-write, restarted from the runner's flash dump,
and completed by a re-run with the application intact throughout. The mega
rounds run the BOOTRST-unprogrammed profile: the fixture application's 'L'
jump is the application-owned loader entry that profile relies on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>