pureboot moves to its own repo

pureboot is at git.blackmark.me/avr/pureboot now, with its own history: the
files it kept in pureboot/ sit at the top level there, its CMakeLists is the
merged whole of that unit and the build around it, and the v1..v8 tags moved
with it - each one checks out and compiles to exactly the .text it compiled to
here. The loader that repo builds is byte-identical to the one this commit
removes, verified before the removal rather than after.

Nothing is rewritten on this side. The history and the tags are untouched, so
every commit before this one still has pureboot in it and still builds it;
this is one commit that stops carrying it forward.

What goes with it: the four pureboot files, the thirteen pb*.py protocol
drivers and their four host-side unit tests, pbapp and the pureboot simavr
runner, check_pi.py, pbhw.py, pbrig.py, sizes.py, and the Studio project.
check_unit.cmake goes too - it had no caller left once the unit tests moved.

What is left is the four TinySafeBoot tiers, and the build shrinks to fit
them: 757 lines of CMakeLists to 137, and the preset matrix from 37 chips to
one, because the tiers reimplement an ATmega328P-only protocol and every other
chip in that list was there for pureboot. check.sh loses its size-table pass -
the table it checked was pureboot's README - and the Studio solution loses the
project that is now in the other repo.

Gate green: nine tests, four tiers at their section sizes and each one's
protocol suite against the simulator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-23 06:05:45 +02:00
parent bf9b86d2fc
commit 7c1a6a140b
35 changed files with 44 additions and 10815 deletions

View File

@@ -36,17 +36,6 @@ if(PROJECT_IS_TOP_LEVEL)
find_program(_host_cxx NAMES c++ g++)
find_package(Python3 COMPONENTS Interpreter)
if(_host_cxx AND Python3_FOUND)
set(PB_DEVICE ${CMAKE_BINARY_DIR}/pureboot_device)
execute_process(
COMMAND ${_host_cxx} -std=c++23 -Wall -Wextra -O2
-I/usr/include/simavr -I/usr/include/simavr/parts
-o ${PB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pureboot_device.cpp
-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(
@@ -103,10 +92,10 @@ endfunction()
# SPM/EEPROM.
# tsb_pure - pure idiomatic libavr, one function per command, TU-local
# (internal linkage), streaming (no SRAM page buffer).
# tsb_policy - the policy floor: pureboot's rules (no asm, no register
# variables) with every pureboot lesson applied, and the
# tsb_policy - the policy floor: no inline assembly and no global register
# variables, which is philosophy #5's own bound, and the
# measured evidence that the 512 B fit is a property of the
# mechanisms philosophy #5 bans.
# mechanisms it bans.
#
# What each measures is oracle/README.md's table, which is the one place the
# four numbers and the hand-written loader's own are compared.
@@ -133,625 +122,16 @@ function(add_tsb_variant name bytes)
endif()
endfunction()
# The tsb tiers reimplement the ATmega328P-only reference protocol; the other
# chips build pureboot alone.
# 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 is measured with the loop flags pureboot's size
# work found (a loader's loop bodies all contain calls); the other tiers
# keep the flag set their recorded floors were measured with - none.
# 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()
# 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_OBJCOPY} ${CMAKE_CXX_COMPILER} ${LIBAVR_MCU}
$<TARGET_FILE:pureboot>
${CMAKE_BINARY_DIR}/CMakeFiles/pureboot.dir/pureboot/pureboot.cpp.obj
${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)
add_test(NAME pureboot.scan
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_scan.py
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
# CMakePresets.json is generated; hand edits drift the moment the
# generator reruns, so the gate holds the pair together.
add_test(NAME presets.generated
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/make_presets.py
--check)
add_test(NAME pureboot.handshake
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_handshake.py)
add_test(NAME pureboot.updatelink
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_update_link.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 activation window as a measured duration: application installed,
# line idle, the first transmit is the application's banner - its
# cycle is the window the source declares, held to +/-2 % (one
# mis-counted cycle per poll is a 10 % shift).
add_test(NAME pureboot.window
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbwindow.py
--device ${PB_DEVICE} --loader $<TARGET_FILE:pureboot>
--mcu ${PUREBOOT_SIM_MCU} --hz ${_pb_stock_hz}
--base ${PUREBOOT_BASE_HEX} --page ${PUREBOOT_PAGE}
--baud ${_pb_stock_baud} --app $<TARGET_FILE:pbapp>.bin
--seconds ${PUREBOOT_TIMEOUT}
--tool ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
--workdir ${CMAKE_BINARY_DIR}/pbwindow-work)
set_tests_properties(pureboot.window PROPERTIES TIMEOUT 300)
# The half-duplex loader's window, same gate: its poll runs through
# readable()'s release-line test, whose outlined call re-shapes the
# whole loop - a per-class cycle count (poll_cost() in pureboot.cpp)
# that only the built image can prove, chip by chip.
if(PUREBOOT_HAS_USART)
add_test(NAME pureboot.window.halfduplex
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbwindow.py
--device ${PB_DEVICE} --loader $<TARGET_FILE:pureboot_hd>
--mcu ${PUREBOOT_SIM_MCU} --hz ${_pb_stock_hz}
--base ${PUREBOOT_BASE_HEX} --page ${PUREBOOT_PAGE}
--baud ${_pb_stock_baud} --app $<TARGET_FILE:pbapp>.bin
--seconds ${PUREBOOT_TIMEOUT}
--tool ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
--workdir ${CMAKE_BINARY_DIR}/pbwindow-hd-work)
set_tests_properties(pureboot.window.halfduplex PROPERTIES TIMEOUT 300)
endif()
# 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}")
# The seal against the one command that proves it: erasing the page the
# loader runs from, refused unsealed and honoured sealed. Destroys the
# loader by design, so it gets a device of its own.
add_test(NAME pureboot.selfwrite
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbselfwrite.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}/pbselfwrite-work)
set_tests_properties(pureboot.selfwrite PROPERTIES TIMEOUT 180)
# The seal against a link that damages bytes on purpose: every header
# field flipped after sealing must be refused, and the identical flip
# applied before sealing must be obeyed.
add_test(NAME pureboot.glitch
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbglitch.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}/pbglitch-work)
set_tests_properties(pureboot.glitch PROPERTIES TIMEOUT 180)
# 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()
# The reset walk region: an application that grew into the span reset
# crosses to reach the loader, refused by the ordinary --flash. One
# boot-sectioned mega carries it - the per-chip BOOTSZ ladder the
# refusal decodes is a planner test (test_planner.py).
if(LIBAVR_MCU STREQUAL "atmega328p")
add_test(NAME pureboot.walk
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbwalk.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}/pbwalk-work)
set_tests_properties(pureboot.walk PROPERTIES TIMEOUT 300)
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), the baud
# through the shapes its bit timing takes, and the pins through the one
# thing they decide (whether a bit-banged link has to release the USART
# that owns them) - each combination must still fit the chip's slot
# budget. The timeout is a constant and adds no 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: one clock-agnostic image per chip, so it has no
# clock x baud axis of its own - the matrix below sweeps those for the
# fixed-baud builds, and this one binary has to serve all of them at run
# time. Size-tested against the same per-chip budget as every other variant.
pureboot_add_loader(pureboot_autobaud SERIAL autobaud)
add_test(NAME pureboot_autobaud.size
COMMAND ${CMAKE_COMMAND} -DSIZE_TOOL=${CMAKE_SIZE} -DELF=$<TARGET_FILE:pureboot_autobaud>
-DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
# The measured unit's home is wire contract, not layout accident: the
# host reads the bit period from it (--info's measured clock). In the
# GPIOR home the image must carry no RAM copy at all; in the RAM home it
# is the loader's only RAM object, at the very start of SRAM.
add_test(NAME pureboot_autobaud.unit
COMMAND ${CMAKE_COMMAND} -DOBJDUMP=${CMAKE_OBJDUMP} -DELF=$<TARGET_FILE:pureboot_autobaud>
-DRAM_START=${PUREBOOT_RAM_START} -DGPIOR=${PUREBOOT_UNIT_GPIOR}
-P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_unit.cmake)
# One point of the exhaustive matrix, named from its resolved parameters
# so the enumeration cannot collide with itself. `pins` is empty for the
# default pair, or the index of the USART whose own pins a bit-banged
# link sits on. Unreachable rates drop out here rather than aborting the
# configure.
# The optional trailing argument is the one-wire shape of the same link:
# ONE_WIRE folds a software point onto its RX pin (the default, or the
# named USART's RXD), HALF_DUPLEX is the hardware USART's turn-around.
function(pureboot_matrix_point hz baud link pins)
set(_name pbm_${hz}_${baud}_${link})
if(link STREQUAL "software")
pureboot_baud_feasible(${hz} ${baud} 1 _ok)
set(_args SERIAL software)
if(NOT pins STREQUAL "")
list(APPEND _args RX ${PUREBOOT_USART${pins}_RX} TX ${PUREBOOT_USART${pins}_TX})
set(_name ${_name}_on${pins})
endif()
if(ARGC GREATER 4 AND ARGV4 STREQUAL "ONE_WIRE")
if(NOT pins STREQUAL "")
set(_args SERIAL software RX ${PUREBOOT_USART${pins}_RX} TX ${PUREBOOT_USART${pins}_RX})
else()
list(APPEND _args RX pb0 TX pb0)
endif()
set(_name ${_name}_1w)
endif()
else()
pureboot_baud_feasible(${hz} ${baud} 0 _ok)
set(_args USART ${link})
if(ARGC GREATER 4 AND ARGV4 STREQUAL "HALF_DUPLEX")
list(APPEND _args HALF_DUPLEX)
set(_name ${_name}_hd)
endif()
endif()
if(_ok)
pureboot_size_variant(${_name} 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.
#
# Every chip runs the full cross product: the size-bearing classes (flash
# addressing, hand-over shape, page size, USART inventory) are what make
# the image differ, and a chip outside them is expected to match its class
# - but "expected" is what a matrix is for, and the whole sweep is cheap
# enough to run rather than reason about. PUREBOOT_FULL_MATRIX is what
# selects it; the compact matrix below is the per-commit default.
get_property(_full_bauds GLOBAL PROPERTY PUREBOOT_BAUD_LADDER)
list(APPEND _full_bauds 16000 4800 2400 1200)
if(DEFINED ENV{PUREBOOT_FULL_MATRIX})
foreach(_matrix_hz IN LISTS _full_clocks)
foreach(_matrix_baud IN LISTS _full_bauds)
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software "")
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software "" ONE_WIRE)
if(PUREBOOT_HAS_USART)
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software 0)
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software 0 ONE_WIRE)
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 0 "")
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 0 "" HALF_DUPLEX)
endif()
if(PUREBOOT_HAS_USART1)
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software 1)
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software 1 ONE_WIRE)
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 1 "")
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 1 "" HALF_DUPLEX)
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)
# The pin axis at the widest software image - the slowest ladder rate
# against the fastest clock, whose bit spin needs the 16-bit delay
# loop - with the USART release on top of it. The exhaustive sweep
# above carries the same axis across its whole cross product.
if(PUREBOOT_HAS_USART)
pureboot_size_variant(pureboot_sw_wide_on_usart0 CLOCK ${_matrix_top_hz} BAUD 9600
SERIAL software RX ${PUREBOOT_USART0_RX} TX ${PUREBOOT_USART0_TX})
endif()
if(PUREBOOT_HAS_USART1)
pureboot_size_variant(pureboot_sw_wide_on_usart1 CLOCK ${_matrix_top_hz} BAUD 9600
SERIAL software RX ${PUREBOOT_USART1_RX} TX ${PUREBOOT_USART1_TX})
endif()
endif()
if(PUREBOOT_HAS_USART1)
pureboot_size_variant(pureboot_usart1 USART 1)
endif()
# The pin axis at its fixed points, in both matrix modes. The autobaud
# loader carries no clock and no baud, so the sweep has nothing to vary
# for it - yet it is the tightest image in the space, and on a USART's
# own pins it pays the release too: that combination is the one that
# overflowed the 1284's slot. The software build on those pins is the
# same deployment the mute test drives.
if(PUREBOOT_HAS_USART)
pureboot_size_variant(pureboot_sw_on_usart0 SERIAL software
RX ${PUREBOOT_USART0_RX} TX ${PUREBOOT_USART0_TX})
pureboot_size_variant(pureboot_autobaud_on_usart0 SERIAL autobaud
RX ${PUREBOOT_USART0_RX} TX ${PUREBOOT_USART0_TX})
endif()
if(PUREBOOT_HAS_USART1)
pureboot_size_variant(pureboot_sw_on_usart1 SERIAL software
RX ${PUREBOOT_USART1_RX} TX ${PUREBOOT_USART1_TX})
pureboot_size_variant(pureboot_autobaud_on_usart1 SERIAL autobaud
RX ${PUREBOOT_USART1_RX} TX ${PUREBOOT_USART1_TX})
endif()
# The OSCCAL axis at its fixed points: the stock shape, and the tightest
# image in the space with the trim on top - the axis adds one register
# write, and these points hold both of its addressing encodings to every
# chip's budget.
pureboot_size_variant(pureboot_osccal OSCCAL 0x9c)
pureboot_size_variant(pureboot_autobaud_osccal SERIAL autobaud OSCCAL 0x9c)
if(PUREBOOT_HAS_USART)
pureboot_size_variant(pureboot_autobaud_osccal_on_usart0 SERIAL autobaud OSCCAL 0x9c
RX ${PUREBOOT_USART0_RX} TX ${PUREBOOT_USART0_TX})
endif()
# The one-wire axis at its fixed points, in both matrix modes (the
# exhaustive sweep carries the same shapes across its cross product):
# the software link folded onto one pin, the tightest autobaud image
# likewise - on the default pin and on the USART's own RXD, whose
# release the now-driven shared pin needs where a receive-only link
# would not - and the hardware USART's half-duplex turn-around, stock
# and at the widest fixed-baud shape.
# The two spellings deliberately split across the two points: HALF_DUPLEX
# folds TX onto RX, RX == TX states the same thing directly.
pureboot_size_variant(pureboot_1w SERIAL software RX pb0 HALF_DUPLEX)
pureboot_size_variant(pureboot_1w_autobaud_osccal SERIAL autobaud OSCCAL 0x9c RX pb0 TX pb0)
if(PUREBOOT_HAS_USART)
pureboot_size_variant(pureboot_1w_on_usart0 SERIAL software
RX ${PUREBOOT_USART0_RX} TX ${PUREBOOT_USART0_RX})
pureboot_size_variant(pureboot_1w_autobaud_osccal_on_usart0 SERIAL autobaud OSCCAL 0x9c
RX ${PUREBOOT_USART0_RX} TX ${PUREBOOT_USART0_RX})
pureboot_size_variant(pureboot_hd HALF_DUPLEX)
list(GET _matrix_clocks -1 _hd_top_hz)
pureboot_size_variant(pureboot_hd_wide CLOCK ${_hd_top_hz} BAUD 9600 HALF_DUPLEX)
endif()
if(PUREBOOT_HAS_USART1)
pureboot_size_variant(pureboot_usart1_hd USART 1 HALF_DUPLEX)
endif()
# The trim byte, observed through the wire from the first prompt - one
# chip per OSCCAL addressing class: extended I/O on the 328P (data 0x66,
# an sts - DS40002061B section 36), plain I/O on the 85 (data 0x51, an out -
# Atmel-2586 section 21).
if(LIBAVR_MCU MATCHES "^(atmega328p|attiny85)$" AND DEFINED PB_DEVICE)
if(LIBAVR_MCU STREQUAL "atmega328p")
set(_osccal_addr 0x66)
else()
set(_osccal_addr 0x51)
endif()
get_target_property(_osccal_hz pureboot_osccal PUREBOOT_HZ)
get_target_property(_osccal_baud pureboot_osccal PUREBOOT_BAUD)
add_test(NAME pureboot.osccal
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbosccal.py
${PB_DEVICE} $<TARGET_FILE:pureboot_osccal> ${PUREBOOT_SIM_MCU}
${_osccal_hz} ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_osccal_baud}
${_osccal_addr} 0x9c ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
${CMAKE_BINARY_DIR}/pbosccal-work)
set_tests_properties(pureboot.osccal PROPERTIES TIMEOUT 120)
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()
# Hand-over with the USART that owns the loader's pins left enabled - the
# state an application reaches by jumping in without a reset, and the one
# that made a bit-banged loader on PD0/PD1 (where the Uno's USB bridge
# lands) receive and obey while answering nothing. Run where it was found
# on silicon; the runner supplies the pin ownership simavr has no model
# for, which is what lets this fail when the release is gone.
if(LIBAVR_MCU STREQUAL "atmega328p" AND DEFINED PB_DEVICE)
get_target_property(_mute_hz pureboot_sw_on_usart0 PUREBOOT_HZ)
get_target_property(_mute_baud pureboot_sw_on_usart0 PUREBOOT_BAUD)
get_target_property(_mute_link pureboot_sw_on_usart0 PUREBOOT_LINK)
add_executable(pbapp_handover test/pbapp.cpp)
target_link_libraries(pbapp_handover PRIVATE libavr)
target_compile_definitions(pbapp_handover PRIVATE PUREBOOT_CLOCK_HZ=${_mute_hz}
PUREBOOT_BAUD=${_mute_baud} PUREBOOT_HANDOVER)
add_custom_command(TARGET pbapp_handover POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary
$<TARGET_FILE:pbapp_handover> $<TARGET_FILE:pbapp_handover>.bin)
add_test(NAME pureboot.mute
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbmute.py
${PB_DEVICE} $<TARGET_FILE:pureboot_sw_on_usart0> ${PUREBOOT_SIM_MCU} ${_mute_hz}
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_mute_baud}
$<TARGET_FILE:pbapp_handover>.bin
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
${CMAKE_BINARY_DIR}/pbmute-work ${_mute_link})
set_tests_properties(pureboot.mute PROPERTIES TIMEOUT 180)
# The same hand-over against the one-wire deployment on that USART's
# RXD: RXEN forces the shared pin's direction, so a loader that only
# released the transmit-side hold would read the wire and answer into
# a pin it cannot drive. The host runs with the --one-wire echo
# discard, which the bridge's shared-line model feeds for real.
get_target_property(_mute1w_link pureboot_1w_on_usart0 PUREBOOT_LINK)
add_test(NAME pureboot.mute.onewire
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbmute.py
${PB_DEVICE} $<TARGET_FILE:pureboot_1w_on_usart0> ${PUREBOOT_SIM_MCU} ${_mute_hz}
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_mute_baud}
$<TARGET_FILE:pbapp_handover>.bin
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
${CMAKE_BINARY_DIR}/pbmute-1w-work ${_mute1w_link})
set_tests_properties(pureboot.mute.onewire PROPERTIES TIMEOUT 180)
# The full protocol suite over one shared pin: the loader folded onto
# PB0, the bridge following the pin's direction, the fixture
# bannering as a guest on the same line, and the host discarding its
# own echo throughout.
get_target_property(_1w_hz pureboot_1w PUREBOOT_HZ)
get_target_property(_1w_baud pureboot_1w PUREBOOT_BAUD)
get_target_property(_1w_link pureboot_1w PUREBOOT_LINK)
add_executable(pbapp_1w test/pbapp.cpp)
target_link_libraries(pbapp_1w PRIVATE libavr)
target_compile_definitions(pbapp_1w PRIVATE PUREBOOT_CLOCK_HZ=${_1w_hz}
PUREBOOT_BAUD=${_1w_baud} PUREBOOT_SOFT_SERIAL
PUREBOOT_RX=pb0 PUREBOOT_TX=pb0)
add_custom_command(TARGET pbapp_1w POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary
$<TARGET_FILE:pbapp_1w> $<TARGET_FILE:pbapp_1w>.bin)
add_test(NAME pureboot.onewire
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
${PB_DEVICE} $<TARGET_FILE:pureboot_1w> ${PUREBOOT_SIM_MCU} ${_1w_hz}
${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE} ${_1w_baud} ${PUREBOOT_EEPROM}
$<TARGET_FILE:pbapp_1w>.bin ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
${CMAKE_BINARY_DIR}/pb1w-work ${_1w_link})
set_tests_properties(pureboot.onewire PROPERTIES TIMEOUT 180)
# The hardware USART's half-duplex turn-around, end to end: every
# reply byte runs drive-line, TXC-hold, release - against simavr's
# RXEN-gated receiver, which drops input to a disabled receiver the
# way silicon does. The pty is a two-wire transport, so the host
# needs no echo discard here; the off-chip tie itself is the
# hardware bench's item.
add_test(NAME pureboot.halfduplex
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbtest.py
${PB_DEVICE} $<TARGET_FILE:pureboot_hd> ${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}/pbhd-work)
set_tests_properties(pureboot.halfduplex 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 loader driven end to end over the software-UART bridge:
# 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)
add_test(NAME pureboot.autobaud
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbautobaud.py
${PB_DEVICE} $<TARGET_FILE:pureboot_autobaud> ${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-work)
set_tests_properties(pureboot.autobaud PROPERTIES TIMEOUT 240)
# The tightest deployment in the space, end to end: the autobaud
# loader folded onto the USART's own RXD with the OSCCAL trim baked
# - one-wire calibration, the receive-side release, and the host's
# echo discard, over the same two-clock sweep. One chip carries it;
# the shape is chip-independent.
if(LIBAVR_MCU STREQUAL "atmega328p")
get_target_property(_ab1w_link pureboot_1w_autobaud_osccal_on_usart0 PUREBOOT_LINK)
add_executable(pbapp_autobaud_1w test/pbapp.cpp)
target_link_libraries(pbapp_autobaud_1w PRIVATE libavr)
target_compile_definitions(pbapp_autobaud_1w PRIVATE PUREBOOT_CLOCK_HZ=1000000
PUREBOOT_BAUD=9600 PUREBOOT_SOFT_SERIAL
PUREBOOT_RX=pd0 PUREBOOT_TX=pd0)
add_custom_command(TARGET pbapp_autobaud_1w POST_BUILD
COMMAND ${CMAKE_OBJCOPY} -O binary
$<TARGET_FILE:pbapp_autobaud_1w> $<TARGET_FILE:pbapp_autobaud_1w>.bin)
add_test(NAME pureboot.autobaud.onewire
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbautobaud.py
${PB_DEVICE} $<TARGET_FILE:pureboot_1w_autobaud_osccal_on_usart0>
${PUREBOOT_SIM_MCU} ${PUREBOOT_BASE_HEX} ${PUREBOOT_PAGE}
$<TARGET_FILE:pbapp_autobaud_1w>.bin
1000000 9600 ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
${CMAKE_BINARY_DIR}/pbautobaud-1w-work ${_ab1w_link})
set_tests_properties(pureboot.autobaud.onewire PROPERTIES TIMEOUT 240)
endif()
# The autobaud window: the calibration poll budget, at the measured
# 10 cycles a poll (pbwindow.py pins the constant the README's
# seconds arithmetic uses; the budget itself is the clock-free knob).
add_test(NAME pureboot.window.autobaud
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pbwindow.py
--device ${PB_DEVICE} --loader $<TARGET_FILE:pureboot_autobaud>
--mcu ${PUREBOOT_SIM_MCU} --hz 1000000
--base ${PUREBOOT_BASE_HEX} --page ${PUREBOOT_PAGE}
--baud 9600 --app $<TARGET_FILE:pbapp_autobaud>.bin
--autobaud-polls 4000000 --link sw
--tool ${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py
--workdir ${CMAKE_BINARY_DIR}/pbwindow-autobaud-work)
set_tests_properties(pureboot.window.autobaud PROPERTIES TIMEOUT 300)
endif()
endif()