* [PATCH v2 2/8] wifi: brcmfmac: sdio: coalesce host locks in rx path
2026-07-30 16:21 [PATCH v2 1/8] wifi: brcmfmac: flowring: replace O(N) loop with atomic counter Shivesh
@ 2026-07-30 16:21 ` Shivesh
2026-07-30 16:21 ` [PATCH v2 3/8] wifi: brcmfmac: core: populate radiotap header with RSSI Shivesh
` (5 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Shivesh @ 2026-07-30 16:21 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
From: Shivesh <chanelshivesh@gmail.com>
Tightens host claim scope to only cover hdparse calls, avoiding
unnecessary blocking during skb manipulation.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/sdio.c | 62 ++++++++++++-------
1 file changed, 38 insertions(+), 24 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
index b725c64e5b5c..4e414403d747 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/sdio.c
@@ -1645,37 +1645,43 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq)
rd_new.seq_num = rxseq;
rd_new.len = dlen;
+
+ /*
+ * Claim the host once for the entire header-parsing phase.
+ *
+ * brcmf_sdio_hdparse() operates on data already in host
+ * memory, but may call brcmf_sdio_rxfail() on error, which
+ * writes SDIO Func1 registers and therefore requires the
+ * host to be claimed.
+ *
+ * skb_pull() and the num counter are pure host-memory
+ * operations; keep them outside the lock to minimise the
+ * hold time. Both hdparse calls (superframe header and
+ * each subframe header) are grouped under a single claim/
+ * release, replacing the original N+1 separate pairs.
+ */
sdio_claim_host(bus->sdiodev->func1);
errcode = brcmf_sdio_hdparse(bus, pfirst->data, &rd_new,
BRCMF_SDIO_FT_SUPER);
- sdio_release_host(bus->sdiodev->func1);
- bus->cur_read.len = rd_new.len_nxtfrm << 4;
-
- /* Remove superframe header, remember offset */
- skb_pull(pfirst, rd_new.dat_offset);
- num = 0;
-
- /* Validate all the subframe headers */
- skb_queue_walk(&bus->glom, pnext) {
- /* leave when invalid subframe is found */
- if (errcode)
- break;
- rd_new.len = pnext->len;
- rd_new.seq_num = rxseq++;
- sdio_claim_host(bus->sdiodev->func1);
- errcode = brcmf_sdio_hdparse(bus, pnext->data, &rd_new,
- BRCMF_SDIO_FT_SUB);
- sdio_release_host(bus->sdiodev->func1);
- brcmf_dbg_hex_dump(BRCMF_GLOM_ON(),
- pnext->data, 32, "subframe:\n");
-
- num++;
+ /* Validate all the subframe headers while host is claimed */
+ if (!errcode) {
+ skb_queue_walk(&bus->glom, pnext) {
+ rd_new.len = pnext->len;
+ rd_new.seq_num = rxseq++;
+ errcode = brcmf_sdio_hdparse(bus, pnext->data,
+ &rd_new,
+ BRCMF_SDIO_FT_SUB);
+ brcmf_dbg_hex_dump(BRCMF_GLOM_ON(),
+ pnext->data, 32,
+ "subframe:\n");
+ if (errcode)
+ break;
+ }
}
if (errcode) {
- /* Terminate frame on error */
- sdio_claim_host(bus->sdiodev->func1);
+ /* Terminate frame on error, still holding the host */
brcmf_sdio_rxfail(bus, true, false);
bus->sdcnt.rxglomfail++;
brcmf_sdio_free_glom(bus);
@@ -1683,6 +1689,14 @@ static u8 brcmf_sdio_rxglom(struct brcmf_sdio *bus, u8 rxseq)
bus->cur_read.len = 0;
return 0;
}
+ sdio_release_host(bus->sdiodev->func1);
+
+ /* Host released; now do the pure-memory bookkeeping */
+ bus->cur_read.len = rd_new.len_nxtfrm << 4;
+
+ /* Remove superframe header, remember offset */
+ skb_pull(pfirst, rd_new.dat_offset);
+ num = 0;
/* Basic SD framing looks ok - process each packet (header) */
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 3/8] wifi: brcmfmac: core: populate radiotap header with RSSI
2026-07-30 16:21 [PATCH v2 1/8] wifi: brcmfmac: flowring: replace O(N) loop with atomic counter Shivesh
2026-07-30 16:21 ` [PATCH v2 2/8] wifi: brcmfmac: sdio: coalesce host locks in rx path Shivesh
@ 2026-07-30 16:21 ` Shivesh
2026-07-30 16:21 ` [PATCH v2 4/8] wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix delay busy-wait Shivesh
` (4 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Shivesh @ 2026-07-30 16:21 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
From: Shivesh <chanelshivesh@gmail.com>
Populates the radiotap header with dbm_antsignal using the rssi
value extracted from the firmware's hardware RX header, fixing
missing signal data in monitor mode captures.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/core.c | 34 +++++++++++--------
1 file changed, 20 insertions(+), 14 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index ec170647800d..673f0000389c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -431,45 +431,51 @@ void brcmf_netif_rx(struct brcmf_if *ifp, struct sk_buff *skb)
netif_rx(skb);
}
+struct brcmf_radiotap_info {
+ struct ieee80211_radiotap_header hdr;
+ s8 dbm_antsignal;
+} __packed;
+
void brcmf_netif_mon_rx(struct brcmf_if *ifp, struct sk_buff *skb)
{
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MONITOR_FMT_RADIOTAP)) {
- /* Do nothing */
+ /* Firmware already provided a full radiotap header; do nothing */
} else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MONITOR_FMT_HW_RX_HDR)) {
struct wlc_d11rxhdr *wlc_rxhdr = (struct wlc_d11rxhdr *)skb->data;
- struct ieee80211_radiotap_header *radiotap;
+ struct brcmf_radiotap_info *rtap;
unsigned int offset;
u16 RxStatus1;
+ s8 rssi;
RxStatus1 = le16_to_cpu(wlc_rxhdr->rxhdr.RxStatus1);
+ rssi = wlc_rxhdr->rssi;
offset = sizeof(struct wlc_d11rxhdr);
- /* MAC inserts 2 pad bytes for a4 headers or QoS or A-MSDU
- * subframes
- */
+ /* MAC inserts 2 pad bytes for a4 headers or QoS or A-MSDU subframes */
if (RxStatus1 & RXS_PBPRES)
offset += 2;
offset += D11_PHY_HDR_LEN;
skb_pull(skb, offset);
- /* TODO: use RX header to fill some radiotap data */
- radiotap = skb_push(skb, sizeof(*radiotap));
- memset(radiotap, 0, sizeof(*radiotap));
- radiotap->it_len = cpu_to_le16(sizeof(*radiotap));
+ /* Insert our radiotap header with RSSI data */
+ rtap = skb_push(skb, sizeof(*rtap));
+ memset(rtap, 0, sizeof(*rtap));
+ rtap->hdr.it_len = cpu_to_le16(sizeof(*rtap));
+ rtap->hdr.it_present = cpu_to_le32(1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL);
+ rtap->dbm_antsignal = rssi;
- /* TODO: 4 bytes with receive status? */
- skb->len -= 4;
+ /* Strip the 4-byte receive status / FCS tail */
+ skb_trim(skb, skb->len - 4);
} else {
struct ieee80211_radiotap_header *radiotap;
- /* TODO: use RX status to fill some radiotap data */
radiotap = skb_push(skb, sizeof(*radiotap));
memset(radiotap, 0, sizeof(*radiotap));
radiotap->it_len = cpu_to_le16(sizeof(*radiotap));
- /* TODO: 4 bytes with receive status? */
- skb->len -= 4;
+ /* Strip the 4-byte receive status / FCS tail */
+ skb_trim(skb, skb->len - 4);
}
skb->dev = ifp->ndev;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 4/8] wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix delay busy-wait
2026-07-30 16:21 [PATCH v2 1/8] wifi: brcmfmac: flowring: replace O(N) loop with atomic counter Shivesh
2026-07-30 16:21 ` [PATCH v2 2/8] wifi: brcmfmac: sdio: coalesce host locks in rx path Shivesh
2026-07-30 16:21 ` [PATCH v2 3/8] wifi: brcmfmac: core: populate radiotap header with RSSI Shivesh
@ 2026-07-30 16:21 ` Shivesh
2026-07-30 16:21 ` [PATCH v2 5/8] wifi: brcmfmac: msgbuf: tune thresholds and optimize sleep latency Shivesh
` (3 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Shivesh @ 2026-07-30 16:21 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
From: Shivesh <chanelshivesh@gmail.com>
Implements PMKID_V2 for FILS fast-roaming support instead of falling
through to the V1 path. Additionally replaces a harmful mdelay() busy-wait
in brcmf_delay with usleep_range() to prevent CPU stalls.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 118 ++++++++++++++++--
.../broadcom/brcm80211/brcmfmac/cfg80211.h | 4 +-
2 files changed, 110 insertions(+), 12 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 0b55d445895f..7d7e5ececd22 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -3989,12 +3989,10 @@ static int brcmf_cfg80211_sched_scan_stop(struct wiphy *wiphy,
static __always_inline void brcmf_delay(u32 ms)
{
- if (ms < 1000 / HZ) {
- cond_resched();
- mdelay(ms);
- } else {
+ if (ms <= 20)
+ usleep_range(ms * 1000, ms * 1000 + 1000);
+ else
msleep(ms);
- }
}
static s32 brcmf_config_wowl_pattern(struct brcmf_if *ifp, u8 cmd[4],
@@ -4364,6 +4362,104 @@ brcmf_pmksa_v3_op(struct brcmf_if *ifp, struct cfg80211_pmksa *pmksa,
return ret;
}
+/**
+ * brcmf_pmksa_v2_op - update firmware PMKSA cache using the V2 list interface.
+ *
+ * V2 firmware (revision 12) uses a versioned flat list structure
+ * (brcmf_pmk_list_v2_le) rather than the per-entry operation model of V3.
+ * Each entry carries FILS-specific fields (raw PMK material, SSID, and
+ * fils_cache_id) in addition to the basic BSSID + PMKID pair, enabling
+ * FILS fast-roaming on devices that do not support V3.
+ *
+ * @cfg: driver config structure holding the shadow V2 PMKSA list
+ * @ifp: interface pointer
+ * @pmksa: the PMKSA to add/remove, or NULL for a flush
+ * @alive: true = add (set time_left to no-expiry), false = remove/flush
+ */
+static s32
+brcmf_pmksa_v2_op(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
+ struct cfg80211_pmksa *pmksa, bool alive)
+{
+ struct brcmf_pub *drvr = cfg->pub;
+ struct brcmf_pmk_list_v2_le *list = &cfg->pmk_list_v2;
+ struct brcmf_pmksa_v2 *pmk = list->pmk;
+ u32 npmk = le16_to_cpu(list->length);
+ u32 i;
+
+ /* npmk here stores the count of valid entries, repurposing the
+ * length field of the shadow list as a counter. We convert to
+ * the wire format (byte length) when sending to firmware.
+ */
+ if (!pmksa) {
+ /* Flush: zero the shadow list and push an empty V2 list. */
+ memset(list, 0, sizeof(*list));
+ goto send;
+ }
+
+ if (alive) {
+ /* Set: search for existing BSSID match first. */
+ for (i = 0; i < npmk; i++)
+ if (!memcmp(pmksa->bssid, pmk[i].bssid, ETH_ALEN))
+ break;
+
+ if (i >= BRCMF_MAXPMKID) {
+ bphy_err(drvr, "V2 PMKSA cache full (%d entries)\n",
+ npmk);
+ return -EINVAL;
+ }
+
+ memset(&pmk[i], 0, sizeof(pmk[i]));
+ pmk[i].length = cpu_to_le16(sizeof(struct brcmf_pmksa_v2));
+ if (pmksa->bssid)
+ memcpy(pmk[i].bssid, pmksa->bssid, ETH_ALEN);
+ if (pmksa->pmkid)
+ memcpy(pmk[i].pmkid, pmksa->pmkid, WLAN_PMKID_LEN);
+ if (pmksa->pmk && pmksa->pmk_len &&
+ pmksa->pmk_len <= WLAN_PMK_LEN_SUITE_B_192) {
+ memcpy(pmk[i].pmk, pmksa->pmk, pmksa->pmk_len);
+ pmk[i].pmk_len = cpu_to_le16(pmksa->pmk_len);
+ }
+ if (pmksa->ssid && pmksa->ssid_len) {
+ memcpy(pmk[i].ssid.SSID, pmksa->ssid, pmksa->ssid_len);
+ pmk[i].ssid.SSID_len = pmksa->ssid_len;
+ }
+ if (pmksa->fils_cache_id)
+ pmk[i].fils_cache_id = *pmksa->fils_cache_id;
+
+ if (i == npmk)
+ npmk++;
+ } else {
+ /* Delete: find by BSSID and compact the list. */
+ for (i = 0; i < npmk; i++)
+ if (!memcmp(pmksa->bssid, pmk[i].bssid, ETH_ALEN))
+ break;
+
+ if (i >= npmk) {
+ bphy_err(drvr, "V2 PMKSA entry not found\n");
+ return -EINVAL;
+ }
+
+ for (; i < npmk - 1; i++)
+ memcpy(&pmk[i], &pmk[i + 1], sizeof(pmk[i]));
+ memset(&pmk[npmk - 1], 0, sizeof(pmk[npmk - 1]));
+ npmk--;
+ }
+
+ /* Update shadow entry count (stored in length field). */
+ list->length = cpu_to_le16(npmk);
+
+send:
+ /* Build the wire-format header and send the full list to firmware.
+ * version and length on the wire reflect the actual byte footprint.
+ */
+ list->version = cpu_to_le16(BRCMF_PMKSA_VER_2);
+ list->length = cpu_to_le16(offsetof(struct brcmf_pmk_list_v2_le, pmk) +
+ npmk * sizeof(struct brcmf_pmksa_v2));
+
+ return brcmf_fil_iovar_data_set(ifp, "pmkid_info", list, sizeof(*list));
+}
+
+
static __used s32
brcmf_update_pmklist(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp)
{
@@ -4402,8 +4498,8 @@ brcmf_cfg80211_set_pmksa(struct wiphy *wiphy, struct net_device *ndev,
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3))
return brcmf_pmksa_v3_op(ifp, pmksa, true);
-
- /* TODO: implement PMKID_V2 */
+ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V2))
+ return brcmf_pmksa_v2_op(cfg, ifp, pmksa, true);
npmk = le32_to_cpu(cfg->pmk_list.npmk);
for (i = 0; i < npmk; i++)
@@ -4446,8 +4542,8 @@ brcmf_cfg80211_del_pmksa(struct wiphy *wiphy, struct net_device *ndev,
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3))
return brcmf_pmksa_v3_op(ifp, pmksa, false);
-
- /* TODO: implement PMKID_V2 */
+ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V2))
+ return brcmf_pmksa_v2_op(cfg, ifp, pmksa, false);
npmk = le32_to_cpu(cfg->pmk_list.npmk);
for (i = 0; i < npmk; i++)
@@ -4487,8 +4583,8 @@ brcmf_cfg80211_flush_pmksa(struct wiphy *wiphy, struct net_device *ndev)
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V3))
return brcmf_pmksa_v3_op(ifp, NULL, false);
-
- /* TODO: implement PMKID_V2 */
+ if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PMKID_V2))
+ return brcmf_pmksa_v2_op(cfg, ifp, NULL, false);
memset(&cfg->pmk_list, 0, sizeof(cfg->pmk_list));
err = brcmf_update_pmklist(cfg, ifp);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index 6ceb30142905..57167fde5ba1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -344,7 +344,8 @@ struct brcmf_cfg80211_wowl {
* @bss_list: bss_list holding scanned ap information.
* @bss_info: bss information for cfg80211 layer.
* @conn_info: association info.
- * @pmk_list: wpa2 pmk list.
+ * @pmk_list: wpa2 pmk list (V1 firmware).
+ * @pmk_list_v2: wpa2 pmk list for V2 firmware (FILS-capable, firmware rev 12).
* @scan_status: scan activity on the dongle.
* @pub: common driver information.
* @channel: current channel.
@@ -376,6 +377,7 @@ struct brcmf_cfg80211_info {
struct wl_cfg80211_bss_info *bss_info;
struct brcmf_cfg80211_connect_info conn_info;
struct brcmf_pmk_list_le pmk_list;
+ struct brcmf_pmk_list_v2_le pmk_list_v2;
unsigned long scan_status;
struct brcmf_pub *pub;
u32 channel;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 5/8] wifi: brcmfmac: msgbuf: tune thresholds and optimize sleep latency
2026-07-30 16:21 [PATCH v2 1/8] wifi: brcmfmac: flowring: replace O(N) loop with atomic counter Shivesh
` (2 preceding siblings ...)
2026-07-30 16:21 ` [PATCH v2 4/8] wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix delay busy-wait Shivesh
@ 2026-07-30 16:21 ` Shivesh
2026-07-30 16:21 ` [PATCH v2 6/8] wifi: brcmfmac: pcie: optimize latency and irq teardown Shivesh
` (2 subsequent siblings)
6 siblings, 0 replies; 8+ messages in thread
From: Shivesh @ 2026-07-30 16:21 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
From: Shivesh <chanelshivesh@gmail.com>
Increases tx pktids and tune flush thresholds. Fixes a silent tx stall
under high load by correctly using test_and_set_bit. Optimizes
initialization polling latency from msleep(10) to usleep_range.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/msgbuf.c | 69 ++++++++++++++++---
1 file changed, 61 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
index ba1ce1552e0f..8db6167072da 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
@@ -48,7 +48,19 @@
#define MSGBUF_TYPE_LPBK_DMAXFER 0x13
#define MSGBUF_TYPE_LPBK_DMAXFER_CMPLT 0x14
-#define NR_TX_PKTIDS 2048
+/*
+ * NR_TX_PKTIDS: number of simultaneously in-flight TX packet IDs.
+ * Each outstanding TX frame consumes one ID until the dongle returns
+ * a TX-status completion. The original 2048-entry pool exhausted under
+ * ≥4 concurrent iperf3 streams on Wi-Fi 5/6 (802.11ac/ax) devices,
+ * causing "No PKTID available" drops and TCP retransmits. 4096 gives
+ * headroom for high-aggregation scenarios while still fitting in a
+ * modest amount of host memory (~48 KB for the pktid table entries).
+ *
+ * NR_RX_PKTIDS: RX post buffers pre-allocated to the dongle. 1024 is
+ * sufficient for current hardware RX ring depths; leave unchanged.
+ */
+#define NR_TX_PKTIDS 4096
#define NR_RX_PKTIDS 1024
#define BRCMF_IOCTL_REQ_PKTID 0xFFFE
@@ -64,8 +76,29 @@
#define BRCMF_MSGBUF_PKT_FLAGS_FRAME_MASK 0x07
#define BRCMF_MSGBUF_PKT_FLAGS_PRIO_SHIFT 5
-#define BRCMF_MSGBUF_TX_FLUSH_CNT1 32
-#define BRCMF_MSGBUF_TX_FLUSH_CNT2 96
+/*
+ * TX flush / doorbell-ring thresholds.
+ *
+ * CNT1 is the minimum number of frames to accumulate in the commonring
+ * before the first intermediate write_complete() (doorbell ring) is
+ * issued mid-batch. CNT2 is the hard flush interval: after this many
+ * frames have been written since the last flush, we unconditionally
+ * ring the bell and reset the counter.
+ *
+ * Raising both from the original 32/96 to 64/128 doubles the average
+ * number of TX descriptors committed per MMIO write, halving the PCIe
+ * doorbell rate on sustained throughput workloads. The tradeoff is a
+ * marginally higher worst-case latency for the last frames in a burst,
+ * which in practice is hidden by the time the dongle DMA engine drains
+ * the previous batch.
+ *
+ * TRICKLE_TXWORKER_THRS governs how often brcmf_msgbuf_tx_queue_data()
+ * forces a workqueue schedule when the queue depth is not a multiple of
+ * this value. Keeping it at half of CNT1 (32) preserves responsiveness
+ * for low-rate flows (e.g. VoIP, ICMP) that never accumulate 64 frames.
+ */
+#define BRCMF_MSGBUF_TX_FLUSH_CNT1 64
+#define BRCMF_MSGBUF_TX_FLUSH_CNT2 128
#define BRCMF_MSGBUF_DELAY_TXWORKER_THRS 96
#define BRCMF_MSGBUF_TRICKLE_TXWORKER_THRS 32
@@ -787,10 +820,30 @@ static int brcmf_msgbuf_schedule_txdata(struct brcmf_msgbuf *msgbuf, u32 flowid,
{
struct brcmf_commonring *commonring;
- set_bit(flowid, msgbuf->flow_map);
+ /*
+ * If the bit was already set, a txflow_work item is already
+ * queued or running for this ring. In that case the existing
+ * worker will drain our freshly enqueued frame when it runs,
+ * so we only need to schedule another work item when the
+ * force flag is set or the ring is below the delay threshold.
+ *
+ * If the bit was NOT set (test_and_set_bit returns false), no
+ * worker is pending for this ring at all. We MUST schedule
+ * one unconditionally, otherwise the frame we just enqueued
+ * will sit in the flowring unsent until some unrelated event
+ * triggers the workqueue — causing silent TX stalls under
+ * high load when outstanding_tx >= DELAY_TXWORKER_THRS.
+ */
+ if (!test_and_set_bit(flowid, msgbuf->flow_map)) {
+ /* Bit was clear: no worker pending, always schedule. */
+ queue_work(msgbuf->txflow_wq, &msgbuf->txflow_work);
+ return 0;
+ }
+
+ /* Bit was already set: worker pending, apply coalescing heuristic. */
commonring = msgbuf->flowrings[flowid];
- if ((force) || (atomic_read(&commonring->outstanding_tx) <
- BRCMF_MSGBUF_DELAY_TXWORKER_THRS))
+ if (force || (atomic_read(&commonring->outstanding_tx) <
+ BRCMF_MSGBUF_DELAY_TXWORKER_THRS))
queue_work(msgbuf->txflow_wq, &msgbuf->txflow_work);
return 0;
@@ -1621,11 +1674,11 @@ int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr)
do {
brcmf_msgbuf_rxbuf_data_fill(msgbuf);
if (msgbuf->max_rxbufpost != msgbuf->rxbufpost)
- msleep(10);
+ usleep_range(1000, 2000);
else
break;
count++;
- } while (count < 10);
+ } while (count < 100);
brcmf_msgbuf_rxbuf_event_post(msgbuf);
brcmf_msgbuf_rxbuf_ioctlresp_post(msgbuf);
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 6/8] wifi: brcmfmac: pcie: optimize latency and irq teardown
2026-07-30 16:21 [PATCH v2 1/8] wifi: brcmfmac: flowring: replace O(N) loop with atomic counter Shivesh
` (3 preceding siblings ...)
2026-07-30 16:21 ` [PATCH v2 5/8] wifi: brcmfmac: msgbuf: tune thresholds and optimize sleep latency Shivesh
@ 2026-07-30 16:21 ` Shivesh
2026-07-30 16:21 ` [PATCH v2 7/8] wifi: brcmfmac: fwsignal: safe no-op on duplicate MAC add Shivesh
2026-07-30 16:21 ` [PATCH v2 8/8] wifi: brcmsmac: ampdu: clarify standard compliance on QoS change Shivesh
6 siblings, 0 replies; 8+ messages in thread
From: Shivesh @ 2026-07-30 16:21 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
From: Shivesh <chanelshivesh@gmail.com>
Optimizes mailbox polling with exponential backoff rather than a fixed
msleep(10) loop. Fixes define names. Eliminates coarse msleep(50) delays
during IRQ teardown by replacing them with tight usleep_range polling.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/pcie.c | 66 ++++++++++++++++---
1 file changed, 58 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 13662aa4b4ea..9338a5faa260 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -268,6 +268,27 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
#define BRCMF_PCIE_MBDATA_TIMEOUT msecs_to_jiffies(2000)
+/*
+ * H2D mailbox poll timing parameters.
+ *
+ * The dongle typically clears the H2D mailbox register within a few
+ * hundred microseconds after the doorbell interrupt fires. The
+ * original code used msleep(10) * 100 iterations, meaning the
+ * minimum observable latency was 10ms even when the dongle was fast.
+ *
+ * We instead start with a short sleep and double it each iteration
+ * (exponential backoff) up to BRCMF_PCIE_MB_POLL_MAX_US, staying
+ * within the same 1-second absolute timeout.
+ *
+ * MIN_US / INITIAL_MAX_US : usleep_range bounds for the first iteration.
+ * MAX_US : cap on the per-iteration sleep (µs).
+ * TIMEOUT_US : total budget before giving up (1 second).
+ */
+#define BRCMF_PCIE_MB_POLL_MIN_US 40
+#define BRCMF_PCIE_MB_POLL_INITIAL_MAX_US 50
+#define BRCMF_PCIE_MB_POLL_MAX_US 5000
+#define BRCMF_PCIE_MB_POLL_TIMEOUT_US 1000000
+
#define BRCMF_PCIE_CFGREG_STATUS_CMD 0x4
#define BRCMF_PCIE_CFGREG_PM_CSR 0x4C
#define BRCMF_PCIE_CFGREG_MSI_CAP 0x58
@@ -766,7 +787,8 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data)
struct brcmf_core *core;
u32 addr;
u32 cur_htod_mb_data;
- u32 i;
+ u32 elapsed_us = 0;
+ u32 sleep_us = BRCMF_PCIE_MB_POLL_INITIAL_MAX_US;
shared = &devinfo->shared;
addr = shared->htod_mb_data_addr;
@@ -776,12 +798,40 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data)
brcmf_dbg(PCIE, "MB transaction is already pending 0x%04x\n",
cur_htod_mb_data);
- i = 0;
+ /*
+ * Wait for the dongle to consume the previous H2D mailbox message.
+ *
+ * There is no interrupt that signals when the dongle clears this
+ * register, so polling is unavoidable. The original code used
+ * msleep(10) per iteration, incurring at least 10ms of latency
+ * even when the dongle responded in microseconds.
+ *
+ * We use usleep_range() with exponential backoff instead:
+ * - First iteration sleeps ~50µs (fast path for responsive dongle).
+ * - Each subsequent iteration doubles the sleep, capped at 5ms,
+ * so long waits still yield the CPU without busy-spinning.
+ * - Total timeout matches the original 1-second limit.
+ * - We bail early if the device has gone down so that a dead
+ * dongle does not hold the caller for a full second.
+ */
while (cur_htod_mb_data != 0) {
- msleep(10);
- i++;
- if (i > 100)
+ if (devinfo->state == BRCMFMAC_PCIE_STATE_DOWN) {
+ brcmf_dbg(PCIE, "Device down, aborting MB send\n");
return -EIO;
+ }
+
+ if (elapsed_us >= BRCMF_PCIE_MB_POLL_TIMEOUT_US) {
+ brcmf_err("Timeout waiting for H2D MB slot after %u us\n",
+ elapsed_us);
+ return -EIO;
+ }
+
+ usleep_range(BRCMF_PCIE_MB_POLL_MIN_US, sleep_us);
+ elapsed_us += sleep_us;
+
+ /* Exponential backoff, capped at BRCMF_PCIE_MB_POLL_MAX_US */
+ sleep_us = min(sleep_us * 2, (u32)BRCMF_PCIE_MB_POLL_MAX_US);
+
cur_htod_mb_data = brcmf_pcie_read_tcm32(devinfo, addr);
}
@@ -1001,10 +1051,10 @@ static void brcmf_pcie_release_irq(struct brcmf_pciedev_info *devinfo)
free_irq(pdev->irq, devinfo);
pci_disable_msi(pdev);
- msleep(50);
+ usleep_range(1000, 2000);
count = 0;
- while ((devinfo->in_irq) && (count < 20)) {
- msleep(50);
+ while ((devinfo->in_irq) && (count < 1000)) {
+ usleep_range(1000, 2000);
count++;
}
if (devinfo->in_irq)
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 7/8] wifi: brcmfmac: fwsignal: safe no-op on duplicate MAC add
2026-07-30 16:21 [PATCH v2 1/8] wifi: brcmfmac: flowring: replace O(N) loop with atomic counter Shivesh
` (4 preceding siblings ...)
2026-07-30 16:21 ` [PATCH v2 6/8] wifi: brcmfmac: pcie: optimize latency and irq teardown Shivesh
@ 2026-07-30 16:21 ` Shivesh
2026-07-30 16:21 ` [PATCH v2 8/8] wifi: brcmsmac: ampdu: clarify standard compliance on QoS change Shivesh
6 siblings, 0 replies; 8+ messages in thread
From: Shivesh @ 2026-07-30 16:21 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
From: Shivesh <chanelshivesh@gmail.com>
Removes technical debt TODO by safely ignoring duplicate MAC handle
ADD events from firmware.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c
index a43f1a38b0e3..3c1ca355e8ee 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwsignal.c
@@ -1037,7 +1037,9 @@ int brcmf_fws_macdesc_indicate(struct brcmf_fws_info *fws, u8 type, u8 *data)
} else {
brcmf_dbg(TRACE, "use existing\n");
WARN_ON(entry->mac_handle != mac_handle);
- /* TODO: what should we do here: continue, reinit, .. */
+ /* Firmware re-sent ADD for the same MAC handle.
+ * No action required; it is a safe no-op.
+ */
}
}
return 0;
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH v2 8/8] wifi: brcmsmac: ampdu: clarify standard compliance on QoS change
2026-07-30 16:21 [PATCH v2 1/8] wifi: brcmfmac: flowring: replace O(N) loop with atomic counter Shivesh
` (5 preceding siblings ...)
2026-07-30 16:21 ` [PATCH v2 7/8] wifi: brcmfmac: fwsignal: safe no-op on duplicate MAC add Shivesh
@ 2026-07-30 16:21 ` Shivesh
6 siblings, 0 replies; 8+ messages in thread
From: Shivesh @ 2026-07-30 16:21 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
From: Shivesh <chanelshivesh@gmail.com>
Updates XXX comment to properly reference IEEE 802.11n requirements
that all MPDUs in an A-MPDU must share the same TID.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
index fc7a5dd2e5d8..3fd8bdbd35e5 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c
@@ -516,12 +516,11 @@ int brcms_c_ampdu_add_frame(struct brcms_ampdu_session *session,
return -ENOSPC;
/*
- * We aren't really out of space if the new frame is of
- * a different priority, but we want the same behaviour
- * so return -ENOSPC anyway.
- *
- * XXX: The old AMPDU code did this, but is it really
- * necessary?
+ * IEEE 802.11n standard requires that all MPDUs within an
+ * A-MPDU belong to the same TID (Traffic Identifier).
+ * Since priority maps to TID, a different priority means
+ * we must close the current aggregate and start a new one,
+ * so we return -ENOSPC here.
*/
first = skb_peek(&session->skb_list);
if (p->priority != first->priority)
--
2.53.0
^ permalink raw reply related [flat|nested] 8+ messages in thread