/*
 * Copyright (c) 2011, 2014-2017 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 <qdf_net_types.h>      /* QDF_NBUF_EXEMPT_NO_EXEMPTION, etc. */
#include <qdf_nbuf.h>           /* qdf_nbuf_t, etc. */
#include <qdf_util.h>           /* qdf_assert */
#include <qdf_lock.h>           /* qdf_spinlock */
#include <qdf_trace.h>          /* qdf_tso_seg_dbg stuff */
#ifdef QCA_COMPUTE_TX_DELAY
#include <qdf_time.h>           /* qdf_system_ticks */
#endif

#include <ol_htt_tx_api.h>      /* htt_tx_desc_id */

#include <ol_tx_desc.h>
#include <ol_txrx_internal.h>
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
#include <ol_txrx_encap.h>      /* OL_TX_RESTORE_HDR, etc */
#endif
#include <ol_txrx.h>

#ifdef QCA_SUPPORT_TXDESC_SANITY_CHECKS
static inline void ol_tx_desc_sanity_checks(struct ol_txrx_pdev_t *pdev,
					struct ol_tx_desc_t *tx_desc)
{
	if (tx_desc->pkt_type != ol_tx_frm_freed) {
		ol_txrx_err(
				   "%s Potential tx_desc corruption pkt_type:0x%x pdev:0x%p",
				   __func__, tx_desc->pkt_type, pdev);
		qdf_assert(0);
	}
}
static inline void ol_tx_desc_reset_pkt_type(struct ol_tx_desc_t *tx_desc)
{
	tx_desc->pkt_type = ol_tx_frm_freed;
}
#ifdef QCA_COMPUTE_TX_DELAY
static inline void ol_tx_desc_compute_delay(struct ol_tx_desc_t *tx_desc)
{
	if (tx_desc->entry_timestamp_ticks != 0xffffffff) {
		ol_txrx_err("%s Timestamp:0x%x\n",
				   __func__, tx_desc->entry_timestamp_ticks);
		qdf_assert(0);
	}
	tx_desc->entry_timestamp_ticks = qdf_system_ticks();
}
static inline void ol_tx_desc_reset_timestamp(struct ol_tx_desc_t *tx_desc)
{
	tx_desc->entry_timestamp_ticks = 0xffffffff;
}
#endif
#else
static inline void ol_tx_desc_sanity_checks(struct ol_txrx_pdev_t *pdev,
						struct ol_tx_desc_t *tx_desc)
{
}
static inline void ol_tx_desc_reset_pkt_type(struct ol_tx_desc_t *tx_desc)
{
}
static inline void ol_tx_desc_compute_delay(struct ol_tx_desc_t *tx_desc)
{
}
static inline void ol_tx_desc_reset_timestamp(struct ol_tx_desc_t *tx_desc)
{
}
#endif

/**
 * ol_tx_desc_vdev_update() - vedv assign.
 * @tx_desc: tx descriptor pointer
 * @vdev: vdev handle
 *
 * Return: None
 */
static inline void
ol_tx_desc_vdev_update(struct ol_tx_desc_t *tx_desc,
		       struct ol_txrx_vdev_t *vdev)
{
	tx_desc->vdev = vdev;
	tx_desc->vdev_id = vdev->vdev_id;
}

#ifdef CONFIG_PER_VDEV_TX_DESC_POOL

/**
 * ol_tx_desc_count_inc() - tx desc count increment for desc allocation.
 * @vdev: vdev handle
 *
 * Return: None
 */
static inline void
ol_tx_desc_count_inc(struct ol_txrx_vdev_t *vdev)
{
	qdf_atomic_inc(&vdev->tx_desc_count);
}
#else

static inline void
ol_tx_desc_count_inc(struct ol_txrx_vdev_t *vdev)
{
}

#endif

#ifndef QCA_LL_TX_FLOW_CONTROL_V2

/**
 * ol_tx_desc_alloc() - allocate descriptor from freelist
 * @pdev: pdev handle
 * @vdev: vdev handle
 *
 * Return: tx descriptor pointer/ NULL in case of error
 */
static
struct ol_tx_desc_t *ol_tx_desc_alloc(struct ol_txrx_pdev_t *pdev,
					     struct ol_txrx_vdev_t *vdev)
{
	struct ol_tx_desc_t *tx_desc = NULL;

	qdf_spin_lock_bh(&pdev->tx_mutex);
	if (pdev->tx_desc.freelist) {
		tx_desc = ol_tx_get_desc_global_pool(pdev);
		ol_tx_desc_dup_detect_set(pdev, tx_desc);
		ol_tx_desc_sanity_checks(pdev, tx_desc);
		ol_tx_desc_compute_delay(tx_desc);
	}
	qdf_spin_unlock_bh(&pdev->tx_mutex);

	if (!tx_desc)
		return NULL;

	ol_tx_desc_vdev_update(tx_desc, vdev);
	ol_tx_desc_count_inc(vdev);
	qdf_atomic_inc(&tx_desc->ref_cnt);

	return tx_desc;
}

/**
 * ol_tx_desc_alloc_wrapper() -allocate tx descriptor
 * @pdev: pdev handler
 * @vdev: vdev handler
 * @msdu_info: msdu handler
 *
 * Return: tx descriptor or NULL
 */
struct ol_tx_desc_t *
ol_tx_desc_alloc_wrapper(struct ol_txrx_pdev_t *pdev,
			 struct ol_txrx_vdev_t *vdev,
			 struct ol_txrx_msdu_info_t *msdu_info)
{
	return ol_tx_desc_alloc(pdev, vdev);
}

#else
/**
 * ol_tx_desc_alloc() -allocate tx descriptor
 * @pdev: pdev handler
 * @vdev: vdev handler
 * @pool: flow pool
 *
 * Return: tx descriptor or NULL
 */
static
struct ol_tx_desc_t *ol_tx_desc_alloc(struct ol_txrx_pdev_t *pdev,
				      struct ol_txrx_vdev_t *vdev,
				      struct ol_tx_flow_pool_t *pool)
{
	struct ol_tx_desc_t *tx_desc = NULL;

	if (pool) {
		qdf_spin_lock_bh(&pool->flow_pool_lock);
		if (pool->avail_desc) {
			tx_desc = ol_tx_get_desc_flow_pool(pool);
			ol_tx_desc_dup_detect_set(pdev, tx_desc);
			if (qdf_unlikely(pool->avail_desc < pool->stop_th)) {
				pool->status = FLOW_POOL_ACTIVE_PAUSED;
				qdf_spin_unlock_bh(&pool->flow_pool_lock);
				/* pause network queues */
				pdev->pause_cb(vdev->vdev_id,
					       WLAN_STOP_ALL_NETIF_QUEUE,
					       WLAN_DATA_FLOW_CONTROL);
			} else {
				qdf_spin_unlock_bh(&pool->flow_pool_lock);
			}
			ol_tx_desc_sanity_checks(pdev, tx_desc);
			ol_tx_desc_compute_delay(tx_desc);
			ol_tx_desc_vdev_update(tx_desc, vdev);
			qdf_atomic_inc(&tx_desc->ref_cnt);
		} else {
			pool->pkt_drop_no_desc++;
			qdf_spin_unlock_bh(&pool->flow_pool_lock);
		}
	} else {
		pdev->pool_stats.pkt_drop_no_pool++;
	}

	return tx_desc;
}

/**
 * ol_tx_desc_alloc_wrapper() -allocate tx descriptor
 * @pdev: pdev handler
 * @vdev: vdev handler
 * @msdu_info: msdu handler
 *
 * Return: tx descriptor or NULL
 */
#ifdef QCA_LL_TX_FLOW_GLOBAL_MGMT_POOL
struct ol_tx_desc_t *
ol_tx_desc_alloc_wrapper(struct ol_txrx_pdev_t *pdev,
			 struct ol_txrx_vdev_t *vdev,
			 struct ol_txrx_msdu_info_t *msdu_info)
{
	if (qdf_unlikely(msdu_info->htt.info.frame_type == htt_pkt_type_mgmt))
		return ol_tx_desc_alloc(pdev, vdev, pdev->mgmt_pool);
	else
		return ol_tx_desc_alloc(pdev, vdev, vdev->pool);
}
#else
struct ol_tx_desc_t *
ol_tx_desc_alloc_wrapper(struct ol_txrx_pdev_t *pdev,
			 struct ol_txrx_vdev_t *vdev,
			 struct ol_txrx_msdu_info_t *msdu_info)
{
	return ol_tx_desc_alloc(pdev, vdev, vdev->pool);
}
#endif
#endif

/**
 * ol_tx_desc_alloc_hl() - allocate tx descriptor
 * @pdev: pdev handle
 * @vdev: vdev handle
 * @msdu_info: tx msdu info
 *
 * Return: tx descriptor pointer/ NULL in case of error
 */
static struct ol_tx_desc_t *
ol_tx_desc_alloc_hl(struct ol_txrx_pdev_t *pdev,
		    struct ol_txrx_vdev_t *vdev,
		    struct ol_txrx_msdu_info_t *msdu_info)
{
	struct ol_tx_desc_t *tx_desc;

	tx_desc = ol_tx_desc_alloc_wrapper(pdev, vdev, msdu_info);
	if (!tx_desc)
		return NULL;

	qdf_atomic_dec(&pdev->tx_queue.rsrc_cnt);

	return tx_desc;
}

#if defined(CONFIG_PER_VDEV_TX_DESC_POOL) && defined(CONFIG_HL_SUPPORT)

/**
 * ol_tx_desc_vdev_rm() - decrement the tx desc count for vdev.
 * @tx_desc: tx desc
 *
 * Return: None
 */
static inline void
ol_tx_desc_vdev_rm(struct ol_tx_desc_t *tx_desc)
{
	qdf_atomic_dec(&tx_desc->vdev->tx_desc_count);
	tx_desc->vdev = NULL;
}
#else

static inline void
ol_tx_desc_vdev_rm(struct ol_tx_desc_t *tx_desc)
{
}
#endif

#ifdef FEATURE_TSO
static void ol_tx_tso_desc_free(struct ol_txrx_pdev_t *pdev,
				struct ol_tx_desc_t *tx_desc)
{
	if (qdf_unlikely(tx_desc->tso_desc == NULL)) {
		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
			  "%s %d TSO desc is NULL!",
			  __func__, __LINE__);
		qdf_assert(0);
	} else if (qdf_unlikely(tx_desc->tso_num_desc == NULL)) {
		QDF_TRACE(QDF_MODULE_ID_TXRX, QDF_TRACE_LEVEL_ERROR,
			  "%s %d TSO common info is NULL!",
			  __func__, __LINE__);
		qdf_assert(0);
	} else {
		struct qdf_tso_num_seg_elem_t *tso_num_desc =
			(struct qdf_tso_num_seg_elem_t *)tx_desc->tso_num_desc;
		if (tso_num_desc->num_seg.tso_cmn_num_seg > 1) {
			tso_num_desc->num_seg.tso_cmn_num_seg--;
			qdf_nbuf_unmap_tso_segment(pdev->osdev,
						   tx_desc->tso_desc, false);
		} else {
			tso_num_desc->num_seg.tso_cmn_num_seg--;
			qdf_assert(tso_num_desc->num_seg.tso_cmn_num_seg == 0);
			qdf_nbuf_unmap_tso_segment(pdev->osdev,
						   tx_desc->tso_desc, true);
			ol_tso_num_seg_free(pdev, tx_desc->tso_num_desc);
			tx_desc->tso_num_desc = NULL;
		}
		ol_tso_free_segment(pdev, tx_desc->tso_desc);
		tx_desc->tso_desc = NULL;
	}
}

#else
static inline void ol_tx_tso_desc_free(struct ol_txrx_pdev_t *pdev,
				       struct ol_tx_desc_t *tx_desc)
{
}

#endif

#ifndef QCA_LL_TX_FLOW_CONTROL_V2
/**
 * ol_tx_desc_free() - put descriptor to freelist
 * @pdev: pdev handle
 * @tx_desc: tx descriptor
 *
 * Return: None
 */
void ol_tx_desc_free(struct ol_txrx_pdev_t *pdev, struct ol_tx_desc_t *tx_desc)
{
	qdf_spin_lock_bh(&pdev->tx_mutex);

	ol_tx_desc_dup_detect_reset(pdev, tx_desc);

	if (tx_desc->pkt_type == OL_TX_FRM_TSO)
		ol_tx_tso_desc_free(pdev, tx_desc);

	ol_tx_desc_reset_pkt_type(tx_desc);
	ol_tx_desc_reset_timestamp(tx_desc);

	ol_tx_put_desc_global_pool(pdev, tx_desc);
	ol_tx_desc_vdev_rm(tx_desc);
	tx_desc->vdev_id = OL_TXRX_INVALID_VDEV_ID;

	qdf_spin_unlock_bh(&pdev->tx_mutex);
}

#else
/**
 * ol_tx_desc_free() - put descriptor to pool freelist
 * @pdev: pdev handle
 * @tx_desc: tx descriptor
 *
 * Return: None
 */
void ol_tx_desc_free(struct ol_txrx_pdev_t *pdev, struct ol_tx_desc_t *tx_desc)
{
	struct ol_tx_flow_pool_t *pool = tx_desc->pool;

	if (tx_desc->pkt_type == OL_TX_FRM_TSO)
		ol_tx_tso_desc_free(pdev, tx_desc);

	ol_tx_desc_reset_pkt_type(tx_desc);
	ol_tx_desc_reset_timestamp(tx_desc);

	qdf_spin_lock_bh(&pool->flow_pool_lock);
	ol_tx_desc_dup_detect_reset(pdev, tx_desc);
	ol_tx_put_desc_flow_pool(pool, tx_desc);
	switch (pool->status) {
	case FLOW_POOL_ACTIVE_PAUSED:
		if (pool->avail_desc > pool->start_th) {
			pdev->pause_cb(pool->member_flow_id,
				       WLAN_WAKE_ALL_NETIF_QUEUE,
				       WLAN_DATA_FLOW_CONTROL);
			pool->status = FLOW_POOL_ACTIVE_UNPAUSED;
		}
		break;
	case FLOW_POOL_INVALID:
		if (pool->avail_desc == pool->flow_pool_size) {
			qdf_spin_unlock_bh(&pool->flow_pool_lock);
			ol_tx_free_invalid_flow_pool(pool);
			qdf_print("%s %d pool is INVALID State!!\n",
				 __func__, __LINE__);
			return;
		}
		break;
	case FLOW_POOL_ACTIVE_UNPAUSED:
		break;
	default:
		qdf_print("%s %d pool is INACTIVE State!!\n",
				 __func__, __LINE__);
		break;
	};

	tx_desc->vdev_id = OL_TXRX_INVALID_VDEV_ID;

	qdf_spin_unlock_bh(&pool->flow_pool_lock);

}
#endif

const uint32_t htt_to_ce_pkt_type[] = {
	[htt_pkt_type_raw] = tx_pkt_type_raw,
	[htt_pkt_type_native_wifi] = tx_pkt_type_native_wifi,
	[htt_pkt_type_ethernet] = tx_pkt_type_802_3,
	[htt_pkt_type_mgmt] = tx_pkt_type_mgmt,
	[htt_pkt_type_eth2] = tx_pkt_type_eth2,
	[htt_pkt_num_types] = 0xffffffff
};

#define WISA_DEST_PORT_6MBPS	50000
#define WISA_DEST_PORT_24MBPS	50001

/**
 * ol_tx_get_wisa_ext_hdr_type() - get header type for WiSA mode
 * @netbuf: network buffer
 *
 * Return: extension header type
 */
static enum extension_header_type
ol_tx_get_wisa_ext_hdr_type(qdf_nbuf_t netbuf)
{
	uint8_t *buf = qdf_nbuf_data(netbuf);
	uint16_t dport;

	if (qdf_is_macaddr_group(
		(struct qdf_mac_addr *)(buf + QDF_NBUF_DEST_MAC_OFFSET))) {

		dport = (uint16_t)(*(uint16_t *)(buf +
			QDF_NBUF_TRAC_IPV4_OFFSET +
			QDF_NBUF_TRAC_IPV4_HEADER_SIZE + sizeof(uint16_t)));

		if (dport == QDF_SWAP_U16(WISA_DEST_PORT_6MBPS))
			return WISA_MODE_EXT_HEADER_6MBPS;
		else if (dport == QDF_SWAP_U16(WISA_DEST_PORT_24MBPS))
			return WISA_MODE_EXT_HEADER_24MBPS;
		else
			return EXT_HEADER_NOT_PRESENT;
	} else {
		return EXT_HEADER_NOT_PRESENT;
	}
}

/**
 * ol_tx_get_ext_header_type() - extension header is required or not
 * @vdev: vdev pointer
 * @netbuf: network buffer
 *
 * This function returns header type and if extension header is
 * not required than returns EXT_HEADER_NOT_PRESENT.
 *
 * Return: extension header type
 */
enum extension_header_type
ol_tx_get_ext_header_type(struct ol_txrx_vdev_t *vdev,
	qdf_nbuf_t netbuf)
{
	if (vdev->is_wisa_mode_enable == true)
		return ol_tx_get_wisa_ext_hdr_type(netbuf);
	else
		return EXT_HEADER_NOT_PRESENT;
}

struct ol_tx_desc_t *ol_tx_desc_ll(struct ol_txrx_pdev_t *pdev,
				   struct ol_txrx_vdev_t *vdev,
				   qdf_nbuf_t netbuf,
				   struct ol_txrx_msdu_info_t *msdu_info)
{
	struct ol_tx_desc_t *tx_desc;
	unsigned int i;
	uint32_t num_frags;
	enum extension_header_type type;

	msdu_info->htt.info.vdev_id = vdev->vdev_id;
	msdu_info->htt.action.cksum_offload = qdf_nbuf_get_tx_cksum(netbuf);
	switch (qdf_nbuf_get_exemption_type(netbuf)) {
	case QDF_NBUF_EXEMPT_NO_EXEMPTION:
	case QDF_NBUF_EXEMPT_ON_KEY_MAPPING_KEY_UNAVAILABLE:
		/* We want to encrypt this frame */
		msdu_info->htt.action.do_encrypt = 1;
		break;
	case QDF_NBUF_EXEMPT_ALWAYS:
		/* We don't want to encrypt this frame */
		msdu_info->htt.action.do_encrypt = 0;
		break;
	default:
		qdf_assert(0);
		break;
	}

	/* allocate the descriptor */
	tx_desc = ol_tx_desc_alloc_wrapper(pdev, vdev, msdu_info);
	if (!tx_desc)
		return NULL;

	/* initialize the SW tx descriptor */
	tx_desc->netbuf = netbuf;

	if (msdu_info->tso_info.is_tso) {
		tx_desc->tso_desc = msdu_info->tso_info.curr_seg;
		tx_desc->tso_num_desc = msdu_info->tso_info.tso_num_seg_list;
		tx_desc->pkt_type = OL_TX_FRM_TSO;
		TXRX_STATS_MSDU_INCR(pdev, tx.tso.tso_pkts, netbuf);
	} else {
		tx_desc->pkt_type = OL_TX_FRM_STD;
	}

	type = ol_tx_get_ext_header_type(vdev, netbuf);

	/* initialize the HW tx descriptor */
	if (qdf_unlikely(htt_tx_desc_init(pdev->htt_pdev, tx_desc->htt_tx_desc,
			 tx_desc->htt_tx_desc_paddr,
			 ol_tx_desc_id(pdev, tx_desc), netbuf, &msdu_info->htt,
			 &msdu_info->tso_info, NULL, type))) {
		/*
		 * HTT Tx descriptor initialization failed.
		 * therefore, free the tx desc
		 */
		ol_tx_desc_free(pdev, tx_desc);
		return NULL;
	}

	/*
	 * Initialize the fragmentation descriptor.
	 * Skip the prefix fragment (HTT tx descriptor) that was added
	 * during the call to htt_tx_desc_init above.
	 */
	num_frags = qdf_nbuf_get_num_frags(netbuf);
	/* num_frags are expected to be 2 max */
	num_frags = (num_frags > QDF_NBUF_CB_TX_MAX_EXTRA_FRAGS)
		? QDF_NBUF_CB_TX_MAX_EXTRA_FRAGS
		: num_frags;
#if defined(HELIUMPLUS)
	/*
	 * Use num_frags - 1, since 1 frag is used to store
	 * the HTT/HTC descriptor
	 * Refer to htt_tx_desc_init()
	 */
	htt_tx_desc_num_frags(pdev->htt_pdev, tx_desc->htt_frag_desc,
			      num_frags - 1);
#else /* ! defined(HELIUMPLUS) */
	htt_tx_desc_num_frags(pdev->htt_pdev, tx_desc->htt_tx_desc,
			      num_frags - 1);
#endif /* defined(HELIUMPLUS) */

	if (msdu_info->tso_info.is_tso) {
		htt_tx_desc_fill_tso_info(pdev->htt_pdev,
			 tx_desc->htt_frag_desc, &msdu_info->tso_info);
		TXRX_STATS_TSO_SEG_UPDATE(pdev,
			 msdu_info->tso_info.msdu_stats_idx,
			 msdu_info->tso_info.curr_seg->seg);
	} else {
		for (i = 1; i < num_frags; i++) {
			qdf_size_t frag_len;
			qdf_dma_addr_t frag_paddr;
#ifdef HELIUMPLUS_DEBUG
			void *frag_vaddr;

			frag_vaddr = qdf_nbuf_get_frag_vaddr(netbuf, i);
#endif
			frag_len = qdf_nbuf_get_frag_len(netbuf, i);
			frag_paddr = qdf_nbuf_get_frag_paddr(netbuf, i);
#if defined(HELIUMPLUS)
			htt_tx_desc_frag(pdev->htt_pdev, tx_desc->htt_frag_desc,
					 i - 1, frag_paddr, frag_len);
#if defined(HELIUMPLUS_DEBUG)
			qdf_print("%s:%d: htt_fdesc=%p frag=%d frag_vaddr=0x%p frag_paddr=0x%llx len=%zu\n",
				  __func__, __LINE__, tx_desc->htt_frag_desc,
				  i-1, frag_vaddr, frag_paddr, frag_len);
			ol_txrx_dump_pkt(netbuf, frag_paddr, 64);
#endif /* HELIUMPLUS_DEBUG */
#else /* ! defined(HELIUMPLUS) */
			htt_tx_desc_frag(pdev->htt_pdev, tx_desc->htt_tx_desc,
					 i - 1, frag_paddr, frag_len);
#endif /* defined(HELIUMPLUS) */
		}
	}

#if defined(HELIUMPLUS_DEBUG)
	ol_txrx_dump_frag_desc("ol_tx_desc_ll()", tx_desc);
#endif
	return tx_desc;
}

struct ol_tx_desc_t *
ol_tx_desc_hl(
	struct ol_txrx_pdev_t *pdev,
	struct ol_txrx_vdev_t *vdev,
	qdf_nbuf_t netbuf,
	struct ol_txrx_msdu_info_t *msdu_info)
{
	struct ol_tx_desc_t *tx_desc;

	/* FIX THIS: these inits should probably be done by tx classify */
	msdu_info->htt.info.vdev_id = vdev->vdev_id;
	msdu_info->htt.info.frame_type = pdev->htt_pkt_type;
	msdu_info->htt.action.cksum_offload = qdf_nbuf_get_tx_cksum(netbuf);
	switch (qdf_nbuf_get_exemption_type(netbuf)) {
	case QDF_NBUF_EXEMPT_NO_EXEMPTION:
	case QDF_NBUF_EXEMPT_ON_KEY_MAPPING_KEY_UNAVAILABLE:
		/* We want to encrypt this frame */
		msdu_info->htt.action.do_encrypt = 1;
		break;
	case QDF_NBUF_EXEMPT_ALWAYS:
		/* We don't want to encrypt this frame */
		msdu_info->htt.action.do_encrypt = 0;
		break;
	default:
		qdf_assert(0);
		break;
	}

	/* allocate the descriptor */
	tx_desc = ol_tx_desc_alloc_hl(pdev, vdev, msdu_info);
	if (!tx_desc)
		return NULL;

	/* initialize the SW tx descriptor */
	tx_desc->netbuf = netbuf;
	/* fix this - get pkt_type from msdu_info */
	tx_desc->pkt_type = OL_TX_FRM_STD;

#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
	tx_desc->orig_l2_hdr_bytes = 0;
#endif
	/* the HW tx descriptor will be initialized later by the caller */

	return tx_desc;
}

void ol_tx_desc_frame_list_free(struct ol_txrx_pdev_t *pdev,
				ol_tx_desc_list *tx_descs, int had_error)
{
	struct ol_tx_desc_t *tx_desc, *tmp;
	qdf_nbuf_t msdus = NULL;

	TAILQ_FOREACH_SAFE(tx_desc, tx_descs, tx_desc_list_elem, tmp) {
		qdf_nbuf_t msdu = tx_desc->netbuf;

		qdf_atomic_init(&tx_desc->ref_cnt);   /* clear the ref cnt */
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
		/* restore original hdr offset */
		OL_TX_RESTORE_HDR(tx_desc, msdu);
#endif
		if (qdf_nbuf_get_users(msdu) <= 1)
			qdf_nbuf_unmap(pdev->osdev, msdu, QDF_DMA_TO_DEVICE);

		/* free the tx desc */
		ol_tx_desc_free(pdev, tx_desc);
		/* link the netbuf into a list to free as a batch */
		qdf_nbuf_set_next(msdu, msdus);
		msdus = msdu;
	}
	/* free the netbufs as a batch */
	qdf_nbuf_tx_free(msdus, had_error);
}

void ol_tx_desc_frame_free_nonstd(struct ol_txrx_pdev_t *pdev,
				  struct ol_tx_desc_t *tx_desc, int had_error)
{
	int mgmt_type;
	ol_txrx_mgmt_tx_cb ota_ack_cb;

	qdf_atomic_init(&tx_desc->ref_cnt);     /* clear the ref cnt */
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
	/* restore original hdr offset */
	OL_TX_RESTORE_HDR(tx_desc, (tx_desc->netbuf));
#endif
	if (tx_desc->pkt_type == OL_TX_FRM_NO_FREE) {
		/* free the tx desc but don't unmap or free the frame */
		if (pdev->tx_data_callback.func) {
			qdf_nbuf_set_next(tx_desc->netbuf, NULL);
			pdev->tx_data_callback.func(pdev->tx_data_callback.ctxt,
						    tx_desc->netbuf, had_error);
			goto free_tx_desc;
		}
		/* let the code below unmap and free the frame */
	}
	qdf_nbuf_unmap(pdev->osdev, tx_desc->netbuf, QDF_DMA_TO_DEVICE);
	/* check the frame type to see what kind of special steps are needed */
	if ((tx_desc->pkt_type >= OL_TXRX_MGMT_TYPE_BASE) &&
		   (tx_desc->pkt_type != ol_tx_frm_freed)) {
		qdf_dma_addr_t frag_desc_paddr = 0;

#if defined(HELIUMPLUS)
		frag_desc_paddr = tx_desc->htt_frag_desc_paddr;
		/* FIX THIS -
		 * The FW currently has trouble using the host's fragments
		 * table for management frames.  Until this is fixed,
		 * rather than specifying the fragment table to the FW,
		 * the host SW will specify just the address of the initial
		 * fragment.
		 * Now that the mgmt frame is done, the HTT tx desc's frags
		 * table pointer needs to be reset.
		 */
#if defined(HELIUMPLUS_DEBUG)
		qdf_print("%s %d: Frag Descriptor Reset [%d] to 0x%x\n",
			  __func__, __LINE__, tx_desc->id,
			  frag_desc_paddr);
#endif /* HELIUMPLUS_DEBUG */
#endif /* HELIUMPLUS */
		htt_tx_desc_frags_table_set(pdev->htt_pdev,
					    tx_desc->htt_tx_desc, 0,
					    frag_desc_paddr, 1);

		mgmt_type = tx_desc->pkt_type - OL_TXRX_MGMT_TYPE_BASE;
		/*
		 *  we already checked the value when the mgmt frame was
		 *  provided to the txrx layer.
		 *  no need to check it a 2nd time.
		 */
		ota_ack_cb = pdev->tx_mgmt.callbacks[mgmt_type].ota_ack_cb;
		if (ota_ack_cb) {
			void *ctxt;

			ctxt = pdev->tx_mgmt.callbacks[mgmt_type].ctxt;
			ota_ack_cb(ctxt, tx_desc->netbuf, had_error);
		}
		/* free the netbuf */
		qdf_nbuf_free(tx_desc->netbuf);
	} else if (had_error == htt_tx_status_download_fail) {
		/* Failed to send to target */

		/* This is to decrement skb->users count for TSO segment */
		if (tx_desc->pkt_type == OL_TX_FRM_TSO)
			qdf_nbuf_tx_free(tx_desc->netbuf, had_error);
		goto free_tx_desc;
	} else {
		/* single regular frame, called from completion path */
		qdf_nbuf_set_next(tx_desc->netbuf, NULL);
		qdf_nbuf_tx_free(tx_desc->netbuf, had_error);
	}
free_tx_desc:
	/* free the tx desc */
	ol_tx_desc_free(pdev, tx_desc);
}

#if defined(FEATURE_TSO)
#ifdef TSOSEG_DEBUG
static int
ol_tso_seg_dbg_sanitize(struct qdf_tso_seg_elem_t *tsoseg)
{
	int rc = -1;
	struct ol_tx_desc_t *txdesc;

	if (tsoseg != NULL) {
		txdesc = tsoseg->dbg.txdesc;
		/* Don't validate if TX desc is NULL*/
		if (!txdesc)
			return 0;
		if (txdesc->tso_desc != tsoseg)
			qdf_tso_seg_dbg_bug("Owner sanity failed");
		else
			rc = 0;
	}
	return rc;

};
#else
static int
ol_tso_seg_dbg_sanitize(struct qdf_tso_seg_elem_t *tsoseg)
{
	return 0;
}
#endif /* TSOSEG_DEBUG */

/**
 * ol_tso_alloc_segment() - function to allocate a TSO segment
 * element
 * @pdev: the data physical device sending the data
 *
 * Allocates a TSO segment element from the free list held in
 * the pdev
 *
 * Return: tso_seg
 */
struct qdf_tso_seg_elem_t *ol_tso_alloc_segment(struct ol_txrx_pdev_t *pdev)
{
	struct qdf_tso_seg_elem_t *tso_seg = NULL;

	qdf_spin_lock_bh(&pdev->tso_seg_pool.tso_mutex);
	if (pdev->tso_seg_pool.freelist) {
		pdev->tso_seg_pool.num_free--;
		tso_seg = pdev->tso_seg_pool.freelist;
		if (tso_seg->on_freelist != 1) {
			qdf_print("Do not alloc tso seg as this seg is not in freelist\n");
			qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);
			QDF_BUG(0);
			return NULL;
		} else if (tso_seg->cookie != TSO_SEG_MAGIC_COOKIE) {
			qdf_print("Do not alloc tso seg as cookie is not good\n");
			qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);
			QDF_BUG(0);
			return NULL;
		}
		/*this tso seg is not a part of freelist now.*/
		tso_seg->on_freelist = 0;
		qdf_tso_seg_dbg_record(tso_seg, TSOSEG_LOC_ALLOC);
		pdev->tso_seg_pool.freelist = pdev->tso_seg_pool.freelist->next;
	}
	qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);

	return tso_seg;
}

/**
 * ol_tso_free_segment() - function to free a TSO segment
 * element
 * @pdev: the data physical device sending the data
 * @tso_seg: The TSO segment element to be freed
 *
 * Returns a TSO segment element to the free list held in the
 * pdev
 *
 * Return: none
 */
void ol_tso_free_segment(struct ol_txrx_pdev_t *pdev,
	 struct qdf_tso_seg_elem_t *tso_seg)
{
	qdf_spin_lock_bh(&pdev->tso_seg_pool.tso_mutex);
	if (tso_seg->on_freelist != 0) {
		qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);
		qdf_tso_seg_dbg_bug("Do not free tso seg, already freed");
		return;
	} else if (tso_seg->cookie != TSO_SEG_MAGIC_COOKIE) {
		qdf_print("Do not free the tso seg as cookie is not good. Looks like memory corruption");
		qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);
		QDF_BUG(0);
		return;
	}
	/* sanitize before free */
	ol_tso_seg_dbg_sanitize(tso_seg);
	qdf_tso_seg_dbg_setowner(tso_seg, NULL);
	/*this tso seg is now a part of freelist*/
	/* retain segment history, if debug is enabled */
	qdf_tso_seg_dbg_zero(tso_seg);
	tso_seg->next = pdev->tso_seg_pool.freelist;
	tso_seg->on_freelist = 1;
	tso_seg->cookie = TSO_SEG_MAGIC_COOKIE;
	qdf_tso_seg_dbg_record(tso_seg, TSOSEG_LOC_FREE);
	pdev->tso_seg_pool.freelist = tso_seg;
	pdev->tso_seg_pool.num_free++;
	qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);
}

/**
 * ol_tso_num_seg_alloc() - function to allocate a element to count TSO segments
 *			    in a jumbo skb packet.
 * @pdev: the data physical device sending the data
 *
 * Allocates a element to count TSO segments from the free list held in
 * the pdev
 *
 * Return: tso_num_seg
 */
struct qdf_tso_num_seg_elem_t *ol_tso_num_seg_alloc(struct ol_txrx_pdev_t *pdev)
{
	struct qdf_tso_num_seg_elem_t *tso_num_seg = NULL;

	qdf_spin_lock_bh(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
	if (pdev->tso_num_seg_pool.freelist) {
		pdev->tso_num_seg_pool.num_free--;
		tso_num_seg = pdev->tso_num_seg_pool.freelist;
		pdev->tso_num_seg_pool.freelist =
				pdev->tso_num_seg_pool.freelist->next;
	}
	qdf_spin_unlock_bh(&pdev->tso_num_seg_pool.tso_num_seg_mutex);

	return tso_num_seg;
}

/**
 * ol_tso_num_seg_free() - function to free a TSO segment
 * element
 * @pdev: the data physical device sending the data
 * @tso_seg: The TSO segment element to be freed
 *
 * Returns a element to the free list held in the pdev
 *
 * Return: none
 */
void ol_tso_num_seg_free(struct ol_txrx_pdev_t *pdev,
	 struct qdf_tso_num_seg_elem_t *tso_num_seg)
{
	qdf_spin_lock_bh(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
	tso_num_seg->next = pdev->tso_num_seg_pool.freelist;
	pdev->tso_num_seg_pool.freelist = tso_num_seg;
		pdev->tso_num_seg_pool.num_free++;
	qdf_spin_unlock_bh(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
}
#endif
