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 <noreply@anthropic.com>
141 lines
6.5 KiB
CMake
141 lines
6.5 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**: 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}")
|
|
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()
|
|
|
|
# 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 -
|
|
# 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 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(<name> <boot-section-bytes>)
|
|
function(add_tsb_variant name 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=${TSB_FLASH_KIB}k)
|
|
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()
|