/*
 * Copyright (c) 2011-2015, 2017 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.
 */

/*
 * This file contains TSPEC and STA admit control related functions
 * NOTE: applies only to AP builds
 *
 * Author:      Sandesh Goel
 * Date:        02/25/02
 * History:-
 * Date            Modified by    Modification Information
 * --------------------------------------------------------------------
 *
 */
#include "limDebug.h"
#include "sysDef.h"
#include "limApi.h"
#include "cfgApi.h" // wlan_cfgGetInt()
#include "limTrace.h"
#include "limSendSmeRspMessages.h"
#include "limTypes.h"


#define ADMIT_CONTROL_LOGLEVEL        LOG1
#define ADMIT_CONTROL_POLICY_LOGLEVEL LOG1

/* total available bandwidth in bps in each phy mode
 * these should be defined in hal or dph - replace these later
 */
#define LIM_TOTAL_BW_11A   54000000
#define LIM_MIN_BW_11A     6000000
#define LIM_TOTAL_BW_11B   11000000
#define LIM_MIN_BW_11B     1000000
#define LIM_TOTAL_BW_11G   LIM_TOTAL_BW_11A
#define LIM_MIN_BW_11G     LIM_MIN_BW_11B

// conversion factors
#define LIM_CONVERT_SIZE_BITS(numBytes) ((numBytes) * 8)
#define LIM_CONVERT_RATE_MBPS(rate)     ((rate)/1000000)


//------------------------------------------------------------------------------
// local protos

static tSirRetStatus
limCalculateSvcInt(tpAniSirGlobal, tSirMacTspecIE *, tANI_U32 *);
static tSirRetStatus
limValidateTspecEdca(tpAniSirGlobal, tSirMacTspecIE *, tpPESession);
static tSirRetStatus
limValidateTspec(tpAniSirGlobal, tSirMacTspecIE *, tpPESession);
static void
limComputeMeanBwUsed(tpAniSirGlobal, tANI_U32 *, tANI_U32, tpLimTspecInfo, tpPESession);
static void
limGetAvailableBw(tpAniSirGlobal, tANI_U32 *, tANI_U32 *, tANI_U32, tANI_U32);
static tSirRetStatus
limAdmitPolicyOversubscription(tpAniSirGlobal, tSirMacTspecIE *, tpLimAdmitPolicyInfo, tpLimTspecInfo, tpPESession);
static tSirRetStatus
limTspecFindByStaAddr(tpAniSirGlobal, tANI_U8 *, tSirMacTspecIE*, tpLimTspecInfo, tpLimTspecInfo *);
static tSirRetStatus
limValidateAccessPolicy(tpAniSirGlobal, tANI_U8, tANI_U16, tpPESession);


/** -------------------------------------------------------------
\fn limCalculateSvcInt
\brief TSPEC validation and servcie interval determination
\param     tpAniSirGlobal    pMac
\param         tSirMacTspecIE *pTspec
\param         tANI_U32            *pSvcInt
\return eSirRetStatus - status of the comparison
  -------------------------------------------------------------*/

static tSirRetStatus
limCalculateSvcInt(
    tpAniSirGlobal  pMac,
    tSirMacTspecIE *pTspec,
    tANI_U32            *pSvcInt)
{
    tANI_U32 msduSz, dataRate;
    *pSvcInt = 0;

    // if a service interval is already specified, we are done
    if ((pTspec->minSvcInterval != 0) || (pTspec->maxSvcInterval != 0))
    {
        *pSvcInt = (pTspec->maxSvcInterval != 0)
                    ? pTspec->maxSvcInterval : pTspec->minSvcInterval;
        return eSIR_SUCCESS;
    }

    /* Masking off the fixed bits according to definition of MSDU size
     * in IEEE 802.11-2007 spec (section 7.3.2.30). Nominal MSDU size
     * is defined as:  Bit[0:14]=Size, Bit[15]=Fixed
     */
    if (pTspec->nomMsduSz != 0)
        msduSz = (pTspec->nomMsduSz & 0x7fff);
    else if (pTspec->maxMsduSz != 0)
        msduSz = pTspec->maxMsduSz;
    else
    {
        PELOGE(limLog(pMac, LOGE, FL("MsduSize not specified"));)
        return eSIR_FAILURE;
    }

    /* need to calculate a reasonable service interval
     * this is simply the msduSz/meanDataRate
     */
    if      (pTspec->meanDataRate != 0) dataRate = pTspec->meanDataRate;
    else if (pTspec->peakDataRate != 0) dataRate = pTspec->peakDataRate;
    else if (pTspec->minDataRate  != 0) dataRate = pTspec->minDataRate;
    else
    {
        PELOGE(limLog(pMac, LOGE, FL("DataRate not specified"));)
        return eSIR_FAILURE;
    }

    *pSvcInt = LIM_CONVERT_SIZE_BITS(msduSz) / LIM_CONVERT_RATE_MBPS(dataRate);
    return eSIR_FAILURE;
}

/** -------------------------------------------------------------
\fn limValidateTspecEdca
\brief validate the parameters in the edca tspec
         mandatory fields are derived from 11e Annex I (Table I.1)
\param   tpAniSirGlobal pMac
\param        tSirMacTspecIE *pTspec
\return eSirRetStatus - status
  -------------------------------------------------------------*/
static tSirRetStatus
limValidateTspecEdca(
    tpAniSirGlobal  pMac,
    tSirMacTspecIE *pTspec,
    tpPESession  psessionEntry)
{
    tANI_U32           maxPhyRate, minPhyRate;
    tANI_U32 phyMode;
    tSirRetStatus retval = eSIR_SUCCESS;

    limGetPhyMode(pMac, &phyMode, psessionEntry);

    limGetAvailableBw(pMac, &maxPhyRate, &minPhyRate, phyMode,
                      1 /* bandwidth mult factor */);
    // mandatory fields are derived from 11e Annex I (Table I.1)
    if ((pTspec->nomMsduSz    == 0) ||
        (pTspec->meanDataRate == 0) ||
        (pTspec->surplusBw    == 0) ||
        (pTspec->minPhyRate   == 0) ||
        (pTspec->minPhyRate   > maxPhyRate))
    {
        limLog(pMac, LOGW, FL("Invalid EDCA Tspec: NomMsdu %d, meanDataRate %d, surplusBw %d, minPhyRate %d"),
               pTspec->nomMsduSz, pTspec->meanDataRate, pTspec->surplusBw, pTspec->minPhyRate);
        retval = eSIR_FAILURE;
    }

    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("return status %d"), retval);
    return retval;
}

/** -------------------------------------------------------------
\fn limValidateTspec
\brief validate the offered tspec
\param   tpAniSirGlobal pMac
\param         tSirMacTspecIE *pTspec
\return eSirRetStatus - status
  -------------------------------------------------------------*/

static tSirRetStatus
limValidateTspec(
    tpAniSirGlobal  pMac,
    tSirMacTspecIE *pTspec,
     tpPESession psessionEntry)
{
    tSirRetStatus retval = eSIR_SUCCESS;
    switch (pTspec->tsinfo.traffic.accessPolicy)
    {
        case SIR_MAC_ACCESSPOLICY_EDCA:
            if ((retval = limValidateTspecEdca(pMac, pTspec, psessionEntry)) != eSIR_SUCCESS)
                PELOGW(limLog(pMac, LOGW, FL("EDCA tspec invalid"));)
            break;

        case SIR_MAC_ACCESSPOLICY_HCCA:
       case SIR_MAC_ACCESSPOLICY_BOTH:
         // TBD: should we support hybrid tspec as well?? for now, just fall through
        default:
            limLog(pMac, LOGW, FL("AccessType %d not supported"),
                   pTspec->tsinfo.traffic.accessPolicy);
            retval = eSIR_FAILURE;
            break;
    }
    return retval;
}

//-----------------------------------------------------------------------------
// Admit Control Policy


/** -------------------------------------------------------------
\fn limComputeMeanBwUsed
\brief determime the used/allocated bandwidth
\param   tpAniSirGlobal pMac
\param       tANI_U32              *pBw
\param       tANI_U32               phyMode
\param       tpLimTspecInfo    pTspecInfo
\return eSirRetStatus - status
  -------------------------------------------------------------*/

static void
limComputeMeanBwUsed(
    tpAniSirGlobal    pMac,
    tANI_U32              *pBw,
    tANI_U32               phyMode,
    tpLimTspecInfo    pTspecInfo,
    tpPESession psessionEntry)
{
    tANI_U32 ctspec;
    *pBw = 0;
    for (ctspec = 0; ctspec < LIM_NUM_TSPEC_MAX; ctspec++, pTspecInfo++)
    {
        if (pTspecInfo->inuse)
        {
            tpDphHashNode pSta = dphGetHashEntry(pMac, pTspecInfo->assocId, &psessionEntry->dph.dphHashTable);
            if (pSta == NULL)
            {
                // maybe we should delete the tspec??
                limLog(pMac, LOGE, FL("Tspec %d (assocId %d): dphNode not found"),
                       ctspec, pTspecInfo->assocId);
                continue;
            }
            *pBw += pTspecInfo->tspec.meanDataRate;
        }
    }
}

/** -------------------------------------------------------------
\fn limGetAvailableBw
\brief based on the phy mode and the bw_factor, determine the total bandwidth that
       can be supported
\param   tpAniSirGlobal pMac
\param       tANI_U32              *pMaxBw
\param       tANI_U32              *pMinBw
\param       tANI_U32               phyMode
\param       tANI_U32               bw_factor
\return eSirRetStatus - status
  -------------------------------------------------------------*/

static void
limGetAvailableBw(
    tpAniSirGlobal    pMac,
    tANI_U32              *pMaxBw,
    tANI_U32              *pMinBw,
    tANI_U32               phyMode,
    tANI_U32               bw_factor)
{
    switch (phyMode)
    {
        case WNI_CFG_PHY_MODE_11B:
            *pMaxBw = LIM_TOTAL_BW_11B;
            *pMinBw = LIM_MIN_BW_11B;
            break;

        case WNI_CFG_PHY_MODE_11A:
            *pMaxBw = LIM_TOTAL_BW_11A;
            *pMinBw = LIM_MIN_BW_11A;
            break;

        case WNI_CFG_PHY_MODE_11G:
        case WNI_CFG_PHY_MODE_NONE:
        default:
            *pMaxBw = LIM_TOTAL_BW_11G;
            *pMinBw = LIM_MIN_BW_11G;
            break;
    }
    *pMaxBw *= bw_factor;
}

/** -------------------------------------------------------------
\fn limAdmitPolicyOversubscription
\brief simple admission control policy based on oversubscription
         if the total bandwidth of all admitted tspec's exceeds (factor * phy-bw) then
         reject the tspec, else admit it. The phy-bw is the peak available bw in the
         current phy mode. The 'factor' is the configured oversubscription factor.
\param   tpAniSirGlobal pMac
\param       tSirMacTspecIE       *pTspec
\param       tpLimAdmitPolicyInfo  pAdmitPolicy
\param       tpLimTspecInfo        pTspecInfo
\return eSirRetStatus - status
  -------------------------------------------------------------*/

/*
 * simple admission control policy based on oversubscription
 * if the total bandwidth of all admitted tspec's exceeds (factor * phy-bw) then
 * reject the tspec, else admit it. The phy-bw is the peak available bw in the
 * current phy mode. The 'factor' is the configured oversubscription factor.
 */
static tSirRetStatus
limAdmitPolicyOversubscription(
    tpAniSirGlobal        pMac,
    tSirMacTspecIE       *pTspec,
    tpLimAdmitPolicyInfo  pAdmitPolicy,
    tpLimTspecInfo        pTspecInfo,
    tpPESession psessionEntry)
{
    tANI_U32 totalbw, minbw, usedbw;
    tANI_U32 phyMode;

    // determine total bandwidth used so far
    limGetPhyMode(pMac, &phyMode, psessionEntry);

    limComputeMeanBwUsed(pMac, &usedbw, phyMode, pTspecInfo, psessionEntry);

    // determine how much bandwidth is available based on the current phy mode
    limGetAvailableBw(pMac, &totalbw, &minbw, phyMode, pAdmitPolicy->bw_factor);

    if (usedbw > totalbw) // this can't possibly happen
        return eSIR_FAILURE;

    if ((totalbw - usedbw) < pTspec->meanDataRate)
    {
        limLog(pMac, ADMIT_CONTROL_POLICY_LOGLEVEL,
               FL("Total BW %d, Used %d, Tspec request %d not possible"),
               totalbw, usedbw, pTspec->meanDataRate);
        return eSIR_FAILURE;
    }
    return eSIR_SUCCESS;
}

/** -------------------------------------------------------------
\fn limAdmitPolicy
\brief determine the current admit control policy and apply it for the offered tspec
\param   tpAniSirGlobal pMac
\param         tSirMacTspecIE   *pTspec
\return eSirRetStatus - status
  -------------------------------------------------------------*/

tSirRetStatus limAdmitPolicy(
    tpAniSirGlobal    pMac,
    tSirMacTspecIE   *pTspec,
    tpPESession psessionEntry)
{
    tSirRetStatus retval = eSIR_FAILURE;
    tpLimAdmitPolicyInfo pAdmitPolicy = &pMac->lim.admitPolicyInfo;

    switch (pAdmitPolicy->type)
    {
        case WNI_CFG_ADMIT_POLICY_ADMIT_ALL:
            retval = eSIR_SUCCESS;
            break;

        case WNI_CFG_ADMIT_POLICY_BW_FACTOR:
            retval = limAdmitPolicyOversubscription(pMac, pTspec,
                        &pMac->lim.admitPolicyInfo, &pMac->lim.tspecInfo[0], psessionEntry);
            if (retval != eSIR_SUCCESS)
                PELOGE(limLog(pMac, LOGE, FL("rejected by BWFactor policy"));)
            break;

        case WNI_CFG_ADMIT_POLICY_REJECT_ALL:
            retval = eSIR_FAILURE;
            break;

        default:
            retval = eSIR_SUCCESS;
            limLog(pMac, LOGE, FL("Admit Policy %d unknown, admitting all traffic"),
                   pAdmitPolicy->type);
            break;
    }
    return retval;
}

/** -------------------------------------------------------------
\fn limTspecDelete
\brief delete the specified tspec
\param   tpAniSirGlobal pMac
\param     tpLimTspecInfo pInfo
\return eSirRetStatus - status
  -------------------------------------------------------------*/

//-----------------------------------------------------------------------------
// delete the specified tspec
void limTspecDelete(tpAniSirGlobal pMac, tpLimTspecInfo pInfo)
{
    if (pInfo == NULL)
        return;
        //pierre
    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("tspec entry = %d"), pInfo->idx);
    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("delete tspec %pK"), pInfo);
    pInfo->inuse = 0;

    return;
}

/** -------------------------------------------------------------
\fn limTspecFindByStaAddr
\brief Send halMsg_AddTs to HAL
\param   tpAniSirGlobal pMac
\param   \param       tANI_U8               *pAddr
\param       tSirMacTspecIE    *pTspecIE
\param       tpLimTspecInfo    pTspecList
\param       tpLimTspecInfo   *ppInfo
\return eSirRetStatus - status
  -------------------------------------------------------------*/

// find the specified tspec in the list
static tSirRetStatus
limTspecFindByStaAddr(
    tpAniSirGlobal    pMac,
    tANI_U8               *pAddr,
    tSirMacTspecIE    *pTspecIE,
    tpLimTspecInfo    pTspecList,
    tpLimTspecInfo   *ppInfo)
{
    int ctspec;

    *ppInfo = NULL;

    for (ctspec = 0; ctspec < LIM_NUM_TSPEC_MAX; ctspec++, pTspecList++)
    {
        if ((pTspecList->inuse)
            && (vos_mem_compare(pAddr, pTspecList->staAddr, sizeof(pTspecList->staAddr)))
            && (vos_mem_compare((tANI_U8 *) pTspecIE, (tANI_U8 *) &pTspecList->tspec,
                                            sizeof(tSirMacTspecIE))))
        {
            *ppInfo = pTspecList;
            return eSIR_SUCCESS;
        }
    }
    return eSIR_FAILURE;
}

/** -------------------------------------------------------------
\fn limTspecFindByAssocId
\brief find tspec with matchin staid and Tspec
\param   tpAniSirGlobal pMac
\param       tANI_U32               staid
\param       tSirMacTspecIE    *pTspecIE
\param       tpLimTspecInfo    pTspecList
\param       tpLimTspecInfo   *ppInfo
\return eSirRetStatus - status
  -------------------------------------------------------------*/

tSirRetStatus
limTspecFindByAssocId(
    tpAniSirGlobal    pMac,
    tANI_U16               assocId,
    tSirMacTspecIE *pTspecIE,
    tpLimTspecInfo    pTspecList,
    tpLimTspecInfo   *ppInfo)
{
    int ctspec;

    *ppInfo = NULL;

    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("Trying to find tspec entry for assocId = %d"), assocId);
    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("pTsInfo->traffic.direction = %d, pTsInfo->traffic.tsid = %d"),
                pTspecIE->tsinfo.traffic.direction, pTspecIE->tsinfo.traffic.tsid);

    for (ctspec = 0; ctspec < LIM_NUM_TSPEC_MAX; ctspec++, pTspecList++)
    {
        if ((pTspecList->inuse)
            && (assocId == pTspecList->assocId)
            && (vos_mem_compare((tANI_U8 *)pTspecIE, (tANI_U8 *)&pTspecList->tspec,
                sizeof(tSirMacTspecIE))))
        {
            *ppInfo = pTspecList;
            return eSIR_SUCCESS;
        }
    }
    return eSIR_FAILURE;
}

/** -------------------------------------------------------------
\fn limFindTspec
\brief finding a TSPEC entry with assocId, tsinfo.direction and tsinfo.tsid
\param    tANI_U16               assocId
\param     tpAniSirGlobal    pMac
\param     tSirMacTSInfo   *pTsInfo
\param         tpLimTspecInfo    pTspecList
\param         tpLimTspecInfo   *ppInfo
\return eSirRetStatus - status of the comparison
  -------------------------------------------------------------*/

tSirRetStatus
limFindTspec(
    tpAniSirGlobal    pMac,
    tANI_U16               assocId,
    tSirMacTSInfo   *pTsInfo,
    tpLimTspecInfo    pTspecList,
    tpLimTspecInfo   *ppInfo)
{
    int ctspec;

    *ppInfo = NULL;

    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("Trying to find tspec entry for assocId = %d"), assocId);
    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("pTsInfo->traffic.direction = %d, pTsInfo->traffic.tsid = %d"),
                pTsInfo->traffic.direction, pTsInfo->traffic.tsid);

    for (ctspec = 0; ctspec < LIM_NUM_TSPEC_MAX; ctspec++, pTspecList++)
    {
        if ((pTspecList->inuse)
            && (assocId == pTspecList->assocId)
            && (pTsInfo->traffic.direction == pTspecList->tspec.tsinfo.traffic.direction)
            && (pTsInfo->traffic.tsid == pTspecList->tspec.tsinfo.traffic.tsid))
        {
            *ppInfo = pTspecList;
            return eSIR_SUCCESS;
        }
    }
    return eSIR_FAILURE;
}

/** -------------------------------------------------------------
\fn limTspecAdd
\brief add or update the specified tspec to the tspec list
\param tpAniSirGlobal    pMac
\param tANI_U8               *pAddr
\param tANI_U16               assocId
\param tSirMacTspecIE   *pTspec
\param tANI_U32               interval
\param tpLimTspecInfo   *ppInfo

\return eSirRetStatus - status of the comparison
  -------------------------------------------------------------*/

tSirRetStatus limTspecAdd(
    tpAniSirGlobal    pMac,
    tANI_U8           *pAddr,
    tANI_U16          assocId,
    tSirMacTspecIE    *pTspec,
    tANI_U32          interval,
    tpLimTspecInfo    *ppInfo)
{
    tpLimTspecInfo pTspecList = &pMac->lim.tspecInfo[0];
    *ppInfo = NULL;

    // validate the assocId
    if (assocId >= pMac->lim.maxStation)
    {
        PELOGE(limLog(pMac, LOGE, FL("Invalid assocId 0x%x"), assocId);)
        return eSIR_FAILURE;
    }

    //decide whether to add/update
    {
      *ppInfo = NULL;

      if(eSIR_SUCCESS == limFindTspec(pMac, assocId, &pTspec->tsinfo, pTspecList, ppInfo))
      {
            //update this entry.
            limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("updating TSPEC table entry = %d"),
                        (*ppInfo)->idx);
      }
      else
      {
          /* We didn't find one to update. So find a free slot in the
           * LIM TSPEC list and add this new entry
           */
          tANI_U8 ctspec = 0;
          for (ctspec = 0 , pTspecList = &pMac->lim.tspecInfo[0]; ctspec < LIM_NUM_TSPEC_MAX; ctspec++, pTspecList++)
          {
              if (! pTspecList->inuse)
              {
                  limLog(pMac, LOG1, FL("Found free slot in TSPEC list. Add to TSPEC table entry %d"), ctspec);
                  break;
              }
          }

          if (ctspec >= LIM_NUM_TSPEC_MAX)
              return eSIR_FAILURE;

          //Record the new index entry
          pTspecList->idx = ctspec;
      }
    }

    // update the tspec info
    pTspecList->tspec = *pTspec;
    pTspecList->assocId = assocId;
    vos_mem_copy(pTspecList->staAddr, pAddr, sizeof(pTspecList->staAddr));

    // for edca tspec's, we are all done
    if (pTspec->tsinfo.traffic.accessPolicy == SIR_MAC_ACCESSPOLICY_EDCA)
    {
        pTspecList->inuse = 1;
        *ppInfo = pTspecList;
        limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("added entry for EDCA AccessPolicy"));
        return eSIR_SUCCESS;
    }

    /*
     * for hcca tspec's, must set the parameterized bit in the queues
     * the 'ts' bit in the queue data structure indicates that the queue is
     * parameterized (hcca). When the schedule is written this bit is used
     * in the tsid field (bit 3) and the other three bits (0-2) are simply
     * filled in as the user priority (or qid). This applies only to uplink
     * polls where the qos control field must contain the tsid specified in the
     * tspec.
     */
    pTspecList->inuse = 1;
    *ppInfo = pTspecList;
    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("added entry for HCCA AccessPolicy"));
    return eSIR_SUCCESS;
}

/** -------------------------------------------------------------
\fn limValidateAccessPolicy
\brief Validates Access policy
\param   tpAniSirGlobal pMac
\param       tANI_U8              accessPolicy
\param       tANI_U16             assocId
\return eSirRetStatus - status
  -------------------------------------------------------------*/

static tSirRetStatus
limValidateAccessPolicy(
    tpAniSirGlobal  pMac,
    tANI_U8              accessPolicy,
    tANI_U16              assocId,
    tpPESession psessionEntry)
{
    tSirRetStatus retval = eSIR_FAILURE;
    tpDphHashNode pSta = dphGetHashEntry(pMac, assocId, &psessionEntry->dph.dphHashTable);

    if ((pSta == NULL) || (! pSta->valid))
    {
        PELOGE(limLog(pMac, LOGE, FL("invalid station address passed"));)
        return eSIR_FAILURE;
    }

    switch (accessPolicy)
    {
        case SIR_MAC_ACCESSPOLICY_EDCA:
            if (pSta->wmeEnabled || pSta->lleEnabled)
                retval = eSIR_SUCCESS;
            break;

        case SIR_MAC_ACCESSPOLICY_HCCA:
        case SIR_MAC_ACCESSPOLICY_BOTH:
        default:
            PELOGE(limLog(pMac, LOGE, FL("Invalid accessPolicy %d"), accessPolicy);)
            break;
    }

    if (retval != eSIR_SUCCESS)
        limLog(pMac, LOGW, FL("failed (accPol %d, staId %d, lle %d, wme %d, wsm %d)"),
               accessPolicy, pSta->staIndex, pSta->lleEnabled, pSta->wmeEnabled, pSta->wsmEnabled);

    return retval;
}

/** -------------------------------------------------------------
\fn limAdmitControlAddTS
\brief Determine if STA with the specified TSPEC can be admitted. If it can,
     a schedule element is provided
\param   tpAniSirGlobal pMac
\param       tANI_U8                     *pAddr,
\param       tSirAddtsReqInfo       *pAddts,
\param       tSirMacQosCapabilityIE *pQos,
\param       tANI_U16                     assocId, // assocId, valid only if alloc==true
\param       tANI_U8                    alloc, // true=>allocate bw for this tspec,
                                   // else determine only if space is available
\param       tSirMacScheduleIE      *pSch,
\param       tANI_U8                   *pTspecIdx //index to the lim tspec table.
\return eSirRetStatus - status
  -------------------------------------------------------------*/

tSirRetStatus limAdmitControlAddTS(
    tpAniSirGlobal          pMac,
    tANI_U8                     *pAddr,
    tSirAddtsReqInfo       *pAddts,
    tSirMacQosCapabilityStaIE *pQos,
    tANI_U16                     assocId, // assocId, valid only if alloc==true
    tANI_U8                    alloc, // true=>allocate bw for this tspec,
                                   // else determine only if space is available
    tSirMacScheduleIE      *pSch,
    tANI_U8                   *pTspecIdx, //index to the lim tspec table.
    tpPESession psessionEntry
    )
{
    tpLimTspecInfo pTspecInfo;
    tSirRetStatus  retval;
    tANI_U32            svcInterval;
    (void) pQos;

    // TBD: modify tspec as needed
    // EDCA: need to fill in the medium time and the minimum phy rate
    // to be consistent with the desired traffic parameters.

    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("tsid %d, directn %d, start %d, intvl %d, accPolicy %d, up %d"),
           pAddts->tspec.tsinfo.traffic.tsid, pAddts->tspec.tsinfo.traffic.direction,
           pAddts->tspec.svcStartTime, pAddts->tspec.minSvcInterval,
           pAddts->tspec.tsinfo.traffic.accessPolicy, pAddts->tspec.tsinfo.traffic.userPrio);

    // check for duplicate tspec
    retval = (alloc)
              ? limTspecFindByAssocId(pMac, assocId, &pAddts->tspec, &pMac->lim.tspecInfo[0], &pTspecInfo)
              : limTspecFindByStaAddr(pMac, pAddr, &pAddts->tspec, &pMac->lim.tspecInfo[0], &pTspecInfo);

    if (retval == eSIR_SUCCESS)
    {
        limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("duplicate tspec (index %d)!"), pTspecInfo->idx);
        return eSIR_FAILURE;
    }

    // check that the tspec's are well formed and acceptable
    if (limValidateTspec(pMac, &pAddts->tspec, psessionEntry) != eSIR_SUCCESS)
    {
        PELOGW(limLog(pMac, LOGW, FL("tspec validation failed"));)
        return eSIR_FAILURE;
    }

    // determine a service interval for the tspec
    if (limCalculateSvcInt(pMac, &pAddts->tspec, &svcInterval) != eSIR_SUCCESS)
    {
        PELOGW(limLog(pMac, LOGW, FL("SvcInt calculate failed"));)
        return eSIR_FAILURE;
    }

    // determine if the tspec can be admitted or not based on current policy
    if (limAdmitPolicy(pMac, &pAddts->tspec, psessionEntry) != eSIR_SUCCESS)
    {
        PELOGW(limLog(pMac, LOGW, FL("tspec rejected by admit control policy"));)
        return eSIR_FAILURE;
    }

    // fill in a schedule if requested
    if (pSch != NULL)
    {
        vos_mem_set((tANI_U8 *) pSch, sizeof(*pSch), 0);
        pSch->svcStartTime   = pAddts->tspec.svcStartTime;
        pSch->svcInterval    = svcInterval;
        pSch->maxSvcDuration = (tANI_U16) pSch->svcInterval; // use SP = SI
        pSch->specInterval   = 0x1000; // fixed for now: TBD

        pSch->info.direction   = pAddts->tspec.tsinfo.traffic.direction;
        pSch->info.tsid        = pAddts->tspec.tsinfo.traffic.tsid;
        pSch->info.aggregation = 0; // no support for aggregation for now: TBD
    }

    // if no allocation is requested, done
    if (! alloc)
        return eSIR_SUCCESS;

    // check that we are in the proper mode to deal with the tspec type
    if (limValidateAccessPolicy(pMac, (tANI_U8) pAddts->tspec.tsinfo.traffic.accessPolicy, assocId, psessionEntry) != eSIR_SUCCESS)
    {
        limLog(pMac, LOGW, FL("AccessPolicy %d is not valid in current mode"),
               pAddts->tspec.tsinfo.traffic.accessPolicy);
        return eSIR_FAILURE;
    }

    // add tspec to list
    if (limTspecAdd(pMac, pAddr, assocId, &pAddts->tspec, svcInterval, &pTspecInfo)
        != eSIR_SUCCESS)
    {
        PELOGE(limLog(pMac, LOGE, FL("no space in tspec list"));)
        return eSIR_FAILURE;
    }

    //passing lim tspec table index to the caller
    *pTspecIdx = pTspecInfo->idx;

    return eSIR_SUCCESS;
}

/** -------------------------------------------------------------
\fn limAdmitControlDeleteTS
\brief Delete the specified Tspec for the specified STA
\param   tpAniSirGlobal pMac
\param       tANI_U16               assocId
\param       tSirMacTSInfo    *pTsInfo
\param       tANI_U8               *pTsStatus
\param       tANI_U8             *ptspecIdx
\return eSirRetStatus - status
  -------------------------------------------------------------*/

tSirRetStatus
limAdmitControlDeleteTS(
    tpAniSirGlobal    pMac,
    tANI_U16               assocId,
    tSirMacTSInfo    *pTsInfo,
    tANI_U8               *pTsStatus,
    tANI_U8             *ptspecIdx)
{
    tpLimTspecInfo pTspecInfo = NULL;

    if (pTsStatus != NULL)
        *pTsStatus = 0;

    if (limFindTspec(pMac, assocId, pTsInfo, &pMac->lim.tspecInfo[0], &pTspecInfo) == eSIR_SUCCESS)
    {
        if(pTspecInfo != NULL)
        {
          limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("Tspec entry %d found"), pTspecInfo->idx);

          *ptspecIdx = pTspecInfo->idx;
          limTspecDelete(pMac, pTspecInfo);
          return eSIR_SUCCESS;
        }
    }
    return eSIR_FAILURE;
}

/** -------------------------------------------------------------
\fn limAdmitControlDeleteSta
\brief Delete all TSPEC for the specified STA
\param   tpAniSirGlobal pMac
\param     tANI_U16 assocId
\return eSirRetStatus - status
  -------------------------------------------------------------*/

tSirRetStatus
limAdmitControlDeleteSta(
    tpAniSirGlobal    pMac,
    tANI_U16 assocId)
{
    tpLimTspecInfo pTspecInfo = &pMac->lim.tspecInfo[0];
    int ctspec;

    for (ctspec = 0; ctspec < LIM_NUM_TSPEC_MAX; ctspec++, pTspecInfo++)
    {
        if (assocId == pTspecInfo->assocId)
        {
            limTspecDelete(pMac, pTspecInfo);
            limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("Deleting TSPEC %d for assocId %d"),
                   ctspec, assocId);
        }
    }
    limLog(pMac, ADMIT_CONTROL_LOGLEVEL, FL("assocId %d done"), assocId);

    return eSIR_SUCCESS;
}

/** -------------------------------------------------------------
\fn limAdmitControlInit
\brief init tspec table
\param   tpAniSirGlobal pMac
\return eSirRetStatus - status
  -------------------------------------------------------------*/
tSirRetStatus limAdmitControlInit(tpAniSirGlobal pMac)
{
    vos_mem_set(pMac->lim.tspecInfo, LIM_NUM_TSPEC_MAX * sizeof(tLimTspecInfo), 0);
    return eSIR_SUCCESS;
}

/** -------------------------------------------------------------
\fn limUpdateAdmitPolicy
\brief Set the admit control policy based on CFG parameters
\param   tpAniSirGlobal pMac
\return eSirRetStatus - status
  -------------------------------------------------------------*/

tSirRetStatus limUpdateAdmitPolicy(tpAniSirGlobal    pMac)
{
    tANI_U32 val;
    if (wlan_cfgGetInt(pMac, WNI_CFG_ADMIT_POLICY, &val) != eSIR_SUCCESS)
    {
        limLog(pMac, LOGP, FL("Unable to get CFG_ADMIT_POLICY"));
        return eSIR_FAILURE;
    }
    pMac->lim.admitPolicyInfo.type = (tANI_U8) val;
    if (wlan_cfgGetInt(pMac, WNI_CFG_ADMIT_BWFACTOR, &val) != eSIR_SUCCESS)
    {
        limLog(pMac, LOGP, FL("Unable to get CFG_ADMIT_BWFACTOR"));
        return eSIR_FAILURE;
    }
    pMac->lim.admitPolicyInfo.bw_factor = (tANI_U8) val;

    PELOG1(limLog(pMac, LOG1, FL("LIM: AdmitPolicy %d, bw_factor %d"),
          pMac->lim.admitPolicyInfo.type, pMac->lim.admitPolicyInfo.bw_factor);)

    return eSIR_SUCCESS;
}


/** -------------------------------------------------------------
\fn limSendHalMsgAddTs
\brief Send halMsg_AddTs to HAL
\param   tpAniSirGlobal pMac
\param     tANI_U16        staIdx
\param     tANI_U8         tspecIdx
\param       tSirMacTspecIE tspecIE
\param       tSirTclasInfo   *tclasInfo
\param       tANI_U8           tclasProc
\param       tANI_U16          tsm_interval
\return eSirRetStatus - status
  -------------------------------------------------------------*/
#ifdef FEATURE_WLAN_ESE
tSirRetStatus
limSendHalMsgAddTs(
  tpAniSirGlobal pMac,
  tANI_U16       staIdx,
  tANI_U8         tspecIdx,
  tSirMacTspecIE tspecIE,
  tANI_U8        sessionId,
  tANI_U16       tsm_interval)
#else
tSirRetStatus
limSendHalMsgAddTs(
  tpAniSirGlobal pMac,
  tANI_U16       staIdx,
  tANI_U8         tspecIdx,
  tSirMacTspecIE tspecIE,
  tANI_U8        sessionId)
#endif
{
    tSirMsgQ msg;
    tpAddTsParams pAddTsParam;
    tpPESession psessionEntry = peFindSessionBySessionId(pMac, sessionId);

    if (psessionEntry == NULL) {
       limLog( pMac, LOGP,
          FL("Unable to get Session for session Id %d"), sessionId);
       return eSIR_FAILURE;
    }

    pAddTsParam = vos_mem_malloc(sizeof(tAddTsParams));
    if (NULL == pAddTsParam)
    {
       PELOGW(limLog(pMac, LOGW, FL("AllocateMemory() failed"));)
       return eSIR_MEM_ALLOC_FAILED;
    }

    vos_mem_set((tANI_U8 *)pAddTsParam, sizeof(tAddTsParams), 0);
    pAddTsParam->staIdx = staIdx;
    pAddTsParam->tspecIdx = tspecIdx;
    vos_mem_copy(&pAddTsParam->tspec, &tspecIE, sizeof(tSirMacTspecIE));
    pAddTsParam->sessionId = sessionId;
    pAddTsParam->sme_session_id = psessionEntry->smeSessionId;

#ifdef FEATURE_WLAN_ESE
    pAddTsParam->tsm_interval = tsm_interval;
#endif

#ifdef WLAN_FEATURE_ROAM_OFFLOAD
    if (pMac->roam.configParam.isRoamOffloadEnabled &&
        psessionEntry->is11Rconnection)
        pAddTsParam->setRICparams = 1;
#endif

    msg.type = WDA_ADD_TS_REQ;
    msg.bodyptr = pAddTsParam;
    msg.bodyval = 0;

    /* We need to defer any incoming messages until we get a
     * WDA_ADD_TS_RSP from HAL.
     */
    SET_LIM_PROCESS_DEFD_MESGS(pMac, false);
    MTRACE(macTraceMsgTx(pMac, sessionId, msg.type));

    if(eSIR_SUCCESS != wdaPostCtrlMsg(pMac, &msg))
    {
       PELOGW(limLog(pMac, LOGW, FL("wdaPostCtrlMsg() failed"));)
       SET_LIM_PROCESS_DEFD_MESGS(pMac, true);
       vos_mem_free(pAddTsParam);
       return eSIR_FAILURE;
    }
  return eSIR_SUCCESS;
}

/** -------------------------------------------------------------
\fn limSendHalMsgDelTs
\brief Send halMsg_AddTs to HAL
\param   tpAniSirGlobal pMac
\param     tANI_U16        staIdx
\param     tANI_U8         tspecIdx
\param     tSirAddtsReqInfo addts
\return eSirRetStatus - status
  -------------------------------------------------------------*/

tSirRetStatus
limSendHalMsgDelTs(
  tpAniSirGlobal pMac,
  tANI_U16       staIdx,
  tANI_U8         tspecIdx,
  tSirDeltsReqInfo delts,
  tANI_U8        sessionId,
  tANI_U8        *bssId)
{
  tSirMsgQ msg;
  tpDelTsParams pDelTsParam;
  tpPESession psessionEntry = NULL;

  pDelTsParam = vos_mem_malloc(sizeof(tDelTsParams));
  if (NULL == pDelTsParam)
  {
     limLog(pMac, LOGP, FL("AllocateMemory() failed"));
     return eSIR_MEM_ALLOC_FAILED;
  }

  msg.type = WDA_DEL_TS_REQ;
  msg.bodyptr = pDelTsParam;
  msg.bodyval = 0;
  vos_mem_set((tANI_U8 *)pDelTsParam, sizeof(tDelTsParams), 0);

  //filling message parameters.
  pDelTsParam->staIdx = staIdx;
  pDelTsParam->tspecIdx = tspecIdx;
  vos_mem_copy(&pDelTsParam->bssId, bssId, sizeof(tSirMacAddr));

  psessionEntry = peFindSessionBySessionId(pMac, sessionId);
  if(psessionEntry == NULL)
  {
     PELOGE(limLog(pMac, LOGE,
              FL("Session does Not exist with given sessionId :%d "),
              sessionId);)
     goto err;
  }
  pDelTsParam->sessionId = psessionEntry->smeSessionId;
  pDelTsParam->userPrio = delts.wmeTspecPresent?
      delts.tspec.tsinfo.traffic.userPrio: delts.tsinfo.traffic.userPrio;

#ifdef WLAN_FEATURE_ROAM_OFFLOAD
  if (pMac->roam.configParam.isRoamOffloadEnabled &&
      psessionEntry->is11Rconnection) {
      vos_mem_copy(&pDelTsParam->delTsInfo, &delts, sizeof(tSirDeltsReqInfo));
      pDelTsParam->setRICparams = 1;
  }
#endif

  PELOGW(limLog(pMac, LOGW, FL("calling wdaPostCtrlMsg()"));)
  MTRACE(macTraceMsgTx(pMac, sessionId, msg.type));

  if(eSIR_SUCCESS != wdaPostCtrlMsg(pMac, &msg))
  {
     PELOGW(limLog(pMac, LOGW, FL("wdaPostCtrlMsg() failed"));)
     goto err;
  }
  return eSIR_SUCCESS;

err:
  vos_mem_free(pDelTsParam);
  return eSIR_FAILURE;
}

/** -------------------------------------------------------------
\fn     limProcessHalAddTsRsp
\brief  This function process the WDA_ADD_TS_RSP from HAL.
\       If response is successful, then send back SME_ADDTS_RSP.
\       Otherwise, send DELTS action frame to peer and then
\       then send back SME_ADDTS_RSP.
\
\param  tpAniSirGlobal  pMac
\param  tpSirMsgQ   limMsg
-------------------------------------------------------------*/
void limProcessHalAddTsRsp(tpAniSirGlobal pMac, tpSirMsgQ limMsg)
{
    tpAddTsParams  pAddTsRspMsg = NULL;
    tpDphHashNode  pSta = NULL;
    tANI_U16  assocId =0;
    tSirMacAddr  peerMacAddr;
    tANI_U8   rspReqd = 1;
    tpPESession  psessionEntry = NULL;


    /* Need to process all the deferred messages enqueued
     * since sending the WDA_ADD_TS_REQ.
     */
    SET_LIM_PROCESS_DEFD_MESGS(pMac, true);

    if (NULL == limMsg->bodyptr)
    {
        limLog(pMac, LOGP, FL("Received WDA_ADD_TS_RSP with NULL "));
        goto end;
    }

    pAddTsRspMsg = (tpAddTsParams) (limMsg->bodyptr);

    // 090803: Use peFindSessionBySessionId() to obtain the PE session context
    // from the sessionId in the Rsp Msg from HAL
    psessionEntry = peFindSessionBySessionId(pMac, pAddTsRspMsg->sessionId);

    if(psessionEntry == NULL)
    {
        PELOGE(limLog(pMac, LOGE,FL("Session does Not exist with given sessionId :%d "), pAddTsRspMsg->sessionId);)
        limSendSmeAddtsRsp(pMac, rspReqd, eSIR_SME_ADDTS_RSP_FAILED, psessionEntry, pAddTsRspMsg->tspec,
              pMac->lim.gLimAddtsReq.sessionId, pMac->lim.gLimAddtsReq.transactionId);
        goto end;
    }

    if(pAddTsRspMsg->status == eHAL_STATUS_SUCCESS)
    {
        PELOG1(limLog(pMac, LOG1, FL("Received successful ADDTS response from HAL "));)
        // Use the smesessionId and smetransactionId from the PE session context
        limSendSmeAddtsRsp(pMac, rspReqd, eSIR_SME_SUCCESS, psessionEntry, pAddTsRspMsg->tspec,
                psessionEntry->smeSessionId, psessionEntry->transactionId);
        goto end;
    }
    else
    {
        PELOG1(limLog(pMac, LOG1, FL("Received failure ADDTS response from HAL "));)

        // Send DELTS action frame to AP
        // 090803: Get peer MAC addr from session
        sirCopyMacAddr(peerMacAddr,psessionEntry->bssId);

        // 090803: Add the SME Session ID
        limSendDeltsReqActionFrame(pMac, peerMacAddr, rspReqd, &pAddTsRspMsg->tspec.tsinfo, &pAddTsRspMsg->tspec,
                psessionEntry);

        // Delete TSPEC
        // 090803: Pull the hash table from the session
        pSta = dphLookupAssocId(pMac, pAddTsRspMsg->staIdx, &assocId,
                &psessionEntry->dph.dphHashTable);
        if (pSta != NULL)
            limAdmitControlDeleteTS(pMac, assocId, &pAddTsRspMsg->tspec.tsinfo, NULL, (tANI_U8 *)&pAddTsRspMsg->tspecIdx);

        // Send SME_ADDTS_RSP
        // 090803: Use the smesessionId and smetransactionId from the PE session context
        limSendSmeAddtsRsp(pMac, rspReqd, eSIR_SME_ADDTS_RSP_FAILED, psessionEntry, pAddTsRspMsg->tspec,
                psessionEntry->smeSessionId, psessionEntry->transactionId);
        goto end;
   }

end:
    if (pAddTsRspMsg != NULL)
        vos_mem_free(pAddTsRspMsg);
    return;
}
