pureboot 5: one command pair for every memory, and a clock-free backend
R/r/w/F collapse into G and g over a selector byte naming the space — flash,
EEPROM, data, fuse, SPM — with the flash bank in its high nibble. Four command
bodies, four transfer loops and four argument decodes become one of each, and
W joins the same decode instead of keeping an address form of its own. The
loader shrinks while gaining everything below: on the 1284P the stock build
goes 480 -> 432 B and the software one 496 -> 450.
What the freed space buys:
- Data space. On AVR one pointer spans SRAM, the register file and the whole
I/O space, so G over space 2 reads all three. pureboot keeps zero static
RAM and pushes no register, so at loader entry an application's SRAM is
still what the application left there — this is a post-mortem, not just a
poke hole. As its own command it needed a dispatch arm and a loop; as one
more space it is a single ld/st.
- Host-issued SPM. W fills the page buffer and stops; erase, write and RWW
re-enable are writes to space 4, which reach the same fused store-and-SPM
pair through the transfer's own address and data. Any SPM operation, lock
bits included, is now reachable and the loader carries no page-commit logic.
The four-cycle SPMCSR-to-SPM window is why that primitive stays fused: no
host can hit it across a serial link, and that — not the byte count — is
the floor on how low-level a bootloader's primitives can go.
- Byte addresses everywhere. The bank in the selector retires the
word-addressed wire the >64 KiB parts needed, so the 1284s stop being the
outlier.
SERIAL autobaud is a third backend on the same loader, over libavr's
software_autobaud: no clock, no baud, one binary per chip for every F_CPU and
every rate. Activation counts poll iterations rather than seconds and bounds
every wait, so a stray pulse cannot hold an unattended device.
b answers with the version and signature only; the host derives geometry from
the signature, which is what an autobaud build requires anyway. An update image
is a bare slot with no device to ask, so every image carries a six-byte stamp —
the same bytes b answers with, and the source of both — that the loader never
reads from flash and the host refuses to install a mismatch against. The
running-slot write guard moved onto the SPM commit, which covers erase and
write both where guarding W covered neither directly.
The position-independence lint now proves the property instead of a proxy for
it: the image must come out byte-identical linked at a different base.
-fno-move-loop-invariants left the tuned flag set — it was fitted to a command
loop carrying four transfer bodies and costs bytes now that it carries one.
Verified: the exhaustive matrix on all 37 chips (every clock x every baud x
every backend, non-standard rates included, plus the autobaud build) —
8174 size checks, no failures, tightest fit the 1284s' autobaud at 510 of 512.
Behavioral suites green on every chip class: t13a 10/10, t85 11/11, m8 13/13,
m16a 13/13, m48pa 13/13, 328P 23/23, 644A 17/17, 1284P 17/17. Data-space
round trip through --peek/--poke and the autobaud handshake are both red-green
proven.
The two prototype sources and their findings file go; the README carries the
protocol and dev/done.md in libavr carries the reasoning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,46 +8,51 @@ erase, reset-vector surgery, updating the loader itself — lives in the host
|
||||
tool (`pureboot.py`).
|
||||
|
||||
The image is **position-independent**: control flow is PC-relative, the
|
||||
read/write paths take wire addresses, the write guard protects the slot the
|
||||
code is *running* in (from the runtime return address), the info block is
|
||||
addressed from that same anchor, and the application jump is an indirect call
|
||||
transfer paths take wire addresses, the write guard protects the slot the code
|
||||
is *running* in (from the runtime return address), nothing else is
|
||||
flash-resident to address at all, and the application jump is an indirect call
|
||||
to an absolute entry. The identical binary therefore runs from any slot with
|
||||
every command intact, which makes pureboot **its own staging loader**: the
|
||||
host installs the same binary one slot below the resident, jumps into it, and
|
||||
lets it rewrite the resident.
|
||||
every command intact, which makes pureboot **its own staging loader**: the host
|
||||
installs the same binary one slot below the resident, jumps into it, and lets
|
||||
it rewrite the resident. The lint holds it to that literally — the image must
|
||||
come out byte-identical linked at a different base.
|
||||
|
||||
## Chips
|
||||
|
||||
Sizes are the default configuration: the hardware USART0 at 115200 8N1 on a
|
||||
16 MHz crystal, or the software UART on RX = PB0 / TX = PB1 at 57600 8N1 on
|
||||
the tinies' RC oscillator (9.6 MHz on the t13s, 8 MHz above). Every axis moves
|
||||
per build — see *Configuration*; the largest image any of them produces is a
|
||||
software UART at a slow baud, which on the 1284s is 494 B, the tightest fit in
|
||||
the whole matrix at 18 B spare.
|
||||
per build — see *Configuration*. The autobaud column is the clock-free build,
|
||||
which is the largest the space produces and the tightest fit in the matrix;
|
||||
it carries the calibration machinery and no clock at all.
|
||||
|
||||
| Chip | Flash | Loader at | Link | Size |
|
||||
|---|---|---|---|---|
|
||||
| ATtiny13, ATtiny13A † | 1 KiB | 0x0200 | software | 416 B |
|
||||
| ATtiny25 † | 2 KiB | 0x0600 | software | 420 B |
|
||||
| ATtiny45 † | 4 KiB | 0x0e00 | software | 424 B |
|
||||
| ATtiny85 † | 8 KiB | 0x1e00 | software | 424 B |
|
||||
| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 396 B |
|
||||
| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 400 B |
|
||||
| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 400 B |
|
||||
| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 414 B |
|
||||
| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 434 B |
|
||||
| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 438 B |
|
||||
| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 438 B |
|
||||
| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 438 B |
|
||||
| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 438 B |
|
||||
| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 432 B |
|
||||
| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 478 B |
|
||||
| Chip | Flash | Loader at | Link | Stock | Autobaud |
|
||||
|---|---|---|---|---|---|
|
||||
| ATtiny13, ATtiny13A † | 1 KiB | 0x0200 | software | 402 B | 472 B |
|
||||
| ATtiny25 † | 2 KiB | 0x0600 | software | 406 B | 476 B |
|
||||
| ATtiny45 † | 4 KiB | 0x0e00 | software | 410 B | 480 B |
|
||||
| ATtiny85 † | 8 KiB | 0x1e00 | software | 410 B | 480 B |
|
||||
| ATmega8, 8A | 8 KiB | 0x1e00 | USART0 | 372 B | 486 B |
|
||||
| ATmega16, 16A | 16 KiB | 0x3e00 | USART0 | 374 B | 490 B |
|
||||
| ATmega32, 32A | 32 KiB | 0x7e00 | USART0 | 374 B | 490 B |
|
||||
| ATmega48, 48A, 48P, 48PA † | 4 KiB | 0x0e00 | USART0 | 400 B | 476 B |
|
||||
| ATmega88, 88A, 88P, 88PA | 8 KiB | 0x1e00 | USART0 | 410 B | 486 B |
|
||||
| ATmega168, 168A, 168P, 168PA | 16 KiB | 0x3e00 | USART0 | 412 B | 490 B |
|
||||
| ATmega328, 328P | 32 KiB | 0x7e00 | USART0 | 412 B | 490 B |
|
||||
| ATmega164A, 164P, 164PA | 16 KiB | 0x3e00 | USART0 | 412 B | 490 B |
|
||||
| ATmega324A, 324P, 324PA | 32 KiB | 0x7e00 | USART0 | 412 B | 490 B |
|
||||
| ATmega644, 644A, 644P, 644PA | 64 KiB | 0xfe00 | USART0 | 406 B | 484 B |
|
||||
| ATmega1284, 1284P | 128 KiB | 0x1fe00 | USART0 | 432 B | 510 B |
|
||||
|
||||
† 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.
|
||||
|
||||
The 1284s are the heaviest because they alone carry the far-flash machinery —
|
||||
ELPM reads, RAMPZ page commands, a word-addressed wire.
|
||||
The tightest fit in the whole space is the 1284s' autobaud build, 510 of its
|
||||
512 — they alone carry the far-flash machinery (ELPM reads, RAMPZ page
|
||||
commands) and autobaud alone carries the calibration loop. Everything else has
|
||||
20 B of headroom or more. The flash bank riding 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
|
||||
quantities are little-endian.
|
||||
@@ -62,7 +67,7 @@ repo's build and by a downstream project alike:
|
||||
|---|---|---|
|
||||
| `CLOCK <hz>` | the clock the board runs | 16 MHz megas, 8 MHz t25/45/85, 9.6 MHz t13s |
|
||||
| `BAUD <bd>` | the wire rate | the ladder below |
|
||||
| `SERIAL auto\|hardware\|software` | the link backend | `auto`: the hardware USART where the chip has one |
|
||||
| `SERIAL auto\|hardware\|software\|autobaud` | the link backend | `auto`: the hardware USART where the chip has one |
|
||||
| `USART <n>` | the USART instance (x4 megas carry two) | 0 |
|
||||
| `RX <pin>`, `TX <pin>` | software-UART pins | `pb0`, `pb1` |
|
||||
| `TIMEOUT <s>` | the activation window | 8 |
|
||||
@@ -74,6 +79,17 @@ receiver's 100-cycles-a-bit floor. Whatever is picked or overridden is
|
||||
re-checked in the compile: an infeasible combination, or a USART the chip does
|
||||
not have, fails with a named static assert.
|
||||
|
||||
`SERIAL autobaud` takes neither: the loader **measures** the host's bit timing
|
||||
at run time, so `CLOCK` and `BAUD` are not build parameters there and one
|
||||
binary per chip serves every clock and every rate. It is for the deployments
|
||||
whose clock is not known at build time and does not hold still — the internal
|
||||
RC oscillator, ±10 % from the factory and moving with supply and temperature —
|
||||
where a fixed-baud software build has to be rebuilt per clock and still drifts
|
||||
out of tolerance. The cost is that it is software-serial only (a hardware USART
|
||||
needs its divisor programmed) and that activation counts poll iterations rather
|
||||
than seconds, since there is no clock to convert them against
|
||||
(`PUREBOOT_AUTOBAUD_POLLS`, default 4,000,000).
|
||||
|
||||
A downstream project brings its usual libavr setup (the `libavr` target, the
|
||||
chip via the `LIBAVR_MCU` toolchain preset), consumes this directory, and
|
||||
states its deployment — an ATmega328P on its shipped 1 MHz fuses with the
|
||||
@@ -113,9 +129,20 @@ window; any other byte is discarded and awaited again, so line noise can delay
|
||||
the loader but never lock it. A window expiring on an idle line boots the
|
||||
application.
|
||||
|
||||
An autobaud build opens differently, because it has to learn the rate before it
|
||||
can read a byte at all: the host sends the **calibration byte 0xC0** — a start
|
||||
bit plus six zero data bits form one low pulse of seven bit-times — and the
|
||||
loader times that pulse into its bit period. A single `p` then activates; the
|
||||
pulse has already proven a host is present, which the two-byte knock exists to
|
||||
establish elsewhere. Both waits are bounded, so a stray low pulse with no host
|
||||
behind it costs one window and then boots the application rather than holding
|
||||
the loader.
|
||||
|
||||
The window is a compile-time constant (`TIMEOUT`, 8 s by default), so the whole
|
||||
EEPROM belongs to the application — pureboot keeps no state of its own.
|
||||
Re-timing a deployed loader is a self-update with a re-timed build.
|
||||
Re-timing a deployed loader is a self-update with a re-timed build. An autobaud
|
||||
build counts poll iterations instead (`PUREBOOT_AUTOBAUD_POLLS`), there being
|
||||
no clock to turn into seconds.
|
||||
|
||||
## Session
|
||||
|
||||
@@ -125,75 +152,116 @@ write and sends the prompt `+` (0x2b), which is therefore also the previous
|
||||
command's completion ack. A session is: await `+`, send a command, read its
|
||||
reply, repeat.
|
||||
|
||||
On chips whose flash exceeds 64 KiB (the 1284s — info-block flag bit 1) the
|
||||
`R`/`W` flash addresses are **word** addresses; everywhere else they are byte
|
||||
addresses (the 644s' 64 KiB is exactly the 16-bit byte space). EEPROM
|
||||
addresses and all counts are bytes.
|
||||
Addresses are **byte addresses within a 64 KiB bank**, and the bank rides in
|
||||
the command's selector byte, so no command has to speak word addresses. `J` is
|
||||
the exception: it takes a word address, because that is what the hardware's own
|
||||
jump takes. EEPROM and data-space addresses and all counts are bytes.
|
||||
|
||||
The loader trusts the host to keep addresses in range: it does not bound them
|
||||
against the info block. **Gotcha:** a `w` (or `r`) that runs past `E2END` wraps
|
||||
— EEAR is only as wide as the array, so an address past the end truncates onto
|
||||
low EEPROM and the write silently overwrites it. Keeping writes within the
|
||||
advertised sizes is the host's job (the shipped tool does); the flash budget
|
||||
is better spent on features than on re-checking a bound the host already holds.
|
||||
against the chip. **Gotcha:** a write (or read) that runs past `E2END` wraps —
|
||||
EEAR is only as wide as the array, so an address past the end truncates onto
|
||||
low EEPROM and the write silently overwrites it. Keeping transfers within the
|
||||
real sizes is the host's job (the shipped tool does); the flash budget is
|
||||
better spent on features than on re-checking a bound the host already holds.
|
||||
|
||||
| Cmd | Arguments | Reply |
|
||||
|---|---|---|
|
||||
| `b` | — | the 12-byte info block |
|
||||
| `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) |
|
||||
| `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 |
|
||||
| `F` | — | 4 bytes: low fuse, lock, extended fuse, high fuse |
|
||||
| `b` | — | 4 bytes: the pureboot version, then the three signature bytes |
|
||||
| `G` | sel8, addr16, n8 | n bytes from the selected space (n = 0 means 256) |
|
||||
| `g` | sel8, addr16, n8, then n data bytes | `+` per byte, sent once its write has begun |
|
||||
| `W` | sel8, addr16, then one page of data | — (completion = next prompt) |
|
||||
| `J` | word address (16-bit) | `+`, then execution continues there |
|
||||
| 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,
|
||||
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
|
||||
cannot brick the running copy and a staged copy may rewrite the resident.
|
||||
`G` and `g` are one letter in two cases, which is the whole command set for
|
||||
every memory: the **selector** byte's low nibble names the space and its high
|
||||
nibble carries the flash bank.
|
||||
|
||||
| Space | | |
|
||||
|---|---|---|
|
||||
| 0 | flash | read-only here; it is written through `W` and the SPM space |
|
||||
| 1 | EEPROM | |
|
||||
| 2 | data | SRAM — and with it the register file and every I/O register, which share the data address space on AVR |
|
||||
| 3 | fuse and lock | index 0..3 in the hardware's own Z order: low, lock, extended, high |
|
||||
| 4 | SPM | write-only: the byte goes to SPMCSR and fires the instruction at the address |
|
||||
|
||||
The data space is worth more than it looks. pureboot keeps **zero static RAM**
|
||||
and pushes no register, so at loader entry an application's SRAM is still
|
||||
whatever the application left there, bar the handful of bytes of return-address
|
||||
stack — which makes `G` over space 2 a post-mortem of a running application,
|
||||
not just a poke hole. The same address space carries the register file and the
|
||||
I/O registers, so peripheral state is readable too; reading some of those has
|
||||
side effects (reading UDR clears its flags), which is the host's business to
|
||||
know.
|
||||
|
||||
Programming a page is therefore `W` to fill the buffer, then a `g` to the SPM
|
||||
space for the erase, another for the write, and on a boot-sectioned chip a
|
||||
third to re-enable the RWW section — `0x03`, `0x05` and `0x11`, the SPMCSR
|
||||
encodings every part pureboot targets shares. The loader carries no page-commit
|
||||
logic of its own, and the same primitive reaches every other SPM operation,
|
||||
lock bits included.
|
||||
|
||||
The SPM store and the SPM instruction must issue within four cycles of each
|
||||
other (§26.2), which no host can hit across a serial link — so this one
|
||||
primitive is *fused* rather than being a poke of SPMCSR followed by a poke of
|
||||
something else. That four-cycle window is the floor on how low-level a
|
||||
bootloader's primitives can go; it is not a byte-count decision.
|
||||
|
||||
An SPM command aimed at the 512-byte slot the loader is **running in** is
|
||||
dropped, so a broken host cannot brick the running copy, while a staged copy
|
||||
one slot lower may rewrite the resident — which is what a self-update is.
|
||||
|
||||
The loader never clears the SPM buffer before a fill, so **one `W` may program
|
||||
the wrong bytes, and the host is what fixes it**. The buffer is write-once per
|
||||
word until cleared, and two things leave words in it: a refused page, and —
|
||||
where SPM runs from anywhere, the tinies and the m48s — an application that
|
||||
self-programmed before entering. The next `W` takes those stale words and
|
||||
clears them, since a page write auto-erases the buffer (§26.2.1; §19.2 on the
|
||||
tinies), so repeating it programs correctly. The host therefore verifies every
|
||||
page it writes and rewrites what comes back wrong (three retries, then it
|
||||
self-programmed before entering. The next page write takes those stale words
|
||||
and clears them, since a page write auto-erases the buffer (§26.2.1; §19.2 on
|
||||
the tinies), so repeating it programs correctly. The host therefore verifies
|
||||
every page it writes and rewrites what comes back wrong (three retries, then it
|
||||
stops).
|
||||
|
||||
`w` is host-paced: send the next byte only after the previous byte's `+`. `F`
|
||||
returns the bytes in the hardware's Z order; on a chip without an extended
|
||||
fuse byte that slot carries no meaning. Fuse *writing* does not exist — SPM
|
||||
reaches flash and boot lock bits only.
|
||||
`g` is host-paced: send the next byte only after the previous byte's `+`. Fuse
|
||||
*writing* does not exist — SPM reaches flash and boot lock bits only.
|
||||
|
||||
`J` is the one control-transfer primitive: it runs the application (word 0 or
|
||||
the trampoline word, both known from the info block) and moves between loader
|
||||
the trampoline word, both derived from the chip) and moves between loader
|
||||
copies during a self-update. A jump to a slot's base re-enters that copy's own
|
||||
startup, which must then be knocked afresh.
|
||||
|
||||
The info block (`b`):
|
||||
`b` answers with the loader's identity — its version and the chip's signature —
|
||||
and nothing else. Everything else the host needs (page size, loader base,
|
||||
EEPROM size, whether the reset vector must be patched, how many flash banks)
|
||||
follows from the signature, and the host holds that table; the loader derived
|
||||
the same facts from its own chip database at build time, so nothing is guessed,
|
||||
it is simply not sent twice.
|
||||
|
||||
| Offset | Content |
|
||||
|---|---|
|
||||
| 0–2 | `'P'`, `'B'`, pureboot version (3) |
|
||||
| 3–5 | device signature |
|
||||
| 6 | SPM page size in bytes (0 means 256) |
|
||||
| 7–8 | loader base — application flash ends here (a word address when bit 1 is set) |
|
||||
| 9–10 | EEPROM size |
|
||||
| 11 | bit 0: host must patch the reset vector (no hardware boot section); bit 1: flash wire addresses are word addresses |
|
||||
An update image, though, is a bare 512-byte slot with no device to ask, and
|
||||
installing one built for another chip bricks the target. Every loader image
|
||||
therefore carries a six-byte **stamp** — `'P'`, `'B'`, the version, the three
|
||||
signature bytes — which the loader itself never reads and the host tool refuses
|
||||
to install a mismatch against.
|
||||
|
||||
## Version
|
||||
|
||||
The info block's third byte is the **pureboot version** — the loader's one
|
||||
identity number, and the only way to tell what a deployed loader is. Nothing
|
||||
else is numbered: the wire protocol has no version, a pureboot version implies
|
||||
it, and the host tool holds that map. The tool states the window of loader
|
||||
versions it speaks (`OLDEST_LOADER`/`NEWEST_LOADER` in `pureboot.py`), and a
|
||||
version that changes the protocol becomes the new floor there. None has so
|
||||
far: 1 through 3 speak the identical session. A loader newer than the tool is
|
||||
refused by name rather than decoded on the assumption that nothing moved.
|
||||
`b`'s first byte is the **pureboot version** — the loader's one identity
|
||||
number, and the only way to tell what a deployed loader is. Nothing else is
|
||||
numbered: the wire protocol has no version, a pureboot version implies it, and
|
||||
the host tool holds that map. The tool states the window of loader versions it
|
||||
speaks (`OLDEST_LOADER`/`NEWEST_LOADER` in `pureboot.py`), and a version that
|
||||
changes the protocol becomes the new floor there. A loader newer than the tool
|
||||
is refused by name rather than decoded on the assumption that nothing moved.
|
||||
|
||||
Two generations exist. **1 through 4** speak one session — a 12-byte info block
|
||||
from `b`, and a command per memory (`R`/`W` flash, `r`/`w` EEPROM, `F` fuses).
|
||||
**5** replaced those with the single `G`/`g` pair over selector-named spaces
|
||||
above; the shipped tool speaks both, choosing on the version it reads, so a
|
||||
deployed pureboot 4 stays drivable and self-updatable to 5.
|
||||
|
||||
Collapsing four command bodies into one transfer loop is what paid for the
|
||||
version: the data space, the host-issued SPM operations and the fuses now share
|
||||
the loop, the cursor and the argument decode that `R`/`r`/`w` each carried a
|
||||
copy of. The loader shrank while gaining all three.
|
||||
|
||||
The tool carries its own version, free to drift; `--version` prints it and the
|
||||
window.
|
||||
@@ -252,11 +320,10 @@ with any pureboot build — a re-timed window, a newer version — using the
|
||||
loader itself as its own staging loader. The image is the loader's own 512
|
||||
bytes as a raw binary, or the Intel HEX the build emits beside it.
|
||||
|
||||
The preflight refuses an image built for another chip: the info block embedded
|
||||
in every pureboot binary (signature, page size, loader base, EEPROM size,
|
||||
flags) must match the device's own, and the error names both. Die revisions
|
||||
share their base signature and geometry, so their images are interchangeable —
|
||||
as the silicon is.
|
||||
The preflight refuses an image built for another chip: the stamp every pureboot
|
||||
binary carries must resolve to the device's own geometry, and the error names
|
||||
both. Die revisions share their base signature and geometry, so their images
|
||||
are interchangeable — as the silicon is.
|
||||
|
||||
1. The staging slot `[base−512, base)` is saved to a host-side state file (on
|
||||
the 1 KB tiny13s that is the whole application, vectors included).
|
||||
@@ -293,8 +360,8 @@ to reset gets its reset pulse and opens the activation window by itself.
|
||||
--info --fuses --flash app.hex
|
||||
|
||||
Operations run in a fixed order within one session: info, fuses, loader
|
||||
update, flash (erase / program / read / verify), EEPROM (the same) — then the
|
||||
loader hands over to the application. `--stay` keeps the session alive
|
||||
update, flash (erase / program / read / verify), EEPROM (the same), then
|
||||
`--peek`/`--poke` — then the loader hands over to the application. `--stay` keeps the session alive
|
||||
instead, and a later invocation reconnects into it. `--flash` and `--eeprom`
|
||||
verify by read-back unless `--no-verify`, and a flash page that reads back
|
||||
wrong is rewritten up to three times before the run stops (see `W` above).
|
||||
@@ -302,9 +369,18 @@ wrong is rewritten up to three times before the run stops (see `W` above).
|
||||
extension. `--force` overrides the refusable safety checks — today, flashing
|
||||
application data into a mega's reset walk region.
|
||||
|
||||
Readouts come one fact per line: `--info` decodes the info block field by
|
||||
field, `--fuses` each fuse byte plus, on a boot-sectioned mega, its decoded
|
||||
meaning. Transfers that take wire time draw a transient progress bar on stderr
|
||||
`--autobaud` opens with the calibration pulse instead of the plain knock, for a
|
||||
loader built `SERIAL autobaud`; the rest of the session is identical, at
|
||||
whatever `--baud` the host chose.
|
||||
|
||||
`--peek ADDR[:N]` and `--poke ADDR:HEX` reach the data space (pureboot 5) —
|
||||
SRAM, and through the same address space the register file and every I/O
|
||||
register. Reading an I/O register can have side effects (reading UDR clears its
|
||||
flags), which is the caller's business to know.
|
||||
|
||||
Readouts come one fact per line: `--info` prints the device's version and
|
||||
signature and the geometry that follows from them, `--fuses` each fuse byte
|
||||
plus, on a boot-sectioned mega, its decoded meaning. Transfers that take wire time draw a transient progress bar on stderr
|
||||
when it is a tty. `-v`/`--verbose` adds the decisions as they happen: knock
|
||||
counts, the programming plan, update state handling and per-phase page counts.
|
||||
|
||||
@@ -323,15 +399,19 @@ Per chip preset, `ctest` runs:
|
||||
the configuration space produces, and a shape the ladder default (always the
|
||||
*fastest* rate a clock reaches) never picks. Pins are immediate operands and
|
||||
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
|
||||
info block within the image's first 256 bytes;
|
||||
- `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
|
||||
below sweeps;
|
||||
- `pbm_*.size` — with `PUREBOOT_FULL_MATRIX=1`, the exhaustive cross product
|
||||
replacing that compact matrix, on **every** chip: 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. Thousands of points per
|
||||
chip, and cheap enough to run rather than reason about;
|
||||
- `pureboot.pi` — the position-independence lint: no absolute `jmp`/`call`, no
|
||||
flash-resident section but `.text`, and the image byte-identical when linked
|
||||
at a different base — which is position independence itself rather than a
|
||||
proxy for it;
|
||||
- `pureboot.planner` — the host tool's pure logic: programming orders and their
|
||||
recovery properties, the surgery, the staging composition, the boot-fuse
|
||||
decode, the update preflight over synthetic fuse bytes, and the repairing
|
||||
@@ -361,7 +441,13 @@ Per chip preset, `ctest` runs:
|
||||
anywhere, which is what makes the path constructible;
|
||||
- `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
|
||||
re-run must complete the update with the application intact.
|
||||
re-run must complete the update with the application intact;
|
||||
- `pureboot.autobaud` (328P, 1284P) — the clock-free build over the GPIO⇄pty
|
||||
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
|
||||
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
|
||||
the application boot, so no wait in activation can be unbounded.
|
||||
|
||||
`size`, `pi` and `planner` are host logic and run anywhere; the
|
||||
simulator-driven targets need simavr and a pty, so they are POSIX-only.
|
||||
|
||||
Reference in New Issue
Block a user