| /* |
| * Copyright (c) 2011-2014, 2016-2018 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * 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. |
| */ |
| |
| /* |
| * This file was originally distributed by Qualcomm Atheros, Inc. |
| * under proprietary terms before Copyright ownership was assigned |
| * to the Linux Foundation. |
| */ |
| |
| /*- |
| * Copyright (c) 2002-2007 Sam Leffler, Errno Consulting |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| #include <ol_htt_api.h> |
| #include <ol_txrx_api.h> |
| #include <ol_txrx_htt_api.h> |
| #include <ol_htt_rx_api.h> |
| #include <ol_txrx_types.h> |
| #include <ol_rx_reorder.h> |
| #include <ol_rx_pn.h> |
| #include <ol_rx_fwd.h> |
| #include <ol_rx.h> |
| #include <ol_txrx_internal.h> |
| #include <ol_ctrl_txrx_api.h> |
| #include <ol_txrx_peer_find.h> |
| #include <adf_nbuf.h> |
| #include <ieee80211.h> |
| #include <adf_os_util.h> |
| #include <athdefs.h> |
| #include <adf_os_mem.h> |
| #include <ol_rx_defrag.h> |
| #include <adf_os_io.h> |
| #include <enet.h> |
| #include <adf_os_time.h> /* adf_os_time */ |
| |
| |
| #define DEFRAG_IEEE80211_ADDR_EQ(a1, a2) \ |
| (adf_os_mem_cmp(a1, a2, IEEE80211_ADDR_LEN) == 0) |
| |
| #define DEFRAG_IEEE80211_ADDR_COPY(dst, src) \ |
| adf_os_mem_copy(dst, src, IEEE80211_ADDR_LEN) |
| |
| #define DEFRAG_IEEE80211_QOS_HAS_SEQ(wh) \ |
| (((wh)->i_fc[0] & \ |
| (IEEE80211_FC0_TYPE_MASK | IEEE80211_FC0_SUBTYPE_QOS)) == \ |
| (IEEE80211_FC0_TYPE_DATA | IEEE80211_FC0_SUBTYPE_QOS)) |
| |
| #define DEFRAG_IEEE80211_QOS_GET_TID(_x) \ |
| ((_x)->i_qos[0] & IEEE80211_QOS_TID) |
| |
| const struct ol_rx_defrag_cipher f_ccmp = { |
| "AES-CCM", |
| IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN + IEEE80211_WEP_EXTIVLEN, |
| IEEE80211_WEP_MICLEN, |
| 0, |
| }; |
| |
| const struct ol_rx_defrag_cipher f_tkip = { |
| "TKIP", |
| IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN + IEEE80211_WEP_EXTIVLEN, |
| IEEE80211_WEP_CRCLEN, |
| IEEE80211_WEP_MICLEN, |
| }; |
| |
| const struct ol_rx_defrag_cipher f_wep = { |
| "WEP", |
| IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN, |
| IEEE80211_WEP_CRCLEN, |
| 0, |
| }; |
| |
| #if defined(CONFIG_HL_SUPPORT) |
| static inline struct ieee80211_frame *OL_RX_FRAG_GET_MAC_HDR( |
| htt_pdev_handle htt_pdev, adf_nbuf_t frag) |
| { |
| void *rx_desc; |
| int rx_desc_len; |
| |
| rx_desc = htt_rx_msdu_desc_retrieve(htt_pdev, frag); |
| rx_desc_len = htt_rx_msdu_rx_desc_size_hl(htt_pdev, rx_desc); |
| return (struct ieee80211_frame *) (adf_nbuf_data(frag) + rx_desc_len); |
| } |
| static inline void OL_RX_FRAG_PULL_HDR(htt_pdev_handle htt_pdev, |
| adf_nbuf_t frag, int hdrsize) |
| { |
| void *rx_desc; |
| int rx_desc_len; |
| |
| rx_desc = htt_rx_msdu_desc_retrieve(htt_pdev, frag); |
| rx_desc_len = htt_rx_msdu_rx_desc_size_hl(htt_pdev, rx_desc); |
| adf_nbuf_pull_head(frag, rx_desc_len + hdrsize); |
| } |
| #else |
| #define OL_RX_FRAG_GET_MAC_HDR(pdev, frag) \ |
| (struct ieee80211_frame *) adf_nbuf_data(frag) |
| #define OL_RX_FRAG_PULL_HDR(pdev, frag, hdrsize) \ |
| adf_nbuf_pull_head(frag, hdrsize); |
| #endif /* CONFIG_HL_SUPPORT */ |
| |
| static inline void |
| ol_rx_frag_desc_adjust( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t msdu, |
| void **rx_desc_old_position, |
| void **ind_old_position, |
| int *rx_desc_len) |
| { |
| if (pdev->cfg.is_high_latency) { |
| *rx_desc_old_position = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, msdu); |
| *ind_old_position = *rx_desc_old_position - HTT_RX_IND_HL_BYTES; |
| *rx_desc_len = htt_rx_msdu_rx_desc_size_hl(pdev->htt_pdev, |
| *rx_desc_old_position); |
| } else { |
| *rx_desc_old_position = NULL; |
| *ind_old_position = NULL; |
| *rx_desc_len = 0; |
| } |
| } |
| |
| static inline void |
| ol_rx_frag_restructure( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t msdu, |
| void *rx_desc_old_position, |
| void *ind_old_position, |
| const struct ol_rx_defrag_cipher *f_type, |
| int rx_desc_len) |
| { |
| if (pdev->cfg.is_high_latency) { |
| if ((ind_old_position == NULL) || (rx_desc_old_position == NULL)) { |
| printk(KERN_ERR "ind_old_position,rx_desc_old_position is NULL\n"); |
| ASSERT(0); |
| return; |
| } |
| /* move rx description*/ |
| adf_os_mem_move(rx_desc_old_position + f_type->ic_header, |
| rx_desc_old_position, rx_desc_len); |
| /* move rx indication*/ |
| adf_os_mem_move(ind_old_position + f_type->ic_header, ind_old_position, |
| HTT_RX_IND_HL_BYTES); |
| } else { |
| /* no op */ |
| } |
| } |
| |
| /* |
| * Process incoming fragments |
| */ |
| void |
| ol_rx_frag_indication_handler( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t rx_frag_ind_msg, |
| u_int16_t peer_id, |
| u_int8_t tid) |
| { |
| u_int16_t seq_num; |
| u_int16_t seq_num_start, seq_num_end; |
| struct ol_txrx_peer_t *peer; |
| htt_pdev_handle htt_pdev; |
| adf_nbuf_t head_msdu, tail_msdu; |
| void *rx_mpdu_desc; |
| |
| if (tid >= OL_TXRX_NUM_EXT_TIDS) { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "%s: invalid tid, %u\n", |
| __FUNCTION__, |
| tid); |
| return; |
| } |
| |
| htt_pdev = pdev->htt_pdev; |
| peer = ol_txrx_peer_find_by_id(pdev, peer_id); |
| |
| /* In case of reorder offload, we will never get a flush indication */ |
| if (!ol_cfg_is_full_reorder_offload(pdev->ctrl_pdev) && |
| htt_rx_ind_flush(pdev->htt_pdev, rx_frag_ind_msg) && peer) { |
| htt_rx_frag_ind_flush_seq_num_range( |
| pdev->htt_pdev, rx_frag_ind_msg, &seq_num_start, &seq_num_end); |
| /* |
| * Assuming flush indication for frags sent from target is seperate |
| * from normal frames |
| */ |
| ol_rx_reorder_flush_frag(htt_pdev, peer, tid, seq_num_start); |
| } |
| if (peer) { |
| htt_rx_frag_pop(htt_pdev, rx_frag_ind_msg, &head_msdu, &tail_msdu); |
| adf_os_assert(head_msdu == tail_msdu); |
| if (ol_cfg_is_full_reorder_offload(pdev->ctrl_pdev)) { |
| rx_mpdu_desc = htt_rx_mpdu_desc_list_next(htt_pdev, head_msdu); |
| } else { |
| rx_mpdu_desc = htt_rx_mpdu_desc_list_next(htt_pdev, rx_frag_ind_msg); |
| } |
| seq_num = htt_rx_mpdu_desc_seq_num(htt_pdev, rx_mpdu_desc); |
| OL_RX_ERR_STATISTICS_1(pdev, peer->vdev, peer, rx_mpdu_desc, OL_RX_ERR_NONE_FRAG); |
| ol_rx_reorder_store_frag(pdev, peer, tid, seq_num, head_msdu); |
| } else { |
| /* invalid frame - discard it */ |
| htt_rx_frag_pop(htt_pdev, rx_frag_ind_msg, &head_msdu, &tail_msdu); |
| if (ol_cfg_is_full_reorder_offload(pdev->ctrl_pdev)) { |
| htt_rx_msdu_desc_retrieve(htt_pdev, head_msdu); |
| } else { |
| htt_rx_mpdu_desc_list_next(htt_pdev, rx_frag_ind_msg); |
| } |
| htt_rx_desc_frame_free(htt_pdev, head_msdu); |
| } |
| /* request HTT to provide new rx MSDU buffers for the target to fill. */ |
| htt_rx_msdu_buff_replenish(htt_pdev); |
| } |
| |
| /* |
| * Flushing fragments |
| */ |
| void |
| ol_rx_reorder_flush_frag( |
| htt_pdev_handle htt_pdev, |
| struct ol_txrx_peer_t *peer, |
| unsigned tid, |
| u_int16_t seq_num) |
| { |
| struct ol_rx_reorder_array_elem_t *rx_reorder_array_elem; |
| int seq; |
| |
| seq = seq_num & peer->tids_rx_reorder[tid].win_sz_mask; |
| rx_reorder_array_elem = &peer->tids_rx_reorder[tid].array[seq]; |
| if (rx_reorder_array_elem->head) { |
| ol_rx_frames_free(htt_pdev, rx_reorder_array_elem->head); |
| rx_reorder_array_elem->head = NULL; |
| rx_reorder_array_elem->tail = NULL; |
| } |
| } |
| |
| /* |
| * Reorder and store fragments |
| */ |
| void |
| ol_rx_reorder_store_frag( |
| ol_txrx_pdev_handle pdev, |
| struct ol_txrx_peer_t *peer, |
| unsigned tid, |
| u_int16_t seq_num, |
| adf_nbuf_t frag) |
| { |
| struct ieee80211_frame *fmac_hdr, *mac_hdr; |
| u_int8_t fragno, more_frag, all_frag_present = 0; |
| struct ol_rx_reorder_array_elem_t *rx_reorder_array_elem; |
| u_int16_t frxseq, rxseq, seq; |
| htt_pdev_handle htt_pdev = pdev->htt_pdev; |
| |
| seq = seq_num & peer->tids_rx_reorder[tid].win_sz_mask; |
| adf_os_assert(seq == 0); |
| rx_reorder_array_elem = &peer->tids_rx_reorder[tid].array[seq]; |
| |
| mac_hdr = (struct ieee80211_frame *)OL_RX_FRAG_GET_MAC_HDR(htt_pdev, frag); |
| rxseq = adf_os_le16_to_cpu(*(u_int16_t *) mac_hdr->i_seq) >> |
| IEEE80211_SEQ_SEQ_SHIFT; |
| fragno = adf_os_le16_to_cpu(*(u_int16_t *) mac_hdr->i_seq) & |
| IEEE80211_SEQ_FRAG_MASK; |
| more_frag = mac_hdr->i_fc[1] & IEEE80211_FC1_MORE_FRAG; |
| |
| if ((!more_frag) && (!fragno) && (!rx_reorder_array_elem->head)) { |
| ol_rx_fraglist_insert(htt_pdev, &rx_reorder_array_elem->head, |
| &rx_reorder_array_elem->tail, frag, &all_frag_present); |
| adf_nbuf_set_next(frag, NULL); |
| ol_rx_defrag(pdev, peer, tid, rx_reorder_array_elem->head); |
| rx_reorder_array_elem->head = NULL; |
| rx_reorder_array_elem->tail = NULL; |
| return; |
| } |
| if (rx_reorder_array_elem->head) { |
| fmac_hdr = (struct ieee80211_frame *)OL_RX_FRAG_GET_MAC_HDR(htt_pdev, |
| rx_reorder_array_elem->head); |
| frxseq = adf_os_le16_to_cpu(*(u_int16_t *) fmac_hdr->i_seq) >> |
| IEEE80211_SEQ_SEQ_SHIFT; |
| if (rxseq != frxseq || |
| !DEFRAG_IEEE80211_ADDR_EQ(mac_hdr->i_addr1, fmac_hdr->i_addr1) || |
| !DEFRAG_IEEE80211_ADDR_EQ(mac_hdr->i_addr2, fmac_hdr->i_addr2)) |
| { |
| ol_rx_frames_free(htt_pdev, rx_reorder_array_elem->head); |
| rx_reorder_array_elem->head = NULL; |
| rx_reorder_array_elem->tail = NULL; |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "\n ol_rx_reorder_store: %s mismatch \n", |
| (rxseq == frxseq) ? "address" : "seq number"); |
| } |
| } |
| |
| ol_rx_fraglist_insert(htt_pdev, &rx_reorder_array_elem->head, |
| &rx_reorder_array_elem->tail, frag, &all_frag_present); |
| |
| if (pdev->rx.flags.defrag_timeout_check) { |
| ol_rx_defrag_waitlist_remove(peer, tid); |
| } |
| |
| if (all_frag_present) { |
| ol_rx_defrag(pdev, peer, tid, rx_reorder_array_elem->head); |
| rx_reorder_array_elem->head = NULL; |
| rx_reorder_array_elem->tail = NULL; |
| peer->tids_rx_reorder[tid].defrag_timeout_ms = 0; |
| peer->tids_last_seq[tid] = seq_num; |
| } else if (pdev->rx.flags.defrag_timeout_check) { |
| u_int32_t now_ms = adf_os_ticks_to_msecs(adf_os_ticks()); |
| |
| peer->tids_rx_reorder[tid].defrag_timeout_ms = now_ms + pdev->rx.defrag.timeout_ms; |
| ol_rx_defrag_waitlist_add(peer, tid); |
| } |
| } |
| |
| /* |
| * Insert and store fragments |
| */ |
| void |
| ol_rx_fraglist_insert( |
| htt_pdev_handle htt_pdev, |
| adf_nbuf_t *head_addr, |
| adf_nbuf_t *tail_addr, |
| adf_nbuf_t frag, |
| u_int8_t *all_frag_present) |
| { |
| adf_nbuf_t next, prev = NULL, cur = *head_addr; |
| struct ieee80211_frame *mac_hdr, *cmac_hdr, *next_hdr, *lmac_hdr; |
| u_int8_t fragno, cur_fragno, lfragno, next_fragno; |
| u_int8_t last_morefrag = 1, count = 0; |
| |
| adf_os_assert(frag); |
| |
| mac_hdr = (struct ieee80211_frame *) OL_RX_FRAG_GET_MAC_HDR(htt_pdev, frag); |
| fragno = adf_os_le16_to_cpu(*(u_int16_t *) mac_hdr->i_seq) & |
| IEEE80211_SEQ_FRAG_MASK; |
| |
| if (!(*head_addr)) { |
| *head_addr = frag; |
| *tail_addr = frag; |
| adf_nbuf_set_next(*tail_addr, NULL); |
| return; |
| } |
| /* For efficiency, compare with tail first */ |
| lmac_hdr = (struct ieee80211_frame *) OL_RX_FRAG_GET_MAC_HDR(htt_pdev, |
| *tail_addr); |
| lfragno = adf_os_le16_to_cpu(*(u_int16_t *) lmac_hdr->i_seq) & |
| IEEE80211_SEQ_FRAG_MASK; |
| if (fragno > lfragno) { |
| adf_nbuf_set_next(*tail_addr, frag); |
| *tail_addr = frag; |
| adf_nbuf_set_next(*tail_addr, NULL); |
| } else { |
| do { |
| cmac_hdr = (struct ieee80211_frame *) OL_RX_FRAG_GET_MAC_HDR( |
| htt_pdev, cur); |
| cur_fragno = adf_os_le16_to_cpu(*(u_int16_t *) cmac_hdr->i_seq) & |
| IEEE80211_SEQ_FRAG_MASK; |
| prev = cur; |
| cur = adf_nbuf_next(cur); |
| } while (fragno > cur_fragno); |
| |
| if (fragno == cur_fragno) { |
| htt_rx_desc_frame_free(htt_pdev, frag); |
| *all_frag_present = 0; |
| return; |
| } else { |
| adf_nbuf_set_next(prev, frag); |
| adf_nbuf_set_next(frag, cur); |
| } |
| } |
| next = adf_nbuf_next(*head_addr); |
| lmac_hdr = (struct ieee80211_frame *) OL_RX_FRAG_GET_MAC_HDR(htt_pdev, |
| *tail_addr); |
| last_morefrag = lmac_hdr->i_fc[1] & IEEE80211_FC1_MORE_FRAG; |
| if (!last_morefrag) { |
| do { |
| next_hdr = (struct ieee80211_frame *) OL_RX_FRAG_GET_MAC_HDR( |
| htt_pdev, next); |
| next_fragno = adf_os_le16_to_cpu(*(u_int16_t *) next_hdr->i_seq) & |
| IEEE80211_SEQ_FRAG_MASK; |
| count++; |
| if (next_fragno != count) { |
| break; |
| } |
| next = adf_nbuf_next(next); |
| } while (next); |
| |
| if (!next) { |
| *all_frag_present = 1; |
| return; |
| } |
| } |
| *all_frag_present = 0; |
| } |
| |
| /* |
| * add tid to pending fragment wait list |
| */ |
| void |
| ol_rx_defrag_waitlist_add( |
| struct ol_txrx_peer_t *peer, |
| unsigned tid) |
| { |
| struct ol_txrx_pdev_t *pdev = peer->vdev->pdev; |
| struct ol_rx_reorder_t *rx_reorder = &peer->tids_rx_reorder[tid]; |
| |
| TAILQ_INSERT_TAIL(&pdev->rx.defrag.waitlist, rx_reorder, |
| defrag_waitlist_elem); |
| } |
| |
| /* |
| * remove tid from pending fragment wait list |
| */ |
| void |
| ol_rx_defrag_waitlist_remove( |
| struct ol_txrx_peer_t *peer, |
| unsigned tid) |
| { |
| struct ol_txrx_pdev_t *pdev = peer->vdev->pdev; |
| struct ol_rx_reorder_t *rx_reorder = &peer->tids_rx_reorder[tid]; |
| |
| if (rx_reorder->defrag_waitlist_elem.tqe_prev != NULL) { |
| |
| TAILQ_REMOVE(&pdev->rx.defrag.waitlist, rx_reorder, |
| defrag_waitlist_elem); |
| |
| rx_reorder->defrag_waitlist_elem.tqe_next = NULL; |
| rx_reorder->defrag_waitlist_elem.tqe_prev = NULL; |
| } else if (rx_reorder->defrag_waitlist_elem.tqe_next != NULL) { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_FATAL_ERR, "waitlist->tqe_prv = NULL\n"); |
| VOS_ASSERT(0); |
| rx_reorder->defrag_waitlist_elem.tqe_next = NULL; |
| } |
| } |
| |
| #ifndef container_of |
| #define container_of(ptr, type, member) ((type *)( \ |
| (char *)(ptr) - (char *)(&((type *)0)->member) ) ) |
| #endif |
| |
| /* |
| * flush stale fragments from the waitlist |
| */ |
| void |
| ol_rx_defrag_waitlist_flush( |
| struct ol_txrx_pdev_t *pdev) |
| { |
| struct ol_rx_reorder_t *rx_reorder, *tmp; |
| u_int32_t now_ms = adf_os_ticks_to_msecs(adf_os_ticks()); |
| |
| TAILQ_FOREACH_SAFE(rx_reorder, &pdev->rx.defrag.waitlist, |
| defrag_waitlist_elem, tmp) { |
| struct ol_txrx_peer_t *peer; |
| struct ol_rx_reorder_t *rx_reorder_base; |
| unsigned tid; |
| |
| if (rx_reorder->defrag_timeout_ms > now_ms) { |
| break; |
| } |
| |
| tid = rx_reorder->tid; |
| if (tid >= OL_TXRX_NUM_EXT_TIDS) { |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "%s: invalid tid, %u\n", __func__, tid); |
| WARN_ON(1); |
| continue; |
| } |
| /* get index 0 of the rx_reorder array */ |
| rx_reorder_base = rx_reorder - tid; |
| peer = container_of(rx_reorder_base, struct ol_txrx_peer_t, tids_rx_reorder[0]); |
| |
| ol_rx_defrag_waitlist_remove(peer, tid); |
| ol_rx_reorder_flush_frag(pdev->htt_pdev, peer, tid, 0 /* fragments always stored at seq 0*/); |
| } |
| } |
| |
| /* |
| * Handling security checking and processing fragments |
| */ |
| void |
| ol_rx_defrag( |
| ol_txrx_pdev_handle pdev, |
| struct ol_txrx_peer_t *peer, |
| unsigned tid, |
| adf_nbuf_t frag_list) |
| { |
| struct ol_txrx_vdev_t *vdev = NULL; |
| adf_nbuf_t tmp_next, msdu, prev = NULL, cur = frag_list; |
| u_int8_t index, tkip_demic = 0; |
| u_int16_t hdr_space; |
| void *rx_desc; |
| struct ieee80211_frame *wh; |
| u_int8_t key[DEFRAG_IEEE80211_KEY_LEN]; |
| |
| htt_pdev_handle htt_pdev = pdev->htt_pdev; |
| vdev = peer->vdev; |
| |
| /* bypass defrag for safe mode */ |
| if (vdev->safemode) { |
| if (ol_cfg_is_full_reorder_offload(pdev->ctrl_pdev)) { |
| ol_rx_in_order_deliver(vdev, peer, tid, frag_list); |
| } else { |
| ol_rx_deliver(vdev, peer, tid, frag_list); |
| } |
| return; |
| } |
| |
| while (cur) { |
| tmp_next = adf_nbuf_next(cur); |
| adf_nbuf_set_next(cur, NULL); |
| if (!ol_rx_pn_check_base(vdev, peer, tid, cur)) { |
| /* PN check failed,discard frags */ |
| if (prev) { |
| adf_nbuf_set_next(prev, NULL); |
| ol_rx_frames_free(htt_pdev, frag_list); |
| } |
| ol_rx_frames_free(htt_pdev, tmp_next); |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, "ol_rx_defrag: PN Check failed\n"); |
| return; |
| } |
| /* remove FCS from each fragment */ |
| adf_nbuf_trim_tail(cur, DEFRAG_IEEE80211_FCS_LEN); |
| prev = cur; |
| adf_nbuf_set_next(cur, tmp_next); |
| cur = tmp_next; |
| } |
| cur = frag_list; |
| wh = (struct ieee80211_frame *) OL_RX_FRAG_GET_MAC_HDR(htt_pdev, cur); |
| hdr_space = ol_rx_frag_hdrsize(wh); |
| rx_desc = htt_rx_msdu_desc_retrieve(htt_pdev, frag_list); |
| adf_os_assert(htt_rx_msdu_has_wlan_mcast_flag(htt_pdev, rx_desc)); |
| index = htt_rx_msdu_is_wlan_mcast(htt_pdev, rx_desc) ? |
| txrx_sec_mcast : txrx_sec_ucast; |
| |
| switch (peer->security[index].sec_type) { |
| case htt_sec_type_tkip: |
| tkip_demic = 1; |
| /* fall-through to rest of tkip ops */ |
| case htt_sec_type_tkip_nomic: |
| while (cur) { |
| tmp_next = adf_nbuf_next(cur); |
| if (!ol_rx_frag_tkip_decap(pdev, cur, hdr_space)) { |
| /* TKIP decap failed, discard frags */ |
| ol_rx_frames_free(htt_pdev, frag_list); |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "\n ol_rx_defrag: TKIP decap failed\n"); |
| return; |
| } |
| cur = tmp_next; |
| } |
| break; |
| |
| case htt_sec_type_aes_ccmp: |
| while (cur) { |
| tmp_next = adf_nbuf_next(cur); |
| if (!ol_rx_frag_ccmp_demic(pdev, cur, hdr_space)) { |
| /* CCMP demic failed, discard frags */ |
| ol_rx_frames_free(htt_pdev, frag_list); |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "\n ol_rx_defrag: CCMP demic failed\n"); |
| return; |
| } |
| if (!ol_rx_frag_ccmp_decap(pdev, cur, hdr_space)) { |
| /* CCMP decap failed, discard frags */ |
| ol_rx_frames_free(htt_pdev, frag_list); |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "\n ol_rx_defrag: CCMP decap failed\n"); |
| return; |
| } |
| cur = tmp_next; |
| } |
| break; |
| |
| case htt_sec_type_wep40: |
| case htt_sec_type_wep104: |
| case htt_sec_type_wep128: |
| while (cur) { |
| tmp_next = adf_nbuf_next(cur); |
| if (!ol_rx_frag_wep_decap(pdev, cur, hdr_space)) { |
| /* wep decap failed, discard frags */ |
| ol_rx_frames_free(htt_pdev, frag_list); |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "\n ol_rx_defrag: wep decap failed\n"); |
| return; |
| } |
| cur = tmp_next; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| msdu = ol_rx_defrag_decap_recombine(htt_pdev, frag_list, hdr_space); |
| if (!msdu) { |
| return; |
| } |
| |
| if (tkip_demic) { |
| adf_os_mem_copy( |
| key, |
| peer->security[index].michael_key, |
| sizeof(peer->security[index].michael_key)); |
| if (!ol_rx_frag_tkip_demic(pdev, key, msdu, hdr_space)) { |
| htt_rx_desc_frame_free(htt_pdev, msdu); |
| ol_rx_err( |
| pdev->ctrl_pdev, |
| vdev->vdev_id, peer->mac_addr.raw, tid, 0, OL_RX_DEFRAG_ERR, |
| msdu, NULL, 0); |
| TXRX_PRINT(TXRX_PRINT_LEVEL_ERR, |
| "\n ol_rx_defrag: TKIP demic failed\n"); |
| return; |
| } |
| } |
| wh = (struct ieee80211_frame *)OL_RX_FRAG_GET_MAC_HDR(htt_pdev, msdu); |
| if (DEFRAG_IEEE80211_QOS_HAS_SEQ(wh)) { |
| ol_rx_defrag_qos_decap(pdev, msdu, hdr_space); |
| } |
| if (ol_cfg_frame_type(pdev->ctrl_pdev) == wlan_frm_fmt_802_3) { |
| ol_rx_defrag_nwifi_to_8023(pdev, msdu); |
| } |
| ol_rx_fwd_check(vdev, peer, tid, msdu); |
| } |
| |
| /* |
| * Handling TKIP processing for defragmentation |
| */ |
| int |
| ol_rx_frag_tkip_decap( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t msdu, |
| u_int16_t hdrlen) |
| { |
| u_int8_t *ivp, *origHdr; |
| |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| msdu, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| /* Header should have extended IV */ |
| origHdr = (u_int8_t*) (adf_nbuf_data(msdu) + rx_desc_len); |
| |
| ivp = origHdr + hdrlen; |
| if (!(ivp[IEEE80211_WEP_IVLEN] & IEEE80211_WEP_EXTIV)) { |
| return OL_RX_DEFRAG_ERR; |
| } |
| adf_os_mem_move(origHdr + f_tkip.ic_header, origHdr, hdrlen); |
| |
| ol_rx_frag_restructure( |
| pdev, |
| msdu, |
| rx_desc_old_position, |
| ind_old_position, |
| &f_tkip, |
| rx_desc_len); |
| |
| adf_nbuf_pull_head(msdu, f_tkip.ic_header); |
| adf_nbuf_trim_tail(msdu, f_tkip.ic_trailer); |
| return OL_RX_DEFRAG_OK; |
| } |
| |
| /* |
| * Handling WEP processing for defragmentation |
| */ |
| int |
| ol_rx_frag_wep_decap( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t msdu, |
| u_int16_t hdrlen) |
| { |
| u_int8_t *origHdr; |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| msdu, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| origHdr = (u_int8_t*) (adf_nbuf_data(msdu) + rx_desc_len); |
| adf_os_mem_move(origHdr + f_wep.ic_header, origHdr, hdrlen); |
| |
| ol_rx_frag_restructure( |
| pdev, |
| msdu, |
| rx_desc_old_position, |
| ind_old_position, |
| &f_wep, |
| rx_desc_len); |
| adf_nbuf_pull_head(msdu, f_wep.ic_header); |
| adf_nbuf_trim_tail(msdu, f_wep.ic_trailer); |
| return OL_RX_DEFRAG_OK; |
| } |
| |
| /* |
| * Verify and strip MIC from the frame. |
| */ |
| int |
| ol_rx_frag_tkip_demic(ol_txrx_pdev_handle pdev, const u_int8_t *key, |
| adf_nbuf_t msdu, u_int16_t hdrlen) |
| { |
| int status; |
| u_int32_t pktlen; |
| u_int8_t mic[IEEE80211_WEP_MICLEN]; |
| u_int8_t mic0[IEEE80211_WEP_MICLEN]; |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| msdu, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| |
| pktlen = ol_rx_defrag_len(msdu) - rx_desc_len; |
| |
| status = ol_rx_defrag_mic(pdev, key, msdu, hdrlen, |
| pktlen - (hdrlen + f_tkip.ic_miclen), mic); |
| if (status != OL_RX_DEFRAG_OK) { |
| return OL_RX_DEFRAG_ERR; |
| } |
| ol_rx_defrag_copydata( |
| msdu, pktlen - f_tkip.ic_miclen + rx_desc_len, f_tkip.ic_miclen, |
| (caddr_t) mic0); |
| if (adf_os_mem_cmp(mic, mic0, f_tkip.ic_miclen)) { |
| return OL_RX_DEFRAG_ERR; |
| } |
| adf_nbuf_trim_tail(msdu, f_tkip.ic_miclen); |
| return OL_RX_DEFRAG_OK; |
| } |
| |
| /* |
| * Handling CCMP processing for defragmentation |
| */ |
| int |
| ol_rx_frag_ccmp_decap( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t nbuf, |
| u_int16_t hdrlen) |
| { |
| u_int8_t *ivp, *origHdr; |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| nbuf, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| |
| origHdr = (u_int8_t *) (adf_nbuf_data(nbuf) + rx_desc_len); |
| ivp = origHdr + hdrlen; |
| if (!(ivp[IEEE80211_WEP_IVLEN] & IEEE80211_WEP_EXTIV)) { |
| return OL_RX_DEFRAG_ERR; |
| } |
| adf_os_mem_move(origHdr + f_ccmp.ic_header, origHdr, hdrlen); |
| |
| ol_rx_frag_restructure( |
| pdev, |
| nbuf, |
| rx_desc_old_position, |
| ind_old_position, |
| &f_ccmp, |
| rx_desc_len); |
| |
| adf_nbuf_pull_head(nbuf, f_ccmp.ic_header); |
| |
| return OL_RX_DEFRAG_OK; |
| } |
| |
| /* |
| * Verify and strip MIC from the frame. |
| */ |
| int |
| ol_rx_frag_ccmp_demic( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t wbuf, |
| u_int16_t hdrlen) |
| { |
| u_int8_t *ivp, *origHdr; |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| wbuf, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| |
| origHdr = (u_int8_t *) (adf_nbuf_data(wbuf) + rx_desc_len); |
| |
| ivp = origHdr + hdrlen; |
| if (!(ivp[IEEE80211_WEP_IVLEN] & IEEE80211_WEP_EXTIV)) { |
| return OL_RX_DEFRAG_ERR; |
| } |
| adf_nbuf_trim_tail(wbuf, f_ccmp.ic_trailer); |
| |
| return OL_RX_DEFRAG_OK; |
| } |
| |
| /* |
| * Craft pseudo header used to calculate the MIC. |
| */ |
| void |
| ol_rx_defrag_michdr( |
| const struct ieee80211_frame *wh0, |
| u_int8_t hdr[]) |
| { |
| const struct ieee80211_frame_addr4 *wh = |
| (const struct ieee80211_frame_addr4 *) wh0; |
| |
| switch (wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) { |
| case IEEE80211_FC1_DIR_NODS: |
| DEFRAG_IEEE80211_ADDR_COPY(hdr, wh->i_addr1); /* DA */ |
| DEFRAG_IEEE80211_ADDR_COPY(hdr + IEEE80211_ADDR_LEN, wh->i_addr2); |
| break; |
| case IEEE80211_FC1_DIR_TODS: |
| DEFRAG_IEEE80211_ADDR_COPY(hdr, wh->i_addr3); /* DA */ |
| DEFRAG_IEEE80211_ADDR_COPY(hdr + IEEE80211_ADDR_LEN, wh->i_addr2); |
| break; |
| case IEEE80211_FC1_DIR_FROMDS: |
| DEFRAG_IEEE80211_ADDR_COPY(hdr, wh->i_addr1); /* DA */ |
| DEFRAG_IEEE80211_ADDR_COPY(hdr + IEEE80211_ADDR_LEN, wh->i_addr3); |
| break; |
| case IEEE80211_FC1_DIR_DSTODS: |
| DEFRAG_IEEE80211_ADDR_COPY(hdr, wh->i_addr3); /* DA */ |
| DEFRAG_IEEE80211_ADDR_COPY(hdr + IEEE80211_ADDR_LEN, wh->i_addr4); |
| break; |
| } |
| /* |
| * Bit 7 is IEEE80211_FC0_SUBTYPE_QOS for data frame, but |
| * it could also be set for deauth, disassoc, action, etc. for |
| * a mgt type frame. It comes into picture for MFP. |
| */ |
| if (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_QOS) { |
| const struct ieee80211_qosframe *qwh = |
| (const struct ieee80211_qosframe *) wh; |
| hdr[12] = qwh->i_qos[0] & IEEE80211_QOS_TID; |
| } else { |
| hdr[12] = 0; |
| } |
| hdr[13] = hdr[14] = hdr[15] = 0; /* reserved */ |
| } |
| |
| /* |
| * Michael_mic for defragmentation |
| */ |
| int |
| ol_rx_defrag_mic( |
| ol_txrx_pdev_handle pdev, |
| const u_int8_t *key, |
| adf_nbuf_t wbuf, |
| u_int16_t off, |
| u_int16_t data_len, |
| u_int8_t mic[]) |
| { |
| u_int8_t hdr[16] = {0,}; |
| u_int32_t l, r; |
| const u_int8_t *data; |
| u_int32_t space; |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| htt_pdev_handle htt_pdev = pdev->htt_pdev; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| wbuf, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| |
| ol_rx_defrag_michdr((struct ieee80211_frame *) (adf_nbuf_data(wbuf) + |
| rx_desc_len), hdr); |
| l = get_le32(key); |
| r = get_le32(key + 4); |
| |
| /* Michael MIC pseudo header: DA, SA, 3 x 0, Priority */ |
| l ^= get_le32(hdr); |
| michael_block(l, r); |
| l ^= get_le32(&hdr[4]); |
| michael_block(l, r); |
| l ^= get_le32(&hdr[8]); |
| michael_block(l, r); |
| l ^= get_le32(&hdr[12]); |
| michael_block(l, r); |
| |
| /* first buffer has special handling */ |
| data = (u_int8_t *)adf_nbuf_data(wbuf) + rx_desc_len + off; |
| space = ol_rx_defrag_len(wbuf) - rx_desc_len - off; |
| for (;;) { |
| if (space > data_len) { |
| space = data_len; |
| } |
| /* collect 32-bit blocks from current buffer */ |
| while (space >= sizeof(u_int32_t)) { |
| l ^= get_le32(data); |
| michael_block(l, r); |
| data += sizeof(u_int32_t); |
| space -= sizeof(u_int32_t); |
| data_len -= sizeof(u_int32_t); |
| } |
| if (data_len < sizeof(u_int32_t)) { |
| break; |
| } |
| wbuf = adf_nbuf_next(wbuf); |
| if (wbuf == NULL) { |
| return OL_RX_DEFRAG_ERR; |
| } |
| if (pdev->cfg.is_high_latency) { |
| rx_desc_old_position = htt_rx_msdu_desc_retrieve(htt_pdev, wbuf); |
| rx_desc_len = htt_rx_msdu_rx_desc_size_hl(htt_pdev, |
| rx_desc_old_position); |
| } else { |
| rx_desc_len = 0; |
| } |
| if (space != 0) { |
| const u_int8_t *data_next; |
| /* |
| * Block straddles buffers, split references. |
| */ |
| data_next = (u_int8_t *)adf_nbuf_data(wbuf) + rx_desc_len; |
| if ((ol_rx_defrag_len(wbuf) - rx_desc_len) < |
| sizeof(u_int32_t) - space) { |
| return OL_RX_DEFRAG_ERR; |
| } |
| switch (space) { |
| case 1: |
| l ^= get_le32_split( |
| data[0], data_next[0], data_next[1], data_next[2]); |
| data = data_next + 3; |
| space = (ol_rx_defrag_len(wbuf) - rx_desc_len) - 3; |
| break; |
| case 2: |
| l ^= get_le32_split( |
| data[0], data[1], data_next[0], data_next[1]); |
| data = data_next + 2; |
| space = (ol_rx_defrag_len(wbuf) - rx_desc_len) - 2; |
| break; |
| case 3: |
| l ^= get_le32_split( |
| data[0], data[1], data[2], data_next[0]); |
| data = data_next + 1; |
| space = (ol_rx_defrag_len(wbuf) - rx_desc_len) - 1; |
| break; |
| } |
| michael_block(l, r); |
| data_len -= sizeof(u_int32_t); |
| } else { |
| /* |
| * Setup for next buffer. |
| */ |
| data = (u_int8_t*) adf_nbuf_data(wbuf) + rx_desc_len; |
| space = ol_rx_defrag_len(wbuf) - rx_desc_len; |
| } |
| } |
| /* Last block and padding (0x5a, 4..7 x 0) */ |
| switch (data_len) { |
| case 0: |
| l ^= get_le32_split(0x5a, 0, 0, 0); |
| break; |
| case 1: |
| l ^= get_le32_split(data[0], 0x5a, 0, 0); |
| break; |
| case 2: |
| l ^= get_le32_split(data[0], data[1], 0x5a, 0); |
| break; |
| case 3: |
| l ^= get_le32_split(data[0], data[1], data[2], 0x5a); |
| break; |
| } |
| michael_block(l, r); |
| michael_block(l, r); |
| put_le32(mic, l); |
| put_le32(mic + 4, r); |
| |
| return OL_RX_DEFRAG_OK; |
| } |
| |
| /* |
| * Calculate headersize |
| */ |
| u_int16_t |
| ol_rx_frag_hdrsize(const void *data) |
| { |
| const struct ieee80211_frame *wh = (const struct ieee80211_frame *) data; |
| u_int16_t size = sizeof(struct ieee80211_frame); |
| |
| if ((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == IEEE80211_FC1_DIR_DSTODS) { |
| size += IEEE80211_ADDR_LEN; |
| } |
| if (DEFRAG_IEEE80211_QOS_HAS_SEQ(wh)) { |
| size += sizeof(u_int16_t); |
| if (wh->i_fc[1] & IEEE80211_FC1_ORDER) { |
| size += sizeof(struct ieee80211_htc); |
| } |
| } |
| return size; |
| } |
| |
| /* |
| * Recombine and decap fragments |
| */ |
| adf_nbuf_t |
| ol_rx_defrag_decap_recombine( |
| htt_pdev_handle htt_pdev, |
| adf_nbuf_t frag_list, |
| u_int16_t hdrsize) |
| { |
| adf_nbuf_t tmp; |
| adf_nbuf_t msdu = frag_list; |
| adf_nbuf_t rx_nbuf = frag_list; |
| struct ieee80211_frame* wh; |
| |
| msdu = adf_nbuf_next(msdu); |
| adf_nbuf_set_next(rx_nbuf, NULL); |
| while (msdu) { |
| htt_rx_msdu_desc_free(htt_pdev, msdu); |
| adf_net_buf_debug_release_skb(msdu); |
| tmp = adf_nbuf_next(msdu); |
| adf_nbuf_set_next(msdu, NULL); |
| OL_RX_FRAG_PULL_HDR(htt_pdev, msdu, hdrsize); |
| if (!ol_rx_defrag_concat(rx_nbuf, msdu)) { |
| ol_rx_frames_free(htt_pdev, tmp); |
| htt_rx_desc_frame_free(htt_pdev, rx_nbuf); |
| adf_nbuf_free(msdu); /* msdu rx desc already freed above */ |
| return NULL; |
| } |
| msdu = tmp; |
| } |
| wh = (struct ieee80211_frame *)OL_RX_FRAG_GET_MAC_HDR(htt_pdev, rx_nbuf); |
| wh->i_fc[1] &= ~IEEE80211_FC1_MORE_FRAG; |
| *(u_int16_t *) wh->i_seq &= ~IEEE80211_SEQ_FRAG_MASK; |
| |
| return rx_nbuf; |
| } |
| |
| void |
| ol_rx_defrag_nwifi_to_8023(ol_txrx_pdev_handle pdev, adf_nbuf_t msdu) |
| { |
| struct ieee80211_frame wh; |
| a_uint32_t hdrsize; |
| struct llc_snap_hdr_t llchdr; |
| struct ethernet_hdr_t *eth_hdr; |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| struct ieee80211_frame *wh_ptr; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| msdu, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| |
| wh_ptr = (struct ieee80211_frame *) (adf_nbuf_data(msdu) + rx_desc_len); |
| adf_os_mem_copy(&wh, wh_ptr, sizeof(wh)); |
| hdrsize = sizeof(struct ieee80211_frame); |
| adf_os_mem_copy(&llchdr, ((a_uint8_t *) (adf_nbuf_data(msdu) + |
| rx_desc_len)) + hdrsize, sizeof(struct llc_snap_hdr_t)); |
| |
| /* |
| * Now move the data pointer to the beginning of the mac header : |
| * new-header = old-hdr + (wifhdrsize + llchdrsize - ethhdrsize) |
| */ |
| adf_nbuf_pull_head(msdu, (rx_desc_len + hdrsize + |
| sizeof(struct llc_snap_hdr_t) - sizeof(struct ethernet_hdr_t))); |
| eth_hdr = (struct ethernet_hdr_t *)(adf_nbuf_data(msdu)); |
| switch (wh.i_fc[1] & IEEE80211_FC1_DIR_MASK) { |
| case IEEE80211_FC1_DIR_NODS: |
| adf_os_mem_copy(eth_hdr->dest_addr, wh.i_addr1, IEEE80211_ADDR_LEN); |
| adf_os_mem_copy(eth_hdr->src_addr, wh.i_addr2, IEEE80211_ADDR_LEN); |
| break; |
| case IEEE80211_FC1_DIR_TODS: |
| adf_os_mem_copy(eth_hdr->dest_addr, wh.i_addr3, IEEE80211_ADDR_LEN); |
| adf_os_mem_copy(eth_hdr->src_addr, wh.i_addr2, IEEE80211_ADDR_LEN); |
| break; |
| case IEEE80211_FC1_DIR_FROMDS: |
| adf_os_mem_copy(eth_hdr->dest_addr, wh.i_addr1, IEEE80211_ADDR_LEN); |
| adf_os_mem_copy(eth_hdr->src_addr, wh.i_addr3, IEEE80211_ADDR_LEN); |
| break; |
| case IEEE80211_FC1_DIR_DSTODS: |
| break; |
| } |
| |
| adf_os_mem_copy( |
| eth_hdr->ethertype, llchdr.ethertype, sizeof(llchdr.ethertype)); |
| if (pdev->cfg.is_high_latency) { |
| adf_nbuf_push_head(msdu, rx_desc_len); |
| adf_os_mem_move( |
| adf_nbuf_data(msdu), rx_desc_old_position, rx_desc_len); |
| adf_os_mem_move( |
| adf_nbuf_data(msdu) - HTT_RX_IND_HL_BYTES, ind_old_position, |
| HTT_RX_IND_HL_BYTES); |
| } |
| } |
| |
| /* |
| * Handling QOS for defragmentation |
| */ |
| void |
| ol_rx_defrag_qos_decap( |
| ol_txrx_pdev_handle pdev, |
| adf_nbuf_t nbuf, |
| u_int16_t hdrlen) |
| { |
| struct ieee80211_frame *wh; |
| u_int16_t qoslen; |
| void *rx_desc_old_position = NULL; |
| void *ind_old_position = NULL; |
| int rx_desc_len = 0; |
| |
| ol_rx_frag_desc_adjust( |
| pdev, |
| nbuf, |
| &rx_desc_old_position, |
| &ind_old_position, |
| &rx_desc_len); |
| |
| wh = (struct ieee80211_frame *) (adf_nbuf_data(nbuf)+rx_desc_len); |
| if (DEFRAG_IEEE80211_QOS_HAS_SEQ(wh)) { |
| qoslen = sizeof(struct ieee80211_qoscntl); |
| /* Qos frame with Order bit set indicates a HTC frame */ |
| if (wh->i_fc[1] & IEEE80211_FC1_ORDER) { |
| qoslen += sizeof(struct ieee80211_htc); |
| } |
| |
| /* remove QoS filed from header */ |
| hdrlen -= qoslen; |
| adf_os_mem_move((u_int8_t *) wh + qoslen, wh, hdrlen); |
| wh = (struct ieee80211_frame *) adf_nbuf_pull_head(nbuf, |
| rx_desc_len + qoslen); |
| /* clear QoS bit */ |
| /* |
| * KW# 6154 'adf_nbuf_pull_head' in turn calls __adf_nbuf_pull_head, |
| * which returns NULL if there is not sufficient data to pull. |
| * It's guaranteed that adf_nbuf_pull_head will succeed rather than |
| * returning NULL, since the entire rx frame is already present in the |
| * rx buffer. |
| * However, to make it obvious to static analyzers that this code is |
| * safe, add an explicit check that adf_nbuf_pull_head returns a |
| * non-NULL value. |
| * Since this part of the code is not performance-critical, adding this |
| * explicit check is okay. |
| */ |
| if (wh) { |
| wh->i_fc[0] &= ~IEEE80211_FC0_SUBTYPE_QOS; |
| } |
| if (pdev->cfg.is_high_latency) { |
| adf_nbuf_push_head(nbuf, rx_desc_len); |
| adf_os_mem_move( |
| adf_nbuf_data(nbuf), rx_desc_old_position, rx_desc_len); |
| adf_os_mem_move( |
| adf_nbuf_data(nbuf)-HTT_RX_IND_HL_BYTES, ind_old_position, |
| HTT_RX_IND_HL_BYTES); |
| } |
| } |
| } |