[PATCH 6/8] wifi: brcmfmac: pcie: optimize latency and irq teardown
From: Shivesh
Date: Thu Jul 30 2026 - 13:31:52 EST
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 <shivesh@xxxxxxxxxxx>
---
.../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