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)
This commit is contained in:
bwn-a1370
2026-08-16 16:08:44 +03:00
commit a8418431bb
26 changed files with 1168 additions and 0 deletions
+10
View File
@@ -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
+120
View File
@@ -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.
+133
View File
@@ -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.
Executable
+33
View File
@@ -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)."
Executable
+64
View File
@@ -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]"
+6
View File
@@ -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"
+338
View File
@@ -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.<OPT> when <OPT> 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.<OPT> 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 <bsd.kmod.mk>
+
+# Temporary debug build
+CFLAGS+= -DBWN_DEBUG
+CFLAGS+= -DBWN_DEBUG
+
+# Temporary debug build
+CFLAGS+= -DBWN_DEBUG
+47
View File
@@ -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.<OPT> when <OPT> 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.<OPT> 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 <bsd.kmod.mk>
+
+# Temporary debug build
+CFLAGS+= -DBWN_DEBUG
+CFLAGS+= -DBWN_DEBUG
+
+# Temporary debug build
+CFLAGS+= -DBWN_DEBUG
+35
View File
@@ -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);
+19
View File
@@ -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 */
+237
View File
@@ -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
@@ -0,0 +1,5 @@
hint.asmc.0.disabled="1"
acpi_video_load="YES"
acpi_ibm_load="YES"
backlight_load="YES"
snd_driver_load="YES"
@@ -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"
Binary file not shown.
Binary file not shown.
@@ -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"
+9
View File
@@ -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 <bsd.kmod.mk>
+3
View File
@@ -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 <bsd.kmod.mk>
+80
View File
@@ -0,0 +1,80 @@
/* * Automatically @generated */
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/linker.h>
#include <sys/firmware.h>
#include <sys/systm.h>
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);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.