new file mode 100644
@@ -0,0 +1,3323 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/kernel.h>
+
+#include "rates.h"
+#include "chip.h"
+#include "utils.h"
+#include "hw.h"
+#include "reg/reg_defs.h"
+#include "debug.h"
+#include "radio.h"
+#include "wrs.h"
+
+#define wrs_pr(wrs_db, level, ...) \
+ do { \
+ if ((level) <= (wrs_db)->debug_level) \
+ pr_debug("[WRS]" __VA_ARGS__); \
+ } while (0)
+
+#define wrs_pr_verbose(wrs_db, ...) wrs_pr(wrs_db, DBG_LVL_VERBOSE, ##__VA_ARGS__)
+#define wrs_pr_err(wrs_db, ...) wrs_pr(wrs_db, DBG_LVL_ERROR, ##__VA_ARGS__)
+#define wrs_pr_warn(wrs_db, ...) wrs_pr(wrs_db, DBG_LVL_WARNING, ##__VA_ARGS__)
+#define wrs_pr_trace(wrs_db, ...) wrs_pr(wrs_db, DBG_LVL_TRACE, ##__VA_ARGS__)
+#define wrs_pr_info(wrs_db, ...) wrs_pr(wrs_db, DBG_LVL_INFO, ##__VA_ARGS__)
+
+#define WRS_LOGGER wrs_params->logger[wrs_params->logger_idx]
+
+static void cl_wrs_reset_params_cntrs(struct cl_wrs_params *wrs_params)
+{
+ wrs_params->frames_total = 0;
+ wrs_params->fail_total = 0;
+ wrs_params->ba_not_rcv_total = 0;
+ wrs_params->epr_acc = 0;
+ wrs_params->up_same_time_cnt = 0;
+ wrs_params->down_time_cnt = 0;
+}
+
+static bool cl_wrs_down_epr_check(struct cl_wrs_db *wrs_db, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params, u8 drop_factor,
+ enum cl_wrs_decision decision)
+{
+ u16 curr_rate_idx = wrs_params->rate_idx;
+ struct cl_wrs_table *curr_rate = &wrs_params->table[curr_rate_idx];
+ u64 curr_epr_acc = curr_rate->epr_acc;
+ u32 curr_total = curr_rate->frames_total;
+ u16 down_rate_idx = curr_rate->rate_down.rate_idx;
+ struct cl_wrs_table *down_rate = &wrs_params->table[down_rate_idx];
+ u64 down_epr_acc = down_rate->epr_acc;
+ u32 down_total = down_rate->frames_total;
+ u16 down_data_rate = 0;
+ u64 condition1 = 0, condition2 = 0;
+ bool down_decision = false, allow_penalty = true;
+
+ if (wrs_params->calc_ba_not_rcv) {
+ curr_total += curr_rate->ba_not_rcv_total;
+ down_total += down_rate->ba_not_rcv_total;
+ }
+
+ /*
+ * In the EPR of down candidate is better than or equal to current EPR => return true
+ *
+ * (1) curr_epr <= down_epr * factor(%)
+ *
+ * curr_epr_acc down_epr_acc factor
+ * (2) -------------- <= -------------- * --------
+ * curr_total down_total 100
+ *
+ * (3) curr_epr_acc * down_total * 100 <= down_epr_acc * curr_total * factor
+ *
+ * (4) conditation1 <= conditation2
+ * down_epr_acc
+ * If (down_total == 0) we use down_data_rate instead of: --------------
+ * down_total
+ */
+ if (down_total) {
+ condition1 = curr_epr_acc * down_total * 100;
+ condition2 = down_epr_acc * curr_total * drop_factor;
+ } else {
+ down_data_rate = cl_data_rates_get_x10(wrs_params->rate_params.mode,
+ down_rate->rate.bw,
+ down_rate->rate.nss,
+ down_rate->rate.mcs,
+ down_rate->rate.gi);
+
+ condition1 = curr_epr_acc * 100;
+ condition2 = (u64)down_data_rate * curr_total * drop_factor;
+ allow_penalty = false;
+ }
+
+ wrs_params->penalty_decision_dn = wrs_db->step_down;
+
+ if (condition2 && condition1 <= condition2) {
+ down_decision = true;
+
+ if (allow_penalty) {
+ /*
+ * The penalty is calculated as follow:
+ *
+ * penalty = MAX_STEP * penalty_factor
+ * epr_curr
+ * penalty = MAX_STEP * (100% - 100% * ----------)
+ * epr_down
+ *
+ * conditation1
+ * penalty = MAX_STEP * (100% - 100% --------------)
+ * conditation2
+ */
+
+ u64 penalty_factor = 100 - div64_u64(condition1 * 100, condition2);
+ u16 max_step = wrs_db->time_th_max_up - wrs_db->step_down;
+
+ wrs_params->penalty_decision_dn +=
+ div64_u64(max_step * penalty_factor, 100);
+ }
+
+ if (decision != WRS_DECISION_SAME)
+ wrs_pr_info(wrs_db,
+ "[%s] EPR check: sta = %u, pkt_curr = %u, "
+ "pkt_down = %u, epr_curr = %llu, epr_down * %u%% = %llu, "
+ "penalty = %u\n",
+ WRS_TYPE_STR(wrs_params->type),
+ wrs_sta->sta_idx,
+ curr_total,
+ down_total,
+ div64_u64(curr_epr_acc, curr_total * 10),
+ drop_factor,
+ down_total ?
+ div64_u64(down_epr_acc * drop_factor, down_total * 1000) :
+ (down_data_rate / 10),
+ wrs_params->penalty_decision_dn);
+ }
+
+ if (wrs_params->is_logger_en && down_decision) {
+ WRS_LOGGER.curr_epr = (u16)div64_u64(curr_epr_acc, 10 * curr_total);
+ WRS_LOGGER.down_epr = down_total ?
+ (u16)div64_u64(down_epr_acc, down_total * 10) : (down_data_rate / 10),
+ WRS_LOGGER.down_epr_factorized = WRS_LOGGER.down_epr * drop_factor / 100;
+ WRS_LOGGER.penalty = wrs_params->penalty_decision_dn;
+ }
+
+ return down_decision;
+}
+
+static void cl_wrs_time_thr_max_handler(struct cl_wrs_db *wrs_db,
+ struct cl_wrs_table *table, u8 up_idx)
+{
+ /*
+ * Check if there are at least two UP rates,
+ * and all UP rates reached max time threshold
+ */
+ u8 i = 0;
+ u8 time_th_max = 0;
+
+ for (i = 0; i < WRS_TABLE_NODE_UP_MAX; i++) {
+ if (table->rate_up[i].rate_idx == WRS_INVALID_RATE)
+ continue;
+
+ if (table->rate_up[i].time_th != wrs_db->time_th_max_up)
+ return;
+
+ time_th_max++;
+ }
+
+ if (time_th_max < 2)
+ return;
+
+ /* Find the next max rate, and decrease its time threshold by 1 */
+ i = 0;
+ while (i < WRS_TABLE_NODE_UP_MAX) {
+ up_idx++;
+ if (up_idx == WRS_TABLE_NODE_UP_MAX)
+ up_idx = WRS_TABLE_NODE_UP_MCS;
+
+ if (table->rate_up[up_idx].rate_idx != WRS_INVALID_RATE) {
+ /*
+ * If all up rates reached max time threshold,the first up
+ * rate will always be selected.
+ * To overcome it, we decrease the time threshold of the next
+ * up rate by 1 (so it will be samller and selected next time)
+ */
+ table->rate_up[up_idx].time_th--;
+ break;
+ }
+
+ i++;
+ }
+}
+
+static bool cl_wrs_find_up_candidate(struct cl_wrs_db *wrs_db, struct cl_wrs_params *wrs_params,
+ u16 *up_rate_idx, u32 *up_time_th)
+{
+ bool up_rate_valid = false;
+ u8 up_idx = 0;
+ u8 up_candidate = 0;
+ u16 rate_idx = 0;
+ struct cl_wrs_table *table = &wrs_params->table[wrs_params->rate_idx];
+
+ *up_rate_idx = WRS_INVALID_RATE;
+ *up_time_th = U32_MAX;
+
+ for (up_idx = 0; up_idx < WRS_TABLE_NODE_UP_MAX; up_idx++) {
+ rate_idx = table->rate_up[up_idx].rate_idx;
+
+ if (rate_idx == WRS_INVALID_RATE)
+ continue;
+
+ if (wrs_db->quick_up_en && table->rate_up[up_idx].quick_up_check) {
+ *up_rate_idx = rate_idx;
+ *up_time_th = wrs_db->quick_up_interval;
+ up_rate_valid = true;
+ up_candidate = up_idx;
+ break;
+ } else if (table->rate_up[up_idx].time_th < *up_time_th) {
+ *up_rate_idx = rate_idx;
+ *up_time_th = table->rate_up[up_idx].time_th;
+ up_rate_valid = true;
+ up_candidate = up_idx;
+ }
+ }
+
+ if (wrs_db->time_th_max_up == *up_time_th)
+ cl_wrs_time_thr_max_handler(wrs_db, table, up_candidate);
+
+ return up_rate_valid;
+}
+
+static bool cl_wrs_epr_immeidate_down(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params,
+ u16 down_rate_idx)
+{
+ if (cl_wrs_down_epr_check(wrs_db, wrs_sta, wrs_params,
+ wrs_db->immediate_drop_epr_factor,
+ WRS_DECISION_DOWN_IMMEDIATE)) {
+ /*
+ * If there are several immediate drops in a row ignore them,
+ * because it is probably not realted to bad TX rate
+ */
+ wrs_params->immediate_drop_cntr++;
+
+ if (wrs_params->immediate_drop_cntr > wrs_db->immediate_drop_max_in_row) {
+ wrs_params->immediate_drop_ignore++;
+
+ cl_wrs_tables_reset(wrs_db, wrs_sta, wrs_params);
+ cl_wrs_reset_params_cntrs(wrs_params);
+
+ wrs_pr_info(wrs_db,
+ "[%s] sta %u - ignore immediate down decision (cntr=%u)\n",
+ WRS_TYPE_STR(wrs_params->type), wrs_sta->sta_idx,
+ wrs_params->immediate_drop_cntr);
+ return true;
+ }
+
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_DOWN_IMMEDIATE, down_rate_idx);
+ return true;
+ }
+
+ return false;
+}
+
+static void cl_wrs_decision_up(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params,
+ u16 up_rate_idx, u32 up_th)
+{
+ enum cl_wrs_decision up_decision = (up_th == wrs_db->quick_up_interval) ?
+ WRS_DECISION_UP_QUICK : WRS_DECISION_UP;
+
+ if (wrs_params->is_logger_en)
+ WRS_LOGGER.up_time = wrs_params->up_same_time_cnt;
+
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params, up_decision, up_rate_idx);
+}
+
+static void cl_wrs_decision_same(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params,
+ u16 rate_idx)
+{
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params, WRS_DECISION_SAME, rate_idx);
+}
+
+static void cl_wrs_epr_decision(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params)
+{
+ u16 curr_rate_idx = wrs_params->rate_idx;
+ struct cl_wrs_table *table = &wrs_params->table[curr_rate_idx];
+ u16 down_rate_idx = table->rate_down.rate_idx;
+ u16 up_rate_idx = 0;
+ u16 down_th = table->rate_down.time_th;
+ u32 up_th = 0;
+ bool up_rate_valid = false;
+
+ /* Check if we transmitted enough frames for taking decision */
+ if ((wrs_params->frames_total + wrs_params->ba_not_rcv_total) <
+ wrs_db->min_frames_for_decision)
+ return;
+
+ if (wrs_params->is_logger_en) {
+ WRS_LOGGER.timestamp = jiffies_to_msecs(jiffies);
+ WRS_LOGGER.rate_idx = wrs_params->rate_idx;
+ WRS_LOGGER.success = wrs_params->frames_total - wrs_params->fail_total;
+ WRS_LOGGER.fail = wrs_params->fail_total;
+ WRS_LOGGER.ba_not_rcv = wrs_params->ba_not_rcv_total;
+ }
+
+ up_rate_valid = cl_wrs_find_up_candidate(wrs_db, wrs_params, &up_rate_idx, &up_th);
+
+ /* RSSI protect */
+ if (!WRS_TYPE_IS_TX_MU_MIMO(wrs_params) && wrs_db->rssi_protect_en)
+ if (cl_wrs_rssi_prot_decision(cl_hw, wrs_db, wrs_sta, wrs_params,
+ up_rate_valid, up_rate_idx, down_rate_idx))
+ return;
+
+ if (down_rate_idx != curr_rate_idx) {
+ /* Down immediate */
+ if (wrs_db->immediate_drop_en)
+ if (cl_wrs_epr_immeidate_down(cl_hw, wrs_db, wrs_sta,
+ wrs_params, down_rate_idx))
+ return;
+
+ /* Down */
+ if (wrs_params->down_time_cnt >= down_th) {
+ if (cl_wrs_down_epr_check(wrs_db, wrs_sta, wrs_params,
+ wrs_db->epr_factor, WRS_DECISION_DOWN)) {
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_DOWN, down_rate_idx);
+ return;
+ }
+
+ wrs_params->down_time_cnt = 0;
+ }
+ }
+
+ /* Up-same */
+ if (wrs_params->up_same_time_cnt >= up_th) {
+ if (up_rate_valid)
+ cl_wrs_decision_up(cl_hw, wrs_db, wrs_sta, wrs_params, up_rate_idx, up_th);
+ else
+ cl_wrs_decision_same(cl_hw, wrs_db, wrs_sta, wrs_params, curr_rate_idx);
+
+ return;
+ }
+
+ /*
+ * If there is no valid UP rate and the EPR is more
+ * than EPR down threshold => make a same decision
+ */
+ if (!up_rate_valid &&
+ !cl_wrs_down_epr_check(wrs_db, wrs_sta, wrs_params,
+ wrs_db->epr_factor, WRS_DECISION_SAME))
+ cl_wrs_decision_same(cl_hw, wrs_db, wrs_sta, wrs_params, curr_rate_idx);
+}
+
+static void cl_wrs_divide_weights_by_two(struct cl_wrs_table *table_node)
+{
+ u8 up_idx = 0;
+ struct cl_wrs_table_node *rate_up;
+
+ /*
+ * Converge weights - divide all weights by 2
+ * (make sure they do not go below their init value)
+ */
+ if (table_node->rate_down.rate_idx != WRS_INVALID_RATE)
+ table_node->rate_down.time_th = max(table_node->rate_down.time_th >> 1,
+ WRS_INIT_MSEC_WEIGHT_DOWN);
+
+ for (up_idx = 0; up_idx < WRS_TABLE_NODE_UP_MAX; up_idx++) {
+ rate_up = &table_node->rate_up[up_idx];
+
+ if (rate_up->rate_idx != WRS_INVALID_RATE)
+ rate_up->time_th = max(rate_up->time_th >> 1, WRS_INIT_MSEC_WEIGHT_UP);
+
+ if (rate_up->time_th == WRS_INIT_MSEC_WEIGHT_UP)
+ rate_up->quick_up_check = false;
+ }
+}
+
+static void cl_wrs_converge_weights(struct cl_wrs_params *wrs_params)
+{
+ /*
+ * Converge weights - divide the weights by 2 (except for the current rate),
+ * and reset PER counters (except for current rate, down rate, and down-down rate).
+ */
+ u16 i;
+ u16 curr_idx = wrs_params->rate_idx;
+ u16 down_idx = wrs_params->table[curr_idx].rate_down.rate_idx;
+ u16 down2_idx = wrs_params->table[down_idx].rate_down.rate_idx;
+
+ for (i = 0; i < wrs_params->table_size; i++) {
+ if (i == curr_idx)
+ continue;
+
+ cl_wrs_divide_weights_by_two(&wrs_params->table[i]);
+
+ if (i != down_idx && i != down2_idx) {
+ wrs_params->table[i].frames_total = 0;
+ wrs_params->table[i].ba_not_rcv_total = 0;
+ wrs_params->table[i].epr_acc = 0;
+ }
+ }
+}
+
+static void cl_wrs_converge_weights_idle_decision(struct cl_hw *cl_hw,
+ struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ /*
+ * Continue normal converge (just like during traffic).
+ * After 6 seconds reset table, and select rate based on RSSI.
+ */
+ if (!wrs_db->converge_idle_en)
+ return;
+
+ wrs_params->converge_time_idle += wrs_db->interval;
+
+ if (wrs_params->converge_mode == WRS_CONVERGE_MODE_RESET) {
+ if (wrs_params->converge_time_idle < wrs_db->converge_idle_interval_reset) {
+ cl_wrs_converge_weights(wrs_params);
+ } else {
+ wrs_params->converge_mode = WRS_CONVERGE_MODE_RSSI;
+ wrs_params->converge_time_idle = 0;
+
+ wrs_pr_info(wrs_db, "[%s] Converge weights: sta %u - RSSI\n",
+ WRS_TYPE_STR(wrs_params->type), wrs_sta->sta_idx);
+
+ /* Reset table and choose new rate based on RSSI */
+ cl_wrs_tables_reset(wrs_db, wrs_sta, wrs_params);
+
+ if (!WRS_TYPE_IS_TX_MU_MIMO(wrs_params))
+ cl_wrs_set_rate_idle(cl_hw, wrs_db, wrs_sta, wrs_params);
+ }
+ } else {
+ if (wrs_params->converge_time_idle < wrs_db->converge_idle_interval_rssi)
+ return;
+
+ /* Choose new rate */
+ if (!WRS_TYPE_IS_TX_MU_MIMO(wrs_params)) {
+ wrs_params->converge_time_idle = 0;
+ cl_wrs_set_rate_idle(cl_hw, wrs_db, wrs_sta, wrs_params);
+ }
+ }
+}
+
+static void cl_wrs_converge_weights_idle_reset(struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ /* There was traffic in last maintenance interval - reset converge parameteres */
+ wrs_params->converge_time_idle = 0;
+
+ if (wrs_params->converge_mode != WRS_CONVERGE_MODE_RESET) {
+ wrs_params->converge_mode = WRS_CONVERGE_MODE_RESET;
+ wrs_pr_info(wrs_db, "[%s] Converge weights: sta %u - RESET\n",
+ WRS_TYPE_STR(wrs_params->type), wrs_sta->sta_idx);
+ }
+}
+
+static void cl_wrs_converge_weights_trfc_decision(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_wrs_db *wrs_db,
+ struct cl_wrs_params *wrs_params)
+{
+ u32 converge_interval = 0;
+
+ if (!wrs_db->converge_trfc_en)
+ return;
+
+ converge_interval = wrs_db->converge_trfc_interval_static;
+
+ if (!cl_motion_sense_is_static(cl_hw, cl_sta))
+ converge_interval = wrs_db->converge_trfc_interval_motion;
+
+ wrs_params->converge_time_trfc += wrs_db->interval;
+
+ if (wrs_params->converge_time_trfc >= converge_interval) {
+ wrs_params->converge_time_trfc = 0;
+ cl_wrs_converge_weights(wrs_params);
+ }
+}
+
+static u32 cl_wrs_get_sync_attempts(struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params)
+{
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+ struct cl_wrs_info *wrs_info = cl_wrs_info_get(cl_sta, wrs_params->type);
+
+ return wrs_info->sync_attempts;
+}
+
+static void cl_wrs_sta_no_sync_handler(struct cl_hw *cl_hw,
+ struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ unsigned long time_delta = jiffies_to_msecs(jiffies - wrs_params->no_sync_timestamp);
+
+ if (time_delta < wrs_db->sync_timeout)
+ return;
+
+ if (cl_wrs_get_sync_attempts(wrs_sta, wrs_params) < wrs_db->sync_min_attempts) {
+ /*
+ * Rate not synced but there is also hardly no traffic -
+ * change mode to synced!
+ */
+ wrs_params->sync = true;
+ wrs_params->sync_timestamp = jiffies;
+ } else {
+ struct cl_wrs_table *wrs_table = &wrs_params->table[wrs_params->rate_idx];
+ struct cl_wrs_rate *curr_rate = &wrs_table->rate;
+
+ if (IS_REAL_PHY(cl_hw->chip))
+ wrs_pr_warn(wrs_db,
+ "[%s] NO SYNC - sta = %u, bw = %u, nss = %u, mcs = %u, gi = %u\n",
+ WRS_TYPE_STR(wrs_params->type), wrs_sta->sta_idx, curr_rate->bw,
+ curr_rate->nss, curr_rate->mcs, curr_rate->gi);
+
+ if (WRS_IS_DECISION_UP(wrs_params->last_decision)) {
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_DOWN_NO_SYNC,
+ wrs_table->rate_down.rate_idx);
+ } else {
+ /* If the last decision was DOWN - change state to SYNCED. */
+ wrs_params->sync = true;
+ wrs_params->sync_timestamp = jiffies;
+ }
+ }
+}
+
+static void cl_wrs_update_ba_not_rcv(struct cl_wrs_db *wrs_db, struct cl_wrs_params *wrs_params)
+{
+ unsigned long time_since_sync = jiffies_to_msecs(jiffies - wrs_params->sync_timestamp);
+
+ wrs_params->calc_ba_not_rcv = (wrs_db->ba_not_rcv_force ||
+ (time_since_sync < wrs_db->ba_not_rcv_time_since_sync));
+}
+
+static void _cl_wrs_cntrs_reset(struct cl_wrs_info *wrs_info)
+{
+ wrs_info->epr_acc = 0;
+ wrs_info->success = 0;
+ wrs_info->fail = 0;
+ wrs_info->ba_not_rcv = 0;
+ wrs_info->ba_not_rcv_consecutive_max = 0;
+}
+
+static void cl_wrs_cntrs_read(struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_cntrs *cntrs,
+ u8 type)
+{
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+ struct cl_wrs_info *wrs_info = cl_wrs_info_get(cl_sta, type);
+
+ cntrs->epr_acc = wrs_info->epr_acc;
+ cntrs->total = wrs_info->success + wrs_info->fail;
+ cntrs->fail = wrs_info->fail;
+ cntrs->ba_not_rcv = wrs_info->ba_not_rcv;
+ cntrs->ba_not_rcv_consecutive = wrs_info->ba_not_rcv_consecutive_max;
+
+ _cl_wrs_cntrs_reset(wrs_info);
+}
+
+static void _cl_wrs_sta_maintenance(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ struct cl_wrs_cntrs cntrs = {0};
+
+ if (wrs_params->is_logger_en)
+ memset(&WRS_LOGGER, 0, sizeof(struct cl_wrs_logger));
+
+ if (!wrs_params->sync) {
+ cl_wrs_sta_no_sync_handler(cl_hw, wrs_db, wrs_sta, wrs_params);
+ goto end_logger;
+ }
+
+ if (!WRS_TYPE_IS_RX(wrs_params))
+ cl_wrs_update_ba_not_rcv(wrs_db, wrs_params);
+
+ cl_wrs_cntrs_read(wrs_sta, &cntrs, wrs_params->type);
+
+ if (wrs_params->is_fixed_rate) {
+ cl_wrs_stats_per_update(wrs_db, wrs_sta, wrs_params, &cntrs);
+ goto end_logger;
+ }
+
+ wrs_params->down_time_cnt += wrs_db->interval;
+ wrs_params->up_same_time_cnt += wrs_db->interval;
+
+ if ((cntrs.total + cntrs.ba_not_rcv) < wrs_db->converge_idle_packet_th) {
+ /*
+ * Very few frames were sent in last maintenance interval
+ * Check if weights should be converged
+ */
+ cl_wrs_converge_weights_idle_decision(cl_hw, wrs_db, wrs_sta, wrs_params);
+
+ cl_wrs_stats_per_update(wrs_db, wrs_sta, wrs_params, &cntrs);
+
+ goto end_logger;
+ } else {
+ /* There was traffic in last maintenance interval - reset converge parameteres */
+ cl_wrs_converge_weights_idle_reset(wrs_db, wrs_sta, wrs_params);
+ }
+
+ cl_wrs_stats_per_update(wrs_db, wrs_sta, wrs_params, &cntrs);
+
+ wrs_params->quick_up_check =
+ (cntrs.ba_not_rcv_consecutive >= wrs_db->quick_up_ba_thr);
+ cl_wrs_epr_decision(cl_hw, wrs_db, wrs_sta, wrs_params);
+
+ cl_wrs_converge_weights_trfc_decision(cl_hw, cl_sta, wrs_db, wrs_params);
+
+end_logger:
+ if (wrs_params->is_logger_en && WRS_LOGGER.decision != WRS_DECISION_NONE)
+ wrs_params->logger_idx = WRS_INC_POW2(wrs_params->logger_idx,
+ wrs_params->logger_size);
+}
+
+static void cl_wrs_sta_maintenance(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ _cl_wrs_sta_maintenance(cl_hw, cl_sta, &cl_sta->wrs_sta.tx_su_params);
+
+ if (cl_sta->wrs_sta.rx_params)
+ _cl_wrs_sta_maintenance(cl_hw, cl_sta, cl_sta->wrs_sta.rx_params);
+}
+
+static void cl_wrs_cca_calc(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db, u8 max_bw)
+{
+ u32 cca_primary_new = mac_hw_edca_cca_busy_get(cl_hw);
+ u32 cca_sec80_new = (max_bw > CHNL_BW_80) ? mac_hw_add_cca_busy_sec_80_get(cl_hw) : 0;
+ u32 cca_sec40_new = (max_bw > CHNL_BW_40) ? mac_hw_add_cca_busy_sec_40_get(cl_hw) : 0;
+ u32 cca_sec20_new = mac_hw_add_cca_busy_sec_20_get(cl_hw);
+
+ u32 cca_primary_diff = cca_primary_new - wrs_db->cca_primary;
+ u32 cca_sec80_diff = cca_sec80_new - wrs_db->cca_sec80;
+ u32 cca_sec40_diff = cca_sec40_new - wrs_db->cca_sec40;
+ u32 cca_sec20_diff = cca_sec20_new - wrs_db->cca_sec20;
+
+ wrs_db->cca_primary = cca_primary_new;
+ wrs_db->cca_sec80 = cca_sec80_new;
+ wrs_db->cca_sec40 = cca_sec40_new;
+ wrs_db->cca_sec20 = cca_sec20_new;
+ wrs_db->cca_timestamp = jiffies;
+
+ /* Increase by 25% */
+ cca_primary_diff = cca_primary_diff * WRS_CCA_PRIMARY_FACTOR >> WRS_CCA_PRIMARY_SHIFT;
+
+ /* Adjacent interference - if secondary is higher than primary by 25%. */
+ wrs_db->adjacent_interference80 = (cca_sec80_diff > cca_primary_diff);
+ wrs_db->adjacent_interference40 = (cca_sec40_diff > cca_primary_diff);
+ wrs_db->adjacent_interference20 = (cca_sec20_diff > cca_primary_diff);
+}
+
+static void cl_wrs_cca_maintenance(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db)
+{
+ u8 max_bw = wrs_db->max_cap.bw;
+
+ if (max_bw == CHNL_BW_20)
+ return;
+
+ if (jiffies_to_msecs(jiffies - wrs_db->cca_timestamp) > WRS_CCA_PERIOD_MS)
+ cl_wrs_cca_calc(cl_hw, wrs_db, max_bw);
+}
+
+static void cl_wrs_maintenance(struct timer_list *t)
+{
+ struct cl_wrs_db *wrs_db = from_timer(wrs_db, t, timer_maintenance);
+ struct cl_hw *cl_hw = container_of(wrs_db, struct cl_hw, wrs_db);
+
+ cl_wrs_cca_maintenance(cl_hw, wrs_db);
+
+ cl_wrs_lock(wrs_db);
+ cl_sta_loop(cl_hw, cl_wrs_sta_maintenance);
+ cl_wrs_unlock(wrs_db);
+
+ mod_timer(&wrs_db->timer_maintenance, jiffies + msecs_to_jiffies(wrs_db->interval));
+}
+
+static void cl_wrs_down_decision_weights_update(struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ u16 new_rate_idx,
+ struct cl_wrs_params *wrs_params)
+{
+ u16 old_rate_idx = wrs_params->rate_idx;
+ u8 up_idx = 0;
+ u16 down_th_min = wrs_db->time_th_min;
+ u16 step = wrs_db->step_down;
+ u16 *th_down = &wrs_params->table[old_rate_idx].rate_down.time_th;
+ u16 *th_up = NULL;
+ struct cl_wrs_table *table_node = &wrs_params->table[new_rate_idx];
+
+ /* Decrease the weight from old rate to new rate */
+ if (*th_down > (down_th_min + step))
+ *th_down -= step;
+ else
+ *th_down = down_th_min;
+
+ /* Increase the weight from new rate to old rate */
+ for (up_idx = 0; up_idx < WRS_TABLE_NODE_UP_MAX; up_idx++) {
+ if (old_rate_idx == table_node->rate_up[up_idx].rate_idx) {
+ th_up = &table_node->rate_up[up_idx].time_th;
+ table_node->rate_up[up_idx].quick_up_check = !!wrs_params->quick_up_check;
+ step = wrs_params->penalty_decision_dn;
+ *th_up = min_t(u16, *th_up + step, wrs_db->time_th_max_up);
+ break;
+ }
+ }
+
+ wrs_pr_info(wrs_db,
+ "[%s] Down update - sta = %u, "
+ "down weight [%u-->%u] = %u, up weight [%u-->%u] = %u\n",
+ WRS_TYPE_STR(wrs_params->type), wrs_sta->sta_idx, old_rate_idx, new_rate_idx,
+ *th_down, new_rate_idx, old_rate_idx, th_up ? *th_up : 0);
+}
+
+static void cl_wrs_up_same_decision_weights_update(struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ u16 curr_rate_idx = wrs_params->rate_idx;
+ u16 down_rate_idx = wrs_params->table[curr_rate_idx].rate_down.rate_idx;
+ u8 up_idx = 0;
+ u16 up_th_min = wrs_db->time_th_min;
+ u16 step = wrs_db->step_up_same;
+ u16 *th_down = &wrs_params->table[curr_rate_idx].rate_down.time_th;
+ u16 *th_up = NULL;
+ u16 th_down_orig = *th_down;
+ u16 th_up_orig = 0;
+ struct cl_wrs_table *table_node = &wrs_params->table[down_rate_idx];
+
+ /* Increase the weight from current rate to down rate */
+ *th_down = min_t(u16, *th_down + step, wrs_db->time_th_max_down);
+
+ /* Decrease the weight from down rate to current rate */
+ for (up_idx = 0; up_idx < WRS_TABLE_NODE_UP_MAX; up_idx++) {
+ if (curr_rate_idx == table_node->rate_up[up_idx].rate_idx) {
+ th_up = &table_node->rate_up[up_idx].time_th;
+ table_node->rate_up[up_idx].quick_up_check = false;
+
+ th_up_orig = *th_up;
+
+ if (*th_up > (up_th_min + step))
+ *th_up -= step;
+ else
+ *th_up = up_th_min;
+ break;
+ }
+ }
+
+ if (th_up && (th_up_orig != *th_up || th_down_orig != *th_down))
+ wrs_pr_info(wrs_db,
+ "[%s] Up/same update - sta = %u, "
+ "down weight [%u-->%u] = %u, up weight [%u-->%u] = %u\n",
+ WRS_TYPE_STR(wrs_params->type), wrs_sta->sta_idx, curr_rate_idx,
+ down_rate_idx, *th_down, down_rate_idx, curr_rate_idx, *th_up);
+}
+
+static bool cl_wrs_is_rate_params_valid(struct cl_wrs_rate *rate_params)
+{
+ return (*(u16 *)rate_params != U16_MAX);
+}
+
+void cl_wrs_init(struct cl_hw *cl_hw)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ /* Default configuration */
+ wrs_db->debug_level = DBG_LVL_ERROR;
+ wrs_db->rssi_protect_en = true;
+ wrs_db->rssi_protect_mode = WRS_RSSI_PROT_MODE_RSSI;
+ wrs_db->rssi_protect_up_thr = WRS_RSSI_PROTECT_UP_THR;
+ wrs_db->rssi_protect_dn_thr = WRS_RSSI_PROTECT_DN_THR;
+ wrs_db->min_frames_for_decision = WRS_MIN_FRAMES_FOR_DECISION;
+ wrs_db->epr_factor = WRS_EPR_FACTOR;
+ wrs_db->converge_idle_en = true;
+ wrs_db->converge_idle_interval_reset = WRS_CONVERGE_IDLE_INTERVAL_RESET;
+ wrs_db->converge_idle_interval_rssi = WRS_CONVERGE_IDLE_INTERVAL_RSSI;
+ wrs_db->converge_idle_packet_th = WRS_CONVERGE_IDLE_PACKET_TH;
+ wrs_db->converge_trfc_en = true;
+ wrs_db->converge_trfc_interval_static = WRS_CONVERGE_TRFC_INTERVAL_STATIC;
+ wrs_db->converge_trfc_interval_motion = WRS_CONVERGE_TRFC_INTERVAL_MOTION;
+ wrs_db->immediate_drop_en = true;
+ wrs_db->immediate_drop_epr_factor = WRS_IMMEDIATE_DROP_EPR_FACTOR;
+ wrs_db->immediate_drop_max_in_row = WRS_IMMEDIATE_DROP_MAX_IN_ROW;
+ wrs_db->time_th_min = WRS_MSEC_WEIGHT_MIN;
+ wrs_db->time_th_max_up = WRS_MSEC_WEIGHT_MAX_UP;
+ wrs_db->time_th_max_down = WRS_MSEC_WEIGHT_MAX_DOWN;
+ wrs_db->step_down = WRS_MSEC_STEP_DOWN;
+ wrs_db->step_up_same = WRS_MSEC_STEP_UP_SAME;
+ wrs_db->interval = msecs_round(WRS_MAINTENANCE_PERIOD_MS);
+ wrs_db->conservative_mcs_noisy_env = false;
+ wrs_db->conservative_nss_noisy_env = false;
+ wrs_db->quick_up_en = true;
+ wrs_db->quick_up_ba_thr = WRS_QUICK_UP_BA_THR;
+ wrs_db->quick_up_interval = msecs_round(WRS_QUICK_UP_INTERVAL_MS);
+ wrs_db->quick_down_en = true;
+ wrs_db->quick_down_epr_factor = WRS_QUICK_DOWN_EPR_FACTOR;
+ wrs_db->quick_down_agg_thr = WRS_QUICK_DOWN_AGG_THR;
+ wrs_db->quick_down_pkt_thr = WRS_QUICK_DOWN_PKT_THR;
+ wrs_db->ba_not_rcv_collision_filter = true;
+ /* Environment of 2.4 is much more noisy, so 'BA not received' are ignored. */
+ wrs_db->ba_not_rcv_force = cl_band_is_24g(cl_hw) ? false : true;
+ wrs_db->ba_not_rcv_time_since_sync = WRS_BA_NOT_RCV_TIME_SINCE_SYNC;
+ wrs_db->sync_timeout = WRS_SYNC_TIMEOUT;
+ wrs_db->sync_min_attempts = WRS_SYNC_MIN_ATTEMPTS;
+
+ /* Init WRS periodic timer */
+ timer_setup(&wrs_db->timer_maintenance, cl_wrs_maintenance, 0);
+
+ if (!cl_hw->chip->conf->ce_production_mode) {
+ wrs_db->cca_timestamp = jiffies;
+ mod_timer(&wrs_db->timer_maintenance,
+ jiffies + msecs_to_jiffies(wrs_db->interval));
+ }
+
+ spin_lock_init(&wrs_db->lock);
+
+ if ((cl_hw->conf->ci_wrs_fixed_rate[WRS_FIXED_PARAM_MODE] != -1) &&
+ (cl_hw->conf->ci_wrs_fixed_rate[WRS_FIXED_PARAM_BW] != -1) &&
+ (cl_hw->conf->ci_wrs_fixed_rate[WRS_FIXED_PARAM_NSS] != -1) &&
+ (cl_hw->conf->ci_wrs_fixed_rate[WRS_FIXED_PARAM_MCS] != -1) &&
+ (cl_hw->conf->ci_wrs_fixed_rate[WRS_FIXED_PARAM_GI] != -1))
+ wrs_db->is_fixed_rate = WRS_FIXED_FALLBACK_DIS;
+}
+
+inline void cl_wrs_lock_bh(struct cl_wrs_db *wrs_db)
+{
+ spin_lock_bh(&wrs_db->lock);
+}
+
+inline void cl_wrs_unlock_bh(struct cl_wrs_db *wrs_db)
+{
+ spin_unlock_bh(&wrs_db->lock);
+}
+
+inline void cl_wrs_lock(struct cl_wrs_db *wrs_db)
+{
+ spin_lock(&wrs_db->lock);
+}
+
+inline void cl_wrs_unlock(struct cl_wrs_db *wrs_db)
+{
+ spin_unlock(&wrs_db->lock);
+}
+
+void cl_wrs_rate_param_sync(struct cl_wrs_db *wrs_db, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ if (wrs_params->sync)
+ return;
+
+ cl_wrs_cntrs_reset(wrs_sta, wrs_params);
+
+ /* Reset counters */
+ cl_wrs_reset_params_cntrs(wrs_params);
+
+ /* Change state to SYNCED */
+ wrs_params->sync = true;
+ wrs_params->sync_timestamp = jiffies;
+
+ wrs_pr_trace(wrs_db, "[%s] Sync - timestamp = %u, sta = %u, rate_idx = %u\n",
+ WRS_TYPE_STR(wrs_params->type),
+ jiffies_to_msecs(jiffies),
+ wrs_sta->sta_idx,
+ wrs_params->rate_idx);
+}
+
+void cl_wrs_rate_params_update(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params,
+ u16 new_rate_idx, bool is_sync_required, bool mu_valid)
+{
+ struct cl_wrs_rate_params *rate_params = &wrs_params->rate_params;
+ struct cl_wrs_rate *rate = &wrs_params->table[new_rate_idx].rate;
+ u16 fallback_rate_idx = wrs_params->table[new_rate_idx].rate_down.rate_idx;
+ struct cl_wrs_rate *rate_fallback = &wrs_params->table[fallback_rate_idx].rate;
+#ifdef CONFIG_CL8K_DYN_BCAST_RATE
+
+ if (!WRS_TYPE_IS_RX(wrs_params)) {
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+
+ cl_dyn_bcast_rate_change(cl_hw, cl_sta, rate_params->mcs, rate->mcs);
+ }
+#endif /* CONFIG_CL8K_DYN_BCAST_RATE */
+
+ rate_params->bw = rate->bw;
+ rate_params->nss = rate->nss;
+ rate_params->mcs = rate->mcs;
+ rate_params->gi = rate->gi;
+ rate_params->mode = wrs_sta->mode;
+ rate_params->fallback_en = (wrs_params->is_fixed_rate != WRS_FIXED_FALLBACK_DIS);
+
+ wrs_pr_trace(wrs_db,
+ "[%s] Rate params update - "
+ "sta = %u, rate_idx = %u, bw = %u, nss = %u, mcs = %u, gi = %u\n",
+ WRS_TYPE_STR(wrs_params->type), wrs_sta->sta_idx,
+ new_rate_idx, rate_params->bw, rate_params->nss,
+ rate_params->mcs, rate_params->gi);
+
+ wrs_params->rate_idx = new_rate_idx;
+
+ /* Converge - restart the time for converging weights of all old rates */
+ wrs_params->converge_time_trfc = 0;
+
+ cl_wrs_rate_param_set(cl_hw, wrs_sta, wrs_params, rate_params,
+ rate_fallback, mu_valid, true);
+
+ if (is_sync_required) {
+ wrs_params->sync = false;
+ wrs_params->no_sync_timestamp = jiffies;
+ } else {
+ wrs_params->sync = true;
+ }
+
+ /* Reset Counters */
+ cl_wrs_reset_params_cntrs(wrs_params);
+}
+
+void cl_wrs_decision_make(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params,
+ enum cl_wrs_decision decision, u16 new_rate_idx)
+{
+ if (WRS_IS_DECISION_DOWN(decision)) {
+ cl_wrs_down_decision_weights_update(wrs_db, wrs_sta, new_rate_idx, wrs_params);
+ } else if (WRS_IS_DECISION_UP(decision)) {
+ cl_wrs_up_same_decision_weights_update(wrs_db, wrs_sta, wrs_params);
+
+ if (wrs_params->rate_idx != wrs_params->table[new_rate_idx].rate_down.rate_idx) {
+ /*
+ * In case the down rate is different from the previous rate,
+ * update down rate index and reset the thresholds
+ */
+ struct cl_wrs_table_node *rate_down =
+ &wrs_params->table[new_rate_idx].rate_down;
+
+ rate_down->rate_idx = wrs_params->rate_idx;
+ rate_down->time_th = WRS_INIT_MSEC_WEIGHT_DOWN;
+ }
+ } else if (decision == WRS_DECISION_SAME) {
+ cl_wrs_up_same_decision_weights_update(wrs_db, wrs_sta, wrs_params);
+
+ /* Reset counters besides down_time_cnt */
+ wrs_params->frames_total = 0;
+ wrs_params->fail_total = 0;
+ wrs_params->ba_not_rcv_total = 0;
+ wrs_params->epr_acc = 0;
+ wrs_params->up_same_time_cnt = 0;
+ }
+
+ cl_wrs_decision_update(wrs_db, wrs_sta, wrs_params, decision, new_rate_idx);
+
+ if (WRS_IS_DECISION_DOWN(decision) || WRS_IS_DECISION_UP(decision))
+ cl_wrs_rate_params_update(cl_hw, wrs_db, wrs_sta, wrs_params,
+ new_rate_idx, true, wrs_params->is_mu_valid);
+}
+
+void cl_wrs_decision_update(struct cl_wrs_db *wrs_db, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params, enum cl_wrs_decision decision,
+ u16 new_rate_idx)
+{
+ wrs_params->last_decision = decision;
+ wrs_params->decision_cnt[decision]++;
+
+ if (decision != WRS_DECISION_DOWN_IMMEDIATE)
+ wrs_params->immediate_drop_cntr = 0;
+
+ if (wrs_params->is_logger_en) {
+ WRS_LOGGER.decision = decision;
+ WRS_LOGGER.new_rate_idx = new_rate_idx;
+ }
+
+ if (decision == WRS_DECISION_SAME)
+ return;
+
+ wrs_pr_trace(wrs_db,
+ "[%s] Decision update - timestamp [%u] sta [%u] decision [%s]\n",
+ WRS_TYPE_STR(wrs_params->type),
+ jiffies_to_msecs(jiffies),
+ wrs_sta->sta_idx,
+ WRS_DECISION_STR(decision));
+}
+
+void cl_wrs_fixed_rate_set(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params,
+ u8 is_fixed_rate, u8 mode, u8 bw, u8 nss, u8 mcs, u8 gi, bool mu_valid)
+{
+ u16 rate_idx = 0;
+ u8 type = wrs_params->type;
+
+ if (!is_fixed_rate) {
+ wrs_params->is_fixed_rate = WRS_AUTO_RATE;
+ wrs_pr_verbose(wrs_db, "[%s] Station %u was set to auto rate!\n",
+ WRS_TYPE_STR(type), wrs_sta->sta_idx);
+ cl_wrs_set_rate_idle(cl_hw, wrs_db, wrs_sta, wrs_params);
+ return;
+ }
+
+ if (mode != wrs_sta->mode) {
+ /* Set fixed rate with a different format-mode */
+ struct cl_wrs_rate_params *rate_params = &wrs_params->rate_params;
+
+ if (cl_band_is_6g(cl_hw) && mode != WRS_MODE_HE) {
+ wrs_pr_verbose(wrs_db, "[%s] Invalid format mode [%u] for 6GHz band\n",
+ WRS_TYPE_STR(type), mode);
+ return;
+ }
+#ifdef CONFIG_CL8K_DYN_BCAST_RATE
+
+ if (!WRS_TYPE_IS_RX(wrs_params)) {
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+
+ cl_dyn_bcast_rate_change(cl_hw, cl_sta, rate_params->mcs, mcs);
+ }
+#endif /* CONFIG_CL8K_DYN_BCAST_RATE */
+
+ rate_params->bw = bw;
+ rate_params->nss = nss;
+ rate_params->mcs = mcs;
+ rate_params->gi = gi;
+ rate_params->mode = mode;
+ rate_params->fallback_en = (wrs_params->is_fixed_rate != WRS_FIXED_FALLBACK_DIS);
+
+ wrs_params->is_fixed_rate = is_fixed_rate;
+
+ cl_wrs_rate_param_set(cl_hw, wrs_sta, wrs_params, rate_params,
+ NULL, mu_valid, true);
+ wrs_pr_verbose(wrs_db,
+ "[%s] Station %u set to %s - "
+ "mode=%u, bw=%u, nss=%u, mcs=%u, gi=%u\n",
+ WRS_TYPE_STR(type), wrs_sta->sta_idx, FIXED_RATE_STR(is_fixed_rate),
+ mode, bw, nss, mcs, gi);
+ return;
+ }
+
+ rate_idx = cl_wrs_tables_find_rate_idx(wrs_params, bw, nss, mcs, gi);
+
+ if (rate_idx == WRS_INVALID_RATE) {
+ wrs_pr_err(wrs_db,
+ "[%s] Invalid fixed rate - mode=%u, bw=%u, nss=%u, mcs=%u, gi=%u\n",
+ WRS_TYPE_STR(type), mode, bw, nss, mcs, gi);
+ return;
+ }
+
+ wrs_params->is_fixed_rate = is_fixed_rate;
+ cl_wrs_rate_params_update(cl_hw, wrs_db, wrs_sta, wrs_params, rate_idx, false, false);
+ wrs_pr_verbose(wrs_db,
+ "[%s] Station %u set to %s - mode=%u, bw=%u, nss=%u, mcs=%u, gi=%u\n",
+ WRS_TYPE_STR(type), wrs_sta->sta_idx, FIXED_RATE_STR(is_fixed_rate),
+ mode, bw, nss, mcs, gi);
+}
+
+void cl_wrs_quick_down_check(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_cntrs cntrs = {0};
+ struct cl_wrs_table *table = NULL;
+ u16 curr_rate_idx = 0;
+ u16 down_rate_idx = 0;
+ u8 type = wrs_params->type;
+
+ if (!wrs_params->sync ||
+ wrs_params->is_fixed_rate ||
+ !WRS_IS_DECISION_UP(wrs_params->last_decision))
+ return;
+
+ if (!WRS_TYPE_IS_RX(wrs_params))
+ cl_wrs_update_ba_not_rcv(wrs_db, wrs_params);
+
+ cl_wrs_cntrs_read(wrs_sta, &cntrs, type);
+ cl_wrs_stats_per_update(wrs_db, wrs_sta, wrs_params, &cntrs);
+
+ curr_rate_idx = wrs_params->rate_idx;
+ table = &wrs_params->table[curr_rate_idx];
+ down_rate_idx = table->rate_down.rate_idx;
+
+ /* Check if we transmitted enough frames for taking decision */
+ if (wrs_params->frames_total < wrs_db->min_frames_for_decision)
+ return;
+
+ if (wrs_params->is_logger_en) {
+ memset(&WRS_LOGGER, 0, sizeof(struct cl_wrs_logger));
+ WRS_LOGGER.timestamp = jiffies_to_msecs(jiffies);
+ WRS_LOGGER.rate_idx = curr_rate_idx;
+ WRS_LOGGER.success = wrs_params->frames_total - wrs_params->fail_total;
+ WRS_LOGGER.fail = wrs_params->fail_total;
+ WRS_LOGGER.ba_not_rcv = wrs_params->ba_not_rcv_total;
+ }
+
+ /* Down decision check */
+ if (down_rate_idx != curr_rate_idx &&
+ cl_wrs_down_epr_check(wrs_db, wrs_sta, wrs_params,
+ wrs_db->quick_down_epr_factor, WRS_DECISION_DOWN_QUICK))
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_DOWN_QUICK, down_rate_idx);
+
+ if (wrs_params->is_logger_en && WRS_LOGGER.decision != WRS_DECISION_NONE)
+ wrs_params->logger_idx = WRS_INC_POW2(wrs_params->logger_idx,
+ wrs_params->logger_size);
+}
+
+bool cl_wrs_up_mcs1(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params)
+{
+ /*
+ * In case of big packets (4300 in VHT and 5400 in HE) and low
+ * rate (BW 20, NSS 1, MCS 0), firmware will increase rate to MCS 1,
+ * and give an indication to driver (set rate_fix_mcs1 in cl_agg_tx_report).
+ * WRS should also move to MCS 1, and give the maximum time
+ * penalty time from MCS 0 toMCS 1.
+ */
+ u16 curr_rate_idx = wrs_params->rate_idx;
+ u16 up_rate_idx = 0;
+ struct cl_wrs_table *table = &wrs_params->table[curr_rate_idx];
+
+ if (!table || wrs_params->is_fixed_rate)
+ return false;
+
+ if (table->rate.bw != CHNL_BW_20 ||
+ table->rate.nss != WRS_SS_1 ||
+ table->rate.mcs != WRS_MCS_0)
+ return false;
+
+ up_rate_idx = cl_wrs_tables_find_rate_idx(wrs_params,
+ CHNL_BW_20, WRS_SS_1, WRS_MCS_1, table->rate.gi);
+
+ if (up_rate_idx == WRS_INVALID_RATE)
+ return false;
+
+ if (wrs_params->is_logger_en) {
+ memset(&WRS_LOGGER, 0, sizeof(struct cl_wrs_logger));
+ WRS_LOGGER.timestamp = jiffies_to_msecs(jiffies);
+ WRS_LOGGER.rate_idx = curr_rate_idx;
+ }
+
+ wrs_params->table[up_rate_idx].rate_down.time_th = wrs_db->time_th_max_up;
+
+ cl_wrs_cntrs_reset(wrs_sta, wrs_params);
+ cl_wrs_decision_update(wrs_db, wrs_sta, wrs_params, WRS_DECISION_UP_MCS1, up_rate_idx);
+ cl_wrs_rate_params_update(cl_hw, wrs_db, wrs_sta, wrs_params,
+ up_rate_idx, true, wrs_params->is_mu_valid);
+
+ if (wrs_params->is_logger_en)
+ wrs_params->logger_idx = WRS_INC_POW2(wrs_params->logger_idx,
+ wrs_params->logger_size);
+
+ return true;
+}
+
+void cl_wrs_rate_param_set(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ struct cl_wrs_rate_params *rate_params,
+ struct cl_wrs_rate *rate_fallback,
+ bool mu_mimo_valid, bool set_su)
+{
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+ struct cl_wrs_info *wrs_info = NULL;
+ u8 op_mode = 0;
+ u8 ltf = 0;
+ u8 ltf_fallback = 0;
+ u8 sta_idx = cl_sta->sta_idx;
+ union cl_rate_ctrl_info rate_ctrl;
+ union cl_rate_ctrl_info rate_ctrl_fallback;
+ union cl_rate_ctrl_info_he rate_ctrl_he;
+
+ rate_ctrl_he.word = 0;
+ wrs_info = cl_wrs_info_get(cl_sta, wrs_params->type);
+
+ wrs_params->data_rate = cl_data_rates_get(rate_params->mode,
+ rate_params->bw,
+ rate_params->nss,
+ rate_params->mcs,
+ rate_params->gi);
+
+ /*
+ * Trying to transmit MU-MIMO in bw < bw of sounding (which is STA's max bw) will fail,
+ * So we prevent it from being transmitted with MU.
+ */
+ if (rate_params->bw != cl_sta->sta->bandwidth)
+ mu_mimo_valid = false;
+
+ rate_ctrl.word = cl_rate_ctrl_generate(cl_hw, cl_sta, rate_params->mode,
+ rate_params->bw, rate_params->nss,
+ rate_params->mcs, rate_params->gi,
+ rate_params->fallback_en, mu_mimo_valid);
+
+ /* For fallback rate use same mode (if it is NULL use same rate). */
+ if (rate_fallback) {
+ rate_ctrl_fallback.word = cl_rate_ctrl_generate(cl_hw,
+ cl_sta,
+ rate_params->mode,
+ rate_fallback->bw,
+ rate_fallback->nss,
+ rate_fallback->mcs,
+ rate_fallback->gi,
+ rate_params->fallback_en,
+ mu_mimo_valid);
+ ltf_fallback = cl_map_gi_to_ltf(rate_params->mode, rate_fallback->gi);
+ } else {
+ rate_ctrl_fallback.word = rate_ctrl.word;
+ }
+
+ /* Save current BF state and SS for the fallback rate */
+ if (WRS_TYPE_IS_TX_SU(wrs_params)) {
+ struct cl_bf_sta_db *bf_db = &cl_sta->bf_db;
+
+ bf_db->is_on = rate_ctrl.field.tx_bf;
+ bf_db->is_on_fallback = rate_ctrl_fallback.field.tx_bf;
+ bf_db->num_ss = rate_params->nss;
+ bf_db->num_ss_fallback = rate_fallback ? rate_fallback->nss : rate_params->nss;
+ }
+
+ /* Reset counters */
+ wrs_info->success = 0;
+ wrs_info->fail = 0;
+
+ /* Mark rate as unsynced */
+ wrs_info->synced = false;
+ wrs_info->quick_rate_check = false;
+ wrs_info->sync_attempts = 0;
+ ltf = cl_map_gi_to_ltf(rate_params->mode, rate_params->gi);
+
+ if (rate_params->mode == WRS_MODE_HE)
+ rate_ctrl_he.field.spatial_conf = RATE_CNTRL_HE_SPATIAL_CONF_DEF;
+
+ /* Send new rate to firmware */
+ if (set_su) {
+ if (WRS_TYPE_IS_TX_SU(wrs_params)) {
+ op_mode = RATE_OP_MODE_STA_SU;
+ cl_msg_tx_update_rate_dl(cl_hw, sta_idx, rate_ctrl.word,
+ rate_ctrl_fallback.word, rate_params->bw,
+ op_mode, wrs_params->group_id, mu_mimo_valid,
+ ltf, ltf_fallback, rate_ctrl_he.word);
+ } else if (WRS_TYPE_IS_RX(wrs_params)) {
+ u8 ul_gi_ltf = CL_TF_GI_TO_GI_LTF(rate_params->gi);
+
+ cl_msg_tx_update_rate_ul(cl_hw, sta_idx, rate_params->bw,
+ rate_params->nss, rate_params->mcs, ul_gi_ltf);
+ }
+ }
+}
+
+s8 cl_wrs_rssi_eq_calc(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ bool read_clear, s8 *sorted_rssi)
+{
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+ struct cl_wrs_rssi *wrs_rssi = &cl_sta->wrs_rssi;
+ int i;
+
+ if (wrs_rssi->cnt == 0) {
+ memcpy(sorted_rssi, cl_sta->last_rssi, cl_hw->num_antennas);
+ goto sort;
+ }
+
+ for (i = 0; i < cl_hw->num_antennas; i++)
+ sorted_rssi[i] = (s8)(wrs_rssi->sum[i] / wrs_rssi->cnt);
+
+ if (read_clear)
+ memset(wrs_rssi, 0, sizeof(struct cl_wrs_rssi));
+
+sort:
+ /* Sort RSSI values in descending order */
+ cl_rssi_sort_descending(sorted_rssi, cl_hw->num_antennas);
+
+ /* Calc equivalent RSSI */
+ return cl_rssi_calc_equivalent(cl_hw, sorted_rssi);
+}
+
+void cl_wrs_cntrs_reset(struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params)
+{
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+ struct cl_wrs_info *wrs_info = cl_wrs_info_get(cl_sta, wrs_params->type);
+
+ _cl_wrs_cntrs_reset(wrs_info);
+}
+
+struct cl_wrs_info *cl_wrs_info_get(struct cl_sta *cl_sta, u8 type)
+{
+ if (type == WRS_TYPE_TX_SU)
+ return &cl_sta->wrs_info_tx_su;
+ else if (type == WRS_TYPE_RX)
+ return &cl_sta->wrs_info_rx;
+
+ return NULL;
+}
+
+struct cl_wrs_params *cl_wrs_params_get(struct cl_wrs_sta *wrs_sta, u8 type)
+{
+ if (type == WRS_TYPE_TX_SU)
+ return &wrs_sta->tx_su_params;
+ else if (type == WRS_TYPE_RX)
+ return wrs_sta->rx_params;
+
+ return NULL;
+}
+
+void cl_wrs_update_rx_rate(struct cl_hw *cl_hw, struct cl_sta *cl_sta, struct hw_rxhdr *rxhdr)
+{
+ struct cl_wrs_rate *rate_params = NULL;
+
+ if (!cl_sta ||
+ !cl_sta->wrs_sta.rx_params ||
+ rxhdr->format_mod == FORMATMOD_HE_TRIG)
+ return;
+
+ rate_params = &cl_sta->wrs_sta.rx_params->rx_rate_idle;
+
+ rate_params->bw = rxhdr->ch_bw;
+ rate_params->nss = rxhdr->n_sts;
+ rate_params->mcs = rxhdr->mcs;
+ rate_params->gi = rxhdr->gi_type;
+}
+
+bool cl_wrs_set_rate_idle(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_rate *rx_rate_idle = &wrs_params->rx_rate_idle;
+ s8 rssi_sort[MAX_ANTENNAS] = {0};
+ u16 new_rate_idx = 0;
+ u8 decision;
+
+ if (WRS_TYPE_IS_RX(wrs_params) &&
+ cl_wrs_is_rate_params_valid(rx_rate_idle)) {
+ decision = WRS_DECISION_RX_RATE;
+ if (wrs_params->initial_rate_idx != WRS_INVALID_RATE)
+ new_rate_idx = wrs_params->initial_rate_idx;
+ else
+ /* Get rate from last data packet */
+ new_rate_idx = cl_wrs_tables_find_rate_idx(wrs_params,
+ rx_rate_idle->bw,
+ rx_rate_idle->nss,
+ rx_rate_idle->mcs,
+ rx_rate_idle->gi);
+
+ cl_wrs_rx_rate_idle_reset(wrs_params);
+ } else {
+ /* Get rate from rssi */
+ cl_wrs_rssi_eq_calc(cl_hw, wrs_sta, true, rssi_sort);
+ new_rate_idx = cl_wrs_rssi_find_rate(cl_hw, wrs_db, wrs_sta, wrs_params, rssi_sort);
+ decision = WRS_DECISION_RSSI_MGMT;
+ }
+
+ if (new_rate_idx != wrs_params->rate_idx) {
+ if (wrs_params->is_logger_en) {
+ memset(&WRS_LOGGER, 0, sizeof(struct cl_wrs_logger));
+ WRS_LOGGER.timestamp = jiffies_to_msecs(jiffies);
+ WRS_LOGGER.rate_idx = wrs_params->rate_idx;
+ }
+
+ cl_wrs_decision_update(wrs_db, wrs_sta, wrs_params, decision, new_rate_idx);
+ cl_wrs_rate_params_update(cl_hw, wrs_db, wrs_sta, wrs_params,
+ new_rate_idx, false, false);
+ } else {
+ wrs_params->sync = true;
+ }
+
+ return true;
+}
+
+struct cl_wrs_rate_params *cl_wrs_rx_rate_get(struct cl_sta *cl_sta)
+{
+ struct cl_wrs_params *rx_params = cl_sta->wrs_sta.rx_params;
+
+ if (rx_params)
+ return &rx_params->rate_params;
+
+ return NULL;
+}
+
+void cl_wrs_rx_rate_idle_reset(struct cl_wrs_params *rx_params)
+{
+ struct cl_wrs_rate *rate_idle = &rx_params->rx_rate_idle;
+
+ *(u16 *)rate_idle = U16_MAX;
+}
+
+static void cl_wrs_ap_set_bitmap(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db)
+{
+ u8 mcs, bw, nss, rate_idx;
+
+ memset(wrs_db->ap_supported_rates, 0, sizeof(wrs_db->ap_supported_rates));
+
+ for (bw = cl_hw->conf->ci_wrs_min_bw; bw <= wrs_db->max_cap.bw; bw++)
+ for (nss = 0; nss <= wrs_db->max_cap.nss; nss++)
+ for (mcs = 0; mcs <= wrs_db->max_cap.mcs; mcs++) {
+ rate_idx = mcs + (nss * WRS_MCS_MAX);
+ wrs_db->ap_supported_rates[bw] |= BIT(rate_idx);
+ }
+}
+
+void cl_wrs_ap_capab_set(struct cl_hw *cl_hw,
+ u8 bw,
+ u8 use_sgi)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_wrs_rate *max_cap = &wrs_db->max_cap;
+ u8 conf_nss = cl_hw->conf->ce_tx_nss - 1;
+
+ switch (cl_hw->wireless_mode) {
+ case WIRELESS_MODE_HE:
+ case WIRELESS_MODE_HT_VHT_HE:
+ wrs_db->mode = WRS_MODE_HE;
+ max_cap->bw = bw;
+ max_cap->nss = conf_nss;
+ max_cap->mcs = WRS_MCS_11;
+ max_cap->gi = use_sgi ? WRS_GI_VSHORT : 0;
+ break;
+ case WIRELESS_MODE_HT_VHT:
+ wrs_db->mode = WRS_MODE_VHT;
+ max_cap->bw = bw;
+ max_cap->nss = conf_nss;
+ max_cap->mcs = WRS_MCS_9;
+ max_cap->gi = use_sgi ? WRS_GI_SHORT : 0;
+ break;
+ case WIRELESS_MODE_HT:
+ wrs_db->mode = WRS_MODE_HT;
+ max_cap->bw = min_t(u8, bw, CHNL_BW_80);
+ max_cap->nss = conf_nss;
+ max_cap->mcs = WRS_MCS_7;
+ max_cap->gi = use_sgi ? WRS_GI_SHORT : 0;
+ break;
+ case WIRELESS_MODE_LEGACY:
+ default:
+ if (cl_hw->hw_mode == HW_MODE_B) {
+ wrs_db->mode = WRS_MODE_CCK;
+ max_cap->mcs = WRS_MCS_3;
+ } else {
+ wrs_db->mode = WRS_MODE_OFDM;
+ max_cap->mcs = WRS_MCS_7;
+ }
+
+ max_cap->bw = CHNL_BW_20;
+ max_cap->nss = 0;
+ max_cap->gi = 0;
+ break;
+ }
+
+ if (cl_hw->conf->ci_wrs_max_bw < max_cap->bw) {
+ max_cap->bw = cl_hw->conf->ci_wrs_max_bw;
+ wrs_pr_warn(wrs_db, "Max BW limited to %uMHz\n", BW_TO_MHZ(max_cap->bw));
+ }
+
+ wrs_db->coex_bw = max_t(u8, max_cap->bw, CHNL_BW_40);
+
+ cl_wrs_ap_set_bitmap(cl_hw, wrs_db);
+}
+
+void cl_wrs_ap_capab_modify_bw(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db, u8 max_bw)
+{
+ wrs_db->max_cap.bw = max_bw;
+
+ cl_wrs_ap_set_bitmap(cl_hw, wrs_db);
+}
+
+void cl_wrs_ap_capab_modify_gi(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db, u8 use_sgi)
+{
+ switch (cl_hw->wireless_mode) {
+ case WIRELESS_MODE_HE:
+ case WIRELESS_MODE_HT_VHT_HE:
+ wrs_db->max_cap.gi = use_sgi ? WRS_GI_VSHORT : 0;
+ break;
+ case WIRELESS_MODE_HT:
+ case WIRELESS_MODE_HT_VHT:
+ wrs_db->max_cap.gi = use_sgi ? WRS_GI_SHORT : 0;
+ break;
+ default:
+ wrs_db->max_cap.gi = 0;
+ }
+
+ cl_wrs_ap_set_bitmap(cl_hw, wrs_db);
+}
+
+void cl_wrs_api_init(struct cl_hw *cl_hw)
+{
+ cl_wrs_init(cl_hw);
+ cl_wrs_ap_capab_set(cl_hw, cl_hw->conf->ci_cap_bandwidth,
+ cl_hw->conf->ci_short_guard_interval);
+}
+
+void cl_wrs_api_close(struct cl_hw *cl_hw)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ del_timer_sync(&wrs_db->timer_maintenance);
+}
+
+void cl_wrs_api_sta_add(struct cl_hw *cl_hw, struct ieee80211_sta *sta)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ cl_wrs_lock_bh(wrs_db);
+ cl_wrs_sta_add(cl_hw, sta);
+
+ if (sta->he_cap.has_he && cl_hw->conf->ce_wrs_rx_en)
+ cl_wrs_sta_add_rx(cl_hw, sta);
+
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+void cl_wrs_api_sta_remove(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ cl_wrs_lock_bh(wrs_db);
+ cl_wrs_sta_remove(cl_hw, wrs_db, cl_sta);
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+void cl_wrs_api_bss_set_bw(struct cl_hw *cl_hw, u8 bw)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ cl_wrs_lock_bh(wrs_db);
+ cl_wrs_ap_capab_modify_bw(cl_hw, wrs_db, bw);
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+void cl_wrs_api_bss_set_sgi(struct cl_hw *cl_hw, u8 use_sgi)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ cl_wrs_lock_bh(wrs_db);
+ cl_wrs_ap_capab_modify_gi(cl_hw, wrs_db, use_sgi);
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+bool cl_wrs_api_bss_is_sgi_en(struct cl_hw *cl_hw)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ bool ret = false;
+
+ cl_wrs_lock_bh(wrs_db);
+ ret = wrs_db->max_cap.gi != 0;
+ cl_wrs_unlock_bh(wrs_db);
+
+ return ret;
+}
+
+void cl_wrs_api_bss_capab_update(struct cl_hw *cl_hw, u8 bw, u8 use_sgi)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ cl_wrs_lock_bh(wrs_db);
+ cl_wrs_ap_capab_set(cl_hw, bw, use_sgi);
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+void cl_wrs_api_nss_or_bw_changed(struct cl_hw *cl_hw, struct ieee80211_sta *sta, u8 nss, u8 bw)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+
+ cl_wrs_lock_bh(wrs_db);
+
+ wrs_sta->max_rate_cap.nss = nss;
+ wrs_sta->max_rate_cap.bw = bw;
+ cl_wrs_sta_capabilities_set(wrs_db, sta);
+ cl_wrs_tables_build(cl_hw, wrs_sta, &wrs_sta->tx_su_params);
+
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+void cl_wrs_api_he_minrate_changed(struct cl_sta *cl_sta, u8 he_minrate)
+{
+ struct cl_hw *cl_hw = cl_sta->cl_vif->cl_hw;
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ u8 mcs = 0, nss = 0, bw = 0;
+ u16 data_rate_x10 = 0;
+
+ cl_wrs_lock_bh(wrs_db);
+
+ wrs_sta->he_minrate = he_minrate;
+ cl_wrs_tables_build(cl_hw, wrs_sta, &wrs_sta->tx_su_params);
+
+ cl_wrs_unlock_bh(wrs_db);
+
+ for (bw = 0; bw < wrs_sta->max_rate_cap.bw; bw++) {
+ for (nss = 0; nss < wrs_sta->max_rate_cap.nss; nss++) {
+ for (mcs = 0; mcs < wrs_sta->max_rate_cap.mcs; mcs++) {
+ data_rate_x10 = cl_data_rates_get_x10(WRS_MODE_HE, bw,
+ nss, mcs,
+ WRS_GI_LONG);
+ if (data_rate_x10 >= (he_minrate * 10)) {
+ cl_rate_ctrl_set_default_per_he_minrate(cl_hw, bw,
+ nss, mcs,
+ WRS_GI_LONG);
+ return;
+ }
+ }
+ }
+ }
+}
+
+static void _cl_wrs_api_recovery(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ u16 fallback_rate_idx = wrs_params->table[wrs_params->rate_idx].rate_down.rate_idx;
+ struct cl_wrs_rate *rate_fallback = &wrs_params->table[fallback_rate_idx].rate;
+ struct cl_wrs_rate_params *rate_params = &wrs_params->rate_params;
+ bool is_mu_valid = wrs_params->is_mu_valid;
+
+ cl_wrs_rate_param_set(cl_hw, wrs_sta, wrs_params, rate_params,
+ rate_fallback, is_mu_valid, true);
+}
+
+void cl_wrs_api_recovery(struct cl_hw *cl_hw)
+{
+ struct cl_sta *cl_sta = NULL;
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_wrs_sta *wrs_sta = NULL;
+
+ cl_wrs_lock_bh(wrs_db);
+ cl_sta_lock(cl_hw);
+
+ list_for_each_entry(cl_sta, &cl_hw->cl_sta_db.head, list) {
+ wrs_sta = &cl_sta->wrs_sta;
+
+ _cl_wrs_api_recovery(cl_hw, wrs_sta, &wrs_sta->tx_su_params);
+
+ if (wrs_sta->rx_params)
+ _cl_wrs_api_recovery(cl_hw, wrs_sta, wrs_sta->rx_params);
+ }
+
+ cl_sta_unlock(cl_hw);
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+void cl_wrs_api_beamforming_sync(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_wrs_params *wrs_params = &cl_sta->wrs_sta.tx_su_params;
+ u8 up_idx = 0;
+ u16 rate_idx = wrs_params->rate_idx;
+
+ cl_wrs_lock(wrs_db);
+
+ for (up_idx = 0; up_idx < WRS_TABLE_NODE_UP_MAX; up_idx++)
+ wrs_params->table[rate_idx].rate_up[up_idx].time_th = WRS_INIT_MSEC_WEIGHT_UP;
+
+ cl_wrs_unlock(wrs_db);
+
+ wrs_pr_info(wrs_db, "[%s] sta %u - beamforming sync\n",
+ WRS_TYPE_STR(wrs_params->type), cl_sta->sta_idx);
+}
+
+void cl_wrs_api_quick_down_check(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ cl_wrs_lock(wrs_db);
+ cl_wrs_quick_down_check(cl_hw, wrs_db, &cl_sta->wrs_sta, wrs_params);
+ cl_wrs_unlock(wrs_db);
+}
+
+void cl_wrs_api_rate_sync(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ cl_wrs_lock(wrs_db);
+ cl_wrs_rate_param_sync(wrs_db, &cl_sta->wrs_sta, wrs_params);
+ cl_wrs_unlock(wrs_db);
+}
+
+bool cl_wrs_api_up_mcs1(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ bool result = false;
+
+ cl_wrs_lock(wrs_db);
+ result = cl_wrs_up_mcs1(cl_hw, wrs_db, &cl_sta->wrs_sta, wrs_params);
+ cl_wrs_unlock(wrs_db);
+
+ return result;
+}
+
+void cl_wrs_api_set_smps_mode(struct cl_hw *cl_hw, struct ieee80211_sta *sta, const u8 bw)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ u8 min_bw;
+
+ if (sta->smps_mode == IEEE80211_SMPS_STATIC ||
+ sta->smps_mode == IEEE80211_SMPS_DYNAMIC) {
+ /* If RTS is enabled, there is no need to go down to 1ss */
+ if (cl_prot_mode_get(cl_hw) == TXL_PROT_RTS_FW)
+ return;
+
+ wrs_sta->smps_enable = true;
+ } else if (sta->smps_mode == IEEE80211_SMPS_OFF && wrs_sta->smps_enable) {
+ wrs_sta->smps_enable = false;
+ }
+
+ cl_wrs_lock_bh(wrs_db);
+ min_bw = min_t(u8, bw, wrs_db->max_cap.bw);
+
+ if (wrs_sta->max_rate_cap.bw != min_bw) {
+ wrs_sta->max_rate_cap.bw = min_bw;
+ wrs_pr_trace(wrs_db, "[TX_SU] SMPS mode: sta %u, bw %u\n",
+ wrs_sta->sta_idx, min_bw);
+ cl_wrs_tables_build(cl_hw, wrs_sta, &wrs_sta->tx_su_params);
+ }
+
+ cl_wrs_unlock_bh(wrs_db);
+}
+
+u16 cl_wrs_api_get_tx_sta_data_rate(struct cl_sta *cl_sta)
+{
+ return cl_sta->wrs_sta.tx_su_params.data_rate;
+}
+
+static enum rate_info_bw cl_wrs_mode_to_nl80211_rateinfo_bw(u8 bw)
+{
+ switch (bw) {
+ case CHNL_BW_20:
+ return RATE_INFO_BW_20;
+ case CHNL_BW_40:
+ return RATE_INFO_BW_40;
+ case CHNL_BW_80:
+ return RATE_INFO_BW_80;
+ case CHNL_BW_160:
+ return RATE_INFO_BW_160;
+ default:
+ return RATE_INFO_BW_20;
+ }
+}
+
+void cl_wrs_fill_sinfo_rates(struct rate_info *rate_info,
+ const struct cl_wrs_params *wrs_params,
+ const struct cl_sta *sta)
+{
+ rate_info->bw = cl_wrs_mode_to_nl80211_rateinfo_bw(wrs_params->rate_params.bw);
+ rate_info->mcs = wrs_params->rate_params.mcs;
+ rate_info->nss = wrs_params->rate_params.nss + 1;
+ rate_info->he_gi = wrs_params->rate_params.gi;
+
+ rate_info->flags = 0;
+ /* GI = 0.4 us */
+ if (wrs_params->rate_params.mode < WRS_MODE_HE && wrs_params->rate_params.gi == 1)
+ rate_info->flags |= RATE_INFO_FLAGS_SHORT_GI;
+
+ if (wrs_params->rate_params.mode == WRS_MODE_HT)
+ rate_info->flags |= RATE_INFO_FLAGS_MCS;
+
+ if (wrs_params->rate_params.mode == WRS_MODE_VHT)
+ rate_info->flags |= RATE_INFO_FLAGS_VHT_MCS;
+
+ if (wrs_params->rate_params.mode == WRS_MODE_HE) {
+ enum cl_mu_ofdma_ru_type ru_type = sta->rate_ctrl_he.field.ru_type;
+
+ rate_info->flags |= RATE_INFO_FLAGS_HE_MCS;
+ if (ru_type) {
+ rate_info->he_ru_alloc = cl_ru_type_to_nl80211_he_ru_alloc(ru_type);
+ rate_info->bw = RATE_INFO_BW_HE_RU;
+ }
+ }
+}
+
+/*
+ * Section #1:
+ * rate based on rssi.
+ */
+static s8 rssi_threshold_he[WRS_MCS_MAX_HE] = {
+ -35, -40, -45, -50, -55, -60, -65, -70, -75, -80, -85, -90
+};
+
+static s8 rssi_threshold_vht[WRS_MCS_MAX_VHT] = {
+ -36, -42, -48, -54, -60, -66, -72, -78, -84, -90
+};
+
+static s8 rssi_threshold_ht[WRS_MCS_MAX_HT] = {
+ -34, -42, -50, -58, -66, -74, -82, -90
+};
+
+static s8 rssi_threshold_ofdm[WRS_MCS_MAX_OFDM] = {
+ -34, -42, -50, -58, -66, -74, -82, -90
+};
+
+static s8 rssi_threshold_cck[WRS_MCS_MAX_CCK] = {
+ -45, -60, -75, -90
+};
+
+static u16 cl_wrs_rssi_find_rate_ht_vht_he(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ s8 *rssi_sort,
+ s8 *thresholds)
+{
+ s8 max_ss = (s8)wrs_sta->max_rate_cap.nss;
+ s8 nss = 0;
+ u8 max_bw = wrs_sta->max_rate_cap.bw;
+ u8 bw = 0;
+ u8 max_mcs = wrs_sta->max_rate_cap.mcs;
+ u8 mcs = 0;
+ u8 gi = WRS_GI_LONG;
+ u8 selected_mcs = 0;
+ u8 selected_nss = 0;
+ u8 selected_bw = 0;
+ u8 i = 0;
+ u16 rate_idx = 0;
+ u16 data_rate = 0;
+ u16 max_data_rate = 0;
+
+ if (max_bw > cl_hw->conf->ci_wrs_max_bw)
+ max_bw = cl_hw->conf->ci_wrs_max_bw;
+
+ for (i = 0; i <= max_mcs; i++) {
+ mcs = max_mcs - i;
+
+ for (nss = max_ss; nss >= 0; nss--) {
+ if (rssi_sort[nss] <= thresholds[i])
+ continue;
+
+ /* In last level decrease BW */
+ bw = ((i == max_mcs) && (max_bw > CHNL_BW_20)) ? (max_bw - 1) : max_bw;
+
+ if (wrs_sta->mode == WRS_MODE_HE) {
+ data_rate = data_rate_he_x10[nss][bw][mcs][WRS_GI_LONG];
+ } else {
+ if (wrs_sta->mode == WRS_MODE_VHT) {
+ /* 160MHz in VHT is valid only for 1/2 SS */
+ if (nss >= WRS_SS_3 && bw == CHNL_BW_160)
+ bw = CHNL_BW_80;
+
+ /* BW 80, 3 SS MCS 6 is invalid in VHT */
+ if (bw == CHNL_BW_80 &&
+ nss == WRS_SS_3 &&
+ mcs == WRS_MCS_6)
+ continue;
+ }
+
+ data_rate = data_rate_ht_vht_x10[bw][nss][mcs][gi];
+ }
+
+ if (data_rate > max_data_rate) {
+ selected_mcs = mcs;
+ selected_nss = nss;
+ selected_bw = bw;
+ max_data_rate = data_rate;
+ rate_idx = cl_wrs_tables_find_rate_idx(wrs_params,
+ bw, nss, mcs, gi);
+ }
+
+ break;
+ }
+ }
+
+ return rate_idx;
+}
+
+static u16 cl_wrs_rssi_find_rate_cck_ofdm(struct cl_wrs_sta *wrs_sta,
+ s8 *rssi_sort, s8 *thresholds)
+{
+ struct cl_wrs_params *wrs_params = &wrs_sta->tx_su_params;
+ u8 max_mcs = wrs_sta->max_rate_cap.mcs;
+ u8 mcs = 0;
+ u8 i = 0;
+
+ for (i = 0; i <= max_mcs; i++) {
+ mcs = max_mcs - i;
+
+ if (rssi_sort[WRS_SS_1] > thresholds[i])
+ return cl_wrs_tables_find_rate_idx(wrs_params,
+ CHNL_BW_20, WRS_SS_1, mcs, WRS_GI_LONG);
+ }
+
+ return 0;
+}
+
+u16 cl_wrs_rssi_find_rate(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ s8 *rssi_sort)
+{
+ u16 rate_idx = 0;
+
+ if (wrs_params->initial_rate_idx != WRS_INVALID_RATE)
+ return wrs_params->initial_rate_idx;
+
+ switch (wrs_sta->mode) {
+ case WRS_MODE_HE:
+ rate_idx = cl_wrs_rssi_find_rate_ht_vht_he(cl_hw, wrs_db, wrs_sta, wrs_params,
+ rssi_sort, rssi_threshold_he);
+ break;
+ case WRS_MODE_VHT:
+ rate_idx = cl_wrs_rssi_find_rate_ht_vht_he(cl_hw, wrs_db, wrs_sta, wrs_params,
+ rssi_sort, rssi_threshold_vht);
+ break;
+ case WRS_MODE_HT:
+ rate_idx = cl_wrs_rssi_find_rate_ht_vht_he(cl_hw, wrs_db, wrs_sta, wrs_params,
+ rssi_sort, rssi_threshold_ht);
+ break;
+ case WRS_MODE_OFDM:
+ rate_idx = cl_wrs_rssi_find_rate_cck_ofdm(wrs_sta, rssi_sort,
+ rssi_threshold_ofdm);
+ break;
+ case WRS_MODE_CCK:
+ rate_idx = cl_wrs_rssi_find_rate_cck_ofdm(wrs_sta, rssi_sort,
+ rssi_threshold_cck);
+ break;
+ default:
+ break;
+ }
+
+ if (rate_idx == WRS_INVALID_RATE)
+ rate_idx = 0;
+
+ wrs_pr_trace(wrs_db,
+ "[%s] Select rate based rssi - sta=%u, rssi [%d,%d,%d,%d], "
+ "rate_idx=%u, bw=%u, nss=%u, mcs=%u\n",
+ WRS_TYPE_STR(wrs_params->type),
+ wrs_sta->sta_idx,
+ rssi_sort[0],
+ rssi_sort[1],
+ rssi_sort[2],
+ rssi_sort[3],
+ rate_idx,
+ wrs_params->table[rate_idx].rate.bw,
+ wrs_params->table[rate_idx].rate.nss,
+ wrs_params->table[rate_idx].rate.mcs);
+
+ return rate_idx;
+}
+
+/*
+ * Section #2:
+ * rssi protect.
+ */
+static void cl_wrs_rssi_prot_set_avg(struct cl_wrs_sta *wrs_sta, s8 avg)
+{
+ struct cl_wrs_rssi_prot_db *rssi_prot_db = &wrs_sta->rssi_prot_db;
+
+ memset(rssi_prot_db->samples_old, avg, WRS_RSSI_PROTECT_BUF_SZ_OLD);
+ memset(rssi_prot_db->samples_new, avg, WRS_RSSI_PROTECT_BUF_SZ_NEW);
+ rssi_prot_db->sum = avg << WRS_RSSI_PROTECT_SHIFT;
+}
+
+static s8 cl_wrs_rssi_prot_add_smpl(struct cl_wrs_sta *wrs_sta, s8 rssi_eq)
+{
+ struct cl_wrs_rssi_prot_db *rssi_prot_db = &wrs_sta->rssi_prot_db;
+ u8 curr_idx_old = rssi_prot_db->curr_idx_old;
+ u8 curr_idx_new = rssi_prot_db->curr_idx_new;
+
+ rssi_prot_db->sum +=
+ rssi_prot_db->samples_new[curr_idx_new] - rssi_prot_db->samples_old[curr_idx_old];
+ rssi_prot_db->samples_old[curr_idx_old] = rssi_prot_db->samples_new[curr_idx_new];
+ rssi_prot_db->samples_new[curr_idx_new] = rssi_eq;
+
+ rssi_prot_db->curr_idx_old =
+ WRS_INC_POW2(rssi_prot_db->curr_idx_old, WRS_RSSI_PROTECT_BUF_SZ_OLD);
+ WRS_INC(rssi_prot_db->curr_idx_new, WRS_RSSI_PROTECT_BUF_SZ_NEW);
+
+ return (s8)(wrs_sta->rssi_prot_db.sum >> WRS_RSSI_PROTECT_SHIFT);
+}
+
+static bool cl_wrs_rssi_prot_decision_up(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ s8 rssi_avg, s8 rssi_eq,
+ s8 *rssi_sort, u8 up_rate_idx)
+{
+ /* Decide UP only if all new samples are greater than old average */
+ s8 *samples_new = wrs_sta->rssi_prot_db.samples_new;
+ s8 up_thr = rssi_avg + wrs_db->rssi_protect_up_thr;
+ u8 i = 0;
+
+ for (i = 0; i < WRS_RSSI_PROTECT_BUF_SZ_NEW; i++)
+ if (samples_new[i] <= up_thr)
+ return false;
+
+ if (wrs_db->rssi_protect_mode == WRS_RSSI_PROT_MODE_RSSI) {
+ u16 rate_idx_old = wrs_params->rate_idx;
+ u16 rate_idx_new = cl_wrs_rssi_find_rate(cl_hw, wrs_db, wrs_sta,
+ wrs_params, rssi_sort);
+ struct cl_wrs_rate *rate_old = &wrs_params->table[rate_idx_old].rate;
+ struct cl_wrs_rate *rate_new = &wrs_params->table[rate_idx_new].rate;
+ u16 data_rate_old = cl_data_rates_get_x10(wrs_sta->mode, rate_old->bw,
+ rate_old->nss, rate_old->mcs,
+ rate_old->gi);
+ u16 data_rate_new = cl_data_rates_get_x10(wrs_sta->mode, rate_new->bw,
+ rate_new->nss, rate_new->mcs,
+ rate_new->gi);
+
+ if (rate_idx_old == rate_idx_new || data_rate_old >= data_rate_new)
+ rate_idx_new = up_rate_idx;
+
+ wrs_pr_info(wrs_db, "[%s] Increase rate based on RSSI - old [%u], new [%u]\n",
+ WRS_TYPE_STR(wrs_params->type), rate_idx_old, rate_idx_new);
+ cl_wrs_decision_update(wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_UP_RSSI, rate_idx_new);
+ cl_wrs_rate_params_update(cl_hw, wrs_db, wrs_sta,
+ wrs_params, rate_idx_new, true, false);
+ } else {
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_UP_RSSI, up_rate_idx);
+ }
+
+ cl_wrs_tables_reset(wrs_db, wrs_sta, wrs_params);
+ cl_wrs_rssi_prot_set_avg(wrs_sta, rssi_eq);
+
+ return true;
+}
+
+static bool cl_wrs_rssi_prot_decision_down(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ s8 rssi_avg, s8 rssi_eq,
+ s8 *rssi_sort, u8 down_rate_idx)
+{
+ /* Decide DOWN only if all new samples are smaller than old average */
+ s8 *samples_new = wrs_sta->rssi_prot_db.samples_new;
+ s8 dn_thr = rssi_avg - wrs_db->rssi_protect_dn_thr;
+ u8 i = 0;
+
+ if (wrs_params->rate_idx == 0)
+ return false;
+
+ for (i = 0; i < WRS_RSSI_PROTECT_BUF_SZ_NEW; i++)
+ if (samples_new[i] >= dn_thr)
+ return false;
+
+ if (wrs_db->rssi_protect_mode == WRS_RSSI_PROT_MODE_RSSI) {
+ u16 rate_idx_old = wrs_params->rate_idx;
+ u16 rate_idx_new = cl_wrs_rssi_find_rate(cl_hw, wrs_db, wrs_sta,
+ wrs_params, rssi_sort);
+ struct cl_wrs_rate *rate_old = &wrs_params->table[rate_idx_old].rate;
+ struct cl_wrs_rate *rate_new = &wrs_params->table[rate_idx_new].rate;
+ u16 data_rate_old = cl_data_rates_get_x10(wrs_sta->mode, rate_old->bw,
+ rate_old->nss, rate_old->mcs,
+ rate_old->gi);
+ u16 data_rate_new = cl_data_rates_get_x10(wrs_sta->mode, rate_new->bw,
+ rate_new->nss, rate_new->mcs,
+ rate_new->gi);
+
+ if (rate_idx_old == rate_idx_new || data_rate_old <= data_rate_new)
+ rate_idx_new = down_rate_idx;
+
+ wrs_pr_info(wrs_db, "[%s] Decrease rate based on RSSI - old [%u], new [%u]\n",
+ WRS_TYPE_STR(wrs_params->type), rate_idx_old, rate_idx_new);
+ cl_wrs_decision_update(wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_DOWN_RSSI, rate_idx_new);
+ cl_wrs_rate_params_update(cl_hw, wrs_db, wrs_sta, wrs_params,
+ rate_idx_new, true, false);
+ } else {
+ cl_wrs_decision_make(cl_hw, wrs_db, wrs_sta, wrs_params,
+ WRS_DECISION_DOWN_RSSI, down_rate_idx);
+ }
+
+ cl_wrs_tables_reset(wrs_db, wrs_sta, wrs_params);
+ cl_wrs_rssi_prot_set_avg(wrs_sta, rssi_eq);
+
+ return true;
+}
+
+void cl_wrs_rssi_prot_start(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ s8 rssi_sort[MAX_ANTENNAS] = {0};
+ s8 rssi_eq = 0;
+
+ if (!cl_hw->wrs_db.rssi_protect_en)
+ return;
+
+ rssi_eq = cl_wrs_rssi_eq_calc(cl_hw, wrs_sta, false, rssi_sort);
+ cl_wrs_rssi_prot_set_avg(wrs_sta, rssi_eq);
+}
+
+bool cl_wrs_rssi_prot_decision(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ bool up_rate_valid,
+ u8 up_rate_idx, u8 down_rate_idx)
+{
+ s8 rssi_avg = 0;
+ s8 rssi_eq = 0;
+ s8 rssi_sort[MAX_ANTENNAS] = {0};
+
+ rssi_eq = cl_wrs_rssi_eq_calc(cl_hw, wrs_sta, true, rssi_sort);
+ rssi_avg = cl_wrs_rssi_prot_add_smpl(wrs_sta, rssi_eq);
+
+ if (up_rate_valid)
+ if (cl_wrs_rssi_prot_decision_up(cl_hw, wrs_db, wrs_sta, wrs_params, rssi_avg,
+ rssi_eq, rssi_sort, up_rate_idx))
+ return true;
+
+ return cl_wrs_rssi_prot_decision_down(cl_hw, wrs_db, wrs_sta, wrs_params, rssi_avg,
+ rssi_eq, rssi_sort, down_rate_idx);
+}
+
+static void cl_wrs_sta_info_set_he(struct cl_wrs_sta *wrs_sta)
+{
+ wrs_sta->mode = WRS_MODE_HE;
+
+ wrs_sta->gi_cap[CHNL_BW_20] = WRS_GI_VSHORT;
+ wrs_sta->gi_cap[CHNL_BW_40] = WRS_GI_VSHORT;
+ wrs_sta->gi_cap[CHNL_BW_80] = WRS_GI_VSHORT;
+ wrs_sta->gi_cap[CHNL_BW_160] = WRS_GI_VSHORT;
+}
+
+static void cl_wrs_sta_info_set_vht(struct cl_wrs_sta *wrs_sta,
+ struct ieee80211_sta_vht_cap *vht_cap,
+ struct ieee80211_sta_ht_cap *ht_cap)
+{
+ wrs_sta->mode = WRS_MODE_VHT;
+
+ wrs_sta->gi_cap[CHNL_BW_20] =
+ (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) ? WRS_GI_SHORT : WRS_GI_LONG;
+ wrs_sta->gi_cap[CHNL_BW_40] =
+ (ht_cap->cap & IEEE80211_HT_CAP_SGI_40) ? WRS_GI_SHORT : WRS_GI_LONG;
+ wrs_sta->gi_cap[CHNL_BW_80] =
+ (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_80) ? WRS_GI_SHORT : WRS_GI_LONG;
+ wrs_sta->gi_cap[CHNL_BW_160] =
+ (vht_cap->cap & IEEE80211_VHT_CAP_SHORT_GI_160) ? WRS_GI_SHORT : WRS_GI_LONG;
+}
+
+static void cl_wrs_sta_info_set_ht(struct cl_wrs_sta *wrs_sta,
+ struct ieee80211_sta_ht_cap *ht_cap)
+{
+ wrs_sta->mode = WRS_MODE_HT;
+
+ wrs_sta->gi_cap[CHNL_BW_20] =
+ (ht_cap->cap & IEEE80211_HT_CAP_SGI_20) ? WRS_GI_SHORT : WRS_GI_LONG;
+ wrs_sta->gi_cap[CHNL_BW_40] =
+ (ht_cap->cap & IEEE80211_HT_CAP_SGI_40) ? WRS_GI_SHORT : WRS_GI_LONG;
+}
+
+static void cl_wrs_sta_info_set_legacy(struct cl_wrs_sta *wrs_sta,
+ struct ieee80211_sta *sta)
+{
+ if (sta->supp_rates[NL80211_BAND_5GHZ] ||
+ (sta->supp_rates[NL80211_BAND_2GHZ] & 0xFF0))
+ wrs_sta->mode = WRS_MODE_OFDM;
+ else
+ wrs_sta->mode = WRS_MODE_CCK;
+}
+
+static void cl_wrs_sta_info_set(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ struct ieee80211_sta *sta)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap;
+ struct ieee80211_sta_vht_cap *vht_cap = &sta->vht_cap;
+ struct ieee80211_sta_he_cap *he_cap = &sta->he_cap;
+ u8 bw = min_t(u8, sta->bandwidth, wrs_db->max_cap.bw);
+
+ if (he_cap->has_he)
+ cl_wrs_sta_info_set_he(wrs_sta);
+ else if (vht_cap->vht_supported && ht_cap->ht_supported)
+ cl_wrs_sta_info_set_vht(wrs_sta, vht_cap, ht_cap);
+ else if (ht_cap->ht_supported)
+ cl_wrs_sta_info_set_ht(wrs_sta, ht_cap);
+ else
+ cl_wrs_sta_info_set_legacy(wrs_sta, sta);
+
+ wrs_sta->max_rate_cap.nss = min_t(u8, sta->rx_nss, WRS_SS_MAX) - 1;
+
+ if (cl_band_is_24g(cl_hw))
+ wrs_sta->max_rate_cap.bw = min(bw, wrs_db->coex_bw);
+ else
+ wrs_sta->max_rate_cap.bw = bw;
+
+ wrs_sta->assoc_bw = bw;
+}
+
+#define TWO_BITS_MASK 0x3
+
+static u8 cl_wrs_calc_max_nss(u16 rx_mcs, u8 max_nss)
+{
+ u8 i, nss = 0;
+
+ for (i = 0; i <= max_nss; i++) {
+ if (((rx_mcs >> (2 * i)) & TWO_BITS_MASK) != IEEE80211_HE_MCS_NOT_SUPPORTED)
+ nss++;
+ else
+ break;
+ }
+
+ return nss;
+}
+
+static void cl_supported_rates_he_cap(struct cl_wrs_db *wrs_db, struct ieee80211_sta *sta)
+{
+ struct cl_sta *cl_sta = (struct cl_sta *)sta->drv_priv;
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ struct ieee80211_he_mcs_nss_supp *mcs_nss = &sta->he_cap.he_mcs_nss_supp;
+ int mcs = 0;
+ int supported_mcs = 0;
+ u16 rx_mcs = 0;
+ u8 max_nss = wrs_sta->max_rate_cap.nss;
+ u8 nss = 0, bw = 0;
+ u8 max_nss_80, max_nss_160;
+
+ for (bw = 0; bw <= wrs_sta->max_rate_cap.bw; bw++) {
+ if (bw < CHNL_BW_160) {
+ rx_mcs = le16_to_cpu(mcs_nss->rx_mcs_80);
+ } else if (bw == CHNL_BW_160) {
+ max_nss_80 = cl_wrs_calc_max_nss(le16_to_cpu(mcs_nss->rx_mcs_80),
+ max_nss) + 1;
+ rx_mcs = le16_to_cpu(mcs_nss->rx_mcs_160);
+ max_nss_160 = cl_wrs_calc_max_nss(rx_mcs, max_nss) + 1;
+ max_nss = max_nss * max_nss_160 / max_nss_80;
+ } else {
+ wrs_pr_err(wrs_db, "bw %u is not supported\n", bw);
+ }
+
+ for (nss = 0; nss <= max_nss; nss++) {
+ switch ((rx_mcs >> (2 * nss)) & TWO_BITS_MASK) {
+ case IEEE80211_HE_MCS_SUPPORT_0_7:
+ supported_mcs = WRS_MCS_7;
+ break;
+ case IEEE80211_HE_MCS_SUPPORT_0_9:
+ supported_mcs = WRS_MCS_9;
+ break;
+ case IEEE80211_HE_MCS_SUPPORT_0_11:
+ supported_mcs = WRS_MCS_11;
+ break;
+ case IEEE80211_HE_MCS_NOT_SUPPORTED:
+ supported_mcs = -1;
+ break;
+ }
+
+ for (mcs = 0; mcs <= supported_mcs; mcs++)
+ cl_wrs_sta_set_supported_rate(wrs_sta, bw, nss, mcs);
+ }
+ }
+}
+
+static void cl_supported_rates_vht_cap(struct ieee80211_sta *sta)
+{
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ u16 mcs_map = le16_to_cpu(sta->vht_cap.vht_mcs.rx_mcs_map);
+ u8 bw = 0;
+ u8 nss = 0;
+ int mcs = 0;
+ int supported_mcs = 0;
+
+ for (bw = 0; bw <= wrs_sta->max_rate_cap.bw; bw++) {
+ for (nss = 0; nss <= wrs_sta->max_rate_cap.nss; nss++) {
+ switch ((mcs_map >> (2 * nss)) & TWO_BITS_MASK) {
+ case 0:
+ supported_mcs = WRS_MCS_7;
+ break;
+ case 1:
+ supported_mcs = WRS_MCS_8;
+ break;
+ case 2:
+ supported_mcs = WRS_MCS_9;
+ break;
+ case 3:
+ supported_mcs = -1;
+ break;
+ }
+
+ for (mcs = 0; mcs <= supported_mcs; mcs++)
+ cl_wrs_sta_set_supported_rate(wrs_sta, bw, nss, mcs);
+ }
+ }
+}
+
+static void cl_supported_rates_ht_cap(struct ieee80211_sta *sta)
+{
+ struct ieee80211_sta_ht_cap *ht_cap = &sta->ht_cap;
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ u8 bw = 0;
+ u8 nss = 0;
+ u8 mcs = 0;
+
+ for (mcs = 0; mcs <= WRS_MCS_7; mcs++) {
+ for (nss = 0; nss <= wrs_sta->max_rate_cap.nss; nss++) {
+ if ((ht_cap->mcs.rx_mask[nss] & (1 << mcs)) == 0)
+ continue;
+
+ for (bw = 0; bw <= wrs_sta->max_rate_cap.bw; bw++)
+ cl_wrs_sta_set_supported_rate(wrs_sta, bw, nss, mcs);
+ }
+ }
+}
+
+static void cl_supported_rates_legacy_cap(struct cl_hw *cl_hw,
+ struct ieee80211_sta *sta,
+ u8 max_mcs)
+{
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ u8 mcs = 0;
+
+ for (mcs = 0; mcs < max_mcs; mcs++)
+ if (rate_supported(sta, cl_hw->nl_band, mcs))
+ cl_wrs_sta_set_supported_rate(wrs_sta, CHNL_BW_20, WRS_SS_1, mcs);
+}
+
+void cl_wrs_sta_add(struct cl_hw *cl_hw, struct ieee80211_sta *sta)
+{
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ struct cl_wrs_params *wrs_params = &wrs_sta->tx_su_params;
+
+ wrs_params->type = WRS_TYPE_TX_SU;
+ wrs_params->is_mu_valid = false;
+ wrs_params->rate_idx = WRS_INVALID_RATE;
+ wrs_params->initial_rate_idx = WRS_INVALID_RATE;
+
+ wrs_sta->sta_idx = cl_sta->sta_idx;
+ cl_wrs_rssi_prot_start(cl_hw, cl_sta);
+ cl_wrs_stats_per_init(wrs_params);
+ cl_wrs_sta_info_set(cl_hw, wrs_sta, sta);
+ cl_wrs_sta_capabilities_set(wrs_db, sta);
+ cl_wrs_tables_build(cl_hw, wrs_sta, wrs_params);
+ cl_wrs_sta_select_first_rate(cl_hw, wrs_db, wrs_sta, wrs_params);
+
+ wrs_pr_trace(wrs_db, "[%s] Add station %pM to database (sta_idx %u)\n",
+ WRS_TYPE_STR(wrs_params->type), cl_sta->addr, cl_sta->sta_idx);
+}
+
+void cl_wrs_sta_add_rx(struct cl_hw *cl_hw, struct ieee80211_sta *sta)
+{
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+ struct cl_wrs_params *wrs_params;
+
+ if (wrs_sta->rx_params) {
+ wrs_pr_warn(wrs_db, "[RX] Params already allocated (sta_idx %u)\n",
+ wrs_sta->sta_idx);
+ return;
+ }
+
+ wrs_params = kzalloc(sizeof(*wrs_params), GFP_ATOMIC);
+
+ if (!wrs_params)
+ return;
+
+ wrs_sta->rx_params = wrs_params;
+ wrs_params->type = WRS_TYPE_RX;
+ wrs_params->is_mu_valid = false;
+ wrs_params->rate_idx = WRS_INVALID_RATE;
+ wrs_params->initial_rate_idx = WRS_INVALID_RATE;
+
+ wrs_sta->sta_idx = cl_sta->sta_idx;
+
+ cl_wrs_rssi_prot_start(cl_hw, cl_sta);
+ cl_wrs_stats_per_init(wrs_params);
+ cl_wrs_sta_info_set(cl_hw, wrs_sta, sta);
+ cl_wrs_sta_capabilities_set(wrs_db, sta);
+ cl_wrs_tables_build(cl_hw, wrs_sta, wrs_params);
+ cl_wrs_sta_select_first_rate(cl_hw, wrs_db, wrs_sta, wrs_params);
+ cl_wrs_rx_rate_idle_reset(wrs_params);
+
+ wrs_pr_trace(wrs_db, "[%s] Add station %pM to database (sta_idx %u)\n",
+ WRS_TYPE_STR(wrs_params->type), cl_sta->addr, cl_sta->sta_idx);
+}
+
+static void _cl_wrs_sta_remove(struct cl_wrs_params *wrs_params)
+{
+ if (wrs_params->is_logger_en) {
+ kfree(wrs_params->logger);
+ wrs_params->logger = NULL;
+ }
+
+ kfree(wrs_params->table);
+ wrs_params->table = NULL;
+
+ cl_wrs_stats_per_remove(wrs_params);
+}
+
+static void cl_wrs_sta_remove_rx(struct cl_wrs_db *wrs_db, struct cl_wrs_sta *wrs_sta)
+{
+ struct cl_wrs_params *wrs_params = wrs_sta->rx_params;
+
+ if (!wrs_params) {
+ wrs_pr_warn(wrs_db, "[RX] Params already removed (sta_idx %u)\n",
+ wrs_sta->sta_idx);
+ return;
+ }
+
+ _cl_wrs_sta_remove(wrs_params);
+
+ kfree(wrs_params);
+ wrs_sta->rx_params = NULL;
+
+ wrs_pr_err(wrs_db, "[RX] Remove params (sta_idx %u)\n", wrs_sta->sta_idx);
+}
+
+void cl_wrs_sta_remove(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db, struct cl_sta *cl_sta)
+{
+ struct cl_wrs_sta *wrs_sta = &cl_sta->wrs_sta;
+
+ _cl_wrs_sta_remove(&wrs_sta->tx_su_params);
+
+ if (wrs_sta->rx_params)
+ cl_wrs_sta_remove_rx(wrs_db, wrs_sta);
+ wrs_pr_trace(wrs_db, "Remove station %pM from database (sta_idx %u)\n",
+ cl_sta->addr, cl_sta->sta_idx);
+}
+
+struct cl_wrs_sta *cl_wrs_sta_get(struct cl_hw *cl_hw, u8 sta_idx)
+{
+ struct cl_sta *cl_sta = cl_sta_get(cl_hw, sta_idx);
+
+ return cl_sta ? &cl_sta->wrs_sta : NULL;
+}
+
+void cl_wrs_sta_select_first_rate(struct cl_hw *cl_hw, struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta, struct cl_wrs_params *wrs_params)
+{
+ if (wrs_db->is_fixed_rate) {
+ s8 *fixed_rate = cl_hw->conf->ci_wrs_fixed_rate;
+
+ cl_wrs_fixed_rate_set(cl_hw, wrs_db, wrs_sta, wrs_params, wrs_db->is_fixed_rate,
+ fixed_rate[WRS_FIXED_PARAM_MODE],
+ fixed_rate[WRS_FIXED_PARAM_BW],
+ fixed_rate[WRS_FIXED_PARAM_NSS],
+ fixed_rate[WRS_FIXED_PARAM_MCS],
+ fixed_rate[WRS_FIXED_PARAM_GI], 0);
+ } else {
+ struct cl_sta *cl_sta = container_of(wrs_sta, struct cl_sta, wrs_sta);
+ struct cl_wrs_rate_params *vif_fixed_params = &cl_sta->cl_vif->fixed_params;
+
+ if (vif_fixed_params->is_fixed)
+ cl_wrs_fixed_rate_set(cl_hw, wrs_db, wrs_sta, wrs_params,
+ WRS_FIXED_FALLBACK_DIS,
+ vif_fixed_params->mode,
+ vif_fixed_params->bw,
+ vif_fixed_params->nss,
+ vif_fixed_params->mcs,
+ vif_fixed_params->gi, 0);
+ }
+
+ if (!wrs_params->is_fixed_rate) {
+ bool result = cl_wrs_set_rate_idle(cl_hw, wrs_db, wrs_sta, wrs_params);
+
+ /*
+ * If new rate wasn't selected according to
+ * rssi (no samples) or data packets set rate to lowest possible
+ */
+ if (!result)
+ cl_wrs_rate_params_update(cl_hw, wrs_db, wrs_sta,
+ wrs_params, 0, false, false);
+ }
+}
+
+void cl_wrs_sta_capabilities_set(struct cl_wrs_db *wrs_db, struct ieee80211_sta *sta)
+{
+ struct cl_sta *cl_sta = IEEE80211_STA_TO_CL_STA(sta);
+ enum cl_wrs_mode mode = cl_sta->wrs_sta.mode;
+
+ if (mode == WRS_MODE_HE) {
+ cl_supported_rates_he_cap(wrs_db, sta);
+ } else if (mode == WRS_MODE_VHT) {
+ cl_supported_rates_vht_cap(sta);
+ } else if (mode == WRS_MODE_HT) {
+ cl_supported_rates_ht_cap(sta);
+ } else {
+ struct cl_hw *cl_hw = cl_sta->cl_vif->cl_hw;
+ u8 max_mcs = (mode == WRS_MODE_OFDM) ? WRS_MCS_MAX_OFDM : WRS_MCS_MAX_CCK;
+
+ cl_supported_rates_legacy_cap(cl_hw, sta, max_mcs);
+ }
+}
+
+void cl_wrs_sta_set_supported_rate(struct cl_wrs_sta *wrs_sta, u8 bw, u8 nss, u8 mcs)
+{
+ u8 rate_idx = mcs + (nss * WRS_MCS_MAX);
+
+ wrs_sta->supported_rates[bw] |= BIT(rate_idx);
+}
+
+static struct cl_wrs_per_stats *cl_wrs_stats_per_entry(struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_per_stats *per_stats = NULL, *per_stats_new = NULL;
+ struct cl_wrs_rate_params *rate_params = &wrs_params->rate_params;
+ struct list_head *list_rates = &wrs_params->list_rates;
+ u8 bw = rate_params->bw;
+ u8 nss = rate_params->nss;
+ u8 mcs = rate_params->mcs;
+ u8 gi = rate_params->gi;
+
+ list_for_each_entry(per_stats, list_rates, list) {
+ if (per_stats->bw != bw ||
+ per_stats->nss != nss ||
+ per_stats->mcs != mcs ||
+ per_stats->gi != gi)
+ continue;
+
+ /*
+ * Move the entry to the beginning of the list, so that it
+ * will be faster to find next time.
+ */
+ if (per_stats != list_entry(list_rates->next, struct cl_wrs_per_stats, list)) {
+ list_del(&per_stats->list);
+ list_add(&per_stats->list, list_rates);
+ }
+
+ return per_stats;
+ }
+
+ /* Entry not found - allocate new entry and add to list */
+ per_stats_new = kzalloc(sizeof(*per_stats_new), GFP_ATOMIC);
+
+ if (!per_stats_new)
+ return NULL;
+
+ per_stats_new->bw = bw;
+ per_stats_new->nss = nss;
+ per_stats_new->mcs = mcs;
+ per_stats_new->gi = gi;
+
+ list_add(&per_stats_new->list, &wrs_params->list_rates);
+
+ return per_stats_new;
+}
+
+void cl_wrs_stats_per_update(struct cl_wrs_db *wrs_db,
+ struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ struct cl_wrs_cntrs *cntrs)
+{
+ u16 curr_rate_idx = wrs_params->rate_idx;
+ struct cl_wrs_table *table_node = &wrs_params->table[curr_rate_idx];
+ struct cl_wrs_per_stats *per_stats = cl_wrs_stats_per_entry(wrs_params);
+
+ if (!per_stats)
+ return;
+
+ wrs_params->frames_total += cntrs->total;
+ wrs_params->fail_total += cntrs->fail;
+ wrs_params->ba_not_rcv_total += cntrs->ba_not_rcv;
+ wrs_params->epr_acc += cntrs->epr_acc;
+
+ per_stats->frames_total += cntrs->total;
+ per_stats->frames_failed += cntrs->fail;
+ per_stats->epr_acc += cntrs->epr_acc;
+
+ if (wrs_params->calc_ba_not_rcv) {
+ per_stats->frames_total += cntrs->ba_not_rcv;
+ per_stats->frames_failed += cntrs->ba_not_rcv;
+ }
+
+ table_node->frames_total = wrs_params->frames_total;
+ table_node->ba_not_rcv_total = wrs_params->ba_not_rcv_total;
+ table_node->epr_acc = wrs_params->epr_acc;
+}
+
+void cl_wrs_stats_per_init(struct cl_wrs_params *wrs_params)
+{
+ INIT_LIST_HEAD(&wrs_params->list_rates);
+}
+
+void cl_wrs_stats_per_remove(struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_per_stats *per_stats = NULL, *per_stats_next = NULL;
+
+ list_for_each_entry_safe(per_stats, per_stats_next, &wrs_params->list_rates, list) {
+ list_del(&per_stats->list);
+ kfree(per_stats);
+ }
+}
+
+static struct cl_wrs_table
+ ap_rate_table_he[CHNL_BW_MAX][WRS_SS_MAX][WRS_MCS_MAX_HE][WRS_GI_MAX_HE];
+static struct cl_wrs_table
+ ap_rate_table_ht_vht[CHNL_BW_MAX][WRS_SS_MAX][WRS_MCS_MAX_VHT][WRS_GI_MAX_VHT];
+
+/* Rate indexes sorted by data rate */
+static u16 rate_idx_sorted_by_data_rate_he[WRS_HE_RATE_TABLE_SIZE] = {0};
+static u16 rate_idx_sorted_by_data_rate_ht_vht[WRS_HT_VHT_RATE_TABLE_SIZE] = {0};
+
+static void cl_wrs_extract_rate_idx_vht(u16 idx, u8 *bw, u8 *nss, u8 *mcs, u8 *gi)
+{
+ *gi = idx % WRS_GI_MAX_VHT;
+ idx = (idx - *gi) / WRS_GI_MAX_VHT;
+ *mcs = idx % WRS_MCS_MAX_VHT;
+ idx = (idx - *mcs) / WRS_MCS_MAX_VHT;
+ *nss = idx % WRS_SS_MAX;
+ *bw = (idx - *nss) / CHNL_BW_MAX;
+}
+
+static void cl_wrs_extract_rate_idx_he(u16 idx, u8 *bw, u8 *nss, u8 *mcs, u8 *gi)
+{
+ *gi = idx % WRS_GI_MAX_HE;
+ idx = (idx - *gi) / WRS_GI_MAX_HE;
+ *mcs = idx % WRS_MCS_MAX_HE;
+ idx = (idx - *mcs) / WRS_MCS_MAX_HE;
+ *nss = idx % WRS_SS_MAX;
+ *bw = (idx - *nss) / CHNL_BW_MAX;
+}
+
+static void cl_wrs_tables_build_sorted_ht_vht(void)
+{
+ /* Sort according to HT/VHT data rate */
+ u16 i, j;
+ u8 bw1, nss1, mcs1, gi1, bw2, nss2, mcs2, gi2;
+
+ for (i = 0; i < WRS_HT_VHT_RATE_TABLE_SIZE; i++)
+ rate_idx_sorted_by_data_rate_ht_vht[i] = i;
+
+ for (i = 0; i < WRS_HT_VHT_RATE_TABLE_SIZE - 1; i++) {
+ for (j = 0; j < WRS_HT_VHT_RATE_TABLE_SIZE - i - 1; j++) {
+ cl_wrs_extract_rate_idx_vht(rate_idx_sorted_by_data_rate_ht_vht[j],
+ &bw1, &nss1, &mcs1, &gi1);
+ cl_wrs_extract_rate_idx_vht(rate_idx_sorted_by_data_rate_ht_vht[j + 1],
+ &bw2, &nss2, &mcs2, &gi2);
+
+ if (data_rate_ht_vht_x10[bw1][nss1][mcs1][gi1] >
+ data_rate_ht_vht_x10[bw2][nss2][mcs2][gi2])
+ swap(rate_idx_sorted_by_data_rate_ht_vht[j],
+ rate_idx_sorted_by_data_rate_ht_vht[j + 1]);
+ }
+ }
+}
+
+static void cl_wrs_tables_build_sorted_he(void)
+{
+ /* Sort according to HE data rate */
+ u16 i, j;
+ u8 bw1, nss1, mcs1, gi1, bw2, nss2, mcs2, gi2;
+
+ for (i = 0; i < WRS_HE_RATE_TABLE_SIZE; i++)
+ rate_idx_sorted_by_data_rate_he[i] = i;
+
+ for (i = 0; i < WRS_HE_RATE_TABLE_SIZE - 1; i++) {
+ for (j = 0; j < WRS_HE_RATE_TABLE_SIZE - i - 1; j++) {
+ cl_wrs_extract_rate_idx_he(rate_idx_sorted_by_data_rate_he[j],
+ &bw1, &nss1, &mcs1, &gi1);
+ cl_wrs_extract_rate_idx_he(rate_idx_sorted_by_data_rate_he[j + 1],
+ &bw2, &nss2, &mcs2, &gi2);
+
+ if (data_rate_he_x10[nss1][bw1][mcs1][gi1] >
+ data_rate_he_x10[nss2][bw2][mcs2][gi2])
+ swap(rate_idx_sorted_by_data_rate_he[j],
+ rate_idx_sorted_by_data_rate_he[j + 1]);
+ }
+ }
+}
+
+static u16 idx_to_offt_ht_vht(u32 bw, u32 nss, u32 mcs, u32 gi)
+{
+ if (bw < CHNL_BW_MAX &&
+ nss < WRS_SS_MAX &&
+ mcs < WRS_MCS_MAX_VHT &&
+ gi < WRS_GI_MAX_VHT)
+ return (gi + WRS_GI_MAX_VHT * (mcs + WRS_MCS_MAX_VHT * (nss + bw * WRS_SS_MAX)));
+ else
+ return -1;
+}
+
+static u16 idx_to_offt_he(u32 bw, u32 nss, u32 mcs, u32 gi)
+{
+ if (bw < CHNL_BW_MAX &&
+ nss < WRS_SS_MAX &&
+ mcs < WRS_MCS_MAX_HE &&
+ gi < WRS_GI_MAX_HE)
+ return (gi + WRS_GI_MAX_HE * (mcs + WRS_MCS_MAX_HE * (nss + bw * WRS_SS_MAX)));
+ else
+ return -1;
+}
+
+static u16 find_down_rate_idx(u32 bw, u32 nss, u32 mcs, u32 gi, u16 *data_rates,
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi))
+{
+ u16 idx;
+
+ if (mcs > 0) {
+ idx = idx_to_offt(bw, nss, mcs - 1, gi);
+ if (data_rates[idx])
+ return idx;
+ if (mcs > 1)
+ return idx_to_offt(bw, nss, mcs - 2, gi);
+ }
+
+ if (bw > 0) {
+ idx = idx_to_offt(bw - 1, nss, mcs, gi);
+ if (data_rates[idx])
+ return idx;
+ if (bw > 1)
+ return idx_to_offt(bw - 2, nss, mcs, gi);
+ }
+
+ if (nss > 0) {
+ idx = idx_to_offt(bw, nss - 1, mcs, gi);
+ if (data_rates[idx])
+ return idx;
+ if (nss > 1) {
+ idx = idx_to_offt(bw, nss - 2, mcs, gi);
+ if (data_rates[idx])
+ return idx;
+ }
+ }
+
+ if (gi > 0)
+ return idx_to_offt(bw, nss, mcs, gi - 1);
+
+ return 0;
+}
+
+static u16 find_up_mcs_rate_idx(u32 bw, u32 nss, u32 mcs, u32 gi, u16 *data_rates,
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi))
+{
+ s16 idx = idx_to_offt(bw, nss, mcs + 1, gi);
+
+ if (idx < 0 || !data_rates[idx])
+ idx = idx_to_offt(bw, nss, mcs + 2, gi);
+
+ return (idx < 0) ? WRS_INVALID_RATE : idx;
+}
+
+static u16 find_up_bw_rate_idx(u32 bw, u32 nss, u32 mcs, u32 gi,
+ u16 *data_rates,
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi))
+{
+ s16 cur_data_rate = data_rates[idx_to_offt(bw, nss, mcs, gi)];
+ s16 min_idx = WRS_INVALID_RATE;
+ s16 idx;
+ s32 min_rate_diff = S32_MAX;
+ s32 rate_diff;
+
+ for (idx = idx_to_offt(++bw, nss, mcs, gi); !(idx < 0);
+ idx = idx_to_offt(bw, nss, --mcs, gi)) {
+ /*
+ * If data_rates[idx] == 0, the difference will be negative,
+ * so the condition below will not hold.
+ * Therefore, no need to check this possiblity specifically.
+ */
+ rate_diff = data_rates[idx] - cur_data_rate;
+ if (rate_diff > 0 &&
+ rate_diff < min_rate_diff &&
+ (data_rates[idx] * 100) > (cur_data_rate * WRS_EPR_FACTOR)) {
+ min_rate_diff = rate_diff;
+ min_idx = idx;
+ }
+ }
+
+ return min_idx;
+}
+
+static u16 find_up_nss_rate_idx(u32 bw, u32 nss, u32 mcs, u32 gi,
+ u16 *data_rates,
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi))
+{
+ s16 cur_data_rate = data_rates[idx_to_offt(bw, nss, mcs, gi)];
+ s16 min_idx = WRS_INVALID_RATE;
+ s16 idx;
+ s32 min_rate_diff = S32_MAX;
+ s32 rate_diff;
+
+ for (idx = idx_to_offt(bw, ++nss, mcs, gi); !(idx < 0);
+ idx = idx_to_offt(bw, nss, --mcs, gi)) {
+ /*
+ * If data_rates[idx] == 0, the difference will be negative,
+ * so the condition below will not hold.
+ * Therefore, no need to check this possiblity specifically.
+ */
+ rate_diff = data_rates[idx] - cur_data_rate;
+ if (rate_diff > 0 &&
+ rate_diff < min_rate_diff &&
+ (data_rates[idx] * 100) > (cur_data_rate * WRS_EPR_FACTOR)) {
+ min_rate_diff = rate_diff;
+ min_idx = idx;
+ }
+ }
+
+ return min_idx;
+}
+
+static u16 find_up_bf_rate_idx(u32 bw, u32 nss, u32 mcs, u32 gi,
+ u16 *data_rates,
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi))
+{
+ s16 cur_data_rate = data_rates[idx_to_offt(bw, nss, mcs, gi)];
+ s16 min_idx = WRS_INVALID_RATE;
+ s16 idx;
+ s32 min_rate_diff = S32_MAX;
+ s16 rate_diff;
+
+ for (idx = idx_to_offt(bw, --nss, mcs, gi); !(idx < 0);
+ idx = idx_to_offt(bw, nss, ++mcs, gi)) {
+ /*
+ * If data_rates[idx] == 0, the difference will be negative,
+ * so the condition below will not hold.
+ * Therefore, no need to check this possiblity specifically.
+ */
+ rate_diff = data_rates[idx] - cur_data_rate;
+ if (rate_diff > 0 &&
+ rate_diff < min_rate_diff &&
+ (data_rates[idx] * 100) > (cur_data_rate * WRS_EPR_FACTOR)) {
+ min_rate_diff = rate_diff;
+ min_idx = idx;
+ }
+ }
+
+ return min_idx;
+}
+
+static u16 find_up_gi_rate_idx(u32 bw, u32 nss, u32 mcs, u32 gi,
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi))
+{
+ s16 idx = idx_to_offt(bw, nss, mcs, gi + 1);
+
+ return (idx < 0) ? WRS_INVALID_RATE : idx;
+}
+
+static void _cl_wrs_tables_init(struct cl_wrs_table *ap_rate_table,
+ u16 *data_rates,
+ u32 bw, u32 nss, u32 mcs, u32 gi,
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi))
+{
+ struct cl_wrs_table *cur_entry = NULL;
+ int i = 0;
+ u16 offt = idx_to_offt(bw, nss, mcs, gi);
+
+ cur_entry = &ap_rate_table[offt];
+ cur_entry->rate.bw = bw;
+ cur_entry->rate.nss = nss;
+ cur_entry->rate.mcs = mcs;
+ cur_entry->rate.gi = gi;
+
+ /* If current rate is invalid, mark it as such and skip it. */
+ if (!data_rates[offt]) {
+ cur_entry->rate_down.rate_idx = WRS_INVALID_RATE;
+ cur_entry->rate_down.time_th = WRS_MSEC_WEIGHT_MAX_DOWN;
+
+ for (i = 0; i < WRS_TABLE_NODE_UP_MAX; i++) {
+ cur_entry->rate_up[i].rate_idx = WRS_INVALID_RATE;
+ cur_entry->rate_up[i].time_th = WRS_MSEC_WEIGHT_MAX_UP;
+ }
+
+ return;
+ }
+
+ cur_entry->rate_down.rate_idx =
+ find_down_rate_idx(bw, nss, mcs, gi, data_rates, idx_to_offt);
+ cur_entry->rate_up[WRS_TABLE_NODE_UP_MCS].rate_idx =
+ find_up_mcs_rate_idx(bw, nss, mcs, gi, data_rates, idx_to_offt);
+ cur_entry->rate_up[WRS_TABLE_NODE_UP_BW].rate_idx =
+ find_up_bw_rate_idx(bw, nss, mcs, gi, data_rates, idx_to_offt);
+ cur_entry->rate_up[WRS_TABLE_NODE_UP_NSS].rate_idx =
+ find_up_nss_rate_idx(bw, nss, mcs, gi, data_rates, idx_to_offt);
+ cur_entry->rate_up[WRS_TABLE_NODE_UP_BF].rate_idx =
+ find_up_bf_rate_idx(bw, nss, mcs, gi, data_rates, idx_to_offt);
+ cur_entry->rate_up[WRS_TABLE_NODE_UP_GI].rate_idx =
+ find_up_gi_rate_idx(bw, nss, mcs, gi, idx_to_offt);
+
+ cur_entry->rate_down.time_th = WRS_INIT_MSEC_WEIGHT_DOWN;
+
+ for (i = 0; i < WRS_TABLE_NODE_UP_MAX; i++)
+ cur_entry->rate_up[i].time_th =
+ (cur_entry->rate_up[i].rate_idx == WRS_INVALID_RATE) ?
+ WRS_MSEC_WEIGHT_MAX_UP : WRS_INIT_MSEC_WEIGHT_UP;
+}
+
+static void cl_wrs_tables_init(u8 mode)
+{
+ u32 bw, nss, mcs, gi;
+ u32 max_bw, max_nss, max_mcs, max_gi;
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi);
+ struct cl_wrs_table *ap_rate_table = NULL;
+ u16 *data_rates = NULL;
+
+ if (mode == WRS_MODE_HE) {
+ idx_to_offt = idx_to_offt_he;
+ max_bw = CHNL_BW_MAX;
+ max_nss = WRS_SS_MAX;
+ max_mcs = WRS_MCS_MAX_HE;
+ max_gi = WRS_GI_MAX_HE;
+ ap_rate_table = (struct cl_wrs_table *)ap_rate_table_he;
+ data_rates = (u16 *)data_rate_he_x10;
+ } else if (mode == WRS_MODE_VHT) {
+ idx_to_offt = idx_to_offt_ht_vht;
+ max_bw = CHNL_BW_MAX;
+ max_nss = WRS_SS_MAX;
+ max_mcs = WRS_MCS_MAX_VHT;
+ max_gi = WRS_GI_MAX_VHT;
+ ap_rate_table = (struct cl_wrs_table *)ap_rate_table_ht_vht;
+ data_rates = (u16 *)data_rate_ht_vht_x10;
+ } else {
+ return;
+ }
+
+ for (bw = 0; bw < max_bw; bw++)
+ for (nss = 0; nss < max_nss; nss++)
+ for (mcs = 0; mcs < max_mcs; mcs++)
+ for (gi = 0; gi < max_gi; gi++)
+ _cl_wrs_tables_init(ap_rate_table,
+ data_rates,
+ bw,
+ nss,
+ mcs,
+ gi,
+ idx_to_offt);
+}
+
+void cl_wrs_tables_global_build(void)
+{
+ cl_wrs_tables_init(WRS_MODE_HE);
+ cl_wrs_tables_init(WRS_MODE_VHT);
+ cl_wrs_tables_build_sorted_he();
+ cl_wrs_tables_build_sorted_ht_vht();
+}
+
+void cl_wrs_tables_reset(struct cl_wrs_db *wrs_db, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_table_node *rate_up;
+ u16 rate_idx = 0;
+ u8 up_idx = 0;
+
+ for (rate_idx = 0; rate_idx < wrs_params->table_size; rate_idx++) {
+ if (wrs_params->table[rate_idx].rate_down.rate_idx != WRS_INVALID_RATE)
+ wrs_params->table[rate_idx].rate_down.time_th = WRS_INIT_MSEC_WEIGHT_DOWN;
+ else
+ wrs_params->table[rate_idx].rate_down.time_th = wrs_db->time_th_max_down;
+
+ wrs_params->table[rate_idx].rate_down.quick_up_check = false;
+
+ for (up_idx = 0; up_idx < WRS_TABLE_NODE_UP_MAX; up_idx++) {
+ rate_up = &wrs_params->table[rate_idx].rate_up[up_idx];
+
+ if (rate_up->rate_idx != WRS_INVALID_RATE)
+ rate_up->time_th = WRS_INIT_MSEC_WEIGHT_UP;
+ else
+ rate_up->time_th = wrs_db->time_th_max_up;
+
+ rate_up->quick_up_check = false;
+ }
+
+ wrs_params->table[rate_idx].frames_total = 0;
+ wrs_params->table[rate_idx].ba_not_rcv_total = 0;
+ wrs_params->table[rate_idx].epr_acc = 0;
+ }
+}
+
+static bool cl_wrs_is_rate_valid_he(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params, u8 bw,
+ u8 nss, u8 mcs, u8 gi)
+{
+ /* Disable rates according to ce_he_mcs_nss_supp_tx */
+ if ((cl_hw->conf->ce_he_mcs_nss_supp_tx[nss] + 1) <= mcs)
+ return false;
+
+ /* TB flow doesn't support 0.8us GI */
+ if (WRS_TYPE_IS_RX(wrs_params) && gi == WRS_GI_VSHORT)
+ return false;
+
+ if (data_rate_he_x10[nss][bw][mcs][gi] < (10 * wrs_sta->he_minrate))
+ return false;
+
+ return true;
+}
+
+static bool cl_wrs_is_rate_valid_vht(struct cl_hw *cl_hw, u8 bw, u8 nss, u8 mcs)
+{
+ /* Disable BW160 */
+ if (bw == CHNL_BW_160)
+ return false;
+
+ /* Disable VHT invalid rates (MCS9 20M 1SS, MCS9 20M 2SS, MCS6 80M 3SS, MCS9 20M 4SS) */
+ if (bw == CHNL_BW_20 && mcs == WRS_MCS_9)
+ if (nss == WRS_SS_1 || nss == WRS_SS_2 || nss == WRS_SS_4)
+ return false;
+
+ if (bw == CHNL_BW_80 && mcs == WRS_MCS_6 && nss == WRS_SS_3)
+ return false;
+
+ /* Disable rates according to ce_vht_mcs_nss_supp_tx */
+ if ((cl_hw->conf->ce_vht_mcs_nss_supp_tx[nss] + 1) <= mcs)
+ return false;
+
+ return true;
+}
+
+static bool cl_wrs_is_rate_valid(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params,
+ u8 mode, u8 bw, u8 nss, u8 mcs, u8 gi)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ if (gi > wrs_db->max_cap.gi || gi > wrs_sta->gi_cap[bw])
+ return false;
+
+ if (mode == WRS_MODE_HE)
+ return cl_wrs_is_rate_valid_he(cl_hw, wrs_sta, wrs_params, bw, nss, mcs, gi);
+
+ if (mode == WRS_MODE_VHT)
+ return cl_wrs_is_rate_valid_vht(cl_hw, bw, nss, mcs);
+
+ return true;
+}
+
+static bool cl_wrs_is_rate_supported(u64 *rate_bitmap, u8 bw, u8 nss, u8 mcs)
+{
+ u8 rate_idx = mcs + (nss * WRS_MCS_MAX);
+ u64 mask = BIT(rate_idx);
+
+ return rate_bitmap[bw] & mask;
+}
+
+static bool cl_wrs_tables_is_up_invalid(struct cl_wrs_table *table)
+{
+ /*
+ * The UP_GI is not part of this if condition, because we would
+ * like to set the same up candidate for LGI & SGI (except the
+ * up from LGI to SGI).
+ */
+ return ((table->rate_up[WRS_TABLE_NODE_UP_MCS].rate_idx == WRS_INVALID_RATE) &&
+ (table->rate_up[WRS_TABLE_NODE_UP_BW].rate_idx == WRS_INVALID_RATE) &&
+ (table->rate_up[WRS_TABLE_NODE_UP_NSS].rate_idx == WRS_INVALID_RATE) &&
+ (table->rate_up[WRS_TABLE_NODE_UP_BF].rate_idx == WRS_INVALID_RATE) &&
+ (table->rate_up[WRS_TABLE_NODE_UP_GI].rate_idx == WRS_INVALID_RATE));
+}
+
+static int cl_wrs_tables_max(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ u8 *max_bw, u8 *max_nss, u8 *max_mcs, u8 *max_gi)
+{
+ *max_bw = wrs_sta->max_rate_cap.bw;
+ *max_nss = wrs_sta->smps_enable ? WRS_SS_1 : wrs_sta->max_rate_cap.nss;
+
+ switch (wrs_sta->mode) {
+ case WRS_MODE_CCK:
+ *max_mcs = WRS_MCS_3;
+ *max_gi = WRS_GI_LONG;
+ break;
+ case WRS_MODE_OFDM:
+ *max_mcs = WRS_MCS_7;
+ *max_gi = WRS_GI_LONG;
+ break;
+ case WRS_MODE_HT:
+ *max_mcs = WRS_MCS_7;
+ *max_gi = WRS_GI_SHORT;
+ break;
+ case WRS_MODE_VHT:
+ *max_mcs = WRS_MCS_9;
+ *max_gi = WRS_GI_SHORT;
+ break;
+ case WRS_MODE_HE:
+ *max_mcs = WRS_MCS_11;
+ *max_gi = WRS_GI_VSHORT;
+
+ if (!cl_hw->conf->ce_txldpc_en) {
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+
+ wrs_pr_verbose(wrs_db,
+ "TX LDPC disabled: limit BW to 20MHz and MCS to 9\n");
+ *max_mcs = WRS_MCS_9;
+ *max_bw = CHNL_BW_20;
+ }
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+void cl_wrs_tables_build(struct cl_hw *cl_hw, struct cl_wrs_sta *wrs_sta,
+ struct cl_wrs_params *wrs_params)
+{
+ struct cl_wrs_db *wrs_db = &cl_hw->wrs_db;
+ u8 bw = 0;
+ u8 nss = 0;
+ u8 mcs = 0;
+ u8 gi = 0;
+ u8 max_bw = 0;
+ u8 max_nss = 0;
+ u8 max_mcs = 0;
+ u8 max_gi = 0;
+ u8 up_idx = 0;
+ u8 type = wrs_params->type;
+ u16 rate_idx = 0;
+ u16 new_rate_idx = 0;
+ u16 tmp_rate_idx = 0;
+ u16 max_table_size = 0;
+ u16 new_table_size = 0;
+ u16 (*idx_to_offt)(u32 bw, u32 nss, u32 mcs, u32 gi);
+ u16 *rate_idx_sorted_by_data_rate;
+ struct cl_wrs_table *ap_rate_table;
+ struct cl_wrs_table *new_table = NULL;
+ struct cl_wrs_table_validity *valid_rates = NULL;
+
+ if (cl_wrs_tables_max(cl_hw, wrs_sta, &max_bw, &max_nss, &max_mcs, &max_gi))
+ return;
+
+ if (wrs_sta->mode == WRS_MODE_HE) {
+ max_table_size = WRS_HE_RATE_TABLE_SIZE;
+ idx_to_offt = idx_to_offt_he;
+ ap_rate_table = (struct cl_wrs_table *)ap_rate_table_he;
+ rate_idx_sorted_by_data_rate = rate_idx_sorted_by_data_rate_he;
+ } else {
+ max_table_size = WRS_HT_VHT_RATE_TABLE_SIZE;
+ idx_to_offt = idx_to_offt_ht_vht;
+ ap_rate_table = (struct cl_wrs_table *)ap_rate_table_ht_vht;
+ rate_idx_sorted_by_data_rate = rate_idx_sorted_by_data_rate_ht_vht;
+ }
+
+ valid_rates = kzalloc(max_table_size * sizeof(struct cl_wrs_table_validity), GFP_ATOMIC);
+ if (!valid_rates)
+ goto out;
+
+ wrs_sta->max_rate_cap.mcs = WRS_MCS_0;
+ wrs_sta->max_rate_cap.gi = WRS_GI_LONG;
+
+ for (bw = 0; bw <= max_bw; bw++) {
+ for (nss = 0; nss <= max_nss; nss++) {
+ for (mcs = 0; mcs <= max_mcs; mcs++) {
+ for (gi = 0; gi <= max_gi; gi++) {
+ rate_idx = idx_to_offt(bw, nss, mcs, gi);
+ valid_rates[rate_idx].is_valid =
+ cl_wrs_is_rate_supported(wrs_sta->supported_rates,
+ bw, nss, mcs) &&
+ cl_wrs_is_rate_supported(wrs_db->ap_supported_rates,
+ bw, nss, mcs) &&
+ cl_wrs_is_rate_valid(cl_hw, wrs_sta, wrs_params,
+ wrs_sta->mode,
+ bw, nss, mcs, gi);
+
+ if (!valid_rates[rate_idx].is_valid)
+ continue;
+
+ valid_rates[rate_idx].new_rate_idx = new_table_size;
+ new_table_size++;
+
+ if (WRS_TYPE_IS_TX_MU_MIMO(wrs_params))
+ continue;
+
+ if (mcs > wrs_sta->max_rate_cap.mcs)
+ wrs_sta->max_rate_cap.mcs = mcs;
+
+ if (gi > wrs_sta->max_rate_cap.gi)
+ wrs_sta->max_rate_cap.gi = gi;
+ }
+ }
+ }
+ }
+
+ if (new_table_size == 0) {
+ /* Error - size of table is 0, add single rate (mcs 0, 1 SS, bw 20 Mhz) */
+ wrs_pr_err(wrs_db, "[%s] Table build error - Size of table is 0\n",
+ WRS_TYPE_STR(type));
+ cl_wrs_sta_set_supported_rate(wrs_sta, CHNL_BW_20, WRS_SS_1, WRS_MCS_0);
+ valid_rates[0].new_rate_idx = 0;
+ valid_rates[0].is_valid = 1;
+ new_table_size = 1;
+ }
+
+ new_table = kzalloc(new_table_size * sizeof(struct cl_wrs_table), GFP_ATOMIC);
+
+ if (!new_table)
+ goto out;
+
+ for (rate_idx = 0; rate_idx < max_table_size; rate_idx++) {
+ if (!valid_rates[rate_idx].is_valid)
+ continue;
+
+ memcpy(new_table + new_rate_idx,
+ ap_rate_table + rate_idx,
+ sizeof(struct cl_wrs_table));
+
+ /* Set down rate */
+ tmp_rate_idx = ap_rate_table[rate_idx].rate_down.rate_idx;
+
+ while ((!valid_rates[tmp_rate_idx].is_valid) &&
+ (ap_rate_table[tmp_rate_idx].rate_down.rate_idx != tmp_rate_idx))
+ tmp_rate_idx = ap_rate_table[tmp_rate_idx].rate_down.rate_idx;
+
+ if (valid_rates[tmp_rate_idx].is_valid) {
+ new_table[new_rate_idx].rate_down.rate_idx =
+ valid_rates[tmp_rate_idx].new_rate_idx;
+ } else {
+ u16 i = 0;
+ u16 down_idx = 0;
+ u16 down_rate_idx = new_rate_idx;
+
+ while (new_table[new_rate_idx].rate_down.rate_idx !=
+ rate_idx_sorted_by_data_rate[i]) {
+ down_idx = rate_idx_sorted_by_data_rate[i];
+
+ if (valid_rates[down_idx].is_valid)
+ down_rate_idx = valid_rates[down_idx].new_rate_idx;
+ i++;
+ }
+
+ new_table[new_rate_idx].rate_down.rate_idx = down_rate_idx;
+ }
+
+ /* Set up rates */
+ for (up_idx = 0; up_idx < WRS_TABLE_NODE_UP_MAX; up_idx++) {
+ tmp_rate_idx = new_table[new_rate_idx].rate_up[up_idx].rate_idx;
+
+ if (tmp_rate_idx != WRS_INVALID_RATE &&
+ valid_rates[tmp_rate_idx].is_valid)
+ new_table[new_rate_idx].rate_up[up_idx].rate_idx =
+ valid_rates[tmp_rate_idx].new_rate_idx;
+ else
+ new_table[new_rate_idx].rate_up[up_idx].rate_idx = WRS_INVALID_RATE;
+ }
+
+ /*
+ * In case all the UP rates are invalid, find one available UP
+ * rate based on PHY rate
+ */
+ if (cl_wrs_tables_is_up_invalid(&new_table[new_rate_idx])) {
+ u16 i = 0;
+
+ for (i = 0; i < max_table_size; i++)
+ if (rate_idx == rate_idx_sorted_by_data_rate[i])
+ break;
+
+ i++;
+
+ while (i < max_table_size) {
+ tmp_rate_idx = rate_idx_sorted_by_data_rate[i];
+ if (!valid_rates[tmp_rate_idx].is_valid) {
+ i++;
+ continue;
+ }
+
+ new_table[new_rate_idx].rate_up[WRS_TABLE_NODE_UP_MCS].rate_idx =
+ valid_rates[tmp_rate_idx].new_rate_idx;
+ break;
+ }
+ }
+
+ new_rate_idx++;
+ }
+
+ if (wrs_params->table) {
+ /*
+ * Copy epr_acc, frames_total and ba_not_rcv_total
+ * from the old table to the new table.
+ * Also, if initial_rate_idx is set, find the new
+ * value in the new table.
+ */
+ u16 old_rate_idx = 0;
+ struct cl_wrs_rate *initial_old_rate = NULL;
+
+ if (wrs_params->initial_rate_idx != WRS_INVALID_RATE) {
+ initial_old_rate = &wrs_params->table[wrs_params->initial_rate_idx].rate;
+ wrs_params->initial_rate_idx = WRS_INVALID_RATE;
+ }
+
+ for (rate_idx = 0; rate_idx < new_table_size; rate_idx++) {
+ old_rate_idx = cl_wrs_tables_find_rate_idx(wrs_params,
+ new_table[rate_idx].rate.bw,
+ new_table[rate_idx].rate.nss,
+ new_table[rate_idx].rate.mcs,
+ new_table[rate_idx].rate.gi);
+
+ if (initial_old_rate &&
+ new_table[rate_idx].rate.bw == initial_old_rate->bw &&
+ new_table[rate_idx].rate.nss == initial_old_rate->nss &&
+ new_table[rate_idx].rate.mcs == initial_old_rate->mcs &&
+ new_table[rate_idx].rate.gi == initial_old_rate->gi) {
+ wrs_params->initial_rate_idx = rate_idx;
+ initial_old_rate = NULL;
+ }
+
+ if (old_rate_idx == WRS_INVALID_RATE)
+ continue;
+
+ new_table[rate_idx].epr_acc =
+ wrs_params->table[old_rate_idx].epr_acc;
+ new_table[rate_idx].frames_total =
+ wrs_params->table[old_rate_idx].frames_total;
+ new_table[rate_idx].ba_not_rcv_total =
+ wrs_params->table[old_rate_idx].ba_not_rcv_total;
+ }
+
+ kfree(wrs_params->table);
+ }
+
+ wrs_params->table = new_table;
+ wrs_params->table_size = new_table_size;
+
+ if (wrs_params->rate_idx != WRS_INVALID_RATE) {
+ /*
+ * Check if current rate is included in the new table.
+ * If not select a rate from the new table accroding to rssi.
+ */
+ struct cl_wrs_rate_params *rate_params = &wrs_params->rate_params;
+
+ rate_idx = cl_wrs_tables_find_rate_idx(wrs_params,
+ rate_params->bw, rate_params->nss,
+ rate_params->mcs, rate_params->gi);
+
+ if (rate_idx != WRS_INVALID_RATE) {
+ wrs_params->rate_idx = rate_idx;
+ } else {
+ if (wrs_params->is_fixed_rate) {
+ wrs_params->is_fixed_rate = false;
+ wrs_pr_verbose(wrs_db,
+ "[%s] Disable fixed rate for station %u\n",
+ WRS_TYPE_STR(type), wrs_sta->sta_idx);
+ }
+
+ cl_wrs_sta_select_first_rate(cl_hw, wrs_db, wrs_sta, wrs_params);
+ cl_wrs_cntrs_reset(wrs_sta, wrs_params);
+ }
+ }
+
+out:
+ kfree(valid_rates);
+}
+
+u16 cl_wrs_tables_find_rate_idx(struct cl_wrs_params *wrs_params,
+ u8 bw, u8 nss, u8 mcs, u8 gi)
+{
+ struct cl_wrs_table *table = wrs_params->table;
+ u16 rate_idx = 0;
+
+ for (rate_idx = 0; rate_idx < wrs_params->table_size; rate_idx++)
+ if (bw == table[rate_idx].rate.bw &&
+ nss == table[rate_idx].rate.nss &&
+ mcs == table[rate_idx].rate.mcs &&
+ gi == table[rate_idx].rate.gi)
+ return rate_idx;
+
+ return WRS_INVALID_RATE;
+}
+