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