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
`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
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
This repo uses the raspberry pi linux kernel fork, and compiling linux takes a
while. CI pushes kernel builds to the nix-community cachix cache that you may
use to avoid compiling linux yourself. The cache can be found at
https://nix-community.cachix.org, and you can follow the instructions there
to use this cache.
outputs = { self, nixpkgs, raspberry-pi-nix }:
let
inherit (nixpkgs.lib) nixosSystem;
basic-config = { pkgs, lib, ... }: {
time.timeZone = "America/New_York";
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
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:
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:
```
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 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.
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, 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, NixOS
system activation will update the firmware and `config.txt` in the
firmware partition __in place__. Linux kernels are stored in the
`NIXOS_SD` partition and will be booted by u-boot in the firmware
partition.
## `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.
configuration and automatically updated on system activation.
The relevant nixos option is
`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
In Raspberry Pi devices the proprietary firmware manipulates the device tree in
a number of ways before handing it off to the kernel (or in our case, to
u-boot). The transformations that are performed aren't documented so well
(although I have found [this
list](https://forums.raspberrypi.com/viewtopic.php?t=329799#p1974233) ).
In Raspberry Pi devices the proprietary firmware manipulates the
device tree in a number of ways before handing it off to the kernel
(or in our case, to u-boot). The transformations that are performed
aren't documented so well (although I have found [this
list](https://forums.raspberrypi.com/viewtopic.php?t=329799#p1974233)
).
This manipulation makes it difficult to use the device tree configured directly
by NixOS as the proprietary firmware's manipulation must be known and
reproduced.
This manipulation makes it difficult to use the device tree configured
directly by NixOS as the proprietary firmware's manipulation must be
known and reproduced.
Even if the manipulation were successfully reproduced, some benefits would be
lost. For example, the firmware can detect connected hardware during boot and
automatically configure the device tree accordingly before passing it onto the
kernel. If this firmware device tree is ignored then a NixOS system rebuild with
a different device tree would be required when swapping connected
hardware. Examples of what I mean by hardware include: the specific Raspberry Pi
device booting the image, connected cameras, and connected displays.
Even if the manipulation were successfully reproduced, some benefits
would be lost. For example, the firmware can detect connected hardware
during boot and automatically configure the device tree accordingly
before passing it onto the kernel. If this firmware device tree is
ignored then a NixOS system rebuild with a different device tree would
be required when swapping connected hardware. Examples of what I mean
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
firmware device tree manipulation, and to reap the benefits afforded by the
firmware device tree configuration, the bootloader is configured to use the
device tree that it is given (i.e. the one that the raspberry pi firmware loads
and manipulates). As a consequence, device tree configuration is controlled via
the [config.txt
So, in order to avoid the headaches associated with failing to
reproduce some firmware device tree manipulation, and to reap the
benefits afforded by the firmware device tree configuration, u-boot is
configured to use the device tree that it is given (i.e. the one that
the raspberry pi firmware loads and manipulates). As a consequence,
device tree configuration is controlled via the [config.txt
file](https://www.raspberrypi.com/documentation/computers/config_txt.html).
Additionally, the firmware, device trees, and overlays from the `raspberrypifw`
package populate the firmware partition. This package is kept up to date by the
overlay applied by 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.
Additionally, the firmware, device trees, and overlays from the
`raspberrypifw` package populate the firmware partition on system
activation. This package is kept up to date by the overlay applied by
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": {
"libcamera-src": {
"libcamera-apps-src": {
"flake": false,
"locked": {
"lastModified": 1725630279,
"narHash": "sha256-KH30jmHfxXq4j2CL7kv18DYECJRp9ECuWNPnqPZajPA=",
"lastModified": 1674645888,
"narHash": "sha256-UBTDHN0lMj02enB8im4Q+f/MCm/G2mFPP3pLImrZc5A=",
"owner": "raspberrypi",
"repo": "libcamera",
"rev": "69a894c4adad524d3063dd027f5c4774485cf9db",
"repo": "libcamera-apps",
"rev": "9f08463997b82c4bf60e12c4ea43577959a8ae15",
"type": "github"
},
"original": {
"owner": "raspberrypi",
"repo": "libcamera",
"rev": "69a894c4adad524d3063dd027f5c4774485cf9db",
"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",
"ref": "v1.1.1",
"repo": "libcamera-apps",
"type": "github"
}
},
"root": {
"inputs": {
"libcamera-src": "libcamera-src",
"libpisp-src": "libpisp-src",
"nixpkgs": "nixpkgs",
"libcamera-apps-src": "libcamera-apps-src",
"rpi-bluez-firmware-src": "rpi-bluez-firmware-src",
"rpi-firmware-nonfree-src": "rpi-firmware-nonfree-src",
"rpi-firmware-src": "rpi-firmware-src",
"rpi-linux-6_12_17-src": "rpi-linux-6_12_17-src",
"rpi-linux-6_6_78-src": "rpi-linux-6_6_78-src",
"rpi-linux-stable-src": "rpi-linux-stable-src",
"rpicam-apps-src": "rpicam-apps-src"
"rpi-firmware-stable-src": "rpi-firmware-stable-src",
"rpi-linux-5_15-src": "rpi-linux-5_15-src",
"rpi-linux-5_15_87-src": "rpi-linux-5_15_87-src",
"u-boot-src": "u-boot-src"
}
},
"rpi-bluez-firmware-src": {
"flake": false,
"locked": {
"lastModified": 1708969706,
"narHash": "sha256-KakKnOBeWxh0exu44beZ7cbr5ni4RA9vkWYb9sGMb8Q=",
"lastModified": 1672928175,
"narHash": "sha256-gKGK0XzNrws5REkKg/JP6SZx3KsJduu53SfH3Dichkc=",
"owner": "RPi-Distro",
"repo": "bluez-firmware",
"rev": "78d6a07730e2d20c035899521ab67726dc028e1c",
"rev": "9556b08ace2a1735127894642cc8ea6529c04c90",
"type": "github"
},
"original": {
"owner": "RPi-Distro",
"ref": "bookworm",
"repo": "bluez-firmware",
"type": "github"
}
@ -84,103 +47,80 @@
"rpi-firmware-nonfree-src": {
"flake": false,
"locked": {
"lastModified": 1723266537,
"narHash": "sha256-T7eTKXqY9cxEMdab8Snda4CEOrEihy5uOhA6Fy+Mhnw=",
"lastModified": 1674638139,
"narHash": "sha256-54JKmwypD7PRQdd7k6IcF7wL8ifMavEM0UwZwmA24O4=",
"owner": "RPi-Distro",
"repo": "firmware-nonfree",
"rev": "4b356e134e8333d073bd3802d767a825adec3807",
"rev": "7f29411baead874b859eda53efdc2472345ea454",
"type": "github"
},
"original": {
"owner": "RPi-Distro",
"ref": "bookworm",
"repo": "firmware-nonfree",
"type": "github"
}
},
"rpi-firmware-src": {
"rpi-firmware-stable-src": {
"flake": false,
"locked": {
"lastModified": 1728405098,
"narHash": "sha256-4gnK0KbqFnjBmWia9Jt2gveVWftmHrprpwBqYVqE/k0=",
"lastModified": 1673003776,
"narHash": "sha256-tdaH+zZwmILNFBge2gMqtzj/1Hydj9cxhPvhw+7jTrU=",
"owner": "raspberrypi",
"repo": "firmware",
"rev": "7bbb5f80d20a2335066a8781459c9f33e5eebc64",
"rev": "78852e166b4cf3ebb31d051e996d54792f0994b0",
"type": "github"
},
"original": {
"owner": "raspberrypi",
"ref": "1.20241008",
"ref": "stable",
"repo": "firmware",
"type": "github"
}
},
"rpi-linux-6_12_17-src": {
"rpi-linux-5_15-src": {
"flake": false,
"locked": {
"lastModified": 1740765145,
"narHash": "sha256-hoCsGc4+RC/2LmxDtswLBL5ZhWlw4vSiL4Vkl39r2MU=",
"lastModified": 1675874870,
"narHash": "sha256-oy+VgoB4IdFZjGwkx88dDSpwWZj2D5t3PyXPIwDsY1Q=",
"owner": "raspberrypi",
"repo": "linux",
"rev": "5985ce32e511f4e8279a841a1b06a8c7d972b386",
"rev": "14b35093ca68bf2c81bbc90aace5007142b40b40",
"type": "github"
},
"original": {
"owner": "raspberrypi",
"ref": "rpi-6.12.y",
"ref": "rpi-5.15.y",
"repo": "linux",
"type": "github"
}
},
"rpi-linux-6_6_78-src": {
"rpi-linux-5_15_87-src": {
"flake": false,
"locked": {
"lastModified": 1740503700,
"narHash": "sha256-Y8+ot4Yi3UKwlZK3ap15rZZ16VZDvmeFkD46+6Ku7bE=",
"lastModified": 1673628667,
"narHash": "sha256-hNLVfhalmRhhRfvu2mR/qDmmGl//Ic1eqR7N1HFj2CY=",
"owner": "raspberrypi",
"repo": "linux",
"rev": "2e071057fded90e789c0101498e45a1778be93fe",
"rev": "da4c8e0ffe7a868b989211045657d600be3046a1",
"type": "github"
},
"original": {
"owner": "raspberrypi",
"ref": "rpi-6.6.y",
"repo": "linux",
"rev": "da4c8e0ffe7a868b989211045657d600be3046a1",
"type": "github"
}
},
"rpi-linux-stable-src": {
"u-boot-src": {
"flake": false,
"locked": {
"lastModified": 1728403745,
"narHash": "sha256-phCxkuO+jUGZkfzSrBq6yErQeO2Td+inIGHxctXbD5U=",
"owner": "raspberrypi",
"repo": "linux",
"rev": "5aeecea9f4a45248bcf564dec924965e066a7bfd",
"type": "github"
"narHash": "sha256-30fe8klLHRsEtEQ1VpYh4S+AflG5yCQYWlGmpWyFL8w=",
"type": "tarball",
"url": "https://ftp.denx.de/pub/u-boot/u-boot-2023.01.tar.bz2"
},
"original": {
"owner": "raspberrypi",
"ref": "stable_20241008",
"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"
"type": "tarball",
"url": "https://ftp.denx.de/pub/u-boot/u-boot-2023.01.tar.bz2"
}
}
},

View file

@ -2,90 +2,34 @@
description = "raspberry-pi nixos configuration";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
rpi-linux-stable-src = {
u-boot-src = {
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;
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;
url = "github:raspberrypi/linux/rpi-6.12.y";
};
rpi-firmware-src = {
flake = false;
url = "github:raspberrypi/firmware/1.20241008";
url = "github:raspberrypi/firmware/stable";
};
rpi-firmware-nonfree-src = {
flake = false;
url = "github:RPi-Distro/firmware-nonfree/bookworm";
url = "github:RPi-Distro/firmware-nonfree";
};
rpi-bluez-firmware-src = {
flake = false;
url = "github:RPi-Distro/bluez-firmware/bookworm";
url = "github:RPi-Distro/bluez-firmware";
};
rpicam-apps-src = {
libcamera-apps-src = {
flake = false;
url = "github:raspberrypi/rpicam-apps/v1.5.2";
};
libcamera-src = {
flake = false;
url = "github:raspberrypi/libcamera/69a894c4adad524d3063dd027f5c4774485cf9db"; # v0.3.1+rpt20240906
};
libpisp-src = {
flake = false;
url = "github:raspberrypi/libpisp/v1.0.7";
url = "github:raspberrypi/libcamera-apps/v1.1.1";
};
};
outputs = srcs@{ self, ... }:
let
pinned = import srcs.nixpkgs {
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;
};
outputs = srcs@{ self, ... }: {
overlay = import ./overlay (builtins.removeAttrs srcs [ "self" ]);
nixosModules.raspberry-pi = import ./rpi { overlay = self.overlay; };
};
}

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 }:
{ lib, stdenvNoCC }:
{ lib, stdenvNoCC, fetchFromGitHub }:
stdenvNoCC.mkDerivation {
pname = "raspberrypi-wireless-firmware";
version = "2024-02-26";
version = "2023-01-19";
srcs = [ ];
@ -23,7 +23,7 @@ stdenvNoCC.mkDerivation {
cp -rv "${firmware-nonfree}/debian/config/brcm80211/." "$out/lib/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
# 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, ... }:
let
cfg = config.hardware.raspberry-pi;
render-raspberrypi-config =
let
render-options = opts:
lib.strings.concatStringsSep "\n" (render-dt-kvs opts);
render-dt-param = x: "dtparam=" + x;
render-dt-kv = k: v:
if isNull v.value then
k
else
let vstr = toString v.value; in "${k}=${vstr}";
render-dt-kvs = x:
lib.attrsets.mapAttrsToList render-dt-kv
(lib.filterAttrs (k: v: v.enable) x);
render-dt-overlay = { overlay, args }:
"dtoverlay=" + overlay + "\n"
+ lib.strings.concatMapStringsSep "\n" render-dt-param args + "\n"
+ "dtoverlay=";
render-base-dt-params = params:
lib.strings.concatMapStringsSep "\n" render-dt-param
(render-dt-kvs params);
render-dt-overlays = overlays:
lib.strings.concatMapStringsSep "\n" render-dt-overlay
(lib.attrsets.mapAttrsToList
(k: v: {
overlay = k;
args = render-dt-kvs v.params;
})
(lib.filterAttrs (k: v: v.enable) overlays));
render-config-section = k:
{ options, base-dt-params, dt-overlays }:
let
all-config = lib.concatStringsSep "\n" (lib.filter (x: x != "") [
(render-options options)
(render-base-dt-params base-dt-params)
(render-dt-overlays dt-overlays)
]);
in
''
[${k}]
${all-config}
'';
in
conf:
lib.strings.concatStringsSep "\n"
(lib.attrsets.mapAttrsToList render-config-section conf);
in
{
render-raspberrypi-config = let
render-options = opts:
lib.strings.concatStringsSep "\n" (render-dt-kvs opts);
render-dt-param = x: "dtparam=" + x;
render-dt-kv = k: v:
if isNull v.value then
k
else
let vstr = toString v.value; in "${k}=${vstr}";
render-dt-kvs = x:
lib.attrsets.mapAttrsToList render-dt-kv
(lib.filterAttrs (k: v: v.enable) x);
render-dt-overlay = { overlay, args }:
"dtoverlay=" + overlay + "\n"
+ lib.strings.concatMapStringsSep "\n" render-dt-param args + "\n"
+ "dtoverlay=";
render-base-dt-params = params:
lib.strings.concatMapStringsSep "\n" render-dt-param
(render-dt-kvs params);
render-dt-overlays = overlays:
lib.strings.concatMapStringsSep "\n" render-dt-overlay
(lib.attrsets.mapAttrsToList (k: v: {
overlay = k;
args = render-dt-kvs v.params;
}) (lib.filterAttrs (k: v: v.enable) overlays));
render-config-section = k:
{ options, base-dt-params, dt-overlays }:
let
all-config = lib.concatStringsSep "\n" (lib.filter (x: x != "") [
(render-options options)
(render-base-dt-params base-dt-params)
(render-dt-overlays dt-overlays)
]);
in ''
[${k}]
${all-config}
'';
in conf:
lib.strings.concatStringsSep "\n"
(lib.attrsets.mapAttrsToList render-config-section conf);
in {
options = {
hardware.raspberry-pi = {
config =
let
rpi-config-param = {
options = {
enable = lib.mkEnableOption "attr";
value =
lib.mkOption { type = with lib.types; oneOf [ int str bool ]; };
};
config = let
rpi-config-param = {
options = {
enable = lib.mkEnableOption "attr";
value =
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);
};
config-generated = lib.mkOption {
type = lib.types.str;
description = "the config text generated by raspberrypi.hardware.config";
readOnly = true;
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 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 {
type = lib.types.package;
default = pkgs.writeTextFile {
name = "config.txt";
text = ''
# 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, ... }:
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; {
raspberry-pi-nix = {
kernel-version = mkOption {
default = "v6_6_51";
type = types.str;
description = "Kernel version to build.";
};
board = mkOption {
type = types.enum [ "bcm2711" "bcm2712" ];
description = ''
The kernel board version to build.
Examples at: https://www.raspberrypi.com/documentation/computers/linux_kernel.html#native-build-configuration
without the _defconfig part.
'';
};
firmware-partition-label = mkOption {
default = "FIRMWARE";
type = types.str;
description = "label of rpi firmware partition";
};
pin-inputs = {
enable = mkOption {
default = true;
type = types.bool;
description = ''
Whether to pin the kernel to the latest cachix build.
'';
# On activation install u-boot, Raspberry Pi firmware, and our
# generated config.txt
system.activationScripts.raspberrypi = {
text = ''
shopt -s nullglob
TARGET_FIRMWARE_DIR="/boot/firmware"
TARGET_OVERLAYS_DIR="$TARGET_FIRMWARE_DIR/overlays"
TMPFILE="$TARGET_FIRMWARE_DIR/tmp"
UBOOT="${pkgs.uboot_rpi_arm64}/u-boot.bin"
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}"
cp "$UBOOT" "$TMPFILE"
mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/u-boot-rpi-arm64.bin"
cp "$CONFIG" "$TMPFILE"
mv -T "$TMPFILE" "$TARGET_FIRMWARE_DIR/config.txt"
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 {
default = true;
type = types.bool;
description = ''
Whether to run the migration service automatically or not.
'';
};
pi4 = {
options = {
arm_boost = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
};
libcamera-overlay = {
enable = mkOption {
default = true;
type = types.bool;
description = ''
If enabled then the libcamera overlay is applied which
overrides libcamera with the rpi fork.
'';
};
all = {
options = {
# The firmware will start our u-boot binary rather than a
# linux kernel.
kernel = {
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 = {
enable = mkOption {
default = false;
type = types.bool;
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?"
'';
dt-overlays = {
vc4-kms-v3d = {
enable = lib.mkDefault true;
params = { };
};
};
};
};
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
nixpkgs = { overlays = [ overlay ]; };
boot = {
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.latest.kernel);
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 = {
cm4 = {
options = {
otg_mode = {
enable = lib.mkDefault true;
value = lib.mkDefault true;
};
};
loader = {
grub.enable = lib.mkDefault false;
generic-extlinux-compatible = {
enable = lib.mkDefault true;
# We want to use the device tree provided by firmware, so don't
# add FDTDIR to the extlinux conf file.
useGenerationDeviceTree = false;
};
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 = {
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
boot.consoleLogLevel = lib.mkDefault 7;
boot.kernelParams = [
# This is ugly and fragile, but the sdImage image has an msdos
# 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"
];
# https://github.com/raspberrypi/firmware/issues/1539#issuecomment-784498108
boot.kernelParams = [ "console=serial0,115200n8" "console=tty1" ];
sdImage =
let
kernel-params = pkgs.writeTextFile {
name = "cmdline.txt";
text = ''
${lib.strings.concatStringsSep " " config.boot.kernelParams}
'';
};
cfg = config.raspberry-pi-nix;
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
'';
};
sdImage = {
populateFirmwareCommands = ''
cp ${pkgs.uboot_rpi_arm64}/u-boot.bin firmware/u-boot-rpi-arm64.bin
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 = ''
mkdir -p ./files/boot
${config.boot.loader.generic-extlinux-compatible.populateCmd} -c ${config.system.build.toplevel} -d ./files/boot
'';
};
};
}

View file

@ -30,8 +30,7 @@ let
} // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
uuid = config.sdImage.rootPartitionUUID;
});
in
{
in {
imports = [ ];
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 {
type = types.nullOr types.str;
default = null;
@ -153,7 +160,7 @@ in
config = {
fileSystems = {
"/boot/firmware" = {
device = "/dev/disk/by-label/${config.raspberry-pi-nix.firmware-partition-label}";
device = "/dev/disk/by-label/${config.sdImage.firmwarePartitionName}";
fsType = "vfat";
};
"/" = {
@ -219,7 +226,7 @@ in
# Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
eval $(partx $img -o START,SECTORS --nr 1 --pairs)
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
mkdir firmware
@ -237,8 +244,7 @@ in
zstd -T$NIX_BUILD_CORES --rm $img
fi
'';
})
{ };
}) { };
boot.postBootCommands = lib.mkIf config.sdImage.expandOnBoot ''
# On the first boot do some maintenance tasks
@ -248,7 +254,7 @@ in
# Figure out device names for the boot device and root filesystem.
rootPart=$(${pkgs.util-linux}/bin/findmnt -n -o SOURCE /)
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
echo ",+," | sfdisk -N$partNum --no-reread $bootDevice