Skip to content

Commit 36d0027

Browse files
mkorjemergify[bot]
authored andcommitted
apple/t2: add Wi-Fi and Bluetooth firmware option
1 parent e8c83f0 commit 36d0027

File tree

6 files changed

+354
-0
lines changed

6 files changed

+354
-0
lines changed

apple/t2/default.nix

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ in
6363
example = "latest";
6464
description = "The kernel release stream to use.";
6565
};
66+
firmware = {
67+
enable = lib.mkEnableOption "automatic and declarative Wi-Fi and Bluetooth firmware configuration";
68+
version = lib.mkOption {
69+
type = types.enum [
70+
"monterey"
71+
"ventura"
72+
"sonoma"
73+
];
74+
default = "sonoma";
75+
example = "ventura";
76+
description = "The macOS version to use.";
77+
};
78+
};
6679
};
6780

6881
config = lib.mkMerge [
@@ -105,5 +118,11 @@ in
105118
options apple-gmux force_igd=y
106119
'';
107120
})
121+
(lib.mkIf t2Cfg.firmware.enable {
122+
# Configure Wi-Fi and Bluetooth firmware
123+
hardware.firmware = [
124+
(pkgs.callPackage ./pkgs/brcm-firmware { version = t2Cfg.firmware.version; })
125+
];
126+
})
108127
];
109128
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
lib,
3+
stdenvNoCC,
4+
callPackage,
5+
vmTools,
6+
util-linux,
7+
linux,
8+
kmod,
9+
version,
10+
}:
11+
12+
let
13+
get-firmware = callPackage ./get-firmware.nix { };
14+
fetchmacos = callPackage ./fetchmacos.nix { };
15+
16+
# See https://github.com/kholia/OSX-KVM/blob/master/fetch-macOS-v2.py#L534-L546.
17+
# Versions before macOS Monterey don't have Bluetooth firmware.
18+
# Whereas macOS Sequoia doesn't have firmware for MacBook Air 2018 and 2019.
19+
boards = {
20+
monterey = {
21+
boardId = "Mac-B809C3757DA9BB8D";
22+
mlb = "00000000000000000";
23+
osType = "latest";
24+
hash = "sha256-My8FLnqHZn+THfGPIhTSApW/kIWM0ZZhjBxWujhhWPM=";
25+
};
26+
ventura = {
27+
boardId = "Mac-4B682C642B45593E";
28+
mlb = "00000000000000000";
29+
osType = "latest";
30+
hash = "sha256-Qy9Whu8pqHo+m6wHnCIqURAR53LYQKc0r87g9eHgnS4=";
31+
};
32+
sonoma = {
33+
boardId = "Mac-827FAC58A8FDFA22";
34+
mlb = "00000000000000000";
35+
osType = "default";
36+
hash = "sha256-phlpwNTYhugqX2KGljqxpbfGtCFDgggQPzB7U29XSmM=";
37+
};
38+
};
39+
in
40+
41+
vmTools.runInLinuxVM (
42+
stdenvNoCC.mkDerivation {
43+
pname = "brcm-firmware";
44+
inherit version;
45+
46+
src = fetchmacos {
47+
name = version;
48+
inherit (boards.${version})
49+
boardId
50+
mlb
51+
osType
52+
hash
53+
;
54+
};
55+
dontUnpack = true;
56+
57+
nativeBuildInputs = [
58+
util-linux
59+
get-firmware
60+
];
61+
buildPhase = ''
62+
ln -s ${linux}/lib /lib
63+
${kmod}/bin/modprobe loop
64+
${kmod}/bin/modprobe hfsplus
65+
66+
imgdir=$(mktemp -d)
67+
loopdev=$(losetup -f | cut -d "/" -f 3)
68+
losetup -P $loopdev $src
69+
loopdev_partition=/dev/$(lsblk -o KNAME,TYPE,MOUNTPOINT -n | grep $loopdev | tail -1 | awk '{print $1}')
70+
mount $loopdev_partition $imgdir
71+
72+
get-bluetooth $imgdir/usr/share/firmware/bluetooth bluetooth/
73+
get-wifi $imgdir/usr/share/firmware/wifi wifi/
74+
'';
75+
76+
installPhase = ''
77+
mkdir -p $out/lib/firmware/brcm
78+
cp bluetooth/brcm/* $out/lib/firmware/brcm/
79+
cp wifi/brcm/* $out/lib/firmware/brcm/
80+
'';
81+
82+
meta = with lib; {
83+
description = "Wi-Fi and Bluetooth firmware for T2 Macs";
84+
license = licenses.unfree;
85+
maintainers = with maintainers; [ mkorje ];
86+
platforms = platforms.linux;
87+
};
88+
}
89+
)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
lib,
3+
stdenvNoCC,
4+
fetchFromGitHub,
5+
callPackage,
6+
dmg2img,
7+
}:
8+
9+
let
10+
macrecovery = callPackage ./macrecovery.nix { };
11+
in
12+
13+
{
14+
name,
15+
boardId,
16+
mlb,
17+
osType,
18+
hash,
19+
}:
20+
21+
stdenvNoCC.mkDerivation {
22+
name = name;
23+
24+
dontUnpack = true;
25+
26+
nativeBuildInputs = [
27+
macrecovery
28+
dmg2img
29+
];
30+
buildPhase = ''
31+
macrecovery download -o . -b ${boardId} -m ${mlb} -os ${osType}
32+
dmg2img -s BaseSystem.dmg fw.img
33+
'';
34+
35+
installPhase = ''
36+
cp fw.img $out
37+
'';
38+
39+
outputHashMode = "recursive";
40+
outputHashAlgo = "sha256";
41+
outputHash = hash;
42+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
diff --git a/asahi_firmware/bluetooth.py b/asahi_firmware/bluetooth.py
2+
index 0934225..3eaa442 100644
3+
--- a/asahi_firmware/bluetooth.py
4+
+++ b/asahi_firmware/bluetooth.py
5+
@@ -1,8 +1,25 @@
6+
+#!/usr/bin/env python3
7+
# SPDX-License-Identifier: MIT
8+
import logging, os, os.path, re, sys
9+
from collections import namedtuple, defaultdict
10+
+from hashlib import sha256
11+
12+
-from .core import FWFile
13+
+class FWFile(object):
14+
+ def __init__(self, name, data):
15+
+ self.name = name
16+
+ self.data = data
17+
+ self.sha = sha256(data).hexdigest()
18+
+
19+
+ def __repr__(self):
20+
+ return f"FWFile({self.name!r}, <{self.sha[:16]}>)"
21+
+
22+
+ def __eq__(self, other):
23+
+ if other is None:
24+
+ return False
25+
+ return self.sha == other.sha
26+
+
27+
+ def __hash__(self):
28+
+ return hash(self.sha)
29+
30+
log = logging.getLogger("asahi_firmware.bluetooth")
31+
32+
@@ -127,16 +144,16 @@ class BluetoothFWCollection(object):
33+
34+
if __name__ == "__main__":
35+
col = BluetoothFWCollection(sys.argv[1])
36+
-
37+
- if len(sys.argv) > 2:
38+
- from . import FWPackage
39+
-
40+
- pkg = FWPackage(sys.argv[2])
41+
- pkg.add_files(sorted(col.files()))
42+
- pkg.close()
43+
-
44+
- for i in pkg.manifest:
45+
- print(i)
46+
- else:
47+
- for name, fwfile in col.files():
48+
- print(name, f"{fwfile.name} ({len(fwfile.data)} bytes)")
49+
+
50+
+ dir = os.path.join(sys.argv[2], "brcm")
51+
+ os.makedirs(dir)
52+
+
53+
+ hashes = {}
54+
+ for name, data in sorted(col.files()):
55+
+ path = os.path.join(sys.argv[2], name)
56+
+ if data.sha in hashes:
57+
+ os.link(hashes[data.sha], path)
58+
+ else:
59+
+ with open(path, "wb") as f:
60+
+ f.write(data.data)
61+
+ hashes[data.sha] = path
62+
diff --git a/asahi_firmware/wifi.py b/asahi_firmware/wifi.py
63+
index 346965c..261aa32 100644
64+
--- a/asahi_firmware/wifi.py
65+
+++ b/asahi_firmware/wifi.py
66+
@@ -1,6 +1,24 @@
67+
+#!/usr/bin/env python3
68+
# SPDX-License-Identifier: MIT
69+
import sys, os, os.path, pprint, statistics, logging
70+
-from .core import FWFile
71+
+from hashlib import sha256
72+
+
73+
+class FWFile(object):
74+
+ def __init__(self, name, data):
75+
+ self.name = name
76+
+ self.data = data
77+
+ self.sha = sha256(data).hexdigest()
78+
+
79+
+ def __repr__(self):
80+
+ return f"FWFile({self.name!r}, <{self.sha[:16]}>)"
81+
+
82+
+ def __eq__(self, other):
83+
+ if other is None:
84+
+ return False
85+
+ return self.sha == other.sha
86+
+
87+
+ def __hash__(self):
88+
+ return hash(self.sha)
89+
90+
log = logging.getLogger("asahi_firmware.wifi")
91+
92+
@@ -40,7 +58,9 @@ class WiFiFWCollection(object):
93+
self.prune()
94+
95+
def load(self, source_path):
96+
+ included_folders = ["C-4355__s-C1", "C-4364__s-B2", "C-4364__s-B3", "C-4377__s-B3"]
97+
for dirpath, dirnames, filenames in os.walk(source_path):
98+
+ dirnames[:] = [d for d in dirnames if d in included_folders]
99+
if "perf" in dirnames:
100+
dirnames.remove("perf")
101+
if "assert" in dirnames:
102+
@@ -141,18 +161,16 @@ class WiFiFWCollection(object):
103+
104+
if __name__ == "__main__":
105+
col = WiFiFWCollection(sys.argv[1])
106+
- if len(sys.argv) > 2:
107+
- from .core import FWPackage
108+
-
109+
- pkg = FWPackage(sys.argv[2])
110+
- pkg.add_files(sorted(col.files()))
111+
- pkg.close()
112+
-
113+
- for i in pkg.manifest:
114+
- print(i)
115+
- else:
116+
- for name, fwfile in col.files():
117+
- if isinstance(fwfile, str):
118+
- print(name, "->", fwfile)
119+
- else:
120+
- print(name, f"({len(fwfile.data)} bytes)")
121+
+
122+
+ dir = os.path.join(sys.argv[2], "brcm")
123+
+ os.makedirs(dir)
124+
+
125+
+ hashes = {}
126+
+ for name, data in sorted(col.files()):
127+
+ path = os.path.join(sys.argv[2], name)
128+
+ if data.sha in hashes:
129+
+ os.link(hashes[data.sha], path)
130+
+ else:
131+
+ with open(path, "wb") as f:
132+
+ f.write(data.data)
133+
+ hashes[data.sha] = path
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
lib,
3+
stdenvNoCC,
4+
fetchFromGitHub,
5+
python3,
6+
}:
7+
8+
stdenvNoCC.mkDerivation {
9+
name = "get-firmware";
10+
11+
src = fetchFromGitHub {
12+
owner = "AsahiLinux";
13+
repo = "asahi-installer";
14+
rev = "v0.7.9";
15+
hash = "sha256-vbhepoZ52k5tW2Gd7tfQTZ5CLqzhV7dUcVh6+AYwECk=";
16+
};
17+
18+
patches = [ ./get-firmware-standalone.patch ];
19+
20+
buildInputs = [ python3 ];
21+
22+
installPhase = ''
23+
cd asahi_firmware
24+
install -Dm755 bluetooth.py $out/bin/get-bluetooth
25+
install -Dm755 wifi.py $out/bin/get-wifi
26+
'';
27+
28+
meta = with lib; {
29+
description = "Patched Asahi Linux Installer scripts to get brcm firmware";
30+
homepage = "https://github.com/AsahiLinux/asahi-installer";
31+
license = licenses.mit;
32+
maintainers = with maintainers; [ mkorje ];
33+
platforms = platforms.all;
34+
};
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
lib,
3+
stdenvNoCC,
4+
fetchFromGitHub,
5+
python3,
6+
}:
7+
8+
stdenvNoCC.mkDerivation {
9+
name = "macrecovery";
10+
11+
src = fetchFromGitHub {
12+
owner = "acidanthera";
13+
repo = "OpenCorePkg";
14+
rev = "1.0.4";
15+
hash = "sha256-5Eypza9teSJSulHaK7Sxh562cTKedXKn3y+Z3+fC6sM=";
16+
};
17+
18+
buildInputs = [ python3 ];
19+
20+
installPhase = ''
21+
cd Utilities/macrecovery
22+
install -Dm755 macrecovery.py $out/opt/macrecovery
23+
cp boards.json $out/opt/boards.json
24+
mkdir $out/bin
25+
ln -s $out/opt/macrecovery $out/bin/macrecovery
26+
'';
27+
28+
meta = with lib; {
29+
description = "A tool that helps to automate recovery interaction";
30+
homepage = "https://github.com/acidanthera/OpenCorePkg";
31+
license = licenses.bsd3;
32+
maintainers = with maintainers; [ mkorje ];
33+
mainProgram = "macrecovery";
34+
platforms = platforms.all;
35+
};
36+
}

0 commit comments

Comments
 (0)