From a8418431bb90de70a398d66ecc806bfc2137f70f Mon Sep 17 00:00:00 2001 From: bwn-a1370 Date: Sun, 16 Aug 2026 16:08:44 +0300 Subject: [PATCH] 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) --- .gitignore | 10 + README.md | 120 +++++++ RESEARCH.md | 133 +++++++ apply.sh | 33 ++ build.sh | 64 ++++ loader.conf.bwn | 6 + patches/combined.patch | 338 ++++++++++++++++++ patches/patch-Makefile | 47 +++ patches/patch-bhndb_pci.c | 35 ++ patches/patch-bhndb_pcivar.h | 19 + patches/patch-if_bwn.c | 237 ++++++++++++ .../loader.conf.before_bwn_20260816134238 | 5 + ...rc.conf.before_bwn0_primary_20260816154658 | 12 + .../rc.conf.before_bwn_20260816123013 | Bin 0 -> 314 bytes .../rc.conf.corrupted_20260816132245 | Bin 0 -> 314 bytes rc.conf.backups/rc.conf.dup_20260816132300 | 17 + src/firmware/Makefile | 9 + src/firmware/Makefile.orig | 3 + src/firmware/bwn_v4_n.c | 80 +++++ src/firmware/n0bsinitvals11.fw | Bin 0 -> 178 bytes src/firmware/n0bsinitvals16.fw | Bin 0 -> 178 bytes src/firmware/n0bsinitvals24.fw | Bin 0 -> 178 bytes src/firmware/n0initvals11.fw | Bin 0 -> 2152 bytes src/firmware/n0initvals16.fw | Bin 0 -> 2738 bytes src/firmware/n0initvals24.fw | Bin 0 -> 2976 bytes src/firmware/ucode16_mimo.fw | Bin 0 -> 39296 bytes 26 files changed, 1168 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 RESEARCH.md create mode 100755 apply.sh create mode 100755 build.sh create mode 100644 loader.conf.bwn create mode 100644 patches/combined.patch create mode 100644 patches/patch-Makefile create mode 100644 patches/patch-bhndb_pci.c create mode 100644 patches/patch-bhndb_pcivar.h create mode 100644 patches/patch-if_bwn.c create mode 100644 rc.conf.backups/loader.conf.before_bwn_20260816134238 create mode 100644 rc.conf.backups/rc.conf.before_bwn0_primary_20260816154658 create mode 100644 rc.conf.backups/rc.conf.before_bwn_20260816123013 create mode 100644 rc.conf.backups/rc.conf.corrupted_20260816132245 create mode 100644 rc.conf.backups/rc.conf.dup_20260816132300 create mode 100644 src/firmware/Makefile create mode 100644 src/firmware/Makefile.orig create mode 100644 src/firmware/bwn_v4_n.c create mode 100644 src/firmware/n0bsinitvals11.fw create mode 100644 src/firmware/n0bsinitvals16.fw create mode 100644 src/firmware/n0bsinitvals24.fw create mode 100644 src/firmware/n0initvals11.fw create mode 100644 src/firmware/n0initvals16.fw create mode 100644 src/firmware/n0initvals24.fw create mode 100644 src/firmware/ucode16_mimo.fw 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 0000000000000000000000000000000000000000..6a31d6e2f00682439761497bfd1f41bbe97de5f4 GIT binary patch literal 314 zcmZ8cF>b>!4D75|5bT;EQ#K7j8=zAgAPs^}f+{h!5LpsL%I@~cDXA9KSRPx~#v>)XqI zDVb1)jmD5jHej8jBG)ME2UN_RfBc=KESQo!+`{R>x-uWBm-`bpC34F ob31U=a(BDn72x}<`M^_k^>5U(OFi93U7|NJuxL!V4P+atKib>!4D75|5bT;EQ#K7j8=zAgAPs^}f+{h!5LpsL%I@~cDXA9KSRPx~#v>)XqI zDVb1)jmD5jHej8jBG)ME2UN_RfBc=KESQo!+`{R>x-uWBm-`bpC34F ob31U=a(BDn72x}<`M^_k^>5U(OFi93U7|NJuxL!V4P+atKi 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 0000000000000000000000000000000000000000..3c49c058455e29a81acd0e6b1b3f5fbd323de28c GIT binary patch literal 178 zcmc~~WB>tWwl;1*wgx*pwk9S!b_NC;wiYI##smgN24=<-h67Mm8Uq)Y#R3!)0khZ{ zQy81UEL9-u3y8)111P%y%1UEU0I?WP0mW>oJg} M0Tz1!XT1fn0Mi2`;s5{u literal 0 HcmV?d00001 diff --git a/src/firmware/n0bsinitvals16.fw b/src/firmware/n0bsinitvals16.fw new file mode 100644 index 0000000000000000000000000000000000000000..6c5ee558e854ee3ae8a871c7d309ac5c9922ad4c GIT binary patch literal 178 zcmc~~WB>tWwl>ZSYz=mHY)wpd>pGC7 M0T#OhXFUY50E|r|b^rhX literal 0 HcmV?d00001 diff --git a/src/firmware/n0bsinitvals24.fw b/src/firmware/n0bsinitvals24.fw new file mode 100644 index 0000000000000000000000000000000000000000..2c4211d97edb018ccb36347d7b7da3da352da5a6 GIT binary patch literal 178 zcmc~~WB>tWwl>ZSYz=mHY)wpd>R#ll$FMy0Aev-2a4H%S$7!I7)-#dM?f(HFzY!`Ob5(*3lxK} SJ^{rvz+yjuVi49}kQe~+l_eGc literal 0 HcmV?d00001 diff --git a/src/firmware/n0initvals11.fw b/src/firmware/n0initvals11.fw new file mode 100644 index 0000000000000000000000000000000000000000..f10855fc68784075346b605ee460b70b335c9f0e GIT binary patch literal 2152 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 literal 0 HcmV?d00001 diff --git a/src/firmware/ucode16_mimo.fw b/src/firmware/ucode16_mimo.fw new file mode 100644 index 0000000000000000000000000000000000000000..cb21ae7fa8e07332e5a150d9c991457d6866c6d9 GIT binary patch literal 39296 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 NnSmZ7cl}rY{vU+BRU`la literal 0 HcmV?d00001