git: dd167e59079b - stable/14 - LinuxKPI: 802.11: implement a deferred RX path

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Mon, 01 Apr 2024 23:47:30 UTC
The branch stable/14 has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=dd167e59079b65a53a45744055db6634f3c19a56

commit dd167e59079b65a53a45744055db6634f3c19a56
Author:     Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2024-02-12 16:03:13 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2024-04-01 23:46:10 +0000

    LinuxKPI: 802.11: implement a deferred RX path
    
    Some calls, e.g., action frames cause us to call through all the
    way down to firmware from the RX path without any deferral in
    net80211.
    
    For LinuxKPI and iwlwifi this goes (with omissions) like this:
    lkpi_napi_task -> linuxkpi_ieee80211_rx -> ieee80211_input_mimo ->
    sta_input -> ht_recv_action_ba_addba_request ->
    lkpi_ic_ampdu_rx_start -> iwl_mvm_mac_ampdu_action ->
    iwl_trans_txq_send_hcmd.  At that point we are waiting for an
    interrupt from the firmware but given the lkpi_napi_task has not
    finished (and may have more to dispatch based on budget and what
    was received) we will not see the new interrupt/fw response.
    With no answer from the firmware, the software timeout in the
    driver kills the command and the firmware and issues a complete
    restart.
    
    Implement the deferred RX path in LinuxKPI for the moment.
    At a later point we should carefully shift this into net80211.
    
    This fixes the hangs for (*ic_ampdu_rx_start)() calls with iwlwifi.
    
    PR:             276083
    Reviewed by:    cc
    
    (cherry picked from commit 759a996d610d9354aac5c48a6bdc9cedcba2f48b)
---
 sys/compat/linuxkpi/common/src/linux_80211.c | 134 ++++++++++++++++++++++++---
 sys/compat/linuxkpi/common/src/linux_80211.h |  31 +++++++
 2 files changed, 154 insertions(+), 11 deletions(-)

diff --git a/sys/compat/linuxkpi/common/src/linux_80211.c b/sys/compat/linuxkpi/common/src/linux_80211.c
index e365e0f574e4..57b3609b2290 100644
--- a/sys/compat/linuxkpi/common/src/linux_80211.c
+++ b/sys/compat/linuxkpi/common/src/linux_80211.c
@@ -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 *);
 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 *);
 #ifdef LKPI_80211_WME
 static int lkpi_wme_update(struct lkpi_hw *, struct ieee80211vap *, bool);
@@ -4272,6 +4273,12 @@ linuxkpi_ieee80211_alloc_hw(size_t priv_len, const struct ieee80211_ops *ops)
 		TAILQ_INIT(&lhw->scheduled_txqs[ac]);
 	}
 
+	/* Deferred RX path. */
+	LKPI_80211_LHW_RXQ_LOCK_INIT(lhw);
+	TASK_INIT(&lhw->rxq_task, 0, lkpi_80211_lhw_rxq_task, lhw);
+	mbufq_init(&lhw->rxq, IFQ_MAXLEN);
+	lhw->rxq_stopped = false;
+
 	/*
 	 * XXX-BZ TODO make sure there is a "_null" function to all ops
 	 * not initialized.
@@ -4297,11 +4304,42 @@ void
 linuxkpi_ieee80211_iffree(struct ieee80211_hw *hw)
 {
 	struct lkpi_hw *lhw;
+	struct mbuf *m;
 
 	lhw = HW_TO_LHW(hw);
 	free(lhw->ic, M_LKPI80211);
 	lhw->ic = NULL;
 
+	/*
+	 * Drain the deferred RX path.
+	 */
+	LKPI_80211_LHW_RXQ_LOCK(lhw);
+	lhw->rxq_stopped = true;
+	LKPI_80211_LHW_RXQ_UNLOCK(lhw);
+
+	/* Drain taskq, won't be restarted due to rxq_stopped being set. */
+	while (taskqueue_cancel(taskqueue_thread, &lhw->rxq_task, NULL) != 0)
+		taskqueue_drain(taskqueue_thread, &lhw->rxq_task);
+
+	/* Flush mbufq (make sure to release ni refs!). */
+	m = mbufq_dequeue(&lhw->rxq);
+	while (m != NULL) {
+		struct m_tag *mtag;
+
+		mtag = m_tag_locate(m, MTAG_ABI_LKPI80211, LKPI80211_TAG_RXNI, NULL);
+		if (mtag != NULL) {
+			struct lkpi_80211_tag_rxni *rxni;
+
+			rxni = (struct lkpi_80211_tag_rxni *)(mtag + 1);
+			ieee80211_free_node(rxni->ni);
+		}
+		m_freem(m);
+		m = mbufq_dequeue(&lhw->rxq);
+	}
+	KASSERT(mbufq_empty(&lhw->rxq), ("%s: lhw %p has rxq len %d != 0\n",
+	    __func__, lhw, mbufq_len(&lhw->rxq)));
+	LKPI_80211_LHW_RXQ_LOCK_DESTROY(lhw);
+
 	/* Cleanup more of lhw here or in wiphy_free()? */
 	LKPI_80211_LHW_TXQ_LOCK_DESTROY(lhw);
 	LKPI_80211_LHW_SCAN_LOCK_DESTROY(lhw);
@@ -4796,6 +4834,66 @@ linuxkpi_ieee80211_scan_completed(struct ieee80211_hw *hw,
 	return;
 }
 
+static void
+lkpi_80211_lhw_rxq_rx_one(struct lkpi_hw *lhw, struct mbuf *m)
+{
+	struct ieee80211_node *ni;
+	struct m_tag *mtag;
+	int ok;
+
+	ni = NULL;
+        mtag = m_tag_locate(m, MTAG_ABI_LKPI80211, LKPI80211_TAG_RXNI, NULL);
+	if (mtag != NULL) {
+		struct lkpi_80211_tag_rxni *rxni;
+
+		rxni = (struct lkpi_80211_tag_rxni *)(mtag + 1);
+		ni = rxni->ni;
+	}
+
+	if (ni != NULL) {
+		ok = ieee80211_input_mimo(ni, m);
+		ieee80211_free_node(ni);		/* Release the reference. */
+		if (ok < 0)
+			m_freem(m);
+	} else {
+		ok = ieee80211_input_mimo_all(lhw->ic, m);
+		/* mbuf got consumed. */
+	}
+
+#ifdef LINUXKPI_DEBUG_80211
+	if (linuxkpi_debug_80211 & D80211_TRACE_RX)
+		printf("TRACE %s: handled frame type %#0x\n", __func__, ok);
+#endif
+}
+
+static void
+lkpi_80211_lhw_rxq_task(void *ctx, int pending)
+{
+	struct lkpi_hw *lhw;
+	struct mbufq mq;
+	struct mbuf *m;
+
+	lhw = ctx;
+
+#ifdef LINUXKPI_DEBUG_80211
+	if (linuxkpi_debug_80211 & D80211_TRACE_RX)
+		printf("%s:%d lhw %p pending %d mbuf_qlen %d\n",
+		    __func__, __LINE__, lhw, pending, mbufq_len(&lhw->rxq));
+#endif
+
+	mbufq_init(&mq, IFQ_MAXLEN);
+
+	LKPI_80211_LHW_RXQ_LOCK(lhw);
+	mbufq_concat(&mq, &lhw->rxq);
+	LKPI_80211_LHW_RXQ_UNLOCK(lhw);
+
+	m = mbufq_dequeue(&mq);
+	while (m != NULL) {
+		lkpi_80211_lhw_rxq_rx_one(lhw, m);
+		m = mbufq_dequeue(&mq);
+	}
+}
+
 /* For %list see comment towards the end of the function. */
 void
 linuxkpi_ieee80211_rx(struct ieee80211_hw *hw, struct sk_buff *skb,
@@ -5019,20 +5117,34 @@ skip_device_ts:
 	}
 #endif
 
+	/*
+	 * Attach meta-information to the mbuf for the deferred RX path.
+	 * Currently this is best-effort.  Should we need to be hard,
+	 * drop the frame and goto err;
+	 */
 	if (ni != NULL) {
-		ok = ieee80211_input_mimo(ni, m);
-		ieee80211_free_node(ni);
-		if (ok < 0)
-			m_freem(m);
-	} else {
-		ok = ieee80211_input_mimo_all(ic, m);
-		/* mbuf got consumed. */
+		struct m_tag *mtag;
+		struct lkpi_80211_tag_rxni *rxni;
+
+		mtag = m_tag_alloc(MTAG_ABI_LKPI80211, LKPI80211_TAG_RXNI,
+		    sizeof(*rxni), IEEE80211_M_NOWAIT);
+		if (mtag != NULL) {
+			rxni = (struct lkpi_80211_tag_rxni *)(mtag + 1);
+			rxni->ni = ni;		/* We hold a reference. */
+			m_tag_prepend(m, mtag);
+		}
 	}
 
-#ifdef LINUXKPI_DEBUG_80211
-	if (linuxkpi_debug_80211 & D80211_TRACE_RX)
-		printf("TRACE %s: handled frame type %#0x\n", __func__, ok);
-#endif
+	LKPI_80211_LHW_RXQ_LOCK(lhw);
+	if (lhw->rxq_stopped) {
+		LKPI_80211_LHW_RXQ_UNLOCK(lhw);
+		m_freem(m);
+		goto err;
+	}
+
+	mbufq_enqueue(&lhw->rxq, m);
+	taskqueue_enqueue(taskqueue_thread, &lhw->rxq_task);
+	LKPI_80211_LHW_RXQ_UNLOCK(lhw);
 
 	IMPROVE();
 
diff --git a/sys/compat/linuxkpi/common/src/linux_80211.h b/sys/compat/linuxkpi/common/src/linux_80211.h
index d25614de56dc..b0156a5ade3f 100644
--- a/sys/compat/linuxkpi/common/src/linux_80211.h
+++ b/sys/compat/linuxkpi/common/src/linux_80211.h
@@ -76,6 +76,18 @@
     if (linuxkpi_debug_80211 & D80211_TRACE_MODE_HT)			\
 	printf("%s:%d: XXX LKPI80211 IMPROVE_HT\n", __func__, __LINE__)
 
+#define	MTAG_ABI_LKPI80211	1707696513	/* LinuxKPI 802.11 KBI */
+
+/*
+ * Deferred RX path.
+ * We need to pass *ni along (and possibly more in the future so
+ * we use a struct right from the start.
+ */
+#define	LKPI80211_TAG_RXNI	0		/* deferred RX path */
+struct lkpi_80211_tag_rxni {
+	struct ieee80211_node	*ni;		/* MUST hold a reference to it. */
+};
+
 struct lkpi_radiotap_tx_hdr {
 	struct ieee80211_radiotap_header wt_ihdr;
 	uint8_t		wt_flags;
@@ -192,6 +204,11 @@ struct lkpi_hw {	/* name it mac80211_sc? */
 	uint32_t			txq_generation[IEEE80211_NUM_ACS];
 	TAILQ_HEAD(, lkpi_txq)		scheduled_txqs[IEEE80211_NUM_ACS];
 
+	/* Deferred RX path. */
+	struct task		rxq_task;
+	struct mbufq		rxq;
+	struct mtx		rxq_mtx;
+
 	/* Scan functions we overload to handle depending on scan mode. */
 	void                    (*ic_scan_curchan)(struct ieee80211_scan_state *,
 				    unsigned long);
@@ -240,6 +257,7 @@ struct lkpi_hw {	/* name it mac80211_sc? */
 
 	bool				update_mc;
 	bool				update_wme;
+	bool				rxq_stopped;
 
 	/* Must be last! */
 	struct ieee80211_hw		hw __aligned(CACHE_LINE_SIZE);
@@ -304,6 +322,19 @@ struct lkpi_wiphy {
 #define	LKPI_80211_LHW_TXQ_UNLOCK_ASSERT(_lhw)		\
     mtx_assert(&(_lhw)->txq_mtx, MA_NOTOWNED)
 
+#define	LKPI_80211_LHW_RXQ_LOCK_INIT(_lhw)		\
+    mtx_init(&(_lhw)->rxq_mtx, "lhw-rxq", NULL, MTX_DEF | MTX_RECURSE);
+#define	LKPI_80211_LHW_RXQ_LOCK_DESTROY(_lhw)		\
+    mtx_destroy(&(_lhw)->rxq_mtx);
+#define	LKPI_80211_LHW_RXQ_LOCK(_lhw)			\
+    mtx_lock(&(_lhw)->rxq_mtx)
+#define	LKPI_80211_LHW_RXQ_UNLOCK(_lhw)			\
+    mtx_unlock(&(_lhw)->rxq_mtx)
+#define	LKPI_80211_LHW_RXQ_LOCK_ASSERT(_lhw)		\
+    mtx_assert(&(_lhw)->rxq_mtx, MA_OWNED)
+#define	LKPI_80211_LHW_RXQ_UNLOCK_ASSERT(_lhw)		\
+    mtx_assert(&(_lhw)->rxq_mtx, MA_NOTOWNED)
+
 #define	LKPI_80211_LHW_LVIF_LOCK(_lhw)	sx_xlock(&(_lhw)->lvif_sx)
 #define	LKPI_80211_LHW_LVIF_UNLOCK(_lhw) sx_xunlock(&(_lhw)->lvif_sx)