--- /dev/null
+/*******************************************************************************
+################################################################################
+# 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: NR RLC Layer
+
+ Type: C file
+
+ Desc: Source code for RLC Unacknowledged mode assembly and
+ reassembly.This file contains following functions
+
+ --rlcUmmQSdu
+ --rlcUmmProcessSdus
+ --rlcUmmProcessPdus
+ --rlcUmmReAssembleSdus
+ --kwUmmReEstablish
+
+ File: rlc_umm_dl.c
+
+**********************************************************************/
+/**
+ * @file rlc_umm_dl.c
+ * @brief RLC Unacknowledged Mode downlink module
+*/
+
+/* header (.h) include files */
+#include "common_def.h"
+#include "ckw.h" /* RRC layer */
+#include "lkw.h" /* RRC layer */
+#include "kwu.h" /* RLC service user */
+#include "lkw.h" /* LM Interface */
+#include "rgu.h" /* MAC layer */
+#include "rlc_env.h" /* RLC environment options */
+#include "rlc_err.h"
+
+
+
+
+/* header/extern include files (.x) */
+
+#include "ckw.x" /* RRC layer */
+#include "kwu.x" /* RLC service user */
+#include "lkw.x" /* LM Interface */
+#include "rgu.x" /* MAC later */
+
+#include "rlc_utils.h" /* RLC layer */
+#include "rlc_dl_ul_inf.h"
+#include "rlc_dl.h"
+
+#define RLC_MODULE (RLC_DBGMASK_UM | RLC_DBGMASK_DL)
+
+/* variables for logging :declared in BRDCM cl */
+#ifndef TENB_ACC
+uint32_t dldrops_kwu_um;
+uint32_t dlpkt_um;
+uint32_t buffer_occ;
+uint32_t dlrate_kwu;
+#endif
+
+static Void rlcUmmCreatePdu ARGS ((RlcCb *gCb,
+ RlcDlRbCb *rbCb,
+ Buffer *pdu,
+ RlcUmHdr *umHdr,
+ KwPduInfo *datReqPduInfo));
+
+/** @addtogroup ummode */
+/*@{*/
+
+/**
+ * @brief Handler to queue a SDU in the SDU queue, update BO and report
+ * it to the lower layer.
+ *
+ * @details
+ * This function is used to queue the received SDU in the
+ * SDU queue maintained in the radio bearer control block.
+ * After queuing the SDU, BO is updated and is reported
+ * to the lower layer.
+ *
+ * @param[in] gCb RLC Instance control block
+ * @param[in] rbCb RB control block
+ * @param[in] datReq Ptr to the datReq sent from PDCP
+ * @param[in] mBuf Sdu data
+ *
+ * @return Void
+*/
+void rlcUmmQSdu(RlcCb *gCb, RlcDlRbCb *rbCb, RlcDatReqInfo *datReq, Buffer *mBuf)
+{
+ MsgLen len; /* SDU buffer length */
+ RlcSdu *sdu; /* SDU */
+
+ RLC_UPD_L2_DL_TOT_SDU_STS(gCb,rbCb);
+
+ RLC_ALLOC_WC(gCb, sdu, (Size)sizeof(RlcSdu));
+#if (ERRCLASS & ERRCLS_ADD_RES)
+ if ( sdu == NULLP )
+ {
+ DU_LOG("\nERROR --> RLC DL : Memory allocation failed in rlcUmmQSdu for UEID:%d CELLID:%d",\
+ rbCb->rlcId.ueId,
+ rbCb->rlcId.cellId);
+ ODU_PUT_MSG_BUF(mBuf);
+ return;
+ }
+#endif /* ERRCLASS & ERRCLS_ADD_RES */
+
+/* Discard new changes starts */
+ rlcUtlGetCurrTime(&sdu->arrTime);
+/* Discard new changes ends */
+ ODU_GET_MSG_LEN(mBuf,&len);
+
+ sdu->mBuf = mBuf;
+ sdu->sduSz = len;
+ sdu->actSz = len;
+ sdu->mode.um.sduId = datReq->sduId;
+ sdu->mode.um.isSegmented = FALSE;
+#ifndef RGL_SPECIFIC_CHANGES
+#ifndef TENB_ACC
+#ifndef LTE_PAL_ENB
+ {
+ dlrate_kwu += len;
+ }
+#endif
+#endif
+#endif
+ rbCb->m.umDl.bo += len;
+ rbCb->m.umDl.bo += RLC_MAX_HDRSZ;
+ cmLListAdd2Tail(&(rbCb->m.umDl.sduQ), &sdu->lstEnt);
+ sdu->lstEnt.node = (PTR)sdu;
+
+ if(!rlcDlUtlIsReestInProgress(rbCb))
+ {
+ rlcUtlSendDedLcBoStatus(gCb, rbCb, rbCb->m.umDl.bo, 0, FALSE,0);
+ }
+
+ /* kw005.201 added support for L2 Measurement */
+#ifdef LTE_L2_MEAS_RLC
+ /* Update numActUe if it is not active */
+ if((rbCb->rbL2Cb.measOn & LKW_L2MEAS_ACT_UE) &&
+ (rbCb->ueCb->numActRb[rbCb->qci]++ == 0))
+ {
+ rlcCb.rlcL2Cb.numActUe[rbCb->qci]++;
+ }
+#endif
+
+ return;
+}
+
+
+/**
+ * @brief Handler to form PDU(s) and update the BO.
+ *
+ * @details
+ * -# This function forms pdu(s) from the SDU(s) in the
+ * SDU queue and returns them.
+ * -# This function also updates the BO along with the
+ * along with the estimated Header size.
+ *
+ * @param[in] rbCb RB control block
+ * @param[out] pduInfo Pdu Info to be filled and the PDU size to be
+ * formed and the updated BO
+ * @param[in] pduSz The size for which PDUs have to constructed
+ *
+ * @return S16
+ * -# ROK In case of success
+ * -# RFAILED If allocation of Sdu fails
+*/
+void rlcUmmProcessSdus(RlcCb *gCb, RlcDlRbCb *rbCb, RlcDatReq *datReq)
+{
+ CmLList *firstNode; /* First Node in SDU queue */
+ Buffer *pdu; /* Buffer for holding the formed PDU */
+ KwPduInfo *pduInfo; /* PDU Info pointer */
+ int16_t pduSz; /* PDU Size to be constructed */
+ RlcUmHdr umHdr; /* Header */
+ uint32_t rlcHdrSz;
+ uint32_t rlcSduSz;
+ uint32_t rlcPduSz;
+ uint32_t macHdrSz;
+
+ /* kw005.201 added support for L2 Measurement */
+#ifdef LTE_L2_MEAS
+ RlcContSduLst contSduLst; /*Contained sduLst */
+ int32_t dataVol = rbCb->m.umDl.bo;
+ uint32_t* totMacGrant= &(datReq->totMacGrant);
+ RlcL2MeasDlIpTh *dlIpThPut = &rbCb->l2MeasIpThruput.dlIpTh;
+ uint8_t *sduIdx = &dlIpThPut->lastSduIdx;
+ bool newIdx = FALSE;
+ int32_t oldBo;
+ RlclchInfo lchInfo = {0};
+ uint32_t segSduCnt = 0;
+#endif
+ Ticks curTime = 0;
+ int16_t timeDiff = 0;
+ RlcSdu *sdu;
+
+ pdu = NULLP;
+ pduInfo = &(datReq->pduInfo);
+ pduSz = datReq->pduSz;
+
+#ifdef LTE_L2_MEAS
+ contSduLst.numSdus = 0;
+ contSduLst.lcId = rbCb->lch.lChId;
+ oldBo = rbCb->m.umDl.bo;
+ lchInfo.lcId = rbCb->lch.lChId;
+ lchInfo.numSdus = 0;
+#endif
+
+ /* Discard new changes starts */
+ rlcUtlGetCurrTime(&curTime);
+
+ /* ccpu00143043 */
+ while ((pduSz > 0) && (rbCb->m.umDl.sduQ.count > 0) && (pduInfo->numPdu < RLC_MAX_PDU))
+ {
+ CM_LLIST_FIRST_NODE(&rbCb->m.umDl.sduQ,firstNode);
+ sdu = (RlcSdu *)(firstNode->node);
+
+ if ((sdu->mode.um.isSegmented == FALSE) && (rbCb->discTmrInt > 0) &&
+ (rbCb->rlcId.rbType == CM_LTE_DRB))
+ {
+ timeDiff = RLC_TIME_DIFF(curTime,sdu->arrTime);
+
+ if (timeDiff >= rbCb->discTmrInt)
+ {
+#ifdef LTE_L2_MEAS
+ RLC_UPD_L2_DL_DISC_SDU_STS(gCb, rbCb);
+#endif
+ rbCb->m.umDl.bo -= sdu->sduSz;
+ RLC_REMOVE_SDU(gCb,&rbCb->m.umDl.sduQ,sdu);
+ continue;
+ }
+ }
+#ifdef LTE_L2_MEAS
+ newIdx = FALSE;
+#endif
+ /* When forming a new PDU, pdu == NULLP
+ -# Eliminate MAC header size for each pdu
+ -# Substract the fixed header length based on SN length
+ */
+ /* account for the RLC header size
+ minimum header size will be 1 , if Sdu is not segmented */
+ rlcHdrSz = RLC_MIN_HDRSZ;
+ if(sdu->mode.um.isSegmented)
+ {
+ /* value of rbCb->m.umDl.snLen will be 1 for 6 bit SN and 2 for 12 bit SN and 2 bytes of SO */
+ rlcHdrSz = (rbCb->m.umDl.snLen + 2);
+ }
+ macHdrSz = RLC_MAC_HDR_SZ2; /*Minimum MacHdr size */
+ rlcSduSz = sdu->sduSz;
+ rlcPduSz = ((rlcSduSz + rlcHdrSz) < (pduSz - macHdrSz))? (rlcSduSz + rlcHdrSz) : (pduSz - macHdrSz);
+ rlcSduSz = rlcPduSz - rlcHdrSz;
+
+ /*Estimate MAC Hdr based on calculated rlcPduSz */
+ macHdrSz = (rlcPduSz > 255 ) ? RLC_MAC_HDR_SZ3 : RLC_MAC_HDR_SZ2;
+
+ if(macHdrSz != RLC_MAC_HDR_SZ2)
+ {
+ rlcSduSz = sdu->sduSz;
+ rlcPduSz = ((rlcSduSz + rlcHdrSz) < (pduSz - macHdrSz))? (rlcSduSz + rlcHdrSz) : (pduSz - macHdrSz);
+ rlcSduSz = rlcPduSz - rlcHdrSz;
+ macHdrSz = (rlcPduSz > 255 ) ? RLC_MAC_HDR_SZ3 : RLC_MAC_HDR_SZ2;
+ }
+
+ if(sdu->mode.um.isSegmented == FALSE)
+ {
+ /* RLC SDU is estimated to be segmented first time */
+ if(rlcSduSz < sdu->sduSz)
+ {
+ rlcHdrSz = rbCb->m.umDl.snLen;
+ rlcSduSz = sdu->sduSz;
+ rlcPduSz = ((rlcSduSz + rlcHdrSz) < (pduSz - macHdrSz))? (rlcSduSz + rlcHdrSz) : (pduSz - macHdrSz);
+ rlcSduSz = rlcPduSz - rlcHdrSz;
+ /*Estimate MAC Hdr based on calculated rlcPduSz */
+ macHdrSz = (rlcPduSz > 255 ) ? RLC_MAC_HDR_SZ3 : RLC_MAC_HDR_SZ2;
+ }
+ }
+
+ pduSz -= (rlcHdrSz + macHdrSz);
+
+ if(pduSz <= 0)
+ {
+ break;
+ }
+
+ /* No Segmentation scenario :
+ If SDU size is less than or equal to the requested PDU size
+ -# Allocate memory and copy SDU into it.
+ -# Update BO
+ -# Remove SDU from the Queue.
+ */
+ if (sdu->sduSz <= pduSz)
+ {
+ if (!pdu)
+ {
+ pdu = sdu->mBuf;
+ sdu->mBuf = NULLP;
+ }
+ rbCb->m.umDl.bo -= sdu->sduSz;
+ rbCb->m.umDl.bo -= RLC_MAX_HDRSZ;
+ pduSz -= sdu->sduSz;
+
+#ifdef LTE_L2_MEAS
+ if(RLC_MEAS_IS_DL_ANY_MEAS_ON_FOR_RB(gCb,rbCb))
+ {
+ if(sdu->mode.um.isSegmented)
+ {
+ *sduIdx = dlIpThPut->lastSduIdx;
+ }
+ else
+ {
+ RLC_GETSDUIDX(*sduIdx);
+ newIdx = TRUE;
+ }
+ rlcUtlUpdateContainedSduLst(*sduIdx, &contSduLst);
+ rlcUtlUpdateOutStandingSduLst(dlIpThPut, *sduIdx, sdu->actSz,
+ sdu->mode.um.sduId, newIdx);
+ /* ccpu00143043 */
+ if ( lchInfo.numSdus < RLC_L2MEAS_SDUIDX)
+ {
+ lchInfo.sduInfo[lchInfo.numSdus].arvlTime = sdu->arrTime;
+ lchInfo.sduInfo[lchInfo.numSdus].isRetxPdu = FALSE;
+ lchInfo.numSdus++;
+ }
+ }
+#endif
+/* kw005.201 added support for L2 Measurement */
+#ifdef LTE_L2_MEAS_RLC
+ rlcUtlUpdSduSnMap(rbCb, sdu, datReq, TRUE);
+ if((rbCb->rbL2Cb.measOn & LKW_L2MEAS_DL_DELAY) ||
+ (rbCb->rbL2Cb.measOn & LKW_L2MEAS_UU_LOSS))
+ {
+ /* ccpu00143043 */
+ if ( lchInfo.numSdus < RLC_L2MEAS_SDUIDX)
+ {
+ lchInfo.arvlTime[lchInfo.numSdus] = sdu->arrTime;
+ lchInfo.numSdus++;
+ }
+ }
+#endif /* LTE_L2_MEAS */
+
+ if(sdu->mode.um.isSegmented)
+ {
+ umHdr.si = RLC_SI_LAST_SEG;
+ umHdr.so = sdu->actSz - sdu->sduSz;
+ sdu->mode.um.isSegmented = FALSE;
+ }
+ else
+ {
+ umHdr.si = 0;
+ umHdr.so = 0;
+ }
+ rlcUmmCreatePdu(gCb, rbCb, pdu, &umHdr, pduInfo);
+ RLC_REMOVE_SDU(gCb,&(rbCb->m.umDl.sduQ),sdu); /* kw003.201 */
+ rlcUtlIncrementKwuStsSduTx(gCb->u.dlCb->rlcKwuDlSap + rbCb->k1wuSapId);
+ pdu = NULLP;
+ }
+ /* Segmentation scenario :
+ If size of SDU is greater than PDU size
+ -# Allocate memory and Segment the Sdu.
+ -# Update BO
+ -# Add segment to the PDU
+ -# Set the second bit of the segmentation info.
+ -# Create the complete PDU and place in pduInfo.
+ */
+ else
+ {
+ Buffer *remSdu;
+
+ ODU_SEGMENT_MSG(sdu->mBuf,pduSz,&remSdu);
+
+#ifdef LTE_L2_MEAS
+ if(RLC_MEAS_IS_DL_IP_MEAS_ON_FOR_RB(gCb, rbCb))
+ {
+ if(sdu->mode.um.isSegmented)
+ {
+ *sduIdx = dlIpThPut->lastSduIdx;
+ }
+ else
+ {
+ RLC_GETSDUIDX(*sduIdx);
+ newIdx = TRUE;
+ }
+ rlcUtlUpdateContainedSduLst(*sduIdx, &contSduLst);
+ rlcUtlUpdateOutStandingSduLst(dlIpThPut, *sduIdx, sdu->actSz,
+ sdu->mode.um.sduId, newIdx);
+ }
+ if(RLC_MEAS_IS_DL_UU_LOSS_MEAS_ON_FOR_RB(gCb,rbCb))
+ {
+ if(sdu->actSz == sdu->sduSz)
+ {
+ segSduCnt++;
+ }
+ }
+#endif
+ if(sdu->mode.um.isSegmented)
+ {
+ umHdr.si = RLC_SI_MID_SEG;
+ umHdr.so = sdu->actSz - sdu->sduSz;
+ }
+ else
+ {
+ umHdr.si = RLC_SI_FIRST_SEG;
+ umHdr.so = 0;
+ sdu->mode.um.isSegmented = TRUE;
+ }
+ pdu = sdu->mBuf;
+ sdu->sduSz -= pduSz;
+ rbCb->m.umDl.bo -= pduSz;
+ sdu->mBuf = remSdu;
+ pduSz = 0;
+
+/* kw005.201 added support for L2 Measurement */
+#ifdef LTE_L2_MEAS_RLC
+ rlcUtlUpdSduSnMap(rbCb, sdu, datReq, FALSE);
+#endif /* LTE_L2_MEAS */
+
+ rlcUmmCreatePdu(gCb, rbCb, pdu, &umHdr, pduInfo);
+ pdu = NULLP;
+ }
+/* kw005.201 added support for L2 Measurement */
+ }
+#ifdef LTE_L2_MEAS_RLC
+ if((rbCb->rbL2Cb.measOn) &&
+ (rbCb->m.umDl.sduQ.count == 0) &&
+ (dataWasPrsnt))
+ {
+ if(--(rbCb->ueCb->numActRb[rbCb->qci]) == 0)
+ {
+ rlcCb.rlcL2Cb.numActUe[rbCb->qci]--;
+ }
+ }
+#endif /* LTE_L2_MEAS */
+#ifdef LTE_L2_MEAS
+ rlcUtlUpdateBurstSdus(gCb, rbCb, &contSduLst, dataVol, *totMacGrant);
+ /* Need to check into optimizing this code : TODO */
+ if(RLC_MEAS_IS_DL_ANY_MEAS_ON_FOR_RB(gCb,rbCb) && (lchInfo.numSdus != 0))
+ {
+ RlcL2MeasTb *l2MeasTb = rlcUtlGetCurMeasTb(gCb, rbCb);
+ /* ccpu00143043 */
+ /* Fix Klock warning */
+ if ((lchInfo.numSdus != 0) && (l2MeasTb != NULLP) &&
+ (l2MeasTb->numLchInfo < RLC_MAX_ACTV_DRB))
+ {
+ memcpy( &l2MeasTb->lchInfo[l2MeasTb->numLchInfo], &lchInfo, sizeof(RlclchInfo));
+ l2MeasTb->numLchInfo++;
+ }
+ l2MeasTb->txSegSduCnt += segSduCnt;
+ }
+ *totMacGrant -= (oldBo - rbCb->m.umDl.bo);
+#endif
+
+ datReq->boRep.bo = rbCb->m.umDl.bo;
+ datReq->boRep.estHdrSz = 0;
+ datReq->boRep.staPduPrsnt = FALSE;
+ if (rbCb->m.umDl.sduQ.count > 0)
+ {
+ datReq->boRep.oldestSduArrTime =
+ ((RlcSdu *)(rbCb->m.umDl.sduQ.first->node))->arrTime;
+ }
+ return;
+}
+
+/**
+ * @brief Handler to process the re-establishment request received from i
+ * the upper layer.
+ *
+ * @details
+ * This function does the following functions :
+ * Remove all the SDUs in the SDU queue.
+ *
+ * @param[in] gCb RLC Instance control block
+ * @param[in] rlcID Identity of the RLC entity for which
+ * re-establishment is to be done
+ * @param[in] sendReEst Whether to send re-establishment complete
+ * indication to upper layer or not
+ * @param[in] rbCb RB control block for which re-establishment
+ * is to be done
+ *
+ * @return Void
+*/
+Void rlcDlUmmReEstablish(RlcCb *gCb,CmLteRlcId rlcId,Bool sendReEst,RlcDlRbCb *rbCb)
+{
+ /* The re-establishment indication is sent from the UL only */
+
+ rlcUmmFreeDlRbCb(gCb, rbCb);
+
+ rbCb->m.umDl.txNext = 0;
+
+ /* this would have been set when re-establishment was triggered
+ for SRB 1 */
+ rlcDlUtlResetReestInProgress(rbCb);
+
+ return;
+}
+/**
+ * @brief Handler to create the header and complete a PDU.
+ *
+ * @details
+ * This function is used to create the header of a PDU and concatenate it
+ * with the data part of the PDU.
+ * Also updates the statistics
+ * Sets the passed pdu to NULLP
+ *
+ * @param[in] gCb RLC instance control block
+ * @param[in,out] rbCb RB control block
+ * @param[in] pdu PDU
+ * @param[in] umHdr UM mode header
+ * @param[out] datReqPduInfo Holder in which to copy the created PDU pointer
+ *
+ * @return Void
+*/
+static void rlcUmmCreatePdu(RlcCb *gCb, RlcDlRbCb *rbCb, Buffer *pdu, RlcUmHdr *umHdr, KwPduInfo *datReqPduInfo)
+{
+ RlcSn sn; /* Sequence Number */
+ uint8_t hdr[RLC_MAX_HDRSZ]; /* Stores header */
+ uint32_t idx = 0; /* To index to the hdr array */
+
+ /* stats updated before for bytes sent before adding RLC headers */
+ rlcUtlIncrementGenStsBytesAndPdusSent(&gCb->genSts, pdu);
+
+ /* If SI = 0, 1 byte header conatining SI/R */
+ if(umHdr->si == 0)
+ {
+ hdr[idx++] = 0;
+ }
+ else
+ {
+ /* Add SN based on SN length */
+ sn = rbCb->m.umDl.txNext;
+ if (rbCb->m.umDl.snLen == RLC_UM_CFG_6BIT_SN_LEN)
+ {
+ hdr[idx++] = (umHdr->si << 6) | sn;
+ }
+ else
+ {
+ hdr[idx++] = (umHdr->si << 6) | (sn >> 8);
+ hdr[idx++] = sn & 0xff ;
+ }
+
+ /* Add SO for middle and last segments*/
+ if((umHdr->si == RLC_SI_MID_SEG) | (umHdr->si == RLC_SI_LAST_SEG))
+ {
+ hdr[idx++] = (umHdr->so >> 8);
+ hdr[idx++] = umHdr->so & 0xff;
+ }
+
+ /* Increment TX_Next if this is last segment of current SDU */
+ if(umHdr->si == RLC_SI_LAST_SEG)
+ rbCb->m.umDl.txNext = (rbCb->m.umDl.txNext + 1) & rbCb->m.umDl.modBitMask;
+
+ }
+
+ /* add the header to the beginning of the pdu */
+ ODU_ADD_PRE_MSG_MULT_IN_ORDER(hdr, idx, pdu);
+
+ datReqPduInfo->mBuf[datReqPduInfo->numPdu++] = pdu;
+ return;
+}
+
+/**
+ * @brief Handler to discard a SDU.
+ *
+ * @details
+ * This function is used to discard a SDU after receiving
+ * the Discard Request from the upper layer.The SDU is discarded if
+ * it is present and is not mapped to any PDU yet.
+ * The discards coming from the upper layer would be coming in
+ * sequence according to the sduId, so we should find the sduId at the
+ * head of the sduQ. Discards if there is a match else does nothing.
+ *
+ * @param[in] rbCb RB control block
+ * @param[in] sduId SDU ID of the SDU to be discarded
+ *
+ * @return Void
+*/
+Void rlcUmmDiscSdu(RlcCb *gCb,RlcDlRbCb *rbCb,uint32_t sduId)
+{
+ CmLList *tmpNode; /* Temporary Node in SDU queue */
+ CM_LLIST_FIRST_NODE(&rbCb->m.umDl.sduQ,tmpNode);
+
+ if (tmpNode)
+ {
+ RlcSdu *sdu = (RlcSdu *)tmpNode->node;
+
+ if (sdu->mode.um.sduId == sduId && sdu->mode.um.isSegmented == FALSE)
+ {
+/* kw005.201 added support for L2 Measurement */
+ RLC_REMOVE_SDU(gCb,&rbCb->m.umDl.sduQ,sdu);
+ gCb->genSts.numSduDisc++;
+ }
+ }
+
+ return;
+}
+
+/*
+ *
+ * @brief
+ * function to free/release the UnAcknowledged mode RBCB buffers
+ *
+ * @details
+ * This primitive Frees the Unacknowldged Mode RbCb sdu queue
+ *
+ * @param [in] gCb - RLC instance control block
+ * @param [in] rbCb - RB Control Block
+ *
+ * @return Void
+ */
+Void rlcUmmFreeDlRbCb(RlcCb *gCb,RlcDlRbCb *rbCb)
+{
+
+ /* cat the SDU queue to the to be freed list */
+ cmLListCatLList(&(gCb->u.dlCb->toBeFreed.sduLst),&(rbCb->m.umDl.sduQ));
+ rlcUtlRaiseDlCleanupEvent(gCb);
+
+ return;
+} /* rlcUmmFreeDlRbCb */
+
+/********************************************************************30**
+ End of file
+**********************************************************************/