NixOS modules to aid in configuring NixOS for raspberry pi products
Find a file
2025-02-07 09:38:24 -08:00
atomic-copy use make-disk-image instead of sd-image 2024-11-21 14:10:07 +01:00
example use make-disk-image instead of sd-image 2024-11-21 14:10:07 +01:00
generic-extlinux-compatible use make-disk-image instead of sd-image 2024-11-21 14:10:07 +01:00
overlays Drop kernel 6.10.12 in favour of 6.12.11 2025-01-29 10:06:49 -08:00
rpi Merge fb248c0047 into f317116725 2025-02-07 09:38:24 -08:00
sd-image move comment 2024-11-12 12:31:36 -05:00
.gitignore use make-disk-image instead of sd-image 2024-11-21 14:10:07 +01:00
flake.lock Drop kernel 6.10.12 in favour of 6.12.11 2025-01-29 10:06:49 -08:00
flake.nix Merge fb248c0047 into f317116725 2025-02-07 09:38:24 -08:00
LICENSE add license 2023-09-14 09:10:55 -04:00
README.md use make-disk-image instead of sd-image 2024-11-21 14:10:07 +01:00

raspberry-pi-nix

The primary goal of this flake is to make it easy to create working NixOS configurations for Raspberry Pi products. Specifically, this repository aims to deliver the following benefits:

  1. Configure the kernel, device tree, and boot loader in a way that is compatible with the hardware and proprietary firmware.
  2. Provide a nix interface to Raspberry Pi/device tree configuration that will be familiar to those who have used Raspberry Pi's config.txt based configuration.
  3. Make it easy to build an image suitable for flashing to an sd-card, without the need to first go through an installation media.

The important modules are overlays/default.nix, rpi/default.nix, and rpi/config.nix. The other modules are mostly wrappers that set config.txt settings and enable required kernel modules.

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 /example in this flake for an configs built by CI.

  • /example/direct.nix boots directly into the linux kernel
  • /example/uboot.nix boots into uboot, provides a generation selection menu, then into linux

Using the provided cache to avoid compiling linux

This repo uses the raspberry pi linux kernel fork, and compiling linux takes a 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.

Building an sd-card image

Use make-disk-image.nix like so:

  system.build.image = (import "${toString modulesPath}/../lib/make-disk-image.nix" {
    inherit lib config pkgs;
    format = "raw";
    partitionTableType = "efi";
    copyChannel = false;
    diskSize = "auto";
    additionalSpace = "64M";
    bootSize = "128M";
    touchEFIVars = false;
    installBootLoader = true;
    label = "nixos";
    deterministic = true;
  });

Add the configuration for your specific board:

  raspberry-pi-nix.board = "bcm2711";
  hardware.raspberry-pi.config = {
    ...
  };

Then get nix to build your image:

nix build '.#nixosConfigurations.my-raspberry-pi.config.system.build.image'

The partition layout

The image produced is partitioned in the same way as any x86 EFI-boot disk generated by make-disk-image.nix: There is a partition labelled ESP which contains the firmware, kernel, config.txt and u-boot or cmdline.txt. The second partition labeled nixos is the root partition which contains the nix store and everything else.

Note

The boot partition is called ESP which stands for EFI System Partition. Raspberry Pis don't boot with EFI, but ESP is the hardcoded partition name used in make-disk-image.nix when using partitionTableType=efi, which just happens to be the closest partition layout to what's needed. By setting touchEFIVars=false you can avoid any EFI boot variables being set, not that they will affect the Raspberry Pi's boot process.

Files in the boot partition are managed as long as the rpi package is imported, whether using uboot or direct-to-kernel boot. Following the upstream behaviour of generic-extlinux-compatible/default.nix, these files are not overwritten once created. The only exceptions are cmdline.txt and config.txt which will get overwritten every time nixos-rebuild switch is run. In practice, this means that you can enjoy kernel updates (because each version is put into a new file), but firmware / bootcode updates will probably not be installed if they arrive in an existing file.

config.txt generation

The config.txt file is generated by rpi package and automatically written to the boot partition when running nixos-rebuild switch.

The relevant nixos option is hardware.raspberry-pi.config. Configuration is partitioned into three sections:

  1. Base device tree parameters base-dt-params
  2. Device tree overlays dt-overlays
  3. Firmware options options

Other than that, the format follows pretty closely to the config.txt format. For example:

hardware.raspberry-pi.config = {
  cm4 = {
    options = {
      otg_mode = {
        enable = true;
        value = true;
      };
    };
  };
  pi4 = {
    options = {
      arm_boost = {
        enable = true;
        value = true;
      };
    };
    dt-overlays = {
      vc4-kms-v3d = {
        enable = true;
        params = { cma-512 = { enable = true; }; };
      };
    };
  };
  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 = true;
        value = true;
      };
      camera_auto_detect = {
        enable = true;
        value = true;
      };
      display_auto_detect = {
        enable = true;
        value = true;
      };
      disable_overscan = {
        enable = true;
        value = true;
      };
    };
    dt-overlays = {
      vc4-kms-v3d = {
        enable = true;
        params = { };
      };
    };
    base-dt-params = {
      krnbt = {
        enable = true;
        value = "on";
      };
      spi = {
        enable = true;
        value = "on";
      };
    };
  };
};

generates the following config.txt:

# This is a generated file. Do not edit!
[all]
arm_64bit=1
avoid_warnings=1
camera_auto_detect=1
disable_overscan=1
display_auto_detect=1
enable_uart=1
kernel=u-boot-rpi-arm64.bin
dtparam=krnbt=on
dtparam=spi=on
dtoverlay=vc4-kms-v3d

dtoverlay=

[cm4]
otg_mode=1

[pi4]
arm_boost=1
dtoverlay=vc4-kms-v3d
dtparam=cma-512
dtoverlay=

If you want to preview the generated config.txt, you can find it at the path config.hardware.raspberry-pi.config-output. For example, if you had the above configuration then you could build the config.txt file with:

nix build '.#nixosConfigurations.rpi-example.config.hardware.raspberry-pi.config-output'

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

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.

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

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.

What's not working?

  • Pi 5 u-boot devices other than sd-cards (i.e. usb, nvme).