Compare commits
6 Commits
1b18f10f4e
...
v6
| Author | SHA1 | Date | |
|---|---|---|---|
| a3ea099105 | |||
| c535c4756c | |||
| 321ff8a4ee | |||
| 531ae6c8dc | |||
| b3f41caf6e | |||
| 7716e1e291 |
@@ -22,15 +22,17 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
|
|
||||||
# The behavioral tests drive the real wire protocols over a simavr pty
|
# The behavioral tests drive the real wire protocols over a simavr pty
|
||||||
# (as the host tools do) and actually flash the device. The runners are
|
# (as the host tools do) and actually flash the device. The runners are
|
||||||
# host programs built at configure time against libsimavr; if they or
|
# host programs built at configure time against libsimavr (C++23 — what
|
||||||
# Python are missing, only the size tests run.
|
# the distribution's compiler speaks in full); if they or Python are
|
||||||
find_program(_host_cc NAMES cc gcc)
|
# missing, only the size tests run.
|
||||||
|
find_program(_host_cxx NAMES c++ g++)
|
||||||
find_package(Python3 COMPONENTS Interpreter)
|
find_package(Python3 COMPONENTS Interpreter)
|
||||||
if(_host_cc AND Python3_FOUND)
|
if(_host_cxx AND Python3_FOUND)
|
||||||
set(PB_DEVICE ${CMAKE_BINARY_DIR}/pureboot_device)
|
set(PB_DEVICE ${CMAKE_BINARY_DIR}/pureboot_device)
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts
|
COMMAND ${_host_cxx} -std=c++23 -Wall -Wextra -O2
|
||||||
-o ${PB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pureboot_device.c
|
-I/usr/include/simavr -I/usr/include/simavr/parts
|
||||||
|
-o ${PB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/pureboot_device.cpp
|
||||||
-lsimavr -lsimavrparts -lelf -lutil
|
-lsimavr -lsimavrparts -lelf -lutil
|
||||||
RESULT_VARIABLE _pbdev_res ERROR_VARIABLE _pbdev_err)
|
RESULT_VARIABLE _pbdev_res ERROR_VARIABLE _pbdev_err)
|
||||||
if(NOT _pbdev_res EQUAL 0)
|
if(NOT _pbdev_res EQUAL 0)
|
||||||
@@ -40,8 +42,9 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
if(LIBAVR_MCU STREQUAL "atmega328p")
|
if(LIBAVR_MCU STREQUAL "atmega328p")
|
||||||
set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device)
|
set(TSB_DEVICE ${CMAKE_BINARY_DIR}/tsb_device)
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND ${_host_cc} -O2 -I/usr/include/simavr -I/usr/include/simavr/parts
|
COMMAND ${_host_cxx} -std=c++23 -Wall -Wextra -O2
|
||||||
-o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.c
|
-I/usr/include/simavr -I/usr/include/simavr/parts
|
||||||
|
-o ${TSB_DEVICE} ${CMAKE_CURRENT_SOURCE_DIR}/test/device.cpp
|
||||||
-lsimavr -lsimavrparts -lelf
|
-lsimavr -lsimavrparts -lelf
|
||||||
RESULT_VARIABLE _dev_res ERROR_VARIABLE _dev_err)
|
RESULT_VARIABLE _dev_res ERROR_VARIABLE _dev_err)
|
||||||
if(NOT _dev_res EQUAL 0)
|
if(NOT _dev_res EQUAL 0)
|
||||||
@@ -66,16 +69,18 @@ function(add_image_outputs name)
|
|||||||
$<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.bin)
|
$<TARGET_FILE:${name}> $<TARGET_FILE:${name}>.bin)
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
# The TinySafeBoot protocol reimplemented on libavr in three variants that trade
|
# The TinySafeBoot protocol reimplemented on libavr in variants that trade
|
||||||
# clarity for size. Each links into the ATmega328P boot section (BOOTSZ selects
|
# clarity for size. Each links into the ATmega328P boot section (BOOTSZ selects
|
||||||
# its size; BOOTRST vectors a reset to its base) with -nostartfiles — a polled
|
# its size; BOOTRST vectors a reset to its base) with -nostartfiles — a polled
|
||||||
# loader has no use for the crt or the vector table. The naked entry sits in
|
# loader has no use for the crt or the vector table. The entry sits in
|
||||||
# .vectors, laid first, and runs. The boot base is FLASHEND+1 minus the section
|
# .vectors, laid first, and runs — avr::startup::entry on the policy tier,
|
||||||
# size; the linker section-start and the source's boot_bytes agree. tsb_app is
|
# the experiment tiers' own naked stubs elsewhere, each documented in its
|
||||||
|
# source. The boot base is FLASHEND+1 minus the section size; the linker
|
||||||
|
# section-start and the source's boot_bytes agree. tsb_app is
|
||||||
# the application's reset vector, pinned to 0 here so the loaders jump to a
|
# the application's reset vector, pinned to 0 here so the loaders jump to a
|
||||||
# named function; --pmem-wrap-around lets relaxation turn that absolute jump
|
# named function; --pmem-wrap-around lets relaxation turn that absolute jump
|
||||||
# into the wrapped rjmp AVR's modulo-flash PC actually executes.
|
# into the wrapped rjmp AVR's modulo-flash PC actually executes.
|
||||||
# All three implement the full oracle feature set (see oracle/README.md):
|
# All four implement the full oracle feature set (see oracle/README.md):
|
||||||
# watchdog bail, one-wire half-duplex, config-page activation timeout, password
|
# watchdog bail, one-wire half-duplex, config-page activation timeout, password
|
||||||
# gate, emergency erase, config/flash/EEPROM read-write. They differ only in how,
|
# gate, emergency erase, config/flash/EEPROM read-write. They differ only in how,
|
||||||
# and the size gradient is the cost of that "how" — see dev/lessons.md.
|
# and the size gradient is the cost of that "how" — see dev/lessons.md.
|
||||||
@@ -170,6 +175,11 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
add_test(NAME pureboot.scan
|
add_test(NAME pureboot.scan
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_scan.py
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_scan.py
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
|
${CMAKE_CURRENT_SOURCE_DIR}/pureboot/pureboot.py)
|
||||||
|
# CMakePresets.json is generated; hand edits drift the moment the
|
||||||
|
# generator reruns, so the gate holds the pair together.
|
||||||
|
add_test(NAME presets.generated
|
||||||
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/make_presets.py
|
||||||
|
--check)
|
||||||
add_test(NAME pureboot.handshake
|
add_test(NAME pureboot.handshake
|
||||||
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_handshake.py)
|
COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test/test_handshake.py)
|
||||||
add_test(NAME pureboot.updatelink
|
add_test(NAME pureboot.updatelink
|
||||||
@@ -509,9 +519,9 @@ if(PROJECT_IS_TOP_LEVEL)
|
|||||||
set_tests_properties(pureboot.usart1 PROPERTIES TIMEOUT 180)
|
set_tests_properties(pureboot.usart1 PROPERTIES TIMEOUT 180)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The autobaud variants driven end to end over the software-UART bridge (both
|
# The autobaud loader driven end to end over the software-UART bridge:
|
||||||
# under review — pureboot/autobaud.md): the host sends the 0xC0 calibration
|
# the host sends the 0xC0 calibration pulse, the loader times it, locks,
|
||||||
# pulse, the loader times it, locks, and programs. Run on the near-flash 328P
|
# and programs. Run on the near-flash 328P
|
||||||
# and the word-addressed 1284P — the two flash-addressing classes — and each
|
# and the word-addressed 1284P — the two flash-addressing classes — and each
|
||||||
# at two clocks with the one binary, which is the clock-agnostic property
|
# at two clocks with the one binary, which is the clock-agnostic property
|
||||||
# autobaud exists for (test/pbautobaud.py). The fixture application banners
|
# autobaud exists for (test/pbautobaud.py). The fixture application banners
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
`master` carries `bootloader.atsln`, so this branch does too: `ide/bootloader.atsln`
|
`master` carries `bootloader.atsln`, so this branch does too: `ide/bootloader.atsln`
|
||||||
builds the loaders from the same sources Ninja does, to a **byte-identical
|
builds the loaders from the same sources Ninja does, to a **byte-identical
|
||||||
`.text`** — 404 B for the 328P pureboot loader, 510 B for the `tsb_asm` tier in
|
`.text`** — 400 B for the 328P pureboot loader, 510 B for the `tsb_asm` tier in
|
||||||
its 512-byte section. CMake remains the build system; the solution is here so the
|
its 512-byte section. CMake remains the build system; the solution is here so the
|
||||||
port opens in Studio as its predecessor did.
|
port opens in Studio as its predecessor did.
|
||||||
|
|
||||||
|
|||||||
2
libavr
2
libavr
Submodule libavr updated: 43b1f34ed1...911a87538f
@@ -28,32 +28,34 @@ 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 | 394 B | 464 B |
|
| ATtiny13, ATtiny13A † | 1 KiB | 0x0200 | software | 390 B | 460 B |
|
||||||
| ATtiny25 † | 2 KiB | 0x0600 | software | 398 B | 468 B |
|
| ATtiny25 † | 2 KiB | 0x0600 | software | 394 B | 464 B |
|
||||||
| ATtiny45 † | 4 KiB | 0x0e00 | software | 402 B | 472 B |
|
| ATtiny45 † | 4 KiB | 0x0e00 | software | 398 B | 468 B |
|
||||||
| ATtiny85 † | 8 KiB | 0x1e00 | software | 402 B | 472 B |
|
| ATtiny85 † | 8 KiB | 0x1e00 | software | 398 B | 468 B |
|
||||||
| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 364 B | 478 B |
|
| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 360 B | 474 B |
|
||||||
| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 366 B | 482 B |
|
| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 362 B | 480 B |
|
||||||
| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 366 B | 482 B |
|
| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 362 B | 480 B |
|
||||||
| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 392 B | 468 B |
|
| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 388 B | 464 B |
|
||||||
| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 402 B | 478 B |
|
| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 398 B | 474 B |
|
||||||
| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 404 B | 482 B |
|
| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 400 B | 480 B |
|
||||||
| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 404 B | 482 B |
|
| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 400 B | 480 B |
|
||||||
| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 404 B | 482 B |
|
| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 400 B | 480 B |
|
||||||
| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 404 B | 482 B |
|
| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 400 B | 480 B |
|
||||||
| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 398 B | 476 B |
|
| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 394 B | 474 B |
|
||||||
| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 424 B | 502 B |
|
| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 420 B | 500 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 deployed on a
|
The tightest fit in the whole space is the 1284s' autobaud build deployed on a
|
||||||
USART's own pins, 506 of its 512 — they alone carry the far-flash machinery
|
USART's own pins with the `OSCCAL` trim baked, 510 of its 512 — they alone
|
||||||
(ELPM reads, RAMPZ page commands), autobaud alone carries the calibration loop,
|
carry the far-flash machinery (ELPM reads, RAMPZ page commands), autobaud
|
||||||
and a bit-banged link on a USART's pins alone has to release it (below). The
|
alone carries the calibration loop, a bit-banged link on a USART's pins alone
|
||||||
same build on the default pins is 502. The flash bank riding in a transfer's
|
has to release it (below), and the trim adds its one register write. Without
|
||||||
selector byte keeps even those chips' addressing the same 16-bit form every
|
the trim that build is 504; on the default pins, 500. The flash bank riding
|
||||||
other chip uses, which is why they are no longer the outlier they were.
|
in a transfer's 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.
|
||||||
@@ -123,12 +125,13 @@ window per reset. Measure with an application in place.
|
|||||||
A downstream project brings its usual libavr setup (the `libavr` target, the
|
A downstream project brings its usual libavr setup (the `libavr` target, the
|
||||||
chip via the `LIBAVR_MCU` toolchain preset), consumes this directory, and
|
chip via the `LIBAVR_MCU` toolchain preset), consumes this directory, and
|
||||||
states its deployment — an ATmega328P on its shipped 1 MHz fuses with the
|
states its deployment — an ATmega328P on its shipped 1 MHz fuses with the
|
||||||
software UART on hand-picked pins, say:
|
software UART on hand-picked pins, say. A submodule pins the loader version
|
||||||
|
(the tags name them; this repo pins its own libavr the same way), where
|
||||||
|
FetchContent tracks whatever `main` is:
|
||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
FetchContent_Declare(bootloader GIT_REPOSITORY git@git.blackmark.me:avr/bootloader.git GIT_TAG main)
|
# git submodule add <forge>/avr/bootloader.git bootloader — or FetchContent
|
||||||
FetchContent_MakeAvailable(bootloader)
|
add_subdirectory(bootloader/pureboot pureboot)
|
||||||
add_subdirectory(${bootloader_SOURCE_DIR}/pureboot pureboot)
|
|
||||||
|
|
||||||
pureboot_add_loader(myboot CLOCK 1000000 SERIAL software TX pb1 RX pb5)
|
pureboot_add_loader(myboot CLOCK 1000000 SERIAL software TX pb1 RX pb5)
|
||||||
```
|
```
|
||||||
@@ -512,7 +515,12 @@ Per chip preset, `ctest` runs:
|
|||||||
too. The timeout is a constant and is no axis;
|
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. `pureboot*osccal*.size` add the `OSCCAL` trim on the stock
|
||||||
|
shape and on the tightest image in the space (autobaud on a USART's own
|
||||||
|
pins), holding both of the trim write's addressing encodings to the budget;
|
||||||
|
- `pureboot_autobaud.unit` — the measured bit period is the loader's only RAM
|
||||||
|
object and sits exactly at ram_start, where `--info` reads it: wire
|
||||||
|
contract, not layout accident;
|
||||||
- `pbm_*.size` — with `PUREBOOT_FULL_MATRIX=1`, the exhaustive cross product
|
- `pbm_*.size` — with `PUREBOOT_FULL_MATRIX=1`, the exhaustive cross product
|
||||||
replacing that compact matrix, on **every** chip: every plausible oscillator
|
replacing that compact matrix, on **every** chip: every plausible oscillator
|
||||||
(the internal ones, the CKDIV8 floor, the plain and the UART crystals) ×
|
(the internal ones, the CKDIV8 floor, the plain and the UART crystals) ×
|
||||||
@@ -535,8 +543,15 @@ Per chip preset, `ctest` runs:
|
|||||||
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
|
||||||
verify against a fake device;
|
verify against a fake device;
|
||||||
|
- `pureboot.scan` — `--scan`'s walk and report logic: the probe order, the
|
||||||
|
rate arithmetic, and the trim advice's direction. A pty carries bytes at
|
||||||
|
any termios rate, so the rate physics itself belongs to the hardware
|
||||||
|
harness, and what the wire would arbitrate is pinned as logic;
|
||||||
|
- `presets.generated` — CMakePresets.json matches its generator
|
||||||
|
(`tools/make_presets.py --check`), so a hand edit or a generator change
|
||||||
|
cannot drift the pair apart;
|
||||||
- `pureboot.protocol` — end to end against a simavr device
|
- `pureboot.protocol` — end to end against a simavr device
|
||||||
(`test/pureboot_device.c`: a hardware USART as a pty, or a cycle-timed
|
(`test/pureboot_device.cpp`: a hardware USART as a pty, or a cycle-timed
|
||||||
GPIO⇄pty bridge for a software-UART build, plus the SPM/NVM module simavr's
|
GPIO⇄pty bridge for a software-UART build, plus the SPM/NVM module simavr's
|
||||||
tiny cores lack) driven by the real host tool through knock-from-reset,
|
tiny cores lack) driven by the real host tool through knock-from-reset,
|
||||||
program + verify of both memories, session reconnect, an external reset
|
program + verify of both memories, session reconnect, an external reset
|
||||||
@@ -567,15 +582,21 @@ Per chip preset, `ctest` runs:
|
|||||||
- `pureboot.update` — the full `--update-loader` flow, then every power-fail
|
- `pureboot.update` — the full `--update-loader` flow, then every power-fail
|
||||||
phase: the device is killed mid-write, restarted from its flash dump, and a
|
phase: the device is killed mid-write, restarted from its flash dump, and a
|
||||||
re-run must complete the update with the application intact;
|
re-run must complete the update with the application intact;
|
||||||
|
- `pureboot.osccal` (328P, t85) — a loader built with the `OSCCAL` axis holds
|
||||||
|
the trim register at the built byte from its first prompt, observed through
|
||||||
|
the wire on one chip per addressing encoding (`sts` and low-I/O `out`);
|
||||||
- `pureboot.autobaud` (328P, 1284P) — the clock-free build over the GPIO⇄pty
|
- `pureboot.autobaud` (328P, 1284P) — the clock-free build over the GPIO⇄pty
|
||||||
bridge: the calibration handshake, a flash + EEPROM + fuse round trip against
|
bridge: the calibration handshake, a flash + EEPROM + fuse round trip against
|
||||||
the simulator's own memory, a data-space round trip, the hand-over — then the
|
the simulator's own memory, a data-space round trip, the hand-over — then the
|
||||||
same binary again at double the clock, which is the property the backend
|
same binary again at double the clock, which is the property the backend
|
||||||
exists for. A lone calibration pulse with no knock behind it must still let
|
exists for. The measured clock `--info` prints is asserted against the
|
||||||
|
simulator's exact clock, inside the unit encoding's own envelope, at both
|
||||||
|
points. 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`, `planner` and `handshake` are host logic and run anywhere; the
|
`size`, `unit`, `pi`, `planner`, `scan` and `handshake` are host logic and run
|
||||||
simulator-driven targets need simavr and a pty, so they are POSIX-only.
|
anywhere; the simulator-driven targets need simavr and a pty, so they are
|
||||||
|
POSIX-only.
|
||||||
|
|
||||||
## Hardware
|
## Hardware
|
||||||
|
|
||||||
|
|||||||
@@ -38,13 +38,6 @@ using dev = avr::device<{.clock = avr::hertz_t{PUREBOOT_CLOCK_HZ}}>;
|
|||||||
constexpr avr::baud_t wire_baud{PUREBOOT_BAUD};
|
constexpr avr::baud_t wire_baud{PUREBOOT_BAUD};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// The watchdog reset flag's home: MCUSR, or the classic megas' MCUCSR.
|
|
||||||
consteval std::int16_t wdrf_field()
|
|
||||||
{
|
|
||||||
auto reg = std::string_view{avr::hw::db.regs[static_cast<std::size_t>(avr::power::detail::reset_reg())].name};
|
|
||||||
return avr::hw::db.field_index(reg, "WDRF");
|
|
||||||
}
|
|
||||||
|
|
||||||
// The loader owns the top 512 bytes; a staging copy goes in the slot below.
|
// The loader owns the top 512 bytes; a staging copy goes in the slot below.
|
||||||
// Chips without a hardware boot section — the tinies and the m48s, whose SPM
|
// Chips without a hardware boot section — the tinies and the m48s, whose SPM
|
||||||
// runs from anywhere (Atmel-8271 §26) — keep the application's relocated
|
// runs from anywhere (Atmel-8271 §26) — keep the application's relocated
|
||||||
@@ -174,27 +167,6 @@ 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}>;
|
||||||
@@ -241,7 +213,6 @@ 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()
|
||||||
@@ -275,7 +246,6 @@ 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()
|
||||||
@@ -351,9 +321,14 @@ consteval std::uint32_t window_polls()
|
|||||||
return timeout_seconds * static_cast<std::uint32_t>(dev::clock.hz / link::poll_cycles);
|
return timeout_seconds * static_cast<std::uint32_t>(dev::clock.hz / link::poll_cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The countdown in the narrowest type that holds it: a fourth byte would
|
||||||
|
// cost a wider decrement chain at every poll for range most windows never
|
||||||
|
// use (the autobaud budget makes the same choice).
|
||||||
|
using window_t = std::conditional_t<window_polls() <= 0xffffff, avr::uint24_t, std::uint32_t>;
|
||||||
|
|
||||||
bool pending_before_deadline()
|
bool pending_before_deadline()
|
||||||
{
|
{
|
||||||
std::uint32_t polls = window_polls();
|
window_t polls = window_polls();
|
||||||
do {
|
do {
|
||||||
if (link::pending())
|
if (link::pending())
|
||||||
return true;
|
return true;
|
||||||
@@ -493,7 +468,7 @@ void fill_page(std::uint8_t bank, std::uint16_t at)
|
|||||||
#endif
|
#endif
|
||||||
// A watchdog reset belongs to the application, whose watchdog stays forced
|
// A watchdog reset belongs to the application, whose watchdog stays forced
|
||||||
// on until it clears WDRF — no activation window in its way.
|
// on until it clears WDRF — no activation window in its way.
|
||||||
if (avr::hw::field_impl<wdrf_field()>::test())
|
if (avr::power::peek_reset_cause().watchdog)
|
||||||
run_app();
|
run_app();
|
||||||
|
|
||||||
link::init();
|
link::init();
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ if os.name == "nt":
|
|||||||
import ctypes
|
import ctypes
|
||||||
from ctypes import wintypes
|
from ctypes import wintypes
|
||||||
else:
|
else:
|
||||||
|
import array
|
||||||
|
import fcntl
|
||||||
import select
|
import select
|
||||||
import termios
|
import termios
|
||||||
|
|
||||||
@@ -158,36 +160,60 @@ class Progress:
|
|||||||
|
|
||||||
|
|
||||||
class PosixPort:
|
class PosixPort:
|
||||||
"""A raw serial port with deadline-based reads, over termios."""
|
"""A raw serial port with deadline-based reads, over termios. A rate with
|
||||||
|
no B-constant — the off-nominal probes `--scan` walks — goes through
|
||||||
|
Linux's termios2 BOTHER; a platform without that ioctl refuses the rate
|
||||||
|
by name."""
|
||||||
|
|
||||||
|
# The termios2 ioctl pair and cflag bits, and the struct's ispeed/ospeed
|
||||||
|
# word offsets: four flag words, then a line-discipline byte and 19
|
||||||
|
# control chars padded to word 9 (include/uapi/asm-generic/termbits.h).
|
||||||
|
_TCGETS2, _TCSETS2 = 0x802C542A, 0x402C542B
|
||||||
|
_BOTHER, _CBAUD = 0o010000, 0o010017
|
||||||
|
_ISPEED, _OSPEED = 9, 10
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _speed(baud):
|
def _speed(baud):
|
||||||
|
return getattr(termios, f"B{baud}", None)
|
||||||
|
|
||||||
|
def _set_arbitrary(self, baud):
|
||||||
|
buf = array.array("i", [0] * (self._OSPEED + 1))
|
||||||
try:
|
try:
|
||||||
return getattr(termios, f"B{baud}")
|
fcntl.ioctl(self.fd, self._TCGETS2, buf, True)
|
||||||
except AttributeError:
|
buf[2] = (buf[2] & ~self._CBAUD) | self._BOTHER
|
||||||
raise Error(f"unsupported baud rate {baud}") from None
|
buf[self._ISPEED] = buf[self._OSPEED] = baud
|
||||||
|
fcntl.ioctl(self.fd, self._TCSETS2, buf)
|
||||||
|
except OSError:
|
||||||
|
raise Error(f"this platform cannot set {baud} Bd (no termios2)") from None
|
||||||
|
|
||||||
|
def _apply_baud(self, attrs, baud):
|
||||||
|
speed = self._speed(baud)
|
||||||
|
attrs[4] = attrs[5] = speed if speed is not None else termios.B38400
|
||||||
|
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
|
||||||
|
if speed is None:
|
||||||
|
self._set_arbitrary(baud)
|
||||||
|
self.baud = baud
|
||||||
|
|
||||||
def __init__(self, path, baud):
|
def __init__(self, path, baud):
|
||||||
self.fd = os.open(path, os.O_RDWR | os.O_NOCTTY)
|
self.fd = os.open(path, os.O_RDWR | os.O_NOCTTY)
|
||||||
attrs = termios.tcgetattr(self.fd)
|
try:
|
||||||
attrs[0] = 0 # iflag
|
attrs = termios.tcgetattr(self.fd)
|
||||||
attrs[1] = 0 # oflag
|
attrs[0] = 0 # iflag
|
||||||
attrs[2] = termios.CREAD | termios.CLOCAL | termios.CS8 # cflag
|
attrs[1] = 0 # oflag
|
||||||
attrs[3] = 0 # lflag
|
attrs[2] = termios.CREAD | termios.CLOCAL | termios.CS8 # cflag
|
||||||
attrs[4] = attrs[5] = self._speed(baud)
|
attrs[3] = 0 # lflag
|
||||||
attrs[6][termios.VMIN] = 0
|
attrs[6][termios.VMIN] = 0
|
||||||
attrs[6][termios.VTIME] = 0
|
attrs[6][termios.VTIME] = 0
|
||||||
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
|
self._apply_baud(attrs, baud)
|
||||||
self.baud = baud
|
except BaseException:
|
||||||
|
os.close(self.fd)
|
||||||
|
raise
|
||||||
|
|
||||||
def set_baud(self, baud):
|
def set_baud(self, baud):
|
||||||
"""Retune the port without closing it — the fd stays open, so no DTR
|
"""Retune the port without closing it — the fd stays open, so no DTR
|
||||||
pulse and no reset. That matters: the only caller is mid-session with a
|
pulse and no reset. That matters: the only caller is mid-session with a
|
||||||
loader copy that a reset would throw away."""
|
loader copy that a reset would throw away."""
|
||||||
attrs = termios.tcgetattr(self.fd)
|
self._apply_baud(termios.tcgetattr(self.fd), baud)
|
||||||
attrs[4] = attrs[5] = self._speed(baud)
|
|
||||||
termios.tcsetattr(self.fd, termios.TCSANOW, attrs)
|
|
||||||
self.baud = baud
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
os.close(self.fd)
|
os.close(self.fd)
|
||||||
@@ -531,6 +557,7 @@ class Loader:
|
|||||||
drain absorbs whatever they produced."""
|
drain absorbs whatever they produced."""
|
||||||
deadline = time.monotonic() + wait
|
deadline = time.monotonic() + wait
|
||||||
knocks = 0
|
knocks = 0
|
||||||
|
refusal = None
|
||||||
while True:
|
while True:
|
||||||
self.port.flush_input()
|
self.port.flush_input()
|
||||||
self.port.write(knock)
|
self.port.write(knock)
|
||||||
@@ -552,12 +579,18 @@ class Loader:
|
|||||||
except Error as failed:
|
except Error as failed:
|
||||||
if "pureboot" in str(failed):
|
if "pureboot" in str(failed):
|
||||||
raise
|
raise
|
||||||
|
# A malformed or unknown identity is retried as noise, but
|
||||||
|
# it was an answer: if nothing better ever arrives, naming
|
||||||
|
# it beats reporting silence.
|
||||||
|
refusal = failed
|
||||||
self.info = None
|
self.info = None
|
||||||
if self.info is not None:
|
if self.info is not None:
|
||||||
self._expect_prompt()
|
self._expect_prompt()
|
||||||
verbose(f"loader answered {what} {knocks}; identity read")
|
verbose(f"loader answered {what} {knocks}; identity read")
|
||||||
return self.info
|
return self.info
|
||||||
if time.monotonic() > deadline:
|
if time.monotonic() > deadline:
|
||||||
|
if refusal is not None:
|
||||||
|
raise Error(f"no usable answer — the last identity reply failed: {refusal}")
|
||||||
raise Error("no answer — reset the device within its activation window")
|
raise Error("no answer — reset the device within its activation window")
|
||||||
|
|
||||||
def connect(self, wait):
|
def connect(self, wait):
|
||||||
@@ -1408,7 +1441,11 @@ def op_scan(port_path, baud, wait, clock=None):
|
|||||||
for pct in scan_ratios():
|
for pct in scan_ratios():
|
||||||
rate = scan_rate(baud, pct)
|
rate = scan_rate(baud, pct)
|
||||||
print(f"scan: {rate} Bd ({pct:+d} %) — reset the target", flush=True)
|
print(f"scan: {rate} Bd ({pct:+d} %) — reset the target", flush=True)
|
||||||
port = Port(port_path, rate)
|
try:
|
||||||
|
port = Port(port_path, rate)
|
||||||
|
except Error as unmakeable:
|
||||||
|
print(f"scan: {rate} Bd skipped — {unmakeable}")
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
info = Loader(port).connect(wait)
|
info = Loader(port).connect(wait)
|
||||||
except Error:
|
except Error:
|
||||||
@@ -1504,11 +1541,13 @@ def main():
|
|||||||
print("device:")
|
print("device:")
|
||||||
for line in info.lines():
|
for line in info.lines():
|
||||||
print(f" {line}")
|
print(f" {line}")
|
||||||
if args.autobaud:
|
if args.autobaud and info.ram is not None:
|
||||||
# The whole of the loader's RAM is the measured bit period at
|
# The whole of the loader's RAM is the measured bit period at
|
||||||
# ram_start; decoded and times the rate this session drives,
|
# ram_start; decoded and times the rate this session drives,
|
||||||
# that is the true clock — the number to hold an OSCCAL bake
|
# that is the true clock — the number to hold an OSCCAL bake
|
||||||
# or a fixed-baud build against (README.md: deployment).
|
# or a fixed-baud build against (README.md: deployment). The
|
||||||
|
# autobaud identity path refuses unknown signatures, so ram is
|
||||||
|
# always known here; the guard states that dependency.
|
||||||
unit = int.from_bytes(loader.read_ram(info.ram, 2), "little")
|
unit = int.from_bytes(loader.read_ram(info.ram, 2), "little")
|
||||||
cycles = unit * UNIT_LOOP_CYCLES + UNIT_DISCOUNT
|
cycles = unit * UNIT_LOOP_CYCLES + UNIT_DISCOUNT
|
||||||
clock = cycles * args.baud
|
clock = cycles * args.baud
|
||||||
@@ -1561,7 +1600,7 @@ def main():
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
main()
|
main()
|
||||||
except Error as error:
|
except (Error, OSError) as error:
|
||||||
print(f"error: {error}", file=sys.stderr)
|
print(f"error: {error}", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|||||||
@@ -7,62 +7,71 @@
|
|||||||
// SPM genuinely writes avr->flash on the mega cores, so on exit (or SIGTERM)
|
// SPM genuinely writes avr->flash on the mega cores, so on exit (or SIGTERM)
|
||||||
// we dump the flash image to a file for a ground-truth cross-check against
|
// we dump the flash image to a file for a ground-truth cross-check against
|
||||||
// what the client read back through the bootloader.
|
// what the client read back through the bootloader.
|
||||||
#include <signal.h>
|
#include <csignal>
|
||||||
#include <stdint.h>
|
#include <cstdint>
|
||||||
#include <stdio.h>
|
#include <cstdio>
|
||||||
#include <stdlib.h>
|
#include <cstdlib>
|
||||||
#include <string.h>
|
#include <cstring>
|
||||||
|
#include <print>
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// The parts headers (uart_pty.h) carry no C++ linkage guards of their own,
|
||||||
|
// unlike simavr's core headers — the block covers both harmlessly.
|
||||||
|
extern "C" {
|
||||||
#include "avr_uart.h"
|
#include "avr_uart.h"
|
||||||
#include "sim_avr.h"
|
#include "sim_avr.h"
|
||||||
#include "sim_elf.h"
|
#include "sim_elf.h"
|
||||||
#include "uart_pty.h"
|
#include "uart_pty.h"
|
||||||
|
}
|
||||||
|
|
||||||
static avr_t *avr;
|
namespace {
|
||||||
static uart_pty_t uart_pty;
|
|
||||||
static const char *dump_path;
|
|
||||||
|
|
||||||
static void finish(int sig)
|
avr_t *avr;
|
||||||
|
uart_pty_t uart_pty;
|
||||||
|
const char *dump_path;
|
||||||
|
|
||||||
|
[[noreturn]] void finish(int)
|
||||||
{
|
{
|
||||||
(void)sig;
|
|
||||||
if (dump_path) {
|
if (dump_path) {
|
||||||
FILE *f = fopen(dump_path, "wb");
|
std::FILE *f = std::fopen(dump_path, "wb");
|
||||||
if (f) {
|
if (f) {
|
||||||
fwrite(avr->flash, 1, avr->flashend + 1, f);
|
std::fwrite(avr->flash, 1, avr->flashend + 1, f);
|
||||||
fclose(f);
|
std::fclose(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uart_pty_stop(&uart_pty);
|
uart_pty_stop(&uart_pty);
|
||||||
_exit(0);
|
_exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
fprintf(stderr, "usage: %s <tsb.elf> <boot_base_hex> [flash_dump.bin]\n", argv[0]);
|
std::println(stderr, "usage: {} <tsb.elf> <boot_base_hex> [flash_dump.bin]", argv[0]);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
uint32_t boot_base = (uint32_t)strtoul(argv[2], NULL, 0);
|
auto boot_base = static_cast<std::uint32_t>(std::strtoul(argv[2], nullptr, 0));
|
||||||
dump_path = argc >= 4 ? argv[3] : NULL;
|
dump_path = argc >= 4 ? argv[3] : nullptr;
|
||||||
|
|
||||||
avr = avr_make_mcu_by_name("atmega328p");
|
avr = avr_make_mcu_by_name("atmega328p");
|
||||||
if (!avr) {
|
if (!avr) {
|
||||||
fprintf(stderr, "device: no ATmega328P core\n");
|
std::println(stderr, "device: no ATmega328P core");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
avr_init(avr);
|
avr_init(avr);
|
||||||
avr->frequency = 16000000;
|
avr->frequency = 16000000;
|
||||||
// Real flash powers up erased (0xff); the app region must look erased
|
// Real flash powers up erased (0xff); the app region must look erased
|
||||||
// before the bootloader programs it.
|
// before the bootloader programs it.
|
||||||
memset(avr->flash, 0xff, avr->flashend + 1);
|
std::memset(avr->flash, 0xff, avr->flashend + 1);
|
||||||
|
|
||||||
// simavr's ELF loader flattens the flash base to 0 (it expects an app at
|
// simavr's ELF loader flattens the flash base to 0 (it expects an app at
|
||||||
// 0x0), but it hands back the boot code in fw.flash; place it at the boot
|
// 0x0), but it hands back the boot code in fw.flash; place it at the boot
|
||||||
// section base ourselves and enter there (BOOTRST is not modelled).
|
// section base ourselves and enter there (BOOTRST is not modelled).
|
||||||
elf_firmware_t fw = {0};
|
elf_firmware_t fw{};
|
||||||
if (elf_read_firmware(argv[1], &fw) != 0) {
|
if (elf_read_firmware(argv[1], &fw) != 0) {
|
||||||
fprintf(stderr, "device: cannot read %s\n", argv[1]);
|
std::println(stderr, "device: cannot read {}", argv[1]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
// An image that runs past flash end cannot execute on hardware, and a
|
// An image that runs past flash end cannot execute on hardware, and a
|
||||||
@@ -70,23 +79,23 @@ int main(int argc, char *argv[])
|
|||||||
// the simulation misbehaves in ways that point everywhere but here.
|
// the simulation misbehaves in ways that point everywhere but here.
|
||||||
// Refuse it loudly instead.
|
// Refuse it loudly instead.
|
||||||
if (boot_base + fw.flashsize > avr->flashend + 1) {
|
if (boot_base + fw.flashsize > avr->flashend + 1) {
|
||||||
fprintf(stderr, "device: %u B at 0x%x runs past flash end 0x%x — image does not fit its slot\n",
|
std::println(stderr, "device: {} B at {:#x} runs past flash end {:#x} — image does not fit its slot",
|
||||||
(unsigned)fw.flashsize, boot_base, avr->flashend);
|
fw.flashsize, boot_base, avr->flashend);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
memcpy(avr->flash + boot_base, fw.flash, fw.flashsize);
|
std::memcpy(avr->flash + boot_base, fw.flash, fw.flashsize);
|
||||||
avr->pc = boot_base;
|
avr->pc = boot_base;
|
||||||
avr->codeend = avr->flashend;
|
avr->codeend = avr->flashend;
|
||||||
|
|
||||||
// Optional: seed the config page (one page below the boot section) with a
|
// Optional: seed the config page (one page below the boot section) with a
|
||||||
// hex byte string, so the password gate and emergency erase can be tested.
|
// hex byte string, so the password gate and emergency erase can be tested.
|
||||||
// Layout: [appjump lo][appjump hi][timeout][password...][0xff].
|
// Layout: [appjump lo][appjump hi][timeout][password...][0xff].
|
||||||
const char *cfg = getenv("TSB_CONFIG");
|
const char *cfg = std::getenv("TSB_CONFIG");
|
||||||
if (cfg) {
|
if (cfg) {
|
||||||
uint32_t app_end = boot_base - 128; // config page sits directly below the boot code
|
std::uint32_t app_end = boot_base - 128; // config page sits directly below the boot code
|
||||||
for (int i = 0; cfg[i] && cfg[i + 1]; i += 2) {
|
for (int i = 0; cfg[i] && cfg[i + 1]; i += 2) {
|
||||||
char b[3] = {cfg[i], cfg[i + 1], 0};
|
char b[3] = {cfg[i], cfg[i + 1], 0};
|
||||||
avr->flash[app_end + i / 2] = (uint8_t)strtoul(b, NULL, 16);
|
avr->flash[app_end + i / 2] = static_cast<std::uint8_t>(std::strtoul(b, nullptr, 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,18 +104,18 @@ int main(int argc, char *argv[])
|
|||||||
// tight-polling loader (one that releases TX between bytes, as one-wire does)
|
// tight-polling loader (one that releases TX between bytes, as one-wire does)
|
||||||
// in real time, distorting protocol timing. Clear it so the loader runs at
|
// in real time, distorting protocol timing. Clear it so the loader runs at
|
||||||
// true cycle speed.
|
// true cycle speed.
|
||||||
uint32_t uflags = 0;
|
std::uint32_t uflags = 0;
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS('0'), &uflags);
|
avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS('0'), &uflags);
|
||||||
uflags &= ~AVR_UART_FLAG_POLL_SLEEP;
|
uflags &= ~AVR_UART_FLAG_POLL_SLEEP;
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS('0'), &uflags);
|
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS('0'), &uflags);
|
||||||
|
|
||||||
uart_pty_init(avr, &uart_pty);
|
uart_pty_init(avr, &uart_pty);
|
||||||
uart_pty_connect(&uart_pty, '0');
|
uart_pty_connect(&uart_pty, '0');
|
||||||
printf("TSB_PTY %s\n", uart_pty.pty.slavename);
|
std::println("TSB_PTY {}", uart_pty.pty.slavename);
|
||||||
fflush(stdout);
|
std::fflush(stdout);
|
||||||
|
|
||||||
signal(SIGTERM, finish);
|
std::signal(SIGTERM, finish);
|
||||||
signal(SIGINT, finish);
|
std::signal(SIGINT, finish);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int state = avr_run(avr);
|
int state = avr_run(avr);
|
||||||
@@ -114,5 +123,4 @@ int main(int argc, char *argv[])
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
finish(0);
|
finish(0);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ 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.
|
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
|
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
|
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).
|
that the mute could not happen here at all (test/pureboot_device.cpp).
|
||||||
|
|
||||||
Usage: pbmute.py <device_bin> <pureboot_elf> <mcu> <hz> <base_hex> <page>
|
Usage: pbmute.py <device_bin> <pureboot_elf> <mcu> <hz> <base_hex> <page>
|
||||||
<baud> <app_bin> <tool_py> <workdir> <link>
|
<baud> <app_bin> <tool_py> <workdir> <link>
|
||||||
|
|||||||
@@ -23,16 +23,22 @@
|
|||||||
//
|
//
|
||||||
// On exit (or SIGTERM) the flash and EEPROM are dumped to files for a
|
// On exit (or SIGTERM) the flash and EEPROM are dumped to files for a
|
||||||
// ground-truth cross-check against what the host read back.
|
// ground-truth cross-check against what the host read back.
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <print>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <pty.h>
|
#include <pty.h>
|
||||||
#include <signal.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// The parts headers (uart_pty.h) carry no C++ linkage guards of their own,
|
||||||
|
// unlike simavr's core headers — the block covers both harmlessly.
|
||||||
|
extern "C" {
|
||||||
#include "avr_eeprom.h"
|
#include "avr_eeprom.h"
|
||||||
#include "avr_flash.h"
|
#include "avr_flash.h"
|
||||||
#include "avr_ioport.h"
|
#include "avr_ioport.h"
|
||||||
@@ -41,31 +47,35 @@
|
|||||||
#include "sim_elf.h"
|
#include "sim_elf.h"
|
||||||
#include "sim_io.h"
|
#include "sim_io.h"
|
||||||
#include "uart_pty.h"
|
#include "uart_pty.h"
|
||||||
|
}
|
||||||
|
|
||||||
static avr_t *avr;
|
namespace {
|
||||||
static uart_pty_t uart_pty;
|
|
||||||
static int link_software;
|
|
||||||
static char uart_digit = '0';
|
|
||||||
static char sw_rx_port = 'B', sw_tx_port = 'B';
|
|
||||||
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 uint32_t reset_pc;
|
|
||||||
static volatile sig_atomic_t reset_requested;
|
|
||||||
|
|
||||||
static int parse_link(const char *spec)
|
avr_t *avr;
|
||||||
|
uart_pty_t uart_pty;
|
||||||
|
bool link_software;
|
||||||
|
char uart_digit = '0';
|
||||||
|
char sw_rx_port = 'B', sw_tx_port = 'B';
|
||||||
|
int sw_rx_bit = 0, sw_tx_bit = 1;
|
||||||
|
char sw_tx_owner = 0; // the USART whose TXD the software link sits on
|
||||||
|
const char *dump_path;
|
||||||
|
std::uint32_t reset_pc;
|
||||||
|
volatile std::sig_atomic_t reset_requested;
|
||||||
|
|
||||||
|
int parse_link(std::string_view spec)
|
||||||
{
|
{
|
||||||
if (strcmp(spec, "usart0") == 0 || strcmp(spec, "usart1") == 0) {
|
if (spec == "usart0" || spec == "usart1") {
|
||||||
link_software = 0;
|
link_software = false;
|
||||||
uart_digit = spec[5];
|
uart_digit = spec[5];
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (strncmp(spec, "sw", 2) == 0) {
|
if (spec.starts_with("sw")) {
|
||||||
link_software = 1;
|
link_software = true;
|
||||||
if (spec[2] == '\0')
|
if (spec.size() == 2)
|
||||||
return 0;
|
return 0;
|
||||||
char owner = 0;
|
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);
|
int fields =
|
||||||
|
std::sscanf(spec.data() + 2, ":%c%d,%c%d@%c", &sw_rx_port, &sw_rx_bit, &sw_tx_port, &sw_tx_bit, &owner);
|
||||||
if (fields == 4 || fields == 5) {
|
if (fields == 4 || fields == 5) {
|
||||||
sw_tx_owner = owner;
|
sw_tx_owner = owner;
|
||||||
return 0;
|
return 0;
|
||||||
@@ -87,19 +97,19 @@ static int parse_link(const char *spec)
|
|||||||
// core — so the discard store falls through into the buffer-fill branch and
|
// core — so the discard store falls through into the buffer-fill branch and
|
||||||
// plants whatever Z/R1:R0 happen to hold. Perform the silicon's discard
|
// plants whatever Z/R1:R0 happen to hold. Perform the silicon's discard
|
||||||
// here instead.
|
// here instead.
|
||||||
static avr_flash_t *mega_flash;
|
avr_flash_t *mega_flash;
|
||||||
static int (*mega_flash_ioctl)(avr_io_t *io, uint32_t ctl, void *param);
|
int (*mega_flash_ioctl)(avr_io_t *io, std::uint32_t ctl, void *param);
|
||||||
|
|
||||||
static int fixed_flash_ioctl(avr_io_t *io, uint32_t ctl, void *param)
|
int fixed_flash_ioctl(avr_io_t *io, std::uint32_t ctl, void *param)
|
||||||
{
|
{
|
||||||
if (ctl == AVR_IOCTL_FLASH_SPM && avr_regbit_get(io->avr, mega_flash->pgers)) {
|
if (ctl == AVR_IOCTL_FLASH_SPM && avr_regbit_get(io->avr, mega_flash->pgers)) {
|
||||||
uint16_t z = (uint16_t)(io->avr->data[30] | (io->avr->data[31] << 8));
|
auto z = static_cast<std::uint16_t>(io->avr->data[30] | (io->avr->data[31] << 8));
|
||||||
uint16_t masked = (uint16_t)(z & ~(mega_flash->spm_pagesize - 1));
|
auto masked = static_cast<std::uint16_t>(z & ~(mega_flash->spm_pagesize - 1));
|
||||||
io->avr->data[30] = (uint8_t)masked;
|
io->avr->data[30] = static_cast<std::uint8_t>(masked);
|
||||||
io->avr->data[31] = (uint8_t)(masked >> 8);
|
io->avr->data[31] = static_cast<std::uint8_t>(masked >> 8);
|
||||||
int result = mega_flash_ioctl(io, ctl, param);
|
int result = mega_flash_ioctl(io, ctl, param);
|
||||||
io->avr->data[30] = (uint8_t)z;
|
io->avr->data[30] = static_cast<std::uint8_t>(z);
|
||||||
io->avr->data[31] = (uint8_t)(z >> 8);
|
io->avr->data[31] = static_cast<std::uint8_t>(z >> 8);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (ctl == AVR_IOCTL_FLASH_SPM && !(mega_flash->flags & AVR_SELFPROG_HAVE_RWW) &&
|
if (ctl == AVR_IOCTL_FLASH_SPM && !(mega_flash->flags & AVR_SELFPROG_HAVE_RWW) &&
|
||||||
@@ -114,46 +124,44 @@ static int fixed_flash_ioctl(avr_io_t *io, uint32_t ctl, void *param)
|
|||||||
return mega_flash_ioctl(io, ctl, param);
|
return mega_flash_ioctl(io, ctl, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fix_mega_flash_erase(void)
|
void fix_mega_flash_erase()
|
||||||
{
|
{
|
||||||
for (avr_io_t *io = avr->io_port; io; io = io->next) {
|
for (avr_io_t *io = avr->io_port; io; io = io->next) {
|
||||||
if (io->kind && strcmp(io->kind, "flash") == 0) {
|
if (io->kind && std::string_view{io->kind} == "flash") {
|
||||||
mega_flash = (avr_flash_t *)io;
|
mega_flash = reinterpret_cast<avr_flash_t *>(io);
|
||||||
mega_flash_ioctl = io->ioctl;
|
mega_flash_ioctl = io->ioctl;
|
||||||
io->ioctl = fixed_flash_ioctl;
|
io->ioctl = fixed_flash_ioctl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fprintf(stderr, "device: no flash module to fix — SPM page erases may misalign\n");
|
std::println(stderr, "device: no flash module to fix — SPM page erases may misalign");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void request_reset(int sig)
|
void request_reset(int)
|
||||||
{
|
{
|
||||||
(void)sig;
|
|
||||||
reset_requested = 1;
|
reset_requested = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------- tiny NVM ---
|
// ------------------------------------------------------------- tiny NVM ---
|
||||||
|
|
||||||
typedef struct {
|
struct tiny_nvm_t {
|
||||||
avr_io_t io;
|
avr_io_t io;
|
||||||
uint8_t buffer[128];
|
std::uint8_t buffer[128];
|
||||||
uint8_t used[128]; // a buffer word loads once until erased — like silicon
|
std::uint8_t used[128]; // a buffer word loads once until erased — like silicon
|
||||||
unsigned page;
|
unsigned page;
|
||||||
} tiny_nvm_t;
|
};
|
||||||
|
|
||||||
static tiny_nvm_t nvm;
|
tiny_nvm_t nvm;
|
||||||
|
|
||||||
static int nvm_ioctl(avr_io_t *io, uint32_t ctl, void *param)
|
int nvm_ioctl(avr_io_t *io, std::uint32_t ctl, void *)
|
||||||
{
|
{
|
||||||
(void)param;
|
|
||||||
if (ctl != AVR_IOCTL_FLASH_SPM)
|
if (ctl != AVR_IOCTL_FLASH_SPM)
|
||||||
return -1;
|
return -1;
|
||||||
tiny_nvm_t *n = (tiny_nvm_t *)io;
|
auto *n = reinterpret_cast<tiny_nvm_t *>(io);
|
||||||
avr_t *mcu = io->avr;
|
avr_t *mcu = io->avr;
|
||||||
uint8_t command = mcu->data[0x57] & 0x1f; // SPMCSR, both tinies
|
std::uint8_t command = mcu->data[0x57] & 0x1f; // SPMCSR, both tinies
|
||||||
uint16_t z = (uint16_t)(mcu->data[30] | (mcu->data[31] << 8));
|
auto z = static_cast<std::uint16_t>(mcu->data[30] | (mcu->data[31] << 8));
|
||||||
uint32_t page_base = (uint32_t)(z & ~(n->page - 1)) % (mcu->flashend + 1);
|
std::uint32_t page_base = static_cast<std::uint32_t>(z & ~(n->page - 1)) % (mcu->flashend + 1);
|
||||||
if (command == 0x01) { // SPMEN alone: buffer fill from r1:r0
|
if (command == 0x01) { // SPMEN alone: buffer fill from r1:r0
|
||||||
unsigned offset = z & (n->page - 1) & ~1u;
|
unsigned offset = z & (n->page - 1) & ~1u;
|
||||||
if (!n->used[offset]) { // first write wins until the buffer clears
|
if (!n->used[offset]) { // first write wins until the buffer clears
|
||||||
@@ -162,45 +170,44 @@ static int nvm_ioctl(avr_io_t *io, uint32_t ctl, void *param)
|
|||||||
n->used[offset] = 1;
|
n->used[offset] = 1;
|
||||||
}
|
}
|
||||||
} else if (command == 0x03) { // PGERS
|
} else if (command == 0x03) { // PGERS
|
||||||
memset(mcu->flash + page_base, 0xff, n->page);
|
std::memset(mcu->flash + page_base, 0xff, n->page);
|
||||||
} else if (command == 0x05) { // PGWRT: programming only clears bits
|
} else if (command == 0x05) { // PGWRT: programming only clears bits
|
||||||
for (unsigned i = 0; i < n->page; i++)
|
for (unsigned i = 0; i < n->page; i++)
|
||||||
mcu->flash[page_base + i] &= n->buffer[i];
|
mcu->flash[page_base + i] &= n->buffer[i];
|
||||||
memset(n->buffer, 0xff, n->page);
|
std::memset(n->buffer, 0xff, n->page);
|
||||||
memset(n->used, 0, n->page);
|
std::memset(n->used, 0, n->page);
|
||||||
} else if (command == 0x11) { // CTPB
|
} else if (command == 0x11) { // CTPB
|
||||||
memset(n->buffer, 0xff, n->page);
|
std::memset(n->buffer, 0xff, n->page);
|
||||||
memset(n->used, 0, n->page);
|
std::memset(n->used, 0, n->page);
|
||||||
}
|
}
|
||||||
mcu->data[0x57] &= (uint8_t)~0x1f; // the operation completes instantly
|
mcu->data[0x57] &= static_cast<std::uint8_t>(~0x1f); // the operation completes instantly
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------- GPIO bridge ---
|
// ----------------------------------------------------------- GPIO bridge ---
|
||||||
|
|
||||||
static int pty_master = -1;
|
int pty_master = -1;
|
||||||
static avr_irq_t *rx_pin; // the loader's RX (PB0), driven from the pty
|
avr_irq_t *rx_pin; // the loader's RX (PB0), driven from the pty
|
||||||
static avr_cycle_count_t bit_cycles;
|
avr_cycle_count_t bit_cycles;
|
||||||
|
|
||||||
static int tx_level = 1, tx_active, tx_bit;
|
int tx_level = 1, tx_active, tx_bit;
|
||||||
static uint8_t tx_shift;
|
std::uint8_t tx_shift;
|
||||||
|
|
||||||
static avr_cycle_count_t tx_sample(avr_t *mcu, avr_cycle_count_t when, void *param)
|
avr_cycle_count_t tx_sample(avr_t *, avr_cycle_count_t when, void *)
|
||||||
{
|
{
|
||||||
(void)mcu;
|
|
||||||
(void)param;
|
|
||||||
if (tx_bit < 8) {
|
if (tx_bit < 8) {
|
||||||
tx_shift = (uint8_t)((tx_shift >> 1) | (tx_level ? 0x80 : 0));
|
tx_shift = static_cast<std::uint8_t>((tx_shift >> 1) | (tx_level ? 0x80 : 0));
|
||||||
if (++tx_bit < 8)
|
if (++tx_bit < 8)
|
||||||
return when + bit_cycles;
|
return when + bit_cycles;
|
||||||
/* The byte is not delivered until its stop bit has passed. A real
|
// The byte is delivered at the stop bit's sampling point (9.5 bit
|
||||||
* receiver cannot answer sooner, and a host that did would put its
|
// times), where a hardware receiver raises its RXC — not sooner: a
|
||||||
* start bit on the wire while the device is still driving the stop
|
// host answering before the stop bit would put its start bit on the
|
||||||
* bit — which the device, transmitting, is not watching for. */
|
// wire while the device is still driving, which the device,
|
||||||
|
// transmitting, is not watching for.
|
||||||
return when + bit_cycles;
|
return when + bit_cycles;
|
||||||
}
|
}
|
||||||
if (write(pty_master, &tx_shift, 1) != 1)
|
if (write(pty_master, &tx_shift, 1) != 1)
|
||||||
fprintf(stderr, "device: pty write lost a byte\n");
|
std::println(stderr, "device: pty write lost a byte");
|
||||||
tx_active = 0;
|
tx_active = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -212,9 +219,9 @@ static avr_cycle_count_t tx_sample(avr_t *mcu, avr_cycle_count_t when, void *par
|
|||||||
// model, so the ownership does not exist there and the mute cannot happen:
|
// 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
|
// 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.
|
// trailing @n names the USART; without one the pins are nobody's.
|
||||||
static avr_uart_t *tx_owner;
|
avr_uart_t *tx_owner;
|
||||||
|
|
||||||
static int tx_pin_taken(void)
|
bool tx_pin_taken()
|
||||||
{
|
{
|
||||||
return tx_owner && avr_regbit_get(avr, tx_owner->txen);
|
return tx_owner && avr_regbit_get(avr, tx_owner->txen);
|
||||||
}
|
}
|
||||||
@@ -224,27 +231,26 @@ static int tx_pin_taken(void)
|
|||||||
// enabled, making a freshly reset chip mute for reasons hardware does not
|
// 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
|
// 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.
|
// nobody's and only an application that really enables the USART takes it.
|
||||||
static void reset_tx_owner(void)
|
void reset_tx_owner()
|
||||||
{
|
{
|
||||||
if (tx_owner)
|
if (tx_owner)
|
||||||
avr_regbit_clear(avr, tx_owner->txen);
|
avr_regbit_clear(avr, tx_owner->txen);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void find_tx_owner(void)
|
void find_tx_owner()
|
||||||
{
|
{
|
||||||
for (avr_io_t *io = avr->io_port; io; io = io->next)
|
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) {
|
if (io->kind && std::string_view{io->kind} == "uart" &&
|
||||||
tx_owner = (avr_uart_t *)io;
|
reinterpret_cast<avr_uart_t *>(io)->name == sw_tx_owner) {
|
||||||
|
tx_owner = reinterpret_cast<avr_uart_t *>(io);
|
||||||
reset_tx_owner();
|
reset_tx_owner();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fprintf(stderr, "device: no USART%c to own the software link's TX pin\n", sw_tx_owner);
|
std::println(stderr, "device: no USART{} to own the software link's TX pin", sw_tx_owner);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tx_hook(avr_irq_t *irq, uint32_t value, void *param)
|
void tx_hook(avr_irq_t *, std::uint32_t value, void *)
|
||||||
{
|
{
|
||||||
(void)irq;
|
|
||||||
(void)param;
|
|
||||||
if (tx_pin_taken()) { // the USART holds the line; the port write goes nowhere
|
if (tx_pin_taken()) { // the USART holds the line; the port write goes nowhere
|
||||||
tx_level = 1;
|
tx_level = 1;
|
||||||
return;
|
return;
|
||||||
@@ -253,22 +259,20 @@ static void tx_hook(avr_irq_t *irq, uint32_t value, void *param)
|
|||||||
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;
|
||||||
tx_bit = 0;
|
tx_bit = 0;
|
||||||
avr_cycle_timer_register(avr, bit_cycles + bit_cycles / 2, tx_sample, NULL);
|
avr_cycle_timer_register(avr, bit_cycles + bit_cycles / 2, tx_sample, nullptr);
|
||||||
}
|
}
|
||||||
tx_level = level;
|
tx_level = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint8_t rx_queue[8192];
|
std::uint8_t rx_queue[8192];
|
||||||
static unsigned rx_head, rx_tail; // ring: head = next to send
|
unsigned rx_head, rx_tail; // ring: head = next to send
|
||||||
static int rx_active, rx_bit;
|
int rx_active, rx_bit;
|
||||||
static uint8_t rx_byte;
|
std::uint8_t rx_byte;
|
||||||
|
|
||||||
static void rx_start_next(void);
|
void rx_start_next();
|
||||||
|
|
||||||
static avr_cycle_count_t rx_step(avr_t *mcu, avr_cycle_count_t when, void *param)
|
avr_cycle_count_t rx_step(avr_t *, avr_cycle_count_t when, void *)
|
||||||
{
|
{
|
||||||
(void)mcu;
|
|
||||||
(void)param;
|
|
||||||
if (rx_bit < 8) {
|
if (rx_bit < 8) {
|
||||||
avr_raise_irq(rx_pin, (rx_byte >> rx_bit) & 1);
|
avr_raise_irq(rx_pin, (rx_byte >> rx_bit) & 1);
|
||||||
rx_bit++;
|
rx_bit++;
|
||||||
@@ -284,7 +288,7 @@ static avr_cycle_count_t rx_step(avr_t *mcu, avr_cycle_count_t when, void *param
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rx_start_next(void)
|
void rx_start_next()
|
||||||
{
|
{
|
||||||
if (rx_active || rx_head == rx_tail)
|
if (rx_active || rx_head == rx_tail)
|
||||||
return;
|
return;
|
||||||
@@ -293,7 +297,7 @@ static void rx_start_next(void)
|
|||||||
rx_active = 1;
|
rx_active = 1;
|
||||||
rx_bit = 0;
|
rx_bit = 0;
|
||||||
avr_raise_irq(rx_pin, 0); // start bit
|
avr_raise_irq(rx_pin, 0); // start bit
|
||||||
avr_cycle_timer_register(avr, bit_cycles, rx_step, NULL);
|
avr_cycle_timer_register(avr, bit_cycles, rx_step, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// A reset abandons whatever the bridge was mid-transfer: bytes still queued
|
// A reset abandons whatever the bridge was mid-transfer: bytes still queued
|
||||||
@@ -303,10 +307,10 @@ static void rx_start_next(void)
|
|||||||
// output latch, whose falling edge starts a spurious decode before this
|
// output latch, whose falling edge starts a spurious decode before this
|
||||||
// runs, and a stale tx_sample would then interleave with the loader's first
|
// runs, and a stale tx_sample would then interleave with the loader's first
|
||||||
// real answer through the shared shift state, corrupting it.
|
// real answer through the shared shift state, corrupting it.
|
||||||
static void bridge_reset(void)
|
void bridge_reset()
|
||||||
{
|
{
|
||||||
avr_cycle_timer_cancel(avr, tx_sample, NULL);
|
avr_cycle_timer_cancel(avr, tx_sample, nullptr);
|
||||||
avr_cycle_timer_cancel(avr, rx_step, NULL);
|
avr_cycle_timer_cancel(avr, rx_step, nullptr);
|
||||||
rx_head = rx_tail = 0;
|
rx_head = rx_tail = 0;
|
||||||
rx_active = 0;
|
rx_active = 0;
|
||||||
tx_active = 0;
|
tx_active = 0;
|
||||||
@@ -314,9 +318,9 @@ static void bridge_reset(void)
|
|||||||
avr_raise_irq(rx_pin, 1); // idle line
|
avr_raise_irq(rx_pin, 1); // idle line
|
||||||
}
|
}
|
||||||
|
|
||||||
static void poll_pty(void)
|
void poll_pty()
|
||||||
{
|
{
|
||||||
uint8_t chunk[256];
|
std::uint8_t chunk[256];
|
||||||
ssize_t got = read(pty_master, chunk, sizeof(chunk));
|
ssize_t got = read(pty_master, chunk, sizeof(chunk));
|
||||||
for (ssize_t i = 0; i < got; i++) {
|
for (ssize_t i = 0; i < got; i++) {
|
||||||
unsigned next = (rx_tail + 1) % sizeof(rx_queue);
|
unsigned next = (rx_tail + 1) % sizeof(rx_queue);
|
||||||
@@ -331,23 +335,22 @@ static void poll_pty(void)
|
|||||||
|
|
||||||
// ------------------------------------------------------------------ main ---
|
// ------------------------------------------------------------------ main ---
|
||||||
|
|
||||||
static void finish(int sig)
|
[[noreturn]] void finish(int)
|
||||||
{
|
{
|
||||||
(void)sig;
|
|
||||||
if (dump_path) {
|
if (dump_path) {
|
||||||
FILE *f = fopen(dump_path, "wb");
|
std::FILE *f = std::fopen(dump_path, "wb");
|
||||||
if (f) {
|
if (f) {
|
||||||
fwrite(avr->flash, 1, avr->flashend + 1, f);
|
std::fwrite(avr->flash, 1, avr->flashend + 1, f);
|
||||||
fclose(f);
|
std::fclose(f);
|
||||||
}
|
}
|
||||||
avr_eeprom_desc_t ee = {.ee = NULL, .offset = 0, .size = 0};
|
avr_eeprom_desc_t ee = {.ee = nullptr, .offset = 0, .size = 0};
|
||||||
if (avr_ioctl(avr, AVR_IOCTL_EEPROM_GET, &ee) == 0 && ee.ee && ee.size) {
|
if (avr_ioctl(avr, AVR_IOCTL_EEPROM_GET, &ee) == 0 && ee.ee && ee.size) {
|
||||||
char path[512];
|
char path[512];
|
||||||
snprintf(path, sizeof(path), "%s.eeprom", dump_path);
|
std::snprintf(path, sizeof(path), "%s.eeprom", dump_path);
|
||||||
f = fopen(path, "wb");
|
f = std::fopen(path, "wb");
|
||||||
if (f) {
|
if (f) {
|
||||||
fwrite(ee.ee, 1, ee.size, f);
|
std::fwrite(ee.ee, 1, ee.size, f);
|
||||||
fclose(f);
|
std::fclose(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,77 +359,87 @@ static void finish(int sig)
|
|||||||
_exit(0);
|
_exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
int link_given = 0;
|
bool link_given = false;
|
||||||
for (int opt; (opt = getopt(argc, argv, "l:")) != -1;) {
|
for (int opt; (opt = getopt(argc, argv, "l:")) != -1;) {
|
||||||
if (opt != 'l' || parse_link(optarg) != 0) {
|
if (opt != 'l' || parse_link(optarg) != 0) {
|
||||||
fprintf(stderr, "device: bad link spec (usart0, usart1, sw, or sw:B0,B1 as RX,TX)\n");
|
std::println(stderr, "device: bad link spec (usart0, usart1, sw, or sw:B0,B1 as RX,TX)");
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
link_given = 1;
|
link_given = true;
|
||||||
}
|
}
|
||||||
int args = argc - optind;
|
int args = argc - optind;
|
||||||
if (args < 7 || args > 9) {
|
if (args < 7 || args > 9) {
|
||||||
fprintf(stderr,
|
std::print(stderr,
|
||||||
"usage: %s [-l link] <pureboot.elf> <mcu> <hz> <base_hex> <page> <baud> <flash_dump>"
|
"usage: {} [-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[@0]] (RX,TX, then the USART owning\n"
|
" -l link: usart0 | usart1 | sw[:B0,B1[@0]] (RX,TX, then the USART owning\n"
|
||||||
" them); default: the chip's own\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",
|
||||||
argv[0]);
|
argv[0]);
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
argv += optind - 1; // argv[1] is the ELF again, whatever was parsed
|
argv += optind - 1; // argv[1] is the ELF again, whatever was parsed
|
||||||
const char *mcu_name = argv[2];
|
const std::string_view mcu_name = argv[2];
|
||||||
uint32_t base = (uint32_t)strtoul(argv[4], NULL, 0);
|
auto base = static_cast<std::uint32_t>(std::strtoul(argv[4], nullptr, 0));
|
||||||
unsigned page = (unsigned)atoi(argv[5]);
|
auto page = static_cast<unsigned>(std::atoi(argv[5]));
|
||||||
unsigned baud = (unsigned)atoi(argv[6]);
|
auto baud = static_cast<unsigned>(std::atoi(argv[6]));
|
||||||
dump_path = argv[7];
|
dump_path = argv[7];
|
||||||
int is_mega = strncmp(mcu_name, "atmega", 6) == 0;
|
const bool is_mega = mcu_name.starts_with("atmega");
|
||||||
if (!link_given)
|
if (!link_given)
|
||||||
link_software = !is_mega; // the chips' natural links: USART0, or PB0/PB1
|
link_software = !is_mega; // the chips' natural links: USART0, or PB0/PB1
|
||||||
|
|
||||||
avr = avr_make_mcu_by_name(mcu_name);
|
avr = avr_make_mcu_by_name(mcu_name.data());
|
||||||
if (!avr) {
|
if (!avr) {
|
||||||
fprintf(stderr, "device: no %s core\n", mcu_name);
|
std::println(stderr, "device: no {} core", mcu_name);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
avr_init(avr);
|
avr_init(avr);
|
||||||
avr->frequency = (uint32_t)strtoul(argv[3], NULL, 0);
|
avr->frequency = static_cast<std::uint32_t>(std::strtoul(argv[3], nullptr, 0));
|
||||||
memset(avr->flash, 0xff, avr->flashend + 1); // real flash powers up erased
|
std::memset(avr->flash, 0xff, avr->flashend + 1); // real flash powers up erased
|
||||||
|
|
||||||
if (args > 8) {
|
if (args > 8) {
|
||||||
// Resume: the full flash image of an interrupted prior run.
|
// Resume: the full flash image of an interrupted prior run.
|
||||||
FILE *f = fopen(argv[9], "rb");
|
std::FILE *f = std::fopen(argv[9], "rb");
|
||||||
if (!f || fread(avr->flash, 1, avr->flashend + 1, f) == 0) {
|
if (!f || std::fread(avr->flash, 1, avr->flashend + 1, f) == 0) {
|
||||||
fprintf(stderr, "device: cannot read %s\n", argv[9]);
|
std::println(stderr, "device: cannot read {}", argv[9]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
fclose(f);
|
std::fclose(f);
|
||||||
} else {
|
} else {
|
||||||
elf_firmware_t fw = {0};
|
elf_firmware_t fw{};
|
||||||
if (elf_read_firmware(argv[1], &fw) != 0) {
|
if (elf_read_firmware(argv[1], &fw) != 0) {
|
||||||
fprintf(stderr, "device: cannot read %s\n", argv[1]);
|
std::println(stderr, "device: cannot read {}", argv[1]);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
memcpy(avr->flash + base, fw.flash, fw.flashsize);
|
// An image past flash end would smash the simulator's heap and turn
|
||||||
|
// into phantom peripheral behavior (lessons: believe the size gate
|
||||||
|
// first) — refuse it loudly instead.
|
||||||
|
if (base + fw.flashsize > avr->flashend + 1) {
|
||||||
|
std::println(stderr, "device: {} B at {:#x} runs past flash end {:#x} — image does not fit its slot",
|
||||||
|
fw.flashsize, base, avr->flashend);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
std::memcpy(avr->flash + base, fw.flash, fw.flashsize);
|
||||||
}
|
}
|
||||||
// The boot-sectioned megas enter the loader in hardware (BOOTRST, not
|
// The boot-sectioned megas enter the loader in hardware (BOOTRST, not
|
||||||
// modeled — the argument picks the modeled fuse's target); the tinies
|
// modeled — the argument picks the modeled fuse's target); the tinies
|
||||||
// and the boot-section-less m48s reset to word 0 like silicon — erased
|
// and the boot-section-less m48s reset to word 0 like silicon — erased
|
||||||
// flash walks up into the loader, and after the host's surgery the
|
// flash walks up into the loader, and after the host's surgery the
|
||||||
// patched vector routes there.
|
// patched vector routes there.
|
||||||
int boot_section = is_mega && strncmp(mcu_name, "atmega48", 8) != 0;
|
const bool boot_section = is_mega && !mcu_name.starts_with("atmega48");
|
||||||
reset_pc = args > 7 ? (uint32_t)strtoul(argv[8], NULL, 0) : (boot_section ? base : 0);
|
reset_pc = args > 7 ? static_cast<std::uint32_t>(std::strtoul(argv[8], nullptr, 0)) : (boot_section ? base : 0);
|
||||||
avr->pc = reset_pc;
|
avr->pc = reset_pc;
|
||||||
avr->codeend = avr->flashend;
|
avr->codeend = avr->flashend;
|
||||||
|
|
||||||
// Erased EEPROM, as hardware powers up (simavr zeroes it).
|
// Erased EEPROM, as hardware powers up (simavr zeroes it).
|
||||||
uint8_t blank[1024];
|
std::uint8_t blank[1024];
|
||||||
memset(blank, 0xff, sizeof(blank));
|
std::memset(blank, 0xff, sizeof(blank));
|
||||||
avr_eeprom_desc_t seed = {.ee = blank, .offset = 0, .size = 0};
|
avr_eeprom_desc_t seed = {.ee = blank, .offset = 0, .size = 0};
|
||||||
if (avr_ioctl(avr, AVR_IOCTL_EEPROM_GET, &seed) == 0 && seed.size <= sizeof(blank)) {
|
if (avr_ioctl(avr, AVR_IOCTL_EEPROM_GET, &seed) == 0 && seed.size <= sizeof(blank)) {
|
||||||
seed.ee = blank;
|
seed.ee = blank;
|
||||||
@@ -440,7 +453,7 @@ int main(int argc, char *argv[])
|
|||||||
fix_mega_flash_erase();
|
fix_mega_flash_erase();
|
||||||
} else {
|
} else {
|
||||||
nvm.page = page;
|
nvm.page = page;
|
||||||
memset(nvm.buffer, 0xff, sizeof(nvm.buffer));
|
std::memset(nvm.buffer, 0xff, sizeof(nvm.buffer));
|
||||||
nvm.io.kind = "tiny_nvm";
|
nvm.io.kind = "tiny_nvm";
|
||||||
nvm.io.ioctl = nvm_ioctl;
|
nvm.io.ioctl = nvm_ioctl;
|
||||||
avr_register_io(avr, &nvm.io);
|
avr_register_io(avr, &nvm.io);
|
||||||
@@ -449,37 +462,38 @@ int main(int argc, char *argv[])
|
|||||||
if (!link_software) {
|
if (!link_software) {
|
||||||
// POLL_SLEEP paces an idle-polling loader in host real time (a
|
// POLL_SLEEP paces an idle-polling loader in host real time (a
|
||||||
// no-hardware CPU-saving hack); clear it so cycles run free.
|
// no-hardware CPU-saving hack); clear it so cycles run free.
|
||||||
uint32_t flags = 0;
|
std::uint32_t flags = 0;
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS(uart_digit), &flags);
|
avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS(uart_digit), &flags);
|
||||||
flags &= ~AVR_UART_FLAG_POLL_SLEEP;
|
flags &= ~AVR_UART_FLAG_POLL_SLEEP;
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags);
|
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags);
|
||||||
uart_pty_init(avr, &uart_pty);
|
uart_pty_init(avr, &uart_pty);
|
||||||
uart_pty_connect(&uart_pty, uart_digit);
|
uart_pty_connect(&uart_pty, uart_digit);
|
||||||
printf("PB_PTY %s\n", uart_pty.pty.slavename);
|
std::println("PB_PTY {}", 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)
|
if (sw_tx_owner)
|
||||||
find_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), static_cast<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(
|
||||||
NULL);
|
avr_io_getirq(avr, AVR_IOCTL_IOPORT_GETIRQ(sw_tx_port), static_cast<unsigned>(sw_tx_bit)), tx_hook,
|
||||||
|
nullptr);
|
||||||
avr_raise_irq(rx_pin, 1); // idle line
|
avr_raise_irq(rx_pin, 1); // idle line
|
||||||
|
|
||||||
int slave;
|
int slave;
|
||||||
struct termios raw;
|
struct termios raw;
|
||||||
cfmakeraw(&raw);
|
cfmakeraw(&raw);
|
||||||
if (openpty(&pty_master, &slave, NULL, &raw, NULL) != 0) {
|
if (openpty(&pty_master, &slave, nullptr, &raw, nullptr) != 0) {
|
||||||
fprintf(stderr, "device: openpty failed\n");
|
std::println(stderr, "device: openpty failed");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
fcntl(pty_master, F_SETFL, O_NONBLOCK);
|
fcntl(pty_master, F_SETFL, O_NONBLOCK);
|
||||||
printf("PB_PTY %s\n", ttyname(slave));
|
std::println("PB_PTY {}", ttyname(slave));
|
||||||
}
|
}
|
||||||
fflush(stdout);
|
std::fflush(stdout);
|
||||||
|
|
||||||
signal(SIGTERM, finish);
|
std::signal(SIGTERM, finish);
|
||||||
signal(SIGINT, finish);
|
std::signal(SIGINT, finish);
|
||||||
signal(SIGUSR1, request_reset); // an external reset line, for the tests
|
std::signal(SIGUSR1, request_reset); // an external reset line, for the tests
|
||||||
|
|
||||||
long since_poll = 0;
|
long since_poll = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -491,7 +505,7 @@ int main(int argc, char *argv[])
|
|||||||
avr_reset(avr);
|
avr_reset(avr);
|
||||||
avr->pc = reset_pc;
|
avr->pc = reset_pc;
|
||||||
if (!link_software) { // reset restores the pacing hack; re-clear it
|
if (!link_software) { // reset restores the pacing hack; re-clear it
|
||||||
uint32_t flags = 0;
|
std::uint32_t flags = 0;
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS(uart_digit), &flags);
|
avr_ioctl(avr, AVR_IOCTL_UART_GET_FLAGS(uart_digit), &flags);
|
||||||
flags &= ~AVR_UART_FLAG_POLL_SLEEP;
|
flags &= ~AVR_UART_FLAG_POLL_SLEEP;
|
||||||
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags);
|
avr_ioctl(avr, AVR_IOCTL_UART_SET_FLAGS(uart_digit), &flags);
|
||||||
@@ -515,5 +529,4 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
finish(0);
|
finish(0);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,24 @@ def main():
|
|||||||
if "9984000" not in report:
|
if "9984000" not in report:
|
||||||
fail(f"the absolute clock must scale with the found ratio:\n{report}")
|
fail(f"the absolute clock must scale with the found ratio:\n{report}")
|
||||||
|
|
||||||
|
# The walk's rates mostly have no termios B-constant, so the POSIX port
|
||||||
|
# must set them through termios2 — probed on a pty, which accepts the
|
||||||
|
# ioctl without caring about the speed. Without this every off-nominal
|
||||||
|
# probe would abort the walk on the platform --scan matters most on.
|
||||||
|
if os.name == "posix":
|
||||||
|
import pty
|
||||||
|
|
||||||
|
master, slave = pty.openpty()
|
||||||
|
try:
|
||||||
|
port = pb.Port(os.ttyname(slave), pb.scan_rate(9600, 4))
|
||||||
|
port.set_baud(pb.scan_rate(9600, -4))
|
||||||
|
port.close()
|
||||||
|
except pb.Error as error:
|
||||||
|
fail(f"PosixPort refused an off-nominal probe rate: {error}")
|
||||||
|
finally:
|
||||||
|
os.close(master)
|
||||||
|
os.close(slave)
|
||||||
|
|
||||||
print("OK")
|
print("OK")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,4 +36,10 @@ if ((full)); then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Every tree is freshly built now — the one moment the README's size table
|
||||||
|
# can be held to what the images measure (a per-preset ctest sees only its
|
||||||
|
# own chip; the table needs all of them, and ungated it drifts: a
|
||||||
|
# common-code shave moves every row at once with nothing over budget).
|
||||||
|
python3 tools/sizes.py check-readme
|
||||||
|
|
||||||
echo "check: every chip green"
|
echo "check: every chip green"
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ port's TUs compile identically; the sims prove nothing new there) exist for
|
|||||||
libavr's reflect spot set only, mirroring its rule: the full reflect matrix
|
libavr's reflect spot set only, mirroring its rule: the full reflect matrix
|
||||||
is never built, one chip per hardware class and pack vintage is.
|
is never built, one chip per hardware class and pack vintage is.
|
||||||
|
|
||||||
Run from the repo root: tools/make_presets.py
|
Run from the repo root: tools/make_presets.py — or with --check, which
|
||||||
|
verifies the committed file matches this generator and edits nothing (the
|
||||||
|
ctest entry `presets.generated` runs that, so drift reds the gate).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
CHIPS = [
|
CHIPS = [
|
||||||
"attiny13", "attiny13a", "attiny25", "attiny45", "attiny85",
|
"attiny13", "attiny13a", "attiny25", "attiny45", "attiny85",
|
||||||
@@ -41,7 +44,7 @@ def main():
|
|||||||
"hidden": True,
|
"hidden": True,
|
||||||
"generator": "Ninja",
|
"generator": "Ninja",
|
||||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||||
"toolchainFile": "$env{LIBAVR_ROOT}/cmake/avr-toolchain.cmake",
|
"toolchainFile": "${sourceDir}/libavr/cmake/avr-toolchain.cmake",
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
"CMAKE_BUILD_TYPE": "Release",
|
"CMAKE_BUILD_TYPE": "Release",
|
||||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||||
@@ -72,6 +75,9 @@ def main():
|
|||||||
for chip in REFLECT_SPOT:
|
for chip in REFLECT_SPOT:
|
||||||
add(chip, "reflect")
|
add(chip, "reflect")
|
||||||
|
|
||||||
|
# CMake rejects unknown fields in the presets root, $comment included, so
|
||||||
|
# the file cannot carry a generated-file marker; the --check ctest is the
|
||||||
|
# whole of rule 10's guard here.
|
||||||
presets = {
|
presets = {
|
||||||
"version": 8,
|
"version": 8,
|
||||||
"configurePresets": configure,
|
"configurePresets": configure,
|
||||||
@@ -79,12 +85,19 @@ def main():
|
|||||||
"testPresets": test,
|
"testPresets": test,
|
||||||
"workflowPresets": workflows,
|
"workflowPresets": workflows,
|
||||||
}
|
}
|
||||||
|
rendered = json.dumps(presets, indent=1) + "\n"
|
||||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "CMakePresets.json")
|
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "CMakePresets.json")
|
||||||
|
if "--check" in sys.argv[1:]:
|
||||||
|
current = open(path).read() if os.path.exists(path) else ""
|
||||||
|
if current != rendered:
|
||||||
|
print("CMakePresets.json does not match its generator — run tools/make_presets.py")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
json.dump(presets, f, indent=1)
|
f.write(rendered)
|
||||||
f.write("\n")
|
|
||||||
print(f"{len(CHIPS)} chips, {len(REFLECT_SPOT)} reflect: {os.path.normpath(path)}")
|
print(f"{len(CHIPS)} chips, {len(REFLECT_SPOT)} reflect: {os.path.normpath(path)}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
sys.exit(main())
|
||||||
|
|||||||
@@ -72,6 +72,39 @@ class Suite:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def scan(self) -> None:
|
||||||
|
"""The --scan walk against real termios and a real oscillator: every
|
||||||
|
probe rate must open a port (the off-nominal rates exist only through
|
||||||
|
termios2), and one probe must answer — the nominal on a healthy board,
|
||||||
|
a neighbor on a drifted one. The rig injects the one reset per probe
|
||||||
|
the operator supplies in the field; this is the rate physics the
|
||||||
|
simulator cannot arbitrate (a pty carries bytes at any rate), pinned
|
||||||
|
on silicon."""
|
||||||
|
module = pbrig.load_pureboot(self.rig.d.pureboot)
|
||||||
|
found = None
|
||||||
|
try:
|
||||||
|
for pct in module.scan_ratios():
|
||||||
|
rate = module.scan_rate(self.rig.d.baud, pct)
|
||||||
|
self.rig.reset()
|
||||||
|
try:
|
||||||
|
port = module.Port(self.rig.d.port, rate)
|
||||||
|
except module.Error as error:
|
||||||
|
self.check("scan opens every probe rate", False, f"{rate} Bd: {error}")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
module.Loader(port).connect(min(self.rig.d.wait, 6.0))
|
||||||
|
found = pct
|
||||||
|
break
|
||||||
|
except module.Error:
|
||||||
|
continue
|
||||||
|
finally:
|
||||||
|
port.close()
|
||||||
|
except Exception as error: # noqa: BLE001 — a rig hiccup is a result
|
||||||
|
self.check("scan walks the probe ladder", False, str(error)[:70])
|
||||||
|
return
|
||||||
|
self.check("scan finds the board's rate", found is not None,
|
||||||
|
"no probe answered" if found is None else f"{found:+d} % of {self.rig.d.baud} Bd")
|
||||||
|
|
||||||
def eeprom(self, info) -> None:
|
def eeprom(self, info) -> None:
|
||||||
size = info.eeprom_size
|
size = info.eeprom_size
|
||||||
if not size:
|
if not size:
|
||||||
@@ -161,6 +194,10 @@ class Suite:
|
|||||||
print("\nthe loader never answered; nothing below can be trusted")
|
print("\nthe loader never answered; nothing below can be trusted")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
if not self.rig.d.autobaud:
|
||||||
|
print("\nscan")
|
||||||
|
self.scan()
|
||||||
|
|
||||||
print("\nEEPROM")
|
print("\nEEPROM")
|
||||||
self.eeprom(info)
|
self.eeprom(info)
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ constexpr std::uint8_t comm_window = 200;
|
|||||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
||||||
|
|
||||||
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
||||||
constexpr auto baud = avr::uart::detail::solve_baud(16_MHz, 115200_Bd);
|
constexpr auto baud = avr::uart::solve_baud(16_MHz, 115200_Bd);
|
||||||
|
|
||||||
// The 16-byte device-info block, streamed out on activation.
|
// The 16-byte device-info block, streamed out on activation.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ extern "C" [[noreturn]] void tsb_app(); // the application's reset vector: --def
|
|||||||
// only the divisor low byte and U2X0 need a store. The solver still does
|
// only the divisor low byte and U2X0 need a store. The solver still does
|
||||||
// the datasheet work; the asserts pin the reset-state assumptions.
|
// the datasheet work; the asserts pin the reset-state assumptions.
|
||||||
{
|
{
|
||||||
constexpr auto sol = avr::uart::detail::solve_baud(dev::clock, 115200_Bd);
|
constexpr auto sol = avr::uart::solve_baud(dev::clock, 115200_Bd);
|
||||||
static_assert(sol.u2x && sol.ubrr < 256, "lean bring-up writes UBRR0L only, with U2X0");
|
static_assert(sol.u2x && sol.ubrr < 256, "lean bring-up writes UBRR0L only, with U2X0");
|
||||||
avr::hw::reg<"UBRR0">::write(static_cast<std::uint8_t>(sol.ubrr));
|
avr::hw::reg<"UBRR0">::write(static_cast<std::uint8_t>(sol.ubrr));
|
||||||
avr::hw::ucsr0a::write(avr::hw::ucsr0a::u2x0(1));
|
avr::hw::ucsr0a::write(avr::hw::ucsr0a::u2x0(1));
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ constexpr std::uint8_t comm_window = 200;
|
|||||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
||||||
|
|
||||||
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
// Fixed 115200 8N1; the library solves UBRR + U2X from clock and baud.
|
||||||
constexpr auto baud = avr::uart::detail::solve_baud(16_MHz, 115200_Bd);
|
constexpr auto baud = avr::uart::solve_baud(16_MHz, 115200_Bd);
|
||||||
|
|
||||||
// The 16-byte device-info block, streamed out on activation.
|
// The 16-byte device-info block, streamed out on activation.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
|
|||||||
Reference in New Issue
Block a user