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:
@@ -66,16 +66,18 @@ function(add_image_outputs name)
|
||||
$<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.bin)
|
||||
endfunction()
|
||||
|
||||
# The TinySafeBoot protocol reimplemented on libavr in three variants that trade
|
||||
# The TinySafeBoot protocol reimplemented on libavr in variants that trade
|
||||
# clarity for size. Each links into the ATmega328P boot section (BOOTSZ selects
|
||||
# its size; BOOTRST vectors a reset to its base) with -nostartfiles — a polled
|
||||
# loader has no use for the crt or the vector table. The naked entry sits in
|
||||
# .vectors, laid first, and runs. The boot base is FLASHEND+1 minus the section
|
||||
# size; the linker section-start and the source's boot_bytes agree. tsb_app is
|
||||
# loader has no use for the crt or the vector table. The entry sits in
|
||||
# .vectors, laid first, and runs — avr::startup::entry on the policy tier,
|
||||
# the experiment tiers' own naked stubs elsewhere, each documented in its
|
||||
# source. The boot base is FLASHEND+1 minus the section size; the linker
|
||||
# section-start and the source's boot_bytes agree. tsb_app is
|
||||
# the application's reset vector, pinned to 0 here so the loaders jump to a
|
||||
# named function; --pmem-wrap-around lets relaxation turn that absolute jump
|
||||
# into the wrapped rjmp AVR's modulo-flash PC actually executes.
|
||||
# All three implement the full oracle feature set (see oracle/README.md):
|
||||
# All four implement the full oracle feature set (see oracle/README.md):
|
||||
# watchdog bail, one-wire half-duplex, config-page activation timeout, password
|
||||
# gate, emergency erase, config/flash/EEPROM read-write. They differ only in how,
|
||||
# and the size gradient is the cost of that "how" — see dev/lessons.md.
|
||||
@@ -170,6 +172,11 @@ if(PROJECT_IS_TOP_LEVEL)
|
||||
add_test(NAME pureboot.scan
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_scan.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
|
||||
# CMakePresets.json is generated; hand edits drift the moment the
|
||||
# generator reruns, so the gate holds the pair together.
|
||||
add_test(NAME presets.generated
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/make_presets.py
|
||||
--check)
|
||||
add_test(NAME pureboot.handshake
|
||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_handshake.py)
|
||||
add_test(NAME pureboot.updatelink
|
||||
@@ -509,9 +516,9 @@ if(PROJECT_IS_TOP_LEVEL)
|
||||
set_tests_properties(pureboot.usart1 PROPERTIES TIMEOUT 180)
|
||||
endif()
|
||||
|
||||
# The autobaud variants driven end to end over the software-UART bridge (both
|
||||
# under review — pureboot/autobaud.md): the host sends the 0xC0 calibration
|
||||
# pulse, the loader times it, locks, and programs. Run on the near-flash 328P
|
||||
# The autobaud loader driven end to end over the software-UART bridge:
|
||||
# the host sends the 0xC0 calibration pulse, the loader times it, locks,
|
||||
# and programs. Run on the near-flash 328P
|
||||
# and the word-addressed 1284P — the two flash-addressing classes — and each
|
||||
# at two clocks with the one binary, which is the clock-agnostic property
|
||||
# autobaud exists for (test/pbautobaud.py). The fixture application banners
|
||||
|
||||
2
libavr
2
libavr
Submodule libavr updated: 43b1f34ed1...d952c7e317
@@ -48,12 +48,14 @@ it carries the calibration machinery and no clock at all.
|
||||
is 510 bytes, since the slot's last word is the trampoline.
|
||||
|
||||
The tightest fit in the whole space is the 1284s' autobaud build deployed on a
|
||||
USART's own pins, 506 of its 512 — they alone carry the far-flash machinery
|
||||
(ELPM reads, RAMPZ page commands), autobaud alone carries the calibration loop,
|
||||
and a bit-banged link on a USART's pins alone has to release it (below). The
|
||||
same build on the default pins is 502. The flash bank riding in a transfer's
|
||||
selector byte keeps even those chips' addressing the same 16-bit form every
|
||||
other chip uses, which is why they are no longer the outlier they were.
|
||||
USART's own pins with the `OSCCAL` trim baked, 510 of its 512 — they alone
|
||||
carry the far-flash machinery (ELPM reads, RAMPZ page commands), autobaud
|
||||
alone carries the calibration loop, a bit-banged link on a USART's pins alone
|
||||
has to release it (below), and the trim adds its one register write. Without
|
||||
the trim that build is 504; on the default pins, 502. The flash bank riding
|
||||
in a transfer's selector byte keeps even those chips' addressing the same
|
||||
16-bit form every other chip uses, which is why they are no longer the
|
||||
outlier they were.
|
||||
|
||||
The software UART enables the RX pull-up; TX idles high. All multi-byte wire
|
||||
quantities are little-endian.
|
||||
@@ -123,12 +125,13 @@ window per reset. Measure with an application in place.
|
||||
A downstream project brings its usual libavr setup (the `libavr` target, the
|
||||
chip via the `LIBAVR_MCU` toolchain preset), consumes this directory, and
|
||||
states its deployment — an ATmega328P on its shipped 1 MHz fuses with the
|
||||
software UART on hand-picked pins, say:
|
||||
software UART on hand-picked pins, say. A submodule pins the loader version
|
||||
(the tags name them; this repo pins its own libavr the same way), where
|
||||
FetchContent tracks whatever `main` is:
|
||||
|
||||
```cmake
|
||||
FetchContent_Declare(bootloader GIT_REPOSITORY git@git.blackmark.me:avr/bootloader.git GIT_TAG main)
|
||||
FetchContent_MakeAvailable(bootloader)
|
||||
add_subdirectory(${bootloader_SOURCE_DIR}/pureboot pureboot)
|
||||
# git submodule add <forge>/avr/bootloader.git bootloader — or FetchContent
|
||||
add_subdirectory(bootloader/pureboot pureboot)
|
||||
|
||||
pureboot_add_loader(myboot CLOCK 1000000 SERIAL software TX pb1 RX pb5)
|
||||
```
|
||||
@@ -512,7 +515,12 @@ Per chip preset, `ctest` runs:
|
||||
too. The timeout is a constant and is no axis;
|
||||
- `pureboot_autobaud.size` — the clock-free build, which has no clock or baud
|
||||
axis of its own: one binary per chip has to serve every point the matrix
|
||||
below sweeps;
|
||||
below sweeps. `pureboot*osccal*.size` add the `OSCCAL` trim on the stock
|
||||
shape and on the tightest image in the space (autobaud on a USART's own
|
||||
pins), holding both of the trim write's addressing encodings to the budget;
|
||||
- `pureboot_autobaud.unit` — the measured bit period is the loader's only RAM
|
||||
object and sits exactly at ram_start, where `--info` reads it: wire
|
||||
contract, not layout accident;
|
||||
- `pbm_*.size` — with `PUREBOOT_FULL_MATRIX=1`, the exhaustive cross product
|
||||
replacing that compact matrix, on **every** chip: every plausible oscillator
|
||||
(the internal ones, the CKDIV8 floor, the plain and the UART crystals) ×
|
||||
@@ -535,6 +543,13 @@ Per chip preset, `ctest` runs:
|
||||
recovery properties, the surgery, the staging composition, the boot-fuse
|
||||
decode, the update preflight over synthetic fuse bytes, and the repairing
|
||||
verify against a fake device;
|
||||
- `pureboot.scan` — `--scan`'s walk and report logic: the probe order, the
|
||||
rate arithmetic, and the trim advice's direction. A pty carries bytes at
|
||||
any termios rate, so the rate physics itself belongs to the hardware
|
||||
harness, and what the wire would arbitrate is pinned as logic;
|
||||
- `presets.generated` — CMakePresets.json matches its generator
|
||||
(`tools/make_presets.py --check`), so a hand edit or a generator change
|
||||
cannot drift the pair apart;
|
||||
- `pureboot.protocol` — end to end against a simavr device
|
||||
(`test/pureboot_device.c`: a hardware USART as a pty, or a cycle-timed
|
||||
GPIO⇄pty bridge for a software-UART build, plus the SPM/NVM module simavr's
|
||||
@@ -567,15 +582,21 @@ Per chip preset, `ctest` runs:
|
||||
- `pureboot.update` — the full `--update-loader` flow, then every power-fail
|
||||
phase: the device is killed mid-write, restarted from its flash dump, and a
|
||||
re-run must complete the update with the application intact;
|
||||
- `pureboot.osccal` (328P, t85) — a loader built with the `OSCCAL` axis holds
|
||||
the trim register at the built byte from its first prompt, observed through
|
||||
the wire on one chip per addressing encoding (`sts` and low-I/O `out`);
|
||||
- `pureboot.autobaud` (328P, 1284P) — the clock-free build over the GPIO⇄pty
|
||||
bridge: the calibration handshake, a flash + EEPROM + fuse round trip against
|
||||
the simulator's own memory, a data-space round trip, the hand-over — then the
|
||||
same binary again at double the clock, which is the property the backend
|
||||
exists for. A lone calibration pulse with no knock behind it must still let
|
||||
exists for. The measured clock `--info` prints is asserted against the
|
||||
simulator's exact clock, inside the unit encoding's own envelope, at both
|
||||
points. A lone calibration pulse with no knock behind it must still let
|
||||
the application boot, so no wait in activation can be unbounded.
|
||||
|
||||
`size`, `pi`, `planner` and `handshake` are host logic and run anywhere; the
|
||||
simulator-driven targets need simavr and a pty, so they are POSIX-only.
|
||||
`size`, `unit`, `pi`, `planner`, `scan` and `handshake` are host logic and run
|
||||
anywhere; the simulator-driven targets need simavr and a pty, so they are
|
||||
POSIX-only.
|
||||
|
||||
## Hardware
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -193,10 +193,11 @@ static avr_cycle_count_t tx_sample(avr_t *mcu, avr_cycle_count_t when, void *par
|
||||
tx_shift = (uint8_t)((tx_shift >> 1) | (tx_level ? 0x80 : 0));
|
||||
if (++tx_bit < 8)
|
||||
return when + bit_cycles;
|
||||
/* The byte is not delivered until its stop bit has passed. A real
|
||||
* receiver cannot answer sooner, and a host that did would put its
|
||||
* start bit on the wire while the device is still driving the stop
|
||||
* bit — which the device, transmitting, is not watching for. */
|
||||
/* The byte is delivered at the stop bit's sampling point (9.5 bit
|
||||
* times), where a hardware receiver raises its RXC — not sooner: a
|
||||
* host answering before the stop bit would put its start bit on the
|
||||
* wire while the device is still driving, which the device,
|
||||
* transmitting, is not watching for. */
|
||||
return when + bit_cycles;
|
||||
}
|
||||
if (write(pty_master, &tx_shift, 1) != 1)
|
||||
@@ -412,6 +413,14 @@ int main(int argc, char *argv[])
|
||||
fprintf(stderr, "device: cannot read %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
// An image past flash end would smash the simulator's heap and turn
|
||||
// into phantom peripheral behavior (lessons: believe the size gate
|
||||
// first) — refuse it loudly instead.
|
||||
if (base + fw.flashsize > avr->flashend + 1) {
|
||||
fprintf(stderr, "device: %u B at 0x%x runs past flash end 0x%x — image does not fit its slot\n",
|
||||
(unsigned)fw.flashsize, base, avr->flashend);
|
||||
return 1;
|
||||
}
|
||||
memcpy(avr->flash + base, fw.flash, fw.flashsize);
|
||||
}
|
||||
// The boot-sectioned megas enter the loader in hardware (BOOTRST, not
|
||||
|
||||
@@ -46,6 +46,24 @@ def main():
|
||||
if "9984000" not in report:
|
||||
fail(f"the absolute clock must scale with the found ratio:\n{report}")
|
||||
|
||||
# The walk's rates mostly have no termios B-constant, so the POSIX port
|
||||
# must set them through termios2 — probed on a pty, which accepts the
|
||||
# ioctl without caring about the speed. Without this every off-nominal
|
||||
# probe would abort the walk on the platform --scan matters most on.
|
||||
if os.name == "posix":
|
||||
import pty
|
||||
|
||||
master, slave = pty.openpty()
|
||||
try:
|
||||
port = pb.Port(os.ttyname(slave), pb.scan_rate(9600, 4))
|
||||
port.set_baud(pb.scan_rate(9600, -4))
|
||||
port.close()
|
||||
except pb.Error as error:
|
||||
fail(f"PosixPort refused an off-nominal probe rate: {error}")
|
||||
finally:
|
||||
os.close(master)
|
||||
os.close(slave)
|
||||
|
||||
print("OK")
|
||||
|
||||
|
||||
|
||||
@@ -7,11 +7,14 @@ port's TUs compile identically; the sims prove nothing new there) exist for
|
||||
libavr's reflect spot set only, mirroring its rule: the full reflect matrix
|
||||
is never built, one chip per hardware class and pack vintage is.
|
||||
|
||||
Run from the repo root: tools/make_presets.py
|
||||
Run from the repo root: tools/make_presets.py — or with --check, which
|
||||
verifies the committed file matches this generator and edits nothing (the
|
||||
ctest entry `presets.generated` runs that, so drift reds the gate).
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
CHIPS = [
|
||||
"attiny13", "attiny13a", "attiny25", "attiny45", "attiny85",
|
||||
@@ -41,7 +44,7 @@ def main():
|
||||
"hidden": True,
|
||||
"generator": "Ninja",
|
||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||
"toolchainFile": "$env{LIBAVR_ROOT}/cmake/avr-toolchain.cmake",
|
||||
"toolchainFile": "${sourceDir}/libavr/cmake/avr-toolchain.cmake",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||
@@ -72,6 +75,9 @@ def main():
|
||||
for chip in REFLECT_SPOT:
|
||||
add(chip, "reflect")
|
||||
|
||||
# CMake rejects unknown fields in the presets root, $comment included, so
|
||||
# the file cannot carry a generated-file marker; the --check ctest is the
|
||||
# whole of rule 10's guard here.
|
||||
presets = {
|
||||
"version": 8,
|
||||
"configurePresets": configure,
|
||||
@@ -79,12 +85,19 @@ def main():
|
||||
"testPresets": test,
|
||||
"workflowPresets": workflows,
|
||||
}
|
||||
rendered = json.dumps(presets, indent=1) + "\n"
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "CMakePresets.json")
|
||||
if "--check" in sys.argv[1:]:
|
||||
current = open(path).read() if os.path.exists(path) else ""
|
||||
if current != rendered:
|
||||
print("CMakePresets.json does not match its generator — run tools/make_presets.py")
|
||||
return 1
|
||||
return 0
|
||||
with open(path, "w") as f:
|
||||
json.dump(presets, f, indent=1)
|
||||
f.write("\n")
|
||||
f.write(rendered)
|
||||
print(f"{len(CHIPS)} chips, {len(REFLECT_SPOT)} reflect: {os.path.normpath(path)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
||||
@@ -72,6 +72,35 @@ class Suite:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def scan(self) -> None:
|
||||
"""The --scan walk against real termios and a real oscillator: every
|
||||
probe rate must open a port (the off-nominal rates exist only through
|
||||
termios2), and one probe must answer — the nominal on a healthy board,
|
||||
a neighbor on a drifted one. The rig injects the one reset per probe
|
||||
the operator supplies in the field; this is the rate physics the
|
||||
simulator cannot arbitrate (a pty carries bytes at any rate), pinned
|
||||
on silicon."""
|
||||
module = pbrig.load_pureboot(self.rig.d.pureboot)
|
||||
found = None
|
||||
for pct in module.scan_ratios():
|
||||
rate = module.scan_rate(self.rig.d.baud, pct)
|
||||
self.rig.reset()
|
||||
try:
|
||||
port = module.Port(self.rig.d.port, rate)
|
||||
except module.Error as error:
|
||||
self.check("scan opens every probe rate", False, f"{rate} Bd: {error}")
|
||||
return
|
||||
try:
|
||||
module.Loader(port).connect(min(self.rig.d.wait, 6.0))
|
||||
found = pct
|
||||
break
|
||||
except module.Error:
|
||||
continue
|
||||
finally:
|
||||
port.close()
|
||||
self.check("scan finds the board's rate", found is not None,
|
||||
"no probe answered" if found is None else f"{found:+d} % of {self.rig.d.baud} Bd")
|
||||
|
||||
def eeprom(self, info) -> None:
|
||||
size = info.eeprom_size
|
||||
if not size:
|
||||
@@ -161,6 +190,10 @@ class Suite:
|
||||
print("\nthe loader never answered; nothing below can be trusted")
|
||||
return 1
|
||||
|
||||
if not self.rig.d.autobaud:
|
||||
print("\nscan")
|
||||
self.scan()
|
||||
|
||||
print("\nEEPROM")
|
||||
self.eeprom(info)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user