Newer
Older
mbed-os / features / FEATURE_BLE / targets / TARGET_CORDIO_LL / stack / controller / sources / ble / lctr / lctr_isr_cis_slave.c
@Paul Szczeanek Paul Szczeanek on 2 Jul 2020 38 KB update cordio LL files to 20.05r
/*************************************************************************************************/
/*!
 *  \file
 *
 *  \brief  Link layer controller slave connected isochronous stream ISR callbacks.
 *
 *  Copyright (c) 2013-2019 Arm Ltd. All Rights Reserved.
 *
 *  Copyright (c) 2019-2020 Packetcraft, Inc.
 *  
 *  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.
 */
/*************************************************************************************************/

#include "lctr_int.h"
#include "lctr_int_cis.h"
#include "lctr_int_cis_slave.h"
#include "lctr_int_iso.h"
#include "lmgr_api_conn.h"
#include "sch_api.h"
#include "sch_api_ble.h"
#include "bb_ble_api.h"
#include "wsf_assert.h"
#include "wsf_math.h"
#include "wsf_msg.h"
#include "wsf_os.h"
#include "wsf_trace.h"
#include "util/bstream.h"
#include <string.h>
#include "pal_codec.h"

#if (LL_ENABLE_TESTER)
#include "pal_bb_ble_tester.h"
#endif

/**************************************************************************************************
  Global Variables
**************************************************************************************************/

/**************************************************************************************************
  Function Declarations
**************************************************************************************************/

#if (LL_ENABLE_TESTER)
void LctrCisProcessRxTxAck(lctrCisCtx_t *pCtx, bool_t *pValidRx, bool_t *pTxPduIsAcked);
#endif

/*************************************************************************************************/
/*!
 *  \brief  Initialize slave CIS ISR resources.
 *
 *  \param  pCisCtx    CIS Context.
 */
/*************************************************************************************************/
static void lctrSlvCisInitIsr(lctrCisCtx_t *pCisCtx)
{
  pCisCtx->data.slv.syncWithMaster = FALSE;
  pCisCtx->data.slv.rxFromMaster = FALSE;
  pCisCtx->data.slv.firstRxFromMaster = TRUE;
  pCisCtx->data.slv.rxStatus = BB_STATUS_RX_TIMEOUT;
  pCisCtx->subEvtCounter = 0;
  pCisCtx->txDataCounter = 0;
  pCisCtx->rxDataCounter = 0;

  pCisCtx->txFtParamList.pHead->ftParam.subEvtCounter = 0;
  pCisCtx->rxFtParamList.pHead->ftParam.subEvtCounter = 0;

  if (pCisCtx->txFtParamList.pHead->ftParam.bn == 0)
  {
    pCisCtx->isTxDone = TRUE;
  }
  else
  {
    pCisCtx->isTxDone = FALSE;
  }

  /*** Setup for transmit ***/

  pCisCtx->txHdr.cie  = 0;
  pCisCtx->txHdr.np   = 0;

  /*** Setup for receive ***/

  pCisCtx->rxHdr.cie  = 0;
  pCisCtx->rxHdr.np   = 0;
  pCisCtx->rxHdr.len  = 0;
  pCisCtx->rxHdr.llid = LL_LLID_ISO_UNF_END_PDU;
}

/*************************************************************************************************/
/*!
 *  \brief  Execution CIG operation
 *
 *  \param  pCisCtx     CIS Context.
 */
/*************************************************************************************************/
static void lctrSlvCisCigExecOp(lctrCisCtx_t *pCisCtx)
{
  uint8_t *pBuf;

  if (!lmgrIsoCb.availRxBuf)
  {
    LL_TRACE_ERR1("!!! lctrSlvCisCigExecOp, RX buffer not available, cisHandle=%u", pCisCtx->cisHandle);
    return;                    /* flow control Rx */
  }

  /*** Setup receiver ***/

  if ((pBuf = lctrCisRxPduAlloc(pCisCtx->localDataPdu.maxRxLen)) != NULL)
  {
#if (LL_ENABLE_TESTER)
    if (llTesterCb.isoAccAddrSeTrigMask &&
        (llTesterCb.isoAccAddrSeTrigMask & (1 << pCisCtx->subEvtCounter)) &&
        llTesterCb.isoAccAddrInvForRx)
    {
      PalBbTesterInvalidateNextAccAddr(TRUE);

      if (llTesterCb.isoAccAddrInvNumTrig)
      {
        llTesterCb.isoAccAddrInvNumTrig--;
        if (llTesterCb.isoAccAddrInvNumTrig == 0)
        {
          llTesterCb.isoAccAddrSeTrigMask = 0;
        }
      }
    }
#endif

    BbBleCisRxData(pBuf, LCTR_CIS_DATA_PDU_LEN(pCisCtx->localDataPdu.maxRxLen));
    /* Rx may fail; no more important statements in this code path */
  }
  else
  {
    LL_TRACE_ERR1("!!! OOM while initializing receive buffer at start of CIS, cisHandle=%u", pCisCtx->cisHandle);
    BbSetBodTerminateFlag();
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Check Tx flush timeout and flush PDU if necessary after RX.
 *
*  \param  pCisCtx         CIS context.
 */
/*************************************************************************************************/
static void lctrCisCheckTxFtAfterRx(lctrCisCtx_t *pCisCtx)
{
  if (lctrCisFtIsListEmpty(&pCisCtx->txFtParamList))
  {
    /* List is empty. */
    return;
  }

  if (pCisCtx->txHdr.np)
  {
    /* NULL PDU doesn't need to be acked or flushed. */
    return;
  }

  lctrFtParam_t *pFtParam = &pCisCtx->txFtParamList.pHead->ftParam;

  if (pFtParam->pduAcked)
  {
    pFtParam->isPduDone[pFtParam->pduCounter] = TRUE;       /* The PDU is done. */
    lctrCisIncPacketCounterTx(pCisCtx);
    pFtParam->pduCounter++;
  }
  else
  {
    /* Check if this is the last FT interval. */
    if ((pFtParam->intervalCounter + 1) == pFtParam->intervalTotal)
    {
      if (pFtParam->subEvtCounter == pFtParam->lastSubEvtFt[pFtParam->pduCounter])
      {
        /* PDU needs to be flushed. */
        pCisCtx->txHdr.sn++;

        lctrCisIncPacketCounterTx(pCisCtx);
        pFtParam->isPduDone[pFtParam->pduCounter] = TRUE;       /* The PDU is done. */

        if (pFtParam->pduType[pFtParam->pduCounter] == LCTR_CIS_PDU_NON_EMPTY)
        {
          /* Need to remove from ack queue if non-empty PDU. */
          lctrCisTxPduAck(pCisCtx);
          pCisCtx->pduFlushed = TRUE;   /* Set the flag, lctrCisProcessTxAckCleanup will be called to do the cleanup later in the lctrMstCisCigPostSubEvt. */
        }
        pFtParam->pduCounter++;
        pCisCtx->isoLinkQualStats.txUnAckPkt++;
      }
    }
    else /* Implies that a retransmit will happen. */
    {
      pCisCtx->isoLinkQualStats.retransmitPkt++;
    }
  }

  /* Remove the node if all the items are done. */
  bool_t removeFromList = TRUE;
  for (unsigned int i = 0; i < WSF_MIN(pFtParam->bn, LCTR_MAX_BN); i++)
  {
    if (pFtParam->isPduDone[i] == FALSE)
    {
      removeFromList = FALSE;
      break;
    }
  }
  if (removeFromList == TRUE)
  {
    lctrCisFtRemoveHead(&pCisCtx->txFtParamList);
  }

  /* No more data to send, send NULL packet. */
  if (lctrCisFtIsListEmpty(&pCisCtx->txFtParamList))
  {
    pCisCtx->isTxDone = TRUE;
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Check Rx flush timeout and flush PDU if necessary after RX.
 *
*  \param  pCisCtx         CIS context.
 */
/*************************************************************************************************/
static void lctrCisCheckRxFtAfterRx(lctrCisCtx_t *pCisCtx)
{
  if (lctrCisFtIsListEmpty(&pCisCtx->rxFtParamList))
  {
    /* List is empty. */
    return;
  }

  if (pCisCtx->rxHdr.np)
  {
    /* NULL PDU doesn't need to be acked or flushed. */
    return;
  }

  lctrFtParam_t *pFtParam = &pCisCtx->rxFtParamList.pHead->ftParam;

  if (pFtParam->pduRcved)
  {
    pFtParam->isPduDone[pFtParam->pduCounter] = TRUE;       /* The PDU is done. */
    pFtParam->pduCounter++;
  }
  else
  {
    /* Check if this is the last interval before FT. */
    if ((pFtParam->intervalCounter + 1) == pFtParam->intervalTotal)
    {
      if (pFtParam->subEvtCounter == pFtParam->lastSubEvtFt[pFtParam->pduCounter])
      {
        /* PDU needs to be flushed. */
        pCisCtx->txHdr.nesn++;
        lctrCisIncPacketCounterRx(pCisCtx);
        pFtParam->isPduDone[pFtParam->pduCounter] = TRUE;       /* The PDU is done. */
        pFtParam->pduCounter++;
        pCisCtx->isoLinkQualStats.rxUnreceivedPkt++;
        pCisCtx->isoalRxCtx.pduFlushed = TRUE;
        pCisCtx->isoalRxCtx.packetSequence++;
        pCisCtx->numRxMissed++;
      }
    }
  }

  /* Remove the node if all the items are done. */
  bool_t removeFromList = TRUE;
  for (unsigned int i = 0; i < WSF_MIN(pFtParam->bn, LCTR_MAX_BN); i++)
  {
    if (pFtParam->isPduDone[i] == FALSE)
    {
      removeFromList = FALSE;
      break;
    }
  }
  if (removeFromList == TRUE)
  {
    lctrCisFtRemoveHead(&pCisCtx->rxFtParamList);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Check flush timeout and flush PDU if necessary after end of BOD.
 *
 *  \param  pCisCtx    CIS context.
 */
/*************************************************************************************************/
static void lctrCisCheckTxFtAfterBod(lctrCisCtx_t *pCisCtx)
{
  if (lctrCisFtIsListEmpty(&pCisCtx->txFtParamList))
  {
    /* List is empty. */
    return;
  }

  if (pCisCtx->txHdr.np)
  {
    /* NULL PDU doesn't need to be acked or flushed. */
    return;
  }

  lctrFtParamNode_t *pNode = pCisCtx->txFtParamList.pHead;

  /* Increment interval counter for all the node in the list. */
  while (pNode)
  {
    pNode->ftParam.intervalCounter++;
    pNode = pNode->pNext;
  }

  /* Only check the head node */
  lctrFtParam_t *pFtParam = &pCisCtx->txFtParamList.pHead->ftParam;
  for (unsigned int i = 0; i < WSF_MIN(pFtParam->bn, LCTR_MAX_BN); i++)
  {
    if (pFtParam->isPduDone[i] == FALSE &&                          /* If the PDU is not done. */
        pFtParam->intervalCounter == pFtParam->intervalTotal)      /* Check if the PDU needs to be flush in this interval. */
    {
      pFtParam->isPduDone[i] = TRUE;
      pCisCtx->txHdr.sn++;

      PalBbBleTxBufDesc_t bbDesc[3];
      if (lctrCisTxQueuePeek(pCisCtx, &bbDesc[0]))
      {
        /* Need to remove from ack queue if non-empty PDU. */
        lctrCisTxPduAck(pCisCtx);
        lctrCisTxQueuePopCleanup(pCisCtx);
      }
      pFtParam->pduCounter++;
    }
  }

  /* Remove the node if all the items are done. */
  bool_t removeFromList = TRUE;
  for (unsigned int i = 0; i < WSF_MIN(pFtParam->bn, LCTR_MAX_BN); i++)
  {
    if (pFtParam->isPduDone[i] == FALSE)
    {
      removeFromList = FALSE;
      break;
    }
  }
  if (removeFromList == TRUE)
  {
    /* pCisCtx->isTxDone = TRUE; No need since BOD is done. */
    lctrCisFtRemoveHead(&pCisCtx->txFtParamList);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Check flush timeout and flush PDU if necessary after end of BOD.
 *
 *  \param  pCisCtx    CIS Context.
 */
/*************************************************************************************************/
static void lctrCisCheckRxFtAfterBod(lctrCisCtx_t *pCisCtx)
{
  if (lctrCisFtIsListEmpty(&pCisCtx->rxFtParamList))
  {
    /* List is empty. */
    return;
  }

  lctrFtParamNode_t *pNode = pCisCtx->rxFtParamList.pHead;

  /* Increment interval counter for all the node in the list. */
  while (pNode)
  {
    pNode->ftParam.intervalCounter++;
    pNode = pNode->pNext;
  }

  /* Only check the head node */
  lctrFtParam_t *pFtParam = &pCisCtx->rxFtParamList.pHead->ftParam;
  for (unsigned int i = 0; i < WSF_MIN(pFtParam->bn, LCTR_MAX_BN); i++)
  {
    if (pFtParam->isPduDone[i] == FALSE &&                          /* If the PDU is not done. */
        pFtParam->intervalCounter == pFtParam->intervalTotal)      /* Check if the PDU needs to be flush in this interval. */
    {
      pFtParam->isPduDone[i] = TRUE;
      pCisCtx->txHdr.nesn++;
      lctrCisIncPacketCounterRx(pCisCtx);
      pFtParam->pduCounter++;
    }
  }

  /* Remove the node if all the items are done. */
  bool_t removeFromList = TRUE;
  for (unsigned int i = 0; i < WSF_MIN(pFtParam->bn, LCTR_MAX_BN); i++)
  {
    if (pFtParam->isPduDone[i] == FALSE)
    {
      removeFromList = FALSE;
      break;
    }
  }
  if (removeFromList == TRUE)
  {
    lctrCisFtRemoveHead(&pCisCtx->rxFtParamList);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Check flush timeout and flush PDU if necessary after RX.
 *
*  \param  pCisCtx         CIS context.
 */
/*************************************************************************************************/
static void lctrCisCheckFtAfterRx(lctrCisCtx_t *pCisCtx)
{
  lctrCisCheckTxFtAfterRx(pCisCtx);
  lctrCisCheckRxFtAfterRx(pCisCtx);
}

/*************************************************************************************************/
/*!
 *  \brief  Check flush timeout and flush PDU if necessary after end of BOD.
 *
 *  \param  pCisCtx    CIS context.
 */
/*************************************************************************************************/
static void lctrCisCheckFtAfterBod(lctrCisCtx_t *pCisCtx)
{
  lctrCisCheckTxFtAfterBod(pCisCtx);
  lctrCisCheckRxFtAfterBod(pCisCtx);
}

/**************************************************************************************************
  External Functions
**************************************************************************************************/

/*************************************************************************************************/
/*!
 *  \brief  Setup for the next current operation.
 *
 *  \param  pOp         Begin operation.
 *  \param  pNewCisCtx  New CIS context to begin.
 *
 *  \return Offset in usec.
 */
/*************************************************************************************************/
uint32_t lctrSlvCisCheckContOp(BbOpDesc_t *pOp, bool_t *pNewCisCtx)
{
  lctrCigCtx_t * const pCigCtx = pOp->pCtx;
  lctrCisCtx_t *pCisCtx = pCigCtx->pCisCtx;
  uint32_t wwTotalUsec;
  uint32_t offsetUsec;
  uint32_t delayUsec = pCisCtx->delayUsec;
  bool_t isSuccess = (pCisCtx->data.slv.rxStatus == BB_STATUS_SUCCESS ? TRUE : FALSE);

  WSF_ASSERT(pCisCtx);

  BbBleData_t *pBle = &pCisCtx->bleData;
  BbBleSlvCisEvent_t * const pCis = &pBle->op.slvCis;

  if (pCigCtx->packing == LL_PACKING_SEQUENTIAL)
  {
    uint32_t subIntervUsec = pCisCtx->subIntervUsec;

    pCisCtx->subEvtCounter++;

    if (pCisCtx->rxHdr.cie || pCisCtx->txHdr.cie)
    {
      goto SwitchSeq;
    }

    /* Check whether the CIS shall start or not. */
    if (pCisCtx->cisCeRef != 0)
    {
      goto SwitchSeq;
    }

    if (pCisCtx->subEvtCounter == pCisCtx->nse)
    {
      pCisCtx->txHdr.cie = 1;
      /* There is no more subevent. */
      goto SwitchSeq;
    }

    /*** Continue current CIS ***/

    /* Set next channel index. */
    pBle->chan.chanIdx = pCisCtx->nextSubEvtChanIdx;      /* Next subevent channel index is pre-calculated. */

    /* Only apply WW when rx success. */
    if (isSuccess)
    {
      wwTotalUsec = lctrCalcWindowWideningUsec(subIntervUsec, pCigCtx->roleData.slv.totalAcc);
      offsetUsec = subIntervUsec - wwTotalUsec;
      pCis->rxSyncDelayUsec = (wwTotalUsec << 1);
    }
    else
    {
      offsetUsec = subIntervUsec;
    }
    return offsetUsec;

SwitchSeq:
    /* Skip additional sub-events if switching CIS, might not be used if next CIS is NULL */
    subIntervUsec = pCisCtx->nextCisOffsetUsec - subIntervUsec * (pCisCtx->subEvtCounter - 1);  /* pCisCtx->subEvtCounter is already incremented */

    if ((pCisCtx = lctrCisGetNextCis(&pCigCtx->list, pCisCtx)) == NULL)
    {
      goto Done;
    }
    else
    {
      /* Check whether the CIS shall start or not. */
      if (pCisCtx->cisCeRef != 0)
      {
        goto Done;
      }

      lctrFtParam_t txFtParam, rxFtParam;

      if (pCisCtx->bnSToM)
      {
        lctrCisInitFtParam(&txFtParam, pCisCtx->bnSToM, pCisCtx->ftSToM, pCisCtx->nse);
        (void)lctrCisFtInsertTail(&pCisCtx->txFtParamList, &txFtParam); /* Assume there is memory. */
      }

      if (pCisCtx->bnMToS)
      {
        lctrCisInitFtParam(&rxFtParam, pCisCtx->bnMToS, pCisCtx->ftMToS, pCisCtx->nse);
        (void)lctrCisFtInsertTail(&pCisCtx->rxFtParamList, &rxFtParam); /* Assume there is memory. */
      }

      lctrSlvCisInitIsr(pCisCtx);

      pBle = &pCisCtx->bleData;
      *pNewCisCtx = TRUE;

      /* Continue on the next subevent. */
      pBle->op.slvCis.rxTsUsec = pCis->rxTsUsec;

      /* Move the current CIS to the next CIS. */
      pOp->prot.pBle = pBle;
      pCigCtx->pCisCtx = pCisCtx;
      memcpy(&pBle->chan, &pCigCtx->pCisCtx->bleData.chan, sizeof(pBle->chan));
      pBle->chan.chanIdx = pCigCtx->pCisCtx->chIdx;   /* Set the next channel to the first channel in the next CIS. */

      /* Only apply WW when rx success. */
      if (isSuccess)
      {
        wwTotalUsec = lctrCalcWindowWideningUsec(subIntervUsec, pCigCtx->roleData.slv.totalAcc);
        offsetUsec = subIntervUsec - wwTotalUsec;
        pCis->rxSyncDelayUsec = (wwTotalUsec << 1);
      }
      else
      {
        offsetUsec = subIntervUsec;
      }

      return offsetUsec;
    }
  }
  else
  {
    uint32_t subIntervUsec = pCisCtx->subIntervUsec;
    uint8_t oriNextSubEvtChanIdx = pCisCtx->nextSubEvtChanIdx;

    if (pCisCtx->rxHdr.cie || pCisCtx->txHdr.cie)
    {
      pCisCtx->cisDone = TRUE;
      goto SwitchInt;
    }

    /* Check whether the CIS shall start or not. */
    if (pCisCtx->cisCeRef != 0)
    {
      lctrCisSetCisDone(&pCigCtx->list, pCisCtx);
      goto SwitchInt;
    }

    pCisCtx->subEvtCounter++;

    if (pCisCtx->subEvtCounter == pCisCtx->nse)
    {
      pCisCtx->txHdr.cie = 1;
      pCisCtx->cisDone = TRUE;
      /* There is no more subevent. */
      goto SwitchInt;
    }

SwitchInt:
    if (lctrCisGetListCount(&pCigCtx->list) == 1)
    {
      if (lctrCisAreCisCtxDone(&pCigCtx->list))
      {
        goto Done;
      }

      /*** Continue current CIS ***/

      /* Set next channel index. */
      pBle->chan.chanIdx = oriNextSubEvtChanIdx;        /* Next subevent channel index is pre-calculated. */
      /* Only apply WW when rx success. */
      if (isSuccess)
      {
        wwTotalUsec = lctrCalcWindowWideningUsec(subIntervUsec, pCigCtx->roleData.slv.totalAcc);
        offsetUsec = subIntervUsec - wwTotalUsec;
        pCis->rxSyncDelayUsec = (wwTotalUsec << 1);
      }
      else
      {
        offsetUsec = subIntervUsec;
      }

      return offsetUsec;
    }

    if ((pCisCtx = lctrCisGetNextCis(&pCigCtx->list, pCisCtx)) == NULL)
    {
      if (lctrCisAreCisCtxDone(&pCigCtx->list))
      {
        goto Done;
      }

      /* End of the list, loop back to the head of the CIS. */
      pCisCtx = lctrCisGetHeadCis(&pCigCtx->list);
      pCigCtx->isLoopBack = TRUE;

      pBle = &pCisCtx->bleData;
      *pNewCisCtx = TRUE;

      /* Point to the next CIS. */
      pOp->prot.pBle = pBle;
      pCigCtx->pCisCtx = pCisCtx;
      memcpy(&pBle->chan, &pCigCtx->pCisCtx->bleData.chan, sizeof(pBle->chan));
      pBle->chan.chanIdx = pCisCtx->nextSubEvtChanIdx;  /* Next subevent channel index is pre-calculated. */

      /* Only apply WW when rx success. */
      if (isSuccess)
      {
        wwTotalUsec = lctrCalcWindowWideningUsec(delayUsec, pCigCtx->roleData.slv.totalAcc);
        offsetUsec = delayUsec - wwTotalUsec;
        pCis->rxSyncDelayUsec = (wwTotalUsec << 1);
      }
      else
      {
        offsetUsec = delayUsec;
      }

      return offsetUsec;
    }
    else
    {
      /* Get the next CIS in the list. */
      if (lctrCisAreCisCtxDone(&pCigCtx->list))
      {
        goto Done;
      }

      /* Check whether the CIS shall start or not. */
      if (pCisCtx->cisCeRef != 0)
      {
        /*** Continue head CIS ***/

        lctrCisSetCisDone(&pCigCtx->list, pCisCtx);

        lctrCisCtx_t *pNextCtx, *pTempCtx;
        uint32_t addDelayUsec = 0;

        pTempCtx = lctrCisGetHeadCis(&pCigCtx->list);
        pCigCtx->pCisCtx = pTempCtx;
        pBle = &pTempCtx->bleData;
        memcpy(&pBle->chan, &pTempCtx->bleData.chan, sizeof(pBle->chan));
        pBle->chan.chanIdx = pTempCtx->nextSubEvtChanIdx;        /* Next subevent channel index is pre-calculated. */
        pOp->prot.pBle = pBle;
        pCigCtx->isLoopBack = TRUE;

        while (pTempCtx)
        {
          pNextCtx = lctrCisGetNextCis(&pCigCtx->list, pTempCtx);

          if (pNextCtx == NULL)
          {
            break;
          }

          if (pNextCtx == pCisCtx)
          {
            break;
          }

          addDelayUsec += pTempCtx->delayUsec;
          pTempCtx = pNextCtx;
        }

        /* Only apply WW when Rx success. */
        if (isSuccess)
        {
          wwTotalUsec = lctrCalcWindowWideningUsec(subIntervUsec, pCigCtx->roleData.slv.totalAcc);
          offsetUsec = subIntervUsec - wwTotalUsec;
          pCis->rxSyncDelayUsec = (wwTotalUsec << 1);
        }
        else
        {
          offsetUsec = subIntervUsec;
        }

        return (offsetUsec - addDelayUsec);
      }

      if (pCigCtx->isLoopBack == FALSE)
      {
        lctrFtParam_t txFtParam, rxFtParam;

        if (pCisCtx->bnSToM)
        {
          lctrCisInitFtParam(&txFtParam, pCisCtx->bnSToM, pCisCtx->ftSToM, pCisCtx->nse);
          (void)lctrCisFtInsertTail(&pCisCtx->txFtParamList, &txFtParam); /* Assume there is memory. */
        }

        if (pCisCtx->bnMToS)
        {
          lctrCisInitFtParam(&rxFtParam, pCisCtx->bnMToS, pCisCtx->ftMToS, pCisCtx->nse);
          (void)lctrCisFtInsertTail(&pCisCtx->rxFtParamList, &rxFtParam); /* Assume there is memory. */
        }

        lctrSlvCisInitIsr(pCisCtx);
      }

      pBle = &pCisCtx->bleData;
      *pNewCisCtx = TRUE;

      /* Point to the next CIS. */
      pOp->prot.pBle = pBle;
      pCigCtx->pCisCtx = pCisCtx;
      memcpy(&pBle->chan, &pCigCtx->pCisCtx->bleData.chan, sizeof(pBle->chan));
      if (pCigCtx->isLoopBack == FALSE)
      {
        pBle->chan.chanIdx = pCigCtx->pCisCtx->chIdx;     /* Set the next channel to the first channel in the next CIS. */
      }
      else
      {
        pBle->chan.chanIdx = pCisCtx->nextSubEvtChanIdx;  /* Next subevent channel index is pre-calculated. */
      }

      /* Only apply WW when rx success. */
      if (isSuccess)
      {
        wwTotalUsec = lctrCalcWindowWideningUsec(delayUsec, pCigCtx->roleData.slv.totalAcc);
        offsetUsec = delayUsec - wwTotalUsec;
        pCis->rxSyncDelayUsec = (wwTotalUsec << 1);
      }
      else
      {
        offsetUsec = delayUsec;
      }
      return offsetUsec;
    }
  }

Done:
  /* All CISs are done. Set next CIS to the head of the list. */
  pCigCtx->pCisCtx = lctrCisGetHeadCis(&pCigCtx->list);
  pOp->prot.pBle = &pCigCtx->pCisCtx->bleData;
  *pNewCisCtx = TRUE;
  lctrCisClearCisDone(&pCigCtx->list);
  return 0;
}

/*************************************************************************************************/
/*!
 *  \brief  Cleanup a connection operation.
 *
 *  \param  pOp     Completed operation.
 */
/*************************************************************************************************/
void lctrSlvCisCigCleanupOp(BbOpDesc_t *pOp)
{

}

/*************************************************************************************************/
/*!
 *  \brief  End a connection operation.
 *
 *  \param  pOp     Completed operation.
 */
/*************************************************************************************************/
void lctrSlvCisCigEndOp(BbOpDesc_t *pOp)
{
  lctrCigCtx_t * const pCigCtx = pOp->pCtx;
  lctrCisCtx_t *pCisCtx  = lctrCisGetHeadCis(&pCigCtx->list);
  lctrConnCtx_t *pConnCtx = LCTR_GET_CONN_CTX(pCisCtx->aclHandle);
  WSF_ASSERT(pCisCtx);  /* At least one CIS in the CIG. */

  if (pCisCtx->data.slv.syncWithMaster)
  {
    /* Re-sync to master's clock. */
    pCigCtx->roleData.slv.anchorPointUsec = pCisCtx->data.slv.firstRxStartTsUsec;
    pCigCtx->roleData.slv.lastActiveEvent = pCigCtx->roleData.slv.cigEvtCounter;
  }

  while (pCisCtx)
  {
    /* LL_CIS_TERMINATION case */
    if ((pCisCtx->isClosing == TRUE) ||
        (pConnCtx->enabled == FALSE) ||
        (lctrResetEnabled == TRUE))
    {
      /* This variable set to TRUE means it is a CIS disconnect that requires an event generation. */
      if (pCisCtx->isClosing == TRUE)
      {
        /* This was a host-initiated termination of the CIS. */
        lctrNotifyHostCisTerm(pCisCtx);
      }

      pCigCtx->numCisEsted--;
      pCisCtx->isClosing = FALSE;
      lctrCleanupCtx(pCisCtx);
    }

    pCisCtx = lctrCisGetNextCis(&pCigCtx->list, pCisCtx);
  }

  if (pCigCtx->numCisEsted == 0)
  {
    return;
  }

  pCisCtx  = lctrCisGetHeadCis(&pCigCtx->list);

  while (pCisCtx)
  {
    lctrCisCheckFtAfterBod(pCisCtx);

    if (!pCisCtx->connEst && pCisCtx->data.slv.rxFromMaster)
    {
      WsfTimerStartMs(&pCisCtx->tmrSupTimeout, pCisCtx->supTimeoutMs);
      pCisCtx->connEst = TRUE;

      if (pCisCtx->powerIndReq && lctrSendPowerChangeIndCback)
      {
        uint8_t txPhy = (pCisCtx->role == LL_ROLE_MASTER) ? pCisCtx->phyMToS : pCisCtx->phySToM;
        lctrSendPowerChangeIndCback(pConnCtx, txPhy, 0, pLctrRtCfg->defTxPwrLvl, TRUE);
      }

      lctrNotifyHostCisEst(pCisCtx, LL_SUCCESS, pCisCtx->cigSyncDelayUsec);
    }
    else if (pCisCtx->data.slv.rxFromMaster)
    {
      /* Reset supervision timer. */
      WsfTimerStartMs(&pCisCtx->tmrSupTimeout, pCisCtx->supTimeoutMs);
    }

    /* Failed to receive packet within the first 6 intervals. */
    if ((pCisCtx->firstFromPeer == FALSE) && (pCisCtx->cisEvtCounter == LCTR_FAST_TERM_CNT))
    {
      LL_TRACE_WARN0("CIS terminated due to fast termination timeout");
      lctrCisStoreConnFailEstablishTerminateReason(pCisCtx);
      lctrSendCisMsg(pCisCtx, LCTR_CIS_MSG_CIS_CLOSED);

      if (pCigCtx->numCisEsted == 1)
      {
        /* If the last CIS to close, schedule no more operation. */
        return;
      }
    }

    /* Terminate connection */
    if (lctrCheckForCisLinkTerm(pCisCtx->aclHandle))
    {
      lctrSendCisLlcpMsg(pCisCtx, LCTR_CIS_TERM_EVENT_CIS_TERM);
    }

    pCisCtx = lctrCisGetNextCis(&pCigCtx->list, pCisCtx);
  }

  pCisCtx  = lctrCisGetHeadCis(&pCigCtx->list);

  if (pCigCtx->headCisRmved == TRUE)
  {
    BbBleData_t *pBle = &pCisCtx->bleData;
    /* Move the current CIS to the head CIS. */
    pOp->prot.pBle = pBle;
    pCigCtx->pCisCtx = pCisCtx;
    memcpy(&pBle->chan, &pCigCtx->pCisCtx->bleData.chan, sizeof(pBle->chan));
    pBle->chan.chanIdx = pCigCtx->pCisCtx->chIdx;   /* Set the next channel to the first channel in the next CIS. */
  }

  uint16_t numUnsyncIntervals = pCigCtx->roleData.slv.cigEvtCounter - pCigCtx->roleData.slv.lastActiveEvent;

  /* Update txPower. */
  pCisCtx->bleData.chan.txPower = LCTR_GET_TXPOWER(pConnCtx, pCisCtx->phySToM, pCisCtx->bleData.chan.initTxPhyOptions);
#if(LL_ENABLE_TESTER)
  pCisCtx->bleData.chan.txPower += pConnCtx->bleData.chan.txPwrOffset;
#endif

  while (TRUE)
  {
    pCigCtx->roleData.slv.cigEvtCounter++;
    numUnsyncIntervals += 1;

    uint32_t cigInterUsec   = LCTR_ISO_INT_TO_US(pCigCtx->isoInterval);
    uint32_t unsyncTimeUsec = cigInterUsec * numUnsyncIntervals;
    uint32_t wwTotalUsec    = lctrCalcWindowWideningUsec(unsyncTimeUsec, pCigCtx->roleData.slv.totalAcc);

    pCisCtx = lctrCisGetHeadCis(&pCigCtx->list);
    WSF_ASSERT(pCisCtx);  /* At least one CIS in the CIG. */

    while (pCisCtx)
    {
      if (pCisCtx->cisCeRef == 0)
      {
        BbBleData_t * const pBle = &pCisCtx->bleData;
        pCisCtx->cisEvtCounter++;
        pCisCtx->chIdx = pBle->chan.chanIdx = LmgrSelectNextChannel(&pCisCtx->chanParam, pCisCtx->cisEvtCounter, 0, TRUE);
        pCisCtx->nextSubEvtChanIdx = LmgrSelectNextSubEvtChannel(&pCisCtx->chanParam);

        if (pCisCtx->txTestEnabled)
        {
          uint32_t sduInt = pCisCtx->sduIntervalSToM;

          while ((pOp->dueUsec - pCisCtx->testSduTs) >= sduInt)
          {
            lctrCisTxTestPayloadHandler(pCisCtx);
            pCisCtx->testSduTs += sduInt;
          }
        }

        /* Assemble framed data. */
        if ((pCisCtx->framing == LL_ISO_PDU_TYPE_FRAMED) &&
             pCisCtx->isoalTxCtx.pendQueueSize)
        {
          uint8_t *pDataBuf = lctrTxIsoDataPduAlloc();

          if (pDataBuf == NULL)
          {
            /* TODO optimize statement as the call to WsfMsgFree() has no effect and should not continue to following statement. */
            LL_TRACE_WARN0("LctrTxIso, Unable to allocate PDU for transmit");
            WsfMsgFree(pDataBuf);
          }

          lctrIsoHdr_t isoHdr;

          isoHdr.sduLen = lctrAssembleTxFramedPdu(&pCisCtx->isoalTxCtx, pDataBuf, pCisCtx->localDataPdu.maxTxLen);
          isoHdr.tsFlag = 0;

          lctrIsoSduTxDecAvailBuf();
          lctrCisTxDataPduQueue(pCisCtx, &isoHdr, pDataBuf);
        }

        /* TODO: resolve magic number */
        if (pCisCtx->nse < 3)
        {
          if (wwTotalUsec >= ((cigInterUsec >> 1) - WSF_MAX(LL_BLE_TIFS_US, BbGetSchSetupDelayUs())))
          {
            LL_TRACE_WARN2("!!! Terminating CIG due to excessive WW handle=%u, eventCounter=%u", pCisCtx->cisHandle, pCisCtx->cisEvtCounter);
            lctrSendCisMsg(pCisCtx, LCTR_CIS_MSG_CIS_CLOSED);

            if (pCigCtx->numCisEsted == 1)
            {
              /* If the last CIS to close, schedule no more operation. */
              return;
            }
          }
        }
        else
        {
          if (wwTotalUsec >= pCisCtx->subIntervUsec)
          {
            LL_TRACE_WARN2("!!! Terminating CIG due to excessive WW handle=%u, eventCounter=%u", pCisCtx->cisHandle, pCisCtx->cisEvtCounter);
            lctrSendCisMsg(pCisCtx, LCTR_CIS_MSG_CIS_CLOSED);

            if (pCigCtx->numCisEsted == 1)
            {
              /* If the last CIS to close, schedule no more operation. */
              return;
            }
          }
        }
      }
      else
      {
        pCisCtx->cisCeRef--;
      }

      pCisCtx = lctrCisGetNextCis(&pCigCtx->list, pCisCtx);
    }

    /* Advance to next interval. */
    pOp->dueUsec = pCigCtx->roleData.slv.anchorPointUsec + unsyncTimeUsec - wwTotalUsec;

    pOp->minDurUsec = pCigCtx->cigSyncDelayUsec + wwTotalUsec;
    pCigCtx->pCisCtx->bleData.op.slvCis.rxSyncDelayUsec = (wwTotalUsec << 1);

    if (pCigCtx->headCisRmved == TRUE)
    {
      pCigCtx->headCisRmved = FALSE;
      pOp->dueUsec += pCigCtx->offsetUsec;
      pCigCtx->offsetUsec = 0;
    }

    if (SchInsertAtDueTime(pOp, lctrCisResolveConflict))
    {
      break;
    }

    pCisCtx = lctrCisGetHeadCis(&pCigCtx->list);
    LL_TRACE_WARN1("!!! CIG slave schedule conflict eventCounter=%u", pCisCtx->cisEvtCounter);
  }


  if (lmgrCb.sendIsoCmplEvt)
  {
    lctrNotifyHostIsoEventComplete(pCigCtx->cigHandle, (uint32_t) pCisCtx->cisEvtCounter);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Abort a connection operation.
 *
 *  \param  pOp     Completed operation.
 */
/*************************************************************************************************/
void lctrSlvCisCigAbortOp(BbOpDesc_t *pOp)
{
  lctrCigCtx_t * const pCigCtx = pOp->pCtx;

  if (pCigCtx->numCisEsted > 0)
  {
    lctrCisCtx_t *pCisCtx  = lctrCisGetHeadCis(&pCigCtx->list);
    /* It is possible there is no CIS since it is aborting. */

    while (pCisCtx)
    {
      lctrFtParam_t txFtParam, rxFtParam;

      /* Need to setup Tx/Rx flush timeout parameter first since some field will be used in the lctrSlvCisInitIsr. */
      if (pCisCtx->bnMToS)
      {
        lctrCisInitFtParam(&txFtParam, pCisCtx->bnSToM, pCisCtx->ftSToM, pCisCtx->nse);
        (void)lctrCisFtInsertTail(&pCisCtx->txFtParamList, &txFtParam); /* Assume there is memory. */
      }

      if (pCisCtx->bnSToM)
      {
        lctrCisInitFtParam(&rxFtParam, pCisCtx->bnMToS, pCisCtx->ftMToS, pCisCtx->nse);
        (void)lctrCisFtInsertTail(&pCisCtx->rxFtParamList, &rxFtParam); /* Assume there is memory. */
      }

      lctrSlvCisInitIsr(pCisCtx);

      pCisCtx = lctrCisGetNextCis(&pCigCtx->list, pCisCtx);
    }
  }

  lctrSlvCisCigEndOp(pOp);
}

/*************************************************************************************************/
/*!
 *  \brief      Complete a transmitted data buffer.
 *
 *  \param      pOp     Operation context.
 *  \param      status  Transmit status.
 */
/*************************************************************************************************/
void lctrSlvCisCigTxCompletion(BbOpDesc_t *pOp, uint8_t status)
{
  lctrCigCtx_t * const pCigCtx = pOp->pCtx;
  lctrCisCtx_t *pCisCtx = pCigCtx->pCisCtx;

  pCisCtx->txFtParamList.pHead->ftParam.subEvtCounter++;
}

/*************************************************************************************************/
/*!
 *  \brief      Complete a received data buffer.
 *
 *  \param      pOp     Operation context.
 *  \param      pRxBuf  Next receive buffer or NULL to flow control.
 *  \param      status  Receive status.
 */
/*************************************************************************************************/
void lctrSlvCisCigRxCompletion(BbOpDesc_t *pOp, uint8_t *pRxBuf, uint8_t status)
{
  lctrCigCtx_t * const pCigCtx = pOp->pCtx;
  lctrCisCtx_t *pCisCtx = pCigCtx->pCisCtx;
  lctrConnCtx_t * pConnCtx = LCTR_GET_CONN_CTX(pCisCtx->aclHandle);
  BbBleData_t * const pBle = &pCisCtx->bleData;
  BbBleSlvCisEvent_t * const pCis = &pBle->op.slvCis;

  pCisCtx->data.slv.rxStatus = status;

  pCisCtx->rxFtParamList.pHead->ftParam.subEvtCounter++;
  pCisCtx->txFtParamList.pHead->ftParam.pduAcked = FALSE;
  pCisCtx->rxFtParamList.pHead->ftParam.pduRcved = FALSE;
  pCisCtx->validRx = FALSE;
  pCisCtx->txPduIsAcked = FALSE;
  pCisCtx->pduFlushed = FALSE;

  /*** Cancellation processing ***/

  if (status == BB_STATUS_CANCELED)
  {
    lctrCisRxPduFree(pRxBuf);

    goto Done;
  }

  /*** CIS event pre-processing ***/

  /* Save time stamp for the first Rx. */
  if (pCisCtx->data.slv.firstRxFromMaster == TRUE)
  {
    pCisCtx->data.slv.firstRxFromMaster = FALSE;
    pCisCtx->data.slv.firstRxStartTsUsec = pCis->startTsUsec;
  }

  if ((!pCisCtx->data.slv.syncWithMaster) &&
      (status == BB_STATUS_SUCCESS))
  {
    /* Receive successful packet, update anchor point later. */
    pCisCtx->firstFromPeer = TRUE;
    pCisCtx->data.slv.syncWithMaster = TRUE;
  }

  /*** Receive packet pre-processing ***/

  switch (status)
  {
  case BB_STATUS_SUCCESS:
    pCisCtx->data.slv.rxFromMaster = TRUE;
    pCisCtx->data.slv.consCrcFailed = 0;
    break;
  case BB_STATUS_CRC_FAILED:
    pCisCtx->isoLinkQualStats.crcErrPkt++;
    /* Fallthrough */
  case BB_STATUS_FAILED:
  case BB_STATUS_RX_TIMEOUT:
    LL_TRACE_WARN2("lctrSlvCisCigRxCompletion: BB failed with status=%u, handle=%u", status, pCisCtx->cisHandle);
    LL_TRACE_WARN2("lctrSlvCisCigRxCompletion: BB failed with cisEvtCounter=%d, bleChan=%u", pCisCtx->cisEvtCounter, pCisCtx->bleData.chan.chanIdx);

    pCisCtx->validRx = FALSE;
    pCisCtx->txPduIsAcked = FALSE;

    goto SetupTx;

  default:
    break;
  }

  lctrCisUnpackDataPduHdr(&pCisCtx->rxHdr, pRxBuf);

#if (LL_ENABLE_TESTER)
  if ((llTesterCb.cisAckMode != LL_TESTER_ACK_MODE_NORMAL) ||
      (llTesterCb.cisFwdPkt == TRUE))
  {
    LctrCisProcessRxTxAck(pCisCtx, &pCisCtx->validRx, &pCisCtx->txPduIsAcked);

    /* Don't report the empty packet. */
    if (pCisCtx->rxHdr.len == 0)
    {
      pCisCtx->validRx = FALSE;
    }
  }
  else
#endif
  {
    pCisCtx->validRx = lctrCisProcessRxAck(pCisCtx);
    pCisCtx->txPduIsAcked = lctrCisProcessTxAck(pCisCtx);             /* Always Tx after Rx. */
  }

  /*** Setup for transmit ***/

SetupTx:

  lctrCisCheckFtAfterRx(pCisCtx);

  /* Slave always transmits after receiving. */
  lctrCisSetupForTx(pCigCtx, status, TRUE);

  /* Tx post-processing. */
  lctrCisProcessTxAckCleanup(pCisCtx);

  /* Rx post-processing. */
  lctrCisRxPostProcessing(pCisCtx, pRxBuf);

  /* Check rssi value if power monitoring. */
  if (pConnCtx->monitoringState == LCTR_PC_MONITOR_ENABLED)
  {
    lctrCisPowerMonitorCheckRssi(pOp->prot.pBle->op.slvCis.rssi,
                                  status,
                                  pCisCtx->phySToM +
                                    (((pCisCtx->phySToM == LL_PHY_LE_CODED) &&
                                    (pBle->chan.initTxPhyOptions == LL_PHY_OPTIONS_S2_PREFERRED)) ? 1 : 0),
                                  pConnCtx);
  }

  return;

  /*** ISR complete ***/

Done:
  lctrCisCheckFtAfterRx(pCisCtx);

  /* Tx post-processing. */
  lctrCisProcessTxAckCleanup(pCisCtx);

  return;
}

/*************************************************************************************************/
/*!
 *  \brief  Begin a connection operation.
 *
 *  \param  pOp     Begin operation.
 *
 *  \note   Scheduler may call this routine multiple times in the following situation:
 *          this BOD is pending and a BOD is inserted before.
 */
/*************************************************************************************************/
void lctrSlvCisCigBeginOp(BbOpDesc_t *pOp)
{
  lctrCigCtx_t * const pCigCtx = pOp->pCtx;
  lctrCisCtx_t *pCisCtx;
  pCisCtx = pCigCtx->pCisCtx;
  pCigCtx->isLoopBack = FALSE;

  /* Need to setup Tx/Rx flush timeout parameter first since some field will be used in the lctrSlvCisInitIsr. */
  lctrFtParam_t txFtParam, rxFtParam;

  if (pCisCtx->bnSToM)
  {
    lctrCisInitFtParam(&txFtParam, pCisCtx->bnSToM, pCisCtx->ftSToM, pCisCtx->nse);
    (void)lctrCisFtInsertTail(&pCisCtx->txFtParamList, &txFtParam); /* Assume there is memory. */
  }

  if (pCisCtx->bnMToS)
  {
    lctrCisInitFtParam(&rxFtParam, pCisCtx->bnMToS, pCisCtx->ftMToS, pCisCtx->nse);
    (void)lctrCisFtInsertTail(&pCisCtx->rxFtParamList, &rxFtParam); /* Assume there is memory. */
  }

  lctrSlvCisInitIsr(pCisCtx);

  lctrSlvCisCigExecOp(pCisCtx);
}

/************************************************* ************************************************/
/*!
 *  \brief  Begin a connection operation.
 *
 *  \param  pOp     Begin operation.
 *
 *  \note   Scheduler may call this routine multiple times in the following situation:
 *          this BOD is pending and a BOD is inserted before.
 */
/*************************************************************************************************/
void lctrSlvCisCigContOp(BbOpDesc_t *pOp)
{
  lctrCigCtx_t * const pCigCtx = pOp->pCtx;
  lctrCisCtx_t *pCisCtx;
  pCisCtx = pCigCtx->pCisCtx;

  lctrSlvCisCigExecOp(pCisCtx);
}

/*************************************************************************************************/
/*!
 *  \brief  Post subevent callback, setup for the next subevent channel index.
 *
 *  \param  pOp       Current operation.
 *  \param  status    Status.
 */
/*************************************************************************************************/
void lctrSlvCisCigPostSubEvt(BbOpDesc_t *pOp, uint8_t status)
{
  lctrCigCtx_t *pCigCtx = pOp->pCtx;
  lctrCisCtx_t *pCisCtx;

  WSF_ASSERT(pCigCtx);
  pCisCtx = pCigCtx->pCisCtx;

  WSF_ASSERT(pCisCtx);
  pCisCtx->nextSubEvtChanIdx = LmgrSelectNextSubEvtChannel(&pCisCtx->chanParam);
}