new file mode 100644
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/* Copyright(c) 2019-2022, Celeno Communications Ltd. */
+
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+
+#include "bf.h"
+#include "tx.h"
+#include "radio.h"
+#include "utils.h"
+#include "debug.h"
+#include "hw.h"
+#include "traffic.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_MU 131070 /* = 10mbit/sec (10 * 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 void cl_traffic_mon_ipv4(struct sk_buff *skb, struct cl_sta *cl_sta,
+ enum cl_traffic_mon_direction direction)
+{
+ struct iphdr *iphdr = ip_hdr(skb);
+
+ if (iphdr->protocol == IPPROTO_UDP)
+ cl_sta->traffic_mon[CL_TRFC_MON_PROT_UDP][direction].bytes +=
+ ntohs(iphdr->tot_len) - IPV4_HDR_LEN(iphdr->ihl) - sizeof(struct udphdr);
+ else if (iphdr->protocol == IPPROTO_TCP)
+ cl_sta->traffic_mon[CL_TRFC_MON_PROT_TCP][direction].bytes +=
+ ntohs(iphdr->tot_len) - IPV4_HDR_LEN(iphdr->ihl) - sizeof(struct tcphdr);
+}
+
+static void cl_traffic_mon_ipv6(struct sk_buff *skb, struct cl_sta *cl_sta,
+ enum cl_traffic_mon_direction direction)
+{
+ struct ipv6hdr *ipv6hdr = ipv6_hdr(skb);
+
+ if (ipv6hdr->nexthdr == IPPROTO_UDP)
+ cl_sta->traffic_mon[CL_TRFC_MON_PROT_UDP][direction].bytes +=
+ ntohs(ipv6hdr->payload_len) - sizeof(struct udphdr);
+ else if (ipv6hdr->nexthdr == IPPROTO_TCP)
+ cl_sta->traffic_mon[CL_TRFC_MON_PROT_TCP][direction].bytes +=
+ ntohs(ipv6hdr->payload_len) - sizeof(struct tcphdr);
+}
+
+static void cl_traffic_mon_calc(struct sk_buff *skb, struct cl_sta *cl_sta,
+ enum cl_traffic_mon_direction direction)
+{
+ if (cl_set_network_header_if_proto(skb, ETH_P_IP))
+ cl_traffic_mon_ipv4(skb, cl_sta, direction);
+ else if (cl_set_network_header_if_proto(skb, ETH_P_IPV6))
+ cl_traffic_mon_ipv6(skb, cl_sta, direction);
+}
+
+void cl_traffic_mon_tx(struct cl_sta *cl_sta, struct sk_buff *skb)
+{
+ struct cl_hw *cl_hw = cl_sta->cl_vif->cl_hw;
+
+ if (cl_hw->conf->ci_traffic_mon_en)
+ cl_traffic_mon_calc(skb, cl_sta, CL_TRFC_MON_DIR_DL);
+}
+
+void cl_traffic_mon_rx(struct cl_sta *cl_sta, struct sk_buff *skb)
+{
+ struct cl_hw *cl_hw = cl_sta->cl_vif->cl_hw;
+
+ if (cl_hw->conf->ci_traffic_mon_en)
+ cl_traffic_mon_calc(skb, cl_sta, CL_TRFC_MON_DIR_UL);
+}
+
+void cl_traffic_mon_sta_maintenance(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ u8 i, j;
+
+ for (i = 0; i < CL_TRFC_MON_PROT_MAX; i++)
+ for (j = 0; j < CL_TRFC_MON_DIR_MAX; j++) {
+ cl_sta->traffic_mon[i][j].bytes_per_sec = cl_sta->traffic_mon[i][j].bytes;
+ cl_sta->traffic_mon[i][j].bytes = 0;
+ }
+}
+
+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_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);
+ }
+}
+
+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);
+ }
+}
+
+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 consective 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 consective 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_monitor_sta_traffic(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);
+ }
+
+ /* Save previous Tx num bytes */
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes_prev =
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes;
+
+ /* Reset num_bytes */
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes = 0;
+ cl_sta->traffic_db[TRAFFIC_DIRECTION_RX].num_bytes = 0;
+}
+
+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;
+ traffic_db->active_bytes_thr[TRAFFIC_LEVEL_MU] = TRAFFIC_ACTIVE_THR_MU;
+
+ 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;
+ cl_sta->tx_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;
+ cl_sta->rx_bytes += num_bytes;
+}
+
+void cl_traffic_maintenance(struct cl_hw *cl_hw)
+{
+ cl_sta_loop(cl_hw, cl_traffic_monitor_sta_traffic);
+}
+
+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_tx_exist(struct cl_hw *cl_hw, struct cl_sta *cl_sta)
+{
+ return ((cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes != 0) ||
+ (cl_sta->traffic_db[TRAFFIC_DIRECTION_TX].num_bytes_prev != 0));
+}