diff --git a/README.md b/README.md index bfdaf09..37ee972 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,22 @@ # raspberry-pi-nix -NixOS modules that make building images for raspberry-pi products -easier. Most of the work in this repository is based on work in -[nixos-hardware](https://github.com/NixOS/nixos-hardware) and -[nixpkgs](https://github.com/NixOS/nixpkgs). Additionally, be aware -that I am no expert and this repo is the product of me fooling around -with some pis. +The primary goal of this software is to make it easy to create +working NixOS configurations for Raspberry Pi products. Specifically, +this repository aims to deliver the following benefits: -This flake provides nixos modules that correspond to different -raspberry-pi products. These modules can be included in nixos -configurations and aim to deliver the following benefits: - -1. Configure the kernel, device tree, and u-boot in a way that is - compatible with the hardware. -2. Provide a nix interface to device tree configuration that will be - familiar to those who have used raspberry-pi's config.txt based - configuration. +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](https://www.raspberrypi.com/documentation/computers/config_txt.html). 3. Make it easy to build an image suitable for flashing to an sd-card, without a need to first go through an installation media. - + The important modules are `overlay/default.nix`, `rpi/default.nix`, -and `rpi/device-tree.nix`. The other modules for i2c, i2s, etc are -mostly wrappers that set common device tree settings for you. +and `rpi/config.nix`. The other modules for i2c, i2s, etc are mostly +wrappers that set `config.txt` settings and enable required kernel +modules. ## Example @@ -46,27 +41,12 @@ mostly wrappers that set common device tree settings for you. useDHCP = false; interfaces = { wlan0.useDHCP = true; }; }; - hardware.raspberry-pi = { - i2c.enable = true; - audio.enable = true; - fkms-3d.enable = true; - deviceTree = { - dt-overlays = [{ - overlay = "imx477"; # add the overlay for the HQ camera - args = [ ]; - }]; - }; - }; }; in { nixosConfigurations = { - rpi-zero-2-w-example = nixosSystem { + rpi-example = nixosSystem { system = "aarch64-linux"; - modules = [ raspberry-pi-nix.rpi-zero-2-w basic-config ]; - }; - rpi-4b-example = nixosSystem { - system = "aarch64-linux"; - modules = [ raspberry-pi-nix.rpi-4b basic-config ]; + modules = [ raspberry-pi-nix.rpi basic-config ]; }; }; }; @@ -77,25 +57,64 @@ mostly wrappers that set common device tree settings for you. 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-zero-2-w-example` in the above configuration +build an image for `rpi-example` in the above configuration example you could run: ``` -nix build '.#nixosConfigurations.rpi-zero-2-w-example.config.system.build.sdImage' +nix build '.#nixosConfigurations.rpi-example.config.system.build.sdImage' ``` -## Other notes +## The firmware partition -The sd-image built 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 that contains everything else. After the sd-image is -built, nixos system updates will not change anything in the firmware -partition ever again. New kernels and device tree configurations will -remain on the nixos partition and be booted by u-boot in the firmware +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. -So, while you can control device tree params and overlays through your -nixos system configuration, if you want to modify other config.txt -variables this must be done manually by mounting the partition and -modifying the config.txt file. +## `config.txt` generation + +As noted, the `config.txt` file is generated by the NixOS +configuration and automatically updated on system activation. + + +## 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) +). + +This manipulation makes it difficult to use the device tree configured +directly by NixOS as you must know and reproduce the manipulation +performed by the proprietary firmware. + +Even if the manipulation was reproduced, some convenience would be +missed out. For example, the firmware can detect hardware during boot +and automatically configure the device tree accordingly, rather than +requiring a NixOS system rebuild with a different device tree for +different 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, 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 populates 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. + diff --git a/rpi/config.nix b/rpi/config.nix index 9afec3d..c311124 100644 --- a/rpi/config.nix +++ b/rpi/config.nix @@ -1,6 +1,6 @@ { lib, config, pkgs, ... }: let - cfg = config; + cfg = config.hardware.raspberry-pi; render-raspberrypi-config = let render-options = opts: lib.strings.concatStringsSep "\n" (render-dt-kvs opts); @@ -22,11 +22,11 @@ let args = render-dt-kvs v; }) overlays); render-config-section = k: - { options, base-dtb-params, dt-overlays }: + { options, base-dt-params, dt-overlays }: let all-config = lib.concatStringsSep "\n" (lib.filter (x: x != "") [ (render-options options) - (render-base-dt-params base-dtb-params) + (render-base-dt-params base-dt-params) (render-dt-overlays dt-overlays) ]); in '' @@ -38,48 +38,48 @@ let (lib.attrsets.mapAttrsToList render-config-section conf); in { options = { - raspberrypi-config = let - raspberrypi-config-options = { - options = { - options = lib.mkOption { - type = with lib.types; attrsOf anything; - default = { }; - example = { - enable_gic = true; - armstub = "armstub8-gic.bin"; - arm_boost = true; + hardware.raspberry-pi = { + config = let + raspberry-pi-config-options = { + options = { + options = lib.mkOption { + type = with lib.types; attrsOf anything; + default = { }; + example = { + enable_gic = true; + arm_boost = true; + }; }; - }; - base-dtb-params = lib.mkOption { - type = with lib.types; attrsOf anything; - default = { }; - example = { - i2c = "on"; - audio = "on"; + base-dt-params = lib.mkOption { + type = with lib.types; attrsOf anything; + default = { }; + example = { + i2c = "on"; + audio = "on"; + }; + description = "parameters to pass to the base dtb"; + }; + dt-overlays = lib.mkOption { + type = with lib.types; attrsOf (attrsOf (nullOr str)); + default = { }; + example = { vc4-kms-v3d = { cma-256 = null; }; }; + description = "dtb overlays to apply"; }; - description = "parameters to pass to the base dtb"; - }; - dt-overlays = lib.mkOption { - type = with lib.types; attrsOf (attrsOf (nullOr str)); - default = { }; - example = { vc4-kms-v3d = { cma-256 = null; }; }; - description = "dtb overlays to apply"; }; }; + in lib.mkOption { + type = with lib.types; attrsOf (submodule raspberry-pi-config-options); }; - in lib.mkOption { - type = with lib.types; attrsOf (submodule raspberrypi-config-options); - }; - raspberrypi-config-output = lib.mkOption { - type = lib.types.package; - default = pkgs.writeTextFile { - name = "config.txt"; - text = '' - # Auto-generated by nix. Modifications will be overwritten. - ${render-raspberrypi-config cfg.raspberrypi-config} - ''; + config-output = lib.mkOption { + type = lib.types.package; + default = pkgs.writeTextFile { + name = "config.txt"; + text = '' + # This is a generated file. Do not edit! + ${render-raspberrypi-config cfg.config} + ''; + }; }; }; }; - } diff --git a/rpi/default.nix b/rpi/default.nix index 8ca7cb7..12a48ae 100644 --- a/rpi/default.nix +++ b/rpi/default.nix @@ -2,8 +2,10 @@ { lib, pkgs, config, ... }: { - imports = [ ../sd-image ./config.nix ]; + imports = [ ../sd-image ./config.nix ./i2c.nix ]; + # On activation install u-boot, Raspberry Pi firmware, and our + # generated config.txt system.activationScripts.raspberrypi = { text = '' if ! grep -qs '/boot/firmware ' /proc/mounts; then @@ -11,29 +13,28 @@ fi cp ${pkgs.uboot_rpi_arm64}/u-boot.bin /boot/firmware/u-boot-rpi-arm64.bin cp -r ${pkgs.raspberrypifw}/share/raspberrypi/boot/{start*.elf,*.dtb,bootcode.bin,fixup*.dat,overlays} /boot/firmware - cp ${config.raspberrypi-config-output} /boot/firmware/config.txt + cp ${config.hardware.raspberry-pi.config-output} /boot/firmware/config.txt ''; }; - raspberrypi-config = { - pi4 = { - options = { - enable_gic = true; - armstub = "armstub8-gic.bin"; - arm_boost = true; - disable_overscan = true; - }; - dt-overlays = { vc4-kms-v3d-pi4 = { cma-512 = null; }; }; - }; - pi02 = { dt-overlays = { vc4-kms-v3d = { cma-256 = null; }; }; }; + # 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 = true; }; }; + pi4 = { options = { arm_boost = true; }; }; all = { options = { + # The firmware will start our u-boot binary rather than a + # linux kernel. kernel = "u-boot-rpi-arm64.bin"; + arm_64bit = true; enable_uart = true; avoid_warnings = true; - arm_64bit = true; + camera_auto_detect = true; + display_auto_detect = true; + disable_overscan = true; }; - base-dtb-params = { krnbt = "on"; }; + dt-overlays = { vc4-kms-v3d = { }; }; }; }; @@ -59,4 +60,12 @@ }; }; 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" + ''; + }; } diff --git a/rpi/i2c.nix b/rpi/i2c.nix new file mode 100644 index 0000000..07c1762 --- /dev/null +++ b/rpi/i2c.nix @@ -0,0 +1,14 @@ +{ config, lib, pkgs, ... }: + +let cfg = config.hardware.raspberry-pi.i2c; +in { + options.hardware.raspberry-pi.i2c = { + enable = lib.mkEnableOption "configuration for i2c"; + }; + config = lib.mkIf cfg.enable { + hardware = { + raspberry-pi.config.all.base-dt-params = { i2c = "on"; }; + i2c.enable = true; + }; + }; +} diff --git a/sd-image/default.nix b/sd-image/default.nix index 0c44d91..2804d06 100644 --- a/sd-image/default.nix +++ b/sd-image/default.nix @@ -16,7 +16,7 @@ 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.raspberrypi-config-output} firmware/config.txt + cp ${config.hardware.raspberry-pi.config-output} firmware/config.txt ''; populateRootCommands = '' mkdir -p ./files/boot diff --git a/sd-image/sd-image.nix b/sd-image/sd-image.nix index 4c0f656..ecad5dc 100644 --- a/sd-image/sd-image.nix +++ b/sd-image/sd-image.nix @@ -162,6 +162,7 @@ in { "/boot/firmware" = { device = "/dev/disk/by-label/${config.sdImage.firmwarePartitionName}"; fsType = "vfat"; + options = [ "nofail" "noauto" ]; }; "/" = { device = "/dev/disk/by-label/NIXOS_SD";