From 538be9fb561a8bb47e60405f16e29b5fd45f190b Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 15 Sep 2026 05:51:17 +0000 Subject: [PATCH 1/2] FROMLIST: wifi: ath12k: set STA EHT MU mode before peer assoc Currently, ath12k_bss_assoc() sets HE MU mode with WMI_VDEV_PARAM_SET_HEMU_MODE before ath12k_wmi_send_peer_assoc_cmd(), but has no equivalent step for EHT. This means firmware can process peer assoc without EHT beamformee/OFDMA mode state matching the current peer and bandwidth, which can leave incorrect beamforming/OFDMA state for 11be links. Fix this by adding ath12k_mac_vif_recalc_sta_eht_txbf() to compute the EHT MU mode bitmask from local and peer EHT capabilities and link bandwidth, and sending WMI_VDEV_PARAM_SET_EHT_MU_MODE before peer assoc, mirroring the existing HE flow. Also send WMI_VDEV_PARAM_SET_EHT_MU_MODE with zero for non-EHT associations to clear stale EHT mode left from a previous association on the same vdev. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Link: https://lore.kernel.org/all/20260915055117.546723-1-hangtian.zhu@oss.qualcomm.com Signed-off-by: Hangtian Zhu --- drivers/net/wireless/ath/ath12k/mac.c | 91 +++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 874bd3fa5263f..833e467ff2b1d 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3773,6 +3773,79 @@ static int ath12k_mac_vif_recalc_sta_he_txbf(struct ath12k *ar, return 0; } +static int ath12k_mac_vif_recalc_sta_eht_txbf(struct ath12k *ar, + struct ath12k_link_vif *arvif, + struct ieee80211_link_sta *link_sta, + u32 *ehtmode) +{ + const struct ieee80211_sta_eht_cap *peer_eht_cap; + const struct ieee80211_sta_eht_cap *own_eht_cap; + const struct ieee80211_sband_iftype_data *iftd; + struct ieee80211_vif *vif = arvif->ahvif->vif; + struct ieee80211_bss_conf *link_conf; + struct cfg80211_chan_def def; + enum nl80211_band band; + u8 mu_bformer; + + *ehtmode = 0; + + if (vif->type != NL80211_IFTYPE_STATION) + return -EINVAL; + + link_conf = ath12k_mac_get_link_bss_conf(arvif); + if (!link_conf) { + ath12k_warn(ar->ab, "unable to access bss link conf in recalc eht txbf conf\n"); + return -EINVAL; + } + + if (!link_conf->eht_support || !link_sta->eht_cap.has_eht) + return 0; + + if (WARN_ON(ath12k_mac_vif_link_chan(vif, arvif->link_id, &def))) + return -EINVAL; + + band = def.chan->band; + iftd = ieee80211_get_sband_iftype_data(&ar->mac.sbands[band], vif->type); + if (!iftd) { + ath12k_dbg(ar->ab, ATH12K_DBG_MAC, + "unable to access iftype data in recalc eht txbf conf\n"); + return 0; + } + + own_eht_cap = &iftd->eht_cap; + peer_eht_cap = &link_sta->eht_cap; + + if (!own_eht_cap->has_eht) + return 0; + + *ehtmode |= u32_encode_bits(EHT_DL_MUOFDMA_ENABLE, EHT_MODE_DL_OFDMA) | + u32_encode_bits(EHT_UL_MUOFDMA_ENABLE, EHT_MODE_UL_OFDMA); + + if ((own_eht_cap->eht_cap_elem.phy_cap_info[0] & + IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE) && + (peer_eht_cap->eht_cap_elem.phy_cap_info[0] & + IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER)) + *ehtmode |= u32_encode_bits(EHT_SU_BFEE_ENABLE, EHT_MODE_SU_TX_BFEE); + + switch (link_sta->bandwidth) { + case IEEE80211_STA_RX_BW_320: + mu_bformer = IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ; + break; + case IEEE80211_STA_RX_BW_160: + mu_bformer = IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_160MHZ; + break; + default: + mu_bformer = IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_80MHZ; + break; + } + + if (u32_get_bits(*ehtmode, EHT_MODE_SU_TX_BFEE) && + (peer_eht_cap->eht_cap_elem.phy_cap_info[7] & mu_bformer)) + *ehtmode |= u32_encode_bits(EHT_MU_BFEE_ENABLE, EHT_MODE_MU_TX_BFEE); + + return 0; +} + static int ath12k_mac_set_eht_txbf_conf(struct ath12k_link_vif *arvif) { struct ath12k_vif *ahvif = arvif->ahvif; @@ -3916,6 +3989,7 @@ static void ath12k_bss_assoc(struct ath12k *ar, struct ath12k_sta *ahsta; struct ath12k_dp_link_peer *peer; bool is_auth = false; + u32 ehtmode = 0; u32 hemode = 0; int ret; struct ath12k_dp *dp = ath12k_ab_to_dp(ar->ab); @@ -3970,6 +4044,14 @@ static void ath12k_bss_assoc(struct ath12k *ar, return; } + ret = ath12k_mac_vif_recalc_sta_eht_txbf(ar, arvif, link_sta, &ehtmode); + if (ret) { + ath12k_warn(ar->ab, "failed to recalc eht txbf for vdev %i on bss %pM: %d\n", + arvif->vdev_id, bss_conf->bssid, ret); + rcu_read_unlock(); + return; + } + rcu_read_unlock(); /* keep this before ath12k_wmi_send_peer_assoc_cmd() */ @@ -3981,6 +4063,15 @@ static void ath12k_bss_assoc(struct ath12k *ar, return; } + ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id, + WMI_VDEV_PARAM_SET_EHT_MU_MODE, + ehtmode); + if (ret) { + ath12k_warn(ar->ab, "failed to submit vdev param eht txbf 0x%x: %d\n", + ehtmode, ret); + return; + } + peer_arg->is_assoc = true; ret = ath12k_mac_peer_assoc(ar, peer_arg); From 2e8f5665523717c665e9cf1710ab8c8437a0e5d5 Mon Sep 17 00:00:00 2001 From: Hangtian Zhu Date: Tue, 20 Oct 2026 06:37:19 +0000 Subject: [PATCH 2/2] FROMLIST: wifi: ath12k: keep ML mgmt tx on active link Currently ath12k_mac_get_tx_link() may select an inactive link for ML management frame transmission. This happens when mac80211 passes IEEE80211_LINK_UNSPECIFIED and the resolved deflink is inactive. As a result, ML management frames such as deauthentication are sent on an inactive link and Tx fails (the frame gets dropped). Fix this by reselecting the Tx link only when the requested link is IEEE80211_LINK_UNSPECIFIED and the resolved deflink is inactive. Start from vif->active_links as the candidate set and prefer links that are both active on the vif and valid for the peer (sta->valid_links). If that intersection is empty, fall back to vif->active_links. Use __ffs() to pick deterministically from the candidate set. If mac80211 explicitly requests a link, keep that link and do not remap management Tx to another link. This change is limited to ML management frames. Data frames, HW-encap frames, and non-MLO paths are unchanged. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Link: https://lore.kernel.org/all/20261020063719.3486519-1-hangtian.zhu@oss.qualcomm.com Signed-off-by: Hangtian Zhu --- drivers/net/wireless/ath/ath12k/mac.c | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 833e467ff2b1d..4b45a9599dd4d 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -9694,6 +9694,7 @@ u8 ath12k_mac_get_tx_link(struct ieee80211_sta *sta, struct ieee80211_vif *vif, struct ieee80211_link_sta *link_sta; struct ieee80211_bss_conf *bss_conf; struct ath12k_sta *ahsta; + bool req_unspecified_link = (link == IEEE80211_LINK_UNSPECIFIED); /* Use the link id passed or the default vif link */ if (!sta) { @@ -9739,6 +9740,33 @@ u8 ath12k_mac_get_tx_link(struct ieee80211_sta *sta, struct ieee80211_vif *vif, if (!ieee80211_is_mgmt(hdr->frame_control)) return link; + /* + * Keep ML management Tx on an active link only when mac80211 did + * not request a specific link and the selected link is inactive. + */ + if (req_unspecified_link && !(vif->active_links & BIT(link))) { + unsigned long tx_links = vif->active_links; + unsigned long peer_tx_links; + + if (!tx_links) + goto out_keep_link; + + peer_tx_links = tx_links & sta->valid_links; + + /* + * Prefer links that are both active on the vif and valid for + * this MLO peer. If no overlap exists, fall back to vif + * active links. + */ + if (peer_tx_links) + tx_links = peer_tx_links; + else + WARN_ON_ONCE(1); + + link = __ffs(tx_links); + } + +out_keep_link: /* Perform address conversion for ML STA Tx */ bss_conf = rcu_dereference(vif->link_conf[link]); link_sta = rcu_dereference(sta->link[link]);