* [PATCH 2/9] wifi: mt76: mt7925: bound P2P off-channel mgmt TX lifetime
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
2026-11-04 9:23 ` [PATCH 3/9] wifi: mt76: mt7925: give every P2P interface a BMC wcid of its own JB Tsai
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, james-ly huang
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
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 3/9] wifi: mt76: mt7925: give every P2P interface a BMC wcid of its own
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 ` [PATCH 2/9] wifi: mt76: mt7925: bound P2P off-channel mgmt TX lifetime JB Tsai
@ 2026-11-04 9:23 ` JB Tsai
2026-11-04 9:23 ` [PATCH 4/9] wifi: mt76: mt7925: harden the ROC channel-grant handshake JB Tsai
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, james-ly huang
From: james-ly huang <james-ly.huang@mediatek.com>
Give every P2P interface (P2P device, GO, GC) a BMC wcid of its own,
kept for its whole interface lifetime:
- group keys are installed on the BMC wcid as well;
- add_bss_info reports the BMC wcid as the bss's basic bmc_tx_wlan_idx.
Signed-off-by: james-ly huang <james-ly.huang@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mt76.h | 1 +
.../wireless/mediatek/mt76/mt76_connac_mcu.c | 3 +-
.../net/wireless/mediatek/mt76/mt7925/main.c | 27 +++++++-
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 4 +-
.../net/wireless/mediatek/mt76/mt7925/p2p.c | 66 +++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/p2p.h | 6 ++
drivers/net/wireless/mediatek/mt76/mt792x.h | 2 +
.../net/wireless/mediatek/mt76/mt792x_core.c | 11 ++++
8 files changed, 117 insertions(+), 3 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 477d33a0022d..ab606e47e96f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -835,6 +835,7 @@ struct mt76_vif_link {
u8 basic_rates_idx;
u8 mcast_rates_idx;
u8 beacon_rates_idx;
+ u8 bmc_wcid; /* 0: bmc shares the vif wcid */
bool offchannel;
unsigned long beacon_mon_last;
u16 beacon_mon_interval;
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
index ad53bb60d5cd..f4cd42bcc80e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -1240,7 +1240,8 @@ int mt76_connac_mcu_uni_add_dev(struct mt76_phy *phy,
.band_idx = mvif->band_idx,
.wmm_idx = mvif->wmm_idx,
.active = enable,
- .bmc_tx_wlan_idx = cpu_to_le16(wcid->idx),
+ .bmc_tx_wlan_idx = cpu_to_le16(mvif->bmc_wcid ?:
+ wcid->idx),
.sta_idx = cpu_to_le16(wcid->idx),
.conn_state = 1,
.link_idx = mvif->link_idx,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index ffbc6b954632..3fa6c48c6549 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -8,6 +8,7 @@
#include <linux/ctype.h>
#include <net/ipv6.h>
#include "mt7925.h"
+#include "p2p.h"
#include "regd.h"
#include "mcu.h"
#include "mac.h"
@@ -437,10 +438,18 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev,
rcu_assign_pointer(dev->mt76.wcid[idx], &mlink->wcid);
+ if (vif->p2p || vif->type == NL80211_IFTYPE_P2P_DEVICE) {
+ ret = mt7925_p2p_bmc_add(dev, mconf);
+ if (ret)
+ goto out;
+ }
+
ret = mt76_connac_mcu_uni_add_dev(&dev->mphy, link_conf, &mconf->mt76,
&mlink->wcid, true);
- if (ret)
+ if (ret) {
+ mt7925_p2p_bmc_remove(dev, mconf);
goto out;
+ }
if (vif->txq) {
mtxq = (struct mt76_txq *)vif->txq->drv_priv;
@@ -493,8 +502,13 @@ mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
struct mt792x_dev *dev = mt792x_hw_dev(hw);
struct mt792x_bss_conf *mconf;
+ mt792x_remove_interface(hw, vif);
+
mt792x_mutex_acquire(dev);
+ /* the p2p device owns a BMC wcid for its whole life time */
+ mt7925_p2p_bmc_remove(dev, mt792x_link_conf_to_mconf(&vif->bss_conf));
+
if (dev->nan_vif == vif)
dev->nan_vif = NULL;
@@ -720,6 +734,17 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
if (err)
goto out;
+ /* group keys protect the frames that now go out on the dedicated BMC
+ * entry, so they have to be installed on that wcid as well
+ */
+ if (!sta && mconf->bmc_valid) {
+ err = mt7925_mcu_add_key(&dev->mt76, vif, &mconf->bmc.bip,
+ key, MCU_UNI_CMD(STA_REC_UPDATE),
+ &mconf->bmc.wcid, cmd, msta);
+ if (err)
+ goto out;
+ }
+
if (key->cipher == WLAN_CIPHER_SUITE_WEP104 ||
key->cipher == WLAN_CIPHER_SUITE_WEP40)
err = mt7925_mcu_add_key(&dev->mt76, vif, &mvif->wep_sta->deflink.bip,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 50f5f57b740e..3eadea1bb453 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -3223,7 +3223,9 @@ int mt7925_mcu_add_bss_info(struct mt792x_phy *phy,
}
return mt7925_mcu_add_bss_info_sta(phy, ctx, link_conf, link_sta,
- mlink_bc->wcid.idx, mlink->wcid.idx, enable);
+ mconf->bmc_valid ? mconf->bmc.wcid.idx :
+ mlink_bc->wcid.idx,
+ mlink->wcid.idx, enable);
}
int mt7925_mcu_set_dbdc(struct mt76_phy *phy, bool enable)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
index 5dad902a0446..4e41ba9beca1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
@@ -361,3 +361,69 @@ mt7925_p2p_rx_drop_chan_mismatch(struct mt792x_dev *dev, struct sk_buff *skb)
return false;
}
+/* ------------------------------------------------------------------ */
+/* BMC wcid bookkeeping */
+/* ------------------------------------------------------------------ */
+
+/* The BMC entry itself is set up by the firmware from the
+ * bmc_tx_wlan_idx of the BSS_INFO_BASIC tlv, so the driver
+ * only has to reserve the index and keep a wcid around for it.
+ */
+int mt7925_p2p_bmc_add(struct mt792x_dev *dev,
+ struct mt792x_bss_conf *mconf)
+{
+ struct mt792x_link_sta *mbmc = &mconf->bmc;
+ int idx;
+
+ if (mconf->bmc_valid)
+ return 0;
+
+ idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
+ if (idx < 0)
+ return -ENOSPC;
+
+ memset(mbmc, 0, sizeof(*mbmc));
+ INIT_LIST_HEAD(&mbmc->wcid.poll_list);
+ mbmc->wcid.idx = idx;
+ mbmc->wcid.phy_idx = 0;
+ mbmc->wcid.hw_key_idx = -1;
+ mbmc->wcid.hw_key_idx2 = -1;
+ mbmc->wcid.tx_info |= MT_WCID_TX_INFO_SET;
+ mbmc->wcid.link_id = mconf->link_id;
+ mt76_wcid_init(&mbmc->wcid, 0);
+
+ rcu_assign_pointer(dev->mt76.wcid[idx], &mbmc->wcid);
+ mt7925_mac_wtbl_update(dev, idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+ mconf->mt76.bmc_wcid = idx;
+ mconf->bmc_valid = true;
+
+ dev_info(dev->mt76.dev,
+ "[James] p2p bss%d BMC wcid=%d (bss default wcid=%d)\n",
+ mconf->mt76.idx, idx, mconf->vif->sta.deflink.wcid.idx);
+
+ return 0;
+}
+
+void mt7925_p2p_bmc_remove(struct mt792x_dev *dev,
+ struct mt792x_bss_conf *mconf)
+{
+ struct mt76_wcid *wcid = &mconf->bmc.wcid;
+
+ if (!mconf->bmc_valid)
+ return;
+
+ mconf->bmc_valid = false;
+ mconf->mt76.bmc_wcid = 0;
+
+ mt76_connac_free_pending_tx_skbs(&dev->pm, wcid);
+ rcu_assign_pointer(dev->mt76.wcid[wcid->idx], NULL);
+
+ spin_lock_bh(&dev->mt76.sta_poll_lock);
+ if (!list_empty(&wcid->poll_list))
+ list_del_init(&wcid->poll_list);
+ spin_unlock_bh(&dev->mt76.sta_poll_lock);
+
+ mt76_wcid_cleanup(&dev->mt76, wcid);
+ mt76_wcid_mask_clear(dev->mt76.wcid_mask, wcid->idx);
+}
+
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
index b005eebbfdca..25b6c57749a2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
@@ -38,6 +38,7 @@ static inline bool mt7925_is_p2p_go_probe_resp(struct sk_buff *skb)
return ieee80211_is_probe_resp(mgmt->frame_control);
}
+
/* TX/RX path overrides, BMC wcid bookkeeping and the P2P-only MCU event
* handlers.
*/
@@ -46,4 +47,9 @@ bool mt7925_p2p_rx_drop_chan_mismatch(struct mt792x_dev *dev,
void mt7925_p2p_txwi_override(struct mt76_dev *dev, __le32 *txwi,
struct sk_buff *skb);
+int mt7925_p2p_bmc_add(struct mt792x_dev *dev,
+ struct mt792x_bss_conf *mconf);
+void mt7925_p2p_bmc_remove(struct mt792x_dev *dev,
+ struct mt792x_bss_conf *mconf);
+
#endif /* __MT7925_P2P_H */
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index a7d71e007da0..66c8722220cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -163,6 +163,8 @@ struct mt792x_bss_conf {
struct ewma_rssi rssi;
struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS];
unsigned int link_id;
+ struct mt792x_link_sta bmc;
+ bool bmc_valid;
};
struct mt792x_nan_conf {
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index dbea3c2e626f..fa29af8fff49 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -4,6 +4,7 @@
#include <linux/module.h>
#include <linux/firmware.h>
#include <linux/slab.h>
+#include <linux/etherdevice.h>
#include "mt792x.h"
#include "dma.h"
@@ -188,10 +189,20 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
}
if (vif && !control->sta) {
+ struct mt792x_bss_conf *mconf;
struct mt792x_vif *mvif;
+ const u8 *da;
mvif = (struct mt792x_vif *)vif->drv_priv;
+ mconf = &mvif->bss_conf;
wcid = &mvif->sta.deflink.wcid;
+
+ da = (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) ?
+ skb->data :
+ ((struct ieee80211_hdr *)skb->data)->addr1;
+
+ if (mconf->bmc_valid && is_multicast_ether_addr(da))
+ wcid = &mconf->bmc.wcid;
}
if (vif && control->sta && ieee80211_vif_is_mld(vif) &&
--
2.45.2
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 4/9] wifi: mt76: mt7925: harden the ROC channel-grant handshake
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 ` [PATCH 2/9] wifi: mt76: mt7925: bound P2P off-channel mgmt TX lifetime JB Tsai
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 ` JB Tsai
2026-11-04 9:23 ` [PATCH 5/9] wifi: mt76: mt7925: describe the real chandef in ROC requests JB Tsai
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, james-ly huang
From: james-ly huang <james-ly.huang@mediatek.com>
A ROC grant is bound to the request it answers, and the expiry
path must not hand the channel to a newer request:
- validate the grant token (mt7925_mcu_roc_handle_grant):
a late grant for an already-aborted request must not
overwrite band_idx nor wake the waiter of a newer request;
- latch the token in mt7925_roc_work() (mt7925_roc_token_iter)
and report the expiry to mac80211 BEFORE the UNI_ROC_ABORT
round trip, so a mgmt_tx arriving while the abort is in
flight cannot get coalesced onto the dead ROC;
- reclaim a stale ROC in mt7925_set_roc() when
MT76_STATE_ROC is set but its roc_timer already expired:
the caller gets a fresh grant instead of -EBUSY for a
channel it does not own (mgd_prepare_tx ignores -EBUSY);
- skip the mgd_prepare_tx/complete_tx ROC pair for the
deauth/disassoc that tears a link down (info->was_assoc):
the associated link already holds a grant, and aborting it
would flush the still-pending deauth.
Signed-off-by: james-ly huang <james-ly.huang@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt7925/main.c | 85 ++++++++++++++++++-
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 10 +++
2 files changed, 91 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 3fa6c48c6549..b82360da9791 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -527,6 +527,25 @@ static void mt7925_roc_iter(void *priv, u8 *mac,
mt7925_mcu_abort_roc(phy, &mvif->bss_conf, phy->roc_token_id);
}
+struct mt7925_roc_abort_data {
+ struct mt792x_phy *phy;
+ u8 token;
+};
+
+/* Same as mt7925_roc_iter(), except that the token to abort is latched by the
+ * caller instead of read from phy->roc_token_id. Needed wherever a newer ROC
+ * may already have bumped roc_token_id while the old one is still being torn
+ * down - aborting with the newer token would revoke the wrong grant.
+ */
+static void mt7925_roc_token_iter(void *priv, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ struct mt7925_roc_abort_data *data = priv;
+
+ mt7925_mcu_abort_roc(data->phy, &mvif->bss_conf, data->token);
+}
+
void mt7925_roc_abort_sync(struct mt792x_dev *dev)
{
struct mt792x_phy *phy = &dev->phy;
@@ -546,6 +565,7 @@ EXPORT_SYMBOL_GPL(mt7925_roc_abort_sync);
void mt7925_roc_work(struct work_struct *work)
{
+ struct mt7925_roc_abort_data data;
struct mt792x_phy *phy;
phy = (struct mt792x_phy *)container_of(work, struct mt792x_phy,
@@ -554,12 +574,28 @@ void mt7925_roc_work(struct work_struct *work)
if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state))
return;
+ data.phy = phy;
+ data.token = phy->roc_token_id;
+
+ /* Report the expiry to mac80211 first, before the UNI_ROC_ABORT round
+ * trip below. ieee80211_remain_on_channel_expired() only queues
+ * mac80211's own hw_roc_done work, while mt792x_mutex_acquire() (which
+ * also wakes the chip from PM) plus waiting for the MCU reply can take
+ * tens of ms. Notifying afterwards keeps the finished ROC in
+ * mac80211's roc_list for that whole window, and a mgmt_tx arriving in
+ * it gets coalesced onto that dead ROC.
+ *
+ * The token is latched above because mac80211 may start the next ROC
+ * (mt7925_set_roc() -> ++roc_token_id) as soon as it is notified, and
+ * this abort must still revoke the old grant, not the new one.
+ */
+ ieee80211_remain_on_channel_expired(phy->mt76->hw);
+
mt792x_mutex_acquire(phy->dev);
ieee80211_iterate_active_interfaces(phy->mt76->hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
- mt7925_roc_iter, phy);
+ mt7925_roc_token_iter, &data);
mt792x_mutex_release(phy->dev);
- ieee80211_remain_on_channel_expired(phy->mt76->hw);
}
static int mt7925_abort_roc(struct mt792x_phy *phy,
@@ -585,14 +621,39 @@ static int mt7925_set_roc(struct mt792x_phy *phy,
enum mt7925_roc_req type)
{
int err;
+ u8 token;
+
+ /* Every granted ROC arms phy->roc_timer (mt7925_mcu_roc_handle_grant()),
+ * and an ungranted one holds the mt76 mutex for its whole 4s wait, so
+ * "MT76_STATE_ROC set but roc_timer no longer pending" can only mean
+ * the previous ROC's hold time is already over in the FW and just
+ * mt7925_roc_work() has not run yet. Returning -EBUSY for that stale
+ * ROC would hand the caller a channel it does not own -
+ * mt7925_mgd_prepare_tx() ignores the return value, so auth/assoc
+ * would go out off-channel. Finish the stale one here instead and
+ * request the channel again.
+ *
+ * cancel_work() (not the _sync variant) is mandatory: every caller
+ * holds the mt76 mutex and mt7925_roc_work() takes that same mutex.
+ */
+ if (test_bit(MT76_STATE_ROC, &phy->mt76->state) &&
+ !timer_pending(&phy->roc_timer)) {
+ cancel_work(&phy->roc_work);
+
+ if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) {
+ mt7925_mcu_abort_roc(phy, mconf, phy->roc_token_id);
+ ieee80211_remain_on_channel_expired(phy->mt76->hw);
+ }
+ }
if (test_and_set_bit(MT76_STATE_ROC, &phy->mt76->state))
return -EBUSY;
phy->roc_grant = false;
- err = mt7925_mcu_set_roc(phy, mconf, chan, duration, type,
- ++phy->roc_token_id);
+ token = ++phy->roc_token_id;
+
+ err = mt7925_mcu_set_roc(phy, mconf, chan, duration, type, token);
if (err < 0) {
clear_bit(MT76_STATE_ROC, &phy->mt76->state);
goto out;
@@ -2117,6 +2178,14 @@ static void mt7925_mgd_prepare_tx(struct ieee80211_hw *hw,
u16 duration = info->duration ? info->duration :
jiffies_to_msecs(HZ);
+ /* info->was_assoc is set only by ieee80211_set_disassoc(), i.e. this is
+ * the deauth/disassoc that tears the link down. That frame goes out on
+ * the link we are already associated on, so the FW already holds a
+ * channel grant for it and the ROC below is redundant.
+ */
+ if (info->was_assoc)
+ return;
+
mt792x_mutex_acquire(dev);
mt7925_set_roc(mvif->phy, &mvif->bss_conf,
mvif->bss_conf.mt76.ctx->def.chan, duration,
@@ -2130,6 +2199,14 @@ static void mt7925_mgd_complete_tx(struct ieee80211_hw *hw,
{
struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ /* Paired with the was_assoc early return in mt7925_mgd_prepare_tx():
+ * no ROC was requested for the deauth/disassoc, so there is nothing to
+ * abort here. Aborting anyway would revoke the still-needed channel
+ * grant (and could kill an unrelated userspace ROC).
+ */
+ if (info->was_assoc)
+ return;
+
/* Keep the join ROC until the station is authorized, so that it
* covers the 4-way handshake. mt7925_mac_sta_event() releases it.
* On MLD, the active ROC is the MLO ROC, which must be released
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 3eadea1bb453..2ce403000765 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -393,6 +393,16 @@ static void mt7925_mcu_roc_handle_grant(struct mt792x_dev *dev,
/* should never happen */
WARN_ON_ONCE((le16_to_cpu(grant->tag) != UNI_EVENT_ROC_GRANT));
+ duration = le32_to_cpu(grant->max_interval);
+
+ /* Drop a grant that does not belong to the outstanding request:
+ * validate the token before acting on the grant, because a late
+ * grant for an already-aborted request would otherwise overwrite
+ * band_idx and wake the waiter of a newer request.
+ */
+ if (grant->tokenid != dev->phy.roc_token_id)
+ return;
+
if (grant->reqtype == MT7925_ROC_REQ_ROC)
ieee80211_ready_on_channel(hw);
else if (grant->reqtype == MT7925_ROC_REQ_JOIN)
--
2.45.2
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 5/9] wifi: mt76: mt7925: describe the real chandef in ROC requests
2026-11-04 9:23 [PATCH 1/9] wifi: mt76: mt7925: drop P2P mgmt frames received on foreign channel JB Tsai
` (2 preceding siblings ...)
2026-11-04 9:23 ` [PATCH 4/9] wifi: mt76: mt7925: harden the ROC channel-grant handshake JB Tsai
@ 2026-11-04 9:23 ` JB Tsai
2026-11-04 9:23 ` [PATCH 6/9] wifi: mt76: mt7925: request the channel before programming a GO BSS JB Tsai
` (3 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, james-ly huang
From: james-ly huang <james-ly.huang@mediatek.com>
Resolve the full chandef behind the bare channel (mt7925_mcu_roc_chandef):
for a BSS-bound request the BSS's own chanctx already describes that
channel; everything else (off-channel dwell) keeps the 20 MHz NOHT
description, which is what a listen dwell actually uses. Program bw /
center_chan / center_chan2 accordingly, and derive sco from the
primary 40 MHz block instead of comparing against the center channel.
Map the nl80211 channel width to the fw ENUM_CHANNEL_WIDTH_T encoding
via mt7925_chandef_to_cw() and the new MT7925_CW_* enum (the fw casts
roc.bw straight to that enum, so CMD_CBW_* values must not be used).
Signed-off-by: james-ly huang <james-ly.huang@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 88 +++++++++++++++++--
.../wireless/mediatek/mt76/mt7925/mt7925.h | 13 +++
2 files changed, 92 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 2ce403000765..056f7e2f284e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1593,11 +1593,57 @@ int mt7925_mcu_set_mlo_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf
&req, sizeof(req), true);
}
+/* Resolve the full chandef behind a bare ieee80211_channel.
+ *
+ * The CNM channel-privilege request carries bw / center_chan / center_chan2 /
+ * sco, and for a BSS-bound request they must match the BSS's own channel.
+ * mt7925 only has a bare channel at this point, but for a BSS-bound
+ * request (JOIN) the BSS's own chanctx already describes exactly that
+ * channel, so use it.
+ *
+ * Everything else - an off-channel dwell (ROC / OFFCHNL_TX), or a request for a
+ * channel that is not the one this BSS is parked on - keeps the previous 20 MHz
+ * NOHT description, which is what a listen dwell actually uses anyway.
+ */
+static void
+mt7925_mcu_roc_chandef(struct mt792x_bss_conf *mconf,
+ struct ieee80211_channel *chan,
+ enum mt7925_roc_req type,
+ struct cfg80211_chan_def *chandef)
+{
+ if (type == MT7925_ROC_REQ_JOIN &&
+ mconf->mt76.ctx && mconf->mt76.ctx->def.chan == chan) {
+ *chandef = mconf->mt76.ctx->def;
+ return;
+ }
+
+ cfg80211_chandef_create(chandef, chan, NL80211_CHAN_NO_HT);
+}
+
+static u8 mt7925_chandef_to_cw(const struct cfg80211_chan_def *chandef)
+{
+ static const u8 width_to_cw[] = {
+ [NL80211_CHAN_WIDTH_20_NOHT] = MT7925_CW_20_40MHZ,
+ [NL80211_CHAN_WIDTH_20] = MT7925_CW_20_40MHZ,
+ [NL80211_CHAN_WIDTH_40] = MT7925_CW_20_40MHZ,
+ [NL80211_CHAN_WIDTH_80] = MT7925_CW_80MHZ,
+ [NL80211_CHAN_WIDTH_160] = MT7925_CW_160MHZ,
+ [NL80211_CHAN_WIDTH_80P80] = MT7925_CW_80P80MHZ,
+ [NL80211_CHAN_WIDTH_5] = MT7925_CW_5MHZ,
+ [NL80211_CHAN_WIDTH_10] = MT7925_CW_10MHZ,
+ [NL80211_CHAN_WIDTH_320] = MT7925_CW_320MHZ,
+ };
+
+ if (chandef->width >= ARRAY_SIZE(width_to_cw))
+ return 0;
+
+ return width_to_cw[chandef->width];
+}
+
int mt7925_mcu_set_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf,
struct ieee80211_channel *chan, int duration,
enum mt7925_roc_req type, u8 token_id)
{
- int center_ch = ieee80211_frequency_to_channel(chan->center_freq);
struct mt792x_dev *dev = phy->dev;
struct {
struct {
@@ -1613,18 +1659,42 @@ int mt7925_mcu_set_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf,
.maxinterval = cpu_to_le32(duration),
.bss_idx = mconf->mt76.idx,
.control_channel = chan->hw_value,
- .bw = CMD_CBW_20MHZ,
- .bw_from_ap = CMD_CBW_20MHZ,
- .center_chan = center_ch,
- .center_chan_from_ap = center_ch,
.dbdcband = 0xff, /* auto */
},
};
+ struct cfg80211_chan_def chandef;
+ int center_ch, pri40_freq;
+ u8 bw;
+
+ mt7925_mcu_roc_chandef(mconf, chan, type, &chandef);
+ center_ch = ieee80211_frequency_to_channel(chandef.center_freq1);
+ bw = mt7925_chandef_to_cw(&chandef);
+
+ /* Describe the REAL chandef, not a 20 MHz stub: CNM uses bw / center_chan
+ * to pick the band and to decide whether the request can share an existing
+ * SCC.
+ */
+ req.roc.bw = bw;
+ req.roc.bw_from_ap = bw;
+ req.roc.center_chan = center_ch;
+ req.roc.center_chan_from_ap = center_ch;
- if (chan->hw_value < center_ch)
- req.roc.sco = 1; /* SCA */
- else if (chan->hw_value > center_ch)
- req.roc.sco = 3; /* SCB */
+ if (chandef.center_freq2) {
+ u8 center_ch2 = ieee80211_frequency_to_channel(chandef.center_freq2);
+
+ req.roc.center_chan2 = center_ch2;
+ req.roc.center_chan2_from_ap = center_ch2;
+ }
+
+ /* Secondary channel offset of the PRIMARY 40 MHz block.
+ * sco: 0 = SCN, 1 = SCA (secondary above), 3 = SCB (secondary below).
+ */
+ if (nl80211_chan_width_to_mhz(chandef.width) >= 40) {
+ pri40_freq = cfg80211_chandef_primary(&chandef,
+ NL80211_CHAN_WIDTH_40, NULL);
+ if (pri40_freq > 0)
+ req.roc.sco = chan->center_freq < pri40_freq ? 1 : 3;
+ }
switch (chan->band) {
case NL80211_BAND_6GHZ:
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index 636efe038392..36cbeaedb7b2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -60,6 +60,19 @@ enum {
UNI_ROC_NUM
};
+/* firmware channel width encoding (FW ENUM_CHANNEL_WIDTH_T), differs from
+ * CMD_CBW_*
+ */
+enum mt7925_fw_chan_width {
+ MT7925_CW_20_40MHZ,
+ MT7925_CW_80MHZ,
+ MT7925_CW_160MHZ,
+ MT7925_CW_80P80MHZ,
+ MT7925_CW_5MHZ,
+ MT7925_CW_10MHZ,
+ MT7925_CW_320MHZ,
+};
+
enum mt7925_roc_req {
MT7925_ROC_REQ_JOIN,
MT7925_ROC_REQ_ROC,
--
2.45.2
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 6/9] wifi: mt76: mt7925: request the channel before programming a GO BSS
2026-11-04 9:23 [PATCH 1/9] wifi: mt76: mt7925: drop P2P mgmt frames received on foreign channel JB Tsai
` (3 preceding siblings ...)
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 ` JB Tsai
2026-11-04 9:23 ` [PATCH 7/9] wifi: mt76: mt7925: inject the deauth for an aging-timeout SAP event JB Tsai
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, james-ly huang
From: james-ly huang <james-ly.huang@mediatek.com>
Mirror the STA JOIN channel-grant handshake for a P2P GO
coming up. Without an ACQUIRE, mconf->mt76.band_idx stayed
at the 0xff auto value that only a ROC grant fills in, so
the BSS_INFO_BASIC always guessed the DBDC band:
- .start_ap runs ACQUIRE (GO_START_BSS reqtype, which CNM treats
as the BSS-owning request) -> add_bss_info with the granted
band_idx -> ABORT, with per-role hold times (3s P2P GO /
200ms SAP). Falls back to a JOIN request once on FW that
predates GO_START_BSS; a refused grant is not fatal;
- gate .change_chanctx on p2p: the RLM-only BSS_INFO_UPDATE
that mac80211 issues BEFORE the grant reaches CNM with a
guessed band - skip it, both roles send a full BASIC+RLM
update once the grant supplied the real band_idx
(mt7925_p2p_chanctx_bss_programmed in p2p.c);
- P2P GC join: hold the channel for 4s instead of the 1s
mac80211 default, so the whole auth/assoc exchange fits in
one grant (a GO only listens part of the time);
- renumber the channel-privilege reqtypes to match the FW CNM
interface (OFFCHNL_TX / GO_START_BSS / DFS_CAC);
- add mt792x_phy::roc_is_mlo so the grant handler can tell an MLO
ROC apart from a plain remain_on_channel request.
Signed-off-by: james-ly huang <james-ly.huang@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt7925/main.c | 65 ++++++++++++++++++-
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 10 +--
.../wireless/mediatek/mt76/mt7925/mt7925.h | 16 ++++-
.../net/wireless/mediatek/mt76/mt7925/p2p.c | 35 ++++++++++
.../net/wireless/mediatek/mt76/mt7925/p2p.h | 2 +
drivers/net/wireless/mediatek/mt76/mt792x.h | 1 +
6 files changed, 119 insertions(+), 10 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index b82360da9791..cfdb736f53ca 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -2050,11 +2050,51 @@ mt7925_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
{
struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
struct mt792x_dev *dev = mt792x_hw_dev(hw);
+ struct mt792x_bss_conf *mconf = &mvif->bss_conf;
+ bool roc_taken = false;
+ u32 hold_time;
int err;
mt792x_mutex_acquire(dev);
- err = mt7925_mcu_add_bss_info(&dev->phy, mvif->bss_conf.mt76.ctx,
+ /* Mirror the STA JOIN channel-grant handshake for a GO/AP coming up.
+ * mt7925_mac_link_bss_add() leaves mconf->mt76.band_idx at 0xff (auto)
+ * and mt7925_mcu_roc_iter() - reached only from a ROC grant event - is
+ * the ONLY writer of it. A GO had no ROC at all, so the BSS_INFO_BASIC
+ * below always carried band_idx=0xff: the host never learned which DBDC
+ * band the FW parked the GO on, TXD1 TGID stayed FW-auto, and the
+ * host-built IEs could end up describing a different channel than the
+ * one the radio actually ended up on.
+ *
+ * The request type is GO_START_BSS, not JOIN: CNM treats
+ * GO_START_BSS as the BSS-owning request, so it is allowed to
+ * create a new channel context rather than only joining an
+ * existing one.
+ */
+ if (mconf->mt76.ctx) {
+ hold_time = vif->p2p ? MT7925_P2P_CHNL_HOLD_TIME_MS :
+ MT7925_SAP_CHNL_HOLD_TIME_MS;
+
+ err = mt7925_set_roc(mvif->phy, mconf, mconf->mt76.ctx->def.chan,
+ hold_time, MT7925_ROC_REQ_GO_START_BSS);
+
+ /* FW that predates the GO_START_BSS reqtype rejects the ACQUIRE
+ * outright. JOIN is understood by every branch and produces the
+ * same band_idx update, so fall back to it once. -EBUSY and
+ * -ETIMEDOUT mean the command itself WAS accepted (another ROC
+ * holds the flag / CNM never granted), so do not burn a second
+ * 4s wait on those.
+ */
+ if (err < 0 && err != -EBUSY && err != -ETIMEDOUT)
+ err = mt7925_set_roc(mvif->phy, mconf,
+ mconf->mt76.ctx->def.chan,
+ hold_time, MT7925_ROC_REQ_JOIN);
+
+ if (!err)
+ roc_taken = true;
+ }
+
+ err = mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx,
link_conf, NULL, true);
if (err)
goto out;
@@ -2069,6 +2109,14 @@ mt7925_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
out:
mt792x_mutex_release(dev);
+ /* Release the grant now that the BSS is programmed. The GO keeps its
+ * channel through the BSS_INFO RLM TLV, not through the ROC, and the FW
+ * CNM arms a band-abort watchdog on GRANTED that asserts if the host
+ * never sends the abort. Also covers the error paths above.
+ */
+ if (roc_taken)
+ mt7925_abort_roc(mvif->phy, mconf);
+
return err;
}
@@ -2156,8 +2204,10 @@ mt7925_change_chanctx(struct ieee80211_hw *hw,
struct ieee80211_bss_conf *link_conf;
link_conf = mt792x_vif_to_bss_conf(vif, mconf->link_id);
- mt7925_mcu_set_chctx(mvif->phy->mt76, &mconf->mt76,
- link_conf, ctx);
+
+ if (mt7925_p2p_chanctx_bss_programmed(vif, link_conf))
+ mt7925_mcu_set_chctx(mvif->phy->mt76, &mconf->mt76,
+ link_conf, ctx);
if (changed & IEEE80211_CHANCTX_CHANGE_PUNCTURING)
mt7925_mcu_set_eht_pp(mvif->phy->mt76, &mconf->mt76,
@@ -2186,6 +2236,15 @@ static void mt7925_mgd_prepare_tx(struct ieee80211_hw *hw,
if (info->was_assoc)
return;
+ /* P2P GC join: hold the channel for
+ * MT7925_P2P_GC_JOIN_CH_REQ_INTERVAL_MS (4s) instead of the 1s
+ * mac80211 default, because a GO is only listening on its operating
+ * channel part of the time and the whole auth/assoc exchange has to
+ * fit inside one grant.
+ */
+ if (!info->duration && vif->p2p)
+ duration = MT7925_P2P_GC_JOIN_CH_REQ_INTERVAL_MS;
+
mt792x_mutex_acquire(dev);
mt7925_set_roc(mvif->phy, &mvif->bss_conf,
mvif->bss_conf.mt76.ctx->def.chan, duration,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 056f7e2f284e..6ec74d7bfd82 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -405,7 +405,9 @@ static void mt7925_mcu_roc_handle_grant(struct mt792x_dev *dev,
if (grant->reqtype == MT7925_ROC_REQ_ROC)
ieee80211_ready_on_channel(hw);
- else if (grant->reqtype == MT7925_ROC_REQ_JOIN)
+ else if (grant->reqtype == MT7925_ROC_REQ_JOIN ||
+ grant->reqtype == MT7925_ROC_REQ_GO_START_BSS ||
+ dev->phy.roc_is_mlo)
ieee80211_iterate_active_interfaces_atomic(hw,
IEEE80211_IFACE_ITER_RESUME_ALL,
mt7925_mcu_roc_iter, grant);
@@ -1598,8 +1600,8 @@ int mt7925_mcu_set_mlo_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf
* The CNM channel-privilege request carries bw / center_chan / center_chan2 /
* sco, and for a BSS-bound request they must match the BSS's own channel.
* mt7925 only has a bare channel at this point, but for a BSS-bound
- * request (JOIN) the BSS's own chanctx already describes exactly that
- * channel, so use it.
+ * request (JOIN / GO_START_BSS) the BSS's own chanctx already describes
+ * exactly that channel, so use it.
*
* Everything else - an off-channel dwell (ROC / OFFCHNL_TX), or a request for a
* channel that is not the one this BSS is parked on - keeps the previous 20 MHz
@@ -1611,7 +1613,7 @@ mt7925_mcu_roc_chandef(struct mt792x_bss_conf *mconf,
enum mt7925_roc_req type,
struct cfg80211_chan_def *chandef)
{
- if (type == MT7925_ROC_REQ_JOIN &&
+ if ((type == MT7925_ROC_REQ_JOIN || type == MT7925_ROC_REQ_GO_START_BSS) &&
mconf->mt76.ctx && mconf->mt76.ctx->def.chan == chan) {
*chandef = mconf->mt76.ctx->def;
return;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index 36cbeaedb7b2..e3eee41b22c3 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -73,15 +73,25 @@ enum mt7925_fw_chan_width {
MT7925_CW_320MHZ,
};
+/* Channel-privilege request types. The values are part of the FW CNM
+ * interface (CH_REQ_TYPE_*), so do not renumber.
+ */
enum mt7925_roc_req {
- MT7925_ROC_REQ_JOIN,
- MT7925_ROC_REQ_ROC,
- MT7925_ROC_REQ_SUB_LINK,
+ MT7925_ROC_REQ_JOIN, /* CH_REQ_TYPE_JOIN */
+ MT7925_ROC_REQ_ROC, /* CH_REQ_TYPE_ROC */
+ MT7925_ROC_REQ_OFFCHNL_TX = 2, /* CH_REQ_TYPE_OFFCHNL_TX */
+ MT7925_ROC_REQ_GO_START_BSS = 3, /* CH_REQ_TYPE_GO_START_BSS */
+ MT7925_ROC_REQ_DFS_CAC = 4, /* CH_REQ_TYPE_DFS_CAC */
MT7925_ROC_REQ_MLSR_AG = 10,
MT7925_ROC_REQ_MLSR_AA,
MT7925_ROC_REQ_NUM
};
+/* CNM channel-privilege hold times, per role. */
+#define MT7925_SAP_CHNL_HOLD_TIME_MS 200 /* SAP_CHNL_HOLD_TIME_MS */
+#define MT7925_P2P_CHNL_HOLD_TIME_MS 3000 /* P2P_CHNL_HOLD_TIME_MS */
+#define MT7925_P2P_GC_JOIN_CH_REQ_INTERVAL_MS 4000 /* P2P_GC_JOIN_CH_REQUEST_INTERVAL */
+
enum {
UNI_EVENT_ROC_GRANT = 0,
UNI_EVENT_ROC_GRANT_SUB_LINK = 4,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
index 4e41ba9beca1..f41098b324ba 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
@@ -427,3 +427,38 @@ void mt7925_p2p_bmc_remove(struct mt792x_dev *dev,
mt76_wcid_mask_clear(dev->mt76.wcid_mask, wcid->idx);
}
+/* ------------------------------------------------------------------ */
+/* .change_chanctx gate */
+/* ------------------------------------------------------------------ */
+
+/* Is this link's BSS already programmed in FW on a granted channel, i.e. is an
+ * RLM-only BSS_INFO_UPDATE from .change_chanctx a genuine *channel update*?
+ *
+ * mac80211 runs ieee80211_link_use_channel() -> ieee80211_assign_link_chanctx()
+ * before .start_ap and before the auth exchange, and that path calls
+ * ieee80211_recalc_chanctx_min_def() -> drv_change_chanctx(). For a P2P link
+ * that is still coming up the resulting RLM TLV therefore reaches the FW
+ * *before* the channel privilege request: CNM creates its channel-info entry
+ * from a band it has to guess, because mconf->mt76.band_idx is still the 0xff
+ * "auto" value that only the ROC grant fills in (mt7925_mcu_roc_iter).
+ *
+ * Nothing is lost by skipping it: once the grant has supplied the real
+ * band_idx both roles send a full BASIC+RLM BSS_INFO_UPDATE anyway, and every
+ * later .change_chanctx (bandwidth change, CSA) passes the test.
+ */
+bool mt7925_p2p_chanctx_bss_programmed(struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *link_conf)
+{
+ if (!vif->p2p || !link_conf)
+ return true;
+
+ switch (vif->type) {
+ case NL80211_IFTYPE_AP:
+ return link_conf->enable_beacon; /* P2P GO */
+ case NL80211_IFTYPE_STATION:
+ return vif->cfg.assoc; /* P2P GC */
+ default:
+ return true;
+ }
+}
+
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
index 25b6c57749a2..f7af45ee4c1f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
@@ -51,5 +51,7 @@ int mt7925_p2p_bmc_add(struct mt792x_dev *dev,
struct mt792x_bss_conf *mconf);
void mt7925_p2p_bmc_remove(struct mt792x_dev *dev,
struct mt792x_bss_conf *mconf);
+bool mt7925_p2p_chanctx_bss_programmed(struct ieee80211_vif *vif,
+ struct ieee80211_bss_conf *link_conf);
#endif /* __MT7925_P2P_H */
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 66c8722220cc..94186e9f77c8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -242,6 +242,7 @@ struct mt792x_phy {
wait_queue_head_t roc_wait;
u8 roc_token_id;
bool roc_grant;
+ bool roc_is_mlo;
};
struct mt792x_irq_map {
--
2.45.2
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 7/9] wifi: mt76: mt7925: inject the deauth for an aging-timeout SAP event
2026-11-04 9:23 [PATCH 1/9] wifi: mt76: mt7925: drop P2P mgmt frames received on foreign channel JB Tsai
` (4 preceding siblings ...)
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 ` 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
7 siblings, 1 reply; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, james-ly huang
From: james-ly huang <james-ly.huang@mediatek.com>
The FW ages out inactive clients of an AP/GO and reports it with a
SAP aging-timeout event; the expected reaction is a deauth with
REASON_CODE_DISASSOC_INACTIVITY. The AP MLME here lives in
wpa_supplicant (softmac, device_ap_sme=0), so synthesize the deauth
frame the GO would have received from the GC and hand it to
userspace via cfg80211_rx_mgmt_khz().
Handle the new MCU_UNI_EVENT_SAP in p2p.c
(mt7925_p2p_mcu_sap_event) and dispatch it from the unsolicited
event handler.
Signed-off-by: james-ly huang <james-ly.huang@mediatek.com>
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 +
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 4 +
.../net/wireless/mediatek/mt76/mt7925/p2p.c | 141 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/p2p.h | 16 ++
4 files changed, 162 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index bb98a6eacac4..817554ffb9d8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1099,6 +1099,7 @@ enum {
MCU_UNI_EVENT_PS_SYNC = 0x0d,
MCU_UNI_EVENT_SCAN_DONE = 0x0e,
MCU_UNI_EVENT_RDD_REPORT = 0x11,
+ MCU_UNI_EVENT_SAP = 0x1c,
MCU_UNI_EVENT_ROC = 0x27,
MCU_UNI_EVENT_MBMC = 0x28,
MCU_UNI_EVENT_TX_DONE = 0x2d,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 6ec74d7bfd82..d9a5b5f57fba 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -8,6 +8,7 @@
#include "mcu.h"
#include "mac.h"
#include "nan.h"
+#include "p2p.h"
#define MT_STA_BFER BIT(0)
#define MT_STA_BFEE BIT(1)
@@ -734,6 +735,9 @@ mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev,
case MCU_UNI_EVENT_BSS_BEACON_LOSS:
mt7925_mcu_connection_loss_event(dev, skb);
break;
+ case MCU_UNI_EVENT_SAP:
+ mt7925_p2p_mcu_sap_event(dev, skb);
+ break;
case MCU_UNI_EVENT_RSSI_MONITOR:
mt7925_mcu_rssi_monitor_event(dev, skb);
break;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
index f41098b324ba..d05c936d40ea 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
@@ -462,3 +462,144 @@ bool mt7925_p2p_chanctx_bss_programmed(struct ieee80211_vif *vif,
}
}
+/* ------------------------------------------------------------------ */
+/* P2P-only MCU events */
+/* ------------------------------------------------------------------ */
+
+/* FW reported that a peer of our AP/GO aged out (no traffic/no ack for the FW
+ * inactivity period); the expected reaction is a deauth with
+ * REASON_CODE_DISASSOC_INACTIVITY. Here the AP MLME lives in
+ * wpa_supplicant (softmac, device_ap_sme=0), so instead of tearing the
+ * STA down in the driver we synthesize the deauth frame the GO would
+ * have received from the GC and hand it to userspace via
+ * NL80211_CMD_FRAME.
+ */
+static void
+mt7925_mcu_sap_aging_timeout(struct mt792x_dev *dev, u16 wlan_idx)
+{
+ struct {
+ struct ieee80211_hdr_3addr hdr;
+ __le16 reason_code;
+ } __packed deauth = {};
+ u8 peer[ETH_ALEN], own[ETH_ALEN], bssid[ETH_ALEN];
+ struct ieee80211_chanctx_conf *ctx;
+ struct mt792x_bss_conf *mconf;
+ struct mt792x_link_sta *mlink;
+ struct ieee80211_vif *vif;
+ struct ieee80211_sta *sta;
+ struct mt792x_vif *mvif;
+ struct mt792x_sta *msta;
+ struct mt76_wcid *wcid;
+ struct wireless_dev *wdev;
+ int freq = 0;
+ u8 link_id;
+
+ rcu_read_lock();
+
+ wcid = mt76_wcid_ptr(dev, wlan_idx);
+ if (!wcid || !wcid->sta)
+ goto unlock;
+
+ mlink = container_of(wcid, struct mt792x_link_sta, wcid);
+ msta = mlink->sta;
+ if (!msta || !msta->vif)
+ goto unlock;
+
+ vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
+ if (vif->type != NL80211_IFTYPE_AP &&
+ vif->type != NL80211_IFTYPE_P2P_GO)
+ goto unlock;
+
+ mvif = (struct mt792x_vif *)vif->drv_priv;
+ sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+ link_id = wcid->link_id;
+
+ ether_addr_copy(peer, sta->addr);
+ ether_addr_copy(own, vif->addr);
+ ether_addr_copy(bssid, vif->bss_conf.bssid);
+ mconf = &mvif->bss_conf;
+
+ if (ieee80211_vif_is_mld(vif) && link_id < IEEE80211_LINK_UNSPECIFIED) {
+ struct ieee80211_link_sta *link_sta;
+ struct ieee80211_bss_conf *link_conf;
+ struct mt792x_bss_conf *link_mconf;
+
+ link_sta = rcu_dereference(sta->link[link_id]);
+ if (link_sta)
+ ether_addr_copy(peer, link_sta->addr);
+
+ link_conf = rcu_dereference(vif->link_conf[link_id]);
+ if (link_conf) {
+ ether_addr_copy(own, link_conf->addr);
+ ether_addr_copy(bssid, link_conf->bssid);
+ }
+
+ link_mconf = rcu_dereference(mvif->link_conf[link_id]);
+ if (link_mconf)
+ mconf = link_mconf;
+ }
+
+ ctx = mconf->mt76.ctx;
+ if (ctx && ctx->def.chan)
+ freq = ctx->def.chan->center_freq;
+ else if (dev->mphy.chandef.chan)
+ freq = dev->mphy.chandef.chan->center_freq;
+
+ rcu_read_unlock();
+
+ deauth.hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
+ IEEE80211_STYPE_DEAUTH);
+ ether_addr_copy(deauth.hdr.addr1, own); /* DA: the GO itself */
+ ether_addr_copy(deauth.hdr.addr2, peer); /* SA: the aged-out GC */
+ ether_addr_copy(deauth.hdr.addr3, bssid);
+ deauth.reason_code = cpu_to_le16(WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY);
+
+ wdev = ieee80211_vif_to_wdev(vif);
+ if (!wdev)
+ return;
+
+ cfg80211_rx_mgmt_khz(wdev, MHZ_TO_KHZ(freq), 0, (const u8 *)&deauth,
+ sizeof(deauth), 0);
+
+ return;
+
+unlock:
+ rcu_read_unlock();
+}
+
+void mt7925_p2p_mcu_sap_event(struct mt792x_dev *dev, struct sk_buff *skb)
+{
+ struct mt7925_uni_sap_event *event;
+ u32 tlv_len;
+ u8 *tlv;
+
+ skb_pull(skb, sizeof(struct mt7925_mcu_rxd));
+ if (skb->len < sizeof(*event))
+ return;
+
+ event = (struct mt7925_uni_sap_event *)skb->data;
+ tlv = event->tlv;
+ tlv_len = skb->len - sizeof(*event);
+
+ while (tlv_len >= sizeof(struct tlv)) {
+ struct tlv *hdr = (struct tlv *)tlv;
+ u16 tag = le16_to_cpu(hdr->tag);
+ u16 len = le16_to_cpu(hdr->len);
+
+ if (len < sizeof(*hdr) || len > tlv_len)
+ break;
+
+ if (tag == MT7925_SAP_TAG_AGING_TIMEOUT &&
+ len >= sizeof(struct mt7925_sap_aging_timeout_tlv)) {
+ struct mt7925_sap_aging_timeout_tlv *aging =
+ (struct mt7925_sap_aging_timeout_tlv *)tlv;
+
+ mt7925_mcu_sap_aging_timeout(dev,
+ le16_to_cpu(aging->wlan_idx));
+ }
+
+ tlv += len;
+ tlv_len -= len;
+ }
+}
+
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
index f7af45ee4c1f..9afae222845f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
@@ -39,6 +39,20 @@ static inline bool mt7925_is_p2p_go_probe_resp(struct sk_buff *skb)
return ieee80211_is_probe_resp(mgmt->frame_control);
}
+/* SAP event (FW event UNI_EVENT_ID_SAP / enum ENUM_UNI_EVENT_SAP_TAG) */
+#define MT7925_SAP_TAG_AGING_TIMEOUT 0
+
+struct mt7925_sap_aging_timeout_tlv {
+ __le16 tag;
+ __le16 len;
+ __le16 wlan_idx;
+ u8 pad[2];
+} __packed;
+
+struct mt7925_uni_sap_event {
+ u8 pad[4]; /* fixed field */
+ u8 tlv[];
+} __packed;
/* TX/RX path overrides, BMC wcid bookkeeping and the P2P-only MCU event
* handlers.
*/
@@ -54,4 +68,6 @@ void mt7925_p2p_bmc_remove(struct mt792x_dev *dev,
bool mt7925_p2p_chanctx_bss_programmed(struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
+void mt7925_p2p_mcu_sap_event(struct mt792x_dev *dev, struct sk_buff *skb);
+
#endif /* __MT7925_P2P_H */
--
2.45.2
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH 7/9] wifi: mt76: mt7925: inject the deauth for an aging-timeout SAP event
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
0 siblings, 0 replies; 10+ messages in thread
From: kernel test robot @ 2026-11-06 0:34 UTC (permalink / raw)
To: JB Tsai, nbd, lorenzo
Cc: oe-kbuild-all, linux-wireless, linux-mediatek, Sean.Wang,
James-LY.Huang, jb.tsai
Hi JB,
kernel test robot noticed the following build warnings:
[auto build test WARNING on wireless-next/main]
[also build test WARNING on linus/master v7.4-rc1 next-20261105]
[cannot apply to wireless/main]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/JB-Tsai/wifi-mt76-mt7925-bound-P2P-off-channel-mgmt-TX-lifetime/20261104-172337
base: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link: https://lore.kernel.org/r/20261104092344.1932406-7-jb.tsai%40mediatek.com
patch subject: [PATCH 7/9] wifi: mt76: mt7925: inject the deauth for an aging-timeout SAP event
config: x86_64-rhel-9.4 (https://download.01.org/0day-ci/archive/20261106/202611060815.93tauu28-lkp@intel.com/config)
compiler: gcc-14 (Debian 14.2.0-19) 14.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261106/202611060815.93tauu28-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611060815.93tauu28-lkp@intel.com/
All warnings (new ones prefixed by >>):
drivers/net/wireless/mediatek/mt76/mt7925/p2p.c: In function 'mt7925_mcu_sap_aging_timeout':
>> drivers/net/wireless/mediatek/mt76/mt7925/p2p.c:483:9: warning: alignment 1 of 'struct <anonymous>' is less than 2 [-Wpacked-not-aligned]
483 | } __packed deauth = {};
| ^
vim +483 drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
468
469 /* FW reported that a peer of our AP/GO aged out (no traffic/no ack for the FW
470 * inactivity period); the expected reaction is a deauth with
471 * REASON_CODE_DISASSOC_INACTIVITY. Here the AP MLME lives in
472 * wpa_supplicant (softmac, device_ap_sme=0), so instead of tearing the
473 * STA down in the driver we synthesize the deauth frame the GO would
474 * have received from the GC and hand it to userspace via
475 * NL80211_CMD_FRAME.
476 */
477 static void
478 mt7925_mcu_sap_aging_timeout(struct mt792x_dev *dev, u16 wlan_idx)
479 {
480 struct {
481 struct ieee80211_hdr_3addr hdr;
482 __le16 reason_code;
> 483 } __packed deauth = {};
484 u8 peer[ETH_ALEN], own[ETH_ALEN], bssid[ETH_ALEN];
485 struct ieee80211_chanctx_conf *ctx;
486 struct mt792x_bss_conf *mconf;
487 struct mt792x_link_sta *mlink;
488 struct ieee80211_vif *vif;
489 struct ieee80211_sta *sta;
490 struct mt792x_vif *mvif;
491 struct mt792x_sta *msta;
492 struct mt76_wcid *wcid;
493 struct wireless_dev *wdev;
494 int freq = 0;
495 u8 link_id;
496
497 rcu_read_lock();
498
499 wcid = mt76_wcid_ptr(dev, wlan_idx);
500 if (!wcid || !wcid->sta)
501 goto unlock;
502
503 mlink = container_of(wcid, struct mt792x_link_sta, wcid);
504 msta = mlink->sta;
505 if (!msta || !msta->vif)
506 goto unlock;
507
508 vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
509 if (vif->type != NL80211_IFTYPE_AP &&
510 vif->type != NL80211_IFTYPE_P2P_GO)
511 goto unlock;
512
513 mvif = (struct mt792x_vif *)vif->drv_priv;
514 sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
515 link_id = wcid->link_id;
516
517 ether_addr_copy(peer, sta->addr);
518 ether_addr_copy(own, vif->addr);
519 ether_addr_copy(bssid, vif->bss_conf.bssid);
520 mconf = &mvif->bss_conf;
521
522 if (ieee80211_vif_is_mld(vif) && link_id < IEEE80211_LINK_UNSPECIFIED) {
523 struct ieee80211_link_sta *link_sta;
524 struct ieee80211_bss_conf *link_conf;
525 struct mt792x_bss_conf *link_mconf;
526
527 link_sta = rcu_dereference(sta->link[link_id]);
528 if (link_sta)
529 ether_addr_copy(peer, link_sta->addr);
530
531 link_conf = rcu_dereference(vif->link_conf[link_id]);
532 if (link_conf) {
533 ether_addr_copy(own, link_conf->addr);
534 ether_addr_copy(bssid, link_conf->bssid);
535 }
536
537 link_mconf = rcu_dereference(mvif->link_conf[link_id]);
538 if (link_mconf)
539 mconf = link_mconf;
540 }
541
542 ctx = mconf->mt76.ctx;
543 if (ctx && ctx->def.chan)
544 freq = ctx->def.chan->center_freq;
545 else if (dev->mphy.chandef.chan)
546 freq = dev->mphy.chandef.chan->center_freq;
547
548 rcu_read_unlock();
549
550 deauth.hdr.frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT |
551 IEEE80211_STYPE_DEAUTH);
552 ether_addr_copy(deauth.hdr.addr1, own); /* DA: the GO itself */
553 ether_addr_copy(deauth.hdr.addr2, peer); /* SA: the aged-out GC */
554 ether_addr_copy(deauth.hdr.addr3, bssid);
555 deauth.reason_code = cpu_to_le16(WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY);
556
557 wdev = ieee80211_vif_to_wdev(vif);
558 if (!wdev)
559 return;
560
561 cfg80211_rx_mgmt_khz(wdev, MHZ_TO_KHZ(freq), 0, (const u8 *)&deauth,
562 sizeof(deauth), 0);
563
564 return;
565
566 unlock:
567 rcu_read_unlock();
568 }
569
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 8/9] wifi: mt76: mt7925: track BSS absence and drop stale GO probe responses
2026-11-04 9:23 [PATCH 1/9] wifi: mt76: mt7925: drop P2P mgmt frames received on foreign channel JB Tsai
` (5 preceding siblings ...)
2026-11-04 9:23 ` [PATCH 7/9] wifi: mt76: mt7925: inject the deauth for an aging-timeout SAP event JB Tsai
@ 2026-11-04 9:23 ` JB Tsai
2026-11-04 9:23 ` [PATCH 9/9] wifi: mt76: mt7925: send the common scan IEs and size the request JB Tsai
7 siblings, 0 replies; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, james-ly huang
From: james-ly huang <james-ly.huang@mediatek.com>
The FW tells the host with a BSS_IS_ABSENCE event when a P2P GO's
BSS currently has no channel
- latch the absent / free-quota state per bss_conf
(mt7925_p2p_mcu_bss_is_absence_event in p2p.c), and kick the
TX path (tx_worker) on the absent->present edge so whatever
piled up while the BSS had no channel is dequeued
immediately;
- clear the latched state where the BSS index is (re)assigned:
a GO stopped while absent and restarted on the same netdev
would otherwise come back with absent still latched and drop
every Probe Response until the FW sends a presence event
for the new bss_idx;
- the TX path (mt792x_tx) drops a GO Probe Response while the
BSS is absent - by the time the FW comes back the requester
has moved on, so the response is stale and only costs airtime
and a TX-abort.
Signed-off-by: james-ly huang <james-ly.huang@mediatek.com>
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 +
.../net/wireless/mediatek/mt76/mt7925/main.c | 12 +++
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 3 +
.../net/wireless/mediatek/mt76/mt7925/p2p.c | 97 +++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/p2p.h | 20 ++++
drivers/net/wireless/mediatek/mt76/mt792x.h | 25 +++++
.../net/wireless/mediatek/mt76/mt792x_core.c | 16 +++
7 files changed, 174 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 817554ffb9d8..2acaae9830fb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1102,6 +1102,7 @@ enum {
MCU_UNI_EVENT_SAP = 0x1c,
MCU_UNI_EVENT_ROC = 0x27,
MCU_UNI_EVENT_MBMC = 0x28,
+ MCU_UNI_EVENT_BSS_IS_ABSENCE = 0x29,
MCU_UNI_EVENT_TX_DONE = 0x2d,
MCU_UNI_EVENT_THERMAL = 0x35,
MCU_UNI_EVENT_RSSI_MONITOR = 0x41,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index cfdb736f53ca..25e981e38ea9 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -401,6 +401,18 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev,
0 : mconf->mt76.idx;
mconf->mt76.band_idx = 0xff;
+ /* A freshly configured BSS is on channel until the FW says otherwise.
+ * mt792x_bss_conf lives in vif->drv_priv / mvif->link_conf[], which
+ * mac80211 only zeroes when the interface is created - a GO that is
+ * stopped while absent and restarted on the same netdev would come back
+ * with absent still latched, and then drop every Probe Response until
+ * the FW happens to send a presence event for the new bss_idx. Clear it
+ * here, where the BSS index is (re)assigned.
+ */
+ mconf->absent = false;
+ mconf->free_quota = 0;
+ mconf->absence_tstamp = 0;
+
if (is_mt7927(&dev->mt76)) {
struct ieee80211_channel *chan;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index d9a5b5f57fba..24de2ff3a15d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -738,6 +738,9 @@ mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev,
case MCU_UNI_EVENT_SAP:
mt7925_p2p_mcu_sap_event(dev, skb);
break;
+ case MCU_UNI_EVENT_BSS_IS_ABSENCE:
+ mt7925_p2p_mcu_bss_is_absence_event(dev, skb);
+ break;
case MCU_UNI_EVENT_RSSI_MONITOR:
mt7925_mcu_rssi_monitor_event(dev, skb);
break;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
index d05c936d40ea..9be6891cc5d9 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.c
@@ -603,3 +603,100 @@ void mt7925_p2p_mcu_sap_event(struct mt792x_dev *dev, struct sk_buff *skb)
}
}
+struct mt7925_bss_absence_data {
+ u8 bss_idx;
+ u8 is_absent;
+ u8 free_quota;
+};
+
+/* Per-BSS handling of the BSS_IS_ABSENCE event: latch the absent /
+ * free-quota state, and on the absent->present edge kick the TX path so
+ * whatever piled up while the BSS had no channel gets dequeued
+ * immediately (the kick is the tx_worker).
+ */
+static void
+mt7925_mcu_bss_absence_iter(void *priv, u8 *mac, struct ieee80211_vif *vif)
+{
+ struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+ struct mt7925_bss_absence_data *data = priv;
+ struct mt792x_bss_conf *mconf = NULL;
+ struct mt792x_dev *dev = mvif->phy->dev;
+ unsigned long valid_links, now = jiffies;
+ unsigned int link_id;
+ bool absent, was_absent;
+
+ if (ieee80211_vif_is_mld(vif)) {
+ valid_links = mvif->valid_links;
+ for_each_set_bit(link_id, &valid_links,
+ IEEE80211_MLD_MAX_NUM_LINKS) {
+ struct mt792x_bss_conf *link_mconf;
+
+ link_mconf = rcu_dereference(mvif->link_conf[link_id]);
+ if (link_mconf && link_mconf->mt76.idx == data->bss_idx) {
+ mconf = link_mconf;
+ break;
+ }
+ }
+ } else if (mvif->bss_conf.mt76.idx == data->bss_idx) {
+ mconf = &mvif->bss_conf;
+ }
+
+ if (!mconf)
+ return;
+
+ absent = !!data->is_absent;
+ was_absent = mconf->absent;
+
+ mconf->free_quota = data->free_quota;
+ if (was_absent != absent) {
+ mconf->absent = absent;
+ mconf->absence_tstamp = now;
+ }
+
+ /* From Absent to Present */
+ if (was_absent && !absent)
+ mt76_worker_schedule(&dev->mt76.tx_worker);
+}
+
+void mt7925_p2p_mcu_bss_is_absence_event(struct mt792x_dev *dev, struct sk_buff *skb)
+{
+ struct mt7925_uni_bss_is_absence_event *event;
+ struct mt7925_bss_absence_data data;
+ u32 tlv_len;
+ u8 *tlv;
+
+ skb_pull(skb, sizeof(struct mt7925_mcu_rxd));
+ if (skb->len < sizeof(*event))
+ return;
+
+ event = (struct mt7925_uni_bss_is_absence_event *)skb->data;
+ tlv = event->tlv;
+ tlv_len = skb->len - sizeof(*event);
+
+ while (tlv_len >= sizeof(struct tlv)) {
+ struct tlv *hdr = (struct tlv *)tlv;
+ u16 tag = le16_to_cpu(hdr->tag);
+ u16 len = le16_to_cpu(hdr->len);
+
+ if (len < sizeof(*hdr) || len > tlv_len)
+ break;
+
+ if (tag == MT7925_BSS_IS_ABSENCE_TAG_INFO &&
+ len >= sizeof(struct mt7925_bss_absence_info_tlv)) {
+ struct mt7925_bss_absence_info_tlv *info =
+ (struct mt7925_bss_absence_info_tlv *)tlv;
+
+ data.bss_idx = event->bss_idx;
+ data.is_absent = info->is_absent;
+ data.free_quota = info->free_quota;
+
+ ieee80211_iterate_active_interfaces_atomic(dev->mt76.phy.hw,
+ IEEE80211_IFACE_ITER_RESUME_ALL,
+ mt7925_mcu_bss_absence_iter,
+ &data);
+ }
+
+ tlv += len;
+ tlv_len -= len;
+ }
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
index 9afae222845f..e189f9e56e93 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/p2p.h
@@ -53,6 +53,24 @@ struct mt7925_uni_sap_event {
u8 pad[4]; /* fixed field */
u8 tlv[];
} __packed;
+
+/* BSS absence/presence event (FW event UNI_EVENT_ID_BSS_IS_ABSENCE) */
+#define MT7925_BSS_IS_ABSENCE_TAG_INFO 0
+
+struct mt7925_bss_absence_info_tlv {
+ __le16 tag;
+ __le16 len;
+ u8 is_absent;
+ u8 free_quota;
+ u8 pad[2];
+} __packed;
+
+struct mt7925_uni_bss_is_absence_event {
+ u8 bss_idx;
+ u8 pad[3];
+ u8 tlv[];
+} __packed;
+
/* TX/RX path overrides, BMC wcid bookkeeping and the P2P-only MCU event
* handlers.
*/
@@ -69,5 +87,7 @@ bool mt7925_p2p_chanctx_bss_programmed(struct ieee80211_vif *vif,
struct ieee80211_bss_conf *link_conf);
void mt7925_p2p_mcu_sap_event(struct mt792x_dev *dev, struct sk_buff *skb);
+void mt7925_p2p_mcu_bss_is_absence_event(struct mt792x_dev *dev,
+ struct sk_buff *skb);
#endif /* __MT7925_P2P_H */
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 94186e9f77c8..6bfbeceaf026 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -163,6 +163,17 @@ struct mt792x_bss_conf {
struct ewma_rssi rssi;
struct ieee80211_tx_queue_params queue_params[IEEE80211_NUM_ACS];
unsigned int link_id;
+ /* FW BSS absence/presence state, updated by the BSS_IS_ABSENCE
+ * event handler.
+ */
+ bool absent;
+ u8 free_quota;
+ unsigned long absence_tstamp;
+
+ /* Dedicated BMC entry, only allocated for a p2p GO. Group-addressed
+ * frames are transmitted on this wcid instead of the vif wcid, so
+ * they no longer share the wcid
+ */
struct mt792x_link_sta bmc;
bool bmc_valid;
};
@@ -407,6 +418,20 @@ mt792x_link_conf_to_mconf(struct ieee80211_bss_conf *link_conf)
return mt792x_vif_to_link(mvif, link_conf->link_id);
}
+/* TRUE only while the BSS has no channel, so nothing we hand to the FW
+ * for it can go on air. Callable from the TX path: reads a single byte
+ * updated by the BSS_IS_ABSENCE event handler.
+ */
+static inline bool mt792x_vif_is_absent(struct ieee80211_vif *vif)
+{
+ struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+
+ if (ieee80211_vif_is_mld(vif))
+ return false;
+
+ return mvif->bss_conf.absent;
+}
+
static inline struct ieee80211_bss_conf *
mt792x_vif_to_bss_conf(struct ieee80211_vif *vif, unsigned int link_id)
{
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index fa29af8fff49..98fa46e85c1b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -178,6 +178,22 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
u8 link_id;
int qid;
+ /* Drop a GO Probe Response outright when mt792x_vif_is_absent()
+ * says the BSS has no channel right now: by the time the FW comes
+ * back the requester has moved on, so the response is stale and
+ * only costs airtime and a TX-abort in the FW.
+ */
+ if (!(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && skb->len >= 2) {
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+
+ if (vif && ieee80211_vif_type_p2p(vif) == NL80211_IFTYPE_P2P_GO &&
+ ieee80211_is_probe_resp(mgmt->frame_control) &&
+ mt792x_vif_is_absent(vif)) {
+ ieee80211_free_txskb(hw, skb);
+ return;
+ }
+ }
+
if (control->sta) {
struct mt792x_link_sta *mlink;
struct mt792x_sta *sta;
--
2.45.2
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH 9/9] wifi: mt76: mt7925: send the common scan IEs and size the request
2026-11-04 9:23 [PATCH 1/9] wifi: mt76: mt7925: drop P2P mgmt frames received on foreign channel JB Tsai
` (6 preceding siblings ...)
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 ` JB Tsai
7 siblings, 0 replies; 10+ messages in thread
From: JB Tsai @ 2026-11-04 9:23 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Sean.Wang, James-LY.Huang,
jb.tsai, Jacobs Wu, stable
From: Jacobs Wu <jacobs.wu@mediatek.com>
mt7925_mcu_build_scan_ie_tlv() sends the per-band sets of
struct ieee80211_scan_ies but not scan_ies->common_ies. mac80211 puts
only the band capabilities, and the custom IEs that must precede them,
into the per-band sets; every other custom IE of the request, the
vendor specific elements (P2P, WPS, MBO, ...) included, goes to
common_ies. Since commit 9f8f4a51f3c1 ("wifi: mt76: mt7925: fix
incorrect scan probe IE handling for hw_scan") these IEs are missing
from the probe requests.
Send common_ies in a UNI_SCAN_IE TLV without a band: the firmware
appends such a TLV to the probe requests of every band. It keeps up to
MT76_CONNAC_SCAN_IE_LEN bytes of it, which max_scan_ie_len already
guarantees for hw_scan; sched_scan shares the TLV, so give it the same
limit instead of the IEEE80211_MAX_DATA_LEN of the connac devices that
have a sched_scan command of their own. The per-band TLVs keep carrying
the per-band sets; the firmware stores their length in a byte, so a
longer set is left out instead of being truncated.
Size the request for the IEs it carries. hw_scan reserved a fixed
sizeof(struct scan_ie_tlv) + MT76_CONNAC_SCAN_IE_LEN, which the
per-band TLVs of a request can exceed, and sched_scan reserved nothing
beyond the TLV header since struct scan_ie_tlv lost its ies[] array, so
mt76_connac_mcu_add_tlv() put the TLVs past the end of the buffer,
which skb_put() turns into a panic, whenever the kmalloc slack did not
happen to cover them. The builder now checks the tailroom of the skb
instead of a budget of its own.
Fixes: 9f8f4a51f3c1 ("wifi: mt76: mt7925: fix incorrect scan probe IE handling for hw_scan")
Cc: stable@vger.kernel.org
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
.../net/wireless/mediatek/mt76/mt7925/init.c | 5 +
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 107 ++++++++++++------
2 files changed, 79 insertions(+), 33 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
index 8c3049b66a85..05305f4e1db8 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c
@@ -301,6 +301,11 @@ int mt7925_register_device(struct mt792x_dev *dev)
if (ret)
return ret;
+ /* hw_scan and sched_scan share the UNI_SCAN_IE TLV, and the firmware
+ * keeps at most MT76_CONNAC_SCAN_IE_LEN bytes of IEs for either.
+ */
+ hw->wiphy->max_sched_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
+
hw->wiphy->reg_notifier = mt7925_regd_notifier;
dev->mphy.sband_2g.sband.ht_cap.cap |=
IEEE80211_HT_CAP_LDPC_CODING |
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 24de2ff3a15d..22a6338acaf1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -3347,56 +3347,99 @@ int mt7925_mcu_set_dbdc(struct mt76_phy *phy, bool enable)
}
EXPORT_SYMBOL_GPL(mt7925_mcu_set_dbdc);
+/* Room for a UNI_SCAN_IE TLV carrying ies_len bytes of IEs */
+static u32
+mt7925_mcu_scan_ie_tlv_len(struct mt76_dev *mdev, u32 ies_len)
+{
+ u32 len = sizeof(struct scan_ie_tlv) + ies_len;
+
+ if (is_mt7928(mdev))
+ len = ALIGN(len, 4);
+
+ return len;
+}
+
+/* Room for the UNI_SCAN_IE TLVs of a scan request */
+static u32
+mt7925_mcu_scan_ies_len(struct mt76_dev *mdev,
+ struct ieee80211_scan_ies *scan_ies)
+{
+ enum nl80211_band i;
+ u32 len = 0;
+
+ if (scan_ies->common_ie_len)
+ len += mt7925_mcu_scan_ie_tlv_len(mdev, scan_ies->common_ie_len);
+
+ for (i = 0; i <= NL80211_BAND_6GHZ; i++) {
+ if (i == NL80211_BAND_60GHZ || !scan_ies->len[i])
+ continue;
+
+ len += mt7925_mcu_scan_ie_tlv_len(mdev, scan_ies->len[i]);
+ }
+
+ return len;
+}
+
+static void
+mt7925_mcu_add_scan_ie_tlv(struct mt76_dev *mdev, struct sk_buff *skb,
+ u8 band, const u8 *ies, u32 ies_len)
+{
+ u32 alloc_len = mt7925_mcu_scan_ie_tlv_len(mdev, ies_len);
+ struct scan_ie_tlv *ie;
+ struct tlv *tlv;
+
+ if (alloc_len > skb_tailroom(skb))
+ return;
+
+ tlv = mt76_connac_mcu_add_tlv(skb, UNI_SCAN_IE, alloc_len);
+ ie = (struct scan_ie_tlv *)tlv;
+
+ memcpy(ie->ies, ies, ies_len);
+ ie->ies_len = cpu_to_le16(ies_len);
+ ie->band = band;
+}
+
static void
mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev,
struct sk_buff *skb,
struct ieee80211_scan_ies *scan_ies)
{
- u32 max_len = sizeof(struct scan_ie_tlv) + MT76_CONNAC_SCAN_IE_LEN;
- u32 ies_len, alloc_len;
- struct scan_ie_tlv *ie;
enum nl80211_band i;
- struct tlv *tlv;
- const u8 *ies;
+
+ /* The firmware appends a TLV without a band to the probe requests
+ * of every band: that is where the common IEs of the request go.
+ */
+ if (scan_ies->common_ie_len)
+ mt7925_mcu_add_scan_ie_tlv(mdev, skb, 0, scan_ies->common_ies,
+ scan_ies->common_ie_len);
for (i = 0; i <= NL80211_BAND_6GHZ; i++) {
+ u8 band;
+
if (i == NL80211_BAND_60GHZ)
continue;
- ies = scan_ies->ies[i];
- ies_len = scan_ies->len[i];
-
- if (!ies || !ies_len)
+ if (!scan_ies->ies[i] || !scan_ies->len[i])
continue;
- if (is_mt7928(mdev))
- alloc_len = ALIGN(sizeof(*ie) + ies_len, 4);
- else
- alloc_len = sizeof(*ie) + ies_len;
-
- if (alloc_len > max_len)
- return;
-
- tlv = mt76_connac_mcu_add_tlv(skb, UNI_SCAN_IE,
- alloc_len);
- ie = (struct scan_ie_tlv *)tlv;
-
- memcpy(ie->ies, ies, ies_len);
- ie->ies_len = cpu_to_le16(ies_len);
+ /* The firmware keeps the length of a per-band set in a byte */
+ if (scan_ies->len[i] > U8_MAX)
+ continue;
switch (i) {
case NL80211_BAND_2GHZ:
- ie->band = 1;
+ band = 1;
break;
case NL80211_BAND_6GHZ:
- ie->band = 3;
+ band = 3;
break;
default:
- ie->band = 2;
+ band = 2;
break;
}
- max_len -= alloc_len;
+ mt7925_mcu_add_scan_ie_tlv(mdev, skb, band, scan_ies->ies[i],
+ scan_ies->len[i]);
}
}
@@ -3415,7 +3458,6 @@ int mt7925_mcu_hw_scan(struct mt76_phy *phy, struct ieee80211_vif *vif,
struct scan_ssid_tlv *ssid;
struct scan_bssid_tlv *bssid;
struct scan_chan_info_tlv *chan_info;
- struct scan_ie_tlv *ie;
struct scan_misc_tlv *misc;
struct tlv *tlv;
int max_len;
@@ -3425,8 +3467,8 @@ int mt7925_mcu_hw_scan(struct mt76_phy *phy, struct ieee80211_vif *vif,
max_len = sizeof(*hdr) + sizeof(*req) + sizeof(*ssid) +
sizeof(*bssid) * MT7925_RNR_SCAN_MAX_BSSIDS +
- sizeof(*chan_info) + sizeof(*misc) + sizeof(*ie) +
- MT76_CONNAC_SCAN_IE_LEN;
+ sizeof(*chan_info) + sizeof(*misc) +
+ mt7925_mcu_scan_ies_len(mdev, &scan_req->ies);
skb = mt76_mcu_msg_alloc(mdev, NULL, max_len);
if (!skb)
@@ -3546,15 +3588,14 @@ int mt7925_mcu_sched_scan_req(struct mt76_phy *phy,
struct scan_sched_req *req;
struct scan_ssid_tlv *ssid;
struct scan_chan_info_tlv *chan_info;
- struct scan_ie_tlv *ie;
struct scan_sched_ssid_match_sets *match;
struct sk_buff *skb;
struct tlv *tlv;
int i, max_len;
max_len = sizeof(*hdr) + sizeof(*req) + sizeof(*ssid) +
- sizeof(*chan_info) + sizeof(*ie) +
- sizeof(*match);
+ sizeof(*chan_info) + sizeof(*match) +
+ mt7925_mcu_scan_ies_len(mdev, ies);
skb = mt76_mcu_msg_alloc(mdev, NULL, max_len);
if (!skb)
--
2.45.2
^ permalink raw reply related [flat|nested] 10+ messages in thread