From fb248c0047f0296a09421589da45228992cb07fd Mon Sep 17 00:00:00 2001 From: Robert Cambridge Date: Mon, 18 Nov 2024 13:46:54 +0100 Subject: [PATCH] use make-disk-image instead of sd-image --- .gitignore | 1 + README.md | 66 ++++-- atomic-copy/atomic-copy-clobber.nix | 8 + atomic-copy/atomic-copy-clobber.sh | 18 ++ atomic-copy/atomic-copy-safe.nix | 8 + atomic-copy/atomic-copy-safe.sh | 20 ++ example/common.nix | 81 +++++++ example/default.nix | 44 ---- example/direct.nix | 6 + example/uboot.nix | 6 + flake.nix | 13 +- generic-extlinux-compatible/README.md | 5 + generic-extlinux-compatible/default.nix | 3 + .../extlinux-conf-builder.nix | 8 + .../extlinux-conf-builder.sh | 163 +++++++++++++ .../generic-extlinux-compatible.nix | 132 +++++++++++ rpi/default.nix | 217 +++++------------- 17 files changed, 570 insertions(+), 229 deletions(-) create mode 100644 .gitignore create mode 100644 atomic-copy/atomic-copy-clobber.nix create mode 100644 atomic-copy/atomic-copy-clobber.sh create mode 100644 atomic-copy/atomic-copy-safe.nix create mode 100644 atomic-copy/atomic-copy-safe.sh create mode 100644 example/common.nix delete mode 100644 example/default.nix create mode 100644 example/direct.nix create mode 100644 example/uboot.nix create mode 100644 generic-extlinux-compatible/README.md create mode 100644 generic-extlinux-compatible/default.nix create mode 100644 generic-extlinux-compatible/extlinux-conf-builder.nix create mode 100644 generic-extlinux-compatible/extlinux-conf-builder.sh create mode 100644 generic-extlinux-compatible/generic-extlinux-compatible.nix diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e2f5dd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +result \ No newline at end of file diff --git a/README.md b/README.md index 8c94ef9..c2de515 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ this repository aims to deliver the following benefits: 3. Make it easy to build an image suitable for flashing to an sd-card, without the need to first go through an installation media. -The important modules are `overlay/default.nix`, `rpi/default.nix`, +The important modules are `overlays/default.nix`, `rpi/default.nix`, and `rpi/config.nix`. The other modules are mostly wrappers that set `config.txt` settings and enable required kernel modules. @@ -25,7 +25,11 @@ upgrading. ## Example -See the `rpi-example` config in this flake for an example config built by CI. +See the `/example` in this flake for an configs [built by CI](https://buildbot.nix-community.org/#/projects/15). + +* `/example/direct.nix` boots directly into the linux kernel +* `/example/uboot.nix` boots into uboot, provides a generation selection menu, +then into linux ## Using the provided cache to avoid compiling linux This repo uses the raspberry pi linux kernel fork, and compiling linux takes a @@ -36,35 +40,53 @@ to use this cache. ## Building an sd-card image -Include the provided `sd-image` nixos module this flake provides, then an image -suitable for flashing to an sd-card can be found at the attribute -`config.system.build.sdImage`. For example, if you wanted to build an image for -`rpi-example` in the above configuration example you could run: +Use [`make-disk-image.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/lib/make-disk-image.nix) like so: + +``` + system.build.image = (import "${toString modulesPath}/../lib/make-disk-image.nix" { + inherit lib config pkgs; + format = "raw"; + partitionTableType = "efi"; + copyChannel = false; + diskSize = "auto"; + additionalSpace = "64M"; + bootSize = "128M"; + touchEFIVars = false; + installBootLoader = true; + label = "nixos"; + deterministic = true; + }); +``` + +Add the configuration for your specific board: + +``` + raspberry-pi-nix.board = "bcm2711"; + hardware.raspberry-pi.config = { + ... + }; +``` + +Then get nix to build your image: ``` -nix build '.#nixosConfigurations.rpi-example.config.system.build.sdImage' +nix build '.#nixosConfigurations.my-raspberry-pi.config.system.build.image' ``` -## The firmware partition +## The partition layout + +The image produced is partitioned in the same way as any x86 EFI-boot disk generated by `make-disk-image.nix`: There is a partition labelled `ESP` which contains the firmware, kernel, config.txt and u-boot or cmdline.txt. The second partition labeled `nixos` is the root partition which contains the nix store and everything else. + +> [!NOTE] +> The boot partition is called ESP which stands for EFI System Partition. Raspberry Pis don't boot with EFI, but `ESP` is the hardcoded partition name used in `make-disk-image.nix` when using `partitionTableType=efi`, which just happens to be the closest partition layout to what's needed. By setting `touchEFIVars=false` you can avoid any EFI boot variables being set, not that they will affect the Raspberry Pi's boot process. -The image produced by this package is partitioned in the same way as the aarch64 -installation media from nixpkgs: There is a firmware partition that contains -necessary firmware, the kernel or u-boot, and config.txt. Then there is another -partition (labeled `NIXOS_SD`) that contains everything else. The firmware and -`config.txt` file are managed by NixOS modules defined in this -package. Additionally, a systemd service will update the firmware and -`config.txt` in the firmware partition __in place__. If uboot is enabled then -linux kernels are stored in the `NIXOS_SD` partition and will be booted by -u-boot in the firmware partition. +Files in the boot partition are managed as long as the `rpi` package is imported, whether using uboot or direct-to-kernel boot. Following the upstream behaviour of `generic-extlinux-compatible/default.nix`, these files are not overwritten once created. The only exceptions are `cmdline.txt` and `config.txt` which will get overwritten every time `nixos-rebuild switch` is run. In practice, this means that you can enjoy kernel updates (because each version is put into a new file), but firmware / bootcode updates will probably not be installed if they arrive in an existing file. ## `config.txt` generation -As noted, the `config.txt` file is generated by the NixOS -configuration and automatically updated on when the nix configuration -is modified. +The `config.txt` file is generated by `rpi` package and automatically written to the boot partition when running `nixos-rebuild switch`. -The relevant nixos option is -`hardware.raspberry-pi.config`. Configuration is partitioned into +The relevant nixos option is `hardware.raspberry-pi.config`. Configuration is partitioned into three sections: 1. Base device tree parameters `base-dt-params` diff --git a/atomic-copy/atomic-copy-clobber.nix b/atomic-copy/atomic-copy-clobber.nix new file mode 100644 index 0000000..aaa1184 --- /dev/null +++ b/atomic-copy/atomic-copy-clobber.nix @@ -0,0 +1,8 @@ +{ pkgs }: + +pkgs.substituteAll { + src = ./atomic-copy-clobber.sh; + isExecutable = true; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + inherit (pkgs) bash; +} diff --git a/atomic-copy/atomic-copy-clobber.sh b/atomic-copy/atomic-copy-clobber.sh new file mode 100644 index 0000000..9e6ecb3 --- /dev/null +++ b/atomic-copy/atomic-copy-clobber.sh @@ -0,0 +1,18 @@ +#! @bash@/bin/sh -e + +# copy+paste of copyToKernelsDir https://github.com/NixOS/nixpkgs/blob/904ecf0b4e055dc465f5ae6574be2af8cc25dec3/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh#L53 +# but without the check which skips the copy if the destination exists + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin; done + +src=$(readlink -f "$1") +dst="$2" + +# Create $dst atomically to prevent partially copied files +# if this script is ever interrupted. +dstTmp=$dst.tmp.$$ +cp -r $src $dstTmp +mv $dstTmp $dst diff --git a/atomic-copy/atomic-copy-safe.nix b/atomic-copy/atomic-copy-safe.nix new file mode 100644 index 0000000..971902f --- /dev/null +++ b/atomic-copy/atomic-copy-safe.nix @@ -0,0 +1,8 @@ +{ pkgs }: + +pkgs.substituteAll { + src = ./atomic-copy-safe.sh; + isExecutable = true; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + inherit (pkgs) bash; +} diff --git a/atomic-copy/atomic-copy-safe.sh b/atomic-copy/atomic-copy-safe.sh new file mode 100644 index 0000000..ab1d46c --- /dev/null +++ b/atomic-copy/atomic-copy-safe.sh @@ -0,0 +1,20 @@ +#! @bash@/bin/sh -e + +# copy+paste of copyToKernelsDir https://github.com/NixOS/nixpkgs/blob/904ecf0b4e055dc465f5ae6574be2af8cc25dec3/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh#L53 + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin; done + +src=$(readlink -f "$1") +dst="$2" + +# Don't copy the file if $dst already exists. +# Also create $dst atomically to prevent partially copied files +# if this script is ever interrupted. +if ! test -e $dst; then + dstTmp=$dst.tmp.$$ + cp -r $src $dstTmp + mv $dstTmp $dst +fi diff --git a/example/common.nix b/example/common.nix new file mode 100644 index 0000000..c1c28ff --- /dev/null +++ b/example/common.nix @@ -0,0 +1,81 @@ +{ config, inputs, lib, modulesPath, pkgs, ... }: { + time.timeZone = "America/New_York"; + users.users.root.initialPassword = "root"; + networking = { + hostName = "example"; + useDHCP = false; + interfaces = { + wlan0.useDHCP = true; + eth0.useDHCP = true; + }; + }; + raspberry-pi-nix = { + board = "bcm2711"; + }; + hardware = { + raspberry-pi = { + config = { + all = { + base-dt-params = { + BOOT_UART = { + value = 1; + enable = true; + }; + uart_2ndstage = { + value = 1; + enable = true; + }; + }; + dt-overlays = { + disable-bt = { + enable = true; + params = { }; + }; + }; + }; + }; + }; + }; + security.rtkit.enable = true; + services.pipewire = { + enable = true; + alsa.enable = true; + alsa.support32Bit = true; + pulse.enable = true; + }; + fileSystems = { + "/boot" = { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; + "/" = { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + autoResize = true; + }; + }; + boot.growPartition = true; + system.build.image = (import "${toString modulesPath}/../lib/make-disk-image.nix" { + inherit lib config pkgs; + format = "raw"; + partitionTableType = "efi"; + copyChannel = false; + diskSize = "auto"; + additionalSpace = "64M"; + bootSize = "128M"; + touchEFIVars = false; + installBootLoader = true; + label = "nixos"; + deterministic = true; + }); + + nix.settings.substituters = lib.mkForce config.nix.settings.trusted-substituters; + nix.settings.trusted-substituters = [ + "https://cache.nixos.org/" + "https://nix-community.cachix.org" + ]; + nix.settings.trusted-public-keys = [ + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" + ]; +} diff --git a/example/default.nix b/example/default.nix deleted file mode 100644 index 31e787f..0000000 --- a/example/default.nix +++ /dev/null @@ -1,44 +0,0 @@ -{ pkgs, lib, ... }: { - time.timeZone = "America/New_York"; - users.users.root.initialPassword = "root"; - networking = { - hostName = "example"; - useDHCP = false; - interfaces = { - wlan0.useDHCP = true; - eth0.useDHCP = true; - }; - }; - raspberry-pi-nix.board = "bcm2711"; - hardware = { - raspberry-pi = { - config = { - all = { - base-dt-params = { - BOOT_UART = { - value = 1; - enable = true; - }; - uart_2ndstage = { - value = 1; - enable = true; - }; - }; - dt-overlays = { - disable-bt = { - enable = true; - params = { }; - }; - }; - }; - }; - }; - }; - security.rtkit.enable = true; - services.pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - }; -} diff --git a/example/direct.nix b/example/direct.nix new file mode 100644 index 0000000..e7d4e74 --- /dev/null +++ b/example/direct.nix @@ -0,0 +1,6 @@ +{ config, inputs, lib, modulesPath, pkgs, ... }: { + imports = [ + ./common.nix + ]; + raspberry-pi-nix.uboot.enable = false; +} diff --git a/example/uboot.nix b/example/uboot.nix new file mode 100644 index 0000000..0e8e510 --- /dev/null +++ b/example/uboot.nix @@ -0,0 +1,6 @@ +{ config, inputs, lib, modulesPath, pkgs, ... }: { + imports = [ + ./common.nix + ]; + raspberry-pi-nix.uboot.enable = true; +} diff --git a/flake.nix b/flake.nix index c0acdc3..0403548 100644 --- a/flake.nix +++ b/flake.nix @@ -59,12 +59,16 @@ core-overlay = self.overlays.core; libcamera-overlay = self.overlays.libcamera; }; - sd-image = import ./sd-image; + generic-extlinux-compatible = import ./generic-extlinux-compatible; }; nixosConfigurations = { - rpi-example = srcs.nixpkgs.lib.nixosSystem { + rpi-example-direct = srcs.nixpkgs.lib.nixosSystem { system = "aarch64-linux"; - modules = [ self.nixosModules.raspberry-pi self.nixosModules.sd-image ./example ]; + modules = [ self.nixosModules.raspberry-pi ./example/direct.nix ]; + }; + rpi-example-uboot = srcs.nixpkgs.lib.nixosSystem { + system = "aarch64-linux"; + modules = [ self.nixosModules.raspberry-pi ./example/uboot.nix ]; }; }; checks.aarch64-linux = self.packages.aarch64-linux; @@ -81,7 +85,8 @@ board-attr-set; in { - example-sd-image = self.nixosConfigurations.rpi-example.config.system.build.sdImage; + example-image-direct = self.nixosConfigurations.rpi-example-direct.config.system.build.image; + example-image-uboot = self.nixosConfigurations.rpi-example-uboot.config.system.build.image; firmware = pinned.raspberrypifw; libcamera = pinned.libcamera; wireless-firmware = pinned.raspberrypiWirelessFirmware; diff --git a/generic-extlinux-compatible/README.md b/generic-extlinux-compatible/README.md new file mode 100644 index 0000000..9a96309 --- /dev/null +++ b/generic-extlinux-compatible/README.md @@ -0,0 +1,5 @@ +Copied from: + +https://github.com/NixOS/nixpkgs/blob/93fb96ecdead26092b8425383fffb0422bf52182/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix + +The goal is to merge the changes back in once tested. \ No newline at end of file diff --git a/generic-extlinux-compatible/default.nix b/generic-extlinux-compatible/default.nix new file mode 100644 index 0000000..c0b522a --- /dev/null +++ b/generic-extlinux-compatible/default.nix @@ -0,0 +1,3 @@ +{ ... }: { + imports = [ ./generic-extlinux-compatible.nix ]; +} \ No newline at end of file diff --git a/generic-extlinux-compatible/extlinux-conf-builder.nix b/generic-extlinux-compatible/extlinux-conf-builder.nix new file mode 100644 index 0000000..576a07c --- /dev/null +++ b/generic-extlinux-compatible/extlinux-conf-builder.nix @@ -0,0 +1,8 @@ +{ pkgs }: + +pkgs.substituteAll { + src = ./extlinux-conf-builder.sh; + isExecutable = true; + path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep]; + inherit (pkgs) bash; +} diff --git a/generic-extlinux-compatible/extlinux-conf-builder.sh b/generic-extlinux-compatible/extlinux-conf-builder.sh new file mode 100644 index 0000000..f2b281d --- /dev/null +++ b/generic-extlinux-compatible/extlinux-conf-builder.sh @@ -0,0 +1,163 @@ +#! @bash@/bin/sh -e + +shopt -s nullglob + +export PATH=/empty +for i in @path@; do PATH=$PATH:$i/bin; done + +usage() { + echo "usage: $0 -t -c [-d ] [-g ] [-n ] [-r]" >&2 + exit 1 +} + +timeout= # Timeout in centiseconds +default= # Default configuration +target=/boot # Target directory +numGenerations=0 # Number of other generations to include in the menu + +while getopts "t:c:d:g:n:r" opt; do + case "$opt" in + t) # U-Boot interprets '0' as infinite and negative as instant boot + if [ "$OPTARG" -lt 0 ]; then + timeout=0 + elif [ "$OPTARG" = 0 ]; then + timeout=-10 + else + timeout=$((OPTARG * 10)) + fi + ;; + c) default="$OPTARG" ;; + d) target="$OPTARG" ;; + g) numGenerations="$OPTARG" ;; + n) dtbName="$OPTARG" ;; + r) noDeviceTree=1 ;; + \?) usage ;; + esac +done + +[ "$timeout" = "" -o "$default" = "" ] && usage + +mkdir -p $target/nixos +mkdir -p $target/extlinux + +# Convert a path to a file in the Nix store such as +# /nix/store/-/file to --. +cleanName() { + local path="$1" + echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g' +} + +# Copy a file from the Nix store to $target/nixos. +declare -A filesCopied + +copyToKernelsDir() { + local src=$(readlink -f "$1") + local dst="$target/nixos/$(cleanName $src)" + # Don't copy the file if $dst already exists. This means that we + # have to create $dst atomically to prevent partially copied + # kernels or initrd if this script is ever interrupted. + if ! test -e $dst; then + local dstTmp=$dst.tmp.$$ + cp -r $src $dstTmp + mv $dstTmp $dst + fi + filesCopied[$dst]=1 + result=$dst +} + +# Copy its kernel, initrd and dtbs to $target/nixos, and echo out an +# extlinux menu entry +addEntry() { + local path=$(readlink -f "$1") + local tag="$2" # Generation number or 'default' + + if ! test -e $path/kernel -a -e $path/initrd; then + return + fi + + copyToKernelsDir "$path/kernel"; kernel=$result + copyToKernelsDir "$path/initrd"; initrd=$result + dtbDir=$(readlink -m "$path/dtbs") + if [ -e "$dtbDir" ]; then + copyToKernelsDir "$dtbDir"; dtbs=$result + fi + + timestampEpoch=$(stat -L -c '%Z' $path) + + timestamp=$(date "+%Y-%m-%d %H:%M" -d @$timestampEpoch) + nixosLabel="$(cat $path/nixos-version)" + extraParams="$(cat $path/kernel-params)" + + echo + echo "LABEL nixos-$tag" + if [ "$tag" = "default" ]; then + echo " MENU LABEL NixOS - Default" + else + echo " MENU LABEL NixOS - Configuration $tag ($timestamp - $nixosLabel)" + fi + echo " LINUX ../nixos/$(basename $kernel)" + echo " INITRD ../nixos/$(basename $initrd)" + echo " APPEND init=$path/init $extraParams" + + if [ -n "$noDeviceTree" ]; then + return + fi + + if [ -d "$dtbDir" ]; then + # if a dtbName was specified explicitly, use that, else use FDTDIR + if [ -n "$dtbName" ]; then + echo " FDT ../nixos/$(basename $dtbs)/${dtbName}" + else + echo " FDTDIR ../nixos/$(basename $dtbs)" + fi + else + if [ -n "$dtbName" ]; then + echo "Explicitly requested dtbName $dtbName, but there's no FDTDIR - bailing out." >&2 + exit 1 + fi + fi +} + +tmpFile="$target/extlinux/extlinux.conf.tmp.$$" + +cat > $tmpFile <> $tmpFile + +if [ "$numGenerations" -gt 0 ]; then + # Add up to $numGenerations generations of the system profile to the menu, + # in reverse (most recent to least recent) order. + for generation in $( + (cd /nix/var/nix/profiles && ls -d system-*-link) \ + | sed 's/system-\([0-9]\+\)-link/\1/' \ + | sort -n -r \ + | head -n $numGenerations); do + link=/nix/var/nix/profiles/system-$generation-link + addEntry $link "${generation}-default" + for specialisation in $( + ls /nix/var/nix/profiles/system-$generation-link/specialisation \ + | sort -n -r); do + link=/nix/var/nix/profiles/system-$generation-link/specialisation/$specialisation + addEntry $link "${generation}-${specialisation}" + done + done >> $tmpFile +fi + +mv -f $tmpFile $target/extlinux/extlinux.conf + +# Remove obsolete files from $target/nixos. +for fn in $target/nixos/*; do + if ! test "${filesCopied[$fn]}" = 1; then + echo "Removing no longer needed boot file: $fn" + chmod +w -- "$fn" + rm -rf -- "$fn" + fi +done diff --git a/generic-extlinux-compatible/generic-extlinux-compatible.nix b/generic-extlinux-compatible/generic-extlinux-compatible.nix new file mode 100644 index 0000000..d966efe --- /dev/null +++ b/generic-extlinux-compatible/generic-extlinux-compatible.nix @@ -0,0 +1,132 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + blCfg = config.boot.loader; + dtCfg = config.hardware.deviceTree; + cfg = blCfg.generic-extlinux-compatible-pi-loader; + + timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout; + + # The builder used to write during system activation + builder = import ./extlinux-conf-builder.nix { inherit pkgs; }; + # The builder exposed in populateCmd, which runs on the build architecture + populateBuilder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; }; +in +{ + options = { + boot.loader.generic-extlinux-compatible-pi-loader = { + enable = mkOption { + default = false; + type = types.bool; + description = '' + Whether to generate an extlinux-compatible configuration file + under `/boot/extlinux.conf`. For instance, + U-Boot's generic distro boot support uses this file format. + + See [U-boot's documentation](https://u-boot.readthedocs.io/en/latest/develop/distro.html) + for more information. + ''; + }; + + useGenerationDeviceTree = mkOption { + default = true; + type = types.bool; + description = '' + Whether to generate Device Tree-related directives in the + extlinux configuration. + + When enabled, the bootloader will attempt to load the device + tree binaries from the generation's kernel. + + Note that this affects all generations, regardless of the + setting value used in their configurations. + ''; + }; + + configurationLimit = mkOption { + default = 20; + example = 10; + type = types.int; + description = '' + Maximum number of configurations in the boot menu. + ''; + }; + + mirroredBoots = mkOption { + default = [ { path = "/boot"; } ]; + example = [ + { path = "/boot1"; } + { path = "/boot2"; } + ]; + description = '' + Mirror the boot configuration to multiple paths. + ''; + + type = with types; listOf (submodule { + options = { + path = mkOption { + example = "/boot1"; + type = types.str; + description = '' + The path to the boot directory where the extlinux-compatible + configuration files will be written. + ''; + }; + }; + }); + }; + + populateCmd = mkOption { + type = types.str; + readOnly = true; + description = '' + Contains the builder command used to populate an image, + honoring all options except the `-c ` + argument. + Useful to have for sdImage.populateRootCommands + ''; + }; + + extraCommandsAfter = mkOption { + type = types.listOf types.str; + description = '' + Optional commands to run after installing the bootloader. + Useful for putting Raspberry Pi firmwares on the boot partition. + ''; + }; + + }; + }; + + config = let + builderArgs = "-g ${toString cfg.configurationLimit} -t ${timeoutStr}" + + lib.optionalString (dtCfg.name != null) " -n ${dtCfg.name}" + + lib.optionalString (!cfg.useGenerationDeviceTree) " -r"; + installBootLoader = pkgs.writeScript "install-extlinux-conf.sh" ('' + #!${pkgs.runtimeShell} + set -e + '' + flip concatMapStrings cfg.mirroredBoots (args: '' + ${builder} ${builderArgs} -d '${args.path}' -c "$@" + '') + '' + ${lib.concatLines cfg.extraCommandsAfter} + ''); + in + mkIf cfg.enable { + system.build.installBootLoader = installBootLoader; + system.boot.loader.id = "generic-extlinux-compatible-pi-loader"; + + boot.loader.generic-extlinux-compatible-pi-loader.populateCmd = "${populateBuilder} ${builderArgs}"; + + assertions = [ + { + assertion = cfg.mirroredBoots != [ ]; + message = '' + You must not remove all elements from option 'boot.loader.generic-extlinux-compatible-pi-loader.mirroredBoots', + otherwise the system will not be bootable. + ''; + } + ]; + }; +} diff --git a/rpi/default.nix b/rpi/default.nix index 5900692..3809f8d 100644 --- a/rpi/default.nix +++ b/rpi/default.nix @@ -1,15 +1,20 @@ { pinned, core-overlay, libcamera-overlay }: { lib, pkgs, config, ... }: - +with lib; let cfg = config.raspberry-pi-nix; version = cfg.kernel-version; board = cfg.board; - kernel = config.system.build.kernel; - initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; + atomicCopySafe = import ../atomic-copy/atomic-copy-safe.nix { inherit pkgs; }; + atomicCopyClobber = import ../atomic-copy/atomic-copy-clobber.nix { inherit pkgs; }; + + # used for direct-to-kernel boot only: emulate cleanName() + # https://github.com/NixOS/nixpkgs/blob/904ecf0b4e055dc465f5ae6574be2af8cc25dec3/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh#L47 + kernelStorePath = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}"; + kernelBootPath = "nixos/${builtins.replaceStrings [ "/nix/store/" "/" ] [ "" "-" ] kernelStorePath}"; in { - imports = [ ./config.nix ./i2c.nix ]; + imports = [ ../generic-extlinux-compatible ./config.nix ./i2c.nix ]; options = with lib; { raspberry-pi-nix = { @@ -77,153 +82,16 @@ in package = mkPackageOption pkgs "uboot-rpi-arm64" { }; }; + rootGPUID = mkOption { + description = "The UID of the root partition"; + type = types.str; + # https://github.com/NixOS/nixpkgs/blob/23e89b7da85c3640bbc2173fe04f4bd114342367/nixos/lib/make-disk-image.nix#L177 + default = "F222513B-DED1-49FA-B591-20CE86A2FE7F"; + }; }; }; config = { - systemd.services = { - "raspberry-pi-firmware-migrate" = - { - description = "update the firmware partition"; - wantedBy = if cfg.firmware-migration-service.enable then [ "multi-user.target" ] else [ ]; - serviceConfig = - let - firmware-path = "/boot/firmware"; - kernel-params = pkgs.writeTextFile { - name = "cmdline.txt"; - text = '' - ${lib.strings.concatStringsSep " " config.boot.kernelParams} - ''; - }; - in - { - Type = "oneshot"; - MountImages = - "/dev/disk/by-label/${cfg.firmware-partition-label}:${firmware-path}"; - StateDirectory = "raspberrypi-firmware"; - ExecStart = pkgs.writeShellScript "migrate-rpi-firmware" '' - shopt -s nullglob - - TARGET_FIRMWARE_DIR="${firmware-path}" - TARGET_OVERLAYS_DIR="$TARGET_FIRMWARE_DIR/overlays" - TMPFILE="$TARGET_FIRMWARE_DIR/tmp" - KERNEL="${kernel}/${config.system.boot.loader.kernelFile}" - SHOULD_UBOOT=${if cfg.uboot.enable then "1" else "0"} - SRC_FIRMWARE_DIR="${pkgs.raspberrypifw}/share/raspberrypi/boot" - STARTFILES=("$SRC_FIRMWARE_DIR"/start*.elf) - DTBS=("$SRC_FIRMWARE_DIR"/*.dtb) - BOOTCODE="$SRC_FIRMWARE_DIR/bootcode.bin" - FIXUPS=("$SRC_FIRMWARE_DIR"/fixup*.dat) - SRC_OVERLAYS_DIR="$SRC_FIRMWARE_DIR/overlays" - SRC_OVERLAYS=("$SRC_OVERLAYS_DIR"/*) - CONFIG="${config.hardware.raspberry-pi.config-output}" - - ${lib.strings.optionalString cfg.uboot.enable '' - UBOOT="${cfg.uboot.package}/u-boot.bin" - - migrate_uboot() { - echo "migrating uboot" - touch "$STATE_DIRECTORY/uboot-migration-in-progress" - cp "$UBOOT" "$TMPFILE" - mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/u-boot-rpi-arm64.bin" - echo "${builtins.toString cfg.uboot.package}" > "$STATE_DIRECTORY/uboot-version" - rm "$STATE_DIRECTORY/uboot-migration-in-progress" - } - ''} - - migrate_kernel() { - echo "migrating kernel" - touch "$STATE_DIRECTORY/kernel-migration-in-progress" - cp "$KERNEL" "$TMPFILE" - mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/kernel.img" - cp "${initrd}" "$TMPFILE" - mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/initrd" - echo "${ - builtins.toString kernel - }" > "$STATE_DIRECTORY/kernel-version" - rm "$STATE_DIRECTORY/kernel-migration-in-progress" - } - - migrate_cmdline() { - echo "migrating cmdline" - touch "$STATE_DIRECTORY/cmdline-migration-in-progress" - cp "${kernel-params}" "$TMPFILE" - mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/cmdline.txt" - echo "${ - builtins.toString kernel-params - }" > "$STATE_DIRECTORY/cmdline-version" - rm "$STATE_DIRECTORY/cmdline-migration-in-progress" - } - - migrate_config() { - echo "migrating config.txt" - touch "$STATE_DIRECTORY/config-migration-in-progress" - cp "$CONFIG" "$TMPFILE" - mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/config.txt" - echo "${config.hardware.raspberry-pi.config-output}" > "$STATE_DIRECTORY/config-version" - rm "$STATE_DIRECTORY/config-migration-in-progress" - } - - migrate_firmware() { - echo "migrating raspberrypi firmware" - touch "$STATE_DIRECTORY/firmware-migration-in-progress" - for SRC in "''${STARTFILES[@]}" "''${DTBS[@]}" "$BOOTCODE" "''${FIXUPS[@]}" - do - cp "$SRC" "$TMPFILE" - mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/$(basename "$SRC")" - done - - if [[ ! -d "$TARGET_OVERLAYS_DIR" ]]; then - mkdir "$TARGET_OVERLAYS_DIR" - fi - - for SRC in "''${SRC_OVERLAYS[@]}" - do - cp "$SRC" "$TMPFILE" - mv -T "$TMPFILE" "$TARGET_OVERLAYS_DIR/$(basename "$SRC")" - done - echo "${ - builtins.toString pkgs.raspberrypifw - }" > "$STATE_DIRECTORY/firmware-version" - rm "$STATE_DIRECTORY/firmware-migration-in-progress" - } - - ${lib.strings.optionalString cfg.uboot.enable '' - if [[ "$SHOULD_UBOOT" -eq 1 ]] && [[ -f "$STATE_DIRECTORY/uboot-migration-in-progress" || ! -f "$STATE_DIRECTORY/uboot-version" || $(< "$STATE_DIRECTORY/uboot-version") != ${ - builtins.toString cfg.uboot.package - } ]]; then - migrate_uboot - fi - ''} - - if [[ "$SHOULD_UBOOT" -ne 1 ]] && [[ ! -f "$STATE_DIRECTORY/kernel-version" || $(< "$STATE_DIRECTORY/kernel-version") != ${ - builtins.toString kernel - } ]]; then - migrate_kernel - fi - - if [[ "$SHOULD_UBOOT" -ne 1 ]] && [[ ! -f "$STATE_DIRECTORY/cmdline-version" || $(< "$STATE_DIRECTORY/cmdline-version") != ${ - builtins.toString kernel-params - } ]]; then - migrate_cmdline - fi - - if [[ -f "$STATE_DIRECTORY/config-migration-in-progress" || ! -f "$STATE_DIRECTORY/config-version" || $(< "$STATE_DIRECTORY/config-version") != ${ - builtins.toString config.hardware.raspberry-pi.config-output - } ]]; then - migrate_config - fi - - if [[ -f "$STATE_DIRECTORY/firmware-migration-in-progress" || ! -f "$STATE_DIRECTORY/firmware-version" || $(< "$STATE_DIRECTORY/firmware-version") != ${ - builtins.toString pkgs.raspberrypifw - } ]]; then - migrate_firmware - fi - ''; - }; - }; - }; - # Default config.txt on Raspberry Pi OS: # https://github.com/RPi-Distro/pi-gen/blob/master/stage1/00-boot-files/files/config.txt hardware.raspberry-pi.config = { @@ -245,11 +113,9 @@ in }; all = { options = { - # The firmware will start our u-boot binary rather than a - # linux kernel. kernel = { enable = true; - value = if cfg.uboot.enable then "u-boot-rpi-arm64.bin" else "kernel.img"; + value = if cfg.uboot.enable then "u-boot-rpi-arm64.bin" else kernelBootPath; }; ramfsfile = { enable = !cfg.uboot.enable; @@ -318,13 +184,15 @@ in }; boot = { kernelParams = - if cfg.uboot.enable then [ ] + [ "console=serial0,115200n8" "console=tty1" ] ++ + (if cfg.uboot.enable then [ ] else [ - "console=tty1" - # https://github.com/raspberrypi/firmware/issues/1539#issuecomment-784498108 - "console=serial0,115200n8" - "init=/sbin/init" - ]; + "root=PARTUUID=${cfg.rootGPUID}" + "rootfstype=ext4" + "fsck.repair=yes" + "rootwait" + "init=/nix/var/nix/profiles/system/init" + ]); initrd = { availableKernelModules = [ "usbhid" @@ -337,12 +205,43 @@ in kernelPackages = pkgs.linuxPackagesFor pkgs.rpi-kernels."${version}"."${board}"; loader = { grub.enable = lib.mkDefault false; - initScript.enable = !cfg.uboot.enable; - generic-extlinux-compatible = { - enable = lib.mkDefault cfg.uboot.enable; + + generic-extlinux-compatible.enable = false; + generic-extlinux-compatible-pi-loader = { + # extlinux-style boot is only used when uboot is enabled + # when uboot is disabled, use this module to put files into + # the boot partition as part of installBootloader + enable = true; # We want to use the device tree provided by firmware, so don't # add FDTDIR to the extlinux conf file. useGenerationDeviceTree = false; + extraCommandsAfter = let + configTxt = config.hardware.raspberry-pi.config-output; + kernelParams = pkgs.writeTextFile { + name = "cmdline.txt"; + text = '' + ${lib.strings.concatStringsSep " " config.boot.kernelParams} + ''; + }; + script = flip concatMapStrings config.boot.loader.generic-extlinux-compatible-pi-loader.mirroredBoots (args: '' + # Add raspi files + cd ${pkgs.raspberrypifw}/share/raspberrypi/boot + ${atomicCopySafe} bootcode.bin ${args.path}/bootcode.bin + ${atomicCopySafe} overlays ${args.path}/overlays + ${pkgs.findutils}/bin/find . -type f -name 'fixup*.dat' -exec ${atomicCopySafe} {} ${args.path}/{} \; + ${pkgs.findutils}/bin/find . -type f -name 'start*.elf' -exec ${atomicCopySafe} {} ${args.path}/{} \; + ${pkgs.findutils}/bin/find . -type f -name '*.dtb' -exec ${atomicCopySafe} {} ${args.path}/{} \; + + # Add config.txt + ${atomicCopyClobber} ${configTxt} ${args.path}/config.txt + '' + (if cfg.uboot.enable then '' + # Add u-boot files + ${atomicCopySafe} ${cfg.uboot.package}/u-boot.bin ${args.path}/u-boot-rpi-arm64.bin + '' else '' + # Add kernel params + ${atomicCopyClobber} ${kernelParams} ${args.path}/cmdline.txt + '')); + in [ (toString (pkgs.writeShellScript "cp-pi-loaders.sh" script)) ]; }; }; };