diff options
author | Andre Eisenbach <andre@broadcom.com> | 2012-02-22 13:18:21 -0800 |
---|---|---|
committer | Matthew Xie <mattx@google.com> | 2012-07-14 11:19:11 -0700 |
commit | e448862a47c08eb23185aaed574b39264f5005fc (patch) | |
tree | 2bc6246e3091315e77224fd798ea2fe8074ef972 /stack/l2cap | |
parent | a2ca4b83ab8bbbfd8d5f6693e927ed4b82094624 (diff) | |
download | external_bluetooth_bluedroid-e448862a47c08eb23185aaed574b39264f5005fc.zip external_bluetooth_bluedroid-e448862a47c08eb23185aaed574b39264f5005fc.tar.gz external_bluetooth_bluedroid-e448862a47c08eb23185aaed574b39264f5005fc.tar.bz2 |
Initial Bluedroid stack commit
Diffstat (limited to 'stack/l2cap')
-rw-r--r-- | stack/l2cap/l2c_api.c | 1800 | ||||
-rw-r--r-- | stack/l2cap/l2c_ble.c | 585 | ||||
-rw-r--r-- | stack/l2cap/l2c_csm.c | 1321 | ||||
-rw-r--r-- | stack/l2cap/l2c_fcr.c | 2697 | ||||
-rw-r--r-- | stack/l2cap/l2c_int.h | 757 | ||||
-rw-r--r-- | stack/l2cap/l2c_link.c | 1703 | ||||
-rw-r--r-- | stack/l2cap/l2c_main.c | 979 | ||||
-rw-r--r-- | stack/l2cap/l2c_ucd.c | 1158 | ||||
-rw-r--r-- | stack/l2cap/l2c_utils.c | 3397 |
9 files changed, 14397 insertions, 0 deletions
diff --git a/stack/l2cap/l2c_api.c b/stack/l2cap/l2c_api.c new file mode 100644 index 0000000..476ded1 --- /dev/null +++ b/stack/l2cap/l2c_api.c @@ -0,0 +1,1800 @@ +/***************************************************************************** +** +** Name: l2c_api.c +** +** Description: This file contains the L2CAP API code +** +** +** +** Copyright (c) 1999-2011, Broadcom Corp., All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "gki.h" +#include "bt_types.h" +#include "hcidefs.h" +#include "hcimsgs.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "btu.h" +#include "btm_api.h" + +/******************************************************************************* +** +** Function L2CA_Register +** +** Description Other layers call this function to register for L2CAP +** services. +** +** Returns PSM to use or zero if error. Typically, the PSM returned +** is the same as was passed in, but for an outgoing-only +** connection to a dynamic PSM, a "virtual" PSM is returned +** and should be used in the calls to L2CA_ConnectReq(), +** L2CA_ErtmConnectReq() and L2CA_Deregister() +** +*******************************************************************************/ +UINT16 L2CA_Register (UINT16 psm, tL2CAP_APPL_INFO *p_cb_info) +{ + tL2C_RCB *p_rcb; + UINT16 vpsm = psm; + + L2CAP_TRACE_API1 ("L2CAP - L2CA_Register() called for PSM: 0x%04x", psm); + + /* Verify that the required callback info has been filled in + ** Note: Connection callbacks are required but not checked + ** for here because it is possible to be only a client + ** or only a server. + */ + if ((!p_cb_info->pL2CA_ConfigCfm_Cb) + || (!p_cb_info->pL2CA_ConfigInd_Cb) + || (!p_cb_info->pL2CA_DataInd_Cb) + || (!p_cb_info->pL2CA_DisconnectInd_Cb)) + { + L2CAP_TRACE_ERROR1 ("L2CAP - no cb registering PSM: 0x%04x", psm); + return (0); + } + + /* Verify PSM is valid */ + if (L2C_INVALID_PSM(psm)) + { + L2CAP_TRACE_ERROR1 ("L2CAP - invalid PSM value, PSM: 0x%04x", psm); + return (0); + } + + /* Check if this is a registration for an outgoing-only connection to */ + /* a dynamic PSM. If so, allocate a "virtual" PSM for the app to use. */ + if ( (psm >= 0x1001) && (p_cb_info->pL2CA_ConnectInd_Cb == NULL) ) + { + for (vpsm = 0x1002; vpsm < 0x8000; vpsm += 2) + { + if ((p_rcb = l2cu_find_rcb_by_psm (vpsm)) == NULL) + break; + } + + L2CAP_TRACE_API2 ("L2CA_Register - Real PSM: 0x%04x Virtual PSM: 0x%04x", psm, vpsm); + } + + /* If registration block already there, just overwrite it */ + if ((p_rcb = l2cu_find_rcb_by_psm (vpsm)) == NULL) + { + if ((p_rcb = l2cu_allocate_rcb (vpsm)) == NULL) + { + L2CAP_TRACE_WARNING2 ("L2CAP - no RCB available, PSM: 0x%04x vPSM: 0x%04x", psm, vpsm); + return (0); + } + } + + p_rcb->api = *p_cb_info; + p_rcb->real_psm = psm; + + return (vpsm); +} + + + +/******************************************************************************* +** +** Function L2CA_Deregister +** +** Description Other layers call this function to de-register for L2CAP +** services. +** +** Returns void +** +*******************************************************************************/ +void L2CA_Deregister (UINT16 psm) +{ + tL2C_RCB *p_rcb; + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb; + int ii; + + L2CAP_TRACE_API1 ("L2CAP - L2CA_Deregister() called for PSM: 0x%04x", psm); + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) != 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_csm_execute (p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + } + } + l2cu_release_rcb (p_rcb); + } + else + { + L2CAP_TRACE_WARNING1 ("L2CAP - PSM: 0x%04x not found for deregistration", psm); + } +} + +/******************************************************************************* +** +** Function L2CA_AllocatePSM +** +** Description Other layers call this function to find an unused PSM for L2CAP +** services. +** +** Returns PSM to use. +** +*******************************************************************************/ +UINT16 L2CA_AllocatePSM(void) +{ + BOOLEAN done = FALSE; + UINT16 psm = l2cb.dyn_psm; + + L2CAP_TRACE_API0( "L2CA_AllocatePSM"); + while (!done) + { + psm += 2; + if (psm > 0xfeff) + { + psm = 0x1001; + } + else if (psm & 0x0100) + { + /* the upper byte must be even */ + psm += 0x0100; + } + + /* if psm is in range of reserved BRCM Aware features */ + if ((BRCM_RESERVED_PSM_START <= psm)&&(psm <= BRCM_RESERVED_PSM_END)) + continue; + + /* make sure the newlly allocated psm is not used right now */ + if ((l2cu_find_rcb_by_psm (psm)) == NULL) + done = TRUE; + } + l2cb.dyn_psm = psm; + + return(psm); +} + +/******************************************************************************* +** +** Function L2CA_ConnectReq +** +** Description Higher layers call this function to create an L2CAP connection. +** Note that the connection is not established at this time, but +** connection establishment gets started. The callback function +** will be invoked when connection establishes or fails. +** +** Returns the CID of the connection, or 0 if it failed to start +** +*******************************************************************************/ +UINT16 L2CA_ConnectReq (UINT16 psm, BD_ADDR p_bd_addr) +{ + return L2CA_ErtmConnectReq (psm, p_bd_addr, NULL); +} + +/******************************************************************************* +** +** Function L2CA_ErtmConnectReq +** +** Description Higher layers call this function to create an L2CAP connection. +** Note that the connection is not established at this time, but +** connection establishment gets started. The callback function +** will be invoked when connection establishes or fails. +** +** Returns the CID of the connection, or 0 if it failed to start +** +*******************************************************************************/ +UINT16 L2CA_ErtmConnectReq (UINT16 psm, BD_ADDR p_bd_addr, tL2CAP_ERTM_INFO *p_ertm_info) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API6 ("L2CA_ErtmConnectReq() PSM: 0x%04x BDA: %08x%04x p_ertm_info: 0x%08x allowed:0x%x preferred:%d", psm, + (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], p_ertm_info, + (p_ertm_info) ? p_ertm_info->allowed_modes : 0, (p_ertm_info) ? p_ertm_info->preferred_mode : 0); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) + { + L2CAP_TRACE_WARNING0 ("L2CAP connect req - BTU not ready"); + return (0); + } + /* Fail if the PSM is not registered */ + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CAP - no RCB for L2CA_conn_req, PSM: 0x%04x", psm); + return (0); + } + + /* First, see if we already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr)) == NULL) + { + /* No link. Get an LCB and start link establishment */ + if ( ((p_lcb = l2cu_allocate_lcb (p_bd_addr, FALSE)) == NULL) + || (l2cu_create_conn(p_lcb) == FALSE) ) + { + L2CAP_TRACE_WARNING2 ("L2CAP - conn not started for PSM: 0x%04x p_lcb: 0x%08x", psm, p_lcb); + return (0); + } + } + + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CAP - no CCB for L2CA_conn_req, PSM: 0x%04x", psm); + return (0); + } + + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + + if (p_ertm_info) + { + p_ccb->ertm_info = *p_ertm_info; + + /* Replace default indicators with the actual default pool */ + if (p_ccb->ertm_info.fcr_rx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.fcr_rx_pool_id = L2CAP_FCR_RX_POOL_ID; + + if (p_ccb->ertm_info.fcr_tx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.fcr_tx_pool_id = L2CAP_FCR_TX_POOL_ID; + + if (p_ccb->ertm_info.user_rx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.user_rx_pool_id = HCI_ACL_POOL_ID; + + if (p_ccb->ertm_info.user_tx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.user_tx_pool_id = HCI_ACL_POOL_ID; + + p_ccb->max_rx_mtu = GKI_get_pool_bufsize (p_ertm_info->user_rx_pool_id) - (L2CAP_MIN_OFFSET + L2CAP_SDU_LEN_OFFSET + L2CAP_FCS_LEN); + } + + /* If link is up, start the L2CAP connection */ + if (p_lcb->link_state == LST_CONNECTED) + { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_REQ, NULL); + } + + /* If link is disconnecting, save link info to retry after disconnect + * Possible Race condition when a reconnect occurs + * on the channel during a disconnect of link. This + * ccb will be automatically retried after link disconnect + * arrives + */ + else if (p_lcb->link_state == LST_DISCONNECTING) + { + L2CAP_TRACE_DEBUG0 ("L2CAP API - link disconnecting: RETRY LATER"); + + /* Save ccb so it can be started after disconnect is finished */ + p_lcb->p_pending_ccb = p_ccb; + } + + L2CAP_TRACE_API2 ("L2CAP - L2CA_conn_req(psm: 0x%04x) returned CID: 0x%04x", psm, p_ccb->local_cid); + + /* Return the local CID as our handle */ + return (p_ccb->local_cid); +} + + +/******************************************************************************* +** +** Function L2CA_ConnectRsp +** +** Description Higher layers call this function to accept an incoming +** L2CAP connection, for which they had gotten an connect +** indication callback. +** +** Returns TRUE for success, FALSE for failure +** +*******************************************************************************/ +BOOLEAN L2CA_ConnectRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, UINT16 result, UINT16 status) +{ + return L2CA_ErtmConnectRsp (p_bd_addr, id, lcid, result, status, NULL); +} + + +/******************************************************************************* +** +** Function L2CA_ErtmConnectRsp +** +** Description Higher layers call this function to accept an incoming +** L2CAP connection, for which they had gotten an connect +** indication callback. +** +** Returns TRUE for success, FALSE for failure +** +*******************************************************************************/ +BOOLEAN L2CA_ErtmConnectRsp (BD_ADDR p_bd_addr, UINT8 id, UINT16 lcid, UINT16 result, + UINT16 status, tL2CAP_ERTM_INFO *p_ertm_info) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API6 ("L2CA_ErtmConnectRsp() CID: 0x%04x Result: %d Status: %d BDA: %08x%04x p_ertm_info:0x%08x", + lcid, result, status, + (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], p_ertm_info); + + /* First, find the link control block */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr)) == NULL) + { + /* No link. Get an LCB and start link establishment */ + L2CAP_TRACE_WARNING0 ("L2CAP - no LCB for L2CA_conn_rsp"); + return (FALSE); + } + + /* Now, find the channel control block */ + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no CCB for L2CA_conn_rsp"); + return (FALSE); + } + + /* The IDs must match */ + if (p_ccb->remote_id != id) + { + L2CAP_TRACE_WARNING2 ("L2CAP - bad id in L2CA_conn_rsp. Exp: %d Got: %d", p_ccb->remote_id, id); + return (FALSE); + } + + if (p_ertm_info) + { + p_ccb->ertm_info = *p_ertm_info; + + /* Replace default indicators with the actual default pool */ + if (p_ccb->ertm_info.fcr_rx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.fcr_rx_pool_id = L2CAP_FCR_RX_POOL_ID; + + if (p_ccb->ertm_info.fcr_tx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.fcr_tx_pool_id = L2CAP_FCR_TX_POOL_ID; + + if (p_ccb->ertm_info.user_rx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.user_rx_pool_id = HCI_ACL_POOL_ID; + + if (p_ccb->ertm_info.user_tx_pool_id == L2CAP_DEFAULT_ERM_POOL_ID) + p_ccb->ertm_info.user_tx_pool_id = HCI_ACL_POOL_ID; + + p_ccb->max_rx_mtu = GKI_get_pool_bufsize (p_ertm_info->user_rx_pool_id) - (L2CAP_MIN_OFFSET + L2CAP_SDU_LEN_OFFSET + L2CAP_FCS_LEN); + } + + if (result == L2CAP_CONN_OK) + { + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP, NULL); + } + else + { + tL2C_CONN_INFO conn_info; + + conn_info.l2cap_result = result; + conn_info.l2cap_status = status; + + if (result == L2CAP_CONN_PENDING) + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP, &conn_info); + else + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONNECT_RSP_NEG, &conn_info); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_ConfigReq +** +** Description Higher layers call this function to send configuration. +** +** Note: The FCR options of p_cfg are not used. +** +** Returns TRUE if configuration sent, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_ConfigReq (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API5 ("L2CA_ConfigReq() CID 0x%04x: fcr_present:%d (mode %d) mtu_present:%d (%d)", + cid, p_cfg->fcr_present, p_cfg->fcr.mode, p_cfg->mtu_present, p_cfg->mtu); + + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_cfg_req, CID: %d", cid); + return (FALSE); + } + + /* We need to have at least one mode type common with the peer */ + if (!l2c_fcr_adj_our_req_options(p_ccb, p_cfg)) + return (FALSE); + + /* Don't adjust FCR options if not used */ + if ((!p_cfg->fcr_present)||(p_cfg->fcr.mode == L2CAP_FCR_BASIC_MODE)) + { + /* FCR and FCS options are not used in basic mode */ + p_cfg->fcs_present = FALSE; + p_cfg->ext_flow_spec_present = FALSE; + + if ( (p_cfg->mtu_present) && (p_cfg->mtu > L2CAP_MTU_SIZE) ) + { + L2CAP_TRACE_WARNING1 ("L2CAP - adjust MTU: %u too large", p_cfg->mtu); + p_cfg->mtu = L2CAP_MTU_SIZE; + } + } + + /* Save the adjusted configuration in case it needs to be used for renegotiation */ + p_ccb->our_cfg = *p_cfg; + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONFIG_REQ, p_cfg); + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_ConfigRsp +** +** Description Higher layers call this function to send a configuration +** response. +** +** Returns TRUE if configuration response sent, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_ConfigRsp (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API6 ("L2CA_ConfigRsp() CID: 0x%04x Result: %d MTU present:%d Flush TO:%d FCR:%d FCS:%d", + cid, p_cfg->result, p_cfg->mtu_present, p_cfg->flush_to_present, p_cfg->fcr_present, p_cfg->fcs_present); + + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_cfg_rsp, CID: %d", cid); + return (FALSE); + } + + if ( (p_cfg->result == L2CAP_CFG_OK) || (p_cfg->result == L2CAP_CFG_PENDING) ) + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONFIG_RSP, p_cfg); + else + { + p_cfg->fcr_present = FALSE; /* FCR options already negotiated before this point */ + + /* Clear out any cached options that are being returned as an error (excluding FCR) */ + if (p_cfg->mtu_present) + p_ccb->peer_cfg.mtu_present = FALSE; + if (p_cfg->flush_to_present) + p_ccb->peer_cfg.flush_to_present = FALSE; + if (p_cfg->qos_present) + p_ccb->peer_cfg.qos_present = FALSE; + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_CONFIG_RSP_NEG, p_cfg); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_DisconnectReq +** +** Description Higher layers call this function to disconnect a channel. +** +** Returns TRUE if disconnect sent, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_DisconnectReq (UINT16 cid) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API1 ("L2CA_DisconnectReq() CID: 0x%04x", 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_WARNING1 ("L2CAP - no CCB for L2CA_disc_req, CID: %d", cid); + return (FALSE); + } + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DISCONNECT_REQ, NULL); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_DisconnectRsp +** +** Description Higher layers call this function to acknowledge the +** disconnection of a channel. +** +** Returns void +** +*******************************************************************************/ +BOOLEAN L2CA_DisconnectRsp (UINT16 cid) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API1 ("L2CA_DisconnectRsp() CID: 0x%04x", 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_WARNING1 ("L2CAP - no CCB for L2CA_disc_rsp, CID: %d", cid); + return (FALSE); + } + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DISCONNECT_RSP, NULL); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_Ping +** +** Description Higher layers call this function to send an echo request. +** +** Returns TRUE if echo request sent, else FALSE. +** +*******************************************************************************/ +BOOLEAN L2CA_Ping (BD_ADDR p_bd_addr, tL2CA_ECHO_RSP_CB *p_callback) +{ + tL2C_LCB *p_lcb; + + L2CAP_TRACE_API6 ("L2CA_Ping() BDA: %02x-%02x-%02x-%02x-%02x-%02x", + p_bd_addr[0], p_bd_addr[1], p_bd_addr[2], p_bd_addr[3], p_bd_addr[4], p_bd_addr[5]); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) + return (FALSE); + + /* First, see if we already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr)) == NULL) + { + /* No link. Get an LCB and start link establishment */ + if ((p_lcb = l2cu_allocate_lcb (p_bd_addr, FALSE)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no LCB for L2CA_ping"); + return (FALSE); + } + if (l2cu_create_conn(p_lcb) == FALSE) + { + return (FALSE); + } + + p_lcb->p_echo_rsp_cb = p_callback; + + return (TRUE); + } + + /* We only allow 1 ping outstanding at a time */ + if (p_lcb->p_echo_rsp_cb != NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - rejected second L2CA_ping"); + return (FALSE); + } + + /* Have a link control block. If link is disconnecting, tell user to retry later */ + if (p_lcb->link_state == LST_DISCONNECTING) + { + L2CAP_TRACE_WARNING0 ("L2CAP - L2CA_ping rejected - link disconnecting"); + return (FALSE); + } + + /* Save address of callback */ + p_lcb->p_echo_rsp_cb = p_callback; + + if (p_lcb->link_state == LST_CONNECTED) + { + l2cu_adj_id(p_lcb, L2CAP_ADJ_BRCM_ID); /* Make sure not using Broadcom ID */ + l2cu_send_peer_echo_req (p_lcb, NULL, 0); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_ECHO_RSP_TOUT); + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_Echo +** +** Description Higher layers call this function to send an echo request +** with application-specific data. +** +** Returns TRUE if echo request sent, else FALSE. +** +*******************************************************************************/ +BOOLEAN L2CA_Echo (BD_ADDR p_bd_addr, BT_HDR *p_data, tL2CA_ECHO_DATA_CB *p_callback) +{ + tL2C_LCB *p_lcb; + UINT8 *pp; + + L2CAP_TRACE_API2 ("L2CA_Echo() BDA: %08X%04X", + ((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]))); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) + return (FALSE); + + if ((memcmp(BT_BD_ANY, p_bd_addr, BD_ADDR_LEN) == 0) && (p_data == NULL)) + { + /* Only register callback without sending message. */ + l2cb.p_echo_data_cb = p_callback; + return TRUE; + } + + /* We assume the upper layer will call this function only when the link is established. */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr)) == NULL) + { + L2CAP_TRACE_ERROR0 ("L2CA_Echo ERROR : link not established"); + return FALSE; + } + + if (p_lcb->link_state != LST_CONNECTED) + { + L2CAP_TRACE_ERROR0 ("L2CA_Echo ERROR : link is not connected"); + return FALSE; + } + + /* Save address of callback */ + l2cb.p_echo_data_cb = p_callback; + + /* Set the pointer to the beginning of the data */ + pp = (UINT8 *)(p_data + 1) + p_data->offset; + l2cu_adj_id(p_lcb, L2CAP_ADJ_BRCM_ID); /* Make sure not using Broadcom ID */ + l2cu_send_peer_echo_req (p_lcb, pp, p_data->len); + + return (TRUE); + +} + +/******************************************************************************* +** +** Function L2CA_SetIdleTimeout +** +** Description Higher layers call this function to set the idle timeout for +** a connection, or for all future connections. The "idle timeout" +** is the amount of time that a connection can remain up with +** no L2CAP channels on it. A timeout of zero means that the +** connection will be torn down immediately when the last channel +** is removed. A timeout of 0xFFFF means no timeout. Values are +** in seconds. +** +** Returns TRUE if command succeeded, FALSE if failed +** +** NOTE This timeout takes effect after at least 1 channel has been +** established and removed. L2CAP maintains its own timer from +** whan a connection is established till the first channel is +** set up. +*******************************************************************************/ +BOOLEAN L2CA_SetIdleTimeout (UINT16 cid, UINT16 timeout, BOOLEAN is_global) +{ + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb; + + if (is_global) + { + l2cb.idle_timeout = timeout; + } + else + { + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_SetIdleTimeout, CID: %d", cid); + return (FALSE); + } + + p_lcb = p_ccb->p_lcb; + + if ((p_lcb) && (p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) + p_lcb->idle_timeout = timeout; + else + return (FALSE); + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SetIdleTimeoutByBdAddr +** +** Description Higher layers call this function to set the idle timeout for +** a connection. The "idle timeout" is the amount of time that +** a connection can remain up with no L2CAP channels on it. +** A timeout of zero means that the connection will be torn +** down immediately when the last channel is removed. +** A timeout of 0xFFFF means no timeout. Values are in seconds. +** A bd_addr is the remote BD address. If bd_addr = BT_BD_ANY, +** then the idle timeouts for all active l2cap links will be +** changed. +** +** Returns TRUE if command succeeded, FALSE if failed +** +** NOTE This timeout applies to all logical channels active on the +** ACL link. +*******************************************************************************/ +BOOLEAN L2CA_SetIdleTimeoutByBdAddr(BD_ADDR bd_addr, UINT16 timeout) +{ + tL2C_LCB *p_lcb; + + if (memcmp (BT_BD_ANY, bd_addr, BD_ADDR_LEN)) + { + p_lcb = l2cu_find_lcb_by_bd_addr( bd_addr ); + if ((p_lcb) && (p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) + p_lcb->idle_timeout = timeout; + else + return (FALSE); + } + else + { + int xx; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) + { + p_lcb->idle_timeout = timeout; + } + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SetTraceLevel +** +** Description This function sets the trace level for L2CAP. If called with +** a value of 0xFF, it simply reads the current trace level. +** +** Returns the new (current) trace level +** +*******************************************************************************/ +UINT8 L2CA_SetTraceLevel (UINT8 new_level) +{ + if (new_level != 0xFF) + l2cb.l2cap_trace_level = new_level; + + return (l2cb.l2cap_trace_level); +} + + +/******************************************************************************* +** +** Function L2CA_SetDesireRole +** +** Description This function sets the desire role for L2CAP. +** If the new role is L2CAP_ROLE_ALLOW_SWITCH, allow switch on +** HciCreateConnection. +** If the new role is L2CAP_ROLE_DISALLOW_SWITCH, do not allow switch on +** HciCreateConnection. +** +** If the new role is a valid role (HCI_ROLE_MASTER or HCI_ROLE_SLAVE), +** the desire role is set to the new value. Otherwise, it is not changed. +** +** Returns the new (current) role +** +*******************************************************************************/ +UINT8 L2CA_SetDesireRole (UINT8 new_role) +{ + L2CAP_TRACE_API2 ("L2CA_SetDesireRole() new:x%x, disallow_switch:%d", + new_role, l2cb.disallow_switch); + + if (L2CAP_ROLE_CHECK_SWITCH != (L2CAP_ROLE_CHECK_SWITCH & new_role)) + { + /* do not process the allow_switch when both bits are set */ + if (new_role & L2CAP_ROLE_ALLOW_SWITCH) + { + l2cb.disallow_switch = FALSE; + } + if (new_role & L2CAP_ROLE_DISALLOW_SWITCH) + { + l2cb.disallow_switch = TRUE; + } + } + + if (new_role == HCI_ROLE_MASTER || new_role == HCI_ROLE_SLAVE) + l2cb.desire_role = new_role; + + return (l2cb.desire_role); +} + +/******************************************************************************* +** +** Function L2CA_LocalLoopbackReq +** +** Description This function sets up a CID for local loopback +** +** Returns CID of 0 if none. +** +*******************************************************************************/ +UINT16 L2CA_LocalLoopbackReq (UINT16 psm, UINT16 handle, BD_ADDR p_bd_addr) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API2 ("L2CA_LocalLoopbackReq() PSM: %d Handle: 0x%04x", psm, handle); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) + { + L2CAP_TRACE_WARNING0 ("L2CAP loop req - BTU not ready"); + return (0); + } + + /* Fail if the PSM is not registered */ + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CAP - no RCB for L2CA_conn_req, PSM: %d", psm); + return (0); + } + + if ((p_lcb = l2cu_allocate_lcb (p_bd_addr, FALSE)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no LCB for L2CA_conn_req"); + return (0); + } + + p_lcb->link_state = LST_CONNECTED; + p_lcb->handle = handle; + + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no CCB for L2CA_conn_req"); + return (0); + } + + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + p_ccb->chnl_state = CST_OPEN; + p_ccb->remote_cid = p_ccb->local_cid; + p_ccb->config_done = CFG_DONE_MASK; + + /* Return the local CID as our handle */ + return (p_ccb->local_cid); +} + +/******************************************************************************* +** +** Function L2CA_SetAclPriority +** +** Description Sets the transmission priority for a channel. +** (For initial implementation only two values are valid. +** L2CAP_PRIORITY_NORMAL and L2CAP_PRIORITY_HIGH). +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetAclPriority (BD_ADDR bd_addr, UINT8 priority) +{ + L2CAP_TRACE_API6 ("L2CA_SetAclPriority() bdaddr: %02x%02x%02x%02x%04x, priority:%d", + bd_addr[0], bd_addr[1], bd_addr[2], + bd_addr[3], (bd_addr[4] << 8) + bd_addr[5], priority); + + return (l2cu_set_acl_priority(bd_addr, priority, FALSE)); +} + +/******************************************************************************* +** +** Function L2CA_FlowControl +** +** Description Higher layers call this function to flow control a channel. +** +** data_enabled - TRUE data flows, FALSE data is stopped +** +** Returns TRUE if valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_FlowControl (UINT16 cid, BOOLEAN data_enabled) +{ + tL2C_CCB *p_ccb; + BOOLEAN on_off = !data_enabled; + + L2CAP_TRACE_API2 ("L2CA_FlowControl(%d) CID: 0x%04x", on_off, 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_WARNING2 ("L2CAP - no CCB for L2CA_FlowControl, CID: 0x%04x data_enabled: %d", cid, data_enabled); + return (FALSE); + } + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) + { + L2CAP_TRACE_EVENT1 ("L2CA_FlowControl() invalid mode:%d", p_ccb->peer_cfg.fcr.mode); + return (FALSE); + } + if (p_ccb->fcrb.local_busy != on_off) + { + p_ccb->fcrb.local_busy = on_off; + + if ( (p_ccb->chnl_state == CST_OPEN) && (!p_ccb->fcrb.wait_ack) ) + { + if (on_off) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + else + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_P_BIT); + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SendTestSFrame +** +** Description Higher layers call this function to send a test S-frame. +** +** Returns TRUE if valid Channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SendTestSFrame (UINT16 cid, UINT8 sup_type, UINT8 back_track) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API3 ("L2CA_SendTestSFrame() CID: 0x%04x Type: 0x%02x back_track: %u", cid, sup_type, back_track); + + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_SendTestSFrame, CID: %d", cid); + return (FALSE); + } + + if ( (p_ccb->chnl_state != CST_OPEN) || (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) ) + return (FALSE); + + p_ccb->fcrb.next_seq_expected -= back_track; + + l2c_fcr_send_S_frame (p_ccb, (UINT16)(sup_type & 3), (UINT16)(sup_type & (L2CAP_FCR_P_BIT | L2CAP_FCR_F_BIT))); + + return (TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_SetTxPriority +** +** Description Sets the transmission priority for a channel. +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetTxPriority (UINT16 cid, tL2CAP_CHNL_PRIORITY priority) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API2 ("L2CA_SetTxPriority() CID: 0x%04x, priority:%d", cid, priority); + + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_SetTxPriority, CID: %d", cid); + return (FALSE); + } + + /* it will update the order of CCB in LCB by priority and update round robin service variables */ + l2cu_change_pri_ccb (p_ccb, priority); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SetChnlDataRate +** +** Description Sets the tx/rx data rate for a channel. +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetChnlDataRate (UINT16 cid, tL2CAP_CHNL_DATA_RATE tx, tL2CAP_CHNL_DATA_RATE rx) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API3 ("L2CA_SetChnlDataRate() CID: 0x%04x, tx:%d, rx:%d", cid, tx, rx); + + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_SetChnlDataRate, CID: %d", cid); + return (FALSE); + } + + p_ccb->tx_data_rate = tx; + p_ccb->rx_data_rate = rx; + + /* Adjust channel buffer allocation */ + l2c_link_adjust_chnl_allocation (); + + return(TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SetFlushTimeout +** +** Description This function set the automatic flush time out in Baseband +** for ACL-U packets. +** BdAddr : the remote BD address of ACL link. If it is BT_DB_ANY +** then the flush time out will be applied to all ACL link. +** FlushTimeout: flush time out in ms +** 0x0000 : No automatic flush +** L2CAP_NO_RETRANSMISSION : No retransmission +** 0x0002 - 0xFFFE : flush time out, if (flush_tout*8)+3/5) +** <= HCI_MAX_AUTO_FLUSH_TOUT (in 625us slot). +** Otherwise, return FALSE. +** L2CAP_NO_AUTOMATIC_FLUSH : No automatic flush +** +** Returns TRUE if command succeeded, FALSE if failed +** +** NOTE This flush timeout applies to all logical channels active on the +** ACL link. +*******************************************************************************/ +BOOLEAN L2CA_SetFlushTimeout (BD_ADDR bd_addr, UINT16 flush_tout) +{ + tL2C_LCB *p_lcb; + UINT16 hci_flush_to; + UINT32 temp; + + /* no automatic flush (infinite timeout) */ + if (flush_tout == 0x0000) + { + hci_flush_to = flush_tout; + flush_tout = L2CAP_NO_AUTOMATIC_FLUSH; + } + /* no retransmission */ + else if (flush_tout == L2CAP_NO_RETRANSMISSION) + { + /* not mandatory range for controller */ + /* Packet is flushed before getting any ACK/NACK */ + /* To do this, flush timeout should be 1 baseband slot */ + hci_flush_to = flush_tout; + } + /* no automatic flush (infinite timeout) */ + else if (flush_tout == L2CAP_NO_AUTOMATIC_FLUSH) + { + hci_flush_to = 0x0000; + } + else + { + /* convert L2CAP flush_to to 0.625 ms units, with round */ + temp = (((UINT32)flush_tout * 8) + 3) / 5; + + /* if L2CAP flush_to within range of HCI, set HCI flush timeout */ + if (temp > HCI_MAX_AUTO_FLUSH_TOUT) + { + L2CAP_TRACE_WARNING1("WARNING L2CA_SetFlushTimeout timeout(0x%x) is out of range", flush_tout); + return FALSE; + } + else + { + hci_flush_to = (UINT16)temp; + } + } + + if (memcmp (BT_BD_ANY, bd_addr, BD_ADDR_LEN)) + { + p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr); + + if ((p_lcb) && (p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) + { + if (p_lcb->link_flush_tout != flush_tout) + { + p_lcb->link_flush_tout = flush_tout; + + L2CAP_TRACE_API4 ("L2CA_SetFlushTimeout 0x%04x ms for bd_addr [...;%02x%02x%02x]", + flush_tout, bd_addr[3], bd_addr[4], bd_addr[5]); + + if (!btsnd_hcic_write_auto_flush_tout (p_lcb->handle, hci_flush_to)) + return (FALSE); + } + } + else + { + L2CAP_TRACE_WARNING3 ("WARNING L2CA_SetFlushTimeout No lcb for bd_addr [...;%02x%02x%02x]", + bd_addr[3], bd_addr[4], bd_addr[5]); + return (FALSE); + } + } + else + { + int xx; + p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTED)) + { + if (p_lcb->link_flush_tout != flush_tout) + { + p_lcb->link_flush_tout = flush_tout; + + L2CAP_TRACE_API4 ("L2CA_SetFlushTimeout 0x%04x ms for bd_addr [...;%02x%02x%02x]", + flush_tout, p_lcb->remote_bd_addr[3], + p_lcb->remote_bd_addr[4], p_lcb->remote_bd_addr[5]); + + if (!btsnd_hcic_write_auto_flush_tout(p_lcb->handle, hci_flush_to)) + return (FALSE); + } + } + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_GetPeerFeatures +** +** Description Get a peers features and fixed channel map +** +** Parameters: BD address of the peer +** Pointers to features and channel mask storage area +** +** Return value: TRUE if peer is connected +** +*******************************************************************************/ +BOOLEAN L2CA_GetPeerFeatures (BD_ADDR bd_addr, UINT32 *p_ext_feat, UINT8 *p_chnl_mask) +{ + tL2C_LCB *p_lcb; + + /* We must already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr)) == NULL) + { + L2CAP_TRACE_WARNING2 ("L2CA_GetPeerFeatures() No BDA: %08x%04x", + (bd_addr[0]<<24)+(bd_addr[1]<<16)+(bd_addr[2]<<8)+bd_addr[3], + (bd_addr[4]<<8)+bd_addr[5]); + return (FALSE); + } + + L2CAP_TRACE_API4 ("L2CA_GetPeerFeatures() BDA: %08x%04x ExtFea: 0x%08x Chnl_Mask[0]: 0x%02x", + (bd_addr[0]<<24)+(bd_addr[1]<<16)+(bd_addr[2]<<8)+bd_addr[3], + (bd_addr[4]<<8)+bd_addr[5], p_lcb->peer_ext_fea, p_lcb->peer_chnl_mask[0]); + + *p_ext_feat = p_lcb->peer_ext_fea; + + memcpy (p_chnl_mask, p_lcb->peer_chnl_mask, L2CAP_FIXED_CHNL_ARRAY_SIZE); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_GetBDAddrbyHandle +** +** Description Get BD address for the given HCI handle +** +** Parameters: HCI handle +** BD address of the peer +** +** Return value: TRUE if found lcb for the given handle, FALSE otherwise +** +*******************************************************************************/ +BOOLEAN L2CA_GetBDAddrbyHandle (UINT16 handle, BD_ADDR bd_addr) +{ + tL2C_LCB *p_lcb = NULL; + BOOLEAN found_dev = FALSE; + + p_lcb = l2cu_find_lcb_by_handle (handle); + if (p_lcb) + { + found_dev = TRUE; + memcpy (bd_addr, p_lcb->remote_bd_addr, BD_ADDR_LEN); + } + + return found_dev; +} + +/******************************************************************************* +** +** Function L2CA_GetChnlFcrMode +** +** Description Get the channel FCR mode +** +** Parameters: Local CID +** +** Return value: Channel mode +** +*******************************************************************************/ +UINT8 L2CA_GetChnlFcrMode (UINT16 lcid) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid (NULL, lcid); + + if (p_ccb) + { + L2CAP_TRACE_API1 ("L2CA_GetChnlFcrMode() returns mode %d", p_ccb->peer_cfg.fcr.mode); + return (p_ccb->peer_cfg.fcr.mode); + } + + L2CAP_TRACE_API0 ("L2CA_GetChnlFcrMode() returns mode L2CAP_FCR_BASIC_MODE"); + return (L2CAP_FCR_BASIC_MODE); +} + +#if (L2CAP_NUM_FIXED_CHNLS > 0) +/******************************************************************************* +** +** Function L2CA_RegisterFixedChannel +** +** Description Register a fixed channel. +** +** Parameters: Fixed Channel # +** Channel Callbacks and config +** +** Return value: - +** +*******************************************************************************/ +BOOLEAN L2CA_RegisterFixedChannel (UINT16 fixed_cid, tL2CAP_FIXED_CHNL_REG *p_freg) +{ + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) ) + { + L2CAP_TRACE_ERROR1 ("L2CA_RegisterFixedChannel() Invalid CID: 0x%04x", fixed_cid); + + return (FALSE); + } + + l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = *p_freg; + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_ConnectFixedChnl +** +** Description Connect an fixed signalling channel to a remote device. +** +** Parameters: Fixed CID +** BD Address of remote +** +** Return value: TRUE if connection started +** +*******************************************************************************/ +BOOLEAN L2CA_ConnectFixedChnl (UINT16 fixed_cid, BD_ADDR rem_bda) +{ + tL2C_LCB *p_lcb; +#if BLE_INCLUDED == TRUE + UINT16 reason; +#endif + + L2CAP_TRACE_API3 ("L2CA_ConnectFixedChnl() CID: 0x%04x BDA: %08x%04x", fixed_cid, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + + /* Check CID is valid and registered */ + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) + || (l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb == NULL) ) + { + L2CAP_TRACE_ERROR1 ("L2CA_ConnectFixedChnl() Invalid CID: 0x%04x", fixed_cid); + return (FALSE); + } + + /* Fail if BT is not yet up */ + if (!BTM_IsDeviceUp()) + { + L2CAP_TRACE_WARNING1 ("L2CA_ConnectFixedChnl(0x%04x) - BTU not ready", fixed_cid); + return (FALSE); + } + + /* If we already have a link to the remote, check if it supports that CID */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) != NULL) + { + if (!(p_lcb->peer_chnl_mask[0] & (1 << fixed_cid))) + { + L2CAP_TRACE_EVENT3 ("L2CA_ConnectFixedChnl() CID: 0x%04x BDA: %08x%04x not supported", fixed_cid, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + return (FALSE); + } + /* Get a CCB and link the lcb to it */ + if (!l2cu_initialize_fixed_ccb (p_lcb, fixed_cid, &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) + { + L2CAP_TRACE_WARNING1 ("L2CA_ConnectFixedChnl(0x%04x) - LCB but no CCB", fixed_cid); + return (FALSE); + } +#if BLE_INCLUDED == TRUE + reason = (p_lcb->is_ble_link) ? 1: 0; + (*l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb)(p_lcb->remote_bd_addr, TRUE, reason); +#else + (*l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedConn_Cb)(p_lcb->remote_bd_addr, TRUE, 0); +#endif + return (TRUE); + } + + /* No link. Get an LCB and start link establishment */ + if ((p_lcb = l2cu_allocate_lcb (rem_bda, FALSE)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CA_ConnectFixedChnl(0x%04x) - no LCB", fixed_cid); + return (FALSE); + } + + /* Get a CCB and link the lcb to it */ + if (!l2cu_initialize_fixed_ccb (p_lcb, fixed_cid, &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) + { + p_lcb->disc_reason = L2CAP_CONN_NO_RESOURCES; + L2CAP_TRACE_WARNING1 ("L2CA_ConnectFixedChnl(0x%04x) - no CCB", fixed_cid); + l2cu_release_lcb (p_lcb); + return (FALSE); + } + + return (l2cu_create_conn(p_lcb)); +} + +/******************************************************************************* +** +** Function L2CA_SendFixedChnlData +** +** Description Write data on a fixed channel. +** +** Parameters: Fixed CID +** BD Address of remote +** Pointer to buffer of type BT_HDR +** +** Return value L2CAP_DW_SUCCESS, if data accepted +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +UINT16 L2CA_SendFixedChnlData (UINT16 fixed_cid, BD_ADDR rem_bda, BT_HDR *p_buf) +{ + tL2C_LCB *p_lcb; + + L2CAP_TRACE_API3 ("L2CA_SendFixedChnlData() CID: 0x%04x BDA: %08x%04x", fixed_cid, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + + /* Check CID is valid and registered */ + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) + || (l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb == NULL) ) + { + L2CAP_TRACE_ERROR1 ("L2CA_SendFixedChnlData() Invalid CID: 0x%04x", fixed_cid); + return (L2CAP_DW_FAILED); + } + + /* Fail if BT is not yet up */ + if (!BTM_IsDeviceUp()) + { + L2CAP_TRACE_WARNING1 ("L2CA_SendFixedChnlData(0x%04x) - BTU not ready", fixed_cid); + return (L2CAP_DW_FAILED); + } + + /* We need to have a link up */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CA_SendFixedChnlData(0x%04x) - no LCB", fixed_cid); + return (L2CAP_DW_FAILED); + } + + if ((p_lcb->peer_chnl_mask[0] & (1 << fixed_cid)) == 0) + { + L2CAP_TRACE_WARNING1 ("L2CA_SendFixedChnlData() - peer does not support fixed chnl: 0x%04x", fixed_cid); + return (L2CAP_DW_FAILED); + } + + p_buf->event = 0; + p_buf->layer_specific = L2CAP_FLUSHABLE_CH_BASED; + + if (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) + { + if (!l2cu_initialize_fixed_ccb (p_lcb, fixed_cid, &l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) + { + L2CAP_TRACE_WARNING1 ("L2CA_SendFixedChnlData() - no CCB for chnl: 0x%4x", fixed_cid); + return (L2CAP_DW_FAILED); + } + } + + l2c_enqueue_peer_data (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL], p_buf); + + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + + /* If there is no dynamic CCB on the link, restart the idle timer each time something is sent */ + if (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED && !p_lcb->ccb_queue.p_first_ccb) + { + l2cu_no_dynamic_ccbs (p_lcb); + } + + return (L2CAP_DW_SUCCESS); +} + +/******************************************************************************* +** +** Function L2CA_RemoveFixedChnl +** +** Description Remove a fixed channel to a remote device. +** +** Parameters: Fixed CID +** BD Address of remote +** Idle timeout to use (or 0xFFFF if don't care) +** +** Return value: TRUE if channel removed +** +*******************************************************************************/ +BOOLEAN L2CA_RemoveFixedChnl (UINT16 fixed_cid, BD_ADDR rem_bda) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + /* Check CID is valid and registered */ + if ( (fixed_cid < L2CAP_FIRST_FIXED_CHNL) || (fixed_cid > L2CAP_LAST_FIXED_CHNL) + || (l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb == NULL) ) + { + L2CAP_TRACE_ERROR1 ("L2CA_RemoveFixedChnl() Invalid CID: 0x%04x", fixed_cid); + return (FALSE); + } + + /* Is a fixed channel connected to the remote BDA ?*/ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda); + if ( ((p_lcb) == NULL) || (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) ) + { + L2CAP_TRACE_WARNING3 ("L2CA_RemoveFixedChnl() CID: 0x%04x BDA: %08x%04x not connected", fixed_cid, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + return (FALSE); + } + + L2CAP_TRACE_API3 ("L2CA_RemoveFixedChnl() CID: 0x%04x BDA: %08x%04x", fixed_cid, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + + /* Release the CCB, starting an inactivity timeout on the LCB if no other CCBs exist */ + p_ccb = p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]; + + p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = NULL; + p_lcb->disc_reason = HCI_ERR_CONN_CAUSE_LOCAL_HOST; + l2cu_release_ccb (p_ccb); + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_SetFixedChannelTout +** +** Description Higher layers call this function to set the idle timeout for +** a fixed channel. The "idle timeout" is the amount of time that +** a connection can remain up with no L2CAP channels on it. +** A timeout of zero means that the connection will be torn +** down immediately when the last channel is removed. +** A timeout of 0xFFFF means no timeout. Values are in seconds. +** A bd_addr is the remote BD address. If bd_addr = BT_BD_ANY, +** then the idle timeouts for all active l2cap links will be +** changed. +** +** Returns TRUE if command succeeded, FALSE if failed +** +*******************************************************************************/ +BOOLEAN L2CA_SetFixedChannelTout (BD_ADDR rem_bda, UINT16 fixed_cid, UINT16 idle_tout) +{ + tL2C_LCB *p_lcb; + + /* Is a fixed channel connected to the remote BDA ?*/ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda); + if ( ((p_lcb) == NULL) || (!p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]) ) + { + L2CAP_TRACE_WARNING3 ("L2CA_SetFixedChannelTout() CID: 0x%04x BDA: %08x%04x not connected", fixed_cid, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + return (FALSE); + } + + p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL]->fixed_chnl_idle_tout = idle_tout; + + if (p_lcb->in_use && p_lcb->link_state == LST_CONNECTED && !p_lcb->ccb_queue.p_first_ccb) + { + /* If there are no dynamic CCBs, (re)start the idle timer in case we changed it */ + l2cu_no_dynamic_ccbs (p_lcb); + } + + return (TRUE); +} + +#endif /* #if (L2CAP_NUM_FIXED_CHNLS > 0) */ + +/******************************************************************************* +** +** Function L2CA_GetCurrentConfig +** +** Description This function returns configurations of L2CAP channel +** pp_our_cfg : pointer of our saved configuration options +** p_our_cfg_bits : valid config in bitmap +** pp_peer_cfg: pointer of peer's saved configuration options +** p_peer_cfg_bits : valid config in bitmap +** +** Returns TRUE if successful +** +*******************************************************************************/ +BOOLEAN L2CA_GetCurrentConfig (UINT16 lcid, + tL2CAP_CFG_INFO **pp_our_cfg, tL2CAP_CH_CFG_BITS *p_our_cfg_bits, + tL2CAP_CFG_INFO **pp_peer_cfg, tL2CAP_CH_CFG_BITS *p_peer_cfg_bits) +{ + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API1 ("L2CA_GetCurrentConfig() CID: 0x%04x", lcid); + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + + if (p_ccb) + { + *pp_our_cfg = &(p_ccb->our_cfg); + + /* convert valid config items into bitmap */ + *p_our_cfg_bits = 0; + if (p_ccb->our_cfg.mtu_present) + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_MTU; + if (p_ccb->our_cfg.qos_present) + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_QOS; + if (p_ccb->our_cfg.flush_to_present) + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_FLUSH_TO; + if (p_ccb->our_cfg.fcr_present) + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_FCR; + if (p_ccb->our_cfg.fcs_present) + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_FCS; + if (p_ccb->our_cfg.ext_flow_spec_present) + *p_our_cfg_bits |= L2CAP_CH_CFG_MASK_EXT_FLOW_SPEC; + + *pp_peer_cfg = &(p_ccb->peer_cfg); + *p_peer_cfg_bits = p_ccb->peer_cfg_bits; + + return TRUE; + } + else + { + L2CAP_TRACE_ERROR1 ("No CCB for CID:0x%04x", lcid); + return FALSE; + } +} + +/******************************************************************************* +** +** Function L2CA_RegForNoCPEvt +** +** Description Register callback for Number of Completed Packets event. +** +** Input Param p_cb - callback for Number of completed packets event +** p_bda - BT address of remote device +** +** Returns TRUE if registered OK, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_RegForNoCPEvt(tL2CA_NOCP_CB *p_cb, BD_ADDR p_bda) +{ + tL2C_LCB *p_lcb; + + /* Find the link that is associated with this remote bdaddr */ + p_lcb = l2cu_find_lcb_by_bd_addr (p_bda); + + /* If no link for this handle, nothing to do. */ + if (!p_lcb) + return FALSE; + + p_lcb->p_nocp_cb = p_cb; + + return TRUE; +} + +/******************************************************************************* +** +** Function L2CA_DataWrite +** +** Description Higher layers call this function to write data. +** +** Returns L2CAP_DW_SUCCESS, if data accepted, else FALSE +** L2CAP_DW_CONGESTED, if data accepted and the channel is congested +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +UINT8 L2CA_DataWrite (UINT16 cid, BT_HDR *p_data) +{ + L2CAP_TRACE_API2 ("L2CA_DataWrite() CID: 0x%04x Len: %d", cid, p_data->len); + return l2c_data_write (cid, p_data, L2CAP_FLUSHABLE_CH_BASED); +} + +/******************************************************************************* +** +** Function L2CA_SetChnlFlushability +** +** Description Higher layers call this function to set a channels +** flushability flags +** +** Returns TRUE if CID found, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_SetChnlFlushability (UINT16 cid, BOOLEAN is_flushable) +{ +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + + tL2C_CCB *p_ccb; + + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_SetChnlFlushability, CID: %d", cid); + return (FALSE); + } + + p_ccb->is_flushable = is_flushable; + + L2CAP_TRACE_API2 ("L2CA_SetChnlFlushability() CID: 0x%04x is_flushable: %d", cid, is_flushable); + +#endif + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_DataWriteEx +** +** Description Higher layers call this function to write data with extended +** flags. +** flags : L2CAP_FLUSHABLE_CH_BASED +** L2CAP_FLUSHABLE_PKT +** L2CAP_NON_FLUSHABLE_PKT +** +** Returns L2CAP_DW_SUCCESS, if data accepted, else FALSE +** L2CAP_DW_CONGESTED, if data accepted and the channel is congested +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +UINT8 L2CA_DataWriteEx (UINT16 cid, BT_HDR *p_data, UINT16 flags) +{ + L2CAP_TRACE_API3 ("L2CA_DataWriteEx() CID: 0x%04x Len: %d Flags:0x%04X", + cid, p_data->len, flags); + return l2c_data_write (cid, p_data, flags); +} + +/******************************************************************************* +** +** Function L2CA_FlushChannel +** +** Description This function flushes none, some or all buffers queued up +** for xmission for a particular CID. If called with +** L2CAP_FLUSH_CHANS_GET (0), it simply returns the number +** of buffers queued for that CID L2CAP_FLUSH_CHANS_ALL (0xffff) +** flushes all buffers. All other values specifies the maximum +** buffers to flush. +** +** Returns Number of buffers left queued for that CID +** +*******************************************************************************/ +UINT16 L2CA_FlushChannel (UINT16 lcid, UINT16 num_to_flush) +{ + tL2C_CCB *p_ccb; + tL2C_LCB *p_lcb; + UINT16 num_left = 0, + num_flushed1 = 0, + num_flushed2 = 0; + BT_HDR *p_buf1, *p_buf; + + p_ccb = l2cu_find_ccb_by_cid(NULL, lcid); + + if ( !p_ccb || ((p_lcb = p_ccb->p_lcb) == NULL) ) + { + L2CAP_TRACE_WARNING1 ("L2CA_FlushChannel() abnormally returning 0 CID: 0x%04x", lcid); + return (0); + } + + if (num_to_flush != L2CAP_FLUSH_CHANS_GET) + { + L2CAP_TRACE_API4 ("L2CA_FlushChannel (FLUSH) CID: 0x%04x NumToFlush: %d QC: %u pFirst: 0x%08x", + lcid, num_to_flush, p_ccb->xmit_hold_q.count, p_ccb->xmit_hold_q.p_first); + } + else + { + L2CAP_TRACE_API1 ("L2CA_FlushChannel (QUERY) CID: 0x%04x", lcid); + } + + /* Cannot flush eRTM buffers once they have a sequence number */ + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) + { +#if L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE + if (num_to_flush != L2CAP_FLUSH_CHANS_GET) + { + /* If the controller supports enhanced flush, flush the data queued at the controller */ + if ( (HCI_NON_FLUSHABLE_PB_SUPPORTED(BTM_ReadLocalFeatures ())) + && (BTM_GetNumScoLinks() == 0) ) + { + if ( l2cb.is_flush_active == FALSE ) + { + l2cb.is_flush_active = TRUE; + + /* The only packet type defined - 0 - Automatically-Flushable Only */ + btsnd_hcic_enhanced_flush (p_lcb->handle, 0); + } + } + } +#endif + + p_buf = (BT_HDR *)p_lcb->link_xmit_data_q.p_first; + + /* First flush the number we are asked to flush */ + while ((p_buf != NULL) && (num_to_flush != 0)) + { + /* Do not flush other CIDs or partial segments */ + if ( (p_buf->layer_specific == 0) && (p_buf->event == lcid) ) + { + p_buf1 = p_buf; + p_buf = (BT_HDR *)GKI_getnext (p_buf); + num_to_flush--; + num_flushed1++; + + GKI_remove_from_queue (&p_lcb->link_xmit_data_q, p_buf1); + GKI_freebuf (p_buf1); + } + else + p_buf = (BT_HDR *)GKI_getnext (p_buf); + } + } + + /* If needed, flush buffers in the CCB xmit hold queue */ + while ( (num_to_flush != 0) && (p_ccb->xmit_hold_q.count != 0) ) + { + p_buf = (BT_HDR *)GKI_dequeue (&p_ccb->xmit_hold_q); + if (p_buf) + GKI_freebuf (p_buf); + num_to_flush--; + num_flushed2++; + } + + /* If app needs to track all packets, call him */ + if ( (p_ccb->p_rcb) && (p_ccb->p_rcb->api.pL2CA_TxComplete_Cb) && (num_flushed2) ) + (*p_ccb->p_rcb->api.pL2CA_TxComplete_Cb)(p_ccb->local_cid, num_flushed2); + + /* Now count how many are left */ + p_buf = (BT_HDR *)p_lcb->link_xmit_data_q.p_first; + + while (p_buf != NULL) + { + if (p_buf->event == lcid) + num_left++; + + p_buf = (BT_HDR *)GKI_getnext (p_buf); + } + + /* Add in the number in the CCB xmit queue */ + num_left += p_ccb->xmit_hold_q.count; + + /* Return the local number of buffers left for the CID */ + L2CAP_TRACE_DEBUG3 ("L2CA_FlushChannel() flushed: %u + %u, num_left: %u", num_flushed1, num_flushed2, num_left); + + /* If we were congested, and now we are not, tell the app */ + l2cu_check_channel_congestion (p_ccb); + + return (num_left); +} + diff --git a/stack/l2cap/l2c_ble.c b/stack/l2cap/l2c_ble.c new file mode 100644 index 0000000..8c69dee --- /dev/null +++ b/stack/l2cap/l2c_ble.c @@ -0,0 +1,585 @@ +/***************************************************************************** +** +** Name: l2c_ble.c +** +** Description: this file contains functions relating to BLE management. +** +** +** Copyright (c) 2009-2012, Broadcom Corp, All Rights Reserved. +******************************************************************************/ + +#include <string.h> +#include "bt_target.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "btu.h" +#include "btm_int.h" +#include "hcimsgs.h" + +#if (BLE_INCLUDED == TRUE) + +/******************************************************************************* +** +** Function L2CA_CancelBleConnectReq +** +** Description Cancel a pending connection attempt to a BLE device. +** +** Parameters: BD Address of remote +** +** Return value: TRUE if connection was cancelled +** +*******************************************************************************/ +BOOLEAN L2CA_CancelBleConnectReq (BD_ADDR rem_bda) +{ + tL2C_LCB *p_lcb; + + /* There can be only one BLE connection request outstanding at a time */ + if (!l2cb.is_ble_connecting) + { + L2CAP_TRACE_WARNING0 ("L2CA_CancelBleConnectReq - no connection pending"); + return(FALSE); + } + + if (memcmp (rem_bda, l2cb.ble_connecting_bda, BD_ADDR_LEN)) + { + L2CAP_TRACE_WARNING4 ("L2CA_CancelBleConnectReq - different BDA Connecting: %08x%04x Cancel: %08x%04x", + (l2cb.ble_connecting_bda[0]<<24)+(l2cb.ble_connecting_bda[1]<<16)+(l2cb.ble_connecting_bda[2]<<8)+l2cb.ble_connecting_bda[3], + (l2cb.ble_connecting_bda[4]<<8)+l2cb.ble_connecting_bda[5], + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + + return(FALSE); + } + + if (btsnd_hcic_ble_create_conn_cancel()) + { + + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) != NULL) + { + p_lcb->disc_reason = L2CAP_CONN_CANCEL; + l2cu_release_lcb (p_lcb); + } + + l2cb.is_ble_connecting = FALSE; + btm_ble_update_bg_state(); + btm_ble_resume_bg_conn(NULL, TRUE); + + return(TRUE); + } + else + return(FALSE); +} + + +/******************************************************************************* +** +** Function L2CA_UpdateBleConnParams +** +** Description Update BLE connection parameters. +** +** Parameters: BD Address of remote +** +** Return value: TRUE if update started +** +*******************************************************************************/ +BOOLEAN L2CA_UpdateBleConnParams (BD_ADDR rem_bda, UINT16 min_int, UINT16 max_int, UINT16 latency, UINT16 timeout) +{ + tL2C_LCB *p_lcb; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda); + + /* If we don't have one, create one and accept the connection. */ + if (!p_lcb) + { + L2CAP_TRACE_WARNING2 ("L2CA_UpdateBleConnParams - unknown BD_ADDR %08x%04x", + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + return(FALSE); + } + + if (!p_lcb->is_ble_link) + { + L2CAP_TRACE_WARNING2 ("L2CA_UpdateBleConnParams - BD_ADDR %08x%04x not LE", + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + return(FALSE); + } + + if (p_lcb->link_role == HCI_ROLE_MASTER) + btsnd_hcic_ble_upd_ll_conn_params (p_lcb->handle, min_int, max_int, latency, timeout, 0, 0); + else + l2cu_send_peer_ble_par_req (p_lcb, min_int, max_int, latency, timeout); + + return(TRUE); +} + + +/******************************************************************************* +** +** Function L2CA_EnableUpdateBleConnParams +** +** Description Enable or disable update based on the request from the peer +** +** Parameters: BD Address of remote +** +** Return value: TRUE if update started +** +*******************************************************************************/ +BOOLEAN L2CA_EnableUpdateBleConnParams (BD_ADDR rem_bda, BOOLEAN enable) +{ + tL2C_LCB *p_lcb; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda); + + /* If we don't have one, create one and accept the connection. */ + if (!p_lcb) + { + L2CAP_TRACE_WARNING2 ("L2CA_EnableUpdateBleConnParams - unknown BD_ADDR %08x%04x", + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5]); + return (FALSE); + } + + L2CAP_TRACE_API4 ("L2CA_EnableUpdateBleConnParams - BD_ADDR %08x%04x enable %d current upd state %d", + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5], enable, p_lcb->upd_disabled); + + if (!p_lcb->is_ble_link || (p_lcb->link_role != HCI_ROLE_MASTER)) + { + L2CAP_TRACE_WARNING3 ("L2CA_EnableUpdateBleConnParams - BD_ADDR %08x%04x not LE or not master %d", + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], (rem_bda[4]<<8)+rem_bda[5], p_lcb->link_role); + return (FALSE); + } + + if (enable) + { + /* application allows to do update, if we were delaying one do it now, otherwise + just mark lcb that updates are enabled */ + if (p_lcb->upd_disabled == UPD_PENDING) + { + btsnd_hcic_ble_upd_ll_conn_params (p_lcb->handle, p_lcb->min_interval, p_lcb->max_interval, + p_lcb->latency, p_lcb->timeout, 0, 0); + p_lcb->upd_disabled = UPD_UPDATED; + } + else + { + p_lcb->upd_disabled = UPD_ENABLED; + } + } + else + { + /* application requests to disable parameters update. If parameters are already updated, lets set them + up to what has been requested during connection establishement */ + if (p_lcb->upd_disabled == UPD_UPDATED) + { + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_or_alloc_dev (rem_bda); + + btsnd_hcic_ble_upd_ll_conn_params (p_lcb->handle, + (UINT16)((p_dev_rec->conn_params.min_conn_int != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.min_conn_int : L2CAP_LE_INT_MIN), + (UINT16)((p_dev_rec->conn_params.max_conn_int != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.max_conn_int : L2CAP_LE_INT_MAX), + (UINT16)((p_dev_rec->conn_params.slave_latency != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.slave_latency : 0), + (UINT16) ((p_dev_rec->conn_params.supervision_tout != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.supervision_tout : L2CAP_LE_TIMEOUT_MAX), + 0, 0); + } + p_lcb->upd_disabled = UPD_DISABLED; + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_GetBleConnRole +** +** Description This function returns the connection role. +** +** Returns link role. +** +*******************************************************************************/ +UINT8 L2CA_GetBleConnRole (BD_ADDR bd_addr) +{ + UINT8 role = HCI_ROLE_UNKNOWN; + + tL2C_LCB *p_lcb; + + if ((p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr)) != NULL) + role = p_lcb->link_role; + + return role; +} +/******************************************************************************* +** +** Function L2CA_GetDisconnectReason +** +** Description This function returns the disconnect reason code. +** +** Returns disconnect reason +** +*******************************************************************************/ +UINT16 L2CA_GetDisconnectReason (BD_ADDR remote_bda) +{ + tL2C_LCB *p_lcb; + UINT16 reason = 0; + + if ((p_lcb = l2cu_find_lcb_by_bd_addr (remote_bda)) != NULL) + reason = p_lcb->disc_reason; + + L2CAP_TRACE_DEBUG1 ("L2CA_GetDisconnectReason=%d ",reason); + + return reason; +} + +/******************************************************************************* +** +** Function l2cble_scanner_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received while we are a scanner (so we are master). +** +** Returns void +** +*******************************************************************************/ +void l2cble_scanner_conn_comp (UINT16 handle, BD_ADDR bda, tBLE_ADDR_TYPE type, UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout) +{ + tL2C_LCB *p_lcb; + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_or_alloc_dev (bda); + + L2CAP_TRACE_DEBUG5 ("l2cble_scanner_conn_comp: HANDLE=%d addr_type=%d conn_interval=%d slave_latency=%d supervision_tout=%d", + handle, type, conn_interval, conn_latency, conn_timeout); + + l2cb.is_ble_connecting = FALSE; + + p_dev_rec->device_type = BT_DEVICE_TYPE_BLE; + p_dev_rec->ble.ble_addr_type = type; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (bda); + + /* If we don't have one, create one. this is auto connection complete. */ + if (!p_lcb) + { + p_lcb = l2cu_allocate_lcb (bda, FALSE); + if (!p_lcb) + { + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); + L2CAP_TRACE_ERROR0 ("l2cble_scanner_conn_comp - failed to allocate LCB"); + return; + } + else + { + if (!l2cu_initialize_fixed_ccb (p_lcb, L2CAP_ATT_CID, &l2cb.fixed_reg[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) + { + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); + L2CAP_TRACE_WARNING0 ("l2cble_scanner_conn_comp - LCB but no CCB"); + return ; + } + } + } + else if (p_lcb->link_state != LST_CONNECTING) + { + L2CAP_TRACE_ERROR1 ("L2CAP got BLE scanner conn_comp in bad state: %d", p_lcb->link_state); + return; + } + btu_stop_timer(&p_lcb->timer_entry); + + /* Save the handle */ + p_lcb->handle = handle; + + /* Connected OK. Change state to connected, we were scanning so we are master */ + p_lcb->link_state = LST_CONNECTED; + p_lcb->link_role = HCI_ROLE_MASTER; + p_lcb->is_ble_link = TRUE; + + /* If there are any preferred connection parameters, set them now */ + if ( (p_dev_rec->conn_params.min_conn_int >= L2CAP_LE_INT_MIN ) && + (p_dev_rec->conn_params.min_conn_int <= L2CAP_LE_INT_MAX ) && + (p_dev_rec->conn_params.max_conn_int >= L2CAP_LE_INT_MIN ) && + (p_dev_rec->conn_params.max_conn_int <= L2CAP_LE_INT_MAX ) && + (p_dev_rec->conn_params.slave_latency <= L2CAP_LE_LATENCY_MAX ) && + (p_dev_rec->conn_params.supervision_tout >= L2CAP_LE_TIMEOUT_MIN) && + (p_dev_rec->conn_params.supervision_tout <= L2CAP_LE_TIMEOUT_MAX) && + ((conn_interval < p_dev_rec->conn_params.min_conn_int && + p_dev_rec->conn_params.min_conn_int != BTM_BLE_CONN_PARAM_UNDEF) || + (conn_interval > p_dev_rec->conn_params.max_conn_int) || + (conn_latency > p_dev_rec->conn_params.slave_latency) || + (conn_timeout > p_dev_rec->conn_params.supervision_tout))) + { + L2CAP_TRACE_ERROR5 ("upd_ll_conn_params: HANDLE=%d min_conn_int=%d max_conn_int=%d slave_latency=%d supervision_tout=%d", + handle, p_dev_rec->conn_params.min_conn_int, p_dev_rec->conn_params.max_conn_int, + p_dev_rec->conn_params.slave_latency, p_dev_rec->conn_params.supervision_tout); + + btsnd_hcic_ble_upd_ll_conn_params (handle, + p_dev_rec->conn_params.min_conn_int, + p_dev_rec->conn_params.max_conn_int, + p_dev_rec->conn_params.slave_latency, + p_dev_rec->conn_params.supervision_tout, + 0, 0); + } + + /* Tell BTM Acl management about the link */ + btm_acl_created (bda, NULL, p_dev_rec->sec_bd_name, handle, p_lcb->link_role, TRUE); + + if (p_lcb->p_echo_rsp_cb) + { + L2CAP_TRACE_ERROR0 ("l2cu_send_peer_echo_req"); + l2cu_send_peer_echo_req (p_lcb, NULL, 0); + } + + p_lcb->peer_chnl_mask[0] = L2CAP_FIXED_CHNL_ATT_BIT | L2CAP_FIXED_CHNL_BLE_SIG_BIT | L2CAP_FIXED_CHNL_SMP_BIT; + + l2cu_process_fixed_chnl_resp (p_lcb); +} + + +/******************************************************************************* +** +** Function l2cble_advertiser_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received while we are an advertiser (so we are slave). +** +** Returns void +** +*******************************************************************************/ +void l2cble_advertiser_conn_comp (UINT16 handle, BD_ADDR bda, tBLE_ADDR_TYPE type, + UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout) +{ + tL2C_LCB *p_lcb; + tBTM_SEC_DEV_REC *p_dev_rec; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (bda); + + /* If we don't have one, create one and accept the connection. */ + if (!p_lcb) + { + p_lcb = l2cu_allocate_lcb (bda, FALSE); + if (!p_lcb) + { + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); + L2CAP_TRACE_ERROR0 ("l2cble_advertiser_conn_comp - failed to allocate LCB"); + return; + } + else + { + if (!l2cu_initialize_fixed_ccb (p_lcb, L2CAP_ATT_CID, &l2cb.fixed_reg[L2CAP_ATT_CID - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) + { + btm_sec_disconnect (handle, HCI_ERR_NO_CONNECTION); + L2CAP_TRACE_WARNING0 ("l2cble_scanner_conn_comp - LCB but no CCB"); + return ; + } + } + } + + /* Save the handle */ + p_lcb->handle = handle; + + /* Connected OK. Change state to connected, we were advertising, so we are slave */ + p_lcb->link_state = LST_CONNECTED; + p_lcb->link_role = HCI_ROLE_SLAVE; + p_lcb->is_ble_link = TRUE; + + /* Tell BTM Acl management about the link */ + p_dev_rec = btm_find_or_alloc_dev (bda); + + p_dev_rec->device_type = BT_DEVICE_TYPE_BLE; + p_dev_rec->ble.ble_addr_type = type; + + btm_acl_created (bda, NULL, p_dev_rec->sec_bd_name, handle, p_lcb->link_role, TRUE); + + p_lcb->peer_chnl_mask[0] = L2CAP_FIXED_CHNL_ATT_BIT | L2CAP_FIXED_CHNL_BLE_SIG_BIT | L2CAP_FIXED_CHNL_SMP_BIT; + + l2cu_process_fixed_chnl_resp (p_lcb); +} + +/******************************************************************************* +** +** Function l2cble_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received. +** +** Returns void +** +*******************************************************************************/ +void l2cble_conn_comp(UINT16 handle, UINT8 role, BD_ADDR bda, tBLE_ADDR_TYPE type, + UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout) +{ + if (role == HCI_ROLE_MASTER) + { + l2cble_scanner_conn_comp(handle, bda, type, conn_interval, conn_latency, conn_timeout); + } + else + { + l2cble_advertiser_conn_comp(handle, bda, type, conn_interval, conn_latency, conn_timeout); + } +} +/******************************************************************************* +** +** Function l2cble_process_sig_cmd +** +** Description This function is called when a signalling packet is received +** on the BLE signalling CID +** +** Returns void +** +*******************************************************************************/ +void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) +{ + UINT8 *p_pkt_end; + UINT8 cmd_code, id; + UINT16 cmd_len, rej_reason; + UINT16 result; + UINT16 min_interval, max_interval, latency, timeout; + + p_pkt_end = p + pkt_len; + + STREAM_TO_UINT8 (cmd_code, p); + STREAM_TO_UINT8 (id, p); + STREAM_TO_UINT16 (cmd_len, p); + + /* Check command length does not exceed packet length */ + if ((p + cmd_len) > p_pkt_end) + { + L2CAP_TRACE_WARNING3 ("L2CAP - LE - format error, pkt_len: %d cmd_len: %d code: %d", pkt_len, cmd_len, cmd_code); + return; + } + + switch (cmd_code) + { + case L2CAP_CMD_REJECT: + case L2CAP_CMD_ECHO_RSP: + case L2CAP_CMD_INFO_RSP: + STREAM_TO_UINT16 (rej_reason, p); + break; + case L2CAP_CMD_ECHO_REQ: + case L2CAP_CMD_INFO_REQ: + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + break; + + case L2CAP_CMD_BLE_UPDATE_REQ: + STREAM_TO_UINT16 (min_interval, p); /* 0x0006 - 0x0C80 */ + STREAM_TO_UINT16 (max_interval, p); /* 0x0006 - 0x0C80 */ + STREAM_TO_UINT16 (latency, p); /* 0x0000 - 0x03E8 */ + STREAM_TO_UINT16 (timeout, p); /* 0x000A - 0x0C80 */ + /* If we are a master, the slave wants to update the parameters */ + if (p_lcb->link_role == HCI_ROLE_MASTER) + { + if (min_interval < L2CAP_LE_INT_MIN || min_interval > L2CAP_LE_INT_MAX || + max_interval < L2CAP_LE_INT_MIN || max_interval > L2CAP_LE_INT_MAX || + latency > L2CAP_LE_LATENCY_MAX || + /*(timeout >= max_interval && latency > (timeout * 10/(max_interval * 1.25) - 1)) ||*/ + timeout < L2CAP_LE_TIMEOUT_MIN || timeout > L2CAP_LE_TIMEOUT_MAX || + max_interval < min_interval) + { + result = L2CAP_CFG_UNACCEPTABLE_PARAMS; + } + else + { + + l2cu_send_peer_ble_par_rsp (p_lcb, L2CAP_CFG_OK, id); + + p_lcb->min_interval = min_interval; + p_lcb->max_interval = max_interval; + p_lcb->latency = latency; + p_lcb->timeout = timeout; + + if (p_lcb->upd_disabled == UPD_ENABLED) + { + btsnd_hcic_ble_upd_ll_conn_params (p_lcb->handle, min_interval, max_interval, + latency, timeout, 0, 0); + p_lcb->upd_disabled = UPD_UPDATED; + } + else + { + L2CAP_TRACE_EVENT0 ("L2CAP - LE - update currently disabled"); + p_lcb->upd_disabled = UPD_PENDING; + } + } + } + else + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + break; + + case L2CAP_CMD_BLE_UPDATE_RSP: + STREAM_TO_UINT16 (result, p); + break; + + default: + L2CAP_TRACE_WARNING1 ("L2CAP - LE - unknown cmd code: %d", cmd_code); + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + return; + } +} + + +/******************************************************************************* +** +** Function l2cble_create_conn +** +** Description This function initiates an acl connection via HCI +** +** Returns TRUE if successful, FALSE if connection not started. +** +*******************************************************************************/ +BOOLEAN l2cble_create_conn (tL2C_LCB *p_lcb) +{ + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_or_alloc_dev (p_lcb->remote_bd_addr); + tBTM_BLE_CB *p_cb = &btm_cb.ble_ctr_cb; + UINT16 scan_int, scan_win; + + /* There can be only one BLE connection request outstanding at a time */ + if (l2cb.is_ble_connecting) + { + L2CAP_TRACE_WARNING0 ("L2CAP - LE - cannot start new connection, already connecting"); + return(FALSE); + } + + p_lcb->link_state = LST_CONNECTING; + l2cb.is_ble_connecting = TRUE; + + memcpy (l2cb.ble_connecting_bda, p_lcb->remote_bd_addr, BD_ADDR_LEN); + btm_ble_suspend_bg_conn(); + + scan_int = (p_cb->scan_int == BTM_BLE_CONN_PARAM_UNDEF) ? L2CAP_LE_INT_MIN : p_cb->scan_int; + scan_win = (p_cb->scan_win == BTM_BLE_CONN_PARAM_UNDEF) ? L2CAP_LE_INT_MIN : p_cb->scan_win; + + if (!btsnd_hcic_ble_create_ll_conn (scan_int,/* UINT16 scan_int */ + scan_win, /* UINT16 scan_win */ + FALSE, /* UINT8 white_list */ + p_lcb->ble_addr_type, /* UINT8 addr_type_peer */ + p_lcb->remote_bd_addr, /* BD_ADDR bda_peer */ + BLE_ADDR_PUBLIC, /* UINT8 addr_type_own */ + (UINT16) ((p_dev_rec->conn_params.min_conn_int != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.min_conn_int : L2CAP_LE_INT_MIN), /* UINT16 conn_int_min */ + (UINT16) ((p_dev_rec->conn_params.max_conn_int != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.max_conn_int : L2CAP_LE_INT_MIN), /* UINT16 conn_int_max */ + (UINT16) ((p_dev_rec->conn_params.slave_latency != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.slave_latency : 0), /* UINT16 conn_latency */ + (UINT16) ((p_dev_rec->conn_params.supervision_tout != BTM_BLE_CONN_PARAM_UNDEF) ? p_dev_rec->conn_params.supervision_tout : L2CAP_LE_TIMEOUT_MAX), /* UINT16 conn_timeout */ + 0, /* UINT16 min_len */ + 0)) /* UINT16 max_len */ + { + /* No buffer for connection request ? */ + l2cb.is_ble_connecting = FALSE; + p_lcb->disc_reason = L2CAP_CONN_NO_RESOURCES; + l2cu_release_lcb (p_lcb); + return(FALSE); + } + else + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_BLE_LINK_CONNECT_TOUT); + + return(TRUE); +} + +/******************************************************************************* +** +** Function l2c_link_processs_ble_num_bufs +** +** Description This function is called when a "controller buffer size" +** event is first received from the controller. It updates +** the L2CAP values. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_processs_ble_num_bufs (UINT16 num_lm_ble_bufs) +{ + l2cb.num_lm_ble_bufs = l2cb.controller_le_xmit_window = num_lm_ble_bufs; +} + +#endif /* (BLE_INCLUDED == TRUE) */ diff --git a/stack/l2cap/l2c_csm.c b/stack/l2cap/l2c_csm.c new file mode 100644 index 0000000..216f45b --- /dev/null +++ b/stack/l2cap/l2c_csm.c @@ -0,0 +1,1321 @@ +/***************************************************************************** +** +** Name: l2c_csm.c +** +** Description: This file contains the L2CAP channel state machine +** +** +** +** Copyright (c) 1999-2011, Broadcom Corp, All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "bt_target.h" +#include "gki.h" +#include "hcidefs.h" +#include "hcimsgs.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "btm_int.h" +#include "btu.h" +#include "hcimsgs.h" + +/********************************************************************************/ +/* L O C A L F U N C T I O N P R O T O T Y P E S */ +/********************************************************************************/ +static void l2c_csm_closed (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_orig_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_term_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2cap_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2ca_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_config (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_open (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2cap_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); +static void l2c_csm_w4_l2ca_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data); + +#if (BT_TRACE_VERBOSE == TRUE) +static char *l2c_csm_get_event_name (UINT16 event); +#endif + +/******************************************************************************* +** +** Function l2c_csm_execute +** +** Description This function executes the state machine. +** +** Returns void +** +*******************************************************************************/ +void l2c_csm_execute (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + switch (p_ccb->chnl_state) + { + case CST_CLOSED: + l2c_csm_closed (p_ccb, event, p_data); + break; + + case CST_ORIG_W4_SEC_COMP: + l2c_csm_orig_w4_sec_comp (p_ccb, event, p_data); + break; + + case CST_TERM_W4_SEC_COMP: + l2c_csm_term_w4_sec_comp (p_ccb, event, p_data); + break; + + case CST_W4_L2CAP_CONNECT_RSP: + l2c_csm_w4_l2cap_connect_rsp (p_ccb, event, p_data); + break; + + case CST_W4_L2CA_CONNECT_RSP: + l2c_csm_w4_l2ca_connect_rsp (p_ccb, event, p_data); + break; + + case CST_CONFIG: + l2c_csm_config (p_ccb, event, p_data); + break; + + case CST_OPEN: + l2c_csm_open (p_ccb, event, p_data); + break; + + case CST_W4_L2CAP_DISCONNECT_RSP: + l2c_csm_w4_l2cap_disconnect_rsp (p_ccb, event, p_data); + break; + + case CST_W4_L2CA_DISCONNECT_RSP: + l2c_csm_w4_l2ca_disconnect_rsp (p_ccb, event, p_data); + break; + + default: + break; + } +} + +/******************************************************************************* +** +** Function l2c_csm_closed +** +** Description This function handles events when the channel is in +** CLOSED state. This state exists only when the link is +** being initially established. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_closed (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2C_CONN_INFO *p_ci = (tL2C_CONN_INFO *)p_data; + UINT16 local_cid = p_ccb->local_cid; + tL2CA_DISCONNECT_IND_CB *disconnect_ind; + tL2CA_CONNECT_CFM_CB *connect_cfm; + + if (p_ccb->p_rcb == NULL) + { +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_ERROR2 ("L2CAP - LCID: 0x%04x st: CLOSED evt: %s p_rcb == NULL", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_ERROR2 ("L2CAP - LCID: 0x%04x st: CLOSED evt: 0x%04x p_rcb == NULL", p_ccb->local_cid, event); +#endif + return; + } + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( local_cid == L2CAP_CONNECTIONLESS_CID ) + { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) + { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + connect_cfm = p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: CLOSED evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: CLOSED evt: %d", event); +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_LP_CONNECT_CFM: /* Link came up */ + p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb); + break; + + case L2CEVT_LP_CONNECT_CFM_NEG: /* Link failed */ + /* Disconnect unless ACL collision and upper layer wants to handle it */ + if (p_ci->status != HCI_ERR_CONNECTION_EXISTS + || !btm_acl_notif_conn_collision(p_ccb->p_lcb->remote_bd_addr)) + { + L2CAP_TRACE_API2 ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, p_ci->status); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, p_ci->status); + } + break; + + case L2CEVT_L2CA_CONNECT_REQ: /* API connect request */ + /* Cancel sniff mode if needed */ +#if BTM_PWR_MGR_INCLUDED == TRUE + { + tBTM_PM_PWR_MD settings; +// btla-specific ++ + memset((void*)&settings, 0, sizeof(settings)); +// btla-specific -- + settings.mode = BTM_PM_MD_ACTIVE; +/* COVERITY +Event uninit_use_in_call: Using uninitialized value "settings" (field "settings".timeout uninitialized) in call to function "BTM_SetPowerMode" [details] +Event uninit_use_in_call: Using uninitialized value "settings.max" in call to function "BTM_SetPowerMode" [details] +Event uninit_use_in_call: Using uninitialized value "settings.min" in call to function "BTM_SetPowerMode" +// FALSE-POSITIVE error from Coverity test-tool. Please do NOT remove following comment. +// coverity[uninit_use_in_call] False-positive: setting the mode to BTM_PM_MD_ACTIVE only uses settings.mode the other data members of tBTM_PM_PWR_MD are ignored +*/ + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } +#else + BTM_CancelSniffMode (p_ccb->p_lcb->remote_bd_addr); +#endif + + /* If sec access does not result in started SEC_COM or COMP_NEG are already processed */ + if (btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb) == BTM_CMD_STARTED) + p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; + break; + + case L2CEVT_SEC_COMP: + p_ccb->chnl_state = CST_W4_L2CAP_CONNECT_RSP; + + /* Wait for the info resp in this state before sending connect req (if needed) */ + if (!p_ccb->p_lcb->w4_info_rsp) + { + /* Need to have at least one compatible channel to continue */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) + { + l2cu_release_ccb (p_ccb); + (*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(local_cid, L2CAP_CONN_NO_LINK); + } + else + { + l2cu_send_peer_connect_req (p_ccb); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + } + } + break; + + case L2CEVT_SEC_COMP_NEG: /* something is really bad with security */ + L2CAP_TRACE_API2 ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, L2CAP_CONN_TIMEOUT); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_SECURITY_BLOCK); + break; + + case L2CEVT_L2CAP_CONNECT_REQ: /* Peer connect request */ + /* stop link timer to avoid race condition between A2MP, Security, and L2CAP */ + btu_stop_timer (&p_ccb->p_lcb->timer_entry); + + /* Cancel sniff mode if needed */ +#if BTM_PWR_MGR_INCLUDED == TRUE + { + tBTM_PM_PWR_MD settings; +// btla-specific ++ + memset((void*)&settings, 0, sizeof(settings)); +// btla-specific -- + settings.mode = BTM_PM_MD_ACTIVE; +/* COVERITY +Event uninit_use_in_call: Using uninitialized value "settings" (field "settings".timeout uninitialized) in call to function "BTM_SetPowerMode" [details] +Event uninit_use_in_call: Using uninitialized value "settings.max" in call to function "BTM_SetPowerMode" [details] +Event uninit_use_in_call: Using uninitialized value "settings.min" in call to function "BTM_SetPowerMode" +// FALSE-POSITIVE error from Coverity test-tool. Please do NOT remove following comment. +// coverity[uninit_use_in_call] False-positive: setting the mode to BTM_PM_MD_ACTIVE only uses settings.mode the other data members of tBTM_PM_PWR_MD are ignored +*/ + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } +#else + BTM_CancelSniffMode (p_ccb->p_lcb->remote_bd_addr); +#endif + + p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; + if (btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, FALSE, &l2c_link_sec_comp, p_ccb) == BTM_CMD_STARTED) + { + /* started the security process, tell the peer to set a longer timer */ + l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_PENDING, 0); + } + break; + + case L2CEVT_TIMEOUT: + L2CAP_TRACE_API2 ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, L2CAP_CONN_TIMEOUT); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_TIMEOUT); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + GKI_freebuf (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_release_ccb (p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_orig_w4_sec_comp +** +** Description This function handles events when the channel is in +** CST_ORIG_W4_SEC_COMP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_orig_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + tL2CA_CONNECT_CFM_CB *connect_cfm = p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: ORIG_W4_SEC_COMP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: ORIG_W4_SEC_COMP evt: %d", event); +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( local_cid == L2CAP_CONNECTIONLESS_CID ) + { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) + { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + case L2CEVT_LP_CONNECT_CFM: /* Link came up */ + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, TRUE, &l2c_link_sec_comp, p_ccb); + break; + + case L2CEVT_SEC_COMP: /* Security completed success */ + /* Wait for the info resp in this state before sending connect req (if needed) */ + p_ccb->chnl_state = CST_W4_L2CAP_CONNECT_RSP; + if (!p_ccb->p_lcb->w4_info_rsp) + { + /* Need to have at least one compatible channel to continue */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) + { + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_NO_LINK); + } + else + { + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + l2cu_send_peer_connect_req (p_ccb); /* Start Connection */ + } + } + break; + + case L2CEVT_SEC_COMP_NEG: + L2CAP_TRACE_API2 ("L2CAP - Calling ConnectCfm_Cb(), CID: 0x%04x Status: %d", p_ccb->local_cid, HCI_ERR_AUTH_FAILURE); + + /* If last channel immediately disconnect the ACL for better security. + Also prevents a race condition between BTM and L2CAP */ + if ( (p_ccb == p_ccb->p_lcb->ccb_queue.p_first_ccb) && (p_ccb == p_ccb->p_lcb->ccb_queue.p_last_ccb) ) + { + p_ccb->p_lcb->idle_timeout = 0; + } + + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, HCI_ERR_AUTH_FAILURE); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + GKI_freebuf (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + /* Tell security manager to abort */ + btm_sec_abort_access_req (p_ccb->p_lcb->remote_bd_addr); + + l2cu_release_ccb (p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_term_w4_sec_comp +** +** Description This function handles events when the channel is in +** CST_TERM_W4_SEC_COMP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_term_w4_sec_comp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: TERM_W4_SEC_COMP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: TERM_W4_SEC_COMP evt: %d", event); +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) + { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) + { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + /* Tell security manager to abort */ + btm_sec_abort_access_req (p_ccb->p_lcb->remote_bd_addr); + + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_SEC_COMP: + p_ccb->chnl_state = CST_W4_L2CA_CONNECT_RSP; + + /* Wait for the info resp in next state before sending connect ind (if needed) */ + if (!p_ccb->p_lcb->w4_info_rsp) + { + /* Don't need to get info from peer or already retrieved so continue */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + L2CAP_TRACE_API1 ("L2CAP - Calling Connect_Ind_Cb(), CID: 0x%04x", p_ccb->local_cid); + + (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) (p_ccb->p_lcb->remote_bd_addr, p_ccb->local_cid, + p_ccb->p_rcb->psm, p_ccb->remote_id); + } + else + { + /* + ** L2CAP Connect Response will be sent out by 3 sec timer expiration + ** because Bluesoleil doesn't respond to L2CAP Information Request. + ** Bluesoleil seems to disconnect ACL link as failure case, because + ** it takes too long (4~7secs) to get response. + ** product version : Bluesoleil 2.1.1.0 EDR Release 060123 + ** stack version : 05.04.11.20060119 + */ + + /* Waiting for the info resp, tell the peer to set a longer timer */ + l2cu_send_peer_connect_rsp(p_ccb, L2CAP_CONN_PENDING, 0); + } + break; + + case L2CEVT_SEC_COMP_NEG: + if (((tL2C_CONN_INFO *)p_data)->status == BTM_DELAY_CHECK) + { + /* start a timer - encryption change not received before L2CAP connect req */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_DELAY_CHECK_SM4); + } + else + { + l2cu_send_peer_connect_rsp (p_ccb, L2CAP_CONN_SECURITY_BLOCK, 0); + l2cu_release_ccb (p_ccb); + } + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + GKI_freebuf (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + + /* Tell security manager to abort */ + btm_sec_abort_access_req (p_ccb->p_lcb->remote_bd_addr); + + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_TIMEOUT: + /* SM4 related. */ + if (!btsnd_hcic_disconnect (p_ccb->p_lcb->handle, HCI_ERR_AUTH_FAILURE)) + { + L2CAP_TRACE_API1 ("L2CAP - Calling btsnd_hcic_disconnect for handle %i failed", p_ccb->p_lcb->handle); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, 1); + } + break; + + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, p_ccb->p_rcb->psm, + p_ccb->p_lcb->handle, FALSE, &l2c_link_sec_comp, p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2cap_connect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CAP_CONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2cap_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2C_CONN_INFO *p_ci = (tL2C_CONN_INFO *)p_data; + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + tL2CA_CONNECT_CFM_CB *connect_cfm = p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: W4_L2CAP_CON_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: W4_L2CAP_CON_RSP evt: %d", event); +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + /* Send disc indication unless peer to peer race condition AND normal disconnect */ + /* *((UINT8 *)p_data) != HCI_ERR_PEER_USER happens when peer device try to disconnect for normal reason */ + p_ccb->chnl_state = CST_CLOSED; + if ((p_ccb->flags & CCB_FLAG_NO_RETRY) || !p_data || (*((UINT8 *)p_data) != HCI_ERR_PEER_USER)) + { + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", + p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + } + p_ccb->flags |= CCB_FLAG_NO_RETRY; + break; + + case L2CEVT_L2CAP_CONNECT_RSP: /* Got peer connect confirm */ + p_ccb->remote_cid = p_ci->remote_cid; + p_ccb->chnl_state = CST_CONFIG; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + L2CAP_TRACE_API1 ("L2CAP - Calling Connect_Cfm_Cb(), CID: 0x%04x, Success", p_ccb->local_cid); + + (*p_ccb->p_rcb->api.pL2CA_ConnectCfm_Cb)(local_cid, L2CAP_CONN_OK); + break; + + case L2CEVT_L2CAP_CONNECT_RSP_PND: /* Got peer connect pending */ + p_ccb->remote_cid = p_ci->remote_cid; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT_EXT); + if (p_ccb->p_rcb->api.pL2CA_ConnectPnd_Cb) + { + L2CAP_TRACE_API1 ("L2CAP - Calling Connect_Pnd_Cb(), CID: 0x%04x", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_ConnectPnd_Cb)(p_ccb->local_cid); + } + break; + + case L2CEVT_L2CAP_CONNECT_RSP_NEG: /* Peer rejected connection */ + L2CAP_TRACE_API2 ("L2CAP - Calling Connect_Cfm_Cb(), CID: 0x%04x, Failure Code: %d", p_ccb->local_cid, p_ci->l2cap_result); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, p_ci->l2cap_result); + break; + + case L2CEVT_TIMEOUT: + L2CAP_TRACE_API1 ("L2CAP - Calling Connect_Cfm_Cb(), CID: 0x%04x, Timeout", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_TIMEOUT); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + /* If we know peer CID from connect pending, we can send disconnect */ + if (p_ccb->remote_cid != 0) + { + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + } + else + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + GKI_freebuf (p_data); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* Need to have at least one compatible channel to continue */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) + { + l2cu_release_ccb (p_ccb); + (*connect_cfm)(local_cid, L2CAP_CONN_NO_LINK); + } + else + { + /* We have feature info, so now send peer connect request */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + l2cu_send_peer_connect_req (p_ccb); /* Start Connection */ + } + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2ca_connect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CA_CONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2ca_connect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2C_CONN_INFO *p_ci; + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: W4_L2CA_CON_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: W4_L2CA_CON_RSP evt: %d", event); +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CA_CONNECT_RSP: + p_ci = (tL2C_CONN_INFO *)p_data; + + /* Result should be OK or PENDING */ + if ((!p_ci) || (p_ci->l2cap_result == L2CAP_CONN_OK)) + { + l2cu_send_peer_connect_rsp (p_ccb, L2CAP_CONN_OK, 0); + p_ccb->chnl_state = CST_CONFIG; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + } + else + { + /* If pending, stay in same state and start extended timer */ + l2cu_send_peer_connect_rsp (p_ccb, p_ci->l2cap_result, p_ci->l2cap_status); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT_EXT); + } + break; + + case L2CEVT_L2CA_CONNECT_RSP_NEG: + p_ci = (tL2C_CONN_INFO *)p_data; + l2cu_send_peer_connect_rsp (p_ccb, p_ci->l2cap_result, p_ci->l2cap_status); + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_TIMEOUT: + l2cu_send_peer_connect_rsp (p_ccb, L2CAP_CONN_NO_PSM, 0); + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + GKI_freebuf (p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* We have feature info, so now give the upper layer connect IND */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CONNECT_TOUT); + L2CAP_TRACE_API1 ("L2CAP - Calling Connect_Ind_Cb(), CID: 0x%04x", p_ccb->local_cid); + + (*p_ccb->p_rcb->api.pL2CA_ConnectInd_Cb) (p_ccb->p_lcb->remote_bd_addr, + p_ccb->local_cid, + p_ccb->p_rcb->psm, + p_ccb->remote_id); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_config +** +** Description This function handles events when the channel is in +** CONFIG state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_config (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CAP_CFG_INFO *p_cfg = (tL2CAP_CFG_INFO *)p_data; + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + UINT16 local_cid = p_ccb->local_cid; + UINT8 cfg_result; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: CONFIG evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: CONFIG evt: %d", event); +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */ + + if ((cfg_result = l2cu_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_OK) + { + L2CAP_TRACE_EVENT2 ("L2CAP - Calling Config_Req_Cb(), CID: 0x%04x, C-bit %d", + p_ccb->local_cid, (p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT)); + (*p_ccb->p_rcb->api.pL2CA_ConfigInd_Cb)(p_ccb->local_cid, p_cfg); + } + else if (cfg_result == L2CAP_PEER_CFG_DISCONNECT) + { + /* Disconnect if channels are incompatible */ + L2CAP_TRACE_EVENT0 ("L2CAP - incompatible configurations disconnect"); + l2cu_disconnect_chnl (p_ccb); + } + else /* Return error to peer so he can renegotiate if possible */ + { + L2CAP_TRACE_EVENT0 ("L2CAP - incompatible configurations trying reconfig"); + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + } + break; + + case L2CEVT_L2CAP_CONFIG_RSP: /* Peer config response */ + l2cu_process_peer_cfg_rsp (p_ccb, p_cfg); + + if (p_cfg->result != L2CAP_CFG_PENDING) + { + /* TBD: When config options grow beyong minimum MTU (48 bytes) + * logic needs to be added to handle responses with + * continuation bit set in flags field. + * 1. Send additional config request out until C-bit is cleared in response + */ + p_ccb->config_done |= OB_CFG_DONE; + + if (p_ccb->config_done & IB_CFG_DONE) + { + /* Verify two sides are in compatible modes before continuing */ + if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) + { + l2cu_send_peer_disc_req (p_ccb); + L2CAP_TRACE_WARNING1 ("L2CAP - Calling Disconnect_Ind_Cb(Incompatible CFG), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + } + + p_ccb->config_done |= RECONFIG_FLAG; + p_ccb->chnl_state = CST_OPEN; + l2c_link_adjust_chnl_allocation (); + btu_stop_timer (&p_ccb->timer_entry); + + /* If using eRTM and waiting for an ACK, restart the ACK timer */ + if (p_ccb->fcrb.wait_ack) + l2c_fcr_start_timer(p_ccb); + + /* + ** check p_ccb->our_cfg.fcr.mon_tout and p_ccb->our_cfg.fcr.rtrans_tout + ** we may set them to zero when sending config request during renegotiation + */ + if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + &&((p_ccb->our_cfg.fcr.mon_tout == 0)||(p_ccb->our_cfg.fcr.rtrans_tout))) + { + l2c_fcr_adj_monitor_retran_timeout (p_ccb); + } + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.connect_tick_count = GKI_get_os_tick_count(); +#endif + /* See if we can forward anything on the hold queue */ + if (p_ccb->xmit_hold_q.count) + { + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } + } + } + + L2CAP_TRACE_API1 ("L2CAP - Calling Config_Rsp_Cb(), CID: 0x%04x", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)(p_ccb->local_cid, p_cfg); + break; + + case L2CEVT_L2CAP_CONFIG_RSP_NEG: /* Peer config error rsp */ + /* Disable the Timer */ + btu_stop_timer (&p_ccb->timer_entry); + + /* If failure was channel mode try to renegotiate */ + if (l2c_fcr_renegotiate_chan(p_ccb, p_cfg) == FALSE) + { + L2CAP_TRACE_API2 ("L2CAP - Calling Config_Rsp_Cb(), CID: 0x%04x, Failure: %d", p_ccb->local_cid, p_cfg->result); + (*p_ccb->p_rcb->api.pL2CA_ConfigCfm_Cb)(p_ccb->local_cid, p_cfg); + } + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x Conf Needed", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, TRUE); + break; + + case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */ + l2cu_process_our_cfg_req (p_ccb, p_cfg); + l2cu_send_peer_config_req (p_ccb, p_cfg); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + break; + + case L2CEVT_L2CA_CONFIG_RSP: /* Upper layer config rsp */ + l2cu_process_our_cfg_rsp (p_ccb, p_cfg); + + /* Not finished if continuation flag is set */ + if ( (p_cfg->flags & L2CAP_CFG_FLAGS_MASK_CONT) || (p_cfg->result == L2CAP_CFG_PENDING) ) + { + /* Send intermediate response; remain in cfg state */ + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + break; + } + + /* Local config done; clear cached configuration in case reconfig takes place later */ + p_ccb->peer_cfg.mtu_present = FALSE; + p_ccb->peer_cfg.flush_to_present = FALSE; + p_ccb->peer_cfg.qos_present = FALSE; + + p_ccb->config_done |= IB_CFG_DONE; + + if (p_ccb->config_done & OB_CFG_DONE) + { + /* Verify two sides are in compatible modes before continuing */ + if (p_ccb->our_cfg.fcr.mode != p_ccb->peer_cfg.fcr.mode) + { + l2cu_send_peer_disc_req (p_ccb); + L2CAP_TRACE_WARNING1 ("L2CAP - Calling Disconnect_Ind_Cb(Incompatible CFG), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + } + + p_ccb->config_done |= RECONFIG_FLAG; + p_ccb->chnl_state = CST_OPEN; + l2c_link_adjust_chnl_allocation (); + btu_stop_timer (&p_ccb->timer_entry); + } + + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + + /* If using eRTM and waiting for an ACK, restart the ACK timer */ + if (p_ccb->fcrb.wait_ack) + l2c_fcr_start_timer(p_ccb); + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.connect_tick_count = GKI_get_os_tick_count(); +#endif + + /* See if we can forward anything on the hold queue */ + if ( (p_ccb->chnl_state == CST_OPEN) && (p_ccb->xmit_hold_q.count) ) + { + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } + break; + + case L2CEVT_L2CA_CONFIG_RSP_NEG: /* Upper layer config reject */ + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + L2CAP_TRACE_API1 ("L2CAP - Calling DataInd_Cb(), CID: 0x%04x", p_ccb->local_cid); +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (p_ccb->local_cid < L2CAP_BASE_APPL_CID) + { + if (l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb) + (*l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb)(p_ccb->p_lcb->remote_bd_addr,(BT_HDR *)p_data); + else + GKI_freebuf (p_data); + + break; + } +#endif + (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR *)p_data); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + if (p_ccb->config_done & OB_CFG_DONE) + l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_data); + else + GKI_freebuf (p_data); + break; + + case L2CEVT_TIMEOUT: + l2cu_send_peer_disc_req (p_ccb); + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_open +** +** Description This function handles events when the channel is in +** OPEN state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_open (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + UINT16 local_cid = p_ccb->local_cid; + tL2CAP_CFG_INFO *p_cfg; + tL2C_CHNL_STATE tempstate; + UINT8 tempcfgdone; + UINT8 cfg_result; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: OPEN evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: OPEN evt: %d", event); +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( local_cid == L2CAP_CONNECTIONLESS_CID ) + { + /* check if this event can be processed by UCD */ + if ( l2c_ucd_process_event (p_ccb, event, p_data) ) + { + /* The event is processed by UCD state machine */ + return; + } + } +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + if (p_ccb->p_rcb) + (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(local_cid, FALSE); + break; + + case L2CEVT_LP_QOS_VIOLATION_IND: /* QOS violation */ + /* Tell upper layer. If service guaranteed, then clear the channel */ + if (p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb) + (*p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb)(p_ccb->p_lcb->remote_bd_addr); + break; + + case L2CEVT_L2CAP_CONFIG_REQ: /* Peer config request */ + p_cfg = (tL2CAP_CFG_INFO *)p_data; + + tempstate = p_ccb->chnl_state; + tempcfgdone = p_ccb->config_done; + p_ccb->chnl_state = CST_CONFIG; + p_ccb->config_done &= ~CFG_DONE_MASK; + + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + + if ((cfg_result = l2cu_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_OK) + { + (*p_ccb->p_rcb->api.pL2CA_ConfigInd_Cb)(p_ccb->local_cid, p_cfg); + } + + /* Error in config parameters: reset state and config flag */ + else if (cfg_result == L2CAP_PEER_CFG_UNACCEPTABLE) + { + btu_stop_timer(&p_ccb->timer_entry); + p_ccb->chnl_state = tempstate; + p_ccb->config_done = tempcfgdone; + l2cu_send_peer_config_rsp (p_ccb, p_cfg); + } + else /* L2CAP_PEER_CFG_DISCONNECT */ + { + /* Disconnect if channels are incompatible + * Note this should not occur if reconfigure + * since this should have never passed original config. + */ + l2cu_disconnect_chnl (p_ccb); + } + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnected request */ +// btla-specific ++ + /* Make sure we are not in sniff mode */ +#if BTM_PWR_MGR_INCLUDED == TRUE + { + tBTM_PM_PWR_MD settings; + settings.mode = BTM_PM_MD_ACTIVE; + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } +#else + BTM_CancelSniffMode (p_ccb->p_lcb->remote_bd_addr); +#endif +// btla-specific -- + + p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x Conf Needed", p_ccb->local_cid); + (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, TRUE); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR *)p_data); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper wants to disconnect */ + /* Make sure we are not in sniff mode */ +#if BTM_PWR_MGR_INCLUDED == TRUE + { + tBTM_PM_PWR_MD settings; + settings.mode = BTM_PM_MD_ACTIVE; + BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings); + } +#else + BTM_CancelSniffMode (p_ccb->p_lcb->remote_bd_addr); +#endif + + l2cu_send_peer_disc_req (p_ccb); + p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP; + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_data); + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + break; + + case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config req */ + p_ccb->chnl_state = CST_CONFIG; + p_ccb->config_done &= ~CFG_DONE_MASK; + l2cu_process_our_cfg_req (p_ccb, (tL2CAP_CFG_INFO *)p_data); + l2cu_send_peer_config_req (p_ccb, (tL2CAP_CFG_INFO *)p_data); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + break; + + case L2CEVT_TIMEOUT: + /* Process the monitor/retransmission time-outs in flow control/retrans mode */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + l2c_fcr_proc_tout (p_ccb); + break; + + case L2CEVT_ACK_TIMEOUT: + l2c_fcr_proc_ack_tout (p_ccb); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2cap_disconnect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CAP_DISCONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2cap_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CA_DISCONNECT_CFM_CB *disconnect_cfm = p_ccb->p_rcb->api.pL2CA_DisconnectCfm_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: W4_L2CAP_DISC_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: W4_L2CAP_DISC_RSP evt: %d", event); +#endif + + switch (event) + { + case L2CEVT_L2CAP_DISCONNECT_RSP: /* Peer disconnect response */ + l2cu_release_ccb (p_ccb); + if (disconnect_cfm) + { + L2CAP_TRACE_API1 ("L2CAP - Calling DisconnectCfm_Cb(), CID: 0x%04x", local_cid); + (*disconnect_cfm)(local_cid, L2CAP_DISC_OK); + } + break; + + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnect request */ + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + l2cu_release_ccb (p_ccb); + if (disconnect_cfm) + { + L2CAP_TRACE_API1 ("L2CAP - Calling DisconnectCfm_Cb(), CID: 0x%04x", local_cid); + (*disconnect_cfm)(local_cid, L2CAP_DISC_OK); + } + break; + + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + case L2CEVT_TIMEOUT: /* Timeout */ + l2cu_release_ccb (p_ccb); + if (disconnect_cfm) + { + L2CAP_TRACE_API1 ("L2CAP - Calling DisconnectCfm_Cb(), CID: 0x%04x", local_cid); + (*disconnect_cfm)(local_cid, L2CAP_DISC_TIMEOUT); + } + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + GKI_freebuf (p_data); + break; + } +} + + +/******************************************************************************* +** +** Function l2c_csm_w4_l2ca_disconnect_rsp +** +** Description This function handles events when the channel is in +** CST_W4_L2CA_DISCONNECT_RSP state. +** +** Returns void +** +*******************************************************************************/ +static void l2c_csm_w4_l2ca_disconnect_rsp (tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + tL2CA_DISCONNECT_IND_CB *disconnect_ind = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + UINT16 local_cid = p_ccb->local_cid; + +#if (BT_TRACE_VERBOSE == TRUE) + L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x st: W4_L2CA_DISC_RSP evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event)); +#else + L2CAP_TRACE_EVENT1 ("L2CAP - st: W4_L2CA_DISC_RSP evt: %d", event); +#endif + + switch (event) + { + case L2CEVT_LP_DISCONNECT_IND: /* Link was disconnected */ + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_TIMEOUT: + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x No Conf Needed", p_ccb->local_cid); + l2cu_release_ccb (p_ccb); + (*disconnect_ind)(local_cid, FALSE); + break; + + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper disconnect request */ + case L2CEVT_L2CA_DISCONNECT_RSP: /* Upper disconnect response */ + l2cu_send_peer_disc_rsp (p_ccb->p_lcb, p_ccb->remote_id, p_ccb->local_cid, p_ccb->remote_cid); + l2cu_release_ccb (p_ccb); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + GKI_freebuf (p_data); + break; + } +} + + +#if (BT_TRACE_VERBOSE == TRUE) +/******************************************************************************* +** +** Function l2c_csm_get_event_name +** +** Description This function returns the event name. +** +** NOTE conditionally compiled to save memory. +** +** Returns pointer to the name +** +*******************************************************************************/ +static char *l2c_csm_get_event_name (UINT16 event) +{ + switch (event) + { + case L2CEVT_LP_CONNECT_CFM: /* Lower layer connect confirm */ + return ("LOWER_LAYER_CONNECT_CFM"); + case L2CEVT_LP_CONNECT_CFM_NEG: /* Lower layer connect confirm (failed) */ + return ("LOWER_LAYER_CONNECT_CFM_NEG"); + case L2CEVT_LP_CONNECT_IND: /* Lower layer connect indication */ + return ("LOWER_LAYER_CONNECT_IND"); + case L2CEVT_LP_DISCONNECT_IND: /* Lower layer disconnect indication */ + return ("LOWER_LAYER_DISCONNECT_IND"); + case L2CEVT_LP_QOS_CFM: /* Lower layer QOS confirmation */ + return ("LOWER_LAYER_QOS_CFM"); + case L2CEVT_LP_QOS_CFM_NEG: /* Lower layer QOS confirmation (failed)*/ + return ("LOWER_LAYER_QOS_CFM_NEG"); + case L2CEVT_LP_QOS_VIOLATION_IND: /* Lower layer QOS violation indication */ + return ("LOWER_LAYER_QOS_VIOLATION_IND"); + + case L2CEVT_SEC_COMP: /* Security cleared successfully */ + return ("SECURITY_COMPLETE"); + case L2CEVT_SEC_COMP_NEG: /* Security procedure failed */ + return ("SECURITY_COMPLETE_NEG"); + + case L2CEVT_L2CAP_CONNECT_REQ: /* Peer connection request */ + return ("PEER_CONNECT_REQ"); + case L2CEVT_L2CAP_CONNECT_RSP: /* Peer connection response */ + return ("PEER_CONNECT_RSP"); + case L2CEVT_L2CAP_CONNECT_RSP_PND: /* Peer connection response pending */ + return ("PEER_CONNECT_RSP_PND"); + case L2CEVT_L2CAP_CONNECT_RSP_NEG: /* Peer connection response (failed) */ + return ("PEER_CONNECT_RSP_NEG"); + case L2CEVT_L2CAP_CONFIG_REQ: /* Peer configuration request */ + return ("PEER_CONFIG_REQ"); + case L2CEVT_L2CAP_CONFIG_RSP: /* Peer configuration response */ + return ("PEER_CONFIG_RSP"); + case L2CEVT_L2CAP_CONFIG_RSP_NEG: /* Peer configuration response (failed) */ + return ("PEER_CONFIG_RSP_NEG"); + case L2CEVT_L2CAP_DISCONNECT_REQ: /* Peer disconnect request */ + return ("PEER_DISCONNECT_REQ"); + case L2CEVT_L2CAP_DISCONNECT_RSP: /* Peer disconnect response */ + return ("PEER_DISCONNECT_RSP"); + case L2CEVT_L2CAP_DATA: /* Peer data */ + return ("PEER_DATA"); + + case L2CEVT_L2CA_CONNECT_REQ: /* Upper layer connect request */ + return ("UPPER_LAYER_CONNECT_REQ"); + case L2CEVT_L2CA_CONNECT_RSP: /* Upper layer connect response */ + return ("UPPER_LAYER_CONNECT_RSP"); + case L2CEVT_L2CA_CONNECT_RSP_NEG: /* Upper layer connect response (failed)*/ + return ("UPPER_LAYER_CONNECT_RSP_NEG"); + case L2CEVT_L2CA_CONFIG_REQ: /* Upper layer config request */ + return ("UPPER_LAYER_CONFIG_REQ"); + case L2CEVT_L2CA_CONFIG_RSP: /* Upper layer config response */ + return ("UPPER_LAYER_CONFIG_RSP"); + case L2CEVT_L2CA_CONFIG_RSP_NEG: /* Upper layer config response (failed) */ + return ("UPPER_LAYER_CONFIG_RSP_NEG"); + case L2CEVT_L2CA_DISCONNECT_REQ: /* Upper layer disconnect request */ + return ("UPPER_LAYER_DISCONNECT_REQ"); + case L2CEVT_L2CA_DISCONNECT_RSP: /* Upper layer disconnect response */ + return ("UPPER_LAYER_DISCONNECT_RSP"); + case L2CEVT_L2CA_DATA_READ: /* Upper layer data read */ + return ("UPPER_LAYER_DATA_READ"); + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data write */ + return ("UPPER_LAYER_DATA_WRITE"); + case L2CEVT_TIMEOUT: /* Timeout */ + return ("TIMEOUT"); + case L2CEVT_SEC_RE_SEND_CMD: + return ("SEC_RE_SEND_CMD"); + case L2CEVT_L2CAP_INFO_RSP: /* Peer information response */ + return ("L2CEVT_L2CAP_INFO_RSP"); + case L2CEVT_ACK_TIMEOUT: + return ("L2CEVT_ACK_TIMEOUT"); + + default: + return ("???? UNKNOWN EVENT"); + } +} +#endif /* (BT_TRACE_VERBOSE == TRUE) */ + + +/******************************************************************************* +** +** Function l2c_enqueue_peer_data +** +** Description Enqueues data destined for the peer in the ccb. Handles +** FCR segmentation and checks for congestion. +** +** Returns void +** +*******************************************************************************/ +void l2c_enqueue_peer_data (tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + UINT8 *p; + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) + { + p_buf->event = 0; + } + else + { + /* Save the channel ID for faster counting */ + p_buf->event = p_ccb->local_cid; + + /* Step back to add the L2CAP header */ + p_buf->offset -= L2CAP_PKT_OVERHEAD; + p_buf->len += L2CAP_PKT_OVERHEAD; + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + /* Now the L2CAP header */ + UINT16_TO_STREAM (p, p_buf->len - L2CAP_PKT_OVERHEAD); + UINT16_TO_STREAM (p, p_ccb->remote_cid); + } + + GKI_enqueue (&p_ccb->xmit_hold_q, p_buf); + + l2cu_check_channel_congestion (p_ccb); + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* if new packet is higher priority than serving ccb and it is not overrun */ + if (( p_ccb->p_lcb->rr_pri > p_ccb->ccb_priority ) + &&( p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota > 0)) + { + /* send out higher priority packet */ + p_ccb->p_lcb->rr_pri = p_ccb->ccb_priority; + } +#endif + + /* if we are doing a round robin scheduling, set the flag */ + if (p_ccb->p_lcb->link_xmit_quota == 0) + l2cb.check_round_robin = TRUE; +} + + diff --git a/stack/l2cap/l2c_fcr.c b/stack/l2cap/l2c_fcr.c new file mode 100644 index 0000000..25a9b3f --- /dev/null +++ b/stack/l2cap/l2c_fcr.c @@ -0,0 +1,2697 @@ +/***************************************************************************** +** +** Name: l2c_fcr.c +** +** Description: This file contains the L2CAP 1.2 Flow Control and +** retransmissions functions +** +** +** +** Copyright (c) 2004-2011, Broadcom Corp., All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "gki.h" +#include "bt_types.h" +#include "hcimsgs.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "l2c_api.h" +#include "btu.h" +#include "btm_api.h" +#include "btm_int.h" + + +/* Flag passed to retransmit_i_frames() when all packets should be retransmitted */ +#define L2C_FCR_RETX_ALL_PKTS 0xFF + +#if BT_TRACE_VERBOSE == TRUE +static char *SAR_types[] = { "Unsegmented", "Start", "End", "Continuation" }; +static char *SUP_types[] = { "RR", "REJ", "RNR", "SREJ" }; +#endif + +/* Look-up table for the CRC calculation */ +static const unsigned short crctab[256] = { + 0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, + 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, + 0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, + 0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, + 0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40, + 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41, + 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641, + 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040, + 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240, + 0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, + 0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, + 0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, + 0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, + 0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40, + 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640, + 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041, + 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240, + 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441, + 0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, + 0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, + 0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, + 0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, + 0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640, + 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041, + 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241, + 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440, + 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40, + 0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, + 0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, + 0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, + 0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, + 0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040, +}; + + +/******************************************************************************* +** Static local functions +*/ +static BOOLEAN process_reqseq (tL2C_CCB *p_ccb, UINT16 ctrl_word); +static void process_s_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word); +static void process_i_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word, BOOLEAN delay_ack); +static BOOLEAN retransmit_i_frames (tL2C_CCB *p_ccb, UINT8 tx_seq); +static void prepare_I_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, BOOLEAN is_retransmission); +static void process_stream_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf); +static BOOLEAN do_sar_reassembly (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word); + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE +static BOOLEAN l2c_corrupt_the_fcr_packet (tL2C_CCB *p_ccb, BT_HDR *p_buf, + BOOLEAN is_rx, UINT16 ctrl_word); +static BOOLEAN l2c_bypass_sframe_packet (tL2C_CCB *p_ccb); +#endif + +#if (L2CAP_ERTM_STATS == TRUE) +static void l2c_fcr_collect_ack_delay (tL2C_CCB *p_ccb, UINT8 num_bufs_acked); +#endif + +/******************************************************************************* +** +** Function l2c_fcr_updcrc +** +** Description This function computes the CRC using the look-up table. +** +** Returns CRC +** +*******************************************************************************/ +unsigned short l2c_fcr_updcrc(unsigned short icrc, unsigned char *icp, int icnt) +{ + register unsigned short crc = icrc; + register unsigned char *cp = icp; + register int cnt = icnt; + + while (cnt--) + { + crc = ((crc >> 8) & 0xff) ^ crctab[(crc & 0xff) ^ *cp++]; + } + + return(crc); +} + + +/******************************************************************************* +** +** Function l2c_fcr_tx_get_fcs +** +** Description This function computes the CRC for a frame to be TXed. +** +** Returns CRC +** +*******************************************************************************/ +UINT16 l2c_fcr_tx_get_fcs (BT_HDR *p_buf) +{ + UINT8 *p = ((UINT8 *) (p_buf + 1)) + p_buf->offset; + + return (l2c_fcr_updcrc (L2CAP_FCR_INIT_CRC, p, p_buf->len)); +} + +/******************************************************************************* +** +** Function l2c_fcr_rx_get_fcs +** +** Description This function computes the CRC for a received frame. +** +** Returns CRC +** +*******************************************************************************/ +UINT16 l2c_fcr_rx_get_fcs (BT_HDR *p_buf) +{ + UINT8 *p = ((UINT8 *) (p_buf + 1)) + p_buf->offset; + + /* offset points past the L2CAP header, but the CRC check includes it */ + p -= L2CAP_PKT_OVERHEAD; + + return (l2c_fcr_updcrc (L2CAP_FCR_INIT_CRC, p, p_buf->len + L2CAP_PKT_OVERHEAD)); +} + +/******************************************************************************* +** +** Function l2c_fcr_start_timer +** +** Description This function starts the (monitor or retransmission) timer. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_start_timer (tL2C_CCB *p_ccb) +{ + UINT32 tout; + + /* The timers which are in milliseconds */ + if (p_ccb->fcrb.wait_ack) + { + tout = (UINT32)p_ccb->our_cfg.fcr.mon_tout; + } + else + { + tout = (UINT32)p_ccb->our_cfg.fcr.rtrans_tout; + } +/* + L2CAP_TRACE_DEBUG4 ("l2c_fcr_start_timer Tout: %u Already Running: %u wait_ack: %u ack_q_count: %u", + tout, p_ccb->timer_entry.in_use, p_ccb->fcrb.wait_ack, p_ccb->fcrb.waiting_for_ack_q.count); +*/ + /* Only start a timer that was not started */ + if (p_ccb->fcrb.mon_retrans_timer.in_use == 0) + btu_start_quick_timer (&p_ccb->fcrb.mon_retrans_timer, BTU_TTYPE_L2CAP_CHNL, tout*QUICK_TIMER_TICKS_PER_SEC/1000); +} + +/******************************************************************************* +** +** Function l2c_fcr_stop_timer +** +** Description This function stops the (monitor or transmission) timer. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_stop_timer (tL2C_CCB *p_ccb) +{ + if (p_ccb->fcrb.mon_retrans_timer.in_use) + { +/* + L2CAP_TRACE_DEBUG2 ("l2c_fcr_stop_timer wait_ack: %u ack_q_count: %u", + p_ccb->fcrb.wait_ack, p_ccb->fcrb.waiting_for_ack_q.count); +*/ + btu_stop_quick_timer (&p_ccb->fcrb.mon_retrans_timer); + } +} + +/******************************************************************************* +** +** Function l2c_fcr_cleanup +** +** Description This function cleans up the variable used for flow-control/retrans. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_cleanup (tL2C_CCB *p_ccb) +{ + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + + l2c_fcr_stop_timer (p_ccb); + + if (p_fcrb->p_rx_sdu) + GKI_freebuf (p_fcrb->p_rx_sdu); + + while (p_fcrb->waiting_for_ack_q.p_first) + GKI_freebuf (GKI_dequeue (&p_fcrb->waiting_for_ack_q)); + + while (p_fcrb->srej_rcv_hold_q.p_first) + GKI_freebuf (GKI_dequeue (&p_fcrb->srej_rcv_hold_q)); + + while (p_fcrb->retrans_q.p_first) + GKI_freebuf (GKI_dequeue (&p_fcrb->retrans_q)); + + btu_stop_quick_timer (&p_fcrb->ack_timer); + btu_stop_quick_timer (&p_ccb->fcrb.mon_retrans_timer); + +#if (L2CAP_ERTM_STATS == TRUE) + if ( (p_ccb->local_cid >= L2CAP_BASE_APPL_CID) && (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) ) + { + UINT32 dur = GKI_get_os_tick_count() - p_ccb->fcrb.connect_tick_count; + char *p_str = (char *)GKI_getbuf(120); + UINT16 i; + UINT32 throughput_avg, ack_delay_avg, ack_q_count_avg; + + dur = GKI_OS_TICKS_TO_MS(dur); + BT_TRACE_2(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "--- L2CAP ERTM Stats for CID: 0x%04x Duration: %08ums", p_ccb->local_cid, dur); + BT_TRACE_4(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "Retransmissions:%08u Times Flow Controlled:%08u Retrans Touts:%08u Ack Touts:%08u", + p_ccb->fcrb.pkts_retransmitted, p_ccb->fcrb.xmit_window_closed, p_ccb->fcrb.retrans_touts, p_ccb->fcrb.xmit_ack_touts); + BT_TRACE_1(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "Times there is less than 2 packets in controller when flow controlled:%08u", p_ccb->fcrb.controller_idle); + BT_TRACE_2(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "max_held_acks:%08u, in_cfg.fcr.tx_win_sz:%08u", p_ccb->fcrb.max_held_acks, p_ccb->peer_cfg.fcr.tx_win_sz ); + if (p_str) + { + sprintf(p_str, "Sent Pkts:%08u Bytes:%10u(%06u/sec) RR:%08u REJ:%08u RNR:%08u SREJ:%08u", + p_ccb->fcrb.ertm_pkt_counts[0], p_ccb->fcrb.ertm_byte_counts[0], + (dur >= 10 ? (p_ccb->fcrb.ertm_byte_counts[0] * 100) / (dur / 10) : 0), + p_ccb->fcrb.s_frames_sent[0], p_ccb->fcrb.s_frames_sent[1], p_ccb->fcrb.s_frames_sent[2], p_ccb->fcrb.s_frames_sent[3]); + + BT_TRACE_1(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", p_str); + + sprintf(p_str, "Rcvd Pkts:%08u Bytes:%10u(%06u/sec) RR:%08u REJ:%08u RNR:%08u SREJ:%08u", + p_ccb->fcrb.ertm_pkt_counts[1], p_ccb->fcrb.ertm_byte_counts[1], + (dur >= 10 ? (p_ccb->fcrb.ertm_byte_counts[1] * 100) / (dur / 10) : 0), + p_ccb->fcrb.s_frames_rcvd[0], p_ccb->fcrb.s_frames_rcvd[1], p_ccb->fcrb.s_frames_rcvd[2], p_ccb->fcrb.s_frames_rcvd[3]); + + BT_TRACE_1(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", p_str); + + throughput_avg = 0; + ack_delay_avg = 0; + ack_q_count_avg = 0; + + for (i = 0; i < L2CAP_ERTM_STATS_NUM_AVG; i++ ) + { + if (i == p_ccb->fcrb.ack_delay_avg_index ) + { + BT_TRACE_1(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "[%02u] collecting data ...", i ); + continue; + } + + sprintf(p_str, "[%02u] throughput: %5u, ack_delay avg:%3u, min:%3u, max:%3u, ack_q_count avg:%3u, min:%3u, max:%3u", + i, p_ccb->fcrb.throughput[i], + p_ccb->fcrb.ack_delay_avg[i], p_ccb->fcrb.ack_delay_min[i], p_ccb->fcrb.ack_delay_max[i], + p_ccb->fcrb.ack_q_count_avg[i], p_ccb->fcrb.ack_q_count_min[i], p_ccb->fcrb.ack_q_count_max[i] ); + + BT_TRACE_1(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", p_str); + + throughput_avg += p_ccb->fcrb.throughput[i]; + ack_delay_avg += p_ccb->fcrb.ack_delay_avg[i]; + ack_q_count_avg += p_ccb->fcrb.ack_q_count_avg[i]; + } + + throughput_avg /= (L2CAP_ERTM_STATS_NUM_AVG - 1); + ack_delay_avg /= (L2CAP_ERTM_STATS_NUM_AVG - 1); + ack_q_count_avg /= (L2CAP_ERTM_STATS_NUM_AVG - 1); + + BT_TRACE_3(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "throughput_avg: %8u (kbytes/sec), ack_delay_avg: %8u ms, ack_q_count_avg: %8u", + throughput_avg, ack_delay_avg, ack_q_count_avg ); + + GKI_freebuf(p_str); + } + + BT_TRACE_0(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, + "---"); + } +#endif + + memset (p_fcrb, 0, sizeof (tL2C_FCRB)); +} + +/******************************************************************************* +** +** Function l2c_fcr_clone_buf +** +** Description This function allocates and copies requested part of a buffer +** at a new-offset. +** +** Returns pointer to new buffer +** +*******************************************************************************/ +BT_HDR *l2c_fcr_clone_buf (BT_HDR *p_buf, UINT16 new_offset, UINT16 no_of_bytes, UINT8 pool) +{ + BT_HDR *p_buf2; + + /* If using the common pool, should be at least 10% free. */ + if ( (pool == HCI_ACL_POOL_ID) && (GKI_poolutilization (pool) > 90) ) + { + L2CAP_TRACE_ERROR1 ("L2CAP - failed to clone buffer on HCI_ACL_POOL_ID Utilization: %u", GKI_poolutilization(pool)); + return (NULL); + } + + if ((p_buf2 = (BT_HDR *)GKI_getpoolbuf(pool)) != NULL) + { + UINT16 pool_buf_size = GKI_get_pool_bufsize (pool); + + /* Make sure buffer fits into buffer pool */ + if ((no_of_bytes + sizeof(BT_HDR) + new_offset) > pool_buf_size) + { + L2CAP_TRACE_ERROR5("##### l2c_fcr_clone_buf (NumBytes %d) -> Exceeds poolsize %d [bytes %d + BT_HDR %d + offset %d]", + (no_of_bytes + sizeof(BT_HDR) + new_offset), + pool_buf_size, no_of_bytes, sizeof(BT_HDR), + new_offset); + + GKI_freebuf(p_buf2); + return (NULL); + } + + p_buf2->offset = new_offset; + p_buf2->len = no_of_bytes; + + memcpy (((UINT8 *)(p_buf2 + 1)) + p_buf2->offset, + ((UINT8 *)(p_buf + 1)) + p_buf->offset, + no_of_bytes); + } + else + { + L2CAP_TRACE_ERROR2 ("L2CAP - failed to clone buffer, Pool: %u Count: %u", pool, GKI_poolfreecount(pool)); + } + + return (p_buf2); +} + +/******************************************************************************* +** +** Function l2c_fcr_is_flow_controlled +** +** Description This function checks if the CCB is flow controlled by peer. +** +** Returns The control word +** +*******************************************************************************/ +BOOLEAN l2c_fcr_is_flow_controlled (tL2C_CCB *p_ccb) +{ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + { + /* Check if remote side flowed us off or the transmit window is full */ + if ( (p_ccb->fcrb.remote_busy == TRUE) + || (p_ccb->fcrb.waiting_for_ack_q.count >= p_ccb->peer_cfg.fcr.tx_win_sz) ) + { +#if (L2CAP_ERTM_STATS == TRUE) + if (p_ccb->xmit_hold_q.count != 0) + { + p_ccb->fcrb.xmit_window_closed++; + + if ((p_ccb->p_lcb->sent_not_acked < 2)&&(l2cb.controller_xmit_window > 0)) + p_ccb->fcrb.controller_idle++; + } +#endif + return (TRUE); + } + } + return (FALSE); +} + +/******************************************************************************* +** +** Function prepare_I_frame +** +** Description This function sets the FCR variables in an I-frame that is +** about to be sent to HCI for transmission. This may be the +** first time the I-frame is sent, or a retransmission +** +** Returns - +** +*******************************************************************************/ +static void prepare_I_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, BOOLEAN is_retransmission) +{ + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT8 *p; + UINT16 fcs; + UINT16 ctrl_word; + BOOLEAN set_f_bit = p_fcrb->send_f_rsp; + + p_fcrb->send_f_rsp = FALSE; + + if (is_retransmission) + { + /* Get the old control word and clear out the old req_seq and F bits */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + L2CAP_PKT_OVERHEAD; + + STREAM_TO_UINT16 (ctrl_word, p); + + ctrl_word &= ~(L2CAP_FCR_REQ_SEQ_BITS + L2CAP_FCR_F_BIT); + } + else + { + ctrl_word = p_buf->layer_specific & L2CAP_FCR_SEG_BITS; /* SAR bits */ + ctrl_word |= (p_fcrb->next_tx_seq << L2CAP_FCR_TX_SEQ_BITS_SHIFT); /* Tx Seq */ + + p_fcrb->next_tx_seq = (p_fcrb->next_tx_seq + 1) & L2CAP_FCR_SEQ_MODULO; + } + + /* Set the F-bit and reqseq only if using re-transmission mode */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + { + if (set_f_bit) + ctrl_word |= L2CAP_FCR_F_BIT; + + ctrl_word |= (p_fcrb->next_seq_expected) << L2CAP_FCR_REQ_SEQ_BITS_SHIFT; + + p_fcrb->last_ack_sent = p_ccb->fcrb.next_seq_expected; + + if (p_ccb->fcrb.ack_timer.in_use) + btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); + } + + /* Set the control word */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + L2CAP_PKT_OVERHEAD; + + UINT16_TO_STREAM (p, ctrl_word); + + /* Compute the FCS and add to the end of the buffer if not bypassed */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) + { + /* length field in l2cap header has to include FCS length */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset; + UINT16_TO_STREAM (p, p_buf->len + L2CAP_FCS_LEN - L2CAP_PKT_OVERHEAD); + + /* Calculate the FCS */ + fcs = l2c_fcr_tx_get_fcs(p_buf); + + /* Point to the end of the buffer and put the FCS there */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + p_buf->len; + + UINT16_TO_STREAM (p, fcs); + + p_buf->len += L2CAP_FCS_LEN; + } + +#if BT_TRACE_VERBOSE == TRUE + if (is_retransmission) + { + L2CAP_TRACE_EVENT6 ("L2CAP eRTM ReTx I-frame CID: 0x%04x Len: %u SAR: %s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } + else + { + L2CAP_TRACE_EVENT6 ("L2CAP eRTM Tx I-frame CID: 0x%04x Len: %u SAR: %-12s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } +#endif + + /* Start the retransmission timer if not already running */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + l2c_fcr_start_timer (p_ccb); +} + +/******************************************************************************* +** +** Function l2c_fcr_send_S_frame +** +** Description This function formats and sends an S-frame for transmission. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_send_S_frame (tL2C_CCB *p_ccb, UINT16 function_code, UINT16 pf_bit) +{ + BT_HDR *p_buf; + UINT8 *p; + UINT16 ctrl_word; + UINT16 fcs; + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE + /* Only used for conformance testing */ + if (l2c_bypass_sframe_packet (p_ccb)) + return; +#endif + + if ((!p_ccb->in_use) || (p_ccb->chnl_state != CST_OPEN)) + return; + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.s_frames_sent[function_code]++; +#endif + + if (pf_bit == L2CAP_FCR_P_BIT) + { + p_ccb->fcrb.wait_ack = TRUE; + + l2c_fcr_stop_timer (p_ccb); /* Restart the monitor timer */ + l2c_fcr_start_timer (p_ccb); + } + + /* Create the control word to use */ + ctrl_word = (function_code << L2CAP_FCR_SUP_SHIFT) | L2CAP_FCR_S_FRAME_BIT; + ctrl_word |= (p_ccb->fcrb.next_seq_expected << L2CAP_FCR_REQ_SEQ_BITS_SHIFT); + ctrl_word |= pf_bit; + + if ((p_buf = (BT_HDR *)GKI_getpoolbuf (L2CAP_CMD_POOL_ID)) != NULL) + { + p_buf->offset = HCI_DATA_PREAMBLE_SIZE; + p_buf->len = L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD; + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + /* Put in the L2CAP header */ + UINT16_TO_STREAM (p, L2CAP_FCR_OVERHEAD + L2CAP_FCS_LEN); + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, ctrl_word); + + /* Compute the FCS and add to the end of the buffer if not bypassed */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) + { + fcs = l2c_fcr_tx_get_fcs (p_buf); + + UINT16_TO_STREAM (p, fcs); + p_buf->len += L2CAP_FCS_LEN; + } + else /* rewrite the length without FCS length */ + { + p -= 6; + UINT16_TO_STREAM (p, L2CAP_FCR_OVERHEAD); + } + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE + /* Get out if packet was dropped */ + if (l2c_corrupt_the_fcr_packet (p_ccb, p_buf, FALSE, ctrl_word)) + return; +#endif + /* Now, the HCI transport header */ + p_buf->layer_specific = L2CAP_NON_FLUSHABLE_PKT; + l2cu_set_acl_hci_header (p_buf, p_ccb); + +#if BT_TRACE_VERBOSE == TRUE + if ((((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 1) + || (((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 3)) + { + L2CAP_TRACE_WARNING6 ("L2CAP eRTM Tx S-frame CID: 0x%04x ctrlword: 0x%04x Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, ctrl_word, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + L2CAP_TRACE_WARNING1 (" Buf Len: %u", p_buf->len); + } + else + { + L2CAP_TRACE_EVENT6 ("L2CAP eRTM Tx S-frame CID: 0x%04x ctrlword: 0x%04x Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, ctrl_word, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + L2CAP_TRACE_EVENT1 (" Buf Len: %u", p_buf->len); + } +#endif /* BT_TRACE_VERBOSE */ + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); + + p_ccb->fcrb.last_ack_sent = p_ccb->fcrb.next_seq_expected; + + if (p_ccb->fcrb.ack_timer.in_use) + btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); + } + else + { + L2CAP_TRACE_ERROR2 ("l2c_fcr_send_S_frame(No Resources) cid 0x%04x, Type: 0x%4x", + p_ccb->local_cid, function_code); + } +} + + +/******************************************************************************* +** +** Function l2c_fcr_proc_pdu +** +** Description This function is the entry point for processing of a +** received PDU when in flow control and/or retransmission modes. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_proc_pdu (tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + UINT8 *p; + UINT16 fcs; + UINT16 min_pdu_len; + UINT16 ctrl_word; + + /* Check the length */ + min_pdu_len = (p_ccb->bypass_fcs == L2CAP_BYPASS_FCS) ? + (UINT16)L2CAP_FCR_OVERHEAD : (UINT16)(L2CAP_FCS_LEN + L2CAP_FCR_OVERHEAD); + + if (p_buf->len < min_pdu_len) + { + L2CAP_TRACE_WARNING2 ("Rx L2CAP PDU: CID: 0x%04x Len too short: %u", p_ccb->local_cid, p_buf->len); + GKI_freebuf (p_buf); + return; + } + + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_STREAM_MODE) + { + process_stream_frame (p_ccb, p_buf); + return; + } + +#if BT_TRACE_VERBOSE == TRUE + /* Get the control word */ + p = ((UINT8 *)(p_buf+1)) + p_buf->offset; + STREAM_TO_UINT16 (ctrl_word, p); + + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + { + if ((((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 1) + || (((ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT) == 3)) + { + /* REJ or SREJ */ + L2CAP_TRACE_WARNING6 ("L2CAP eRTM Rx S-frame: cid: 0x%04x Len: %u Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, p_buf->len, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } + else + { + L2CAP_TRACE_EVENT6 ("L2CAP eRTM Rx S-frame: cid: 0x%04x Len: %u Type: %s ReqSeq: %u P: %u F: %u", + p_ccb->local_cid, p_buf->len, + SUP_types[(ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT], + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_P_BIT) >> L2CAP_FCR_P_BIT_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } + } + else + { + L2CAP_TRACE_EVENT6 ("L2CAP eRTM Rx I-frame: cid: 0x%04x Len: %u SAR: %-12s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); + } + + L2CAP_TRACE_EVENT6 (" eRTM Rx Nxt_tx_seq %u, Lst_rx_ack %u, Nxt_seq_exp %u, Lst_ack_snt %u, wt_q.cnt %u, tries %u", + p_ccb->fcrb.next_tx_seq, p_ccb->fcrb.last_rx_ack, p_ccb->fcrb.next_seq_expected, + p_ccb->fcrb.last_ack_sent, p_ccb->fcrb.waiting_for_ack_q.count, p_ccb->fcrb.num_tries); + +#endif /* BT_TRACE_VERBOSE */ + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE + p = ((UINT8 *)(p_buf+1)) + p_buf->offset; + STREAM_TO_UINT16 (ctrl_word, p); + + /* Get out if packet was dropped */ + if (l2c_corrupt_the_fcr_packet (p_ccb, p_buf, TRUE, ctrl_word)) + return; +#endif /* L2CAP_CORRUPT_ERTM_PKTS */ + + /* Verify FCS if using */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) + { + p = ((UINT8 *)(p_buf+1)) + p_buf->offset + p_buf->len - L2CAP_FCS_LEN; + + /* Extract and drop the FCS from the packet */ + STREAM_TO_UINT16 (fcs, p); + p_buf->len -= L2CAP_FCS_LEN; + + if (l2c_fcr_rx_get_fcs(p_buf) != fcs) + { + L2CAP_TRACE_WARNING1 ("Rx L2CAP PDU: CID: 0x%04x BAD FCS", p_ccb->local_cid); + GKI_freebuf(p_buf); + return; + } + } + + /* Get the control word */ + p = ((UINT8 *)(p_buf+1)) + p_buf->offset; + + STREAM_TO_UINT16 (ctrl_word, p); + + p_buf->len -= L2CAP_FCR_OVERHEAD; + p_buf->offset += L2CAP_FCR_OVERHEAD; + + /* If we had a poll bit outstanding, check if we got a final response */ + if (p_ccb->fcrb.wait_ack) + { + /* If final bit not set, ignore the frame unless it is a polled S-frame */ + if ( !(ctrl_word & L2CAP_FCR_F_BIT) ) + { + if ( (ctrl_word & L2CAP_FCR_P_BIT) && (ctrl_word & L2CAP_FCR_S_FRAME_BIT) ) + { + if (p_ccb->fcrb.srej_sent) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_SREJ, L2CAP_FCR_F_BIT); + else if (p_ccb->fcrb.local_busy) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, L2CAP_FCR_F_BIT); + else + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_F_BIT); + + /* Got a poll while in wait_ack state, so re-start our timer with 1-second */ + /* This is a small optimization... the monitor timer is 12 secs, but we saw */ + /* that if the other side sends us a poll when we are waiting for a final, */ + /* then it speeds up recovery significantly if we poll him back soon after his poll. */ + btu_start_quick_timer (&p_ccb->fcrb.mon_retrans_timer, BTU_TTYPE_L2CAP_CHNL, QUICK_TIMER_TICKS_PER_SEC); + } + GKI_freebuf (p_buf); + return; + } + + p_ccb->fcrb.wait_ack = FALSE; + + /* P and F are mutually exclusive */ + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + ctrl_word &= ~L2CAP_FCR_P_BIT; + + if (p_ccb->fcrb.waiting_for_ack_q.count == 0) + p_ccb->fcrb.num_tries = 0; + + l2c_fcr_stop_timer (p_ccb); + } + else + { + /* Otherwise, ensure the final bit is ignored */ + ctrl_word &= ~L2CAP_FCR_F_BIT; + } + + /* Process receive sequence number */ + if (!process_reqseq (p_ccb, ctrl_word)) + { + GKI_freebuf (p_buf); + return; + } + + /* Process based on whether it is an S-frame or an I-frame */ + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + process_s_frame (p_ccb, p_buf, ctrl_word); + else + process_i_frame (p_ccb, p_buf, ctrl_word, FALSE); + + /* Return if the channel got disconnected by a bad packet or max retransmissions */ + if ( (!p_ccb->in_use) || (p_ccb->chnl_state != CST_OPEN) ) + return; + + /* If we have some buffers held while doing SREJ, and SREJ has cleared, process them now */ + if ( (!p_ccb->fcrb.local_busy) && (!p_ccb->fcrb.srej_sent) && (p_ccb->fcrb.srej_rcv_hold_q.count > 0) ) + { + BUFFER_Q temp_q = p_ccb->fcrb.srej_rcv_hold_q; + + GKI_init_q (&p_ccb->fcrb.srej_rcv_hold_q); + + while ((p_buf = (BT_HDR *)GKI_dequeue (&temp_q)) != NULL) + { + if (p_ccb->in_use && (p_ccb->chnl_state == CST_OPEN)) + { + /* Get the control word */ + p = ((UINT8 *)(p_buf+1)) + p_buf->offset - L2CAP_FCR_OVERHEAD; + + STREAM_TO_UINT16 (ctrl_word, p); + + L2CAP_TRACE_DEBUG3 ("l2c_fcr_proc_pdu() CID: 0x%04x Process Buffer from SREJ_Hold_Q TxSeq: %u Expected_Seq: %u", + p_ccb->local_cid, (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + p_ccb->fcrb.next_seq_expected); + + /* Process the SREJ held I-frame, but do not send an RR for each individual frame */ + process_i_frame (p_ccb, p_buf, ctrl_word, TRUE); + } + else + GKI_freebuf (p_buf); + + /* If more frames were lost during SREJ, send a REJ */ + if (p_ccb->fcrb.rej_after_srej) + { + p_ccb->fcrb.rej_after_srej = FALSE; + p_ccb->fcrb.rej_sent = TRUE; + + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_REJ, 0); + } + } + + /* Now, if needed, send one RR for the whole held queue */ + if ( (!p_ccb->fcrb.local_busy) && (!p_ccb->fcrb.rej_sent) && (!p_ccb->fcrb.srej_sent) + && (p_ccb->fcrb.next_seq_expected != p_ccb->fcrb.last_ack_sent) ) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, 0); + else + { + L2CAP_TRACE_DEBUG6 ("l2c_fcr_proc_pdu() not sending RR CID: 0x%04x local_busy:%d rej_sent:%d srej_sent:%d Expected_Seq:%u Last_Ack:%u", + p_ccb->local_cid, p_ccb->fcrb.local_busy, p_ccb->fcrb.rej_sent, p_ccb->fcrb.srej_sent, p_ccb->fcrb.next_seq_expected, + p_ccb->fcrb.last_ack_sent); + } + } + + /* If a window has opened, check if we can send any more packets */ + if ( (p_ccb->fcrb.retrans_q.count || p_ccb->xmit_hold_q.count) + && (p_ccb->fcrb.wait_ack == FALSE) + && (l2c_fcr_is_flow_controlled (p_ccb) == FALSE) ) + { + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } +} + +/******************************************************************************* +** +** Function l2c_fcr_proc_tout +** +** Description Handle a timeout. We should be in error recovery state. +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_proc_tout (tL2C_CCB *p_ccb) +{ + L2CAP_TRACE_DEBUG5 ("l2c_fcr_proc_tout: CID: 0x%04x num_tries: %u (max: %u) wait_ack: %u ack_q_count: %u", + p_ccb->local_cid, p_ccb->fcrb.num_tries, p_ccb->peer_cfg.fcr.max_transmit, + p_ccb->fcrb.wait_ack, p_ccb->fcrb.waiting_for_ack_q.count); + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.retrans_touts++; +#endif + + if ( (p_ccb->peer_cfg.fcr.max_transmit != 0) && (++p_ccb->fcrb.num_tries > p_ccb->peer_cfg.fcr.max_transmit) ) + { + l2cu_disconnect_chnl (p_ccb); + } + else + { + if (!p_ccb->fcrb.srej_sent && !p_ccb->fcrb.rej_sent) + { + if (p_ccb->fcrb.local_busy) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, L2CAP_FCR_P_BIT); + else + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_P_BIT); + } + } +} + + +/******************************************************************************* +** +** Function l2c_fcr_proc_ack_tout +** +** Description Send RR/RNR if we have not acked I frame +** +** Returns - +** +*******************************************************************************/ +void l2c_fcr_proc_ack_tout (tL2C_CCB *p_ccb) +{ + L2CAP_TRACE_DEBUG5 ("l2c_fcr_proc_ack_tout: CID: 0x%04x State: %u Wack:%u Rq:%d Acked:%d", p_ccb->local_cid, + p_ccb->chnl_state, p_ccb->fcrb.wait_ack, p_ccb->fcrb.next_seq_expected, p_ccb->fcrb.last_ack_sent); + + if ( (p_ccb->chnl_state == CST_OPEN) && (!p_ccb->fcrb.wait_ack) + && (p_ccb->fcrb.last_ack_sent != p_ccb->fcrb.next_seq_expected) ) + { +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.xmit_ack_touts++; +#endif + if (p_ccb->fcrb.local_busy) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + else + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, 0); + } +} + + +/******************************************************************************* +** +** Function process_reqseq +** +** Description Handle receive sequence number +** +** Returns - +** +*******************************************************************************/ +static BOOLEAN process_reqseq (tL2C_CCB *p_ccb, UINT16 ctrl_word) +{ + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT8 req_seq, num_bufs_acked, xx; + UINT16 ls; + UINT16 full_sdus_xmitted; + + /* Receive sequence number does not ack anything for SREJ with P-bit set to zero */ + if ( (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + && ((ctrl_word & L2CAP_FCR_SUP_BITS) == (L2CAP_FCR_SUP_SREJ << L2CAP_FCR_SUP_SHIFT)) + && ((ctrl_word & L2CAP_FCR_P_BIT) == 0) ) + { + /* If anything still waiting for ack, restart the timer if it was stopped */ + if (p_fcrb->waiting_for_ack_q.count) + l2c_fcr_start_timer (p_ccb); + + return (TRUE); + } + + /* Extract the receive sequence number from the control word */ + req_seq = (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT; + + num_bufs_acked = (req_seq - p_fcrb->last_rx_ack) & L2CAP_FCR_SEQ_MODULO; + + /* Verify the request sequence is in range before proceeding */ + if (num_bufs_acked > p_fcrb->waiting_for_ack_q.count) + { + /* The channel is closed if ReqSeq is not in range */ + L2CAP_TRACE_WARNING4 ("L2CAP eRTM Frame BAD Req_Seq - ctrl_word: 0x%04x req_seq 0x%02x last_rx_ack: 0x%02x QCount: %u", + ctrl_word, req_seq, p_fcrb->last_rx_ack, p_fcrb->waiting_for_ack_q.count); + + l2cu_disconnect_chnl (p_ccb); + return (FALSE); + } + +/* + L2CAP_TRACE_DEBUG3 ("L2CAP process_reqseq 0x%02x last_rx_ack: 0x%02x QCount: %u", + req_seq, p_fcrb->last_rx_ack, p_fcrb->waiting_for_ack_q.count); +*/ + p_fcrb->last_rx_ack = req_seq; + + /* Now we can release all acknowledged frames, and restart the retransmission timer if needed */ + if (num_bufs_acked != 0) + { + p_fcrb->num_tries = 0; + full_sdus_xmitted = 0; + +#if (L2CAP_ERTM_STATS == TRUE) + l2c_fcr_collect_ack_delay (p_ccb, num_bufs_acked); +#endif + + for (xx = 0; xx < num_bufs_acked; xx++) + { + ls = ((BT_HDR *)(p_fcrb->waiting_for_ack_q.p_first))->layer_specific & L2CAP_FCR_SAR_BITS; + + if ( (ls == L2CAP_FCR_UNSEG_SDU) || (ls == L2CAP_FCR_END_SDU) ) + full_sdus_xmitted++; + + GKI_freebuf (GKI_dequeue (&p_fcrb->waiting_for_ack_q)); + } + + /* If we are still in a wait_ack state, do not mess with the timer */ + if (!p_ccb->fcrb.wait_ack) + l2c_fcr_stop_timer (p_ccb); + + /* Check if we need to call the "packet_sent" callback */ + if ( (p_ccb->p_rcb) && (p_ccb->p_rcb->api.pL2CA_TxComplete_Cb) && (full_sdus_xmitted) ) + { + /* Special case for eRTM, if all packets sent, send 0xFFFF */ + if ( (p_fcrb->waiting_for_ack_q.count == 0) && (p_ccb->xmit_hold_q.count == 0) ) + full_sdus_xmitted = 0xFFFF; + + (*p_ccb->p_rcb->api.pL2CA_TxComplete_Cb)(p_ccb->local_cid, full_sdus_xmitted); + } + } + + /* If anything still waiting for ack, restart the timer if it was stopped */ + if (p_fcrb->waiting_for_ack_q.count) + l2c_fcr_start_timer (p_ccb); + + return (TRUE); +} + + +/******************************************************************************* +** +** Function process_s_frame +** +** Description Process an S frame +** +** Returns - +** +*******************************************************************************/ +static void process_s_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word) +{ + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT16 s_frame_type = (ctrl_word & L2CAP_FCR_SUP_BITS) >> L2CAP_FCR_SUP_SHIFT; + BOOLEAN remote_was_busy; + BOOLEAN all_ok = TRUE; + + if (p_buf->len != 0) + { + L2CAP_TRACE_WARNING1 ("Incorrect S-frame Length (%d)", p_buf->len); + } + + L2CAP_TRACE_DEBUG2 ("process_s_frame ctrl_word 0x%04x fcrb_remote_busy:%d", ctrl_word, p_fcrb->remote_busy); + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.s_frames_rcvd[s_frame_type]++; +#endif + + if (ctrl_word & L2CAP_FCR_P_BIT) + { + p_fcrb->rej_sent = FALSE; /* After checkpoint, we can send anoher REJ */ + p_fcrb->send_f_rsp = TRUE; /* Set a flag in case an I-frame is pending */ + } + + switch (s_frame_type) + { + case L2CAP_FCR_SUP_RR: + remote_was_busy = p_fcrb->remote_busy; + p_fcrb->remote_busy = FALSE; + + if ( (ctrl_word & L2CAP_FCR_F_BIT) || (remote_was_busy) ) + all_ok = retransmit_i_frames (p_ccb, L2C_FCR_RETX_ALL_PKTS); + break; + + case L2CAP_FCR_SUP_REJ: + p_fcrb->remote_busy = FALSE; + all_ok = retransmit_i_frames (p_ccb, L2C_FCR_RETX_ALL_PKTS); + break; + + case L2CAP_FCR_SUP_RNR: + p_fcrb->remote_busy = TRUE; + l2c_fcr_stop_timer (p_ccb); + break; + + case L2CAP_FCR_SUP_SREJ: + p_fcrb->remote_busy = FALSE; + all_ok = retransmit_i_frames (p_ccb, (UINT8)((ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT)); + break; + } + + if (all_ok) + { + /* If polled, we need to respond with F-bit. Note, we may have sent a I-frame with the F-bit */ + if (p_fcrb->send_f_rsp) + { + if (p_fcrb->srej_sent) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_SREJ, L2CAP_FCR_F_BIT); + else if (p_fcrb->local_busy) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, L2CAP_FCR_F_BIT); + else + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, L2CAP_FCR_F_BIT); + + p_fcrb->send_f_rsp = FALSE; + } + } + else + { + L2CAP_TRACE_DEBUG0 ("process_s_frame hit_max_retries"); + } + + GKI_freebuf (p_buf); +} + + +/******************************************************************************* +** +** Function process_i_frame +** +** Description Process an I frame +** +** Returns - +** +*******************************************************************************/ +static void process_i_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word, BOOLEAN delay_ack) +{ + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT8 tx_seq, num_lost, num_to_ack, next_srej; + + /* If we were doing checkpoint recovery, first retransmit all unacked I-frames */ + if (ctrl_word & L2CAP_FCR_F_BIT) + { + if (!retransmit_i_frames (p_ccb, L2C_FCR_RETX_ALL_PKTS)) + { + GKI_freebuf(p_buf); + return; + } + } + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.ertm_pkt_counts[1]++; + p_ccb->fcrb.ertm_byte_counts[1] += p_buf->len; +#endif + + /* Extract the sequence number */ + tx_seq = (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT; + + /* If we have flow controlled the peer, ignore any bad I-frames from him */ + if ( (tx_seq != p_fcrb->next_seq_expected) && (p_fcrb->local_busy) ) + { + L2CAP_TRACE_WARNING1 ("Dropping bad I-Frame since we flowed off, tx_seq:%u", tx_seq); + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + GKI_freebuf(p_buf); + return; + } + + /* If there are no free buffers in the user Rx queue, drop the */ + /* received buffer now before we update any sequence numbers */ + if (GKI_poolfreecount (p_ccb->ertm_info.user_rx_pool_id) == 0) + { + L2CAP_TRACE_WARNING4 ("L2CAP CID: 0x%04x Dropping I-Frame seq: %u User RX Pool: %u (Size: %u) has no free buffers!!", + p_ccb->local_cid, tx_seq, p_ccb->ertm_info.user_rx_pool_id, + GKI_poolcount (p_ccb->ertm_info.user_rx_pool_id)); + GKI_freebuf(p_buf); + return; + } + + /* Check if tx-sequence is the expected one */ + if (tx_seq != p_fcrb->next_seq_expected) + { + num_lost = (tx_seq - p_fcrb->next_seq_expected) & L2CAP_FCR_SEQ_MODULO; + + /* Is the frame a duplicate ? If so, just drop it */ + if (num_lost >= p_ccb->our_cfg.fcr.tx_win_sz) + { + /* Duplicate - simply drop it */ + L2CAP_TRACE_WARNING2 ("process_i_frame() Dropping Duplicate Frame tx_seq:%u ExpectedTxSeq %u", tx_seq, p_fcrb->next_seq_expected); + GKI_freebuf(p_buf); + } + else + { + L2CAP_TRACE_WARNING6 ("process_i_frame() CID: 0x%04x Lost: %u tx_seq:%u ExpTxSeq %u Rej: %u SRej: %u", + p_ccb->local_cid, num_lost, tx_seq, p_fcrb->next_seq_expected, p_fcrb->rej_sent, p_fcrb->srej_sent); + + if (p_fcrb->srej_sent) + { + /* If SREJ sent, save the frame for later processing as long as it is in sequence */ + next_srej = (((BT_HDR *)p_fcrb->srej_rcv_hold_q.p_last)->layer_specific + 1) & L2CAP_FCR_SEQ_MODULO; + + if ( (tx_seq == next_srej) && (p_fcrb->srej_rcv_hold_q.count < p_ccb->our_cfg.fcr.tx_win_sz) ) + { + /* If user gave us a pool for held rx buffers, use that */ + if (p_ccb->ertm_info.fcr_rx_pool_id != HCI_ACL_POOL_ID) + { + BT_HDR *p_buf2; + + /* Adjust offset and len so that control word is copied */ + p_buf->offset -= L2CAP_FCR_OVERHEAD; + p_buf->len += L2CAP_FCR_OVERHEAD; + + p_buf2 = l2c_fcr_clone_buf (p_buf, p_buf->offset, p_buf->len, p_ccb->ertm_info.fcr_rx_pool_id); + + if (p_buf2) + { + GKI_freebuf (p_buf); + p_buf = p_buf2; + } + p_buf->offset += L2CAP_FCR_OVERHEAD; + p_buf->len -= L2CAP_FCR_OVERHEAD; + } + L2CAP_TRACE_DEBUG4 ("process_i_frame() Lost: %u tx_seq:%u ExpTxSeq %u Rej: %u SRej1", + num_lost, tx_seq, p_fcrb->next_seq_expected, p_fcrb->rej_sent); + + p_buf->layer_specific = tx_seq; + GKI_enqueue (&p_fcrb->srej_rcv_hold_q, p_buf); + } + else + { + L2CAP_TRACE_WARNING4 ("process_i_frame() CID: 0x%04x frame dropped in Srej Sent next_srej:%u hold_q.count:%u win_sz:%u", + p_ccb->local_cid, next_srej, p_fcrb->srej_rcv_hold_q.count, p_ccb->our_cfg.fcr.tx_win_sz); + + p_fcrb->rej_after_srej = TRUE; + GKI_freebuf (p_buf); + } + } + else if (p_fcrb->rej_sent) + { + L2CAP_TRACE_WARNING5 ("process_i_frame() CID: 0x%04x Lost: %u tx_seq:%u ExpTxSeq %u Rej: 1 SRej: %u", + p_ccb->local_cid, num_lost, tx_seq, p_fcrb->next_seq_expected, p_fcrb->srej_sent); + + /* If REJ sent, just drop the frame */ + GKI_freebuf (p_buf); + } + else + { + L2CAP_TRACE_DEBUG4 ("process_i_frame() CID: 0x%04x tx_seq:%u ExpTxSeq %u Rej: %u", + p_ccb->local_cid, tx_seq, p_fcrb->next_seq_expected, p_fcrb->rej_sent); + + /* If only one lost, we will send SREJ, otherwise we will send REJ */ + if (num_lost > 1) + { + GKI_freebuf (p_buf); + p_fcrb->rej_sent = TRUE; + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_REJ, 0); + } + else + { + if (p_fcrb->srej_rcv_hold_q.count != 0) + { + L2CAP_TRACE_ERROR3 ("process_i_frame() CID: 0x%04x sending SREJ tx_seq:%d hold_q.count:%u", + p_ccb->local_cid, tx_seq, p_fcrb->srej_rcv_hold_q.count); + } + p_buf->layer_specific = tx_seq; + GKI_enqueue (&p_fcrb->srej_rcv_hold_q, p_buf); + p_fcrb->srej_sent = TRUE; + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_SREJ, 0); + } + btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); + } + } + return; + } + + /* Seq number is the next expected. Clear possible reject exception in case it occured */ + p_fcrb->rej_sent = p_fcrb->srej_sent = FALSE; + + /* Adjust the next_seq, so that if the upper layer sends more data in the callback + context, the received frame is acked by an I-frame. */ + p_fcrb->next_seq_expected = (tx_seq + 1) & L2CAP_FCR_SEQ_MODULO; + + /* If any SAR problem in eRTM mode, spec says disconnect. */ + if (!do_sar_reassembly (p_ccb, p_buf, ctrl_word)) + { + L2CAP_TRACE_WARNING1 ("process_i_frame() CID: 0x%04x reassembly failed", p_ccb->local_cid); + l2cu_disconnect_chnl (p_ccb); + return; + } + + /* RR optimization - if peer can still send us more, then start an ACK timer */ + num_to_ack = (p_fcrb->next_seq_expected - p_fcrb->last_ack_sent) & L2CAP_FCR_SEQ_MODULO; + + if ( (num_to_ack < p_ccb->fcrb.max_held_acks) && (!p_fcrb->local_busy) ) + delay_ack = TRUE; + + /* We should neve never ack frame if we are not in OPEN state */ + if ((num_to_ack != 0) && p_ccb->in_use && (p_ccb->chnl_state == CST_OPEN)) + { + /* If no frames are awaiting transmission or are held, send an RR or RNR S-frame for ack */ + if (delay_ack) + { + /* If it is the first I frame we did not ack, start ack timer */ + if (!p_ccb->fcrb.ack_timer.in_use) + { + btu_start_quick_timer (&p_ccb->fcrb.ack_timer, BTU_TTYPE_L2CAP_FCR_ACK, + (L2CAP_FCR_ACK_TOUT*QUICK_TIMER_TICKS_PER_SEC)/1000); + } + } + else if ( ((p_ccb->xmit_hold_q.count == 0) || (l2c_fcr_is_flow_controlled (p_ccb))) + && (p_ccb->fcrb.srej_rcv_hold_q.count == 0) ) + { + if (p_fcrb->local_busy) + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RNR, 0); + else + l2c_fcr_send_S_frame (p_ccb, L2CAP_FCR_SUP_RR, 0); + } + } +} + + +/******************************************************************************* +** +** Function process_stream_frame +** +** Description This function processes frames in streaming mode +** +** Returns - +** +*******************************************************************************/ +static void process_stream_frame (tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + UINT16 ctrl_word; + UINT16 fcs; + UINT8 *p; + UINT8 tx_seq; + + /* Verify FCS if using */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) + { + p = ((UINT8 *)(p_buf+1)) + p_buf->offset + p_buf->len - L2CAP_FCS_LEN; + + /* Extract and drop the FCS from the packet */ + STREAM_TO_UINT16 (fcs, p); + p_buf->len -= L2CAP_FCS_LEN; + + if (l2c_fcr_rx_get_fcs(p_buf) != fcs) + { + L2CAP_TRACE_WARNING1 ("Rx L2CAP PDU: CID: 0x%04x BAD FCS", p_ccb->local_cid); + GKI_freebuf(p_buf); + return; + } + } + + /* Get the control word */ + p = ((UINT8 *)(p_buf+1)) + p_buf->offset; + + STREAM_TO_UINT16 (ctrl_word, p); + + p_buf->len -= L2CAP_FCR_OVERHEAD; + p_buf->offset += L2CAP_FCR_OVERHEAD; + + /* Make sure it is an I-frame */ + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + { + L2CAP_TRACE_WARNING2 ("Rx L2CAP PDU: CID: 0x%04x BAD S-frame in streaming mode ctrl_word: 0x%04x", p_ccb->local_cid, ctrl_word); + GKI_freebuf (p_buf); + return; + } + +#if BT_TRACE_VERBOSE == TRUE + L2CAP_TRACE_EVENT6 ("L2CAP eRTM Rx I-frame: cid: 0x%04x Len: %u SAR: %-12s TxSeq: %u ReqSeq: %u F: %u", + p_ccb->local_cid, p_buf->len, + SAR_types[(ctrl_word & L2CAP_FCR_SAR_BITS) >> L2CAP_FCR_SAR_BITS_SHIFT], + (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_REQ_SEQ_BITS) >> L2CAP_FCR_REQ_SEQ_BITS_SHIFT, + (ctrl_word & L2CAP_FCR_F_BIT) >> L2CAP_FCR_F_BIT_SHIFT); +#endif + + /* Extract the sequence number */ + tx_seq = (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT; + + /* Check if tx-sequence is the expected one */ + if (tx_seq != p_ccb->fcrb.next_seq_expected) + { + L2CAP_TRACE_WARNING4 ("Rx L2CAP PDU: CID: 0x%04x Lost frames Exp: %u Got: %u p_rx_sdu: 0x%08x", + p_ccb->local_cid, p_ccb->fcrb.next_seq_expected, tx_seq, p_ccb->fcrb.p_rx_sdu); + + /* Lost one or more packets, so flush the SAR queue */ + if (p_ccb->fcrb.p_rx_sdu != NULL) + { + GKI_freebuf (p_ccb->fcrb.p_rx_sdu); + p_ccb->fcrb.p_rx_sdu = NULL; + } + } + + p_ccb->fcrb.next_seq_expected = (tx_seq + 1) & L2CAP_FCR_SEQ_MODULO; + + if (!do_sar_reassembly (p_ccb, p_buf, ctrl_word)) + { + /* Some sort of SAR error, so flush the SAR queue */ + if (p_ccb->fcrb.p_rx_sdu != NULL) + { + GKI_freebuf (p_ccb->fcrb.p_rx_sdu); + p_ccb->fcrb.p_rx_sdu = NULL; + } + } +} + + +/******************************************************************************* +** +** Function do_sar_reassembly +** +** Description Process SAR bits and re-assemble frame +** +** Returns TRUE if all OK, else FALSE +** +*******************************************************************************/ +static BOOLEAN do_sar_reassembly (tL2C_CCB *p_ccb, BT_HDR *p_buf, UINT16 ctrl_word) +{ + tL2C_FCRB *p_fcrb = &p_ccb->fcrb; + UINT16 sar_type = ctrl_word & L2CAP_FCR_SEG_BITS; + BOOLEAN packet_ok = TRUE; + UINT8 *p; + + /* Check if the SAR state is correct */ + if ((sar_type == L2CAP_FCR_UNSEG_SDU) || (sar_type == L2CAP_FCR_START_SDU)) + { + if (p_fcrb->p_rx_sdu != NULL) + { + L2CAP_TRACE_WARNING2 ("SAR - got unexpected unsegmented or start SDU Expected len: %u Got so far: %u", + p_fcrb->rx_sdu_len, p_fcrb->p_rx_sdu->len); + + packet_ok = FALSE; + } + /* Check the length of the packet */ + if ( (sar_type == L2CAP_FCR_START_SDU) && (p_buf->len < L2CAP_SDU_LEN_OVERHEAD) ) + { + L2CAP_TRACE_WARNING1 ("SAR start packet too short: %u", p_buf->len); + packet_ok = FALSE; + } + } + else + { + if (p_fcrb->p_rx_sdu == NULL) + { + L2CAP_TRACE_WARNING0 ("SAR - got unexpected cont or end SDU"); + packet_ok = FALSE; + } + } + + if ( (packet_ok) && (sar_type != L2CAP_FCR_UNSEG_SDU) ) + { + p = ((UINT8 *)(p_buf + 1)) + p_buf->offset; + + /* For start SDU packet, extract the SDU length */ + if (sar_type == L2CAP_FCR_START_SDU) + { + /* Get the SDU length */ + STREAM_TO_UINT16 (p_fcrb->rx_sdu_len, p); + p_buf->offset += 2; + p_buf->len -= 2; + + if (p_fcrb->rx_sdu_len > p_ccb->max_rx_mtu) + { + L2CAP_TRACE_WARNING2 ("SAR - SDU len: %u larger than MTU: %u", p_fcrb->rx_sdu_len, p_fcrb->rx_sdu_len); + packet_ok = FALSE; + } + else if ((p_fcrb->p_rx_sdu = (BT_HDR *)GKI_getpoolbuf (p_ccb->ertm_info.user_rx_pool_id)) == NULL) + { + L2CAP_TRACE_ERROR1 ("SAR - no buffer for SDU start user_rx_pool_id:%d", p_ccb->ertm_info.user_rx_pool_id); + packet_ok = FALSE; + } + else + { + p_fcrb->p_rx_sdu->offset = 4; /* this is the minimal offset required by OBX to process incoming packets */ + p_fcrb->p_rx_sdu->len = 0; + } + } + + if (packet_ok) + { + if ((p_fcrb->p_rx_sdu->len + p_buf->len) > p_fcrb->rx_sdu_len) + { + L2CAP_TRACE_ERROR4 ("SAR - SDU len exceeded Type: %u Lengths: %u %u %u", + sar_type, p_fcrb->p_rx_sdu->len, p_buf->len, p_fcrb->rx_sdu_len); + packet_ok = FALSE; + } + else if ( (sar_type == L2CAP_FCR_END_SDU) && ((p_fcrb->p_rx_sdu->len + p_buf->len) != p_fcrb->rx_sdu_len) ) + { + L2CAP_TRACE_WARNING3 ("SAR - SDU end rcvd but SDU incomplete: %u %u %u", + p_fcrb->p_rx_sdu->len, p_buf->len, p_fcrb->rx_sdu_len); + packet_ok = FALSE; + } + else + { + memcpy (((UINT8 *) (p_fcrb->p_rx_sdu + 1)) + p_fcrb->p_rx_sdu->offset + p_fcrb->p_rx_sdu->len, p, p_buf->len); + + p_fcrb->p_rx_sdu->len += p_buf->len; + + GKI_freebuf (p_buf); + p_buf = NULL; + + if (sar_type == L2CAP_FCR_END_SDU) + { + p_buf = p_fcrb->p_rx_sdu; + p_fcrb->p_rx_sdu = NULL; + } + } + } + } + + if (packet_ok == FALSE) + { + GKI_freebuf (p_buf); + } + else if (p_buf != NULL) + { +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (p_ccb->local_cid < L2CAP_BASE_APPL_CID) + (*l2cb.fixed_reg[p_ccb->local_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb)(p_ccb->p_lcb->remote_bd_addr, p_buf); + else +#endif + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DATA, p_buf); + } + + return (packet_ok); +} + + +/******************************************************************************* +** +** Function retransmit_i_frames +** +** Description This function retransmits i-frames awaiting acks. +** +** Returns BOOLEAN - TRUE if retransmitted +** +*******************************************************************************/ +static BOOLEAN retransmit_i_frames (tL2C_CCB *p_ccb, UINT8 tx_seq) +{ + BT_HDR *p_buf, *p_buf2; + UINT8 *p; + UINT8 buf_seq; + UINT16 ctrl_word; + + if ( (p_ccb->fcrb.waiting_for_ack_q.p_first) + && (p_ccb->peer_cfg.fcr.max_transmit != 0) + && (p_ccb->fcrb.num_tries >= p_ccb->peer_cfg.fcr.max_transmit) ) + { + L2CAP_TRACE_EVENT5 ("Max Tries Exceeded: (last_acq: %d CID: 0x%04x num_tries: %u (max: %u) ack_q_count: %u", + p_ccb->fcrb.last_rx_ack, p_ccb->local_cid, p_ccb->fcrb.num_tries, p_ccb->peer_cfg.fcr.max_transmit, + p_ccb->fcrb.waiting_for_ack_q.count); + + l2cu_disconnect_chnl (p_ccb); + return (FALSE); + } + + /* tx_seq indicates whether to retransmit a specific sequence or all (if == L2C_FCR_RETX_ALL_PKTS) */ + if (tx_seq != L2C_FCR_RETX_ALL_PKTS) + { + /* If sending only one, the sequence number tells us which one. Look for it. + */ + for (p_buf = (BT_HDR *)p_ccb->fcrb.waiting_for_ack_q.p_first; p_buf; p_buf = (BT_HDR *)GKI_getnext (p_buf)) + { + /* Get the old control word */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + L2CAP_PKT_OVERHEAD; + + STREAM_TO_UINT16 (ctrl_word, p); + + buf_seq = (ctrl_word & L2CAP_FCR_TX_SEQ_BITS) >> L2CAP_FCR_TX_SEQ_BITS_SHIFT; + + L2CAP_TRACE_DEBUG2 ("retransmit_i_frames() cur seq: %u looking for: %u", buf_seq, tx_seq); + + if (tx_seq == buf_seq) + break; + } + + if (!p_buf) + { + L2CAP_TRACE_ERROR2 ("retransmit_i_frames() UNKNOWN seq: %u q_count: %u", tx_seq, p_ccb->fcrb.waiting_for_ack_q.count); + return (TRUE); + } + } + else + { + /* Retransmitting everything. Flush buffers we already put in the link xmit queue. + */ + p_buf = (BT_HDR *)p_ccb->p_lcb->link_xmit_data_q.p_first; + + while (p_buf != NULL) + { + /* Do not flush other CIDs or partial segments */ + if ( (p_buf->layer_specific == 0) && (p_buf->event == p_ccb->local_cid) ) + { + p_buf2 = p_buf; + p_buf = (BT_HDR *)GKI_getnext (p_buf); + + GKI_remove_from_queue (&p_ccb->p_lcb->link_xmit_data_q, p_buf2); + GKI_freebuf (p_buf2); + } + else + p_buf = (BT_HDR *)GKI_getnext (p_buf); + } + + /* Also flush our retransmission queue */ + while (p_ccb->fcrb.retrans_q.p_first) + GKI_freebuf (GKI_dequeue (&p_ccb->fcrb.retrans_q)); + + p_buf = (BT_HDR *)p_ccb->fcrb.waiting_for_ack_q.p_first; + } + + while (p_buf != NULL) + { + p_buf2 = l2c_fcr_clone_buf (p_buf, p_buf->offset, p_buf->len, p_ccb->ertm_info.fcr_tx_pool_id); + + if (p_buf2) + { + p_buf2->layer_specific = p_buf->layer_specific; + + GKI_enqueue (&p_ccb->fcrb.retrans_q, p_buf2); + } + + if ( (tx_seq != L2C_FCR_RETX_ALL_PKTS) || (p_buf2 == NULL) ) + break; + else + p_buf = (BT_HDR *)GKI_getnext (p_buf); + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + + if (p_ccb->fcrb.waiting_for_ack_q.count) + { + p_ccb->fcrb.num_tries++; + l2c_fcr_start_timer (p_ccb); + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2c_fcr_get_next_xmit_sdu_seg +** +** Description Get the next SDU segment to transmit. +** +** Returns pointer to buffer with segment or NULL +** +*******************************************************************************/ +BT_HDR *l2c_fcr_get_next_xmit_sdu_seg (tL2C_CCB *p_ccb, UINT16 max_packet_length) +{ + BOOLEAN first_seg = FALSE, /* The segment is the first part of data */ + mid_seg = FALSE, /* The segment is the middle part of data */ + last_seg = FALSE; /* The segment is the last part of data */ + UINT16 sdu_len; + BT_HDR *p_buf, *p_xmit; + UINT8 *p; + UINT16 max_pdu = p_ccb->tx_mps /* Needed? - L2CAP_MAX_HEADER_FCS*/; + + /* If there is anything in the retransmit queue, that goes first + */ + if (p_ccb->fcrb.retrans_q.p_first) + { + p_buf = (BT_HDR *)GKI_dequeue (&p_ccb->fcrb.retrans_q); + + /* Update Rx Seq and FCS if we acked some packets while this one was queued */ + prepare_I_frame (p_ccb, p_buf, TRUE); + + p_buf->event = p_ccb->local_cid; + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE + /* Use sdu_len to hold the control word */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + L2CAP_PKT_OVERHEAD; + STREAM_TO_UINT16 (sdu_len, p); + + /* Get out if packet was dropped; just pretend it went out */ + if (l2c_corrupt_the_fcr_packet (p_ccb, p_buf, FALSE, sdu_len)) + return (NULL); +#endif /* L2CAP_CORRUPT_ERTM_PKTS */ + +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.pkts_retransmitted++; + p_ccb->fcrb.ertm_pkt_counts[0]++; + p_ccb->fcrb.ertm_byte_counts[0] += (p_buf->len - 8); +#endif + return (p_buf); + } + + /* For BD/EDR controller, max_packet_length is set to 0 */ + /* For AMP controller, max_packet_length is set by available blocks */ + if ( (max_packet_length > L2CAP_MAX_HEADER_FCS) + && (max_pdu + L2CAP_MAX_HEADER_FCS > max_packet_length) ) + { + max_pdu = max_packet_length - L2CAP_MAX_HEADER_FCS; + } + + p_buf = (BT_HDR *)p_ccb->xmit_hold_q.p_first; + + /* If there is more data than the MPS, it requires segmentation */ + if (p_buf->len > max_pdu) + { + /* We are using the "event" field to tell is if we already started segmentation */ + if (p_buf->event == 0) + { + first_seg = TRUE; + sdu_len = p_buf->len; + } + else + mid_seg = TRUE; + + /* Get a new buffer and copy the data that can be sent in a PDU */ + p_xmit = l2c_fcr_clone_buf (p_buf, L2CAP_MIN_OFFSET + L2CAP_SDU_LEN_OFFSET, + max_pdu, p_ccb->ertm_info.fcr_tx_pool_id); + + if (p_xmit != NULL) + { + p_buf->event = p_ccb->local_cid; + p_xmit->event = p_ccb->local_cid; + + p_buf->len -= max_pdu; + p_buf->offset += max_pdu; + + /* copy PBF setting */ + p_xmit->layer_specific = p_buf->layer_specific; + } + else /* Should never happen if the application has configured buffers correctly */ + { + L2CAP_TRACE_ERROR1 ("L2CAP - cannot get buffer, for segmentation, pool: %u", p_ccb->ertm_info.fcr_tx_pool_id); + return (NULL); + } + } + else /* Use the original buffer if no segmentation, or the last segment */ + { + p_xmit = (BT_HDR *)GKI_dequeue (&p_ccb->xmit_hold_q); + + if (p_xmit->event != 0) + last_seg = TRUE; + + p_xmit->event = p_ccb->local_cid; + } + + /* Step back to add the L2CAP headers */ + p_xmit->offset -= (L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD); + p_xmit->len += L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD; + + if (first_seg) + { + p_xmit->offset -= L2CAP_SDU_LEN_OVERHEAD; + p_xmit->len += L2CAP_SDU_LEN_OVERHEAD; + } + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_xmit + 1) + p_xmit->offset; + + /* Now the L2CAP header */ + + /* Note: if FCS has to be included then the length is recalculated later */ + UINT16_TO_STREAM (p, p_xmit->len - L2CAP_PKT_OVERHEAD); + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + + if (first_seg) + { + /* Skip control word and add SDU length */ + p += 2; + UINT16_TO_STREAM (p, sdu_len); + + /* We will store the SAR type in layer-specific */ + /* layer_specific is shared with flushable flag(bits 0-1), don't clear it */ + p_xmit->layer_specific |= L2CAP_FCR_START_SDU; + + first_seg = FALSE; + } + else if (mid_seg) + p_xmit->layer_specific |= L2CAP_FCR_CONT_SDU; + else if (last_seg) + p_xmit->layer_specific |= L2CAP_FCR_END_SDU; + else + p_xmit->layer_specific |= L2CAP_FCR_UNSEG_SDU; + + prepare_I_frame (p_ccb, p_xmit, FALSE); + + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + { + BT_HDR *p_wack = l2c_fcr_clone_buf (p_xmit, HCI_DATA_PREAMBLE_SIZE, p_xmit->len, p_ccb->ertm_info.fcr_tx_pool_id); + + if (!p_wack) + { + L2CAP_TRACE_ERROR3 ("L2CAP - no buffer for xmit cloning, CID: 0x%04x Pool: %u Count: %u", + p_ccb->local_cid, p_ccb->ertm_info.fcr_tx_pool_id, GKI_poolfreecount(p_ccb->ertm_info.fcr_tx_pool_id)); + + /* We will not save the FCS in case we reconfigure and change options */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) + p_xmit->len -= L2CAP_FCS_LEN; + + /* Pretend we sent it and it got lost */ + GKI_enqueue (&p_ccb->fcrb.waiting_for_ack_q, p_xmit); + return (NULL); + } + else + { +#if (L2CAP_ERTM_STATS == TRUE) + /* set timestamp at the end of tx I-frame to get acking delay */ + p = ((UINT8 *) (p_wack+1)) + p_wack->offset + p_wack->len; + UINT32_TO_STREAM (p, GKI_get_os_tick_count()); +#endif + /* We will not save the FCS in case we reconfigure and change options */ + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) + p_wack->len -= L2CAP_FCS_LEN; + + p_wack->layer_specific = p_xmit->layer_specific; + GKI_enqueue (&p_ccb->fcrb.waiting_for_ack_q, p_wack); + } + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE + { + UINT16 ctrl_word; + p = ((UINT8 *) (p_xmit+1)) + p_xmit->offset + L2CAP_PKT_OVERHEAD; + STREAM_TO_UINT16 (ctrl_word, p); + + /* Get out if packet was dropped; pretend it was sent */ + if (l2c_corrupt_the_fcr_packet (p_ccb, p_xmit, FALSE, ctrl_word)) + return (NULL); + } +#endif +#if (L2CAP_ERTM_STATS == TRUE) + p_ccb->fcrb.ertm_pkt_counts[0]++; + p_ccb->fcrb.ertm_byte_counts[0] += (p_xmit->len - 8); +#endif + + } + + return (p_xmit); +} + + +/******************************************************************************* +** Configuration negotiation functions +** +** The following functions are used in negotiating channel modes during +** configuration +********************************************************************************/ + +/******************************************************************************* +** +** Function l2c_fcr_chk_chan_modes +** +** Description Validates and adjusts if necessary, the FCR options +** based on remote EXT features. +** +** Note: This assumes peer EXT Features have been received. +** Basic mode is used if FCR Options have not been received +** +** Returns UINT8 - nonzero if can continue, '0' if no compatible channels +** +*******************************************************************************/ +UINT8 l2c_fcr_chk_chan_modes (tL2C_CCB *p_ccb) +{ + /* Remove nonbasic options that the peer does not support */ + if (!(p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_ENH_RETRANS)) + p_ccb->ertm_info.allowed_modes &= ~L2CAP_FCR_CHAN_OPT_ERTM; + + if (!(p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_STREAM_MODE)) + p_ccb->ertm_info.allowed_modes &= ~L2CAP_FCR_CHAN_OPT_STREAM; + + /* At least one type needs to be set (Basic, ERTM, STM) to continue */ + if (!p_ccb->ertm_info.allowed_modes) + { + L2CAP_TRACE_WARNING0 ("L2CAP - Peer does not support our desired channel types"); + } + + return (p_ccb->ertm_info.allowed_modes); +} + +/******************************************************************************* +** +** Function l2c_fcr_adj_our_req_options +** +** Description Validates and sets up the FCR options passed in from +** L2CA_ConfigReq based on remote device's features. +** +** Returns TRUE if no errors, Otherwise FALSE +** +*******************************************************************************/ +BOOLEAN l2c_fcr_adj_our_req_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + tL2CAP_FCR_OPTS *p_fcr = &p_cfg->fcr; + + if (p_fcr->mode != p_ccb->ertm_info.preferred_mode) + { + L2CAP_TRACE_WARNING2 ("l2c_fcr_adj_our_req_options - preferred_mode (%d), does not match mode (%d)", + p_ccb->ertm_info.preferred_mode, p_fcr->mode); + + /* The preferred mode is passed in through tL2CAP_ERTM_INFO, so override this one */ + p_fcr->mode = p_ccb->ertm_info.preferred_mode; + } + + /* If upper layer did not request eRTM mode, BASIC must be used */ + if (p_ccb->ertm_info.allowed_modes == L2CAP_FCR_CHAN_OPT_BASIC) + { + if (p_cfg->fcr_present && p_fcr->mode != L2CAP_FCR_BASIC_MODE) + { + L2CAP_TRACE_WARNING1 ("l2c_fcr_adj_our_req_options (mode %d): ERROR: No FCR options set using BASIC mode", p_fcr->mode); + } + p_fcr->mode = L2CAP_FCR_BASIC_MODE; + } + + /* Process the FCR options if initial channel bring-up (not a reconfig request) + ** Determine initial channel mode to try based on our options and remote's features + */ + if (p_cfg->fcr_present && !(p_ccb->config_done & RECONFIG_FLAG)) + { + /* We need to have at least one mode type common with the peer */ + if (!l2c_fcr_chk_chan_modes(p_ccb)) + { + /* Two channels have incompatible supported types */ + l2cu_disconnect_chnl (p_ccb); + return (FALSE); + } + + /* Basic is the only common channel mode between the two devices */ + else if (p_ccb->ertm_info.allowed_modes == L2CAP_FCR_CHAN_OPT_BASIC) + { + /* We only want to try Basic, so bypass sending the FCR options entirely */ + p_cfg->fcr_present = FALSE; + p_cfg->fcs_present = FALSE; /* Illegal to use FCS option in basic mode */ + p_cfg->ext_flow_spec_present = FALSE; /* Illegal to use extended flow spec in basic mode */ + } + + /* We have at least one non-basic mode available + * Override mode from available mode options based on preference, if needed + */ + else + { + /* If peer does not support STREAMING, try ERTM */ + if (p_fcr->mode == L2CAP_FCR_STREAM_MODE && !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_STREAM)) + { + L2CAP_TRACE_DEBUG0 ("L2C CFG: mode is STREAM, but peer does not support; Try ERTM"); + p_fcr->mode = L2CAP_FCR_ERTM_MODE; + } + + /* If peer does not support ERTM, try BASIC (will support this if made it here in the code) */ + if (p_fcr->mode == L2CAP_FCR_ERTM_MODE && !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_ERTM)) + { + L2CAP_TRACE_DEBUG0 ("L2C CFG: mode is ERTM, but peer does not support; Try BASIC"); + p_fcr->mode = L2CAP_FCR_BASIC_MODE; + } + } + + if (p_fcr->mode != L2CAP_FCR_BASIC_MODE) + { + /* MTU must be smaller than buffer size */ + if ( (p_cfg->mtu_present) && (p_cfg->mtu > p_ccb->max_rx_mtu) ) + { + L2CAP_TRACE_WARNING2 ("L2CAP - MTU: %u larger than buf size: %u", p_cfg->mtu, p_ccb->max_rx_mtu); + return (FALSE); + } + + /* application want to use the default MPS */ + if (p_fcr->mps == L2CAP_DEFAULT_ERM_MPS) + { + p_fcr->mps = L2CAP_MPS_OVER_BR_EDR; + } + /* MPS must be less than MTU */ + else if (p_fcr->mps > p_ccb->max_rx_mtu) + { + L2CAP_TRACE_WARNING2 ("L2CAP - MPS %u invalid MTU: %u", p_fcr->mps, p_ccb->max_rx_mtu); + return (FALSE); + } + + /* We always initially read into the HCI buffer pool, so make sure it fits */ + if (p_fcr->mps > (L2CAP_MTU_SIZE - L2CAP_MAX_HEADER_FCS)) + p_fcr->mps = L2CAP_MTU_SIZE - L2CAP_MAX_HEADER_FCS; + } + else + { + p_cfg->fcs_present = FALSE; /* Illegal to use FCS option in basic mode */ + p_cfg->ext_flow_spec_present = FALSE; /* Illegal to use extended flow spec in basic mode */ + } + + p_ccb->our_cfg.fcr = *p_fcr; + } + else /* Not sure how to send a reconfiguration(??) should fcr be included? */ + { + p_ccb->our_cfg.fcr_present = FALSE; + } + + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2c_fcr_adj_monitor_retran_timeout +** +** Description Overrides monitor/retrans timer value based on controller +** +** Returns None +** +*******************************************************************************/ +void l2c_fcr_adj_monitor_retran_timeout (tL2C_CCB *p_ccb) +{ + /* adjust our monitor/retran timeout */ + if (p_ccb->out_cfg_fcr_present) + { + /* + ** if we requestd ERTM or accepted ERTM + ** We may accept ERTM even if we didn't request ERTM, in case of requesting STREAM + */ + if ((p_ccb->our_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) + ||(p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE)) + { + /* upper layer setting is ignored */ + p_ccb->our_cfg.fcr.mon_tout = L2CAP_MIN_MONITOR_TOUT; + p_ccb->our_cfg.fcr.rtrans_tout = L2CAP_MIN_RETRANS_TOUT; + } + else + { + p_ccb->our_cfg.fcr.mon_tout = 0; + p_ccb->our_cfg.fcr.rtrans_tout = 0; + } + + L2CAP_TRACE_DEBUG2 ("l2c_fcr_adj_monitor_retran_timeout: mon_tout:%d, rtrans_tout:%d", + p_ccb->our_cfg.fcr.mon_tout, p_ccb->our_cfg.fcr.rtrans_tout); + } +} +/******************************************************************************* +** +** Function l2c_fcr_adj_our_rsp_options +** +** Description Overrides any neccesary FCR options passed in from +** L2CA_ConfigRsp based on our FCR options. +** Only makes adjustments if channel is in ERTM mode. +** +** Returns None +** +*******************************************************************************/ +void l2c_fcr_adj_our_rsp_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + /* adjust our monitor/retran timeout */ + l2c_fcr_adj_monitor_retran_timeout(p_ccb); + + p_cfg->fcr_present = p_ccb->out_cfg_fcr_present; + + if (p_cfg->fcr_present) + { +// btla-specific ++ + /* Temporary - until a better algorithm is implemented */ + /* If peer's tx_wnd_sz requires too many buffers for us to support, then adjust it. For now, respond with our own tx_wnd_sz. */ + /* Note: peer is not guaranteed to obey our adjustment */ + if (p_ccb->peer_cfg.fcr.tx_win_sz > p_ccb->our_cfg.fcr.tx_win_sz) + { + L2CAP_TRACE_DEBUG3 ("%s: adjusting requested tx_win_sz from %i to %i", __FUNCTION__, p_ccb->peer_cfg.fcr.tx_win_sz, p_ccb->our_cfg.fcr.tx_win_sz); + p_ccb->peer_cfg.fcr.tx_win_sz = p_ccb->our_cfg.fcr.tx_win_sz; + } +// btla-specific -- + + p_cfg->fcr.mode = p_ccb->peer_cfg.fcr.mode; + p_cfg->fcr.tx_win_sz = p_ccb->peer_cfg.fcr.tx_win_sz; + p_cfg->fcr.max_transmit = p_ccb->peer_cfg.fcr.max_transmit; + p_cfg->fcr.mps = p_ccb->peer_cfg.fcr.mps; + p_cfg->fcr.rtrans_tout = p_ccb->our_cfg.fcr.rtrans_tout; + p_cfg->fcr.mon_tout = p_ccb->our_cfg.fcr.mon_tout; + } +} + +/******************************************************************************* +** +** Function l2c_fcr_renegotiate_chan +** +** Description Called upon unsuccessful peer response to config request. +** If the error is because of the channel mode, it will try +** to resend using another supported optional channel. +** +** Returns TRUE if resent configuration, False if channel matches or +** cannot match. +** +*******************************************************************************/ +BOOLEAN l2c_fcr_renegotiate_chan(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + UINT8 peer_mode = p_ccb->our_cfg.fcr.mode; + BOOLEAN can_renegotiate; + + /* Skip if this is a reconfiguration from OPEN STATE or if FCR is not returned */ + if (!p_cfg->fcr_present || (p_ccb->config_done & RECONFIG_FLAG)) + return (FALSE); + + /* Only retry if there are more channel options to try */ + if (p_cfg->result == L2CAP_CFG_UNACCEPTABLE_PARAMS) + { + peer_mode = (p_cfg->fcr_present) ? p_cfg->fcr.mode : L2CAP_FCR_BASIC_MODE; + + if (p_ccb->our_cfg.fcr.mode != peer_mode) + { + + if ((--p_ccb->fcr_cfg_tries) == 0) + { + p_cfg->result = L2CAP_CFG_FAILED_NO_REASON; + L2CAP_TRACE_WARNING0 ("l2c_fcr_renegotiate_chan (Max retries exceeded)"); + } + + can_renegotiate = FALSE; + + /* Try another supported mode if available based on our last attempted channel */ + switch (p_ccb->our_cfg.fcr.mode) + { + /* Our Streaming mode request was unnacceptable; try ERTM or Basic */ + case L2CAP_FCR_STREAM_MODE: + /* Peer wants ERTM and we support it */ + if ( (peer_mode == L2CAP_FCR_ERTM_MODE) && (p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_ERTM) ) + { + L2CAP_TRACE_DEBUG0 ("l2c_fcr_renegotiate_chan(Trying ERTM)"); + p_ccb->our_cfg.fcr.mode = L2CAP_FCR_ERTM_MODE; + can_renegotiate = TRUE; + } + else /* Falls through */ + + case L2CAP_FCR_ERTM_MODE: + { + /* We can try basic for any other peer mode if we support it */ + if (p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_BASIC) + { + L2CAP_TRACE_DEBUG0 ("l2c_fcr_renegotiate_chan(Trying Basic)"); + can_renegotiate = TRUE; + p_ccb->our_cfg.fcr.mode = L2CAP_FCR_BASIC_MODE; + } + } + break; + + default: + /* All other scenarios cannot be renegotiated */ + break; + } + + if (can_renegotiate) + { + p_ccb->our_cfg.fcr_present = TRUE; + + if (p_ccb->our_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) + { + p_ccb->our_cfg.fcs_present = FALSE; + p_ccb->our_cfg.ext_flow_spec_present = FALSE; + + /* Basic Mode uses ACL Data Pool, make sure the MTU fits */ + if ( (p_cfg->mtu_present) && (p_cfg->mtu > L2CAP_MTU_SIZE) ) + { + L2CAP_TRACE_WARNING1 ("L2CAP - adjust MTU: %u too large", p_cfg->mtu); + p_cfg->mtu = L2CAP_MTU_SIZE; + } + } + + l2cu_process_our_cfg_req (p_ccb, &p_ccb->our_cfg); + l2cu_send_peer_config_req (p_ccb, &p_ccb->our_cfg); + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT); + return (TRUE); + } + } + } + + /* Disconnect if the channels do not match */ + if (p_ccb->our_cfg.fcr.mode != peer_mode) + { + L2CAP_TRACE_WARNING2 ("L2C CFG: Channels incompatible (local %d, peer %d)", + p_ccb->our_cfg.fcr.mode, peer_mode); + l2cu_disconnect_chnl (p_ccb); + } + + return (FALSE); +} + + +/******************************************************************************* +** +** Function l2c_fcr_process_peer_cfg_req +** +** Description This function is called to process the FCR options passed +** in the peer's configuration request. +** +** Returns UINT8 - L2CAP_PEER_CFG_OK, L2CAP_PEER_CFG_UNACCEPTABLE, +** or L2CAP_PEER_CFG_DISCONNECT. +** +*******************************************************************************/ +UINT8 l2c_fcr_process_peer_cfg_req(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + UINT16 max_retrans_size; + UINT8 fcr_ok = L2CAP_PEER_CFG_OK; + + p_ccb->p_lcb->w4_info_rsp = FALSE; /* Handles T61x SonyEricsson Bug in Info Request */ + + L2CAP_TRACE_EVENT5 ("l2c_fcr_process_peer_cfg_req() CFG fcr_present:%d fcr.mode:%d CCB FCR mode:%d preferred: %u allowed:%u", + p_cfg->fcr_present, p_cfg->fcr.mode, p_ccb->our_cfg.fcr.mode, p_ccb->ertm_info.preferred_mode, + p_ccb->ertm_info.allowed_modes); + + /* If Peer wants basic, we are done (accept it or disconnect) */ + if (p_cfg->fcr.mode == L2CAP_FCR_BASIC_MODE) + { + /* If we do not allow basic, disconnect */ + if ( !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_BASIC) ) + fcr_ok = L2CAP_PEER_CFG_DISCONNECT; + } + + /* Need to negotiate if our modes are not the same */ + else if (p_cfg->fcr.mode != p_ccb->ertm_info.preferred_mode) + { + /* If peer wants a mode that we don't support then retry our mode (ex. rtx/flc), OR + ** If we want ERTM and they wanted streaming retry our mode. + ** Note: If we have already determined they support our mode previously + ** from their EXF mask. + */ + if ( (((1 << p_cfg->fcr.mode) & L2CAP_FCR_CHAN_OPT_ALL_MASK) == 0) + || (p_ccb->ertm_info.preferred_mode == L2CAP_FCR_ERTM_MODE) ) + { + p_cfg->fcr.mode = p_ccb->our_cfg.fcr.mode; + p_cfg->fcr.tx_win_sz = p_ccb->our_cfg.fcr.tx_win_sz; + p_cfg->fcr.max_transmit = p_ccb->our_cfg.fcr.max_transmit; + fcr_ok = L2CAP_PEER_CFG_UNACCEPTABLE; + } + + /* If we wanted basic, then try to renegotiate it */ + else if (p_ccb->ertm_info.preferred_mode == L2CAP_FCR_BASIC_MODE) + { + p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE; + p_cfg->fcr.max_transmit = p_cfg->fcr.tx_win_sz = 0; + p_cfg->fcr.rtrans_tout = p_cfg->fcr.mon_tout = p_cfg->fcr.mps = 0; + p_ccb->our_cfg.fcr.rtrans_tout = p_ccb->our_cfg.fcr.mon_tout = p_ccb->our_cfg.fcr.mps = 0; + fcr_ok = L2CAP_PEER_CFG_UNACCEPTABLE; + } + + /* Only other valid case is if they want ERTM and we wanted STM which should be + accepted if we support it; otherwise the channel should be disconnected */ + else if ( (p_cfg->fcr.mode != L2CAP_FCR_ERTM_MODE) + || !(p_ccb->ertm_info.allowed_modes & L2CAP_FCR_CHAN_OPT_ERTM) ) + { + fcr_ok = L2CAP_PEER_CFG_DISCONNECT; + } + } + + /* Configuration for FCR channels so make any adjustments and fwd to upper layer */ + if (fcr_ok == L2CAP_PEER_CFG_OK) + { + /* by default don't need to send params in the response */ + p_ccb->out_cfg_fcr_present = FALSE; + + /* Make any needed adjustments for the response to the peer */ + if (p_cfg->fcr_present && p_cfg->fcr.mode != L2CAP_FCR_BASIC_MODE) + { + /* Peer desires to bypass FCS check, and streaming or ERTM mode */ + if (p_cfg->fcs_present) + { + p_ccb->peer_cfg.fcs = p_cfg->fcs; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_FCS; + if( p_cfg->fcs == L2CAP_CFG_FCS_BYPASS) + p_ccb->bypass_fcs |= L2CAP_CFG_FCS_PEER; + } + + max_retrans_size = GKI_get_pool_bufsize (p_ccb->ertm_info.fcr_tx_pool_id) - sizeof(BT_HDR) + - L2CAP_MIN_OFFSET - L2CAP_SDU_LEN_OFFSET - L2CAP_FCS_LEN; + + /* Ensure the MPS is not bigger than the MTU */ + if ( (p_cfg->fcr.mps == 0) || (p_cfg->fcr.mps > p_ccb->peer_cfg.mtu) ) + { + p_cfg->fcr.mps = p_ccb->peer_cfg.mtu; + p_ccb->out_cfg_fcr_present = TRUE; + } + + /* Ensure the MPS is not bigger than our retransmission buffer */ + if (p_cfg->fcr.mps > max_retrans_size) + { + L2CAP_TRACE_DEBUG2("CFG: Overriding MPS to %d (orig %d)", max_retrans_size, p_cfg->fcr.mps); + + p_cfg->fcr.mps = max_retrans_size; + p_ccb->out_cfg_fcr_present = TRUE; + } + + if (p_cfg->fcr.mode == L2CAP_FCR_ERTM_MODE) + { + /* Always respond with FCR ERTM parameters */ + p_ccb->out_cfg_fcr_present = TRUE; + } + } + + /* Everything ok, so save the peer's adjusted fcr options */ + p_ccb->peer_cfg.fcr = p_cfg->fcr; + + if (p_cfg->fcr_present) + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_FCR; + } + else if (fcr_ok == L2CAP_PEER_CFG_UNACCEPTABLE) + { + /* Allow peer only one retry for mode */ + if (p_ccb->peer_cfg_already_rejected) + fcr_ok = L2CAP_PEER_CFG_DISCONNECT; + else + p_ccb->peer_cfg_already_rejected = TRUE; + } + + return (fcr_ok); +} + + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE +/******************************************************************************* +** Functions used for testing ERTM mode +*/ +/* If FALSE, will also corrupt cid, length, control word, etc. */ +#ifndef L2CAP_CORR_FCS_ONLY +#define L2CAP_CORR_FCS_ONLY TRUE +#endif + +#define L2C_DISP_FRAME_SIZE 16 +/******************************************************************************* +** +** Function l2c_corrupt_the_fcr_packet +** +** Description This function is used for testing purposes only. +** It systematically or randomly corrupts packets used with +** ERTM channels. +** +** Returns BOOLEAN TRUE if packet was dropped, +** FALSE if fcs corrupted or no corruption +** +*******************************************************************************/ +static BOOLEAN l2c_corrupt_the_fcr_packet (tL2C_CCB *p_ccb, BT_HDR *p_buf, + BOOLEAN is_rx, UINT16 ctrl_word) +{ + tL2C_FCR_TEST_CFG *p_cfg; + UINT32 tc; + UINT8 *p; + UINT32 xx; + char buf[L2C_DISP_FRAME_SIZE]; + + if (!p_ccb || !p_ccb->fcrb.test_cb.cfg.in_use) + return FALSE; + + /* Prepare bad FCS */ + p = ((UINT8 *) (p_buf + 1)) + p_buf->offset; + tc = GKI_get_os_tick_count(); + tc ^= p[p_buf->len - 1]; + xx = tc % 47; + + p_cfg = &p_ccb->fcrb.test_cb.cfg; +#if 0 + L2CAP_TRACE_DEBUG4 ("testcfg: type: %d, freq: %d (NRM-0, RDM-1), is_rx: %d, count: %d", + p_cfg->type, p_cfg->freq, p_cfg->is_rx, p_cfg->count); +#endif + /* If not time to corrupt get out */ + if (p_cfg->freq == L2CAP_FCR_FREQ_RANDOM) + { + if (xx != 0) + return FALSE; + } + else /* check test criteria before corrupting */ + { + if ( (p_cfg->count == 0) + || (p_cfg->is_rx != is_rx) + || ((UINT8)(ctrl_word & L2CAP_FCR_S_FRAME_BIT) != p_cfg->type) ) + { + return FALSE; + } + + /* Turn off if done */ + if (--(p_cfg->count) == 0) + { + p_ccb->fcrb.test_cb.cfg.in_use = FALSE; + } + } + +#if L2CAP_CORR_FCS_ONLY == TRUE + /* Corrupt the FCS check */ + p[p_buf->len - 1] = p[p_buf->len - 2] = 0; +#else + /* If made it this far packet needs to be corrupted */ + xx = tc % p_buf->len; + + /* Make sure the value was changed */ + if (p[xx + 1] == 0) + p[xx + 1] = 0x5A; + + p[xx] = p[xx] ^ p[xx + 1]; + +#if 1 /* Enable if not wishing to corrupting frame type */ + { + UINT8 *p_temp = ((UINT8 *) (p_buf + 1)) + p_buf->offset; + p_temp += L2CAP_PKT_OVERHEAD; + if ((UINT16)((*p_temp) & 0x01) != (ctrl_word & 0x0001)) + { + (*p_temp) |= (UINT8)(ctrl_word & 0x0001); + } + } +#endif +#endif + + if (is_rx) + { + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + BCM_STRCPY_S(buf, L2C_DISP_FRAME_SIZE, "Rx S-Frame"); + else + BCM_STRCPY_S(buf, L2C_DISP_FRAME_SIZE, "Rx I-Frame"); + } + else + { + if (ctrl_word & L2CAP_FCR_S_FRAME_BIT) + BCM_STRCPY_S(buf, L2C_DISP_FRAME_SIZE, "Tx S-Frame"); + else + BCM_STRCPY_S(buf, L2C_DISP_FRAME_SIZE, "Tx I-Frame"); + } + + /* Lastly, just drop packet if FCS is not being used or if Tx */ + if (!is_rx || p_ccb->bypass_fcs == L2CAP_BYPASS_FCS) + { + L2CAP_TRACE_ERROR6 ("-=-=-=-=-=-=-=- Dropping %s packet (0x%04x) tc: %u Buf Len: %u xx: %u count: %d", + buf, (UINT32)p_buf, tc, p_buf->len, xx, p_cfg->count); + GKI_freebuf(p_buf); + return TRUE; + } + else + { + L2CAP_TRACE_ERROR6 ("-=-=-=-=-=-=-=- Corrupting %s packet (0x%04x) tc: %u Buf Len: %u xx: %u count: %d", + buf, (UINT32)p_buf, tc, p_buf->len, xx, p_cfg->count); + } + + return FALSE; +} + + +/******************************************************************************* +** +** Function L2CA_SetupErtmTest +** +** Description This function is used for testing purposes only. +** It corrupts or drops one or more packets used with ERTM channels. +** +** Parameters +** cid - channel ID (0 uses RFCOMM PSM's CID) +** +** type - type of test to run (L2CAP_FCR_TTYPE_CORR_IFRAMES +** L2CAP_FCR_TTYPE_CORR_SFRAME +** L2CAP_FCR_TTYPE_STOP_TEST +** L2CAP_FCR_TTYPE_GET_CID - returns rfcomm cid only) +** +** is_rx - TRUE to corrupt Rx packet, FALSE for Tx packet) +** +** freq - L2CAP_FCR_FREQ_RANDOM (turns on random corruptions/drops) +** L2CAP_FCR_FREQ_NORMAL (turns on test with "count" corruptions/drops) +** +** count - number of packets in a row to drop or corrupt +** +** Returns CID of channel running test +** +*******************************************************************************/ +UINT16 L2CA_SetupErtmTest (UINT16 cid, UINT8 type, BOOLEAN is_rx, UINT8 freq, UINT16 count) +{ + tL2C_CCB *p_ccb = NULL; + tL2C_CCB *p_temp_ccb; + tL2C_FCR_TEST_CB *p_test_cb; + UINT16 xx; + + /* If '0' tests run on top of RFCOMM CID if active */ + if (cid == 0) + { + p_temp_ccb = l2cb.ccb_pool; + for (xx = 0; xx < MAX_L2CAP_CHANNELS; xx++, p_temp_ccb++) + { + /* Fixed channels don't have p_rcb's */ + if (p_temp_ccb->in_use && p_temp_ccb->p_rcb && p_temp_ccb->p_rcb->psm == BT_PSM_RFCOMM) + { + p_ccb = p_temp_ccb; + cid = L2CAP_BASE_APPL_CID + xx; + break; + } + } + } + else + p_ccb = l2cu_find_ccb_by_cid (NULL, cid); + + if (!p_ccb || type == L2CAP_FCR_TTYPE_GET_CID) + { + if (type == L2CAP_FCR_TTYPE_GET_CID) + { + L2CAP_TRACE_API1 ("L2CA_SetupErtmTest (GET_CID): cid = 0x%04x", cid); + } + return (cid); + } + + p_test_cb = &p_ccb->fcrb.test_cb; + + /* Turn off the current test */ + if (type == L2CAP_FCR_TTYPE_STOP_TEST) + { + if (p_test_cb->cfg.in_use) + { + L2CAP_TRACE_ERROR1 ("L2CA_SetupErtmTest (OFF): cid 0x%04x", cid); + } + p_test_cb->cfg.in_use = FALSE; + p_test_cb->cfg.count = 0; + } + + /* Process the new request */ + else if (!p_test_cb->cfg.in_use) + { + /* count must be positive unless random is used */ + if (!count && freq != L2CAP_FCR_FREQ_RANDOM) + { + L2CAP_TRACE_ERROR1 ("L2CA_SetupErtmTest (FAIL): Count = 0, freq = %d", freq); + return (cid); + } + + L2CAP_TRACE_ERROR5 ("L2CA_SetupErtmTest (START): cid 0x%04x, type %d, is_rx %d, freq %d, count %d", + cid, type, is_rx, freq, count); + + p_test_cb->cfg.in_use = TRUE; + p_test_cb->cfg.freq = freq; + p_test_cb->cfg.type = type; + p_test_cb->cfg.is_rx = is_rx; + p_test_cb->cfg.count = count; + } + else /* Test already in progress so ignore */ + { + L2CAP_TRACE_ERROR5 ("L2CA_SetupErtmTest (ignoring): cid 0x%04x, type %d, is_rx %d, freq %d, count %d", + cid, type, is_rx, freq, count); + } + + return (cid); +} + + +/******************************************************************************* +** The following routines are only used in conjunction with Conformance testing +********************************************************************************/ + +/******************************************************************************* +** +** Function L2CA_SendPolledSFrame +** +** Description This function is used for testing purposes only. +** It Sends a Polled RR or RNR to the peer +** +** Parameters +** cid - channel ID +** +** sup_type - (L2CAP_FCR_SUP_RR or L2CAP_FCR_SUP_RNR) +** +** Returns void +** +*******************************************************************************/ +void L2CA_SendPolledSFrame (UINT16 cid, UINT16 sup_type) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid (NULL, cid); + + if (p_ccb && (sup_type == L2CAP_FCR_SUP_RR || sup_type == L2CAP_FCR_SUP_RNR)) + { + l2c_fcr_send_S_frame (p_ccb, sup_type, L2CAP_FCR_P_BIT); + } + else + { + L2CAP_TRACE_ERROR2 ("L2CA_SendPolledSFrame(ERROR): sup_type %u, p_ccb 0x%07x", + sup_type, (UINT32)p_ccb); + } +} + +/******************************************************************************* +** +** Function l2c_bypass_sframe_packet +** +** Description This function is used for testing purposes only. +** It returns TRUE if S-Frame should be bypassed. +** +** Returns void +** +*******************************************************************************/ +static BOOLEAN l2c_bypass_sframe_packet (tL2C_CCB *p_ccb) +{ + if (p_ccb && p_ccb->fcrb.test_cb.cfm.in_use) + { + if (p_ccb->fcrb.test_cb.cfm.skip_sframe_count > 0) + { + L2CAP_TRACE_ERROR1 ("l2c_bypass_sframe_packet (count %d)", + p_ccb->fcrb.test_cb.cfm.skip_sframe_count); + + if (--p_ccb->fcrb.test_cb.cfm.skip_sframe_count == 0) + p_ccb->fcrb.test_cb.cfm.in_use = FALSE; + + /* Bypass sending S-Frame */ + return TRUE; + } + else + p_ccb->fcrb.test_cb.cfm.in_use = FALSE; + } + + return FALSE; +} + +/******************************************************************************* +** +** Function L2CA_BypassSFrame +** +** Description This function is used for testing purposes only. +** It skips sending 'count' S-Frames. +** +** Parameters +** cid - channel ID +** +** count - Number of S-Frames to skip sending +** +** Returns void +** +*******************************************************************************/ +void L2CA_BypassSFrame (UINT16 cid, UINT8 count) +{ + tL2C_CCB *p_ccb = l2cu_find_ccb_by_cid (NULL, cid); + tL2C_FCR_CFM_TEST_CB *p_test_cb; + + if (!p_ccb) + { + L2CAP_TRACE_WARNING1 ("L2CA_BypassSFrame(ERROR): no p_ccb (0x%07x)", (UINT32)p_ccb); + return; + } + + p_test_cb = &p_ccb->fcrb.test_cb.cfm; + + /* Initiate test if not active */ + if (!p_test_cb->in_use) + { + p_test_cb->in_use = TRUE; + p_test_cb->skip_sframe_count = count; + } + else + { + L2CAP_TRACE_WARNING0 ("L2CA_BypassSFrame(ERROR): already in use (ignoring...)"); + } +} + +#endif /* L2CAP_CORRUPT_ERTM_PKTS == TRUE */ + +#if (L2CAP_ERTM_STATS == TRUE) +/******************************************************************************* +** +** Function l2c_fcr_collect_ack_delay +** +** Description collect throughput, delay, queue size of waiting ack +** +** Parameters +** tL2C_CCB +** +** Returns void +** +*******************************************************************************/ +static void l2c_fcr_collect_ack_delay (tL2C_CCB *p_ccb, UINT8 num_bufs_acked) +{ + UINT32 index; + BT_HDR *p_buf; + UINT8 *p; + UINT32 timestamp, delay; + UINT8 xx; + UINT8 str[120]; + + index = p_ccb->fcrb.ack_delay_avg_index; + + /* update sum, max and min of waiting for ack queue size */ + p_ccb->fcrb.ack_q_count_avg[index] += p_ccb->fcrb.waiting_for_ack_q.count; + + if ( p_ccb->fcrb.waiting_for_ack_q.count > p_ccb->fcrb.ack_q_count_max[index] ) + p_ccb->fcrb.ack_q_count_max[index] = p_ccb->fcrb.waiting_for_ack_q.count; + + if ( p_ccb->fcrb.waiting_for_ack_q.count < p_ccb->fcrb.ack_q_count_min[index] ) + p_ccb->fcrb.ack_q_count_min[index] = p_ccb->fcrb.waiting_for_ack_q.count; + + /* update sum, max and min of round trip delay of acking */ + p_buf = (BT_HDR *)(p_ccb->fcrb.waiting_for_ack_q.p_first); + for (xx = 0; (xx < num_bufs_acked)&&(p_buf); xx++) + { + /* adding up length of acked I-frames to get throughput */ + p_ccb->fcrb.throughput[index] += p_buf->len - 8; + + if ( xx == num_bufs_acked - 1 ) + { + /* get timestamp from tx I-frame that receiver is acking */ + p = ((UINT8 *) (p_buf+1)) + p_buf->offset + p_buf->len; + if (p_ccb->bypass_fcs != L2CAP_BYPASS_FCS) + { + p += L2CAP_FCS_LEN; + } + + STREAM_TO_UINT32 (timestamp, p); + delay = GKI_get_os_tick_count() - timestamp; + + p_ccb->fcrb.ack_delay_avg[index] += delay; + if ( delay > p_ccb->fcrb.ack_delay_max[index] ) + p_ccb->fcrb.ack_delay_max[index] = delay; + if ( delay < p_ccb->fcrb.ack_delay_min[index] ) + p_ccb->fcrb.ack_delay_min[index] = delay; + } + + p_buf = GKI_getnext(p_buf); + } + + p_ccb->fcrb.ack_delay_avg_count++; + + /* calculate average and initialize next avg, min and max */ + if (p_ccb->fcrb.ack_delay_avg_count > L2CAP_ERTM_STATS_AVG_NUM_SAMPLES) + { + p_ccb->fcrb.ack_delay_avg_count = 0; + + p_ccb->fcrb.ack_q_count_avg[index] /= L2CAP_ERTM_STATS_AVG_NUM_SAMPLES; + p_ccb->fcrb.ack_delay_avg[index] /= L2CAP_ERTM_STATS_AVG_NUM_SAMPLES; + + /* calculate throughput */ + timestamp = GKI_get_os_tick_count(); + if (timestamp - p_ccb->fcrb.throughput_start > 0 ) + p_ccb->fcrb.throughput[index] /= (timestamp - p_ccb->fcrb.throughput_start); + + p_ccb->fcrb.throughput_start = timestamp; + + sprintf(str, "[%02u] throughput: %5u, ack_delay avg:%3u, min:%3u, max:%3u, ack_q_count avg:%3u, min:%3u, max:%3u", + index, p_ccb->fcrb.throughput[index], + p_ccb->fcrb.ack_delay_avg[index], p_ccb->fcrb.ack_delay_min[index], p_ccb->fcrb.ack_delay_max[index], + p_ccb->fcrb.ack_q_count_avg[index], p_ccb->fcrb.ack_q_count_min[index], p_ccb->fcrb.ack_q_count_max[index] ); + + BT_TRACE_1(TRACE_CTRL_GENERAL | TRACE_LAYER_GKI | TRACE_ORG_GKI , TRACE_TYPE_GENERIC, "%s", str); + + index = (index + 1) % L2CAP_ERTM_STATS_NUM_AVG; + p_ccb->fcrb.ack_delay_avg_index = index; + + p_ccb->fcrb.ack_q_count_max[index] = 0; + p_ccb->fcrb.ack_q_count_min[index] = 0xFFFFFFFF; + p_ccb->fcrb.ack_q_count_avg[index] = 0; + + + p_ccb->fcrb.ack_delay_max[index] = 0; + p_ccb->fcrb.ack_delay_min[index] = 0xFFFFFFFF; + p_ccb->fcrb.ack_delay_avg[index] = 0; + + p_ccb->fcrb.throughput[index] = 0; + } +} +#endif + + + diff --git a/stack/l2cap/l2c_int.h b/stack/l2cap/l2c_int.h new file mode 100644 index 0000000..6dd93b0 --- /dev/null +++ b/stack/l2cap/l2c_int.h @@ -0,0 +1,757 @@ +/***************************************************************************** +** +** Name: l2c_int.h +** +** Description: This file contains L2CAP internal definitions +** +** Copyright (c) 1999-2012, Broadcom Corp., All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ +#ifndef L2C_INT_H +#define L2C_INT_H + +#include "l2c_api.h" +#include "l2cdefs.h" +#include "gki.h" +#include "btm_api.h" + +#define L2CAP_MIN_MTU 48 /* Minimum acceptable MTU is 48 bytes */ + +/* Timeouts. Since L2CAP works off a 1-second list, all are in seconds. +*/ +#define L2CAP_LINK_ROLE_SWITCH_TOUT 10 /* 10 seconds */ +#define L2CAP_LINK_CONNECT_TOUT 60 /* 30 seconds */ +#define L2CAP_LINK_CONNECT_TOUT_EXT 120 /* 120 seconds */ +#define L2CAP_ECHO_RSP_TOUT 30 /* 30 seconds */ +#define L2CAP_LINK_FLOW_CONTROL_TOUT 2 /* 2 seconds */ +#define L2CAP_LINK_DISCONNECT_TOUT 30 /* 30 seconds */ + +#ifndef L2CAP_CHNL_CONNECT_TOUT /* BSA needs to override for internal project needs */ +#define L2CAP_CHNL_CONNECT_TOUT 60 /* 60 seconds */ +#endif + +#define L2CAP_CHNL_CONNECT_TOUT_EXT 120 /* 120 seconds */ +#define L2CAP_CHNL_CFG_TIMEOUT 30 /* 30 seconds */ +#define L2CAP_CHNL_DISCONNECT_TOUT 10 /* 10 seconds */ +#define L2CAP_DELAY_CHECK_SM4 2 /* 2 seconds */ +#define L2CAP_WAIT_INFO_RSP_TOUT 3 /* 3 seconds */ +#define L2CAP_WAIT_UNPARK_TOUT 2 /* 2 seconds */ +#define L2CAP_LINK_INFO_RESP_TOUT 2 /* 2 seconds */ +#define L2CAP_BLE_LINK_CONNECT_TOUT 30 /* 30 seconds */ + +/* quick timer uses millisecond unit */ +#define L2CAP_DEFAULT_RETRANS_TOUT 2000 /* 2000 milliseconds */ +#define L2CAP_DEFAULT_MONITOR_TOUT 12000 /* 12000 milliseconds */ +#define L2CAP_FCR_ACK_TOUT 200 /* 200 milliseconds */ + +/* Define the possible L2CAP channel states. The names of +** the states may seem a bit strange, but they are taken from +** the Bluetooth specification. +*/ +typedef enum +{ + CST_CLOSED, /* Channel is in clodes state */ + CST_ORIG_W4_SEC_COMP, /* Originator waits security clearence */ + CST_TERM_W4_SEC_COMP, /* Acceptor waits security clearence */ + CST_W4_L2CAP_CONNECT_RSP, /* Waiting for peer conenct response */ + CST_W4_L2CA_CONNECT_RSP, /* Waiting for upper layer connect rsp */ + CST_CONFIG, /* Negotiating configuration */ + CST_OPEN, /* Data transfer state */ + CST_W4_L2CAP_DISCONNECT_RSP, /* Waiting for peer disconnect rsp */ + CST_W4_L2CA_DISCONNECT_RSP /* Waiting for upper layer disc rsp */ +} tL2C_CHNL_STATE; + +/* Define the possible L2CAP link states +*/ +typedef enum +{ + LST_DISCONNECTED, + LST_CONNECT_HOLDING, + LST_CONNECTING_WAIT_SWITCH, + LST_CONNECTING, + LST_CONNECTED, + LST_DISCONNECTING +} tL2C_LINK_STATE; + + + +/* Define input events to the L2CAP link and channel state machines. The names +** of the events may seem a bit strange, but they are taken from +** the Bluetooth specification. +*/ +#define L2CEVT_LP_CONNECT_CFM 0 /* Lower layer connect confirm */ +#define L2CEVT_LP_CONNECT_CFM_NEG 1 /* Lower layer connect confirm (failed) */ +#define L2CEVT_LP_CONNECT_IND 2 /* Lower layer connect indication */ +#define L2CEVT_LP_DISCONNECT_IND 3 /* Lower layer disconnect indication */ +#define L2CEVT_LP_QOS_CFM 4 /* Lower layer QOS confirmation */ +#define L2CEVT_LP_QOS_CFM_NEG 5 /* Lower layer QOS confirmation (failed)*/ +#define L2CEVT_LP_QOS_VIOLATION_IND 6 /* Lower layer QOS violation indication */ + +#define L2CEVT_SEC_COMP 7 /* Security cleared successfully */ +#define L2CEVT_SEC_COMP_NEG 8 /* Security procedure failed */ + +#define L2CEVT_L2CAP_CONNECT_REQ 10 /* Peer connection request */ +#define L2CEVT_L2CAP_CONNECT_RSP 11 /* Peer connection response */ +#define L2CEVT_L2CAP_CONNECT_RSP_PND 12 /* Peer connection response pending */ +#define L2CEVT_L2CAP_CONNECT_RSP_NEG 13 /* Peer connection response (failed) */ +#define L2CEVT_L2CAP_CONFIG_REQ 14 /* Peer configuration request */ +#define L2CEVT_L2CAP_CONFIG_RSP 15 /* Peer configuration response */ +#define L2CEVT_L2CAP_CONFIG_RSP_NEG 16 /* Peer configuration response (failed) */ +#define L2CEVT_L2CAP_DISCONNECT_REQ 17 /* Peer disconnect request */ +#define L2CEVT_L2CAP_DISCONNECT_RSP 18 /* Peer disconnect response */ +#define L2CEVT_L2CAP_INFO_RSP 19 /* Peer information response */ +#define L2CEVT_L2CAP_DATA 20 /* Peer data */ + +#define L2CEVT_L2CA_CONNECT_REQ 21 /* Upper layer connect request */ +#define L2CEVT_L2CA_CONNECT_RSP 22 /* Upper layer connect response */ +#define L2CEVT_L2CA_CONNECT_RSP_NEG 23 /* Upper layer connect response (failed)*/ +#define L2CEVT_L2CA_CONFIG_REQ 24 /* Upper layer config request */ +#define L2CEVT_L2CA_CONFIG_RSP 25 /* Upper layer config response */ +#define L2CEVT_L2CA_CONFIG_RSP_NEG 26 /* Upper layer config response (failed) */ +#define L2CEVT_L2CA_DISCONNECT_REQ 27 /* Upper layer disconnect request */ +#define L2CEVT_L2CA_DISCONNECT_RSP 28 /* Upper layer disconnect response */ +#define L2CEVT_L2CA_DATA_READ 29 /* Upper layer data read */ +#define L2CEVT_L2CA_DATA_WRITE 30 /* Upper layer data write */ +#define L2CEVT_L2CA_FLUSH_REQ 31 /* Upper layer flush */ + +#define L2CEVT_TIMEOUT 32 /* Timeout */ +#define L2CEVT_SEC_RE_SEND_CMD 33 /* btm_sec has enough info to proceed */ + +#define L2CEVT_ACK_TIMEOUT 34 /* RR delay timeout */ + + +/* Bitmask to skip over Broadcom feature reserved (ID) to avoid sending two + successive ID values, '0' id only or both */ +#define L2CAP_ADJ_BRCM_ID 0x1 +#define L2CAP_ADJ_ZERO_ID 0x2 +#define L2CAP_ADJ_ID 0x3 + +/* Return values for l2cu_process_peer_cfg_req() */ +#define L2CAP_PEER_CFG_UNACCEPTABLE 0 +#define L2CAP_PEER_CFG_OK 1 +#define L2CAP_PEER_CFG_DISCONNECT 2 + +/* eL2CAP option constants */ +#define L2CAP_MIN_RETRANS_TOUT 2000 /* Min retransmission timeout if no flush timeout or PBF */ +#define L2CAP_MIN_MONITOR_TOUT 12000 /* Min monitor timeout if no flush timeout or PBF */ + +#define L2CAP_MAX_FCR_CFG_TRIES 2 /* Config attempts before disconnecting */ + +/* Only compiled in when in test mode. Production devices must not include +*/ +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE + +/* These are used for conformance and corruption testing only */ +typedef struct +{ + BOOLEAN in_use; /* TRUE if test in progress */ + UINT8 type; /* Type of test to run or turns off random test */ + UINT8 freq; /* One-shot or random */ + BOOLEAN is_rx; /* TRUE if incoming packets */ + UINT16 count; /* How many I-frames to drop in a row; used only with one-shot tests */ +} tL2C_FCR_TEST_CFG; + +typedef struct +{ + BOOLEAN in_use; /* TRUE if test in progress */ + UINT8 skip_sframe_count; /* Number of S-Frames to skip sending */ +} tL2C_FCR_CFM_TEST_CB; + +typedef struct +{ + tL2C_FCR_TEST_CFG cfg; /* Current corruption test configuration */ + tL2C_FCR_CFM_TEST_CB cfm; /* Conformance test structure */ +} tL2C_FCR_TEST_CB; + +#endif /* L2CAP_CORRUPT_ERTM_PKTS == TRUE */ + +typedef struct +{ + UINT8 next_tx_seq; /* Next sequence number to be Tx'ed */ + UINT8 last_rx_ack; /* Last sequence number ack'ed by the peer */ + UINT8 next_seq_expected; /* Next peer sequence number expected */ + UINT8 last_ack_sent; /* Last peer sequence number ack'ed */ + UINT8 num_tries; /* Number of retries to send a packet */ + UINT8 max_held_acks; /* Max acks we can hold before sending */ + + BOOLEAN remote_busy; /* TRUE if peer has flowed us off */ + BOOLEAN local_busy; /* TRUE if we have flowed off the peer */ + + BOOLEAN rej_sent; /* Reject was sent */ + BOOLEAN srej_sent; /* Selective Reject was sent */ + BOOLEAN wait_ack; /* Transmitter is waiting ack (poll sent) */ + BOOLEAN rej_after_srej; /* Send a REJ when SREJ clears */ + + BOOLEAN send_f_rsp; /* We need to send an F-bit response */ + + UINT16 rx_sdu_len; /* Length of the SDU being received */ + BT_HDR *p_rx_sdu; /* Buffer holding the SDU being received */ + BUFFER_Q waiting_for_ack_q; /* Buffers sent and waiting for peer to ack */ + BUFFER_Q srej_rcv_hold_q; /* Buffers rcvd but held pending SREJ rsp */ + BUFFER_Q retrans_q; /* Buffers being retransmitted */ + + TIMER_LIST_ENT ack_timer; /* Timer delaying RR */ + TIMER_LIST_ENT mon_retrans_timer; /* Timer Monitor or Retransmission */ + +#if (L2CAP_ERTM_STATS == TRUE) + UINT32 connect_tick_count; /* Time channel was established */ + UINT32 ertm_pkt_counts[2]; /* Packets sent and received */ + UINT32 ertm_byte_counts[2]; /* Bytes sent and received */ + UINT32 s_frames_sent[4]; /* S-frames sent (RR, REJ, RNR, SREJ) */ + UINT32 s_frames_rcvd[4]; /* S-frames rcvd (RR, REJ, RNR, SREJ) */ + UINT32 xmit_window_closed; /* # of times the xmit window was closed */ + UINT32 controller_idle; /* # of times less than 2 packets in controller */ + /* when the xmit window was closed */ + UINT32 pkts_retransmitted; /* # of packets that were retransmitted */ + UINT32 retrans_touts; /* # of retransmission timouts */ + UINT32 xmit_ack_touts; /* # of xmit ack timouts */ + +#define L2CAP_ERTM_STATS_NUM_AVG 10 +#define L2CAP_ERTM_STATS_AVG_NUM_SAMPLES 100 + UINT32 ack_delay_avg_count; + UINT32 ack_delay_avg_index; + UINT32 throughput_start; + UINT32 throughput[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_delay_avg[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_delay_min[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_delay_max[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_q_count_avg[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_q_count_min[L2CAP_ERTM_STATS_NUM_AVG]; + UINT32 ack_q_count_max[L2CAP_ERTM_STATS_NUM_AVG]; +#endif + +#if L2CAP_CORRUPT_ERTM_PKTS == TRUE + tL2C_FCR_TEST_CB test_cb; /* Used for SVT and UPF testing */ +#endif + +} tL2C_FCRB; + + +/* Define a registration control block. Every application (e.g. RFCOMM, SDP, +** TCS etc) that registers with L2CAP is assigned one of these. +*/ +#if (L2CAP_UCD_INCLUDED == TRUE) +#define L2C_UCD_RCB_ID 0x00 +#define L2C_UCD_STATE_UNUSED 0x00 +#define L2C_UCD_STATE_W4_DATA 0x01 +#define L2C_UCD_STATE_W4_RECEPTION 0x02 +#define L2C_UCD_STATE_W4_MTU 0x04 + +typedef struct +{ + UINT8 state; + tL2CAP_UCD_CB_INFO cb_info; +} tL2C_UCD_REG; +#endif + +typedef struct +{ + BOOLEAN in_use; + UINT16 psm; + UINT16 real_psm; /* This may be a dummy RCB for an o/b connection but */ + /* this is the real PSM that we need to connect to */ +#if (L2CAP_UCD_INCLUDED == TRUE) + tL2C_UCD_REG ucd; +#endif + + tL2CAP_APPL_INFO api; +} tL2C_RCB; + + +/* Define a channel control block (CCB). There may be many channel control blocks +** between the same two Bluetooth devices (i.e. on the same link). +** Each CCB has unique local and remote CIDs. All channel control blocks on +** the same physical link and are chained together. +*/ +typedef struct t_l2c_ccb +{ + BOOLEAN in_use; /* TRUE when in use, FALSE when not */ + tL2C_CHNL_STATE chnl_state; /* Channel state */ + + struct t_l2c_ccb *p_next_ccb; /* Next CCB in the chain */ + struct t_l2c_ccb *p_prev_ccb; /* Previous CCB in the chain */ + struct t_l2c_linkcb *p_lcb; /* Link this CCB is assigned to */ + + UINT16 local_cid; /* Local CID */ + UINT16 remote_cid; /* Remote CID */ + + TIMER_LIST_ENT timer_entry; /* CCB Timer List Entry */ + + tL2C_RCB *p_rcb; /* Registration CB for this Channel */ + +#define IB_CFG_DONE 0x01 +#define OB_CFG_DONE 0x02 +#define RECONFIG_FLAG 0x04 /* True after initial configuration */ +#define CFG_DONE_MASK (IB_CFG_DONE | OB_CFG_DONE) + + UINT8 config_done; /* Configuration flag word */ + UINT8 local_id; /* Transaction ID for local trans */ + UINT8 remote_id; /* Transaction ID for local */ + +#define CCB_FLAG_NO_RETRY 0x01 /* no more retry */ +#define CCB_FLAG_SENT_PENDING 0x02 /* already sent pending response */ + UINT8 flags; + + tL2CAP_CFG_INFO our_cfg; /* Our saved configuration options */ + tL2CAP_CH_CFG_BITS peer_cfg_bits; /* Store what peer wants to configure */ + tL2CAP_CFG_INFO peer_cfg; /* Peer's saved configuration options */ + + BUFFER_Q xmit_hold_q; /* Transmit data hold queue */ + + BOOLEAN cong_sent; /* Set when congested status sent */ + UINT16 buff_quota; /* Buffer quota before sending congestion */ + + tL2CAP_CHNL_PRIORITY ccb_priority; /* Channel priority */ + tL2CAP_CHNL_DATA_RATE tx_data_rate; /* Channel Tx data rate */ + tL2CAP_CHNL_DATA_RATE rx_data_rate; /* Channel Rx data rate */ + + /* Fields used for eL2CAP */ + tL2CAP_ERTM_INFO ertm_info; + tL2C_FCRB fcrb; + UINT16 tx_mps; /* TX MPS adjusted based on current controller */ + UINT16 max_rx_mtu; + UINT8 fcr_cfg_tries; /* Max number of negotiation attempts */ + BOOLEAN peer_cfg_already_rejected; /* If mode rejected once, set to TRUE */ + BOOLEAN out_cfg_fcr_present; /* TRUE if cfg response shoulkd include fcr options */ + +#define L2CAP_CFG_FCS_OUR 0x01 /* Our desired config FCS option */ +#define L2CAP_CFG_FCS_PEER 0x02 /* Peer's desired config FCS option */ +#define L2CAP_BYPASS_FCS (L2CAP_CFG_FCS_OUR | L2CAP_CFG_FCS_PEER) + UINT8 bypass_fcs; + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + BOOLEAN is_flushable; /* TRUE if channel is flushable */ +#endif + +#if (L2CAP_NUM_FIXED_CHNLS > 0) || (L2CAP_UCD_INCLUDED == TRUE) + UINT16 fixed_chnl_idle_tout; /* Idle timeout to use for the fixed channel */ +#endif + +} tL2C_CCB; + +/*********************************************************************** +** Define a queue of linked CCBs. +*/ +typedef struct +{ + tL2C_CCB *p_first_ccb; /* The first channel in this queue */ + tL2C_CCB *p_last_ccb; /* The last channel in this queue */ +} tL2C_CCB_Q; + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + +/* Round-Robin service for the same priority channels */ +#define L2CAP_NUM_CHNL_PRIORITY 3 /* Total number of priority group (high, medium, low)*/ +#define L2CAP_CHNL_PRIORITY_WEIGHT 5 /* weight per priority for burst transmission quota */ +#define L2CAP_GET_PRIORITY_QUOTA(pri) ((L2CAP_NUM_CHNL_PRIORITY - (pri)) * L2CAP_CHNL_PRIORITY_WEIGHT) + +/* CCBs within the same LCB are served in round robin with priority */ +/* It will make sure that low priority channel (for example, HF signaling on RFCOMM) */ +/* can be sent to headset even if higher priority channel (for example, AV media channel) */ +/* is congested. */ + +typedef struct +{ + tL2C_CCB *p_serve_ccb; /* current serving ccb within priority group */ + tL2C_CCB *p_first_ccb; /* first ccb of priority group */ + UINT8 num_ccb; /* number of channels in priority group */ + UINT8 quota; /* burst transmission quota */ +} tL2C_RR_SERV; + +#endif /* (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) */ + +/* Define a link control block. There is one link control block between +** this device and any other device (i.e. BD ADDR). +*/ +typedef struct t_l2c_linkcb +{ + BOOLEAN in_use; /* TRUE when in use, FALSE when not */ + tL2C_LINK_STATE link_state; + + TIMER_LIST_ENT timer_entry; /* Timer list entry for timeout evt */ + UINT16 handle; /* The handle used with LM */ + + tL2C_CCB_Q ccb_queue; /* Queue of CCBs on this LCB */ + + tL2C_CCB *p_pending_ccb; /* ccb of waiting channel during link disconnect */ + TIMER_LIST_ENT info_timer_entry; /* Timer entry for info resp timeout evt */ + BD_ADDR remote_bd_addr; /* The BD address of the remote */ + + UINT8 link_role; /* Master or slave */ + UINT8 id; + tL2CA_ECHO_RSP_CB *p_echo_rsp_cb; /* Echo response callback */ + UINT16 idle_timeout; /* Idle timeout */ + BOOLEAN is_bonding; /* True - link active only for bonding */ + + UINT16 link_flush_tout; /* Flush timeout used */ + + UINT16 link_xmit_quota; /* Num outstanding pkts allowed */ + UINT16 sent_not_acked; /* Num packets sent but not acked */ + + BOOLEAN partial_segment_being_sent; /* Set TRUE when a partial segment */ + /* is being sent. */ + BOOLEAN w4_info_rsp; /* TRUE when info request is active */ + UINT8 info_rx_bits; /* set 1 if received info type */ + UINT32 peer_ext_fea; /* Peer's extended features mask */ + BUFFER_Q link_xmit_data_q; /* Transmit data buffer queue */ + + UINT8 peer_chnl_mask[L2CAP_FIXED_CHNL_ARRAY_SIZE]; +#if (L2CAP_UCD_INCLUDED == TRUE) + UINT16 ucd_mtu; /* peer MTU on UCD */ + BUFFER_Q ucd_out_sec_pending_q; /* Security pending outgoing UCD packet */ + BUFFER_Q ucd_in_sec_pending_q; /* Security pending incoming UCD packet */ +#endif + +#if (L2CAP_HOST_FLOW_CTRL == TRUE) + UINT16 link_pkts_unacked; /* Packets received but not acked */ + UINT16 link_ack_thresh; /* Threshold at which to ack pkts */ +#endif + + BT_HDR *p_hcit_rcv_acl; /* Current HCIT ACL buf being rcvd */ + UINT16 idle_timeout_sv; /* Save current Idle timeout */ + UINT8 acl_priority; /* L2C_PRIORITY_NORMAL or L2C_PRIORITY_HIGH */ + tL2CA_NOCP_CB *p_nocp_cb; /* Num Cmpl pkts callback */ + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + tL2C_CCB *p_fixed_ccbs[L2CAP_NUM_FIXED_CHNLS]; + UINT16 disc_reason; +#endif + +#if (BLE_INCLUDED == TRUE) + BOOLEAN is_ble_link; + tBLE_ADDR_TYPE ble_addr_type; + +#define UPD_ENABLED 0 /* If peer requests update, we will change params */ +#define UPD_DISABLED 1 /* application requested not to update */ +#define UPD_PENDING 2 /* while updates are disabled, peer requested new parameters */ +#define UPD_UPDATED 3 /* peer updated connection parameters */ + UINT8 upd_disabled; + + UINT16 min_interval; /* parameters as requested by peripheral */ + UINT16 max_interval; + UINT16 latency; + UINT16 timeout; + +#endif + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* each priority group is limited burst transmission */ + /* round robin service for the same priority channels */ + tL2C_RR_SERV rr_serv[L2CAP_NUM_CHNL_PRIORITY]; + UINT8 rr_pri; /* current serving priority group */ +#endif + +} tL2C_LCB; + +/* Define the L2CAP control structure +*/ +typedef struct +{ + UINT8 l2cap_trace_level; + UINT16 controller_xmit_window; /* Total ACL window for all links */ + + UINT16 round_robin_quota; /* Round-robin link quota */ + UINT16 round_robin_unacked; /* Round-robin unacked */ + BOOLEAN check_round_robin; /* Do a round robin check */ + + BOOLEAN is_cong_cback_context; + + tL2C_LCB lcb_pool[MAX_L2CAP_LINKS]; /* Link Control Block pool */ + tL2C_CCB ccb_pool[MAX_L2CAP_CHANNELS]; /* Channel Control Block pool */ + tL2C_RCB rcb_pool[MAX_L2CAP_CLIENTS]; /* Registration info pool */ + + tL2C_CCB *p_free_ccb_first; /* Pointer to first free CCB */ + tL2C_CCB *p_free_ccb_last; /* Pointer to last free CCB */ + + UINT8 desire_role; /* desire to be master/slave when accepting a connection */ + BOOLEAN disallow_switch; /* FALSE, to allow switch at create conn */ + UINT16 num_lm_acl_bufs; /* # of ACL buffers on controller */ + UINT16 idle_timeout; /* Idle timeout */ + + BUFFER_Q rcv_hold_q; /* Recv pending queue */ + TIMER_LIST_ENT rcv_hold_tle; /* Timer list entry for rcv hold */ + + tL2C_LCB *p_cur_hcit_lcb; /* Current HCI Transport buffer */ + UINT16 num_links_active; /* Number of links active */ + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + UINT16 non_flushable_pbf; /* L2CAP_PKT_START_NON_FLUSHABLE if controller supports */ + /* Otherwise, L2CAP_PKT_START */ + BOOLEAN is_flush_active; /* TRUE if an HCI_Enhanced_Flush has been sent */ +#endif + +#if L2CAP_CONFORMANCE_TESTING == TRUE + UINT32 test_info_resp; /* Conformance testing needs a dynamic response */ +#endif + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + tL2CAP_FIXED_CHNL_REG fixed_reg[L2CAP_NUM_FIXED_CHNLS]; /* Reg info for fixed channels */ +#endif + +#if (BLE_INCLUDED == TRUE) + BOOLEAN is_ble_connecting; + BD_ADDR ble_connecting_bda; + UINT16 controller_le_xmit_window; /* Total ACL window for all links */ + UINT16 num_lm_ble_bufs; /* # of ACL buffers on controller */ +#endif + + tL2CA_ECHO_DATA_CB *p_echo_data_cb; /* Echo data callback */ + +#if (defined(L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE) && (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == TRUE)) + UINT16 high_pri_min_xmit_quota; /* Minimum number of ACL credit for high priority link */ +#endif /* (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == TRUE) */ + + UINT16 dyn_psm; +} tL2C_CB; + + + +/* Define a structure that contains the information about a connection. +** This structure is used to pass between functions, and not all the +** fields will always be filled in. +*/ +typedef struct +{ + BD_ADDR bd_addr; /* Remote BD address */ + UINT8 status; /* Connection status */ + UINT16 psm; /* PSM of the connection */ + UINT16 l2cap_result; /* L2CAP result */ + UINT16 l2cap_status; /* L2CAP status */ + UINT16 remote_cid; /* Remote CID */ +} tL2C_CONN_INFO; + + +typedef void (tL2C_FCR_MGMT_EVT_HDLR) (UINT8, tL2C_CCB *); + +/* The offset in a buffer that L2CAP will use when building commands. +*/ +#define L2CAP_SEND_CMD_OFFSET 0 + + +/* Number of ACL buffers to use for high priority channel +*/ +#if (!defined(L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE) || (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == FALSE)) +#define L2CAP_HIGH_PRI_MIN_XMIT_QUOTA_A (L2CAP_HIGH_PRI_MIN_XMIT_QUOTA) +#else +#define L2CAP_HIGH_PRI_MIN_XMIT_QUOTA_A (l2cb.high_pri_min_xmit_quota) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* L2CAP global data +************************************ +*/ +#if (!defined L2C_DYNAMIC_MEMORY) || (L2C_DYNAMIC_MEMORY == FALSE) +L2C_API extern tL2C_CB l2cb; +#else +L2C_API extern tL2C_CB *l2c_cb_ptr; +#define l2cb (*l2c_cb_ptr) +#endif + + +/* Functions provided by l2c_main.c +************************************ +*/ +extern void l2c_init (void); +extern void l2c_process_timeout (TIMER_LIST_ENT *p_tle); +extern UINT8 l2c_data_write (UINT16 cid, BT_HDR *p_data, UINT16 flag); +extern void l2c_rcv_acl_data (BT_HDR *p_msg); +extern void l2c_process_held_packets (BOOLEAN timed_out); + +/* Functions provided by l2c_utils.c +************************************ +*/ +extern tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding); +extern BOOLEAN l2cu_start_post_bond_timer (UINT16 handle); +extern void l2cu_release_lcb (tL2C_LCB *p_lcb); +extern tL2C_LCB *l2cu_find_lcb_by_bd_addr (BD_ADDR p_bd_addr); +extern tL2C_LCB *l2cu_find_lcb_by_handle (UINT16 handle); +extern void l2cu_update_lcb_4_bonding (BD_ADDR p_bd_addr, BOOLEAN is_bonding); + +extern UINT8 l2cu_get_conn_role (tL2C_LCB *p_this_lcb); +extern BOOLEAN l2cu_set_acl_priority (BD_ADDR bd_addr, UINT8 priority, BOOLEAN reset_after_rs); + +extern void l2cu_enqueue_ccb (tL2C_CCB *p_ccb); +extern void l2cu_dequeue_ccb (tL2C_CCB *p_ccb); +extern void l2cu_change_pri_ccb (tL2C_CCB *p_ccb, tL2CAP_CHNL_PRIORITY priority); + +extern tL2C_CCB *l2cu_allocate_ccb (tL2C_LCB *p_lcb, UINT16 cid); +extern void l2cu_release_ccb (tL2C_CCB *p_ccb); +extern tL2C_CCB *l2cu_find_ccb_by_cid (tL2C_LCB *p_lcb, UINT16 local_cid); +extern tL2C_CCB *l2cu_find_ccb_by_remote_cid (tL2C_LCB *p_lcb, UINT16 remote_cid); +extern void l2cu_adj_id (tL2C_LCB *p_lcb, UINT8 adj_mask); + +extern void l2cu_send_peer_cmd_reject (tL2C_LCB *p_lcb, UINT16 reason, + UINT8 rem_id,UINT16 p1, UINT16 p2); +extern void l2cu_send_peer_connect_req (tL2C_CCB *p_ccb); +extern void l2cu_send_peer_connect_rsp (tL2C_CCB *p_ccb, UINT16 result, UINT16 status); +extern void l2cu_send_peer_config_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_send_peer_config_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_send_peer_config_rej (tL2C_CCB *p_ccb, UINT8 *p_data, UINT16 data_len, UINT16 rej_len); +extern void l2cu_send_peer_disc_req (tL2C_CCB *p_ccb); +extern void l2cu_send_peer_disc_rsp (tL2C_LCB *p_lcb, UINT8 remote_id, UINT16 local_cid, UINT16 remote_cid); +extern void l2cu_send_peer_echo_req (tL2C_LCB *p_lcb, UINT8 *p_data, UINT16 data_len); +extern void l2cu_send_peer_echo_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len); +extern void l2cu_send_peer_info_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT16 info_type); +extern void l2cu_reject_connection (tL2C_LCB *p_lcb, UINT16 remote_cid, UINT8 rem_id, UINT16 result); +extern void l2cu_send_peer_info_req (tL2C_LCB *p_lcb, UINT16 info_type); +extern void l2cu_set_acl_hci_header (BT_HDR *p_buf, tL2C_CCB *p_ccb); +extern void l2cu_check_channel_congestion (tL2C_CCB *p_ccb); +extern void l2cu_disconnect_chnl (tL2C_CCB *p_ccb); + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) +extern void l2cu_set_non_flushable_pbf(BOOLEAN); +#endif + +#if (BLE_INCLUDED == TRUE) +extern void l2cu_send_peer_ble_par_req (tL2C_LCB *p_lcb, UINT16 min_int, UINT16 max_int, UINT16 latency, UINT16 timeout); +extern void l2cu_send_peer_ble_par_rsp (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id); +#endif + +extern BOOLEAN l2cu_initialize_fixed_ccb (tL2C_LCB *p_lcb, UINT16 fixed_cid, tL2CAP_FCR_OPTS *p_fcr); +extern void l2cu_no_dynamic_ccbs (tL2C_LCB *p_lcb); +extern void l2cu_process_fixed_chnl_resp (tL2C_LCB *p_lcb); + +/* Functions provided by l2c_ucd.c +************************************ +*/ +#if (L2CAP_UCD_INCLUDED == TRUE) +void l2c_ucd_delete_sec_pending_q(tL2C_LCB *p_lcb); +void l2c_ucd_enqueue_pending_out_sec_q(tL2C_CCB *p_ccb, void *p_data); +BOOLEAN l2c_ucd_check_pending_info_req(tL2C_CCB *p_ccb); +BOOLEAN l2c_ucd_check_pending_out_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_send_pending_out_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_discard_pending_out_sec_q(tL2C_CCB *p_ccb); +BOOLEAN l2c_ucd_check_pending_in_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_send_pending_in_sec_q(tL2C_CCB *p_ccb); +void l2c_ucd_discard_pending_in_sec_q(tL2C_CCB *p_ccb); +BOOLEAN l2c_ucd_check_rx_pkts(tL2C_LCB *p_lcb, BT_HDR *p_msg); +BOOLEAN l2c_ucd_process_event(tL2C_CCB *p_ccb, UINT16 event, void *p_data); +#endif + +#if (BLE_INCLUDED == TRUE) +extern void l2cu_send_peer_ble_par_req (tL2C_LCB *p_lcb, UINT16 min_int, UINT16 max_int, UINT16 latency, UINT16 timeout); +extern void l2cu_send_peer_ble_par_rsp (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id); +#endif + +extern BOOLEAN l2cu_initialize_fixed_ccb (tL2C_LCB *p_lcb, UINT16 fixed_cid, tL2CAP_FCR_OPTS *p_fcr); +extern void l2cu_no_dynamic_ccbs (tL2C_LCB *p_lcb); +extern void l2cu_process_fixed_chnl_resp (tL2C_LCB *p_lcb); + + +/* Functions provided for Broadcom Aware +**************************************** +*/ +extern BOOLEAN l2cu_check_feature_req (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len); +extern void l2cu_check_feature_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len); +extern void l2cu_send_feature_req (tL2C_CCB *p_ccb); + +extern tL2C_RCB *l2cu_allocate_rcb (UINT16 psm); +extern tL2C_RCB *l2cu_find_rcb_by_psm (UINT16 psm); +extern void l2cu_release_rcb (tL2C_RCB *p_rcb); + +extern UINT8 l2cu_process_peer_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_process_peer_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_process_our_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2cu_process_our_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); + +extern void l2cu_device_reset (void); +extern tL2C_LCB *l2cu_find_lcb_by_state (tL2C_LINK_STATE state); +extern BOOLEAN l2cu_lcb_disconnecting (void); + +extern BOOLEAN l2cu_create_conn (tL2C_LCB *p_lcb); +extern BOOLEAN l2cu_create_conn_after_switch (tL2C_LCB *p_lcb); +extern BT_HDR *l2cu_get_next_buffer_to_send (tL2C_LCB *p_lcb); +extern void l2cu_resubmit_pending_sec_req (BD_ADDR p_bda); +extern void l2cu_initialize_amp_ccb (tL2C_LCB *p_lcb); +extern void l2cu_adjust_out_mps (tL2C_CCB *p_ccb); + +/* Functions provided by l2c_link.c +************************************ +*/ +extern BOOLEAN l2c_link_hci_conn_req (BD_ADDR bd_addr); +extern BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda); +extern BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason); +extern BOOLEAN l2c_link_hci_qos_violation (UINT16 handle); +extern void l2c_link_timeout (tL2C_LCB *p_lcb); +extern void l2c_info_timeout (tL2C_LCB *p_lcb); +extern void l2c_link_check_send_pkts (tL2C_LCB *p_lcb, tL2C_CCB *p_ccb, BT_HDR *p_buf); +extern void l2c_link_adjust_allocation (void); +extern void l2c_link_process_num_completed_pkts (UINT8 *p); +extern void l2c_link_process_num_completed_blocks (UINT8 controller_id, UINT8 *p, UINT16 evt_len); +extern void l2c_link_processs_num_bufs (UINT16 num_lm_acl_bufs); +extern UINT8 l2c_link_pkts_rcvd (UINT16 *num_pkts, UINT16 *handles); +extern void l2c_link_role_changed (BD_ADDR bd_addr, UINT8 new_role, UINT8 hci_status); +extern void l2c_link_sec_comp (BD_ADDR p_bda, void *p_ref_data, UINT8 status); +extern void l2c_link_segments_xmitted (BT_HDR *p_msg); +extern void l2c_pin_code_request (BD_ADDR bd_addr); +extern void l2c_link_adjust_chnl_allocation (void); + +#if (BLE_INCLUDED == TRUE) +extern void l2c_link_processs_ble_num_bufs (UINT16 num_lm_acl_bufs); +#endif + +#if ((BTM_PWR_MGR_INCLUDED == TRUE) && L2CAP_WAKE_PARKED_LINK == TRUE) +extern BOOLEAN l2c_link_check_power_mode ( tL2C_LCB *p_lcb ); +#define L2C_LINK_CHECK_POWER_MODE(x) l2c_link_check_power_mode ((x)) +#else +#define L2C_LINK_CHECK_POWER_MODE(x) (FALSE) +#endif + +#if L2CAP_CONFORMANCE_TESTING == TRUE +/* Used only for conformance testing */ +L2C_API extern void l2cu_set_info_rsp_mask (UINT32 mask); +#endif + +/* Functions provided by l2c_csm.c +************************************ +*/ +extern void l2c_csm_execute (tL2C_CCB *p_ccb, UINT16 event, void *p_data); + +L2C_API extern BT_HDR *l2cap_link_chk_pkt_start(BT_HDR *); /* Called at start of rcv to check L2CAP segmentation */ +L2C_API extern BOOLEAN l2cap_link_chk_pkt_end (void); /* Called at end of rcv to check L2CAP segmentation */ + +L2C_API extern void l2c_enqueue_peer_data (tL2C_CCB *p_ccb, BT_HDR *p_buf); + + +/* Functions provided by l2c_fcr.c +************************************ +*/ +extern void l2c_fcr_cleanup (tL2C_CCB *p_ccb); +extern void l2c_fcr_proc_pdu (tL2C_CCB *p_ccb, BT_HDR *p_buf); +extern void l2c_fcr_proc_tout (tL2C_CCB *p_ccb); +extern void l2c_fcr_proc_ack_tout (tL2C_CCB *p_ccb); +extern void l2c_fcr_send_S_frame (tL2C_CCB *p_ccb, UINT16 function_code, UINT16 pf_bit); +extern BT_HDR *l2c_fcr_clone_buf (BT_HDR *p_buf, UINT16 new_offset, UINT16 no_of_bytes, UINT8 pool); +extern BOOLEAN l2c_fcr_is_flow_controlled (tL2C_CCB *p_ccb); +extern BT_HDR *l2c_fcr_get_next_xmit_sdu_seg (tL2C_CCB *p_ccb, UINT16 max_packet_length); +extern void l2c_fcr_start_timer (tL2C_CCB *p_ccb); + +/* Configuration negotiation */ +extern UINT8 l2c_fcr_chk_chan_modes (tL2C_CCB *p_ccb); +extern BOOLEAN l2c_fcr_adj_our_req_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2c_fcr_adj_our_rsp_options (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_peer_cfg); +extern BOOLEAN l2c_fcr_renegotiate_chan(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern UINT8 l2c_fcr_process_peer_cfg_req(tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg); +extern void l2c_fcr_adj_monitor_retran_timeout (tL2C_CCB *p_ccb); +extern void l2c_fcr_stop_timer (tL2C_CCB *p_ccb); + +/* Functions provided by l2c_ble.c +************************************ +*/ +#if (BLE_INCLUDED == TRUE) +extern BOOLEAN l2cble_create_conn (tL2C_LCB *p_lcb); +extern void l2cble_process_sig_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len); +extern void l2cble_conn_comp (UINT16 handle, UINT8 role, BD_ADDR bda, tBLE_ADDR_TYPE type, + UINT16 conn_interval, UINT16 conn_latency, UINT16 conn_timeout); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/stack/l2cap/l2c_link.c b/stack/l2cap/l2c_link.c new file mode 100644 index 0000000..d0899aa --- /dev/null +++ b/stack/l2cap/l2c_link.c @@ -0,0 +1,1703 @@ +/***************************************************************************** +** +** Name: l2c_link.c +** +** Description: this file contains the functions relating to link +** management. A "link" is a connection between this device +** and another device. Only ACL links are managed. +** +** +** Copyright (c) 1999-2011, Broadcom Corp, All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "gki.h" +#include "bt_types.h" +#include "hcimsgs.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "l2c_api.h" +#include "btu.h" +#include "btm_api.h" +#include "btm_int.h" + +static BOOLEAN l2c_link_send_to_lower (tL2C_LCB *p_lcb, BT_HDR *p_buf); + +#define L2C_LINK_SEND_ACL_DATA(x) HCI_ACL_DATA_TO_LOWER((x)) + +#if (BLE_INCLUDED == TRUE) +#define L2C_LINK_SEND_BLE_ACL_DATA(x) HCI_BLE_ACL_DATA_TO_LOWER((x)) +#endif + +/******************************************************************************* +** +** Function l2c_link_hci_conn_req +** +** Description This function is called when an HCI Connection Request +** event is received. +** +** Returns TRUE, if accept conn +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_conn_req (BD_ADDR bd_addr) +{ + tL2C_LCB *p_lcb; + tL2C_LCB *p_lcb_cur; + int xx; + BOOLEAN no_links; + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr); + + /* If we don't have one, create one and accept the connection. */ + if (!p_lcb) + { + p_lcb = l2cu_allocate_lcb (bd_addr, FALSE); + if (!p_lcb) + { + btsnd_hcic_reject_conn (bd_addr, HCI_ERR_HOST_REJECT_RESOURCES); + L2CAP_TRACE_ERROR0 ("L2CAP failed to allocate LCB"); + return FALSE; + } + + no_links = TRUE; + + /* If we already have connection, accept as a master */ + for (xx = 0, p_lcb_cur = &l2cb.lcb_pool[0]; xx < MAX_L2CAP_LINKS; xx++, p_lcb_cur++) + { + if (p_lcb_cur == p_lcb) + continue; + + if (p_lcb_cur->in_use) + { + no_links = FALSE; + p_lcb->link_role = HCI_ROLE_MASTER; + break; + } + } + + if (no_links) + { + if (!btm_dev_support_switch (bd_addr)) + p_lcb->link_role = HCI_ROLE_SLAVE; + else + p_lcb->link_role = l2cu_get_conn_role(p_lcb); + } + + /* Tell the other side we accept the connection */ + btsnd_hcic_accept_conn (bd_addr, p_lcb->link_role); + + p_lcb->link_state = LST_CONNECTING; + + /* Start a timer waiting for connect complete */ + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_CONNECT_TOUT); + return (TRUE); + } + + /* We already had a link control block to the guy. Check what state it is in */ + if ((p_lcb->link_state == LST_CONNECTING) || (p_lcb->link_state == LST_CONNECT_HOLDING)) + { + /* Connection collision. Accept the connection anyways. */ + + if (!btm_dev_support_switch (bd_addr)) + p_lcb->link_role = HCI_ROLE_SLAVE; + else + p_lcb->link_role = l2cu_get_conn_role(p_lcb); + + btsnd_hcic_accept_conn (bd_addr, p_lcb->link_role); + + p_lcb->link_state = LST_CONNECTING; + return (TRUE); + } + else if (p_lcb->link_state == LST_DISCONNECTING) + { + /* In disconnecting state, reject the connection. */ + btsnd_hcic_reject_conn (bd_addr, HCI_ERR_HOST_REJECT_DEVICE); + } + else + { + L2CAP_TRACE_ERROR0 ("L2CAP got conn_req while connected"); + } + return (FALSE); +} + +/******************************************************************************* +** +** Function l2c_link_hci_conn_comp +** +** Description This function is called when an HCI Connection Complete +** event is received. +** +** Returns void +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_conn_comp (UINT8 status, UINT16 handle, BD_ADDR p_bda) +{ + tL2C_CONN_INFO ci; + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tBTM_SEC_DEV_REC *p_dev_info = NULL; + +#if (defined(BTM_BUSY_LEVEL_CHANGE_INCLUDED) && BTM_BUSY_LEVEL_CHANGE_INCLUDED == TRUE) + btm_acl_update_busy_level (BTM_BLI_PAGE_DONE_EVT); +#endif + + /* Save the parameters */ + ci.status = status; + memcpy (ci.bd_addr, p_bda, BD_ADDR_LEN); + + /* See if we have a link control block for the remote device */ + p_lcb = l2cu_find_lcb_by_bd_addr (ci.bd_addr); + + /* If we don't have one, this is an error */ + if (!p_lcb) + { + L2CAP_TRACE_WARNING0 ("L2CAP got conn_comp for unknown BD_ADDR"); + return (FALSE); + } + + if (p_lcb->link_state != LST_CONNECTING) + { + L2CAP_TRACE_ERROR2 ("L2CAP got conn_comp in bad state: %d status: 0x%d", p_lcb->link_state, status); + + if (status != HCI_SUCCESS) + l2c_link_hci_disc_comp (p_lcb->handle, status); + + return (FALSE); + } + + /* Save the handle */ + p_lcb->handle = handle; + + if (ci.status == HCI_SUCCESS) + { + /* Connected OK. Change state to connected */ + p_lcb->link_state = LST_CONNECTED; + + /* Get the peer information if the l2cap flow-control/rtrans is supported */ + l2cu_send_peer_info_req (p_lcb, L2CAP_EXTENDED_FEATURES_INFO_TYPE); + + /* Tell BTM Acl management about the link */ + if ((p_dev_info = btm_find_dev (p_bda)) != NULL) + btm_acl_created (ci.bd_addr, p_dev_info->dev_class, + p_dev_info->sec_bd_name, handle, + p_lcb->link_role, FALSE); + else + btm_acl_created (ci.bd_addr, NULL, NULL, handle, p_lcb->link_role, FALSE); + + /* If dedicated bonding do not process any further */ + if (p_lcb->is_bonding) + { + if (l2cu_start_post_bond_timer(handle)) + return (TRUE); + } + + /* Update the timeouts in the hold queue */ + l2c_process_held_packets(FALSE); + + btu_stop_timer (&p_lcb->timer_entry); + + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + l2c_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM, &ci); + } + + if (p_lcb->p_echo_rsp_cb) + { + l2cu_send_peer_echo_req (p_lcb, NULL, 0); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_ECHO_RSP_TOUT); + } + else if (!p_lcb->ccb_queue.p_first_ccb) + { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_STARTUP_TOUT); + } + } + /* Max number of acl connections. */ + /* If there's an lcb disconnecting set this one to holding */ + else if ((ci.status == HCI_ERR_MAX_NUM_OF_CONNECTIONS) && l2cu_lcb_disconnecting()) + { + p_lcb->link_state = LST_CONNECT_HOLDING; + p_lcb->handle = HCI_INVALID_HANDLE; + } + else + { + /* Just in case app decides to try again in the callback context */ + p_lcb->link_state = LST_DISCONNECTING; + + /* Connection failed. For all channels, send the event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) + { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + l2c_csm_execute (p_ccb, L2CEVT_LP_CONNECT_CFM_NEG, &ci); + + p_ccb = pn; + } + + p_lcb->disc_reason = status; + /* Release the LCB */ + if (p_lcb->ccb_queue.p_first_ccb == NULL) + l2cu_release_lcb (p_lcb); + else /* there are any CCBs remaining */ + { + if (ci.status == HCI_ERR_CONNECTION_EXISTS) + { + /* we are in collision situation, wait for connecttion request from controller */ + p_lcb->link_state = LST_CONNECTING; + } + else + { + l2cu_create_conn(p_lcb); + } + } + } + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2c_link_sec_comp +** +** Description This function is called when required security procedures +** are completed. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_sec_comp (BD_ADDR p_bda, void *p_ref_data, UINT8 status) +{ + tL2C_CONN_INFO ci; + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_CCB *p_next_ccb; + UINT8 event; + + L2CAP_TRACE_DEBUG2 ("l2c_link_sec_comp: %d, 0x%x", status, p_ref_data); + + if (status == BTM_SUCCESS_NO_SECURITY) + status = BTM_SUCCESS; + + /* Save the parameters */ + ci.status = status; + memcpy (ci.bd_addr, p_bda, BD_ADDR_LEN); + + p_lcb = l2cu_find_lcb_by_bd_addr (p_bda); + + /* If we don't have one, this is an error */ + if (!p_lcb) + { + L2CAP_TRACE_WARNING0 ("L2CAP got sec_comp for unknown BD_ADDR"); + return; + } + + /* Match p_ccb with p_ref_data returned by sec manager */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_next_ccb) + { + p_next_ccb = p_ccb->p_next_ccb; + + if (p_ccb == p_ref_data) + { + switch(status) + { + case BTM_SUCCESS: + L2CAP_TRACE_DEBUG1 ("ccb timer ticks: %u", p_ccb->timer_entry.ticks); + event = L2CEVT_SEC_COMP; + break; + + case BTM_DELAY_CHECK: + /* start a timer - encryption change not received before L2CAP connect req */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_DELAY_CHECK_SM4); + return; + + default: + event = L2CEVT_SEC_COMP_NEG; + } + l2c_csm_execute (p_ccb, event, &ci); + break; + } + } +} + + +/******************************************************************************* +** +** Function l2c_link_hci_disc_comp +** +** Description This function is called when an HCI Disconnect Complete +** event is received. +** +** Returns TRUE if the link is known about, else FALSE +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_disc_comp (UINT16 handle, UINT8 reason) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + BOOLEAN status = TRUE; + BOOLEAN lcb_is_free = TRUE; + + /* See if we have a link control block for the connection */ + p_lcb = l2cu_find_lcb_by_handle (handle); + + /* If we don't have one, maybe an SCO link. Send to MM */ + if (!p_lcb) + { + status = FALSE; + } + else + { + /* There can be a case when we rejected PIN code authentication */ + /* otherwise save a new reason */ + if (btm_cb.acl_disc_reason != HCI_ERR_HOST_REJECT_SECURITY) + btm_cb.acl_disc_reason = reason; + + p_lcb->disc_reason = btm_cb.acl_disc_reason; + + /* Just in case app decides to try again in the callback context */ + p_lcb->link_state = LST_DISCONNECTING; + + /* Link is disconnected. For all channels, send the event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) + { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + /* Keep connect pending control block (if exists) + * Possible Race condition when a reconnect occurs + * on the channel during a disconnect of link. This + * ccb will be automatically retried after link disconnect + * arrives + */ + if (p_ccb != p_lcb->p_pending_ccb) + { + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, &reason); + } + p_ccb = pn; + } + +#if BTM_SCO_INCLUDED == TRUE + /* Tell SCO management to drop any SCOs on this ACL */ + btm_sco_acl_removed (p_lcb->remote_bd_addr); +#endif + + /* If waiting for disconnect and reconnect is pending start the reconnect now + race condition where layer above issued connect request on link that was + disconnecting + */ + if (p_lcb->ccb_queue.p_first_ccb != NULL) + { +#if (L2CAP_NUM_FIXED_CHNLS > 0) + /* If we are going to re-use the LCB without dropping it, release all fixed channels here */ + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) + { + if (p_lcb->p_fixed_ccbs[xx]) + { + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason); + l2cu_release_ccb (p_lcb->p_fixed_ccbs[xx]); + + p_lcb->p_fixed_ccbs[xx] = NULL; + } + } +#endif + L2CAP_TRACE_DEBUG0("l2c_link_hci_disc_comp: Restarting pending ACL request"); + + if (l2cu_create_conn(p_lcb)) + lcb_is_free = FALSE; /* still using this lcb */ + } + + p_lcb->p_pending_ccb = NULL; + + /* Release the LCB */ + if (lcb_is_free) + l2cu_release_lcb (p_lcb); + } + + /* Now that we have a free acl connection, see if any lcbs are pending */ + if (lcb_is_free && ((p_lcb = l2cu_find_lcb_by_state(LST_CONNECT_HOLDING)) != NULL)) + { + /* we found one-- create a connection */ + l2cu_create_conn(p_lcb); + } + + return status; +} + + +/******************************************************************************* +** +** Function l2c_link_hci_qos_violation +** +** Description This function is called when an HCI QOS Violation +** event is received. +** +** Returns TRUE if the link is known about, else FALSE +** +*******************************************************************************/ +BOOLEAN l2c_link_hci_qos_violation (UINT16 handle) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + /* See if we have a link control block for the connection */ + p_lcb = l2cu_find_lcb_by_handle (handle); + + /* If we don't have one, maybe an SCO link. */ + if (!p_lcb) + return (FALSE); + + /* For all channels, tell the upper layer about it */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + if (p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb) + l2c_csm_execute (p_ccb, L2CEVT_LP_QOS_VIOLATION_IND, NULL); + } + + return (TRUE); +} + + + +/******************************************************************************* +** +** Function l2c_link_timeout +** +** Description This function is called when a link timer expires +** +** Returns void +** +*******************************************************************************/ +void l2c_link_timeout (tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + UINT16 timeout; + tBTM_STATUS rc; + + L2CAP_TRACE_EVENT3 ("L2CAP - l2c_link_timeout() link state %d first CCB %p is_bonding:%d", + p_lcb->link_state, p_lcb->ccb_queue.p_first_ccb, p_lcb->is_bonding); + + /* If link was connecting or disconnecting, clear all channels and drop the LCB */ + if ((p_lcb->link_state == LST_CONNECTING_WAIT_SWITCH) || + (p_lcb->link_state == LST_CONNECTING) || + (p_lcb->link_state == LST_CONNECT_HOLDING) || + (p_lcb->link_state == LST_DISCONNECTING)) + { + p_lcb->p_pending_ccb = NULL; + + /* For all channels, send a disconnect indication event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) + { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL); + + p_ccb = pn; + } +#if (BLE_INCLUDED == TRUE) + if (p_lcb->link_state == LST_CONNECTING && + l2cb.is_ble_connecting == TRUE) + { + L2CA_CancelBleConnectReq(l2cb.ble_connecting_bda); + } +#endif + /* Release the LCB */ + l2cu_release_lcb (p_lcb); + } + + /* If link is connected, check for inactivity timeout */ + if (p_lcb->link_state == LST_CONNECTED) + { + /* Check for ping outstanding */ + if (p_lcb->p_echo_rsp_cb) + { + tL2CA_ECHO_RSP_CB *p_cb = p_lcb->p_echo_rsp_cb; + + /* Zero out the callback in case app immediately calls us again */ + p_lcb->p_echo_rsp_cb = NULL; + + (*p_cb) (L2CAP_PING_RESULT_NO_RESP); + + L2CAP_TRACE_WARNING0 ("L2CAP - ping timeout"); + + /* For all channels, send a disconnect indication event through */ + /* their FSMs. The CCBs should remove themselves from the LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; ) + { + tL2C_CCB *pn = p_ccb->p_next_ccb; + + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL); + + p_ccb = pn; + } + } + + /* If no channels in use, drop the link. */ + if (!p_lcb->ccb_queue.p_first_ccb) + { + rc = btm_sec_disconnect (p_lcb->handle, HCI_ERR_PEER_USER); + + if (rc == BTM_CMD_STORED) + { + /* Security Manager will take care of disconnecting, state will be updated at that time */ + timeout = 0xFFFF; + } + else if (rc == BTM_CMD_STARTED) + { + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } + else if (rc == BTM_SUCCESS) + { + /* BTM SEC will make sure that link is release (probably after pairing is done) */ + p_lcb->link_state = LST_DISCONNECTING; + timeout = 0xFFFF; + } + else if (rc == BTM_BUSY) + { + /* BTM is still executing security process. Let lcb stay as connected */ + timeout = 0xFFFF; + } + else if ((p_lcb->is_bonding) + && (btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER))) + { + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } + else + { + /* probably no buffer to send disconnect */ + timeout = BT_1SEC_TIMEOUT; + } + + if (timeout != 0xFFFF) + { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, timeout); + } + } + else + { + /* Check in case we were flow controlled */ + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + } + } +} + +/******************************************************************************* +** +** Function l2c_info_timeout +** +** Description This function is called when an info request times out +** +** Returns void +** +*******************************************************************************/ +void l2c_info_timeout (tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + tL2C_CONN_INFO ci; + + /* If we timed out waiting for info response, just continue using basic if allowed */ + if (p_lcb->w4_info_rsp) + { + /* If waiting for security complete, restart the info response timer */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + if ( (p_ccb->chnl_state == CST_ORIG_W4_SEC_COMP) || (p_ccb->chnl_state == CST_TERM_W4_SEC_COMP) ) + { + btu_start_timer (&p_lcb->info_timer_entry, BTU_TTYPE_L2CAP_INFO, L2CAP_WAIT_INFO_RSP_TOUT); + return; + } + } + + p_lcb->w4_info_rsp = FALSE; + + /* If link is in process of being brought up */ + if ((p_lcb->link_state != LST_DISCONNECTED) && + (p_lcb->link_state != LST_DISCONNECTING)) + { + /* Notify active channels that peer info is finished */ + if (p_lcb->ccb_queue.p_first_ccb) + { + ci.status = HCI_SUCCESS; + memcpy (ci.bd_addr, p_lcb->remote_bd_addr, sizeof(BD_ADDR)); + + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci); + } + } + } + } +} + +/******************************************************************************* +** +** Function l2c_link_adjust_allocation +** +** Description This function is called when a link is created or removed +** to calculate the amount of packets each link may send to +** the HCI without an ack coming back. +** +** Currently, this is a simple allocation, dividing the +** number of Controller Packets by the number of links. In +** the future, QOS configuration should be examined. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_adjust_allocation (void) +{ + UINT16 qq, yy, qq_remainder; + tL2C_LCB *p_lcb; + UINT16 hi_quota, low_quota; + UINT16 num_lowpri_links = 0; + UINT16 num_hipri_links = 0; + UINT16 controller_xmit_quota = l2cb.num_lm_acl_bufs; + UINT16 high_pri_link_quota = L2CAP_HIGH_PRI_MIN_XMIT_QUOTA_A; + + /* If no links active, nothing to do. */ + if (l2cb.num_links_active == 0) + { + l2cb.round_robin_quota = l2cb.round_robin_unacked = 0; + return; + } + + /* First, count the links */ + for (yy = 0, p_lcb = &l2cb.lcb_pool[0]; yy < MAX_L2CAP_LINKS; yy++, p_lcb++) + { + if (p_lcb->in_use) + { + if (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) + num_hipri_links++; + else + num_lowpri_links++; + } + } + + /* now adjust high priority link quota */ + low_quota = num_lowpri_links ? 1 : 0; + while ( (num_hipri_links * high_pri_link_quota + low_quota) > controller_xmit_quota ) + high_pri_link_quota--; + + /* Work out the xmit quota and buffer quota high and low priorities */ + hi_quota = num_hipri_links * high_pri_link_quota; + low_quota = (hi_quota < controller_xmit_quota) ? controller_xmit_quota - hi_quota : 1; + + /* Work out and save the HCI xmit quota for each low priority link */ + + /* If each low priority link cannot have at least one buffer */ + if (num_lowpri_links > low_quota) + { + l2cb.round_robin_quota = low_quota; + qq = qq_remainder = 0; + } + /* If each low priority link can have at least one buffer */ + else if (num_lowpri_links > 0) + { + l2cb.round_robin_quota = 0; + l2cb.round_robin_unacked = 0; + qq = low_quota / num_lowpri_links; + qq_remainder = low_quota % num_lowpri_links; + } + /* If no low priority link */ + else + { + l2cb.round_robin_quota = 0; + l2cb.round_robin_unacked = 0; + qq = qq_remainder = 0; + } + + L2CAP_TRACE_EVENT5 ("l2c_link_adjust_allocation num_hipri: %u num_lowpri: %u low_quota: %u round_robin_quota: %u qq: %u", + num_hipri_links, num_lowpri_links, low_quota, + l2cb.round_robin_quota, qq); + + /* Now, assign the quotas to each link */ + for (yy = 0, p_lcb = &l2cb.lcb_pool[0]; yy < MAX_L2CAP_LINKS; yy++, p_lcb++) + { + if (p_lcb->in_use) + { + if (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) + { + p_lcb->link_xmit_quota = high_pri_link_quota; + } + else + { + /* Safety check in case we switched to round-robin with something outstanding */ + /* if sent_not_acked is added into round_robin_unacked then don't add it again */ + /* l2cap keeps updating sent_not_acked for exiting from round robin */ + if (( p_lcb->link_xmit_quota > 0 )&&( qq == 0 )) + l2cb.round_robin_unacked += p_lcb->sent_not_acked; + + p_lcb->link_xmit_quota = qq; + if (qq_remainder > 0) + { + p_lcb->link_xmit_quota++; + qq_remainder--; + } + } + +#if L2CAP_HOST_FLOW_CTRL + p_lcb->link_ack_thresh = L2CAP_HOST_FC_ACL_BUFS / l2cb.num_links_active; +#endif + L2CAP_TRACE_EVENT3 ("l2c_link_adjust_allocation LCB %d Priority: %d XmitQuota: %d", + yy, p_lcb->acl_priority, p_lcb->link_xmit_quota); + + L2CAP_TRACE_EVENT2 (" SentNotAcked: %d RRUnacked: %d", + p_lcb->sent_not_acked, l2cb.round_robin_unacked); + + /* There is a special case where we have readjusted the link quotas and */ + /* this link may have sent anything but some other link sent packets so */ + /* so we may need a timer to kick off this link's transmissions. */ + if ( (p_lcb->link_state == LST_CONNECTED) + && (p_lcb->link_xmit_data_q.count) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota) ) + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_FLOW_CONTROL_TOUT); + } + } + +} + +/******************************************************************************* +** +** Function l2c_link_adjust_chnl_allocation +** +** Description This function is called to calculate the amount of packets each +** non-F&EC channel may have outstanding. +** +** Currently, this is a simple allocation, dividing the number +** of packets allocated to the link by the number of channels. In +** the future, QOS configuration should be examined. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_adjust_chnl_allocation (void) +{ + tL2C_CCB *p_ccb; + UINT8 xx; + + UINT16 weighted_chnls[GKI_NUM_TOTAL_BUF_POOLS]; + UINT16 quota_per_weighted_chnls[GKI_NUM_TOTAL_BUF_POOLS]; + UINT16 reserved_buff[GKI_NUM_TOTAL_BUF_POOLS]; + + L2CAP_TRACE_DEBUG0 ("l2c_link_adjust_chnl_allocation"); + + /* initialize variables */ + for (xx = 0; xx < GKI_NUM_TOTAL_BUF_POOLS; xx++ ) + { + weighted_chnls[xx] = 0; + reserved_buff[xx] = 0; + } + + /* add up all of tx and rx data rate requirement */ + /* channel required higher data rate will get more buffer quota */ + for (xx = 0; xx < MAX_L2CAP_CHANNELS; xx++) + { + p_ccb = l2cb.ccb_pool + xx; + + if (!p_ccb->in_use) + continue; + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) + { + weighted_chnls[p_ccb->ertm_info.user_tx_pool_id] += p_ccb->tx_data_rate; + weighted_chnls[p_ccb->ertm_info.user_rx_pool_id] += p_ccb->rx_data_rate; + + if (p_ccb->ertm_info.fcr_tx_pool_id == HCI_ACL_POOL_ID) + { + /* reserve buffers only for wait_for_ack_q to maximize throughput */ + /* retrans_q will work based on buffer status */ + reserved_buff[HCI_ACL_POOL_ID] += p_ccb->peer_cfg.fcr.tx_win_sz; + } + + if (p_ccb->ertm_info.fcr_rx_pool_id == HCI_ACL_POOL_ID) + { + /* reserve buffers for srej_rcv_hold_q */ + reserved_buff[HCI_ACL_POOL_ID] += p_ccb->peer_cfg.fcr.tx_win_sz; + } + } + else + { + /* low data rate is 1, medium is 2, high is 3 and no traffic is 0 */ + weighted_chnls[HCI_ACL_POOL_ID] += p_ccb->tx_data_rate + p_ccb->rx_data_rate; + } + } + + + /* get unit quota per pool */ + for (xx = 0; xx < GKI_NUM_TOTAL_BUF_POOLS; xx++ ) + { + if ( weighted_chnls[xx] > 0 ) + { + if (GKI_poolcount(xx) > reserved_buff[xx]) + quota_per_weighted_chnls[xx] = ((GKI_poolcount(xx) - reserved_buff[xx])/weighted_chnls[xx]) + 1; + else + quota_per_weighted_chnls[xx] = 1; + + L2CAP_TRACE_DEBUG5 ("POOL ID:%d, GKI_poolcount = %d, reserved_buff = %d, weighted_chnls = %d, quota_per_weighted_chnls = %d", + xx, GKI_poolcount(xx), reserved_buff[xx], weighted_chnls[xx], quota_per_weighted_chnls[xx] ); + } + else + quota_per_weighted_chnls[xx] = 0; + } + + + /* assign buffer quota to each channel based on its data rate requirement */ + for (xx = 0; xx < MAX_L2CAP_CHANNELS; xx++) + { + p_ccb = l2cb.ccb_pool + xx; + + if (!p_ccb->in_use) + continue; + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) + { + p_ccb->buff_quota = quota_per_weighted_chnls[p_ccb->ertm_info.user_tx_pool_id] * p_ccb->tx_data_rate; + + L2CAP_TRACE_EVENT6 ("CID:0x%04x FCR Mode:%u UserTxPool:%u Priority:%u TxDataRate:%u Quota:%u", + p_ccb->local_cid, p_ccb->peer_cfg.fcr.mode, p_ccb->ertm_info.user_tx_pool_id, + p_ccb->ccb_priority, p_ccb->tx_data_rate, p_ccb->buff_quota); + + } + else + { + p_ccb->buff_quota = quota_per_weighted_chnls[HCI_ACL_POOL_ID] * p_ccb->tx_data_rate; + + L2CAP_TRACE_EVENT4 ("CID:0x%04x Priority:%u TxDataRate:%u Quota:%u", + p_ccb->local_cid, + p_ccb->ccb_priority, p_ccb->tx_data_rate, p_ccb->buff_quota); + } + + /* quota may be change so check congestion */ + l2cu_check_channel_congestion (p_ccb); + } +} + +/******************************************************************************* +** +** Function l2c_link_processs_num_bufs +** +** Description This function is called when a "controller buffer size" +** event is first received from the controller. It updates +** the L2CAP values. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_processs_num_bufs (UINT16 num_lm_acl_bufs) +{ + l2cb.num_lm_acl_bufs = l2cb.controller_xmit_window = num_lm_acl_bufs; + +} + +/******************************************************************************* +** +** Function l2c_link_pkts_rcvd +** +** Description This function is called from the HCI transport when it is time +** tto send a "Host ready for packets" command. This is only when +** host to controller flow control is used. If fills in the arrays +** of numbers of packets and handles. +** +** Returns count of number of entries filled in +** +*******************************************************************************/ +UINT8 l2c_link_pkts_rcvd (UINT16 *num_pkts, UINT16 *handles) +{ + UINT8 num_found = 0; + +#if (L2CAP_HOST_FLOW_CTRL == TRUE) + + int xx; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->link_pkts_unacked)) + { + num_pkts[num_found] = p_lcb->link_pkts_unacked; + handles[num_found] = p_lcb->handle; + p_lcb->link_pkts_unacked = 0; + num_found++; + } + } + +#endif + + return (num_found); +} + +/******************************************************************************* +** +** Function l2c_link_role_changed +** +** Description This function is called whan a link's master/slave role change +** event is received. It simply updates the link control block. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_role_changed (BD_ADDR bd_addr, UINT8 new_role, UINT8 hci_status) +{ + tL2C_LCB *p_lcb; + int xx; + + /* Make sure not called from HCI Command Status (bd_addr and new_role are invalid) */ + if (bd_addr) + { + /* If here came form hci role change event */ + p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr); + if (p_lcb) + { + p_lcb->link_role = new_role; + + /* Reset high priority link if needed */ + if (hci_status == HCI_SUCCESS) + l2cu_set_acl_priority(bd_addr, p_lcb->acl_priority, TRUE); + } + } + + /* Check if any LCB was waiting for switch to be completed */ + for (xx = 0, p_lcb = &l2cb.lcb_pool[0]; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->link_state == LST_CONNECTING_WAIT_SWITCH)) + { + l2cu_create_conn_after_switch (p_lcb); + } + } +} + +/******************************************************************************* +** +** Function l2c_pin_code_request +** +** Description This function is called whan a pin-code request is received +** on a connection. If there are no channels active yet on the +** link, it extends the link first connection timer. Make sure +** that inactivity timer is not extended if PIN code happens +** to be after last ccb released. +** +** Returns void +** +*******************************************************************************/ +void l2c_pin_code_request (BD_ADDR bd_addr) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr (bd_addr); + + if ( (p_lcb) && (!p_lcb->ccb_queue.p_first_ccb) ) + { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_CONNECT_TOUT_EXT); + } +} + +#if ((BTM_PWR_MGR_INCLUDED == TRUE) && L2CAP_WAKE_PARKED_LINK == TRUE) +/******************************************************************************* +** +** Function l2c_link_check_power_mode +** +** Description This function is called to check power mode. +** +** Returns TRUE if link is going to be active from park +** FALSE if nothing to send or not in park mode +** +*******************************************************************************/ +BOOLEAN l2c_link_check_power_mode (tL2C_LCB *p_lcb) +{ + tBTM_PM_MODE mode; + tBTM_PM_PWR_MD pm; + tL2C_CCB *p_ccb; + BOOLEAN need_to_active = FALSE; + + /* + * We only switch park to active only if we have unsent packets + */ + if ( p_lcb->link_xmit_data_q.count == 0 ) + { + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + if (p_ccb->xmit_hold_q.count != 0) + { + need_to_active = TRUE; + break; + } + } + } + else + need_to_active = TRUE; + + /* if we have packets to send */ + if ( need_to_active ) + { + /* check power mode */ + if (BTM_ReadPowerMode(p_lcb->remote_bd_addr, &mode) == BTM_SUCCESS) + { + if ( mode == BTM_PM_MD_PARK ) + { + L2CAP_TRACE_DEBUG1 ("LCB(0x%x) is in park mode", p_lcb->handle); +/* Coverity: +// FALSE-POSITIVE error from Coverity test tool. Please do NOT remove following comment. +// coverity[uninit_use_in_call] False-positive: setting the mode to BTM_PM_MD_ACTIVE only uses settings.mode + the other data members of tBTM_PM_PWR_MD are ignored +*/ + memset((void*)&pm, 0, sizeof(pm)); + pm.mode = BTM_PM_MD_ACTIVE; + BTM_SetPowerMode(BTM_PM_SET_ONLY_ID, p_lcb->remote_bd_addr, &pm); + btu_start_timer (&p_lcb->timer_entry, + BTU_TTYPE_L2CAP_LINK, L2CAP_WAIT_UNPARK_TOUT); + return TRUE; + } + else if ( mode == BTM_PM_STS_PENDING ) + { + L2CAP_TRACE_DEBUG1 ("LCB(0x%x) is in PM pending state", p_lcb->handle); + + btu_start_timer (&p_lcb->timer_entry, + BTU_TTYPE_L2CAP_LINK, L2CAP_WAIT_UNPARK_TOUT); + return TRUE; + } + } + } + return FALSE; +} +#endif /* ((BTM_PWR_MGR_INCLUDED == TRUE) && L2CAP_WAKE_PARKED_LINK == TRUE) */ + +/******************************************************************************* +** +** Function l2c_link_check_send_pkts +** +** Description This function is called to check if it can send packets +** to the Host Controller. It may be passed the address of +** a packet to send. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_check_send_pkts (tL2C_LCB *p_lcb, tL2C_CCB *p_ccb, BT_HDR *p_buf) +{ + int xx; + BOOLEAN single_write = FALSE; + + /* Save the channel ID for faster counting */ + if (p_buf) + { + if (p_ccb != NULL) + { + p_buf->event = p_ccb->local_cid; + single_write = TRUE; + } + else + p_buf->event = 0; + + p_buf->layer_specific = 0; + GKI_enqueue (&p_lcb->link_xmit_data_q, p_buf); + + if (p_lcb->link_xmit_quota == 0) + l2cb.check_round_robin = TRUE; + } + + /* If this is called from uncongested callback context break recursive calling. + ** This LCB will be served when receiving number of completed packet event. + */ + if (l2cb.is_cong_cback_context) + return; + + /* If we are in a scenario where there are not enough buffers for each link to + ** have at least 1, then do a round-robin for all the LCBs + */ + if ( (p_lcb == NULL) || (p_lcb->link_xmit_quota == 0) ) + { + if (p_lcb == NULL) + p_lcb = l2cb.lcb_pool; + else if (!single_write) + p_lcb++; + + /* Loop through, starting at the next */ + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + /* If controller window is full, nothing to do */ + if ( (l2cb.controller_xmit_window == 0 +#if (BLE_INCLUDED == TRUE) + && !p_lcb->is_ble_link +#endif + ) +#if (BLE_INCLUDED == TRUE) + || (p_lcb->is_ble_link && l2cb.controller_le_xmit_window == 0 ) +#endif + || (l2cb.round_robin_unacked >= l2cb.round_robin_quota) ) + break; + + /* Check for wraparound */ + if (p_lcb == &l2cb.lcb_pool[MAX_L2CAP_LINKS]) + p_lcb = &l2cb.lcb_pool[0]; + + if ( (!p_lcb->in_use) + || (p_lcb->partial_segment_being_sent) + || (p_lcb->link_state != LST_CONNECTED) + || (p_lcb->link_xmit_quota != 0) + || (L2C_LINK_CHECK_POWER_MODE (p_lcb)) ) + continue; + + /* See if we can send anything from the Link Queue */ + if ((p_buf = (BT_HDR *)GKI_dequeue (&p_lcb->link_xmit_data_q)) != NULL) + { + l2c_link_send_to_lower (p_lcb, p_buf); + } + else if (single_write) + { + /* If only doing one write, break out */ + break; + } + /* If nothing on the link queue, check the channel queue */ + else if ((p_buf = l2cu_get_next_buffer_to_send (p_lcb)) != NULL) + { + l2c_link_send_to_lower (p_lcb, p_buf); + } + } + + /* If we finished without using up our quota, no need for a safety check */ +#if (BLE_INCLUDED == TRUE) + if ( ((l2cb.controller_xmit_window > 0 && !p_lcb->is_ble_link) || + (l2cb.controller_le_xmit_window > 0 && p_lcb->is_ble_link)) + && (l2cb.round_robin_unacked < l2cb.round_robin_quota) ) +#else + if ( (l2cb.controller_xmit_window > 0) + && (l2cb.round_robin_unacked < l2cb.round_robin_quota) ) + +#endif + l2cb.check_round_robin = FALSE; + } + else /* if this is not round-robin service */ + { + /* If a partial segment is being sent, can't send anything else */ + if ( (p_lcb->partial_segment_being_sent) + || (p_lcb->link_state != LST_CONNECTED) + || (L2C_LINK_CHECK_POWER_MODE (p_lcb)) ) + return; + + /* See if we can send anything from the link queue */ +#if (BLE_INCLUDED == TRUE) + while ( ((l2cb.controller_xmit_window != 0 && !p_lcb->is_ble_link) || + (l2cb.controller_le_xmit_window != 0 && p_lcb->is_ble_link)) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#else + while ( (l2cb.controller_xmit_window != 0) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#endif + { + if ((p_buf = (BT_HDR *)GKI_dequeue (&p_lcb->link_xmit_data_q)) == NULL) + break; + + if (!l2c_link_send_to_lower (p_lcb, p_buf)) + break; + } + + if (!single_write) + { + /* See if we can send anything for any channel */ +#if (BLE_INCLUDED == TRUE) + while ( ((l2cb.controller_xmit_window != 0 && !p_lcb->is_ble_link) || + (l2cb.controller_le_xmit_window != 0 && p_lcb->is_ble_link)) + && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#else + while ((l2cb.controller_xmit_window != 0) && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota)) +#endif + { + if ((p_buf = l2cu_get_next_buffer_to_send (p_lcb)) == NULL) + break; + + if (!l2c_link_send_to_lower (p_lcb, p_buf)) + break; + } + } + + /* There is a special case where we have readjusted the link quotas and */ + /* this link may have sent anything but some other link sent packets so */ + /* so we may need a timer to kick off this link's transmissions. */ + if ( (p_lcb->link_xmit_data_q.count) && (p_lcb->sent_not_acked < p_lcb->link_xmit_quota) ) + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_FLOW_CONTROL_TOUT); + } + +} + +/******************************************************************************* +** +** Function l2c_link_send_to_lower +** +** Description This function queues the buffer for HCI transmission +** +** Returns TRUE for success, FALSE for fail +** +*******************************************************************************/ +static BOOLEAN l2c_link_send_to_lower (tL2C_LCB *p_lcb, BT_HDR *p_buf) +{ + UINT16 num_segs; + UINT16 xmit_window, acl_data_size; + +#if (BLE_INCLUDED == TRUE) + if ((!p_lcb->is_ble_link && (p_buf->len <= btu_cb.hcit_acl_pkt_size)) || + (p_lcb->is_ble_link && (p_buf->len <= btu_cb.hcit_ble_acl_pkt_size))) +#else + if (p_buf->len <= btu_cb.hcit_acl_pkt_size) +#endif + { + if (p_lcb->link_xmit_quota == 0) + l2cb.round_robin_unacked++; + + p_lcb->sent_not_acked++; + p_buf->layer_specific = 0; + +#if (BLE_INCLUDED == TRUE) + if (p_lcb->is_ble_link) + { + l2cb.controller_le_xmit_window--; + L2C_LINK_SEND_BLE_ACL_DATA (p_buf); + } + else +#endif + { + l2cb.controller_xmit_window--; + L2C_LINK_SEND_ACL_DATA (p_buf); + } + } + else + { +#if BLE_INCLUDED == TRUE + if (p_lcb->is_ble_link) + { + acl_data_size = btu_cb.hcit_ble_acl_data_size; + xmit_window = l2cb.controller_le_xmit_window; + + } + else +#endif + { + acl_data_size = btu_cb.hcit_acl_data_size; + xmit_window = l2cb.controller_xmit_window; + } + num_segs = (p_buf->len - HCI_DATA_PREAMBLE_SIZE + acl_data_size - 1) / acl_data_size; + + + /* If doing round-robin, then only 1 segment each time */ + if (p_lcb->link_xmit_quota == 0) + { + num_segs = 1; + p_lcb->partial_segment_being_sent = TRUE; + } + else + { + /* Multi-segment packet. Make sure it can fit */ + if (num_segs > xmit_window) + { + num_segs = xmit_window; + p_lcb->partial_segment_being_sent = TRUE; + } + + if (num_segs > (p_lcb->link_xmit_quota - p_lcb->sent_not_acked)) + { + num_segs = (p_lcb->link_xmit_quota - p_lcb->sent_not_acked); + p_lcb->partial_segment_being_sent = TRUE; + } + } + + p_buf->layer_specific = num_segs; +#if BLE_INCLUDED == TRUE + if (p_lcb->is_ble_link) + { + l2cb.controller_le_xmit_window -= num_segs; + + } + else +#endif + l2cb.controller_xmit_window -= num_segs; + + if (p_lcb->link_xmit_quota == 0) + l2cb.round_robin_unacked += num_segs; + + p_lcb->sent_not_acked += num_segs; +#if BLE_INCLUDED == TRUE + if (p_lcb->is_ble_link) + { + L2C_LINK_SEND_BLE_ACL_DATA(p_buf); + } + else +#endif + { + L2C_LINK_SEND_ACL_DATA (p_buf); + } + } + +#if (L2CAP_HCI_FLOW_CONTROL_DEBUG == TRUE) +#if (BLE_INCLUDED == TRUE) + if (p_lcb->is_ble_link) + { + L2CAP_TRACE_DEBUG6 ("TotalWin=%d,Hndl=0x%x,Quota=%d,Unack=%d,RRQuota=%d,RRUnack=%d", + l2cb.controller_le_xmit_window, + p_lcb->handle, + p_lcb->link_xmit_quota, p_lcb->sent_not_acked, + l2cb.round_robin_quota, l2cb.round_robin_unacked); + } + else +#endif + { + L2CAP_TRACE_DEBUG6 ("TotalWin=%d,Hndl=0x%x,Quota=%d,Unack=%d,RRQuota=%d,RRUnack=%d", + l2cb.controller_xmit_window, + p_lcb->handle, + p_lcb->link_xmit_quota, p_lcb->sent_not_acked, + l2cb.round_robin_quota, l2cb.round_robin_unacked); + } +#endif + + return TRUE; +} + +/******************************************************************************* +** +** Function l2c_link_process_num_completed_pkts +** +** Description This function is called when a "number-of-completed-packets" +** event is received from the controller. It updates all the +** LCB transmit counts. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_process_num_completed_pkts (UINT8 *p) +{ + UINT8 num_handles, xx; + UINT16 handle; + UINT16 num_sent; + tL2C_LCB *p_lcb; + + STREAM_TO_UINT8 (num_handles, p); + + for (xx = 0; xx < num_handles; xx++) + { + STREAM_TO_UINT16 (handle, p); + STREAM_TO_UINT16 (num_sent, p); + + p_lcb = l2cu_find_lcb_by_handle (handle); + + /* Callback for number of completed packet event */ + /* Originally designed for [3DSG] */ + if((p_lcb != NULL) && (p_lcb->p_nocp_cb)) + { + L2CAP_TRACE_DEBUG0 ("L2CAP - calling NoCP callback"); + (*p_lcb->p_nocp_cb)(p_lcb->remote_bd_addr); + } + +#if (BLE_INCLUDED == TRUE) + if (p_lcb && p_lcb->is_ble_link) + l2cb.controller_le_xmit_window += num_sent; + else +#endif + { + + /* Maintain the total window to the controller */ + l2cb.controller_xmit_window += num_sent; + } + + if (p_lcb) + { + /* If doing round-robin, adjust communal counts */ + if (p_lcb->link_xmit_quota == 0) + { + /* Don't go negative */ + if (l2cb.round_robin_unacked > num_sent) + l2cb.round_robin_unacked -= num_sent; + else + l2cb.round_robin_unacked = 0; + } + + /* Don't go negative */ + if (p_lcb->sent_not_acked > num_sent) + p_lcb->sent_not_acked -= num_sent; + else + p_lcb->sent_not_acked = 0; + + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + + /* If we were doing round-robin for low priority links, check 'em */ + if ( (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH) + && (l2cb.check_round_robin) + && (l2cb.round_robin_unacked < l2cb.round_robin_quota) ) + { + l2c_link_check_send_pkts (NULL, NULL, NULL); + } + } + +#if (L2CAP_HCI_FLOW_CONTROL_DEBUG == TRUE) + if (p_lcb) + { +#if (BLE_INCLUDED == TRUE) + if (p_lcb->is_ble_link) + { + L2CAP_TRACE_DEBUG5 ("TotalWin=%d,LinkUnack(0x%x)=%d,RRCheck=%d,RRUnack=%d", + l2cb.controller_le_xmit_window, + p_lcb->handle, p_lcb->sent_not_acked, + l2cb.check_round_robin, l2cb.round_robin_unacked); + } + else +#endif + { + L2CAP_TRACE_DEBUG5 ("TotalWin=%d,LinkUnack(0x%x)=%d,RRCheck=%d,RRUnack=%d", + l2cb.controller_xmit_window, + p_lcb->handle, p_lcb->sent_not_acked, + l2cb.check_round_robin, l2cb.round_robin_unacked); + + } + } + else + { +#if (BLE_INCLUDED == TRUE) + L2CAP_TRACE_DEBUG5 ("TotalWin=%d LE_Win: %d, Handle=0x%x, RRCheck=%d, RRUnack=%d", + l2cb.controller_xmit_window, + l2cb.controller_le_xmit_window, + handle, + l2cb.check_round_robin, l2cb.round_robin_unacked); +#else + L2CAP_TRACE_DEBUG4 ("TotalWin=%d Handle=0x%x RRCheck=%d RRUnack=%d", + l2cb.controller_xmit_window, + handle, + l2cb.check_round_robin, l2cb.round_robin_unacked); +#endif + } +#endif + } + +#if (defined(HCILP_INCLUDED) && HCILP_INCLUDED == TRUE) + /* only full stack can enable sleep mode */ + btu_check_bt_sleep (); +#endif +} + +/******************************************************************************* +** +** Function l2cap_link_chk_pkt_start +** +** Description This function is called from the HCI transport when the first +** 4 bytes of an HCI ACL packet have been received. It checks if the +** packet is the next segment of a fragmented L2CAP message. If it +** is, and the length is OK, it returns the address of the +** starting L2CAP message segment buffer. +** +** Returns the address of the receive buffer HCIT should use +** (CR419: Modified to return NULL in case of error.) +** +** NOTE This assumes that the L2CAP MTU size is less than the size +** of an HCI ACL buffer, so the maximum L2CAP message will fit +** into one buffer. +** +*******************************************************************************/ +BT_HDR *l2cap_link_chk_pkt_start (BT_HDR *p_cur_buf) +{ + UINT8 *p; + UINT16 handle; + UINT16 hci_len; + UINT16 pkt_type; + tL2C_LCB *p_lcb; + BT_HDR * p_return_buf; /* CR419: To avoid returning from too many places */ + + + if (p_cur_buf) + { + p = (UINT8 *)(p_cur_buf + 1) + p_cur_buf->offset; + } + else + { + return (NULL); + } + + /* L2CAP expects all rcvd packets to have a layer-specific value of 0 */ + p_cur_buf->layer_specific = 0; + + STREAM_TO_UINT16 (handle, p); + STREAM_TO_UINT16 (hci_len, p); + + pkt_type = HCID_GET_EVENT (handle); + handle = HCID_GET_HANDLE (handle); + + l2cb.p_cur_hcit_lcb = NULL; + + /* Find the link that is associated with this handle */ + p_lcb = l2cu_find_lcb_by_handle (handle); + + /* If no link for this handle, nothing to do. */ + if (!p_lcb) + return (p_cur_buf) ; + + if (pkt_type == L2CAP_PKT_START) /*** START PACKET ***/ + { + /* Start of packet. If we were in the middle of receiving */ + /* a packet, it is incomplete. Drop it. */ + if (p_lcb->p_hcit_rcv_acl) + { + L2CAP_TRACE_WARNING0 ("L2CAP - dropping incomplete pkt"); + GKI_freebuf (p_lcb->p_hcit_rcv_acl); + p_lcb->p_hcit_rcv_acl = NULL; + } + + /* Save the active buffer address in the LCB */ + if ((p_return_buf = p_cur_buf) != NULL) + { + p_lcb->p_hcit_rcv_acl = p_return_buf; + l2cb.p_cur_hcit_lcb = p_lcb; + } + } + else /*** CONTINUATION PACKET ***/ + { + /* Packet continuation. Check if we were expecting it */ + if (p_lcb->p_hcit_rcv_acl) + { + UINT16 total_len; + BT_HDR *p_base_buf = p_lcb->p_hcit_rcv_acl; + UINT8 *p_f = (UINT8 *)(p_base_buf + 1) + p_base_buf->offset + 2; + + STREAM_TO_UINT16 (total_len, p_f); + + /* We were expecting the CONTINUATION packet. If length fits, it can go in the */ + /* current buffer. */ + if ((total_len + hci_len) <= (L2CAP_MTU_SIZE + HCI_DATA_PREAMBLE_SIZE)) + { + /* GKI_freebuf (p_cur_buf); CR419:Do not free it yet */ + p_return_buf = p_lcb->p_hcit_rcv_acl; /* CR419: return base buffer */ + l2cb.p_cur_hcit_lcb = p_lcb; + + if ((p_cur_buf->len > HCI_DATA_PREAMBLE_SIZE)) + { + UINT8 * p = (UINT8 *)(p_cur_buf + 1) + + p_cur_buf->offset + + HCI_DATA_PREAMBLE_SIZE; + UINT8 * p1 = (UINT8 *)(p_return_buf + 1) + + p_return_buf->offset + + p_return_buf->len; + + /* Copy data from new buffer into base buffer then update the data */ + /* count in the base buffer accordingly. */ + memcpy (p1, p, p_cur_buf->len - HCI_DATA_PREAMBLE_SIZE); + p_return_buf->len += (p_cur_buf->len - HCI_DATA_PREAMBLE_SIZE); + } + + GKI_freebuf (p_cur_buf); + p_cur_buf = NULL; + + /* Update HCI header of first segment (base buffer) with new length */ + total_len += hci_len; + p_f = (UINT8 *)(p_base_buf + 1) + p_base_buf->offset + 2; + UINT16_TO_STREAM (p_f, total_len); + } + else + { + /* Packet too long. Drop the base packet */ + L2CAP_TRACE_WARNING3 ("L2CAP - dropping too long pkt BufLen: %d total_len: %d hci_len: %d", + p_lcb->p_hcit_rcv_acl->len, total_len, hci_len); + + GKI_freebuf (p_lcb->p_hcit_rcv_acl); + p_lcb->p_hcit_rcv_acl = NULL; + p_return_buf = NULL ; /* Can't hold onto it any more */ + } + } + else /*** NEITHER START OR CONTINUATION PACKET ***/ + { + p_return_buf = NULL ; + } + } + + if (p_return_buf == NULL) /* if error is indicated.. */ + { + if (p_cur_buf != NULL) /* ..drop input buffer */ + GKI_freebuf(p_cur_buf); /* (if present) */ + } + + return (p_return_buf); +} + +/******************************************************************************* +** +** Function l2cap_link_chk_pkt_end +** +** Description This function is called from the HCI transport when the last +** byte of an HCI ACL packet has been received. It checks if the +** L2CAP message is complete, i.e. no more continuation packets +** are expected. +** +** Returns TRUE if message complete, FALSE if continuation expected +** +*******************************************************************************/ +BOOLEAN l2cap_link_chk_pkt_end (void) +{ + UINT8 *p; + BT_HDR *p_buf; + UINT16 l2cap_len; + tL2C_LCB *p_lcb; + + /* If link or buffer pointer not set up, let main line handle it */ + if (((p_lcb = l2cb.p_cur_hcit_lcb) == NULL) || ((p_buf = p_lcb->p_hcit_rcv_acl) == NULL)) + return (TRUE); + + /* Point to the L2CAP length */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset + HCI_DATA_PREAMBLE_SIZE; + + STREAM_TO_UINT16 (l2cap_len, p); + + /* If the L2CAP length has not been reached, tell HCIT not to send this buffer to BTU */ + if (l2cap_len > (p_buf->len - (HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD))) + { + return (FALSE); + } + else + { + p_lcb->p_hcit_rcv_acl = NULL; + return (TRUE); + } +} + + +/******************************************************************************* +** +** Function l2c_link_segments_xmitted +** +** Description This function is called from the HCI Interface when an ACL +** data packet segment is transmitted. +** +** Returns void +** +*******************************************************************************/ +void l2c_link_segments_xmitted (BT_HDR *p_msg) +{ + UINT8 *p = (UINT8 *)(p_msg + 1) + p_msg->offset; + UINT16 handle; + tL2C_LCB *p_lcb; + + /* Extract the handle */ + STREAM_TO_UINT16 (handle, p); + handle = HCID_GET_HANDLE (handle); + + /* Find the LCB based on the handle */ + if ((p_lcb = l2cu_find_lcb_by_handle (handle)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CAP - rcvd segment complete, unknown handle: %d", handle); + GKI_freebuf (p_msg); + return; + } + + if (p_lcb->link_state == LST_CONNECTED) + { + /* Enqueue the buffer to the head of the transmit queue, and see */ + /* if we can transmit anything more. */ + GKI_enqueue_head (&p_lcb->link_xmit_data_q, p_msg); + + p_lcb->partial_segment_being_sent = FALSE; + + l2c_link_check_send_pkts (p_lcb, NULL, NULL); + } + else + GKI_freebuf (p_msg); +} diff --git a/stack/l2cap/l2c_main.c b/stack/l2cap/l2c_main.c new file mode 100644 index 0000000..17b9df1 --- /dev/null +++ b/stack/l2cap/l2c_main.c @@ -0,0 +1,979 @@ +/***************************************************************************** +** +** Name: l2c_main.c +** +** Description: This file contains the main L2CAP entry points +** +** +** +** Copyright (c) 1999-2011, Broadcom Corp., All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ + +#include "bt_target.h" +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "gki.h" +#include "hcimsgs.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "l2c_api.h" +#include "btu.h" +#include "btm_int.h" + +/********************************************************************************/ +/* L O C A L F U N C T I O N P R O T O T Y P E S */ +/********************************************************************************/ +static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len); + +/********************************************************************************/ +/* G L O B A L L 2 C A P D A T A */ +/********************************************************************************/ +#if L2C_DYNAMIC_MEMORY == FALSE +tL2C_CB l2cb; +#endif + +/* Temporary - until l2cap implements group management */ +#if (TCS_BCST_SETUP_INCLUDED == TRUE && TCS_INCLUDED == TRUE) +extern void tcs_proc_bcst_msg( BD_ADDR addr, BT_HDR *p_msg ) ; + +/******************************************************************************* +** +** Function l2c_bcst_msg +** +** Description +** +** Returns void +** +*******************************************************************************/ +void l2c_bcst_msg( BT_HDR *p_buf, UINT16 psm ) +{ + UINT8 *p; + + /* Ensure we have enough space in the buffer for the L2CAP and HCI headers */ + if (p_buf->offset < L2CAP_BCST_MIN_OFFSET) + { + L2CAP_TRACE_ERROR1 ("L2CAP - cannot send buffer, offset: %d", p_buf->offset); + GKI_freebuf (p_buf); + return; + } + + /* Step back some bytes to add the headers */ + p_buf->offset -= (HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_BCST_OVERHEAD); + p_buf->len += L2CAP_PKT_OVERHEAD + L2CAP_BCST_OVERHEAD; + + /* Set the pointer to the beginning of the data */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + /* First, the HCI transport header */ + UINT16_TO_STREAM (p, 0x0050 | (L2CAP_PKT_START << 12) | (2 << 14)); + + /* The HCI transport will segment the buffers. */ + if (p_buf->len > btu_cb.hcit_acl_data_size) + { + UINT16_TO_STREAM (p, btu_cb.hcit_acl_data_size); + } + else + { + UINT16_TO_STREAM (p, p_buf->len); + } + + /* Now the L2CAP header */ + UINT16_TO_STREAM (p, p_buf->len - L2CAP_PKT_OVERHEAD); + UINT16_TO_STREAM (p, L2CAP_CONNECTIONLESS_CID); + UINT16_TO_STREAM (p, psm); + + p_buf->len += HCI_DATA_PREAMBLE_SIZE; + + if (p_buf->len <= btu_cb.hcit_acl_pkt_size) + { + HCI_ACL_DATA_TO_LOWER (p_buf); + } +} +#endif + +/******************************************************************************* +** +** Function l2c_rcv_acl_data +** +** Description This function is called from the HCI Interface when an ACL +** data packet is received. +** +** Returns void +** +*******************************************************************************/ +void l2c_rcv_acl_data (BT_HDR *p_msg) +{ + UINT8 *p = (UINT8 *)(p_msg + 1) + p_msg->offset; + UINT16 handle, hci_len; + UINT8 pkt_type; + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb = NULL; + UINT16 l2cap_len, rcv_cid, psm; + + /* Extract the handle */ + STREAM_TO_UINT16 (handle, p); + pkt_type = HCID_GET_EVENT (handle); + handle = HCID_GET_HANDLE (handle); + + /* Since the HCI Transport is putting segmented packets back together, we */ + /* should never get a valid packet with the type set to "continuation" */ + if (pkt_type != L2CAP_PKT_CONTINUE) + { + /* Find the LCB based on the handle */ + if ((p_lcb = l2cu_find_lcb_by_handle (handle)) == NULL) + { + UINT8 cmd_code; + + /* There is a slight possibility (specifically with USB) that we get an */ + /* L2CAP connection request before we get the HCI connection complete. */ + /* So for these types of messages, hold them for up to 2 seconds. */ + STREAM_TO_UINT16 (hci_len, p); + STREAM_TO_UINT16 (l2cap_len, p); + STREAM_TO_UINT16 (rcv_cid, p); + STREAM_TO_UINT8 (cmd_code, p); + + if ((p_msg->layer_specific == 0) && (rcv_cid == L2CAP_SIGNALLING_CID) + && (cmd_code == L2CAP_CMD_INFO_REQ || cmd_code == L2CAP_CMD_CONN_REQ)) + { + L2CAP_TRACE_WARNING5 ("L2CAP - holding ACL for unknown handle:%d ls:%d cid:%d opcode:%d cur count:%d", + handle, p_msg->layer_specific, rcv_cid, cmd_code, + l2cb.rcv_hold_q.count); + p_msg->layer_specific = 2; + GKI_enqueue (&l2cb.rcv_hold_q, p_msg); + + if (l2cb.rcv_hold_q.count == 1) + btu_start_timer (&l2cb.rcv_hold_tle, BTU_TTYPE_L2CAP_HOLD, BT_1SEC_TIMEOUT); + + return; + } + else + { + L2CAP_TRACE_ERROR5 ("L2CAP - rcvd ACL for unknown handle:%d ls:%d cid:%d opcode:%d cur count:%d", + handle, p_msg->layer_specific, rcv_cid, cmd_code, l2cb.rcv_hold_q.count); + } + GKI_freebuf (p_msg); + return; + } + } + else + { + L2CAP_TRACE_WARNING1 ("L2CAP - expected pkt start or complete, got: %d", pkt_type); + GKI_freebuf (p_msg); + return; + } + + /* Extract the length and update the buffer header */ + STREAM_TO_UINT16 (hci_len, p); + p_msg->offset += 4; + +#if (L2CAP_HOST_FLOW_CTRL == TRUE) + /* Send ack if we hit the threshold */ + if (++p_lcb->link_pkts_unacked >= p_lcb->link_ack_thresh) + btu_hcif_send_host_rdy_for_data(); +#endif + + /* Extract the length and CID */ + STREAM_TO_UINT16 (l2cap_len, p); + STREAM_TO_UINT16 (rcv_cid, p); + + /* Find the CCB for this CID */ + if (rcv_cid >= L2CAP_BASE_APPL_CID) + { + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, rcv_cid)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CAP - unknown CID: 0x%04x", rcv_cid); + GKI_freebuf (p_msg); + return; + } + } + + if (hci_len >= L2CAP_PKT_OVERHEAD) /* Must receive at least the L2CAP length and CID.*/ + { + p_msg->len = hci_len - L2CAP_PKT_OVERHEAD; + p_msg->offset += L2CAP_PKT_OVERHEAD; + } + else + { + L2CAP_TRACE_WARNING0 ("L2CAP - got incorrect hci header" ); + GKI_freebuf (p_msg); + return; + } + + if (l2cap_len != p_msg->len) + { + L2CAP_TRACE_WARNING2 ("L2CAP - bad length in pkt. Exp: %d Act: %d", + l2cap_len, p_msg->len); + + GKI_freebuf (p_msg); + return; + } + + /* Send the data through the channel state machine */ + if (rcv_cid == L2CAP_SIGNALLING_CID) + { + process_l2cap_cmd (p_lcb, p, l2cap_len); + GKI_freebuf (p_msg); + } + else if (rcv_cid == L2CAP_CONNECTIONLESS_CID) + { + /* process_connectionless_data (p_lcb); */ + STREAM_TO_UINT16 (psm, p); + L2CAP_TRACE_DEBUG1( "GOT CONNECTIONLESS DATA PSM:%d", psm ) ; +#if (TCS_BCST_SETUP_INCLUDED == TRUE && TCS_INCLUDED == TRUE) + if (psm == TCS_PSM_INTERCOM || psm == TCS_PSM_CORDLESS) + { + p_msg->offset += L2CAP_BCST_OVERHEAD; + p_msg->len -= L2CAP_BCST_OVERHEAD; + tcs_proc_bcst_msg( p_lcb->remote_bd_addr, p_msg ) ; + GKI_freebuf (p_msg); + } + else +#endif + +#if (L2CAP_UCD_INCLUDED == TRUE) + /* if it is not broadcast, check UCD registration */ + if ( l2c_ucd_check_rx_pkts( p_lcb, p_msg ) ) + { + /* nothing to do */ + } + else +#endif + GKI_freebuf (p_msg); + } +#if (BLE_INCLUDED == TRUE) + else if (rcv_cid == L2CAP_BLE_SIGNALLING_CID) + { + l2cble_process_sig_cmd (p_lcb, p, l2cap_len); + GKI_freebuf (p_msg); + } +#endif +#if (L2CAP_NUM_FIXED_CHNLS > 0) + else if ((rcv_cid >= L2CAP_FIRST_FIXED_CHNL) && (rcv_cid <= L2CAP_LAST_FIXED_CHNL) && + (l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb != NULL) ) + { + /* If no CCB for this channel, allocate one */ + if (l2cu_initialize_fixed_ccb (p_lcb, rcv_cid, &l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts)) + { + p_ccb = p_lcb->p_fixed_ccbs[rcv_cid - L2CAP_FIRST_FIXED_CHNL]; + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) + l2c_fcr_proc_pdu (p_ccb, p_msg); + else + (*l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb)(p_lcb->remote_bd_addr, p_msg); + } + else + GKI_freebuf (p_msg); + } +#endif + + else + { + if (p_ccb == NULL) + GKI_freebuf (p_msg); + else + { + /* Basic mode packets go straight to the state machine */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DATA, p_msg); + else + { + /* eRTM or streaming mode, so we need to validate states first */ + if ((p_ccb->chnl_state == CST_OPEN) || (p_ccb->chnl_state == CST_CONFIG)) + l2c_fcr_proc_pdu (p_ccb, p_msg); + else + GKI_freebuf (p_msg); + } + } + } +} + +/******************************************************************************* +** +** Function process_l2cap_cmd +** +** Description This function is called when a packet is received on the +** L2CAP signalling CID +** +** Returns void +** +*******************************************************************************/ +static void process_l2cap_cmd (tL2C_LCB *p_lcb, UINT8 *p, UINT16 pkt_len) +{ + UINT8 *p_pkt_end, *p_next_cmd, *p_cfg_end, *p_cfg_start; + UINT8 cmd_code, cfg_code, cfg_len, id; + tL2C_CONN_INFO con_info; + tL2CAP_CFG_INFO cfg_info; + UINT16 rej_reason, rej_mtu, lcid, rcid, info_type; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + BOOLEAN cfg_rej; + UINT16 cfg_rej_len, cmd_len; + UINT16 result; + tL2C_CONN_INFO ci; + +#if (defined BLE_INCLUDED && BLE_INCLUDED == TRUE) + /* if l2cap command received in CID 1 on top of an LE link, ignore this command */ + if (p_lcb->is_ble_link) + return; +#endif + p_next_cmd = p; + p_pkt_end = p + pkt_len; + + memset (&cfg_info, 0, sizeof(cfg_info)); + + /* An L2CAP packet may contain multiple commands */ + while (TRUE) + { + /* Smallest command is 4 bytes */ + if ((p = p_next_cmd) > (p_pkt_end - 4)) + break; + + STREAM_TO_UINT8 (cmd_code, p); + STREAM_TO_UINT8 (id, p); + STREAM_TO_UINT16 (cmd_len, p); + + /* Check command length does not exceed packet length */ + if ((p_next_cmd = p + cmd_len) > p_pkt_end) + { + L2CAP_TRACE_WARNING3 ("Command len bad pkt_len: %d cmd_len: %d code: %d", + pkt_len, cmd_len, cmd_code); + break; + } + + switch (cmd_code) + { + case L2CAP_CMD_REJECT: + STREAM_TO_UINT16 (rej_reason, p); + if (rej_reason == L2CAP_CMD_REJ_MTU_EXCEEDED) + { + STREAM_TO_UINT16 (rej_mtu, p); + /* What to do with the MTU reject ? We have negotiated an MTU. For now */ + /* we will ignore it and let a higher protocol timeout take care of it */ + + L2CAP_TRACE_WARNING2 ("L2CAP - MTU rej Handle: %d MTU: %d", p_lcb->handle, rej_mtu); + } + if (rej_reason == L2CAP_CMD_REJ_INVALID_CID) + { + STREAM_TO_UINT16 (rcid, p); + STREAM_TO_UINT16 (lcid, p); + + L2CAP_TRACE_WARNING2 ("L2CAP - rej with CID invalid, LCID: 0x%04x RCID: 0x%04x", lcid, rcid); + + /* Remote CID invalid. Treat as a disconnect */ + if (((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) + && (p_ccb->remote_cid == rcid)) + { + /* Fake link disconnect - no reply is generated */ + l2c_csm_execute (p_ccb, L2CEVT_LP_DISCONNECT_IND, NULL); + } + } + + /* SonyEricsson Info request Bug workaround (Continue connection) */ + else if (rej_reason == L2CAP_CMD_REJ_NOT_UNDERSTOOD && p_lcb->w4_info_rsp) + { + btu_stop_timer (&p_lcb->info_timer_entry); + + p_lcb->w4_info_rsp = FALSE; + ci.status = HCI_SUCCESS; + memcpy (ci.bd_addr, p_lcb->remote_bd_addr, sizeof(BD_ADDR)); + + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci); + } + } + break; + + case L2CAP_CMD_CONN_REQ: + STREAM_TO_UINT16 (con_info.psm, p); + STREAM_TO_UINT16 (rcid, p); + if ((p_rcb = l2cu_find_rcb_by_psm (con_info.psm)) == NULL) + { + L2CAP_TRACE_WARNING1 ("L2CAP - rcvd conn req for unknown PSM: %d", con_info.psm); + l2cu_reject_connection (p_lcb, rcid, id, L2CAP_CONN_NO_PSM); + break; + } + else + { + if (!p_rcb->api.pL2CA_ConnectInd_Cb) + { + L2CAP_TRACE_WARNING1 ("L2CAP - rcvd conn req for outgoing-only connection PSM: %d", con_info.psm); + l2cu_reject_connection (p_lcb, rcid, id, L2CAP_CONN_NO_PSM); + break; + } + } + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) + { + L2CAP_TRACE_ERROR0 ("L2CAP - unable to allocate CCB"); + l2cu_reject_connection (p_lcb, rcid, id, L2CAP_CONN_NO_RESOURCES); + break; + } + p_ccb->remote_id = id; + p_ccb->p_rcb = p_rcb; + p_ccb->remote_cid = rcid; + + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_REQ, &con_info); + break; + + case L2CAP_CMD_CONN_RSP: + STREAM_TO_UINT16 (con_info.remote_cid, p); + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (con_info.l2cap_result, p); + STREAM_TO_UINT16 (con_info.l2cap_status, p); + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) == NULL) + { + L2CAP_TRACE_WARNING2 ("L2CAP - no CCB for conn rsp, LCID: %d RCID: %d", + lcid, con_info.remote_cid); + break; + } + if (p_ccb->local_id != id) + { + L2CAP_TRACE_WARNING2 ("L2CAP - con rsp - bad ID. Exp: %d Got: %d", + p_ccb->local_id, id); + break; + } + + if (con_info.l2cap_result == L2CAP_CONN_OK) + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP, &con_info); + else if (con_info.l2cap_result == L2CAP_CONN_PENDING) + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_PND, &con_info); + else + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_CONNECT_RSP_NEG, &con_info); + + break; + + case L2CAP_CMD_CONFIG_REQ: + p_cfg_end = p + cmd_len; + cfg_rej = FALSE; + cfg_rej_len = 0; + + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (cfg_info.flags, p); + + p_cfg_start = p; + + cfg_info.flush_to_present = cfg_info.mtu_present = cfg_info.qos_present = + cfg_info.fcr_present = cfg_info.fcs_present = FALSE; + + while (p < p_cfg_end) + { + STREAM_TO_UINT8 (cfg_code, p); + STREAM_TO_UINT8 (cfg_len, p); + + switch (cfg_code & 0x7F) + { + case L2CAP_CFG_TYPE_MTU: + cfg_info.mtu_present = TRUE; + STREAM_TO_UINT16 (cfg_info.mtu, p); + break; + + case L2CAP_CFG_TYPE_FLUSH_TOUT: + cfg_info.flush_to_present = TRUE; + STREAM_TO_UINT16 (cfg_info.flush_to, p); + break; + + case L2CAP_CFG_TYPE_QOS: + cfg_info.qos_present = TRUE; + STREAM_TO_UINT8 (cfg_info.qos.qos_flags, p); + STREAM_TO_UINT8 (cfg_info.qos.service_type, p); + STREAM_TO_UINT32 (cfg_info.qos.token_rate, p); + STREAM_TO_UINT32 (cfg_info.qos.token_bucket_size, p); + STREAM_TO_UINT32 (cfg_info.qos.peak_bandwidth, p); + STREAM_TO_UINT32 (cfg_info.qos.latency, p); + STREAM_TO_UINT32 (cfg_info.qos.delay_variation, p); + break; + + case L2CAP_CFG_TYPE_FCR: + cfg_info.fcr_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcr.mode, p); + STREAM_TO_UINT8 (cfg_info.fcr.tx_win_sz, p); + STREAM_TO_UINT8 (cfg_info.fcr.max_transmit, p); + STREAM_TO_UINT16 (cfg_info.fcr.rtrans_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mon_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mps, p); + break; + + case L2CAP_CFG_TYPE_FCS: + cfg_info.fcs_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcs, p); + break; + + case L2CAP_CFG_TYPE_EXT_FLOW: + cfg_info.ext_flow_spec_present = TRUE; + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.id, p); + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.stype, p); + STREAM_TO_UINT16 (cfg_info.ext_flow_spec.max_sdu_size, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.sdu_inter_time, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.access_latency, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.flush_timeout, p); + break; + + default: + /* sanity check option length */ + if ((cfg_len + L2CAP_CFG_OPTION_OVERHEAD) <= cmd_len) + { + p += cfg_len; + if ((cfg_code & 0x80) == 0) + { + cfg_rej_len += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + cfg_rej = TRUE; + } + } + /* bad length; force loop exit */ + else + { + p = p_cfg_end; + cfg_rej = TRUE; + } + break; + } + } + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) + { + p_ccb->remote_id = id; + if (cfg_rej) + { + l2cu_send_peer_config_rej (p_ccb, p_cfg_start, (UINT16) (cmd_len - L2CAP_CONFIG_REQ_LEN), cfg_rej_len); + } + else + { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_CONFIG_REQ, &cfg_info); + } + } + else + { + /* updated spec says send command reject on invalid cid */ + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_INVALID_CID, id, 0, 0); + } + break; + + case L2CAP_CMD_CONFIG_RSP: + p_cfg_end = p + cmd_len; + STREAM_TO_UINT16 (lcid, p); + STREAM_TO_UINT16 (cfg_info.flags, p); + STREAM_TO_UINT16 (cfg_info.result, p); + + cfg_info.flush_to_present = cfg_info.mtu_present = cfg_info.qos_present = + cfg_info.fcr_present = cfg_info.fcs_present = FALSE; + + while (p < p_cfg_end) + { + STREAM_TO_UINT8 (cfg_code, p); + STREAM_TO_UINT8 (cfg_len, p); + + switch (cfg_code & 0x7F) + { + case L2CAP_CFG_TYPE_MTU: + cfg_info.mtu_present = TRUE; + STREAM_TO_UINT16 (cfg_info.mtu, p); + break; + + case L2CAP_CFG_TYPE_FLUSH_TOUT: + cfg_info.flush_to_present = TRUE; + STREAM_TO_UINT16 (cfg_info.flush_to, p); + break; + + case L2CAP_CFG_TYPE_QOS: + cfg_info.qos_present = TRUE; + STREAM_TO_UINT8 (cfg_info.qos.qos_flags, p); + STREAM_TO_UINT8 (cfg_info.qos.service_type, p); + STREAM_TO_UINT32 (cfg_info.qos.token_rate, p); + STREAM_TO_UINT32 (cfg_info.qos.token_bucket_size, p); + STREAM_TO_UINT32 (cfg_info.qos.peak_bandwidth, p); + STREAM_TO_UINT32 (cfg_info.qos.latency, p); + STREAM_TO_UINT32 (cfg_info.qos.delay_variation, p); + break; + + case L2CAP_CFG_TYPE_FCR: + cfg_info.fcr_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcr.mode, p); + STREAM_TO_UINT8 (cfg_info.fcr.tx_win_sz, p); + STREAM_TO_UINT8 (cfg_info.fcr.max_transmit, p); + STREAM_TO_UINT16 (cfg_info.fcr.rtrans_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mon_tout, p); + STREAM_TO_UINT16 (cfg_info.fcr.mps, p); + break; + + case L2CAP_CFG_TYPE_FCS: + cfg_info.fcs_present = TRUE; + STREAM_TO_UINT8 (cfg_info.fcs, p); + break; + + case L2CAP_CFG_TYPE_EXT_FLOW: + cfg_info.ext_flow_spec_present = TRUE; + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.id, p); + STREAM_TO_UINT8 (cfg_info.ext_flow_spec.stype, p); + STREAM_TO_UINT16 (cfg_info.ext_flow_spec.max_sdu_size, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.sdu_inter_time, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.access_latency, p); + STREAM_TO_UINT32 (cfg_info.ext_flow_spec.flush_timeout, p); + break; + } + } + + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, lcid)) != NULL) + { + if (p_ccb->local_id != id) + { + L2CAP_TRACE_WARNING2 ("L2CAP - cfg rsp - bad ID. Exp: %d Got: %d", + p_ccb->local_id, id); + break; + } + if ( (cfg_info.result == L2CAP_CFG_OK) || (cfg_info.result == L2CAP_CFG_PENDING) ) + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_CONFIG_RSP, &cfg_info); + else + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_CONFIG_RSP_NEG, &cfg_info); + } + else + { + L2CAP_TRACE_WARNING1 ("L2CAP - rcvd cfg rsp for unknown CID: 0x%04x", lcid); + } + 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_csm_execute (p_ccb, L2CEVT_L2CAP_DISCONNECT_REQ, &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_csm_execute (p_ccb, L2CEVT_L2CAP_DISCONNECT_RSP, &con_info); + } + } + break; + + case L2CAP_CMD_ECHO_REQ: + +#if (L2CAP_ENHANCED_FEATURES != 0) + if (!l2cu_check_feature_req (p_lcb, id, p, cmd_len)) + { + if (cmd_len < (btu_cb.hcit_acl_pkt_size - L2CAP_PKT_OVERHEAD + - L2CAP_CMD_OVERHEAD + - L2CAP_ECHO_RSP_LEN + - HCI_DATA_PREAMBLE_SIZE)) + { + l2cu_send_peer_echo_rsp (p_lcb, id, p, cmd_len); + } + else + { + l2cu_send_peer_echo_rsp (p_lcb, id, NULL, 0); + } + } +#else + l2cu_send_peer_echo_rsp (p_lcb, id, NULL, 0); +#endif + break; + + case L2CAP_CMD_ECHO_RSP: +#if (L2CAP_ENHANCED_FEATURES != 0) + l2cu_check_feature_rsp (p_lcb, id, p, cmd_len); +#endif + if (p_lcb->p_echo_rsp_cb) + { + tL2CA_ECHO_RSP_CB *p_cb = p_lcb->p_echo_rsp_cb; + + /* Zero out the callback in case app immediately calls us again */ + p_lcb->p_echo_rsp_cb = NULL; + + (*p_cb) (L2CAP_PING_RESULT_OK); + } + break; + + case L2CAP_CMD_INFO_REQ: + STREAM_TO_UINT16 (info_type, p); + l2cu_send_peer_info_rsp (p_lcb, id, info_type); + break; + + case L2CAP_CMD_INFO_RSP: + /* Stop the link connect timer if sent before L2CAP connection is up */ + if (p_lcb->w4_info_rsp) + { + btu_stop_timer (&p_lcb->info_timer_entry); + p_lcb->w4_info_rsp = FALSE; + } + + STREAM_TO_UINT16 (info_type, p); + STREAM_TO_UINT16 (result, p); + + p_lcb->info_rx_bits |= (1 << info_type); + + if ( (info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (result == L2CAP_INFO_RESP_RESULT_SUCCESS) ) + { + STREAM_TO_UINT32( p_lcb->peer_ext_fea, p ); + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (p_lcb->peer_ext_fea & L2CAP_EXTFEA_FIXED_CHNLS) + { + l2cu_send_peer_info_req (p_lcb, L2CAP_FIXED_CHANNELS_INFO_TYPE); + break; + } + else + { + l2cu_process_fixed_chnl_resp (p_lcb); + } +#endif + } + + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + if (info_type == L2CAP_FIXED_CHANNELS_INFO_TYPE) + { + if (result == L2CAP_INFO_RESP_RESULT_SUCCESS) + { + memcpy (p_lcb->peer_chnl_mask, p, L2CAP_FIXED_CHNL_ARRAY_SIZE); + } + + l2cu_process_fixed_chnl_resp (p_lcb); + } +#endif +#if (L2CAP_UCD_INCLUDED == TRUE) + else if (info_type == L2CAP_CONNLESS_MTU_INFO_TYPE) + { + if (result == L2CAP_INFO_RESP_RESULT_SUCCESS) + { + STREAM_TO_UINT16 (p_lcb->ucd_mtu, p); + } + } +#endif + + ci.status = HCI_SUCCESS; + memcpy (ci.bd_addr, p_lcb->remote_bd_addr, sizeof(BD_ADDR)); + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + l2c_csm_execute (p_ccb, L2CEVT_L2CAP_INFO_RSP, &ci); + } + break; + + default: + L2CAP_TRACE_WARNING1 ("L2CAP - bad cmd code: %d", cmd_code); + l2cu_send_peer_cmd_reject (p_lcb, L2CAP_CMD_REJ_NOT_UNDERSTOOD, id, 0, 0); + return; + } + } +} + +/******************************************************************************* +** +** Function l2c_process_held_packets +** +** Description This function processes any L2CAP packets that arrived before +** the HCI connection complete arrived. It is a work around for +** badly behaved controllers. +** +** Returns void +** +*******************************************************************************/ +void l2c_process_held_packets (BOOLEAN timed_out) +{ + BT_HDR *p_buf, *p_buf1; + BUFFER_Q *p_rcv_hold_q = &l2cb.rcv_hold_q; + + if (!p_rcv_hold_q->count) + return; + + if (!timed_out) + { + btu_stop_timer(&l2cb.rcv_hold_tle); + L2CAP_TRACE_WARNING0("L2CAP HOLD CONTINUE"); + } + else + { + L2CAP_TRACE_WARNING0("L2CAP HOLD TIMEOUT"); + } + + /* Update the timeouts in the hold queue */ + for (p_buf = (BT_HDR *)GKI_getfirst (p_rcv_hold_q); p_buf; p_buf = p_buf1) + { + p_buf1 = (BT_HDR *)GKI_getnext (p_buf); + if (!timed_out || (!p_buf->layer_specific) || (--p_buf->layer_specific == 0)) + { + GKI_remove_from_queue (p_rcv_hold_q, p_buf); + p_buf->layer_specific = 0xFFFF; + l2c_rcv_acl_data (p_buf); + } + } + + /* If anyone still in the queue, restart the timeout */ + if (p_rcv_hold_q->count) + btu_start_timer (&l2cb.rcv_hold_tle, BTU_TTYPE_L2CAP_HOLD, BT_1SEC_TIMEOUT); +} + + +/******************************************************************************* +** +** Function l2c_init +** +** Description This function is called once at startup to initialize +** all the L2CAP structures +** +** Returns void +** +*******************************************************************************/ +void l2c_init (void) +{ + INT16 xx; + + memset (&l2cb, 0, sizeof (tL2C_CB)); + /* the psm is increased by 2 before being used */ + l2cb.dyn_psm = 0xFFF; + + /* Put all the channel control blocks on the free queue */ + for (xx = 0; xx < MAX_L2CAP_CHANNELS - 1; xx++) + { + l2cb.ccb_pool[xx].p_next_ccb = &l2cb.ccb_pool[xx + 1]; + } + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + /* it will be set to L2CAP_PKT_START_NON_FLUSHABLE if controller supports */ + l2cb.non_flushable_pbf = L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT; +#endif + + + l2cb.p_free_ccb_first = &l2cb.ccb_pool[0]; + l2cb.p_free_ccb_last = &l2cb.ccb_pool[MAX_L2CAP_CHANNELS - 1]; + +#ifdef L2CAP_DESIRED_LINK_ROLE + l2cb.desire_role = L2CAP_DESIRED_LINK_ROLE; +#else + l2cb.desire_role = HCI_ROLE_SLAVE; +#endif + + /* Set the default idle timeout */ + l2cb.idle_timeout = L2CAP_LINK_INACTIVITY_TOUT; + +#if defined(L2CAP_INITIAL_TRACE_LEVEL) + l2cb.l2cap_trace_level = L2CAP_INITIAL_TRACE_LEVEL; +#else + l2cb.l2cap_trace_level = BT_TRACE_LEVEL_NONE; /* No traces */ +#endif + +#if L2CAP_CONFORMANCE_TESTING == TRUE + /* Conformance testing needs a dynamic response */ + l2cb.test_info_resp = L2CAP_EXTFEA_SUPPORTED_MASK; +#endif + + /* Number of ACL buffers to use for high priority channel */ +#if (defined(L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE) && (L2CAP_HIGH_PRI_CHAN_QUOTA_IS_CONFIGURABLE == TRUE)) + l2cb.high_pri_min_xmit_quota = L2CAP_HIGH_PRI_MIN_XMIT_QUOTA; +#endif + +} + +/******************************************************************************* +** +** Function l2c_process_timeout +** +** Description This function is called when an L2CAP-related timeout occurs +** +** Returns void +** +*******************************************************************************/ +void l2c_process_timeout (TIMER_LIST_ENT *p_tle) +{ + /* What type of timeout ? */ + switch (p_tle->event) + { + case BTU_TTYPE_L2CAP_LINK: + l2c_link_timeout ((tL2C_LCB *)p_tle->param); + break; + + case BTU_TTYPE_L2CAP_CHNL: + l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_TIMEOUT, NULL); + break; + + case BTU_TTYPE_L2CAP_FCR_ACK: + l2c_csm_execute (((tL2C_CCB *)p_tle->param), L2CEVT_ACK_TIMEOUT, NULL); + break; + + case BTU_TTYPE_L2CAP_HOLD: + /* Update the timeouts in the hold queue */ + l2c_process_held_packets(TRUE); + break; + + case BTU_TTYPE_L2CAP_INFO: + l2c_info_timeout((tL2C_LCB *)p_tle->param); + break; + + } +} + +/******************************************************************************* +** +** Function l2c_data_write +** +** Description API functions call this function to write data. +** +** Returns L2CAP_DW_SUCCESS, if data accepted, else FALSE +** L2CAP_DW_CONGESTED, if data accepted and the channel is congested +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +UINT8 l2c_data_write (UINT16 cid, BT_HDR *p_data, UINT16 flags) +{ + tL2C_CCB *p_ccb; + + /* 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_WARNING1 ("L2CAP - no CCB for L2CA_DataWrite, CID: %d", cid); + GKI_freebuf (p_data); + return (L2CAP_DW_FAILED); + } + +#ifndef TESTER /* Tester may send any amount of data. otherwise sending message + bigger than mtu size of peer is a violation of protocol */ + if (p_data->len > p_ccb->peer_cfg.mtu) + { + L2CAP_TRACE_WARNING1 ("L2CAP - CID: 0x%04x cannot send message bigger than peer's mtu size", cid); + GKI_freebuf (p_data); + return (L2CAP_DW_FAILED); + } +#endif + + /* channel based, packet based flushable or non-flushable */ + p_data->layer_specific = flags; + + /* If already congested, do not accept any more packets */ + if (p_ccb->cong_sent) + { + L2CAP_TRACE_ERROR3 ("L2CAP - CID: 0x%04x cannot send, already congested xmit_hold_q.count: %u buff_quota: %u", + p_ccb->local_cid, p_ccb->xmit_hold_q.count, p_ccb->buff_quota); + + GKI_freebuf (p_data); + return (L2CAP_DW_FAILED); + } + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_data); + + if (p_ccb->cong_sent) + return (L2CAP_DW_CONGESTED); + + return (L2CAP_DW_SUCCESS); +} + diff --git a/stack/l2cap/l2c_ucd.c b/stack/l2cap/l2c_ucd.c new file mode 100644 index 0000000..375988e --- /dev/null +++ b/stack/l2cap/l2c_ucd.c @@ -0,0 +1,1158 @@ +/***************************************************************************** +** +** Name: l2c_ucd.c +** +** Description: This file contains the L2CAP UCD code +** +** +** +** Copyright (c) 1999-2011, Broadcom Corp., All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "gki.h" +#include "bt_types.h" +#include "hcidefs.h" +#include "hcimsgs.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "btu.h" +#include "btm_api.h" +#include "btm_int.h" + +#if (L2CAP_UCD_INCLUDED == TRUE) +static BOOLEAN l2c_ucd_connect ( BD_ADDR rem_bda ); + +/******************************************************************************* +** +** Function l2c_ucd_discover_cback +** +** Description UCD Discover callback +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_discover_cback (BD_ADDR rem_bda, UINT8 info_type, UINT32 data) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + L2CAP_TRACE_DEBUG0 ("L2CAP - l2c_ucd_discover_cback"); + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if (p_rcb->in_use) + { + /* if this application is waiting UCD reception info */ + if (( info_type == L2CAP_UCD_INFO_TYPE_RECEPTION ) + && ( p_rcb->ucd.state & L2C_UCD_STATE_W4_RECEPTION )) + { + p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (rem_bda, info_type, data); + p_rcb->ucd.state &= ~(L2C_UCD_STATE_W4_RECEPTION); + } + + /* if this application is waiting UCD MTU info */ + if (( info_type == L2CAP_UCD_INFO_TYPE_MTU ) + && ( p_rcb->ucd.state & L2C_UCD_STATE_W4_MTU )) + { + p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (rem_bda, info_type, data); + p_rcb->ucd.state &= ~(L2C_UCD_STATE_W4_MTU); + } + } + } +} + +/******************************************************************************* +** +** Function l2c_ucd_data_ind_cback +** +** Description UCD Data callback +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_data_ind_cback (BD_ADDR rem_bda, BT_HDR *p_buf) +{ + UINT8 *p; + UINT16 psm; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_DEBUG0 ("L2CAP - l2c_ucd_data_ind_cback"); + + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + STREAM_TO_UINT16(psm, p) + + p_buf->offset += L2CAP_UCD_OVERHEAD; + p_buf->len -= L2CAP_UCD_OVERHEAD; + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + { + L2CAP_TRACE_ERROR1 ("L2CAP - no RCB for l2c_ucd_data_ind_cback, PSM: 0x%04x", psm); + GKI_freebuf (p_buf); + } + else + { + p_rcb->ucd.cb_info.pL2CA_UCD_Data_Cb(rem_bda, p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_congestion_status_cback +** +** Description UCD Congestion Status callback +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_congestion_status_cback (BD_ADDR rem_bda, BOOLEAN is_congested) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + L2CAP_TRACE_DEBUG0 ("L2CAP - l2c_ucd_congestion_status_cback"); + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if (( p_rcb->in_use ) + &&( p_rcb->ucd.state != L2C_UCD_STATE_UNUSED )) + { + if ( p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ) + { + L2CAP_TRACE_DEBUG4 ("L2CAP - Calling UCDCongestionStatus_Cb (%d), PSM=0x%04x, BDA: %08x%04x,", + is_congested, p_rcb->psm, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], + (rem_bda[4]<<8)+rem_bda[5]); + + p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ( rem_bda, is_congested ); + } + } + } +} + +/******************************************************************************* +** +** Function l2c_ucd_disconnect_ind_cback +** +** Description UCD disconnect callback (This prevent to access null pointer) +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_disconnect_ind_cback (UINT16 cid, BOOLEAN result) +{ + /* do nothing */ +} + +/******************************************************************************* +** +** Function l2c_ucd_config_ind_cback +** +** Description UCD config callback (This prevent to access null pointer) +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_config_ind_cback (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + /* do nothing */ +} + +/******************************************************************************* +** +** Function l2c_ucd_config_cfm_cback +** +** Description UCD config callback (This prevent to access null pointer) +** +** Returns void +** +*******************************************************************************/ +static void l2c_ucd_config_cfm_cback (UINT16 cid, tL2CAP_CFG_INFO *p_cfg) +{ + /* do nothing */ +} + +/******************************************************************************* +** +** Function L2CA_UcdRegister +** +** Description Register PSM on UCD. +** +** Parameters: tL2CAP_UCD_CB_INFO +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdRegister ( UINT16 psm, tL2CAP_UCD_CB_INFO *p_cb_info ) +{ + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API1 ("L2CA_UcdRegister() PSM: 0x%04x", psm); + + if ((!p_cb_info->pL2CA_UCD_Discover_Cb) + || (!p_cb_info->pL2CA_UCD_Data_Cb)) + { + L2CAP_TRACE_ERROR1 ("L2CAP - no callback registering PSM(0x%04x) on UCD", psm); + return (FALSE); + } + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + { + L2CAP_TRACE_ERROR1 ("L2CAP - no RCB for L2CA_UcdRegister, PSM: 0x%04x", psm); + return (FALSE); + } + + p_rcb->ucd.state = L2C_UCD_STATE_W4_DATA; + p_rcb->ucd.cb_info = *p_cb_info; + + /* check if master rcb is created for UCD */ + if ((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) == NULL) + { + if ((p_rcb = l2cu_allocate_rcb (L2C_UCD_RCB_ID)) == NULL) + { + L2CAP_TRACE_ERROR0 ("L2CAP - no RCB available for L2CA_UcdRegister"); + return (FALSE); + } + else + { + /* these callback functions will forward data to each UCD application */ + p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb = l2c_ucd_discover_cback; + p_rcb->ucd.cb_info.pL2CA_UCD_Data_Cb = l2c_ucd_data_ind_cback; + p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb = l2c_ucd_congestion_status_cback; + + memset (&p_rcb->api, 0, sizeof(tL2CAP_APPL_INFO)); + p_rcb->api.pL2CA_DisconnectInd_Cb = l2c_ucd_disconnect_ind_cback; + + /* This will make L2CAP check UCD congestion callback */ + p_rcb->api.pL2CA_CongestionStatus_Cb = NULL; + + /* do nothing but prevent crash */ + p_rcb->api.pL2CA_ConfigInd_Cb = l2c_ucd_config_ind_cback; + p_rcb->api.pL2CA_ConfigCfm_Cb = l2c_ucd_config_cfm_cback; + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_UcdDeregister +** +** Description Deregister PSM on UCD. +** +** Parameters: PSM +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdDeregister ( UINT16 psm ) +{ + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + UINT16 xx; + + L2CAP_TRACE_API1 ("L2CA_UcdDeregister() PSM: 0x%04x", psm); + + if ((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + { + L2CAP_TRACE_ERROR1 ("L2CAP - no RCB for L2CA_UcdDeregister, PSM: 0x%04x", psm); + return (FALSE); + } + + p_rcb->ucd.state = L2C_UCD_STATE_UNUSED; + + /* check this was the last UCD registration */ + p_rcb = &l2cb.rcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if ((p_rcb->in_use) && (p_rcb->ucd.state != L2C_UCD_STATE_UNUSED)) + return (TRUE); + } + + /* delete master rcb for UCD */ + if ((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) != NULL) + { + l2cu_release_rcb (p_rcb); + } + + /* delete CCB for UCD */ + p_ccb = l2cb.ccb_pool; + for ( xx = 0; xx < MAX_L2CAP_CHANNELS; xx++ ) + { + if (( p_ccb->in_use ) + &&( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID )) + { + l2cu_release_ccb (p_ccb); + } + p_ccb++; + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_UcdDiscover +** +** Description Discover UCD of remote device. +** +** Parameters: PSM +** BD_ADDR of remote device +** info_type : L2CAP_UCD_INFO_TYPE_RECEPTION +** L2CAP_UCD_INFO_TYPE_MTU +** +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdDiscover ( UINT16 psm, BD_ADDR rem_bda, UINT8 info_type ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_API4 ("L2CA_UcdDiscover() PSM: 0x%04x BDA: %08x%04x, InfoType=0x%02x", psm, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], + (rem_bda[4]<<8)+rem_bda[5], info_type); + + /* Fail if the PSM is not registered */ + if (((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + ||( p_rcb->ucd.state == L2C_UCD_STATE_UNUSED )) + { + L2CAP_TRACE_WARNING1 ("L2CAP - no RCB for L2CA_UcdDiscover, PSM: 0x%04x", psm); + return (FALSE); + } + + /* First, see if we already have a link to the remote */ + /* then find the channel control block for UCD. */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) == NULL) + ||((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) + { + if ( l2c_ucd_connect (rem_bda) == FALSE ) + { + return (FALSE); + } + } + + /* set waiting flags in rcb */ + + if ( info_type & L2CAP_UCD_INFO_TYPE_RECEPTION ) + p_rcb->ucd.state |= L2C_UCD_STATE_W4_RECEPTION; + + if ( info_type & L2CAP_UCD_INFO_TYPE_MTU ) + p_rcb->ucd.state |= L2C_UCD_STATE_W4_MTU; + + /* if link is already established */ + if ((p_lcb)&&(p_lcb->link_state == LST_CONNECTED)) + { + if (!p_ccb) + { + p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID); + } + l2c_ucd_check_pending_info_req(p_ccb); + } + return (TRUE); +} + +/******************************************************************************* +** +** Function L2CA_UcdDataWrite +** +** Description Send UCD to remote device +** +** Parameters: PSM +** BD Address of remote +** Pointer to buffer of type BT_HDR +** flags : L2CAP_FLUSHABLE_CH_BASED +** L2CAP_FLUSHABLE_PKT +** L2CAP_NON_FLUSHABLE_PKT +** +** Return value L2CAP_DW_SUCCESS, if data accepted +** L2CAP_DW_FAILED, if error +** +*******************************************************************************/ +UINT16 L2CA_UcdDataWrite (UINT16 psm, BD_ADDR rem_bda, BT_HDR *p_buf, UINT16 flags) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + UINT8 *p; + + L2CAP_TRACE_API3 ("L2CA_UcdDataWrite() PSM: 0x%04x BDA: %08x%04x", psm, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], + (rem_bda[4]<<8)+rem_bda[5]); + + /* Fail if the PSM is not registered */ + if (((p_rcb = l2cu_find_rcb_by_psm (psm)) == NULL) + ||( p_rcb->ucd.state == L2C_UCD_STATE_UNUSED )) + { + L2CAP_TRACE_WARNING1 ("L2CAP - no RCB for L2CA_UcdDataWrite, PSM: 0x%04x", psm); + GKI_freebuf (p_buf); + return (L2CAP_DW_FAILED); + } + + /* First, see if we already have a link to the remote */ + /* then find the channel control block for UCD */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) == NULL) + ||((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) + { + if ( l2c_ucd_connect (rem_bda) == FALSE ) + { + GKI_freebuf (p_buf); + return (L2CAP_DW_FAILED); + } + + /* If we still don't have lcb and ccb after connect attempt, then can't proceed */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) == NULL) + || ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) + { + GKI_freebuf (p_buf); + return (L2CAP_DW_FAILED); + } + } + + /* write PSM */ + p_buf->offset -= L2CAP_UCD_OVERHEAD; + p_buf->len += L2CAP_UCD_OVERHEAD; + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + + UINT16_TO_STREAM (p, psm); + + /* UCD MTU check */ + if ((p_lcb->ucd_mtu) && (p_buf->len > p_lcb->ucd_mtu)) + { + L2CAP_TRACE_WARNING1 ("L2CAP - Handle: 0x%04x UCD bigger than peer's UCD mtu size cannot be sent", p_lcb->handle); + GKI_freebuf (p_buf); + return (L2CAP_DW_FAILED); + } + + /* If already congested, do not accept any more packets */ + if (p_ccb->cong_sent) + { + L2CAP_TRACE_ERROR3 ("L2CAP - Handle: 0x%04x UCD cannot be sent, already congested count: %u buff_quota: %u", + p_lcb->handle, + (p_ccb->xmit_hold_q.count + p_lcb->ucd_out_sec_pending_q.count), + p_ccb->buff_quota); + + GKI_freebuf (p_buf); + return (L2CAP_DW_FAILED); + } + + /* channel based, packet based flushable or non-flushable */ + p_buf->layer_specific = flags; + + l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_buf); + + if (p_ccb->cong_sent) + return (L2CAP_DW_CONGESTED); + else + return (L2CAP_DW_SUCCESS); +} + +/******************************************************************************* +** +** Function L2CA_UcdSetIdleTimeout +** +** Description Set UCD Idle timeout. +** +** Parameters: BD Addr +** Timeout in second +** +** Return value: TRUE if successs +** +*******************************************************************************/ +BOOLEAN L2CA_UcdSetIdleTimeout ( BD_ADDR rem_bda, UINT16 timeout ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API3 ("L2CA_UcdSetIdleTimeout() Timeout: 0x%04x BDA: %08x%04x", timeout, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], + (rem_bda[4]<<8)+rem_bda[5]); + + /* First, see if we already have a link to the remote */ + /* then find the channel control block. */ + if (((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) == NULL) + ||((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL)) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no UCD channel"); + return (FALSE); + } + else + { + p_ccb->fixed_chnl_idle_tout = timeout; + return (TRUE); + } +} + +/******************************************************************************* +** +** Function L2CA_UCDSetTxPriority +** +** Description Sets the transmission priority for a connectionless channel. +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN L2CA_UCDSetTxPriority ( BD_ADDR rem_bda, tL2CAP_CHNL_PRIORITY priority ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + + L2CAP_TRACE_API3 ("L2CA_UCDSetTxPriority() priority: 0x%02x BDA: %08x%04x", priority, + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], + (rem_bda[4]<<8)+rem_bda[5]); + + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no LCB for L2CA_UCDSetTxPriority"); + return (FALSE); + } + + /* Find the channel control block */ + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no CCB for L2CA_UCDSetTxPriority"); + return (FALSE); + } + + /* it will update the order of CCB in LCB by priority and update round robin service variables */ + l2cu_change_pri_ccb (p_ccb, priority); + + return (TRUE); +} + +/******************************************************************************* +** +** Function l2c_ucd_connect +** +** Description Connect UCD to remote device. +** +** Parameters: BD_ADDR of remote device +** +** Return value: TRUE if successs +** +*******************************************************************************/ +static BOOLEAN l2c_ucd_connect ( BD_ADDR rem_bda ) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + L2CAP_TRACE_DEBUG2 ("l2c_ucd_connect() BDA: %08x%04x", + (rem_bda[0]<<24)+(rem_bda[1]<<16)+(rem_bda[2]<<8)+rem_bda[3], + (rem_bda[4]<<8)+rem_bda[5]); + + /* Fail if we have not established communications with the controller */ + if (!BTM_IsDeviceUp()) + { + L2CAP_TRACE_WARNING0 ("l2c_ucd_connect - BTU not ready"); + return (FALSE); + } + + /* First, see if we already have a link to the remote */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr (rem_bda)) == NULL) + { + /* No link. Get an LCB and start link establishment */ + if ( ((p_lcb = l2cu_allocate_lcb (rem_bda, FALSE)) == NULL) + || (l2cu_create_conn(p_lcb) == FALSE) ) + { + L2CAP_TRACE_WARNING0 ("L2CAP - conn not started l2c_ucd_connect"); + return (FALSE); + } + } + else if ( p_lcb->info_rx_bits & (1 << L2CAP_EXTENDED_FEATURES_INFO_TYPE) ) + { + if (!(p_lcb->peer_ext_fea & L2CAP_EXTFEA_UCD_RECEPTION)) + { + L2CAP_TRACE_WARNING0 ("L2CAP - UCD is not supported by peer, l2c_ucd_connect"); + return (FALSE); + } + } + + /* Find the channel control block. */ + if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) == NULL) + { + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no CCB for l2c_ucd_connect"); + return (FALSE); + } + else + { + /* Set CID for the connection */ + p_ccb->local_cid = L2CAP_CONNECTIONLESS_CID; + p_ccb->remote_cid = L2CAP_CONNECTIONLESS_CID; + + /* Set the default idle timeout value to use */ + p_ccb->fixed_chnl_idle_tout = L2CAP_UCD_IDLE_TIMEOUT; + + /* Set the default channel priority value to use */ + l2cu_change_pri_ccb (p_ccb, L2CAP_UCD_CH_PRIORITY); + + if ((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no UCD registered, l2c_ucd_connect"); + return (FALSE); + } + /* Save UCD registration info */ + p_ccb->p_rcb = p_rcb; + + /* There is no configuration, so if the link is up, the channel is up */ + if (p_lcb->link_state == LST_CONNECTED) + { + p_ccb->chnl_state = CST_OPEN; + } + } + } + + return (TRUE); +} + +/******************************************************************************* +** +** Function l2c_ucd_delete_sec_pending_q +** +** Description discard all of UCD packets in security pending queue +** +** Returns None +** +*******************************************************************************/ +void l2c_ucd_delete_sec_pending_q(tL2C_LCB *p_lcb) +{ + /* clean up any security pending UCD */ + while (p_lcb->ucd_out_sec_pending_q.p_first) + GKI_freebuf (GKI_dequeue (&p_lcb->ucd_out_sec_pending_q)); + + while (p_lcb->ucd_in_sec_pending_q.p_first) + GKI_freebuf (GKI_dequeue (&p_lcb->ucd_in_sec_pending_q)); +} + +/******************************************************************************* +** +** Function l2c_ucd_check_pending_info_req +** +** Description check if any application is waiting for UCD information +** +** Return TRUE if any pending UCD info request +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_pending_info_req(tL2C_CCB *p_ccb) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + BOOLEAN pending = FALSE; + + if (p_ccb == NULL) + { + L2CAP_TRACE_ERROR0 ("L2CAP - NULL p_ccb in l2c_ucd_check_pending_info_req"); + return (FALSE); + } + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if (p_rcb->in_use) + { + /* if application is waiting UCD reception info */ + if (p_rcb->ucd.state & L2C_UCD_STATE_W4_RECEPTION) + { + /* if this information is available */ + if ( p_ccb->p_lcb->info_rx_bits & (1 << L2CAP_EXTENDED_FEATURES_INFO_TYPE) ) + { + if (!(p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_UCD_RECEPTION)) + { + L2CAP_TRACE_WARNING0 ("L2CAP - UCD is not supported by peer, l2c_ucd_check_pending_info_req"); + + l2c_ucd_delete_sec_pending_q(p_ccb->p_lcb); + l2cu_release_ccb (p_ccb); + } + + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (p_ccb->p_lcb->remote_bd_addr, + L2CAP_UCD_INFO_TYPE_RECEPTION, + p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_UCD_RECEPTION); + } + else + { + pending = TRUE; + if (p_ccb->p_lcb->w4_info_rsp == FALSE) + { + l2cu_send_peer_info_req (p_ccb->p_lcb, L2CAP_EXTENDED_FEATURES_INFO_TYPE); + } + } + } + + /* if application is waiting for UCD MTU */ + if (p_rcb->ucd.state & L2C_UCD_STATE_W4_MTU) + { + /* if this information is available */ + if ( p_ccb->p_lcb->info_rx_bits & (1 << L2CAP_CONNLESS_MTU_INFO_TYPE)) + { + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Discover_Cb (p_ccb->p_lcb->remote_bd_addr, + L2CAP_UCD_INFO_TYPE_MTU, + p_ccb->p_lcb->ucd_mtu); + } + else + { + pending = TRUE; + if (p_ccb->p_lcb->w4_info_rsp == FALSE) + { + l2cu_send_peer_info_req (p_ccb->p_lcb, L2CAP_CONNLESS_MTU_INFO_TYPE); + } + } + } + } + } + return (pending); +} + +/******************************************************************************* +** +** Function l2c_ucd_enqueue_pending_out_sec_q +** +** Description enqueue outgoing UCD packet into security pending queue +** and check congestion +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_enqueue_pending_out_sec_q(tL2C_CCB *p_ccb, void *p_data) +{ + GKI_enqueue (&p_ccb->p_lcb->ucd_out_sec_pending_q, p_data); + l2cu_check_channel_congestion (p_ccb); +} + +/******************************************************************************* +** +** Function l2c_ucd_check_pending_out_sec_q +** +** Description check outgoing security +** +** Return TRUE if any UCD packet for security +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_pending_out_sec_q(tL2C_CCB *p_ccb) +{ + UINT8 *p; + UINT16 psm; + BT_HDR *p_buf; + + if ( p_ccb->p_lcb->ucd_out_sec_pending_q.count ) + { + p_buf = (BT_HDR*)(p_ccb->p_lcb->ucd_out_sec_pending_q.p_first); + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + STREAM_TO_UINT16(psm, p) + + p_ccb->chnl_state = CST_ORIG_W4_SEC_COMP; + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, psm, + p_ccb->p_lcb->handle, CONNLESS_ORIG, &l2c_link_sec_comp, p_ccb); + + return (TRUE); + } + return (FALSE); +} + +/******************************************************************************* +** +** Function l2c_ucd_send_pending_out_sec_q +** +** Description dequeue UCD packet from security pending queue and +** enqueue it into CCB +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_send_pending_out_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + + if ( p_ccb->p_lcb->ucd_out_sec_pending_q.count ) + { + p_buf = (BT_HDR*)GKI_dequeue (&p_ccb->p_lcb->ucd_out_sec_pending_q); + + l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_buf); + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_discard_pending_out_sec_q +** +** Description dequeue UCD packet from security pending queue and +** discard it. +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_discard_pending_out_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + + p_buf = (BT_HDR*)GKI_dequeue (&p_ccb->p_lcb->ucd_out_sec_pending_q); + + /* we may need to report to application */ + + if (p_buf) + { + GKI_freebuf (p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_check_pending_in_sec_q +** +** Description check incoming security +** +** Return TRUE if any UCD packet for security +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_pending_in_sec_q(tL2C_CCB *p_ccb) +{ + UINT8 *p; + UINT16 psm; + BT_HDR *p_buf; + + if ( p_ccb->p_lcb->ucd_in_sec_pending_q.count ) + { + p_buf = (BT_HDR*)(p_ccb->p_lcb->ucd_in_sec_pending_q.p_first); + p = (UINT8 *)(p_buf + 1) + p_buf->offset; + STREAM_TO_UINT16(psm, p) + + p_ccb->chnl_state = CST_TERM_W4_SEC_COMP; + btm_sec_l2cap_access_req (p_ccb->p_lcb->remote_bd_addr, psm, + p_ccb->p_lcb->handle, CONNLESS_TERM, &l2c_link_sec_comp, p_ccb); + + return (TRUE); + } + return (FALSE); +} + +/******************************************************************************* +** +** Function l2c_ucd_send_pending_in_sec_q +** +** Description dequeue UCD packet from security pending queue and +** send it to application +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_send_pending_in_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + + if ( p_ccb->p_lcb->ucd_in_sec_pending_q.count ) + { + p_buf = (BT_HDR*)GKI_dequeue (&p_ccb->p_lcb->ucd_in_sec_pending_q); + + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Data_Cb(p_ccb->p_lcb->remote_bd_addr, (BT_HDR *)p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_discard_pending_in_sec_q +** +** Description dequeue UCD packet from security pending queue and +** discard it. +** +** Return None +** +*******************************************************************************/ +void l2c_ucd_discard_pending_in_sec_q(tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + + p_buf = (BT_HDR*)GKI_dequeue (&p_ccb->p_lcb->ucd_in_sec_pending_q); + + if (p_buf) + { + GKI_freebuf (p_buf); + } +} + +/******************************************************************************* +** +** Function l2c_ucd_check_rx_pkts +** +** Description Check if UCD reception is registered. +** Process received UCD packet if application is expecting. +** +** Return TRUE if UCD reception is registered +** +*******************************************************************************/ +BOOLEAN l2c_ucd_check_rx_pkts(tL2C_LCB *p_lcb, BT_HDR *p_msg) +{ + tL2C_CCB *p_ccb; + tL2C_RCB *p_rcb; + + if (((p_ccb = l2cu_find_ccb_by_cid (p_lcb, L2CAP_CONNECTIONLESS_CID)) != NULL) + ||((p_rcb = l2cu_find_rcb_by_psm (L2C_UCD_RCB_ID)) != NULL)) + { + if (p_ccb == NULL) + { + /* Allocate a channel control block */ + if ((p_ccb = l2cu_allocate_ccb (p_lcb, 0)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no CCB for UCD reception"); + GKI_freebuf (p_msg); + return TRUE; + } + else + { + /* Set CID for the connection */ + p_ccb->local_cid = L2CAP_CONNECTIONLESS_CID; + p_ccb->remote_cid = L2CAP_CONNECTIONLESS_CID; + + /* Set the default idle timeout value to use */ + p_ccb->fixed_chnl_idle_tout = L2CAP_UCD_IDLE_TIMEOUT; + + /* Set the default channel priority value to use */ + l2cu_change_pri_ccb (p_ccb, L2CAP_UCD_CH_PRIORITY); + + /* Save registration info */ + p_ccb->p_rcb = p_rcb; + + p_ccb->chnl_state = CST_OPEN; + } + } + l2c_csm_execute(p_ccb, L2CEVT_L2CAP_DATA, p_msg); + return TRUE; + } + else + return FALSE; +} + +/******************************************************************************* +** +** Function l2c_ucd_process_event +** +** Description This is called from main state machine when LCID is connectionless +** Process the event if it is for UCD. +** +** Return TRUE if the event is consumed by UCD +** FALSE if the event needs to be processed by main state machine +** +*******************************************************************************/ +BOOLEAN l2c_ucd_process_event(tL2C_CCB *p_ccb, UINT16 event, void *p_data) +{ + /* if the event is not processed by this function, this variable will be set to FALSE */ + BOOLEAN done = TRUE; + + switch (p_ccb->chnl_state) + { + case CST_CLOSED: + switch (event) + { + case L2CEVT_LP_CONNECT_CFM: /* Link came up */ + /* check if waiting for UCD info */ + if (!l2c_ucd_check_pending_info_req (p_ccb)) + { + /* check if any outgoing UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_out_sec_q(p_ccb)) + { + p_ccb->chnl_state = CST_OPEN; + } + } + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + GKI_enqueue (&p_ccb->p_lcb->ucd_in_sec_pending_q, p_data); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + if (!l2c_ucd_check_pending_info_req (p_ccb)) + { + /* check if any outgoing UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_out_sec_q(p_ccb)) + { + p_ccb->chnl_state = CST_OPEN; + } + } + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + case CST_ORIG_W4_SEC_COMP: + switch (event) + { + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + /* check if any outgoing UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_out_sec_q(p_ccb)) + { + p_ccb->chnl_state = CST_OPEN; + } + break; + + case L2CEVT_SEC_COMP: /* Security completed success */ + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_send_pending_out_sec_q(p_ccb); + + if ( p_ccb->p_lcb->ucd_out_sec_pending_q.count ) + { + /* start a timer to send next UCD packet in OPEN state */ + /* it will prevent stack overflow */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, 0); + } + else + { + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + } + break; + + case L2CEVT_SEC_COMP_NEG: + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_discard_pending_out_sec_q(p_ccb); + + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + GKI_enqueue (&p_ccb->p_lcb->ucd_in_sec_pending_q, p_data); + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + l2c_ucd_check_pending_info_req (p_ccb); + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + + case CST_TERM_W4_SEC_COMP: + switch (event) + { + case L2CEVT_SEC_COMP: + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_send_pending_in_sec_q (p_ccb); + + if ( p_ccb->p_lcb->ucd_in_sec_pending_q.count ) + { + /* start a timer to check next UCD packet in OPEN state */ + /* it will prevent stack overflow */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, 0); + } + else + { + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + } + break; + + case L2CEVT_SEC_COMP_NEG: + if (((tL2C_CONN_INFO *)p_data)->status == BTM_DELAY_CHECK) + { + done = FALSE; + break; + } + p_ccb->chnl_state = CST_OPEN; + l2c_ucd_discard_pending_in_sec_q (p_ccb); + + /* start a timer for idle timeout of UCD */ + btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, p_ccb->fixed_chnl_idle_tout); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + break; + + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + GKI_enqueue (&p_ccb->p_lcb->ucd_in_sec_pending_q, p_data); + break; + + case L2CEVT_SEC_RE_SEND_CMD: /* BTM has enough info to proceed */ + /* check if any incoming UCD packet is waiting security check */ + if (!l2c_ucd_check_pending_in_sec_q(p_ccb)) + { + p_ccb->chnl_state = CST_OPEN; + } + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + l2c_ucd_check_pending_info_req (p_ccb); + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + case CST_OPEN: + switch (event) + { + case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd */ + /* stop idle timer of UCD */ + btu_stop_timer (&p_ccb->timer_entry); + + GKI_enqueue (&p_ccb->p_lcb->ucd_in_sec_pending_q, p_data); + l2c_ucd_check_pending_in_sec_q (p_ccb); + break; + + case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */ + /* stop idle timer of UCD */ + btu_stop_timer (&p_ccb->timer_entry); + + l2c_ucd_enqueue_pending_out_sec_q(p_ccb, p_data); + + /* coverity[check_return] */ /* coverity[unchecked_value] */ + /* success changes state, failure stays in current state */ + l2c_ucd_check_pending_out_sec_q (p_ccb); + break; + + case L2CEVT_TIMEOUT: + /* check if any UCD packet is waiting security check */ + if ((!l2c_ucd_check_pending_in_sec_q(p_ccb)) + &&(!l2c_ucd_check_pending_out_sec_q(p_ccb))) + { + l2cu_release_ccb (p_ccb); + } + break; + + case L2CEVT_L2CAP_INFO_RSP: + /* check if waiting for UCD info */ + l2c_ucd_check_pending_info_req (p_ccb); + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + break; + + default: + done = FALSE; /* main state machine continues to process event */ + break; + } + + return done; +} +#endif /* (L2CAP_UCD_INCLUDED == TRUE) */ diff --git a/stack/l2cap/l2c_utils.c b/stack/l2cap/l2c_utils.c new file mode 100644 index 0000000..0c1fb60 --- /dev/null +++ b/stack/l2cap/l2c_utils.c @@ -0,0 +1,3397 @@ +/***************************************************************************** +** +** Name: l2c_utils.c +** +** +** Description: This file contains L2CAP utility functions +** +** +** +** Copyright (c) 1999-2012, Broadcom Corp., All Rights Reserved. +** Broadcom Bluetooth Core. Proprietary and confidential. +******************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "gki.h" +#include "bt_types.h" +#include "hcimsgs.h" +#include "l2cdefs.h" +#include "l2c_int.h" +#include "hcidefs.h" +#include "btu.h" +#include "btm_api.h" +#include "btm_int.h" +#include "hcidefs.h" + +/******************************************************************************* +** +** Function l2cu_allocate_lcb +** +** Description Look for an unused LCB +** +** Returns LCB address or NULL if none found +** +*******************************************************************************/ +tL2C_LCB *l2cu_allocate_lcb (BD_ADDR p_bd_addr, BOOLEAN is_bonding) +{ + int xx; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if (!p_lcb->in_use) + { + memset (p_lcb, 0, sizeof (tL2C_LCB)); + + memcpy (p_lcb->remote_bd_addr, p_bd_addr, BD_ADDR_LEN); + + p_lcb->in_use = TRUE; + p_lcb->link_state = LST_DISCONNECTED; + p_lcb->handle = HCI_INVALID_HANDLE; + p_lcb->link_flush_tout = 0xFFFF; + p_lcb->timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; + p_lcb->info_timer_entry.param = (TIMER_PARAM_TYPE)p_lcb; + p_lcb->idle_timeout = l2cb.idle_timeout; + p_lcb->id = 1; /* spec does not allow '0' */ + p_lcb->is_bonding = is_bonding; + + l2cb.num_links_active++; + + l2c_link_adjust_allocation(); + return (p_lcb); + } + } + + /* If here, no free LCB found */ + return (NULL); +} + +/******************************************************************************* +** +** Function l2cu_update_lcb_4_bonding +** +** Description Mark the lcb for bonding. Used when bonding takes place on +** an existing ACL connection. (Pre-Lisbon devices) +** +** Returns Nothing +** +*******************************************************************************/ +void l2cu_update_lcb_4_bonding (BD_ADDR p_bd_addr, BOOLEAN is_bonding) +{ + tL2C_LCB *p_lcb = l2cu_find_lcb_by_bd_addr (p_bd_addr); + + if (p_lcb) + { + L2CAP_TRACE_DEBUG3 ("l2cu_update_lcb_4_bonding BDA: %08x%04x is_bonding: %d", + (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], is_bonding); + p_lcb->is_bonding = is_bonding; + } +} + +/******************************************************************************* +** +** Function l2cu_release_lcb +** +** Description Release an LCB. All timers will be stopped, channels +** dropped, buffers returned etc. +** +** Returns void +** +*******************************************************************************/ +void l2cu_release_lcb (tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + + p_lcb->in_use = FALSE; + p_lcb->is_bonding = FALSE; + + /* Stop timers */ + btu_stop_timer (&p_lcb->timer_entry); + btu_stop_timer (&p_lcb->info_timer_entry); + + /* Release any unfinished L2CAP packet on this link */ + if (p_lcb->p_hcit_rcv_acl) + { + GKI_freebuf(p_lcb->p_hcit_rcv_acl); + p_lcb->p_hcit_rcv_acl = NULL; + } + +#if BTM_SCO_INCLUDED == TRUE + /* Release all SCO links */ + btm_remove_sco_links(p_lcb->remote_bd_addr); +#endif + +#if (BLE_INCLUDED == TRUE) + p_lcb->is_ble_link = FALSE; + l2cb.is_ble_connecting = FALSE; +#endif + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + { + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) + { + if (p_lcb->p_fixed_ccbs[xx]) + { + l2cu_release_ccb (p_lcb->p_fixed_ccbs[xx]); + p_lcb->p_fixed_ccbs[xx] = NULL; + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason); + } + else if ( (p_lcb->peer_chnl_mask[0] & (1 << (xx + L2CAP_FIRST_FIXED_CHNL))) + && (l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb != NULL) ) + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason); + } + } +#endif + + /* Ensure no CCBs left on this LCB */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_lcb->ccb_queue.p_first_ccb) + { + l2cu_release_ccb (p_ccb); + } + + /* Tell BTM Acl management the link was removed */ + if ((p_lcb->link_state == LST_CONNECTED) || (p_lcb->link_state == LST_DISCONNECTING)) + btm_acl_removed (p_lcb->remote_bd_addr); + + /* Release any held buffers */ + while (p_lcb->link_xmit_data_q.p_first) + GKI_freebuf (GKI_dequeue (&p_lcb->link_xmit_data_q)); + +#if (L2CAP_UCD_INCLUDED == TRUE) + /* clean up any security pending UCD */ + l2c_ucd_delete_sec_pending_q(p_lcb); +#endif + + /* Re-adjust flow control windows make sure it does not go negative */ + if (l2cb.num_links_active >= 1) + l2cb.num_links_active--; + + l2c_link_adjust_allocation(); + + /* Check for ping outstanding */ + if (p_lcb->p_echo_rsp_cb) + { + tL2CA_ECHO_RSP_CB *p_cb = p_lcb->p_echo_rsp_cb; + + /* Zero out the callback in case app immediately calls us again */ + p_lcb->p_echo_rsp_cb = NULL; + + (*p_cb) (L2CAP_PING_RESULT_NO_LINK); + } +} + + +/******************************************************************************* +** +** Function l2cu_find_lcb_by_bd_addr +** +** Description Look through all active LCBs for a match based on the +** remote BD address. +** +** Returns pointer to matched LCB, or NULL if no match +** +*******************************************************************************/ +tL2C_LCB *l2cu_find_lcb_by_bd_addr (BD_ADDR p_bd_addr) +{ + int xx; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (!memcmp (p_lcb->remote_bd_addr, p_bd_addr, BD_ADDR_LEN))) + { + return (p_lcb); + } + } + + /* If here, no match found */ + return (NULL); +} + +/******************************************************************************* +** +** Function l2cu_get_conn_role +** +** Description Determine the desired role (master or slave) of a link. +** If already got a slave link, this one must be a master. If +** already got at least 1 link where we are the master, make this +** also a master. +** +** Returns HCI_ROLE_MASTER or HCI_ROLE_SLAVE +** +*******************************************************************************/ +UINT8 l2cu_get_conn_role (tL2C_LCB *p_this_lcb) +{ + return l2cb.desire_role; +} + +/******************************************************************************* +** +** Function l2cu_build_header +** +** Description Builds the L2CAP command packet header +** +** Returns Pointer to allocated packet or NULL if no resources +** +*******************************************************************************/ +BT_HDR *l2cu_build_header (tL2C_LCB *p_lcb, UINT16 len, UINT8 cmd, UINT8 id) +{ + BT_HDR *p_buf = (BT_HDR *)GKI_getpoolbuf (L2CAP_CMD_POOL_ID); + UINT8 *p; + + if (!p_buf) + { + L2CAP_TRACE_ERROR0 ("l2cu_build_header - no buffer"); + return (NULL); + } + + p_buf->offset = L2CAP_SEND_CMD_OFFSET; + p_buf->len = len + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET; + + /* Put in HCI header - handle + pkt boundary */ +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + UINT16_TO_STREAM (p, p_lcb->handle | l2cb.non_flushable_pbf); +#else + UINT16_TO_STREAM (p, (p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT))); +#endif + + UINT16_TO_STREAM (p, len + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD); + UINT16_TO_STREAM (p, len + L2CAP_CMD_OVERHEAD); + +#if (BLE_INCLUDED == TRUE) + if (p_lcb->is_ble_link) + { + UINT16_TO_STREAM (p, L2CAP_BLE_SIGNALLING_CID); + } + else +#endif + { + UINT16_TO_STREAM (p, L2CAP_SIGNALLING_CID); + } + + /* Put in L2CAP command header */ + UINT8_TO_STREAM (p, cmd); + UINT8_TO_STREAM (p, id); + UINT16_TO_STREAM (p, len); + + return (p_buf); +} + +/******************************************************************************* +** +** Function l2cu_adj_id +** +** Description Checks for valid ID based on specified mask +** and adjusts the id if invalid. +** +** Returns void +** +*******************************************************************************/ +void l2cu_adj_id (tL2C_LCB *p_lcb, UINT8 adj_mask) +{ +#if (L2CAP_ENHANCED_FEATURES != 0) + if ((adj_mask & L2CAP_ADJ_BRCM_ID) && p_lcb->id == L2CAP_FEATURE_REQ_ID) + { + p_lcb->id++; + } +#endif + + if ((adj_mask & L2CAP_ADJ_ZERO_ID) && !p_lcb->id) + { + p_lcb->id++; + } +} + +/******************************************************************************* +** +** Function l2cu_send_peer_cmd_reject +** +** Description Build and send an L2CAP "command reject" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_cmd_reject (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id, + UINT16 p1, UINT16 p2) +{ + UINT16 param_len; + BT_HDR *p_buf; + UINT8 *p; + + /* Put in L2CAP packet header */ + if (reason == L2CAP_CMD_REJ_MTU_EXCEEDED) + param_len = 2; + else if (reason == L2CAP_CMD_REJ_INVALID_CID) + param_len = 4; + else + param_len = 0; + + if ((p_buf = l2cu_build_header (p_lcb, (UINT16) (L2CAP_CMD_REJECT_LEN + param_len), L2CAP_CMD_REJECT, rem_id)) == NULL ) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer cmd_rej"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, reason); + + if (param_len >= 2) + UINT16_TO_STREAM (p, p1); + + if (param_len >= 4) + UINT16_TO_STREAM (p, p2); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_connect_req +** +** Description Build and send an L2CAP "connection request" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_connect_req (tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf; + UINT8 *p; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if ((p_buf = l2cu_build_header (p_ccb->p_lcb, L2CAP_CONN_REQ_LEN, L2CAP_CMD_CONN_REQ, + p_ccb->local_id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET+HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->p_rcb->real_psm); + UINT16_TO_STREAM (p, p_ccb->local_cid); + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_connect_rsp +** +** Description Build and send an L2CAP "connection response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_connect_rsp (tL2C_CCB *p_ccb, UINT16 result, UINT16 status) +{ + BT_HDR *p_buf; + UINT8 *p; + + if (result == L2CAP_CONN_PENDING) + { + /* if we already sent pending response */ + if (p_ccb->flags & CCB_FLAG_SENT_PENDING) + return; + else + p_ccb->flags |= CCB_FLAG_SENT_PENDING; + } + + if ((p_buf=l2cu_build_header(p_ccb->p_lcb, L2CAP_CONN_RSP_LEN, L2CAP_CMD_CONN_RSP, p_ccb->remote_id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for conn_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET+HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->local_cid); + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, result); + UINT16_TO_STREAM (p, status); + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_reject_connection +** +** Description Build and send an L2CAP "connection response neg" message +** to the peer. This function is called when there is no peer +** CCB (non-existant PSM or no resources). +** +** Returns void +** +*******************************************************************************/ +void l2cu_reject_connection (tL2C_LCB *p_lcb, UINT16 remote_cid, UINT8 rem_id, UINT16 result) +{ + BT_HDR *p_buf; + UINT8 *p; + + if ((p_buf = l2cu_build_header(p_lcb, L2CAP_CONN_RSP_LEN, L2CAP_CMD_CONN_RSP, rem_id)) == NULL ) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET+HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, 0); /* Local CID of 0 */ + UINT16_TO_STREAM (p, remote_cid); + UINT16_TO_STREAM (p, result); + UINT16_TO_STREAM (p, 0); /* Status of 0 */ + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_config_req +** +** Description Build and send an L2CAP "configuration request" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_config_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + BT_HDR *p_buf; + UINT16 cfg_len=0; + UINT8 *p; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if (p_cfg->mtu_present) + cfg_len += L2CAP_CFG_MTU_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->flush_to_present) + cfg_len += L2CAP_CFG_FLUSH_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->qos_present) + cfg_len += L2CAP_CFG_QOS_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->fcr_present) + cfg_len += L2CAP_CFG_FCR_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->fcs_present) + cfg_len += L2CAP_CFG_FCS_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->ext_flow_spec_present) + cfg_len += L2CAP_CFG_EXT_FLOW_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + + if ((p_buf = l2cu_build_header (p_ccb->p_lcb, (UINT16) (L2CAP_CONFIG_REQ_LEN + cfg_len), + L2CAP_CMD_CONFIG_REQ, p_ccb->local_id)) == NULL ) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, p_cfg->flags); /* Flags (continuation) */ + + /* Now, put the options */ + if (p_cfg->mtu_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_MTU); + UINT8_TO_STREAM (p, L2CAP_CFG_MTU_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->mtu); + } + if (p_cfg->flush_to_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FLUSH_TOUT); + UINT8_TO_STREAM (p, L2CAP_CFG_FLUSH_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->flush_to); + } + if (p_cfg->qos_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_QOS); + UINT8_TO_STREAM (p, L2CAP_CFG_QOS_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->qos.qos_flags); + UINT8_TO_STREAM (p, p_cfg->qos.service_type); + UINT32_TO_STREAM (p, p_cfg->qos.token_rate); + UINT32_TO_STREAM (p, p_cfg->qos.token_bucket_size); + UINT32_TO_STREAM (p, p_cfg->qos.peak_bandwidth); + UINT32_TO_STREAM (p, p_cfg->qos.latency); + UINT32_TO_STREAM (p, p_cfg->qos.delay_variation); + } + if (p_cfg->fcr_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FCR); + UINT8_TO_STREAM (p, L2CAP_CFG_FCR_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->fcr.mode); + UINT8_TO_STREAM (p, p_cfg->fcr.tx_win_sz); + UINT8_TO_STREAM (p, p_cfg->fcr.max_transmit); + UINT16_TO_STREAM (p, p_cfg->fcr.rtrans_tout); + UINT16_TO_STREAM (p, p_cfg->fcr.mon_tout); + UINT16_TO_STREAM (p, p_cfg->fcr.mps); + } + + if (p_cfg->fcs_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FCS); + UINT8_TO_STREAM (p, L2CAP_CFG_FCS_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->fcs); + } + + if (p_cfg->ext_flow_spec_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_EXT_FLOW); + UINT8_TO_STREAM (p, L2CAP_CFG_EXT_FLOW_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.id); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.stype); + UINT16_TO_STREAM (p, p_cfg->ext_flow_spec.max_sdu_size); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.sdu_inter_time); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.access_latency); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.flush_timeout); + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_config_rsp +** +** Description Build and send an L2CAP "configuration response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_config_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + BT_HDR *p_buf; + UINT16 cfg_len = 0; + UINT8 *p; + + /* Create an identifier for this packet */ + if (p_cfg->mtu_present) + cfg_len += L2CAP_CFG_MTU_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->flush_to_present) + cfg_len += L2CAP_CFG_FLUSH_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->qos_present) + cfg_len += L2CAP_CFG_QOS_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->fcr_present) + cfg_len += L2CAP_CFG_FCR_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + if (p_cfg->ext_flow_spec_present) + cfg_len += L2CAP_CFG_EXT_FLOW_OPTION_LEN + L2CAP_CFG_OPTION_OVERHEAD; + + if ((p_buf = l2cu_build_header (p_ccb->p_lcb, (UINT16)(L2CAP_CONFIG_RSP_LEN + cfg_len), + L2CAP_CMD_CONFIG_RSP, p_ccb->remote_id)) == NULL ) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for conn_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET+HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, p_cfg->flags); /* Flags (continuation) Must match request */ + UINT16_TO_STREAM (p, p_cfg->result); + + /* Now, put the options */ + if (p_cfg->mtu_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_MTU); + UINT8_TO_STREAM (p, L2CAP_CFG_MTU_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->mtu); + } + if (p_cfg->flush_to_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FLUSH_TOUT); + UINT8_TO_STREAM (p, L2CAP_CFG_FLUSH_OPTION_LEN); + UINT16_TO_STREAM (p, p_cfg->flush_to); + } + if (p_cfg->qos_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_QOS); + UINT8_TO_STREAM (p, L2CAP_CFG_QOS_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->qos.qos_flags); + UINT8_TO_STREAM (p, p_cfg->qos.service_type); + UINT32_TO_STREAM (p, p_cfg->qos.token_rate); + UINT32_TO_STREAM (p, p_cfg->qos.token_bucket_size); + UINT32_TO_STREAM (p, p_cfg->qos.peak_bandwidth); + UINT32_TO_STREAM (p, p_cfg->qos.latency); + UINT32_TO_STREAM (p, p_cfg->qos.delay_variation); + } + if (p_cfg->fcr_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_FCR); + UINT8_TO_STREAM (p, L2CAP_CFG_FCR_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->fcr.mode); + UINT8_TO_STREAM (p, p_cfg->fcr.tx_win_sz); + UINT8_TO_STREAM (p, p_cfg->fcr.max_transmit); + UINT16_TO_STREAM (p, p_ccb->our_cfg.fcr.rtrans_tout); + UINT16_TO_STREAM (p, p_ccb->our_cfg.fcr.mon_tout); + UINT16_TO_STREAM (p, p_cfg->fcr.mps); + } + + if (p_cfg->ext_flow_spec_present) + { + UINT8_TO_STREAM (p, L2CAP_CFG_TYPE_EXT_FLOW); + UINT8_TO_STREAM (p, L2CAP_CFG_EXT_FLOW_OPTION_LEN); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.id); + UINT8_TO_STREAM (p, p_cfg->ext_flow_spec.stype); + UINT16_TO_STREAM (p, p_cfg->ext_flow_spec.max_sdu_size); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.sdu_inter_time); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.access_latency); + UINT32_TO_STREAM (p, p_cfg->ext_flow_spec.flush_timeout); + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_config_rej +** +** Description Build and send an L2CAP "configuration reject" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_config_rej (tL2C_CCB *p_ccb, UINT8 *p_data, UINT16 data_len, UINT16 rej_len) +{ + BT_HDR *p_buf = (BT_HDR *)GKI_getpoolbuf (L2CAP_CMD_POOL_ID); + UINT16 len, cfg_len; + UINT8 *p, *p_hci_len, *p_data_end; + UINT8 cfg_code; + + if (!p_buf) + { + L2CAP_TRACE_ERROR0 ("L2CAP - no buffer for cfg_rej"); + return; + } + + p_buf->offset = L2CAP_SEND_CMD_OFFSET; + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET; + + /* Put in HCI header - handle + pkt boundary */ +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + if (HCI_NON_FLUSHABLE_PB_SUPPORTED(BTM_ReadLocalFeatures ())) + { + UINT16_TO_STREAM (p, (p_ccb->p_lcb->handle | (L2CAP_PKT_START_NON_FLUSHABLE << L2CAP_PKT_TYPE_SHIFT))); + } + else +#endif + { + UINT16_TO_STREAM (p, (p_ccb->p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT))); + } + + /* Remember the HCI header length position, and save space for it */ + p_hci_len = p; + p += 2; + + /* Put in L2CAP packet header */ + UINT16_TO_STREAM (p, L2CAP_CMD_OVERHEAD + L2CAP_CONFIG_RSP_LEN + rej_len); + UINT16_TO_STREAM (p, L2CAP_SIGNALLING_CID); + + /* Put in L2CAP command header */ + UINT8_TO_STREAM (p, L2CAP_CMD_CONFIG_RSP); + UINT8_TO_STREAM (p, p_ccb->remote_id); + + UINT16_TO_STREAM (p, L2CAP_CONFIG_RSP_LEN + rej_len); + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, 0); /* Flags = 0 (no continuation) */ + UINT16_TO_STREAM (p, L2CAP_CFG_UNKNOWN_OPTIONS); + + /* Now, put the rejected options */ + p_data_end = p_data + data_len; + while (p_data < p_data_end) + { + cfg_code = *p_data; + cfg_len = *(p_data + 1); + + switch (cfg_code & 0x7F) + { + /* skip known options */ + case L2CAP_CFG_TYPE_MTU: + case L2CAP_CFG_TYPE_FLUSH_TOUT: + case L2CAP_CFG_TYPE_QOS: + p_data += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + break; + + /* unknown options; copy into rsp if not hints */ + default: + /* sanity check option length */ + if ((cfg_len + L2CAP_CFG_OPTION_OVERHEAD) <= data_len) + { + if ((cfg_code & 0x80) == 0) + { + memcpy(p, p_data, cfg_len + L2CAP_CFG_OPTION_OVERHEAD); + p += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + } + p_data += cfg_len + L2CAP_CFG_OPTION_OVERHEAD; + } + /* bad length; force loop exit */ + else + { + p_data = p_data_end; + } + break; + } + } + + len = (UINT16) (p - p_hci_len - 2); + UINT16_TO_STREAM (p_hci_len, len); + + p_buf->len = len + 4; + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_disc_req +** +** Description Build and send an L2CAP "disconnect request" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_disc_req (tL2C_CCB *p_ccb) +{ + BT_HDR *p_buf, *p_buf2; + UINT8 *p; + + /* Create an identifier for this packet */ + p_ccb->p_lcb->id++; + l2cu_adj_id(p_ccb->p_lcb, L2CAP_ADJ_ID); + + p_ccb->local_id = p_ccb->p_lcb->id; + + if ((p_buf = l2cu_build_header(p_ccb->p_lcb, L2CAP_DISC_REQ_LEN, L2CAP_CMD_DISC_REQ, p_ccb->local_id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for disc_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, p_ccb->remote_cid); + UINT16_TO_STREAM (p, p_ccb->local_cid); + + /* Move all queued data packets to the LCB. In FCR mode, assume the higher + layer checks that all buffers are sent before disconnecting. + */ + if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE) + { + while (p_ccb->xmit_hold_q.p_first) + { + p_buf2 = (BT_HDR *)GKI_dequeue (&p_ccb->xmit_hold_q); + l2cu_set_acl_hci_header (p_buf2, p_ccb); + l2c_link_check_send_pkts (p_ccb->p_lcb, p_ccb, p_buf2); + } + } + + l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_disc_rsp +** +** Description Build and send an L2CAP "disconnect response" message +** to the peer. +** +** This function is passed the parameters for the disconnect +** response instead of the CCB address, as it may be called +** to send a disconnect response when there is no CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_disc_rsp (tL2C_LCB *p_lcb, UINT8 remote_id, UINT16 local_cid, + UINT16 remote_cid) +{ + BT_HDR *p_buf; + UINT8 *p; + + if ((p_buf=l2cu_build_header(p_lcb, L2CAP_DISC_RSP_LEN, L2CAP_CMD_DISC_RSP, remote_id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for disc_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, local_cid); + UINT16_TO_STREAM (p, remote_cid); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_echo_req +** +** Description Build and send an L2CAP "echo request" message +** to the peer. Note that we do not currently allow +** data in the echo request. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_echo_req (tL2C_LCB *p_lcb, UINT8 *p_data, UINT16 data_len) +{ + BT_HDR *p_buf; + UINT8 *p; + + p_lcb->id++; + l2cu_adj_id(p_lcb, L2CAP_ADJ_ZERO_ID); /* check for wrap to '0' */ + + if ((p_buf = l2cu_build_header(p_lcb, (UINT16) (L2CAP_ECHO_REQ_LEN + data_len), L2CAP_CMD_ECHO_REQ, p_lcb->id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for echo_req"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + if (data_len) + { + ARRAY_TO_STREAM (p, p_data, data_len); + } + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_echo_rsp +** +** Description Build and send an L2CAP "echo response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_echo_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len) +{ + BT_HDR *p_buf; + UINT8 *p; + UINT16 maxlen; + + /* Don't return data if it does not fit in ACL and L2CAP MTU */ + maxlen = (GKI_get_pool_bufsize(L2CAP_CMD_POOL_ID) > btu_cb.hcit_acl_pkt_size) ? + btu_cb.hcit_acl_data_size : (UINT16)GKI_get_pool_bufsize(L2CAP_CMD_POOL_ID); + maxlen -= (UINT16)(BT_HDR_SIZE + HCI_DATA_PREAMBLE_SIZE + L2CAP_PKT_OVERHEAD + + L2CAP_CMD_OVERHEAD + L2CAP_ECHO_RSP_LEN); + + if (data_len > maxlen) + data_len = 0; + + if ((p_buf = l2cu_build_header (p_lcb, (UINT16)(L2CAP_ECHO_RSP_LEN + data_len), L2CAP_CMD_ECHO_RSP, id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for echo_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + if (data_len) + { + ARRAY_TO_STREAM (p, p_data, data_len); + } + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_info_req +** +** Description Build and send an L2CAP "info request" message +** to the peer. +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_info_req (tL2C_LCB *p_lcb, UINT16 info_type) +{ + BT_HDR *p_buf; + UINT8 *p; + + /* check for wrap and/or BRCM ID */ + p_lcb->id++; + l2cu_adj_id(p_lcb, L2CAP_ADJ_ID); + + if ((p_buf = l2cu_build_header(p_lcb, 2, L2CAP_CMD_INFO_REQ, p_lcb->id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for info_req"); + return; + } + + L2CAP_TRACE_EVENT1 ("l2cu_send_peer_info_req: type 0x%04x", info_type); + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET+HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, info_type); + + p_lcb->w4_info_rsp = TRUE; + btu_start_timer (&p_lcb->info_timer_entry, BTU_TTYPE_L2CAP_INFO, L2CAP_WAIT_INFO_RSP_TOUT); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + + +/******************************************************************************* +** +** Function l2cu_send_peer_info_rsp +** +** Description Build and send an L2CAP "info response" message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_info_rsp (tL2C_LCB *p_lcb, UINT8 remote_id, UINT16 info_type) +{ + BT_HDR *p_buf; + UINT8 *p; + UINT16 len = L2CAP_INFO_RSP_LEN; + +#if (L2CAP_CONFORMANCE_TESTING == TRUE) + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (l2cb.test_info_resp & (L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE | + L2CAP_EXTFEA_NO_CRC | L2CAP_EXTFEA_EXT_FLOW_SPEC | + L2CAP_EXTFEA_FIXED_CHNLS | L2CAP_EXTFEA_EXT_WINDOW | + L2CAP_EXTFEA_UCD_RECEPTION )) ) +#else + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (L2CAP_EXTFEA_SUPPORTED_MASK & (L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE | + L2CAP_EXTFEA_NO_CRC |L2CAP_EXTFEA_FIXED_CHNLS | + L2CAP_EXTFEA_UCD_RECEPTION )) ) +#endif + { + len += L2CAP_EXTENDED_FEATURES_ARRAY_SIZE; + } + else if (info_type == L2CAP_FIXED_CHANNELS_INFO_TYPE) + { + len += L2CAP_FIXED_CHNL_ARRAY_SIZE; + } + else if (info_type == L2CAP_CONNLESS_MTU_INFO_TYPE) + { + len += L2CAP_CONNLESS_MTU_INFO_SIZE; + } + + if ((p_buf = l2cu_build_header(p_lcb, len, L2CAP_CMD_INFO_RSP, remote_id)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no buffer for info_rsp"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, info_type); + +#if (L2CAP_CONFORMANCE_TESTING == TRUE) + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (l2cb.test_info_resp & ( L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE + | L2CAP_EXTFEA_UCD_RECEPTION )) ) +#else + if ((info_type == L2CAP_EXTENDED_FEATURES_INFO_TYPE) + && (L2CAP_EXTFEA_SUPPORTED_MASK & ( L2CAP_EXTFEA_ENH_RETRANS | L2CAP_EXTFEA_STREAM_MODE + | L2CAP_EXTFEA_UCD_RECEPTION )) ) +#endif + { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_SUCCESS); +#if (BLE_INCLUDED == TRUE) + if (p_lcb->is_ble_link) + { + /* optional data are not added for now */ + UINT32_TO_STREAM (p, L2CAP_BLE_EXTFEA_MASK); + } + else +#endif + { +#if L2CAP_CONFORMANCE_TESTING == TRUE + UINT32_TO_STREAM (p, l2cb.test_info_resp); +#else +#if (L2CAP_NUM_FIXED_CHNLS > 0) + UINT32_TO_STREAM (p, L2CAP_EXTFEA_SUPPORTED_MASK | L2CAP_EXTFEA_FIXED_CHNLS); +#else + UINT32_TO_STREAM (p, L2CAP_EXTFEA_SUPPORTED_MASK); +#endif +#endif + } + } + else if (info_type == L2CAP_FIXED_CHANNELS_INFO_TYPE) + { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_SUCCESS); + memset (p, 0, L2CAP_FIXED_CHNL_ARRAY_SIZE); + + p[0] = L2CAP_FIXED_CHNL_SIG_BIT; + + if ( L2CAP_EXTFEA_SUPPORTED_MASK & L2CAP_EXTFEA_UCD_RECEPTION ) + p[0] |= L2CAP_FIXED_CHNL_CNCTLESS_BIT; + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + { + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) + if (l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb != NULL) + p[0] |= 1 << (xx + L2CAP_FIRST_FIXED_CHNL); + } +#endif + } + else if (info_type == L2CAP_CONNLESS_MTU_INFO_TYPE) + { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_SUCCESS); + UINT16_TO_STREAM (p, L2CAP_UCD_MTU); + } + else + { + UINT16_TO_STREAM (p, L2CAP_INFO_RESP_RESULT_NOT_SUPPORTED); /* 'not supported' */ + } + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/****************************************************************************** +** +** Function l2cu_enqueue_ccb +** +** Description queue CCB by priority. The first CCB is highest priority and +** is served at first. The CCB is queued to an LLCB or an LCB. +** +** Returns None +** +*******************************************************************************/ +void l2cu_enqueue_ccb (tL2C_CCB *p_ccb) +{ + tL2C_CCB *p_ccb1; + tL2C_CCB_Q *p_q = NULL; + + /* Find out which queue the channel is on + */ + if (p_ccb->p_lcb != NULL) + p_q = &p_ccb->p_lcb->ccb_queue; + + if ( (!p_ccb->in_use) || (p_q == NULL) ) + { + L2CAP_TRACE_ERROR3 ("l2cu_enqueue_ccb CID: 0x%04x ERROR in_use: %u p_lcb: 0x%08x", + p_ccb->local_cid, p_ccb->in_use, p_ccb->p_lcb); + return; + } + + L2CAP_TRACE_DEBUG2 ("l2cu_enqueue_ccb CID: 0x%04x priority: %d", + p_ccb->local_cid, p_ccb->ccb_priority); + + /* If the queue is empty, we go at the front */ + if (!p_q->p_first_ccb) + { + p_q->p_first_ccb = p_q->p_last_ccb = p_ccb; + p_ccb->p_next_ccb = p_ccb->p_prev_ccb = NULL; + } + else + { + p_ccb1 = p_q->p_first_ccb; + + while (p_ccb1 != NULL) + { + /* Insert new ccb at the end of the same priority. Lower number, higher priority */ + if (p_ccb->ccb_priority < p_ccb1->ccb_priority) + { + /* Are we at the head of the queue ? */ + if (p_ccb1 == p_q->p_first_ccb) + p_q->p_first_ccb = p_ccb; + else + p_ccb1->p_prev_ccb->p_next_ccb = p_ccb; + + p_ccb->p_next_ccb = p_ccb1; + p_ccb->p_prev_ccb = p_ccb1->p_prev_ccb; + p_ccb1->p_prev_ccb = p_ccb; + break; + } + + p_ccb1 = p_ccb1->p_next_ccb; + } + + /* If we are lower then anyone in the list, we go at the end */ + if (!p_ccb1) + { + /* add new ccb at the end of the list */ + p_q->p_last_ccb->p_next_ccb = p_ccb; + + p_ccb->p_next_ccb = NULL; + p_ccb->p_prev_ccb = p_q->p_last_ccb; + p_q->p_last_ccb = p_ccb; + } + } + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* Adding CCB into round robin service table of its LCB */ + if (p_ccb->p_lcb != NULL) + { + /* if this is the first channel in this priority group */ + if (p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb == 0 ) + { + /* Set the first channel to this CCB */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = p_ccb; + /* Set the next serving channel in this group to this CCB */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = p_ccb; + /* Initialize quota of this priority group based on its priority */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota = L2CAP_GET_PRIORITY_QUOTA(p_ccb->ccb_priority); + } + /* increase number of channels in this group */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb++; + } +#endif + +} + +/****************************************************************************** +** +** Function l2cu_dequeue_ccb +** +** Description dequeue CCB from a queue +** +** Returns - +** +*******************************************************************************/ +void l2cu_dequeue_ccb (tL2C_CCB *p_ccb) +{ + tL2C_CCB_Q *p_q = NULL; + + L2CAP_TRACE_DEBUG1 ("l2cu_dequeue_ccb CID: 0x%04x", p_ccb->local_cid); + + /* Find out which queue the channel is on + */ + if (p_ccb->p_lcb != NULL) + p_q = &p_ccb->p_lcb->ccb_queue; + + if ( (!p_ccb->in_use) || (p_q == NULL) || (p_q->p_first_ccb == NULL) ) + { + L2CAP_TRACE_ERROR5 ("l2cu_dequeue_ccb CID: 0x%04x ERROR in_use: %u p_lcb: 0x%08x p_q: 0x%08x p_q->p_first_ccb: 0x%08x", + p_ccb->local_cid, p_ccb->in_use, p_ccb->p_lcb, p_q, p_q ? p_q->p_first_ccb : 0); + return; + } + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* Removing CCB from round robin service table of its LCB */ + if (p_ccb->p_lcb != NULL) + { + /* decrease number of channels in this priority group */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb--; + + /* if it was the last channel in the priority group */ + if (p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb == 0 ) + { + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = NULL; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = NULL; + } + else + { + /* if it is the first channel of this group */ + if ( p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb == p_ccb ) + { + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = p_ccb->p_next_ccb; + } + /* if it is the next serving channel of this group */ + if ( p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb == p_ccb ) + { + /* simply, start serving from the first channel */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb + = p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb; + } + } + } +#endif + + if (p_ccb == p_q->p_first_ccb) + { + /* We are removing the first in a queue */ + p_q->p_first_ccb = p_ccb->p_next_ccb; + + if (p_q->p_first_ccb) + p_q->p_first_ccb->p_prev_ccb = NULL; + else + p_q->p_last_ccb = NULL; + } + else if (p_ccb == p_q->p_last_ccb) + { + /* We are removing the last in a queue */ + p_q->p_last_ccb = p_ccb->p_prev_ccb; + p_q->p_last_ccb->p_next_ccb = NULL; + } + else + { + /* In the middle of a chain. */ + p_ccb->p_prev_ccb->p_next_ccb = p_ccb->p_next_ccb; + p_ccb->p_next_ccb->p_prev_ccb = p_ccb->p_prev_ccb; + } + + p_ccb->p_next_ccb = p_ccb->p_prev_ccb = NULL; +} + +/****************************************************************************** +** +** Function l2cu_change_pri_ccb +** +** Description +** +** Returns - +** +*******************************************************************************/ +void l2cu_change_pri_ccb (tL2C_CCB *p_ccb, tL2CAP_CHNL_PRIORITY priority) +{ + if (p_ccb->ccb_priority != priority) + { + /* If CCB is not the only guy on the queue */ + if ( (p_ccb->p_next_ccb != NULL) || (p_ccb->p_prev_ccb != NULL) ) + { + L2CAP_TRACE_DEBUG0 ("Update CCB list in logical link"); + + /* Remove CCB from queue and re-queue it at new priority */ + l2cu_dequeue_ccb (p_ccb); + + p_ccb->ccb_priority = priority; + l2cu_enqueue_ccb (p_ccb); + } +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + else + { + /* If CCB is the only guy on the queue, no need to re-enqueue */ + /* update only round robin service data */ + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb = 0; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = NULL; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = NULL; + + p_ccb->ccb_priority = priority; + + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_first_ccb = p_ccb; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].p_serve_ccb = p_ccb; + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota = L2CAP_GET_PRIORITY_QUOTA(p_ccb->ccb_priority); + p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].num_ccb = 1; + } +#endif + } +} + +/******************************************************************************* +** +** Function l2cu_allocate_ccb +** +** Description This function allocates a Channel Control Block and +** attaches it to a link control block. The local CID +** is also assigned. +** +** Returns pointer to CCB, or NULL if none +** +*******************************************************************************/ +tL2C_CCB *l2cu_allocate_ccb (tL2C_LCB *p_lcb, UINT16 cid) +{ + tL2C_CCB *p_ccb; + tL2C_CCB *p_prev; + + L2CAP_TRACE_DEBUG1 ("l2cu_allocate_ccb: cid 0x%04x", cid); + + if (!l2cb.p_free_ccb_first) + return (NULL); + + /* If a CID was passed in, use that, else take the first free one */ + if (cid == 0) + { + p_ccb = l2cb.p_free_ccb_first; + l2cb.p_free_ccb_first = p_ccb->p_next_ccb; + } + else + { + p_prev = NULL; + + p_ccb = &l2cb.ccb_pool[cid - L2CAP_BASE_APPL_CID]; + + if (p_ccb == l2cb.p_free_ccb_first) + l2cb.p_free_ccb_first = p_ccb->p_next_ccb; + else + { + for (p_prev = l2cb.p_free_ccb_first; p_prev != NULL; p_prev = p_prev->p_next_ccb) + { + if (p_prev->p_next_ccb == p_ccb) + { + p_prev->p_next_ccb = p_ccb->p_next_ccb; + + if (p_ccb == l2cb.p_free_ccb_last) + l2cb.p_free_ccb_last = p_prev; + + break; + } + } + if (p_prev == NULL) + { + L2CAP_TRACE_ERROR1 ("l2cu_allocate_ccb: could not find CCB for CID 0x%04x in the free list", cid); + return NULL; + } + } + } + + p_ccb->p_next_ccb = p_ccb->p_prev_ccb = NULL; + + p_ccb->in_use = TRUE; + + /* Get a CID for the connection */ + p_ccb->local_cid = L2CAP_BASE_APPL_CID + (UINT16)(p_ccb - l2cb.ccb_pool); + + p_ccb->p_lcb = p_lcb; + p_ccb->p_rcb = NULL; + + /* Set priority then insert ccb into LCB queue (if we have an LCB) */ + p_ccb->ccb_priority = L2CAP_CHNL_PRIORITY_LOW; + + if (p_lcb) + l2cu_enqueue_ccb (p_ccb); + + /* clear what peer wants to configure */ + p_ccb->peer_cfg_bits = 0; + + /* Put in default values for configuration */ + memset (&p_ccb->our_cfg, 0, sizeof(tL2CAP_CFG_INFO)); + memset (&p_ccb->peer_cfg, 0, sizeof(tL2CAP_CFG_INFO)); + + /* Put in default values for local/peer configurations */ + p_ccb->our_cfg.flush_to = p_ccb->peer_cfg.flush_to = L2CAP_DEFAULT_FLUSH_TO; + p_ccb->our_cfg.mtu = p_ccb->peer_cfg.mtu = L2CAP_DEFAULT_MTU; + p_ccb->our_cfg.qos.service_type = p_ccb->peer_cfg.qos.service_type = L2CAP_DEFAULT_SERV_TYPE; + p_ccb->our_cfg.qos.token_rate = p_ccb->peer_cfg.qos.token_rate = L2CAP_DEFAULT_TOKEN_RATE; + p_ccb->our_cfg.qos.token_bucket_size = p_ccb->peer_cfg.qos.token_bucket_size = L2CAP_DEFAULT_BUCKET_SIZE; + p_ccb->our_cfg.qos.peak_bandwidth = p_ccb->peer_cfg.qos.peak_bandwidth = L2CAP_DEFAULT_PEAK_BANDWIDTH; + p_ccb->our_cfg.qos.latency = p_ccb->peer_cfg.qos.latency = L2CAP_DEFAULT_LATENCY; + p_ccb->our_cfg.qos.delay_variation = p_ccb->peer_cfg.qos.delay_variation = L2CAP_DEFAULT_DELAY; + + p_ccb->bypass_fcs = 0; + memset (&p_ccb->ertm_info, 0, sizeof(tL2CAP_ERTM_INFO)); + p_ccb->peer_cfg_already_rejected = FALSE; + p_ccb->fcr_cfg_tries = L2CAP_MAX_FCR_CFG_TRIES; + p_ccb->fcrb.ack_timer.param = (TIMER_PARAM_TYPE)p_ccb; + + /* if timer is running, remove it from timer list */ + if (p_ccb->fcrb.ack_timer.in_use) + btu_stop_quick_timer (&p_ccb->fcrb.ack_timer); + + p_ccb->fcrb.mon_retrans_timer.param = (TIMER_PARAM_TYPE)p_ccb; + +// btla-specific ++ + /* CSP408639 Fix: When L2CAP send amp move channel request or receive + * L2CEVT_AMP_MOVE_REQ do following sequence. Send channel move + * request -> Stop retrans/monitor timer -> Change channel state to CST_AMP_MOVING. */ + if (p_ccb->fcrb.mon_retrans_timer.in_use) + btu_stop_quick_timer (&p_ccb->fcrb.mon_retrans_timer); +// btla-specific -- + + l2c_fcr_stop_timer (p_ccb); + + p_ccb->ertm_info.preferred_mode = L2CAP_FCR_BASIC_MODE; /* Default mode for channel is basic mode */ + p_ccb->ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_BASIC; /* Default mode for channel is basic mode */ + p_ccb->ertm_info.fcr_rx_pool_id = L2CAP_FCR_RX_POOL_ID; + p_ccb->ertm_info.fcr_tx_pool_id = L2CAP_FCR_TX_POOL_ID; + p_ccb->ertm_info.user_rx_pool_id = HCI_ACL_POOL_ID; + p_ccb->ertm_info.user_tx_pool_id = HCI_ACL_POOL_ID; + p_ccb->max_rx_mtu = L2CAP_MTU_SIZE; + p_ccb->tx_mps = GKI_get_pool_bufsize(HCI_ACL_POOL_ID) - 32; + + GKI_init_q (&p_ccb->xmit_hold_q); + + p_ccb->cong_sent = FALSE; + p_ccb->buff_quota = 2; /* This gets set after config */ + + /* If CCB was reserved Config_Done can already have some value */ + if (cid == 0) + p_ccb->config_done = 0; + else + { + L2CAP_TRACE_DEBUG2 ("l2cu_allocate_ccb: cid 0x%04x config_done:0x%x", cid, p_ccb->config_done); + } + + p_ccb->chnl_state = CST_CLOSED; + p_ccb->flags = 0; + p_ccb->tx_data_rate = L2CAP_CHNL_DATA_RATE_LOW; + p_ccb->rx_data_rate = L2CAP_CHNL_DATA_RATE_LOW; + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + p_ccb->is_flushable = FALSE; +#endif + + p_ccb->timer_entry.param = (TIMER_PARAM_TYPE)p_ccb; + p_ccb->timer_entry.in_use = 0; + + l2c_link_adjust_chnl_allocation (); + + return (p_ccb); +} + +/******************************************************************************* +** +** Function l2cu_start_post_bond_timer +** +** Description This function starts the ACL Link inactivity timer after +** dedicated bonding +** This timer can be longer than the normal link inactivity +** timer for some platforms. +** +** Returns BOOLEAN - TRUE if idle timer started or disconnect initiated +** FALSE if there's one or more pending CCB's exist +** +*******************************************************************************/ +BOOLEAN l2cu_start_post_bond_timer (UINT16 handle) +{ + UINT16 timeout; + tL2C_LCB *p_lcb = l2cu_find_lcb_by_handle(handle); + + if (!p_lcb) + return (TRUE); + + p_lcb->is_bonding = FALSE; + + /* Only start timer if no control blocks allocated */ + if (p_lcb->ccb_queue.p_first_ccb != NULL) + return (FALSE); + + /* If no channels on the connection, start idle timeout */ + if ( (p_lcb->link_state == LST_CONNECTED) || (p_lcb->link_state == LST_CONNECTING) || (p_lcb->link_state == LST_DISCONNECTING) ) + { + if (p_lcb->idle_timeout == 0) + { + if (btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER)) + { + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } + else + timeout = BT_1SEC_TIMEOUT; + } + else + { + timeout = L2CAP_BONDING_TIMEOUT; + } + + if (timeout != 0xFFFF) + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, timeout); + + return (TRUE); + } + + return (FALSE); +} + +/******************************************************************************* +** +** Function l2cu_release_ccb +** +** Description This function releases a Channel Control Block. The timer +** is stopped, any attached buffers freed, and the CCB is removed +** from the link control block. +** +** Returns void +** +*******************************************************************************/ +void l2cu_release_ccb (tL2C_CCB *p_ccb) +{ + tL2C_LCB *p_lcb = p_ccb->p_lcb; + tL2C_RCB *p_rcb = p_ccb->p_rcb; + + L2CAP_TRACE_DEBUG2 ("l2cu_release_ccb: cid 0x%04x in_use: %u", p_ccb->local_cid, p_ccb->in_use); + + /* If already released, could be race condition */ + if (!p_ccb->in_use) + return; + + if (p_rcb && (p_rcb->psm != p_rcb->real_psm)) + { + btm_sec_clr_service_by_psm(p_rcb->psm); + } + /* Stop the timer */ + btu_stop_timer (&p_ccb->timer_entry); + + while (p_ccb->xmit_hold_q.p_first) + GKI_freebuf (GKI_dequeue (&p_ccb->xmit_hold_q)); + + l2c_fcr_cleanup (p_ccb); + + /* Channel may not be assigned to any LCB if it was just pre-reserved */ + if ( (p_lcb) && + ( (p_ccb->local_cid >= L2CAP_BASE_APPL_CID) +#if (L2CAP_UCD_INCLUDED == TRUE) + ||(p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID) +#endif + ) + ) + { + l2cu_dequeue_ccb (p_ccb); + + /* Delink the CCB from the LCB */ + p_ccb->p_lcb = NULL; + } + + /* Put the CCB back on the free pool */ + if (!l2cb.p_free_ccb_first) + { + l2cb.p_free_ccb_first = p_ccb; + l2cb.p_free_ccb_last = p_ccb; + p_ccb->p_next_ccb = NULL; + p_ccb->p_prev_ccb = NULL; + } + else + { + p_ccb->p_next_ccb = NULL; + p_ccb->p_prev_ccb = l2cb.p_free_ccb_last; + l2cb.p_free_ccb_last->p_next_ccb = p_ccb; + l2cb.p_free_ccb_last = p_ccb; + } + + /* Flag as not in use */ + p_ccb->in_use = FALSE; + + /* If no channels on the connection, start idle timeout */ + if ((p_lcb) && p_lcb->in_use && (p_lcb->link_state == LST_CONNECTED)) + { + if (!p_lcb->ccb_queue.p_first_ccb) + { + l2cu_no_dynamic_ccbs (p_lcb); + } + else + { + /* Link is still active, adjust channel quotas. */ + l2c_link_adjust_chnl_allocation (); + } + } +} + +/******************************************************************************* +** +** Function l2cu_find_ccb_by_remote_cid +** +** Description Look through all active CCBs on a link for a match based +** on the remote CID. +** +** Returns pointer to matched CCB, or NULL if no match +** +*******************************************************************************/ +tL2C_CCB *l2cu_find_ccb_by_remote_cid (tL2C_LCB *p_lcb, UINT16 remote_cid) +{ + tL2C_CCB *p_ccb; + + /* If LCB is NULL, look through all active links */ + if (!p_lcb) + { + return NULL; + } + else + { + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + if ((p_ccb->in_use) && (p_ccb->remote_cid == remote_cid)) + return (p_ccb); + } + + /* If here, no match found */ + return (NULL); +} + +/******************************************************************************* +** +** Function l2cu_allocate_rcb +** +** Description Look through the Registration Control Blocks for a free +** one. +** +** Returns Pointer to the RCB or NULL if not found +** +*******************************************************************************/ +tL2C_RCB *l2cu_allocate_rcb (UINT16 psm) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if (!p_rcb->in_use) + { + p_rcb->in_use = TRUE; + p_rcb->psm = psm; +#if (L2CAP_UCD_INCLUDED == TRUE) + p_rcb->ucd.state = L2C_UCD_STATE_UNUSED; +#endif + return (p_rcb); + } + } + + /* If here, no free RCB found */ + return (NULL); +} + + +/******************************************************************************* +** +** Function l2cu_release_rcb +** +** Description Mark an RCB as no longet in use +** +** Returns void +** +*******************************************************************************/ +void l2cu_release_rcb (tL2C_RCB *p_rcb) +{ + p_rcb->in_use = FALSE; + p_rcb->psm = 0; +} + + +/******************************************************************************* +** +** Function l2cu_disconnect_chnl +** +** Description Disconnect a channel. Typically, this is due to either +** receiving a bad configuration, bad packet or max_retries expiring. +** +*******************************************************************************/ +void l2cu_disconnect_chnl (tL2C_CCB *p_ccb) +{ + UINT16 local_cid = p_ccb->local_cid; + + if (local_cid >= L2CAP_BASE_APPL_CID) + { + tL2CA_DISCONNECT_IND_CB *p_disc_cb = p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb; + + L2CAP_TRACE_WARNING1 ("L2CAP - disconnect_chnl CID: 0x%04x", local_cid); + + l2cu_send_peer_disc_req (p_ccb); + + l2cu_release_ccb (p_ccb); + + (*p_disc_cb)(local_cid, FALSE); + } + else + { + /* failure on the AMP channel, probably need to disconnect ACL */ + L2CAP_TRACE_ERROR1 ("L2CAP - disconnect_chnl CID: 0x%04x Ignored", local_cid); + } +} + + +/******************************************************************************* +** +** Function l2cu_find_rcb_by_psm +** +** Description Look through the Registration Control Blocks to see if +** anyone registered to handle the PSM in question +** +** Returns Pointer to the RCB or NULL if not found +** +*******************************************************************************/ +tL2C_RCB *l2cu_find_rcb_by_psm (UINT16 psm) +{ + tL2C_RCB *p_rcb = &l2cb.rcb_pool[0]; + UINT16 xx; + + for (xx = 0; xx < MAX_L2CAP_CLIENTS; xx++, p_rcb++) + { + if ((p_rcb->in_use) && (p_rcb->psm == psm)) + return (p_rcb); + } + + /* If here, no match found */ + return (NULL); +} + + +/******************************************************************************* +** +** Function l2cu_process_peer_cfg_req +** +** Description This function is called when the peer sends us a "config request" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Note: Negotiation of the FCR channel type is handled internally, +** all others are passed to the upper layer. +** +** Returns UINT8 - L2CAP_PEER_CFG_OK if passed to upper layer, +** L2CAP_PEER_CFG_UNACCEPTABLE if automatically responded to +** because parameters are unnacceptable from a specification +** point of view. +** L2CAP_PEER_CFG_DISCONNECT if no compatible channel modes +** between the two devices, and shall be closed. +** +*******************************************************************************/ +UINT8 l2cu_process_peer_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + BOOLEAN mtu_ok = TRUE; + BOOLEAN qos_type_ok = TRUE; + BOOLEAN flush_to_ok = TRUE; + BOOLEAN fcr_ok = TRUE; + UINT8 fcr_status; + + /* Ignore FCR parameters for basic mode */ + if (!p_cfg->fcr_present) + p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE; + + /* Save the MTU that our peer can receive */ + if (p_cfg->mtu_present) + { + /* Make sure MTU is at least the minimum */ + if (p_cfg->mtu >= L2CAP_MIN_MTU) + { + /* In basic mode, limit the MTU to our buffer size */ + if ( (p_cfg->fcr_present == FALSE) && (p_cfg->mtu > L2CAP_MTU_SIZE) ) + p_cfg->mtu = L2CAP_MTU_SIZE; + + /* Save the accepted value in case of renegotiation */ + p_ccb->peer_cfg.mtu = p_cfg->mtu; + p_ccb->peer_cfg.mtu_present = TRUE; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_MTU; + } + else /* Illegal MTU value */ + { + p_cfg->mtu = L2CAP_MIN_MTU; + mtu_ok = FALSE; + } + } + /* Reload mtu from a previously accepted config request */ + else if (p_ccb->peer_cfg.mtu_present) + { + p_cfg->mtu_present = TRUE; + p_cfg->mtu = p_ccb->peer_cfg.mtu; + } + + /* Verify that the flush timeout is a valid value (0 is illegal) */ + if (p_cfg->flush_to_present) + { + if (!p_cfg->flush_to) + { + p_cfg->flush_to = 0xFFFF; /* Infinite retransmissions (spec default) */ + flush_to_ok = FALSE; + } + else /* Save the accepted value in case of renegotiation */ + { + p_ccb->peer_cfg.flush_to_present = TRUE; + p_ccb->peer_cfg.flush_to = p_cfg->flush_to; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_FLUSH_TO; + } + } + /* Reload flush_to from a previously accepted config request */ + else if (p_ccb->peer_cfg.flush_to_present) + { + p_cfg->flush_to_present = TRUE; + p_cfg->flush_to = p_ccb->peer_cfg.flush_to; + } + + /* Save the QOS settings the the peer is using */ + if (p_cfg->qos_present) + { + /* Make sure service type is not a reserved value; otherwise let upper + layer decide if acceptable + */ + if (p_cfg->qos.service_type <= GUARANTEED) + { + p_ccb->peer_cfg.qos = p_cfg->qos; + p_ccb->peer_cfg.qos_present = TRUE; + p_ccb->peer_cfg_bits |= L2CAP_CH_CFG_MASK_QOS; + } + else /* Illegal service type value */ + { + p_cfg->qos.service_type = BEST_EFFORT; + qos_type_ok = FALSE; + } + } + /* Reload QOS from a previously accepted config request */ + else if (p_ccb->peer_cfg.qos_present) + { + p_cfg->qos_present = TRUE; + p_cfg->qos = p_ccb->peer_cfg.qos; + } + + if ((fcr_status = l2c_fcr_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_DISCONNECT) + { + /* Notify caller to disconnect the channel (incompatible modes) */ + p_cfg->result = L2CAP_CFG_FAILED_NO_REASON; + p_cfg->mtu_present = p_cfg->qos_present = p_cfg->flush_to_present = 0; + + return (L2CAP_PEER_CFG_DISCONNECT); + } + + fcr_ok = (fcr_status == L2CAP_PEER_CFG_OK); + + /* Return any unacceptable parameters */ + if (mtu_ok && flush_to_ok && qos_type_ok && fcr_ok) + { + l2cu_adjust_out_mps (p_ccb); + return (L2CAP_PEER_CFG_OK); + } + else + { + p_cfg->result = L2CAP_CFG_UNACCEPTABLE_PARAMS; + + if (mtu_ok) + p_cfg->mtu_present = FALSE; + if (flush_to_ok) + p_cfg->flush_to_present = FALSE; + if (qos_type_ok) + p_cfg->qos_present = FALSE; + if (fcr_ok) + p_cfg->fcr_present = FALSE; + + return (L2CAP_PEER_CFG_UNACCEPTABLE); + } +} + + +/******************************************************************************* +** +** Function l2cu_process_peer_cfg_rsp +** +** Description This function is called when the peer sends us a "config response" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_process_peer_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + /* If we wanted QoS and the peer sends us a positive response with QoS, use his values */ + if ( (p_cfg->qos_present) && (p_ccb->our_cfg.qos_present) ) + p_ccb->our_cfg.qos = p_cfg->qos; + + if (p_cfg->fcr_present) + { + /* Save the retransmission and monitor timeout values */ + if (p_cfg->fcr.mode == L2CAP_FCR_ERTM_MODE) + { + p_ccb->peer_cfg.fcr.rtrans_tout = p_cfg->fcr.rtrans_tout; + p_ccb->peer_cfg.fcr.mon_tout = p_cfg->fcr.mon_tout; + } + + /* Calculate the max number of packets for which we can delay sending an ack */ + if (p_cfg->fcr.tx_win_sz < p_ccb->our_cfg.fcr.tx_win_sz) + p_ccb->fcrb.max_held_acks = p_cfg->fcr.tx_win_sz / 3; + else + p_ccb->fcrb.max_held_acks = p_ccb->our_cfg.fcr.tx_win_sz / 3; + + L2CAP_TRACE_DEBUG3 ("l2cu_process_peer_cfg_rsp(): peer tx_win_sz: %d, our tx_win_sz: %d, max_held_acks: %d", + p_cfg->fcr.tx_win_sz, p_ccb->our_cfg.fcr.tx_win_sz, p_ccb->fcrb.max_held_acks); + } +} + +/******************************************************************************* +** +** Function l2cu_process_our_cfg_req +** +** Description This function is called when we send a "config request" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_process_our_cfg_req (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + tL2C_LCB *p_lcb; + UINT16 hci_flush_to; + + /* Save the QOS settings we are using for transmit */ + if (p_cfg->qos_present) + { + p_ccb->our_cfg.qos_present = TRUE; + p_ccb->our_cfg.qos = p_cfg->qos; + } + + if (p_cfg->fcr_present) + { + /* Override FCR options if attempting streaming or basic */ + if (p_cfg->fcr.mode == L2CAP_FCR_BASIC_MODE) + memset(&p_cfg->fcr, 0, sizeof(tL2CAP_FCR_OPTS)); + else + { + /* On BR/EDR, timer values are zero in config request */ + /* On class 2 AMP, timer value in config request shall be non-0 processing time */ + /* timer value in config response shall be greater than received processing time */ + p_cfg->fcr.mon_tout = p_cfg->fcr.rtrans_tout = 0; + + if (p_cfg->fcr.mode == L2CAP_FCR_STREAM_MODE) + p_cfg->fcr.max_transmit = p_cfg->fcr.tx_win_sz = 0; + } + + /* Set the threshold to send acks (may be updated in the cfg response) */ + p_ccb->fcrb.max_held_acks = p_cfg->fcr.tx_win_sz / 3; + + /* Include FCS option only if peer can handle it */ + if (p_ccb->p_lcb->peer_ext_fea & L2CAP_EXTFEA_NO_CRC) + { + /* FCS check can be bypassed if peer also desires to bypass */ + if (p_cfg->fcs_present && p_cfg->fcs == L2CAP_CFG_FCS_BYPASS) + p_ccb->bypass_fcs |= L2CAP_CFG_FCS_OUR; + } + else + p_cfg->fcs_present = FALSE; + } + else + { + p_cfg->fcr.mode = L2CAP_FCR_BASIC_MODE; + } + + p_ccb->our_cfg.fcr.mode = p_cfg->fcr.mode; + p_ccb->our_cfg.fcr_present = p_cfg->fcr_present; + + /* Check the flush timeout. If it is lower than the current one used */ + /* then we need to adjust the flush timeout sent to the controller */ + if (p_cfg->flush_to_present) + { + if ((p_cfg->flush_to == 0)||(p_cfg->flush_to == L2CAP_NO_AUTOMATIC_FLUSH)) + { + /* don't send invalid flush timeout */ + /* SPEC: The sender of the Request shall specify its flush timeout value */ + /* if it differs from the default value of 0xFFFF */ + p_cfg->flush_to_present = FALSE; + } + else + { + p_ccb->our_cfg.flush_to = p_cfg->flush_to; + p_lcb = p_ccb->p_lcb; + + if (p_cfg->flush_to < p_lcb->link_flush_tout) + { + p_lcb->link_flush_tout = p_cfg->flush_to; + + /* If the timeout is within range of HCI, set the flush timeout */ + if (p_cfg->flush_to <= ((HCI_MAX_AUTO_FLUSH_TOUT * 5) / 8)) + { + /* Convert flush timeout to 0.625 ms units, with round */ + hci_flush_to = ((p_cfg->flush_to * 8) + 3) / 5; + btsnd_hcic_write_auto_flush_tout (p_lcb->handle, hci_flush_to); + } + } + } + } +} + + +/******************************************************************************* +** +** Function l2cu_process_our_cfg_rsp +** +** Description This function is called when we send the peer a "config response" +** message. It extracts the configuration of interest and saves +** it in the CCB. +** +** Returns void +** +*******************************************************************************/ +void l2cu_process_our_cfg_rsp (tL2C_CCB *p_ccb, tL2CAP_CFG_INFO *p_cfg) +{ + /* If peer wants QoS, we are allowed to change the values in a positive response */ + if ( (p_cfg->qos_present) && (p_ccb->peer_cfg.qos_present) ) + p_ccb->peer_cfg.qos = p_cfg->qos; + else + p_cfg->qos_present = FALSE; + + l2c_fcr_adj_our_rsp_options (p_ccb, p_cfg); +} + + +/******************************************************************************* +** +** Function l2cu_device_reset +** +** Description This function is called when reset of the device is +** completed. For all active connection simulate HCI_DISC +** +** Returns void +** +*******************************************************************************/ +void l2cu_device_reset (void) +{ + int xx; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->handle != HCI_INVALID_HANDLE)) + { + l2c_link_hci_disc_comp (p_lcb->handle, (UINT8) -1); + } + } +} + +#if (TCS_WUG_MEMBER_INCLUDED == TRUE && TCS_INCLUDED == TRUE) +extern UINT16 tcs_wug_get_clk_offset( BD_ADDR addr ) ; +#endif + +/******************************************************************************* +** +** Function l2cu_create_conn +** +** Description This function initiates an acl connection via HCI +** +** Returns TRUE if successful, FALSE if gki get buffer fails. +** +*******************************************************************************/ +BOOLEAN l2cu_create_conn (tL2C_LCB *p_lcb) +{ + int xx; + tL2C_LCB *p_lcb_cur = &l2cb.lcb_pool[0]; +#if BTM_SCO_INCLUDED == TRUE + BOOLEAN is_sco_active; +#endif + +#if (BLE_INCLUDED == TRUE) + tBT_DEVICE_TYPE dev_type; + tBLE_ADDR_TYPE addr_type; + + BTM_ReadDevInfo(p_lcb->remote_bd_addr, &dev_type, &addr_type); + + if (dev_type == BT_DEVICE_TYPE_BLE) + { + p_lcb->ble_addr_type = addr_type; + p_lcb->is_ble_link = TRUE; + + return (l2cble_create_conn(p_lcb)); + } +#endif + + /* If there is a connection where we perform as a slave, try to switch roles + for this connection */ + for (xx = 0, p_lcb_cur = &l2cb.lcb_pool[0]; xx < MAX_L2CAP_LINKS; xx++, p_lcb_cur++) + { + if (p_lcb_cur == p_lcb) + continue; + + if ((p_lcb_cur->in_use) && (p_lcb_cur->link_role == HCI_ROLE_SLAVE)) + { + +#if BTM_SCO_INCLUDED == TRUE + /* The LMP_switch_req shall be sent only if the ACL logical transport + is in active mode, when encryption is disabled, and all synchronous + logical transports on the same physical link are disabled." */ + + /* Check if there is any SCO Active on this BD Address */ + is_sco_active = btm_is_sco_active_by_bdaddr(p_lcb_cur->remote_bd_addr); + + L2CAP_TRACE_API1 ("l2cu_create_conn - btm_is_sco_active_by_bdaddr() is_sco_active = %s", \ + (is_sco_active == TRUE) ? "TRUE":"FALSE"); + + if (is_sco_active == TRUE) + continue; /* No Master Slave switch not allowed when SCO Active */ +#endif + + if (HCI_SWITCH_SUPPORTED(btm_cb.devcb.local_features)) + { + /* mark this lcb waiting for switch to be completed and + start switch on the other one */ + p_lcb->link_state = LST_CONNECTING_WAIT_SWITCH; + p_lcb->link_role = HCI_ROLE_MASTER; + + if (BTM_SwitchRole (p_lcb_cur->remote_bd_addr, HCI_ROLE_MASTER, NULL) == BTM_CMD_STARTED) + { + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, L2CAP_LINK_ROLE_SWITCH_TOUT); + return (TRUE); + } + } + } + } + + p_lcb->link_state = LST_CONNECTING; + + return (l2cu_create_conn_after_switch (p_lcb)); +} + +/******************************************************************************* +** +** Function l2cu_get_num_hi_priority +** +** Description Gets the number of high priority channels. +** +** Returns +** +*******************************************************************************/ +UINT8 l2cu_get_num_hi_priority (void) +{ + UINT8 no_hi = 0; + int xx; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH)) + { + no_hi++; + } + } + return no_hi; +} + + +/******************************************************************************* +** +** Function l2cu_create_conn_after_switch +** +** Description This function initiates an acl connection via HCI +** If switch required to create connection it is already done. +** +** Returns TRUE if successful, FALSE if gki get buffer fails. +** +*******************************************************************************/ + +BOOLEAN l2cu_create_conn_after_switch (tL2C_LCB *p_lcb) +{ + UINT8 allow_switch = HCI_CR_CONN_ALLOW_SWITCH; + tBTM_INQ_INFO *p_inq_info; + UINT8 page_scan_rep_mode; + UINT8 page_scan_mode; + UINT16 clock_offset; + UINT8 *p_features; + UINT16 num_acl = BTM_GetNumAclLinks(); + tBTM_SEC_DEV_REC *p_dev_rec = btm_find_dev (p_lcb->remote_bd_addr); + UINT8 no_hi_prio_chs = l2cu_get_num_hi_priority(); + + p_features = BTM_ReadLocalFeatures(); + + L2CAP_TRACE_DEBUG4 ("l2cu_create_conn_after_switch :%d num_acl:%d no_hi: %d is_bonding:%d", + l2cb.disallow_switch, num_acl, no_hi_prio_chs, p_lcb->is_bonding); + /* FW team says that we can participant in 4 piconets + * typically 3 piconet + 1 for scanning. + * We can enhance the code to count the number of piconets later. */ + if ( ((!l2cb.disallow_switch && (num_acl < 3)) || (p_lcb->is_bonding && (no_hi_prio_chs==0))) + && HCI_SWITCH_SUPPORTED(p_features)) + allow_switch = HCI_CR_CONN_ALLOW_SWITCH; + else + allow_switch = HCI_CR_CONN_NOT_ALLOW_SWITCH; + + p_lcb->link_state = LST_CONNECTING; + + +#if (TCS_WUG_MEMBER_INCLUDED == TRUE && TCS_INCLUDED == TRUE) + if ( (clock_offset = tcs_wug_get_clk_offset( p_lcb->remote_bd_addr )) != 0 ) + { + page_scan_rep_mode = HCI_PAGE_SCAN_REP_MODE_R0; + page_scan_mode = HCI_MANDATARY_PAGE_SCAN_MODE; + } + else + { +#endif + + /* Check with the BT manager if details about remote device are known */ + if ((p_inq_info = BTM_InqDbRead(p_lcb->remote_bd_addr)) != NULL) + { + page_scan_rep_mode = p_inq_info->results.page_scan_rep_mode; + page_scan_mode = p_inq_info->results.page_scan_mode; + clock_offset = (UINT16)(p_inq_info->results.clock_offset); + } + else + { + /* No info known. Use default settings */ + page_scan_rep_mode = HCI_PAGE_SCAN_REP_MODE_R1; + page_scan_mode = HCI_MANDATARY_PAGE_SCAN_MODE; + + clock_offset = (p_dev_rec) ? p_dev_rec->clock_offset : 0; + } +#if (TCS_WUG_MEMBER_INCLUDED == TRUE && TCS_INCLUDED == TRUE) + } +#endif + + if (!btsnd_hcic_create_conn (p_lcb->remote_bd_addr, + HCI_PKT_TYPES_MASK_DM1 + HCI_PKT_TYPES_MASK_DH1, + page_scan_rep_mode, + page_scan_mode, + clock_offset, + allow_switch)) + + { + L2CAP_TRACE_ERROR0 ("L2CAP - no buffer for l2cu_create_conn"); + l2cu_release_lcb (p_lcb); + return (FALSE); + } + +#if (defined(BTM_BUSY_LEVEL_CHANGE_INCLUDED) && BTM_BUSY_LEVEL_CHANGE_INCLUDED == TRUE) + btm_acl_update_busy_level (BTM_BLI_PAGE_EVT); +#endif + + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, + L2CAP_LINK_CONNECT_TOUT); + + return (TRUE); +} + + +/******************************************************************************* +** +** Function l2cu_find_lcb_by_state +** +** Description Look through all active LCBs for a match based on the +** LCB state. +** +** Returns pointer to first matched LCB, or NULL if no match +** +*******************************************************************************/ +tL2C_LCB *l2cu_find_lcb_by_state (tL2C_LINK_STATE state) +{ + UINT16 i; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (i = 0; i < MAX_L2CAP_LINKS; i++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->link_state == state)) + { + return (p_lcb); + } + } + + /* If here, no match found */ + return (NULL); +} + + +/******************************************************************************* +** +** Function l2cu_lcb_disconnecting +** +** Description On each active lcb, check if the lcb is in disconnecting +** state, or if there are no ccb's on the lcb (implying + idle timeout is running), or if last ccb on the link + is in disconnecting state. +** +** Returns TRUE if any of above conditions met, FALSE otherwise +** +*******************************************************************************/ +BOOLEAN l2cu_lcb_disconnecting (void) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + UINT16 i; + BOOLEAN status = FALSE; + + p_lcb = &l2cb.lcb_pool[0]; + + for (i = 0; i < MAX_L2CAP_LINKS; i++, p_lcb++) + { + if (p_lcb->in_use) + { + /* no ccbs on lcb, or lcb is in disconnecting state */ + if ((!p_lcb->ccb_queue.p_first_ccb) || (p_lcb->link_state == LST_DISCONNECTING)) + { + status = TRUE; + break; + } + /* only one ccb left on lcb */ + else if (p_lcb->ccb_queue.p_first_ccb == p_lcb->ccb_queue.p_last_ccb) + { + p_ccb = p_lcb->ccb_queue.p_first_ccb; + + if ((p_ccb->in_use) && + ((p_ccb->chnl_state == CST_W4_L2CAP_DISCONNECT_RSP) || + (p_ccb->chnl_state == CST_W4_L2CA_DISCONNECT_RSP))) + { + status = TRUE; + break; + } + } + } + } + return status; +} + + +/******************************************************************************* +** +** Function l2cu_set_acl_priority +** +** Description Sets the transmission priority for a channel. +** (For initial implementation only two values are valid. +** L2CAP_PRIORITY_NORMAL and L2CAP_PRIORITY_HIGH). +** +** Returns TRUE if a valid channel, else FALSE +** +*******************************************************************************/ +BOOLEAN l2cu_set_acl_priority (BD_ADDR bd_addr, UINT8 priority, BOOLEAN reset_after_rs) +{ + tL2C_LCB *p_lcb; +#ifdef BRCM_VS + UINT8 *pp; + UINT8 command[HCI_BRCM_ACL_PRIORITY_PARAM_SIZE]; + UINT8 vs_param; +#endif + + /* Find the link control block for the acl channel */ + if ((p_lcb = l2cu_find_lcb_by_bd_addr(bd_addr)) == NULL) + { + L2CAP_TRACE_WARNING0 ("L2CAP - no LCB for L2CA_SetAclPriority"); + return (FALSE); + } + +#ifdef BRCM_VS + if (BTM_IS_BRCM_CONTROLLER()) + { + /* Called from above L2CAP through API; send VSC if changed */ + if ((!reset_after_rs && (priority != p_lcb->acl_priority)) || + + /* Called because of a master/slave role switch; if high resend VSC */ + ( reset_after_rs && p_lcb->acl_priority == L2CAP_PRIORITY_HIGH)) + { + pp = command; + + vs_param = (priority == L2CAP_PRIORITY_HIGH) ? HCI_BRCM_ACL_PRIORITY_HIGH : HCI_BRCM_ACL_PRIORITY_LOW; + + UINT16_TO_STREAM (pp, p_lcb->handle); + UINT8_TO_STREAM (pp, vs_param); + + BTM_VendorSpecificCommand (HCI_BRCM_SET_ACL_PRIORITY, HCI_BRCM_ACL_PRIORITY_PARAM_SIZE, command, NULL); + + /* Adjust lmp buffer allocation for this channel if priority changed */ + if (p_lcb->acl_priority != priority) + { + p_lcb->acl_priority = priority; + l2c_link_adjust_allocation(); + } + } + } +#endif + return(TRUE); +} + +#if (L2CAP_ENHANCED_FEATURES != 0) +/******************************************************************************* +** +** Function l2cu_send_feature_req +** +** Description Called at connection establishment by the originator +** of the connection to send an L2CAP Echo request message +** to the peer to determine if he supports Widcomm proprietary +** features. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_feature_req (tL2C_CCB *p_ccb) +{ + UINT8 saved_id; + UINT8 buff[100], *p = buff; + + UINT8_TO_STREAM (p, 'R'); + UINT8_TO_STREAM (p, 'Q'); + + UINT8_TO_STREAM (p, 'r'); + UINT8_TO_STREAM (p, 'q'); + + /* save current ID to be restored after feature request */ + saved_id = p_ccb->p_lcb->id; + + /* Set appropriate ID */ + p_ccb->p_lcb->id = L2CAP_FEATURE_REQ_ID - 1; + + l2cu_send_peer_echo_req (p_ccb->p_lcb, buff, (UINT16)(p - buff)); + + /* packet has been built so we can restore the control block id */ + p_ccb->p_lcb->id = saved_id; +} + + + +/******************************************************************************* +** +** Function l2cu_check_feature_req +** +** Description Called when an echo request is received to check if the +** other side is doing a proprietary feature request. If so, +** extract the values and reply with a features response. +** +** Returns void +** +*******************************************************************************/ +BOOLEAN l2cu_check_feature_req (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len) +{ + UINT8 buff[100]; + UINT8 *p_out = buff; + UINT8 *p_end = p_data + data_len - 2; + UINT8 pe_type, pe_len; + + if ((data_len <= 4) + || (p_data[0] != 'R') + || (p_data[1] != 'Q') + || (p_data[data_len - 2] != 'r') + || (p_data[data_len - 1] != 'q') + || (id != L2CAP_FEATURE_REQ_ID)) + return (FALSE); + + /* Skip leading frame characters */ + p_data += 2; + + UINT8_TO_STREAM (p_out, 'R'); + UINT8_TO_STREAM (p_out, 'S'); + + while (p_data < p_end) + { + pe_type = *p_data++; + pe_len = *p_data++; + + switch (pe_type) + { + default: + p_data += pe_len; + break; + } + } + + /* Sanity check - we should not overrun the input */ + if (p_data != p_end) + { + L2CAP_TRACE_ERROR0 ("L2CAP - badly formatted feature req"); + return (FALSE); + } + + UINT8_TO_STREAM (p_out, 'r'); + UINT8_TO_STREAM (p_out, 's'); + + l2cu_send_peer_echo_rsp (p_lcb, L2CAP_FEATURE_RSP_ID, buff, (UINT16)(p_out - buff)); + + return (TRUE); +} + +/******************************************************************************* +** +** Function l2cu_check_feature_rsp +** +** Description Called when an echo response is received to check if the +** other side is suports proprietary feature(s). If so, +** extract the values. +** +** Returns void +** +*******************************************************************************/ +void l2cu_check_feature_rsp (tL2C_LCB *p_lcb, UINT8 id, UINT8 *p_data, UINT16 data_len) +{ + UINT8 *p_end = p_data + data_len - 2; + + if ((data_len <= 4) + || (p_data[0] != 'R') + || (p_data[1] != 'S') + || (p_data[data_len - 2] != 'r') + || (p_data[data_len - 1] != 's') + || (id != L2CAP_FEATURE_RSP_ID)) + { + return; + } + + /* Skip leading frame characters */ + p_data += 2; + + while (p_data < p_end) + { + UINT8 pe_id = *p_data++; + UINT8 pe_len = *p_data++; + + switch (pe_id) + { + default: + p_data += pe_len; + break; + } + } + + /* Sanity check - we should not overrun the input */ + if (p_data != p_end) + { + L2CAP_TRACE_ERROR0 ("L2CAP - badly formatted feature rsp"); + } +} +#endif /* L2CAP_ENHANCED_FEATURES != 0 */ + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) +/****************************************************************************** +** +** Function l2cu_set_non_flushable_pbf +** +** Description set L2CAP_PKT_START_NON_FLUSHABLE if controller supoorts +** +** Returns void +** +*******************************************************************************/ +void l2cu_set_non_flushable_pbf (BOOLEAN is_supported) +{ + if (is_supported) + l2cb.non_flushable_pbf = (L2CAP_PKT_START_NON_FLUSHABLE << L2CAP_PKT_TYPE_SHIFT); + else + l2cb.non_flushable_pbf = (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT); +} +#endif + +/******************************************************************************* +** +** Function l2cu_resubmit_pending_sec_req +** +** Description This function is called when required security procedures +** are completed and any pending requests can be re-submitted. +** +** Returns void +** +*******************************************************************************/ +void l2cu_resubmit_pending_sec_req (BD_ADDR p_bda) +{ + tL2C_LCB *p_lcb; + tL2C_CCB *p_ccb; + tL2C_CCB *p_next_ccb; + int xx; + + L2CAP_TRACE_DEBUG1 ("l2cu_resubmit_pending_sec_req p_bda: 0x%08x", p_bda); + + /* If we are called with a BDA, only resubmit for that BDA */ + if (p_bda) + { + p_lcb = l2cu_find_lcb_by_bd_addr (p_bda); + + /* If we don't have one, this is an error */ + if (p_lcb) + { + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_next_ccb) + { + p_next_ccb = p_ccb->p_next_ccb; + l2c_csm_execute (p_ccb, L2CEVT_SEC_RE_SEND_CMD, NULL); + } + } + else + { + L2CAP_TRACE_WARNING0 ("l2cu_resubmit_pending_sec_req - unknown BD_ADDR"); + } + } + else + { + /* No BDA pasesed in, so check all links */ + for (xx = 0, p_lcb = &l2cb.lcb_pool[0]; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if (p_lcb->in_use) + { + /* For all channels, send the event through their FSMs */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_next_ccb) + { + p_next_ccb = p_ccb->p_next_ccb; + l2c_csm_execute (p_ccb, L2CEVT_SEC_RE_SEND_CMD, NULL); + } + } + } + } +} + +#if L2CAP_CONFORMANCE_TESTING == TRUE +/******************************************************************************* +** +** Function l2cu_set_info_rsp_mask +** +** Description This function allows the script wrapper to change the +** info resp mask for conformance testing. +** +** Returns pointer to CCB, or NULL if none +** +*******************************************************************************/ +void l2cu_set_info_rsp_mask (UINT32 mask) +{ + l2cb.test_info_resp = mask; +} +#endif /* L2CAP_CONFORMANCE_TESTING */ + +/******************************************************************************* +** +** Function l2cu_adjust_out_mps +** +** Description Sets our MPS based on current controller capabilities +** +** Returns void +** +*******************************************************************************/ +void l2cu_adjust_out_mps (tL2C_CCB *p_ccb) +{ + UINT16 packet_size; + + /* on the tx side MTU is selected based on packet size of the controller */ + packet_size = btm_get_max_packet_size (p_ccb->p_lcb->remote_bd_addr); + + if (packet_size <= (L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD + L2CAP_SDU_LEN_OVERHEAD + L2CAP_FCS_LEN)) + { + /* something is very wrong */ + L2CAP_TRACE_ERROR2 ("l2cu_adjust_out_mps bad packet size: %u will use MPS: %u", packet_size, p_ccb->peer_cfg.fcr.mps); + p_ccb->tx_mps = p_ccb->peer_cfg.fcr.mps; + } + else + { + packet_size -= (L2CAP_PKT_OVERHEAD + L2CAP_FCR_OVERHEAD + L2CAP_SDU_LEN_OVERHEAD + L2CAP_FCS_LEN); + + /* We try to negotiate MTU that each packet can be split into whole + number of max packets. For example if link is 1.2 max packet size is 339 bytes. + At first calculate how many whole packets it is. MAX L2CAP is 1691 + 4 overhead. + 1695, that will be 5 Dh5 packets. Now maximum L2CAP packet is + 5 * 339 = 1695. Minus 4 bytes L2CAP header 1691. + + For EDR 2.0 packet size is 1027. So we better send RFCOMM packet as 1 3DH5 packet + 1 * 1027 = 1027. Minus 4 bytes L2CAP header 1023. */ + if (p_ccb->peer_cfg.fcr.mps >= packet_size) + p_ccb->tx_mps = p_ccb->peer_cfg.fcr.mps / packet_size * packet_size; + else + p_ccb->tx_mps = p_ccb->peer_cfg.fcr.mps; + + L2CAP_TRACE_DEBUG3 ("l2cu_adjust_out_mps use %d Based on peer_cfg.fcr.mps: %u packet_size: %u", + p_ccb->tx_mps, p_ccb->peer_cfg.fcr.mps, packet_size); + } +} + + +/******************************************************************************* +** +** Function l2cu_initialize_fixed_ccb +** +** Description Initialize a fixed channel's CCB +** +** Returns TRUE or FALSE +** +*******************************************************************************/ +BOOLEAN l2cu_initialize_fixed_ccb (tL2C_LCB *p_lcb, UINT16 fixed_cid, tL2CAP_FCR_OPTS *p_fcr) +{ +#if (L2CAP_NUM_FIXED_CHNLS > 0) + tL2C_CCB *p_ccb; + + /* If we already have a CCB, then simply return */ + if (p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] != NULL) + return (TRUE); + + if ((p_ccb = l2cu_allocate_ccb (NULL, 0)) == NULL) + return (FALSE); + + /* Set CID for the connection */ + p_ccb->local_cid = fixed_cid; + p_ccb->remote_cid = fixed_cid; + + GKI_init_q (&p_ccb->xmit_hold_q); + + p_ccb->is_flushable = FALSE; + + p_ccb->timer_entry.param = (TIMER_PARAM_TYPE)p_ccb; + + + if (p_fcr) + { + /* Set the FCR parameters. For now, we will use default pools */ + p_ccb->our_cfg.fcr = p_ccb->peer_cfg.fcr = *p_fcr; + + p_ccb->ertm_info.fcr_rx_pool_id = HCI_ACL_POOL_ID; + p_ccb->ertm_info.fcr_tx_pool_id = HCI_ACL_POOL_ID; + p_ccb->ertm_info.user_rx_pool_id = HCI_ACL_POOL_ID; + p_ccb->ertm_info.user_tx_pool_id = HCI_ACL_POOL_ID; + + p_ccb->fcrb.max_held_acks = p_fcr->tx_win_sz / 3; + } + + /* Link ccb to lcb and lcb to ccb */ + p_lcb->p_fixed_ccbs[fixed_cid - L2CAP_FIRST_FIXED_CHNL] = p_ccb; + p_ccb->p_lcb = p_lcb; + + /* There is no configuration, so if the link is up, the channel is up */ + if (p_lcb->link_state == LST_CONNECTED) + p_ccb->chnl_state = CST_OPEN; + + /* Set the default idle timeout value to use */ + p_ccb->fixed_chnl_idle_tout = l2cb.fixed_reg[fixed_cid - L2CAP_FIRST_FIXED_CHNL].default_idle_tout; +#endif + return (TRUE); +} + +/******************************************************************************* +** +** Function l2cu_no_dynamic_ccbs +** +** Description Handles the case when there are no more dynamic CCBs. If there +** are any fixed CCBs, start the longest of the fixed CCB timeouts, +** otherwise start the default link idle timeout or disconnect. +** +** Returns void +** +*******************************************************************************/ +void l2cu_no_dynamic_ccbs (tL2C_LCB *p_lcb) +{ + tBTM_STATUS rc; + UINT16 timeout = p_lcb->idle_timeout; + +#if (L2CAP_NUM_FIXED_CHNLS > 0) + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) + { + if ( (p_lcb->p_fixed_ccbs[xx] != NULL) && (p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout > timeout) ) + timeout = p_lcb->p_fixed_ccbs[xx]->fixed_chnl_idle_tout; + } +#endif + + /* If the link is pairing, do not mess with the timeouts */ + if (p_lcb->is_bonding) + return; + + if (timeout == 0) + { + L2CAP_TRACE_DEBUG0 ("l2cu_no_dynamic_ccbs() IDLE timer 0, disconnecting link"); + + rc = btm_sec_disconnect (p_lcb->handle, HCI_ERR_PEER_USER); + if (rc == BTM_CMD_STARTED) + { + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } + else if (rc == BTM_SUCCESS) + { + /* BTM SEC will make sure that link is release (probably after pairing is done) */ + p_lcb->link_state = LST_DISCONNECTING; + timeout = 0xFFFF; + } + else if ( (p_lcb->is_bonding) + && (btsnd_hcic_disconnect (p_lcb->handle, HCI_ERR_PEER_USER)) ) + { + p_lcb->link_state = LST_DISCONNECTING; + timeout = L2CAP_LINK_DISCONNECT_TOUT; + } + else + { + /* probably no buffer to send disconnect */ + timeout = BT_1SEC_TIMEOUT; + } + } + + if (timeout != 0xFFFF) + { + L2CAP_TRACE_DEBUG1 ("l2cu_no_dynamic_ccbs() starting IDLE timeout: %d", timeout); + btu_start_timer (&p_lcb->timer_entry, BTU_TTYPE_L2CAP_LINK, timeout); + } + else + { + btu_stop_timer(&p_lcb->timer_entry); + } +} + +#if (L2CAP_NUM_FIXED_CHNLS > 0) +/******************************************************************************* +** +** Function l2cu_process_fixed_chnl_resp +** +** Description handle a fixed channel response (or lack thereof) +** if the link failed, or a fixed channel response was +** not received, the bitfield is all zeros. +** +*******************************************************************************/ +void l2cu_process_fixed_chnl_resp (tL2C_LCB *p_lcb) +{ + int xx; +#if BLE_INCLUDED == TRUE + UINT16 reason = (p_lcb->is_ble_link ) ? 1 : 0; +#else + UINT16 reason =0; +#endif + + /* Tell all registered fixed channels about the connection */ + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) + { + if (l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb != NULL) + { + if (p_lcb->peer_chnl_mask[0] & (1 << (xx + L2CAP_FIRST_FIXED_CHNL))) + { + if (p_lcb->p_fixed_ccbs[xx]) + p_lcb->p_fixed_ccbs[xx]->chnl_state = CST_OPEN; + + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(p_lcb->remote_bd_addr, TRUE, reason); + } + else if (p_lcb->p_fixed_ccbs[xx]) + { + (*l2cb.fixed_reg[xx].pL2CA_FixedConn_Cb)(p_lcb->remote_bd_addr, FALSE, p_lcb->disc_reason); + l2cu_release_ccb (p_lcb->p_fixed_ccbs[xx]); + p_lcb->p_fixed_ccbs[xx] = NULL; + } + } + } +} +#endif + +#if (BLE_INCLUDED == TRUE) +/******************************************************************************* +** +** Function l2cu_send_peer_ble_par_req +** +** Description Build and send a BLE parameter update request message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_par_req (tL2C_LCB *p_lcb, UINT16 min_int, UINT16 max_int, UINT16 latency, UINT16 timeout) +{ + BT_HDR *p_buf; + UINT8 *p; + + /* Create an identifier for this packet */ + p_lcb->id++; + l2cu_adj_id (p_lcb, L2CAP_ADJ_ID); + + if ((p_buf = l2cu_build_header (p_lcb, L2CAP_CMD_BLE_UPD_REQ_LEN, L2CAP_CMD_BLE_UPDATE_REQ, p_lcb->id)) == NULL ) + { + L2CAP_TRACE_WARNING0 ("l2cu_send_peer_ble_par_req - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, min_int); + UINT16_TO_STREAM (p, max_int); + UINT16_TO_STREAM (p, latency); + UINT16_TO_STREAM (p, timeout); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +/******************************************************************************* +** +** Function l2cu_send_peer_ble_par_rsp +** +** Description Build and send a BLE parameter update response message +** to the peer. +** +** Returns void +** +*******************************************************************************/ +void l2cu_send_peer_ble_par_rsp (tL2C_LCB *p_lcb, UINT16 reason, UINT8 rem_id) +{ + BT_HDR *p_buf; + UINT8 *p; + + if ((p_buf = l2cu_build_header (p_lcb, L2CAP_CMD_BLE_UPD_RSP_LEN, L2CAP_CMD_BLE_UPDATE_RSP, rem_id)) == NULL ) + { + L2CAP_TRACE_WARNING0 ("l2cu_send_peer_ble_par_rsp - no buffer"); + return; + } + + p = (UINT8 *)(p_buf + 1) + L2CAP_SEND_CMD_OFFSET + HCI_DATA_PREAMBLE_SIZE + + L2CAP_PKT_OVERHEAD + L2CAP_CMD_OVERHEAD; + + UINT16_TO_STREAM (p, reason); + + l2c_link_check_send_pkts (p_lcb, NULL, p_buf); +} + +#endif /* BLE_INCLUDED == TRUE */ + + +/******************************************************************************* +** Functions used by both Full and Light Stack +********************************************************************************/ + +/******************************************************************************* +** +** Function l2cu_find_lcb_by_handle +** +** Description Look through all active LCBs for a match based on the +** HCI handle. +** +** Returns pointer to matched LCB, or NULL if no match +** +*******************************************************************************/ +tL2C_LCB *l2cu_find_lcb_by_handle (UINT16 handle) +{ + int xx; + tL2C_LCB *p_lcb = &l2cb.lcb_pool[0]; + + for (xx = 0; xx < MAX_L2CAP_LINKS; xx++, p_lcb++) + { + if ((p_lcb->in_use) && (p_lcb->handle == handle)) + { + return (p_lcb); + } + } + + /* If here, no match found */ + return (NULL); +} + +/******************************************************************************* +** +** Function l2cu_find_ccb_by_cid +** +** Description Look through all active CCBs on a link for a match based +** on the local CID. If passed the link pointer is NULL, all +** active links are searched. +** +** Returns pointer to matched CCB, or NULL if no match +** +*******************************************************************************/ +tL2C_CCB *l2cu_find_ccb_by_cid (tL2C_LCB *p_lcb, UINT16 local_cid) +{ + tL2C_CCB *p_ccb = NULL; +#if (L2CAP_UCD_INCLUDED == TRUE) + UINT8 xx; +#endif + + if (local_cid >= L2CAP_BASE_APPL_CID) + { + /* find the associated CCB by "index" */ + local_cid -= L2CAP_BASE_APPL_CID; + + if (local_cid >= MAX_L2CAP_CHANNELS) + return NULL; + + p_ccb = l2cb.ccb_pool + local_cid; + + /* make sure the CCB is in use */ + if (!p_ccb->in_use) + { + p_ccb = NULL; + } + /* make sure it's for the same LCB */ + else if (p_lcb && p_lcb != p_ccb->p_lcb) + { + p_ccb = NULL; + } + } +#if (L2CAP_UCD_INCLUDED == TRUE) + else + { + /* searching fixed channel */ + p_ccb = l2cb.ccb_pool; + for ( xx = 0; xx < MAX_L2CAP_CHANNELS; xx++ ) + { + if ((p_ccb->local_cid == local_cid) + &&(p_ccb->in_use) + &&(p_lcb == p_ccb->p_lcb)) + break; + else + p_ccb++; + } + if ( xx >= MAX_L2CAP_CHANNELS ) + return NULL; + } +#endif + + return (p_ccb); +} + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + +/****************************************************************************** +** +** Function l2cu_get_next_channel_in_rr +** +** Description get the next channel to send on a link. It also adjusts the +** CCB queue to do a basic priority and round-robin scheduling. +** +** Returns pointer to CCB or NULL +** +*******************************************************************************/ +static tL2C_CCB *l2cu_get_next_channel_in_rr(tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_serve_ccb = NULL; + tL2C_CCB *p_ccb; + + int i, j; + + /* scan all of priority until finding a channel to serve */ + for ( i = 0; (i < L2CAP_NUM_CHNL_PRIORITY)&&(!p_serve_ccb); i++ ) + { + /* scan all channel within serving priority group until finding a channel to serve */ + for ( j = 0; (j < p_lcb->rr_serv[p_lcb->rr_pri].num_ccb)&&(!p_serve_ccb); j++) + { + /* scaning from next serving channel */ + p_ccb = p_lcb->rr_serv[p_lcb->rr_pri].p_serve_ccb; + + if (!p_ccb) + { + L2CAP_TRACE_ERROR1("p_serve_ccb is NULL, rr_pri=%d", p_lcb->rr_pri); + return NULL; + } + + L2CAP_TRACE_DEBUG3("RR scan pri=%d, lcid=0x%04x, q_cout=%d", + p_ccb->ccb_priority, p_ccb->local_cid, p_ccb->xmit_hold_q.count ); + + /* store the next serving channel */ + /* this channel is the last channel of its priority group */ + if (( p_ccb->p_next_ccb == NULL ) + ||( p_ccb->p_next_ccb->ccb_priority != p_ccb->ccb_priority )) + { + /* next serving channel is set to the first channel in the group */ + p_lcb->rr_serv[p_lcb->rr_pri].p_serve_ccb = p_lcb->rr_serv[p_lcb->rr_pri].p_first_ccb; + } + else + { + /* next serving channel is set to the next channel in the group */ + p_lcb->rr_serv[p_lcb->rr_pri].p_serve_ccb = p_ccb->p_next_ccb; + } + + if (p_ccb->chnl_state != CST_OPEN) + continue; + + /* eL2CAP option in use */ + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) + { + if (p_ccb->fcrb.wait_ack || p_ccb->fcrb.remote_busy) + continue; + + if ( p_ccb->fcrb.retrans_q.count == 0 ) + { + if ( p_ccb->xmit_hold_q.count == 0 ) + continue; + + /* If using the common pool, should be at least 10% free. */ + if ( (p_ccb->ertm_info.fcr_tx_pool_id == HCI_ACL_POOL_ID) && (GKI_poolutilization (HCI_ACL_POOL_ID) > 90) ) + continue; + + /* If in eRTM mode, check for window closure */ + if ( (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) && (l2c_fcr_is_flow_controlled (p_ccb)) ) + continue; + } + } + else + { + if (p_ccb->xmit_hold_q.count == 0) + continue; + } + + /* found a channel to serve */ + p_serve_ccb = p_ccb; + /* decrease quota of its priority group */ + p_lcb->rr_serv[p_lcb->rr_pri].quota--; + } + + /* if there is no more quota of the priority group or no channel to have data to send */ + if ((p_lcb->rr_serv[p_lcb->rr_pri].quota == 0)||(!p_serve_ccb)) + { + /* serve next priority group */ + p_lcb->rr_pri = (p_lcb->rr_pri + 1) % L2CAP_NUM_CHNL_PRIORITY; + /* initialize its quota */ + p_lcb->rr_serv[p_lcb->rr_pri].quota = L2CAP_GET_PRIORITY_QUOTA(p_lcb->rr_pri); + } + } + + if (p_serve_ccb) + { + L2CAP_TRACE_DEBUG3("RR service pri=%d, quota=%d, lcid=0x%04x", + p_serve_ccb->ccb_priority, + p_lcb->rr_serv[p_serve_ccb->ccb_priority].quota, + p_serve_ccb->local_cid ); + } + + return p_serve_ccb; +} + +#else /* (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) */ + +/****************************************************************************** +** +** Function l2cu_get_next_channel +** +** Description get the next channel to send on a link bassed on priority +** scheduling. +** +** Returns pointer to CCB or NULL +** +*******************************************************************************/ +static tL2C_CCB *l2cu_get_next_channel(tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + + /* Get the first CCB with data to send. + */ + for (p_ccb = p_lcb->ccb_queue.p_first_ccb; p_ccb; p_ccb = p_ccb->p_next_ccb) + { + if (p_ccb->chnl_state != CST_OPEN) + continue; + + if (p_ccb->fcrb.wait_ack || p_ccb->fcrb.remote_busy) + continue; + + if (p_ccb->fcrb.retrans_q.count != 0) + return p_ccb; + + if (p_ccb->xmit_hold_q.count == 0) + continue; + + /* If using the common pool, should be at least 10% free. */ + if ( (p_ccb->ertm_info.fcr_tx_pool_id == HCI_ACL_POOL_ID) && (GKI_poolutilization (HCI_ACL_POOL_ID) > 90) ) + continue; + + /* If in eRTM mode, check for window closure */ + if ( (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) && (l2c_fcr_is_flow_controlled (p_ccb)) ) + continue; + + /* If here, we found someone */ + return p_ccb; + } + + return NULL; +} +#endif /* (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) */ + +/****************************************************************************** +** +** Function l2cu_get_next_buffer_to_send +** +** Description get the next buffer to send on a link. It also adjusts the +** CCB queue to do a basic priority and round-robin scheduling. +** +** Returns pointer to buffer or NULL +** +*******************************************************************************/ +BT_HDR *l2cu_get_next_buffer_to_send (tL2C_LCB *p_lcb) +{ + tL2C_CCB *p_ccb; + BT_HDR *p_buf; + + /* Highest priority are fixed channels */ +#if (L2CAP_NUM_FIXED_CHNLS > 0) + int xx; + + for (xx = 0; xx < L2CAP_NUM_FIXED_CHNLS; xx++) + { + if ((p_ccb = p_lcb->p_fixed_ccbs[xx]) == NULL) + continue; + + /* eL2CAP option in use */ + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) + { + if (p_ccb->fcrb.wait_ack || p_ccb->fcrb.remote_busy) + continue; + + /* No more checks needed if sending from the reatransmit queue */ + if (p_ccb->fcrb.retrans_q.count == 0) + { + if (p_ccb->xmit_hold_q.count == 0) + continue; + + /* If using the common pool, should be at least 10% free. */ + if ( (p_ccb->ertm_info.fcr_tx_pool_id == HCI_ACL_POOL_ID) && (GKI_poolutilization (HCI_ACL_POOL_ID) > 90) ) + continue; + + /* If in eRTM mode, check for window closure */ + if ( (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE) && (l2c_fcr_is_flow_controlled (p_ccb)) ) + continue; + } + + if ((p_buf = l2c_fcr_get_next_xmit_sdu_seg(p_ccb, 0)) != NULL) + { + l2cu_set_acl_hci_header (p_buf, p_ccb); + return (p_buf); + } + } + else + { + if (p_ccb->xmit_hold_q.count != 0) + { + p_buf = (BT_HDR *)GKI_dequeue (&p_ccb->xmit_hold_q); + l2cu_set_acl_hci_header (p_buf, p_ccb); + return (p_buf); + } + } + } +#endif + +#if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE) + /* get next serving channel in round-robin */ + p_ccb = l2cu_get_next_channel_in_rr( p_lcb ); +#else + p_ccb = l2cu_get_next_channel( p_lcb ); +#endif + + /* Return if no buffer */ + if (p_ccb == NULL) + return (NULL); + + if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE) + { + if ((p_buf = l2c_fcr_get_next_xmit_sdu_seg(p_ccb, 0)) == NULL) + return (NULL); + } + else + { + p_buf = (BT_HDR *)GKI_dequeue (&p_ccb->xmit_hold_q); + } + + if ( p_ccb->p_rcb && p_ccb->p_rcb->api.pL2CA_TxComplete_Cb && (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_ERTM_MODE) ) + (*p_ccb->p_rcb->api.pL2CA_TxComplete_Cb)(p_ccb->local_cid, 1); + + + l2cu_check_channel_congestion (p_ccb); + + l2cu_set_acl_hci_header (p_buf, p_ccb); + + return (p_buf); +} + +/****************************************************************************** +** +** Function l2cu_set_acl_hci_header +** +** Description Set HCI handle for ACL packet +** +** Returns None +** +*******************************************************************************/ +void l2cu_set_acl_hci_header (BT_HDR *p_buf, tL2C_CCB *p_ccb) +{ + UINT8 *p; + + /* Set the pointer to the beginning of the data minus 4 bytes for the packet header */ + p = (UINT8 *)(p_buf + 1) + p_buf->offset - HCI_DATA_PREAMBLE_SIZE; + +#if (L2CAP_NON_FLUSHABLE_PB_INCLUDED == TRUE) + if ( (((p_buf->layer_specific & L2CAP_FLUSHABLE_MASK) == L2CAP_FLUSHABLE_CH_BASED) && (p_ccb->is_flushable)) + || ((p_buf->layer_specific & L2CAP_FLUSHABLE_MASK) == L2CAP_FLUSHABLE_PKT) ) + { + UINT16_TO_STREAM (p, p_ccb->p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT)); + } + else + { + UINT16_TO_STREAM (p, p_ccb->p_lcb->handle | l2cb.non_flushable_pbf); + } +#else + UINT16_TO_STREAM (p, p_ccb->p_lcb->handle | (L2CAP_PKT_START << L2CAP_PKT_TYPE_SHIFT)); +#endif +#if (BLE_INCLUDED == TRUE) + if (p_ccb->p_lcb->is_ble_link) + { + /* The HCI transport will segment the buffers. */ + if (p_buf->len > btu_cb.hcit_ble_acl_data_size) + { + UINT16_TO_STREAM (p, btu_cb.hcit_ble_acl_data_size); + } + else + { + UINT16_TO_STREAM (p, p_buf->len); + } + } /* (BLE_INCLUDED == TRUE) */ + else +#endif + { + + /* The HCI transport will segment the buffers. */ + if (p_buf->len > btu_cb.hcit_acl_data_size) + { + UINT16_TO_STREAM (p, btu_cb.hcit_acl_data_size); + } + else + { + UINT16_TO_STREAM (p, p_buf->len); + } + } + p_buf->offset -= HCI_DATA_PREAMBLE_SIZE; + p_buf->len += HCI_DATA_PREAMBLE_SIZE; +} + +/****************************************************************************** +** +** Function l2cu_check_channel_congestion +** +** Description check if any change in congestion status +** +** Returns None +** +*******************************************************************************/ +void l2cu_check_channel_congestion (tL2C_CCB *p_ccb) +{ + UINT16 q_count = p_ccb->xmit_hold_q.count; + +#if (L2CAP_UCD_INCLUDED == TRUE) + if ( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) + { + q_count += p_ccb->p_lcb->ucd_out_sec_pending_q.count; + } +#endif + + /* If the CCB queue limit is subject to a quota, check for congestion */ + + /* if this channel has outgoing traffic */ + if ((p_ccb->p_rcb)&&(p_ccb->buff_quota != 0)) + { + /* If this channel was congested */ + if ( p_ccb->cong_sent ) + { + /* If the channel is not congested now, tell the app */ + if (q_count <= (p_ccb->buff_quota / 2)) + { + p_ccb->cong_sent = FALSE; + if (p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb) + { + L2CAP_TRACE_DEBUG3 ("L2CAP - Calling CongestionStatus_Cb (FALSE), CID: 0x%04x xmit_hold_q.count: %u buff_quota: %u", + p_ccb->local_cid, q_count, p_ccb->buff_quota); + + /* Prevent recursive calling */ + l2cb.is_cong_cback_context = TRUE; + (*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid, FALSE); + l2cb.is_cong_cback_context = FALSE; + } +#if (L2CAP_UCD_INCLUDED == TRUE) + else if ( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) + { + if ( p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ) + { + L2CAP_TRACE_DEBUG3 ("L2CAP - Calling UCD CongestionStatus_Cb (FALSE), SecPendingQ:%u,XmitQ:%u,Quota:%u", + p_ccb->p_lcb->ucd_out_sec_pending_q.count, + p_ccb->xmit_hold_q.count, p_ccb->buff_quota); + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb( p_ccb->p_lcb->remote_bd_addr, FALSE ); + } + } +#endif + } + } + else + { + /* If this channel was not congested but it is congested now, tell the app */ + if (q_count > p_ccb->buff_quota) + { + p_ccb->cong_sent = TRUE; + if (p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb) + { + L2CAP_TRACE_DEBUG3 ("L2CAP - Calling CongestionStatus_Cb (TRUE),CID:0x%04x,XmitQ:%u,Quota:%u", + p_ccb->local_cid, q_count, p_ccb->buff_quota); + + (*p_ccb->p_rcb->api.pL2CA_CongestionStatus_Cb)(p_ccb->local_cid, TRUE); + } +#if (L2CAP_UCD_INCLUDED == TRUE) + else if ( p_ccb->local_cid == L2CAP_CONNECTIONLESS_CID ) + { + if ( p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb ) + { + L2CAP_TRACE_DEBUG3 ("L2CAP - Calling UCD CongestionStatus_Cb (TRUE), SecPendingQ:%u,XmitQ:%u,Quota:%u", + p_ccb->p_lcb->ucd_out_sec_pending_q.count, + p_ccb->xmit_hold_q.count, p_ccb->buff_quota); + p_ccb->p_rcb->ucd.cb_info.pL2CA_UCD_Congestion_Status_Cb( p_ccb->p_lcb->remote_bd_addr, TRUE ); + } + } +#endif + } + } + } +} + |