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.
#   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(<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)
