11#! /usr/bin/env bash
22#
3- # https://github.com/t2linux/wiki/blob/master/docs/tools/firmware.sh
43# Copyright (C) 2022 Aditya Garg <gargaditya08@live.com>
54# Copyright (C) 2022 Orlando Chamberlain <redecorating@protonmail.com>
65#
@@ -50,24 +49,24 @@ case "$os" in
5049 echo " Unmounting the EFI partition"
5150 sudo diskutil unmount disk0s1
5251 echo
53- echo -e " Run the following commands or run this script itself in Linux now to set up Wi-Fi :-\n\nsudo umount /dev/nvme0n1p1\nsudo mkdir /tmp/apple-wifi-efi\nsudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi\nbash /tmp/apple-wifi-efi/firmware.sh\n"
52+ echo -e " Run the following commands or run this script itself in Linux now to set up Wi-Fi :-\n\nsudo umount /dev/nvme0n1p1\nsudo mkdir -p /tmp/apple-wifi-efi\nsudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi\nbash /tmp/apple-wifi-efi/firmware.sh\n"
5453 ;;
5554 (Linux)
5655 echo " Detected Linux"
5756 echo " Re-mounting the EFI partition"
5857 if [[ ${1-default} = -v ]]
5958 then
6059 sudo umount -v /dev/nvme0n1p1 || true
61- sudo mkdir -v /tmp/apple-wifi-efi || true
60+ sudo mkdir -p - v /tmp/apple-wifi-efi || true
6261 sudo mount -v /dev/nvme0n1p1 /tmp/apple-wifi-efi || true
6362 else
6463 sudo umount /dev/nvme0n1p1 2> /dev/null || true
65- sudo mkdir /tmp/apple-wifi-efi 2> /dev/null || true
64+ sudo mkdir -p /tmp/apple-wifi-efi 2> /dev/null || true
6665 sudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi 2> /dev/null || true
6766 fi
6867 mountpoint=$( findmnt -n -o TARGET /dev/nvme0n1p1)
6968 echo " Getting Wi-Fi and Bluetooth firmware"
70- sudo mkdir /tmp/apple-wifi-fw
69+ sudo mkdir -p /tmp/apple-wifi-fw
7170 cd /tmp/apple-wifi-fw
7271 if [[ ${1-default} = -v ]]
7372 then
@@ -134,14 +133,20 @@ case "$os" in
134133esac
135134exit 0
136135" " "
136+
137137# SPDX-License-Identifier: MIT
138138import logging, os, os.path, re, sys
139139from collections import namedtuple, defaultdict
140+
140141#from .core import FWFile
142+
141143log = logging.getLogger(" asahi_firmware.bluetooth" )
144+
142145BluetoothChip = namedtuple(
143146 " BluetoothChip" , (" chip" , " stepping" , " board_type" , " vendor" )
144147)
148+
149+
145150class BluetoothFWCollection(object):
146151 VENDORMAP = {
147152 " MUR" : " m" ,
@@ -151,12 +156,15 @@ class BluetoothFWCollection(object):
151156 STRIP_SUFFIXES = [
152157 " ES2"
153158 ]
159+
154160 def __init__(self, source_path):
155161 self.fwfiles = defaultdict(lambda: [None, None])
156162 self.load(source_path)
163+
157164 def load(self, source_path):
158165 for fname in os.listdir(source_path):
159166 root, ext = os.path.splitext(fname)
167+
160168 # index for bin and ptb inside self.fwfiles
161169 if ext == " .bin" :
162170 idx = 0
@@ -165,39 +173,49 @@ class BluetoothFWCollection(object):
165173 else:
166174 # skip firmware for older (UART) chips
167175 continue
176+
168177 # skip T2 _DEV firmware
169178 if " _DEV" in root:
170179 continue
180+
171181 chip = self.parse_fname(root)
172182 if chip is None:
173183 continue
184+
174185 if self.fwfiles[chip][idx] is not None:
175186 log.warning(f" duplicate entry for {chip}: {self.fwfiles[chip][idx].name} and now {fname + ext}" )
176187 continue
188+
177189 path = os.path.join(source_path, fname)
178190 with open(path, " rb" ) as f:
179191 data = f.read()
192+
180193 self.fwfiles[chip][idx] = FWFile(fname, data)
194+
181195 def parse_fname(self, fname):
182196 fname = fname.split(" _" )
197+
183198 match = re.fullmatch(" bcm(43[0-9]{2})([a-z][0-9])" , fname[0].lower())
184199 if not match:
185200 log.warning(f" Unexpected firmware file: {fname}" )
186201 return None
187202 chip, stepping = match.groups()
203+
188204 # board type is either preceeded by PCIE_macOS or by PCIE
189205 try:
190206 pcie_offset = fname.index(" PCIE" )
191207 except:
192208 log.warning(f" Can' t find board type in {fname}")
193209 return None
210+
194211 if fname[pcie_offset + 1] == "macOS":
195212 board_type = fname[pcie_offset + 2]
196213 else:
197214 board_type = fname[pcie_offset + 1]
198215 for i in self.STRIP_SUFFIXES:
199216 board_type = board_type.rstrip(i)
200217 board_type = "apple," + board_type.lower()
218+
201219 # make sure we can identify exactly one vendor
202220 otp_values = set()
203221 for vendor, otp_value in self.VENDORMAP.items():
@@ -207,44 +225,57 @@ class BluetoothFWCollection(object):
207225 log.warning(f"Unable to determine vendor ({otp_values}) in {fname}")
208226 return None
209227 vendor = otp_values.pop()
228+
210229 return BluetoothChip(
211230 chip=chip, stepping=stepping, board_type=board_type, vendor=vendor
212231 )
232+
213233 def files(self):
214234 for chip, (bin, ptb) in self.fwfiles.items():
215235 fname_base = f"brcm/brcmbt{chip.chip}{chip.stepping}-{chip.board_type}"
216236 if chip.vendor is not None:
217237 fname_base += f"-{chip.vendor}"
238+
218239 if bin is None:
219240 log.warning(f"no bin for {chip}")
220241 continue
221242 else:
222243 yield fname_base + ".bin", bin
244+
223245 if ptb is None:
224246 log.warning(f"no ptb for {chip}")
225247 continue
226248 else:
227249 yield fname_base + ".ptb", ptb
250+
251+
228252# SPDX-License-Identifier: MIT
229253import sys, os, os.path, pprint, statistics, logging
230254#from .core import FWFile
255+
231256log = logging.getLogger("asahi_firmware.wifi")
257+
232258class FWNode(object):
233259 def __init__(self, this=None, leaves=None):
234260 if leaves is None:
235261 leaves = {}
236262 self.this = this
237263 self.leaves = leaves
264+
238265 def __eq__(self, other):
239266 return self.this == other.this and self.leaves == other.leaves
267+
240268 def __hash__(self):
241269 return hash((self.this, tuple(self.leaves.items())))
270+
242271 def __repr__(self):
243272 return f"FWNode({self.this!r}, {self.leaves!r})"
273+
244274 def print(self, depth=0, tag=""):
245275 print(f"{' ' * depth} * {tag}: {self.this or ' ' } ({hash(self)})")
246276 for k, v in self.leaves.items():
247277 v.print(depth + 1, k)
278+
248279class WiFiFWCollection(object):
249280 EXTMAP = {
250281 "trx": "bin",
@@ -257,8 +288,11 @@ class WiFiFWCollection(object):
257288 self.root = FWNode()
258289 self.load(source_path)
259290 self.prune()
291+
260292 def load(self, source_path):
293+ included_folders = ["C-4355__s-C1", "C-4364__s-B2", "C-4364__s-B3", "C-4377__s-B3"]
261294 for dirpath, dirnames, filenames in os.walk(source_path):
295+ dirnames[:] = [d for d in dirnames if d in included_folders]
262296 if "perf" in dirnames:
263297 dirnames.remove("perf")
264298 if "assert" in dirnames:
@@ -288,46 +322,58 @@ class WiFiFWCollection(object):
288322 if dim in props:
289323 ident.append(props.pop(dim))
290324 assert not props
325+
291326 node = self.root
292327 for k in ident:
293328 node = node.leaves.setdefault(k, FWNode())
294329 with open(path, "rb") as fd:
295330 data = fd.read()
331+
296332 if name.endswith(".txt"):
297333 data = self.process_nvram(data)
334+
298335 node.this = FWFile(relpath, data)
336+
299337 def prune(self, node=None, depth=0):
300338 if node is None:
301339 node = self.root
340+
302341 for i in node.leaves.values():
303342 self.prune(i, depth + 1)
343+
304344 if node.this is None and node.leaves and depth > 3:
305345 first = next(iter(node.leaves.values()))
306346 if all(i == first for i in node.leaves.values()):
307347 node.this = first.this
348+
308349 for i in node.leaves.values():
309350 if not i.this or not node.this:
310351 break
311352 if i.this != node.this:
312353 break
313354 else:
314355 node.leaves = {}
356+
315357 def _walk_files(self, node, ident):
316358 if node.this is not None:
317359 yield ident, node.this
318360 for k, subnode in node.leaves.items():
319361 yield from self._walk_files(subnode, ident + [k])
362+
320363 def files(self):
321364 for ident, fwfile in self._walk_files(self.root, []):
322365 (ext, chip, rev), rest = ident[:3], ident[3:]
323366 rev = rev.lower()
324367 ext = self.EXTMAP[ext]
368+
325369 if rest:
326370 rest = "," + "-".join(rest)
327371 else:
328372 rest = ""
329373 filename = f"brcm/brcmfmac{chip}{rev}-pcie.apple{rest}.{ext}"
374+
330375 yield filename, fwfile
376+
331377 def process_nvram(self, data):
332378 data = data.decode("ascii")
333379 keys = {}
@@ -339,33 +385,43 @@ class WiFiFWCollection(object):
339385 keys[key] = value
340386 # Clean up spurious whitespace that Linux does not like
341387 lines.append(f"{key.strip()}={value}\n")
388+
342389 return "".join(lines).encode("ascii")
390+
343391 def print(self):
344392 self.root.print()
393+
345394# SPDX-License-Identifier: MIT
346395import tarfile, io, logging
347396from hashlib import sha256
397+
348398class FWFile(object):
349399 def __init__(self, name, data):
350400 self.name = name
351401 self.data = data
352402 self.sha = sha256(data).hexdigest()
403+
353404 def __repr__(self):
354405 return f"FWFile({self.name!r}, <{self.sha[:16]}>)"
406+
355407 def __eq__(self, other):
356408 if other is None:
357409 return False
358410 return self.sha == other.sha
411+
359412 def __hash__(self):
360413 return hash(self.sha)
414+
361415class FWPackage(object):
362416 def __init__(self, target):
363417 self.path = target
364418 self.tarfile = tarfile.open(target, mode="w")
365419 self.hashes = {}
366420 self.manifest = []
421+
367422 def close(self):
368423 self.tarfile.close()
424+
369425 def add_file(self, name, data):
370426 ti = tarfile.TarInfo(name)
371427 fd = None
@@ -379,20 +435,28 @@ class FWPackage(object):
379435 fd = io.BytesIO(data.data)
380436 self.hashes[data.sha] = name
381437 self.manifest.append(f"FILE {name} SHA256 {data.sha}")
438+
382439 logging.info(f"+ {self.manifest[-1]}")
383440 self.tarfile.addfile(ti, fd)
441+
384442 def add_files(self, it):
385443 for name, data in it:
386444 self.add_file(name, data)
445+
387446 def save_manifest(self, filename):
388447 with open(filename, "w") as fd:
389448 for i in self.manifest:
390449 fd.write(i + "\n")
450+
391451 def __del__(self):
392452 self.tarfile.close()
453+
454+
455+
456+
393457pkg = FWPackage("firmware.tar")
394458col = WiFiFWCollection(sys.argv[1]+"/wifi")
395459pkg.add_files(sorted(col.files()))
396460col = BluetoothFWCollection(sys.argv[1]+"/bluetooth")
397461pkg.add_files(sorted(col.files()))
398- pkg.close()
462+ pkg.close()
0 commit comments