* [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes
@ 2026-07-31 16:06 Shivesh
2026-07-31 16:06 ` [PATCH v4 1/8] wifi: brcmfmac: flowring: replace O(N) blocked-ring scan with atomic counter Shivesh
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
This series fixes several performance bottlenecks and correctness
issues in both brcmfmac (PCIe/SDIO) and brcmsmac drivers.
Changes since v3:
- cfg80211.c: Removed spurious double blank line flagged by
checkpatch --strict.
- All commit messages rewritten to follow kernel standards:
explain the problem symptom first, then the solution, add
Fixes: tags where applicable, wrap lines at 72 chars.
- pcie.c: Added Fixes: tag referencing the original PCIe commit.
Changes since v2:
- core.c: Add skb_cow_head() before skb_push() to prevent a
kernel panic when skb arrives with zero headroom.
- cfg80211.c: Fix PMKID V2 shadow counter corruption bug.
Changes since v1:
- Correct author and Signed-off-by to chanelshivesh@gmail.com.
Patch overview:
1/8 flowring: Replace O(N) blocked-ring scan with atomic counter.
2/8 sdio: Coalesce N+1 sdio_claim_host() pairs in rxglom path.
3/8 core: Fix skb_push() panic; populate radiotap RSSI field.
4/8 cfg80211: Implement PMKID_V2; fix shadow counter overwrite;
replace mdelay() with usleep_range().
5/8 msgbuf: Fix silent TX stall; increase NR_TX_PKTIDS to 4096.
6/8 pcie: Replace msleep() with usleep_range() + backoff.
7/8 fwsignal: Document safe no-op for duplicate MAC handle ADD.
8/8 brcmsmac/ampdu: Reference IEEE 802.11n TID requirement.
Shivesh (8):
wifi: brcmfmac: flowring: replace O(N) blocked-ring scan with atomic
counter
wifi: brcmfmac: sdio: coalesce sdio_claim_host calls in rxglom path
wifi: brcmfmac: core: fix missing headroom check and populate radiotap
RSSI
wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix brcmf_delay
busy-wait
wifi: brcmfmac: msgbuf: fix TX stall and tune buffer/threshold
constants
wifi: brcmfmac: pcie: replace msleep polling with usleep_range and
backoff
wifi: brcmfmac: fwsignal: document safe no-op for duplicate MAC handle
ADD
wifi: brcmsmac: ampdu: document IEEE 802.11n TID requirement
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 122 ++++++++++++++++--
.../broadcom/brcm80211/brcmfmac/cfg80211.h | 4 +-
.../broadcom/brcm80211/brcmfmac/core.c | 44 ++++---
.../broadcom/brcm80211/brcmfmac/flowring.c | 65 +++++++---
.../broadcom/brcm80211/brcmfmac/flowring.h | 18 +++
.../broadcom/brcm80211/brcmfmac/fwsignal.c | 4 +-
.../broadcom/brcm80211/brcmfmac/msgbuf.c | 69 ++++++++--
.../broadcom/brcm80211/brcmfmac/pcie.c | 66 ++++++++--
.../broadcom/brcm80211/brcmfmac/sdio.c | 62 +++++----
.../broadcom/brcm80211/brcmsmac/ampdu.c | 11 +-
10 files changed, 374 insertions(+), 91 deletions(-)
base-commit: 5e0bcb4920bcb9e0ef6c03a91730fe45f643f3a4
--
2.53.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v4 1/8] wifi: brcmfmac: flowring: replace O(N) blocked-ring scan with atomic counter
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
@ 2026-07-31 16:06 ` Shivesh
2026-07-31 16:06 ` [PATCH v4 2/8] wifi: brcmfmac: sdio: coalesce sdio_claim_host calls in rxglom path Shivesh
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
brcmf_flowring_block() previously determined whether any sibling ring
was already blocked by walking all nrofrings entries under block_lock.
This O(N) scan is both slow and incomplete: a ring that transitions
from RING_OPEN to RING_CLOSING while blocked causes the unblock path
to skip the decrement (because ring->status is no longer RING_OPEN),
leaking the implicit "someone is blocked" state and permanently
stopping the netif queue for that interface.
Replace the per-call O(N) walk with a per-interface atomic counter
if_blocked_cnt[BRCMF_MAX_IFS]. Introduce a per-ring boolean
counted_in_blocked that records whether the ring has been added to
the counter, so the matching decrement is always applied regardless
of the ring status at unblock time.
The decision to stop or wake the netif queue is now a simple
atomic_read() check, still performed under block_lock to prevent
races between concurrent brcmf_flowring_block() callers.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/flowring.c | 65 ++++++++++++++-----
.../broadcom/brcm80211/brcmfmac/flowring.h | 18 +++++
2 files changed, 66 insertions(+), 17 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.c
index 35cbcea0abc9..b9c518939f50 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.c
@@ -182,10 +182,8 @@ static void brcmf_flowring_block(struct brcmf_flowring *flow, u16 flowid,
struct brcmf_bus *bus_if;
struct brcmf_pub *drvr;
struct brcmf_if *ifp;
- bool currently_blocked;
- int i;
- u8 ifidx;
unsigned long flags;
+ u8 ifidx;
spin_lock_irqsave(&flow->block_lock, flags);
@@ -194,23 +192,54 @@ static void brcmf_flowring_block(struct brcmf_flowring *flow, u16 flowid,
spin_unlock_irqrestore(&flow->block_lock, flags);
return;
}
- ifidx = brcmf_flowring_ifidx_get(flow, flowid);
- currently_blocked = false;
- for (i = 0; i < flow->nrofrings; i++) {
- if ((flow->rings[i]) && (i != flowid)) {
- ring = flow->rings[i];
- if ((ring->status == RING_OPEN) &&
- (brcmf_flowring_ifidx_get(flow, i) == ifidx)) {
- if (ring->blocked) {
- currently_blocked = true;
- break;
- }
- }
+ ifidx = brcmf_flowring_ifidx_get(flow, flowid);
+ ring->blocked = blocked;
+
+ /*
+ * Maintain the per-interface blocked-ring counter.
+ *
+ * We use ring->counted_in_blocked rather than checking
+ * ring->status here. A ring that became blocked while
+ * RING_OPEN has already been counted (counted_in_blocked=true).
+ * By the time we unblock it during teardown its status may have
+ * advanced to RING_CLOSING, so testing RING_OPEN would wrongly
+ * skip the atomic_dec and permanently leak the counter, leaving
+ * the netif queue stopped forever.
+ *
+ * Rule:
+ * block transition (unblocked→blocked): count only if RING_OPEN,
+ * set counted_in_blocked.
+ * unblock transition (blocked→unblocked): decrement only if we
+ * previously counted it,
+ * clear counted_in_blocked.
+ */
+ if (blocked) {
+ if (ring->status == RING_OPEN) {
+ atomic_inc(&flow->if_blocked_cnt[ifidx]);
+ ring->counted_in_blocked = true;
}
+ } else {
+ if (ring->counted_in_blocked) {
+ atomic_dec(&flow->if_blocked_cnt[ifidx]);
+ ring->counted_in_blocked = false;
+ }
+ }
+
+ /*
+ * Only propagate a netif queue-stop/wake when the interface
+ * transitions between fully-clear and at-least-one-blocked.
+ * Reading the atomic is safe here: we hold block_lock, so no
+ * concurrent brcmf_flowring_block() call can race the update
+ * we just made above.
+ */
+ if (blocked && atomic_read(&flow->if_blocked_cnt[ifidx]) != 1) {
+ /* Another ring was already blocked; no new queue-stop needed. */
+ spin_unlock_irqrestore(&flow->block_lock, flags);
+ return;
}
- flow->rings[flowid]->blocked = blocked;
- if (currently_blocked) {
+ if (!blocked && atomic_read(&flow->if_blocked_cnt[ifidx]) != 0) {
+ /* More rings still blocked; do not wake the queue yet. */
spin_unlock_irqrestore(&flow->block_lock, flags);
return;
}
@@ -367,6 +396,8 @@ struct brcmf_flowring *brcmf_flowring_attach(struct device *dev, u16 nrofrings)
spin_lock_init(&flow->block_lock);
for (i = 0; i < ARRAY_SIZE(flow->addr_mode); i++)
flow->addr_mode[i] = ADDR_INDIRECT;
+ for (i = 0; i < ARRAY_SIZE(flow->if_blocked_cnt); i++)
+ atomic_set(&flow->if_blocked_cnt[i], 0);
for (i = 0; i < ARRAY_SIZE(flow->hash); i++)
flow->hash[i].ifidx = BRCMF_FLOWRING_INVALID_IFIDX;
}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.h
index f3d511f9a3c9..afdea8b3f8aa 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/flowring.h
@@ -5,6 +5,8 @@
#ifndef BRCMFMAC_FLOWRING_H
#define BRCMFMAC_FLOWRING_H
+#include <linux/atomic.h>
+
#define BRCMF_FLOWRING_HASHSIZE 512 /* has to be 2^x */
#define BRCMF_FLOWRING_INVALID_ID 0xFFFFFFFF
@@ -26,6 +28,16 @@ enum ring_status {
struct brcmf_flowring_ring {
u16 hash_id;
bool blocked;
+ /*
+ * True when this ring has been counted in the per-interface
+ * if_blocked_cnt[]. Set to true whenever the ring transitions
+ * unblocked→blocked while RING_OPEN; cleared on the matching
+ * blocked→unblocked transition. Needed so that a ring that
+ * becomes blocked while RING_OPEN and is later moved to
+ * RING_CLOSING still correctly decrements the counter at
+ * teardown, even though its status is no longer RING_OPEN.
+ */
+ bool counted_in_blocked;
enum ring_status status;
struct sk_buff_head skblist;
};
@@ -40,6 +52,12 @@ struct brcmf_flowring {
struct brcmf_flowring_hash hash[BRCMF_FLOWRING_HASHSIZE];
spinlock_t block_lock;
enum proto_addr_mode addr_mode[BRCMF_MAX_IFS];
+ /* Per-interface count of currently blocked open rings.
+ * Maintained atomically so brcmf_flowring_block() can check
+ * whether any sibling ring is already blocked in O(1) without
+ * holding block_lock across an O(nrofrings) walk.
+ */
+ atomic_t if_blocked_cnt[BRCMF_MAX_IFS];
u16 nrofrings;
bool tdls_active;
struct brcmf_flowring_tdls_entry *tdls_entry;
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v4 2/8] wifi: brcmfmac: sdio: coalesce sdio_claim_host calls in rxglom path
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
2026-07-31 16:06 ` [PATCH v4 1/8] wifi: brcmfmac: flowring: replace O(N) blocked-ring scan with atomic counter Shivesh
@ 2026-07-31 16:06 ` Shivesh
2026-07-31 16:06 ` [PATCH v4 3/8] wifi: brcmfmac: core: fix missing headroom check and populate radiotap RSSI Shivesh
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
brcmf_sdio_rxglom() performs one sdio_claim_host()/sdio_release_host()
pair for the superframe header parse and then one additional pair for
every subframe header parse. For a superframe containing N subframes
this means N+1 mutex lock/unlock round-trips on the hot RX path.
Group all header parse calls — superframe and all subframes — under a
single claim/release pair. The host is still released before the
subsequent pure host-memory processing (skb_pull, len_nxtfrm update),
keeping the hold time as short as possible. The error path (rxfail)
also executes while the host is claimed, which is required because
brcmf_sdio_rxfail() writes SDIO Func1 registers.
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] 9+ messages in thread
* [PATCH v4 3/8] wifi: brcmfmac: core: fix missing headroom check and populate radiotap RSSI
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
2026-07-31 16:06 ` [PATCH v4 1/8] wifi: brcmfmac: flowring: replace O(N) blocked-ring scan with atomic counter Shivesh
2026-07-31 16:06 ` [PATCH v4 2/8] wifi: brcmfmac: sdio: coalesce sdio_claim_host calls in rxglom path Shivesh
@ 2026-07-31 16:06 ` Shivesh
2026-07-31 16:06 ` [PATCH v4 4/8] wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix brcmf_delay busy-wait Shivesh
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
Two problems exist in brcmf_netif_mon_rx():
1. skb_push() is called without first verifying that the skb has
sufficient headroom. If the skb arrives with zero headroom the
kernel will panic. Use skb_cow_head() before each skb_push() and
free the skb on allocation failure.
2. When the firmware provides a hardware RX header
(BRCMF_FEAT_MONITOR_FMT_HW_RX_HDR), the driver strips it and
inserts a blank ieee80211_radiotap_header with no signal data.
Monitor-mode packet captures therefore show no signal strength,
making tools like Wireshark and iw unable to report RSSI.
Define struct brcmf_radiotap_info that embeds the standard radiotap
header followed by a dbm_antsignal field. Extract the rssi value from
wlc_d11rxhdr and populate it_present with
IEEE80211_RADIOTAP_DBM_ANTSIGNAL so userspace tools can read signal
strength from monitor-mode frames.
Also replace the open-coded "skb->len -= 4" with skb_trim(), which
is the correct API for shortening a linear skb.
Fixes: e665988be29c ("brcmfmac: support monitor frames with the hardware/ucode header")
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/core.c | 44 ++++++++++++-------
1 file changed, 29 insertions(+), 15 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index ec170647800d..eefc437dd055 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -431,45 +431,55 @@ 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));
-
- /* TODO: 4 bytes with receive status? */
- skb->len -= 4;
+ /* Insert our radiotap header with RSSI data */
+ if (skb_cow_head(skb, sizeof(*rtap)))
+ goto drop;
+ 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;
+
+ /* 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 */
+ if (skb_cow_head(skb, sizeof(*radiotap)))
+ goto drop;
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;
@@ -478,6 +488,10 @@ void brcmf_netif_mon_rx(struct brcmf_if *ifp, struct sk_buff *skb)
skb->protocol = htons(ETH_P_802_2);
brcmf_netif_rx(ifp, skb);
+ return;
+
+drop:
+ brcmu_pkt_buf_free_skb(skb);
}
static int brcmf_rx_hdrpull(struct brcmf_pub *drvr, struct sk_buff *skb,
--
2.53.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v4 4/8] wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix brcmf_delay busy-wait
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
` (2 preceding siblings ...)
2026-07-31 16:06 ` [PATCH v4 3/8] wifi: brcmfmac: core: fix missing headroom check and populate radiotap RSSI Shivesh
@ 2026-07-31 16:06 ` Shivesh
2026-07-31 16:06 ` [PATCH v4 5/8] wifi: brcmfmac: msgbuf: fix TX stall and tune buffer/threshold constants Shivesh
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
Two independent fixes:
1. PMKID_V2 implementation
Firmware revision 12 introduced a versioned PMKID list (V2) with
FILS-specific fields: raw PMK material, SSID, and fils_cache_id.
The set/del/flush callbacks all contained "TODO: implement PMKID_V2"
placeholders and fell through to the V1 path, breaking FILS
fast-roaming on devices with V2-capable firmware.
Add brcmf_pmksa_v2_op() which maintains a shadow brcmf_pmk_list_v2_le
in cfg80211_info and pushes the full updated list to firmware via the
pmkid_info iovar on every set/del/flush. The shadow counter is kept
in list->length between calls; the wire-format byte-length is
computed only at send time to avoid corrupting the shadow on the next
call. Dispatch V2 between the existing V3 and V1 paths.
2. brcmf_delay() busy-wait
When ms < (1000/HZ), brcmf_delay() called mdelay(), which is a
CPU busy-wait loop. Since the function is always called in a
sleepable context (it falls back to msleep() for larger values),
the busy-wait is unnecessary and wastes CPU cycles. Replace with
usleep_range() for values <= 20ms.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../broadcom/brcm80211/brcmfmac/cfg80211.c | 123 ++++++++++++++++--
.../broadcom/brcm80211/brcmfmac/cfg80211.h | 4 +-
2 files changed, 115 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..2375c2f9d97a 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,109 @@ 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--;
+ }
+
+ /* Write the updated entry count back to shadow BEFORE we overwrite
+ * list->length with the wire-format byte length at send:. If we
+ * don't do this here, the next call will read a byte-length back
+ * as an entry count and silently corrupt the list.
+ */
+ list->length = cpu_to_le16(npmk);
+
+send:
+ /* Build the wire-format byte length and send the full list to firmware.
+ * Read npmk back from the shadow (handles the flush path where npmk=0).
+ */
+ npmk = le16_to_cpu(list->length);
+ 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 +4503,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 +4547,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 +4588,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] 9+ messages in thread
* [PATCH v4 5/8] wifi: brcmfmac: msgbuf: fix TX stall and tune buffer/threshold constants
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
` (3 preceding siblings ...)
2026-07-31 16:06 ` [PATCH v4 4/8] wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix brcmf_delay busy-wait Shivesh
@ 2026-07-31 16:06 ` Shivesh
2026-07-31 16:06 ` [PATCH v4 6/8] wifi: brcmfmac: pcie: replace msleep polling with usleep_range and backoff Shivesh
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
Three related changes:
1. Fix silent TX stall under high load
brcmf_msgbuf_schedule_txdata() used set_bit() followed by a
conditional queue_work(). When outstanding_tx >= DELAY_TXWORKER_THRS
and the flow_map bit was already set by a previous call, no new work
item was queued. If the existing worker had already run and cleared
its flow_map bits, the freshly enqueued frame would sit unsent until
an unrelated event woke the workqueue.
Replace set_bit() with test_and_set_bit(). If the bit was clear,
a worker must be scheduled unconditionally. If the bit was already
set, the existing coalescing heuristic applies.
2. Increase NR_TX_PKTIDS from 2048 to 4096
The 2048-entry TX packet-ID pool exhausts under >= 4 concurrent
iperf3 streams on Wi-Fi 5/6 devices, causing "No PKTID available"
drops and TCP retransmits. 4096 provides headroom for high-
aggregation workloads (~48 KB of additional host memory).
3. Raise TX flush thresholds from 32/96 to 64/128
Doubling CNT1 and CNT2 halves the PCIe doorbell rate on sustained
TX workloads. Latency impact on low-rate flows is negligible because
TRICKLE_TXWORKER_THRS (32) still causes a schedule before 64 frames
accumulate.
4. Replace msleep(10) with usleep_range() in init buffer fill loop
The post-attach RX buffer fill loop slept for at least 10ms per
iteration (often 20ms+ due to jiffy granularity). Convert to
usleep_range(1000, 2000) and increase the retry limit from 10 to
100 to preserve the same 100ms total budget with much lower latency
on fast hardware.
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] 9+ messages in thread
* [PATCH v4 6/8] wifi: brcmfmac: pcie: replace msleep polling with usleep_range and backoff
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
` (4 preceding siblings ...)
2026-07-31 16:06 ` [PATCH v4 5/8] wifi: brcmfmac: msgbuf: fix TX stall and tune buffer/threshold constants Shivesh
@ 2026-07-31 16:06 ` Shivesh
2026-07-31 16:06 ` [PATCH v4 7/8] wifi: brcmfmac: fwsignal: document safe no-op for duplicate MAC handle ADD Shivesh
2026-07-31 16:06 ` [PATCH v4 8/8] wifi: brcmsmac: ampdu: document IEEE 802.11n TID requirement Shivesh
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
Two latency improvements:
1. H2D mailbox polling
brcmf_pcie_send_mb_data() polled for the dongle to consume the
previous H2D mailbox message with msleep(10) per iteration. As the
dongle typically clears the register within a few hundred
microseconds, each call incurred at least 10ms of unnecessary
latency (often 20ms+ due to jiffy rounding).
Replace with usleep_range() using exponential backoff: start at
~50us and double each iteration up to 5ms, with a 1-second absolute
timeout matching the original budget. Add an early-exit check for
BRCMFMAC_PCIE_STATE_DOWN so a dead dongle does not hold the caller
for a full second.
2. IRQ teardown polling
brcmf_pcie_release_irq() waited for in_irq to clear using
msleep(50) in a 20-iteration loop (up to 1 second). Replace with
usleep_range(1000, 2000) in a 1000-iteration loop, preserving the
same ~1 second maximum while allowing the function to return in
microseconds when the IRQ handler finishes quickly.
Fixes: 9e37f045d5e7 ("brcmfmac: Adding PCIe bus layer support.")
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] 9+ messages in thread
* [PATCH v4 7/8] wifi: brcmfmac: fwsignal: document safe no-op for duplicate MAC handle ADD
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
` (5 preceding siblings ...)
2026-07-31 16:06 ` [PATCH v4 6/8] wifi: brcmfmac: pcie: replace msleep polling with usleep_range and backoff Shivesh
@ 2026-07-31 16:06 ` Shivesh
2026-07-31 16:06 ` [PATCH v4 8/8] wifi: brcmsmac: ampdu: document IEEE 802.11n TID requirement Shivesh
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
When the firmware sends a MACDESC_ADD event for a MAC address that
is already registered to the same slot (same mac_handle), the driver
hit a TODO comment with no documented rationale. Per the firmware
protocol, retransmitting an ADD for an existing handle is idempotent;
overwriting the entry or reinitialising it would drop in-flight
traffic queued in the per-descriptor psq.
Replace the TODO with an explanatory comment so the intent is clear
to future readers and static analysis tools do not flag the empty
branch as dead code.
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] 9+ messages in thread
* [PATCH v4 8/8] wifi: brcmsmac: ampdu: document IEEE 802.11n TID requirement
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
` (6 preceding siblings ...)
2026-07-31 16:06 ` [PATCH v4 7/8] wifi: brcmfmac: fwsignal: document safe no-op for duplicate MAC handle ADD Shivesh
@ 2026-07-31 16:06 ` Shivesh
7 siblings, 0 replies; 9+ messages in thread
From: Shivesh @ 2026-07-31 16:06 UTC (permalink / raw)
To: arend.vanspriel
Cc: linux-wireless, brcm80211, brcm80211-dev-list.pdl, linux-kernel,
Shivesh
An XXX comment in brcmsmac_ampdu_tx_add() questioned whether it is
necessary to reject frames whose QoS priority differs from the current
A-MPDU session. IEEE 802.11n (IEEE Std 802.11-2012, section 9.10.1)
requires that all MPDUs within an A-MPDU carry the same TID; mixing
TIDs would violate the Block ACK agreement and cause receiver-side
reassembly failures.
The existing behaviour of returning -ENOSPC to close the current
aggregate and start a new one is therefore required by the standard.
Replace the questioning XXX with a comment referencing the standard.
Signed-off-by: Shivesh <chanelshivesh@gmail.com>
---
.../wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 1 -
.../net/wireless/broadcom/brcm80211/brcmsmac/ampdu.c | 11 +++++------
2 files changed, 5 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 2375c2f9d97a..dc4228fb31c1 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -4464,7 +4464,6 @@ brcmf_pmksa_v2_op(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
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)
{
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] 9+ messages in thread
end of thread, other threads:[~2026-07-31 16:07 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 16:06 [PATCH v4 0/8] wifi: brcm80211: performance and stability fixes Shivesh
2026-07-31 16:06 ` [PATCH v4 1/8] wifi: brcmfmac: flowring: replace O(N) blocked-ring scan with atomic counter Shivesh
2026-07-31 16:06 ` [PATCH v4 2/8] wifi: brcmfmac: sdio: coalesce sdio_claim_host calls in rxglom path Shivesh
2026-07-31 16:06 ` [PATCH v4 3/8] wifi: brcmfmac: core: fix missing headroom check and populate radiotap RSSI Shivesh
2026-07-31 16:06 ` [PATCH v4 4/8] wifi: brcmfmac: cfg80211: implement PMKID_V2 and fix brcmf_delay busy-wait Shivesh
2026-07-31 16:06 ` [PATCH v4 5/8] wifi: brcmfmac: msgbuf: fix TX stall and tune buffer/threshold constants Shivesh
2026-07-31 16:06 ` [PATCH v4 6/8] wifi: brcmfmac: pcie: replace msleep polling with usleep_range and backoff Shivesh
2026-07-31 16:06 ` [PATCH v4 7/8] wifi: brcmfmac: fwsignal: document safe no-op for duplicate MAC handle ADD Shivesh
2026-07-31 16:06 ` [PATCH v4 8/8] wifi: brcmsmac: ampdu: document IEEE 802.11n TID requirement Shivesh
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox