cmake_minimum_required(VERSION 3.28) project(tsb_libavr LANGUAGES CXX) # CMake's GNU compiler module appends "-O3 -DNDEBUG" to CMAKE_CXX_FLAGS_RELEASE # after the libavr toolchain sets "-Os", and the later -O3 wins — so a Release # build is silently -O3, ~15-20 % larger than -Os. For a boot loader measured to # the byte that is fatal, so strip it and get the size build the design intends. # (The same leak affects libavr's own Release builds; a fix belongs upstream.) string(REPLACE "-O3" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") # libavr from a local checkout (LIBAVR_ROOT) or the forge; the toolchain file # comes from the same checkout via CMakePresets.json. include(FetchContent) if(NOT LIBAVR_ROOT AND DEFINED ENV{LIBAVR_ROOT}) set(LIBAVR_ROOT $ENV{LIBAVR_ROOT}) endif() if(LIBAVR_ROOT) FetchContent_Declare(libavr SOURCE_DIR ${LIBAVR_ROOT}) else() FetchContent_Declare(libavr GIT_REPOSITORY git@git.blackmark.me:avr/libavr.git GIT_TAG main) endif() FetchContent_MakeAvailable(libavr) if(PROJECT_IS_TOP_LEVEL) add_compile_options(-Werror) # warnings are errors for the port's own code enable_testing() # The behavioral test drives the real TinySafeBoot wire protocol over a # simavr pty (as the host tools do) and actually flashes the device. The # runner is a host program built at configure time against libsimavr; if it # or Python is missing, only the size tests run. find_program(_host_cc NAMES cc gcc) find_package(Python3 COMPONENTS Interpreter) if(_host_cc AND Python3_FOUND) set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device) execute_process( COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts -o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.c -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() # The TinySafeBoot protocol reimplemented on libavr in three 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 naked entry sits in # .vectors, laid first, and runs. The boot base is FLASHEND+1 minus the section # size; the linker section-start and the source's boot_bytes agree. # tsb_asm — inline-asm variant, the headline: ≤512 B, the 512 B section. # tsb_pure / tsb_tricks — pure-C++ and compiler-trickery variants, larger, # shown in the 1 KB section (BOOTSZ=10) they fit. # # add_tsb_variant( ) 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}) add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $) if(PROJECT_IS_TOP_LEVEL) add_test(NAME ${name}.size COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$ -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} $ ${base_hex}) endif() endif() endfunction() add_tsb_variant(tsb_asm 512) add_tsb_variant(tsb_pure 1024) add_tsb_variant(tsb_tricks 1024)