The reading pass over this repo found the tiers disagreeing with themselves,
and every fix here was measured.
**The turn-around guard is real code.** `tsb_asm` and `tsb_tricks` wrote
`for (std::uint8_t guard = 46; guard; --guard) ;` between taking the one-wire
line and the first UDR0 store, under a comment naming it a turn-around guard.
It has no side effect, so GCC deleted it - `sts UCSR0B` went straight to
`sts UDR0` - while the hand-written oracle spends six bytes on that wait and
libavr's own half-duplex spends them through `delay::cycles`. Two of four
tiers described a feature they did not have, which made the size gradient a
comparison between different loaders. `avr::delay::cycles<one bit time>()`
bottoms out in asm and cannot be deleted.
**The entry belongs to the library, and hand-rolling it was expensive.** Three
tiers wrote their own naked `.vectors` stub with `asm volatile("clr
__zero_reg__")` - which design.md fences to libavr and never a port, and which
`tsb_tricks` denied having in its own title line. `avr::startup::entry` also
keeps the body `noinline` for a stated reason: avr-ld must not shrink a
`.vectors` section, so a loader inlined into one forfeits call relaxation
everywhere. `tsb_pure` came out **836 -> 734** bytes for that alone.
`stack::hardware` - the reset value this part guarantees, with the write kept
where a part does not - saved another four, which is what let `tsb_asm` afford
the guard it had been four bytes short of. It fills its 512-byte section
exactly now, with the whole feature set.
**`tsb_pure` had no receive timeout.** Its `rx()` was `read_blocking()`, so a
silent host wedged the password gate and the command loop forever - the one
fix the oracle's own header lists by name, and one the other three tiers
implement. It is bounded now, and 0-on-silence falls through every compare as
theirs does.
Three gates could pass without proving anything. `sizes.py check-readme`
reported a match when every row's lookup missed; `check_size.cmake` used
`CMAKE_MATCH_1` without checking the match succeeded, which is the guard its
sibling `check_unit.cmake` has and it is the size gate; `check_pi.py` raised
IndexError instead of reporting a position-independence break that changed the
image's length. And `check.sh` spelled the 37-chip list a second time beside
make_presets.py, where a chip added to one and missed in the other is a
silently unbuilt chip - it reads the presets now, and produces the same 37 and
12.
tsbtest.py gains the scenario nothing covered: a wrong password byte must
neither activate the loader nor reach the emergency erase behind it. Red-green
on a tier with the refusal removed.
Smaller, all measured or checked: the signature is `hw::db.signature` in every
tier as the page size and EEPROM end beside it already were; `act_min` derives
from the clock; pureboot.py's `rjmp` helpers refuse a part past rjmp's
4096-word reach rather than silently folding an offset (unreachable today, the
ATtiny85 sits exactly on it); the host tool calls space 2 `data` as the wire
and the loader do; `.clangd` strips the fifth GCC-only flag the build passes;
pbrig's bitclock guard reads its own ladder; pbreloc's unexplained retry is
gone, the write being reliable on five runs without it; and the four tier
sizes live in oracle/README.md's table instead of four file headers and a
CMake comment.
`--poke` before `--peek` turned out to be right - pbtest.py round-trips a poke
through the peek behind it - so the parser order and README say so now.
Every chip green, the README size table matching every image.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
62 lines
3.1 KiB
Markdown
62 lines
3.1 KiB
Markdown
# Oracle — the hand-written TinySafeBoot assembly
|
||
|
||
`tsb-fixedbaud.asm` is the reference implementation this port is measured
|
||
against: the **native-UART, fixed-baud** TinySafeBoot bootloader, hand-written
|
||
in AVR assembly. It is the size-and-feature bar for the port's `tsb_asm` tier.
|
||
|
||
- **Source**: <https://github.com/seedrobotics/tinysafeboot>
|
||
(`firmware_ASM/latest_stable_release/20200727-fixedbaud/main.asm`), the Seed
|
||
Robotics fixed-baud fork of Julien Thomas' TinySafeBoot.
|
||
- **License**: GPLv3 (see the header in the file). It is vendored here **only as
|
||
a reference oracle** — it is not compiled, linked, or distributed as part of
|
||
the MIT-licensed port. Mere aggregation.
|
||
|
||
## Why this variant
|
||
|
||
The user chose the fixed-baud, hardware-UART variant deliberately: it is the one
|
||
whose feature set the port must match. It fits the **complete** TSB feature set
|
||
into the 512-byte ATmega boot section:
|
||
|
||
| Feature | Oracle routine |
|
||
|---|---|
|
||
| Watchdog-reset bail straight to the app | `RESET` (WDRF check) |
|
||
| One-wire half-duplex (RX/TX shorted): RXEN/TXEN toggled per direction, TX turnaround guard | `SetRX` / `SetTX` / `TransmitByte` |
|
||
| Activation timeout read from the config page, with a lockout-proof minimum | `WRX1To` (uses `utimeoutH`) |
|
||
| 3×`@` activation knock | `ActCharRcvd` |
|
||
| Password gate; wrong byte hangs (still draining the UART) | `CheckPassword` |
|
||
| Emergency erase on password `\0` + double-confirm — wipes flash, EEPROM and the config page | `EmergencyErase` |
|
||
| Device-info block (16 bytes) | `SendDeviceInfo` / `DEVICEINFO` |
|
||
| App-flash read/write (`f`/`F`), EEPROM read/write (`e`/`E`), config read/write (`c`/`C`) | `CheckCommands` |
|
||
|
||
## Assembled size (the bar)
|
||
|
||
Assembled for the ATmega328P with `avra`:
|
||
|
||
```
|
||
avra -I /usr/share/avra tsb-fixedbaud.asm # after uncommenting .include "m328Pdef.inc"
|
||
# Code : 250 words (500 bytes) — the whole loader, all features, in the 512 B section
|
||
```
|
||
|
||
**500 bytes with every feature** — the proof that ≤512 B and full feature parity
|
||
are simultaneously reachable. The port's four tiers reach it from the other
|
||
side, and the gradient between them is the cost of the mechanisms each is
|
||
allowed:
|
||
|
||
| tier | bytes | section | what it is allowed |
|
||
|---|---|---|---|
|
||
| oracle | 500 | 512 B | hand-written assembly, the reference |
|
||
| `tsb_asm` | 512 | 512 B | C++ on libavr, two routines in asm |
|
||
| `tsb_tricks` | 528 | 1 KB | no asm; global register variables |
|
||
| `tsb_policy` | 630 | 1 KB | pureboot's rules: no asm, no register variables |
|
||
| `tsb_pure` | 776 | 1 KB | idiomatic libavr throughout |
|
||
|
||
The two routines `tsb_asm` keeps are the ones whose remaining cost is the
|
||
calling convention itself: the bounded rx and the page-store loop. It fills
|
||
its section exactly, with the same one-bit-time turn-around guard the oracle
|
||
spends six bytes on - every tier implements the whole feature set, which is
|
||
what makes the column a gradient rather than four different loaders.
|
||
|
||
The oracle targets 20 MHz / 33333 baud; the port targets 16 MHz / 115200 baud
|
||
(what the simavr protocol test drives). Baud and geometry differ, code size and
|
||
feature set do not.
|