NixOS on ARM/Raspberry Pi 4: Difference between revisions

From NixOS Wiki
imported>Waschtl
removed general, non nixOS specific, Raspberry Pi Information
 
(19 intermediate revisions by 11 users not shown)
Line 27: Line 27:
|}
|}
</div>
</div>
The Raspberry Pi family of devices is a series of single-board computers made by the Raspberry Pi Foundation. They are all based on Broadcom System-on-a-chip (SoCs).  
The Raspberry Pi family of devices is a series of single-board computers made by the Raspberry Pi Foundation. They are all based on Broadcom System-on-a-chip (SoCs).


== Status ==
== Status ==
Line 37: Line 37:
First follow the [[NixOS_on_ARM#Installation|generic installation steps]] to get the installer image and install using the [[NixOS_on_ARM#NixOS_installation_.26_configuration|installation and configuration steps]].
First follow the [[NixOS_on_ARM#Installation|generic installation steps]] to get the installer image and install using the [[NixOS_on_ARM#NixOS_installation_.26_configuration|installation and configuration steps]].


The Raspberry Pi 4B works with the [https://hydra.nixos.org/job/nixos/trunk-combined/nixos.sd_image.aarch64-linux generic SD image].
The Raspberry Pi 4B works with the [https://hydra.nixos.org/job/nixos/trunk-combined/nixos.sd_image.aarch64-linux generic SD image].


Sample instructions for [https://nix.dev/tutorials/installing-nixos-on-a-raspberry-pi installing NixOS on a Raspberry Pi] are available at nix.dev.
Sample instructions for [https://nix.dev/tutorials/installing-nixos-on-a-raspberry-pi installing NixOS on a Raspberry Pi] are available at nix.dev.
Line 45: Line 45:
=== Configuration ===
=== Configuration ===


{{outdated|These instructions were written when the generic image did not work. Using the vendor kernel may be desirable under some conditions<sup>[which?]</sup>}}
Using <code>nixos-generate-config</code> will generate the required minimal configuration.


Using <code>nixos-generate-config</code> will not generate the required minimal configuration.
Raspberry Pi 4 is well-supported on modern kernels. However, if you encounter issues with GPU support or other deviceTree quirks, you may wish to add the nixos-hardware channel:


For better GPU Support, remember to add the nixos-hardware channel:
<code>
<code>nixos-hardware https://github.com/NixOS/nixos-hardware/archive/master.tar.gz</code>
nix-channel --add https://github.com/NixOS/nixos-hardware/archive/master.tar.gz nixos-hardware
 
nix-channel --update
</code>


{{file|/etc/nixos/configuration.nix|nix|<nowiki>
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
{ config, pkgs, lib, ... }:
{ config, pkgs, lib, ... }:
{
{
# This configuration worked on 09-03-2021 nixos-unstable @ commit 102eb68ceec
  imports =
# The image used https://hydra.nixos.org/build/134720986
    [
 
      </nowiki><<nowiki>nixos-hardware/raspberry-pi/4</nowiki>><nowiki>
  boot = {
      ./hardware-configuration.nix
    kernelPackages = pkgs.linuxPackages_rpi4;
    tmpOnTmpfs = true;
    initrd.availableKernelModules = [ "usbhid" "usb_storage" ];
    # ttyAMA0 is the serial console broken out to the GPIO
    kernelParams = [
        "8250.nr_uarts=1"
        "console=ttyAMA0,115200"
        "console=tty1"
        # A lot GUI programs need this, nearly all wayland applications
        "cma=128M"
     ];
     ];
   };
   hardware = {
 
     raspberry-pi."4".apply-overlays-dtmerge.enable = true;
  boot.loader.raspberryPi = {
     deviceTree = {
     enable = true;
    version = 4;
  };
  boot.loader.grub.enable = false;
 
  # Required for the Wireless firmware
  hardware.enableRedistributableFirmware = true;
 
  networking = {
    hostName = "nixos-raspi-4"; # Define your hostname.
     networkmanager = {
       enable = true;
       enable = true;
      filter = "*rpi-4-*.dtb";
     };
     };
   };
   };
 
   console.enable = false;
   nix = {
  environment.systemPackages = with pkgs; [
    autoOptimiseStore = true;
     libraspberrypi
    gc = {
     raspberrypi-eeprom
      automatic = true;
   ];
      dates = "weekly";
   system.stateVersion = "23.11";
      options = "--delete-older-than 30d";
     };
    # Free up to 1GiB whenever there is less than 100MiB left.
     extraOptions = ''
      min-free = ${toString (100 * 1024 * 1024)}
      max-free = ${toString (1024 * 1024 * 1024)}
    '';
   };
   system.stateVersion = "20.09";
}
}
</nowiki>}}
</nowiki>}}
=== USB boot ===
=== USB boot ===


Line 114: Line 88:
</nowiki>}}
</nowiki>}}


Now reboot the device so it can update the firmware from boot partition.
Now reboot the device so it can update the firmware from the boot partition.


When running from USB device without SD card present, kernel spams log about missing SD card, workaround for this is to set:
{{outdated|This will only work when not using U-Boot. Configuring through an overlay will be required.}}
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
{
  boot.loader.raspberryPi.firmwareConfig = "dtparam=sd_poll_once=on";
}
</nowiki>}}
=== GPU support ===
=== GPU support ===


Line 135: Line 100:
     enable = true;
     enable = true;
     displayManager.lightdm.enable = true;
     displayManager.lightdm.enable = true;
     desktopManager.gnome3.enable = true;
     desktopManager.gnome.enable = true;
     videoDrivers = [ "fbdev" ];
     videoDrivers = [ "fbdev" ];
   };
   };
Line 142: Line 107:
==== With GPU ====
==== With GPU ====


In [https://github.com/NixOS/nixos-hardware/pull/261 nixos-hardware#261] a new option has been added to use the <code>fkms-3d</code> overlay. This will only work with the vendor kernel.
In [https://github.com/NixOS/nixos-hardware/pull/261 nixos-hardware#261] an option has been added to use the <code>fkms-3d</code> ([https://wiki.archlinux.org/title/Kernel_mode_setting modesetting]) overlay which uses the [https://www.raspberrypi.com/news/vc4-and-v3d-opengl-drivers-for-raspberry-pi-an-update/ V3D renderer]. This will only work with the vendor kernel, which is the default in NixOS.


{{file|/etc/nixos/configuration.nix|nix|<nowiki>
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
Line 157: Line 122:
     enable = true;
     enable = true;
     displayManager.lightdm.enable = true;
     displayManager.lightdm.enable = true;
     desktopManager.gnome3.enable = true;
     desktopManager.gnome.enable = true;
   };
   };
}
}
</nowiki>}}
</nowiki>}}
==== Tools ====
 
=== Tools ===


The raspberry tools are available in the <code>libraspberrypi</code> package and include commands like <code>vcgencmd</code> to measure temperature and CPU frequency.
The raspberry tools are available in the <code>libraspberrypi</code> package and include commands like <code>vcgencmd</code> to measure temperature and CPU frequency.


==== Audio ====
=== Audio ===
 
{{outdated|An equivalent change that works with U-Boot through <code>hardware.deviceTree</code> is needed.}}


In addition to the usual config, you will need to enable audio support explicitly in the firmwareConfig.
In addition to the usual config, you will need to enable hardware audio support:


{{file|/etc/nixos/configuration.nix|nix|<nowiki>
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
   sound.enable = true;
{
  hardware.pulseaudio.enable = true;
  # Enable audio devices
 
   boot.kernelParams = [ "snd_bcm2835.enable_hdmi=1" "snd_bcm2835.enable_headphones=1" ];
   boot.loader.raspberryPi.firmwareConfig = ''
   boot.loader.raspberryPi.firmwareConfig = ''
     dtparam=audio=on
     dtparam=audio=on
   '';
   '';
}
</nowiki>}}
If you're running headless, you can also disable HDMI audio and force use of the headphones jack by adding <code>hdmi_ignore_edid_audio=1</code> on a line below <code>dtparam=audio=on</code>.
=== Networking ===
Ethernet and wifi interfaces should work out of the box. In addition to normal network configuration, consider disabling wifi powersaving if you experience slowness or issues with the host becoming unreachable on the network shortly after boot. For NetworkManager, the following configuration is sufficient:
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
{
  # Basic networking
  networking.networkmanager.enable = true;
  # Prevent host becoming unreachable on wifi after some time.
  networking.networkmanager.wifi.powersave = false;
}
</nowiki>}}
</nowiki>}}


=== Using GPIO pins as non root ===
=== Using GPIO pins as non-root ===


By default, the GPIO pins are enable, but can only be accessed by the root user.
By default, the GPIO pins are enabled, but can only be accessed by the root user.
This can be address by adding a [https://wiki.archlinux.org/title/Udev udev] rule to your configuration that changes the owner ship of <code>/dev/gpiomem</code> and the other required devices.
This can be addressed by adding a [https://wiki.archlinux.org/title/Udev udev] rule to your configuration that changes the ownership of <code>/dev/gpiomem</code> and the other required devices.


The following code adds a group <code>gpio</code> and adds the user <code>mygpiouser</code> to that group.
The following code adds a group <code>gpio</code> and adds the user <code>mygpiouser</code> to that group. You probably want to put your own user name here.
You probably want to put your own user name here.


The <code>extraRules</code> change the owner of <code>gpiomem</code> and all other files needed for GPIO to work to <code>root:gpio</code> and changes the permissions to <code>0660</code>.
The <code>extraRules</code> changes the owner of <code>gpiomem</code> and all other files needed for GPIO to work to <code>root:gpio</code> and changes the permissions to <code>0660</code>.
Therefore, the root user and anyone in the gpio group can now access the GPIO pins.
Therefore, the root user and anyone in the gpio group can now access the GPIO pins.


Line 198: Line 177:
   services.udev.extraRules = ''
   services.udev.extraRules = ''
     SUBSYSTEM=="bcm2835-gpiomem", KERNEL=="gpiomem", GROUP="gpio",MODE="0660"
     SUBSYSTEM=="bcm2835-gpiomem", KERNEL=="gpiomem", GROUP="gpio",MODE="0660"
     SUBSYSTEM=="gpio", KERNEL=="gpiochip*", ACTION=="add", RUN+="${pkgs.bash}/bin/bash -c 'chown root:gpio /sys/class/gpio/export /sys/class/gpio/unexport ; chmod 220 /sys/class/gpio/export /sys/class/gpio/unexport'"
     SUBSYSTEM=="gpio", KERNEL=="gpiochip*", ACTION=="add", RUN+="${pkgs.bash}/bin/bash -c 'chown root:gpio /sys/class/gpio/export /sys/class/gpio/unexport ; chmod 220 /sys/class/gpio/export /sys/class/gpio/unexport'"
     SUBSYSTEM=="gpio", KERNEL=="gpio*", ACTION=="add",RUN+="${pkgs.bash}/bin/bash -c 'chown root:gpio /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value ; chmod 660 /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value'"
     SUBSYSTEM=="gpio", KERNEL=="gpio*", ACTION=="add",RUN+="${pkgs.bash}/bin/bash -c 'chown root:gpio /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value ; chmod 660 /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value'"
   '';
   '';
Line 214: Line 193:


To enable the SPI, you would normally add <code>dtparam=spi=on</code> to <code>/boot/config.txt</code>.
To enable the SPI, you would normally add <code>dtparam=spi=on</code> to <code>/boot/config.txt</code>.
This is not possbible on NixOS, and instead you have to apply a device tree overlay.
This is not possible on NixOS, and instead you have to apply a device tree overlay.
For this we use the <code>hardware.deviceTree.overlays</code> option.
For this we use the <code>hardware.deviceTree.overlays</code> option.
After applying the overlay, we add an <code>spi</code> group and change the owner of the <code>spidev</code> device to it, similarly to [[#Using GPIO pins as non root |GPIO]].
After applying the overlay, we add an <code>spi</code> group and change the owner of the <code>spidev</code> device to it, similarly to [[#Using GPIO pins as non root |GPIO]].


<syntaxHighlight lang="nix">
<syntaxhighlight lang="nix">
hardware.raspberry-pi."4".apply-overlays-dtmerge.enable = true;
hardware.raspberry-pi."4".apply-overlays-dtmerge.enable = true;
hardware.deviceTree = {
hardware.deviceTree = {
Line 226: Line 205:
     {
     {
       name = "spi";
       name = "spi";
       dtsoFile = ./spi0-0cd.dtso;
       dtboFile = ./spi0-0cs.dtbo;
     }
     }
   ];
   ];
Line 236: Line 215:
   SUBSYSTEM=="spidev", KERNEL=="spidev0.0", GROUP="spi", MODE="0660"
   SUBSYSTEM=="spidev", KERNEL=="spidev0.0", GROUP="spi", MODE="0660"
'';
'';
</syntaxHighlight>
</syntaxhighlight>


The the <code>spi0-0cd.dtso</code> file can be downlaoded [https://github.com/raspberrypi/firmware/blob/master/boot/overlays/spi0-0cs.dtbo here].
The the <code>spi0-0cd.dtso</code> file can be downloaded [https://github.com/raspberrypi/firmware/blob/master/boot/overlays/spi0-0cs.dtbo here].
You might have to change the <code>compatible</code> field to "raspberrypi" in the dtbo file.
You might have to change the <code>compatible</code> field to "raspberrypi" in the dtbo file.


Line 266: Line 245:
   services.udev.extraRules = ''
   services.udev.extraRules = ''
     # allow access to raspi cec device for video group (and optionally register it as a systemd device, used below)
     # allow access to raspi cec device for video group (and optionally register it as a systemd device, used below)
     SUBSYSTEM=="vchiq", GROUP="video", MODE="0660", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/vchiq"
     KERNEL=="vchiq", GROUP="video", MODE="0660", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/vchiq"
   '';
   '';


   # optional: attach a persisted cec-client to `/run/cec.fifo`, to avoid the CEC ~1s startup delay per command
   # optional: attach a persisted cec-client to `/run/cec.fifo`, to avoid the CEC ~1s startup delay per command
   # scan for devices: `echo 'scan' > /run/cec.fifo ; journalctl -u cec-client.service`
   # scan for devices: `echo 'scan' </nowiki>><nowiki> /run/cec.fifo ; journalctl -u cec-client.service`
   # set pi as active source: `echo 'as' > /run/cec.fifo`
   # set pi as active source: `echo 'as' </nowiki>><nowiki> /run/cec.fifo`
   systemd.sockets."cec-client" = {
   systemd.sockets."cec-client" = {
     after = [ "dev-vchiq.device" ];
     after = [ "dev-vchiq.device" ];
Line 288: Line 267:
     serviceConfig = {
     serviceConfig = {
       ExecStart = ''${pkgs.libcec}/bin/cec-client -d 1'';
       ExecStart = ''${pkgs.libcec}/bin/cec-client -d 1'';
       ExecStop = ''/bin/sh -c "echo q > /run/cec.fifo"'';
       ExecStop = ''/bin/sh -c "echo q </nowiki>><nowiki> /run/cec.fifo"'';
       StandardInput = "socket";
       StandardInput = "socket";
       StandardOutput = "journal";
       StandardOutput = "journal";
Line 294: Line 273:
   };
   };
}
}
</nowiki>}}
=== Enabling Bluetooth ===
One might get bluetooth to work with this in the configuration file:
{{file|/etc/nixos/configuration.nix|nix|<nowiki>
  systemd.services.btattach = {
    before = [ "bluetooth.service" ];
    after = [ "dev-ttyAMA0.device" ];
    wantedBy = [ "multi-user.target" ];
    serviceConfig = {
      ExecStart = "${pkgs.bluez}/bin/btattach -B /dev/ttyAMA0 -P bcm -S 3000000";
    };
  };
</nowiki>}}
</nowiki>}}


Line 312: Line 305:
=== Audio not playing and Bluetooth: no controller available ===
=== Audio not playing and Bluetooth: no controller available ===


On the Raspberry Pi kernel, the jack may never play audio, and no Bluetooth devices may ever be found. To get this to work, it is recommended to switch to the mainline kernel. See [https://github.com/NixOS/nixpkgs/issues/123725 nixpkgs#123725] for more info.
On the Raspberry Pi kernel, the jack may never play audio, and no Bluetooth devices may ever be found. To get this to work, it is recommended to switch to the mainline kernel. See [https://github.com/NixOS/nixpkgs/issues/123725 nixpkgs#123725] for more info.


<hr />
=== Touch screen not working ===
You have to declare this in your <code>configuration.nix</code><ref>https://discourse.nixos.org/t/cant-get-nixos-x-to-work-on-a-raspberry-pi-with-dsi-display/44532/3</ref>:<syntaxhighlight lang="nix">
hardware.raspberry-pi."4" = {
  touch-ft5406.enable = true;
};
</syntaxhighlight>

Latest revision as of 14:19, 8 October 2024

Raspberry Pi 4 Family
A Raspberry Pi 4.
Manufacturer Raspberry Pi Foundation
Architecture AArch64
Bootloader Custom or U-Boot
Boot order Configurable; SD, USB, Netboot
Maintainer
Raspberry Pi 4B
SoC BCM2711

The Raspberry Pi family of devices is a series of single-board computers made by the Raspberry Pi Foundation. They are all based on Broadcom System-on-a-chip (SoCs).

Status

The Raspberry Pi 4 Family is only supported as AArch64. Use as armv7 is community supported.

Board-specific installation notes

First follow the generic installation steps to get the installer image and install using the installation and configuration steps.

The Raspberry Pi 4B works with the generic SD image.

Sample instructions for installing NixOS on a Raspberry Pi are available at nix.dev.

Warning: Note that the Raspberry Pi 4 has two HDMI outputs, and apparently sometimes the user prompt for the console/TTY is displayed on HDMI 1 while the boot process is displayed on HDMI 0 (this may even be the case with the official (non NixOs) non-graphical lite image). So if after the message "Welcome on NixOs" at the end of phase 2 your screen goes black/disconnects, try to use the other HDMI port. See the related bug here.

Configuration

Using nixos-generate-config will generate the required minimal configuration.

Raspberry Pi 4 is well-supported on modern kernels. However, if you encounter issues with GPU support or other deviceTree quirks, you may wish to add the nixos-hardware channel:

nix-channel --add https://github.com/NixOS/nixos-hardware/archive/master.tar.gz nixos-hardware

nix-channel --update

/etc/nixos/configuration.nix
{ config, pkgs, lib, ... }:
{
  imports =
    [
      <nixos-hardware/raspberry-pi/4>
      ./hardware-configuration.nix
    ];
  hardware = {
    raspberry-pi."4".apply-overlays-dtmerge.enable = true;
    deviceTree = {
      enable = true;
      filter = "*rpi-4-*.dtb";
    };
  };
  console.enable = false;
  environment.systemPackages = with pkgs; [
    libraspberrypi
    raspberrypi-eeprom
  ];
  system.stateVersion = "23.11";
}

USB boot

For USB booting to work properly, a firmware update might be needed:

 $ nix-shell -p raspberrypi-eeprom
 $ rpi-eeprom-update -d -a

Now reboot the device so it can update the firmware from the boot partition.

GPU support

The following configuration samples are built on the assumption that they are added to an already working configuration. They are not complete configurations.

Without GPU

/etc/nixos/configuration.nix
{
  services.xserver = {
    enable = true;
    displayManager.lightdm.enable = true;
    desktopManager.gnome.enable = true;
    videoDrivers = [ "fbdev" ];
  };
}

With GPU

In nixos-hardware#261 an option has been added to use the fkms-3d (modesetting) overlay which uses the V3D renderer. This will only work with the vendor kernel, which is the default in NixOS.

/etc/nixos/configuration.nix
{ pkgs, ... }:

{
  imports = [
    .../nixos-hardware/raspberry-pi/4
  ];

  hardware.raspberry-pi."4".fkms-3d.enable = true;

  services.xserver = {
    enable = true;
    displayManager.lightdm.enable = true;
    desktopManager.gnome.enable = true;
  };
}

Tools

The raspberry tools are available in the libraspberrypi package and include commands like vcgencmd to measure temperature and CPU frequency.

Audio

In addition to the usual config, you will need to enable hardware audio support:

/etc/nixos/configuration.nix
{
  # Enable audio devices
  boot.kernelParams = [ "snd_bcm2835.enable_hdmi=1" "snd_bcm2835.enable_headphones=1" ];
  boot.loader.raspberryPi.firmwareConfig = ''
    dtparam=audio=on
  '';
}

If you're running headless, you can also disable HDMI audio and force use of the headphones jack by adding hdmi_ignore_edid_audio=1 on a line below dtparam=audio=on.

Networking

Ethernet and wifi interfaces should work out of the box. In addition to normal network configuration, consider disabling wifi powersaving if you experience slowness or issues with the host becoming unreachable on the network shortly after boot. For NetworkManager, the following configuration is sufficient:

/etc/nixos/configuration.nix
{
  # Basic networking
  networking.networkmanager.enable = true;
  # Prevent host becoming unreachable on wifi after some time.
  networking.networkmanager.wifi.powersave = false;
}

Using GPIO pins as non-root

By default, the GPIO pins are enabled, but can only be accessed by the root user. This can be addressed by adding a udev rule to your configuration that changes the ownership of /dev/gpiomem and the other required devices.

The following code adds a group gpio and adds the user mygpiouser to that group. You probably want to put your own user name here.

The extraRules changes the owner of gpiomem and all other files needed for GPIO to work to root:gpio and changes the permissions to 0660. Therefore, the root user and anyone in the gpio group can now access the GPIO pins.

  # Create gpio group
  users.groups.gpio = {};

  # Change permissions gpio devices
  services.udev.extraRules = ''
    SUBSYSTEM=="bcm2835-gpiomem", KERNEL=="gpiomem", GROUP="gpio",MODE="0660"
    SUBSYSTEM=="gpio", KERNEL=="gpiochip*", ACTION=="add", RUN+="${pkgs.bash}/bin/bash -c 'chown root:gpio /sys/class/gpio/export /sys/class/gpio/unexport ; chmod 220 /sys/class/gpio/export /sys/class/gpio/unexport'"
    SUBSYSTEM=="gpio", KERNEL=="gpio*", ACTION=="add",RUN+="${pkgs.bash}/bin/bash -c 'chown root:gpio /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value ; chmod 660 /sys%p/active_low /sys%p/direction /sys%p/edge /sys%p/value'"
  '';

  # Add user to group
  users = {
    users.mygpiouser = {
      extraGroups = [ "gpio" ... ];
      ....
    };
  };

Enabling the SPI

To enable the SPI, you would normally add dtparam=spi=on to /boot/config.txt. This is not possible on NixOS, and instead you have to apply a device tree overlay. For this we use the hardware.deviceTree.overlays option. After applying the overlay, we add an spi group and change the owner of the spidev device to it, similarly to GPIO.

hardware.raspberry-pi."4".apply-overlays-dtmerge.enable = true;
hardware.deviceTree = {
  enable = true;
  filter = "*-rpi-*.dtb";
  overlays = [
    {
      name = "spi";
      dtboFile = ./spi0-0cs.dtbo;
    }
  ];
};

users.groups.spi = {};

services.udev.extraRules = ''
  SUBSYSTEM=="spidev", KERNEL=="spidev0.0", GROUP="spi", MODE="0660"
'';

The the spi0-0cd.dtso file can be downloaded here. You might have to change the compatible field to "raspberrypi" in the dtbo file.

HDMI-CEC

A few bits and pieces for using HDMI-CEC on the Pi4:

/etc/nixos/configuration.nix
{ pkgs, ... }:

{
  # an overlay to enable raspberrypi support in libcec, and thus cec-client
  nixpkgs.overlays = [
    # nixos-22.05
    # (self: super: { libcec = super.libcec.override { inherit (self) libraspberrypi; }; })
    # nixos-22.11
    (self: super: { libcec = super.libcec.override { withLibraspberrypi = true; }; })
  ];

  # install libcec, which includes cec-client (requires root or "video" group, see udev rule below)
  # scan for devices: `echo 'scan' | cec-client -s -d 1`
  # set pi as active source: `echo 'as' | cec-client -s -d 1`
  environment.systemPackages = with pkgs; [
    libcec
  ];

  services.udev.extraRules = ''
    # allow access to raspi cec device for video group (and optionally register it as a systemd device, used below)
    KERNEL=="vchiq", GROUP="video", MODE="0660", TAG+="systemd", ENV{SYSTEMD_ALIAS}="/dev/vchiq"
  '';

  # optional: attach a persisted cec-client to `/run/cec.fifo`, to avoid the CEC ~1s startup delay per command
  # scan for devices: `echo 'scan' > /run/cec.fifo ; journalctl -u cec-client.service`
  # set pi as active source: `echo 'as' > /run/cec.fifo`
  systemd.sockets."cec-client" = {
    after = [ "dev-vchiq.device" ];
    bindsTo = [ "dev-vchiq.device" ];
    wantedBy = [ "sockets.target" ];
    socketConfig = {
      ListenFIFO = "/run/cec.fifo";
      SocketGroup = "video";
      SocketMode = "0660";
    };
  };
  systemd.services."cec-client" = {
    after = [ "dev-vchiq.device" ];
    bindsTo = [ "dev-vchiq.device" ];
    wantedBy = [ "multi-user.target" ];
    serviceConfig = {
      ExecStart = ''${pkgs.libcec}/bin/cec-client -d 1'';
      ExecStop = ''/bin/sh -c "echo q > /run/cec.fifo"'';
      StandardInput = "socket";
      StandardOutput = "journal";
      Restart="no";
  };
}

Enabling Bluetooth

One might get bluetooth to work with this in the configuration file:

/etc/nixos/configuration.nix
  systemd.services.btattach = {
    before = [ "bluetooth.service" ];
    after = [ "dev-ttyAMA0.device" ];
    wantedBy = [ "multi-user.target" ];
    serviceConfig = {
      ExecStart = "${pkgs.bluez}/bin/btattach -B /dev/ttyAMA0 -P bcm -S 3000000";
    };
  };

Notes about the boot process

Unless using an extremely early WIP image, the Raspberry Pi 4B boots using the U-Boot platform firmware.

Updating U-Boot/Firmware

 
$ nix-shell -p raspberrypi-eeprom
$ sudo mount /dev/disk/by-label/FIRMWARE /mnt
$ sudo BOOTFS=/mnt FIRMWARE_RELEASE_STATUS=stable rpi-eeprom-update -d -a

source

Troubleshooting

Audio not playing and Bluetooth: no controller available

On the Raspberry Pi kernel, the jack may never play audio, and no Bluetooth devices may ever be found. To get this to work, it is recommended to switch to the mainline kernel. See nixpkgs#123725 for more info.

Touch screen not working

You have to declare this in your configuration.nix[1]:

hardware.raspberry-pi."4" = {
  touch-ft5406.enable = true;
};