From f50032ee4639a020a0491dec3afd35511f4b4ac3 Mon Sep 17 00:00:00 2001 From: BlackMark Date: Fri, 23 Oct 2026 21:41:55 +0200 Subject: [PATCH] build: presets.generated runs, and the tier list lives in the sources make_presets.py documented a presets.generated test that was never registered, so a hand edit to CMakePresets.json could drift unseen; it is registered now, and the generator names its encoding and line ending so a regeneration on Windows writes the same file. The CMake comment's per-tier essay repeated each source's own header - and claimed naked stubs where all four use startup::entry - so it points there instead, and the flash size the boot base and the wrap-around both follow from is named once. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 61 ++++++++++++++++--------------------------- tools/make_presets.py | 4 +-- 2 files changed, 24 insertions(+), 41 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c6339c..ffe2337 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,9 +34,8 @@ if(PROJECT_IS_TOP_LEVEL) # distribution's compiler speaks in full). # # **A host that cannot build it registers those tests anyway and skips - # them.** They used to be left out, which makes the suite a different size - # on a different machine - and a suite whose size is a property of the - # machine is one nothing can be compared against. + # them**: leaving them out would make the suite's size a property of the + # machine, and such a suite is one nothing can be compared against. set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device) find_program(_host_cxx NAMES c++ g++) set(_tsb_absent "${LIBAVR_NO_PYTHON}") @@ -66,6 +65,11 @@ if(PROJECT_IS_TOP_LEVEL) if(_tsb_absent) message(STATUS "the protocol tests skip here - ${_tsb_absent}") endif() + + # CMakePresets.json is generated; the gate holds it to its generator. + libavr_launcher(_presets_python "${LIBAVR_NO_PYTHON}" ${Python3_EXECUTABLE}) + add_test(NAME presets.generated + COMMAND ${_presets_python} ${CMAKE_CURRENT_SOURCE_DIR}/tools/make_presets.py --check) endif() # The ELF is only a container (symbols, section headers) and is never flashed - @@ -82,48 +86,27 @@ function(add_image_outputs name) $ $.bin) endfunction() -# 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 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 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". -# tsb_asm - the tricks tier's C++ with exactly two routines in asm: the -# bounded rx and the page-store loop, the two whose remaining -# cost is the C ABI itself. Everything else, bring-up to -# dispatch, is C++ on libavr. -# tsb_tricks - no asm at all: the whole-loader register allocation lives in -# global register variables (Y walks the page pointer), every -# helper is a tiny noinline primitive placed by the -# global-register store rules, pages stream straight to -# SPM/EEPROM. -# tsb_pure - pure idiomatic libavr, one function per command, TU-local -# (internal linkage), streaming (no SRAM page buffer). -# tsb_policy - the policy floor: no inline assembly and no global register -# variables, which is philosophy #5's own bound, and the -# measured evidence that the 512 B fit is a property of the -# mechanisms it bans. -# -# What each measures is oracle/README.md's table, which is the one place the -# four numbers and the hand-written loader's own are compared. -# +# The TinySafeBoot protocol reimplemented on libavr in four tiers that trade +# clarity for size, each with the full oracle feature set; each source's header +# says what its tier is allowed, and oracle/README.md compares what they +# measure. 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 - and its entry sits in +# .vectors, laid first. The boot base is the flash size 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. +set(TSB_FLASH_KIB 32) + # add_tsb_variant( ) function(add_tsb_variant name bytes) - math(EXPR base_dec "32768 - ${bytes}") + math(EXPR base_dec "${TSB_FLASH_KIB} * 1024 - ${bytes}") math(EXPR base_hex "${base_dec}" OUTPUT_FORMAT HEXADECIMAL) add_executable(${name} tsb/${name}.cpp) target_link_libraries(${name} PRIVATE libavr) target_link_options(${name} PRIVATE -nostartfiles -Wl,--section-start=.text=${base_hex} - -Wl,--defsym=tsb_app=0 -Wl,--pmem-wrap-around=32k) + -Wl,--defsym=tsb_app=0 -Wl,--pmem-wrap-around=${TSB_FLASH_KIB}k) add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $) add_image_outputs(${name}) if(PROJECT_IS_TOP_LEVEL) diff --git a/tools/make_presets.py b/tools/make_presets.py index 6118184..811b648 100755 --- a/tools/make_presets.py +++ b/tools/make_presets.py @@ -75,12 +75,12 @@ def main(): 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 "" + current = open(path, encoding="utf-8").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: + with open(path, "w", encoding="utf-8", newline="\n") as f: f.write(rendered) print(f"{len(CHIPS)} chips, {len(REFLECT_SPOT)} reflect: {os.path.normpath(path)}") return 0