rpi-kickstart 0.4.0

Application scaffolding for bare-metal Raspberry Pi boards: console, config, storage, web and the rest of the bring-up that is the same on every board, on top of rpi-hal
Documentation

rpi-kickstart

CI crates.io docs.rs

The bring-up a bare-metal Raspberry Pi application does before it is an application, on top of the rpi-hal crate.

rpi-hal stops at the peripheral: it hands out a Uart, a Mailbox, an Sd. What every board then does with them turns out to be the same program written again — a console the whole image can print to, a heap sized from what the firmware reports, a card mounted and a settings file read off it, a site served out of RAM, a clock that something has to set. This crate is that program, written once.

Status

Early. The repository, the build and the release path are in place, and the modules are being moved in one at a time, each behind the feature named for it.

Feature What it is
console One sink for the whole image, and logln!
entropy The SoC's hardware RNG, and the getrandom backend over it
heap A TLSF #[global_allocator], sized from what the firmware reports
config A TOML settings file into the board's own schema, with file:line:column errors and the checks TOML cannot make
storage The card's FAT volume, mounted with resident-fat; with config, config::load reads the settings file off it
site Everything under /WWW on the card, read into RAM at boot and looked up by request path
clock The wall clock: one sink every time source sets and everything else reads, None until set; with storage, files are stamped with it
tz Local time over a tz-rs zone
sntp Sets clock from an NTP server, and keeps it set
tls TLS 1.3 client connections over an embassy-net TCP socket, with real certificate verification — over rustls-rustcrypto, pre-release and unaudited
https A minimal HTTPS client over tls: one request per connection, chunked bodies reassembled
notify Outbound notifications over https: a Channel trait, with Ntfy (push) and Resend (email) shipped
webpki-roots Mozilla's trust anchors for tls, compiled in
net Gets the board onto the network whichever way it can — USB Ethernet first, the radio as a fallback — as an embassy-net stack
ethernet USB Ethernet as a net interface: the LAN9514 on a Pi 2B/3B and the LAN7800 on a 3B+, told apart on the bus
wifi The on-board radio as a net interface, through the bring-up the board supplies
mdns An mDNS responder for one name, re-read on every pass so a rename takes effect without a reboot
web The picoserve socket loop a pool of the board's tasks runs, and the pieces its router is built from; with site, SiteFiles serves the card's /WWW
auth Password login for the web interface: a PBKDF2 verifier, a session table, the session cookie, and RequireLogin, a layer over every route that asks the board's whitelist
metrics A writer for Prometheus's text exposition format: escaped labels, and decimals without a float formatter; with heap and storage, the heap's usage and the card's command counts as ready-made series, and the build's identity (version, commit, dirty, Cargo.lock hash) as <prefix>_build_info
metrics-build The capture behind metrics::BuildInfo, for a board's build.rs: the commit, whether the tree was dirty, and a hash of Cargo.lock, read by build_info!(). Host only — it needs std and runs git
ota The over-the-air update route — read, install, answer in one shared JSON shape — and the reboot handshake after it
splash A boot logo baked into the kernel as run-length-encoded RGB565, decoded with no allocation
splash-build The encoder for splash, for a board's build.rs: a PNG in, the bytes splash::Image reads. Host only — it needs std

The examples read their settings from kickstart.toml at the root of the card; copy kickstart.toml.example and fill it in.

entropy and heap each install something the whole binary resolves against — a getrandom backend and a #[global_allocator]. There is no way for a library to make such a choice overridable, so both are off by default and enabling one is how a board says it wants this crate to take it.

Chips

bcm2835 (Pi 1, Zero), bcm2837 (Pi 2, 3) and bcm2711 (Pi 4) forward to rpi-hal's features of the same name. None is a default. There is no bcm2712 (Pi 5) yet: rpi-hal's support for that chip is preliminary, and doesn't yet cover what this crate builds on.

Most boards never name one here: an application's own rpi-hal line already selects a chip and cargo unifies it across the graph. They exist for a build that has no such line — this repository's own, and a consumer that depends on rpi-kickstart without depending on rpi-hal directly.

They are not mutually exclusive, because cargo features cannot be. Two at once resolves by rpi-hal's precedence — bcm2712 > bcm2711 > bcm2837 > bcm2835 — rather than failing, which is why nothing here builds with --all-features; the make recipes name an explicit set instead.

entropy and heap need a chip; console deliberately reaches nothing in rpi-hal, so a board taking only that needs no selection at all.

examples/hello.rs takes no features at all, which is what makes it useful: it proves the parts that have nothing to do with the crate's contents — toolchain, targets, linker script, both load addresses — so if it boots, none of those is what broke.

Design

Everything is opt-in. Each capability is its own feature and its own module, and none are on by default. A board states what it is and pays for nothing else — a board with no network takes no TLS, and a board with a DS3231 takes no SNTP.

Everything is replaceable. Where a piece has a plausible second implementation — the time source above all — what this crate owns is the sink the rest of the image reads through, and filling it is the application's choice. Nothing here is a fork to escape from.

The tasks stay with the application. An #[embassy_executor::task] cannot be generic, so this crate never declares one: it exposes async fns and the application wraps them in tasks with its own concrete types. Routing, the #[global_allocator] and the #[panic_handler] stay there too, for the same reason — they are single, program-wide choices that a library taking them away cannot give back.

Relationship to the other crates

Crate What it is
rpi-hal The HAL: peripherals, boot sequence, MMU, drivers
rpi-hal-embassy Embassy's platform pieces: time driver, executor, network adapters
rpi-kickstart This crate: the application layer above both
rpi-loader-ota The OTA bundle format, shared with the loader

Building

cargo build                                         # AArch32 (Pi 2, Pi 3)
cargo build --target aarch64-unknown-none-softfloat # AArch64 (Pi 3)

AArch32 is the default target only because it is the one a Pi 2 can run; make build covers both. The example images link against rpi-hal's published rpi-link.x, which is already at the load address for the target being built — 0x8000 for a kernel7.img, 0x80000 for a kernel8.img — so an image direct-boots where the firmware puts it.

make pre-commit runs the whole check set: formatting, clippy, the library and example builds for both architectures, and a doc build with warnings denied.

ARMv6 (Pi 1, Pi Zero) has no make recipes here, unlike rpi-hal and rpi-hal-embassy. That target is tier 3 — no precompiled core, so nightly and -Z build-std — and nothing in this crate is architecture-specific, so adding it is a make change rather than a source one whenever a board wants it.

Examples

See examples/. Each one's header comment says what it demonstrates and what its output is evidence for.

scripts/build-example.sh <name> produces target/kernel7.img; scripts/build-example64.sh <name> produces target/kernel8.img. Copy either to an SD card's boot partition, or send it over UART with rpi-loader:

scripts/build-example.sh hello
rpi-loader --device /dev/ttyUSB0 boot --load-addr 0x8000 target/kernel7.img

License

MIT OR Apache-2.0