From fca8ee915dbc5a646f48d48a15e893d3d81e10a2 Mon Sep 17 00:00:00 2001 From: Jared Baur Date: Wed, 12 Jun 2024 17:02:35 -0700 Subject: [PATCH 1/2] nixos/systemd-boot: add support for devicetree entry The [Boot Loader Specification](https://uapi-group.org/specifications/specs/boot_loader_specification/) allows for using a key called "devicetree" for specifying which devicetree the bootloader should use during boot. With regards to systemd-boot, this key is used to specify which file should be picked up from the ESP to install to the EFI DTB Configuration Table. Linux then uses this Configuration Table to setup the machine. This change is similar to the one done in https://github.com/NixOS/nixpkgs/pull/295096, where that change was for adding DTB support to systemd-stub, and this is for systemd-boot. --- .../loader/systemd-boot/systemd-boot-builder.py | 15 ++++++++++++--- .../boot/loader/systemd-boot/systemd-boot.nix | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py index 87ea569bdb74e48..581c6cd36ed864c 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py @@ -47,6 +47,7 @@ class BootSpec: toplevel: str specialisations: dict[str, "BootSpec"] sortKey: str # noqa: N815 + devicetree: str | None = None # noqa: N815 initrdSecrets: str | None = None # noqa: N815 @dataclass @@ -84,6 +85,7 @@ class DiskEntry: kernel_params: str | None machine_id: str | None sort_key: str + devicetree: str | None @classmethod def from_path(cls: Type["DiskEntry"], path: Path) -> "DiskEntry": @@ -108,7 +110,9 @@ def from_path(cls: Type["DiskEntry"], path: Path) -> "DiskEntry": initrd=entry_map["initrd"], kernel_params=entry_map.get("options"), machine_id=entry_map.get("machine-id"), - sort_key=entry_map.get("sort_key", "nixos")) + sort_key=entry_map.get("sort_key", "nixos"), + devicetree=entry_map.get("devicetree"), + ) return disk_entry def write(self, sorted_first: str) -> None: @@ -127,7 +131,8 @@ def write(self, sorted_first: str) -> None: f"initrd {self.initrd}", f"options {self.kernel_params}" if self.kernel_params is not None else None, f"machine-id {self.machine_id}" if self.machine_id is not None else None, - f"sort-key {default_sort_key if self.default else self.sort_key}" + f"sort-key {default_sort_key if self.default else self.sort_key}", + f"devicetree {self.devicetree}" if self.devicetree is not None else None, ] f.write("\n".join(filter(None, boot_entry))) @@ -233,10 +238,12 @@ def bootspec_from_json(bootspec_json: dict[str, Any]) -> BootSpec: specialisations = {k: bootspec_from_json(v) for k, v in specialisations.items()} systemdBootExtension = bootspec_json.get('org.nixos.systemd-boot', {}) sortKey = systemdBootExtension.get('sortKey', 'nixos') + devicetree = systemdBootExtension.get('devicetree') return BootSpec( **bootspec_json['org.nixos.bootspec.v1'], specialisations=specialisations, - sortKey=sortKey + sortKey=sortKey, + devicetree=devicetree, ) @@ -261,6 +268,7 @@ def write_entry(profile: str | None, bootspec = bootspec.specialisations[specialisation] kernel = copy_from_file(bootspec.kernel) initrd = copy_from_file(bootspec.initrd) + devicetree = copy_from_file(bootspec.devicetree) if bootspec.devicetree is not None else None title = "{name}{profile}{specialisation}".format( name=DISTRO_NAME, @@ -303,6 +311,7 @@ def write_entry(profile: str | None, machine_id=machine_id, description=f"Generation {generation} {bootspec.label}, built on {build_date}", sort_key=bootspec.sortKey, + devicetree=devicetree, default=current ).write(sorted_first) diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix index b1b2b02e6d0493c..f4beb557e3314d6 100644 --- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix +++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix @@ -191,6 +191,15 @@ in { ''; }; + installDeviceTree = mkOption { + default = with config.hardware.deviceTree; enable && name != null; + defaultText = ''with config.hardware.deviceTree; enable && name != null''; + description = '' + Install the devicetree blob specified by `config.hardware.deviceTree.name` + to the ESP and instruct systemd-boot to pass this DTB to linux. + ''; + }; + extraInstallCommands = mkOption { default = ""; example = '' @@ -353,6 +362,10 @@ in { assertion = (config.boot.kernelPackages.kernel.features or { efiBootStub = true; }) ? efiBootStub; message = "This kernel does not support the EFI boot stub"; } + { + assertion = cfg.installDeviceTree -> config.hardware.deviceTree.enable -> config.hardware.deviceTree.name != null; + message = "Cannot install devicetree without 'config.hardware.deviceTree.enable' enabled and 'config.hardware.deviceTree.name' set"; + } ] ++ concatMap (filename: [ { assertion = !(hasInfix "/" filename); @@ -410,6 +423,7 @@ in { boot.bootspec.extensions."org.nixos.systemd-boot" = { inherit (config.boot.loader.systemd-boot) sortKey; + devicetree = lib.mkIf cfg.installDeviceTree "${config.hardware.deviceTree.package}/${config.hardware.deviceTree.name}"; }; system = { From 22199c70d468a2d5f93a6b232a32225cac5a4dc6 Mon Sep 17 00:00:00 2001 From: Jared Baur Date: Thu, 1 Aug 2024 11:18:32 -0700 Subject: [PATCH 2/2] nixos/systemd-boot: add test for devicetree --- nixos/tests/systemd-boot.nix | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/nixos/tests/systemd-boot.nix b/nixos/tests/systemd-boot.nix index ca6464614d4d815..a9f42900addd0c7 100644 --- a/nixos/tests/systemd-boot.nix +++ b/nixos/tests/systemd-boot.nix @@ -172,10 +172,23 @@ rec { imports = [ common ]; specialisation.something.configuration = { boot.loader.systemd-boot.sortKey = "something"; + + # Since qemu will dynamically create a devicetree blob when starting + # up, it is not straight forward to create an export of that devicetree + # blob without knowing before-hand all the flags we would pass to qemu + # (we would then be able to use `dumpdtb`). Thus, the following config + # will not boot, but it does allow us to assert that the boot entry has + # the correct contents. + boot.loader.systemd-boot.installDeviceTree = pkgs.stdenv.hostPlatform.isAarch64; + hardware.deviceTree.name = "dummy.dtb"; + hardware.deviceTree.package = lib.mkForce (pkgs.runCommand "dummy-devicetree-package" { } '' + mkdir -p $out + cp ${pkgs.emptyFile} $out/dummy.dtb + ''); }; }; - testScript = '' + testScript = { nodes, ... }: '' machine.start() machine.wait_for_unit("multi-user.target") @@ -188,6 +201,10 @@ rec { machine.succeed( "grep 'sort-key something' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" ) + '' + pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isAarch64 '' + machine.succeed( + "grep 'devicetree .*dummy' /boot/loader/entries/nixos-generation-1-specialisation-something.conf" + ) ''; };