This commit is contained in:
Robert Cambridge 2025-02-07 09:38:24 -08:00 committed by GitHub
commit c0cba6304f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 570 additions and 229 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
result

View file

@ -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, 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. 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 and `rpi/config.nix`. The other modules are mostly wrappers that set
`config.txt` settings and enable required kernel modules. `config.txt` settings and enable required kernel modules.
@ -25,7 +25,11 @@ upgrading.
## Example ## 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 ## Using the provided cache to avoid compiling linux
This repo uses the raspberry pi linux kernel fork, and compiling linux takes a 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 ## Building an sd-card image
Include the provided `sd-image` nixos module this flake provides, then an image Use [`make-disk-image.nix`](https://github.com/NixOS/nixpkgs/blob/master/nixos/lib/make-disk-image.nix) like so:
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:
``` ```
nix build '.#nixosConfigurations.rpi-example.config.system.build.sdImage' 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;
});
``` ```
## The firmware partition Add the configuration for your specific board:
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 raspberry-pi-nix.board = "bcm2711";
necessary firmware, the kernel or u-boot, and config.txt. Then there is another hardware.raspberry-pi.config = {
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 Then get nix to build your image:
u-boot in the firmware partition.
```
nix build '.#nixosConfigurations.my-raspberry-pi.config.system.build.image'
```
## 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.
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 ## `config.txt` generation
As noted, the `config.txt` file is generated by the NixOS The `config.txt` file is generated by `rpi` package and automatically written to the boot partition when running `nixos-rebuild switch`.
configuration and automatically updated on when the nix configuration
is modified.
The relevant nixos option is The relevant nixos option is `hardware.raspberry-pi.config`. Configuration is partitioned into
`hardware.raspberry-pi.config`. Configuration is partitioned into
three sections: three sections:
1. Base device tree parameters `base-dt-params` 1. Base device tree parameters `base-dt-params`

View file

@ -0,0 +1,8 @@
{ pkgs }:
pkgs.substituteAll {
src = ./atomic-copy-clobber.sh;
isExecutable = true;
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
inherit (pkgs) bash;
}

View file

@ -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

View file

@ -0,0 +1,8 @@
{ pkgs }:
pkgs.substituteAll {
src = ./atomic-copy-safe.sh;
isExecutable = true;
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
inherit (pkgs) bash;
}

View file

@ -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

81
example/common.nix Normal file
View file

@ -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="
];
}

View file

@ -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;
};
}

6
example/direct.nix Normal file
View file

@ -0,0 +1,6 @@
{ config, inputs, lib, modulesPath, pkgs, ... }: {
imports = [
./common.nix
];
raspberry-pi-nix.uboot.enable = false;
}

6
example/uboot.nix Normal file
View file

@ -0,0 +1,6 @@
{ config, inputs, lib, modulesPath, pkgs, ... }: {
imports = [
./common.nix
];
raspberry-pi-nix.uboot.enable = true;
}

View file

@ -63,12 +63,16 @@
core-overlay = self.overlays.core; core-overlay = self.overlays.core;
libcamera-overlay = self.overlays.libcamera; libcamera-overlay = self.overlays.libcamera;
}; };
sd-image = import ./sd-image; generic-extlinux-compatible = import ./generic-extlinux-compatible;
}; };
nixosConfigurations = { nixosConfigurations = {
rpi-example = srcs.nixpkgs.lib.nixosSystem { rpi-example-direct = srcs.nixpkgs.lib.nixosSystem {
system = "aarch64-linux"; 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; checks.aarch64-linux = self.packages.aarch64-linux;
@ -85,7 +89,8 @@
board-attr-set; board-attr-set;
in 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; firmware = pinned.raspberrypifw;
libcamera = pinned.libcamera; libcamera = pinned.libcamera;
wireless-firmware = pinned.raspberrypiWirelessFirmware; wireless-firmware = pinned.raspberrypiWirelessFirmware;

View file

@ -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.

View file

@ -0,0 +1,3 @@
{ ... }: {
imports = [ ./generic-extlinux-compatible.nix ];
}

View file

@ -0,0 +1,8 @@
{ pkgs }:
pkgs.substituteAll {
src = ./extlinux-conf-builder.sh;
isExecutable = true;
path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
inherit (pkgs) bash;
}

View file

@ -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 <timeout> -c <path-to-default-configuration> [-d <boot-dir>] [-g <num-generations>] [-n <dtbName>] [-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/<hash>-<name>/file to <hash>-<name>-<file>.
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 <<EOF
# Generated file, all changes will be lost on nixos-rebuild!
# Change this to e.g. nixos-42 to temporarily boot to an older configuration.
DEFAULT nixos-default
MENU TITLE ------------------------------------------------------------
TIMEOUT $timeout
EOF
addEntry $default default >> $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

View file

@ -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 <path-to-default-configuration>`
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.
'';
}
];
};
}

View file

@ -1,15 +1,20 @@
{ pinned, core-overlay, libcamera-overlay }: { pinned, core-overlay, libcamera-overlay }:
{ lib, pkgs, config, ... }: { lib, pkgs, config, ... }:
with lib;
let let
cfg = config.raspberry-pi-nix; cfg = config.raspberry-pi-nix;
version = cfg.kernel-version; version = cfg.kernel-version;
board = cfg.board; board = cfg.board;
kernel = config.system.build.kernel; atomicCopySafe = import ../atomic-copy/atomic-copy-safe.nix { inherit pkgs; };
initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; 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 in
{ {
imports = [ ./config.nix ./i2c.nix ]; imports = [ ../generic-extlinux-compatible ./config.nix ./i2c.nix ];
options = with lib; { options = with lib; {
raspberry-pi-nix = { raspberry-pi-nix = {
@ -77,153 +82,16 @@ in
package = mkPackageOption pkgs "uboot-rpi-arm64" { }; 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 = { 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: # Default config.txt on Raspberry Pi OS:
# https://github.com/RPi-Distro/pi-gen/blob/master/stage1/00-boot-files/files/config.txt # https://github.com/RPi-Distro/pi-gen/blob/master/stage1/00-boot-files/files/config.txt
hardware.raspberry-pi.config = { hardware.raspberry-pi.config = {
@ -245,11 +113,9 @@ in
}; };
all = { all = {
options = { options = {
# The firmware will start our u-boot binary rather than a
# linux kernel.
kernel = { kernel = {
enable = true; 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 = { ramfsfile = {
enable = !cfg.uboot.enable; enable = !cfg.uboot.enable;
@ -318,13 +184,15 @@ in
}; };
boot = { boot = {
kernelParams = kernelParams =
if cfg.uboot.enable then [ ] [ "console=serial0,115200n8" "console=tty1" ] ++
(if cfg.uboot.enable then [ ]
else [ else [
"console=tty1" "root=PARTUUID=${cfg.rootGPUID}"
# https://github.com/raspberrypi/firmware/issues/1539#issuecomment-784498108 "rootfstype=ext4"
"console=serial0,115200n8" "fsck.repair=yes"
"init=/sbin/init" "rootwait"
]; "init=/nix/var/nix/profiles/system/init"
]);
initrd = { initrd = {
availableKernelModules = [ availableKernelModules = [
"usbhid" "usbhid"
@ -337,12 +205,43 @@ in
kernelPackages = pkgs.linuxPackagesFor pkgs.rpi-kernels."${version}"."${board}"; kernelPackages = pkgs.linuxPackagesFor pkgs.rpi-kernels."${version}"."${board}";
loader = { loader = {
grub.enable = lib.mkDefault false; grub.enable = lib.mkDefault false;
initScript.enable = !cfg.uboot.enable;
generic-extlinux-compatible = { generic-extlinux-compatible.enable = false;
enable = lib.mkDefault cfg.uboot.enable; 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 # We want to use the device tree provided by firmware, so don't
# add FDTDIR to the extlinux conf file. # add FDTDIR to the extlinux conf file.
useGenerationDeviceTree = false; 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)) ];
}; };
}; };
}; };