Porting GPU Drivers to Rust on ARM64: The Hardest Trial for Kernel-Level Computing
Rust in the Linux kernel is gaining traction, but GPU drivers are the real test, where complex memory, synchronization, and recovery paths make C bugs costly.
Join the DZone community and get the full member experience.
Join For FreeRust Has Entered the Kernel. Now Comes the Dangerous Part.
A kernel driver does not fail politely.
It does not throw a friendly exception, generate a neat stack trace, and ask whether you would like to restart. It corrupts memory, wedges hardware, leaks secrets, freezes the compositor, and leaves engineers spelunking through logs at 2 a.m. with the emotional range of a haunted printer.
Nowhere is this more obvious than in GPU drivers.
GPU drivers are some of the most complex pieces of kernel-level software in modern systems. They sit between userspace graphics APIs, memory managers, firmware, hardware queues, display engines, DMA buffers, synchronization fences, interrupts, power states, and error recovery paths. They are not “just drivers.” They are operating systems within the operating system.
That is why Rust in GPU drivers matters.
Rust support exists in the Linux kernel documentation today, but the kernel documentation is careful about its scope: Rust support is still primarily aimed at kernel developers and maintainers building abstractions, drivers, infrastructure, and tooling. It is not a blanket promise that every Rust kernel module is production-ready everywhere.
That caution is healthy. Kernel engineering is allergic to magic, and rightly so. Rust does not sprinkle safety dust on MMIO registers. It does not fix firmware bugs. It does not turn a bad architecture into a good one.
But Rust does attack one of the oldest and most expensive problems in systems software: memory unsafety.
And if Rust can survive in GPU drivers on ARM64, it can survive almost anywhere.
Why GPU Drivers Are the Real Test
Most Rust-in-kernel discussions start too gently.
They talk about simple drivers, toy modules, or safe wrappers around existing C APIs. Useful, yes. Convincing, not enough.
The real test is graphics.
A modern GPU driver must handle:
- Device probing and initialization - Firmware loading and communication - Command submission queues - Shared memory between userspace, kernel, and device - DMA buffer ownership - Synchronization fences - Interrupt handling - Runtime power management - GPU reset and recovery - Userspace ABI compatibility - Performance under real graphical workloads
This is where C’s sharp edges show up in full costume. A buffer may outlive the object that owns it, a command queue may still point to freed memory, or firmware may enter a state it should never reach. Reset and teardown paths add more chances for things to go wrong, especially if userspace still holds handles, an interrupt arrives at the wrong moment, or a fence is never signaled.
These are not rare problems. They are normal driver engineering problems.
The security motivation is also real. Google reported that memory-safety vulnerabilities accounted for 76% of Android vulnerabilities in 2019 and 24% in 2024 after shifting new development toward memory-safe languages. Google later reported major reductions in memory-safety vulnerability density for Rust code compared with Android’s C and C++ code, along with lower rollback rates and less code-review time for Rust changes.
Do not overread that. Android is not the Linux DRM subsystem. A phone platform is not a GPU kernel driver. But the broader lesson is hard to ignore: when memory-safety bugs dominate the risk profile, changing the language of new code can change the shape of future vulnerability data.
GPU drivers are exactly the kind of high-risk subsystem where that bet deserves serious attention.
Why ARM64 Makes the Story More Important
ARM64 is no longer just “the phone architecture.” It is in laptops, cloud servers, edge systems, automotive platforms, developer boards, AI devices, and embedded systems. On many ARM64 systems, the GPU is not a discrete PCIe card sitting at a safe distance. It is part of a tightly integrated SoC, sharing memory, power constraints, thermal limits, and firmware relationships with the rest of the system.
That changes the stakes.
A GPU driver bug on an ARM64 SoC can affect:
- System memory safety - Display stability - Battery life - Thermal behavior - Compositor responsiveness - Application latency - AI and graphics workloads sharing the same memory fabric
Rust support for AArch64 entered the Linux kernel development story as part of the broader Rust-for-Linux effort, and Linux kernel documentation now includes Rust materials for kernel developers working on Rust abstractions and drivers.
That makes ARM64 GPU work more than a curiosity. It is a practical proving ground for the next decade of heterogeneous computing.
The future machine is not CPU-only. It is CPU plus GPU plus NPU plus DSP plus video accelerator plus firmware-controlled subsystems. The kernel increasingly becomes an orchestration layer for compute fabrics. That means more shared memory, more queues, more firmware protocols, and more places for C lifetime bugs to hide like raccoons in ductwork.
The Serious Case Study: Tyr for Arm Mali
The strongest ARM64 GPU example today is Tyr, a Rust-based DRM driver for CSF-based Arm Mali GPUs.
Tyr is especially interesting because it is not a random greenfield fantasy. It is a Rust port of Panthor, the C driver for the same class of hardware. Tyr is being developed as a joint effort involving Collabora, Arm, and Google engineers, and it aims to implement the same userspace API as Panthor for compatibility, so it can eventually be used as a drop-in replacement by PanVK, the Vulkan driver.
That one design choice is the difference between serious engineering and conference glitter.
Tyr is not trying to rewrite the entire graphics stack at once. It is trying to preserve the userspace contract while changing the kernel implementation language. That is exactly how infrastructure migration should be done.
Change one major variable. Measure the result. Then decide.
The target is also meaningful. CSF-based Arm Mali GPUs use a command-stream frontend where the driver must coordinate with firmware and hardware scheduling mechanisms. That naturally creates state-machine-heavy code, shared buffers, queues, and lifetime-sensitive resource handling.
In other words: the kind of code where Rust’s ownership model is not academic. It is directly relevant.
The Other Serious Case Study: Nova for NVIDIA GSP GPUs
The second important example is Nova, a Rust-based driver for NVIDIA GPUs that use the GPU System Processor, or GSP.
Nova is intended to become the successor to Nouveau for GSP-based NVIDIA GPUs in Linux and targets NVIDIA GPUs beginning with the GeForce RTX 20-series Turing family and newer.
Nova is not primarily an ARM64 story, but it matters because it shows Rust entering serious DRM and GPU-driver territory, not just small demo modules. Together, Tyr and Nova point toward the same pattern: Rust is being explored where GPU drivers interact with firmware protocols, memory objects, queues, and kernel graphics APIs.
This is the important architectural shift.
As GPU firmware takes on more low-level responsibilities, host drivers often become protocol coordinators. They manage firmware boot, message queues, device objects, error states, recovery paths, memory handles, and userspace interfaces.
That is a very Rust-shaped problem.
Not because Rust is trendy. Trendy is how JavaScript frameworks reproduce.
Rust is relevant because protocol state, resource ownership, and invalid transitions can often be modeled explicitly in the type system.
The Core Engineering Idea: Make Illegal States Hard to Represent
Here is the difference between a shallow Rust port and a serious one.
A shallow port translates C into Rust line by line and celebrates because the file extension changed.
A serious port rethinks dangerous state transitions.
GPU drivers are full of implicit states:
Buffer: Allocated -> Mapped -> Submitted -> Retired -> Freed Queue: Created -> Active -> Hung -> Recovering -> Destroyed Firmware: Absent -> Loaded -> Booting -> Running -> Failed Device: Probed -> Initialized -> Suspended -> Resuming -> Resetting -> Removed
In C, these states are often spread across flags, pointers, locks, comments, and prayers. In Rust, they can be modeled more directly:
enum BufferState {
Allocated,
Mapped,
Submitted,
Retired,
}
struct GpuBuffer<S> {
handle: BufferHandle,
size: usize,
state: S,
}
struct Allocated;
struct Mapped;
struct Submitted;
struct Retired;
impl GpuBuffer<Allocated> {
fn map(self) -> Result<GpuBuffer<Mapped>, DriverError> {
// Map buffer into GPU-visible address space.
Ok(GpuBuffer {
handle: self.handle,
size: self.size,
state: Mapped,
})
}
}
impl GpuBuffer<Mapped> {
fn submit(self, queue: &mut CommandQueue) -> Result<GpuBuffer<Submitted>, DriverError> {
queue.push(self.handle)?;
Ok(GpuBuffer {
handle: self.handle,
size: self.size,
state: Submitted,
})
}
}
This is simplified, but the principle is powerful: make the dangerous lifecycle visible in the type system.
In C, the rule might live in a comment:
/* Do not free this buffer after submission until the fence signals. */
That comment is useful until someone edits a cleanup path six months later and accidentally turns it into historical fiction.
Rust lets engineers encode more of that rule into APIs. It does not remove the need for review. It makes review more focused.
Unsafe Rust Is Not a Loophole. It Is the Blast Radius.
Kernel Rust still needs unsafe.
Anyone claiming otherwise should be escorted away from the whiteboard.
Drivers touch hardware. They read and write MMIO registers. They interact with C APIs. They manage DMA. They cross boundaries where the compiler cannot verify everything.
The right goal is not “no unsafe code.” The right goal is small, explicit, audited unsafe code.
Example:
struct RegisterBlock {
base: *mut u32,
}
impl RegisterBlock {
unsafe fn read_raw(&self, offset: usize) -> u32 {
core::ptr::read_volatile(self.base.add(offset))
}
fn read_status(&self) -> DeviceStatus {
let raw = unsafe { self.read_raw(STATUS_REGISTER_OFFSET) };
DeviceStatus::from_bits(raw)
}
}
The outer driver should not scatter volatile pointer arithmetic everywhere. It should interact with typed operations such as:
read_status() submit_queue() reset_engine() map_buffer() signal_fence()
This is the real win: concentrate unsafety behind abstractions whose invariants can be documented, reviewed, and tested.
Diffuse unsafety is archaeology. Concentrated unsafety is engineering.
Research around Rust safety continues to focus on the fact that unsafe Rust and linked unsafe libraries can still compromise memory safety if not isolated or analyzed properly. That is directly relevant to kernel work. Rust is not a force field. It is a tool for shrinking the zone where humans must be perfect.
Humans are bad at being perfect. That is why we invented compilers.
What a Real Porting Plan Looks Like
A credible GPU subsystem port should not start with “rewrite the driver.”
That sentence is how you summon budget demons.
A better plan looks like this:
Phase 1: Choose a narrow subsystem
Start where Rust gives clear leverage:
- Buffer lifetime tracking - Command submission validation - Firmware message queues - Fence ownership - Reset and recovery state machines
Do not begin with the entire DRM subsystem. That is not bravery. That is poor impulse control.
Phase 2: Preserve the userspace API
Tyr’s compatibility goal with Panthor’s userspace API is exactly the right instinct. If the userspace API remains stable, the migration can focus on kernel-internal safety and maintainability rather than forcing the whole graphics stack to change at once.
Stable outside. Safer inside.
That is the migration pattern.
Phase 3: Wrap unsafe boundaries
Every unsafe block should answer three questions:
1. What invariant must be true before this code runs? 2. Who guarantees that invariant? 3. How do we test that the invariant remains true?
If the answer is “trust me,” the code is not ready. Trust is not a test strategy.
Phase 4: Measure performance with real workloads
A Rust GPU driver that is safer but introduces unacceptable frame-time spikes will not survive. Kernel developers care about safety, but they also care about latency, throughput, and not humiliating themselves in front of perf.
A real benchmark plan should include:
- Frame-time mean, p95, and p99 - Command submission latency - CPU cycles during graphics workloads - Context switches - Interrupt rate - GPU reset recovery time - Firmware boot time - Memory bandwidth - Power draw under sustained load - Thermal throttling behavior
A basic harness might look like this:
#!/usr/bin/env bashset -euo pipefailDRIVER="${1:?usage: ./bench.sh <driver-name>}"FRAMES="${2:-600}"OUT="results-${DRIVER}-$(date +%Y%m%d-%H%M%S)"mkdir -p "${OUT}"echo "Driver: ${DRIVER}" | tee "${OUT}/metadata.txt"uname -a | tee -a "${OUT}/metadata.txt"lscpu | tee "${OUT}/cpu.txt"sudo dmesg -Cperf stat -d \-o "${OUT}/perf.txt" \-- ./gpu_workload_runner \--driver "${DRIVER}" \--frames "${FRAMES}" \--json "${OUT}/frames.json"dmesg > "${OUT}/dmesg.log"echo "Benchmark complete: ${OUT}"
That is not enough for a final paper-quality result, but it is the start of an honest engineering conversation. One run is a screenshot. Ten controlled runs are evidence. A bar chart with no methodology is decorative nonsense.
The ARM64 Benchmark Matrix
For an ARM64-focused evaluation, use a matrix like this:
Hardware:- Rockchip RK3588 board, such as Rock 5B- Stable power supply- Active cooling- Fixed CPU governor- Fixed GPU governor, where available Software:- Same kernel baseline for C and Rust driver tests- Same Mesa version - Same compositor setting- Same Vulkan or OpenGL workload- Same thermal constraints Workloads:- Synthetic command submission stress test- Vulkan sample workload- Mesa or IGT graphics tests- Real application trace- Forced GPU reset and recovery test Metrics:- Mean frame time- p95 frame time- p99 frame time- CPU cycles- Context switches- Interrupts- GPU resets- Kernel warnings- Power draw
This is where many articles fail. They show a chart but hide the setup. That is amateur hour.
For DZone, the article can be powerful even without original benchmark results if it clearly presents the benchmark plan. But to become exceptional, it needs real numbers from a reproducible setup.
No fake numbers. No “up to 5x faster” nonsense unless measured. The internet already has enough performance astrology.
What Rust Will Not Fix
Rust can reduce some classes of bugs, but it does not solve the hard parts of driver development by itself. It cannot compensate for poor hardware documentation, opaque firmware behavior, bad scheduling decisions, or flawed abstractions, and it does not make DRM any less complex. Low-level drivers will still require unsafe code, and deadlocks, design mistakes, and logic errors remain very much on the table.
The Linux kernel documentation itself remains cautious: Rust support is still aimed at developers and maintainers working on abstractions, drivers, infrastructure, and tools, and it notes that Rust support is still under development, especially for certain configurations.
That caution should be repeated, not buried. The correct argument is not:
Rust makes kernel drivers safe.
The correct argument is:
Rust can reduce specific classes of memory and lifetime bugs in new kernel driver code, especially when unsafe hardware access is isolated behind reviewed abstractions.
That is less flashy. It is also true. True wins.
Why This Matters Beyond GPUs
GPU drivers are a proxy for where kernel-level computing is headed.
Modern systems are becoming accelerator orchestras. The CPU no longer owns the whole performance story. Work moves across GPUs, NPUs, DSPs, video encoders, SmartNICs, security processors, and firmware-managed islands.
That means kernel software must manage:
- Shared memory across devices- Complex queue lifetimes- Cross-device synchronization- Firmware protocols- Userspace-visible handles- Device reset semantics- Security boundaries around accelerators
If Rust helps in GPU drivers, it can help in other accelerator drivers too.
That includes:
- AI accelerator drivers- Media encode and decode engines- Camera pipelines- SmartNIC offload paths- Storage acceleration- Embedded display controllers
The real innovation is not “Rust replaces C.” That is a bumper sticker. The real innovation is selective memory-safe kernel development for high-risk hardware boundaries. That is a more mature thesis, and it is the one engineering leaders should care about.
The Takeaway
For application developers, this matters because kernel reliability eventually becomes application reliability. A browser tab, a video call, a game engine, a dashboard, a vision model, or an edge AI pipeline can all be ruined by a GPU stack that mishandles memory or fails recovery.
For systems developers, this matters because Rust offers a practical way to encode ownership and state transitions that C leaves to discipline and code review.
For engineering leaders, this matters because memory-safety work is not just a security initiative. It is a maintenance initiative. Safer new code can reduce future review burden, rollback risk, and vulnerability exposure, as Google’s Android reporting suggests.
For kernel maintainers, this matters because the only acceptable Rust adoption path is incremental, measurable, and compatible with existing kernel development culture. The path is not revolution. It is disciplined infiltration.
A Practical Checklist for Teams
Before starting a Rust GPU driver effort, answer these questions:
1. What exact bug class are we trying to reduce?2. Which subsystem has the worst lifetime complexity?3. Can we preserve the userspace API?4. Where must unsafe code exist?5. Can unsafe code be isolated behind reviewed abstractions?6. What real workload will prove performance?7. What metric would make us abandon or redesign the port?8. Who will maintain the Rust abstractions after the prototype hype fades?
That last question is brutal and necessary. A prototype is easy. Maintenance is the boss fight.
Conclusion: Rust Must Earn Its Place Where the Bugs Are Worst
Rust in the Linux kernel should not be judged by toy modules. It should be judged where kernel bugs are expensive: drivers, firmware interfaces, DMA, shared memory, synchronization, and recovery paths.
That is why GPU drivers on ARM64 are such an important proving ground. They combine modern hardware complexity with exactly the memory and lifecycle hazards Rust was designed to reduce.
Tyr shows the most direct ARM Mali path: a Rust DRM driver for CSF-based Arm Mali GPUs, designed as a port of the C Panthor driver while aiming for userspace API compatibility. Nova shows that Rust GPU driver work is also moving into NVIDIA GSP territory, with ambitions to succeed Nouveau for supported modern NVIDIA GPUs.
The lesson is not that Rust is perfect.
The lesson is that the next generation of kernel-level computing is becoming too heterogeneous, too concurrent, and too security-sensitive to keep writing every new dangerous subsystem in C by default.
Rust will not replace engineering discipline. It will punish the lack of it earlier. And in kernel development, earlier is everything.
The Buffer That Came Back From the Dead
The board had been running for nine hours. Same workload. Same scene. Same cursed GPU path that used to crash whenever the moon was wrong and the scheduler sneezed. In the old driver, the bug never arrived on command. It preferred drama. Sometimes frame 417. Sometimes frame 12,004. Sometimes only when the engineer walked away, because bugs respect neither science nor lunch.
The Rust port failed too, at first. But it failed differently.
Not with a corrupted pointer three layers below the crime scene. Not with a dead queue holding a ghost reference to a buffer that should have been buried. It failed at compile time, loudly, rudely, and before anyone had to read 900 lines of logs with the dead-eyed stare of a person reconsidering their career.
The compiler pointed at the illegal transition. The engineer stared at it. The GPU kept rendering. For once, the monster had left footprints.
Opinions expressed by DZone contributors are their own.
Comments