Drivers and Control Link
Relevant source files
- kernel/src/driver/mod.rs
- kernel/src/driver/pci.rs
- kernel/src/driver/block.rs
- kernel/src/driver/raw.rs
- kernel/src/driver/ram.rs
- kernel/src/driver/iio.rs
- kernel/src/driver/virtio/block.rs
- kernel/src/driver/virtio/gpu.rs
- kernel/src/driver/virtio/hal.rs
- kernel/src/driver/virtio/mmio.rs
- kernel/src/file/devfs.rs
- kernel/src/serial.rs
- kernel/src/watchdog.rs
- kernel/src/actuation.rs
- kernel/crates/kernel_device/src/lib.rs
- kernel/crates/kernel_pci/src/lib.rs
- kernel/crates/kernel_devfs/src/lib.rs
- kernel/crates/shrike_link/src/lib.rs
- kernel/src/arch/aarch64/platform/rpi5/control_link.rs
- firmware/README.md
- firmware/shrike/README.md
- firmware/shrike/fpga/README.md
This page covers the device-model crates, the drivers in kernel/src/driver, the console and watchdog hooks, and the Shrike control link that connects the Pi 5 to an external RP2040/FPGA safety sidecar.
Device model
Three small no_std crates under kernel/crates hold the portable part of the model; the kernel instantiates them with KernelDeviceId, a process-wide counter.
| Crate | Contents |
|---|---|
kernel_device | Device and DeviceId traits; BlockDevice trait with a const sector size (512 in the kernel); RawDevice trait (exposes a physical frame range) and RawDeviceRegistry; RegisterDeviceError::AlreadyRegistered |
kernel_pci | PciAddress (bus, device, function) with typed getters for vendor, device, header type, BARs and subsystem id; ReadConfig/WriteConfig/ConfigurationAccess traits; PortCam (x86 port I/O config access at 0xCF8/0xCFC) |
kernel_devfs | DevFs filesystem implementing the kernel_vfs FileSystem trait; DevFile nodes Null, Zero, Serial and BlockDeviceFile; ArcLockedDevFs wrapper |
Driver availability by target
| Driver | x86_64 | aarch64 virt | aarch64 rpi5 |
|---|---|---|---|
| PCI bus scan and driver registry | Yes | No (module gated to x86_64) | No |
| VirtIO block | Yes, over PCI | Yes, over MMIO | No |
| VirtIO GPU | Yes, over PCI (draws a test pattern) | No | No |
| Embedded RAM disk | No | No | Yes (root filesystem) |
| IIO manager | Yes | Yes | Yes |
| Serial console | COM1 16550 | PL011 | PL011 UART10 |
| GPIO, PWM, control link | No | No | Yes (RP1) |
PCI and VirtIO (x86_64)
pci::init in driver/pci.rs scans all 256 buses and 32 slots with a port-I/O configuration access method, expands multifunction devices to 8 functions, and matches each address against PCI_DRIVERS. That registry is a linkme distributed slice: a driver registers a PciDriverDescriptor with a name, a type (Generic or Specific), a probe function and an init function.
Selection rules:
- A
Specificdriver wins over aGenericone. - Two matches of the same type is an error: the device is skipped and an “ambiguous PCI driver match” error is logged.
- A failing
initis logged and does not stop the scan.
Registered drivers:
| Driver | Match | Behavior |
|---|---|---|
virtio-blk | vendor 0x1af4, device 0x1000 to 0x103f, subsystem 0x02 | Builds a VirtIO block device using the virtio_drivers crate and registers it as a block device |
virtio-gpu | vendor 0x1af4, device 0x1050 | Sets up a framebuffer, fills it with a gradient pattern, flushes, and registers a RawDevice that exposes the framebuffer’s physical frames |
virtio/hal.rs implements the virtio_drivers Hal trait on the kernel’s physical and virtual memory managers (DMA allocation, MMIO mapping, address translation).
VirtIO MMIO (QEMU virt)
virtio/mmio.rs, compiled only for aarch64 with virt, probes 32 fixed slots from 0x0A00_0000 in 0x200-byte steps. MmioTransport::new validates the magic value and version; empty slots are skipped silently. Only DeviceType::Block is initialized; other device types are recognized and ignored, and the source carries a TODO for network, console and GPU.
Block layer and devfs
driver/block.rs keeps a global map of block devices. BlockDevices::register_block_device assigns a sequential id, publishes a node named blkN in the devfs (mounted at /dev, so it appears as /dev/blkN), and then inserts the device in the registry while holding the devfs write lock so the node cannot be observed before the registry entry. Errors are typed (invalid generated path, devfs failure) and the registry is unchanged on error. At boot the kernel looks up block device 0 and mounts it as an ext2 root filesystem.
RamBlockDevice (driver/ram.rs) is a 512-byte-sector block device backed by heap memory. On rpi5 it is created from a disk image compiled into the kernel (EMBEDDED_DISK_PATH) and registered as block device 0. This is how the Pi 5 image gets its root filesystem today; there is no SD, NVMe or USB storage driver.
RawDevices (driver/raw.rs) registers raw devices in a registry; its own TODO notes that no VFS entry is created for them yet.
The devfs is initialized in file/devfs.rs with /serial, /stdin (a Null device), /stdout and /stderr (both Serial writers to the kernel console), under a source TODO for proper stdio. Serial is write-only: reads return end-of-file.
Serial console
serial.rs provides serial_print/serial_println per architecture:
- x86_64: a lazily initialized COM1 (
0x3F8) 16550 port; interrupts are disabled around each write. - aarch64
rpi5: the PL011 UART10UARTglobal, with IRQs masked around the lock if they were enabled. - aarch64
virt: the PL011 at0x0900_0000.
Industrial I/O (IIO)
driver/iio.rs is the sensor-event path that feeds BPF programs. Its header says that “in the future” it will interface with real I2C/SPI drivers and that for now it only provides the mechanism to inject sensor events and trigger BPF hooks. IioManager holds IioDevice entries with channels (for example AccelX, AccelY, AccelZ) and dispatch_event runs the attached IIO hooks.
At boot init_simulated_device registers a simulated accelerometer (id 0). On x86_64 and non-Pi aarch64 builds a background task generates events; on rpi5 that task is skipped on purpose so it does not preempt init or benchmarks. The Sensor frames arriving from the Shrike link are injected into IIO with a synthetic device id for an ultrasonic proximity channel; there is no real I2C or SPI sensor driver.
Watchdog
watchdog.rs is ten lines: trigger_estop and invariant_failed(reason) (which logs the reason first) call actuation::watchdog_estop_trigger, which latches the actuation monitor, applies safe-drive values to the outputs and notifies the control link to assert a soft e-stop. It is a software hook, not a hardware watchdog: the README lists “No hardware watchdog integration” (issue 72) under known limitations.
Actuation seam
actuation.rs is the single path through which actuation requests (guard_pwm, guard_gpio, e-stop operations) are decided by a global monitor from kernel_bpf::actuation and then applied to RP1 MMIO or sent over the control link. The decision logic is a pure, host-tested crate; this file owns the global monitor, an APPLY_LOCK that is always taken with IRQs masked (it is reachable from both thread context and BPF hooks firing in IRQ context), and the MMIO application. Details of the policy belong to the BPF and safety documentation.
Shrike control link
What Shrike is
From firmware/shrike/README.md: the directory holds the platform-independent control and motor state machines, the physical RP2040 firmware and a mock-HAL host simulation. The RP2040 firmware’s own header describes it as bridging the Pi 5 UART control link to the robot’s actuators and sensors: UART bytes are decoded by shrike_link, pass through the watchdog and drive L298N motors, and a periodic ultrasonic Sensor frame goes back to the Pi. The firmware is built and versioned separately from the kernel workspace; firmware/README.md states this explicitly.
| Directory | Role |
|---|---|
firmware/shrike/control | Platform-independent no_std control and motor state machines (shrike_control) |
firmware/shrike/rp2040 | Physical RP2040 firmware and target config (shrike_rp2040) |
firmware/shrike/simulation | Mock-HAL host simulation and state-machine tests (shrike_rp2040_host_sim) |
firmware/shrike/fpga | shrike_safety_gate.sv reference and testbench for the ForgeFPGA |
The pin map in rp2040/src/main.rs is documented as bench configuration: GPIO0/1 are UART0 TX/RX, GPIO2 and GPIO4 are the left and right motor enables (PWM), GPIO6/7 and GPIO8/9 are left and right direction, GPIO10/11 are ultrasonic trigger and echo, and GPIO12 is the e-stop input.
The firmware header says its pin map, baud and timing are bench config knobs to be matched to the wiring, and that real-hardware behavior is unverified until flashed. The FPGA README says the gate directory contains no ForgeFPGA pin-constraint or project file because the repository has no finalized Shrike-lite board revision or pin map. Both RP2040 firmware and the Pi 5 are tiered “Supported after HIL” in ADR-0003, which means that HIL evidence is still required.
FPGA safety envelope
Per the FPGA README, shrike_safety_gate.sv is the v0.4 final motor-PWM gate. It receives the RP2040’s signed per-mille setpoints and RP2040-generated PWM, and its outputs are the only signals meant to reach the motor-driver enable inputs. The physical e-stop is an active-low latching line routed directly to the FPGA and must not be generated by RP2040 firmware. The module is clockless: a low e-stop or any command outside the envelope (default plus or minus 800 per-mille) forces both PWM outputs low combinationally, and one invalid command disables both motors. The e-stop input is deliberately not synchronized. The repository provides an iverilog or Verilator testbench for the gate; no synthesized bitstream, pin constraints or electrical timing are included.
Wire protocol (shrike_link)
kernel/crates/shrike_link is the shared no_std crate used by both the kernel and firmware. Frame layout:
SYNC(0x7E) VER(0x01) TYPE LEN PAYLOAD[LEN] CRC_LO CRC_HICRC16-CCITT/FALSE (polynomial 0x1021, init 0xFFFF) covers version through payload. Maximum payload is 16 bytes. The decoder is a byte-fed state machine; once LEN is accepted, payload and CRC are consumed by count, so 0x7E inside a payload is data and no COBS framing is needed.
| TYPE | Direction | Message |
|---|---|---|
0x01 | Pi to Shrike | MotorSetpoint: sequence, signed left and right duty in per-mille (not clamped by the codec) |
0x02 | Pi to Shrike | Estop: advisory soft e-stop |
0x03 | Pi to Shrike | HeartbeatToShrike |
0x81 | Shrike to Pi | Sensor: ultrasonic echo in microseconds, e-stop line state, flags |
0x82 | Shrike to Pi | HeartbeatToPi |
The codec does not clamp duty: the kernel actuation monitor and the FPGA envelope are the clamp authorities. Unknown-type frames with a valid CRC are consumed so the stream stays aligned.
Other modules: watchdog (Shrike-side fail-safe: SafeStop on link silence, e-stop latch that dominates a simultaneous fresh setpoint, anti-replay by sequence number, cold-start SafeStop; setpoints, e-stop frames and heartbeats refresh liveness), session (Pi-side liveness and heartbeat scheduling), ring (fixed byte ring buffer) and motor (per-mille to PWM and direction math; v0.4 is forward-only on the ARM-A path, reverse is deferred).
Kernel transport on the Pi 5
control_link.rs runs the transport in a dedicated kernel thread (spawn(), called from main after init) so RX side effects (IIO dispatch, e-stop) and motor TX happen in thread context where taking the BPF manager and apply locks is safe. The timer IRQ does nothing for the link.
| Parameter | Value |
|---|---|
| UART | RP1 UART0, GPIO14/15, 115200 baud, Pl011 driver, polled |
| RX ring | 256 bytes; per-poll caps of 64 bytes RX and 64 bytes TX |
| Inbound-silence timeout | 80 ms (kept below the 100 ms RP2040 link timeout noted in the source) |
| Heartbeat period | 20 ms (50 Hz) while alive |
| Motor channels | Actuation chip 0, channel 1 = left, channel 2 = right; other channels keep local RP1 PWM |
| Failure behavior | Fail-closed: link replaces local PWM for mapped channels; a dead link or full TX ring refuses the actuation |
Inbound Sensor frames feed the IIO hook and the hardware e-stop path; outbound motor commands are the monitor-clamped values. Public helpers are link_alive, rx_overflow_count, send_motor, command_estop and send. Side effects run after the link lock is released to avoid nesting with the BPF manager or apply lock.
The control-link HIL checks listed in ADR-0003 (control-link, watchdog and fail-safe evidence on physical hardware) are not recorded as completed results in the repository.