LinuxKPI: 802.11: make sure we can send DISASSOC or DEAUTH frames

The "Invalid TXQ" error from iwlwifi seems to be triggered by a
frame being sent for a sta which is no longer known to the driver/fw.

While we make sure to trigger the sending of the frame in net80211
early enough (by calling (*iv_newstate)() early on rather than at
the end), TX in LinuxKPI is run in a deferred task.  When we drop the
net80211 ic lock again and re-acquire the LHW lock the packet may not
yet have made it to the driver.
Work around this between the (ic and lhw) locks by making sure
(a) no new packets get queued after we return from (*iv_newstate)(),
and (b) the TX task has run or gets cancelled and we manually push
any remaining packets out (or let lsta_free() clean them up).
The disabled packet queuing now also needs to be re-enabled in
scan_to_auth() in case an lsta is staying in service or gets re-used.

Also make sure that any following lkpi_wake_tx_queues() calls no
longer ignore queues which have not seen a prior dequeue.
This former workaround "feature" (ltxq->seen_dequeue) should be
fully garbage collected in a later change on its own.

Sponsored by:	The FreeBSD Foundation
MFC after:	3 days
PR:		274382
Tested by:	emaste, lwhsu, thj, rkoberman at gmail.com
Accepted by:	adrian
Differential Revision: https://reviews.freebsd.org/D45508
This commit is contained in:
Bjoern A. Zeeb 2024-06-05 22:54:36 +00:00
parent 105b9df26e
commit 8866534929

View file

@ -145,6 +145,7 @@ const struct cfg80211_ops linuxkpi_mac80211cfgops = {
static struct lkpi_sta *lkpi_find_lsta_by_ni(struct lkpi_vif *,
struct ieee80211_node *);
#endif
static void lkpi_80211_txq_tx_one(struct lkpi_sta *, struct mbuf *);
static void lkpi_80211_txq_task(void *, int);
static void lkpi_80211_lhw_rxq_task(void *, int);
static void lkpi_ieee80211_free_skb_mbuf(void *);
@ -1061,6 +1062,51 @@ lkpi_wake_tx_queues(struct ieee80211_hw *hw, struct ieee80211_sta *sta,
}
}
/*
* On the way down from RUN -> ASSOC -> AUTH we may send a DISASSOC or DEAUTH
* packet. The problem is that the state machine functions tend to hold the
* LHW lock which will prevent lkpi_80211_txq_tx_one() from sending the packet.
* We call this after dropping the ic lock and before acquiring the LHW lock.
* we make sure no further packets are queued and if they are queued the task
* will finish or be cancelled. At the end if a packet is left we manually
* send it. scan_to_auth() would re-enable sending if the lsta would be
* re-used.
*/
static void
lkpi_80211_flush_tx(struct lkpi_hw *lhw, struct lkpi_sta *lsta)
{
struct mbufq mq;
struct mbuf *m;
int len;
LKPI_80211_LHW_UNLOCK_ASSERT(lhw);
/* Do not accept any new packets until scan_to_auth or lsta_free(). */
LKPI_80211_LSTA_TXQ_LOCK(lsta);
lsta->txq_ready = false;
LKPI_80211_LSTA_TXQ_UNLOCK(lsta);
while (taskqueue_cancel(taskqueue_thread, &lsta->txq_task, NULL) != 0)
taskqueue_drain(taskqueue_thread, &lsta->txq_task);
LKPI_80211_LSTA_TXQ_LOCK(lsta);
len = mbufq_len(&lsta->txq);
if (len <= 0) {
LKPI_80211_LSTA_TXQ_UNLOCK(lsta);
return;
}
mbufq_init(&mq, IFQ_MAXLEN);
mbufq_concat(&mq, &lsta->txq);
LKPI_80211_LSTA_TXQ_UNLOCK(lsta);
m = mbufq_dequeue(&mq);
while (m != NULL) {
lkpi_80211_txq_tx_one(lsta, m);
m = mbufq_dequeue(&mq);
}
}
/* -------------------------------------------------------------------------- */
static int
@ -1274,6 +1320,14 @@ lkpi_sta_scan_to_auth(struct ieee80211vap *vap, enum ieee80211_state nstate, int
__func__, ni, ni->ni_drv_data));
lsta = ni->ni_drv_data;
/*
* Make sure in case the sta did not change and we re-add it,
* that we can tx again.
*/
LKPI_80211_LSTA_TXQ_LOCK(lsta);
lsta->txq_ready = true;
LKPI_80211_LSTA_TXQ_UNLOCK(lsta);
LKPI_80211_LVIF_LOCK(lvif);
/* Insert the [l]sta into the list of known stations. */
TAILQ_INSERT_TAIL(&lvif->lsta_head, lsta, lsta_entry);
@ -1426,7 +1480,7 @@ lkpi_sta_auth_to_scan(struct ieee80211vap *vap, enum ieee80211_state nstate, int
lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), true);
/* Wake tx queues to get packet(s) out. */
lkpi_wake_tx_queues(hw, sta, true, true);
lkpi_wake_tx_queues(hw, sta, false, true);
/* flush, no drop */
lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false);
@ -1584,7 +1638,7 @@ lkpi_sta_auth_to_assoc(struct ieee80211vap *vap, enum ieee80211_state nstate, in
}
/* Wake tx queue to get packet out. */
lkpi_wake_tx_queues(hw, LSTA_TO_STA(lsta), true, true);
lkpi_wake_tx_queues(hw, LSTA_TO_STA(lsta), false, true);
/*
* <twiddle> .. we end up in "assoc_to_run"
@ -1728,7 +1782,7 @@ _lkpi_sta_assoc_to_down(struct ieee80211vap *vap, enum ieee80211_state nstate, i
LKPI_80211_LHW_UNLOCK(lhw);
IEEE80211_LOCK(vap->iv_ic);
/* Call iv_newstate first so we get potential DISASSOC packet out. */
/* Call iv_newstate first so we get potential DEAUTH packet out. */
error = lvif->iv_newstate(vap, nstate, arg);
if (error != 0) {
ic_printf(vap->iv_ic, "%s:%d: iv_newstate(%p, %d, %d) "
@ -1737,12 +1791,16 @@ _lkpi_sta_assoc_to_down(struct ieee80211vap *vap, enum ieee80211_state nstate, i
}
IEEE80211_UNLOCK(vap->iv_ic);
/* Ensure the packets get out. */
lkpi_80211_flush_tx(lhw, lsta);
LKPI_80211_LHW_LOCK(lhw);
lkpi_lsta_dump(lsta, ni, __func__, __LINE__);
/* Wake tx queues to get packet(s) out. */
lkpi_wake_tx_queues(hw, sta, true, true);
lkpi_wake_tx_queues(hw, sta, false, true);
/* flush, no drop */
lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false);
@ -2120,12 +2178,16 @@ lkpi_sta_run_to_assoc(struct ieee80211vap *vap, enum ieee80211_state nstate, int
}
IEEE80211_UNLOCK(vap->iv_ic);
/* Ensure the packets get out. */
lkpi_80211_flush_tx(lhw, lsta);
LKPI_80211_LHW_LOCK(lhw);
lkpi_lsta_dump(lsta, ni, __func__, __LINE__);
/* Wake tx queues to get packet(s) out. */
lkpi_wake_tx_queues(hw, sta, true, true);
lkpi_wake_tx_queues(hw, sta, false, true);
/* flush, no drop */
lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false);
@ -2254,12 +2316,16 @@ lkpi_sta_run_to_init(struct ieee80211vap *vap, enum ieee80211_state nstate, int
}
IEEE80211_UNLOCK(vap->iv_ic);
/* Ensure the packets get out. */
lkpi_80211_flush_tx(lhw, lsta);
LKPI_80211_LHW_LOCK(lhw);
lkpi_lsta_dump(lsta, ni, __func__, __LINE__);
/* Wake tx queues to get packet(s) out. */
lkpi_wake_tx_queues(hw, sta, true, true);
lkpi_wake_tx_queues(hw, sta, false, true);
/* flush, no drop */
lkpi_80211_mo_flush(hw, vif, nitems(sta->txq), false);
@ -3595,7 +3661,7 @@ lkpi_ic_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
lsta = ni->ni_drv_data;
LKPI_80211_LSTA_TXQ_LOCK(lsta);
if (!lsta->txq_ready) {
if (!lsta->added_to_drv || !lsta->txq_ready) {
LKPI_80211_LSTA_TXQ_UNLOCK(lsta);
/*
* Free the mbuf (do NOT release ni ref for the m_pkthdr.rcvif!
@ -3821,6 +3887,7 @@ lkpi_80211_txq_task(void *ctx, int pending)
struct lkpi_sta *lsta;
struct mbufq mq;
struct mbuf *m;
bool shall_tx;
lsta = ctx;
@ -3836,9 +3903,19 @@ lkpi_80211_txq_task(void *ctx, int pending)
LKPI_80211_LSTA_TXQ_LOCK(lsta);
/*
* Do not re-check lsta->txq_ready here; we may have a pending
* disassoc frame still.
* disassoc/deauth frame still. On the contrary if txq_ready is
* false we do not have a valid sta anymore in the firmware so no
* point to try to TX.
* We also use txq_ready as a semaphore and will drain the txq manually
* if needed on our way towards SCAN/INIT in the state machine.
*/
shall_tx = lsta->added_to_drv && lsta->txq_ready;
if (__predict_true(shall_tx))
mbufq_concat(&mq, &lsta->txq);
/*
* else a state change will push the packets out manually or
* lkpi_lsta_free() will drain the lsta->txq and free the mbufs.
*/
mbufq_concat(&mq, &lsta->txq);
LKPI_80211_LSTA_TXQ_UNLOCK(lsta);
m = mbufq_dequeue(&mq);