Deborah Brouwer
October 21, 2026
Reading time:
Since Tyr was introduced last year as a mainline user of Rust in the Linux Kernel, members across the development community have worked patiently and persistently to build Tyr's functional capacity. At this year's XDC in Toronto, I explained how upstream Tyr recently achieved the milestone of booting the GPU's Microcontroller Unit (MCU). I outlined the main Rust abstractions Tyr relied on to reach this milestone and offered some perspective on Rust development in the kernel. In case you missed the talk, this blog post is for you.
Tyr is a Linux kernel driver for the Arm Mali-G610 GPU. This model uses Valhall architecture v10 and provides a Command Stream Frontend (CSF) interface for communication with the kernel driver. There is already an upstream driver for this GPU model: Panthor, written in C. The purpose of Tyr is to port Panthor into Rust in a manner that is completely transparent to userspace. The great advantage of this approach is that we already know how to interact with the hardware, so we can focus our efforts entirely on the Rust implementation.
The initial stub for Tyr was merged into the kernel v6.18 in November 2025. At that point, the driver could probe and bind to the GPU, power it on, query information about the hardware, and shut it down. Now, a year later, with commits merged in v7.3, Tyr is finally able to boot the MCU to communicate with the GPU. To reach this point, all of Tyr's memory management infrastructure needed to be in place: we needed virtual memory spaces and mapping methods to make the memory accessible to the GPU. We also needed to reliably configure the GPU's memory management unit (MMU). And, of course, we wanted to use the Rust abstractions that were being added to mainline Linux. In particular, Tyr depends on three important abstractions: DRM GEM shared-memory helpers, page table management, and GPUVM, all of which are discussed below.
The MCU firmware is a binary image available from the Linux firmware repository or through distribution packages. Since Rust already provides an abstraction for the Linux firmware request API, loading the firmware into system memory is relatively straightforward. But loading firmware into system memory isn't enough to make it accessible to the GPU. Instead we need to place the firmware into special GEM buffers from the Direct Rendering Manager (DRM) memory management framework.
The original Rust GEM abstractions were written by Asahi Lina. The basic GEM implementation, without backing memory, was upstreamed in Linux v6.16 in July 2025. Then, in June 2026, Lyude Paul and Danilo Krummrich upstreamed the shared-memory helper abstractions in Linux v7.1. With these abstractions available, Tyr could finally place the MCU firmware into GEM buffers that could be mapped for GPU access. But we still weren't ready to start the MCU.
![]() |
| MCU firmware sections mapped from GEM SHMEM buffers into GPU virtual memory. |
The GPU's MMU expects to find the firmware parsed into sections and available at specific virtual memory addresses. So Tyr creates a virtual memory space based on the MMU's addressing capabilities, with enough space for all the firmware sections. Next, we need to map the physical memory backing our GEM buffers to the appropriate GPU virtual addresses. And, for this, Tyr uses the Rust page table abstraction.
The Rust page table abstraction was originally written by Asahi Lina and was substantially redeveloped by Alice Ryhl. It was upstreamed in April 2026 into Linux v7.0.
At this point, we had the firmware loaded into GEM buffers, a virtual address space for the GPU, and an abstraction that allows us to map the memory into that address space. So, it seems like we would have everything we need to start up the MCU, but there was an interesting complication: mapping pages through the Rust page table API is an unsafe operation.
One of the advantages of writing a kernel driver in Rust is that the compiler can prevent many kinds of undefined behavior in safe code. In the upstream Tyr driver, we currently have approximately 3,700 lines of code, of which just six lines use unsafe operations. Three of those are related to the page table abstraction.
Unsafe operations in Rust kernel code need to be accompanied by a SAFETY comment explaining why their use is sound, that is, why they will not introduce undefined behavior. SAFETY comments provide safety justifications, but there are also comments documenting safety requirements as discussed by Benno Lossin in his proposed Rust Safety Standard. A safety justification explains why calling unsafe code is sound; it documents the justification for encapsulating unsafe code so that it can be called safely by users. But a safety requirement is different: it documents the conditions a caller must meet to use the operation safely. Safety requirements appear in the function's documentation under a # Safety heading.
The page table abstraction's map_pages() function provides an example of a safety requirement:
/// # Safety
///
/// * No other io-pgtable operation may access the range `iova .. iova+pgsize*pgcount` while
/// this `map_pages` operation executes.
/// * This page table must not contain any mapping that overlaps with the mapping created by
/// this call.
/// * If this page table is live, then the caller must ensure that it's okay to access the
/// physical address being mapped for the duration in which it is mapped.
#[inline]
pub unsafe fn map_pages(
To use this function safely, Tyr must meet all of these safety requirements: ensure appropriate locking, keep the backing memory alive, and avoid mappings over existing page table entries.
Where did these requirements come from?
Essentially, they reflect assumptions made by the underlying C API. Sometimes the assumptions are clear, for example, that we need locking to avoid data races. But in other cases an interpretation of the C code is required. For example, before creating a page table entry, the C implementation checks whether a page table entry is already populated. If an existing mapping is found, the function triggers a WARN_ON (unless certain warnings have been suppressed) and returns -EEXIST.
static int __arm_lpae_map(
...
if (pte && !iopte_leaf(pte, lvl, data->iop.fmt)) {
cptep = iopte_deref(pte, data);
} else if (pte) {
/* We require an unmap first */
WARN_ON(!(cfg->quirks & IO_PGTABLE_QUIRK_NO_WARN));
return -EEXIST;
}
Since this function returns an error, this doesn't by itself demonstrate undefined behavior. The error can be handled like any other. But the warning is significant. The Linux kernel coding style explains that warnings should not be used "lightly" but rather be reserved for "unexpected, this-should-never-happen" situations. Since the C implementation treats an attempt to overwrite an existing mapping as something that should not happen, the Rust abstraction elevates this warning to a prohibition at the Rust interface level. It is an interesting example of how a WARN_ON may be interpreted as an implicit invariant that the Rust abstraction chooses to enforce explicitly. For Tyr, that leaves us with an important question: how can we guarantee that we won't map over an existing mapping? For this we need another Rust abstraction: GPUVM.
GPUVM is a memory management API from the DRM subsystem written by Danilo Krummrich in 2023. Asahi Lina wrote the original Rust abstraction for GPUVM and it was subsequently developed by Alice Ryhl. Rust GPUVM was merged into Linux v7.2 in August 2026.
GPUVM provides record-keeping for a GPU virtual address space. It keeps track of all the virtual address ranges that are mapped. Before changes are applied to the page tables, GPUVM can determine which mappings are affected and arrange the necessary unmap or remap operations. This allows Tyr to manage the virtual address space without inadvertently trying to insert a new page table mapping over an existing one.
For our MCU firmware, using GPUVM might initially seem like overkill. We know the virtual addresses of all the firmware sections in advance. We know that they don't overlap, and we don't expect to add more mappings after the firmware has been loaded. But we use the full GPU virtual memory abstraction, even in this simple case, because it provides the machinery we need to maintain the mapping invariants required by the Rust page table API. Rather than relying on our own assumptions about the layout of the firmware, we can use the GPUVM abstraction that explicitly tracks and validates mappings.
With the memory management infrastructure in place, we're finally ready to boot the MCU. Writing UPDATE to the COMMAND register activates the configured address space, then writing to the MCU_CONTROL register starts MCU execution.
Being able to boot the MCU is an important milestone for Tyr because it means much of the memory management infrastructure we need is now in place. But there's still a lot of work to do before Tyr can become a functional replacement for Panthor.
One of our biggest challenges has been developing Tyr while the Rust kernel abstractions and driver interfaces are also being developed. As the driver interfaces are naturally revised and improved, we have rewritten Tyr several times to adapt to the larger Rust ecosystem. This not only makes progress much slower than it would be with a mature API, but also stalls progress for capabilities, such as job scheduling, while the abstraction is properly designed, reviewed, tested, and merged upstream.
But working in this evolving environment is also precisely where Tyr is able to provide the most value. As a user of these new Rust abstractions, Tyr helps to expose their requirements and limitations. The problems we encounter when implementing a real GPU driver provide feedback that can improve the abstractions themselves. We're not just writing a driver using Rust for Linux. We're also helping to develop the infrastructure that future Rust kernel drivers will rely on. And as that infrastructure stabilizes, writing kernel drivers in Rust should become easier, safer, and, I think, much more attractive for developers to come.
21/10/2026
Tyr has reached a major milestone by booting the Arm Mali-G610 GPU’s MCU using upstream Rust DRM memory-management abstractions, while helping…
26/08/2026
Tyr’s latest Rust GPU kernel driver work puts GPU memory management in place, from VM binding and GPUVM to GEM-backed buffers and firmware…
07/07/2026
AI development is shifting from implementing models from scratch to composing powerful capabilities via APIs, enabling developers to integrate…
26/05/2026
New upstream BlueZ documentation helps simplify Bluetooth qualification for Linux-based products by mapping supported profiles, test requirements,…
14/05/2026
See how Tyr moves beyond MCU firmware boot to build the group, queue, VM, submission, and completion paths needed to run real Vulkan workloads…
07/05/2026
A complete breakdown of Mesa’s NIR compiler detailing how it optimizes shader memory access with SSA promotion, deref analysis, copy propagation,…