Building an Out-of-Tree Linux Kernel Module in Nix
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 can’t upstream drivers. Background information on how to build them can be found on kernel.org. When a module is build, it must match a specific version of kernel source and kernel configuration.
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. For this, we can use one of the existing kernels in nixpkgs, such as pkgs.linux_6_2
. We can either use the default configuration (pkgs.linux_6_2
as is) or a custom one, as in the following example:
kernel.nix
{ lib , pkgs }: let # Function that builds a kernel from the provided Linux source with the # given config. buildKernel = selectedLinuxKernelPkg: pkgs.linuxKernel.manualConfig { inherit (pkgs) stdenv lib; src = selectedLinuxKernelPkg.src; # Add your kernel config file here. configfile = ./kernel.config; version = "${selectedLinuxKernelPkg.version}"; # Probably that's a weird nixpkgs upstream thingy. Linux 6.2 wants # "6.2.0" instead of "6.2". Hence, I add ".0" here. modDirVersion = "${selectedLinuxKernelPkg.version}.0"; allowImportFromDerivation = true; }; in buildKernel pkgs.linux_6_2
The example above expects that you know how to get a Linux kernel config. The kernel.config
file corresponds to the .config
file in a Linux source tree build.
Both attributes, pkgs.linux_6_2
and the result of pkgs.callPackage ./kernel.nix {}
do have the same structure. They only differ in their kernel configuration. This is relevant for the Nix derivation of the module following shortly.
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 Makefile may look like this:
default: driver obj-m = foo.o .PHONY: clean default driver driver: make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Packaging the Out-of-Tree Driver in Nix
However, we can’t use this Makefile as is easily in a Nix derivation (or on NixOS in general), as /lib/modules/$(shell uname -r)/
highly depends on system state which is not directly available in a Nix environment. (It would work on Debian-based systems for example, tho.) Instead, we must construct make
flags in a Nix derivation that match the expectations of the kernel build system. The kernel_module.nix
for the Kernel module then might look like this:
{ lib # The Linux kernel Nix package for which this module is compiled. # Either 'pkgs.linux_6_2` or the custom kernel created above. , kernel , stdenv }: stdenv.mkDerivation rec { pname = "foobar-driver"; version = "0.1"; src = ./.; setSourceRoot = '' export sourceRoot=$(pwd)/source ''; nativeBuildInputs = kernel.moduleBuildDependencies; makeFlags = kernel.makeFlags ++ [ "-C" "${kernel.dev}/lib/modules/${kernel.modDirVersion}/build" "M=$(sourceRoot)" ]; buildFlags = [ "modules" ]; installFlags = [ "INSTALL_MOD_PATH=${placeholder "out"}" ]; installTargets = [ "modules_install" ]; meta = with lib; { description = "A foobar Driver for Linux"; platforms = platforms.linux; }; }
I used an existing Nix-packaged kernel module as template, so thanks to the original author. Now, we only need to invoke the kernel_module.nix
with its desired parameters and in the end, we get a kernel module for the given Linux kernel in result/
.
Summary
The typical approach to build an out-of-tree Linux module doesn’t work on NixOS and in Nix derivations. Instead, we need some magic wrapping around the build. However, as you can see, the necessary clutter in the Nix file is 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