new file mode 100644
@@ -0,0 +1,315 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "traffic.h"
+#include "env_det.h"
+#include "edca.h"
+#include "bf.h"
+#include "prot_mode.h"
+#include "band.h"
+#include "utils/utils.h"
+#include "rsrc_mgmt.h"
+
+#define TRAFFIC_CNTR_ACTIVE_THR 3 /* 3 * 100ms = 300ms */
+#define TRAFFIC_CNTR_IDLE_THR 20 /* 20 * 100ms = 2sec */
+
+/* Threshold in bytes */
+#define TRAFFIC_ACTIVE_THR_DRV 1920 /* = 150Kbit/sec (150 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_BF 26214 /* = 2mbit/sec (2 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_EDCA_6G 2621440 /* = 200mbit/sec (200 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_EDCA_5G 2621440 /* = 200mbit/sec (200 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_EDCA_24G 655360 /* = 50mbit/sec (50 * 1024 * 1024 / 8 / 10) */
+#define TRAFFIC_ACTIVE_THR_DFS 13107 /* = 1mbit/sec (1 * 1024 * 1024 / 8 / 10) */
+
+static const char *level_str[TRAFFIC_LEVEL_MAX] = {
+ [TRAFFIC_LEVEL_DRV] = "DRV",
+ [TRAFFIC_LEVEL_BF] = "BF",
+ [TRAFFIC_LEVEL_EDCA] = "EDCA",
+ [TRAFFIC_LEVEL_DFS] = "DFS"
+};
+
+static void cl_traffic_sta_start(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ enum cl_traffic_level level, enum cl_traffic_direction direction)
+{
+ cl_hw->traffic_db.num_active_sta_dir[direction][level]++;
+
+ /* If other direction is not active increase num_active_sta */
+ if (!cl_sta->traffic_db[1 - direction].activity_db[level].is_active)
+ cl_hw->traffic_db.num_active_sta[level]++;
+
+ if (level == TRAFFIC_LEVEL_DRV) {
+ /*
+ * Dynamic CTS:
+ * If protection mode is disabled, environment is clean
+ * and station threshold was reached switch to CTS.
+ */
+ if (cl_hw->traffic_db.num_active_sta[TRAFFIC_LEVEL_DRV] ==
+ cl_hw->conf->ci_dyn_cts_sta_thr)
+ if (cl_env_det_is_clean(cl_hw) &&
+ (cl_prot_mode_get(cl_hw) == TXL_NO_PROT)) {
+ cl_hw->traffic_db.dynamic_cts = true;
+ cl_prot_mode_set(cl_hw, TXL_PROT_CTS);
+ }
+ } else if (level == TRAFFIC_LEVEL_BF) {
+ if (direction == TRAFFIC_DIRECTION_TX)
+ cl_bf_sta_active(cl_hw, cl_sta, true);
+ } else if (level == TRAFFIC_LEVEL_EDCA) {
+ /* No action */
+ }
+
+ cl_rsrc_mgmt_traffic_start(cl_hw, level, direction);
+}
+
+static void cl_traffic_sta_stop(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ enum cl_traffic_level level, enum cl_traffic_direction direction)
+{
+ cl_hw->traffic_db.num_active_sta_dir[direction][level]--;
+
+ /* If other direction is not active decrease num_active_sta */
+ if (!cl_sta->traffic_db[1 - direction].activity_db[level].is_active)
+ cl_hw->traffic_db.num_active_sta[level]--;
+
+ if (level == TRAFFIC_LEVEL_DRV) {
+ /*
+ * Dynamic CTS:
+ * If it was turned on and active station count became lower than
+ * threshold --> return to no protection
+ */
+ if (cl_hw->traffic_db.dynamic_cts) {
+ if (cl_hw->traffic_db.num_active_sta[TRAFFIC_LEVEL_DRV] ==
+ (cl_hw->conf->ci_dyn_cts_sta_thr - 1)) {
+ cl_hw->traffic_db.dynamic_cts = false;
+ cl_prot_mode_set(cl_hw, TXL_NO_PROT);
+ }
+ }
+ } else if (level == TRAFFIC_LEVEL_BF) {
+ if (direction == TRAFFIC_DIRECTION_TX)
+ cl_bf_sta_active(cl_hw, cl_sta, false);
+ } else if (level == TRAFFIC_LEVEL_EDCA) {
+ /* No action */
+ }
+
+ cl_rsrc_mgmt_traffic_stop(cl_hw, level, direction);
+}
+
+static void cl_traffic_check_activity(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ enum cl_traffic_level level,
+ enum cl_traffic_direction direction)
+{
+ struct cl_traffic_activity *activity_db =
+ &cl_sta->traffic_db[direction].activity_db[level];
+ u32 num_bytes = cl_sta->traffic_db[direction].num_bytes;
+
+ if (num_bytes > cl_hw->traffic_db.active_bytes_thr[level]) {
+ activity_db->cntr_active++;
+ activity_db->cntr_idle = 0;
+
+ /* If traffic is above threshold for X consecutive times change state to active */
+ if (!activity_db->is_active &&
+ activity_db->cntr_active >= TRAFFIC_CNTR_ACTIVE_THR) {
+ activity_db->is_active = true;
+ cl_traffic_sta_start(cl_hw, cl_sta, level, direction);
+ }
+ } else {
+ activity_db->cntr_active = 0;
+ activity_db->cntr_idle++;
+
+ /* If traffic is below threshold for Y consecutive times change state to idle */
+ if (activity_db->is_active && activity_db->cntr_idle >= TRAFFIC_CNTR_IDLE_THR) {
+ activity_db->is_active = false;
+ cl_traffic_sta_stop(cl_hw, cl_sta, level, direction);
+ }
+ }
+}
+
+static void cl_traffic_maintenance_sta(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ enum cl_traffic_level level = 0;
+
+ /* Check Tx & Rx activity in all levels */
+ for (level = 0; level < TRAFFIC_LEVEL_MAX; level++) {
+ cl_traffic_check_activity(cl_hw, cl_sta, level, TRAFFIC_DIRECTION_TX);
+ cl_traffic_check_activity(cl_hw, cl_sta, level, TRAFFIC_DIRECTION_RX);
+ }
+
+ /* Reset num_bytes */
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes = 0;
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].num_bytes = 0;
+}
+
+static int cl_traffic_print_state(struct cl_hw *cl_hw,
+ enum cl_traffic_level level)
+{
+ struct cl_sta *cl_sta = NULL;
+ char *buf = NULL;
+ ssize_t buf_size;
+ int err = 0;
+ int len = 0;
+ struct cl_traffic_main *db = &cl_hw->traffic_db;
+
+ if (level >= TRAFFIC_LEVEL_MAX) {
+ cl_snprintf(&buf, &len, &buf_size,
+ "invalid level %d\n", level);
+ goto out;
+ }
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "Level %s (%d)\n", level_str[level], level);
+ cl_snprintf(&buf, &len, &buf_size,
+ "Active stations - %u\n",
+ db->num_active_sta[level]);
+ cl_snprintf(&buf, &len, &buf_size,
+ "Active stations TX - %u\n",
+ db->num_active_sta_dir[TRAFFIC_DIRECTION_TX][level]);
+ cl_snprintf(&buf, &len, &buf_size,
+ "Active stations RX - %u\n",
+ db->num_active_sta_dir[TRAFFIC_DIRECTION_RX][level]);
+
+ if (db->num_active_sta[level] == 0)
+ goto out;
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "|---------------------|\n"
+ "|Sta|ActiveTx|ActiveRx|\n"
+ "|---------------------|\n");
+
+ /* Go over all stations */
+ cl_sta_lock_bh(cl_hw);
+
+ list_for_each_entry(cl_sta, &cl_hw->cl_sta_db.head, list)
+ cl_snprintf(&buf, &len, &buf_size,
+ "|%3u|%-8s|%-8s|\n",
+ cl_sta->sta_idx,
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].activity_db[level].is_active ?
+ "True" : "False",
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].activity_db[level].is_active ?
+ "True" : "False");
+
+ cl_sta_unlock_bh(cl_hw);
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "|---------------------|\n");
+out:
+ err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+ kfree(buf);
+
+ return err;
+}
+
+static int cl_traffic_cli_help(struct cl_hw *cl_hw)
+{
+ char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
+ int err = 0;
+
+ if (!buf)
+ return -ENOMEM;
+
+ snprintf(buf, PAGE_SIZE,
+ "traffic usage:\n"
+ "-s : Print traffic state [0-drv/1-bf/2-edca/3-dfs]\n");
+
+ err = cl_vendor_reply(cl_hw, buf, strlen(buf));
+ kfree(buf);
+
+ return err;
+}
+
+void cl_traffic_init(struct cl_hw *cl_hw)
+{
+ struct cl_traffic_main *traffic_db = &cl_hw->traffic_db;
+
+ traffic_db->active_bytes_thr[TRAFFIC_LEVEL_DRV] = TRAFFIC_ACTIVE_THR_DRV;
+ traffic_db->active_bytes_thr[TRAFFIC_LEVEL_BF] = TRAFFIC_ACTIVE_THR_BF;
+
+ if (cl_band_is_6g(cl_hw))
+ traffic_db->active_bytes_thr[TRAFFIC_LEVEL_EDCA] = TRAFFIC_ACTIVE_THR_EDCA_6G;
+ else if (cl_band_is_5g(cl_hw))
+ traffic_db->active_bytes_thr[TRAFFIC_LEVEL_EDCA] = TRAFFIC_ACTIVE_THR_EDCA_5G;
+ else
+ traffic_db->active_bytes_thr[TRAFFIC_LEVEL_EDCA] = TRAFFIC_ACTIVE_THR_EDCA_24G;
+
+ traffic_db->active_bytes_thr[TRAFFIC_LEVEL_DFS] = TRAFFIC_ACTIVE_THR_DFS;
+}
+
+void cl_traffic_tx_handler(struct cl_hw *cl_hw, struct cl_sta *cl_sta, u32 num_bytes)
+{
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes += num_bytes;
+}
+
+void cl_traffic_rx_handler(struct cl_hw *cl_hw, struct cl_sta *cl_sta, u32 num_bytes)
+{
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].num_bytes += num_bytes;
+}
+
+void cl_traffic_maintenance(struct cl_hw *cl_hw)
+{
+ cl_sta_loop(cl_hw, cl_traffic_maintenance_sta);
+}
+
+void cl_traffic_sta_remove(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ /* Check if station disconnected during traffic */
+ enum cl_traffic_level level = 0;
+ enum cl_traffic_direction direction = 0;
+
+ for (direction = 0; direction < TRAFFIC_DIRECTION_MAX; direction++) {
+ for (level = 0; level < TRAFFIC_LEVEL_MAX; level++) {
+ if (cl_sta->traffic_db[direction].activity_db[level].is_active)
+ cl_traffic_sta_stop(cl_hw, cl_sta, level, direction);
+ }
+
+ memset(&cl_sta->traffic_db, 0, sizeof(cl_sta->traffic_db));
+ }
+}
+
+bool cl_traffic_is_sta_active(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ return (cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].activity_db[TRAFFIC_LEVEL_DRV].is_active ||
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].activity_db[TRAFFIC_LEVEL_DRV].is_active);
+}
+
+bool cl_traffic_is_sta_active_tx(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ return cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].activity_db[TRAFFIC_LEVEL_DRV].is_active;
+}
+
+u32 cl_traffic_num_active_sta(struct cl_hw *cl_hw)
+{
+ return cl_hw->traffic_db.num_active_sta[TRAFFIC_LEVEL_DRV];
+}
+
+u32 cl_traffic_num_active_dfs(struct cl_hw *cl_hw)
+{
+ return cl_hw->traffic_db.num_active_sta[TRAFFIC_LEVEL_DFS];
+}
+
+int cl_traffic_cli(struct cl_hw *cl_hw, struct cli_params *cli_params)
+{
+ bool print_state = false;
+ int expected_params = -1;
+
+ switch (cli_params->option) {
+ case 's':
+ print_state = true;
+ expected_params = 1;
+ break;
+ case '?':
+ return cl_traffic_cli_help(cl_hw);
+ default:
+ cl_dbg_err(cl_hw, "Illegal option (%c) - try '?' for help\n", cli_params->option);
+ goto out_err;
+ }
+
+ if (expected_params != cli_params->num_params) {
+ cl_dbg_err(cl_hw, "Wrong number of arguments (expected %u) (actual %u)\n",
+ expected_params, cli_params->num_params);
+ goto out_err;
+ }
+
+ if (print_state)
+ return cl_traffic_print_state(cl_hw,
+ (u8)(cli_params->params[0]));
+
+out_err:
+ return -EIO;
+}