* [PATCH ath-next v4 1/8] wifi: ath9k: return an error from a failed multi-register read
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 2/8] wifi: ath9k_htc: fix the byte count of a full RMW buffer flush Nerijus Bendžiūnas
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
ath9k_multi_regread() logs a failed or timed-out WMI read and still
copies its result buffer to the caller, so the caller gets
uninitialised stack data for every register it asked for. The MIB
counters in ath9k_hw_update_mibstats() then take arbitrary jumps, and
an EEPROM read at probe fails on the checksum rather than on the read.
Give the multi_read op a return value, return the WMI error from
ath9k_multi_regread(), and check it: skip the counter update and fail
the EEPROM fill when the read did not happen.
Fixes: 09a525d33870 ("ath9k_htc: Add multiple register read API")
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
drivers/net/wireless/ath/ath.h | 2 +-
drivers/net/wireless/ath/ath9k/ani.c | 4 +++-
drivers/net/wireless/ath/ath9k/eeprom.c | 10 +++++++---
drivers/net/wireless/ath/ath9k/eeprom.h | 2 +-
drivers/net/wireless/ath/ath9k/eeprom_4k.c | 4 +---
drivers/net/wireless/ath/ath9k/eeprom_9287.c | 7 +++----
drivers/net/wireless/ath/ath9k/eeprom_def.c | 5 ++---
drivers/net/wireless/ath/ath9k/htc_drv_init.c | 7 +++++--
drivers/net/wireless/ath/ath9k/init.c | 6 ++++--
9 files changed, 27 insertions(+), 20 deletions(-)
diff --git a/drivers/net/wireless/ath/ath.h b/drivers/net/wireless/ath/ath.h
index 34654f710d8a..d1af0b81768a 100644
--- a/drivers/net/wireless/ath/ath.h
+++ b/drivers/net/wireless/ath/ath.h
@@ -126,7 +126,7 @@ enum ath_cipher {
*/
struct ath_ops {
unsigned int (*read)(void *, u32 reg_offset);
- void (*multi_read)(void *, u32 *addr, u32 *val, u16 count);
+ int (*multi_read)(void *hw_priv, u32 *addr, u32 *val, u16 count);
void (*write)(void *, u32 val, u32 reg_offset);
void (*enable_write_buffer)(void *);
void (*write_flush) (void *);
diff --git a/drivers/net/wireless/ath/ath9k/ani.c b/drivers/net/wireless/ath/ath9k/ani.c
index 41d192709e8e..6d9f0ee26237 100644
--- a/drivers/net/wireless/ath/ath9k/ani.c
+++ b/drivers/net/wireless/ath/ath9k/ani.c
@@ -111,7 +111,9 @@ static void ath9k_hw_update_mibstats(struct ath_hw *ah,
AR_FCS_FAIL, AR_BEACON_CNT};
u32 data[5];
- REG_READ_MULTI(ah, &addr[0], &data[0], 5);
+ if (REG_READ_MULTI(ah, &addr[0], &data[0], 5))
+ return;
+
/* AR_RTS_OK */
stats->rts_good += data[0];
/* AR_RTS_FAIL */
diff --git a/drivers/net/wireless/ath/ath9k/eeprom.c b/drivers/net/wireless/ath/ath9k/eeprom.c
index df58dc02e104..6bf86d017c13 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom.c
@@ -76,7 +76,7 @@ bool ath9k_hw_get_lower_upper_index(u8 target, u8 *pList, u16 listSize,
return false;
}
-void ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data,
+bool ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data,
int eep_start_loc, int size)
{
int i = 0, j, addr;
@@ -88,7 +88,8 @@ void ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data,
((addr + eep_start_loc) << AR5416_EEPROM_S);
i++;
if (i == 8) {
- REG_READ_MULTI(ah, addrdata, data, i);
+ if (REG_READ_MULTI(ah, addrdata, data, i))
+ return false;
for (j = 0; j < i; j++) {
*eep_data = data[j];
@@ -99,13 +100,16 @@ void ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data,
}
if (i != 0) {
- REG_READ_MULTI(ah, addrdata, data, i);
+ if (REG_READ_MULTI(ah, addrdata, data, i))
+ return false;
for (j = 0; j < i; j++) {
*eep_data = data[j];
eep_data++;
}
}
+
+ return true;
}
static bool ath9k_hw_nvram_read_array(u16 *blob, size_t blob_size,
diff --git a/drivers/net/wireless/ath/ath9k/eeprom.h b/drivers/net/wireless/ath/ath9k/eeprom.h
index f1cde43fcb55..dc5ad4a3e752 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom.h
+++ b/drivers/net/wireless/ath/ath9k/eeprom.h
@@ -680,7 +680,7 @@ bool ath9k_hw_nvram_read(struct ath_hw *ah, u32 off, u16 *data);
int ath9k_hw_nvram_swap_data(struct ath_hw *ah, bool *swap_needed, int size);
bool ath9k_hw_nvram_validate_checksum(struct ath_hw *ah, int size);
bool ath9k_hw_nvram_check_version(struct ath_hw *ah, int version, int minrev);
-void ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data,
+bool ath9k_hw_usb_gen_fill_eeprom(struct ath_hw *ah, u16 *eep_data,
int eep_start_loc, int size);
void ath9k_hw_fill_vpd_table(u8 pwrMin, u8 pwrMax, u8 *pPwrList,
u8 *pVpdList, u16 numIntercepts,
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_4k.c b/drivers/net/wireless/ath/ath9k/eeprom_4k.c
index 3e16cfe059f3..d5a8275ceb03 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_4k.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_4k.c
@@ -53,9 +53,7 @@ static bool __ath9k_hw_usb_4k_fill_eeprom(struct ath_hw *ah)
{
u16 *eep_data = (u16 *)&ah->eeprom.map4k;
- ath9k_hw_usb_gen_fill_eeprom(ah, eep_data, 64, SIZE_EEPROM_4K);
-
- return true;
+ return ath9k_hw_usb_gen_fill_eeprom(ah, eep_data, 64, SIZE_EEPROM_4K);
}
static bool ath9k_hw_4k_fill_eeprom(struct ath_hw *ah)
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_9287.c b/drivers/net/wireless/ath/ath9k/eeprom_9287.c
index c139ac49ccf6..73213416fc8c 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_9287.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_9287.c
@@ -55,10 +55,9 @@ static bool __ath9k_hw_usb_ar9287_fill_eeprom(struct ath_hw *ah)
{
u16 *eep_data = (u16 *)&ah->eeprom.map9287;
- ath9k_hw_usb_gen_fill_eeprom(ah, eep_data,
- AR9287_HTC_EEP_START_LOC,
- SIZE_EEPROM_AR9287);
- return true;
+ return ath9k_hw_usb_gen_fill_eeprom(ah, eep_data,
+ AR9287_HTC_EEP_START_LOC,
+ SIZE_EEPROM_AR9287);
}
static bool ath9k_hw_ar9287_fill_eeprom(struct ath_hw *ah)
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_def.c b/drivers/net/wireless/ath/ath9k/eeprom_def.c
index 5ba467cb7425..085e544f7c4b 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_def.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_def.c
@@ -112,9 +112,8 @@ static bool __ath9k_hw_usb_def_fill_eeprom(struct ath_hw *ah)
{
u16 *eep_data = (u16 *)&ah->eeprom.def;
- ath9k_hw_usb_gen_fill_eeprom(ah, eep_data,
- 0x100, SIZE_EEPROM_DEF);
- return true;
+ return ath9k_hw_usb_gen_fill_eeprom(ah, eep_data,
+ 0x100, SIZE_EEPROM_DEF);
}
static bool ath9k_hw_def_fill_eeprom(struct ath_hw *ah)
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_init.c b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
index 6de78ae85726..3798d3375158 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_init.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
@@ -252,8 +252,8 @@ static unsigned int ath9k_regread(void *hw_priv, u32 reg_offset)
return be32_to_cpu(val);
}
-static void ath9k_multi_regread(void *hw_priv, u32 *addr,
- u32 *val, u16 count)
+static int ath9k_multi_regread(void *hw_priv, u32 *addr,
+ u32 *val, u16 count)
{
struct ath_hw *ah = hw_priv;
struct ath_common *common = ath9k_hw_common(ah);
@@ -273,11 +273,14 @@ static void ath9k_multi_regread(void *hw_priv, u32 *addr,
if (unlikely(ret)) {
ath_dbg(common, WMI,
"Multiple REGISTER READ FAILED (count: %d)\n", count);
+ return ret;
}
for (i = 0; i < count; i++) {
val[i] = be32_to_cpu(tmpval[i]);
}
+
+ return 0;
}
static void ath9k_regwrite_multi(struct ath_common *common)
diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c
index 2f0c4ef86b7e..e425d355bf94 100644
--- a/drivers/net/wireless/ath/ath9k/init.c
+++ b/drivers/net/wireless/ath/ath9k/init.c
@@ -201,13 +201,15 @@ static unsigned int ath9k_ioread32(void *hw_priv, u32 reg_offset)
return val;
}
-static void ath9k_multi_ioread32(void *hw_priv, u32 *addr,
- u32 *val, u16 count)
+static int ath9k_multi_ioread32(void *hw_priv, u32 *addr,
+ u32 *val, u16 count)
{
int i;
for (i = 0; i < count; i++)
val[i] = ath9k_ioread32(hw_priv, addr[i]);
+
+ return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH ath-next v4 2/8] wifi: ath9k_htc: fix the byte count of a full RMW buffer flush
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 1/8] wifi: ath9k: return an error from a failed multi-register read Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 3/8] wifi: ath9k_htc: refuse a command that fills whole USB packets Nerijus Bendžiūnas
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
ath9k_reg_rmw_buffer() sizes a full-buffer flush with
sizeof(struct register_write), 8 bytes, instead of
sizeof(struct register_rmw), 12 bytes. ar9271_hw_pa_cal() queues
exactly 15 read-modify-writes, so the AR9271 PA calibration has been
losing its last five writes since the buffer was added.
The corrected command is 192 bytes, three full 64-byte USB packets,
and the firmware ends a command only on a short packet, so the device
stops answering WMI on the first interface open. Send the whole buffer
on a full flush and cap it at 14 entries; 15 is the only count within
the buffer's reach that fills whole packets.
Fixes: 8badb50cfab6 ("ath9k_htc: add new WMI_REG_RMW_CMDID command")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
drivers/net/wireless/ath/ath9k/htc_drv_init.c | 2 +-
drivers/net/wireless/ath/ath9k/wmi.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_init.c b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
index 3798d3375158..2d9c14006972 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_init.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_init.c
@@ -408,7 +408,7 @@ static void ath9k_reg_rmw_buffer(void *hw_priv,
if (priv->wmi->multi_rmw_idx == MAX_RMW_CMD_NUMBER) {
r = ath9k_wmi_cmd(priv->wmi, WMI_REG_RMW_CMDID,
(u8 *) &priv->wmi->multi_rmw,
- sizeof(struct register_write) * priv->wmi->multi_rmw_idx,
+ sizeof(priv->wmi->multi_rmw),
(u8 *) &rsp_status, sizeof(rsp_status),
100);
if (unlikely(r)) {
diff --git a/drivers/net/wireless/ath/ath9k/wmi.h b/drivers/net/wireless/ath/ath9k/wmi.h
index 5c3b710b8f31..f9c0fe54a884 100644
--- a/drivers/net/wireless/ath/ath9k/wmi.h
+++ b/drivers/net/wireless/ath/ath9k/wmi.h
@@ -126,7 +126,7 @@ enum wmi_event_id {
};
#define MAX_CMD_NUMBER 62
-#define MAX_RMW_CMD_NUMBER 15
+#define MAX_RMW_CMD_NUMBER 14
struct register_write {
__be32 reg;
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH ath-next v4 3/8] wifi: ath9k_htc: refuse a command that fills whole USB packets
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 1/8] wifi: ath9k: return an error from a failed multi-register read Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 2/8] wifi: ath9k_htc: fix the byte count of a full RMW buffer flush Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 4/8] wifi: ath9k: stop a failed register read from opening the RX filter Nerijus Bendžiūnas
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
The firmware ends a command on the interrupt OUT endpoint only on a
short packet, so a command whose length is a multiple of the packet
size is never delivered and the device stops answering WMI. The
previous patch removes the one such command; nothing prevents the next.
Refuse such a command in hif_usb_send_regout(), where the packet size
is known, with a warning.
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
drivers/net/wireless/ath/ath9k/hif_usb.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c
index d3491ff08e6e..4b57cc2f3d6c 100644
--- a/drivers/net/wireless/ath/ath9k/hif_usb.c
+++ b/drivers/net/wireless/ath/ath9k/hif_usb.c
@@ -99,10 +99,17 @@ static void hif_usb_regout_cb(struct urb *urb)
static int hif_usb_send_regout(struct hif_device_usb *hif_dev,
struct sk_buff *skb)
{
+ u16 maxpacket = usb_maxpacket(hif_dev->udev,
+ usb_sndintpipe(hif_dev->udev,
+ USB_REG_OUT_PIPE));
struct urb *urb;
struct cmd_buf *cmd;
int ret = 0;
+ if (WARN_ONCE(maxpacket && skb->len % maxpacket == 0,
+ "%u-byte command fills whole USB packets\n", skb->len))
+ return -EMSGSIZE;
+
urb = usb_alloc_urb(0, GFP_KERNEL);
if (urb == NULL)
return -ENOMEM;
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH ath-next v4 4/8] wifi: ath9k: stop a failed register read from opening the RX filter
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
` (2 preceding siblings ...)
2026-09-16 17:34 ` [PATCH ath-next v4 3/8] wifi: ath9k_htc: refuse a command that fills whole USB packets Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 5/8] wifi: ath9k: clear the PHY error filter when a spectral scan is disabled Nerijus Bendžiūnas
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
On USB a register read is a WMI round trip and a timeout returns -1.
The spectral trigger reads AR_RX_FILTER back, ORs in the PHY error
bits and writes it, so a timed-out read stores 0xffffffff and the
device forwards every frame and PHY error to the host.
Add ath9k_hw_enable_rxfilter(), which sets the requested bits with
REG_SET_BIT() instead of reading the register from the driver, and
use it in the trigger. With firmware 1.4 and later the
read-modify-write is done by the firmware; older firmware still reads
from the host.
Fixes: e93d083f42a1 ("ath9k: add spectral scan feature")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
.../net/wireless/ath/ath9k/common-spectral.c | 7 ++----
drivers/net/wireless/ath/ath9k/hw.c | 23 +++++++++++++++++++
drivers/net/wireless/ath/ath9k/hw.h | 1 +
3 files changed, 26 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..7cafd6c5870f 100644
--- a/drivers/net/wireless/ath/ath9k/hw.c
+++ b/drivers/net/wireless/ath/ath9k/hw.c
@@ -2905,6 +2905,29 @@ void ath9k_hw_setrxfilter(struct ath_hw *ah, u32 bits)
}
EXPORT_SYMBOL(ath9k_hw_setrxfilter);
+/**
+ * ath9k_hw_enable_rxfilter() - set RX filter bits without a driver-side read
+ * @ah: the atheros hardware data structure
+ * @bits: ATH9K_RX_FILTER_* bits to set
+ */
+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);
+ 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);
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH ath-next v4 5/8] wifi: ath9k: clear the PHY error filter when a spectral scan is disabled
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
` (3 preceding siblings ...)
2026-09-16 17:34 ` [PATCH ath-next v4 4/8] wifi: ath9k: stop a failed register read from opening the RX filter Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 6/8] wifi: ath9k: count spectral samples in the driver's own RX stats Nerijus Bendžiūnas
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
Triggering a spectral scan sets the PHY error bits in AR_RX_FILTER and
AR_PHY_ERR and sets AR_RXCFG_ZLFDMA; disabling it clears none of them.
ath9k_htc carries the hardware bits into every later filter update, so
an idle device keeps forwarding every PHY error as a zero-length frame.
Clear all three when the scan is disabled, unless radar detection is
enabled and needs the same bits. AR_RXCFG_ZLFDMA must go too, or the
frames keep coming.
Fixes: e93d083f42a1 ("ath9k: add spectral scan feature")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
.../net/wireless/ath/ath9k/common-spectral.c | 5 ++++
drivers/net/wireless/ath/ath9k/hw.c | 23 +++++++++++++++++++
drivers/net/wireless/ath/ath9k/hw.h | 1 +
3 files changed, 29 insertions(+)
diff --git a/drivers/net/wireless/ath/ath9k/common-spectral.c b/drivers/net/wireless/ath/ath9k/common-spectral.c
index 73c1eb4ebe0e..24000d5b2a6f 100644
--- a/drivers/net/wireless/ath/ath9k/common-spectral.c
+++ b/drivers/net/wireless/ath/ath9k/common-spectral.c
@@ -776,6 +776,11 @@ int ath9k_cmn_spectral_scan_config(struct ath_common *common,
ath_ps_ops(common)->wakeup(common);
ath9k_hw_ops(ah)->spectral_scan_config(ah, &spec_priv->spec_config);
+
+ if (spectral_mode == SPECTRAL_DISABLED &&
+ !common->hw->conf.radar_enabled)
+ ath9k_hw_disable_rxfilter(ah, ATH9K_RX_FILTER_PHYRADAR |
+ ATH9K_RX_FILTER_PHYERR);
ath_ps_ops(common)->restore(common);
spec_priv->spectral_mode = spectral_mode;
diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c
index 7cafd6c5870f..b8f3a8afaeb3 100644
--- a/drivers/net/wireless/ath/ath9k/hw.c
+++ b/drivers/net/wireless/ath/ath9k/hw.c
@@ -2928,6 +2928,29 @@ void ath9k_hw_enable_rxfilter(struct ath_hw *ah, u32 bits)
}
EXPORT_SYMBOL(ath9k_hw_enable_rxfilter);
+/**
+ * ath9k_hw_disable_rxfilter() - clear RX filter bits without a driver-side read
+ * @ah: the atheros hardware data structure
+ * @bits: ATH9K_RX_FILTER_* bits to clear
+ */
+void ath9k_hw_disable_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_CLR_BIT(ah, AR_RX_FILTER, bits);
+
+ if (phybits) {
+ REG_CLR_BIT(ah, AR_PHY_ERR, phybits);
+ REG_CLR_BIT(ah, AR_RXCFG, AR_RXCFG_ZLFDMA);
+ }
+}
+EXPORT_SYMBOL(ath9k_hw_disable_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 f102f73a0114..983146208f26 100644
--- a/drivers/net/wireless/ath/ath9k/hw.h
+++ b/drivers/net/wireless/ath/ath9k/hw.h
@@ -1056,6 +1056,7 @@ void ath9k_hw_get_channel_centers(struct ath_hw *ah,
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);
+void ath9k_hw_disable_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);
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH ath-next v4 6/8] wifi: ath9k: count spectral samples in the driver's own RX stats
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
` (4 preceding siblings ...)
2026-09-16 17:34 ` [PATCH ath-next v4 5/8] wifi: ath9k: clear the PHY error filter when a spectral scan is disabled Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 7/8] wifi: ath9k_htc: pass CRC-tagged spectral samples to the FFT parser Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 8/8] wifi: ath9k_htc: derive the PHY error filter bits from software state Nerijus Bendžiūnas
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
ath_cmn_process_fft() is shared by ath9k and ath9k_htc, but it casts
common->priv to struct ath_softc to reach the rx_spectral_sample_good
and rx_spectral_sample_err counters. On ath9k_htc common->priv is a
struct ath9k_htc_priv, a much smaller structure, and with
CONFIG_ATH9K_DEBUGFS the increment lands far past the end of that
allocation, once per FFT sample.
Store a pointer to the driver's struct ath_rx_stats in struct
ath_spec_scan_priv and count through it. Both drivers pass their own
stats to ath9k_cmn_spectral_init_debug(), and both print them in the
shared recv debugfs file, so the two counters now also work on
ath9k_htc. Without debugfs the pointer stays NULL and nothing is
counted, as before.
Fixes: 03224678c013 ("ath9k: add counters for good and errorneous FFT/spectral frames")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
.../net/wireless/ath/ath9k/common-spectral.c | 29 ++++++++++++-------
.../net/wireless/ath/ath9k/common-spectral.h | 10 +++++--
drivers/net/wireless/ath/ath9k/debug.c | 3 +-
.../net/wireless/ath/ath9k/htc_drv_debug.c | 3 +-
4 files changed, 31 insertions(+), 14 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/common-spectral.c b/drivers/net/wireless/ath/ath9k/common-spectral.c
index 24000d5b2a6f..44b5a5bbe74a 100644
--- a/drivers/net/wireless/ath/ath9k/common-spectral.c
+++ b/drivers/net/wireless/ath/ath9k/common-spectral.c
@@ -465,6 +465,20 @@ ath_cmn_is_fft_buf_full(struct ath_spec_scan_priv *spec_priv)
return 0;
}
+static void ath_cmn_count_fft_sample(struct ath_spec_scan_priv *spec_priv,
+ int ret)
+{
+ struct ath_rx_stats *rx_stats = spec_priv->rx_stats;
+
+ if (!rx_stats)
+ return;
+
+ if (ret == 0)
+ rx_stats->rx_spectral_sample_good++;
+ else
+ rx_stats->rx_spectral_sample_err++;
+}
+
/* returns 1 if this was a spectral frame, even if not handled. */
int ath_cmn_process_fft(struct ath_spec_scan_priv *spec_priv, struct ieee80211_hdr *hdr,
struct ath_rx_status *rs, u64 tsf)
@@ -472,7 +486,6 @@ int ath_cmn_process_fft(struct ath_spec_scan_priv *spec_priv, struct ieee80211_h
u8 sample_buf[SPECTRAL_SAMPLE_MAX_LEN] = {0};
struct ath_hw *ah = spec_priv->ah;
struct ath_common *common = ath9k_hw_common(spec_priv->ah);
- struct ath_softc *sc = common->priv;
u8 num_bins, *vdata = (u8 *)hdr;
struct ath_radar_info *radar_info;
int len = rs->rs_datalen;
@@ -624,10 +637,7 @@ int ath_cmn_process_fft(struct ath_spec_scan_priv *spec_priv, struct ieee80211_h
ret = fft_handler(rs, spec_priv, sample_buf,
tsf, freq, chan_type);
- if (ret == 0)
- RX_STAT_INC(sc, rx_spectral_sample_good);
- else
- RX_STAT_INC(sc, rx_spectral_sample_err);
+ ath_cmn_count_fft_sample(spec_priv, ret);
/* Mix the received bins to the /dev/random
* pool
@@ -642,10 +652,7 @@ int ath_cmn_process_fft(struct ath_spec_scan_priv *spec_priv, struct ieee80211_h
ret = fft_handler(rs, spec_priv, sample_start,
tsf, freq, chan_type);
- if (ret == 0)
- RX_STAT_INC(sc, rx_spectral_sample_good);
- else
- RX_STAT_INC(sc, rx_spectral_sample_err);
+ ath_cmn_count_fft_sample(spec_priv, ret);
/* Mix the received bins to the /dev/random
* pool
@@ -1054,8 +1061,10 @@ void ath9k_cmn_spectral_deinit_debug(struct ath_spec_scan_priv *spec_priv)
EXPORT_SYMBOL(ath9k_cmn_spectral_deinit_debug);
void ath9k_cmn_spectral_init_debug(struct ath_spec_scan_priv *spec_priv,
- struct dentry *debugfs_phy)
+ struct dentry *debugfs_phy,
+ struct ath_rx_stats *rx_stats)
{
+ spec_priv->rx_stats = rx_stats;
spec_priv->rfs_chan_spec_scan = relay_open("spectral_scan",
debugfs_phy,
1024, 256, &rfs_spec_scan_cb,
diff --git a/drivers/net/wireless/ath/ath9k/common-spectral.h b/drivers/net/wireless/ath/ath9k/common-spectral.h
index 011d8ab8b974..6c397b8726b9 100644
--- a/drivers/net/wireless/ath/ath9k/common-spectral.h
+++ b/drivers/net/wireless/ath/ath9k/common-spectral.h
@@ -94,12 +94,15 @@ struct ath_ht20_40_fft_packet {
struct ath_radar_info radar_info;
} __packed;
+struct ath_rx_stats;
+
struct ath_spec_scan_priv {
struct ath_hw *ah;
/* relay(fs) channel for spectral scan */
struct rchan *rfs_chan_spec_scan;
enum spectral_mode spectral_mode;
struct ath_spec_scan spec_config;
+ struct ath_rx_stats *rx_stats;
};
#define SPECTRAL_HT20_40_TOTAL_DATA_LEN (sizeof(struct ath_ht20_40_fft_packet))
@@ -169,7 +172,9 @@ static inline u8 spectral_bitmap_weight(u8 *bins)
}
#ifdef CONFIG_ATH9K_COMMON_SPECTRAL
-void ath9k_cmn_spectral_init_debug(struct ath_spec_scan_priv *spec_priv, struct dentry *debugfs_phy);
+void ath9k_cmn_spectral_init_debug(struct ath_spec_scan_priv *spec_priv,
+ struct dentry *debugfs_phy,
+ struct ath_rx_stats *rx_stats);
void ath9k_cmn_spectral_deinit_debug(struct ath_spec_scan_priv *spec_priv);
void ath9k_cmn_spectral_scan_trigger(struct ath_common *common,
@@ -181,7 +186,8 @@ int ath_cmn_process_fft(struct ath_spec_scan_priv *spec_priv, struct ieee80211_h
struct ath_rx_status *rs, u64 tsf);
#else
static inline void ath9k_cmn_spectral_init_debug(struct ath_spec_scan_priv *spec_priv,
- struct dentry *debugfs_phy)
+ struct dentry *debugfs_phy,
+ struct ath_rx_stats *rx_stats)
{
}
diff --git a/drivers/net/wireless/ath/ath9k/debug.c b/drivers/net/wireless/ath/ath9k/debug.c
index 74a0134075cf..042a4f542a94 100644
--- a/drivers/net/wireless/ath/ath9k/debug.c
+++ b/drivers/net/wireless/ath/ath9k/debug.c
@@ -1389,7 +1389,8 @@ int ath9k_init_debug(struct ath_hw *ah)
ath9k_dfs_init_debug(sc);
ath9k_tx99_init_debug(sc);
- ath9k_cmn_spectral_init_debug(&sc->spec_priv, sc->debug.debugfs_phy);
+ ath9k_cmn_spectral_init_debug(&sc->spec_priv, sc->debug.debugfs_phy,
+ &sc->debug.stats.rxstats);
debugfs_create_devm_seqfile(sc->dev, "dma", sc->debug.debugfs_phy,
read_file_dma);
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_debug.c b/drivers/net/wireless/ath/ath9k/htc_drv_debug.c
index 9437d69877cc..9d354b1d929c 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_debug.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_debug.c
@@ -487,7 +487,8 @@ int ath9k_htc_init_debug(struct ath_hw *ah)
priv->debug.debugfs_phy = debugfs_create_dir(KBUILD_MODNAME,
priv->hw->wiphy->debugfsdir);
- ath9k_cmn_spectral_init_debug(&priv->spec_priv, priv->debug.debugfs_phy);
+ ath9k_cmn_spectral_init_debug(&priv->spec_priv, priv->debug.debugfs_phy,
+ &priv->debug.rx_stats);
debugfs_create_file("tgt_int_stats", 0400, priv->debug.debugfs_phy,
priv, &fops_tgt_int_stats);
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH ath-next v4 7/8] wifi: ath9k_htc: pass CRC-tagged spectral samples to the FFT parser
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
` (5 preceding siblings ...)
2026-09-16 17:34 ` [PATCH ath-next v4 6/8] wifi: ath9k: count spectral samples in the driver's own RX stats Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
2026-09-16 17:34 ` [PATCH ath-next v4 8/8] wifi: ath9k_htc: derive the PHY error filter bits from software state Nerijus Bendžiūnas
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
The AR9271 firmware reports a frame that failed its CRC as a CRC error
even when the PHY error bit is set too, so spectral samples received
under interference reach the host as CRC errors, and the host passes
only PHY errors to the FFT parser. ath9k reordered the same check in
commit 3a325565c7fa ("ath9k: reorder error codes for spectral"); the
firmware never followed.
While a scan is active, also pass a CRC error frame of FFT report size
to the parser, with the PHY error code it expects. The parser rejects a
frame without the spectral bit in its trailer. A monitor interface with
FIF_FCSFAIL no longer sees those frames.
Fixes: 83fb287ecd8a ("ath9k_htc: process rx spectral packets")
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
drivers/net/wireless/ath/ath9k/htc_drv_txrx.c | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
index bed7ea2425a0..23d1ef2407a4 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
@@ -969,6 +969,25 @@ static void rx_status_htc_to_ath(struct ath_rx_status *rx_stats,
convert_htc_flag(rx_stats, rxstatus);
}
+static bool ath9k_htc_is_spectral_sample_len(struct ath9k_htc_priv *priv,
+ u16 len)
+{
+ enum nl80211_channel_type chan_type;
+ u16 fft_len;
+
+ if (priv->spec_priv.spectral_mode == SPECTRAL_DISABLED)
+ return false;
+
+ chan_type = cfg80211_get_chandef_type(&priv->hw->conf.chandef);
+ if (chan_type == NL80211_CHAN_HT40MINUS ||
+ chan_type == NL80211_CHAN_HT40PLUS)
+ fft_len = SPECTRAL_HT20_40_TOTAL_DATA_LEN;
+ else
+ fft_len = SPECTRAL_HT20_TOTAL_DATA_LEN;
+
+ return len >= fft_len - 1 && len <= fft_len + 2;
+}
+
static bool ath9k_rx_prepare(struct ath9k_htc_priv *priv,
struct ath9k_htc_rxbuf *rxbuf,
struct ieee80211_rx_status *rx_status)
@@ -1052,6 +1071,16 @@ static bool ath9k_rx_prepare(struct ath9k_htc_priv *priv,
goto rx_next;
}
+ if (unlikely(rx_stats.rs_status & ATH9K_RXERR_CRC) &&
+ ath9k_htc_is_spectral_sample_len(priv, rs_datalen)) {
+ struct ath_rx_status sample_rs = rx_stats;
+
+ sample_rs.rs_phyerr = ATH9K_PHYERR_RADAR;
+ if (ath_cmn_process_fft(&priv->spec_priv, hdr, &sample_rs,
+ rx_status->mactime))
+ goto rx_next;
+ }
+
if (!ath9k_cmn_rx_accept(common, hdr, rx_status, &rx_stats,
&decrypt_error, priv->rxfilter))
goto rx_next;
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH ath-next v4 8/8] wifi: ath9k_htc: derive the PHY error filter bits from software state
2026-09-16 17:34 [PATCH ath-next v4 0/8] wifi: ath9k: fix register access and spectral scan filter handling on ath9k_htc Nerijus Bendžiūnas
` (6 preceding siblings ...)
2026-09-16 17:34 ` [PATCH ath-next v4 7/8] wifi: ath9k_htc: pass CRC-tagged spectral samples to the FFT parser Nerijus Bendžiūnas
@ 2026-09-16 17:34 ` Nerijus Bendžiūnas
7 siblings, 0 replies; 9+ messages in thread
From: Nerijus Bendžiūnas @ 2026-09-16 17:34 UTC (permalink / raw)
To: Toke Høiland-Jørgensen
Cc: linux-wireless, linux-kernel, John W . Linville,
Simon Wunderlich, Oleksij Rempel, Kalle Valo, Jeff Johnson,
stable
ath9k_htc_calcrxfilter() reads the RX filter back from the device to
preserve the PHY error bits a spectral scan may have set. When that WMI
read times out it returns all ones, and the recalculated filter forwards
every PHY error to the host with no scan running.
Set the two bits from the spectral mode instead. A mode written to
spectral_scan_ctl now sets them at the next filter update, not only at
the trigger.
Fixes: fb9987d0f748 ("ath9k_htc: Support for AR9271 chipset.")
Assisted-by: LLM
Signed-off-by: Nerijus Bendžiūnas <nerijus.bendziunas@gmail.com>
---
drivers/net/wireless/ath/ath9k/htc_drv_txrx.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
index 23d1ef2407a4..1a051d23cf2a 100644
--- a/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
+++ b/drivers/net/wireless/ath/ath9k/htc_drv_txrx.c
@@ -868,14 +868,14 @@ int ath9k_htc_cabq_setup(struct ath9k_htc_priv *priv)
*/
u32 ath9k_htc_calcrxfilter(struct ath9k_htc_priv *priv)
{
-#define RX_FILTER_PRESERVE (ATH9K_RX_FILTER_PHYERR | ATH9K_RX_FILTER_PHYRADAR)
-
struct ath_hw *ah = priv->ah;
u32 rfilt;
- rfilt = (ath9k_hw_getrxfilter(ah) & RX_FILTER_PRESERVE)
- | ATH9K_RX_FILTER_UCAST | ATH9K_RX_FILTER_BCAST
- | ATH9K_RX_FILTER_MCAST;
+ rfilt = ATH9K_RX_FILTER_UCAST | ATH9K_RX_FILTER_BCAST |
+ ATH9K_RX_FILTER_MCAST;
+
+ if (priv->spec_priv.spectral_mode != SPECTRAL_DISABLED)
+ rfilt |= ATH9K_RX_FILTER_PHYRADAR | ATH9K_RX_FILTER_PHYERR;
if (priv->rxfilter & FIF_PROBE_REQ)
rfilt |= ATH9K_RX_FILTER_PROBEREQ;
@@ -906,8 +906,6 @@ u32 ath9k_htc_calcrxfilter(struct ath9k_htc_priv *priv)
rfilt |= ATH9K_RX_FILTER_MCAST_BCAST_ALL;
return rfilt;
-
-#undef RX_FILTER_PRESERVE
}
/*
--
2.55.0
^ permalink raw reply [flat|nested] 9+ messages in thread