[PATCH ath-next] wifi: ath12k: move arsta grouping from rhashtable to per-radio hashtable

Baochen Qiang baochen.qiang at oss.qualcomm.com
Sun Nov 1 17:47:47 PST 2026



On 10/22/2026 1:33 AM, Maharaja Kennadyrajan wrote:
> From: Nagarajan Maran <nagarajan.maran at oss.qualcomm.com>
> 
> The link-station lookup table is a single rhashtable at the ath12k_base
> level, keyed only by station address. Rhashtable does not allow duplicate
> keys, so the same address cannot be represented on two radios at once;
> a roam or split-PHY overlap where an address briefly exists on more than
> one radio is instead handled by deleting the previous radio's entry
> before inserting the new one. Because the table is keyed only by address
> and shared device-wide, a lookup also cannot be scoped to a specific
> radio: during that overlap window, or due to stale remnants, the single
> entry present for an address can belong to a different radio than the
> one being looked up, silently attaching the wrong radio's data to the
> wrong arsta.
> 
> Move the lookup table down to the ath12k (per-radio) level, and switch its
> backing structure from rhashtable to hlist, which is simpler to walk in
> full for the iteration that some code paths already need. Each radio now
> owns its own table, so a lookup is inherently scoped to that radio, and
> the same address can occupy an entry on two different radios at once
> without any delete-then-insert workaround.
> 
> Update ath12k_peer_sta_kickout_event(), ath12k_wmi_tlv_fw_stats_data_parse()
> and ath12k_wmi_tlv_rssi_chain_parse() to use the new per-radio arsta lookup
> helpers accordingly.
> 
> The datapath's link-peer table (struct ath12k_dp_link_peer, in dp_peer.c) has
> the same device-level rhashtable design and delete-then-insert workaround; it
> is unaffected by this change and remains out of scope here.
> 
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.5-01651-QCAHKSWPL_SILICONZ-1
> 
> Signed-off-by: Nagarajan Maran <nagarajan.maran at oss.qualcomm.com>
> Co-developed-by: Maharaja Kennadyrajan <maharaja.kennadyrajan at oss.qualcomm.com>
> Signed-off-by: Maharaja Kennadyrajan <maharaja.kennadyrajan at oss.qualcomm.com>
> ---
>  drivers/net/wireless/ath/ath12k/core.c |   9 --
>  drivers/net/wireless/ath/ath12k/core.h |  21 ++--
>  drivers/net/wireless/ath/ath12k/mac.c  | 122 ++++++++++-----------
>  drivers/net/wireless/ath/ath12k/peer.c | 144 +++++++++++--------------
>  drivers/net/wireless/ath/ath12k/peer.h |  11 +-
>  drivers/net/wireless/ath/ath12k/wmi.c  | 112 ++++++++++++-------
>  6 files changed, 213 insertions(+), 206 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c
> index 25987bc58556..2309ccae5338 100644
> --- a/drivers/net/wireless/ath/ath12k/core.c
> +++ b/drivers/net/wireless/ath/ath12k/core.c
> @@ -716,8 +716,6 @@ void ath12k_core_to_group_ref_put(struct ath12k_base *ab)
>  
>  static void ath12k_core_stop(struct ath12k_base *ab)
>  {
> -	ath12k_link_sta_rhash_tbl_destroy(ab);
> -
>  	ath12k_core_to_group_ref_put(ab);
>  
>  	if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) {
> @@ -1016,12 +1014,6 @@ static int ath12k_core_start(struct ath12k_base *ab)
>  	/* Indicate the core start in the appropriate group */
>  	ath12k_core_to_group_ref_get(ab);
>  
> -	ret = ath12k_link_sta_rhash_tbl_init(ab);
> -	if (ret) {
> -		ath12k_warn(ab, "failed to init peer addr rhash table %d\n", ret);
> -		goto err_reo_cleanup;
> -	}
> -
>  	return 0;
>  
>  err_reo_cleanup:
> @@ -1425,7 +1417,6 @@ static int ath12k_core_reconfigure_on_crash(struct ath12k_base *ab)
>  	int ret, total_vdev;
>  
>  	mutex_lock(&ab->core_lock);
> -	ath12k_link_sta_rhash_tbl_destroy(ab);
>  	ath12k_thermal_unregister(ab);
>  	ath12k_dp_pdev_free(ab);
>  	ath12k_ce_cleanup_pipes(ab);
> diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h
> index 99d71dab7c04..bde3b4985023 100644
> --- a/drivers/net/wireless/ath/ath12k/core.h
> +++ b/drivers/net/wireless/ath/ath12k/core.h
> @@ -17,7 +17,7 @@
>  #include <linux/of_reserved_mem.h>
>  #include <linux/average.h>
>  #include <linux/of.h>
> -#include <linux/rhashtable.h>
> +#include <linux/hashtable.h>
>  #include "qmi.h"
>  #include "htc.h"
>  #include "wmi.h"
> @@ -510,8 +510,8 @@ struct ath12k_link_sta {
>  	 /* for firmware use only */
>  	u8 link_idx;
>  
> -	/* peer addr based rhashtable list pointer */
> -	struct rhash_head rhash_addr;
> +	/* node for ar->arsta_hlist, the peer addr keyed hash table */
> +	struct hlist_node hash_node;
>  };
>  
>  struct ath12k_sta {
> @@ -658,11 +658,21 @@ struct ath12k {
>  	/* protects the radio specific data like debug stats, ppdu_stats_info stats,
>  	 * vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info,
>  	 * channel context data, test mode data, regd_channel_update_queue,
> -	 * peer_delete_waits.
> +	 * peer_delete_waits, arsta hash table (arsta_hlist/arsta_hash_bits).
>  	 */
>  	spinlock_t data_lock;
>  
>  	struct list_head arvifs;
> +
> +	/* per-radio hash buckets for station address lookup */
> +	struct hlist_head *arsta_hlist;
> +
> +	/*
> +	 * Number of bits used to size arsta_hlist (arsta_hlist has
> +	 * BIT(arsta_hash_bits) buckets).
> +	 */
> +	u8 arsta_hash_bits;
> +
>  	/* should never be NULL; needed for regular htt rx */
>  	struct ieee80211_channel *rx_channel;
>  
> @@ -1149,9 +1159,6 @@ struct ath12k_base {
>  	 */
>  	const struct ieee80211_ops *ath12k_ops;
>  
> -	struct rhashtable *rhead_sta_addr;
> -	struct rhashtable_params rhash_sta_addr_param;
> -
>  	/* must be last */
>  	u8 drv_priv[] __aligned(sizeof(void *));
>  };
> diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c
> index 01f8c6572f65..d21b0b50853e 100644
> --- a/drivers/net/wireless/ath/ath12k/mac.c
> +++ b/drivers/net/wireless/ath/ath12k/mac.c
> @@ -1185,35 +1185,6 @@ static int ath12k_mac_set_kickout(struct ath12k_link_vif *arvif)
>  	return 0;
>  }
>  
> -static void ath12k_mac_link_sta_rhash_cleanup(void *data, struct ieee80211_sta *sta)
> -{
> -	u8 link_id;
> -	unsigned long links_map;
> -	struct ath12k_sta *ahsta;
> -	struct ath12k *ar = data;
> -	struct ath12k_link_sta *arsta;
> -	struct ath12k_link_vif *arvif;
> -	struct ath12k_base *ab = ar->ab;
> -
> -	ahsta = ath12k_sta_to_ahsta(sta);
> -	links_map = ahsta->links_map;
> -
> -	rcu_read_lock();
> -	for_each_set_bit(link_id, &links_map, IEEE80211_MLD_MAX_NUM_LINKS) {
> -		arsta = rcu_dereference(ahsta->link[link_id]);
> -		if (!arsta)
> -			continue;
> -		arvif = arsta->arvif;
> -		if (!(arvif->ar == ar))
> -			continue;
> -
> -		spin_lock_bh(&ab->base_lock);
> -		ath12k_link_sta_rhash_delete(ab, arsta);
> -		spin_unlock_bh(&ab->base_lock);
> -	}
> -	rcu_read_unlock();
> -}
> -
>  void ath12k_mac_peer_cleanup_all(struct ath12k *ar)
>  {
>  	struct ath12k_dp_link_peer *peer, *tmp;
> @@ -1262,9 +1233,10 @@ void ath12k_mac_peer_cleanup_all(struct ath12k *ar)
>  	ar->num_peers = 0;
>  	ar->num_stations = 0;
>  
> -	/* Cleanup rhash table maintained for arsta by iterating over sta */
> -	ieee80211_iterate_stations_mtx(ar->ah->hw, ath12k_mac_link_sta_rhash_cleanup,
> -				       ar);
> +	/* Cleanup address hash maintained for arsta on this radio */
> +	spin_lock_bh(&ar->data_lock);
> +	ath12k_link_sta_hlist_flush(ar);
> +	spin_unlock_bh(&ar->data_lock);
>  
>  	/* Delete all the self dp_peers on asserted radio */
>  	list_for_each_entry_safe_reverse(arvif, tmp_vif, &ar->arvifs, list) {
> @@ -7101,7 +7073,6 @@ static int ath12k_mac_station_remove(struct ath12k *ar,
>  	struct ieee80211_sta *sta = ath12k_ahsta_to_sta(arsta->ahsta);
>  	struct ath12k_vif *ahvif = arvif->ahvif;
>  	int ret = 0;
> -	struct ath12k_link_sta *temp_arsta;
>  
>  	lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
>  
> @@ -7130,14 +7101,9 @@ static int ath12k_mac_station_remove(struct ath12k *ar,
>  
>  	ath12k_mac_station_post_remove(ar, arvif, arsta);
>  
> -	spin_lock_bh(&ar->ab->base_lock);
> -
> -	/* To handle roaming and split phy scenario */
> -	temp_arsta = ath12k_link_sta_find_by_addr(ar->ab, arsta->addr);
> -	if (temp_arsta && temp_arsta->arvif->ar == ar)
> -		ath12k_link_sta_rhash_delete(ar->ab, arsta);
> -
> -	spin_unlock_bh(&ar->ab->base_lock);
> +	spin_lock_bh(&ar->data_lock);
> +	ath12k_link_sta_hlist_delete(ar, arsta);
> +	spin_unlock_bh(&ar->data_lock);
>  
>  	if (sta->valid_links)
>  		ath12k_mac_free_unassign_link_sta(ahvif->ah,
> @@ -7155,7 +7121,6 @@ static int ath12k_mac_station_add(struct ath12k *ar,
>  	struct ieee80211_sta *sta = ath12k_ahsta_to_sta(arsta->ahsta);
>  	struct ath12k_wmi_peer_create_arg peer_param = {};
>  	int ret;
> -	struct ath12k_link_sta *temp_arsta;
>  
>  	lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
>  
> @@ -7166,22 +7131,11 @@ static int ath12k_mac_station_add(struct ath12k *ar,
>  		goto exit;
>  	}
>  
> -	spin_lock_bh(&ab->base_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_sta is
> -	 * already present
> -	 */
> -	temp_arsta = ath12k_link_sta_find_by_addr(ab, arsta->addr);
> -	if (temp_arsta && temp_arsta->arvif->ar != ar)
> -		ath12k_link_sta_rhash_delete(ab, temp_arsta);
> -
> -	ret = ath12k_link_sta_rhash_add(ab, arsta);
> -	spin_unlock_bh(&ab->base_lock);
> +	spin_lock_bh(&ar->data_lock);
> +	ret = ath12k_link_sta_hlist_add(ar, arsta);
> +	spin_unlock_bh(&ar->data_lock);
>  	if (ret) {
> -		ath12k_warn(ab, "Failed to add arsta: %pM to hash table, ret: %d",
> +		ath12k_warn(ab, "failed to add %pM to hash table %d\n",

it is warning, so you can have colons before error code:

"failed to add %pM to hash table: %d\n"


with that fixed:

Reviewed-by: Baochen Qiang <baochen.qiang at oss.qualcomm.com>



More information about the ath12k mailing list