1. Introduction
Heterogeneous compute is no longer a datacenter novelty. Every modern SoC — from a RISC-V-based edge camera to a multi-tile AI accelerator card — pairs a general-purpose CPU cluster with one or more special-purpose compute engines: NPUs, DSPs, cryptographic offload blocks, or vector coprocessors. The kernel’s job is to expose these engines to userspace safely, cheaply, and predictably, without becoming the bottleneck it’s meant to eliminate.
This lesson builds a real memory-mapped accelerator driver for RISC-V in Rust, using the Linux kernel’s Rust support (rust-for-linux, upstream since 6.1) as the vehicle, and explains every layer the driver touches: the syscall interface, VFS, the platform driver model, MMIO, DMA/IOMMU, interrupt handling, and RISC-V’s own privilege and trap architecture. By the end you will understand not just how to write the driver, but why the kernel forces you into this particular shape.
We use RISC-V deliberately. Its open ISA, explicit privilege levels (M/S/U mode), and PLIC/CLINT interrupt model make the hardware-to-kernel boundary far more legible than x86, where decades of microcode and firmware hide the mechanics. RISC-V is also where most new Rust-in-kernel accelerator work is landing, because vendors shipping novel compute silicon increasingly prefer Rust’s memory-safety guarantees for driver code that talks directly to untrusted hardware registers.
2. Historical Background
Linux driver development has always trailed hardware innovation. Early accelerators (3D graphics, then GPGPU, then TPUs/NPUs) were bolted on through whichever subsystem was closest at hand — framebuffer drivers, DRM, or raw /dev/mem hacks. The DRM subsystem eventually became the de-facto home for compute accelerators because it already solved memory management (GEM/TTM), fencing, and userspace submission queues, even for devices that render nothing.
Rust’s entry into the kernel (accepted into mainline in v6.1, October 2022) was driven by exactly this class of problem: driver code is where the kernel’s worst memory-safety bugs concentrate, because drivers touch attacker- or hardware-controlled memory directly. Google, Microsoft’s Azure Silicon, and Asahi Linux’s GPU work were early, visible proof points. RISC-V support for Rust-in-kernel matured alongside rust-for-linux‘s core abstractions (kernel::platform, kernel::io_mem, kernel::irq) landing between kernel 6.6 and 6.9.
The historical arc matters because it explains today’s constraint: Rust kernel drivers are not “Rust with kernel headers.” They are Rust code written against a deliberately narrow, hand-audited kernel crate that wraps the unsafe C APIs once, centrally, so individual drivers never need unsafe for routine operations.
3. Systems-Level Problem
An accelerator on a RISC-V SoC typically presents itself as:
A block of MMIO registers (control, status, doorbell, descriptor-ring base address).
One or more interrupt lines routed through the PLIC (Platform-Level Interrupt Controller).
A DMA-capable memory path, possibly behind an IOMMU, for descriptor rings and payload buffers.
A discoverability mechanism — usually a Devicetree node (
compatible = "vendor,accel-v1") since RISC-V SoCs rarely use ACPI outside server-class parts.
The driver must solve five problems simultaneously:
Discovery — bind to the correct hardware instance via the platform bus.
Resource mapping — translate a physical MMIO range into a safe kernel virtual address without violating cacheability or ordering rules.
Synchronization — coordinate userspace submission with hardware completion via interrupts, without busy-polling the CPU into uselessness.
Memory safety across a hardware boundary — the compiler cannot verify that a register write has the side effect you think it does; Rust’s type system can only prevent software-side misuse (aliasing, use-after-free, data races between kernel threads).
Failure isolation — a stuck or misbehaving accelerator must not be able to crash the kernel, corrupt unrelated memory, or hang unrelated processes.


