Repository navigation
Releases
rpi-loader 0.5.0
Added
bundle --uploadshows its progress: megabytes sent, percent and
rate on one redrawn line, then a note that the wait which follows is
the board installing. Drawn only when stdout is a terminal.
Fixed
- Ctrl-C stops
bundle --upload. The Ctrl-C handler, which only sets
a flag the serial commands poll, was installed for every command, so a
bundlethat never polls it ignored Ctrl-C for as long as the body was
still being sent. It is now installed only once a command is about to
open the serial link.
Changed
- The
sd-*commands useresident-fatin place ofembedded-sdmmc,
so long file names work.sd-write ./kickstart.toml /kickstart.toml
used to fail with "filesystem error", becauseembedded-sdmmcspeaks
only 8.3 names; it now createskickstart.toml, and a PC sees that
name.sd-read,sd-deleteandsd-mkdirtake long names too,
sd-listshows them, and lookups match either the long name or its
8.3 alias, case-insensitively. - The volume is the first FAT partition in the card's table, whichever
slot it is in, rather than always slot 0 — or the whole card, when it
has no partition table. sd-writereceives the whole file before writing any of it. The
file lands contiguous and goes to the card one transfer per run instead
of a command per 512-byte block, and an upload interrupted partway
leaves the old file untouched instead of truncated.sd-readlikewise
reads the whole file before sending it, so a card error is a clean
failure rather than a transfer that stops short. Both need the file to
fit in the device's RAM.- A failure says why.
sd-*errors that were all "filesystem error"
are now named separately: a name FAT cannot store, a file that already
exists, a file where a directory was needed or the reverse, a full
card, the device out of memory, a card read/write failure, and no FAT32
volume at all. A failedsd-writecommit reports its cause rather than
"write failed". "Filesystem error" is left for a volume found
inconsistent. The CLI still names the old codes, for a loader flashed
before this. sd-listhas no size limit. A listing was built in an 8 KiB
buffer and refused beyond that; it is now allocated.- New files are stamped with the FAT epoch (1980-01-01) rather than a
made-up 2026-01-01. The loader has no clock, and a fixed date that
looks real is worse than one that is plainly a default. rpi-hal0.9.0, up from 0.6.0, for itsresident-fatfeature and
sd::SdBlockDevice, onresident-fat0.3 — whosemount_first_fat
is now how the volume is found.- The firmware now has a heap, running from the top of the loader's
stack to the top of the ARM's RAM. That is above wheremem-writeis
allowed to write, so a kernel being loaded cannot overwrite it.
Building the Pi 1 / Pi Zero image now needs-Z build-std=core,alloc
(themakerecipe passes it).
Images
Copy the image matching your board to the SD card's boot
partition, renamed to the filename in its own name:
kernel7.img (32-bit), kernel8.img (64-bit) or kernel.img
(ARMv6). The firmware loads one of those names and no others,
chosen by the CPU it finds, and a board handed the wrong one
stops without printing anything. Use kernel8.img only with
arm_64bit=1 in config.txt.
bcm2837 covers the Pi 2 v1.2 and Pi 3; bcm2711 covers the
Pi 4; bcm2835 covers the Pi 1 and Pi Zero, and is ARMv6, so it
has no 64-bit counterpart. Verify with
sha256sum -c SHA256SUMS.
rpi-loader-<version>.bash is the bash completion for the host
CLI, for installing without building it first:
sudo cp rpi-loader-<version>.bash \
/usr/share/bash-completion/completions/rpi-loaderThe host CLI is a separate install: cargo install rpi-loader.
rpi-loader-ota 0.3.0
Added
measure::Measure, behindapply: aProgressthat times each
entry's write and read-back, with the clock passed in as a function
(|| Instant::now().as_millis()), so no runtime's clock reaches this
crate. It records rather than logs — ameasure::Timedper entry, with
itsOutcome(written, unchanged, or unfinished when the install
failed on it) and one-lineDisplay— and the record outlives a
failed install.Measure::kernel()is the kernel's write and verify,
the figure comparable between updates;measure::Phase::rate_kib_s
turns one into a rate, andmeasure::rate_kib_s(bytes, ms)does the
same for figures that are not phases of an install, such as how fast
the bundle arrived.Measure::countingalso records each phase's
card commands from aresident_fat::counted::Counters. Moved from the
two boards, each of which had written its own.
Changed
resident-fat0.1 → 0.3, behindapply. Breaking for a board
usingapply: it handsapplya 0.3FileSystem, so its own
resident-fat— and the HAL whose block device sits under it — moves
with this. Nothing in this package's source changed; the format half,
which the CLI uses, has noresident-fatin it at all.
Add it with cargo add rpi-loader-ota. The rpi-loader CLI
builds bundles in this format; a device installs one.
rpi-loader 0.4.0
Added
-
rpi-loader completions [shell], writing a shell completion script
to stdout — bash unless another is named, with zsh, fish, elvish and
powershell also available. Each release also carries a ready-made
rpi-loader-<version>.bash, for installing it without building the CLI
first.Generated from the same
Commandclap builds for--help, so it
describes the CLI that is actually compiled. That is why it is a
subcommand rather than a build script: a build script cannot see the
Clitype without moving it into a library, and a checked-in script
goes stale the first time a flag is renamed.Printed rather than written to a path this picks, because where a
completion belongs differs by shell, distribution and whether the user
can write outside their home. The subcommand's--helpgives the two
usual destinations.The bash script completes
--deviceto the serial ports actually
attached, by askingrpi-loader listwhen Tab is pressed. clap knows
only that the flag takes a string, which its generator turns into
filename completion over the whole machine; which ports exist is a
runtime fact that no generated script can hold. A small wrapper around
clap's own function does it, so everything else completes exactly as
generated, and it is bash-only — the other shells get clap's script
untouched.The bash script also carries a workaround for a clap_complete bug that
makes every subcommand's completion silently empty when the binary's
name contains a hyphen — which this one does. Its generator writes each
case label twice and mangles the name differently each time: the
dispatcher setscmd="rpi__loader__subcmd__boot"while the arm holding
that subcommand's options is spelledrpi__subcmd__loader__subcmd__boot,
so no arm is ever reachable and nothing past the first word completes.
tests/completions.rschecks the invariant rather than the instance —
every label the dispatcher can set must exist as an arm — so a future
desync fails a test whether or not it is this one.Deliberately not clap_complete's dynamic engine, which would do this
for every argument at once: it is behind two unstable feature flags
(clap_complete/unstable-dynamicandclap/unstable-ext), which in a
crate installed withcargo installmeans someone else's install
breaking on a dependency bump — and the script it registers embeds the
absolute path of the binary that generated it, so it could not be the
file shipped with a release. -
Pi 1 / Pi Zero (BCM2835) support.
src/boot6.sis the ARMv6
counterpart to the existing relocating boot stub: the same copy to
0x00200000and jump, without the core-id check (MPIDRis an ARMv7
register, and this chip has one core) and with the two barriers as the
CP15 operations ARMv6 has instead ofdsb/isb— the same substitution
execnow makes.make build-bcm2835producesfirmware/target/ kernel.img, on thearmv6-none-eabitarget, and is the one recipe here
that needs nightly: that target is tier 3, so-Z build-stdhas to
compilecorefor it.The chip is now a feature of this package (
bcm2837by default,
forwarding torpi-hal's) rather than a hardcoded entry on therpi-hal
dependency line. It has to be:rpi-halprefersbcm2837over
bcm2835when both are enabled, so a Pi Zero build that could not turn
bcm2837off would have silently compiled the Pi 3's peripheral base
into a Pi Zero image. Existing invocations are unaffected — a plain
build is still the Pi 2/3 one, and--features bcm2711still the Pi 4
one.The
rpi-halfloor moves to 0.6.0, which is where thebcm2835
feature arrived.
Images
Copy the image matching your board to the SD card's boot
partition, renamed to the filename in its own name:
kernel7.img (32-bit), kernel8.img (64-bit) or kernel.img
(ARMv6). The firmware loads one of those names and no others,
chosen by the CPU it finds, and a board handed the wrong one
stops without printing anything. Use kernel8.img only with
arm_64bit=1 in config.txt.
bcm2837 covers the Pi 2 v1.2 and Pi 3; bcm2711 covers the
Pi 4; bcm2835 covers the Pi 1 and Pi Zero, and is ARMv6, so it
has no 64-bit counterpart. Verify with
sha256sum -c SHA256SUMS.
rpi-loader-<version>.bash is the bash completion for the host
CLI, for installing without building it first:
sudo cp rpi-loader-<version>.bash \
/usr/share/bash-completion/completions/rpi-loaderThe host CLI is a separate install: cargo install rpi-loader.
rpi-loader-ota 0.2.0
Added
-
apply, behind the feature of the same name: the other half of an
update. It takes a validated bundle and aresident-fatvolume and
writes every entry where its path says, in an order the crate imposes
rather than one the bundle chooses — ordinary files, then Raspberry Pi
firmware, thenconfig.txt, then the kernel. The kernel is last
because while a board has one boot image that write is the commit, so
everything that could fail has to have failed already.Nested destinations are created as needed, since a bundle can carry a
path andwrite_fileresolves a parent rather than making one. -
Entries the card already holds are read and not rewritten. The same
function answers both halves of the question — before a write it
decides whether to write at all, and after one it is the
verification — so a skipped entry is checked exactly as strictly as a
written one. A bundle carrying the Raspberry Pi firmware carries about
3 MB of it, and that changes roughly once a year; on hardware, applying
an unchanged bundle now costs 1055 ms and no card writes at all,
against 3724 ms to write the same thing in full. -
Progress, which is how timing stays with the caller. Every method
defaults to doing nothing and()implements the whole trait, so a
caller names only what it wants. The boundaries separate the write from
the read-back deliberately: those are not the same operation and do not
have the same fix, so one figure covering both would hide which of them
an improvement had touched. -
Checksum, the streaming form ofchecksum, for checking a file
already on a card against a fixed scratch buffer rather than a second
copy of it in memory.
Changed
tests/is no longer published. The suite builds its FAT32 volumes
withmkfs.vfatand judges them withfsck.vfat, so it fails rather
than skips withoutdosfstools— a suite that cannot run from the
tarball it ships in says nothing about the crate.
Add it with cargo add rpi-loader-ota. The rpi-loader CLI
builds bundles in this format; a device installs one.
rpi-loader 0.3.0
Added
-
bundle, a subcommand that packs an over-the-air update bundle
from abundle.tomland, with--upload <url>, posts it to a running
board. The first subcommand that opens no serial port: the container is
rpi-loader-ota, shared with the firmware that installs one, and this
is the half that builds them. A manifest rather than arguments because
what goes into a bundle is a property of the project — a kernel, whole
directories, firmware blobs andconfig.txt, each with a destination —
and arguments carrying that would put a card's layout in aMakefile
where nothing can check it.Uploading is plain HTTP with no TLS anywhere in the dependency tree;
the endpoint is a board on a local network.--sdcard <dir>writes the bundle's contents onto a card in a reader
instead, for the update that cannot arrive over the network — a build
that changes the format the running firmware reads, or one that broke
networking, or a board not on the network yet. It unpacks the bundle it
just built rather than copying the sources again, so a card written by
hand and a board updated over HTTP carry the same bytes by
construction. It writes only what the bundle carries, which is less
than a card needs to boot.
Images
Copy the image matching your board to the SD card's boot
partition, renamed to kernel7.img (32-bit) or kernel8.img
(64-bit) -- the firmware loads those names and no others. Use
kernel8.img only with arm_64bit=1 in config.txt.
bcm2837 covers the Pi 2 v1.2 and Pi 3; bcm2711 covers the
Pi 4. Verify with sha256sum -c SHA256SUMS.
The host CLI is a separate install: cargo install rpi-loader.
rpi-loader-ota 0.1.0
First release.
Added
-
The bundle container, version 2 of the format: a list of
(path, role, bytes) under an IEEE CRC-32, encoded on a host and
validated on a device by the same code. An update can therefore replace
anything on a boot partition — a settings file, a firmware blob, a
certificate,config.txt, the Raspberry Pi firmware itself — rather
than a kernel and one hardcoded directory of assets. -
Bundle::parse, which allocates nothing. It validates in place and
borrows, so a device holds one buffer — the upload as it arrived — and
nothing else.encode, behind the defaultallocfeature, is the
other direction. -
Roles. An entry is a
File, theKernel,FirmwareorConfig,
and the installer decides what that means. Naming the boot image by
role rather than by position or filename is what lets the device choose
its own destination, which matters to anything running two kernel
slots. -
Rules enforced identically in both directions, so a packer cannot
build what a device would reject: no absolute paths,..components,
empty components or backslashes; no duplicate destinations; at most one
kernel and oneconfig.txt; and astart*.elfonly alongside its
matchingfixup*.dat, since the two are released together and a
mismatched pair does not boot. -
checksum, so a device checksums a file already on its card the
same way the packer checksummed the entry, and can skip rewriting one
whose bytes have not changed.
Add it with cargo add rpi-loader-ota. The rpi-loader CLI
builds bundles in this format; a device installs one.
v0.2.0
Added
-
eeprom-writeandeeprom-read: program and dump a serial EEPROM
on the HAT ID bus (BSC0 on GPIO0/1,ID_SD/ID_SC), which is where a
board's identity lives — the HAT specification's image, and whatever a
design puts beside it.eeprom-writetakes the.eepfileeepmake
produces;eeprom-readwith no--lengthreads the image length out
of the HAT header first, rather than dumping the whole address space.
Both take--address(default0x50) and--offset; the write also
takes--page-size(default 32, safe for every part from the 24C32 up).The device reads every page back after programming it and fails the
command on a mismatch, because a write-protected part acknowledges
every byte and stores none — without the read-back, writing to one
would report success and change nothing.New wire commands
EEPROM_READ(9) andEEPROM_WRITE(10), and error
codes 7 (I2C transfer failed), 8 (read-back mismatch), 9 (a request
outside what the device will address) and 10 (the part never answered
the read-back).
Changed
-
The handshake's protocol version is now 2, in both halves. It is a
bump for an addition rather than a change, which the version byte
cannot express — but the mismatch it names is the ordinary one for this
project, not an exotic one: the loader image is flashed once and left on
the card for months while the CLI is reinstalled from crates.io
whenever, so a newer CLI meeting an older loader is the expected way for
the two to drift.Without it,
eeprom-writeagainst a 0.1.0 loader comes back as
error code 0— the unknown command byte answered withFAIL— which
reads as a fault on the I2C bus rather than as a stale image on the SD
card. With it, the handshake saysdevice protocol version 1, expected 2first. A mismatch stays a warning, so an older CLI keeps driving a
newer loader for every command they share.
Fixed
- Terminal mode no longer puts the invoking terminal into raw mode when
stdin is not a terminal.crossterm'senable_raw_modedoes not consult
stdin — on Unix it falls back to opening/dev/tty— so redirecting stdin
did not prevent it, and a caller that stopped the loader with a signal
never reached the restore, handing back a shell with no echo and\nno
longer implying\r. Affected anything drivingbootorterminalfrom
a script rather than a keyboard.
Images
Copy the image matching your board to the SD card's boot
partition, renamed to kernel7.img (32-bit) or kernel8.img
(64-bit) -- the firmware loads those names and no others. Use
kernel8.img only with arm_64bit=1 in config.txt.
bcm2837 covers the Pi 2 v1.2 and Pi 3; bcm2711 covers the
Pi 4. Verify with sha256sum -c SHA256SUMS.
The host CLI is a separate install: cargo install rpi-loader.
v0.1.0
Added
- The loader firmware: a self-relocating UART command agent for the
Raspberry Pi 2, 3 and 4 that stays resident and services commands, so
the SD card is rewritten only to re-flash the loader itself. Builds for
AArch32 (kernel7.img) and AArch64 (kernel8.img), for BCM2837 and
BCM2711. mem-write,exec, andbootfor getting an image into memory and
running it, with CRC-checked chunks and per-chunk retries.sd-list,sd-read,sd-write,sd-delete, andsd-mkdirfor
working with the SD card's FAT boot partition over the same link.- A bidirectional
terminal, so a kernel that prompts for input can be
driven. Ctrl-] exits; every other key, Ctrl-C included, reaches the
device. list, which reports the host's USB serial ports without opening one.- The host CLI in Rust, published to crates.io as
rpi-loader.
Images
Copy the image matching your board to the SD card's boot
partition, renamed to kernel7.img (32-bit) or kernel8.img
(64-bit) -- the firmware loads those names and no others. Use
kernel8.img only with arm_64bit=1 in config.txt.
bcm2837 covers the Pi 2 v1.2 and Pi 3; bcm2711 covers the
Pi 4. Verify with sha256sum -c SHA256SUMS.
The host CLI is a separate install: cargo install rpi-loader.