/* * Copyright (c) 2011-2021 The Linux Foundation. All rights reserved. * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. 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_malloc,free, etc. */ #include /* qdf_print, bool */ #include /* qdf_nbuf_t, etc. */ #include /* qdf_timer_free */ #include /* HTT_HL_RX_DESC_SIZE */ #include #include #include #include /* HTT_ASSERT, htt_pdev_t, HTT_RX_BUF_SIZE */ #include "regtable.h" #include /* ieee80211_frame, ieee80211_qoscntl */ #include #include #include "ol_txrx_types.h" #ifdef DEBUG_DMA_DONE #include #include #endif #include #define HTT_FCS_LEN (4) enum { HW_RX_DECAP_FORMAT_RAW = 0, HW_RX_DECAP_FORMAT_NWIFI, HW_RX_DECAP_FORMAT_8023, HW_RX_DECAP_FORMAT_ETH2, }; struct mon_rx_status g_ppdu_rx_status; /** * 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("netbuf pop failed!"); 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 = QDF_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("netbuf pop failed!"); 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 = QDF_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); if (ht_flags) rx_status->ht_mcs = mcs; rx_status->ht_flags = ht_flags; rx_status->vht_flags = vht_flags; rx_status->rtap_flags |= ((preamble == SHORT_PREAMBLE) ? BIT(1) : 0); rx_status->vht_flag_values2 = bw; } /** * 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; } 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) { 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); if (rx_desc->ppdu_start.l_sig_rate_select) rx_status->cck_flag = 1; else rx_status->ofdm_flag = 1; rx_status->ant_signal_db = rx_desc->ppdu_start.rssi_comb; rx_status->rssi_comb = rx_desc->ppdu_start.rssi_comb; rx_status->chan_noise_floor = pdev->txrx_pdev->chan_noise_floor; } /** * 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; uint32_t amsdu_len; uint32_t len; uint32_t last_frag; qdf_dma_addr_t paddr; static uint8_t preamble_type; static uint32_t vht_sig_a_1; static uint32_t vht_sig_a_2; 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("netbuf pop failed!"); *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("netbuf pop failed!"); 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)); /* * Only the first mpdu has valid preamble type, so use it * till the last mpdu is reached */ if (rx_desc->attention.first_mpdu) { preamble_type = rx_desc->ppdu_start.preamble_type; if (preamble_type == 8 || preamble_type == 9 || preamble_type == 0x0c || preamble_type == 0x0d) { vht_sig_a_1 = VHT_SIG_A_1(rx_desc); vht_sig_a_2 = VHT_SIG_A_2(rx_desc); } } else { rx_desc->ppdu_start.preamble_type = preamble_type; if (preamble_type == 8 || preamble_type == 9 || preamble_type == 0x0c || preamble_type == 0x0d) { VHT_SIG_A_1(rx_desc) = vht_sig_a_1; VHT_SIG_A_2(rx_desc) = vht_sig_a_2; } } if (rx_desc->attention.last_mpdu) { preamble_type = 0; vht_sig_a_1 = 0; vht_sig_a_2 = 0; } /* * Make the netbuf's data pointer point to the payload rather * than the descriptor. */ if (rx_desc->attention.first_mpdu) { memset(&g_ppdu_rx_status, 0, sizeof(struct mon_rx_status)); htt_rx_mon_get_rx_status(pdev, rx_desc, &g_ppdu_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(&g_ppdu_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 = QDF_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("failed to handle amsdu packet"); 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("netbuf pop failed!"); *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(FEATURE_MONITOR_MODE_SUPPORT) #if !defined(QCA6290_HEADERS_DEF) && !defined(QCA6390_HEADERS_DEF) && \ !defined(QCA6490_HEADERS_DEF) && !defined(QCA6750_HEADERS_DEF) && \ !defined(KIWI_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] & QDF_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 * accommodating 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 #endif