git: 567a097c8ab6 - main - LinuxKPI: 802.11: lock down the "txq_scheduled" tailq
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 11 Dec 2025 00:46:52 UTC
The branch main has been updated by bz:
URL: https://cgit.FreeBSD.org/src/commit/?id=567a097c8ab60d9fcd68a87c3c5ad605fe8715cc
commit 567a097c8ab60d9fcd68a87c3c5ad605fe8715cc
Author: Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2025-12-10 20:29:23 +0000
Commit: Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2025-12-11 00:46:15 +0000
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
---
sys/compat/linuxkpi/common/src/linux_80211.c | 51 ++++++++++++++++++++++------
sys/compat/linuxkpi/common/src/linux_80211.h | 3 +-
2 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/sys/compat/linuxkpi/common/src/linux_80211.c b/sys/compat/linuxkpi/common/src/linux_80211.c
index 8098d02da1a9..1d510577f00c 100644
--- a/sys/compat/linuxkpi/common/src/linux_80211.c
+++ b/sys/compat/linuxkpi/common/src/linux_80211.c
@@ -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)
diff --git a/sys/compat/linuxkpi/common/src/linux_80211.h b/sys/compat/linuxkpi/common/src/linux_80211.h
index 0dfcd7646c34..fcbef46fc6de 100644
--- a/sys/compat/linuxkpi/common/src/linux_80211.h
+++ b/sys/compat/linuxkpi/common/src/linux_80211.h
@@ -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. */