From f9c9c9254dc0b15abe960b08a9a3c9e27024d530 Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 15 Nov 2024 16:43:02 +0000 Subject: [PATCH] blog: nixos-kernel-hacking: finish the rest of the article --- .../DRAFT-2024-11-15-nixos-kernel-hacking.md | 168 +++++++++++++++++- 1 file changed, 163 insertions(+), 5 deletions(-) diff --git a/content/blog/DRAFT-2024-11-15-nixos-kernel-hacking.md b/content/blog/DRAFT-2024-11-15-nixos-kernel-hacking.md index 4fc6939..0dc0904 100644 --- a/content/blog/DRAFT-2024-11-15-nixos-kernel-hacking.md +++ b/content/blog/DRAFT-2024-11-15-nixos-kernel-hacking.md @@ -101,7 +101,7 @@ this patch can alternately be represented as a [Device Tree Overlay] (DTO): // instruct the system to only apply this overlay // to PinePhone Pro, and not any other devices. / { - compatible = "pine64,pinephone-pro"; + compatible = "pine64,pinephone-pro"; }; // address the parent node we want to patch, @@ -109,7 +109,7 @@ this patch can alternately be represented as a [Device Tree Overlay] (DTO): // this overrides `press-threshold-microvolt` // while leaving all other properties unchanged. &{/adc-keys/button-down} { - press-threshold-microvolt = <400000>; + press-threshold-microvolt = <400000>; }; ``` @@ -155,15 +155,173 @@ then, we can ship new kernel modules by: ### Building a Kernel Module -TODO +my kernel module has 3 files, which live in my nix repo inline: +- [rk818_battery.c](https://git.uninsane.org/colin/nix-files/src/commit/4e008c34208143a489d824b288437201f7137ace/pkgs/linux-packages/rk818-charger/rk818_battery.c) +- [rk818_battery.h](https://git.uninsane.org/colin/nix-files/src/commit/4e008c34208143a489d824b288437201f7137ace/pkgs/linux-packages/rk818-charger/rk818_battery.h) +- [rk818_charger.c](https://git.uninsane.org/colin/nix-files/src/commit/4e008c34208143a489d824b288437201f7137ace/pkgs/linux-packages/rk818-charger/rk818_charger.c) + +these are compiled into `rk818_battery.ko` and `rk818_charger.ko` by a Makefile: + +```make +obj-m := rk818_battery.o rk818_charger.o + +all: + $(MAKE) -C "$(KERNEL_DIR)" M="$(PWD)" modules +install: + install -Dm444 rk818_battery.ko $(INSTALL_MOD_PATH)/drivers/power/supply/rk818_battery.ko + install -Dm444 rk818_charger.ko $(INSTALL_MOD_PATH)/drivers/power/supply/rk818_charger.ko +``` + +this can be packaged for nix as so: + +```nix +# file: pkgs/linux-packages/rk818-charger/default.nix +{ + buildPackages, + kernel, + lib, + stdenv, +}: stdenv.mkDerivation { + pname = "rk818-charger"; + version = "0-unstable-2024-10-01"; + + src = ./.; + + hardeningDisable = [ "pic" ]; + nativeBuildInputs = kernel.moduleBuildDependencies; + + makeFlags = [ + "KERNEL_DIR=${kernel.dev}/lib/modules/${kernel.modDirVersion}/build" + "INSTALL_MOD_PATH=$(out)/lib/modules/${kernel.modDirVersion}/kernel" + # from pkgs/os-specific/linux/kernel/manual-config.nix: + "O=$(buildRoot)" + "CC=${stdenv.cc}/bin/${stdenv.cc.targetPrefix}cc" + "HOSTCC=${buildPackages.stdenv.cc}/bin/${buildPackages.stdenv.cc.targetPrefix}cc" + "HOSTLD=${buildPackages.stdenv.cc.bintools}/bin/${buildPackages.stdenv.cc.targetPrefix}ld" + "ARCH=${stdenv.hostPlatform.linuxArch}" + ] ++ lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform) [ + "CROSS_COMPILE=${stdenv.cc.targetPrefix}" + ]; + + # NixOS kernel expects compressed kernel modules, so do that here. + postInstall = '' + find $out -name '*.ko' -exec xz {} \; + ''; +} +``` + +then add this package to the `linuxKernel` package set: + +```nix +{ ... }: +{ + nixpkgs.overlays = [(self: super: { + linuxKernel = super.linuxKernel // { + packagesFor = kernel: + ( + super.linuxKernel.packagesFor kernel + ).extend (kFinal: kPrev: with kFinal; { + rk818-charger = callPackage ./pkgs/linux-packages/rk818-charger { }; + }); + }; + })]; +} +``` + +kernel modules must be built against the same kernel version +that they'll be loaded by: this `linuxKernel` package set looks +funny at first, but it exists to make that easier. + +your kernel module can be built like this: +- `nix-build -A linuxPackages_6_11.rk818-charger` +- `nix-build -A linuxPackages_6_10.rk818-charger` + +and so on, depending on which kernel you're running. ### Deploying a Kernel Module -TODO +add the following to your NixOS config: + +```nix +{ config, ... }: +{ + # this builds our modules against the specific kernel in use by the host + # and makes them available under /run/current-system/kernel-modules to tools like `modprobe` + boot.extraModulePackages = [ + config.boot.kernelPackages.rk818-charger + ]; + + # explicitly load the modules during stage-2 boot. + # if they're needed earlier in the boot process, use `boot.initrd.kernelModules` instead. + boot.kernelModules = [ + "rk818_battery" + "rk818_charger" + ]; +} +``` + +now we've recreated the same behavior as if the kernel module had been shipped in-tree, +but we still have to associate the driver with a specific device if we want it to do anything. ### Device Tree + Module Integration -TODO +add the following Device Tree Overlay to `hardware.deviceTree.overlays`, +as we did earlier with the battery DTO: + +```dts +/dts-v1/; +/plugin/; + +// apply the overlay only to PinePhone Pro; no other devices. +/ { + compatible = "pine64,pinephone-pro"; +}; + +&{/} { + bat: battery { + compatible = "simple-battery"; + voltage-min-design-microvolt = <3400000>; + voltage-max-design-microvolt = <4350000>; + }; +}; + +&rk818 { + battery { + // this line associates this device with the rk818-battery module + // we just shipped + compatible = "rockchip,rk818-battery"; + // constants were taken from the pine64-org kernel tree: + ocv_table = <3400 3675 3689 3716 3740 3756 3768 3780 + 3793 3807 3827 3853 3896 3937 3974 4007 4066 + 4110 4161 4217 4308>; + design_capacity = <2916>; + design_qmax = <2708>; + bat_res = <150>; + max_input_current = <3000>; + max_chrg_current = <2000>; + max_chrg_voltage = <4350>; + sleep_enter_current = <300>; + sleep_exit_current = <300>; + power_off_thresd = <3400>; + zero_algorithm_vol = <3950>; + fb_temperature = <105>; + sample_res = <10>; + max_soc_offset = <60>; + energy_mode = <0>; + monitor_sec = <5>; + virtual_power = <0>; + power_dc2otg = <0>; + otg5v_suspend_enable = <1>; + }; + + charger { + // this line associates this device with the rk818-charger module + // we just shipped + compatible = "rockchip,rk818-charger"; + monitored-battery = <&bat>; + }; +}; +``` ## Patching an Upstream Kernel Module