* [PATCH ath-next 1/3] wifi: ath11k: don't disable ext IRQs twice
2026-11-01 12:58 [PATCH ath-next 0/3] wifi: ath11k: more firmware crash recovery fixes for AHB Florian Maurer
@ 2026-11-01 12:58 ` Florian Maurer
2026-11-01 12:58 ` [PATCH ath-next 2/3] wifi: ath11k: depend on RPMSG instead of selecting it Florian Maurer
2026-11-01 12:58 ` [PATCH ath-next 3/3] wifi: ath11k: ahb: stop TX before WCSS is reset on a firmware crash Florian Maurer
2 siblings, 0 replies; 4+ messages in thread
From: Florian Maurer @ 2026-11-01 12:58 UTC (permalink / raw)
To: Jeff Johnson
Cc: Florian Maurer, ath11k, linux-wireless, linux-kernel,
Julius Bairaktaris, Baochen Qiang, Miaoqing Pan,
Rameshkumar Sundaram
ath11k_core_reconfigure_on_crash() disables the IRQs at the start of
every firmware recovery and ath11k_core_qmi_firmware_ready() enables
them again at its end. If a recovery fails, they stay disabled. The
next recovery disables them again, so the disable depth of the ext
IRQs goes to two and the single enable_irq() leaves them masked. The
DP rings would then not be serviced after an otherwise successful
recovery.
This is the same kind of imbalance as the one fixed for suspend/resume
by commit e56c9d429ba0 ("wifi: ath11k: fix unbalanced IRQ enable/disable
during suspend/resume"), but caused by a failed recovery.
NAPI already handles this: it is only disabled if it is enabled. The
ext IRQs are only enabled together with NAPI, so only disable them if
NAPI is enabled as well. The pcic ext IRQ code has the same pattern, so
change it there too. The PCI CE IRQs (ath11k_pcic_ce_irqs_disable())
can have the same imbalance after a failed recovery; they are not
changed here. The AHB CE IRQs are not affected, as they are enabled
and disabled through register bits without a disable depth.
On IPQ8074 (AHB) this shows up when a failed recovery (forced with a
test patch) is followed by another firmware crash: the recovery reports
success, but TX completions stop, TX fails with "failed to transmit
frame -12" and the firmware asserts again repeatedly. With this change
the same sequence recovered 3 out of 3 times. The pcic change is
compile-tested only.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Fixes: f7a74e131d3f ("wifi: ath11k: disable interrupts during firmware crash recovery")
Signed-off-by: Florian Maurer <fmaurer@disroot.org>
---
Hardware-tested on IPQ8074 with [1] backported to backports-6.18.55
(OpenWrt 25.12), build-tested on ath-next with [1].
[1] https://lore.kernel.org/linux-wireless/20261018210153.1925962-1-julius@bairaktaris.de/
drivers/net/wireless/ath/ath11k/ahb.c | 4 ++--
drivers/net/wireless/ath/ath11k/pcic.c | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c
index 27d01411b6e9..3ff7d5f27f5e 100644
--- a/drivers/net/wireless/ath/ath11k/ahb.c
+++ b/drivers/net/wireless/ath/ath11k/ahb.c
@@ -235,9 +235,9 @@ static void __ath11k_ahb_ext_irq_disable(struct ath11k_base *ab)
for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) {
struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i];
- ath11k_ahb_ext_grp_disable(irq_grp);
-
+ /* The IRQs are only enabled together with NAPI */
if (irq_grp->napi_enabled) {
+ ath11k_ahb_ext_grp_disable(irq_grp);
napi_synchronize(&irq_grp->napi);
napi_disable(&irq_grp->napi);
irq_grp->napi_enabled = false;
diff --git a/drivers/net/wireless/ath/ath11k/pcic.c b/drivers/net/wireless/ath/ath11k/pcic.c
index 2259adc3bbdc..2e7dfa8388b0 100644
--- a/drivers/net/wireless/ath/ath11k/pcic.c
+++ b/drivers/net/wireless/ath/ath11k/pcic.c
@@ -455,9 +455,9 @@ static void __ath11k_pcic_ext_irq_disable(struct ath11k_base *ab)
for (i = 0; i < ATH11K_EXT_IRQ_GRP_NUM_MAX; i++) {
struct ath11k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i];
- ath11k_pcic_ext_grp_disable(irq_grp);
-
+ /* The IRQs are only enabled together with NAPI */
if (irq_grp->napi_enabled) {
+ ath11k_pcic_ext_grp_disable(irq_grp);
napi_synchronize(&irq_grp->napi);
napi_disable(&irq_grp->napi);
irq_grp->napi_enabled = false;
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH ath-next 3/3] wifi: ath11k: ahb: stop TX before WCSS is reset on a firmware crash
2026-11-01 12:58 [PATCH ath-next 0/3] wifi: ath11k: more firmware crash recovery fixes for AHB Florian Maurer
2026-11-01 12:58 ` [PATCH ath-next 1/3] wifi: ath11k: don't disable ext IRQs twice Florian Maurer
2026-11-01 12:58 ` [PATCH ath-next 2/3] wifi: ath11k: depend on RPMSG instead of selecting it Florian Maurer
@ 2026-11-01 12:58 ` Florian Maurer
2 siblings, 0 replies; 4+ messages in thread
From: Florian Maurer @ 2026-11-01 12:58 UTC (permalink / raw)
To: Jeff Johnson
Cc: Florian Maurer, ath11k, linux-wireless, linux-kernel,
Julius Bairaktaris, Baochen Qiang, Miaoqing Pan,
Rameshkumar Sundaram
On IPQ8074 a firmware crash is handled by remoteproc, which stops the
remote processor and thereby resets WCSS. ath11k sets
ATH11K_FLAG_CRASH_FLUSH, which stops the TX paths, only when it handles
the QMI server exit event. That event results from the glink teardown
in rproc_stop() and is handled asynchronously, so it can be processed
after WCSS is already in reset. A TX in that window writes a ring head
pointer to the reset block, which ends in an asynchronous SError:
remoteproc remoteproc0: stopped remote processor cd00000.q6v5_wcss
SError Interrupt on CPU3, code 0x00000000bf000002 -- SError
pc : ath11k_dp_tx+0x45c/0x878 [ath11k]
Kernel panic - not syncing: Asynchronous SError Interrupt
remoteproc sends a QCOM_SSR_BEFORE_SHUTDOWN notification with crashed
set before it stops the processor. Register for it, like ath10k does on
SNOC, set ATH11K_FLAG_CRASH_FLUSH there and wait with synchronize_rcu()
for TX paths which have not seen the flag yet. ath11k_dp_tx() is called
by mac80211 within RCU read-side critical sections or with BHs
disabled. Check the flag in ath11k_ce_send() again under ce_lock, so
that this also covers WMI and HTC.
This stops new TX descriptors, for data and for everything sent through
CE. Ring pointer updates from the RX and TX completion paths (NAPI, CE
tasklets) are not stopped by this.
A crash during the restart work of an earlier crash, after the QMI
firmware start and before ath11k_core_qmi_firmware_ready() clears
ATH11K_FLAG_CRASH_FLUSH after ath11k_dp_alloc(), is not covered yet:
the flag set by the notifier is cleared again, and CE/HTC sends from
the restart work can still reach the reset WCSS. mac80211 data queues
are still stopped at that point. That window is a few milliseconds and
will be addressed separately.
The SSR subsystem name depends on the remoteproc driver, so add it to
hw_params. Only IPQ8074 sets it for now. qcom_register_ssr_notifier()
must be reachable from ath11k_ahb, so add the same Kconfig dependency
on QCOM_RPROC_COMMON as ath10k SNOC has.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Signed-off-by: Florian Maurer <fmaurer@disroot.org>
---
Depends on 4/5 of [1], which moved clearing ATH11K_FLAG_CRASH_FLUSH
into ath11k_core_qmi_firmware_ready(), and on patch 2 of this series for
the Kconfig dependency. For that reason there is no Fixes tag: without
patch 2 the Kconfig dependency added here is a recursive dependency
error, so this must not be backported on its own.
Hardware-tested on IPQ8074 with [1] backported to backports-6.18.55
(OpenWrt 25.12). The tested version also had a give-up path for a
crash during the restart work, which was never triggered in the tests
and is left out here; the remaining code is the same apart from a
shorter comment. Build-tested on ath-next with [1], including
ATH11K_AHB=y with QCOM_RPROC_COMMON=y.
[1] https://lore.kernel.org/linux-wireless/20261018210153.1925962-1-julius@bairaktaris.de/
drivers/net/wireless/ath/ath11k/Kconfig | 1 +
drivers/net/wireless/ath/ath11k/ahb.c | 59 +++++++++++++++++++++++++
drivers/net/wireless/ath/ath11k/ahb.h | 2 +
drivers/net/wireless/ath/ath11k/ce.c | 7 +++
drivers/net/wireless/ath/ath11k/core.c | 1 +
drivers/net/wireless/ath/ath11k/hw.h | 1 +
6 files changed, 71 insertions(+)
diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig
index 84b7eb5df253..a425da76934b 100644
--- a/drivers/net/wireless/ath/ath11k/Kconfig
+++ b/drivers/net/wireless/ath/ath11k/Kconfig
@@ -14,6 +14,7 @@ config ATH11K_AHB
tristate "Atheros ath11k AHB support"
depends on ATH11K
depends on REMOTEPROC
+ depends on QCOM_RPROC_COMMON || QCOM_RPROC_COMMON=n
depends on RPMSG
select QRTR
select QRTR_SMD
diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c
index 3ff7d5f27f5e..893a3270394e 100644
--- a/drivers/net/wireless/ath/ath11k/ahb.c
+++ b/drivers/net/wireless/ath/ath11k/ahb.c
@@ -20,6 +20,7 @@
#include "pcic.h"
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
+#include <linux/remoteproc/qcom_rproc.h>
static const struct of_device_id ath11k_ahb_of_match[] = {
/* TODO: Should we change the compatible string to something similar
@@ -832,6 +833,60 @@ static const struct ath11k_hif_ops ath11k_ahb_hif_ops_wcn6750 = {
.ce_irq_disable = ath11k_pci_disable_ce_irqs_except_wake_irq,
};
+static int ath11k_ahb_ssr_notifier(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct ath11k_ahb *ab_ahb = container_of(nb, struct ath11k_ahb, ssr_nb);
+ struct ath11k_base *ab = container_of((void *)ab_ahb, struct ath11k_base,
+ drv_priv);
+ struct qcom_ssr_notify_data *notify = data;
+
+ if (action != QCOM_SSR_BEFORE_SHUTDOWN || !notify->crashed)
+ return NOTIFY_DONE;
+
+ /* WCSS can be reset before the QMI server exit event sets the flag.
+ * ath11k_dp_tx() runs in RCU read-side sections or with BHs disabled
+ * and ath11k_ce_send() checks the flag under ce_lock, so after
+ * synchronize_rcu() no new TX descriptors are queued.
+ */
+ ath11k_dbg(ab, ATH11K_DBG_AHB, "firmware crashed, stopping tx\n");
+ set_bit(ATH11K_FLAG_CRASH_FLUSH, &ab->dev_flags);
+ synchronize_rcu();
+
+ return NOTIFY_OK;
+}
+
+static void ath11k_ahb_register_ssr_notifier(struct ath11k_base *ab)
+{
+ struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
+ void *handle;
+
+ if (!ab->hw_params.ssr_name)
+ return;
+
+ ab_ahb->ssr_nb.notifier_call = ath11k_ahb_ssr_notifier;
+ handle = qcom_register_ssr_notifier(ab->hw_params.ssr_name,
+ &ab_ahb->ssr_nb);
+ if (IS_ERR(handle)) {
+ ath11k_warn(ab, "failed to register ssr notifier: %ld\n",
+ PTR_ERR(handle));
+ return;
+ }
+
+ ab_ahb->ssr_handle = handle;
+}
+
+static void ath11k_ahb_unregister_ssr_notifier(struct ath11k_base *ab)
+{
+ struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
+
+ if (!ab_ahb->ssr_handle)
+ return;
+
+ qcom_unregister_ssr_notifier(ab_ahb->ssr_handle, &ab_ahb->ssr_nb);
+ ab_ahb->ssr_handle = NULL;
+}
+
static int ath11k_core_get_rproc(struct ath11k_base *ab)
{
struct ath11k_ahb *ab_ahb = ath11k_ahb_priv(ab);
@@ -1230,6 +1285,8 @@ static int ath11k_ahb_probe(struct platform_device *pdev)
if (ret)
goto err_ce_free;
+ ath11k_ahb_register_ssr_notifier(ab);
+
ret = ath11k_core_init(ab);
if (ret) {
ath11k_err(ab, "failed to init core: %d\n", ret);
@@ -1247,6 +1304,7 @@ static int ath11k_ahb_probe(struct platform_device *pdev)
return 0;
err_ce_free:
+ ath11k_ahb_unregister_ssr_notifier(ab);
ath11k_ce_free_pipes(ab);
err_hal_srng_deinit:
@@ -1288,6 +1346,7 @@ static void ath11k_ahb_free_resources(struct ath11k_base *ab)
{
struct platform_device *pdev = ab->pdev;
+ ath11k_ahb_unregister_ssr_notifier(ab);
ath11k_ahb_free_irq(ab);
ath11k_hal_srng_deinit(ab);
ath11k_ahb_release_smp2p_handle(ab);
diff --git a/drivers/net/wireless/ath/ath11k/ahb.h b/drivers/net/wireless/ath/ath11k/ahb.h
index 8c1eb1e8e6b1..d7ea47f42edf 100644
--- a/drivers/net/wireless/ath/ath11k/ahb.h
+++ b/drivers/net/wireless/ath/ath11k/ahb.h
@@ -23,6 +23,8 @@ struct ath11k_base;
struct ath11k_ahb {
struct rproc *tgt_rproc;
+ struct notifier_block ssr_nb;
+ void *ssr_handle;
struct {
struct device *dev;
struct iommu_domain *iommu_domain;
diff --git a/drivers/net/wireless/ath/ath11k/ce.c b/drivers/net/wireless/ath/ath11k/ce.c
index 1e08b0970b14..895648687d99 100644
--- a/drivers/net/wireless/ath/ath11k/ce.c
+++ b/drivers/net/wireless/ath/ath11k/ce.c
@@ -745,6 +745,12 @@ int ath11k_ce_send(struct ath11k_base *ab, struct sk_buff *skb, u8 pipe_id,
spin_lock_bh(&ab->ce.ce_lock);
+ /* Check again with BHs disabled, see ath11k_ahb_ssr_notifier() */
+ if (test_bit(ATH11K_FLAG_CRASH_FLUSH, &ab->dev_flags)) {
+ ret = -ESHUTDOWN;
+ goto err_ce_unlock;
+ }
+
write_index = pipe->src_ring->write_index;
nentries_mask = pipe->src_ring->nentries_mask;
@@ -791,6 +797,7 @@ int ath11k_ce_send(struct ath11k_base *ab, struct sk_buff *skb, u8 pipe_id,
err_unlock:
spin_unlock_bh(&srng->lock);
+err_ce_unlock:
spin_unlock_bh(&ab->ce.ce_lock);
return ret;
diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c
index 4666245aee36..7ba05c63ede6 100644
--- a/drivers/net/wireless/ath/ath11k/core.c
+++ b/drivers/net/wireless/ath/ath11k/core.c
@@ -91,6 +91,7 @@ static const struct ath11k_hw_params ath11k_hw_params[] = {
.coldboot_cal_mm = true,
.coldboot_cal_ftm = true,
.cbcal_restart_fw = true,
+ .ssr_name = "q6wcss",
.fw_mem_mode = 0,
.num_vdevs = 16 + 1,
.num_peers = 512,
diff --git a/drivers/net/wireless/ath/ath11k/hw.h b/drivers/net/wireless/ath/ath11k/hw.h
index 4996536fbd14..ce52c61d661d 100644
--- a/drivers/net/wireless/ath/ath11k/hw.h
+++ b/drivers/net/wireless/ath/ath11k/hw.h
@@ -191,6 +191,7 @@ struct ath11k_hw_params {
bool coldboot_cal_mm;
bool coldboot_cal_ftm;
bool cbcal_restart_fw;
+ const char *ssr_name;
int fw_mem_mode;
u32 num_vdevs;
u32 num_peers;
--
2.47.3
^ permalink raw reply related [flat|nested] 4+ messages in thread