Tools
Relevant source files
- userspace/tools/bpf_loader/src/main.rs
- userspace/tools/signed_bpf_loader/src/main.rs
- userspace/tools/signed_bpf_loader/src/lib.rs
- userspace/tools/signed_bpf_loader/src/syscall_probe.rs
- userspace/tools/signed_bpf_loader/Cargo.toml
- userspace/tools/rk_cli/src/main.rs
- userspace/tools/rk_cli/src/commands/bundle.rs
- userspace/tools/rk_cli/src/commands/runtime.rs
- userspace/tools/rk_cli/src/commands/runtime/audit.rs
- userspace/tools/rk_cli/src/commands/runtime/audit/decode.rs
- userspace/tools/rk_cli/src/commands/verify.rs
- userspace/tools/rk_cli/tests/managed_bundle.rs
- userspace/tools/rk_bridge/src/main.rs
- userspace/tools/rk_bridge/src/input.rs
- userspace/tools/rk_uart_forwarder/src/main.rs
- docs/architecture/protocols/rk-bridge.md
The tools split into two groups. bpf_loader, signed_bpf_loader and rk_uart_forwarder are guest binaries shipped in the rootfs. rk (the rk_cli package) and rk_bridge (binary rk-to-ros) are standalone host packages excluded from the kernel workspace.
| Tool | Where it runs | Artifact (components.toml) |
|---|---|---|
bpf_loader | Guest, /bin | rootfs:bpf_loader |
signed_bpf_loader | Guest, /bin | rootfs:signed_bpf_loader |
rk_uart_forwarder | Guest, /bin | rootfs:rk_uart_forwarder |
rk_cli | Host | host:rk |
rk_bridge | Host | host:rk-to-ros |
bpf_loader
An end-to-end BPF demo using hand-assembled instructions, with a banner “axiomos BPF end-to-end demo: Maps + Ringbuf + Timer Attach”. It runs five steps: create an array map for a tick counter (key 4 bytes, value 8, one entry), create a ringbuf, load a program that increments the counter and writes to the ringbuf, attach it to the timer, then poll until it has seen 10 events and read the counter back through MAP_LOOKUP_ELEM. It also exercises the privileged load and unload paths. It uses helpers such as map lookup, ringbuf_output, trace_printk and kernel heap statistics. It submits raw instructions through BPF_PROG_LOAD; whether the kernel accepts them depends on the signing policy of the build (see signed_bpf_loader for the signed-container path).
signed_bpf_loader
The loader that init spawns in default (signed) builds. With the managed-runtime feature it becomes the managed-runtime installer instead (see Managed-runtime installer mode). The description below is the default, legacy mode.
- Opens
/var/lib/rkbpf/programs/startup.rbpf. On failure printsSIGNED_BPF_INPUT_MISSINGand exits 2. - Reads up to 256 KiB. An empty or full buffer prints
SIGNED_BPF_INPUT_INVALID(exit 4). - Calls
BPF_PROG_LOAD_ELFwith the container bytes. A rejection printsSIGNED_BPF_LOAD_REJECTED(exit 5). - On success prints
SIGNED_BPF_LOAD_OKand parks inpause().
The container must be signed (Ed25519) and verified by the kernel against the embedded trust key. Bake one into the image with AXIOM_SIGNED_BPF_STARTUP_PATH at build time, as described in Building. The input path comment notes that rk deploy --program startup.rbpf places the signed container here.
Managed-runtime installer mode
The package has a lib.rs plus two features: managed-runtime (pulls in shrike_link and zerocopy) and managed-syscall-probe (implies managed-runtime). Without managed-runtime, the binary is the legacy loader above. With it, the binary does not read startup.rbpf. Instead it runs a UART service loop:
Transport(fromlib.rs) reads bytes from fd 0, feeds them to theshrike_linkinstaller frameDecoder, and keeps the encoded reply in a fixed buffer that is written to fd 1 until fully sent.- For each decoded request,
dispatchforwards the message to the kernel throughminilib::managed_bpf_bytes(the managedSYS_BPFcommands). A stop command (0xffff) callsestop_trigger. - Read errors other than
EAGAINandEINTRreset the endpoint and decoder, and an idle loop sleeps 1 ms.
managed-syscall-probe replaces the service loop with an opt-in QEMU probe (syscall_probe.rs) that runs the real installer binary with real syscalls, credentials, process memory and the preparation worker before any UART service. It prints MANAGED_SYSCALL_PROBE_FAIL stage=... observed=... expected=... and exits 1 on a mismatch. The protocol, framing and kernel side are described in Installer.
rk (rk_cli)
A host CLI for signing and managing BPF programs. The package is rk-cli, the binary rk. Global flags are -v/--verbose and -c/--config. Configuration lives in ~/.config/rkbpf (trusted keys in trusted_keys).
| Command | Purpose |
|---|---|
rk key generate -o PREFIX | New Ed25519 keypair, writes PREFIX.pub and PREFIX.key |
rk key export -k FILE [-f pem or der or raw] | Export a public key |
rk key import -k FILE -a ALIAS | Import a trusted public key |
rk key list | List trusted keys |
rk sign -i OBJ.o -k KEY [-o OUT.rbpf] | Sign a BPF object into a container |
rk verify -i FILE [-k PUB or -t TRUSTED_DIR] | Verify a signed program, or an AXMB managed bundle (see below) |
rk build -s SRC [-o DIR] [-p cloud or embedded] | Build a BPF program (default profile embedded) |
rk deploy -p FILE... [-t TARGET] [-a ATTACH] | Deploy signed programs; target localhost (default), ssh://user@host or user@host |
rk list, rk unload -p ID, rk info -i FILE | Inspect and manage |
rk init NAME [-p PROFILE] | Scaffold a project |
rk bundle | Sign normalized raw BPF instructions as a managed AXMB bundle |
rk runtime --port DEV COMMAND | Upload and administer managed controllers over the debug UART |
rk audit-decode INPUT --output FILE | Decode a saved bounded audit export offline |
Deploy refuses files that do not start with the RBPF magic. Local deploy copies into /var/lib/rkbpf/programs/ as NAME.rbpf. Signing uses ring.
rk bundle
Signs a managed controller bundle. The input is normalized raw little-endian BPF instructions, not an ELF object (the managed examples build these with llvm-objcopy, see Demos). The key is an Ed25519 PKCS#8 private key. The output path must be new.
| Flag | Meaning |
|---|---|
--input, --output, --key | Raw instruction file, new AXMB path, PKCS#8 private key |
--behavior-id | Logical behavior ID, exactly 32 hexadecimal characters |
--revision | Signer-provided behavior revision |
--motor-pair | Request permission to emit one motor pair per invocation |
--envelope | Declare the read-only envelope at local handle 0 |
--array-value-size, --array-entries | Declare one private ARRAY (each requires the other) |
It prints the behavior ID, revision, signer public key and fingerprint, payload and bundle digests and size, then states that it only signed: kernel authentication, verification, admission and activation have not run. Format and trust rules are in Managed bundles.
rk runtime
rk runtime --port /dev/ttyACM0 COMMAND talks to the installer over a Linux serial character device. It configures the port with stty at 115200 baud, raw mode and no flow control, and sends one bounded request at a time. The first exchange is a reset frame whose challenge must be echoed back. If a response times out, the tool warns that the outcome may be unknown and tells you to query the slot or operation before retrying.
| Command | Purpose |
|---|---|
query [--operation ID or --artifact N] | Slot status, a retained operation (0 selects the latest), or an artifact |
upload BUNDLE | Upload a canonical signed bundle in chunks, then enqueue preparation |
activate, rollback, deactivate, retire | Lifecycle requests; each needs --expected-generation and --artifact |
cancel ID [...] | Cancel an upload, preparation or rearm by ID, or an exact lifecycle request (with --expected-generation, --artifact, --target-kind 1 to 4) |
rearm --expected-generation N | Requalify the stopped link; leaves the controller inhibited until activation |
stop | Trigger trusted stop; does not unload code or release e-stop |
audit-status | Retained audit interval, clock and explicit loss counters |
audit-export --output FILE | Export one bounded audit interval as JSON lines, including gaps |
Lifecycle commands carry the generation you observed, so a stale view is rejected rather than applied. See Lifecycle, Handoff and rearm and Recorder.
rk audit-decode and rk verify
rk audit-decode INPUT --output FILE interprets a saved audit export without opening a serial device. The input is capped at 8 MiB and must end with a newline; the output file must be new. The source calls it offline interpretation of one retained window, never a qualification verdict.
rk verify reads the first magic bytes. If they are the managed magic, it authenticates the AXMB bundle against the full trusted signer key (from -k or the trusted-keys directory), enforces the 256 KiB bundle limit, and prints the identity as JSON. It then states that bytecode verification and live admission are still required, so a pass here is authentication only.
CI lints and tests rk_cli and rk_bridge as standalone workspaces. Whether the end-to-end sign, bake, boot loop is exercised on hardware is manual.
rk_uart_forwarder
A guest binary that opens the pinned /sys/fs/bpf/maps/sched_switch_events ringbuf with BPF_OBJ_GET, polls it with BPF_RINGBUF_POLL (backing off 10 ms when empty), and prints newline-delimited JSON to stdout. On the Pi, stdout is the UART when launched from init. If BPF_OBJ_GET fails it prints “run sched_switch_export_demo first” to fd 2 and exits 1. It emits a meta banner, a ready record, then one sched_switch record per event with a monotonically increasing seq. Protocol version is 1.
rk_bridge (rk-to-ros)
A host program that bridges events to stdout or ROS2. It is its own Cargo package (with [workspace]) and has a fuzz target (rk_bridge/fuzz, event_stream).
| Option | Default | Meaning |
|---|---|---|
-m, --map PATH | /sys/fs/bpf/maps/sched_switch_events | Pinned ringbuf (needs axiomOS sys_bpf) |
-t, --topic | /rk/events | ROS2 topic; with the default, events route to /rk/imu, /rk/motor, /rk/safety, /rk/gpio, /rk/timeseries, /rk/sched_switch, /rk/trace |
--stdout | off | Print instead of publishing |
-f, --format | json-lines | json, json-lines or text |
-r, --rate-limit | 0 | Events per second, 0 is unlimited |
--poll-interval | 10 | Milliseconds |
--event-kind | sched-switch | legacy or sched-switch |
--input stdin | none | Read JSON lines from stdin instead of a pinned object |
--demo | off | Synthetic events |
Real ROS2 publishing requires the Cargo feature ros2, which uses the host ros2 CLI and publishes std_msgs/msg/String. Without it, construction of the ROS publisher does not publish to a real graph.
Wire protocol
The forwarder and rk-to-ros --input stdin talk over a line-oriented JSON protocol (docs/architecture/protocols/rk-bridge.md). It is deliberately minimal, a stopgap until a faster transport such as Ethernet exists.
# Host side
socat /dev/ttyUSB0,raw,echo=0,b1500000 STDOUT \
| rk-to-ros --input stdin --topic /rk/sched_switch| Record | Fields | Notes |
|---|---|---|
meta | type, protocol (u32), source | First record each session. Consumer aborts if protocol is not 1 |
ready | type, map_fd | Optional, after the pinned object is opened |
sched_switch | type, seq, cpu, prev_pid, prev_tid, next_pid, next_tid | seq is forwarder-assigned; 0 if absent |
Consumer rules: skip lines not starting with {; tolerate unknown records and fields; treat a malformed line as recoverable; treat end of stream as clean. There is no reliability layer: the doc estimates a ceiling near 1500 events per second at 1.5 Mbit/s and notes that seq lets you detect, not recover, gaps. Serial-port input is a stated follow-up; only stdin is supported today.