From 447cc8c89c172297c527d733a915e0d05aa4a813 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Sat, 7 Mar 2026 19:22:34 +0000 Subject: [PATCH] wifi: mt76: wait for firmware TX completion of mgmt frames before channel switch After flushing software-pending frames to DMA, mt76_has_tx_pending() only checks DMA ring q->queued. For token-based drivers, q->queued is decremented at DMA consumption, but firmware may not have transmitted the frame yet. Waiting for all tokens is not feasible because data frames may be stuck in firmware powersave/aggregation queues. Track PSD queue tokens (firmware ALTX) per phy using an atomic counter. These frames are sent by firmware immediately without PS buffering, so the counter reliably reaches zero after transmission. Increment the counter in mt76_token_consume() and decrement it in mt76_token_release(), only for PSD queue tokens. Include the counter in mt76_has_tx_pending() so channel switch waits for firmware TX completion of management and nullfunc frames. mt7615 (uses mt76_token_get/put) and non-token drivers are unaffected as they never call mt76_token_consume/release. Signed-off-by: Felix Fietkau --- dma.c | 2 ++ mac80211.c | 3 +++ mt76.h | 3 +++ mt76_connac_mac.c | 6 ++++++ mt7996/mac.c | 6 ++++++ tx.c | 14 +++++++++++++- 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/dma.c b/dma.c index 2d133ace..f8c2fe5f 100644 --- a/dma.c +++ b/dma.c @@ -666,6 +666,8 @@ mt76_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q, if (!t) goto free_skb; + t->phy_idx = phy->band_idx; + t->qid = qid; txwi = mt76_get_txwi_ptr(dev, t); skb->prev = skb->next = NULL; diff --git a/mac80211.c b/mac80211.c index bd85a470..103d617f 100644 --- a/mac80211.c +++ b/mac80211.c @@ -972,6 +972,9 @@ bool mt76_has_tx_pending(struct mt76_phy *phy) return true; } + if (atomic_read(&phy->mgmt_tx_pending)) + return true; + return false; } EXPORT_SYMBOL_GPL(mt76_has_tx_pending); diff --git a/mt76.h b/mt76.h index ad035fca..ad6c85a7 100644 --- a/mt76.h +++ b/mt76.h @@ -462,6 +462,7 @@ struct mt76_txwi_cache { }; u8 qid; + u8 phy_idx; }; struct mt76_rx_tid { @@ -872,6 +873,8 @@ struct mt76_phy { struct list_head tx_list; struct mt76_queue *q_tx[__MT_TXQ_MAX]; + atomic_t mgmt_tx_pending; + struct cfg80211_chan_def chandef; struct cfg80211_chan_def main_chandef; bool offchannel; diff --git a/mt76_connac_mac.c b/mt76_connac_mac.c index 15d8a6da..ad539b22 100644 --- a/mt76_connac_mac.c +++ b/mt76_connac_mac.c @@ -1209,5 +1209,11 @@ void mt76_connac2_tx_token_put(struct mt76_dev *dev) } spin_unlock_bh(&dev->token_lock); idr_destroy(&dev->token); + + for (id = 0; id < __MT_MAX_BAND; id++) { + struct mt76_phy *phy = dev->phys[id]; + if (phy) + atomic_set(&phy->mgmt_tx_pending, 0); + } } EXPORT_SYMBOL_GPL(mt76_connac2_tx_token_put); diff --git a/mt7996/mac.c b/mt7996/mac.c index 9112abcd..ed5f7ecf 100644 --- a/mt7996/mac.c +++ b/mt7996/mac.c @@ -2215,6 +2215,12 @@ void mt7996_tx_token_put(struct mt7996_dev *dev) } spin_unlock_bh(&dev->mt76.token_lock); idr_destroy(&dev->mt76.token); + + for (id = 0; id < __MT_MAX_BAND; id++) { + struct mt76_phy *phy = dev->mt76.phys[id]; + if (phy) + atomic_set(&phy->mgmt_tx_pending, 0); + } } static int diff --git a/tx.c b/tx.c index 7b0fae69..22f96906 100644 --- a/tx.c +++ b/tx.c @@ -866,9 +866,15 @@ int mt76_token_consume(struct mt76_dev *dev, struct mt76_txwi_cache **ptxwi) token = idr_alloc(&dev->token, *ptxwi, dev->token_start, dev->token_start + dev->token_size, GFP_ATOMIC); - if (token >= dev->token_start) + if (token >= dev->token_start) { dev->token_count++; + if ((*ptxwi)->qid == MT_TXQ_PSD) { + struct mt76_phy *mphy = mt76_dev_phy(dev, (*ptxwi)->phy_idx); + atomic_inc(&mphy->mgmt_tx_pending); + } + } + #ifdef CONFIG_NET_MEDIATEK_SOC_WED if (mtk_wed_device_active(&dev->mmio.wed) && token >= dev->mmio.wed.wlan.token_start) @@ -913,6 +919,12 @@ mt76_token_release(struct mt76_dev *dev, int token, bool *wake) if (txwi) { dev->token_count--; + if (txwi->qid == MT_TXQ_PSD) { + struct mt76_phy *mphy = mt76_dev_phy(dev, txwi->phy_idx); + if (atomic_dec_and_test(&mphy->mgmt_tx_pending)) + wake_up(&dev->tx_wait); + } + #ifdef CONFIG_NET_MEDIATEK_SOC_WED if (mtk_wed_device_active(&dev->mmio.wed) && token >= dev->mmio.wed.wlan.token_start &&