[PATCH 4/7] ath11k: add PCIe link recovery retry
Jose Ignacio Tornos Martinez
jtornosm at redhat.com
Wed Sep 30 07:08:30 PDT 2026
When ath11k devices are passed through to VMs via PCIe passthrough,
the PCIe link may not stabilize immediately after global reset due to
virtualization layer timing variations. This causes MHI initialization
to fail with "BHI offset: 0xffffffff is out of range" errors because
the device is read before it's ready.
Currently, ath11k_pci_soc_global_reset() just warns if the link is
down but continues anyway, leading to inevitable MHI failures.
This patch adds:
1. A retry loop (up to 5000ms) to wait for the PCIe link to recover
after global reset (similar to the existing ath12k LTSSM retry logic)
2. Proper error propagation to prevent continuing with a dead link
This is especially important for VM environments where timing can vary
significantly from bare metal.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm at redhat.com>
---
drivers/net/wireless/ath/ath11k/pci.c | 39 ++++++++++++++++++++++-----
1 file changed, 32 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/pci.c b/drivers/net/wireless/ath/ath11k/pci.c
index 7114eca8810d..4fc6aaf036c1 100644
--- a/drivers/net/wireless/ath/ath11k/pci.c
+++ b/drivers/net/wireless/ath/ath11k/pci.c
@@ -190,8 +190,10 @@ static void ath11k_pci_restore_window(struct ath11k_base *ab)
spin_unlock_bh(&ab_pci->window_lock);
}
-static void ath11k_pci_soc_global_reset(struct ath11k_base *ab)
+static int ath11k_pci_soc_global_reset(struct ath11k_base *ab)
{
+ unsigned long timeout;
+ bool link_recovered = false;
u32 val, delay;
val = ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET);
@@ -211,14 +213,29 @@ static void ath11k_pci_soc_global_reset(struct ath11k_base *ab)
mdelay(delay);
- val = ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET);
- if (val == 0xffffffff)
- ath11k_warn(ab, "link down error during global reset\n");
+ /* Wait for PCIe link to recover, especially important in VM environments
+ * where timing can vary significantly from bare metal.
+ */
+ timeout = jiffies + msecs_to_jiffies(5000);
+ while (time_before(jiffies, timeout)) {
+ val = ath11k_pcic_read32(ab, PCIE_SOC_GLOBAL_RESET);
+ if (val != 0xffffffff) {
+ link_recovered = true;
+ break;
+ }
+ mdelay(20);
+ }
+ if (!link_recovered) {
+ ath11k_err(ab, "PCIe link failed to recover - device may need power cycle\n");
+ return -ETIMEDOUT;
+ }
/* Restore window register as its content is cleared during
* hardware global reset, such that it aligns with host cache.
*/
ath11k_pci_restore_window(ab);
+
+ return 0;
}
static void ath11k_pci_clear_dbg_registers(struct ath11k_base *ab)
@@ -373,8 +390,10 @@ static void ath11k_pci_force_wake(struct ath11k_base *ab)
mdelay(5);
}
-static void ath11k_pci_sw_reset(struct ath11k_base *ab, bool power_on)
+static int ath11k_pci_sw_reset(struct ath11k_base *ab, bool power_on)
{
+ int ret;
+
mdelay(100);
if (power_on) {
@@ -387,8 +406,12 @@ static void ath11k_pci_sw_reset(struct ath11k_base *ab, bool power_on)
ath11k_mhi_clear_vector(ab);
ath11k_pci_clear_dbg_registers(ab);
- ath11k_pci_soc_global_reset(ab);
+ ret = ath11k_pci_soc_global_reset(ab);
+ if (ret)
+ return ret;
ath11k_mhi_set_mhictrl_reset(ab);
+
+ return 0;
}
static void ath11k_pci_init_qmi_ce_config(struct ath11k_base *ab)
@@ -818,7 +841,9 @@ static int ath11k_pci_power_up(struct ath11k_base *ab)
ab_pci->register_window = 0;
clear_bit(ATH11K_FLAG_DEVICE_INIT_DONE, &ab->dev_flags);
- ath11k_pci_sw_reset(ab_pci->ab, true);
+ ret = ath11k_pci_sw_reset(ab_pci->ab, true);
+ if (ret)
+ return ret;
/* Disable ASPM during firmware download due to problems switching
* to AMSS state.
--
2.53.0
More information about the ath12k
mailing list