pureboot: every libavr chip — 37 loaders, the m48 class, the 644 geometry
The chip table becomes family blocks covering all 37 targets. The m48s are a new deployment class: no boot section, so the tiny profile spoken over the hardware USART — host-patched reset vector, trampoline hand-over, a 510-byte budget (474 B built), no fuse preflight — while their RWWSRE store stays the buffer discard (Atmel-8271 §26.2); the device keys the patch flag and the CPU-halt waits on the curated boot-section capability and the discard on the RWWSRE bit itself. The 644s' 64 KiB is exactly the 16-bit byte space: plain LPM, byte wire addresses, 498 B in a 512-byte slot — and their 1 KiB minimum boot section holds the resident and staging slots together, so self-update needs no fuse step (the update test's slot pick now keys word-flash on base >= 64 KiB; base + slot merely touching the boundary stays byte-addressed). The 1284 joins the 1284P's word-addressed 1 KiB slot at 558 B. BOOT_FUSE gains every boot-sectioned family's ladder and fuse byte; the planner exercises them all. The sim scaffolding keys patch-vector-ness instead of the atmega name prefix, the fixture app picks its clock by family (the tiny25/45/13 builds surfaced the 16 MHz fallthrough as garbled banners), and the runner's wrapped flash ioctl performs the m48 discard simavr's no-RWW cores turn into a stray buffer fill. Sizes across the fleet: 466-504 B megas, 474 B m48s, 498 B 644s, 488-502 B tinies, 558 B 1284s — every chip passing size/pi/planner/protocol/reloc/update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,17 +17,17 @@ namespace {
|
||||
|
||||
consteval avr::hertz_t clock()
|
||||
{
|
||||
if (avr::hw::db.name == "ATtiny13A")
|
||||
auto name = std::string_view{avr::hw::db.name};
|
||||
if (name.starts_with("ATtiny13"))
|
||||
return 9.6_MHz;
|
||||
if (avr::hw::db.name == "ATtiny85")
|
||||
if (name.starts_with("ATtiny"))
|
||||
return 8_MHz;
|
||||
return 16_MHz;
|
||||
}
|
||||
|
||||
using dev = avr::device<{.clock = clock()}>;
|
||||
|
||||
template <avr::hertz_t C,
|
||||
bool Hardware = avr::hw::db.has_instance("USART0") || avr::hw::db.has_instance("USART")>
|
||||
template <avr::hertz_t C, bool Hardware = avr::hw::db.has_instance("USART0") || avr::hw::db.has_instance("USART")>
|
||||
struct link {
|
||||
using tx_t = avr::uart::usart0<C, {.baud = 115200_Bd, .max_baud_error = 2.5_pct}>;
|
||||
static void tx(char c)
|
||||
|
||||
@@ -11,7 +11,11 @@ class Device:
|
||||
def __init__(self, binary, elf, mcu, hz, base_hex, page, baud, dump, reset_hex=None, resume=None):
|
||||
cmd = [binary, elf, mcu, hz, base_hex, str(page), str(baud), dump]
|
||||
if reset_hex is not None or resume is not None:
|
||||
cmd.append(reset_hex if reset_hex is not None else ("0" if not mcu.startswith("atmega") else base_hex))
|
||||
# Chips without a hardware boot section — the tinies and the
|
||||
# m48s — reset to address 0 like silicon; the boot-sectioned
|
||||
# megas re-vector to the loader base (BOOTRST).
|
||||
patch = not mcu.startswith("atmega") or mcu.startswith("atmega48")
|
||||
cmd.append(reset_hex if reset_hex is not None else ("0" if patch else base_hex))
|
||||
if resume is not None:
|
||||
cmd.append(resume)
|
||||
self.log = open(dump + ".log", "a")
|
||||
|
||||
@@ -91,12 +91,14 @@ def main():
|
||||
read_eeprom = os.path.join(workdir, "readback_eeprom.bin")
|
||||
|
||||
# The geometry the host will discover, for computing the expected image:
|
||||
# megas carry a boot section (no vector surgery), the large ones speak
|
||||
# word addresses, and the page byte is the wire's 0-means-256.
|
||||
# the boot-sectioned megas need no vector surgery (the tinies and the
|
||||
# boot-section-less m48s do), the large chips speak word addresses, and
|
||||
# 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
|
||||
wire_base = base // 2 if word_flash else base
|
||||
flags = (0 if mega else 1) | (2 if word_flash else 0)
|
||||
flags = (1 if patch else 0) | (2 if word_flash else 0)
|
||||
info = pb.Info(
|
||||
bytes([ord("P"), ord("B"), 1, 0, 0, 0, page & 0xFF])
|
||||
+ bytes([wire_base & 0xFF, wire_base >> 8, eeprom_size & 0xFF, eeprom_size >> 8])
|
||||
@@ -156,9 +158,9 @@ def main():
|
||||
fail("loader region looks erased in the ground-truth dump")
|
||||
|
||||
# The surgery, decoded independently: the patched vector must land on the
|
||||
# loader, the trampoline on the application's own entry (tinies only —
|
||||
# the megas' word 0 stays the application's).
|
||||
if not mega:
|
||||
# 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
|
||||
app = open(app_bin, "rb").read()
|
||||
word0 = flash_true[0] | (flash_true[1] << 8)
|
||||
|
||||
@@ -5,12 +5,13 @@ replaces itself with a re-timed build through the host tool's
|
||||
killing the simulated device mid-write, restarting it from its flash dump,
|
||||
and letting a re-run complete the update.
|
||||
|
||||
The mega runs the BOOTRST-unprogrammed profile (reset boots the application;
|
||||
the fixture application's 'L' jump is the application-owned loader entry),
|
||||
with --assume-fuses standing in for the fuse read simavr cannot model. The
|
||||
tinies reset into a loader at every phase by construction — the t13a because
|
||||
its staging slot carries the reset vector itself, the t85 through the word-0
|
||||
redirect the tool plants around the resident rewrite.
|
||||
The boot-sectioned megas run the BOOTRST-unprogrammed profile (reset boots
|
||||
the application; the fixture application's 'L' jump is the application-owned
|
||||
loader entry), with --assume-fuses standing in for the fuse read simavr
|
||||
cannot model. The patched-vector chips — the tinies and the m48s — reset
|
||||
into a loader at every phase by construction: the t13a because its staging
|
||||
slot carries the reset vector itself, the others through the word-0 redirect
|
||||
the tool plants around the resident rewrite.
|
||||
|
||||
Usage: pbupdate.py <device_bin> <pureboot_elf> <update_elf> <mcu> <hz>
|
||||
<base_hex> <page> <baud> <app_bin> <tool_py> <workdir>
|
||||
@@ -89,8 +90,14 @@ 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
|
||||
# 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
|
||||
@@ -107,7 +114,7 @@ def main():
|
||||
fail("the update image is byte-identical to the resident build")
|
||||
dump = os.path.join(workdir, "dump.bin")
|
||||
state = os.path.join(workdir, "update.pbstate")
|
||||
fuses = assumed_fuses(pb, images["v0"]) if mega else None
|
||||
fuses = assumed_fuses(pb, images["v0"]) if mega and not patch else None
|
||||
|
||||
def connect(device):
|
||||
port = pb.Port(device.pty, baud)
|
||||
@@ -147,7 +154,7 @@ def main():
|
||||
|
||||
# A clean CLI update, resident -> v9.
|
||||
args = ["--update-loader", os.path.join(workdir, "v9.bin"), "--state", state, "--stay"]
|
||||
if mega:
|
||||
if fuses:
|
||||
args += ["--assume-fuses", fuses.hex()]
|
||||
out = pbsim.run_tool(tool, device.pty, baud, *args)
|
||||
if "loader updated" not in out:
|
||||
@@ -172,7 +179,7 @@ def main():
|
||||
# cost of that profile (README).
|
||||
for kill_region, kill_hits, kill_device in (
|
||||
("stage", 2, True),
|
||||
("resident", 1, not mega),
|
||||
("resident", 1, patch),
|
||||
("stage_restore", 2, True),
|
||||
):
|
||||
device.reset() # the previous round left the application running
|
||||
@@ -203,11 +210,11 @@ def main():
|
||||
device.stop()
|
||||
|
||||
# Ground truth: the simulator's own flash against the final state, and
|
||||
# on the tinies an independent decode of the reset routing.
|
||||
# on the patched-vector chips an independent decode of the reset routing.
|
||||
flash = open(dump, "rb").read()
|
||||
if flash[base : base + slot] != padded(images[final]):
|
||||
fail("ground-truth resident region does not match the final image")
|
||||
if not mega:
|
||||
if patch:
|
||||
flash_words = (base + slot) // 2
|
||||
word0 = flash[0] | (flash[1] << 8)
|
||||
if rjmp_decode(word0, 0, flash_words) != base // 2:
|
||||
|
||||
@@ -49,6 +49,13 @@ static volatile sig_atomic_t reset_requested;
|
||||
// anywhere inside the page wipes half the neighbouring page in simulation
|
||||
// only. Wrap the mega's registered flash ioctl and re-dispatch page erases
|
||||
// with Z forced to the page boundary; everything else passes through.
|
||||
//
|
||||
// A second gap on the boot-section-less m48s: their RWWSRE bit is the
|
||||
// temporary-buffer discard (Atmel-8271 §26.2/§26.3.1), but the stock model
|
||||
// gates its RWWSRE branch on AVR_SELFPROG_HAVE_RWW — absent on the m48
|
||||
// core — so the discard store falls through into the buffer-fill branch and
|
||||
// plants whatever Z/R1:R0 happen to hold. Perform the silicon's discard
|
||||
// here instead.
|
||||
static avr_flash_t *mega_flash;
|
||||
static int (*mega_flash_ioctl)(avr_io_t *io, uint32_t ctl, void *param);
|
||||
|
||||
@@ -64,6 +71,15 @@ static int fixed_flash_ioctl(avr_io_t *io, uint32_t ctl, void *param)
|
||||
io->avr->data[31] = (uint8_t)(z >> 8);
|
||||
return result;
|
||||
}
|
||||
if (ctl == AVR_IOCTL_FLASH_SPM && !(mega_flash->flags & AVR_SELFPROG_HAVE_RWW) &&
|
||||
(io->avr->data[mega_flash->r_spm] & 0x11) == 0x11) { // RWWSRE|SELFPRGEN: the m48 buffer discard
|
||||
for (int i = 0; i < mega_flash->spm_pagesize / 2; i++) {
|
||||
mega_flash->tmppage[i] = 0xffff;
|
||||
mega_flash->tmppage_used[i] = 0;
|
||||
}
|
||||
avr_regbit_clear(io->avr, mega_flash->selfprgen);
|
||||
return 0;
|
||||
}
|
||||
return mega_flash_ioctl(io, ctl, param);
|
||||
}
|
||||
|
||||
@@ -306,11 +322,13 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
memcpy(avr->flash + base, fw.flash, fw.flashsize);
|
||||
}
|
||||
// The mega enters the loader in hardware (BOOTRST, not modeled — the
|
||||
// argument picks the modeled fuse's target); the tinies reset to word 0
|
||||
// like silicon — erased flash walks up into the loader, and after the
|
||||
// host's surgery the patched vector routes there.
|
||||
reset_pc = argc > 8 ? (uint32_t)strtoul(argv[8], NULL, 0) : (use_uart_pty ? base : 0);
|
||||
// The boot-sectioned megas enter the loader in hardware (BOOTRST, not
|
||||
// modeled — the argument picks the modeled fuse's target); the tinies
|
||||
// and the boot-section-less m48s reset to word 0 like silicon — erased
|
||||
// flash walks up into the loader, and after the host's surgery the
|
||||
// patched vector routes there.
|
||||
int boot_section = use_uart_pty && strncmp(mcu_name, "atmega48", 8) != 0;
|
||||
reset_pc = argc > 8 ? (uint32_t)strtoul(argv[8], NULL, 0) : (boot_section ? base : 0);
|
||||
avr->pc = reset_pc;
|
||||
avr->codeend = avr->flashend;
|
||||
|
||||
|
||||
@@ -56,15 +56,26 @@ def main():
|
||||
mega = info_of(pb, 0x7E00, 128, False, 0x8000, signature=(0x1E, 0x95, 0x0F))
|
||||
|
||||
# mega_boot: BOOTSZ words and the BOOTRST sense per chip — the fuse byte
|
||||
# index (HIGH everywhere but the m168A's EXTENDED) and the per-family
|
||||
# ladders (Atmel-2486/2466/2503/8271/42719). Synthetic 'F' replies: only
|
||||
# the boot byte carries meaning.
|
||||
# index (EXTENDED on the x8 line except the m328s' HIGH, HIGH elsewhere)
|
||||
# and the per-family ladders (Atmel-2486/2466/2503/2545/8271/DS40002065/
|
||||
# 8272/8011/2593/42719). Synthetic 'F' replies: only the boot byte
|
||||
# carries meaning.
|
||||
cases = (
|
||||
((0x1E, 0x93, 0x07), 0x2000, 3, {0b11: 0x1F00, 0b10: 0x1E00, 0b01: 0x1C00, 0b00: 0x1800}), # m8
|
||||
((0x1E, 0x94, 0x03), 0x4000, 3, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m16
|
||||
((0x1E, 0x95, 0x02), 0x8000, 3, {0b11: 0x7E00, 0b10: 0x7C00, 0b01: 0x7800, 0b00: 0x7000}), # m32
|
||||
((0x1E, 0x94, 0x06), 0x4000, 2, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m168A
|
||||
((0x1E, 0x93, 0x0A), 0x2000, 2, {0b11: 0x1F00, 0b10: 0x1E00, 0b01: 0x1C00, 0b00: 0x1800}), # m88
|
||||
((0x1E, 0x93, 0x0F), 0x2000, 2, {0b11: 0x1F00, 0b10: 0x1E00, 0b01: 0x1C00, 0b00: 0x1800}), # m88P
|
||||
((0x1E, 0x94, 0x06), 0x4000, 2, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m168/168A
|
||||
((0x1E, 0x94, 0x0B), 0x4000, 2, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m168P
|
||||
((0x1E, 0x95, 0x14), 0x8000, 3, {0b11: 0x7E00, 0b10: 0x7C00, 0b01: 0x7800, 0b00: 0x7000}), # m328
|
||||
((0x1E, 0x95, 0x0F), 0x8000, 3, {0b11: 0x7E00, 0b10: 0x7C00, 0b01: 0x7800, 0b00: 0x7000}), # m328P
|
||||
((0x1E, 0x94, 0x0F), 0x4000, 3, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m164A
|
||||
((0x1E, 0x94, 0x0A), 0x4000, 3, {0b11: 0x3F00, 0b10: 0x3E00, 0b01: 0x3C00, 0b00: 0x3800}), # m164P
|
||||
((0x1E, 0x95, 0x15), 0x8000, 3, {0b11: 0x7E00, 0b10: 0x7C00, 0b01: 0x7800, 0b00: 0x7000}), # m324A
|
||||
((0x1E, 0x96, 0x09), 0x10000, 3, {0b11: 0xFC00, 0b10: 0xF800, 0b01: 0xF000, 0b00: 0xE000}), # m644
|
||||
((0x1E, 0x96, 0x0A), 0x10000, 3, {0b11: 0xFC00, 0b10: 0xF800, 0b01: 0xF000, 0b00: 0xE000}), # m644P
|
||||
((0x1E, 0x97, 0x06), 0x20000, 3, {0b11: 0x1FC00, 0b10: 0x1F800, 0b01: 0x1F000, 0b00: 0x1E000}), # 1284
|
||||
((0x1E, 0x97, 0x05), 0x20000, 3, {0b11: 0x1FC00, 0b10: 0x1F800, 0b01: 0x1F000, 0b00: 0x1E000}), # 1284P
|
||||
)
|
||||
for signature, flash, which, ladder in cases:
|
||||
|
||||
Reference in New Issue
Block a user