pureboot v8: one-wire on every backend

HALF_DUPLEX deploys a shared line per backend. The hardware USART takes
the library's .half_duplex turn-around — RXD and TXD tied off-chip, each
reply byte held to transmit-complete before the line can be released
(m8 404 B, m328P 440, 1284P 460; the window poll runs through the
outlined release-line call at 18 or 22 cycles a poll, measured off the
built loops and held per chip by pureboot.window.halfduplex). The
software and autobaud links fold onto the RX pin — RX == TX spells the
same — and cost nothing: the frame's direction wrap is what the dropped
second-pin init paid, and the worst image in the space is unchanged at
the 1284s' 502 of 512, now with its one-wire twin proven equal across
the exhaustive matrix. The host gains --one-wire, the echo discard a
shared line requires: the adapter's echo is matched byte for byte and a
reply interleaving a blind write — a loader already in session
re-prompts inside the knock — is held for the reader. The device runner
models the shared line by direction (drives only while the firmware's
DDR reads input, decodes only while the firmware owns it, supplies the
host-side echo), extends the USART pin-ownership model to RXEN's hold
on RXD, and starts the pty USART from the datasheet's zeroed UCSR#B:
simavr's TXEN-set reset plus its clear-UDRE-on-TXEN-drop otherwise
wedges the first transmitter after a receiver-only program, which the
half-duplex window gate caught as a banner that never came. v7 is
tagged at its era's last commit; v8 changes nothing on the wire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 02:32:11 +02:00
parent 47419400f6
commit f71d76a815
13 changed files with 507 additions and 50 deletions

View File

@@ -166,7 +166,9 @@ set_property(GLOBAL PROPERTY PUREBOOT_WRAP "${_pb_wrap}")
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_USART1 ${_pb_has_usart1})
set_property(GLOBAL PROPERTY PUREBOOT_USART0_RX ${_pb_usart0_rx})
set_property(GLOBAL PROPERTY PUREBOOT_USART0_TX ${_pb_usart0_tx})
set_property(GLOBAL PROPERTY PUREBOOT_USART1_RX ${_pb_usart1_rx})
set_property(GLOBAL PROPERTY PUREBOOT_USART1_TX ${_pb_usart1_tx})
# The port's own build (tests, the size matrix) reads the geometry from the
@@ -236,7 +238,8 @@ endfunction()
# pureboot_add_loader(<name> [CLOCK <hz>] [BAUD <bd>]
# [SERIAL auto|hardware|software|autobaud] [USART <n>]
# [RX <pin>] [TX <pin>] [TIMEOUT <s>] [OSCCAL <byte>])
# [RX <pin>] [TX <pin>] [TIMEOUT <s>] [OSCCAL <byte>]
# [HALF_DUPLEX])
#
# The loader target plus its flashable images (<name>.hex for a programmer,
# <name>.bin for --update-loader). The resolved deployment is stamped on the
@@ -244,6 +247,11 @@ endfunction()
# 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.
#
# HALF_DUPLEX is the one-wire deployment, per backend: on the hardware USART
# it enables the library's .half_duplex turn-around (RXD and TXD tied
# together off-chip); on a software or autobaud link it puts both directions
# on the RX pin — the same thing RX == TX spells directly.
#
# 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
@@ -257,7 +265,7 @@ endfunction()
# purely for the application's benefit, its own link being clock-free. No
# value, no code.
function(pureboot_add_loader name)
cmake_parse_arguments(PB "" "CLOCK;BAUD;SERIAL;USART;RX;TX;TIMEOUT;OSCCAL" "" ${ARGN})
cmake_parse_arguments(PB "HALF_DUPLEX" "CLOCK;BAUD;SERIAL;USART;RX;TX;TIMEOUT;OSCCAL" "" ${ARGN})
if(PB_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "pureboot_add_loader(${name}): unknown arguments ${PB_UNPARSED_ARGUMENTS}")
endif()
@@ -294,6 +302,9 @@ function(pureboot_add_loader name)
message(FATAL_ERROR "pureboot_add_loader(${name}): ${LIBAVR_MCU} has no hardware USART")
endif()
set(_serial_defines PUREBOOT_USART=${PB_USART})
if(PB_HALF_DUPLEX)
list(APPEND _serial_defines PUREBOOT_HALF_DUPLEX)
endif()
set(_link usart${PB_USART})
else()
if(PB_SERIAL STREQUAL "auto")
@@ -303,6 +314,9 @@ function(pureboot_add_loader name)
endif()
if(_usart)
set(_link usart0)
if(PB_HALF_DUPLEX)
set(_serial_defines PUREBOOT_HALF_DUPLEX)
endif()
else()
set(PB_SERIAL software)
endif()
@@ -311,6 +325,15 @@ function(pureboot_add_loader name)
if(NOT PB_RX)
set(PB_RX pb0)
endif()
if(PB_HALF_DUPLEX)
# One-wire: both directions on the RX pin. RX == TX spells
# the same deployment directly.
if(PB_TX AND NOT PB_TX STREQUAL PB_RX)
message(FATAL_ERROR "pureboot_add_loader(${name}): HALF_DUPLEX puts both "
"directions on RX (${PB_RX}); TX ${PB_TX} contradicts it")
endif()
set(PB_TX ${PB_RX})
endif()
if(NOT PB_TX)
set(PB_TX pb1)
endif()
@@ -334,10 +357,19 @@ function(pureboot_add_loader name)
string(REPLACE "SW" "sw" _link ${_link})
get_property(_tx0 GLOBAL PROPERTY PUREBOOT_USART0_TX)
get_property(_tx1 GLOBAL PROPERTY PUREBOOT_USART1_TX)
get_property(_rx0 GLOBAL PROPERTY PUREBOOT_USART0_RX)
get_property(_rx1 GLOBAL PROPERTY PUREBOOT_USART1_RX)
if(_usart AND PB_TX STREQUAL _tx0)
set(_link "${_link}@0")
elseif(_usart1 AND PB_TX STREQUAL _tx1)
set(_link "${_link}@1")
elseif(PB_TX STREQUAL PB_RX AND _usart AND PB_RX STREQUAL _rx0)
# One-wire on a USART's RXD: RXEN forces that pin's direction
# (§20.7.3), so the driven shared pin is held exactly like a
# TXD — the harness models the hold either way.
set(_link "${_link}@0")
elseif(PB_TX STREQUAL PB_RX AND _usart1 AND PB_RX STREQUAL _rx1)
set(_link "${_link}@1")
endif()
endif()
endif()