Hardware testing found that a lone calibration pulse wedged the autobaud loader: run() budgeted only the start-edge wait in measure(), and the rx() that read the knock behind it was unbudgeted, so one stray low pulse held an unattended device in the loader and the application never ran. Bound the whole activation — an expired knock budget returns a byte that cannot be the knock, so control falls back into the budgeted measure() and an idle line boots the app there. That fix costs ~22 B, which neither version under review could absorb: the pure one goes 508 -> 530 on the 1284P and the register one 512 -> 534, both over a 512 B slot. Their margin was never spare capacity, it was the space the missing fix should have occupied. So the choice between them is moot; both are kept for the record and no longer built. pureboot_autobaud_uni.cpp replaces them at 464 B. It is pureboot 5: one read command and one write command over named spaces (G/g, sel8, addr16, n8) instead of four per-memory bodies, which collapses four transfer loops into one. The selector's high nibble carries flash's bank, so the shared cursor stays 16 bits and no command speaks word addresses. Three things fall out of the freed space: RAM read/write — the missing feature, and with it arbitrary I/O access, since AVR maps peripherals into the data space; host-issued SPM, so W's hardcoded erase/write/RWW tail becomes three writes to a space and any SPM operation is reachable; and W on the same selector-and-address decode as everything else. Strictly pure throughout: no inline asm, no global register variable, and no GPIOR either — the unit lives in a .noinit static, so the loader claims no chip resource and the chips without GPIOR stop being a special case. pureboot.py speaks both generations, keyed on the version, so the fixed-baud path is untouched; --peek/--poke reach the new data space. pbautobaud.py adds a RAM round-trip and a regression for the hang: a lone pulse must still let the app boot. All 37 chips plus the 12-preset reflect spot set build and size-test green, 444-466 B, worst case 46 B under budget. Sim suites 100%: 1284P 17/17, 328P 23/23. Only real-hardware acceptance remains (pureboot/autobaud.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
423 lines
22 KiB
CMake
423 lines
22 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 tests drive the real wire protocols over a simavr pty
|
||
# (as the host tools do) and actually flash the device. The runners are
|
||
# host programs built at configure time against libsimavr; if they or
|
||
# Python are 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(PB_DEVICE ${CMAKE_BINARY_DIR}/pureboot_device)
|
||
execute_process(
|
||
COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts
|
||
-o ${PB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pureboot_device.c
|
||
-lsimavr -lsimavrparts -lelf -lutil
|
||
RESULT_VARIABLE _pbdev_res ERROR_VARIABLE _pbdev_err)
|
||
if(NOT _pbdev_res EQUAL 0)
|
||
message(STATUS "pureboot_device not built (${_pbdev_err}) — protocol tests skipped")
|
||
unset(PB_DEVICE)
|
||
endif()
|
||
if(LIBAVR_MCU STREQUAL "atmega328p")
|
||
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()
|
||
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 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_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 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 — 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): 510 B in the 512 B section the
|
||
# hand-written 500 B oracle occupies. Everything else, from
|
||
# 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, and the bring-up is the two reset-non-default
|
||
# registers only. 526 B in the 1 KB section (BOOTSZ=10) — 14
|
||
# over the oracle's section, from 168 over at this tier's first
|
||
# floor.
|
||
# tsb_pure — pure idiomatic libavr, one function per command, TU-local
|
||
# (internal linkage), streaming (no SRAM page buffer): 836 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}
|
||
-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)
|
||
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()
|
||
|
||
# The tsb tiers reimplement the ATmega328P-only reference protocol; the other
|
||
# chips build pureboot alone.
|
||
if(LIBAVR_MCU STREQUAL "atmega328p")
|
||
add_tsb_variant(tsb_asm 512)
|
||
add_tsb_variant(tsb_pure 1024)
|
||
add_tsb_variant(tsb_tricks 1024)
|
||
endif()
|
||
|
||
# pureboot — the pure-constraint port (see pureboot/README.md): one source,
|
||
# no inline assembly, no global register variables, every libavr chip,
|
||
# fitting each chip's smallest boot sector. The geometry and the
|
||
# pureboot_add_loader() deployment function live in pureboot/CMakeLists.txt —
|
||
# the unit a downstream project consumes; everything below is this port's
|
||
# own build: the stock loaders, their tests, and the size matrix. The
|
||
# distinct binary dir keeps the `pureboot` target's output name free.
|
||
add_subdirectory(pureboot pureboot-cmake)
|
||
|
||
# The stock loader: the family-default deployment (crystal/RC clock, the
|
||
# chip's natural link, default pins). The activation window stays a cache
|
||
# variable — re-timing a deployed loader is a self-update with a re-timed
|
||
# build. pureboot9 is that re-timed build, and what the update test installs.
|
||
set(PUREBOOT_TIMEOUT 8 CACHE STRING "pureboot activation window, seconds")
|
||
pureboot_add_loader(pureboot TIMEOUT ${PUREBOOT_TIMEOUT})
|
||
if(PROJECT_IS_TOP_LEVEL)
|
||
get_target_property(_pb_stock_hz pureboot PUREBOOT_HZ)
|
||
get_target_property(_pb_stock_baud pureboot PUREBOOT_BAUD)
|
||
|
||
add_test(NAME pureboot.size
|
||
COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:pureboot>
|
||
-DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
|
||
if(Python3_FOUND)
|
||
add_test(NAME pureboot.pi
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/check_pi.py
|
||
${CMAKE_OBJDUMP} ${CMAKE_NM} $<TARGET_FILE:pureboot> ${PUREBOOT_BASE_HEX})
|
||
add_test(NAME pureboot.planner
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_planner.py
|
||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
|
||
endif()
|
||
|
||
# The protocol test flashes this fixture through the loader with the real
|
||
# host tool and expects its banner after the hand-over; a normally linked
|
||
# application whose reset vector is what the tinies' surgery re-homes.
|
||
if(DEFINED PB_DEVICE)
|
||
add_executable(pbapp test/pbapp.cpp)
|
||
target_link_libraries(pbapp PRIVATE libavr)
|
||
add_custom_command(TARGET pbapp POST_BUILD
|
||
COMMAND ${CMAKE_OBJCOPY} -O binary $<TARGET_FILE:pbapp> $<TARGET_FILE:pbapp>.bin)
|
||
add_test(NAME pureboot.protocol
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot> ${PUREBOOT_SIM_MCU} ${_pb_stock_hz}
|
||
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_pb_stock_baud} ${PUREBOOT_EEPROM}
|
||
$<TARGET_FILE:pbapp>.bin ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbtest-work)
|
||
set_tests_properties(pureboot.protocol PROPERTIES TIMEOUT 180)
|
||
|
||
# The position-independence acceptance test: the identical image,
|
||
# installed one slot lower, must serve the full command set.
|
||
add_test(NAME pureboot.reloc
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbreloc.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot> ${PUREBOOT_SIM_MCU} ${_pb_stock_hz}
|
||
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_pb_stock_baud}
|
||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbreloc-work)
|
||
set_tests_properties(pureboot.reloc PROPERTIES TIMEOUT 180
|
||
ENVIRONMENT "PB_OBJCOPY=${CMAKE_OBJCOPY}")
|
||
|
||
# Entering the loader from a running application with no reset
|
||
# between, over a page buffer the application dirtied — the case the
|
||
# loader declines to guard and the host repairs. Hardware forbids the
|
||
# state here (SPM runs only from the boot section); simavr does not,
|
||
# which is what makes it constructible.
|
||
if(LIBAVR_MCU STREQUAL "atmega328p")
|
||
add_test(NAME pureboot.dirty
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbdirty.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot> ${PUREBOOT_SIM_MCU} ${_pb_stock_hz}
|
||
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_pb_stock_baud}
|
||
$<TARGET_FILE:pbapp>.bin
|
||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbdirty-work)
|
||
set_tests_properties(pureboot.dirty PROPERTIES TIMEOUT 180)
|
||
endif()
|
||
|
||
# Re-homing: a loader mistakenly programmed at address 0 (a raw .bin
|
||
# handed to a programmer) or sitting in the staging slot must heal
|
||
# into the canonical slot through the ordinary --update-loader flow.
|
||
# Patched-vector behavior, so one representative chip carries it.
|
||
if(LIBAVR_MCU STREQUAL "attiny85")
|
||
add_test(NAME pureboot.rehome
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbrehome.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot> $<TARGET_FILE:pureboot9>.bin
|
||
${PUREBOOT_SIM_MCU} ${_pb_stock_hz} ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE}
|
||
${_pb_stock_baud} $<TARGET_FILE:pbapp>.bin
|
||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbrehome-work)
|
||
set_tests_properties(pureboot.rehome PROPERTIES TIMEOUT 180)
|
||
endif()
|
||
|
||
# The self-update end-to-end: the re-timed build (same source, only
|
||
# the timeout differs — a byte-different image) replaces the resident
|
||
# through --update-loader, with every power-fail phase rehearsed from
|
||
# the runner's flash dumps.
|
||
pureboot_add_loader(pureboot9 TIMEOUT 9)
|
||
add_test(NAME pureboot.update
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbupdate.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot> $<TARGET_FILE:pureboot9>
|
||
${PUREBOOT_SIM_MCU} ${_pb_stock_hz} ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE}
|
||
${_pb_stock_baud} $<TARGET_FILE:pbapp>.bin
|
||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbupdate-work)
|
||
set_tests_properties(pureboot.update PROPERTIES TIMEOUT 600
|
||
ENVIRONMENT "PB_OBJCOPY=${CMAKE_OBJCOPY}")
|
||
endif()
|
||
|
||
# The size matrix: every configuration axis that could move the image
|
||
# size — the serial backend (different code), the USART instance
|
||
# (different registers), the clock (different constants), and the baud
|
||
# through the shapes its bit timing takes — each combination must still
|
||
# fit the chip's slot budget. Pins are size-neutral (port and bit are
|
||
# immediate operands) and the timeout is a constant, so neither adds an
|
||
# axis. The stock build is one point of this matrix and already has its
|
||
# test.
|
||
function(pureboot_size_variant name)
|
||
pureboot_add_loader(${name} ${ARGN})
|
||
add_test(NAME ${name}.size
|
||
COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:${name}>
|
||
-DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
|
||
endfunction()
|
||
|
||
# The autobaud loader (pureboot/autobaud.md): one clock-agnostic image per
|
||
# chip, no clock × baud axis, size-tested against the same per-chip budget on
|
||
# every chip.
|
||
#
|
||
# Only the unified version is built. Its two predecessors —
|
||
# pureboot_autobaud_pure.cpp at 508 B and pureboot_autobaud_reg.cpp at 512 —
|
||
# had 4 B and 0 B of margin on the 1284P, and the fix for the activation hang
|
||
# costs ~22, which puts them at 530 and 534. Neither can ship, so the choice
|
||
# the branch existed to offer is settled by measurement rather than taste.
|
||
# The sources stay for the record; autobaud.md carries the numbers.
|
||
function(pureboot_autobaud_variant name source)
|
||
pureboot_add_autobaud(${name} ${source})
|
||
add_test(NAME ${name}.size
|
||
COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:${name}>
|
||
-DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
|
||
endfunction()
|
||
pureboot_autobaud_variant(pureboot_autobaud_uni pureboot_autobaud_uni.cpp)
|
||
|
||
# One point of the exhaustive matrix, named from its resolved parameters
|
||
# so the enumeration cannot collide with itself. Unreachable rates drop
|
||
# out here rather than aborting the configure.
|
||
function(pureboot_matrix_point hz baud link)
|
||
if(link STREQUAL "software")
|
||
pureboot_baud_feasible(${hz} ${baud} 1 _ok)
|
||
set(_args SERIAL software)
|
||
else()
|
||
pureboot_baud_feasible(${hz} ${baud} 0 _ok)
|
||
set(_args USART ${link})
|
||
endif()
|
||
if(_ok)
|
||
pureboot_size_variant(pbm_${hz}_${baud}_${link} CLOCK ${hz} BAUD ${baud} ${_args})
|
||
endif()
|
||
endfunction()
|
||
|
||
# Clock points: the shipped-fuse floor (CKDIV8), the calibrated RC, and
|
||
# the crystal the stock build assumes (the tiny13's ladder is its own RC
|
||
# menu — it has no crystal option).
|
||
if(LIBAVR_MCU MATCHES "^attiny13")
|
||
set(_matrix_clocks 1200000 4800000 9600000)
|
||
set(_full_clocks 128000 600000 1200000 4800000 9600000)
|
||
else()
|
||
set(_matrix_clocks 1000000 8000000 16000000)
|
||
set(_full_clocks 128000 1000000 1843200 2000000 3686400 4000000 7372800 8000000
|
||
11059200 12000000 14745600 16000000 18432000 20000000)
|
||
endif()
|
||
|
||
# The exhaustive cross product: every clock a deployment plausibly runs
|
||
# — the internal oscillators, the shipped CKDIV8 floor, the plain
|
||
# crystals and the UART crystals — against every rate, against every
|
||
# backend. Beyond the ladder the list carries the slow rates a
|
||
# sub-megahertz oscillator is left with, which no ladder rate reaches
|
||
# (16000 Bd is the only rate the 128 kHz oscillator holds exactly); at
|
||
# the fast clocks those same rates also select the software UART's
|
||
# 16-bit _delay_loop_2 bit spin (two words more setup at each of its five
|
||
# sites), the largest image the space produces and a shape the ladder
|
||
# default — always the *fastest* rate a clock reaches — never picks.
|
||
#
|
||
# Bounded to one chip per size-bearing class: flash addressing (the
|
||
# word-addressed 1284), hand-over shape (the patched vector on the tinies
|
||
# and m48s), page size, and USART inventory. Everything else in the image
|
||
# is chip-independent code, so a further chip buys builds and no
|
||
# coverage; every chip outside the set carries the compact matrix.
|
||
get_property(_full_bauds GLOBAL PROPERTY PUREBOOT_BAUD_LADDER)
|
||
list(APPEND _full_bauds 16000 4800 2400 1200)
|
||
set(_matrix_spot attiny13a attiny85 atmega48pa atmega8a atmega168pa
|
||
atmega328p atmega164a atmega644a atmega1284p)
|
||
if(DEFINED ENV{PUREBOOT_FULL_MATRIX} AND LIBAVR_MCU IN_LIST _matrix_spot)
|
||
foreach(_matrix_hz IN LISTS _full_clocks)
|
||
foreach(_matrix_baud IN LISTS _full_bauds)
|
||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software)
|
||
if(PUREBOOT_HAS_USART)
|
||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 0)
|
||
endif()
|
||
if(PUREBOOT_HAS_USART1)
|
||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 1)
|
||
endif()
|
||
endforeach()
|
||
endforeach()
|
||
else()
|
||
foreach(_matrix_hz IN LISTS _matrix_clocks)
|
||
math(EXPR _matrix_khz "${_matrix_hz} / 1000")
|
||
if(PUREBOOT_HAS_USART OR NOT _matrix_hz EQUAL _pb_stock_hz)
|
||
pureboot_size_variant(pureboot_sw_${_matrix_khz}k CLOCK ${_matrix_hz} SERIAL software)
|
||
endif()
|
||
if(PUREBOOT_HAS_USART AND NOT _matrix_hz EQUAL _pb_stock_hz)
|
||
pureboot_size_variant(pureboot_hw_${_matrix_khz}k CLOCK ${_matrix_hz} SERIAL hardware)
|
||
endif()
|
||
if(PUREBOOT_HAS_USART1 AND NOT _matrix_hz EQUAL _pb_stock_hz)
|
||
pureboot_size_variant(pureboot_usart1_${_matrix_khz}k CLOCK ${_matrix_hz} USART 1)
|
||
endif()
|
||
endforeach()
|
||
list(GET _matrix_clocks -1 _matrix_top_hz)
|
||
pureboot_size_variant(pureboot_sw_wide CLOCK ${_matrix_top_hz} BAUD 9600 SERIAL software)
|
||
endif()
|
||
if(PUREBOOT_HAS_USART1)
|
||
pureboot_size_variant(pureboot_usart1 USART 1)
|
||
endif()
|
||
|
||
# One configured deployment end to end — a real board's shape rather
|
||
# than the stock assumption: the ATmega328P on its shipped 1 MHz fuses,
|
||
# the software UART on hand-picked pins (TX = PB1, RX = PB5), the ladder
|
||
# baud (9600). The full protocol suite runs against it, fixture
|
||
# application included, over the runner's GPIO bridge — proving the
|
||
# configuration plumbing produces a working loader, not just one that
|
||
# fits.
|
||
if(LIBAVR_MCU STREQUAL "atmega328p" AND DEFINED PB_DEVICE)
|
||
pureboot_size_variant(pureboot_custom CLOCK 1000000 SERIAL software RX pb5 TX pb1)
|
||
get_target_property(_custom_hz pureboot_custom PUREBOOT_HZ)
|
||
get_target_property(_custom_baud pureboot_custom PUREBOOT_BAUD)
|
||
get_target_property(_custom_link pureboot_custom PUREBOOT_LINK)
|
||
add_executable(pbapp_custom test/pbapp.cpp)
|
||
target_link_libraries(pbapp_custom PRIVATE libavr)
|
||
target_compile_definitions(pbapp_custom PRIVATE PUREBOOT_CLOCK_HZ=${_custom_hz}
|
||
PUREBOOT_BAUD=${_custom_baud} PUREBOOT_SOFT_SERIAL PUREBOOT_TX=pb1)
|
||
add_custom_command(TARGET pbapp_custom POST_BUILD
|
||
COMMAND ${CMAKE_OBJCOPY} -O binary
|
||
$<TARGET_FILE:pbapp_custom> $<TARGET_FILE:pbapp_custom>.bin)
|
||
add_test(NAME pureboot.custom
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot_custom> ${PUREBOOT_SIM_MCU} ${_custom_hz}
|
||
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_custom_baud} ${PUREBOOT_EEPROM}
|
||
$<TARGET_FILE:pbapp_custom>.bin ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbcustom-work ${_custom_link})
|
||
set_tests_properties(pureboot.custom PROPERTIES TIMEOUT 180)
|
||
endif()
|
||
|
||
# The second USART, driven for real on one chip: instance selection is
|
||
# compile-checked everywhere, but only a live session proves the loader
|
||
# initialized and polls the USART it claims to. The fixture application
|
||
# banners on the same instance.
|
||
if(LIBAVR_MCU STREQUAL "atmega644a" AND DEFINED PB_DEVICE)
|
||
get_target_property(_usart1_hz pureboot_usart1 PUREBOOT_HZ)
|
||
get_target_property(_usart1_baud pureboot_usart1 PUREBOOT_BAUD)
|
||
add_executable(pbapp_usart1 test/pbapp.cpp)
|
||
target_link_libraries(pbapp_usart1 PRIVATE libavr)
|
||
target_compile_definitions(pbapp_usart1 PRIVATE PUREBOOT_CLOCK_HZ=${_usart1_hz}
|
||
PUREBOOT_BAUD=${_usart1_baud} PUREBOOT_USART=1)
|
||
add_custom_command(TARGET pbapp_usart1 POST_BUILD
|
||
COMMAND ${CMAKE_OBJCOPY} -O binary
|
||
$<TARGET_FILE:pbapp_usart1> $<TARGET_FILE:pbapp_usart1>.bin)
|
||
add_test(NAME pureboot.usart1
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot_usart1> ${PUREBOOT_SIM_MCU} ${_usart1_hz}
|
||
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_usart1_baud} ${PUREBOOT_EEPROM}
|
||
$<TARGET_FILE:pbapp_usart1>.bin ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbusart1-work usart1)
|
||
set_tests_properties(pureboot.usart1 PROPERTIES TIMEOUT 180)
|
||
endif()
|
||
|
||
# The autobaud variants driven end to end over the software-UART bridge (both
|
||
# under review — pureboot/autobaud.md): the host sends the 0xC0 calibration
|
||
# pulse, the loader times it, locks, and programs. Run on the near-flash 328P
|
||
# and the word-addressed 1284P — the two flash-addressing classes — and each
|
||
# at two clocks with the one binary, which is the clock-agnostic property
|
||
# autobaud exists for (test/pbautobaud.py). The fixture application banners
|
||
# over the same software link at the first clock's rate.
|
||
if(LIBAVR_MCU MATCHES "^atmega(328p|1284p)$" AND DEFINED PB_DEVICE)
|
||
add_executable(pbapp_autobaud test/pbapp.cpp)
|
||
target_link_libraries(pbapp_autobaud PRIVATE libavr)
|
||
target_compile_definitions(pbapp_autobaud PRIVATE PUREBOOT_CLOCK_HZ=1000000
|
||
PUREBOOT_BAUD=9600 PUREBOOT_SOFT_SERIAL PUREBOOT_TX=pb1)
|
||
add_custom_command(TARGET pbapp_autobaud POST_BUILD
|
||
COMMAND ${CMAKE_OBJCOPY} -O binary
|
||
$<TARGET_FILE:pbapp_autobaud> $<TARGET_FILE:pbapp_autobaud>.bin)
|
||
foreach(_variant uni)
|
||
add_test(NAME pureboot.autobaud_${_variant}
|
||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbautobaud.py
|
||
${PB_DEVICE} $<TARGET_FILE:pureboot_autobaud_${_variant}> ${PUREBOOT_SIM_MCU}
|
||
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} $<TARGET_FILE:pbapp_autobaud>.bin
|
||
1000000 9600 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
|
||
${CMAKE_BINARY_DIR}/pbautobaud-${_variant}-work)
|
||
set_tests_properties(pureboot.autobaud_${_variant} PROPERTIES TIMEOUT 240)
|
||
endforeach()
|
||
endif()
|
||
endif()
|