mirror of
https://github.com/t2linux/wiki.git
synced 2026-04-30 13:32:14 -07:00
33d31df012
Older kernels do not have the Bluetooth driver, so running the script there may cause it to fail.
459 lines
14 KiB
Bash
459 lines
14 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Copyright (C) 2022 Aditya Garg <gargaditya08@live.com>
|
|
# Copyright (C) 2022 Orlando Chamberlain <redecorating@protonmail.com>
|
|
#
|
|
# The python script is based upon the original work by The Asahi Linux Contributors.
|
|
|
|
""":"
|
|
set -euo pipefail
|
|
|
|
os=$(uname -s)
|
|
case "$os" in
|
|
(Darwin)
|
|
echo "Detected macOS"
|
|
ver=$(sw_vers -productVersion | cut -d "." -f 1)
|
|
if [[ ${ver} < 12 ]]
|
|
then
|
|
echo -e "\nThis script is compatible only with macOS Monterey or later. Please upgrade your macOS."
|
|
exit 1
|
|
fi
|
|
identifier=$(system_profiler SPHardwareDataType | grep "Model Identifier" | cut -d ":" -f 2 | xargs)
|
|
echo "Mounting the EFI partition"
|
|
sudo diskutil mount disk0s1
|
|
echo "Getting Wi-Fi and Bluetooth firmware"
|
|
cd /usr/share/firmware
|
|
if [[ ${1-default} = -v ]]
|
|
then
|
|
tar czvf /Volumes/EFI/firmware.tar.gz *
|
|
else
|
|
tar czf /Volumes/EFI/firmware.tar.gz *
|
|
fi
|
|
if [[ (${identifier} = iMac19,1) || (${identifier} = iMac19,2) ]]
|
|
then
|
|
nvramfile=$(ioreg -l | grep RequestedFiles | cut -d "/" -f 5 | rev | cut -c 4- | rev)
|
|
txcapblob=$(ioreg -l | grep RequestedFiles | cut -d "/" -f 3 | cut -d "\"" -f 1)
|
|
if [[ ${1-default} = -v ]]
|
|
then
|
|
cp -v /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} /Volumes/EFI/brcmfmac4364b2-pcie.txt
|
|
cp -v /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} /Volumes/EFI/brcmfmac4364b2-pcie.txcap_blob
|
|
else
|
|
cp /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} /Volumes/EFI/brcmfmac4364b2-pcie.txt
|
|
cp /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} /Volumes/EFI/brcmfmac4364b2-pcie.txcap_blob
|
|
fi
|
|
fi
|
|
echo "Copying this script to EFI"
|
|
cd - >/dev/null
|
|
cp "$0" "/Volumes/EFI/firmware.sh"|| (echo -e "\nFailed to copy script.\nPlease copy the script manually to the EFI partition using Finder\nMake sure the name of the script is wifi.sh in the EFI partition\n" && echo && read -p "Press enter after you have copied" && echo)
|
|
echo "Unmounting the EFI partition"
|
|
sudo diskutil unmount disk0s1
|
|
echo
|
|
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"
|
|
;;
|
|
(Linux)
|
|
echo "Detected Linux"
|
|
echo "Re-mounting the EFI partition"
|
|
if [[ ${1-default} = -v ]]
|
|
then
|
|
sudo umount -v /dev/nvme0n1p1 || true
|
|
sudo mkdir -v /tmp/apple-wifi-efi || true
|
|
sudo mount -v /dev/nvme0n1p1 /tmp/apple-wifi-efi || true
|
|
else
|
|
sudo umount /dev/nvme0n1p1 2>/dev/null || true
|
|
sudo mkdir /tmp/apple-wifi-efi 2>/dev/null || true
|
|
sudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi 2>/dev/null || true
|
|
fi
|
|
mountpoint=$(findmnt -n -o TARGET /dev/nvme0n1p1)
|
|
echo "Getting Wi-Fi and Bluetooth firmware"
|
|
sudo mkdir /tmp/apple-wifi-fw
|
|
cd /tmp/apple-wifi-fw
|
|
if [[ ${1-default} = -v ]]
|
|
then
|
|
sudo tar xvf $mountpoint/firmware.tar.gz
|
|
else
|
|
sudo tar xf $mountpoint/firmware.tar.gz 2>/dev/null
|
|
fi
|
|
echo "Setting up Wi-Fi and Bluetooth"
|
|
if [[ ${1-default} = -v ]]
|
|
then
|
|
sudo python3 $mountpoint/firmware.sh /tmp/apple-wifi-fw
|
|
else
|
|
sudo python3 $mountpoint/firmware.sh /tmp/apple-wifi-fw >/dev/null
|
|
fi
|
|
cd /lib/firmware
|
|
if [[ ${1-default} = -v ]]
|
|
then
|
|
sudo tar xvf /tmp/apple-wifi-fw/firmware.tar
|
|
else
|
|
sudo tar xf /tmp/apple-wifi-fw/firmware.tar
|
|
fi
|
|
for file in "$mountpoint/brcmfmac4364b2-pcie.txt" \
|
|
"$mountpoint/brcmfmac4364b2-pcie.txcap_blob"
|
|
do
|
|
if [ -f "$file" ]
|
|
then
|
|
if [[ ${1-default} = -v ]]
|
|
then
|
|
sudo cp -v $file /lib/firmware/brcm
|
|
else
|
|
sudo cp $file /lib/firmware/brcm
|
|
fi
|
|
fi
|
|
done
|
|
sudo modprobe -r brcmfmac || true
|
|
sudo modprobe brcmfmac || true
|
|
sudo modprobe -r hci_bcm4377 || true
|
|
sudo modprobe hci_bcm4377 || true
|
|
echo "Cleaning up"
|
|
sudo rm -r /tmp/apple-wifi-fw
|
|
echo "Keeping a copy of the firmware and the script in the EFI partition shall allow you to set up Wi-Fi again in the future by running this script or the commands told in the macOS step in Linux only, without the macOS step. Do you want to keep a copy? (y/N)"
|
|
read input
|
|
if [[ ($input != y) && ($input != Y) ]]
|
|
then
|
|
echo "Removing the copy from the EFI partition"
|
|
for file in "$mountpoint/brcmfmac4364b2-pcie.txt" \
|
|
"$mountpoint/brcmfmac4364b2-pcie.txcap_blob" \
|
|
"$mountpoint/firmware.tar.gz" \
|
|
"$mountpoint/firmware.sh"
|
|
do
|
|
if [ -f "$file" ]
|
|
then
|
|
sudo rm $file
|
|
fi
|
|
done
|
|
fi
|
|
echo "Running post-installation scripts"
|
|
exec sudo sh -c "umount /dev/nvme0n1p1 && mount -a && rmdir /tmp/apple-wifi-efi && echo Done!"
|
|
;;
|
|
(*)
|
|
echo "Error: unsupported platform"
|
|
;;
|
|
esac
|
|
exit 0
|
|
"""
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
import logging, os, os.path, re, sys
|
|
from collections import namedtuple, defaultdict
|
|
|
|
#from .core import FWFile
|
|
|
|
log = logging.getLogger("asahi_firmware.bluetooth")
|
|
|
|
BluetoothChip = namedtuple(
|
|
"BluetoothChip", ("chip", "stepping", "board_type", "vendor")
|
|
)
|
|
|
|
|
|
class BluetoothFWCollection(object):
|
|
VENDORMAP = {
|
|
"MUR": "m",
|
|
"USI": "u",
|
|
"GEN": None,
|
|
}
|
|
STRIP_SUFFIXES = [
|
|
"ES2"
|
|
]
|
|
|
|
def __init__(self, source_path):
|
|
self.fwfiles = defaultdict(lambda: [None, None])
|
|
self.load(source_path)
|
|
|
|
def load(self, source_path):
|
|
for fname in os.listdir(source_path):
|
|
root, ext = os.path.splitext(fname)
|
|
|
|
# index for bin and ptb inside self.fwfiles
|
|
if ext == ".bin":
|
|
idx = 0
|
|
elif ext == ".ptb":
|
|
idx = 1
|
|
else:
|
|
# skip firmware for older (UART) chips
|
|
continue
|
|
|
|
# skip T2 _DEV firmware
|
|
if "_DEV" in root:
|
|
continue
|
|
|
|
chip = self.parse_fname(root)
|
|
if chip is None:
|
|
continue
|
|
|
|
if self.fwfiles[chip][idx] is not None:
|
|
log.warning(f"duplicate entry for {chip}: {self.fwfiles[chip][idx].name} and now {fname + ext}")
|
|
continue
|
|
|
|
path = os.path.join(source_path, fname)
|
|
with open(path, "rb") as f:
|
|
data = f.read()
|
|
|
|
self.fwfiles[chip][idx] = FWFile(fname, data)
|
|
|
|
def parse_fname(self, fname):
|
|
fname = fname.split("_")
|
|
|
|
match = re.fullmatch("bcm(43[0-9]{2})([a-z][0-9])", fname[0].lower())
|
|
if not match:
|
|
log.warning(f"Unexpected firmware file: {fname}")
|
|
return None
|
|
chip, stepping = match.groups()
|
|
|
|
# board type is either preceeded by PCIE_macOS or by PCIE
|
|
try:
|
|
pcie_offset = fname.index("PCIE")
|
|
except:
|
|
log.warning(f"Can't find board type in {fname}")
|
|
return None
|
|
|
|
if fname[pcie_offset + 1] == "macOS":
|
|
board_type = fname[pcie_offset + 2]
|
|
else:
|
|
board_type = fname[pcie_offset + 1]
|
|
for i in self.STRIP_SUFFIXES:
|
|
board_type = board_type.rstrip(i)
|
|
board_type = "apple," + board_type.lower()
|
|
|
|
# make sure we can identify exactly one vendor
|
|
otp_values = set()
|
|
for vendor, otp_value in self.VENDORMAP.items():
|
|
if vendor in fname:
|
|
otp_values.add(otp_value)
|
|
if len(otp_values) != 1:
|
|
log.warning(f"Unable to determine vendor ({otp_values}) in {fname}")
|
|
return None
|
|
vendor = otp_values.pop()
|
|
|
|
return BluetoothChip(
|
|
chip=chip, stepping=stepping, board_type=board_type, vendor=vendor
|
|
)
|
|
|
|
def files(self):
|
|
for chip, (bin, ptb) in self.fwfiles.items():
|
|
fname_base = f"brcm/brcmbt{chip.chip}{chip.stepping}-{chip.board_type}"
|
|
if chip.vendor is not None:
|
|
fname_base += f"-{chip.vendor}"
|
|
|
|
if bin is None:
|
|
log.warning(f"no bin for {chip}")
|
|
continue
|
|
else:
|
|
yield fname_base + ".bin", bin
|
|
|
|
if ptb is None:
|
|
log.warning(f"no ptb for {chip}")
|
|
continue
|
|
else:
|
|
yield fname_base + ".ptb", ptb
|
|
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
import sys, os, os.path, pprint, statistics, logging
|
|
#from .core import FWFile
|
|
|
|
log = logging.getLogger("asahi_firmware.wifi")
|
|
|
|
class FWNode(object):
|
|
def __init__(self, this=None, leaves=None):
|
|
if leaves is None:
|
|
leaves = {}
|
|
self.this = this
|
|
self.leaves = leaves
|
|
|
|
def __eq__(self, other):
|
|
return self.this == other.this and self.leaves == other.leaves
|
|
|
|
def __hash__(self):
|
|
return hash((self.this, tuple(self.leaves.items())))
|
|
|
|
def __repr__(self):
|
|
return f"FWNode({self.this!r}, {self.leaves!r})"
|
|
|
|
def print(self, depth=0, tag=""):
|
|
print(f"{' ' * depth} * {tag}: {self.this or ''} ({hash(self)})")
|
|
for k, v in self.leaves.items():
|
|
v.print(depth + 1, k)
|
|
|
|
class WiFiFWCollection(object):
|
|
EXTMAP = {
|
|
"trx": "bin",
|
|
"txt": "txt",
|
|
"clmb": "clm_blob",
|
|
"txcb": "txcap_blob",
|
|
}
|
|
DIMS = ["C", "s", "P", "M", "V", "m", "A"]
|
|
def __init__(self, source_path):
|
|
self.root = FWNode()
|
|
self.load(source_path)
|
|
self.prune()
|
|
|
|
def load(self, source_path):
|
|
for dirpath, dirnames, filenames in os.walk(source_path):
|
|
if "perf" in dirnames:
|
|
dirnames.remove("perf")
|
|
if "assert" in dirnames:
|
|
dirnames.remove("assert")
|
|
subpath = dirpath.lstrip(source_path)
|
|
for name in sorted(filenames):
|
|
if not any(name.endswith("." + i) for i in self.EXTMAP):
|
|
continue
|
|
path = os.path.join(dirpath, name)
|
|
relpath = os.path.join(subpath, name)
|
|
if not name.endswith(".txt"):
|
|
name = "P-" + name
|
|
idpath, ext = os.path.join(subpath, name).rsplit(".", 1)
|
|
props = {}
|
|
for i in idpath.replace("/", "_").split("_"):
|
|
if not i:
|
|
continue
|
|
k, v = i.split("-", 1)
|
|
if k == "P" and "-" in v:
|
|
plat, ant = v.split("-", 1)
|
|
props["P"] = plat
|
|
props["A"] = ant
|
|
else:
|
|
props[k] = v
|
|
ident = [ext]
|
|
for dim in self.DIMS:
|
|
if dim in props:
|
|
ident.append(props.pop(dim))
|
|
assert not props
|
|
|
|
node = self.root
|
|
for k in ident:
|
|
node = node.leaves.setdefault(k, FWNode())
|
|
with open(path, "rb") as fd:
|
|
data = fd.read()
|
|
|
|
if name.endswith(".txt"):
|
|
data = self.process_nvram(data)
|
|
|
|
node.this = FWFile(relpath, data)
|
|
|
|
def prune(self, node=None, depth=0):
|
|
if node is None:
|
|
node = self.root
|
|
|
|
for i in node.leaves.values():
|
|
self.prune(i, depth + 1)
|
|
|
|
if node.this is None and node.leaves and depth > 3:
|
|
first = next(iter(node.leaves.values()))
|
|
if all(i == first for i in node.leaves.values()):
|
|
node.this = first.this
|
|
|
|
for i in node.leaves.values():
|
|
if not i.this or not node.this:
|
|
break
|
|
if i.this != node.this:
|
|
break
|
|
else:
|
|
node.leaves = {}
|
|
|
|
def _walk_files(self, node, ident):
|
|
if node.this is not None:
|
|
yield ident, node.this
|
|
for k, subnode in node.leaves.items():
|
|
yield from self._walk_files(subnode, ident + [k])
|
|
|
|
def files(self):
|
|
for ident, fwfile in self._walk_files(self.root, []):
|
|
(ext, chip, rev), rest = ident[:3], ident[3:]
|
|
rev = rev.lower()
|
|
ext = self.EXTMAP[ext]
|
|
|
|
if rest:
|
|
rest = "," + "-".join(rest)
|
|
else:
|
|
rest = ""
|
|
filename = f"brcm/brcmfmac{chip}{rev}-pcie.apple{rest}.{ext}"
|
|
|
|
yield filename, fwfile
|
|
|
|
def process_nvram(self, data):
|
|
data = data.decode("ascii")
|
|
keys = {}
|
|
lines = []
|
|
for line in data.split("\n"):
|
|
if not line:
|
|
continue
|
|
key, value = line.split("=", 1)
|
|
keys[key] = value
|
|
# Clean up spurious whitespace that Linux does not like
|
|
lines.append(f"{key.strip()}={value}\n")
|
|
|
|
return "".join(lines).encode("ascii")
|
|
|
|
def print(self):
|
|
self.root.print()
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
import tarfile, io, logging
|
|
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)
|
|
|
|
class FWPackage(object):
|
|
def __init__(self, target):
|
|
self.path = target
|
|
self.tarfile = tarfile.open(target, mode="w")
|
|
self.hashes = {}
|
|
self.manifest = []
|
|
|
|
def close(self):
|
|
self.tarfile.close()
|
|
|
|
def add_file(self, name, data):
|
|
ti = tarfile.TarInfo(name)
|
|
fd = None
|
|
if data.sha in self.hashes:
|
|
ti.type = tarfile.LNKTYPE
|
|
ti.linkname = self.hashes[data.sha]
|
|
self.manifest.append(f"LINK {name} {ti.linkname}")
|
|
else:
|
|
ti.type = tarfile.REGTYPE
|
|
ti.size = len(data.data)
|
|
fd = io.BytesIO(data.data)
|
|
self.hashes[data.sha] = name
|
|
self.manifest.append(f"FILE {name} SHA256 {data.sha}")
|
|
|
|
logging.info(f"+ {self.manifest[-1]}")
|
|
self.tarfile.addfile(ti, fd)
|
|
|
|
def add_files(self, it):
|
|
for name, data in it:
|
|
self.add_file(name, data)
|
|
|
|
def save_manifest(self, filename):
|
|
with open(filename, "w") as fd:
|
|
for i in self.manifest:
|
|
fd.write(i + "\n")
|
|
|
|
def __del__(self):
|
|
self.tarfile.close()
|
|
|
|
|
|
|
|
|
|
pkg = FWPackage("firmware.tar")
|
|
col = WiFiFWCollection(sys.argv[1]+"/wifi")
|
|
pkg.add_files(sorted(col.files()))
|
|
col = BluetoothFWCollection(sys.argv[1]+"/bluetooth")
|
|
pkg.add_files(sorted(col.files()))
|
|
pkg.close()
|