]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: ath12k: Add support to enqueue management frame at MLD level
authorSriram R <quic_srirrama@quicinc.com>
Fri, 11 Jul 2025 09:17:04 +0000 (14:47 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Mon, 14 Jul 2025 14:32:16 +0000 (07:32 -0700)
A multi-link client can use any link for transmissions. It can decide to
put one link in power save mode for longer periods while listening on the
other links as per MLD listen interval. Unicast management frames sent to
that link station might get dropped if that link station is in power save
mode or inactive. In such cases, firmware can take decision on which link
to use.

Allow the firmware to decide on which link management frame should be
sent on, by filling the hardware link with maximum value of u32, so that
the firmware will not have a specific link to transmit data on and so
the management frames will be link agnostic. For QCN devices, all action
frames are marked as link agnostic. For WCN devices, if the device is
configured as an AP, then all frames other than probe response frames,
authentication frames, association response frames, re-association response
frames and ADDBA response frames are marked as link agnostic and if the
device is configured as a station, then all frames other than probe request
frames, authentication frames, de-authentication frames and ADDBA response
frames are marked as link agnostic.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.4.1-00199-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3

Signed-off-by: Sriram R <quic_srirrama@quicinc.com>
Co-developed-by: Roopni Devanathan <quic_rdevanat@quicinc.com>
Signed-off-by: Roopni Devanathan <quic_rdevanat@quicinc.com>
Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>
Link: https://patch.msgid.link/20250711091704.3704379-1-quic_rdevanat@quicinc.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath12k/core.h
drivers/net/wireless/ath/ath12k/hw.c
drivers/net/wireless/ath/ath12k/hw.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/peer.c
drivers/net/wireless/ath/ath12k/peer.h
drivers/net/wireless/ath/ath12k/wmi.c
drivers/net/wireless/ath/ath12k/wmi.h

index 3e55b7b89eaed2ce835f772162f956d31247861c..519f826f56c8ebb871997777261dbd1f2e5482de 100644 (file)
@@ -116,6 +116,7 @@ static inline u64 ath12k_le32hilo_to_u64(__le32 hi, __le32 lo)
 enum ath12k_skb_flags {
        ATH12K_SKB_HW_80211_ENCAP = BIT(0),
        ATH12K_SKB_CIPHER_SET = BIT(1),
+       ATH12K_SKB_MLO_STA = BIT(2),
 };
 
 struct ath12k_skb_cb {
index ec77ad498b33a20bc92e30e52257be70cc406d36..6791ae1d64e50feb96df9add0d8b5f2f76b425e4 100644 (file)
@@ -14,6 +14,7 @@
 #include "hw.h"
 #include "mhi.h"
 #include "dp_rx.h"
+#include "peer.h"
 
 static const guid_t wcn7850_uuid = GUID_INIT(0xf634f534, 0x6147, 0x11ec,
                                             0x90, 0xd6, 0x02, 0x42,
@@ -49,6 +50,12 @@ static bool ath12k_dp_srng_is_comp_ring_qcn9274(int ring_num)
        return false;
 }
 
+static bool ath12k_is_frame_link_agnostic_qcn9274(struct ath12k_link_vif *arvif,
+                                                 struct ieee80211_mgmt *mgmt)
+{
+       return ieee80211_is_action(mgmt->frame_control);
+}
+
 static int ath12k_hw_mac_id_to_pdev_id_wcn7850(const struct ath12k_hw_params *hw,
                                               int mac_id)
 {
@@ -74,6 +81,52 @@ static bool ath12k_dp_srng_is_comp_ring_wcn7850(int ring_num)
        return false;
 }
 
+static bool ath12k_is_addba_resp_action_code(struct ieee80211_mgmt *mgmt)
+{
+       if (!ieee80211_is_action(mgmt->frame_control))
+               return false;
+
+       if (mgmt->u.action.category != WLAN_CATEGORY_BACK)
+               return false;
+
+       if (mgmt->u.action.u.addba_resp.action_code != WLAN_ACTION_ADDBA_RESP)
+               return false;
+
+       return true;
+}
+
+static bool ath12k_is_frame_link_agnostic_wcn7850(struct ath12k_link_vif *arvif,
+                                                 struct ieee80211_mgmt *mgmt)
+{
+       struct ieee80211_vif *vif = ath12k_ahvif_to_vif(arvif->ahvif);
+       struct ath12k_hw *ah = ath12k_ar_to_ah(arvif->ar);
+       struct ath12k_base *ab = arvif->ar->ab;
+       __le16 fc = mgmt->frame_control;
+
+       spin_lock_bh(&ab->base_lock);
+       if (!ath12k_peer_find_by_addr(ab, mgmt->da) &&
+           !ath12k_peer_ml_find(ah, mgmt->da)) {
+               spin_unlock_bh(&ab->base_lock);
+               return false;
+       }
+       spin_unlock_bh(&ab->base_lock);
+
+       if (vif->type == NL80211_IFTYPE_STATION)
+               return arvif->is_up &&
+                      (vif->valid_links == vif->active_links) &&
+                      !ieee80211_is_probe_req(fc) &&
+                      !ieee80211_is_auth(fc) &&
+                      !ieee80211_is_deauth(fc) &&
+                      !ath12k_is_addba_resp_action_code(mgmt);
+
+       if (vif->type == NL80211_IFTYPE_AP)
+               return !(ieee80211_is_probe_resp(fc) || ieee80211_is_auth(fc) ||
+                        ieee80211_is_assoc_resp(fc) || ieee80211_is_reassoc_resp(fc) ||
+                        ath12k_is_addba_resp_action_code(mgmt));
+
+       return false;
+}
+
 static const struct ath12k_hw_ops qcn9274_ops = {
        .get_hw_mac_from_pdev_id = ath12k_hw_qcn9274_mac_from_pdev_id,
        .mac_id_to_pdev_id = ath12k_hw_mac_id_to_pdev_id_qcn9274,
@@ -81,6 +134,7 @@ static const struct ath12k_hw_ops qcn9274_ops = {
        .rxdma_ring_sel_config = ath12k_dp_rxdma_ring_sel_config_qcn9274,
        .get_ring_selector = ath12k_hw_get_ring_selector_qcn9274,
        .dp_srng_is_tx_comp_ring = ath12k_dp_srng_is_comp_ring_qcn9274,
+       .is_frame_link_agnostic = ath12k_is_frame_link_agnostic_qcn9274,
 };
 
 static const struct ath12k_hw_ops wcn7850_ops = {
@@ -90,6 +144,7 @@ static const struct ath12k_hw_ops wcn7850_ops = {
        .rxdma_ring_sel_config = ath12k_dp_rxdma_ring_sel_config_wcn7850,
        .get_ring_selector = ath12k_hw_get_ring_selector_wcn7850,
        .dp_srng_is_tx_comp_ring = ath12k_dp_srng_is_comp_ring_wcn7850,
+       .is_frame_link_agnostic = ath12k_is_frame_link_agnostic_wcn7850,
 };
 
 #define ATH12K_TX_RING_MASK_0 0x1
index 9f4ea5e96150c44c00db8ddbfeb2b1a82a4282ac..8ce11c3e6d5c21b2affef8cbe4b0b867e678faae 100644 (file)
@@ -230,6 +230,8 @@ struct ath12k_hw_ops {
        int (*rxdma_ring_sel_config)(struct ath12k_base *ab);
        u8 (*get_ring_selector)(struct sk_buff *skb);
        bool (*dp_srng_is_tx_comp_ring)(int ring_num);
+       bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif,
+                                      struct ieee80211_mgmt *mgmt);
 };
 
 static inline
index 0118c9492e40cdd36d35a124089cc1a651a5e5cf..8d6f0869f2d5db80fcedae47d8f1c4156e6c1d6f 100644 (file)
@@ -8377,7 +8377,7 @@ static int ath12k_mac_mgmt_tx_wmi(struct ath12k *ar, struct ath12k_link_vif *arv
 
        skb_cb->paddr = paddr;
 
-       ret = ath12k_wmi_mgmt_send(ar, arvif->vdev_id, buf_id, skb);
+       ret = ath12k_wmi_mgmt_send(arvif, buf_id, skb);
        if (ret) {
                ath12k_warn(ar->ab, "failed to send mgmt frame: %d\n", ret);
                goto err_unmap_buf;
@@ -8871,6 +8871,9 @@ static void ath12k_mac_op_tx(struct ieee80211_hw *hw,
 
                skb_cb->flags |= ATH12K_SKB_HW_80211_ENCAP;
        } else if (ieee80211_is_mgmt(hdr->frame_control)) {
+               if (sta && sta->mlo)
+                       skb_cb->flags |= ATH12K_SKB_MLO_STA;
+
                ret = ath12k_mac_mgmt_tx(ar, skb, is_prb_rsp);
                if (ret) {
                        ath12k_warn(ar->ab, "failed to queue management frame %d\n",
index ec7236bbccc0feef66d787efdf896e6a1656695c..eb7aeff0149038e3082bf6034591c80e407d10c6 100644 (file)
@@ -8,7 +8,7 @@
 #include "peer.h"
 #include "debug.h"
 
-static struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah, const u8 *addr)
+struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah, const u8 *addr)
 {
        struct ath12k_ml_peer *ml_peer;
 
index 92c4988df2f1658058a2e89a9bcb45b24339e801..44afc0b7dd53ea41897de3d2e98dc7583a783d55 100644 (file)
@@ -91,6 +91,8 @@ struct ath12k_peer *ath12k_peer_find_by_ast(struct ath12k_base *ab, int ast_hash
 int ath12k_peer_ml_create(struct ath12k_hw *ah, struct ieee80211_sta *sta);
 int ath12k_peer_ml_delete(struct ath12k_hw *ah, struct ieee80211_sta *sta);
 int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta);
+struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah,
+                                          const u8 *addr);
 static inline
 struct ath12k_link_sta *ath12k_peer_get_link_sta(struct ath12k_base *ab,
                                                 struct ath12k_peer *peer)
index 2f0a310ec57df46ea722ec79f6e4bf47db6fd08f..ed3c08dbd89934b4efaf7e439cebae02a2470364 100644 (file)
@@ -785,20 +785,46 @@ struct sk_buff *ath12k_wmi_alloc_skb(struct ath12k_wmi_base *wmi_ab, u32 len)
        return skb;
 }
 
-int ath12k_wmi_mgmt_send(struct ath12k *ar, u32 vdev_id, u32 buf_id,
+int ath12k_wmi_mgmt_send(struct ath12k_link_vif *arvif, u32 buf_id,
                         struct sk_buff *frame)
 {
+       struct ath12k *ar = arvif->ar;
        struct ath12k_wmi_pdev *wmi = ar->wmi;
        struct wmi_mgmt_send_cmd *cmd;
        struct ieee80211_tx_info *info = IEEE80211_SKB_CB(frame);
-       struct wmi_tlv *frame_tlv;
+       struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)frame->data;
+       struct ieee80211_vif *vif = ath12k_ahvif_to_vif(arvif->ahvif);
+       int cmd_len = sizeof(struct ath12k_wmi_mgmt_send_tx_params);
+       struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)hdr;
+       struct ath12k_wmi_mlo_mgmt_send_params *ml_params;
+       struct ath12k_base *ab = ar->ab;
+       struct wmi_tlv *frame_tlv, *tlv;
+       struct ath12k_skb_cb *skb_cb;
+       u32 buf_len, buf_len_aligned;
+       u32 vdev_id = arvif->vdev_id;
+       bool link_agnostic = false;
        struct sk_buff *skb;
-       u32 buf_len;
        int ret, len;
+       void *ptr;
 
        buf_len = min_t(int, frame->len, WMI_MGMT_SEND_DOWNLD_LEN);
 
-       len = sizeof(*cmd) + sizeof(*frame_tlv) + roundup(buf_len, 4);
+       buf_len_aligned = roundup(buf_len, sizeof(u32));
+
+       len = sizeof(*cmd) + sizeof(*frame_tlv) + buf_len_aligned;
+
+       if (ieee80211_vif_is_mld(vif)) {
+               skb_cb = ATH12K_SKB_CB(frame);
+               if ((skb_cb->flags & ATH12K_SKB_MLO_STA) &&
+                   ab->hw_params->hw_ops->is_frame_link_agnostic &&
+                   ab->hw_params->hw_ops->is_frame_link_agnostic(arvif, mgmt)) {
+                       len += cmd_len + TLV_HDR_SIZE + sizeof(*ml_params);
+                       ath12k_generic_dbg(ATH12K_DBG_MGMT,
+                                          "Sending Mgmt Frame fc 0x%0x as link agnostic",
+                                          mgmt->frame_control);
+                       link_agnostic = true;
+               }
+       }
 
        skb = ath12k_wmi_alloc_skb(wmi->wmi_ab, len);
        if (!skb)
@@ -821,6 +847,28 @@ int ath12k_wmi_mgmt_send(struct ath12k *ar, u32 vdev_id, u32 buf_id,
 
        memcpy(frame_tlv->value, frame->data, buf_len);
 
+       if (!link_agnostic)
+               goto send;
+
+       ptr = skb->data + sizeof(*cmd) + sizeof(*frame_tlv) + buf_len_aligned;
+
+       tlv = ptr;
+
+       /* Tx params not used currently */
+       tlv->header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_TX_SEND_PARAMS, cmd_len);
+       ptr += cmd_len;
+
+       tlv = ptr;
+       tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_STRUCT, sizeof(*ml_params));
+       ptr += TLV_HDR_SIZE;
+
+       ml_params = ptr;
+       ml_params->tlv_header = ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_TX_SEND_PARAMS,
+                                                      sizeof(*ml_params));
+
+       ml_params->hw_link_id = cpu_to_le32(WMI_MGMT_LINK_AGNOSTIC_ID);
+
+send:
        ret = ath12k_wmi_cmd_send(wmi, skb, WMI_MGMT_TX_SEND_CMDID);
        if (ret) {
                ath12k_warn(ar->ab,
index 967a7cc1e3d557af87f708991bd00e2aa038c75f..5b782258f87085245867a311f2e631a90cf5666c 100644 (file)
@@ -4018,6 +4018,7 @@ struct wmi_scan_chan_list_cmd {
 } __packed;
 
 #define WMI_MGMT_SEND_DOWNLD_LEN       64
+#define WMI_MGMT_LINK_AGNOSTIC_ID      0xFFFFFFFF
 
 #define WMI_TX_PARAMS_DWORD0_POWER             GENMASK(7, 0)
 #define WMI_TX_PARAMS_DWORD0_MCS_MASK          GENMASK(19, 8)
@@ -4043,7 +4044,18 @@ struct wmi_mgmt_send_cmd {
 
        /* This TLV is followed by struct wmi_mgmt_frame */
 
-       /* Followed by struct wmi_mgmt_send_params */
+       /* Followed by struct ath12k_wmi_mlo_mgmt_send_params */
+} __packed;
+
+struct ath12k_wmi_mlo_mgmt_send_params {
+       __le32 tlv_header;
+       __le32 hw_link_id;
+} __packed;
+
+struct ath12k_wmi_mgmt_send_tx_params {
+       __le32 tlv_header;
+       __le32 tx_param_dword0;
+       __le32 tx_param_dword1;
 } __packed;
 
 struct wmi_sta_powersave_mode_cmd {
@@ -6277,7 +6289,7 @@ void ath12k_wmi_init_wcn7850(struct ath12k_base *ab,
 int ath12k_wmi_cmd_send(struct ath12k_wmi_pdev *wmi, struct sk_buff *skb,
                        u32 cmd_id);
 struct sk_buff *ath12k_wmi_alloc_skb(struct ath12k_wmi_base *wmi_sc, u32 len);
-int ath12k_wmi_mgmt_send(struct ath12k *ar, u32 vdev_id, u32 buf_id,
+int ath12k_wmi_mgmt_send(struct ath12k_link_vif *arvif, u32 buf_id,
                         struct sk_buff *frame);
 int ath12k_wmi_p2p_go_bcn_ie(struct ath12k *ar, u32 vdev_id,
                             const u8 *p2p_ie);