[PATCH v2] wifi: ath9k: stop a failed register read from opening the RX filter
From: Nerijus Bendžiūnas
Date: Fri Sep 04 2026 - 14:39:54 EST
On the USB devices a register read is a WMI round trip, and
ath9k_regread() reports a timeout as -1. The spectral trigger reads
AR_RX_FILTER and AR_PHY_ERR back through ath9k_hw_getrxfilter(), ORs in
the PHY error bits and writes both registers. A timed-out read stores
0xffffffff in one of them, the device forwards every frame and PHY
error to the host, and the full RX ring drops the FFT reports the scan
was started for.
Add ath9k_hw_enable_rxfilter(), which sets the requested filter bits,
the matching AR_PHY_ERR bits and AR_RXCFG_ZLFDMA with REG_SET_BIT() and
reads nothing back, and use it in the spectral trigger. It sets bits in
AR_PHY_ERR where ath9k_hw_setrxfilter() overwrites the register. With
firmware 1.4 and later REG_SET_BIT() is one read-modify-write done by
the firmware; older firmware still reads from the host and is not
helped.
Fixes: e93d083f42a1 ("ath9k: add spectral scan feature")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-fable-5-1
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@xxxxxxxxx>
---
Changes in v2:
- Describe the whole of ath9k_hw_enable_rxfilter(): it also sets the
AR_PHY_ERR bits and AR_RXCFG_ZLFDMA, and sets bits in AR_PHY_ERR where
ath9k_hw_setrxfilter() overwrites it. Say that the fix does not help
firmware older than 1.4.
- Add Assisted-by, rewrite the commit message, rebase onto ath-next. No
code change.
.../net/wireless/ath/ath9k/common-spectral.c | 7 ++---
drivers/net/wireless/ath/ath9k/hw.c | 29 +++++++++++++++++++
drivers/net/wireless/ath/ath9k/hw.h | 1 +
3 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/common-spectral.c b/drivers/net/wireless/ath/ath9k/common-spectral.c
index ca01a07f6630..73c1eb4ebe0e 100644
--- a/drivers/net/wireless/ath/ath9k/common-spectral.c
+++ b/drivers/net/wireless/ath/ath9k/common-spectral.c
@@ -716,7 +716,6 @@ void ath9k_cmn_spectral_scan_trigger(struct ath_common *common,
struct ath_spec_scan_priv *spec_priv)
{
struct ath_hw *ah = spec_priv->ah;
- u32 rxfilter;
if (IS_ENABLED(CONFIG_ATH9K_TX99))
return;
@@ -730,10 +729,8 @@ void ath9k_cmn_spectral_scan_trigger(struct ath_common *common,
return;
ath_ps_ops(common)->wakeup(common);
- rxfilter = ath9k_hw_getrxfilter(ah);
- ath9k_hw_setrxfilter(ah, rxfilter |
- ATH9K_RX_FILTER_PHYRADAR |
- ATH9K_RX_FILTER_PHYERR);
+ ath9k_hw_enable_rxfilter(ah, ATH9K_RX_FILTER_PHYRADAR |
+ ATH9K_RX_FILTER_PHYERR);
/* TODO: usually this should not be necessary, but for some reason
* (or in some mode?) the trigger must be called after the
diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c
index e08ab73fcacb..caa221ce0d32 100644
--- a/drivers/net/wireless/ath/ath9k/hw.c
+++ b/drivers/net/wireless/ath/ath9k/hw.c
@@ -2905,6 +2905,35 @@ void ath9k_hw_setrxfilter(struct ath_hw *ah, u32 bits)
}
EXPORT_SYMBOL(ath9k_hw_setrxfilter);
+/*
+ * Turn extra bits on in the RX filter without reading the current value back.
+ *
+ * On USB a register read is a WMI round trip that can time out, and
+ * ath9k_regread() reports that failure as -1, which is indistinguishable from
+ * a genuine all-ones read. Feeding it into a read-modify-write stores
+ * 0xffffffff in AR_RX_FILTER, enabling every filter bit at once, and the
+ * device then floods the host with every frame and PHY error it sees. Set only
+ * the requested bits so that no read is involved.
+ */
+void ath9k_hw_enable_rxfilter(struct ath_hw *ah, u32 bits)
+{
+ u32 phybits = 0;
+
+ if (bits & ATH9K_RX_FILTER_PHYRADAR)
+ phybits |= AR_PHY_ERR_RADAR;
+ if (bits & ATH9K_RX_FILTER_PHYERR)
+ phybits |= AR_PHY_ERR_OFDM_TIMING | AR_PHY_ERR_CCK_TIMING;
+
+ REG_SET_BIT(ah, AR_RX_FILTER, bits);
+
+ if (phybits) {
+ REG_SET_BIT(ah, AR_PHY_ERR, phybits);
+ /* PHY errors are reported in zero length frames. */
+ REG_SET_BIT(ah, AR_RXCFG, AR_RXCFG_ZLFDMA);
+ }
+}
+EXPORT_SYMBOL(ath9k_hw_enable_rxfilter);
+
bool ath9k_hw_phy_disable(struct ath_hw *ah)
{
if (ath9k_hw_mci_is_enabled(ah))
diff --git a/drivers/net/wireless/ath/ath9k/hw.h b/drivers/net/wireless/ath/ath9k/hw.h
index b942b8303d8f..f102f73a0114 100644
--- a/drivers/net/wireless/ath/ath9k/hw.h
+++ b/drivers/net/wireless/ath/ath9k/hw.h
@@ -1055,6 +1055,7 @@ void ath9k_hw_get_channel_centers(struct ath_hw *ah,
struct chan_centers *centers);
u32 ath9k_hw_getrxfilter(struct ath_hw *ah);
void ath9k_hw_setrxfilter(struct ath_hw *ah, u32 bits);
+void ath9k_hw_enable_rxfilter(struct ath_hw *ah, u32 bits);
bool ath9k_hw_phy_disable(struct ath_hw *ah);
bool ath9k_hw_disable(struct ath_hw *ah);
void ath9k_hw_set_txpowerlimit(struct ath_hw *ah, u32 limit, bool test);
base-commit: 1d8e73163ef933624341075f576e2f36ef9133f7
--
2.55.0