pureboot: a 512-byte slot on every chip, the 1284s included
The word-addressed 1284s were the one family deploying in a 1 KiB slot, because the far-flash machinery (ELPM reads, RAMPZ page commands, a word-addressed wire) did not fit 512 B. It does now: 478 B stock, 494 B in the heaviest configuration the build can produce. They take the 644s' geometry, where the smallest boot section holds the resident slot and its staging slot together. Most of the saving is one restructure. The info block and a flash read are the same act, so giving all four streamed commands one address-and-count path leaves exactly one call site for the flash streamer: it inlines into the never-returning command loop and its 24-bit cursor stops being saved and restored around every transmit. Around it, the ack byte moved out of line, the wire's byte pair is bit_cast into the word it already is, the fuse loop ends on its count, the info block's in-slot offset is taken as the one-byte relocation it is, and -fno-expensive-optimizations gives way to -fno-move-loop-invariants -fno-tree-ter. Every chip shrank 14-18 B. The size matrix grew the axes it was missing: the USART1 instance across the whole clock ladder, and the shape a slow baud gives a software UART — past 255 delay iterations libavr takes the 16-bit delay loop, which the ladder default never selects and which was 4 B over the 1284's slot the first time it was built. The protocol fixture stopped deriving the loader entry from the flash size; on the 1284s it had been jumping a slot low and reaching the loader only because erased flash walked it up. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -66,9 +66,10 @@ struct link {
|
||||
}
|
||||
[[noreturn]] static void idle()
|
||||
{
|
||||
// 'L' hands back to the loader at the top slot — 512 bytes, or the
|
||||
// 1 KiB the >64 KiB chips use.
|
||||
constexpr std::uint32_t slot = avr::hw::db.mem.flash_size > 65536 ? 1024 : 512;
|
||||
// 'L' hands back to the loader in the top slot — 512 bytes on every
|
||||
// chip. The jump takes a word address, which is what makes the
|
||||
// >64 KiB chips' entry reachable through a 16-bit pointer at all.
|
||||
constexpr std::uint32_t slot = 512;
|
||||
for (;;) {
|
||||
auto command = tx_t::read_blocking();
|
||||
if (command == 'L')
|
||||
|
||||
@@ -90,7 +90,7 @@ def main():
|
||||
# The staging slot: erased flash with the loader sitting exactly where
|
||||
# a staging copy would — the tool must leave it in place and let it
|
||||
# stream the (different) update build into the resident slot.
|
||||
stage = base - 512
|
||||
stage = base - pb.SLOT
|
||||
rehome_from(pbsim, pb, device_bin, elf, hex(stage), hex(stage), update_bin, base, page, baud, app_bin, workdir,
|
||||
mcu, hz)
|
||||
print("re-home from the staging slot: converged")
|
||||
|
||||
@@ -83,7 +83,7 @@ def main():
|
||||
|
||||
# Restore the resident image through the staged copy, then 'J' back
|
||||
# into it and prove it lives.
|
||||
resident = image + b"\xff" * (info.slot - len(image))
|
||||
resident = image + b"\xff" * (pb.SLOT - len(image))
|
||||
pb.write_differing(loader, base, resident)
|
||||
back_info = loader.enter_copy(base, 25)
|
||||
if back_info.raw != resident_info:
|
||||
|
||||
@@ -57,7 +57,7 @@ def main():
|
||||
# the page byte is the wire's 0-means-256.
|
||||
mega = mcu.startswith("atmega")
|
||||
patch = not mega or mcu.startswith("atmega48")
|
||||
word_flash = base + 512 > 0x10000
|
||||
word_flash = base + pb.SLOT > 0x10000
|
||||
wire_base = base // 2 if word_flash else base
|
||||
flags = (1 if patch else 0) | (2 if word_flash else 0)
|
||||
info = pb.Info(
|
||||
@@ -127,7 +127,7 @@ def main():
|
||||
# loader, the trampoline on the application's own entry (patched-vector
|
||||
# chips only — a boot-sectioned mega's word 0 stays the application's).
|
||||
if patch:
|
||||
flash_words = (base + 512) // 2
|
||||
flash_words = (base + pb.SLOT) // 2
|
||||
app = open(app_bin, "rb").read()
|
||||
word0 = flash_true[0] | (flash_true[1] << 8)
|
||||
if rjmp_decode(word0, 0, flash_words) != base // 2:
|
||||
|
||||
@@ -50,7 +50,7 @@ def assumed_fuses(pb, image):
|
||||
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 >= 2 * info.slot), key=lambda b: ladder[b])
|
||||
bits = min((b for b in ladder if ladder[b] * 2 >= 2 * pb.SLOT), key=lambda b: ladder[b])
|
||||
fuses = bytearray((0xFF, 0xFF, 0xFF, 0xFF))
|
||||
fuses[which] = 0xF8 | (bits << 1) | 1
|
||||
return bytes(fuses)
|
||||
@@ -93,16 +93,13 @@ def main():
|
||||
# The m48s are megas without a boot section: patched vector, no fuse
|
||||
# preflight, and the same reset-to-0 the tinies get.
|
||||
patch = not mega or mcu.startswith("atmega48")
|
||||
# Word-addressed (>64 KiB) chips use the 1 KiB slot; their loader base
|
||||
# itself sits beyond the 16-bit byte space — the 644's base + slot only
|
||||
# touches the 64 KiB boundary and stays byte-addressed.
|
||||
slot = 1024 if base >= 0x10000 and mega else 512
|
||||
reset_hex = "0" if mega else None # the boot-sectioned 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__)))
|
||||
import pbsim
|
||||
import pureboot as pb
|
||||
|
||||
slot = pb.SLOT
|
||||
os.makedirs(workdir, exist_ok=True)
|
||||
objcopy = os.environ.get("PB_OBJCOPY", "avr-objcopy")
|
||||
images = {}
|
||||
|
||||
@@ -34,7 +34,8 @@ def info_of(pb, base, page, patch, flash, signature=(0x1E, 0x93, 0x0B), word_fla
|
||||
raw = bytes((0x50, 0x42, pb.NEWEST_LOADER if version is None else version,
|
||||
*signature, page & 0xFF, wire_base & 0xFF, wire_base >> 8, 0, 2, flags))
|
||||
info = pb.Info(raw)
|
||||
assert info.flash_size == flash
|
||||
if info.flash_size != flash:
|
||||
fail(f"info_of({base:#x}) decodes to {info.flash_size:#x} of flash, not {flash:#x}")
|
||||
return info
|
||||
|
||||
|
||||
@@ -94,9 +95,7 @@ def main():
|
||||
((0x1E, 0x97, 0x05), 0x20000, 3, {0b11: 0x1FC00, 0b10: 0x1F800, 0b01: 0x1F000, 0b00: 0x1E000}), # 1284P
|
||||
)
|
||||
for signature, flash, which, ladder in cases:
|
||||
# Word-addressed chips carry the 1 KiB slot (their smallest boot sector).
|
||||
slot = 1024 if flash > 0x10000 else 512
|
||||
chip = info_of(pb, flash - slot, 128 if flash < 0x20000 else 0, False, flash,
|
||||
chip = info_of(pb, flash - pb.SLOT, 128 if flash < 0x20000 else 0, False, flash,
|
||||
signature=signature, word_flash=flash > 0x10000)
|
||||
for bits, start in ladder.items():
|
||||
fuses = bytearray((0xFF, 0xFF, 0xFF, 0xFF))
|
||||
@@ -109,10 +108,12 @@ def main():
|
||||
if prog or at != start:
|
||||
fail(f"mega_boot {signature[1]:02x}{signature[2]:02b} unprogrammed: {prog} {at:#07x}")
|
||||
|
||||
# Word-addressed info decode: the 1284P's base/page ride the wire scaled,
|
||||
# and its slot is 1 KiB.
|
||||
big = info_of(pb, 0x1FC00, 0, False, 0x20000, signature=(0x1E, 0x97, 0x05), word_flash=True)
|
||||
if big.page != 256 or big.base != 0x1FC00 or big.stage != 0x1F800 or big.slot != 1024:
|
||||
# Word-addressed info decode: the 1284P's base and page ride the wire
|
||||
# scaled — a 17-bit base halved into the block's two bytes, a 256-byte page
|
||||
# spelled 0 — and its slot is the same 512 bytes as everywhere else, so its
|
||||
# staging slot lands inside the 1 KiB minimum boot section.
|
||||
big = info_of(pb, 0x1FE00, 0, False, 0x20000, signature=(0x1E, 0x97, 0x05), word_flash=True)
|
||||
if big.page != 256 or big.base != 0x1FE00 or big.stage != 0x1FC00:
|
||||
fail(f"word-addressed info decode: page {big.page}, base {big.base:#x}, stage {big.stage:#x}")
|
||||
|
||||
# Surgery: word 0 lands on the loader, the trampoline on the original
|
||||
@@ -215,6 +216,15 @@ def main():
|
||||
if pb.update_preflight(bytes((0xAA,)) * 8 + tiny.raw, tiny, None) != []:
|
||||
fail("tiny preflight should pass without fuses")
|
||||
|
||||
# The 1284s' smallest boot section (512 words) is exactly the resident
|
||||
# slot plus its staging slot, so self-update is possible at the minimum
|
||||
# BOOTSZ — no fuse step up, the 644's geometry. That holds only while a
|
||||
# slot is 512 B: at 1 KiB the staging slot would fall outside the section
|
||||
# and the preflight would refuse.
|
||||
notes = pb.update_preflight(bytes((0xAA,)) * 8 + big.raw, big, fuses(0xFE))
|
||||
if not any("staging slot" in n for n in notes):
|
||||
fail(f"1284 minimum-BOOTSZ notes: {notes}")
|
||||
|
||||
# The walk-region refusal: BOOTRST aimed below the loader plus app data
|
||||
# in the walk span errors without --force; erased spans and unprogrammed
|
||||
# BOOTRST pass.
|
||||
|
||||
Reference in New Issue
Block a user