Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2)
@ 2026-09-27 21:02 Sean Wang
  2026-09-27 21:02 ` [PATCH 01/23] wifi: mt76: mt7925: make NMI address TLV tail padding explicit Sean Wang
                   ` (22 more replies)
  0 siblings, 23 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Sean Wang

This is part 2 of the MT7925 NAN work. It continues the 15-patch series
that fixed NAN operation and NDP performance:

https://lore.kernel.org/all/20260824195227.12589-1-sean.wang@kernel.org/

This series adds NAN MAC address randomization, low power event control,
and key management for secure NAN and NAN data interfaces. It also makes
unicast management TX respect peer availability and discovery windows,
and fixes shared BSS teardown, cluster event ordering, and device removal
and frame lifetime issues found during NDP setup and teardown.

The 23 patches here cover the mt76 follow-up work. The separate mac80211
NAN receive-filter change is not included in this series.

Chengwei Yu (10):
  wifi: mt76: mt7925: make NMI address TLV tail padding explicit
  wifi: mt76: mt7925: add NMI/NDI MAC address setter helpers
  wifi: mt76: mt7925: implement NAN MAC address randomization
  wifi: mt76: mt7925: wire up NAN MAC randomization lifecycle
  wifi: mt76: mt7925: add NAN low power event control on suspend/resume
  wifi: mt76: mt7925: implement NAN key management MCU command
  wifi: mt76: mt7925: add per-peer RX IGTK/BIGTK and RX GTK WTBLs
  wifi: mt76: mt7925: add NDC-aware TX GTK table for NAN_DATA
  wifi: mt76: mt7925: implement mt7925_nan_set_key for NAN security
  wifi: mt76: mt7925: install NAN BIP keys standalone in sta_key_tlv

Jacobs Wu (13):
  wifi: mt76: mt7925: do not disable RX NAPI twice on unload
  wifi: mt76: mt7925: assign the interface WTBL to the NAN management
    TXQ
  wifi: mt76: mt7925: do not deactivate shared NAN BSS on peer STA
    removal
  wifi: mt76: gate NAN unicast mgmt by peer availability via STA queues
  wifi: mt76: mt7925: hold NAN mgmt TX to the discovery window
  wifi: mt76: mt7925: double the retry budget for NAN unicast management
  wifi: mt76: mt7925: let TX status of DW-held NAN frames outlive the DW
    period
  wifi: mt76: mt7925: defer the NAN joined-cluster event out of
    NAN_START
  wifi: mt76: mt7925: steer NAN handshake frames by committed-bitmap
    state
  wifi: mt76: mt7925: disable only the RX NAPI instances that exist
  wifi: mt76: mt7925: stop queueing resets once the device is being
    removed
  wifi: mt76: mt7925: bound the lifetime of NAN unicast management
    frames
  wifi: mt76: mt7925: always deliver the joined-cluster event through
    the deferred work

 drivers/net/wireless/mediatek/mt76/mt76.h     |   1 +
 .../net/wireless/mediatek/mt76/mt7925/mac.c   |  23 +
 .../net/wireless/mediatek/mt76/mt7925/main.c  | 147 +++-
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   |  30 +-
 .../net/wireless/mediatek/mt76/mt7925/mcu.h   |  11 +
 .../wireless/mediatek/mt76/mt7925/mt7925.h    |   3 +
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 710 +++++++++++++++++-
 .../net/wireless/mediatek/mt76/mt7925/nan.h   | 131 ++++
 .../net/wireless/mediatek/mt76/mt7925/pci.c   |  19 +-
 .../wireless/mediatek/mt76/mt7925/pci_mac.c   |  58 +-
 drivers/net/wireless/mediatek/mt76/mt792x.h   |  98 +++
 .../net/wireless/mediatek/mt76/mt792x_core.c  |  34 +-
 drivers/net/wireless/mediatek/mt76/tx.c       |  25 +-
 13 files changed, 1248 insertions(+), 42 deletions(-)

-- 
2.43.0

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH 01/23] wifi: mt76: mt7925: make NMI address TLV tail padding explicit
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 02/23] wifi: mt76: mt7925: add NMI/NDI MAC address setter helpers Sean Wang
                   ` (21 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

struct mt7925_nan_nmi_addr_tlv is __packed __aligned(4). tag+len+
nmi_addr is 10 bytes, so the aligned attribute already inserts 2
bytes of implicit tail padding to round the struct up to 12 bytes;
sizeof() is unchanged by this patch.

Every sibling NAN TLV struct in this file (cluster_id, sync_rssi,
peer_rec, map_sta_rec, ...) declares its tail padding as an explicit
reserved[] member instead of relying on implicit compiler padding.
Make mt7925_nan_nmi_addr_tlv consistent with that convention, and
give the field a name so it shows up (zeroed) in TLV dumps instead
of appearing as anonymous struct padding.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/nan.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index d81d84a222d9..297c2856578e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -346,6 +346,7 @@ struct mt7925_nan_nmi_addr_tlv {
 	__le16 tag;
 	__le16 len;
 	u8 nmi_addr[ETH_ALEN];
+	u8 reserved[2];
 } __packed __aligned(4);
 
 struct mt7925_nan_avail_ctrl_tlv {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 02/23] wifi: mt76: mt7925: add NMI/NDI MAC address setter helpers
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
  2026-09-27 21:02 ` [PATCH 01/23] wifi: mt76: mt7925: make NMI address TLV tail padding explicit Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-10-06  9:25   ` Felix Fietkau
  2026-09-27 21:02 ` [PATCH 03/23] wifi: mt76: mt7925: implement NAN MAC address randomization Sean Wang
                   ` (20 subsequent siblings)
  22 siblings, 1 reply; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

Add mt7925_nan_set_nmi_address() and mt7925_nan_set_ndi_address() for
runtime MAC address changes: NMI goes through the MCU (firmware owns
the hardware address), NDI updates vif->addr directly.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 48 +++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/nan.h   |  8 ++++
 2 files changed, 56 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index 8a59f7b1aee2..abbd3f7b081c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -697,6 +697,54 @@ void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb)
 	}
 }
 
+int mt7925_nan_set_nmi_address(struct ieee80211_vif *vif,
+			       struct mt792x_dev *dev,
+			       const u8 *mac_address)
+{
+	struct mt76_dev *mdev = &dev->mt76;
+	struct {
+		u8 rsv[4];
+		struct mt7925_nan_nmi_addr_tlv nmi_addr_tlv;
+	} nmi_cmd = {
+		.nmi_addr_tlv = {
+			.tag = cpu_to_le16(NAN_UNI_CMD_CHANGE_NMI_ADDRESS),
+			.len = cpu_to_le16(sizeof(struct mt7925_nan_nmi_addr_tlv)),
+		},
+	};
+
+	if (!dev || !vif || !mac_address)
+		return -EINVAL;
+
+	if (is_zero_ether_addr(mac_address) ||
+	    is_multicast_ether_addr(mac_address))
+		return -EINVAL;
+
+	memcpy(nmi_cmd.nmi_addr_tlv.nmi_addr, mac_address, ETH_ALEN);
+
+	return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nmi_cmd,
+				 sizeof(nmi_cmd), true);
+}
+
+int mt7925_nan_set_ndi_address(struct ieee80211_vif *vif,
+			       struct mt792x_dev *dev,
+			       const u8 *mac_address)
+{
+	if (!dev || !vif || !mac_address)
+		return -EINVAL;
+
+	if (is_zero_ether_addr(mac_address) ||
+	    is_multicast_ether_addr(mac_address))
+		return -EINVAL;
+
+	/*
+	 * The NDI address is not managed by firmware; it is the vif's own
+	 * hardware address, so updating it locally is sufficient.
+	 */
+	memcpy(vif->addr, mac_address, ETH_ALEN);
+
+	return 0;
+}
+
 static int mt7925_nan_avail_ctrl_tlv(struct sk_buff *skb,
 				     struct ieee80211_vif *vif)
 {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 297c2856578e..9fd349e1cdab 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -495,6 +495,14 @@ int mt7925_nan_change_configure(struct ieee80211_vif *vif,
 
 void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb);
 
+int mt7925_nan_set_nmi_address(struct ieee80211_vif *vif,
+			       struct mt792x_dev *dev,
+			       const u8 *mac_address);
+
+int mt7925_nan_set_ndi_address(struct ieee80211_vif *vif,
+			       struct mt792x_dev *dev,
+			       const u8 *mac_address);
+
 void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
 				    struct ieee80211_vif *vif);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 03/23] wifi: mt76: mt7925: implement NAN MAC address randomization
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
  2026-09-27 21:02 ` [PATCH 01/23] wifi: mt76: mt7925: make NMI address TLV tail padding explicit Sean Wang
  2026-09-27 21:02 ` [PATCH 02/23] wifi: mt76: mt7925: add NMI/NDI MAC address setter helpers Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-10-06  9:18   ` Felix Fietkau
  2026-09-27 21:02 ` [PATCH 04/23] wifi: mt76: mt7925: wire up NAN MAC randomization lifecycle Sean Wang
                   ` (19 subsequent siblings)
  22 siblings, 1 reply; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

Add mt7925_nan_start/stop_mac_randomization() using a per-vif
delayed_work and period stored in struct mt792x_nan.

Per-vif (not per-dev) because the NAN iface combinations allow up
to two concurrent NAN_DATA vifs; a single dev-wide field would
silently lose randomization for the second NDI or let it overwrite
the first vif's pending work.

delayed_work (not timer_list) because the work function calls
mt7925_nan_set_nmi/ndi_address() which invokes mt76_mcu_send_msg()
with wait_resp=true; that can sleep, which is not allowed in softirq
context. This is consistent with scan_work, mlo_pm_work and ps_work
in this driver.

stop_mac_randomization() must be called without holding the dev
mutex since the work function acquires it. No caller is wired up
yet; the next patch does that in add/remove_interface.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 59 +++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/nan.h   |  7 +++
 drivers/net/wireless/mediatek/mt76/mt792x.h   |  9 +++
 3 files changed, 75 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index abbd3f7b081c..0b94bd4005d4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -745,6 +745,65 @@ int mt7925_nan_set_ndi_address(struct ieee80211_vif *vif,
 	return 0;
 }
 
+void mt7925_nan_mac_rand_work(struct work_struct *work)
+{
+	struct mt792x_vif *mvif = container_of(work, struct mt792x_vif,
+					       nan.mac_rand_work.work);
+	struct mt792x_dev *dev = mvif->phy->dev;
+	struct ieee80211_vif *vif =
+		container_of((void *)mvif, struct ieee80211_vif, drv_priv);
+	bool is_nmi = (vif->type == NL80211_IFTYPE_NAN);
+	u8 addr[ETH_ALEN];
+	int ret;
+
+	mt792x_mutex_acquire(dev);
+
+	if (!mvif->nan.mac_rand_period_sec)
+		goto out;
+
+	eth_random_addr(addr);
+
+	ret = is_nmi ? mt7925_nan_set_nmi_address(vif, dev, addr)
+		     : mt7925_nan_set_ndi_address(vif, dev, addr);
+
+	if (ret)
+		dev_err(dev->mt76.dev,
+			"NAN: failed to randomize %s address: %d\n",
+			is_nmi ? "NMI" : "NDI", ret);
+
+	ieee80211_queue_delayed_work(mt76_hw(dev), &mvif->nan.mac_rand_work,
+				     secs_to_jiffies(mvif->nan.mac_rand_period_sec));
+out:
+	mt792x_mutex_release(dev);
+}
+
+void mt7925_nan_start_mac_randomization(struct ieee80211_vif *vif,
+					u32 period_sec)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt792x_dev *dev = mvif->phy->dev;
+
+	if (!period_sec)
+		return;
+
+	mvif->nan.mac_rand_period_sec = period_sec;
+
+	ieee80211_queue_delayed_work(mt76_hw(dev), &mvif->nan.mac_rand_work, 0);
+}
+
+/*
+ * Must be called without holding the dev mutex: mt7925_nan_mac_rand_work()
+ * acquires it, so cancel_delayed_work_sync() here would deadlock against a
+ * concurrently running instance of that work otherwise.
+ */
+void mt7925_nan_stop_mac_randomization(struct ieee80211_vif *vif)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+
+	mvif->nan.mac_rand_period_sec = 0;
+	cancel_delayed_work_sync(&mvif->nan.mac_rand_work);
+}
+
 static int mt7925_nan_avail_ctrl_tlv(struct sk_buff *skb,
 				     struct ieee80211_vif *vif)
 {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 9fd349e1cdab..191134825c3b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -503,6 +503,13 @@ int mt7925_nan_set_ndi_address(struct ieee80211_vif *vif,
 			       struct mt792x_dev *dev,
 			       const u8 *mac_address);
 
+void mt7925_nan_mac_rand_work(struct work_struct *work);
+
+void mt7925_nan_start_mac_randomization(struct ieee80211_vif *vif,
+					u32 period_sec);
+
+void mt7925_nan_stop_mac_randomization(struct ieee80211_vif *vif);
+
 void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
 				    struct ieee80211_vif *vif);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 9722d840c1af..64cca8852359 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -181,6 +181,15 @@ struct mt792x_nan {
 
 	/* Connection index bitmap, up to NAN_MAX_CONN_CFG peers */
 	unsigned long conn_bitmap;
+
+	/*
+	 * NMI/NDI MAC address randomization. Kept per-vif (rather than a
+	 * single pointer on mt792x_dev) because up to two NAN_DATA vifs can
+	 * coexist (see if_limits_nan_mcc/scc), each needing its own
+	 * independent randomization period and pending work.
+	 */
+	struct delayed_work mac_rand_work;
+	u32 mac_rand_period_sec;
 };
 
 struct mt792x_vif {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 04/23] wifi: mt76: mt7925: wire up NAN MAC randomization lifecycle
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (2 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 03/23] wifi: mt76: mt7925: implement NAN MAC address randomization Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 05/23] wifi: mt76: mt7925: add NAN low power event control on suspend/resume Sean Wang
                   ` (18 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

Start mac_rand_work in mt7925_add_interface() and stop it in
mt7925_remove_interface() so the work item added by prior patches
is usable and cannot outlive its vif.

INIT_DELAYED_WORK() is unconditional across all vif types, matching
how csa_work is already initialized unconditionally -- mvif->nan
exists on every mt792x_vif so there is nothing NAN-specific to guard.

mt7925_nan_stop_mac_randomization() is called before
mt792x_mutex_acquire(), not inside it: the work function takes the
same dev mutex, so cancel_delayed_work_sync() while holding it would
deadlock. This follows the same ordering mt792x_unassign_vif_chanctx()
uses for csa_work.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/main.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index c882952f5df1..3e1c223e8da6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -480,6 +480,12 @@ mt7925_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 
 	if (vif->type == NL80211_IFTYPE_NAN)
 		dev->nan_vif = vif;
+
+	if (vif->type == NL80211_IFTYPE_NAN ||
+	    vif->type == NL80211_IFTYPE_NAN_DATA)
+		INIT_DELAYED_WORK(&mvif->nan.mac_rand_work,
+				  mt7925_nan_mac_rand_work);
+
 out:
 	mt792x_mutex_release(dev);
 
@@ -493,6 +499,15 @@ mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 	struct mt792x_dev *dev = mt792x_hw_dev(hw);
 	struct mt792x_bss_conf *mconf;
 
+	/*
+	 * Stop before taking the dev mutex: mt7925_nan_mac_rand_work()
+	 * acquires it too, so cancel_delayed_work_sync() would deadlock
+	 * against a concurrently running instance if called while held.
+	 */
+	if (vif->type == NL80211_IFTYPE_NAN ||
+	    vif->type == NL80211_IFTYPE_NAN_DATA)
+		mt7925_nan_stop_mac_randomization(vif);
+
 	mt792x_mutex_acquire(dev);
 
 	if (dev->nan_vif == vif)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 05/23] wifi: mt76: mt7925: add NAN low power event control on suspend/resume
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (3 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 04/23] wifi: mt76: mt7925: wire up NAN MAC randomization lifecycle Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 06/23] wifi: mt76: mt7925: implement NAN key management MCU command Sean Wang
                   ` (17 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

Disable NAN low power event reporting when the interface suspends so
the firmware stops firing peer-schedule notifications into a quiesced
system, and re-enable it on resume.

Re-enabling is conditional: it only fires if firmware has previously
reported an active peer schedule (non-zero peer_sch_record_tx_map in
the LOWPOWER_CTRL event). This avoids sending the command on every
resume when no peer connection is in progress.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   |  9 ++
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 93 +++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/nan.h   | 18 ++++
 drivers/net/wireless/mediatek/mt76/mt792x.h   |  7 ++
 4 files changed, 127 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 2afd3f5e3266..76dcbbffabfc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -296,6 +296,7 @@ void mt7925_mcu_set_suspend_iter(void *priv, u8 *mac,
 	bool suspend = !test_bit(MT76_STATE_RUNNING, &phy->state);
 	struct ieee80211_hw *hw = phy->hw;
 	struct cfg80211_wowlan *wowlan = hw->wiphy->wowlan_config;
+	struct mt792x_dev *dev = mt792x_hw_dev(hw);
 	int i;
 
 	mt76_connac_mcu_set_gtk_rekey(phy->dev, vif, suspend);
@@ -306,6 +307,14 @@ void mt7925_mcu_set_suspend_iter(void *priv, u8 *mac,
 		mt7925_mcu_set_wow_pattern(phy->dev, vif, i, suspend,
 					   &wowlan->patterns[i]);
 	mt7925_connac_mcu_set_wow_ctrl(phy, vif, suspend, wowlan);
+
+	/*
+	 * Disable NAN low power event reporting while suspended, and
+	 * re-enable it on resume so firmware resumes reporting peer
+	 * schedule activity.
+	 */
+	if (vif->type == NL80211_IFTYPE_NAN)
+		mt7925_nan_send_lowpower_ctrl_cmd(dev, !suspend);
 }
 
 #endif /* CONFIG_PM */
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index 0b94bd4005d4..73e45be8b40e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -654,6 +654,44 @@ mt7925_nan_handle_sched_update_done(struct mt792x_dev *dev, struct tlv *tlv)
 	ieee80211_queue_work(dev->mt76.hw, &dev->nan_deferred_work);
 }
 
+static void
+mt7925_nan_handle_lowpower_ctrl(struct mt792x_dev *dev, struct tlv *tlv)
+{
+	struct mt7925_nan_lowpower_ctrl_evt *evt;
+	struct mt792x_vif *mvif;
+	u16 len;
+
+	if (!dev || !tlv)
+		return;
+
+	len = le16_to_cpu(tlv->len);
+	if (len < sizeof(*tlv) + sizeof(*evt)) {
+		dev_warn(dev->mt76.dev,
+			 "nan: short lowpower_ctrl event tlv len=%u\n", len);
+		return;
+	}
+
+	if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif))
+		return;
+
+	evt = (struct mt7925_nan_lowpower_ctrl_evt *)tlv->data;
+
+	dev_dbg(dev->mt76.dev,
+		"nan: lowpower_ctrl event - peer_sch_record_tx_map=0x%x\n",
+		evt->peer_sch_record_tx_map);
+
+	if (!evt->peer_sch_record_tx_map)
+		return;
+
+	/*
+	 * Firmware reports an active peer schedule; remember it so the
+	 * next resume re-enables low power event reporting instead of
+	 * sending the command unconditionally every suspend/resume cycle.
+	 */
+	mvif = (struct mt792x_vif *)dev->nan_vif->drv_priv;
+	mvif->nan.lowpower_mode = true;
+}
+
 void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb)
 {
 	struct tlv *tlv;
@@ -688,6 +726,9 @@ void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb)
 		case NAN_UNI_EVENT_ID_SCHED_UPDATE_DONE:
 			mt7925_nan_handle_sched_update_done(dev, tlv);
 			break;
+		case NAN_UNI_EVENT_LOWPOWER_CTRL:
+			mt7925_nan_handle_lowpower_ctrl(dev, tlv);
+			break;
 		default:
 			break;
 		}
@@ -745,6 +786,58 @@ int mt7925_nan_set_ndi_address(struct ieee80211_vif *vif,
 	return 0;
 }
 
+int mt7925_nan_send_lowpower_ctrl_cmd(struct mt792x_dev *dev, bool enable)
+{
+	struct mt7925_nan_lowpower_ctrl_tlv *lp_tlv;
+	struct mt7925_nan_common_hdr *hdr;
+	struct mt792x_vif *mvif;
+	struct mt76_dev *mdev;
+	struct sk_buff *skb;
+	struct tlv *tlv;
+
+	if (!dev)
+		return -EINVAL;
+
+	/*
+	 * Only re-enabling (on resume) is gated on firmware having reported
+	 * an active peer schedule while suspended; disabling (on suspend)
+	 * always goes through so firmware stops reporting immediately.
+	 */
+	if (enable) {
+		if (!dev->nan_vif)
+			return 0;
+
+		mvif = (struct mt792x_vif *)dev->nan_vif->drv_priv;
+		if (!mvif->nan.lowpower_mode)
+			return 0;
+
+		mvif->nan.lowpower_mode = false;
+	}
+
+	mdev = &dev->mt76;
+
+	skb = mt76_mcu_msg_alloc(mdev, NULL,
+				 sizeof(struct mt7925_nan_common_hdr) +
+				 sizeof(struct mt7925_nan_lowpower_ctrl_tlv));
+	if (!skb)
+		return -ENOMEM;
+
+	hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
+	memset(hdr, 0, sizeof(*hdr));
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_LOWPOWER_CTRL,
+				      sizeof(struct mt7925_nan_lowpower_ctrl_tlv));
+	if (!tlv) {
+		dev_kfree_skb(skb);
+		return -ENOMEM;
+	}
+
+	lp_tlv = (struct mt7925_nan_lowpower_ctrl_tlv *)tlv;
+	lp_tlv->enabled = enable ? 1 : 0;
+
+	return mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+}
+
 void mt7925_nan_mac_rand_work(struct work_struct *work)
 {
 	struct mt792x_vif *mvif = container_of(work, struct mt792x_vif,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 191134825c3b..3a14ec384e4f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -88,6 +88,7 @@ enum nan_uni_cmd_tag {
 	NAN_UNI_CMD_UPDATE_PEER_CAPABILITY	= 21,
 	NAN_UNI_CMD_CHANGE_NMI_ADDRESS		= 24,
 	NAN_UNI_CMD_SET_DW_INTERVAL		= 26,
+	NAN_UNI_CMD_LOWPOWER_CTRL		= 37,
 	NAN_UNI_CMD_SET_SYNC_RSSI		= 39,
 	NAN_UNI_CMD_SET_CLUSTER_ID		= 40,
 	NAN_UNI_CMD_KEY_MANAGEMENT		= 53,
@@ -95,6 +96,7 @@ enum nan_uni_cmd_tag {
 
 enum nan_uni_event_tag {
 	NAN_UNI_EVENT_ID_DE_EVENT_IND		= 19,
+	NAN_UNI_EVENT_LOWPOWER_CTRL		= 37,
 	NAN_UNI_EVENT_ID_ULW_UPDATE		= 39,
 	NAN_UNI_EVENT_ID_SCHED_UPDATE_DONE	= 43,
 	NAN_UNI_EVENT_REPORT_DW_START		= 59,
@@ -476,6 +478,20 @@ struct mt7925_nan_update_phy_setting_tlv {
 	struct mt7925_nan_phy_setting phy_5g;
 } __packed __aligned(4);
 
+/* NAN Low Power Control Command */
+struct mt7925_nan_lowpower_ctrl_tlv {
+	__le16 tag;
+	__le16 len;
+	u8 enabled;
+	u8 reserved[3];
+} __packed __aligned(4);
+
+/* NAN Low Power Control Event */
+struct mt7925_nan_lowpower_ctrl_evt {
+	u8 peer_sch_record_tx_map;
+	u8 reserved[7];
+} __packed __aligned(4);
+
 int mt7925_nan_update_phy_setting(struct mt792x_dev *dev);
 
 struct ieee80211_chanctx_conf *
@@ -510,6 +526,8 @@ void mt7925_nan_start_mac_randomization(struct ieee80211_vif *vif,
 
 void mt7925_nan_stop_mac_randomization(struct ieee80211_vif *vif);
 
+int mt7925_nan_send_lowpower_ctrl_cmd(struct mt792x_dev *dev, bool enable);
+
 void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
 				    struct ieee80211_vif *vif);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 64cca8852359..fa28a77d12bb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -182,6 +182,13 @@ struct mt792x_nan {
 	/* Connection index bitmap, up to NAN_MAX_CONN_CFG peers */
 	unsigned long conn_bitmap;
 
+	/*
+	 * Set when firmware reports an active peer schedule via
+	 * NAN_UNI_EVENT_LOWPOWER_CTRL; cleared once the re-enable command
+	 * has been sent on resume.
+	 */
+	bool lowpower_mode;
+
 	/*
 	 * NMI/NDI MAC address randomization. Kept per-vif (rather than a
 	 * single pointer on mt792x_dev) because up to two NAN_DATA vifs can
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 06/23] wifi: mt76: mt7925: implement NAN key management MCU command
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (4 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 05/23] wifi: mt76: mt7925: add NAN low power event control on suspend/resume Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 07/23] wifi: mt76: mt7925: add per-peer RX IGTK/BIGTK and RX GTK WTBLs Sean Wang
                   ` (16 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

Add mt7925_nan_manage_key_cmd() and mt7925_nan_key_mgmt_tlv() to
install and remove NAN group and pairwise keys via
NAN_UNI_CMD_KEY_MANAGEMENT. The TLV builder covers all firmware key
types: NM-TK, ND-TK (unicast), TX/RX GTK (multicast data) and
TX/RX IGTK/BIGTK (multicast management).

Follows the same skb + mt76_connac_mcu_add_tlv() pattern as other NAN
commands (mt7925_nan_enable(), mt7925_nan_change_configure()) rather
than hand-assembling a packed struct, so it composes naturally with
the existing NAN command helpers.

Key material is grouped into struct mt7925_nan_key_info so callers
pass one pointer instead of five arguments; init/key_exist firmware
flags are hardcoded 1/0 since every real caller uses the same values.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 122 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/nan.h   |  72 +++++++++++
 2 files changed, 194 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index 73e45be8b40e..0579501207eb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -897,6 +897,128 @@ void mt7925_nan_stop_mac_randomization(struct ieee80211_vif *vif)
 	cancel_delayed_work_sync(&mvif->nan.mac_rand_work);
 }
 
+static int
+mt7925_nan_key_mgmt_tlv(struct sk_buff *skb,
+			enum mt7925_nan_key_operation key_op,
+			enum mt7925_nan_key_type key_type,
+			u16 wtbl_entry,
+			const u8 *local_addr,
+			const u8 *peer_addr,
+			const struct mt7925_nan_key_info *key)
+{
+	struct mt7925_nan_key_mgmt_tlv *key_mgmt_tlv = NULL;
+	struct tlv *tlv = NULL;
+
+	if (!skb)
+		return -EINVAL;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, NAN_UNI_CMD_KEY_MANAGEMENT,
+				      sizeof(struct mt7925_nan_key_mgmt_tlv));
+	if (!tlv)
+		return -ENOMEM;
+
+	key_mgmt_tlv = (struct mt7925_nan_key_mgmt_tlv *)tlv;
+	key_mgmt_tlv->op = (u8)key_op;
+	key_mgmt_tlv->key_type = (u8)key_type;
+	key_mgmt_tlv->wtbl_idx = cpu_to_le16(wtbl_entry);
+	key_mgmt_tlv->init = 1;
+	key_mgmt_tlv->key_exist = 0;
+	key_mgmt_tlv->is_nmi_tk = 0;
+
+	memcpy(key_mgmt_tlv->local_addr, local_addr, ETH_ALEN);
+	memcpy(key_mgmt_tlv->peer_addr, peer_addr, ETH_ALEN);
+
+	switch (key_type) {
+	case NAN_KEY_TYPE_NMI_CXT_MGMT_KEY:
+		key_mgmt_tlv->nmi_key_idx = 0;
+		key_mgmt_tlv->is_nmi_tk = true;
+		break;
+	case NAN_KEY_TYPE_MC_TX_KEY:
+	case NAN_KEY_TYPE_MC_MGMT_TX_KEY:
+		/* TX GTK/IGTK/BIGTK: NDI-indexed slot; NDC ID not yet tracked */
+		key_mgmt_tlv->ndi_idx = 0;
+		break;
+	case NAN_KEY_TYPE_MC_RX_KEY:
+	case NAN_KEY_TYPE_MC_MGMT_RX_KEY:
+		key_mgmt_tlv->mc_rx_idx = (u8)wtbl_entry;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (key_op == NAN_KEY_OP_SET_KEY) {
+		key_mgmt_tlv->algorithm_id = key->algo_id;
+		key_mgmt_tlv->key_id = key->key_id;
+		key_mgmt_tlv->key_len = key->key_len;
+
+		if (key_type == NAN_KEY_TYPE_MC_MGMT_RX_KEY && key->pn) {
+			memcpy(key_mgmt_tlv->key_rsc, key->pn,
+			       min_t(size_t, NAN_PACKET_NUMBER_LEN,
+				     sizeof(key_mgmt_tlv->key_rsc)));
+		}
+
+		if (key->key_data) {
+			memcpy(key_mgmt_tlv->key_material, key->key_data,
+			       min_t(size_t, key->key_len,
+				     sizeof(key_mgmt_tlv->key_material)));
+		}
+	}
+
+	return 0;
+}
+
+/**
+ * mt7925_nan_manage_key_cmd - Install/remove a NAN group or pairwise key
+ * @dev: mt792x device pointer
+ * @key_op: NAN_KEY_OP_SET_KEY or NAN_KEY_OP_CLS_KEY
+ * @key_type: which NAN key slot this targets (enum mt7925_nan_key_type)
+ * @wtbl_entry: WTBL index the key is bound to (WTBL_RESERVED_ENTRY invalid)
+ * @local_addr: local MAC address for this key context
+ * @peer_addr: peer MAC address for this key context
+ * @key: key material; key->key_data required for NAN_KEY_OP_SET_KEY
+ *
+ * There are no other callers yet; NAN group key installation added by a
+ * later change calls into this single command path.
+ */
+int
+mt7925_nan_manage_key_cmd(struct mt792x_dev *dev,
+			  enum mt7925_nan_key_operation key_op,
+			  enum mt7925_nan_key_type key_type,
+			  u16 wtbl_entry,
+			  const u8 *local_addr,
+			  const u8 *peer_addr,
+			  const struct mt7925_nan_key_info *key)
+{
+	struct mt76_dev *mdev = &dev->mt76;
+	struct mt7925_nan_common_hdr *hdr = NULL;
+	struct sk_buff *skb = NULL;
+	int ret;
+
+	if (!key || !local_addr || !peer_addr ||
+	    key_type >= NAN_KEY_TYPE_NUM ||
+	    wtbl_entry == WTBL_RESERVED_ENTRY ||
+	    (key_op == NAN_KEY_OP_SET_KEY && !key->key_data))
+		return -EINVAL;
+
+	skb = mt76_mcu_msg_alloc(mdev, NULL,
+				 sizeof(struct mt7925_nan_common_hdr) +
+				 sizeof(struct mt7925_nan_key_mgmt_tlv));
+	if (!skb)
+		return -ENOMEM;
+
+	hdr = (struct mt7925_nan_common_hdr *)skb_put(skb, sizeof(*hdr));
+	memset(hdr, 0, sizeof(*hdr));
+
+	ret = mt7925_nan_key_mgmt_tlv(skb, key_op, key_type, wtbl_entry,
+				      local_addr, peer_addr, key);
+	if (ret) {
+		dev_kfree_skb(skb);
+		return ret;
+	}
+
+	return mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+}
+
 static int mt7925_nan_avail_ctrl_tlv(struct sk_buff *skb,
 				     struct ieee80211_vif *vif)
 {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 3a14ec384e4f..f15a16b773bb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -109,6 +109,24 @@ enum nan_disc_event_type {
 	NAN_EVENT_ID_JOINED_CLUSTER		= 2,
 };
 
+enum mt7925_nan_key_operation {
+	NAN_KEY_OP_SET_KEY,
+	NAN_KEY_OP_CLS_KEY,
+};
+
+enum mt7925_nan_key_type {
+	NAN_KEY_TYPE_NMI_CXT_MGMT_KEY,	/* NM-TK: NAN Management pairwise key */
+	NAN_KEY_TYPE_ND_TK,		/* ND-TK: NAN Data pairwise key */
+	NAN_KEY_TYPE_MC_TX_KEY,		/* TX GTK (multicast data) */
+	NAN_KEY_TYPE_MC_RX_KEY,		/* RX GTK (multicast data) */
+	NAN_KEY_TYPE_MC_MGMT_TX_KEY,	/* TX IGTK/BIGTK (multicast mgmt) */
+	NAN_KEY_TYPE_MC_MGMT_RX_KEY,	/* RX IGTK/BIGTK (multicast mgmt) */
+	NAN_KEY_TYPE_NUM,
+};
+
+#define WTBL_RESERVED_ENTRY	0xFFFF
+#define NAN_PACKET_NUMBER_LEN	6
+
 /* bit indices into mt792x_dev->nan_deferred_pending, set from the atomic
  * MCU-event RX path and consumed by mt7925_nan_deferred_work()
  */
@@ -478,6 +496,35 @@ struct mt7925_nan_update_phy_setting_tlv {
 	struct mt7925_nan_phy_setting phy_5g;
 } __packed __aligned(4);
 
+struct mt7925_nan_key_mgmt_tlv {
+	__le16 tag;
+	__le16 len;
+
+	u8 op;			/* enum mt7925_nan_key_operation */
+	u8 key_type;		/* enum mt7925_nan_key_type */
+	__le16 wtbl_idx;
+	u8 init;
+	u8 key_exist;
+	u8 is_nmi_tk;
+	u8 reserved0;
+
+	u8 local_addr[ETH_ALEN];
+	u8 peer_addr[ETH_ALEN];
+
+	union {
+		u8 nmi_key_idx;
+		u8 ndi_idx;
+		u8 mc_rx_idx;
+	};
+	u8 algorithm_id;
+	u8 key_id;
+	u8 key_len;
+
+	u8 key_rsc[NAN_PACKET_NUMBER_LEN];
+	u8 reserved1[2];
+	u8 key_material[32];
+} __packed __aligned(4);
+
 /* NAN Low Power Control Command */
 struct mt7925_nan_lowpower_ctrl_tlv {
 	__le16 tag;
@@ -528,6 +575,31 @@ void mt7925_nan_stop_mac_randomization(struct ieee80211_vif *vif);
 
 int mt7925_nan_send_lowpower_ctrl_cmd(struct mt792x_dev *dev, bool enable);
 
+/**
+ * struct mt7925_nan_key_info - key material for mt7925_nan_manage_key_cmd
+ * @algo_id:  firmware cipher algorithm ID (from mt7925_mcu_get_cipher())
+ * @key_id:   key index (1-2 for GTK, 4-7 for IGTK/BIGTK)
+ * @key_len:  key material length in bytes (16 or 32)
+ * @key_data: key bytes; must be non-NULL for SET_KEY operations
+ * @pn:       6-byte packet number / RSC; only used for MC_MGMT_RX_KEY,
+ *            pass NULL for all other key types
+ */
+struct mt7925_nan_key_info {
+	u8 algo_id;
+	u8 key_id;
+	u8 key_len;
+	const u8 *key_data;
+	const u8 *pn;
+};
+
+int mt7925_nan_manage_key_cmd(struct mt792x_dev *dev,
+			      enum mt7925_nan_key_operation key_op,
+			      enum mt7925_nan_key_type key_type,
+			      u16 wtbl_entry,
+			      const u8 *local_addr,
+			      const u8 *peer_addr,
+			      const struct mt7925_nan_key_info *key);
+
 void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
 				    struct ieee80211_vif *vif);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 07/23] wifi: mt76: mt7925: add per-peer RX IGTK/BIGTK and RX GTK WTBLs
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (5 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 06/23] wifi: mt76: mt7925: implement NAN key management MCU command Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 08/23] wifi: mt76: mt7925: add NDC-aware TX GTK table for NAN_DATA Sean Wang
                   ` (15 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

NAN RX group keys are per-peer: each NAN peer can carry an independent
IGTK/BIGTK, and each NAN_DATA peer an independent GTK. Add
nan_rx_igtk_wcid and nan_rx_gtk_wcid to mt792x_sta to hold dedicated
WTBL entries for these keys.

WTBLs are allocated lazily: mt7925_nan_sta_wcids_init() marks both
fields MT792x_WCID_IDX_UNSET at peer-add time, and
mt7925_nan_peer_wcids_free() releases whichever slots were actually
allocated at peer-remove time. Peers that never install a NAN group
key carry zero overhead.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/main.c  | 47 +++++++++++++++++++
 drivers/net/wireless/mediatek/mt76/mt792x.h   | 11 +++++
 2 files changed, 58 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 3e1c223e8da6..2d3bf7d806a7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -923,6 +923,8 @@ mt7925_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
 	return mvif->basic_rates_idx;
 }
 
+static void mt7925_nan_sta_wcids_init(struct mt792x_sta *msta);
+
 static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
 				   struct ieee80211_vif *vif,
 				   struct ieee80211_link_sta *link_sta,
@@ -1060,6 +1062,9 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
 			goto out_pm;
 	}
 
+	if (vif->type == NL80211_IFTYPE_NAN || vif->type == NL80211_IFTYPE_NAN_DATA)
+		mt7925_nan_sta_wcids_init(msta);
+
 	mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
 
 	return 0;
@@ -1306,6 +1311,45 @@ int mt7925_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif,
 }
 EXPORT_SYMBOL_GPL(mt7925_mac_sta_event);
 
+/* Mark per-peer NAN group key WTBLs as unallocated at station add time.
+ * Pairs with mt7925_nan_peer_wcids_free() at station remove time.
+ */
+static void mt7925_nan_sta_wcids_init(struct mt792x_sta *msta)
+{
+	msta->nan_rx_gtk_wcid.idx = MT792x_WCID_IDX_UNSET;
+	msta->nan_rx_igtk_wcid.idx = MT792x_WCID_IDX_UNSET;
+}
+
+/* Free a lazily-allocated per-peer NAN group key WTBL and reset its sentinel. */
+static void mt7925_nan_wcid_free(struct mt76_dev *mdev, struct mt76_wcid *wcid)
+{
+	u16 idx = wcid->idx;
+
+	rcu_assign_pointer(mdev->wcid[idx], NULL);
+	mt76_wcid_cleanup(mdev, wcid);
+	mt76_wcid_mask_clear(mdev->wcid_mask, idx);
+	wcid->idx = MT792x_WCID_IDX_UNSET;
+}
+
+/* Release any lazily-allocated per-peer NAN group key WTBLs for a departing
+ * station.  Called from mt7925_mac_link_sta_remove() after the primary WTBL
+ * is already freed, so the NAN-specific cleanup is clearly separated from the
+ * generic STA teardown path.
+ */
+static void mt7925_nan_peer_wcids_free(struct mt76_dev *mdev,
+				       struct ieee80211_vif *vif,
+				       struct ieee80211_sta *sta)
+{
+	struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv;
+
+	if (vif->type == NL80211_IFTYPE_NAN_DATA &&
+	    msta->nan_rx_gtk_wcid.idx != MT792x_WCID_IDX_UNSET)
+		mt7925_nan_wcid_free(mdev, &msta->nan_rx_gtk_wcid);
+
+	if (msta->nan_rx_igtk_wcid.idx != MT792x_WCID_IDX_UNSET)
+		mt7925_nan_wcid_free(mdev, &msta->nan_rx_igtk_wcid);
+}
+
 static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
 				       struct ieee80211_vif *vif,
 				       struct ieee80211_link_sta *link_sta,
@@ -1350,6 +1394,9 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev,
 	mt76_wcid_cleanup(mdev, wcid);
 	mt76_wcid_mask_clear(mdev->wcid_mask, idx);
 
+	if (vif->type == NL80211_IFTYPE_NAN || vif->type == NL80211_IFTYPE_NAN_DATA)
+		mt7925_nan_peer_wcids_free(mdev, vif, link_sta->sta);
+
 	mt76_connac_power_save_sched(&dev->mphy, &dev->pm);
 }
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index fa28a77d12bb..707ca24193f9 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -18,6 +18,11 @@
 #define MT792x_WTBL_SIZE	20
 #define MT792x_WTBL_RESERVED	(MT792x_WTBL_SIZE - 1)
 #define MT792x_WTBL_STA		(MT792x_WTBL_RESERVED - MT792x_MAX_INTERFACES)
+/* Sentinel for a lazily-allocated wcid that has not yet been assigned a slot.
+ * U16_MAX is outside every valid alloc range and matches the 0xffff value
+ * used in mt76 DMA ring entries to mark an unused wcid field.
+ */
+#define MT792x_WCID_IDX_UNSET	U16_MAX
 
 #define MT792x_CFEND_RATE_DEFAULT	0x49	/* OFDM 24M */
 #define MT792x_CFEND_RATE_11B		0x03	/* 11B LP, 11M */
@@ -148,6 +153,12 @@ struct mt792x_sta {
 
 	/* NAN peer schedule */
 	struct mt792x_sta_nan_sched nan_sched;
+
+	/* NAN per-peer group key WTBLs (allocated lazily when keys are installed)
+	 * Note: TX GTK is interface-level, see mt792x_vif->nan_tx_gtk_table
+	 */
+	struct mt76_wcid nan_rx_gtk_wcid;    /* NAN_DATA peer: RX GTK WTBL (per-peer) */
+	struct mt76_wcid nan_rx_igtk_wcid;   /* NAN peer: RX IGTK/BIGTK WTBL (per-peer) */
 };
 
 DECLARE_EWMA(rssi, 10, 8);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 08/23] wifi: mt76: mt7925: add NDC-aware TX GTK table for NAN_DATA
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (6 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 07/23] wifi: mt76: mt7925: add per-peer RX IGTK/BIGTK and RX GTK WTBLs Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-10-06  9:20   ` Felix Fietkau
  2026-09-27 21:02 ` [PATCH 09/23] wifi: mt76: mt7925: implement mt7925_nan_set_key for NAN security Sean Wang
                   ` (14 subsequent siblings)
  22 siblings, 1 reply; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

NAN TX GTK is NDC-scoped: every station in the same NAN Data Cluster
shares one key, and a single NAN_DATA vif can join multiple NDCs
concurrently. Add mt792x_nan.nan_tx_gtk_table[] (up to
MT792X_MAX_NAN_NDC = 8 RCU on-demand entries, one per NDC) and route
interface-directed NAN_DATA multicast through the matching WTBL in
mt792x_tx(), falling back to the interface WTBL when no entry exists.

Teardown lives in mt7925_remove_interface(), not the shared
mt792x_remove_interface(): NAN_DATA vifs are mt7925-only so cleanup
in the shared path would never run.

NOTE: NDC selection from skb context is not yet implemented; the
lookup always picks the first installed entry until mt7925_nan_set_key()
populates the table.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/main.c  | 36 +++++++++++
 drivers/net/wireless/mediatek/mt76/mt792x.h   | 64 ++++++++++++++++++-
 .../net/wireless/mediatek/mt76/mt792x_core.c  | 27 ++++++--
 3 files changed, 122 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 2d3bf7d806a7..1b253989f43b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -486,12 +486,22 @@ mt7925_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 		INIT_DELAYED_WORK(&mvif->nan.mac_rand_work,
 				  mt7925_nan_mac_rand_work);
 
+	if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+		/* nan_tx_gtk_table[] slots are NULL from mac80211's kzalloc of
+		 * drv_priv; entries are allocated on demand by set_key when a
+		 * TX GTK is installed.
+		 */
+		spin_lock_init(&mvif->nan.nan_tx_gtk_lock);
+	}
 out:
 	mt792x_mutex_release(dev);
 
 	return ret;
 }
 
+static void mt7925_nan_gtk_table_cleanup(struct mt792x_dev *dev,
+					 struct mt792x_vif *mvif);
+
 static void
 mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 {
@@ -516,6 +526,9 @@ mt7925_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
 	mconf = mt792x_link_conf_to_mconf(&vif->bss_conf);
 	mt792x_mac_link_bss_remove(dev, mconf, &mvif->sta.deflink);
 
+	if (vif->type == NL80211_IFTYPE_NAN_DATA)
+		mt7925_nan_gtk_table_cleanup(dev, mvif);
+
 	mt792x_mutex_release(dev);
 }
 
@@ -1331,6 +1344,29 @@ static void mt7925_nan_wcid_free(struct mt76_dev *mdev, struct mt76_wcid *wcid)
 	wcid->idx = MT792x_WCID_IDX_UNSET;
 }
 
+/* Free all TX GTK WTBL entries for a NAN_DATA vif on interface teardown.
+ * Caller must hold dev->mt76.mutex.
+ */
+static void mt7925_nan_gtk_table_cleanup(struct mt792x_dev *dev,
+					 struct mt792x_vif *mvif)
+{
+	int i;
+
+	for (i = 0; i < MT792X_MAX_NAN_NDC; i++) {
+		struct mt792x_nan_gtk_entry *e;
+
+		e = rcu_dereference_protected(mvif->nan.nan_tx_gtk_table[i],
+					      lockdep_is_held(&dev->mt76.mutex));
+		if (!e)
+			continue;
+		rcu_assign_pointer(mvif->nan.nan_tx_gtk_table[i], NULL);
+		rcu_assign_pointer(dev->mt76.wcid[e->wcid.idx], NULL);
+		mt76_wcid_cleanup(&dev->mt76, &e->wcid);
+		mt76_wcid_mask_clear(dev->mt76.wcid_mask, e->wcid.idx);
+		kfree(e);
+	}
+}
+
 /* Release any lazily-allocated per-peer NAN group key WTBLs for a departing
  * station.  Called from mt7925_mac_link_sta_remove() after the primary WTBL
  * is already freed, so the NAN-specific cleanup is clearly separated from the
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 707ca24193f9..342733e1001c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -155,7 +155,7 @@ struct mt792x_sta {
 	struct mt792x_sta_nan_sched nan_sched;
 
 	/* NAN per-peer group key WTBLs (allocated lazily when keys are installed)
-	 * Note: TX GTK is interface-level, see mt792x_vif->nan_tx_gtk_table
+	 * Note: TX GTK is interface-level, see mvif->nan.nan_tx_gtk_table
 	 */
 	struct mt76_wcid nan_rx_gtk_wcid;    /* NAN_DATA peer: RX GTK WTBL (per-peer) */
 	struct mt76_wcid nan_rx_igtk_wcid;   /* NAN peer: RX IGTK/BIGTK WTBL (per-peer) */
@@ -183,6 +183,60 @@ struct mt792x_nan_conf {
 	bool enable_dw_notification;
 };
 
+/*
+ * NAN WTBL layout (mt7925)
+ *
+ * TX scope -- determined by who shares the encrypting key:
+ *
+ *   mvif->sta.deflink.wcid          [NAN vif, interface-level]
+ *     TX IGTK/BIGTK: multicast management TX.  Shared by all NMI peers;
+ *     installed via the normal hardware key path (set_key fallback).
+ *
+ *   mvif->sta.deflink.wcid          [NAN_DATA vif, interface-level]
+ *     TX GTK fallback when nan.nan_tx_gtk_table[] has no entry for the NDC.
+ *
+ *   mvif->nan.nan_tx_gtk_table[i]->wcid [NAN_DATA vif, NDC-level, on-demand]
+ *     TX GTK: multicast data TX.  All peers in the same NAN Data Cluster
+ *     share one TX GTK; different NDCs use independent keys.  Each slot
+ *     is NULL until set_key installs a key for that NDC (up to
+ *     MT792X_MAX_NAN_NDC concurrent NDCs).
+ *     NOTE: currently returns -EOPNOTSUPP -- mac80211 does not pass an
+ *     NDC ID at set_key time, so the correct table slot cannot be
+ *     selected.
+ *
+ * RX scope -- always per-peer because each sender has its own key:
+ *
+ *   msta->deflink.wcid              [NMI or NDI peer, peer-level]
+ *     NM-TK (NMI unicast) or ND-TK (NDI unicast).
+ *
+ *   msta->nan_rx_gtk_wcid           [NDI peer under NAN_DATA vif, peer-level]
+ *     RX GTK: multicast data RX.  Keyidx 1-2; allocated lazily in set_key.
+ *
+ *   msta->nan_rx_igtk_wcid          [NMI peer under NAN vif, peer-level]
+ *     RX IGTK/BIGTK: multicast management RX.  Keyidx 4-7; allocated
+ *     lazily in set_key.
+ *
+ * Example -- 2 peers in different NDCs, full security (design intent):
+ *   1  NAN vif WTBL                 TX IGTK/BIGTK
+ *   1  NAN_DATA vif WTBL            TX GTK fallback
+ *   2  NMI peer WTBLs               NM-TK (one per peer)
+ *   2  NDI peer WTBLs               ND-TK (one per peer)
+ *   2  TX GTK WTBLs                 nan.nan_tx_gtk_table[], one per NDC
+ *   2  RX GTK WTBLs                 nan_rx_gtk_wcid, one per NDI peer
+ *   2  RX IGTK/BIGTK WTBLs          nan_rx_igtk_wcid, one per NMI peer
+ *  = 12 WTBLs total
+ *
+ * Maximum concurrent NDCs is based on NAN_MAX_CONN_CFG (8 peers).
+ */
+#define MT792X_MAX_NAN_NDC	8
+
+/* TX GTK WTBL for one NAN Data Cluster; allocated only when the key is installed */
+struct mt792x_nan_gtk_entry {
+	u8 ndc_id[ETH_ALEN];
+	struct mt76_wcid wcid;
+	u8 key_idx;
+};
+
 struct mt792x_nan {
 	struct mt792x_nan_conf conf;
 
@@ -208,6 +262,14 @@ struct mt792x_nan {
 	 */
 	struct delayed_work mac_rand_work;
 	u32 mac_rand_period_sec;
+
+	/* NAN_DATA TX GTK table (mt7925 only): one entry per NAN Data Cluster,
+	 * allocated on demand when set_key installs a TX GTK for that NDC.
+	 * Protected by nan_tx_gtk_lock; slots are NULL until populated.
+	 */
+	struct mt792x_nan_gtk_entry __rcu *nan_tx_gtk_table[MT792X_MAX_NAN_NDC];
+	/* Protects nan_tx_gtk_table. */
+	spinlock_t nan_tx_gtk_lock;
 };
 
 struct mt792x_vif {
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index 0ad33f74c228..c8e42447f43a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -187,11 +187,30 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control,
 		wcid = &mlink->wcid;
 	}
 
+	/* NAN_DATA multicast: route through the NDC-specific TX GTK WTBL.
+	 * Full WTBL layout is documented in mt792x.h.
+	 * NOTE: NDC selection is not yet implemented; always falls back to
+	 * the interface WTBL until set_key populates nan_tx_gtk_table[].
+	 */
 	if (vif && !control->sta) {
-		struct mt792x_vif *mvif;
-
-		mvif = (struct mt792x_vif *)vif->drv_priv;
-		wcid = &mvif->sta.deflink.wcid;
+		struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+
+		if (vif->type == NL80211_IFTYPE_NAN_DATA) {
+			struct mt792x_nan_gtk_entry *entry = NULL;
+			int i;
+
+			rcu_read_lock();
+			for (i = 0; i < MT792X_MAX_NAN_NDC; i++) {
+				entry = rcu_dereference(mvif->nan.nan_tx_gtk_table[i]);
+				if (entry)
+					break;
+			}
+			rcu_read_unlock();
+
+			wcid = entry ? &entry->wcid : &mvif->sta.deflink.wcid;
+		} else {
+			wcid = &mvif->sta.deflink.wcid;
+		}
 	}
 
 	if (vif && control->sta && ieee80211_vif_is_mld(vif) &&
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 09/23] wifi: mt76: mt7925: implement mt7925_nan_set_key for NAN security
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (7 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 08/23] wifi: mt76: mt7925: add NDC-aware TX GTK table for NAN_DATA Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 10/23] wifi: mt76: mt7925: install NAN BIP keys standalone in sta_key_tlv Sean Wang
                   ` (13 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

Wire up NAN/NAN_DATA group key installation on top of the MCU command
and per-peer WTBLs from prior commits:

 - NAN RX IGTK/BIGTK (keyidx 4-7, sta != NULL): lazy WTBL alloc via
   nan_rx_igtk_wcid; installed as NAN_KEY_TYPE_MC_MGMT_RX_KEY.
 - NAN_DATA RX GTK (keyidx 1-2, sta != NULL): lazy WTBL alloc via
   nan_rx_gtk_wcid; installed as NAN_KEY_TYPE_MC_RX_KEY.
 - NAN_DATA TX GTK: -EOPNOTSUPP; NDC ID unavailable at set_key time,
   keeping mac80211 in SW crypto until NDC-aware support lands.

mt7925_set_key() routes NAN/NAN_DATA vifs through mt7925_nan_set_key()
and blocks NAN_DATA group keys from reaching set_link_key, preventing
silent HW-offload of the unsupported TX GTK path.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/main.c  |  27 +++
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 154 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/nan.h   |  16 ++
 .../net/wireless/mediatek/mt76/mt792x_core.c  |   7 +
 4 files changed, 204 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 1b253989f43b..6c603d57e89f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -765,6 +765,33 @@ static int mt7925_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
 	struct mt792x_link_sta *mlink;
 	int err;
 
+	/* Route NAN/NAN_DATA keys.
+	 *
+	 * mt7925_nan_set_key() owns every key that needs a dedicated WTBL and
+	 * returns -EOPNOTSUPP to signal "not mine, use the normal HW path".
+	 * Three cases legitimately reach the normal path:
+	 *
+	 *   NAN     + pairwise  (NM-TK):     set_link_key via NMI peer WTBL
+	 *   NAN_DATA + pairwise (ND-TK):     set_link_key via NDI peer WTBL
+	 *   NAN     + group     (TX IGTK/BIGTK): set_link_key via NAN iface WTBL
+	 *
+	 * NAN_DATA TX GTK is the one group key on NAN_DATA that nan_set_key
+	 * also declines (-EOPNOTSUPP) because NDC ID is unavailable at set_key
+	 * time.  It must NOT reach set_link_key: that function would succeed
+	 * and silently install the key on the interface WTBL, causing mac80211
+	 * to mark it as HW-offloaded and skip SW crypto.  Return -EOPNOTSUPP
+	 * so mac80211 uses SW encryption until TX GTK support is complete.
+	 */
+	if (vif->type == NL80211_IFTYPE_NAN || vif->type == NL80211_IFTYPE_NAN_DATA) {
+		err = mt7925_nan_set_key(hw, cmd, vif, sta, key);
+		if (err != -EOPNOTSUPP)
+			return err;
+		/* Block NAN_DATA group keys from reaching set_link_key. */
+		if (vif->type == NL80211_IFTYPE_NAN_DATA &&
+		    !(key->flags & IEEE80211_KEY_FLAG_PAIRWISE))
+			return -EOPNOTSUPP;
+	}
+
 	/* The hardware does not support per-STA RX GTK, fallback
 	 * to software mode for these.
 	 */
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index 0579501207eb..e1dfcdc88382 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -1581,3 +1581,157 @@ int mt792x_nan_map_sta_rec(struct mt76_dev *mdev,
 
 	return ret ?: -ENOMEM;
 }
+
+/* Allocate a WTBL slot for a lazily-created per-peer NAN group key wcid.
+ * Mirrors mt76_wcid_alloc/init but wires the slot into the RCU wcid table and
+ * clears the hardware admission counter, matching what mt7925_mac_link_sta_add
+ * does for every normal per-link wcid.  Caller must hold dev->mt76.mutex; RCU
+ * publish is the final step so readers always see a fully initialised wcid.
+ */
+int mt7925_nan_wcid_alloc(struct mt792x_dev *dev, struct mt76_wcid *wcid)
+{
+	int idx;
+
+	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
+	if (idx < 0)
+		return -ENOSPC;
+
+	wcid->idx = idx;
+	wcid->tx_info |= MT_WCID_TX_INFO_SET;
+	mt76_wcid_init(wcid, 0);
+	mt7925_mac_wtbl_update(dev, idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+	rcu_assign_pointer(dev->mt76.wcid[idx], wcid);
+
+	return 0;
+}
+
+/* NAN RX IGTK/BIGTK (keyidx 4-7, sta != NULL): lock first, then lazy alloc. */
+static int mt7925_nan_set_rx_igtk(struct mt792x_dev *dev,
+				  const u8 *local_addr, const u8 *peer_addr,
+				  struct mt792x_sta *msta,
+				  enum set_key_cmd cmd,
+				  struct ieee80211_key_conf *key)
+{
+	enum mt7925_nan_key_operation key_op;
+	u16 wtbl_idx;
+	int err;
+
+	mt792x_mutex_acquire(dev);
+
+	/* Lazy allocation of per-peer RX IGTK/BIGTK WTBL: mt76_wcid_alloc()
+	 * touches the shared wcid mask and mt7925_mac_wtbl_update() writes
+	 * hardware registers, so both need dev->mt76.mutex and a woken chip.
+	 */
+	if (msta->nan_rx_igtk_wcid.idx == MT792x_WCID_IDX_UNSET) {
+		if (cmd != SET_KEY) {
+			mt792x_mutex_release(dev);
+			return 0;
+		}
+		err = mt7925_nan_wcid_alloc(dev, &msta->nan_rx_igtk_wcid);
+		if (err) {
+			mt792x_mutex_release(dev);
+			return err;
+		}
+	}
+
+	key_op = (cmd == SET_KEY) ? NAN_KEY_OP_SET_KEY : NAN_KEY_OP_CLS_KEY;
+	wtbl_idx = msta->nan_rx_igtk_wcid.idx;
+
+	err = mt7925_nan_manage_key_cmd(dev, key_op, NAN_KEY_TYPE_MC_MGMT_RX_KEY,
+					wtbl_idx, local_addr, peer_addr,
+					&(struct mt7925_nan_key_info){
+						.algo_id = mt7925_mcu_get_cipher(key->cipher),
+						.key_id  = key->keyidx,
+						.key_len = key->keylen,
+						.key_data = key->key,
+					});
+	mt792x_mutex_release(dev);
+	return err;
+}
+
+/* NAN_DATA GTK (keyidx 1-2): lock first, then lazy alloc for RX; TX unsupported. */
+static int mt7925_nan_set_data_gtk(struct mt792x_dev *dev,
+				   struct ieee80211_sta *sta,
+				   struct mt792x_sta *msta,
+				   enum set_key_cmd cmd,
+				   struct ieee80211_key_conf *key)
+{
+	static const u8 bcast_addr[ETH_ALEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+	enum mt7925_nan_key_operation key_op;
+	enum mt7925_nan_key_type key_type;
+	u8 *peer_addr, *local_addr;
+	u16 wtbl_idx;
+	int err;
+
+	mt792x_mutex_acquire(dev);
+
+	key_op = (cmd == SET_KEY) ? NAN_KEY_OP_SET_KEY : NAN_KEY_OP_CLS_KEY;
+
+	if (sta) {
+		/* RX GTK: per-peer dedicated WTBL */
+		peer_addr = sta->addr;
+		local_addr = (u8 *)bcast_addr;
+		key_type = NAN_KEY_TYPE_MC_RX_KEY;
+
+		/* Lazy allocation of per-peer RX GTK WTBL */
+		if (msta->nan_rx_gtk_wcid.idx == MT792x_WCID_IDX_UNSET) {
+			if (key_op != NAN_KEY_OP_SET_KEY) {
+				mt792x_mutex_release(dev);
+				return 0;
+			}
+			err = mt7925_nan_wcid_alloc(dev, &msta->nan_rx_gtk_wcid);
+			if (err) {
+				mt792x_mutex_release(dev);
+				return err;
+			}
+		}
+
+		wtbl_idx = msta->nan_rx_gtk_wcid.idx;
+	} else {
+		/* TX GTK: not supported yet; NDC ID is unavailable at set_key
+		 * time so the correct TX GTK table entry cannot be selected.
+		 */
+		dev_warn(dev->mt76.dev,
+			 "nan: TX GTK not supported (missing NDC ID)\n");
+		mt792x_mutex_release(dev);
+		return -EOPNOTSUPP;
+	}
+
+	err = mt7925_nan_manage_key_cmd(dev, key_op, key_type, wtbl_idx,
+					local_addr, peer_addr,
+					&(struct mt7925_nan_key_info){
+						.algo_id = mt7925_mcu_get_cipher(key->cipher),
+						.key_id  = key->keyidx,
+						.key_len = key->keylen,
+						.key_data = key->key,
+					});
+	mt792x_mutex_release(dev);
+	return err;
+}
+
+int mt7925_nan_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+		       struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+		       struct ieee80211_key_conf *key)
+{
+	struct mt792x_dev *dev = mt792x_hw_dev(hw);
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt792x_sta *msta = sta ? (struct mt792x_sta *)sta->drv_priv : &mvif->sta;
+
+	/* NAN RX IGTK/BIGTK (keyidx 4-7, sta != NULL) */
+	if (vif->type == NL80211_IFTYPE_NAN && sta &&
+	    key->keyidx >= NAN_KEY_IDX_MGMT_INTEG_MIN &&
+	    key->keyidx <= NAN_KEY_IDX_MGMT_INTEG_MAX)
+		return mt7925_nan_set_rx_igtk(dev, vif->addr, sta->addr, msta,
+					      cmd, key);
+
+	/* NAN_DATA GTK (keyidx 1-2) */
+	if (vif->type == NL80211_IFTYPE_NAN_DATA &&
+	    key->keyidx >= NAN_KEY_IDX_GTK_MIN &&
+	    key->keyidx <= NAN_KEY_IDX_GTK_MAX)
+		return mt7925_nan_set_data_gtk(dev, sta, msta, cmd, key);
+
+	/* Unicast keys (NM-TK, ND-TK) and NAN TX IGTK/BIGTK use the normal
+	 * hardware path via set_link_key.
+	 */
+	return -EOPNOTSUPP;
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index f15a16b773bb..9a47940f5d61 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -127,6 +127,16 @@ enum mt7925_nan_key_type {
 #define WTBL_RESERVED_ENTRY	0xFFFF
 #define NAN_PACKET_NUMBER_LEN	6
 
+/* NAN key index ranges from the Wi-Fi NAN spec.  mac80211 passes keyidx
+ * straight from wpa_supplicant without interpretation, so the driver must
+ * define these boundaries itself -- no kernel-wide equivalent exists.
+ * Wi-Fi NAN spec 7.1.3.2 (GTKSA), 7.1.3.3 (IGTKSA), 7.1.3.4 (BIGTKSA).
+ */
+#define NAN_KEY_IDX_GTK_MIN		1	/* GTKSA:  key ID 1-2 */
+#define NAN_KEY_IDX_GTK_MAX		2
+#define NAN_KEY_IDX_MGMT_INTEG_MIN	4	/* IGTKSA (4-5) + BIGTKSA (6-7) */
+#define NAN_KEY_IDX_MGMT_INTEG_MAX	7
+
 /* bit indices into mt792x_dev->nan_deferred_pending, set from the atomic
  * MCU-event RX path and consumed by mt7925_nan_deferred_work()
  */
@@ -600,6 +610,12 @@ int mt7925_nan_manage_key_cmd(struct mt792x_dev *dev,
 			      const u8 *peer_addr,
 			      const struct mt7925_nan_key_info *key);
 
+int mt7925_nan_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
+		       struct ieee80211_vif *vif, struct ieee80211_sta *sta,
+		       struct ieee80211_key_conf *key);
+
+int mt7925_nan_wcid_alloc(struct mt792x_dev *dev, struct mt76_wcid *wcid);
+
 void mt7925_nan_local_sched_changed(struct mt792x_dev *dev,
 				    struct ieee80211_vif *vif);
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
index c8e42447f43a..94bd0e3fe2e3 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c
+++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c
@@ -824,6 +824,13 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw)
 		wiphy->nan_capa.max_channel_switch_time = 12;
 		wiphy->nan_capa.dev_capabilities = NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
 		wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_SECURE_NAN);
+		wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_PROTECTION);
+		wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT);
+		/* Per-peer group key WTBL needed for NAN RX IGTK/BIGTK delivery:
+		 * ieee80211_key_enable_hw_accel() drops per-STA group keys unless
+		 * this flag is set or the vif is NAN_DATA.
+		 */
+		ieee80211_hw_set(hw, SUPPORTS_PER_STA_GTK);
 	}
 
 	wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 10/23] wifi: mt76: mt7925: install NAN BIP keys standalone in sta_key_tlv
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (8 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 09/23] wifi: mt76: mt7925: implement mt7925_nan_set_key for NAN security Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 11/23] wifi: mt76: mt7925: do not disable RX NAPI twice on unload Sean Wang
                   ` (12 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

From: Chengwei Yu <chengwei.yu@mediatek.com>

mt7925_mcu_sta_key_tlv() packs a BIP_CMAC_128 key together with the CCMP
key that sta_key_conf cached from an earlier key installation on the same
link: cipher_id BIP_CMAC_128, key_len 32, the CCMP key in the first 16
bytes and the CMAC key in the second 16, indexed by the CCMP key's id.
That matches STA/AP mode, where the IGTK always follows the GTK on the
same WTBL.

A NAN interface has no such pairing. No CCMP key is ever installed on a
NAN or NAN_DATA interface WTBL, so sta_key_conf is still zeroed when a NAN
TX IGTK/BIGTK arrives: the command would carry 16 bytes of zeros as the
first half of the key material and key_id 0 instead of the real 4-7 key
index.

Restrict the combined-key path to non-NAN interfaces so that a NAN BIP key
takes the generic path instead and is installed standalone, with its own
keyidx and its own 16-byte key material.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 76dcbbffabfc..d494753cdf04 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1378,7 +1378,15 @@ mt7925_mcu_sta_key_tlv(struct mt76_wcid *wcid,
 		if (cipher == CONNAC3_CIPHER_NONE)
 			return -EOPNOTSUPP;
 
-		if (cipher == CONNAC3_CIPHER_BIP_CMAC_128) {
+		/* STA/AP mode installs the IGTK together with the CCMP GTK that
+		 * sta_key_conf cached earlier on the same link.  NAN has no such
+		 * pairing: no CCMP key is ever installed on a NAN interface WTBL,
+		 * so sta_key_conf would contribute a zero key and a stale key_id.
+		 * Install the NAN BIP key standalone via the generic path instead.
+		 */
+		if (cipher == CONNAC3_CIPHER_BIP_CMAC_128 &&
+		    vif->type != NL80211_IFTYPE_NAN &&
+		    vif->type != NL80211_IFTYPE_NAN_DATA) {
 			sec->cipher_id = CONNAC3_CIPHER_BIP_CMAC_128;
 			sec->key_id = sta_key_conf->keyidx;
 			sec->key_len = 32;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 11/23] wifi: mt76: mt7925: do not disable RX NAPI twice on unload
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (9 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 10/23] wifi: mt76: mt7925: install NAN BIP keys standalone in sta_key_tlv Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 12/23] wifi: mt76: mt7925: assign the interface WTBL to the NAN management TXQ Sean Wang
                   ` (11 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

mt7925e_unregister_device() disables every RX NAPI and then calls
mt792x_dma_cleanup(), whose mt76_dma_cleanup() disables them again.
The second napi_disable() spins waiting for NAPI_STATE_SCHED, which the
first one already set and nobody will clear, so module unload hangs in
D state and the module refcount underflows to -1; only a reboot recovers.

Drop the redundant loop and leave the RX NAPI teardown to
mt76_dma_cleanup(), making driver hot-reload work again.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index 24585c992ff5..09153d624fd5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -40,7 +40,6 @@ static int mt7925e_init_reset(struct mt792x_dev *dev)
 
 static void mt7925e_unregister_device(struct mt792x_dev *dev)
 {
-	int i;
 	struct mt76_connac_pm *pm = &dev->pm;
 	struct ieee80211_hw *hw = mt76_hw(dev);
 
@@ -50,8 +49,12 @@ static void mt7925e_unregister_device(struct mt792x_dev *dev)
 	cancel_work_sync(&dev->reset_work);
 	cancel_work_sync(&dev->init_work);
 	mt76_unregister_device(&dev->mt76);
-	mt76_for_each_q_rx(&dev->mt76, i)
-		napi_disable(&dev->mt76.napi[i]);
+	/* Do not disable the RX NAPIs here: mt76_dma_cleanup(), reached below
+	 * via mt792x_dma_cleanup(), disables them as part of the generic
+	 * teardown. Disabling twice makes the second napi_disable() wait
+	 * forever for NAPI_STATE_SCHED to clear, hanging module unload in D
+	 * state with the module refcount left at -1.
+	 */
 	cancel_delayed_work_sync(&pm->ps_work);
 	cancel_delayed_work_sync(&dev->mlo_pm_work);
 	cancel_work_sync(&pm->wake_work);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 12/23] wifi: mt76: mt7925: assign the interface WTBL to the NAN management TXQ
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (10 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 11/23] wifi: mt76: mt7925: do not disable RX NAPI twice on unload Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 13/23] wifi: mt76: mt7925: do not deactivate shared NAN BSS on peer STA removal Sean Wang
                   ` (10 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

mac80211 does not allocate vif->txq for a NAN device, so every SDF the
host sends - multicast discovery and unicast follow-up alike - is queued
on the dedicated management TXQ (vif->txq_mgmt) with txq->sta == NULL.

mt7925_mac_link_bss_add() only programs the wcid of vif->txq, so the
management TXQ keeps its initial wcid of 0. The mt76 TXQ scheduler then
resolves dev->wcid[0], i.e. the global WTBL, and every host-originated
SDF is transmitted on it. That WTBL only carries the parameters used for
beacons and has no usable unicast rate, so a unicast follow-up sent on it
is frequently not heard by the peer and the send is reported as failed.
Multicast SDFs are unaffected because they need no acknowledgment.

Program the management TXQ with the same interface WTBL that the beacon
and interface paths already use, which is fully initialised here.

Instrumenting the TX descriptor write shows every follow-up leaving on
WTBL 0 before the fix and on the interface WTBL after it; a 100-frame
unicast follow-up burst goes from 92-96 frames received to 100.

Fixes: 0f3605e4f8de ("wifi: mt76: mt7925: wire up NAN operations")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/main.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 6c603d57e89f..64853fc7f60e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -447,6 +447,17 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev,
 		mtxq->wcid = idx;
 	}
 
+	/* A NAN vif has no vif->txq; its SDFs are queued on vif->txq_mgmt,
+	 * whose wcid was never set and defaults to 0 (WTBL 0 has no unicast
+	 * rate), so unicast follow-ups go unacknowledged. Point txq_mgmt at
+	 * the same interface WTBL as the beacon path so SDFs continue to use
+	 * txq_mgmt but with a properly initialised wcid.
+	 */
+	if (vif->txq_mgmt) {
+		mtxq = (struct mt76_txq *)vif->txq_mgmt->drv_priv;
+		mtxq->wcid = idx;
+	}
+
 out:
 	return ret;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 13/23] wifi: mt76: mt7925: do not deactivate shared NAN BSS on peer STA removal
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (11 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 12/23] wifi: mt76: mt7925: assign the interface WTBL to the NAN management TXQ Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 14/23] wifi: mt76: gate NAN unicast mgmt by peer availability via STA queues Sean Wang
                   ` (9 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

mt7925_mac_sta_remove_links() sent BSS_INFO(active=0) for every vif type
except AP when a peer STA was removed. NAN (nan0) and NAN_DATA (ndi0)
BSSes host multiple peer STAs like an AP does: removing the NMI peer on
NDP teardown deactivated the shared NAN sync BSS in FW, after which FW
dropped every host-initiated NAN mgmt frame (TXM: An MMPDU for an
inactive BSS_INFO) until the next NAN_START. With partial availability
bitmaps this made every NDP re-establishment after a teardown fail
(unicast NAF TXCNT 15/15 no-ack); full bitmaps masked the bug. It also
tears down the shared data BSS while other NDPs on the same NDI live.

Exclude NAN and NAN_DATA vifs like AP.

Verified on 187/235: teardown no longer emits the BSS disable cmd, the
FW drop flood is gone, and half-bitmap re-establishment after
nan_ndp_terminate succeeds repeatedly (was 100% fail).

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/main.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 64853fc7f60e..08a5bdcf1094 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -1489,7 +1489,16 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif,
 		struct mt792x_bss_conf *mconf;
 		struct mt792x_link_sta *mlink;
 
-		if (vif->type == NL80211_IFTYPE_AP)
+		/* AP, NAN, and NAN_DATA BSSes host multiple peer STAs; removing
+		 * one peer must not deactivate the shared BSS. For NAN, doing
+		 * so would kill the sync BSS and the firmware would drop all
+		 * subsequent host-originated NAN management frames. For
+		 * NAN_DATA, it would tear down all other NDPs sharing the same
+		 * NDI.
+		 */
+		if (vif->type == NL80211_IFTYPE_AP ||
+		    vif->type == NL80211_IFTYPE_NAN ||
+		    vif->type == NL80211_IFTYPE_NAN_DATA)
 			break;
 
 		if (vif->type == NL80211_IFTYPE_STATION && sta->tdls)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 14/23] wifi: mt76: gate NAN unicast mgmt by peer availability via STA queues
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (12 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 13/23] wifi: mt76: mt7925: do not deactivate shared NAN BSS on peer STA removal Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 15/23] wifi: mt76: mt7925: hold NAN mgmt TX to the discovery window Sean Wang
                   ` (8 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

Host-initiated NAN action frames (NDP Request/Response/Confirm) were
sent on MT_TXQ_PSD -> MT_LMAC_ALTX0 through the interface WTBL: a
force-TX path with no relation to the NAN timeline. With a partial
availability bitmap the peer is off-channel most of the time, so the
15-retry burst (~11-51ms) regularly misses the peer entirely and NDP
setup fails probabilistically (half ~2/3, quarter ~0), and per spec
unicast NAN mgmt must be transmitted within the peer availability
windows.

Route unicast NAN mgmt through the peer STA WTBL on its AC queue
instead: the FW pauses/resumes the peer WTBL AC queues according to
the peer schedule record (the committed-window intersection computed
by the supplicant), which is the same gating the NDP data path already
relies on. Multicast NAN mgmt (and unicast without a peer STA) keeps
using the interface WTBL, which has no pause driver.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/tx.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index 3707ee19e4ae..a3688b9fe635 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -681,6 +681,18 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid,
 		     head == &wcid->tx_offchannel))
 			qid = MT_TXQ_PSD;
 
+		/* NAN unicast mgmt (NAFs) must stay on its AC queue: the
+		 * PSD path maps to the ALTX (force-TX) hardware queue,
+		 * which escapes the per-peer STA_PAUSE availability gating
+		 * and transmits regardless of the peer's committed bitmap.
+		 */
+		if (qid == MT_TXQ_PSD && wcid->sta &&
+		    info->control.vif &&
+		    (info->control.vif->type == NL80211_IFTYPE_NAN ||
+		     info->control.vif->type == NL80211_IFTYPE_NAN_DATA) &&
+		    head != &wcid->tx_offchannel)
+			qid = skb_get_queue_mapping(skb);
+
 		q = phy->q_tx[qid];
 		if (mt76_txq_stopped(q) || test_bit(MT76_RESET, &phy->state)) {
 			ret = -1;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 15/23] wifi: mt76: mt7925: hold NAN mgmt TX to the discovery window
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (13 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 14/23] wifi: mt76: gate NAN unicast mgmt by peer availability via STA queues Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 16/23] wifi: mt76: mt7925: double the retry budget for NAN unicast management Sean Wang
                   ` (7 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

Host-initiated NAN management - SDF follow-ups and the frames that open
an NDP setup - rides the interface WTBL and airs the moment the host
hands it over. Until its NDL is confirmed a peer is only reliably awake
in the discovery window, so those frames regularly land while it is off
channel. The same WTBL carries the beacons and the multicast data that
must never be held, so gating that queue is not an option.

Create a dedicated STA and steer host management TX onto it, so the
firmware can hold that one queue outside the DW while the interface
WTBL keeps flowing. The driver hands the STA's WTBL index down with
NAN_UNI_CMD_DW_WTBL_IDX right after the STA record is added - the index
is the handle the firmware's queue pause works on, so no reserved
address is agreed on and the record's address is an arbitrary locally
administered one that never goes on air. A firmware that predates the
tag ignores it, and on a failed handoff the firmware's gate stays
disarmed, so management TX degrades to stock (ungated) behavior rather
than breaking NAN. The split is done per frame: multicast stays on the
interface WTBL, since a STA-type WTBL spends the full retry budget on an
unACKable multicast RA and one frame then eats a whole DW window, and
only unencrypted unicast - discovery and NDP setup - is held to the DW.
Secured frames belong to an established peer and keep its own WTBL and
key.

MT_TXQ_PSD reaches ALTX and escapes the pause, so the existing NAN
reroute is extended to cover this WTBL. The STA record is driven to the
associated state with its rate table configured; without either the
firmware buffers the queue or the hardware falls back to the lowest rate,
both of which show up as management frames failing after seconds. On
teardown the wcid is released only after pending TX status entries are
flushed, or the next status sweep dereferences them (KASAN use-after-free
via wpdma_reset -> mt76_tx_status_check).

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt76.h     |   1 +
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   |  11 +-
 .../net/wireless/mediatek/mt76/mt7925/mcu.h   |  11 ++
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 174 +++++++++++++++++-
 .../net/wireless/mediatek/mt76/mt7925/nan.h   |   8 +
 .../wireless/mediatek/mt76/mt7925/pci_mac.c   |  48 +++++
 drivers/net/wireless/mediatek/mt76/mt792x.h   |   7 +
 drivers/net/wireless/mediatek/mt76/tx.c       |   2 +-
 8 files changed, 250 insertions(+), 12 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 62b41c8bb7c0..8122da3301ab 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -397,6 +397,7 @@ struct mt76_wcid {
 	u8 sta:1;
 	u8 sta_disabled:1;
 	u8 amsdu:1;
+	u8 nan_dw:1;
 	u8 phy_idx:2;
 	u8 link_id:4;
 	bool link_valid;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index d494753cdf04..4dcb1fbda793 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1972,16 +1972,7 @@ mt7925_mcu_sta_state_v2_tlv(struct mt76_phy *mphy, struct sk_buff *skb,
 			    struct ieee80211_vif *vif,
 			    u8 rcpi, u8 sta_state)
 {
-	struct sta_rec_state_v2 {
-		__le16 tag;
-		__le16 len;
-		u8 state;
-		u8 rsv[3];
-		__le32 flags;
-		u8 vht_opmode;
-		u8 action;
-		u8 rsv2[2];
-	} __packed * state;
+	struct sta_rec_state_v2 *state;
 	struct tlv *tlv;
 
 	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_STATE, sizeof(*state));
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
index 11f9eac13ffc..212c8caadd0c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
@@ -466,6 +466,17 @@ struct sta_rec_hdr_trans {
 	u8 rsv;
 } __packed;
 
+struct sta_rec_state_v2 {
+	__le16 tag;
+	__le16 len;
+	u8 state;
+	u8 rsv[3];
+	__le32 flags;
+	u8 vht_opmode;
+	u8 action;
+	u8 rsv2[2];
+} __packed;
+
 struct sta_rec_mld {
 	__le16 tag;
 	__le16 len;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index e1dfcdc88382..715e080ce4ee 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -4,6 +4,7 @@
 #include <asm/byteorder.h>
 #include <linux/bitfield.h>
 #include <linux/errno.h>
+#include <linux/etherdevice.h>
 #include <linux/kernel.h>
 #include <linux/stddef.h>
 #include <linux/string.h>
@@ -329,6 +330,167 @@ mt7925_nan_seed_link_sta(struct mt792x_dev *dev,
 	return nan_ctx;
 }
 
+/* Hand the DW-WTBL STA's WTBL index down to the firmware, which gates the
+ * queue by index alone - no address has to be agreed on. Until the index
+ * arrives the firmware's gate stays disarmed, so a failure here degrades
+ * to stock (ungated) management TX rather than breaking NAN.
+ */
+static int mt7925_nan_set_dw_wtbl_idx(struct mt792x_dev *dev, u16 wlan_idx)
+{
+	struct {
+		u8 rsv[4];
+		struct mt7925_nan_dw_wtbl_idx_tlv tlv;
+	} cmd = {
+		.tlv = {
+			.tag = cpu_to_le16(NAN_UNI_CMD_DW_WTBL_IDX),
+			.len = cpu_to_le16(sizeof(struct mt7925_nan_dw_wtbl_idx_tlv)),
+			.wlan_idx = cpu_to_le16(wlan_idx),
+		},
+	};
+
+	return mt76_mcu_send_msg(&dev->mt76, MCU_UNI_CMD(NAN), &cmd,
+				 sizeof(cmd), true);
+}
+
+/* Create the DW-WTBL STA and steer host mgmt TX onto it. The firmware is
+ * handed its WTBL index and holds that queue outside the DW, so unicast
+ * discovery only airs when every synced peer is awake. Best-effort: on
+ * failure the stock path keeps mgmt on the interface WTBL.
+ */
+static void mt7925_nan_dw_wcid_setup(struct mt792x_dev *dev,
+				     struct ieee80211_vif *vif)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt792x_bss_conf *mconf = &mvif->bss_conf;
+	struct mt76_wcid *own = &mvif->sta.deflink.wcid;
+	struct sta_rec_basic *basic;
+	struct mt76_txq *mtxq;
+	struct sk_buff *skb;
+	struct tlv *tlv;
+	int idx, ret;
+
+	if (mvif->nan_dw_wcid.idx > 0 &&
+	    mvif->nan_dw_wcid.idx < MT792x_WTBL_STA)
+		return;
+
+	idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1);
+	if (idx < 0)
+		goto err;
+
+	mvif->nan_dw_wcid.idx = idx;
+	mvif->nan_dw_wcid.phy_idx = own->phy_idx;
+	mvif->nan_dw_wcid.nan_dw = 1;
+	mvif->nan_dw_wcid.tx_info |= MT_WCID_TX_INFO_SET;
+	mt76_wcid_init(&mvif->nan_dw_wcid, mconf->mt76.band_idx);
+	mt7925_mac_wtbl_update(dev, idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR);
+
+	skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76,
+					      &mvif->nan_dw_wcid,
+					      MT7925_STA_UPDATE_MAX_SIZE);
+	if (IS_ERR(skb))
+		goto err_free;
+
+	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_BASIC, sizeof(*basic));
+	basic = (struct sta_rec_basic *)tlv;
+	basic->conn_type = cpu_to_le32(CONNECTION_NAN);
+	basic->conn_state = CONN_STATE_PORT_SECURE;
+	basic->extra_info = cpu_to_le16(EXTRA_INFO_VER | EXTRA_INFO_NEW);
+	/* The record needs an address but nothing ever matches on it: frame
+	 * headers carry the real NMI/peer addresses and the firmware gates the
+	 * queue by WTBL index. Any locally administered address will do.
+	 */
+	eth_random_addr(basic->peer_addr);
+
+	/* A bare STA_REC leaves the WTBL rate table unconfigured and HW
+	 * unicast from it crawls (multi-second delivery). Give it the NAN
+	 * base rates: OFDM+ERP, no CCK.
+	 */
+	{
+		struct sta_rec_phy *phy;
+		struct sta_rec_ra_info *ra_info;
+
+		tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_PHY, sizeof(*phy));
+		phy = (struct sta_rec_phy *)tlv;
+		phy->phy_type = PHY_TYPE_BIT_OFDM | PHY_TYPE_BIT_ERP;
+		phy->basic_rate = cpu_to_le16(0x150);
+
+		tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_RA,
+					      sizeof(*ra_info));
+		ra_info = (struct sta_rec_ra_info *)tlv;
+		ra_info->legacy = cpu_to_le16(FIELD_PREP(RA_LEGACY_OFDM, 0xff));
+	}
+
+	/* Drive the record to the associated state: firmware buffers TX for
+	 * a STA that never left the initial state, which shows up as NAFs
+	 * sitting in the queue for seconds and then failing.
+	 */
+	{
+		struct sta_rec_state_v2 *state;
+
+		tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_STATE,
+					      sizeof(*state));
+		state = (struct sta_rec_state_v2 *)tlv;
+		state->state = MT76_STA_INFO_STATE_ASSOC;
+	}
+
+	ret = mt76_mcu_skb_send_msg(&dev->mt76, skb,
+				    MCU_UNI_CMD(STA_REC_UPDATE), true);
+	if (ret)
+		goto err_free;
+
+	rcu_assign_pointer(dev->mt76.wcid[idx], &mvif->nan_dw_wcid);
+
+	/* Arm the firmware's DW gate for this WTBL index; on failure the gate
+	 * stays disarmed and management TX degrades to stock (ungated).
+	 */
+	ret = mt7925_nan_set_dw_wtbl_idx(dev, idx);
+	if (ret)
+		dev_warn(dev->mt76.dev,
+			 "NAN: DW-WTBL index handoff failed (%d)\n", ret);
+
+	if (vif->txq_mgmt) {
+		mtxq = (struct mt76_txq *)vif->txq_mgmt->drv_priv;
+		mtxq->wcid = idx;
+	}
+
+	dev_info(dev->mt76.dev, "NAN DW-WTBL up: wcid=%d\n", idx);
+	return;
+
+err_free:
+	mt76_wcid_cleanup(&dev->mt76, &mvif->nan_dw_wcid);
+	mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx);
+	mvif->nan_dw_wcid.idx = 0;
+err:
+	dev_warn(dev->mt76.dev, "NAN DW-WTBL setup failed, stock mgmt path\n");
+}
+
+/* Rebind host mgmt TX to the interface WTBL and drop the DW-WTBL STA.
+ * The firmware frees the STA record (and force-releases the gate) as
+ * part of NAN disable, so only the driver-side wcid is torn down here.
+ */
+static void mt7925_nan_dw_wcid_release(struct mt792x_dev *dev,
+				       struct ieee80211_vif *vif)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt76_txq *mtxq;
+	int idx = mvif->nan_dw_wcid.idx;
+
+	if (idx <= 0 || idx >= MT792x_WTBL_STA)
+		return;
+
+	if (vif->txq_mgmt) {
+		mtxq = (struct mt76_txq *)vif->txq_mgmt->drv_priv;
+		mtxq->wcid = mvif->sta.deflink.wcid.idx;
+	}
+
+	/* Flush TX-status entries still tracked on this wcid before it goes. */
+	mt76_tx_status_check(&dev->mt76, true);
+	rcu_assign_pointer(dev->mt76.wcid[idx], NULL);
+	mt76_wcid_cleanup(&dev->mt76, &mvif->nan_dw_wcid);
+	mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx);
+	mvif->nan_dw_wcid.idx = 0;
+}
+
 int mt7925_nan_enable(struct ieee80211_vif *vif,
 		      struct mt792x_dev *dev,
 		      struct cfg80211_nan_conf *conf)
@@ -338,6 +500,7 @@ int mt7925_nan_enable(struct ieee80211_vif *vif,
 	struct mt7925_nan_common_hdr *hdr;
 	struct mt7925_nan_enable_req_tlv *req;
 	struct sk_buff *skb;
+	int ret;
 
 	if (!vif || !dev || !conf)
 		return -EINVAL;
@@ -377,7 +540,13 @@ int mt7925_nan_enable(struct ieee80211_vif *vif,
 
 	mt7925_nan_update_conf(mvif, conf);
 
-	return mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+	ret = mt76_mcu_skb_send_msg(mdev, skb, MCU_UNI_CMD(NAN), true);
+	if (ret)
+		return ret;
+
+	mt7925_nan_dw_wcid_setup(dev, vif);
+
+	return 0;
 }
 
 int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev)
@@ -397,6 +566,9 @@ int mt7925_nan_disable(struct ieee80211_vif *vif, struct mt792x_dev *dev)
 	if (!dev)
 		return -EINVAL;
 
+	if (vif)
+		mt7925_nan_dw_wcid_release(dev, vif);
+
 	return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nan_cmd, sizeof(nan_cmd), true);
 }
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 9a47940f5d61..415fd6b6f0e6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -92,6 +92,7 @@ enum nan_uni_cmd_tag {
 	NAN_UNI_CMD_SET_SYNC_RSSI		= 39,
 	NAN_UNI_CMD_SET_CLUSTER_ID		= 40,
 	NAN_UNI_CMD_KEY_MANAGEMENT		= 53,
+	NAN_UNI_CMD_DW_WTBL_IDX			= 55,
 };
 
 enum nan_uni_event_tag {
@@ -379,6 +380,13 @@ struct mt7925_nan_nmi_addr_tlv {
 	u8 reserved[2];
 } __packed __aligned(4);
 
+struct mt7925_nan_dw_wtbl_idx_tlv {
+	__le16 tag;
+	__le16 len;
+	__le16 wlan_idx;
+	u8 reserved[2];
+} __packed __aligned(4);
+
 struct mt7925_nan_avail_ctrl_tlv {
 	__le16 tag;
 	__le16 len;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
index 8477d21abc66..5bdf3ebe6aef 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
@@ -14,6 +14,8 @@ int mt7925e_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
 	struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
 	struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx_info->skb);
 	struct ieee80211_key_conf *key = info->control.hw_key;
+	struct ieee80211_hdr *hdr = (void *)tx_info->skb->data;
+	struct ieee80211_vif *vif = info->control.vif;
 	struct mt76_connac_hw_txp *txp;
 	struct mt76_txwi_cache *t;
 	int id, pid;
@@ -25,6 +27,52 @@ int mt7925e_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
 	if (!wcid)
 		wcid = &dev->mt76.global_wcid;
 
+	/* Split NAN mgmt TX per frame, by what is known about the destination.
+	 *
+	 * Multicast must not ride the DW-WTBL STA: a STA-type WTBL spends the
+	 * full retry budget on an unACKable multicast RA, so one frame eats a
+	 * whole DW window and the publish SDF queue backlogs.
+	 *
+	 * Unencrypted unicast to a peer we have no station for is first
+	 * contact (SDF, NDP request): the DW is the only rendezvous, so it
+	 * goes on the DW-WTBL and waits for it.
+	 *
+	 * Once its schedule is known - the peer station exists, which is also
+	 * when firmware holds its committed bitmap - the frame belongs on that
+	 * station instead. Firmware then airs it inside the peer's own
+	 * committed slots, where the peer is awake and the medium is not the
+	 * DW pile-up, and follows the FAW onto whatever channel the window
+	 * actually uses rather than being pinned to the DW channel.
+	 *
+	 * Secured frames belong to an established peer and keep its own WTBL
+	 * and key.
+	 */
+	if (vif && vif->type == NL80211_IFTYPE_NAN &&
+	    ieee80211_is_mgmt(hdr->frame_control)) {
+		struct mt792x_vif *mvif = (void *)vif->drv_priv;
+		bool mcast = is_multicast_ether_addr(hdr->addr1);
+
+		if (mcast && wcid == &mvif->nan_dw_wcid) {
+			wcid = &mvif->sta.deflink.wcid;
+		} else if (!mcast && !key &&
+			   mvif->nan_dw_wcid.idx &&
+			   mvif->nan_dw_wcid.idx < MT792x_WTBL_STA) {
+			struct ieee80211_sta *psta;
+			struct mt792x_sta *pmsta;
+
+			rcu_read_lock();
+			psta = ieee80211_find_sta(vif, hdr->addr1);
+			pmsta = psta ? (struct mt792x_sta *)psta->drv_priv :
+				       NULL;
+
+			if (pmsta && pmsta->deflink.wcid.idx)
+				wcid = &pmsta->deflink.wcid;
+			else
+				wcid = &mvif->nan_dw_wcid;
+			rcu_read_unlock();
+		}
+	}
+
 	t = (struct mt76_txwi_cache *)(txwi + mdev->drv->txwi_size);
 	t->skb = tx_info->skb;
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 342733e1001c..ee462eab4028 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -288,6 +288,13 @@ struct mt792x_vif {
 	struct timer_list csa_timer;
 
 	struct mt792x_nan nan;
+
+	/* NAN DW-WTBL: driver-created STA the firmware gates to the DW;
+	 * host mgmt TX (SDF / pre-NDP NAF) is steered onto it so the BMC
+	 * WTBL carries multicast data only and is never paused for it.
+	 * idx == 0 or >= MT792x_WTBL_STA means not set up.
+	 */
+	struct mt76_wcid nan_dw_wcid;
 };
 
 struct mt792x_phy {
diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index a3688b9fe635..72a4bc505aed 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -686,7 +686,7 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid,
 		 * which escapes the per-peer STA_PAUSE availability gating
 		 * and transmits regardless of the peer's committed bitmap.
 		 */
-		if (qid == MT_TXQ_PSD && wcid->sta &&
+		if (qid == MT_TXQ_PSD && (wcid->sta || wcid->nan_dw) &&
 		    info->control.vif &&
 		    (info->control.vif->type == NL80211_IFTYPE_NAN ||
 		     info->control.vif->type == NL80211_IFTYPE_NAN_DATA) &&
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 16/23] wifi: mt76: mt7925: double the retry budget for NAN unicast management
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (14 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 15/23] wifi: mt76: mt7925: hold NAN mgmt TX to the discovery window Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:02 ` [PATCH 17/23] wifi: mt76: mt7925: let TX status of DW-held NAN frames outlive the DW period Sean Wang
                   ` (6 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

The NAN setup handshakes are one-shot at the host: wpa_supplicant aborts
PASN on the first unacknowledged authentication frame and completes the
NDP state machine without retransmitting an unacknowledged key-install.
Right after the data path confirm both peers transmit at each other
nearly simultaneously, and with half-duplex radios one 15-retry burst
regularly dies inside the peer's own TX window (measured ~50% loss of
the key-install on an MT7925 pair, killing the NDP setup).

Double the TXD retry budget to the field maximum for NAN unicast
management so the burst outlives the cross-fire window.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index 101f571b027f..f19d0451f373 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -806,6 +806,19 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
 
 	val = FIELD_PREP(MT_TXD3_REM_TX_COUNT, 15);
 
+	/* NAN setup handshakes are one-shot at the host: a single lost burst
+	 * kills the NDP (peers transmit at each other nearly simultaneously
+	 * after M3 and half-duplex radios miss the reply). Double the retry
+	 * budget so the burst outlives the cross-fire window.
+	 */
+	if (vif && vif->type == NL80211_IFTYPE_NAN) {
+		struct ieee80211_hdr *nan_hdr = (struct ieee80211_hdr *)skb->data;
+
+		if (ieee80211_is_mgmt(nan_hdr->frame_control) &&
+		    !is_multicast_ether_addr(nan_hdr->addr1))
+			val = FIELD_PREP(MT_TXD3_REM_TX_COUNT, 31);
+	}
+
 	if (key)
 		val |= MT_TXD3_PROTECT_FRAME;
 	if (info->flags & IEEE80211_TX_CTL_NO_ACK)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 17/23] wifi: mt76: mt7925: let TX status of DW-held NAN frames outlive the DW period
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (15 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 16/23] wifi: mt76: mt7925: double the retry budget for NAN unicast management Sean Wang
@ 2026-09-27 21:02 ` Sean Wang
  2026-09-27 21:03 ` [PATCH 18/23] wifi: mt76: mt7925: defer the NAN joined-cluster event out of NAN_START Sean Wang
                   ` (5 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:02 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

Frames on the NAN DW-WTBL legitimately wait up to a full discovery
window period (524ms) in a paused queue before they air, but the TX
status tracking times a packet out MT_TX_STATUS_SKB_TIMEOUT (250ms)
after DMA completion and reports it as failed. wpa_supplicant treats
the false no-ack as a delivery failure and retransmits: a NIK follow-up
held to the DW is then re-queued every cycle, the peer receives one
copy per DW (56 copies measured over one 30s window), and the resulting
pairing-confirm storm poisons the pairing-verification state, which
never triggers and times out.

Extend the TX status lifetime for frames tracked on the DW wcid so the
status is reported from the actual transmission.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/tx.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c
index 72a4bc505aed..4197b8221f73 100644
--- a/drivers/net/wireless/mediatek/mt76/tx.c
+++ b/drivers/net/wireless/mediatek/mt76/tx.c
@@ -185,11 +185,20 @@ mt76_tx_status_skb_get(struct mt76_dev *dev, struct mt76_wcid *wcid, int pktid,
 		struct mt76_tx_cb *cb = mt76_tx_skb_cb(skb);
 
 		if (pktid >= 0) {
+			unsigned long timeout = MT_TX_STATUS_SKB_TIMEOUT;
+
 			if (!(cb->flags & MT_TX_CB_DMA_DONE))
 				continue;
 
-			if (time_is_after_jiffies(cb->jiffies +
-						   MT_TX_STATUS_SKB_TIMEOUT))
+			/* A frame held to the NAN DW legitimately waits up to
+			 * a full DW period (524ms) before it airs; timing its
+			 * status out earlier reports a false no-ack to the
+			 * one-shot NAN handshakes (PASN, NDP key install).
+			 */
+			if (wcid->nan_dw)
+				timeout = HZ;
+
+			if (time_is_after_jiffies(cb->jiffies + timeout))
 				continue;
 		}
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 18/23] wifi: mt76: mt7925: defer the NAN joined-cluster event out of NAN_START
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (16 preceding siblings ...)
  2026-09-27 21:02 ` [PATCH 17/23] wifi: mt76: mt7925: let TX status of DW-held NAN frames outlive the DW period Sean Wang
@ 2026-09-27 21:03 ` Sean Wang
  2026-09-27 21:03 ` [PATCH 19/23] wifi: mt76: mt7925: steer NAN handshake frames by committed-bitmap state Sean Wang
                   ` (4 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:03 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

The firmware can join an existing cluster from inside the passive scan it
runs at the start of NAN_START, so NAN_EVENT_ID_JOINED_CLUSTER lands while
the start path is still executing and ieee80211_vif_nan_started() is not
true yet. The handler dropped the event with a warning, leaving userspace
unaware of the cluster it had joined, and service discovery then never
completed: measured 4 failures in 12 NDP bring-ups on an MT7925 pair, each
one accompanied by exactly one dropped event on the joining side and none
on the side that created the cluster.

NAN_EVENT_ID_STARTED_CLUSTER already defers past NAN_START through
nan_deferred_work for the same reason; JOINED_CLUSTER is the other exit of
the same firmware warm-up branch and needs the same treatment. Queue it on
that work with its own cluster id, so a started and a joined event pending
at once cannot overwrite each other.

Fixes: a5487a682406 ("wifi: mt76: mt7925: add NAN MCU helpers")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 20 ++++++++++++++++++-
 .../net/wireless/mediatek/mt76/mt7925/nan.h   |  1 +
 drivers/net/wireless/mediatek/mt76/mt792x.h   |  1 +
 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index 715e080ce4ee..7aacfd6527e4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -709,8 +709,20 @@ mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv)
 		return;
 	}
 
+	/* JOINED_CLUSTER has the same race as STARTED_CLUSTER above: the
+	 * firmware joins an existing cluster from inside its start-up passive
+	 * scan, so the event can land while NAN_START is still running and
+	 * nan.started is not set yet. Defer it the same way instead of
+	 * dropping it - a lost join leaves userspace unaware of the cluster it
+	 * is in and service discovery never completes.
+	 */
 	if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) {
-		dev_warn(dev->mt76.dev, "nan: joined-cluster event but NAN not started\n");
+		spin_lock_bh(&dev->nan_deferred_lock);
+		memcpy(dev->nan_joined_cluster_id, cluster_id, ETH_ALEN);
+		set_bit(MT7925_NAN_DEFERRED_JOINED_CLUSTER,
+			&dev->nan_deferred_pending);
+		spin_unlock_bh(&dev->nan_deferred_lock);
+		ieee80211_queue_work(dev->mt76.hw, &dev->nan_deferred_work);
 		return;
 	}
 
@@ -732,6 +744,7 @@ mt7925_nan_deferred_work(struct work_struct *work)
 {
 	struct mt792x_dev *dev = container_of(work, struct mt792x_dev,
 					      nan_deferred_work);
+	u8 joined_cluster_id[ETH_ALEN];
 	struct ieee80211_vif *vif;
 	unsigned long pending;
 	u8 cluster_id[ETH_ALEN];
@@ -740,6 +753,7 @@ mt7925_nan_deferred_work(struct work_struct *work)
 	pending = dev->nan_deferred_pending;
 	dev->nan_deferred_pending = 0;
 	memcpy(cluster_id, dev->nan_started_cluster_id, ETH_ALEN);
+	memcpy(joined_cluster_id, dev->nan_joined_cluster_id, ETH_ALEN);
 	spin_unlock_bh(&dev->nan_deferred_lock);
 
 	if (!pending)
@@ -753,6 +767,10 @@ mt7925_nan_deferred_work(struct work_struct *work)
 	if (test_bit(MT7925_NAN_DEFERRED_STARTED_CLUSTER, &pending))
 		ieee80211_nan_cluster_joined(vif, cluster_id, true, GFP_KERNEL);
 
+	if (test_bit(MT7925_NAN_DEFERRED_JOINED_CLUSTER, &pending))
+		ieee80211_nan_cluster_joined(vif, joined_cluster_id, false,
+					     GFP_KERNEL);
+
 	if (test_bit(MT7925_NAN_DEFERRED_SCHED_UPDATE_DONE, &pending))
 		ieee80211_nan_sched_update_done(vif);
 out:
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
index 415fd6b6f0e6..32740e3769d6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.h
@@ -143,6 +143,7 @@ enum mt7925_nan_key_type {
  */
 enum mt7925_nan_deferred_event {
 	MT7925_NAN_DEFERRED_STARTED_CLUSTER,
+	MT7925_NAN_DEFERRED_JOINED_CLUSTER,
 	MT7925_NAN_DEFERRED_SCHED_UPDATE_DONE,
 };
 /* NAN 4.0 Table 79. Device Capability attribute format, Supported Bands */
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index ee462eab4028..8e1588970ac1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -446,6 +446,7 @@ struct mt792x_dev {
 	spinlock_t nan_deferred_lock;
 	unsigned long nan_deferred_pending;
 	u8 nan_started_cluster_id[ETH_ALEN];
+	u8 nan_joined_cluster_id[ETH_ALEN];
 };
 
 static inline struct mt792x_bss_conf *
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 19/23] wifi: mt76: mt7925: steer NAN handshake frames by committed-bitmap state
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (17 preceding siblings ...)
  2026-09-27 21:03 ` [PATCH 18/23] wifi: mt76: mt7925: defer the NAN joined-cluster event out of NAN_START Sean Wang
@ 2026-09-27 21:03 ` Sean Wang
  2026-09-27 21:03 ` [PATCH 20/23] wifi: mt76: mt7925: disable only the RX NAPI instances that exist Sean Wang
                   ` (3 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:03 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

Unencrypted unicast NAN management is held to the discovery window on the
DW-WTBL, because a peer whose NDL is not yet confirmed is only reliably
awake there. That is the right call while nothing is known about the
peer, but it stays in force for the whole session, and the DW is a narrow
place to live: it opens for 16 ms every 512 ms and is shared with the
discovery queue, so roughly four transmit opportunities exist inside the
two seconds a Data Path Response has to complete. Under load the
handshake loses that race. Routing the same frames through the peer STA
WTBL was what made NDP setup reliable with a partial availability bitmap
in the first place, since the firmware then paces them by the committed
window instead of bursting blindly.

Neither placement is right on its own: the peer WTBL is only usable once
the firmware actually holds that peer's committed bitmap. A peer station
existing in mac80211 does not imply that - until the CRB download lands,
the peer's availability gate has no slots to open and a frame steered
there parks behind the pause until the session is torn down.

Track the committed state per peer. mt7925_nan_fill_crb_committed()
returns the number of committed slots it programmed, and
mt7925_nan_update_crb_tlv() latches whether that count was non-zero into
nan_sched.has_commit; the allocation rollback in
mt792x_nan_set_peer_schedule() and the peer record removal in
mt792x_nan_set_peer_rec() clear it again. Frames to a peer holding a
committed bitmap keep that peer's WTBL and are served in the slots it
has promised to be awake for; frames to a peer without one, and frames
with no station resolved, keep riding the DW-WTBL. Secured frames are
untouched and keep their own WTBL and key.

Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 30 +++++++++++----
 .../wireless/mediatek/mt76/mt7925/pci_mac.c   | 37 +++++++------------
 drivers/net/wireless/mediatek/mt76/mt792x.h   |  1 +
 3 files changed, 38 insertions(+), 30 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index 7aacfd6527e4..77a91a9ee4d4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -1446,14 +1446,14 @@ static int mt7925_nan_peer_cap_tlv(struct sk_buff *skb,
 	return 0;
 }
 
-static void
+static u32
 mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv,
 			      struct ieee80211_nan_peer_sched *sched)
 {
-	u32 m, slot;
+	u32 m, slot, total = 0;
 
 	if (!sched)
-		return;
+		return 0;
 
 	for (m = 0; m < CFG80211_NAN_MAX_PEER_MAPS &&
 	     m < NAN_TIMELINE_MGMT_SIZE; m++) {
@@ -1479,10 +1479,14 @@ mt7925_nan_fill_crb_committed(struct mt7925_nan_sched_update_crb_tlv *crb_tlv,
 			if (!ch || !ch->chanctx_conf)
 				continue;
 
+			total++;
+
 			for (dw = 0; dw < NAN_TOTAL_DW; dw++)
 				tl->avail_map[dw] |= cpu_to_le32(BIT(slot));
 		}
 	}
+
+	return total;
 }
 
 static int mt7925_nan_update_crb_tlv(struct sk_buff *skb,
@@ -1507,9 +1511,10 @@ static int mt7925_nan_update_crb_tlv(struct sk_buff *skb,
 	crb_tlv->is_use_ranging = false;
 	crb_tlv->comm_ndc_ctrl.is_valid = false;
 
-	mt7925_nan_fill_crb_committed(crb_tlv, sta->nan_sched);
-
-	return 0;
+	/* Returns the number of committed slots programmed; the caller latches
+	 * has_commit only once the command has actually reached firmware.
+	 */
+	return mt7925_nan_fill_crb_committed(crb_tlv, sta->nan_sched);
 }
 
 static int
@@ -1552,6 +1557,7 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
 	struct mt792x_nan *nan;
 	struct mt76_dev *mdev;
 	struct sk_buff *skb;
+	int committed;
 	int ret;
 
 	if (!dev || !sta)
@@ -1590,7 +1596,8 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
 		}
 	}
 
-	if (mt7925_nan_update_crb_tlv(skb, sta, msta)) {
+	committed = mt7925_nan_update_crb_tlv(skb, sta, msta);
+	if (committed < 0) {
 		ret = -ENOMEM;
 		goto free_skb;
 	}
@@ -1603,6 +1610,13 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
 	if (ret && idx_allocated)
 		goto clear_idx;
 
+	/* Latch only now: firmware holds this peer's committed bitmap, so its
+	 * availability gate has slots to open and unencrypted NAF may be
+	 * steered at the peer WTBL. Cleared when the CRB is torn down.
+	 */
+	if (!ret)
+		msta->nan_sched.has_commit = committed > 0;
+
 	return ret;
 
 free_skb:
@@ -1613,6 +1627,7 @@ int mt792x_nan_set_peer_schedule(struct mt792x_dev *dev,
 clear_idx:
 	clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap);
 	msta->nan_sched.idx_assigned = false;
+	msta->nan_sched.has_commit = false;
 
 	return ret;
 }
@@ -1676,6 +1691,7 @@ int mt792x_nan_set_peer_rec(struct mt76_dev *mdev,
 
 	clear_bit(msta->nan_sched.sch_idx, &nan->conn_bitmap);
 	msta->nan_sched.idx_assigned = false;
+	msta->nan_sched.has_commit = false;
 
 	return 0;
 }
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
index 5bdf3ebe6aef..6e9daf96da88 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
@@ -33,16 +33,15 @@ int mt7925e_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
 	 * full retry budget on an unACKable multicast RA, so one frame eats a
 	 * whole DW window and the publish SDF queue backlogs.
 	 *
-	 * Unencrypted unicast to a peer we have no station for is first
-	 * contact (SDF, NDP request): the DW is the only rendezvous, so it
-	 * goes on the DW-WTBL and waits for it.
-	 *
-	 * Once its schedule is known - the peer station exists, which is also
-	 * when firmware holds its committed bitmap - the frame belongs on that
-	 * station instead. Firmware then airs it inside the peer's own
-	 * committed slots, where the peer is awake and the medium is not the
-	 * DW pile-up, and follows the FAW onto whatever channel the window
-	 * actually uses rather than being pinned to the DW channel.
+	 * Unencrypted unicast is handshake traffic (SDF follow-up, NDP
+	 * request/response, pairing bootstrap). Until firmware holds the
+	 * peer's committed bitmap its availability gate has nothing to open,
+	 * so a frame steered at the peer WTBL would park behind the BY_NAN
+	 * pause; such frames ride the DW-WTBL, which firmware unpauses every
+	 * DW - the one rendezvous both peers must be awake for. Once the CRB
+	 * download latches has_commit, the frame keeps the peer's own WTBL
+	 * and firmware serves it in the committed slots, which a 2-second
+	 * handshake deadline needs (the DW alone offers only ~4 shots).
 	 *
 	 * Secured frames belong to an established peer and keep its own WTBL
 	 * and key.
@@ -52,24 +51,16 @@ int mt7925e_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr,
 		struct mt792x_vif *mvif = (void *)vif->drv_priv;
 		bool mcast = is_multicast_ether_addr(hdr->addr1);
 
+		struct mt792x_sta *peer = (!mcast && sta) ?
+			(struct mt792x_sta *)sta->drv_priv : NULL;
+
 		if (mcast && wcid == &mvif->nan_dw_wcid) {
 			wcid = &mvif->sta.deflink.wcid;
 		} else if (!mcast && !key &&
+			   !(peer && peer->nan_sched.has_commit) &&
 			   mvif->nan_dw_wcid.idx &&
 			   mvif->nan_dw_wcid.idx < MT792x_WTBL_STA) {
-			struct ieee80211_sta *psta;
-			struct mt792x_sta *pmsta;
-
-			rcu_read_lock();
-			psta = ieee80211_find_sta(vif, hdr->addr1);
-			pmsta = psta ? (struct mt792x_sta *)psta->drv_priv :
-				       NULL;
-
-			if (pmsta && pmsta->deflink.wcid.idx)
-				wcid = &pmsta->deflink.wcid;
-			else
-				wcid = &mvif->nan_dw_wcid;
-			rcu_read_unlock();
+			wcid = &mvif->nan_dw_wcid;
 		}
 	}
 
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index 8e1588970ac1..6ed0282775ba 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -133,6 +133,7 @@ struct mt792x_sta_nan_sched {
 	u16 committed_dw;
 	u32 sch_idx;
 	bool idx_assigned;
+	bool has_commit;	/* last CRB carried a non-empty committed map */
 	unsigned long ndp_ctx_bitmap;
 	bool ndp_ctx_assigned;
 	u8 ndp_ctx_id;		/* assigned NDP context ID (for NDI sta) */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 20/23] wifi: mt76: mt7925: disable only the RX NAPI instances that exist
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (18 preceding siblings ...)
  2026-09-27 21:03 ` [PATCH 19/23] wifi: mt76: mt7925: steer NAN handshake frames by committed-bitmap state Sean Wang
@ 2026-09-27 21:03 ` Sean Wang
  2026-09-27 21:03 ` [PATCH 21/23] wifi: mt76: mt7925: stop queueing resets once the device is being removed Sean Wang
                   ` (2 subsequent siblings)
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:03 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

mt7925e_mac_reset() picks the RX NAPI instances to disable from the
interrupt masks, but the instances themselves are created per allocated
RX queue by mt76_dma_init(). On mt7925 the two do not agree: the tx-done
ring is only described by the mt7928 DMA layout, so q_rx[MT_RXQ_MCU_WA]
is never allocated and never gets a NAPI, while mt7925_irq_map still
carries rx.wm2_complete_mask.

A MAC reset therefore calls napi_disable() on an instance whose
net_device pointer is NULL. The fault happens inside napi_disable()
while the reset work holds the RTNL, so networking goes down with it and
the machine is left answering ping with no usable userspace; only a
power cycle recovers it. Repeated NAN data path setup and teardown hits
this reliably on a busy channel, where MAC resets are frequent.

Iterate the RX queues instead, the same way the restore path further
down re-enables them, so only instances that were actually added are
touched.

Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 .../wireless/mediatek/mt76/mt7925/pci_mac.c   | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
index 6e9daf96da88..32463ef30ebb 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c
@@ -127,12 +127,19 @@ int mt7925e_mac_reset(struct mt792x_dev *dev)
 	mt76_txq_schedule_all(&dev->mphy);
 
 	mt76_worker_disable(&dev->mt76.tx_worker);
-	if (irq_map->rx.data_complete_mask)
-		napi_disable(&dev->mt76.napi[MT_RXQ_MAIN]);
-	if (irq_map->rx.wm_complete_mask)
-		napi_disable(&dev->mt76.napi[MT_RXQ_MCU]);
-	if (irq_map->rx.wm2_complete_mask)
-		napi_disable(&dev->mt76.napi[MT_RXQ_MCU_WA]);
+
+	/*
+	 * Disable the RX NAPI instances that were actually created. They are
+	 * added per allocated RX queue by mt76_dma_init(), while the interrupt
+	 * masks describe what the hardware is able to raise - on mt7925 the
+	 * tx-done queue is never allocated (only mt7928 defines that ring), so
+	 * its NAPI has no net_device and napi_disable() faults on it while
+	 * holding the RTNL, wedging the whole machine. Iterate the queues, the
+	 * same way the restore path below re-enables them.
+	 */
+	mt76_for_each_q_rx(&dev->mt76, i)
+		napi_disable(&dev->mt76.napi[i]);
+
 	if (irq_map->tx.all_complete_mask)
 		napi_disable(&dev->mt76.tx_napi);
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 21/23] wifi: mt76: mt7925: stop queueing resets once the device is being removed
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (19 preceding siblings ...)
  2026-09-27 21:03 ` [PATCH 20/23] wifi: mt76: mt7925: disable only the RX NAPI instances that exist Sean Wang
@ 2026-09-27 21:03 ` Sean Wang
  2026-09-27 21:03 ` [PATCH 22/23] wifi: mt76: mt7925: bound the lifetime of NAN unicast management frames Sean Wang
  2026-09-27 21:03 ` [PATCH 23/23] wifi: mt76: mt7925: always deliver the joined-cluster event through the deferred work Sean Wang
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:03 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

mt7925e_unregister_device() cancels reset_work before it tears the device
down, but every source that can raise a reset stays live past that point:
the MCU command path, the system error recovery and interrupt handlers,
and the MAC watchdog all
call mt792x_reset(), and the interrupt tasklet is only disabled at the very
end of the function. A reset raised in that window is queued behind the
cancel and then runs while the device is being dismantled -
mt7925_mac_reset_work() sets hw_full_reset, stops the queues and
cancels the PM works before it can notice that the device is gone.

mt792x_reset() already bails out on !hw_init_done, and that flag has no
other consumer: it is set once during hardware init and read only there.
Clear it at the top of the teardown so no reset can be queued for its whole
duration.

Measured on rauru with kprobes on mt792x_reset() (queue), on
mt7925_mac_reset_work() (execution) and on mt76_unregister_device(), which
runs immediately after the cancel and so serves as the anchor, while
chip_reset was written in a loop across the module unload:

  before: 205 resets queued and 415 mt7925_mac_reset_work() runs after the
          anchor, that is after cancel_work_sync() had already returned
  after:  no run after the anchor; mt792x_reset() is still entered but
          returns early

A chip_reset on a running device still triggers a reset as before, and
unload/reload cycles stay clean.

Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index 09153d624fd5..f7b57a82f2d4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -46,6 +46,16 @@ static void mt7925e_unregister_device(struct mt792x_dev *dev)
 	if (dev->phy.chip_cap & MT792x_CHIP_CAP_WF_RF_PIN_CTRL_EVT_EN)
 		wiphy_rfkill_stop_polling(hw->wiphy);
 
+	/* Stop new resets from being queued for the rest of the teardown.
+	 * mt792x_reset() bails out on !hw_init_done, which is otherwise only
+	 * set once at init, so clearing it here closes the window in which an
+	 * MCU timeout, a system error recovery interrupt or the watchdog
+	 * could still schedule
+	 * reset_work behind the cancel below and run it against a device that
+	 * is already being dismantled.
+	 */
+	dev->hw_init_done = false;
+
 	cancel_work_sync(&dev->reset_work);
 	cancel_work_sync(&dev->init_work);
 	mt76_unregister_device(&dev->mt76);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 22/23] wifi: mt76: mt7925: bound the lifetime of NAN unicast management frames
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (20 preceding siblings ...)
  2026-09-27 21:03 ` [PATCH 21/23] wifi: mt76: mt7925: stop queueing resets once the device is being removed Sean Wang
@ 2026-09-27 21:03 ` Sean Wang
  2026-09-27 21:03 ` [PATCH 23/23] wifi: mt76: mt7925: always deliver the joined-cluster event through the deferred work Sean Wang
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:03 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

A NAN unicast management frame sent to a peer that has disappeared never
comes back to the host. The frame sits on the gated DW-WTBL queue, the
gate opens at every discovery window and the hardware retransmits, but
the retry budget is re-armed each time the queue is released, so it
never runs out and no TX status is ever generated. The host keeps the
skb in its status table indefinitely, the supplicant waits for a TX
status that does not arrive, and every later management frame on that
queue lines up behind the dead one. In practice a single lost peer stalls
all further NAN handshakes on the interface.

Set MAX_TX_TIME in the TXD for these frames so the hardware bounds them
in time rather than in attempts. When the limit expires the frame is
dropped with the lifetime-expired bit set, the host sees a no-ACK
status, and the queue drains. Forty-six units of 64 TU is about 3 s, six
discovery windows, which is beyond the 2 s NDP handshake deadline so a
frame that still has a chance to be delivered is never cut short.

Fixes: 0f3605e4f8de ("wifi: mt76: mt7925: wire up NAN operations")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 drivers/net/wireless/mediatek/mt76/mt7925/mac.c    | 12 +++++++++++-
 drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h |  3 +++
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index f19d0451f373..75d2081b0920 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -815,8 +815,18 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi,
 		struct ieee80211_hdr *nan_hdr = (struct ieee80211_hdr *)skb->data;
 
 		if (ieee80211_is_mgmt(nan_hdr->frame_control) &&
-		    !is_multicast_ether_addr(nan_hdr->addr1))
+		    !is_multicast_ether_addr(nan_hdr->addr1)) {
 			val = FIELD_PREP(MT_TXD3_REM_TX_COUNT, 31);
+
+			/* The retry budget alone never finalises a frame whose
+			 * peer has gone: the DW-WTBL gate re-arms it every DW,
+			 * so firmware holds the frame indefinitely and the host
+			 * never gets a TX status. Bound it in time instead -
+			 * about six DWs, beyond the 2 s handshake deadline.
+			 */
+			txwi[2] |= cpu_to_le32(FIELD_PREP(MT_TXD2_MAX_TX_TIME,
+							  NAN_MGMT_MAX_TX_TIME));
+		}
 	}
 
 	if (key)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index 33782d9ba9ed..bc335740638b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -26,6 +26,9 @@
 #define MT7925_SKU_MAX_DELTA_IDX	MT7925_SKU_RATE_NUM
 #define MT7925_SKU_TABLE_SIZE		(MT7925_SKU_RATE_NUM + 1)
 
+/* NAN unicast mgmt TXD MAX_TX_TIME, units of 64 TU: 46 is about 3 s */
+#define NAN_MGMT_MAX_TX_TIME		46
+
 #define MCU_UNI_EVENT_ROC  0x27
 
 #define HIF_TRAFFIC_IDLE 0x2
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* [PATCH 23/23] wifi: mt76: mt7925: always deliver the joined-cluster event through the deferred work
  2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
                   ` (21 preceding siblings ...)
  2026-09-27 21:03 ` [PATCH 22/23] wifi: mt76: mt7925: bound the lifetime of NAN unicast management frames Sean Wang
@ 2026-09-27 21:03 ` Sean Wang
  22 siblings, 0 replies; 27+ messages in thread
From: Sean Wang @ 2026-09-27 21:03 UTC (permalink / raw)
  To: nbd
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Jacobs Wu, Sean Wang

From: Jacobs Wu <jacobs.wu@mediatek.com>

A JOINED_CLUSTER arriving while nan.started is already set is sent to
mac80211 directly, but a deferred STARTED_CLUSTER may still be in flight:
NAN_START holds the wiphy lock, so the deferred work has often already
snapshotted and cleared its pending bits and now sleeps on that lock
before it can deliver STARTED. The directly sent JOINED then reaches
userspace first and the late STARTED overwrites it, so the supplicant
keeps the stale self cluster for the whole session and its RX filters
reject the real cluster's SDFs (10 forced-split bring-ups out of ~600
showed this inversion on the bench).

Route JOINED through the deferred work unconditionally. The work always
delivers STARTED before JOINED, so firmware event order is preserved no
matter when the events land, and the extra scheduling hop on an idle
work is negligible for this once-per-session event.

Fixes: a5487a682406 ("wifi: mt76: mt7925: add NAN MCU helpers")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Jacobs Wu <jacobs.wu@mediatek.com>
---
 .../net/wireless/mediatek/mt76/mt7925/nan.c   | 36 +++++++++----------
 1 file changed, 17 insertions(+), 19 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
index 77a91a9ee4d4..4e3c531a8d4e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
@@ -709,31 +709,29 @@ mt7925_nan_mcu_handle_de_event(struct mt792x_dev *dev, struct tlv *tlv)
 		return;
 	}
 
-	/* JOINED_CLUSTER has the same race as STARTED_CLUSTER above: the
-	 * firmware joins an existing cluster from inside its start-up passive
-	 * scan, so the event can land while NAN_START is still running and
-	 * nan.started is not set yet. Defer it the same way instead of
-	 * dropping it - a lost join leaves userspace unaware of the cluster it
-	 * is in and service discovery never completes.
-	 */
-	if (!dev->nan_vif || !ieee80211_vif_nan_started(dev->nan_vif)) {
-		spin_lock_bh(&dev->nan_deferred_lock);
-		memcpy(dev->nan_joined_cluster_id, cluster_id, ETH_ALEN);
-		set_bit(MT7925_NAN_DEFERRED_JOINED_CLUSTER,
-			&dev->nan_deferred_pending);
-		spin_unlock_bh(&dev->nan_deferred_lock);
-		ieee80211_queue_work(dev->mt76.hw, &dev->nan_deferred_work);
-		return;
-	}
-
 	dev_dbg(dev->mt76.dev, "nan: anchor_master_rank=%*phN\n",
 		NAN_ANCHOR_MASTER_RANK_NUM, de_evt->anchor_master_rank);
 
 	dev_dbg(dev->mt76.dev, "nan: own_nmi=%pM master_nmi=%pM\n",
 		de_evt->own_nmi, de_evt->master_nmi);
 
-	/* joined an existing cluster, not a self-anchored new one */
-	ieee80211_nan_cluster_joined(dev->nan_vif, cluster_id, false, GFP_KERNEL);
+	/* JOINED_CLUSTER has the same race as STARTED_CLUSTER above (the
+	 * firmware joins from inside its start-up passive scan, so the event
+	 * can land while NAN_START is still running), and it can also race
+	 * the work that is about to deliver a deferred STARTED_CLUSTER -
+	 * userspace keeps the last event it sees, so a directly sent JOINED
+	 * would be overwritten by the stale self cluster for the whole
+	 * session. Always deliver JOINED through the work, which sends
+	 * STARTED before JOINED.
+	 */
+	dev_dbg(dev->mt76.dev, "nan: deferring JOINED_CLUSTER cluster=%pM\n",
+		cluster_id);
+	spin_lock_bh(&dev->nan_deferred_lock);
+	memcpy(dev->nan_joined_cluster_id, cluster_id, ETH_ALEN);
+	set_bit(MT7925_NAN_DEFERRED_JOINED_CLUSTER,
+		&dev->nan_deferred_pending);
+	spin_unlock_bh(&dev->nan_deferred_lock);
+	ieee80211_queue_work(dev->mt76.hw, &dev->nan_deferred_work);
 }
 
 /* Runs the deferred NAN MCU events in process context; takes wiphy_lock
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 27+ messages in thread

* Re: [PATCH 03/23] wifi: mt76: mt7925: implement NAN MAC address randomization
  2026-09-27 21:02 ` [PATCH 03/23] wifi: mt76: mt7925: implement NAN MAC address randomization Sean Wang
@ 2026-10-06  9:18   ` Felix Fietkau
  0 siblings, 0 replies; 27+ messages in thread
From: Felix Fietkau @ 2026-10-06  9:18 UTC (permalink / raw)
  To: Sean Wang
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

On 27.09.26 23:02, Sean Wang wrote:
> From: Chengwei Yu <chengwei.yu@mediatek.com>
> 
> Add mt7925_nan_start/stop_mac_randomization() using a per-vif
> delayed_work and period stored in struct mt792x_nan.
> 
> Per-vif (not per-dev) because the NAN iface combinations allow up
> to two concurrent NAN_DATA vifs; a single dev-wide field would
> silently lose randomization for the second NDI or let it overwrite
> the first vif's pending work.
> 
> delayed_work (not timer_list) because the work function calls
> mt7925_nan_set_nmi/ndi_address() which invokes mt76_mcu_send_msg()
> with wait_resp=true; that can sleep, which is not allowed in softirq
> context. This is consistent with scan_work, mlo_pm_work and ps_work
> in this driver.
> 
> stop_mac_randomization() must be called without holding the dev
> mutex since the work function acquires it. No caller is wired up
> yet; the next patch does that in add/remove_interface.
> 
> Co-developed-by: Sean Wang <sean.wang@mediatek.com>
> Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
> ---
>   .../net/wireless/mediatek/mt76/mt7925/nan.c   | 59 +++++++++++++++++++
>   .../net/wireless/mediatek/mt76/mt7925/nan.h   |  7 +++
>   drivers/net/wireless/mediatek/mt76/mt792x.h   |  9 +++
>   3 files changed, 75 insertions(+)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
> index abbd3f7b081c..0b94bd4005d4 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
> @@ -745,6 +745,65 @@ int mt7925_nan_set_ndi_address(struct ieee80211_vif *vif,
> [...]
> +void mt7925_nan_start_mac_randomization(struct ieee80211_vif *vif,
> +					u32 period_sec)
> +{
> +	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
> +	struct mt792x_dev *dev = mvif->phy->dev;
> +
> +	if (!period_sec)
> +		return;
> +
> +	mvif->nan.mac_rand_period_sec = period_sec;
> +
> +	ieee80211_queue_delayed_work(mt76_hw(dev), &mvif->nan.mac_rand_work, 0);
> +}
This function is added, but not used in this series.

- Felix

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH 08/23] wifi: mt76: mt7925: add NDC-aware TX GTK table for NAN_DATA
  2026-09-27 21:02 ` [PATCH 08/23] wifi: mt76: mt7925: add NDC-aware TX GTK table for NAN_DATA Sean Wang
@ 2026-10-06  9:20   ` Felix Fietkau
  0 siblings, 0 replies; 27+ messages in thread
From: Felix Fietkau @ 2026-10-06  9:20 UTC (permalink / raw)
  To: Sean Wang
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

On 27.09.26 23:02, Sean Wang wrote:
> From: Chengwei Yu <chengwei.yu@mediatek.com>
> 
> NAN TX GTK is NDC-scoped: every station in the same NAN Data Cluster
> shares one key, and a single NAN_DATA vif can join multiple NDCs
> concurrently. Add mt792x_nan.nan_tx_gtk_table[] (up to
> MT792X_MAX_NAN_NDC = 8 RCU on-demand entries, one per NDC) and route
> interface-directed NAN_DATA multicast through the matching WTBL in
> mt792x_tx(), falling back to the interface WTBL when no entry exists.
> 
> Teardown lives in mt7925_remove_interface(), not the shared
> mt792x_remove_interface(): NAN_DATA vifs are mt7925-only so cleanup
> in the shared path would never run.
> 
> NOTE: NDC selection from skb context is not yet implemented; the
> lookup always picks the first installed entry until mt7925_nan_set_key()
> populates the table.
> 
> Co-developed-by: Sean Wang <sean.wang@mediatek.com>
> Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
I don't see anything in the code that fills nan_tx_gtk_table.
Am I missing something here?

Thanks,

- Felix

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH 02/23] wifi: mt76: mt7925: add NMI/NDI MAC address setter helpers
  2026-09-27 21:02 ` [PATCH 02/23] wifi: mt76: mt7925: add NMI/NDI MAC address setter helpers Sean Wang
@ 2026-10-06  9:25   ` Felix Fietkau
  0 siblings, 0 replies; 27+ messages in thread
From: Felix Fietkau @ 2026-10-06  9:25 UTC (permalink / raw)
  To: Sean Wang
  Cc: linux-wireless, linux-mediatek, yu-ching.liu, jenhao.yang,
	posh.sun, Chengwei Yu, Sean Wang

On 27.09.26 23:02, Sean Wang wrote:
> From: Chengwei Yu <chengwei.yu@mediatek.com>
> 
> Add mt7925_nan_set_nmi_address() and mt7925_nan_set_ndi_address() for
> runtime MAC address changes: NMI goes through the MCU (firmware owns
> the hardware address), NDI updates vif->addr directly.
> 
> Co-developed-by: Sean Wang <sean.wang@mediatek.com>
> Signed-off-by: Sean Wang <sean.wang@mediatek.com>
> Signed-off-by: Chengwei Yu <chengwei.yu@mediatek.com>
> ---
>   .../net/wireless/mediatek/mt76/mt7925/nan.c   | 48 +++++++++++++++++++
>   .../net/wireless/mediatek/mt76/mt7925/nan.h   |  8 ++++
>   2 files changed, 56 insertions(+)
> 
> diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
> index 8a59f7b1aee2..abbd3f7b081c 100644
> --- a/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
> +++ b/drivers/net/wireless/mediatek/mt76/mt7925/nan.c
> @@ -697,6 +697,54 @@ void mt7925_nan_mcu_event(struct mt792x_dev *dev, struct sk_buff *skb)
>   	}
>   }
>   
> +int mt7925_nan_set_nmi_address(struct ieee80211_vif *vif,
> +			       struct mt792x_dev *dev,
> +			       const u8 *mac_address)
> +{
> +	struct mt76_dev *mdev = &dev->mt76;
> +	struct {
> +		u8 rsv[4];
> +		struct mt7925_nan_nmi_addr_tlv nmi_addr_tlv;
> +	} nmi_cmd = {
> +		.nmi_addr_tlv = {
> +			.tag = cpu_to_le16(NAN_UNI_CMD_CHANGE_NMI_ADDRESS),
> +			.len = cpu_to_le16(sizeof(struct mt7925_nan_nmi_addr_tlv)),
> +		},
> +	};
> +
> +	if (!dev || !vif || !mac_address)
> +		return -EINVAL;
> +
> +	if (is_zero_ether_addr(mac_address) ||
> +	    is_multicast_ether_addr(mac_address))
> +		return -EINVAL;
> +
> +	memcpy(nmi_cmd.nmi_addr_tlv.nmi_addr, mac_address, ETH_ALEN);
> +
> +	return mt76_mcu_send_msg(mdev, MCU_UNI_CMD(NAN), &nmi_cmd,
> +				 sizeof(nmi_cmd), true);
> +}
> +
> +int mt7925_nan_set_ndi_address(struct ieee80211_vif *vif,
> +			       struct mt792x_dev *dev,
> +			       const u8 *mac_address)
> +{
> +	if (!dev || !vif || !mac_address)
> +		return -EINVAL;
> +
> +	if (is_zero_ether_addr(mac_address) ||
> +	    is_multicast_ether_addr(mac_address))
> +		return -EINVAL;
> +
> +	/*
> +	 * The NDI address is not managed by firmware; it is the vif's own
> +	 * hardware address, so updating it locally is sufficient.
> +	 */
> +	memcpy(vif->addr, mac_address, ETH_ALEN);
What about bss_conf->addr and the netdev addr. Do those need to be updated?

- Felix

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2026-10-06  9:26 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-27 21:02 [PATCH 00/23] wifi: mt76: mt7925: add NAN security and improve NDP reliability (part 2) Sean Wang
2026-09-27 21:02 ` [PATCH 01/23] wifi: mt76: mt7925: make NMI address TLV tail padding explicit Sean Wang
2026-09-27 21:02 ` [PATCH 02/23] wifi: mt76: mt7925: add NMI/NDI MAC address setter helpers Sean Wang
2026-10-06  9:25   ` Felix Fietkau
2026-09-27 21:02 ` [PATCH 03/23] wifi: mt76: mt7925: implement NAN MAC address randomization Sean Wang
2026-10-06  9:18   ` Felix Fietkau
2026-09-27 21:02 ` [PATCH 04/23] wifi: mt76: mt7925: wire up NAN MAC randomization lifecycle Sean Wang
2026-09-27 21:02 ` [PATCH 05/23] wifi: mt76: mt7925: add NAN low power event control on suspend/resume Sean Wang
2026-09-27 21:02 ` [PATCH 06/23] wifi: mt76: mt7925: implement NAN key management MCU command Sean Wang
2026-09-27 21:02 ` [PATCH 07/23] wifi: mt76: mt7925: add per-peer RX IGTK/BIGTK and RX GTK WTBLs Sean Wang
2026-09-27 21:02 ` [PATCH 08/23] wifi: mt76: mt7925: add NDC-aware TX GTK table for NAN_DATA Sean Wang
2026-10-06  9:20   ` Felix Fietkau
2026-09-27 21:02 ` [PATCH 09/23] wifi: mt76: mt7925: implement mt7925_nan_set_key for NAN security Sean Wang
2026-09-27 21:02 ` [PATCH 10/23] wifi: mt76: mt7925: install NAN BIP keys standalone in sta_key_tlv Sean Wang
2026-09-27 21:02 ` [PATCH 11/23] wifi: mt76: mt7925: do not disable RX NAPI twice on unload Sean Wang
2026-09-27 21:02 ` [PATCH 12/23] wifi: mt76: mt7925: assign the interface WTBL to the NAN management TXQ Sean Wang
2026-09-27 21:02 ` [PATCH 13/23] wifi: mt76: mt7925: do not deactivate shared NAN BSS on peer STA removal Sean Wang
2026-09-27 21:02 ` [PATCH 14/23] wifi: mt76: gate NAN unicast mgmt by peer availability via STA queues Sean Wang
2026-09-27 21:02 ` [PATCH 15/23] wifi: mt76: mt7925: hold NAN mgmt TX to the discovery window Sean Wang
2026-09-27 21:02 ` [PATCH 16/23] wifi: mt76: mt7925: double the retry budget for NAN unicast management Sean Wang
2026-09-27 21:02 ` [PATCH 17/23] wifi: mt76: mt7925: let TX status of DW-held NAN frames outlive the DW period Sean Wang
2026-09-27 21:03 ` [PATCH 18/23] wifi: mt76: mt7925: defer the NAN joined-cluster event out of NAN_START Sean Wang
2026-09-27 21:03 ` [PATCH 19/23] wifi: mt76: mt7925: steer NAN handshake frames by committed-bitmap state Sean Wang
2026-09-27 21:03 ` [PATCH 20/23] wifi: mt76: mt7925: disable only the RX NAPI instances that exist Sean Wang
2026-09-27 21:03 ` [PATCH 21/23] wifi: mt76: mt7925: stop queueing resets once the device is being removed Sean Wang
2026-09-27 21:03 ` [PATCH 22/23] wifi: mt76: mt7925: bound the lifetime of NAN unicast management frames Sean Wang
2026-09-27 21:03 ` [PATCH 23/23] wifi: mt76: mt7925: always deliver the joined-cluster event through the deferred work Sean Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox