[PATCH v4 6/8] wifi: brcmfmac: pcie: replace msleep polling with usleep_range and backoff

From: Shivesh

Date: Fri Jul 31 2026 - 12:19:37 EST


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