Files
bwn-a1370 a8418431bb 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)
2026-08-16 16:08:44 +03:00

238 lines
9.5 KiB
C

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