Linux-mediatek Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: JB Tsai <jb.tsai@mediatek.com>
To: <nbd@nbd.name>, <lorenzo@kernel.org>
Cc: <linux-wireless@vger.kernel.org>,
	<linux-mediatek@lists.infradead.org>, <Sean.Wang@mediatek.com>,
	<James-LY.Huang@mediatek.com>, <jb.tsai@mediatek.com>,
	james-ly huang <james-ly.huang@mediatek.com>
Subject: [PATCH 2/9] wifi: mt76: mt7925: bound P2P off-channel mgmt TX lifetime
Date: Wed, 4 Nov 2026 17:23:37 +0800	[thread overview]
Message-ID: <20261104092344.1932406-2-jb.tsai@mediatek.com> (raw)
In-Reply-To: <20261104092344.1932406-1-jb.tsai@mediatek.com>

From: james-ly huang <james-ly.huang@mediatek.com>

A P2P GO's Probe Response is only useful while the requester is
still on the listen channel: give it a short TX lifetime and a
small retry limit, and arm the TXD timestamp, so a response that
cannot get on air in time is aged out by the HW instead of going
out long after the requester has left.

Bound an off-channel P2P mgmt frame to the dwell window it was
queued for: program the lifetime from whatever is left of the
granted ROC interval, and aim the expiry MT7925_P2P_OFFCHAN_TXS_MARGIN_MS
short of its end so the TX status still reaches cfg80211 while it
waits on the cookie. Both live in p2p.c behind
mt7925_p2p_txwi_override(), which mt7925_mac_write_txwi() calls
last so the more specific lifetime wins.

Signed-off-by: james-ly huang <james-ly.huang@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/mac.c   |   2 +
 .../net/wireless/mediatek/mt76/mt7925/p2p.c   | 197 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/p2p.h   |  31 +++
 3 files changed, 230 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 6e67ef3c52fa..577463af74b3 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -865,6 +865,8 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
 
 		txwi[3] |= cpu_to_le32(MT_TXD3_BA_DISABLE);
 	}
+
+	mt7925_p2p_txwi_override(dev, txwi, skb);
 }
 EXPORT_SYMBOL_GPL(mt7925_mac_write_txwi);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
index 12d6eee8ce10..5dad902a0446 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
@@ -21,6 +21,203 @@
 #include "mcu.h"
 #include "p2p.h"
 
+/* ------------------------------------------------------------------ */
+/* TX: TXD overrides                                                    */
+/* ------------------------------------------------------------------ */
+
+/* DW2 MAX_TX_TIME is a remaining-lifetime counter in units of 64 TU
+ * (64 * 1024 us). Only bits [6:0] of the field may be programmed by the host -
+ * the MSB is reserved for HW, which replaces the field with its own "max TX
+ * time" once the packet is queued - so clamp there. A non-zero request must
+ * never round down to 0 either, because 0 means "no lifetime" to the HW.
+ */
+#define MT7925_TXD_MAX_TX_TIME_LIMIT	0x7f
+
+static u32 mt7925_mac_txd_life_time(u32 ms)
+{
+	u32 unit;
+
+	if (!ms)
+		return 0;
+
+	/* ms -> TU (1024 us) -> 64 TU */
+	unit = (ms * USEC_PER_MSEC / 1024) >> 6;
+
+	if (unit > MT7925_TXD_MAX_TX_TIME_LIMIT)
+		return MT7925_TXD_MAX_TX_TIME_LIMIT;
+
+	return unit ? unit : 1;
+}
+
+/* Bound an off-channel P2P mgmt frame to the dwell window it was queued for.
+ *
+ * mt76 leaves DW2 MAX_TX_TIME at 0 (= no limit) for everything except a P2P
+ * GO Probe Response, so an off-channel frame that cannot win the medium
+ * inside its dwell stays in the PLE ALTX queue and keeps contending after
+ * the window is gone: UNI_ROC_ABORT only pauses and resumes the STA, so
+ * nothing drops the frame. If it does reach the air then, cfg80211 has
+ * already emitted NL80211_CMD_FRAME_WAIT_CANCEL and wpa_supplicant has
+ * dropped the cookie, so the TX status lands on an unknown cookie,
+ * 'p2p-send-action' radio work is never freed and wpa_supplicant's
+ * serialized radio-work queue stalls - p2p_find dies.
+ *
+ * Program the lifetime from the ROC instead: whatever is left of the
+ * granted interval that mt7925_mcu_roc_handle_grant() armed phy->roc_timer
+ * with, so the HW ages the frame out rather than transmitting it long
+ * after the window is gone.
+ *
+ * Granularity: DW2 MAX_TX_TIME counts 64 TU (~65.5 ms) units and
+ * mt7925_mac_txd_life_time() floors, with a floor of one unit, so it never
+ * writes 0 (= no lifetime) for a non-zero input.
+ */
+static void
+mt7925_mac_txwi_p2p_offchan_life_time(struct mt76_dev *mdev, __le32 *txwi,
+				      struct sk_buff *skb)
+{
+	struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_vif *vif = info->control.vif;
+	struct mt792x_phy *phy = &dev->phy;
+	struct ieee80211_hdr *hdr;
+	unsigned long expires;
+	u32 life_time, dwell_ms;
+
+	if (!(info->flags & IEEE80211_TX_CTL_TX_OFFCHAN))
+		return;
+
+	if (!vif)
+		return;
+
+	switch (ieee80211_vif_type_p2p(vif)) {
+	case NL80211_IFTYPE_P2P_DEVICE:
+	case NL80211_IFTYPE_P2P_GO:
+	case NL80211_IFTYPE_P2P_CLIENT:
+		break;
+	default:
+		return;
+	}
+
+	/* skb->data is an 802.3 header in the HW-encap path, not a frame ctl */
+	if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP)
+		return;
+
+	if (skb->len < sizeof(hdr->frame_control))
+		return;
+
+	hdr = (struct ieee80211_hdr *)skb->data;
+	if (!ieee80211_is_mgmt(hdr->frame_control))
+		return;
+
+	/* Only meaningful while a dwell window actually exists. roc_timer is
+	 * armed from the grant event only, so timer_pending() also means the
+	 * FW has confirmed the channel.
+	 */
+	if (!test_bit(MT76_STATE_ROC, &phy->mt76->state) ||
+	    !timer_pending(&phy->roc_timer))
+		return;
+
+	expires = phy->roc_timer.expires;
+	if (time_after_eq(jiffies, expires))
+		return;
+
+	dwell_ms = jiffies_to_msecs(expires - jiffies);
+
+	/* Aim the expiry short of the end of the dwell, not at it. Two things
+	 * have to fit in front of cfg80211's deadline:
+	 *
+	 *   - the TXS round trip, ~4..12 ms observed on healthy frames;
+	 *   - the head start cfg80211's wait has over phy->roc_timer. The
+	 *     wait starts when ieee80211_mgmt_tx() queues the frame, while
+	 *     the timer is only armed when the FW grants the channel, and
+	 *     the grant costs ~8..20 ms. So the ROC deadline this function
+	 *     reads is ~17..20 ms later than the deadline that actually
+	 *     matters: the frame wait expires just ahead of roc_work.
+	 *
+	 * 40 ms covers both with slack. Because mt7925_mac_txd_life_time()
+	 * floors, subtracting first guarantees life_time <= dwell - margin.
+	 * Without it the margin would swing between ~0 and ~65 ms depending
+	 * on where the remaining dwell lands inside a unit.
+	 */
+	if (dwell_ms > MT7925_P2P_OFFCHAN_TXS_MARGIN_MS)
+		dwell_ms -= MT7925_P2P_OFFCHAN_TXS_MARGIN_MS;
+
+	/* mt7925_mac_txd_life_time() only returns 0 for a 0 ms input, which
+	 * time_after_eq() above already excluded, and otherwise floors to a
+	 * minimum of one 64 TU unit (~65.5 ms) - it never writes 0, because
+	 * 0 means "no lifetime" to the HW.
+	 *
+	 * The flip side is that a dwell shorter than ~65 ms cannot be
+	 * expressed: it is rounded *up* to ~65.5 ms, so for e.g. a 50 ms
+	 * off-channel wait the frame is bounded but not necessarily reported
+	 * inside the wait. That is still strictly better than the unlimited
+	 * lifetime it has today; capping the damage is the goal, and the
+	 * short-dwell case would need a finer-grained field than DW2
+	 * MAX_TX_TIME to do better.
+	 */
+	life_time = mt7925_mac_txd_life_time(dwell_ms);
+	if (!life_time)
+		return;
+
+	txwi[2] &= ~cpu_to_le32(MT_TXD2_MAX_TX_TIME);
+	txwi[2] |= cpu_to_le32(FIELD_PREP(MT_TXD2_MAX_TX_TIME, life_time));
+}
+
+/* A P2P GO's Probe Response is only useful while the requester is still
+ * on the listen channel, so give it a short TX lifetime and a small retry
+ * limit: a response that cannot get on air in time is aged out by the HW
+ * instead of being transmitted long after the requester has left the
+ * channel.
+ *
+ * Bound the lifetime, keep the small retry limit, and otherwise align the
+ * TXD with a regular management frame.
+ */
+#define MT7925_TXD_PROBERESP_TIMESTAMP_IDX	0	/* WH_TXD_PROBERSP_TIMESTAMP_IDX */
+
+static void
+mt7925_mac_txwi_p2p_go_probe_resp(struct mt76_dev *dev, __le32 *txwi,
+				  struct sk_buff *skb)
+{
+	u32 life_time, val;
+
+	if (!mt7925_is_p2p_go_probe_resp(skb))
+		return;
+
+	life_time = mt7925_mac_txd_life_time(MT7925_P2P_PROBERESP_LIFE_TIME_MS);
+
+	txwi[2] &= ~cpu_to_le32(MT_TXD2_MAX_TX_TIME);
+	txwi[2] |= cpu_to_le32(FIELD_PREP(MT_TXD2_MAX_TX_TIME, life_time));
+
+	txwi[3] &= ~cpu_to_le32(MT_TXD3_REM_TX_COUNT);
+	txwi[3] |= cpu_to_le32(FIELD_PREP(MT_TXD3_REM_TX_COUNT,
+					  MT7925_P2P_PROBERESP_RETRY_LIMIT));
+
+	if (is_mt7928(dev)) {
+		txwi[6] &= ~cpu_to_le32(MT_TXD6_MSDU_CNT_V2 |
+					MT_TXD6_TIMESTAMP_OFS_EN_V2 |
+					MT_TXD6_TIMESTAMP_OFS_IDX_V2);
+		val = FIELD_PREP(MT_TXD6_TIMESTAMP_OFS_EN_V2, 1) |
+		      FIELD_PREP(MT_TXD6_TIMESTAMP_OFS_IDX_V2,
+				 MT7925_TXD_PROBERESP_TIMESTAMP_IDX);
+	} else {
+		txwi[6] &= ~cpu_to_le32(MT_TXD6_TIMESTAMP_OFS_EN |
+					MT_TXD6_TIMESTAMP_OFS_IDX);
+		val = MT_TXD6_TIMESTAMP_OFS_EN |
+		      FIELD_PREP(MT_TXD6_TIMESTAMP_OFS_IDX,
+				 MT7925_TXD_PROBERESP_TIMESTAMP_IDX);
+	}
+	txwi[6] |= cpu_to_le32(val | MT_TXD6_VTA);
+}
+
+void mt7925_p2p_txwi_override(struct mt76_dev *dev, __le32 *txwi,
+			      struct sk_buff *skb)
+{
+	/* Ordered so that the more specific lifetime wins if a frame ever
+	 * matches both composers.
+	 */
+	mt7925_mac_txwi_p2p_offchan_life_time(dev, txwi, skb);
+	mt7925_mac_txwi_p2p_go_probe_resp(dev, txwi, skb);
+}
+
 /* ------------------------------------------------------------------ */
 /* RX: foreign-channel discovery frames                                 */
 /* ------------------------------------------------------------------ */
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
index ed51670d9633..b005eebbfdca 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
@@ -9,10 +9,41 @@
 #include "../mt76.h"
 #include "../mt792x.h"
 
+/* P2P TX timing knobs (values align gen4m FW defaults). */
+#define MT7925_P2P_PROBERESP_LIFE_TIME_MS	100
+#define MT7925_P2P_PROBERESP_RETRY_LIMIT	2	/* DEFAULT_P2P_PROBERESP_RETRY_LIMIT */
+
+/* How far short of the end of an off-channel dwell an off-channel P2P mgmt
+ * frame's TX lifetime is aimed, so that the resulting TX status still reaches
+ * cfg80211 while it is waiting for it. Covers the TXS round trip (~4..12 ms
+ * observed) plus the head start cfg80211's wait has over phy->roc_timer,
+ * which is only armed once the FW grants the channel (~8..20 ms later).
+ */
+#define MT7925_P2P_OFFCHAN_TXS_MARGIN_MS	40
+
+static inline bool mt7925_is_p2p_go_probe_resp(struct sk_buff *skb)
+{
+	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
+	struct ieee80211_vif *vif = info->control.vif;
+	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+
+	if (!vif || ieee80211_vif_type_p2p(vif) != NL80211_IFTYPE_P2P_GO)
+		return false;
+
+	if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP)
+		return false;
+
+	if (skb->len < sizeof(mgmt->frame_control))
+		return false;
+
+	return ieee80211_is_probe_resp(mgmt->frame_control);
+}
 /* TX/RX path overrides, BMC wcid bookkeeping and the P2P-only MCU event
  * handlers.
  */
 bool mt7925_p2p_rx_drop_chan_mismatch(struct mt792x_dev *dev,
 				      struct sk_buff *skb);
+void mt7925_p2p_txwi_override(struct mt76_dev *dev, __le32 *txwi,
+			      struct sk_buff *skb);
 
 #endif /* __MT7925_P2P_H */
-- 
2.45.2



  reply	other threads:[~2026-11-04  9:23 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-04  9:23 [PATCH 1/9] wifi: mt76: mt7925: drop P2P mgmt frames received on foreign channel JB Tsai
2026-11-04  9:23 ` JB Tsai [this message]
2026-11-04  9:23 ` [PATCH 3/9] wifi: mt76: mt7925: give every P2P interface a BMC wcid of its own JB Tsai
2026-11-04  9:23 ` [PATCH 4/9] wifi: mt76: mt7925: harden the ROC channel-grant handshake JB Tsai
2026-11-04  9:23 ` [PATCH 5/9] wifi: mt76: mt7925: describe the real chandef in ROC requests JB Tsai
2026-11-04  9:23 ` [PATCH 6/9] wifi: mt76: mt7925: request the channel before programming a GO BSS JB Tsai
2026-11-04  9:23 ` [PATCH 7/9] wifi: mt76: mt7925: inject the deauth for an aging-timeout SAP event JB Tsai
2026-11-06  0:34   ` kernel test robot
2026-11-04  9:23 ` [PATCH 8/9] wifi: mt76: mt7925: track BSS absence and drop stale GO probe responses JB Tsai
2026-11-04  9:23 ` [PATCH 9/9] wifi: mt76: mt7925: send the common scan IEs and size the request JB Tsai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261104092344.1932406-2-jb.tsai@mediatek.com \
    --to=jb.tsai@mediatek.com \
    --cc=James-LY.Huang@mediatek.com \
    --cc=Sean.Wang@mediatek.com \
    --cc=linux-mediatek@lists.infradead.org \
    --cc=linux-wireless@vger.kernel.org \
    --cc=lorenzo@kernel.org \
    --cc=nbd@nbd.name \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox