[PATCH 1/2] [PATCH 1/2] wifi: mt76: mt7925: add IE-parsing helpers for AP NSTS extraction for MT7928

JB Tsai <[email protected]> Tue, 4 Aug 2026 14:24:02 +0800
Newsgroups org.kernel.vger.linux-wireless,org.infradead.lists.linux-mediatek
Message-ID <[email protected]>
From: Shengwei Lu <[email protected]>

Add a dedicated module (ies.c / ies.h) that provides four helpers to
parse BSS IEs and extract the AP's maximum TX spatial-stream count
(NSTS):

 - mt7928_ht_nsts_from_ies()  - reads HT Capability TX MCS streams
 - mt7928_vht_nsts_from_ies() - reads VHT Capability TX MCS map
 - mt7928_he_nsts_from_ies()  - reads HE Capability TX MCS/NSS for 80 MHz
 - mt7928_eht_nsts_from_ies() - reads EHT Capability TX MCS/NSS

Also add mt7928_get_bss_suswm_cap() which walks the HT/VHT/HE/EHT caps
of both the AP (peer link_sta) and the local device (own sband) and
fills a struct mt7928_suswm_cap {bfer_en, bfee_en, nsts} summarising
the SU beamforming capabilities and the AP's NSTS count. The
MT_SU_SWM_{BFER,BFEE,NSTS_MEET} bitmap defines used by the STA_REC_SUSWM
TLV are also introduced here.

These helpers will be consumed by the STA_REC_SUSWM TLV builder to
determine whether the AP's NSTS count falls within the device's
supported spatial-stream limit.

Signed-off-by: Shengwei Lu <[email protected]>
Co-developed-by: Sean Wang <[email protected]>
Signed-off-by: Sean Wang <[email protected]>
---
 .../wireless/mediatek/mt76/mt7925/Makefile    |   2 +-
 .../net/wireless/mediatek/mt76/mt7925/ies.c   | 183 ++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/ies.h   |  31 +++
 3 files changed, 215 insertions(+), 1 deletion(-)
 create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/ies.c
 create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/ies.h

diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index f9dcc0bba393..af62c775c3d6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o
 obj-$(CONFIG_MT7925E) += mt7925e.o
 obj-$(CONFIG_MT7925U) += mt7925u.o
 
-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o ies.o
 mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
 mt7925e-y := pci.o pci_mac.o pci_mcu.o
 mt7925u-y := usb.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/ies.c b/drivers/net/wireless/mediatek/mt76/mt7925/ies.c
new file mode 100644
index 000000000000..0eb7dc7665b3
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/ies.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#include <linux/ieee80211.h>
+#include <net/cfg80211.h>
+#include <net/mac80211.h>
+#include "ies.h"
+
+u8 mt7928_ht_nsts_from_ies(const struct cfg80211_bss_ies *ies)
+{
+	const struct ieee80211_ht_cap *ht_cap;
+	const struct element *elem;
+	u8 nsts = 0, i, max_tx_streams;
+
+	elem = cfg80211_find_elem(WLAN_EID_HT_CAPABILITY,
+				  ies->data, ies->len);
+	if (!elem || elem->datalen < sizeof(*ht_cap))
+		return 0;
+
+	ht_cap = (const void *)elem->data;
+
+	if ((ht_cap->mcs.tx_params & IEEE80211_HT_MCS_TX_RX_DIFF) &&
+	    (ht_cap->mcs.tx_params & IEEE80211_HT_MCS_TX_DEFINED))
+		max_tx_streams =
+			((ht_cap->mcs.tx_params & IEEE80211_HT_MCS_TX_MAX_STREAMS_MASK)
+				>> IEEE80211_HT_MCS_TX_MAX_STREAMS_SHIFT) + 1;
+	else
+		max_tx_streams = IEEE80211_HT_MCS_TX_MAX_STREAMS;
+
+	for (i = 0; i < max_tx_streams; i++) {
+		if (ht_cap->mcs.rx_mask[i])
+			nsts = i + 1;
+	}
+
+	return nsts;
+}
+
+u8 mt7928_vht_nsts_from_ies(const struct cfg80211_bss_ies *ies)
+{
+	const struct ieee80211_vht_cap *vht_cap;
+	const struct element *elem;
+	u16 tx_mcs_map;
+	u8 nsts = 0, i;
+
+	elem = cfg80211_find_elem(WLAN_EID_VHT_CAPABILITY,
+				  ies->data, ies->len);
+	if (!elem || elem->datalen < sizeof(*vht_cap))
+		return 0;
+
+	vht_cap = (const void *)elem->data;
+	tx_mcs_map = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map);
+
+	for (i = 0; i < 8; i++) {
+		if (((tx_mcs_map >> (2 * i)) & 0x3) != IEEE80211_VHT_MCS_NOT_SUPPORTED)
+			nsts = i + 1;
+	}
+
+	return nsts;
+}
+
+u8 mt7928_he_nsts_from_ies(const struct cfg80211_bss_ies *ies)
+{
+	const struct ieee80211_he_cap_elem *he_cap;
+	const struct ieee80211_he_mcs_nss_supp *mcs_nss;
+	const struct element *elem;
+	u8 mcs_nss_size;
+	u16 tx_mcs_80;
+	u8 nsts = 0, i;
+
+	elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY,
+				      ies->data, ies->len);
+
+	if (!elem || elem->datalen < 1 + sizeof(*he_cap))
+		return 0;
+
+	he_cap = (const void *)(elem->data + 1);
+	mcs_nss_size = ieee80211_he_mcs_nss_size(he_cap);
+
+	if (elem->datalen < 1 + sizeof(*he_cap) + mcs_nss_size)
+		return 0;
+
+	mcs_nss = (const void *)(he_cap + 1);
+	tx_mcs_80 = le16_to_cpu(mcs_nss->tx_mcs_80);
+
+	for (i = 0; i < 8; i++) {
+		if (((tx_mcs_80 >> (2 * i)) & 0x3) != IEEE80211_HE_MCS_NOT_SUPPORTED)
+			nsts = i + 1;
+	}
+
+	return nsts;
+}
+
+u8 mt7928_eht_nsts_from_ies(const struct cfg80211_bss_ies *ies)
+{
+	const struct ieee80211_eht_cap_elem_fixed *eht_cap;
+	const struct ieee80211_he_cap_elem *he_cap;
+	const struct element *eht_elem, *he_elem;
+	const u8 *mcs_nss_data;
+	u8 mcs_nss_size;
+	u8 nsts = 0, i;
+
+	eht_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY,
+					  ies->data, ies->len);
+	if (!eht_elem || eht_elem->datalen < 1 + sizeof(*eht_cap))
+		return 0;
+
+	he_elem = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY,
+					 ies->data, ies->len);
+	if (!he_elem || he_elem->datalen < 1 + sizeof(*he_cap))
+		return 0;
+
+	eht_cap = (const void *)(eht_elem->data + 1);
+	he_cap = (const void *)(he_elem->data + 1);
+
+	mcs_nss_size = ieee80211_eht_mcs_nss_size(he_cap, eht_cap, true);
+	if (eht_elem->datalen < 1 + sizeof(*eht_cap) + mcs_nss_size)
+		return 0;
+
+	mcs_nss_data = (const u8 *)(eht_cap + 1);
+
+	for (i = 0; i < mcs_nss_size; i++)
+		nsts = (mcs_nss_data[i] >> 4) & 0xf;
+
+	return nsts;
+}
+
+void mt7928_get_bss_suswm_cap(const struct cfg80211_bss_ies *ies,
+			      const struct ieee80211_link_sta *link_sta,
+			      const struct ieee80211_supported_band *own_sband,
+			      enum nl80211_iftype iftype,
+			      struct mt7928_suswm_cap *cap)
+{
+	const struct ieee80211_sta_ht_cap *ht_cap = &link_sta->ht_cap;
+	const struct ieee80211_sta_vht_cap *vht_cap = &link_sta->vht_cap;
+	const struct ieee80211_sta_he_cap *he_cap = &link_sta->he_cap;
+	const struct ieee80211_sta_eht_cap *eht_cap = &link_sta->eht_cap;
+	const struct ieee80211_sta_vht_cap *own_vht_cap = &own_sband->vht_cap;
+	const struct ieee80211_sta_he_cap *own_he_cap =
+		ieee80211_get_he_iftype_cap(own_sband, iftype);
+	const struct ieee80211_sta_eht_cap *own_eht_cap =
+		ieee80211_get_eht_iftype_cap(own_sband, iftype);
+
+	cap->bfer_en = false;
+	cap->bfee_en = false;
+	cap->nsts = 0;
+
+	if (ht_cap->ht_supported)
+		cap->nsts = mt7928_ht_nsts_from_ies(ies);
+
+	if (vht_cap->vht_supported) {
+		if (vht_cap->cap & IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)
+			cap->bfer_en = true;
+
+		if (own_vht_cap->cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE)
+			cap->bfee_en = true;
+
+		cap->nsts = mt7928_vht_nsts_from_ies(ies);
+	}
+
+	if (he_cap->has_he && own_he_cap) {
+		if (he_cap->he_cap_elem.phy_cap_info[3] &
+		    IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER)
+			cap->bfer_en = true;
+
+		if (own_he_cap->he_cap_elem.phy_cap_info[4] &
+		    IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE)
+			cap->bfee_en = true;
+
+		cap->nsts = mt7928_he_nsts_from_ies(ies);
+	}
+
+	if (eht_cap->has_eht && own_eht_cap) {
+		if (eht_cap->eht_cap_elem.phy_cap_info[0] &
+		    IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER)
+			cap->bfer_en = true;
+
+		if (own_eht_cap->eht_cap_elem.phy_cap_info[0] &
+		    IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE)
+			cap->bfee_en = true;
+
+		cap->nsts = mt7928_eht_nsts_from_ies(ies);
+	}
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/ies.h b/drivers/net/wireless/mediatek/mt76/mt7925/ies.h
new file mode 100644
index 000000000000..a1239f22ef54
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/ies.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#ifndef __MT7925_IES_H
+#define __MT7925_IES_H
+
+#include <net/cfg80211.h>
+#include <net/mac80211.h>
+
+#define MT_SU_SWM_BFER		BIT(0)
+#define MT_SU_SWM_BFEE		BIT(1)
+#define MT_SU_SWM_NSTS_MEET	BIT(2)
+
+struct mt7928_suswm_cap {
+	bool bfee_en;
+	bool bfer_en;
+	u8 nsts;
+};
+
+u8 mt7928_ht_nsts_from_ies(const struct cfg80211_bss_ies *ies);
+u8 mt7928_vht_nsts_from_ies(const struct cfg80211_bss_ies *ies);
+u8 mt7928_he_nsts_from_ies(const struct cfg80211_bss_ies *ies);
+u8 mt7928_eht_nsts_from_ies(const struct cfg80211_bss_ies *ies);
+
+void mt7928_get_bss_suswm_cap(const struct cfg80211_bss_ies *ies,
+			      const struct ieee80211_link_sta *link_sta,
+			      const struct ieee80211_supported_band *own_sband,
+			      enum nl80211_iftype iftype,
+			      struct mt7928_suswm_cap *cap);
+
+#endif /* __MT7925_IES_H */
-- 
2.45.2