Compare commits

..

No commits in common. "master" and "v0.1.0" have entirely different histories.

16 changed files with 505 additions and 1108 deletions

View file

@ -1,23 +0,0 @@
name: update-flake-lock
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0'
permissions:
contents: write
pull-requests: write
jobs:
lockfile:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Install nix
uses: DeterminateSystems/nix-installer-action@e50d5f73bfe71c2dd0aa4218de8f4afa59f8f81d # v16
- name: Update flake.lock
uses: DeterminateSystems/update-flake-lock@a2bbe0274e3a0c4194390a1e445f734c597ebc37 # v24

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Travis Staton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

146
README.md
View file

@ -17,29 +17,64 @@ The important modules are `overlay/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.
## Stability note
`master` is the development branch -- if you want to avoid breaking changes, you
should pin your flake to a specific release and refer to the release notes when
upgrading.
## Example ## Example
See the `rpi-example` config in this flake for an example config built by CI. ```nix
{
description = "raspberry-pi-nix example";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11";
raspberry-pi-nix.url = "github:tstat/raspberry-pi-nix";
};
## Using the provided cache to avoid compiling linux outputs = { self, nixpkgs, raspberry-pi-nix }:
This repo uses the raspberry pi linux kernel fork, and compiling linux takes a let
while. CI pushes kernel builds to the nix-community cachix cache that you may inherit (nixpkgs.lib) nixosSystem;
use to avoid compiling linux yourself. The cache can be found at basic-config = { pkgs, lib, ... }: {
https://nix-community.cachix.org, and you can follow the instructions there time.timeZone = "America/New_York";
to use this cache. users.users.root.initialPassword = "root";
networking = {
hostName = "basic-example";
useDHCP = false;
interfaces = { wlan0.useDHCP = true; };
};
environment.systemPackages = with pkgs; [ bluez bluez-tools ];
hardware = {
bluetooth.enable = true;
raspberry-pi = {
config = {
all = {
base-dt-params = {
# enable autoprobing of bluetooth driver
# https://github.com/raspberrypi/linux/blob/c8c99191e1419062ac8b668956d19e788865912a/arch/arm/boot/dts/overlays/README#L222-L224
krnbt = {
enable = true;
value = "on";
};
};
};
};
};
};
};
in {
nixosConfigurations = {
rpi-example = nixosSystem {
system = "aarch64-linux";
modules = [ raspberry-pi-nix.nixosModules.raspberry-pi basic-config ];
};
};
};
}
```
## Building an sd-card image ## Building an sd-card image
Include the provided `sd-image` nixos module this flake provides, then an image An image suitable for flashing to an sd-card can be found at the
suitable for flashing to an sd-card can be found at the attribute attribute `config.system.build.sdImage`. For example, if you wanted to
`config.system.build.sdImage`. For example, if you wanted to build an image for build an image for `rpi-example` in the above configuration
`rpi-example` in the above configuration example you could run: example you could run:
``` ```
nix build '.#nixosConfigurations.rpi-example.config.system.build.sdImage' nix build '.#nixosConfigurations.rpi-example.config.system.build.sdImage'
@ -47,21 +82,21 @@ nix build '.#nixosConfigurations.rpi-example.config.system.build.sdImage'
## The firmware partition ## The firmware partition
The image produced by this package is partitioned in the same way as the aarch64 The image produced by this package is partitioned in the same way as
installation media from nixpkgs: There is a firmware partition that contains the aarch64 installation media from nixpkgs: There is a firmware
necessary firmware, the kernel or u-boot, and config.txt. Then there is another partition that contains necessary firmware, u-boot, and
partition (labeled `NIXOS_SD`) that contains everything else. The firmware and config.txt. Then there is another partition (labeled `NIXOS_SD`) that
`config.txt` file are managed by NixOS modules defined in this contains everything else. The firmware and `config.txt` file are
package. Additionally, a systemd service will update the firmware and managed by NixOS modules defined in this package. Additionally, NixOS
`config.txt` in the firmware partition __in place__. If uboot is enabled then system activation will update the firmware and `config.txt` in the
linux kernels are stored in the `NIXOS_SD` partition and will be booted by firmware partition __in place__. Linux kernels are stored in the
u-boot in the firmware partition. `NIXOS_SD` partition and will be booted by u-boot in the firmware
partition.
## `config.txt` generation ## `config.txt` generation
As noted, the `config.txt` file is generated by the NixOS As noted, the `config.txt` file is generated by the NixOS
configuration and automatically updated on when the nix configuration configuration and automatically updated on system activation.
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
@ -190,36 +225,37 @@ nix build '.#nixosConfigurations.rpi-example.config.hardware.raspberry-pi.config
## Firmware partition implementation notes ## Firmware partition implementation notes
In Raspberry Pi devices the proprietary firmware manipulates the device tree in In Raspberry Pi devices the proprietary firmware manipulates the
a number of ways before handing it off to the kernel (or in our case, to device tree in a number of ways before handing it off to the kernel
u-boot). The transformations that are performed aren't documented so well (or in our case, to u-boot). The transformations that are performed
(although I have found [this aren't documented so well (although I have found [this
list](https://forums.raspberrypi.com/viewtopic.php?t=329799#p1974233) ). list](https://forums.raspberrypi.com/viewtopic.php?t=329799#p1974233)
).
This manipulation makes it difficult to use the device tree configured directly This manipulation makes it difficult to use the device tree configured
by NixOS as the proprietary firmware's manipulation must be known and directly by NixOS as the proprietary firmware's manipulation must be
reproduced. known and reproduced.
Even if the manipulation were successfully reproduced, some benefits would be Even if the manipulation were successfully reproduced, some benefits
lost. For example, the firmware can detect connected hardware during boot and would be lost. For example, the firmware can detect connected hardware
automatically configure the device tree accordingly before passing it onto the during boot and automatically configure the device tree accordingly
kernel. If this firmware device tree is ignored then a NixOS system rebuild with before passing it onto the kernel. If this firmware device tree is
a different device tree would be required when swapping connected ignored then a NixOS system rebuild with a different device tree would
hardware. Examples of what I mean by hardware include: the specific Raspberry Pi be required when swapping connected hardware. Examples of what I mean
device booting the image, connected cameras, and connected displays. by hardware include: the specific Raspberry Pi device booting the
image, connected cameras, and connected displays.
So, in order to avoid the headaches associated with failing to reproduce some So, in order to avoid the headaches associated with failing to
firmware device tree manipulation, and to reap the benefits afforded by the reproduce some firmware device tree manipulation, and to reap the
firmware device tree configuration, the bootloader is configured to use the benefits afforded by the firmware device tree configuration, u-boot is
device tree that it is given (i.e. the one that the raspberry pi firmware loads configured to use the device tree that it is given (i.e. the one that
and manipulates). As a consequence, device tree configuration is controlled via the raspberry pi firmware loads and manipulates). As a consequence,
the [config.txt device tree configuration is controlled via the [config.txt
file](https://www.raspberrypi.com/documentation/computers/config_txt.html). file](https://www.raspberrypi.com/documentation/computers/config_txt.html).
Additionally, the firmware, device trees, and overlays from the `raspberrypifw` Additionally, the firmware, device trees, and overlays from the
package populate the firmware partition. This package is kept up to date by the `raspberrypifw` package populate the firmware partition on system
overlay applied by this package, so you don't need configure this. However, if activation. This package is kept up to date by the overlay applied by
you want to use different firmware you can override that package to do so. this package, so you don't need configure this. However, if you want
to use different firmware you can override that package to do so.
## What's not working?
- [ ] Pi 5 u-boot devices other than sd-cards (i.e. usb, nvme).

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

138
flake.lock generated
View file

@ -1,82 +1,45 @@
{ {
"nodes": { "nodes": {
"libcamera-src": { "libcamera-apps-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1725630279, "lastModified": 1674645888,
"narHash": "sha256-KH30jmHfxXq4j2CL7kv18DYECJRp9ECuWNPnqPZajPA=", "narHash": "sha256-UBTDHN0lMj02enB8im4Q+f/MCm/G2mFPP3pLImrZc5A=",
"owner": "raspberrypi", "owner": "raspberrypi",
"repo": "libcamera", "repo": "libcamera-apps",
"rev": "69a894c4adad524d3063dd027f5c4774485cf9db", "rev": "9f08463997b82c4bf60e12c4ea43577959a8ae15",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "raspberrypi", "owner": "raspberrypi",
"repo": "libcamera", "ref": "v1.1.1",
"rev": "69a894c4adad524d3063dd027f5c4774485cf9db", "repo": "libcamera-apps",
"type": "github"
}
},
"libpisp-src": {
"flake": false,
"locked": {
"lastModified": 1724944683,
"narHash": "sha256-Fo2UJmQHS855YSSKKmGrsQnJzXog1cdpkIOO72yYAM4=",
"owner": "raspberrypi",
"repo": "libpisp",
"rev": "28196ed6edcfeda88d23cc5f213d51aa6fa17bb3",
"type": "github"
},
"original": {
"owner": "raspberrypi",
"ref": "v1.0.7",
"repo": "libpisp",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1736061677,
"narHash": "sha256-DjkQPnkAfd7eB522PwnkGhOMuT9QVCZspDpJJYyOj60=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "cbd8ec4de4469333c82ff40d057350c30e9f7d36",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github" "type": "github"
} }
}, },
"root": { "root": {
"inputs": { "inputs": {
"libcamera-src": "libcamera-src", "libcamera-apps-src": "libcamera-apps-src",
"libpisp-src": "libpisp-src",
"nixpkgs": "nixpkgs",
"rpi-bluez-firmware-src": "rpi-bluez-firmware-src", "rpi-bluez-firmware-src": "rpi-bluez-firmware-src",
"rpi-firmware-nonfree-src": "rpi-firmware-nonfree-src", "rpi-firmware-nonfree-src": "rpi-firmware-nonfree-src",
"rpi-firmware-src": "rpi-firmware-src", "rpi-firmware-stable-src": "rpi-firmware-stable-src",
"rpi-linux-6_12_17-src": "rpi-linux-6_12_17-src", "rpi-linux-5_15-src": "rpi-linux-5_15-src",
"rpi-linux-6_6_78-src": "rpi-linux-6_6_78-src", "rpi-linux-5_15_87-src": "rpi-linux-5_15_87-src",
"rpi-linux-stable-src": "rpi-linux-stable-src", "u-boot-src": "u-boot-src"
"rpicam-apps-src": "rpicam-apps-src"
} }
}, },
"rpi-bluez-firmware-src": { "rpi-bluez-firmware-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1708969706, "lastModified": 1672928175,
"narHash": "sha256-KakKnOBeWxh0exu44beZ7cbr5ni4RA9vkWYb9sGMb8Q=", "narHash": "sha256-gKGK0XzNrws5REkKg/JP6SZx3KsJduu53SfH3Dichkc=",
"owner": "RPi-Distro", "owner": "RPi-Distro",
"repo": "bluez-firmware", "repo": "bluez-firmware",
"rev": "78d6a07730e2d20c035899521ab67726dc028e1c", "rev": "9556b08ace2a1735127894642cc8ea6529c04c90",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "RPi-Distro", "owner": "RPi-Distro",
"ref": "bookworm",
"repo": "bluez-firmware", "repo": "bluez-firmware",
"type": "github" "type": "github"
} }
@ -84,103 +47,80 @@
"rpi-firmware-nonfree-src": { "rpi-firmware-nonfree-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1723266537, "lastModified": 1674638139,
"narHash": "sha256-T7eTKXqY9cxEMdab8Snda4CEOrEihy5uOhA6Fy+Mhnw=", "narHash": "sha256-54JKmwypD7PRQdd7k6IcF7wL8ifMavEM0UwZwmA24O4=",
"owner": "RPi-Distro", "owner": "RPi-Distro",
"repo": "firmware-nonfree", "repo": "firmware-nonfree",
"rev": "4b356e134e8333d073bd3802d767a825adec3807", "rev": "7f29411baead874b859eda53efdc2472345ea454",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "RPi-Distro", "owner": "RPi-Distro",
"ref": "bookworm",
"repo": "firmware-nonfree", "repo": "firmware-nonfree",
"type": "github" "type": "github"
} }
}, },
"rpi-firmware-src": { "rpi-firmware-stable-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1728405098, "lastModified": 1673003776,
"narHash": "sha256-4gnK0KbqFnjBmWia9Jt2gveVWftmHrprpwBqYVqE/k0=", "narHash": "sha256-tdaH+zZwmILNFBge2gMqtzj/1Hydj9cxhPvhw+7jTrU=",
"owner": "raspberrypi", "owner": "raspberrypi",
"repo": "firmware", "repo": "firmware",
"rev": "7bbb5f80d20a2335066a8781459c9f33e5eebc64", "rev": "78852e166b4cf3ebb31d051e996d54792f0994b0",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "raspberrypi", "owner": "raspberrypi",
"ref": "1.20241008", "ref": "stable",
"repo": "firmware", "repo": "firmware",
"type": "github" "type": "github"
} }
}, },
"rpi-linux-6_12_17-src": { "rpi-linux-5_15-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1740765145, "lastModified": 1675874870,
"narHash": "sha256-hoCsGc4+RC/2LmxDtswLBL5ZhWlw4vSiL4Vkl39r2MU=", "narHash": "sha256-oy+VgoB4IdFZjGwkx88dDSpwWZj2D5t3PyXPIwDsY1Q=",
"owner": "raspberrypi", "owner": "raspberrypi",
"repo": "linux", "repo": "linux",
"rev": "5985ce32e511f4e8279a841a1b06a8c7d972b386", "rev": "14b35093ca68bf2c81bbc90aace5007142b40b40",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "raspberrypi", "owner": "raspberrypi",
"ref": "rpi-6.12.y", "ref": "rpi-5.15.y",
"repo": "linux", "repo": "linux",
"type": "github" "type": "github"
} }
}, },
"rpi-linux-6_6_78-src": { "rpi-linux-5_15_87-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1740503700, "lastModified": 1673628667,
"narHash": "sha256-Y8+ot4Yi3UKwlZK3ap15rZZ16VZDvmeFkD46+6Ku7bE=", "narHash": "sha256-hNLVfhalmRhhRfvu2mR/qDmmGl//Ic1eqR7N1HFj2CY=",
"owner": "raspberrypi", "owner": "raspberrypi",
"repo": "linux", "repo": "linux",
"rev": "2e071057fded90e789c0101498e45a1778be93fe", "rev": "da4c8e0ffe7a868b989211045657d600be3046a1",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "raspberrypi", "owner": "raspberrypi",
"ref": "rpi-6.6.y",
"repo": "linux", "repo": "linux",
"rev": "da4c8e0ffe7a868b989211045657d600be3046a1",
"type": "github" "type": "github"
} }
}, },
"rpi-linux-stable-src": { "u-boot-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1728403745, "narHash": "sha256-30fe8klLHRsEtEQ1VpYh4S+AflG5yCQYWlGmpWyFL8w=",
"narHash": "sha256-phCxkuO+jUGZkfzSrBq6yErQeO2Td+inIGHxctXbD5U=", "type": "tarball",
"owner": "raspberrypi", "url": "https://ftp.denx.de/pub/u-boot/u-boot-2023.01.tar.bz2"
"repo": "linux",
"rev": "5aeecea9f4a45248bcf564dec924965e066a7bfd",
"type": "github"
}, },
"original": { "original": {
"owner": "raspberrypi", "type": "tarball",
"ref": "stable_20241008", "url": "https://ftp.denx.de/pub/u-boot/u-boot-2023.01.tar.bz2"
"repo": "linux",
"type": "github"
}
},
"rpicam-apps-src": {
"flake": false,
"locked": {
"lastModified": 1727515047,
"narHash": "sha256-qCYGrcibOeGztxf+sd44lD6VAOGoUNwRqZDdAmcTa/U=",
"owner": "raspberrypi",
"repo": "rpicam-apps",
"rev": "a8ccf9f3cd9df49875dfb834a2b490d41d226031",
"type": "github"
},
"original": {
"owner": "raspberrypi",
"ref": "v1.5.2",
"repo": "rpicam-apps",
"type": "github"
} }
} }
}, },

View file

@ -2,90 +2,34 @@
description = "raspberry-pi nixos configuration"; description = "raspberry-pi nixos configuration";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; u-boot-src = {
rpi-linux-stable-src = {
flake = false; flake = false;
url = "github:raspberrypi/linux/stable_20241008"; url = "https://ftp.denx.de/pub/u-boot/u-boot-2023.01.tar.bz2";
}; };
rpi-linux-6_6_78-src = { rpi-linux-5_15-src = {
flake = false; flake = false;
url = "github:raspberrypi/linux/rpi-6.6.y"; url = "github:raspberrypi/linux/rpi-5.15.y";
}; };
rpi-linux-6_12_17-src = { rpi-firmware-stable-src = {
flake = false; flake = false;
url = "github:raspberrypi/linux/rpi-6.12.y"; url = "github:raspberrypi/firmware/stable";
};
rpi-firmware-src = {
flake = false;
url = "github:raspberrypi/firmware/1.20241008";
}; };
rpi-firmware-nonfree-src = { rpi-firmware-nonfree-src = {
flake = false; flake = false;
url = "github:RPi-Distro/firmware-nonfree/bookworm"; url = "github:RPi-Distro/firmware-nonfree";
}; };
rpi-bluez-firmware-src = { rpi-bluez-firmware-src = {
flake = false; flake = false;
url = "github:RPi-Distro/bluez-firmware/bookworm"; url = "github:RPi-Distro/bluez-firmware";
}; };
rpicam-apps-src = { libcamera-apps-src = {
flake = false; flake = false;
url = "github:raspberrypi/rpicam-apps/v1.5.2"; url = "github:raspberrypi/libcamera-apps/v1.1.1";
};
libcamera-src = {
flake = false;
url = "github:raspberrypi/libcamera/69a894c4adad524d3063dd027f5c4774485cf9db"; # v0.3.1+rpt20240906
};
libpisp-src = {
flake = false;
url = "github:raspberrypi/libpisp/v1.0.7";
}; };
}; };
outputs = srcs@{ self, ... }: outputs = srcs@{ self, ... }: {
let overlay = import ./overlay (builtins.removeAttrs srcs [ "self" ]);
pinned = import srcs.nixpkgs { nixosModules.raspberry-pi = import ./rpi { overlay = self.overlay; };
system = "aarch64-linux"; };
overlays = with self.overlays; [ core libcamera ];
};
in
{
overlays = {
core = import ./overlays (builtins.removeAttrs srcs [ "self" ]);
libcamera = import ./overlays/libcamera.nix (builtins.removeAttrs srcs [ "self" ]);
};
nixosModules = {
raspberry-pi = import ./rpi {
inherit pinned;
core-overlay = self.overlays.core;
libcamera-overlay = self.overlays.libcamera;
};
sd-image = import ./sd-image;
};
nixosConfigurations = {
rpi-example = srcs.nixpkgs.lib.nixosSystem {
system = "aarch64-linux";
modules = [ self.nixosModules.raspberry-pi self.nixosModules.sd-image ./example ];
};
};
checks.aarch64-linux = self.packages.aarch64-linux;
packages.aarch64-linux = with pinned.lib;
let
kernels =
foldlAttrs f { } pinned.rpi-kernels;
f = acc: kernel-version: board-attr-set:
foldlAttrs
(acc: board-version: drv: acc // {
"linux-${kernel-version}-${board-version}" = drv;
})
acc
board-attr-set;
in
{
example-sd-image = self.nixosConfigurations.rpi-example.config.system.build.sdImage;
firmware = pinned.raspberrypifw;
libcamera = pinned.libcamera;
wireless-firmware = pinned.raspberrypiWirelessFirmware;
uboot-rpi-arm64 = pinned.uboot-rpi-arm64;
} // kernels;
};
} }

82
overlay/default.nix Normal file
View file

@ -0,0 +1,82 @@
{ u-boot-src, rpi-linux-5_15-src, rpi-firmware-stable-src
, rpi-firmware-nonfree-src, rpi-bluez-firmware-src, libcamera-apps-src }:
final: prev:
let
# The version to stick at `pkgs.rpi-kernels.latest'
latest = "v5_15_92";
# Helpers for building the `pkgs.rpi-kernels' map.
rpi-kernel = { kernel, version, fw, wireless-fw, argsOverride ? null }:
let
new-kernel = prev.linux_rpi4.override {
argsOverride = {
src = kernel;
inherit version;
modDirVersion = version;
} // (if builtins.isNull argsOverride then { } else argsOverride);
};
new-fw = prev.raspberrypifw.overrideAttrs (oldfw: { src = fw; });
new-wireless-fw = final.callPackage wireless-fw { };
version-slug = builtins.replaceStrings [ "." ] [ "_" ] version;
in {
"v${version-slug}" = {
kernel = new-kernel;
firmware = new-fw;
wireless-firmware = new-wireless-fw;
};
};
rpi-kernels = builtins.foldl' (b: a: b // rpi-kernel a) { };
in {
# disable firmware compression so that brcm firmware can be found at
# the path expected by raspberry pi firmware/device tree
compressFirmwareXz = x: x;
# A recent known working version of libcamera-apps
libcamera-apps =
final.callPackage ./libcamera-apps.nix { inherit libcamera-apps-src; };
# provide generic rpi arm64 u-boot
uboot_rpi_arm64 = prev.buildUBoot rec {
defconfig = "rpi_arm64_defconfig";
extraMeta.platforms = [ "aarch64-linux" ];
filesToInstall = [ "u-boot.bin" ];
version = "2023.01";
src = u-boot-src;
# In raspberry pi sbcs the firmware manipulates the device tree in
# a variety of ways before handing it off to the linux kernel. [1]
# Since we have installed u-boot in place of a linux kernel we may
# pass the device tree passed by the firmware onto the kernel, or
# we may provide the kernel with a device tree of our own. This
# configuration uses the device tree provided by firmware so that
# we don't have to be aware of all manipulation done by the
# firmware and attempt to mimic it.
#
# 1. https://forums.raspberrypi.com/viewtopic.php?t=329799#p1974233
extraConfig = ''
CONFIG_OF_HAS_PRIOR_STAGE=y
CONFIG_OF_BOARD=y
'';
};
# default to latest firmware
raspberrypiWirelessFirmware = final.rpi-kernels.latest.wireless-firmware;
raspberrypifw = final.rpi-kernels.latest.firmware;
} // {
# rpi kernels and firmware are available at
# `pkgs.rpi-kernels.<VERSION>.{kernel,firmware,wireless-firmware}'.
#
# For example: `pkgs.rpi-kernels.v5_15_87.kernel'
rpi-kernels = rpi-kernels [{
version = "5.15.92";
kernel = rpi-linux-5_15-src;
fw = rpi-firmware-stable-src;
wireless-fw = import ./raspberrypi-wireless-firmware.nix {
bluez-firmware = rpi-bluez-firmware-src;
firmware-nonfree = rpi-firmware-nonfree-src;
};
}] // {
latest = final.rpi-kernels."${latest}";
};
}

View file

@ -0,0 +1,27 @@
{ libcamera-apps-src, lib, stdenv, fetchFromGitHub, fetchpatch, cmake
, pkg-config, libjpeg, libtiff, libpng, libcamera, libepoxy, boost, libexif }:
stdenv.mkDerivation rec {
pname = "libcamera-apps";
version = "v1.1.0";
src = libcamera-apps-src;
nativeBuildInputs = [ cmake pkg-config ];
buildInputs = [ libjpeg libtiff libcamera libepoxy boost libexif libpng ];
cmakeFlags = [
"-DENABLE_QT=0"
"-DENABLE_OPENCV=0"
"-DENABLE_TFLITE=0"
"-DENABLE_X11=1"
"-DENABLE_DRM=1"
(if (stdenv.hostPlatform.isAarch64) then "-DARM64=ON" else "-DARM64=OFF")
];
meta = with lib; {
description = "Userland tools interfacing with Raspberry Pi cameras";
homepage = "https://github.com/raspberrypi/libcamera-apps";
license = licenses.bsd2;
platforms = [ "aarch64-linux" ];
};
}

View file

@ -1,9 +1,9 @@
{ bluez-firmware, firmware-nonfree }: { bluez-firmware, firmware-nonfree }:
{ lib, stdenvNoCC }: { lib, stdenvNoCC, fetchFromGitHub }:
stdenvNoCC.mkDerivation { stdenvNoCC.mkDerivation {
pname = "raspberrypi-wireless-firmware"; pname = "raspberrypi-wireless-firmware";
version = "2024-02-26"; version = "2023-01-19";
srcs = [ ]; srcs = [ ];
@ -23,7 +23,7 @@ stdenvNoCC.mkDerivation {
cp -rv "${firmware-nonfree}/debian/config/brcm80211/." "$out/lib/firmware/" cp -rv "${firmware-nonfree}/debian/config/brcm80211/." "$out/lib/firmware/"
# Bluetooth firmware # Bluetooth firmware
cp -rv "${bluez-firmware}/debian/firmware/broadcom/." "$out/lib/firmware/brcm" cp -rv "${bluez-firmware}/broadcom/." "$out/lib/firmware/brcm"
# brcmfmac43455-stdio.bin is a symlink to ../cypress/cyfmac43455-stdio.bin that doesn't exist # brcmfmac43455-stdio.bin is a symlink to ../cypress/cyfmac43455-stdio.bin that doesn't exist
# See https://github.com/RPi-Distro/firmware-nonfree/issues/26 # See https://github.com/RPi-Distro/firmware-nonfree/issues/26

View file

@ -1,122 +0,0 @@
{ rpi-linux-stable-src
, rpi-linux-6_6_78-src
, rpi-linux-6_12_17-src
, rpi-firmware-src
, rpi-firmware-nonfree-src
, rpi-bluez-firmware-src
, ...
}:
final: prev:
let
versions = {
v6_6_51.src = rpi-linux-stable-src;
v6_6_78.src = rpi-linux-6_6_78-src;
v6_12_17 = {
src = rpi-linux-6_12_17-src;
patches = [
{
name = "remove-readme-target.patch";
patch = final.fetchpatch {
url = "https://github.com/raspberrypi/linux/commit/3c0fd51d184f1748b83d28e1113265425c19bcb5.patch";
hash = "sha256-v7uZOmPCUp2i7NGVgjqnQYe6dEBD+aATuP/oRs9jfuk=";
};
}
];
};
};
boards = [ "bcm2711" "bcm2712" ];
# Helpers for building the `pkgs.rpi-kernels' map.
rpi-kernel = { version, board }:
let
kernel = builtins.getAttr version versions;
version-slug = builtins.replaceStrings [ "v" "_" ] [ "" "." ] version;
in
{
"${version}"."${board}" = (final.buildLinux {
modDirVersion = version-slug;
version = version-slug;
pname = "linux-rpi";
src = kernel.src;
defconfig = "${board}_defconfig";
structuredExtraConfig = with final.lib.kernel; {
# The perl script to generate kernel options sets unspecified
# parameters to `m` if possible [1]. This results in the
# unspecified config option KUNIT [2] getting set to `m` which
# causes DRM_VC4_KUNIT_TEST [3] to get set to `y`.
#
# This vc4 unit test fails on boot due to a null pointer
# exception with the existing config. I'm not sure why, but in
# any case, the DRM_VC4_KUNIT_TEST config option itself states
# that it is only useful for kernel developers working on the
# vc4 driver. So, I feel no need to deviate from the standard
# rpi kernel and attempt to successfully enable this test and
# other unit tests because the nixos perl script has this
# sloppy "default to m" behavior. So, I set KUNIT to `n`.
#
# [1] https://github.com/NixOS/nixpkgs/blob/85bcb95aa83be667e562e781e9d186c57a07d757/pkgs/os-specific/linux/kernel/generate-config.pl#L1-L10
# [2] https://github.com/raspberrypi/linux/blob/1.20230405/lib/kunit/Kconfig#L5-L14
# [3] https://github.com/raspberrypi/linux/blob/bb63dc31e48948bc2649357758c7a152210109c4/drivers/gpu/drm/vc4/Kconfig#L38-L52
KUNIT = no;
};
features.efiBootStub = false;
kernelPatches =
if kernel ? "patches" then kernel.patches else [ ];
ignoreConfigErrors = true;
}).overrideAttrs
(oldAttrs: {
postConfigure = ''
# The v7 defconfig has this set to '-v7' which screws up our modDirVersion.
sed -i $buildRoot/.config -e 's/^CONFIG_LOCALVERSION=.*/CONFIG_LOCALVERSION=""/'
sed -i $buildRoot/include/config/auto.conf -e 's/^CONFIG_LOCALVERSION=.*/CONFIG_LOCALVERSION=""/'
'';
});
};
rpi-kernels = builtins.foldl'
(b: a: final.lib.recursiveUpdate b (rpi-kernel a))
{ };
in
{
# disable firmware compression so that brcm firmware can be found at
# the path expected by raspberry pi firmware/device tree
compressFirmwareXz = x: x;
compressFirmwareZstd = x: x;
# provide generic rpi arm64 u-boot
uboot-rpi-arm64 = final.buildUBoot {
defconfig = "rpi_arm64_defconfig";
extraMeta.platforms = [ "aarch64-linux" ];
filesToInstall = [ "u-boot.bin" ];
# In raspberry pi sbcs the firmware manipulates the device tree in
# a variety of ways before handing it off to the linux kernel. [1]
# Since we have installed u-boot in place of a linux kernel we may
# pass the device tree passed by the firmware onto the kernel, or
# we may provide the kernel with a device tree of our own. This
# configuration uses the device tree provided by firmware so that
# we don't have to be aware of all manipulation done by the
# firmware and attempt to mimic it.
#
# 1. https://forums.raspberrypi.com/viewtopic.php?t=329799#p1974233
};
# default to latest firmware
raspberrypiWirelessFirmware = final.callPackage
(
import ./raspberrypi-wireless-firmware.nix {
bluez-firmware = rpi-bluez-firmware-src;
firmware-nonfree = rpi-firmware-nonfree-src;
}
)
{ };
raspberrypifw = prev.raspberrypifw.overrideAttrs (oldfw: { src = rpi-firmware-src; });
} // {
# rpi kernels and firmware are available at
# `pkgs.rpi-kernels.<VERSION>.<BOARD>'.
#
# For example: `pkgs.rpi-kernels.v6_6_78.bcm2712'
rpi-kernels = rpi-kernels (
final.lib.cartesianProduct
{ board = boards; version = (builtins.attrNames versions); }
);
}

View file

@ -1,57 +0,0 @@
{ rpicam-apps-src
, libcamera-src
, libpisp-src
, ...
}:
final: prev: {
# A recent known working version of rpicam-apps
libcamera-apps =
final.callPackage ./rpicam-apps.nix { inherit rpicam-apps-src; };
libpisp = final.stdenv.mkDerivation {
name = "libpisp";
version = "1.0.7";
src = libpisp-src;
nativeBuildInputs = with final; [ pkg-config meson ninja ];
buildInputs = with final; [ nlohmann_json boost ];
# Meson is no longer able to pick up Boost automatically.
# https://github.com/NixOS/nixpkgs/issues/86131
BOOST_INCLUDEDIR = "${prev.lib.getDev final.boost}/include";
BOOST_LIBRARYDIR = "${prev.lib.getLib final.boost}/lib";
};
libcamera = prev.libcamera.overrideAttrs (old: {
version = "0.3.1";
src = libcamera-src;
buildInputs = old.buildInputs ++ (with final; [
libpisp
openssl
libtiff
(python3.withPackages (ps: with ps; [
python3-gnutls
pybind11
pyyaml
ply
]))
libglibutil
gst_all_1.gst-plugins-base
]);
patches = [ ];
postPatch = ''
patchShebangs src/py/ utils/
'';
mesonFlags = [
"--buildtype=release"
"-Dpipelines=rpi/vc4,rpi/pisp"
"-Dipas=rpi/vc4,rpi/pisp"
"-Dv4l2=true"
"-Dgstreamer=enabled"
"-Dtest=false"
"-Dlc-compliance=disabled"
"-Dcam=disabled"
"-Dqcam=disabled"
"-Ddocumentation=enabled"
"-Dpycamera=enabled"
];
});
}

View file

@ -1,30 +0,0 @@
{ rpicam-apps-src, lib, pkgs, stdenv }:
stdenv.mkDerivation {
pname = "libcamera-apps";
version = "v1.5.0";
src = rpicam-apps-src;
nativeBuildInputs = with pkgs; [ meson pkg-config ];
buildInputs = with pkgs; [ libjpeg libtiff libcamera libepoxy boost libexif libpng ffmpeg libdrm ninja ];
mesonFlags = [
"-Denable_qt=disabled"
"-Denable_opencv=disabled"
"-Denable_tflite=disabled"
"-Denable_egl=disabled"
"-Denable_hailo=disabled"
"-Denable_drm=enabled"
];
# Meson is no longer able to pick up Boost automatically.
# https://github.com/NixOS/nixpkgs/issues/86131
BOOST_INCLUDEDIR = "${lib.getDev pkgs.boost}/include";
BOOST_LIBRARYDIR = "${lib.getLib pkgs.boost}/lib";
meta = with lib; {
description = "Userland tools interfacing with Raspberry Pi cameras";
homepage = "https://github.com/raspberrypi/libcamera-apps";
license = licenses.bsd2;
platforms = [ "aarch64-linux" ];
};
}

View file

@ -1,144 +1,126 @@
{ lib, config, pkgs, ... }: { lib, config, pkgs, ... }:
let let
cfg = config.hardware.raspberry-pi; cfg = config.hardware.raspberry-pi;
render-raspberrypi-config = render-raspberrypi-config = let
let render-options = opts:
render-options = opts: lib.strings.concatStringsSep "\n" (render-dt-kvs opts);
lib.strings.concatStringsSep "\n" (render-dt-kvs opts); render-dt-param = x: "dtparam=" + x;
render-dt-param = x: "dtparam=" + x; render-dt-kv = k: v:
render-dt-kv = k: v: if isNull v.value then
if isNull v.value then k
k else
else let vstr = toString v.value; in "${k}=${vstr}";
let vstr = toString v.value; in "${k}=${vstr}"; render-dt-kvs = x:
render-dt-kvs = x: lib.attrsets.mapAttrsToList render-dt-kv
lib.attrsets.mapAttrsToList render-dt-kv (lib.filterAttrs (k: v: v.enable) x);
(lib.filterAttrs (k: v: v.enable) x); render-dt-overlay = { overlay, args }:
render-dt-overlay = { overlay, args }: "dtoverlay=" + overlay + "\n"
"dtoverlay=" + overlay + "\n" + lib.strings.concatMapStringsSep "\n" render-dt-param args + "\n"
+ lib.strings.concatMapStringsSep "\n" render-dt-param args + "\n" + "dtoverlay=";
+ "dtoverlay="; render-base-dt-params = params:
render-base-dt-params = params: lib.strings.concatMapStringsSep "\n" render-dt-param
lib.strings.concatMapStringsSep "\n" render-dt-param (render-dt-kvs params);
(render-dt-kvs params); render-dt-overlays = overlays:
render-dt-overlays = overlays: lib.strings.concatMapStringsSep "\n" render-dt-overlay
lib.strings.concatMapStringsSep "\n" render-dt-overlay (lib.attrsets.mapAttrsToList (k: v: {
(lib.attrsets.mapAttrsToList overlay = k;
(k: v: { args = render-dt-kvs v.params;
overlay = k; }) (lib.filterAttrs (k: v: v.enable) overlays));
args = render-dt-kvs v.params; render-config-section = k:
}) { options, base-dt-params, dt-overlays }:
(lib.filterAttrs (k: v: v.enable) overlays)); let
render-config-section = k: all-config = lib.concatStringsSep "\n" (lib.filter (x: x != "") [
{ options, base-dt-params, dt-overlays }: (render-options options)
let (render-base-dt-params base-dt-params)
all-config = lib.concatStringsSep "\n" (lib.filter (x: x != "") [ (render-dt-overlays dt-overlays)
(render-options options) ]);
(render-base-dt-params base-dt-params) in ''
(render-dt-overlays dt-overlays) [${k}]
]); ${all-config}
in '';
'' in conf:
[${k}] lib.strings.concatStringsSep "\n"
${all-config} (lib.attrsets.mapAttrsToList render-config-section conf);
''; in {
in
conf:
lib.strings.concatStringsSep "\n"
(lib.attrsets.mapAttrsToList render-config-section conf);
in
{
options = { options = {
hardware.raspberry-pi = { hardware.raspberry-pi = {
config = config = let
let rpi-config-param = {
rpi-config-param = { options = {
options = { enable = lib.mkEnableOption "attr";
enable = lib.mkEnableOption "attr"; value =
value = lib.mkOption { type = with lib.types; oneOf [ int str bool ]; };
lib.mkOption { type = with lib.types; oneOf [ int str bool ]; };
};
}; };
dt-param = {
options = {
enable = lib.mkEnableOption "attr";
value = lib.mkOption {
type = with lib.types; nullOr (oneOf [ int str bool ]);
default = null;
};
};
};
dt-overlay = {
options = {
enable = lib.mkEnableOption "overlay";
params = lib.mkOption {
type = with lib.types; attrsOf (submodule dt-param);
};
};
};
raspberry-pi-config-options = {
options = {
options = lib.mkOption {
type = with lib.types; attrsOf (submodule rpi-config-param);
default = { };
example = {
enable_gic = {
enable = true;
value = true;
};
arm_boost = {
enable = true;
value = true;
};
};
};
base-dt-params = lib.mkOption {
type = with lib.types; attrsOf (submodule dt-param);
default = { };
example = {
i2c = {
enable = true;
value = "on";
};
audio = {
enable = true;
value = "on";
};
};
description = "parameters to pass to the base dtb";
};
dt-overlays = lib.mkOption {
type = with lib.types; attrsOf (submodule dt-overlay);
default = { };
example = { vc4-kms-v3d = { cma-256 = { enable = true; }; }; };
description = "dtb overlays to apply";
};
};
};
in
lib.mkOption {
type = with lib.types; attrsOf (submodule raspberry-pi-config-options);
}; };
dt-param = {
config-generated = lib.mkOption { options = {
type = lib.types.str; enable = lib.mkEnableOption "attr";
description = "the config text generated by raspberrypi.hardware.config"; value = lib.mkOption {
readOnly = true; type = with lib.types; nullOr (oneOf [ int str bool ]);
default = null;
};
};
};
dt-overlay = {
options = {
enable = lib.mkEnableOption "overlay";
params = lib.mkOption {
type = with lib.types; attrsOf (submodule dt-param);
};
};
};
raspberry-pi-config-options = {
options = {
options = lib.mkOption {
type = with lib.types; attrsOf (submodule rpi-config-param);
default = { };
example = {
enable_gic = {
enable = true;
value = true;
};
arm_boost = {
enable = true;
value = true;
};
};
};
base-dt-params = lib.mkOption {
type = with lib.types; attrsOf (submodule rpi-config-param);
default = { };
example = {
i2c = {
enable = true;
value = "on";
};
audio = {
enable = true;
value = "on";
};
};
description = "parameters to pass to the base dtb";
};
dt-overlays = lib.mkOption {
type = with lib.types; attrsOf (submodule dt-overlay);
default = { };
example = { vc4-kms-v3d = { cma-256 = { enable = true; }; }; };
description = "dtb overlays to apply";
};
};
};
in lib.mkOption {
type = with lib.types; attrsOf (submodule raspberry-pi-config-options);
}; };
config-output = lib.mkOption { config-output = lib.mkOption {
type = lib.types.package; type = lib.types.package;
default = pkgs.writeTextFile { default = pkgs.writeTextFile {
name = "config.txt"; name = "config.txt";
text = '' text = ''
# This is a generated file. Do not edit! # This is a generated file. Do not edit!
${cfg.config-generated} ${render-raspberrypi-config cfg.config}
''; '';
}; };
}; };
}; };
}; };
config = {
hardware.raspberry-pi.config-generated = render-raspberrypi-config cfg.config;
};
} }

View file

@ -1,418 +1,137 @@
{ pinned, core-overlay, libcamera-overlay }: { overlay }:
{ lib, pkgs, config, ... }: { lib, pkgs, config, ... }:
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}";
in
{ {
imports = [ ./config.nix ./i2c.nix ]; imports = [ ../sd-image ./config.nix ./i2c.nix ];
options = with lib; { # On activation install u-boot, Raspberry Pi firmware, and our
raspberry-pi-nix = { # generated config.txt
kernel-version = mkOption { system.activationScripts.raspberrypi = {
default = "v6_6_51"; text = ''
type = types.str; shopt -s nullglob
description = "Kernel version to build.";
}; TARGET_FIRMWARE_DIR="/boot/firmware"
board = mkOption { TARGET_OVERLAYS_DIR="$TARGET_FIRMWARE_DIR/overlays"
type = types.enum [ "bcm2711" "bcm2712" ]; TMPFILE="$TARGET_FIRMWARE_DIR/tmp"
description = '' UBOOT="${pkgs.uboot_rpi_arm64}/u-boot.bin"
The kernel board version to build. SRC_FIRMWARE_DIR="${pkgs.raspberrypifw}/share/raspberrypi/boot"
Examples at: https://www.raspberrypi.com/documentation/computers/linux_kernel.html#native-build-configuration STARTFILES=("$SRC_FIRMWARE_DIR"/start*.elf)
without the _defconfig part. DTBS=("$SRC_FIRMWARE_DIR"/*.dtb)
''; BOOTCODE="$SRC_FIRMWARE_DIR/bootcode.bin"
}; FIXUPS=("$SRC_FIRMWARE_DIR"/fixup*.dat)
firmware-partition-label = mkOption { SRC_OVERLAYS_DIR="$SRC_FIRMWARE_DIR/overlays"
default = "FIRMWARE"; SRC_OVERLAYS=("$SRC_OVERLAYS_DIR"/*)
type = types.str; CONFIG="${config.hardware.raspberry-pi.config-output}"
description = "label of rpi firmware partition";
}; cp "$UBOOT" "$TMPFILE"
pin-inputs = { mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/u-boot-rpi-arm64.bin"
enable = mkOption {
default = true; cp "$CONFIG" "$TMPFILE"
type = types.bool; mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/config.txt"
description = ''
Whether to pin the kernel to the latest cachix build. for SRC in "''${STARTFILES[@]}" "''${DTBS[@]}" "$BOOTCODE" "''${FIXUPS[@]}"
''; do
cp "$SRC" "$TMPFILE"
mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/$(basename "$SRC")"
done
for SRC in "''${SRC_OVERLAYS[@]}"
do
cp "$SRC" "$TMPFILE"
mv -T "$TMPFILE" "$TARGET_OVERLAYS_DIR/$(basename "$SRC")"
done
'';
};
# 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 = {
cm4 = {
options = {
otg_mode = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
}; };
}; };
firmware-migration-service = { };
enable = mkOption { pi4 = {
default = true; options = {
type = types.bool; arm_boost = {
description = '' enable = lib.mkDefault true;
Whether to run the migration service automatically or not. value = lib.mkDefault true;
'';
}; };
}; };
libcamera-overlay = { };
enable = mkOption { all = {
default = true; options = {
type = types.bool; # The firmware will start our u-boot binary rather than a
description = '' # linux kernel.
If enabled then the libcamera overlay is applied which kernel = {
overrides libcamera with the rpi fork. enable = true;
''; value = "u-boot-rpi-arm64.bin";
};
arm_64bit = {
enable = true;
value = true;
};
enable_uart = {
enable = true;
value = true;
};
avoid_warnings = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
camera_auto_detect = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
display_auto_detect = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
disable_overscan = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
}; };
}; };
uboot = { dt-overlays = {
enable = mkOption { vc4-kms-v3d = {
default = false; enable = lib.mkDefault true;
type = types.bool; params = { };
description = ''
If enabled then uboot is used as the bootloader. If disabled
then the linux kernel is installed directly into the
firmware directory as expected by the raspberry pi boot
process.
This can be useful for newer hardware that doesn't yet have
uboot compatibility or less common setups, like booting a
cm4 with an nvme drive.
'';
};
package = mkPackageOption pkgs "uboot-rpi-arm64" { };
};
serial-console = {
enable = mkOption {
default = true;
type = types.bool;
description = ''
Whether to enable a console on serial0.
Corresponds with raspi-config's setting
"Would you like a login shell to be accessible over serial?"
'';
}; };
}; };
}; };
}; };
config = { nixpkgs = { overlays = [ overlay ]; };
systemd.services = { boot = {
"raspberry-pi-firmware-migrate" = initrd.availableKernelModules = [
{ "usbhid"
description = "update the firmware partition"; "usb_storage"
wantedBy = if cfg.firmware-migration-service.enable then [ "multi-user.target" ] else [ ]; "vc4"
serviceConfig = "pcie_brcmstb" # required for the pcie bus to work
let "reset-raspberrypi" # required for vl805 firmware to load
firmware-path = "/boot/firmware"; ];
kernel-params = pkgs.writeTextFile { kernelPackages = pkgs.linuxPackagesFor (pkgs.rpi-kernels.latest.kernel);
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}" loader = {
TARGET_OVERLAYS_DIR="$TARGET_FIRMWARE_DIR/overlays" grub.enable = lib.mkDefault false;
TMPFILE="$TARGET_FIRMWARE_DIR/tmp" generic-extlinux-compatible = {
KERNEL="${kernel}/${config.system.boot.loader.kernelFile}" enable = lib.mkDefault true;
SHOULD_UBOOT=${if cfg.uboot.enable then "1" else "0"} # We want to use the device tree provided by firmware, so don't
SRC_FIRMWARE_DIR="${pkgs.raspberrypifw}/share/raspberrypi/boot" # add FDTDIR to the extlinux conf file.
STARTFILES=("$SRC_FIRMWARE_DIR"/start*.elf) useGenerationDeviceTree = false;
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 = {
cm4 = {
options = {
otg_mode = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
};
}; };
pi4 = {
options = {
arm_boost = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
};
};
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";
};
ramfsfile = {
enable = !cfg.uboot.enable;
value = "initrd";
};
ramfsaddr = {
enable = !cfg.uboot.enable;
value = -1;
};
arm_64bit = {
enable = true;
value = true;
};
enable_uart = {
enable = true;
value = true;
};
avoid_warnings = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
camera_auto_detect = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
display_auto_detect = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
disable_overscan = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
};
dt-overlays = {
vc4-kms-v3d = {
enable = lib.mkDefault true;
params = { };
};
};
};
};
nixpkgs = {
overlays =
let
rpi-overlays = [ core-overlay ]
++ (if config.raspberry-pi-nix.libcamera-overlay.enable
then [ libcamera-overlay ] else [ ]);
rpi-overlay = lib.composeManyExtensions rpi-overlays;
pin-prev-overlay = overlay: pinned-prev: final: prev:
let
# apply the overlay to pinned-prev and fix that so no references to the actual final
# and prev appear in applied-overlay
applied-overlay =
lib.fix (final: pinned-prev // overlay final pinned-prev);
# We only want to set keys that appear in the overlay, so restrict applied-overlay to
# these keys
restricted-overlay = lib.getAttrs (builtins.attrNames (overlay { } { })) applied-overlay;
in
prev // restricted-overlay;
in
if cfg.pin-inputs.enable
then [ (pin-prev-overlay rpi-overlay pinned) ]
else [ rpi-overlay ];
};
boot = {
kernelParams =
if cfg.uboot.enable then [ ]
else builtins.concatLists [
[ "console=tty1" ]
(if cfg.serial-console.enable then [
# https://github.com/raspberrypi/firmware/issues/1539#issuecomment-784498108
"console=serial0,115200n8"
] else [ ]
)
[ "init=/sbin/init" ]
];
initrd = {
availableKernelModules = [
"usbhid"
"usb_storage"
"vc4"
"pcie_brcmstb" # required for the pcie bus to work
"reset-raspberrypi" # required for vl805 firmware to load
];
};
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;
# We want to use the device tree provided by firmware, so don't
# add FDTDIR to the extlinux conf file.
useGenerationDeviceTree = false;
};
};
};
hardware.enableRedistributableFirmware = true;
users.groups = builtins.listToAttrs (map (k: { name = k; value = { }; })
[ "input" "sudo" "plugdev" "games" "netdev" "gpio" "i2c" "spi" ]);
services = {
udev.extraRules =
let shell = "${pkgs.bash}/bin/bash";
in ''
# https://raw.githubusercontent.com/RPi-Distro/raspberrypi-sys-mods/master/etc.armhf/udev/rules.d/99-com.rules
SUBSYSTEM=="input", GROUP="input", MODE="0660"
SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660"
SUBSYSTEM=="spidev", GROUP="spi", MODE="0660"
SUBSYSTEM=="*gpiomem*", GROUP="gpio", MODE="0660"
SUBSYSTEM=="rpivid-*", GROUP="video", MODE="0660"
KERNEL=="vcsm-cma", GROUP="video", MODE="0660"
SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660"
SUBSYSTEM=="gpio", GROUP="gpio", MODE="0660"
SUBSYSTEM=="gpio", KERNEL=="gpiochip*", ACTION=="add", PROGRAM="${shell} -c 'chgrp -R gpio /sys/class/gpio && chmod -R g=u /sys/class/gpio'"
SUBSYSTEM=="gpio", ACTION=="add", PROGRAM="${shell} -c 'chgrp -R gpio /sys%p && chmod -R g=u /sys%p'"
# PWM export results in a "change" action on the pwmchip device (not "add" of a new device), so match actions other than "remove".
SUBSYSTEM=="pwm", ACTION!="remove", PROGRAM="${shell} -c 'chgrp -R gpio /sys%p && chmod -R g=u /sys%p'"
KERNEL=="ttyAMA[0-9]*|ttyS[0-9]*", PROGRAM="${shell} -c '\
ALIASES=/proc/device-tree/aliases; \
TTYNODE=$$(readlink /sys/class/tty/%k/device/of_node | sed 's/base/:/' | cut -d: -f2); \
if [ -e $$ALIASES/bluetooth ] && [ $$TTYNODE/bluetooth = $$(strings $$ALIASES/bluetooth) ]; then \
echo 1; \
elif [ -e $$ALIASES/console ]; then \
if [ $$TTYNODE = $$(strings $$ALIASES/console) ]; then \
echo 0;\
else \
exit 1; \
fi \
elif [ $$TTYNODE = $$(strings $$ALIASES/serial0) ]; then \
echo 0; \
elif [ $$TTYNODE = $$(strings $$ALIASES/serial1) ]; then \
echo 1; \
else \
exit 1; \
fi \
'", SYMLINK+="serial%c"
ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon1", RUN+="${shell} -c '\
if echo RPi-Sense FB | cmp -s /sys/class/graphics/fb0/name; then \
echo 0 > /sys$devpath/bind; \
fi; \
'"
'';
}; };
}; };
hardware.enableRedistributableFirmware = true;
services = {
udev.extraRules = ''
SUBSYSTEM=="dma_heap", GROUP="video", MODE="0660"
KERNEL=="gpiomem", GROUP="gpio", MODE="0660"
KERNEL=="gpiochip*", GROUP="gpio", MODE="0660"
'';
};
} }

View file

@ -5,65 +5,23 @@
config = { config = {
boot.loader.grub.enable = false; boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
boot.consoleLogLevel = lib.mkDefault 7; boot.consoleLogLevel = lib.mkDefault 7;
boot.kernelParams = [ # https://github.com/raspberrypi/firmware/issues/1539#issuecomment-784498108
# This is ugly and fragile, but the sdImage image has an msdos boot.kernelParams = [ "console=serial0,115200n8" "console=tty1" ];
# table, so the partition table id is a 1-indexed hex
# number. So, we drop the hex prefix and stick on a "02" to
# refer to the root partition.
"root=PARTUUID=${lib.strings.removePrefix "0x" config.sdImage.firmwarePartitionID}-02"
"rootfstype=ext4"
"fsck.repair=yes"
"rootwait"
];
sdImage = sdImage = {
let populateFirmwareCommands = ''
kernel-params = pkgs.writeTextFile { cp ${pkgs.uboot_rpi_arm64}/u-boot.bin firmware/u-boot-rpi-arm64.bin
name = "cmdline.txt"; cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/{start*.elf,*.dtb,bootcode.bin,fixup*.dat,overlays} firmware
text = '' cp ${config.hardware.raspberry-pi.config-output} firmware/config.txt
${lib.strings.concatStringsSep " " config.boot.kernelParams} '';
''; populateRootCommands = ''
}; mkdir -p ./files/boot
cfg = config.raspberry-pi-nix; ${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
version = cfg.kernel-version; '';
board = cfg.board; };
kernel = "${config.system.build.kernel}/${config.system.boot.loader.kernelFile}";
initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
populate-kernel =
if cfg.uboot.enable
then ''
cp ${cfg.uboot.package}/u-boot.bin firmware/u-boot-rpi-arm64.bin
''
else ''
cp "${kernel}" firmware/kernel.img
cp "${initrd}" firmware/initrd
cp "${kernel-params}" firmware/cmdline.txt
'';
in
{
populateFirmwareCommands = ''
${populate-kernel}
cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/{start*.elf,*.dtb,bootcode.bin,fixup*.dat,overlays} firmware
cp ${config.hardware.raspberry-pi.config-output} firmware/config.txt
'';
populateRootCommands =
if cfg.uboot.enable
then ''
mkdir -p ./files/boot
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
''
else ''
mkdir -p ./files/sbin
content="$(
echo "#!${pkgs.bash}/bin/bash"
echo "exec ${config.system.build.toplevel}/init"
)"
echo "$content" > ./files/sbin/init
chmod 744 ./files/sbin/init
'';
};
}; };
} }

View file

@ -30,8 +30,7 @@ let
} // optionalAttrs (config.sdImage.rootPartitionUUID != null) { } // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
uuid = config.sdImage.rootPartitionUUID; uuid = config.sdImage.rootPartitionUUID;
}); });
in in {
{
imports = [ ]; imports = [ ];
options.sdImage = { options.sdImage = {
@ -83,6 +82,14 @@ in
''; '';
}; };
firmwarePartitionName = mkOption {
type = types.str;
default = "FIRMWARE";
description = ''
Name of the filesystem which holds the boot firmware.
'';
};
rootPartitionUUID = mkOption { rootPartitionUUID = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
@ -153,7 +160,7 @@ in
config = { config = {
fileSystems = { fileSystems = {
"/boot/firmware" = { "/boot/firmware" = {
device = "/dev/disk/by-label/${config.raspberry-pi-nix.firmware-partition-label}"; device = "/dev/disk/by-label/${config.sdImage.firmwarePartitionName}";
fsType = "vfat"; fsType = "vfat";
}; };
"/" = { "/" = {
@ -219,7 +226,7 @@ in
# Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
eval $(partx $img -o START,SECTORS --nr 1 --pairs) eval $(partx $img -o START,SECTORS --nr 1 --pairs)
truncate -s $((SECTORS * 512)) firmware_part.img truncate -s $((SECTORS * 512)) firmware_part.img
faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n ${config.raspberry-pi-nix.firmware-partition-label} firmware_part.img faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n ${config.sdImage.firmwarePartitionName} firmware_part.img
# Populate the files intended for /boot/firmware # Populate the files intended for /boot/firmware
mkdir firmware mkdir firmware
@ -237,8 +244,7 @@ in
zstd -T$NIX_BUILD_CORES --rm $img zstd -T$NIX_BUILD_CORES --rm $img
fi fi
''; '';
}) }) { };
{ };
boot.postBootCommands = lib.mkIf config.sdImage.expandOnBoot '' boot.postBootCommands = lib.mkIf config.sdImage.expandOnBoot ''
# On the first boot do some maintenance tasks # On the first boot do some maintenance tasks
@ -248,7 +254,7 @@ in
# Figure out device names for the boot device and root filesystem. # Figure out device names for the boot device and root filesystem.
rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /) rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /)
bootDevice=$(lsblk -npo PKNAME $rootPart) bootDevice=$(lsblk -npo PKNAME $rootPart)
partNum=$(lsblk -npo PARTN $rootPart) partNum=$(lsblk -npo MAJ:MIN $rootPart | ${pkgs.gawk}/bin/awk -F: '{print $2}')
# Resize the root partition and the filesystem to fit the disk # Resize the root partition and the filesystem to fit the disk
echo ",+," | sfdisk -N$partNum --no-reread $bootDevice echo ",+," | sfdisk -N$partNum --no-reread $bootDevice