From 500d26c914843154cf0afde4c5c6560906f279de Mon Sep 17 00:00:00 2001 From: Shawn Yeager Date: Fri, 21 Aug 2026 06:49:34 -0500 Subject: [PATCH 1/6] =?UTF-8?q?Install=20BCM43602=20NVRAM=20so=202016?= =?UTF-8?q?=E2=80=932017=20Macs=20see=205=20GHz=20Wi-Fi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit linux-firmware-broadcom ships the chip firmware but no board calibration, so brcmfmac only advertises 2.4 GHz. Install a calibrated NVRAM under firmware/updates on Apple machines with PCI 14e4:43ba, and migrate existing installs. --- install/hardware/all.sh | 1 + install/hardware/apple/brcmfmac-43602.sh | 126 +++++++++ install/hardware/apple/brcmfmac43602-pcie.txt | 256 ++++++++++++++++++ install/hardware/apple/fix-brcmfmac-5ghz.sh | 15 + manual/44-mac-support.md | 2 +- migrations/1787312531.sh | 13 + test/shell.d/brcmfmac-5ghz-test.sh | 226 ++++++++++++++++ test/shell.d/unowned-system-paths-test.sh | 4 + 8 files changed, 642 insertions(+), 1 deletion(-) create mode 100644 install/hardware/apple/brcmfmac-43602.sh create mode 100644 install/hardware/apple/brcmfmac43602-pcie.txt create mode 100644 install/hardware/apple/fix-brcmfmac-5ghz.sh create mode 100644 migrations/1787312531.sh create mode 100755 test/shell.d/brcmfmac-5ghz-test.sh diff --git a/install/hardware/all.sh b/install/hardware/all.sh index 6adcff9c8a4..3d07c77e64a 100644 --- a/install/hardware/all.sh +++ b/install/hardware/all.sh @@ -36,6 +36,7 @@ run_logged "$OMARCHY_INSTALL/hardware/apple/fix-spi-keyboard.sh" run_logged "$OMARCHY_INSTALL/hardware/apple/fix-suspend-nvme.sh" run_logged "$OMARCHY_INSTALL/hardware/apple/fix-t2.sh" run_logged "$OMARCHY_INSTALL/hardware/apple/fix-brcmfmac-supplicant.sh" +run_logged "$OMARCHY_INSTALL/hardware/apple/fix-brcmfmac-5ghz.sh" run_logged "$OMARCHY_INSTALL/hardware/lenovo/fix-yoga-pro7-bass-speakers.sh" diff --git a/install/hardware/apple/brcmfmac-43602.sh b/install/hardware/apple/brcmfmac-43602.sh new file mode 100644 index 00000000000..2a458559227 --- /dev/null +++ b/install/hardware/apple/brcmfmac-43602.sh @@ -0,0 +1,126 @@ +# Shared BCM43602 5 GHz NVRAM helpers for the install leaf and the migration. +# +# linux-firmware-broadcom ships the BCM43602 chip firmware but not a board +# calibration file. Without one, brcmfmac brings the card up with placeholder +# 5 GHz values (aa5g=1, no per-channel tx-power tables) and only 2.4 GHz +# networks are visible. A calibrated NVRAM with aa5g=7 / txchain=7 / rxchain=7 +# and ccode=00 / regrev=245 (defer channel legality to the host) unlocks 5 GHz. +# +# The NVRAM is a community dump attached to a BCM43602 kernel.org bugzilla +# ticket (attachment 290569), not vendor-certified. Channel legality still +# follows the host regulatory domain; Omarchy already persists that from the +# timezone via install/hardware/set-wireless-regdom.sh. +# +# Destinations live under /usr/lib/firmware/updates so they override, and do +# not collide with, linux-firmware-broadcom. + +brcmfmac43602_as_root() { + if (( EUID == 0 )); then + "$@" + else + sudo "$@" + fi +} + +brcmfmac43602_fwdir() { + printf '%s\n' "${OMARCHY_BRCMFMAC_FWDIR:-/usr/lib/firmware/updates/brcm}" +} + +brcmfmac43602_nvram_src() { + printf '%s\n' "${OMARCHY_BRCMFMAC43602_NVRAM:-${OMARCHY_INSTALL:-${OMARCHY_PATH:-/usr/share/omarchy}/install}/hardware/apple/brcmfmac43602-pcie.txt}" +} + +brcmfmac43602_dmi_vendor() { + cat "${OMARCHY_BRCMFMAC_DMI_VENDOR:-/sys/class/dmi/id/sys_vendor}" 2>/dev/null || true +} + +brcmfmac43602_dmi_product() { + cat "${OMARCHY_BRCMFMAC_DMI_PRODUCT:-/sys/class/dmi/id/product_name}" 2>/dev/null || true +} + +# Dual-band BCM43602 (14e4:43ba) on Apple hardware. The 2 GHz-only (43bb) and +# 5 GHz-only (43bc) variants are left alone, as are T2-era chips that already +# get board files from apple-bcm-firmware. +brcmfmac43602_needed() { + local sys_vendor + sys_vendor=$(brcmfmac43602_dmi_vendor) + [[ $sys_vendor == Apple* ]] || return 1 + lspci -nn | grep "14e4:43ba" >/dev/null +} + +brcmfmac43602_file_complete() { + local file=$1 + [[ -f $file ]] || return 1 + grep -qx 'aa5g=7' "$file" || return 1 + grep -qx 'txchain=7' "$file" || return 1 + grep -qx 'ccode=00' "$file" || return 1 +} + +brcmfmac43602_dmi_dest() { + local fwdir vendor product + fwdir=$(brcmfmac43602_fwdir) + vendor=$(brcmfmac43602_dmi_vendor) + product=$(brcmfmac43602_dmi_product) + [[ -n $vendor && -n $product ]] || return 0 + printf '%s\n' "$fwdir/brcmfmac43602-pcie.${vendor}-${product}.txt" +} + +brcmfmac43602_complete() { + local fwdir generic dmi + fwdir=$(brcmfmac43602_fwdir) + generic="$fwdir/brcmfmac43602-pcie.txt" + brcmfmac43602_file_complete "$generic" || return 1 + dmi=$(brcmfmac43602_dmi_dest) + [[ -z $dmi ]] && return 0 + brcmfmac43602_file_complete "$dmi" +} + +brcmfmac43602_wifi_mac() { + local netdir wireless iface mac nullglob_was_on=0 + netdir="${OMARCHY_BRCMFMAC_NETDIR:-/sys/class/net}" + shopt -q nullglob && nullglob_was_on=1 + shopt -s nullglob + for wireless in "$netdir"/*/wireless; do + iface=$(basename "$(dirname "$wireless")") + mac=$(cat "$netdir/$iface/address" 2>/dev/null || true) + if [[ $mac =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]; then + (( nullglob_was_on )) || shopt -u nullglob + printf '%s\n' "$mac" + return 0 + fi + done + (( nullglob_was_on )) || shopt -u nullglob + return 1 +} + +# Installs the calibrated NVRAM. Returns 0 when files were written, 1 when this +# machine does not need it or already has a complete copy. Reloading brcmfmac +# here would drop a live Wi-Fi connection, including the one carrying an update. +brcmfmac43602_apply() { + local src fwdir generic dmi mac + + brcmfmac43602_needed || return 1 + brcmfmac43602_complete && return 1 + + src=$(brcmfmac43602_nvram_src) + [[ -f $src ]] || return 1 + + fwdir=$(brcmfmac43602_fwdir) + generic="$fwdir/brcmfmac43602-pcie.txt" + dmi=$(brcmfmac43602_dmi_dest) + + brcmfmac43602_as_root mkdir -p "$fwdir" + brcmfmac43602_as_root install -m 644 "$src" "$generic" + if [[ -n $dmi ]]; then + brcmfmac43602_as_root install -m 644 "$src" "$dmi" + fi + + if mac=$(brcmfmac43602_wifi_mac); then + brcmfmac43602_as_root sed -i "s/^macaddr=.*/macaddr=$mac/" "$generic" + if [[ -n $dmi ]]; then + brcmfmac43602_as_root sed -i "s/^macaddr=.*/macaddr=$mac/" "$dmi" + fi + fi + + return 0 +} diff --git a/install/hardware/apple/brcmfmac43602-pcie.txt b/install/hardware/apple/brcmfmac43602-pcie.txt new file mode 100644 index 00000000000..9216608b43e --- /dev/null +++ b/install/hardware/apple/brcmfmac43602-pcie.txt @@ -0,0 +1,256 @@ +sromrev=11 +subvid=0x14e4 +boardtype=0x61b +boardrev=0x1421 +vendid=0x14e4 +devid=0x43ba + +macaddr=00:90:4c:0d:f4:3e + +ccode=00 +regrev=245 + +boardflags=0x10401001 +boardflags2=0x00000002 +boardflags3=0xC0000303 +boardnum=62526 + +swctrlmap_2g=0x08080808,0x04010401,0x08080808,0x00000000,0x000000ff +swctrlmapext_2g=0x00000000,0x00000000,0x00000000,0x000000,0x003 + +swctrlmap_5g=0x08080808,0x04010401,0x08080808,0x00000000,0x000000ff +swctrlmapext_5g=0x00000000,0x00000000,0x00000000,0x000000,0x003 + +aa2g=7 +aa5g=7 +agbg0=133 +agbg1=133 +agbg2=133 +aga0=71 +aga1=71 +aga2=71 +txchain=7 +rxchain=7 +antswitch=0 +tssiposslope2g=1 +epagain2g=0 +pdgain2g=28 +tworangetssi2g=0 +papdcap2g=0 +femctrl=2 +tssiposslope5g=1 +epagain5g=0 +pdgain5g=28 +tworangetssi5g=0 +papdcap5g=0 +gainctrlsph=0 +tempthresh=120 +tempoffset=255 +rawtempsense=0x1ff +measpower=0x7f +tempsense_slope=0xff +tempcorrx=0x3f +tempsense_option=0x3 +xtalfreq=40000 +phycal_tempdelta=40 +temps_period=1 +temps_hysteresis=5 +measpower1=0x7f +measpower2=0x7f +pdoffsetcck=1057 +pdoffset20in40m5gb0=0 +pdoffset20in40m5gb1=0 +pdoffset20in40m5gb2=0 +pdoffset20in40m5gb3=0 +pdoffset20in40m5gb4=0 +pdoffset40in80m5gb0=0 +pdoffset40in80m5gb1=0 +pdoffset40in80m5gb2=0 +pdoffset40in80m5gb3=0 +pdoffset40in80m5gb4=0 +pdoffset20in80m5gb0=0 +pdoffset20in80m5gb1=0 +pdoffset20in80m5gb2=0 +pdoffset20in80m5gb3=0 +pdoffset20in80m5gb4=0 +subband5gver=0x5 +cckbw202gpo=0 +cckbw20ul2gpo=0 +mcsbw202gpo=2536714240 +mcsbw402gpo=2536714240 +dot11agofdmhrbw202gpo=13056 +ofdmlrbw202gpo=0 +mcsbw205glpo=1966288896 +mcsbw405glpo=2252619776 +mcsbw805glpo=2252619776 +mcsbw205gmpo=1966288896 +mcsbw405gmpo=2252619776 +mcsbw805gmpo=2252619776 +mcsbw205ghpo=1966288896 +mcsbw405ghpo=2252619776 +mcsbw805ghpo=2252619776 +mcsbw205gx1po=1966288896 +mcsbw405gx1po=2252619776 +mcsbw805gx1po=2252619776 +mcsbw205gx2po=1966288896 +mcsbw405gx2po=2252619776 +mcsbw805gx2po=2252619776 +mcslr5glpo=0 +mcslr5gmpo=0 +mcslr5ghpo=0 +mcslr5gx1po=0 +mcslr5gx2po=0 +sb20in40hrpo=0 +sb20in80and160hr5glpo=0 +sb40and80hr5glpo=0 +sb20in80and160hr5gmpo=0 +sb40and80hr5gmpo=0 +sb20in80and160hr5ghpo=0 +sb40and80hr5ghpo=0 +sb20in40lrpo=0 +sb20in80and160lr5glpo=0 +sb40and80lr5glpo=0 +sb20in80and160lr5gmpo=0 +sb40and80lr5gmpo=0 +sb20in80and160lr5ghpo=0 +sb40and80lr5ghpo=0 +sb20in80and160hr5gx1po=0 +sb20in80and160lr5gx1po=0 +sb40and80hr5gx1po=0 +sb20in80and160hr5gx2po=0 +sb20in80and160lr5gx2po=0 +sb40and80hr5gx2po=0 +dot11agduphrpo=0 +dot11agduplrpo=52416 +pcieingress_war=15 +sar2g=18 +sar5g=15 +noiselvl2ga0=31 +noiselvl2ga1=31 +noiselvl2ga2=31 +noiselvl5ga0=18,18,18,20 +noiselvl5ga1=1,1,1,17 +noiselvl5ga2=11,11,11,11 +rxgainerr2ga0=63 +rxgainerr2ga1=31 +rxgainerr2ga2=31 +rxgainerr5ga0=50,50,50,52 +rxgainerr5ga1=16,16,16,24 +rxgainerr5ga2=5,5,5,5 +rpcal2g=65535 +rpcal5gb0=65535 +rpcal5gb1=65535 +rpcal5gb2=65535 +rpcal5gb3=65535 +rxgains5gmelnagaina0=3 +rxgains5gmelnagaina1=3 +rxgains5gmelnagaina2=3 +rxgains5gmtrisoa0=9 +rxgains5gmtrisoa1=9 +rxgains5gmtrisoa2=9 +rxgains5gmtrelnabypa0=0 +rxgains5gmtrelnabypa1=0 +rxgains5gmtrelnabypa2=0 +rxgains5ghelnagaina0=2 +rxgains5ghelnagaina1=2 +rxgains5ghelnagaina2=2 +rxgains5ghtrisoa0=8 +rxgains5ghtrisoa1=8 +rxgains5ghtrisoa2=8 +rxgains5ghtrelnabypa0=0 +rxgains5ghtrelnabypa1=0 +rxgains5ghtrelnabypa2=0 +gain_cal_temp=39 +rssi_delta_2gb0=8,16,8,16,253,18,253,18,254,17,254,17 +rssi_delta_2gb1=255,255,255,255,255,255,255,255,255,255,255,255 +rssi_delta_2gb2=255,255,255,255,255,255,255,255,255,255,255,255 +rssi_delta_2gb3=255,255,255,255,255,255,255,255,255,255,255,255 +rssi_delta_2gb4=255,255,255,255,255,255,255,255,255,255,255,255 +rssi_cal_freq_grp_2g=0,0,128,0,0,0,0 +rud_agc_enable=0 +temp_comp_tr_loss=1 +rssi_qdB_en=0 +rssi_delta_5gl=8,6,8,6,8,6,5,6,5,6,5,6,9,8,9,8,9,8 +rssi_delta_5gml=250,237,250,237,250,237,248,236,248,236,248,236,249,237,249,237,249,237 +rssi_delta_5gmu=250,237,250,237,250,237,248,234,248,234,248,234,249,237,249,237,249,237 +rssi_delta_5gh=250,235,250,235,250,235,247,233,247,233,247,233,249,237,249,237,249,237 +rssicorrnorm_c0=251,255 +rssicorrnorm_c1=251,255 +rssicorrnorm_c2=251,255 +trloss_adj_temp_thresh=10 +trloss_adj_time_dur=30 +rstr_rxgaintempcoeff5gl=50,50,50 +rstr_rxgaintempcoeff5gl_elnaoff=44,44,44 +rstr_rxgaintempcoeff5gml=50,50,50 +rstr_rxgaintempcoeff5gml_elnaoff=44,44,44 +rstr_rxgaintempcoeff5gmu=50,50,50 +rstr_rxgaintempcoeff5gmu_elnaoff=44,44,44 +rstr_rxgaintempcoeff5gh=52,52,52 +rstr_rxgaintempcoeff5gh_elnaoff=46,46,46 +rstr_rxgaintempcoeff2g_sub=75,75,75,75,75,75,75,75,75,75,75,75,75,75,75 +rstr_rxgaintempcoeff2g_sub_elnaoff=66,66,66,66,66,66,66,66,66,66,66,66,66,66,66 +length_txcal=65535 +version_txcal=65535 +devicetype_txcal=65535 +caltype_txcal=65535 +tempsense_txcal=35 +ncores_txcal=255 +reserved1_txcal=255 +reserved2_txcal=255 +20mhz_tbl_len_txcal=65535 +nsteps_txcal=22 +nchannels_txcal=10 +chan_id_txcal_ch0=7 +chan_id_txcal_ch1=36 +chan_id_txcal_ch2=52 +chan_id_txcal_ch3=64 +chan_id_txcal_ch4=100 +chan_id_txcal_ch5=120 +chan_id_txcal_ch6=140 +chan_id_txcal_ch7=149 +chan_id_txcal_ch8=157 +chan_id_txcal_ch9=165 +ptssi_txcal_ch0=0 +ptssi_txcal_ch1=0 +ptssi_txcal_ch2=0 +ptssi_txcal_ch3=0 +ptssi_txcal_ch4=0 +ptssi_txcal_ch5=0 +ptssi_txcal_ch6=0 +ptssi_txcal_ch7=0 +ptssi_txcal_ch8=0 +ptssi_txcal_ch9=0 +gain_index_c0_ch0=54 +gain_index_c0_ch1=57 +gain_index_c0_ch2=58 +gain_index_c0_ch3=59 +gain_index_c0_ch4=60 +gain_index_c0_ch5=59 +gain_index_c0_ch6=61 +gain_index_c0_ch7=60 +gain_index_c0_ch8=61 +gain_index_c0_ch9=62 +gain_index_c1_ch0=54 +gain_index_c1_ch1=58 +gain_index_c1_ch2=59 +gain_index_c1_ch3=60 +gain_index_c1_ch4=61 +gain_index_c1_ch5=61 +gain_index_c1_ch6=62 +gain_index_c1_ch7=62 +gain_index_c1_ch8=62 +gain_index_c1_ch9=62 +gain_index_c2_ch0=57 +gain_index_c2_ch1=57 +gain_index_c2_ch2=59 +gain_index_c2_ch3=59 +gain_index_c2_ch4=61 +gain_index_c2_ch5=60 +gain_index_c2_ch6=61 +gain_index_c2_ch7=61 +gain_index_c2_ch8=61 +gain_index_c2_ch9=61 +tssi_chan0_c0=248,244,241,237,232,226,220,211,203,193,183,172,162,150,138,128,116,105,94,83,73,61 +tssi_chan0_c1=247,245,241,237,232,226,218,210,201,191,181,168,158,146,134,122,111,98,87,75,64,52 +tssi_chan0_c2=248,245,242,238,233,227,221,213,204,194,185,173,162,151,140,129,118,107,97,87,77,67 +tssi_chan1_c0=234,228,223,218,212,205,198,191,183,176,168,159,150,141,131,121,110,98,87,75,64,53 diff --git a/install/hardware/apple/fix-brcmfmac-5ghz.sh b/install/hardware/apple/fix-brcmfmac-5ghz.sh new file mode 100644 index 00000000000..3961f6388ed --- /dev/null +++ b/install/hardware/apple/fix-brcmfmac-5ghz.sh @@ -0,0 +1,15 @@ +# Apple Macs with BCM43602 (2015–2017, PCI 14e4:43ba) only see 2.4 GHz out of +# the box: linux-firmware-broadcom has no board NVRAM, so brcmfmac loads +# placeholder 5 GHz calibration. Install a calibrated copy under +# /usr/lib/firmware/updates so the card advertises the 5 GHz band. +# +# See install/hardware/apple/brcmfmac-43602.sh for the NVRAM provenance and +# why this does not also hard-code a cfg80211 country. + +: "${OMARCHY_INSTALL:=${OMARCHY_PATH:-/usr/share/omarchy}/install}" +# shellcheck source=brcmfmac-43602.sh +source "$OMARCHY_INSTALL/hardware/apple/brcmfmac-43602.sh" + +if brcmfmac43602_apply; then + echo "Installed BCM43602 5 GHz NVRAM" +fi diff --git a/manual/44-mac-support.md b/manual/44-mac-support.md index e86fdd064d8..a4eb331401e 100644 --- a/manual/44-mac-support.md +++ b/manual/44-mac-support.md @@ -35,7 +35,7 @@ It is necessary to disable Apple's Secure Boot in order to boot the bootable USB 3. Select the orange EFI Boot device 4. Proceed with the [install as normal](02-getting-started.md) -The installer detects Mac hardware and applies the needed fixes automatically: Broadcom Wi-Fi drivers and firmware, the SPI keyboard driver on the MacBook models that need it, and an NVMe suspend fix for those same models. +The installer detects Mac hardware and applies the needed fixes automatically: Broadcom Wi-Fi drivers and firmware (including 5 GHz board calibration on BCM43602), the SPI keyboard driver on the MacBook models that need it, and an NVMe suspend fix for those same models. ### Known Limitations diff --git a/migrations/1787312531.sh b/migrations/1787312531.sh new file mode 100644 index 00000000000..2075daa97ba --- /dev/null +++ b/migrations/1787312531.sh @@ -0,0 +1,13 @@ +echo "Unlock 5 GHz Wi-Fi on BCM43602 Macs" + +# Existing installs never ran the install leaf, so they still only see 2.4 GHz. +# See install/hardware/apple/brcmfmac-43602.sh for the failure this repairs. +: "${OMARCHY_INSTALL:=${OMARCHY_PATH:-/usr/share/omarchy}/install}" +# shellcheck source=../install/hardware/apple/brcmfmac-43602.sh +source "$OMARCHY_INSTALL/hardware/apple/brcmfmac-43602.sh" + +if brcmfmac43602_apply; then + # The driver only rereads NVRAM when brcmfmac next loads. Reloading it here + # would drop a Wi-Fi connection that works on the network carrying this update. + omarchy-state set reboot-required +fi diff --git a/test/shell.d/brcmfmac-5ghz-test.sh b/test/shell.d/brcmfmac-5ghz-test.sh new file mode 100755 index 00000000000..ed85a56ea0d --- /dev/null +++ b/test/shell.d/brcmfmac-5ghz-test.sh @@ -0,0 +1,226 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +leaf="$ROOT/install/hardware/apple/fix-brcmfmac-5ghz.sh" +helper="$ROOT/install/hardware/apple/brcmfmac-43602.sh" +nvram="$ROOT/install/hardware/apple/brcmfmac43602-pcie.txt" +all="$ROOT/install/hardware/all.sh" +migration="$ROOT/migrations/1787312531.sh" +manual="$ROOT/manual/44-mac-support.md" + +grep -q 'apple/fix-brcmfmac-5ghz.sh' "$all" || + fail "the BCM43602 5 GHz NVRAM runs during hardware setup" +grep -q 'apple/fix-brcmfmac-supplicant.sh' "$all" || + fail "the 5 GHz leaf does not replace the WPA handshake quirk" +pass "the BCM43602 5 GHz NVRAM runs during setup" + +[[ -f $nvram ]] || fail "the calibrated NVRAM is in the tree" +grep -qx 'aa5g=7' "$nvram" || fail "NVRAM enables the 5 GHz antenna chain" +grep -qx 'txchain=7' "$nvram" || fail "NVRAM enables the full TX chain" +grep -qx 'rxchain=7' "$nvram" || fail "NVRAM enables the full RX chain" +grep -qx 'ccode=00' "$nvram" || fail "NVRAM defers channel legality to the host" +grep -qx 'regrev=245' "$nvram" || fail "NVRAM uses the host-deferral revision" +! grep -q '^aa5g=1$' "$nvram" || fail "NVRAM is not the placeholder board file" +pass "the vendored NVRAM has full 5 GHz calibration" + +grep -Fq '5 GHz board calibration on BCM43602' "$manual" || + fail "Mac support chapter mentions 5 GHz calibration" +pass "Mac support chapter mentions 5 GHz calibration" + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +stub_bin="$test_tmp/bin" +calls="$test_tmp/calls.log" +fwdir="$test_tmp/firmware/brcm" +netdir="$test_tmp/net" +mkdir -p "$stub_bin" "$test_tmp/dmi" "$fwdir" + +cat >"$stub_bin/lspci" <<'SH' +#!/bin/bash + +# Chatty like real lspci: keep writing well past the pipe buffer after the +# match, so a grep -q consumer would kill this stub with SIGPIPE and pipefail +# would read that as "no such hardware" (#6608). +if [[ -n ${WIFI_ID:-} ]]; then + echo "03:00.0 Network controller [0280]: Broadcom Inc. Wireless [14e4:$WIFI_ID]" +fi +for _ in {1..4096}; do + echo '02:00.0 Host bridge [0600]: Filler Device [ffff:0000]' +done +SH + +cat >"$stub_bin/sudo" <<'SH' +#!/bin/bash + +printf 'sudo' >>"$TEST_LOG" +printf '\t%s' "$@" >>"$TEST_LOG" +printf '\n' >>"$TEST_LOG" +"$@" +SH + +cat >"$stub_bin/omarchy-state" <<'SH' +#!/bin/bash + +printf 'omarchy-state' >>"$TEST_LOG" +printf '\t%s' "$@" >>"$TEST_LOG" +printf '\n' >>"$TEST_LOG" +SH + +chmod +x "$stub_bin"/* + +generic() { + printf '%s\n' "$fwdir/brcmfmac43602-pcie.txt" +} + +dmi_file() { + local vendor=$1 product=$2 + printf '%s\n' "$fwdir/brcmfmac43602-pcie.${vendor}-${product}.txt" +} + +invoke_leaf() { + local wifi_id="${1:-}" + WIFI_ID="$wifi_id" PATH="$stub_bin:$PATH" TEST_LOG="$calls" \ + OMARCHY_INSTALL="$ROOT/install" \ + OMARCHY_BRCMFMAC_FWDIR="$fwdir" \ + OMARCHY_BRCMFMAC_DMI_VENDOR="$test_tmp/dmi/sys_vendor" \ + OMARCHY_BRCMFMAC_DMI_PRODUCT="$test_tmp/dmi/product_name" \ + OMARCHY_BRCMFMAC_NETDIR="$netdir" \ + bash -eE -o pipefail -c 'source "$1"' bash "$leaf" "$test_tmp/dmi/sys_vendor" + printf '%s' "$product" >"$test_tmp/dmi/product_name" + + invoke_leaf "$wifi_id" +} + +run_migration() { + local vendor=$1 product=$2 wifi_id="${3:-}" + printf '%s' "$vendor" >"$test_tmp/dmi/sys_vendor" + printf '%s' "$product" >"$test_tmp/dmi/product_name" + : >"$calls" + + WIFI_ID="$wifi_id" PATH="$stub_bin:$PATH" TEST_LOG="$calls" \ + OMARCHY_INSTALL="$ROOT/install" \ + OMARCHY_PATH="$ROOT" \ + OMARCHY_BRCMFMAC_FWDIR="$fwdir" \ + OMARCHY_BRCMFMAC43602_NVRAM="$nvram" \ + OMARCHY_BRCMFMAC_DMI_VENDOR="$test_tmp/dmi/sys_vendor" \ + OMARCHY_BRCMFMAC_DMI_PRODUCT="$test_tmp/dmi/product_name" \ + OMARCHY_BRCMFMAC_NETDIR="$netdir" \ + bash -euo pipefail "$migration" >/dev/null +} + +run_leaf "Apple Inc." "MacBookPro14,3" 43ba >/dev/null +[[ -f "$(generic)" ]] || fail "a BCM43602 Mac gets the generic NVRAM" +[[ -f "$(dmi_file "Apple Inc." "MacBookPro14,3")" ]] || + fail "a BCM43602 Mac gets the DMI-specific NVRAM" +grep -qx 'aa5g=7' "$(generic)" || fail "installed NVRAM enables 5 GHz" +pass "a MacBookPro14,3 with BCM43602 gets both NVRAM names" + +run_leaf "Apple Inc." "MacBookPro14,2" 43ba >/dev/null +[[ -f "$(dmi_file "Apple Inc." "MacBookPro14,2")" ]] || + fail "MacBookPro14,2 gets its DMI-specific NVRAM" +pass "MacBookPro14,2 gets its DMI-specific NVRAM" + +run_leaf "Apple Computer, Inc." "MacBookPro11,4" 43ba >/dev/null +[[ -f "$(dmi_file "Apple Computer, Inc." "MacBookPro11,4")" ]] || + fail "the older Apple vendor string is recognized" +pass "the older Apple vendor string is recognized" + +# Live interface address is written into macaddr= so we don't change the +# station ID the machine is already using. +rm -rf "$fwdir" "$netdir" +mkdir -p "$fwdir" "$netdir/wlp3s0/wireless" +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +printf '8c:85:90:a3:af:dc\n' >"$netdir/wlp3s0/address" +invoke_leaf 43ba >/dev/null +grep -qx 'macaddr=8c:85:90:a3:af:dc' "$(generic)" || + fail "the live Wi-Fi MAC is written into the NVRAM" "$(head "$(generic)")" +grep -qx 'macaddr=8c:85:90:a3:af:dc' "$(dmi_file "Apple Inc." "MacBookPro14,3")" || + fail "the DMI-specific NVRAM gets the live MAC" +pass "the live Wi-Fi MAC is written into the NVRAM" + +# No wireless interface yet (early install): keep the dump's default MAC. +rm -rf "$netdir" +run_leaf "Apple Inc." "MacBookPro14,3" 43ba >/dev/null +grep -qx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || + fail "the firmware-default MAC is kept when no interface exists" +pass "the firmware-default MAC is kept when no interface exists" + +run_leaf "Apple Inc." "MacBookPro14,3" 43a0 >/dev/null +[[ ! -f "$(generic)" ]] || fail "a Mac whose Wi-Fi brcmfmac does not drive is left alone" +pass "a Mac whose Wi-Fi brcmfmac does not drive is left alone" + +run_leaf "Apple Inc." "MacBookPro15,1" 4488 >/dev/null +[[ ! -f "$(generic)" ]] || fail "a T2-era chip is left to apple-bcm-firmware" +pass "a T2-era chip is left to apple-bcm-firmware" + +run_leaf "Apple Inc." "MacBookPro14,3" 43bb >/dev/null +[[ ! -f "$(generic)" ]] || fail "the 2 GHz-only BCM43602 variant is left alone" +pass "the 2 GHz-only BCM43602 variant is left alone" + +run_leaf "LENOVO" "ThinkPad" 43ba >/dev/null +[[ ! -f "$(generic)" ]] || fail "non-Apple hardware is left alone" +pass "non-Apple hardware is left alone" + +run_leaf "Apple Inc." "MacBookPro14,3" >/dev/null +[[ ! -f "$(generic)" ]] || fail "a Mac with no wireless device is left alone" +pass "a Mac with no wireless device is left alone" + +# Incomplete placeholder already on disk is replaced. +mkdir -p "$fwdir" +printf 'aa5g=1\ntxchain=1\nccode=ALL\n' >"$(generic)" +run_leaf "Apple Inc." "MacBookPro14,3" 43ba >/dev/null +grep -qx 'aa5g=7' "$(generic)" || fail "a placeholder NVRAM is replaced" +pass "a placeholder NVRAM is replaced" + +# Complete files are left alone, including a user-set MAC. +mkdir -p "$fwdir" +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +cp "$nvram" "$(generic)" +cp "$nvram" "$(dmi_file "Apple Inc." "MacBookPro14,3")" +sed -i 's/^macaddr=.*/macaddr=de:ad:be:ef:00:01/' "$(generic)" +sed -i 's/^macaddr=.*/macaddr=de:ad:be:ef:00:01/' "$(dmi_file "Apple Inc." "MacBookPro14,3")" +: >"$calls" +invoke_leaf 43ba >/dev/null +grep -qx 'macaddr=de:ad:be:ef:00:01' "$(generic)" || + fail "a complete NVRAM is not overwritten" +[[ ! -s $calls ]] || fail "a complete install does not escalate" "$(cat "$calls")" +pass "a complete NVRAM is not overwritten" + +rm -rf "$fwdir" "$netdir" +mkdir -p "$fwdir" +run_migration "Apple Inc." "MacBookPro14,3" 43ba +[[ -f "$(generic)" ]] || fail "the migration installs NVRAM on an existing Mac" +grep -Fq $'omarchy-state\tset\treboot-required' "$calls" || + fail "the migration asks for the reboot that applies it" "$(cat "$calls")" +pass "the migration installs NVRAM and asks for a reboot" + +run_migration "Apple Inc." "MacBookPro14,3" 43ba +[[ ! -s $calls ]] || fail "the migration is idempotent" "$(cat "$calls")" +pass "the migration is idempotent" + +rm -rf "$fwdir" +run_migration "LENOVO" "ThinkPad" 43ba +[[ ! -e "$(generic)" ]] || fail "the migration skips non-Apple hardware" +[[ ! -s $calls ]] || fail "the migration escalates nothing on unaffected machines" "$(cat "$calls")" +pass "the migration skips non-Apple hardware" + +# Helper is sourced by install leaves; leaking nullglob would change later globbing. +# shellcheck source=../../install/hardware/apple/brcmfmac-43602.sh +OMARCHY_INSTALL="$ROOT/install" source "$helper" +OMARCHY_BRCMFMAC_NETDIR="$test_tmp/empty-net" brcmfmac43602_wifi_mac || true +shopt -q nullglob && fail "applying NVRAM does not leave nullglob on" +pass "applying NVRAM does not leave nullglob on" diff --git a/test/shell.d/unowned-system-paths-test.sh b/test/shell.d/unowned-system-paths-test.sh index f3bec23c743..29ca3f66b1e 100755 --- a/test/shell.d/unowned-system-paths-test.sh +++ b/test/shell.d/unowned-system-paths-test.sh @@ -30,6 +30,10 @@ allowed = { # Hardware-conditional sleep hooks, installed only on the machines that need # them so the hook does not exist where it does not apply. "/usr/lib/systemd/system-sleep", + # Hardware-conditional BCM43602 board NVRAM. linux-firmware-broadcom ships the + # chip firmware but not this calibration file; /usr/lib/firmware/updates is + # the kernel override path so a later packaged txt cannot conflict. + "/usr/lib/firmware/updates", # Written through a variable, so the scan below cannot see them at the point # they are written. Both drop configuration into another project's tree rather # than Omarchy's, which is why neither is a candidate for omarchy-settings. From 53796d773f5a34a1f94712e61749c91554b2b06f Mon Sep 17 00:00:00 2001 From: Shawn Yeager Date: Fri, 21 Aug 2026 09:09:18 -0500 Subject: [PATCH 2/6] Bound BCM43602 NVRAM to 14,2/14,3 and stop shipping a donor MAC Gate on the 2017 Touch Bar models this dump has actually been seen to work on. Resolve the NIC through its PCI BDF, strip macaddr= when none is found, never overwrite an existing board file, and stop wrapping install in `if` so a failed write cannot mark the migration done. --- .../firmware}/apple/brcmfmac43602-pcie.txt | 14 + install/hardware/apple/brcmfmac-43602.sh | 148 ++++++----- install/hardware/apple/fix-brcmfmac-5ghz.sh | 25 +- manual/44-mac-support.md | 2 +- migrations/1787312531.sh | 23 +- test/shell.d/brcmfmac-5ghz-test.sh | 239 +++++++++++++----- test/shell.d/unowned-system-paths-test.sh | 6 +- 7 files changed, 306 insertions(+), 151 deletions(-) rename {install/hardware => default/firmware}/apple/brcmfmac43602-pcie.txt (88%) diff --git a/install/hardware/apple/brcmfmac43602-pcie.txt b/default/firmware/apple/brcmfmac43602-pcie.txt similarity index 88% rename from install/hardware/apple/brcmfmac43602-pcie.txt rename to default/firmware/apple/brcmfmac43602-pcie.txt index 9216608b43e..271d33403f1 100644 --- a/install/hardware/apple/brcmfmac43602-pcie.txt +++ b/default/firmware/apple/brcmfmac43602-pcie.txt @@ -1,3 +1,17 @@ +# Community BCM43602 board calibration from kernel.org bugzilla attachment +# 290569 (https://bugzilla.kernel.org/attachment.cgi?id=290569). Not provided +# or licensed by Broadcom or Apple. linux-firmware ships only the .bin for this +# part; without an NVRAM, brcmfmac advertises 2.4 GHz only. +# +# ccode=00 / regrev=245 defers channel legality to the host regulatory domain. +# Omarchy already persists that from the timezone via set-wireless-regdom.sh. +# The values below are otherwise this dump's calibration data; do not edit them. +# +# Lines starting with # are comments; the brcmfmac NVRAM parser ignores them. +# macaddr= is a placeholder (00:90:4c is the Broadcom OUI, not a device MAC). +# The install leaf and migration substitute the NIC's live address or strip +# the line so this value is never used verbatim. + sromrev=11 subvid=0x14e4 boardtype=0x61b diff --git a/install/hardware/apple/brcmfmac-43602.sh b/install/hardware/apple/brcmfmac-43602.sh index 2a458559227..89f2fd1dfdf 100644 --- a/install/hardware/apple/brcmfmac-43602.sh +++ b/install/hardware/apple/brcmfmac-43602.sh @@ -3,16 +3,20 @@ # linux-firmware-broadcom ships the BCM43602 chip firmware but not a board # calibration file. Without one, brcmfmac brings the card up with placeholder # 5 GHz values (aa5g=1, no per-channel tx-power tables) and only 2.4 GHz -# networks are visible. A calibrated NVRAM with aa5g=7 / txchain=7 / rxchain=7 -# and ccode=00 / regrev=245 (defer channel legality to the host) unlocks 5 GHz. +# networks are visible. # -# The NVRAM is a community dump attached to a BCM43602 kernel.org bugzilla -# ticket (attachment 290569), not vendor-certified. Channel legality still -# follows the host regulatory domain; Omarchy already persists that from the -# timezone via install/hardware/set-wireless-regdom.sh. +# The NVRAM is a community dump (kernel.org bugzilla attachment 290569) with +# ccode=00 / regrev=245, which defers channel legality to the host. Omarchy +# already persists that from the timezone via set-wireless-regdom.sh. It is +# calibration for one board, not every Apple BCM43602: this only runs on the +# 2017 Touch Bar MacBook Pros (MacBookPro14,2 / 14,3) where the dump has been +# seen to work. MacBookPro13,3 is the same PCI ID and is excluded — a report +# of this dump on that model described unusable range. # # Destinations live under /usr/lib/firmware/updates so they override, and do -# not collide with, linux-firmware-broadcom. +# not collide with, linux-firmware-broadcom. A file already at either the +# override path or the packaged path wins: user-placed or a future +# linux-firmware board file both outrank this copy. brcmfmac43602_as_root() { if (( EUID == 0 )); then @@ -26,8 +30,12 @@ brcmfmac43602_fwdir() { printf '%s\n' "${OMARCHY_BRCMFMAC_FWDIR:-/usr/lib/firmware/updates/brcm}" } +brcmfmac43602_packaged_fwdir() { + printf '%s\n' "${OMARCHY_BRCMFMAC_PACKAGED_FWDIR:-/usr/lib/firmware/brcm}" +} + brcmfmac43602_nvram_src() { - printf '%s\n' "${OMARCHY_BRCMFMAC43602_NVRAM:-${OMARCHY_INSTALL:-${OMARCHY_PATH:-/usr/share/omarchy}/install}/hardware/apple/brcmfmac43602-pcie.txt}" + printf '%s\n' "${OMARCHY_BRCMFMAC43602_NVRAM:-${OMARCHY_PATH:-/usr/share/omarchy}/default/firmware/apple/brcmfmac43602-pcie.txt}" } brcmfmac43602_dmi_vendor() { @@ -38,89 +46,101 @@ brcmfmac43602_dmi_product() { cat "${OMARCHY_BRCMFMAC_DMI_PRODUCT:-/sys/class/dmi/id/product_name}" 2>/dev/null || true } -# Dual-band BCM43602 (14e4:43ba) on Apple hardware. The 2 GHz-only (43bb) and -# 5 GHz-only (43bc) variants are left alone, as are T2-era chips that already -# get board files from apple-bcm-firmware. +brcmfmac43602_pci_devices() { + printf '%s\n' "${OMARCHY_BRCMFMAC_PCI_DEVICES:-/sys/bus/pci/devices}" +} + +# Dual-band BCM43602 (14e4:43ba) on the 2017 Touch Bar MacBook Pros. The 2 GHz +# only (43bb) and 5 GHz-only (43bc) variants are left alone, as are T2-era +# chips that already get board files from apple-bcm-firmware. brcmfmac43602_needed() { - local sys_vendor + local sys_vendor product sys_vendor=$(brcmfmac43602_dmi_vendor) + product=$(brcmfmac43602_dmi_product) [[ $sys_vendor == Apple* ]] || return 1 + [[ $product =~ ^MacBookPro14,[23]$ ]] || return 1 lspci -nn | grep "14e4:43ba" >/dev/null } -brcmfmac43602_file_complete() { - local file=$1 - [[ -f $file ]] || return 1 - grep -qx 'aa5g=7' "$file" || return 1 - grep -qx 'txchain=7' "$file" || return 1 - grep -qx 'ccode=00' "$file" || return 1 -} - -brcmfmac43602_dmi_dest() { - local fwdir vendor product - fwdir=$(brcmfmac43602_fwdir) +brcmfmac43602_dmi_name() { + local vendor product vendor=$(brcmfmac43602_dmi_vendor) product=$(brcmfmac43602_dmi_product) [[ -n $vendor && -n $product ]] || return 0 - printf '%s\n' "$fwdir/brcmfmac43602-pcie.${vendor}-${product}.txt" + printf '%s\n' "brcmfmac43602-pcie.${vendor}-${product}.txt" } -brcmfmac43602_complete() { - local fwdir generic dmi - fwdir=$(brcmfmac43602_fwdir) - generic="$fwdir/brcmfmac43602-pcie.txt" - brcmfmac43602_file_complete "$generic" || return 1 - dmi=$(brcmfmac43602_dmi_dest) - [[ -z $dmi ]] && return 0 - brcmfmac43602_file_complete "$dmi" +brcmfmac43602_installed() { + local name dir + for dir in "$(brcmfmac43602_fwdir)" "$(brcmfmac43602_packaged_fwdir)"; do + [[ -e $dir/brcmfmac43602-pcie.txt ]] && return 0 + name=$(brcmfmac43602_dmi_name) + [[ -n $name && -e "$dir/$name" ]] && return 0 + done + return 1 } +# The NIC bound to 14e4:43ba, not the first wireless interface in glob order +# (a USB adapter at install time would otherwise donate its address). brcmfmac43602_wifi_mac() { - local netdir wireless iface mac nullglob_was_on=0 - netdir="${OMARCHY_BRCMFMAC_NETDIR:-/sys/class/net}" - shopt -q nullglob && nullglob_was_on=1 - shopt -s nullglob - for wireless in "$netdir"/*/wireless; do - iface=$(basename "$(dirname "$wireless")") - mac=$(cat "$netdir/$iface/address" 2>/dev/null || true) - if [[ $mac =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]; then - (( nullglob_was_on )) || shopt -u nullglob - printf '%s\n' "$mac" - return 0 - fi - done - (( nullglob_was_on )) || shopt -u nullglob + local bdf pci_devices net_addrs mac + pci_devices=$(brcmfmac43602_pci_devices) + bdf=$(lspci -Dnn | awk '/14e4:43ba/ { if (!found) { print $1; found=1 } }') + [[ -n $bdf ]] || return 1 + net_addrs=("$pci_devices/$bdf"/net/*/address) + mac=$(cat "${net_addrs[0]}" 2>/dev/null || true) + if [[ $mac =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]; then + printf '%s\n' "$mac" + return 0 + fi return 1 } -# Installs the calibrated NVRAM. Returns 0 when files were written, 1 when this -# machine does not need it or already has a complete copy. Reloading brcmfmac -# here would drop a live Wi-Fi connection, including the one carrying an update. -brcmfmac43602_apply() { - local src fwdir generic dmi mac - - brcmfmac43602_needed || return 1 - brcmfmac43602_complete && return 1 +# Reloading brcmfmac here would drop a live Wi-Fi connection, including the +# one carrying an update. Callers must not wrap this in `if`; bash would then +# disable errexit for the body and a failed install would look like success. +brcmfmac43602_install() { + local src fwdir generic dmi mac work name src=$(brcmfmac43602_nvram_src) [[ -f $src ]] || return 1 fwdir=$(brcmfmac43602_fwdir) generic="$fwdir/brcmfmac43602-pcie.txt" - dmi=$(brcmfmac43602_dmi_dest) - - brcmfmac43602_as_root mkdir -p "$fwdir" - brcmfmac43602_as_root install -m 644 "$src" "$generic" - if [[ -n $dmi ]]; then - brcmfmac43602_as_root install -m 644 "$src" "$dmi" + dmi="" + name=$(brcmfmac43602_dmi_name) + if [[ -n $name ]]; then + dmi="$fwdir/$name" fi + work=$(mktemp) || return 1 if mac=$(brcmfmac43602_wifi_mac); then - brcmfmac43602_as_root sed -i "s/^macaddr=.*/macaddr=$mac/" "$generic" - if [[ -n $dmi ]]; then - brcmfmac43602_as_root sed -i "s/^macaddr=.*/macaddr=$mac/" "$dmi" + if ! sed "s/^macaddr=.*/macaddr=$mac/" "$src" >"$work"; then + rm -f "$work" + return 1 + fi + else + # No MAC discoverable: drop the line and let the firmware use the OTP + # address, which is how these NICs already run with no NVRAM at all. + if ! sed '/^macaddr=/d' "$src" >"$work"; then + rm -f "$work" + return 1 fi fi - return 0 + if ! brcmfmac43602_as_root mkdir -p "$fwdir"; then + rm -f "$work" + return 1 + fi + if ! brcmfmac43602_as_root install -m 644 "$work" "$generic"; then + rm -f "$work" + return 1 + fi + if [[ -n $dmi ]]; then + if ! brcmfmac43602_as_root install -m 644 "$work" "$dmi"; then + rm -f "$work" + return 1 + fi + fi + rm -f "$work" } diff --git a/install/hardware/apple/fix-brcmfmac-5ghz.sh b/install/hardware/apple/fix-brcmfmac-5ghz.sh index 3961f6388ed..45cdf98d0c6 100644 --- a/install/hardware/apple/fix-brcmfmac-5ghz.sh +++ b/install/hardware/apple/fix-brcmfmac-5ghz.sh @@ -1,15 +1,22 @@ -# Apple Macs with BCM43602 (2015–2017, PCI 14e4:43ba) only see 2.4 GHz out of -# the box: linux-firmware-broadcom has no board NVRAM, so brcmfmac loads -# placeholder 5 GHz calibration. Install a calibrated copy under -# /usr/lib/firmware/updates so the card advertises the 5 GHz band. +# 2017 Touch Bar MacBook Pros (MacBookPro14,2 / 14,3) with BCM43602 only see +# 2.4 GHz out of the box: linux-firmware-broadcom has no board NVRAM, so +# brcmfmac loads placeholder 5 GHz calibration. Install a calibrated copy +# under /usr/lib/firmware/updates so the card advertises the 5 GHz band. # -# See install/hardware/apple/brcmfmac-43602.sh for the NVRAM provenance and -# why this does not also hard-code a cfg80211 country. +# See install/hardware/apple/brcmfmac-43602.sh for the NVRAM provenance, the +# model gate, and why this does not also hard-code a cfg80211 country. -: "${OMARCHY_INSTALL:=${OMARCHY_PATH:-/usr/share/omarchy}/install}" +: "${OMARCHY_PATH:=/usr/share/omarchy}" +: "${OMARCHY_INSTALL:=$OMARCHY_PATH/install}" # shellcheck source=brcmfmac-43602.sh source "$OMARCHY_INSTALL/hardware/apple/brcmfmac-43602.sh" -if brcmfmac43602_apply; then - echo "Installed BCM43602 5 GHz NVRAM" +if ! brcmfmac43602_needed; then + return 0 fi +if brcmfmac43602_installed; then + return 0 +fi + +echo "Installed BCM43602 5 GHz NVRAM" +brcmfmac43602_install diff --git a/manual/44-mac-support.md b/manual/44-mac-support.md index a4eb331401e..4e285002941 100644 --- a/manual/44-mac-support.md +++ b/manual/44-mac-support.md @@ -35,7 +35,7 @@ It is necessary to disable Apple's Secure Boot in order to boot the bootable USB 3. Select the orange EFI Boot device 4. Proceed with the [install as normal](02-getting-started.md) -The installer detects Mac hardware and applies the needed fixes automatically: Broadcom Wi-Fi drivers and firmware (including 5 GHz board calibration on BCM43602), the SPI keyboard driver on the MacBook models that need it, and an NVMe suspend fix for those same models. +The installer detects Mac hardware and applies the needed fixes automatically: Broadcom Wi-Fi drivers and firmware (including 5 GHz board calibration on 2017 Touch Bar MacBook Pros), the SPI keyboard driver on the MacBook models that need it, and an NVMe suspend fix for those same models. ### Known Limitations diff --git a/migrations/1787312531.sh b/migrations/1787312531.sh index 2075daa97ba..5c9fad174d6 100644 --- a/migrations/1787312531.sh +++ b/migrations/1787312531.sh @@ -1,13 +1,22 @@ -echo "Unlock 5 GHz Wi-Fi on BCM43602 Macs" +echo "Unlock 5 GHz Wi-Fi on 2017 Touch Bar MacBook Pros" # Existing installs never ran the install leaf, so they still only see 2.4 GHz. -# See install/hardware/apple/brcmfmac-43602.sh for the failure this repairs. -: "${OMARCHY_INSTALL:=${OMARCHY_PATH:-/usr/share/omarchy}/install}" +# See install/hardware/apple/brcmfmac-43602.sh for the failure this repairs +# and for why the gate is limited to MacBookPro14,2 / 14,3. +: "${OMARCHY_PATH:=/usr/share/omarchy}" +: "${OMARCHY_INSTALL:=$OMARCHY_PATH/install}" # shellcheck source=../install/hardware/apple/brcmfmac-43602.sh source "$OMARCHY_INSTALL/hardware/apple/brcmfmac-43602.sh" -if brcmfmac43602_apply; then - # The driver only rereads NVRAM when brcmfmac next loads. Reloading it here - # would drop a Wi-Fi connection that works on the network carrying this update. - omarchy-state set reboot-required +if ! brcmfmac43602_needed; then + exit 0 fi +if brcmfmac43602_installed; then + exit 0 +fi + +brcmfmac43602_install + +# The driver only rereads NVRAM when brcmfmac next loads. Reloading it here +# would drop a Wi-Fi connection that works on the network carrying this update. +omarchy-state set reboot-required diff --git a/test/shell.d/brcmfmac-5ghz-test.sh b/test/shell.d/brcmfmac-5ghz-test.sh index ed85a56ea0d..1846f4d8cc0 100755 --- a/test/shell.d/brcmfmac-5ghz-test.sh +++ b/test/shell.d/brcmfmac-5ghz-test.sh @@ -5,8 +5,7 @@ set -euo pipefail source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" leaf="$ROOT/install/hardware/apple/fix-brcmfmac-5ghz.sh" -helper="$ROOT/install/hardware/apple/brcmfmac-43602.sh" -nvram="$ROOT/install/hardware/apple/brcmfmac43602-pcie.txt" +nvram="$ROOT/default/firmware/apple/brcmfmac43602-pcie.txt" all="$ROOT/install/hardware/all.sh" migration="$ROOT/migrations/1787312531.sh" manual="$ROOT/manual/44-mac-support.md" @@ -23,10 +22,13 @@ grep -qx 'txchain=7' "$nvram" || fail "NVRAM enables the full TX chain" grep -qx 'rxchain=7' "$nvram" || fail "NVRAM enables the full RX chain" grep -qx 'ccode=00' "$nvram" || fail "NVRAM defers channel legality to the host" grep -qx 'regrev=245' "$nvram" || fail "NVRAM uses the host-deferral revision" +grep -q '290569' "$nvram" || fail "NVRAM documents its bugzilla attachment" +grep -qF 'https://bugzilla.kernel.org/attachment.cgi?id=290569' "$nvram" || + fail "NVRAM names the exact attachment it was vendored from" ! grep -q '^aa5g=1$' "$nvram" || fail "NVRAM is not the placeholder board file" -pass "the vendored NVRAM has full 5 GHz calibration" +pass "the vendored NVRAM has full 5 GHz calibration and provenance" -grep -Fq '5 GHz board calibration on BCM43602' "$manual" || +grep -Fq '5 GHz board calibration on 2017 Touch Bar MacBook Pros' "$manual" || fail "Mac support chapter mentions 5 GHz calibration" pass "Mac support chapter mentions 5 GHz calibration" @@ -35,21 +37,39 @@ trap 'rm -rf "$test_tmp"' EXIT stub_bin="$test_tmp/bin" calls="$test_tmp/calls.log" -fwdir="$test_tmp/firmware/brcm" -netdir="$test_tmp/net" -mkdir -p "$stub_bin" "$test_tmp/dmi" "$fwdir" +fwdir="$test_tmp/firmware/updates/brcm" +packaged="$test_tmp/firmware/brcm" +pci_devices="$test_tmp/sys-pci" +mkdir -p "$stub_bin" "$test_tmp/dmi" cat >"$stub_bin/lspci" <<'SH' #!/bin/bash +# Real lspci prints the domain in the BDF only when -D is passed, and /sys uses +# the domain form, so a consumer that parses plain lspci -nn finds no MAC. +domain=0 +for arg in "$@"; do + if [[ $arg == -*D* ]]; then + domain=1 + fi +done + +if (( domain == 1 )); then + wifi_bdf=0000:03:00.0 + filler_bdf=0000:02:00.0 +else + wifi_bdf=03:00.0 + filler_bdf=02:00.0 +fi + # Chatty like real lspci: keep writing well past the pipe buffer after the # match, so a grep -q consumer would kill this stub with SIGPIPE and pipefail # would read that as "no such hardware" (#6608). if [[ -n ${WIFI_ID:-} ]]; then - echo "03:00.0 Network controller [0280]: Broadcom Inc. Wireless [14e4:$WIFI_ID]" + echo "$wifi_bdf Network controller [0280]: Broadcom Inc. Wireless [14e4:$WIFI_ID]" fi for _ in {1..4096}; do - echo '02:00.0 Host bridge [0600]: Filler Device [ffff:0000]' + echo "$filler_bdf Host bridge [0600]: Filler Device [ffff:0000]" done SH @@ -81,25 +101,31 @@ dmi_file() { printf '%s\n' "$fwdir/brcmfmac43602-pcie.${vendor}-${product}.txt" } +provide_mac() { + mkdir -p "$pci_devices/0000:03:00.0/net/wlp3s0" + printf 'aa:bb:cc:dd:ee:ff\n' >"$pci_devices/0000:03:00.0/net/wlp3s0/address" +} + +# Production run_logged uses bash -eE with no pipefail. invoke_leaf() { local wifi_id="${1:-}" WIFI_ID="$wifi_id" PATH="$stub_bin:$PATH" TEST_LOG="$calls" \ + OMARCHY_PATH="$ROOT" \ OMARCHY_INSTALL="$ROOT/install" \ OMARCHY_BRCMFMAC_FWDIR="$fwdir" \ + OMARCHY_BRCMFMAC_PACKAGED_FWDIR="$packaged" \ OMARCHY_BRCMFMAC_DMI_VENDOR="$test_tmp/dmi/sys_vendor" \ OMARCHY_BRCMFMAC_DMI_PRODUCT="$test_tmp/dmi/product_name" \ - OMARCHY_BRCMFMAC_NETDIR="$netdir" \ - bash -eE -o pipefail -c 'source "$1"' bash "$leaf" "$test_tmp/dmi/sys_vendor" printf '%s' "$product" >"$test_tmp/dmi/product_name" - invoke_leaf "$wifi_id" } @@ -110,53 +136,63 @@ run_migration() { : >"$calls" WIFI_ID="$wifi_id" PATH="$stub_bin:$PATH" TEST_LOG="$calls" \ - OMARCHY_INSTALL="$ROOT/install" \ OMARCHY_PATH="$ROOT" \ + OMARCHY_INSTALL="$ROOT/install" \ OMARCHY_BRCMFMAC_FWDIR="$fwdir" \ + OMARCHY_BRCMFMAC_PACKAGED_FWDIR="$packaged" \ OMARCHY_BRCMFMAC43602_NVRAM="$nvram" \ OMARCHY_BRCMFMAC_DMI_VENDOR="$test_tmp/dmi/sys_vendor" \ OMARCHY_BRCMFMAC_DMI_PRODUCT="$test_tmp/dmi/product_name" \ - OMARCHY_BRCMFMAC_NETDIR="$netdir" \ + OMARCHY_BRCMFMAC_PCI_DEVICES="$pci_devices" \ bash -euo pipefail "$migration" >/dev/null } -run_leaf "Apple Inc." "MacBookPro14,3" 43ba >/dev/null -[[ -f "$(generic)" ]] || fail "a BCM43602 Mac gets the generic NVRAM" +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +invoke_leaf 43ba >/dev/null +[[ -f "$(generic)" ]] || fail "a MacBookPro14,3 gets the generic NVRAM" [[ -f "$(dmi_file "Apple Inc." "MacBookPro14,3")" ]] || - fail "a BCM43602 Mac gets the DMI-specific NVRAM" + fail "a MacBookPro14,3 gets the DMI-specific NVRAM" grep -qx 'aa5g=7' "$(generic)" || fail "installed NVRAM enables 5 GHz" -pass "a MacBookPro14,3 with BCM43602 gets both NVRAM names" +grep -qx 'macaddr=aa:bb:cc:dd:ee:ff' "$(generic)" || + fail "the installed NVRAM carries the NIC's live MAC" "$(grep '^macaddr' "$(generic)")" +grep -qx 'macaddr=aa:bb:cc:dd:ee:ff' "$(dmi_file "Apple Inc." "MacBookPro14,3")" || + fail "the DMI-specific NVRAM gets the live MAC" +pass "a MacBookPro14,3 with BCM43602 gets both NVRAM names and the live MAC" -run_leaf "Apple Inc." "MacBookPro14,2" 43ba >/dev/null +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,2" >"$test_tmp/dmi/product_name" +invoke_leaf 43ba >/dev/null [[ -f "$(dmi_file "Apple Inc." "MacBookPro14,2")" ]] || fail "MacBookPro14,2 gets its DMI-specific NVRAM" pass "MacBookPro14,2 gets its DMI-specific NVRAM" -run_leaf "Apple Computer, Inc." "MacBookPro11,4" 43ba >/dev/null -[[ -f "$(dmi_file "Apple Computer, Inc." "MacBookPro11,4")" ]] || +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +printf '%s' "Apple Computer, Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +invoke_leaf 43ba >/dev/null +[[ -f "$(dmi_file "Apple Computer, Inc." "MacBookPro14,3")" ]] || fail "the older Apple vendor string is recognized" pass "the older Apple vendor string is recognized" -# Live interface address is written into macaddr= so we don't change the -# station ID the machine is already using. -rm -rf "$fwdir" "$netdir" -mkdir -p "$fwdir" "$netdir/wlp3s0/wireless" +# No MAC discoverable: drop the line rather than shipping the donor address. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" -printf '8c:85:90:a3:af:dc\n' >"$netdir/wlp3s0/address" invoke_leaf 43ba >/dev/null -grep -qx 'macaddr=8c:85:90:a3:af:dc' "$(generic)" || - fail "the live Wi-Fi MAC is written into the NVRAM" "$(head "$(generic)")" -grep -qx 'macaddr=8c:85:90:a3:af:dc' "$(dmi_file "Apple Inc." "MacBookPro14,3")" || - fail "the DMI-specific NVRAM gets the live MAC" -pass "the live Wi-Fi MAC is written into the NVRAM" - -# No wireless interface yet (early install): keep the dump's default MAC. -rm -rf "$netdir" -run_leaf "Apple Inc." "MacBookPro14,3" 43ba >/dev/null -grep -qx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || - fail "the firmware-default MAC is kept when no interface exists" -pass "the firmware-default MAC is kept when no interface exists" +[[ -f "$(generic)" ]] || fail "a Mac with no discoverable MAC still gets the NVRAM" +! grep -q '^macaddr=' "$(generic)" || + fail "the macaddr line is stripped when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" +pass "the macaddr line is stripped when no MAC is discoverable" run_leaf "Apple Inc." "MacBookPro14,3" 43a0 >/dev/null [[ ! -f "$(generic)" ]] || fail "a Mac whose Wi-Fi brcmfmac does not drive is left alone" @@ -170,6 +206,15 @@ run_leaf "Apple Inc." "MacBookPro14,3" 43bb >/dev/null [[ ! -f "$(generic)" ]] || fail "the 2 GHz-only BCM43602 variant is left alone" pass "the 2 GHz-only BCM43602 variant is left alone" +# Same PCI ID, different board; this dump has a report of unusable range there. +run_leaf "Apple Inc." "MacBookPro13,3" 43ba >/dev/null +[[ ! -f "$(generic)" ]] || fail "MacBookPro13,3 is outside the model gate" +pass "MacBookPro13,3 is outside the model gate" + +run_leaf "Apple Inc." "MacBookPro11,4" 43ba >/dev/null +[[ ! -f "$(generic)" ]] || fail "a 2015 BCM43602 Mac is outside the model gate" +pass "a 2015 BCM43602 Mac is outside the model gate" + run_leaf "LENOVO" "ThinkPad" 43ba >/dev/null [[ ! -f "$(generic)" ]] || fail "non-Apple hardware is left alone" pass "non-Apple hardware is left alone" @@ -178,32 +223,57 @@ run_leaf "Apple Inc." "MacBookPro14,3" >/dev/null [[ ! -f "$(generic)" ]] || fail "a Mac with no wireless device is left alone" pass "a Mac with no wireless device is left alone" -# Incomplete placeholder already on disk is replaced. -mkdir -p "$fwdir" -printf 'aa5g=1\ntxchain=1\nccode=ALL\n' >"$(generic)" -run_leaf "Apple Inc." "MacBookPro14,3" 43ba >/dev/null -grep -qx 'aa5g=7' "$(generic)" || fail "a placeholder NVRAM is replaced" -pass "a placeholder NVRAM is replaced" - -# Complete files are left alone, including a user-set MAC. -mkdir -p "$fwdir" +# An existing file wins, user-placed or package-shipped. invoke_leaf, not +# run_leaf: run_leaf wipes fwdir first, which made the previous clobber +# assertion vacuous. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +printf 'user-owned\n' >"$(generic)" +invoke_leaf 43ba >/dev/null +grep -qx 'user-owned' "$(generic)" || + fail "an existing NVRAM is never clobbered" "$(cat "$(generic)")" +pass "an existing NVRAM is never clobbered" + +# A packaged linux-firmware board file also outranks this copy. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" -cp "$nvram" "$(generic)" -cp "$nvram" "$(dmi_file "Apple Inc." "MacBookPro14,3")" -sed -i 's/^macaddr=.*/macaddr=de:ad:be:ef:00:01/' "$(generic)" -sed -i 's/^macaddr=.*/macaddr=de:ad:be:ef:00:01/' "$(dmi_file "Apple Inc." "MacBookPro14,3")" -: >"$calls" +printf 'package-shipped\n' >"$packaged/brcmfmac43602-pcie.txt" invoke_leaf 43ba >/dev/null -grep -qx 'macaddr=de:ad:be:ef:00:01' "$(generic)" || - fail "a complete NVRAM is not overwritten" -[[ ! -s $calls ]] || fail "a complete install does not escalate" "$(cat "$calls")" -pass "a complete NVRAM is not overwritten" +[[ ! -e "$(generic)" ]] || fail "a packaged NVRAM prevents writing the override" +pass "a packaged NVRAM prevents writing the override" + +# A failed install must not look like success. Stub install(1) to fail after +# the gate has matched, so the leaf's set -e surfaces the error. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +cat >"$stub_bin/install" <<'SH' +#!/bin/bash +exit 1 +SH +chmod +x "$stub_bin/install" +if invoke_leaf 43ba >/dev/null 2>&1; then + fail "a failed install does not look like success" +fi +rm -f "$stub_bin/install" +[[ ! -e "$(generic)" ]] || fail "a failed install leaves no dest file" +pass "a failed install does not look like success" -rm -rf "$fwdir" "$netdir" -mkdir -p "$fwdir" +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac run_migration "Apple Inc." "MacBookPro14,3" 43ba [[ -f "$(generic)" ]] || fail "the migration installs NVRAM on an existing Mac" +grep -qx 'macaddr=aa:bb:cc:dd:ee:ff' "$(generic)" || + fail "the migration substitutes the live MAC" "$(grep '^macaddr' "$(generic)")" grep -Fq $'omarchy-state\tset\treboot-required' "$calls" || fail "the migration asks for the reboot that applies it" "$(cat "$calls")" pass "the migration installs NVRAM and asks for a reboot" @@ -212,15 +282,50 @@ run_migration "Apple Inc." "MacBookPro14,3" 43ba [[ ! -s $calls ]] || fail "the migration is idempotent" "$(cat "$calls")" pass "the migration is idempotent" +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +run_migration "Apple Inc." "MacBookPro14,3" 43ba +[[ -f "$(generic)" ]] || fail "the migration installs NVRAM without a discoverable MAC" +! grep -q '^macaddr=' "$(generic)" || + fail "the migration strips macaddr when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" +grep -Fq $'omarchy-state\tset\treboot-required' "$calls" || + fail "the migration still asks for a reboot without a MAC" "$(cat "$calls")" +pass "the migration strips macaddr when no MAC is discoverable" + +rm -rf "$fwdir" "$packaged" +mkdir -p "$fwdir" "$packaged" +printf 'already-there\n' >"$(generic)" +run_migration "Apple Inc." "MacBookPro14,3" 43ba +grep -qx 'already-there' "$(generic)" || + fail "the migration never overwrites an existing NVRAM" "$(cat "$(generic)")" +[[ ! -s $calls ]] || fail "the migration escalates nothing when the file already exists" "$(cat "$calls")" +pass "the migration never overwrites an existing NVRAM" + +rm -rf "$fwdir" "$packaged" +run_migration "Apple Inc." "MacBookPro13,3" 43ba +[[ ! -e "$(generic)" ]] || fail "the migration skips MacBookPro13,3" +[[ ! -s $calls ]] || fail "the migration escalates nothing on excluded models" "$(cat "$calls")" +pass "the migration skips MacBookPro13,3" + rm -rf "$fwdir" run_migration "LENOVO" "ThinkPad" 43ba [[ ! -e "$(generic)" ]] || fail "the migration skips non-Apple hardware" [[ ! -s $calls ]] || fail "the migration escalates nothing on unaffected machines" "$(cat "$calls")" pass "the migration skips non-Apple hardware" -# Helper is sourced by install leaves; leaking nullglob would change later globbing. -# shellcheck source=../../install/hardware/apple/brcmfmac-43602.sh -OMARCHY_INSTALL="$ROOT/install" source "$helper" -OMARCHY_BRCMFMAC_NETDIR="$test_tmp/empty-net" brcmfmac43602_wifi_mac || true -shopt -q nullglob && fail "applying NVRAM does not leave nullglob on" -pass "applying NVRAM does not leave nullglob on" +# A failed migration install must not mark the migration done via reboot-required. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +cat >"$stub_bin/install" <<'SH' +#!/bin/bash +exit 1 +SH +chmod +x "$stub_bin/install" +if run_migration "Apple Inc." "MacBookPro14,3" 43ba; then + fail "a failed migration install does not look like success" +fi +rm -f "$stub_bin/install" +grep -q 'omarchy-state' "$calls" && + fail "a failed migration does not ask for a reboot" "$(cat "$calls")" +pass "a failed migration install does not look like success" diff --git a/test/shell.d/unowned-system-paths-test.sh b/test/shell.d/unowned-system-paths-test.sh index 29ca3f66b1e..692afe4aed7 100755 --- a/test/shell.d/unowned-system-paths-test.sh +++ b/test/shell.d/unowned-system-paths-test.sh @@ -31,9 +31,9 @@ allowed = { # them so the hook does not exist where it does not apply. "/usr/lib/systemd/system-sleep", # Hardware-conditional BCM43602 board NVRAM. linux-firmware-broadcom ships the - # chip firmware but not this calibration file; /usr/lib/firmware/updates is - # the kernel override path so a later packaged txt cannot conflict. - "/usr/lib/firmware/updates", + # chip firmware but not this calibration file; /usr/lib/firmware/updates/brcm + # is the kernel override path so a later packaged txt cannot conflict. + "/usr/lib/firmware/updates/brcm", # Written through a variable, so the scan below cannot see them at the point # they are written. Both drop configuration into another project's tree rather # than Omarchy's, which is why neither is a candidate for omarchy-settings. From f81f4d524de2b890a4a1f93c3b55a2c6ab9fdb67 Mon Sep 17 00:00:00 2001 From: Omabot Date: Sat, 22 Aug 2026 05:30:09 -0700 Subject: [PATCH 3/6] See a packaged board file that arrives compressed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arch ships everything under /usr/lib/firmware zstd-compressed — all 118 files in /usr/lib/firmware/brcm on a current install, including NVRAM board files for other chips such as `brcmfmac43241b4-sdio.Intel Corp.-VALLEYVIEW C0 PLATFORM.txt.zst`. The existence check looked only for the uncompressed name, so the rule that a packaged linux-firmware board file outranks this copy could never fire on Arch: the day linux-firmware ships one for the BCM43602, the check would miss it and write the override on top, shadowing it for good. Co-Authored-By: Claude Opus 5 (1M context) Co-Authored-By: Codex XHigh --- install/hardware/apple/brcmfmac-43602.sh | 13 +++++++++---- test/shell.d/brcmfmac-5ghz-test.sh | 12 ++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/install/hardware/apple/brcmfmac-43602.sh b/install/hardware/apple/brcmfmac-43602.sh index 89f2fd1dfdf..5ba9cdb5eae 100644 --- a/install/hardware/apple/brcmfmac-43602.sh +++ b/install/hardware/apple/brcmfmac-43602.sh @@ -70,12 +70,17 @@ brcmfmac43602_dmi_name() { printf '%s\n' "brcmfmac43602-pcie.${vendor}-${product}.txt" } +# Arch compresses everything under /usr/lib/firmware, so a packaged board file +# arrives as brcmfmac43602-pcie..txt.zst, not under the name the driver +# asks for. Checking only the plain name would miss it and shadow it anyway. brcmfmac43602_installed() { - local name dir + local name dir suffix + name=$(brcmfmac43602_dmi_name) for dir in "$(brcmfmac43602_fwdir)" "$(brcmfmac43602_packaged_fwdir)"; do - [[ -e $dir/brcmfmac43602-pcie.txt ]] && return 0 - name=$(brcmfmac43602_dmi_name) - [[ -n $name && -e "$dir/$name" ]] && return 0 + for suffix in "" .zst .xz; do + [[ -e $dir/brcmfmac43602-pcie.txt$suffix ]] && return 0 + [[ -n $name && -e "$dir/$name$suffix" ]] && return 0 + done done return 1 } diff --git a/test/shell.d/brcmfmac-5ghz-test.sh b/test/shell.d/brcmfmac-5ghz-test.sh index 1846f4d8cc0..99efc91573d 100755 --- a/test/shell.d/brcmfmac-5ghz-test.sh +++ b/test/shell.d/brcmfmac-5ghz-test.sh @@ -248,6 +248,18 @@ invoke_leaf 43ba >/dev/null [[ ! -e "$(generic)" ]] || fail "a packaged NVRAM prevents writing the override" pass "a packaged NVRAM prevents writing the override" +# Arch ships every firmware file zstd-compressed, so this is the name a real +# linux-firmware board file would arrive under. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +printf 'package-shipped\n' >"$packaged/brcmfmac43602-pcie.Apple Inc.-MacBookPro14,3.txt.zst" +invoke_leaf 43ba >/dev/null +[[ ! -e "$(generic)" ]] || fail "a compressed packaged NVRAM prevents writing the override" +pass "a compressed packaged NVRAM prevents writing the override" + # A failed install must not look like success. Stub install(1) to fail after # the gate has matched, so the leaf's set -e surfaces the error. rm -rf "$fwdir" "$packaged" "$pci_devices" From 21b1f9222a955520f727c196cbb82ce019d945fd Mon Sep 17 00:00:00 2001 From: Shawn Yeager Date: Thu, 3 Sep 2026 09:16:30 -0500 Subject: [PATCH 4/6] Stage BCM43602 NVRAM atomically and persist only a real permanent MAC A write that failed part-way could leave a truncated or half-written NVRAM pair under the name the driver loads, where the skip guard then kept it forever and the migration never asked for the reboot. Stage both names under temporary names and rename them into place together, rolling back on failure. Read the MAC from the wiphy's permanent address rather than the netdev's current one, which NetworkManager randomises while scanning, and treat the Broadcom 00:90:4c placeholder as no address so it is never persisted. Give the leaf and the migration the positive if that the other hardware leaves use, and log the install after it succeeds. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QH9zheAn7LKZeTzgwpHqBF --- install/hardware/apple/brcmfmac-43602.sh | 54 +++++++++----- install/hardware/apple/fix-brcmfmac-5ghz.sh | 11 +-- migrations/1787312531.sh | 17 ++--- test/shell.d/brcmfmac-5ghz-test.sh | 79 ++++++++++++++++++++- 4 files changed, 120 insertions(+), 41 deletions(-) diff --git a/install/hardware/apple/brcmfmac-43602.sh b/install/hardware/apple/brcmfmac-43602.sh index 5ba9cdb5eae..c50108ad439 100644 --- a/install/hardware/apple/brcmfmac-43602.sh +++ b/install/hardware/apple/brcmfmac-43602.sh @@ -15,8 +15,9 @@ # # Destinations live under /usr/lib/firmware/updates so they override, and do # not collide with, linux-firmware-broadcom. A file already at either the -# override path or the packaged path wins: user-placed or a future -# linux-firmware board file both outrank this copy. +# override path or the packaged path when this runs wins, user-placed or +# package-shipped. Once this copy is installed the override path is searched +# first, so a board file linux-firmware ships later does not replace it. brcmfmac43602_as_root() { if (( EUID == 0 )); then @@ -86,36 +87,48 @@ brcmfmac43602_installed() { } # The NIC bound to 14e4:43ba, not the first wireless interface in glob order -# (a USB adapter at install time would otherwise donate its address). +# (a USB adapter at install time would otherwise donate its address). The +# wiphy's macaddress is the permanent address; net/*/address is whatever is +# current, which NetworkManager randomises while scanning. 00:90:4c is the +# Broadcom OUI the firmware falls back to when the card has no usable OTP +# address, so it is a placeholder and never worth persisting. brcmfmac43602_wifi_mac() { - local bdf pci_devices net_addrs mac + local bdf pci_devices candidate mac pci_devices=$(brcmfmac43602_pci_devices) bdf=$(lspci -Dnn | awk '/14e4:43ba/ { if (!found) { print $1; found=1 } }') [[ -n $bdf ]] || return 1 - net_addrs=("$pci_devices/$bdf"/net/*/address) - mac=$(cat "${net_addrs[0]}" 2>/dev/null || true) - if [[ $mac =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]]; then + for candidate in "$pci_devices/$bdf"/ieee80211/phy*/macaddress "$pci_devices/$bdf"/net/*/address; do + mac=$(cat "$candidate" 2>/dev/null || true) + [[ $mac =~ ^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$ ]] || continue + if [[ ${mac,,} == 00:90:4c:* ]]; then + return 1 + fi printf '%s\n' "$mac" return 0 - fi + done return 1 } # Reloading brcmfmac here would drop a live Wi-Fi connection, including the # one carrying an update. Callers must not wrap this in `if`; bash would then # disable errexit for the body and a failed install would look like success. +# +# Both names are staged under temporary names and renamed into place together. +# A write that fails part-way (ENOSPC truncates its destination) must not leave +# a partial file under the name the driver loads, where brcmfmac43602_installed +# would keep it forever and the migration would never ask for the reboot. brcmfmac43602_install() { - local src fwdir generic dmi mac work name + local src fwdir mac work name target + local -a targets src=$(brcmfmac43602_nvram_src) [[ -f $src ]] || return 1 fwdir=$(brcmfmac43602_fwdir) - generic="$fwdir/brcmfmac43602-pcie.txt" - dmi="" + targets=("$fwdir/brcmfmac43602-pcie.txt") name=$(brcmfmac43602_dmi_name) if [[ -n $name ]]; then - dmi="$fwdir/$name" + targets+=("$fwdir/$name") fi work=$(mktemp) || return 1 @@ -137,15 +150,18 @@ brcmfmac43602_install() { rm -f "$work" return 1 fi - if ! brcmfmac43602_as_root install -m 644 "$work" "$generic"; then - rm -f "$work" - return 1 - fi - if [[ -n $dmi ]]; then - if ! brcmfmac43602_as_root install -m 644 "$work" "$dmi"; then + for target in "${targets[@]}"; do + if ! brcmfmac43602_as_root install -m 644 "$work" "$target.tmp"; then + brcmfmac43602_as_root rm -f "${targets[@]/%/.tmp}" rm -f "$work" return 1 fi - fi + done rm -f "$work" + for target in "${targets[@]}"; do + if ! brcmfmac43602_as_root mv -f "$target.tmp" "$target"; then + brcmfmac43602_as_root rm -f "${targets[@]}" "${targets[@]/%/.tmp}" + return 1 + fi + done } diff --git a/install/hardware/apple/fix-brcmfmac-5ghz.sh b/install/hardware/apple/fix-brcmfmac-5ghz.sh index 45cdf98d0c6..e98f274e823 100644 --- a/install/hardware/apple/fix-brcmfmac-5ghz.sh +++ b/install/hardware/apple/fix-brcmfmac-5ghz.sh @@ -11,12 +11,7 @@ # shellcheck source=brcmfmac-43602.sh source "$OMARCHY_INSTALL/hardware/apple/brcmfmac-43602.sh" -if ! brcmfmac43602_needed; then - return 0 +if brcmfmac43602_needed && ! brcmfmac43602_installed; then + brcmfmac43602_install + echo "Installed BCM43602 5 GHz NVRAM" fi -if brcmfmac43602_installed; then - return 0 -fi - -echo "Installed BCM43602 5 GHz NVRAM" -brcmfmac43602_install diff --git a/migrations/1787312531.sh b/migrations/1787312531.sh index 5c9fad174d6..abe342917dd 100644 --- a/migrations/1787312531.sh +++ b/migrations/1787312531.sh @@ -8,15 +8,10 @@ echo "Unlock 5 GHz Wi-Fi on 2017 Touch Bar MacBook Pros" # shellcheck source=../install/hardware/apple/brcmfmac-43602.sh source "$OMARCHY_INSTALL/hardware/apple/brcmfmac-43602.sh" -if ! brcmfmac43602_needed; then - exit 0 -fi -if brcmfmac43602_installed; then - exit 0 -fi +if brcmfmac43602_needed && ! brcmfmac43602_installed; then + brcmfmac43602_install -brcmfmac43602_install - -# The driver only rereads NVRAM when brcmfmac next loads. Reloading it here -# would drop a Wi-Fi connection that works on the network carrying this update. -omarchy-state set reboot-required + # The driver only rereads NVRAM when brcmfmac next loads. Reloading it here + # would drop a Wi-Fi connection that works on the network carrying this update. + omarchy-state set reboot-required +fi diff --git a/test/shell.d/brcmfmac-5ghz-test.sh b/test/shell.d/brcmfmac-5ghz-test.sh index 99efc91573d..2b4e331b41d 100755 --- a/test/shell.d/brcmfmac-5ghz-test.sh +++ b/test/shell.d/brcmfmac-5ghz-test.sh @@ -106,6 +106,13 @@ provide_mac() { printf 'aa:bb:cc:dd:ee:ff\n' >"$pci_devices/0000:03:00.0/net/wlp3s0/address" } +# The wiphy's permanent address, as opposed to the netdev's current one. +provide_perm_mac() { + local mac=$1 + mkdir -p "$pci_devices/0000:03:00.0/ieee80211/phy0" + printf '%s\n' "$mac" >"$pci_devices/0000:03:00.0/ieee80211/phy0/macaddress" +} + # Production run_logged uses bash -eE with no pipefail. invoke_leaf() { local wifi_id="${1:-}" @@ -183,6 +190,36 @@ invoke_leaf 43ba >/dev/null fail "the older Apple vendor string is recognized" pass "the older Apple vendor string is recognized" +# NetworkManager randomises the netdev address while scanning; the wiphy's +# permanent address is the one to persist. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_perm_mac 11:22:33:44:55:66 +mkdir -p "$pci_devices/0000:03:00.0/net/wlp3s0" +printf 'f2:11:22:33:44:55\n' >"$pci_devices/0000:03:00.0/net/wlp3s0/address" +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +invoke_leaf 43ba >/dev/null +grep -qx 'macaddr=11:22:33:44:55:66' "$(generic)" || + fail "the permanent address wins over a randomised netdev address" "$(grep '^macaddr' "$(generic)")" +pass "the permanent address wins over a randomised netdev address" + +# A card with no usable OTP boots on Broadcom's 00:90:4c placeholder and +# reports it as its own address; persisting that would share it across +# machines. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_perm_mac 00:90:4c:0d:f4:3e +mkdir -p "$pci_devices/0000:03:00.0/net/wlp3s0" +printf '00:90:4C:0D:F4:3E\n' >"$pci_devices/0000:03:00.0/net/wlp3s0/address" +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +invoke_leaf 43ba >/dev/null +[[ -f "$(generic)" ]] || fail "a card on the placeholder address still gets the NVRAM" +! grep -q '^macaddr=' "$(generic)" || + fail "the Broadcom placeholder address is never persisted" "$(grep '^macaddr' "$(generic)")" +pass "the Broadcom placeholder address is never persisted" + # No MAC discoverable: drop the line rather than shipping the donor address. rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" @@ -260,8 +297,10 @@ invoke_leaf 43ba >/dev/null [[ ! -e "$(generic)" ]] || fail "a compressed packaged NVRAM prevents writing the override" pass "a compressed packaged NVRAM prevents writing the override" -# A failed install must not look like success. Stub install(1) to fail after -# the gate has matched, so the leaf's set -e surfaces the error. +# A failed install must not look like success. Stub install(1) to truncate its +# destination and then fail, the shape ENOSPC takes, after the gate has +# matched, so the leaf's set -e surfaces the error and nothing partial is left +# under a name the driver loads or the skip guard honours. rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" provide_mac @@ -269,6 +308,7 @@ printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" cat >"$stub_bin/install" <<'SH' #!/bin/bash +: >"${@: -1}" exit 1 SH chmod +x "$stub_bin/install" @@ -277,8 +317,32 @@ if invoke_leaf 43ba >/dev/null 2>&1; then fi rm -f "$stub_bin/install" [[ ! -e "$(generic)" ]] || fail "a failed install leaves no dest file" +[[ -z $(ls -A "$fwdir") ]] || fail "a failed install leaves nothing behind" "$(ls -A "$fwdir")" pass "a failed install does not look like success" +# The generic and DMI-specific names land together or not at all: one file on +# disk would satisfy the skip guard while the migration's reboot prompt is lost. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_mac +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +cat >"$stub_bin/install" </dev/null 2>&1; then + fail "a half-written NVRAM pair does not look like success" +fi +rm -f "$stub_bin/install" "$test_tmp/install-once" +[[ -z $(ls -A "$fwdir") ]] || fail "a half-written NVRAM pair is rolled back" "$(ls -A "$fwdir")" +pass "a half-written NVRAM pair is rolled back" + rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" provide_mac @@ -325,12 +389,14 @@ run_migration "LENOVO" "ThinkPad" 43ba [[ ! -s $calls ]] || fail "the migration escalates nothing on unaffected machines" "$(cat "$calls")" pass "the migration skips non-Apple hardware" -# A failed migration install must not mark the migration done via reboot-required. +# A failed migration install must not mark the migration done via reboot-required, +# and the rerun omarchy-migrate then makes must do the work it skipped. rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" provide_mac cat >"$stub_bin/install" <<'SH' #!/bin/bash +: >"${@: -1}" exit 1 SH chmod +x "$stub_bin/install" @@ -341,3 +407,10 @@ rm -f "$stub_bin/install" grep -q 'omarchy-state' "$calls" && fail "a failed migration does not ask for a reboot" "$(cat "$calls")" pass "a failed migration install does not look like success" + +run_migration "Apple Inc." "MacBookPro14,3" 43ba +grep -qx 'aa5g=7' "$(generic)" || + fail "the rerun after a failed migration installs the NVRAM" "$(ls -A "$fwdir")" +grep -Fq $'omarchy-state\tset\treboot-required' "$calls" || + fail "the rerun after a failed migration asks for the reboot" "$(cat "$calls")" +pass "the rerun after a failed migration installs and asks for the reboot" From ffda5d49e045dc5bb87641494eb015d015190e42 Mon Sep 17 00:00:00 2001 From: Shawn Yeager Date: Sat, 5 Sep 2026 15:39:35 -0500 Subject: [PATCH 5/6] Keep macaddr= in BCM43602 NVRAM; stripping it crashes the dongle On MacBookPro14,3, an NVRAM file with no macaddr= key makes brcmfmac time out on cur_etheraddr and fail dongle setup. OTP does not answer. Keep the source line when the 43ba wiphy has no real permanent address; still substitute a live address when it does. --- default/firmware/apple/brcmfmac43602-pcie.txt | 8 +++--- install/hardware/apple/brcmfmac-43602.sh | 21 ++++++++------- test/shell.d/brcmfmac-5ghz-test.sh | 26 +++++++++---------- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/default/firmware/apple/brcmfmac43602-pcie.txt b/default/firmware/apple/brcmfmac43602-pcie.txt index 271d33403f1..7279cd89007 100644 --- a/default/firmware/apple/brcmfmac43602-pcie.txt +++ b/default/firmware/apple/brcmfmac43602-pcie.txt @@ -8,9 +8,11 @@ # The values below are otherwise this dump's calibration data; do not edit them. # # Lines starting with # are comments; the brcmfmac NVRAM parser ignores them. -# macaddr= is a placeholder (00:90:4c is the Broadcom OUI, not a device MAC). -# The install leaf and migration substitute the NIC's live address or strip -# the line so this value is never used verbatim. +# macaddr= is a firmware key. The 00:90:4c value is Broadcom's placeholder, +# not a board identity. The install leaf and migration substitute a real +# permanent address from the 43ba wiphy when one exists. They keep this line +# when it does not: dropping it makes the dongle time out on cur_etheraddr +# and crash. sromrev=11 subvid=0x14e4 diff --git a/install/hardware/apple/brcmfmac-43602.sh b/install/hardware/apple/brcmfmac-43602.sh index c50108ad439..e3d4e65ebb7 100644 --- a/install/hardware/apple/brcmfmac-43602.sh +++ b/install/hardware/apple/brcmfmac-43602.sh @@ -90,8 +90,9 @@ brcmfmac43602_installed() { # (a USB adapter at install time would otherwise donate its address). The # wiphy's macaddress is the permanent address; net/*/address is whatever is # current, which NetworkManager randomises while scanning. 00:90:4c is the -# Broadcom OUI the firmware falls back to when the card has no usable OTP -# address, so it is a placeholder and never worth persisting. +# Broadcom OUI, not a board identity, so it is not substituted in. The +# macaddr= key still has to exist in the file: without it, BCM43602 firmware +# times out on cur_etheraddr and crashes the dongle (MacBookPro14,3). brcmfmac43602_wifi_mac() { local bdf pci_devices candidate mac pci_devices=$(brcmfmac43602_pci_devices) @@ -132,15 +133,15 @@ brcmfmac43602_install() { fi work=$(mktemp) || return 1 + # Copy first so macaddr= always survives. Then substitute a real permanent + # address when wifi_mac found one. Do not strip the key: that crash is + # documented in the wifi_mac comment. + if ! cat "$src" >"$work"; then + rm -f "$work" + return 1 + fi if mac=$(brcmfmac43602_wifi_mac); then - if ! sed "s/^macaddr=.*/macaddr=$mac/" "$src" >"$work"; then - rm -f "$work" - return 1 - fi - else - # No MAC discoverable: drop the line and let the firmware use the OTP - # address, which is how these NICs already run with no NVRAM at all. - if ! sed '/^macaddr=/d' "$src" >"$work"; then + if ! sed -i "s/^macaddr=.*/macaddr=$mac/" "$work"; then rm -f "$work" return 1 fi diff --git a/test/shell.d/brcmfmac-5ghz-test.sh b/test/shell.d/brcmfmac-5ghz-test.sh index 2b4e331b41d..0643e6e6859 100755 --- a/test/shell.d/brcmfmac-5ghz-test.sh +++ b/test/shell.d/brcmfmac-5ghz-test.sh @@ -204,9 +204,9 @@ grep -qx 'macaddr=11:22:33:44:55:66' "$(generic)" || fail "the permanent address wins over a randomised netdev address" "$(grep '^macaddr' "$(generic)")" pass "the permanent address wins over a randomised netdev address" -# A card with no usable OTP boots on Broadcom's 00:90:4c placeholder and -# reports it as its own address; persisting that would share it across -# machines. +# A card whose wiphy only shows Broadcom's 00:90:4c placeholder: do not treat +# that as a unique address, but keep the source macaddr= key. Firmware crashes +# without it. rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" provide_perm_mac 00:90:4c:0d:f4:3e @@ -216,20 +216,20 @@ printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" invoke_leaf 43ba >/dev/null [[ -f "$(generic)" ]] || fail "a card on the placeholder address still gets the NVRAM" -! grep -q '^macaddr=' "$(generic)" || - fail "the Broadcom placeholder address is never persisted" "$(grep '^macaddr' "$(generic)")" -pass "the Broadcom placeholder address is never persisted" +grep -qx "$(grep '^macaddr=' "$nvram")" "$(generic)" || + fail "placeholder wiphy keeps the source macaddr= key" "$(grep '^macaddr' "$(generic)")" +pass "placeholder wiphy keeps the source macaddr= key" -# No MAC discoverable: drop the line rather than shipping the donor address. +# No MAC discoverable: keep the source macaddr= key. rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" invoke_leaf 43ba >/dev/null [[ -f "$(generic)" ]] || fail "a Mac with no discoverable MAC still gets the NVRAM" -! grep -q '^macaddr=' "$(generic)" || - fail "the macaddr line is stripped when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" -pass "the macaddr line is stripped when no MAC is discoverable" +grep -qx "$(grep '^macaddr=' "$nvram")" "$(generic)" || + fail "macaddr= is kept from the source when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" +pass "macaddr= is kept from the source when no MAC is discoverable" run_leaf "Apple Inc." "MacBookPro14,3" 43a0 >/dev/null [[ ! -f "$(generic)" ]] || fail "a Mac whose Wi-Fi brcmfmac does not drive is left alone" @@ -362,11 +362,11 @@ rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" run_migration "Apple Inc." "MacBookPro14,3" 43ba [[ -f "$(generic)" ]] || fail "the migration installs NVRAM without a discoverable MAC" -! grep -q '^macaddr=' "$(generic)" || - fail "the migration strips macaddr when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" +grep -qx "$(grep '^macaddr=' "$nvram")" "$(generic)" || + fail "the migration keeps source macaddr= when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" grep -Fq $'omarchy-state\tset\treboot-required' "$calls" || fail "the migration still asks for a reboot without a MAC" "$(cat "$calls")" -pass "the migration strips macaddr when no MAC is discoverable" +pass "the migration keeps source macaddr= when no MAC is discoverable" rm -rf "$fwdir" "$packaged" mkdir -p "$fwdir" "$packaged" From c0fea4e972d0b563883e78ca9ccc368616b89f0b Mon Sep 17 00:00:00 2001 From: Shawn Yeager Date: Sun, 6 Sep 2026 09:15:04 -0500 Subject: [PATCH 6/6] Persist a per-machine MAC instead of the BCM43602 donor address Keep the macaddr= key (stripping it crashes the dongle). Never write the dump's shared 00:90:4c:0d:f4:3e. When the 43ba wiphy has no real permanent address, substitute a locally-administered MAC from machine-id so install with no interface up still works. Tests fail if the donor is written. --- default/firmware/apple/brcmfmac43602-pcie.txt | 9 +- install/hardware/apple/brcmfmac-43602.sh | 40 ++++- test/shell.d/brcmfmac-5ghz-test.sh | 137 +++++++++++++++--- 3 files changed, 158 insertions(+), 28 deletions(-) diff --git a/default/firmware/apple/brcmfmac43602-pcie.txt b/default/firmware/apple/brcmfmac43602-pcie.txt index 7279cd89007..48a7743c0ce 100644 --- a/default/firmware/apple/brcmfmac43602-pcie.txt +++ b/default/firmware/apple/brcmfmac43602-pcie.txt @@ -9,10 +9,11 @@ # # Lines starting with # are comments; the brcmfmac NVRAM parser ignores them. # macaddr= is a firmware key. The 00:90:4c value is Broadcom's placeholder, -# not a board identity. The install leaf and migration substitute a real -# permanent address from the 43ba wiphy when one exists. They keep this line -# when it does not: dropping it makes the dongle time out on cur_etheraddr -# and crash. +# not a board identity. The install leaf and migration substitute the 43ba +# wiphy's permanent address when it is not a Broadcom placeholder, otherwise +# a machine-id-derived locally-administered address. The key is never dropped +# (dropping it makes the dongle time out on cur_etheraddr and crash). The +# donor value is never persisted. sromrev=11 subvid=0x14e4 diff --git a/install/hardware/apple/brcmfmac-43602.sh b/install/hardware/apple/brcmfmac-43602.sh index e3d4e65ebb7..4e731afe360 100644 --- a/install/hardware/apple/brcmfmac-43602.sh +++ b/install/hardware/apple/brcmfmac-43602.sh @@ -90,9 +90,10 @@ brcmfmac43602_installed() { # (a USB adapter at install time would otherwise donate its address). The # wiphy's macaddress is the permanent address; net/*/address is whatever is # current, which NetworkManager randomises while scanning. 00:90:4c is the -# Broadcom OUI, not a board identity, so it is not substituted in. The -# macaddr= key still has to exist in the file: without it, BCM43602 firmware -# times out on cur_etheraddr and crashes the dongle (MacBookPro14,3). +# Broadcom OUI, not a board identity, so it is not substituted in — install +# then writes a per-machine locally-administered address instead of the dump +# donor. The macaddr= key still has to exist in the file: without it, BCM43602 +# firmware times out on cur_etheraddr and crashes the dongle (MacBookPro14,3). brcmfmac43602_wifi_mac() { local bdf pci_devices candidate mac pci_devices=$(brcmfmac43602_pci_devices) @@ -110,6 +111,24 @@ brcmfmac43602_wifi_mac() { return 1 } +brcmfmac43602_machine_id() { + cat "${OMARCHY_BRCMFMAC_MACHINE_ID:-/etc/machine-id}" 2>/dev/null || true +} + +# Locally-administered unicast address unique to this machine. Used when the +# 43ba wiphy has no real permanent address so macaddr= stays present without +# persisting the dump's shared 00:90:4c:0d:f4:3e. Salt is this helper's, not +# #10141's mbp133-wifi — do not copy that PR's 13,3-only restriction. +brcmfmac43602_stable_mac() { + local id seed + id=$(brcmfmac43602_machine_id) + [[ -n $id ]] || return 1 + seed=$(printf '%s' "$id:bcm43602-wifi" | sha256sum | cut -c1-10) + [[ ${#seed} == 10 ]] || return 1 + printf '02:%s:%s:%s:%s:%s\n' \ + "${seed:0:2}" "${seed:2:2}" "${seed:4:2}" "${seed:6:2}" "${seed:8:2}" +} + # Reloading brcmfmac here would drop a live Wi-Fi connection, including the # one carrying an update. Callers must not wrap this in `if`; bash would then # disable errexit for the body and a failed install would look like success. @@ -133,9 +152,10 @@ brcmfmac43602_install() { fi work=$(mktemp) || return 1 - # Copy first so macaddr= always survives. Then substitute a real permanent - # address when wifi_mac found one. Do not strip the key: that crash is - # documented in the wifi_mac comment. + # Copy first so macaddr= always survives. Substitute the 43ba wiphy's + # permanent address when it is not a Broadcom placeholder; otherwise a + # machine-id-derived locally-administered address. Never drop the key + # (dongle crash) and never persist the dump's 00:90:4c:0d:f4:3e. if ! cat "$src" >"$work"; then rm -f "$work" return 1 @@ -145,6 +165,14 @@ brcmfmac43602_install() { rm -f "$work" return 1 fi + elif mac=$(brcmfmac43602_stable_mac); then + if ! sed -i "s/^macaddr=.*/macaddr=$mac/" "$work"; then + rm -f "$work" + return 1 + fi + else + rm -f "$work" + return 1 fi if ! brcmfmac43602_as_root mkdir -p "$fwdir"; then diff --git a/test/shell.d/brcmfmac-5ghz-test.sh b/test/shell.d/brcmfmac-5ghz-test.sh index 0643e6e6859..7a1c5c2dc89 100755 --- a/test/shell.d/brcmfmac-5ghz-test.sh +++ b/test/shell.d/brcmfmac-5ghz-test.sh @@ -40,7 +40,9 @@ calls="$test_tmp/calls.log" fwdir="$test_tmp/firmware/updates/brcm" packaged="$test_tmp/firmware/brcm" pci_devices="$test_tmp/sys-pci" +machine_id_file="$test_tmp/machine-id" mkdir -p "$stub_bin" "$test_tmp/dmi" +printf '%s\n' '0123456789abcdef0123456789abcdef' >"$machine_id_file" cat >"$stub_bin/lspci" <<'SH' #!/bin/bash @@ -113,6 +115,14 @@ provide_perm_mac() { printf '%s\n' "$mac" >"$pci_devices/0000:03:00.0/ieee80211/phy0/macaddress" } +# Same derivation as brcmfmac43602_stable_mac, from this test's machine-id file. +expected_stable_macaddr() { + local seed + seed=$(printf '%s' "$(cat "$machine_id_file" 2>/dev/null || true):bcm43602-wifi" | sha256sum | cut -c1-10) + printf 'macaddr=02:%s:%s:%s:%s:%s\n' \ + "${seed:0:2}" "${seed:2:2}" "${seed:4:2}" "${seed:6:2}" "${seed:8:2}" +} + # Production run_logged uses bash -eE with no pipefail. invoke_leaf() { local wifi_id="${1:-}" @@ -124,6 +134,7 @@ invoke_leaf() { OMARCHY_BRCMFMAC_DMI_VENDOR="$test_tmp/dmi/sys_vendor" \ OMARCHY_BRCMFMAC_DMI_PRODUCT="$test_tmp/dmi/product_name" \ OMARCHY_BRCMFMAC_PCI_DEVICES="$pci_devices" \ + OMARCHY_BRCMFMAC_MACHINE_ID="$machine_id_file" \ bash -eE -c 'source "$1"' bash "$leaf" /dev/null } @@ -164,9 +176,11 @@ invoke_leaf 43ba >/dev/null [[ -f "$(dmi_file "Apple Inc." "MacBookPro14,3")" ]] || fail "a MacBookPro14,3 gets the DMI-specific NVRAM" grep -qx 'aa5g=7' "$(generic)" || fail "installed NVRAM enables 5 GHz" -grep -qx 'macaddr=aa:bb:cc:dd:ee:ff' "$(generic)" || +expected=macaddr=aa:bb:cc:dd:ee:ff +[[ -n $expected ]] || fail "expected macaddr is non-empty" +grep -Fqx "$expected" "$(generic)" || fail "the installed NVRAM carries the NIC's live MAC" "$(grep '^macaddr' "$(generic)")" -grep -qx 'macaddr=aa:bb:cc:dd:ee:ff' "$(dmi_file "Apple Inc." "MacBookPro14,3")" || +grep -Fqx "$expected" "$(dmi_file "Apple Inc." "MacBookPro14,3")" || fail "the DMI-specific NVRAM gets the live MAC" pass "a MacBookPro14,3 with BCM43602 gets both NVRAM names and the live MAC" @@ -200,13 +214,14 @@ printf 'f2:11:22:33:44:55\n' >"$pci_devices/0000:03:00.0/net/wlp3s0/address" printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" invoke_leaf 43ba >/dev/null -grep -qx 'macaddr=11:22:33:44:55:66' "$(generic)" || +expected=macaddr=11:22:33:44:55:66 +[[ -n $expected ]] || fail "expected macaddr is non-empty" +grep -Fqx "$expected" "$(generic)" || fail "the permanent address wins over a randomised netdev address" "$(grep '^macaddr' "$(generic)")" pass "the permanent address wins over a randomised netdev address" -# A card whose wiphy only shows Broadcom's 00:90:4c placeholder: do not treat -# that as a unique address, but keep the source macaddr= key. Firmware crashes -# without it. +# A card whose wiphy only shows Broadcom's 00:90:4c placeholder: keep macaddr= +# but do not persist the dump donor or the wiphy's placeholder. rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" provide_perm_mac 00:90:4c:0d:f4:3e @@ -216,20 +231,99 @@ printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" invoke_leaf 43ba >/dev/null [[ -f "$(generic)" ]] || fail "a card on the placeholder address still gets the NVRAM" -grep -qx "$(grep '^macaddr=' "$nvram")" "$(generic)" || - fail "placeholder wiphy keeps the source macaddr= key" "$(grep '^macaddr' "$(generic)")" -pass "placeholder wiphy keeps the source macaddr= key" - -# No MAC discoverable: keep the source macaddr= key. +expected=$(expected_stable_macaddr) +[[ -n $expected ]] || fail "expected macaddr is non-empty" +grep -q '^macaddr=' "$(generic)" || fail "placeholder wiphy keeps the macaddr= key" "$(cat "$(generic)")" +! grep -Fqx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || + fail "placeholder wiphy must not persist the dump donor" "$(grep '^macaddr' "$(generic)")" +grep -Fqx "$expected" "$(generic)" || + fail "placeholder wiphy gets this machine's stable MAC" "$(grep '^macaddr' "$(generic)")" +grep -Fqx "$expected" "$(dmi_file "Apple Inc." "MacBookPro14,3")" || + fail "placeholder wiphy DMI file gets this machine's stable MAC" "$(grep '^macaddr' "$(dmi_file "Apple Inc." "MacBookPro14,3")")" +pass "placeholder wiphy keeps macaddr= as a per-machine address" + +# No MAC discoverable: still install, with a per-machine macaddr= rather than the donor. rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" invoke_leaf 43ba >/dev/null [[ -f "$(generic)" ]] || fail "a Mac with no discoverable MAC still gets the NVRAM" -grep -qx "$(grep '^macaddr=' "$nvram")" "$(generic)" || - fail "macaddr= is kept from the source when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" -pass "macaddr= is kept from the source when no MAC is discoverable" +expected=$(expected_stable_macaddr) +[[ -n $expected ]] || fail "expected macaddr is non-empty" +grep -q '^macaddr=' "$(generic)" || fail "macaddr= is present when no MAC is discoverable" "$(cat "$(generic)")" +! grep -Fqx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || + fail "the dump donor is never persisted when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" +grep -Fqx "$expected" "$(generic)" || + fail "no discoverable MAC uses this machine's stable MAC" "$(grep '^macaddr' "$(generic)")" +pass "macaddr= is a per-machine address when no MAC is discoverable" + +# A distinct Broadcom placeholder, including the kernel's 00:90:4c:c5:12:38 +# default, must not be persisted and must not fall through to the dump donor. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_perm_mac 00:90:4c:aa:bb:cc +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +invoke_leaf 43ba >/dev/null +expected=$(expected_stable_macaddr) +[[ -n $expected ]] || fail "expected macaddr is non-empty" +! grep -Fqx 'macaddr=00:90:4c:aa:bb:cc' "$(generic)" || + fail "a distinct Broadcom placeholder is not persisted" "$(grep '^macaddr' "$(generic)")" +! grep -Fqx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || + fail "rejecting 00:90:4c:* must not persist the dump donor" "$(grep '^macaddr' "$(generic)")" +grep -Fqx "$expected" "$(generic)" || + fail "a distinct Broadcom placeholder falls through to the stable MAC" "$(grep '^macaddr' "$(generic)")" +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +provide_perm_mac 00:90:4c:c5:12:38 +invoke_leaf 43ba >/dev/null +! grep -Fqx 'macaddr=00:90:4c:c5:12:38' "$(generic)" || + fail "the kernel default placeholder is not persisted" "$(grep '^macaddr' "$(generic)")" +! grep -Fqx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || + fail "the kernel default placeholder must not persist the dump donor" "$(grep '^macaddr' "$(generic)")" +grep -Fqx "$expected" "$(generic)" || + fail "the kernel default placeholder falls through to the stable MAC" "$(grep '^macaddr' "$(generic)")" +pass "Broadcom 00:90:4c placeholders fall through to a per-machine MAC" + +# Two machines with no interface up must not share a station address. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +printf '%s\n' 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' >"$machine_id_file" +invoke_leaf 43ba >/dev/null +mac_one=$(grep '^macaddr=' "$(generic)") +[[ -n $mac_one ]] || fail "first machine-id produced a macaddr=" +! grep -Fqx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || + fail "first machine-id must not persist the dump donor" "$mac_one" +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +printf '%s\n' 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' >"$machine_id_file" +invoke_leaf 43ba >/dev/null +mac_two=$(grep '^macaddr=' "$(generic)") +[[ -n $mac_two ]] || fail "second machine-id produced a macaddr=" +[[ $mac_one != $mac_two ]] || fail "two machine-ids must not share a macaddr" "$mac_one" +printf '%s\n' '0123456789abcdef0123456789abcdef' >"$machine_id_file" +pass "two machine-ids produce two different macaddr= values" + +# Empty or missing machine-id and no live MAC: fail closed, persist nothing. +rm -rf "$fwdir" "$packaged" "$pci_devices" +mkdir -p "$fwdir" "$packaged" +printf '%s' "Apple Inc." >"$test_tmp/dmi/sys_vendor" +printf '%s' "MacBookPro14,3" >"$test_tmp/dmi/product_name" +: >"$machine_id_file" +if invoke_leaf 43ba >/dev/null 2>&1; then + fail "empty machine-id without a live MAC must not look like success" +fi +[[ -z $(ls -A "$fwdir") ]] || fail "empty machine-id leaves nothing behind" "$(ls -A "$fwdir")" +rm -f "$machine_id_file" +if invoke_leaf 43ba >/dev/null 2>&1; then + fail "missing machine-id without a live MAC must not look like success" +fi +[[ -z $(ls -A "$fwdir") ]] || fail "missing machine-id leaves nothing behind" "$(ls -A "$fwdir")" +printf '%s\n' '0123456789abcdef0123456789abcdef' >"$machine_id_file" +pass "empty machine-id without a live MAC fails and persists nothing" run_leaf "Apple Inc." "MacBookPro14,3" 43a0 >/dev/null [[ ! -f "$(generic)" ]] || fail "a Mac whose Wi-Fi brcmfmac does not drive is left alone" @@ -348,7 +442,9 @@ mkdir -p "$fwdir" "$packaged" provide_mac run_migration "Apple Inc." "MacBookPro14,3" 43ba [[ -f "$(generic)" ]] || fail "the migration installs NVRAM on an existing Mac" -grep -qx 'macaddr=aa:bb:cc:dd:ee:ff' "$(generic)" || +expected=macaddr=aa:bb:cc:dd:ee:ff +[[ -n $expected ]] || fail "expected macaddr is non-empty" +grep -Fqx "$expected" "$(generic)" || fail "the migration substitutes the live MAC" "$(grep '^macaddr' "$(generic)")" grep -Fq $'omarchy-state\tset\treboot-required' "$calls" || fail "the migration asks for the reboot that applies it" "$(cat "$calls")" @@ -362,11 +458,16 @@ rm -rf "$fwdir" "$packaged" "$pci_devices" mkdir -p "$fwdir" "$packaged" run_migration "Apple Inc." "MacBookPro14,3" 43ba [[ -f "$(generic)" ]] || fail "the migration installs NVRAM without a discoverable MAC" -grep -qx "$(grep '^macaddr=' "$nvram")" "$(generic)" || - fail "the migration keeps source macaddr= when no MAC is discoverable" "$(grep '^macaddr' "$(generic)")" +expected=$(expected_stable_macaddr) +[[ -n $expected ]] || fail "expected macaddr is non-empty" +grep -q '^macaddr=' "$(generic)" || fail "the migration keeps the macaddr= key" "$(cat "$(generic)")" +! grep -Fqx 'macaddr=00:90:4c:0d:f4:3e' "$(generic)" || + fail "the migration must not persist the dump donor" "$(grep '^macaddr' "$(generic)")" +grep -Fqx "$expected" "$(generic)" || + fail "the migration uses this machine's stable MAC when none is discoverable" "$(grep '^macaddr' "$(generic)")" grep -Fq $'omarchy-state\tset\treboot-required' "$calls" || fail "the migration still asks for a reboot without a MAC" "$(cat "$calls")" -pass "the migration keeps source macaddr= when no MAC is discoverable" +pass "the migration uses a per-machine MAC when none is discoverable" rm -rf "$fwdir" "$packaged" mkdir -p "$fwdir" "$packaged"