blog: nixos-kernel-hacking: proof reading

This commit is contained in:
2024-11-16 14:07:36 +00:00
parent ec5857cfe9
commit 56359d0cac

View File

@@ -5,13 +5,15 @@ description = "patching kernels and tweaking device trees *without* lengthy rebu
# to publish: uncomment the `date` field and remove the `DRAFT` prefix
+++
one of my NixOS devices is a [PinePhone Pro].
at time of writing, it does boot to a working display/touchscreen and even WiFi on stock NixOS,
one of my NixOS devices is a [PinePhone Pro][PinePhone Pro].
at time of writing, it boots to a working display/touchscreen and even WiFi on stock NixOS,
however other basic features like audio and battery readouts aren't available.
actually, these features *are* available on distros like [postmarketOS] which ship kernel forks
actually, these features *are* available on distros like [postmarketOS][postmarketOS] which ship kernel forks
specifically for this. but maintaining a fork is at least a bit of work, and NixOS gives you lots
_better_ ways to tweak your kernel than simply building the whole thing from a different source tree.
so, how best to develop and deploy kernel-level changes on a NixOS system?
## Kernel Pain Points
skip ahead if you're already familiar with the NixOS kernel options.
@@ -41,13 +43,13 @@ or if your kernel patches are simple enough, then track the NixOS kernel and pat
}
```
well the huge downside to this is that tweaking just a single line in `my-kernel-patch.patch` forces
a complete rebuild of your kernel: your iteration cycle time might well be 30 minutes with this approach.
but the crushing downside to this approach is that tweaking just a single line in `my-kernel-patch.patch` forces
a complete rebuild of the kernel: your iteration cycle time might well be 30 minutes.
## Faster Kernel Iterations
until we get something like [dynamic derivations], the path out of costly iteration is to ship whichever tweaks you're pursuing via some other mechanism than as part of the top-level kernel derivation.
until we get something like [dynamic derivations][dynamic derivations], the path out of costly iteration is to ship whichever tweaks you're pursuing via some other mechanism than as part of the top-level kernel derivation.
the kernel is fairly pluggable; consider:
1. device tree files can be loaded at runtime.
@@ -57,20 +59,25 @@ with the right config, either of these things can be freely modified without for
## Shipping Device Tree Patches
Linux uses [device tree] files to determine which peripherals are available on your
device and how to use them. it's more relevant for embedded devices than for traditional x86 PCs.
Linux uses [device tree][device tree] files to determine which peripherals are available on your
device and how to interface with them. it's more relevant for embedded devices than for traditional x86 machines.
the typical boot flow for an embedded device is that the bootloader (U-Boot) knows the name of
the device it's running on (perhaps because it was built _specifically_ for that device) and communicates
this to the kernel when it hands over control of the device. the name of this device tree might look something like "pine64,pinephone-pro".
this to the kernel when it hands over control of the device.
the kernel ships with device tree definitions for hundreds of different devices,
each with a name like `samsung,exynos7-espresso` or `pine64,pinephone-pro`,
and after learning which device it's actually running on it applies the appropriate device tree,
instantiating and configuring device drivers accordingly.
device trees are composable by design; the spec already defines the concept of an "overlay",
device trees are composable by design; the spec defines the concept of an "overlay",
such that the actual device tree to apply to a device should be the union of whatever's
defined in the kernel source tree plus any overlays that apply to the same device.
NixOS makes this feature available via the [`hardware.deviceTree.overlays`] option.
NixOS makes this feature available via the [`hardware.deviceTree.overlays`][hardware.deviceTree.overlays] option,
and a certain amount of our kernel changes can be expressed via this option instead of costly `.patch` files.
one issue with the PinePhone Pro on stock Linux is that the volume-down button doesn't work.
but it works in the pine64 kernel, because they patched the device tree file like so:
for example, the stock kernel misidentifies volume-down key presses as volume-up events on the PinePhone Pro.
distros like postmarketOS fix this by patching the device tree file like so:
```diff
--- arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts
+++ arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts
@@ -89,12 +96,12 @@ but it works in the pine64 kernel, because they patched the device tree file lik
};
```
this patch can alternately be represented as a [Device Tree Overlay] (DTO):
this patch can alternately be represented as a [Device Tree Overlay][Device Tree Overlay] (DTO):
```dts
// file: rk3399-pinephone-pro-lradc-fix.dtso
// metadata for the device tree compiler
// boilerplate required by the device tree compiler.
/dts-v1/;
/plugin/;
@@ -133,7 +140,8 @@ to a PinePhone Pro, the volume down button should now be fixed!
## Shipping Custom Kernel Modules
one of the less trivial patches yet to be mainlined is support for
battery monitoring on the PinePhone Pro. this feature is supported
battery monitoring on the PinePhone Pro (i.e. viewing the charge level
and the charge/discharge rate). this feature is supported
in downstream kernels by shipping two new kernel modules:
`rk818_battery` and `rk818_charger`.
@@ -168,8 +176,10 @@ 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
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:
@@ -210,7 +220,7 @@ this can be packaged for nix as so:
}
```
then add this package to the `linuxKernel` package set:
then add this package to nixpkgs' `linuxKernel` package set:
```nix
{ ... }:
@@ -246,13 +256,15 @@ add the following to your NixOS config:
{ 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`
# and makes them available under /run/current-system/kernel-modules
# where they can be found by 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.
# if they're needed earlier in the boot process,
# then use `boot.initrd.kernelModules` instead.
boot.kernelModules = [
"rk818_battery"
"rk818_charger"
@@ -331,10 +343,10 @@ kernel module (`rk8xx-i2c`). since the kernel module is defined in-tree, the nat
way to do that would force a kernel rebuild every time we adjust the patches.
we can build our patched module out-of-tree using the same method
as we build a wholly new module out-of-tree (writing the derivation
as some `patches` atop `src = linux-latest`, or just copying the entire
module source into our repo instead of tracking patches). then we just
configure the kernel to prefer our module over its in-tree module.
we used to build a wholly new module out-of-tree (write the derivation
as some `patches` atop `src = linux-latest`, or forget about tracking
patches and just copy the entire module source into our repo).
then we just configure the kernel to prefer our module over its in-tree module.
a first approach might be to configure the kernel with `CONFIG_MFD_RK8XX_I2C=n`,
then ship our module as above. this works:
@@ -351,7 +363,6 @@ then ship our module as above. this works:
};
}
];
}
```
@@ -368,7 +379,7 @@ remove the `MFD_RK8XX_I2C = no` patch and replace it with this:
nixpkgs.config.hostPlatform.linux-kernel = {
# build every module known to the mainline kernel
autoModules = true;
# and build those as dynamically loaded modules (`=m`), instead of builtins (`=y`).
# and build them as dynamically loaded modules (`=m`), not builtins (`=y`)
preferBuiltin = false;
};
@@ -378,19 +389,25 @@ remove the `MFD_RK8XX_I2C = no` patch and replace it with this:
# to not complain.
system.modulesTree = lib.mkForce [(
(pkgs.aggregateModules
( config.boot.extraModulePackages ++ [ config.boot.kernelPackages.kernel ])
(config.boot.extraModulePackages ++ [ config.boot.kernelPackages.kernel ])
).overrideAttrs {
# when collisions are ignored, earlier items override the contents of later items
# when ignoring collisions, order becomes important:
# earlier items (config.boot.extraModulePackages) override later items
# (config.boot.kernelPackages.kernel).
ignoreCollisions = true;
}
)];
}
```
more examples for this method can be found on the [nixos wiki][nixos-wiki-intree-modules].
## Conclusion
and there you have it! if you found anything here useful, then my request to you
and there you have it! several techniques for working with device trees and kernel
modules without suffering lengthy builds.
if you found anything here useful, then my request to you
is to upstream your kernel work so that the next reader of this blog doesn't have
to go through the same pain 😛
@@ -400,8 +417,9 @@ in case i missed some crucial detail in this writeup.
[PinePhone Pro]: https://en.wikipedia.org/wiki/PinePhone_Pro
[postmarketOS]: https://postmarketos.org/
[dynamic-derivations]: https://github.com/NixOS/rfcs/pull/92
[dynamic derivations]: https://github.com/NixOS/rfcs/pull/92
[device tree]: https://docs.kernel.org/devicetree/usage-model.html
[`hardware.deviceTree.overlays`]: https://search.nixos.org/options?channel=unstable&show=hardware.deviceTree.overlays&from=0&size=50&sort=relevance&type=packages&query=hardware.deviceTree.overlays
[Device Tree Overlay]: TODO
[sane-nix-ppp]: https://git.uninsane.org/colin/nix-files/src/commit/4e008c34208143a489d824b288437201f7137ace/hosts/modules/hal/pine64-pinephone-pro/default.nix
[hardware.deviceTree.overlays]: https://search.nixos.org/options?channel=unstable&show=hardware.deviceTree.overlays&from=0&size=50&sort=relevance&type=packages&query=hardware.deviceTree.overlays
[Device Tree Overlay]: https://docs.kernel.org/devicetree/overlay-notes.html
[sane-nix-ppp]: https://git.uninsane.org/colin/nix-files/src/commit/4e008c34208143a489d824b288437201f7137ace/hosts/modules/hal/pine64-pinephone-pro/default.nix#L59
[nixos-wiki-intree-modules]: https://wiki.nixos.org/wiki/Linux_kernel#Patching_a_single_In-tree_kernel_module