{ config, lib, pkgs, namespace, ... }: let cfg = config.${namespace}.hardware.raspberry-pi; dt_ao_overlay = _final: prev: { deviceTree = prev.deviceTree // { applyOverlays = _final.callPackage ./apply-overlays-dtmerge.nix { }; }; }; # installs raspberry's firmware independent of the nixos generations # sometimes referred to as "boot code" raspberryPiFirmware = ( { pkgs, firmware, configTxt, }: pkgs.replaceVarsWith { src = ./generational/install-firmware.sh; isExecutable = true; replacements = { inherit (pkgs) bash; path = pkgs.lib.makeBinPath [ pkgs.coreutils ]; inherit firmware configTxt; }; } ); kernelbootGenBuilder = ( { pkgs, deviceTreeInstaller, }: pkgs.replaceVarsWith { src = ./generational/kernelboot-gen-builder.sh; isExecutable = true; replacements = { inherit (pkgs) bash; path = pkgs.lib.makeBinPath [ pkgs.coreutils ]; installDeviceTree = deviceTreeInstaller; }; } ); deviceTree = ( { pkgs, firmware, }: pkgs.replaceVarsWith { src = ./generational/install-device-tree.sh; isExecutable = true; replacements = { inherit (pkgs) bash; path = pkgs.lib.makeBinPath [ pkgs.coreutils ]; inherit firmware; }; } ); mkBootloader = pkgs: bootloader { inherit pkgs; inherit (cfg) nixosGenerationsDir; firmwareInstaller = "${raspberryPiFirmware { inherit pkgs; firmware = pkgs.${namespace}.raspberrypifw; configTxt = pkgs.writeTextFile { name = "config.txt"; text = '' # Do not edit! # This configuration file is generated from NixOS configuration # options `hardware.raspberry-pi.config`. # Any manual changes will be overwritten on the next configuration # switch. ${config.${namespace}.hardware.raspberry-pi.config-generated} ''; }; }}"; nixosGenBuilder = "${kernelbootGenBuilder { inherit pkgs; deviceTreeInstaller = let cmd = deviceTree { inherit pkgs; firmware = cfg.firmwarePackage; }; args = lib.optionalString (!cfg.useGenerationDeviceTree) " -r"; in "${cmd} ${args}"; }}"; }; bootloader = ( { pkgs, nixosGenerationsDir, firmwareInstaller, nixosGenBuilder, }: pkgs.replaceVarsWith { src = ./generational/nixos-generations-builder.sh; isExecutable = true; replacements = { inherit (pkgs) bash; path = pkgs.lib.makeBinPath [ pkgs.coreutils pkgs.gnused ]; # NixOS-generations -independent installFirmwareBuilder = firmwareInstaller; # NixOS-generations -dependent inherit nixosGenerationsDir nixosGenBuilder; }; } ); # Builders used to write during system activation ubootBuilder = import ./uboot-builder.nix { inherit pkgs; ubootPackage = ( if (cfg.variant == "5") then pkgs.${namespace}.uboot-pi5 else pkgs.${namespace}.uboot-pi4 ); firmwareBuilder = firmwarePopulateCmd; extlinuxConfBuilder = config.boot.loader.generic-extlinux-compatible.populateCmd; }; uefiBuilder = import ./uefi-builder.nix { inherit pkgs; uefiPackage = ( if (cfg.variant == "5") then pkgs.${namespace}.uefi-rpi5 else pkgs.${namespace}.edk2.override { MODEL = "4"; } ); firmwareBuilder = firmwarePopulateCmd; }; # Builders exposed via populateCmd, which run on the build architecture populateFirmwareBuilder = import ./firmware-builder.nix { pkgs = pkgs.buildPackages; configTxt = pkgs.writeTextFile { name = "config.txt"; text = '' # Do not edit! # This configuration file is generated from NixOS configuration # options `hardware.raspberry-pi.config`. # Any manual changes will be overwritten on the next configuration # switch. ${config.${namespace}.hardware.raspberry-pi.config-generated} ''; }; firmware = pkgs.${namespace}.raspberrypifw; }; firmwarePopulateCmd = "${populateFirmwareBuilder} ${firmwareBuilderArgs}"; firmwareBuilderArgs = lib.optionalString (!true) " -r"; # these will receive the top-level path as an argument when invoked as # system.build.installBootloader builder = { # system.build.installBootLoader uboot = "${ubootBuilder} -f /boot/firmware -b /boot -c"; uefi = "${uefiBuilder} -f /boot/firmware -b /boot -c"; kernel = builtins.concatStringsSep " " [ "${mkBootloader pkgs}" "-g ${toString 10}" "-f /boot/firmware" "-c" ]; }; # firmware: caller must provide `-c ` and `-f ` # boot: caller must provide `-c ` and `-b ` in { options.${namespace}.hardware.raspberry-pi = { enable = lib.mkEnableOption "Raspberry Pi common configuration"; variant = lib.mkOption { type = lib.types.enum [ "4" "5" ]; description = "Raspberry Pi variant (4 or 5)"; }; bootType = lib.mkOption { type = lib.types.enum [ "uefi" "uboot" "kernel" ]; default = "uefi"; }; apply-overlays-dtmerge = { enable = lib.mkEnableOption "" // { description = '' Whether replace deviceTree.applyOverlays implementation to use dtmerge from libraspberrypi. This can resolve issues with applying dtbs for the pi. ''; }; }; }; imports = [ ./audio.nix ./bluetooth.nix ./config.nix ./i2c.nix ./leds.nix ./modesetting.nix ./pwm.nix ./wifi.nix ]; config = lib.mkIf cfg.enable { boot = { initrd.availableKernelModules = [ "usbhid" "usb-storage" ] ++ ( if (cfg.variant == "5") then [ "nvme" ] else [ "vc4" "pcie-brcmstb" # required for the pcie bus to work "reset-raspberrypi" # required for vl805 firmware to load ] ); loader = { # kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target; generic-extlinux-compatible = { enable = lib.mkDefault (if cfg.bootType == "uefi" then false else true); useGenerationDeviceTree = lib.mkOverride 60 (if cfg.bootType == "uefi" then false else true); }; systemd-boot.enable = (if cfg.bootType == "uefi" then true else false); systemd-boot.extraInstallCommands = let bootloaderInstaller = (builder."${cfg.bootType}"); in '' ${bootloaderInstaller} -f /boot/firmware -b /boot -c ''; grub.enable = lib.mkForce false; }; }; # Common Raspberry Pi packages environment.systemPackages = with pkgs; [ dconf i2c-tools raspberrypi-eeprom pkgs.${namespace}.raspberrypi-utils pkgs.${namespace}.raspberrypifw pkgs.${namespace}.raspberryPiWirelessFirmware raspberrypi-armstubs erofs-utils fex squashfuse squashfsTools ]; # Common Bluetooth configuration systemd = { services.btattach = { before = [ "bluetooth.service" ]; after = [ "dev-ttyAMA0.device" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { ExecStart = "${lib.getExe' pkgs.bluez "btattach"} -B /dev/ttyAMA0 -P bcm -S 3000000"; }; }; tmpfiles.packages = [ pkgs.${namespace}.udev-rules ]; }; ${namespace}.hardware.raspberry-pi = { config = { all = { options = { os_prefix = lib.mkIf (cfg.bootType == "kernel") { enable = true; value = "${cfg.nixosGenerationsDir}/default/"; # "nixos//" }; kernel = lib.mkIf (cfg.bootType == "kernel" || cfg.bootType == "uboot") { enable = true; value = ( if cfg.bootType == "uboot" then "u-boot.bin" else if cfg.bootType == "kernel" then "kernel.img" else "" ); }; }; }; }; extra-config = let # https://www.raspberrypi.com/documentation/computers/config_txt.html#initramfs ramfsfile = "initrd"; ramfsaddr = "followkernel"; # same as 0 = "after the kernel image" in '' [all] initramfs ${ramfsfile} ${ramfsaddr} ''; }; # Common hardware settings hardware = { deviceTree = { enable = true; package = config.boot.kernelPackages.kernel; name = "bcm2712-rpi5-b.dtb"; filter = lib.mkDefault (if (cfg.variant == "5") then "bcm2712-rpi5-*.dtb" else "bcm2711-rpi4-*.dtb"); overlays = ( if (cfg.variant == "4") then [ { name = "rpi4-cpu-revision"; dtsText = '' /dts-v1/; /plugin/; / { compatible = "brcm,bcm2711"; fragment@0 { target-path = "/"; __overlay__ { system { linux,revision = <0x00d03114>; }; }; }; }; ''; } ] else [ # { # name = "bcm2712d0-overlay"; # dtsFile = "${pkgs.${namespace}.raspberrypi-overlays}/dtbs/raspberrypi-overlays/bcm2712d0-overlay.dts"; # } ] ); }; firmware = [ pkgs.${namespace}.raspberryPiWirelessFirmware ]; graphics.enable32Bit = lib.mkForce false; i2c.enable = lib.mkDefault true; }; system = { #build.installBootLoader = lib.mkOverride 60 (if cfg.bootType == "uefi" then (builder."uefi") else (builder."uboot")); # todo #boot = { # loader = { # id = lib.mkOverride 60 (if cfg.bootType == "uefi" then "raspberrypi-uefi" else "raspberrypi-uboot"); # todo # kernelFile = pkgs.stdenv.hostPlatform.linux-kernel.target; # }; #}; # Pi specific system tags nixos.tags = [ "raspberry-pi-${cfg.variant}" # config.boot.loader.raspberry-pi.bootloader config.boot.kernelPackages.kernel.version ]; }; # Common programs programs.kdeconnect.enable = lib.mkDefault false; # Root user shell configuration users = { users.root.shell = pkgs.zsh; extraGroups = { gpio = { }; i2c = { }; input = { }; plugdev = { }; spi = { }; video = { }; }; }; services = { udev.packages = [ pkgs.${namespace}.udev-rules ]; xserver.extraConfig = let identifier = "rp1"; driver = "rp1-vec|rp1-dsi|rp1-dpi"; in '' Section "OutputClass" Identifier "${identifier}" MatchDriver "${driver}" Driver "modesetting" Option "PrimaryGPU" "true" EndSection ''; }; nixpkgs.overlays = [ ] ++ ( if cfg.variant == "5" then [ (_final: prev: { # https://github.com/nvmd/nixos-raspberrypi/issues/64 # credit for the initial version of this snippet goes to @micahcc jemalloc = prev.jemalloc.overrideAttrs (old: { # --with-lg-page=(log2 page_size) # RPi5 (bcm2712): since our page size is 16384 (2**14), we need 14 configureFlags = let pageSizeFlag = "--with-lg-page"; in (prev.lib.filter (flag: prev.lib.hasPrefix pageSizeFlag flag == false) old.configureFlags) ++ [ "${pageSizeFlag}=14" ]; }); }) ] else [ ] ) ++ (if cfg.apply-overlays-dtmerge.enable then [ dt_ao_overlay ] else [ ]); }; }