new file mode 100644
@@ -0,0 +1,438 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/string.h>
+
+#include "reg/reg_access.h"
+#include "sta.h"
+#include "utils.h"
+#include "reg/reg_defs.h"
+#include "rates.h"
+#include "debug.h"
+#include "tx.h"
+#include "vif.h"
+#include "stats.h"
+
+static void cll_stats_config_ps(struct cl_sta *cl_sta)
+{
+ struct sta_info *stainfo = IEEE80211_STA_TO_STAINFO(cl_sta->sta);
+
+ cl_sta->stats->ps.timestamp_sleep = jiffies;
+ cl_sta->stats->ps.is_ps = test_sta_flag(stainfo, WLAN_STA_PS_STA);
+}
+
+static void cl_stats_free(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ kfree(cl_sta->stats);
+ cl_sta->stats = NULL;
+}
+
+static void cl_stats_disable(struct cl_hw *cl_hw)
+{
+ pr_debug("Statistics disabled\n");
+ cl_hw->conf->ci_stats_en = false;
+ cl_sta_loop(cl_hw, cl_stats_free);
+
+ if (cl_hw_is_prod_or_listener(cl_hw)) {
+ kfree(cl_hw->rx_stats);
+ cl_hw->rx_stats = NULL;
+ }
+}
+
+static void _cl_stats_update_tx(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_agg_tx_report *agg_report)
+{
+ struct cl_stats *stats = cl_sta->stats;
+ struct cl_tx_cntrs *cntrs;
+ union cl_rate_ctrl_info rate_ctrl_info = {
+ .word = le32_to_cpu(agg_report->rate_cntrl_info)};
+ u8 bw, nss, mcs, gi, bf;
+
+ switch (rate_ctrl_info.field.format_mod) {
+ case WRS_MODE_HE:
+ nss = (rate_ctrl_info.field.mcs_index >> 4);
+ mcs = (rate_ctrl_info.field.mcs_index & 0xF);
+ gi = rate_ctrl_info.field.gi;
+
+ {
+ bw = rate_ctrl_info.field.bw;
+ bf = agg_report->bf;
+
+ cntrs = &stats->tx.he[bw][nss][mcs][gi][bf];
+ }
+ break;
+ case WRS_MODE_VHT:
+ bw = rate_ctrl_info.field.bw;
+ nss = (rate_ctrl_info.field.mcs_index >> 4);
+ mcs = (rate_ctrl_info.field.mcs_index & 0xF);
+ gi = rate_ctrl_info.field.gi;
+ bf = agg_report->bf;
+
+ cntrs = &stats->tx.vht[bw][nss][mcs][gi][bf];
+ break;
+ case WRS_MODE_HT:
+ bw = rate_ctrl_info.field.bw;
+ nss = (rate_ctrl_info.field.mcs_index >> 3);
+ mcs = (rate_ctrl_info.field.mcs_index & 0x7);
+ gi = rate_ctrl_info.field.gi;
+ cntrs = &stats->tx.ht[bw][nss][mcs][gi];
+ break;
+ case WRS_MODE_OFDM:
+ mcs = rate_ctrl_info.field.mcs_index - RATE_CTRL_OFFSET_OFDM;
+ cntrs = &stats->tx.ofdm[mcs];
+ break;
+ case WRS_MODE_CCK:
+ mcs = rate_ctrl_info.field.mcs_index;
+ cntrs = &stats->tx.cck[mcs];
+ break;
+ default:
+ return;
+ }
+
+ cntrs->success += agg_report->success;
+ cntrs->fail += agg_report->fail;
+ stats->tx.packet_success += agg_report->success;
+ stats->tx.packet_fail += agg_report->fail;
+}
+
+static void _cl_stats_update_rx_rate(struct cl_hw *cl_hw, struct cl_rx_stats *rx_stats,
+ struct hw_rxhdr *rxhdr)
+{
+ u8 bw, nss, mcs, gi;
+
+ switch (rxhdr->format_mod) {
+ case FORMATMOD_HE_TRIG:
+ nss = rxhdr->n_sts & 0x3;
+ mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+ gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+ rx_stats->he_trig[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_HE_TRIG;
+ break;
+ case FORMATMOD_HE_EXT:
+ nss = rxhdr->n_sts & 0x3;
+ mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+ gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+ rx_stats->he_ext[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_HE_EXT;
+ break;
+ case FORMATMOD_HE_MU:
+ nss = rxhdr->n_sts & 0x3;
+ mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+ gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+ rx_stats->he_mu[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_HE_MU;
+ break;
+ case FORMATMOD_HE_SU:
+ nss = rxhdr->n_sts & 0x3;
+ mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_HE);
+ gi = min_t(u8, rxhdr->gi_type, WRS_GI_MAX_HE);
+ rx_stats->he_su[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_HE_SU;
+ break;
+ case FORMATMOD_VHT:
+ nss = rxhdr->n_sts & 0x3;
+ mcs = min_t(u8, rxhdr->mcs, WRS_MCS_MAX_VHT);
+ gi = rxhdr->gi_type & 0x1;
+ rx_stats->vht[rxhdr->ch_bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_VHT;
+ break;
+ case FORMATMOD_HT_MF:
+ case FORMATMOD_HT_GF:
+ bw = rxhdr->ch_bw & 0x1;
+ nss = (rxhdr->mcs >> 3) & 0x3;
+ mcs = rxhdr->mcs & 0x7;
+ gi = rxhdr->gi_type & 0x1;
+ rx_stats->ht[bw][nss][mcs][gi] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_HT;
+ break;
+ case FORMATMOD_NON_HT:
+ if (rxhdr->mcs >= RATE_CTRL_OFFSET_OFDM) {
+ mcs = (rxhdr->mcs - RATE_CTRL_OFFSET_OFDM) & 0x7;
+ rx_stats->ofdm[mcs] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_OFDM;
+ } else if (cl_band_is_24g(cl_hw)) {
+ mcs = rxhdr->mcs & 0x3;
+ rx_stats->cck[mcs] += rxhdr->frm_successful_rx;
+ rx_stats->flag |= RX_STATS_CCK;
+ }
+ break;
+ }
+
+ rx_stats->packet_success += rxhdr->frm_successful_rx;
+}
+
+void cl_stats_init(struct cl_hw *cl_hw)
+{
+ spin_lock_init(&cl_hw->lock_stats);
+
+ if (cl_hw->conf->ci_stats_en && cl_hw_is_prod_or_listener(cl_hw)) {
+ cl_hw->rx_stats = kzalloc(sizeof(*cl_hw->rx_stats), GFP_ATOMIC);
+
+ if (!cl_hw->rx_stats)
+ cl_hw->conf->ci_stats_en = false;
+ }
+}
+
+void cl_stats_deinit(struct cl_hw *cl_hw)
+{
+ spin_lock_bh(&cl_hw->lock_stats);
+
+ if (cl_hw->conf->ci_stats_en && (cl_hw_is_prod_or_listener(cl_hw))) {
+ cl_hw->conf->ci_stats_en = false;
+
+ kfree(cl_hw->rx_stats);
+ cl_hw->rx_stats = NULL;
+ }
+
+ spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+void cl_stats_sta_add(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ /*
+ * If allocation failed disable ci_stats_en
+ * and free the memory of all other stations
+ */
+ bool disable = false;
+
+ if (cl_hw->conf->ci_stats_en) {
+ /*
+ * Take regular lock and not BH,
+ * because cl_sta_add_to_lut() already disables BH
+ */
+ spin_lock(&cl_hw->lock_stats);
+
+ cl_sta->stats = kzalloc(sizeof(*cl_sta->stats), GFP_ATOMIC);
+
+ if (cl_sta->stats)
+ cll_stats_config_ps(cl_sta);
+ else
+ disable = true;
+
+ spin_unlock(&cl_hw->lock_stats);
+ }
+
+ if (disable && cl_hw->conf->ci_stats_en) {
+ spin_lock_bh(&cl_hw->lock_stats);
+ cl_stats_disable(cl_hw);
+ spin_unlock_bh(&cl_hw->lock_stats);
+ }
+}
+
+void cl_stats_sta_remove(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock_bh(&cl_hw->lock_stats);
+
+ cl_stats_free(cl_hw, cl_sta);
+
+ spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_tx_agg(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_agg_tx_report *agg_report)
+{
+ struct cl_stats *stats = cl_sta->stats;
+
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock(&cl_hw->lock_stats);
+
+ stats->tx.agg_cntr++;
+ stats->tx.fail_cntr += agg_report->fail;
+ _cl_stats_update_tx(cl_hw, cl_sta, agg_report);
+
+ spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_tx_single(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_agg_tx_report *agg_report)
+{
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock(&cl_hw->lock_stats);
+
+ cl_sta->stats->tx.fail_cntr += agg_report->fail;
+ _cl_stats_update_tx(cl_hw, cl_sta, agg_report);
+
+ spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_rx_rssi(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ s8 rssi[MAX_ANTENNAS])
+{
+ int i;
+ s8 rx_rssi;
+
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock_bh(&cl_hw->lock_stats);
+
+ for (i = 0; i < cl_hw->num_antennas; i++) {
+ rx_rssi = rssi[i] * -1;
+
+ if (rx_rssi >= 0 && rx_rssi < RSSI_ARR_SIZE)
+ cl_sta->stats->rssi[rx_rssi][i]++;
+ }
+
+ spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_rx_rate(struct cl_hw *cl_hw, struct cl_sta *cl_sta, struct hw_rxhdr *rxhdr)
+{
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock(&cl_hw->lock_stats);
+
+ _cl_stats_update_rx_rate(cl_hw, &cl_sta->stats->rx, rxhdr);
+ cl_sta->stats->fec_coding[rxhdr->fec_coding]++;
+
+ spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_rx_rate_production(struct cl_hw *cl_hw, struct hw_rxhdr *rxhdr)
+{
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock(&cl_hw->lock_stats);
+
+ _cl_stats_update_rx_rate(cl_hw, cl_hw->rx_stats, rxhdr);
+
+ spin_unlock(&cl_hw->lock_stats);
+}
+
+void cl_stats_update_ps(struct cl_hw *cl_hw, struct cl_sta *cl_sta, bool is_ps)
+{
+ struct cl_ps_stats *ps;
+
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock_bh(&cl_hw->lock_stats);
+
+ ps = &cl_sta->stats->ps;
+
+ if (ps->is_ps == is_ps)
+ goto out;
+
+ ps->is_ps = is_ps;
+
+ if (is_ps) {
+ ps->timestamp_sleep = jiffies;
+ } else {
+ unsigned long sleep_time = jiffies_to_msecs(jiffies - ps->timestamp_sleep);
+
+ if (sleep_time <= 50)
+ ps->period[PS_PERIOD_50MS]++;
+ else if (sleep_time <= 100)
+ ps->period[PS_PERIOD_100MS]++;
+ else if (sleep_time <= 250)
+ ps->period[PS_PERIOD_250MS]++;
+ else if (sleep_time <= 500)
+ ps->period[PS_PERIOD_500MS]++;
+ else if (sleep_time <= 750)
+ ps->period[PS_PERIOD_750MS]++;
+ else if (sleep_time <= 1000)
+ ps->period[PS_PERIOD_1000MS]++;
+ else if (sleep_time <= 2000)
+ ps->period[PS_PERIOD_2000MS]++;
+ else if (sleep_time <= 5000)
+ ps->period[PS_PERIOD_5000MS]++;
+ else if (sleep_time <= 10000)
+ ps->period[PS_PERIOD_10000MS]++;
+ else
+ ps->period[PS_PERIOD_ABOVE]++;
+ }
+
+out:
+ spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+int cl_stats_get_rssi(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ struct cl_stats *stats = NULL;
+ u32 i = 0, j = 0;
+ u64 total_rssi = 0;
+ s8 avg_signal = 0;
+
+ if (!cl_hw->conf->ci_stats_en)
+ return 0;
+
+ u64 avg_rssi[MAX_ANTENNAS] = {0};
+ u64 sum_rssi[MAX_ANTENNAS] = {0};
+
+ spin_lock_bh(&cl_hw->lock_stats);
+
+ stats = cl_sta->stats;
+
+ if (!stats)
+ goto out;
+
+ for (i = 0; i < RSSI_ARR_SIZE; i++) {
+ total_rssi = 0;
+
+ for (j = 0; j < cl_hw->num_antennas; j++) {
+ sum_rssi[j] += stats->rssi[i][j];
+ avg_rssi[j] += i * stats->rssi[i][j];
+ }
+ }
+
+ for (j = 0; j < cl_hw->num_antennas; j++)
+ if (sum_rssi[j])
+ avg_rssi[j] = div64_u64(avg_rssi[j], sum_rssi[j]);
+
+ for (j = 0; j < cl_hw->num_antennas; j++)
+ total_rssi += avg_rssi[j];
+
+ avg_signal = -div64_u64(total_rssi, cl_hw->num_antennas);
+out:
+ spin_unlock_bh(&cl_hw->lock_stats);
+
+ return avg_signal;
+}
+
+void cl_stats_get_tx(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ u64 *total_tx_success, u64 *total_tx_fail)
+{
+ if (!cl_hw->conf->ci_stats_en)
+ return;
+
+ spin_lock_bh(&cl_hw->lock_stats);
+
+ if (!cl_sta->stats)
+ goto out;
+
+ *total_tx_success = cl_sta->stats->tx.packet_success;
+ *total_tx_fail = cl_sta->stats->tx.packet_fail;
+
+out:
+ spin_unlock_bh(&cl_hw->lock_stats);
+}
+
+u64 cl_stats_get_rx(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ u64 total_rx_packets = 0;
+
+ if (!cl_hw->conf->ci_stats_en)
+ return 0;
+
+ spin_lock_bh(&cl_hw->lock_stats);
+
+ if (!cl_sta->stats)
+ goto out;
+
+ total_rx_packets = cl_sta->stats->rx.packet_success;
+
+out:
+ spin_unlock_bh(&cl_hw->lock_stats);
+
+ return total_rx_packets;
+}
+