From 503c643b78bf5619686edc409e361f9865ba9b86 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 8 Aug 2026 14:42:18 +0200 Subject: [PATCH] wifi: mt76: account non-AQL frames per peer rather than per link wcid->non_aql_packets is charged when a frame reaches the hardware queue and credited back from the completion path. For an MLO peer the hardware reports the completion against the link it actually transmitted on, which need not be the link the frame was queued for, so the charge and the credit can land on different link wcids. The credited side is clamped at zero while the charged side keeps the residue. Once it passes MT_MAX_NON_AQL_PKT, mt76_txq_schedule_list() skips that station on every pass and its traffic stops for good, with the frames left sitting in the mac80211 queues and nothing counted as dropped. Only a reassociation, which allocates new wcids, recovers it. Keep the count on the primary link's wcid, as is already done for the other peer-wide state. Signed-off-by: Felix Fietkau --- mt76.h | 7 +++++++ tx.c | 13 ++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/mt76.h b/mt76.h index 091431e3..1eb997cc 100644 --- a/mt76.h +++ b/mt76.h @@ -1435,6 +1435,13 @@ mtxq_to_txq(struct mt76_txq *mtxq) return container_of(ptr, struct ieee80211_txq, drv_priv); } +/* Peer-wide state lives on the primary link's wcid */ +static inline struct mt76_wcid * +mt76_wcid_primary(struct mt76_wcid *wcid) +{ + return wcid->def_wcid ? wcid->def_wcid : wcid; +} + static inline struct ieee80211_sta * wcid_to_sta(struct mt76_wcid *wcid) { diff --git a/tx.c b/tx.c index e78e807b..5021db06 100644 --- a/tx.c +++ b/tx.c @@ -234,6 +234,8 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, if (!wcid || info->tx_time_est) return; + wcid = mt76_wcid_primary(wcid); + pending = atomic_dec_return(&wcid->non_aql_packets); if (pending < 0) atomic_cmpxchg(&wcid->non_aql_packets, pending, 0); @@ -344,7 +346,10 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (!non_aql) return idx; - pending = atomic_inc_return(&wcid->non_aql_packets); + /* Completions come back on whichever link the hardware used, so + * counting per link would leak the charge on every link switch. + */ + pending = atomic_inc_return(&mt76_wcid_primary(wcid)->non_aql_packets); if (stop && pending >= MT_MAX_NON_AQL_PKT) *stop = true; @@ -527,7 +532,8 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, return 0; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) return 0; skb = mt76_txq_dequeue(phy, mtxq); @@ -636,7 +642,8 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) continue; if (dev->queue_ops->tx_cleanup && q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) {