/******************************************************************************* ################################################################################ # Copyright (c) [2017-2019] [Radisys] # # # # Licensed under the Apache License, Version 2.0 (the "License"); # # you may not use this file except in compliance with the License. # # You may obtain a copy of the License at # # # # http://www.apache.org/licenses/LICENSE-2.0 # # # # Unless required by applicable law or agreed to in writing, software # # distributed under the License is distributed on an "AS IS" BASIS, # # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # # See the License for the specific language governing permissions and # # limitations under the License. # ################################################################################ *******************************************************************************/ /************************************************************************ Name: LTE-MAC layer Type: C source file Desc: C source code for power control functionality File: rg_sch_pwr.c **********************************************************************/ /** @file rg_sch_pwr.c @brief This module handles schedulers' power control functionality */ static const char* RLOG_MODULE_NAME="MAC"; static int RLOG_MODULE_ID=4096; static int RLOG_FILE_ID=188; /* header include files -- defines (.h) */ #include "common_def.h" #include "lrg.h" #include "rgr.h" #include "rgm.h" #include "tfu.h" #include "rg_env.h" #include "rg_sch_inf.h" #include "rg_sch_err.h" #include "rg_sch.h" #include "rg_sch_cmn.h" /* header/extern include files (.x) */ #include "tfu.x" /* RGU types */ #include "lrg.x" /* layer management typedefs for MAC */ #include "rgr.x" /* layer management typedefs for MAC */ #include "rgm.x" /* layer management typedefs for MAC */ #include "rg_sch_inf.x" /* typedefs for Scheduler */ #include "rg_sch.x" /* typedefs for Scheduler */ #include "rg_sch_cmn.x" #include "rl_interface.h" #include "rl_common.h" /* Current specs have 23 dBm as max tx power capability for UEs */ #define RG_SCH_PWR_UE_MAX_PWR 23 #define RG_SCH_REF_PCMAX 0xFF #define RG_SCH_CMN_GET_UL_UE(_ue,_cell) (&(((RgSchCmnUe *)((_ue->cellInfo[_ue->cellIdToCellIdxMap\ [RG_SCH_CELLINDEX(_cell)]])->sch))->ul)) #define RG_SCH_PWR_GETUEPWR(_ue, _cell) &(((RgSchCmnUe *)((_ue->cellInfo[_ue->cellIdToCellIdxMap\ [RG_SCH_CELLINDEX(_cell)]])->sch))->ul.ulPwrCb) #define RG_SCH_PWR_GETCELLPWR(cell) &((RgSchCmnCell *)((cell)->sc.sch))->ul.ulPwrCb typedef S8 RgSchCmnUlPwrCqiToPwrTbl[RG_SCH_CMN_UL_NUM_CQI]; static RgSchCmnUlPwrCqiToPwrTbl rgSchPwrCqiToPwrTbl; /* This table maps a given number of RBs (given by array index) * to the power in dB that these many RBs map to. */ const uint8_t rgSchPwrRbToPwrTbl[111] = { 0, /* First entry is dummy */ 0, 3, 4, 6, 7, 7, 8, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20 }; /* This table maps power (in dB) to number of RBs */ /* The array size comes from max power in rgSchPwrRbToPwrTbl */ const uint8_t rgSchPwrToRbTbl[20+1] = { 1, 1, 2, 2, 3, 4, 5, 6, 7, 9, 11, 13, 17, 21, 26, 33, 41, 52, 65, 82, 103 }; static S8 rgSCHPwrGetCqiPwr ARGS(( uint8_t cqi )); static S8 rgSCHPwrGetCqiPwrForUe ARGS(( RgSchCellCb *cell, RgSchUeCb *ue, uint8_t cqi )); static S8 rgSCHPwrCalcEfficncyPwr ARGS(( uint32_t eff )); static S8 rgSCHPwrGetDelta2FrmCqi ARGS(( uint8_t crntCqi, uint8_t trgCqi, RgSchUeCb *ue, RgSchCellCb *cell )); static Void rgSCHPwrGetPuschTpc ARGS(( uint8_t isAcc, S8 delta, S8 availPwr, uint8_t *tpc, S8 *tpcDelta )); static uint8_t rgSCHPwrGetMaxRb ARGS(( RgSchCellCb *cell, S8 pwr )); static uint8_t rgSCHPwrRbToPwr ARGS(( RgSchCellCb *cell, uint8_t numRb )); static Void rgSCHPwrSchedPucchRnti ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchPdcch *pdcch, RgSchDlSf *dlSf, Bool *sched )); static Void rgSCHPwrPuschCntrl ARGS(( RgSchCellCb *cell, RgSchUeCb *ue )); static Void rgSCHPwrPucchCntrl ARGS(( RgSchCellCb *cell, RgSchUeCb *ue )); static Void rgSCHPwrSchedPuschRnti ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchPdcch *pdcch, RgSchUlSf *ulSf, Bool *sched )); static Void rgSCHPwrGetPucchFmt3TpcForUe ARGS(( RgSchUeCb *ue, uint8_t *tpc, S8 *delta )); static Void rgSCHPwrGetPucchFmt3aTpcForUe ARGS(( RgSchUeCb *ue, uint8_t *tpc, S8 *delta )); static Void rgSCHPwrGetPuschFmt3TpcForUe ARGS(( RgSchUeCb *ue, uint8_t *tpc, S8 *delta )); static Void rgSCHPwrGetPuschFmt3aTpcForUe ARGS(( RgSchUeCb *ue, uint8_t *tpc, S8 *delta )); static Void rgSCHPwrGetAcc1bitTpc ARGS(( S8 remPwr, uint8_t *tpc, S8 *delta )); static Void rgSCHPwrGetAcc2bitTpc ARGS(( S8 remPwr, uint8_t *tpc, S8 *delta )); static Void rgSCHPwrGetAbsTpc ARGS(( S8 remPwr, uint8_t *tpc, S8 *delta )); static Void rgSCHPwrOnPucchGrpPwrForUe ARGS(( RgSchCellCb *cell, RgSchUeCb *ue, S8 delta )); static Void rgSCHPwrOnPuschGrpPwrForUe ARGS(( RgSchCellCb *cell, RgSchUeCb *ue, S8 delta )); static Bool rgSCHPwrIsDlUeSched ARGS(( RgSchCellCb *cell, RgSchUeCb *ue, RgSchDlSf *sf )); static Bool rgSCHPwrIsUlUeSched ARGS(( RgSchCellCb *cell, RgSchUeCb *ue, RgSchUlSf *sf )); static Void rgSCHPwrOnSchedPucchTpc ARGS(( RgSchCellCb *cell, RgSchUeCb *ue, S8 delta )); static Void rgSCHPwrOnSchedPuschTpc ARGS(( RgSchCellCb *cell, RgSchUeCb *ue )); static S16 rgSCHPwrApplyUePwrCfg ARGS(( RgSchCellCb *cell, RgSchUeCb *ue, RgrUeUlPwrCfg *pwrCfg )); static Void rgSCHPwrUeResetPucch ARGS(( RgSchCellCb *cell, RgSchUeCb *ue )); static Void rgSCHPwrUeResetPusch ARGS(( RgSchCellCb *cell, RgSchUeCb *ue )); static Void rgSCHPwrOnPuschPwrUpd ARGS(( RgSchCellCb *cell, RgSchUeCb *ue )); static Void rgSCHPwrAddRntiToPucchRntiLst ARGS(( RgSchCellCb *cell, CmLteRnti rnti, Bool isFmt3a )); static Void rgSCHPwrAddRntiToPuschRntiLst ARGS(( RgSchCellCb *cell, CmLteRnti rnti, Bool isFmt3a )); static Void rgSCHPwrInitTpcRntiCb ARGS(( RgSchCmnTpcRntiCb *cb, CmLteRnti rnti, Bool isFmt3a )); static RgSchCmnTpcRntiCb* rgSCHPwrGetPucchRntiCb ARGS(( RgSchCellCb *cell, CmLteRnti tpcRnti )); static RgSchCmnTpcRntiCb* rgSCHPwrGetPuschRntiCb ARGS(( RgSchCellCb *cell, CmLteRnti tpcRnti )); static Void rgSCHPwrAddUeToPucchTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrDelUeFrmPucchTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrRmvSchdUeFrmPucchTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrRmvSchdUeOnlyFrmPucchTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrRmvSchdPucchTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb )); static Void rgSCHPwrAddSchdUeToPucchTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrAddSchdPucchTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb )); static Void rgSCHPwrAddUeToPuschTpcRntiCb ARGS(( RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrAddSchdUeToPuschTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrDelUeFrmPuschTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrRmvSchdUeFrmPuschTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrRmvSchdUeOnlyFrmPuschTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb, RgSchUeCb *ue )); static Void rgSCHPwrAddSchdPuschTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb )); static Void rgSCHPwrRmvSchdPuschTpcRntiCb ARGS(( RgSchCellCb *cell, RgSchCmnTpcRntiCb *cb )); static S16 rgSCHPwrChkPucchTpcRntiIdx ARGS(( RgSchCmnTpcRntiCb *cb, uint8_t idx )); static S16 rgSCHPwrChkPuschTpcRntiIdx ARGS(( RgSchCmnTpcRntiCb *cb, uint8_t idx )); static S16 rgSCHPwrChkUniqPucchTpcRntiIdx ARGS(( RgSchCmnTpcRntiCb *cb, uint8_t idx )); static S16 rgSCHPwrChkUniqPuschTpcRntiIdx ARGS(( RgSchCmnTpcRntiCb *cb, uint8_t idx )); static S16 rgSCHPwrChkTpcRntiIdx ARGS(( RgSchCmnTpcRntiCb *cb, uint8_t idx )); static S8 rgSCHPwrGetPhValFromPhr ARGS(( uint8_t phr )); static S8 rgSCHPwrGetPCMaxValFromPCMax ARGS(( uint8_t pCMax )); /* local defines */ /** * @brief Does power related initialisation (not cell specific). * * * @details * * Function : rgSCHPwrInit * * Processing Steps: * - This shall precompute coding efficiency to power * mappings (assuming beta of 1). * * @return Void **/ Void rgSCHPwrInit() { uint8_t idx; rgSchPwrCqiToPwrTbl[0] = 0; /* This should never be used anyway */ for (idx = 1; idx < RG_SCH_CMN_UL_NUM_CQI; ++idx) { rgSchPwrCqiToPwrTbl[idx] = rgSCHPwrCalcEfficncyPwr(rgSchCmnUlCqiTbl[idx].eff); } return; } /*********************************************************** * * Func : rgSCHPwrGetCqiPwr * * Desc : Returns power corresponding to coding efficiency * when beta pusch is assumed 1. * * Ret : uint8_t * * Notes: * * File : * **********************************************************/ static S8 rgSCHPwrGetCqiPwr(uint8_t cqi) { return (rgSchPwrCqiToPwrTbl[cqi]); } /* rgSCHPwrGetCqiPwr */ /*********************************************************** * * Func : rgSCHPwrGetCqiPwrForUe * * Desc : If MCS control is enabled for UE, returns * power corresponding to CQI, else 0. * * Ret : uint8_t * * Notes: * * File : * **********************************************************/ static S8 rgSCHPwrGetCqiPwrForUe(RgSchCellCb *cell,RgSchUeCb *ue,uint8_t cqi) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); if (!uePwr->deltaMcsEnbld) { return (0); } return (rgSCHPwrGetCqiPwr(cqi)); } /* rgSCHPwrGetCqiPwrForUe */ /*********************************************************** * * Func : rgSCHPwrCalcEfficncyPwr * * Desc : Computes power corresponding to a coding * efficiency. * * Ret : uint8_t * * Notes: Assumes beta pusch to be 1 * * File : * **********************************************************/ static S8 rgSCHPwrCalcEfficncyPwr(uint32_t eff) { F64 ks = 1.25; /* or F64 */ F64 tmp = cmPow(2, ks*eff/1024) - 1; if (tmp <= 0) return (0); return ((S8)(10 * cmLog10(tmp))); } /* rgSCHPwrCalcEfficncyPwr */ /** * @brief Returns TPC to be sent in UL allocation * * @details * * Function : rgSCHPwrPuschTpcForUe * * Invoking Module Processing: * - After allocation for UE, this function shall * be invoked to retrieve TPC. * - This assumes that rgSCHPwrGetMaxUlRb() was * invoked prior to final allocation for UE. * * Processing Steps: * - Just return TPC that was determined * earlier. * - After this, do necessary updates. * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return uint8_t **/ uint8_t rgSCHPwrPuschTpcForUe(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue,cell); UNUSED(cell); rgSCHPwrOnSchedPuschTpc(cell, ue); return (uePwr->puschTpc); } /** * @brief Handles Pusch power control for DCI format 0 * * @details * * Function : rgSCHPwrGetMaxUlRb * * Invoking Module Processing: * - This shall be invoked to determine maximum * number of UL RBs for scheduling. * - This is expected to be invoked every time * priority to attempt at UE allocation. Later * TPC retrieval depends on it. * * Processing Steps: * - Returns maximum allowed UL RBs to be granted * after invoking Pusch power control. * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return Void **/ uint8_t rgSCHPwrGetMaxUlRb(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); rgSCHPwrPuschCntrl(cell, ue); /* This stores tpc, delta and maxRb * in uePwr */ return (uePwr->maxUlRbs); } /** * @brief Handles Pusch power control for DCI format 0 * * @details * * Function : rgSCHPwrPuschCntrl * * Invoking Module Processing: * - This shall be invoked to determine TPC * and maximum number of UL RBs for scheduling * (through DCI format 0). * * Processing Steps: * - 'remPuschPwr' is the final delta power that the UE * should apply to get to target CQI. * - The available headroom (availPwr) is determined. * - Power command is given by considering remPuschPwr and * availPwr. * - After factoring in the power command into availPwr, the * maximum number of RBs that can be supported is determined * assuming that UE is going to use transmission efficiency * corresponding to current CQI. * - The results determined in this function are stored * in the UE power control block. * - [Not doing anything of power control of msg3 * retransmissions now] * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return Void **/ static Void rgSCHPwrPuschCntrl(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUlUe *ueUl = RG_SCH_CMN_GET_UL_UE(ue, cell); RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); RgSchCmnUlCell *cellUl = RG_SCH_CMN_GET_UL_CELL(cell); S8 delta; #ifdef TFU_UPGRADE uint8_t cqi = ueUl->validUlCqi; S32 tmp; #else uint8_t cqi = ueUl->crntUlCqi[0]; #endif Bool isAcc = uePwr->isAccumulated; uint8_t tpc; S8 availPwr; uint8_t maxRb; UNUSED(cell); if (!uePwr->isPhrAvail) { availPwr = 60; /* setting a large value so that availPwr does * not constrain delta */ } else { availPwr = uePwr->maxUePwr - uePwr->pwrPerRb; availPwr -= rgSCHPwrGetCqiPwrForUe(cell, ue, cqi); } delta = uePwr->remPuschPwr; rgSCHPwrGetPuschTpc(isAcc, delta, availPwr, &tpc, &delta); availPwr -= delta; maxRb = rgSCHPwrGetMaxRb(cell,availPwr); /* Store the results in ue power control block to be used later */ if(maxRb < cellUl->sbSize) { maxRb = cellUl->sbSize; #ifdef TFU_UPGRADE if(uePwr->maxPwrDeltaByPhr < 0) { tmp = ueUl->validUlCqi; tmp = tmp + uePwr->maxPwrDeltaByPhr; if (tmp < 1 ) { ueUl->validUlCqi = 1; } else { ueUl->validUlCqi = tmp; } } #endif } RLOG_ARG4(L_UNUSED,DBG_CELLID,cell->cellId, "UEID:%d Output Max Rb (%d), phVal (%d) AvailPwr (%d) ", ue->ueId, maxRb, uePwr->phVal, availPwr); RLOG_ARG3(L_UNUSED,DBG_CELLID,cell->cellId, "UEID:%d pwrPerRb %d remPuschPwr %d", ue->ueId, uePwr->pwrPerRb, uePwr->remPuschPwr); uePwr->delta = delta; uePwr->maxUlRbs = maxRb; uePwr->puschTpc = tpc; return; } /** * @brief Returns TPC to be sent in DL allocation * * @details * * Function : rgSCHPwrPucchTpcForUe * * Invoking Module Processing: * - After DL allocation for UE, this function shall * be invoked to obtain TPC. * * Processing Steps: * - Do Pucch power control processing * and return TPC * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return uint8_t **/ uint8_t rgSCHPwrPucchTpcForUe(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); rgSCHPwrPucchCntrl(cell, ue); return (uePwr->pucchTpc); } /*********************************************************** * * Func : rgSCHPwrGetDelta2FrmCqi * * Desc : Get power to be applied to achieve * target CQI (the power returned is * twice is actual power) * * Ret : S8 * * Notes: * * File : * **********************************************************/ static S8 rgSCHPwrGetDelta2FrmCqi(uint8_t crntCqi,uint8_t trgCqi,RgSchUeCb *ue,RgSchCellCb *cell) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); if (uePwr->isPhrAvail) { //uePwr->maxPwrDeltaByPhr = uePwr->maxPwrPerRb - uePwr->pwrPerRb - uePwr->remPuschPwr; uePwr->maxPwrDeltaByPhr = uePwr->maxPwrPerRb - uePwr->pwrPerRb; } else { uePwr->maxPwrDeltaByPhr = 0; } if (uePwr->maxPwrDeltaByPhr < 0 && (trgCqi - crntCqi) * RG_SCH_UL_CQI_DB_STEP_2 > 0) { return (0); } return (RGSCH_MIN(uePwr->maxPwrDeltaByPhr, (trgCqi - crntCqi) * RG_SCH_UL_CQI_DB_STEP_2)); } /* rgSCHPwrGetDelta2FrmCqi */ /*********************************************************** * * Func : rgSCHPwrGetPuschTpc * * Desc : Based on whether accumulation is enabled or * not, this returns an applicable power delta * to be applied based on the input delta. * * Ret : S8 * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetPuschTpc(uint8_t isAcc,S8 delta,S8 availPwr,uint8_t *tpc,S8 *tpcDelta) { delta = RGSCH_MIN(delta, availPwr); /* As of now, the functions below possibly cause delta * to be breached by 1 only. So calling these as is. */ if (isAcc) { rgSCHPwrGetAcc2bitTpc(delta, tpc, tpcDelta); } else { rgSCHPwrGetAbsTpc(delta, tpc, tpcDelta); } return; } /* rgSCHPwrGetPuschTpc */ /*********************************************************** * * Func : rgSCHPwrGetMaxRb * * Desc : Get the maximum number of RBs that can be * expected to be supported by the passed * power headroom. * * Ret : uint8_t * * Notes: * * File : * **********************************************************/ static uint8_t rgSCHPwrGetMaxRb(RgSchCellCb *cell,S8 pwr) { RgSchCmnUlCell *cellUl; cellUl = RG_SCH_CMN_GET_UL_CELL(cell); if (pwr <= 0) { /* Give 4 RBS so that UE can report changed power status*/ /* [ccpu00119916] Mod -return 0th index of rgSchPwrToRbTbl when pwr <=0 * Change the Macros from RGSCH_MAX_DL_BW to RGSCH_MAX_UL_BW*/ return (rgSchPwrToRbTbl[0]); } if (pwr > rgSchPwrRbToPwrTbl[cellUl->maxUlBwPerUe]) { return (cellUl->maxUlBwPerUe); } return (RGSCH_MIN(cellUl->maxUlBwPerUe,rgSchPwrToRbTbl[(uint8_t)pwr])); } /* rgSCHPwrGetMaxRb */ /*********************************************************** * * Func : rgSCHPwrRbToPwr * * Desc : Get the power corresponding to number of RBs * * Ret : uint8_t * * Notes: * * File : * **********************************************************/ static uint8_t rgSCHPwrRbToPwr(RgSchCellCb *cell,uint8_t numRb) { #ifndef NO_ERRCLS RgSchCmnUlCell *cellUl; #endif #if (ERRCLASS & ERRCLS_DEBUG) cellUl = RG_SCH_CMN_GET_UL_CELL(cell); if (numRb > cellUl->maxUlBwPerUe) { numRb = cellUl->maxUlBwPerUe; } #endif return (rgSchPwrRbToPwrTbl[numRb]); } /* rgSCHPwrRbToPwr */ /** * @brief Handles Pucch power control for DCI formats 1A/1B/1D/1/2A/2 * * @details * * Function : rgSCHPwrPucchCntrl * * Processing Steps: * - Determine 2 bit TPC to be sent using remPucchPwr. * - Update remPucchPwr appropriately * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return Void **/ static Void rgSCHPwrPucchCntrl(RgSchCellCb *cell,RgSchUeCb *ue) { S8 delta; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); rgSCHPwrGetAcc2bitTpc(uePwr->remPucchPwr, &uePwr->pucchTpc, &delta); rgSCHPwrOnSchedPucchTpc(cell, ue, delta); return; } /** * @brief Handles group power control for DCI formats 3/3A for Pucch and Pusch * * @details * * Function : rgSCHPwrGrpCntrlPucch * * Invoking Module Processing: * - This shall be invoked to do group power control for * all TPC RNTIs for which it is deemed necessary to * do the same (group power control). * - This function should only be invoked after all UEs * have been scheduled for uplink (re)transmissions * requiring DL DCI format in the passed subframe. * * Processing Steps: * - For Pucch group power control * - For each TPC-Pucch-RNTI in the pucchGrpPwr List and * TPC-Pusch-RNTI in the puschGrpPwr List, * - Request for PDCCH, skip if not available * - Form DCI format 3/3A information depending * on the format type of the TPC-RNTI and add it to the sub-frame. * - For each Ue in ueLst of TPC RNTI Cb * - if (fmtType == 3A) * - if((Ue not scheduled DL dci formats) * && (remPwr >= 2 || remPwr <= -2)) * - Determine TPC. Set puschTpc/pucchTpc. * - remPwr -= TPC * - if (remPwr >= -1 && remPwr <= 1) * - If already added, remove from toBeSchdLst * - else * - Toggle the remainig power value * - else if (fmtType == 3) * - if((Ue not scheduled DL dci formats) * && (remPwr)) * - Determine TPC. Set puschTpc/pucchTpc. * - remPwr -= TPC * - if (!remPwr) * - If already added, remove from toBeSchdLst * - if (!toBeSchdUeCnt) * - Remove the tpcRntiCb frm pucchGrpPwr/puschGrpPwr List * - else, Move the tpcRntiCb to end of the list (not doing * this) * * @param[in] RgSchCellCb *cell * @param[in] RgSchDlSf *dlSf * @return Void **/ Void rgSCHPwrGrpCntrlPucch(RgSchCellCb *cell,RgSchDlSf *dlSf) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); CmLListCp *lst; CmLList *lnk; RgSchPdcch *pdcch; lst = &cellPwr->pucchGrpPwr; lnk = lst->first; while (lnk && ((pdcch = rgSCHCmnCmnPdcchAlloc(cell, dlSf)) != NULLP)) { RgSchCmnTpcRntiCb *cb = (RgSchCmnTpcRntiCb *)lnk->node; Bool sched; lnk = lnk->next; rgSCHPwrSchedPucchRnti(cell, cb, pdcch, dlSf, &sched); if (!sched) { rgSCHUtlPdcchPut(cell, &dlSf->pdcchInfo, pdcch); } /* TPC RNTI would not have been removed if needs to * be scheduled again */ } return; } /** * @brief Handles group power control for DCI formats 3/3A for Pusch and Pusch * * @details * * Function : rgSCHPwrGrpCntrlPusch * * Invoking Module Processing: * - This shall be invoked to do group power control for * all TPC RNTIs for which it is deemed necessary to * do the same (group power control). * - This function should only be invoked after all UEs * have been scheduled for uplink (re)transmissions * requiring DCI format 0 in the passed subframe. * * Processing Steps: * - For Pusch group power control * - For each TPC-Pusch-RNTI in the puschGrpPwr List and * - Request for PDCCH, skip if not available * - Form DCI format 3/3A information depending * on the format type of the TPC-RNTI and add it to the sub-frame. * - For each Ue in ueLst of TPC RNTI Cb * - if (fmtType == 3A) * - if (Ue not scheduled for dci format 0) and * (remPwr >= 2 || remPwr <= -2)) * - Determine TPC. Set puschTpc/puschTpc. * - remPwr -= TPC * - if (remPwr >= -1 && remPwr <= 1) * - If already added, remove from toBeSchdLst * - else * - Toggle the remainig power value * - else if (fmtType == 3) * - if((Ue not scheduled for dci format 0) && (remPwr)) * - Determine TPC. Set puschTpc. * - remPwr -= TPC * - if (!remPwr) * - If already added, remove from toBeSchdLst * - if (!toBeSchdUeCnt) * - Remove the tpcRntiCb frm puschGrpPwr/puschGrpPwr List * - else, Move the tpcRntiCb to end of the list (not doing * this now) * * @param[in] RgSchCellCb *cell * @param[in] RgSchDlSf *sf * @return Void **/ Void rgSCHPwrGrpCntrlPusch(RgSchCellCb *cell,RgSchDlSf *dlSf,RgSchUlSf *ulSf) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); CmLListCp *lst; CmLList *lnk; RgSchPdcch *pdcch; lst = &cellPwr->puschGrpPwr; lnk = lst->first; while (lnk && ((pdcch = rgSCHCmnCmnPdcchAlloc(cell, dlSf)) != NULLP)) { RgSchCmnTpcRntiCb *cb = (RgSchCmnTpcRntiCb *)lnk->node; Bool sched; lnk = lnk->next; rgSCHPwrSchedPuschRnti(cell, cb, pdcch, ulSf, &sched); if (!sched) { rgSCHUtlPdcchPut(cell, &dlSf->pdcchInfo, pdcch); } /* TPC RNTI would not have been removed if needs to * be scheduled again */ } return; } /*********************************************************** * * Func : rgSCHPwrSchedPucchRnti * * Desc : Schedule TPC RNTI to be sent out * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrSchedPucchRnti(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchPdcch *pdcch,RgSchDlSf *dlSf,Bool *sched) { CmLListCp *lst; CmLList *lnk; uint8_t *tpcCmds; uint8_t tpc; S8 delta; Bool atleastOne; pdcch->rnti = cb->tpcRnti; if (cb->isFmt3a) { /* Go through all UEs for format 3A case */ lst = &cb->cfgdUes; pdcch->dci.dciFormat = TFU_DCI_FORMAT_3A; pdcch->dciNumOfBits = cell->dciSize.size[TFU_DCI_FORMAT_3A]; pdcch->dci.u.format3AInfo.isPucch = TRUE; tpcCmds = pdcch->dci.u.format3AInfo.tpcCmd; /* No need to memset zero initially as every TPC is going * to be filled up for every configured UE */ for (atleastOne = FALSE, lnk = lst->first; lnk; lnk = lnk->next) { RgSchUeCb *ue = (RgSchUeCb *)lnk->node; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); if ( ue->isDrxEnabled == TRUE && !RG_SCH_DRX_DL_IS_UE_ACTIVE(ue->drxCb)) { /* UE is in its DRX time. So we cannot give command * to this UE. */ continue; } if (rgSCHPwrIsDlUeSched(cell, ue, dlSf)) { /* UE already scheduled in downlink with PDCCH * carrying PUCCH pwr cmd. So don't care about * giving command to this UE. */ continue; } rgSCHPwrGetPucchFmt3aTpcForUe(ue, &tpc, &delta); tpcCmds[uePwr->pucchIdx] = tpc; atleastOne = TRUE; rgSCHPwrOnPucchGrpPwrForUe(cell, ue, delta); } } else { /* Go through to-be-scheduled UEs for format 3 case */ lst = &cb->toBeSchdUes; pdcch->dci.dciFormat = TFU_DCI_FORMAT_3; pdcch->dciNumOfBits = cell->dciSize.size[TFU_DCI_FORMAT_3]; tpcCmds = pdcch->dci.u.format3Info.tpcCmd; pdcch->dci.u.format3Info.isPucch = TRUE; /* Fill TPC 1 (corresponding to no power change) initially */ memset(tpcCmds, 1, sizeof(pdcch->dci.u.format3Info.tpcCmd)); for (atleastOne = FALSE, lnk = lst->first; lnk; lnk = lnk->next) { RgSchUeCb *ue = (RgSchUeCb *)lnk->node; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); if ( ue->isDrxEnabled == TRUE && !RG_SCH_DRX_DL_IS_UE_ACTIVE(ue->drxCb)) { /* UE is in its DRX time. So we cannot give command * to this UE. */ continue; } if (rgSCHPwrIsDlUeSched(cell, ue, dlSf)) { /* UE already scheduled in downlink with PDCCH * carrying PUCCH pwr cmd. So don't care about * giving command to this UE. */ continue; } rgSCHPwrGetPucchFmt3TpcForUe(ue, &tpc, &delta); tpcCmds[uePwr->pucchIdx] = tpc; atleastOne = TRUE; rgSCHPwrOnPucchGrpPwrForUe(cell, ue, delta); } } *sched = atleastOne; /* Check if no more UEs in TPC RNTI, and then remove * this TPC RNTI from scheduled list */ if (cb->toBeSchdUes.count == 0) { rgSCHPwrRmvSchdPucchTpcRntiCb(cell, cb); } return; } /* rgSCHPwrSchedPucchRnti */ /*********************************************************** * * Func : rgSCHPwrSchedPuschRnti * * Desc : Schedule TPC RNTI to be sent out * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrSchedPuschRnti(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchPdcch *pdcch,RgSchUlSf *ulSf,Bool *sched) { CmLListCp *lst; CmLList *lnk; uint8_t *tpcCmds; uint8_t tpc; S8 delta; Bool atleastOne; pdcch->rnti = cb->tpcRnti; if (cb->isFmt3a) { /* Go through all UEs for format 3A case */ lst = &cb->cfgdUes; pdcch->dci.dciFormat = TFU_DCI_FORMAT_3A; pdcch->dciNumOfBits = cell->dciSize.size[TFU_DCI_FORMAT_3A]; pdcch->dci.u.format3AInfo.isPucch = FALSE; tpcCmds = pdcch->dci.u.format3AInfo.tpcCmd; /* No need to memset zero initially as every TPC is going * to be filled up for every configured UE */ for (atleastOne = FALSE, lnk = lst->first; lnk; lnk = lnk->next) { RgSchUeCb *ue = (RgSchUeCb *)lnk->node; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); if (rgSCHPwrIsUlUeSched(cell, ue, ulSf)) { /* UE already scheduled in uplink with DCI * format 0. So don't care about giving * command to this UE. */ continue; } if ( ue->isDrxEnabled == TRUE && !RG_SCH_DRX_DL_IS_UE_ACTIVE(ue->drxCb)) { /* UE is in its DRX time. So we cannot give command * to this UE. */ continue; } rgSCHPwrGetPuschFmt3aTpcForUe(ue, &tpc, &delta); tpcCmds[uePwr->puschIdx] = tpc; atleastOne = TRUE; rgSCHPwrOnPuschGrpPwrForUe(cell, ue, delta); } } else { /* Go through to-be-scheduled UEs for format 3 case */ lst = &cb->toBeSchdUes; pdcch->dci.dciFormat = TFU_DCI_FORMAT_3; pdcch->dciNumOfBits = cell->dciSize.size[TFU_DCI_FORMAT_3]; pdcch->dci.u.format3Info.isPucch = FALSE; tpcCmds = pdcch->dci.u.format3Info.tpcCmd; /* Fill TPC 1 (corresponding to no power change) initially */ memset(tpcCmds, 1, sizeof(pdcch->dci.u.format3Info.tpcCmd)); for (atleastOne = FALSE, lnk = lst->first; lnk; lnk = lnk->next) { RgSchUeCb *ue = (RgSchUeCb *)lnk->node; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); if (rgSCHPwrIsUlUeSched(cell, ue, ulSf)) { /* UE already scheduled in uplink with DCI * format 0. So don't care about giving * command to this UE. */ continue; } if ( ue->isDrxEnabled == TRUE && !RG_SCH_DRX_DL_IS_UE_ACTIVE(ue->drxCb)) { /* UE is in its DRX time. So we cannot give command * to this UE. */ continue; } rgSCHPwrGetPuschFmt3TpcForUe(ue, &tpc, &delta); tpcCmds[uePwr->puschIdx] = tpc; atleastOne = TRUE; rgSCHPwrOnPuschGrpPwrForUe(cell, ue, delta); } } *sched = atleastOne; /* Check if no more UEs in TPC RNTI, and then remove * this TPC RNTI from scheduled list */ if (cb->toBeSchdUes.count == 0) { rgSCHPwrRmvSchdPuschTpcRntiCb(cell, cb); } return; } /* rgSCHPwrSchedPuschRnti */ /*********************************************************** * * Func : rgSCHPwrGetPucchFmt3TpcForUe * * Desc : Gets 2 bit TPC cmd for PUCCH * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetPucchFmt3TpcForUe(RgSchUeCb *ue,uint8_t *tpc,S8 *delta) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); rgSCHPwrGetAcc2bitTpc(uePwr->remPucchPwr, tpc, delta); return; } /* rgSCHPwrGetPucchFmt3TpcForUe */ /*********************************************************** * * Func : rgSCHPwrGetPucchFmt3aTpcForUe * * Desc : Gets 1 bit TPC cmd for PUCCH * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetPucchFmt3aTpcForUe(RgSchUeCb *ue,uint8_t *tpc,S8 *delta) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); rgSCHPwrGetAcc1bitTpc(uePwr->remPucchPwr, tpc, delta); return; } /* rgSCHPwrGetPucchFmt3aTpcForUe */ /*********************************************************** * * Func : rgSCHPwrGetPuschFmt3TpcForUe * * Desc : Gets 2 bit TPC cmd for PUCCH * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetPuschFmt3TpcForUe(RgSchUeCb *ue,uint8_t *tpc,S8 *delta) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); S8 adj = RGSCH_MIN(uePwr->remPuschPwr, uePwr->phVal); rgSCHPwrGetAcc2bitTpc(adj, tpc, delta); return; } /* rgSCHPwrGetPuschFmt3TpcForUe */ /*********************************************************** * * Func : rgSCHPwrGetPuschFmt3aTpcForUe * * Desc : Gets 1 bit TPC cmd for PUCCH * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetPuschFmt3aTpcForUe(RgSchUeCb *ue,uint8_t *tpc,S8 *delta) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); /* Don't attempt to look at headroom now, power * adjustment is small anyway */ rgSCHPwrGetAcc1bitTpc(uePwr->remPuschPwr, tpc, delta); return; } /* rgSCHPwrGetPuschFmt3aTpcForUe */ /*********************************************************** * * Func : rgSCHPwrGetAcc1bitTpc * * Desc : Gets 1 bit TPC cmd * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetAcc1bitTpc(S8 remPwr,uint8_t *tpc,S8 *delta) { /* * TPC delta * 0 -1 * 1 1 */ if (remPwr <= 0) { *delta = -1; *tpc = 0; } else { *delta = 1; *tpc = 1; } return; } /* rgSCHPwrGetAcc1bitTpc */ /*********************************************************** * * Func : rgSCHPwrGetAcc2bitTpc * * Desc : Allocate PDCCH for group power control * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetAcc2bitTpc(S8 remPwr,uint8_t *tpc,S8 *delta) { /* * TPC delta * 0 -1 * 1 0 * 2 1 * 3 3 */ uint8_t tpcs[3] = {1, 2, 2}; uint8_t deltas[3] = {0, 1, 1}; if (remPwr <= -1) { *tpc = 0; *delta = -1; } else if (remPwr >= 3) { *tpc = 3; *delta = 3; } else { *tpc = tpcs[(uint8_t)remPwr]; *delta = deltas[(uint8_t)remPwr]; } return; } /* rgSCHPwrGetAcc2bitTpc */ /*********************************************************** * * Func : rgSCHPwrGetAbsTpc * * Desc : Allocate PDCCH for group power control * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrGetAbsTpc(S8 remPwr,uint8_t *tpc,S8 *delta) { /* * TPC delta * 0 -4 * 1 -1 * 2 1 * 3 4 */ if (remPwr <= -3) { *tpc = 0; *delta = -4; } else if (remPwr < 1) { *tpc = 1; *delta = -1; } else if (remPwr < 4) { *tpc = 2; *delta = 1; } else { *tpc = 3; *delta = 4; } return; } /* rgSCHPwrGetAbsTpc */ /*********************************************************** * * Func : rgSCHPwrOnPucchGrpPwrForUe * * Desc : Processing on sending TPC for UE through group power * control. Apart from updating remPwr, this only takes * care of possibly removing UE from scheduled * list in TPC RNTI. * It does not take care of possibly removing TPC RNTI * from scheduled list in cell. This is done * in the caller after TPC for all UEs has been * determined. (This is where it differs * from the usual OnSendingPu[cs]ch TPC] * * Ret : ROK/RFAILED * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrOnPucchGrpPwrForUe(RgSchCellCb *cell,RgSchUeCb *ue,S8 delta) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); Bool rmvUe = FALSE; UNUSED(cell); uePwr->remPucchPwr -= delta; /* UE was already scheduled for PUCCH group power * control which is why we came here. Don't * again check for this. */ /* UE was scheduled for pucch grp pwr, sent TPC may * possibly cause it to be removed. */ if (!uePwr->remPucchPwr) { rmvUe = TRUE; } if (rmvUe) { rgSCHPwrRmvSchdUeOnlyFrmPucchTpcRntiCb(cell, uePwr->tpcPucchRntiCb, ue); /* Not removing TPC RNTI from scheduled list, * this will happen in the caller once this * function is called for every UE scheduled. */ } return; } /*********************************************************** * * Func : rgSCHPwrOnPuschGrpPwrForUe * * Desc : Processing on sending TPC for UE through group power * control. Apart from updating remPwr, this only takes * care of possibly removing UE from scheduled * list in TPC RNTI. * It does not take care of possibly removing TPC RNTI * from scheduled list in cell. This is done * in the caller after TPC for all UEs has been * determined. (This is where it differs * from the usual OnSendingPu[cs]ch TPC] * * Ret : ROK/RFAILED * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrOnPuschGrpPwrForUe(RgSchCellCb *cell,RgSchUeCb *ue,S8 delta) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); Bool rmvUe = FALSE; UNUSED(cell); uePwr->delta = delta; uePwr->remPuschPwr -= delta; if (uePwr->isPhrAvail) { uePwr->phVal -= uePwr->delta; uePwr->phVal = RGSCH_MAX(-23, uePwr->phVal); } /* UE was already scheduled for PUSCH group power * control which is why we came here. Don't * again check for this. */ /* UE was scheduled for pusch grp pwr, sent TPC may * possibly cause it to be removed. */ if (!uePwr->remPuschPwr) { rmvUe = TRUE; } if (rmvUe) { rgSCHPwrRmvSchdUeOnlyFrmPuschTpcRntiCb(cell, uePwr->tpcPuschRntiCb, ue); /* Not removing TPC RNTI from scheduled list, * this will happen in the caller once this * function is called for every UE scheduled. */ } return; } /*********************************************************** * * Func : rgSCHPwrIsDlUeSched * * Desc : Check if UE is scheduled in the passed DL SF * * Ret : Void * * Notes: * * File : * **********************************************************/ static Bool rgSCHPwrIsDlUeSched(RgSchCellCb *cell,RgSchUeCb *ue,RgSchDlSf *sf) { RgSchDlHqEnt *hqEnt = RG_SCH_CMN_GET_UE_HQE(ue, cell); RgSchDlHqProcCb *proc = rgSCHDhmLastSchedHqProc(hqEnt); if (proc == NULLP) { return (FALSE); } /* * The following subframe check is assumed enough, since * scheduled procs stay for a short time (until feedback * arrives), which typically is expected to have a * turnaround time of less than 8 subframes. So * we are probably never going to come across cases * where a process stays in the list for more than * 10 subframes, which would have otherwise caused * the check to succeed for a possibly older process. */ if ((proc->tbInfo[0].timingInfo.slot == sf->sfNum) || (proc->tbInfo[1].timingInfo.slot == sf->sfNum)) { /* * Later, if a proc can be scheduled without having an * associated PDCCH, need to also check if PDCCH exists. * This is because for power, what matters is whether * TPC is going out for UE at this time or not, at least * that is what this function was introduced for. * Checking for PDCCH would have to be in common proc * the way things are now. */ return (TRUE); } else { return (FALSE); } } /* rgSCHPwrIsDlUeSched */ /*********************************************************** * * Func : rgSCHPwrIsUlUeSched * * Desc : Check if UE is scheduled in the passed UL SF * * Ret : Void * * Notes: * * File : * **********************************************************/ static Bool rgSCHPwrIsUlUeSched(RgSchCellCb *cell,RgSchUeCb *ue,RgSchUlSf *sf) { RgSchCmnUlCell *cmnCell = RG_SCH_CMN_GET_UL_CELL(cell); RgSchUlHqProcCb *proc = rgSCHUhmGetUlHqProc(cell, ue, cmnCell->schdHqProcIdx); UNUSED(sf); #if (ERRCLASS & ERRCLS_DEBUG) if( proc == NULLP ) { return (FALSE); } #endif if (proc->alloc) { return (TRUE); } else { return (FALSE); } } /* rgSCHPwrIsUlUeSched */ /** * @brief Handles Pucch power delta indication recieved from PHY * * @details * * Function : rgSCHPwrPucchDeltaInd * * Invoking Module Processing: * - This shall be invoked on reception of Pucch power * delta indication from PHY. * * Processing Steps: * - Update the remPucchPwr * ue->remPucchPwr = pwrDelta * - If (ue->tpcPucchRntiCb) * - If (fmtType = 3A) * - if (remPucchPwr >= 2 || remPucchPwr <= -2 ) * - if (tpcPucchRntiCb is not in the pucchGrpPwr List) * - Add tpcPucchRntiCb to the pucchGrpPwr list. * - If not added, add to toBeSchdLst * - else * - If already added, remove from toBeSchdLst * - else If (fmtType == 3) * - if (remPucchPwr) * - if (tpcPucchRntiCb is not in the pucchGrpPwr List) * - Add tpcPucchRntiCb to the pucchGrpPwr list. * - If not added, add to toBeSchdLst * - else * - If already added, remove from toBeSchdLst * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] uint8_t pwrDelta * @return Void **/ Void rgSCHPwrPucchDeltaInd(RgSchCellCb *cell,RgSchUeCb *ue,S8 pwrDelta) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); RgSchCmnTpcRntiCb *cb; Bool toAdd; uePwr->remPucchPwr = pwrDelta; if ((cb = uePwr->tpcPucchRntiCb) == NULLP) { return; } toAdd = FALSE; if (0 != uePwr->remPucchPwr) { toAdd = TRUE; } if (toAdd) { rgSCHPwrAddSchdUeToPucchTpcRntiCb(cell, cb, ue); } else { rgSCHPwrRmvSchdUeFrmPucchTpcRntiCb(cell, cb, ue); } return; } /** * @brief Does processing after TPC for Pucch has been sent * * @details * * Function : rgSCHPwrOnSchedPucchTpc * * Invoking Module Processing: * - It shall be invoked after it is determined that PDCCH for UE * is finalised to go out, and thus TPC for PUCCH is being * sent out. * * Processing Steps: * - Update remPucchPwr with the delta * - Do group power control related processing * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] S8 delta * @return Void **/ static Void rgSCHPwrOnSchedPucchTpc(RgSchCellCb *cell,RgSchUeCb *ue,S8 delta) { /* Similar to rgSCHPwrPucchDeltaInd.. not reusing * that since we use the fact that UE could only have * improved its remPwr as part of power control. */ RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); Bool rmvUe = FALSE; uePwr->remPucchPwr -= delta; if (uePwr->schdPucchGrpLnk.node == NULLP) { return; } /* UE was scheduled for TPC, sent TPC may * possibly cause it to be removed. */ if (!uePwr->remPucchPwr) { rmvUe = TRUE; } if (rmvUe) { rgSCHPwrRmvSchdUeFrmPucchTpcRntiCb(cell, uePwr->tpcPucchRntiCb, ue); if (uePwr->tpcPucchRntiCb->toBeSchdUes.count == 0) { rgSCHPwrRmvSchdPucchTpcRntiCb(cell, uePwr->tpcPucchRntiCb); } } return; } /** * @brief Does processing after TPC for Pusch has been sent * * @details * * Function : rgSCHPwrOnSchedPuschTpc * * Processing Steps: * - If accumulative * - Update remPuschPwr with the delta * - Do group power related processing if applicable * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return Void **/ static Void rgSCHPwrOnSchedPuschTpc(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); Bool rmvUe = FALSE; /* Don't do anything for the case of absolute TPC commands */ if (!uePwr->isAccumulated) { return; } uePwr->remPuschPwr -= uePwr->delta; if (uePwr->isPhrAvail) { uePwr->phVal -= uePwr->delta; uePwr->phVal = RGSCH_MAX(-23, uePwr->phVal); } if (uePwr->schdPuschGrpLnk.node == NULLP) { return; } /* UE was scheduled for pusch TPC, sent TPC may * possibly cause it to be removed. */ if (!uePwr->remPuschPwr) { rmvUe = TRUE; } if (rmvUe) { rgSCHPwrRmvSchdUeFrmPuschTpcRntiCb(cell, uePwr->tpcPuschRntiCb, ue); } return; } /** * @brief Handles PHR updation for the UE * * @details * * Function : rgSCHPwrUpdExtPhr * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] RgInfExtPhrCEInfo *extPhr * @param[in] RgSchCmnAllocRecord allocInfo * @return Void **/ Void rgSCHPwrUpdExtPhr(RgSchCellCb *cell,RgSchUeCb *ue,RgInfExtPhrCEInfo *extPhr,RgSchCmnAllocRecord *allocInfo) { uint8_t idx; RgInfExtPhrSCellInfo *servCellPhr; S8 pCMax; for (idx = 0; idx < extPhr->numServCells; idx++) { servCellPhr = &extPhr->servCellPhr[idx]; if (RG_SCH_REF_PCMAX == servCellPhr->pCmax) { pCMax = RG_SCH_CMN_PWR_USE_CFG_MAX_PWR; } else { pCMax = rgSCHPwrGetPCMaxValFromPCMax(servCellPhr->pCmax); } rgSCHPwrUpdPhr(ue->cellInfo[servCellPhr->sCellIdx]->cell, ue, servCellPhr->phr, allocInfo, pCMax); } return; } /** * @brief Handles PHR updation for the UE * * @details * * Function : rgSCHPwrUpdPhr * * Invoking Module Processing: * - This shall be invoked on reception of PHR from MAC to SCH. It shall * pass the information of number of RBs, coding efficiency and TPC for * the Pusch transmission for which PHR has been reported. * * Processing Steps: * - Compute power per RB using the PHR report * - ue_transmit_pwr = ue_max_pwr - PHR * - if isDeltaMcs = TRUE * - ue_transmit_pwr - * [10log(phr_num_rb) + 10log(2^ (1.25 * phr_coding_effeciency) -1) * + phr_tpc(if absolute TPC)] = pwrPerRB * - else * - ue_transmit_pwr - [10log(phr_num_rb) + phr_tpc(if absolute TPC)] * = pwrPerRB * (Use the number of RBs and efficiency used by UE which caused the PHR * report to happen) * - Adjust PHR according to last allocation (take into account * number of RBs granted in the last allocation) * - Update the PHR report in the control block * - Set isPhrAvail = TRUE * - Do group power control related processing if applicable * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] uint8_t phr * @param[in] RgSchCmnAllocRecord allocInfo * @param[in] uint8_t maxUePwr * @return Void **/ Void rgSCHPwrUpdPhr(RgSchCellCb *cell,RgSchUeCb *ue,uint8_t phr,RgSchCmnAllocRecord *allocInfo,S8 maxUePwr ) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); uint8_t rbPwr; uint8_t effPwr; RgSchCmnUlCell *cellUl = RG_SCH_CMN_GET_UL_CELL(cell); uePwr->phVal = rgSCHPwrGetPhValFromPhr(phr); if (maxUePwr == RG_SCH_CMN_PWR_USE_CFG_MAX_PWR) { maxUePwr = uePwr->maxUePwr; } rbPwr = rgSCHPwrRbToPwr(cell,allocInfo->numRb); effPwr = rgSCHPwrGetCqiPwrForUe(cell, ue, allocInfo->cqi); uePwr->pwrPerRb = maxUePwr - uePwr->phVal - rbPwr - effPwr; /*if (!uePwr->isAccumulated) { uePwr->pwrPerRb -= rgSCHPwrGetDeltaFrmAbsTpc(allocInfo->tpc); }*/ /* Let headroom reflect remaining power according to last * allocated number of RBs. Intermediate TPCs not yet * taken care of (for accumulated case, it is anyway * not applicable for absolute commands). */ uePwr->phVal -= (rgSCHPwrRbToPwr(cell, cellUl->sbSize)) - rbPwr; uePwr->phVal = RGSCH_MAX(-23, uePwr->phVal); uePwr->isPhrAvail = TRUE; rgSCHPwrOnPuschPwrUpd(cell, ue); RLOG_ARG4(L_DEBUG,DBG_UEID,ue->ueId, "Output: Reported PHR[%d] cqi[%u] allocRb[%u] uePwr->pwrPerRb[%d]", uePwr->phVal, allocInfo->cqi, allocInfo->numRb, uePwr->pwrPerRb); return; } /** * @brief Handles UL CQI indication * * @details * * Function : rgSCHPwrUlCqiInd * * Invoking Module Processing: * - This shall be invoked when uplink CQI indication * is receiving from PHY for a UE. * * Processing Steps: * - Update remPuschPwr. * - Possibly schedule for group power control. * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] uint8_t numRb * @return Void **/ Void rgSCHPwrUlCqiInd(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUlUe *ueUl = RG_SCH_CMN_GET_UL_UE(ue, cell); RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); #ifdef TFU_UPGRADE S32 tmp; #endif /* * For absolute power cmd case, we could look at the time * at which CQI was received, determine if there was a * PUSCH TPC cmd for that time (this could come from * group power control too), and (if this * CQI report is indeed based on the the PUSCH tx) * then factor in that cmd here. Not doing * this as of now. */ /* See how much power needs to be adjusted based on cqi * differential */ uePwr->remPuschPwr = #ifdef TFU_UPGRADE rgSCHPwrGetDelta2FrmCqi(ueUl->validUlCqi, uePwr->trgCqi, ue, cell); #else rgSCHPwrGetDelta2FrmCqi(ueUl->crntUlCqi[0], uePwr->trgCqi, ue, cell); #endif rgSCHPwrOnPuschPwrUpd(cell, ue); #ifdef TFU_UPGRADE if(uePwr->maxPwrDeltaByPhr < 0) { tmp = ueUl->validUlCqi; tmp = tmp + uePwr->maxPwrDeltaByPhr; if (tmp < 1 ) { ueUl->validUlCqi = 1; } else { ueUl->validUlCqi = tmp; } } #endif return; } /** * @brief Updates the stored last number of RBs allocated * * @details * * Function : rgSCHPwrRecordRbAlloc * * Invoking Module Processing: * - This shall be invoked when uplink allocation is made for * a UE. * - Note: If outstanding TPCs are considered at the time * of PHR report, the last numRb would also be known * and then this API would not be needed. * * Processing Steps: * - Adjust PHR according to now allocated number of RBs * - Store the number of RBs * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] uint8_t numRb * @return Void **/ Void rgSCHPwrRecordRbAlloc(RgSchCellCb *cell,RgSchUeCb *ue,uint8_t numRb) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); UNUSED(cell); return; if (uePwr->isPhrAvail) { uePwr->phVal += rgSCHPwrRbToPwr(cell,numRb) - rgSCHPwrRbToPwr(cell,uePwr->numRb); uePwr->phVal = RGSCH_MIN(40, uePwr->phVal); } uePwr->numRb = numRb; return; } /** * @brief Handles power related configuration for a cell * * @details * * Function : rgSCHPwrCellCfg * * Invoking Module Processing: * - This shall be invoked during cell config * * Processing Steps: * - Set pMax * - Set target CQI * - Update TPC-RNTI information for the cell for Pucch and Pusch. * - For each TPC-Pucch-RNTI, * - Call rgSCHAddRntiToPucchRntiLst() * - For each TPC-Pusch-RNTI, * - Call rgSCHAddRntiToPuschRntiLst() * - Return ROK * * @param[in] RgSchCellCb *cell * @param[in] RgrCellCfg *cfg * @return S16 * -# ROK * -# RFAILED **/ S16 rgSCHPwrCellCfg(RgSchCellCb *cell,RgrCellCfg *cfg) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); CmLteRnti rnti; CmLteRnti startRnti; uint16_t size; Bool isFmt3a; /* Right now, all UEs have fixed maximum power capability. So * we store cell wide pMax as minimum of configured pMax and * UE's max power */ cellPwr->pMax = RGSCH_MIN(cfg->pMax, RG_SCH_PWR_UE_MAX_PWR); /* trgUlCqi already validated by common */ cellPwr->trgUlCqi = cfg->trgUlCqi.trgCqi; /* Validate number of TPC RNTIs */ if ((cfg->pwrCfg.pucchPwrFmt3.size + cfg->pwrCfg.pucchPwrFmt3a.size > RG_SCH_CMN_MAX_NUM_TPC_PUCCH_RNTI) || (cfg->pwrCfg.puschPwrFmt3.size + cfg->pwrCfg.puschPwrFmt3a.size > RG_SCH_CMN_MAX_NUM_TPC_PUSCH_RNTI)) { return RFAILED; } /* Now initialise TPC RNTIs */ /* Format 3 Pucch TPC RNTIs */ isFmt3a = FALSE; startRnti = cfg->pwrCfg.pucchPwrFmt3.startTpcRnti; size = cfg->pwrCfg.pucchPwrFmt3.size; for (rnti = startRnti; (rnti < startRnti + size); ++rnti) { rgSCHPwrAddRntiToPucchRntiLst(cell, rnti, isFmt3a); } /* Format 3A Pucch TPC RNTIs */ isFmt3a = TRUE; startRnti = cfg->pwrCfg.pucchPwrFmt3a.startTpcRnti; size = cfg->pwrCfg.pucchPwrFmt3a.size; for (rnti = startRnti; (rnti < startRnti + size); ++rnti) { rgSCHPwrAddRntiToPucchRntiLst(cell, rnti, isFmt3a); } /* Format 3 Pusch TPC RNTIs */ isFmt3a = FALSE; startRnti = cfg->pwrCfg.puschPwrFmt3.startTpcRnti; size = cfg->pwrCfg.puschPwrFmt3.size; for (rnti = startRnti; (rnti < startRnti + size); ++rnti) { rgSCHPwrAddRntiToPuschRntiLst(cell, rnti, isFmt3a); } /* Format 3A Pusch TPC RNTIs */ isFmt3a = TRUE; startRnti = cfg->pwrCfg.puschPwrFmt3a.startTpcRnti; size = cfg->pwrCfg.puschPwrFmt3a.size; for (rnti = startRnti; (rnti < startRnti + size); ++rnti) { rgSCHPwrAddRntiToPuschRntiLst(cell, rnti, isFmt3a); } return ROK; } /** * @brief Handles power related re-configuration for a cell * * @details * * Function : rgSCHPwrCellRecfg * * Processing Steps: * - NONE * * @param[in] RgSchCellCb *cell * @param[in] RgrCellRecfg *recfg * @return S16 * -# ROK **/ S16 rgSCHPwrCellRecfg(RgSchCellCb *cell,RgrCellRecfg *recfg) { UNUSED(cell); UNUSED(recfg); /* Not doing anything for power reconfig, so such structure * in RGR */ return ROK; } /** * @brief Frees power related data structures in cell * * @details * * Function : rgSCHPwrCellDel * * Processing Steps: * - NONE * * @param[in] RgSchCellCb *cell * @return Void **/ Void rgSCHPwrCellDel(RgSchCellCb *cell) { UNUSED(cell); /* There is no allocated memory, so do nothing */ return; } #ifdef LTE_ADV /** * @brief Configures ULPC CB for a SCELL being added * * @details * * Function : rgSCHPwrUeSCellCfg * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] RgrUeCfg *cfg * @return S16 * -# ROK * -# RFAILED **/ S16 rgSCHPwrUeSCellCfg(RgSchCellCb *cell,RgSchUeCb *ue,RgrUeSecCellCfg *sCellInfoCfg) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); RgSchCmnUeUlPwrCb *uePwrPCell = RG_SCH_PWR_GETUEPWR(ue, ue->cell); RgSchCmnUlUe *ueUl = RG_SCH_CMN_GET_UL_UE(ue, cell); RgSchCmnUlCell *cellUl = RG_SCH_CMN_GET_UL_CELL(cell); uePwr->maxUePwr = cellPwr->pMax; uePwr->trgCqi = cellPwr->trgUlCqi; /* Overriding with UE's happens later */ uePwr->numRb = 1; uePwr->maxPwrPerRb = uePwr->maxUePwr - rgSchPwrRbToPwrTbl[cellUl->sbSize]; uePwr->isPhrAvail = FALSE; uePwr->phVal = 40; uePwr->maxUlRbs = RGSCH_MAX_DL_BW; uePwr->delta = 0; uePwr->puschTpc = 1; uePwr->remPuschPwr = 0; /* Rest of the vars update and group power control related * config happens in the function below */ uePwr->isAccumulated = sCellInfoCfg->ueSCellUlDedPwrCfg.isAccumulated; uePwr->deltaMcsEnbld = sCellInfoCfg->ueSCellUlDedPwrCfg.isDeltaMCSEnabled; uePwr->trgCqi = uePwrPCell->trgCqi; if (ueUl->maxUlCqi < uePwr->trgCqi) { uePwr->trgCqi = ueUl->maxUlCqi; } uePwr->p0UePusch = sCellInfoCfg->ueSCellUlDedPwrCfg.p0UePusch; return ROK; } #endif /** * @brief Handles power related configuration for a UE * * @details * * Function : rgSCHPwrUeCfg * * Processing Steps: * - If Pusch group power configuration exists && accumulation enabled, * - Fetch the TPC-Pusch-RNTI control block for the configured * TPC-Pusch-RNTI. Call rgSCHGetRntiFrmPuschRntiLst(). If it does not * exist, return RFAILED. * - Add Ue to the ueLst of TPC-Pusch-RNTI control block. * - Update tpcPuschRntiCb pointer in UE. * - Update the puschIdx value. * - If Pucch group power configuration exists && accumulation enabled, * - Fetch the TPC-Pucch-RNTI control block for the configured * TPC-Pucch-RNTI. Call rgSCHGetRntiFrmPucchRntiLst(). If it does not * exist, return RFAILED. * - Add Ue to the ueLst of TPC-Pucch-RNTI control block. * - Update tpcPucchRntiCb pointer in UE. * - Update the pucchIdx value. * - Update isAccumulated and isDeltaMcs variables. * - maxUlRbs = configured maximum UL bandwidth value per UE. * - trgUlCqi = configured value, if any, else cell-wide default trg CQI value. * - If format type is format 3A, update remaining power to +1 * - Update TPC-RNTI information for the cell for Pucch and Pusch. * - Return ROK * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] RgrUeCfg *cfg * @return S16 * -# ROK * -# RFAILED **/ S16 rgSCHPwrUeCfg(RgSchCellCb *cell,RgSchUeCb *ue,RgrUeCfg *cfg) { S16 ret; RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); RgSchCmnUlCell *cellUl = RG_SCH_CMN_GET_UL_CELL(cell); uePwr->maxUePwr = cellPwr->pMax; uePwr->trgCqi = cellPwr->trgUlCqi; /* Overriding with UE's happens later */ uePwr->numRb = 1; uePwr->maxPwrPerRb = uePwr->maxUePwr - rgSchPwrRbToPwrTbl[cellUl->sbSize]; rgSCHPwrUeResetPucch(cell, ue); rgSCHPwrUeResetPusch(cell, ue); /* Rest of the vars update and group power control related * config happens in the function below */ ret = rgSCHPwrApplyUePwrCfg(cell, ue, &cfg->ueUlPwrCfg); return (ret); } /** * @brief Handles power related re-configuration for a UE * * @details * * Function : rgSCHPwrUeRecfg * * Invoking Module Processing: * - This shall be invoked by SCH_GOM at UE re-configuration. * * Processing Steps: * - If change in TPC-RNTI, update the pointer and the TPC RNTI Cb appropriately. * - If accumulation disabled, remove the UE from TPC-RNTI lists of UE, if * it exists. * - If group power configuration disabled, remove the UE from TPC-RNTI lists of UE, if * it exists. * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @param[in] RgrUeRecfg *recfg * * @return S16 * -# ROK * -# RFAILED **/ S16 rgSCHPwrUeRecfg(RgSchCellCb *cell,RgSchUeCb *ue,RgrUeRecfg *recfg) { S16 ret; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); RgrUeUlPwrCfg *pwrCfg = &recfg->ueUlPwrRecfg; if (pwrCfg->p0UePucch != uePwr->p0UePucch) { rgSCHPwrUeResetPucch(cell, ue); } if ((pwrCfg->isAccumulated != uePwr->isAccumulated) || (pwrCfg->p0UePusch != uePwr->p0UePusch)) { rgSCHPwrUeResetPusch(cell, ue); } ret = rgSCHPwrApplyUePwrCfg(cell, ue, &recfg->ueUlPwrRecfg); return (ret); } /*********************************************************** * * Func : rgSCHPwrApplyUePwrCfg * * Desc : Applies power config for UE. Meants to be * used during both power config and reconfig. * * Ret : ROK/RFAILED * * Notes: * * File : * **********************************************************/ static S16 rgSCHPwrApplyUePwrCfg(RgSchCellCb *cell,RgSchUeCb *ue,RgrUeUlPwrCfg *pwrCfg) { S16 ret; RgSchCmnUlUe *ueUl = RG_SCH_CMN_GET_UL_UE(ue, cell); RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); RgSchCmnTpcRntiCb *pucchRntiCb = NULLP; RgSchCmnTpcRntiCb *puschRntiCb = NULLP; uint8_t pucchIdx = 0; uint8_t puschIdx = 0; /* Validate Pucch group power control config */ if (pwrCfg->uePucchPwr.pres) { pucchRntiCb = rgSCHPwrGetPucchRntiCb(cell, pwrCfg->uePucchPwr.tpcRnti); if (pucchRntiCb == NULLP) { return RFAILED; } pucchIdx = pwrCfg->uePucchPwr.idx; ret = rgSCHPwrChkPucchTpcRntiIdx(pucchRntiCb, pucchIdx); if (ret != ROK) { return (ret); } } /* Validate Pusch group power control config */ if (pwrCfg->uePuschPwr.pres) { puschRntiCb = rgSCHPwrGetPuschRntiCb(cell, pwrCfg->uePuschPwr.tpcRnti); if (puschRntiCb == NULLP) { return RFAILED; } puschIdx = pwrCfg->uePuschPwr.idx; ret = rgSCHPwrChkPuschTpcRntiIdx(puschRntiCb, puschIdx); if (ret != ROK) { return (ret); } } /* Apply Pucch group power control config */ if (pucchRntiCb) { if (uePwr->tpcPucchRntiCb != pucchRntiCb) /* This part for recfg */ { if (uePwr->tpcPucchRntiCb) { rgSCHPwrDelUeFrmPucchTpcRntiCb(cell, uePwr->tpcPucchRntiCb, ue); } uePwr->tpcPucchRntiCb = pucchRntiCb; rgSCHPwrAddUeToPucchTpcRntiCb(cell, pucchRntiCb, ue); } uePwr->pucchIdx = pucchIdx; #ifndef ALIGN_64BIT RLOG_ARG4(L_UNUSED,DBG_CELLID,cell->cellId, "PucchRntiCb cfgdUes(%ld %lu %lu) UEID:%d", pucchRntiCb->cfgdUes.count,((uint32_t)pucchRntiCb->cfgdUes.first), ((uint32_t)pucchRntiCb->cfgdUes.last),ue->ueId); RLOG_ARG3(L_UNUSED,DBG_CELLID,cell->cellId, "UEID:%d isFmt3a(%u) ueNode(%ld)", ue->ueId,pucchRntiCb->isFmt3a, pucchRntiCb->schdLnk.node); RLOG_ARG4(L_UNUSED,DBG_CELLID,cell->cellId, "toBeSchdUes(%ld %lu %lu) tpcRnti(%u)", pucchRntiCb->toBeSchdUes.count, ((uint32_t)pucchRntiCb->toBeSchdUes.first), ((uint32_t)pucchRntiCb->toBeSchdUes.last), pucchRntiCb->tpcRnti); #else RLOG_ARG4(L_UNUSED,DBG_CELLID,cell->cellId, "PucchRntiCb cfgdUes(%ld %lu %lu) UEID:%d", pucchRntiCb->cfgdUes.count,((uint64_t)pucchRntiCb->cfgdUes.first), ((uint64_t)pucchRntiCb->cfgdUes.last),ue->ueId); RLOG_ARG3(L_UNUSED,DBG_CELLID,cell->cellId, "UEID:%d isFmt3a(%u) ueNode(%ld)", ue->ueId,pucchRntiCb->isFmt3a, pucchRntiCb->schdLnk.node); RLOG_ARG4(L_UNUSED,DBG_CELLID,cell->cellId, "toBeSchdUes(%ld %lu %lu) tpcRnti(%u)", pucchRntiCb->toBeSchdUes.count, ((uint64_t)pucchRntiCb->toBeSchdUes.first), ((uint64_t)pucchRntiCb->toBeSchdUes.last), pucchRntiCb->tpcRnti); #endif } /* Apply Pusch group power control config */ if (puschRntiCb) { if (uePwr->tpcPuschRntiCb != puschRntiCb) /* This part for recfg */ { if (uePwr->tpcPuschRntiCb) { rgSCHPwrDelUeFrmPuschTpcRntiCb(cell, uePwr->tpcPuschRntiCb, ue); } uePwr->tpcPuschRntiCb = puschRntiCb; rgSCHPwrAddUeToPuschTpcRntiCb(puschRntiCb, ue); } uePwr->puschIdx = puschIdx; } /* Update vars */ uePwr->isAccumulated = pwrCfg->isAccumulated; uePwr->deltaMcsEnbld = pwrCfg->isDeltaMCSEnabled; if (pwrCfg->trgCqi) { uePwr->trgCqi = pwrCfg->trgCqi; } if (ueUl->maxUlCqi < uePwr->trgCqi) { uePwr->trgCqi = ueUl->maxUlCqi; } uePwr->p0UePusch = pwrCfg->p0UePusch; uePwr->p0UePucch = pwrCfg->p0UePucch; return ROK; } /** * @brief Deletes power related information for UE * * @details * * Function : rgSCHPwrUeDel * * Invoking Module Processing: * - This shall be invoked by at the time of UE deletion. * * Processing Steps: * - if (ue->tpcPucchRntiCb) * - delete UE from tpcPucchRntiCb->ueLst * - ue->tpcPucchRntiCb = NULLP * - If in (ue->tpcPucchRntiCb->toBeSchdLst) * - remove from the list. * - if (ue->tpcPuschRntiCb) * - delete UE from tpcPuschRntiCb->ueLst * - ue->tpcPuschRntiCb = NULLP * - If in (ue->tpcPuschRntiCb->toBeSchdLst) * - remove from the list. * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return Void **/ Void rgSCHPwrUeDel(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); if (uePwr->tpcPucchRntiCb) { rgSCHPwrDelUeFrmPucchTpcRntiCb(cell, uePwr->tpcPucchRntiCb, ue); uePwr->tpcPucchRntiCb = NULLP; } if (uePwr->tpcPuschRntiCb) { rgSCHPwrDelUeFrmPuschTpcRntiCb(cell, uePwr->tpcPuschRntiCb, ue); uePwr->tpcPuschRntiCb = NULLP; } return; } /** * @brief Resets UE's power state * * @details * * Function : rgSCHPwrUeReset * * Invoking Module Processing: * - This shall be invoked by at the time PDCCH order. * * Processing Steps: * - Reset PUSCH power state * - Reset PUCCH power state * * @param[in] RgSchCellCb *cell * @param[in] RgSchUeCb *ue * @return Void **/ Void rgSCHPwrUeReset(RgSchCellCb *cell,RgSchUeCb *ue) { rgSCHPwrUeResetPucch(cell, ue); rgSCHPwrUeResetPusch(cell, ue); return; } /*********************************************************** * * Func : rgSCHPwrUeResetPucch * * Desc : This function is called to reset UE * to initial PUCCH power state. * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrUeResetPucch(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); uePwr->pucchTpc = 1; uePwr->remPucchPwr = 0; if (uePwr->tpcPucchRntiCb) { rgSCHPwrRmvSchdUeFrmPucchTpcRntiCb(cell, uePwr->tpcPucchRntiCb, ue); } /* Stack Crash problem for TRACE5 changes. Added the line below */ return; } /*********************************************************** * * Func : rgSCHPwrUeResetPusch * * Desc : This function is called to reset UE * to initial PUSCH power state. * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrUeResetPusch(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); uePwr->isPhrAvail = FALSE; uePwr->phVal = 40; uePwr->maxUlRbs = RGSCH_MAX_DL_BW; uePwr->delta = 0; uePwr->puschTpc = 1; uePwr->remPuschPwr = 0; if (uePwr->tpcPuschRntiCb) { rgSCHPwrRmvSchdUeFrmPuschTpcRntiCb(cell, uePwr->tpcPuschRntiCb, ue); } return; } /*********************************************************** * * Func : rgSCHPwrOnPuschPwrUpd * * Desc : This function is called whenever 'remPuschPwr' * is updated * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrOnPuschPwrUpd(RgSchCellCb *cell,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, cell); RgSchCmnTpcRntiCb *cb; Bool toAdd; if ((cb = uePwr->tpcPuschRntiCb) == NULLP) { return; } /* Not checking for uwPwr->isPhrAvail as uePwr->phVal * is set to a large value initially */ toAdd = FALSE; if ((uePwr->phVal != 0) && (uePwr->remPuschPwr != 0)) { toAdd = TRUE; } if (toAdd) { rgSCHPwrAddSchdUeToPuschTpcRntiCb(cell, cb, ue); } else { rgSCHPwrRmvSchdUeFrmPuschTpcRntiCb(cell, cb, ue); } return; } /*********************************************************** * * Func : rgSCHPwrAddRntiToPucchRntiLst * * * Desc : Adds RNTI to Pucch Rnti list and updates requisite * information. * * Ret : Void * * Notes: Assumed that array bounds are checked * in caller before adding. * * File : * **********************************************************/ static Void rgSCHPwrAddRntiToPucchRntiLst(RgSchCellCb *cell,CmLteRnti rnti,Bool isFmt3a) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); rgSCHPwrInitTpcRntiCb(&cellPwr->tpcPucchRntiLst[cellPwr->tpcPucchRntiCnt++], rnti, isFmt3a); return; } /*********************************************************** * * Func : rgSCHPwrAddRntiToPuschRntiLst * * * Desc : Adds RNTI to Pusch Rnti list and updates requisite * information. * * Ret : Void * * Notes: Assumed that array bounds are checked * in caller before adding. * * File : * **********************************************************/ static Void rgSCHPwrAddRntiToPuschRntiLst(RgSchCellCb *cell,CmLteRnti rnti,Bool isFmt3a) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); rgSCHPwrInitTpcRntiCb(&cellPwr->tpcPuschRntiLst[cellPwr->tpcPuschRntiCnt++], rnti, isFmt3a); return; } /*********************************************************** * * Func : rgSCHPwrInitTpcRntiCb * * * Desc : Initialises a TPC RNTI CB * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrInitTpcRntiCb(RgSchCmnTpcRntiCb *cb,CmLteRnti rnti,Bool isFmt3a) { memset(cb, 0, sizeof(*cb)); cb->tpcRnti = rnti; cb->isFmt3a = isFmt3a; /* Not initialising lists as memset 0 takes care of it */ /* cb->schdLnk.node is set when this rnti is to be scheduled */ return; } /*********************************************************** * * Func : rgSCHPwrGetPucchRntiCb * * * Desc : Gets TPC RNTI control block from Pucch rnti list * * Ret : RgSchCmnTpcRntiCb * - Success * NULLP - Fail * * Notes: * * File : * **********************************************************/ static RgSchCmnTpcRntiCb* rgSCHPwrGetPucchRntiCb(RgSchCellCb *cell,CmLteRnti tpcRnti) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); uint16_t idx; if (!cellPwr->tpcPucchRntiCnt) { return (NULLP); } for (idx = 0; idx < cellPwr->tpcPucchRntiCnt; ++idx) { if (cellPwr->tpcPucchRntiLst[idx].tpcRnti == tpcRnti) { return (&cellPwr->tpcPucchRntiLst[idx]); } } return (NULLP); } /*********************************************************** * * Func : rgSCHPwrGetPuschRntiCb * * * Desc : Gets TPC RNTI control block from Pusch rnti list * * Ret : RgSchCmnTpcRntiCb * - Success * NULLP - Fail * * Notes: * * File : * **********************************************************/ static RgSchCmnTpcRntiCb* rgSCHPwrGetPuschRntiCb(RgSchCellCb *cell,CmLteRnti tpcRnti) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); uint16_t idx; if (!cellPwr->tpcPuschRntiCnt) { return (NULLP); } for (idx = 0; idx < cellPwr->tpcPuschRntiCnt; ++idx) { if (cellPwr->tpcPuschRntiLst[idx].tpcRnti == tpcRnti) { return (&cellPwr->tpcPuschRntiLst[idx]); } } return (NULLP); } /*********************************************************** * * Func : rgSCHPwrAddUeToPucchTpcRntiCb * * * Desc : Add UE to cfgd list of UEs in rnti cb * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrAddUeToPucchTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); UNUSED(cell); cmLListAdd2Tail(&cb->cfgdUes, &uePwr->pucchGrpLnk); uePwr->pucchGrpLnk.node = (PTR)ue; return; } /*********************************************************** * * Func : rgSCHPwrDelUeFrmPucchTpcRntiCb * * * Desc : Remove UE from Pucch TPC RNTI CB * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrDelUeFrmPucchTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); rgSCHPwrRmvSchdUeFrmPucchTpcRntiCb(cell, cb, ue); cmLListDelFrm(&cb->cfgdUes, &uePwr->pucchGrpLnk); uePwr->pucchGrpLnk.node = NULLP; return; } /*********************************************************** * * Func : rgSCHPwrRmvSchdUeFrmPucchTpcRntiCb * * * Desc : Remove UE from to-be-scheduled list of UEs * in Pusch RNTI CB * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrRmvSchdUeFrmPucchTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->schdPucchGrpLnk.node == NULLP) { return; } rgSCHPwrRmvSchdUeOnlyFrmPucchTpcRntiCb(cell, cb, ue); if (!cb->toBeSchdUes.count) { rgSCHPwrRmvSchdPucchTpcRntiCb(cell, cb); } return; } /*********************************************************** * * Func : rgSCHPwrRmvSchdUeOnlyFrmPucchTpcRntiCb * * Desc : Remove UE from to-be-scheduled list of UEs * in Pucch RNTI CB. Do not both about * possibly removing Pucch RNTI CB from * the cell wide to-be-scheduled list. * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrRmvSchdUeOnlyFrmPucchTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->schdPucchGrpLnk.node != NULLP) { cmLListDelFrm(&cb->toBeSchdUes, &uePwr->schdPucchGrpLnk); uePwr->schdPucchGrpLnk.node = NULLP; } return; } /*********************************************************** * * Func : rgSCHPwrRmvSchdPucchTpcRntiCb * * Desc : Remove Pucch TPC RNTI CB from to-be-scheduled * list in cell * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrRmvSchdPucchTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); if (cb->schdLnk.node == NULLP) { return; } cmLListDelFrm(&cellPwr->pucchGrpPwr, &cb->schdLnk); cb->schdLnk.node = NULLP; return; } /*********************************************************** * * Func : rgSCHPwrAddSchdUeToPucchTpcRntiCb * * Desc : Add UE to to-be-scheduled list of UEs * in Pucch RNTI CB * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrAddSchdUeToPucchTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->schdPucchGrpLnk.node != NULLP) { /* UE is already in the list */ return; } cmLListAdd2Tail(&cb->toBeSchdUes, &uePwr->schdPucchGrpLnk); uePwr->schdPucchGrpLnk.node = (PTR)ue; if (cb->toBeSchdUes.count == 1) { /* This is first UE, so queue up this TPC RNTI * for scheduling */ rgSCHPwrAddSchdPucchTpcRntiCb(cell, cb); } return; } /*********************************************************** * * Func : rgSCHPwrAddSchdPucchTpcRntiCb * * Desc : Add Pucch TPC RNTI CB from to-be-scheduled * list in cell * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrAddSchdPucchTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); cmLListAdd2Tail(&cellPwr->pucchGrpPwr, &cb->schdLnk); cb->schdLnk.node = (PTR)cb; return; } /*********************************************************** * * Func : rgSCHPwrAddUeToPuschTpcRntiCb * * * Desc : Add UE to cfgd list of UEs in rnti cb * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrAddUeToPuschTpcRntiCb(RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); cmLListAdd2Tail(&cb->cfgdUes, &uePwr->puschGrpLnk); uePwr->puschGrpLnk.node = (PTR)ue; return; } /*********************************************************** * * Func : rgSCHPwrAddSchdUeToPuschTpcRntiCb * * Desc : Add UE to to-be-scheduled list of UEs * in Pusch RNTI CB * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrAddSchdUeToPuschTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->schdPuschGrpLnk.node != NULLP) { /* UE is already in the list */ return; } cmLListAdd2Tail(&cb->toBeSchdUes, &uePwr->schdPuschGrpLnk); uePwr->schdPuschGrpLnk.node = (PTR)ue; if (cb->toBeSchdUes.count == 1) { /* This is first UE, so queue up this TPC RNTI * for scheduling */ rgSCHPwrAddSchdPuschTpcRntiCb(cell, cb); } return; } /*********************************************************** * * Func : rgSCHPwrDelUeFrmPuschTpcRntiCb * * * Desc : Add UE to cfgd list of UEs in rnti cb * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrDelUeFrmPuschTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); rgSCHPwrRmvSchdUeFrmPuschTpcRntiCb(cell, cb, ue); cmLListDelFrm(&cb->cfgdUes, &uePwr->puschGrpLnk); uePwr->puschGrpLnk.node = NULLP; return; } /*********************************************************** * * Func : rgSCHPwrRmvSchdUeFrmPuschTpcRntiCb * * Desc : Remove UE from to-be-scheduled list of UEs * in Pusch RNTI CB * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrRmvSchdUeFrmPuschTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->schdPuschGrpLnk.node == NULLP) { return; } rgSCHPwrRmvSchdUeOnlyFrmPuschTpcRntiCb(cell, cb, ue); if (!cb->toBeSchdUes.count) { rgSCHPwrRmvSchdPuschTpcRntiCb(cell, cb); } return; } /*********************************************************** * * Func : rgSCHPwrRmvSchdUeOnlyFrmPuschTpcRntiCb * * Desc : Remove UE from to-be-scheduled list of UEs * in Pusch RNTI CB. Do not both about * possibly removing Pusch RNTI CB from * the cell wide to-be-scheduled list. * * Ret : * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrRmvSchdUeOnlyFrmPuschTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb,RgSchUeCb *ue) { RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->schdPuschGrpLnk.node != NULLP) { cmLListDelFrm(&cb->toBeSchdUes, &uePwr->schdPuschGrpLnk); uePwr->schdPuschGrpLnk.node = NULLP; } return; } /*********************************************************** * * Func : rgSCHPwrAddSchdPuschTpcRntiCb * * Desc : Add Pusch TPC RNTI CB from to-be-scheduled * list in cell * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrAddSchdPuschTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); cmLListAdd2Tail(&cellPwr->puschGrpPwr, &cb->schdLnk); cb->schdLnk.node = (PTR)cb; return; } /*********************************************************** * * Func : rgSCHPwrRmvSchdPuschTpcRntiCb * * Desc : Remove Pusch TPC RNTI CB from to-be-scheduled * list in cell * * Ret : Void * * Notes: * * File : * **********************************************************/ static Void rgSCHPwrRmvSchdPuschTpcRntiCb(RgSchCellCb *cell,RgSchCmnTpcRntiCb *cb) { RgSchCmnUlPwrCb *cellPwr = RG_SCH_PWR_GETCELLPWR(cell); if (cb->schdLnk.node == NULLP) { return; } cmLListDelFrm(&cellPwr->puschGrpPwr, &cb->schdLnk); cb->schdLnk.node = NULLP; return; } /*********************************************************** * * Func : rgSCHPwrChkPucchTpcRntiIdx * * Desc : Validate that the given index is OK to * be assigned to a new UE for the Pucch TPC * RNTI * * Ret : * * Notes: * * File : * **********************************************************/ static S16 rgSCHPwrChkPucchTpcRntiIdx(RgSchCmnTpcRntiCb *cb,uint8_t idx) { if (rgSCHPwrChkTpcRntiIdx(cb, idx) != ROK) { return RFAILED; } if (rgSCHPwrChkUniqPucchTpcRntiIdx(cb, idx) != ROK) { return RFAILED; } return ROK; } /*********************************************************** * * Func : rgSCHPwrChkPuschTpcRntiIdx * * Desc : Validate that the given index is OK to * be assigned to a new UE for the Pusch TPC * RNTI * * Ret : * * Notes: * * File : * **********************************************************/ static S16 rgSCHPwrChkPuschTpcRntiIdx(RgSchCmnTpcRntiCb *cb,uint8_t idx) { if (rgSCHPwrChkTpcRntiIdx(cb, idx) != ROK) { return RFAILED; } if (rgSCHPwrChkUniqPuschTpcRntiIdx(cb, idx) != ROK) { return RFAILED; } return ROK; } /*********************************************************** * * Func : rgSCHPwrChkUniqPucchTpcRntiIdx * * Desc : Validate index against format type of TPC RNTI * * Ret : * * Notes: * * File : * **********************************************************/ static S16 rgSCHPwrChkUniqPucchTpcRntiIdx(RgSchCmnTpcRntiCb *cb,uint8_t idx) { CmLList *lnk; for (lnk = cb->cfgdUes.first; lnk; lnk = lnk->next) { RgSchUeCb *ue = (RgSchUeCb *)lnk->node; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->pucchIdx == idx) { return RFAILED; } } return ROK; } /*********************************************************** * * Func : rgSCHPwrChkUniqPuschTpcRntiIdx * * Desc : Validate index against format type of TPC RNTI * * Ret : * * Notes: * * File : * **********************************************************/ static S16 rgSCHPwrChkUniqPuschTpcRntiIdx(RgSchCmnTpcRntiCb *cb,uint8_t idx) { CmLList *lnk; for (lnk = cb->cfgdUes.first; lnk; lnk = lnk->next) { RgSchUeCb *ue = (RgSchUeCb *)lnk->node; RgSchCmnUeUlPwrCb *uePwr = RG_SCH_PWR_GETUEPWR(ue, ue->cell); if (uePwr->puschIdx == idx) { return RFAILED; } } return ROK; } /*********************************************************** * * Func : rgSCHPwrChkTpcRntiIdx * * Desc : Validate index against format type of TPC RNTI. * * Ret : * * Notes: * * File : * **********************************************************/ static S16 rgSCHPwrChkTpcRntiIdx(RgSchCmnTpcRntiCb *cb,uint8_t idx) { if (cb->isFmt3a) { if (idx >= TFU_MAX_1BIT_TPC) { return RFAILED; } } else { if (idx >= TFU_MAX_2BIT_TPC) { return RFAILED; } } return ROK; } /* Warning Fix: Commenting out as not used */ /*********************************************************** * * Func : rgSCHPwrGetPCMaxValFromPCMax * * Desc : Returns the power headroom in dB * corresponding to a power headroom * report * * Ret : S8 * * Notes: * * File : * **********************************************************/ static S8 rgSCHPwrGetPCMaxValFromPCMax(uint8_t pCMax) { return ((pCMax & 63) - 30); } /*********************************************************** * * Func : rgSCHPwrGetPhValFromPhr * * Desc : Returns the power headroom in dB * corresponding to a power headroom * report * * Ret : S8 * * Notes: * * File : * **********************************************************/ static S8 rgSCHPwrGetPhValFromPhr(uint8_t phr) { return ((phr & 63) - 23); } /********************************************************************** End of file **********************************************************************/