/*************************************************************************************************/ /*! * \file * * \brief DM Connected Isochronous Stream (CIS) management module. * * 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 <string.h> #include "wsf_types.h" #include "wsf_assert.h" #include "wsf_trace.h" #include "wsf_msg.h" #include "wsf_os.h" #include "util/bstream.h" #include "dm_api.h" #include "dm_dev.h" #include "dm_main.h" #include "dm_conn.h" #include "dm_cis.h" /************************************************************************************************** Local Functions **************************************************************************************************/ static void dmCisReset(void); static void dmCisMsgHandler(wsfMsgHdr_t *pMsg); static void dmCisHciHandler(hciEvt_t *pEvent); /************************************************************************************************** Local Variables **************************************************************************************************/ /*! Action set for CIS module */ static const dmCisAct_t dmCisActSetMain[] = { dmCisSmActNone, dmCisSmActClose, dmCisSmActCisEst, dmCisSmActCisEstFailed, dmCisSmActCisClosed, }; /* CIS component function interface */ static const dmFcnIf_t dmCisFcnIf = { dmCisReset, dmCisHciHandler, dmCisMsgHandler }; /************************************************************************************************** Global Variables **************************************************************************************************/ /* Control block */ dmCisCb_t dmCisCb; /*************************************************************************************************/ /*! * \brief Allocate a DM CIG control block. * * \param cigId CIG identifier. * * \return Pointer to CIG CB or NULL if failure. */ /*************************************************************************************************/ dmCisCigCb_t *dmCisCigCbAlloc(uint8_t cigId) { dmCisCigCb_t *pCigCb = dmCisCb.cisCigCb; uint8_t i; WSF_ASSERT(cigId < HCI_MAX_CIG_ID); for (i = 0; i < DM_CIG_MAX; i++, pCigCb++) { if (pCigCb->inUse == FALSE) { memset(pCigCb, 0, sizeof(dmCisCigCb_t)); pCigCb->cigId = cigId; /* set Cig default values */ pCigCb->packing = HCI_PACKING_SEQUENTIAL; pCigCb->framing = HCI_FRAMING_UNFRAMED; pCigCb->sca = HCI_MIN_SCA; pCigCb->sduIntervalMToS = pCigCb->sduIntervalSToM = HCI_DEFAULT_SDU_INTERV; pCigCb->transLatMToS = pCigCb->transLatSToM = HCI_DEFAULT_CIS_TRANS_LAT; pCigCb->inUse = TRUE; DM_TRACE_ALLOC1("dmCisCigCbAlloc cigId:%d", cigId); return pCigCb; } } DM_TRACE_ERR0("dmCisCigCbAlloc failed"); return NULL; } /*************************************************************************************************/ /*! * \brief Allocate a DM CIS connection control block. * * \param cigId Identfier of CIG that CIS belongs to. * \param cisId CIS identfier. * \param role Device role. * * \return Pointer to CIS CCB or NULL if failure. */ /*************************************************************************************************/ dmCisCcb_t *dmCisCcbAlloc(uint8_t cigId, uint8_t cisId, uint8_t role) { dmCisCcb_t *pCcb = dmCisCb.cisCcb; uint8_t i; WSF_ASSERT(cigId < HCI_MAX_CIG_ID); WSF_ASSERT(cisId < HCI_MAX_CIS_ID); for (i = 0; i < DM_CIS_MAX; i++, pCcb++) { if (pCcb->inUse == FALSE) { memset(pCcb, 0, sizeof(dmCisCcb_t)); pCcb->cigId = cigId; pCcb->cisId = cisId; pCcb->role = role; pCcb->aclHandle = DM_CONN_HCI_HANDLE_NONE; pCcb->cisHandle = DM_CONN_HCI_HANDLE_NONE; pCcb->inUse = TRUE; DM_TRACE_ALLOC2("dmCisCcbAlloc cigId:%d cisId:%d", cigId, cisId); return pCcb; } } DM_TRACE_ERR0("dmCisCcbAlloc failed"); return NULL; } /*************************************************************************************************/ /*! * \brief Deallocate a DM CIG control block. * * \param pCigCb CIG control block. * * \return None. */ /*************************************************************************************************/ void dmCisCigCbDealloc(dmCisCigCb_t *pCigCb) { DM_TRACE_FREE1("dmCigCbDealloc %d", pCigCb->cigId); pCigCb->inUse = FALSE; } /*************************************************************************************************/ /*! * \brief Deallocate a DM CIS connection control block. * * \param pCcb CIS connection control block. * * \return None. */ /*************************************************************************************************/ void dmCisCcbDealloc(dmCisCcb_t *pCcb) { DM_TRACE_FREE2("dmCisCcbDealloc cigId:%d cisId:%d", pCcb->cigId, pCcb->cisId); pCcb->inUse = FALSE; } /*************************************************************************************************/ /*! * \brief Deallocate all CIS connection control blocks associated with the given CIG ID. * * \param cigId CIG identifer. * * \return None. */ /*************************************************************************************************/ void dmCisCcbDeallocByCigId(uint8_t cigId) { dmCisCcb_t *pCcb = dmCisCb.cisCcb; uint8_t i; for (i = DM_CIS_MAX; i > 0; i--, pCcb++) { if (pCcb->inUse && (pCcb->cigId == cigId)) { dmCisCcbDealloc(pCcb); } } } /*************************************************************************************************/ /*! * \brief Find a CIS connection control block with matching handle. * * \param handle CIS connection handle. * * \return Pointer to CIS connection control block. NULL if not found. */ /*************************************************************************************************/ dmCisCcb_t *dmCisCcbByHandle(uint16_t handle) { dmCisCcb_t *pCcb = dmCisCb.cisCcb; uint8_t i; for (i = DM_CIS_MAX; i > 0; i--, pCcb++) { if (pCcb->inUse && (pCcb->cisHandle == handle)) { return pCcb; } } DM_TRACE_INFO1("dmCisCcbByHandle not found %d", handle); return NULL; } /*************************************************************************************************/ /*! * \brief Return the CIS connection control block for the given CIG/CIS IDs. * * \param cigId CIG identifer. * \param cisId CIS identifer. * * \return Pointer to CIS connection control block. NULL if not found. */ /*************************************************************************************************/ dmCisCcb_t *dmCisCcbById(uint8_t cigId, uint8_t cisId) { dmCisCcb_t *pCcb = dmCisCb.cisCcb; uint8_t i; for (i = DM_CIS_MAX; i > 0; i--, pCcb++) { if (pCcb->inUse && (pCcb->cigId == cigId) && (pCcb->cisId == cisId)) { return pCcb; } } DM_TRACE_INFO2("dmCisCcbById not found cigId:%d cisId:%d", cigId, cisId); return NULL; } /*************************************************************************************************/ /*! * \brief Return the CIG control block for the given CIG ID. * * \param cigId CIG identifer. * * \return Pointer to CIG control block. NULL if not found. */ /*************************************************************************************************/ dmCisCigCb_t *dmCisCigCbById(uint8_t cigId) { dmCisCigCb_t *pCigCb = dmCisCb.cisCigCb; uint8_t i; for (i = DM_CIG_MAX; i > 0; i--, pCigCb++) { if (pCigCb->inUse && (pCigCb->cigId == cigId)) { return pCigCb; } } return NULL; } /*************************************************************************************************/ /*! * \brief Empty action. * * \param pCcb CIS connection control block. * \param pMsg WSF message. * \param oldState Old state. * * \return None. */ /*************************************************************************************************/ void dmCisSmActNone(dmCisCcb_t *pCcb, dmCisMsg_t *pMsg) { return; } /*************************************************************************************************/ /*! * \brief Close a CIS connection. * * \param pCcb CIS connection control block. * \param pMsg WSF message. * \param oldState Old state. * * \return None. */ /*************************************************************************************************/ void dmCisSmActClose(dmCisCcb_t *pCcb, dmCisMsg_t *pMsg) { /* close CIS connection */ HciDisconnectCmd(pCcb->cisHandle, pMsg->apiClose.reason); } /*************************************************************************************************/ /*! * \brief Handle a CIS established event from HCI. * * \param pCcb CIS connection control block. * \param pMsg WSF message. * \param oldState Old state. * * \return None. */ /*************************************************************************************************/ void dmCisSmActCisEst(dmCisCcb_t *pCcb, dmCisMsg_t *pMsg) { pMsg->hdr.event = DM_CIS_OPEN_IND; (*dmCb.cback)((dmEvt_t *) pMsg); } /*************************************************************************************************/ /*! * \brief Handle a CIS establish failure event from HCI. * * \param pCcb CIS connection control block. * \param pMsg WSF message. * \param oldState Old state. * * \return None. */ /*************************************************************************************************/ void dmCisSmActCisEstFailed(dmCisCcb_t *pCcb, dmCisMsg_t *pMsg) { pMsg->hdr.event = DM_CIS_CLOSE_IND; (*dmCb.cback)((dmEvt_t *) pMsg); } /*************************************************************************************************/ /*! * \brief Handle a disconnect complete event from HCI. * * \param pCcb CIS connection control block. * \param pMsg WSF message. * \param oldState Old state. * * \return None. */ /*************************************************************************************************/ void dmCisSmActCisClosed(dmCisCcb_t *pCcb, dmCisMsg_t *pMsg) { pMsg->hdr.event = DM_CIS_CLOSE_IND; (*dmCb.cback)((dmEvt_t *) pMsg); } /*************************************************************************************************/ /*! * \brief Handle a CIS connection close event. * * \param aclHandle ACL connection handle. * \param reason Reason. * * \return None. */ /*************************************************************************************************/ static void dmCisHandleConnClose(uint16_t aclHandle, uint8_t reason) { dmCisCcb_t *pCcb = dmCisCb.cisCcb; hciDisconnectCmplEvt_t disconnectCmpl; /* generate HCI CIS disconnect complete event */ disconnectCmpl.hdr.event = HCI_CIS_DISCONNECT_CMPL_CBACK_EVT; disconnectCmpl.hdr.status = disconnectCmpl.status = HCI_SUCCESS; disconnectCmpl.reason = reason; for (uint8_t i = DM_CIS_MAX; i > 0; i--, pCcb++) { if (!pCcb->inUse) { continue; } if ((pCcb->aclHandle == aclHandle) || (aclHandle == DM_CONN_HCI_HANDLE_NONE)) { /* set Cis Id */ disconnectCmpl.hdr.param = disconnectCmpl.handle = pCcb->cisHandle; /* handle the event */ dmCisHciHandler((hciEvt_t *) &disconnectCmpl); } } } /*************************************************************************************************/ /*! * \brief Reset the CIS module. * * \return None. */ /*************************************************************************************************/ static void dmCisReset(void) { dmCisCigCb_t *pCigCb = dmCisCb.cisCigCb; hciLeRemoveCigCmdCmplEvt_t removeCigCmdCmpl; dmCisHandleConnClose(DM_CONN_HCI_HANDLE_NONE, HCI_ERR_LOCAL_TERMINATED); /* generate HCI remove CIG command complete event */ removeCigCmdCmpl.hdr.event = HCI_LE_REMOVE_CIG_CMD_CMPL_CBACK_EVT; removeCigCmdCmpl.hdr.status = removeCigCmdCmpl.status = HCI_SUCCESS; for (uint8_t i = DM_CIG_MAX; i > 0; i--, pCigCb++) { if (pCigCb->inUse) { /* set Cig Id */ removeCigCmdCmpl.hdr.param = removeCigCmdCmpl.cigId = pCigCb->cigId; /* handle the event */ dmCisCigHciHandler((hciEvt_t *) &removeCigCmdCmpl); } } } /*************************************************************************************************/ /*! * \brief DM CIS WSF message handler. * * \param pMsg WSF message. * * \return None. */ /*************************************************************************************************/ static void dmCisMsgHandler(wsfMsgHdr_t *pMsg) { dmCisCcb_t *pCcb; /* look up ccb from cis handle */ if ((pCcb = dmCisCcbByHandle(pMsg->param)) != NULL) { /* execute state machine */ dmCisSmExecute(pCcb, (dmCisMsg_t *) pMsg); } } /*************************************************************************************************/ /*! * \brief DM Conn HCI callback event handler. * * \param pEvent Pointer to HCI callback event structure. * * \return None. */ /*************************************************************************************************/ static void dmCisHciHandler(hciEvt_t *pEvent) { dmCisCcb_t *pCcb = dmCisCcbByHandle(pEvent->hdr.param); /* handle special cases for CIS request event */ if (pEvent->hdr.event == HCI_LE_CIS_REQ_CBACK_EVT) { /* first check if ccb exists for this handle */ if (pCcb == NULL) { dmConnId_t connId; if ((connId = DmConnIdByHandle(pEvent->leCisReq.aclHandle)) != DM_CONN_ID_NONE) { /* if slave, allocate new ccb */ if (DmConnRole(connId) == DM_ROLE_SLAVE) { pCcb = dmCisCcbAlloc(pEvent->leCisReq.cigId, pEvent->leCisReq.cisId, DM_ROLE_SLAVE); } } } /* translate HCI event to state machine event */ pEvent->hdr.event = DM_CIS_MSG_HCI_LE_CIS_REQ; } else if (pEvent->hdr.event == HCI_LE_CIS_EST_CBACK_EVT) { /* translate HCI event to state machine event */ if (pEvent->hdr.status == HCI_SUCCESS) { pEvent->hdr.event = DM_CIS_MSG_HCI_LE_CIS_EST; } else { pEvent->hdr.event = DM_CIS_MSG_HCI_LE_CIS_EST_FAIL; } } else /* HCI_CIS_DISCONNECT_CMPL_CBACK_EVT */ { /* translate HCI event to state machine event */ pEvent->hdr.event = DM_CIS_MSG_HCI_DISCONNECT_CMPL; } /* if ccb found */ if (pCcb != NULL) { /* execute state machine */ dmCisSmExecute(pCcb, (dmCisMsg_t *) pEvent); } } /*************************************************************************************************/ /*! * \brief Initialize DM Connected Isochronous Stream (CIS) manager. * * \return None. */ /*************************************************************************************************/ void DmCisInit(void) { WsfTaskLock(); dmFcnIfTbl[DM_ID_CIS] = (dmFcnIf_t *) &dmCisFcnIf; dmCisActSet[DM_CIS_ACT_SET_MAIN] = (dmCisAct_t *) dmCisActSetMain; HciSetLeSupFeat(HCI_LE_SUP_FEAT_ISO_HOST_SUPPORT, TRUE); WsfTaskUnlock(); } /*************************************************************************************************/ /*! * \brief Close the Connected Isochronous Stream (CIS) connection with the given handle. * * \param handle CIS connection handle. * \param reason Reason connection is being closed. * * \return None. */ /*************************************************************************************************/ void DmCisClose(uint16_t handle, uint8_t reason) { dmCisApiClose_t *pMsg; if ((pMsg = WsfMsgAlloc(sizeof(dmCisApiClose_t))) != NULL) { pMsg->hdr.event = DM_CIS_MSG_API_CLOSE; pMsg->hdr.param = handle; pMsg->hdr.status = pMsg->reason = reason; WsfMsgSend(dmCb.handlerId, pMsg); } } /*************************************************************************************************/ /*! * \brief For internal use only. Find the Connected Isochronous Stream (CIS) ID with matching * handle. * * \param handle CIS identifer. * * \return CIS identifier or DM_CIS_ID_NONE if error. */ /*************************************************************************************************/ uint8_t DmCisIdByHandle(uint16_t handle) { dmCisCcb_t *pCcb; if ((pCcb = dmCisCcbByHandle(handle)) != NULL) { return pCcb->cisId; } return DM_CIS_ID_NONE; } /*************************************************************************************************/ /*! * \brief For internal use only. Return TRUE if the Connected Isochronous Stream (CIS) * connection is in use. * * \param handle CIS connection handle. * * \return TRUE if the CIS connection is in use, FALSE otherwise. */ /*************************************************************************************************/ bool_t DmCisConnInUse(uint16_t handle) { dmCisCcb_t *pCcb; if ((pCcb = dmCisCcbByHandle(handle)) != NULL) { return pCcb->inUse; } return FALSE; } /*************************************************************************************************/ /*! * \brief For internal use only. Return TRUE if Connected Isochronous Group (CIG) is in use. * * \param cigId CIG identifier. * * \return TRUE if CIG is in use, FALSE otherwise. */ /*************************************************************************************************/ bool_t DmCisCigInUse(uint8_t cigId) { if (dmCisCigCbById(cigId) != NULL) { return TRUE; } return FALSE; } /*************************************************************************************************/ /*! * \brief For internal use only. Return TRUE if the Connected Isochronous Stream (CIS) * connection is in use. * * \param cigId CIG identifier. * \param cisId CIS identifier. * * \return TRUE if the CIS connection is in use, FALSE otherwise. */ /*************************************************************************************************/ bool_t DmCisInUse(uint8_t cigId, uint8_t cisId) { if (dmCisCcbById(cigId, cisId) != NULL) { return TRUE; } return FALSE; }