Skip to Content
axiomOSmainPlatformDrivers and Control Link

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. In bench builds, once the bench is active, output goes to a deferred buffer instead (bench_buffer.rs): 16384 bytes, 1024-byte records, drained at most 64 bytes per timer tick with non-blocking sends. Overflow produces a PI5_BENCH_LOG_LOSS marker, and any loss invalidates serial-derived evidence.
  • 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) log and call actuation::watchdog_estop_trigger, which latches the actuation monitor, applies safe-drive values to the outputs, notifies the control link to assert a soft e-stop, and prints a V04_ESTOP ... source=watchdog record. 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_motor, guard_motor_pair, 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: “RP2040 safety/control sidecar and simulation.” The hardware profile is Shrike-lite V1.0/R0.4, whose electrical sources are hash-pinned under vendor/, together with an upstream ForgeFPGA scaffold. So it is a board pairing an RP2040 microcontroller with a ForgeFPGA, not an FPGA soft core running axiomOS. Firmware lives outside the kernel workspace and is built and versioned separately.

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 project
firmware/shrike/vendor, recoveryPinned vendor sources and recovery material

The README’s R0.4 pin plan reserves RP2040 GPIO0 to 3 for FPGA SPI, GPIO12/13 for FPGA power and enable, GPIO14/15 for the post-configuration FPGA link and GPIO4 for the LED, with provisional assignments for the e-stop observe line (GPIO5), motor direction (GPIO6 to 9), ultrasonic (GPIO10/11) and the Pi UART (GPIO16/17).

The Shrike README states that the current RP2040 firmware “deliberately remains safe-low and non-operational” because no validated bitstream/timing manifest or atomic UART/watchdog/e-stop runtime adapter exists, and that a fpga-runtime feature is a compile error until those exist. The FPGA safety gate is described as a reference whose 50 MHz clock and 20 kHz carrier defaults “require measurement and motor-driver validation on assembled hardware”, and the runbook says the RTL gate proves only the truth table, not a synthesized bitstream, pin constraints, routing or electrical timing. Both RP2040 firmware and the Pi 5 are tiered “Supported after HIL” in ADR-0003, which means that HIL evidence is still required.

Final PWM authority

Per the FPGA README, the FPGA generates the motor PWM from accepted signed commands and ignores the retained *_pwm_in pins. The physical e-stop is an active-low latching line routed directly to the FPGA and must not be generated by RP2040 firmware. A single invalid command disables both motors; e-stop assertion forces both outputs low combinationally; deassertion goes through a two-stage synchronizer and stays disarmed until a fresh, complete motor transaction arrives. The runbook’s HIL step probes both outputs around a plus or minus 800 per-mille limit.

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 command silence, e-stop latch that dominates a simultaneous fresh setpoint, anti-replay by sequence number, cold-start SafeStop; heartbeats cannot restore motor authority), session (Pi-side liveness and heartbeat scheduling), tx (transmit state with motor-command priority, replacement and expiry), 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; send of the safe motor pair is available

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_pair, send_safe_motor_pair, command_estop and send. Side effects run after the link lock is released to avoid nesting with the BPF manager or apply lock.

The runbook’s section 4.3 is the verification plan: flash only the reviewed RP2040 binary, capture startup, framing, watchdog/fail-safe, invalid-frame and loss-of-link behavior with motors disconnected, and confirm that e-stop and out-of-range commands yield a motor-disabled state before any powered test. The repository documents these as pending checklist items, not as completed results.

Last updated on