pureboot: autobaud host support and simavr end-to-end for both variants
pureboot.py --autobaud sends the 0xC0 calibration pulse and a single knock at the host's chosen baud, reads the slimmed info block, and derives the full geometry from the signature (AUTOBAUD_GEOMETRY, a table over every pureboot chip). Everything downstream — flash, EEPROM, fuses, hand-over, verify — is the fixed-baud path unchanged; the dropped write guard is host-transparent. test/pbautobaud.py drives each variant over the GPIO⇄pty software-UART bridge through the calibration handshake and a flash + EEPROM + fuse round-trip cross-checked against the simulator's ground-truth memory, then repeats at double the F_CPU with the same binary — the clock-agnostic property autobaud exists for. Wired as pureboot.autobaud_pure/reg on the near-flash 328P and the word-addressed 1284P. A wrong measured unit fails the flash/verify, so the test also pins the codegen-coupled calibration constant against a toolchain bump. Both variants green in sim on both chips at two clocks each; the fixed-baud suite is unaffected. Only real-hardware acceptance on an RC part remains (pureboot/autobaud.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,44 @@ NEWEST_LOADER = 4
|
||||
SLOT = 512 # the loader slot, on every chip
|
||||
RETRIES = 3 # rewrites of a page that reads back wrong, before the run stops
|
||||
|
||||
# Calibration byte for the autobaud loader: 0xC0 is a start bit plus six zero
|
||||
# data bits — one low pulse of seven bit-times, which the loader times into its
|
||||
# per-bit unit. Sent at whatever baud the host chose; the loader locks to it.
|
||||
CALIBRATE = 0xC0
|
||||
|
||||
# The autobaud loader slims its info block to the version and signature; the host
|
||||
# derives the rest of the geometry from the signature. flash, page, eeprom,
|
||||
# patch-vector per distinct signature, over every chip pureboot targets (the
|
||||
# loader computes the same from its chip database at build time). Die revisions
|
||||
# that share a signature share this row, as they share the silicon.
|
||||
AUTOBAUD_GEOMETRY = {
|
||||
# signature : (flash, page, eeprom, patch_vector)
|
||||
(0x1E, 0x90, 0x07): (1024, 32, 64, True), # ATtiny13/13A
|
||||
(0x1E, 0x91, 0x08): (2048, 32, 128, True), # ATtiny25
|
||||
(0x1E, 0x92, 0x06): (4096, 64, 256, True), # ATtiny45
|
||||
(0x1E, 0x93, 0x0B): (8192, 64, 512, True), # ATtiny85
|
||||
(0x1E, 0x92, 0x05): (4096, 64, 256, True), # ATmega48/48A
|
||||
(0x1E, 0x92, 0x0A): (4096, 64, 256, True), # ATmega48P/48PA
|
||||
(0x1E, 0x93, 0x07): (8192, 64, 512, False), # ATmega8/8A
|
||||
(0x1E, 0x93, 0x0A): (8192, 64, 512, False), # ATmega88/88A
|
||||
(0x1E, 0x93, 0x0F): (8192, 64, 512, False), # ATmega88P/88PA
|
||||
(0x1E, 0x94, 0x03): (16384, 128, 512, False), # ATmega16/16A
|
||||
(0x1E, 0x94, 0x06): (16384, 128, 512, False), # ATmega168/168A
|
||||
(0x1E, 0x94, 0x0B): (16384, 128, 512, False), # ATmega168P/168PA
|
||||
(0x1E, 0x94, 0x0A): (16384, 128, 512, False), # ATmega164P/164PA
|
||||
(0x1E, 0x94, 0x0F): (16384, 128, 512, False), # ATmega164A
|
||||
(0x1E, 0x95, 0x02): (32768, 128, 1024, False), # ATmega32/32A
|
||||
(0x1E, 0x95, 0x0F): (32768, 128, 1024, False), # ATmega328P
|
||||
(0x1E, 0x95, 0x14): (32768, 128, 1024, False), # ATmega328
|
||||
(0x1E, 0x95, 0x08): (32768, 128, 1024, False), # ATmega324P
|
||||
(0x1E, 0x95, 0x11): (32768, 128, 1024, False), # ATmega324PA
|
||||
(0x1E, 0x95, 0x15): (32768, 128, 1024, False), # ATmega324A
|
||||
(0x1E, 0x96, 0x09): (65536, 256, 2048, False), # ATmega644/644A
|
||||
(0x1E, 0x96, 0x0A): (65536, 256, 2048, False), # ATmega644P/644PA
|
||||
(0x1E, 0x97, 0x05): (131072, 256, 4096, False),# ATmega1284P
|
||||
(0x1E, 0x97, 0x06): (131072, 256, 4096, False),# ATmega1284
|
||||
}
|
||||
|
||||
VERBOSE = False
|
||||
|
||||
|
||||
@@ -301,6 +339,28 @@ Port = WindowsPort if os.name == "nt" else PosixPort
|
||||
class Info:
|
||||
"""The 12-byte info block."""
|
||||
|
||||
@classmethod
|
||||
def from_slim(cls, raw):
|
||||
"""The autobaud loader's slimmed reply — version and signature only —
|
||||
with the rest of the geometry looked up from the signature (the loader
|
||||
derived it from the same chip facts at build time). Reconstructs the full
|
||||
block so every derived attribute matches the fixed-baud path exactly."""
|
||||
if len(raw) != 4:
|
||||
raise Error(f"bad slim info block: {raw.hex()}")
|
||||
version, signature = raw[0], tuple(raw[1:4])
|
||||
geometry = AUTOBAUD_GEOMETRY.get(signature)
|
||||
if geometry is None:
|
||||
sig = " ".join(f"{b:02x}" for b in signature)
|
||||
raise Error(f"unknown signature {sig} — this tool has no autobaud geometry for it")
|
||||
flash, page, eeprom, patch = geometry
|
||||
base = flash - SLOT
|
||||
word_flash = flash > 0x10000
|
||||
wire_base = base // 2 if word_flash else base
|
||||
flags = (1 if patch else 0) | (2 if word_flash else 0)
|
||||
raw12 = bytes((ord("P"), ord("B"), version, *signature, page & 0xFF,
|
||||
wire_base & 0xFF, wire_base >> 8, eeprom & 0xFF, eeprom >> 8, flags))
|
||||
return cls(raw12)
|
||||
|
||||
def __init__(self, raw):
|
||||
if len(raw) != 12 or raw[0:2] != b"PB":
|
||||
raise Error(f"bad info block: {raw.hex()}")
|
||||
@@ -396,6 +456,37 @@ class Loader:
|
||||
if time.monotonic() > deadline:
|
||||
raise Error("no answer — reset the device within its activation window")
|
||||
|
||||
def connect_autobaud(self, wait):
|
||||
"""The autobaud handshake. Instead of the p+b knock, the host sends the
|
||||
0xC0 calibration pulse — a single seven-bit-time low pulse at the host's
|
||||
chosen baud — which the loader times into its per-bit unit, then a single
|
||||
'p' knock the loader decodes at the rate it just measured. As with
|
||||
connect(), each attempt is the whole handshake, retried until the slim
|
||||
info block comes back or the window closes: a lost pulse or a knock that
|
||||
lands while the loader is mid-frame simply fails to answer, and the
|
||||
loader's measurement loop is back waiting for the next pulse."""
|
||||
deadline = time.monotonic() + wait
|
||||
knocks = 0
|
||||
while True:
|
||||
self.port.flush_input()
|
||||
self.port.write(bytes((CALIBRATE, ord("p"))))
|
||||
knocks += 1
|
||||
if PROMPT in self.port.read_available(0.4):
|
||||
while self.port.read_available(0.3):
|
||||
pass
|
||||
self.port.write(b"b")
|
||||
try:
|
||||
block = self.port.read_exact(4, 2.0)
|
||||
except Error:
|
||||
block = b""
|
||||
if len(block) == 4:
|
||||
self.info = Info.from_slim(block)
|
||||
self._expect_prompt()
|
||||
verbose(f"loader locked on knock {knocks}; slim info read")
|
||||
return self.info
|
||||
if time.monotonic() > deadline:
|
||||
raise Error("no answer — reset the device within its activation window")
|
||||
|
||||
def _expect_prompt(self, timeout=2.0):
|
||||
byte = self.port.read_exact(1, timeout)
|
||||
if byte != PROMPT:
|
||||
@@ -1049,6 +1140,9 @@ def main():
|
||||
parser.add_argument("--port", required=True, help="serial device: COM6, /dev/ttyUSB0, or a simavr pty")
|
||||
parser.add_argument("--baud", type=int, default=115200, help="115200 mega, 57600 tinies")
|
||||
parser.add_argument("--wait", type=float, default=30.0, help="seconds to keep knocking")
|
||||
parser.add_argument("--autobaud", action="store_true",
|
||||
help="drive an autobaud loader: send the 0xC0 calibration pulse and a single "
|
||||
"knock, and take geometry from the signature (no clock/baud baked in)")
|
||||
parser.add_argument("--info", action="store_true", help="print the device info block")
|
||||
parser.add_argument("--fuses", action="store_true", help="read the fuse and lock bytes")
|
||||
parser.add_argument("--update-loader", metavar="FILE", help="replace the loader with this pureboot binary")
|
||||
@@ -1086,7 +1180,7 @@ def main():
|
||||
verbose(f"{args.port}: {args.baud} Bd 8N1, DTR/RTS asserted")
|
||||
try:
|
||||
loader = Loader(port)
|
||||
info = loader.connect(args.wait)
|
||||
info = loader.connect_autobaud(args.wait) if args.autobaud else loader.connect(args.wait)
|
||||
if args.info:
|
||||
print("device:")
|
||||
for line in info.lines():
|
||||
|
||||
Reference in New Issue
Block a user