audit: the port's pass — the scan that could not walk, and the drift a generator ends
--scan's walk was unwalkable on POSIX: probe rates have no termios B-constant, so the first off-nominal probe raised out of the loop. The port speaks termios2 BOTHER now (red-proven on a pty at 9984 Bd), the probe's open lives inside the walk's error handling, an fd no longer leaks on an unmakeable rate, and the swallowed unknown-signature reply is named at timeout instead of reported as silence. CMakePresets.json's generator emits the submodule toolchain path it had drifted from — a hand edit on a generated file, exactly the class rule 10 exists for — and presets.generated gates the pair from here on (the marker CMake rejects at the presets root stayed out; the check is the guard). The over-slot image guard the tsb runner gained reaches the pureboot runner too; the GPIO bridge's delivery comment states the hardware truth (RXC at the stop bit's sampling point); the hardware suite gains the scan check — the one place the rate physics is real; and the libavr pin advances over both audit rounds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,8 @@ if os.name == "nt":
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
else:
|
||||
import array
|
||||
import fcntl
|
||||
import select
|
||||
import termios
|
||||
|
||||
@@ -158,36 +160,60 @@ class Progress:
|
||||
|
||||
|
||||
class PosixPort:
|
||||
"""A raw serial port with deadline-based reads, over termios."""
|
||||
"""A raw serial port with deadline-based reads, over termios. A rate with
|
||||
no B-constant — the off-nominal probes `--scan` walks — goes through
|
||||
Linux's termios2 BOTHER; a platform without that ioctl refuses the rate
|
||||
by name."""
|
||||
|
||||
# The termios2 ioctl pair and cflag bits, and the struct's ispeed/ospeed
|
||||
# word offsets: four flag words, then a line-discipline byte and 19
|
||||
# control chars padded to word 9 (include/uapi/asm-generic/termbits.h).
|
||||
_TCGETS2, _TCSETS2 = 0x802C542A, 0x402C542B
|
||||
_BOTHER, _CBAUD = 0o010000, 0o010017
|
||||
_ISPEED, _OSPEED = 9, 10
|
||||
|
||||
@staticmethod
|
||||
def _speed(baud):
|
||||
return getattr(termios, f"B{baud}", None)
|
||||
|
||||
def _set_arbitrary(self, baud):
|
||||
buf = array.array("i", [0] * (self._OSPEED + 1))
|
||||
try:
|
||||
return getattr(termios, f"B{baud}")
|
||||
except AttributeError:
|
||||
raise Error(f"unsupported baud rate {baud}") from None
|
||||
fcntl.ioctl(self.fd, self._TCGETS2, buf, True)
|
||||
buf[2] = (buf[2] & ~self._CBAUD) | self._BOTHER
|
||||
buf[self._ISPEED] = buf[self._OSPEED] = baud
|
||||
fcntl.ioctl(self.fd, self._TCSETS2, buf)
|
||||
except OSError:
|
||||
raise Error(f"this platform cannot set {baud} Bd (no termios2)") from None
|
||||
|
||||
def _apply_baud(self, attrs, baud):
|
||||
speed = self._speed(baud)
|
||||
attrs[4] = attrs[5] = speed if speed is not None else termios.B38400
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
|
||||
if speed is None:
|
||||
self._set_arbitrary(baud)
|
||||
self.baud = baud
|
||||
|
||||
def __init__(self, path, baud):
|
||||
self.fd = os.open(path, os.O_RDWR | os.O_NOCTTY)
|
||||
attrs = termios.tcgetattr(self.fd)
|
||||
attrs[0] = 0 # iflag
|
||||
attrs[1] = 0 # oflag
|
||||
attrs[2] = termios.CREAD | termios.CLOCAL | termios.CS8 # cflag
|
||||
attrs[3] = 0 # lflag
|
||||
attrs[4] = attrs[5] = self._speed(baud)
|
||||
attrs[6][termios.VMIN] = 0
|
||||
attrs[6][termios.VTIME] = 0
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
|
||||
self.baud = baud
|
||||
try:
|
||||
attrs = termios.tcgetattr(self.fd)
|
||||
attrs[0] = 0 # iflag
|
||||
attrs[1] = 0 # oflag
|
||||
attrs[2] = termios.CREAD | termios.CLOCAL | termios.CS8 # cflag
|
||||
attrs[3] = 0 # lflag
|
||||
attrs[6][termios.VMIN] = 0
|
||||
attrs[6][termios.VTIME] = 0
|
||||
self._apply_baud(attrs, baud)
|
||||
except BaseException:
|
||||
os.close(self.fd)
|
||||
raise
|
||||
|
||||
def set_baud(self, baud):
|
||||
"""Retune the port without closing it — the fd stays open, so no DTR
|
||||
pulse and no reset. That matters: the only caller is mid-session with a
|
||||
loader copy that a reset would throw away."""
|
||||
attrs = termios.tcgetattr(self.fd)
|
||||
attrs[4] = attrs[5] = self._speed(baud)
|
||||
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
|
||||
self.baud = baud
|
||||
self._apply_baud(termios.tcgetattr(self.fd), baud)
|
||||
|
||||
def close(self):
|
||||
os.close(self.fd)
|
||||
@@ -531,6 +557,7 @@ class Loader:
|
||||
drain absorbs whatever they produced."""
|
||||
deadline = time.monotonic() + wait
|
||||
knocks = 0
|
||||
refusal = None
|
||||
while True:
|
||||
self.port.flush_input()
|
||||
self.port.write(knock)
|
||||
@@ -552,12 +579,18 @@ class Loader:
|
||||
except Error as failed:
|
||||
if "pureboot" in str(failed):
|
||||
raise
|
||||
# A malformed or unknown identity is retried as noise, but
|
||||
# it was an answer: if nothing better ever arrives, naming
|
||||
# it beats reporting silence.
|
||||
refusal = failed
|
||||
self.info = None
|
||||
if self.info is not None:
|
||||
self._expect_prompt()
|
||||
verbose(f"loader answered {what} {knocks}; identity read")
|
||||
return self.info
|
||||
if time.monotonic() > deadline:
|
||||
if refusal is not None:
|
||||
raise Error(f"no usable answer — the last identity reply failed: {refusal}")
|
||||
raise Error("no answer — reset the device within its activation window")
|
||||
|
||||
def connect(self, wait):
|
||||
@@ -1408,7 +1441,11 @@ def op_scan(port_path, baud, wait, clock=None):
|
||||
for pct in scan_ratios():
|
||||
rate = scan_rate(baud, pct)
|
||||
print(f"scan: {rate} Bd ({pct:+d} %) — reset the target", flush=True)
|
||||
port = Port(port_path, rate)
|
||||
try:
|
||||
port = Port(port_path, rate)
|
||||
except Error as unmakeable:
|
||||
print(f"scan: {rate} Bd skipped — {unmakeable}")
|
||||
continue
|
||||
try:
|
||||
info = Loader(port).connect(wait)
|
||||
except Error:
|
||||
@@ -1504,11 +1541,13 @@ def main():
|
||||
print("device:")
|
||||
for line in info.lines():
|
||||
print(f" {line}")
|
||||
if args.autobaud:
|
||||
if args.autobaud and info.ram is not None:
|
||||
# The whole of the loader's RAM is the measured bit period at
|
||||
# ram_start; decoded and times the rate this session drives,
|
||||
# that is the true clock — the number to hold an OSCCAL bake
|
||||
# or a fixed-baud build against (README.md: deployment).
|
||||
# or a fixed-baud build against (README.md: deployment). The
|
||||
# autobaud identity path refuses unknown signatures, so ram is
|
||||
# always known here; the guard states that dependency.
|
||||
unit = int.from_bytes(loader.read_ram(info.ram, 2), "little")
|
||||
cycles = unit * UNIT_LOOP_CYCLES + UNIT_DISCOUNT
|
||||
clock = cycles * args.baud
|
||||
|
||||
Reference in New Issue
Block a user