Patching an in-tree Linux kernel module

I’ve written a patch for an in-tree kernel module (r8169). I got it working by patching the kernel:

boot.kernelPatches = [{
  name = "r8169";
  patch = ./r8169.patch;
}];

But this requires rebuilding the whole kernel. How can I build just the r8169 module with a patched source? I’m hoping I can then include it in boot.extraModulePackages and skip the long kernel build times.

I ended up doing it like this:

realtek.nix:

{ stdenv, lib, kernel }:

let
  modPath = "drivers/net/ethernet/realtek";
  modDestDir = "$out/lib/modules/${kernel.modDirVersion}/kernel/${modPath}";

in stdenv.mkDerivation rec {
  name = "realtek-${kernel.version}";

  inherit (kernel) src version;

  postPatch = ''
    cd ${modPath}
  '';

  nativeBuildInputs = kernel.moduleBuildDependencies;

  makeFlags = kernel.makeFlags ++ [
    "-C ${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
    "M=$(PWD)"
    "modules"
  ];

  enableParallelBuilding = true;

  installPhase = ''
    runHook preInstall

    mkdir -p ${modDestDir}
    find . -name '*.ko' -exec cp --parents '{}' ${modDestDir} \;
    find ${modDestDir} -name '*.ko' -exec xz -f '{}' \;

    runHook postInstall
  '';

  meta = with lib; {
    description = "Realtek ethernet drivers";
    inherit (kernel.meta) license platforms;
  };
}

default.nix:

{ config, lib, pkgs, ... }:

let
  realtek-kernel-module = pkgs.callPackage ./realtek.nix { inherit (config.boot.kernelPackages) kernel; };
  patched = realtek-kernel-module.overrideAttrs (prev: {
    patches = [ ./r8169.patch ];
  });
in
{
  boot.extraModulePackages = [
    (lib.hiPrio patched)
  ];
}

I was surprised there wasn’t a simple mkLinuxKernelModule or similar. I cobbled together the above from examples in nixpkgs.

2 Likes

The above solution no longer works for me after Move in-tree kernel modules to separate derivation output by jmbaur · Pull Request #423933 · NixOS/nixpkgs · GitHub merged. The module build fails with an error like this:

Running phase: buildPhase
@nix { "action": "setPhase", "phase": "buildPhase" }
build flags: -j12 SHELL=/nix/store/qsydfxm1vq6q9jac2kq3r8kn0xdmsldf-bash-5.3p3/bin/bash O=\$\(buildRoot\) --eval=undefine modules CC=/nix/store/68ndh04pl2hhhizsarvzwa9cnlp7zj3d-gcc-14.3.0/bin/gcc LD=/nix/store/asc30cdibhz4d82jv1bgaadfv529xqli-binutils-2.44/bin/ld AR=/nix/store/ag7fc58qzff4j1sgd7v98irhcr6vcg8v-gcc-wrapper-14.3.0/bin/ar NM=/nix/store/ag7fc58qzff4j1sgd7v98irhcr6vcg8v-gcc-wrapper-14.3.0/bin/nm STRIP=/nix/store/asc30cdibhz4d82jv1bgaadfv529xqli-binutils-2.44/bin/strip OBJCOPY=/nix/store/ag7fc58qzff4j1sgd7v98irhcr6vcg8v-gcc-wrapper-14.3.0/bin/objcopy OBJDUMP=/nix/store/ag7fc58qzff4j1sgd7v98irhcr6vcg8v-gcc-wrapper-14.3.0/bin/objdump READELF=/nix/store/ag7fc58qzff4j1sgd7v98irhcr6vcg8v-gcc-wrapper-14.3.0/bin/readelf HOSTCC=/nix/store/ag7fc58qzff4j1sgd7v98irhcr6vcg8v-gcc-wrapper-14.3.0/bin/cc HOSTCXX=/nix/store/ag7fc58qzff4j1sgd7v98irhcr6vcg8v-gcc-wrapper-14.3.0/bin/c++ HOSTAR=/nix/store/1rrjjyl7davy8znlykxzxwrsxkmxbvbq-binutils-wrapper-2.44/bin/ar HOSTLD=/nix/store/1rrjjyl7davy8znlykxzxwrsxkmxbvbq-binutils-wrapper-2.44/bin/ld ARCH=x86_64 CROSS_COMPILE= -C /nix/store/n350v7jbm90ns4af8fy69b8464w496bm-linux-6.12.58-dev/lib/modules/6.12.58/build M=\$\(PWD\) modules
make: Entering directory '/nix/store/n350v7jbm90ns4af8fy69b8464w496bm-linux-6.12.58-dev/lib/modules/6.12.58/build'
make: *** empty variable name.  Stop.
make: Leaving directory '/nix/store/n350v7jbm90ns4af8fy69b8464w496bm-linux-6.12.58-dev/lib/modules/6.12.58/build'

I can fix the build by undoing the makeFlags change from the PR in my kernel module derivation:

   nativeBuildInputs = kernel.moduleBuildDependencies;
 
-  makeFlags = kernel.makeFlags ++ [
+  makeFlags = (lib.filter (x: x != "--eval=undefine modules") kernel.makeFlags) ++ [
     "-C ${kernel.dev}/lib/modules/${kernel.modDirVersion}/build"
     "M=$(PWD)"
     "modules"

But I honestly have no idea what I’m doing here. So, what is the right way to patch an in-tree kernel module without rebuilding the whole kernel?

Feature request: build single in-tree Linux kernel module · Issue #458785 · NixOS/nixpkgs · GitHub is a related issue about upstreaming a snippet from the wiki.

I ended up having to remove both O=$(buildRoot) and --eval=undefine modules to get my modules to build. The error (with O=$(buildRoot) in place) was about being unable to find the file in this line of the Makefile:

include $(objtree)/include/config/auto.conf

Maybe related to this kernel commit?

This is the function I use on my systems (cobbled together from your original post and some out-of-tree modules in nixpkgs):

{
  kernelPackages,
  module,
  patches,
}:
kernelPackages.stdenv.mkDerivation {
  pname = builtins.baseNameOf module;
  inherit (kernelPackages.kernel) version src;

  inherit patches;

  nativeBuildInputs = kernelPackages.kernel.moduleBuildDependencies;

  enableParallelBuilding = true;

  makeFlags = kernelPackages.kernelModuleMakeFlags ++ [
    "INSTALL_MOD_PATH=$(out)"
    "M=$(PWD)/${module}"
  ];

  buildFlags = [ "modules" ];

  installFlags = [ "modules_install" ];
}

In particular, using kernelModuleMakeFlags does not include the problematic flags, removing the need to filter them out.

Oh, that’s much better! Thank you.

We can also do this to replace the modules from the kernel build instead of overriding them in updates/:

   makeFlags = kernelPackages.kernelModuleMakeFlags ++ [
     "INSTALL_MOD_PATH=$(out)"
+    "INSTALL_MOD_DIR=kernel/${module}"
     "M=$(PWD)/${module}"
   ];

Either way works. I prefer this way because it feels more Nix-like to produce the ultimately correct module tree, rather than including duplicates and having the system decide between them at runtime.

1 Like