build: the libavr pin advances past phase 6

The renames land (interrupt_guard, consume_reset_cause, set_duty), the
sampler binds its input in the new converter shape (the input pack plus
in<>::start() as free-running's one kick), and the console states
.allow_baud_error = true for the 115200-at-16-MHz this board has always
spoken - the receiver-tolerance table libavr now enforces is stricter
than the rate's own +2.1 %. The loader probe reads through
avr::flash_load instead of raw pgmspace, the terminal's line buffer is
std::array with backspace and delete named, the tree is reformatted
under InsertBraces, and the sources are ASCII.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-09 11:45:07 +02:00
parent 0e9060db69
commit b1caf49522
13 changed files with 161 additions and 113 deletions

View File

@@ -1,6 +1,6 @@
# The board has no reset line and no programming header, so the loader-entry
# route in the emitted image is the only thing standing between a firmware change
# and an unreflashable board. It has been wrong before see the script.
# and an unreflashable board. It has been wrong before - see the script.
find_package(Python3 COMPONENTS Interpreter)
if(Python3_FOUND)
add_test(NAME fantemp.reachability
@@ -8,5 +8,5 @@ if(Python3_FOUND)
--objdump ${CMAKE_OBJDUMP} --elf $<TARGET_FILE:fantemp>
--image $<TARGET_FILE_DIR:fantemp>/fantemp.bin)
else()
message(STATUS "Python not found the reachability check is skipped")
message(STATUS "Python not found - the reachability check is skipped")
endif()

View File

@@ -3,7 +3,7 @@
This board has no reset line and no programming header. The single route to the
bootloader is the running firmware's `bootloader` command, so a firmware that
gets that route wrong is a board that cannot be reflashed and the failure is
gets that route wrong is a board that cannot be reflashed - and the failure is
silent, because everything else still works.
It has been wrong before. The firmware this one replaces probed and jumped to
@@ -15,7 +15,7 @@ hardware, which is what this replaces.
Three properties, all read out of the disassembly rather than the source:
1. The image ends below the boot section. `hfuse d4` puts that at 0x7c00, so an
application reaching into it would be overwritten by the loader or worse,
application reaching into it would be overwritten by the loader - or worse,
executed at reset, since BOOTRST points there.
2. The hand-over targets the loader base. A word address of 0x3f00 is byte
0x7e00; anything else is the 0x7800 bug again.
@@ -60,7 +60,7 @@ def main() -> int:
text = subprocess.run([args.objdump, "-d", str(args.elf)],
capture_output=True, text=True, check=True).stdout
# The address the hand-over actually targets, read at its call sites not
# The address the hand-over actually targets, read at its call sites - not
# "does the image contain this byte somewhere", which proves nothing: 0x3f is
# an ordinary constant that appears in the curve tables, so a check like that
# passes just as happily on the 0x7800 bug it is supposed to catch.
@@ -83,7 +83,7 @@ def main() -> int:
want = LOADER_BASE // 2
if not sites:
failures.append("no call to bootloader::call with a loaded target the "
failures.append("no call to bootloader::call with a loaded target - the "
"hand-over could not be read out of the image")
elif wrong := [a for a in sites if a != want]:
failures.append(f"the hand-over targets word {[hex(a) for a in wrong]} "
@@ -95,13 +95,13 @@ def main() -> int:
# An icall/ijmp has to exist for that address to be jumped to indirectly.
if not re.search(r"\b(icall|ijmp)\b", text):
failures.append("no icall/ijmp the hand-over cannot reach across flash")
failures.append("no icall/ijmp - the hand-over cannot reach across flash")
else:
print(" ok an indirect call exists (a relative one cannot reach)")
# What actually reaches WDTCSR, not what the image happens to load somewhere.
# A timed disable writes WDCE|WDE (0x18) and then zero. Arming writes WDE
# *without* WDCE including 0x08, a 16 ms timeout with every prescaler bit
# *without* WDCE - including 0x08, a 16 ms timeout with every prescaler bit
# clear, which is precisely what the legacy route used and is why this cannot
# be a check for "a prescaler is present".
WDCE, WDE = 0x10, 0x08
@@ -114,14 +114,14 @@ def main() -> int:
values.append(0 if reg == "r1" else held.get(reg))
armed = [v for v in values if v is not None and (v & WDE) and not (v & WDCE)]
if armed:
failures.append(f"WDTCSR is written {[hex(v) for v in armed]} WDE without "
failures.append(f"WDTCSR is written {[hex(v) for v in armed]} - WDE without "
f"WDCE is arming the watchdog, and a reset-based hand-over "
f"opens no pureboot window")
elif not values:
print(" ok the watchdog is never written")
else:
print(f" ok WDTCSR writes are {[hex(v) if v is not None else '?' for v in values]}"
f" unlock and clear, never an arm")
f" - unlock and clear, never an arm")
for line in failures:
print(f" FAIL {line}")