tricks 778->666: always_inline every single-call handler into the [[noreturn]] reset entry (which pays no prologue, so their push/pop of call-saved registers vanishes), walk the page pointer in Y (adiw, base recovered as g_addr-page) instead of recomputing Z=base+offset, bring the UART up in the two registers that are not already at their reset value, and seed the activation counter as __uint24. pure 896->842: TU-local internal linkage (proper hygiene, and it lets the compiler inline the one-call handlers), a byte-wide activation count, __uint24 timeout. Still one readable function per command. asm unchanged at 498: its C++-expressible parts are already C++; the core stays asm (the 666 B all-tricks tier is 168 B over — per-call ABI tax, not a feature). All three cross-mode byte-identical, protocol green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
4.2 KiB
CMake
90 lines
4.2 KiB
CMake
cmake_minimum_required(VERSION 3.28)
|
|
|
|
project(tsb_libavr LANGUAGES CXX)
|
|
|
|
# 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.
|
|
# All three 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" — see dev/lessons.md.
|
|
# tsb_asm — dense inline asm for the loader core, libavr for every geometry/
|
|
# baud/info constant and the stack bring-up: 498 B in the 512 B
|
|
# section, beating the hand-written oracle (500 B) at its own
|
|
# feature set. The core is asm because C++ cannot reach it under
|
|
# 512 (the tricks tier is the proof).
|
|
# tsb_tricks — compiler trickery, no asm: the hot page pointer is walked in Y
|
|
# (adiw), flash/EEPROM paths share one runtime-flag body, every
|
|
# single-call handler is always_inline'd into the no-prologue
|
|
# reset entry, pages stream straight to SPM/EEPROM, and the
|
|
# bring-up is the two reset-non-default registers only. 666 B in
|
|
# the 1 KB section (BOOTSZ=10).
|
|
# tsb_pure — pure idiomatic libavr, one function per command, TU-local
|
|
# (internal linkage), streaming (no SRAM page buffer): 842 B in
|
|
# the 1 KB section.
|
|
#
|
|
# 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})
|
|
add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${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()
|
|
|
|
add_tsb_variant(tsb_asm 512)
|
|
add_tsb_variant(tsb_pure 1024)
|
|
add_tsb_variant(tsb_tricks 1024)
|