/**
 * @file mlan_txrx.c
 *
 *  @brief This file contains the handling of TX/RX in MLAN
 *
 *
 *  Copyright 2009-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:
    05/11/2009: initial version
************************************************************/

#include "mlan.h"
#ifdef STA_SUPPORT
#include "mlan_join.h"
#endif
#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 processes the received buffer
 *
 *   @param pmadapter A pointer to mlan_adapter
 *   @param pmbuf     A pointer to the received buffer
 *
 *   @return        MLAN_STATUS_SUCCESS or MLAN_STATUS_FAILURE
 */
mlan_status wlan_handle_rx_packet(pmlan_adapter pmadapter, pmlan_buffer pmbuf)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	pmlan_private priv = MNULL;
	RxPD *prx_pd;
#ifdef DEBUG_LEVEL1
	t_u32 sec = 0, usec = 0;
#endif

	ENTER();

	prx_pd = (RxPD *)(pmbuf->pbuf + pmbuf->data_offset);
	/* Get the BSS number from RxPD, get corresponding priv */
	priv = wlan_get_priv_by_id(pmadapter, prx_pd->bss_num & BSS_NUM_MASK,
				   prx_pd->bss_type);
	if (!priv)
		priv = wlan_get_priv(pmadapter, MLAN_BSS_ROLE_ANY);
	if (!priv) {
		ret = MLAN_STATUS_FAILURE;
		goto done;
	}
	pmbuf->bss_index = priv->bss_index;
	PRINTM_GET_SYS_TIME(MDATA, &sec, &usec);
	PRINTM_NETINTF(MDATA, priv);
	PRINTM(MDATA, "%lu.%06lu : Data <= FW\n", sec, usec);
	ret = priv->ops.process_rx_packet(pmadapter, pmbuf);

done:
	LEAVE();
	return ret;
}

/**
 *  @brief This function checks the conditions and sends packet to device
 *
 *  @param priv	   A pointer to mlan_private structure
 *  @param pmbuf   A pointer to the mlan_buffer for process
 *  @param tx_param A pointer to mlan_tx_param structure
 *
 *  @return         MLAN_STATUS_SUCCESS/MLAN_STATUS_PENDING --success, otherwise
 * failure
 */
mlan_status wlan_process_tx(pmlan_private priv, pmlan_buffer pmbuf,
			    mlan_tx_param *tx_param)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	pmlan_adapter pmadapter = priv->adapter;
	t_u8 *head_ptr = MNULL;
#ifdef DEBUG_LEVEL1
	t_u32 sec = 0, usec = 0;
#endif
#ifdef STA_SUPPORT
	PTxPD plocal_tx_pd = MNULL;
#endif

	ENTER();
	head_ptr = (t_u8 *)priv->ops.process_txpd(priv, pmbuf);
	if (!head_ptr) {
		pmbuf->status_code = MLAN_ERROR_PKT_INVALID;
		ret = MLAN_STATUS_FAILURE;
		goto done;
	}
#ifdef STA_SUPPORT
	if (GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA)
		plocal_tx_pd = (TxPD *)(head_ptr + priv->intf_hr_len);
#endif
	if (pmadapter->tp_state_on)
		pmadapter->callbacks.moal_tp_accounting(pmadapter->pmoal_handle,
							pmbuf, 4);
	if (pmadapter->tp_state_drop_point == 4)
		goto done;
	else {
		ret = pmadapter->ops.host_to_card(priv, MLAN_TYPE_DATA, pmbuf,
						  tx_param);
	}
done:
	switch (ret) {
#ifdef USB
	case MLAN_STATUS_PRESOURCE:
		PRINTM(MINFO, "MLAN_STATUS_PRESOURCE is returned\n");
		DBG_HEXDUMP(MDAT_D, "Tx", head_ptr + priv->intf_hr_len,
			    MIN(pmbuf->data_len + sizeof(TxPD),
				MAX_DATA_DUMP_LEN));
		break;
#endif
	case MLAN_STATUS_RESOURCE:
#ifdef STA_SUPPORT
		if ((GET_BSS_ROLE(priv) == MLAN_BSS_ROLE_STA) &&
		    pmadapter->pps_uapsd_mode &&
		    (pmadapter->tx_lock_flag == MTRUE)) {
			pmadapter->tx_lock_flag = MFALSE;
			if (plocal_tx_pd != MNULL)
				plocal_tx_pd->flags = 0;
		}
#endif
		PRINTM(MINFO, "MLAN_STATUS_RESOURCE is returned\n");
		break;
	case MLAN_STATUS_FAILURE:
		pmadapter->dbg.num_tx_host_to_card_failure++;
		pmbuf->status_code = MLAN_ERROR_DATA_TX_FAIL;
		wlan_write_data_complete(pmadapter, pmbuf, ret);
		break;
	case MLAN_STATUS_PENDING:
		DBG_HEXDUMP(MDAT_D, "Tx", head_ptr + priv->intf_hr_len,
			    MIN(pmbuf->data_len + sizeof(TxPD),
				MAX_DATA_DUMP_LEN));
		break;
	case MLAN_STATUS_SUCCESS:
		DBG_HEXDUMP(MDAT_D, "Tx", head_ptr + priv->intf_hr_len,
			    MIN(pmbuf->data_len + sizeof(TxPD),
				MAX_DATA_DUMP_LEN));
		wlan_write_data_complete(pmadapter, pmbuf, ret);
		break;
	default:
		break;
	}

	if ((ret == MLAN_STATUS_SUCCESS) || (ret == MLAN_STATUS_PENDING)) {
		PRINTM_GET_SYS_TIME(MDATA, &sec, &usec);
		PRINTM_NETINTF(MDATA, priv);
		PRINTM(MDATA, "%lu.%06lu : Data => FW\n", sec, usec);
	}
	LEAVE();
	return ret;
}

/**
 *  @brief Packet send completion handling
 *
 *  @param pmadapter		A pointer to mlan_adapter structure
 *  @param pmbuf		A pointer to mlan_buffer structure
 *  @param status		Callback status
 *
 *  @return			MLAN_STATUS_SUCCESS
 */
mlan_status wlan_write_data_complete(pmlan_adapter pmadapter,
				     pmlan_buffer pmbuf, mlan_status status)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;
	pmlan_callbacks pcb;

	ENTER();

	MASSERT(pmadapter && pmbuf);
	if (!pmadapter || !pmbuf) {
		LEAVE();
		return MLAN_STATUS_FAILURE;
	}

	pcb = &pmadapter->callbacks;

	if ((pmbuf->buf_type == MLAN_BUF_TYPE_DATA) ||
	    (pmbuf->buf_type == MLAN_BUF_TYPE_RAW_DATA)) {
		PRINTM(MINFO, "wlan_write_data_complete: DATA %p\n", pmbuf);
#if defined(USB)
		if ((pmbuf->flags & MLAN_BUF_FLAG_USB_TX_AGGR) &&
		    pmbuf->use_count) {
			pmlan_buffer pmbuf_next;
			t_u32 i, use_count = pmbuf->use_count;
			for (i = 0; i <= use_count; i++) {
				pmbuf_next = pmbuf->pnext;
				if (pmbuf->flags & MLAN_BUF_FLAG_MOAL_TX_BUF)
					pcb->moal_send_packet_complete(
						pmadapter->pmoal_handle, pmbuf,
						status);
				else
					wlan_free_mlan_buffer(pmadapter, pmbuf);
				pmbuf = pmbuf_next;
			}
		} else {
#endif
			if (pmbuf->flags & MLAN_BUF_FLAG_MOAL_TX_BUF) {
				/* pmbuf was allocated by MOAL */
				pcb->moal_send_packet_complete(
					pmadapter->pmoal_handle, pmbuf, status);
			} else {
				/* pmbuf was allocated by MLAN */
				wlan_free_mlan_buffer(pmadapter, pmbuf);
			}
#if defined(USB)
		}
#endif
	}

	LEAVE();
	return ret;
}

/**
 *  @brief Packet receive completion callback handler
 *
 *  @param pmadapter		A pointer to mlan_adapter structure
 *  @param pmbuf		A pointer to mlan_buffer structure
 *  @param status		Callback status
 *
 *  @return			MLAN_STATUS_SUCCESS
 */
mlan_status wlan_recv_packet_complete(pmlan_adapter pmadapter,
				      pmlan_buffer pmbuf, mlan_status status)
{
	mlan_status ret = MLAN_STATUS_SUCCESS;

	ENTER();

	MASSERT(pmadapter && pmbuf);
	if (!pmadapter || !pmbuf) {
		LEAVE();
		return MLAN_STATUS_FAILURE;
	}

	MASSERT(pmbuf->bss_index < pmadapter->priv_num);

	if (pmbuf->pparent) {
		/** we will free the pparaent at the end of deaggr */
		wlan_free_mlan_buffer(pmadapter, pmbuf);
	} else {
		pmadapter->ops.data_complete(pmadapter, pmbuf, status);
	}

	LEAVE();
	return ret;
}

/**
 *  @brief Add packet to Bypass TX queue
 *
 *  @param pmadapter  Pointer to the mlan_adapter driver data struct
 *  @param pmbuf      Pointer to the mlan_buffer data struct
 *
 *  @return         N/A
 */
t_void wlan_add_buf_bypass_txqueue(mlan_adapter *pmadapter, pmlan_buffer pmbuf)
{
	pmlan_private priv = pmadapter->priv[pmbuf->bss_index];
	ENTER();

	if (pmbuf->buf_type != MLAN_BUF_TYPE_RAW_DATA)
		pmbuf->buf_type = MLAN_BUF_TYPE_DATA;
	pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle,
					    priv->bypass_txq.plock);
	pmadapter->bypass_pkt_count++;
	util_enqueue_list_tail(pmadapter->pmoal_handle, &priv->bypass_txq,
			       (pmlan_linked_list)pmbuf, MNULL, MNULL);
	pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle,
					      priv->bypass_txq.plock);
	LEAVE();
}

/**
 *  @brief Check if packets are available in Bypass TX queue
 *
 *  @param pmadapter  Pointer to the mlan_adapter driver data struct
 *
 *  @return         MFALSE if not empty; MTRUE if empty
 */
INLINE t_u8 wlan_bypass_tx_list_empty(mlan_adapter *pmadapter)
{
#if defined(USB)
	if (IS_USB(pmadapter->card_type)) {
		pmlan_callbacks pcb = &pmadapter->callbacks;
		pmlan_private priv;
		int j = 0;
		for (j = 0; j < pmadapter->priv_num; ++j) {
			priv = pmadapter->priv[j];
			if (priv) {
				if (!wlan_is_port_ready(pmadapter,
							priv->port_index))
					continue;
				if (util_peek_list(pmadapter->pmoal_handle,
						   &priv->bypass_txq,
						   pcb->moal_spin_lock,
						   pcb->moal_spin_unlock))
					return MFALSE;
			}
		}
		return MTRUE;
	} else
#endif
		return (pmadapter->bypass_pkt_count) ? MFALSE : MTRUE;
}

/**
 *  @brief Clean up the By-pass TX queue
 *
 *  @param priv     Pointer to the mlan_private data struct
 *
 *  @return      N/A
 */
t_void wlan_cleanup_bypass_txq(mlan_private *priv)
{
	pmlan_buffer pmbuf;
	mlan_adapter *pmadapter = priv->adapter;
	ENTER();
	pmadapter->callbacks.moal_spin_lock(pmadapter->pmoal_handle,
					    priv->bypass_txq.plock);
	while ((pmbuf = (pmlan_buffer)util_peek_list(pmadapter->pmoal_handle,
						     &priv->bypass_txq, MNULL,
						     MNULL))) {
		util_unlink_list(pmadapter->pmoal_handle, &priv->bypass_txq,
				 (pmlan_linked_list)pmbuf, MNULL, MNULL);
		wlan_write_data_complete(pmadapter, pmbuf, MLAN_STATUS_FAILURE);
		pmadapter->bypass_pkt_count--;
	}
	pmadapter->callbacks.moal_spin_unlock(pmadapter->pmoal_handle,
					      priv->bypass_txq.plock);
	LEAVE();
}

/**
 *  @brief Transmit the By-passed packet awaiting in by-pass queue
 *
 *  @param pmadapter Pointer to the mlan_adapter driver data struct
 *
 *  @return        N/A
 */
t_void wlan_process_bypass_tx(pmlan_adapter pmadapter)
{
	pmlan_buffer pmbuf;
	mlan_tx_param tx_param;
	mlan_status status = MLAN_STATUS_SUCCESS;
	pmlan_private priv;
	int j = 0;
	ENTER();
	do {
		for (j = 0; j < pmadapter->priv_num; ++j) {
			priv = pmadapter->priv[j];
			if (priv) {
#if defined(USB)
				if (!wlan_is_port_ready(pmadapter,
							priv->port_index))
					continue;
#endif
				pmbuf = (pmlan_buffer)util_dequeue_list(
					pmadapter->pmoal_handle,
					&priv->bypass_txq,
					pmadapter->callbacks.moal_spin_lock,
					pmadapter->callbacks.moal_spin_unlock);
				if (pmbuf) {
					pmadapter->callbacks.moal_spin_lock(
						pmadapter->pmoal_handle,
						priv->bypass_txq.plock);
					pmadapter->bypass_pkt_count--;
					pmadapter->callbacks.moal_spin_unlock(
						pmadapter->pmoal_handle,
						priv->bypass_txq.plock);
					PRINTM(MINFO,
					       "Dequeuing bypassed packet %p\n",
					       pmbuf);
					if (wlan_bypass_tx_list_empty(
						    pmadapter))
						tx_param.next_pkt_len = 0;
					else
						tx_param.next_pkt_len =
							pmbuf->data_len;
					status = wlan_process_tx(
						pmadapter->priv[pmbuf->bss_index],
						pmbuf, &tx_param);

					if (status == MLAN_STATUS_RESOURCE) {
						/* Queue the packet again so
						 * that it will be TX'ed later
						 */
						pmadapter->callbacks.moal_spin_lock(
							pmadapter->pmoal_handle,
							priv->bypass_txq.plock);
						pmadapter->bypass_pkt_count++;
						util_enqueue_list_head(
							pmadapter->pmoal_handle,
							&priv->bypass_txq,
							(pmlan_linked_list)pmbuf,
							pmadapter->callbacks
								.moal_spin_lock,
							pmadapter->callbacks
								.moal_spin_unlock);
						pmadapter->callbacks.moal_spin_unlock(
							pmadapter->pmoal_handle,
							priv->bypass_txq.plock);
					}
					break;
				} else {
					PRINTM(MINFO, "Nothing to send\n");
				}
			}
		}
	} while (!pmadapter->data_sent && !pmadapter->tx_lock_flag &&
		 !wlan_bypass_tx_list_empty(pmadapter));
	LEAVE();
}
