* [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