Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH v2 wireless-next 07/14] wifi: mac80211_hwsim: add NAN PHY capabilities
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Daniel Gabay <daniel.gabay@intel.com>

Add static HT, VHT and HE PHY capabilities to the NAN capabilities
structure. These are based on the existing band capability structures
and initialization in mac80211_hwsim.

The NAN PHY capabilities are used by mac80211 and nl80211 to
advertise device capabilities for NAN data interfaces.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../wireless/virtual/mac80211_hwsim_main.c    | 102 ++++++++++++++++--
 1 file changed, 94 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 3397acfe61c8..fc940b38c52c 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5412,6 +5412,95 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = {
 #endif
 };
 
+#define HWSIM_VHT_MCS_MAP				\
+	(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 6 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 8 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 |		\
+	 IEEE80211_VHT_MCS_SUPPORT_0_9 << 14)
+
+static const struct ieee80211_sta_ht_cap hwsim_nan_ht_cap = {
+	.ht_supported = true,
+	.cap = IEEE80211_HT_CAP_SUP_WIDTH_20_40 |
+	       IEEE80211_HT_CAP_GRN_FLD |
+	       IEEE80211_HT_CAP_SGI_20 |
+	       IEEE80211_HT_CAP_SGI_40 |
+	       IEEE80211_HT_CAP_DSSSCCK40,
+	.ampdu_factor = 0x3,
+	.ampdu_density = 0x6,
+	.mcs = {
+		.rx_mask = { 0xff, 0xff },
+		.tx_params = IEEE80211_HT_MCS_TX_DEFINED,
+	},
+};
+
+static const struct ieee80211_sta_vht_cap hwsim_nan_vht_cap = {
+	.vht_supported = true,
+	.cap = IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 |
+	       IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ |
+	       IEEE80211_VHT_CAP_RXLDPC |
+	       IEEE80211_VHT_CAP_SHORT_GI_80 |
+	       IEEE80211_VHT_CAP_SHORT_GI_160 |
+	       IEEE80211_VHT_CAP_TXSTBC |
+	       IEEE80211_VHT_CAP_RXSTBC_4 |
+	       IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK,
+	.vht_mcs = {
+		.rx_mcs_map = cpu_to_le16(HWSIM_VHT_MCS_MAP),
+		.tx_mcs_map = cpu_to_le16(HWSIM_VHT_MCS_MAP),
+	},
+};
+
+static const struct ieee80211_sta_he_cap hwsim_nan_he_cap = {
+	.has_he = true,
+	.he_cap_elem = {
+		.mac_cap_info[0] =
+			IEEE80211_HE_MAC_CAP0_HTC_HE,
+		.mac_cap_info[1] =
+			IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US |
+			IEEE80211_HE_MAC_CAP1_MULTI_TID_AGG_RX_QOS_8,
+		.mac_cap_info[2] =
+			IEEE80211_HE_MAC_CAP2_BSR |
+			IEEE80211_HE_MAC_CAP2_MU_CASCADING |
+			IEEE80211_HE_MAC_CAP2_ACK_EN,
+		.mac_cap_info[3] =
+			IEEE80211_HE_MAC_CAP3_OMI_CONTROL |
+			IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_3,
+		.mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU,
+		.phy_cap_info[0] =
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
+			IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_80PLUS80_MHZ_IN_5G,
+		.phy_cap_info[1] =
+			IEEE80211_HE_PHY_CAP1_PREAMBLE_PUNC_RX_MASK |
+			IEEE80211_HE_PHY_CAP1_DEVICE_CLASS_A |
+			IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD |
+			IEEE80211_HE_PHY_CAP1_MIDAMBLE_RX_TX_MAX_NSTS,
+		.phy_cap_info[2] =
+			IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+			IEEE80211_HE_PHY_CAP2_STBC_TX_UNDER_80MHZ |
+			IEEE80211_HE_PHY_CAP2_STBC_RX_UNDER_80MHZ |
+			IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
+			IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO,
+
+		/*
+		 * Leave all the other PHY capability bytes
+		 * unset, as DCM, beam forming, RU and PPE
+		 * threshold information are not supported
+		 */
+	},
+	.he_mcs_nss_supp = {
+		.rx_mcs_80 = cpu_to_le16(0xfffa),
+		.tx_mcs_80 = cpu_to_le16(0xfffa),
+		.rx_mcs_160 = cpu_to_le16(0xfffa),
+		.tx_mcs_160 = cpu_to_le16(0xfffa),
+		.rx_mcs_80p80 = cpu_to_le16(0xfffa),
+		.tx_mcs_80p80 = cpu_to_le16(0xfffa),
+	},
+};
+
 static void mac80211_hwsim_sband_capab(struct ieee80211_supported_band *sband)
 {
 	switch (sband->band) {
@@ -5635,6 +5724,10 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		data->if_limits[n_limits].max = 2;
 		data->if_limits[n_limits].types = BIT(NL80211_IFTYPE_NAN_DATA);
 		n_limits++;
+
+		hw->wiphy->nan_capa.phy.ht = hwsim_nan_ht_cap;
+		hw->wiphy->nan_capa.phy.vht = hwsim_nan_vht_cap;
+		hw->wiphy->nan_capa.phy.he = hwsim_nan_he_cap;
 	}
 
 	data->if_combination.radar_detect_widths =
@@ -5816,14 +5909,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 				IEEE80211_VHT_CAP_RXSTBC_4 |
 				IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK;
 			sband->vht_cap.vht_mcs.rx_mcs_map =
-				cpu_to_le16(IEEE80211_VHT_MCS_SUPPORT_0_9 << 0 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 2 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 4 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 6 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 8 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 10 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 12 |
-					    IEEE80211_VHT_MCS_SUPPORT_0_9 << 14);
+				cpu_to_le16(HWSIM_VHT_MCS_MAP);
 			sband->vht_cap.vht_mcs.tx_mcs_map =
 				sband->vht_cap.vht_mcs.rx_mcs_map;
 			break;
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 08/14] wifi: mac80211_hwsim: implement NAN schedule callbacks
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Daniel Gabay <daniel.gabay@intel.com>

Implement mac80211 schedule callbacks for NAN Data Path support:

- Track local schedule via BSS_CHANGED_NAN_LOCAL_SCHED, caching
  the channel for each 16TU time slot.
- Copy peer schedule to driver-private storage in
  nan_peer_sched_changed callback for use in TX availability
  decisions.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_i.h   | 21 +++++
 .../wireless/virtual/mac80211_hwsim_main.c    | 14 ++--
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 80 +++++++++++++++++++
 .../net/wireless/virtual/mac80211_hwsim_nan.h | 15 +++-
 4 files changed, 121 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_i.h b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
index d182b5117bfb..0f0f2ac6d80e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_i.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_i.h
@@ -14,6 +14,27 @@
 #include "mac80211_hwsim.h"
 #include "mac80211_hwsim_nan.h"
 
+struct hwsim_sta_nan_sched {
+	/* Later members are protected by this lock */
+	spinlock_t lock;
+	u16 committed_dw;
+	struct {
+		u8 map_id;
+		struct cfg80211_chan_def chans[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
+	} maps[CFG80211_NAN_MAX_PEER_MAPS];
+};
+
+struct hwsim_sta_priv {
+	u32 magic;
+	unsigned int last_link;
+	u16 active_links_rx;
+
+	/* NAN peer schedule - must be accessed under nan_sched.lock */
+	struct hwsim_sta_nan_sched nan_sched;
+};
+
+#define HWSIM_STA_MAGIC	0x6d537749
+
 struct mac80211_hwsim_link_data {
 	u32 link_id;
 	u64 beacon_int	/* beacon interval in us */;
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index fc940b38c52c..1ea33ec577dd 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -252,14 +252,6 @@ static inline void hwsim_clear_magic(struct ieee80211_vif *vif)
 	vp->magic = 0;
 }
 
-struct hwsim_sta_priv {
-	u32 magic;
-	unsigned int last_link;
-	u16 active_links_rx;
-};
-
-#define HWSIM_STA_MAGIC	0x6d537749
-
 static inline void hwsim_check_sta_magic(struct ieee80211_sta *sta)
 {
 	struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
@@ -2652,6 +2644,9 @@ static void mac80211_hwsim_vif_info_changed(struct ieee80211_hw *hw,
 		vp->aid = vif->cfg.aid;
 	}
 
+	if (changed & BSS_CHANGED_NAN_LOCAL_SCHED)
+		mac80211_hwsim_nan_local_sched_changed(hw, vif);
+
 	if (vif->type == NL80211_IFTYPE_STATION &&
 	    changed & (BSS_CHANGED_MLD_VALID_LINKS | BSS_CHANGED_MLD_TTLM)) {
 		u16 usable_links = ieee80211_vif_usable_links(vif);
@@ -2818,6 +2813,8 @@ static int mac80211_hwsim_sta_add(struct ieee80211_hw *hw,
 		sp->active_links_rx = sta->valid_links;
 	}
 
+	spin_lock_init(&sp->nan_sched.lock);
+
 	return 0;
 }
 
@@ -4245,6 +4242,7 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
 	.start_nan = mac80211_hwsim_nan_start,			\
 	.stop_nan = mac80211_hwsim_nan_stop,			\
 	.nan_change_conf = mac80211_hwsim_nan_change_config,	\
+	.nan_peer_sched_changed = mac80211_hwsim_nan_peer_sched_changed, \
 	HWSIM_DEBUGFS_OPS
 
 #define HWSIM_NON_MLO_OPS					\
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 6053b6f8f91f..16883edd2215 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -1008,3 +1008,83 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 
 	return false;
 }
+
+void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
+					    struct ieee80211_vif *vif)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	struct ieee80211_nan_channel **slots = vif->cfg.nan_sched.schedule;
+
+	if (WARN_ON(vif->type != NL80211_IFTYPE_NAN))
+		return;
+
+	spin_lock_bh(&data->nan.state_lock);
+
+	for (int i = 0; i < ARRAY_SIZE(data->nan.local_sched); i++) {
+		struct ieee80211_chanctx_conf *chanctx;
+
+		if (!slots[i] || IS_ERR(slots[i])) {
+			memset(&data->nan.local_sched[i], 0,
+			       sizeof(data->nan.local_sched[i]));
+			continue;
+		}
+
+		chanctx = slots[i]->chanctx_conf;
+		if (!chanctx) {
+			memset(&data->nan.local_sched[i], 0,
+			       sizeof(data->nan.local_sched[i]));
+			continue;
+		}
+
+		data->nan.local_sched[i] = chanctx->def;
+	}
+
+	spin_unlock_bh(&data->nan.state_lock);
+}
+
+int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
+					  struct ieee80211_sta *sta)
+{
+	struct hwsim_sta_priv *sp = (void *)sta->drv_priv;
+	struct ieee80211_nan_peer_sched *sched = sta->nan_sched;
+
+	spin_lock_bh(&sp->nan_sched.lock);
+
+	/* Clear existing schedule */
+	sp->nan_sched.committed_dw = 0;
+	for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+		sp->nan_sched.maps[i].map_id = CFG80211_NAN_INVALID_MAP_ID;
+		memset(sp->nan_sched.maps[i].chans, 0,
+		       sizeof(sp->nan_sched.maps[i].chans));
+	}
+
+	if (!sched)
+		goto out;
+
+	sp->nan_sched.committed_dw = sched->committed_dw;
+
+	for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+		struct ieee80211_nan_peer_map *map = &sched->maps[i];
+
+		if (map->map_id == CFG80211_NAN_INVALID_MAP_ID)
+			continue;
+
+		sp->nan_sched.maps[i].map_id = map->map_id;
+
+		for (int j = 0; j < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; j++) {
+			struct ieee80211_nan_channel *peer_chan =
+				map->slots[j];
+
+			if (peer_chan && peer_chan->chanreq.oper.chan)
+				sp->nan_sched.maps[i].chans[j] =
+					peer_chan->chanreq.oper;
+			else
+				memset(&sp->nan_sched.maps[i].chans[j], 0,
+				       sizeof(sp->nan_sched.maps[i].chans[j]));
+		}
+	}
+
+out:
+	spin_unlock_bh(&sp->nan_sched.lock);
+	return 0;
+}
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index 3199e5c5376b..eb53bacee206 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * mac80211_hwsim_nan - NAN software simulation for mac80211_hwsim
- * Copyright (C) 2025 Intel Corporation
+ * Copyright (C) 2025-2026 Intel Corporation
  */
 
 #ifndef __MAC80211_HWSIM_NAN_H
@@ -52,6 +52,13 @@ struct mac80211_hwsim_nan_data {
 
 	bool tsf_adjusted;
 	bool tsf_discontinuity;
+
+	/*
+	 * Local schedule - stores channel definition for each 16TU slot.
+	 * Derived from NMI vif->cfg.nan_schedule. chan == NULL means not
+	 * available in that slot (except DW which is implicit).
+	 */
+	struct cfg80211_chan_def local_sched[CFG80211_NAN_SCHED_NUM_TIME_SLOTS];
 };
 
 enum hrtimer_restart
@@ -73,6 +80,9 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 				     struct cfg80211_nan_conf *conf,
 				     u32 changes);
 
+int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
+					  struct ieee80211_sta *sta);
+
 bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq);
 
@@ -86,4 +96,7 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 void mac80211_hwsim_nan_rx(struct ieee80211_hw *hw,
 			   struct sk_buff *skb);
 
+void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
+					    struct ieee80211_vif *vif);
+
 #endif /* __MAC80211_HWSIM_NAN_H */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 09/14] wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Daniel Gabay <daniel.gabay@intel.com>

- NAN switches between bands/channels per its schedule, so mac80211
  rate control can't work, set HAS_RATE_CONTROL instead.
- Skip rate control checks for NAN interfaces in
  mac80211_hwsim_sta_rc_update() as it's not relevant.
- Move set_rts_threshold stub to HWSIM_COMMON_OPS and return 0 instead
  of -EOPNOTSUPP to prevent failures in non-MLO tests that set RTS
  threshold (hwsim ignores the use_rts instruction from mac80211
  anyway).

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_main.c    | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 1ea33ec577dd..107d47d31073 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -2747,6 +2747,10 @@ mac80211_hwsim_sta_rc_update(struct ieee80211_hw *hw,
 	u32 bw = U32_MAX;
 	int link_id;
 
+	if (vif->type == NL80211_IFTYPE_NAN ||
+	    vif->type == NL80211_IFTYPE_NAN_DATA)
+		return;
+
 	rcu_read_lock();
 	for (link_id = 0;
 	     link_id < ARRAY_SIZE(vif->link_conf);
@@ -3475,7 +3479,8 @@ static int mac80211_hwsim_tx_last_beacon(struct ieee80211_hw *hw)
 static int mac80211_hwsim_set_rts_threshold(struct ieee80211_hw *hw,
 					    int radio_idx, u32 value)
 {
-	return -EOPNOTSUPP;
+	/* hwsim ignores the use_rts instruction from mac80211 anyway */
+	return 0;
 }
 
 static int mac80211_hwsim_change_vif_links(struct ieee80211_hw *hw,
@@ -4239,6 +4244,7 @@ static int mac80211_hwsim_set_radar_background(struct ieee80211_hw *hw,
 	.abort_pmsr = mac80211_hwsim_abort_pmsr,		\
 	.set_radar_background = mac80211_hwsim_set_radar_background, \
 	.set_key = mac80211_hwsim_set_key,			\
+	.set_rts_threshold = mac80211_hwsim_set_rts_threshold,	\
 	.start_nan = mac80211_hwsim_nan_start,			\
 	.stop_nan = mac80211_hwsim_nan_stop,			\
 	.nan_change_conf = mac80211_hwsim_nan_change_config,	\
@@ -4284,7 +4290,6 @@ static const struct ieee80211_ops mac80211_hwsim_mchan_ops = {
 static const struct ieee80211_ops mac80211_hwsim_mlo_ops = {
 	HWSIM_COMMON_OPS
 	HWSIM_CHANCTX_OPS
-	.set_rts_threshold = mac80211_hwsim_set_rts_threshold,
 	.change_vif_links = mac80211_hwsim_change_vif_links,
 	.change_sta_links = mac80211_hwsim_change_sta_links,
 	.sta_state = mac80211_hwsim_sta_state,
@@ -5726,6 +5731,12 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		hw->wiphy->nan_capa.phy.ht = hwsim_nan_ht_cap;
 		hw->wiphy->nan_capa.phy.vht = hwsim_nan_vht_cap;
 		hw->wiphy->nan_capa.phy.he = hwsim_nan_he_cap;
+
+		/*
+		 * NAN switches between bands/channels per its schedule,
+		 * so mac80211 rate control can't work here.
+		 */
+		ieee80211_hw_set(hw, HAS_RATE_CONTROL);
 	}
 
 	data->if_combination.radar_detect_widths =
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 10/14] wifi: mac80211_hwsim: add NAN data path TX/RX support
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Daniel Gabay <daniel.gabay@intel.com>

Implement TX and RX path handling for NAN Data Path (NDP) frames,
enabling data communication between NAN peers during scheduled
availability windows.

TX path:
- Select TX channel based on current time slot: use DW channel
  during Discovery Windows, or FAW channel from local
  schedule during Further Availability Windows.
- Verify peer availability before transmission by checking committed
  DW schedule or FAW of the peer schedule.

RX path:
- Extend NAN receive filtering to handle NAN_DATA interface frames.
- Accept incoming frames during FAW slots when channel matches local
  schedule.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../wireless/virtual/mac80211_hwsim_main.c    |  18 +-
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 250 ++++++++++++++++--
 .../net/wireless/virtual/mac80211_hwsim_nan.h |   4 +-
 3 files changed, 243 insertions(+), 29 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 107d47d31073..66cc8c528c6b 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -1744,7 +1744,8 @@ static void mac80211_hwsim_tx_iter(void *_data, u8 *addr,
 	struct tx_iter_data *data = _data;
 	int i;
 
-	if (vif->type == NL80211_IFTYPE_NAN) {
+	if (vif->type == NL80211_IFTYPE_NAN ||
+	    vif->type == NL80211_IFTYPE_NAN_DATA) {
 		data->receive = mac80211_hwsim_nan_receive(data->hw,
 							   data->channel,
 							   data->rx_status);
@@ -2093,13 +2094,19 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw,
 	/* re-assign hdr since skb data may have shifted after encryption */
 	hdr = (void *)skb->data;
 
-	if (vif && vif->type == NL80211_IFTYPE_NAN && !data->tmp_chan) {
-		channel = mac80211_hwsim_nan_get_tx_channel(hw);
+	if (vif && !data->tmp_chan &&
+	    (vif->type == NL80211_IFTYPE_NAN ||
+	     vif->type == NL80211_IFTYPE_NAN_DATA)) {
+		struct cfg80211_chan_def chandef;
 
-		if (WARN_ON(!channel)) {
+		mac80211_hwsim_nan_get_tx_chandef(hw, &chandef);
+		if (WARN_ON(!chandef.chan)) {
+			/* No valid channel in current slot, drop frame */
 			ieee80211_free_txskb(hw, skb);
 			return;
 		}
+		channel = chandef.chan;
+		confbw = chandef.width;
 	} else if (!data->use_chanctx) {
 		channel = data->channel;
 		confbw = data->bw;
@@ -2249,7 +2256,8 @@ void ieee80211_hwsim_wake_tx_queue(struct ieee80211_hw *hw,
 	};
 	struct sk_buff *skb;
 
-	if (txq->vif->type == NL80211_IFTYPE_NAN &&
+	if ((txq->vif->type == NL80211_IFTYPE_NAN ||
+	     txq->vif->type == NL80211_IFTYPE_NAN_DATA) &&
 	    !mac80211_hwsim_nan_txq_transmitting(hw, txq))
 		return;
 
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 16883edd2215..0397c43724fe 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -36,6 +36,11 @@ static_assert(DW0_TSF_MASK + 1 == 8192 * 1024);
 /* Quiet time at the end of each slot where TX is suppressed */
 #define NAN_CHAN_SWITCH_TIME_US		256
 
+struct hwsim_nan_sta_iter_ctx {
+	struct ieee80211_hw *hw;
+	bool can_tx;
+};
+
 static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
 
 static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -77,6 +82,109 @@ static u64 hwsim_nan_get_master_rank(struct mac80211_hwsim_data *data)
 					    data->nan.device_vif->addr);
 }
 
+static bool mac80211_hwsim_nan_is_dw_slot(struct mac80211_hwsim_data *data,
+					  u8 slot)
+{
+	return slot == SLOT_24GHZ_DW ||
+		(slot == SLOT_5GHZ_DW &&
+		 (data->nan.bands & BIT(NL80211_BAND_5GHZ)));
+}
+
+static bool
+hwsim_nan_rx_chandef_compatible(struct mac80211_hwsim_data *data, u8 slot,
+				struct ieee80211_channel *rx_chan, u8 rx_bw)
+{
+	static const int bw_to_mhz[] = {
+		[RATE_INFO_BW_20] = 20, [RATE_INFO_BW_40] = 40,
+		[RATE_INFO_BW_80] = 80, [RATE_INFO_BW_160] = 160,
+	};
+	struct cfg80211_chan_def sched_chandef;
+	int rx_mhz, sched_mhz;
+
+	scoped_guard(spinlock_bh, &data->nan.state_lock)
+		sched_chandef = data->nan.local_sched[slot];
+
+	if (!sched_chandef.chan ||
+	    sched_chandef.chan->center_freq != rx_chan->center_freq)
+		return false;
+
+	if (rx_bw >= ARRAY_SIZE(bw_to_mhz) || !bw_to_mhz[rx_bw])
+		return false;
+
+	rx_mhz = bw_to_mhz[rx_bw];
+	sched_mhz = cfg80211_chandef_get_width(&sched_chandef);
+
+	/* Accept RX at narrower or equal bandwidth */
+	return rx_mhz <= sched_mhz;
+}
+
+static bool hwsim_nan_peer_present_in_dw(struct hwsim_sta_priv *sp, u64 tsf)
+{
+	u8 slot = hwsim_nan_slot_from_tsf(tsf);
+	u8 cdw = 0;
+	u8 dw_index, wake_interval;
+	u16 committed_dw;
+
+	scoped_guard(spinlock_bh, &sp->nan_sched.lock)
+		committed_dw = sp->nan_sched.committed_dw;
+
+	/* If peer doesn't advertise committed DW, assume presence in
+	 * all 2.4 GHz DW slots
+	 */
+	if (!committed_dw)
+		return slot == SLOT_24GHZ_DW;
+
+	/* Get DW index (0-15) within the 16-DWST DW0 cycle */
+	dw_index = (tsf / ieee80211_tu_to_usec(DWST_TU)) & 0xf;
+
+	/* Extract CDW for the appropriate band (spec Table 80) */
+	if (slot == SLOT_24GHZ_DW)
+		cdw = committed_dw & 0x7;
+	else if (slot == SLOT_5GHZ_DW)
+		cdw = (committed_dw >> 3) & 0x7;
+
+	if (cdw == 0)
+		return false;
+
+	/* Peer wakes every 2^(cdw-1) DWs: 1, 2, 4, 8, or 16 */
+	wake_interval = 1 << (cdw - 1);
+
+	return (dw_index % wake_interval) == 0;
+}
+
+static bool
+hwsim_nan_peer_present_in_faw(struct hwsim_sta_priv *sp,
+			      struct mac80211_hwsim_data *data, u8 slot)
+{
+	struct cfg80211_chan_def local_chandef;
+
+	scoped_guard(spinlock_bh, &data->nan.state_lock)
+		local_chandef = data->nan.local_sched[slot];
+
+	if (!local_chandef.chan)
+		return false;
+
+	scoped_guard(spinlock_bh, &sp->nan_sched.lock) {
+		for (int i = 0; i < CFG80211_NAN_MAX_PEER_MAPS; i++) {
+			struct cfg80211_chan_def *peer_chandef;
+
+			if (sp->nan_sched.maps[i].map_id ==
+			    CFG80211_NAN_INVALID_MAP_ID)
+				continue;
+
+			peer_chandef = &sp->nan_sched.maps[i].chans[slot];
+			if (!peer_chandef->chan)
+				continue;
+
+			if (cfg80211_chandef_compatible(&local_chandef,
+							peer_chandef))
+				return true;
+		}
+	}
+
+	return false;
+}
+
 static void
 mac80211_hwsim_nan_schedule_slot(struct mac80211_hwsim_data *data, u8 slot,
 				 bool discontinuity)
@@ -880,15 +988,65 @@ int mac80211_hwsim_nan_change_config(struct ieee80211_hw *hw,
 	return 0;
 }
 
+static void hwsim_nan_can_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
+{
+	struct hwsim_nan_sta_iter_ctx *ctx = _ctx;
+
+	if (ctx->can_tx)
+		return;
+
+	for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+		struct ieee80211_txq *txq = sta->txq[i];
+
+		if (!txq)
+			continue;
+
+		if (txq->vif->type != NL80211_IFTYPE_NAN &&
+		    txq->vif->type != NL80211_IFTYPE_NAN_DATA)
+			return;
+
+		if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq)) {
+			ctx->can_tx = true;
+			return;
+		}
+	}
+}
+
 static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
 {
+	u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
+	u8 slot = hwsim_nan_slot_from_tsf(tsf);
+	bool is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
+	struct hwsim_nan_sta_iter_ctx ctx = {
+		.hw = data->hw,
+		.can_tx = false,
+	};
 	u32 timeout_ns;
 
-	/* Nothing to do if we are not in a DW */
-	if (!mac80211_hwsim_nan_txq_transmitting(data->hw,
-						 data->nan.device_vif->txq_mgmt))
+	/* Outside DW, require local FAW schedule to proceed */
+	if (!is_dw_slot) {
+		scoped_guard(spinlock_bh, &data->nan.state_lock) {
+			if (!data->nan.local_sched[slot].chan)
+				return;
+		}
+	}
+
+	guard(rcu)();
+
+	/* Check if management queue can transmit */
+	if (mac80211_hwsim_nan_txq_transmitting(data->hw,
+						data->nan.device_vif->txq_mgmt))
+		goto resume_txqs_timer;
+
+	/* Check if any STA queue can transmit */
+	ieee80211_iterate_stations_atomic(data->hw,
+					  hwsim_nan_can_sta_transmit,
+					  &ctx);
+
+	if (!ctx.can_tx)
 		return;
 
+resume_txqs_timer:
 	/*
 	 * Wait a bit and also randomize things so that not everyone is TXing
 	 * at the same time. Each slot is 16 TU long, this waits between 100 us
@@ -902,6 +1060,26 @@ static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data)
 		      HRTIMER_MODE_REL_SOFT);
 }
 
+static void hwsim_nan_wake_sta_iter(void *_data, struct ieee80211_sta *sta)
+{
+	struct ieee80211_hw *hw = _data;
+
+	for (int i = 0; i < ARRAY_SIZE(sta->txq); i++) {
+		struct ieee80211_txq *txq = sta->txq[i];
+
+		if (!txq)
+			continue;
+
+		/* exit early if non-NAN */
+		if (txq->vif->type != NL80211_IFTYPE_NAN &&
+		    txq->vif->type != NL80211_IFTYPE_NAN_DATA)
+			return;
+
+		if (mac80211_hwsim_nan_txq_transmitting(hw, txq))
+			ieee80211_hwsim_wake_tx_queue(hw, txq);
+	}
+}
+
 enum hrtimer_restart
 mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
 {
@@ -917,6 +1095,11 @@ mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
 		ieee80211_hwsim_wake_tx_queue(data->hw,
 					      data->nan.device_vif->txq_mgmt);
 
+	/* Wake TX queues for all stations */
+	ieee80211_iterate_stations_atomic(data->hw,
+					  hwsim_nan_wake_sta_iter,
+					  data->hw);
+
 	return HRTIMER_NORESTART;
 }
 
@@ -924,6 +1107,9 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
+	struct ieee80211_sta *nmi_sta;
+	struct hwsim_sta_priv *sp;
+	bool is_dw_slot;
 	u64 tsf;
 	u8 slot;
 
@@ -937,36 +1123,54 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 	if (slot != hwsim_nan_slot_from_tsf(tsf + NAN_CHAN_SWITCH_TIME_US))
 		return false;
 
-	/* Check NAN device interface management frame transmission */
-	if (!txq->sta) {
-		/* Only transmit these during one of the DWs */
-		if (slot == SLOT_24GHZ_DW ||
-		    (slot == SLOT_5GHZ_DW &&
-		     (data->nan.bands & BIT(NL80211_BAND_5GHZ))))
-			return true;
+	is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
 
-		return false;
+	/* Non-STA TXQ: allow management frames during DW */
+	if (!txq->sta)
+		return is_dw_slot;
+
+	/* STA TXQ: need peer schedule for availability check */
+	nmi_sta = rcu_dereference(txq->sta->nmi) ?: txq->sta;
+	sp = (void *)nmi_sta->drv_priv;
+
+	/* DW slot: NDI can TX only mgmt but not worth checking,
+	 * NMI checks peer's committed DW
+	 */
+	if (is_dw_slot) {
+		if (txq->vif->type == NL80211_IFTYPE_NAN_DATA)
+			return false;
+		return hwsim_nan_peer_present_in_dw(sp, tsf);
 	}
 
-	return true;
+	/* FAW slot: verify local schedule and peer availability */
+	return hwsim_nan_peer_present_in_faw(sp, data, slot);
 }
 
-struct ieee80211_channel *
-mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw)
+void mac80211_hwsim_nan_get_tx_chandef(struct ieee80211_hw *hw,
+				       struct cfg80211_chan_def *chandef)
 {
 	struct mac80211_hwsim_data *data = hw->priv;
 	u64 tsf = mac80211_hwsim_get_tsf(data->hw, data->nan.device_vif);
 	u8 slot = hwsim_nan_slot_from_tsf(tsf);
 
-	if (slot == SLOT_24GHZ_DW)
-		return ieee80211_get_channel(hw->wiphy, 2437);
+	/* DW slots are always 20 MHz */
+	if (slot == SLOT_24GHZ_DW) {
+		cfg80211_chandef_create(chandef,
+					ieee80211_get_channel(hw->wiphy, 2437),
+					NL80211_CHAN_NO_HT);
+		return;
+	}
 
-	if (slot == SLOT_5GHZ_DW &&
-	    data->nan.bands & BIT(NL80211_BAND_5GHZ))
-		return ieee80211_get_channel(hw->wiphy, 5745);
+	if (slot == SLOT_5GHZ_DW && data->nan.bands & BIT(NL80211_BAND_5GHZ)) {
+		cfg80211_chandef_create(chandef,
+					ieee80211_get_channel(hw->wiphy, 5745),
+					NL80211_CHAN_NO_HT);
+		return;
+	}
 
-	/* drop frame and warn, NAN_CHAN_SWITCH_TIME_US should avoid races */
-	return NULL;
+	/* FAW slot: copy local schedule for this slot */
+	scoped_guard(spinlock_bh, &data->nan.state_lock)
+		*chandef = data->nan.local_sched[slot];
 }
 
 bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
@@ -1006,7 +1210,9 @@ bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 	    channel->center_freq == 5745)
 		return true;
 
-	return false;
+	/* Accept frames during FAW slots if chandef is compatible */
+	return hwsim_nan_rx_chandef_compatible(data, slot, channel,
+					       rx_status->bw);
 }
 
 void mac80211_hwsim_nan_local_sched_changed(struct ieee80211_hw *hw,
diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
index eb53bacee206..81e105ac7b8e 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.h
@@ -86,8 +86,8 @@ int mac80211_hwsim_nan_peer_sched_changed(struct ieee80211_hw *hw,
 bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq);
 
-struct ieee80211_channel *
-mac80211_hwsim_nan_get_tx_channel(struct ieee80211_hw *hw);
+void mac80211_hwsim_nan_get_tx_chandef(struct ieee80211_hw *hw,
+				       struct cfg80211_chan_def *chandef);
 
 bool mac80211_hwsim_nan_receive(struct ieee80211_hw *hw,
 				struct ieee80211_channel *channel,
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 11/14] wifi: mac80211_hwsim: Declare support for secure NAN
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay, Avraham Stern
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Daniel Gabay <daniel.gabay@intel.com>

Advertise NL80211_EXT_FEATURE_SECURE_NAN to indicate support for
NAN Pairing, enabling peer authentication and secure data path
establishment.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Reviewed-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 66cc8c528c6b..2b228ae3029a 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5718,6 +5718,9 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
 			NAN_DEV_CAPA_NDPE_SUPPORTED;
 
+		wiphy_ext_feature_set(hw->wiphy,
+				      NL80211_EXT_FEATURE_SECURE_NAN);
+
 		hrtimer_setup(&data->nan.slot_timer,
 			      mac80211_hwsim_nan_slot_timer,
 			      CLOCK_BOOTTIME, HRTIMER_MODE_ABS_SOFT);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 12/14] wifi: mac80211_hwsim: enable NAN_DATA interface simulation support
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Daniel Gabay
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Daniel Gabay <daniel.gabay@intel.com>

Enable NAN_DATA interface simulation support by adding it to the
supported interface types. This completes the NAN Data Path
simulation introduced in the previous patches.

Signed-off-by: Daniel Gabay <daniel.gabay@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 2b228ae3029a..bf45d48a3fe0 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -6765,12 +6765,9 @@ static int hwsim_new_radio_nl(struct sk_buff *msg, struct genl_info *info)
 		param.p2p_device = true;
 	}
 
-	/* ensure both flag and iftype support is honored */
-	if (param.nan_device ||
-	    param.iftypes & BIT(NL80211_IFTYPE_NAN)) {
-		param.iftypes |= BIT(NL80211_IFTYPE_NAN);
-		param.nan_device = true;
-	}
+	if (param.nan_device)
+		param.iftypes |= BIT(NL80211_IFTYPE_NAN) |
+				 BIT(NL80211_IFTYPE_NAN_DATA);
 
 	if (info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]) {
 		u32 len = nla_len(info->attrs[HWSIM_ATTR_CIPHER_SUPPORT]);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 13/14] wifi: mac80211_hwsim: Do not declare support for NDPE
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Ilan Peer <ilan.peer@intel.com>

Do not declare support for NAN Data Path Extension attribute
as this is handled by user space and should be set by it.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index bf45d48a3fe0..a214a3a18841 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5715,8 +5715,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 		hw->wiphy->nan_capa.n_antennas = 0x22;
 		hw->wiphy->nan_capa.max_channel_switch_time = 0;
 		hw->wiphy->nan_capa.dev_capabilities =
-			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED |
-			NAN_DEV_CAPA_NDPE_SUPPORTED;
+			NAN_DEV_CAPA_EXT_KEY_ID_SUPPORTED;
 
 		wiphy_ext_feature_set(hw->wiphy,
 				      NL80211_EXT_FEATURE_SECURE_NAN);
-- 
2.34.1


^ permalink raw reply related

* [PATCH v2 wireless-next 14/14] wifi: mac80211_hwsim: Support Tx of multicast data on NAN
From: Miri Korenblit @ 2026-05-06  3:44 UTC (permalink / raw)
  To: linux-wireless; +Cc: Ilan Peer, Benjamin Berg
In-Reply-To: <20260506034433.3328362-1-miriam.rachel.korenblit@intel.com>

From: Ilan Peer <ilan.peer@intel.com>

Add support for transmitting multicast data frames. These
frames can be transmitted when all the peer NDI stations
on the interface are available at the current slot.

Signed-off-by: Ilan Peer <ilan.peer@intel.com>
Reviewed-by: Benjamin Berg <benjamin.berg@intel.com>
Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
---
 .../net/wireless/virtual/mac80211_hwsim_nan.c | 56 ++++++++++++++++++-
 1 file changed, 53 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
index 0397c43724fe..7be64c45babf 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_nan.c
@@ -41,6 +41,13 @@ struct hwsim_nan_sta_iter_ctx {
 	bool can_tx;
 };
 
+struct hwsim_nan_mcast_data_iter_ctx {
+	struct ieee80211_hw *hw;
+	struct ieee80211_vif *vif;
+	size_t n_vif_sta;
+	size_t n_sta_can_tx;
+};
+
 static void mac80211_hwsim_nan_resume_txqs(struct mac80211_hwsim_data *data);
 
 static u64 hwsim_nan_get_timer_tsf(struct mac80211_hwsim_data *data)
@@ -1103,6 +1110,42 @@ mac80211_hwsim_nan_resume_txqs_timer(struct hrtimer *timer)
 	return HRTIMER_NORESTART;
 }
 
+static void
+hwsim_nan_can_mcast_sta_transmit(void *_ctx, struct ieee80211_sta *sta)
+{
+	struct hwsim_nan_mcast_data_iter_ctx *ctx = _ctx;
+	struct ieee80211_txq *txq = sta->txq[0];
+
+	if (!txq || txq->vif != ctx->vif)
+		return;
+
+	ctx->n_vif_sta++;
+	if (mac80211_hwsim_nan_txq_transmitting(ctx->hw, txq))
+		ctx->n_sta_can_tx++;
+}
+
+static bool
+mac80211_hwsim_nan_mcast_data_transmitting(struct ieee80211_hw *hw,
+					   struct ieee80211_txq *txq)
+{
+	struct mac80211_hwsim_data *data = hw->priv;
+	struct hwsim_nan_mcast_data_iter_ctx ctx = {
+		.hw = hw,
+		.vif = txq->vif,
+		.n_sta_can_tx = 0,
+		.n_vif_sta = 0,
+	};
+
+	/* Check if all the stations associated with the current
+	 * interface are available.
+	 */
+	ieee80211_iterate_stations_atomic(data->hw,
+					  hwsim_nan_can_mcast_sta_transmit,
+					  &ctx);
+
+	return ctx.n_vif_sta && ctx.n_sta_can_tx == ctx.n_vif_sta;
+}
+
 bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 					 struct ieee80211_txq *txq)
 {
@@ -1125,9 +1168,16 @@ bool mac80211_hwsim_nan_txq_transmitting(struct ieee80211_hw *hw,
 
 	is_dw_slot = mac80211_hwsim_nan_is_dw_slot(data, slot);
 
-	/* Non-STA TXQ: allow management frames during DW */
-	if (!txq->sta)
-		return is_dw_slot;
+	if (!txq->sta) {
+		/* Non-STA TXQ: allow management frames during DW */
+		if (txq->vif->type == NL80211_IFTYPE_NAN)
+			return is_dw_slot;
+
+		/* Allow multicast data when all the peers are available
+		 * on this slot
+		 */
+		return mac80211_hwsim_nan_mcast_data_transmitting(hw, txq);
+	}
 
 	/* STA TXQ: need peer schedule for availability check */
 	nmi_sta = rcu_dereference(txq->sta->nmi) ?: txq->sta;
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH v2] wifi: ath11k: fix use after free in ath11k_dp_rx_msdu_coalesce.
From: Rameshkumar Sundaram @ 2026-05-06  4:12 UTC (permalink / raw)
  To: Willmar Knikker, jjohnson; +Cc: linux-wireless, ath11k
In-Reply-To: <20260505171709.547274-1-willmar@met-dubbel-l.nl>

On 5/5/2026 10:47 PM, Willmar Knikker wrote:
> In ath11k_dp_rx_msdu_coalesce the loop uses ->is_continuation after
> the dev_kfree_skb_any. This can cause a use after free kfence.
> 
> Use flag for caching is_continuation for use after the
> dev_kfree_skb_any.
> 
> Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices")
> Signed-off-by: Willmar Knikker <willmar@met-dubbel-l.nl>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* Re: [PATCH ath-next v2] wifi: ath10k: skip WMI and beacon transmission when device is wedged
From: Rameshkumar Sundaram @ 2026-05-06  5:10 UTC (permalink / raw)
  To: Kang Yang, ath10k; +Cc: linux-wireless
In-Reply-To: <20260428061737.37-1-kang.yang@oss.qualcomm.com>

On 4/28/2026 11:47 AM, Kang Yang wrote:
> In ath10k_wmi_cmd_send(), the current code detects ATH10K_STATE_WEDGED
> and sets ret to -ESHUTDOWN, but still proceeds to transmit pending
> beacons and calls ath10k_wmi_cmd_send_nowait().
> 
> This can lead to incorrect behavior, as WMI commands and beacons are
> still sent after the device has been marked as wedged, and the original
> -ESHUTDOWN return value may be overwritten by the result of the send
> path.
> 
> The wedged state indicates the hardware is already unreliable, and no
> further interaction with firmware is expected or meaningful in this
> state.
> 
> Fix this by skipping beacon transmission and the WMI send path entirely
> once ATH10K_STATE_WEDGED is detected, ensuring consistent return values
> and avoiding unnecessary firmware interaction.
> 
> Tested-on: QCA6174 hw3.2 PCI WLAN.RM.4.4.1-00288-QCARMSWPZ-1
> Tested-on: QCA6174 hw3.2 SDIO WLAN.RMH.4.4.1-00189
> 
> Fixes: c256a94d1b1b ("wifi: ath10k: shutdown driver when hardware is unreliable")
> Signed-off-by: Kang Yang <kang.yang@oss.qualcomm.com>

Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>

^ permalink raw reply

* [PATCH v2] wifi: mt76: mt792x: fix use-after-free in mt76_rx_poll_complete
From: JB Tsai @ 2026-05-06  5:29 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
	Ryder.Lee, Leon.Yen, litien.chang, eason.lai, jb.tsai, Eason Lai

From: Eason Lai <Eason.Lai@mediatek.com>

A use-after-free issue occurs in mt76_rx_poll_complete due to a race
condition. The STA has already been removed, but the rx_status still
had a pointer to the wcid in the STA.

Use wcid_idx instead of storing the wcid pointer, and look up the wcid
via rcu_dereference() by wcid_idx.

BUG: KASAN: invalid-access in mt76_rx_poll_complete+0x280/0x470 [mt76]
Call trace:
dump_backtrace+0xec/0x128
show_stack+0x18/0x28
dump_stack_lvl+0x40/0xc8
print_report+0x1b8/0x710
kasan_report+0xe0/0x144
do_bad_area+0x120/0x260
do_tag_check_fault+0x20/0x34
do_mem_abort+0x54/0xa8
el1_abort+0x3c/0x5c
el1h_64_sync_handler+0x40/0xcc
el1h_64_sync+0x7c/0x80
mt76_rx_poll_complete+0x280/0x470
mt76_dma_rx_poll+0x114/0x51c
mt792x_poll_rx+0x60/0xf8
napi_threaded_poll_loop+0xe0/0x450
napi_threaded_poll+0x80/0x9c
kthread+0x11c/0x158
ret_from_fork+0x10/0x20

Fixes: 9c68a57bc22d ("mt76: get station pointer by wcid and pass it to mac80211")
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
---
v2: fix mt76x02 build errors
---
 drivers/net/wireless/mediatek/mt76/agg-rx.c   | 18 ++++---
 drivers/net/wireless/mediatek/mt76/mac80211.c | 47 ++++++++-----------
 drivers/net/wireless/mediatek/mt76/mt76.h     |  9 ++--
 .../net/wireless/mediatek/mt76/mt7603/mac.c   |  6 ++-
 .../net/wireless/mediatek/mt76/mt7615/mac.c   | 19 +++++---
 .../net/wireless/mediatek/mt76/mt76_connac.h  |  3 +-
 .../wireless/mediatek/mt76/mt76_connac_mac.c  | 12 ++++-
 .../net/wireless/mediatek/mt76/mt76x02_mac.c  |  9 ++--
 .../net/wireless/mediatek/mt76/mt7915/mac.c   | 16 +++++--
 .../net/wireless/mediatek/mt76/mt7921/mac.c   | 13 +++--
 .../net/wireless/mediatek/mt76/mt7925/mac.c   | 19 +++++---
 .../net/wireless/mediatek/mt76/mt7996/mac.c   | 29 ++++++++----
 12 files changed, 119 insertions(+), 81 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/agg-rx.c b/drivers/net/wireless/mediatek/mt76/agg-rx.c
index bf1babac3895..b72d13975466 100644
--- a/drivers/net/wireless/mediatek/mt76/agg-rx.c
+++ b/drivers/net/wireless/mediatek/mt76/agg-rx.c
@@ -116,14 +116,15 @@ mt76_rx_aggr_reorder_work(struct work_struct *work)
 }
 
 static void
-mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
+mt76_rx_aggr_check_ctl(struct mt76_dev *dev, struct sk_buff *skb,
+		       struct sk_buff_head *frames)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ieee80211_bar *bar = mt76_skb_get_hdr(skb);
-	struct mt76_wcid *wcid = status->wcid;
 	struct mt76_rx_tid *tid;
-	u8 tidno;
+	struct mt76_wcid *wcid;
 	u16 seqno;
+	u8 tidno;
 
 	if (!ieee80211_is_ctl(bar->frame_control))
 		return;
@@ -131,6 +132,10 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
 	if (!ieee80211_is_back_req(bar->frame_control))
 		return;
 
+	wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+	if (!wcid)
+		return;
+
 	status->qos_ctl = tidno = le16_to_cpu(bar->control) >> 12;
 	seqno = IEEE80211_SEQ_TO_SN(le16_to_cpu(bar->start_seq_num));
 	tid = rcu_dereference(wcid->aggr[tidno]);
@@ -145,10 +150,11 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
 	spin_unlock_bh(&tid->lock);
 }
 
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+			  struct sk_buff_head *frames)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	struct ieee80211_sta *sta;
 	struct mt76_rx_tid *tid;
 	bool sn_less;
@@ -164,7 +170,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
 
 	if (!status->aggr) {
 		if (!(status->flag & RX_FLAG_8023))
-			mt76_rx_aggr_check_ctl(skb, frames);
+			mt76_rx_aggr_check_ctl(dev, skb, frames);
 		return;
 	}
 
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 4ae5e4715a9c..2933b29782d3 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1283,21 +1283,24 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
 	memcpy(status->chain_signal, mstat.chain_signal,
 	       sizeof(mstat.chain_signal));
 
-	if (mstat.wcid) {
-		status->link_valid = mstat.wcid->link_valid;
-		status->link_id = mstat.wcid->link_id;
-	}
+	if (mstat.wcid_idx != MT76_WCID_IDX_INVALID) {
+		struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, mstat.wcid_idx);
 
-	*sta = wcid_to_sta(mstat.wcid);
+		if (wcid) {
+			status->link_valid = wcid->link_valid;
+			status->link_id = wcid->link_id;
+			*sta = wcid_to_sta(wcid);
+		}
+	}
 	*hw = mt76_phy_hw(dev, mstat.phy_idx);
 }
 
 static void
-mt76_check_ccmp_pn(struct sk_buff *skb)
+mt76_check_ccmp_pn(struct mt76_dev *dev, struct sk_buff *skb)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
-	struct mt76_wcid *wcid = status->wcid;
 	struct ieee80211_hdr *hdr;
+	struct mt76_wcid *wcid;
 	int security_idx;
 	int ret;
 
@@ -1307,6 +1310,7 @@ mt76_check_ccmp_pn(struct sk_buff *skb)
 	if (status->flag & RX_FLAG_ONLY_MONITOR)
 		return;
 
+	wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	if (!wcid || !wcid->rx_check_pn)
 		return;
 
@@ -1354,7 +1358,7 @@ static void
 mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
 		    int len)
 {
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	struct ieee80211_rx_status info = {
 		.enc_flags = status->enc_flags,
 		.rate_idx = status->rate_idx,
@@ -1382,19 +1386,9 @@ mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
 static void
 mt76_airtime_flush_ampdu(struct mt76_dev *dev)
 {
-	struct mt76_wcid *wcid;
-	int wcid_idx;
-
 	if (!dev->rx_ampdu_len)
 		return;
 
-	wcid_idx = dev->rx_ampdu_status.wcid_idx;
-	if (wcid_idx < ARRAY_SIZE(dev->wcid))
-		wcid = rcu_dereference(dev->wcid[wcid_idx]);
-	else
-		wcid = NULL;
-	dev->rx_ampdu_status.wcid = wcid;
-
 	mt76_airtime_report(dev, &dev->rx_ampdu_status, dev->rx_ampdu_len);
 
 	dev->rx_ampdu_len = 0;
@@ -1405,7 +1399,7 @@ static void
 mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 
 	if (!(dev->drv->drv_flags & MT_DRV_SW_RX_AIRTIME))
 		return;
@@ -1418,8 +1412,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
 
 		if (!ether_addr_equal(hdr->addr1, dev->phy.macaddr))
 			return;
-
-		wcid = NULL;
 	}
 
 	if (!(status->flag & RX_FLAG_AMPDU_DETAILS) ||
@@ -1430,7 +1422,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
 		if (!dev->rx_ampdu_len ||
 		    status->ampdu_ref != dev->rx_ampdu_ref) {
 			dev->rx_ampdu_status = *status;
-			dev->rx_ampdu_status.wcid_idx = wcid ? wcid->idx : 0xff;
 			dev->rx_ampdu_ref = status->ampdu_ref;
 		}
 
@@ -1448,7 +1439,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
 	struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
 	struct ieee80211_sta *sta;
 	struct ieee80211_hw *hw;
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK;
 	bool ps;
 
@@ -1456,8 +1447,10 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
 	if (ieee80211_is_pspoll(hdr->frame_control) && !wcid &&
 	    !(status->flag & RX_FLAG_8023)) {
 		sta = ieee80211_find_sta_by_ifaddr(hw, hdr->addr2, NULL);
-		if (sta)
-			wcid = status->wcid = (struct mt76_wcid *)sta->drv_priv;
+		if (sta) {
+			wcid = (struct mt76_wcid *)sta->drv_priv;
+			status->wcid_idx = wcid->idx;
+		}
 	}
 
 	mt76_airtime_check(dev, skb);
@@ -1521,7 +1514,7 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
 	while ((skb = __skb_dequeue(frames)) != NULL) {
 		struct sk_buff *nskb = skb_shinfo(skb)->frag_list;
 
-		mt76_check_ccmp_pn(skb);
+		mt76_check_ccmp_pn(dev, skb);
 		skb_shinfo(skb)->frag_list = NULL;
 		mt76_rx_convert(dev, skb, &hw, &sta);
 		ieee80211_rx_list(hw, sta, skb, &list);
@@ -1563,7 +1556,7 @@ void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
 		    mt76_npu_device_active(dev))
 			__skb_queue_tail(&frames, skb);
 		else
-			mt76_rx_aggr_reorder(skb, &frames);
+			mt76_rx_aggr_reorder(dev, skb, &frames);
 	}
 
 	mt76_rx_complete(dev, &frames, napi);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..1e1bd410a7a7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -364,6 +364,7 @@ enum mt76_wcid_flags {
 };
 
 #define MT76_N_WCIDS 1088
+#define MT76_WCID_IDX_INVALID		0xffff
 #define MT76_BEACON_MON_MAX_MISS	7
 
 /* stored in ieee80211_tx_info::hw_queue */
@@ -728,10 +729,7 @@ struct mt76_mmio {
 };
 
 struct mt76_rx_status {
-	union {
-		struct mt76_wcid *wcid;
-		u16 wcid_idx;
-	};
+	u16 wcid_idx;
 
 	u32 reorder_time;
 
@@ -1793,7 +1791,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
 		      struct napi_struct *napi);
 void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
 			   struct napi_struct *napi);
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames);
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+			  struct sk_buff_head *frames);
 void mt76_testmode_tx_pending(struct mt76_phy *phy);
 void mt76_queue_tx_complete(struct mt76_dev *dev, struct mt76_queue *q,
 			    struct mt76_queue_entry *e);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
index d3110eeb45d7..aeb890df808e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
@@ -513,6 +513,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
 	u32 rxd2 = le32_to_cpu(rxd[2]);
 	bool unicast = rxd1 & MT_RXD1_NORMAL_U2M;
 	bool insert_ccmp_hdr = false;
+	struct mt76_wcid *wcid;
 	bool remove_pad;
 	int idx;
 	int i;
@@ -524,7 +525,8 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
 	i >>= 1;
 
 	idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
-	status->wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+	wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
 	status->band = sband->band;
 	if (i < sband->n_channels)
@@ -672,7 +674,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
 	}
 
 	hdr = (struct ieee80211_hdr *)skb->data;
-	if (!status->wcid || !ieee80211_is_data_qos(hdr->frame_control))
+	if (!wcid || !ieee80211_is_data_qos(hdr->frame_control))
 		return 0;
 
 	status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
index ce0051468501..84be00a13930 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
@@ -246,17 +246,20 @@ static void mt7615_mac_fill_tm_rx(struct mt7615_phy *phy, __le32 *rxv)
 }
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7615_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7615_reverse_frag0_hdr_trans(struct mt7615_dev *dev,
+					  struct sk_buff *skb, u16 hdr_gap)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
-	struct mt7615_sta *msta = (struct mt7615_sta *)status->wcid;
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_vif *vif;
 	struct ieee80211_hdr hdr;
+	struct mt7615_sta *msta;
 	u16 frame_control;
 
+	msta = (struct mt7615_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
 	if (le32_get_bits(rxd[1], MT_RXD1_NORMAL_ADDR_TYPE) !=
 	    MT_RXD1_NORMAL_U2M)
 		return -EINVAL;
@@ -335,6 +338,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 	struct ieee80211_supported_band *sband;
 	struct ieee80211_hdr *hdr;
 	struct mt7615_phy *phy2;
+	struct mt76_wcid *wcid;
 	__le32 *rxd = (__le32 *)skb->data;
 	u32 rxd0 = le32_to_cpu(rxd[0]);
 	u32 rxd1 = le32_to_cpu(rxd[1]);
@@ -378,12 +382,13 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 
 	unicast = (rxd1 & MT_RXD1_NORMAL_ADDR_TYPE) == MT_RXD1_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
-	status->wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+	wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
-	if (status->wcid) {
+	if (wcid) {
 		struct mt7615_sta *msta;
 
-		msta = container_of(status->wcid, struct mt7615_sta, wcid);
+		msta = container_of(wcid, struct mt7615_sta, wcid);
 		mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
 	}
 
@@ -590,7 +595,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 
 	hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
 	if (hdr_trans && ieee80211_has_morefrags(fc)) {
-		if (mt7615_reverse_frag0_hdr_trans(skb, hdr_gap))
+		if (mt7615_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
 			return -EINVAL;
 		hdr_trans = false;
 	} else {
@@ -638,7 +643,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 		status->flag |= RX_FLAG_8023;
 	}
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (!wcid || !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
index 51423c7740bd..d9ae8d204442 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
@@ -448,7 +448,8 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
 void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
 					 struct sk_buff *skb,
 					 __le32 *rxv, u32 mode);
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+					 struct ieee80211_vif *vif,
 					 struct sk_buff *skb, u16 hdr_offset);
 int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev,
 				  struct mt76_rx_status *status,
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
index 0339e2e7ab60..376d59d517cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
@@ -952,7 +952,8 @@ void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
 EXPORT_SYMBOL_GPL(mt76_connac2_mac_decode_he_radiotap);
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+					 struct ieee80211_vif *vif,
 					 struct sk_buff *skb, u16 hdr_offset)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
@@ -960,6 +961,7 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_hdr hdr;
+	struct mt76_wcid *wcid;
 	u16 frame_control;
 
 	if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -969,7 +971,13 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
 	if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
 		return -EINVAL;
 
-	sta = container_of((void *)status->wcid, struct ieee80211_sta, drv_priv);
+	wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+	if (!wcid)
+		return -EINVAL;
+
+	sta = container_of((void *)wcid, struct ieee80211_sta, drv_priv);
+	if (!sta)
+		return -EINVAL;
 
 	/* store the info from RXD and ethhdr to avoid being overridden */
 	frame_control = le32_get_bits(rxd[6], MT_RXD6_FRAME_CONTROL);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
index 14ee5b3b94d3..ce5d3f8f019a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
@@ -779,11 +779,10 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
 	u32 ctl = le32_to_cpu(rxwi->ctl);
 	u16 rate = le16_to_cpu(rxwi->rate);
 	u16 tid_sn = le16_to_cpu(rxwi->tid_sn);
-	bool unicast = rxwi->rxinfo & cpu_to_le32(MT_RXINFO_UNICAST);
 	int pad_len = 0, nstreams = dev->mphy.chainmask & 0xf;
+	u8 wcid_idx;
 	s8 signal;
 	u8 pn_len;
-	u8 wcid;
 	int len;
 
 	if (!test_bit(MT76_STATE_RUNNING, &dev->mphy.state))
@@ -799,9 +798,9 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
 		status->flag |= RX_FLAG_IV_STRIPPED;
 	}
 
-	wcid = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
-	sta = mt76x02_rx_get_sta(&dev->mt76, wcid);
-	status->wcid = mt76x02_rx_get_sta_wcid(sta, unicast);
+	wcid_idx = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
+	sta = mt76x02_rx_get_sta(&dev->mt76, wcid_idx);
+	status->wcid_idx = wcid_idx;
 
 	len = FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl);
 	pn_len = FIELD_GET(MT_RXINFO_PN_LEN, rxinfo);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index cec2c4208255..30672713323f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -280,6 +280,7 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 	u8 mode = 0, qos_ctl = 0;
 	struct mt7915_sta *msta = NULL;
 	u32 csum_status = *(u32 *)skb->cb;
+	struct mt76_wcid *wcid;
 	bool hdr_trans;
 	u16 hdr_gap;
 	u16 seq_ctrl = 0;
@@ -313,10 +314,13 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt7915_rx_get_wcid(dev, idx, unicast);
 
-	if (status->wcid) {
-		msta = container_of(status->wcid, struct mt7915_sta, wcid);
+	wcid = mt7915_rx_get_wcid(dev, idx, unicast);
+
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+	if (wcid) {
+		msta = container_of(wcid, struct mt7915_sta, wcid);
 		mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
 	}
 
@@ -475,7 +479,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 
 		vif = container_of((void *)msta->vif, struct ieee80211_vif,
 				   drv_priv);
-		err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+		err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+							   skb, hdr_gap);
 		if (err)
 			return err;
 
@@ -532,7 +537,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 	if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
 		mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (status->wcid_idx == MT76_WCID_IDX_INVALID ||
+	    !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..59c64b951678 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -184,6 +184,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	u32 rxd4 = le32_to_cpu(rxd[4]);
 	struct mt792x_sta *msta = NULL;
 	struct mt792x_link_sta *mlink;
+	struct mt76_wcid *wcid;
 	u16 seq_ctrl = 0;
 	__le16 fc = 0;
 	u8 mode = 0;
@@ -211,10 +212,11 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
-	if (status->wcid) {
-		mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+	if (wcid) {
+		mlink = container_of(wcid, struct mt792x_link_sta, wcid);
 		msta = container_of(mlink, struct mt792x_sta, deflink);
 		mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
 	}
@@ -395,7 +397,8 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 
 		vif = container_of((void *)msta->vif, struct ieee80211_vif,
 				   drv_priv);
-		err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+		err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+							   skb, hdr_gap);
 		if (err)
 			return err;
 
@@ -433,7 +436,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
 		mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (!wcid || !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index c47bd812b66b..e429f78596e2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -166,17 +166,20 @@ void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev,
 }
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7925_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7925_reverse_frag0_hdr_trans(struct mt792x_dev *dev,
+					  struct sk_buff *skb, u16 hdr_gap)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
-	struct mt792x_sta *msta = (struct mt792x_sta *)status->wcid;
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_vif *vif;
 	struct ieee80211_hdr hdr;
+	struct mt792x_sta *msta;
 	u16 frame_control;
 
+	msta = (struct mt792x_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
 	if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
 	    MT_RXD3_NORMAL_U2M)
 		return -EINVAL;
@@ -368,6 +371,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	u32 rxd3 = le32_to_cpu(rxd[3]);
 	u32 rxd4 = le32_to_cpu(rxd[4]);
 	struct mt792x_link_sta *mlink;
+	struct mt76_wcid *wcid;
 	u8 mode = 0; /* , band_idx; */
 	u16 seq_ctrl = 0;
 	__le16 fc = 0;
@@ -392,10 +396,11 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
-	if (status->wcid) {
-		mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+	if (wcid) {
+		mlink = container_of(wcid, struct mt792x_link_sta, wcid);
 		mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
 	}
 
@@ -545,7 +550,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 
 	hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
 	if (hdr_trans && ieee80211_has_morefrags(fc)) {
-		if (mt7925_reverse_frag0_hdr_trans(skb, hdr_gap))
+		if (mt7925_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
 			return -EINVAL;
 		hdr_trans = false;
 	} else {
@@ -608,7 +613,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 		}
 	}
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (!wcid || !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index e2a83da3a09c..98c30957dd76 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -186,17 +186,19 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
 }
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7996_reverse_frag0_hdr_trans(struct mt7996_dev *dev,
+					  struct sk_buff *skb, u16 hdr_gap)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
-	struct mt7996_sta_link *msta_link = (void *)status->wcid;
-	struct mt7996_sta *msta = msta_link->sta;
+	struct mt76_wcid *wcid = mt76_wcid_ptr(dev, status->wcid_idx);
+	struct mt7996_sta_link *msta_link;
 	struct ieee80211_bss_conf *link_conf;
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_vif *vif;
 	struct ieee80211_hdr hdr;
+	struct mt7996_sta *msta;
 	u16 frame_control;
 
 	if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -206,10 +208,16 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
 	if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
 		return -EINVAL;
 
+	if (!wcid)
+		return -EINVAL;
+
+	msta_link = container_of(wcid, struct mt7996_sta_link, wcid);
+	msta = msta_link->sta;
+
 	if (!msta || !msta->vif)
 		return -EINVAL;
 
-	sta = wcid_to_sta(status->wcid);
+	sta = wcid_to_sta(wcid);
 	vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
 	link_conf = rcu_dereference(vif->link_conf[msta_link->wcid.link_id]);
 	if (!link_conf)
@@ -429,6 +437,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 	bool unicast, insert_ccmp_hdr = false;
 	u8 remove_pad, amsdu_info, band_idx;
 	u8 mode = 0, qos_ctl = 0;
+	struct mt76_wcid *wcid;
 	bool hdr_trans;
 	u16 hdr_gap;
 	u16 seq_ctrl = 0;
@@ -461,12 +470,14 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
 
-	if (status->wcid) {
+	wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+	if (wcid) {
 		struct mt7996_sta_link *msta_link;
 
-		msta_link = container_of(status->wcid, struct mt7996_sta_link,
+		msta_link = container_of(wcid, struct mt7996_sta_link,
 					 wcid);
 		msta = msta_link->sta;
 		mt76_wcid_add_poll(&dev->mt76, &msta_link->wcid);
@@ -620,7 +631,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 
 	hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
 	if (hdr_trans && ieee80211_has_morefrags(fc)) {
-		if (mt7996_reverse_frag0_hdr_trans(skb, hdr_gap))
+		if (mt7996_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
 			return -EINVAL;
 		hdr_trans = false;
 	} else {
@@ -697,7 +708,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 		}
 	}
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
+	if (!wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
 		return 0;
 
 	status->aggr = unicast &&
-- 
2.45.2


^ permalink raw reply related

* Re: [PATCH v2] wifi: ath12k: fix incorrect HT/VHT/HE/EHT MCS reporting in monitor mode
From: Tamizh Raja @ 2026-05-06  5:32 UTC (permalink / raw)
  To: Jeff Johnson; +Cc: kwan1996, ath12k, linux-wireless
In-Reply-To: <69fd7b6b-ebda-4203-a798-6227feba7350@oss.qualcomm.com>

On Tue, May 5, 2026 at 10:52 PM Jeff Johnson
<jeff.johnson@oss.qualcomm.com> wrote:
>
> On 5/5/2026 9:43 AM, Tamizh Raja wrote:
> > On Tue, May 5, 2026 at 9:40 AM kwan1996 <laicheehou9@gmail.com> wrote:
> >>
> >> In monitor mode, the driver incorrectly assigns the legacy rate
> >> to the rate_idx field of the radiotap header for HT/VHT/HE/EHT
> >> frames, ignoring the actual MCS value parsed from the hardware.
> >>
> >> This causes packet analyzers (like Wireshark) to display incorrect
> >> MCS values (e.g., legacy base rates instead of the true MCS).
> >>
> >> Fix this by assigning ppdu_info->mcs instead of ppdu_info->rate
> >> for HT/VHT/HE/EHT frame types in ath12k_dp_mon_fill_rx_rate()
> >> and ath12k_dp_mon_update_radiotap().
> >>
> >> Link: https://bugzilla.kernel.org/show_bug.cgi?id=220864
> >>
> >> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ
> >>
> >> Signed-off-by: kwan1996 <laicheehou9@gmail.com>
> >>
> >> ---
> >>
> >> v2: Fix indentation and formatting issues in v1.
> >>
> >> ---
> >>  drivers/net/wireless/ath/ath12k/dp_mon.c | 10 +++++++---
> >>  1 file changed, 7 insertions(+), 3 deletions(-)
> >>
> >> diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c
> >> index 39d1967..4119bb8 100644
> >> --- a/drivers/net/wireless/ath/ath12k/dp_mon.c
> >> +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c
> >> @@ -1925,6 +1925,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> >>                 }
> >>                 break;
> >>         case RX_MSDU_START_PKT_TYPE_11N:
> >> +               rate_mcs = ppdu_info->mcs;
> >
> > Can we assign this rate_mcs before the switch case? Since in all cases
> > we are assigning unmodified ppdu_info->mcs.
> >>                 rx_status->encoding = RX_ENC_HT;
> >>                 if (rate_mcs > ATH12K_HT_MCS_MAX) {
> >>                         ath12k_warn(ar->ab,
> >> @@ -1937,6 +1938,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> >>                         rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
> >>                 break;
> >>         case RX_MSDU_START_PKT_TYPE_11AC:
> >> +               rate_mcs = ppdu_info->mcs;
> >>                 rx_status->encoding = RX_ENC_VHT;
> >>                 rx_status->rate_idx = rate_mcs;
> >>                 if (rate_mcs > ATH12K_VHT_MCS_MAX) {
> >> @@ -1949,6 +1951,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> >>                         rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
> >>                 break;
> >>         case RX_MSDU_START_PKT_TYPE_11AX:
> >> +               rate_mcs = ppdu_info->mcs;
> >>                 rx_status->rate_idx = rate_mcs;
> >>                 if (rate_mcs > ATH12K_HE_MCS_MAX) {
> >>                         ath12k_warn(ar->ab,
> >> @@ -1960,6 +1963,7 @@ ath12k_dp_mon_fill_rx_rate(struct ath12k *ar,
> >>                 rx_status->he_gi = ath12k_he_gi_to_nl80211_he_gi(sgi);
> >>                 break;
> >>         case RX_MSDU_START_PKT_TYPE_11BE:
> >> +               rate_mcs = ppdu_info->mcs;
> >>                 rx_status->rate_idx = rate_mcs;
> >>                 if (rate_mcs > ATH12K_EHT_MCS_MAX) {
> >>                         ath12k_warn(ar->ab,
> >> @@ -2259,13 +2263,13 @@ static void ath12k_dp_mon_update_radiotap(struct ath12k *ar,
> >>                 rxs->encoding = RX_ENC_HE;
> >>                 ptr = skb_push(mon_skb, sizeof(struct ieee80211_radiotap_he));
> >>                 ath12k_dp_mon_rx_update_radiotap_he(ppduinfo, ptr);
> >> -               rxs->rate_idx = ppduinfo->rate;
> >> +               rxs->rate_idx = ppduinfo->mcs;
> >>         } else if (ppduinfo->vht_flags) {
> >>                 rxs->encoding = RX_ENC_VHT;
> >> -               rxs->rate_idx = ppduinfo->rate;
> >> +               rxs->rate_idx = ppduinfo->mcs;
> >>         } else if (ppduinfo->ht_flags) {
> >>                 rxs->encoding = RX_ENC_HT;
> >> -               rxs->rate_idx = ppduinfo->rate;
> >> +               rxs->rate_idx = ppduinfo->mcs;
> >
> > rate_idx should be assigned with ppdu_info->rate only not mcs.
>
> why is that? documentation says:
>  * @rate_idx: index of data rate into band's supported rates or MCS index if
>  *      HT or VHT is used (%RX_FLAG_HT/%RX_FLAG_VHT)
>
> ppduinfo contains separate rate and mcs so doesn't one or the other need to be
> copied to rxs->rate_idx based upon the current PHY configuration?
>
Yeah, sorry for the confusion. Change looks good to me, We need this
fix for ath11k as well.

> btw looks like the struct ieee80211_rx_status documentation needs to be
> updated for HE & EHT (and UHR?)
Yes.

^ permalink raw reply

* Re: [PATCH v2 3/3] mmc: sdhci-esdhc-imx: consolidate imx25/35 data and add Kingston CID
From: Adrian Hunter @ 2026-05-06  6:04 UTC (permalink / raw)
  To: Adrián García Casado, Ulf Hansson, Andreas Hindborg,
	Jens Axboe, Miri Korenblit
  Cc: Miguel Ojeda, Haibo Chen, Frank Li, Sascha Hauer, Boqun Feng,
	linux-mmc, imx, linux-arm-kernel, linux-block, rust-for-linux,
	linux-wireless, linux-kernel, Adrián García Casado
In-Reply-To: <20260315172746.270734-4-adriangarciacasado42@gmail.com>

On 15/03/2026 19:26, Adrián García Casado wrote:
> Consolidate esdhc_imx25 and esdhc_imx35 soc data into a single shared
> struct since they share the same flags. This reduces redundancy. Also
> add the CID_MANFID_KINGSTON definition to quirks.h for centralized
> management.
> 
> Signed-off-by: Adrián García Casado <adriangarciacicuelo@gmail.com>

scripts/checkpatch.pl warning:

WARNING: From:/Signed-off-by: email address mismatch: 'From: "Adrián García Casado" <adriangarciacasado42@gmail.com>' != 'Signed-off-by: Adrián García Casado <adriangarciacicuelo@gmail.com>'

> ---
>  drivers/mmc/core/quirks.h          |  4 ++++
>  drivers/mmc/host/sdhci-esdhc-imx.c | 12 ++++--------
>  2 files changed, 8 insertions(+), 8 deletions(-)
> 
> diff --git a/drivers/mmc/core/quirks.h b/drivers/mmc/core/quirks.h
> index c417ed34c..d736bb4be 100644
> --- a/drivers/mmc/core/quirks.h
> +++ b/drivers/mmc/core/quirks.h
> @@ -15,6 +15,10 @@
>  
>  #include "card.h"
>  
> +#ifndef CID_MANFID_KINGSTON
> +#define CID_MANFID_KINGSTON	0x70
> +#endif

CID_MANFID_KINGSTON is already defined in "card.h"
so the change is not needed.

> +
>  static const struct mmc_fixup __maybe_unused mmc_sd_fixups[] = {
>  	/*
>  	 * Kingston Canvas Go! Plus microSD cards never finish SD cache flush.
> diff --git a/drivers/mmc/host/sdhci-esdhc-imx.c b/drivers/mmc/host/sdhci-esdhc-imx.c
> index a7a5df673..9cfa26722 100644
> --- a/drivers/mmc/host/sdhci-esdhc-imx.c
> +++ b/drivers/mmc/host/sdhci-esdhc-imx.c
> @@ -256,11 +256,7 @@ struct esdhc_soc_data {
>  	u32 quirks;
>  };
>  
> -static const struct esdhc_soc_data esdhc_imx25_data = {
> -	.flags = ESDHC_FLAG_ERR004536,
> -};
> -
> -static const struct esdhc_soc_data esdhc_imx35_data = {
> +static const struct esdhc_soc_data esdhc_imx25_35_data = {
>  	.flags = ESDHC_FLAG_ERR004536,
>  };
>  
> @@ -391,8 +387,8 @@ struct pltfm_imx_data {
>  };
>  
>  static const struct of_device_id imx_esdhc_dt_ids[] = {
> -	{ .compatible = "fsl,imx25-esdhc", .data = &esdhc_imx25_data, },
> -	{ .compatible = "fsl,imx35-esdhc", .data = &esdhc_imx35_data, },
> +	{ .compatible = "fsl,imx25-esdhc", .data = &esdhc_imx25_35_data, },
> +	{ .compatible = "fsl,imx35-esdhc", .data = &esdhc_imx25_35_data, },
>  	{ .compatible = "fsl,imx51-esdhc", .data = &esdhc_imx51_data, },
>  	{ .compatible = "fsl,imx53-esdhc", .data = &esdhc_imx53_data, },
>  	{ .compatible = "fsl,imx6sx-usdhc", .data = &usdhc_imx6sx_data, },
> @@ -414,7 +410,7 @@ MODULE_DEVICE_TABLE(of, imx_esdhc_dt_ids);
>  
>  static inline int is_imx25_esdhc(struct pltfm_imx_data *data)
>  {
> -	return data->socdata == &esdhc_imx25_data;
> +	return data->socdata == &esdhc_imx25_35_data;
>  }

Doesn't look right.
Previously this matched only "fsl,imx25-esdhc".
Now it also matches "fsl,imx35-esdhc".  So this change
has unintended consequences.

>  
>  static inline int is_imx53_esdhc(struct pltfm_imx_data *data)


^ permalink raw reply

* Re: [PATCH 2/2] wifi: ath11k: fix error path leaks in some WMI calls
From: Nicolas Escande @ 2026-05-06  6:21 UTC (permalink / raw)
  To: Rameshkumar Sundaram, Nicolas Escande, ath11k; +Cc: linux-wireless
In-Reply-To: <151425e9-564d-4d61-b37d-3eef9bc54b6f@oss.qualcomm.com>

On Tue May 5, 2026 at 7:23 PM CEST, Rameshkumar Sundaram wrote:
> On 5/2/2026 12:44 PM, Nicolas Escande wrote:
>> This is the same pattern that was previously identified as problematic:
>> direct 'return ath11k_wmi_cmd_send(...)' will leak the skb in the error
>> path if it is not explicitly handled.
>> 
>> Fixes: c417b247ba04 ("ath11k: implement hardware data filter")
>> Fixes: 9cbd7fc9be82 ("ath11k: support MAC address randomization in scan")
>> Fixes: ba9177fcef21 ("ath11k: Add basic WoW functionalities")
>> Fixes: fec4b898f369 ("ath11k: Add WoW net-detect functionality")
>> Fixes: c3c36bfe998b ("ath11k: support ARP and NS offload")
>> Fixes: a16d9b50cfba ("ath11k: support GTK rekey offload")
>> Fixes: 652f69ed9c1b ("ath11k: Add support for SAR")
>> Fixes: 0f84a156aa3b ("ath11k: Handle keepalive during WoWLAN suspend and resume")
>> Signed-off-by: Nicolas Escande <nico.escande@gmail.com>
>> ---
>>   drivers/net/wireless/ath/ath11k/wmi.c | 112 ++++++++++++++++++++++----
>>   1 file changed, 96 insertions(+), 16 deletions(-)
>> 
>> diff --git a/drivers/net/wireless/ath/ath11k/wmi.c b/drivers/net/wireless/ath/ath11k/wmi.c
>> index 024c2aad9fb4..dca6e011cc40 100644
>> --- a/drivers/net/wireless/ath/ath11k/wmi.c
>> +++ b/drivers/net/wireless/ath/ath11k/wmi.c
>> @@ -9299,7 +9299,7 @@ int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id,
>>   {
>>   	struct wmi_hw_data_filter_cmd *cmd;
>>   	struct sk_buff *skb;
>> -	int len;
>> +	int ret, len;
>>   
>>   	len = sizeof(*cmd);
>>   	skb = ath11k_wmi_alloc_skb(ar->wmi->wmi_ab, len);
>> @@ -9324,7 +9324,13 @@ int ath11k_wmi_hw_data_filter_cmd(struct ath11k *ar, u32 vdev_id,
>>   		   "hw data filter enable %d filter_bitmap 0x%x\n",
>>   		   enable, filter_bitmap);
>>   
>> -	return ath11k_wmi_cmd_send(ar->wmi, skb, WMI_HW_DATA_FILTER_CMDID);
>> +	ret = ath11k_wmi_cmd_send(ar->wmi, skb, WMI_HW_DATA_FILTER_CMDID);
>> +	if (ret) {
>> +		ath11k_warn(ar->ab, "failed to send WMI_HW_DATA_FILTER_CMDID\n");
>> +		dev_kfree_skb(skb);
>> +	}
>> +
>> +	return ret;
>>   }
>>
>
> { .. }
>
>
>> @@ -10053,7 +10127,13 @@ int ath11k_wmi_sta_keepalive(struct ath11k *ar,
>>   		   "sta keepalive vdev %d enabled %d method %d interval %d\n",
>>   		   arg->vdev_id, arg->enabled, arg->method, arg->interval);
>>   
>> -	return ath11k_wmi_cmd_send(wmi, skb, WMI_STA_KEEPALIVE_CMDID);
>> +	ret = ath11k_wmi_cmd_send(wmi, skb, WMI_STA_KEEPALIVE_CMDID);
>> +	if (ret) {
>> +		ath11k_warn(ar->ab, "failed to send WMI_STA_KEEPALIVE_CMDID\n");
>> +		dev_kfree_skb(skb);
>> +	}
>> +
>> +	return ret;
>>   }
>>   
>>   bool ath11k_wmi_supports_6ghz_cc_ext(struct ath11k *ar)
>
>
> Thanks for fixing these. One more instance of the same pattern remains 
> in ath11k_tm_cmd_wmi_ftm().
>
Ha nice catch, I originally skipped it because I saw there was a 'if (ret)' and
assumed it had the proper error handling.

> Please add dev_kfree_skb(skb) before goto out, matching 
> ath11k_tm_cmd_wmi() above.
Yes seems it needs it's own patch and fixes tag. I'll respin the series then.

Thanks

^ permalink raw reply

* Re: [PATCH v2 1/2] wifi: nl80211: require CAP_NET_ADMIN over the target netns in SET_WIPHY_NETNS
From: Maoyi Xie @ 2026-05-06  6:27 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel
In-Reply-To: <4a80d9a16c37af6f35904b5298238f64f030c374.camel@sipsolutions.net>

Hi Johannes,

Thanks for the catch on both.

>> Reported-by: Maoyi Xie <maoyi.xie@ntu.edu.sg>

> Crediting yourself with finding the bug seems ... a bit weird?

The Reported-by line was a mistake. We will drop it in v3.

> Bot reports

>  WARNING: From:/Signed-off-by: email address mismatch: 'From:
> Maoyi Xie <maoyixie.tju@gmail.com>' != 'Signed-off-by: Maoyi Xie
> <maoyi.xie@ntu.edu.sg>'

> so wonder if that was intentional? Maybe you wanted to include
> another

The From and Signed-off-by mismatch was not intentional. The
patch is authored from maoyi.xie@ntu.edu.sg, but our NTU SMTP
does not accept git send-email. We sent the series through
Gmail with maoyixie.tju@gmail.com as the envelope sender, and
Gmail rewrote the From header.

In v3 we will set the email header From to the Gmail address and
add an explicit From: line at the top of the commit body, with the
NTU address, so the trailer matches the author.

Maoyi
Nanyang Technological University
https://maoyixie.com/

^ permalink raw reply

* [PATCH v3 0/2] wifi: nl80211: tighten netns handling in SET_WIPHY_NETNS and dump continuation
From: Maoyi Xie @ 2026-05-06  6:48 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel, Maoyi Xie

From: Maoyi Xie <maoyi.xie@ntu.edu.sg>

This series addresses two related namespace handling gaps in
nl80211 that came up during a recent inquiry on the list. Both
follow the netdev convention.

Patch 1/2: NL80211_CMD_SET_WIPHY_NETNS uses GENL_UNS_ADMIN_PERM,
which only checks CAP_NET_ADMIN over the source netns. The target
netns selected via NL80211_ATTR_NETNS_FD or NL80211_ATTR_PID is
not checked. Mirror the rtnetlink convention spelled out by
rtnl_get_net_ns_capable() and require ns_capable(target_net->user_ns,
CAP_NET_ADMIN) on the resolved target netns. Reachable from an
unprivileged user namespace whenever the caller already holds a
WIPHY_FLAG_NETNS_OK wiphy in their own netns (mac80211_hwsim, or
an admin-delegated container scenario). PoC reproduces on a KASAN
VM with mac80211_hwsim and shows the call now returns -EPERM.

Patch 2/2: nl80211_prepare_wdev_dump() validates the wdev's netns
on the first dumpit invocation but not on subsequent ones, where
it looks up the wiphy by global index. If the wiphy moves netns
between dumpit invocations (via SET_WIPHY_NETNS), the dump silently
keeps copying BSS list contents from the wiphy's new netns into
the caller's netns. Add a net_eq() check on the continuation path
so the dump terminates cleanly. This is mostly a clarity and
convention fix. Combined with 1/2 it also closes the path by
which an unprivileged-userns caller could trigger the race
themselves.

Changes since v2:
  Both patches: dropped the Reported-by trailer that pointed to
    the same address as Signed-off-by, per Johannes's review.
  Both patches: added an explicit From: line at the top of the
    commit body so the email header From and the Signed-off-by
    trailer match the patch author, addressing the bot warning
    Johannes flagged on v2 1/2.
  No code changes since v2.

Changes since v1:
  Patch 2/2: trimmed the comment per Johannes's review. The
    comment now describes the invariant being protected (wiphy
    may move between dumpit invocations) rather than referencing
    other dump callers as motivation.
  Patch 1/2: unchanged.

Maoyi Xie (2):
  wifi: nl80211: require CAP_NET_ADMIN over the target netns in
    SET_WIPHY_NETNS
  wifi: nl80211: re-check wiphy netns in nl80211_prepare_wdev_dump()
    continuation

 net/wireless/nl80211.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)


base-commit: 65493f27a6008bf84bd11bd41c5e1ea6b0bf3c3d
-- 
2.34.1


^ permalink raw reply

* [PATCH v3 1/2] wifi: nl80211: require CAP_NET_ADMIN over the target netns in SET_WIPHY_NETNS
From: Maoyi Xie @ 2026-05-06  6:48 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel, Maoyi Xie
In-Reply-To: <20260506064854.2207105-1-maoyixie.tju@gmail.com>

From: Maoyi Xie <maoyi.xie@ntu.edu.sg>

NL80211_CMD_SET_WIPHY_NETNS dispatches with GENL_UNS_ADMIN_PERM, which
verifies that the caller has CAP_NET_ADMIN over the user namespace
owning the source netns (the netlink socket's netns). It does not
verify that the caller has CAP_NET_ADMIN over the target netns
selected by NL80211_ATTR_NETNS_FD or NL80211_ATTR_PID.

This diverges from the convention enforced in
net/core/rtnetlink.c::rtnl_get_net_ns_capable():

    /* For now, the caller is required to have CAP_NET_ADMIN in
     * the user namespace owning the target net ns.
     */
    if (!sk_ns_capable(sk, net->user_ns, CAP_NET_ADMIN))
        return ERR_PTR(-EACCES);

A user with CAP_NET_ADMIN in their own user namespace can therefore
push a wiphy into an arbitrary netns (including init_net) over which
they have no privilege.

Reachable from an unprivileged user namespace as soon as the caller
holds, in their own netns, a wiphy that has WIPHY_FLAG_NETNS_OK set
(true for mac80211_hwsim and for any wiphy that an administrator has
delegated into a container).

Reproducer (mac80211_hwsim, KASAN VM):

    1. As real root, modprobe mac80211_hwsim radios=1 in init_net.
    2. fork(); child unshare(CLONE_NEWUSER | CLONE_NEWNET) and writes
       0-mapped uid_map.
    3. Real root migrates phyN into the child's netns via
       NL80211_CMD_SET_WIPHY_NETNS (legitimate admin step).
    4. Child, with CAP_NET_ADMIN only in its own user_ns, sends
       NL80211_CMD_SET_WIPHY_NETNS targeting init_net's netns fd.
    5. The kernel honours the request and the wiphy is moved back
       to init_net even though the caller has no privilege there.

Mirror the rtnetlink convention by requiring ns_capable(net->user_ns,
CAP_NET_ADMIN) on the resolved target netns before calling
cfg80211_switch_netns().

Signed-off-by: Maoyi Xie <maoyi.xie@ntu.edu.sg>
---
 net/wireless/nl80211.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 67088804d..db546dd93 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -13867,6 +13867,19 @@ static int nl80211_wiphy_netns(struct sk_buff *skb, struct genl_info *info)
 	if (IS_ERR(net))
 		return PTR_ERR(net);
 
+	/*
+	 * The caller already has CAP_NET_ADMIN over the source netns
+	 * (enforced by GENL_UNS_ADMIN_PERM on the genl op). Mirror the
+	 * convention used by net/core/rtnetlink.c::rtnl_get_net_ns_capable()
+	 * and require CAP_NET_ADMIN over the target netns as well, so that
+	 * a caller that is privileged in their own user namespace cannot
+	 * push a wiphy into a netns where they have no privilege.
+	 */
+	if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) {
+		put_net(net);
+		return -EPERM;
+	}
+
 	err = 0;
 
 	/* check if anything to do */
-- 
2.34.1


^ permalink raw reply related

* [PATCH v3 2/2] wifi: nl80211: re-check wiphy netns in nl80211_prepare_wdev_dump() continuation
From: Maoyi Xie @ 2026-05-06  6:48 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, linux-kernel, Maoyi Xie
In-Reply-To: <20260506064854.2207105-1-maoyixie.tju@gmail.com>

From: Maoyi Xie <maoyi.xie@ntu.edu.sg>

NL80211_CMD_GET_SCAN is implemented as a multi-call dumpit. The first
invocation of nl80211_prepare_wdev_dump() validates the requested wdev
against the caller's netns via __cfg80211_wdev_from_attrs(). Subsequent
invocations look up the same wiphy by global index via
wiphy_idx_to_wiphy() and do not re-check that the wiphy is still in
the caller's netns.

If the wiphy is moved between dumpit invocations (via
NL80211_CMD_SET_WIPHY_NETNS), the dump silently continues to copy BSS
list contents from the wiphy's new netns into the caller's netns
socket buffer. The other dump paths in nl80211.c (e.g.
nl80211_dump_wiphy() and the parallel scheduled scan dump) already
filter by net_eq(wiphy_net(...), sock_net(skb->sk)) on every iteration.

Add the same filter to the continuation path. If the wiphy's netns no
longer matches the caller's, return -ENODEV and the netlink dump
machinery terminates the walk cleanly.

This is most usefully fixed alongside the SET_WIPHY_NETNS target-cap
hardening in patch 1/2, which closes the path by which an
unprivileged-userns caller could trigger this race themselves.

Signed-off-by: Maoyi Xie <maoyi.xie@ntu.edu.sg>
---
 net/wireless/nl80211.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index db546dd93..f2c91a939 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -1276,6 +1276,16 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb,
 			rtnl_unlock();
 			return -ENODEV;
 		}
+		/*
+		 * The first invocation validated the wdev's netns against
+		 * the caller via __cfg80211_wdev_from_attrs(). The wiphy
+		 * may have moved netns between dumpit invocations (via
+		 * NL80211_CMD_SET_WIPHY_NETNS), so re-check here.
+		 */
+		if (!net_eq(wiphy_net(wiphy), sock_net(cb->skb->sk))) {
+			rtnl_unlock();
+			return -ENODEV;
+		}
 		*rdev = wiphy_to_rdev(wiphy);
 		*wdev = NULL;
 
-- 
2.34.1


^ permalink raw reply related

* [PATCH v3] wifi: mt76: mt7921: Add PCIe AER handler support to prevent system crash
From: JB Tsai @ 2026-05-06  7:04 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
	Ryder.Lee, Leon.Yen, litien.chang, eason.lai, jb.tsai, Eason Lai,
	Sean Wang, Jeff Hsu, Michael Lo

From: Eason Lai <Eason.Lai@mediatek.com>

When an AER error occurs and the bus is hung, the register reads return
0xFFFFFFFF, causing the DMA queue state to be corrupted and resulting in
an invalid memory access when accessing q->desc[] or q->entry[].

Unable to handle kernel paging request at virtual address
ffffffc01099eac0
pc : mt76_dma_add_buf+0x124/0x188 [mt76]
lr : mt76_dma_rx_fill+0x11c/0x1d8 [mt76]
sp : ffffffc016d9bbf0
x29: ffffffc016d9bc10 x28: 0000000000000000
x27: 0000000000000000 x26: ffffffb7855e50b8
x25: ffffffb80d04f000 x24: 0000000000000000
x23: 0000000000000ec0 x22: ffffffb796803648
x21: ffffffb796801f80 x20: ffffffb7968035f8
x19: 0000000000000ec0 x18: 0000000000000000
x17: 000000004ec00000 x16: 000000000ec00000
x15: ffffffc01099eac0 x14: 000000004ec00000
x13: 00000000ffc5a000 x12: ffffffc016d9bc32
x11: 00000000ffffffff x10: 0000000000000002
x9 : 0000000000000000 x8 : 000000000000b4ac
x7 : 0000000000000a20 x6 : ffffffb6c1806400
x5 : 0000000000000000 x4 : ffffffb80d04f000
x3 : 0000000000000000 x2 : 0000000000000001
x1 : 000000000ec04000 x0 : ffffffb7968035f8
Call trace:
 mt76_dma_add_buf+0x124/0x188 [mt76 (HASH:1029 4)]
 mt76_dma_rx_reset+0xe8/0xfc [mt76 (HASH:1029 4)]
 mt7921_wpdma_reset+0x188/0x1b0 [mt7921e (HASH:ee48 5)]
 mt7921e_mac_reset+0x128/0x418 [mt7921e (HASH:ee48 5)]
 mt7921_mac_reset_work+0xac/0x1a8 [mt7921_common (HASH:f721 6)]
 process_one_work+0x188/0x514
 worker_thread+0x12c/0x300
 kthread+0x140/0x1fc
 ret_from_fork+0x10/0x30

Fix this by checking the bus_hung flag in mt7921_mac_reset_work() before
attempting the reset sequence, and by installing no-op bus operations
when an unrecoverable AER error is detected, preventing further invalid
hardware accesses.

Due to hardware limitations - such as the lack of a connected hardware
reset pin or the absence of host re-probe functionality - affected Wi-Fi
devices may not fully recover to a normal operational state after
certain errors, even with AER enabled.

Fixes: 17f1de56df05 ("mt76: add common code shared between multiple chipsets")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Co-developed-by: Jeff Hsu <jeff.hsu@mediatek.com>
Signed-off-by: Jeff Hsu <jeff.hsu@mediatek.com>
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
Co-developed-by: Michael Lo <michael.lo@mediatek.com>
---
v2: remove the timestamp of error call trace
v3: fix bus hung and causing invalid access issue
---
 drivers/net/wireless/mediatek/mt76/dma.c      |  10 +-
 drivers/net/wireless/mediatek/mt76/dma.h      |  16 ++-
 drivers/net/wireless/mediatek/mt76/mcu.c      |  12 +-
 .../net/wireless/mediatek/mt76/mt76_connac.h  |   5 +
 .../net/wireless/mediatek/mt76/mt7921/mac.c   |   3 +
 .../net/wireless/mediatek/mt76/mt7921/pci.c   | 103 ++++++++++++++++++
 6 files changed, 140 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
index f8c2fe5f2f58..ec84961b11db 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.c
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -189,6 +189,8 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q)
 static void
 mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q)
 {
+	int last = 0;
+
 	if ((q->flags & MT_QFLAG_WED_RRO_EN) &&
 	    (!is_mt7992(dev) || !mt76_npu_device_active(dev)))
 		Q_WRITE(q, ring_size, MT_DMA_RRO_EN | q->ndesc);
@@ -201,7 +203,9 @@ mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q)
 	}
 
 	Q_WRITE(q, desc_base, q->desc_dma);
-	q->head = Q_READ(q, dma_idx);
+
+	last = Q_READ(q, dma_idx);
+	q->head = (last >= 0 && last < q->ndesc) ? last : 0;
 	q->tail = q->head;
 }
 
@@ -625,8 +629,8 @@ mt76_dma_tx_queue_skb_raw(struct mt76_dev *dev, struct mt76_queue *q,
 	buf.len = skb->len;
 
 	spin_lock_bh(&q->lock);
-	mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL);
-	mt76_dma_kick_queue(dev, q);
+	if (mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL) >= 0)
+		mt76_dma_kick_queue(dev, q);
 	spin_unlock_bh(&q->lock);
 
 	return 0;
diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h
index 2a0226c83f3c..7f395a4c509f 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.h
+++ b/drivers/net/wireless/mediatek/mt76/dma.h
@@ -108,8 +108,20 @@
 
 #else
 
-#define Q_READ(_q, _field)		readl(&(_q)->regs->_field)
-#define Q_WRITE(_q, _field, _val)	writel(_val, &(_q)->regs->_field)
+#define Q_READ(_q, _field) ({						\
+	u32 _val;							\
+	if (unlikely(atomic_read(&(_q)->dev->bus_hung)))		\
+		_val = 0;						\
+	else								\
+		_val = readl(&(_q)->regs->(_field));			\
+	_val;								\
+})
+
+#define Q_WRITE(_q, _field, _val) do {					\
+	if (unlikely(atomic_read(&(_q)->dev->bus_hung)))		\
+		break;							\
+	writel(_val, &(_q)->regs->(_field));				\
+} while (0)
 
 #endif
 
diff --git a/drivers/net/wireless/mediatek/mt76/mcu.c b/drivers/net/wireless/mediatek/mt76/mcu.c
index cbfb3bbec503..7149b2f7aafd 100644
--- a/drivers/net/wireless/mediatek/mt76/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mcu.c
@@ -78,15 +78,19 @@ int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb,
 	unsigned long expires;
 	int ret, seq;
 
-	if (mt76_is_sdio(dev))
-		if (test_bit(MT76_RESET, &dev->phy.state) && atomic_read(&dev->bus_hung))
-			return -EIO;
-
 	if (ret_skb)
 		*ret_skb = NULL;
 
 	mutex_lock(&dev->mcu.mutex);
 
+	if ((mt76_is_mmio(dev) && atomic_read(&dev->bus_hung)) ||
+	    (mt76_is_sdio(dev) && test_bit(MT76_RESET, &dev->phy.state) &&
+	    atomic_read(&dev->bus_hung))) {
+		orig_skb = skb;
+		ret = -EIO;
+		goto out;
+	}
+
 	if (dev->mcu_ops->mcu_skb_prepare_msg) {
 		orig_skb = skb;
 		ret = dev->mcu_ops->mcu_skb_prepare_msg(dev, skb, cmd, &seq);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
index 51423c7740bd..17f9c6e65849 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
@@ -48,6 +48,11 @@ enum rx_pkt_type {
 #define MT_TXD_LEN_MSDU_LAST		BIT(14)
 #define MT_TXD_LEN_AMSDU_LAST		BIT(15)
 
+/* PCIE part */
+#define PCIE_AER_UNC_STATUS_OFFSET  0x204
+#define PCIE_AER_UNC_MASK_OFFSET    0x208
+#define PCIE_AER_CO_STATUS_OFFSET   0x210
+
 enum {
 	CMD_CBW_20MHZ = IEEE80211_STA_RX_BW_20,
 	CMD_CBW_40MHZ = IEEE80211_STA_RX_BW_40,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..30f431896b1c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -668,6 +668,9 @@ void mt7921_mac_reset_work(struct work_struct *work)
 	cancel_work_sync(&pm->wake_work);
 
 	for (i = 0; i < 10; i++) {
+		if (atomic_read(&dev->mt76.bus_hung))
+			return;
+
 		mutex_lock(&dev->mt76.mutex);
 		ret = mt792x_dev_reset(dev);
 		mutex_unlock(&dev->mt76.mutex);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index 7a790ddf43bb..6b9d267a7e7b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -594,6 +594,108 @@ static int mt7921_pci_resume(struct device *device)
 	return err;
 }
 
+static u32 mt7921_aer_rr(struct mt76_dev *mdev, u32 offset)
+{
+	return 0;
+}
+
+static void mt7921_aer_wr(struct mt76_dev *mdev, u32 offset, u32 val)
+{
+	;
+}
+
+static u32 mt791_aer_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val)
+{
+	return 0;
+}
+
+static const struct mt76_bus_ops mt7921_aer_bus_hung_ops = {
+	.rr = mt7921_aer_rr,
+	.wr = mt7921_aer_wr,
+	.rmw = mt791_aer_rmw,
+	.type = MT76_BUS_MMIO
+};
+
+static void mt7921_pci_set_aer_bus_hung_ops(struct mt792x_dev *dev)
+{
+	if (READ_ONCE(dev->mt76.bus) == &mt7921_aer_bus_hung_ops)
+		return;
+
+	atomic_set(&dev->mt76.bus_hung, true);
+	WRITE_ONCE(dev->mt76.bus, &mt7921_aer_bus_hung_ops);
+}
+
+static pci_ers_result_t mt7921_error_detected(struct pci_dev *pdev,
+					      pci_channel_state_t state)
+{
+	struct mt76_dev *mdev = pci_get_drvdata(pdev);
+	struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
+	u32 aer_unc_val = 0, aer_co_val = 0;
+
+	dev_err(mdev->dev, "PCIE error detect state: %d\n", state);
+
+	/* Clear SW IRQ tasklet first */
+	tasklet_kill(&mdev->irq_tasklet);
+
+	if (state == pci_channel_io_perm_failure) {
+		mt7921_pci_set_aer_bus_hung_ops(dev);
+		return PCI_ERS_RESULT_DISCONNECT;
+	}
+
+	pci_read_config_dword(pdev, PCIE_AER_UNC_STATUS_OFFSET, &aer_unc_val);
+	pci_read_config_dword(pdev, PCIE_AER_CO_STATUS_OFFSET, &aer_co_val);
+
+	dev_warn(mdev->dev, "PCIE_AER_UNC_STATUS_OFFSET: 0x%x\n", aer_unc_val);
+	dev_warn(mdev->dev, "PCIE_AER_CO_STATUS_OFFSET: 0x%x\n", aer_co_val);
+
+	/**
+	 * Due to this error is from link error and this AER is un-correctable,
+	 * so can't covered by device
+	 **/
+	if (aer_unc_val != 0) {
+		mt7921_pci_set_aer_bus_hung_ops(dev);
+		return PCI_ERS_RESULT_DISCONNECT;
+	}
+
+	/**
+	 * Try to recover it when state is pci_channel_io_frozen or
+	 * AER is correctable error
+	 **/
+	if (state == pci_channel_io_frozen || aer_co_val != 0) {
+		/* Disable PCIE activity first. */
+		pci_disable_device(pdev);
+		return PCI_ERS_RESULT_NEED_RESET;
+	}
+
+	return PCI_ERS_RESULT_NONE;
+}
+
+static pci_ers_result_t mt7921_slot_reset(struct pci_dev *pdev)
+{
+	struct mt76_dev *mdev = pci_get_drvdata(pdev);
+	int ret = 0;
+
+	ret = pci_enable_device_mem(pdev);
+
+	if (ret) {
+		dev_err(mdev->dev, "pci_enable_device_mem failed: %d\n", ret);
+		return PCI_ERS_RESULT_DISCONNECT;
+	}
+
+	pci_set_master(pdev);
+	pci_restore_state(pdev);
+	pci_save_state(pdev);
+	/* Also try do the vendor reset to let it more clear. */
+	mt792x_reset(mdev);
+
+	return PCI_ERS_RESULT_RECOVERED;
+}
+
+static const struct pci_error_handlers mt7921_err_handler = {
+	.error_detected = mt7921_error_detected,
+	.slot_reset     = mt7921_slot_reset,
+};
+
 static void mt7921_pci_shutdown(struct pci_dev *pdev)
 {
 	mt7921_pci_remove(pdev);
@@ -608,6 +710,7 @@ static struct pci_driver mt7921_pci_driver = {
 	.remove		= mt7921_pci_remove,
 	.shutdown	= mt7921_pci_shutdown,
 	.driver.pm	= pm_sleep_ptr(&mt7921_pm_ops),
+	.err_handler    = &mt7921_err_handler,
 };
 
 module_pci_driver(mt7921_pci_driver);
-- 
2.45.2


^ permalink raw reply related

* Re: [PATCH 1/2] wifi: mac80211: use aesgcm library
From: Johannes Berg @ 2026-05-06  7:06 UTC (permalink / raw)
  To: Eric Biggers; +Cc: linux-wireless, linux-crypto
In-Reply-To: <20260505221653.GA10301@quark>

Hi Eric,

> I really appreciate the enthusiasm for the crypto library!

:)

> And it isn't
> surprising, since it's clearly the way to go.

I was kinda just playing with it, having been reminded that some code
was already ported.

> But I do think these two
> patches are jumping the gun a bit, since we haven't yet migrated all the
> optimized AES-GCM code into the library, or added an improved AES-GCM
> API that provides enough functionality to fulfill all the in-kernel use
> cases (for example, incremental computation of AES-GMAC).

> So as-is these two patches could regress performance in some cases
> (despite the library having less overhead).

Fair. I don't think the performance matters all that much (though I
shouldn't lie about it in the commit log) since this is mostly used for
testing - I don't think there are many users of software crypto beyond
that. Some, for sure, but I believe those are all old drivers that will
get you a maximum of ~25 Mbps throughput (both directions combined) if
you're lucky :)

Anyway, I don't really disagree either, none of this is urgent or
important at this point.

Also, there's a separate conversation to be had here - I was looking and
we also instantiate ccm(aes) and ctr(aes) in mac80211, and I didn't find
equivalent library calls for those.

> And also the AES-GCM API is
> likely to change a bit.  In particular I don't think code outside the
> crypto subsystem should be constructing its own AES-GMAC by combining
> the GHASH functions with the AES functions, as your second patch does.
> Instead they should invoke an AES-GMAC API (or AES-GCM, of which
> AES-GMAC is a special case) provided by lib/crypto/.

I _was_ thinking that could be better ... and forgot that GMAC is just a
GCM special case, despite obviously constructing it by hand. Oops.

I actually thought about exporting aesgcm_mac(), but of course that'd
basically be equivalent to just using aesgcm_encrypt() without data.

However, both of them can only use a single buffer for the associated
data, so they can't be used here. The crypto API used sg tables which
aren't great either, but definitely more flexible than the current
function. Note that in this case I actually need to use three or four
AAD buffers:

 - the pseudo-header constructed outside the frame buffer specifically
   for WiFi, representing the frame header but not exactly the same
 - for beacons an 8-byte zero buffer representing the Timestamp
 - the frame payload without the MIC
   (and without the Timestamp for beacons)
 - a 16-byte zero buffer representing the MIC

This would require a more specific GMAC API like the CMAC API, or,
equivalently but more flexible, an init/aad_update/data_update/final GCM
API. Could even have

  aes_gcm_init()
  aes_gcm_update_aad()
  aes_gcm_update_data()
  aes_gcm_final()

and
  #define aes_gmac_init aes_gcm_init
  #define aes_gmac_update aes_gmac_update_aad
  #define aes_gmac_final aes_gcm_final

or something like that, I guess.

> So I'd ask that we wait just a bit until I can finish getting the
> AES-GCM library APIs into a good state.  I got a lot of the prerequisite
> work in for 7.0 and 7.1, and I'll see if I can finish it in 7.2.  I've
> just been a bit busy with other things in the past few weeks.

Sure, no hurry, was mostly playing with how that'd look like. Maybe it
even helps figure out the right APIs ;-)

Thanks,
johannes

^ permalink raw reply

* Re: [PATCH rtw-next v2] wifi: rtl8xxxu: Detect the maximum supported channel width
From: Ping-Ke Shih @ 2026-05-06  7:57 UTC (permalink / raw)
  To: Bitterblue Smith, linux-wireless@vger.kernel.org
  Cc: Ping-Ke Shih, Jes Sorensen, art1310
In-Reply-To: <c57de68e-5d57-4c26-898f-8a284bb25381@gmail.com>

Bitterblue Smith <rtl8821cerfe2@gmail.com> wrote:

> Some devices malfunction when connected to a network with 40 MHz channel
> width, because they don't support that.
> 
> RTL8188FU, RTL8192FU, and RTL8710BU (RTL8188GU) have a way to signal
> this (and some other capabilities) to the driver. Get this information
> from the hardware and advertise 40 MHz support only when the hardware
> can handle it. We assume the other chips can always handle it.
> 
> RTL8710BU needs a different way to retrieve this information, which will
> be implemented some other time.
> 
> Fixes: dbf9b7bb0edf ("wifi: rtl8xxxu: Enable 40 MHz width by default")
> Cc: stable@vger.kernel.org
> Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221394
> Signed-off-by: Bitterblue Smith <rtl8821cerfe2@gmail.com>
> Reviewed-by: Ping-Ke Shih <pkshih@realtek.com>

1 patch(es) applied to rtw-next branch of rtw.git, thanks.

ef771eabc79d wifi: rtl8xxxu: Detect the maximum supported channel width

---
https://github.com/pkshih/rtw.git


^ permalink raw reply

* Re: [PATCH rtw-next 1/7] wifi: rtw89: mlo: rearrange MLSR link decision flow
From: Ping-Ke Shih @ 2026-05-06  8:23 UTC (permalink / raw)
  To: Ping-Ke Shih, linux-wireless; +Cc: damon.chen
In-Reply-To: <20260429132625.1659182-2-pkshih@realtek.com>

Ping-Ke Shih <pkshih@realtek.com> wrote:

> From: Kuan-Chung Chen <damon.chen@realtek.com>
> 
> The original MLSR link decision refers to RSSI, but it should be
> based on the premise of an existing link. Otherwise, make a link
> decision to select a new link from any available band.
> 
> Signed-off-by: Kuan-Chung Chen <damon.chen@realtek.com>
> Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>

7 patch(es) applied to rtw-next branch of rtw.git, thanks.

7284f5be5d29 wifi: rtw89: mlo: rearrange MLSR link decision flow
09d369c66373 wifi: rtw89: phy: support per PHY RX statistics
523d012f47e7 wifi: rtw89: debug: bb_info entry including TX rate count for WiFi 7 chips
3b851f36c3f1 wifi: rtw89: debug: add PMAC counter in bb_info
419ed7f4a053 wifi: rtw89: debug: extend bb_info with TX status and PER
c77bb66e244e wifi: rtw89: debug: add RX statistics in bb_info
c1ed02655f91 wifi: rtw89: debug: add BB diagnose

---
https://github.com/pkshih/rtw.git


^ permalink raw reply

* RE: [PATCH] wifi: rtw88: increase TX report timeout to fix race condition
From: Ping-Ke Shih @ 2026-05-06  8:23 UTC (permalink / raw)
  To: Luka Gejak, Bitterblue Smith, Kalle Valo
  Cc: Yan-Hsuan Chuang, Brian Norris, Stanislaw Gruszka,
	linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
	stable@vger.kernel.org
In-Reply-To: <39E7B292-F03C-4307-B0BE-62DEC191FED8@linux.dev>

> >> I also realize now that globally changing RTW_TX_PROBE_TIMEOUT to
> >> 2500ms is too heavy-handed. Since this impacts all rtw88 chips,
> >> including PCIe variants where 500ms might be exactly what is needed to
> >> catch a real firmware lockup, the blast radius is too large. How would
> >> you prefer I handle this for the v2 patch? I can either implement a
> >> more conservative global bump, or make the timeout dynamic based on
> >> the HCI interface so USB devices get a longer timeout to accommodate
> >> the bus latency during scans.

I'd limit this change to USB devices, even limit to RTL8723DU you tested. 
As RTL8822CU supports hw_scan, the symptom might be different. 

Ping-Ke


^ permalink raw reply

* RE: [PATCH] wifi: rtw88: usb: fix memory leaks on USB write failures
From: Ping-Ke Shih @ 2026-05-06  8:41 UTC (permalink / raw)
  To: luka.gejak@linux.dev, Kalle Valo
  Cc: Stanislaw Gruszka, Yan-Hsuan Chuang, Brian Norris,
	linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org,
	stable@vger.kernel.org
In-Reply-To: <20260501162012.250013-1-luka.gejak@linux.dev>

luka.gejak@linux.dev <luka.gejak@linux.dev> wrote:
> From: Luka Gejak <luka.gejak@linux.dev>
> 
> When rtw_usb_write_port() fails to submit a USB Request Block (URB)
> (e.g., due to device disconnect or ENOMEM), the completion callback is
> never executed.
> 
> Currently, the driver ignores the return value of rtw_usb_write_port()
> in rtw_usb_write_data() and rtw_usb_tx_agg_skb(). Because these
> functions rely on the completion callback to free the socket buffers
> (skbs) and the transaction control block (txcb), a submission failure
> results in:
> 1. A memory leak of the allocated skb in rtw_usb_write_data().
> 2. A memory leak of the txcb structure and all aggregated skbs in
>    rtw_usb_tx_agg_skb().
> 
> Fix this by checking the return value of rtw_usb_write_port(). If it
> fails, explicitly free the skb in rtw_usb_write_data(), and properly
> purge the tx_ack_queue and free the txcb in rtw_usb_tx_agg_skb().
> 
> Fixes: e3037485c68e ("rtw88: new Realtek 802.11ac driver")

This commit doesn't introduce USB support.

> Cc: stable@vger.kernel.org
> Tested-by: Luka Gejak <luka.gejak@linux.dev>

How did you test this patch? Have you really encountered leak problem and
fixed it by this patch? Or make this patch by analysis, and test it
without regression?

> Signed-off-by: Luka Gejak <luka.gejak@linux.dev>
> ---
>  drivers/net/wireless/realtek/rtw88/usb.c | 12 ++++++++++--
>  1 file changed, 10 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/wireless/realtek/rtw88/usb.c b/drivers/net/wireless/realtek/rtw88/usb.c
> index 718940ebba31..d430645a3ef3 100644
> --- a/drivers/net/wireless/realtek/rtw88/usb.c
> +++ b/drivers/net/wireless/realtek/rtw88/usb.c
> @@ -456,7 +456,13 @@ static bool rtw_usb_tx_agg_skb(struct rtw_usb *rtwusb, struct sk_buff_head *list
>         tx_desc = (struct rtw_tx_desc *)skb_head->data;
>         qsel = le32_get_bits(tx_desc->w1, RTW_TX_DESC_W1_QSEL);
> 
> -       rtw_usb_write_port(rtwdev, qsel, skb_head, rtw_usb_write_port_tx_complete, txcb);
> +       if (rtw_usb_write_port(rtwdev, qsel, skb_head,
> +                              rtw_usb_write_port_tx_complete, txcb)) {

Can we use 'ret = rtw_usb_write_port(...);' style, and check by next line?
Like the other one modified by this patch.

> +               /* URB submission failed, completion won't run, free the queue */

I think this comment is not necessary. 

> +               skb_queue_purge(&txcb->tx_ack_queue);

Should use ieee80211_purge_tx_queue() since this is TX? 

> +               kfree(txcb);
> +               return false;
> +       }
> 
>         return true;
>  }
> @@ -518,8 +524,10 @@ static int rtw_usb_write_data(struct rtw_dev *rtwdev,
> 
>         ret = rtw_usb_write_port(rtwdev, qsel, skb,
>                                  rtw_usb_write_port_complete, skb);
> -       if (unlikely(ret))
> +       if (unlikely(ret)) {
>                 rtw_err(rtwdev, "failed to do USB write, ret=%d\n", ret);
> +               dev_kfree_skb_any(skb);
> +       }
> 
>         return ret;
>  }
> --
> 2.54.0


^ permalink raw reply

* [PATCH v3] wifi: mt76: mt792x: fix use-after-free in mt76_rx_poll_complete
From: JB Tsai @ 2026-05-06  8:43 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
	Ryder.Lee, Leon.Yen, litien.chang, eason.lai, jb.tsai, Eason Lai

From: Eason Lai <Eason.Lai@mediatek.com>

A use-after-free issue occurs in mt76_rx_poll_complete due to a race
condition. The STA has already been removed, but the rx_status still
had a pointer to the wcid in the STA.

Use wcid_idx instead of storing the wcid pointer, and look up the wcid
via rcu_dereference() by wcid_idx.

BUG: KASAN: invalid-access in mt76_rx_poll_complete+0x280/0x470 [mt76]
Call trace:
dump_backtrace+0xec/0x128
show_stack+0x18/0x28
dump_stack_lvl+0x40/0xc8
print_report+0x1b8/0x710
kasan_report+0xe0/0x144
do_bad_area+0x120/0x260
do_tag_check_fault+0x20/0x34
do_mem_abort+0x54/0xa8
el1_abort+0x3c/0x5c
el1h_64_sync_handler+0x40/0xcc
el1h_64_sync+0x7c/0x80
mt76_rx_poll_complete+0x280/0x470
mt76_dma_rx_poll+0x114/0x51c
mt792x_poll_rx+0x60/0xf8
napi_threaded_poll_loop+0xe0/0x450
napi_threaded_poll+0x80/0x9c
kthread+0x11c/0x158
ret_from_fork+0x10/0x20

Fixes: 9c68a57bc22d ("mt76: get station pointer by wcid and pass it to mac80211")
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
---
v2: fix mt76x02 build errors
v3: fix mt76x02 build error due to variable set but not used
---
 drivers/net/wireless/mediatek/mt76/agg-rx.c   | 18 ++++---
 drivers/net/wireless/mediatek/mt76/mac80211.c | 47 ++++++++-----------
 drivers/net/wireless/mediatek/mt76/mt76.h     |  9 ++--
 .../net/wireless/mediatek/mt76/mt7603/mac.c   |  6 ++-
 .../net/wireless/mediatek/mt76/mt7615/mac.c   | 19 +++++---
 .../net/wireless/mediatek/mt76/mt76_connac.h  |  3 +-
 .../wireless/mediatek/mt76/mt76_connac_mac.c  | 12 ++++-
 .../net/wireless/mediatek/mt76/mt76x02_mac.c  |  9 ++--
 .../net/wireless/mediatek/mt76/mt7915/mac.c   | 16 +++++--
 .../net/wireless/mediatek/mt76/mt7921/mac.c   | 13 +++--
 .../net/wireless/mediatek/mt76/mt7925/mac.c   | 19 +++++---
 .../net/wireless/mediatek/mt76/mt7996/mac.c   | 29 ++++++++----
 12 files changed, 118 insertions(+), 82 deletions(-)

diff --git a/drivers/net/wireless/mediatek/mt76/agg-rx.c b/drivers/net/wireless/mediatek/mt76/agg-rx.c
index bf1babac3895..b72d13975466 100644
--- a/drivers/net/wireless/mediatek/mt76/agg-rx.c
+++ b/drivers/net/wireless/mediatek/mt76/agg-rx.c
@@ -116,14 +116,15 @@ mt76_rx_aggr_reorder_work(struct work_struct *work)
 }
 
 static void
-mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
+mt76_rx_aggr_check_ctl(struct mt76_dev *dev, struct sk_buff *skb,
+		       struct sk_buff_head *frames)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ieee80211_bar *bar = mt76_skb_get_hdr(skb);
-	struct mt76_wcid *wcid = status->wcid;
 	struct mt76_rx_tid *tid;
-	u8 tidno;
+	struct mt76_wcid *wcid;
 	u16 seqno;
+	u8 tidno;
 
 	if (!ieee80211_is_ctl(bar->frame_control))
 		return;
@@ -131,6 +132,10 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
 	if (!ieee80211_is_back_req(bar->frame_control))
 		return;
 
+	wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+	if (!wcid)
+		return;
+
 	status->qos_ctl = tidno = le16_to_cpu(bar->control) >> 12;
 	seqno = IEEE80211_SEQ_TO_SN(le16_to_cpu(bar->start_seq_num));
 	tid = rcu_dereference(wcid->aggr[tidno]);
@@ -145,10 +150,11 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
 	spin_unlock_bh(&tid->lock);
 }
 
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+			  struct sk_buff_head *frames)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	struct ieee80211_sta *sta;
 	struct mt76_rx_tid *tid;
 	bool sn_less;
@@ -164,7 +170,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
 
 	if (!status->aggr) {
 		if (!(status->flag & RX_FLAG_8023))
-			mt76_rx_aggr_check_ctl(skb, frames);
+			mt76_rx_aggr_check_ctl(dev, skb, frames);
 		return;
 	}
 
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 4ae5e4715a9c..2933b29782d3 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1283,21 +1283,24 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
 	memcpy(status->chain_signal, mstat.chain_signal,
 	       sizeof(mstat.chain_signal));
 
-	if (mstat.wcid) {
-		status->link_valid = mstat.wcid->link_valid;
-		status->link_id = mstat.wcid->link_id;
-	}
+	if (mstat.wcid_idx != MT76_WCID_IDX_INVALID) {
+		struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, mstat.wcid_idx);
 
-	*sta = wcid_to_sta(mstat.wcid);
+		if (wcid) {
+			status->link_valid = wcid->link_valid;
+			status->link_id = wcid->link_id;
+			*sta = wcid_to_sta(wcid);
+		}
+	}
 	*hw = mt76_phy_hw(dev, mstat.phy_idx);
 }
 
 static void
-mt76_check_ccmp_pn(struct sk_buff *skb)
+mt76_check_ccmp_pn(struct mt76_dev *dev, struct sk_buff *skb)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
-	struct mt76_wcid *wcid = status->wcid;
 	struct ieee80211_hdr *hdr;
+	struct mt76_wcid *wcid;
 	int security_idx;
 	int ret;
 
@@ -1307,6 +1310,7 @@ mt76_check_ccmp_pn(struct sk_buff *skb)
 	if (status->flag & RX_FLAG_ONLY_MONITOR)
 		return;
 
+	wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	if (!wcid || !wcid->rx_check_pn)
 		return;
 
@@ -1354,7 +1358,7 @@ static void
 mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
 		    int len)
 {
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	struct ieee80211_rx_status info = {
 		.enc_flags = status->enc_flags,
 		.rate_idx = status->rate_idx,
@@ -1382,19 +1386,9 @@ mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
 static void
 mt76_airtime_flush_ampdu(struct mt76_dev *dev)
 {
-	struct mt76_wcid *wcid;
-	int wcid_idx;
-
 	if (!dev->rx_ampdu_len)
 		return;
 
-	wcid_idx = dev->rx_ampdu_status.wcid_idx;
-	if (wcid_idx < ARRAY_SIZE(dev->wcid))
-		wcid = rcu_dereference(dev->wcid[wcid_idx]);
-	else
-		wcid = NULL;
-	dev->rx_ampdu_status.wcid = wcid;
-
 	mt76_airtime_report(dev, &dev->rx_ampdu_status, dev->rx_ampdu_len);
 
 	dev->rx_ampdu_len = 0;
@@ -1405,7 +1399,7 @@ static void
 mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 
 	if (!(dev->drv->drv_flags & MT_DRV_SW_RX_AIRTIME))
 		return;
@@ -1418,8 +1412,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
 
 		if (!ether_addr_equal(hdr->addr1, dev->phy.macaddr))
 			return;
-
-		wcid = NULL;
 	}
 
 	if (!(status->flag & RX_FLAG_AMPDU_DETAILS) ||
@@ -1430,7 +1422,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
 		if (!dev->rx_ampdu_len ||
 		    status->ampdu_ref != dev->rx_ampdu_ref) {
 			dev->rx_ampdu_status = *status;
-			dev->rx_ampdu_status.wcid_idx = wcid ? wcid->idx : 0xff;
 			dev->rx_ampdu_ref = status->ampdu_ref;
 		}
 
@@ -1448,7 +1439,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
 	struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
 	struct ieee80211_sta *sta;
 	struct ieee80211_hw *hw;
-	struct mt76_wcid *wcid = status->wcid;
+	struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
 	u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK;
 	bool ps;
 
@@ -1456,8 +1447,10 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
 	if (ieee80211_is_pspoll(hdr->frame_control) && !wcid &&
 	    !(status->flag & RX_FLAG_8023)) {
 		sta = ieee80211_find_sta_by_ifaddr(hw, hdr->addr2, NULL);
-		if (sta)
-			wcid = status->wcid = (struct mt76_wcid *)sta->drv_priv;
+		if (sta) {
+			wcid = (struct mt76_wcid *)sta->drv_priv;
+			status->wcid_idx = wcid->idx;
+		}
 	}
 
 	mt76_airtime_check(dev, skb);
@@ -1521,7 +1514,7 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
 	while ((skb = __skb_dequeue(frames)) != NULL) {
 		struct sk_buff *nskb = skb_shinfo(skb)->frag_list;
 
-		mt76_check_ccmp_pn(skb);
+		mt76_check_ccmp_pn(dev, skb);
 		skb_shinfo(skb)->frag_list = NULL;
 		mt76_rx_convert(dev, skb, &hw, &sta);
 		ieee80211_rx_list(hw, sta, skb, &list);
@@ -1563,7 +1556,7 @@ void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
 		    mt76_npu_device_active(dev))
 			__skb_queue_tail(&frames, skb);
 		else
-			mt76_rx_aggr_reorder(skb, &frames);
+			mt76_rx_aggr_reorder(dev, skb, &frames);
 	}
 
 	mt76_rx_complete(dev, &frames, napi);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..1e1bd410a7a7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -364,6 +364,7 @@ enum mt76_wcid_flags {
 };
 
 #define MT76_N_WCIDS 1088
+#define MT76_WCID_IDX_INVALID		0xffff
 #define MT76_BEACON_MON_MAX_MISS	7
 
 /* stored in ieee80211_tx_info::hw_queue */
@@ -728,10 +729,7 @@ struct mt76_mmio {
 };
 
 struct mt76_rx_status {
-	union {
-		struct mt76_wcid *wcid;
-		u16 wcid_idx;
-	};
+	u16 wcid_idx;
 
 	u32 reorder_time;
 
@@ -1793,7 +1791,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
 		      struct napi_struct *napi);
 void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
 			   struct napi_struct *napi);
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames);
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+			  struct sk_buff_head *frames);
 void mt76_testmode_tx_pending(struct mt76_phy *phy);
 void mt76_queue_tx_complete(struct mt76_dev *dev, struct mt76_queue *q,
 			    struct mt76_queue_entry *e);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
index d3110eeb45d7..aeb890df808e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
@@ -513,6 +513,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
 	u32 rxd2 = le32_to_cpu(rxd[2]);
 	bool unicast = rxd1 & MT_RXD1_NORMAL_U2M;
 	bool insert_ccmp_hdr = false;
+	struct mt76_wcid *wcid;
 	bool remove_pad;
 	int idx;
 	int i;
@@ -524,7 +525,8 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
 	i >>= 1;
 
 	idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
-	status->wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+	wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
 	status->band = sband->band;
 	if (i < sband->n_channels)
@@ -672,7 +674,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
 	}
 
 	hdr = (struct ieee80211_hdr *)skb->data;
-	if (!status->wcid || !ieee80211_is_data_qos(hdr->frame_control))
+	if (!wcid || !ieee80211_is_data_qos(hdr->frame_control))
 		return 0;
 
 	status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
index ce0051468501..84be00a13930 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
@@ -246,17 +246,20 @@ static void mt7615_mac_fill_tm_rx(struct mt7615_phy *phy, __le32 *rxv)
 }
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7615_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7615_reverse_frag0_hdr_trans(struct mt7615_dev *dev,
+					  struct sk_buff *skb, u16 hdr_gap)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
-	struct mt7615_sta *msta = (struct mt7615_sta *)status->wcid;
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_vif *vif;
 	struct ieee80211_hdr hdr;
+	struct mt7615_sta *msta;
 	u16 frame_control;
 
+	msta = (struct mt7615_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
 	if (le32_get_bits(rxd[1], MT_RXD1_NORMAL_ADDR_TYPE) !=
 	    MT_RXD1_NORMAL_U2M)
 		return -EINVAL;
@@ -335,6 +338,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 	struct ieee80211_supported_band *sband;
 	struct ieee80211_hdr *hdr;
 	struct mt7615_phy *phy2;
+	struct mt76_wcid *wcid;
 	__le32 *rxd = (__le32 *)skb->data;
 	u32 rxd0 = le32_to_cpu(rxd[0]);
 	u32 rxd1 = le32_to_cpu(rxd[1]);
@@ -378,12 +382,13 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 
 	unicast = (rxd1 & MT_RXD1_NORMAL_ADDR_TYPE) == MT_RXD1_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
-	status->wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+	wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
-	if (status->wcid) {
+	if (wcid) {
 		struct mt7615_sta *msta;
 
-		msta = container_of(status->wcid, struct mt7615_sta, wcid);
+		msta = container_of(wcid, struct mt7615_sta, wcid);
 		mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
 	}
 
@@ -590,7 +595,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 
 	hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
 	if (hdr_trans && ieee80211_has_morefrags(fc)) {
-		if (mt7615_reverse_frag0_hdr_trans(skb, hdr_gap))
+		if (mt7615_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
 			return -EINVAL;
 		hdr_trans = false;
 	} else {
@@ -638,7 +643,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
 		status->flag |= RX_FLAG_8023;
 	}
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (!wcid || !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
index 51423c7740bd..d9ae8d204442 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
@@ -448,7 +448,8 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
 void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
 					 struct sk_buff *skb,
 					 __le32 *rxv, u32 mode);
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+					 struct ieee80211_vif *vif,
 					 struct sk_buff *skb, u16 hdr_offset);
 int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev,
 				  struct mt76_rx_status *status,
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
index 0339e2e7ab60..376d59d517cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
@@ -952,7 +952,8 @@ void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
 EXPORT_SYMBOL_GPL(mt76_connac2_mac_decode_he_radiotap);
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+					 struct ieee80211_vif *vif,
 					 struct sk_buff *skb, u16 hdr_offset)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
@@ -960,6 +961,7 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_hdr hdr;
+	struct mt76_wcid *wcid;
 	u16 frame_control;
 
 	if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -969,7 +971,13 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
 	if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
 		return -EINVAL;
 
-	sta = container_of((void *)status->wcid, struct ieee80211_sta, drv_priv);
+	wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+	if (!wcid)
+		return -EINVAL;
+
+	sta = container_of((void *)wcid, struct ieee80211_sta, drv_priv);
+	if (!sta)
+		return -EINVAL;
 
 	/* store the info from RXD and ethhdr to avoid being overridden */
 	frame_control = le32_get_bits(rxd[6], MT_RXD6_FRAME_CONTROL);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
index 14ee5b3b94d3..0bcc3417ae6a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
@@ -774,16 +774,14 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ieee80211_hdr *hdr;
 	struct mt76x02_rxwi *rxwi = rxi;
-	struct mt76x02_sta *sta;
 	u32 rxinfo = le32_to_cpu(rxwi->rxinfo);
 	u32 ctl = le32_to_cpu(rxwi->ctl);
 	u16 rate = le16_to_cpu(rxwi->rate);
 	u16 tid_sn = le16_to_cpu(rxwi->tid_sn);
-	bool unicast = rxwi->rxinfo & cpu_to_le32(MT_RXINFO_UNICAST);
 	int pad_len = 0, nstreams = dev->mphy.chainmask & 0xf;
+	u8 wcid_idx;
 	s8 signal;
 	u8 pn_len;
-	u8 wcid;
 	int len;
 
 	if (!test_bit(MT76_STATE_RUNNING, &dev->mphy.state))
@@ -799,9 +797,8 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
 		status->flag |= RX_FLAG_IV_STRIPPED;
 	}
 
-	wcid = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
-	sta = mt76x02_rx_get_sta(&dev->mt76, wcid);
-	status->wcid = mt76x02_rx_get_sta_wcid(sta, unicast);
+	wcid_idx = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
+	status->wcid_idx = wcid_idx;
 
 	len = FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl);
 	pn_len = FIELD_GET(MT_RXINFO_PN_LEN, rxinfo);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index cec2c4208255..30672713323f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -280,6 +280,7 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 	u8 mode = 0, qos_ctl = 0;
 	struct mt7915_sta *msta = NULL;
 	u32 csum_status = *(u32 *)skb->cb;
+	struct mt76_wcid *wcid;
 	bool hdr_trans;
 	u16 hdr_gap;
 	u16 seq_ctrl = 0;
@@ -313,10 +314,13 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt7915_rx_get_wcid(dev, idx, unicast);
 
-	if (status->wcid) {
-		msta = container_of(status->wcid, struct mt7915_sta, wcid);
+	wcid = mt7915_rx_get_wcid(dev, idx, unicast);
+
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+	if (wcid) {
+		msta = container_of(wcid, struct mt7915_sta, wcid);
 		mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
 	}
 
@@ -475,7 +479,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 
 		vif = container_of((void *)msta->vif, struct ieee80211_vif,
 				   drv_priv);
-		err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+		err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+							   skb, hdr_gap);
 		if (err)
 			return err;
 
@@ -532,7 +537,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
 	if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
 		mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (status->wcid_idx == MT76_WCID_IDX_INVALID ||
+	    !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..59c64b951678 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -184,6 +184,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	u32 rxd4 = le32_to_cpu(rxd[4]);
 	struct mt792x_sta *msta = NULL;
 	struct mt792x_link_sta *mlink;
+	struct mt76_wcid *wcid;
 	u16 seq_ctrl = 0;
 	__le16 fc = 0;
 	u8 mode = 0;
@@ -211,10 +212,11 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
-	if (status->wcid) {
-		mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+	if (wcid) {
+		mlink = container_of(wcid, struct mt792x_link_sta, wcid);
 		msta = container_of(mlink, struct mt792x_sta, deflink);
 		mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
 	}
@@ -395,7 +397,8 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 
 		vif = container_of((void *)msta->vif, struct ieee80211_vif,
 				   drv_priv);
-		err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+		err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+							   skb, hdr_gap);
 		if (err)
 			return err;
 
@@ -433,7 +436,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
 		mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (!wcid || !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index c47bd812b66b..e429f78596e2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -166,17 +166,20 @@ void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev,
 }
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7925_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7925_reverse_frag0_hdr_trans(struct mt792x_dev *dev,
+					  struct sk_buff *skb, u16 hdr_gap)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
-	struct mt792x_sta *msta = (struct mt792x_sta *)status->wcid;
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_vif *vif;
 	struct ieee80211_hdr hdr;
+	struct mt792x_sta *msta;
 	u16 frame_control;
 
+	msta = (struct mt792x_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
 	if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
 	    MT_RXD3_NORMAL_U2M)
 		return -EINVAL;
@@ -368,6 +371,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	u32 rxd3 = le32_to_cpu(rxd[3]);
 	u32 rxd4 = le32_to_cpu(rxd[4]);
 	struct mt792x_link_sta *mlink;
+	struct mt76_wcid *wcid;
 	u8 mode = 0; /* , band_idx; */
 	u16 seq_ctrl = 0;
 	__le16 fc = 0;
@@ -392,10 +396,11 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 	chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
 
-	if (status->wcid) {
-		mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+	if (wcid) {
+		mlink = container_of(wcid, struct mt792x_link_sta, wcid);
 		mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
 	}
 
@@ -545,7 +550,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 
 	hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
 	if (hdr_trans && ieee80211_has_morefrags(fc)) {
-		if (mt7925_reverse_frag0_hdr_trans(skb, hdr_gap))
+		if (mt7925_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
 			return -EINVAL;
 		hdr_trans = false;
 	} else {
@@ -608,7 +613,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
 		}
 	}
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc))
+	if (!wcid || !ieee80211_is_data_qos(fc))
 		return 0;
 
 	status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index e2a83da3a09c..98c30957dd76 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -186,17 +186,19 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
 }
 
 /* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7996_reverse_frag0_hdr_trans(struct mt7996_dev *dev,
+					  struct sk_buff *skb, u16 hdr_gap)
 {
 	struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
 	struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
-	struct mt7996_sta_link *msta_link = (void *)status->wcid;
-	struct mt7996_sta *msta = msta_link->sta;
+	struct mt76_wcid *wcid = mt76_wcid_ptr(dev, status->wcid_idx);
+	struct mt7996_sta_link *msta_link;
 	struct ieee80211_bss_conf *link_conf;
 	__le32 *rxd = (__le32 *)skb->data;
 	struct ieee80211_sta *sta;
 	struct ieee80211_vif *vif;
 	struct ieee80211_hdr hdr;
+	struct mt7996_sta *msta;
 	u16 frame_control;
 
 	if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -206,10 +208,16 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
 	if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
 		return -EINVAL;
 
+	if (!wcid)
+		return -EINVAL;
+
+	msta_link = container_of(wcid, struct mt7996_sta_link, wcid);
+	msta = msta_link->sta;
+
 	if (!msta || !msta->vif)
 		return -EINVAL;
 
-	sta = wcid_to_sta(status->wcid);
+	sta = wcid_to_sta(wcid);
 	vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
 	link_conf = rcu_dereference(vif->link_conf[msta_link->wcid.link_id]);
 	if (!link_conf)
@@ -429,6 +437,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 	bool unicast, insert_ccmp_hdr = false;
 	u8 remove_pad, amsdu_info, band_idx;
 	u8 mode = 0, qos_ctl = 0;
+	struct mt76_wcid *wcid;
 	bool hdr_trans;
 	u16 hdr_gap;
 	u16 seq_ctrl = 0;
@@ -461,12 +470,14 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 
 	unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
 	idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
-	status->wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
 
-	if (status->wcid) {
+	wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
+	status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+	if (wcid) {
 		struct mt7996_sta_link *msta_link;
 
-		msta_link = container_of(status->wcid, struct mt7996_sta_link,
+		msta_link = container_of(wcid, struct mt7996_sta_link,
 					 wcid);
 		msta = msta_link->sta;
 		mt76_wcid_add_poll(&dev->mt76, &msta_link->wcid);
@@ -620,7 +631,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 
 	hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
 	if (hdr_trans && ieee80211_has_morefrags(fc)) {
-		if (mt7996_reverse_frag0_hdr_trans(skb, hdr_gap))
+		if (mt7996_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
 			return -EINVAL;
 		hdr_trans = false;
 	} else {
@@ -697,7 +708,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
 		}
 	}
 
-	if (!status->wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
+	if (!wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
 		return 0;
 
 	status->aggr = unicast &&
-- 
2.45.2


^ permalink raw reply related


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