/*
 * Copyright (c) 2011-2019 The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
 *
 * Permission to use, copy, modify, and/or distribute this software for
 * any purpose with or without fee is hereby granted, provided that the
 * above copyright notice and this permission notice appear in all
 * copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#include <qdf_atomic.h>         /* qdf_atomic_inc, etc. */
#include <qdf_lock.h>           /* qdf_os_spinlock */
#include <qdf_time.h>           /* qdf_system_ticks, etc. */
#include <qdf_nbuf.h>           /* qdf_nbuf_t */
#include <qdf_net_types.h>      /* QDF_NBUF_TX_EXT_TID_INVALID */

#include "queue.h"          /* TAILQ */
#ifdef QCA_COMPUTE_TX_DELAY
#include <enet.h>               /* ethernet_hdr_t, etc. */
#include <ipv6_defs.h>          /* ipv6_traffic_class */
#endif

#include <ol_txrx_api.h>        /* ol_txrx_vdev_handle, etc. */
#include <ol_htt_tx_api.h>      /* htt_tx_compl_desc_id */
#include <ol_txrx_htt_api.h>    /* htt_tx_status */

#include <ol_ctrl_txrx_api.h>
#include <cdp_txrx_tx_delay.h>
#include <ol_txrx_types.h>      /* ol_txrx_vdev_t, etc */
#include <ol_tx_desc.h>         /* ol_tx_desc_find, ol_tx_desc_frame_free */
#ifdef QCA_COMPUTE_TX_DELAY
#include <ol_tx_classify.h>     /* ol_tx_dest_addr_find */
#endif
#include <ol_txrx_internal.h>   /* OL_TX_DESC_NO_REFS, etc. */
#include <ol_osif_txrx_api.h>
#include <ol_tx.h>              /* ol_tx_reinject */
#include <ol_tx_send.h>

#include <ol_cfg.h>             /* ol_cfg_is_high_latency */
#include <ol_tx_sched.h>
#ifdef QCA_SUPPORT_SW_TXRX_ENCAP
#include <ol_txrx_encap.h>      /* OL_TX_RESTORE_HDR, etc */
#endif
#include <ol_tx_queue.h>
#include <ol_txrx.h>
#include <pktlog_ac_fmt.h>
#include <cdp_txrx_handle.h>

void ol_tx_init_pdev(ol_txrx_pdev_handle pdev)
{
	qdf_atomic_add(ol_cfg_target_tx_credit(pdev->ctrl_pdev),
		       &pdev->target_tx_credit);
}

qdf_nbuf_t ol_tx_reinject(struct ol_txrx_vdev_t *vdev,
			  qdf_nbuf_t msdu, uint16_t peer_id)
{
	struct ol_tx_desc_t *tx_desc = NULL;
	struct ol_txrx_msdu_info_t msdu_info;

	msdu_info.htt.info.l2_hdr_type = vdev->pdev->htt_pkt_type;
	msdu_info.htt.info.ext_tid = HTT_TX_EXT_TID_INVALID;
	msdu_info.peer = NULL;
	msdu_info.htt.action.tx_comp_req = 0;
	msdu_info.tso_info.is_tso = 0;

	tx_desc = ol_tx_prepare_ll(vdev, msdu, &msdu_info);
	if (!tx_desc)
		return msdu;

	HTT_TX_DESC_POSTPONED_SET(*((uint32_t *)(tx_desc->htt_tx_desc)), true);

	htt_tx_desc_set_peer_id(tx_desc->htt_tx_desc, peer_id);

	ol_tx_send(vdev->pdev, tx_desc, msdu, vdev->vdev_id);

	return NULL;
}

/*
 * The TXRX module doesn't accept tx frames unless the target has
 * enough descriptors for them.
 * For LL, the TXRX descriptor pool is sized to match the target's
 * descriptor pool.  Hence, if the descriptor allocation in TXRX
 * succeeds, that guarantees that the target has room to accept
 * the new tx frame.
 */
struct ol_tx_desc_t *
ol_tx_prepare_ll(ol_txrx_vdev_handle vdev,
		 qdf_nbuf_t msdu,
		 struct ol_txrx_msdu_info_t *msdu_info)
{
	struct ol_tx_desc_t *tx_desc;
	struct ol_txrx_pdev_t *pdev = vdev->pdev;

	(msdu_info)->htt.info.frame_type = pdev->htt_pkt_type;
	tx_desc = ol_tx_desc_ll(pdev, vdev, msdu, msdu_info);
	if (qdf_unlikely(!tx_desc)) {
		/*
		 * If TSO packet, free associated
		 * remaining TSO segment descriptors
		 */
		if (qdf_nbuf_is_tso(msdu))
			ol_free_remaining_tso_segs(
					vdev, msdu_info, true);
		TXRX_STATS_MSDU_LIST_INCR(
				pdev, tx.dropped.host_reject, msdu);
		return NULL;
	}

	return tx_desc;
}

qdf_nbuf_t
ol_tx_non_std_ll(struct ol_txrx_vdev_t *vdev,
		 enum ol_tx_spec tx_spec,
		 qdf_nbuf_t msdu_list)
{
	qdf_nbuf_t msdu = msdu_list;
	htt_pdev_handle htt_pdev = vdev->pdev->htt_pdev;
	struct ol_txrx_msdu_info_t msdu_info;

	msdu_info.htt.info.l2_hdr_type = vdev->pdev->htt_pkt_type;
	msdu_info.htt.action.tx_comp_req = 0;

	/*
	 * The msdu_list variable could be used instead of the msdu var,
	 * but just to clarify which operations are done on a single MSDU
	 * vs. a list of MSDUs, use a distinct variable for single MSDUs
	 * within the list.
	 */
	while (msdu) {
		qdf_nbuf_t next;
		struct ol_tx_desc_t *tx_desc = NULL;

		msdu_info.htt.info.ext_tid = qdf_nbuf_get_tid(msdu);
		msdu_info.peer = NULL;
		msdu_info.tso_info.is_tso = 0;

		tx_desc = ol_tx_prepare_ll(vdev, msdu, &msdu_info);
		if (!tx_desc)
			return msdu;

		/*
		 * The netbuf may get linked into a different list inside the
		 * ol_tx_send function, so store the next pointer before the
		 * tx_send call.
		 */
		next = qdf_nbuf_next(msdu);

		if (tx_spec != OL_TX_SPEC_STD) {
			if (tx_spec & OL_TX_SPEC_NO_FREE) {
				tx_desc->pkt_type = OL_TX_FRM_NO_FREE;
			} else if (tx_spec & OL_TX_SPEC_TSO) {
				tx_desc->pkt_type = OL_TX_FRM_TSO;
			} else if (tx_spec & OL_TX_SPEC_NWIFI_NO_ENCRYPT) {
				uint8_t sub_type =
					ol_txrx_tx_raw_subtype(tx_spec);
				htt_tx_desc_type(htt_pdev, tx_desc->htt_tx_desc,
						 htt_pkt_type_native_wifi,
						 sub_type);
			} else if (ol_txrx_tx_is_raw(tx_spec)) {
				/* different types of raw frames */
				uint8_t sub_type =
					ol_txrx_tx_raw_subtype(tx_spec);
				htt_tx_desc_type(htt_pdev, tx_desc->htt_tx_desc,
						 htt_pkt_type_raw, sub_type);
			}
		}
		/*
		 * If debug display is enabled, show the meta-data being
		 * downloaded to the target via the HTT tx descriptor.
		 */
		htt_tx_desc_display(tx_desc->htt_tx_desc);
		ol_tx_send(vdev->pdev, tx_desc, msdu, vdev->vdev_id);
		msdu = next;
	}
	return NULL;            /* all MSDUs were accepted */
}

void ol_tx_trace_pkt(qdf_nbuf_t skb, uint16_t msdu_id, uint8_t vdev_id)
{
	DPTRACE(qdf_dp_trace_ptr(skb,
				 QDF_DP_TRACE_TXRX_FAST_PACKET_PTR_RECORD,
				 QDF_TRACE_DEFAULT_PDEV_ID,
				 qdf_nbuf_data_addr(skb),
				 sizeof(qdf_nbuf_data(skb)),
				 msdu_id, vdev_id, 0));

	qdf_dp_trace_log_pkt(vdev_id, skb, QDF_TX, QDF_TRACE_DEFAULT_PDEV_ID);

	DPTRACE(qdf_dp_trace_data_pkt(skb, QDF_TRACE_DEFAULT_PDEV_ID,
				      QDF_DP_TRACE_TX_PACKET_RECORD,
				      msdu_id, QDF_TX));
}

#if defined(HELIUMPLUS)
void ol_txrx_dump_frag_desc(char *msg, struct ol_tx_desc_t *tx_desc)
{
	uint32_t                *frag_ptr_i_p;
	int                     i;

	ol_txrx_err("OL TX Descriptor 0x%pK msdu_id %d",
		    tx_desc, tx_desc->id);
	ol_txrx_err("HTT TX Descriptor vaddr: 0x%pK paddr: %pad",
		    tx_desc->htt_tx_desc, &tx_desc->htt_tx_desc_paddr);
	ol_txrx_err("Fragment Descriptor 0x%pK (paddr=%pad)",
		    tx_desc->htt_frag_desc, &tx_desc->htt_frag_desc_paddr);

	/*
	 * it looks from htt_tx_desc_frag() that tx_desc->htt_frag_desc
	 * is already de-referrable (=> in virtual address space)
	 */
	frag_ptr_i_p = tx_desc->htt_frag_desc;

	/* Dump 6 words of TSO flags */
	print_hex_dump(KERN_DEBUG, "MLE Desc:TSO Flags:  ",
		       DUMP_PREFIX_NONE, 8, 4,
		       frag_ptr_i_p, 24, true);

	frag_ptr_i_p += 6; /* Skip 6 words of TSO flags */

	i = 0;
	while (*frag_ptr_i_p) {
		print_hex_dump(KERN_DEBUG, "MLE Desc:Frag Ptr:  ",
			       DUMP_PREFIX_NONE, 8, 4,
			       frag_ptr_i_p, 8, true);
		i++;
		if (i > 5) /* max 6 times: frag_ptr0 to frag_ptr5 */
			break;
		/* jump to next  pointer - skip length */
		frag_ptr_i_p += 2;
	}
}
#endif /* HELIUMPLUS */

struct ol_tx_desc_t *
ol_txrx_mgmt_tx_desc_alloc(
	struct ol_txrx_pdev_t *pdev,
	struct ol_txrx_vdev_t *vdev,
	qdf_nbuf_t tx_mgmt_frm,
	struct ol_txrx_msdu_info_t *tx_msdu_info)
{
	struct ol_tx_desc_t *tx_desc;

	/* For LL tx_comp_req is not used so initialized to 0 */
	tx_msdu_info->htt.action.tx_comp_req = 0;
	tx_desc = ol_tx_desc_ll(pdev, vdev, tx_mgmt_frm, tx_msdu_info);
	/* 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, specify just the
	 * address of the initial fragment.
	 */
#if defined(HELIUMPLUS)
	/* ol_txrx_dump_frag_desc("ol_txrx_mgmt_send(): after ol_tx_desc_ll",
	 *			  tx_desc);
	 */
#endif /* defined(HELIUMPLUS) */
	if (tx_desc) {
		/*
		 * Following the call to ol_tx_desc_ll, frag 0 is the
		 * HTT tx HW descriptor, and the frame payload is in
		 * frag 1.
		 */
		htt_tx_desc_frags_table_set(
				pdev->htt_pdev,
				tx_desc->htt_tx_desc,
				qdf_nbuf_get_frag_paddr(tx_mgmt_frm, 1),
				0, 0);
#if defined(HELIUMPLUS) && defined(HELIUMPLUS_DEBUG)
		ol_txrx_dump_frag_desc(
				"after htt_tx_desc_frags_table_set",
				tx_desc);
#endif /* defined(HELIUMPLUS) */
	}

	return tx_desc;
}

int ol_txrx_mgmt_send_frame(
	struct ol_txrx_vdev_t *vdev,
	struct ol_tx_desc_t *tx_desc,
	qdf_nbuf_t tx_mgmt_frm,
	struct ol_txrx_msdu_info_t *tx_msdu_info,
	uint16_t chanfreq)
{
	struct ol_txrx_pdev_t *pdev = vdev->pdev;

	htt_tx_desc_set_chanfreq(tx_desc->htt_tx_desc, chanfreq);
	QDF_NBUF_CB_TX_PACKET_TRACK(tx_desc->netbuf) =
					QDF_NBUF_TX_PKT_MGMT_TRACK;
	ol_tx_send_nonstd(pdev, tx_desc, tx_mgmt_frm,
			  htt_pkt_type_mgmt);

	return 0;
}

#if defined(FEATURE_TSO)
void ol_free_remaining_tso_segs(ol_txrx_vdev_handle vdev,
				struct ol_txrx_msdu_info_t *msdu_info,
				bool is_tso_seg_mapping_done)
{
	struct qdf_tso_seg_elem_t *next_seg;
	struct qdf_tso_seg_elem_t *free_seg = msdu_info->tso_info.curr_seg;
	struct ol_txrx_pdev_t *pdev;
	bool is_last_seg = false;

	if (qdf_unlikely(!vdev)) {
		ol_txrx_err("vdev is null");
		return;
	}

	pdev = vdev->pdev;
	if (qdf_unlikely(!pdev)) {
		ol_txrx_err("pdev is null");
		return;
	}

	/*
	 * TSO segment are mapped already, therefore,
	 * 1. unmap the tso segments,
	 * 2. free tso num segment if it is a last segment, and
	 * 3. free the tso segments.
	 */

	if (is_tso_seg_mapping_done) {
		struct qdf_tso_num_seg_elem_t *tso_num_desc =
				msdu_info->tso_info.tso_num_seg_list;

		if (qdf_unlikely(!tso_num_desc)) {
			ol_txrx_err("TSO common info is NULL!");
			return;
		}

		while (free_seg) {
			qdf_spin_lock_bh(&pdev->tso_seg_pool.tso_mutex);
			tso_num_desc->num_seg.tso_cmn_num_seg--;

			is_last_seg = (tso_num_desc->num_seg.tso_cmn_num_seg ==
				       0) ? true : false;
			qdf_nbuf_unmap_tso_segment(pdev->osdev, free_seg,
						   is_last_seg);
			qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);

			if (is_last_seg) {
				ol_tso_num_seg_free(pdev,
						    msdu_info->tso_info.
						    tso_num_seg_list);
				msdu_info->tso_info.tso_num_seg_list = NULL;
			}

			next_seg = free_seg->next;
			free_seg->force_free = 1;
			ol_tso_free_segment(pdev, free_seg);
			free_seg = next_seg;
		}
	} else {
		/*
		 * TSO segment are not mapped therefore,
		 * free the tso segments only.
		 */
		while (free_seg) {
			next_seg = free_seg->next;
			free_seg->force_free = 1;
			ol_tso_free_segment(pdev, free_seg);
			free_seg = next_seg;
		}
	}
}

/**
 * ol_tx_prepare_tso() - Given a jumbo msdu, prepare the TSO
 * related information in the msdu_info meta data
 * @vdev: virtual device handle
 * @msdu: network buffer
 * @msdu_info: meta data associated with the msdu
 *
 * Return: 0 - success, >0 - error
 */
uint8_t ol_tx_prepare_tso(ol_txrx_vdev_handle vdev,
			  qdf_nbuf_t msdu,
			  struct ol_txrx_msdu_info_t *msdu_info)
{
	msdu_info->tso_info.curr_seg = NULL;
	if (qdf_nbuf_is_tso(msdu)) {
		int num_seg = qdf_nbuf_get_tso_num_seg(msdu);
		struct qdf_tso_num_seg_elem_t *tso_num_seg;

		msdu_info->tso_info.tso_num_seg_list = NULL;
		msdu_info->tso_info.tso_seg_list = NULL;
		msdu_info->tso_info.num_segs = num_seg;
		while (num_seg) {
			struct qdf_tso_seg_elem_t *tso_seg =
				ol_tso_alloc_segment(vdev->pdev);
			if (tso_seg) {
				qdf_tso_seg_dbg_record(tso_seg,
						       TSOSEG_LOC_PREPARETSO);
				tso_seg->next =
					msdu_info->tso_info.tso_seg_list;
				msdu_info->tso_info.tso_seg_list
					= tso_seg;
				num_seg--;
			} else {
				/* Free above allocated TSO segments till now */
				msdu_info->tso_info.curr_seg =
					msdu_info->tso_info.tso_seg_list;
				ol_free_remaining_tso_segs(vdev, msdu_info,
							   false);
				return 1;
			}
		}
		tso_num_seg = ol_tso_num_seg_alloc(vdev->pdev);
		if (tso_num_seg) {
			tso_num_seg->next = msdu_info->tso_info.
						tso_num_seg_list;
			msdu_info->tso_info.tso_num_seg_list = tso_num_seg;
		} else {
			/* Free the already allocated num of segments */
			msdu_info->tso_info.curr_seg =
				msdu_info->tso_info.tso_seg_list;
			ol_free_remaining_tso_segs(vdev, msdu_info, false);
			return 1;
		}

		if (qdf_unlikely(!qdf_nbuf_get_tso_info(vdev->pdev->osdev,
						msdu, &msdu_info->tso_info))) {
			/* Free the already allocated num of segments */
			msdu_info->tso_info.curr_seg =
				msdu_info->tso_info.tso_seg_list;
			ol_free_remaining_tso_segs(vdev, msdu_info, false);
			return 1;
		}

		msdu_info->tso_info.curr_seg =
			msdu_info->tso_info.tso_seg_list;
		num_seg = msdu_info->tso_info.num_segs;
	} else {
		msdu_info->tso_info.is_tso = 0;
		msdu_info->tso_info.num_segs = 1;
	}
	return 0;
}

/**
 * ol_tx_tso_update_stats() - update TSO stats
 * @pdev: pointer to ol_txrx_pdev_t structure
 * @msdu_info: tso msdu_info for the msdu
 * @msdu: tso mdsu for which stats are updated
 * @tso_msdu_idx: stats index in the global TSO stats array where stats will be
 *                updated
 *
 * Return: None
 */
void ol_tx_tso_update_stats(struct ol_txrx_pdev_t *pdev,
			    struct qdf_tso_info_t  *tso_info, qdf_nbuf_t msdu,
			    uint32_t tso_msdu_idx)
{
	TXRX_STATS_TSO_HISTOGRAM(pdev, tso_info->num_segs);
	TXRX_STATS_TSO_GSO_SIZE_UPDATE(pdev, tso_msdu_idx,
				       qdf_nbuf_tcp_tso_size(msdu));
	TXRX_STATS_TSO_TOTAL_LEN_UPDATE(pdev,
					tso_msdu_idx, qdf_nbuf_len(msdu));
	TXRX_STATS_TSO_NUM_FRAGS_UPDATE(pdev, tso_msdu_idx,
					qdf_nbuf_get_nr_frags(msdu));
}

/**
 * ol_tx_tso_get_stats_idx() - retrieve global TSO stats index and increment it
 * @pdev: pointer to ol_txrx_pdev_t structure
 *
 * Retrieve  the current value of the global variable and increment it. This is
 * done in a spinlock as the global TSO stats may be accessed in parallel by
 * multiple TX streams.
 *
 * Return: The current value of TSO stats index.
 */
uint32_t ol_tx_tso_get_stats_idx(struct ol_txrx_pdev_t *pdev)
{
	uint32_t msdu_stats_idx = 0;

	qdf_spin_lock_bh(&pdev->stats.pub.tx.tso.tso_stats_lock);
	msdu_stats_idx = pdev->stats.pub.tx.tso.tso_info.tso_msdu_idx;
	pdev->stats.pub.tx.tso.tso_info.tso_msdu_idx++;
	pdev->stats.pub.tx.tso.tso_info.tso_msdu_idx &=
					NUM_MAX_TSO_MSDUS_MASK;
	qdf_spin_unlock_bh(&pdev->stats.pub.tx.tso.tso_stats_lock);

	TXRX_STATS_TSO_RESET_MSDU(pdev, msdu_stats_idx);

	return msdu_stats_idx;
}

/**
 * ol_tso_seg_list_init() - function to initialise the tso seg freelist
 * @pdev: the data physical device sending the data
 * @num_seg: number of segments needs to be initialized
 *
 * Return: none
 */
void ol_tso_seg_list_init(struct ol_txrx_pdev_t *pdev, uint32_t num_seg)
{
	int i = 0;
	struct qdf_tso_seg_elem_t *c_element;

	/* Host should not allocate any c_element. */
	if (num_seg <= 0) {
		ol_txrx_err("Pool size passed is 0");
		QDF_BUG(0);
		pdev->tso_seg_pool.pool_size = i;
		qdf_spinlock_create(&pdev->tso_seg_pool.tso_mutex);
		return;
	}

	c_element = qdf_mem_malloc(sizeof(struct qdf_tso_seg_elem_t));
	pdev->tso_seg_pool.freelist = c_element;
	for (i = 0; i < (num_seg - 1); i++) {
		if (qdf_unlikely(!c_element)) {
			ol_txrx_err("c_element NULL for seg %d", i);
			QDF_BUG(0);
			pdev->tso_seg_pool.pool_size = i;
			pdev->tso_seg_pool.num_free = i;
			qdf_spinlock_create(&pdev->tso_seg_pool.tso_mutex);
			return;
		}
		/* set the freelist bit and magic cookie*/
		c_element->on_freelist = 1;
		c_element->cookie = TSO_SEG_MAGIC_COOKIE;
#ifdef TSOSEG_DEBUG
		c_element->dbg.txdesc = NULL;
		qdf_atomic_init(&c_element->dbg.cur); /* history empty */
		qdf_tso_seg_dbg_record(c_element, TSOSEG_LOC_INIT1);
#endif /* TSOSEG_DEBUG */
		c_element->next =
			qdf_mem_malloc(sizeof(struct qdf_tso_seg_elem_t));
		c_element = c_element->next;
	}
	/*
	 * NULL check for the last c_element of the list or
	 * first c_element if num_seg is equal to 1.
	 */
	if (qdf_unlikely(!c_element)) {
		ol_txrx_err("c_element NULL for seg %d", i);
		QDF_BUG(0);
		pdev->tso_seg_pool.pool_size = i;
		pdev->tso_seg_pool.num_free = i;
		qdf_spinlock_create(&pdev->tso_seg_pool.tso_mutex);
		return;
	}
	c_element->on_freelist = 1;
	c_element->cookie = TSO_SEG_MAGIC_COOKIE;
#ifdef TSOSEG_DEBUG
	qdf_tso_seg_dbg_init(c_element);
	qdf_tso_seg_dbg_record(c_element, TSOSEG_LOC_INIT2);
#endif /* TSOSEG_DEBUG */
	c_element->next = NULL;
	pdev->tso_seg_pool.pool_size = num_seg;
	pdev->tso_seg_pool.num_free = num_seg;
	qdf_spinlock_create(&pdev->tso_seg_pool.tso_mutex);
}

/**
 * ol_tso_seg_list_deinit() - function to de-initialise the tso seg freelist
 * @pdev: the data physical device sending the data
 *
 * Return: none
 */
void ol_tso_seg_list_deinit(struct ol_txrx_pdev_t *pdev)
{
	int i;
	struct qdf_tso_seg_elem_t *c_element;
	struct qdf_tso_seg_elem_t *temp;

	/* pool size 0 implies that tso seg list is not initialised*/
	if (!pdev->tso_seg_pool.freelist &&
	    pdev->tso_seg_pool.pool_size == 0)
		return;

	qdf_spin_lock_bh(&pdev->tso_seg_pool.tso_mutex);
	c_element = pdev->tso_seg_pool.freelist;
	i = pdev->tso_seg_pool.pool_size;

	pdev->tso_seg_pool.freelist = NULL;
	pdev->tso_seg_pool.num_free = 0;
	pdev->tso_seg_pool.pool_size = 0;

	qdf_spin_unlock_bh(&pdev->tso_seg_pool.tso_mutex);
	qdf_spinlock_destroy(&pdev->tso_seg_pool.tso_mutex);

	while (i-- > 0 && c_element) {
		temp = c_element->next;
		if (c_element->on_freelist != 1) {
			qdf_tso_seg_dbg_bug("seg already freed (double?)");
			return;
		} else if (c_element->cookie != TSO_SEG_MAGIC_COOKIE) {
			qdf_tso_seg_dbg_bug("seg cookie is bad (corruption?)");
			return;
		}
		/* free this seg, so reset the cookie value*/
		c_element->cookie = 0;
		qdf_mem_free(c_element);
		c_element = temp;
	}
}

/**
 * ol_tso_num_seg_list_init() - function to initialise the freelist of elements
 *				use to count the num of tso segments in jumbo
 *				skb packet freelist
 * @pdev: the data physical device sending the data
 * @num_seg: number of elements needs to be initialized
 *
 * Return: none
 */
void ol_tso_num_seg_list_init(struct ol_txrx_pdev_t *pdev, uint32_t num_seg)
{
	int i = 0;
	struct qdf_tso_num_seg_elem_t *c_element;

	/* Host should not allocate any c_element. */
	if (num_seg <= 0) {
		ol_txrx_err("Pool size passed is 0");
		QDF_BUG(0);
		pdev->tso_num_seg_pool.num_seg_pool_size = i;
		qdf_spinlock_create(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
		return;
	}

	c_element = qdf_mem_malloc(sizeof(struct qdf_tso_num_seg_elem_t));
	pdev->tso_num_seg_pool.freelist = c_element;
	for (i = 0; i < (num_seg - 1); i++) {
		if (qdf_unlikely(!c_element)) {
			ol_txrx_err("c_element NULL for num of seg %d", i);
			QDF_BUG(0);
			pdev->tso_num_seg_pool.num_seg_pool_size = i;
			pdev->tso_num_seg_pool.num_free = i;
			qdf_spinlock_create(&pdev->tso_num_seg_pool.
							tso_num_seg_mutex);
			return;
		}
		c_element->next =
			qdf_mem_malloc(sizeof(struct qdf_tso_num_seg_elem_t));
		c_element = c_element->next;
	}
	/*
	 * NULL check for the last c_element of the list or
	 * first c_element if num_seg is equal to 1.
	 */
	if (qdf_unlikely(!c_element)) {
		ol_txrx_err("c_element NULL for num of seg %d", i);
		QDF_BUG(0);
		pdev->tso_num_seg_pool.num_seg_pool_size = i;
		pdev->tso_num_seg_pool.num_free = i;
		qdf_spinlock_create(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
		return;
	}
	c_element->next = NULL;
	pdev->tso_num_seg_pool.num_seg_pool_size = num_seg;
	pdev->tso_num_seg_pool.num_free = num_seg;
	qdf_spinlock_create(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
}

/**
 * ol_tso_num_seg_list_deinit() - function to de-initialise the freelist of
 *				  elements use to count the num of tso segment
 *				  in a jumbo skb packet freelist
 * @pdev: the data physical device sending the data
 *
 * Return: none
 */
void ol_tso_num_seg_list_deinit(struct ol_txrx_pdev_t *pdev)
{
	int i;
	struct qdf_tso_num_seg_elem_t *c_element;
	struct qdf_tso_num_seg_elem_t *temp;

	/* pool size 0 implies that tso num seg list is not initialised*/
	if (!pdev->tso_num_seg_pool.freelist &&
	    pdev->tso_num_seg_pool.num_seg_pool_size == 0)
		return;

	qdf_spin_lock_bh(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
	c_element = pdev->tso_num_seg_pool.freelist;
	i = pdev->tso_num_seg_pool.num_seg_pool_size;

	pdev->tso_num_seg_pool.freelist = NULL;
	pdev->tso_num_seg_pool.num_free = 0;
	pdev->tso_num_seg_pool.num_seg_pool_size = 0;

	qdf_spin_unlock_bh(&pdev->tso_num_seg_pool.tso_num_seg_mutex);
	qdf_spinlock_destroy(&pdev->tso_num_seg_pool.tso_num_seg_mutex);

	while (i-- > 0 && c_element) {
		temp = c_element->next;
		qdf_mem_free(c_element);
		c_element = temp;
	}
}
#endif /* FEATURE_TSO */

#if defined(FEATURE_TSO) && defined(FEATURE_TSO_DEBUG)
void ol_txrx_tso_stats_init(ol_txrx_pdev_handle pdev)
{
	qdf_spinlock_create(&pdev->stats.pub.tx.tso.tso_stats_lock);
}

void ol_txrx_tso_stats_deinit(ol_txrx_pdev_handle pdev)
{
	qdf_spinlock_destroy(&pdev->stats.pub.tx.tso.tso_stats_lock);
}

void ol_txrx_stats_display_tso(ol_txrx_pdev_handle pdev)
{
	int msdu_idx;
	int seg_idx;

	txrx_nofl_info("TSO Statistics:");
	txrx_nofl_info("TSO pkts %lld, bytes %lld",
		       pdev->stats.pub.tx.tso.tso_pkts.pkts,
		       pdev->stats.pub.tx.tso.tso_pkts.bytes);

	txrx_nofl_info("TSO Histogram for numbers of segments:\n"
		       "Single segment	%d\n"
		       "  2-5 segments	%d\n"
		       " 6-10 segments	%d\n"
		       "11-15 segments	%d\n"
		       "16-20 segments	%d\n"
		       "  20+ segments	%d\n",
		       pdev->stats.pub.tx.tso.tso_hist.pkts_1,
		       pdev->stats.pub.tx.tso.tso_hist.pkts_2_5,
		       pdev->stats.pub.tx.tso.tso_hist.pkts_6_10,
		       pdev->stats.pub.tx.tso.tso_hist.pkts_11_15,
		       pdev->stats.pub.tx.tso.tso_hist.pkts_16_20,
		       pdev->stats.pub.tx.tso.tso_hist.pkts_20_plus);

	txrx_nofl_info("TSO History Buffer: Total size %d, current_index %d",
		       NUM_MAX_TSO_MSDUS,
		       TXRX_STATS_TSO_MSDU_IDX(pdev));

	for (msdu_idx = 0; msdu_idx < NUM_MAX_TSO_MSDUS; msdu_idx++) {
		if (TXRX_STATS_TSO_MSDU_TOTAL_LEN(pdev, msdu_idx) == 0)
			continue;
		txrx_nofl_info("jumbo pkt idx: %d num segs %d gso_len %d total_len %d nr_frags %d",
			       msdu_idx,
			       TXRX_STATS_TSO_MSDU_NUM_SEG(pdev, msdu_idx),
			       TXRX_STATS_TSO_MSDU_GSO_SIZE(pdev, msdu_idx),
			       TXRX_STATS_TSO_MSDU_TOTAL_LEN(pdev, msdu_idx),
			       TXRX_STATS_TSO_MSDU_NR_FRAGS(pdev, msdu_idx));

		for (seg_idx = 0;
			 ((seg_idx < TXRX_STATS_TSO_MSDU_NUM_SEG(pdev,
			   msdu_idx)) && (seg_idx < NUM_MAX_TSO_SEGS));
			 seg_idx++) {
			struct qdf_tso_seg_t tso_seg =
				 TXRX_STATS_TSO_SEG(pdev, msdu_idx, seg_idx);

			txrx_nofl_info("seg idx: %d", seg_idx);
			txrx_nofl_info("tso_enable: %d",
				       tso_seg.tso_flags.tso_enable);
			txrx_nofl_info("fin %d syn %d rst %d psh %d ack %d urg %d ece %d cwr %d ns %d",
				       tso_seg.tso_flags.fin,
				       tso_seg.tso_flags.syn,
				       tso_seg.tso_flags.rst,
				       tso_seg.tso_flags.psh,
				       tso_seg.tso_flags.ack,
				       tso_seg.tso_flags.urg,
				       tso_seg.tso_flags.ece,
				       tso_seg.tso_flags.cwr,
				       tso_seg.tso_flags.ns);
			txrx_nofl_info("tcp_seq_num: 0x%x ip_id: %d",
				       tso_seg.tso_flags.tcp_seq_num,
				       tso_seg.tso_flags.ip_id);
		}
	}
}

void ol_txrx_tso_stats_clear(ol_txrx_pdev_handle pdev)
{
	qdf_mem_zero(&pdev->stats.pub.tx.tso.tso_pkts,
		     sizeof(struct ol_txrx_stats_elem));
#if defined(FEATURE_TSO)
	qdf_mem_zero(&pdev->stats.pub.tx.tso.tso_info,
		     sizeof(struct ol_txrx_stats_tso_info));
	qdf_mem_zero(&pdev->stats.pub.tx.tso.tso_hist,
		     sizeof(struct ol_txrx_tso_histogram));
#endif
}
#endif /* defined(FEATURE_TSO) && defined(FEATURE_TSO_DEBUG) */
