The protocol tests are registered on every host now and skip where the simavr device cannot be built. They used to be left out of the suite entirely, which makes its size a property of the machine - and a suite whose size is a property of the machine is one nothing can be compared against. The reason travels with the skip: no compiler, no Python, or one bounded line of whatever stopped test/device.cpp from linking, so the reading says what is absent rather than that something is. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
158 lines
7.7 KiB
CMake
158 lines
7.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 runner is a
|
|
# host program built at configure time against libsimavr (C++23 - what the
|
|
# 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.
|
|
set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device)
|
|
find_program(_host_cxx NAMES c++ g++)
|
|
set(_tsb_absent "${LIBAVR_NO_PYTHON}")
|
|
if(NOT _host_cxx)
|
|
set(_tsb_absent "no host C++ compiler on PATH, and the simavr device is a host program")
|
|
elseif(NOT _tsb_absent)
|
|
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)
|
|
# One bounded line of it: this becomes a single argument on a
|
|
# command line, and the reading has to say what stopped the build
|
|
# rather than that something did.
|
|
string(REGEX REPLACE "[\r\n\t]+" " " _dev_err "${_dev_err}")
|
|
string(REPLACE ";" "," _dev_err "${_dev_err}")
|
|
string(LENGTH "${_dev_err}" _dev_len)
|
|
if(_dev_len GREATER 240)
|
|
string(SUBSTRING "${_dev_err}" 0 240 _dev_err)
|
|
endif()
|
|
set(_tsb_absent "test/device.cpp does not build here: ${_dev_err}")
|
|
endif()
|
|
endif()
|
|
libavr_launcher(_tsb_python "${_tsb_absent}" ${Python3_EXECUTABLE})
|
|
if(_tsb_absent)
|
|
message(STATUS "the protocol tests skip here - ${_tsb_absent}")
|
|
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)
|
|
add_test(NAME ${name}.protocol
|
|
COMMAND ${_tsb_python} ${CMAKE_CURRENT_SOURCE_DIR}/test/tsbtest.py
|
|
${TSB_DEVICE} $<TARGET_FILE:${name}> ${base_hex})
|
|
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()
|
|
|
|
# Every test registered above carries the marker a stubbed launcher prints, so
|
|
# a check this host cannot run reads as Skipped rather than Failed.
|
|
if(PROJECT_IS_TOP_LEVEL)
|
|
libavr_skip_unverified()
|
|
endif()
|