Skip to Content
Viewing release/v0.5.0-alpha.3 docs — not the stable branch. Changes vs main · Go to main

Drivers and Control Link

Relevant source files

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.

CrateContents
kernel_deviceDevice 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_pciPciAddress (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_devfsDevFs filesystem implementing the kernel_vfs FileSystem trait; DevFile nodes Null, Zero, Serial and BlockDeviceFile; ArcLockedDevFs wrapper

Driver availability by target

Driverx86_64aarch64 virtaarch64 rpi5
PCI bus scan and driver registryYesNo (module gated to x86_64)No
VirtIO blockYes, over PCIYes, over MMIONo
VirtIO GPUYes, over PCI (draws a test pattern)NoNo
Embedded RAM diskNoNoYes (root filesystem)
IIO managerYesYesYes
Serial consoleCOM1 16550PL011PL011 UART10
GPIO, PWM, control linkNoNoYes (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 Specific driver wins over a Generic one.
  • Two matches of the same type is an error: the device is skipped and an “ambiguous PCI driver match” error is logged.
  • A failing init is logged and does not stop the scan.

Registered drivers:

DriverMatchBehavior
virtio-blkvendor 0x1af4, device 0x1000 to 0x103f, subsystem 0x02Builds a VirtIO block device using the virtio_drivers crate and registers it as a block device
virtio-gpuvendor 0x1af4, device 0x1050Sets 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 UART10 UART global, with IRQs masked around the lock if they were enabled.
  • aarch64 virt: the PL011 at 0x0900_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.

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.

DirectoryRole
firmware/shrike/controlPlatform-independent no_std control and motor state machines (shrike_control)
firmware/shrike/rp2040Physical RP2040 firmware and target config (shrike_rp2040)
firmware/shrike/simulationMock-HAL host simulation and state-machine tests (shrike_rp2040_host_sim)
firmware/shrike/fpgashrike_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.

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_HI

CRC16-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.

TYPEDirectionMessage
0x01Pi to ShrikeMotorSetpoint: sequence, signed left and right duty in per-mille (not clamped by the codec)
0x02Pi to ShrikeEstop: advisory soft e-stop
0x03Pi to ShrikeHeartbeatToShrike
0x81Shrike to PiSensor: ultrasonic echo in microseconds, e-stop line state, flags
0x82Shrike to PiHeartbeatToPi

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.

ParameterValue
UARTRP1 UART0, GPIO14/15, 115200 baud, Pl011 driver, polled
RX ring256 bytes; per-poll caps of 64 bytes RX and 64 bytes TX
Inbound-silence timeout80 ms (kept below the 100 ms RP2040 link timeout noted in the source)
Heartbeat period20 ms (50 Hz) while alive
Motor channelsActuation chip 0, channel 1 = left, channel 2 = right; other channels keep local RP1 PWM
Failure behaviorFail-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.

Last updated on