1 /******************************************************************************
2  *
3  *  Copyright 2009-2012 Broadcom Corporation
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at:
8  *
9  *  http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ******************************************************************************/
18 
19 /******************************************************************************
20  *
21  *  This is the implementation file for the MCAP at L2CAP Interface.
22  *
23  ******************************************************************************/
24 #include <string.h>
25 
26 #include "bt_target.h"
27 #include "bt_utils.h"
28 #include "btm_api.h"
29 #include "btm_int.h"
30 #include "mca_api.h"
31 #include "mca_defs.h"
32 #include "mca_int.h"
33 #include "osi/include/osi.h"
34 
35 /* L2CAP callback function structure */
36 const tL2CAP_APPL_INFO mca_l2c_int_appl = {
37     NULL,
38     mca_l2c_connect_cfm_cback,
39     NULL,
40     mca_l2c_config_ind_cback,
41     mca_l2c_config_cfm_cback,
42     mca_l2c_disconnect_ind_cback,
43     mca_l2c_disconnect_cfm_cback,
44     NULL,
45     mca_l2c_data_ind_cback,
46     mca_l2c_congestion_ind_cback,
47     NULL,
48     NULL /* tL2CA_CREDITS_RECEIVED_CB */};
49 
50 /* Control channel eL2CAP default options */
51 const tL2CAP_FCR_OPTS mca_l2c_fcr_opts_def = {
52     L2CAP_FCR_ERTM_MODE,          /* Mandatory for MCAP */
53     MCA_FCR_OPT_TX_WINDOW_SIZE,   /* Tx window size */
54     MCA_FCR_OPT_MAX_TX_B4_DISCNT, /* Maximum transmissions before
55                                      disconnecting */
56     MCA_FCR_OPT_RETX_TOUT,        /* Retransmission timeout (2 secs) */
57     MCA_FCR_OPT_MONITOR_TOUT,     /* Monitor timeout (12 secs) */
58     MCA_FCR_OPT_MPS_SIZE          /* MPS segment size */
59 };
60 
61 /*******************************************************************************
62  *
63  * Function         mca_sec_check_complete_term
64  *
65  * Description      The function called when Security Manager finishes
66  *                  verification of the service side connection
67  *
68  * Returns          void
69  *
70  ******************************************************************************/
mca_sec_check_complete_term(const RawAddress * bd_addr,UNUSED_ATTR tBT_TRANSPORT transport,void * p_ref_data,uint8_t res)71 static void mca_sec_check_complete_term(const RawAddress* bd_addr,
72                                         UNUSED_ATTR tBT_TRANSPORT transport,
73                                         void* p_ref_data, uint8_t res) {
74   tMCA_TC_TBL* p_tbl = (tMCA_TC_TBL*)p_ref_data;
75   tL2CAP_CFG_INFO cfg;
76   tL2CAP_ERTM_INFO ertm_info;
77 
78   MCA_TRACE_DEBUG("mca_sec_check_complete_term res: %d", res);
79 
80   if (res == BTM_SUCCESS) {
81     MCA_TRACE_DEBUG("lcid:x%x id:x%x", p_tbl->lcid, p_tbl->id);
82     /* Set the FCR options: control channel mandates ERTM */
83     ertm_info.preferred_mode = mca_l2c_fcr_opts_def.mode;
84     ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_ERTM;
85     ertm_info.user_rx_buf_size = MCA_USER_RX_BUF_SIZE;
86     ertm_info.user_tx_buf_size = MCA_USER_TX_BUF_SIZE;
87     ertm_info.fcr_rx_buf_size = MCA_FCR_RX_BUF_SIZE;
88     ertm_info.fcr_tx_buf_size = MCA_FCR_TX_BUF_SIZE;
89     /* Send response to the L2CAP layer. */
90     L2CA_ErtmConnectRsp(*bd_addr, p_tbl->id, p_tbl->lcid, L2CAP_CONN_OK,
91                         L2CAP_CONN_OK, &ertm_info);
92 
93     /* transition to configuration state */
94     p_tbl->state = MCA_TC_ST_CFG;
95 
96     /* Send L2CAP config req */
97     mca_set_cfg_by_tbl(&cfg, p_tbl);
98     L2CA_ConfigReq(p_tbl->lcid, &cfg);
99   } else {
100     L2CA_ConnectRsp(*bd_addr, p_tbl->id, p_tbl->lcid, L2CAP_CONN_SECURITY_BLOCK,
101                     L2CAP_CONN_OK);
102     mca_tc_close_ind(p_tbl, L2CAP_CONN_SECURITY_BLOCK);
103   }
104 }
105 
106 /*******************************************************************************
107  *
108  * Function         mca_sec_check_complete_orig
109  *
110  * Description      The function called when Security Manager finishes
111  *                  verification of the service side connection
112  *
113  * Returns          void
114  *
115  ******************************************************************************/
mca_sec_check_complete_orig(UNUSED_ATTR const RawAddress * bd_addr,UNUSED_ATTR tBT_TRANSPORT transport,void * p_ref_data,uint8_t res)116 static void mca_sec_check_complete_orig(UNUSED_ATTR const RawAddress* bd_addr,
117                                         UNUSED_ATTR tBT_TRANSPORT transport,
118                                         void* p_ref_data, uint8_t res) {
119   tMCA_TC_TBL* p_tbl = (tMCA_TC_TBL*)p_ref_data;
120   tL2CAP_CFG_INFO cfg;
121 
122   MCA_TRACE_DEBUG("mca_sec_check_complete_orig res: %d", res);
123 
124   if (res == BTM_SUCCESS) {
125     /* set channel state */
126     p_tbl->state = MCA_TC_ST_CFG;
127 
128     /* Send L2CAP config req */
129     mca_set_cfg_by_tbl(&cfg, p_tbl);
130     L2CA_ConfigReq(p_tbl->lcid, &cfg);
131   } else {
132     L2CA_DisconnectReq(p_tbl->lcid);
133     mca_tc_close_ind(p_tbl, L2CAP_CONN_SECURITY_BLOCK);
134   }
135 }
136 /*******************************************************************************
137  *
138  * Function         mca_l2c_cconn_ind_cback
139  *
140  * Description      This is the L2CAP connect indication callback function.
141  *
142  * Returns          void
143  *
144  ******************************************************************************/
mca_l2c_cconn_ind_cback(const RawAddress & bd_addr,uint16_t lcid,uint16_t psm,uint8_t id)145 void mca_l2c_cconn_ind_cback(const RawAddress& bd_addr, uint16_t lcid,
146                              uint16_t psm, uint8_t id) {
147   tMCA_HANDLE handle = mca_handle_by_cpsm(psm);
148   tMCA_CCB* p_ccb;
149   tMCA_TC_TBL* p_tbl = NULL;
150   uint16_t result = L2CAP_CONN_NO_RESOURCES;
151   tBTM_STATUS rc;
152   tL2CAP_ERTM_INFO ertm_info, *p_ertm_info = NULL;
153   tL2CAP_CFG_INFO cfg;
154 
155   MCA_TRACE_EVENT("mca_l2c_cconn_ind_cback: lcid:x%x psm:x%x id:x%x", lcid, psm,
156                   id);
157 
158   /* do we already have a control channel for this peer? */
159   p_ccb = mca_ccb_by_bd(handle, bd_addr);
160   if (p_ccb == NULL) {
161     /* no, allocate ccb */
162     p_ccb = mca_ccb_alloc(handle, bd_addr);
163     if (p_ccb != NULL) {
164       /* allocate and set up entry */
165       p_ccb->lcid = lcid;
166       p_tbl = mca_tc_tbl_calloc(p_ccb);
167       p_tbl->id = id;
168       p_tbl->cfg_flags = MCA_L2C_CFG_CONN_ACP;
169       /* proceed with connection */
170       /* Check the security */
171       rc = btm_sec_mx_access_request(bd_addr, psm, false, BTM_SEC_PROTO_MCA, 0,
172                                      &mca_sec_check_complete_term, p_tbl);
173       if (rc == BTM_CMD_STARTED) {
174         /* Set the FCR options: control channel mandates ERTM */
175         ertm_info.preferred_mode = mca_l2c_fcr_opts_def.mode;
176         ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_ERTM;
177         ertm_info.user_rx_buf_size = MCA_USER_RX_BUF_SIZE;
178         ertm_info.user_tx_buf_size = MCA_USER_TX_BUF_SIZE;
179         ertm_info.fcr_rx_buf_size = MCA_FCR_RX_BUF_SIZE;
180         ertm_info.fcr_tx_buf_size = MCA_FCR_TX_BUF_SIZE;
181         p_ertm_info = &ertm_info;
182         result = L2CAP_CONN_PENDING;
183       } else
184         result = L2CAP_CONN_OK;
185     }
186 
187     /*  deal with simultaneous control channel connect case */
188   }
189   /* else reject their connection */
190 
191   if (!p_tbl || (p_tbl->state != MCA_TC_ST_CFG)) {
192     /* Send L2CAP connect rsp */
193     L2CA_ErtmConnectRsp(bd_addr, id, lcid, result, L2CAP_CONN_OK, p_ertm_info);
194 
195     /* if result ok, proceed with connection and send L2CAP
196        config req */
197     if (result == L2CAP_CONN_OK) {
198       /* set channel state */
199       p_tbl->state = MCA_TC_ST_CFG;
200 
201       /* Send L2CAP config req */
202       mca_set_cfg_by_tbl(&cfg, p_tbl);
203       L2CA_ConfigReq(p_tbl->lcid, &cfg);
204     }
205   }
206 }
207 
208 /*******************************************************************************
209  *
210  * Function         mca_l2c_dconn_ind_cback
211  *
212  * Description      This is the L2CAP connect indication callback function.
213  *
214  *
215  * Returns          void
216  *
217  ******************************************************************************/
mca_l2c_dconn_ind_cback(const RawAddress & bd_addr,uint16_t lcid,uint16_t psm,uint8_t id)218 void mca_l2c_dconn_ind_cback(const RawAddress& bd_addr, uint16_t lcid,
219                              uint16_t psm, uint8_t id) {
220   tMCA_HANDLE handle = mca_handle_by_dpsm(psm);
221   tMCA_CCB* p_ccb;
222   tMCA_DCB* p_dcb;
223   tMCA_TC_TBL* p_tbl = NULL;
224   uint16_t result;
225   tL2CAP_CFG_INFO cfg;
226   tL2CAP_ERTM_INFO *p_ertm_info = NULL, ertm_info;
227   const tMCA_CHNL_CFG* p_chnl_cfg;
228 
229   MCA_TRACE_EVENT("mca_l2c_dconn_ind_cback: lcid:x%x psm:x%x ", lcid, psm);
230 
231   if (((p_ccb = mca_ccb_by_bd(handle, bd_addr)) != NULL) && /* find the CCB */
232       (p_ccb->status ==
233        MCA_CCB_STAT_PENDING) && /* this CCB is expecting a MDL */
234       (p_ccb->p_tx_req &&
235        (p_dcb = mca_dcb_by_hdl(p_ccb->p_tx_req->dcb_idx)) != NULL)) {
236     /* found the associated dcb in listening mode */
237     /* proceed with connection */
238     p_dcb->lcid = lcid;
239     p_tbl = mca_tc_tbl_dalloc(p_dcb);
240     p_tbl->id = id;
241     p_tbl->cfg_flags = MCA_L2C_CFG_CONN_ACP;
242     p_chnl_cfg = p_dcb->p_chnl_cfg;
243     /* assume that control channel has verified the security requirement */
244     /* Set the FCR options: control channel mandates ERTM */
245     ertm_info.preferred_mode = p_chnl_cfg->fcr_opt.mode;
246     ertm_info.allowed_modes = (1 << p_chnl_cfg->fcr_opt.mode);
247     ertm_info.user_rx_buf_size = p_chnl_cfg->user_rx_buf_size;
248     ertm_info.user_tx_buf_size = p_chnl_cfg->user_tx_buf_size;
249     ertm_info.fcr_rx_buf_size = p_chnl_cfg->fcr_rx_buf_size;
250     ertm_info.fcr_tx_buf_size = p_chnl_cfg->fcr_tx_buf_size;
251     p_ertm_info = &ertm_info;
252     result = L2CAP_CONN_OK;
253   } else {
254     /* else we're not listening for traffic channel; reject
255      * (this error code is specified by MCAP spec) */
256     result = L2CAP_CONN_NO_RESOURCES;
257   }
258 
259   /* Send L2CAP connect rsp */
260   L2CA_ErtmConnectRsp(bd_addr, id, lcid, result, result, p_ertm_info);
261 
262   /* if result ok, proceed with connection */
263   if (result == L2CAP_CONN_OK) {
264     /* transition to configuration state */
265     p_tbl->state = MCA_TC_ST_CFG;
266 
267     /* Send L2CAP config req */
268     mca_set_cfg_by_tbl(&cfg, p_tbl);
269     L2CA_ConfigReq(lcid, &cfg);
270   }
271 }
272 
273 /*******************************************************************************
274  *
275  * Function         mca_l2c_connect_cfm_cback
276  *
277  * Description      This is the L2CAP connect confirm callback function.
278  *
279  *
280  * Returns          void
281  *
282  ******************************************************************************/
mca_l2c_connect_cfm_cback(uint16_t lcid,uint16_t result)283 void mca_l2c_connect_cfm_cback(uint16_t lcid, uint16_t result) {
284   tMCA_TC_TBL* p_tbl;
285   tL2CAP_CFG_INFO cfg;
286   tMCA_CCB* p_ccb;
287 
288   MCA_TRACE_DEBUG("mca_l2c_connect_cfm_cback lcid: x%x, result: %d", lcid,
289                   result);
290   /* look up info for this channel */
291   p_tbl = mca_tc_tbl_by_lcid(lcid);
292   if (p_tbl != NULL) {
293     MCA_TRACE_DEBUG("p_tbl state: %d, tcid: %d", p_tbl->state, p_tbl->tcid);
294     /* if in correct state */
295     if (p_tbl->state == MCA_TC_ST_CONN) {
296       /* if result successful */
297       if (result == L2CAP_CONN_OK) {
298         if (p_tbl->tcid != 0) {
299           /* set channel state */
300           p_tbl->state = MCA_TC_ST_CFG;
301 
302           /* Send L2CAP config req */
303           mca_set_cfg_by_tbl(&cfg, p_tbl);
304           L2CA_ConfigReq(lcid, &cfg);
305         } else {
306           p_ccb = mca_ccb_by_hdl((tMCA_CL)p_tbl->cb_idx);
307           if (p_ccb == NULL) {
308             result = L2CAP_CONN_NO_RESOURCES;
309           } else {
310             /* set channel state */
311             p_tbl->state = MCA_TC_ST_SEC_INT;
312             p_tbl->lcid = lcid;
313             p_tbl->cfg_flags = MCA_L2C_CFG_CONN_INT;
314 
315             /* Check the security */
316             btm_sec_mx_access_request(p_ccb->peer_addr, p_ccb->ctrl_vpsm, true,
317                                       BTM_SEC_PROTO_MCA, p_tbl->tcid,
318                                       &mca_sec_check_complete_orig, p_tbl);
319           }
320         }
321       }
322 
323       /* failure; notify adaption that channel closed */
324       if (result != L2CAP_CONN_OK) {
325         p_tbl->cfg_flags |= MCA_L2C_CFG_DISCN_INT;
326         mca_tc_close_ind(p_tbl, result);
327       }
328     }
329   }
330 }
331 
332 /*******************************************************************************
333  *
334  * Function         mca_l2c_config_cfm_cback
335  *
336  * Description      This is the L2CAP config confirm callback function.
337  *
338  *
339  * Returns          void
340  *
341  ******************************************************************************/
mca_l2c_config_cfm_cback(uint16_t lcid,tL2CAP_CFG_INFO * p_cfg)342 void mca_l2c_config_cfm_cback(uint16_t lcid, tL2CAP_CFG_INFO* p_cfg) {
343   tMCA_TC_TBL* p_tbl;
344 
345   /* look up info for this channel */
346   p_tbl = mca_tc_tbl_by_lcid(lcid);
347   if (p_tbl != NULL) {
348     /* if in correct state */
349     if (p_tbl->state == MCA_TC_ST_CFG) {
350       /* if result successful */
351       if (p_cfg->result == L2CAP_CONN_OK) {
352         /* update cfg_flags */
353         p_tbl->cfg_flags |= MCA_L2C_CFG_CFM_DONE;
354 
355         /* if configuration complete */
356         if (p_tbl->cfg_flags & MCA_L2C_CFG_IND_DONE) {
357           mca_tc_open_ind(p_tbl);
358         }
359       }
360       /* else failure */
361       else {
362         /* Send L2CAP disconnect req */
363         L2CA_DisconnectReq(lcid);
364       }
365     }
366   }
367 }
368 
369 /*******************************************************************************
370  *
371  * Function         mca_l2c_config_ind_cback
372  *
373  * Description      This is the L2CAP config indication callback function.
374  *
375  *
376  * Returns          void
377  *
378  ******************************************************************************/
mca_l2c_config_ind_cback(uint16_t lcid,tL2CAP_CFG_INFO * p_cfg)379 void mca_l2c_config_ind_cback(uint16_t lcid, tL2CAP_CFG_INFO* p_cfg) {
380   tMCA_TC_TBL* p_tbl;
381   uint16_t result = L2CAP_CFG_OK;
382 
383   /* look up info for this channel */
384   p_tbl = mca_tc_tbl_by_lcid(lcid);
385   if (p_tbl != NULL) {
386     /* store the mtu in tbl */
387     if (p_cfg->mtu_present) {
388       p_tbl->peer_mtu = p_cfg->mtu;
389       if (p_tbl->peer_mtu < MCA_MIN_MTU) {
390         result = L2CAP_CFG_UNACCEPTABLE_PARAMS;
391       }
392     } else {
393       p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;
394     }
395     MCA_TRACE_DEBUG("peer_mtu: %d, lcid: x%x mtu_present:%d", p_tbl->peer_mtu,
396                     lcid, p_cfg->mtu_present);
397 
398     /* send L2CAP configure response */
399     memset(p_cfg, 0, sizeof(tL2CAP_CFG_INFO));
400     p_cfg->result = result;
401     L2CA_ConfigRsp(lcid, p_cfg);
402 
403     /* if first config ind */
404     if ((p_tbl->cfg_flags & MCA_L2C_CFG_IND_DONE) == 0) {
405       /* update cfg_flags */
406       p_tbl->cfg_flags |= MCA_L2C_CFG_IND_DONE;
407 
408       /* if configuration complete */
409       if (p_tbl->cfg_flags & MCA_L2C_CFG_CFM_DONE) {
410         mca_tc_open_ind(p_tbl);
411       }
412     }
413   }
414 }
415 
416 /*******************************************************************************
417  *
418  * Function         mca_l2c_disconnect_ind_cback
419  *
420  * Description      This is the L2CAP disconnect indication callback function.
421  *
422  *
423  * Returns          void
424  *
425  ******************************************************************************/
mca_l2c_disconnect_ind_cback(uint16_t lcid,bool ack_needed)426 void mca_l2c_disconnect_ind_cback(uint16_t lcid, bool ack_needed) {
427   tMCA_TC_TBL* p_tbl;
428   uint16_t reason = L2CAP_DISC_TIMEOUT;
429 
430   MCA_TRACE_DEBUG("mca_l2c_disconnect_ind_cback lcid: %d, ack_needed: %d", lcid,
431                   ack_needed);
432   /* look up info for this channel */
433   p_tbl = mca_tc_tbl_by_lcid(lcid);
434   if (p_tbl != NULL) {
435     if (ack_needed) {
436       /* send L2CAP disconnect response */
437       L2CA_DisconnectRsp(lcid);
438     }
439 
440     p_tbl->cfg_flags = MCA_L2C_CFG_DISCN_ACP;
441     if (ack_needed) reason = L2CAP_DISC_OK;
442     mca_tc_close_ind(p_tbl, reason);
443   }
444 }
445 
446 /*******************************************************************************
447  *
448  * Function         mca_l2c_disconnect_cfm_cback
449  *
450  * Description      This is the L2CAP disconnect confirm callback function.
451  *
452  *
453  * Returns          void
454  *
455  ******************************************************************************/
mca_l2c_disconnect_cfm_cback(uint16_t lcid,uint16_t result)456 void mca_l2c_disconnect_cfm_cback(uint16_t lcid, uint16_t result) {
457   tMCA_TC_TBL* p_tbl;
458 
459   MCA_TRACE_DEBUG("mca_l2c_disconnect_cfm_cback lcid: x%x, result: %d", lcid,
460                   result);
461   /* look up info for this channel */
462   p_tbl = mca_tc_tbl_by_lcid(lcid);
463   if (p_tbl != NULL) {
464     p_tbl->cfg_flags = MCA_L2C_CFG_DISCN_INT;
465     mca_tc_close_ind(p_tbl, result);
466   }
467 }
468 
469 /*******************************************************************************
470  *
471  * Function         mca_l2c_congestion_ind_cback
472  *
473  * Description      This is the L2CAP congestion indication callback function.
474  *
475  *
476  * Returns          void
477  *
478  ******************************************************************************/
mca_l2c_congestion_ind_cback(uint16_t lcid,bool is_congested)479 void mca_l2c_congestion_ind_cback(uint16_t lcid, bool is_congested) {
480   tMCA_TC_TBL* p_tbl;
481 
482   /* look up info for this channel */
483   p_tbl = mca_tc_tbl_by_lcid(lcid);
484   if (p_tbl != NULL) {
485     mca_tc_cong_ind(p_tbl, is_congested);
486   }
487 }
488 
489 /*******************************************************************************
490  *
491  * Function         mca_l2c_data_ind_cback
492  *
493  * Description      This is the L2CAP data indication callback function.
494  *
495  *
496  * Returns          void
497  *
498  ******************************************************************************/
mca_l2c_data_ind_cback(uint16_t lcid,BT_HDR * p_buf)499 void mca_l2c_data_ind_cback(uint16_t lcid, BT_HDR* p_buf) {
500   tMCA_TC_TBL* p_tbl;
501 
502   /* look up info for this channel */
503   p_tbl = mca_tc_tbl_by_lcid(lcid);
504   if (p_tbl != NULL) {
505     mca_tc_data_ind(p_tbl, p_buf);
506   } else /* prevent buffer leak */
507     osi_free(p_buf);
508 }
509 
510 /*******************************************************************************
511  *
512  * Function         mca_l2c_open_req
513  *
514  * Description      This function calls L2CA_ConnectReq() to initiate a L2CAP
515  *                  channel.
516  *
517  * Returns          void.
518  *
519  ******************************************************************************/
mca_l2c_open_req(const RawAddress & bd_addr,uint16_t psm,const tMCA_CHNL_CFG * p_chnl_cfg)520 uint16_t mca_l2c_open_req(const RawAddress& bd_addr, uint16_t psm,
521                           const tMCA_CHNL_CFG* p_chnl_cfg) {
522   tL2CAP_ERTM_INFO ertm_info;
523 
524   if (p_chnl_cfg) {
525     ertm_info.preferred_mode = p_chnl_cfg->fcr_opt.mode;
526     ertm_info.allowed_modes = (1 << p_chnl_cfg->fcr_opt.mode);
527     ertm_info.user_rx_buf_size = p_chnl_cfg->user_rx_buf_size;
528     ertm_info.user_tx_buf_size = p_chnl_cfg->user_tx_buf_size;
529     ertm_info.fcr_rx_buf_size = p_chnl_cfg->fcr_rx_buf_size;
530     ertm_info.fcr_tx_buf_size = p_chnl_cfg->fcr_tx_buf_size;
531   } else {
532     ertm_info.preferred_mode = mca_l2c_fcr_opts_def.mode;
533     ertm_info.allowed_modes = L2CAP_FCR_CHAN_OPT_ERTM;
534     ertm_info.user_rx_buf_size = MCA_USER_RX_BUF_SIZE;
535     ertm_info.user_tx_buf_size = MCA_USER_TX_BUF_SIZE;
536     ertm_info.fcr_rx_buf_size = MCA_FCR_RX_BUF_SIZE;
537     ertm_info.fcr_tx_buf_size = MCA_FCR_TX_BUF_SIZE;
538   }
539   return L2CA_ErtmConnectReq(psm, bd_addr, &ertm_info);
540 }
541