Linux wireless drivers development
 help / color / mirror / Atom feed
* [PATCH 1/2] [PATCH 1/2] wifi: mt76: mt7925: add IE-parsing helpers for AP NSTS extraction for MT7928
@ 2026-08-04  6:24 JB Tsai
  2026-08-04  6:24 ` [PATCH 2/2] [PATCH 2/2] wifi: mt76: mt7925: add STA_REC_SUSWM TLV for SU beamforming capability " JB Tsai
  0 siblings, 1 reply; 2+ messages in thread
From: JB Tsai @ 2026-08-04  6:24 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	Shengwei.Lu, litien.chang, jb.tsai, Shengwei Lu, Sean Wang

From: Shengwei Lu <shengwei.lu@mediatek.com>

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 <shengwei.lu@mediatek.com>
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
 .../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


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

* [PATCH 2/2] [PATCH 2/2] wifi: mt76: mt7925: add STA_REC_SUSWM TLV for SU beamforming capability for MT7928
  2026-08-04  6:24 [PATCH 1/2] [PATCH 1/2] wifi: mt76: mt7925: add IE-parsing helpers for AP NSTS extraction for MT7928 JB Tsai
@ 2026-08-04  6:24 ` JB Tsai
  0 siblings, 0 replies; 2+ messages in thread
From: JB Tsai @ 2026-08-04  6:24 UTC (permalink / raw)
  To: nbd, lorenzo
  Cc: linux-wireless, linux-mediatek, Sean.Wang, Quan.Zhou, Ryder.Lee,
	Shengwei.Lu, litien.chang, jb.tsai, Shengwei Lu, Sean Wang

From: Shengwei Lu <shengwei.lu@mediatek.com>

MT7928 firmware requires a STA_REC_SUSWM TLV to convey SU beamforming
and spatial stream capabilities when associating with a BSS. Add:

 - sta_rec_su_swm struct with a capability bitmap (tag 0x1A)
 - STA_REC_SUSWM enum entry in the STA record tag list
 - mt7928_mcu_sta_suswm_tlv() that resolves the peer link_sta and own
   sband, then calls mt7928_get_bss_suswm_cap() to obtain the SU BF
   bfer/bfee/nsts summary and fills the TLV
 - Call site in mt7925_mcu_sta_cmd() guarded by is_mt7928()

The capability bitmap bit layout passed to firmware:
  bit 0 - AP is SU beamformer capable
  bit 1 - local device is SU beamformee capable
  bit 2 - AP NSTS <= 2 (within supported spatial-stream limit)

Signed-off-by: Shengwei Lu <shengwei.lu@mediatek.com>
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
---
 .../wireless/mediatek/mt76/mt76_connac_mcu.h  |  1 +
 .../net/wireless/mediatek/mt76/mt7925/mcu.c   | 67 +++++++++++++++++++
 .../net/wireless/mediatek/mt76/mt7925/mcu.h   |  8 +++
 3 files changed, 76 insertions(+)

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 51380849d24e..b2c79d65525a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -857,6 +857,7 @@ enum {
 	STA_REC_PHY = 0x15,
 	STA_REC_HE_6G = 0x17,
 	STA_REC_HE_V2 = 0x19,
+	STA_REC_SUSWM = 0x1A,
 	STA_REC_MLD = 0x20,
 	STA_REC_EHT_MLD = 0x21,
 	STA_REC_EHT = 0x22,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 983a9eacd8c7..e94742c8c370 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -8,6 +8,7 @@
 #include "mcu.h"
 #include "mac.h"
 #include "nan.h"
+#include "ies.h"
 
 #define MT_STA_BFER			BIT(0)
 #define MT_STA_BFEE			BIT(1)
@@ -2110,6 +2111,70 @@ mt7925_mcu_sta_mld_tlv(struct sk_buff *skb,
 	mld->link_num = cnt;
 }
 
+static void
+mt7928_mcu_sta_suswm_tlv(struct sk_buff *skb,
+			 struct ieee80211_vif *vif, struct mt76_wcid *wcid)
+{
+	struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv;
+	struct mt792x_phy *phy = mvif->phy;
+	struct ieee80211_link_sta *link_sta;
+	struct ieee80211_bss_conf *link_conf;
+	struct cfg80211_chan_def *chandef;
+	struct mt792x_bss_conf *mconf;
+	enum nl80211_band band;
+	struct ieee80211_sta *sta;
+	struct ieee80211_supported_band *sband;
+	const struct cfg80211_bss_ies *ies;
+	struct cfg80211_bss *bss;
+	struct mt7928_suswm_cap cap = {};
+	struct sta_rec_su_swm *su_swm;
+	struct tlv *tlv;
+
+	if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc)
+		return;
+
+	rcu_read_lock();
+	sta = ieee80211_find_sta(vif, vif->bss_conf.bssid);
+
+	if (!sta)
+		goto exit;
+
+	link_sta = mt792x_sta_to_link_sta(vif, sta, wcid->link_id);
+
+	if (!link_sta)
+		goto exit;
+
+	link_conf = mt792x_vif_to_bss_conf(vif, link_sta->link_id);
+	mconf = mt792x_vif_to_link(mvif, link_sta->link_id);
+
+	chandef = mconf->mt76.ctx ? &mconf->mt76.ctx->def :
+				    &link_conf->chanreq.oper;
+	band = chandef->chan->band;
+
+	sband = phy->mt76->hw->wiphy->bands[band];
+
+	bss = link_conf->bss;
+	if (!bss || !bss->ies)
+		goto exit;
+
+	ies = rcu_dereference(bss->ies);
+
+	mt7928_get_bss_suswm_cap(ies, link_sta, sband, vif->type, &cap);
+
+	tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_SUSWM, sizeof(*su_swm));
+	su_swm = (struct sta_rec_su_swm *)tlv;
+
+	if (cap.bfer_en)
+		su_swm->su_swm_cap_map |= MT_SU_SWM_BFER;
+	if (cap.bfee_en)
+		su_swm->su_swm_cap_map |= MT_SU_SWM_BFEE;
+	if (cap.nsts > 0 && cap.nsts <= 2)
+		su_swm->su_swm_cap_map |= MT_SU_SWM_NSTS_MEET;
+
+exit:
+	rcu_read_unlock();
+}
+
 static void
 mt7925_mcu_sta_remove_tlv(struct sk_buff *skb)
 {
@@ -2179,6 +2244,8 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy,
 			mlink = &mvif->sta.deflink;
 
 		mt7925_mcu_sta_hdr_trans_tlv(skb, info->vif, mlink);
+		if (is_mt7928(dev))
+			mt7928_mcu_sta_suswm_tlv(skb, info->vif, info->wcid);
 	}
 
 	return mt76_mcu_skb_send_msg(dev, skb, info->cmd, true);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
index 11f9eac13ffc..f8696ce36413 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
@@ -493,6 +493,13 @@ struct sta_rec_eht_mld {
 	u8 __rsv2[3];
 } __packed;
 
+struct sta_rec_su_swm {
+	__le16 tag;
+	__le16 len;
+	u8   su_swm_cap_map;
+	u8 rsv[3];
+} __packed;
+
 struct bss_ifs_time_tlv {
 	__le16 tag;
 	__le16 len;
@@ -542,6 +549,7 @@ struct bss_rlm_tlv {
 					 sizeof(struct sta_rec_eht) +		\
 					 sizeof(struct sta_rec_hdr_trans) +	\
 					 sizeof(struct sta_rec_mld) +		\
+					 sizeof(struct sta_rec_su_swm) +		\
 					 sizeof(struct tlv) * 2 +		\
 					 sizeof(struct sta_rec_remove))
 
-- 
2.45.2


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

end of thread, other threads:[~2026-08-04  6:24 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-04  6:24 [PATCH 1/2] [PATCH 1/2] wifi: mt76: mt7925: add IE-parsing helpers for AP NSTS extraction for MT7928 JB Tsai
2026-08-04  6:24 ` [PATCH 2/2] [PATCH 2/2] wifi: mt76: mt7925: add STA_REC_SUSWM TLV for SU beamforming capability " JB Tsai

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