Clock, baud, serial backend (hardware USART 0/1 or the software UART on any pins) and the activation window all resolve through one CMake function, pureboot_add_loader() in pureboot/CMakeLists.txt — the unit a downstream project consumes. The default baud is the fastest standard rate within 2.5 % (the same best-divisor search libavr's solver runs), gated on software builds by the polled receiver's 100-cycles-a-bit floor; every explicit pick is re-checked by the compile's static asserts. The size matrix builds each axis that can move the image — backend x clock ladder x USART instance, per chip — against the slot budget, and two nondefault deployments run the whole protocol suite live: the 328P on its shipped 1 MHz fuses over software serial on TX=PB1/RX=PB5 (pureboot.custom), and the 644A over USART1 (pureboot.usart1). The sim runner takes -l to bridge any link, paces a fully quiet bridge toward real time (a free-running 8 M-cycle window loses the reset-race knock), and the fixture application speaks the deployment it is built for. The loader itself shed bytes on the way: the return-address high byte spelled through byteswap (the double swap folds to the one-byte pick), the info-block address composed instead of bit_cast, and libavr's new polled-UART helpers replacing the port's uart::detail reaches. Every combination fits: 458-506 B across the megas' whole matrix, 470-484 B on the tinies, 556-562 B in the 1284s' 1 KiB slot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
69 lines
2.4 KiB
Python
69 lines
2.4 KiB
Python
"""Shared simavr harness for the pureboot tests: spawn the device runner,
|
|
hand out its pty, restart it from a flash dump (the power-fail path), and
|
|
keep its chatter out of undrained pipes."""
|
|
|
|
import os
|
|
import signal
|
|
import subprocess
|
|
|
|
|
|
class Device:
|
|
def __init__(self, binary, elf, mcu, hz, base_hex, page, baud, dump, reset_hex=None, resume=None, link=None):
|
|
cmd = [binary]
|
|
if link:
|
|
cmd += ["-l", link]
|
|
cmd += [elf, mcu, hz, base_hex, str(page), str(baud), dump]
|
|
if reset_hex is not None or resume is not None:
|
|
# 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")
|
|
self.proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=self.log, text=True)
|
|
self.dump = dump
|
|
self.pty = None
|
|
for _ in range(50):
|
|
line = self.proc.stdout.readline()
|
|
if not line:
|
|
break
|
|
if line.startswith("PB_PTY"):
|
|
self.pty = line.split()[1]
|
|
break
|
|
if not self.pty:
|
|
self.stop()
|
|
raise RuntimeError("device did not report a pty")
|
|
|
|
def reset(self):
|
|
"""The external reset line: SIGUSR1 re-enters at the reset vector."""
|
|
self.proc.send_signal(signal.SIGUSR1)
|
|
|
|
def power_fail(self):
|
|
"""SIGTERM: the runner dumps its flash and exits — the image a
|
|
restart resumes from."""
|
|
self.stop()
|
|
return self.dump
|
|
|
|
def stop(self):
|
|
self.proc.terminate()
|
|
try:
|
|
self.proc.wait(timeout=5)
|
|
except subprocess.TimeoutExpired:
|
|
self.proc.kill()
|
|
self.log.close()
|
|
|
|
|
|
def run_tool(tool, pty, baud, *args, timeout=180):
|
|
result = subprocess.run(
|
|
[os.environ.get("PYTHON", "python3"), tool, "--port", pty, "--baud", str(baud), "--wait", "25", *args],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=timeout,
|
|
)
|
|
print(result.stdout, end="")
|
|
if result.returncode != 0:
|
|
raise RuntimeError(f"tool exited {result.returncode}: {result.stderr.strip()}")
|
|
return result.stdout
|