RP1 Peripherals
Relevant source files
- kernel/src/arch/aarch64/platform/rpi5/gpio.rs
- kernel/src/arch/aarch64/platform/rpi5/pwm.rs
- kernel/src/arch/aarch64/platform/rpi5/uart.rs
- kernel/src/arch/aarch64/platform/rpi5/pl011.rs
- kernel/src/arch/aarch64/platform/rpi5/rp1_irq.rs
- kernel/src/arch/aarch64/platform/rpi5/mmio.rs
- kernel/src/arch/aarch64/platform/rpi5/memory_map.rs
- kernel/src/arch/aarch64/interrupts.rs
- kernel/src/syscall/bpf.rs
- kernel/src/actuation.rs
- kernel/src/bench.rs
RP1 is the Raspberry Pi 5’s southbridge. It is a separate chip reached from the BCM2712 over PCIe (the PCIe2 link), and it holds the 40-pin-header peripherals. axiomOS has no general PCIe or device-tree-driven driver model for it: the kernel uses fixed physical addresses inside a window the firmware has already set up (see Raspberry Pi 5 for the config.txt shortcuts).
Register access
mmio.rs provides MmioReg<T>, a volatile register wrapper with read, write, modify, set_bits, clear_bits, is_set, wait_set and wait_clear (the waits spin).
RP1 registers are addressed as RP1_PERIPHERAL_BASE + offset, where the base is the higher-half alias of physical 0x1F_0000_0000:
| Block | RP1 offset | Status |
|---|---|---|
| UART0 (PL011-compatible) | 0x0003_0000 | Used by the Shrike control link |
| UART1 | 0x0003_4000 | Address defined; no driver found |
| I2C0 | 0x0007_0000 | Address defined; no driver found |
| SPI0 | 0x0005_0000 | Address defined; no driver found |
| PWM0 | 0x0009_8000 | Driver |
| PWM1 | 0x0009_C000 | Driver |
| IO_BANK0 (GPIO) | 0x000D_0000 | Driver |
| PADS_BANK0 | 0x000F_0000 | Driver (pad control) |
| PCIe endpoint APB config | 0x0010_8000 | Used by the MSI-X route |
| Clocks | 0x0001_8000 | PWM0 clock only |
Register layouts in the drivers are cross-checked in comments against the Linux sources pinctrl-rp1.c, pwm-rp1.c and clk-rp1.c. RP1 exposes atomic set (+0x2000) and clear (+0x3000) aliases for ordinary peripheral registers; the driver uses them for the PCIe interrupt-enable mask. The RP1 PCIe config block is the exception, using +0x800 and +0xC00.
GPIO
gpio.rs implements Rp1Gpio for the 28 user GPIOs (NUM_PINS = 28; out-of-range pins assert).
| Feature | API |
|---|---|
| Function select | set_function with GpioFunction (Alt0 to Alt4, Gpio = 5, Alt6 to Alt8, Null = 31); get_function |
| Output | set_high, set_low, toggle, configure_output, force_output_override |
| Input | read, configure_input |
| Pulls | set_pull with GpioPull, programmed through PADS_BANK0 (schmitt, pull select, input enable, output disable bits) |
| Edge events | enable_interrupt(pin, rising, falling), disable_interrupt, clear_interrupt, get_pending_events, has_rising_event, has_falling_event |
| Diagnostics | pad_readback, ctrl_readback, interrupt_state (full readback of status, control, pad, raw interrupts, PCIe enable and status) |
| UART pinmux | setup_uart sets GPIO14 and GPIO15 to Alt4 |
Level-triggered event enable bits are defined but marked unused; the code arms rising and falling edges only.
GPIO interrupt route
RP1 interrupts reach the CPU as PCIe MSI-X messages, so a pin interrupt crosses several stages. rp1_irq.rs documents and programs the route for RP1 vector 0 (IO_BANK0):
initialize_gpio_route validates every stage with readbacks and returns a typed error for each failure mode: PCIe link down, root-bus readback, endpoint unavailable (expects vendor/device 0x0001_1DE4 and chip id 0x2000_1927), missing or invalid MSI-X capability, invalid BAR layout, inbound-window, MIP, MSI-X table and vector readbacks. It repoints the RP1 BARs itself (BAR1 peripherals at PCIe 0, BAR2 SRAM at 4 MiB, BAR0 MSI-X table at 8 MiB, a 9 MiB outbound window), quiesces leftover firmware state, and enables MSI-X globally only after everything downstream is installed.
On the GIC side, interrupts::init unconditionally (on rpi5) marks ID 160 edge-triggered, enables it at priority 0x80, and dispatches it to gpio::handle_interrupt. That handler reads the pending PCIe interrupt bitmap, scans pending pins, reads events, invokes attached BPF programs with a timestamp from the generic counter, clears events, and acknowledges the RP1 vector.
In the source reviewed, init_pcie_interrupt_route (and enable_pwm0_clock, below) are called from kernel/src/bench.rs, which is compiled only with the bench feature. sys_bpf GPIO attach calls configure_input and enable_interrupt for the pin, but no call to the route initializer appears on that path, and no other call site was found. Whether a non-bench image establishes the MSI-X route some other way was not confirmed. The GPIO interrupt HIL evidence required by ADR-0003 is also a pending checklist item, not a recorded result.
How GPIO is used by the rest of the kernel
sys_bpfattach for GPIO edges: after the BPF manager records the route, the pin is configured as input and its edge interrupt enabled.- BPF helpers and
actuation::guard_gpiowrite pins throughRp1Gpio, with the actuation monitor deciding first. bench.rsuses GPIO23 as a sensor input and GPIO24 as an e-stop input (see Raspberry Pi 5).
PWM
pwm.rs drives RP1’s own PWM block, which the file states does not use the BCM2835 register layout. Each of PWM0 and PWM1 has a GLOBAL_CTRL register (bit N enables channel N, bit 31 latches) and four channels at a 16-byte stride with CHAN_CTRL, RANGE, PHASE and DUTY.
- Channel numbering is 1-based in the public API to match the actuation monitor: channels 1 to 4 map to hardware channels 0 to 3. GPIO12 (Alt0) is PWM0 hardware channel 0, so monitor channel 1.
- API:
init(disables all channels),enable,disable,set_range,set_data,set_frequency,set_duty_cycle(percent),debug_regs. - Channel default:
CHAN_CTRL = 0x101(trailing-edge mark/space plus FIFO-pop mask), matching the Linux driver. - Clock: the PWM counter has a functional clock separate from the APB bus, and firmware leaves it gated.
enable_pwm0_clockprograms the divider, selects the parent through the AUX source mux and sets the enable bit so the crystal oscillator feeds PWM0 divide-by-one. Per the code comments, an earlier approach that skipped selecting the aux source left the counter frozen. - Assumed rate:
RP1_PWM_CLOCK_HZ = 50_000_000is an assumption used to turn a requested frequency intoRANGE. A source comment says to calibrate it against a scope before quoting carrier frequencies, and the runbook asks for the physical period to be measured separately. - Motor ownership: actuation channels 1 and 2 on chip 0 are owned by the signed Shrike motor link in normal builds (
control_link::motor_side), so local RP1 PWM is not what drives the wheels on those channels. See Drivers.
UARTs
| Role | Hardware | Driver |
|---|---|---|
| Kernel console | BCM2712 PL011 UART10 at 0x10_7D00_1000 (not RP1) | uart.rs, no-op init relying on firmware; blocking and non-blocking TX and RX |
| Shrike control link | RP1 UART0 on GPIO14/15 | pl011.rs, real init at 115200 8N1 with a 48 MHz UART clock default, polled. On the branch, cold init rejects invalid baud, RX errors and busy/non-empty hardware within a fixed register-access bound, and reads distinguish corrupt or lost bytes from idle |
On the branch, pl011.rs also implements a bounded local reset/quiescence path used before managed operation and during rearm: disable TX, wait one 8N1 character time from the configured divisor, flush TX, drain retained RX (at most 64 bytes per pass), and enable only RX until at least 200 ms of silence plus the counter quantum (from CNTFRQ). The whole attempt is bounded at one second. RX activity restarts the quiet interval; RX faults, non-empty TX, clock reversal or deadline expiry fail the attempt. This follows the PL011 reprogramming sequence in Arm DDI0183G section 3.3.8. Local quiet never enables the transmitter by itself and is not bilateral session evidence. The debug console driver (uart.rs) gains try_getc_checked, a nonblocking receive that reports framing, parity, break and overrun errors for the managed installer.
The control-link PL011 driver masks all interrupts; enable_rx_irq exists but the comments state IRQ-driven RX is deferred because it needs RP1 peripheral interrupt demultiplexing (issue 65). The rpi5/mod.rs module also creates UART, PWM0 and PWM1 as lazily initialized mutex-guarded globals.
What is missing
The README states there are no I2C, SPI, CAN or Ethernet drivers, and only GPIO, PWM and UART exist on RP1. The source agrees:
| Peripheral | State |
|---|---|
| I2C, SPI | Only base offset constants (RP1_I2C0_OFFSET, RP1_SPI0_OFFSET); no driver. The IIO module’s header says it will interface with I2C/SPI drivers in future |
| CAN, Ethernet | No driver |
| UART1 | Address constant only |
| RP1 interrupt demux for peripherals other than IO_BANK0 vector 0 | Not implemented (IRQ-driven UART RX deferred) |
| General PCIe enumeration | Not implemented; RP1 is configured with fixed constants, relying on pciex4_reset=0 |
| DMA, USB, SD/MMC, ADC | No driver found |
Issue references from the README for the missing drivers include 63 and 64 (and others on the roadmap, issue 81).