LinuxKPI: 802.11: lock down the "txq_scheduled" tailq
For consistency rename the "scheduled_txqs" tailq to
"txq_scheduled" and add a lock per txq ("txq_scheduled_lock[]").
We use the "_bh" locking as this called from the device driver.
This fixes panics due to concurrent access to the tailq, especially
in between "first" and "remove" on the out-direction and between
"insert" and "elem_init" on the in-direction.
This was easily reproducible just running iperf3 at basic rates for
a few seconds to minutes with multiple chipsets, not only rtw89.
Sponsored by: The FreeBSD Foundation
PR: 290636
Reported by: arved, and others before
MFC after: 3 days
This commit is contained in:
@@ -6472,8 +6472,9 @@ linuxkpi_ieee80211_alloc_hw(size_t priv_len, const struct ieee80211_ops *ops)
|
||||
TAILQ_INIT(&lhw->lvif_head);
|
||||
__hw_addr_init(&lhw->mc_list);
|
||||
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
|
||||
spin_lock_init(&lhw->txq_scheduled_lock[ac]);
|
||||
lhw->txq_generation[ac] = 1;
|
||||
TAILQ_INIT(&lhw->scheduled_txqs[ac]);
|
||||
TAILQ_INIT(&lhw->txq_scheduled[ac]);
|
||||
}
|
||||
|
||||
/* Chanctx_conf */
|
||||
@@ -6507,6 +6508,7 @@ linuxkpi_ieee80211_iffree(struct ieee80211_hw *hw)
|
||||
{
|
||||
struct lkpi_hw *lhw;
|
||||
struct mbuf *m;
|
||||
int ac;
|
||||
|
||||
lhw = HW_TO_LHW(hw);
|
||||
free(lhw->ic, M_LKPI80211);
|
||||
@@ -6571,6 +6573,9 @@ linuxkpi_ieee80211_iffree(struct ieee80211_hw *hw)
|
||||
lkpi_cleanup_mcast_list_locked(lhw);
|
||||
LKPI_80211_LHW_MC_UNLOCK(lhw);
|
||||
|
||||
for (ac = 0; ac < IEEE80211_NUM_ACS; ac++)
|
||||
spin_lock_destroy(&lhw->txq_scheduled_lock[ac]);
|
||||
|
||||
/* Cleanup more of lhw here or in wiphy_free()? */
|
||||
spin_lock_destroy(&lhw->txq_lock);
|
||||
LKPI_80211_LHW_TXQ_LOCK_DESTROY(lhw);
|
||||
@@ -8664,7 +8669,12 @@ linuxkpi_ieee80211_wake_queue(struct ieee80211_hw *hw, int qnum)
|
||||
spin_unlock_irqrestore(&lhw->txq_lock, flags);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
/* This is just hardware queues. */
|
||||
/*
|
||||
* Being called from the driver thus use _bh() locking.
|
||||
*/
|
||||
void
|
||||
linuxkpi_ieee80211_txq_schedule_start(struct ieee80211_hw *hw, uint8_t ac)
|
||||
{
|
||||
@@ -8672,10 +8682,16 @@ linuxkpi_ieee80211_txq_schedule_start(struct ieee80211_hw *hw, uint8_t ac)
|
||||
|
||||
lhw = HW_TO_LHW(hw);
|
||||
|
||||
IMPROVE_TXQ("Are there reasons why we wouldn't schedule?");
|
||||
IMPROVE_TXQ("LOCKING");
|
||||
if (ac >= IEEE80211_NUM_ACS) {
|
||||
ic_printf(lhw->ic, "%s: ac %u out of bounds.\n", __func__, ac);
|
||||
return;
|
||||
}
|
||||
|
||||
spin_lock_bh(&lhw->txq_scheduled_lock[ac]);
|
||||
IMPROVE("check AIRTIME_FAIRNESS");
|
||||
if (++lhw->txq_generation[ac] == 0)
|
||||
lhw->txq_generation[ac]++;
|
||||
spin_unlock_bh(&lhw->txq_scheduled_lock[ac]);
|
||||
}
|
||||
|
||||
struct ieee80211_txq *
|
||||
@@ -8688,24 +8704,33 @@ linuxkpi_ieee80211_next_txq(struct ieee80211_hw *hw, uint8_t ac)
|
||||
lhw = HW_TO_LHW(hw);
|
||||
txq = NULL;
|
||||
|
||||
IMPROVE_TXQ("LOCKING");
|
||||
if (ac >= IEEE80211_NUM_ACS) {
|
||||
ic_printf(lhw->ic, "%s: ac %u out of bounds.\n", __func__, ac);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
spin_lock_bh(&lhw->txq_scheduled_lock[ac]);
|
||||
|
||||
/* Check that we are scheduled. */
|
||||
if (lhw->txq_generation[ac] == 0)
|
||||
goto out;
|
||||
|
||||
ltxq = TAILQ_FIRST(&lhw->scheduled_txqs[ac]);
|
||||
ltxq = TAILQ_FIRST(&lhw->txq_scheduled[ac]);
|
||||
if (ltxq == NULL)
|
||||
goto out;
|
||||
if (ltxq->txq_generation == lhw->txq_generation[ac])
|
||||
goto out;
|
||||
|
||||
IMPROVE("check AIRTIME_FAIRNESS");
|
||||
|
||||
TAILQ_REMOVE(&lhw->txq_scheduled[ac], ltxq, txq_entry);
|
||||
ltxq->txq_generation = lhw->txq_generation[ac];
|
||||
TAILQ_REMOVE(&lhw->scheduled_txqs[ac], ltxq, txq_entry);
|
||||
txq = <xq->txq;
|
||||
TAILQ_ELEM_INIT(ltxq, txq_entry);
|
||||
|
||||
out:
|
||||
spin_unlock_bh(&lhw->txq_scheduled_lock[ac]);
|
||||
|
||||
return (txq);
|
||||
}
|
||||
|
||||
@@ -8718,8 +8743,6 @@ void linuxkpi_ieee80211_schedule_txq(struct ieee80211_hw *hw,
|
||||
|
||||
ltxq = TXQ_TO_LTXQ(txq);
|
||||
|
||||
IMPROVE_TXQ("LOCKING");
|
||||
|
||||
/* Only schedule if work to do or asked to anyway. */
|
||||
LKPI_80211_LTXQ_LOCK(ltxq);
|
||||
ltxq_empty = skb_queue_empty(<xq->skbq);
|
||||
@@ -8727,20 +8750,26 @@ void linuxkpi_ieee80211_schedule_txq(struct ieee80211_hw *hw,
|
||||
if (!withoutpkts && ltxq_empty)
|
||||
goto out;
|
||||
|
||||
lhw = HW_TO_LHW(hw);
|
||||
spin_lock_bh(&lhw->txq_scheduled_lock[txq->ac]);
|
||||
/*
|
||||
* Make sure we do not double-schedule. We do this by checking tqe_prev,
|
||||
* the previous entry in our tailq. tqe_prev is always valid if this entry
|
||||
* is queued, tqe_next may be NULL if this is the only element in the list.
|
||||
*/
|
||||
if (ltxq->txq_entry.tqe_prev != NULL)
|
||||
goto out;
|
||||
goto unlock;
|
||||
|
||||
TAILQ_INSERT_TAIL(&lhw->txq_scheduled[txq->ac], ltxq, txq_entry);
|
||||
unlock:
|
||||
spin_unlock_bh(&lhw->txq_scheduled_lock[txq->ac]);
|
||||
|
||||
lhw = HW_TO_LHW(hw);
|
||||
TAILQ_INSERT_TAIL(&lhw->scheduled_txqs[txq->ac], ltxq, txq_entry);
|
||||
out:
|
||||
return;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
void
|
||||
linuxkpi_ieee80211_handle_wake_tx_queue(struct ieee80211_hw *hw,
|
||||
struct ieee80211_txq *txq)
|
||||
|
||||
@@ -252,7 +252,8 @@ struct lkpi_hw { /* name it mac80211_sc? */
|
||||
|
||||
struct mtx txq_mtx;
|
||||
uint32_t txq_generation[IEEE80211_NUM_ACS];
|
||||
TAILQ_HEAD(, lkpi_txq) scheduled_txqs[IEEE80211_NUM_ACS];
|
||||
spinlock_t txq_scheduled_lock[IEEE80211_NUM_ACS];
|
||||
TAILQ_HEAD(, lkpi_txq) txq_scheduled[IEEE80211_NUM_ACS];
|
||||
spinlock_t txq_lock;
|
||||
|
||||
/* Deferred RX path. */
|
||||
|
||||
Reference in New Issue
Block a user