/*
 * Copyright (c) 2012-2020 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.
 */

#include <adf_nbuf.h>         /* adf_nbuf_t, etc. */
#include <htt.h>              /* HTT_TX_EXT_TID_MGMT */
#include <ol_htt_tx_api.h>    /* htt_tx_desc_tid */
#include <ol_txrx_api.h>      /* ol_txrx_vdev_handle */
#include <ol_txrx_ctrl_api.h> /* ol_txrx_sync */
#include <ol_txrx.h>
#include <ol_txrx_internal.h> /* TXRX_ASSERT1 */
#include <ol_txrx_types.h>    /* pdev stats */
#include <ol_tx_desc.h>       /* ol_tx_desc */
#include <ol_tx_send.h>       /* ol_tx_send */
#include <ol_txrx_peer_find.h>
#include <ol_tx_classify.h>
#include <ol_tx_queue.h>
#include <ipv4.h>
#include <ipv6_defs.h>
#include <ip_prot.h>
#include <enet.h>             /* ETHERTYPE_VLAN, etc. */
#include <ieee80211_common.h>        /* ieee80211_frame */

/*
 * In theory, this tx classify code could be used on the host or in the target.
 * Thus, this code uses generic OS primitives, that can be aliased to either
 * the host's OS primitives or the target's OS primitives.
 * For now, the following #defines set up these host-specific or
 * target-specific aliases.
 */

#if defined(CONFIG_HL_SUPPORT)

#define OL_TX_CLASSIFY_EXTENSION(vdev, tx_desc, netbuf, msdu_info, txq)
#define OL_TX_CLASSIFY_MGMT_EXTENSION(vdev, tx_desc, netbuf, msdu_info, txq)

#ifdef QCA_TX_HTT2_SUPPORT
static void
ol_tx_classify_htt2_frm(
    struct ol_txrx_vdev_t *vdev,
    adf_nbuf_t tx_nbuf,
    struct ol_txrx_msdu_info_t *tx_msdu_info)
{
    struct htt_msdu_info_t *htt = &tx_msdu_info->htt;
    A_UINT8 candi_frm = 0;

    /*
     * Offload the frame re-order to L3 protocol and ONLY support
     * TCP protocol now.
     */
    if ((htt->info.l2_hdr_type == htt_pkt_type_ethernet) &&
        (htt->info.frame_type == htt_frm_type_data) &&
        htt->info.is_unicast &&
        (htt->info.ethertype == ETHERTYPE_IPV4))
    {
        struct ipv4_hdr_t *ipHdr;

        ipHdr = (struct ipv4_hdr_t *)(adf_nbuf_data(tx_nbuf) +
                                        htt->info.l3_hdr_offset);
        if (ipHdr->protocol == IP_PROTOCOL_TCP) {
            candi_frm = 1;
        }
    }

    adf_nbuf_set_tx_parallel_dnload_frm(tx_nbuf, candi_frm);
}

#define OL_TX_CLASSIFY_HTT2_EXTENSION(vdev, netbuf, msdu_info)      \
    ol_tx_classify_htt2_frm(vdev, netbuf, msdu_info);
#else
#define OL_TX_CLASSIFY_HTT2_EXTENSION(vdev, netbuf, msdu_info)      /* no-op */
#endif /* QCA_TX_HTT2_SUPPORT */
/* DHCP go with voice priority; WMM_AC_VO_TID1();*/
#define TX_DHCP_TID  6

#if defined(QCA_BAD_PEER_TX_FLOW_CL)
static inline A_BOOL
ol_if_tx_bad_peer_txq_overflow(
    struct ol_txrx_pdev_t *pdev,
    struct ol_txrx_peer_t *peer,
    struct ol_tx_frms_queue_t *txq)
{
     if (peer && pdev && txq && (peer->tx_limit_flag) && (txq->frms >= pdev->tx_peer_bal.peer_bal_txq_limit)) {
          return TRUE;
     } else {
          return FALSE;
     }
}
#else
static inline A_BOOL ol_if_tx_bad_peer_txq_overflow(
    struct ol_txrx_pdev_t *pdev,
    struct ol_txrx_peer_t *peer,
    struct ol_tx_frms_queue_t *txq)
{
    return FALSE;
}
#endif

/* EAPOL go with voice priority: WMM_AC_TO_TID1(WMM_AC_VO);*/
#define TX_EAPOL_TID  6

/* ARP go with voice priority: WMM_AC_TO_TID1(pdev->arp_ac_override)*/
#define TX_ARP_TID  6

/* For non-IP case, use default TID */
#define TX_DEFAULT_TID  0

/*
 * Determine IP TOS priority
 * IP Tos format :
 *        (Refer Pg 57 WMM-test-plan-v1.2)
 * IP-TOS - 8bits
 *            : DSCP(6-bits) ECN(2-bits)
 *            : DSCP - P2 P1 P0 X X X
 *                where (P2 P1 P0) form 802.1D
 */
static inline A_UINT8
ol_tx_tid_by_ipv4(
    A_UINT8 *pkt)
{
    A_UINT8 ipPri, tid;
    struct ipv4_hdr_t *ipHdr = (struct ipv4_hdr_t *)pkt;

    ipPri = ipHdr->tos >> 5;
    tid = ipPri & 0x7;

    return tid;
}

static inline A_UINT8
ol_tx_tid_by_ipv6(
    A_UINT8 *pkt)
{
    return (IPV6_TRAFFIC_CLASS((struct ipv6_hdr_t *) pkt) >> 5) & 0x7;
}

static inline void
ol_tx_set_ether_type(
    A_UINT8 *datap,
    struct ol_txrx_msdu_info_t *tx_msdu_info)
{
    A_UINT16 typeorlength;
    A_UINT8 * ptr;
    A_UINT8 *l3_data_ptr;

    if (tx_msdu_info->htt.info.l2_hdr_type == htt_pkt_type_raw) {
         /* adjust hdr_ptr to RA */
        struct ieee80211_frame *wh = (struct ieee80211_frame *)datap;

        if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) {
            struct llc_snap_hdr_t *llc;
            /* dot11 encapsulated frame */
            struct ieee80211_qosframe *whqos = (struct ieee80211_qosframe *)datap;
            if (whqos->i_fc[0] & IEEE80211_FC0_SUBTYPE_QOS) {
                tx_msdu_info->htt.info.l3_hdr_offset =
                    sizeof(struct ieee80211_qosframe);
            } else {
                tx_msdu_info->htt.info.l3_hdr_offset =
                    sizeof(struct ieee80211_frame);
            }
            llc = (struct llc_snap_hdr_t *)
                (datap + tx_msdu_info->htt.info.l3_hdr_offset);
            tx_msdu_info->htt.info.ethertype =
                (llc->ethertype[0] << 8) | llc->ethertype[1];
            /*
             * l3_hdr_offset refers to the end of the 802.3 or 802.11 header,
             * which may be a LLC/SNAP header rather than the IP header.
             * Thus, don't increment l3_hdr_offset += sizeof(*llc); rather,
             * leave it as is.
             */
        } else {
            /*
             * This function should only be applied to data frames.
             * For management frames, we already know to use HTT_TX_EXT_TID_MGMT.
             */
            TXRX_ASSERT2(0);
        }
    } else if (tx_msdu_info->htt.info.l2_hdr_type == htt_pkt_type_ethernet) {
        ptr = (datap + ETHERNET_ADDR_LEN * 2);
        typeorlength = (ptr[0] << 8) | ptr[1];
        l3_data_ptr = datap + sizeof(struct ethernet_hdr_t);//ETHERNET_HDR_LEN;

        if (typeorlength == ETHERTYPE_VLAN) {
            ptr = (datap + ETHERNET_ADDR_LEN * 2 + ETHERTYPE_VLAN_LEN);
            typeorlength = (ptr[0] << 8) | ptr[1];
            l3_data_ptr += ETHERTYPE_VLAN_LEN;
        }

        if (!IS_ETHERTYPE(typeorlength)) { // 802.3 header
            struct llc_snap_hdr_t *llc_hdr = (struct llc_snap_hdr_t *) l3_data_ptr;
            typeorlength = (llc_hdr->ethertype[0] << 8) | llc_hdr->ethertype[1];
            l3_data_ptr += sizeof(struct llc_snap_hdr_t);
        }

        tx_msdu_info->htt.info.l3_hdr_offset = (A_UINT8)(l3_data_ptr - datap);
        tx_msdu_info->htt.info.ethertype = typeorlength;
    }
}

static inline A_UINT8
ol_tx_tid_by_ether_type(
    A_UINT8 *datap,
    struct ol_txrx_msdu_info_t *tx_msdu_info)
{
    A_UINT8 tid;
    A_UINT8 *l3_data_ptr;
    A_UINT16 typeorlength;

    l3_data_ptr = datap + tx_msdu_info->htt.info.l3_hdr_offset;
    typeorlength = tx_msdu_info->htt.info.ethertype;

    /* IP packet, do packet inspection for TID */
    if (typeorlength == ETHERTYPE_IPV4) {
        tid = ol_tx_tid_by_ipv4(l3_data_ptr);
    } else if (typeorlength == ETHERTYPE_IPV6) {
        tid = ol_tx_tid_by_ipv6(l3_data_ptr);
    } else if (ETHERTYPE_IS_EAPOL_WAPI(typeorlength)) {
        /* EAPOL go with voice priority*/
        tid = TX_EAPOL_TID;
    } else if (typeorlength == ETHERTYPE_ARP) {
        tid = TX_ARP_TID;
    } else {
        /* For non-IP case, use default TID */
        tid = TX_DEFAULT_TID;
    }
    return tid;
}

static inline A_UINT8
ol_tx_tid_by_raw_type(
    A_UINT8 *datap,
    struct ol_txrx_msdu_info_t *tx_msdu_info)
{
    A_UINT8 tid = HTT_TX_EXT_TID_NON_QOS_MCAST_BCAST;

    /* adjust hdr_ptr to RA */
    struct ieee80211_frame *wh = (struct ieee80211_frame *)datap;

    /* FIXME: This code does not handle 4 address formats. The QOS field
     * is not at usual location.
     */
    if ((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == IEEE80211_FC0_TYPE_DATA) {
        /* dot11 encapsulated frame */
        struct ieee80211_qosframe *whqos = (struct ieee80211_qosframe *)datap;
        if (whqos->i_fc[0] & IEEE80211_FC0_SUBTYPE_QOS) {
            tid = whqos->i_qos[0] & IEEE80211_QOS_TID;
        } else {
            tid = HTT_NON_QOS_TID;
        }
    } else {
        /*
         * This function should only be applied to data frames.
         * For management frames, we already know to use HTT_TX_EXT_TID_MGMT.
         */
        adf_os_assert(0);
    }
    return tid;
}

static A_UINT8
ol_tx_tid(
    struct ol_txrx_pdev_t *pdev,
    adf_nbuf_t tx_nbuf,
    struct ol_txrx_msdu_info_t *tx_msdu_info)
{
    A_UINT8 *datap = adf_nbuf_data(tx_nbuf);
    A_UINT8 tid;

    if (pdev->frame_format == wlan_frm_fmt_raw) {
        tx_msdu_info->htt.info.l2_hdr_type = htt_pkt_type_raw;

        ol_tx_set_ether_type(datap, tx_msdu_info);
        tid = tx_msdu_info->htt.info.ext_tid == ADF_NBUF_TX_EXT_TID_INVALID ?
            ol_tx_tid_by_raw_type(datap, tx_msdu_info) :
            tx_msdu_info->htt.info.ext_tid;
    } else if (pdev->frame_format == wlan_frm_fmt_802_3) {
        tx_msdu_info->htt.info.l2_hdr_type = htt_pkt_type_ethernet;

        ol_tx_set_ether_type(datap, tx_msdu_info);
        tid =
            tx_msdu_info->htt.info.ext_tid == ADF_NBUF_TX_EXT_TID_INVALID ?
            ol_tx_tid_by_ether_type(datap, tx_msdu_info) :
            tx_msdu_info->htt.info.ext_tid;
    } else if (pdev->frame_format == wlan_frm_fmt_native_wifi) {
        struct llc_snap_hdr_t *llc;

        tx_msdu_info->htt.info.l2_hdr_type = htt_pkt_type_native_wifi;
        tx_msdu_info->htt.info.l3_hdr_offset = sizeof(struct ieee80211_frame);
        llc = (struct llc_snap_hdr_t *)
            (datap + tx_msdu_info->htt.info.l3_hdr_offset);
        tx_msdu_info->htt.info.ethertype =
            (llc->ethertype[0] << 8) | llc->ethertype[1];
        /*
         * Native WiFi is a special case of "raw" 802.11 header format.
         * However, we expect that for all cases that use native WiFi,
         * the TID will be directly specified out of band.
         */
        tid = tx_msdu_info->htt.info.ext_tid;
    } else {
        VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_FATAL,
            "Invalid standard frame type: %d\n", pdev->frame_format);
        adf_os_assert(0);
        tid = HTT_TX_EXT_TID_INVALID;
    }
    return tid;
}

#if defined(AUDIO_MULTICAST_AGGR_SUPPORT)
static inline int
ol_tx_vo_mcast_group_find(struct ol_audio_multicast_aggr_conf *conf,
			  A_UINT8 *dest_addr, A_UINT8 *group_id)
{
	A_UINT8 i, ret = 0;

	/* double check in critical context */
	if (!conf->aggr_enable || !conf->group_num)
		goto out;

	for (i = 0; i < MAX_GROUP_NUM; i++) {
		if (!conf->multicast_group[i].in_use)
			continue;

		if (!adf_os_mem_cmp(dest_addr,
				    conf->multicast_group[i].macaddr,
				    OL_TXRX_MAC_ADDR_LEN)) {
			*group_id = i;
			ret = 1;
			break;
		}
	}
out:
	return ret;
}

static struct ol_tx_frms_queue_t *
ol_tx_vo_mcast_classify_fixup(adf_nbuf_t tx_nbuf,
			      struct ol_txrx_pdev_t *pdev,
			      struct ol_txrx_vdev_t *vdev,
			      struct ol_tx_desc_t *tx_desc,
			      struct ol_txrx_msdu_info_t *tx_msdu_info,
			      struct ol_tx_frms_queue_t *txq)
{
	A_UINT8 *data;
	struct ol_audio_multicast_aggr_conf *conf;
	A_UINT8 seqno, total_num, group_id;
	A_UINT32 datalen;

	/* sanity check */
        if (vdev->opmode != wlan_op_mode_ap ||
	    pdev->frame_format != wlan_frm_fmt_802_3 ||
	    !adf_nbuf_is_ipv4_pkt(tx_nbuf))
		return txq;

	conf = &vdev->au_mcast_conf;
	if (!conf->aggr_enable)
		return txq;

	data = adf_nbuf_data(tx_nbuf);

	adf_os_spin_lock_bh(&conf->lock);

	if (!ol_tx_vo_mcast_group_find(conf, data, &group_id))
		goto out;

	/*
	 * Packet info format:
	 * 1 byte		1 byte		8 bytes
	 * sequence number	total number	time limit
	 */
	datalen = adf_nbuf_len(tx_nbuf);
	seqno = *(data + datalen - 8 - 2);
	total_num = *(data + datalen - 8 - 1);

	if (!seqno || !total_num || seqno > total_num) {
		conf->packet_error++;
		goto out;
	}

	if (!adf_nbuf_put_tail(tx_nbuf, OL_TX_VO_MCAST_TAG_SIZE)) {
		VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
			  "No enough room to add audio multicast tag");
		goto out;
	}

	/* tsf only for compare, no byte order conversion */
	tx_desc->tsf = *((A_UINT64 *)(data + datalen - 8));

	tx_desc->tag_offset = datalen;
	tx_desc->seqno = seqno;
	tx_desc->total_num = total_num;
	tx_desc->group_id = group_id;

        tx_msdu_info->htt.info.ext_tid = HTT_TX_EXT_TID_MCAST_VO;
        txq = &vdev->txqs[OL_TX_VDEV_MCAST_VO];
	txq->last_enq_time = jiffies;
	conf->enqeue_count++;
out:
	adf_os_spin_unlock_bh(&conf->lock);

	return txq;
}
#else
static struct ol_tx_frms_queue_t *
ol_tx_vo_mcast_classify_fixup(adf_nbuf_t tx_nbuf,
			      struct ol_txrx_pdev_t *pdev,
			      struct ol_txrx_vdev_t *vdev,
			      struct ol_tx_desc_t *tx_desc,
			      struct ol_txrx_msdu_info_t *tx_msdu_info,
			      struct ol_tx_frms_queue_t *txq)
{
	return txq;
}
#endif

struct ol_tx_frms_queue_t *
ol_tx_classify(
    struct ol_txrx_vdev_t *vdev,
    struct ol_tx_desc_t *tx_desc,
    adf_nbuf_t tx_nbuf,
    struct ol_txrx_msdu_info_t *tx_msdu_info)
{
    struct ol_txrx_pdev_t *pdev = vdev->pdev;
    struct ol_txrx_peer_t *peer = NULL;
    struct ol_tx_frms_queue_t *txq = NULL;
    A_UINT8 *dest_addr;
    A_UINT8 tid;
#if defined(CONFIG_HL_SUPPORT) && defined(FEATURE_WLAN_TDLS)
    u_int8_t peer_id;
#endif

    TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__);

    dest_addr = ol_tx_dest_addr_find(pdev, tx_nbuf);
    if (!dest_addr) {
       VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
                 "%s: Invalid dest_addr", __func__);
        return NULL;
    }
    if ((IEEE80211_IS_MULTICAST(dest_addr))
            || (vdev->opmode == wlan_op_mode_ocb)) {
        txq = &vdev->txqs[OL_TX_VDEV_MCAST_BCAST];
        tx_msdu_info->htt.info.ext_tid = HTT_TX_EXT_TID_NON_QOS_MCAST_BCAST;
        if (vdev->opmode == wlan_op_mode_sta) {
            /*
             * The STA sends a frame with a broadcast dest addr (DA) as a
             * unicast frame to the AP's receive addr (RA).
             * Find the peer object that represents the AP that the STA
             * is associated with.
             */
            peer = ol_txrx_assoc_peer_find(vdev);
            if (!peer) {
                VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
                    "Error: STA %pK (%02x:%02x:%02x:%02x:%02x:%02x) "
                    "trying to send bcast DA tx data frame "
                    "w/o association\n",
                    vdev,
                    vdev->mac_addr.raw[0], vdev->mac_addr.raw[1],
                    vdev->mac_addr.raw[2], vdev->mac_addr.raw[3],
                    vdev->mac_addr.raw[4], vdev->mac_addr.raw[5]);
                return NULL; /* error */
            } else if ((peer->security[OL_TXRX_PEER_SECURITY_MULTICAST].sec_type
                              != htt_sec_type_wapi) &&
                              adf_nbuf_is_dhcp_pkt(tx_nbuf)) {
                /* DHCP frame to go with voice priority */
                txq = &peer->txqs[TX_DHCP_TID];
                tx_msdu_info->htt.info.ext_tid = TX_DHCP_TID;
            }
            /*
             * The following line assumes each peer object has a single ID.
             * This is currently true, and is expected to remain true.
             */
            tx_msdu_info->htt.info.peer_id = peer->peer_ids[0];
        } else if (vdev->opmode == wlan_op_mode_ocb) {
            tx_msdu_info->htt.info.peer_id = HTT_INVALID_PEER_ID;
            /* In OCB mode, don't worry about the peer. We don't need it. */
            peer = NULL;
        } else {
            tx_msdu_info->htt.info.peer_id = HTT_INVALID_PEER_ID;
            /*
             * Look up the vdev's BSS peer, so that the classify_extension
             * function can check whether to encrypt multicast / broadcast
             * frames.
             */
            peer = ol_txrx_peer_find_hash_find(pdev, vdev->mac_addr.raw, 0, 1);
            if (!peer) {
                VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
                    "Error: vdev %pK (%02x:%02x:%02x:%02x:%02x:%02x) "
                    "trying to send bcast/mcast, but no self-peer found\n",
                    vdev,
                    vdev->mac_addr.raw[0], vdev->mac_addr.raw[1],
                    vdev->mac_addr.raw[2], vdev->mac_addr.raw[3],
                    vdev->mac_addr.raw[4], vdev->mac_addr.raw[5]);
                return NULL; /* error */
            }

	    txq = ol_tx_vo_mcast_classify_fixup(tx_nbuf, pdev, vdev, tx_desc,
						tx_msdu_info, txq);
        }
        tx_msdu_info->htt.info.is_unicast = FALSE;
    } else {
        /* tid would be overwritten for non QoS case*/
        tid = ol_tx_tid(pdev, tx_nbuf, tx_msdu_info);
        if ((HTT_TX_EXT_TID_INVALID == tid) || (tid >= OL_TX_NUM_TIDS)) {
             VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
                 "%s Error: could not classify packet into valid TID(%d).\n",
                 __func__, tid);
             return NULL;
        }
        #ifdef ATH_SUPPORT_WAPI
        /* Check to see if a frame is a WAI frame */
        if (tx_msdu_info->htt.info.ethertype == ETHERTYPE_WAI) {
            /* WAI frames should not be encrypted */
            tx_msdu_info->htt.action.do_encrypt = 0;
            VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO,
                "Tx Frame is a WAI frame\n");
        }
        #endif /* ATH_SUPPORT_WAPI */

        /*
         * Find the peer and increment its reference count.
         * If this vdev is an AP, use the dest addr (DA) to determine
         * which peer STA this unicast data frame is for.
         * If this vdev is a STA, the unicast data frame is for the
         * AP the STA is associated with.
         */
        if (vdev->opmode == wlan_op_mode_sta) {
            /*
             * TO DO:
             * To support TDLS, first check if there is a TDLS peer STA,
             * and if so, check if the DA matches the TDLS peer STA's
             * MAC address.
             * If there is no peer TDLS STA, or if the DA is not the
             * TDLS STA's address, then the frame is either for the AP
             * itself, or is supposed to be sent to the AP for forwarding.
             */
            #if 0
            if (vdev->num_tdls_peers > 0) {
                peer = NULL;
                for (i = 0; i < vdev->num_tdls_peers); i++) {
                    int differs = adf_os_mem_cmp(
                        vdev->tdls_peers[i]->mac_addr.raw,
                        dest_addr, OL_TXRX_MAC_ADDR_LEN);
                    if (!differs) {
                        peer = vdev->tdls_peers[i];
                        break;
                    }
                }
            } else {
                /* send to AP */
                peer = ol_txrx_assoc_peer_find(vdev);
            }
            #endif
            #if defined(CONFIG_HL_SUPPORT) && defined(FEATURE_WLAN_TDLS)
            if (vdev->hlTdlsFlag) {
                peer = ol_txrx_find_peer_by_addr(pdev, vdev->hl_tdls_ap_mac_addr.raw, &peer_id);
                if (peer &&  (peer->peer_ids[0] == HTT_INVALID_PEER_ID))
                    peer = NULL;
                else {
                    if (peer)
                       adf_os_atomic_inc(&peer->ref_cnt);
                }
            }
            if (!peer)
                peer = ol_txrx_assoc_peer_find(vdev);
            #else
            peer = ol_txrx_assoc_peer_find(vdev);
            #endif
        } else {
            peer = ol_txrx_peer_find_hash_find(pdev, dest_addr, 0, 1);
        }
        tx_msdu_info->htt.info.is_unicast = TRUE;
        if (!peer) {
            /*
             * Unicast data xfer can only happen to an associated peer.
             * It is illegitimate to send unicast data if there is no peer
             * to send it to.
             */
            VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_INFO,
                "Error: vdev %pK (%02x:%02x:%02x:%02x:%02x:%02x) "
                "trying to send unicast tx data frame to an unknown peer\n",
                vdev,
                vdev->mac_addr.raw[0], vdev->mac_addr.raw[1],
                vdev->mac_addr.raw[2], vdev->mac_addr.raw[3],
                vdev->mac_addr.raw[4], vdev->mac_addr.raw[5]);
            return NULL; /* error */
        }
        TX_SCHED_DEBUG_PRINT("Peer found\n");
        if ((adf_nbuf_get_fwd_flag(tx_nbuf) != ADF_NBUF_FWD_FLAG) &&
                          (!peer->qos_capable)) {
            tid = OL_TX_NON_QOS_TID;
        } else if ((peer->security[OL_TXRX_PEER_SECURITY_UNICAST].sec_type
                          != htt_sec_type_wapi) &&
                          adf_nbuf_is_dhcp_pkt(tx_nbuf)) {
            /* DHCP frame to go with voice priority */
            tid = TX_DHCP_TID;
        }

        /* Only allow encryption when in authenticated state */
        if (ol_txrx_peer_state_auth != peer->state) {
            tx_msdu_info->htt.action.do_encrypt = 0;
        }
        txq = &peer->txqs[tid];
        tx_msdu_info->htt.info.ext_tid = tid;
        /*
         * The following line assumes each peer object has a single ID.
         * This is currently true, and is expected to remain true.
         */
        tx_msdu_info->htt.info.peer_id = peer->peer_ids[0];
        /*
         * WORKAROUND - check that the peer ID is valid.
         * If tx data is provided before ol_rx_peer_map_handler is called
         * to record the peer ID specified by the target, then we could
         * end up here with an invalid peer ID.
         * TO DO: rather than dropping the tx frame, pause the txq it
         * goes into, then fill in the peer ID for the entries in the
         * txq when the peer_map event provides the peer ID, and then
         * unpause the txq.
         */
        if (tx_msdu_info->htt.info.peer_id == HTT_INVALID_PEER_ID) {
            if (peer) {
                TXRX_PRINT(TXRX_PRINT_LEVEL_ERR,
                      "%s: remove the peer for invalid peer_id %pK\n",
                      __func__, peer);
                /* remove the peer reference added above */
                ol_txrx_peer_unref_delete(peer);
                tx_msdu_info->peer = NULL;
            }
            return NULL;
        }
    }
    tx_msdu_info->peer = peer;
    if (ol_if_tx_bad_peer_txq_overflow(pdev, peer, txq)) {
        return NULL;
    }
    /*
     * If relevant, do a deeper inspection to determine additional
     * characteristics of the tx frame.
     * If the frame is invalid, then the txq will be set to NULL to
     * indicate an error.
     */
    OL_TX_CLASSIFY_EXTENSION(vdev, tx_desc, tx_nbuf, tx_msdu_info, txq);
    if (IEEE80211_IS_MULTICAST(dest_addr) && vdev->opmode != wlan_op_mode_sta &&
        tx_msdu_info->peer != NULL) {

        TXRX_PRINT(TXRX_PRINT_LEVEL_INFO1,
                      "%s: remove the peer reference %pK\n", __func__, peer);
        /* remove the peer reference added above */
        ol_txrx_peer_unref_delete(tx_msdu_info->peer);
        /* Making peer NULL in case if multicast non STA mode */
        tx_msdu_info->peer = NULL;
    }

    /* Whether this frame can download though HTT2 data pipe or not. */
    OL_TX_CLASSIFY_HTT2_EXTENSION(vdev, tx_nbuf, tx_msdu_info);

    /* Update Tx Queue info */
    tx_desc->txq = txq;

    TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__);
    return txq;
}

struct ol_tx_frms_queue_t *
ol_tx_classify_mgmt(
    struct ol_txrx_vdev_t *vdev,
    struct ol_tx_desc_t *tx_desc,
    adf_nbuf_t tx_nbuf,
    struct ol_txrx_msdu_info_t *tx_msdu_info)
{
    struct ol_txrx_pdev_t *pdev = vdev->pdev;
    struct ol_txrx_peer_t *peer = NULL;
    struct ol_tx_frms_queue_t *txq = NULL;
    A_UINT8 *dest_addr;
    union ol_txrx_align_mac_addr_t local_mac_addr_aligned, *mac_addr;

    TX_SCHED_DEBUG_PRINT("Enter %s\n", __func__);
    dest_addr = ol_tx_dest_addr_find(pdev, tx_nbuf);
    if (!dest_addr) {
       VOS_TRACE(VOS_MODULE_ID_TXRX, VOS_TRACE_LEVEL_ERROR,
                 "%s: Invalid dest_addr", __func__);
        return NULL;
    }
    if (IEEE80211_IS_MULTICAST(dest_addr)) {
        /*
         * AP:  beacons are broadcast,
         *      public action frames (e.g. extended channel switch announce)
         *      may be broadcast
         * STA: probe requests can be either broadcast or unicast
         */
        txq = &vdev->txqs[OL_TX_VDEV_DEFAULT_MGMT];
        tx_msdu_info->htt.info.peer_id = HTT_INVALID_PEER_ID;
        tx_msdu_info->peer = NULL;
        tx_msdu_info->htt.info.is_unicast = 0;
    } else {
        /*
         * Find the peer and increment its reference count.
         * If this vdev is an AP, use the receiver addr (RA) to determine
         * which peer STA this unicast mgmt frame is for.
         * If this vdev is a STA, the unicast mgmt frame is for the
         * AP the STA is associated with.
         * Probe request / response and Assoc request / response are
         * sent before the peer exists - in this case, use the
         * vdev's default tx queue.
         */
        if (vdev->opmode == wlan_op_mode_sta) {
            /*
             * TO DO:
             * To support TDLS, first check if there is a TDLS peer STA,
             * and if so, check if the DA matches the TDLS peer STA's
             * MAC address.
             */
            peer = ol_txrx_assoc_peer_find(vdev);
            /*
             * Some special case(preauth for example) needs to send
             * unicast mgmt frame to unassociated AP. In such case,
             * we need to check if dest addr match the associated
             * peer addr. If not, we set peer as NULL to queue this
             * frame to vdev queue.
             */
            if (peer) {
                adf_os_mem_copy(
                    &local_mac_addr_aligned.raw[0],
                    dest_addr, OL_TXRX_MAC_ADDR_LEN);
                mac_addr = &local_mac_addr_aligned;
                if (ol_txrx_peer_find_mac_addr_cmp(mac_addr, &peer->mac_addr) != 0) {
                    adf_os_atomic_dec(&peer->ref_cnt);
                    peer = NULL;
                }
            }
        } else {
            /* find the peer and increment its reference count */
            peer = ol_txrx_peer_find_hash_find(pdev, dest_addr, 0, 1);
        }
        tx_msdu_info->peer = peer;
        if (!peer) {
            txq = &vdev->txqs[OL_TX_VDEV_DEFAULT_MGMT];
            tx_msdu_info->htt.info.peer_id = HTT_INVALID_PEER_ID;
        } else {
            txq = &peer->txqs[HTT_TX_EXT_TID_MGMT];
            tx_msdu_info->htt.info.ext_tid = HTT_TX_EXT_TID_MGMT;
            /*
             * The following line assumes each peer object has a single ID.
             * This is currently true, and is expected to remain true.
             */
            tx_msdu_info->htt.info.peer_id = peer->peer_ids[0];
        }
        tx_msdu_info->htt.info.is_unicast = 1;
    }
    /*
     * If relevant, do a deeper inspection to determine additional
     * characteristics of the tx frame.
     * If the frame is invalid, then the txq will be set to NULL to
     * indicate an error.
     */
    OL_TX_CLASSIFY_MGMT_EXTENSION(vdev, tx_desc, tx_nbuf, tx_msdu_info, txq);

    /* Whether this frame can download though HTT2 data pipe or not. */
    OL_TX_CLASSIFY_HTT2_EXTENSION(vdev, tx_nbuf, tx_msdu_info);

    /* Update Tx Queue info */
    tx_desc->txq = txq;

    TX_SCHED_DEBUG_PRINT("Leave %s\n", __func__);
    return txq;
}

A_STATUS
ol_tx_classify_extension(
    struct ol_txrx_vdev_t *vdev,
    struct ol_tx_desc_t *tx_desc,
    adf_nbuf_t tx_msdu,
    struct ol_txrx_msdu_info_t *msdu_info)
{
    A_UINT8 *datap = adf_nbuf_data(tx_msdu);
    struct ol_txrx_peer_t *peer;
    int which_key;

    /*
     * The following msdu_info fields were already filled in by the
     * ol_tx entry function or the regular ol_tx_classify function:
     *     htt.info.vdev_id            (ol_tx_hl or ol_tx_non_std_hl)
     *     htt.info.ext_tid            (ol_tx_non_std_hl or ol_tx_classify)
     *     htt.info.frame_type         (ol_tx_hl or ol_tx_non_std_hl)
     *     htt.info.l2_hdr_type        (ol_tx_hl or ol_tx_non_std_hl)
     *     htt.info.is_unicast         (ol_tx_classify)
     *     htt.info.peer_id            (ol_tx_classify)
     *     peer                        (ol_tx_classify)
     *     if (is_unicast) {
     *         htt.info.ethertype      (ol_tx_classify)
     *         htt.info.l3_hdr_offset  (ol_tx_classify)
     *     }
     * The following fields need to be filled in by this function:
     *     if (!is_unicast) {
     *         htt.info.ethertype
     *         htt.info.l3_hdr_offset
     *     }
     *     htt.action.band (NOT CURRENTLY USED)
     *     htt.action.do_encrypt
     *     htt.action.do_tx_complete
     * The following fields are not needed for data frames, and can
     * be left uninitialized:
     *     htt.info.frame_subtype
     */

    if (!msdu_info->htt.info.is_unicast) {
        int l2_hdr_size;
        A_UINT16 ethertype;

        if (msdu_info->htt.info.l2_hdr_type == htt_pkt_type_ethernet) {
            struct ethernet_hdr_t *eh;

            eh = (struct ethernet_hdr_t *) datap;
            l2_hdr_size = sizeof(*eh);
            ethertype = (eh->ethertype[0] << 8) | eh->ethertype[1];

            if (ethertype == ETHERTYPE_VLAN) {
                struct ethernet_vlan_hdr_t *evh;

                evh = (struct ethernet_vlan_hdr_t *) datap;
                l2_hdr_size = sizeof(*evh);
                ethertype = (evh->ethertype[0] << 8) | evh->ethertype[1];
            }

            if (!IS_ETHERTYPE(ethertype)) { // 802.3 header
                struct llc_snap_hdr_t *llc =
                    (struct llc_snap_hdr_t *) (datap + l2_hdr_size);
                ethertype = (llc->ethertype[0] << 8) | llc->ethertype[1];
                l2_hdr_size += sizeof(*llc);
            }
            msdu_info->htt.info.l3_hdr_offset = l2_hdr_size;
            msdu_info->htt.info.ethertype = ethertype;
        } else { /* 802.11 */
            struct llc_snap_hdr_t *llc;
            l2_hdr_size = ol_txrx_ieee80211_hdrsize(datap);
            llc = (struct llc_snap_hdr_t *) (datap + l2_hdr_size);
            ethertype = (llc->ethertype[0] << 8) | llc->ethertype[1];
            /*
             * Don't include the LLC/SNAP header in l2_hdr_size, because
             * l3_hdr_offset is actually supposed to refer to the header
             * after the 802.3 or 802.11 header, which could be a LLC/SNAP
             * header rather than the L3 header.
             */
        }
        msdu_info->htt.info.l3_hdr_offset = l2_hdr_size;
        msdu_info->htt.info.ethertype = ethertype;
        which_key = txrx_sec_mcast;
    } else {
        which_key = txrx_sec_ucast;
    }
    peer = msdu_info->peer;
    /*
     * msdu_info->htt.action.do_encrypt is initially set in ol_tx_desc_hl.
     * Add more check here.
     */
    msdu_info->htt.action.do_encrypt = (!peer) ? 0 :
        (peer->security[which_key].sec_type == htt_sec_type_none) ? 0 :
            msdu_info->htt.action.do_encrypt;
    /*
     * For systems that have a frame by frame spec for whether to receive
     * a tx completion notification, use the tx completion notification only
     * for certain management frames, not for data frames.
     * (In the future, this may be changed slightly, e.g. to request a
     * tx completion notification for the final EAPOL message sent by a
     * STA during the key delivery handshake.)
     */
    msdu_info->htt.action.do_tx_complete = 0;

    return A_OK;
}

A_STATUS
ol_tx_classify_mgmt_extension(
    struct ol_txrx_vdev_t *vdev,
    struct ol_tx_desc_t *tx_desc,
    adf_nbuf_t tx_msdu,
    struct ol_txrx_msdu_info_t *msdu_info)
{
    struct ieee80211_frame *wh;

    /*
     * The following msdu_info fields were already filled in by the
     * ol_tx entry function or the regular ol_tx_classify_mgmt function:
     *     htt.info.vdev_id          (ol_txrx_mgmt_send)
     *     htt.info.frame_type       (ol_txrx_mgmt_send)
     *     htt.info.l2_hdr_type      (ol_txrx_mgmt_send)
     *     htt.action.do_tx_complete (ol_txrx_mgmt_send)
     *     htt.info.peer_id          (ol_tx_classify_mgmt)
     *     htt.info.ext_tid          (ol_tx_classify_mgmt)
     *     htt.info.is_unicast       (ol_tx_classify_mgmt)
     *     peer                      (ol_tx_classify_mgmt)
     * The following fields need to be filled in by this function:
     *     htt.info.frame_subtype
     *     htt.info.l3_hdr_offset
     *     htt.action.band (NOT CURRENTLY USED)
     * The following fields are not needed for mgmt frames, and can
     * be left uninitialized:
     *     htt.info.ethertype
     *     htt.action.do_encrypt
     *         (This will be filled in by other SW, which knows whether
     *         the peer has robust-managment-frames enabled.)
     */
    wh = (struct ieee80211_frame *) adf_nbuf_data(tx_msdu);
    msdu_info->htt.info.frame_subtype =
        (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) >>
        IEEE80211_FC0_SUBTYPE_SHIFT;
    msdu_info->htt.info.l3_hdr_offset = sizeof(struct ieee80211_frame);

    return A_OK;
}

#endif /* defined(CONFIG_HL_SUPPORT) */
