// HACKER NEWS — CYBERSECURITY
Building a Linux GPU Driver for the M4 Mac Mini in One Month
Previous blog post: https://codyho.dev/blog/hypervisor-macbook-neo/
TL;DR: Niklas and I built a fully OpenGL ES 3.0 compliant GPU driver for the
M4 Mac Mini and MacBook Neo in about a month, a process which normally takes
years. Here is Chrome and Firefox running WebGL on the M4 Mac Mini with working
compositing:
Most importantly, the driver is fast enough to run Minecraft at 200fps:
Building this driver involved reverse engineering the AGX’s (Apple’s name for
the GPU) incredibly complicated firmware ABI and user-space components. This
was all done in a transparent, verifiably clean room manner using well
established techniques. The code is not yet ready for end users, but we are
looking to get it to end users as soon as possible.
Previously, I built a hypervisor to reverse engineer macOS. Now the goal became
to actually do something useful with it, and what better target than writing a
GPU driver. The GPU is effectively a requirement for any modern system,
otherwise everything needs to be CPU rendered which is orders of magnitude
slower and less power efficient. Our goal was to implement conformant OpenGL
(and soon, Vulkan) drivers for the M4 Mac Mini and MacBook Neo.
Normally, building a GPU driver is an endeavor that takes years; our goal was
to do it in days. It turns out that days was overly optimistic, but weeks is
still a massive improvement. In those weeks we have:
Throughout this process, we have not looked at any Apple binaries, only
hardware traces (from our hypervisor) and shaders we built ourselves. For
user-space graphics RE, we were careful to treat any required Apple blobs as
opaque objects. We had a friend write documentation on these blobs 1 so we
could write a clean room implementation ourselves (which was mostly built by
just blindly trying stuff until it worked). We have published all of our
experiments so that anyone can verify the provenance of our work (see the twin
agx-re repos under Deliverables).
This blog post is divided into two parts, user and kernel space. This mirrors
the split in all modern GPU drivers: the kernel is responsible for interfacing
with the firmware, allocating buffers, and managing scheduling, while the
actual contents of those buffers and what is being scheduled are opaque.
User space is responsible for actually understanding how the GPU works and
filling those buffers with stuff.
On Apple Silicon, the kernel driver does not interface directly with the
hardware. Instead, it talks to the GPU firmware running a custom RTOS called
RTKit. That means that the first step to a kernel driver is not talking to
hardware, it’s figuring out the firmware ABI.
The firmware ABI was by far the most annoying part of this project, because
rather than doing the sane thing of coming up with a reasonable ABI with nice
interfaces, Apple essentially took a regular kernel driver, cut it in half, and
then put half of it in the AGX and called it firmware, with the other half of
the kernel driver communicating using shared structs in memory. Many of these
structs have firmware owned fields (which we must never modify and which we
must learn from reverse engineering) interleaved with host controlled fields.
For an idea of how complicated the ABI is, this is what the shared memory
tree looks like on the M1/M2: