[PATCH v3 5/8] wifi: brcmfmac: msgbuf: tune thresholds and optimize sleep latency

From: Shivesh

Date: Fri Jul 31 2026 - 11:57:37 EST


From: Shivesh <chanelshivesh@xxxxxxxxx>

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@xxxxxxxxx>
---
.../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