Newer
Older
mbed-os / connectivity / FEATURE_BLE / cordio / TARGET_CORDIO_LL / stack / controller / sources / ble / lctr / lctr_main_iso.c
@Paul Szczeanek Paul Szczeanek on 7 Aug 2020 35 KB remove generic, TPPs, nested namespaces
/*************************************************************************************************/
/*!
 *  \file
 *
 *  \brief  Link layer controller connected isochronous stream main implementation file.
 *
 *  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_cis.h"
#include "lctr_int_bis.h"
#include "lctr_int_iso.h"
#include "hci_defs.h"
#include "wsf_assert.h"
#include "wsf_math.h"
#include "wsf_trace.h"
#include "util/bstream.h"
#include "pal_codec.h"
#include <string.h>

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

/*! \brief      Codec event handlers (function pointers to decouple linker dependencies). */
lctrCodecHandlers_t lctrCodecHdlr;

/**************************************************************************************************
  Local Functions
**************************************************************************************************/

/*************************************************************************************************/
/*!
 *  \brief      Process received ISO test data.
 *
 *  \param      pCisCtx     CIS context.
 *  \param      pDataBuf    Data buffer.
 *  \param      dataLen     Data length.
 */
/*************************************************************************************************/
static void lctrIsoProcessRxTestData(lctrCisCtx_t *pCisCtx, uint8_t *pDataBuf, uint8_t dataLen)
{
  uint16_t pldType = 0;
  uint64_t plTestCounter = pCisCtx->rxPktCounter;

  if (pCisCtx->rxPendInit)
  {
    memcpy(&pCisCtx->expectedPkt, pDataBuf, 4);
    pCisCtx->rxPendInit = FALSE;
    LL_TRACE_INFO1("lctrCisRxPostProcessing, Rx counter initialized to %d", pCisCtx->expectedPkt);
  }

  switch (pCisCtx->isoRxPldType)
  {
    case LL_ISO_PLD_TYPE_MAX_LEN:

      pldType = (pCisCtx->role == LL_ROLE_MASTER) ? pCisCtx->sduSizeSToM : pCisCtx->sduSizeMToS;
      break;

    case LL_ISO_PLD_TYPE_VAR_LEN:
      pldType = plTestCounter * 4;
      pldType = WSF_MIN(pCisCtx->localDataPdu.maxRxLen, pldType);
      break;

    case LL_ISO_PLD_TYPE_ZERO_LEN:
    default:
      break;
  }

  uint32_t pktNum = 0;
  if (dataLen >= LL_ISO_TEST_VAR_MIN_LEN)
  {
    memcpy(&pktNum, pDataBuf, 4);
  }

  switch (pCisCtx->isoRxPldType)
  {
    case LL_ISO_PLD_TYPE_MAX_LEN:
      if ((dataLen == pldType) &&
          (pktNum == pCisCtx->expectedPkt))
      {
        pCisCtx->numRxSuccess++;
      }
      else
      {
        pCisCtx->expectedPkt = pktNum + 1;
        pCisCtx->numRxFailed++;
      }
      break;

    case LL_ISO_PLD_TYPE_VAR_LEN:
      if ((dataLen >= LL_ISO_TEST_VAR_MIN_LEN) &&
          (pktNum == pCisCtx->expectedPkt))
      {
          pCisCtx->numRxSuccess++;
      }
      else
      {
        pCisCtx->expectedPkt = pktNum + 1;
        pCisCtx->numRxFailed++;
      }
      break;

    case LL_ISO_PLD_TYPE_ZERO_LEN:
      if (dataLen == 0)
      {
        pCisCtx->numRxSuccess++;
      }
      else
      {
        pCisCtx->numRxFailed++;
      }
      break;

    default:
      LL_TRACE_ERR1("Invalid value pldType=%u", pldType);
      break;
  }
}

/*************************************************************************************************/
/*!
 *  \brief      Enable ISO Tx test.
 *
 *  \param      pCisCtx     CIS context.
 *  \param      pldType     Payload length type.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
static uint8_t lctrCisTxTest(lctrCisCtx_t *pCisCtx, uint8_t pldType)
{
  lctrCigCtx_t *pCigCtx = lctrFindCigById(pCisCtx->cigId);

  if (((pCisCtx->bnMToS == 0) && (pCisCtx->role == LL_ROLE_MASTER)) ||
      ((pCisCtx->bnSToM == 0) && (pCisCtx->role == LL_ROLE_SLAVE)))
  {
    LL_TRACE_WARN0("Transmit burst number must be greater than 0");
    return LL_ERROR_CODE_UNSUPPORTED_FEATURE_PARAM_VALUE;
  }

  if (pldType > LL_ISO_PLD_TYPE_MAX_LEN)
  {
    LL_TRACE_WARN1("Unknown payload type=%u", pldType);
    return LL_ERROR_CODE_INVALID_HCI_CMD_PARAMS;
  }

  pCisCtx->testTxPktCtr = 0;
#if (LL_ENABLE_TESTER)
  pCisCtx->testTxPktCtr = llTesterCb.isoTxTestNumInit;
#endif
  pCisCtx->testSduTs = pCigCtx->cigBod.dueUsec;
  pCisCtx->testPldType = pldType;
  pCisCtx->txTestEnabled = TRUE;

  return LL_SUCCESS;
}

/*************************************************************************************************/
/*!
 *  \brief      CIS read ISO test counters.
 *
 *  \param      pCisCtx     CIS context.
 *  \param      pStats      Pointer to the statistics block.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
static uint8_t LctrCisReadTestCounters(lctrCisCtx_t *pCisCtx, LlIsoTestCtrs_t *pStats)
{

  if (!pCisCtx->rxTestEnabled &&
      !pCisCtx->txTestEnabled)
  {
    LL_TRACE_WARN0("Invalid ISO Test state, test mode must be enabled");
    return LL_ERROR_CODE_CMD_DISALLOWED;
  }

  if (pCisCtx->rxTestEnabled)
  {
    pStats->numSuccess = pCisCtx->numRxSuccess;
    pStats->numMissed = pCisCtx->numRxMissed;
    pStats->numFailed = pCisCtx->numRxFailed;
  }
  else /* if (pCisCtx->txTestEnabled) */
  {
    pStats->numSuccess = 0;
    pStats->numMissed = 0;
    pStats->numFailed = 0;
  }

  return LL_SUCCESS;
}

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

/*************************************************************************************************/
/*!
 *  \brief      Initialize codec resources.
 *
 *  Map codec handlers for codec operations. Function pointer abstraction is used to decouple
 *  linker dependencies when a codec is not required by the system.
 */
/*************************************************************************************************/
void LctrInitCodec(void)
{
  /* Add codec. */
  lctrCodecHdlr.start = PalCodecDataStartStream;
  lctrCodecHdlr.stop  = PalCodecDataStopStream;
  lctrCodecHdlr.in    = PalCodecDataStreamIn;
  lctrCodecHdlr.out   = PalCodecDataStreamOut;
}

/*************************************************************************************************/
/*!
 *  \brief      Tx data completed task event handler.
 */
/*************************************************************************************************/
void lctrIsoTxCompletedHandler(void)
{
  WSF_CS_INIT(cs);

  uint8_t numHandles = 0;
  uint16_t handle[LL_MAX_CIS] = { 0 };
  uint16_t numSdu[LL_MAX_CIS] = { 0 };

  /* Cache buffer count within single CS. */
  WSF_CS_ENTER(cs);
  for (unsigned int i = 0; i < pLctrRtCfg->maxCis; i++)
  {
    lctrCisCtx_t *pCisCtx = &pLctrCisTbl[i];

    if (pCisCtx->enabled && !pCisCtx->txTestEnabled)
    {
      if (pCisCtx->numTxComp && (pCisCtx->framing == LL_ISO_PDU_TYPE_UNFRAMED))
      {
        numSdu[numHandles] = pCisCtx->numTxComp;
        handle[numHandles] = pCisCtx->cisHandle;
        pCisCtx->numTxComp = 0;
        numHandles++;
      }
      else if ((pCisCtx->framing == LL_ISO_PDU_TYPE_FRAMED) && pCisCtx->isoalTxCtx.compSdu)
      {
        numSdu[numHandles] = pCisCtx->isoalTxCtx.compSdu;
        handle[numHandles] = pCisCtx->cisHandle;
        pCisCtx->numTxComp = 0; /* numTxComp is still recorded in framed, so clear it here. */
        pCisCtx->isoalTxCtx.compSdu = 0;
        numHandles++;
      }
    }
  }

  WSF_CS_EXIT(cs);

  if (numHandles)
  {
    /* Notify host. */
    lmgrPersistCb.sendIsoCompCback(numHandles, handle, numSdu);
  }
}

/*************************************************************************************************/
/*!
 *  \brief      Rx data pending task event handler.
 */
/*************************************************************************************************/
void lctrCisRxPendingHandler(void)
{
  uint16_t cisHandle = 0;
  uint8_t *pRxBuf;

  /* Route and demux received Data PDUs. */

  while ((pRxBuf = lctrCisRxDeq(&cisHandle)) != NULL)
  {
    WSF_ASSERT(pRxBuf);

    lctrCisCtx_t *pCisCtx = lctrFindCisByHandle(cisHandle);
    lctrIsoalRxCtx_t *pRxCtx = &pCisCtx->isoalRxCtx;

    if (!pCisCtx->enabled)
    {
      LL_TRACE_ERR1("!!! Data received on terminated cisHandle=%u", cisHandle);
      lctrCisRxPduFree(pRxBuf);
      continue;
    }

    /* Disassemble PDU. */
    lctrCisDataPduHdr_t rxHdr;
    lctrCisUnpackDataPduHdr(&rxHdr, pRxBuf);

    /* Decrypt PDU. */
    if (lctrPktDecryptHdlr)
    {
      if (lctrPktDecryptHdlr(&pCisCtx->bleData.chan.enc, pRxBuf) == FALSE)
      {
        LL_TRACE_ERR1("!!! MIC verification failed on connHandle=%u", cisHandle);
        LL_TRACE_ERR1("!!! MIC verification failed on cisEvtCounter=%u", pCisCtx->cisEvtCounter);
        lctrCisRxPduFree(pRxBuf);
        lctrSendCisMsg(pCisCtx, LCTR_CIS_MSG_CIS_TERM_MIC_FAILED);
        continue;
      }
    }

    /* Increase packet counter after decryption. */
    lctrCisIncPacketCounterRx(pCisCtx);

    lctrCisDataPduHdr_t cisDataHdr;
    lctrCisUnpackDataPduHdr(&cisDataHdr, pRxBuf);

    /* Demux PDU. */
    if (pCisCtx->framing == LL_ISO_PDU_TYPE_UNFRAMED)
    {
      lctrIsoHdr_t isoHdr = { 0 };
      switch (rxHdr.llid)
      {
        /* Received a end/complete PDU. */
        case LL_LLID_ISO_UNF_END_PDU:
          switch (pRxCtx->rxState)
          {
          case LL_ISO_SDU_STATE_NEW:
            isoHdr.pb = LCTR_PB_COMP;
            break;
          case LL_ISO_SDU_STATE_CONT:
            isoHdr.pb = LCTR_PB_LAST;
            pRxCtx->rxState = LL_ISO_SDU_STATE_NEW;
            break;
          default:
            lctrCisRxPduFree(pRxBuf);
            LL_TRACE_ERR2("!!! Invalid rxState; dropping Rx data PDU, connHandle=%u, rxState=%u", cisHandle, pRxCtx->rxState);
            pRxCtx->rxState = LL_ISO_SDU_STATE_NEW;
          }
          break;

        /* Received a continuation/start PDU. */
        case LL_LLID_ISO_UNF_CONT_PDU:
          isoHdr.pb = (pRxCtx->rxState == LL_ISO_SDU_STATE_NEW) ? LCTR_PB_FIRST : LCTR_PB_CONT;
          pRxCtx->rxState = LL_ISO_SDU_STATE_CONT;
          break;

        /* Unknown LLID. */
        default:
          lctrCisRxPduFree(pRxBuf);
          LL_TRACE_ERR2("!!! Invalid LLID; dropping Rx data PDU, connHandle=%u llid=%u", cisHandle, rxHdr.llid);
          break;
      }

      /* If the packet was flushed, change the packet status flag to warn host of errors. */
      if (pRxCtx->pduFlushed)
      {
        switch (pRxCtx->rxState)
        {
          case LL_ISO_SDU_STATE_CONT:
            /* Lost data since last transfer; invalidate packet. */
            pRxCtx->data.unframed.ps = LCTR_PS_INVALID;
            break;
          case LL_ISO_SDU_STATE_NEW:
            /* This may be a fragmented packet with the end fragment, but no way to tell, so process as normal. */
            pRxCtx->data.unframed.ps = (rxHdr.llid == LL_LLID_ISO_UNF_END_PDU) ? LCTR_PS_INVALID : LCTR_PS_LOST;
            break;
        }
        pRxCtx->pduFlushed = FALSE;
      }

      /* Pack isoHdr and queue PDU. */
      if (pRxBuf)
      {
        uint8_t * pSduBuf = pRxBuf - LCTR_CIS_DATA_PDU_START_OFFSET - HCI_ISO_DL_MAX_LEN;

        /* Pack current packet. */
        isoHdr.handle = cisHandle;
        isoHdr.tsFlag = ((isoHdr.pb == LCTR_PB_COMP) || (isoHdr.pb == LCTR_PB_FIRST)) ? 1 : 0;
        if (isoHdr.tsFlag)
        {
          isoHdr.ts = 0xFF;
        }
        isoHdr.len = pRxBuf[LCTR_ISO_DATA_PDU_LEN_OFFSET];
        /* isoHdr.pktSn = 0; */

        /* LCTR_PB_COMP and LCTR_PB_FIRST will have their headers re-packed in lctrIsoUnframedRxSduPendQueue. */
        uint8_t headerOffset = lctrIsoPackHdr(pSduBuf, &isoHdr);

        /* Process received buffer for Rx testing purpose. */
        if (pCisCtx->rxTestEnabled == TRUE)
        {
          lctrIsoProcessRxTestData(pCisCtx, pRxBuf + LL_DATA_HDR_LEN, rxHdr.len);
        }

        /* TODO optimize memory layout */
        /* Move payload next to header. */
        if (LCTR_CIS_DATA_PDU_START_OFFSET + LL_ISO_DATA_HDR_LEN > HCI_ISO_HDR_LEN)
        {
          memmove(pSduBuf + headerOffset , pRxBuf + LL_DATA_HDR_LEN, rxHdr.len);
        }

        /* Put onto pending queue until whole SDU is ready to be sent. */
        if (!lctrIsoUnframedRxSduPendQueue(pRxCtx, pSduBuf, cisHandle, rxHdr.len, rxHdr.llid))
        {
          break;
        }

        /* If it is time to enqueue the SDU, do so here. */
        uint8_t handlerId;
        while ((pSduBuf = WsfMsgDeq(&pRxCtx->data.unframed.pendSduQ, &handlerId)) != NULL)
        {
          /* Enqueue SDU for processing. */
          if (!lctrIsoRxConnEnq(&pCisCtx->dataPathOutCtx, pCisCtx->cisHandle, pSduBuf))
          {
            /* The buffer was not freed, so free it now. */
            WsfMsgFree(pSduBuf);
          }
        }
      }
    }
    else /* LL_ISO_PDU_TYPE_FRAMED */
    {
      switch (rxHdr.llid)
      {
        case LL_LLID_ISO_FRA_PDU:
        {
          pCisCtx->dataPathOutCtx.cfg.hci.numRxPend += lctrAssembleRxFramedSdu(pRxCtx, &pCisCtx->dataPathOutCtx.cfg.hci.rxDataQ, pCisCtx->cisHandle, pRxBuf, cisDataHdr.len);

          /* Consume and process packets for iso test mode */
          if (pCisCtx->rxTestEnabled == TRUE)
          {
            while (pCisCtx->dataPathOutCtx.cfg.hci.numRxPend)
            {
              lctrIsoHdr_t isoHdr;
              uint8_t *pTestRxBuf = lctrIsoRxConnDeq(&pCisCtx->dataPathOutCtx);
              lctrIsoUnpackHdr(&isoHdr, pTestRxBuf);
              lctrIsoProcessRxTestData(pCisCtx, pTestRxBuf+ HCI_ISO_HDR_LEN + HCI_ISO_DL_MAX_LEN, isoHdr.sduLen);
              WsfMsgFree(pTestRxBuf);
              LctrRxIsoComplete(1);
              pCisCtx->dataPathOutCtx.cfg.hci.numRxPend--;
            }
          }
          lctrCisRxPduFree(pRxBuf);
          break;
        }

        default:
          lctrCisRxPduFree(pRxBuf);
          LL_TRACE_ERR2("!!! Invalid LLID; dropping Rx data PDU, connHandle=%u llid=%u", cisHandle, rxHdr.llid);
          break;
      }
    }
  }

  /* Notify host of pending Rx data. */
  WSF_CS_INIT(cs);
  uint8_t numHandles = 0;
  uint16_t numSdu[LL_MAX_CIS] = { 0 };
  uint16_t handle[LL_MAX_CIS] = { 0 };

  /* Cache buffer count within single CS. */
  WSF_CS_ENTER(cs);
  for (unsigned int i = 0; i < pLctrRtCfg->maxCis; i++)
  {
    lctrCisCtx_t *pCisCtx = &pLctrCisTbl[i];

    if (pCisCtx->enabled &&
        pCisCtx->dataPathOutCtx.cfg.hci.numRxPend)
    {
      if (pCisCtx->dataPathOutCtx.id == LL_ISO_DATA_PATH_HCI)
      {
        handle[numHandles] = pCisCtx->cisHandle;
        numSdu[numHandles] = pCisCtx->dataPathOutCtx.cfg.hci.numRxPend;
        pCisCtx->dataPathOutCtx.cfg.hci.numRxPend = 0;
        numHandles++;
      }
    }
  }
  WSF_CS_EXIT(cs);

  if (numHandles)
  {
    /* Notify host. */
    lmgrPersistCb.recvIsoPendCback(numHandles, handle, numSdu);
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Initialize ISO memory resources.
 *
 *  \param  pFreeMem    Pointer to free memory.
 *  \param  freeMemSize Size of pFreeMem.
 *
 *  \return Amount of free memory consumed.
 */
/*************************************************************************************************/
uint16_t LctrInitIsoMem(uint8_t *pFreeMem, uint32_t freeMemSize)
{
  uint8_t *pAvailMem = pFreeMem;

  if (((uint32_t)pAvailMem) & 3)
  {
    /* Align to next word. */
    pAvailMem = (uint8_t *)(((uint32_t)pAvailMem & ~3) + sizeof(uint32_t));
  }

  /*** Tx Memory ***/

  freeMemSize -= pAvailMem - pFreeMem;
  uint16_t memUsed = lctrIsoTxInitMem(pAvailMem, freeMemSize);
  if (memUsed == 0)
  {
    LL_TRACE_ERR2("LctrInitIsoMem: failed to allocate descriptors, need=%u available=%u", (pAvailMem - pFreeMem), freeMemSize);
    WSF_ASSERT(FALSE);
    return 0;
  }

  pAvailMem += memUsed;

  return (pAvailMem - pFreeMem);
}

/*************************************************************************************************/
/*!
 *  \brief  Transmit ISO data path.
 *
 *  \param  pIsoBuf     ISO buffer.
 */
/*************************************************************************************************/
void LctrTxIso(uint8_t *pIsoBuf)
{
  lctrIsoHdr_t isoHdr;

  /*** Disassemble ISO packet. ***/

  lctrIsoUnpackHdr(&isoHdr, pIsoBuf);

  if (isoHdr.sduLen > pLctrRtCfg->maxIsoSduLen)
  {
    LL_TRACE_ERR2("Invalid ISO header: invalid packet length, actLen=%u, maxLen=%u", isoHdr.sduLen, pLctrRtCfg->maxIsoSduLen);
    WsfMsgFree(pIsoBuf);
    lmgrPersistCb.sendCompCback(isoHdr.handle, 1);
    return;
  }

  if (lmgrIsoCb.availTxBuf == 0)
  {
    LL_TRACE_ERR1("ISO Tx path flow controlled, handle=%u", isoHdr.handle);
    WsfMsgFree(pIsoBuf);
    lmgrPersistCb.sendCompCback(isoHdr.handle, 1);
    return;
  }

  uint16_t expIsoLen = isoHdr.len - HCI_ISO_DL_MIN_LEN + ((isoHdr.tsFlag) ? HCI_ISO_TS_LEN : 0);
  if (isoHdr.sduLen != expIsoLen)
  {
    LL_TRACE_ERR2("Invalid ISO header: packet length mismatch, expSduLen=%u, actSduLen=%u", expIsoLen, isoHdr.sduLen);
    WsfMsgFree(pIsoBuf);
    lmgrPersistCb.sendCompCback(isoHdr.handle, 1);
    return;
  }

  /*** Resolve ISO context. ***/

  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;

  if ((pCisCtx = lctrFindCisByHandle(isoHdr.handle)) != NULL)
  {
    if (!lctrCheckIsCisEstCis(pCisCtx->cisHandle))
    {
      LL_TRACE_ERR1("Invalid ISO handle: link not established cisHandle=%u; dropping packet", isoHdr.handle);
      lmgrPersistCb.sendCompCback(isoHdr.handle, 1);
      WsfMsgFree(pIsoBuf);
      return;
    }

    if ((pCisCtx->localDataPdu.maxTxLen == 0) ||
        ((pCisCtx->bnMToS == 0) && (pCisCtx->role == LL_ROLE_MASTER)) ||
        ((pCisCtx->bnSToM == 0) && (pCisCtx->role == LL_ROLE_SLAVE)))
    {
      LL_TRACE_ERR1("Invalid CIS state: handle=%u does not accept transmissions; dropping packet", isoHdr.handle);
      WsfMsgFree(pIsoBuf);
      lmgrPersistCb.sendCompCback(isoHdr.handle, 1);
      return;
    }

    if (pCisCtx->framing == LL_ISO_PDU_TYPE_UNFRAMED)
    {
      lctrIsoSduTxDecAvailBuf();

      /*** Queue for transmit. ***/

      lctrCisTxDataPduQueue(pCisCtx, &isoHdr, pIsoBuf);
    }
    else /* LL_ISO_PDU_TYPE_FRAMED */
    {
      /* Queue up for transmission. */
      WsfMsgEnq(&pCisCtx->isoalTxCtx.pendingSduQ, 0, pIsoBuf);
      pCisCtx->isoalTxCtx.pendQueueSize++;

      /* PDU assembly handled in the EndOp. */
    }
  }
  else if ((pBisCtx = lctrFindBisByHandle(isoHdr.handle)) != NULL)
  {
    if (isoHdr.sduLen > pBisCtx->pBigCtx->maxSdu)
    {
      LL_TRACE_ERR2("Invalid ISO header: invalid packet length, actLen=%u, maxSdu=%u", isoHdr.sduLen, pBisCtx->pBigCtx->maxSdu);
      WsfMsgFree(pIsoBuf);
      lmgrPersistCb.sendCompCback(isoHdr.handle, 1);
      return;
    }

    lctrIsoSduTxDecAvailBuf();

    /*** Queue for transmit. ***/

    if (pBisCtx->pBigCtx->framing == LL_ISO_PDU_TYPE_UNFRAMED)
    {
      lctrBisTxIsoPduQueue(pBisCtx, &isoHdr, pIsoBuf);
    }
    else /* LL_ISO_PDU_TYPE_FRAMED */
    {
      /* Queue up for transmission. */
      WsfMsgEnq(&pBisCtx->roleData.slv.isoalTxCtx.pendingSduQ, 0, pIsoBuf);
      pBisCtx->roleData.slv.isoalTxCtx.pendQueueSize++;
    }
  }
  else
  {
    LL_TRACE_ERR1("Invalid ISO handle: unknown handle handle=%u; dropping packet", isoHdr.handle);
    WsfMsgFree(pIsoBuf);
    /* Do not inform host of the number of completed event. */
  }
}

/*************************************************************************************************/
/*!
 *  \brief  Receive ISO data path.
 *
 *  \return Received data PDU buffer or NULL if queue empty or data consumed by a data path.
 */
/*************************************************************************************************/
uint8_t *LctrRxIso(void)
{
  uint8_t *pBuf;

  for (unsigned int i = 0; i < pLctrRtCfg->maxCis; i++)
  {
    lctrCisCtx_t *pCisCtx = &pLctrCisTbl[i];

    if ((pCisCtx->enabled) &&
        ((pBuf = lctrIsoRxConnDeq(&pCisCtx->dataPathOutCtx)) != NULL))
    {
      return pBuf;
    }
  }

  for (unsigned int i = 0; i < pLctrRtCfg->maxBis; i++)
  {
    lctrBisCtx_t *pBisCtx = &pLctrBisTbl[i];

    if ((pBisCtx->enabled) &&
        (pBisCtx->pBigCtx->role == LL_ROLE_MASTER) &&
        ((pBuf = lctrBisRxIsoSduDeq(pBisCtx)) != NULL))
    {
      return pBuf;
    }
  }

  return NULL;
}

/*************************************************************************************************/
/*!
 *  \brief      Indicate that received ISO data buffer has been deallocated
 *
 *  \param      numBufs     Number of completed packets.
 *
 *  Indicate that received ISO data buffer has been deallocated.
 */
/*************************************************************************************************/
void LctrRxIsoComplete(uint8_t numBufs)
{
  lctrIsoDataRxIncAvailBuf(numBufs);
}

/*************************************************************************************************/
/*!
 *  \brief      Used to identify and enable the isochronous data path between the host and the
 *              controller for each connected isochronous or broadcast isochronous stream.
 *
 *  \param      handle      BIS or CIS handle.
 *  \param      pPktSn      Packet sequence number.
 *  \param      pTs         Timestamp.
 *  \param      pTimeOffs   Time offset.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
uint8_t LctrReadIsoTxSync(uint16_t handle, uint16_t *pPktSn, uint32_t *pTs, uint32_t *pTimeOffs)
{
  uint8_t status = LL_SUCCESS;
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;

  if ((pCisCtx = lctrFindCisByHandle(handle)) != NULL)
  {
    lctrCigCtx_t *pCigCtx = lctrFindCigById(pCisCtx->cigId);

    *pPktSn = pCisCtx->cisEvtCounter;
    *pTs = pCigCtx->cigBod.dueUsec;
    *pTimeOffs = 0;
  }
  else if ((pBisCtx = lctrFindBisByHandle(handle)) != NULL)
  {
    *pPktSn = pBisCtx->pBigCtx->eventCounter * pBisCtx->pBigCtx->bn;
    *pTs = pBisCtx->pBigCtx->bod.dueUsec;
    *pTimeOffs = 0;
  }
  else
  {
    LL_TRACE_WARN1("LctrReadIsoTxSync: handle=%u not found", handle);
    status = LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief      Used to identify and enable the isochronous data path between the host and the controller for each connected isochronous stream or broadcast isochronous stream.
 *
 *  \param      pSetupDataPath   Parameters for setup ISO data path.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
uint8_t LctrSetupIsoDataPath(LlIsoSetupDataPath_t *pSetupDataPath)
{
  uint8_t status = LL_SUCCESS;
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;
  lctrDataPathCtx_t *pDataPathDir;

  if ((pCisCtx = lctrFindCisByHandle(pSetupDataPath->handle)) != NULL)
  {
    switch (pSetupDataPath->dpDir)
    {
    case LL_ISO_DATA_DIR_INPUT:
      pDataPathDir = (lctrDataPathCtx_t *) (&pCisCtx->dataPathInCtx);
      break;
    case LL_ISO_DATA_DIR_OUTPUT:
      pDataPathDir = (lctrDataPathCtx_t *) (&pCisCtx->dataPathOutCtx);
      break;
    default:
      LL_TRACE_WARN2("LctrSetupIsoDataPath: handle=%u invalid direction dpDir=%u", pSetupDataPath->handle, pSetupDataPath->dpDir);
      return LL_ERROR_CODE_CMD_DISALLOWED;
    }
    return lctrSetupIsoDataPath(pSetupDataPath, pDataPathDir);
  }

  else if ((pBisCtx = lctrFindBisByHandle(pSetupDataPath->handle)) != NULL)
  {
    switch (pSetupDataPath->dpDir)
    {
    case LL_ISO_DATA_DIR_INPUT:
      if (pBisCtx->pBigCtx->role == LL_ROLE_SLAVE)
      {
        status = lctrBisSetDataPath(pBisCtx, pSetupDataPath->dpDir, pSetupDataPath->dpId);
      }
      else
      {
        LL_TRACE_WARN1("LctrSetupIsoDataPath: handle=%u invalid input direction for master", pSetupDataPath->handle);
        status = LL_ERROR_CODE_CMD_DISALLOWED;
      }
      break;
    case LL_ISO_DATA_DIR_OUTPUT:
      if (pBisCtx->pBigCtx->role == LL_ROLE_MASTER)
      {
        status = lctrBisSetDataPath(pBisCtx, pSetupDataPath->dpDir, pSetupDataPath->dpId);
      }
      else
      {
        LL_TRACE_WARN1("LctrSetupIsoDataPath: handle=%u invalid output direction for slave", pSetupDataPath->handle);
        status = LL_ERROR_CODE_CMD_DISALLOWED;
      }
      break;
    default:
      break;
    }
  }
  else
  {
    LL_TRACE_WARN1("LctrSetupIsoDataPath: handle=%u not found", pSetupDataPath->handle);
    status = LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief      Used to remove the isochronous data path associated with the
 *              connected isochronous stream or broadcast isochronous stream.
 *
 *  \param      handle      CIS or BIS handle.
 *  \param      dpDir       Direction of data path to remove.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
uint8_t LctrRemoveIsoDataPath(uint16_t handle, uint8_t dpDir)
{
  uint8_t status = LL_SUCCESS;
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;

  if ((pCisCtx = lctrFindCisByHandle(handle)) != NULL)
  {
    /* Check for validity of parameters before operating on them. */
    if (dpDir | LL_ISO_DATA_PATH_INPUT_BIT)
    {
      if (pCisCtx->dataPathInCtx.id == LL_ISO_DATA_PATH_DISABLED)
      {
        return LL_ERROR_CODE_CMD_DISALLOWED;
      }
    }
    if (dpDir | LL_ISO_DATA_PATH_OUTPUT_BIT)
    {
      if (pCisCtx->dataPathOutCtx.id == LL_ISO_DATA_PATH_DISABLED)
      {
        return LL_ERROR_CODE_CMD_DISALLOWED;
      }
    }

    if (dpDir | LL_ISO_DATA_PATH_INPUT_BIT)
    {
      pCisCtx->dataPathInCtx.id = LL_ISO_DATA_PATH_DISABLED;
    }
    if (dpDir | LL_ISO_DATA_PATH_OUTPUT_BIT)
    {
      pCisCtx->dataPathOutCtx.id = LL_ISO_DATA_PATH_DISABLED;
    }
  }
  else if ((pBisCtx = lctrFindBisByHandle(handle)) != NULL)
  {
    pBisCtx->path = LL_ISO_DATA_PATH_DISABLED;
  }
  else
  {
    LL_TRACE_WARN1("LctrRemoveIsoDataPath: handle=%u not found", handle);
    status = LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief      Enable ISO Tx test.
 *
 *  \param      handle      CIS or BIS handle.
 *  \param      pldType     Payload length type.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
uint8_t LctrIsoTxTest(uint16_t handle, uint8_t pldType)
{
  uint8_t status;
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;

  if ((pCisCtx = lctrFindCisByHandle(handle)) != NULL)
  {
    status = lctrCisTxTest(pCisCtx, pldType);
  }
  else if ((pBisCtx = lctrFindBisByHandle(handle)) != NULL)
  {
    status = lctrBisTxTest(pBisCtx, pldType);
  }
  else
  {
    LL_TRACE_WARN0("LctrIsoTxTest: handle=%u not found");
    return LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief      Enable ISO Rx test.
 *
 *  \param      handle      CIS or BIS handle.
 *  \param      pldType     Payload length type.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
uint8_t LctrIsoRxTest(uint16_t handle, uint8_t pldType)
{
  uint8_t status;
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;

  if ((pCisCtx = lctrFindCisByHandle(handle)) != NULL)
  {
    pCisCtx->rxPendInit = (pldType == LL_ISO_TEST_PL_LEN_ZERO) ? FALSE : TRUE;

    pCisCtx->rxTestEnabled = TRUE;
    pCisCtx->numRxSuccess = 0;
    pCisCtx->numRxMissed = 0;
    pCisCtx->numRxFailed = 0;
    pCisCtx->isoRxPldType = pldType;

    status = LL_SUCCESS;
  }
  else if ((pBisCtx = lctrFindBisByHandle(handle)) != NULL)
  {
    status = lctrBisRxTest(pBisCtx, pldType);
  }
  else
  {
    LL_TRACE_WARN0("LctrIsoRxTest: handle=%u not found");
    return LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief      ISO read test counter.
 *
 *  \param      handle      CIS or BIS handle.
 *  \param      pStats      Pointer to the statistics block.
 *
 *  \return     Status code.
 */
/*************************************************************************************************/
uint8_t LctrIsoReadTestCounter(uint16_t handle, LlIsoTestCtrs_t *pStats)
{
  uint8_t status;
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;

  if ((pCisCtx = lctrFindCisByHandle(handle)) != NULL)
  {
    status = LctrCisReadTestCounters(pCisCtx, pStats);
  }
  else if ((pBisCtx = lctrFindBisByHandle(handle)) != NULL)
  {
    status = LctrBisReadTestCounters(pBisCtx, pStats);
  }
  else
  {
    LL_TRACE_WARN1("LctrIsoReadTestCounter: handle=%u not found", handle);
    status = LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief      Terminate ISO Rx test.
 *
 *  \param      handle      CIS or BIS handle.
 *  \param      pStats      Pointer to the statistics block.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
uint8_t LctrIsoTestEnd(uint16_t handle, LlIsoTestCtrs_t *pStats)
{
  uint8_t status;
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;

  if ((pCisCtx = lctrFindCisByHandle(handle)) != NULL)
  {
    if (!pCisCtx->rxTestEnabled &&
        !pCisCtx->txTestEnabled)
    {
      LL_TRACE_WARN0("Invalid ISO Test state, test mode must be enabled");
      return LL_ERROR_CODE_CMD_DISALLOWED;
    }

    if (pCisCtx->rxTestEnabled)
    {
      status = LctrCisReadTestCounters(pCisCtx, pStats);
      pCisCtx->rxTestEnabled = FALSE;

      /* Counters will be reset on re-initialization of Rx test. */
    }
    if (pCisCtx->txTestEnabled)
    {
      pCisCtx->txTestEnabled = FALSE;
    }
 }
  else if ((pBisCtx = lctrFindBisByHandle(handle)) != NULL)
  {
    status = LctrBisReadTestCounters(pBisCtx, pStats);

    pBisCtx->test.term = TRUE;
  }
  else
  {
    LL_TRACE_WARN1("LctrIsoTestEnd: handle=%u not found", handle);
    status = LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }

  return status;
}

/*************************************************************************************************/
/*!
 *  \brief      Read ISO Link Quality action.
 *
 *  \param      handle      CIS or BIS handle.
 *  \param      pStats      Storage for statistics.
 *
 *  \return     Status error code.
 */
/*************************************************************************************************/
uint8_t LctrReadIsoLinkQual(uint16_t handle, LlIsoLinkQual_t *pStats)
{
  lctrCisCtx_t *pCisCtx;
  lctrBisCtx_t *pBisCtx;
  if ((pCisCtx = lctrFindCisByHandle(handle)) != NULL)
  {
    memcpy(pStats, &pCisCtx->isoLinkQualStats, sizeof(LlIsoLinkQual_t));
    return LL_SUCCESS;
  }
  else if ((pBisCtx = lctrFindBisByHandle(handle)) != NULL)
  {
    return LL_SUCCESS;
  }
  else
  {
    LL_TRACE_WARN1("LctrReadIsoLinkQual: handle=%u not found", handle);
    return LL_ERROR_CODE_UNKNOWN_CONN_ID;
  }
}

/*************************************************************************************************/
/*!
 *  \brief      ISO event complete enable.
 *
 *  \param      enable          Set to TRUE to enable, FALSE to disable.
 *
 *  Enable or disable reports about the scanners from which an advertiser receives scan requests.
 */
/*************************************************************************************************/
void LlIsoEventCompleteEnable(uint8_t enable)
{
  lmgrCb.sendIsoCmplEvt = enable;
}

/*************************************************************************************************/
/*!
 *  \brief      Notify host ISO Event completion.
 *
 *  \param      handle          CIG or BIG Handle.
 *  \param      evtCtr          Event Counter.
 */
/*************************************************************************************************/
void lctrNotifyHostIsoEventComplete(uint8_t handle, uint32_t evtCtr)
{
  LlIsoEventCmplInd_t evt;

  evt.hdr.param = handle;
  evt.hdr.event = LL_ISO_EVT_CMPL_IND;
  evt.hdr.status = LL_SUCCESS;

  evt.handle = handle;
  evt.evtCtr = evtCtr;

  LL_TRACE_INFO1("### LlEvent ###  LL_ISO_EVT_CMPL_IND, handle=%u", handle);

  LmgrSendEvent((LlEvt_t *)&evt);
}

/*************************************************************************************************/
/*!
 *  \brief      Send Codec SDU data.
 *
 *  \param      handle          Stream ID.
 */
/*************************************************************************************************/
void lctrIsoSendCodecSdu(uint16_t handle)
{
  lctrBisCtx_t *pBisCtx;

  if ((pBisCtx = lctrFindBisByHandle(handle)) == NULL)
  {
    LL_TRACE_WARN1("lctrIsoSendCodecSdu: handle=%u not found", handle);
    return;
  }

  uint8_t *pSduBuf;

  if ((pSduBuf = WsfMsgAlloc(pLctrRtCfg->maxIsoSduLen + HCI_ISO_DL_MAX_LEN)) == NULL)
  {
    LL_TRACE_ERR1("!!! Out of memory; dropping input SDU, stream=%u", handle);
    return;
  }

  uint16_t len;
  uint32_t pktCtr;

  WSF_ASSERT(lctrCodecHdlr.in);
  if ((len = lctrCodecHdlr.in(handle, pSduBuf + HCI_ISO_HDR_LEN + HCI_ISO_DL_MAX_LEN,
                              pBisCtx->pBigCtx->maxSdu, &pktCtr)) == 0)
  {
    LL_TRACE_WARN1("ISO audio stream data not available, stream=%u", handle);
    WsfMsgFree(pSduBuf);
    return;
  }

  lctrIsoHdr_t hdr =
  {
    .handle = handle,
    .pb = LCTR_PB_COMP,
    .tsFlag = TRUE,
    .len = len,
    .ts = 0,
    .pktSn = pktCtr,
    .sduLen = len,
    .ps = LCTR_PS_VALID
  };

  lctrIsoPackHdr(pSduBuf, &hdr);
  LctrTxIso(pSduBuf);
}

/*************************************************************************************************/
/*!
 *  \brief      Setup ISO data path.
 *
 *  \param      pSetupDataPath     Data path setup parameters.
 *  \param      pDataPathCtx       Generic data path context.
 */
/*************************************************************************************************/
uint8_t lctrSetupIsoDataPath(LlIsoSetupDataPath_t *pSetupDataPath, lctrDataPathCtx_t *pDataPathCtx)
{
  switch (pSetupDataPath->dpDir)
  {
    case LL_ISO_DATA_DIR_INPUT:
    {
      if (pSetupDataPath->dpId == pDataPathCtx->in.id)
      {
        return LL_SUCCESS;
      }

      /* No teardown needed. */
      pDataPathCtx->in.id = pSetupDataPath->dpId;
      /* No setup needed. */
      break;
    }

    case LL_ISO_DATA_DIR_OUTPUT:
    {
      if (pSetupDataPath->dpId == pDataPathCtx->out.id)
      {
        return LL_SUCCESS;
      }

      lctrIsoOutDataPathClear(&pDataPathCtx->out);
      pDataPathCtx->out.id = pSetupDataPath->dpId;
      lctrIsoOutDataPathSetup(&pDataPathCtx->out);
      break;
    }

    default:
      LL_TRACE_ERR1("Invalid value dpDir=%u", pSetupDataPath->dpDir);
      break;
  }

  return LL_SUCCESS;
}