/** @file mlan_sta_tx.c
 *
 *  @brief This file contains the handling of data packet
 *  transmission in MLAN module.
 *
 *
 *  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:
    10/21/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"

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

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

/********************************************************
		Local Functions
********************************************************/

/********************************************************
		Global functions
********************************************************/
/**
 *  @brief This function fill the txpd for tx packet
 *
 *  @param priv    A pointer to mlan_private structure
 *  @param pmbuf   A pointer to the mlan_buffer for process
 *
 *  @return        headptr or MNULL
 */
t_void *wlan_ops_sta_process_txpd(t_void *priv, pmlan_buffer pmbuf)
{
	mlan_private *pmpriv = (mlan_private *)priv;
	pmlan_adapter pmadapter = pmpriv->adapter;
	TxPD *plocal_tx_pd;
	t_u8 *head_ptr = MNULL;
	t_u32 pkt_type;
	t_u32 tx_control;

	ENTER();

	if (!pmbuf->data_len) {
		PRINTM(MERROR, "STA Tx Error: Invalid packet length: %d\n",
		       pmbuf->data_len);
		pmbuf->status_code = MLAN_ERROR_PKT_SIZE_INVALID;
		goto done;
	}
	if (pmbuf->buf_type == MLAN_BUF_TYPE_RAW_DATA) {
		memcpy_ext(pmpriv->adapter, &pkt_type,
			   pmbuf->pbuf + pmbuf->data_offset, sizeof(pkt_type),
			   sizeof(pkt_type));
		memcpy_ext(pmpriv->adapter, &tx_control,
			   pmbuf->pbuf + pmbuf->data_offset + sizeof(pkt_type),
			   sizeof(tx_control), sizeof(tx_control));
		pmbuf->data_offset += sizeof(pkt_type) + sizeof(tx_control);
		pmbuf->data_len -= sizeof(pkt_type) + sizeof(tx_control);
	}

	if (pmbuf->data_offset <
	    (sizeof(TxPD) + pmpriv->intf_hr_len + DMA_ALIGNMENT)) {
		PRINTM(MERROR,
		       "not enough space for TxPD: headroom=%d pkt_len=%d, required=%d\n",
		       pmbuf->data_offset, pmbuf->data_len,
		       sizeof(TxPD) + pmpriv->intf_hr_len + DMA_ALIGNMENT);
		pmbuf->status_code = MLAN_ERROR_PKT_SIZE_INVALID;
		goto done;
	}

	/* head_ptr should be aligned */
	head_ptr = pmbuf->pbuf + pmbuf->data_offset - sizeof(TxPD) -
		   pmpriv->intf_hr_len;
	head_ptr = (t_u8 *)((t_ptr)head_ptr & ~((t_ptr)(DMA_ALIGNMENT - 1)));

	plocal_tx_pd = (TxPD *)(head_ptr + pmpriv->intf_hr_len);
	memset(pmadapter, plocal_tx_pd, 0, sizeof(TxPD));
	/* Set the BSS number to TxPD */
	plocal_tx_pd->bss_num = GET_BSS_NUM(pmpriv);
	plocal_tx_pd->bss_type = pmpriv->bss_type;
	plocal_tx_pd->tx_pkt_length = (t_u16)pmbuf->data_len;

	plocal_tx_pd->priority = (t_u8)pmbuf->priority;
	plocal_tx_pd->pkt_delay_2ms =
		wlan_wmm_compute_driver_packet_delay(pmpriv, pmbuf);

	if (plocal_tx_pd->priority <
	    NELEMENTS(pmpriv->wmm.user_pri_pkt_tx_ctrl))
		/*
		 * Set the priority specific tx_control field, setting of 0 will
		 *   cause the default value to be used later in this function
		 */
		plocal_tx_pd->tx_control =
			pmpriv->wmm.user_pri_pkt_tx_ctrl[plocal_tx_pd->priority];
	if (pmadapter->pps_uapsd_mode) {
		if (MTRUE == wlan_check_last_packet_indication(pmpriv)) {
			pmadapter->tx_lock_flag = MTRUE;
			plocal_tx_pd->flags =
				MRVDRV_TxPD_POWER_MGMT_LAST_PACKET;
		}
	}
	if (pmbuf->flags & MLAN_BUF_FLAG_TDLS)
		plocal_tx_pd->flags |= MRVDRV_TxPD_FLAGS_TDLS_PACKET;
	/* Offset of actual data */
	plocal_tx_pd->tx_pkt_offset = (t_u16)(
		(t_ptr)pmbuf->pbuf + pmbuf->data_offset - (t_ptr)plocal_tx_pd);

	if (!plocal_tx_pd->tx_control) {
		/* TxCtrl set by user or default */
		plocal_tx_pd->tx_control = pmpriv->pkt_tx_ctrl;
	}

	if (pmbuf->buf_type == MLAN_BUF_TYPE_RAW_DATA) {
		plocal_tx_pd->tx_pkt_type = (t_u16)pkt_type;
		plocal_tx_pd->tx_control = tx_control;
	}

	if (pmbuf->flags & MLAN_BUF_FLAG_TX_STATUS) {
		plocal_tx_pd->tx_control_1 |= pmbuf->tx_seq_num << 8;
		plocal_tx_pd->flags |= MRVDRV_TxPD_FLAGS_TX_PACKET_STATUS;
	}
	if (pmbuf->flags & MLAN_BUF_FLAG_TX_CTRL) {
		if (pmbuf->u.tx_info.data_rate) {
			plocal_tx_pd->tx_control |=
				(wlan_ieee_rateid_to_mrvl_rateid(
					 pmpriv, pmbuf->u.tx_info.data_rate,
					 MNULL)
				 << 16);
			plocal_tx_pd->tx_control |= TXPD_TXRATE_ENABLE;
		}
		plocal_tx_pd->tx_control_1 |= pmbuf->u.tx_info.channel << 21;
		if (pmbuf->u.tx_info.bw) {
			plocal_tx_pd->tx_control_1 |= pmbuf->u.tx_info.bw << 16;
			plocal_tx_pd->tx_control_1 |= TXPD_BW_ENABLE;
		}
		if (pmbuf->u.tx_info.tx_power.tp.hostctl)
			plocal_tx_pd->tx_control |=
				(t_u32)pmbuf->u.tx_info.tx_power.val;
		if (pmbuf->u.tx_info.retry_limit) {
			plocal_tx_pd->tx_control |= pmbuf->u.tx_info.retry_limit
						    << 8;
			plocal_tx_pd->tx_control |= TXPD_RETRY_ENABLE;
		}
	}
	if (pmbuf->flags & MLAN_BUF_FLAG_MC_AGGR_PKT) {
		tx_ctrl *ctrl = (tx_ctrl *)&plocal_tx_pd->tx_control;
		mc_tx_ctrl *mc_ctrl =
			(mc_tx_ctrl *)&plocal_tx_pd->pkt_delay_2ms;
		plocal_tx_pd->tx_pkt_type = PKT_TYPE_802DOT11_MC_AGGR;
		if (pmbuf->u.mc_tx_info.mc_pkt_flags & MC_FLAG_START_CYCLE)
			ctrl->mc_cycle_start = MTRUE;
		else
			ctrl->mc_cycle_start = MFALSE;
		if (pmbuf->u.mc_tx_info.mc_pkt_flags & MC_FLAG_END_CYCLE)
			ctrl->mc_cycle_end = MTRUE;
		else
			ctrl->mc_cycle_end = MFALSE;
		if (pmbuf->u.mc_tx_info.mc_pkt_flags & MC_FLAG_START_AMPDU)
			ctrl->mc_ampdu_start = MTRUE;
		else
			ctrl->mc_ampdu_start = MFALSE;
		if (pmbuf->u.mc_tx_info.mc_pkt_flags & MC_FLAG_END_AMPDU)
			ctrl->mc_ampdu_end = MTRUE;
		else
			ctrl->mc_ampdu_end = MFALSE;
		if (pmbuf->u.mc_tx_info.mc_pkt_flags & MC_FLAG_RETRY)
			ctrl->mc_pkt_retry = MTRUE;
		else
			ctrl->mc_pkt_retry = MFALSE;
		ctrl->bw = pmbuf->u.mc_tx_info.bandwidth & 0x7;
		ctrl->tx_rate = pmbuf->u.mc_tx_info.mcs_index & 0x1f;
		mc_ctrl->abs_tsf_expirytime =
			wlan_cpu_to_le32(pmbuf->u.mc_tx_info.pkt_expiry);
		mc_ctrl->mc_seq = wlan_cpu_to_le16(pmbuf->u.mc_tx_info.seq_num);
	}
	endian_convert_TxPD(plocal_tx_pd);

	/* Adjust the data offset and length to include TxPD in pmbuf */
	pmbuf->data_len += pmbuf->data_offset;
	pmbuf->data_offset = (t_u32)(head_ptr - pmbuf->pbuf);
	pmbuf->data_len -= pmbuf->data_offset;

done:
	LEAVE();
	return head_ptr;
}

/**
 *  @brief This function tells firmware to send a NULL data packet.
 *
 *  @param priv     A pointer to mlan_private structure
 *  @param flags    Transmit Pkt Flags
 *
 *  @return         MLAN_STATUS_SUCCESS/MLAN_STATUS_PENDING --success, otherwise
 * failure
 */
mlan_status wlan_send_null_packet(pmlan_private priv, t_u8 flags)
{
	pmlan_adapter pmadapter = MNULL;
	TxPD *ptx_pd;
/* sizeof(TxPD) + Interface specific header */
#define NULL_PACKET_HDR 256
	t_u32 data_len = NULL_PACKET_HDR;
	pmlan_buffer pmbuf = MNULL;
	t_u8 *ptr;
	mlan_status ret = MLAN_STATUS_SUCCESS;
#ifdef DEBUG_LEVEL1
	t_u32 sec = 0, usec = 0;
#endif

	ENTER();

	if (!priv) {
		LEAVE();
		return MLAN_STATUS_FAILURE;
	}
	pmadapter = priv->adapter;

	if (pmadapter->surprise_removed == MTRUE) {
		ret = MLAN_STATUS_FAILURE;
		goto done;
	}

	if (priv->media_connected == MFALSE) {
		ret = MLAN_STATUS_FAILURE;
		goto done;
	}

	if (pmadapter->data_sent == MTRUE) {
		ret = MLAN_STATUS_FAILURE;
		goto done;
	}
#if defined(USB)
	if (!wlan_is_port_ready(pmadapter, priv->port_index)) {
		ret = MLAN_STATUS_FAILURE;
		goto done;
	}
#endif

	pmbuf = wlan_alloc_mlan_buffer(pmadapter, data_len, 0,
				       MOAL_MALLOC_BUFFER);
	if (!pmbuf) {
		ret = MLAN_STATUS_FAILURE;
		goto done;
	}
	memset(pmadapter, pmbuf->pbuf, 0, data_len);
	pmbuf->bss_index = priv->bss_index;
	pmbuf->buf_type = MLAN_BUF_TYPE_DATA;
	pmbuf->flags |= MLAN_BUF_FLAG_NULL_PKT;
	ptr = pmbuf->pbuf + pmbuf->data_offset;
	pmbuf->data_len = sizeof(TxPD) + priv->intf_hr_len;
	ptx_pd = (TxPD *)(ptr + priv->intf_hr_len);
	ptx_pd->tx_control = priv->pkt_tx_ctrl;
	ptx_pd->flags = flags;
	ptx_pd->priority = WMM_HIGHEST_PRIORITY;
	ptx_pd->tx_pkt_offset = sizeof(TxPD);
	/* Set the BSS number to TxPD */
	ptx_pd->bss_num = GET_BSS_NUM(priv);
	ptx_pd->bss_type = priv->bss_type;

	endian_convert_TxPD(ptx_pd);

	ret = pmadapter->ops.host_to_card(priv, MLAN_TYPE_DATA, pmbuf, MNULL);

	switch (ret) {
#ifdef USB
	case MLAN_STATUS_PRESOURCE:
		PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n");
		break;
#endif
	case MLAN_STATUS_RESOURCE:
		wlan_free_mlan_buffer(pmadapter, pmbuf);
		PRINTM(MERROR, "STA Tx Error: Failed to send NULL packet!\n");
		pmadapter->dbg.num_tx_host_to_card_failure++;
		goto done;
	case MLAN_STATUS_FAILURE:
		wlan_free_mlan_buffer(pmadapter, pmbuf);
		PRINTM(MERROR, "STA Tx Error: Failed to send NULL packet!\n");
		pmadapter->dbg.num_tx_host_to_card_failure++;
		goto done;
	case MLAN_STATUS_SUCCESS:
		wlan_free_mlan_buffer(pmadapter, pmbuf);
		PRINTM(MINFO, "STA Tx: Successfully send the NULL packet\n");
		pmadapter->tx_lock_flag = MTRUE;
		break;
	case MLAN_STATUS_PENDING:
		pmadapter->tx_lock_flag = MTRUE;
		break;
	default:
		break;
	}

	PRINTM_GET_SYS_TIME(MDATA, &sec, &usec);
	PRINTM_NETINTF(MDATA, priv);
	PRINTM(MDATA, "%lu.%06lu : Null data => FW\n", sec, usec);
	DBG_HEXDUMP(MDAT_D, "Null data", ptr, sizeof(TxPD) + priv->intf_hr_len);
done:
	LEAVE();
	return ret;
}

/**
 *  @brief This function checks if we need to send last packet indication.
 *
 *  @param priv    A pointer to mlan_private structure
 *
 *  @return        MTRUE or MFALSE
 */
t_u8 wlan_check_last_packet_indication(pmlan_private priv)
{
	pmlan_adapter pmadapter = priv->adapter;
	t_u8 ret = MFALSE;
	t_u8 prop_ps = MTRUE;

	ENTER();

	if (!pmadapter->sleep_period.period) {
		LEAVE();
		return ret;
	}
	if (wlan_bypass_tx_list_empty(pmadapter) &&
	    wlan_wmm_lists_empty(pmadapter)) {
		if (((priv->curr_bss_params.wmm_uapsd_enabled == MTRUE) &&
		     priv->wmm_qosinfo) ||
		    prop_ps)

			ret = MTRUE;
	}
	if (ret && !pmadapter->cmd_sent && !pmadapter->curr_cmd &&
	    !wlan_is_cmd_pending(pmadapter)) {
		pmadapter->delay_null_pkt = MFALSE;
		ret = MTRUE;
	} else {
		ret = MFALSE;
		pmadapter->delay_null_pkt = MTRUE;
	}
	LEAVE();
	return ret;
}
