Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH rtw-next 11/14] wifi: rtw89: phy: check length before parsing PHY status IE
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

Hardware might report PHY status IE with unexpected length, and parser
might access out of range. Check the length ahead.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 7fa479ce52e2..7e1182a49942 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -2276,7 +2276,6 @@ static int rtw89_core_rx_parse_phy_sts(struct rtw89_dev *rtwdev,
 		const struct rtw89_phy_sts_iehdr *iehdr = pos;
 
 		ie_len = rtw89_core_get_phy_status_ie_len(rtwdev, iehdr);
-		rtw89_core_process_phy_status_ie(rtwdev, iehdr, phy_ppdu);
 		pos += ie_len;
 		if (pos > end || ie_len == 0) {
 			/* clear pointers to prevent accessing out of IE */
@@ -2285,8 +2284,11 @@ static int rtw89_core_rx_parse_phy_sts(struct rtw89_dev *rtwdev,
 
 			rtw89_debug(rtwdev, RTW89_DBG_TXRX,
 				    "phy status parse failed\n");
+
 			return -EINVAL;
 		}
+
+		rtw89_core_process_phy_status_ie(rtwdev, iehdr, phy_ppdu);
 	}
 
 	rtw89_chip_convert_rpl_to_rssi(rtwdev, phy_ppdu);
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 10/14] wifi: rtw89: add debugfs entry of monitor mode options to capture HE-MU packets
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

To capture HE-MU packets, set BSS color and AID for specific connected
station.

The writing format: <bss color> <aid>
For example,
  $ echo 0x4 0x16 > monitor_opts

Read this entry to get current setting:
  bss_color=0x4 aid=0x16

By the way, add another sec2() function to create debugfs entries to
prevent running smatch timeout.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/debug.c | 57 ++++++++++++++++++++++
 drivers/net/wireless/realtek/rtw89/phy.c   | 23 ++++++---
 drivers/net/wireless/realtek/rtw89/phy.h   |  2 +
 3 files changed, 74 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c
index 6fe8e7254e80..8ee800c76cfe 100644
--- a/drivers/net/wireless/realtek/rtw89/debug.c
+++ b/drivers/net/wireless/realtek/rtw89/debug.c
@@ -93,6 +93,7 @@ struct rtw89_debugfs {
 	struct rtw89_debugfs_priv beacon_info;
 	struct rtw89_debugfs_priv diag_mac;
 	struct rtw89_debugfs_priv diag_bb;
+	struct rtw89_debugfs_priv monitor_opts;
 };
 
 struct rtw89_debugfs_iter_data {
@@ -5326,6 +5327,54 @@ rtw89_debug_priv_diag_bb_get(struct rtw89_dev *rtwdev,
 	return p - buf;
 }
 
+static ssize_t
+rtw89_debug_priv_monitor_opts_get(struct rtw89_dev *rtwdev,
+				  struct rtw89_debugfs_priv *debugfs_priv,
+				  char *buf, size_t bufsz)
+{
+	const struct rtw89_chip_info *chip = rtwdev->chip;
+	char *p = buf, *end = buf + bufsz;
+	u32 bss_color;
+	u32 aid;
+
+	lockdep_assert_wiphy(rtwdev->hw->wiphy);
+
+	rtw89_leave_ps_mode(rtwdev);
+
+	bss_color = rtw89_phy_read32_idx(rtwdev, chip->bss_clr_map_reg,
+					 B_BSS_CLR_MAP_TGT, RTW89_PHY_0);
+	aid = rtw89_phy_read32_idx(rtwdev, chip->bss_clr_map_reg,
+				   B_BSS_CLR_MAP_STAID, RTW89_PHY_0);
+
+	p += scnprintf(p, end - p, "bss_color=0x%x aid=0x%x\n", bss_color, aid);
+
+	return p - buf;
+}
+
+static ssize_t
+rtw89_debug_priv_monitor_opts_set(struct rtw89_dev *rtwdev,
+				  struct rtw89_debugfs_priv *debugfs_priv,
+				  const char *buf, size_t count)
+{
+	u32 bss_color;
+	u32 aid;
+	int num;
+
+	lockdep_assert_wiphy(rtwdev->hw->wiphy);
+
+	num = sscanf(buf, "%x %x", &bss_color, &aid);
+	if (num != 2) {
+		rtw89_info(rtwdev, "valid format: <bss color> <aid>\n");
+		return -EINVAL;
+	}
+
+	rtw89_leave_ps_mode(rtwdev);
+
+	__rtw89_phy_set_bss_color(rtwdev, bss_color, aid, RTW89_PHY_0);
+
+	return count;
+}
+
 #define rtw89_debug_priv_get(name, opts...)			\
 {								\
 	.cb_read = rtw89_debug_priv_ ##name## _get,		\
@@ -5390,6 +5439,7 @@ static const struct rtw89_debugfs rtw89_debugfs_templ = {
 	.beacon_info = rtw89_debug_priv_get(beacon_info),
 	.diag_mac = rtw89_debug_priv_get(diag_mac, RSIZE_16K, RLOCK),
 	.diag_bb = rtw89_debug_priv_get(diag_bb, RSIZE_8K, RLOCK),
+	.monitor_opts = rtw89_debug_priv_set_and_get(monitor_opts, RWLOCK),
 };
 
 #define rtw89_debugfs_add(name, mode, fopname, parent)				\
@@ -5435,12 +5485,18 @@ void rtw89_debugfs_add_sec1(struct rtw89_dev *rtwdev, struct dentry *debugfs_top
 	rtw89_debugfs_add_r(phy_info);
 	rtw89_debugfs_add_rw(bb_info);
 	rtw89_debugfs_add_r(stations);
+}
+
+static
+void rtw89_debugfs_add_sec2(struct rtw89_dev *rtwdev, struct dentry *debugfs_topdir)
+{
 	rtw89_debugfs_add_rw(disable_dm);
 	rtw89_debugfs_add_rw(static_pd_th);
 	rtw89_debugfs_add_rw(mlo_mode);
 	rtw89_debugfs_add_r(beacon_info);
 	rtw89_debugfs_add_r(diag_mac);
 	rtw89_debugfs_add_r(diag_bb);
+	rtw89_debugfs_add_rw(monitor_opts);
 }
 
 void rtw89_debugfs_init(struct rtw89_dev *rtwdev)
@@ -5457,6 +5513,7 @@ void rtw89_debugfs_init(struct rtw89_dev *rtwdev)
 
 	rtw89_debugfs_add_sec0(rtwdev, debugfs_topdir);
 	rtw89_debugfs_add_sec1(rtwdev, debugfs_topdir);
+	rtw89_debugfs_add_sec2(rtwdev, debugfs_topdir);
 }
 
 void rtw89_debugfs_deinit(struct rtw89_dev *rtwdev)
diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c
index 15483a86951d..759be4dab42b 100644
--- a/drivers/net/wireless/realtek/rtw89/phy.c
+++ b/drivers/net/wireless/realtek/rtw89/phy.c
@@ -8231,12 +8231,24 @@ void rtw89_phy_dm_init_data(struct rtw89_dev *rtwdev)
 		__rtw89_phy_dm_init_data(rtwdev, bb);
 }
 
+void __rtw89_phy_set_bss_color(struct rtw89_dev *rtwdev, u8 bss_color, u16 aid,
+			       enum rtw89_phy_idx phy_idx)
+{
+	const struct rtw89_chip_info *chip = rtwdev->chip;
+	const struct rtw89_reg_def *bss_clr_vld = &chip->bss_clr_vld;
+
+	rtw89_phy_write32_idx(rtwdev, bss_clr_vld->addr, bss_clr_vld->mask, 0x1,
+			      phy_idx);
+	rtw89_phy_write32_idx(rtwdev, chip->bss_clr_map_reg, B_BSS_CLR_MAP_TGT,
+			      bss_color, phy_idx);
+	rtw89_phy_write32_idx(rtwdev, chip->bss_clr_map_reg, B_BSS_CLR_MAP_STAID,
+			      aid, phy_idx);
+}
+
 void rtw89_phy_set_bss_color(struct rtw89_dev *rtwdev,
 			     struct rtw89_vif_link *rtwvif_link)
 {
 	struct ieee80211_vif *vif = rtwvif_link_to_vif(rtwvif_link);
-	const struct rtw89_chip_info *chip = rtwdev->chip;
-	const struct rtw89_reg_def *bss_clr_vld = &chip->bss_clr_vld;
 	enum rtw89_phy_idx phy_idx = rtwvif_link->phy_idx;
 	struct ieee80211_bss_conf *bss_conf;
 	u8 bss_color;
@@ -8253,12 +8265,7 @@ void rtw89_phy_set_bss_color(struct rtw89_dev *rtwdev,
 
 	rcu_read_unlock();
 
-	rtw89_phy_write32_idx(rtwdev, bss_clr_vld->addr, bss_clr_vld->mask, 0x1,
-			      phy_idx);
-	rtw89_phy_write32_idx(rtwdev, chip->bss_clr_map_reg, B_BSS_CLR_MAP_TGT,
-			      bss_color, phy_idx);
-	rtw89_phy_write32_idx(rtwdev, chip->bss_clr_map_reg, B_BSS_CLR_MAP_STAID,
-			      vif->cfg.aid, phy_idx);
+	__rtw89_phy_set_bss_color(rtwdev, bss_color, vif->cfg.aid, phy_idx);
 }
 
 static bool rfk_chan_validate_desc(const struct rtw89_rfk_chan_desc *desc)
diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h
index 74fbf5baff58..b4b818f786df 100644
--- a/drivers/net/wireless/realtek/rtw89/phy.h
+++ b/drivers/net/wireless/realtek/rtw89/phy.h
@@ -1128,6 +1128,8 @@ void rtw89_phy_antdiv_track(struct rtw89_dev *rtwdev);
 void rtw89_phy_antdiv_work(struct wiphy *wiphy, struct wiphy_work *work);
 void rtw89_phy_set_bss_color(struct rtw89_dev *rtwdev,
 			     struct rtw89_vif_link *rtwvif_link);
+void __rtw89_phy_set_bss_color(struct rtw89_dev *rtwdev, u8 bss_color, u16 aid,
+			       enum rtw89_phy_idx phy_idx);
 void rtw89_phy_tssi_ctrl_set_bandedge_cfg(struct rtw89_dev *rtwdev,
 					  enum rtw89_mac_idx mac_idx,
 					  enum rtw89_tssi_bandedge_cfg bandedge_cfg);
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 09/14] wifi: rtw89: debug: make implementation of beacon_info entry in order
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

When adding more debugfs entries, the beacon_info entry becomes not in
order. Move to correct location. Don't change logic at all.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/debug.c | 142 ++++++++++-----------
 1 file changed, 71 insertions(+), 71 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/debug.c b/drivers/net/wireless/realtek/rtw89/debug.c
index 597052261974..6fe8e7254e80 100644
--- a/drivers/net/wireless/realtek/rtw89/debug.c
+++ b/drivers/net/wireless/realtek/rtw89/debug.c
@@ -4919,6 +4919,77 @@ rtw89_debug_priv_mlo_mode_set(struct rtw89_dev *rtwdev,
 	return count;
 }
 
+static int rtw89_get_beacon_info(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb,
+				 char *buf, size_t bufsz)
+{
+	struct rtw89_pkt_stat *pkt_stat = &bb->last_pkt_stat;
+	char *p = buf, *end = buf + bufsz;
+
+	p += scnprintf(p, end - p, "[PHY %u]\n", bb->phy_idx);
+	p += scnprintf(p, end - p, "Beacon: %u\n", pkt_stat->beacon_nr);
+	p += scnprintf(p, end - p, "raw rssi: %lu\n", ewma_rssi_read(&bb->bcn_rssi));
+	p += scnprintf(p, end - p, "hw rate: %u\n", pkt_stat->beacon_rate);
+	p += scnprintf(p, end - p, "length: %u\n\n", pkt_stat->beacon_len);
+
+	return p - buf;
+}
+
+static ssize_t
+rtw89_debug_priv_beacon_info_get(struct rtw89_dev *rtwdev,
+				 struct rtw89_debugfs_priv *debugfs_priv,
+				 char *buf, size_t bufsz)
+{
+	struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track;
+	struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat;
+	struct rtw89_beacon_dist *bcn_dist = &bcn_stat->bcn_dist;
+	u16 upper, lower = bcn_stat->tbtt_tu_min;
+	char *p = buf, *end = buf + bufsz;
+	u16 *drift = bcn_stat->drift;
+	u8 bcn_num = bcn_stat->num;
+	struct rtw89_bb_ctx *bb;
+	u8 count;
+	u8 i;
+
+	rtw89_for_each_active_bb(rtwdev, bb)
+		p += rtw89_get_beacon_info(rtwdev, bb, p, end - p);
+
+	p += scnprintf(p, end - p, "[Beacon info]\n");
+	p += scnprintf(p, end - p, "interval: %u\n", bcn_track->beacon_int);
+	p += scnprintf(p, end - p, "dtim: %u\n", bcn_track->dtim);
+
+	p += scnprintf(p, end - p, "\n[Distribution]\n");
+	p += scnprintf(p, end - p, "tbtt\n");
+	for (i = 0; i < RTW89_BCN_TRACK_MAX_BIN_NUM; i++) {
+		upper = lower + RTW89_BCN_TRACK_BIN_WIDTH - 1;
+		if (i == RTW89_BCN_TRACK_MAX_BIN_NUM - 1)
+			upper = max(upper, bcn_stat->tbtt_tu_max);
+
+		p += scnprintf(p, end - p, "%02u - %02u: %u\n",
+			       lower, upper, bcn_dist->bins[i]);
+
+		lower = upper + 1;
+	}
+
+	p += scnprintf(p, end - p, "\ndrift\n");
+
+	for (i = 0; i < bcn_num; i += count) {
+		count = 1;
+		while (i + count < bcn_num && drift[i] == drift[i + count])
+			count++;
+
+		p += scnprintf(p, end - p, "%u: %u\n", drift[i], count);
+	}
+	p += scnprintf(p, end - p, "\nlower bound: %u\n", bcn_dist->lower_bound);
+	p += scnprintf(p, end - p, "upper bound: %u\n", bcn_dist->upper_bound);
+	p += scnprintf(p, end - p, "outlier count: %u\n", bcn_dist->outlier_count);
+
+	p += scnprintf(p, end - p, "\n[Tracking]\n");
+	p += scnprintf(p, end - p, "tbtt offset: %u\n", bcn_track->tbtt_offset);
+	p += scnprintf(p, end - p, "bcn timeout: %u\n", bcn_track->bcn_timeout);
+
+	return p - buf;
+}
+
 enum __diag_mac_cmd {
 	__CMD_EQUALV,
 	__CMD_EQUALO,
@@ -5255,77 +5326,6 @@ rtw89_debug_priv_diag_bb_get(struct rtw89_dev *rtwdev,
 	return p - buf;
 }
 
-static int rtw89_get_beacon_info(struct rtw89_dev *rtwdev, struct rtw89_bb_ctx *bb,
-				 char *buf, size_t bufsz)
-{
-	struct rtw89_pkt_stat *pkt_stat = &bb->last_pkt_stat;
-	char *p = buf, *end = buf + bufsz;
-
-	p += scnprintf(p, end - p, "[PHY %u]\n", bb->phy_idx);
-	p += scnprintf(p, end - p, "Beacon: %u\n", pkt_stat->beacon_nr);
-	p += scnprintf(p, end - p, "raw rssi: %lu\n", ewma_rssi_read(&bb->bcn_rssi));
-	p += scnprintf(p, end - p, "hw rate: %u\n", pkt_stat->beacon_rate);
-	p += scnprintf(p, end - p, "length: %u\n\n", pkt_stat->beacon_len);
-
-	return p - buf;
-}
-
-static ssize_t
-rtw89_debug_priv_beacon_info_get(struct rtw89_dev *rtwdev,
-				 struct rtw89_debugfs_priv *debugfs_priv,
-				 char *buf, size_t bufsz)
-{
-	struct rtw89_beacon_track_info *bcn_track = &rtwdev->bcn_track;
-	struct rtw89_beacon_stat *bcn_stat = &rtwdev->phystat.bcn_stat;
-	struct rtw89_beacon_dist *bcn_dist = &bcn_stat->bcn_dist;
-	u16 upper, lower = bcn_stat->tbtt_tu_min;
-	char *p = buf, *end = buf + bufsz;
-	u16 *drift = bcn_stat->drift;
-	u8 bcn_num = bcn_stat->num;
-	struct rtw89_bb_ctx *bb;
-	u8 count;
-	u8 i;
-
-	rtw89_for_each_active_bb(rtwdev, bb)
-		p += rtw89_get_beacon_info(rtwdev, bb, p, end - p);
-
-	p += scnprintf(p, end - p, "[Beacon info]\n");
-	p += scnprintf(p, end - p, "interval: %u\n", bcn_track->beacon_int);
-	p += scnprintf(p, end - p, "dtim: %u\n", bcn_track->dtim);
-
-	p += scnprintf(p, end - p, "\n[Distribution]\n");
-	p += scnprintf(p, end - p, "tbtt\n");
-	for (i = 0; i < RTW89_BCN_TRACK_MAX_BIN_NUM; i++) {
-		upper = lower + RTW89_BCN_TRACK_BIN_WIDTH - 1;
-		if (i == RTW89_BCN_TRACK_MAX_BIN_NUM - 1)
-			upper = max(upper, bcn_stat->tbtt_tu_max);
-
-		p += scnprintf(p, end - p, "%02u - %02u: %u\n",
-			       lower, upper, bcn_dist->bins[i]);
-
-		lower = upper + 1;
-	}
-
-	p += scnprintf(p, end - p, "\ndrift\n");
-
-	for (i = 0; i < bcn_num; i += count) {
-		count = 1;
-		while (i + count < bcn_num && drift[i] == drift[i + count])
-			count++;
-
-		p += scnprintf(p, end - p, "%u: %u\n", drift[i], count);
-	}
-	p += scnprintf(p, end - p, "\nlower bound: %u\n", bcn_dist->lower_bound);
-	p += scnprintf(p, end - p, "upper bound: %u\n", bcn_dist->upper_bound);
-	p += scnprintf(p, end - p, "outlier count: %u\n", bcn_dist->outlier_count);
-
-	p += scnprintf(p, end - p, "\n[Tracking]\n");
-	p += scnprintf(p, end - p, "tbtt offset: %u\n", bcn_track->tbtt_offset);
-	p += scnprintf(p, end - p, "bcn timeout: %u\n", bcn_track->bcn_timeout);
-
-	return p - buf;
-}
-
 #define rtw89_debug_priv_get(name, opts...)			\
 {								\
 	.cb_read = rtw89_debug_priv_ ##name## _get,		\
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 08/14] wifi: rtw89: fill HE-SU/HE-TB/HE-MU/HE-EXT_SU radiotap
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

Fill HE radiotap by PHY status IE-09/IE-10 which contains HE SIG-A/SIG-B
respectively.

The IE-10 may contain two content channels (if bandwidth is larger than
40MHz), and starting address of second content channel can be calculated by
length of first content channel containing up to 15 user fields with 8-byte
alignment.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c | 393 +++++++++++++++++++++-
 drivers/net/wireless/realtek/rtw89/core.h |  18 +-
 drivers/net/wireless/realtek/rtw89/txrx.h |  54 +++
 3 files changed, 461 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 2a9326335c53..7fa479ce52e2 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -3220,9 +3220,361 @@ static void rtw89_core_update_radiotap_vht(struct rtw89_dev *rtwdev,
 	}
 }
 
+static void rtw89_core_update_radiotap_he_su(struct rtw89_dev *rtwdev,
+					     struct sk_buff *skb,
+					     struct ieee80211_rx_status *rx_status,
+					     struct ieee80211_radiotap_he *he,
+					     const struct rtw89_phy_sts_ie09 *ie09)
+{
+	u32 sig_a1, sig_a2;
+	u16 t;
+
+	if (!ie09)
+		return;
+
+	sig_a1 = le64_get_bits(ie09->qw0, RTW89_PHY_STS_IE09_HE_SU_SIG_A1_MASK);
+	sig_a2 = le64_get_bits(ie09->qw0, RTW89_PHY_STS_IE09_HE_SU_SIG_A2_MASK);
+
+	he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BEAM_CHANGE_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_UL_DL_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_BSS_COLOR_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_LDPC_XSYMSEG_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_DOPPLER_KNOWN |
+				 0);
+
+	he->data2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_TXOP_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA2_TXBF_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA2_PRE_FEC_PAD_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA2_PE_DISAMBIG_KNOWN |
+				 0);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_SU_SIG_A1_BEAM_CHANGE);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_BEAM_CHANGE);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_SU_SIG_A1_ULDL);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_UL_DL);
+
+	rx_status->he_dcm = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_SU_SIG_A1_DCM);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_SU_SIG_A1_BSS_COLOR);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_BSS_COLOR);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_SU_SIG_A1_SR);
+	he->data4 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA4_SU_MU_SPTL_REUSE);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_SU_SIG_A2_TXOP);
+	he->data6 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA6_TXOP);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_SU_SIG_A2_LDPC_XSYMSEG);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_LDPC_XSYMSEG);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_SU_SIG_A2_BEAMFORMED);
+	he->data5 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA5_TXBF);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_SU_SIG_A2_PREFEC);
+	he->data5 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA5_PRE_FEC_PAD);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_SU_SIG_A2_PE);
+	he->data5 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA5_PE_DISAMBIG);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_SU_SIG_A2_DOPPLER);
+	he->data6 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA6_DOPPLER);
+}
+
+static void rtw89_core_update_radiotap_he_tb(struct rtw89_dev *rtwdev,
+					     struct sk_buff *skb,
+					     struct ieee80211_rx_status *rx_status,
+					     struct ieee80211_radiotap_he *he,
+					     const struct rtw89_phy_sts_ie09 *sig)
+{
+	u32 sig_a1, sig_a2;
+	u16 t;
+
+	if (!sig)
+		return;
+
+	sig_a1 = le64_get_bits(sig->qw0, RTW89_PHY_STS_IE09_HE_TB_SIG_A1_MASK);
+	sig_a2 = le64_get_bits(sig->qw0, RTW89_PHY_STS_IE09_HE_TB_SIG_A2_MASK);
+
+	he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_BSS_COLOR_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE2_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE3_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE4_KNOWN);
+
+	he->data2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_TXOP_KNOWN);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_TB_SIG_A1_BSS_COLOR);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_BSS_COLOR);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB1);
+	he->data4 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE1);
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB2);
+	he->data4 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE2);
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB3);
+	he->data4 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE3);
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB4);
+	he->data4 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA4_TB_SPTL_REUSE4);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_TB_SIG_A2_TXOP);
+	he->data6 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA6_TXOP);
+}
+
+static u8 rtw89_core_he_mu_get_n_user_fields_by_ru_alloc(struct rtw89_dev *rtwdev,
+							 u8 ru_alloc)
+{
+	u8 mu_users;
+
+	if (ru_alloc <= 15) {
+		static const u8 entries_0_15[16] = {
+			9, 8, 8, 7, 8, 7, 7, 6, 8, 7, 7, 6, 7, 6, 6, 5,
+		};
+
+		return entries_0_15[ru_alloc];
+	}
+
+	if (ru_alloc >= 16 && ru_alloc <= 95) {
+		mu_users = ru_alloc & 0x07;
+
+		switch (ru_alloc & 0xF8) {
+		case 16: case 24: return mu_users + 3;
+		case 32: case 64: return mu_users + 6;
+		case 40: case 48: case 72: case 80: return mu_users + 5;
+		case 56: case 88: return mu_users + 4;
+		}
+	}
+
+	if (ru_alloc >= 96 && ru_alloc <= 111) {
+		mu_users = ru_alloc & 0x03;
+		mu_users += ru_alloc >> 2 & 0x03;
+
+		return mu_users + 2;
+	}
+
+	if (ru_alloc == 112)
+		return 4;
+
+	if (ru_alloc >= 113 && ru_alloc <= 115)
+		return 0;
+
+	if (ru_alloc >= 128 && ru_alloc <= 191) {
+		mu_users = ru_alloc & 0x07;
+		mu_users += ru_alloc >> 3 & 0x07;
+
+		return mu_users + 3;
+	}
+
+	if (ru_alloc >= 192 && ru_alloc <= 215)
+		return (ru_alloc & 0x07) + 1;
+
+	return 0;
+}
+
+static int rtw89_core_he_mu_get_n_ru_alloc(struct rtw89_dev *rtwdev, u8 bw)
+{
+	switch (bw) {
+	case 0:
+	case 1:
+		return 1;
+	case 2:
+	case 4:
+	case 5:
+		return 2;
+	case 3:
+	case 6:
+	case 7:
+		return 3;
+	}
+
+	return 0;
+}
+
+static void rtw89_core_he_mu_get_cc_ptr(struct rtw89_dev *rtwdev,
+					const struct rtw89_phy_sts_ie10 *ie10,
+					u8 bw, int n_ru, int n_center_26tone,
+					const u8 **c1, const u8 **c2)
+{
+	const u8 *sigb, *end_sigb;
+	int total_bits, c2_offset;
+	int n_user_fields = 0;
+	const u8 *ptr;
+	int i;
+
+	sigb = &ie10->sigb[0];
+	end_sigb = (const void *)ie10 +
+		   rtw89_core_get_phy_status_ie_len(rtwdev, (const void *)ie10);
+
+	ptr = sigb;
+	if (ptr + n_ru + n_center_26tone >= end_sigb)
+		return;
+	*c1 = ptr;
+
+	if (bw == 0)	/* 20MHz doesn't have content channel 2 */
+		return;
+
+	for (i = 0; i < n_ru; i++)
+		n_user_fields +=
+			rtw89_core_he_mu_get_n_user_fields_by_ru_alloc(rtwdev, (*c1)[i]);
+
+	/* hardware report max 15 recordes, align 8 */
+	n_user_fields = min(n_user_fields, 15);
+
+	total_bits = n_ru * 8 + n_center_26tone + 10 +
+		     n_user_fields * 21 + DIV_ROUND_UP(n_user_fields, 2) * 10;
+	c2_offset = ALIGN(DIV_ROUND_UP(total_bits, 8), 8);
+
+	ptr = &sigb[c2_offset];
+	if (ptr + n_ru + n_center_26tone >= end_sigb)
+		return;
+
+	*c2 = ptr;
+}
+
+static void rtw89_core_update_radiotap_he_mu(struct rtw89_dev *rtwdev,
+					     struct sk_buff *skb,
+					     struct ieee80211_rx_status *rx_status,
+					     struct ieee80211_radiotap_he *he,
+					     struct ieee80211_radiotap_he_mu *he_mu,
+					     const struct rtw89_phy_sts_ie09 *ie09,
+					     const struct rtw89_phy_sts_ie10 *ie10)
+{
+	const u8 *c1 = NULL, *c2 = NULL;
+	int n_center_26tone, n_ru, i;
+	bool doppler, comp;
+	u32 sig_a1, sig_a2;
+	u16 t;
+	u8 bw;
+
+	if (!ie09)
+		return;
+
+	he->data1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_UL_DL_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_BSS_COLOR_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_SPTL_REUSE_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_DOPPLER_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA1_LDPC_XSYMSEG_KNOWN);
+	he->data2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_TXOP_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA2_MIDAMBLE_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA2_PRE_FEC_PAD_KNOWN |
+				 IEEE80211_RADIOTAP_HE_DATA2_PE_DISAMBIG_KNOWN);
+
+	sig_a1 = le64_get_bits(ie09->qw0, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_MASK);
+	sig_a2 = le64_get_bits(ie09->qw0, RTW89_PHY_STS_IE09_HE_MU_SIG_A2_MASK);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_ULDL);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_UL_DL);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_BSS_COLOR);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_BSS_COLOR);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SR);
+	he->data4 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA4_SU_MU_SPTL_REUSE);
+
+	doppler = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_DOPPLER);
+	he->data6 |= le16_encode_bits(doppler, IEEE80211_RADIOTAP_HE_DATA6_DOPPLER);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_MU_SIG_A2_TXOP);
+	he->data6 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA6_TXOP);
+
+	if (doppler) {
+		t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_MU_SIG_A2_MID);
+		he->data6 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA6_MIDAMBLE_PDCTY);
+	}
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_MU_SIG_A2_LPDC_XSYMSEG);
+	he->data3 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA3_LDPC_XSYMSEG);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_MU_SIG_A2_FEC);
+	he->data5 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA5_PRE_FEC_PAD);
+
+	t = u32_get_bits(sig_a2, RTW89_PHY_STS_IE09_HE_MU_SIG_A2_PE);
+	he->data5 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_DATA5_PE_DISAMBIG);
+
+	he_mu->flags1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS_KNOWN |
+				     IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM_KNOWN |
+				     IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_SYMS_USERS_KNOWN |
+				     IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_COMP_KNOWN);
+	he_mu->flags2 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW_KNOWN);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_MCS);
+	he_mu->flags1 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_MCS);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_DCM);
+	he_mu->flags1 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_MU_FLAGS1_SIG_B_DCM);
+
+	t = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_SYM_USR);
+	he_mu->flags2 |= le16_encode_bits(t, IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_SYMS_USERS);
+
+	comp = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_COM);
+	he_mu->flags2 |= le16_encode_bits(comp, IEEE80211_RADIOTAP_HE_MU_FLAGS2_SIG_B_COMP);
+
+	bw = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_HE_MU_SIG_A1_BW);
+	he_mu->flags2 |= le16_encode_bits(bw, IEEE80211_RADIOTAP_HE_MU_FLAGS2_BW_FROM_SIG_A_BW);
+
+	if (comp == 0 && bw <= 5) {
+		u8 punc = clamp_t(int, bw - 3, 0, 2);
+
+		he_mu->flags2 |=
+			cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW_KNOWN);
+		he_mu->flags2 |=
+			le16_encode_bits(punc, IEEE80211_RADIOTAP_HE_MU_FLAGS2_PUNC_FROM_SIG_A_BW);
+	}
+
+	/* the Common field in the HE-SIG-B field is not present */
+	if (comp || !ie10)
+		return;
+
+	n_ru = rtw89_core_he_mu_get_n_ru_alloc(rtwdev, bw);
+	n_center_26tone = bw >= 1;
+
+	rtw89_core_he_mu_get_cc_ptr(rtwdev, ie10, bw, n_ru, n_center_26tone, &c1, &c2);
+	if (!c1)
+		return;
+
+	he_mu->flags1 |= cpu_to_le16(IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_RU_KNOWN |
+				     IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_RU_KNOWN |
+				     IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU_KNOWN |
+				     IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH2_CTR_26T_RU_KNOWN);
+
+	for (i = 0; i < n_ru; i++) {
+		he_mu->ru_ch1[i] = c1[i];
+		if (c2)
+			he_mu->ru_ch2[i] = c2[i];
+	}
+
+	if (n_center_26tone) {
+		u8 ru;
+
+		ru = c1[n_ru] & BIT(0);
+		he_mu->flags1 |=
+			le16_encode_bits(ru, IEEE80211_RADIOTAP_HE_MU_FLAGS1_CH1_CTR_26T_RU);
+
+		ru = c2 ? c2[n_ru] & BIT(0) : 0;
+		he_mu->flags1 |=
+			le16_encode_bits(ru, IEEE80211_RADIOTAP_HE_MU_FLAGS2_CH2_CTR_26T_RU);
+	}
+}
+
+static u16 rtw89_core_get_radiotap_he_format(struct rtw89_rx_desc_info *desc_info)
+{
+	switch (desc_info->ppdu_type) {
+	case RTW89_RX_PPDU_T_HE_MU:
+		return IEEE80211_RADIOTAP_HE_DATA1_FORMAT_MU;
+	case RTW89_RX_PPDU_T_HE_ERSU:
+		return IEEE80211_RADIOTAP_HE_DATA1_FORMAT_EXT_SU;
+	case RTW89_RX_PPDU_T_HE_TB:
+		return IEEE80211_RADIOTAP_HE_DATA1_FORMAT_TRIG;
+	default:
+		return IEEE80211_RADIOTAP_HE_DATA1_FORMAT_SU;
+	}
+}
+
 static void rtw89_core_update_radiotap_he(struct rtw89_dev *rtwdev,
+					  struct rtw89_rx_desc_info *desc_info,
 					  struct sk_buff *skb,
-					  struct ieee80211_rx_status *rx_status)
+					  struct ieee80211_rx_status *rx_status,
+					  struct rtw89_rx_phy_ppdu *phy_ppdu)
 {
 	static const struct ieee80211_radiotap_he known_he = {
 		.data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN |
@@ -3231,11 +3583,45 @@ static void rtw89_core_update_radiotap_he(struct rtw89_dev *rtwdev,
 				     IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN),
 		.data2 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN),
 	};
+	struct ieee80211_radiotap_he_mu *he_mu = NULL;
+	const struct rtw89_phy_sts_ie09 *ie09;
+	const struct rtw89_phy_sts_ie10 *ie10;
 	struct ieee80211_radiotap_he *he;
+	u16 he_format;
+
+	he_format = rtw89_core_get_radiotap_he_format(desc_info);
+
+	/* Radiotap of HE-MU must be placed after HE (skb_push ahead) */
+	if (he_format == IEEE80211_RADIOTAP_HE_DATA1_FORMAT_MU) {
+		rx_status->flag |= RX_FLAG_RADIOTAP_HE_MU;
+		he_mu = skb_push(skb, sizeof(*he_mu));
+		memset(he_mu, 0, sizeof(*he_mu));
+	}
 
 	rx_status->flag |= RX_FLAG_RADIOTAP_HE;
 	he = skb_push(skb, sizeof(*he));
 	*he = known_he;
+
+	he->data1 |= le16_encode_bits(he_format, IEEE80211_RADIOTAP_HE_DATA1_FORMAT_MASK);
+
+	if (!phy_ppdu)
+		return;
+
+	ie09 = phy_ppdu->ie09;
+	ie10 = phy_ppdu->ie10;
+
+	switch (he_format) {
+	case IEEE80211_RADIOTAP_HE_DATA1_FORMAT_MU:
+		rtw89_core_update_radiotap_he_mu(rtwdev, skb, rx_status, he, he_mu,
+						 ie09, ie10);
+		break;
+	case IEEE80211_RADIOTAP_HE_DATA1_FORMAT_TRIG:
+		rtw89_core_update_radiotap_he_tb(rtwdev, skb, rx_status, he, ie09);
+		break;
+	default:
+		rtw89_core_update_radiotap_he_su(rtwdev, skb, rx_status, he, ie09);
+		break;
+	}
 }
 
 static const u8 rx_status_bw_to_radiotap_eht_usig[] = {
@@ -3309,6 +3695,7 @@ static void rtw89_core_update_radiotap_eht(struct rtw89_dev *rtwdev,
 }
 
 static void rtw89_core_update_radiotap(struct rtw89_dev *rtwdev,
+				       struct rtw89_rx_desc_info *desc_info,
 				       struct sk_buff *skb,
 				       struct ieee80211_rx_status *rx_status,
 				       struct rtw89_rx_phy_ppdu *phy_ppdu)
@@ -3319,7 +3706,7 @@ static void rtw89_core_update_radiotap(struct rtw89_dev *rtwdev,
 	if (rx_status->encoding == RX_ENC_VHT)
 		rtw89_core_update_radiotap_vht(rtwdev, skb, rx_status, phy_ppdu);
 	else if (rx_status->encoding == RX_ENC_HE)
-		rtw89_core_update_radiotap_he(rtwdev, skb, rx_status);
+		rtw89_core_update_radiotap_he(rtwdev, desc_info, skb, rx_status, phy_ppdu);
 	else if (rx_status->encoding == RX_ENC_EHT)
 		rtw89_core_update_radiotap_eht(rtwdev, skb, rx_status);
 }
@@ -3531,7 +3918,7 @@ static void rtw89_core_rx_to_mac80211(struct rtw89_dev *rtwdev,
 	rtw89_core_hw_to_sband_rate(rx_status);
 	rtw89_core_rx_stats(rtwdev, phy_ppdu, desc_info, skb_ppdu);
 	rtw89_core_update_rx_status_by_ppdu(rtwdev, rx_status, phy_ppdu);
-	rtw89_core_update_radiotap(rtwdev, skb_ppdu, rx_status, phy_ppdu);
+	rtw89_core_update_radiotap(rtwdev, desc_info, skb_ppdu, rx_status, phy_ppdu);
 	rtw89_core_validate_rx_signal(rx_status);
 	rtw89_core_update_rx_freq_from_ie(rtwdev, skb_ppdu, rx_status);
 	rtw89_core_correct_mcc_chan(rtwdev, desc_info, rx_status, phy_ppdu);
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index d1cd5997cf97..b85d7fa4296a 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -59,7 +59,8 @@ extern const struct ieee80211_ops rtw89_ops;
 #define DELTA_SWINGIDX_SIZE 30
 
 #define RTW89_RADIOTAP_ROOM_VHT sizeof(struct ieee80211_radiotap_vht)
-#define RTW89_RADIOTAP_ROOM_HE sizeof(struct ieee80211_radiotap_he)
+#define RTW89_RADIOTAP_ROOM_HE (sizeof(struct ieee80211_radiotap_he) + \
+				sizeof(struct ieee80211_radiotap_he_mu))
 #define RTW89_RADIOTAP_ROOM_EHT \
 	(sizeof(struct ieee80211_radiotap_tlv) + \
 	 ALIGN(struct_size((struct ieee80211_radiotap_eht *)0, user_info, 1), 4) + \
@@ -955,6 +956,21 @@ enum rtw89_bandwidth {
 	RTW89_CHANNEL_WIDTH_10 = 7,
 };
 
+enum rtw89_rx_ppdu_type {
+	RTW89_RX_PPDU_T_LCCK = 0,
+	RTW89_RX_PPDU_T_SCCK = 1,
+	RTW89_RX_PPDU_T_OFDM = 2,
+	RTW89_RX_PPDU_T_HT = 3,
+	RTW89_RX_PPDU_T_HTGF = 4,
+	RTW89_RX_PPDU_T_VHT_SU = 5,
+	RTW89_RX_PPDU_T_VHT_MU = 6,
+	RTW89_RX_PPDU_T_HE_SU = 7,
+	RTW89_RX_PPDU_T_HE_ERSU = 8,
+	RTW89_RX_PPDU_T_HE_MU = 9,
+	RTW89_RX_PPDU_T_HE_TB = 10,
+	RTW89_RX_PPDU_T_UNKNOWN = 15,
+};
+
 enum rtw89_ps_mode {
 	RTW89_PS_MODE_NONE	= 0,
 	RTW89_PS_MODE_RFOFF	= 1,
diff --git a/drivers/net/wireless/realtek/rtw89/txrx.h b/drivers/net/wireless/realtek/rtw89/txrx.h
index ddf8742d6712..4a567d3fe3f7 100644
--- a/drivers/net/wireless/realtek/rtw89/txrx.h
+++ b/drivers/net/wireless/realtek/rtw89/txrx.h
@@ -687,6 +687,60 @@ struct rtw89_phy_sts_ie09 {
 #define RTW89_PHY_STS_IE09_VHT_SIG_A2_SUMU_CODE3 GENMASK(7, 4)
 #define RTW89_PHY_STS_IE09_VHT_SIG_A2_BF BIT(8)
 #define RTW89_PHY_STS_IE09_VHT_SIG_A2_RSVD3 BIT(9)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_MASK GENMASK_ULL(47, 22) /* HE SU SIG-A1 */
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_FORMAT BIT(0)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_BEAM_CHANGE BIT(1)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_ULDL BIT(2)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_MCS GENMASK(6, 3)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_DCM BIT(7)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_BSS_COLOR GENMASK(13, 8)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_RSVD1 BIT(14)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_SR GENMASK(18, 15)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_BW GENMASK(20, 19)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_GILTF GENMASK(22, 21)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A1_NSTS GENMASK(25, 23)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_MASK GENMASK_ULL(63, 48) /* HE SU SIG-A2 */
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_TXOP GENMASK(6, 0)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_CODING BIT(7)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_LDPC_XSYMSEG BIT(8)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_STBC BIT(9)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_BEAMFORMED BIT(10)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_PREFEC GENMASK(12, 11)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_PE BIT(13)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_RSVD2 BIT(14)
+#define RTW89_PHY_STS_IE09_HE_SU_SIG_A2_DOPPLER BIT(15)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_MASK GENMASK_ULL(47, 22) /* HE TB SIG-A1 */
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_FORMAT BIT(0)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_BSS_COLOR GENMASK(6, 1)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB1 GENMASK(10, 7)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB2 GENMASK(14, 11)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB3 GENMASK(18, 15)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_SB4 GENMASK(22, 19)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_RSVD1 BIT(23)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A1_BW GENMASK(25, 24)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A2_MASK GENMASK_ULL(63, 48) /* HE TB SIG-A2 */
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A2_TXOP GENMASK(6, 0)
+#define RTW89_PHY_STS_IE09_HE_TB_SIG_A2_RSVD1 GENMASK(15, 7)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_MASK GENMASK_ULL(47, 22) /* HE MU SIG-A1 */
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_ULDL BIT(0)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_MCS GENMASK(3, 1)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_DCM BIT(4)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_BSS_COLOR GENMASK(10, 5)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SR GENMASK(14, 11)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_BW GENMASK(17, 15)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_SYM_USR GENMASK(21, 18)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_SIGB_COM BIT(22)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_GI_LTF GENMASK(24, 23)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A1_DOPPLER BIT(25)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_MASK GENMASK_ULL(63, 48) /* HE MU SIG-A2 */
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_TXOP GENMASK(6, 0)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_RSVD BIT(7)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_LTF_MID GENMASK(10, 8)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_MID BIT(10)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_LPDC_XSYMSEG BIT(11)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_STBC BIT(12)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_FEC GENMASK(14, 13)
+#define RTW89_PHY_STS_IE09_HE_MU_SIG_A2_PE BIT(15)
 
 struct rtw89_phy_sts_ie10 {
 	__le64 qw0;
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 07/14] wifi: rtw89: fill VHT radiotap
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

Fill VHT radiotap by PHY status IE-09 which contains VHT SIG-A.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c | 53 +++++++++++++++++++++--
 drivers/net/wireless/realtek/rtw89/core.h |  4 +-
 drivers/net/wireless/realtek/rtw89/txrx.h | 26 +++++++++++
 3 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index a27e482e38a6..2a9326335c53 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -3176,6 +3176,50 @@ void rtw89_core_update_rx_status_by_ppdu(struct rtw89_dev *rtwdev,
 		rx_status->enc_flags |= u8_encode_bits(1, RX_ENC_FLAG_STBC_MASK);
 }
 
+static void rtw89_core_update_radiotap_vht(struct rtw89_dev *rtwdev,
+					   struct sk_buff *skb,
+					   struct ieee80211_rx_status *rx_status,
+					   struct rtw89_rx_phy_ppdu *phy_ppdu)
+{
+	const struct rtw89_phy_sts_ie09 *ie09;
+	struct ieee80211_radiotap_vht *vht;
+	u8 group_id;
+	u32 sig_a1;
+	u16 paid;
+	u8 nss;
+
+	if (!phy_ppdu)
+		return;
+
+	ie09 = phy_ppdu->ie09;
+	if (!ie09)
+		return;
+
+	vht = skb_push(skb, sizeof(*vht));
+	memset(vht, 0, sizeof(*vht));
+	rx_status->flag |= RX_FLAG_RADIOTAP_VHT;
+
+	sig_a1 = le64_get_bits(ie09->qw0, RTW89_PHY_STS_IE09_VHT_SIG_A1_MASK);
+
+	group_id = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_VHT_SIG_A1_GRP_ID);
+	vht->group_id = group_id;
+
+	if (group_id == 0 || group_id == 63) {
+		paid = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_VHT_SIG_A1_SU_PAID);
+		vht->partial_aid = cpu_to_le16(paid);
+	} else {
+		/* let mac80211 fill vht->mcs_nss[0] */
+#define WITH_MCS_IS_NOT_KNOWN(nss) ((nss) ? (15 << 4) | (nss) : 0)
+		nss = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_VHT_SIG_A1_MU_NSTS1);
+		vht->mcs_nss[1] = WITH_MCS_IS_NOT_KNOWN(nss);
+		nss = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_VHT_SIG_A1_MU_NSTS2);
+		vht->mcs_nss[2] = WITH_MCS_IS_NOT_KNOWN(nss);
+		nss = u32_get_bits(sig_a1, RTW89_PHY_STS_IE09_VHT_SIG_A1_MU_NSTS3);
+		vht->mcs_nss[3] = WITH_MCS_IS_NOT_KNOWN(nss);
+#undef WITH_MCS_IS_NOT_KNOWN
+	}
+}
+
 static void rtw89_core_update_radiotap_he(struct rtw89_dev *rtwdev,
 					  struct sk_buff *skb,
 					  struct ieee80211_rx_status *rx_status)
@@ -3266,12 +3310,15 @@ static void rtw89_core_update_radiotap_eht(struct rtw89_dev *rtwdev,
 
 static void rtw89_core_update_radiotap(struct rtw89_dev *rtwdev,
 				       struct sk_buff *skb,
-				       struct ieee80211_rx_status *rx_status)
+				       struct ieee80211_rx_status *rx_status,
+				       struct rtw89_rx_phy_ppdu *phy_ppdu)
 {
 	if (!(rtwdev->hw->conf.flags & IEEE80211_CONF_MONITOR))
 		return;
 
-	if (rx_status->encoding == RX_ENC_HE)
+	if (rx_status->encoding == RX_ENC_VHT)
+		rtw89_core_update_radiotap_vht(rtwdev, skb, rx_status, phy_ppdu);
+	else if (rx_status->encoding == RX_ENC_HE)
 		rtw89_core_update_radiotap_he(rtwdev, skb, rx_status);
 	else if (rx_status->encoding == RX_ENC_EHT)
 		rtw89_core_update_radiotap_eht(rtwdev, skb, rx_status);
@@ -3484,7 +3531,7 @@ static void rtw89_core_rx_to_mac80211(struct rtw89_dev *rtwdev,
 	rtw89_core_hw_to_sband_rate(rx_status);
 	rtw89_core_rx_stats(rtwdev, phy_ppdu, desc_info, skb_ppdu);
 	rtw89_core_update_rx_status_by_ppdu(rtwdev, rx_status, phy_ppdu);
-	rtw89_core_update_radiotap(rtwdev, skb_ppdu, rx_status);
+	rtw89_core_update_radiotap(rtwdev, skb_ppdu, rx_status, phy_ppdu);
 	rtw89_core_validate_rx_signal(rx_status);
 	rtw89_core_update_rx_freq_from_ie(rtwdev, skb_ppdu, rx_status);
 	rtw89_core_correct_mcc_chan(rtwdev, desc_info, rx_status, phy_ppdu);
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index ca716e95cb2c..d1cd5997cf97 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -58,6 +58,7 @@ extern const struct ieee80211_ops rtw89_ops;
 #define RTW89_TX_DIV_RSSI_RAW_TH (2 << RSSI_FACTOR)
 #define DELTA_SWINGIDX_SIZE 30
 
+#define RTW89_RADIOTAP_ROOM_VHT sizeof(struct ieee80211_radiotap_vht)
 #define RTW89_RADIOTAP_ROOM_HE sizeof(struct ieee80211_radiotap_he)
 #define RTW89_RADIOTAP_ROOM_EHT \
 	(sizeof(struct ieee80211_radiotap_tlv) + \
@@ -65,7 +66,8 @@ extern const struct ieee80211_ops rtw89_ops;
 	 sizeof(struct ieee80211_radiotap_tlv) + \
 	 ALIGN(sizeof(struct ieee80211_radiotap_eht_usig), 4))
 #define RTW89_RADIOTAP_ROOM \
-	ALIGN(max(RTW89_RADIOTAP_ROOM_HE, RTW89_RADIOTAP_ROOM_EHT), 64)
+	ALIGN(max3(RTW89_RADIOTAP_ROOM_VHT, RTW89_RADIOTAP_ROOM_HE, \
+		   RTW89_RADIOTAP_ROOM_EHT), 64)
 
 #define RTW89_HTC_MASK_VARIANT GENMASK(1, 0)
 #define RTW89_HTC_VARIANT_HE 3
diff --git a/drivers/net/wireless/realtek/rtw89/txrx.h b/drivers/net/wireless/realtek/rtw89/txrx.h
index 18fe6d3d0f83..ddf8742d6712 100644
--- a/drivers/net/wireless/realtek/rtw89/txrx.h
+++ b/drivers/net/wireless/realtek/rtw89/txrx.h
@@ -662,6 +662,32 @@ struct rtw89_phy_sts_ie09 {
 	__le64 qw0;
 } __packed;
 
+#define RTW89_PHY_STS_IE09_L_SIG_MASK GENMASK(21, 5) /* Legacy/VHT/HE L-SIG */
+#define RTW89_PHY_STS_IE09_L_SIG_RATE GENMASK(3, 0)
+#define RTW89_PHY_STS_IE09_L_SIG_RSVD BIT(4)
+#define RTW89_PHY_STS_IE09_L_SIG_LENGTH GENMASK(16, 5)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_MASK GENMASK_ULL(45, 22) /* VHT SIG-A1 */
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_BW GENMASK(1, 0)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_RSVD1 BIT(2)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_STBC BIT(3)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_GRP_ID GENMASK(9, 4)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_SU_NSTS GENMASK(12, 10)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_SU_PAID GENMASK(21, 13)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_MU_NSTS0 GENMASK(12, 10)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_MU_NSTS1 GENMASK(15, 13)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_MU_NSTS2 GENMASK(18, 16)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_MU_NSTS3 GENMASK(21, 19)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_TXOP_NOPS BIT(22)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A1_RSVD2 BIT(23)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_MASK GENMASK_ULL(55, 46) /* VHT SIG-A2 */
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_SGI BIT(0)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_SGI_DISA BIT(1)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_SUMU_CODE0 BIT(2)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_LDPC BIT(3)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_SUMU_CODE3 GENMASK(7, 4)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_BF BIT(8)
+#define RTW89_PHY_STS_IE09_VHT_SIG_A2_RSVD3 BIT(9)
+
 struct rtw89_phy_sts_ie10 {
 	__le64 qw0;
 	u8 sigb[];
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 06/14] wifi: rtw89: move HE radiotap to an individual function
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

To implement more fields of HE radiotap, move the code to an individual
function ahead. Not change logic at all.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c | 36 +++++++++++++----------
 1 file changed, 21 insertions(+), 15 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index f877c2707c84..a27e482e38a6 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -3176,6 +3176,24 @@ void rtw89_core_update_rx_status_by_ppdu(struct rtw89_dev *rtwdev,
 		rx_status->enc_flags |= u8_encode_bits(1, RX_ENC_FLAG_STBC_MASK);
 }
 
+static void rtw89_core_update_radiotap_he(struct rtw89_dev *rtwdev,
+					  struct sk_buff *skb,
+					  struct ieee80211_rx_status *rx_status)
+{
+	static const struct ieee80211_radiotap_he known_he = {
+		.data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN |
+				     IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN |
+				     IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN |
+				     IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN),
+		.data2 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN),
+	};
+	struct ieee80211_radiotap_he *he;
+
+	rx_status->flag |= RX_FLAG_RADIOTAP_HE;
+	he = skb_push(skb, sizeof(*he));
+	*he = known_he;
+}
+
 static const u8 rx_status_bw_to_radiotap_eht_usig[] = {
 	[RATE_INFO_BW_20] = IEEE80211_RADIOTAP_EHT_USIG_COMMON_BW_20MHZ,
 	[RATE_INFO_BW_5] = U8_MAX,
@@ -3250,25 +3268,13 @@ static void rtw89_core_update_radiotap(struct rtw89_dev *rtwdev,
 				       struct sk_buff *skb,
 				       struct ieee80211_rx_status *rx_status)
 {
-	static const struct ieee80211_radiotap_he known_he = {
-		.data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN |
-				     IEEE80211_RADIOTAP_HE_DATA1_CODING_KNOWN |
-				     IEEE80211_RADIOTAP_HE_DATA1_STBC_KNOWN |
-				     IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN),
-		.data2 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA2_GI_KNOWN),
-	};
-	struct ieee80211_radiotap_he *he;
-
 	if (!(rtwdev->hw->conf.flags & IEEE80211_CONF_MONITOR))
 		return;
 
-	if (rx_status->encoding == RX_ENC_HE) {
-		rx_status->flag |= RX_FLAG_RADIOTAP_HE;
-		he = skb_push(skb, sizeof(*he));
-		*he = known_he;
-	} else if (rx_status->encoding == RX_ENC_EHT) {
+	if (rx_status->encoding == RX_ENC_HE)
+		rtw89_core_update_radiotap_he(rtwdev, skb, rx_status);
+	else if (rx_status->encoding == RX_ENC_EHT)
 		rtw89_core_update_radiotap_eht(rtwdev, skb, rx_status);
-	}
 }
 
 static void rtw89_core_validate_rx_signal(struct ieee80211_rx_status *rx_status)
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 05/14] wifi: rtw89: phy: enable IE-09/IE-10 PHY status report for monitor mode
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

The IE-09/IE-10 of PHY status contain SIG-A/SIG-B respectively, so enable
them in monitor mode to have rich information. If the parser detects
length invalid, ignore to reference IE-09/IE-10 to prevent accessing out
of range.

The RTL8922D is generation 2 of PHY status, which doesn't report SIG-B by
IE-10, so not enable it.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c     | 43 +++++++++++++++++--
 drivers/net/wireless/realtek/rtw89/core.h     |  6 ++-
 drivers/net/wireless/realtek/rtw89/mac80211.c |  3 ++
 drivers/net/wireless/realtek/rtw89/phy.c      | 17 +++++++-
 drivers/net/wireless/realtek/rtw89/phy.h      |  1 +
 drivers/net/wireless/realtek/rtw89/txrx.h     |  9 ++++
 6 files changed, 74 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index caedb2bd21d5..f877c2707c84 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -2152,14 +2152,38 @@ static void rtw89_core_parse_phy_status_ie00_v2(struct rtw89_dev *rtwdev,
 		rpl_path[i] = tmp_rpl[i] >> 1;
 }
 
+static void rtw89_core_parse_phy_status_ie09(struct rtw89_dev *rtwdev,
+					     const struct rtw89_phy_sts_iehdr *iehdr,
+					     struct rtw89_rx_phy_ppdu *phy_ppdu)
+{
+	phy_ppdu->ie09 = (const void *)iehdr;
+}
+
+static void rtw89_core_parse_phy_status_ie10(struct rtw89_dev *rtwdev,
+					     const struct rtw89_phy_sts_iehdr *iehdr,
+					     struct rtw89_rx_phy_ppdu *phy_ppdu)
+{
+	phy_ppdu->ie10 = (const void *)iehdr;
+}
+
 static int rtw89_core_process_phy_status_ie(struct rtw89_dev *rtwdev,
 					    const struct rtw89_phy_sts_iehdr *iehdr,
 					    struct rtw89_rx_phy_ppdu *phy_ppdu)
 {
+	bool accept;
 	u8 ie;
 
 	ie = le32_get_bits(iehdr->w0, RTW89_PHY_STS_IEHDR_TYPE);
 
+	/*
+	 * For normal mode, only parse ppdu_sts that are A1-matched, except for
+	 * scanning that needs to get chan_idx in IE01.
+	 */
+	accept = phy_ppdu->to_self || ie == RTW89_PHYSTS_IE01_CMN_OFDM ||
+		 rtwdev->hw->conf.flags & IEEE80211_CONF_MONITOR;
+	if (!accept)
+		return 0;
+
 	switch (ie) {
 	case RTW89_PHYSTS_IE00_CMN_CCK:
 		rtw89_core_parse_phy_status_ie00(rtwdev, iehdr, phy_ppdu);
@@ -2169,6 +2193,12 @@ static int rtw89_core_process_phy_status_ie(struct rtw89_dev *rtwdev,
 	case RTW89_PHYSTS_IE01_CMN_OFDM:
 		rtw89_core_parse_phy_status_ie01(rtwdev, iehdr, phy_ppdu);
 		break;
+	case RTW89_PHYSTS_IE09_FTR_0:
+		rtw89_core_parse_phy_status_ie09(rtwdev, iehdr, phy_ppdu);
+		break;
+	case RTW89_PHYSTS_IE10_FTR_PLCP_EXT:
+		rtw89_core_parse_phy_status_ie10(rtwdev, iehdr, phy_ppdu);
+		break;
 	default:
 		break;
 	}
@@ -2228,11 +2258,14 @@ static int rtw89_core_rx_process_phy_ppdu(struct rtw89_dev *rtwdev,
 static int rtw89_core_rx_parse_phy_sts(struct rtw89_dev *rtwdev,
 				       struct rtw89_rx_phy_ppdu *phy_ppdu)
 {
-	u16 ie_len;
 	void *pos, *end;
+	bool accept;
+	u16 ie_len;
 
-	/* mark invalid reports and bypass them */
-	if (phy_ppdu->ie < RTW89_CCK_PKT)
+	/* for normal mode, mark invalid reports and bypass them */
+	accept = phy_ppdu->ie >= RTW89_CCK_PKT ||
+		 rtwdev->hw->conf.flags & IEEE80211_CONF_MONITOR;
+	if (!accept)
 		return -EINVAL;
 
 	pos = phy_ppdu->buf + PHY_STS_HDR_LEN;
@@ -2246,6 +2279,10 @@ static int rtw89_core_rx_parse_phy_sts(struct rtw89_dev *rtwdev,
 		rtw89_core_process_phy_status_ie(rtwdev, iehdr, phy_ppdu);
 		pos += ie_len;
 		if (pos > end || ie_len == 0) {
+			/* clear pointers to prevent accessing out of IE */
+			phy_ppdu->ie09 = NULL;
+			phy_ppdu->ie10 = NULL;
+
 			rtw89_debug(rtwdev, RTW89_DBG_TXRX,
 				    "phy status parse failed\n");
 			return -EINVAL;
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index 66dbb1fc3ca8..ca716e95cb2c 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -24,6 +24,8 @@ struct rtw89_h2c_rf_tssi;
 struct rtw89_fw_txpwr_track_cfg;
 struct rtw89_phy_rfk_log_fmt;
 struct rtw89_phy_calc_efuse_gain;
+struct rtw89_phy_sts_ie09;
+struct rtw89_phy_sts_ie10;
 struct rtw89_debugfs;
 struct rtw89_regd_data;
 struct rtw89_wow_cam_info;
@@ -840,7 +842,7 @@ struct rtw89_rx_phy_ppdu {
 	u8 mac_id;
 	u8 chan_idx;
 	u8 phy_idx;
-	u8 ie;
+	u8 ie; /* enum rtw89_phy_status_bitmap */
 	u16 rate;
 	u8 rpl_avg;
 	u8 rpl_path[RF_PATH_MAX];
@@ -862,6 +864,8 @@ struct rtw89_rx_phy_ppdu {
 	bool to_self;
 	bool valid;
 	bool hdr_2_en;
+	const struct rtw89_phy_sts_ie09 *ie09; /* SIG-A */
+	const struct rtw89_phy_sts_ie10 *ie10; /* SIG-B */
 };
 
 enum rtw89_mac_idx {
diff --git a/drivers/net/wireless/realtek/rtw89/mac80211.c b/drivers/net/wireless/realtek/rtw89/mac80211.c
index 2c6711133c80..330ece51286d 100644
--- a/drivers/net/wireless/realtek/rtw89/mac80211.c
+++ b/drivers/net/wireless/realtek/rtw89/mac80211.c
@@ -98,6 +98,9 @@ static int rtw89_ops_config(struct ieee80211_hw *hw, int radio_idx, u32 changed)
 	    !rtwdev->scanning)
 		rtw89_enter_ips(rtwdev);
 
+	if (changed & IEEE80211_CONF_CHANGE_MONITOR)
+		rtw89_physts_parsing_init(rtwdev);
+
 	return 0;
 }
 
diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c
index 4f82b1a9fa4c..15483a86951d 100644
--- a/drivers/net/wireless/realtek/rtw89/phy.c
+++ b/drivers/net/wireless/realtek/rtw89/phy.c
@@ -7085,10 +7085,21 @@ static void rtw89_physts_enable_hdr_2(struct rtw89_dev *rtwdev, enum rtw89_phy_i
 static void __rtw89_physts_parsing_init(struct rtw89_dev *rtwdev,
 					enum rtw89_phy_idx phy_idx)
 {
+	const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def;
 	const struct rtw89_chip_info *chip = rtwdev->chip;
+	u32 monitor_mode_mu_ies = 0;
+	u32 monitor_mode_su_ies = 0;
 	u32 val;
 	u8 i;
 
+	if (rtwdev->hw->conf.flags & IEEE80211_CONF_MONITOR) {
+		monitor_mode_mu_ies = BIT(RTW89_PHYSTS_IE09_FTR_0);
+		if (phy->physt_gen < 2)
+			monitor_mode_mu_ies |= BIT(RTW89_PHYSTS_IE10_FTR_PLCP_EXT);
+
+		monitor_mode_su_ies = BIT(RTW89_PHYSTS_IE09_FTR_0);
+	}
+
 	rtw89_physts_enable_fail_report(rtwdev, false, phy_idx);
 
 	/* enable hdr_2 for 8922D (PHYSTS_BE_GEN2 above) */
@@ -7102,6 +7113,7 @@ static void __rtw89_physts_parsing_init(struct rtw89_dev *rtwdev,
 		val = rtw89_physts_get_ie_bitmap(rtwdev, i, phy_idx);
 		if (i == RTW89_HE_MU || i == RTW89_VHT_MU) {
 			val |= BIT(RTW89_PHYSTS_IE13_DL_MU_DEF);
+			val |= monitor_mode_mu_ies;
 		} else if (i == RTW89_TRIG_BASE_PPDU) {
 			val |= BIT(RTW89_PHYSTS_IE13_DL_MU_DEF) |
 			       BIT(RTW89_PHYSTS_IE01_CMN_OFDM);
@@ -7115,11 +7127,14 @@ static void __rtw89_physts_parsing_init(struct rtw89_dev *rtwdev,
 				val |= BIT(RTW89_PHYSTS_IE20_DBG_OFDM_FD_USER_SEG_0);
 		}
 
+		if (i == RTW89_HE_PKT || i == RTW89_VHT_PKT)
+			val |= monitor_mode_su_ies;
+
 		rtw89_physts_set_ie_bitmap(rtwdev, i, val, phy_idx);
 	}
 }
 
-static void rtw89_physts_parsing_init(struct rtw89_dev *rtwdev)
+void rtw89_physts_parsing_init(struct rtw89_dev *rtwdev)
 {
 	__rtw89_physts_parsing_init(rtwdev, RTW89_PHY_0);
 	if (rtwdev->dbcc_en)
diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h
index d8038ae5ca86..74fbf5baff58 100644
--- a/drivers/net/wireless/realtek/rtw89/phy.h
+++ b/drivers/net/wireless/realtek/rtw89/phy.h
@@ -914,6 +914,7 @@ void rtw89_phy_config_rf_reg_v1(struct rtw89_dev *rtwdev,
 void rtw89_phy_dm_init(struct rtw89_dev *rtwdev);
 void rtw89_phy_dm_reinit(struct rtw89_dev *rtwdev);
 void rtw89_phy_dm_init_data(struct rtw89_dev *rtwdev);
+void rtw89_physts_parsing_init(struct rtw89_dev *rtwdev);
 void rtw89_phy_write32_idx(struct rtw89_dev *rtwdev, u32 addr, u32 mask,
 			   u32 data, enum rtw89_phy_idx phy_idx);
 void rtw89_phy_write32_idx_set(struct rtw89_dev *rtwdev, u32 addr, u32 bits,
diff --git a/drivers/net/wireless/realtek/rtw89/txrx.h b/drivers/net/wireless/realtek/rtw89/txrx.h
index 125ba2a9f145..18fe6d3d0f83 100644
--- a/drivers/net/wireless/realtek/rtw89/txrx.h
+++ b/drivers/net/wireless/realtek/rtw89/txrx.h
@@ -658,6 +658,15 @@ struct rtw89_phy_sts_ie01_v2 {
 #define RTW89_PHY_STS_IE01_V2_W9_RPL_FD_C GENMASK(11, 4)
 #define RTW89_PHY_STS_IE01_V2_W9_RPL_FD_D GENMASK(23, 16)
 
+struct rtw89_phy_sts_ie09 {
+	__le64 qw0;
+} __packed;
+
+struct rtw89_phy_sts_ie10 {
+	__le64 qw0;
+	u8 sigb[];
+} __packed;
+
 enum rtw89_tx_channel {
 	RTW89_TXCH_ACH0	= 0,
 	RTW89_TXCH_ACH1	= 1,
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 04/14] wifi: rtw89: phy: define PHY status IE length for generations
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

Both RTL8922A and RTL8922D are WiFi 7 chips, but their IE length of PHY
status are different. Define them accordingly.

Generation 0: WiFi 6 chips
Generation 1: WiFi 7 RTL8922A
Generation 2: WiFi 7 RTL8922D

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c   | 19 ++-----------------
 drivers/net/wireless/realtek/rtw89/phy.c    |  4 ++++
 drivers/net/wireless/realtek/rtw89/phy.h    |  5 +++++
 drivers/net/wireless/realtek/rtw89/phy_be.c |  8 ++++++++
 4 files changed, 19 insertions(+), 17 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index d6bf1d57e8e1..caedb2bd21d5 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -2040,29 +2040,14 @@ static void rtw89_core_rx_process_phy_ppdu_iter(void *data,
 	}
 }
 
-#define VAR_LEN 0xff
-#define VAR_LEN_UNIT 8
 static u16 rtw89_core_get_phy_status_ie_len(struct rtw89_dev *rtwdev,
 					    const struct rtw89_phy_sts_iehdr *iehdr)
 {
-	static const u8 physts_ie_len_tabs[RTW89_CHIP_GEN_NUM][32] = {
-		[RTW89_CHIP_AX] = {
-			16, 32, 24, 24, 8, 8, 8, 8, VAR_LEN, 8, VAR_LEN, 176, VAR_LEN,
-			VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, 16, 24, VAR_LEN,
-			VAR_LEN, VAR_LEN, 0, 24, 24, 24, 24, 32, 32, 32, 32
-		},
-		[RTW89_CHIP_BE] = {
-			32, 40, 24, 24, 8, 8, 8, 8, VAR_LEN, 8, VAR_LEN, 176, VAR_LEN,
-			VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, 88, 56, VAR_LEN,
-			VAR_LEN, VAR_LEN, 0, 24, 24, 24, 24, 32, 32, 32, 32
-		},
-	};
-	const u8 *physts_ie_len_tab;
+	const struct rtw89_phy_gen_def *phy = rtwdev->chip->phy_def;
+	const u8 *physts_ie_len_tab = phy->physt_ie_len;
 	u16 ie_len;
 	u8 ie;
 
-	physts_ie_len_tab = physts_ie_len_tabs[rtwdev->chip->chip_gen];
-
 	ie = le32_get_bits(iehdr->w0, RTW89_PHY_STS_IEHDR_TYPE);
 	if (physts_ie_len_tab[ie] != VAR_LEN)
 		ie_len = physts_ie_len_tab[ie];
diff --git a/drivers/net/wireless/realtek/rtw89/phy.c b/drivers/net/wireless/realtek/rtw89/phy.c
index f98a77f9fc1a..4f82b1a9fa4c 100644
--- a/drivers/net/wireless/realtek/rtw89/phy.c
+++ b/drivers/net/wireless/realtek/rtw89/phy.c
@@ -8948,6 +8948,10 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_ax = {
 	.cr_base = 0x10000,
 	.physt_bmp_start = R_PHY_STS_BITMAP_ADDR_START,
 	.physt_bmp_eht = 0xfc,
+	.physt_ie_len = {16, 32, 24, 24, 8, 8, 8, 8, VAR_LEN, 8, VAR_LEN, 176, VAR_LEN,
+			 VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, 16, 24, VAR_LEN,
+			 VAR_LEN, VAR_LEN, 0, 24, 24, 24, 24, 32, 32, 32, 32},
+	.physt_gen = 0,
 	.ccx = &rtw89_ccx_regs_ax,
 	.physts = &rtw89_physts_regs_ax,
 	.cfo = &rtw89_cfo_regs_ax,
diff --git a/drivers/net/wireless/realtek/rtw89/phy.h b/drivers/net/wireless/realtek/rtw89/phy.h
index a6e685654037..d8038ae5ca86 100644
--- a/drivers/net/wireless/realtek/rtw89/phy.h
+++ b/drivers/net/wireless/realtek/rtw89/phy.h
@@ -154,6 +154,9 @@
 #define EDCCA_UNIT_CONVER 128
 #define EDCCA_PWROFST_DEFAULT 18
 
+#define VAR_LEN 0xff
+#define VAR_LEN_UNIT 8
+
 enum rtw89_phy_c2h_ra_func {
 	RTW89_PHY_C2H_FUNC_STS_RPT,
 	RTW89_PHY_C2H_FUNC_MU_GPTBL_RPT,
@@ -573,6 +576,8 @@ struct rtw89_phy_gen_def {
 	u32 cr_base;
 	u32 physt_bmp_start;
 	u32 physt_bmp_eht;
+	u8 physt_ie_len[32];
+	u8 physt_gen;
 	const struct rtw89_ccx_regs *ccx;
 	const struct rtw89_physts_regs *physts;
 	const struct rtw89_cfo_regs *cfo;
diff --git a/drivers/net/wireless/realtek/rtw89/phy_be.c b/drivers/net/wireless/realtek/rtw89/phy_be.c
index 5cd298a2c91b..23137f2dbd4b 100644
--- a/drivers/net/wireless/realtek/rtw89/phy_be.c
+++ b/drivers/net/wireless/realtek/rtw89/phy_be.c
@@ -1561,6 +1561,10 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_be = {
 	.cr_base = 0x20000,
 	.physt_bmp_start = R_PHY_STS_BITMAP_ADDR_START,
 	.physt_bmp_eht = R_PHY_STS_BITMAP_EHT,
+	.physt_ie_len = {32, 40, 24, 24, 8, 8, 8, 8, VAR_LEN, 8, VAR_LEN, 176, VAR_LEN,
+			 VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, 88, 56, VAR_LEN,
+			 VAR_LEN, VAR_LEN, 0, 24, 24, 24, 24, 32, 32, 32, 32},
+	.physt_gen = 1,
 	.ccx = &rtw89_ccx_regs_be,
 	.physts = &rtw89_physts_regs_be,
 	.cfo = &rtw89_cfo_regs_be,
@@ -1582,6 +1586,10 @@ const struct rtw89_phy_gen_def rtw89_phy_gen_be_v1 = {
 	.cr_base = 0x0,
 	.physt_bmp_start = R_PHY_STS_BITMAP_ADDR_START_BE4,
 	.physt_bmp_eht = R_PHY_STS_BITMAP_EHT_BE4,
+	.physt_ie_len = {32, 40, 24, 24, 16, 16, 16, 16, VAR_LEN, VAR_LEN, VAR_LEN, 168,
+			 VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, VAR_LEN, 32, 56,
+			 96, VAR_LEN, VAR_LEN, 0, 24, 24, 24, 24, 32, 32, 32, 32},
+	.physt_gen = 2,
 	.ccx = &rtw89_ccx_regs_be_v1,
 	.physts = &rtw89_physts_regs_be_v1,
 	.cfo = &rtw89_cfo_regs_be_v1,
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 03/14] wifi: rtw89: SNIFFER_MODE bit along IEEE80211_CONF_MONITOR
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

The SNIFFER_MODE bit can ignore filter rules, and receive packets to
driver, so set the bit to accept all packets.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/mac80211.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/mac80211.c b/drivers/net/wireless/realtek/rtw89/mac80211.c
index b72f6661fbd1..2c6711133c80 100644
--- a/drivers/net/wireless/realtek/rtw89/mac80211.c
+++ b/drivers/net/wireless/realtek/rtw89/mac80211.c
@@ -356,6 +356,11 @@ static void rtw89_ops_configure_filter(struct ieee80211_hw *hw,
 		}
 	}
 
+	if (rtwdev->hw->conf.flags & IEEE80211_CONF_MONITOR)
+		rtwdev->hal.rx_fltr |= B_AX_SNIFFER_MODE;
+	else
+		rtwdev->hal.rx_fltr &= ~B_AX_SNIFFER_MODE;
+
 	rx_fltr = rtwdev->hal.rx_fltr;
 
 	/* mac80211 doesn't configure filter when HW scan, driver need to
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 02/14] wifi: rtw89: add VHT beamformed to radiotap
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

Set VHT beamformed bit by PHY status IE-01 report.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index 240502b25b2e..d6bf1d57e8e1 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -2099,6 +2099,7 @@ static void rtw89_core_parse_phy_status_ie01(struct rtw89_dev *rtwdev,
 	phy_ppdu->chan_idx = le32_get_bits(ie->w0, RTW89_PHY_STS_IE01_W0_CH_IDX);
 	phy_ppdu->ldpc = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_LDPC);
 	phy_ppdu->stbc = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_STBC);
+	phy_ppdu->bf = le32_get_bits(ie->w3, RTW89_PHY_STS_IE01_W3_BF);
 
 	if (!phy_ppdu->hdr_2_en)
 		phy_ppdu->rx_path_en =
@@ -2115,7 +2116,6 @@ static void rtw89_core_parse_phy_status_ie01(struct rtw89_dev *rtwdev,
 	phy_ppdu->ofdm.avg_snr = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_AVG_SNR);
 	phy_ppdu->ofdm.evm_max = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_EVM_MAX);
 	phy_ppdu->ofdm.evm_min = le32_get_bits(ie->w2, RTW89_PHY_STS_IE01_W2_EVM_MIN);
-	phy_ppdu->bf = le32_get_bits(ie->w3, RTW89_PHY_STS_IE01_W3_BF);
 	phy_ppdu->ofdm.has = true;
 
 	/* sign conversion for S(12,2) */
@@ -3146,6 +3146,8 @@ void rtw89_core_update_rx_status_by_ppdu(struct rtw89_dev *rtwdev,
 	if (!phy_ppdu)
 		return;
 
+	if (phy_ppdu->bf)
+		rx_status->enc_flags |= RX_ENC_FLAG_BF;
 	if (phy_ppdu->ldpc)
 		rx_status->enc_flags |= RX_ENC_FLAG_LDPC;
 	if (phy_ppdu->stbc)
@@ -6848,7 +6850,8 @@ static int rtw89_core_register_hw(struct rtw89_dev *rtwdev)
 
 	hw->radiotap_mcs_details |= IEEE80211_RADIOTAP_MCS_HAVE_FEC |
 				    IEEE80211_RADIOTAP_MCS_HAVE_STBC;
-	hw->radiotap_vht_details |= IEEE80211_RADIOTAP_VHT_KNOWN_STBC;
+	hw->radiotap_vht_details |= IEEE80211_RADIOTAP_VHT_KNOWN_STBC |
+				    IEEE80211_RADIOTAP_VHT_KNOWN_BEAMFORMED;
 
 	ieee80211_hw_set(hw, SIGNAL_DBM);
 	ieee80211_hw_set(hw, HAS_RATE_CONTROL);
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 01/14] wifi: rtw89: add AMPDU to radiotap
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless
In-Reply-To: <20260506131000.1706298-1-pkshih@realtek.com>

The RX desc can report current frame is in AMPDU, but no way point out
if it is a last one in AMPDU. Update AMPDU reference only.

Signed-off-by: Ping-Ke Shih <pkshih@realtek.com>
---
 drivers/net/wireless/realtek/rtw89/core.c | 8 ++++++++
 drivers/net/wireless/realtek/rtw89/core.h | 1 +
 2 files changed, 9 insertions(+)

diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c
index b85728ceb63c..240502b25b2e 100644
--- a/drivers/net/wireless/realtek/rtw89/core.c
+++ b/drivers/net/wireless/realtek/rtw89/core.c
@@ -3568,6 +3568,7 @@ void rtw89_core_query_rxdesc(struct rtw89_dev *rtwdev,
 	desc_info->hw_dec = le32_get_bits(rxd_s->dword3, AX_RXD_HW_DEC);
 	desc_info->sw_dec = le32_get_bits(rxd_s->dword3, AX_RXD_SW_DEC);
 	desc_info->addr1_match = le32_get_bits(rxd_s->dword3, AX_RXD_A1_MATCH);
+	desc_info->ampdu = le32_get_bits(rxd_s->dword3, AX_RXD_AMPDU);
 
 	shift_len = desc_info->shift << 1; /* 2-byte unit */
 	drv_info_len = desc_info->drv_info_size << 3; /* 8-byte unit */
@@ -3624,6 +3625,7 @@ void rtw89_core_query_rxdesc_v2(struct rtw89_dev *rtwdev,
 	desc_info->hw_dec = le32_get_bits(rxd_s->dword3, BE_RXD_HW_DEC);
 	desc_info->sw_dec = le32_get_bits(rxd_s->dword3, BE_RXD_SW_DEC);
 	desc_info->addr1_match = le32_get_bits(rxd_s->dword3, BE_RXD_A1_MATCH);
+	desc_info->ampdu = le32_get_bits(rxd_s->dword3, BE_RXD_AMPDU);
 
 	desc_info->bw = le32_get_bits(rxd_s->dword4, BE_RXD_BW_MASK);
 	desc_info->data_rate = le32_get_bits(rxd_s->dword4, BE_RXD_RX_DATARATE_MASK);
@@ -3698,6 +3700,7 @@ void rtw89_core_query_rxdesc_v3(struct rtw89_dev *rtwdev,
 	desc_info->hw_dec = le32_get_bits(rxd_s->dword3, BE_RXD_HW_DEC);
 	desc_info->sw_dec = le32_get_bits(rxd_s->dword3, BE_RXD_SW_DEC);
 	desc_info->addr1_match = le32_get_bits(rxd_s->dword3, BE_RXD_A1_MATCH);
+	desc_info->ampdu = le32_get_bits(rxd_s->dword3, BE_RXD_AMPDU);
 
 	desc_info->bw = le32_get_bits(rxd_s->dword4, BE_RXD_BW_MASK);
 	desc_info->data_rate = le32_get_bits(rxd_s->dword4, BE_RXD_RX_DATARATE_MASK);
@@ -3830,6 +3833,11 @@ static void rtw89_core_update_rx_status(struct rtw89_dev *rtwdev,
 	    !(desc_info->sw_dec || desc_info->icv_err))
 		rx_status->flag |= RX_FLAG_DECRYPTED;
 
+	if (desc_info->ampdu) {
+		rx_status->flag |= RX_FLAG_AMPDU_DETAILS;
+		rx_status->ampdu_reference = desc_info->ppdu_cnt;
+	}
+
 	rx_status->bw = rtw89_hw_to_rate_info_bw(desc_info->bw);
 
 	data_rate = desc_info->data_rate;
diff --git a/drivers/net/wireless/realtek/rtw89/core.h b/drivers/net/wireless/realtek/rtw89/core.h
index bccfee7535a7..66dbb1fc3ca8 100644
--- a/drivers/net/wireless/realtek/rtw89/core.h
+++ b/drivers/net/wireless/realtek/rtw89/core.h
@@ -1130,6 +1130,7 @@ struct rtw89_rx_desc_info {
 	bool hw_dec;
 	bool sw_dec;
 	bool addr1_match;
+	bool ampdu;
 	u8 frag;
 	u16 seq;
 	u8 frame_type;
-- 
2.25.1


^ permalink raw reply related

* [PATCH rtw-next 00/14] wifi: rtw89: improve radiotap especially HE SIG-A/SIG-B
From: Ping-Ke Shih @ 2026-05-06 13:09 UTC (permalink / raw)
  To: linux-wireless

The rtw89 has supported monitor mode already, but only basic channel,
bandwidth and data rate. This patchset introduces SIG-A/SIG-B from
PHY status IE-09/IE-10 to fill VHT and HE-SU/HE-TB/HE-MU/HE-EXT_SU
radiotap.

Hardware captures HE-MU packets by target BSS color and AID, so add a
debugfs to fill the target values.

Ping-Ke Shih (14):
  wifi: rtw89: add AMPDU to radiotap
  wifi: rtw89: add VHT beamformed to radiotap
  wifi: rtw89: SNIFFER_MODE bit along IEEE80211_CONF_MONITOR
  wifi: rtw89: phy: define PHY status IE length for generations
  wifi: rtw89: phy: enable IE-09/IE-10 PHY status report for monitor
    mode
  wifi: rtw89: move HE radiotap to an individual function
  wifi: rtw89: fill VHT radiotap
  wifi: rtw89: fill HE-SU/HE-TB/HE-MU/HE-EXT_SU radiotap
  wifi: rtw89: debug: make implementation of beacon_info entry in order
  wifi: rtw89: add debugfs entry of monitor mode options to capture
    HE-MU packets
  wifi: rtw89: phy: check length before parsing PHY status IE
  wifi: rtw89: phy: skip trailing 8-byte zeros of PHY status IE for
    RTL8922D
  wifi: rtw89: phy: support PHY status IE-09 GEN2 for RTL8922D
  wifi: rtw89: check skb headroom before adding radiotap

 drivers/net/wireless/realtek/rtw89/core.c     | 615 ++++++++++++++++--
 drivers/net/wireless/realtek/rtw89/core.h     |  29 +-
 drivers/net/wireless/realtek/rtw89/debug.c    | 165 +++--
 drivers/net/wireless/realtek/rtw89/mac80211.c |   8 +
 drivers/net/wireless/realtek/rtw89/phy.c      |  44 +-
 drivers/net/wireless/realtek/rtw89/phy.h      |   8 +
 drivers/net/wireless/realtek/rtw89/phy_be.c   |   8 +
 drivers/net/wireless/realtek/rtw89/txrx.h     | 108 +++
 8 files changed, 879 insertions(+), 106 deletions(-)


base-commit: c1ed02655f9134d6af6a01a58b734329c2f4f22c
-- 
2.25.1


^ permalink raw reply

* [PATCH wireless 2/2] wifi: mac80211: don't recalc min def for S1G chan ctx
From: Lachlan Hodges @ 2026-05-06 12:43 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, arien.judge, Lachlan Hodges
In-Reply-To: <20260506124307.2070157-1-lachlan.hodges@morsemicro.com>

__ieee80211_recalc_chanctx_min_def() currently does not attempt
to find the min def for S1G widths, meaning the BW will never change.
However, the following call into ieee80211_chan_bw_change() will
lead to a WARN within ieee80211_chan_width_to_rx_bw(). Not only that,
this entire path is geared towards 20MHz based channels, so it doesn't
make sense anyway. For now, return early when calculating the mindef
for S1G channels.

Fixes: d879d4da4579 ("wifi: mac80211: clean up initial STA NSS/bandwidth handling")
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
 net/mac80211/chan.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index b9d563f927da..c64a99131954 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -751,8 +751,14 @@ _ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local,
 				  struct ieee80211_link_data *rsvd_for,
 				  bool check_reserved)
 {
-	u32 changed = __ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for,
-							 check_reserved);
+	u32 changed;
+
+	/* No recalc for S1G chan ctx's */
+	if (cfg80211_chandef_is_s1g(&ctx->conf.def))
+		return;
+
+	changed = __ieee80211_recalc_chanctx_min_def(local, ctx, rsvd_for,
+						     check_reserved);
 
 	/* check is BW narrowed */
 	ieee80211_chan_bw_change(local, ctx, false, true);
-- 
2.43.0


^ permalink raw reply related

* [PATCH wireless 1/2] wifi: mac80211: skip NSS and BW init for S1G sta
From: Lachlan Hodges @ 2026-05-06 12:43 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, arien.judge, Lachlan Hodges
In-Reply-To: <20260506124307.2070157-1-lachlan.hodges@morsemicro.com>

Currently there is no S1G STA bandwidth support throughout mac80211
as existing support is all based on 20MHz widths. With the recent
STA NSS/BW handling rework, S1G associations now hit the new WARN within
ieee80211_chan_width_to_rx_bw() as the chandef is not a 20MHz based
width. For now, skip initialisating link_sta->pub->bandwidth for
S1G chandefs to avoid the WARN though this should at some point be
properly implemented since there are vendors that offer differing
maximum bandwidths.

Additionally, currently all S1G hardware out there is 1SS so rather
then introducing new parsing code which wouldn't be used anyway, just
initialise the NSS related fields to 1 and skip initialising the STA
bandwidth for S1G chandefs within ieee80211_sta_init_nss_bw_capa().

Fixes: d879d4da4579 ("wifi: mac80211: clean up initial STA NSS/bandwidth handling")
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
 net/mac80211/sta_info.c | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index aba2fabfe0db..0ea37016cd4f 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -3516,6 +3516,23 @@ static u8 ieee80211_sta_nss_capability(struct link_sta_info *link_sta)
 void ieee80211_sta_init_nss_bw_capa(struct link_sta_info *link_sta,
 				    struct cfg80211_chan_def *chandef)
 {
+	/*
+	 * TODO: The entirety of the STA Tx/Rx bandwidth handling
+	 * assumes 20MHz based widths, so for now don't initialise
+	 * pubsta->bandwidth for S1G bands. Since enum
+	 * ieee80211_sta_rx_bandwidth is ordered, we will probably
+	 * need to introduce ieee80211_s1g_sta_rx_bandwidth with
+	 * S1G widths and associated S1G specific code. Additionally,
+	 * existing S1G hardware is all 1SS, in the future if hardware
+	 * starts supporting >1SS this should be implemented in
+	 * ieee80211_sta_nss_capability().
+	 */
+	if (cfg80211_chandef_is_s1g(chandef)) {
+		link_sta->capa_nss = 1;
+		link_sta->pub->rx_nss = 1;
+		return;
+	}
+
 	link_sta->capa_nss = ieee80211_sta_nss_capability(link_sta);
 	link_sta->pub->rx_nss = link_sta->capa_nss;
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH wireless 0/2] S1G channel fixes
From: Lachlan Hodges @ 2026-05-06 12:43 UTC (permalink / raw)
  To: johannes; +Cc: linux-wireless, arien.judge, Lachlan Hodges

Some recent changes to the STA BW handling now lead to WARNs during
S1G association. These are more or less bandaids that will need to
be fixed in the future hence the TODO.

This will definitely need to be sorted out in the future, even if it's
just a rudimentary implementation especially if we ever want channel
switching and "proper" interop with other vendors such as Newracomm
who have different maximum bandwidths. I've sent these two atleast
while the driver is under review, though I know TODOs can very easily
remain as TODOs 10 years from now...

[1] https://lore.kernel.org/linux-wireless/20251008014006.219605-1-lachlan.hodges@morsemicro.com/

Lachlan Hodges (2):
  wifi: mac80211: skip NSS and BW init for S1G sta
  wifi: mac80211: don't recalc min def for S1G chan ctx

 net/mac80211/chan.c     | 10 ++++++++--
 net/mac80211/sta_info.c | 17 +++++++++++++++++
 2 files changed, 25 insertions(+), 2 deletions(-)

-- 
2.43.0


^ permalink raw reply

* Re: [PATCH v5 14/16] wifi: ath12k: Switch to generic PAS TZ APIs
From: Sumit Garg @ 2026-05-06 11:44 UTC (permalink / raw)
  To: Jeff Johnson
  Cc: andersson, konradybcio, Sowmiya Sree Elavalagan, linux-arm-msm,
	devicetree, dri-devel, freedreno, linux-media, netdev,
	linux-wireless, ath12k, linux-remoteproc, robh, krzk+dt, conor+dt,
	robin.clark, sean, akhilpo, lumag, abhinav.kumar, jesszhan0024,
	marijn.suijten, airlied, simona, vikash.garodia, dikshita.agarwal,
	bod, mchehab, elder, andrew+netdev, davem, edumazet, kuba, pabeni,
	jjohnson, mathieu.poirier, trilokkumar.soni, mukesh.ojha,
	pavan.kondeti, jorge.ramirez, tonyh, vignesh.viswanathan,
	srinivas.kandagatla, amirreza.zarrabi, jens.wiklander, op-tee,
	apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <ae68bdb3-d683-4e7a-a27f-739214656ce2@oss.qualcomm.com>

On Tue, May 05, 2026 at 07:50:03AM -0700, Jeff Johnson wrote:
> On 5/5/2026 7:27 AM, Jeff Johnson wrote:
> > On 5/4/2026 6:06 AM, Sumit Garg wrote:
> >> @@ -485,9 +485,9 @@ static void ath12k_ahb_power_down(struct ath12k_base *ab, bool is_suspend)
> >>  		pasid = (u32_encode_bits(ab_ahb->userpd_id, ATH12K_USERPD_ID_MASK)) |
> >>  			 ATH12K_AHB_UPD_SWID;
> >>  		/* Release the firmware */
> >> -		ret = qcom_scm_pas_shutdown(pasid);
> >> +		ret = qcom_pas_shutdown(pasid);
> >>  		if (ret)
> >> -			ath12k_err(ab, "scm pas shutdown failed for userPD%d\n",
> >> +			ath12k_err(ab, "pas shutdown failed for userPD%d: %d\n",
> >>  				   ab_ahb->userpd_id);
> > 
> > at some point the "ret" param was dropped, and this now generates build warnings
> The 'ret' param was dropped by:
> 8fb66931fe31 ("wifi: ath12k: Enable IPQ5424 WiFi device support")
> 
> Not sure if that was on purpose or accidental. Sowmiya?
> 
> -       if (ret)
> -               ath12k_err(ab, "scm pas shutdown failed for userPD%d: %d\n",
> -                          ab_ahb->userpd_id, ret);
> ...
> +               if (ret)
> +                       ath12k_err(ab, "scm pas shutdown failed for userPD%d\n",
> +                                  ab_ahb->userpd_id);
>

Ah I see, not sure why I didn't see any build issues in the defconfig
builds. Maybe this driver isn't enabled there?

Anyhow I can add the "ret" back here.

-Sumit

^ permalink raw reply

* [GIT PULL] wireless-next-2026-05-06
From: Johannes Berg @ 2026-05-06 11:10 UTC (permalink / raw)
  To: netdev; +Cc: linux-wireless

Hi,

More content for next, as below. I'm sending this now
in hopes that we'll get net merged into net-next after
it all lands, because I'm annoyed by the use-after-free
issue that somehow we never noticed and now hits all
the time.

Please pull and let us know if there's any problem.

Thanks,
johannes



The following changes since commit 6855a52318b3a8c33031209e38bef497c971ef17:

  Merge tag 'wireless-next-2026-04-30' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next (2026-04-30 17:10:21 -0700)

are available in the Git repository at:

  https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git tags/wireless-next-2026-05-06

for you to fetch changes up to 89e367a90c1a877ca9c5d75d3848582d80fd0e60:

  wifi: mac80211: explicitly disable FTM responder on AP stop (2026-05-06 11:52:14 +0200)

----------------------------------------------------------------
Lots of new content in cfg80211/mac80211, notably
 - more NAN work, mostly complete now (also hwsim)
 - more UHR work (e.g. non-primary channel access),
   this will continue for a while
 - FTM ranging APIs

----------------------------------------------------------------
Andrei Otcheretianski (1):
      wifi: mac80211: Fix a kernel panic in ieee80211_encrypt_tx_skb()

Avraham Stern (1):
      wifi: mac80211: accept protected frames for NAN device

Benjamin Berg (13):
      wifi: mac80211_hwsim: remove unused nan_vif struct member
      wifi: mac80211_hwsim: move NAN related variables into a struct
      wifi: mac80211_hwsim: split NAN handling into separate file
      wifi: mac80211_hwsim: rename and switch simulation time to boottime
      wifi: mac80211_hwsim: move timestamp writing later in the datapath
      wifi: mac80211_hwsim: register beacon timer by calculating TBTT
      wifi: mac80211_hwsim: refactor NAN timer handling
      wifi: mac80211_hwsim: switch to use TXQs
      wifi: mac80211_hwsim: limit TX of frames to the NAN DW
      wifi: mac80211_hwsim: select NAN TX channel based on current TSF
      wifi: mac80211_hwsim: only RX on NAN when active on a slot
      wifi: mac80211_hwsim: protect tsf_offset using a spinlock
      wifi: mac80211_hwsim: implement NAN synchronization

Daniel Gabay (7):
      wifi: mac80211_hwsim: add NAN_DATA interface limits
      wifi: mac80211_hwsim: add NAN PHY capabilities
      wifi: mac80211_hwsim: implement NAN schedule callbacks
      wifi: mac80211_hwsim: set HAS_RATE_CONTROL when using NAN
      wifi: mac80211_hwsim: add NAN data path TX/RX support
      wifi: mac80211_hwsim: Declare support for secure NAN
      wifi: mac80211_hwsim: enable NAN_DATA interface simulation support

Emmanuel Grumbach (1):
      wifi: iwlwifi: don't blindly start the responder upon BSS_CHANGED_FTM_RESPONDER

Ilan Peer (4):
      wifi: mac80211: allow userspace TX/RX over NAN Data interfaces
      wifi: mac80211: Allow setting MAC address on interface creation
      wifi: mac80211_hwsim: Do not declare support for NDPE
      wifi: mac80211_hwsim: Support Tx of multicast data on NAN

Israel Kozitz (1):
      wifi: cfg80211: fix max_channel_switch_time documentation unit

Johannes Berg (23):
      wifi: mac80211: move frame RX handling to type files
      wifi: mac80211: update UHR capabilities field order
      wifi: ieee80211: define UHR ML-PM extended MLD capability
      wifi: mac80211: track AP's extended MLD capa/ops
      wifi: cfg80211: ensure UHR ML-PM flag is consistent
      wifi: cfg80211: allow devices to advertise extended MLD capa/ops
      wifi: mac80211: mlme: advertise driver's extended MLD capa/ops
      wifi: mac80211: use struct for ieee80211_determine_ap_chan() args
      wifi: mac80211: move ieee80211_chandef_usable() up
      wifi: mac80211: carry element parsing frame type/from_ap
      wifi: cfg80211: allow representing NPCA in chandef
      wifi: cfg80211: add helper for parsing NPCA to chandef
      wifi: mac80211: use NPCA in chandef for validation
      wifi: mac80211: remove NPCA during chandef downgrade
      wifi: mac80211: add NPCA to chandef tracing
      wifi: mac80211: allow only AP chanctx sharing with NPCA
      wifi: mac80211: mlme: use NPCA chandef if capable
      wifi: mac80211: set AP NPCA parameters in bss_conf
      wifi: cfg80211: separate NPCA validity from chandef validity
      wifi: mac80211: don't parse full UHR operation from beacons
      wifi: mac80211: check AP using NPCA has NPCA capability
      wifi: mac80211_hwsim: claim HT STBC capability
      wifi: mac80211: explicitly disable FTM responder on AP stop

Kavita Kavita (3):
      wifi: cfg80211: indicate (Re)Association frame encryption to userspace
      wifi: mac80211: set assoc_encrypted for EPP associations
      wifi: mac80211_hwsim: Add support for extended FTM ranging

Miri Korenblit (4):
      wifi: mac80211: track the id of the NAN cluster we joined
      wifi: mac80211: avoid out-of-bounds access in monitor
      wifi: mac80211: add NAN channel evacuation support
      wifi: cfg80211: don't allow NAN DATA on multi radio devices

Peddolla Harshavardhan Reddy (12):
      wifi: cfg80211: restrict LMR feedback check to TB and non-TB ranging
      wifi: cfg80211: Add MAC address filter to remain_on_channel
      wifi: cfg80211/mac80211: Add NL80211_IFTYPE_PD for PD PASN and PMSR operations
      wifi: cfg80211: add start/stop proximity detection commands
      wifi: cfg80211: add proximity detection capabilities to PMSR
      wifi: cfg80211: add NTB continuous ranging and FTM request type support
      wifi: cfg80211: extend PMSR FTM response for proximity ranging
      wifi: cfg80211: add role-based peer limits to FTM capabilities
      wifi: cfg80211: add ingress/egress distance thresholds for FTM
      wifi: cfg80211: add PD-specific preamble and bandwidth capabilities
      wifi: cfg80211: allow suppressing FTM result reporting for PD requests
      wifi: cfg80211: add LTF keyseed support for secure ranging

 MAINTAINERS                                        |    2 +-
 drivers/net/wireless/ath/ath6kl/cfg80211.c         |    3 +-
 drivers/net/wireless/ath/wil6210/cfg80211.c        |    3 +-
 .../net/wireless/broadcom/brcm80211/brcmfmac/p2p.c |    4 +-
 .../net/wireless/broadcom/brcm80211/brcmfmac/p2p.h |    3 +-
 drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c  |    4 +-
 .../net/wireless/intel/iwlwifi/mvm/mld-mac80211.c  |    4 +-
 drivers/net/wireless/marvell/mwifiex/cfg80211.c    |    3 +-
 drivers/net/wireless/microchip/wilc1000/cfg80211.c |    3 +-
 drivers/net/wireless/virtual/Makefile              |    2 +
 drivers/net/wireless/virtual/mac80211_hwsim_i.h    |  168 +++
 .../{mac80211_hwsim.c => mac80211_hwsim_main.c}    |  974 +++++++-------
 drivers/net/wireless/virtual/mac80211_hwsim_nan.c  | 1346 ++++++++++++++++++++
 drivers/net/wireless/virtual/mac80211_hwsim_nan.h  |  102 ++
 include/linux/ieee80211-eht.h                      |   14 +-
 include/linux/ieee80211-uhr.h                      |   55 +-
 include/linux/ieee80211.h                          |    1 +
 include/net/cfg80211.h                             |  251 +++-
 include/net/mac80211.h                             |   64 +
 include/uapi/linux/nl80211.h                       |  293 ++++-
 net/mac80211/Makefile                              |    2 +-
 net/mac80211/ap.c                                  |  207 +++
 net/mac80211/cfg.c                                 |   77 +-
 net/mac80211/chan.c                                |  102 +-
 net/mac80211/eht.c                                 |  174 ---
 net/mac80211/ieee80211_i.h                         |   28 +-
 net/mac80211/iface.c                               |   62 +-
 net/mac80211/main.c                                |    7 +-
 net/mac80211/mlme.c                                |  579 +++++----
 net/mac80211/nan.c                                 |  126 ++
 net/mac80211/offchannel.c                          |   13 +-
 net/mac80211/parse.c                               |    3 +
 net/mac80211/rx.c                                  |   33 +-
 net/mac80211/status.c                              |    9 +-
 net/mac80211/trace.h                               |   14 +-
 net/mac80211/tx.c                                  |   18 +-
 net/mac80211/uhr.c                                 |    5 +-
 net/mac80211/util.c                                |   11 +-
 net/wireless/chan.c                                |  124 +-
 net/wireless/core.c                                |   36 +-
 net/wireless/core.h                                |    2 +
 net/wireless/mlme.c                                |   11 +-
 net/wireless/nl80211.c                             |  368 +++++-
 net/wireless/nl80211.h                             |    5 +-
 net/wireless/pmsr.c                                |  190 ++-
 net/wireless/rdev-ops.h                            |   26 +-
 net/wireless/reg.c                                 |    3 +
 net/wireless/sme.c                                 |    1 +
 net/wireless/trace.h                               |   38 +-
 net/wireless/util.c                                |   19 +-
 50 files changed, 4520 insertions(+), 1072 deletions(-)
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_i.h
 rename drivers/net/wireless/virtual/{mac80211_hwsim.c => mac80211_hwsim_main.c} (91%)
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_nan.c
 create mode 100644 drivers/net/wireless/virtual/mac80211_hwsim_nan.h
 create mode 100644 net/mac80211/ap.c

^ permalink raw reply

* [GIT PULL] wireless-2026-05-06
From: Johannes Berg @ 2026-05-06 11:01 UTC (permalink / raw)
  To: netdev; +Cc: linux-wireless

Hi,

More content given last week's kunit failure, but I've fixed
that now, sorry about that. Things are all over really.

Please pull and let us know if there's any problem.

Thanks,
johannes



The following changes since commit 254f49634ee16a731174d2ae34bc50bd5f45e731:

  Linux 7.1-rc1 (2026-04-26 14:19:00 -0700)

are available in the Git repository at:

  https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git tags/wireless-2026-05-06

for you to fetch changes up to 79240f3f6d766b342b57c32397d643e1cfa26b81:

  wifi: nl80211: re-check wiphy netns in nl80211_prepare_wdev_dump() continuation (2026-05-06 11:08:41 +0200)

----------------------------------------------------------------
Quite a number of fixes now:
 - mac80211
   - remove HT NSS validation to work with broken APs
     (with a kunit fix now)
   - remove 'static' that could cause races
   - check station link lookup before further processing
   - fix use-after-free due to delete in list iteration
   - remove AP station on assoc failures to fix crashes
 - ath12k
   - fix OF node refcount imbalance
   - fix queue flush ("REO update") in MLO
   - fix RCU assert
 - ath12k:
   - fix Kconfig with POWER_SEQUENCING
   - fix WMI buffer leaks on error conditions
   - don't use uninitialized stack data when processing RSSI events
   - fix logic for determining the peer ID in the RX path
 - ath5k: fix a potential stack buffer overwrite
 - rsi: fix thread lifetime race
 - brcmfmac: fix potential UAF
 - nl80211:
   - stricter permissions/checks for PMK and netns
   - fix netlink policy vs. code type confusion
 - cw1200: revert a broken locking change
 - various fixes to not trust values from firmware

----------------------------------------------------------------
Aaradhana Sahu (1):
      wifi: ath12k: fix OF node refcount imbalance in WSI graph traversal

Amir Mohammad Jahangirzad (1):
      wifi: libertas: fix integer underflow in process_cmdrequest()

Baochen Qiang (2):
      wifi: ath12k: prepare REO update element only for primary link
      wifi: ath12k: fix peer_id usage in normal RX path

Bart Van Assche (1):
      wifi: cw1200: Revert "Fix locking in error paths"

Benjamin Berg (1):
      wifi: mac80211: use safe list iteration in radar detect work

Catherine (1):
      wifi: mac80211: drop stray 'static' from fast-RX rx_result

Dmitry Baryshkov (1):
      wifi: ath10k: snoc: select POWER_SEQUENCING

Jakov Novak (1):
      wifi: libertas: notify firmware load wait on disconnect

Jeongjun Park (1):
      wifi: rsi: fix kthread lifetime race between self-exit and external-stop

Jiri Slaby (SUSE) (1):
      wifi: ath5k: do not access array OOB

Johannes Berg (5):
      Merge tag 'ath-current-20260427' of git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath
      wifi: mac80211: tests: mark HT check strict
      Merge tag 'ath-current-20260505' of git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath
      wifi: mac80211: remove station if connection prep fails
      wifi: nl80211: fix NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST usage

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

Marek Szyprowski (1):
      wifi: brcmfmac: Fix potential use-after-free issue when stopping watchdog task

Michael Bommarito (2):
      wifi: nl80211: require admin perm on SET_PMK / DEL_PMK
      wifi: mac80211: check ieee80211_rx_data_set_link return in pubsta MLO path

Nicolas Escande (1):
      wifi: ath12k: fix leak in some ath12k_wmi_xxx() functions

Rameshkumar Sundaram (1):
      wifi: ath12k: initialize RSSI dBm conversion event state

Rio Liu (1):
      wifi: mac80211: skip ieee80211_verify_sta_ht_mcs_support check in non-strict mode

Tristan Madani (2):
      wifi: b43: enforce bounds check on firmware key index in b43_rx()
      wifi: b43legacy: enforce bounds check on firmware key index in RX path

Yu-Hsiang Tseng (1):
      wifi: ath12k: use lockdep_assert_in_rcu_read_lock() for RCU assertions

 drivers/net/wireless/ath/ath10k/Kconfig            |   1 +
 drivers/net/wireless/ath/ath12k/core.c             |  77 +++++++++------
 drivers/net/wireless/ath/ath12k/dp_rx.c            |   5 +-
 drivers/net/wireless/ath/ath12k/mac.c              |   2 +-
 drivers/net/wireless/ath/ath12k/p2p.c              |   2 +-
 drivers/net/wireless/ath/ath12k/wmi.c              | 105 +++++++++++++++++----
 drivers/net/wireless/ath/ath5k/base.c              |   3 +-
 drivers/net/wireless/broadcom/b43/xmit.c           |   3 +-
 drivers/net/wireless/broadcom/b43legacy/xmit.c     |   3 +-
 .../wireless/broadcom/brcm80211/brcmfmac/sdio.c    |   6 +-
 drivers/net/wireless/marvell/libertas/if_usb.c     |   6 +-
 drivers/net/wireless/rsi/rsi_common.h              |   5 +-
 drivers/net/wireless/st/cw1200/pm.c                |   2 -
 net/mac80211/mlme.c                                |  18 +++-
 net/mac80211/rx.c                                  |   6 +-
 net/mac80211/tests/chan-mode.c                     |   1 +
 net/mac80211/util.c                                |   4 +-
 net/wireless/nl80211.c                             |  27 ++++++
 net/wireless/pmsr.c                                |   2 +-
 19 files changed, 208 insertions(+), 70 deletions(-)

^ permalink raw reply

* Re: [PATCH v3 3/3] wifi: iwlwifi: mld: skip TX when firmware is dead
From: Minxi Hou @ 2026-05-06 10:39 UTC (permalink / raw)
  To: cole; +Cc: linux-wireless, greearb, miriam.rachel.korenblit, johannes,
	Minxi Hou
In-Reply-To: <20260420174406.128254-4-cole@unwrap.rs>

On Mon, 20 Apr 2026 10:44:06 -0700, Cole Leavitt wrote:
> When firmware encounters an error, STATUS_FW_ERROR is set but the
> mac80211 TX path continues pulling frames from TXQs. Each frame
> fails at iwl_trans_tx() which checks STATUS_FW_ERROR and returns
> -EIO, but iwl_mld_tx_from_txq() keeps looping over every queued
> frame. This burns CPU in a tight loop on dead firmware and can
> cause soft lockups during firmware error recovery.

I can confirm this on BE200 with a tproxy VPN daemon that keeps many
concurrent TCP flows open.  A firmware NMI causes 22-30 second
system-wide soft lockups affecting 11-34 CPUs.

Setup:
  Intel BE200 Wi-Fi 7 [8086:272b] rev 1a
  Firmware 101.6e695a70.0 (gl-c0-fm-c0-c101.ucode)
  Kernel 6.19.12-200.fc43.x86_64 (Fedora 43)
  Driver: iwlmld
  Workload: tproxy daemon (Go, ~50 goroutines doing TCP write())

Reproducer:
  echo 1 > /sys/kernel/debug/ieee80211/phy0/iwlwifi/iwlmld/fw_nmi

The call trace consistently shows goroutines spinning in:

  write() -> tcp_sendmsg -> __dev_queue_xmit -> ... ->
  iwl_mld_tx_from_txq -> iwl_mld_tx_skb

carrier stays 1 throughout the entire crash, so mac80211 never
stops queuing frames.  The lockup resolves only after iwlwifi
completes the firmware SW reset (~22-30s).  Firmware occasionally
crashes again within 15 seconds of restart, causing cascading
lockups.

This matches exactly what your patch addresses.

Regards,
Minxi Hou

^ permalink raw reply

* [PATCH wireless-next] wifi: mac80211_hwsim: claim HT STBC capability
From: Johannes Berg @ 2026-05-06  9:32 UTC (permalink / raw)
  To: linux-wireless; +Cc: Johannes Berg

From: Johannes Berg <johannes.berg@intel.com>

This is already claimed for VHT and HE, so it doesn't really
make sense to not claim it for HT, and this causes sigma-dut
failures since it assumes VHT support implies HT support.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
 drivers/net/wireless/virtual/mac80211_hwsim_main.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
index 3a0c4366dfdb..923196173efc 100644
--- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c
+++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c
@@ -5815,7 +5815,9 @@ static int mac80211_hwsim_new_radio(struct genl_info *info,
 					    IEEE80211_HT_CAP_GRN_FLD |
 					    IEEE80211_HT_CAP_SGI_20 |
 					    IEEE80211_HT_CAP_SGI_40 |
-					    IEEE80211_HT_CAP_DSSSCCK40;
+					    IEEE80211_HT_CAP_DSSSCCK40 |
+					    IEEE80211_HT_CAP_TX_STBC |
+					    IEEE80211_HT_CAP_RX_STBC;
 			sband->ht_cap.ampdu_factor = 0x3;
 			sband->ht_cap.ampdu_density = 0x6;
 			memset(&sband->ht_cap.mcs, 0,
-- 
2.53.0


^ permalink raw reply related

* RE: [PATCH 0/4] wifi: rtw89: advertise WFA-certified EHT capabilities for 8922a
From: Ping-Ke Shih @ 2026-05-06  9:12 UTC (permalink / raw)
  To: Louis Kotze; +Cc: linux-wireless@vger.kernel.org, linux-kernel@vger.kernel.org
In-Reply-To: <cover.1777832019.git.loukot@gmail.com>


Louis Kotze <loukot@gmail.com> wrote:
> This series advances rtw89 EHT capability advertisement to match what
> Realtek's own RTL8922AE silicon is certified for by the Wi-Fi Alliance
> in WFA Cert WFA129313 (RTL8922AE 802.11be and Bluetooth combo module,
> 2024-05-23, FW 6102.24.109.0,
> https://api.cert.wi-fi.org/api/certificate/download/public?variantId=129706).

Wi-Fi Component Operating System: Windows

That means hardware is capable, but Linux driver might need more
implementation, not just declaration. I will ask internal to check them.

But I think it is not necessary to add this link as reference.

[...]

> 
> Tested only on RTL8922AU (USB variant); rtw89_init_eht_cap() and the
> iftype-ext-cap struct are shared across the rtw89 chip family. Non-AU
> variant testing from anyone with rtw89-supported hardware is welcome.

I'd like to know if these patches have explicit improvement to you?
Or you only test these without regression?

As I know, at least TTLM needs to apply configuration to hardware and
firmware. Without necessary changes, it should not work properly.

Ping-Ke


^ permalink raw reply

* Re: [PATCH wireless-next v3 2/2] wifi: mac80211: set assoc_encrypted for EPP associations
From: Kavita Kavita @ 2026-05-06  8:58 UTC (permalink / raw)
  To: Johannes Berg; +Cc: linux-wireless, Jouni Malinen, ilan.peer
In-Reply-To: <0a53312265b6f466f01e169f0b385a0ef4d0b157.camel@sipsolutions.net>



On 5/4/2026 7:45 PM, Johannes Berg wrote:
> Hi,
> 
>> +	/*
>> +	 * If epp_peer set, unprotected (Re)Association Request/Response frames
>> +	 * are dropped, which ensures that the (re)association exchange is
>> +	 * encrypted over the air.
>> +	 */
>> +	sta = sta_info_get_bss(sdata, sdata->vif.cfg.ap_addr);
>> +	resp.assoc_encrypted = sta && sta->sta.epp_peer;
>>
> 
> Not related to this patch, but something I realised just now looking at
> this, coming from your earlier commit 63e7e3b6433f ("wifi: mac80211:
> allow key installation before association") ...
> 
> The code you added in that commit seems insufficient to me. As far as I
> can tell, it's possible to have assoc frame encryption with FT (see
> 802.11bi D4.0 "12.16.8.2 FT protocol"), but that doesn't explicitly
> specify that it can only be FT-over-the-air. If FT-over-the-DS is
> possible, then the code in mac80211 cannot support it, because the only
> way to get the sta->epp_peer flag set is via authentication (802.1X over
> auth frames or EPPKE), and the only way to install the TK before
> association is to *have* a station entry in the first place, and have it
> have the epp_peer flag already from authentication. It also sort of
> breaks down if the station entry is removed for some reason (rather than
> not being present in the first place) and from mac80211's POV we go to
> assoc immediately without having the station.
> 
> One way to fix it would be to add the TK to the ASSOCIATE command, but
> that would have to replicate a number of settings there, I'm not sure
> that's desirable.
> 
> Another way to fix it would be to have an NL80211_AUTHTYPE_FT_EPP or so,
> that just does all the processing, adds the AP's station entry and
> immediately moves it to authenticated while setting the epp_peer flag.
> That way, wpa_s could do this and then proceed to install the key and do
> association as it otherwise would.



Thank you for pointing this out. We agree with your points and will follow the
approach you suggested. We will take care of this in the future when we add support
for FT protocol with (Re)Association frame encryption support
("IEEE P802.11bi/D4.0, clause 12.16.8.2").
Currently, we have only implemented support for initial association
(EPPKE and IEEE 802.1X Authentication).



> 
> johannes


^ permalink raw reply

* RE: [PATCH] wifi: rtw88: add quirk to disable deep LPS for ASUS VivoBook X515JA
From: Ping-Ke Shih @ 2026-05-06  8:48 UTC (permalink / raw)
  To: Gabriel Maia, linux-wireless@vger.kernel.org
  Cc: kvalo@kernel.org, Gabriel Maia
In-Reply-To: <20260503164944.27114-1-gabriel_v_maia@estudante.sesisenai.org.br>

Gabriel Maia <gabrielvinicius.damaia@gmail.com> wrote:
> On ASUS VivoBook X515JA laptops equipped with a Realtek RTL8821CE
> 802.11ac PCIe adapter (PCI ID: 10ec:c821, subsystem: 1a3b:3040),
> the driver periodically emits the following messages:
> 
>   rtw88_8821ce 0000:01:00.0: firmware failed to leave lps state
>   rtw88_8821ce 0000:01:00.0: failed to send h2c command
> 
> The firmware fails to leave the Low Power State (LPS) in time,
> causing subsequent h2c commands to be dropped. Disable deep LPS
> to avoid this issue.

Just want to know that messages become absolutely silent with this patch?

> 
> Signed-off-by: Gabriel Maia <gabriel_v_maia@estudante.sesisenai.org.br>

WARNING: From:/Signed-off-by: email address mismatch: 
'From: Gabriel Maia <gabrielvinicius.damaia@gmail.com>' != 
'Signed-off-by: Gabriel Maia <gabriel_v_maia@estudante.sesisenai.org.br>'

> ---
>  drivers/net/wireless/realtek/rtw88/pci.c | 9 +++++++++
>  1 file changed, 9 insertions(+)
> 
> diff --git a/drivers/net/wireless/realtek/rtw88/pci.c b/drivers/net/wireless/realtek/rtw88/pci.c
> index bba370ad510c..b0eae0971ef4 100644
> --- a/drivers/net/wireless/realtek/rtw88/pci.c
> +++ b/drivers/net/wireless/realtek/rtw88/pci.c
> @@ -1770,6 +1770,15 @@ static const struct dmi_system_id rtw_pci_quirks[] = {
>                 .driver_data = (void *)(BIT(QUIRK_DIS_CAP_PCI_ASPM) |
>                                         BIT(QUIRK_DIS_CAP_LPS_DEEP)),
>         },
> +       {
> +               .callback = rtw_pci_disable_caps,
> +               .ident = "ASUS VivoBook X515JA",
> +               .matches = {
> +                       DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
> +                       DMI_MATCH(DMI_PRODUCT_NAME, "VivoBook_ASUSLaptop X515JA_X515JA"),
> +               },
> +               .driver_data = (void *)BIT(QUIRK_DIS_CAP_LPS_DEEP),
> +       },
>         {}
>  };
> 
> --
> 2.53.0


^ permalink raw reply

* RE: [PATCH] wifi: rtw88: remove rtw_txq_dequeue
From: Ping-Ke Shih @ 2026-05-06  8:44 UTC (permalink / raw)
  To: Martin Kaiser
  Cc: Bitterblue Smith, linux-wireless@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <20260502131641.141136-1-martin@kaiser.cx>

Martin Kaiser <martin@kaiser.cx> wrote:
> Remove the rtw_txq_dequeue helper. It's a wrapper around
> ieee80211_tx_dequeue with just one caller.
> 
> Call ieee80211_tx_dequeue directly in rtw_txq_push. There's no need to
> fetch txq for every frame, we can do this once outside of the rcu lock.
> 
> Make the loop variable i unsigned long, it should have the same type as
> frames.
> 
> Signed-off-by: Martin Kaiser <martin@kaiser.cx>
> ---
>  drivers/net/wireless/realtek/rtw88/tx.c | 18 +++---------------
>  1 file changed, 3 insertions(+), 15 deletions(-)
> 
> diff --git a/drivers/net/wireless/realtek/rtw88/tx.c b/drivers/net/wireless/realtek/rtw88/tx.c
> index 3106edb84fb4..ea2dc9bda1b6 100644
> --- a/drivers/net/wireless/realtek/rtw88/tx.c
> +++ b/drivers/net/wireless/realtek/rtw88/tx.c
> @@ -619,31 +619,19 @@ static int rtw_txq_push_skb(struct rtw_dev *rtwdev,
>         return 0;
>  }
> 
> -static struct sk_buff *rtw_txq_dequeue(struct rtw_dev *rtwdev,
> -                                      struct rtw_txq *rtwtxq)
> -{
> -       struct ieee80211_txq *txq = rtwtxq_to_txq(rtwtxq);
> -       struct sk_buff *skb;
> -
> -       skb = ieee80211_tx_dequeue(rtwdev->hw, txq);
> -       if (!skb)
> -               return NULL;
> -
> -       return skb;
> -}
> -
>  static void rtw_txq_push(struct rtw_dev *rtwdev,
>                          struct rtw_txq *rtwtxq,
>                          unsigned long frames)
>  {
> +       struct ieee80211_txq *txq = rtwtxq_to_txq(rtwtxq);
>         struct sk_buff *skb;
>         int ret;
> -       int i;
> +       unsigned long i;

In reverse X'mas order.

> 
>         rcu_read_lock();
> 
>         for (i = 0; i < frames; i++) {
> -               skb = rtw_txq_dequeue(rtwdev, rtwtxq);
> +               skb = ieee80211_tx_dequeue(rtwdev->hw, txq);
>                 if (!skb)
>                         break;
> 
> --
> 2.43.7
> 


^ 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