[PATCH ath-next v5 8/9] wifi: ath11k: stop a scheduling round when the hardware refuses a frame
From: Julius Bairaktaris
Date: Sun Oct 18 2026 - 13:50:21 EST
ath11k_dp_tx() returns -ENOSPC when the MSDU idr is full and -ENOMEM
when no TCL descriptor is free or a DMA mapping fails; it frees the
frame, and the round then dequeues and frees the next frame of the same
flow. End the round on those errors and on -ENOSPC from a full
management queue, leaving the frames behind queued; other errors concern
only the frame. A category whose round ended this way or on a full TCL
ring may have nothing in flight, so mark it and start its round from the
next tx completion on the device.
Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.9.0.1-02146-QCAHKSWPL_SILICONZ-1
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@xxxxxxxxxxxxxx>
---
drivers/net/wireless/ath/ath11k/core.h | 2 ++
drivers/net/wireless/ath/ath11k/dp_tx.c | 10 ++++++++-
drivers/net/wireless/ath/ath11k/mac.c | 30 +++++++++++++++++++------
3 files changed, 34 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/ath/ath11k/core.h b/drivers/net/wireless/ath/ath11k/core.h
index 40895d276de5f..8b7bfb0cefcf2 100644
--- a/drivers/net/wireless/ath/ath11k/core.h
+++ b/drivers/net/wireless/ath/ath11k/core.h
@@ -713,6 +713,8 @@ struct ath11k {
* other contexts that drive it
*/
spinlock_t txq_lock[IEEE80211_NUM_ACS];
+ /* access categories whose round ended for lack of room */
+ unsigned long txq_starved;
struct list_head arvifs;
/* should never be NULL; needed for regular htt rx */
diff --git a/drivers/net/wireless/ath/ath11k/dp_tx.c b/drivers/net/wireless/ath/ath11k/dp_tx.c
index 5bcdeeb7e65ad..fb6a43dc45b81 100644
--- a/drivers/net/wireless/ath/ath11k/dp_tx.c
+++ b/drivers/net/wireless/ath/ath11k/dp_tx.c
@@ -776,8 +776,16 @@ void ath11k_dp_tx_completion_handler(struct ath11k_base *ab, int ring_id)
/* A completion returns the airtime the frame was charged, which is
* what an airtime-limited station in that access category is waiting
* on. The arrivals that would otherwise start a round are themselves
- * queued behind that limit.
+ * queued behind that limit. It also frees the ring space and MSDU ids
+ * that a round ended early on any radio is waiting on.
*/
+ for (i = 0; i < ab->num_radios; i++) {
+ ar = ab->pdevs[i].ar;
+ if (unlikely(READ_ONCE(ar->txq_starved)))
+ push |= xchg(&ar->txq_starved, 0) <<
+ (i * IEEE80211_NUM_ACS);
+ }
+
for_each_set_bit(i, &push, ab->num_radios * IEEE80211_NUM_ACS)
ath11k_mac_tx_push_pending(ab->pdevs[i / IEEE80211_NUM_ACS].ar,
i % IEEE80211_NUM_ACS);
diff --git a/drivers/net/wireless/ath/ath11k/mac.c b/drivers/net/wireless/ath/ath11k/mac.c
index d17f80cbcdb5d..6b9cc54a7f5cb 100644
--- a/drivers/net/wireless/ath/ath11k/mac.c
+++ b/drivers/net/wireless/ath/ath11k/mac.c
@@ -6519,9 +6519,9 @@ static int ath11k_mac_mgmt_tx(struct ath11k *ar, struct sk_buff *skb,
return 0;
}
-static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
- struct ieee80211_tx_control *control,
- struct sk_buff *skb)
+static int ath11k_mac_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
{
struct ath11k_skb_cb *skb_cb = ATH11K_SKB_CB(skb);
struct ath11k *ar = hw->priv;
@@ -6553,7 +6553,7 @@ static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
ret);
ieee80211_free_txskb(ar->hw, skb);
}
- return;
+ return ret;
}
if (control->sta)
@@ -6564,6 +6564,15 @@ static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
ath11k_warn(ar->ab, "failed to transmit frame %d\n", ret);
ieee80211_free_txskb(ar->hw, skb);
}
+
+ return ret;
+}
+
+static void ath11k_mac_op_tx(struct ieee80211_hw *hw,
+ struct ieee80211_tx_control *control,
+ struct sk_buff *skb)
+{
+ ath11k_mac_tx(hw, control, skb);
}
void ath11k_mac_drain_tx(struct ath11k *ar)
@@ -10090,6 +10099,7 @@ static int ath11k_mac_tx_push_txq(struct ath11k *ar, struct ieee80211_txq *txq)
struct sk_buff *skb;
u32 ring_selector;
int num_free;
+ int ret;
u8 ring_id;
ring_selector = ar->ab->hw_params.hw_ops->get_ring_selector(txq->ac);
@@ -10099,7 +10109,8 @@ static int ath11k_mac_tx_push_txq(struct ath11k *ar, struct ieee80211_txq *txq)
/* ieee80211_tx_dequeue() applies the airtime queue limit, so a
* selection ends where that limit binds or the queue empties. A full
- * TCL ring ends the whole round.
+ * TCL ring ends the whole round, and so does a frame the hardware
+ * refuses.
*/
while (1) {
if (unlikely(test_bit(ATH11K_FLAG_CRASH_FLUSH,
@@ -10123,9 +10134,12 @@ static int ath11k_mac_tx_push_txq(struct ath11k *ar, struct ieee80211_txq *txq)
break;
}
- ath11k_mac_op_tx(hw, &control, skb);
+ ret = ath11k_mac_tx(hw, &control, skb);
spin_unlock_bh(&tx_ring->wake_tx_lock);
+
+ if (unlikely(ret == -ENOSPC || ret == -ENOMEM))
+ return ret;
}
return 0;
@@ -10143,8 +10157,10 @@ static void ath11k_mac_schedule_txq(struct ath11k *ar, u8 ac)
int ret = ath11k_mac_tx_push_txq(ar, txq);
ieee80211_return_txq(hw, txq, false);
- if (unlikely(ret))
+ if (unlikely(ret)) {
+ set_bit(ac, &ar->txq_starved);
break;
+ }
}
ieee80211_txq_schedule_end(hw, ac);
--
2.53.0