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 <[email protected]>
This commit is contained in:
Felix Fietkau
2026-08-08 14:43:09 +02:00
parent 2c7b3011d2
commit 503c643b78
2 changed files with 17 additions and 3 deletions
+7
View File
@@ -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)
{
+10 -3
View File
@@ -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) {