diff mbox series

[RFC,v1,182/256] cl8k: add sta.c

Message ID 20210617160223.160998-183-viktor.barna@celeno.com (mailing list archive)
State RFC
Delegated to: Kalle Valo
Headers show
Series wireless: cl8k driver for Celeno IEEE 802.11ax devices | expand

Commit Message

Viktor Barna June 17, 2021, 4:01 p.m. UTC
From: Viktor Barna <viktor.barna@celeno.com>

(Part of the split. Please, take a look at the cover letter for more
details).

Signed-off-by: Viktor Barna <viktor.barna@celeno.com>
---
 drivers/net/wireless/celeno/cl8k/sta.c | 536 +++++++++++++++++++++++++
 1 file changed, 536 insertions(+)
 create mode 100644 drivers/net/wireless/celeno/cl8k/sta.c

--
2.30.0
diff mbox series

Patch

diff --git a/drivers/net/wireless/celeno/cl8k/sta.c b/drivers/net/wireless/celeno/cl8k/sta.c
new file mode 100644
index 000000000000..625edd51cb93
--- /dev/null
+++ b/drivers/net/wireless/celeno/cl8k/sta.c
@@ -0,0 +1,536 @@ 
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "sta.h"
+#include "rssi.h"
+#include "bf.h"
+#include "stats.h"
+#include "wrs/wrs_api.h"
+#include "utils/utils.h"
+#include "band.h"
+#include "vns.h"
+#include "radio.h"
+#include "tx/tx_inject.h"
+#include "twt.h"
+#include "motion_sense.h"
+#include "mac_addr.h"
+#include "tx/baw.h"
+#include "recovery.h"
+#include "tx/tx_queue.h"
+#include "tx/single_cfm.h"
+#include "ext/dyn_mcast_rate.h"
+#include "ext/dyn_bcast_rate.h"
+#include "chip.h"
+#include "fw/msg_tx.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_stainfo(struct cl_hw *cl_hw, struct sta_info *stainfo)
+{
+       if (!cl_recovery_in_progress(cl_hw)) {
+
+               struct cl_sta *cl_sta = STA_INFO_TO_CL_STA(stainfo);
+
+               /* Reset all cl_sta structure */
+               memset(cl_sta, 0, sizeof(struct cl_sta));
+               cl_sta->stainfo = stainfo;
+               /*
+                * 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_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);
+
+       /*
+        * 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,
+                                cl_sta->stainfo->cur_max_bandwidth);
+       /* 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);
+       cl_dyn_bcast_rate_update_upon_assoc(cl_hw,
+                                           cl_sta->wrs_sta.su_params.tx_params.mcs,
+                                           cl_hw->cl_sta_db.num);
+
+       /* !!! Must be last !!! */
+       cl_sta_add_to_list(cl_hw, cl_sta);
+}
+
+static void cl_sta_disassociate(struct cl_hw *cl_hw, bool ap_only)
+{
+       struct cl_sta *cl_sta;
+       int sta_idx;
+       int cnt = 0;
+       int sta_num = cl_hw->cl_sta_db.num;
+
+       for (sta_idx = 0; ((sta_idx < CL_MAX_NUM_STA) && (cnt < sta_num)); sta_idx++) {
+               cl_sta = cl_hw->cl_sta_db.lut[sta_idx];
+               if (cl_sta) {
+                       cnt++;
+                       if (ap_only && cl_sta->cl_vif->vif->type != NL80211_IFTYPE_AP)
+                               continue;
+                       cfg80211_del_sta(cl_sta->cl_vif->dev, cl_sta->addr, GFP_ATOMIC);
+               }
+       }
+}
+
+/*
+ * 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(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(cl_hw->sband.ht_cap.ampdu_density), 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;
+}
+
+bool cl_sta_is_assoc(struct cl_hw *cl_hw, u8 sta_idx)
+{
+       bool is_assoc = false;
+
+       if (sta_idx < CL_MAX_NUM_STA) {
+               read_lock_bh(&cl_hw->cl_sta_db.lock);
+               is_assoc = cl_hw->cl_sta_db.lut[sta_idx] ? true : false;
+               read_unlock_bh(&cl_hw->cl_sta_db.lock);
+       }
+
+       return is_assoc;
+}
+
+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);
+}
+
+void cl_sta_loop_safe(struct cl_hw *cl_hw, sta_callback callback)
+{
+       struct cl_sta *cl_sta = NULL;
+       struct cl_sta *cl_sta_tmp = NULL;
+
+       /* Go over all stations */
+       read_lock(&cl_hw->cl_sta_db.lock);
+
+       list_for_each_entry_safe(cl_sta, cl_sta_tmp, &cl_hw->cl_sta_db.head, list)
+               callback(cl_hw, cl_sta);
+
+       read_unlock(&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_tx_params *tx_params = &cl_sta->wrs_sta.su_params.tx_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, tx_params->mode,
+                                                      tx_params->bw, tx_params->nss,
+                                                      tx_params->mcs, tx_params->gi,
+                                                      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);
+       }
+
+       /* 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;
+
+       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) {
+               cl_vif_ap_tx_enable(cl_hw, true);
+               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);
+
+       /* 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);
+       /* 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);
+       cl_dyn_bcast_rate_update_upon_assoc(cl_hw,
+                                           cl_sta->wrs_sta.su_params.tx_params.mcs,
+                                           cl_hw->cl_sta_db.num);
+
+       /* !!! 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;
+
+       /* !!! 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_dyn_mcast_rate_update_upon_disassoc(cl_hw,
+                                              cl_sta->wrs_sta.mode,
+                                              cl_hw->cl_sta_db.num);
+       cl_dyn_bcast_rate_update_upon_disassoc(cl_hw,
+                                              cl_sta->wrs_sta.su_params.tx_params.mcs,
+                                              cl_hw->cl_sta_db.num);
+       cl_wrs_api_sta_remove(cl_hw, cl_sta);
+       cl_tx_inject_sta_remove(cl_hw, cl_sta);
+       cl_twt_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) {
+               cl_sta_disassociate_ap_iface(cl_hw);
+               cl_vif_ap_tx_enable(cl_hw, false);
+               clear_bit(CL_DEV_MESH_AP, &cl_hw->drv_flags);
+       }
+
+       cl_msg_tx_sta_del(cl_hw, sta_idx);
+
+       cl_single_cfm_flush_sta(cl_hw, sta_idx);
+
+       if (cl_vif->num_sta == 0)
+               cl_radio_off_wake_up(cl_hw);
+}
+
+void cl_sta_disassociate_all(struct cl_hw *cl_hw)
+{
+       /* Disassociate all associated stations (AP + STA mode) */
+       cl_sta_disassociate(cl_hw, false);
+}
+
+void cl_sta_disassociate_ap_iface(struct cl_hw *cl_hw)
+{
+       /* Disassociate all AP associated stations (AP mode only) */
+       cl_sta_disassociate(cl_hw, true);
+}
+
+void cl_sta_ps_notify(struct cl_hw *cl_hw, struct cl_sta *cl_sta, bool is_ps)
+{
+       /*
+        * 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(cl_sta->stainfo, WLAN_STA_SP);
+
+       cl_stats_update_ps(cl_hw, cl_sta, is_ps);
+}
+