| /* |
| * Copyright (c) 2011-2018 The Linux Foundation. All rights reserved. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #include <qdf_mem.h> /* qdf_mem_malloc,free, etc. */ |
| #include <qdf_types.h> /* qdf_print, bool */ |
| #include <qdf_nbuf.h> /* qdf_nbuf_t, etc. */ |
| #include <qdf_timer.h> /* qdf_timer_free */ |
| |
| #include <htt.h> /* HTT_HL_RX_DESC_SIZE */ |
| #include <ol_cfg.h> |
| #include <ol_rx.h> |
| #include <ol_htt_rx_api.h> |
| #include <htt_internal.h> /* HTT_ASSERT, htt_pdev_t, HTT_RX_BUF_SIZE */ |
| #include "regtable.h" |
| |
| #include <cds_ieee80211_common.h> /* ieee80211_frame, ieee80211_qoscntl */ |
| #include <cds_ieee80211_defines.h> /* ieee80211_rx_status */ |
| #include <cds_utils.h> |
| #include <wlan_policy_mgr_api.h> |
| #include "ol_txrx_types.h" |
| #ifdef DEBUG_DMA_DONE |
| #include <asm/barrier.h> |
| #include <wma_api.h> |
| #endif |
| #include <pktlog_ac_fmt.h> |
| |
| #define HTT_FCS_LEN (4) |
| |
| #define MIN(a, b) (((a) < (b)) ? (a) : (b)) |
| |
| enum { |
| HW_RX_DECAP_FORMAT_RAW = 0, |
| HW_RX_DECAP_FORMAT_NWIFI, |
| HW_RX_DECAP_FORMAT_8023, |
| HW_RX_DECAP_FORMAT_ETH2, |
| }; |
| |
| /** |
| * htt_rx_mon_note_capture_channel() - Make note of channel to update in |
| * radiotap |
| * @pdev: handle to htt_pdev |
| * @mon_ch: capture channel number. |
| * |
| * Return: None |
| */ |
| void htt_rx_mon_note_capture_channel(htt_pdev_handle pdev, int mon_ch) |
| { |
| struct mon_channel *ch_info = &pdev->mon_ch_info; |
| |
| ch_info->ch_num = mon_ch; |
| ch_info->ch_freq = cds_chan_to_freq(mon_ch); |
| } |
| |
| #ifndef CONFIG_HL_SUPPORT |
| /** |
| * htt_mon_rx_handle_amsdu_packet() - Handle consecutive fragments of amsdu |
| * @msdu: pointer to first msdu of amsdu |
| * @pdev: Handle to htt_pdev_handle |
| * @msg_word: Input and output variable, so pointer to HTT msg pointer |
| * @amsdu_len: remaining length of all N-1 msdu msdu's |
| * @frag_cnt: number of frags handled |
| * |
| * This function handles the (N-1) msdu's of amsdu, N'th msdu is already |
| * handled by calling function. N-1 msdu's are tied using frags_list. |
| * msdu_info field updated by FW indicates if this is last msdu. All the |
| * msdu's before last msdu will be of MAX payload. |
| * |
| * Return: 1 on success and 0 on failure. |
| */ |
| static |
| int htt_mon_rx_handle_amsdu_packet(qdf_nbuf_t msdu, htt_pdev_handle pdev, |
| uint32_t **msg_word, uint32_t amsdu_len, |
| uint32_t *frag_cnt) |
| { |
| qdf_nbuf_t frag_nbuf; |
| qdf_nbuf_t prev_frag_nbuf; |
| uint32_t len; |
| uint32_t last_frag; |
| qdf_dma_addr_t paddr; |
| |
| *msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| paddr = htt_rx_in_ord_paddr_get(*msg_word); |
| frag_nbuf = htt_rx_in_order_netbuf_pop(pdev, paddr); |
| if (qdf_unlikely(!frag_nbuf)) { |
| qdf_print("%s: netbuf pop failed!\n", __func__); |
| return 0; |
| } |
| *frag_cnt = *frag_cnt + 1; |
| last_frag = ((struct htt_rx_in_ord_paddr_ind_msdu_t *)*msg_word)-> |
| msdu_info; |
| qdf_nbuf_append_ext_list(msdu, frag_nbuf, amsdu_len); |
| qdf_nbuf_set_pktlen(frag_nbuf, HTT_RX_BUF_SIZE); |
| qdf_nbuf_unmap(pdev->osdev, frag_nbuf, QDF_DMA_FROM_DEVICE); |
| /* For msdu's other than parent will not have htt_host_rx_desc_base */ |
| len = MIN(amsdu_len, HTT_RX_BUF_SIZE); |
| amsdu_len -= len; |
| qdf_nbuf_trim_tail(frag_nbuf, HTT_RX_BUF_SIZE - len); |
| |
| HTT_PKT_DUMP(qdf_trace_hex_dump(QDF_MODULE_ID_TXRX, |
| QDF_TRACE_LEVEL_INFO_HIGH, |
| qdf_nbuf_data(frag_nbuf), |
| qdf_nbuf_len(frag_nbuf))); |
| prev_frag_nbuf = frag_nbuf; |
| while (!last_frag) { |
| *msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| paddr = htt_rx_in_ord_paddr_get(*msg_word); |
| frag_nbuf = htt_rx_in_order_netbuf_pop(pdev, paddr); |
| last_frag = ((struct htt_rx_in_ord_paddr_ind_msdu_t *) |
| *msg_word)->msdu_info; |
| |
| if (qdf_unlikely(!frag_nbuf)) { |
| qdf_print("%s: netbuf pop failed!\n", __func__); |
| prev_frag_nbuf->next = NULL; |
| return 0; |
| } |
| *frag_cnt = *frag_cnt + 1; |
| qdf_nbuf_set_pktlen(frag_nbuf, HTT_RX_BUF_SIZE); |
| qdf_nbuf_unmap(pdev->osdev, frag_nbuf, QDF_DMA_FROM_DEVICE); |
| |
| len = MIN(amsdu_len, HTT_RX_BUF_SIZE); |
| amsdu_len -= len; |
| qdf_nbuf_trim_tail(frag_nbuf, HTT_RX_BUF_SIZE - len); |
| HTT_PKT_DUMP(qdf_trace_hex_dump(QDF_MODULE_ID_TXRX, |
| QDF_TRACE_LEVEL_INFO_HIGH, |
| qdf_nbuf_data(frag_nbuf), |
| qdf_nbuf_len(frag_nbuf))); |
| |
| qdf_nbuf_set_next(prev_frag_nbuf, frag_nbuf); |
| prev_frag_nbuf = frag_nbuf; |
| } |
| qdf_nbuf_set_next(prev_frag_nbuf, NULL); |
| return 1; |
| } |
| |
| #define SHORT_PREAMBLE 1 |
| #define LONG_PREAMBLE 0 |
| #ifdef HELIUMPLUS |
| /** |
| * htt_rx_get_rate() - get rate info in terms of 500Kbps from htt_rx_desc |
| * @l_sig_rate_select: OFDM or CCK rate |
| * @l_sig_rate: |
| * |
| * If l_sig_rate_select is 0: |
| * 0x8: OFDM 48 Mbps |
| * 0x9: OFDM 24 Mbps |
| * 0xA: OFDM 12 Mbps |
| * 0xB: OFDM 6 Mbps |
| * 0xC: OFDM 54 Mbps |
| * 0xD: OFDM 36 Mbps |
| * 0xE: OFDM 18 Mbps |
| * 0xF: OFDM 9 Mbps |
| * If l_sig_rate_select is 1: |
| * 0x1: DSSS 1 Mbps long preamble |
| * 0x2: DSSS 2 Mbps long preamble |
| * 0x3: CCK 5.5 Mbps long preamble |
| * 0x4: CCK 11 Mbps long preamble |
| * 0x5: DSSS 2 Mbps short preamble |
| * 0x6: CCK 5.5 Mbps |
| * 0x7: CCK 11 Mbps short preamble |
| * |
| * Return: rate interms of 500Kbps. |
| */ |
| static unsigned char htt_rx_get_rate(uint32_t l_sig_rate_select, |
| uint32_t l_sig_rate, uint8_t *preamble) |
| { |
| char ret = 0x0; |
| *preamble = SHORT_PREAMBLE; |
| if (l_sig_rate_select == 0) { |
| switch (l_sig_rate) { |
| case 0x8: |
| ret = 0x60; |
| break; |
| case 0x9: |
| ret = 0x30; |
| break; |
| case 0xA: |
| ret = 0x18; |
| break; |
| case 0xB: |
| ret = 0x0c; |
| break; |
| case 0xC: |
| ret = 0x6c; |
| break; |
| case 0xD: |
| ret = 0x48; |
| break; |
| case 0xE: |
| ret = 0x24; |
| break; |
| case 0xF: |
| ret = 0x12; |
| break; |
| default: |
| break; |
| } |
| } else if (l_sig_rate_select == 1) { |
| switch (l_sig_rate) { |
| case 0x1: |
| ret = 0x2; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0x2: |
| ret = 0x4; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0x3: |
| ret = 0xB; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0x4: |
| ret = 0x16; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0x5: |
| ret = 0x4; |
| break; |
| case 0x6: |
| ret = 0xB; |
| break; |
| case 0x7: |
| ret = 0x16; |
| break; |
| default: |
| break; |
| } |
| } else { |
| qdf_print("Invalid rate info\n"); |
| } |
| return ret; |
| } |
| #else |
| /** |
| * htt_rx_get_rate() - get rate info in terms of 500Kbps from htt_rx_desc |
| * @l_sig_rate_select: OFDM or CCK rate |
| * @l_sig_rate: |
| * |
| * If l_sig_rate_select is 0: |
| * 0x8: OFDM 48 Mbps |
| * 0x9: OFDM 24 Mbps |
| * 0xA: OFDM 12 Mbps |
| * 0xB: OFDM 6 Mbps |
| * 0xC: OFDM 54 Mbps |
| * 0xD: OFDM 36 Mbps |
| * 0xE: OFDM 18 Mbps |
| * 0xF: OFDM 9 Mbps |
| * If l_sig_rate_select is 1: |
| * 0x8: CCK 11 Mbps long preamble |
| * 0x9: CCK 5.5 Mbps long preamble |
| * 0xA: CCK 2 Mbps long preamble |
| * 0xB: CCK 1 Mbps long preamble |
| * 0xC: CCK 11 Mbps short preamble |
| * 0xD: CCK 5.5 Mbps short preamble |
| * 0xE: CCK 2 Mbps short preamble |
| * |
| * Return: rate interms of 500Kbps. |
| */ |
| static unsigned char htt_rx_get_rate(uint32_t l_sig_rate_select, |
| uint32_t l_sig_rate, uint8_t *preamble) |
| { |
| char ret = 0x0; |
| *preamble = SHORT_PREAMBLE; |
| if (l_sig_rate_select == 0) { |
| switch (l_sig_rate) { |
| case 0x8: |
| ret = 0x60; |
| break; |
| case 0x9: |
| ret = 0x30; |
| break; |
| case 0xA: |
| ret = 0x18; |
| break; |
| case 0xB: |
| ret = 0x0c; |
| break; |
| case 0xC: |
| ret = 0x6c; |
| break; |
| case 0xD: |
| ret = 0x48; |
| break; |
| case 0xE: |
| ret = 0x24; |
| break; |
| case 0xF: |
| ret = 0x12; |
| break; |
| default: |
| break; |
| } |
| } else if (l_sig_rate_select == 1) { |
| switch (l_sig_rate) { |
| case 0x8: |
| ret = 0x16; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0x9: |
| ret = 0x0B; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0xA: |
| ret = 0x4; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0xB: |
| ret = 0x02; |
| *preamble = LONG_PREAMBLE; |
| break; |
| case 0xC: |
| ret = 0x16; |
| break; |
| case 0xD: |
| ret = 0x0B; |
| break; |
| case 0xE: |
| ret = 0x04; |
| break; |
| default: |
| break; |
| } |
| } else { |
| qdf_print("Invalid rate info\n"); |
| } |
| return ret; |
| } |
| #endif /* HELIUMPLUS */ |
| |
| /** |
| * htt_mon_rx_get_phy_info() - Get phy info |
| * @rx_desc: Pointer to struct htt_host_rx_desc_base |
| * @rx_status: Return variable updated with phy_info in rx_status |
| * |
| * Return: None |
| */ |
| static void htt_mon_rx_get_phy_info(struct htt_host_rx_desc_base *rx_desc, |
| struct mon_rx_status *rx_status) |
| { |
| uint8_t preamble = 0; |
| uint8_t preamble_type = rx_desc->ppdu_start.preamble_type; |
| uint8_t mcs = 0, nss = 0, sgi = 0, bw = 0, beamformed = 0; |
| uint16_t vht_flags = 0, ht_flags = 0; |
| uint32_t l_sig_rate_select = rx_desc->ppdu_start.l_sig_rate_select; |
| uint32_t l_sig_rate = rx_desc->ppdu_start.l_sig_rate; |
| bool is_stbc = 0, ldpc = 0; |
| |
| switch (preamble_type) { |
| case 4: |
| /* legacy */ |
| rx_status->rate = htt_rx_get_rate(l_sig_rate_select, l_sig_rate, |
| &preamble); |
| break; |
| case 8: |
| is_stbc = ((VHT_SIG_A_2(rx_desc) >> 4) & 3); |
| /* fallthrough */ |
| case 9: |
| ht_flags = 1; |
| sgi = (VHT_SIG_A_2(rx_desc) >> 7) & 0x01; |
| bw = (VHT_SIG_A_1(rx_desc) >> 7) & 0x01; |
| mcs = (VHT_SIG_A_1(rx_desc) & 0x7f); |
| nss = mcs >> 3; |
| beamformed = |
| (VHT_SIG_A_2(rx_desc) >> 8) & 0x1; |
| break; |
| case 0x0c: |
| is_stbc = (VHT_SIG_A_2(rx_desc) >> 3) & 1; |
| ldpc = (VHT_SIG_A_2(rx_desc) >> 2) & 1; |
| /* fallthrough */ |
| case 0x0d: |
| { |
| uint8_t gid_in_sig = ((VHT_SIG_A_1(rx_desc) >> 4) & 0x3f); |
| |
| vht_flags = 1; |
| sgi = VHT_SIG_A_2(rx_desc) & 0x01; |
| bw = (VHT_SIG_A_1(rx_desc) & 0x03); |
| if (gid_in_sig == 0 || gid_in_sig == 63) { |
| /* SU case */ |
| mcs = (VHT_SIG_A_2(rx_desc) >> 4) & |
| 0xf; |
| nss = (VHT_SIG_A_1(rx_desc) >> 10) & |
| 0x7; |
| } else { |
| /* MU case */ |
| uint8_t sta_user_pos = |
| (uint8_t)((rx_desc->ppdu_start.reserved_4a >> 8) |
| & 0x3); |
| mcs = (rx_desc->ppdu_start.vht_sig_b >> 16); |
| if (bw >= 2) |
| mcs >>= 3; |
| else if (bw > 0) |
| mcs >>= 1; |
| mcs &= 0xf; |
| nss = (((VHT_SIG_A_1(rx_desc) >> 10) + |
| sta_user_pos * 3) & 0x7); |
| } |
| beamformed = (VHT_SIG_A_2(rx_desc) >> 8) & 0x1; |
| } |
| /* fallthrough */ |
| default: |
| break; |
| } |
| |
| rx_status->mcs = mcs; |
| rx_status->bw = bw; |
| rx_status->nr_ant = nss; |
| rx_status->is_stbc = is_stbc; |
| rx_status->sgi = sgi; |
| rx_status->ldpc = ldpc; |
| rx_status->beamformed = beamformed; |
| rx_status->vht_flag_values3[0] = mcs << 0x4 | (nss + 1); |
| rx_status->ht_flags = ht_flags; |
| rx_status->vht_flags = vht_flags; |
| rx_status->rtap_flags |= ((preamble == SHORT_PREAMBLE) ? BIT(1) : 0); |
| if (bw == 0) |
| rx_status->vht_flag_values2 = 0; |
| else if (bw == 1) |
| rx_status->vht_flag_values2 = 1; |
| else if (bw == 2) |
| rx_status->vht_flag_values2 = 4; |
| } |
| |
| /** |
| * htt_mon_rx_get_rtap_flags() - Get radiotap flags |
| * @rx_desc: Pointer to struct htt_host_rx_desc_base |
| * |
| * Return: Bitmapped radiotap flags. |
| */ |
| static uint8_t htt_mon_rx_get_rtap_flags(struct htt_host_rx_desc_base *rx_desc) |
| { |
| uint8_t rtap_flags = 0; |
| |
| /* WEP40 || WEP104 || WEP128 */ |
| if (rx_desc->mpdu_start.encrypt_type == 0 || |
| rx_desc->mpdu_start.encrypt_type == 1 || |
| rx_desc->mpdu_start.encrypt_type == 3) |
| rtap_flags |= BIT(2); |
| |
| /* IEEE80211_RADIOTAP_F_FRAG */ |
| if (rx_desc->attention.fragment) |
| rtap_flags |= BIT(3); |
| |
| /* IEEE80211_RADIOTAP_F_FCS */ |
| rtap_flags |= BIT(4); |
| |
| /* IEEE80211_RADIOTAP_F_BADFCS */ |
| if (rx_desc->mpdu_end.fcs_err) |
| rtap_flags |= BIT(6); |
| |
| return rtap_flags; |
| } |
| |
| /** |
| * htt_rx_mon_get_rx_status() - Update information about the rx status, |
| * which is used later for radiotap updation. |
| * @rx_desc: Pointer to struct htt_host_rx_desc_base |
| * @rx_status: Return variable updated with rx_status |
| * |
| * Return: None |
| */ |
| static void htt_rx_mon_get_rx_status(htt_pdev_handle pdev, |
| struct htt_host_rx_desc_base *rx_desc, |
| struct mon_rx_status *rx_status) |
| { |
| uint16_t channel_flags = 0; |
| struct mon_channel *ch_info = &pdev->mon_ch_info; |
| |
| rx_status->tsft = (u_int64_t)TSF_TIMESTAMP(rx_desc); |
| rx_status->chan_freq = ch_info->ch_freq; |
| rx_status->chan_num = ch_info->ch_num; |
| htt_mon_rx_get_phy_info(rx_desc, rx_status); |
| rx_status->rtap_flags |= htt_mon_rx_get_rtap_flags(rx_desc); |
| channel_flags |= rx_desc->ppdu_start.l_sig_rate_select ? |
| IEEE80211_CHAN_CCK : IEEE80211_CHAN_OFDM; |
| channel_flags |= |
| (cds_chan_to_band(ch_info->ch_num) == CDS_BAND_2GHZ ? |
| IEEE80211_CHAN_2GHZ : IEEE80211_CHAN_5GHZ); |
| |
| rx_status->chan_flags = channel_flags; |
| rx_status->ant_signal_db = rx_desc->ppdu_start.rssi_comb; |
| } |
| |
| /** |
| * htt_rx_mon_amsdu_rx_in_order_pop_ll() - Monitor mode HTT Rx in order pop |
| * function |
| * @pdev: Handle to htt_pdev_handle |
| * @rx_ind_msg: In order indication message. |
| * @head_msdu: Return variable pointing to head msdu. |
| * @tail_msdu: Return variable pointing to tail msdu. |
| * |
| * This function pops the msdu based on paddr:length of inorder indication |
| * message. |
| * |
| * Return: 1 for success, 0 on failure. |
| */ |
| int htt_rx_mon_amsdu_rx_in_order_pop_ll(htt_pdev_handle pdev, |
| qdf_nbuf_t rx_ind_msg, |
| qdf_nbuf_t *head_msdu, |
| qdf_nbuf_t *tail_msdu, |
| uint32_t *replenish_cnt) |
| { |
| qdf_nbuf_t msdu, next, prev = NULL; |
| uint8_t *rx_ind_data; |
| uint32_t *msg_word; |
| uint32_t msdu_count; |
| struct htt_host_rx_desc_base *rx_desc; |
| struct mon_rx_status rx_status = {0}; |
| uint32_t amsdu_len; |
| uint32_t len; |
| uint32_t last_frag; |
| qdf_dma_addr_t paddr; |
| |
| HTT_ASSERT1(htt_rx_in_order_ring_elems(pdev) != 0); |
| |
| rx_ind_data = qdf_nbuf_data(rx_ind_msg); |
| msg_word = (uint32_t *)rx_ind_data; |
| |
| *replenish_cnt = 0; |
| HTT_PKT_DUMP(qdf_trace_hex_dump(QDF_MODULE_ID_TXRX, |
| QDF_TRACE_LEVEL_INFO_HIGH, |
| (void *)rx_ind_data, |
| (int)qdf_nbuf_len(rx_ind_msg))); |
| |
| /* Get the total number of MSDUs */ |
| msdu_count = HTT_RX_IN_ORD_PADDR_IND_MSDU_CNT_GET(*(msg_word + 1)); |
| HTT_RX_CHECK_MSDU_COUNT(msdu_count); |
| |
| msg_word = (uint32_t *)(rx_ind_data + |
| HTT_RX_IN_ORD_PADDR_IND_HDR_BYTES); |
| paddr = htt_rx_in_ord_paddr_get(msg_word); |
| msdu = htt_rx_in_order_netbuf_pop(pdev, paddr); |
| |
| if (qdf_unlikely(!msdu)) { |
| qdf_print("%s: netbuf pop failed!\n", __func__); |
| *tail_msdu = NULL; |
| return 0; |
| } |
| *replenish_cnt = *replenish_cnt + 1; |
| |
| while (msdu_count > 0) { |
| msdu_count--; |
| /* |
| * Set the netbuf length to be the entire buffer length |
| * initially, so the unmap will unmap the entire buffer. |
| */ |
| qdf_nbuf_set_pktlen(msdu, HTT_RX_BUF_SIZE); |
| qdf_nbuf_unmap(pdev->osdev, msdu, QDF_DMA_FROM_DEVICE); |
| |
| /* |
| * cache consistency has been taken care of by the |
| * qdf_nbuf_unmap |
| */ |
| rx_desc = htt_rx_desc(msdu); |
| if ((unsigned int)(*(uint32_t *)&rx_desc->attention) & |
| RX_DESC_ATTN_MPDU_LEN_ERR_BIT) { |
| qdf_nbuf_free(msdu); |
| last_frag = ((struct htt_rx_in_ord_paddr_ind_msdu_t *) |
| msg_word)->msdu_info; |
| while (!last_frag) { |
| msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| paddr = htt_rx_in_ord_paddr_get(msg_word); |
| msdu = htt_rx_in_order_netbuf_pop(pdev, paddr); |
| last_frag = ((struct |
| htt_rx_in_ord_paddr_ind_msdu_t *) |
| msg_word)->msdu_info; |
| if (qdf_unlikely(!msdu)) { |
| qdf_print("%s: netbuf pop failed!\n", |
| __func__); |
| return 0; |
| } |
| *replenish_cnt = *replenish_cnt + 1; |
| qdf_nbuf_unmap(pdev->osdev, msdu, |
| QDF_DMA_FROM_DEVICE); |
| qdf_nbuf_free(msdu); |
| } |
| msdu = prev; |
| goto next_pop; |
| } |
| |
| if (!prev) |
| (*head_msdu) = msdu; |
| prev = msdu; |
| |
| HTT_PKT_DUMP(htt_print_rx_desc(rx_desc)); |
| /* |
| * Make the netbuf's data pointer point to the payload rather |
| * than the descriptor. |
| */ |
| htt_rx_mon_get_rx_status(pdev, rx_desc, &rx_status); |
| /* |
| * For certain platform, 350 bytes of headroom is already |
| * appended to accommodate radiotap header but |
| * qdf_nbuf_update_radiotap() API again will try to create |
| * a room for radiotap header. To make our design simple |
| * let qdf_nbuf_update_radiotap() API create a room for radiotap |
| * header and update it, do qdf_nbuf_pull_head() operation and |
| * pull 350 bytes of headroom. |
| * |
| * |
| * |
| * (SKB buffer) |
| * skb->head --> +-----------+ <-- skb->data |
| * | | (Before pulling headroom) |
| * | | |
| * | HEAD | 350 bytes of headroom |
| * | | |
| * | | |
| * +-----------+ <-- skb->data |
| * | | (After pulling headroom) |
| * | | |
| * | DATA | |
| * | | |
| * | | |
| * +-----------+ |
| * | | |
| * | | |
| * | TAIL | |
| * | | |
| * | | |
| * +-----------+ |
| * |
| */ |
| if (qdf_nbuf_head(msdu) == qdf_nbuf_data(msdu)) |
| qdf_nbuf_pull_head(msdu, HTT_RX_STD_DESC_RESERVATION); |
| qdf_nbuf_update_radiotap(&rx_status, msdu, |
| HTT_RX_STD_DESC_RESERVATION); |
| amsdu_len = HTT_RX_IN_ORD_PADDR_IND_MSDU_LEN_GET(*(msg_word + |
| NEXT_FIELD_OFFSET_IN32)); |
| |
| /* |
| * MAX_RX_PAYLOAD_SZ when we have AMSDU packet. amsdu_len in |
| * which case is the total length of sum of all AMSDU's |
| */ |
| len = MIN(amsdu_len, MAX_RX_PAYLOAD_SZ); |
| amsdu_len -= len; |
| qdf_nbuf_trim_tail(msdu, HTT_RX_BUF_SIZE - |
| (RX_STD_DESC_SIZE + len)); |
| |
| HTT_PKT_DUMP(qdf_trace_hex_dump(QDF_MODULE_ID_TXRX, |
| QDF_TRACE_LEVEL_INFO_HIGH, |
| qdf_nbuf_data(msdu), |
| qdf_nbuf_len(msdu))); |
| last_frag = ((struct htt_rx_in_ord_paddr_ind_msdu_t *) |
| msg_word)->msdu_info; |
| |
| /* Handle amsdu packet */ |
| if (!last_frag) { |
| /* |
| * For AMSDU packet msdu->len is sum of all the msdu's |
| * length, msdu->data_len is sum of length's of |
| * remaining msdu's other than parent. |
| */ |
| if (!htt_mon_rx_handle_amsdu_packet(msdu, pdev, |
| &msg_word, |
| amsdu_len, |
| replenish_cnt)) { |
| qdf_print("%s: failed to handle amsdu packet\n", |
| __func__); |
| return 0; |
| } |
| } |
| |
| next_pop: |
| /* check if this is the last msdu */ |
| if (msdu_count) { |
| msg_word += HTT_RX_IN_ORD_PADDR_IND_MSDU_DWORDS; |
| paddr = htt_rx_in_ord_paddr_get(msg_word); |
| next = htt_rx_in_order_netbuf_pop(pdev, paddr); |
| if (qdf_unlikely(!next)) { |
| qdf_print("%s: netbuf pop failed!\n", |
| __func__); |
| *tail_msdu = NULL; |
| return 0; |
| } |
| *replenish_cnt = *replenish_cnt + 1; |
| if (msdu) |
| qdf_nbuf_set_next(msdu, next); |
| msdu = next; |
| } else { |
| *tail_msdu = msdu; |
| if (msdu) |
| qdf_nbuf_set_next(msdu, NULL); |
| } |
| } |
| |
| return 1; |
| } |
| #endif /* CONFIG_HL_SUPPORT */ |
| |
| #if !defined(QCA6290_HEADERS_DEF) |
| static void |
| htt_rx_parse_ppdu_start_status(struct htt_host_rx_desc_base *rx_desc, |
| struct ieee80211_rx_status *rs) |
| { |
| struct rx_ppdu_start *ppdu_start = &rx_desc->ppdu_start; |
| |
| /* RSSI */ |
| rs->rs_rssi = ppdu_start->rssi_comb; |
| |
| /* PHY rate */ |
| /* |
| * rs_ratephy coding |
| * [b3 - b0] |
| * 0 -> OFDM |
| * 1 -> CCK |
| * 2 -> HT |
| * 3 -> VHT |
| * OFDM / CCK |
| * [b7 - b4 ] => LSIG rate |
| * [b23 - b8 ] => service field |
| * (b'12 static/dynamic, |
| * b'14..b'13 BW for VHT) |
| * [b31 - b24 ] => Reserved |
| * HT / VHT |
| * [b15 - b4 ] => SIG A_2 12 LSBs |
| * [b31 - b16] => SIG A_1 16 LSBs |
| */ |
| if (ppdu_start->preamble_type == 0x4) { |
| rs->rs_ratephy = ppdu_start->l_sig_rate_select; |
| rs->rs_ratephy |= ppdu_start->l_sig_rate << 4; |
| rs->rs_ratephy |= ppdu_start->service << 8; |
| } else { |
| rs->rs_ratephy = (ppdu_start->preamble_type & 0x4) ? 3 : 2; |
| #ifdef HELIUMPLUS |
| rs->rs_ratephy |= |
| (ppdu_start->ht_sig_vht_sig_ah_sig_a_2 & 0xFFF) << 4; |
| rs->rs_ratephy |= |
| (ppdu_start->ht_sig_vht_sig_ah_sig_a_1 & 0xFFFF) << 16; |
| #else |
| rs->rs_ratephy |= (ppdu_start->ht_sig_vht_sig_a_2 & 0xFFF) << 4; |
| rs->rs_ratephy |= |
| (ppdu_start->ht_sig_vht_sig_a_1 & 0xFFFF) << 16; |
| #endif |
| } |
| } |
| |
| /* Util fake function that has same prototype as qdf_nbuf_clone that just |
| * returns the same nbuf |
| */ |
| static qdf_nbuf_t htt_rx_qdf_noclone_buf(qdf_nbuf_t buf) |
| { |
| return buf; |
| } |
| |
| /* This function is used by montior mode code to restitch an MSDU list |
| * corresponding to an MPDU back into an MPDU by linking up the skbs. |
| */ |
| qdf_nbuf_t |
| htt_rx_restitch_mpdu_from_msdus(htt_pdev_handle pdev, |
| qdf_nbuf_t head_msdu, |
| struct ieee80211_rx_status *rx_status, |
| unsigned int clone_not_reqd) |
| { |
| qdf_nbuf_t msdu, mpdu_buf, prev_buf, msdu_orig, head_frag_list_cloned; |
| unsigned int decap_format, wifi_hdr_len, sec_hdr_len, msdu_llc_len, |
| mpdu_buf_len, decap_hdr_pull_bytes, frag_list_sum_len, dir, |
| is_amsdu, is_first_frag, amsdu_pad, msdu_len; |
| struct htt_host_rx_desc_base *rx_desc; |
| char *hdr_desc; |
| unsigned char *dest; |
| struct ieee80211_frame *wh; |
| struct ieee80211_qoscntl *qos; |
| |
| /* The nbuf has been pulled just beyond the status and points to the |
| * payload |
| */ |
| msdu_orig = head_msdu; |
| rx_desc = htt_rx_desc(msdu_orig); |
| |
| /* Fill out the rx_status from the PPDU start and end fields */ |
| if (rx_desc->attention.first_mpdu) { |
| htt_rx_parse_ppdu_start_status(rx_desc, rx_status); |
| |
| /* The timestamp is no longer valid - It will be valid only for |
| * the last MPDU |
| */ |
| rx_status->rs_tstamp.tsf = ~0; |
| } |
| |
| decap_format = |
| GET_FIELD(&rx_desc->msdu_start, RX_MSDU_START_2_DECAP_FORMAT); |
| |
| head_frag_list_cloned = NULL; |
| |
| /* Easy case - The MSDU status indicates that this is a non-decapped |
| * packet in RAW mode. |
| * return |
| */ |
| if (decap_format == HW_RX_DECAP_FORMAT_RAW) { |
| /* Note that this path might suffer from headroom unavailabilty, |
| * but the RX status is usually enough |
| */ |
| if (clone_not_reqd) |
| mpdu_buf = htt_rx_qdf_noclone_buf(head_msdu); |
| else |
| mpdu_buf = qdf_nbuf_clone(head_msdu); |
| |
| if (!mpdu_buf) |
| goto mpdu_stitch_fail; |
| |
| prev_buf = mpdu_buf; |
| |
| frag_list_sum_len = 0; |
| is_first_frag = 1; |
| msdu_len = qdf_nbuf_len(mpdu_buf); |
| |
| /* Drop the zero-length msdu */ |
| if (!msdu_len) |
| goto mpdu_stitch_fail; |
| |
| msdu_orig = qdf_nbuf_next(head_msdu); |
| |
| while (msdu_orig) { |
| /* TODO: intra AMSDU padding - do we need it ??? */ |
| if (clone_not_reqd) |
| msdu = htt_rx_qdf_noclone_buf(msdu_orig); |
| else |
| msdu = qdf_nbuf_clone(msdu_orig); |
| |
| if (!msdu) |
| goto mpdu_stitch_fail; |
| |
| if (is_first_frag) { |
| is_first_frag = 0; |
| head_frag_list_cloned = msdu; |
| } |
| |
| msdu_len = qdf_nbuf_len(msdu); |
| /* Drop the zero-length msdu */ |
| if (!msdu_len) |
| goto mpdu_stitch_fail; |
| |
| frag_list_sum_len += msdu_len; |
| |
| /* Maintain the linking of the cloned MSDUS */ |
| qdf_nbuf_set_next_ext(prev_buf, msdu); |
| |
| /* Move to the next */ |
| prev_buf = msdu; |
| msdu_orig = qdf_nbuf_next(msdu_orig); |
| } |
| |
| /* The last msdu length need be larger than HTT_FCS_LEN */ |
| if (msdu_len < HTT_FCS_LEN) |
| goto mpdu_stitch_fail; |
| |
| qdf_nbuf_trim_tail(prev_buf, HTT_FCS_LEN); |
| |
| /* If there were more fragments to this RAW frame */ |
| if (head_frag_list_cloned) { |
| qdf_nbuf_append_ext_list(mpdu_buf, |
| head_frag_list_cloned, |
| frag_list_sum_len); |
| } |
| |
| goto mpdu_stitch_done; |
| } |
| |
| /* Decap mode: |
| * Calculate the amount of header in decapped packet to knock off based |
| * on the decap type and the corresponding number of raw bytes to copy |
| * status header |
| */ |
| |
| hdr_desc = &rx_desc->rx_hdr_status[0]; |
| |
| /* Base size */ |
| wifi_hdr_len = sizeof(struct ieee80211_frame); |
| wh = (struct ieee80211_frame *)hdr_desc; |
| |
| dir = wh->i_fc[1] & IEEE80211_FC1_DIR_MASK; |
| if (dir == IEEE80211_FC1_DIR_DSTODS) |
| wifi_hdr_len += 6; |
| |
| is_amsdu = 0; |
| if (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_QOS) { |
| qos = (struct ieee80211_qoscntl *) |
| (hdr_desc + wifi_hdr_len); |
| wifi_hdr_len += 2; |
| |
| is_amsdu = (qos->i_qos[0] & IEEE80211_QOS_AMSDU); |
| } |
| |
| /* TODO: Any security headers associated with MPDU */ |
| sec_hdr_len = 0; |
| |
| /* MSDU related stuff LLC - AMSDU subframe header etc */ |
| msdu_llc_len = is_amsdu ? (14 + 8) : 8; |
| |
| mpdu_buf_len = wifi_hdr_len + sec_hdr_len + msdu_llc_len; |
| |
| /* "Decap" header to remove from MSDU buffer */ |
| decap_hdr_pull_bytes = 14; |
| |
| /* Allocate a new nbuf for holding the 802.11 header retrieved from the |
| * status of the now decapped first msdu. Leave enough headroom for |
| * accomodating any radio-tap /prism like PHY header |
| */ |
| #define HTT_MAX_MONITOR_HEADER (512) |
| mpdu_buf = qdf_nbuf_alloc(pdev->osdev, |
| HTT_MAX_MONITOR_HEADER + mpdu_buf_len, |
| HTT_MAX_MONITOR_HEADER, 4, false); |
| |
| if (!mpdu_buf) |
| goto mpdu_stitch_fail; |
| |
| /* Copy the MPDU related header and enc headers into the first buffer |
| * - Note that there can be a 2 byte pad between heaader and enc header |
| */ |
| |
| prev_buf = mpdu_buf; |
| dest = qdf_nbuf_put_tail(prev_buf, wifi_hdr_len); |
| if (!dest) |
| goto mpdu_stitch_fail; |
| qdf_mem_copy(dest, hdr_desc, wifi_hdr_len); |
| hdr_desc += wifi_hdr_len; |
| |
| /* NOTE - This padding is present only in the RAW header status - not |
| * when the MSDU data payload is in RAW format. |
| */ |
| /* Skip the "IV pad" */ |
| if (wifi_hdr_len & 0x3) |
| hdr_desc += 2; |
| |
| /* The first LLC len is copied into the MPDU buffer */ |
| frag_list_sum_len = 0; |
| frag_list_sum_len -= msdu_llc_len; |
| |
| msdu_orig = head_msdu; |
| is_first_frag = 1; |
| amsdu_pad = 0; |
| |
| while (msdu_orig) { |
| /* TODO: intra AMSDU padding - do we need it ??? */ |
| if (clone_not_reqd) |
| msdu = htt_rx_qdf_noclone_buf(msdu_orig); |
| else |
| msdu = qdf_nbuf_clone(msdu_orig); |
| |
| if (!msdu) |
| goto mpdu_stitch_fail; |
| |
| if (is_first_frag) { |
| is_first_frag = 0; |
| head_frag_list_cloned = msdu; |
| } else { |
| /* Maintain the linking of the cloned MSDUS */ |
| qdf_nbuf_set_next_ext(prev_buf, msdu); |
| |
| /* Reload the hdr ptr only on non-first MSDUs */ |
| rx_desc = htt_rx_desc(msdu_orig); |
| hdr_desc = &rx_desc->rx_hdr_status[0]; |
| } |
| |
| /* Copy this buffers MSDU related status into the prev buffer */ |
| dest = qdf_nbuf_put_tail(prev_buf, msdu_llc_len + amsdu_pad); |
| dest += amsdu_pad; |
| qdf_mem_copy(dest, hdr_desc, msdu_llc_len); |
| |
| /* Push the MSDU buffer beyond the decap header */ |
| qdf_nbuf_pull_head(msdu, decap_hdr_pull_bytes); |
| frag_list_sum_len += |
| msdu_llc_len + qdf_nbuf_len(msdu) + amsdu_pad; |
| |
| /* |
| * Set up intra-AMSDU pad to be added to start of next buffer - |
| * AMSDU pad is 4 byte pad on AMSDU subframe |
| */ |
| amsdu_pad = (msdu_llc_len + qdf_nbuf_len(msdu)) & 0x3; |
| amsdu_pad = amsdu_pad ? (4 - amsdu_pad) : 0; |
| |
| /* |
| * TODO FIXME How do we handle MSDUs that have fraglist - Should |
| * probably iterate all the frags cloning them along the way and |
| * and also updating the prev_buf pointer |
| */ |
| |
| /* Move to the next */ |
| prev_buf = msdu; |
| msdu_orig = qdf_nbuf_next(msdu_orig); |
| } |
| |
| /* TODO: Convert this to suitable qdf routines */ |
| qdf_nbuf_append_ext_list(mpdu_buf, head_frag_list_cloned, |
| frag_list_sum_len); |
| |
| mpdu_stitch_done: |
| /* Check if this buffer contains the PPDU end status for TSF */ |
| if (rx_desc->attention.last_mpdu) |
| #ifdef HELIUMPLUS |
| rx_status->rs_tstamp.tsf = |
| rx_desc->ppdu_end.rx_pkt_end.phy_timestamp_1_lower_32; |
| #else |
| rx_status->rs_tstamp.tsf = rx_desc->ppdu_end.tsf_timestamp; |
| #endif |
| /* All the nbufs have been linked into the ext list |
| * and then unlink the nbuf list |
| */ |
| if (clone_not_reqd) { |
| msdu = head_msdu; |
| while (msdu) { |
| msdu_orig = msdu; |
| msdu = qdf_nbuf_next(msdu); |
| qdf_nbuf_set_next(msdu_orig, NULL); |
| } |
| } |
| |
| return mpdu_buf; |
| |
| mpdu_stitch_fail: |
| /* Free these alloced buffers and the orig buffers in non-clone case */ |
| if (!clone_not_reqd) { |
| /* Free the head buffer */ |
| if (mpdu_buf) |
| qdf_nbuf_free(mpdu_buf); |
| |
| /* Free the partial list */ |
| while (head_frag_list_cloned) { |
| msdu = head_frag_list_cloned; |
| head_frag_list_cloned = |
| qdf_nbuf_next_ext(head_frag_list_cloned); |
| qdf_nbuf_free(msdu); |
| } |
| } else { |
| /* Free the alloced head buffer */ |
| if (decap_format != HW_RX_DECAP_FORMAT_RAW) |
| if (mpdu_buf) |
| qdf_nbuf_free(mpdu_buf); |
| |
| /* Free the orig buffers */ |
| msdu = head_msdu; |
| while (msdu) { |
| msdu_orig = msdu; |
| msdu = qdf_nbuf_next(msdu); |
| qdf_nbuf_free(msdu_orig); |
| } |
| } |
| |
| return NULL; |
| } |
| #endif |