From 491ff76447c044ae1476bb2a80b900d305f06a66 Mon Sep 17 00:00:00 2001 From: BlackMark Date: Sun, 23 Aug 2026 22:38:19 +0200 Subject: [PATCH] build: pureboot rides as a pinned submodule, and the libavr pin advances pureboot moved to its own repo, so the loader this board hands over to stopped being reachable through a sibling checkout of the bootloader repo. It is a submodule here now: this board has no reset line and no programming header, so the resident loader is the only way in, and the commit naming the firmware should name the loader it has to reach. The reachability check stops carrying its own copy of where that loader is. 0x7e00 was a literal beside pureboot's own geometry, which the submodule exports as PUREBOOT_BASE_HEX - one source for the fact now, and the check reads whichever slot the pinned loader actually has. The boot-section bound stays a literal, being a fuse fact rather than a loader one. Built and tested at both pins on the bench: 5/5, cross-mode identity included, and the image deployed to the board verifies byte-for-byte through its loader. Co-Authored-By: Claude Opus 5 --- .gitmodules | 3 +++ CMakeLists.txt | 9 +++++++++ libavr | 2 +- pureboot | 1 + test/CMakeLists.txt | 3 ++- test/check_reachability.py | 14 +++++++++----- 6 files changed, 25 insertions(+), 7 deletions(-) create mode 160000 pureboot diff --git a/.gitmodules b/.gitmodules index 1c0fdfd..465203c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libavr"] path = libavr url = ../libavr.git +[submodule "pureboot"] + path = pureboot + url = ../pureboot.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a5a0ef..d9e7c46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,16 @@ endif() if(NOT EXISTS ${LIBAVR_ROOT}/CMakeLists.txt) message(FATAL_ERROR "libavr not found at ${LIBAVR_ROOT} - run: git submodule update --init libavr") endif() +# pureboot rides as a pinned submodule too: this board's only way in is its +# resident loader, so the commit that names this firmware names the loader it +# has to hand over to. Consumed for the geometry it exports - the loader links +# the libavr target above, so pureboot's own libavr submodule stays +# uninitialised. +if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/CMakeLists.txt) + message(FATAL_ERROR "pureboot not found - run: git submodule update --init pureboot") +endif() add_subdirectory(${LIBAVR_ROOT} libavr-build) +add_subdirectory(pureboot pureboot-build) include(${LIBAVR_ROOT}/cmake/checks.cmake) add_executable(fantemp src/main.cpp) diff --git a/libavr b/libavr index e697920..3678ed7 160000 --- a/libavr +++ b/libavr @@ -1 +1 @@ -Subproject commit e69792013ec53f47defed956fdb93b2ba8c1cc64 +Subproject commit 3678ed7e5bb90f16021390fa3f877e9d1d2129db diff --git a/pureboot b/pureboot new file mode 160000 index 0000000..416ee18 --- /dev/null +++ b/pureboot @@ -0,0 +1 @@ +Subproject commit 416ee188e1724e9b871722d279be9b5eedd4b653 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aff37b5..10dceb6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,7 +25,8 @@ if(Python3_FOUND) add_test(NAME fantemp.reachability COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/check_reachability.py --objdump ${CMAKE_OBJDUMP} --elf $ - --image $/fantemp.bin) + --image $/fantemp.bin + --loader-base ${PUREBOOT_BASE_HEX}) else() message(STATUS "Python not found - the reachability check is skipped") endif() diff --git a/test/check_reachability.py b/test/check_reachability.py index 44b57a4..7ec8b31 100644 --- a/test/check_reachability.py +++ b/test/check_reachability.py @@ -35,8 +35,7 @@ import re import subprocess import sys -BOOT_SECTION = 0x7C00 # hfuse d4: BOOTSZ 512 words -LOADER_BASE = 0x7E00 # pureboot's 512-byte slot, at the top +BOOT_SECTION = 0x7C00 # hfuse d4: BOOTSZ 512 words - a fuse fact, not a loader one WDTCSR = 0x60 @@ -45,7 +44,12 @@ def main() -> int: parser.add_argument("--objdump", required=True) parser.add_argument("--elf", type=pathlib.Path, required=True) parser.add_argument("--image", type=pathlib.Path, required=True) + # pureboot's own geometry, from the pinned submodule that exports it, so the + # slot's base is stated once for the loader this firmware is deployed with. + parser.add_argument("--loader-base", required=True, + type=lambda v: int(v, 0), metavar="ADDR") args = parser.parse_args() + loader_base = args.loader_base failures = [] @@ -81,17 +85,17 @@ def main() -> int: if "r24" in held and "r25" in held: sites.append(held["r25"] << 8 | held["r24"]) - want = LOADER_BASE // 2 + want = loader_base // 2 if not sites: 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]} " f"(byte {[hex(a * 2) for a in wrong]}), not the loader at " - f"0x{LOADER_BASE:04x}") + f"0x{loader_base:04x}") else: print(f" ok all {len(sites)} hand-over site(s) target word 0x{want:04x} " - f"(byte 0x{LOADER_BASE:04x})") + f"(byte 0x{loader_base:04x})") # An icall/ijmp has to exist for that address to be jumped to indirectly. if not re.search(r"\b(icall|ijmp)\b", text):