pureboot 5: one command pair for every memory, and a clock-free backend
R/r/w/F collapse into G and g over a selector byte naming the space — flash,
EEPROM, data, fuse, SPM — with the flash bank in its high nibble. Four command
bodies, four transfer loops and four argument decodes become one of each, and
W joins the same decode instead of keeping an address form of its own. The
loader shrinks while gaining everything below: on the 1284P the stock build
goes 480 -> 432 B and the software one 496 -> 450.
What the freed space buys:
- Data space. On AVR one pointer spans SRAM, the register file and the whole
I/O space, so G over space 2 reads all three. pureboot keeps zero static
RAM and pushes no register, so at loader entry an application's SRAM is
still what the application left there — this is a post-mortem, not just a
poke hole. As its own command it needed a dispatch arm and a loop; as one
more space it is a single ld/st.
- Host-issued SPM. W fills the page buffer and stops; erase, write and RWW
re-enable are writes to space 4, which reach the same fused store-and-SPM
pair through the transfer's own address and data. Any SPM operation, lock
bits included, is now reachable and the loader carries no page-commit logic.
The four-cycle SPMCSR-to-SPM window is why that primitive stays fused: no
host can hit it across a serial link, and that — not the byte count — is
the floor on how low-level a bootloader's primitives can go.
- Byte addresses everywhere. The bank in the selector retires the
word-addressed wire the >64 KiB parts needed, so the 1284s stop being the
outlier.
SERIAL autobaud is a third backend on the same loader, over libavr's
software_autobaud: no clock, no baud, one binary per chip for every F_CPU and
every rate. Activation counts poll iterations rather than seconds and bounds
every wait, so a stray pulse cannot hold an unattended device.
b answers with the version and signature only; the host derives geometry from
the signature, which is what an autobaud build requires anyway. An update image
is a bare slot with no device to ask, so every image carries a six-byte stamp —
the same bytes b answers with, and the source of both — that the loader never
reads from flash and the host refuses to install a mismatch against. The
running-slot write guard moved onto the SPM commit, which covers erase and
write both where guarding W covered neither directly.
The position-independence lint now proves the property instead of a proxy for
it: the image must come out byte-identical linked at a different base.
-fno-move-loop-invariants left the tuned flag set — it was fitted to a command
loop carrying four transfer bodies and costs bytes now that it carries one.
Verified: the exhaustive matrix on all 37 chips (every clock x every baud x
every backend, non-standard rates included, plus the autobaud build) —
8174 size checks, no failures, tightest fit the 1284s' autobaud at 510 of 512.
Behavioral suites green on every chip class: t13a 10/10, t85 11/11, m8 13/13,
m16a 13/13, m48pa 13/13, 328P 23/23, 644A 17/17, 1284P 17/17. Data-space
round trip through --peek/--poke and the autobaud handshake are both red-green
proven.
The two prototype sources and their findings file go; the README carries the
protocol and dev/done.md in libavr carries the reasoning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -194,13 +194,19 @@ function(pureboot_default_baud clock software outvar)
|
||||
endfunction()
|
||||
|
||||
# pureboot_add_loader(<name> [CLOCK <hz>] [BAUD <bd>]
|
||||
# [SERIAL auto|hardware|software] [USART <n>]
|
||||
# [SERIAL auto|hardware|software|autobaud] [USART <n>]
|
||||
# [RX <pin>] [TX <pin>] [TIMEOUT <s>])
|
||||
#
|
||||
# The loader target plus its flashable images (<name>.hex for a programmer,
|
||||
# <name>.bin for --update-loader). The resolved deployment is stamped on the
|
||||
# target as PUREBOOT_HZ / PUREBOOT_BAUD / PUREBOOT_LINK (the link spelled
|
||||
# usart0, usart1 or sw:<RX>,<TX>) — what a test harness speaks to it with.
|
||||
#
|
||||
# SERIAL autobaud measures the host's bit timing at run time, so the image
|
||||
# carries no clock and no baud: CLOCK and BAUD are not build parameters there,
|
||||
# and one binary per chip serves every F_CPU and every rate. The stamped
|
||||
# PUREBOOT_HZ/PUREBOOT_BAUD then record what a harness should *drive* it at,
|
||||
# not what it was built for.
|
||||
function(pureboot_add_loader name)
|
||||
cmake_parse_arguments(PB "" "CLOCK;BAUD;SERIAL;USART;RX;TX;TIMEOUT" "" ${ARGN})
|
||||
if(PB_UNPARSED_ARGUMENTS)
|
||||
@@ -222,8 +228,8 @@ function(pureboot_add_loader name)
|
||||
if(NOT PB_SERIAL)
|
||||
set(PB_SERIAL auto)
|
||||
endif()
|
||||
if(DEFINED PB_USART AND PB_SERIAL STREQUAL "software")
|
||||
message(FATAL_ERROR "pureboot_add_loader(${name}): USART ${PB_USART} contradicts SERIAL software")
|
||||
if(DEFINED PB_USART AND NOT PB_SERIAL MATCHES "^(auto|hardware)$")
|
||||
message(FATAL_ERROR "pureboot_add_loader(${name}): USART ${PB_USART} contradicts SERIAL ${PB_SERIAL}")
|
||||
endif()
|
||||
if(DEFINED PB_USART)
|
||||
set(PB_SERIAL hardware)
|
||||
@@ -252,7 +258,7 @@ function(pureboot_add_loader name)
|
||||
set(PB_SERIAL software)
|
||||
endif()
|
||||
endif()
|
||||
if(PB_SERIAL STREQUAL "software")
|
||||
if(PB_SERIAL MATCHES "^(software|autobaud)$")
|
||||
if(NOT PB_RX)
|
||||
set(PB_RX pb0)
|
||||
endif()
|
||||
@@ -264,7 +270,11 @@ function(pureboot_add_loader name)
|
||||
message(FATAL_ERROR "pureboot_add_loader(${name}): pin '${_pin}' is not of the form pb1")
|
||||
endif()
|
||||
endforeach()
|
||||
set(_serial_defines PUREBOOT_SOFT_SERIAL PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
||||
if(PB_SERIAL STREQUAL "autobaud")
|
||||
set(_serial_defines PUREBOOT_AUTOBAUD PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
||||
else()
|
||||
set(_serial_defines PUREBOOT_SOFT_SERIAL PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
||||
endif()
|
||||
# sw:<RX>,<TX> as port letter and bit, upcased.
|
||||
string(SUBSTRING ${PB_RX} 1 2 _rx_pin)
|
||||
string(SUBSTRING ${PB_TX} 1 2 _tx_pin)
|
||||
@@ -280,23 +290,29 @@ function(pureboot_add_loader name)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(_defines PUREBOOT_CLOCK_HZ=${PB_CLOCK} PUREBOOT_BAUD=${PB_BAUD} PUREBOOT_TIMEOUT=${PB_TIMEOUT}
|
||||
${_serial_defines})
|
||||
if(PB_SERIAL STREQUAL "autobaud")
|
||||
# No clock and no baud reach the image; the window is a poll budget.
|
||||
set(_defines ${_serial_defines})
|
||||
else()
|
||||
set(_defines PUREBOOT_CLOCK_HZ=${PB_CLOCK} PUREBOOT_BAUD=${PB_BAUD} PUREBOOT_TIMEOUT=${PB_TIMEOUT}
|
||||
${_serial_defines})
|
||||
endif()
|
||||
|
||||
add_executable(${name} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/pureboot.cpp)
|
||||
target_link_libraries(${name} PRIVATE libavr)
|
||||
target_compile_definitions(${name} PRIVATE ${_defines})
|
||||
# Codegen shaping for the loader TU only, worth 14–36 B depending on the
|
||||
# chip. At -Os GCC otherwise rewrites the byte-stream loops' counters into
|
||||
# end-pointer forms that cost registers (-fno-ivopts,
|
||||
# -fno-split-wide-types), leaves register pressure on the table with the
|
||||
# default allocator (-fira-algorithm=priority), and keeps loop-invariant
|
||||
# immediates and expression temporaries in registers
|
||||
# (-fno-move-loop-invariants, -fno-tree-ter) — but every loop body here
|
||||
# contains a call, so a register held across it costs more than the
|
||||
# load-immediate it saves.
|
||||
# Codegen shaping for the loader TU only. At -Os GCC otherwise rewrites the
|
||||
# byte-stream loops' counters into end-pointer forms that cost registers
|
||||
# (-fno-ivopts, -fno-split-wide-types), leaves register pressure on the
|
||||
# table with the default allocator (-fira-algorithm=priority), and keeps
|
||||
# expression temporaries in registers (-fno-tree-ter) — but every loop body
|
||||
# here contains a call, so a register held across it costs more than the
|
||||
# load-immediate it saves. The set is fitted to the loader's body and has to
|
||||
# be re-measured when that body changes: -fno-move-loop-invariants belonged
|
||||
# here while the command loop carried four transfer bodies and costs bytes
|
||||
# now that it carries one.
|
||||
target_compile_options(${name} PRIVATE
|
||||
-fno-ivopts -fira-algorithm=priority -fno-move-loop-invariants -fno-tree-ter -fno-split-wide-types)
|
||||
-fno-ivopts -fira-algorithm=priority -fno-tree-ter -fno-split-wide-types)
|
||||
target_link_options(${name} PRIVATE -nostartfiles -Wl,--section-start=.text=${_base_hex}
|
||||
-Wl,--defsym=pureboot_app=${_app} ${_wrap})
|
||||
add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${name}>)
|
||||
@@ -311,37 +327,3 @@ function(pureboot_add_loader name)
|
||||
PUREBOOT_LINK ${_link})
|
||||
endfunction()
|
||||
|
||||
# pureboot_add_autobaud(<name> <source> [RX <pin>] [TX <pin>])
|
||||
#
|
||||
# An autobaud software-serial loader from <source> (pureboot_autobaud_*.cpp).
|
||||
# Autobaud measures the host's bit timing at runtime, so the image carries no
|
||||
# clock and no baud — one binary per chip runs at any F_CPU. Same per-chip
|
||||
# geometry, link and codegen flags as pureboot_add_loader(); only the clock and
|
||||
# baud axes fall away. Two source files are under review (autobaud.md):
|
||||
# pureboot_autobaud_pure.cpp and pureboot_autobaud_reg.cpp.
|
||||
function(pureboot_add_autobaud name source)
|
||||
cmake_parse_arguments(PB "" "RX;TX" "" ${ARGN})
|
||||
if(NOT PB_RX)
|
||||
set(PB_RX pb0)
|
||||
endif()
|
||||
if(NOT PB_TX)
|
||||
set(PB_TX pb1)
|
||||
endif()
|
||||
foreach(_pin ${PB_RX} ${PB_TX})
|
||||
if(NOT _pin MATCHES "^p[a-h][0-7]$")
|
||||
message(FATAL_ERROR "pureboot_add_autobaud(${name}): pin '${_pin}' is not of the form pb1")
|
||||
endif()
|
||||
endforeach()
|
||||
get_property(_base_hex GLOBAL PROPERTY PUREBOOT_BASE_HEX)
|
||||
get_property(_app GLOBAL PROPERTY PUREBOOT_APP)
|
||||
get_property(_wrap GLOBAL PROPERTY PUREBOOT_WRAP)
|
||||
|
||||
add_executable(${name} ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/${source})
|
||||
target_link_libraries(${name} PRIVATE libavr)
|
||||
target_compile_definitions(${name} PRIVATE PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
||||
target_compile_options(${name} PRIVATE
|
||||
-fno-ivopts -fira-algorithm=priority -fno-move-loop-invariants -fno-tree-ter -fno-split-wide-types)
|
||||
target_link_options(${name} PRIVATE -nostartfiles -Wl,--section-start=.text=${_base_hex}
|
||||
-Wl,--defsym=pureboot_app=${_app} ${_wrap})
|
||||
add_custom_command(TARGET ${name} POST_BUILD COMMAND ${CMAKE_SIZE} $<TARGET_FILE:${name}>)
|
||||
endfunction()
|
||||
|
||||
Reference in New Issue
Block a user