Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e7f6cd3ee8 | |||
| 0f00f6bdeb | |||
| 3990f53180 | |||
| 3899e8ef40 | |||
| 0ddc65ca28 | |||
| 142de8775d |
@@ -157,6 +157,8 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
add_test(NAME pureboot.planner
|
add_test(NAME pureboot.planner
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_planner.py
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_planner.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
|
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
|
||||||
|
add_test(NAME pureboot.handshake
|
||||||
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_handshake.py)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The protocol test flashes this fixture through the loader with the real
|
# The protocol test flashes this fixture through the loader with the real
|
||||||
@@ -235,12 +237,12 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
|
|
||||||
# The size matrix: every configuration axis that could move the image
|
# The size matrix: every configuration axis that could move the image
|
||||||
# size — the serial backend (different code), the USART instance
|
# size — the serial backend (different code), the USART instance
|
||||||
# (different registers), the clock (different constants), and the baud
|
# (different registers), the clock (different constants), the baud
|
||||||
# through the shapes its bit timing takes — each combination must still
|
# through the shapes its bit timing takes, and the pins through the one
|
||||||
# fit the chip's slot budget. Pins are size-neutral (port and bit are
|
# thing they decide (whether a bit-banged link has to release the USART
|
||||||
# immediate operands) and the timeout is a constant, so neither adds an
|
# that owns them) — each combination must still fit the chip's slot
|
||||||
# axis. The stock build is one point of this matrix and already has its
|
# budget. The timeout is a constant and adds no axis. The stock build is
|
||||||
# test.
|
# one point of this matrix and already has its test.
|
||||||
function(pureboot_size_variant name)
|
function(pureboot_size_variant name)
|
||||||
pureboot_add_loader(${name} ${ARGN})
|
pureboot_add_loader(${name} ${ARGN})
|
||||||
add_test(NAME ${name}.size
|
add_test(NAME ${name}.size
|
||||||
@@ -258,18 +260,25 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
-DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
|
-DLIMIT=${PUREBOOT_LIMIT} -P ${CMAKE_CURRENT_SOURCE_DIR}/test/check_size.cmake)
|
||||||
|
|
||||||
# One point of the exhaustive matrix, named from its resolved parameters
|
# One point of the exhaustive matrix, named from its resolved parameters
|
||||||
# so the enumeration cannot collide with itself. Unreachable rates drop
|
# so the enumeration cannot collide with itself. `pins` is empty for the
|
||||||
# out here rather than aborting the configure.
|
# default pair, or the index of the USART whose own pins a bit-banged
|
||||||
function(pureboot_matrix_point hz baud link)
|
# link sits on. Unreachable rates drop out here rather than aborting the
|
||||||
|
# configure.
|
||||||
|
function(pureboot_matrix_point hz baud link pins)
|
||||||
|
set(_name pbm_${hz}_${baud}_${link})
|
||||||
if(link STREQUAL "software")
|
if(link STREQUAL "software")
|
||||||
pureboot_baud_feasible(${hz} ${baud} 1 _ok)
|
pureboot_baud_feasible(${hz} ${baud} 1 _ok)
|
||||||
set(_args SERIAL software)
|
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()
|
||||||
else()
|
else()
|
||||||
pureboot_baud_feasible(${hz} ${baud} 0 _ok)
|
pureboot_baud_feasible(${hz} ${baud} 0 _ok)
|
||||||
set(_args USART ${link})
|
set(_args USART ${link})
|
||||||
endif()
|
endif()
|
||||||
if(_ok)
|
if(_ok)
|
||||||
pureboot_size_variant(pbm_${hz}_${baud}_${link} CLOCK ${hz} BAUD ${baud} ${_args})
|
pureboot_size_variant(${_name} CLOCK ${hz} BAUD ${baud} ${_args})
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
@@ -307,12 +316,14 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
if(DEFINED ENV{PUREBOOT_FULL_MATRIX})
|
if(DEFINED ENV{PUREBOOT_FULL_MATRIX})
|
||||||
foreach(_matrix_hz IN LISTS _full_clocks)
|
foreach(_matrix_hz IN LISTS _full_clocks)
|
||||||
foreach(_matrix_baud IN LISTS _full_bauds)
|
foreach(_matrix_baud IN LISTS _full_bauds)
|
||||||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software)
|
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software "")
|
||||||
if(PUREBOOT_HAS_USART)
|
if(PUREBOOT_HAS_USART)
|
||||||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 0)
|
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software 0)
|
||||||
|
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 0 "")
|
||||||
endif()
|
endif()
|
||||||
if(PUREBOOT_HAS_USART1)
|
if(PUREBOOT_HAS_USART1)
|
||||||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 1)
|
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software 1)
|
||||||
|
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 1 "")
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
endforeach()
|
endforeach()
|
||||||
@@ -331,11 +342,42 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
endforeach()
|
endforeach()
|
||||||
list(GET _matrix_clocks -1 _matrix_top_hz)
|
list(GET _matrix_clocks -1 _matrix_top_hz)
|
||||||
pureboot_size_variant(pureboot_sw_wide CLOCK ${_matrix_top_hz} BAUD 9600 SERIAL software)
|
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()
|
endif()
|
||||||
if(PUREBOOT_HAS_USART1)
|
if(PUREBOOT_HAS_USART1)
|
||||||
pureboot_size_variant(pureboot_usart1 USART 1)
|
pureboot_size_variant(pureboot_usart1 USART 1)
|
||||||
endif()
|
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()
|
||||||
|
|
||||||
# One configured deployment end to end — a real board's shape rather
|
# One configured deployment end to end — a real board's shape rather
|
||||||
# than the stock assumption: the ATmega328P on its shipped 1 MHz fuses,
|
# 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
|
# the software UART on hand-picked pins (TX = PB1, RX = PB5), the ladder
|
||||||
@@ -364,6 +406,33 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
set_tests_properties(pureboot.custom PROPERTIES TIMEOUT 180)
|
set_tests_properties(pureboot.custom PROPERTIES TIMEOUT 180)
|
||||||
endif()
|
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)
|
||||||
|
endif()
|
||||||
|
|
||||||
# The second USART, driven for real on one chip: instance selection is
|
# The second USART, driven for real on one chip: instance selection is
|
||||||
# compile-checked everywhere, but only a live session proves the loader
|
# compile-checked everywhere, but only a live session proves the loader
|
||||||
# initialized and polls the USART it claims to. The fixture application
|
# initialized and polls the USART it claims to. The fixture application
|
||||||
|
|||||||
@@ -116,6 +116,17 @@ else()
|
|||||||
math(EXPR _pb_limit "${_pb_slot} - 2")
|
math(EXPR _pb_limit "${_pb_slot} - 2")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# The pins each USART owns. A bit-banged link deployed on them has to release
|
||||||
|
# that USART before it can drive the line, and those instructions are the one
|
||||||
|
# way the choice of pins moves the image — so a size matrix needs them as an
|
||||||
|
# axis even though pins are otherwise immediate operands. Uniform across every
|
||||||
|
# mega libavr covers: USART0 (the classics' un-numbered USART included) on
|
||||||
|
# PD0/PD1, USART1 on PD2/PD3.
|
||||||
|
set(_pb_usart0_rx pd0)
|
||||||
|
set(_pb_usart0_tx pd1)
|
||||||
|
set(_pb_usart1_rx pd2)
|
||||||
|
set(_pb_usart1_tx pd3)
|
||||||
|
|
||||||
# simavr names its cores after the base dies; the A revisions run on them
|
# simavr names its cores after the base dies; the A revisions run on them
|
||||||
# (the 644PA on the 644P core).
|
# (the 644PA on the 644P core).
|
||||||
set(_pb_sim_mcu ${LIBAVR_MCU})
|
set(_pb_sim_mcu ${LIBAVR_MCU})
|
||||||
@@ -133,6 +144,8 @@ set_property(GLOBAL PROPERTY PUREBOOT_WRAP "${_pb_wrap}")
|
|||||||
set_property(GLOBAL PROPERTY PUREBOOT_DEFAULT_HZ ${_pb_hz})
|
set_property(GLOBAL PROPERTY PUREBOOT_DEFAULT_HZ ${_pb_hz})
|
||||||
set_property(GLOBAL PROPERTY PUREBOOT_HAS_USART ${_pb_has_usart})
|
set_property(GLOBAL PROPERTY PUREBOOT_HAS_USART ${_pb_has_usart})
|
||||||
set_property(GLOBAL PROPERTY PUREBOOT_HAS_USART1 ${_pb_has_usart1})
|
set_property(GLOBAL PROPERTY PUREBOOT_HAS_USART1 ${_pb_has_usart1})
|
||||||
|
set_property(GLOBAL PROPERTY PUREBOOT_USART0_TX ${_pb_usart0_tx})
|
||||||
|
set_property(GLOBAL PROPERTY PUREBOOT_USART1_TX ${_pb_usart1_tx})
|
||||||
|
|
||||||
# The port's own build (tests, the size matrix) reads the geometry from the
|
# The port's own build (tests, the size matrix) reads the geometry from the
|
||||||
# parent scope; a downstream consumer gets the same variables for free.
|
# parent scope; a downstream consumer gets the same variables for free.
|
||||||
@@ -145,6 +158,10 @@ set(PUREBOOT_DEFAULT_HZ ${_pb_hz} PARENT_SCOPE)
|
|||||||
set(PUREBOOT_HAS_USART ${_pb_has_usart} PARENT_SCOPE)
|
set(PUREBOOT_HAS_USART ${_pb_has_usart} PARENT_SCOPE)
|
||||||
set(PUREBOOT_HAS_USART1 ${_pb_has_usart1} PARENT_SCOPE)
|
set(PUREBOOT_HAS_USART1 ${_pb_has_usart1} PARENT_SCOPE)
|
||||||
set(PUREBOOT_SIM_MCU ${_pb_sim_mcu} PARENT_SCOPE)
|
set(PUREBOOT_SIM_MCU ${_pb_sim_mcu} PARENT_SCOPE)
|
||||||
|
set(PUREBOOT_USART0_RX ${_pb_usart0_rx} PARENT_SCOPE)
|
||||||
|
set(PUREBOOT_USART0_TX ${_pb_usart0_tx} PARENT_SCOPE)
|
||||||
|
set(PUREBOOT_USART1_RX ${_pb_usart1_rx} PARENT_SCOPE)
|
||||||
|
set(PUREBOOT_USART1_TX ${_pb_usart1_tx} PARENT_SCOPE)
|
||||||
|
|
||||||
# The rates a default may pick, fastest first.
|
# The rates a default may pick, fastest first.
|
||||||
set_property(GLOBAL PROPERTY PUREBOOT_BAUD_LADDER 115200 57600 38400 19200 9600)
|
set_property(GLOBAL PROPERTY PUREBOOT_BAUD_LADDER 115200 57600 38400 19200 9600)
|
||||||
@@ -200,7 +217,8 @@ endfunction()
|
|||||||
# The loader target plus its flashable images (<name>.hex for a programmer,
|
# The loader target plus its flashable images (<name>.hex for a programmer,
|
||||||
# <name>.bin for --update-loader). The resolved deployment is stamped on the
|
# <name>.bin for --update-loader). The resolved deployment is stamped on the
|
||||||
# target as PUREBOOT_HZ / PUREBOOT_BAUD / PUREBOOT_LINK (the link spelled
|
# 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.
|
# usart0, usart1, or sw:<RX>,<TX> with a trailing @<n> where those pins are a
|
||||||
|
# USART's own) — what a test harness speaks to it with.
|
||||||
#
|
#
|
||||||
# SERIAL autobaud measures the host's bit timing at run time, so the image
|
# 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,
|
# carries no clock and no baud: CLOCK and BAUD are not build parameters there,
|
||||||
@@ -275,11 +293,21 @@ function(pureboot_add_loader name)
|
|||||||
else()
|
else()
|
||||||
set(_serial_defines PUREBOOT_SOFT_SERIAL PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
set(_serial_defines PUREBOOT_SOFT_SERIAL PUREBOOT_RX=${PB_RX} PUREBOOT_TX=${PB_TX})
|
||||||
endif()
|
endif()
|
||||||
# sw:<RX>,<TX> as port letter and bit, upcased.
|
# sw:<RX>,<TX> as port letter and bit, upcased — with @<n> where
|
||||||
|
# the TX pin is a USART's own TXD, since a harness driving that
|
||||||
|
# link has to know the USART owns the pin until the loader
|
||||||
|
# releases it.
|
||||||
string(SUBSTRING ${PB_RX} 1 2 _rx_pin)
|
string(SUBSTRING ${PB_RX} 1 2 _rx_pin)
|
||||||
string(SUBSTRING ${PB_TX} 1 2 _tx_pin)
|
string(SUBSTRING ${PB_TX} 1 2 _tx_pin)
|
||||||
string(TOUPPER "sw:${_rx_pin},${_tx_pin}" _link)
|
string(TOUPPER "sw:${_rx_pin},${_tx_pin}" _link)
|
||||||
string(REPLACE "SW" "sw" _link ${_link})
|
string(REPLACE "SW" "sw" _link ${_link})
|
||||||
|
get_property(_tx0 GLOBAL PROPERTY PUREBOOT_USART0_TX)
|
||||||
|
get_property(_tx1 GLOBAL PROPERTY PUREBOOT_USART1_TX)
|
||||||
|
if(_usart AND PB_TX STREQUAL _tx0)
|
||||||
|
set(_link "${_link}@0")
|
||||||
|
elseif(_usart1 AND PB_TX STREQUAL _tx1)
|
||||||
|
set(_link "${_link}@1")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
if(NOT PB_BAUD)
|
if(NOT PB_BAUD)
|
||||||
|
|||||||
@@ -28,31 +28,32 @@ it carries the calibration machinery and no clock at all.
|
|||||||
|
|
||||||
| Chip | Flash | Loader at | Link | Stock | Autobaud |
|
| Chip | Flash | Loader at | Link | Stock | Autobaud |
|
||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| ATtiny13, ATtiny13A † | 1 KiB | 0x0200 | software | 402 B | 472 B |
|
| ATtiny13, ATtiny13A † | 1 KiB | 0x0200 | software | 394 B | 464 B |
|
||||||
| ATtiny25 † | 2 KiB | 0x0600 | software | 406 B | 476 B |
|
| ATtiny25 † | 2 KiB | 0x0600 | software | 398 B | 468 B |
|
||||||
| ATtiny45 † | 4 KiB | 0x0e00 | software | 410 B | 480 B |
|
| ATtiny45 † | 4 KiB | 0x0e00 | software | 402 B | 472 B |
|
||||||
| ATtiny85 † | 8 KiB | 0x1e00 | software | 410 B | 480 B |
|
| ATtiny85 † | 8 KiB | 0x1e00 | software | 402 B | 472 B |
|
||||||
| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 372 B | 486 B |
|
| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 364 B | 478 B |
|
||||||
| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 374 B | 490 B |
|
| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 366 B | 482 B |
|
||||||
| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 374 B | 490 B |
|
| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 366 B | 482 B |
|
||||||
| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 400 B | 476 B |
|
| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 392 B | 468 B |
|
||||||
| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 410 B | 486 B |
|
| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 402 B | 478 B |
|
||||||
| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 412 B | 490 B |
|
| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 404 B | 482 B |
|
||||||
| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 412 B | 490 B |
|
| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 404 B | 482 B |
|
||||||
| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 412 B | 490 B |
|
| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 404 B | 482 B |
|
||||||
| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 412 B | 490 B |
|
| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 404 B | 482 B |
|
||||||
| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 406 B | 484 B |
|
| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 398 B | 476 B |
|
||||||
| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 432 B | 510 B |
|
| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 424 B | 502 B |
|
||||||
|
|
||||||
† No hardware boot section: the host patches the reset vector, and the budget
|
† No hardware boot section: the host patches the reset vector, and the budget
|
||||||
is 510 bytes, since the slot's last word is the trampoline.
|
is 510 bytes, since the slot's last word is the trampoline.
|
||||||
|
|
||||||
The tightest fit in the whole space is the 1284s' autobaud build, 510 of its
|
The tightest fit in the whole space is the 1284s' autobaud build deployed on a
|
||||||
512 — they alone carry the far-flash machinery (ELPM reads, RAMPZ page
|
USART's own pins, 506 of its 512 — they alone carry the far-flash machinery
|
||||||
commands) and autobaud alone carries the calibration loop. Everything else has
|
(ELPM reads, RAMPZ page commands), autobaud alone carries the calibration loop,
|
||||||
20 B of headroom or more. The flash bank riding in a transfer's selector byte
|
and a bit-banged link on a USART's pins alone has to release it (below). The
|
||||||
keeps even those chips' addressing the same 16-bit form every other chip uses,
|
same build on the default pins is 502. The flash bank riding in a transfer's
|
||||||
which is why they are no longer the outlier they were.
|
selector byte keeps even those chips' addressing the same 16-bit form every
|
||||||
|
other chip uses, which is why they are no longer the outlier they were.
|
||||||
|
|
||||||
The software UART enables the RX pull-up; TX idles high. All multi-byte wire
|
The software UART enables the RX pull-up; TX idles high. All multi-byte wire
|
||||||
quantities are little-endian.
|
quantities are little-endian.
|
||||||
@@ -79,6 +80,13 @@ receiver's 100-cycles-a-bit floor. Whatever is picked or overridden is
|
|||||||
re-checked in the compile: an infeasible combination, or a USART the chip does
|
re-checked in the compile: an infeasible combination, or a USART the chip does
|
||||||
not have, fails with a named static assert.
|
not have, fails with a named static assert.
|
||||||
|
|
||||||
|
Putting a bit-banged link on a USART's own pins is a supported deployment, and
|
||||||
|
the usual one where a board's USB bridge is wired to RXD/TXD: the link's `init`
|
||||||
|
clears that USART's `UCSRnB` first, because while its `TXEN` is set the USART —
|
||||||
|
not the port register — owns the TX pin, and a loader entered from an
|
||||||
|
application that left it enabled would receive and obey while answering nothing
|
||||||
|
(§20.2). It costs four bytes, and only on those pins.
|
||||||
|
|
||||||
`SERIAL autobaud` takes neither: the loader **measures** the host's bit timing
|
`SERIAL autobaud` takes neither: the loader **measures** the host's bit timing
|
||||||
at run time, so `CLOCK` and `BAUD` are not build parameters there and one
|
at run time, so `CLOCK` and `BAUD` are not build parameters there and one
|
||||||
binary per chip serves every clock and every rate. It is for the deployments
|
binary per chip serves every clock and every rate. It is for the deployments
|
||||||
@@ -397,8 +405,10 @@ Per chip preset, `ctest` runs:
|
|||||||
the fastest clock — where a software UART's per-bit spin outgrows its
|
the fastest clock — where a software UART's per-bit spin outgrows its
|
||||||
one-register delay loop and takes the 16-bit one. That is the largest image
|
one-register delay loop and takes the 16-bit one. That is the largest image
|
||||||
the configuration space produces, and a shape the ladder default (always the
|
the configuration space produces, and a shape the ladder default (always the
|
||||||
*fastest* rate a clock reaches) never picks. Pins are immediate operands and
|
*fastest* rate a clock reaches) never picks. Pins are an axis for one reason
|
||||||
the timeout is a constant: neither is an axis;
|
only, and it is enough: a bit-banged link on a USART's own pins has to
|
||||||
|
release that USART, so `pureboot_{sw,autobaud}_on_usart{0,1}` build there
|
||||||
|
too. The timeout is a constant and is no axis;
|
||||||
- `pureboot_autobaud.size` — the clock-free build, which has no clock or baud
|
- `pureboot_autobaud.size` — the clock-free build, which has no clock or baud
|
||||||
axis of its own: one binary per chip has to serve every point the matrix
|
axis of its own: one binary per chip has to serve every point the matrix
|
||||||
below sweeps;
|
below sweeps;
|
||||||
@@ -412,6 +422,9 @@ Per chip preset, `ctest` runs:
|
|||||||
flash-resident section but `.text`, and the image byte-identical when linked
|
flash-resident section but `.text`, and the image byte-identical when linked
|
||||||
at a different base — which is position independence itself rather than a
|
at a different base — which is position independence itself rather than a
|
||||||
proxy for it;
|
proxy for it;
|
||||||
|
- `pureboot.handshake` — the host tool's activation must not hang on a target
|
||||||
|
that never falls quiet: the drain after a prompt is bounded by the handshake
|
||||||
|
deadline, and a well-behaved loader still connects;
|
||||||
- `pureboot.planner` — the host tool's pure logic: programming orders and their
|
- `pureboot.planner` — the host tool's pure logic: programming orders and their
|
||||||
recovery properties, the surgery, the staging composition, the boot-fuse
|
recovery properties, the surgery, the staging composition, the boot-fuse
|
||||||
decode, the update preflight over synthetic fuse bytes, and the repairing
|
decode, the update preflight over synthetic fuse bytes, and the repairing
|
||||||
@@ -434,6 +447,12 @@ Per chip preset, `ctest` runs:
|
|||||||
- `pureboot.usart1` (644A) — the same suite over the second hardware USART:
|
- `pureboot.usart1` (644A) — the same suite over the second hardware USART:
|
||||||
instance selection is compile-checked everywhere, but only a live session
|
instance selection is compile-checked everywhere, but only a live session
|
||||||
proves the loader polls the USART it claims;
|
proves the loader polls the USART it claims;
|
||||||
|
- `pureboot.mute` (328P) — a software link on USART0's own pins, entered from an
|
||||||
|
application that handed over with that USART still enabled: the loader must
|
||||||
|
still answer, which it does only because it releases it. The pin ownership is
|
||||||
|
the runner's, not simavr's — simavr wires a USART through IRQs and never takes
|
||||||
|
the pin from the port, so without that model the state under test could not
|
||||||
|
arise at all;
|
||||||
- `pureboot.dirty` (328P) — entering the loader from a running application over
|
- `pureboot.dirty` (328P) — entering the loader from a running application over
|
||||||
an SPM buffer it deliberately dirtied, the case the loader declines to guard:
|
an SPM buffer it deliberately dirtied, the case the loader declines to guard:
|
||||||
a bare verify must see the corruption and the repairing verify must fix it in
|
a bare verify must see the corruption and the repairing verify must fix it in
|
||||||
@@ -449,5 +468,5 @@ Per chip preset, `ctest` runs:
|
|||||||
exists for. A lone calibration pulse with no knock behind it must still let
|
exists for. A lone calibration pulse with no knock behind it must still let
|
||||||
the application boot, so no wait in activation can be unbounded.
|
the application boot, so no wait in activation can be unbounded.
|
||||||
|
|
||||||
`size`, `pi` and `planner` are host logic and run anywhere; the
|
`size`, `pi`, `planner` and `handshake` are host logic and run anywhere; the
|
||||||
simulator-driven targets need simavr and a pty, so they are POSIX-only.
|
simulator-driven targets need simavr and a pty, so they are POSIX-only.
|
||||||
|
|||||||
@@ -166,6 +166,27 @@ constexpr char usart_digit = '0' + PUREBOOT_USART;
|
|||||||
constexpr char usart_digit = '0';
|
constexpr char usart_digit = '0';
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Release a hardware USART the application may have left enabled onto a
|
||||||
|
// bit-banged link's pins. A software transmitter drives its TX pin through the
|
||||||
|
// port register, but while that USART's TXEN is set the USART owns the pin and
|
||||||
|
// the port write does nothing — the loader would receive and obey yet never
|
||||||
|
// answer. Writing UCSRnB zero hands the pin back to the port. Guarded on the
|
||||||
|
// pin actually being a USART's TXD, so a link on non-USART pins emits nothing.
|
||||||
|
template <char Inst, avr::io::pin Tx>
|
||||||
|
[[gnu::always_inline]] inline void release_usart_on()
|
||||||
|
{
|
||||||
|
if constexpr (avr::uart::has_usart<Inst>())
|
||||||
|
if constexpr (avr::uart::detail::usart_pin<Inst>("TXD") == Tx)
|
||||||
|
avr::hw::reg_impl<avr::uart::detail::ureg<Inst, "UCSR#B">()>::write(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <avr::io::pin Tx>
|
||||||
|
[[gnu::always_inline]] inline void release_usarts_on()
|
||||||
|
{
|
||||||
|
release_usart_on<'0', Tx>();
|
||||||
|
release_usart_on<'1', Tx>();
|
||||||
|
}
|
||||||
|
|
||||||
template <avr::hertz_t C, avr::baud_t B>
|
template <avr::hertz_t C, avr::baud_t B>
|
||||||
struct hardware_link {
|
struct hardware_link {
|
||||||
using uart = avr::uart::usart<usart_digit, C, {.baud = B, .max_baud_error = 2.5_pct}>;
|
using uart = avr::uart::usart<usart_digit, C, {.baud = B, .max_baud_error = 2.5_pct}>;
|
||||||
@@ -212,6 +233,7 @@ struct software_link {
|
|||||||
static void init()
|
static void init()
|
||||||
{
|
{
|
||||||
avr::init<rx_t, tx_t>();
|
avr::init<rx_t, tx_t>();
|
||||||
|
release_usarts_on<avr::PUREBOOT_TX>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool pending()
|
static bool pending()
|
||||||
@@ -245,6 +267,7 @@ struct autobaud_link {
|
|||||||
static void init()
|
static void init()
|
||||||
{
|
{
|
||||||
avr::init<uart>();
|
avr::init<uart>();
|
||||||
|
release_usarts_on<avr::PUREBOOT_TX>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::uint8_t rx()
|
static std::uint8_t rx()
|
||||||
@@ -465,8 +488,7 @@ void fill_page(std::uint8_t bank, std::uint16_t at)
|
|||||||
// address is a word address and a slot is half as many words as bytes, so
|
// address is a word address and a slot is half as many words as bytes, so
|
||||||
// its high byte is the slot index outright. No absolute address is ever
|
// its high byte is the slot index outright. No absolute address is ever
|
||||||
// formed, so the image stays position-independent.
|
// formed, so the image stays position-independent.
|
||||||
const auto return_words = reinterpret_cast<std::uint16_t>(__builtin_return_address(0));
|
const auto slot_high = avr::startup::caller_page();
|
||||||
const auto slot_high = static_cast<std::uint8_t>(return_words >> 8);
|
|
||||||
|
|
||||||
await_host();
|
await_host();
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ else:
|
|||||||
import termios
|
import termios
|
||||||
|
|
||||||
PROMPT = b"+"
|
PROMPT = b"+"
|
||||||
VERSION = 4 # this tool's own version — free to drift from a loader's
|
VERSION = 5 # this tool's own version — free to drift from a loader's
|
||||||
# The loader versions this tool speaks. A pureboot version implies its wire
|
# The loader versions this tool speaks. A pureboot version implies its wire
|
||||||
# protocol, which carries no number of its own, so this window is where that
|
# protocol, which carries no number of its own, so this window is where that
|
||||||
# map lives: every version so far speaks the same protocol, and one that
|
# map lives: every version so far speaks the same protocol, and one that
|
||||||
@@ -484,8 +484,13 @@ class Loader:
|
|||||||
self.port.write(knock)
|
self.port.write(knock)
|
||||||
knocks += 1
|
knocks += 1
|
||||||
if PROMPT in self.port.read_available(0.4):
|
if PROMPT in self.port.read_available(0.4):
|
||||||
|
# Settle: absorb a real loader's trailing bytes before asking
|
||||||
|
# for the identity. Bounded by the deadline so a target that
|
||||||
|
# never falls quiet — a board stuck in a reset loop, whose
|
||||||
|
# garbage carries a stray prompt — cannot spin here forever.
|
||||||
while self.port.read_available(0.3):
|
while self.port.read_available(0.3):
|
||||||
pass
|
if time.monotonic() > deadline:
|
||||||
|
break
|
||||||
self.port.write(b"b")
|
self.port.write(b"b")
|
||||||
try:
|
try:
|
||||||
# A version the tool cannot speak is the loader's own
|
# A version the tool cannot speak is the loader's own
|
||||||
|
|||||||
@@ -11,6 +11,10 @@
|
|||||||
// reset reaches those loaders through the patched vector (or the runner
|
// reset reaches those loaders through the patched vector (or the runner
|
||||||
// models BOOTRST), so the application owes them nothing.
|
// models BOOTRST), so the application owes them nothing.
|
||||||
//
|
//
|
||||||
|
// PUREBOOT_HANDOVER drops the listening and jumps straight in, leaving the
|
||||||
|
// USART enabled behind it — the hand-over state a loader bit-banging on that
|
||||||
|
// USART's own pins has to survive.
|
||||||
|
//
|
||||||
// The fixture speaks the deployment its loader was built for: the same
|
// The fixture speaks the deployment its loader was built for: the same
|
||||||
// PUREBOOT_* defines configure it, and without them it assumes the stock
|
// PUREBOOT_* defines configure it, and without them it assumes the stock
|
||||||
// deployment (the crystal/RC clock table below, the chip's natural link).
|
// deployment (the crystal/RC clock table below, the chip's natural link).
|
||||||
@@ -64,16 +68,29 @@ struct link {
|
|||||||
{
|
{
|
||||||
tx_t::write(static_cast<std::uint8_t>(c));
|
tx_t::write(static_cast<std::uint8_t>(c));
|
||||||
}
|
}
|
||||||
|
// The loader sits in the top slot — 512 bytes on every chip. The jump
|
||||||
|
// takes a word address, which is what makes the >64 KiB chips' entry
|
||||||
|
// reachable through a 16-bit pointer at all.
|
||||||
|
static void enter_loader()
|
||||||
|
{
|
||||||
|
constexpr std::uint32_t slot = 512;
|
||||||
|
reinterpret_cast<void (*)()>(static_cast<std::uint16_t>((avr::hw::db.mem.flash_size - slot) / 2))();
|
||||||
|
}
|
||||||
|
|
||||||
[[noreturn]] static void idle()
|
[[noreturn]] static void idle()
|
||||||
{
|
{
|
||||||
// 'L' hands back to the loader in the top slot — 512 bytes on every
|
#if defined(PUREBOOT_HANDOVER)
|
||||||
// chip. The jump takes a word address, which is what makes the
|
// Hand back at once, with this USART still enabled — the state that
|
||||||
// >64 KiB chips' entry reachable through a 16-bit pointer at all.
|
// leaves a bit-banged loader on its pins mute unless the loader
|
||||||
constexpr std::uint32_t slot = 512;
|
// releases it. Unconditional because there is no command wire to
|
||||||
|
// wait on: that loader's link is the pins, not this peripheral.
|
||||||
|
enter_loader();
|
||||||
|
__builtin_unreachable();
|
||||||
|
#else
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto command = tx_t::read_blocking();
|
auto command = tx_t::read_blocking();
|
||||||
if (command == 'L')
|
if (command == 'L')
|
||||||
reinterpret_cast<void (*)()>(static_cast<std::uint16_t>((avr::hw::db.mem.flash_size - slot) / 2))();
|
enter_loader();
|
||||||
// 'D' leaves every word of the SPM page buffer dirty, so that a
|
// 'D' leaves every word of the SPM page buffer dirty, so that a
|
||||||
// following 'L' enters the loader with the buffer it never clears.
|
// following 'L' enters the loader with the buffer it never clears.
|
||||||
if (command == 'D') {
|
if (command == 'D') {
|
||||||
@@ -82,6 +99,7 @@ struct link {
|
|||||||
tx('D');
|
tx('D');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -109,8 +127,13 @@ struct link<C, false> {
|
|||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
avr::init<typename link<dev::clock>::tx_t>();
|
avr::init<typename link<dev::clock>::tx_t>();
|
||||||
|
#if !defined(PUREBOOT_HANDOVER)
|
||||||
link<dev::clock>::tx('A');
|
link<dev::clock>::tx('A');
|
||||||
link<dev::clock>::tx('P');
|
link<dev::clock>::tx('P');
|
||||||
link<dev::clock>::tx('P');
|
link<dev::clock>::tx('P');
|
||||||
|
#endif
|
||||||
|
// The hand-over fixture stays silent: nothing is listening on the USART it
|
||||||
|
// brings up — the loader it hands to speaks those pins directly — so its
|
||||||
|
// banner would be a write into a peer that does not exist.
|
||||||
link<dev::clock>::idle();
|
link<dev::clock>::idle();
|
||||||
}
|
}
|
||||||
|
|||||||
73
test/pbmute.py
Normal file
73
test/pbmute.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Hand-over with a USART left enabled on the loader's own pins.
|
||||||
|
|
||||||
|
A software or autobaud link deployed on a USART's TxD is mute if an
|
||||||
|
application hands over with that USART still enabled: TXEN keeps the USART
|
||||||
|
owning the pin, so the bit-banged transmitter's port writes go nowhere and the
|
||||||
|
loader receives and obeys while answering nothing. The link's init releases it.
|
||||||
|
|
||||||
|
The state is reached the way silicon reaches it — an application that sets up
|
||||||
|
its USART and jumps in with no reset between, so nothing clears UCSRnB for it.
|
||||||
|
The pin ownership itself is modelled by the device runner: simavr wires a
|
||||||
|
USART through IRQs alone and never takes the pin from the port, so without
|
||||||
|
that the mute could not happen here at all (test/pureboot_device.c).
|
||||||
|
|
||||||
|
Usage: pbmute.py <device_bin> <pureboot_elf> <mcu> <hz> <base_hex> <page>
|
||||||
|
<baud> <app_bin> <tool_py> <workdir> <link>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def fail(message):
|
||||||
|
print(f"FAIL: {message}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
device_bin, elf, mcu, hz, base_hex, page, baud, app_bin, tool, workdir, link = sys.argv[1:]
|
||||||
|
page, baud = int(page), int(baud)
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(tool)))
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
import pbsim
|
||||||
|
import pureboot as pb
|
||||||
|
|
||||||
|
if "@" not in link:
|
||||||
|
fail(f"the link {link} names no owning USART — nothing would be under test")
|
||||||
|
|
||||||
|
os.makedirs(workdir, exist_ok=True)
|
||||||
|
dump = os.path.join(workdir, "dump.bin")
|
||||||
|
|
||||||
|
device = pbsim.Device(device_bin, elf, mcu, hz, base_hex, page, baud, dump, link=link)
|
||||||
|
try:
|
||||||
|
port = pb.Port(device.pty, baud)
|
||||||
|
loader = pb.Loader(port)
|
||||||
|
loader.connect(25)
|
||||||
|
resident = loader.info.version
|
||||||
|
|
||||||
|
# Install the fixture and let it take over. It brings up the USART
|
||||||
|
# that owns these pins and jumps straight back in.
|
||||||
|
pb.op_flash(loader, app_bin, erase=False, verify=True)
|
||||||
|
loader.run_application()
|
||||||
|
|
||||||
|
# The loader is running again with that USART enabled behind it. Only
|
||||||
|
# the release makes it audible; without it the connect times out.
|
||||||
|
loader = pb.Loader(port)
|
||||||
|
try:
|
||||||
|
loader.connect(25)
|
||||||
|
except pb.Error as error:
|
||||||
|
fail(f"the loader never answered after the hand-over — the USART still owns its TX pin ({error})")
|
||||||
|
if loader.info.version != resident:
|
||||||
|
fail(f"identity changed across the hand-over: {resident} then {loader.info.version}")
|
||||||
|
|
||||||
|
# Answering is not enough: it has to still be a working loader.
|
||||||
|
pb.verify_pages(loader, pb.plan_flash(open(app_bin, "rb").read(), loader.info))
|
||||||
|
port.close()
|
||||||
|
finally:
|
||||||
|
device.stop()
|
||||||
|
print("pbmute: a loader on a USART's own pins answers after a hand-over that left it enabled")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -10,7 +10,9 @@
|
|||||||
//
|
//
|
||||||
// The link follows the chip's natural default (USART0 on the megas, the
|
// The link follows the chip's natural default (USART0 on the megas, the
|
||||||
// software UART on PB0/PB1 elsewhere) unless -l overrides it: `-l usart1`
|
// software UART on PB0/PB1 elsewhere) unless -l overrides it: `-l usart1`
|
||||||
// for the second instance, `-l sw:B5,B1` for a software build's RX,TX pins.
|
// for the second instance, `-l sw:B5,B1` for a software build's RX,TX pins,
|
||||||
|
// and `-l sw:D0,D1@0` where those pins are a USART's own — see the pin
|
||||||
|
// ownership the bridge models below.
|
||||||
//
|
//
|
||||||
// simavr's tiny cores decode the SPM opcode but attach no NVM module — SPM
|
// simavr's tiny cores decode the SPM opcode but attach no NVM module — SPM
|
||||||
// is a silent no-op (the mega's boot section has one, avr_flash). The
|
// is a silent no-op (the mega's boot section has one, avr_flash). The
|
||||||
@@ -46,6 +48,7 @@ static int link_software;
|
|||||||
static char uart_digit = '0';
|
static char uart_digit = '0';
|
||||||
static char sw_rx_port = 'B', sw_tx_port = 'B';
|
static char sw_rx_port = 'B', sw_tx_port = 'B';
|
||||||
static int sw_rx_bit = 0, sw_tx_bit = 1;
|
static int sw_rx_bit = 0, sw_tx_bit = 1;
|
||||||
|
static char sw_tx_owner = 0; // the USART whose TXD the software link sits on
|
||||||
static const char *dump_path;
|
static const char *dump_path;
|
||||||
static uint32_t reset_pc;
|
static uint32_t reset_pc;
|
||||||
static volatile sig_atomic_t reset_requested;
|
static volatile sig_atomic_t reset_requested;
|
||||||
@@ -61,9 +64,13 @@ static int parse_link(const char *spec)
|
|||||||
link_software = 1;
|
link_software = 1;
|
||||||
if (spec[2] == '\0')
|
if (spec[2] == '\0')
|
||||||
return 0;
|
return 0;
|
||||||
if (sscanf(spec + 2, ":%c%d,%c%d", &sw_rx_port, &sw_rx_bit, &sw_tx_port, &sw_tx_bit) == 4)
|
char owner = 0;
|
||||||
|
int fields = sscanf(spec + 2, ":%c%d,%c%d@%c", &sw_rx_port, &sw_rx_bit, &sw_tx_port, &sw_tx_bit, &owner);
|
||||||
|
if (fields == 4 || fields == 5) {
|
||||||
|
sw_tx_owner = owner;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,10 +205,50 @@ static avr_cycle_count_t tx_sample(avr_t *mcu, avr_cycle_count_t when, void *par
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A USART owns its TxD pin whenever its transmitter is enabled, and the port
|
||||||
|
// register cannot drive it (§20.2 / Atmel-8271 §19.2) — which is why a
|
||||||
|
// bit-banged link deployed on those pins is mute until it clears UCSRnB.
|
||||||
|
// simavr wires a USART entirely through IRQs and never touches the port pin
|
||||||
|
// model, so the ownership does not exist there and the mute cannot happen:
|
||||||
|
// supply it, or the very state this models is untestable. The link spec's
|
||||||
|
// trailing @n names the USART; without one the pins are nobody's.
|
||||||
|
static avr_uart_t *tx_owner;
|
||||||
|
|
||||||
|
static int tx_pin_taken(void)
|
||||||
|
{
|
||||||
|
return tx_owner && avr_regbit_get(avr, tx_owner->txen);
|
||||||
|
}
|
||||||
|
|
||||||
|
// simavr leaves TXEN set in UCSRnB out of reset, where silicon clears the
|
||||||
|
// whole register (§20.11.3) — which would hand the pin to a USART no code has
|
||||||
|
// enabled, making a freshly reset chip mute for reasons hardware does not
|
||||||
|
// have. Reset it the way the datasheet does, so the ownership starts from
|
||||||
|
// nobody's and only an application that really enables the USART takes it.
|
||||||
|
static void reset_tx_owner(void)
|
||||||
|
{
|
||||||
|
if (tx_owner)
|
||||||
|
avr_regbit_clear(avr, tx_owner->txen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void find_tx_owner(void)
|
||||||
|
{
|
||||||
|
for (avr_io_t *io = avr->io_port; io; io = io->next)
|
||||||
|
if (io->kind && strcmp(io->kind, "uart") == 0 && ((avr_uart_t *)io)->name == sw_tx_owner) {
|
||||||
|
tx_owner = (avr_uart_t *)io;
|
||||||
|
reset_tx_owner();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "device: no USART%c to own the software link's TX pin\n", sw_tx_owner);
|
||||||
|
}
|
||||||
|
|
||||||
static void tx_hook(avr_irq_t *irq, uint32_t value, void *param)
|
static void tx_hook(avr_irq_t *irq, uint32_t value, void *param)
|
||||||
{
|
{
|
||||||
(void)irq;
|
(void)irq;
|
||||||
(void)param;
|
(void)param;
|
||||||
|
if (tx_pin_taken()) { // the USART holds the line; the port write goes nowhere
|
||||||
|
tx_level = 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
int level = value & 1;
|
int level = value & 1;
|
||||||
if (!tx_active && tx_level == 1 && level == 0) { // start edge
|
if (!tx_active && tx_level == 1 && level == 0) { // start edge
|
||||||
tx_active = 1;
|
tx_active = 1;
|
||||||
@@ -324,7 +371,8 @@ int main(int argc, char *argv[])
|
|||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"usage: %s [-l link] <pureboot.elf> <mcu> <hz> <base_hex> <page> <baud> <flash_dump>"
|
"usage: %s [-l link] <pureboot.elf> <mcu> <hz> <base_hex> <page> <baud> <flash_dump>"
|
||||||
" [reset_hex] [resume_flash]\n"
|
" [reset_hex] [resume_flash]\n"
|
||||||
" -l link: usart0 | usart1 | sw[:B0,B1] (RX,TX); default: the chip's own\n"
|
" -l link: usart0 | usart1 | sw[:B0,B1[@0]] (RX,TX, then the USART owning\n"
|
||||||
|
" them); default: the chip's own\n"
|
||||||
" reset_hex: reset vector (default: base with a boot section, else 0)\n"
|
" reset_hex: reset vector (default: base with a boot section, else 0)\n"
|
||||||
" resume_flash: raw full-flash image loaded instead of the ELF — a prior\n"
|
" resume_flash: raw full-flash image loaded instead of the ELF — a prior\n"
|
||||||
" run's dump, for power-fail resume tests\n",
|
" run's dump, for power-fail resume tests\n",
|
||||||
@@ -410,6 +458,8 @@ int main(int argc, char *argv[])
|
|||||||
printf("PB_PTY %s\n", uart_pty.pty.slavename);
|
printf("PB_PTY %s\n", uart_pty.pty.slavename);
|
||||||
} else {
|
} else {
|
||||||
bit_cycles = (avr->frequency + baud / 2) / baud; // matches uart.hpp's own rounding exactly
|
bit_cycles = (avr->frequency + baud / 2) / baud; // matches uart.hpp's own rounding exactly
|
||||||
|
if (sw_tx_owner)
|
||||||
|
find_tx_owner();
|
||||||
rx_pin = avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_rx_port), (unsigned)sw_rx_bit);
|
rx_pin = avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_rx_port), (unsigned)sw_rx_bit);
|
||||||
avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_tx_port), (unsigned)sw_tx_bit), tx_hook,
|
avr_irq_register_notify(avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_tx_port), (unsigned)sw_tx_bit), tx_hook,
|
||||||
NULL);
|
NULL);
|
||||||
@@ -447,6 +497,7 @@ int main(int argc, char *argv[])
|
|||||||
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags);
|
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags);
|
||||||
} else {
|
} else {
|
||||||
bridge_reset();
|
bridge_reset();
|
||||||
|
reset_tx_owner();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (link_software && ++since_poll >= 2000) {
|
if (link_software && ++since_poll >= 2000) {
|
||||||
|
|||||||
105
test/test_handshake.py
Normal file
105
test/test_handshake.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Host-tool activation handshake: it must not hang on a flooding target.
|
||||||
|
|
||||||
|
`_handshake` drains the line after it sees a prompt, to absorb a real loader's
|
||||||
|
trailing bytes before it asks for the identity. That drain must be bounded: a
|
||||||
|
target that never falls quiet — a board stuck in a reset loop presents exactly
|
||||||
|
this, ~60 reboots/s of UART-reset garbage in which a stray 0x2b reads as a
|
||||||
|
prompt — otherwise spins the tool forever. Regression for that hang, plus a
|
||||||
|
control that a well-behaved loader still connects.
|
||||||
|
|
||||||
|
Stdlib only, no device: host-tool logic, so it runs on every chip's preset
|
||||||
|
beside pureboot.planner.
|
||||||
|
"""
|
||||||
|
import importlib.util
|
||||||
|
import pathlib
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
PB = pathlib.Path(__file__).resolve().parents[1] / "pureboot" / "pureboot.py"
|
||||||
|
_spec = importlib.util.spec_from_file_location("pureboot", PB)
|
||||||
|
pb = importlib.util.module_from_spec(_spec)
|
||||||
|
_spec.loader.exec_module(pb)
|
||||||
|
|
||||||
|
P = F = 0
|
||||||
|
|
||||||
|
|
||||||
|
def check(name, ok):
|
||||||
|
global P, F
|
||||||
|
P, F = P + (1 if ok else 0), F + (0 if ok else 1)
|
||||||
|
print(f" [{'PASS' if ok else 'FAIL'}] {name}")
|
||||||
|
|
||||||
|
|
||||||
|
class FloodPort:
|
||||||
|
"""A line that never falls quiet: read_available always returns bytes, and
|
||||||
|
they contain a prompt. No identity ever completes."""
|
||||||
|
|
||||||
|
def flush_input(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read_available(self, wait):
|
||||||
|
time.sleep(0.01) # a real read waits; keep the busy loop off a core
|
||||||
|
return b"+\x00\xff"
|
||||||
|
|
||||||
|
def read_exact(self, count, timeout):
|
||||||
|
raise pb.Error("no identity")
|
||||||
|
|
||||||
|
|
||||||
|
class LoaderPort:
|
||||||
|
"""A well-behaved pureboot 5: one prompt to the knock, then quiet, then the
|
||||||
|
slim identity (version 5 + m328p signature) and a closing prompt."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.reads = self.exacts = 0
|
||||||
|
|
||||||
|
def flush_input(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read_available(self, wait):
|
||||||
|
self.reads += 1
|
||||||
|
return b"+" if self.reads == 1 else b"" # prompt once, then settle quiet
|
||||||
|
|
||||||
|
def read_exact(self, count, timeout):
|
||||||
|
self.exacts += 1
|
||||||
|
return b"\x05\x1e\x95\x0f" if self.exacts == 1 else b"+" # identity, then prompt
|
||||||
|
|
||||||
|
|
||||||
|
def terminates(port, wait, budget):
|
||||||
|
"""Run connect_autobaud in a thread; True if it returns/raises within
|
||||||
|
`budget` seconds rather than hanging."""
|
||||||
|
done = threading.Event()
|
||||||
|
|
||||||
|
def run():
|
||||||
|
try:
|
||||||
|
pb.Loader(port).connect_autobaud(wait)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
done.set()
|
||||||
|
|
||||||
|
threading.Thread(target=run, daemon=True).start()
|
||||||
|
return done.wait(budget)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# the hang: a flooding target must not spin the drain forever. With wait=0.5
|
||||||
|
# the whole handshake has to give up well inside a few seconds.
|
||||||
|
check("flooding target: handshake terminates, drain is bounded",
|
||||||
|
terminates(FloodPort(), wait=0.5, budget=4.0))
|
||||||
|
|
||||||
|
# the control: a real loader still connects and reads identity.
|
||||||
|
info = pb.Loader(LoaderPort()).connect_autobaud(2.0)
|
||||||
|
check("well-behaved loader still connects (version 5)", info.version == 5)
|
||||||
|
|
||||||
|
print(f"\n {P} passed, {F} failed")
|
||||||
|
return 1 if F else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user