Building an Out-of-Tree Linux Kernel Module in Nix

Published by Philipp Schuster on

Update 2024-04-10: Updated with my latest knowledge about this topic.

In this blog post, I’m going to show you briefly how you can compile an out-of-tree Linux kernel module in Nix. I use regular Nix for that and no Nix flakes. However, with flakes, the approach is similar.

Background Knowledge

An out-of-tree Linux kernel module is a standalone piece of code outside the Linux kernel source repository. They are used when people don’t want to, are not allowed to, or simply can’t upstream drivers. Background information on how to build them can be found on kernel.org. Basically, an out-of-tree module consists of a project directory with the C source and header files, a specifically crafted Makefile following certain conventions to forward work to the main Makefile of the Linux source tree, and optionally a KBuild file. The latter is not needed for this small driver.

Nix is a functional package manager suitable for reproducible and stable build environments.

Pre-Requisite: Packaged Linux Kernel in Nix

At first, we need a Nix-packaged kernel to get access to a Linux kernel Nix derivation, as it provides relevant Nix-packaged Linux infrastructure to build (out of tree) modules. One can either use a pre-packaged kernel with the NixOS default Linux configuration (for example pkgs.linux_6_8 as is) or a Kernel build from a custom config (using pkgs.linuxKernel.manualConfig). Just make sure that the kernel was build with CONFIG_MODULES=y.

Linux Driver

The driver used in this minimal example is written in C and might look like this:

foo.c

// basic definitions for kernel module development
#include <linux/module.h>

// Module/Driver description.
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name <foo@bar.com>");
MODULE_DESCRIPTION("Description Foo Bar");

static int __init foobar_init(void) {
    int rc;
    pr_info("Foobar Driver inserted.\n");
    return 0;
}

static void __exit foobar_exit(void) {
    pr_info("Foobar Driver unloaded.\n");
}

module_init(foobar_init);
module_exit(foobar_exit);

A corresponding minimal Makefile following the recommended guidelines may look like this:

# Out of the box, the build with this Makefile only works in FHS environments,
# such as on Ubuntu or Debian. On NixOS, you either need to open an FHS
# environment using a Nix shell or build this from a specially crafted Nix
# derivation.
#
# This file follows the conventions written down here:
# https://docs.kernel.org/kbuild/modules.html

# Make it possible to override the kernel src tree location from Nix derivation.
KERNEL ?= $(shell uname -r)
KERNELDIR ?= /lib/modules/$(KERNEL)/build/

.PHONY: default
default: modules

# -m: Build as module.
obj-m = foo.o

.PHONY: modules
modules:
	@#"M=": Module source. Special variable of the kernel's main Makefile.
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

.PHONY: modules_install
modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install

Packaging the Out-of-Tree Driver in a Nix derivation

However, we can’t use this Makefile as is in a Nix derivation (or on NixOS in general), as /lib/modules/$(shell uname -r)/ depends on system state in a FHS, which is not available in a Nix derivation. Instead, we must pass special flags to make that match the expectations of the kernels build system. This might look like this:

build_kmod.nix

# Derivation for the out-of-tree build of the Linux driver.

{ lib
, stdenv

, kernel  # The Linux kernel Nix package for which this module will be compiled.
}:

stdenv.mkDerivation {
  pname = "foo-linux-driver";
  version = "0.0.0-dev";

  src = ../src;

  nativeBuildInputs = kernel.moduleBuildDependencies;

  makeFlags = kernel.makeFlags ++ [
    # Variable refers to the local Makefile.
    "KERNELDIR=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
    # Variable of the Linux src tree's main Makefile.
    "INSTALL_MOD_PATH=$(out)"
  ];

  buildFlags = [ "modules" ];
  installTargets = [ "modules_install" ];
}

We basically reuse the existing Makefile, but we pass the right Linux source (which is inside the Nix store) to it.

Summary

The typical approach to build an out-of-tree Linux module doesn’t work without further setup or configuration on NixOS/in Nix derivations. However, as you can see, the necessary additions are manageable. And of course, Nix is awesome for many reasons, so the whole effort is worth it.

A fully working example can be found on my GitHub.


Philipp Schuster

Hi, I'm Philipp and interested in Computer Science. I especially like low level development, making ugly things nice, and de-mystify "low level magic".

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *