mt76_release_buffered_frames() always clears the more-data bit on the last released frame, so the client stops polling while frames are still buffered for it. The .release_buffered_frames documentation in include/net/mac80211.h requires the bit if @more_data is set or if the released TIDs still hold frames. Set the bit in both cases. Fixes: 17f1de56df05 ("mt76: add common code shared between multiple chipsets") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 29 +++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index b022d3843473..ea9993e9a199 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -420,7 +420,7 @@ mt76_txq_dequeue(struct mt76_phy *phy, struct mt76_txq *mtxq) static void mt76_queue_ps_skb(struct mt76_phy *phy, struct ieee80211_sta *sta, - struct sk_buff *skb, bool last, + struct sk_buff *skb, bool last, bool more_data, enum ieee80211_frame_release_type reason) { struct mt76_wcid *wcid = (struct mt76_wcid *)sta->drv_priv; @@ -437,10 +437,29 @@ mt76_queue_ps_skb(struct mt76_phy *phy, struct ieee80211_sta *sta, *ieee80211_get_qos_ctl(hdr) |= IEEE80211_QOS_CTL_EOSP; } - mt76_skb_set_moredata(skb, !last); + mt76_skb_set_moredata(skb, !last || more_data); __mt76_tx_queue_skb(phy, MT_TXQ_PSD, skb, wcid, sta, NULL); } +static bool +mt76_ps_tids_pending(struct ieee80211_sta *sta, u16 tids) +{ + int i; + + for (i = 0; tids; i++, tids >>= 1) { + unsigned long frames, bytes; + + if (!(tids & 1) || !sta->txq[i]) + continue; + + ieee80211_txq_get_depth(sta->txq[i], &frames, &bytes); + if (frames) + return true; + } + + return false; +} + void mt76_release_buffered_frames(struct ieee80211_hw *hw, struct ieee80211_sta *sta, u16 tids, int nframes, @@ -451,6 +470,7 @@ mt76_release_buffered_frames(struct ieee80211_hw *hw, struct ieee80211_sta *sta, struct mt76_dev *dev = phy->dev; struct sk_buff *last_skb = NULL; struct mt76_queue *hwq = phy->q_tx[MT_TXQ_PSD]; + u16 release_tids = tids; int i; spin_lock_bh(&hwq->lock); @@ -470,14 +490,15 @@ mt76_release_buffered_frames(struct ieee80211_hw *hw, struct ieee80211_sta *sta, nframes--; if (last_skb) mt76_queue_ps_skb(phy, sta, last_skb, false, - reason); + false, reason); last_skb = skb; } while (nframes); } if (last_skb) { - mt76_queue_ps_skb(phy, sta, last_skb, true, reason); + more_data |= mt76_ps_tids_pending(sta, release_tids); + mt76_queue_ps_skb(phy, sta, last_skb, true, more_data, reason); dev->queue_ops->kick(dev, hwq); } else { ieee80211_sta_eosp(sta); -- 2.47.3 mt76x02_mac_wcid_set_drop() changes one bit of an MT_WCID_DROP word, which holds the drop bits of 32 stations, with an unlocked read-modify-write. On MMIO devices it runs from the rx and tx paths, concurrently with station add and remove in process context. One update can thus overwrite another, and the hardware then discards all traffic of a station until its next powersave transition. Take mt76.lock around the update on MMIO devices. On USB devices, only station add and remove reach it. Register access there can sleep, and mt76.mutex already serialises both. Fixes: 7bc04215a66b ("mt76: add driver code for MT76x2e") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x02_mac.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c index 158aa356b710..457767bbd8b6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c @@ -166,7 +166,8 @@ void mt76x02_mac_wcid_setup(struct mt76x02_dev *dev, u8 idx, } EXPORT_SYMBOL_GPL(mt76x02_mac_wcid_setup); -void mt76x02_mac_wcid_set_drop(struct mt76x02_dev *dev, u8 idx, bool drop) +static void +__mt76x02_mac_wcid_set_drop(struct mt76x02_dev *dev, u8 idx, bool drop) { u32 val = mt76_rr(dev, MT_WCID_DROP(idx)); u32 bit = MT_WCID_DROP_MASK(idx); @@ -176,6 +177,19 @@ void mt76x02_mac_wcid_set_drop(struct mt76x02_dev *dev, u8 idx, bool drop) mt76_wr(dev, MT_WCID_DROP(idx), (val & ~bit) | (bit * drop)); } +void mt76x02_mac_wcid_set_drop(struct mt76x02_dev *dev, u8 idx, bool drop) +{ + /* USB register access can sleep, and mt76.mutex serialises USB callers */ + if (!mt76_is_mmio(&dev->mt76)) { + __mt76x02_mac_wcid_set_drop(dev, idx, drop); + return; + } + + spin_lock_bh(&dev->mt76.lock); + __mt76x02_mac_wcid_set_drop(dev, idx, drop); + spin_unlock_bh(&dev->mt76.lock); +} + static u16 mt76x02_mac_tx_rate_val(struct mt76x02_dev *dev, const struct ieee80211_tx_rate *rate, u8 *nss_val) -- 2.47.3 mt76x02_tx_prepare_skb() clears the drop bit of a sleeping station only for frames on MT_TXQ_PSD. Only frames that mt76_release_buffered_frames() releases from the driver TXQs use that queue. Frames that mac80211 buffered itself go through the regular tx path on their access category queue, and the hardware discards these PS-Poll and U-APSD responses. Clear the drop bit for every frame that mac80211 marks as a powersave response or as exempt from powersave buffering, like mt7603_tx_prepare_skb() does. Fixes: 7bc04215a66b ("mt76: add driver code for MT76x2e") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x02_txrx.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_txrx.c b/drivers/net/wireless/mediatek/mt76/mt76x02_txrx.c index 3ca530f1c171..1698eb9051c3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02_txrx.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_txrx.c @@ -158,11 +158,15 @@ int mt76x02_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, { struct mt76x02_dev *dev = container_of(mdev, struct mt76x02_dev, mt76); struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx_info->skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_info->skb); struct mt76x02_txwi *txwi = txwi_ptr; - bool ampdu = IEEE80211_SKB_CB(tx_info->skb)->flags & IEEE80211_TX_CTL_AMPDU; + bool ampdu = info->flags & IEEE80211_TX_CTL_AMPDU; int hdrlen, len, pid, qsel = MT_QSEL_EDCA; - if (qid == MT_TXQ_PSD && wcid && wcid->idx < 128) + if (wcid && wcid->idx < MT76x02_N_WCIDS && + ((info->flags & (IEEE80211_TX_CTL_NO_PS_BUFFER | + IEEE80211_TX_CTL_CLEAR_PS_FILT)) || + (info->control.flags & IEEE80211_TX_CTRL_PS_RESPONSE))) mt76x02_mac_wcid_set_drop(dev, wcid->idx, false); hdrlen = ieee80211_hdrlen(hdr->frame_control); -- 2.47.3