Compare commits
39
Commits
v3
..
29b9e47d3d
No files matched your search
@@ -1,3 +0,0 @@
|
|||||||
[submodule "libavr"]
|
|
||||||
path = libavr
|
|
||||||
url = ../libavr.git
|
|
||||||
+17
-64
@@ -8,9 +8,6 @@ include(FetchContent)
|
|||||||
if(NOT LIBAVR_ROOT AND DEFINED ENV{LIBAVR_ROOT})
|
if(NOT LIBAVR_ROOT AND DEFINED ENV{LIBAVR_ROOT})
|
||||||
set(LIBAVR_ROOT $ENV{LIBAVR_ROOT})
|
set(LIBAVR_ROOT $ENV{LIBAVR_ROOT})
|
||||||
endif()
|
endif()
|
||||||
if(NOT LIBAVR_ROOT)
|
|
||||||
set(LIBAVR_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/libavr)
|
|
||||||
endif()
|
|
||||||
if(LIBAVR_ROOT)
|
if(LIBAVR_ROOT)
|
||||||
FetchContent_Declare(libavr SOURCE_DIR ${LIBAVR_ROOT})
|
FetchContent_Declare(libavr SOURCE_DIR ${LIBAVR_ROOT})
|
||||||
else()
|
else()
|
||||||
@@ -236,11 +233,11 @@ 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), and the baud
|
||||||
# through the shapes its bit timing takes — each combination must still
|
# through the two shapes its bit timing takes — each combination must
|
||||||
# fit the chip's slot budget. Pins are size-neutral (port and bit are
|
# still fit the chip's slot budget. Pins are size-neutral (port and bit
|
||||||
# immediate operands) and the timeout is a constant, so neither adds an
|
# are immediate operands) and the timeout is a constant, so neither adds
|
||||||
# axis. The stock build is one point of this matrix and already has its
|
# an axis. The stock build is one point of this matrix and already has
|
||||||
# test.
|
# 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
|
||||||
@@ -248,67 +245,14 @@ 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)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# One point of the exhaustive matrix, named from its resolved parameters
|
|
||||||
# so the enumeration cannot collide with itself. Unreachable rates drop
|
|
||||||
# out here rather than aborting the configure.
|
|
||||||
function(pureboot_matrix_point hz baud link)
|
|
||||||
if(link STREQUAL "software")
|
|
||||||
pureboot_baud_feasible(${hz} ${baud} 1 _ok)
|
|
||||||
set(_args SERIAL software)
|
|
||||||
else()
|
|
||||||
pureboot_baud_feasible(${hz} ${baud} 0 _ok)
|
|
||||||
set(_args USART ${link})
|
|
||||||
endif()
|
|
||||||
if(_ok)
|
|
||||||
pureboot_size_variant(pbm_${hz}_${baud}_${link} CLOCK ${hz} BAUD ${baud} ${_args})
|
|
||||||
endif()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# Clock points: the shipped-fuse floor (CKDIV8), the calibrated RC, and
|
# 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
|
# the crystal the stock build assumes (the tiny13's ladder is its own RC
|
||||||
# menu — it has no crystal option).
|
# menu — it has no crystal option).
|
||||||
if(LIBAVR_MCU MATCHES "^attiny13")
|
if(LIBAVR_MCU MATCHES "^attiny13")
|
||||||
set(_matrix_clocks 1200000 4800000 9600000)
|
set(_matrix_clocks 1200000 4800000 9600000)
|
||||||
set(_full_clocks 128000 600000 1200000 4800000 9600000)
|
|
||||||
else()
|
else()
|
||||||
set(_matrix_clocks 1000000 8000000 16000000)
|
set(_matrix_clocks 1000000 8000000 16000000)
|
||||||
set(_full_clocks 128000 1000000 1843200 2000000 3686400 4000000 7372800 8000000
|
|
||||||
11059200 12000000 14745600 16000000 18432000 20000000)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The exhaustive cross product: every clock a deployment plausibly runs
|
|
||||||
# — the internal oscillators, the shipped CKDIV8 floor, the plain
|
|
||||||
# crystals and the UART crystals — against every rate, against every
|
|
||||||
# backend. Beyond the ladder the list carries the slow rates a
|
|
||||||
# sub-megahertz oscillator is left with, which no ladder rate reaches
|
|
||||||
# (16000 Bd is the only rate the 128 kHz oscillator holds exactly); at
|
|
||||||
# the fast clocks those same rates also select the software UART's
|
|
||||||
# 16-bit _delay_loop_2 bit spin (two words more setup at each of its five
|
|
||||||
# sites), the largest image the space produces and a shape the ladder
|
|
||||||
# default — always the *fastest* rate a clock reaches — never picks.
|
|
||||||
#
|
|
||||||
# Bounded to one chip per size-bearing class: flash addressing (the
|
|
||||||
# word-addressed 1284), hand-over shape (the patched vector on the tinies
|
|
||||||
# and m48s), page size, and USART inventory. Everything else in the image
|
|
||||||
# is chip-independent code, so a further chip buys builds and no
|
|
||||||
# coverage; every chip outside the set carries the compact matrix.
|
|
||||||
get_property(_full_bauds GLOBAL PROPERTY PUREBOOT_BAUD_LADDER)
|
|
||||||
list(APPEND _full_bauds 16000 4800 2400 1200)
|
|
||||||
set(_matrix_spot attiny13a attiny85 atmega48pa atmega8a atmega168pa
|
|
||||||
atmega328p atmega164a atmega644a atmega1284p)
|
|
||||||
if(DEFINED ENV{PUREBOOT_FULL_MATRIX} AND LIBAVR_MCU IN_LIST _matrix_spot)
|
|
||||||
foreach(_matrix_hz IN LISTS _full_clocks)
|
|
||||||
foreach(_matrix_baud IN LISTS _full_bauds)
|
|
||||||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} software)
|
|
||||||
if(PUREBOOT_HAS_USART)
|
|
||||||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 0)
|
|
||||||
endif()
|
|
||||||
if(PUREBOOT_HAS_USART1)
|
|
||||||
pureboot_matrix_point(${_matrix_hz} ${_matrix_baud} 1)
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endforeach()
|
|
||||||
else()
|
|
||||||
foreach(_matrix_hz IN LISTS _matrix_clocks)
|
foreach(_matrix_hz IN LISTS _matrix_clocks)
|
||||||
math(EXPR _matrix_khz "${_matrix_hz} / 1000")
|
math(EXPR _matrix_khz "${_matrix_hz} / 1000")
|
||||||
if(PUREBOOT_HAS_USART OR NOT _matrix_hz EQUAL _pb_stock_hz)
|
if(PUREBOOT_HAS_USART OR NOT _matrix_hz EQUAL _pb_stock_hz)
|
||||||
@@ -321,13 +265,22 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
pureboot_size_variant(pureboot_usart1_${_matrix_khz}k CLOCK ${_matrix_hz} USART 1)
|
pureboot_size_variant(pureboot_usart1_${_matrix_khz}k CLOCK ${_matrix_hz} USART 1)
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
list(GET _matrix_clocks -1 _matrix_top_hz)
|
|
||||||
pureboot_size_variant(pureboot_sw_wide CLOCK ${_matrix_top_hz} BAUD 9600 SERIAL software)
|
|
||||||
endif()
|
|
||||||
if(PUREBOOT_HAS_USART1)
|
if(PUREBOOT_HAS_USART1)
|
||||||
pureboot_size_variant(pureboot_usart1 USART 1)
|
pureboot_size_variant(pureboot_usart1 USART 1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# The baud axis, whose one size-bearing shape the ladder never picks: a
|
||||||
|
# software UART spins out each bit with _delay_loop_1 while the count
|
||||||
|
# fits a byte and with the 16-bit _delay_loop_2 beyond it, two words more
|
||||||
|
# setup at every one of its five sites — the largest image the
|
||||||
|
# configuration space produces. The ladder default takes the *fastest*
|
||||||
|
# rate a clock reaches, which always lands in the byte, so the wide form
|
||||||
|
# needs the slowest ladder rate against the fastest clock to appear. The
|
||||||
|
# hardware USART has no such shape: its baud is a divisor constant, and
|
||||||
|
# the ladder's U2X solutions are already its larger form.
|
||||||
|
list(GET _matrix_clocks -1 _matrix_top_hz)
|
||||||
|
pureboot_size_variant(pureboot_sw_wide CLOCK ${_matrix_top_hz} BAUD 9600 SERIAL software)
|
||||||
|
|
||||||
# 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
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"hidden": true,
|
"hidden": true,
|
||||||
"generator": "Ninja",
|
"generator": "Ninja",
|
||||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||||
"toolchainFile": "${sourceDir}/libavr/cmake/avr-toolchain.cmake",
|
"toolchainFile": "$env{LIBAVR_ROOT}/cmake/avr-toolchain.cmake",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "Release",
|
"CMAKE_BUILD_TYPE": "Release",
|
||||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||||
|
|||||||
-1
Submodule libavr deleted from a8ed8c4851.
+7
-22
@@ -146,19 +146,16 @@ 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)
|
||||||
|
|
||||||
# The rates a default may pick, fastest first.
|
# The fastest standard rate the clock reaches within 2.5 %, by the same
|
||||||
set_property(GLOBAL PROPERTY PUREBOOT_BAUD_LADDER 115200 57600 38400 19200 9600)
|
# best-of-U2X-and-plain divisor search libavr's solve_baud runs, so a default
|
||||||
|
|
||||||
# Whether <baud> is reachable from <clock> within 2.5 %, by the same
|
|
||||||
# best-of-U2X-and-plain divisor search libavr's solve_baud runs, so a build
|
|
||||||
# never trips the compile-time error it is checked against. A software build
|
# never trips the compile-time error it is checked against. A software build
|
||||||
# also needs the polled receiver's 100-cycles-a-bit floor: at low clocks the
|
# also needs the polled receiver's 100-cycles-a-bit floor: at low clocks the
|
||||||
# U2X divisor reaches rates the bit-banged sampler cannot.
|
# U2X divisor reaches rates the bit-banged sampler cannot.
|
||||||
function(pureboot_baud_feasible clock baud software outvar)
|
function(pureboot_default_baud clock software outvar)
|
||||||
set(${outvar} 0 PARENT_SCOPE)
|
foreach(baud 115200 57600 38400 19200 9600)
|
||||||
math(EXPR _cycles "${clock} / ${baud}")
|
math(EXPR _cycles "${clock} / ${baud}")
|
||||||
if(software AND _cycles LESS 100)
|
if(software AND _cycles LESS 100)
|
||||||
return()
|
continue()
|
||||||
endif()
|
endif()
|
||||||
foreach(divisor 8 16)
|
foreach(divisor 8 16)
|
||||||
math(EXPR _step "${divisor} * ${baud}")
|
math(EXPR _step "${divisor} * ${baud}")
|
||||||
@@ -173,24 +170,12 @@ function(pureboot_baud_feasible clock baud software outvar)
|
|||||||
endif()
|
endif()
|
||||||
math(EXPR _error_bp "${_delta} * 10000 / ${baud}")
|
math(EXPR _error_bp "${_delta} * 10000 / ${baud}")
|
||||||
if(_error_bp LESS_EQUAL 250)
|
if(_error_bp LESS_EQUAL 250)
|
||||||
set(${outvar} 1 PARENT_SCOPE)
|
|
||||||
return()
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
endfunction()
|
|
||||||
|
|
||||||
# The fastest ladder rate the clock reaches.
|
|
||||||
function(pureboot_default_baud clock software outvar)
|
|
||||||
get_property(_ladder GLOBAL PROPERTY PUREBOOT_BAUD_LADDER)
|
|
||||||
foreach(baud ${_ladder})
|
|
||||||
pureboot_baud_feasible(${clock} ${baud} ${software} _ok)
|
|
||||||
if(_ok)
|
|
||||||
set(${outvar} ${baud} PARENT_SCOPE)
|
set(${outvar} ${baud} PARENT_SCOPE)
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
message(FATAL_ERROR "pureboot: no standard baud rate fits a ${clock} Hz clock within 2.5 % "
|
endforeach()
|
||||||
"— pass BAUD <rate> to deploy a non-standard one")
|
message(FATAL_ERROR "pureboot: no standard baud rate fits a ${clock} Hz clock within 2.5 %")
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# pureboot_add_loader(<name> [CLOCK <hz>] [BAUD <bd>]
|
# pureboot_add_loader(<name> [CLOCK <hz>] [BAUD <bd>]
|
||||||
|
|||||||
+3
-10
@@ -127,15 +127,15 @@ addresses and all counts are bytes.
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `b` | — | the 12-byte info block |
|
| `b` | — | the 12-byte info block |
|
||||||
| `R` | addr16, n8 | n flash bytes (n = 0 means 256) |
|
| `R` | addr16, n8 | n flash bytes (n = 0 means 256) |
|
||||||
| `W` | addr16 (any address in the page), then one page of data | — (completion = next prompt) |
|
| `W` | addr16, then one page of data | — (completion = next prompt) |
|
||||||
| `r` | addr16, n8 | n EEPROM bytes (n = 0 means 256) |
|
| `r` | addr16, n8 | n EEPROM bytes (n = 0 means 256) |
|
||||||
| `w` | addr16, n8, then n data bytes | `+` per byte, sent once its write has begun |
|
| `w` | addr16, n8, then n data bytes | `+` per byte, sent once its write has begun |
|
||||||
| `F` | — | 4 bytes: low fuse, lock, extended fuse, high fuse |
|
| `F` | — | 4 bytes: low fuse, lock, extended fuse, high fuse |
|
||||||
| `J` | word address (16-bit) | `+`, then execution continues there |
|
| `J` | word address (16-bit) | `+`, then execution continues there |
|
||||||
| other | — | ignored; the loop re-prompts (send a junk byte, await `+`, to resync) |
|
| other | — | ignored; the loop re-prompts (send a junk byte, await `+`, to resync) |
|
||||||
|
|
||||||
`W` streams exactly one SPM page (size from the info block) into the buffer,
|
`W` streams exactly one page-aligned SPM page (size from the info block) into
|
||||||
then erases and programs — except pages inside the 512-byte slot
|
the buffer, then erases and programs — except pages inside the 512-byte slot
|
||||||
the loader is *running* in, which are drained and left alone, so a broken host
|
the loader is *running* in, which are drained and left alone, so a broken host
|
||||||
cannot brick the running copy and a staged copy may rewrite the resident.
|
cannot brick the running copy and a staged copy may rewrite the resident.
|
||||||
|
|
||||||
@@ -309,13 +309,6 @@ Per chip preset, `ctest` runs:
|
|||||||
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 immediate operands and
|
||||||
the timeout is a constant: neither is an axis;
|
the timeout is a constant: neither is an axis;
|
||||||
- `pbm_*.size` — under `--full`, the exhaustive cross product replacing that
|
|
||||||
compact matrix: every plausible oscillator (the internal ones, the CKDIV8
|
|
||||||
floor, the plain and the UART crystals) × every rate reachable from it ×
|
|
||||||
every backend, unreachable combinations dropping out rather than aborting
|
|
||||||
the configure. Bounded to one chip per size-bearing class — flash
|
|
||||||
addressing, hand-over shape, page size, USART inventory — since everything
|
|
||||||
else in the image is chip-independent code;
|
|
||||||
- `pureboot.pi` — the position-independence lint: no absolute `jmp`/`call`, the
|
- `pureboot.pi` — the position-independence lint: no absolute `jmp`/`call`, the
|
||||||
info block within the image's first 256 bytes;
|
info block within the image's first 256 bytes;
|
||||||
- `pureboot.planner` — the host tool's pure logic: programming orders and their
|
- `pureboot.planner` — the host tool's pure logic: programming orders and their
|
||||||
|
|||||||
@@ -309,20 +309,18 @@ void store_eeprom(std::uint16_t address, std::uint8_t count)
|
|||||||
// that write clears the condition and the host's read-back rewrites the page.
|
// that write clears the condition and the host's read-back rewrites the page.
|
||||||
void program_flash(std::uint16_t wire_address, std::uint8_t slot_high)
|
void program_flash(std::uint16_t wire_address, std::uint8_t slot_high)
|
||||||
{
|
{
|
||||||
// The address names a page, so its in-page bits are dropped and the walk
|
// One induction either way: a byte-addressed wire address walks the page
|
||||||
// starts at the page base — one induction either way: a byte-addressed
|
// itself (aligned, so the offset bits wrap to zero), while a word one
|
||||||
// wire address walks the page itself (the offset bits wrap back to zero),
|
// becomes a byte cursor once. The slot index is the wire address's high
|
||||||
// while a word one becomes a byte cursor once. The slot index is the wire
|
// byte — on byte-addressed chips the byte address's, with the low bit
|
||||||
// address's high byte — on byte-addressed chips the byte address's, with
|
// dropped, since a slot is two of those.
|
||||||
// the low bit dropped, since a slot is two of those.
|
|
||||||
spm::flash_address_t address;
|
spm::flash_address_t address;
|
||||||
std::uint8_t page_high;
|
std::uint8_t page_high;
|
||||||
if constexpr (word_flash) {
|
if constexpr (word_flash) {
|
||||||
// A page is aligned, so it never crosses 64 KiB: RAMPZ is a per-page
|
// A page is aligned, so it never crosses 64 KiB: RAMPZ is a per-page
|
||||||
// constant and the 16-bit Z's low byte is the whole in-page offset.
|
// constant and the 16-bit Z's low byte is the whole in-page offset.
|
||||||
const std::uint8_t rampz = static_cast<std::uint8_t>(wire_address >> 15);
|
const std::uint8_t rampz = static_cast<std::uint8_t>(wire_address >> 15);
|
||||||
const std::uint16_t z0 =
|
const std::uint16_t z0 = static_cast<std::uint16_t>(wire_address << 1);
|
||||||
static_cast<std::uint16_t>(wire_address << 1) & ~static_cast<std::uint16_t>(page - 1);
|
|
||||||
std::uint16_t z = z0;
|
std::uint16_t z = z0;
|
||||||
do {
|
do {
|
||||||
std::uint8_t low = link::rx();
|
std::uint8_t low = link::rx();
|
||||||
@@ -333,7 +331,7 @@ void program_flash(std::uint16_t wire_address, std::uint8_t slot_high)
|
|||||||
address = (static_cast<spm::flash_address_t>(rampz) << 16) | z0;
|
address = (static_cast<spm::flash_address_t>(rampz) << 16) | z0;
|
||||||
page_high = static_cast<std::uint8_t>(wire_address >> 8);
|
page_high = static_cast<std::uint8_t>(wire_address >> 8);
|
||||||
} else {
|
} else {
|
||||||
address = static_cast<spm::flash_address_t>(wire_address & ~static_cast<std::uint16_t>(page - 1));
|
address = static_cast<spm::flash_address_t>(wire_address);
|
||||||
do {
|
do {
|
||||||
std::uint8_t low = link::rx();
|
std::uint8_t low = link::rx();
|
||||||
std::uint8_t high = link::rx();
|
std::uint8_t high = link::rx();
|
||||||
|
|||||||
+8
-19
@@ -364,37 +364,26 @@ class Loader:
|
|||||||
self.info = None
|
self.info = None
|
||||||
|
|
||||||
def connect(self, wait):
|
def connect(self, wait):
|
||||||
"""Knock until the info block comes back. The block is what proves the
|
"""Knock until the window answers, then read the info block. Also
|
||||||
loader is listening — a prompt byte alone does not, since one left over
|
converges into a live session: the knock bytes are ignored there and
|
||||||
from a previous session can still be in the pipeline while the port
|
the drain absorbs whatever they produced."""
|
||||||
opening resets the device into a fresh activation window, where a
|
self.port.flush_input()
|
||||||
command without its knock is discarded. Each attempt is therefore the
|
|
||||||
whole handshake, retried until it produces the block or the window
|
|
||||||
closes. Also converges into a live session: the knock bytes are ignored
|
|
||||||
there and the drain absorbs whatever they produced."""
|
|
||||||
deadline = time.monotonic() + wait
|
deadline = time.monotonic() + wait
|
||||||
knocks = 0
|
knocks = 0
|
||||||
while True:
|
while True:
|
||||||
self.port.flush_input()
|
|
||||||
self.port.write(b"pb")
|
self.port.write(b"pb")
|
||||||
knocks += 1
|
knocks += 1
|
||||||
if PROMPT in self.port.read_available(0.4):
|
if PROMPT in self.port.read_available(0.4):
|
||||||
|
break
|
||||||
|
if time.monotonic() > deadline:
|
||||||
|
raise Error("no answer — reset the device within its activation window")
|
||||||
while self.port.read_available(0.3):
|
while self.port.read_available(0.3):
|
||||||
pass
|
pass
|
||||||
self.port.write(b"b")
|
self.port.write(b"b")
|
||||||
try:
|
self.info = Info(self.port.read_exact(12, 2.0))
|
||||||
block = self.port.read_exact(12, 2.0)
|
|
||||||
except Error:
|
|
||||||
block = b""
|
|
||||||
# A version the tool cannot speak is the loader's own answer,
|
|
||||||
# not a failed knock: Info reports it rather than retrying.
|
|
||||||
if block[0:2] == b"PB":
|
|
||||||
self.info = Info(block)
|
|
||||||
self._expect_prompt()
|
self._expect_prompt()
|
||||||
verbose(f"loader answered knock {knocks}; info block read")
|
verbose(f"loader answered knock {knocks}; info block read")
|
||||||
return self.info
|
return self.info
|
||||||
if time.monotonic() > deadline:
|
|
||||||
raise Error("no answer — reset the device within its activation window")
|
|
||||||
|
|
||||||
def _expect_prompt(self, timeout=2.0):
|
def _expect_prompt(self, timeout=2.0):
|
||||||
byte = self.port.read_exact(1, timeout)
|
byte = self.port.read_exact(1, timeout)
|
||||||
|
|||||||
@@ -105,17 +105,6 @@ def main():
|
|||||||
# was never told about is a loader it would refuse to speak to.
|
# was never told about is a loader it would refuse to speak to.
|
||||||
if live.version != pb.NEWEST_LOADER:
|
if live.version != pb.NEWEST_LOADER:
|
||||||
fail(f"loader reports pureboot {live.version}, the tool's newest is {pb.NEWEST_LOADER}")
|
fail(f"loader reports pureboot {live.version}, the tool's newest is {pb.NEWEST_LOADER}")
|
||||||
|
|
||||||
# A W addressed inside a page rather than at its base must still
|
|
||||||
# consume exactly one page and prompt. The loader's own slot is
|
|
||||||
# the target — it is drained and never programmed — and the
|
|
||||||
# payload is erased-state bytes, so the probe can disturb neither
|
|
||||||
# the image nor the page buffer it leaves behind.
|
|
||||||
wire = wire_base + 1
|
|
||||||
port.write(bytes((ord("W"), wire & 0xFF, wire >> 8)) + b"\xff" * page)
|
|
||||||
if port.read_exact(1, 5.0) != pb.PROMPT:
|
|
||||||
fail("unaligned W did not return to the prompt")
|
|
||||||
|
|
||||||
loader.run_application()
|
loader.run_application()
|
||||||
banner = port.read_exact(3, 5.0)
|
banner = port.read_exact(3, 5.0)
|
||||||
if banner != b"APP":
|
if banner != b"APP":
|
||||||
|
|||||||
@@ -280,68 +280,6 @@ def main():
|
|||||||
if device.writes != pb.RETRIES + 1:
|
if device.writes != pb.RETRIES + 1:
|
||||||
fail(f"unrepairable page took {device.writes} writes, expected {pb.RETRIES + 1}")
|
fail(f"unrepairable page took {device.writes} writes, expected {pb.RETRIES + 1}")
|
||||||
|
|
||||||
# The knock handshake against a device that is not listening yet — the
|
|
||||||
# state a port open leaves behind: it resets the chip into a fresh
|
|
||||||
# activation window while the previous session's prompt is still in
|
|
||||||
# flight, so the first knock is lost and a prompt arrives anyway.
|
|
||||||
class FakePort:
|
|
||||||
"""A loader in its activation window, plus `lost` leading writes the
|
|
||||||
reset swallows and one stale prompt still on the wire."""
|
|
||||||
|
|
||||||
def __init__(self, info_raw, lost=0, stale=b"", active=False):
|
|
||||||
self.info_raw = info_raw
|
|
||||||
self.lost = lost
|
|
||||||
self.inflight = bytearray(stale)
|
|
||||||
self.rx = bytearray()
|
|
||||||
self.active = active
|
|
||||||
self.last = None
|
|
||||||
|
|
||||||
def flush_input(self):
|
|
||||||
self.rx.clear()
|
|
||||||
|
|
||||||
def write(self, data):
|
|
||||||
if self.lost:
|
|
||||||
self.lost -= 1
|
|
||||||
return
|
|
||||||
for byte in bytes(data):
|
|
||||||
if not self.active:
|
|
||||||
self.active = self.last == ord("p") and byte == ord("b")
|
|
||||||
self.last = byte
|
|
||||||
if self.active:
|
|
||||||
self.rx += pb.PROMPT
|
|
||||||
elif byte == ord("b"):
|
|
||||||
self.rx += self.info_raw + pb.PROMPT
|
|
||||||
else:
|
|
||||||
self.rx += pb.PROMPT
|
|
||||||
|
|
||||||
def read_available(self, wait):
|
|
||||||
self.rx = self.inflight + self.rx # the stale prompt lands late
|
|
||||||
self.inflight.clear()
|
|
||||||
out, self.rx = bytes(self.rx), bytearray()
|
|
||||||
return out
|
|
||||||
|
|
||||||
def read_exact(self, count, timeout):
|
|
||||||
if len(self.rx) < count:
|
|
||||||
raise pb.Error(f"timeout: got {len(self.rx)} of {count} bytes")
|
|
||||||
out, self.rx = bytes(self.rx[:count]), self.rx[count:]
|
|
||||||
return out
|
|
||||||
|
|
||||||
raw = info_of(pb, 0x7E00, 128, False, 0x8000).raw
|
|
||||||
for what, port in (
|
|
||||||
("clean window", FakePort(raw)),
|
|
||||||
("stale prompt over a lost knock", FakePort(raw, lost=1, stale=pb.PROMPT)),
|
|
||||||
("live session", FakePort(raw, active=True)),
|
|
||||||
):
|
|
||||||
info = pb.Loader(port).connect(5)
|
|
||||||
if info.raw != raw:
|
|
||||||
fail(f"connect ({what}) returned {info.raw.hex()}")
|
|
||||||
|
|
||||||
# A device that never answers still says so, and a version the tool cannot
|
|
||||||
# speak is reported as such rather than retried into a timeout.
|
|
||||||
expect_error("dead device", lambda: pb.Loader(FakePort(raw, lost=99)).connect(0), "no answer")
|
|
||||||
old = bytes(raw[:2]) + bytes((pb.NEWEST_LOADER + 1,)) + bytes(raw[3:])
|
|
||||||
expect_error("unspeakable version", lambda: pb.Loader(FakePort(old)).connect(5), "needs a newer tool")
|
|
||||||
|
|
||||||
print("test_planner: all planner and policy checks pass")
|
print("test_planner: all planner and policy checks pass")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -2,14 +2,12 @@
|
|||||||
# The port's gate: every chip's generated workflow — build, size matrix, and
|
# The port's gate: every chip's generated workflow — build, size matrix, and
|
||||||
# the simulator-driven protocol suites. --full adds the reflect-spot builds
|
# the simulator-driven protocol suites. --full adds the reflect-spot builds
|
||||||
# (libavr's rule: reflect compiles are bounded to its spot set, never the
|
# (libavr's rule: reflect compiles are bounded to its spot set, never the
|
||||||
# full matrix) and swaps the compact size matrix for the exhaustive
|
# full matrix). LIBAVR_ROOT must point at the libavr checkout.
|
||||||
# clock × baud × backend cross product. LIBAVR_ROOT must point at the libavr
|
|
||||||
# checkout.
|
|
||||||
set -e
|
set -e
|
||||||
cd "$(dirname "$0")/.."
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
full=0
|
full=0
|
||||||
[[ "$1" == "--full" ]] && { full=1; shift; export PUREBOOT_FULL_MATRIX=1; }
|
[[ "$1" == "--full" ]] && { full=1; shift; }
|
||||||
|
|
||||||
CHIPS=(attiny13 attiny13a attiny25 attiny45 attiny85
|
CHIPS=(attiny13 attiny13a attiny25 attiny45 attiny85
|
||||||
atmega8 atmega8a atmega16 atmega16a atmega32 atmega32a
|
atmega8 atmega8a atmega16 atmega16a atmega32 atmega32a
|
||||||
|
|||||||
Reference in new issue
Block a user