pureboot: the 1284P rides a 1 KiB slot — its own boot-sector minimum
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>
This commit is contained in:
@@ -42,19 +42,20 @@ class PowerFail(Exception):
|
||||
|
||||
|
||||
def assumed_fuses(pb, image):
|
||||
"""Synthetic 'F' bytes for --assume-fuses: the smallest boot section of
|
||||
at least 1 KB (what a self-update needs), BOOTRST unprogrammed — the
|
||||
per-chip BOOTSZ ladder and fuse byte come from the tool's own table,
|
||||
keyed by the update image's embedded signature."""
|
||||
"""Synthetic 'F' bytes for --assume-fuses: the smallest boot section
|
||||
covering both the resident and the staging slot (two slots — what a
|
||||
self-update needs), BOOTRST unprogrammed — the per-chip BOOTSZ ladder
|
||||
and fuse byte come from the tool's own table, keyed by the update
|
||||
image's embedded signature."""
|
||||
info = pb.image_info(image)
|
||||
which, ladder = pb.BOOT_FUSE[bytes(info.signature[1:3])]
|
||||
bits = min((b for b in ladder if ladder[b] * 2 >= 1024), key=lambda b: ladder[b])
|
||||
bits = min((b for b in ladder if ladder[b] * 2 >= 2 * info.slot), key=lambda b: ladder[b])
|
||||
fuses = bytearray((0xFF, 0xFF, 0xFF, 0xFF))
|
||||
fuses[which] = 0xF8 | (bits << 1) | 1
|
||||
return bytes(fuses)
|
||||
|
||||
|
||||
def make_fault_loader(pb, base, kill_region, kill_hits, device):
|
||||
def make_fault_loader(pb, base, slot, kill_region, kill_hits, device):
|
||||
"""A Loader whose write_page kills the device (or, with device=None,
|
||||
just the host) at the Nth write into a region; the sequence
|
||||
stage->resident->stage distinguishes the install from the restore."""
|
||||
@@ -69,7 +70,7 @@ def make_fault_loader(pb, base, kill_region, kill_hits, device):
|
||||
if address >= base:
|
||||
phase = "resident"
|
||||
self.seen_resident = True
|
||||
elif address >= base - 512:
|
||||
elif address >= base - slot:
|
||||
phase = "stage_restore" if self.seen_resident else "stage"
|
||||
else:
|
||||
phase = "app"
|
||||
@@ -88,6 +89,7 @@ def main():
|
||||
(device_bin, elf, update_elf, mcu, hz, base_hex, page, baud, app_bin, tool, workdir) = sys.argv[1:]
|
||||
base, page, baud = int(base_hex, 0), int(page), int(baud)
|
||||
mega = mcu.startswith("atmega")
|
||||
slot = 1024 if base + 1024 > 0x10000 and mega else 512 # word-addressed chips use the 1 KiB slot
|
||||
reset_hex = "0" if mega else None # the mega runs BOOTRST-unprogrammed here
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(tool)))
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -119,16 +121,16 @@ def main():
|
||||
return port, loader
|
||||
|
||||
def padded(image):
|
||||
return image + b"\xff" * (512 - len(image))
|
||||
return image + b"\xff" * (slot - len(image))
|
||||
|
||||
def resident_bytes(loader):
|
||||
return loader.read_flash(base, 256) + loader.read_flash(base + 256, 256)
|
||||
return loader.read_flash(base, slot)
|
||||
|
||||
def assert_state(loader, image, app_pages):
|
||||
if resident_bytes(loader) != padded(image):
|
||||
fail("resident loader does not match the update image")
|
||||
stage = base - 512
|
||||
got = loader.read_flash(stage, 256) + loader.read_flash(stage + 256, 256)
|
||||
stage = base - slot
|
||||
got = loader.read_flash(stage, slot)
|
||||
for address, data in app_pages.items():
|
||||
if stage <= address < base:
|
||||
if got[address - stage : address - stage + page] != data:
|
||||
@@ -177,7 +179,7 @@ def main():
|
||||
port, loader = connect(device)
|
||||
target = "v9" if resident_bytes(loader) == padded(images["v0"]) else "v0"
|
||||
image_path = os.path.join(workdir, target + ".bin")
|
||||
injected = make_fault_loader(pb, base, kill_region, kill_hits, device if kill_device else None)(port)
|
||||
injected = make_fault_loader(pb, base, slot, kill_region, kill_hits, device if kill_device else None)(port)
|
||||
injected.info = loader.info
|
||||
try:
|
||||
pb.op_update_loader(injected, 25, image_path, state, fuses)
|
||||
@@ -203,10 +205,10 @@ def main():
|
||||
# Ground truth: the simulator's own flash against the final state, and
|
||||
# on the tinies an independent decode of the reset routing.
|
||||
flash = open(dump, "rb").read()
|
||||
if flash[base : base + 512] != padded(images[final]):
|
||||
if flash[base : base + slot] != padded(images[final]):
|
||||
fail("ground-truth resident region does not match the final image")
|
||||
if not mega:
|
||||
flash_words = (base + 512) // 2
|
||||
flash_words = (base + slot) // 2
|
||||
word0 = flash[0] | (flash[1] << 8)
|
||||
if rjmp_decode(word0, 0, flash_words) != base // 2:
|
||||
fail("ground-truth reset vector does not land on the loader")
|
||||
|
||||
Reference in New Issue
Block a user