new file mode 100644
@@ -0,0 +1,1108 @@
+// SPDX-License-Identifier: MIT
+/* Copyright(c) 2019-2021, Celeno Communications Ltd. */
+
+#include "tx/tx.h"
+#include "rx/rx.h"
+#include "rx/rx_amsdu.h"
+#include "stats.h"
+#include "rate_ctrl.h"
+#include "rssi.h"
+#include "band.h"
+#include "utils/utils.h"
+#include "vns.h"
+#include "dfs/dfs.h"
+#include "wrs/wrs_api.h"
+#include "twt.h"
+#include "recovery.h"
+#include "fw/fw_dbg.h"
+#include "def.h"
+#include "rx/rx_reorder.h"
+#include "ops.h"
+#include "chip.h"
+#include "channel.h"
+#ifdef CONFIG_CL_PCIE
+#include "bus/pci/rx_pci.h"
+#endif
+#ifdef TRACE_SUPPORT
+#include "trace.h"
+#endif
+
+/* Must correspond to FW definition of MM_SEC_DEFAULT_KEY_COUNT */
+#define MM_SEC_DEFAULT_KEY_COUNT 64
+
+#define VHT_MCS_MASK 0x0F
+#define VHT_MCS_OFT 0
+
+/* Number of entries in HW legacy rate conversion table */
+#define LEGACY_RATE_MAX 16
+
+static const s8 legacy_rates_lut[LEGACY_RATE_MAX] = {
+ 0, /* 0: 1 Mbps */
+ 1, /* 1: 2 Mbps */
+ 2, /* 2: 5.5 Mbps */
+ 3, /* 3: 11 Mbps */
+ -1, /* 4: Invalid */
+ -1, /* 5: Invalid */
+ -1, /* 6: Invalid */
+ -1, /* 7: Invalid */
+ 10, /* 8: 48 Mbps */
+ 8, /* 9: 24 Mbps */
+ 6, /* 10: 12 Mbps */
+ 4, /* 11: 6 Mbps */
+ 11, /* 12: 54 Mbps */
+ 9, /* 13: 36 Mbps */
+ 7, /* 14: 18 Mbps */
+ 5 /* 15: 9 Mbps */
+};
+
+/*
+ * rx_skb_cnt is an atomic counter that tracks the total number of skbs in
+ * the entire host.
+ * The counter is incremented when skb is allocated, and freed when the skb
+ * is freed (=destructor function called).
+ * Therefore the counter is global (and not part of cl_hw or cl_chip).
+ *
+ * rx_skb_max is the configured to:
+ * max(chip0->conf->ci_rx_skb_max, chip1->conf->ci_rx_skb_max)
+ */
+static atomic_t rx_skb_cnt = ATOMIC_INIT(0);
+static u32 rx_skb_max;
+
+static void cl_rx_skb_destructor(struct sk_buff *skb)
+{
+ atomic_dec(&rx_skb_cnt);
+}
+
+static DEFINE_PER_CPU(struct tasklet_struct, rx_remote_tasklet_mac[TCV_TOTAL]);
+
+static void cl_rx_remote_cpu_mac(struct cl_hw *cl_hw)
+{
+ int cpu = cl_hw->conf->ci_rx_remote_cpu_mac;
+ struct tasklet_struct *t = &per_cpu(rx_remote_tasklet_mac[cl_hw->idx], cpu);
+
+ if (!test_bit(TASKLET_STATE_SCHED, &t->state))
+ smp_call_function_single(cpu, cl_rx_remote_tasklet_sched, t, 0);
+}
+
+static int cl_rx_check_err(struct cl_hw *cl_hw, struct sk_buff *skb, struct hw_rxhdr *rxhdr)
+{
+ u32 status;
+
+ if (rxhdr->frm_successful_rx)
+ return 0;
+
+ /* The status field is in offset of 14 u32's */
+ status = *((u32 *)rxhdr + 14);
+
+ if (rxhdr->fcs_err) {
+ cl_hw->radio_stats[CL_RADIO_FCS_ERROR]++;
+ cl_dbg_err(cl_hw, "fcs_err (status 0x%x)\n", status);
+ }
+
+ if (rxhdr->rx_fifo_oflow) {
+ cl_hw->radio_stats[CL_RADIO_RX_FIFO_OVERFLOW]++;
+ cl_dbg_err(cl_hw, "rx_fifo_oflow (status 0x%x)\n", status);
+ }
+
+ if (rxhdr->undef_err) {
+ cl_hw->radio_stats[CL_RADIO_UNDEFINED_ERROR]++;
+ cl_dbg_err(cl_hw, "undef_err (status 0x%x)\n", status);
+ }
+
+ if (rxhdr->phy_err) {
+ cl_hw->radio_stats[CL_RADIO_PHY_ERROR]++;
+ cl_dbg_err(cl_hw, "phy_err (status 0x%x)\n", status);
+ }
+
+ if (rxhdr->addr_mismatch) {
+ cl_hw->radio_stats[CL_RADIO_ADDRESS_MISMATCH]++;
+ cl_dbg_err(cl_hw, "addr_mismatch (status 0x%x)\n", status);
+ }
+
+ cl_hw->rx_info.pkt_drop_not_success++;
+ kfree_skb(skb);
+
+ return -1;
+}
+
+static u8 chnl_bw_to_rate_info_bw[CHNL_BW_MAX] = {
+ [CHNL_BW_20] = RATE_INFO_BW_20,
+ [CHNL_BW_40] = RATE_INFO_BW_40,
+ [CHNL_BW_80] = RATE_INFO_BW_80,
+ [CHNL_BW_160] = RATE_INFO_BW_160,
+};
+
+static u8 chnl_bw_factor[CHNL_BW_MAX] = {
+ [CHNL_BW_20] = 0,
+ [CHNL_BW_40] = 3,
+ [CHNL_BW_80] = 6,
+ [CHNL_BW_160] = 9,
+};
+
+static int cl_rx_fill_status(struct cl_hw *cl_hw, struct cl_sta *cl_sta, struct sk_buff *skb,
+ struct hw_rxhdr *rxhdr, u8 *encrypt_len)
+{
+ s8 rssi[MAX_ANTENNAS] = RX_HDR_RSSI(rxhdr);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+ u8 tid = ieee80211_get_tid(hdr);
+ u8 factor;
+
+ memset(status, 0, sizeof(struct ieee80211_rx_status));
+
+ status->mactime = ((u64)le32_to_cpu((rxhdr->tsf_hi)) << 32) | le32_to_cpu(rxhdr->tsf_lo);
+ status->flag |= RX_FLAG_MACTIME_END;
+
+ if (cl_sta && cl_sta->tid_agg_rx[tid])
+ status->flag |= RX_FLAG_DUP_VALIDATED;
+
+ status->antenna = rxhdr->antenna_set;
+ status->band = cl_band_from_fw_idx(rxhdr->phy_band);
+
+ if (rxhdr->format_mod >= FORMATMOD_HE_SU) {
+ status->encoding = RX_ENC_HE;
+ status->rate_idx = (rxhdr->mcs & VHT_MCS_MASK) >> VHT_MCS_OFT;
+ status->nss = rxhdr->n_sts + 1;
+
+ /* he_gi expectes to get values according to enum nl80211_he_gi */
+ status->he_gi = convert_gi_format_wrs_to_fw(WRS_MODE_HE, rxhdr->gi_type);
+ } else if (rxhdr->format_mod == FORMATMOD_VHT) {
+ status->encoding = RX_ENC_VHT;
+ status->rate_idx = (rxhdr->mcs & VHT_MCS_MASK) >> VHT_MCS_OFT;
+ status->nss = rxhdr->n_sts + 1;
+
+ if (rxhdr->gi_type)
+ status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
+ } else if (rxhdr->format_mod == FORMATMOD_HT_GF) {
+ status->encoding = RX_ENC_HT;
+ status->enc_flags |= RX_ENC_FLAG_HT_GF;
+ status->rate_idx = rxhdr->mcs;
+
+ if (rxhdr->gi_type)
+ status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
+
+ } else if (rxhdr->format_mod == FORMATMOD_HT_MF) {
+ status->encoding = RX_ENC_HT;
+ status->rate_idx = rxhdr->mcs;
+
+ if (rxhdr->gi_type)
+ status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
+ } else {
+ if (legacy_rates_lut[rxhdr->leg_rate] != -1)
+ status->rate_idx = legacy_rates_lut[rxhdr->leg_rate];
+ if (status->band != NL80211_BAND_2GHZ)
+ status->rate_idx -= RATE_CTRL_OFFSET_OFDM;
+ if (!rxhdr->pre_type)
+ status->enc_flags |= RX_ENC_FLAG_SHORTPRE;
+ }
+
+ if (rxhdr->aggregation) {
+ status->flag |= RX_FLAG_AMPDU_DETAILS;
+ status->ampdu_reference = rxhdr->ampdu_cnt;
+ }
+
+ /* Set bw field */
+ status->bw = chnl_bw_to_rate_info_bw[rxhdr->ch_bw];
+
+ factor = chnl_bw_factor[rxhdr->ch_bw];
+ cl_rssi_bw_adjust(cl_hw, rxhdr, factor);
+
+ /*
+ * Todo check if when a frame is received on 40MHz or more bandwidth,
+ * we need to take the center1_freq instead of the prim20_freq
+ */
+ status->freq = le16_to_cpu(Q2_TO_FREQ(rxhdr->phy_prim20_freq));
+
+ status->signal = cl_rssi_calc_equivalent(cl_hw, rssi);
+
+ switch (rxhdr->decr_status) {
+ case CL_RX_HDR_DECR_UNENC:
+ if (ieee80211_has_protected(hdr->frame_control)) {
+ cl_dbg_warn(cl_hw, "Protected frame unencrypted\n");
+ cl_hw->rx_info.pkt_drop_unencrypted++;
+ return -1;
+ }
+ break;
+ case CL_RX_HDR_DECR_ICVFAIL:
+ case CL_RX_HDR_DECR_AMSDUDISCARD:
+ case CL_RX_HDR_DECR_NULLKEY:
+ case CL_RX_HDR_DECR_CCMPFAIL:
+ cl_dbg_warn(cl_hw, "Decryption failed (%u)\n", rxhdr->decr_status);
+ cl_hw->rx_info.pkt_drop_decrypt_fail++;
+ *encrypt_len = 0;
+ return -1;
+ case CL_RX_HDR_DECR_WEPSUCCESS:
+ case CL_RX_HDR_DECR_TKIPSUCCESS:
+ *encrypt_len = IEEE80211_WEP_ICV_LEN;
+ status->flag |= (RX_FLAG_DECRYPTED | RX_FLAG_ICV_STRIPPED);
+ break;
+ case CL_RX_HDR_DECR_CCMPSUCCESS:
+ *encrypt_len = IEEE80211_CCMP_HDR_LEN;
+ status->flag |= (RX_FLAG_DECRYPTED | RX_FLAG_MIC_STRIPPED);
+ status->flag |= RX_FLAG_PN_VALIDATED;
+ break;
+ }
+
+ return 0;
+}
+
+static void cl_rx_action_twt_setup(struct cl_hw *cl_hw, struct cl_ieee80211_mgmt *mgmt,
+ int len, struct cl_sta *cl_sta)
+{
+ u32 min_size = 0;
+ u8 negotiation_type =
+ mgmt->u.action.u.twt_individual_setup.twt_elem.control.fields.negotiation_type;
+
+ /* Individual TWT */
+ if ((negotiation_type & 0x2) == 0) {
+ /* Verify min size */
+ min_size = IEEE80211_MIN_ACTION_SIZE + 4 +
+ sizeof(mgmt->u.action.u.twt_individual_setup.twt_elem);
+
+ if (len < min_size) {
+ cl_dbg_err(cl_hw, "TWT: Individual setup action frame length error\n");
+ return;
+ }
+
+ /* Regular individual TWT */
+ if (negotiation_type == 0)
+ cl_twt_handle_individual_setup_request(cl_hw, cl_sta, mgmt);
+ } else { /* Broadcast TWT */
+ /* Verify min size */
+ min_size = IEEE80211_MIN_ACTION_SIZE + 4 +
+ sizeof(mgmt->u.action.u.twt_broadcast_setup.twt_elem);
+
+ if (len < min_size) {
+ cl_dbg_err(cl_hw, "TWT: Broadcast setup action frame length error\n");
+ return;
+ }
+ }
+}
+
+static void cl_rx_action_twt_teardown(struct cl_hw *cl_hw, struct cl_ieee80211_mgmt *mgmt,
+ int len, struct cl_sta *cl_sta)
+{
+ u8 negotiation_type;
+
+ if (len < IEEE80211_MIN_ACTION_SIZE + 2) {
+ cl_dbg_err(cl_hw, "Invalid length of TWT teardown action frame\n");
+ return;
+ }
+
+ negotiation_type = mgmt->u.action.u.twt_individual_teardown.negotiation_type;
+
+ if (negotiation_type <= 1)
+ cl_twt_handle_individual_teardown_request(cl_hw, cl_sta, mgmt);
+}
+
+static void cl_rx_action_frame_handler(struct cl_hw *cl_hw, struct cl_ieee80211_mgmt *mgmt,
+ int len, struct cl_sta *cl_sta)
+{
+ /* Verify action code is present */
+ if (len < IEEE80211_MIN_ACTION_SIZE + 1)
+ return;
+
+ switch (mgmt->u.action.category) {
+ case WLAN_CATEGORY_UNPROTECTED_S1G:
+ if (!cl_twt_is_enabled(cl_hw))
+ break;
+
+ if (cl_sta->cl_vif->vif->type != NL80211_IFTYPE_AP)
+ break;
+
+ switch (mgmt->u.action.u.twt_individual_setup.action_code) {
+ case WLAN_UNPROT_S1G_ACTION_TWT_SETUP:
+ cl_rx_action_twt_setup(cl_hw, mgmt, len, cl_sta);
+ break;
+ case WLAN_UNPROT_S1G_ACTION_TWT_TEARDOWN:
+ cl_rx_action_twt_teardown(cl_hw, mgmt, len, cl_sta);
+ break;
+ default:
+ break;
+ }
+ break;
+ case WLAN_CATEGORY_WNM:
+ /* TODO: Here may be bss_color_check_action */
+ break;
+ default:
+ break;
+ }
+}
+
+static void cl_rx_mgmt_check(struct cl_hw *cl_hw, struct sk_buff *skb,
+ struct cl_sta *cl_sta, struct hw_rxhdr *rxhdr)
+{
+ struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;
+ __le16 fc = mgmt->frame_control;
+
+ if (!ieee80211_is_mgmt(fc))
+ return;
+
+ if (cl_sta) {
+ if (ieee80211_is_action(fc))
+ cl_rx_action_frame_handler(cl_hw, (struct cl_ieee80211_mgmt *)mgmt,
+ skb->len, cl_sta);
+ } else {
+ s8 rssi[MAX_ANTENNAS] = RX_HDR_RSSI(rxhdr);
+
+ cl_vns_mgmt_handler(cl_hw, mgmt->sa, rssi);
+
+ if (ieee80211_is_assoc_req(fc) || ieee80211_is_assoc_resp(fc))
+ cl_rssi_assoc_handle(cl_hw, mgmt->sa, rxhdr);
+ }
+}
+
+static void cl_rx_data_check(struct cl_hw *cl_hw, struct sk_buff *skb,
+ struct cl_sta *cl_sta, u32 packet_len, struct hw_rxhdr *rxhdr)
+{
+ if (cl_sta) {
+ cl_traffic_rx_handler(cl_hw, cl_sta, packet_len);
+
+ if (!rxhdr->aggregation || (rxhdr->aggregation && rxhdr->mpdu_cnt == 0))
+ cl_motion_sense_rssi_data(cl_hw, cl_sta, rxhdr);
+ }
+}
+
+static bool cl_rx_skb_done(struct cl_hw *cl_hw, struct sk_buff *skb,
+ struct cl_sta *cl_sta, struct hw_rxhdr *rxhdr)
+{
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
+ __le16 fc = hdr->frame_control;
+ struct cl_vif *cl_vif = NULL;
+
+ /* Update trigger base statistics */
+ cl_fw_dbg_trigger_based_update(cl_hw, rxhdr, hdr);
+
+ if (cl_sta) {
+ cl_vif = cl_sta->cl_vif;
+ skb->dev = cl_vif->dev;
+
+ cl_stats_update_rx_rate(cl_hw, cl_sta, rxhdr);
+
+ if (!rxhdr->aggregation || (rxhdr->aggregation && rxhdr->mpdu_cnt == 0))
+ cl_rssi_rx_handler(cl_hw, cl_sta, rxhdr, status->signal);
+ } else {
+ cl_vif = cl_vif_get_by_mac(cl_hw, hdr->addr3);
+ skb->dev = cl_vif ? cl_vif->dev : NULL;
+
+ if (cl_hw->chip->conf->ce_production_mode)
+ cl_stats_update_rx_rate_production(cl_hw, rxhdr);
+ }
+
+ /* DATA */
+ if (ieee80211_is_data(fc)) {
+ cl_rx_data_check(cl_hw, skb, cl_sta, skb->len, rxhdr);
+ goto out;
+ }
+
+ /* MGMT/CTL */
+ if (cl_sta)
+ cl_motion_sense_rssi_mgmt_ctl(cl_hw, cl_sta, rxhdr);
+
+ /* MGMT */
+ cl_rx_mgmt_check(cl_hw, skb, cl_sta, rxhdr);
+
+out:
+ if (rx_skb_max &&
+ atomic_read(&rx_skb_cnt) >= rx_skb_max) {
+ cl_hw->rx_info.pkt_drop_host_limit++;
+ kfree_skb(skb);
+ return false;
+ }
+
+ return true;
+}
+
+static void cl_rx_pass_to_mac(struct cl_hw *cl_hw,
+ struct ieee80211_sta *sta,
+ struct sk_buff_head *frames)
+{
+ if (cl_hw->conf->ci_rx_remote_cpu_mac == -1) {
+ struct sk_buff *skb = NULL;
+
+ while ((skb = __skb_dequeue(frames)))
+ ieee80211_rx_napi(cl_hw->hw, sta, skb, NULL);
+ } else {
+ struct sk_buff_head *rx_remote_queue_mac = &cl_hw->rx_remote_queue_mac;
+
+ spin_lock(&rx_remote_queue_mac->lock);
+ skb_queue_splice_tail_init(frames, rx_remote_queue_mac);
+ spin_unlock(&rx_remote_queue_mac->lock);
+
+ cl_rx_remote_cpu_mac(cl_hw);
+ }
+}
+
+static void cl_rx_amsdu_done_reorder(struct cl_hw *cl_hw, struct cl_sta *cl_sta,
+ struct sk_buff_head *frames)
+{
+ struct sk_buff *skb = NULL;
+ struct sk_buff_head reorder_buf;
+
+ /* Init the reorder buffer */
+ __skb_queue_head_init(&reorder_buf);
+
+ while ((skb = __skb_dequeue(frames)))
+ cl_rx_reorder_ampdu(cl_hw, cl_sta, skb, &reorder_buf);
+
+ if (!skb_queue_empty(&reorder_buf))
+ cl_rx_pass_to_mac(cl_hw, &cl_sta->stainfo->sta, &reorder_buf);
+}
+
+static void cl_rx_amsdu_done(struct cl_hw *cl_hw, struct cl_amsdu_rx_state *amsdu_rx_state)
+{
+ struct sk_buff_head *frames = &amsdu_rx_state->frames;
+ struct sk_buff *skb = __skb_peek(frames);
+ struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb);
+ struct cl_sta *cl_sta;
+ struct cl_vif *cl_vif;
+ struct hw_rxhdr *rxhdr = amsdu_rx_state->rxhdr;
+ u32 packet_len = amsdu_rx_state->packet_len;
+ struct ieee80211_sta *sta;
+
+ /* START - cl_sta protected block */
+ cl_sta_lock(cl_hw);
+ cl_sta = cl_sta_get(cl_hw, amsdu_rx_state->sta_idx);
+
+ if (!cl_sta) {
+ cl_sta_unlock(cl_hw);
+ cl_hw->rx_info.pkt_drop_sta_null += amsdu_rx_state->msdu_cnt;
+ __skb_queue_purge(frames);
+ return;
+ }
+
+ sta = &cl_sta->stainfo->sta;
+ cl_vif = cl_sta->cl_vif;
+ skb->dev = cl_vif->dev;
+
+ cl_rx_data_check(cl_hw, skb, cl_sta, packet_len, rxhdr);
+ cl_stats_update_rx_rate(cl_hw, cl_sta, rxhdr);
+
+ if (!rxhdr->aggregation || (rxhdr->aggregation && rxhdr->mpdu_cnt == 0))
+ cl_rssi_rx_handler(cl_hw, cl_sta, rxhdr, status->signal);
+
+ cl_sta_unlock(cl_hw);
+ /* END - cl_sta protected block */
+
+ if (rx_skb_max &&
+ (atomic_read(&rx_skb_cnt) + amsdu_rx_state->msdu_cnt) >= rx_skb_max) {
+ cl_hw->rx_info.pkt_drop_host_limit += amsdu_rx_state->msdu_cnt;
+ __skb_queue_purge(frames);
+ return;
+ }
+
+ if (cl_sta->tid_agg_rx[amsdu_rx_state->tid])
+ cl_rx_amsdu_done_reorder(cl_hw, cl_sta, frames);
+ else
+ cl_rx_pass_to_mac(cl_hw, sta, frames);
+}
+
+static void cl_rx_invalid_tailroom(struct cl_hw *cl_hw, struct sk_buff *skb, u32 len)
+{
+ cl_dbg_err(cl_hw, "Invalid RX header length - tailroom=%d, len=%u\n",
+ skb_tailroom(skb), len);
+ cl_hw->rx_info.pkt_drop_rxhdr_len_error++;
+ kfree_skb(skb);
+}
+
+static void cl_rx_invalid_pattern(struct cl_hw *cl_hw, struct sk_buff *skb, u32 pattern)
+{
+ cl_dbg_err(cl_hw, "WRONG PATTERN - 0x%x\n", pattern);
+ cl_hw->rx_info.pkt_drop_wrong_pattern++;
+ kfree_skb(skb);
+}
+
+static int cl_rx_get_sta_idx(struct cl_hw *cl_hw, struct hw_rxhdr *rxhdr)
+{
+ int sta_idx = rxhdr->key_sram_index - MM_SEC_DEFAULT_KEY_COUNT;
+
+ if (sta_idx >= 0 && sta_idx < CL_MAX_NUM_STA)
+ return sta_idx;
+
+ cl_dbg_err(cl_hw, "invalid sta_idx %d, key_sram_index=%d\n",
+ sta_idx, rxhdr->key_sram_index);
+
+ return -1;
+}
+
+static void cl_rx_handle_first_amsdu(struct cl_hw *cl_hw, struct sk_buff *skb,
+ struct cl_amsdu_rx_state *amsdu_rx_state,
+ struct hw_rxhdr *rxhdr, u8 sta_idx, u8 tid, u8 encrypt_len)
+{
+ /*
+ * First MSDU recived frame:
+ * ------------------------------------------
+ * || WLAN_HDR || MSDU HDR || MSDU PAYLOAD ||
+ * ------------------------------------------
+ */
+ cl_rx_amsdu_stats(cl_hw, rxhdr->msdu_cnt);
+
+ if (rxhdr->corrupted_amsdu) {
+ cl_rx_amsdu_first_corrupted(cl_hw, skb, rxhdr);
+ } else {
+ cl_rx_amsdu_first(cl_hw, skb, rxhdr, sta_idx, tid, encrypt_len);
+
+ /* If there are more MSDU's, hold on with the update
+ * to the upper layer until A-MSDU is complete
+ */
+ if (amsdu_rx_state->msdu_remaining_cnt == 0)
+ cl_rx_amsdu_done(cl_hw, amsdu_rx_state);
+ }
+}
+
+static void cl_rx_handle_sub_amsdu(struct cl_hw *cl_hw, struct sk_buff *skb,
+ struct cl_amsdu_rx_state *amsdu_rx_state)
+{
+ /* Update the remaining MSDU counter */
+ amsdu_rx_state->msdu_remaining_cnt--;
+
+ /* Free MSDU with error */
+ if (amsdu_rx_state->amsdu_error) {
+ cl_rx_amsdu_sub_error(cl_hw, skb);
+ return;
+ }
+
+ /* Add the sub-MSDU to the existing ones */
+ if (!cl_rx_amsdu_sub(cl_hw, skb))
+ return;
+
+ /* This is the last MSDU, A-MSDU is complete, push to upper layer */
+ if (amsdu_rx_state->msdu_remaining_cnt == 0)
+ cl_rx_amsdu_done(cl_hw, amsdu_rx_state);
+}
+
+static void cl_rx_handle_ps(struct cl_hw *cl_hw, struct cl_sta *cl_sta, struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)(skb->data);
+ struct ieee80211_sta *sta = &cl_sta->stainfo->sta;
+ bool is_ps;
+ __le16 fc = hdr->frame_control;
+
+ if (ieee80211_is_pspoll(fc) ||
+ ieee80211_has_morefrags(fc) ||
+ !(ieee80211_is_mgmt(fc) ||
+ ieee80211_is_data(fc)))
+ return;
+
+ is_ps = ieee80211_has_pm(hdr->frame_control);
+
+ cl_sta_ps_notify(cl_hw, cl_sta, is_ps);
+ ieee80211_sta_ps_transition(sta, is_ps);
+}
+
+static void cl_rx_handle_skb(struct cl_hw *cl_hw, struct sk_buff *skb)
+{
+ u8 encrypt_len = 0;
+ u8 tid = 0;
+ u32 mpdu_offset = 0;
+ u32 len = 0;
+ int sta_idx = -1;
+ bool is_amsdu = false;
+ bool skb_done = false;
+ struct cl_sta *cl_sta = NULL;
+ struct ieee80211_sta *sta = NULL;
+ struct hw_rxhdr *rxhdr = NULL;
+ struct cl_tid_ampdu_rx *tid_agg_rx = NULL;
+ struct cl_amsdu_rx_state *amsdu_rx_state = &cl_hw->amsdu_rx_state;
+ s8 remote_cpu_mac = cl_hw->conf->ci_rx_remote_cpu_mac;
+
+ if (amsdu_rx_state->msdu_remaining_cnt > 0) {
+ cl_rx_handle_sub_amsdu(cl_hw, skb, amsdu_rx_state);
+ return;
+ }
+
+ rxhdr = (struct hw_rxhdr *)skb->data;
+ mpdu_offset = sizeof(struct hw_rxhdr);
+
+ if (rxhdr->rx_padding_done)
+ mpdu_offset += CL_PADDING_IN_BYTES;
+
+ /* Pull the HW RX header */
+ skb_reserve(skb, mpdu_offset);
+
+ /*
+ * Sanity check - the embedded layer is responsible to validate the pattern correctness.
+ * If pattern is invalid then it is likely that the embedded layer did some thing wrong.
+ */
+ if (rxhdr->pattern != IPC_RX_DMA_OVER_PATTERN) {
+ cl_rx_invalid_pattern(cl_hw, skb, rxhdr->pattern);
+ return;
+ }
+
+ if (cl_rx_check_err(cl_hw, skb, rxhdr))
+ return;
+
+ /* Convert gi from firmware format to driver format */
+ rxhdr->gi_type = convert_gi_format_fw_to_wrs(rxhdr->format_mod, rxhdr->gi_type);
+
+ /*
+ * For TCV1 fill in the rxhdr rssi "holes" so that values will start from rssi1.
+ * The implementation below takes into account elastic mimo, and maximum number
+ * of antennas for TCV1.
+ */
+ if (cl_hw_is_tcv1(cl_hw)) {
+ if (cl_chip_is_6ant(cl_hw->chip)) {
+ rxhdr->rssi1 = rxhdr->rssi2;
+ rxhdr->rssi2 = rxhdr->rssi3;
+ rxhdr->rssi3 = rxhdr->rssi4;
+ rxhdr->rssi4 = rxhdr->rssi5;
+ rxhdr->rssi5 = rxhdr->rssi6;
+ } else if (cl_chip_is_4ant(cl_hw->chip)) {
+ rxhdr->rssi1 = rxhdr->rssi3;
+ rxhdr->rssi2 = rxhdr->rssi4;
+ rxhdr->rssi3 = rxhdr->rssi5;
+ rxhdr->rssi4 = rxhdr->rssi6;
+ }
+ }
+
+ if (rxhdr->key_sram_v)
+ sta_idx = cl_rx_get_sta_idx(cl_hw, rxhdr);
+
+ cl_sta_lock(cl_hw);
+
+ if (sta_idx != -1) {
+ cl_sta = cl_sta_get(cl_hw, sta_idx);
+
+ if (cl_sta) {
+ sta = &cl_sta->stainfo->sta;
+
+ if (cl_hw->conf->ci_fast_rx_en) {
+ tid = ieee80211_get_tid((struct ieee80211_hdr *)skb->data);
+ tid_agg_rx = cl_sta->tid_agg_rx[tid];
+ cl_rx_handle_ps(cl_hw, cl_sta, skb);
+ }
+
+ /* Store the pointer to sta in the skb->sk field */
+ if (remote_cpu_mac != -1)
+ skb->sk = (struct sock *)sta;
+ }
+ }
+
+ if (unlikely(cl_rx_fill_status(cl_hw, cl_sta, skb, rxhdr, &encrypt_len))) {
+ cl_sta_unlock(cl_hw);
+ kfree_skb(skb);
+ return;
+ }
+
+ /*
+ * RXM sets rxhdr->msdu_cnt=1 also for non AMSDU, so the correct check
+ * is the new amsdu_present bit.
+ */
+ is_amsdu = rxhdr->amsdu_present;
+
+ /* Is A-MSDU frame? */
+ if (is_amsdu) {
+ cl_rx_handle_first_amsdu(cl_hw, skb, amsdu_rx_state, rxhdr, sta_idx,
+ tid, encrypt_len);
+ cl_sta_unlock(cl_hw);
+ return;
+ }
+
+ len = le32_to_cpu(rxhdr->len);
+
+ if (skb_tailroom(skb) >= len) {
+ /* Push the WLAN HDR + MDPU payload to the skb data */
+ skb_put(skb, len);
+ cl_hw->rx_info.non_amsdu++;
+ } else {
+ cl_sta_unlock(cl_hw);
+ cl_rx_invalid_tailroom(cl_hw, skb, len);
+ return;
+ }
+
+ skb_done = cl_rx_skb_done(cl_hw, skb, cl_sta, rxhdr);
+
+ cl_sta_unlock(cl_hw);
+
+ if (!skb_done)
+ return;
+
+ if (tid_agg_rx) {
+ struct sk_buff_head reorder_buf;
+
+ /* Init the reorder buffer */
+ __skb_queue_head_init(&reorder_buf);
+ cl_rx_reorder_ampdu(cl_hw, cl_sta, skb, &reorder_buf);
+
+ if (!skb_queue_empty(&reorder_buf))
+ cl_rx_pass_to_mac(cl_hw, sta, &reorder_buf);
+ } else {
+ if (remote_cpu_mac == -1) {
+ ieee80211_rx_napi(cl_hw->hw, sta, skb, NULL);
+ } else {
+ skb_queue_tail(&cl_hw->rx_remote_queue_mac, skb);
+ cl_rx_remote_cpu_mac(cl_hw);
+ }
+ }
+}
+
+static void cl_rx_tasklet(unsigned long data)
+{
+ struct cl_hw *cl_hw = (struct cl_hw *)data;
+ struct sk_buff *skb = NULL;
+ u16 pkt_cnt = 0;
+
+ if (cl_recovery_in_progress(cl_hw))
+ return;
+
+#ifdef TRACE_SUPPORT
+ trace_cl_trace_rx_tasklet_start(cl_hw->idx);
+#endif
+
+ while ((skb = skb_dequeue(&cl_hw->rx_skb_queue))) {
+ cl_rx_handle_skb(cl_hw, skb);
+
+ if (++pkt_cnt > cl_hw->conf->ce_rx_pkts_budget) {
+ if (cl_hw->chip->conf->ci_rx_resched_tasklet)
+ tasklet_schedule(&cl_hw->rx_resched_tasklet);
+ else
+ tasklet_schedule(&cl_hw->rx_tasklet);
+
+ cl_hw->rx_info.exceed_pkt_budget++;
+ return;
+ }
+ }
+
+#ifdef TRACE_SUPPORT
+ trace_cl_trace_rx_tasklet_end(cl_hw->idx, pkt_cnt);
+#endif
+}
+
+static void cl_rx_resched_tasklet(unsigned long data)
+{
+ struct cl_hw *cl_hw = (struct cl_hw *)data;
+
+ tasklet_schedule(&cl_hw->rx_tasklet);
+}
+
+static void cl_rx_remote_tasklet_mac(unsigned long data)
+{
+ struct cl_hw *cl_hw = (struct cl_hw *)data;
+ struct sk_buff *skb = NULL;
+ struct ieee80211_sta *sta;
+
+ if (cl_recovery_in_progress(cl_hw))
+ return;
+
+ cl_rx_remote_cpu_info(cl_hw);
+
+ while ((skb = skb_dequeue(&cl_hw->rx_remote_queue_mac))) {
+ /*
+ * Get sta pointer from skb->sk (stored their in cl_rx_remote_cpu_mac)
+ * and reset skb->sk.
+ */
+ sta = (struct ieee80211_sta *)skb->sk;
+ skb->sk = NULL;
+
+ ieee80211_rx_napi(cl_hw->hw, sta, skb, NULL);
+ }
+}
+
+void cl_rx_init(struct cl_hw *cl_hw)
+{
+ s8 cpu_mac = cl_hw->conf->ci_rx_remote_cpu_mac;
+
+ /* Set rx_skb_max to be the maximum of ci_rx_skb_max configured for each chip */
+ rx_skb_max = max(cl_hw->chip->conf->ci_rx_skb_max, rx_skb_max);
+
+ skb_queue_head_init(&cl_hw->rx_remote_queue_mac);
+ skb_queue_head_init(&cl_hw->rx_skb_queue);
+ __skb_queue_head_init(&cl_hw->amsdu_rx_state.frames);
+
+ tasklet_init(&cl_hw->rx_tasklet, cl_rx_tasklet, (unsigned long)cl_hw);
+ tasklet_init(&cl_hw->rx_resched_tasklet, cl_rx_resched_tasklet, (unsigned long)cl_hw);
+
+ if (cpu_mac >= 0)
+ tasklet_init(&per_cpu(rx_remote_tasklet_mac[cl_hw->idx], cpu_mac),
+ cl_rx_remote_tasklet_mac,
+ (unsigned long)cl_hw);
+#ifdef CONFIG_CL_PCIE
+ cl_rx_pci_init(cl_hw);
+#endif
+}
+
+void cl_rx_off(struct cl_hw *cl_hw)
+{
+ s8 cpu_mac = cl_hw->conf->ci_rx_remote_cpu_mac;
+
+ if (cpu_mac >= 0)
+ tasklet_kill(&per_cpu(rx_remote_tasklet_mac[cl_hw->idx], cpu_mac));
+
+ tasklet_kill(&cl_hw->rx_tasklet);
+ tasklet_kill(&cl_hw->rx_resched_tasklet);
+
+ skb_queue_purge(&cl_hw->rx_remote_queue_mac);
+ skb_queue_purge(&cl_hw->rx_skb_queue);
+
+ cl_rx_amsdu_reset(cl_hw);
+#ifdef CONFIG_CL_PCIE
+ cl_rx_pci_deinit(cl_hw);
+#endif
+}
+
+void cl_rx_remote_tasklet_sched(void *t)
+{
+ tasklet_schedule((struct tasklet_struct *)t);
+}
+
+void cl_rx_remote_cpu_info(struct cl_hw *cl_hw)
+{
+ u32 processor_id = smp_processor_id();
+
+ if (processor_id < CPU_MAX_NUM)
+ cl_hw->rx_info.remote_cpu[processor_id]++;
+}
+
+void cl_rx_push_queue(struct cl_hw *cl_hw, struct sk_buff *skb)
+{
+ skb_queue_tail(&cl_hw->rx_skb_queue, skb);
+ tasklet_schedule(&cl_hw->rx_tasklet);
+}
+
+void cl_rx_skb_alloc_handler(struct sk_buff *skb)
+{
+ skb->destructor = cl_rx_skb_destructor;
+ atomic_inc(&rx_skb_cnt);
+}
+
+void cl_rx_post_recovery(struct cl_hw *cl_hw)
+{
+ if (!skb_queue_empty(&cl_hw->rx_skb_queue))
+ tasklet_schedule(&cl_hw->rx_tasklet);
+
+ if (!skb_queue_empty(&cl_hw->rx_remote_queue_mac))
+ tasklet_schedule(&per_cpu(rx_remote_tasklet_mac[cl_hw->idx],
+ cl_hw->conf->ci_rx_remote_cpu_mac));
+}
+
+void cl_rx_info_reset(struct cl_hw *cl_hw)
+{
+ pr_debug("Reset uplink stats\n");
+ memset(&cl_hw->rx_info, 0, sizeof(struct cl_rx_path_info));
+}
+
+int cl_rx_info_print(struct cl_hw *cl_hw)
+{
+ struct cl_rx_path_info *rx_info = &cl_hw->rx_info;
+ struct ieee80211_local *local = hw_to_local(cl_hw->hw);
+ int i;
+ bool uplink_amsdu = false;
+ char *buf = NULL;
+ ssize_t buf_size;
+ int err = 0;
+ int len = 0;
+
+ for (i = 0; i < RX_MAX_MSDU_IN_AMSDU; i++)
+ if (rx_info->amsdu_cnt[i] > 0) {
+ uplink_amsdu = true;
+ break;
+ }
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "Uplink counters\n"
+ "-----------------------------------\n");
+ cl_snprintf(&buf, &len, &buf_size,
+ "rx_desc[RXM] = %u\n",
+ rx_info->rx_desc[CL_RX_BUF_RXM]);
+ cl_snprintf(&buf, &len, &buf_size,
+ "rx_desc[FW] = %u\n",
+ rx_info->rx_desc[CL_RX_BUF_FW]);
+ cl_snprintf(&buf, &len, &buf_size,
+ "elem_alloc_fail = %u\n",
+ rx_info->elem_alloc_fail);
+ cl_snprintf(&buf, &len, &buf_size,
+ "skb_null = %u\n",
+ rx_info->skb_null);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_amsdu_corrupted = %u\n",
+ rx_info->pkt_drop_amsdu_corrupted);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_sub_amsdu_corrupted = %u\n",
+ rx_info->pkt_drop_sub_amsdu_corrupted);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_amsdu_len_error = %u\n",
+ rx_info->pkt_drop_amsdu_len_error);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_sub_amsdu_len_error = %u\n",
+ rx_info->pkt_drop_sub_amsdu_len_error);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_wrong_pattern = %u\n",
+ rx_info->pkt_drop_wrong_pattern);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_not_success = %u\n",
+ rx_info->pkt_drop_not_success);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_unencrypted = %u\n",
+ rx_info->pkt_drop_unencrypted);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_decrypt_fail = %u\n",
+ rx_info->pkt_drop_decrypt_fail);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_rxhdr_len_error = %u\n",
+ rx_info->pkt_drop_rxhdr_len_error);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_sta_null = %u\n",
+ rx_info->pkt_drop_sta_null);
+ cl_snprintf(&buf, &len, &buf_size,
+ "pkt_drop_host_limit = %u\n",
+ rx_info->pkt_drop_host_limit);
+ cl_snprintf(&buf, &len, &buf_size,
+ "netif_rx = %u\n",
+ rx_info->netif_rx);
+
+ len += snprintf(buf + len, PAGE_SIZE - len,
+ "\nQueue length\n"
+ "-----------------------------------\n");
+ len += snprintf(buf + len, PAGE_SIZE - len,
+ "rx_skb_queue = %u\n",
+ skb_queue_len(&cl_hw->rx_skb_queue));
+ len += snprintf(buf + len, PAGE_SIZE - len,
+ "rx_remote_queue_mac = %u\n",
+ skb_queue_len(&cl_hw->rx_remote_queue_mac));
+ len += snprintf(buf + len, PAGE_SIZE - len,
+ "local_skb_queue = %u\n",
+ skb_queue_len(&local->skb_queue));
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nSKB count\n"
+ "-----------------------------------\n");
+ cl_snprintf(&buf, &len, &buf_size,
+ "rx_skb_max = %u\n",
+ rx_skb_max);
+ cl_snprintf(&buf, &len, &buf_size,
+ "rx_skb_cnt = %u\n",
+ atomic_read(&rx_skb_cnt));
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nBuffer processing\n"
+ "-----------------------------------\n");
+ cl_snprintf(&buf, &len, &buf_size,
+ "IRQ = %u\n",
+ rx_info->buffer_process_irq);
+ cl_snprintf(&buf, &len, &buf_size,
+ "Tasklet = %u\n",
+ rx_info->buffer_process_tasklet);
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nUplink Non AMSDU\n"
+ "-----------------------------------\n"
+ "NON AMSDU = %u\n", rx_info->non_amsdu);
+
+ if (uplink_amsdu) {
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nUplink AMSDU\n"
+ "-----------------------------------\n");
+
+ for (i = 0; i < RX_MAX_MSDU_IN_AMSDU; i++)
+ if (rx_info->amsdu_cnt[i] > 0)
+ cl_snprintf(&buf, &len, &buf_size,
+ "AMSDU[%d] = %u\n", i + 1, rx_info->amsdu_cnt[i]);
+ }
+
+ if (cl_hw->conf->ci_rx_remote_cpu_drv != -1 ||
+ cl_hw->conf->ci_rx_remote_cpu_mac != -1) {
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nRemote CPU\n"
+ "-----------------------------------\n");
+
+ for (i = 0; i < CPU_MAX_NUM; i++) {
+ if (rx_info->remote_cpu[i] == 0)
+ continue;
+
+ cl_snprintf(&buf, &len, &buf_size, "cpu #%u: %u\n",
+ i, rx_info->remote_cpu[i]);
+ }
+ }
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nUplink schedule\n"
+ "-----------------------------------\n"
+ "exceed_pkt_budget = %u\n",
+ rx_info->exceed_pkt_budget);
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nUplink buckets RXM\n"
+ "-----------------------------------\n");
+
+ for (i = 0; i < IPC_RXBUF_NUM_BUCKETS_RXM; i++) {
+ if (rx_info->pkt_handle_bucket_rxm[i] == 0)
+ continue;
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "Bucket [%lu -> %lu]: %u\n",
+ i * IPC_RXBUF_BUCKET_SIZE,
+ (i + 1) * IPC_RXBUF_BUCKET_SIZE - 1,
+ rx_info->pkt_handle_bucket_rxm[i]);
+ }
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "\nUplink buckets FW\n"
+ "-----------------------------------\n");
+
+ for (i = 0; i < IPC_RXBUF_NUM_BUCKETS_FW; i++) {
+ if (rx_info->pkt_handle_bucket_fw[i] == 0)
+ continue;
+
+ cl_snprintf(&buf, &len, &buf_size,
+ "Bucket [%lu -> %lu]: %u\n",
+ i * IPC_RXBUF_BUCKET_SIZE,
+ (i + 1) * IPC_RXBUF_BUCKET_SIZE - 1,
+ rx_info->pkt_handle_bucket_fw[i]);
+ }
+
+ err = cl_vendor_reply(cl_hw, buf, len);
+ kfree(buf);
+
+ return err;
+}
+
+void cl_rx_netif(struct cl_hw *cl_hw, struct cl_vif *cl_vif, struct sk_buff *skb)
+{
+#ifdef TRACE_SUPPORT
+ trace_cl_trace_netif_rx_start(cl_hw->idx);
+#endif
+
+ cl_hw->rx_info.netif_rx++;
+
+ netif_receive_skb(skb);
+
+#ifdef TRACE_SUPPORT
+ trace_cl_trace_netif_rx_end(cl_hw->idx);
+#endif
+}
+
+void cl_rx_finish(struct cl_hw *cl_hw, struct sk_buff *skb)
+{
+ struct cl_vif *cl_vif = NETDEV_TO_CL_VIF(skb->dev);
+
+ cl_rx_netif(cl_hw, cl_vif, skb);
+}
+
+u8 cl_rx_get_skb_ac(struct ieee80211_hdr *hdr)
+{
+ if (ieee80211_is_data_qos(hdr->frame_control)) {
+ u8 *qos_ctl = ieee80211_get_qos_ctl(hdr);
+ u8 tid = *qos_ctl & IEEE80211_QOS_CTL_TAG1D_MASK;
+ return tid_to_ac[tid];
+ }
+
+ return AC_BE;
+}
+
+bool cl_rx_process_in_irq(struct cl_hw *cl_hw)
+{
+ struct cl_ipc_ring_indices *indices = cl_hw->ipc_env->ring_indices_elem->indices;
+ u32 read_idx = le32_to_cpu(indices->rxdesc_read_idx[CL_RX_BUF_RXM]);
+ u32 write_idx = le32_to_cpu(indices->rxdesc_write_idx[CL_RX_BUF_RXM]);
+ u32 free_buffers = read_idx - write_idx;
+
+ if (free_buffers < (IPC_RXBUF_CNT_RXM / 2)) {
+ cl_hw->rx_info.buffer_process_irq++;
+ return true;
+ }
+
+ cl_hw->rx_info.buffer_process_tasklet++;
+ return false;
+}
+