339 lines
13 KiB
Diff
339 lines
13 KiB
Diff
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
|