/*
 * Copyright (c) 2011-2014, 2016-2018,2021 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 <linux/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;
    void *rx_desc;
    int index;              /* unicast vs. multicast */

    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;

    /*
     * CR 2868034
     * A broadcast or multicast frame should never be fragmented
     * driver should drop all fragmented broadcast / multicast frames.
     */
    rx_desc = htt_rx_msdu_desc_retrieve(htt_pdev, frag);
    if (htt_rx_msdu_is_wlan_mcast(htt_pdev, rx_desc)) {
        /* Discard Mcast/Bcast single frag frames */
        ol_rx_frames_free(htt_pdev, frag);

        VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
            "%s: QSV2020004 discard MCAST/BCAST frag frame\n\n",
            __FUNCTION__);
        return;
    }

    index = htt_rx_msdu_is_wlan_mcast(htt_pdev, rx_desc) ?
                txrx_sec_mcast : txrx_sec_ucast;

    if (peer->security[index].sec_type != htt_sec_type_none &&
        !htt_rx_mpdu_is_encrypted(htt_pdev, rx_desc)) {
        ol_rx_frames_free(htt_pdev, frag);

        VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
            "%s: QSV2020004, Unencrypted fragment received in security mode %d\n",
            __FUNCTION__,
            peer->security[index].sec_type);

        return;
    }

    if ((!more_frag) && (!fragno) && (!rx_reorder_array_elem->head)) {
        ol_rx_fraglist_insert(htt_pdev, peer,
            &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;

        VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
            "%s: QSV2020008 not expected behavior, but also handled\n",
            __FUNCTION__);
        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, peer,
        &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,
    struct ol_txrx_peer_t *peer,
    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;
    int pn_len = 0;
    void *rx_desc;
    int index;              /* unicast vs. multicast */
    union htt_rx_pn_t curr_pn, next_pn;

    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;
            VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
                   "\n%s:Duplicated frag No, fragno = %u, discard it\n",
                   __FUNCTION__,
                   fragno);
            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) {
        rx_desc = htt_rx_msdu_desc_retrieve(htt_pdev, *head_addr);
        index = htt_rx_msdu_is_wlan_mcast(htt_pdev, rx_desc) ?
                txrx_sec_mcast : txrx_sec_ucast;
        pn_len = htt_pdev->txrx_pdev->rx_pn[peer->security[index].sec_type].len;
        if (pn_len) {
            htt_rx_mpdu_desc_pn(htt_pdev, rx_desc, &curr_pn, pn_len);
        }

        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;

			/*
			 * CR 2868016
			 * Perform strict PN check if non-consecutive
			 * discard all frags
			 */
			if (pn_len) {
				/* retrieve PN from rx descriptor */
			    rx_desc = htt_rx_msdu_desc_retrieve(htt_pdev, next);
			    htt_rx_mpdu_desc_pn(htt_pdev, rx_desc, &next_pn, pn_len);
			}

            count++;
			/*
			 * Fragment number consecutive is gauranteed
			 * by count++, all_frage_present only set to
			 * 1 when checking complete, otherwise, frags
			 * will be discard due to timeout
			 */

            if (next_fragno != count) {
                break;
            }

			if (pn_len) {
				if(!ol_rx_pn_strict_chk(&next_pn,
				    &curr_pn,
				    index == txrx_sec_ucast,
				    pn_len,
				    peer->vdev->opmode)) {
					VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
                               "\n%s:QSV2020003 expected discard non-strict PN behavior, curr_pn=%llu, next_pn=%llu\n",
                               __FUNCTION__,
                               curr_pn.pn48,
                               next_pn.pn48
                               );
					break;
				}

				vos_mem_copy((uint8_t *) &curr_pn,
					         (uint8_t *) &next_pn,
					         sizeof(union htt_rx_pn_t));
			}

            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);
        }
    }
}
