[PATCH v3 3/5] wifi: ath9k: check all tx queues with one multi-read
From: Nerijus Bendžiūnas <hidden>
Date: 2026-09-04 18:53:15
Also in:
lkml
Subsystem:
atheros ath generic utilities, qualcomm atheros ath9k wireless driver, the rest · Maintainers:
Jeff Johnson, Toke Høiland-Jørgensen, Linus Torvalds
Before a channel change, ath9k_hw_channel_change() calls ath9k_hw_numtxpending() for each of the 10 queues. Each call reads AR_QSTS and then AR_Q_TXE, so confirming that the radio has drained takes up to 20 register reads. On the USB devices each read is a synchronous WMI round trip, paid on every channel change before tuning starts. Add ath9k_hw_first_txpending(), which collects the 10 queue status registers and AR_Q_TXE with REG_READ_MULTI() and applies the same pending test. Use it in ath9k_hw_channel_change(). On PCI the multi-read is a loop of single reads, so the register traffic there is unchanged. A multi-read that fails over USB returns all ones, which counts as pending, so a lost read still fails the fast channel change and the caller falls back to a full reset. Assisted-by: Claude:claude-fable-5-1 Signed-off-by: Nerijus Bendžiūnas <redacted> --- drivers/net/wireless/ath/ath9k/hw.c | 13 +++++---- drivers/net/wireless/ath/ath9k/mac.c | 40 ++++++++++++++++++++++++++++ drivers/net/wireless/ath/ath9k/mac.h | 1 + 3 files changed, 47 insertions(+), 7 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/hw.c b/drivers/net/wireless/ath/ath9k/hw.c
index e08ab73fcacb..d204cdf3fa8f 100644
--- a/drivers/net/wireless/ath/ath9k/hw.c
+++ b/drivers/net/wireless/ath/ath9k/hw.c@@ -1547,7 +1547,7 @@ static bool ath9k_hw_channel_change(struct ath_hw *ah, struct ath9k_hw_capabilities *pCap = &ah->caps; bool band_switch = false, mode_diff = false; u8 ini_reloaded = 0; - u32 qnum; + int qnum; int r; if (pCap->hw_caps & ATH9K_HW_CAP_FCC_BAND_SWITCH) {
@@ -1556,12 +1556,11 @@ static bool ath9k_hw_channel_change(struct ath_hw *ah, mode_diff = !!(flags_diff & ~CHANNEL_HT); } - for (qnum = 0; qnum < AR_NUM_QCU; qnum++) { - if (ath9k_hw_numtxpending(ah, qnum)) { - ath_dbg(common, QUEUE, - "Transmit frames pending on queue %d\n", qnum); - return false; - } + qnum = ath9k_hw_first_txpending(ah); + if (qnum >= 0) { + ath_dbg(common, QUEUE, + "Transmit frames pending on queue %d\n", qnum); + return false; } if (!ath9k_hw_rfbus_req(ah)) {
diff --git a/drivers/net/wireless/ath/ath9k/mac.c b/drivers/net/wireless/ath/ath9k/mac.c
index b070403e083f..27926b67d628 100644
--- a/drivers/net/wireless/ath/ath9k/mac.c
+++ b/drivers/net/wireless/ath/ath9k/mac.c@@ -77,6 +77,46 @@ u32 ath9k_hw_numtxpending(struct ath_hw *ah, u32 q) } EXPORT_SYMBOL(ath9k_hw_numtxpending); +/** + * ath9k_hw_first_txpending - find a tx queue that still has frames pending + * @ah: hardware + * + * Asking ath9k_hw_numtxpending() about each queue in turn costs up to two + * register reads per queue, and on the USB devices every one of those is a + * synchronous WMI round trip. Collect the queue status registers and AR_Q_TXE + * with the multi-read op instead, in chunks of ATH9K_MULTI_READ_MAX. A + * multi-read that fails over USB reads as all ones, which counts as pending, + * so a WMI timeout still refuses the fast channel change. + * + * Return: the first queue with frames pending, or -1 if all are drained. + */ +int ath9k_hw_first_txpending(struct ath_hw *ah) +{ + u32 addr[AR_NUM_QCU + 1]; + u32 val[AR_NUM_QCU + 1]; + u32 q, txe, done = 0; + + for (q = 0; q < AR_NUM_QCU; q++) + addr[q] = AR_QSTS(q); + addr[AR_NUM_QCU] = AR_Q_TXE; + + while (done < ARRAY_SIZE(addr)) { + u32 count = min_t(u32, ARRAY_SIZE(addr) - done, + ATH9K_MULTI_READ_MAX); + + REG_READ_MULTI(ah, addr + done, val + done, count); + done += count; + } + + txe = val[AR_NUM_QCU]; + for (q = 0; q < AR_NUM_QCU; q++) { + if ((val[q] & AR_Q_STS_PEND_FR_CNT) || (txe & BIT(q))) + return q; + } + + return -1; +} + /** * ath9k_hw_updatetxtriglevel - adjusts the frame trigger level *
diff --git a/drivers/net/wireless/ath/ath9k/mac.h b/drivers/net/wireless/ath/ath9k/mac.h
index 16203e7ecf29..5b94ce087be2 100644
--- a/drivers/net/wireless/ath/ath9k/mac.h
+++ b/drivers/net/wireless/ath/ath9k/mac.h@@ -721,6 +721,7 @@ u32 ath9k_hw_gettxbuf(struct ath_hw *ah, u32 q); void ath9k_hw_puttxbuf(struct ath_hw *ah, u32 q, u32 txdp); void ath9k_hw_txstart(struct ath_hw *ah, u32 q); u32 ath9k_hw_numtxpending(struct ath_hw *ah, u32 q); +int ath9k_hw_first_txpending(struct ath_hw *ah); bool ath9k_hw_updatetxtriglevel(struct ath_hw *ah, bool bIncTrigLevel); bool ath9k_hw_stop_dma_queue(struct ath_hw *ah, u32 q); void ath9k_hw_abort_tx_dma(struct ath_hw *ah);
--
2.55.0