new file mode 100644
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include "chip.h"
+#include "phy.h"
+#include "bf.h"
+#include "vns.h"
+#include "tx.h"
+#include "radio.h"
+#include "motion_sense.h"
+#include "mac_addr.h"
+#include "recovery.h"
+#include "dfs.h"
+#include "stats.h"
+#include "utils.h"
+#include "sta.h"
+
+void cl_sta_init(struct cl_hw *cl_hw)
+{
+ u32 i;
+
+ rwlock_init(&cl_hw->cl_sta_db.lock);
+ INIT_LIST_HEAD(&cl_hw->cl_sta_db.head);
+
+ for (i = 0; i < CL_STA_HASH_SIZE; i++)
+ INIT_LIST_HEAD(&cl_hw->cl_sta_db.hash[i]);
+}
+
+void cl_sta_init_sta(struct cl_hw *cl_hw, struct ieee80211_sta *sta)
+{
+ if (!cl_recovery_in_progress(cl_hw)) {
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+
+ /* Reset all cl_sta strcture */
+ memset(cl_sta, 0, sizeof(struct cl_sta));
+ cl_sta->sta = sta;
+ /*
+ * Set sta_idx to 0xFF since FW expects this value as long as
+ * the STA is not fully connected
+ */
+ cl_sta->sta_idx = STA_IDX_INVALID;
+ }
+}
+
+static void cl_sta_add_to_lut(struct cl_hw *cl_hw, struct cl_vif *cl_vif, struct cl_sta *cl_sta)
+{
+ write_lock_bh(&cl_hw->cl_sta_db.lock);
+
+ cl_hw->cl_sta_db.num++;
+ cl_vif->num_sta++;
+ cl_hw->cl_sta_db.lut[cl_sta->sta_idx] = cl_sta;
+
+ /* Done here inside the lock because it allocates cl_stats */
+ cl_stats_sta_add(cl_hw, cl_sta);
+
+ write_unlock_bh(&cl_hw->cl_sta_db.lock);
+
+ cl_dbg_verbose(cl_hw, "mac=%pM, sta_idx=%u, vif_index=%u\n",
+ cl_sta->addr, cl_sta->sta_idx, cl_sta->cl_vif->vif_index);
+}
+
+static void cl_sta_add_to_list(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ u8 hash_idx = CL_STA_HASH_IDX(cl_sta->addr[5]);
+
+ write_lock_bh(&cl_hw->cl_sta_db.lock);
+
+ /* Add to hash table */
+ list_add(&cl_sta->list_hash, &cl_hw->cl_sta_db.hash[hash_idx]);
+
+ /* Make sure that cl_sta's are stored in the list according to their sta_idx. */
+ if (list_empty(&cl_hw->cl_sta_db.head)) {
+ list_add(&cl_sta->list, &cl_hw->cl_sta_db.head);
+ } else if (list_is_singular(&cl_hw->cl_sta_db.head)) {
+ struct cl_sta *cl_sta_singular =
+ list_first_entry(&cl_hw->cl_sta_db.head, struct cl_sta, list);
+
+ if (cl_sta_singular->sta_idx < cl_sta->sta_idx)
+ list_add_tail(&cl_sta->list, &cl_hw->cl_sta_db.head);
+ else
+ list_add(&cl_sta->list, &cl_hw->cl_sta_db.head);
+ } else {
+ struct cl_sta *cl_sta_last =
+ list_last_entry(&cl_hw->cl_sta_db.head, struct cl_sta, list);
+
+ if (cl_sta->sta_idx > cl_sta_last->sta_idx) {
+ list_add_tail(&cl_sta->list, &cl_hw->cl_sta_db.head);
+ } else {
+ struct cl_sta *cl_sta_next = NULL;
+ struct cl_sta *cl_sta_prev = NULL;
+
+ list_for_each_entry(cl_sta_next, &cl_hw->cl_sta_db.head, list) {
+ if (cl_sta_next->sta_idx < cl_sta->sta_idx)
+ continue;
+
+ cl_sta_prev = list_prev_entry(cl_sta_next, list);
+ __list_add(&cl_sta->list, &cl_sta_prev->list, &cl_sta_next->list);
+ break;
+ }
+ }
+ }
+
+ write_unlock_bh(&cl_hw->cl_sta_db.lock);
+
+ cl_sta->add_complete = true;
+}
+
+static void cl_connection_data_add(struct cl_vif *cl_vif)
+{
+ struct cl_connection_data *conn_data = cl_vif->conn_data;
+
+ if (cl_vif->num_sta > conn_data->max_client) {
+ conn_data->max_client = cl_vif->num_sta;
+ conn_data->max_client_timestamp = ktime_get_real_seconds();
+ }
+
+ if (cl_vif->num_sta == conn_data->watermark_threshold)
+ conn_data->watermark_reached_cnt++;
+}
+
+static void cl_connection_data_remove(struct cl_vif *cl_vif)
+{
+ struct cl_connection_data *conn_data = cl_vif->conn_data;
+
+ if (cl_vif->num_sta == conn_data->watermark_threshold)
+ conn_data->watermark_reached_cnt++;
+}
+
+static void _cl_sta_add(struct cl_hw *cl_hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta)
+{
+ struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+
+ /* !!! Must be first !!! */
+ cl_sta_add_to_lut(cl_hw, cl_vif, cl_sta);
+
+ cl_baw_init(cl_sta);
+ cl_txq_sta_add(cl_hw, cl_sta);
+ cl_vns_sta_add(cl_hw, cl_sta);
+ cl_connection_data_add(cl_vif);
+
+ /*
+ * Add rssi of association request to rssi pool
+ * Make sure to call it before cl_wrs_api_sta_add()
+ */
+ cl_rssi_assoc_find(cl_hw, cl_sta, cl_hw->cl_sta_db.num);
+
+ cl_motion_sense_sta_add(cl_hw, cl_sta);
+ cl_bf_sta_add(cl_hw, cl_sta, sta);
+ cl_wrs_api_sta_add(cl_hw, sta);
+ cl_wrs_api_set_smps_mode(cl_hw, sta, sta->bandwidth);
+#ifdef CONFIG_CL8K_DYN_MCAST_RATE
+ /* Should be called after cl_wrs_api_sta_add() */
+ cl_dyn_mcast_rate_update_upon_assoc(cl_hw, cl_sta->wrs_sta.mode,
+ cl_hw->cl_sta_db.num);
+#endif /* CONFIG_CL8K_DYN_MCAST_RATE */
+#ifdef CONFIG_CL8K_DYN_BCAST_RATE
+ cl_dyn_bcast_rate_update_upon_assoc(cl_hw,
+ cl_sta->wrs_sta.tx_su_params.rate_params.mcs,
+ cl_hw->cl_sta_db.num);
+#endif /* CONFIG_CL8K_DYN_BCAST_RATE */
+
+ /* !!! Must be last !!! */
+ cl_sta_add_to_list(cl_hw, cl_sta);
+}
+
+/*
+ * Parse the ampdu density to retrieve the value in usec, according to
+ * the values defined in ieee80211.h
+ */
+static u8 cl_sta_density2usec(u8 ampdu_density)
+{
+ switch (ampdu_density) {
+ case IEEE80211_HT_MPDU_DENSITY_NONE:
+ return 0;
+ /* 1 microsecond is our granularity */
+ case IEEE80211_HT_MPDU_DENSITY_0_25:
+ case IEEE80211_HT_MPDU_DENSITY_0_5:
+ case IEEE80211_HT_MPDU_DENSITY_1:
+ return 1;
+ case IEEE80211_HT_MPDU_DENSITY_2:
+ return 2;
+ case IEEE80211_HT_MPDU_DENSITY_4:
+ return 4;
+ case IEEE80211_HT_MPDU_DENSITY_8:
+ return 8;
+ case IEEE80211_HT_MPDU_DENSITY_16:
+ return 16;
+ default:
+ return 0;
+ }
+}
+
+static void cl_sta_set_min_spacing(struct cl_hw *cl_hw,
+ struct ieee80211_sta *sta)
+{
+ bool is_6g = cl_band_is_6g(cl_hw);
+ u8 sta_min_spacing = 0;
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+
+ if (is_6g)
+ sta_min_spacing =
+ cl_sta_density2usec(le16_get_bits(sta->he_6ghz_capa.capa,
+ IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START));
+ else if (sta->ht_cap.ht_supported)
+ sta_min_spacing =
+ cl_sta_density2usec(sta->ht_cap.ampdu_density);
+ else
+ cl_dbg_err(cl_hw, "HT is not supported - cannot set sta_min_spacing\n");
+
+ cl_sta->ampdu_min_spacing =
+ max(cl_sta_density2usec(IEEE80211_HT_MPDU_DENSITY_1), sta_min_spacing);
+}
+
+u32 cl_sta_num(struct cl_hw *cl_hw)
+{
+ u32 num = 0;
+
+ read_lock(&cl_hw->cl_sta_db.lock);
+ num = cl_hw->cl_sta_db.num;
+ read_unlock(&cl_hw->cl_sta_db.lock);
+
+ return num;
+}
+
+u32 cl_sta_num_bh(struct cl_hw *cl_hw)
+{
+ u32 num = 0;
+
+ read_lock_bh(&cl_hw->cl_sta_db.lock);
+ num = cl_hw->cl_sta_db.num;
+ read_unlock_bh(&cl_hw->cl_sta_db.lock);
+
+ return num;
+}
+
+struct cl_sta *cl_sta_get(struct cl_hw *cl_hw, u8 sta_idx)
+{
+ if (sta_idx < CL_MAX_NUM_STA)
+ return cl_hw->cl_sta_db.lut[sta_idx];
+
+ return NULL;
+}
+
+struct cl_sta *cl_sta_get_by_addr(struct cl_hw *cl_hw, u8 *addr)
+{
+ struct cl_sta *cl_sta = NULL;
+ u8 hash_idx = CL_STA_HASH_IDX(addr[5]);
+
+ if (is_multicast_ether_addr(addr))
+ return NULL;
+
+ list_for_each_entry(cl_sta, &cl_hw->cl_sta_db.hash[hash_idx], list_hash)
+ if (cl_mac_addr_compare(cl_sta->addr, addr))
+ return cl_sta;
+
+ return NULL;
+}
+
+void cl_sta_loop(struct cl_hw *cl_hw, sta_callback callback)
+{
+ struct cl_sta *cl_sta = NULL;
+
+ /* Go over all stations */
+ read_lock(&cl_hw->cl_sta_db.lock);
+
+ list_for_each_entry(cl_sta, &cl_hw->cl_sta_db.head, list)
+ callback(cl_hw, cl_sta);
+
+ read_unlock(&cl_hw->cl_sta_db.lock);
+}
+
+void cl_sta_loop_bh(struct cl_hw *cl_hw, sta_callback callback)
+{
+ struct cl_sta *cl_sta = NULL;
+
+ /* Go over all stations - use bottom-half lock */
+ read_lock_bh(&cl_hw->cl_sta_db.lock);
+
+ list_for_each_entry(cl_sta, &cl_hw->cl_sta_db.head, list)
+ callback(cl_hw, cl_sta);
+
+ read_unlock_bh(&cl_hw->cl_sta_db.lock);
+}
+
+static int cl_sta_add_to_firmware(struct cl_hw *cl_hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct cl_sta *cl_sta = (struct cl_sta *)sta->drv_priv;
+ struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+ struct mm_sta_add_cfm *sta_add_cfm;
+ int error = 0;
+ u8 recovery_sta_idx = 0;
+ u32 rate_ctrl_info = 0;
+
+ if (cl_recovery_in_progress(cl_hw)) {
+ struct cl_wrs_rate_params *rate_params = &cl_sta->wrs_sta.tx_su_params.rate_params;
+
+ /*
+ * If station is added to firmware during recovery, the driver passes to firmware
+ * the station index to be used instead of firmware selecting a free index
+ */
+ recovery_sta_idx = cl_sta->sta_idx;
+
+ /* Keep current rate value */
+ rate_ctrl_info = cl_rate_ctrl_generate(cl_hw, cl_sta, rate_params->mode,
+ rate_params->bw, rate_params->nss,
+ rate_params->mcs, rate_params->gi,
+ false, false);
+ } else {
+ bool is_cck = cl_band_is_24g(cl_hw) && cl_hw_mode_is_b_or_bg(cl_hw);
+ u8 mode = is_cck ? WRS_MODE_CCK : WRS_MODE_OFDM;
+
+ /*
+ * Not in recovery:
+ * firmware will set sta_idx and will return in confirmation message
+ */
+ recovery_sta_idx = STA_IDX_INVALID;
+
+ /* Default rate value */
+ rate_ctrl_info = cl_rate_ctrl_generate(cl_hw, cl_sta, mode,
+ 0, 0, 0, 0, false, false);
+ }
+
+ /* Must be called before cl_msg_tx_sta_add() */
+ cl_sta_set_min_spacing(cl_hw, sta);
+
+ /* Send message to firmware */
+ error = cl_msg_tx_sta_add(cl_hw, sta, cl_vif, recovery_sta_idx, rate_ctrl_info);
+ if (error)
+ return error;
+
+ sta_add_cfm = (struct mm_sta_add_cfm *)(cl_hw->msg_cfm_params[MM_STA_ADD_CFM]);
+ if (!sta_add_cfm)
+ return -ENOMSG;
+
+ if (sta_add_cfm->status != 0) {
+ cl_dbg_verbose(cl_hw, "Status Error (%u)\n", sta_add_cfm->status);
+ cl_msg_tx_free_cfm_params(cl_hw, MM_STA_ADD_CFM);
+ return -EIO;
+ }
+
+ /* Save the index retrieved from firmware */
+ cl_sta->sta_idx = sta_add_cfm->sta_idx;
+
+ /* Release cfm msg */
+ cl_msg_tx_free_cfm_params(cl_hw, MM_STA_ADD_CFM);
+
+ return 0;
+}
+
+int cl_sta_add(struct cl_hw *cl_hw, struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta)
+{
+ struct cl_sta *cl_sta = (struct cl_sta *)sta->drv_priv;
+ struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+ int error = 0;
+
+ if (cl_radio_is_going_down(cl_hw))
+ return -EPERM;
+
+ if (vif->type == NL80211_IFTYPE_MESH_POINT &&
+ cl_vif->num_sta >= CL_MAX_NUM_MESH_POINT)
+ return -EPERM;
+
+ cl_sta->cl_vif = cl_vif;
+ cl_mac_addr_copy(cl_sta->addr, sta->addr);
+
+ error = cl_sta_add_to_firmware(cl_hw, vif, sta);
+ if (error)
+ return error;
+
+ if (!cl_recovery_in_progress(cl_hw))
+ if (vif->type != NL80211_IFTYPE_STATION ||
+ cl_hw->chip->conf->ce_production_mode)
+ _cl_sta_add(cl_hw, vif, sta);
+
+ if (vif->type == NL80211_IFTYPE_MESH_POINT && cl_vif->num_sta == 1)
+ set_bit(CL_DEV_MESH_AP, &cl_hw->drv_flags);
+
+ return 0;
+}
+
+void cl_sta_mgd_add(struct cl_hw *cl_hw, struct cl_vif *cl_vif, struct ieee80211_sta *sta)
+{
+ /* Should be called in station mode */
+ struct cl_sta *cl_sta = (struct cl_sta *)sta->drv_priv;
+
+ /* !!! Must be first !!! */
+ cl_sta_add_to_lut(cl_hw, cl_vif, cl_sta);
+
+ cl_baw_init(cl_sta);
+ cl_txq_sta_add(cl_hw, cl_sta);
+ cl_vns_sta_add(cl_hw, cl_sta);
+
+ /*
+ * Add rssi of association response to rssi pool
+ * Make sure to call it before cl_wrs_api_sta_add()
+ */
+ cl_rssi_assoc_find(cl_hw, cl_sta, cl_hw->cl_sta_db.num);
+
+ cl_connection_data_add(cl_vif);
+
+ /* In station mode we assume that the AP we connect to is static */
+ cl_motion_sense_sta_add(cl_hw, cl_sta);
+ cl_bf_sta_add(cl_hw, cl_sta, sta);
+ cl_wrs_api_sta_add(cl_hw, sta);
+#ifdef CONFIG_CL8K_DYN_MCAST_RATE
+ /* Should be called after cl_wrs_api_sta_add() */
+ cl_dyn_mcast_rate_update_upon_assoc(cl_hw, cl_sta->wrs_sta.mode,
+ cl_hw->cl_sta_db.num);
+#endif /* CONFIG_CL8K_DYN_MCAST_RATE */
+#ifdef CONFIG_CL8K_DYN_BCAST_RATE
+ cl_dyn_bcast_rate_update_upon_assoc(cl_hw,
+ cl_sta->wrs_sta.tx_su_params.rate_params.mcs,
+ cl_hw->cl_sta_db.num);
+
+#endif /* CONFIG_CL8K_DYN_BCAST_RATE */
+ /* !!! Must be last !!! */
+ cl_sta_add_to_list(cl_hw, cl_sta);
+}
+
+static void _cl_sta_remove(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ write_lock_bh(&cl_hw->cl_sta_db.lock);
+
+ list_del(&cl_sta->list);
+ list_del(&cl_sta->list_hash);
+
+ cl_hw->cl_sta_db.lut[cl_sta->sta_idx] = NULL;
+ cl_hw->cl_sta_db.num--;
+ cl_sta->cl_vif->num_sta--;
+
+ cl_dbg_verbose(cl_hw, "mac=%pM, sta_idx=%u, vif_index=%u\n",
+ cl_sta->addr, cl_sta->sta_idx, cl_sta->cl_vif->vif_index);
+
+ write_unlock_bh(&cl_hw->cl_sta_db.lock);
+}
+
+void cl_sta_remove(struct cl_hw *cl_hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta)
+{
+ struct cl_vif *cl_vif = (struct cl_vif *)vif->drv_priv;
+ struct cl_sta *cl_sta = (struct cl_sta *)sta->drv_priv;
+ u8 sta_idx = cl_sta->sta_idx;
+
+ cl_sta->remove_start = true;
+
+ /* !!! Must be first - remove from list and LUT !!! */
+ _cl_sta_remove(cl_hw, cl_sta);
+
+ cl_traffic_sta_remove(cl_hw, cl_sta);
+ cl_bf_sta_remove(cl_hw, cl_sta);
+ cl_connection_data_remove(cl_vif);
+#ifdef CONFIG_CL8K_DYN_MCAST_RATE
+ cl_dyn_mcast_rate_update_upon_disassoc(cl_hw,
+ cl_sta->wrs_sta.mode,
+ cl_hw->cl_sta_db.num);
+#endif /* CONFIG_CL8K_DYN_MCAST_RATE */
+#ifdef CONFIG_CL8K_DYN_BCAST_RATE
+ cl_dyn_bcast_rate_update_upon_disassoc(cl_hw,
+ cl_sta->wrs_sta.tx_su_params.rate_params.mcs,
+ cl_hw->cl_sta_db.num);
+#endif /* CONFIG_CL8K_DYN_BCAST_RATE */
+ cl_wrs_api_sta_remove(cl_hw, cl_sta);
+ cl_stats_sta_remove(cl_hw, cl_sta);
+
+ /*
+ * TX stop flow:
+ * 1) Flush TX queues
+ * 2) Poll confirmation queue and clear enhanced TIM
+ * 3) Send MM_STA_DEL_REQ message to firmware
+ * 4) Flush confirmation queue
+ * 5) Reset write index
+ */
+
+ cl_txq_flush_sta(cl_hw, cl_sta);
+ cl_single_cfm_poll_empty_sta(cl_hw, sta_idx);
+ cl_txq_sta_remove(cl_hw, sta_idx);
+
+ if (cl_vif->vif->type == NL80211_IFTYPE_MESH_POINT &&
+ cl_vif->num_sta == 0) {
+ clear_bit(CL_DEV_MESH_AP, &cl_hw->drv_flags);
+ }
+
+ cl_single_cfm_flush_sta(cl_hw, sta_idx);
+
+ cl_msg_tx_sta_del(cl_hw, sta_idx);
+
+ if (cl_vif->num_sta == 0)
+ cl_radio_off_wake_up(cl_hw);
+}
+
+void cl_sta_ps_notify(struct cl_hw *cl_hw, struct cl_sta *cl_sta, bool is_ps)
+{
+ struct sta_info *stainfo = IEEE80211_STA_TO_STAINFO(cl_sta->sta);
+
+ /*
+ * PS-Poll & UAPSD are handled by FW, by setting
+ * WLAN_STA_SP we ensure mac80211 does not re-handle.
+ * flag is unset at ieee80211_sta_ps_deliver_wakeup
+ */
+ if (is_ps)
+ set_sta_flag(stainfo, WLAN_STA_SP);
+
+ cl_stats_update_ps(cl_hw, cl_sta, is_ps);
+}
+