Building an Out-of-Tree Linux Kernel Module in Nix
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.
0 Comments