]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
wifi: ath12k: Add support for 4-address mode
authorTamizh Chelvam Raja <tamizh.raja@oss.qualcomm.com>
Mon, 25 May 2026 11:09:38 +0000 (16:39 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Mon, 1 Jun 2026 16:58:05 +0000 (09:58 -0700)
The current driver does not support enabling 4-address mode data traffic
in WDS mode. Add the required functionality by introducing the
sta_set_4addr() API, which is invoked when a 4-address AP/STA connects.
This API sends the WMI_PEER_USE_4ADDR peer parameter to notify firmware
about the 4-address peer, allowing firmware and hardware to transmit
and receive frames in 4-address format for that peer.

For 4-address multicast packet transmission, update the handling
to set peer metadata values in HAL_TCL_DATA_CMD_INFO1_CMD_NUM instead
of using vdev metadata values. Vdev metadata is used only for 3-address
and 4-address unicast traffic and for 3-address multicast traffic.
The peer metadata path embeds the correct peer_id, enabling proper
multicast transmission in 4-address mode.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1

Signed-off-by: Tamizh Chelvam Raja <tamizh.raja@oss.qualcomm.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
Link: https://patch.msgid.link/20260525110942.2890212-3-tamizh.raja@oss.qualcomm.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath12k/core.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/mac.h
drivers/net/wireless/ath/ath12k/peer.c
drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c
drivers/net/wireless/ath/ath12k/wifi7/dp_tx.h
drivers/net/wireless/ath/ath12k/wifi7/hw.c
drivers/net/wireless/ath/ath12k/wmi.h

index f6d8ec9ef7b040addc37a1653750b32a5ce0beb1..70ad9742c21dd0cfa09620733d70faebc574e208 100644 (file)
@@ -353,6 +353,8 @@ struct ath12k_link_vif {
        u16 num_stations;
        bool is_csa_in_progress;
        struct wiphy_work bcn_tx_work;
+
+       bool set_wds_vdev_param;
 };
 
 struct ath12k_vif {
@@ -492,6 +494,8 @@ struct ath12k_link_sta {
        /* link address similar to ieee80211_link_sta */
        u8 addr[ETH_ALEN];
 
+       u16 tcl_metadata;
+
        /* the following are protected by ar->data_lock */
        u32 changed; /* IEEE80211_RC_* */
        u32 bw;
@@ -527,6 +531,8 @@ struct ath12k_sta {
        u16 free_logical_link_idx_map;
 
        enum ieee80211_sta_state state;
+
+       bool enable_4addr;
 };
 
 #define ATH12K_HALF_20MHZ_BW   10
index 0f68b4beeb6b3195869c4fde5f15f256f2263aa0..616d1326130560ab37c916cdd6e7aea9b6ae99c5 100644 (file)
@@ -6571,6 +6571,62 @@ static int ath12k_mac_station_disassoc(struct ath12k *ar,
        return 0;
 }
 
+static int ath12k_mac_sta_set_4addr(struct wiphy *wiphy, struct ath12k_sta *ahsta)
+{
+       struct ath12k_dp_link_peer *peer;
+       struct ath12k_link_vif *arvif;
+       struct ath12k_link_sta *arsta;
+       struct ath12k_dp *dp;
+       unsigned long links;
+       struct ath12k *ar;
+       u8 link_id;
+       int ret;
+
+       links = ahsta->links_map;
+       for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
+               arsta = wiphy_dereference(wiphy, ahsta->link[link_id]);
+               if (!arsta)
+                       continue;
+
+               arvif = arsta->arvif;
+               ar = arvif->ar;
+
+               if (arvif->set_wds_vdev_param)
+                       goto skip_use_4addr;
+
+               ath12k_dbg(ar->ab, ATH12K_DBG_MAC,
+                          "setting USE_4ADDR for peer %pM\n", arsta->addr);
+
+               ret = ath12k_wmi_set_peer_param(ar, arsta->addr,
+                                               arvif->vdev_id,
+                                               WMI_PEER_USE_4ADDR,
+                                               WMI_PEER_4ADDR_ALLOW_EAPOL_DATA_FRAME);
+               if (ret) {
+                       ath12k_warn(ar->ab, "failed to set peer %pM 4addr capability: %d\n",
+                                   arsta->addr, ret);
+                       return ret;
+               }
+
+skip_use_4addr:
+               dp = ath12k_ab_to_dp(ar->ab);
+               spin_lock_bh(&dp->dp_lock);
+               peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, arvif->vdev_id,
+                                                                arsta->addr);
+               if (peer && peer->dp_peer) {
+                       peer->dp_peer->ucast_ra_only = true;
+               } else {
+                       spin_unlock_bh(&dp->dp_lock);
+                       ath12k_warn(ar->ab, "failed to find DP peer for %pM\n",
+                                   arsta->addr);
+                       return -ENOENT;
+               }
+
+               spin_unlock_bh(&dp->dp_lock);
+       }
+
+       return 0;
+}
+
 static void ath12k_sta_rc_update_wk(struct wiphy *wiphy, struct wiphy_work *wk)
 {
        struct ieee80211_link_sta *link_sta;
@@ -7865,6 +7921,28 @@ out:
 }
 EXPORT_SYMBOL(ath12k_mac_op_sta_set_txpwr);
 
+void ath12k_mac_op_sta_set_4addr(struct ieee80211_hw *hw,
+                                struct ieee80211_vif *vif,
+                                struct ieee80211_sta *sta, bool enabled)
+{
+       struct ath12k_sta *ahsta = ath12k_sta_to_ahsta(sta);
+
+       lockdep_assert_wiphy(hw->wiphy);
+
+       /*
+        * 4-address mode disabled option is available only for station
+        * interface from mac80211, and we have wds_vdev_param for station
+        * interface and target will not allow to disable the wds_vdev_param
+        * during run time. So, add support only for enable case, for
+        * disable case station interface needs to be reconnect.
+        */
+       if (enabled && !ahsta->enable_4addr) {
+               if (!ath12k_mac_sta_set_4addr(hw->wiphy, ahsta))
+                       ahsta->enable_4addr = true;
+       }
+}
+EXPORT_SYMBOL(ath12k_mac_op_sta_set_4addr);
+
 void ath12k_mac_op_link_sta_rc_update(struct ieee80211_hw *hw,
                                      struct ieee80211_vif *vif,
                                      struct ieee80211_link_sta *link_sta,
@@ -10421,6 +10499,7 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif)
                                            ret);
                                goto err_peer_del;
                        }
+                       arvif->set_wds_vdev_param = true;
                }
 
                if (test_bit(WMI_TLV_SERVICE_11D_OFFLOAD, ab->wmi_ab.svc_map) &&
index 7b50c59763840c5c769c64f9fc414bf67f177468..aba98afd4365cb591cac1d7f869b61d46dad2d96 100644 (file)
@@ -255,6 +255,9 @@ int ath12k_mac_op_sta_state(struct ieee80211_hw *hw,
 int ath12k_mac_op_sta_set_txpwr(struct ieee80211_hw *hw,
                                struct ieee80211_vif *vif,
                                struct ieee80211_sta *sta);
+void ath12k_mac_op_sta_set_4addr(struct ieee80211_hw *hw,
+                                struct ieee80211_vif *vif,
+                                struct ieee80211_sta *sta, bool enabled);
 void ath12k_mac_op_link_sta_rc_update(struct ieee80211_hw *hw,
                                      struct ieee80211_vif *vif,
                                      struct ieee80211_link_sta *link_sta,
index 2e875176baaadaaf626763148339b9000fc2bdd9..42488b4655400eac8cc3776d017c0dabf07afd19 100644 (file)
@@ -218,7 +218,12 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif,
                ahsta = ath12k_sta_to_ahsta(sta);
                arsta = wiphy_dereference(ath12k_ar_to_hw(ar)->wiphy,
                                          ahsta->link[link_id]);
-
+               /* TODO: Split DP related field usage to DP peer structure */
+               arsta->tcl_metadata = u16_encode_bits(0, HTT_TCL_META_DATA_TYPE) |
+                                      u16_encode_bits(peer->peer_id,
+                                                      HTT_TCL_META_DATA_PEER_ID) |
+                                      u16_encode_bits(0,
+                                                      HTT_TCL_META_DATA_VALID_HTT);
                peer->link_id = arsta->link_id;
 
                /* Fill ML info into created peer */
index 629084aa36d859b746329076e30765217b2e47b2..5f298133dee94b8ca25a8b486a32500bb39ef5cd 100644 (file)
@@ -59,8 +59,8 @@ static int ath12k_wifi7_dp_prepare_htt_metadata(struct sk_buff *skb)
 
 /* TODO: Remove the export once this file is built with wifi7 ko */
 int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *arvif,
-                      struct sk_buff *skb, bool gsn_valid, int mcbc_gsn,
-                      bool is_mcast)
+                      struct ath12k_link_sta *arsta, struct sk_buff *skb,
+                      bool gsn_valid, int mcbc_gsn, bool is_mcast)
 {
        struct ath12k_dp *dp = dp_pdev->dp;
        struct ath12k_hal *hal = dp->hal;
@@ -125,6 +125,12 @@ tcl_ring_sel:
        ti.bank_id = dp_link_vif->bank_id;
        ti.meta_data_flags = dp_link_vif->tcl_metadata;
 
+       if (ieee80211_has_a4(hdr->frame_control) &&
+           is_multicast_ether_addr(hdr->addr3) && arsta) {
+               ti.meta_data_flags = arsta->tcl_metadata;
+               ti.flags0 |= u32_encode_bits(1, HAL_TCL_DATA_CMD_INFO2_TO_FW);
+       }
+
        if (dp_vif->tx_encap_type == HAL_TCL_ENCAP_TYPE_RAW &&
            test_bit(ATH12K_FLAG_HW_CRYPTO_DISABLED, &ab->dev_flags)) {
                if (skb_cb->flags & ATH12K_SKB_CIPHER_SET) {
index 24cf7972d41bad76cf2a9d3836ae03ad93a24c14..86bc813878c07be079931cc6a9d43f144a4fa969 100644 (file)
@@ -8,8 +8,8 @@
 #define ATH12K_DP_TX_WIFI7_H
 
 int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *arvif,
-                      struct sk_buff *skb, bool gsn_valid, int mcbc_gsn,
-                      bool is_mcast);
+                      struct ath12k_link_sta *arsta, struct sk_buff *skb,
+                      bool gsn_valid, int mcbc_gsn, bool is_mcast);
 void ath12k_wifi7_dp_tx_completion_handler(struct ath12k_dp *dp, int ring_id);
 u32 ath12k_wifi7_dp_tx_get_vdev_bank_config(struct ath12k_base *ab,
                                            struct ath12k_link_vif *arvif);
index 98bf9293dd33d0101b00e889b23de623156f694c..3d59fa452ec03d6abdabb1b76602fee2b4c5a04b 100644 (file)
@@ -889,7 +889,9 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
        struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
        struct ieee80211_key_conf *key = info->control.hw_key;
        struct ieee80211_sta *sta = control->sta;
+       struct ath12k_link_sta *arsta = NULL;
        struct ath12k_link_vif *tmp_arvif;
+       struct ath12k_sta *ahsta = NULL;
        u32 info_flags = info->flags;
        struct sk_buff *msdu_copied;
        struct ath12k *ar, *tmp_ar;
@@ -971,6 +973,12 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
        if (!(info_flags & IEEE80211_TX_CTL_HW_80211_ENCAP))
                is_mcast = is_multicast_ether_addr(hdr->addr1);
 
+       if (sta) {
+               ahsta = ath12k_sta_to_ahsta(control->sta);
+               if (ahsta && ahsta->enable_4addr)
+                       arsta = rcu_dereference(ahsta->link[link_id]);
+       }
+
        /* This is case only for P2P_GO */
        if (vif->type == NL80211_IFTYPE_AP && vif->p2p)
                ath12k_mac_add_p2p_noa_ie(ar, vif, skb, is_prb_rsp);
@@ -991,7 +999,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
        if (!vif->valid_links || !is_mcast || is_dvlan ||
            (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) ||
            test_bit(ATH12K_FLAG_RAW_MODE, &ar->ab->dev_flags)) {
-               ret = ath12k_wifi7_dp_tx(dp_pdev, arvif, skb, false, 0, is_mcast);
+               ret = ath12k_wifi7_dp_tx(dp_pdev, arvif, arsta, skb, false, 0, is_mcast);
                if (unlikely(ret)) {
                        ath12k_warn(ar->ab, "failed to transmit frame %d\n", ret);
                        ieee80211_free_txskb(ar->ah->hw, skb);
@@ -1029,6 +1037,11 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
                        skb_cb->vif = vif;
                        skb_cb->ar = tmp_ar;
 
+                       if (ahsta && ahsta->enable_4addr)
+                               arsta = rcu_dereference(ahsta->link[link_id]);
+                       else
+                               arsta = NULL;
+
                        /* For open mode, skip peer find logic */
                        if (unlikely(!ahvif->dp_vif.key_cipher))
                                goto skip_peer_find;
@@ -1060,7 +1073,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
                        spin_unlock_bh(&tmp_dp->dp_lock);
 
 skip_peer_find:
-                       ret = ath12k_wifi7_dp_tx(tmp_dp_pdev, tmp_arvif,
+                       ret = ath12k_wifi7_dp_tx(tmp_dp_pdev, tmp_arvif, arsta,
                                                 msdu_copied, true, mcbc_gsn, is_mcast);
                        if (unlikely(ret)) {
                                if (ret == -ENOMEM) {
@@ -1105,6 +1118,7 @@ static const struct ieee80211_ops ath12k_ops_wifi7 = {
        .sta_state                      = ath12k_mac_op_sta_state,
        .sta_set_txpwr                  = ath12k_mac_op_sta_set_txpwr,
        .link_sta_rc_update             = ath12k_mac_op_link_sta_rc_update,
+       .sta_set_4addr                  = ath12k_mac_op_sta_set_4addr,
        .conf_tx                        = ath12k_mac_op_conf_tx,
        .set_antenna                    = ath12k_mac_op_set_antenna,
        .get_antenna                    = ath12k_mac_op_get_antenna,
index 14b8dcdf881dd4f0b4f844b0bbf78e841e763267..a827b3c99f8ff12631efd064476b317957543fd6 100644 (file)
@@ -2334,6 +2334,11 @@ enum wmi_preamble {
        WMI_VDEV_PREAMBLE_SHORT = 2,
 };
 
+enum wmi_peer_4addr_allow_frame {
+       WMI_PEER_4ADDR_ALLOW_DATA_FRAME = 1,
+       WMI_PEER_4ADDR_ALLOW_EAPOL_DATA_FRAME = 2,
+};
+
 enum wmi_peer_smps_state {
        WMI_PEER_SMPS_PS_NONE = 0,
        WMI_PEER_SMPS_STATIC  = 1,