# Why I run a self-built Linux kernel on my daily driver

> Building, packaging, and daily-driving a custom audio-tuned Linux kernel — XanMod base, hand-curated patch stack, ISA-targeted, packaged the proper Debian way — and the audio-stack work that makes it worth the trouble.

By Jeff Mixon, published 2025-11-12. Canonical: https://www.jeffmixon.com/post/self-built-audio-kernel/

## Why bother

`apt install linux-image-amd64` is fine. It will be fine for most people
forever. I do not run it on my workstation. The reason is small and concrete:
the audio hardware on modern x86 laptops and motherboards is rapidly enough
moving that the kernel my distribution ships when I install it is regularly
*not* the kernel that knows how to drive the audio paths I actually have. By
the time the fix reaches a stable release and is backported into the
distribution kernel, six months have passed and I have not had a working
microphone the whole time.

So I build my own. Not a once-a-decade adventure — the everyday kernel I
boot into. Currently `linux-image-6.18.34-x64v3-xanmod1-audio`, with
matching `linux-headers` and `linux-libc-dev` packages installed side by
side. The build flow is the part worth talking about; the audio-stack work
the build flow exists to enable is the more interesting part.

## The build flow

The starting point is a cloned [XanMod](https://xanmod.org/) tree — a
maintained patch stack atop mainline that gives me lower-latency scheduler
defaults and a sane base for desktop workloads. On top of that I lay the
audio patches I care about — codec quirks, smart-amp driver fixes, ALSA UCM2
profiles, ACPI subsystem-ID matchings — that have not yet landed upstream or
have not yet propagated to a release.

A few choices that aren't accidental:

- **A hand-crafted `EXTRAVERSION` and a distinct release codename.** The
  installed package name encodes the patch stack (`xanmod1-audio`), and
  `uname -r` tells me at a glance what is actually running. Kernel debugging
  is hard enough without ambiguity about which kernel you are debugging.
- **`x64v3` ISA targeting.** Compile for the actual instruction set on the
  machine and let the optimizer use it. The portability cost is zero — this
  kernel only ever runs on this hardware.
- **Full Debian packaging.** Not an out-of-tree blob, not a manually-copied
  `bzImage`. A proper `.deb` with a real `changelog`, `control`, `files`,
  `rules`, and substvars — produced by the standard build flow,
  installable and removable through `dpkg` like anything else. The
  `linux-headers` and `linux-libc-dev` packages are built and installed
  alongside the image so anything else on the system that wants to compile
  against this kernel (out-of-tree Wi-Fi drivers, virtualbox modules, the
  V4L2 loopback module) finds the headers where it expects them.

I keep multiple trees checked out — 6.8, 6.11, the Rockchip 6.6 vendor tree
for SBC work, the XanMod tree for the daily driver — so when something
breaks I can bisect across the relevant one without reconstructing the
environment from scratch.

## What the audio patches actually are

This is the half that justifies the build pipeline. Modern x86 audio is no
longer a single chip with a single driver — it is a stack:

- **Sound Open Firmware (SOF)** is the firmware running on the audio DSP,
  with its own topology files describing pipelines, mixers, and endpoints.
- **ALSA Use Case Manager (UCM2)** profiles tell userspace how to route
  audio for a given hardware configuration (which jack is the headset, what
  to do when the speakers are routed through a separate amplifier, etc).
- **HDA codec quirks** match specific machine models to specific pin
  configurations and verb sequences so the codec actually wakes up correctly.
- **I²C smart-amp drivers** like `snd_soc_aw88399` drive standalone
  amplifier chips that sit between the DSP and the speakers, with their own
  firmware and their own calibration.
- **DMI/ACPI subsystem-ID quirks** are the glue: how does the kernel know
  *which* machine it is on, and therefore which of the above pieces apply?

A new laptop landing without working audio is, almost always, one or more of
these layers missing a quirk or a topology entry for that specific
machine's identifier. The fix is the same shape every time — find the
identifier, write the quirk, route it through the right subsystem — and
upstreaming it is the actual contribution; running the kernel that includes
it is just a daily-driver detail.

The reason I run the kernel I built rather than waiting is that *I am the
person closing the loop*. I write the quirk, build the kernel with the
quirk, boot it, confirm audio works, submit the patch upstream, and keep
running the local build until the upstream patch lands in a release. Without
the build pipeline that whole loop is gated on someone else's release
cadence.

## Userspace audio too

The kernel work has a userspace mirror: a locally-built
[`qpwgraph`](https://github.com/rncbc/qpwgraph) for visual PipeWire graph
inspection (Qt6 / C++20 / CMake) and a tracked PipeWire configuration the
patches were tuned for. The point is the same: if you are debugging audio
plumbing, you want the graph tool that matches the graph you are debugging,
not whatever version your distribution shipped six months ago.

## Closer

The whole flow — kernel patch → personal kernel package → daily driver
→ upstream patch — is mundane in any given iteration. The compounding effect
is that "audio doesn't work on Linux on my hardware" is a problem I have
been *causally responsible for solving on my own machine* for years, and
the same skills make the upstream-contribution side cheap. There is no
mystery to the Linux audio stack; there is just a stack of layers each of
which rewards someone willing to read its actual source.
