Linux kernel: Difference between revisions
imported>Khoi |
imported>Lelgenio Add instructions for patching In-tree kernel modules without rebuilding the entire kernel |
||
Line 451: | Line 451: | ||
</syntaxHighlight> | </syntaxHighlight> | ||
== Patching a single In-tree kernel module == | |||
If you wish to patch a single kernel module, you can avoid rebuilding the entire linux kernel | |||
by packaging that module applying patches to it. | |||
=== Packaging a single kernel module === | |||
Here is an example for how to package the <code>amdgpu</code> kernel module. | |||
<syntaxHighlight lang=nix> | |||
{ pkgs | |||
, lib | |||
, kernel ? pkgs.linuxPackages_latest.kernel | |||
}: | |||
pkgs.stdenv.mkDerivation { | |||
pname = "amdgpu-kernel-module"; | |||
inherit (kernel) src version postPatch nativeBuildInputs; | |||
kernel_dev = kernel.dev; | |||
kernelVersion = kernel.modDirVersion; | |||
modulePath = "drivers/gpu/drm/amd/amdgpu"; | |||
buildPhase = '' | |||
BUILT_KERNEL=$kernel_dev/lib/modules/$kernelVersion/build | |||
cp $BUILT_KERNEL/Module.symvers . | |||
cp $BUILT_KERNEL/.config . | |||
cp $kernel_dev/vmlinux . | |||
make "-j$NIX_BUILD_CORES" modules_prepare | |||
make "-j$NIX_BUILD_CORES" M=$modulePath modules | |||
''; | |||
installPhase = '' | |||
make \ | |||
INSTALL_MOD_PATH="$out" \ | |||
XZ="xz -T$NIX_BUILD_CORES" \ | |||
M="$modulePath" \ | |||
modules_install | |||
''; | |||
meta = { | |||
description = "AMD GPU kernel module"; | |||
license = lib.licenses.gpl3; | |||
}; | |||
} | |||
</syntaxHighlight> | |||
=== Replacing default kernel modules === | |||
After packaging the kernel module you can add it to your system just like an out-of-tree kernel module, it will replace the default module provided by the linux package because it has the same name: | |||
<syntaxHighlight lang=nix> | |||
{pkgs, config, ...}: | |||
let | |||
amdgpu-kernel-module = pkgs.callPackage ./amdgpu-kernel-module.nix { | |||
# Make sure the module targets the same kernel as your system is using. | |||
kernel = config.boot.kernelPackages.kernel; | |||
}; | |||
in | |||
{ | |||
boot.extraModulePackages = [ | |||
(amdgpu-kernel-module.overrideAttrs (_: { | |||
patches = [ ./patches/amdgpu-foo-bar.patch ]; | |||
}) | |||
]; | |||
} | |||
</syntaxHighlight> | |||
== Troubleshooting == | == Troubleshooting == |
Revision as of 18:45, 20 August 2023
By default, the latest LTS linux kernel is installed (Linux Kernel Version History).
Configuration
You can choose your kernel simply by setting the boot.kernelPackages
option
For example by adding this to /etc/nixos/configuration.nix
:
boot.kernelPackages = pkgs.linuxPackages_latest;
And rebuild your system and reboot to use your new kernel:
$ sudo nixos-rebuild boot
$ sudo reboot
List available kernels
You can list available kernels using nix repl
(previously nix-repl
) by typing the package name and using the tab completion:
$ nix repl
nix-repl> :l <nixpkgs>
Added 12607 variables.
nix-repl> pkgs.linuxPackages
pkgs.linuxPackages pkgs.linuxPackages_latest
pkgs.linuxPackages-libre pkgs.linuxPackages_latest-libre
pkgs.linuxPackages-rt pkgs.linuxPackages_latest_hardened
pkgs.linuxPackages-rt_5_4 pkgs.linuxPackages_latest_xen_dom0
pkgs.linuxPackages-rt_5_6 pkgs.linuxPackages_latest_xen_dom0_hardened
pkgs.linuxPackages-rt_latest pkgs.linuxPackages_mptcp
pkgs.linuxPackagesFor pkgs.linuxPackages_rpi0
pkgs.linuxPackages_4_14 pkgs.linuxPackages_rpi1
pkgs.linuxPackages_4_19 pkgs.linuxPackages_rpi2
pkgs.linuxPackages_4_4 pkgs.linuxPackages_rpi3
pkgs.linuxPackages_4_9 pkgs.linuxPackages_rpi4
pkgs.linuxPackages_5_4 pkgs.linuxPackages_testing
pkgs.linuxPackages_5_8 pkgs.linuxPackages_testing_bcachefs
pkgs.linuxPackages_5_9 pkgs.linuxPackages_testing_hardened
pkgs.linuxPackages_custom pkgs.linuxPackages_xen_dom0
pkgs.linuxPackages_custom_tinyconfig_kernel pkgs.linuxPackages_xen_dom0_hardened
pkgs.linuxPackages_hardened pkgs.linuxPackages_zen
pkgs.linuxPackages_hardkernel_4_14
pkgs.linuxPackages_hardkernel_latest
Custom kernel modules
Note that if you deviate from the default kernel version, you should also take extra care that extra kernel modules must match the same version. The safest way to do this is to use config.boot.kernelPackages
to select the correct module set:
{ config, ... }:
{
boot.extraModulePackages = with config.boot.kernelPackages; [ wireguard ];
}
Note that wireguard has been merged into the mainline kernel, so on newer (21.05+) versions of NixOS with the default kernel wireguard is no longer an option available.
Custom kernel commandline
The config attribute boot.kernelParams
can be set to supply the Linux kernel with additional command line arguments at boot time.
{ pkgs, config, ... }:
{
boot.kernelParams = [ /* list of command line arguments */ ];
}
Custom configuration
It is sometimes desirable to change the configuration of your kernel, while keeping the kernel version itself managed through Nixpkgs. To do so, you can add the configuration to a dummy boot.kernelPatches
,[1][2] which will then be merged and applied to the current kernel. As with kernel configuration with NixOS, drop the CONFIG_ prefix from the kernel configuration names.
This example is from the boot.crashDump.enable
option:
{
boot.kernelPatches = [ {
name = "crashdump-config";
patch = null;
extraConfig = ''
CRASH_DUMP y
DEBUG_INFO y
PROC_VMCORE y
LOCKUP_DETECTOR y
HARDLOCKUP_DETECTOR y
'';
} ];
}
Another way to build a kernel with custom configuration is to create a overlay and use either extraConfig or structuredExtraConfig. This allows for more flexibility, since you can basically override any kernel option available here [3]. Using structuredExtraConfig is recommended since there is some sanity check if the options you're passing to your kernel is correct. Also, setting ignoreConfigErrors is useful to avoid error: unused option during build.
{
nixpkgs = {
overlays = [
(self: super: {
linuxZenWMuQSS = pkgs.linuxPackagesFor (pkgs.linux_zen.override {
structuredExtraConfig = with lib.kernel; {
SCHED_MUQSS = yes;
};
ignoreConfigErrors = true;
});
})
];
};
}
Pinning a kernel version
boot.kernelPackages = pkgs.linuxPackagesFor (pkgs.linux_4_19.override {
argsOverride = rec {
src = pkgs.fetchurl {
url = "mirror://kernel/linux/kernel/v4.x/linux-${version}.tar.xz";
sha256 = "0ibayrvrnw2lw7si78vdqnr20mm1d3z0g6a0ykndvgn5vdax5x9a";
};
version = "4.19.60";
modDirVersion = "4.19.60";
};
});
Debugging a failed configuration
As dependencies between kernel configurations items need to be addressed manually, use --keep-failed
to inspect the intermediate nix config file after for instance the error
note: keeping build directory '/tmp/nix-build-linux-config-4.19.0-mptcp_v0.94.1.drv-0'
by opening /tmp/nix-build-linux-config-4.19.0-mptcp_v0.94.1.drv-0/.attr-0
.
This section details the on how to get an environment that can be used to configure and cross-compile the kernel for embedded Linux development. The kernel source won't come from nix unpack phase but rather gotten externally(such as from the chip vendor github). So first clone the kernel source, then use the shell.nix below to set up the dev environment(example below is for aarch64):
let
pkgs = import <nixpkgs> { };
in
(pkgs.buildFHSUserEnv {
name = "kernel-build-env";
targetPkgs = pkgs: (with pkgs;
[
pkgconfig
ncurses
qt5.qtbase
# new gcc usually causes issues with building kernel so use an old one
pkgsCross.aarch64-multiplatform.gcc8Stdenv.cc
(hiPrio gcc8)
]
++ pkgs.linux.nativeBuildInputs);
runScript = pkgs.writeScript "init.sh" ''
export ARCH=arm64
export hardeningDisable=all
export CROSS_COMPILE=aarch64-unknown-linux-gnu-
export PKG_CONFIG_PATH="${pkgs.ncurses.dev}/lib/pkgconfig:${pkgs.qt5.qtbase.dev}/lib/pkgconfig"
export QT_QPA_PLATFORM_PLUGIN_PATH="${pkgs.qt5.qtbase.bin}/lib/qt-${pkgs.qt5.qtbase.version}/plugins"
export QT_QPA_PLATFORMTHEME=qt5ct
exec bash
'';
}).env
Finally, cd to the kernel source dir and do development normally.
It is (currently) not possible to run make menuconfig
in the checked out linux kernel sources. This is because ncurses
is not part of your working environment when you start it with nix-shell '<nixpkgs>' -A linuxPackages.kernel
.
This nix-shell hack adds ncurses as a build dependency to the kernel:
$ nix-shell -E 'with import <nixpkgs> {}; linux.overrideAttrs (o: {nativeBuildInputs=o.nativeBuildInputs ++ [ pkg-config ncurses ];})'
[nix-shell] $ unpackPhase && cd linux-*
[nix-shell] $ patchPhase
[nix-shell] $ make menuconfig
(thanks to sphalerite)
make xconfig
Similarly to make menuconfig, you need to import qt in the environment:
$ nix-shell -E 'with import <nixpkgs> {}; linux.overrideAttrs (o: {nativeBuildInputs=o.nativeBuildInputs ++ [ pkg-config qt5.qtbase ];})'
If the source was unpacked and an initial config exists, you can run make xconfig KCONFIG_CONFIG=build/.config
Requesting a change in the default nixos kernel configuration
Please provide a comparison with other distributions' kernel: - arch: https://github.com/archlinux/svntogit-packages/blob/packages/linux/trunk/config - debian: https://salsa.debian.org/kernel-team/linux/blob/master/debian/config/config and the ARCH specific ones
Booting a kernel from a custom source
The following example shows how to configure NixOS to compile and boot a kernel from a custom source, and with custom configuration options.
{ pkgs, ... }:
{
boot.kernelPackages = let
linux_sgx_pkg = { fetchurl, buildLinux, ... } @ args:
buildLinux (args // rec {
version = "5.4.0-rc3";
modDirVersion = version;
src = fetchurl {
url = "https://github.com/jsakkine-intel/linux-sgx/archive/v23.tar.gz";
sha256 = "11rwlwv7s071ia889dk1dgrxprxiwgi7djhg47vi56dj81jgib20";
};
kernelPatches = [];
extraConfig = ''
INTEL_SGX y
'';
extraMeta.branch = "5.4";
} // (args.argsOverride or {}));
linux_sgx = pkgs.callPackage linux_sgx_pkg{};
in
pkgs.recurseIntoAttrs (pkgs.linuxPackagesFor linux_sgx);
}
Out-of-tree kernel modules
Packaging out-of-tree kernel modules
There are a couple of steps that you will most likely need to do a couple of things. Here is an annotated example:
{ stdenv, lib, fetchFromGitHub, kernel, kmod }:
stdenv.mkDerivation rec {
name = "v4l2loopback-dc-${version}-${kernel.version}";
version = "1.6";
src = fetchFromGitHub {
owner = "aramg";
repo = "droidcam";
rev = "v${version}";
sha256 = "1d9qpnmqa3pfwsrpjnxdz76ipk4w37bbxyrazchh4vslnfc886fx";
};
sourceRoot = "source/linux/v4l2loopback";
hardeningDisable = [ "pic" "format" ]; # 1
nativeBuildInputs = kernel.moduleBuildDependencies; # 2
makeFlags = [
"KERNELRELEASE=${kernel.modDirVersion}" # 3
"KERNEL_DIR=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build" # 4
"INSTALL_MOD_PATH=$(out)" # 5
];
meta = with lib; {
description = "A kernel module to create V4L2 loopback devices";
homepage = "https://github.com/aramg/droidcam";
license = licenses.gpl2;
maintainers = [ maintainers.makefu ];
platforms = platforms.linux;
};
}
1. For kernel modules it is necessary to disable pic
in compiler hardenings as the kernel need different compiler flags.
2. In addition to other dependencies in nativeBuildInputs
you should include kernel.moduleBuildDependencies
as this propagates additional libraries required during the build.
3. Some kernel modules try guess the kernel version based on the running kernel via uname
. Usually they save this information in a makefile variable like KERNELRELEASE
. If this is the case you can override the kernel version via makeFlags
. The right kernel version string can be found in kernel.modDirVersion
.
4. You need to find out how the build environment (Makefile in general) finds the kernel tree. This is sometimes KDIR
and sometimes KERNEL_DIR
.
5. Lastly it is required to give the kernel build system the right location where to install the kernel module. This is done by setting INSTALL_MOD_PATH
to $out
Otherwise an error like mkdir: cannot create directory '/lib': Permission denied
is generated.
You can then call your program using something like let yourprogram = config.boot.kernelPackages.callPackage ./your-derivation.nix {}; in …
(or if you want to compile it for the default kernel used by nix you can use pkgs.linuxPackages.callPackage
but be aware that if you install it on a system running another kernel it will not work) and install the module in the kernel using boot.extraModulePackages = [ yourprogram ];
.
Developing out-of-tree kernel modules
See also: NixOS Manual, 12.2. Developing kernel modules
If you work on an out-of-tree kernel module the workflow could look as follow:
#include <linux/module.h>
#define MODULE_NAME "hello"
static int __init hello_init(void)
{
printk(KERN_INFO "hello world!\n");
return 0;
}
static void __exit hello_cleanup(void) {
printk(KERN_INFO "bye world!\n");
}
module_init(hello_init);
module_exit(hello_cleanup);
obj-m += hello.o
$ nix-shell '<nixpkgs>' -A linux.dev
$ make -C $(nix-build -E '(import <nixpkgs> {}).linux.dev' --no-out-link)/lib/modules/*/build M=$(pwd) modules
$ insmod ./hello.ko
$ dmesg | grep hello
[ 82.027229] hello world!
If wishing to develop out-of-tree kernel modules for kernels aside from the default variant shipped under pkgs.linuxPackages
, a flow similar to the example below will be used.
The example below illustrates a flow for building kernel modules for pkgs.linuxPackages_latest
.
$ nix-shell '<nixpkgs>' -A linuxPackages_latest.kernel.dev
$ make -C $(nix-build -E '(import <nixpkgs> {}).linuxPackages_latest.kernel.dev' --no-out-link)/lib/modules/*/build M=$(pwd) modules
$ insmod ./hello.ko
$ dmesg | grep hello
[ 82.027229] hello world!
Loading out-of-tree kernel modules
As far as I understand, if you developed a kernel module, you should end up with having some .ko
files inside a subfolder inside $out/lib/modules/${kernel.modDirVersion}
. Now, if you want to make your module loadable inside the kernel by modprobe
, you should do:
boot.extraModulePackages = [ yourmodulename ];
Then, the user can load it using:
$ sudo modprobe yourmodulename
or unload it using
$ sudo modprobe -r yourmodulename
However, if you want to autoload your module at startup in stage 2, you need to do:
boot.kernelModules = [ "yourmodulename" ];
and the module will be automatically loaded after a reboot. If you want instead to load it at stage 1 (before the root is even mounted), you need to add it to boot.initrd.availableKernelModules
and boot.initrd.kernelModules
.
Note that if you don't reboot, you can still load manually the module using modprobe yourmodulename>
, and to automatically enable a module during configuration switch/reboot, you can put modprobe yourmodulename || true
inside the script of a systemctl service (it is for example what does wireguard).
Finally, if you want to define some options by default (used when you load manually a module using modprobe
, or when the system boots), you can specify them in:
boot.extraModprobeConfig = ''
options yourmodulename optionA=valueA optionB=valueB
'';
If you have developed a Nix package for your module, and want to only add the module to your configuration.nix
instead of complete rebuilding the system based on your local nixpkgs
, you need to update boot.kernelPackages
as well, so kernel and modules can match each other:
{
boot = {
kernelPackages = pkgs.wip.linuxPackages;
extraModulePackages = with config.boot.kernelPackages; [ yourmodulename ];
}
nixpkgs.config = {
# Allow unfree modules
allowUnfree = true;
packageOverrides = pkgs: {
wip = import (fetchGit { url = "/home/user/nixpkgs"; shallow = true;}) {
config = config.nixpkgs.config;
};
};
};
};
Cross-compiling Linux from source
Save the following as shell.nix
for your nix-shell:
{ pkgs ? import <nixpkgs> {} }:
let
# more platforms are defined here: https://github.com/NixOS/nixpkgs/blob/master/lib/systems/examples.nix
aarch64 = pkgs.pkgsCross.aarch64-multiplatform;
in
aarch64.linux.overrideAttrs (old: {
# the override is optional if you need for example more build dependencies
nativeBuildInputs = old.nativeBuildInputs ++ [ pkgs.gllvm ];
}
Than you can run the following commands (in the nix-shell $makeFlags
will contain
the necessary cross-compiling arguments)
$ nix-shell
# to configure
$ nix-shell> make $makeFlags defconfig -j $(nproc)
# to build
$ nix-shell> make $makeFlags -j $(nproc)
Compiling Linux with clang
Save this as shell.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.linux.override {
# needs to be clang11Stdenv or newer
stdenv = clang11Stdenv;
}
Than you can use:
$ nix-shell
# to configure
$ nix-shell> make $makeFlags defconfig -j $(nproc)
# to build
$ nix-shell> make $makeFlags -j $(nproc)
Overriding kernel packages
In order to override linuxPackages
, use the extend
attribute. Example:
linuxPackages.extend (self: super: {
xpadneo = super.xpadneo.overrideAttrs (o: rec {
src = pkgs.fetchFromGitHub {
# Custom override goes here.
};
});
});
Here is a fully worked example to enable debugging for the zfsUnstable
module:
nixpkgs.overlays = [
(self: super: {
linuxPackages = super.linuxPackages.extend (lpself: lpsuper: {
zfsUnstable = super.linuxPackages.zfsUnstable.overrideAttrs (oldAttrs: {
configureFlags = oldAttrs.configureFlags ++ [ "--enable-debug" ];
});
});
})
];
Patching a single In-tree kernel module
If you wish to patch a single kernel module, you can avoid rebuilding the entire linux kernel by packaging that module applying patches to it.
Packaging a single kernel module
Here is an example for how to package the amdgpu
kernel module.
{ pkgs
, lib
, kernel ? pkgs.linuxPackages_latest.kernel
}:
pkgs.stdenv.mkDerivation {
pname = "amdgpu-kernel-module";
inherit (kernel) src version postPatch nativeBuildInputs;
kernel_dev = kernel.dev;
kernelVersion = kernel.modDirVersion;
modulePath = "drivers/gpu/drm/amd/amdgpu";
buildPhase = ''
BUILT_KERNEL=$kernel_dev/lib/modules/$kernelVersion/build
cp $BUILT_KERNEL/Module.symvers .
cp $BUILT_KERNEL/.config .
cp $kernel_dev/vmlinux .
make "-j$NIX_BUILD_CORES" modules_prepare
make "-j$NIX_BUILD_CORES" M=$modulePath modules
'';
installPhase = ''
make \
INSTALL_MOD_PATH="$out" \
XZ="xz -T$NIX_BUILD_CORES" \
M="$modulePath" \
modules_install
'';
meta = {
description = "AMD GPU kernel module";
license = lib.licenses.gpl3;
};
}
Replacing default kernel modules
After packaging the kernel module you can add it to your system just like an out-of-tree kernel module, it will replace the default module provided by the linux package because it has the same name:
{pkgs, config, ...}:
let
amdgpu-kernel-module = pkgs.callPackage ./amdgpu-kernel-module.nix {
# Make sure the module targets the same kernel as your system is using.
kernel = config.boot.kernelPackages.kernel;
};
in
{
boot.extraModulePackages = [
(amdgpu-kernel-module.overrideAttrs (_: {
patches = [ ./patches/amdgpu-foo-bar.patch ];
})
];
}
Troubleshooting
Build fails
Too high ram usage
turn off `DEBUG_INFO_BTF`