From ef06c3074fdca002adea9db516b3f86e80c72fad Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 5 Nov 2018 21:11:39 +0100 Subject: [PATCH] mt76: avoid queue/status spinlocks while passing tx status to mac80211 There is some code in the mac80211 tx status processing code that could potentially call back into the tx codepath. To avoid deadlocks, make sure that no tx related spinlocks are taken during the ieee80211_tx_status call. Signed-off-by: Felix Fietkau --- dma.c | 11 ++++++---- mac80211.c | 2 +- mt76.h | 30 +++++++++++--------------- mt7603/mac.c | 11 +++++----- mt7603/main.c | 1 + mt76x02_mac.c | 11 +++++----- mt76x02_util.c | 2 +- tx.c | 58 ++++++++++++++++++++++++++++++++++++++++++-------- 8 files changed, 83 insertions(+), 43 deletions(-) diff --git a/dma.c b/dma.c index 2d7bd268..d62f8d9f 100644 --- a/dma.c +++ b/dma.c @@ -157,17 +157,20 @@ mt76_dma_tx_cleanup(struct mt76_dev *dev, enum mt76_txq_id qid, bool flush) if (entry.schedule) q->swq_queued--; - if (entry.skb) + q->tail = (q->tail + 1) % q->ndesc; + q->queued--; + + if (entry.skb) { + spin_unlock_bh(&q->lock); dev->drv->tx_complete_skb(dev, q, &entry, flush); + spin_lock_bh(&q->lock); + } if (entry.txwi) { mt76_put_txwi(dev, entry.txwi); wake = true; } - q->tail = (q->tail + 1) % q->ndesc; - q->queued--; - if (!flush && q->tail == last) last = ioread32(&q->regs->dma_idx); } diff --git a/mac80211.c b/mac80211.c index 85e05a82..3be73b02 100644 --- a/mac80211.c +++ b/mac80211.c @@ -359,7 +359,7 @@ void mt76_unregister_device(struct mt76_dev *dev) { struct ieee80211_hw *hw = dev->hw; - mt76_tx_status_flush(dev, NULL); + mt76_tx_status_check(dev, NULL, true); ieee80211_unregister_hw(hw); mt76_tx_free(dev); } diff --git a/mt76.h b/mt76.h index e1d89163..ea74ba00 100644 --- a/mt76.h +++ b/mt76.h @@ -648,28 +648,22 @@ void mt76_rx_aggr_stop(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tid); void mt76_wcid_key_setup(struct mt76_dev *dev, struct mt76_wcid *wcid, struct ieee80211_key_conf *key); + +void mt76_tx_status_lock(struct mt76_dev *dev, struct sk_buff_head *list) + __acquires(&dev->status_list.lock); +void mt76_tx_status_unlock(struct mt76_dev *dev, struct sk_buff_head *list) + __releases(&dev->status_list.lock); + int mt76_tx_status_skb_add(struct mt76_dev *dev, struct mt76_wcid *wcid, struct sk_buff *skb); struct sk_buff *mt76_tx_status_skb_get(struct mt76_dev *dev, - struct mt76_wcid *wcid, int pktid); -void mt76_tx_status_skb_done(struct mt76_dev *dev, struct sk_buff *skb); + struct mt76_wcid *wcid, int pktid, + struct sk_buff_head *list); +void mt76_tx_status_skb_done(struct mt76_dev *dev, struct sk_buff *skb, + struct sk_buff_head *list); void mt76_tx_complete_skb(struct mt76_dev *dev, struct sk_buff *skb); - -static inline void -mt76_tx_status_check(struct mt76_dev *dev) -{ - spin_lock_bh(&dev->status_list.lock); - mt76_tx_status_skb_get(dev, NULL, 0); - spin_unlock_bh(&dev->status_list.lock); -} - -static inline void -mt76_tx_status_flush(struct mt76_dev *dev, struct mt76_wcid *wcid) -{ - spin_lock_bh(&dev->status_list.lock); - mt76_tx_status_skb_get(dev, wcid, -1); - spin_unlock_bh(&dev->status_list.lock); -} +void mt76_tx_status_check(struct mt76_dev *dev, struct mt76_wcid *wcid, + bool flush); struct ieee80211_sta *mt76_rx_convert(struct sk_buff *skb); diff --git a/mt7603/mac.c b/mt7603/mac.c index 46c911fb..4adbbb23 100644 --- a/mt7603/mac.c +++ b/mt7603/mac.c @@ -983,13 +983,14 @@ mt7603_mac_add_txs_skb(struct mt7603_dev *dev, struct mt7603_sta *sta, int pid, __le32 *txs_data) { struct mt76_dev *mdev = &dev->mt76; + struct sk_buff_head list; struct sk_buff *skb; if (!pid) return false; - spin_lock_bh(&mdev->status_list.lock); - skb = mt76_tx_status_skb_get(mdev, &sta->wcid, pid); + mt76_tx_status_lock(mdev, &list); + skb = mt76_tx_status_skb_get(mdev, &sta->wcid, pid, &list); if (skb) { struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); @@ -1002,9 +1003,9 @@ mt7603_mac_add_txs_skb(struct mt7603_dev *dev, struct mt7603_sta *sta, int pid, spin_unlock_bh(&dev->mt76.lock); } mt7603_fill_txs(dev, sta, info, txs_data); - mt76_tx_status_skb_done(mdev, skb); + mt76_tx_status_skb_done(mdev, skb, &list); } - spin_unlock_bh(&mdev->status_list.lock); + mt76_tx_status_unlock(mdev, &list); return !!skb; } @@ -1303,7 +1304,7 @@ void mt7603_mac_work(struct work_struct *work) struct mt7603_dev *dev = container_of(work, struct mt7603_dev, mac_work.work); bool reset = false; - mt76_tx_status_check(&dev->mt76); + mt76_tx_status_check(&dev->mt76, NULL, false); mutex_lock(&dev->mutex); diff --git a/mt7603/main.c b/mt7603/main.c index 7a09f63f..f5046ef3 100644 --- a/mt7603/main.c +++ b/mt7603/main.c @@ -333,6 +333,7 @@ mt7603_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int i; mutex_lock(&dev->mutex); + mt76_tx_status_check(&dev->mt76, &msta->wcid, true); rcu_assign_pointer(dev->wcid[idx], NULL); mt7603_wtbl_clear(dev, idx); diff --git a/mt76x02_mac.c b/mt76x02_mac.c index f80cc816..4d340db9 100644 --- a/mt76x02_mac.c +++ b/mt76x02_mac.c @@ -440,12 +440,13 @@ void mt76x02_send_tx_status(struct mt76x02_dev *dev, struct mt76_wcid *wcid = NULL; struct mt76x02_sta *msta = NULL; struct mt76_dev *mdev = &dev->mt76; + struct sk_buff_head list; if (stat->pktid == MT_PACKET_ID_NO_ACK) return; rcu_read_lock(); - spin_lock_bh(&mdev->status_list.lock); + mt76_tx_status_lock(mdev, &list); if (stat->wcid < ARRAY_SIZE(dev->mt76.wcid)) wcid = rcu_dereference(dev->mt76.wcid[stat->wcid]); @@ -461,7 +462,7 @@ void mt76x02_send_tx_status(struct mt76x02_dev *dev, if (wcid) { if (stat->pktid) status.skb = mt76_tx_status_skb_get(mdev, wcid, - stat->pktid); + stat->pktid, &list); if (status.skb) status.info = IEEE80211_SKB_CB(status.skb); } @@ -492,12 +493,12 @@ void mt76x02_send_tx_status(struct mt76x02_dev *dev, } if (status.skb) - mt76_tx_status_skb_done(mdev, status.skb); + mt76_tx_status_skb_done(mdev, status.skb, &list); else ieee80211_tx_status_ext(mt76_hw(dev), &status); out: - spin_unlock_bh(&mdev->status_list.lock); + mt76_tx_status_unlock(mdev, &list); rcu_read_unlock(); } @@ -821,7 +822,7 @@ void mt76x02_mac_work(struct work_struct *work) if (!dev->beacon_mask) mt76x02_check_mac_err(dev); - mt76_tx_status_check(&dev->mt76); + mt76_tx_status_check(&dev->mt76, NULL, false); ieee80211_queue_delayed_work(mt76_hw(dev), &dev->mac_work, MT_CALIBRATE_INTERVAL); diff --git a/mt76x02_util.c b/mt76x02_util.c index e2bbf56d..3f65c791 100644 --- a/mt76x02_util.c +++ b/mt76x02_util.c @@ -215,7 +215,7 @@ int mt76x02_sta_remove(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int i; mutex_lock(&dev->mt76.mutex); - mt76_tx_status_flush(&dev->mt76, &msta->wcid); + mt76_tx_status_check(&dev->mt76, &msta->wcid, true); rcu_assign_pointer(dev->mt76.wcid[idx], NULL); for (i = 0; i < ARRAY_SIZE(sta->txq); i++) mt76_txq_remove(&dev->mt76, sta->txq[i]); diff --git a/tx.c b/tx.c index e6cd583a..e42a2462 100644 --- a/tx.c +++ b/tx.c @@ -103,8 +103,33 @@ mt76_check_agg_ssn(struct mt76_txq *mtxq, struct sk_buff *skb) mtxq->agg_ssn = le16_to_cpu(hdr->seq_ctrl) + 0x10; } +void +mt76_tx_status_lock(struct mt76_dev *dev, struct sk_buff_head *list) + __acquires(&dev->status_list.lock) +{ + __skb_queue_head_init(list); + spin_lock_bh(&dev->status_list.lock); + __acquire(&dev->status_list.lock); +} +EXPORT_SYMBOL_GPL(mt76_tx_status_lock); + +void +mt76_tx_status_unlock(struct mt76_dev *dev, struct sk_buff_head *list) + __releases(&dev->status_list.unlock) +{ + struct sk_buff *skb; + + spin_unlock_bh(&dev->status_list.lock); + __release(&dev->status_list.unlock); + + while ((skb = __skb_dequeue(list)) != NULL) + ieee80211_tx_status(dev->hw, skb); +} +EXPORT_SYMBOL_GPL(mt76_tx_status_unlock); + static void -__mt76_tx_status_skb_done(struct mt76_dev *dev, struct sk_buff *skb, u8 flags) +__mt76_tx_status_skb_done(struct mt76_dev *dev, struct sk_buff *skb, u8 flags, + struct sk_buff_head *list) { struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct mt76_tx_cb *cb = mt76_tx_skb_cb(skb); @@ -125,13 +150,14 @@ __mt76_tx_status_skb_done(struct mt76_dev *dev, struct sk_buff *skb, u8 flags) info->flags |= IEEE80211_TX_STAT_ACK; } - ieee80211_tx_status(dev->hw, skb); + __skb_queue_tail(list, skb); } void -mt76_tx_status_skb_done(struct mt76_dev *dev, struct sk_buff *skb) +mt76_tx_status_skb_done(struct mt76_dev *dev, struct sk_buff *skb, + struct sk_buff_head *list) { - __mt76_tx_status_skb_done(dev, skb, MT_TX_CB_TXS_DONE); + __mt76_tx_status_skb_done(dev, skb, MT_TX_CB_TXS_DONE, list); } EXPORT_SYMBOL_GPL(mt76_tx_status_skb_done); @@ -173,7 +199,8 @@ mt76_tx_status_skb_add(struct mt76_dev *dev, struct mt76_wcid *wcid, EXPORT_SYMBOL_GPL(mt76_tx_status_skb_add); struct sk_buff * -mt76_tx_status_skb_get(struct mt76_dev *dev, struct mt76_wcid *wcid, int pktid) +mt76_tx_status_skb_get(struct mt76_dev *dev, struct mt76_wcid *wcid, int pktid, + struct sk_buff_head *list) { struct sk_buff *skb, *tmp; @@ -194,23 +221,36 @@ mt76_tx_status_skb_get(struct mt76_dev *dev, struct mt76_wcid *wcid, int pktid) continue; __mt76_tx_status_skb_done(dev, skb, MT_TX_CB_TXS_FAILED | - MT_TX_CB_TXS_DONE); + MT_TX_CB_TXS_DONE, list); } return NULL; } EXPORT_SYMBOL_GPL(mt76_tx_status_skb_get); +void +mt76_tx_status_check(struct mt76_dev *dev, struct mt76_wcid *wcid, bool flush) +{ + struct sk_buff_head list; + + mt76_tx_status_lock(dev, &list); + mt76_tx_status_skb_get(dev, wcid, flush ? -1 : 0, &list); + mt76_tx_status_unlock(dev, &list); +} +EXPORT_SYMBOL_GPL(mt76_tx_status_check); + void mt76_tx_complete_skb(struct mt76_dev *dev, struct sk_buff *skb) { + struct sk_buff_head list; + if (!skb->prev) { ieee80211_free_txskb(dev->hw, skb); return; } - spin_lock_bh(&dev->status_list.lock); - __mt76_tx_status_skb_done(dev, skb, MT_TX_CB_DMA_DONE); - spin_unlock_bh(&dev->status_list.lock); + mt76_tx_status_lock(dev, &list); + __mt76_tx_status_skb_done(dev, skb, MT_TX_CB_DMA_DONE, &list); + mt76_tx_status_unlock(dev, &list); } EXPORT_SYMBOL_GPL(mt76_tx_complete_skb);