commit a8418431bb90de70a398d66ecc806bfc2137f70f Author: bwn-a1370 Date: Sun Aug 16 16:08:44 2026 +0300 bwn BCM43224 (MacBook Air A1370) driver fixes for FreeBSD 15.1 Patches + build scripts + firmware sources to make the Broadcom BCM43224 (PCI 0x14e4:0x4353, subvendor 0x106b:0x00d1) work on FreeBSD 15.1. Fixes: - Fix 1: compile GPL N-PHY code (BWN_GPL_PHY build wiring) - Fix 2: NULL-guard bwn_dma_ringfree on partial attach - Fix 3: wire bwn_update_mcast to ic->ic_update_mcast - Fix 5: load bwn_v4_n firmware at loader stage (loader.conf) - Fix 6: reclaim pending TX frames at bwn_dma_stop (detach double-free) - Fix 7: driver-owned node ref + remove dr_usedslot early-return + bhndb_pci BHNDB_PCI_QUIRK_NO_MSI for BCM43224 (legacy INTx) - Fix 8: restrict reclaim to TX rings only (RX reclaim caused heap corruption / TCP panic) README.md = rebuild guide (apply.sh + build.sh, manual steps) RESEARCH.md = root-cause investigation log (Fixes 5-8) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2423af5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Build artifacts (regenerated by build.sh / make) +*.o +*.fwo +*.ko +.depend* +opt_global.h +machine +x86 +i386 +src/firmware/bwn_v4_n.ko diff --git a/README.md b/README.md new file mode 100644 index 0000000..505c18b --- /dev/null +++ b/README.md @@ -0,0 +1,120 @@ +# bwn driver fixes for Broadcom BCM43224 (Apple MacBook Air A1370) + +A complete, self-contained set of fixes that make the FreeBSD `bwn(4)` driver +work on the Apple MacBook Air A1370's BCM43224 +(PCI `0x14e4:0x4353`, subvendor `0x106b:0x00d1`) 802.11a/b/g/n chip. + +The driver has been tested through multiple `kldunload`/`kldload` cycles and +reboots on the target hardware with no panic. + +## Build & install (clean FreeBSD 15.1) + +You need the FreeBSD 15.1 **source tree** at `/usr/src` (or pass an alternate +`SRC_ROOT` to the scripts). Then, as root: + +```sh +cd bwn-a1370 +sudo ./apply.sh # patch /usr/src (or: sudo ./apply.sh /path/to/src) +sudo ./build.sh # build + install if_bwn.ko, bhndb_pci.ko, bwn_v4_n.ko +reboot +``` + +After reboot, bring up the interface: + +```sh +ifconfig wlan create wlandev bwn0 [wap] +# or, to join a WPA network, create the wlan and run wpa_supplicant as usual +``` + +`build.sh` installs everything for the **next boot**; it does not touch the +running kernel, so an active session (e.g. SSH over a fallback `rtwn0/wlan0` +interface) is never disrupted. + +### What the scripts do + +- `apply.sh` — idempotently applies the 4 source patches to `/usr/src` + (`patches/patch-*.c` and `patches/patch-Makefile`). Safe to re-run. +- `build.sh` — builds `if_bwn.ko` (`sys/modules/bwn`), `bhndb_pci.ko` + (`sys/modules/bhnd/bhndb_pci`), and `bwn_v4_n.ko` (from `src/firmware/`); + installs them to `/boot/modules` and `/boot/kernel`; and appends + `bwn_v4_n_load="YES"` to `/boot/loader.conf` if missing (Fix 5). + +### Manual build (equivalent to build.sh) + +```sh +SRC=/usr/src + +# 1. driver +make -C $SRC/sys/modules/bwn SRCTOP=$SRC SYSDIR=$SRC/sys +install -m 444 $SRC/sys/modules/bwn/if_bwn.ko /boot/modules/ + +# 2. PCIe bridge (NO_MSI quirk) +make -C $SRC/sys/modules/bhnd/bhndb_pci SRCTOP=$SRC SYSDIR=$SRC/sys +install -m 444 $SRC/sys/modules/bhnd/bhndb_pci/bhndb_pci.ko /boot/kernel/ + +# 3. firmware +make -C src/firmware SRCTOP=$SRC SYSDIR=$SRC/sys +install -m 444 src/firmware/bwn_v4_n.ko /boot/modules/ + +# 4. loader.conf (Fix 5) +echo 'bwn_v4_n_load="YES"' >> /boot/loader.conf +``` + +## Directory layout + +``` +bwn-a1370/ + README.md This file (rebuild guide) + RESEARCH.md Root-cause investigation log (Fixes 5-8) + apply.sh Apply the 4 source patches to /usr/src (idempotent) + build.sh Build + install all 3 modules and add the loader.conf entry + loader.conf.bwn Snippet for /boot/loader.conf (Fix 5: load firmware at boot) + patches/ + patch-if_bwn.c Driver fixes (Fixes 1-4, 6-8) + patch-Makefile GPL PHY build wiring (Fix 1) + patch-bhndb_pci.c NO_MSI quirk for BCM43224 (Fix 7) + patch-bhndb_pcivar.h NO_MSI quirk definition (Fix 7) + combined.patch All of the above in one file (alternative to apply.sh) + src/firmware/ Broadcom v4 firmware module (bwn_v4_n.ko) sources + Makefile + rc.conf.backups/ Backups of loader.conf/rc.conf made by the scripts +``` + +## What was broken (summary) + +On a stock FreeBSD 15.1 system this chip (an N-PHY rev-23 core behind a +BCMA/bhnd bus, attached via the `bhndb_pci` PCIe-G1 bridge) failed in several +ways: + +1. The GPL N-PHY code is not compiled in (build wiring). +2. A NULL ring pointer can be dereferenced on a partial attach. +3. Multicast updates are not wired to `ic->ic_update_mcast`. +4. Firmware is not found at attach (loader vs kld_list ordering). +5. **MSI interrupts are never delivered** on this host (NVIDIA MCP89 bridge), + so TX completions never run -> "device timeout" and the NIC never passes + traffic. +6. Detaching while a TX frame is pending double-frees an `ieee80211_node` + (kernel panic). +7. Reclaiming the **RX** ring at device-stop DMAs received frames into freed + kernel memory -> heap corruption / TCP panic. + +| # | Area | Problem | Fix | +|---|------|---------|-----| +| 1 | build | GPL N-PHY code not compiled in | `BWN_GPL_PHY` via `opt_bwn.h`; add 7 GPL PHY files to `SRCS` in `modules/bwn/Makefile` | +| 2 | `bwn_dma_ringfree` | NULL ring deref on partial attach | NULL-guard `if (dr == NULL \|\| *dr == NULL) return;` | +| 3 | `bwn_update_mcast` | mcast not updated | wire to `ic->ic_update_mcast` | +| 4 | `build.sh` | reload clobbered live iface | unload/rebuild/reinstall/reload `if_bwn` only | +| 5 | `/boot/loader.conf` | firmware not found at attach | `bwn_v4_n_load="YES"` loads firmware at loader stage | +| 6 | `bwn_dma_stop` | detach double-frees node (vmcore.4) | reclaim pending TX frames (drop node refs) at device-stop | +| 7 | `if_bwn` + `bhndb_pci` | node ref ownership + MSI never delivered (vmcore.5 / "device timeout") | driver takes its own `ieee80211_ref_node()`; remove `dr_usedslot` early-return; `BHNDB_PCI_QUIRK_NO_MSI` for BCM43224 forces legacy INTx | +| 8 | `bwn_dma_stop` | RX-ring reclaim -> heap corruption (vmcore.6) | restrict reclaim to TX rings only (new `bwn_dma_reclaim_tx()`) | + +## Files modified in /usr/src + +- `sys/dev/bwn/if_bwn.c` +- `sys/modules/bwn/Makefile` +- `sys/dev/bhnd/bhndb/bhndb_pci.c` +- `sys/dev/bhnd/bhndb/bhndb_pcivar.h` + +(Plus the prebuilt `bwn_v4_n.ko` firmware, reproducible from `src/firmware/`.) + +See `RESEARCH.md` for the full root-cause analysis behind each fix. diff --git a/RESEARCH.md b/RESEARCH.md new file mode 100644 index 0000000..60a6312 --- /dev/null +++ b/RESEARCH.md @@ -0,0 +1,133 @@ +# Research & Root-Cause Log + +This document records the investigation that produced the fixes in this +repository. It is kept separate from `README.md` (the rebuild guide) so the +how-to stays clean. Each section maps to a fix number used throughout the +project. + +Hardware under test: Apple MacBook Air A1370, Broadcom BCM43224 +(PCI `0x14e4:0x4353`, subvendor `0x106b:0x00d1`), N-PHY rev-23 core behind a +BCMA/bhnd bus attached via the `bhndb_pci` PCIe-G1 bridge, on an NVIDIA MCP89 +host bridge. FreeBSD 15.1-RELEASE-p2 (amd64). + +--- + +## Fix 5 — Load Broadcom v4 firmware at loader time (firmware "not found" panic) + +**Problem.** `if_bwn.ko` has a `pci/bwn_pci` devmatch modalias, so the kernel +AUTO-LOADS it when `bwn_pci0` attaches. The firmware-only `bwn_v4_n.ko` has NO +modalias and loads only via `kld_list`. If the firmware module is missing at +attach time (e.g. `rc.conf` `kld_list` got corrupted/shortened), +`bwn_fw_get()` fails and the driver panics while cleaning up +("firmware bwn_v4_ucode16_mimo not found"). + +**Fix.** Add `bwn_v4_n_load="YES"` to `/boot/loader.conf` so the firmware +module is loaded at the loader stage, before any device attach. This makes +firmware availability independent of `rc.conf`/`kld_list` ordering. + +**Verification.** With `bwn_v4_n` loaded, creating `wlan1` on `bwn0` loads +`ucode16_mimo` with no "not found" error. + +--- + +## Fix 6 — Kernel panic on kldunload after a "device timeout" (vmcore.4) + +**Symptom.** `kldunload if_bwn` (or any detach while a wlan existed on bwn0) +panicked with a page fault in `node_cleanup` / `ieee80211_ageq_remove` +(fault addr `0x488`), always after a `bwn0: device timeout`. + +**Root cause.** TX never completes because MSI interrupts are not delivered, +so the TX slot keeps its `mbuf` + `ieee80211_node` (`mt_ni`) reference — +`bwn_dma_handle_txeof` (the only place that clears `mt_ni`) never runs. +`bwn_dma_stop()` only halts the DMA engine and never frees queued frames, so +the node reference lingers. When net80211 later frees that node (scan cancel / +VAP teardown) the slot points at freed memory; then `bwn_detach` -> +`bwn_dma_free` -> `bwn_dma_ringfree` -> `bwn_dma_free_descbufs` -> +`ieee80211_free_node(stale mt_ni)` walks freed memory and panics. + +**Fix.** Reclaim pending frames (and their node references) in +`bwn_dma_stop()`, which runs from both the detach path +(`bwn_stop` -> `bwn_core_exit`) and every HW reset. This drops node references +while the node is still alive (before `ieee80211_ifdetach` frees nodes). +`bwn_dma_free_descbufs()` is idempotent (NULLs `mt_m`/`mt_ni`), so the later +detach free finds empty slots. + +--- + +## Fix 7 — TX node reference ownership + unconditional reclaim + MSI/INTx (vmcore.5) + +**Symptom.** After Fix 6 a fresh reboot still panicked on `kldunload` +(vmcore.5): page fault in `ieee80211_ratectl_node_deinit` (fault addr `0x30`, +`vap->iv_rate` deref), `ni->ni_refcnt = 1`, node's `ni_vap` already torn down. +Also, with MSI never delivered, the chip showed "device timeout" and passed no +traffic. + +**Root cause.** The driver relied on the mbuf's `rcvif` node reference (owned +by the net80211 stack) for the lifetime of a queued TX frame. The stack can +release that reference at any time (scan cancel, VAP teardown, node reclaim) +while the frame is still in the ring, because TX completion never runs when +interrupts are not delivered. Also `bwn_dma_free_descbufs()` early-returned on +`if (!dr->dr_usedslot)`; `dr_usedslot` is only a free-slot accounting hint and +is not a reliable indicator of pending frames, so the reclaim could be +skipped. + +Separately, `bhndb_pci` enables MSI for the BCM43224, but on this host (NVIDIA +MCP89 Apple MacBook Air) MSI is never delivered. + +**Fix.** +1. `bwn_dma_tx_start()` / `bwn_pio_tx_start()` now take the driver's OWN + reference via `ieee80211_ref_node(ni)` before stashing it in `mt->mt_ni` / + `tp->tp_ni`. `ieee80211_tx_complete()` in the txeof path releases exactly + one reference, so accounting stays balanced and detach cannot double-free. +2. Removed the `if (!dr->dr_usedslot) return;` early-return from + `bwn_dma_free_descbufs()` so reclaim unconditionally walks every slot. +3. Added `BHNDB_PCI_QUIRK_NO_MSI` for BCM43224 in `bhndb_pci.c` / + `bhndb_pcivar.h` so the bridge falls back to legacy INTx — TX interrupts + are then delivered and the "device timeout" root cause is gone. + +--- + +## Fix 8 — Remove RX-ring reclaim from bwn_dma_stop (vmcore.6 heap corruption) + +**Symptom.** After Fix 6 the bwn *detach* panic was gone (clean `bwn0: +detached` cycles), but ~47 min into a session the box panicked with a +DIFFERENT crash: a TCP stack NULL-deref inside `tcp_m_copym` while processing +an inbound packet on wlan0/rtwn0 (the SSH link). The SSH socket's send buffer +was corrupted (`so_snd.sb_mb == NULL` while `sb_ccc == 126404`; `tp` seq +numbers garbage). + +**Root cause.** Fix 6's reclaim in `bwn_dma_stop()` walked **all six rings +including the RX ring**. `bwn_dma_stop()` runs on **every** `bwn_hwreset` +(every "device timeout" — and on this chip MSI TX interrupts are not +delivered, so timeouts fire constantly). `bwn_dma_free_descbufs()` frees the +RX mbuf WITHOUT clearing the DMA descriptor's physical address. The RX ring is +persistent (refilled in place by `bwn_rxeof()` -> `bwn_dma_newbuf()`, never +re-allocated) and `bwn_dma_cleanup()` only zeroes the ring *control* register. +So after a reset the descriptor still pointed at the freed page; when RX was +re-enabled the NIC DMAd received frames into freed kernel memory -> heap +corruption that eventually clobbered the SSH socket. The console log showed +~60 lines of `bwn0: bwn_dma_free_descbufs: not TX?` (one per RX slot whose +mbuf had just been freed) immediately before the panic — the smoking gun. + +**Fix.** Restrict the reclaim in `bwn_dma_stop()` (factored into a new helper +`bwn_dma_reclaim_tx()`) to the **TX rings only** (wme BK/BE/VI/VO + mcast). +The RX ring is never reclaimed there. `bwn_dma_reclaim_tx()` is also called +from `bwn_detach()` BEFORE `ieee80211_ifdetach()` (defence-in-depth on top of +the ref_node fix in Fix 7). + +**Verification.** Disassembly: `bwn_dma_stop` has 6 ringstops (RX + 5 TX) + +exactly 5 `bwn_dma_free_descbufs` calls (TX only; RX ring no longer +reclaimed). + +--- + +## Why the MSI quirk matters (Fix 7, point 3) + +On this host the `bhndb_pci` bridge enables MSI for the BCM43224, but the +interrupts are never delivered (no `bwn`/`MSI` line in `vmstat -i`). Because +the only place that clears a TX slot's node reference is the TX-completion +path (`bwn_dma_handle_txeof`), TX frames never complete -> "device timeout" +and the cascade of use-after-free / heap-corruption panics. Forcing legacy +INTx (the `NO_MSI` quirk) makes TX interrupts arrive, so completions run +normally. The reclaim/ref_node fixes (6-8) are defence-in-depth that keep the +driver robust even if completions are delayed. diff --git a/apply.sh b/apply.sh new file mode 100755 index 0000000..af0c809 --- /dev/null +++ b/apply.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Apply the bwn BCM43224 (Apple MacBook Air A1370) patches to a clean +# FreeBSD 15.1 source tree. Idempotent: it skips any patch that is already +# applied. +# +# Usage: sudo ./apply.sh [SRC_ROOT] +# Default SRC_ROOT=/usr/src +# +# The patches: +# patches/patch-if_bwn.c - driver fixes (Fixes 1-4, 6-8) +# patches/patch-Makefile - GPL PHY build wiring (Fix 1) +# patches/patch-bhndb_pci.c - NO_MSI quirk for BCM43224 (Fix 7) +# patches/patch-bhndb_pcivar.h - NO_MSI quirk definition (Fix 7) +set -e +SRC="${1:-/usr/src}" +HERE="$(cd "$(dirname "$0")" && pwd)" + +if [ ! -f "$SRC/sys/dev/bwn/if_bwn.c" ]; then + echo "ERROR: $SRC/sys/dev/bwn/if_bwn.c not found." >&2 + echo " Point SRC_ROOT at a FreeBSD 15.1 src tree (e.g. /usr/src)." >&2 + exit 1 +fi + +echo "Applying patches into: $SRC" +for p in patch-if_bwn.c patch-Makefile patch-bhndb_pci.c patch-bhndb_pcivar.h; do + if (cd "$SRC" && patch -p1 -N --dry-run < "$HERE/patches/$p" >/dev/null 2>&1); then + (cd "$SRC" && patch -p1 -N < "$HERE/patches/$p") + echo " applied $p" + else + echo " skip $p (already applied or not applicable)" + fi +done +echo "Done. Source patched. Now run ./build.sh (or 'make' in sys/modules/bwn)." diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5f23899 --- /dev/null +++ b/build.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# Build and install the patched bwn driver (if_bwn.ko), the patched PCIe +# bridge (bhndb_pci.ko) and the Broadcom v4 firmware (bwn_v4_n.ko) on a +# clean FreeBSD 15.1 system. +# +# Usage: sudo ./build.sh [SRC_ROOT] +# Default SRC_ROOT=/usr/src +# +# What it does: +# 1. Builds if_bwn.ko (sys/modules/bwn) -> /boot/modules/if_bwn.ko +# 2. Builds bhndb_pci.ko(sys/modules/bhnd/bhndb_pci) -> /boot/kernel/bhndb_pci.ko +# 3. Builds bwn_v4_n.ko (src/firmware) -> /boot/modules/bwn_v4_n.ko +# 4. Adds "bwn_v4_n_load=YES" to /boot/loader.conf (Fix 5) if missing. +# +# All changes take effect on the NEXT reboot (modules are only loaded at +# boot). The running kernel is NOT touched, so an active SSH session (e.g. +# over the fallback rtwn0/wlan0 interface) is never disrupted by this script. +# +# After reboot, bwn0 attaches on the BCM43224 and uses legacy INTx (the +# NO_MSI quirk), so TX interrupts are delivered and the "device timeout" / +# detach-panic / heap-corruption issues are resolved. +set -e +SRC="${1:-/usr/src}" +HERE="$(cd "$(dirname "$0")" && pwd)" + +if [ ! -d "$SRC/sys" ]; then + echo "ERROR: $SRC/sys not found. Point SRC_ROOT at a FreeBSD 15.1 src tree." >&2 + exit 1 +fi + +echo "==> [1/3] Building if_bwn.ko" +make -C "$SRC/sys/modules/bwn" SRCTOP="$SRC" SYSDIR="$SRC/sys" clean +make -C "$SRC/sys/modules/bwn" SRCTOP="$SRC" SYSDIR="$SRC/sys" +install -m 444 -o root -g wheel "$SRC/sys/modules/bwn/if_bwn.ko" /boot/modules/if_bwn.ko +sync +echo " -> /boot/modules/if_bwn.ko" + +echo "==> [2/3] Building bhndb_pci.ko (NO_MSI quirk)" +make -C "$SRC/sys/modules/bhnd/bhndb_pci" SRCTOP="$SRC" SYSDIR="$SRC/sys" clean +make -C "$SRC/sys/modules/bhnd/bhndb_pci" SRCTOP="$SRC" SYSDIR="$SRC/sys" +install -m 444 -o root -g wheel "$SRC/sys/modules/bhnd/bhndb_pci/bhndb_pci.ko" /boot/kernel/bhndb_pci.ko +sync +echo " -> /boot/kernel/bhndb_pci.ko" + +echo "==> [3/3] Building + installing bwn_v4_n.ko (firmware)" +make -C "$HERE/src/firmware" SRCTOP="$SRC" SYSDIR="$SRC/sys" clean +make -C "$HERE/src/firmware" SRCTOP="$SRC" SYSDIR="$SRC/sys" +install -m 444 -o root -g wheel "$HERE/src/firmware/bwn_v4_n.ko" /boot/modules/bwn_v4_n.ko +sync +echo " -> /boot/modules/bwn_v4_n.ko" + +echo "==> Adding firmware load to /boot/loader.conf (Fix 5)" +if ! grep -q 'bwn_v4_n_load="YES"' /boot/loader.conf 2>/dev/null; then + cp /boot/loader.conf "$HERE/rc.conf.backups/loader.conf.before_build_$(date +%Y%m%d%H%M%S)" + printf '\n# bwn driver: load Broadcom v4 firmware at loader time (Fix 5)\nbwn_v4_n_load="YES"\n' >> /boot/loader.conf + echo " added bwn_v4_n_load=YES" +else + echo " already present, skipped" +fi + +echo +echo "==> All modules built and installed. REBOOT to activate." +echo " After reboot, create the interface with:" +echo " ifconfig wlan create wlandev bwn0 [wap]" diff --git a/loader.conf.bwn b/loader.conf.bwn new file mode 100644 index 0000000..505f112 --- /dev/null +++ b/loader.conf.bwn @@ -0,0 +1,6 @@ +# Fix 5 (bwn driver): load the Broadcom v4 firmware module at loader time so it +# is present BEFORE if_bwn attaches bwn0 (if_bwn auto-loads via devmatch when +# bwn_pci0 attaches, but the firmware-only module has no modalias and would +# otherwise only load from kld_list, which is fragile). Without this the driver +# panics with "firmware bwn_v4_ucode16_mimo not found" during cleanup. +bwn_v4_n_load="YES" diff --git a/patches/combined.patch b/patches/combined.patch new file mode 100644 index 0000000..bcd83ce --- /dev/null +++ b/patches/combined.patch @@ -0,0 +1,338 @@ +diff --git a/sys/dev/bhnd/bhndb/bhndb_pci.c b/sys/dev/bhnd/bhndb/bhndb_pci.c +index 0cdcba1d..7b6ae0f3 100644 +--- a/sys/dev/bhnd/bhndb/bhndb_pci.c ++++ b/sys/dev/bhnd/bhndb/bhndb_pci.c +@@ -195,6 +195,16 @@ static struct bhndb_pci_quirk bhndb_pci_quirks[] = { + static struct bhndb_pci_quirk bhndb_pcie_quirks[] = { + /* All PCIe-G1 core revisions require the SRSH work-around */ + BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR), ++ ++ /* ++ * BCM43224 (and other Broadcom PCIe-G1 endpoints) do not have MSI ++ * delivered on some host platforms (e.g. NVIDIA MCP89 based Apple ++ * MacBook Air); force legacy INTx instead. ++ */ ++ { { BHND_MATCH_CHIP_ID(BCM43224) }, ++ { BHND_MATCH_ANY }, ++ BHNDB_PCI_QUIRK_NO_MSI }, ++ + BHNDB_PCI_QUIRK_END + }; + +@@ -327,6 +337,13 @@ bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, int *msi_count) + { + int error, count; + ++ /* MSI disabled by bridge quirk? */ ++ if (sc->pci_quirks & BHNDB_PCI_QUIRK_NO_MSI) { ++ device_printf(sc->dev, "MSI disabled by quirk; using INTx on " ++ "%s\n", device_get_nameunit(sc->parent)); ++ return (ENXIO); ++ } ++ + /* Is MSI available? */ + if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT) + return (ENXIO); +diff --git a/sys/dev/bhnd/bhndb/bhndb_pcivar.h b/sys/dev/bhnd/bhndb/bhndb_pcivar.h +index ed749518..7b473fa6 100644 +--- a/sys/dev/bhnd/bhndb/bhndb_pcivar.h ++++ b/sys/dev/bhnd/bhndb/bhndb_pcivar.h +@@ -70,6 +70,14 @@ enum { + * interrupt flags via the SIBA_CFG0_INTVEC register. + */ + BHNDB_PCI_QUIRK_SIBA_INTVEC = (1<<1), ++ ++ /** ++ * The PCI/PCIe bridge must not use MSI; instead, fall back to legacy ++ * INTx line interrupts. Required on some systems (e.g. NVIDIA MCP89 ++ * based Apple MacBook Air) where MSI messages from the Broadcom ++ * PCIe-G1 endpoint are never delivered, causing device timeouts. ++ */ ++ BHNDB_PCI_QUIRK_NO_MSI = (1<<2), + }; + + /** bhndb_pci quirk table entry */ +diff --git a/sys/dev/bwn/if_bwn.c b/sys/dev/bwn/if_bwn.c +index 38bf6f5d..7a5b4230 100644 +--- a/sys/dev/bwn/if_bwn.c ++++ b/sys/dev/bwn/if_bwn.c +@@ -147,6 +147,7 @@ static int bwn_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); + static void bwn_updateslot(struct ieee80211com *); + static void bwn_update_promisc(struct ieee80211com *); ++static void bwn_update_mcast(struct ieee80211com *); + static void bwn_wme_init(struct bwn_mac *); + static int bwn_wme_update(struct ieee80211com *); + static void bwn_wme_clear(struct bwn_softc *); +@@ -250,6 +251,7 @@ static void bwn_dma_setup(struct bwn_dma_ring *); + static void bwn_dma_free_ringmemory(struct bwn_dma_ring *); + static void bwn_dma_cleanup(struct bwn_dma_ring *); + static void bwn_dma_free_descbufs(struct bwn_dma_ring *); ++static void bwn_dma_reclaim_tx(struct bwn_mac *); + static int bwn_dma_tx_reset(struct bwn_mac *, uint16_t, int); + static void bwn_dma_rx(struct bwn_dma_ring *); + static int bwn_dma_rx_reset(struct bwn_mac *, uint16_t, int); +@@ -812,6 +814,7 @@ bwn_attach_post(struct bwn_softc *sc) + ic->ic_raw_xmit = bwn_raw_xmit; + ic->ic_updateslot = bwn_updateslot; + ic->ic_update_promisc = bwn_update_promisc; ++ ic->ic_update_mcast = bwn_update_mcast; + ic->ic_wme.wme_update = bwn_wme_update; + ic->ic_scan_start = bwn_scan_start; + ic->ic_scan_end = bwn_scan_end; +@@ -855,7 +858,6 @@ bwn_detach(device_t dev) + BWN_LOCK(sc); + bwn_stop(sc); + BWN_UNLOCK(sc); +- bwn_dma_free(mac); + callout_drain(&sc->sc_led_blink_ch); + callout_drain(&sc->sc_rfswitch_ch); + callout_drain(&sc->sc_task_ch); +@@ -863,7 +865,17 @@ bwn_detach(device_t dev) + bwn_phy_detach(mac); + ieee80211_draintask(ic, &mac->mac_hwreset); + ieee80211_draintask(ic, &mac->mac_txpower); ++ /* ++ * Drop any TX-ring node references BEFORE ieee80211_ifdetach() ++ * frees all nodes. bwn_dma_free()/detach would otherwise find a ++ * dangling node pointer (the ring slot still holds mt_ni for a ++ * frame whose TX completion never ran) and double-free it ++ * (vmcore.5). This is independent of mac_status: a frame can be ++ * queued in a ring without the MAC ever reaching STARTED. ++ */ ++ bwn_dma_reclaim_tx(mac); + ieee80211_ifdetach(ic); ++ bwn_dma_free(mac); + } + taskqueue_drain(sc->sc_tq, &mac->mac_intrtask); + taskqueue_free(sc->sc_tq); +@@ -1094,7 +1106,12 @@ bwn_pio_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, + tq = bwn_pio_select(mac, M_WME_GETAC(m)); + KASSERT(!TAILQ_EMPTY(&tq->tq_pktlist), ("%s: fail", __func__)); + tp = TAILQ_FIRST(&tq->tq_pktlist); +- tp->tp_ni = ni; ++ /* ++ * Take our own reference on the node (see bwn_dma_tx_start for the ++ * rationale); the PIO path otherwise shares the same use-after-free ++ * hazard as the DMA path. ++ */ ++ tp->tp_ni = ieee80211_ref_node(ni); + tp->tp_m = m; + + error = bwn_set_txhdr(mac, ni, m, &txhdr, BWN_PIO_COOKIE(tq, tp)); +@@ -1218,7 +1235,15 @@ bwn_dma_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, + KASSERT(mt->mt_txtype == BWN_DMADESC_METATYPE_BODY && + mt->mt_islast == 1, ("%s:%d: fail", __func__, __LINE__)); + mt->mt_m = m; +- mt->mt_ni = ni; ++ /* ++ * Take our own reference on the node. The mbuf's rcvif reference ++ * belongs to the net80211 stack and may be released (e.g. when the ++ * VAP is torn down or a scan is cancelled) while the frame is still ++ * pending in the TX ring. Without our own reference the slot would ++ * point at a freed node, and freeing it later (on TX completion or ++ * at detach) would double-free / use-after-free and panic. ++ */ ++ mt->mt_ni = ieee80211_ref_node(ni); + + error = bus_dmamap_load_mbuf(dma->txbuf_dtag, mt->mt_dmap, m, + bwn_dma_buf_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); +@@ -1892,6 +1917,33 @@ bwn_update_promisc(struct ieee80211com *ic) + BWN_UNLOCK(sc); + } + ++static void ++bwn_update_mcast(struct ieee80211com *ic) ++{ ++ struct bwn_softc *sc = ic->ic_softc; ++ struct bwn_mac *mac; ++ ++ /* ++ * bwn has no hardware multicast hash filter; the only RX filter bit ++ * available for multicast is BWN_MACCTL_PROMISC (the same bit used by ++ * bwn_update_promisc). When any VAP needs all-multicast (e.g. for ++ * IPv6 ND/MLD), set PROMISC so the frames are passed up to net80211, ++ * which then does the multicast filtering in software. Without this ++ * callback net80211 uses the null stub and prints ++ * "need multicast update callback", and IPv6 multicast breaks. ++ */ ++ BWN_LOCK(sc); ++ mac = sc->sc_curmac; ++ if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { ++ if (ic->ic_allmulti > 0) ++ sc->sc_filters |= BWN_MACCTL_PROMISC; ++ else ++ sc->sc_filters &= ~BWN_MACCTL_PROMISC; ++ bwn_set_opmode(mac); ++ } ++ BWN_UNLOCK(sc); ++} ++ + /* + * Callback from the 802.11 layer to update WME parameters. + */ +@@ -2999,7 +3051,17 @@ static void + bwn_dma_ringfree(struct bwn_dma_ring **dr) + { + +- if (dr == NULL) ++ /* ++ * bwn_dma_attach() sets BWN_MAC_FLAG_DMA early, before the individual ++ * rings (rx/wme[]/mcast) are allocated via bwn_dma_ringsetup(). If ++ * attach fails partway through (or the device is detached after a ++ * partial attach), bwn_dma_free() is still invoked because the flag is ++ * set, but the ring pointers are still NULL. Dereferencing them here ++ * faults (the crash in bwn_dma_ringfree+0x14c reading at offset 0x30, ++ * i.e. (*dr)->dr_mac). Guard against a NULL ring so teardown after a ++ * partial attach is safe. ++ */ ++ if (dr == NULL || *dr == NULL) + return; + + bwn_dma_free_descbufs(*dr); +@@ -3364,8 +3426,18 @@ bwn_dma_free_descbufs(struct bwn_dma_ring *dr) + struct bwn_softc *sc = mac->mac_sc; + int i; + +- if (!dr->dr_usedslot) +- return; ++ /* ++ * Do NOT skip this loop based on dr_usedslot. dr_usedslot is only an ++ * accounting hint for free-slot tracking and is not a reliable ++ * indicator of whether a frame (and its node reference) is pending; ++ * after a HW reset / ring re-init it can be inconsistent with the ++ * actual slot contents. We must unconditionally walk every slot and ++ * release any mbuf + node reference still attached, otherwise a ++ * dangling node pointer survives to bwn_dma_free()/detach and ++ * double-frees the node (vmcore.5). bwn_dma_free_descbuf() is ++ * idempotent (NULLs mt_m/mt_ni and skips empty slots), so walking ++ * all slots is safe even when none hold a frame. ++ */ + for (i = 0; i < dr->dr_numslots; i++) { + dr->getdesc(dr, i, &desc, &meta); + +@@ -3388,6 +3460,31 @@ bwn_dma_free_descbufs(struct bwn_dma_ring *dr) + } + } + ++static void ++bwn_dma_reclaim_tx(struct bwn_mac *mac) ++{ ++ struct bwn_dma *dma; ++ ++ if ((mac->mac_flags & BWN_MAC_FLAG_DMA) == 0) ++ return; ++ dma = &mac->mac_method.dma; ++ ++ /* ++ * Drop the mbuf + ieee80211_node references for any frame still ++ * pending in the TX rings. This must be called BEFORE ++ * ieee80211_ifdetach() (which frees all nodes) and is safe to ++ * call even if the MAC was never fully started, because a ++ * frame can be queued in a ring without the MAC reaching ++ * STARTED (e.g. the watchdog fired right after a transmit). ++ * See Fix 6/7 and vmcore.5 (node double-free) / vmcore.4. ++ */ ++ bwn_dma_free_descbufs(dma->wme[WME_AC_BK]); ++ bwn_dma_free_descbufs(dma->wme[WME_AC_BE]); ++ bwn_dma_free_descbufs(dma->wme[WME_AC_VI]); ++ bwn_dma_free_descbufs(dma->wme[WME_AC_VO]); ++ bwn_dma_free_descbufs(dma->mcast); ++} ++ + static int + bwn_dma_tx_reset(struct bwn_mac *mac, uint16_t base, + int type) +@@ -7349,6 +7446,45 @@ bwn_dma_stop(struct bwn_mac *mac) + bwn_dma_ringstop(&dma->wme[WME_AC_VI]); + bwn_dma_ringstop(&dma->wme[WME_AC_VO]); + bwn_dma_ringstop(&dma->mcast); ++ ++ /* ++ * Reclaim any frames still pending in the DMA rings. ++ * ++ * When TX never completes (e.g. interrupts are not delivered, which ++ * on this chip manifests as "device timeout"), the ring slots keep ++ * their mbuf + ieee80211_node references until a TX-completion ++ * callback (bwn_dma_handle_txeof) runs. bwn_dma_handle_txeof is the ++ * ONLY place that clears mt_ni, so without it the references linger ++ * here. If net80211 later frees that node (scan cancel / VAP ++ * teardown) while we still point at it, the reference becomes ++ * dangling; then bwn_dma_free() -> bwn_dma_ringfree() -> ++ * bwn_dma_free_descbufs() -> ieee80211_free_node() walks freed ++ * memory and panics (vmcore.4: page fault in node_cleanup / ++ * ieee80211_ageq_remove). ++ * ++ * Releasing the pending frames here, at device-stop time, drops the ++ * node references while the node is still alive (this runs before ++ * ieee80211_ifdetach() during detach and during every HW reset), ++ * so no dangling reference survives to detach. bwn_dma_free_descbufs ++ * is idempotent (it NULLs mt_m/mt_ni), so the later free at detach ++ * simply finds empty slots and skips them. ++ * ++ * IMPORTANT: only the TX rings (wme[] + mcast) are reclaimed here. ++ * The RX ring MUST NOT be reclaimed at stop time: bwn_dma_stop() runs ++ * on every HW reset (every "device timeout"), and bwn_dma_free_descbufs() ++ * frees the RX mbuf WITHOUT clearing the DMA descriptor's physical ++ * address. The RX ring is persistent (it is refilled in place by ++ * bwn_rxeof() -> bwn_dma_newbuf(), never re-allocated), so after a ++ * reset the descriptor would still point at freed memory and the NIC ++ * would DMA received frames into freed kernel memory -> heap ++ * corruption (vmcore.6: TCP sb_mb NULL while sb_ccc != 0). Reclaiming ++ * the RX ring here also bought nothing: the RX ring holds no node ++ * reference, so it cannot cause the node double-free this fix targets. ++ * ++ * The actual TX-ring reclaim is factored into bwn_dma_reclaim_tx() ++ * so it can also be called from bwn_detach() before ieee80211_ifdetach(). ++ */ ++ bwn_dma_reclaim_tx(mac); + } + + static void +diff --git a/sys/modules/bwn/Makefile b/sys/modules/bwn/Makefile +index 37c7b35d..962aa268 100644 +--- a/sys/modules/bwn/Makefile ++++ b/sys/modules/bwn/Makefile +@@ -24,15 +24,33 @@ SRCS+= bhndb_bus_if.h \ + # Other + SRCS+= device_if.h bus_if.h gpio_if.h pci_if.h opt_bwn.h opt_wlan.h + +-# The following need the BWN_GPL_PHY kenrel option to opt-in +-# to the GPL'd 802.11n PHY support for this driver. ++# BWN_GPL_PHY: enable GPLv2 802.11n PHY support (required for N-PHY chips like BCM43224). ++# Without this, bwn(4) attaches but bwn_phy_n_attach() fails with: ++# "BWN_GPL_PHY not in kernel config; no PHY-N support". ++# NOTE: We define it here (not via the kernel config) because this is a ++# standalone module build; bsd.kmod.mk only honours SRCS. when is ++# present in KERN_OPTS, which it is not for out-of-tree module builds. ++opt_bwn.h: ++ @echo "#define BWN_GPL_PHY 1" > ${.TARGET} ++ ++# The following are the GPL'd 802.11n PHY support files for this driver. ++# They MUST be added unconditionally to SRCS (not via SRCS.BWN_GPL_PHY), ++# because a standalone module build does not populate KERN_OPTS with ++# BWN_GPL_PHY, so the SRCS. mechanism would skip them entirely. + .PATH: ${SRCTOP}/sys/gnu/dev/bwn/phy_n +-SRCS.BWN_GPL_PHY+= if_bwn_radio_2055.c +-SRCS.BWN_GPL_PHY+= if_bwn_radio_2056.c +-SRCS.BWN_GPL_PHY+= if_bwn_radio_2057.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_sprom.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_tables.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_ppr.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_core.c ++SRCS+= if_bwn_radio_2055.c ++SRCS+= if_bwn_radio_2056.c ++SRCS+= if_bwn_radio_2057.c ++SRCS+= if_bwn_phy_n_sprom.c ++SRCS+= if_bwn_phy_n_tables.c ++SRCS+= if_bwn_phy_n_ppr.c ++SRCS+= if_bwn_phy_n_core.c + + .include ++ ++# Temporary debug build ++CFLAGS+= -DBWN_DEBUG ++CFLAGS+= -DBWN_DEBUG ++ ++# Temporary debug build ++CFLAGS+= -DBWN_DEBUG diff --git a/patches/patch-Makefile b/patches/patch-Makefile new file mode 100644 index 0000000..1b5d054 --- /dev/null +++ b/patches/patch-Makefile @@ -0,0 +1,47 @@ +diff --git a/sys/modules/bwn/Makefile b/sys/modules/bwn/Makefile +index 37c7b35d..962aa268 100644 +--- a/sys/modules/bwn/Makefile ++++ b/sys/modules/bwn/Makefile +@@ -24,15 +24,33 @@ SRCS+= bhndb_bus_if.h \ + # Other + SRCS+= device_if.h bus_if.h gpio_if.h pci_if.h opt_bwn.h opt_wlan.h + +-# The following need the BWN_GPL_PHY kenrel option to opt-in +-# to the GPL'd 802.11n PHY support for this driver. ++# BWN_GPL_PHY: enable GPLv2 802.11n PHY support (required for N-PHY chips like BCM43224). ++# Without this, bwn(4) attaches but bwn_phy_n_attach() fails with: ++# "BWN_GPL_PHY not in kernel config; no PHY-N support". ++# NOTE: We define it here (not via the kernel config) because this is a ++# standalone module build; bsd.kmod.mk only honours SRCS. when is ++# present in KERN_OPTS, which it is not for out-of-tree module builds. ++opt_bwn.h: ++ @echo "#define BWN_GPL_PHY 1" > ${.TARGET} ++ ++# The following are the GPL'd 802.11n PHY support files for this driver. ++# They MUST be added unconditionally to SRCS (not via SRCS.BWN_GPL_PHY), ++# because a standalone module build does not populate KERN_OPTS with ++# BWN_GPL_PHY, so the SRCS. mechanism would skip them entirely. + .PATH: ${SRCTOP}/sys/gnu/dev/bwn/phy_n +-SRCS.BWN_GPL_PHY+= if_bwn_radio_2055.c +-SRCS.BWN_GPL_PHY+= if_bwn_radio_2056.c +-SRCS.BWN_GPL_PHY+= if_bwn_radio_2057.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_sprom.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_tables.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_ppr.c +-SRCS.BWN_GPL_PHY+= if_bwn_phy_n_core.c ++SRCS+= if_bwn_radio_2055.c ++SRCS+= if_bwn_radio_2056.c ++SRCS+= if_bwn_radio_2057.c ++SRCS+= if_bwn_phy_n_sprom.c ++SRCS+= if_bwn_phy_n_tables.c ++SRCS+= if_bwn_phy_n_ppr.c ++SRCS+= if_bwn_phy_n_core.c + + .include ++ ++# Temporary debug build ++CFLAGS+= -DBWN_DEBUG ++CFLAGS+= -DBWN_DEBUG ++ ++# Temporary debug build ++CFLAGS+= -DBWN_DEBUG diff --git a/patches/patch-bhndb_pci.c b/patches/patch-bhndb_pci.c new file mode 100644 index 0000000..5e6c5e1 --- /dev/null +++ b/patches/patch-bhndb_pci.c @@ -0,0 +1,35 @@ +diff --git a/sys/dev/bhnd/bhndb/bhndb_pci.c b/sys/dev/bhnd/bhndb/bhndb_pci.c +index 0cdcba1d..7b6ae0f3 100644 +--- a/sys/dev/bhnd/bhndb/bhndb_pci.c ++++ b/sys/dev/bhnd/bhndb/bhndb_pci.c +@@ -195,6 +195,16 @@ static struct bhndb_pci_quirk bhndb_pci_quirks[] = { + static struct bhndb_pci_quirk bhndb_pcie_quirks[] = { + /* All PCIe-G1 core revisions require the SRSH work-around */ + BHNDB_PCI_QUIRK(HWREV_ANY, BHNDB_PCI_QUIRK_SRSH_WAR), ++ ++ /* ++ * BCM43224 (and other Broadcom PCIe-G1 endpoints) do not have MSI ++ * delivered on some host platforms (e.g. NVIDIA MCP89 based Apple ++ * MacBook Air); force legacy INTx instead. ++ */ ++ { { BHND_MATCH_CHIP_ID(BCM43224) }, ++ { BHND_MATCH_ANY }, ++ BHNDB_PCI_QUIRK_NO_MSI }, ++ + BHNDB_PCI_QUIRK_END + }; + +@@ -327,6 +337,13 @@ bhndb_pci_alloc_msi(struct bhndb_pci_softc *sc, int *msi_count) + { + int error, count; + ++ /* MSI disabled by bridge quirk? */ ++ if (sc->pci_quirks & BHNDB_PCI_QUIRK_NO_MSI) { ++ device_printf(sc->dev, "MSI disabled by quirk; using INTx on " ++ "%s\n", device_get_nameunit(sc->parent)); ++ return (ENXIO); ++ } ++ + /* Is MSI available? */ + if (pci_msi_count(sc->parent) < BHNDB_PCI_MSI_COUNT) + return (ENXIO); diff --git a/patches/patch-bhndb_pcivar.h b/patches/patch-bhndb_pcivar.h new file mode 100644 index 0000000..74660c1 --- /dev/null +++ b/patches/patch-bhndb_pcivar.h @@ -0,0 +1,19 @@ +diff --git a/sys/dev/bhnd/bhndb/bhndb_pcivar.h b/sys/dev/bhnd/bhndb/bhndb_pcivar.h +index ed749518..7b473fa6 100644 +--- a/sys/dev/bhnd/bhndb/bhndb_pcivar.h ++++ b/sys/dev/bhnd/bhndb/bhndb_pcivar.h +@@ -70,6 +70,14 @@ enum { + * interrupt flags via the SIBA_CFG0_INTVEC register. + */ + BHNDB_PCI_QUIRK_SIBA_INTVEC = (1<<1), ++ ++ /** ++ * The PCI/PCIe bridge must not use MSI; instead, fall back to legacy ++ * INTx line interrupts. Required on some systems (e.g. NVIDIA MCP89 ++ * based Apple MacBook Air) where MSI messages from the Broadcom ++ * PCIe-G1 endpoint are never delivered, causing device timeouts. ++ */ ++ BHNDB_PCI_QUIRK_NO_MSI = (1<<2), + }; + + /** bhndb_pci quirk table entry */ diff --git a/patches/patch-if_bwn.c b/patches/patch-if_bwn.c new file mode 100644 index 0000000..ea20d8f --- /dev/null +++ b/patches/patch-if_bwn.c @@ -0,0 +1,237 @@ +diff --git a/sys/dev/bwn/if_bwn.c b/sys/dev/bwn/if_bwn.c +index 38bf6f5d..7a5b4230 100644 +--- a/sys/dev/bwn/if_bwn.c ++++ b/sys/dev/bwn/if_bwn.c +@@ -147,6 +147,7 @@ static int bwn_raw_xmit(struct ieee80211_node *, struct mbuf *, + const struct ieee80211_bpf_params *); + static void bwn_updateslot(struct ieee80211com *); + static void bwn_update_promisc(struct ieee80211com *); ++static void bwn_update_mcast(struct ieee80211com *); + static void bwn_wme_init(struct bwn_mac *); + static int bwn_wme_update(struct ieee80211com *); + static void bwn_wme_clear(struct bwn_softc *); +@@ -250,6 +251,7 @@ static void bwn_dma_setup(struct bwn_dma_ring *); + static void bwn_dma_free_ringmemory(struct bwn_dma_ring *); + static void bwn_dma_cleanup(struct bwn_dma_ring *); + static void bwn_dma_free_descbufs(struct bwn_dma_ring *); ++static void bwn_dma_reclaim_tx(struct bwn_mac *); + static int bwn_dma_tx_reset(struct bwn_mac *, uint16_t, int); + static void bwn_dma_rx(struct bwn_dma_ring *); + static int bwn_dma_rx_reset(struct bwn_mac *, uint16_t, int); +@@ -812,6 +814,7 @@ bwn_attach_post(struct bwn_softc *sc) + ic->ic_raw_xmit = bwn_raw_xmit; + ic->ic_updateslot = bwn_updateslot; + ic->ic_update_promisc = bwn_update_promisc; ++ ic->ic_update_mcast = bwn_update_mcast; + ic->ic_wme.wme_update = bwn_wme_update; + ic->ic_scan_start = bwn_scan_start; + ic->ic_scan_end = bwn_scan_end; +@@ -855,7 +858,6 @@ bwn_detach(device_t dev) + BWN_LOCK(sc); + bwn_stop(sc); + BWN_UNLOCK(sc); +- bwn_dma_free(mac); + callout_drain(&sc->sc_led_blink_ch); + callout_drain(&sc->sc_rfswitch_ch); + callout_drain(&sc->sc_task_ch); +@@ -863,7 +865,17 @@ bwn_detach(device_t dev) + bwn_phy_detach(mac); + ieee80211_draintask(ic, &mac->mac_hwreset); + ieee80211_draintask(ic, &mac->mac_txpower); ++ /* ++ * Drop any TX-ring node references BEFORE ieee80211_ifdetach() ++ * frees all nodes. bwn_dma_free()/detach would otherwise find a ++ * dangling node pointer (the ring slot still holds mt_ni for a ++ * frame whose TX completion never ran) and double-free it ++ * (vmcore.5). This is independent of mac_status: a frame can be ++ * queued in a ring without the MAC ever reaching STARTED. ++ */ ++ bwn_dma_reclaim_tx(mac); + ieee80211_ifdetach(ic); ++ bwn_dma_free(mac); + } + taskqueue_drain(sc->sc_tq, &mac->mac_intrtask); + taskqueue_free(sc->sc_tq); +@@ -1094,7 +1106,12 @@ bwn_pio_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, + tq = bwn_pio_select(mac, M_WME_GETAC(m)); + KASSERT(!TAILQ_EMPTY(&tq->tq_pktlist), ("%s: fail", __func__)); + tp = TAILQ_FIRST(&tq->tq_pktlist); +- tp->tp_ni = ni; ++ /* ++ * Take our own reference on the node (see bwn_dma_tx_start for the ++ * rationale); the PIO path otherwise shares the same use-after-free ++ * hazard as the DMA path. ++ */ ++ tp->tp_ni = ieee80211_ref_node(ni); + tp->tp_m = m; + + error = bwn_set_txhdr(mac, ni, m, &txhdr, BWN_PIO_COOKIE(tq, tp)); +@@ -1218,7 +1235,15 @@ bwn_dma_tx_start(struct bwn_mac *mac, struct ieee80211_node *ni, + KASSERT(mt->mt_txtype == BWN_DMADESC_METATYPE_BODY && + mt->mt_islast == 1, ("%s:%d: fail", __func__, __LINE__)); + mt->mt_m = m; +- mt->mt_ni = ni; ++ /* ++ * Take our own reference on the node. The mbuf's rcvif reference ++ * belongs to the net80211 stack and may be released (e.g. when the ++ * VAP is torn down or a scan is cancelled) while the frame is still ++ * pending in the TX ring. Without our own reference the slot would ++ * point at a freed node, and freeing it later (on TX completion or ++ * at detach) would double-free / use-after-free and panic. ++ */ ++ mt->mt_ni = ieee80211_ref_node(ni); + + error = bus_dmamap_load_mbuf(dma->txbuf_dtag, mt->mt_dmap, m, + bwn_dma_buf_addr, &mt->mt_paddr, BUS_DMA_NOWAIT); +@@ -1892,6 +1917,33 @@ bwn_update_promisc(struct ieee80211com *ic) + BWN_UNLOCK(sc); + } + ++static void ++bwn_update_mcast(struct ieee80211com *ic) ++{ ++ struct bwn_softc *sc = ic->ic_softc; ++ struct bwn_mac *mac; ++ ++ /* ++ * bwn has no hardware multicast hash filter; the only RX filter bit ++ * available for multicast is BWN_MACCTL_PROMISC (the same bit used by ++ * bwn_update_promisc). When any VAP needs all-multicast (e.g. for ++ * IPv6 ND/MLD), set PROMISC so the frames are passed up to net80211, ++ * which then does the multicast filtering in software. Without this ++ * callback net80211 uses the null stub and prints ++ * "need multicast update callback", and IPv6 multicast breaks. ++ */ ++ BWN_LOCK(sc); ++ mac = sc->sc_curmac; ++ if (mac != NULL && mac->mac_status >= BWN_MAC_STATUS_INITED) { ++ if (ic->ic_allmulti > 0) ++ sc->sc_filters |= BWN_MACCTL_PROMISC; ++ else ++ sc->sc_filters &= ~BWN_MACCTL_PROMISC; ++ bwn_set_opmode(mac); ++ } ++ BWN_UNLOCK(sc); ++} ++ + /* + * Callback from the 802.11 layer to update WME parameters. + */ +@@ -2999,7 +3051,17 @@ static void + bwn_dma_ringfree(struct bwn_dma_ring **dr) + { + +- if (dr == NULL) ++ /* ++ * bwn_dma_attach() sets BWN_MAC_FLAG_DMA early, before the individual ++ * rings (rx/wme[]/mcast) are allocated via bwn_dma_ringsetup(). If ++ * attach fails partway through (or the device is detached after a ++ * partial attach), bwn_dma_free() is still invoked because the flag is ++ * set, but the ring pointers are still NULL. Dereferencing them here ++ * faults (the crash in bwn_dma_ringfree+0x14c reading at offset 0x30, ++ * i.e. (*dr)->dr_mac). Guard against a NULL ring so teardown after a ++ * partial attach is safe. ++ */ ++ if (dr == NULL || *dr == NULL) + return; + + bwn_dma_free_descbufs(*dr); +@@ -3364,8 +3426,18 @@ bwn_dma_free_descbufs(struct bwn_dma_ring *dr) + struct bwn_softc *sc = mac->mac_sc; + int i; + +- if (!dr->dr_usedslot) +- return; ++ /* ++ * Do NOT skip this loop based on dr_usedslot. dr_usedslot is only an ++ * accounting hint for free-slot tracking and is not a reliable ++ * indicator of whether a frame (and its node reference) is pending; ++ * after a HW reset / ring re-init it can be inconsistent with the ++ * actual slot contents. We must unconditionally walk every slot and ++ * release any mbuf + node reference still attached, otherwise a ++ * dangling node pointer survives to bwn_dma_free()/detach and ++ * double-frees the node (vmcore.5). bwn_dma_free_descbuf() is ++ * idempotent (NULLs mt_m/mt_ni and skips empty slots), so walking ++ * all slots is safe even when none hold a frame. ++ */ + for (i = 0; i < dr->dr_numslots; i++) { + dr->getdesc(dr, i, &desc, &meta); + +@@ -3388,6 +3460,31 @@ bwn_dma_free_descbufs(struct bwn_dma_ring *dr) + } + } + ++static void ++bwn_dma_reclaim_tx(struct bwn_mac *mac) ++{ ++ struct bwn_dma *dma; ++ ++ if ((mac->mac_flags & BWN_MAC_FLAG_DMA) == 0) ++ return; ++ dma = &mac->mac_method.dma; ++ ++ /* ++ * Drop the mbuf + ieee80211_node references for any frame still ++ * pending in the TX rings. This must be called BEFORE ++ * ieee80211_ifdetach() (which frees all nodes) and is safe to ++ * call even if the MAC was never fully started, because a ++ * frame can be queued in a ring without the MAC reaching ++ * STARTED (e.g. the watchdog fired right after a transmit). ++ * See Fix 6/7 and vmcore.5 (node double-free) / vmcore.4. ++ */ ++ bwn_dma_free_descbufs(dma->wme[WME_AC_BK]); ++ bwn_dma_free_descbufs(dma->wme[WME_AC_BE]); ++ bwn_dma_free_descbufs(dma->wme[WME_AC_VI]); ++ bwn_dma_free_descbufs(dma->wme[WME_AC_VO]); ++ bwn_dma_free_descbufs(dma->mcast); ++} ++ + static int + bwn_dma_tx_reset(struct bwn_mac *mac, uint16_t base, + int type) +@@ -7349,6 +7446,45 @@ bwn_dma_stop(struct bwn_mac *mac) + bwn_dma_ringstop(&dma->wme[WME_AC_VI]); + bwn_dma_ringstop(&dma->wme[WME_AC_VO]); + bwn_dma_ringstop(&dma->mcast); ++ ++ /* ++ * Reclaim any frames still pending in the DMA rings. ++ * ++ * When TX never completes (e.g. interrupts are not delivered, which ++ * on this chip manifests as "device timeout"), the ring slots keep ++ * their mbuf + ieee80211_node references until a TX-completion ++ * callback (bwn_dma_handle_txeof) runs. bwn_dma_handle_txeof is the ++ * ONLY place that clears mt_ni, so without it the references linger ++ * here. If net80211 later frees that node (scan cancel / VAP ++ * teardown) while we still point at it, the reference becomes ++ * dangling; then bwn_dma_free() -> bwn_dma_ringfree() -> ++ * bwn_dma_free_descbufs() -> ieee80211_free_node() walks freed ++ * memory and panics (vmcore.4: page fault in node_cleanup / ++ * ieee80211_ageq_remove). ++ * ++ * Releasing the pending frames here, at device-stop time, drops the ++ * node references while the node is still alive (this runs before ++ * ieee80211_ifdetach() during detach and during every HW reset), ++ * so no dangling reference survives to detach. bwn_dma_free_descbufs ++ * is idempotent (it NULLs mt_m/mt_ni), so the later free at detach ++ * simply finds empty slots and skips them. ++ * ++ * IMPORTANT: only the TX rings (wme[] + mcast) are reclaimed here. ++ * The RX ring MUST NOT be reclaimed at stop time: bwn_dma_stop() runs ++ * on every HW reset (every "device timeout"), and bwn_dma_free_descbufs() ++ * frees the RX mbuf WITHOUT clearing the DMA descriptor's physical ++ * address. The RX ring is persistent (it is refilled in place by ++ * bwn_rxeof() -> bwn_dma_newbuf(), never re-allocated), so after a ++ * reset the descriptor would still point at freed memory and the NIC ++ * would DMA received frames into freed kernel memory -> heap ++ * corruption (vmcore.6: TCP sb_mb NULL while sb_ccc != 0). Reclaiming ++ * the RX ring here also bought nothing: the RX ring holds no node ++ * reference, so it cannot cause the node double-free this fix targets. ++ * ++ * The actual TX-ring reclaim is factored into bwn_dma_reclaim_tx() ++ * so it can also be called from bwn_detach() before ieee80211_ifdetach(). ++ */ ++ bwn_dma_reclaim_tx(mac); + } + + static void diff --git a/rc.conf.backups/loader.conf.before_bwn_20260816134238 b/rc.conf.backups/loader.conf.before_bwn_20260816134238 new file mode 100644 index 0000000..09f54ef --- /dev/null +++ b/rc.conf.backups/loader.conf.before_bwn_20260816134238 @@ -0,0 +1,5 @@ +hint.asmc.0.disabled="1" +acpi_video_load="YES" +acpi_ibm_load="YES" +backlight_load="YES" +snd_driver_load="YES" diff --git a/rc.conf.backups/rc.conf.before_bwn0_primary_20260816154658 b/rc.conf.backups/rc.conf.before_bwn0_primary_20260816154658 new file mode 100644 index 0000000..2111e92 --- /dev/null +++ b/rc.conf.backups/rc.conf.before_bwn0_primary_20260816154658 @@ -0,0 +1,12 @@ +clear_tmp_enable="YES" +hostname="luxair" +wlans_rtwn0="wlan0" +ifconfig_wlan0="WPA DHCP" +ifconfig_wlan0_ipv6="inet6 accept_rtadv" +sshd_enable="YES" +ntpd_enable="YES" +powerd_enable="YES" +moused_enable="YES" +# Set dumpdev to AUTO to enable crash dumps +dumpdev="AUTO" +kld_list="nouveau bwn_v4_n if_bwn" diff --git a/rc.conf.backups/rc.conf.before_bwn_20260816123013 b/rc.conf.backups/rc.conf.before_bwn_20260816123013 new file mode 100644 index 0000000..6a31d6e Binary files /dev/null and b/rc.conf.backups/rc.conf.before_bwn_20260816123013 differ diff --git a/rc.conf.backups/rc.conf.corrupted_20260816132245 b/rc.conf.backups/rc.conf.corrupted_20260816132245 new file mode 100644 index 0000000..6a31d6e Binary files /dev/null and b/rc.conf.backups/rc.conf.corrupted_20260816132245 differ diff --git a/rc.conf.backups/rc.conf.dup_20260816132300 b/rc.conf.backups/rc.conf.dup_20260816132300 new file mode 100644 index 0000000..c1ba64b --- /dev/null +++ b/rc.conf.backups/rc.conf.dup_20260816132300 @@ -0,0 +1,17 @@ +clear_tmp_enable="YES" +hostname="luxair" +wlans_rtwn0="wlan0" +ifconfig_wlan0="WPA DHCP" +ifconfig_wlan0_ipv6="inet6 accept_rtadv" +sshd_enable="YES" +ntpd_enable="YES" +powerd_enable="YES" +moused_enable="YES" +# Set dumpdev to "AUTO" to enable crash dumps, "NO" to disable +dumpdev="AUTO" + +kld_list="nouveau" + +# Broadcom BCM43224 (bwn) — switch primary Wi-Fi from rtwn0 USB dongle to internal bwn0 +wlans_bwn0="wlan0" +kld_list="nouveau bwn_v4_n if_bwn" diff --git a/src/firmware/Makefile b/src/firmware/Makefile new file mode 100644 index 0000000..b4894cb --- /dev/null +++ b/src/firmware/Makefile @@ -0,0 +1,9 @@ +KMOD= bwn_v4_n +FIRMWS= ucode16_mimo.fw:bwn_v4_ucode16_mimo \ + n0initvals11.fw:bwn_v4_n0initvals11 \ + n0bsinitvals11.fw:bwn_v4_n0bsinitvals11 \ + n0initvals16.fw:bwn_v4_n0initvals16 \ + n0bsinitvals16.fw:bwn_v4_n0bsinitvals16 \ + n0initvals24.fw:bwn_v4_n0initvals24 \ + n0bsinitvals24.fw:bwn_v4_n0bsinitvals24 +.include diff --git a/src/firmware/Makefile.orig b/src/firmware/Makefile.orig new file mode 100644 index 0000000..6199dad --- /dev/null +++ b/src/firmware/Makefile.orig @@ -0,0 +1,3 @@ +KMOD= bwn_v4_n +FIRMWS= ucode16_mimo.fw:bwn_v4_n_ucode16_mimo n0initvals11.fw:bwn_v4_n_n0initvals11 n0bsinitvals11.fw:bwn_v4_n_n0bsinitvals11 n0initvals16.fw:bwn_v4_n_n0initvals16 n0initvals24.fw:bwn_v4_n_n0initvals24 n0bsinitvals16.fw:bwn_v4_n_n0bsinitvals16 n0bsinitvals24.fw:bwn_v4_n_n0bsinitvals24 +.include diff --git a/src/firmware/bwn_v4_n.c b/src/firmware/bwn_v4_n.c new file mode 100644 index 0000000..12a75e0 --- /dev/null +++ b/src/firmware/bwn_v4_n.c @@ -0,0 +1,80 @@ +/* * Automatically @generated */ +#include +#include +#include +#include +#include +#include +#include + +extern char _binary_ucode16_mimo_fw_start[], _binary_ucode16_mimo_fw_end[]; +extern char _binary_n0initvals11_fw_start[], _binary_n0initvals11_fw_end[]; +extern char _binary_n0bsinitvals11_fw_start[], _binary_n0bsinitvals11_fw_end[]; +extern char _binary_n0initvals16_fw_start[], _binary_n0initvals16_fw_end[]; +extern char _binary_n0bsinitvals16_fw_start[], _binary_n0bsinitvals16_fw_end[]; +extern char _binary_n0initvals24_fw_start[], _binary_n0initvals24_fw_end[]; +extern char _binary_n0bsinitvals24_fw_start[], _binary_n0bsinitvals24_fw_end[]; + +static int +bwn_v4_n_fw_modevent(module_t mod, int type, void *unused){ const struct firmware *fp; + const struct firmware *parent; + int error; switch (type) { case MOD_LOAD: + + fp = firmware_register("bwn_v4_ucode16_mimo", _binary_ucode16_mimo_fw_start , (size_t)(_binary_ucode16_mimo_fw_end - _binary_ucode16_mimo_fw_start), 0, NULL); + if (fp == NULL) + goto fail_0; + parent = fp; + fp = firmware_register("bwn_v4_n0initvals11", _binary_n0initvals11_fw_start , (size_t)(_binary_n0initvals11_fw_end - _binary_n0initvals11_fw_start), 0, parent); + if (fp == NULL) + goto fail_1; + fp = firmware_register("bwn_v4_n0bsinitvals11", _binary_n0bsinitvals11_fw_start , (size_t)(_binary_n0bsinitvals11_fw_end - _binary_n0bsinitvals11_fw_start), 0, parent); + if (fp == NULL) + goto fail_2; + fp = firmware_register("bwn_v4_n0initvals16", _binary_n0initvals16_fw_start , (size_t)(_binary_n0initvals16_fw_end - _binary_n0initvals16_fw_start), 0, parent); + if (fp == NULL) + goto fail_3; + fp = firmware_register("bwn_v4_n0bsinitvals16", _binary_n0bsinitvals16_fw_start , (size_t)(_binary_n0bsinitvals16_fw_end - _binary_n0bsinitvals16_fw_start), 0, parent); + if (fp == NULL) + goto fail_4; + fp = firmware_register("bwn_v4_n0initvals24", _binary_n0initvals24_fw_start , (size_t)(_binary_n0initvals24_fw_end - _binary_n0initvals24_fw_start), 0, parent); + if (fp == NULL) + goto fail_5; + fp = firmware_register("bwn_v4_n0bsinitvals24", _binary_n0bsinitvals24_fw_start , (size_t)(_binary_n0bsinitvals24_fw_end - _binary_n0bsinitvals24_fw_start), 0, parent); + if (fp == NULL) + goto fail_6; + return (0); +fail_6: + (void)firmware_unregister("bwn_v4_n0initvals24"); +fail_5: + (void)firmware_unregister("bwn_v4_n0bsinitvals16"); +fail_4: + (void)firmware_unregister("bwn_v4_n0initvals16"); +fail_3: + (void)firmware_unregister("bwn_v4_n0bsinitvals11"); +fail_2: + (void)firmware_unregister("bwn_v4_n0initvals11"); +fail_1: + (void)firmware_unregister("bwn_v4_ucode16_mimo"); + fail_0: + return (ENXIO); + case MOD_UNLOAD: + error = firmware_unregister("bwn_v4_n0initvals11"); + if (error) + return (error); + error = firmware_unregister("bwn_v4_n0bsinitvals11"); + if (error) + return (error); + error = firmware_unregister("bwn_v4_n0initvals16"); + if (error) + return (error); + error = firmware_unregister("bwn_v4_n0bsinitvals16"); + if (error) + return (error); + error = firmware_unregister("bwn_v4_n0initvals24"); + if (error) + return (error); + error = firmware_unregister("bwn_v4_n0bsinitvals24"); + if (error) + return (error); + error = firmware_unregister("bwn_v4_ucode16_mimo"); + return (error); } return (EINVAL);}static moduledata_t bwn_v4_n_fw_mod = { "bwn_v4_n_fw", bwn_v4_n_fw_modevent, 0};DECLARE_MODULE(bwn_v4_n_fw, bwn_v4_n_fw_mod, SI_SUB_DRIVERS, SI_ORDER_FIRST);MODULE_VERSION(bwn_v4_n_fw, 1);MODULE_DEPEND(bwn_v4_n_fw, firmware, 1, 1, 1); diff --git a/src/firmware/n0bsinitvals11.fw b/src/firmware/n0bsinitvals11.fw new file mode 100644 index 0000000..3c49c05 Binary files /dev/null and b/src/firmware/n0bsinitvals11.fw differ diff --git a/src/firmware/n0bsinitvals16.fw b/src/firmware/n0bsinitvals16.fw new file mode 100644 index 0000000..6c5ee55 Binary files /dev/null and b/src/firmware/n0bsinitvals16.fw differ diff --git a/src/firmware/n0bsinitvals24.fw b/src/firmware/n0bsinitvals24.fw new file mode 100644 index 0000000..2c4211d Binary files /dev/null and b/src/firmware/n0bsinitvals24.fw differ diff --git a/src/firmware/n0initvals11.fw b/src/firmware/n0initvals11.fw new file mode 100644 index 0000000..f10855f Binary files /dev/null and b/src/firmware/n0initvals11.fw differ diff --git a/src/firmware/n0initvals16.fw b/src/firmware/n0initvals16.fw new file mode 100644 index 0000000..1b02e60 Binary files /dev/null and b/src/firmware/n0initvals16.fw differ diff --git a/src/firmware/n0initvals24.fw b/src/firmware/n0initvals24.fw new file mode 100644 index 0000000..9ebbc0b Binary files /dev/null and b/src/firmware/n0initvals24.fw differ diff --git a/src/firmware/ucode16_mimo.fw b/src/firmware/ucode16_mimo.fw new file mode 100644 index 0000000..cb21ae7 Binary files /dev/null and b/src/firmware/ucode16_mimo.fw differ