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:
Bjoern A. Zeeb
2025-12-10 20:29:23 +00:00
parent 0a19464bf7
commit 567a097c8a
2 changed files with 42 additions and 12 deletions
+40 -11
View File
@@ -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 = &ltxq->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(&ltxq->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)
+2 -1
View File
@@ -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. */