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:
@@ -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
|
||||
Reference in New Issue
Block a user