blog: nixos-kernel-hacking: proof reading
This commit is contained in:
@@ -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
|
||||
|
Reference in New Issue
Block a user