/****************************************************************************** * * Copyright (C) 2009-2012 Broadcom Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ /****************************************************************************** * * This is the implementation file for the MCAP at L2CAP Interface. * ******************************************************************************/ #include <string.h> #include "bt_target.h" #include "btm_api.h" #include "btm_int.h" #include "mca_api.h" #include "mca_defs.h" #include "mca_int.h" /* L2CAP callback function structure */ const tL2CAP_APPL_INFO mca_l2c_int_appl = { NULL, mca_l2c_connect_cfm_cback, NULL, mca_l2c_config_ind_cback, mca_l2c_config_cfm_cback, mca_l2c_disconnect_ind_cback, mca_l2c_disconnect_cfm_cback, NULL, mca_l2c_data_ind_cback, mca_l2c_congestion_ind_cback, NULL }; /* Control channel eL2CAP default options */ const tL2CAP_FCR_OPTS mca_l2c_fcr_opts_def = { L2CAP_FCR_ERTM_MODE, /* Mandatory for MCAP */ MCA_FCR_OPT_TX_WINDOW_SIZE, /* Tx window size */ MCA_FCR_OPT_MAX_TX_B4_DISCNT, /* Maximum transmissions before disconnecting */ MCA_FCR_OPT_RETX_TOUT, /* Retransmission timeout (2 secs) */ MCA_FCR_OPT_MONITOR_TOUT, /* Monitor timeout (12 secs) */ MCA_FCR_OPT_MPS_SIZE /* MPS segment size */ }; /******************************************************************************* ** ** Function mca_sec_check_complete_term ** ** Description The function called when Security Manager finishes ** verification of the service side connection ** ** Returns void ** *******************************************************************************/ static void mca_sec_check_complete_term (BD_ADDR bd_addr, void *p_ref_data, UINT8 res) { tMCA_TC_TBL *p_tbl = (tMCA_TC_TBL *)p_ref_data; tL2CAP_CFG_INFO cfg; tL2CAP_ERTM_INFO ertm_info; MCA_TRACE_DEBUG1("mca_sec_check_complete_term res: %d", res); if ( res == BTM_SUCCESS ) { MCA_TRACE_DEBUG2 ("lcid:x%x id:x%x", p_tbl->lcid, p_tbl->id); /* Set the FCR options: control channel mandates ERTM */ ertm_info.preferred_mode = mca_l2c_fcr_opts_def.mode; ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_ERTM; ertm_info.user_rx_pool_id = MCA_USER_RX_POOL_ID; ertm_info.user_tx_pool_id = MCA_USER_TX_POOL_ID; ertm_info.fcr_rx_pool_id = MCA_FCR_RX_POOL_ID; ertm_info.fcr_tx_pool_id = MCA_FCR_TX_POOL_ID; /* Send response to the L2CAP layer. */ L2CA_ErtmConnectRsp (bd_addr, p_tbl->id, p_tbl->lcid, L2CAP_CONN_OK, L2CAP_CONN_OK, &ertm_info); /* transition to configuration state */ p_tbl->state = MCA_TC_ST_CFG; /* Send L2CAP config req */ mca_set_cfg_by_tbl (&cfg, p_tbl); L2CA_ConfigReq(p_tbl->lcid, &cfg); } else { L2CA_ConnectRsp (bd_addr, p_tbl->id, p_tbl->lcid, L2CAP_CONN_SECURITY_BLOCK, L2CAP_CONN_OK); mca_tc_close_ind(p_tbl, L2CAP_CONN_SECURITY_BLOCK); } } /******************************************************************************* ** ** Function mca_sec_check_complete_orig ** ** Description The function called when Security Manager finishes ** verification of the service side connection ** ** Returns void ** *******************************************************************************/ static void mca_sec_check_complete_orig (BD_ADDR bd_addr, void *p_ref_data, UINT8 res) { tMCA_TC_TBL *p_tbl = (tMCA_TC_TBL *)p_ref_data; tL2CAP_CFG_INFO cfg; MCA_TRACE_DEBUG1("mca_sec_check_complete_orig res: %d", res); if ( res == BTM_SUCCESS ) { /* set channel state */ p_tbl->state = MCA_TC_ST_CFG; /* Send L2CAP config req */ mca_set_cfg_by_tbl (&cfg, p_tbl); L2CA_ConfigReq(p_tbl->lcid, &cfg); } else { L2CA_DisconnectReq (p_tbl->lcid); mca_tc_close_ind(p_tbl, L2CAP_CONN_SECURITY_BLOCK); } } /******************************************************************************* ** ** Function mca_l2c_cconn_ind_cback ** ** Description This is the L2CAP connect indication callback function. ** ** Returns void ** *******************************************************************************/ void mca_l2c_cconn_ind_cback(BD_ADDR bd_addr, UINT16 lcid, UINT16 psm, UINT8 id) { tMCA_HANDLE handle = mca_handle_by_cpsm(psm); tMCA_CCB *p_ccb; tMCA_TC_TBL *p_tbl = NULL; UINT16 result = L2CAP_CONN_NO_RESOURCES; tBTM_STATUS rc; tL2CAP_ERTM_INFO ertm_info, *p_ertm_info = NULL; tL2CAP_CFG_INFO cfg; MCA_TRACE_EVENT3 ("mca_l2c_cconn_ind_cback: lcid:x%x psm:x%x id:x%x", lcid, psm, id); /* do we already have a control channel for this peer? */ if ((p_ccb = mca_ccb_by_bd(handle, bd_addr)) == NULL) { /* no, allocate ccb */ if ((p_ccb = mca_ccb_alloc(handle, bd_addr)) != NULL) { /* allocate and set up entry */ p_ccb->lcid = lcid; p_tbl = mca_tc_tbl_calloc(p_ccb); p_tbl->id = id; p_tbl->cfg_flags= MCA_L2C_CFG_CONN_ACP; /* proceed with connection */ /* Check the security */ rc = btm_sec_mx_access_request (bd_addr, psm, FALSE, BTM_SEC_PROTO_MCA, 0, &mca_sec_check_complete_term, p_tbl); if (rc == BTM_CMD_STARTED) { /* Set the FCR options: control channel mandates ERTM */ ertm_info.preferred_mode = mca_l2c_fcr_opts_def.mode; ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_ERTM; ertm_info.user_rx_pool_id = MCA_USER_RX_POOL_ID; ertm_info.user_tx_pool_id = MCA_USER_TX_POOL_ID; ertm_info.fcr_rx_pool_id = MCA_FCR_RX_POOL_ID; ertm_info.fcr_tx_pool_id = MCA_FCR_TX_POOL_ID; p_ertm_info = &ertm_info; result = L2CAP_CONN_PENDING; } else result = L2CAP_CONN_OK; } /* deal with simultaneous control channel connect case */ } /* else reject their connection */ if (!p_tbl || (p_tbl->state != MCA_TC_ST_CFG)) { /* Send L2CAP connect rsp */ L2CA_ErtmConnectRsp (bd_addr, id, lcid, result, L2CAP_CONN_OK, p_ertm_info); /* if result ok, proceed with connection and send L2CAP config req */ if (result == L2CAP_CONN_OK) { /* set channel state */ p_tbl->state = MCA_TC_ST_CFG; /* Send L2CAP config req */ mca_set_cfg_by_tbl (&cfg, p_tbl); L2CA_ConfigReq(p_tbl->lcid, &cfg); } } } /******************************************************************************* ** ** Function mca_l2c_dconn_ind_cback ** ** Description This is the L2CAP connect indication callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_dconn_ind_cback(BD_ADDR bd_addr, UINT16 lcid, UINT16 psm, UINT8 id) { tMCA_HANDLE handle = mca_handle_by_dpsm(psm); tMCA_CCB *p_ccb; tMCA_DCB *p_dcb; tMCA_TC_TBL *p_tbl = NULL; UINT16 result; tL2CAP_CFG_INFO cfg; tL2CAP_ERTM_INFO *p_ertm_info = NULL, ertm_info; const tMCA_CHNL_CFG *p_chnl_cfg; MCA_TRACE_EVENT2 ("mca_l2c_dconn_ind_cback: lcid:x%x psm:x%x ", lcid, psm); if (((p_ccb = mca_ccb_by_bd(handle, bd_addr)) != NULL) && /* find the CCB */ (p_ccb->status == MCA_CCB_STAT_PENDING) && /* this CCB is expecting a MDL */ (p_ccb->p_tx_req && (p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) != NULL)) { /* found the associated dcb in listening mode */ /* proceed with connection */ p_dcb->lcid = lcid; p_tbl = mca_tc_tbl_dalloc(p_dcb); p_tbl->id = id; p_tbl->cfg_flags= MCA_L2C_CFG_CONN_ACP; p_chnl_cfg = p_dcb->p_chnl_cfg; /* assume that control channel has verified the security requirement */ /* Set the FCR options: control channel mandates ERTM */ ertm_info.preferred_mode = p_chnl_cfg->fcr_opt.mode; ertm_info.allowed_modes = (1 << p_chnl_cfg->fcr_opt.mode); ertm_info.user_rx_pool_id = p_chnl_cfg->user_rx_pool_id; ertm_info.user_tx_pool_id = p_chnl_cfg->user_tx_pool_id; ertm_info.fcr_rx_pool_id = p_chnl_cfg->fcr_rx_pool_id; ertm_info.fcr_tx_pool_id = p_chnl_cfg->fcr_tx_pool_id; p_ertm_info = &ertm_info; result = L2CAP_CONN_OK; } else { /* else we're not listening for traffic channel; reject * (this error code is specified by MCAP spec) */ result = L2CAP_CONN_NO_RESOURCES; } /* Send L2CAP connect rsp */ L2CA_ErtmConnectRsp (bd_addr, id, lcid, result, result, p_ertm_info); /* if result ok, proceed with connection */ if (result == L2CAP_CONN_OK) { /* transition to configuration state */ p_tbl->state = MCA_TC_ST_CFG; /* Send L2CAP config req */ mca_set_cfg_by_tbl (&cfg, p_tbl); L2CA_ConfigReq(lcid, &cfg); } } /******************************************************************************* ** ** Function mca_l2c_connect_cfm_cback ** ** Description This is the L2CAP connect confirm callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_connect_cfm_cback(UINT16 lcid, UINT16 result) { tMCA_TC_TBL *p_tbl; tL2CAP_CFG_INFO cfg; tMCA_CCB *p_ccb; MCA_TRACE_DEBUG2("mca_l2c_connect_cfm_cback lcid: x%x, result: %d", lcid, result); /* look up info for this channel */ if ((p_tbl = mca_tc_tbl_by_lcid(lcid)) != NULL) { MCA_TRACE_DEBUG2("p_tbl state: %d, tcid: %d", p_tbl->state, p_tbl->tcid); /* if in correct state */ if (p_tbl->state == MCA_TC_ST_CONN) { /* if result successful */ if (result == L2CAP_CONN_OK) { if (p_tbl->tcid != 0) { /* set channel state */ p_tbl->state = MCA_TC_ST_CFG; /* Send L2CAP config req */ mca_set_cfg_by_tbl (&cfg, p_tbl); L2CA_ConfigReq(lcid, &cfg); } else { p_ccb = mca_ccb_by_hdl((tMCA_CL)p_tbl->cb_idx); if (p_ccb == NULL) { result = L2CAP_CONN_NO_RESOURCES; } else { /* set channel state */ p_tbl->state = MCA_TC_ST_SEC_INT; p_tbl->lcid = lcid; p_tbl->cfg_flags= MCA_L2C_CFG_CONN_INT; /* Check the security */ btm_sec_mx_access_request (p_ccb->peer_addr, p_ccb->ctrl_vpsm, TRUE, BTM_SEC_PROTO_MCA, p_tbl->tcid, &mca_sec_check_complete_orig, p_tbl); } } } /* failure; notify adaption that channel closed */ if (result != L2CAP_CONN_OK) { p_tbl->cfg_flags |= MCA_L2C_CFG_DISCN_INT; mca_tc_close_ind(p_tbl, result); } } } } /******************************************************************************* ** ** Function mca_l2c_config_cfm_cback ** ** Description This is the L2CAP config confirm callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_config_cfm_cback(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) { tMCA_TC_TBL *p_tbl; /* look up info for this channel */ if ((p_tbl = mca_tc_tbl_by_lcid(lcid)) != NULL) { /* if in correct state */ if (p_tbl->state == MCA_TC_ST_CFG) { /* if result successful */ if (p_cfg->result == L2CAP_CONN_OK) { /* update cfg_flags */ p_tbl->cfg_flags |= MCA_L2C_CFG_CFM_DONE; /* if configuration complete */ if (p_tbl->cfg_flags & MCA_L2C_CFG_IND_DONE) { mca_tc_open_ind(p_tbl); } } /* else failure */ else { /* Send L2CAP disconnect req */ L2CA_DisconnectReq(lcid); } } } } /******************************************************************************* ** ** Function mca_l2c_config_ind_cback ** ** Description This is the L2CAP config indication callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_config_ind_cback(UINT16 lcid, tL2CAP_CFG_INFO *p_cfg) { tMCA_TC_TBL *p_tbl; UINT16 result = L2CAP_CFG_OK; /* look up info for this channel */ if ((p_tbl = mca_tc_tbl_by_lcid(lcid)) != NULL) { /* store the mtu in tbl */ if (p_cfg->mtu_present) { p_tbl->peer_mtu = p_cfg->mtu; if (p_tbl->peer_mtu < MCA_MIN_MTU) { result = L2CAP_CFG_UNACCEPTABLE_PARAMS; } } else { p_tbl->peer_mtu = L2CAP_DEFAULT_MTU; } MCA_TRACE_DEBUG3("peer_mtu: %d, lcid: x%x mtu_present:%d",p_tbl->peer_mtu, lcid, p_cfg->mtu_present); /* send L2CAP configure response */ memset(p_cfg, 0, sizeof(tL2CAP_CFG_INFO)); p_cfg->result = result; L2CA_ConfigRsp(lcid, p_cfg); /* if first config ind */ if ((p_tbl->cfg_flags & MCA_L2C_CFG_IND_DONE) == 0) { /* update cfg_flags */ p_tbl->cfg_flags |= MCA_L2C_CFG_IND_DONE; /* if configuration complete */ if (p_tbl->cfg_flags & MCA_L2C_CFG_CFM_DONE) { mca_tc_open_ind(p_tbl); } } } } /******************************************************************************* ** ** Function mca_l2c_disconnect_ind_cback ** ** Description This is the L2CAP disconnect indication callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_disconnect_ind_cback(UINT16 lcid, BOOLEAN ack_needed) { tMCA_TC_TBL *p_tbl; UINT16 reason = L2CAP_DISC_TIMEOUT; MCA_TRACE_DEBUG2("mca_l2c_disconnect_ind_cback lcid: %d, ack_needed: %d", lcid, ack_needed); /* look up info for this channel */ if ((p_tbl = mca_tc_tbl_by_lcid(lcid)) != NULL) { if (ack_needed) { /* send L2CAP disconnect response */ L2CA_DisconnectRsp(lcid); } p_tbl->cfg_flags = MCA_L2C_CFG_DISCN_ACP; if (ack_needed) reason = L2CAP_DISC_OK; mca_tc_close_ind(p_tbl, reason); } } /******************************************************************************* ** ** Function mca_l2c_disconnect_cfm_cback ** ** Description This is the L2CAP disconnect confirm callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_disconnect_cfm_cback(UINT16 lcid, UINT16 result) { tMCA_TC_TBL *p_tbl; MCA_TRACE_DEBUG2("mca_l2c_disconnect_cfm_cback lcid: x%x, result: %d", lcid, result); /* look up info for this channel */ if ((p_tbl = mca_tc_tbl_by_lcid(lcid)) != NULL) { p_tbl->cfg_flags = MCA_L2C_CFG_DISCN_INT; mca_tc_close_ind(p_tbl, result); } } /******************************************************************************* ** ** Function mca_l2c_congestion_ind_cback ** ** Description This is the L2CAP congestion indication callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_congestion_ind_cback(UINT16 lcid, BOOLEAN is_congested) { tMCA_TC_TBL *p_tbl; /* look up info for this channel */ if ((p_tbl = mca_tc_tbl_by_lcid(lcid)) != NULL) { mca_tc_cong_ind(p_tbl, is_congested); } } /******************************************************************************* ** ** Function mca_l2c_data_ind_cback ** ** Description This is the L2CAP data indication callback function. ** ** ** Returns void ** *******************************************************************************/ void mca_l2c_data_ind_cback(UINT16 lcid, BT_HDR *p_buf) { tMCA_TC_TBL *p_tbl; /* look up info for this channel */ if ((p_tbl = mca_tc_tbl_by_lcid(lcid)) != NULL) { mca_tc_data_ind(p_tbl, p_buf); } else /* prevent buffer leak */ GKI_freebuf(p_buf); } /******************************************************************************* ** ** Function mca_l2c_open_req ** ** Description This function calls L2CA_ConnectReq() to initiate a L2CAP channel. ** ** Returns void. ** *******************************************************************************/ UINT16 mca_l2c_open_req(BD_ADDR bd_addr, UINT16 psm, const tMCA_CHNL_CFG *p_chnl_cfg) { tL2CAP_ERTM_INFO ertm_info; if (p_chnl_cfg) { ertm_info.preferred_mode = p_chnl_cfg->fcr_opt.mode; ertm_info.allowed_modes = (1 << p_chnl_cfg->fcr_opt.mode); ertm_info.user_rx_pool_id = p_chnl_cfg->user_rx_pool_id; ertm_info.user_tx_pool_id = p_chnl_cfg->user_tx_pool_id; ertm_info.fcr_rx_pool_id = p_chnl_cfg->fcr_rx_pool_id; ertm_info.fcr_tx_pool_id = p_chnl_cfg->fcr_tx_pool_id; } else { ertm_info.preferred_mode = mca_l2c_fcr_opts_def.mode; ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_ERTM; ertm_info.user_rx_pool_id = MCA_USER_RX_POOL_ID; ertm_info.user_tx_pool_id = MCA_USER_TX_POOL_ID; ertm_info.fcr_rx_pool_id = MCA_FCR_RX_POOL_ID; ertm_info.fcr_tx_pool_id = MCA_FCR_TX_POOL_ID; } return L2CA_ErtmConnectReq (psm, bd_addr, &ertm_info); }