pureboot is at git.blackmark.me/avr/pureboot now, with its own history: the files it kept in pureboot/ sit at the top level there, its CMakeLists is the merged whole of that unit and the build around it, and the v1..v8 tags moved with it - each one checks out and compiles to exactly the .text it compiled to here. The loader that repo builds is byte-identical to the one this commit removes, verified before the removal rather than after. Nothing is rewritten on this side. The history and the tags are untouched, so every commit before this one still has pureboot in it and still builds it; this is one commit that stops carrying it forward. What goes with it: the four pureboot files, the thirteen pb*.py protocol drivers and their four host-side unit tests, pbapp and the pureboot simavr runner, check_pi.py, pbhw.py, pbrig.py, sizes.py, and the Studio project. check_unit.cmake goes too - it had no caller left once the unit tests moved. What is left is the four TinySafeBoot tiers, and the build shrinks to fit them: 757 lines of CMakeLists to 137, and the preset matrix from 37 chips to one, because the tiers reimplement an ATmega328P-only protocol and every other chip in that list was there for pureboot. check.sh loses its size-table pass - the table it checked was pureboot's README - and the Studio solution loses the project that is now in the other repo. Gate green: nine tests, four tiers at their section sizes and each one's protocol suite against the simulator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
138 lines
6.7 KiB
CMake
138 lines
6.7 KiB
CMake
cmake_minimum_required(VERSION 3.28)
|
|
|
|
project(tsb_libavr LANGUAGES CXX)
|
|
|
|
# libavr rides as the pinned submodule; LIBAVR_ROOT (cache or environment)
|
|
# overrides it for tandem development against a working tree. The toolchain
|
|
# file comes from the submodule via CMakePresets.json either way.
|
|
if(NOT LIBAVR_ROOT AND DEFINED ENV{LIBAVR_ROOT})
|
|
set(LIBAVR_ROOT $ENV{LIBAVR_ROOT})
|
|
endif()
|
|
if(NOT LIBAVR_ROOT)
|
|
set(LIBAVR_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/libavr)
|
|
endif()
|
|
if(NOT EXISTS ${LIBAVR_ROOT}/CMakeLists.txt)
|
|
message(FATAL_ERROR "libavr not found at ${LIBAVR_ROOT} - run: git submodule update --init libavr")
|
|
endif()
|
|
add_subdirectory(${LIBAVR_ROOT} libavr-build)
|
|
|
|
include(${LIBAVR_ROOT}/cmake/checks.cmake)
|
|
|
|
if(PROJECT_IS_TOP_LEVEL)
|
|
add_compile_options(-Werror) # warnings are errors for the port's own code
|
|
enable_testing()
|
|
|
|
# Rules 11 and 33 over this repo's own sources. The oracle's assembly needs
|
|
# no exclusion: it is neither formatted nor ASCII-checked, being in neither
|
|
# glob, which is the right answer for a vendored reference whose text is
|
|
# the artifact.
|
|
libavr_format_test()
|
|
|
|
# The behavioral tests drive the real wire protocols over a simavr pty
|
|
# (as the host tools do) and actually flash the device. The runners are
|
|
# host programs built at configure time against libsimavr (C++23 - what
|
|
# the distribution's compiler speaks in full); if they or Python are
|
|
# missing, only the size tests run.
|
|
find_program(_host_cxx NAMES c++ g++)
|
|
find_package(Python3 COMPONENTS Interpreter)
|
|
if(_host_cxx AND Python3_FOUND)
|
|
if(LIBAVR_MCU STREQUAL "atmega328p")
|
|
set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device)
|
|
execute_process(
|
|
COMMAND ${_host_cxx} -std=c++23 -Wall -Wextra -O2
|
|
-I/usr/include/simavr -I/usr/include/simavr/parts
|
|
-o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.cpp
|
|
-lsimavr -lsimavrparts -lelf
|
|
RESULT_VARIABLE _dev_res ERROR_VARIABLE _dev_err)
|
|
if(NOT _dev_res EQUAL 0)
|
|
message(STATUS "tsb_device not built (${_dev_err}) - protocol tests skipped")
|
|
unset(TSB_DEVICE)
|
|
endif()
|
|
endif()
|
|
endif()
|
|
endif()
|
|
|
|
# The ELF is only a container (symbols, section headers) and is never flashed -
|
|
# and the host tool's load_image() dispatches on extension, so handing it one
|
|
# would silently program the header bytes. Every loader image therefore gets
|
|
# both flashable forms beside it at link time: .hex for avrdude, and .bin for
|
|
# the host tool's raw path (which is what the reloc and update tests convert to
|
|
# on the fly). .eeprom is dropped - EEPROM content is its own update.
|
|
function(add_image_outputs name)
|
|
add_custom_command(TARGET ${name} POST_BUILD
|
|
COMMAND ${CMAKE_OBJCOPY} -O ihex -R .eeprom
|
|
$<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.hex
|
|
COMMAND ${CMAKE_OBJCOPY} -O binary -R .eeprom
|
|
$<TARGET_FILE:${name}> $<TARGET_FILE:${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.
|
|
#
|
|
# add_tsb_variant(<name> <boot-section-bytes>)
|
|
function(add_tsb_variant name bytes)
|
|
math(EXPR base_dec "32768 - ${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)
|
|
add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${name}>)
|
|
add_image_outputs(${name})
|
|
if(PROJECT_IS_TOP_LEVEL)
|
|
add_test(NAME ${name}.size
|
|
COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:${name}>
|
|
-DLIMIT=${bytes} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
|
|
if(DEFINED TSB_DEVICE)
|
|
add_test(NAME ${name}.protocol
|
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/tsbtest.py
|
|
${TSB_DEVICE} $<TARGET_FILE:${name}> ${base_hex})
|
|
endif()
|
|
endif()
|
|
endfunction()
|
|
|
|
# The tiers reimplement the ATmega328P-only reference protocol, so the guard is
|
|
# the whole of what this repo builds.
|
|
if(LIBAVR_MCU STREQUAL "atmega328p")
|
|
add_tsb_variant(tsb_asm 512)
|
|
add_tsb_variant(tsb_policy 1024)
|
|
add_tsb_variant(tsb_pure 1024)
|
|
add_tsb_variant(tsb_tricks 1024)
|
|
# The policy tier's floor needs these two: a loader's loop bodies all
|
|
# contain calls, which is what makes hoisting an invariant out of one cost
|
|
# more than it saves. The other tiers keep the flag set their recorded
|
|
# floors were measured with - none.
|
|
target_compile_options(tsb_policy PRIVATE -fno-move-loop-invariants -fno-tree-ter)
|
|
endif()
|