From f11797062227e975f40e507207abf52498b11d05 Mon Sep 17 00:00:00 2001 From: Aditya Garg <85610623+AdityaGarg8@users.noreply.github.com> Date: Thu, 23 May 2024 19:01:06 +0530 Subject: [PATCH] Firmware: Add multiple other options to move firmware to Linux (#533) Co-authored-by: Sharpened Blade Co-authored-by: Cassie Cheung --- docs/guides/preinstall.md | 2 +- docs/guides/wifi-bluetooth.md | 166 +++++- docs/tools/firmware.sh | 953 +++++++++++++++++++++++----------- 3 files changed, 779 insertions(+), 342 deletions(-) mode change 100644 => 100755 docs/tools/firmware.sh diff --git a/docs/guides/preinstall.md b/docs/guides/preinstall.md index 2f28a257..f2d2c50d 100644 --- a/docs/guides/preinstall.md +++ b/docs/guides/preinstall.md @@ -72,7 +72,7 @@ While the installation image is being written to the USB, you can skip to [Copy !!! Warning "Arch/EndeavourOS" If you're going to install Arch or EndeavourOS, you do not need to follow this step. -Linux's Wi-Fi driver uses the same Wi-Fi firmware files as macOS, so we copy these files from macOS to the EFI partition where Linux can access and eventually install them. +Linux's Wi-Fi driver uses the same Wi-Fi firmware files as macOS, so we need to copy these files from macOS to Linux. [Follow here](https://wiki.t2linux.org/guides/wifi-bluetooth/#on-macos) the **first part in macOS** and come back to this page. diff --git a/docs/guides/wifi-bluetooth.md b/docs/guides/wifi-bluetooth.md index 29331532..ef9fd4de 100644 --- a/docs/guides/wifi-bluetooth.md +++ b/docs/guides/wifi-bluetooth.md @@ -1,3 +1,5 @@ + + # Introduction This page is a step by step guide to get Wi-Fi and Bluetooth working on T2 Macs. This guide is also applicable to **iMac19,1** and **iMac19,2**, which are T1 Intel Macs. This guide is NOT applicable for rest T1 and older Intel Macs. @@ -12,9 +14,9 @@ Check if this command outputs any lines: `modinfo brcmfmac | grep 4387` If it do Refer to the "Updating Kernel" section on your distro's FAQ for instructions if you need to update your kernel: - [Arch](https://wiki.t2linux.org/distributions/arch/faq/#updating-kernel) -- [Fedora](https://github.com/mikeeq/mbp-fedora-kernel#how-to-update-mbp-fedora-kernel) +- [Fedora](https://github.com/t2linux/fedora?tab=readme-ov-file#instalation) - [Manjaro](https://wiki.t2linux.org/distributions/manjaro/faq/#updating-kernel) -- [Ubuntu](https://wiki.t2linux.org/distributions/ubuntu/faq/#updating-kernel) +- [Ubuntu](https://github.com/t2linux/T2-Debian-and-Ubuntu-Kernel?tab=readme-ov-file#using-the-apt-repo) ## Setting up @@ -25,46 +27,158 @@ We now use a script which can help you set up Wi-Fi and Bluetooth. Follow the in 1. Click [here](../tools/firmware.sh) to download the script. 2. Boot into macOS. 3. Run this script there. -4. When the script shall run successfully, it shall ask you to follow either of the two options mentioned in the [On Linux](#on-linux) section, on Linux. -5. Boot into Linux. + +When you run the script in macOS, it will ask you to choose between 3 methods to move firmware to Linux: + +=== "Method 1" + **Method 1: Run the same script on Linux** + + If you choose this method, unlike **Method 2** and **Method 3**, you need not have any specific dependency already installed on your Mac. So if you don't want to install any additional software on macOS, this method is the only option for you. + + In this method, the script will copy the firmware to your **EFI** partition. + + To retrieve the firmware from **EFI** partition in Linux, you shall have to run the same script on Linux. You have 2 options do so, described in detail in [On Linux](#on-linux) section. + +=== "Method 2" + **Method 2: Create a tarball of the firmware and extract it to Linux** + + If you choose this method, the script will install the following dependencies, if missing, on macOS: + + 1. **python3** - Renames the firmware and creates the tarball. + + The script shall automatically detect if any dependency is missing, and if required, will give you the option of installing it. So you need not worry about not having any dependency installed. + + Once the script confirms that you have the necessary dependencies installed, it shall create a tarball of the firmware by the name of `firmware.tar` in your **Downloads** folder. + + Now you have to extract the firmware in the tarball to Linux. The procedure has been described in detail in [On Linux](#on-linux) section. + +=== "Method 3" + **Method 3: Create a Linux specific package which can be installed using a package manager** + + If you choose this method, the script will install the following dependencies, if missing, on macOS: + + 1. **python3** - Renames the firmware. + 2. **dpkg** - Creates a package that can be installed on Linux using `apt`. + 3. **rpm** - Creates a package that can be installed on Linux using `dnf`. + 4. **makepkg** - Creates a package that can be installed on Linux using `pacman`. + 5. **coreutils** - Additional requirement of **makepkg**. + + The script shall automatically detect if any dependency is missing, and if required, will give you the option of installing it. So you need not worry about not having any dependency installed. + + Once the script confirms that you have the necessary dependencies installed, it shall create a package of the firmware which can be installed by `apt`, `dnf` or `pacman`, depending on the option you chose while running the script. The package shall be saved in your **Downloads** folder. + + Now you have to install the package in Linux using your package manager. The procedure has been described in detail in [On Linux](#on-linux) section. ### On Linux -You have two options here. You can follow either of the two, its purely based on your choice. If your distro installer requires internet to install, you can also follow these steps on a Live ISO environment: +Once you have run the script on macOS, depending on the method you chose, the steps to be followed on Linux are described below: !!! Warning "Running script directly on Linux" We have noticed a lot of users directly running the script on Linux and without running it first on macOS. Please ensure that you have run the script on macOS first. If you have removed macOS, this script won't be very helpful. -- The first is to either copy this script to Linux via a USB, download it if you have a wired internet connection, or use some other method to get it to Linux. You can then run the script again from Linux and it will finish setting up Wi-Fi and Bluetooth. +=== "Method 1" + **Method 1: Run the same script on Linux** + + Now we need to retrieve the firmware from the **EFI** partition. You further have 2 options to do so: + + === "Option 1" + + In this option, you simply have to copy the same script to Linux, and run it with: + + ```bash + bash /path/to/firmware.sh + ``` + + !!! note + + Replace `/path/to/firmware.sh` with the actual path of the script. For example, if the script is in the Downloads folder in Linux, command to be run would be `bash $HOME/Downloads/firmware.sh` + + === "Option 2" -- The second method is to simply run the following commands on Linux :- + In this option, you simply have to run the following commands in Linux: - ```sh - sudo mkdir -p /tmp/apple-wifi-efi - sudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi - bash /tmp/apple-wifi-efi/firmware.sh - sudo umount /tmp/apple-wifi-efi - ``` + ```bash + sudo mkdir -p /tmp/apple-wifi-efi + sudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi + bash /tmp/apple-wifi-efi/firmware.sh + sudo umount /tmp/apple-wifi-efi + ``` -#### For those who don’t know how to run a script + This option shall be useful if you are unable to copy the script to Linux. -If you don’t know how to run a script, follow these instructions. +=== "Method 2" + **Method 2: Create a tarball of the firmware and extract it to Linux** -1. Boot into macOS, and download the script. Make sure the script is there in your **Downloads** folder. + Now we shall extract the tarball of the firmware which was saved in the **Downloads** folder in macOS as `firmware.tar`. In order to do so, copy `firmware.tar` to Linux and extract the firmware to `/lib/firmware/brcm` by running: -2. Open the terminal and run :- - - ``` bash - bash ~/Downloads/firmware.sh + ```bash + sudo tar -v -xC /lib/firmware/brcm -f /path/to/firmware.tar ``` - -3. Then boot into Linux and place the same script in the **Downloads** folder over there or simply run the commands the script asked you to run in Linux when you executed it in macOS. -4. If you placed the script in the **Downloads** folder instead of running the commands told by the script in macOS, run step 2 command on the terminal, this time in Linux. Else you needn't follow this step. + !!! note + + Replace `/path/to/firmware.tar` with the actual path of the tarball. For example, if `firmware.tar` is copied to the Downloads folder in Linux, command to be run would be `sudo tar -v -xC /lib/firmware/brcm -f $HOME/Downloads/firmware.tar` + + Then reload the Wi-Fi and Bluetooth drivers by running: + + ```bash + sudo modprobe -r brcmfmac_wcc + sudo modprobe -r brcmfmac + sudo modprobe brcmfmac + sudo modprobe -r hci_bcm4377 + sudo modprobe hci_bcm4377 + ``` + +=== "Method 3" + **Method 3: Create a Linux specific package which can be installed using a package manager** + + Now we have to install the firmware package which was saved in the **Downloads** folder in macOS. Copy the package to Linux and follow the instructions below, depending on whether you use `apt`, `dnf` or `rpm`: + + === "apt" + + This package manager is found in Ubuntu, Debian and other similar distros. + + To install using `apt`, run: + + ```bash + sudo apt install /path/to/firmware_package.deb + ``` + + !!! note + + Replace `/path/to/firmware_package.deb` with the actual path of the package. For example, if `apple-firmware_14.5-1_all.deb` was created in macOS and has been copied to the Downloads folder in Linux, command to be run would be `sudo apt install $HOME/Downloads/apple-firmware_14.5-1_all.deb` + + === "dnf" + + This package manager is found in Fedora. + + To install using `dnf`, run: + + ```bash + sudo dnf install --disablerepo=* /path/to/firmware_package.rpm + ``` + + !!! note + + Replace `/path/to/firmware_package.rpm` with the actual path of the package. For example, if `apple-firmware-14.5-1.noarch.rpm` was created in macOS and has been copied to the Downloads folder in Linux, command to be run would be `sudo dnf install --disablerepo=* $HOME/Downloads/apple-firmware-14.5-1.noarch.rpm` + + === "pacman" + + This package manager is found in Arch Linux, EndeavourOS, Manjaro and other similar distros. + + To install using `pacman`, run: + + ```bash + sudo pacman -U /path/to/firmware_package.pkg.tar.zst + ``` + + !!! note + + Replace `/path/to/firmware_package.pkg.tar.zst` with the actual path of the package. For example, if `apple-firmware-14.5-1-any.pkg.tar.zst` was created in macOS and has been copied to the Downloads folder in Linux, command to be run would be `sudo pacman -U $HOME/Downloads/apple-firmware-14.5-1-any.pkg.tar.zst` ## Testing Firmware -You can check the logs to confirm correct loading of the firmware using `sudo journalctl -k --grep=brcmfmac`, the output should look similar to this +You can check the logs to confirm correct loading of the firmware using `sudo journalctl -k --grep=brcmfmac`, the output should look similar to this: ```log Dec 24 22:34:19 hostname kernel: usbcore: registered new interface driver brcmfmac @@ -82,7 +196,7 @@ Dec 24 22:34:20 hostname kernel: brcmfmac 0000:01:00.0 wlp1s0f0: renamed from wl ## Fixing unstable WPA2 using iwd -Using iwd is technically not needed for using wifi. But if you are facing unstable WPA2 issues and have to follow step 1 of the above section every time you connect to a WPA2 network, you will have to follow this section. If your connection is stable, you needn't follow this section. +Using iwd is technically not needed for using Wi-Fi. But if you are facing unstable WPA2 issues and have to reload the Wi-Fi driver every time you connect to a WPA2 network, you will have to follow this section. If your connection is stable, you needn't follow this section. Instructions in this section might be different for the distribution that you are trying to install. @@ -102,4 +216,4 @@ Instructions in this section might be different for the distribution that you ar sudo systemctl restart NetworkManager ``` -If you wifi disconnects or has issues otherwise its advised to restart iwd: `sudo systemctl restart iwd`, or reprobe the wifi kernel module: `sudo modprobe -r brcmfmac && sudo modprobe brcmfmac`. +If you Wi-Fi disconnects or has issues otherwise its advised to restart iwd: `sudo systemctl restart iwd`, or reprobe the Wi-Fi kernel module: `sudo modprobe -r brcmfmac_wcc && sudo modprobe -r brcmfmac && sudo modprobe brcmfmac`. diff --git a/docs/tools/firmware.sh b/docs/tools/firmware.sh old mode 100644 new mode 100755 index 80b78535..8f5ad551 --- a/docs/tools/firmware.sh +++ b/docs/tools/firmware.sh @@ -18,36 +18,361 @@ while getopts "vhx" option; do esac done +python_check () { + if [ ! -f "/Library/Developer/CommandLineTools/usr/bin/python3" ] + then + echo -e "\nPython 3 not found. You will be prompted to install Xcode command line developer tools." + xcode-select --install + echo + read -p "Press enter after you have installed Xcode command line developer tools." + fi +} + +homebrew_check () { + if [ ! -f "/usr/local/bin/brew" ] + then + echo -e "\nHomebrew not found!" + echo + read -p "Press enter to install Homebrew." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi +} + +create_deb () { + if [ ! -f "/usr/local/bin/dpkg" ] + then + echo -e "\ndpkg and/or its dependencies are missing!" + echo + read -p "Press enter to install dpkg and its dependencies via Homebrew. This script can install Homebrew automatically if you haven't installed it. Alternatively you can terminate this script by pressing Control+C and install dpkg yourself, if you want to install it via some alternate method." + homebrew_check + echo -e "\nInstalling dpkg and its dependencies" + brew install dpkg + fi + + echo -e "\nBuilding deb package" + workarea=$(mktemp -d) + python3 "$0" /usr/share/firmware ${workarea}/firmware.tar + cd ${workarea} + mkdir -p deb + cd deb + mkdir -p DEBIAN + mkdir -p usr/lib/firmware/brcm + cd usr/lib/firmware/brcm + tar -xf ${workarea}/firmware.tar ${verbose} + cd - >/dev/null + + if [[ (${identifier} = iMac19,1) || (${identifier} = iMac19,2) || (${identifier} = iMacPro1,1) ]] + 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) + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} "usr/lib/firmware/brcm/brcmfmac4364b2-pcie.txt" + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} "usr/lib/firmware/brcm/brcmfmac4364b2-pcie.txcap_blob" + fi + + cat <<- EOF > DEBIAN/control + Package: apple-firmware + Version: ${ver}-1 + Maintainer: Apple + Architecture: all + Description: Wi-Fi and Bluetooth firmware for T2 Macs + EOF + + cat <<- EOF > DEBIAN/postinst + modprobe -r brcmfmac_wcc || true + modprobe -r brcmfmac || true + modprobe brcmfmac || true + modprobe -r hci_bcm4377 || true + modprobe hci_bcm4377 || true + EOF + + chmod a+x DEBIAN/control + chmod a+x DEBIAN/postinst + + cd ${workarea} + if [[ ${verbose} = -v ]] + then + dpkg-deb --build --root-owner-group -Zgzip deb + dpkg-name deb.deb + else + dpkg-deb --build --root-owner-group -Zgzip deb >/dev/null || echo "Failed to make deb package. Run the script with -v to get logs." + dpkg-name deb.deb >/dev/null + fi + + cp ${verbose} apple-firmware_${ver}-1_all.deb $HOME/Downloads + echo -e "\nCleaning up" + rm -r ${verbose} ${workarea} + + echo -e "\nDeb package apple-firmware_${ver}-1_all.deb has been saved to Downloads!" + echo "Copy it to Linux and install it by running the following in the Linux terminal:" + echo -e "\nsudo apt install /path/to/apple-firmware_${ver}-1_all.deb" +} + +create_rpm () { + if [ ! -f "/usr/local/bin/rpmbuild" ] + then + echo -e "\nrpm and/or its dependencies are missing!" + echo + read -p "Press enter to install rpm and its dependencies via Homebrew. This script can install Homebrew automatically if you haven't installed it. Alternatively you can terminate this script by pressing Control+C and install rpm yourself, if you want to install it via some alternate method." + homebrew_check + echo -e "\nInstalling rpm and its dependencies" + brew install rpm + fi + + echo -e "\nBuilding rpm package" + mkdir -p $HOME/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + + # Extract firmware + python3 "$0" /usr/share/firmware $HOME/rpmbuild/SOURCES/firmware.tar + cd $HOME/rpmbuild/BUILD + + if [[ (${identifier} = iMac19,1) || (${identifier} = iMac19,2) || (${identifier} = iMacPro1,1) ]] + 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) + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} brcmfmac4364b2-pcie.txt + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} brcmfmac4364b2-pcie.txcap_blob + tar --append ${verbose} -f $HOME/rpmbuild/SOURCES/firmware.tar brcmfmac4364b2-pcie.txt + tar --append ${verbose} -f $HOME/rpmbuild/SOURCES/firmware.tar brcmfmac4364b2-pcie.txcap_blob + rm brcmfmac4364b2-pcie.txcap_blob brcmfmac4364b2-pcie.txt + fi + + # Create the spec file + cat <<- EOF > $HOME/rpmbuild/SPECS/apple-firmware.spec + Name: apple-firmware + Version: ${ver} + Release: 1 + Summary: Wi-Fi and Bluetooth firmware for T2 Macs + License: Proprietary + BuildArch: noarch + + Source1: firmware.tar + + %description + Wi-Fi and Bluetooth firmware for T2 Macs + + %prep + tar -xf %{SOURCE1} + + %build + + %install + mkdir -p %{buildroot}/usr/lib/firmware/brcm + install -m 644 * %{buildroot}/usr/lib/firmware/brcm + + %posttrans + modprobe -r brcmfmac_wcc || true + modprobe -r brcmfmac || true + modprobe brcmfmac || true + modprobe -r hci_bcm4377 || true + modprobe hci_bcm4377 || true + + %files + /usr/lib/firmware/brcm/* + EOF + + # Build + if [[ ${verbose} = -v ]] + then + rpmbuild -bb --define '_target_os linux' $HOME/rpmbuild/SPECS/apple-firmware.spec + else + rpmbuild -bb --define '_target_os linux' $HOME/rpmbuild/SPECS/apple-firmware.spec >/dev/null 2>&1 || echo "Failed to make rpm package. Run the script with -v to get logs." + fi + + # Copy and Cleanup + cp ${verbose} $HOME/rpmbuild/RPMS/noarch/apple-firmware-${ver}-1.noarch.rpm $HOME/Downloads + echo -e "\nCleaning up" + rm -r ${verbose} $HOME/rpmbuild + + echo -e "\nRpm package apple-firmware-${ver}-1.noarch.rpm has been saved to Downloads!" + echo "Copy it to Linux and install it by running the following in the Linux terminal:" + echo -e "\nsudo dnf install --disablerepo=* /path/to/apple-firmware-${ver}-1.noarch.rpm" +} + +create_arch_pkg () { + if [ ! -f "/usr/local/bin/makepkg" ] || [ ! -f "/usr/local/bin/sha256sum" ] + then + echo -e "\nmakepkg and/or its dependencies are missing!" + echo + read -p "Press enter to install makepkg and its dependencies via Homebrew. This script can install Homebrew automatically if you haven't installed it. Alternatively you can terminate this script by pressing Control+C and install makepkg yourself, if you want to install it via some alternate method." + homebrew_check + echo -e "\nInstalling makepkg and its dependencies" + brew install makepkg coreutils + fi + + echo -e "\nBuilding Arch package" + workarea=$(mktemp -d) + python3 "$0" /usr/share/firmware ${workarea}/firmware.tar + cd ${workarea} + if [[ (${identifier} = iMac19,1) || (${identifier} = iMac19,2) || (${identifier} = iMacPro1,1) ]] + 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) + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} "${workarea}/brcmfmac4364b2-pcie.txt" + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} "${workarea}/brcmfmac4364b2-pcie.txcap_blob" + tar --append ${verbose} -f firmware.tar brcmfmac4364b2-pcie.txt + tar --append ${verbose} -f firmware.tar brcmfmac4364b2-pcie.txcap_blob + rm ${verbose} brcmfmac4364b2-pcie.txt + rm ${verbose} brcmfmac4364b2-pcie.txcap_blob + fi + + # Create the PKGBUILD + cat <<- EOF > PKGBUILD + pkgname=apple-firmware + pkgver=${ver} + pkgrel=1 + pkgdesc="Wi-Fi and Bluetooth Firmware for T2 Macs" + arch=("any") + url="" + license=('unknown') + replaces=('apple-bcm-wifi-firmware') + source=("firmware.tar") + noextract=("firmware.tar") + sha256sums=('SKIP') + + package() { + mkdir -p \$pkgdir/usr/lib/firmware/brcm + cd \$pkgdir/usr/lib/firmware/brcm + tar xf \$srcdir/firmware.tar + } + + install=apple-firmware.install + EOF + + cat <<- EOF > apple-firmware.install + post_install() { + modprobe -r brcmfmac_wcc || true + modprobe -r brcmfmac || true + modprobe brcmfmac || true + modprobe -r hci_bcm4377 || true + modprobe hci_bcm4377 || true + } + EOF + + # Set path to use newer bsdtar and GNU touch + PATH_OLD=$PATH + PATH=/usr/local/Cellar/libarchive/$(ls /usr/local/Cellar/libarchive | head -n 1)/bin:/usr/local/opt/coreutils/libexec/gnubin:$PATH_OLD + + # Build + if [[ ${verbose} = -v ]] + then + PKGEXT='.pkg.tar.zst' makepkg + else + PKGEXT='.pkg.tar.zst' makepkg >/dev/null 2>&1 || echo "Failed to make Arch package. Run the script with -v to get logs." + fi + + # Revert path to its original form + PATH=${PATH_OLD} + + # Copy to Downloads and cleanup + cp ${verbose} apple-firmware-${ver}-1-any.pkg.tar.zst $HOME/Downloads + echo -e "\nCleaning up" + rm -r ${verbose} ${workarea} + + echo -e "\nArch package apple-firmware-${ver}-1-any.pkg.tar.zst has been saved to Downloads!" + echo "Copy it to Linux and install it by running the following in the Linux terminal:" + echo -e "\nsudo pacman -U /path/to/apple-firmware-${ver}-1-any.pkg.tar.zst" +} + os=$(uname -s) case "$os" in (Darwin) echo "Detected macOS" - ver=$(sw_vers -productVersion | cut -d "." -f 1) - if [[ ${ver} < 12 ]] + ver=$(sw_vers -productVersion) + ver_check=$(sw_vers -productVersion | cut -d "." -f 1) + if [[ ${ver_check} < 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" - EFILABEL=$(diskutil info disk0s1 | grep "Volume Name" | cut -d ":" -f 2 | xargs) - sudo diskutil mount disk0s1 - echo "Getting Wi-Fi and Bluetooth firmware" - tar ${verbose} -cf "/Volumes/${EFILABEL}/firmware.tar" -C /usr/share/firmware/ . - gzip ${verbose} --best "/Volumes/${EFILABEL}/firmware.tar" - if [[ (${identifier} = iMac19,1) || (${identifier} = iMac19,2) || (${identifier} = iMacPro1,1) ]] + echo -e "\nHow do you want to copy the firmware to Linux?" + echo -e "\n1. Run the same script on Linux." + echo "2. Create a tarball of the firmware and extract it to Linux." + echo "3. Create a Linux specific package which can be installed using a package manager." + echo -e "\nNote: Option 2 and 3 require additional software like python3 and tools specific for your package manager. Requirements will be told as you proceed further." + read choice + case ${choice} in + (1) + echo -e "\nMounting the EFI partition" + EFILABEL=$(diskutil info disk0s1 | grep "Volume Name" | cut -d ":" -f 2 | xargs) + sudo diskutil mount disk0s1 + echo "Getting Wi-Fi and Bluetooth firmware" + tar ${verbose} -cf "/Volumes/${EFILABEL}/firmware.tar" -C /usr/share/firmware/ . + gzip ${verbose} --best "/Volumes/${EFILABEL}/firmware.tar" + if [[ (${identifier} = iMac19,1) || (${identifier} = iMac19,2) || (${identifier} = iMacPro1,1) ]] 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) - cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} "/Volumes/${EFILABEL}/brcmfmac4364b2-pcie.txt" - cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} "/Volumes/${EFILABEL}/brcmfmac4364b2-pcie.txcap_blob" - fi - echo "Copying this script to EFI" - cp "$0" "/Volumes/${EFILABEL}/firmware.sh" 2>/dev/null || curl -s https://wiki.t2linux.org/tools/firmware.sh > "/Volumes/${EFILABEL}/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 firmware.sh in the EFI partition\n" && echo && read -p "Press enter after you have copied" && echo) - echo "Unmounting the EFI partition" - sudo diskutil unmount "/Volumes/${EFILABEL}/" - echo - echo -e "Run the following commands or run this script itself in Linux now to set up Wi-Fi :-\n\nsudo mkdir -p /tmp/apple-wifi-efi\nsudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi\nbash /tmp/apple-wifi-efi/firmware.sh\nsudo umount /tmp/apple-wifi-efi\n" + 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) + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} "/Volumes/${EFILABEL}/brcmfmac4364b2-pcie.txt" + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} "/Volumes/${EFILABEL}/brcmfmac4364b2-pcie.txcap_blob" + fi + echo "Copying this script to EFI" + cp "$0" "/Volumes/${EFILABEL}/firmware.sh" 2>/dev/null || curl -s https://wiki.t2linux.org/tools/firmware.sh > "/Volumes/${EFILABEL}/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 firmware.sh in the EFI partition\n" && echo && read -p "Press enter after you have copied" && echo) + echo "Unmounting the EFI partition" + sudo diskutil unmount "/Volumes/${EFILABEL}/" + echo + echo -e "Run the following commands or run this script itself in Linux now to set up Wi-Fi :-\n\nsudo mkdir -p /tmp/apple-wifi-efi\nsudo mount /dev/nvme0n1p1 /tmp/apple-wifi-efi\nbash /tmp/apple-wifi-efi/firmware.sh\nsudo umount /tmp/apple-wifi-efi\n" + ;; + (2) + + echo -e "\nChecking for missing dependencies" + python_check + echo -e "\nCreating a tarball of the firmware" + python3 "$0" /usr/share/firmware $HOME/Downloads/firmware.tar ${verbose} + if [[ (${identifier} = iMac19,1) || (${identifier} = iMac19,2) || (${identifier} = iMacPro1,1) ]] + 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) + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${nvramfile} "$HOME/Downloads/brcmfmac4364b2-pcie.txt" + cp ${verbose} /usr/share/firmware/wifi/C-4364__s-B2/${txcapblob} "$HOME/Downloads/brcmfmac4364b2-pcie.txcap_blob" + cd $HOME/Downloads + tar --append ${verbose} -f firmware.tar brcmfmac4364b2-pcie.txt + tar --append ${verbose} -f firmware.tar brcmfmac4364b2-pcie.txcap_blob + rm ${verbose} brcmfmac4364b2-pcie.txt + rm ${verbose} brcmfmac4364b2-pcie.txcap_blob + cd - >/dev/null + fi + echo -e "\nFirmware tarball saved to Downloads!" + echo -e "\nExtract the tarball contents to /lib/firmware/brcm in Linux and run the following in the Linux terminal:" + echo -e "\nsudo modprobe -r brcmfmac_wcc" + echo "sudo modprobe -r brcmfmac" + echo "sudo modprobe brcmfmac" + echo "sudo modprobe -r hci_bcm4377" + echo "sudo modprobe hci_bcm4377" + ;; + (3) + echo -e "\nWhat package manager does your Linux distribution use?" + echo -e "\n1. apt" + echo "2. dnf" + echo "3. pacman" + read package + case ${package} in + (1) + echo -e "\nChecking for missing dependencies" + python_check + create_deb + ;; + (2) + echo -e "\nChecking for missing dependencies" + python_check + create_rpm + ;; + (3) + echo -e "\nChecking for missing dependencies" + python_check + create_arch_pkg + ;; + (*) + echo -e "\nError: Invalid option!" + exit 1 + ;; + esac + ;; + (*) + echo -e "\nError: Invalid option!" + exit 1 + ;; + esac ;; (Linux) @@ -59,16 +384,23 @@ case "$os" in echo "Exiting..." exit 1 fi - - echo "Re-mounting the EFI partition" + echo -e "\nIf you are running this script in Linux, make sure you ran it first in macOS and choose option 1 (Run the same script on Linux)." + echo -e "\nContinue? (Y/n)" + read input + if [[ ($input = n) || ($input = N) ]] + then + echo -e "\nRun the script again in Linux when you have ran it in macOS." + exit 1 + fi + echo -e "\nRe-mounting the EFI partition" mountpoint=$(mktemp -d) workdir=$(mktemp -d) echo "Installing Wi-Fi and Bluetooth firmware" sudo mount ${verbose} /dev/nvme0n1p1 $mountpoint sudo tar --warning=no-unknown-keyword ${verbose} -xC $workdir -f $mountpoint/firmware.tar.gz - sudo python3 $0 $workdir $workdir/firmware-renamed.tar ${verbose} + sudo python3 "$0" $workdir $workdir/firmware-renamed.tar ${verbose} - sudo tar ${verbose} -xC /lib/firmware -f $workdir/firmware-renamed.tar + sudo tar ${verbose} -xC /lib/firmware/brcm -f $workdir/firmware-renamed.tar for file in "$mountpoint/brcmfmac4364b2-pcie.txt" \ "$mountpoint/brcmfmac4364b2-pcie.txcap_blob" @@ -96,11 +428,11 @@ case "$os" in do if [ -f "$file" ] then - sudo rm $file + sudo rm ${verbose} $file fi done fi - sudo rm -r $workdir + sudo rm -r ${verbose} $workdir sudo umount $mountpoint sudo rmdir $mountpoint echo "Done!" @@ -113,321 +445,311 @@ exit 0 """ # SPDX-License-Identifier: MIT -import logging, os, os.path, re, sys +import logging, os, os.path, re, sys, pprint, statistics, tarfile, io from collections import namedtuple, defaultdict - -#from .core import FWFile +from hashlib import sha256 log = logging.getLogger("asahi_firmware.bluetooth") BluetoothChip = namedtuple( - "BluetoothChip", ("chip", "stepping", "board_type", "vendor") + "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 + 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"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 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 __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 __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 __hash__(self): + return hash((self.this, tuple(self.leaves.items()))) - def __repr__(self): - return f"FWNode({self.this!r}, {self.leaves!r})" + 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) + 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): - 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: - 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 + 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): + 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: + 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"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() class FWFile(object): - def __init__(self, name, data): - self.name = name - self.data = data - self.sha = sha256(data).hexdigest() + 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 __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 __eq__(self, other): + if other is None: + return False + return self.sha == other.sha - def __hash__(self): - return hash(self.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() + 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() logging.getLogger().setLevel(logging.WARNING if (len(sys.argv) >= 4 and sys.argv[3] == "-v") else logging.ERROR) @@ -437,3 +759,4 @@ pkg.add_files(sorted(wifi_col.files())) bt_col = BluetoothFWCollection(sys.argv[1]+"/bluetooth") pkg.add_files(sorted(bt_col.files())) pkg.close() +