/** @file mlan_11n_aggr.c
 *
 *  @brief This file contains functions for 11n Aggregation.
 *
 *
 *  Copyright 2008-2021 NXP
 *
 *  NXP CONFIDENTIAL
 *  The source code contained or described herein and all documents related to
 *  the source code (Materials) are owned by NXP, its
 *  suppliers and/or its licensors. Title to the Materials remains with NXP,
 *  its suppliers and/or its licensors. The Materials contain
 *  trade secrets and proprietary and confidential information of NXP, its
 *  suppliers and/or its licensors. The Materials are protected by worldwide
 *  copyright and trade secret laws and treaty provisions. No part of the
 *  Materials may be used, copied, reproduced, modified, published, uploaded,
 *  posted, transmitted, distributed, or disclosed in any way without NXP's
 *  prior express written permission.
 *
 *  No license under any patent, copyright, trade secret or other intellectual
 *  property right is granted to or conferred upon you by disclosure or delivery
 *  of the Materials, either expressly, by implication, inducement, estoppel or
 *  otherwise. Any license under such intellectual property rights must be
 *  express and approved by NXP in writing.
 *
 *  Alternatively, this software may be distributed under the terms of GPL v2.
 *  SPDX-License-Identifier:    GPL-2.0
 *
 *
 */

/********************************************************
Change log:
    11/10/2008: initial version
********************************************************/

#include "mlan.h"
#include "mlan_join.h"
#include "mlan_util.h"
#include "mlan_fw.h"
#include "mlan_main.h"
#include "mlan_wmm.h"
#include "mlan_11n.h"
#include "mlan_11n_aggr.h"

/********************************************************
			Local Variables
********************************************************/

/********************************************************
			Global Variables
********************************************************/

/********************************************************
			Local Functions
********************************************************/
/**
 *  @brief Aggregate individual packets into one AMSDU packet
 *
 *  @param pmadapter A pointer to mlan_adapter structure
 *  @param amsdu_buf A pointer to packet buffer
 *  @param data      A pointer to aggregated data packet being formed
 *  @param pkt_len   Length of current packet to aggregate
 *  @param pad       Pad
 *
 *  @return         Final packet size
 */
static int wlan_11n_form_amsdu_pkt(pmlan_adapter pmadapter, t_u8 *amsdu_buf,
				   t_u8 *data, int pkt_len, int *pad)
{
	int dt_offset, amsdu_buf_offset;
	Rfc1042Hdr_t snap = {
		0xaa, /* LLC DSAP */
		0xaa, /* LLC SSAP */
		0x03, /* LLC CTRL */
		{0x00, 0x00, 0x00}, /* SNAP OUI */
		0x0000 /* SNAP type */
		/*
		 * This field will be overwritten
		 * later with ethertype
		 */
	};

	ENTER();

	memcpy_ext(pmadapter, amsdu_buf, data, (MLAN_MAC_ADDR_LENGTH)*2,
		   (MLAN_MAC_ADDR_LENGTH)*2);
	dt_offset = amsdu_buf_offset = (MLAN_MAC_ADDR_LENGTH)*2;

	snap.snap_type = *(t_u16 *)(data + dt_offset);
	dt_offset += sizeof(t_u16);
	*(t_u16 *)(amsdu_buf + amsdu_buf_offset) =
		mlan_htons(pkt_len + LLC_SNAP_LEN -
			   ((2 * MLAN_MAC_ADDR_LENGTH) + sizeof(t_u16)));
	amsdu_buf_offset += sizeof(t_u16);
	memcpy_ext(pmadapter, amsdu_buf + amsdu_buf_offset, &snap, LLC_SNAP_LEN,
		   LLC_SNAP_LEN);
	amsdu_buf_offset += LLC_SNAP_LEN;

	memcpy_ext(pmadapter, amsdu_buf + amsdu_buf_offset, data + dt_offset,
		   pkt_len - dt_offset, pkt_len - dt_offset);
	*pad = (((pkt_len + LLC_SNAP_LEN) & 3)) ?
		       (4 - (((pkt_len + LLC_SNAP_LEN)) & 3)) :
		       0;

	LEAVE();
	return pkt_len + LLC_SNAP_LEN + *pad;
}

/**
 *  @brief Add TxPD to AMSDU header
 *
 *  @param priv     A pointer to mlan_private structure
 *  @param mbuf		Pointer to buffer where the TxPD will be formed
 *
 *  @return		N/A
 */
static void wlan_11n_form_amsdu_txpd(mlan_private *priv, mlan_buffer *mbuf)
{
	TxPD *ptx_pd;
	mlan_adapter *pmadapter = priv->adapter;

	ENTER();

	ptx_pd = (TxPD *)mbuf->pbuf;
	memset(pmadapter, ptx_pd, 0, sizeof(TxPD));

	/*
	 * Original priority has been overwritten
	 */
	ptx_pd->priority = (t_u8)mbuf->priority;
	ptx_pd->bss_num = GET_BSS_NUM(priv);
	ptx_pd->bss_type = priv->bss_type;
	/* Always zero as the data is followed by TxPD */
	ptx_pd->tx_pkt_offset = sizeof(TxPD);
	ptx_pd->tx_pkt_type = PKT_TYPE_AMSDU;
	if (mbuf->flags & MLAN_BUF_FLAG_TDLS)
		ptx_pd->flags = MRVDRV_TxPD_FLAGS_TDLS_PACKET;
	if (ptx_pd->tx_control == 0)
		/* TxCtrl set by user or default */
		ptx_pd->tx_control = priv->pkt_tx_ctrl;

	endian_convert_TxPD(ptx_pd);

	LEAVE();
}

/**
 *  @brief Update the TxPktLength field in TxPD after the complete AMSDU
 *  packet is formed
 *
 *  @param priv     A pointer to mlan_private structure
 *  @param mbuf     TxPD buffer
 *
 *  @return         N/A
 */
static INLINE void wlan_11n_update_pktlen_amsdu_txpd(mlan_private *priv,
						     pmlan_buffer mbuf)
{
	TxPD *ptx_pd;
	ENTER();

	ptx_pd = (TxPD *)mbuf->pbuf;
	ptx_pd->tx_pkt_length =
		(t_u16)wlan_cpu_to_le16(mbuf->data_len - sizeof(TxPD));
	ptx_pd->pkt_delay_2ms =
		wlan_wmm_compute_driver_packet_delay(priv, mbuf);

#ifdef STA_SUPPORT
	if ((GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA) &&
	    (priv->adapter->pps_uapsd_mode)) {
		if (MTRUE == wlan_check_last_packet_indication(priv)) {
			priv->adapter->tx_lock_flag = MTRUE;
			ptx_pd->flags |= MRVDRV_TxPD_POWER_MGMT_LAST_PACKET;
		}
	}
#endif /* STA_SUPPORT */
	LEAVE();
}

/**
 *  @brief Get number of aggregated packets
 *
 *  @param data			A pointer to packet data
 *  @param total_pkt_len	Total packet length
 *
 *  @return			Number of packets
 */
static int wlan_11n_get_num_aggrpkts(t_u8 *data, int total_pkt_len)
{
	int pkt_count = 0, pkt_len, pad;
	t_u8 hdr_len = sizeof(Eth803Hdr_t);

	ENTER();
	while (total_pkt_len >= hdr_len) {
		/* Length will be in network format, change it to host */
		pkt_len = mlan_ntohs(
			(*(t_u16 *)(data + (2 * MLAN_MAC_ADDR_LENGTH))));
		if (pkt_len > total_pkt_len) {
			PRINTM(MERROR, "Error in packet length.\n");
			break;
		}

		pad = (((pkt_len + sizeof(Eth803Hdr_t)) & 3)) ?
			      (4 - ((pkt_len + sizeof(Eth803Hdr_t)) & 3)) :
			      0;
		data += pkt_len + pad + sizeof(Eth803Hdr_t);
		total_pkt_len -= pkt_len + pad + sizeof(Eth803Hdr_t);
		++pkt_count;
	}
	LEAVE();
	return pkt_count;
}

/********************************************************
			Global Functions
********************************************************/

/**
 *  @brief Deaggregate the received AMSDU packet
 *
 *  @param priv		A pointer to mlan_private structure
 *  @param pmbuf	A pointer to aggregated data packet
 *
 *  @return		MLAN_STATUS_SUCCESS --success, otherwise fail
 */
mlan_status wlan_11n_deaggregate_pkt(mlan_private *priv, pmlan_buffer pmbuf)
{
	t_u16 pkt_len;
	int total_pkt_len;
	t_u8 *data;
	mlan_adapter *pmadapter = priv->adapter;
	t_u32 max_rx_data_size = MLAN_RX_DATA_BUF_SIZE;
	int pad;
	mlan_status ret = MLAN_STATUS_FAILURE;
	RxPacketHdr_t *prx_pkt;
	mlan_buffer *daggr_mbuf = MNULL;
	t_u8 rfc1042_eth_hdr[MLAN_MAC_ADDR_LENGTH] = {0xaa, 0xaa, 0x03,
						      0x00, 0x00, 0x00};
	t_u8 hdr_len = sizeof(Eth803Hdr_t);
	t_u8 eapol_type[2] = {0x88, 0x8e};
	t_u8 tdls_action_type[2] = {0x89, 0x0d};
	t_u32 in_ts_sec, in_ts_usec;
	t_u32 out_ts_sec, out_ts_usec;
	t_u32 in_copy_ts_sec, in_copy_ts_usec;
	t_u32 out_copy_ts_sec, out_copy_ts_usec;
	t_u32 copy_delay = 0;
	t_u32 delay = 0;

	ENTER();

	data = (t_u8 *)(pmbuf->pbuf + pmbuf->data_offset);
	total_pkt_len = pmbuf->data_len;

	/* Sanity test */
#if defined(USB)
	if (IS_USB(pmadapter->card_type) &&
	    pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.enable) {
		max_rx_data_size =
			pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.aggr_max;
		if (pmadapter->pcard_usb->usb_rx_deaggr.aggr_ctrl.aggr_mode ==
		    MLAN_USB_AGGR_MODE_NUM) {
			max_rx_data_size *=
				MAX(MLAN_USB_MAX_PKT_SIZE,
				    pmadapter->pcard_usb->usb_rx_deaggr
					    .aggr_ctrl.aggr_align);
			max_rx_data_size =
				MAX(max_rx_data_size, MLAN_RX_DATA_BUF_SIZE);
		}
	}
#endif
	if (total_pkt_len > (int)max_rx_data_size) {
		PRINTM(MERROR,
		       "Total packet length greater than tx buffer"
		       " size %d\n",
		       total_pkt_len);
		goto done;
	}
	if (pmadapter->tp_state_on)
		pmadapter->callbacks.moal_get_system_time(
			pmadapter->pmoal_handle, &in_ts_sec, &in_ts_usec);
	pmbuf->use_count = wlan_11n_get_num_aggrpkts(data, total_pkt_len);

	// rx_trace 7
	if (pmadapter->tp_state_on) {
		pmadapter->callbacks.moal_tp_accounting(
			pmadapter->pmoal_handle, pmbuf, 7 /*RX_DROP_P3*/);
		pmadapter->callbacks.moal_tp_accounting_rx_param(
			pmadapter->pmoal_handle, 4, pmbuf->use_count);
	}
	if (pmadapter->tp_state_drop_point == 7 /*RX_DROP_P3*/)
		goto done;
	prx_pkt = (RxPacketHdr_t *)data;
	if (pmbuf->pdesc && !memcmp(pmadapter, prx_pkt->eth803_hdr.dest_addr,
				    priv->curr_addr, MLAN_MAC_ADDR_LENGTH)) {
		if (pmadapter->callbacks.moal_recv_amsdu_packet) {
			ret = pmadapter->callbacks.moal_recv_amsdu_packet(
				pmadapter->pmoal_handle, pmbuf);
			if (ret == MLAN_STATUS_PENDING) {
				priv->msdu_in_rx_amsdu_cnt += pmbuf->use_count;
				priv->amsdu_rx_cnt++;
				return ret;
			}
			goto done;
		}
	}
	while (total_pkt_len >= hdr_len) {
		prx_pkt = (RxPacketHdr_t *)data;
		/* Length will be in network format, change it to host */
		pkt_len = mlan_ntohs(
			(*(t_u16 *)(data + (2 * MLAN_MAC_ADDR_LENGTH))));
		if (pkt_len > total_pkt_len) {
			PRINTM(MERROR,
			       "Error in packet length: total_pkt_len = %d, pkt_len = %d\n",
			       total_pkt_len, pkt_len);
			ret = MLAN_STATUS_FAILURE;
			break;
		}

		pad = (((pkt_len + sizeof(Eth803Hdr_t)) & 3)) ?
			      (4 - ((pkt_len + sizeof(Eth803Hdr_t)) & 3)) :
			      0;

		total_pkt_len -= pkt_len + pad + sizeof(Eth803Hdr_t);

		if (memcmp(pmadapter, &prx_pkt->rfc1042_hdr, rfc1042_eth_hdr,
			   sizeof(rfc1042_eth_hdr)) == 0) {
			memmove(pmadapter, data + LLC_SNAP_LEN, data,
				(2 * MLAN_MAC_ADDR_LENGTH));
			data += LLC_SNAP_LEN;
			pkt_len += sizeof(Eth803Hdr_t) - LLC_SNAP_LEN;
		} else {
			*(t_u16 *)(data + (2 * MLAN_MAC_ADDR_LENGTH)) =
				(t_u16)0;
			pkt_len += sizeof(Eth803Hdr_t);
		}
		daggr_mbuf = wlan_alloc_mlan_buffer(pmadapter,
						    pkt_len + MLAN_NET_IP_ALIGN,
						    0, MOAL_ALLOC_MLAN_BUFFER);
		if (daggr_mbuf == MNULL) {
			PRINTM(MERROR, "Error allocating daggr mlan_buffer\n");
			ret = MLAN_STATUS_FAILURE;
			break;
		}
		daggr_mbuf->data_offset += MLAN_NET_IP_ALIGN;
		daggr_mbuf->bss_index = pmbuf->bss_index;
		daggr_mbuf->buf_type = pmbuf->buf_type;
		daggr_mbuf->data_len = pkt_len;
		daggr_mbuf->in_ts_sec = pmbuf->in_ts_sec;
		daggr_mbuf->in_ts_usec = pmbuf->in_ts_usec;
		daggr_mbuf->extra_ts_sec = pmbuf->extra_ts_sec;
		daggr_mbuf->extra_ts_usec = pmbuf->extra_ts_usec;
		daggr_mbuf->pparent = pmbuf;
		daggr_mbuf->priority = pmbuf->priority;
		if (pmadapter->tp_state_on)
			pmadapter->callbacks.moal_get_system_time(
				pmadapter->pmoal_handle, &in_copy_ts_sec,
				&in_copy_ts_usec);
		memcpy_ext(pmadapter,
			   daggr_mbuf->pbuf + daggr_mbuf->data_offset, data,
			   pkt_len, daggr_mbuf->data_len);
		if (pmadapter->tp_state_on) {
			pmadapter->callbacks.moal_get_system_time(
				pmadapter->pmoal_handle, &out_copy_ts_sec,
				&out_copy_ts_usec);
			copy_delay +=
				(t_s32)(out_copy_ts_sec - in_copy_ts_sec) *
				1000000;
			copy_delay +=
				(t_s32)(out_copy_ts_usec - in_copy_ts_usec);
		}
#ifdef UAP_SUPPORT
		if (GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_UAP) {
			ret = wlan_uap_recv_packet(priv, daggr_mbuf);
		} else {
#endif /* UAP_SUPPORT */
			/** send EAPOL from AMSDU pkt to firmware */
			if (priv->sec_info.ewpa_enabled &&
			    (!memcmp(pmadapter,
				     daggr_mbuf->pbuf +
					     daggr_mbuf->data_offset +
					     MLAN_ETHER_PKT_TYPE_OFFSET,
				     eapol_type, sizeof(eapol_type)))) {
				ret = wlan_prepare_cmd(
					priv, HostCmd_CMD_802_11_EAPOL_PKT, 0,
					0, MNULL, daggr_mbuf);
				if (ret == MLAN_STATUS_SUCCESS)
					wlan_recv_event(
						priv,
						MLAN_EVENT_ID_DRV_DEFER_HANDLING,
						MNULL);
				wlan_free_mlan_buffer(pmadapter, daggr_mbuf);
				data += pkt_len + pad;
				continue;
			}
			/**process tdls packet*/
			if (!memcmp(pmadapter,
				    daggr_mbuf->pbuf + daggr_mbuf->data_offset +
					    MLAN_ETHER_PKT_TYPE_OFFSET,
				    tdls_action_type,
				    sizeof(tdls_action_type))) {
				PRINTM(MEVENT,
				       "Recevie AMSDU TDLS action frame\n");
				wlan_process_tdls_action_frame(
					priv,
					daggr_mbuf->pbuf +
						daggr_mbuf->data_offset,
					daggr_mbuf->data_len);
			}

			ret = pmadapter->callbacks.moal_recv_packet(
				pmadapter->pmoal_handle, daggr_mbuf);
#ifdef UAP_SUPPORT
		}
#endif /* UAP_SUPPORT */
		switch (ret) {
		case MLAN_STATUS_PENDING:
			break;
		case MLAN_STATUS_FAILURE:
			PRINTM(MERROR, "Deaggr, send to moal failed\n");
			daggr_mbuf->status_code = MLAN_ERROR_PKT_INVALID;
			/* fall through */
		case MLAN_STATUS_SUCCESS:
			wlan_recv_packet_complete(pmadapter, daggr_mbuf, ret);
			break;
		default:
			break;
		}

		data += pkt_len + pad;
	}
	if (pmadapter->tp_state_on) {
		pmadapter->callbacks.moal_get_system_time(
			pmadapter->pmoal_handle, &out_ts_sec, &out_ts_usec);
		delay += (t_s32)(out_ts_sec - in_ts_sec) * 1000000;
		delay += (t_s32)(out_ts_usec - in_ts_usec);
		pmadapter->callbacks.moal_amsdu_tp_accounting(
			pmadapter->pmoal_handle, delay, copy_delay);
	}

done:
	priv->msdu_in_rx_amsdu_cnt += pmbuf->use_count;
	priv->amsdu_rx_cnt++;
	/** we should free the aggr buffer after deaggr */
	pmadapter->ops.data_complete(pmadapter, pmbuf, ret);
	LEAVE();
	return ret;
}

/**
 *  @brief Aggregate multiple packets into one single AMSDU packet
 *
 *  @param priv     A pointer to mlan_private structure
 *  @param pra_list Pointer to the RA List table containing the pointers
 *                  to packets.
 *  @param headroom Any interface specific headroom that may be need. TxPD
 *                  will be formed leaving this headroom.
 *  @param ptrindex Pointer index
 *
 *  @return     Final packet size or MLAN_STATUS_FAILURE
 */
int wlan_11n_aggregate_pkt(mlan_private *priv, raListTbl *pra_list,
			   int headroom, int ptrindex)
{
	int pkt_size = 0;
	pmlan_adapter pmadapter = priv->adapter;
	mlan_buffer *pmbuf_aggr, *pmbuf_src;
	t_u8 *data;
	int pad = 0;
	mlan_status ret = MLAN_STATUS_SUCCESS;
#ifdef DEBUG_LEVEL1
	t_u32 sec = 0, usec = 0;
#endif
	mlan_tx_param tx_param;
#ifdef STA_SUPPORT
	TxPD *ptx_pd = MNULL;
#endif
	t_u32 max_amsdu_size = MIN(pra_list->max_amsdu, pmadapter->tx_buf_size);
	t_u32 msdu_in_tx_amsdu_cnt = 0;
	ENTER();

	PRINTM(MDAT_D, "Handling Aggr packet\n");

	pmbuf_src = (pmlan_buffer)util_peek_list(
		pmadapter->pmoal_handle, &pra_list->buf_head, MNULL, MNULL);
	if (pmbuf_src) {
		pmbuf_aggr = wlan_alloc_mlan_buffer(
			pmadapter, pmadapter->tx_buf_size, 0,
			MOAL_MALLOC_BUFFER | MOAL_MEM_FLAG_ATOMIC);
		if (!pmbuf_aggr) {
			PRINTM(MERROR, "Error allocating mlan_buffer\n");
			pmadapter->callbacks.moal_spin_unlock(
				pmadapter->pmoal_handle,
				priv->wmm.ra_list_spinlock);
			LEAVE();
			return MLAN_STATUS_FAILURE;
		}

		data = pmbuf_aggr->pbuf + headroom;
		pmbuf_aggr->bss_index = pmbuf_src->bss_index;
		pmbuf_aggr->buf_type = pmbuf_src->buf_type;
		pmbuf_aggr->priority = pmbuf_src->priority;
		pmbuf_aggr->pbuf = data;
		pmbuf_aggr->data_offset = 0;
		pmbuf_aggr->in_ts_sec = pmbuf_src->in_ts_sec;
		pmbuf_aggr->in_ts_usec = pmbuf_src->in_ts_usec;
		pmbuf_aggr->extra_ts_sec = pmbuf_src->extra_ts_sec;
		pmbuf_aggr->extra_ts_usec = pmbuf_src->extra_ts_usec;
		if (pmbuf_src->flags & MLAN_BUF_FLAG_TDLS)
			pmbuf_aggr->flags |= MLAN_BUF_FLAG_TDLS;
		if (pmbuf_src->flags & MLAN_BUF_FLAG_TCP_ACK)
			pmbuf_aggr->flags |= MLAN_BUF_FLAG_TCP_ACK;

		/* Form AMSDU */
		wlan_11n_form_amsdu_txpd(priv, pmbuf_aggr);
		pkt_size = sizeof(TxPD);
#ifdef STA_SUPPORT
		if (GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA)
			ptx_pd = (TxPD *)pmbuf_aggr->pbuf;
#endif
		priv->msdu_in_tx_amsdu_cnt++;
	} else {
		pmadapter->callbacks.moal_spin_unlock(
			pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock);
		goto exit;
	}

	while (pmbuf_src && ((pkt_size + (pmbuf_src->data_len + LLC_SNAP_LEN) +
			      headroom) <= max_amsdu_size)) {
		pmbuf_src =
			(pmlan_buffer)util_dequeue_list(pmadapter->pmoal_handle,
							&pra_list->buf_head,
							MNULL, MNULL);
		/* Collects TP statistics */
		if (pmadapter->tp_state_on && (pkt_size > sizeof(TxPD)))
			pmadapter->callbacks.moal_tp_accounting(
				pmadapter->pmoal_handle, pmbuf_src, 3);
		pra_list->total_pkts--;

		/* decrement for every PDU taken from the list */
		priv->wmm.pkts_queued[ptrindex]--;
		util_scalar_decrement(pmadapter->pmoal_handle,
				      &priv->wmm.tx_pkts_queued, MNULL, MNULL);

		pmadapter->callbacks.moal_spin_unlock(
			pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock);

		if (pmbuf_src) {
			pkt_size += wlan_11n_form_amsdu_pkt(
				pmadapter, (data + pkt_size),
				pmbuf_src->pbuf + pmbuf_src->data_offset,
				pmbuf_src->data_len, &pad);

			DBG_HEXDUMP(MDAT_D, "pmbuf_src", pmbuf_src,
				    sizeof(mlan_buffer));
			wlan_write_data_complete(pmadapter, pmbuf_src,
						 MLAN_STATUS_SUCCESS);
		}

		pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle,
						    priv->wmm.ra_list_spinlock);

		if (!wlan_is_ralist_valid(priv, pra_list, ptrindex)) {
			pmadapter->callbacks.moal_spin_unlock(
				pmadapter->pmoal_handle,
				priv->wmm.ra_list_spinlock);
			LEAVE();
			return MLAN_STATUS_FAILURE;
		}

		pmbuf_src =
			(pmlan_buffer)util_peek_list(pmadapter->pmoal_handle,
						     &pra_list->buf_head, MNULL,
						     MNULL);
		priv->msdu_in_tx_amsdu_cnt++;
		msdu_in_tx_amsdu_cnt++;
	}

	pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle,
					      priv->wmm.ra_list_spinlock);

	/* Last AMSDU packet does not need padding */
	pkt_size -= pad;
	pmbuf_aggr->data_len = pkt_size;
	wlan_11n_update_pktlen_amsdu_txpd(priv, pmbuf_aggr);
	pmbuf_aggr->data_len += headroom;
	pmbuf_aggr->pbuf = data - headroom;
	tx_param.next_pkt_len =
		((pmbuf_src) ? pmbuf_src->data_len + sizeof(TxPD) : 0);
	/* Collects TP statistics */
	if (pmadapter->tp_state_on) {
		pmadapter->callbacks.moal_tp_accounting(pmadapter->pmoal_handle,
							pmbuf_aggr, 4);
		pmadapter->callbacks.moal_tp_accounting_rx_param(
			pmadapter->pmoal_handle, 5, msdu_in_tx_amsdu_cnt);
	}

	/* Drop Tx packets at drop point 4 */
	if (pmadapter->tp_state_drop_point == 4) {
		wlan_write_data_complete(pmadapter, pmbuf_aggr, ret);
		goto exit;
	} else
		ret = pmadapter->ops.host_to_card(priv, MLAN_TYPE_DATA,
						  pmbuf_aggr, &tx_param);
	switch (ret) {
#ifdef USB
	case MLAN_STATUS_PRESOURCE:
		PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n");
		break;
#endif
	case MLAN_STATUS_RESOURCE:
		pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle,
						    priv->wmm.ra_list_spinlock);

		if (!wlan_is_ralist_valid(priv, pra_list, ptrindex)) {
			pmadapter->callbacks.moal_spin_unlock(
				pmadapter->pmoal_handle,
				priv->wmm.ra_list_spinlock);
			pmbuf_aggr->status_code = MLAN_ERROR_PKT_INVALID;
			wlan_write_data_complete(pmadapter, pmbuf_aggr,
						 MLAN_STATUS_FAILURE);
			LEAVE();
			return MLAN_STATUS_FAILURE;
		}
#ifdef STA_SUPPORT
		/* reset tx_lock_flag */
		if ((GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA) &&
		    pmadapter->pps_uapsd_mode &&
		    (pmadapter->tx_lock_flag == MTRUE)) {
			pmadapter->tx_lock_flag = MFALSE;
			if (ptx_pd != MNULL)
				ptx_pd->flags = 0;
		}
#endif
		util_enqueue_list_head(pmadapter->pmoal_handle,
				       &pra_list->buf_head,
				       (pmlan_linked_list)pmbuf_aggr, MNULL,
				       MNULL);

		pra_list->total_pkts++;

		/* add back only one: aggregated packet is requeued as one */
		priv->wmm.pkts_queued[ptrindex]++;
		util_scalar_increment(pmadapter->pmoal_handle,
				      &priv->wmm.tx_pkts_queued, MNULL, MNULL);
		pmbuf_aggr->flags |= MLAN_BUF_FLAG_REQUEUED_PKT;
		pmadapter->callbacks.moal_spin_unlock(
			pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock);
		PRINTM(MINFO, "MLAN_STATUS_RESOURCE is returned\n");
		pmbuf_aggr->status_code = MLAN_ERROR_PKT_INVALID;
		break;
	case MLAN_STATUS_FAILURE:
		pmbuf_aggr->status_code = MLAN_ERROR_DATA_TX_FAIL;
		pmadapter->dbg.num_tx_host_to_card_failure++;
		wlan_write_data_complete(pmadapter, pmbuf_aggr, ret);
		goto exit;
	case MLAN_STATUS_PENDING:
		break;
	case MLAN_STATUS_SUCCESS:
		wlan_write_data_complete(pmadapter, pmbuf_aggr, ret);
		break;
	default:
		break;
	}
	if (ret != MLAN_STATUS_RESOURCE) {
		pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle,
						    priv->wmm.ra_list_spinlock);
		if (wlan_is_ralist_valid(priv, pra_list, ptrindex)) {
			priv->wmm.packets_out[ptrindex]++;
			priv->wmm.tid_tbl_ptr[ptrindex].ra_list_curr = pra_list;
		}
		pmadapter->bssprio_tbl[priv->bss_priority].bssprio_cur =
			pmadapter->bssprio_tbl[priv->bss_priority]
				.bssprio_cur->pnext;
		pmadapter->callbacks.moal_spin_unlock(
			pmadapter->pmoal_handle, priv->wmm.ra_list_spinlock);
	}
	PRINTM_GET_SYS_TIME(MDATA, &sec, &usec);
	PRINTM_NETINTF(MDATA, priv);
	PRINTM(MDATA, "%lu.%06lu : Data => FW\n", sec, usec);
	priv->amsdu_tx_cnt++;

exit:
	LEAVE();
	return MIN((pkt_size + headroom), INT_MAX);
}
