diff options
Diffstat (limited to 'stack/l2cap/l2c_ble.c')
-rw-r--r-- | stack/l2cap/l2c_ble.c | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/stack/l2cap/l2c_ble.c b/stack/l2cap/l2c_ble.c index a5cfb4d78..db9eae6b4 100644 --- a/stack/l2cap/l2c_ble.c +++ b/stack/l2cap/l2c_ble.c @@ -225,6 +225,357 @@ UINT16 L2CA_GetDisconnectReason (BD_ADDR remote_bda, tBT_TRANSPORT transport) return reason; } +#if (defined(LE_L2CAP_CFC_INCLUDED) && (LE_L2CAP_CFC_INCLUDED == TRUE)) +/******************************************************************************* +** +** Function L2CA_LE_Register +** +** Description Other layers call this function to register for LE L2CAP +** services. +** +** Returns PSM to use or zero if error. Typically, the PSM returned +** is the same as was passed in. +** +*******************************************************************************/ +UINT16 L2CA_LE_Register (UINT16 psm, tL2CAP_APPL_INFO *p_cb_info) +{ + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API ("LE-L2CAP: %s called for PSM: 0x%04x", __FUNCTION__, psm); + + /* Verify that the required callback info has been filled */ + + if ((!p_cb_info->pL2CA_LE_ConnectInd_Cb) + || (!p_cb_info->pL2CA_LE_ConnectCfm_Cb) + || (!p_cb_info->pL2CA_DataInd_Cb) + || (!p_cb_info->pL2CA_DisconnectInd_Cb) + || (!p_cb_info->pL2CA_DisconnectCfm_Cb)) + { + L2CAP_TRACE_ERROR ("LE-L2CAP: no cb registering PSM: 0x%04x", psm); + return (0); + } + + /* Verify PSM is valid */ + if (L2C_LE_INVALID_PSM(psm)) + { + L2CAP_TRACE_ERROR ("LE-L2CAP: invalid PSM value, PSM: 0x%04x", psm); + return (0); + } + + /* If registration block already there, just overwrite it */ + if ((p_rcb = l2cu_find_rcb_by_psm (psm, BT_TRANSPORT_LE)) == NULL) + { + if ((p_rcb = l2cu_allocate_rcb (psm, BT_TRANSPORT_LE)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: no RCB available, PSM: 0x%04x ", psm); + return (0); + } + } + + p_rcb->api = *p_cb_info; + p_rcb->real_psm = psm; + + return (psm); +} + +/******************************************************************************* +** +** Function L2CA_LE_Deregister +** +** Description Other layers call this function to de-register for L2CAP +** services. +** +** Returns void +** +*******************************************************************************/ +void L2CA_LE_Deregister (UINT16 psm) +{ + tL2C_RCB *p_rcb; + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb; + int ii; + + L2CAP_TRACE_API ("LE-L2CAP: L2CA_LE_Deregister() called for PSM: 0x%04x", psm); + + if ((p_rcb = l2cu_find_rcb_by_psm (psm, BT_TRANSPORT_LE)) != NULL) + { + p_lcb = &l2cb.lcb_pool[0]; + for (ii = 0; ii < MAX_L2CAP_LINKS; ii++, p_lcb++) + { + if (p_lcb->in_use) + { + if (((p_ccb = p_lcb->ccb_queue.p_first_ccb) == NULL) + || (p_lcb->link_state == LST_DISCONNECTING)) + continue; + + if ((p_ccb->in_use) && + ((p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP) || + (p_ccb->chnl_state == CST_W4_L2CA_DISCONNECT_RSP))) + continue; + + if (p_ccb->p_rcb == p_rcb) + l2c_le_csm_execute (p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + } + } + l2cu_release_rcb (p_rcb); + } + else + { + L2CAP_TRACE_WARNING ("LE-L2CAP: - PSM: 0x%04x not found for deregistration", psm); + } +} + +/******************************************************************************* +** +** Function LE_L2CA_CreditBasedConn_Req +** +** Description This function used to send the LE CB connection request +** +** Returns local cid +** +*******************************************************************************/ +UINT16 L2CA_LE_CreditBasedConn_Req (BD_ADDR rem_bda, tL2CAP_LE_CONN_INFO *conn_info) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + UINT16 max_mtu; + + /* Fail if we have not established communications with the controller */ + if (!conn_info || !BTM_IsDeviceUp()) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Device not ready/No conn info"); + return (0); + } + /* Fail if the PSM is not registered */ + if ((p_rcb = l2cu_find_rcb_by_psm (conn_info->le_psm, BT_TRANSPORT_LE)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: no RCB for PSM: 0x%04x", conn_info->le_psm); + return (0); + } + + max_mtu = (GKI_get_pool_bufsize (HCI_LE_ACL_POOL_ID) - + L2CAP_LE_MIN_OFFSET - L2CAP_SDU_LEN_OFFSET); + + /* mtu check */ + if((conn_info->le_mtu < L2CAP_LE_MIN_MTU) || + (conn_info->le_mtu > max_mtu)) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Invalid MTU size"); + L2CAP_TRACE_WARNING ("LE-L2CAP: mtu should be in the range 23 to %d", max_mtu); + return (0); + } + + /* mps check */ + if((conn_info->le_mps < L2CAP_LE_MIN_MTU) || + (conn_info->le_mps > conn_info->le_mtu) || + (conn_info->le_mps > L2CAP_LE_FLOWCTL_MAX_MPS)) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Invalid MPS size"); + L2CAP_TRACE_WARNING ("LE-L2CAP: mps should be in the range 23 to %d", conn_info->le_mtu); + return (0); + } + /* First, see if we already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda, BT_TRANSPORT_LE)) == NULL) + { + /* No link. Get an LCB and start link establishment */ + if ( ((p_lcb = l2cu_allocate_lcb (rem_bda, FALSE, BT_TRANSPORT_LE)) == NULL) + || (l2cu_create_conn(p_lcb, BT_TRANSPORT_LE) == FALSE) ) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: conn not started for PSM: 0x%04x p_lcb: 0x%08x", + conn_info->le_psm, p_lcb); + return (0); + } + } + + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: no CCB for PSM: 0x%04x", conn_info->le_psm); + return (0); + } + + /* make the channel as LE COC channel */ + p_ccb->is_le_coc = TRUE; + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + + if (conn_info) + { + p_ccb->le_loc_conn_info.le_psm = conn_info->le_psm; + p_ccb->le_loc_conn_info.le_mtu = conn_info->le_mtu; + p_ccb->le_loc_conn_info.le_mps = conn_info->le_mps; + p_ccb->le_loc_conn_info.init_credits = conn_info->init_credits; + p_ccb->le_loc_conn_info.credits = conn_info->init_credits; + } + /* If link is up, start the L2CAP connection */ + if (p_lcb->link_state == LST_CONNECTED) + { + l2c_le_csm_execute (p_ccb, L2CEVT_L2CA_LE_CONNECT_REQ, NULL); + } + else if (p_lcb->link_state == LST_DISCONNECTING) + { + L2CAP_TRACE_DEBUG ("LE-L2CAP: link disconnecting: RETRY LATER"); + + /* Save ccb to pending list so that it can restarted */ + p_lcb->p_pending_ccb = p_ccb; + } + + L2CAP_TRACE_API ("LE-L2CAP: (psm: 0x%04x) returned CID: 0x%04x", conn_info->le_psm, + p_ccb->local_cid); + + /* Return the local CID as our handle */ + return (p_ccb->local_cid); + +} +/******************************************************************************* +** +** Function L2CA_LE_CreditBasedConn_Rsp +** +** Description This function enables the LE credit based connection response +** +** Returns +** +*******************************************************************************/ +BOOLEAN L2CA_LE_CreditBasedConn_Rsp (BD_ADDR p_bd_addr, UINT8 identifier, + UINT16 lcid, tL2CAP_LE_CONN_INFO *conn_info) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + UINT16 max_mtu; + + L2CAP_TRACE_WARNING ("LE-L2CAP: %s CID: 0x%04x Result: %d BDA: %08x%04x ", + __FUNCTION__, lcid, conn_info->result, + (p_bd_addr[0]<<24)+(p_bd_addr[1]<<16)+(p_bd_addr[2]<<8)+p_bd_addr[3], + (p_bd_addr[4]<<8)+p_bd_addr[5]); + + if(conn_info == NULL) + { + return (FALSE); + } + + /* First, find the link control block */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr, BT_TRANSPORT_LE)) == NULL) + { + /* No link. Get an LCB and start link establishment */ + L2CAP_TRACE_WARNING ("LE-L2CAP: no LCB for L2CA_LE_CreditBasedConn_Rsp"); + return (FALSE); + } + + /* Now, find the channel control block */ + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: no CCB for L2CA_LE_CreditBasedConn_Rsp"); + return (FALSE); + } + + max_mtu = (GKI_get_pool_bufsize (HCI_LE_ACL_POOL_ID) - + L2CAP_LE_MIN_OFFSET - L2CAP_SDU_LEN_OFFSET); + + /* mtu check */ + if((conn_info->le_mtu < L2CAP_LE_MIN_MTU) || + (conn_info->le_mtu > max_mtu)) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Invalid MTU size"); + L2CAP_TRACE_WARNING ("LE-L2CAP: mtu should be in the range 23 to %d", max_mtu); + return (0); + } + + /* mps check */ + if((conn_info->le_mps < L2CAP_LE_MIN_MTU) || + (conn_info->le_mps > conn_info->le_mtu) || + (conn_info->le_mps > L2CAP_LE_FLOWCTL_MAX_MPS)) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Invalid MPS size"); + L2CAP_TRACE_WARNING ("LE-L2CAP: mps should be in the range 23 to %d", + conn_info->le_mtu); + return (0); + } + + /* The IDs must match */ + if (p_ccb->remote_id != identifier) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: identifier is not matched p_ccb->remote_id: %d"\ + "identifier: %d", p_ccb->remote_id, identifier); + return (FALSE); + } + + if (conn_info) + { + p_ccb->le_loc_conn_info.le_psm = conn_info->le_psm; + p_ccb->le_loc_conn_info.le_mtu = conn_info->le_mtu; + p_ccb->le_loc_conn_info.le_mps = conn_info->le_mps; + p_ccb->le_loc_conn_info.init_credits = conn_info->init_credits; + p_ccb->le_loc_conn_info.credits = conn_info->init_credits; + p_ccb->le_loc_conn_info.result = conn_info->result; + } + + if (conn_info->result == L2CAP_LE_CONN_OK) + l2c_le_csm_execute (p_ccb, L2CEVT_L2CA_LE_CONNECT_RSP, NULL); + else + l2c_le_csm_execute (p_ccb, L2CEVT_L2CA_LE_CONNECT_RSP_NEG, NULL); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_LE_SetFlowControlCredits +** +** Description This function sets the credits for LE incase credits did +** not set during the LE connection establishment. +** +** Returns +** +*******************************************************************************/ +BOOLEAN L2CA_LE_SetFlowControlCredits (UINT16 cid, UINT16 credits) +{ + tL2C_CCB *p_ccb; + L2CAP_TRACE_WARNING ("LE-L2CAP: %s credits: %d CID: 0x%04x", __FUNCTION__, + credits, cid); + + /* Find the channel control block. We don't know the link it is on. */ + if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: no CCB found"); + return (FALSE); + } + + if ((BT_TRANSPORT_LE != l2cu_get_chnl_transport(p_ccb)) || + (p_ccb->is_le_coc == FALSE)) + { + L2CAP_TRACE_EVENT ("LE-L2CAP: not a LE COC channel"); + return (FALSE); + } + + /* check for initial credits set to zero */ + if( (!p_ccb->le_loc_conn_info.init_credits) && + (credits >= L2CAP_LE_FLOWCTL_MIN_CREDITS) && + (p_ccb->chnl_state == CST_OPEN) ) + { + p_ccb->le_loc_conn_info.init_credits = credits; + l2c_le_send_credits (p_ccb); + return TRUE; + } + else if (p_ccb->le_loc_conn_info.init_credits) + { + if (p_ccb->le_loc_conn_info.init_credits > p_ccb->le_loc_conn_info.credits) + { + l2cu_send_peer_le_credit_based_flow_ctrl(p_ccb, + p_ccb->le_loc_conn_info.init_credits - p_ccb->le_loc_conn_info.credits); + return (TRUE); + } + else + { + return (FALSE); + } + } + else + { + return (FALSE); + } +} +#endif /* LE_L2CAP_CFC_INCLUDED */ /******************************************************************************* ** @@ -239,6 +590,9 @@ void l2cble_notify_le_connection (BD_ADDR bda) { tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr (bda, BT_TRANSPORT_LE); tACL_CONN *p_acl = btm_bda_to_acl(bda, BT_TRANSPORT_LE) ; +#if (defined(LE_L2CAP_CFC_INCLUDED) && (LE_L2CAP_CFC_INCLUDED == TRUE)) + tL2C_CCB *p_ccb; +#endif if (p_lcb != NULL && p_acl != NULL && p_lcb->link_state != LST_CONNECTED) { @@ -246,6 +600,14 @@ void l2cble_notify_le_connection (BD_ADDR bda) btm_establish_continue(p_acl); /* update l2cap link status and send callback */ p_lcb->link_state = LST_CONNECTED; +#if (defined(LE_L2CAP_CFC_INCLUDED) && (LE_L2CAP_CFC_INCLUDED == TRUE)) + L2CAP_TRACE_DEBUG ("LE-L2CAP: %s Notifying connection completed", __FUNCTION__); + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + l2c_le_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM, NULL); + } +#endif + l2cu_process_fixed_chnl_resp (p_lcb); } } @@ -379,6 +741,9 @@ void l2cble_advertiser_conn_comp (UINT16 handle, BD_ADDR bda, tBLE_ADDR_TYPE typ UNUSED(conn_interval); UNUSED(conn_latency); UNUSED(conn_timeout); +#if (defined(LE_L2CAP_CFC_INCLUDED) && (LE_L2CAP_CFC_INCLUDED == TRUE)) + tL2C_CCB *p_ccb; +#endif /* See if we have a link control block for the remote device */ p_lcb = l2cu_find_lcb_by_bd_addr (bda, BT_TRANSPORT_LE); @@ -431,6 +796,13 @@ void l2cble_advertiser_conn_comp (UINT16 handle, BD_ADDR bda, tBLE_ADDR_TYPE typ if (!HCI_LE_SLAVE_INIT_FEAT_EXC_SUPPORTED(controller_get_interface()->get_features_ble()->as_array)) { p_lcb->link_state = LST_CONNECTED; +#if (defined(LE_L2CAP_CFC_INCLUDED) && (LE_L2CAP_CFC_INCLUDED == TRUE)) + L2CAP_TRACE_DEBUG ("LE-L2CAP: %s Notifying connection completed", __FUNCTION__); + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + l2c_le_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM, NULL); + } +#endif l2cu_process_fixed_chnl_resp (p_lcb); } @@ -599,6 +971,16 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) UINT16 cmd_len; UINT16 min_interval, max_interval, latency, timeout; +#if (defined(LE_L2CAP_CFC_INCLUDED) && (LE_L2CAP_CFC_INCLUDED == TRUE)) + UINT16 lcid, rcid, credits, max_credits; + tL2C_LE_CB_CONN_REQ le_cb_conn_req; + tL2C_LE_CB_CONN_RSP le_cb_conn_rsp; + tL2C_LE_CB_FLOW_CTRL le_cb_flow_ctrl; + tL2CAP_LE_CONN_INFO le_con_info; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; +#endif /* LE_L2CAP_CFC_INCLUDED */ + p_pkt_end = p + pkt_len; STREAM_TO_UINT8 (cmd_code, p); @@ -665,7 +1047,185 @@ void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) case L2CAP_CMD_BLE_UPDATE_RSP: p += 2; break; +#if (defined(LE_L2CAP_CFC_INCLUDED) && (LE_L2CAP_CFC_INCLUDED == TRUE)) + case LE_L2CAP_CMD_CB_CONN_REQ: + STREAM_TO_UINT16 (le_cb_conn_req.le_psm, p); + STREAM_TO_UINT16 (le_cb_conn_req.scid, p); + STREAM_TO_UINT16 (le_cb_conn_req.mtu, p); + STREAM_TO_UINT16 (le_cb_conn_req.mps, p); + STREAM_TO_UINT16 (le_cb_conn_req.init_credits, p); + + if(p_lcb->link_state != LST_CONNECTED) + { + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_or_alloc_dev (p_lcb->remote_bd_addr); + if(p_dev_rec) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Creating thr ACL for LE COC channel"); + btm_acl_created (p_lcb->remote_bd_addr, NULL, p_dev_rec->sec_bd_name, + p_lcb->handle,p_lcb->link_role, BT_TRANSPORT_LE); + l2cble_notify_le_connection(p_lcb->remote_bd_addr); + } + } + /* mtu/mps check */ + if((le_cb_conn_req.mtu < L2CAP_LE_MIN_MTU) || + (le_cb_conn_req.mps > le_cb_conn_req.mtu) || + (le_cb_conn_req.mps > L2CAP_LE_FLOWCTL_MAX_MPS)) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Invalid MTU/MPS size"); + l2cu_le_reject_connection (p_lcb, id, L2CAP_LE_CONN_NO_RESOURCES); + break; + } + if ((p_rcb = l2cu_find_rcb_by_psm (le_cb_conn_req.le_psm, BT_TRANSPORT_LE)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: rcvd le credit based conn req for unknown PSM: %d", + le_cb_conn_req.le_psm); + l2cu_le_reject_connection (p_lcb, id, L2CAP_LE_CONN_NO_PSM); + break; + } + else + { + if (!p_rcb->api.pL2CA_LE_ConnectInd_Cb) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: rcvd conn req for outgoing-only connection PSM: %d", + le_cb_conn_req.le_psm); + l2cu_le_reject_connection (p_lcb, id, L2CAP_LE_CONN_NO_PSM); + break; + } + } + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) + { + L2CAP_TRACE_ERROR ("LE-L2CAP: unable to allocate CCB"); + l2cu_le_reject_connection (p_lcb, id, L2CAP_LE_CONN_NO_RESOURCES); + break; + } + /* make the channel as LE COC channel */ + p_ccb->is_le_coc = TRUE; + p_ccb->remote_id = id; + p_ccb->p_rcb = p_rcb; + p_ccb->remote_cid = le_cb_conn_req.scid; + + /* Save all connection params */ + p_ccb->le_rmt_conn_info.le_psm = le_cb_conn_req.le_psm; + p_ccb->le_rmt_conn_info.le_mtu = le_cb_conn_req.mtu; + p_ccb->le_rmt_conn_info.le_mps = le_cb_conn_req.mps; + p_ccb->le_rmt_conn_info.init_credits = le_cb_conn_req.init_credits; + p_ccb->le_rmt_conn_info.credits = le_cb_conn_req.init_credits; + + l2c_le_csm_execute(p_ccb, L2CEVT_L2CAP_LE_CONNECT_REQ, NULL); + break; + + case LE_L2CAP_CMD_CB_CONN_RSP: /* Got Credit Based L2CAP Connect Rsp from peer device */ + STREAM_TO_UINT16 (le_cb_conn_rsp.dcid, p); + STREAM_TO_UINT16 (le_cb_conn_rsp.mtu, p); + STREAM_TO_UINT16 (le_cb_conn_rsp.mps, p); + STREAM_TO_UINT16 (le_cb_conn_rsp.init_credits, p); + STREAM_TO_UINT16 (le_cb_conn_rsp.result, p); + + if ((p_ccb = l2cu_find_ccb_by_local_id (p_lcb, id)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: no CCB for conn rsp, CID: %d ", le_cb_conn_rsp.dcid); + break; + } + + p_ccb->remote_cid = le_cb_conn_rsp.dcid; + + if (le_cb_conn_rsp.result == L2CAP_LE_CONN_OK) + { + p_ccb->le_rmt_conn_info.le_mtu = le_cb_conn_rsp.mtu; + p_ccb->le_rmt_conn_info.le_mps = le_cb_conn_rsp.mps; + p_ccb->le_rmt_conn_info.init_credits = le_cb_conn_rsp.init_credits; + p_ccb->le_rmt_conn_info.credits = le_cb_conn_rsp.init_credits; + p_ccb->le_rmt_conn_info.result = le_cb_conn_rsp.result; + + /* save psm from local chnl info */ + p_ccb->le_rmt_conn_info.le_psm = p_ccb->le_loc_conn_info.le_psm; + + l2c_le_csm_execute(p_ccb, L2CEVT_L2CAP_LE_CONNECT_RSP, NULL); + + if((p_ccb->le_rmt_conn_info.le_mtu < L2CAP_LE_MIN_MTU) || + (p_ccb->le_rmt_conn_info.le_mps > p_ccb->le_rmt_conn_info.le_mtu) || + (p_ccb->le_rmt_conn_info.le_mps > L2CAP_LE_FLOWCTL_MAX_MPS)) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: Invalid MTU/MPS size, disconnect channel"); + l2c_le_csm_execute(p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + break; + } + } + else + { + p_ccb->le_rmt_conn_info.result = le_cb_conn_rsp.result; + l2c_le_csm_execute(p_ccb, L2CEVT_L2CAP_LE_CONNECT_RSP_NEG, &le_cb_conn_rsp); + } + + break; + + case LE_L2CAP_CMD_CB_FLOW_CTRL: + STREAM_TO_UINT16 (rcid, p); + STREAM_TO_UINT16 (credits, p); + L2CAP_TRACE_DEBUG("LE-L2CAP: rcid 0x%4.4x credits 0x%4.4x", rcid, credits); + + if ((p_ccb = l2cu_find_ccb_by_remote_cid (p_lcb, rcid)) == NULL) + { + L2CAP_TRACE_WARNING ("LE-L2CAP: no CCB for LE_L2CAP_CMD_CB_FLOW_CTRL, CID: %d ", + le_cb_conn_rsp.dcid); + break; + } + max_credits = L2CAP_LE_FLOWCTL_MAX_CREDITS - p_ccb->le_rmt_conn_info.credits; + + if(!credits) + { + /* ignore the packet if credits are 0 */ + break; + } + if (credits > max_credits) + { + L2CAP_TRACE_ERROR("LE-L2CAP: LE credits overflow"); + l2c_le_csm_execute(p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + break; + } + p_ccb->le_rmt_conn_info.credits += credits; + + /* Clearing the channel congestion if it is already congested */ + if (p_ccb->cong_sent) + { + l2cu_check_channel_congestion (p_ccb); + } + + if(p_ccb->le_rmt_conn_info.credits) + { + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } + break; + case L2CAP_CMD_DISC_REQ: + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (rcid, p); + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) + { + if (p_ccb->remote_cid == rcid) + { + p_ccb->remote_id = id; + l2c_le_csm_execute (p_ccb, L2CEVT_L2CAP_DISCONNECT_REQ, &le_con_info); + } + } + else + l2cu_send_peer_disc_rsp (p_lcb, id, lcid, rcid); + + break; + case L2CAP_CMD_DISC_RSP: + STREAM_TO_UINT16 (rcid, p); + STREAM_TO_UINT16 (lcid, p); + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) + { + if ((p_ccb->remote_cid == rcid) && (p_ccb->local_id == id)) + { + l2c_le_csm_execute (p_ccb, L2CEVT_L2CAP_DISCONNECT_RSP, &le_con_info); + } + } + break; +#endif /* LE_L2CAP_CFC_INCLUDED */ default: L2CAP_TRACE_WARNING ("L2CAP - LE - unknown cmd code: %d", cmd_code); l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); |