]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath12k: Attach and detach ath12k_dp_link_peer to ath12k_dp_peer
authorHarsh Kumar Bijlani <quic_hbijlani@quicinc.com>
Fri, 24 Oct 2025 18:15:46 +0000 (23:45 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Mon, 27 Oct 2025 14:02:01 +0000 (07:02 -0700)
Introduce explicit attach/detach of ath12k_dp_link_peer objects to their parent
ath12k_dp_peer to formalize the data path station hierarchy:

    ath12k_dp_peer
       |
       |--> ath12k_dp_link_peer
       |
       |--> ath12k_dp_link_peer
       |
       |--> ath12k_dp_link_peer

ath12k_dp_peer maintains an array of RCU-protected pointers
"link_peers[ATH12K_NUM_MAX_LINKS]" to ath12k_dp_link_peer indexed by its
protocol_link_id, and each ath12k_dp_link_peer holds a back pointer to its
parent ath12k_dp_peer.

Attach is performed after link peer creation, and detach occurs before link peer
deletion. This ensures consistent lifetime management and safe concurrent
access.

ath12k_dp_peer also maintains an array "hw_links[ATH12K_GROUP_MAX_RADIO]" to
store the mapping between hw_link_id and protocol_link_id for each of the
ath12k_dp_link_peer.

RCU locking/unlocking rules:
- Readers must hold rcu_read_lock() and fetch the pointer with
  rcu_dereference(dp_peer->link[link_id]); drop with rcu_read_unlock() when
  done.
- Writers publish with rcu_assign_pointer() and reclaim only after
  synchronize_rcu().

Handle the case of detachment of link peer from ath12k_dp_peer in case of core
reset.

Ensure the following order of locks to be followed for attach and detach:
- Lock dp->dp_lock
   - Lock dp_hw->peer_lock
   - Unlock dp_hw->peer_lock
- Unlock dp->dp_lock

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: Harsh Kumar Bijlani <quic_hbijlani@quicinc.com>
Signed-off-by: Ripan Deuri <quic_rdeuri@quicinc.com>
Reviewed-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
Link: https://patch.msgid.link/20251024181548.3255166-8-quic_rdeuri@quicinc.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath12k/dp.c
drivers/net/wireless/ath/ath12k/dp.h
drivers/net/wireless/ath/ath12k/dp_cmn.h
drivers/net/wireless/ath/ath12k/dp_peer.c
drivers/net/wireless/ath/ath12k/dp_peer.h
drivers/net/wireless/ath/ath12k/mac.c
drivers/net/wireless/ath/ath12k/peer.c

index 676af752f069b41278268b2eaa171d0800d9e52e..e682c0cd18ad26bb72e21a486b949ecb31b796c9 100644 (file)
@@ -887,6 +887,7 @@ int ath12k_dp_pdev_alloc(struct ath12k_base *ab)
                dp_pdev->hw = ar->ah->hw;
                dp_pdev->dp = dp;
                dp_pdev->hw_link_id = ar->hw_link_id;
+               dp_pdev->dp_hw = &ar->ah->dp_hw;
 
                ret = ath12k_dp_rx_pdev_alloc(ab, i);
                if (ret) {
index b90725094111cedb6c07044cff475bd232542a89..1ffa3066ade55a1df86448d14357c29682228d66 100644 (file)
@@ -158,6 +158,7 @@ struct ath12k_pdev_dp {
        struct ath12k_dp *dp;
        struct ieee80211_hw *hw;
        u8 hw_link_id;
+       struct ath12k_dp_hw *dp_hw;
 
        /* Protects ppdu stats */
        spinlock_t ppdu_list_lock;
index 52b7fa2eb138bef699eca11136c87a34f11248aa..dd10426bd12def581db6529e537fc1ebdcccf442 100644 (file)
@@ -86,5 +86,9 @@ void ath12k_dp_cmn_hw_group_unassign(struct ath12k_dp *dp,
                                     struct ath12k_hw_group *ag);
 void ath12k_dp_cmn_hw_group_assign(struct ath12k_dp *dp,
                                   struct ath12k_hw_group *ag);
-
+int ath12k_dp_link_peer_assign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_hw,
+                              u8 vdev_id, struct ieee80211_sta *sta, u8 *addr,
+                              u8 link_id, u32 hw_link_id);
+void ath12k_dp_link_peer_unassign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_hw,
+                                 u8 vdev_id, u8 *addr, u32 hw_link_id);
 #endif
index a2834d043dd52709fa5abb68b9a74bf0a9eabb05..fe4748648a84427169fa03c354d1cf967608a2c7 100644 (file)
@@ -139,7 +139,6 @@ void ath12k_dp_link_peer_unmap_event(struct ath12k_base *ab, u16 peer_id)
        ath12k_dbg(ab, ATH12K_DBG_DP_HTT, "htt peer unmap vdev %d peer %pM id %d\n",
                   peer->vdev_id, peer->addr, peer_id);
 
-       ath12k_dp_link_peer_rhash_delete(dp, peer);
        list_del(&peer->list);
        kfree(peer);
        wake_up(&ab->peer_mapping_wq);
@@ -153,7 +152,6 @@ void ath12k_dp_link_peer_map_event(struct ath12k_base *ab, u8 vdev_id, u16 peer_
 {
        struct ath12k_dp_link_peer *peer;
        struct ath12k_dp *dp = ath12k_ab_to_dp(ab);
-       int ret;
 
        spin_lock_bh(&dp->dp_lock);
        peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, vdev_id, mac_addr);
@@ -167,11 +165,7 @@ void ath12k_dp_link_peer_map_event(struct ath12k_base *ab, u8 vdev_id, u16 peer_
                peer->ast_hash = ast_hash;
                peer->hw_peer_id = hw_peer_id;
                ether_addr_copy(peer->addr, mac_addr);
-               ret = ath12k_dp_link_peer_rhash_add(dp, peer);
-               if (!ret)
-                       list_add(&peer->list, &dp->peers);
-               else
-                       kfree(peer);
+               list_add(&peer->list, &dp->peers);
                wake_up(&ab->peer_mapping_wq);
        }
 
@@ -377,6 +371,23 @@ static struct ath12k_dp_peer *ath12k_dp_peer_create_find(struct ath12k_dp_hw *dp
        return NULL;
 }
 
+/*
+ * Index of ath12k_dp_peer for MLO client is same as peer id of ath12k_dp_peer,
+ * while for ath12k_dp_link_peer(mlo and non-mlo) and ath12k_dp_peer for
+ * Non-MLO client it is derived as ((DEVICE_ID << 10) | (10 bits of peer id)).
+ *
+ * This is done because ml_peer_id and peer_id_table are at hw granularity,
+ * while link_peer_id is at device granularity, hence in order to avoid
+ * conflict this approach is followed.
+ */
+#define ATH12K_DP_PEER_TABLE_DEVICE_ID_SHIFT        10
+
+u16 ath12k_dp_peer_get_peerid_index(struct ath12k_dp *dp, u16 peer_id)
+{
+       return (peer_id & ATH12K_PEER_ML_ID_VALID) ? peer_id :
+               ((dp->device_id << ATH12K_DP_PEER_TABLE_DEVICE_ID_SHIFT) | peer_id);
+}
+
 int ath12k_dp_peer_create(struct ath12k_dp_hw *dp_hw, u8 *addr,
                          struct ath12k_dp_peer_create_params *params)
 {
@@ -451,3 +462,124 @@ void ath12k_dp_peer_delete(struct ath12k_dp_hw *dp_hw, u8 *addr,
        synchronize_rcu();
        kfree(dp_peer);
 }
+
+int ath12k_dp_link_peer_assign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_hw,
+                              u8 vdev_id, struct ieee80211_sta *sta, u8 *addr,
+                              u8 link_id, u32 hw_link_id)
+{
+       struct ath12k_dp_peer *dp_peer;
+       struct ath12k_dp_link_peer *peer, *temp_peer;
+       u16 peerid_index;
+       int ret = -EINVAL;
+       u8 *dp_peer_mac = !sta ? addr : sta->addr;
+
+       spin_lock_bh(&dp->dp_lock);
+
+       peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, vdev_id, addr);
+       if (!peer) {
+               ath12k_warn(dp, "failed to find dp_link_peer with mac %pM on vdev %u\n",
+                           addr, vdev_id);
+               ret = -ENOENT;
+               goto err_peer;
+       }
+
+       spin_lock_bh(&dp_hw->peer_lock);
+
+       dp_peer = ath12k_dp_peer_find_by_addr_and_sta(dp_hw, dp_peer_mac, sta);
+       if (!dp_peer) {
+               ath12k_warn(dp, "failed to find dp_peer with mac %pM\n", dp_peer_mac);
+               ret = -ENOENT;
+               goto err_dp_peer;
+       }
+
+       /*
+        * Set peer_id in dp_peer for non-mlo client, peer_id for mlo client is
+        * set during dp_peer create
+        */
+       if (!dp_peer->is_mlo)
+               dp_peer->peer_id = peer->peer_id;
+
+       peer->dp_peer = dp_peer;
+       peer->hw_link_id = hw_link_id;
+
+       dp_peer->hw_links[peer->hw_link_id] = link_id;
+
+       peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id);
+
+       rcu_assign_pointer(dp_peer->link_peers[peer->link_id], peer);
+
+       rcu_assign_pointer(dp_hw->dp_peers[peerid_index], dp_peer);
+
+       spin_unlock_bh(&dp_hw->peer_lock);
+
+       /*
+        * In case of Split PHY and roaming scenario, pdev idx
+        * might differ but both the pdev will share same rhash
+        * table. In that case update the rhash table if link_peer is
+        * already present
+        */
+       temp_peer = ath12k_dp_link_peer_find_by_addr(dp, addr);
+       if (temp_peer && temp_peer->hw_link_id != hw_link_id)
+               ath12k_dp_link_peer_rhash_delete(dp, temp_peer);
+
+       ret = ath12k_dp_link_peer_rhash_add(dp, peer);
+       if (ret) {
+               /*
+                * If new entry addition failed, add back old entry
+                * If old entry addition also fails, then nothing
+                * can be done, simply proceed
+                */
+               if (temp_peer)
+                       ath12k_dp_link_peer_rhash_add(dp, temp_peer);
+       }
+
+       spin_unlock_bh(&dp->dp_lock);
+
+       return ret;
+
+err_dp_peer:
+       spin_unlock_bh(&dp_hw->peer_lock);
+
+err_peer:
+       spin_unlock_bh(&dp->dp_lock);
+
+       return ret;
+}
+
+void ath12k_dp_link_peer_unassign(struct ath12k_dp *dp, struct ath12k_dp_hw *dp_hw,
+                                 u8 vdev_id, u8 *addr, u32 hw_link_id)
+{
+       struct ath12k_dp_peer *dp_peer;
+       struct ath12k_dp_link_peer *peer, *temp_peer;
+       u16 peerid_index;
+
+       spin_lock_bh(&dp->dp_lock);
+
+       peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, vdev_id, addr);
+       if (!peer || !peer->dp_peer) {
+               spin_unlock_bh(&dp->dp_lock);
+               return;
+       }
+
+       spin_lock_bh(&dp_hw->peer_lock);
+
+       dp_peer = peer->dp_peer;
+       dp_peer->hw_links[peer->hw_link_id] = 0;
+
+       peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id);
+
+       rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL);
+
+       rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL);
+
+       spin_unlock_bh(&dp_hw->peer_lock);
+
+       /* To handle roaming and split phy scenario */
+       temp_peer = ath12k_dp_link_peer_find_by_addr(dp, addr);
+       if (temp_peer && temp_peer->hw_link_id == hw_link_id)
+               ath12k_dp_link_peer_rhash_delete(dp, peer);
+
+       spin_unlock_bh(&dp->dp_lock);
+
+       synchronize_rcu();
+}
index b4aa4f09337e05f0a0c39d7671ca8105d68a360f..36cce662031048eebb481bece2af1ace03a81000 100644 (file)
@@ -26,6 +26,7 @@ struct ppdu_user_delayba {
 struct ath12k_dp_link_peer {
        struct list_head list;
        struct ieee80211_sta *sta;
+       struct ath12k_dp_peer *dp_peer;
        int vdev_id;
        u8 addr[ETH_ALEN];
        int peer_id;
@@ -68,6 +69,8 @@ struct ath12k_dp_link_peer {
 
        /* peer addr based rhashtable list pointer */
        struct rhash_head rhash_addr;
+
+       u8 hw_link_id;
 };
 
 void ath12k_dp_link_peer_unmap_event(struct ath12k_base *ab, u16 peer_id);
@@ -81,10 +84,13 @@ struct ath12k_dp_peer {
        u8 addr[ETH_ALEN];
        bool is_mlo;
 
+       struct ath12k_dp_link_peer __rcu *link_peers[ATH12K_NUM_MAX_LINKS];
+
        u16 sec_type;
        u16 sec_type_grp;
 
        bool ucast_ra_only;
+       u8 hw_links[ATH12K_GROUP_MAX_RADIO];
 };
 
 struct ath12k_dp_link_peer *
@@ -116,4 +122,5 @@ struct ath12k_dp_peer *ath12k_dp_peer_find_by_addr(struct ath12k_dp_hw *dp_hw, u
 struct ath12k_dp_peer *ath12k_dp_peer_find_by_addr_and_sta(struct ath12k_dp_hw *dp_hw,
                                                           u8 *addr,
                                                           struct ieee80211_sta *sta);
+u16 ath12k_dp_peer_get_peerid_index(struct ath12k_dp *dp, u16 peer_id);
 #endif
index 42fbfb13f23f501cb9dd19f4c14adbaa0e3512e2..02168a857e0a865d0b9140490400a9ccc7e408bd 100644 (file)
@@ -1181,6 +1181,11 @@ void ath12k_mac_peer_cleanup_all(struct ath12k *ar)
        struct ath12k_dp *dp = ath12k_ab_to_dp(ab);
        struct ath12k_link_vif *arvif, *tmp_vif;
        struct ath12k_dp_hw *dp_hw = &ar->ah->dp_hw;
+       struct ath12k_dp_peer *dp_peer = NULL;
+       u16 peerid_index;
+       struct list_head peers;
+
+       INIT_LIST_HEAD(&peers);
 
        lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
 
@@ -1190,12 +1195,26 @@ void ath12k_mac_peer_cleanup_all(struct ath12k *ar)
                if (peer->sta)
                        ath12k_dp_rx_peer_tid_cleanup(ar, peer);
 
+               /* cleanup dp peer */
+               spin_lock_bh(&dp_hw->peer_lock);
+               dp_peer = peer->dp_peer;
+               peerid_index = ath12k_dp_peer_get_peerid_index(dp, peer->peer_id);
+               rcu_assign_pointer(dp_peer->link_peers[peer->link_id], NULL);
+               rcu_assign_pointer(dp_hw->dp_peers[peerid_index], NULL);
+               spin_unlock_bh(&dp_hw->peer_lock);
+
                ath12k_dp_link_peer_rhash_delete(dp, peer);
 
+               list_move(&peer->list, &peers);
+       }
+       spin_unlock_bh(&dp->dp_lock);
+
+       synchronize_rcu();
+
+       list_for_each_entry_safe(peer, tmp, &peers, list) {
                list_del(&peer->list);
                kfree(peer);
        }
-       spin_unlock_bh(&dp->dp_lock);
 
        ar->num_peers = 0;
        ar->num_stations = 0;
index 691314efe23f62de3a0b57bc475be292b270052d..8b6fea685a701aa411c2cac59bd7fed8a4d3e95d 100644 (file)
@@ -110,6 +110,10 @@ int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr)
 
        lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
 
+       ath12k_dp_link_peer_unassign(ath12k_ab_to_dp(ar->ab),
+                                    &(ath12k_ar_to_ah(ar)->dp_hw), vdev_id,
+                                    addr, ar->hw_link_id);
+
        ret = ath12k_peer_delete_send(ar, vdev_id, addr);
        if (ret)
                return ret;
@@ -243,7 +247,13 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif,
 
        spin_unlock_bh(&dp->dp_lock);
 
-       return 0;
+       ret = ath12k_dp_link_peer_assign(ath12k_ab_to_dp(ar->ab),
+                                        &(ath12k_ar_to_ah(ar)->dp_hw),
+                                        arvif->vdev_id, sta,
+                                        (u8 *)arg->peer_addr, link_id,
+                                        ar->hw_link_id);
+
+       return ret;
 }
 
 u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah)
@@ -298,6 +308,11 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st
 
                ath12k_dp_peer_cleanup(ar, arvif->vdev_id, arsta->addr);
 
+               ath12k_dp_link_peer_unassign(ath12k_ab_to_dp(ar->ab),
+                                            &(ath12k_ar_to_ah(ar)->dp_hw),
+                                            arvif->vdev_id, arsta->addr,
+                                            ar->hw_link_id);
+
                ret = ath12k_peer_delete_send(ar, arvif->vdev_id, arsta->addr);
                if (ret) {
                        ath12k_warn(ar->ab,