diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath11k/peer.c')
-rw-r--r-- | drivers/net/wireless/ath/ath11k/peer.c | 411 |
1 files changed, 371 insertions, 40 deletions
diff --git a/drivers/net/wireless/ath/ath11k/peer.c b/drivers/net/wireless/ath/ath11k/peer.c index 85471f8b3563..1ae7af02c364 100644 --- a/drivers/net/wireless/ath/ath11k/peer.c +++ b/drivers/net/wireless/ath/ath11k/peer.c @@ -1,23 +1,22 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear /* * Copyright (c) 2018-2019 The Linux Foundation. All rights reserved. + * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved. */ #include "core.h" #include "peer.h" #include "debug.h" -struct ath11k_peer *ath11k_peer_find(struct ath11k_base *ab, int vdev_id, - const u8 *addr) +static struct ath11k_peer *ath11k_peer_find_list_by_id(struct ath11k_base *ab, + int peer_id) { struct ath11k_peer *peer; lockdep_assert_held(&ab->base_lock); list_for_each_entry(peer, &ab->peers, list) { - if (peer->vdev_id != vdev_id) - continue; - if (!ether_addr_equal(peer->addr, addr)) + if (peer->peer_id != peer_id) continue; return peer; @@ -26,15 +25,15 @@ struct ath11k_peer *ath11k_peer_find(struct ath11k_base *ab, int vdev_id, return NULL; } -static struct ath11k_peer *ath11k_peer_find_by_pdev_idx(struct ath11k_base *ab, - u8 pdev_idx, const u8 *addr) +struct ath11k_peer *ath11k_peer_find(struct ath11k_base *ab, int vdev_id, + const u8 *addr) { struct ath11k_peer *peer; lockdep_assert_held(&ab->base_lock); list_for_each_entry(peer, &ab->peers, list) { - if (peer->pdev_idx != pdev_idx) + if (peer->vdev_id != vdev_id) continue; if (!ether_addr_equal(peer->addr, addr)) continue; @@ -52,14 +51,13 @@ struct ath11k_peer *ath11k_peer_find_by_addr(struct ath11k_base *ab, lockdep_assert_held(&ab->base_lock); - list_for_each_entry(peer, &ab->peers, list) { - if (!ether_addr_equal(peer->addr, addr)) - continue; + if (!ab->rhead_peer_addr) + return NULL; - return peer; - } + peer = rhashtable_lookup_fast(ab->rhead_peer_addr, addr, + ab->rhash_peer_addr_param); - return NULL; + return peer; } struct ath11k_peer *ath11k_peer_find_by_id(struct ath11k_base *ab, @@ -69,11 +67,13 @@ struct ath11k_peer *ath11k_peer_find_by_id(struct ath11k_base *ab, lockdep_assert_held(&ab->base_lock); - list_for_each_entry(peer, &ab->peers, list) - if (peer_id == peer->peer_id) - return peer; + if (!ab->rhead_peer_id) + return NULL; - return NULL; + peer = rhashtable_lookup_fast(ab->rhead_peer_id, &peer_id, + ab->rhash_peer_id_param); + + return peer; } struct ath11k_peer *ath11k_peer_find_by_vdev_id(struct ath11k_base *ab, @@ -99,7 +99,7 @@ void ath11k_peer_unmap_event(struct ath11k_base *ab, u16 peer_id) spin_lock_bh(&ab->base_lock); - peer = ath11k_peer_find_by_id(ab, peer_id); + peer = ath11k_peer_find_list_by_id(ab, peer_id); if (!peer) { ath11k_warn(ab, "peer-unmap-event: unknown peer id %d\n", peer_id); @@ -167,6 +167,76 @@ static int ath11k_wait_for_peer_common(struct ath11k_base *ab, int vdev_id, return 0; } +static inline int ath11k_peer_rhash_insert(struct ath11k_base *ab, + struct rhashtable *rtbl, + struct rhash_head *rhead, + struct rhashtable_params *params, + void *key) +{ + struct ath11k_peer *tmp; + + lockdep_assert_held(&ab->tbl_mtx_lock); + + tmp = rhashtable_lookup_get_insert_fast(rtbl, rhead, *params); + + if (!tmp) + return 0; + else if (IS_ERR(tmp)) + return PTR_ERR(tmp); + else + return -EEXIST; +} + +static inline int ath11k_peer_rhash_remove(struct ath11k_base *ab, + struct rhashtable *rtbl, + struct rhash_head *rhead, + struct rhashtable_params *params) +{ + int ret; + + lockdep_assert_held(&ab->tbl_mtx_lock); + + ret = rhashtable_remove_fast(rtbl, rhead, *params); + if (ret && ret != -ENOENT) + return ret; + + return 0; +} + +static int ath11k_peer_rhash_add(struct ath11k_base *ab, struct ath11k_peer *peer) +{ + int ret; + + lockdep_assert_held(&ab->base_lock); + lockdep_assert_held(&ab->tbl_mtx_lock); + + if (!ab->rhead_peer_id || !ab->rhead_peer_addr) + return -EPERM; + + ret = ath11k_peer_rhash_insert(ab, ab->rhead_peer_id, &peer->rhash_id, + &ab->rhash_peer_id_param, &peer->peer_id); + if (ret) { + ath11k_warn(ab, "failed to add peer %pM with id %d in rhash_id ret %d\n", + peer->addr, peer->peer_id, ret); + return ret; + } + + ret = ath11k_peer_rhash_insert(ab, ab->rhead_peer_addr, &peer->rhash_addr, + &ab->rhash_peer_addr_param, &peer->addr); + if (ret) { + ath11k_warn(ab, "failed to add peer %pM with id %d in rhash_addr ret %d\n", + peer->addr, peer->peer_id, ret); + goto err_clean; + } + + return 0; + +err_clean: + ath11k_peer_rhash_remove(ab, ab->rhead_peer_id, &peer->rhash_id, + &ab->rhash_peer_id_param); + return ret; +} + void ath11k_peer_cleanup(struct ath11k *ar, u32 vdev_id) { struct ath11k_peer *peer, *tmp; @@ -174,6 +244,7 @@ void ath11k_peer_cleanup(struct ath11k *ar, u32 vdev_id) lockdep_assert_held(&ar->conf_mutex); + mutex_lock(&ab->tbl_mtx_lock); spin_lock_bh(&ab->base_lock); list_for_each_entry_safe(peer, tmp, &ab->peers, list) { if (peer->vdev_id != vdev_id) @@ -182,12 +253,14 @@ void ath11k_peer_cleanup(struct ath11k *ar, u32 vdev_id) ath11k_warn(ab, "removing stale peer %pM from vdev_id %d\n", peer->addr, vdev_id); + ath11k_peer_rhash_delete(ab, peer); list_del(&peer->list); kfree(peer); ar->num_peers--; } spin_unlock_bh(&ab->base_lock); + mutex_unlock(&ab->tbl_mtx_lock); } static int ath11k_wait_for_peer_deleted(struct ath11k *ar, int vdev_id, const u8 *addr) @@ -217,17 +290,51 @@ int ath11k_wait_for_peer_delete_done(struct ath11k *ar, u32 vdev_id, return 0; } -int ath11k_peer_delete(struct ath11k *ar, u32 vdev_id, u8 *addr) +static int __ath11k_peer_delete(struct ath11k *ar, u32 vdev_id, const u8 *addr) { int ret; + struct ath11k_peer *peer; + struct ath11k_base *ab = ar->ab; lockdep_assert_held(&ar->conf_mutex); + mutex_lock(&ab->tbl_mtx_lock); + spin_lock_bh(&ab->base_lock); + + peer = ath11k_peer_find_by_addr(ab, addr); + /* Check if the found peer is what we want to remove. + * While the sta is transitioning to another band we may + * have 2 peer with the same addr assigned to different + * vdev_id. Make sure we are deleting the correct peer. + */ + if (peer && peer->vdev_id == vdev_id) + ath11k_peer_rhash_delete(ab, peer); + + /* Fallback to peer list search if the correct peer can't be found. + * Skip the deletion of the peer from the rhash since it has already + * been deleted in peer add. + */ + if (!peer) + peer = ath11k_peer_find(ab, vdev_id, addr); + + if (!peer) { + spin_unlock_bh(&ab->base_lock); + mutex_unlock(&ab->tbl_mtx_lock); + + ath11k_warn(ab, + "failed to find peer vdev_id %d addr %pM in delete\n", + vdev_id, addr); + return -EINVAL; + } + + spin_unlock_bh(&ab->base_lock); + mutex_unlock(&ab->tbl_mtx_lock); + reinit_completion(&ar->peer_delete_done); ret = ath11k_wmi_send_peer_delete_cmd(ar, addr, vdev_id); if (ret) { - ath11k_warn(ar->ab, + ath11k_warn(ab, "failed to delete peer vdev_id %d addr %pM ret %d\n", vdev_id, addr, ret); return ret; @@ -237,6 +344,19 @@ int ath11k_peer_delete(struct ath11k *ar, u32 vdev_id, u8 *addr) if (ret) return ret; + return 0; +} + +int ath11k_peer_delete(struct ath11k *ar, u32 vdev_id, u8 *addr) +{ + int ret; + + lockdep_assert_held(&ar->conf_mutex); + + ret = __ath11k_peer_delete(ar, vdev_id, addr); + if (ret) + return ret; + ar->num_peers--; return 0; @@ -252,7 +372,7 @@ int ath11k_peer_create(struct ath11k *ar, struct ath11k_vif *arvif, { struct ath11k_peer *peer; struct ath11k_sta *arsta; - int ret; + int ret, fbret; lockdep_assert_held(&ar->conf_mutex); @@ -263,10 +383,19 @@ int ath11k_peer_create(struct ath11k *ar, struct ath11k_vif *arvif, } spin_lock_bh(&ar->ab->base_lock); - peer = ath11k_peer_find_by_pdev_idx(ar->ab, ar->pdev_idx, param->peer_addr); + peer = ath11k_peer_find_by_addr(ar->ab, param->peer_addr); if (peer) { - spin_unlock_bh(&ar->ab->base_lock); - return -EINVAL; + if (peer->vdev_id == param->vdev_id) { + spin_unlock_bh(&ar->ab->base_lock); + return -EINVAL; + } + + /* Assume sta is transitioning to another band. + * Remove here the peer from rhash. + */ + mutex_lock(&ar->ab->tbl_mtx_lock); + ath11k_peer_rhash_delete(ar->ab, peer); + mutex_unlock(&ar->ab->tbl_mtx_lock); } spin_unlock_bh(&ar->ab->base_lock); @@ -283,30 +412,25 @@ int ath11k_peer_create(struct ath11k *ar, struct ath11k_vif *arvif, if (ret) return ret; + mutex_lock(&ar->ab->tbl_mtx_lock); spin_lock_bh(&ar->ab->base_lock); peer = ath11k_peer_find(ar->ab, param->vdev_id, param->peer_addr); if (!peer) { spin_unlock_bh(&ar->ab->base_lock); + mutex_unlock(&ar->ab->tbl_mtx_lock); ath11k_warn(ar->ab, "failed to find peer %pM on vdev %i after creation\n", param->peer_addr, param->vdev_id); - reinit_completion(&ar->peer_delete_done); - - ret = ath11k_wmi_send_peer_delete_cmd(ar, param->peer_addr, - param->vdev_id); - if (ret) { - ath11k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n", - param->vdev_id, param->peer_addr); - return ret; - } - - ret = ath11k_wait_for_peer_delete_done(ar, param->vdev_id, - param->peer_addr); - if (ret) - return ret; + ret = -ENOENT; + goto cleanup; + } - return -ENOENT; + ret = ath11k_peer_rhash_add(ar->ab, peer); + if (ret) { + spin_unlock_bh(&ar->ab->base_lock); + mutex_unlock(&ar->ab->tbl_mtx_lock); + goto cleanup; } peer->pdev_idx = ar->pdev_idx; @@ -333,6 +457,213 @@ int ath11k_peer_create(struct ath11k *ar, struct ath11k_vif *arvif, ar->num_peers++; spin_unlock_bh(&ar->ab->base_lock); + mutex_unlock(&ar->ab->tbl_mtx_lock); return 0; + +cleanup: + fbret = __ath11k_peer_delete(ar, param->vdev_id, param->peer_addr); + if (fbret) + ath11k_warn(ar->ab, "failed peer %pM delete vdev_id %d fallback ret %d\n", + param->peer_addr, param->vdev_id, fbret); + + return ret; +} + +int ath11k_peer_rhash_delete(struct ath11k_base *ab, struct ath11k_peer *peer) +{ + int ret; + + lockdep_assert_held(&ab->base_lock); + lockdep_assert_held(&ab->tbl_mtx_lock); + + if (!ab->rhead_peer_id || !ab->rhead_peer_addr) + return -EPERM; + + ret = ath11k_peer_rhash_remove(ab, ab->rhead_peer_addr, &peer->rhash_addr, + &ab->rhash_peer_addr_param); + if (ret) { + ath11k_warn(ab, "failed to remove peer %pM id %d in rhash_addr ret %d\n", + peer->addr, peer->peer_id, ret); + return ret; + } + + ret = ath11k_peer_rhash_remove(ab, ab->rhead_peer_id, &peer->rhash_id, + &ab->rhash_peer_id_param); + if (ret) { + ath11k_warn(ab, "failed to remove peer %pM id %d in rhash_id ret %d\n", + peer->addr, peer->peer_id, ret); + return ret; + } + + return 0; +} + +static int ath11k_peer_rhash_id_tbl_init(struct ath11k_base *ab) +{ + struct rhashtable_params *param; + struct rhashtable *rhash_id_tbl; + int ret; + size_t size; + + lockdep_assert_held(&ab->tbl_mtx_lock); + + if (ab->rhead_peer_id) + return 0; + + size = sizeof(*ab->rhead_peer_id); + rhash_id_tbl = kzalloc(size, GFP_KERNEL); + if (!rhash_id_tbl) { + ath11k_warn(ab, "failed to init rhash id table due to no mem (size %zu)\n", + size); + return -ENOMEM; + } + + param = &ab->rhash_peer_id_param; + + param->key_offset = offsetof(struct ath11k_peer, peer_id); + param->head_offset = offsetof(struct ath11k_peer, rhash_id); + param->key_len = sizeof_field(struct ath11k_peer, peer_id); + param->automatic_shrinking = true; + param->nelem_hint = ab->num_radios * TARGET_NUM_PEERS_PDEV(ab); + + ret = rhashtable_init(rhash_id_tbl, param); + if (ret) { + ath11k_warn(ab, "failed to init peer id rhash table %d\n", ret); + goto err_free; + } + + spin_lock_bh(&ab->base_lock); + + if (!ab->rhead_peer_id) { + ab->rhead_peer_id = rhash_id_tbl; + } else { + spin_unlock_bh(&ab->base_lock); + goto cleanup_tbl; + } + + spin_unlock_bh(&ab->base_lock); + + return 0; + +cleanup_tbl: + rhashtable_destroy(rhash_id_tbl); +err_free: + kfree(rhash_id_tbl); + + return ret; +} + +static int ath11k_peer_rhash_addr_tbl_init(struct ath11k_base *ab) +{ + struct rhashtable_params *param; + struct rhashtable *rhash_addr_tbl; + int ret; + size_t size; + + lockdep_assert_held(&ab->tbl_mtx_lock); + + if (ab->rhead_peer_addr) + return 0; + + size = sizeof(*ab->rhead_peer_addr); + rhash_addr_tbl = kzalloc(size, GFP_KERNEL); + if (!rhash_addr_tbl) { + ath11k_warn(ab, "failed to init rhash addr table due to no mem (size %zu)\n", + size); + return -ENOMEM; + } + + param = &ab->rhash_peer_addr_param; + + param->key_offset = offsetof(struct ath11k_peer, addr); + param->head_offset = offsetof(struct ath11k_peer, rhash_addr); + param->key_len = sizeof_field(struct ath11k_peer, addr); + param->automatic_shrinking = true; + param->nelem_hint = ab->num_radios * TARGET_NUM_PEERS_PDEV(ab); + + ret = rhashtable_init(rhash_addr_tbl, param); + if (ret) { + ath11k_warn(ab, "failed to init peer addr rhash table %d\n", ret); + goto err_free; + } + + spin_lock_bh(&ab->base_lock); + + if (!ab->rhead_peer_addr) { + ab->rhead_peer_addr = rhash_addr_tbl; + } else { + spin_unlock_bh(&ab->base_lock); + goto cleanup_tbl; + } + + spin_unlock_bh(&ab->base_lock); + + return 0; + +cleanup_tbl: + rhashtable_destroy(rhash_addr_tbl); +err_free: + kfree(rhash_addr_tbl); + + return ret; +} + +static inline void ath11k_peer_rhash_id_tbl_destroy(struct ath11k_base *ab) +{ + lockdep_assert_held(&ab->tbl_mtx_lock); + + if (!ab->rhead_peer_id) + return; + + rhashtable_destroy(ab->rhead_peer_id); + kfree(ab->rhead_peer_id); + ab->rhead_peer_id = NULL; +} + +static inline void ath11k_peer_rhash_addr_tbl_destroy(struct ath11k_base *ab) +{ + lockdep_assert_held(&ab->tbl_mtx_lock); + + if (!ab->rhead_peer_addr) + return; + + rhashtable_destroy(ab->rhead_peer_addr); + kfree(ab->rhead_peer_addr); + ab->rhead_peer_addr = NULL; +} + +int ath11k_peer_rhash_tbl_init(struct ath11k_base *ab) +{ + int ret; + + mutex_lock(&ab->tbl_mtx_lock); + + ret = ath11k_peer_rhash_id_tbl_init(ab); + if (ret) + goto out; + + ret = ath11k_peer_rhash_addr_tbl_init(ab); + if (ret) + goto cleanup_tbl; + + mutex_unlock(&ab->tbl_mtx_lock); + + return 0; + +cleanup_tbl: + ath11k_peer_rhash_id_tbl_destroy(ab); +out: + mutex_unlock(&ab->tbl_mtx_lock); + return ret; +} + +void ath11k_peer_rhash_tbl_destroy(struct ath11k_base *ab) +{ + mutex_lock(&ab->tbl_mtx_lock); + + ath11k_peer_rhash_addr_tbl_destroy(ab); + ath11k_peer_rhash_id_tbl_destroy(ab); + + mutex_unlock(&ab->tbl_mtx_lock); } |