diff --git a/sources/t2linux-config/firmware.sh b/sources/t2linux-config/firmware.sh new file mode 100644 index 0000000..1f818aa --- /dev/null +++ b/sources/t2linux-config/firmware.sh @@ -0,0 +1,464 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2022 Aditya Garg +# Copyright (C) 2022 Orlando Chamberlain +# +# 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 firmware in ./brcm/brcmfmac4355c1* + do + sudo cp $firmware ${firmware/brcmfmac4355c1/brcmfmac89459} + done + + 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() diff --git a/sources/t2linux-config/rmmod_tb.sh b/sources/t2linux-config/rmmod_tb.sh new file mode 100644 index 0000000..d03d730 --- /dev/null +++ b/sources/t2linux-config/rmmod_tb.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +if [ "${1}" = "pre" ]; then + modprobe -r apple-touchbar +elif [ "${1}" = "post" ]; then + modprobe apple-touchbar +fi diff --git a/sources/t2linux-config/touchbar.sh b/sources/t2linux-config/touchbar.sh new file mode 100644 index 0000000..a5a5e08 --- /dev/null +++ b/sources/t2linux-config/touchbar.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +set -euo pipefail +echo "Select Touch Bar mode" +echo +echo "0: Only show F1-F12" +echo "1: Show media and brightness controls, use the fn key to switch to F1-F12" +echo "2: Show F1-F12, use the fn key to switch to media and brightness controls" +echo "3: Only show media and brightness controls" +echo "4: Only show the escape key" +read tb +if [[ \$tb != 0 && \$tb != 1 && \$tb != 2 && \$tb != 3 && \$tb != 4 ]] +then +echo "Invalid input. Aborting!" +exit 1 +fi +echo "Changing default mode ..." +echo "# display f* key in touchbar" > /etc/modprobe.d/apple-tb.conf +echo "options apple-touchbar fnmode=\$tb" >> /etc/modprobe.d/apple-tb.conf +bash -c "echo \$tb > /sys/class/input/*/device/fnmode" +echo "Done!" diff --git a/specs/t2linux-config.spec b/specs/t2linux-config.spec index 9a93d94..3e06464 100644 --- a/specs/t2linux-config.spec +++ b/specs/t2linux-config.spec @@ -1,5 +1,5 @@ Name: t2linux-config -Version: 6.2.0 +Version: 7.0.0 Release: 1%{?dist} Summary: System configuration for linux on t2 macs. License: MIT @@ -12,8 +12,12 @@ URL: https://t2linux.org %global audio_config_commit %(c=%{audio_config_commit_long}; echo ${c:0:7}) Source1: https://github.com/kekrby/t2-better-audio/archive/%{audio_config_commit_long}/t2-better-audio-%{audio_config_commit}.tar.gz +Source2: rmmod_tb.sh +Source3: touchbar.sh +Source4: firmware.sh + %description -Configuration files for linux on t2 macs. Everything works except for TouchId, eGPU, and audio switching. +Configuration and tools for linux on t2 macs. Fixes suspend and audio. %prep tar -xf %{_sourcedir}/t2-better-audio-%{audio_config_commit}.tar.gz @@ -24,6 +28,16 @@ echo -e 'apple_bce\nsnd-seq' > t2linux.conf echo -e 'add_drivers+=" apple_bce snd_seq "' > t2linux-install.conf %install + +mkdir -p %{buildroot}/usr/lib/systemd/system-sleep/ +mv %{_sourcedir}/rmmod_tb.sh %{buildroot}/usr/lib/systemd/system-sleep/rmmod_tb.sh +chmod +x %{buildroot}/usr/lib/systemd/system-sleep/rmmod_tb.sh + +mkdir -p %{buildroot}/usr/bin/ +mv %{_sourcedir}/firmware.sh %{buildroot}/usr/bin/firmware +mv %{_sourcedir}/touchbar.sh %{buildroot}/usr/bin/touchbar +chmod +x %{buildroot}/usr/bin/* + mkdir -p %{buildroot}/etc/dracut.conf.d/ mv t2linux-install.conf %{buildroot}/etc/dracut.conf.d/t2linux-install.conf @@ -44,8 +58,11 @@ done grubby --args="intel_iommu=on iommu=pt pcie_ports=compat" --update-kernel=ALL %files +/usr/lib/systemd/system-sleep/rmmod_tb.sh /etc/modules-load.d/t2linux.conf /etc/dracut.conf.d/t2linux-install.conf /usr/share/alsa-card-profile/mixer /usr/share/pulseaudio/alsa-mixer /usr/lib/udev/rules.d/ +/usr/bin/firmware +/usr/bin/touchbar