/*
 * Copyright (c) 2012-2018,2021 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.
 */

/**===========================================================================

  \file  wlan_hdd_softap_tx_rx.c

  \brief Linux HDD Tx/RX APIs

  ==========================================================================*/

/*---------------------------------------------------------------------------
  Include files
  -------------------------------------------------------------------------*/
#include <linux/semaphore.h>
#include <wlan_hdd_tx_rx.h>
#include <wlan_hdd_softap_tx_rx.h>
#include <wlan_hdd_dp_utils.h>
#include <wlan_qct_tl.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/etherdevice.h>
#include <vos_types.h>
#include <vos_sched.h>
#include <aniGlobal.h>
#include <halTypes.h>
#include <net/ieee80211_radiotap.h>
#ifdef IPA_OFFLOAD
#include <wlan_hdd_ipa.h>
#endif
#include "adf_trace.h"
#include <wlan_hdd_tsf.h>
/*---------------------------------------------------------------------------
  Preprocessor definitions and constants
  -------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
  Type declarations
  -------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------
  Function definitions and documentation
  -------------------------------------------------------------------------*/

/**============================================================================
  @brief hdd_softap_flush_tx_queues() - Utility function to flush the TX queues

  @param pAdapter : [in] pointer to adapter context
  @return         : VOS_STATUS_E_FAILURE if any errors encountered
                  : VOS_STATUS_SUCCESS otherwise
  ===========================================================================*/
static VOS_STATUS hdd_softap_flush_tx_queues( hdd_adapter_t *pAdapter )
{
   VOS_STATUS status = VOS_STATUS_SUCCESS;
   v_SINT_t i = -1;
   v_U8_t STAId = 0;
   hdd_list_node_t *anchor = NULL;
   skb_list_node_t *pktNode = NULL;
   struct sk_buff *skb = NULL;

   adf_os_spin_lock_bh( &pAdapter->staInfo_lock );
   for (STAId = 0; STAId < WLAN_MAX_STA_COUNT; STAId++)
   {
      if (FALSE == pAdapter->aStaInfo[STAId].isUsed)
      {
         continue;
      }

      for (i = 0; i < NUM_TX_QUEUES; i ++)
      {
         adf_os_spin_lock_bh(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i].lock);
         while (true)
         {
            status = hdd_list_remove_front ( &pAdapter->aStaInfo[STAId].wmm_tx_queue[i], &anchor);

            if (VOS_STATUS_E_EMPTY != status)
            {
               //If success then we got a valid packet from some AC
               pktNode = list_entry(anchor, skb_list_node_t, anchor);
               skb = pktNode->skb;
               ++pAdapter->stats.tx_dropped;
               kfree_skb(skb);
               continue;
            }

            //current list is empty
            break;
         }
         pAdapter->aStaInfo[STAId].txSuspended[i] = VOS_FALSE;
         adf_os_spin_unlock_bh(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i].lock);
      }
      pAdapter->aStaInfo[STAId].vosLowResource = VOS_FALSE;
   }

   adf_os_spin_unlock_bh( &pAdapter->staInfo_lock );

   return status;
}

#ifdef QCA_LL_TX_FLOW_CT
/**============================================================================
  @brief hdd_softap_tx_resume_timer_expired_handler() - Resume OS TX Q timer
      expired handler for SAP and P2P GO interface.
      If Blocked OS Q is not resumed during timeout period, to prevent
      permanent stall, resume OS Q forcefully for SAP and P2P GO interface.

  @param adapter_context : [in] pointer to vdev adapter

  @return         : NONE
  ===========================================================================*/
void hdd_softap_tx_resume_timer_expired_handler(void *adapter_context)
{
   hdd_adapter_t *pAdapter = (hdd_adapter_t *)adapter_context;

   if (!pAdapter)
   {
      VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                "%s: INV ARG", __func__);
      /* INVALID ARG */
      return;
   }

   hddLog(LOG1, FL("Enabling queues"));
   wlan_hdd_netif_queue_control(pAdapter, WLAN_WAKE_ALL_NETIF_QUEUE,
                 WLAN_DATA_FLOW_CONTROL);
   return;
}

/**============================================================================
  @brief hdd_softap_tx_resume_cb() - Resume OS TX Q.
      Q was stopped due to WLAN TX path low resource condition

  @param adapter_context : [in] pointer to vdev adapter
  @param tx_resume       : [in] TX Q resume trigger

  @return         : NONE
  ===========================================================================*/
void hdd_softap_tx_resume_cb(void *adapter_context,
                        v_BOOL_t tx_resume)
{
   hdd_adapter_t *pAdapter = (hdd_adapter_t *)adapter_context;

   if (!pAdapter)
   {
      VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                "%s: INV ARG", __func__);
      /* INVALID ARG */
      return;
   }

   /* Resume TX  */
   if (VOS_TRUE == tx_resume)
   {
       if(VOS_TIMER_STATE_STOPPED !=
          vos_timer_getCurrentState(&pAdapter->tx_flow_control_timer))
       {
          vos_timer_stop(&pAdapter->tx_flow_control_timer);
       }

       hddLog(LOG1, FL("Enabling queues"));
       wlan_hdd_netif_queue_control(pAdapter,
            WLAN_WAKE_ALL_NETIF_QUEUE,
            WLAN_DATA_FLOW_CONTROL);
       pAdapter->hdd_stats.hddTxRxStats.txflow_unpause_cnt++;
       pAdapter->hdd_stats.hddTxRxStats.is_txflow_paused = FALSE;

   }
#if defined(CONFIG_PER_VDEV_TX_DESC_POOL)
    else if (VOS_FALSE == tx_resume)  /* Pause TX  */
    {
        hddLog(LOG1, FL("Disabling queues"));
        wlan_hdd_netif_queue_control(pAdapter,
            WLAN_STOP_ALL_NETIF_QUEUE,
            WLAN_DATA_FLOW_CONTROL);
        if (VOS_TIMER_STATE_STOPPED ==
            vos_timer_getCurrentState(&pAdapter->tx_flow_control_timer))
        {
            VOS_STATUS status;
            status = vos_timer_start(&pAdapter->tx_flow_control_timer,
                          WLAN_SAP_HDD_TX_FLOW_CONTROL_OS_Q_BLOCK_TIME);
            if ( !VOS_IS_STATUS_SUCCESS(status) )
            {
                VOS_TRACE( VOS_MODULE_ID_HDD, VOS_TRACE_LEVEL_ERROR,
                "%s: Failed to start tx_flow_control_timer", __func__);
            }
            else
            {
                pAdapter->hdd_stats.hddTxRxStats.txflow_timer_cnt++;
            }
        }
        pAdapter->hdd_stats.hddTxRxStats.txflow_pause_cnt++;
        pAdapter->hdd_stats.hddTxRxStats.is_txflow_paused = TRUE;
    }
#endif

   return;
}
#endif /* QCA_LL_TX_FLOW_CT */

/**============================================================================
  @brief hdd_softap_hard_start_xmit() - Function registered with the Linux OS
                                        for transmitting packets.

  @param skb      : [in]  pointer to OS packet (sk_buff)
  @param dev      : [in] pointer to Libra network device

  @return         : NETDEV_TX_OK
  ===========================================================================*/
int __hdd_softap_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
   WLANTL_ACEnumType ac;
   hdd_adapter_t *pAdapter = (hdd_adapter_t *)netdev_priv(dev);
   hdd_ap_ctx_t *pHddApCtx = WLAN_HDD_GET_AP_CTX_PTR(pAdapter);
   hdd_context_t *hddCtxt = WLAN_HDD_GET_CTX(pAdapter);
   tHalHandle hal = WLAN_HDD_GET_HAL_CTX(pAdapter);
   tpAniSirGlobal mac = PMAC_STRUCT(hal);
   v_MACADDR_t *pDestMacAddress;
   v_U8_t STAId;
   struct sk_buff *skb_next, *list_head = NULL, *list_tail = NULL;
   void *vdev_handle = NULL, *vdev_temp;
   bool is_update_ac_stats = FALSE;
#ifdef QCA_PKT_PROTO_TRACE
   v_U8_t proto_type = 0;
#endif /* QCA_PKT_PROTO_TRACE */

   ++pAdapter->hdd_stats.hddTxRxStats.txXmitCalled;
   if (mac->sap_tx_off)
       goto drop_list;
   /* Prevent this function to be called during SSR since TL context may
      not be reinitialized at this time which will lead crash. */
   if (hddCtxt->isLogpInProgress)
   {
       VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO_HIGH,
                 "%s: LOGP in Progress. Ignore!!!", __func__);
       goto drop_list;
   }

   /*
    * If the device is operating on a DFS Channel
    * then check if SAP is in CAC WAIT state and
    * drop the packets. In CAC WAIT state device
    * is expected not to transmit any frames.
    * SAP starts Tx only after the BSS START is
    * done.
    */
   if (pHddApCtx->dfs_cac_block_tx)
   {
       goto drop_list;
   }

   while (skb) {
       skb_next = skb->next;
       /* memset skb control block */
       vos_mem_zero(skb->cb, sizeof(skb->cb));
       wlan_hdd_classify_pkt(skb);

       pDestMacAddress = (v_MACADDR_t*)skb->data;

       hdd_tsf_record_sk_for_skb(hddCtxt, skb);

/*
* The TCP TX throttling logic is changed a little after 3.19-rc1 kernel,
* the TCP sending limit will be smaller, which will throttle the TCP packets
* to the host driver. The TCP UP LINK throughput will drop heavily.
* In order to fix this issue, need to orphan the socket buffer asap, which will
* call skb's destructor to notify the TCP stack that the SKB buffer is
* unowned. And then the TCP stack will pump more packets to host driver.
*
* The TX packets might be dropped for UDP case in the iperf testing.
* So need to be protected by follow control.
*/
#ifdef QCA_LL_TX_FLOW_CT
#if (LINUX_VERSION_CODE > KERNEL_VERSION(3,19,0))
        //remove if condition for improving SCC TCP TX KPI
       //if (pAdapter->tx_flow_low_watermark > 0) {
           skb_orphan(skb);
       //}
#endif
#else
      /*
       * For PTP feature enabled system, need to orphan the socket buffer asap
       * otherwise the latency will become unacceptable
       */
      if (hdd_cfg_is_ptp_opt_enable(hddCtxt))
          skb_orphan(skb);
#endif

       if (vos_is_macaddr_broadcast( pDestMacAddress ) ||
           vos_is_macaddr_group(pDestMacAddress))
       {
           // The BC/MC station ID is assigned during BSS starting phase.
           // SAP will return the station ID used for BC/MC traffic.
           STAId = pHddApCtx->uBCStaId;
       }
       else
       {
           if (VOS_STATUS_SUCCESS !=
               hdd_softap_GetStaId(pAdapter, pDestMacAddress, &STAId)) {
               VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_WARN,
                        "%s: Failed to find the station id", __func__);
               goto drop_pkt;
           }

           if (STAId == HDD_WLAN_INVALID_STA_ID || STAId >= WLAN_MAX_STA_COUNT)
           {
               VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_WARN,
                          "%s: Failed to find right station", __func__);
               goto drop_pkt;
           }
           else if (FALSE == pAdapter->aStaInfo[STAId].isUsed )
           {
               VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_WARN,
                    "%s: STA %d is unregistered", __func__, STAId);
               goto drop_pkt;
           }

           if ((WLANTL_STA_CONNECTED !=
                pAdapter->aStaInfo[STAId].tlSTAState) &&
               (WLANTL_STA_AUTHENTICATED !=
                pAdapter->aStaInfo[STAId].tlSTAState) )
           {
               VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_WARN,
                          "%s: Station not connected yet", __func__);
               goto drop_pkt;
           }
           else if(WLANTL_STA_CONNECTED ==
                   pAdapter->aStaInfo[STAId].tlSTAState)
           {
               if(ntohs(skb->protocol) != HDD_ETHERTYPE_802_1_X)
               {
                   VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_WARN,
                   "%s: NON-EAPOL packet in non-Authenticated state", __func__);
                   goto drop_pkt;
               }
           }
       }

       vdev_temp = tlshim_peer_validity(hddCtxt->pvosContext, STAId);
       if (!vdev_temp)
           goto drop_pkt;

       vdev_handle = vdev_temp;

#ifdef QCA_LL_TX_FLOW_CT
       if ((pAdapter->hdd_stats.hddTxRxStats.is_txflow_paused != TRUE) &&
            VOS_FALSE ==
              WLANTL_GetTxResource(hddCtxt->pvosContext,
				   pAdapter->sessionId,
				   pAdapter->tx_flow_low_watermark,
				   pAdapter->tx_flow_high_watermark_offset)) {
           if ((pAdapter->tx_flow_timer_initialized == TRUE) &&
               (VOS_TIMER_STATE_STOPPED ==
                vos_timer_getCurrentState(&pAdapter->tx_flow_control_timer))) {
               hddLog(LOG1, FL("Disabling queues"));
               wlan_hdd_netif_queue_control(pAdapter, WLAN_STOP_ALL_NETIF_QUEUE,
                           WLAN_DATA_FLOW_CONTROL);
               vos_timer_start(&pAdapter->tx_flow_control_timer,
                               WLAN_SAP_HDD_TX_FLOW_CONTROL_OS_Q_BLOCK_TIME);
               pAdapter->hdd_stats.hddTxRxStats.txflow_timer_cnt++;
               pAdapter->hdd_stats.hddTxRxStats.txflow_pause_cnt++;
               pAdapter->hdd_stats.hddTxRxStats.is_txflow_paused = TRUE;
           }
       }
#endif /* QCA_LL_TX_FLOW_CT */

       //Get TL AC corresponding to Qdisc queue index/AC.
       ac = hdd_QdiscAcToTlAC[skb->queue_mapping];
       ++pAdapter->hdd_stats.hddTxRxStats.txXmitClassifiedAC[ac];

#ifdef QCA_PKT_PROTO_TRACE
       if ((hddCtxt->cfg_ini->gEnableDebugLog & VOS_PKT_TRAC_TYPE_EAPOL) ||
           (hddCtxt->cfg_ini->gEnableDebugLog & VOS_PKT_TRAC_TYPE_DHCP))
       {
           /* Proto Trace enabled */
           proto_type = vos_pkt_get_proto_type(skb,
                        hddCtxt->cfg_ini->gEnableDebugLog, 0);
           switch (proto_type) {
           case VOS_PKT_TRAC_TYPE_EAPOL:
               vos_pkt_trace_buf_update("HA:T:EPL");
               break;
           case VOS_PKT_TRAC_TYPE_DHCP:
               hdd_dhcp_pkt_trace_buf_update(skb, TX_PATH, AP);
               break;
           case VOS_PKT_TRAC_TYPE_ARP:
               vos_pkt_trace_buf_update("HA:T:ARP");
               break;
           case VOS_PKT_TRAC_TYPE_NS:
               vos_pkt_trace_buf_update("HA:T:NS");
               break;
           case VOS_PKT_TRAC_TYPE_NA:
               vos_pkt_trace_buf_update("HA:T:NA");
               break;
           default:
               break;
           }
       }
#endif /* QCA_PKT_PROTO_TRACE */

       pAdapter->stats.tx_bytes += skb->len;
       ++pAdapter->stats.tx_packets;

       /* Per peer tx/rx statistics */
       pAdapter->aStaInfo[STAId].tx_packets++;
       pAdapter->aStaInfo[STAId].tx_bytes += skb->len;
       pAdapter->aStaInfo[STAId].last_tx_rx_ts = vos_system_ticks();

       if (!list_head) {
           list_head = skb;
           list_tail = skb;
       } else {
           list_tail->next = skb;
           list_tail = list_tail->next;
       }

       adf_dp_trace_log_pkt(pAdapter->sessionId, skb, ADF_TX);
       NBUF_SET_PACKET_TRACK(skb, NBUF_TX_PKT_DATA_TRACK);
       NBUF_UPDATE_TX_PKT_COUNT(skb, NBUF_TX_PKT_HDD);

       adf_dp_trace_set_track(skb, ADF_TX);
       DPTRACE(adf_dp_trace(skb, ADF_DP_TRACE_HDD_TX_PACKET_PTR_RECORD,
                  (uint8_t *)&skb->data, sizeof(skb->data), ADF_TX));
       DPTRACE(adf_dp_trace(skb, ADF_DP_TRACE_HDD_TX_PACKET_RECORD,
                  (uint8_t *)skb->data, adf_nbuf_len(skb), ADF_TX));

       if (adf_nbuf_len(skb) > ADF_DP_TRACE_RECORD_SIZE)
            DPTRACE(adf_dp_trace(skb, ADF_DP_TRACE_HDD_TX_PACKET_RECORD,
                    (uint8_t *)&skb->data[ADF_DP_TRACE_RECORD_SIZE],
                    (adf_nbuf_len(skb) - ADF_DP_TRACE_RECORD_SIZE),
                    ADF_TX));

       skb = skb_next;
       continue;

drop_pkt:
       hdd_drop_skb(pAdapter, skb);
       skb = skb_next;

   } /* end of while */

   if (!vdev_handle) {
       VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                 "%s: All packets dropped in the list", __func__);
       return NETDEV_TX_OK;
   }

   list_tail->next = NULL;

   skb = WLANTL_SendSTA_DataFrame(hddCtxt->pvosContext, vdev_handle, list_head
#ifdef QCA_PKT_PROTO_TRACE
                                 , proto_type
#endif /* QCA_PKT_PROTO_TRACE */
                                 );
   if (skb != NULL) {
       VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_WARN,
                  "%s: Failed to send packet to txrx",
                  __func__);
       is_update_ac_stats = TRUE;
       goto drop_list;
   }
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0))
   netif_trans_update(dev);
#else
   dev->trans_start = jiffies;
#endif
   return NETDEV_TX_OK;

drop_list:
   hdd_drop_skb_list(pAdapter, skb, is_update_ac_stats);
   return NETDEV_TX_OK;

}

int hdd_softap_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
	int ret;

	vos_ssr_protect(__func__);
	ret = __hdd_softap_hard_start_xmit(skb, dev);
	vos_ssr_unprotect(__func__);
	return ret;
}

/**
 * __hdd_softap_tx_timeout() - softap tx timeout
 * @dev: pointer to net_device
 *
 * Function called by OS if there is any timeout during transmission.
 * Since HDD simply enqueues packet and returns control to OS right away,
 * this would never be invoked.
 *
 * Return: none
 */
static void __hdd_softap_tx_timeout(struct net_device *dev)
{
   hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
   hdd_context_t *hdd_ctx;
   struct netdev_queue *txq;
   int i = 0;

   DPTRACE(adf_dp_trace(NULL, ADF_DP_TRACE_HDD_SOFTAP_TX_TIMEOUT,
                        NULL, 0, ADF_TX));

   hdd_ctx = WLAN_HDD_GET_CTX(adapter);
   if (hdd_ctx->isLogpInProgress) {
       VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                 "%s: LOGP in Progress. Ignore!!!", __func__);
       return;
   }

   /*
    * Getting here implies we disabled the TX queues for too long. Queues are
    * disabled either because of disassociation or low resource scenarios. In
    * case of disassociation it is ok to ignore this. But if associated, we have
    * do possible recovery here.
    */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0))
    VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
        "%s: Transmission timeout occurred jiffies %lu",
        __func__, jiffies);
#else
    VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
        "%s: Transmission timeout occurred jiffies %lu trans_start %lu",
        __func__, jiffies, dev->trans_start);
#endif
    for (i = 0; i < NUM_TX_QUEUES; i++) {
        txq = netdev_get_tx_queue(dev, i);
        VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA,
             VOS_TRACE_LEVEL_ERROR,
             "Queue%d status: %d txq->trans_start %lu",
             i, netif_tx_queue_stopped(txq), txq->trans_start);
    }
    wlan_display_tx_timeout_stats(adapter);
}

/**
 * hdd_softap_tx_timeout() - SSR wrapper for __hdd_softap_tx_timeout
 * @dev: pointer to net_device
 *
 * Return: none
 */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,5,0))
void hdd_softap_tx_timeout(struct net_device *dev, unsigned int txqueue)
#else
void hdd_softap_tx_timeout(struct net_device *dev)
#endif
{
	vos_ssr_protect(__func__);
	__hdd_softap_tx_timeout(dev);
	vos_ssr_unprotect(__func__);
}

/**
 * __hdd_softap_stats() - get softap tx/rx stats
 * @dev: pointer to net_device
 *
 * Function registered with the Linux OS for device TX/RX statistics.
 *
 * Return: pointer to net_device stats
 */
static struct net_device_stats *__hdd_softap_stats(struct net_device *dev)
{
	hdd_adapter_t *priv = netdev_priv(dev);
	return &priv->stats;
}

/**
 * hdd_softap_stats() - SSR wrapper function for __hdd_softap_stats
 * @dev: pointer to net_device
 *
 * Return: pointer to net_device stats
 */
struct net_device_stats* hdd_softap_stats(struct net_device *dev)
{
	struct net_device_stats *priv_stats;

	vos_ssr_protect(__func__);
	priv_stats = __hdd_softap_stats(dev);
	vos_ssr_unprotect(__func__);

	return priv_stats;
}

/**============================================================================
  @brief hdd_softap_init_tx_rx() - Init function to initialize Tx/RX
  modules in HDD

  @param pAdapter : [in] pointer to adapter context
  @return         : VOS_STATUS_E_FAILURE if any errors encountered
                  : VOS_STATUS_SUCCESS otherwise
  ===========================================================================*/
VOS_STATUS hdd_softap_init_tx_rx( hdd_adapter_t *pAdapter )
{
   VOS_STATUS status = VOS_STATUS_SUCCESS;
   v_SINT_t i = -1;
   v_SIZE_t size = 0;

   v_U8_t STAId = 0;

   pAdapter->isVosOutOfResource = VOS_FALSE;
   pAdapter->isVosLowResource = VOS_FALSE;

   vos_mem_zero(&pAdapter->stats, sizeof(struct net_device_stats));

   while (++i != NUM_TX_QUEUES)
      hdd_list_init( &pAdapter->wmm_tx_queue[i], HDD_TX_QUEUE_MAX_LEN);

   /* Initial HDD buffer control / flow control fields*/
   vos_pkt_get_available_buffer_pool (VOS_PKT_TYPE_TX_802_3_DATA, &size);

   pAdapter->aTxQueueLimit[WLANTL_AC_BK] = HDD_SOFTAP_TX_BK_QUEUE_MAX_LEN;
   pAdapter->aTxQueueLimit[WLANTL_AC_BE] = HDD_SOFTAP_TX_BE_QUEUE_MAX_LEN;
   pAdapter->aTxQueueLimit[WLANTL_AC_VI] = HDD_SOFTAP_TX_VI_QUEUE_MAX_LEN;
   pAdapter->aTxQueueLimit[WLANTL_AC_VO] = HDD_SOFTAP_TX_VO_QUEUE_MAX_LEN;

   adf_os_spinlock_init( &pAdapter->staInfo_lock );

   for (STAId = 0; STAId < WLAN_MAX_STA_COUNT; STAId++)
   {
      vos_mem_zero(&pAdapter->aStaInfo[STAId], sizeof(hdd_station_info_t));
      for (i = 0; i < NUM_TX_QUEUES; i ++)
      {
         hdd_list_init(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i], HDD_TX_QUEUE_MAX_LEN);
      }
   }

   return status;
}

/**============================================================================
  @brief hdd_softap_deinit_tx_rx() - Deinit function to clean up Tx/RX
  modules in HDD

  @param pAdapter : [in] pointer to adapter context
  @return         : VOS_STATUS_E_FAILURE if any errors encountered
                  : VOS_STATUS_SUCCESS otherwise
  ===========================================================================*/
VOS_STATUS hdd_softap_deinit_tx_rx( hdd_adapter_t *pAdapter )
{
   VOS_STATUS status = VOS_STATUS_SUCCESS;

   status = hdd_softap_flush_tx_queues(pAdapter);

   return status;
}

/**============================================================================
  @brief hdd_softap_flush_tx_queues_sta() - Utility function to flush the TX queues of a station

  @param pAdapter : [in] pointer to adapter context
  @param STAId    : [in] Station ID to deinit
  @return         : void
  ===========================================================================*/
static void hdd_softap_flush_tx_queues_sta( hdd_adapter_t *pAdapter, v_U8_t STAId )
{
   v_U8_t i = -1;

   hdd_list_node_t *anchor = NULL;

   skb_list_node_t *pktNode = NULL;
   struct sk_buff *skb = NULL;

   if (FALSE == pAdapter->aStaInfo[STAId].isUsed)
      return;

   for (i = 0; i < NUM_TX_QUEUES; i ++)
   {
      adf_os_spin_lock_bh(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i].lock);
      while (true)
      {
         if (VOS_STATUS_E_EMPTY !=
            hdd_list_remove_front(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i],
                                 &anchor))
         {
            //If success then we got a valid packet from some AC
            pktNode = list_entry(anchor, skb_list_node_t, anchor);
            skb = pktNode->skb;
            ++pAdapter->stats.tx_dropped;
            kfree_skb(skb);
            continue;
         }

         //current list is empty
         break;
      }
      adf_os_spin_unlock_bh(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i].lock);
   }

   return;
}

/**============================================================================
  @brief hdd_softap_init_tx_rx_sta() - Init function to initialize a station in Tx/RX
  modules in HDD

  @param pAdapter : [in] pointer to adapter context
  @param STAId    : [in] Station ID to deinit
  @param pmacAddrSTA  : [in] pointer to the MAC address of the station
  @return         : VOS_STATUS_E_FAILURE if any errors encountered
                  : VOS_STATUS_SUCCESS otherwise
  ===========================================================================*/
VOS_STATUS hdd_softap_init_tx_rx_sta( hdd_adapter_t *pAdapter, v_U8_t STAId, v_MACADDR_t *pmacAddrSTA)
{
   v_U8_t i = 0;
   adf_os_spin_lock_bh( &pAdapter->staInfo_lock );
   if (pAdapter->aStaInfo[STAId].isUsed)
   {
      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                 "%s: Reinit station %d", __func__, STAId );
      adf_os_spin_unlock_bh( &pAdapter->staInfo_lock );
      return VOS_STATUS_E_FAILURE;
   }

   vos_mem_zero(&pAdapter->aStaInfo[STAId], sizeof(hdd_station_info_t));
   for (i = 0; i < NUM_TX_QUEUES; i ++)
   {
      hdd_list_init(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i], HDD_TX_QUEUE_MAX_LEN);
   }

   pAdapter->aStaInfo[STAId].isUsed = TRUE;
   pAdapter->aStaInfo[STAId].isDeauthInProgress = FALSE;
   vos_copy_macaddr( &pAdapter->aStaInfo[STAId].macAddrSTA, pmacAddrSTA);

   adf_os_spin_unlock_bh( &pAdapter->staInfo_lock );
   return VOS_STATUS_SUCCESS;
}

/**============================================================================
  @brief hdd_softap_deinit_tx_rx_sta() - Deinit function to clean up a station in Tx/RX
  modules in HDD

  @param pAdapter : [in] pointer to adapter context
  @param STAId    : [in] Station ID to deinit
  @return         : VOS_STATUS_E_FAILURE if any errors encountered
                  : VOS_STATUS_SUCCESS otherwise
  ===========================================================================*/
VOS_STATUS hdd_softap_deinit_tx_rx_sta ( hdd_adapter_t *pAdapter, v_U8_t STAId )
{
   VOS_STATUS status = VOS_STATUS_SUCCESS;
   v_U8_t ac;
   /**Track whether OS TX queue has been disabled.*/
   v_BOOL_t txSuspended[NUM_TX_QUEUES];
   v_U8_t tlAC;
   hdd_hostapd_state_t *pHostapdState;
   v_U8_t i;

   pHostapdState = WLAN_HDD_GET_HOSTAP_STATE_PTR(pAdapter);

   adf_os_spin_lock_bh( &pAdapter->staInfo_lock );
   if (FALSE == pAdapter->aStaInfo[STAId].isUsed)
   {
      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                 "%s: Deinit station not inited %d", __func__, STAId );
      adf_os_spin_unlock_bh( &pAdapter->staInfo_lock );
      return VOS_STATUS_E_FAILURE;
   }

   hdd_softap_flush_tx_queues_sta(pAdapter, STAId);

   pAdapter->aStaInfo[STAId].isUsed = FALSE;
   pAdapter->aStaInfo[STAId].isDeauthInProgress = FALSE;

   /* if this STA had any of its WMM TX queues suspended, then the
      associated queue on the network interface was disabled.  check
      to see if that is the case, in which case we need to re-enable
      the interface queue.  but we only do this if the BSS is running
      since, if the BSS is stopped, all of the interfaces have been
      stopped and should not be re-enabled */

   if (BSS_START == pHostapdState->bssState)
   {
      for (ac = HDD_LINUX_AC_VO; ac <= HDD_LINUX_AC_BK; ac++)
      {
         tlAC = hdd_QdiscAcToTlAC[ac];
         txSuspended[ac] = pAdapter->aStaInfo[STAId].txSuspended[tlAC];
      }
   }
   vos_mem_zero(&pAdapter->aStaInfo[STAId], sizeof(hdd_station_info_t));

   /* re-init spin lock, since netdev can still open adapter until
    * driver gets unloaded
    */
   for (i = 0; i < NUM_TX_QUEUES; i ++)
   {
      hdd_list_init(&pAdapter->aStaInfo[STAId].wmm_tx_queue[i],
                    HDD_TX_QUEUE_MAX_LEN);
   }

   if (BSS_START == pHostapdState->bssState)
   {
      for (ac = HDD_LINUX_AC_VO; ac <= HDD_LINUX_AC_BK; ac++)
      {
         if (txSuspended[ac])
         {
            VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                       "%s: TX queue re-enabled", __func__);
            netif_wake_subqueue(pAdapter->dev, ac);
         }
      }
   }

   adf_os_spin_unlock_bh( &pAdapter->staInfo_lock );
   return status;
}

/**============================================================================
  @brief hdd_softap_rx_packet_cbk() - Receive callback registered with TL.
  TL will call this to notify the HDD when one or more packets were
  received for a registered STA.

  @param vosContext      : [in] pointer to VOS context
  @param rxBuf           : [in] pointer to rx adf_nbuf
  @param staId           : [in] Station Id (Address 1 Index)

  @return                : VOS_STATUS_E_FAILURE if any errors encountered,
                         : VOS_STATUS_SUCCESS otherwise
  ===========================================================================*/
VOS_STATUS hdd_softap_rx_packet_cbk(v_VOID_t *vosContext,
                                    adf_nbuf_t rxBuf, v_U8_t staId)
{
   hdd_adapter_t *pAdapter = NULL;
   int rxstat;
   struct sk_buff *skb = NULL;
   hdd_context_t *pHddCtx = NULL;
#ifdef QCA_PKT_PROTO_TRACE
   v_U8_t proto_type = 0;
#endif /* QCA_PKT_PROTO_TRACE */
   struct sk_buff *skb_next;
   unsigned int cpu_index;

   //Sanity check on inputs
   if ((NULL == vosContext) || (NULL == rxBuf))
   {
      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,"%s: Null params being passed", __func__);
      return VOS_STATUS_E_FAILURE;
   }

   pHddCtx = (hdd_context_t *)vos_get_context( VOS_MODULE_ID_HDD, vosContext );
   if ( NULL == pHddCtx )
   {
      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,"%s: HDD adapter context is Null", __func__);
      return VOS_STATUS_E_FAILURE;
   }

   pAdapter = pHddCtx->sta_to_adapter[staId];
   if ((NULL == pAdapter) || (WLAN_HDD_ADAPTER_MAGIC != pAdapter->magic)) {
      hddLog(LOGE, FL("invalid adapter or adapter has invalid magic"));
      return VOS_STATUS_E_FAILURE;
   }

   if (WLAN_HDD_ADAPTER_MAGIC != pAdapter->magic) {
       VOS_TRACE(VOS_MODULE_ID_HDD_DATA, VOS_TRACE_LEVEL_FATAL,
          "Magic cookie(%x) for adapter sanity verification is invalid",
          pAdapter->magic);
       return VOS_STATUS_E_FAILURE;
   }

   if (!pAdapter->dev) {
       VOS_TRACE(VOS_MODULE_ID_HDD_DATA, VOS_TRACE_LEVEL_FATAL,
          "Invalid DEV(NULL) Drop packets");
       return VOS_STATUS_E_FAILURE;
   }

   // walk the chain until all are processed
   skb = (struct sk_buff *) rxBuf;

   while (NULL != skb) {
      skb_next = skb->next;
      skb->dev = pAdapter->dev;

      cpu_index = wlan_hdd_get_cpu();

      ++pAdapter->hdd_stats.hddTxRxStats.rxPackets[cpu_index];
      ++pAdapter->stats.rx_packets;
      pAdapter->stats.rx_bytes += skb->len;

      pAdapter->aStaInfo[staId].rx_packets++;
      pAdapter->aStaInfo[staId].rx_bytes += skb->len;
      pAdapter->aStaInfo[staId].last_tx_rx_ts = vos_system_ticks();

      DPTRACE(adf_dp_trace(skb,
              ADF_DP_TRACE_RX_HDD_PACKET_PTR_RECORD,
              adf_nbuf_data_addr(skb),
              sizeof(adf_nbuf_data(skb)), ADF_RX));
      DPTRACE(adf_dp_trace(skb,
              ADF_DP_TRACE_HDD_RX_PACKET_RECORD,
              (uint8_t *)skb->data, adf_nbuf_len(skb), ADF_RX));

      if (adf_nbuf_len(skb) > ADF_DP_TRACE_RECORD_SIZE)
          DPTRACE(adf_dp_trace(skb,
                  ADF_DP_TRACE_HDD_RX_PACKET_RECORD,
                  (uint8_t *)&skb->data[ADF_DP_TRACE_RECORD_SIZE],
                  (adf_nbuf_len(skb) - ADF_DP_TRACE_RECORD_SIZE),
                  ADF_RX));

#ifdef QCA_PKT_PROTO_TRACE
      if ((pHddCtx->cfg_ini->gEnableDebugLog & VOS_PKT_TRAC_TYPE_EAPOL) ||
          (pHddCtx->cfg_ini->gEnableDebugLog & VOS_PKT_TRAC_TYPE_DHCP)) {
         proto_type = vos_pkt_get_proto_type(skb,
                           pHddCtx->cfg_ini->gEnableDebugLog, 0);
         switch (proto_type) {
         case VOS_PKT_TRAC_TYPE_EAPOL:
             vos_pkt_trace_buf_update("HA:R:EPL");
             break;
         case VOS_PKT_TRAC_TYPE_DHCP:
             hdd_dhcp_pkt_trace_buf_update(skb, RX_PATH, AP);
             break;
         case VOS_PKT_TRAC_TYPE_ARP:
             vos_pkt_trace_buf_update("HA:R:ARP");
             break;
         case VOS_PKT_TRAC_TYPE_NS:
             vos_pkt_trace_buf_update("HA:R:NS");
             break;
         case VOS_PKT_TRAC_TYPE_NA:
             vos_pkt_trace_buf_update("HA:R:NA");
             break;
         default:
             break;
         }
      }
#endif /* QCA_PKT_PROTO_TRACE */

      if (pHddCtx->cfg_ini->gEnableSapEapolChecking) {
          if (adf_nbuf_is_eapol_pkt(skb)) {
              /* CR 2868053 */
              VOS_TRACE(VOS_MODULE_ID_HDD_DATA, VOS_TRACE_LEVEL_INFO,
                  "%s: QSV2020005, dev, mode=%d, session=%u, %s, addr (%pM)",
                  __FUNCTION__,
                  pAdapter->device_mode,
                  pAdapter->sessionId,
                  pAdapter->dev->name,
                  pAdapter->dev->dev_addr);
              VOS_TRACE(VOS_MODULE_ID_HDD_DATA, VOS_TRACE_LEVEL_INFO,
                  "%s:QSV2020005 pkt addr (%pM)",
                  __FUNCTION__,
                  skb->data);
              if (adf_os_mem_cmp(pAdapter->dev->dev_addr,
                skb->data, VOS_MAC_ADDR_SIZE)) {
                  /* CR 2868053, discard this EAPOL */
                  VOS_TRACE(VOS_MODULE_ID_HDD_DATA, VOS_TRACE_LEVEL_ERROR,
                      "%s:QSV2020005 discard invalid EAPOL frame, dev=%pM, "
                      "pkt_da=%pM",
                      __FUNCTION__,
                      pAdapter->dev->dev_addr,
                      skb->data);

                  adf_nbuf_free(skb);
                  skb = skb_next;
                  continue;
              }
          }
      }

      skb->protocol = eth_type_trans(skb, skb->dev);

      /**
       * Remove SKB from internal tracking table before submitting it
       * to stack.
       */
      adf_net_buf_debug_release_skb(skb);

      /*
       * If this is not a last packet on the chain
       * Just put packet into backlog queue, not scheduling RX sirq
       */
      if (skb->next) {
         rxstat = netif_rx(skb);
      } else {
         if ((pHddCtx->cfg_ini->rx_wakelock_timeout) &&
             (PACKET_BROADCAST != skb->pkt_type) &&
             (PACKET_MULTICAST != skb->pkt_type))
               vos_wake_lock_timeout_acquire(&pHddCtx->rx_wake_lock,
                              pHddCtx->cfg_ini->rx_wakelock_timeout,
                              WIFI_POWER_EVENT_WAKELOCK_HOLD_RX);
         /*
          * This is the last packet on the chain
          * Scheduling rx sirq
          */
         rxstat = netif_rx_ni(skb);
      }

      if (NET_RX_SUCCESS == rxstat)
         ++pAdapter->hdd_stats.hddTxRxStats.rxDelivered[cpu_index];
      else
         ++pAdapter->hdd_stats.hddTxRxStats.rxRefused[cpu_index];

      skb = skb_next;
   }
#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,11,0))
   pAdapter->dev->last_rx = jiffies;
#endif
   return VOS_STATUS_SUCCESS;
}

VOS_STATUS hdd_softap_DeregisterSTA( hdd_adapter_t *pAdapter, tANI_U8 staId )
{
    VOS_STATUS vosStatus = VOS_STATUS_SUCCESS;
    hdd_context_t *pHddCtx;
    v_U8_t i;

    if (NULL == pAdapter)
    {
        VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                    "%s: pAdapter is NULL", __func__);
        return VOS_STATUS_E_INVAL;
    }

    if (WLAN_HDD_ADAPTER_MAGIC != pAdapter->magic)
    {
        VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                    "%s: Invalid pAdapter magic", __func__);
        return VOS_STATUS_E_INVAL;
    }

    pHddCtx = (hdd_context_t*)(pAdapter->pHddCtx);
    //Clear station in TL and then update HDD data structures. This helps
    //to block RX frames from other station to this station.
    vosStatus = WLANTL_ClearSTAClient( pHddCtx->pvosContext, staId );
    if ( !VOS_IS_STATUS_SUCCESS( vosStatus ) )
    {
        VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                    "WLANTL_ClearSTAClient() failed to for staID %d.  "
                    "Status= %d [0x%08X]",
                    staId, vosStatus, vosStatus );
    }

    if (pAdapter->aStaInfo[staId].isUsed) {
        adf_os_spin_lock_bh( &pAdapter->staInfo_lock );
        vos_mem_zero(&pAdapter->aStaInfo[staId], sizeof(hdd_station_info_t));

        /* re-init spin lock, since netdev can still open adapter until
         * driver gets unloaded
         */
        for (i = 0; i < NUM_TX_QUEUES; i ++)
        {
            hdd_list_init(&pAdapter->aStaInfo[staId].wmm_tx_queue[i],
                          HDD_TX_QUEUE_MAX_LEN);
        }
        adf_os_spin_unlock_bh( &pAdapter->staInfo_lock );
   }
    pHddCtx->sta_to_adapter[staId] = NULL;

    return( vosStatus );
}

VOS_STATUS hdd_softap_RegisterSTA( hdd_adapter_t *pAdapter,
                                       v_BOOL_t fAuthRequired,
                                       v_BOOL_t fPrivacyBit,
                                       v_U8_t staId,
                                       v_U8_t ucastSig,
                                       v_U8_t bcastSig,
                                       v_MACADDR_t *pPeerMacAddress,
                                       v_BOOL_t fWmmEnabled )
{
   VOS_STATUS vosStatus = VOS_STATUS_E_FAILURE;
   WLAN_STADescType staDesc = {0};
   hdd_context_t *pHddCtx = pAdapter->pHddCtx;

   //eCsrEncryptionType connectedCipherAlgo;
   //v_BOOL_t  fConnected;

   /*
    * Clean up old entry if it is not cleaned up properly
   */
   if ( pAdapter->aStaInfo[staId].isUsed )
   {
      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                 "clean up old entry for STA %d", staId);
      hdd_softap_DeregisterSTA( pAdapter, staId );
   }

   /* Get the Station ID from the one saved during the association */

   staDesc.ucSTAId = staId;


   /*Save the pAdapter Pointer for this staId*/
   pHddCtx->sta_to_adapter[staId] = pAdapter;

   staDesc.wSTAType = WLAN_STA_SOFTAP;

   vos_mem_copy( staDesc.vSTAMACAddress.bytes, pPeerMacAddress->bytes,sizeof(pPeerMacAddress->bytes) );
   vos_mem_copy( staDesc.vBSSIDforIBSS.bytes, &pAdapter->macAddressCurrent,6 );
   vos_copy_macaddr( &staDesc.vSelfMACAddress, &pAdapter->macAddressCurrent );

   VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
              "register station");
   VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
              "station mac " MAC_ADDRESS_STR,
              MAC_ADDR_ARRAY(staDesc.vSTAMACAddress.bytes));
   VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
              "BSSIDforIBSS " MAC_ADDRESS_STR,
              MAC_ADDR_ARRAY(staDesc.vBSSIDforIBSS.bytes));
   VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
              "SOFTAP SELFMAC " MAC_ADDRESS_STR,
              MAC_ADDR_ARRAY(staDesc.vSelfMACAddress.bytes));

   vosStatus = hdd_softap_init_tx_rx_sta(pAdapter, staId, &staDesc.vSTAMACAddress);

   staDesc.ucQosEnabled = fWmmEnabled;
   VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
              "HDD SOFTAP register TL QoS_enabled=%d",
              staDesc.ucQosEnabled );

   staDesc.ucProtectedFrame = (v_U8_t)fPrivacyBit ;


   // For PRIMA UMA frame translation is not enable yet.
   staDesc.ucSwFrameTXXlation = 1;
   staDesc.ucSwFrameRXXlation = 1;
   staDesc.ucAddRmvLLC = 1;

   // Initialize signatures and state
   staDesc.ucUcastSig  = ucastSig;
   staDesc.ucBcastSig  = bcastSig;
   staDesc.ucInitState = fAuthRequired ?
      WLANTL_STA_CONNECTED : WLANTL_STA_AUTHENTICATED;

   staDesc.ucIsReplayCheckValid = VOS_FALSE;

   // Register the Station with TL...
   /* Incase Micro controller data path offload enabled,
    * All the traffic routed to WLAN host driver, do not need to
    * route IPA. It should be routed kernel network stack */
#if defined(IPA_OFFLOAD) && !defined(IPA_UC_OFFLOAD)
   if (hdd_ipa_is_enabled(pHddCtx))
      vosStatus = WLANTL_RegisterSTAClient(
                              (WLAN_HDD_GET_CTX(pAdapter))->pvosContext,
                              hdd_ipa_process_rxt,
                              &staDesc, 0);
   else
#endif
   vosStatus = WLANTL_RegisterSTAClient(
                              (WLAN_HDD_GET_CTX(pAdapter))->pvosContext,
                              hdd_softap_rx_packet_cbk,
                              &staDesc, 0);
   if ( !VOS_IS_STATUS_SUCCESS( vosStatus ) )
   {
      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                 "SOFTAP WLANTL_RegisterSTAClient() failed to register.  Status= %d [0x%08X]",
                 vosStatus, vosStatus );
      return vosStatus;
   }

   // if ( WPA ), tell TL to go to 'connected' and after keys come to the driver,
   // then go to 'authenticated'.  For all other authentication types (those that do
   // not require upper layer authentication) we can put TL directly into 'authenticated'
   // state.

   //VOS_ASSERT( fConnected );
   pAdapter->aStaInfo[staId].ucSTAId = staId;
   pAdapter->aStaInfo[staId].isQosEnabled = fWmmEnabled;

   if ( !fAuthRequired )
   {
      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                 "open/shared auth StaId= %d.  Changing TL state to AUTHENTICATED at Join time",
                  pAdapter->aStaInfo[staId].ucSTAId );

      // Connections that do not need Upper layer auth, transition TL directly
      // to 'Authenticated' state.
      vosStatus = WLANTL_ChangeSTAState( (WLAN_HDD_GET_CTX(pAdapter))->pvosContext, staDesc.ucSTAId,
                                         WLANTL_STA_AUTHENTICATED, VOS_FALSE);

      pAdapter->aStaInfo[staId].tlSTAState = WLANTL_STA_AUTHENTICATED;
      pAdapter->sessionCtx.ap.uIsAuthenticated = VOS_TRUE;
   }
   else
   {

      VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                 "ULA auth StaId= %d.  Changing TL state to CONNECTED at Join time", pAdapter->aStaInfo[staId].ucSTAId );

      vosStatus = WLANTL_ChangeSTAState( (WLAN_HDD_GET_CTX(pAdapter))->pvosContext, staDesc.ucSTAId,
                                         WLANTL_STA_CONNECTED, VOS_FALSE);
      pAdapter->aStaInfo[staId].tlSTAState = WLANTL_STA_CONNECTED;

      pAdapter->sessionCtx.ap.uIsAuthenticated = VOS_FALSE;

   }

   //Enable Tx queue
   hddLog(LOG1, FL("Enabling queues"));
   wlan_hdd_netif_queue_control(pAdapter,
        WLAN_START_ALL_NETIF_QUEUE_N_CARRIER,
        WLAN_CONTROL_PATH);
   return( vosStatus );
}

VOS_STATUS hdd_softap_Register_BC_STA( hdd_adapter_t *pAdapter, v_BOOL_t fPrivacyBit)
{
   VOS_STATUS vosStatus = VOS_STATUS_E_FAILURE;
   hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
   v_MACADDR_t broadcastMacAddr = VOS_MAC_ADDR_BROADCAST_INITIALIZER;
   hdd_ap_ctx_t *pHddApCtx;

   pHddApCtx = WLAN_HDD_GET_AP_CTX_PTR(pAdapter);

   pHddCtx->sta_to_adapter[WLAN_RX_BCMC_STA_ID] = pAdapter;
#ifdef WLAN_FEATURE_MBSSID
   pHddCtx->sta_to_adapter[pHddApCtx->uBCStaId] = pAdapter;
#else
   pHddCtx->sta_to_adapter[WLAN_RX_SAP_SELF_STA_ID] = pAdapter;
#endif
   vosStatus = hdd_softap_RegisterSTA( pAdapter, VOS_FALSE, fPrivacyBit, (WLAN_HDD_GET_AP_CTX_PTR(pAdapter))->uBCStaId, 0, 1, &broadcastMacAddr,0);

   return vosStatus;
}

VOS_STATUS hdd_softap_Deregister_BC_STA( hdd_adapter_t *pAdapter)
{
   return hdd_softap_DeregisterSTA( pAdapter, (WLAN_HDD_GET_AP_CTX_PTR(pAdapter))->uBCStaId);
}

VOS_STATUS hdd_softap_stop_bss( hdd_adapter_t *pAdapter)
{
    VOS_STATUS vosStatus = VOS_STATUS_E_FAILURE;
    v_U8_t staId = 0;
    hdd_context_t *pHddCtx;
    pHddCtx = WLAN_HDD_GET_CTX(pAdapter);

    /*bss deregister is not allowed during wlan driver loading or unloading*/
    if ((pHddCtx->isLoadInProgress) ||
        (pHddCtx->isUnloadInProgress))
    {
        VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                   "%s:Loading_unloading in Progress. Ignore!!!",__func__);
        return VOS_STATUS_E_PERM;
    }

    vosStatus = hdd_softap_Deregister_BC_STA( pAdapter);

    if (!VOS_IS_STATUS_SUCCESS(vosStatus))
    {
        VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                   "%s: Failed to deregister BC sta Id %d", __func__, (WLAN_HDD_GET_AP_CTX_PTR(pAdapter))->uBCStaId);
    }

    for (staId = 0; staId < WLAN_MAX_STA_COUNT; staId++)
    {
        if (pAdapter->aStaInfo[staId].isUsed)// This excludes BC sta as it is already deregistered
            vosStatus = hdd_softap_DeregisterSTA( pAdapter, staId);

        if (!VOS_IS_STATUS_SUCCESS(vosStatus))
        {
                VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                       "%s: Failed to deregister sta Id %d", __func__, staId);
        }
    }
    return vosStatus;
}

VOS_STATUS hdd_softap_change_STA_state( hdd_adapter_t *pAdapter, v_MACADDR_t *pDestMacAddress, WLANTL_STAStateType state)
{
    v_U8_t ucSTAId = WLAN_MAX_STA_COUNT;
    VOS_STATUS vosStatus = eHAL_STATUS_SUCCESS;
    v_CONTEXT_t pVosContext = (WLAN_HDD_GET_CTX(pAdapter))->pvosContext;

    VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
               "%s: enter", __func__);

    if (VOS_STATUS_SUCCESS != hdd_softap_GetStaId(pAdapter, pDestMacAddress, &ucSTAId))
    {
        VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                    "%s: Failed to find right station", __func__);
        return VOS_STATUS_E_FAILURE;
    }

    if (FALSE == vos_is_macaddr_equal(&pAdapter->aStaInfo[ucSTAId].macAddrSTA, pDestMacAddress))
    {
         VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_ERROR,
                    "%s: Station MAC address does not matching", __func__);
         return VOS_STATUS_E_FAILURE;
    }

    vosStatus = WLANTL_ChangeSTAState( pVosContext, ucSTAId, state, VOS_FALSE);
    VOS_TRACE( VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                   "%s: change station to state %d succeed", __func__, state);

    if (VOS_STATUS_SUCCESS == vosStatus)
    {
       pAdapter->aStaInfo[ucSTAId].tlSTAState = WLANTL_STA_AUTHENTICATED;
    }

    VOS_TRACE(VOS_MODULE_ID_HDD_SAP_DATA, VOS_TRACE_LEVEL_INFO,
                                     "%s exit", __func__);

    return vosStatus;
}


VOS_STATUS hdd_softap_GetStaId(hdd_adapter_t *pAdapter, v_MACADDR_t *pMacAddress, v_U8_t *staId)
{
    v_U8_t i;

    for (i = 0; i < WLAN_MAX_STA_COUNT; i++)
    {
        if (vos_mem_compare(&pAdapter->aStaInfo[i].macAddrSTA, pMacAddress, sizeof(v_MACADDR_t)) &&
            pAdapter->aStaInfo[i].isUsed)
        {
            *staId = i;
            return VOS_STATUS_SUCCESS;
        }
    }

    return VOS_STATUS_E_FAILURE;
}

