From 36d0027ef4a2d23476a5e29bc7f76c8861912b57 Mon Sep 17 00:00:00 2001 From: mkorje Date: Mon, 17 Mar 2025 12:40:06 +1100 Subject: [PATCH] apple/t2: add Wi-Fi and Bluetooth firmware option --- apple/t2/default.nix | 19 +++ apple/t2/pkgs/brcm-firmware/default.nix | 89 ++++++++++++ apple/t2/pkgs/brcm-firmware/fetchmacos.nix | 42 ++++++ .../get-firmware-standalone.patch | 133 ++++++++++++++++++ apple/t2/pkgs/brcm-firmware/get-firmware.nix | 35 +++++ apple/t2/pkgs/brcm-firmware/macrecovery.nix | 36 +++++ 6 files changed, 354 insertions(+) create mode 100644 apple/t2/pkgs/brcm-firmware/default.nix create mode 100644 apple/t2/pkgs/brcm-firmware/fetchmacos.nix create mode 100644 apple/t2/pkgs/brcm-firmware/get-firmware-standalone.patch create mode 100644 apple/t2/pkgs/brcm-firmware/get-firmware.nix create mode 100644 apple/t2/pkgs/brcm-firmware/macrecovery.nix diff --git a/apple/t2/default.nix b/apple/t2/default.nix index 28f9258a..38115ba2 100644 --- a/apple/t2/default.nix +++ b/apple/t2/default.nix @@ -63,6 +63,19 @@ in example = "latest"; description = "The kernel release stream to use."; }; + firmware = { + enable = lib.mkEnableOption "automatic and declarative Wi-Fi and Bluetooth firmware configuration"; + version = lib.mkOption { + type = types.enum [ + "monterey" + "ventura" + "sonoma" + ]; + default = "sonoma"; + example = "ventura"; + description = "The macOS version to use."; + }; + }; }; config = lib.mkMerge [ @@ -105,5 +118,11 @@ in options apple-gmux force_igd=y ''; }) + (lib.mkIf t2Cfg.firmware.enable { + # Configure Wi-Fi and Bluetooth firmware + hardware.firmware = [ + (pkgs.callPackage ./pkgs/brcm-firmware { version = t2Cfg.firmware.version; }) + ]; + }) ]; } diff --git a/apple/t2/pkgs/brcm-firmware/default.nix b/apple/t2/pkgs/brcm-firmware/default.nix new file mode 100644 index 00000000..742d6ff6 --- /dev/null +++ b/apple/t2/pkgs/brcm-firmware/default.nix @@ -0,0 +1,89 @@ +{ + lib, + stdenvNoCC, + callPackage, + vmTools, + util-linux, + linux, + kmod, + version, +}: + +let + get-firmware = callPackage ./get-firmware.nix { }; + fetchmacos = callPackage ./fetchmacos.nix { }; + + # See https://github.com/kholia/OSX-KVM/blob/master/fetch-macOS-v2.py#L534-L546. + # Versions before macOS Monterey don't have Bluetooth firmware. + # Whereas macOS Sequoia doesn't have firmware for MacBook Air 2018 and 2019. + boards = { + monterey = { + boardId = "Mac-B809C3757DA9BB8D"; + mlb = "00000000000000000"; + osType = "latest"; + hash = "sha256-My8FLnqHZn+THfGPIhTSApW/kIWM0ZZhjBxWujhhWPM="; + }; + ventura = { + boardId = "Mac-4B682C642B45593E"; + mlb = "00000000000000000"; + osType = "latest"; + hash = "sha256-Qy9Whu8pqHo+m6wHnCIqURAR53LYQKc0r87g9eHgnS4="; + }; + sonoma = { + boardId = "Mac-827FAC58A8FDFA22"; + mlb = "00000000000000000"; + osType = "default"; + hash = "sha256-phlpwNTYhugqX2KGljqxpbfGtCFDgggQPzB7U29XSmM="; + }; + }; +in + +vmTools.runInLinuxVM ( + stdenvNoCC.mkDerivation { + pname = "brcm-firmware"; + inherit version; + + src = fetchmacos { + name = version; + inherit (boards.${version}) + boardId + mlb + osType + hash + ; + }; + dontUnpack = true; + + nativeBuildInputs = [ + util-linux + get-firmware + ]; + buildPhase = '' + ln -s ${linux}/lib /lib + ${kmod}/bin/modprobe loop + ${kmod}/bin/modprobe hfsplus + + imgdir=$(mktemp -d) + loopdev=$(losetup -f | cut -d "/" -f 3) + losetup -P $loopdev $src + loopdev_partition=/dev/$(lsblk -o KNAME,TYPE,MOUNTPOINT -n | grep $loopdev | tail -1 | awk '{print $1}') + mount $loopdev_partition $imgdir + + get-bluetooth $imgdir/usr/share/firmware/bluetooth bluetooth/ + get-wifi $imgdir/usr/share/firmware/wifi wifi/ + ''; + + installPhase = '' + mkdir -p $out/lib/firmware/brcm + cp bluetooth/brcm/* $out/lib/firmware/brcm/ + cp wifi/brcm/* $out/lib/firmware/brcm/ + ''; + + meta = with lib; { + description = "Wi-Fi and Bluetooth firmware for T2 Macs"; + license = licenses.unfree; + maintainers = with maintainers; [ mkorje ]; + platforms = platforms.linux; + }; + } +) diff --git a/apple/t2/pkgs/brcm-firmware/fetchmacos.nix b/apple/t2/pkgs/brcm-firmware/fetchmacos.nix new file mode 100644 index 00000000..8af21291 --- /dev/null +++ b/apple/t2/pkgs/brcm-firmware/fetchmacos.nix @@ -0,0 +1,42 @@ +{ + lib, + stdenvNoCC, + fetchFromGitHub, + callPackage, + dmg2img, +}: + +let + macrecovery = callPackage ./macrecovery.nix { }; +in + +{ + name, + boardId, + mlb, + osType, + hash, +}: + +stdenvNoCC.mkDerivation { + name = name; + + dontUnpack = true; + + nativeBuildInputs = [ + macrecovery + dmg2img + ]; + buildPhase = '' + macrecovery download -o . -b ${boardId} -m ${mlb} -os ${osType} + dmg2img -s BaseSystem.dmg fw.img + ''; + + installPhase = '' + cp fw.img $out + ''; + + outputHashMode = "recursive"; + outputHashAlgo = "sha256"; + outputHash = hash; +} diff --git a/apple/t2/pkgs/brcm-firmware/get-firmware-standalone.patch b/apple/t2/pkgs/brcm-firmware/get-firmware-standalone.patch new file mode 100644 index 00000000..f0b13f22 --- /dev/null +++ b/apple/t2/pkgs/brcm-firmware/get-firmware-standalone.patch @@ -0,0 +1,133 @@ +diff --git a/asahi_firmware/bluetooth.py b/asahi_firmware/bluetooth.py +index 0934225..3eaa442 100644 +--- a/asahi_firmware/bluetooth.py ++++ b/asahi_firmware/bluetooth.py +@@ -1,8 +1,25 @@ ++#!/usr/bin/env python3 + # SPDX-License-Identifier: MIT + import logging, os, os.path, re, sys + from collections import namedtuple, defaultdict ++from hashlib import sha256 + +-from .core import FWFile ++class FWFile(object): ++ def __init__(self, name, data): ++ self.name = name ++ self.data = data ++ self.sha = sha256(data).hexdigest() ++ ++ def __repr__(self): ++ return f"FWFile({self.name!r}, <{self.sha[:16]}>)" ++ ++ def __eq__(self, other): ++ if other is None: ++ return False ++ return self.sha == other.sha ++ ++ def __hash__(self): ++ return hash(self.sha) + + log = logging.getLogger("asahi_firmware.bluetooth") + +@@ -127,16 +144,16 @@ class BluetoothFWCollection(object): + + if __name__ == "__main__": + col = BluetoothFWCollection(sys.argv[1]) +- +- if len(sys.argv) > 2: +- from . import FWPackage +- +- pkg = FWPackage(sys.argv[2]) +- pkg.add_files(sorted(col.files())) +- pkg.close() +- +- for i in pkg.manifest: +- print(i) +- else: +- for name, fwfile in col.files(): +- print(name, f"{fwfile.name} ({len(fwfile.data)} bytes)") ++ ++ dir = os.path.join(sys.argv[2], "brcm") ++ os.makedirs(dir) ++ ++ hashes = {} ++ for name, data in sorted(col.files()): ++ path = os.path.join(sys.argv[2], name) ++ if data.sha in hashes: ++ os.link(hashes[data.sha], path) ++ else: ++ with open(path, "wb") as f: ++ f.write(data.data) ++ hashes[data.sha] = path +diff --git a/asahi_firmware/wifi.py b/asahi_firmware/wifi.py +index 346965c..261aa32 100644 +--- a/asahi_firmware/wifi.py ++++ b/asahi_firmware/wifi.py +@@ -1,6 +1,24 @@ ++#!/usr/bin/env python3 + # SPDX-License-Identifier: MIT + import sys, os, os.path, pprint, statistics, logging +-from .core import FWFile ++from hashlib import sha256 ++ ++class FWFile(object): ++ def __init__(self, name, data): ++ self.name = name ++ self.data = data ++ self.sha = sha256(data).hexdigest() ++ ++ def __repr__(self): ++ return f"FWFile({self.name!r}, <{self.sha[:16]}>)" ++ ++ def __eq__(self, other): ++ if other is None: ++ return False ++ return self.sha == other.sha ++ ++ def __hash__(self): ++ return hash(self.sha) + + log = logging.getLogger("asahi_firmware.wifi") + +@@ -40,7 +58,9 @@ class WiFiFWCollection(object): + self.prune() + + def load(self, source_path): ++ included_folders = ["C-4355__s-C1", "C-4364__s-B2", "C-4364__s-B3", "C-4377__s-B3"] + for dirpath, dirnames, filenames in os.walk(source_path): ++ dirnames[:] = [d for d in dirnames if d in included_folders] + if "perf" in dirnames: + dirnames.remove("perf") + if "assert" in dirnames: +@@ -141,18 +161,16 @@ class WiFiFWCollection(object): + + if __name__ == "__main__": + col = WiFiFWCollection(sys.argv[1]) +- if len(sys.argv) > 2: +- from .core import FWPackage +- +- pkg = FWPackage(sys.argv[2]) +- pkg.add_files(sorted(col.files())) +- pkg.close() +- +- for i in pkg.manifest: +- print(i) +- else: +- for name, fwfile in col.files(): +- if isinstance(fwfile, str): +- print(name, "->", fwfile) +- else: +- print(name, f"({len(fwfile.data)} bytes)") ++ ++ dir = os.path.join(sys.argv[2], "brcm") ++ os.makedirs(dir) ++ ++ hashes = {} ++ for name, data in sorted(col.files()): ++ path = os.path.join(sys.argv[2], name) ++ if data.sha in hashes: ++ os.link(hashes[data.sha], path) ++ else: ++ with open(path, "wb") as f: ++ f.write(data.data) ++ hashes[data.sha] = path diff --git a/apple/t2/pkgs/brcm-firmware/get-firmware.nix b/apple/t2/pkgs/brcm-firmware/get-firmware.nix new file mode 100644 index 00000000..9c294016 --- /dev/null +++ b/apple/t2/pkgs/brcm-firmware/get-firmware.nix @@ -0,0 +1,35 @@ +{ + lib, + stdenvNoCC, + fetchFromGitHub, + python3, +}: + +stdenvNoCC.mkDerivation { + name = "get-firmware"; + + src = fetchFromGitHub { + owner = "AsahiLinux"; + repo = "asahi-installer"; + rev = "v0.7.9"; + hash = "sha256-vbhepoZ52k5tW2Gd7tfQTZ5CLqzhV7dUcVh6+AYwECk="; + }; + + patches = [ ./get-firmware-standalone.patch ]; + + buildInputs = [ python3 ]; + + installPhase = '' + cd asahi_firmware + install -Dm755 bluetooth.py $out/bin/get-bluetooth + install -Dm755 wifi.py $out/bin/get-wifi + ''; + + meta = with lib; { + description = "Patched Asahi Linux Installer scripts to get brcm firmware"; + homepage = "https://github.com/AsahiLinux/asahi-installer"; + license = licenses.mit; + maintainers = with maintainers; [ mkorje ]; + platforms = platforms.all; + }; +} diff --git a/apple/t2/pkgs/brcm-firmware/macrecovery.nix b/apple/t2/pkgs/brcm-firmware/macrecovery.nix new file mode 100644 index 00000000..eb4a867a --- /dev/null +++ b/apple/t2/pkgs/brcm-firmware/macrecovery.nix @@ -0,0 +1,36 @@ +{ + lib, + stdenvNoCC, + fetchFromGitHub, + python3, +}: + +stdenvNoCC.mkDerivation { + name = "macrecovery"; + + src = fetchFromGitHub { + owner = "acidanthera"; + repo = "OpenCorePkg"; + rev = "1.0.4"; + hash = "sha256-5Eypza9teSJSulHaK7Sxh562cTKedXKn3y+Z3+fC6sM="; + }; + + buildInputs = [ python3 ]; + + installPhase = '' + cd Utilities/macrecovery + install -Dm755 macrecovery.py $out/opt/macrecovery + cp boards.json $out/opt/boards.json + mkdir $out/bin + ln -s $out/opt/macrecovery $out/bin/macrecovery + ''; + + meta = with lib; { + description = "A tool that helps to automate recovery interaction"; + homepage = "https://github.com/acidanthera/OpenCorePkg"; + license = licenses.bsd3; + maintainers = with maintainers; [ mkorje ]; + mainProgram = "macrecovery"; + platforms = platforms.all; + }; +}