]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: mt76: wait for firmware TX completion of mgmt frames before channel switch
authorFelix Fietkau <nbd@nbd.name>
Mon, 9 Mar 2026 06:07:29 +0000 (06:07 +0000)
committerFelix Fietkau <nbd@nbd.name>
Tue, 24 Mar 2026 15:49:31 +0000 (15:49 +0000)
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.

Link: https://patch.msgid.link/20260309060730.87840-10-nbd@nbd.name
Signed-off-by: Felix Fietkau <nbd@nbd.name>
drivers/net/wireless/mediatek/mt76/dma.c
drivers/net/wireless/mediatek/mt76/mac80211.c
drivers/net/wireless/mediatek/mt76/mt76.h
drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
drivers/net/wireless/mediatek/mt76/mt7996/mac.c
drivers/net/wireless/mediatek/mt76/tx.c

index 2d133ace7c33ac5492e287a53510ee4b6a6b7403..f8c2fe5f2f58772c39719fc97e8e8d721613cf28 100644 (file)
@@ -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;
index 2ddbfdcae939b45ddccdbb3ea8d588fa0147c572..54ed1cec0228ccd329822e92fd64cac4e98d4571 100644 (file)
@@ -971,6 +971,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);
index 210aafc1e21edc497e552483adb065c1cc4f02e5..bc1153142b713af671980d83bcbe0dd5a54c3a7e 100644 (file)
@@ -450,6 +450,7 @@ struct mt76_txwi_cache {
        };
 
        u8 qid;
+       u8 phy_idx;
 };
 
 struct mt76_rx_tid {
@@ -860,6 +861,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;
index c2cf6893848bfab8d1878f2bb1d5918a33ad0f9e..0339e2e7ab601a027df3933a1b45d594f9a7e187 100644 (file)
@@ -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);
index f5537eba9c6b86dd055610881a0aefb8c1e256e3..7deedd3fcb1916594f2b85e051961744f3495796 100644 (file)
@@ -2220,6 +2220,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
index 7b0fae694f120afdfb2271cb2001b483d344443c..22f9690634c942cbbc7712a403dc3c57e01284ff 100644 (file)
@@ -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 &&