欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

L2CAP 数据传输和接收

最编程 2024-07-04 12:29:41
...

ACL 链路在 Bluetooth 中非常重要,一些重要的应用如 A2DP, 基于 RFCOMM 的应用。BNEP等都要建立 ACL 链路,发送/接收ACL 包。跟大家一起来分析 ACL 包发送/接收流程,以及涉及到的重要 command/event。


(一)


ACL包发送

以下的图是各种应用层使用 L2CAP 的 API:L2CA_DataWrite 发送数据流的过程,此API继续往下走,我仅分析了正常数据流的走向(临时没有考虑别的情况)。



应用层数据到 L2CAP 的入口

我们假设一个听音乐的场景,我跟大家一起分析音乐数据流 AVDTP 以下层的传送。

在 AVDTP 中,所有的功能想发送 Data,必须调用 avdt_ad_write_req 这个函数,我们就从这个函数入手分析。

 1 //当CCB或SCB给l2cap的 Channel 发送数据时,他们终于都会使用到L2CAP的 API:L2CA_Data_Write
 2 UINT8 avdt_ad_write_req(UINT8 type, tAVDT_CCB *p_ccb, tAVDT_SCB *p_scb, BT_HDR *p_buf)
 3 {
 4     UINT8   tcid;
 5 
 6     /* get tcid from type, scb */
 7     tcid = avdt_ad_type_to_tcid(type, p_scb);
 8 
 9 
10     return L2CA_DataWrite(avdt_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid, p_buf);
11 }
12 //L2CA_DataWrite的返回形式有三种,各自是:
13 //1. L2CAP_DW_SUCCESS:此数据写成功
14 //2.L2CAP_DW_CONGESTED:写数据成功,可是当前信道拥堵
15 //3.L2CAP_DW_FAILED:写数据失败
16 UINT8 L2CA_DataWrite (UINT16 cid, BT_HDR *p_data)
17 {
18     L2CAP_TRACE_API2 ("L2CA_DataWrite()  CID: 0x%04x  Len: %d", cid, p_data->len);
19     return l2c_data_write (cid, p_data, L2CAP_FLUSHABLE_CH_BASED);
20 }

当我们的音乐数据流到达 l2c_data_write 这个函数时。标志数据流正式进入到L2CAP层。

我们在以下的源代码中深入分析 l2c_data_write 这个函数。

l2c_data_write 这个函数做的事情主要有:

  1. 依据參数 cid(Channel ID) 找到 相应的 ccb(Channel Control Block), 找不到返回 L2CAP_DW_FAILED
  2. 假设測试者 打开 TESTER 这个宏,发送随意数据,当数据大小 大于 MTU 最大值。也会返回 L2CAP_DW_FAILED
  3. 通过检查 p_ccb->cong_sent 字段,TRUE,则说明当前 Channel 已经拥挤。此时L2CAP的这个Channel不在接收数据,返回 L2CAP_DW_FAILED
  4. 以上三个条件都通过,说明数据可发送,将数据通过 l2c_csm_execute 继续处理。进入 l2c_csm_execute 函数,标志着这笔数据已经成功交给 l2CAP 来处理,与上层已经没有关系了。
  5. l2c_csm_execute 函数运行结束后。再次检查 p_ccb->cong_sent 字段,看看当前的 Channel 是否拥挤,假设拥挤则告诉上层 L2CAP_DW_CONGESTED。否则返回 L2CAP_DW_SUCCESS,表示数据已经成功发送。

  1 //返回的数据跟上面的 L2CA_DataWrite 作用同样
  2 UINT8 l2c_data_write (UINT16 cid, BT_HDR *p_data, UINT16 flags)
  3 {
  4     tL2C_CCB        *p_ccb;
  5 
  6     //遍历l2cb.ccb_pool,通过Channel ID找到相应的Channel Control Block
  7     //l2cu_find_ccb_by_cid 见以下源代码凝视
  8     if ((p_ccb = l2cu_find_ccb_by_cid (NULL, cid)) == NULL)
  9     {
 10         L2CAP_TRACE_WARNING1 ("L2CAP - no CCB for L2CA_DataWrite, CID: %d", cid);
 11         GKI_freebuf (p_data);
 12         return (L2CAP_DW_FAILED);
 13     }
 14 
 15 #ifndef TESTER /* Tester may send any amount of data. otherwise sending message
 16                   bigger than mtu size of peer is a violation of protocol */
 17     if (p_data->len > p_ccb->peer_cfg.mtu)
 18     {
 19         L2CAP_TRACE_WARNING1 ("L2CAP - CID: 0x%04x  cannot send message bigger than peer's mtu size", cid);
 20         GKI_freebuf (p_data);
 21         return (L2CAP_DW_FAILED);
 22     }
 23 #endif
 24 
 25     /* channel based, packet based flushable or non-flushable */
 26     //Bluedroid中默认的是 L2CAP_FLUSHABLE_CH_BASED
 27     //这个 layer_specific 在 数据发送的 l2c_link_send_to_lower 中表示 ACL包分包 个数
 28     p_data->layer_specific = flags;
 29 
 30     //发现本 Channel 已经拥堵,直接返回L2CAP_DW_FAILED 告诉上层等会再发数据
 31     //当几个应用 共用 此 Channel 可能会出现这样的情况
 32     if (p_ccb->cong_sent)
 33     {
 34         L2CAP_TRACE_ERROR3 ("L2CAP - CID: 0x%04x cannot send, already congested  xmit_hold_q.count: %u  buff_quota: %u",
 35                             p_ccb->local_cid, p_ccb->xmit_hold_q.count, p_ccb->buff_quota);
 36 
 37         GKI_freebuf (p_data);
 38         return (L2CAP_DW_FAILED);
 39     }
 40     //毫无疑问啦,这个函数就是我们继续须要分析的函数
 41     l2c_csm_execute (p_ccb, L2CEVT_L2CA_DATA_WRITE, p_data);
 42 
 43     //已经将上层的这笔数据发送完,假设此 Channel 拥挤了(之前发送这笔包还没拥挤)
 44     //返回 L2CAP_DW_CONGESTED 告诉上层当前信道拥挤,你要给我L2CAP层发数据,是不发下来的
 45     if (p_ccb->cong_sent)
 46         return (L2CAP_DW_CONGESTED);
 47 
 48     //成功发送,而且此时 Channel 并不拥挤
 49     return (L2CAP_DW_SUCCESS);
 50 }
 51 
 52 //通过 Channel ID 找到 Channel Control Block
 53 tL2C_CCB *l2cu_find_ccb_by_cid (tL2C_LCB *p_lcb, UINT16 local_cid)
 54 {
 55     tL2C_CCB    *p_ccb = NULL;
 56 #if (L2CAP_UCD_INCLUDED == TRUE)
 57     UINT8 xx;
 58 #endif
 59 
 60     if (local_cid >= L2CAP_BASE_APPL_CID) //大于或等于 0x0040 说明不是 Fixed Channel
 61     {
 62         /* find the associated CCB by "index" */
 63         local_cid -= L2CAP_BASE_APPL_CID;
 64 
 65         if (local_cid >= MAX_L2CAP_CHANNELS)
 66             return NULL;
 67 
 68         p_ccb = l2cb.ccb_pool + local_cid; //直接通过地址偏移找到
 69 
 70         /* make sure the CCB is in use */
 71         if (!p_ccb->in_use)
 72         {
 73             p_ccb = NULL;
 74         }
 75         /* make sure it's for the same LCB */
 76         else if (p_lcb && p_lcb != p_ccb->p_lcb)
 77         {
 78             p_ccb = NULL;
 79         }
 80     }
 81 #if (L2CAP_UCD_INCLUDED == TRUE) //默认是关闭的,既然从上层来的都是 数据包了,我觉得不会用到 Fixed Channel
 82     else
 83     {
 84         /* searching fixed channel */
 85         p_ccb = l2cb.ccb_pool;
 86         for ( xx = 0; xx < MAX_L2CAP_CHANNELS; xx++ )
 87         {
 88             if ((p_ccb->local_cid == local_cid)
 89               &&(p_ccb->in_use)
 90               &&(p_lcb == p_ccb->p_lcb))
 91                 break;
 92             else
 93                 p_ccb++;
 94         }
 95         if ( xx >= MAX_L2CAP_CHANNELS )
 96             return NULL;
 97     }
 98 #endif
 99 
100     return (p_ccb);
101 }


(二)

上一节讲了数据流入口。本文分析L2CAP的处理函数。

L2CAP层的处理

我们的音乐数据。通过 L2CAP 入口函数 l2c_data_write 的层层“考验”。已经顺利进入到 L2CAP 里了,以下我们来看看 L2CAP 层详细是怎么处理数据的。

首先我们进入了 L2CAP 层的状态机。

 1 void l2c_csm_execute (tL2C_CCB *p_ccb, UINT16 event, void *p_data)
 2 {
 3     switch (p_ccb->chnl_state)
 4     {
 5     case CST_CLOSED:
 6         l2c_csm_closed (p_ccb, event, p_data);
 7         break;
 8 
 9     case CST_ORIG_W4_SEC_COMP:
10         l2c_csm_orig_w4_sec_comp (p_ccb, event, p_data);
11         break;
12 
13     case CST_TERM_W4_SEC_COMP:
14         l2c_csm_term_w4_sec_comp (p_ccb, event, p_data);
15         break;
16 
17     case CST_W4_L2CAP_CONNECT_RSP:
18         l2c_csm_w4_l2cap_connect_rsp (p_ccb, event, p_data);
19         break;
20 
21     case CST_W4_L2CA_CONNECT_RSP:
22         l2c_csm_w4_l2ca_connect_rsp (p_ccb, event, p_data);
23         break;
24 
25     case CST_CONFIG:
26         l2c_csm_config (p_ccb, event, p_data);
27         break;
28 
29     case CST_OPEN:
30         l2c_csm_open (p_ccb, event, p_data);
31         break;
32 
33     case CST_W4_L2CAP_DISCONNECT_RSP:
34         l2c_csm_w4_l2cap_disconnect_rsp (p_ccb, event, p_data);
35         break;
36 
37     case CST_W4_L2CA_DISCONNECT_RSP:
38         l2c_csm_w4_l2ca_disconnect_rsp (p_ccb, event, p_data);
39         break;
40 
41     default:
42         break;
43     }
44 }

详细的 Channel 状态信息例如以下

 1 typedef enum
 2 {
 3     CST_CLOSED,                           /* Channel is in clodes state           */
 4     CST_ORIG_W4_SEC_COMP,                 /* Originator waits security clearence  */
 5     CST_TERM_W4_SEC_COMP,                 /* Acceptor waits security clearence    */
 6     CST_W4_L2CAP_CONNECT_RSP,             /* Waiting for peer conenct response    */
 7     CST_W4_L2CA_CONNECT_RSP,              /* Waiting for upper layer connect rsp  */
 8     CST_CONFIG,                           /* Negotiating configuration            */
 9     CST_OPEN,                             /* Data transfer state                  */
10     CST_W4_L2CAP_DISCONNECT_RSP,          /* Waiting for peer disconnect rsp      */
11     CST_W4_L2CA_DISCONNECT_RSP            /* Waiting for upper layer disc rsp     */
12 } tL2C_CHNL_STATE;

l2c_csm_execute 函数通过 p_ccb 中的 chnl_state 字段,来确定接下来数据包的走向。例如以下图所看到的:


  1. CST_CLOSED 状态:这个 case 处理 Channel 处于 CLOSED 状态的事件。这个状态仅仅存在于 L2CAP 的 Link 初始建立过程中。

  2. 假设发现事件是 L2CEVT_LP_DISCONNECT_IND,则当前 Link 已经断开。则释放当前 Channel的 ccb;

  3. 若事件是 L2CEVT_LP_CONNECT_CFM,则置 p_ccb->chnl_state 为 CST_ORIG_W4_SEC_COMP 状态,以下会接着介绍这个。
  4. 假设是 L2CEVT_LP_CONNECT_CFM_NEG 则说明当前 Link 失败,
  5. 假设是 L2CEVT_SEC_COMP 则说明 Security 已经清除成功。

  6. 若是 L2CEVT_L2CA_CONNECT_REQ 则说明 来自上层的 connect 请求。假设当前处于 sniff 状态,要先取消 sniff。
  7. L2CEVT_SEC_COMP_NEG 说明 Security 失败,清除当前 CCB,返回
  8. L2CEVT_L2CAP_CONNECT_REQ 说明是 Peer connect request。既然成功连接了。结束掉 timer
  9. L2CEVT_L2CA_DATA_WRITE,假设我们的数据从上层经过这里,而且是 CST_CLOSED,由于当前的 Channel 没有建立好,而且上层已经将数据丢给L2CAP了,仅仅能将数据丢弃处理了(数据流不能逆向)。
  10. L2CEVT_L2CA_DISCONNECT_REQ,上层想断开链接,会使用这个 Event来处理。

  11. CST_ORIG_W4_SEC_COMP 状态:Originator(我的理解是 发起 link 建立的应用)等待 security 的间隙,这个间隙须要处理的事件。跟 CST_CLOSED 差点儿相同,源代码非常easy读,这里不再次做分析。注意。假设是 L2CEVT_SEC_COMP 事件(跟安全相关),会把 p_ccb->chnl_state 置为 CST_W4_L2CAP_CONNECT_RSP

  12. CST_TERM_W4_SEC_COMP状态:Acceptor(接收者)等待 security 的间隙,源代码易读,不再详细展开分析,注意一个事件 L2CEVT_SEC_COMP 的处理,会将 p_ccb->chnl_state 置为 CST_W4_L2CA_CONNECT_RSP
  13. CST_W4_L2CAP_CONNECT_RSP: 经过 2 或 3 这个关于安全链接的步骤,当然要等待 Peer connect的回应(Response)。

    分为 获取 peer connect的 confirm、pending、rejected connection等信息。作进一步的推断。

  14. CST_CONFIG:商讨配置的过程。

  15. CST_OPEN:Channel 处于 OPEN 状态,我们能够发送上层传过来的数据。我们的音乐数据就是从这个 case 里发出去的。
  16. CST_W4_L2CAP_DISCONNECT_RSP:等待 peer(对方设备)断开链接的 Response
  17. CST_W4_L2CA_DISCONNECT_RSP:等待上层 disc rsp

分析了这么一大堆,我们的听音乐那那笔数据包,走的是 CST_OPEN 这个case。由于别的几个 case 在link 建立完毕时就走完了。

(三)

我们的音乐数据包走的是 CST_OPEN 这个 case。这个 case 调用的是 l2c_csm_open 这个函数,接下来我们继续分析这个函数。

我们的音乐数据包在函数 l2c_csm_open 中流转,经过各种选择和推断。最后走的是 L2CEVT_L2CA_DATA_WRITE 这个 case。

这个 case 调用了 l2c_enqueue_peer_data 让数据进入到当前 ccb 的 xmit_hold_q 队列中,暂存此数据包。l2c_link_check_send_pkts 这个函数发送数据包。以下我们会继续分析这两个函数。

  1 //l2c_csm_open 处理 Channel 处于 OPEN 状态下的各种 Event
  2 static void l2c_csm_open (tL2C_CCB *p_ccb, UINT16 event, void *p_data)
  3 {
  4     UINT16                  local_cid = p_ccb->local_cid;
  5     tL2CAP_CFG_INFO         *p_cfg;
  6     tL2C_CHNL_STATE         tempstate;
  7     UINT8                   tempcfgdone;
  8     UINT8                   cfg_result;
  9 
 10 #if (BT_TRACE_VERBOSE == TRUE)
 11     L2CAP_TRACE_EVENT2 ("L2CAP - LCID: 0x%04x  st: OPEN  evt: %s", p_ccb->local_cid, l2c_csm_get_event_name (event));
 12 #else
 13     L2CAP_TRACE_EVENT1 ("L2CAP - st: OPEN evt: %d", event);
 14 #endif
 15 
 16 #if (L2CAP_UCD_INCLUDED == TRUE) //默认 UCD 是关闭的
 17     if ( local_cid == L2CAP_CONNECTIONLESS_CID )
 18     {
 19         /* check if this event can be processed by UCD */
 20         if ( l2c_ucd_process_event (p_ccb, event, p_data) )
 21         {
 22             /* The event is processed by UCD state machine */
 23             return;
 24         }
 25     }
 26 #endif
 27 
 28     switch (event)
 29     {
 30     case L2CEVT_LP_DISCONNECT_IND:  //Link 都断开连接了,自然 Channel也没有存在的必要了,各种清除 CCB 的工作
 31         L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x  No Conf Needed", p_ccb->local_cid);
 32         l2cu_release_ccb (p_ccb);//释放 当前的 CCB 
 33         if (p_ccb->p_rcb)
 34             (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(local_cid, FALSE);
 35         break;
 36 
 37     case L2CEVT_LP_QOS_VIOLATION_IND:               /* QOS violation         */
 38         /* Tell upper layer. If service guaranteed, then clear the channel   */
 39         if (p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb)
 40             (*p_ccb->p_rcb->api.pL2CA_QoSViolationInd_Cb)(p_ccb->p_lcb->remote_bd_addr);
 41         break;
 42 
 43     case L2CEVT_L2CAP_CONFIG_REQ:                  /* Peer config request   */
 44         p_cfg = (tL2CAP_CFG_INFO *)p_data;
 45 
 46         tempstate = p_ccb->chnl_state;
 47         tempcfgdone = p_ccb->config_done;
 48         p_ccb->chnl_state = CST_CONFIG; //假设数据流中的数据是 L2CEVT_L2CAP_CONFIG_REQ。当然要转到 CST_CONFIG中继续处理
 49         p_ccb->config_done &= ~CFG_DONE_MASK;
 50         //启动一个 timer 。一段时间后。查看 cfg 的状态
 51         //假设配置处于 L2CAP_PEER_CFG_UNACCEPTABLE,继续尝试配置
 52         //假设配置处于断开状态。那当前 Channel 直接断开连接。
 53         btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT);
 54 
 55         if ((cfg_result = l2cu_process_peer_cfg_req (p_ccb, p_cfg)) == L2CAP_PEER_CFG_OK)
 56         {
 57             (*p_ccb->p_rcb->api.pL2CA_ConfigInd_Cb)(p_ccb->local_cid, p_cfg);
 58         }
 59 
 60         /* Error in config parameters: reset state and config flag */
 61         else if (cfg_result == L2CAP_PEER_CFG_UNACCEPTABLE)
 62         {
 63             btu_stop_timer(&p_ccb->timer_entry);
 64             p_ccb->chnl_state = tempstate;
 65             p_ccb->config_done = tempcfgdone;
 66             l2cu_send_peer_config_rsp (p_ccb, p_cfg);
 67         }
 68         else    /* L2CAP_PEER_CFG_DISCONNECT */
 69         {
 70             /* Disconnect if channels are incompatible
 71              * Note this should not occur if reconfigure
 72              * since this should have never passed original config.
 73              */
 74             l2cu_disconnect_chnl (p_ccb);
 75         }
 76         break;
 77 
 78     case L2CEVT_L2CAP_DISCONNECT_REQ:                  /* Peer disconnected request */
 79 // btla-specific ++
 80         /* Make sure we are not in sniff mode */
 81 #if BTM_PWR_MGR_INCLUDED == TRUE
 82         {
 83             tBTM_PM_PWR_MD settings;
 84             settings.mode = BTM_PM_MD_ACTIVE;
 85             BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings);
 86         }
 87 #else
 88         BTM_CancelSniffMode (p_ccb->p_lcb->remote_bd_addr);
 89 #endif
 90 // btla-specific --
 91 
 92         p_ccb->chnl_state = CST_W4_L2CA_DISCONNECT_RSP; //Peer 发送 Disconnect,我们要对此发 Response
 93         btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT);
 94         L2CAP_TRACE_API1 ("L2CAP - Calling Disconnect_Ind_Cb(), CID: 0x%04x  Conf Needed", p_ccb->local_cid);
 95         (*p_ccb->p_rcb->api.pL2CA_DisconnectInd_Cb)(p_ccb->local_cid, TRUE);
 96         break;
 97 
 98     case L2CEVT_L2CAP_DATA:                         /* Peer data packet rcvd    */
 99         //收到 Peer 传来的数据。当然要把这个数据通过回调送到上层应用去
100         //pL2CA_DataInd_Cb 中定义了回调,交给上层处理收到的数据
101         (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid, (BT_HDR *)p_data);
102         break;
103 
104     case L2CEVT_L2CA_DISCONNECT_REQ:                 /* Upper wants to disconnect */
105         /* Make sure we are not in sniff mode */
106 #if BTM_PWR_MGR_INCLUDED == TRUE
107         {
108             tBTM_PM_PWR_MD settings;
109             settings.mode = BTM_PM_MD_ACTIVE;
110             BTM_SetPowerMode (BTM_PM_SET_ONLY_ID, p_ccb->p_lcb->remote_bd_addr, &settings);
111         }
112 #else
113         BTM_CancelSniffMode (p_ccb->p_lcb->remote_bd_addr);
114 #endif
115 
116         l2cu_send_peer_disc_req (p_ccb);
117         p_ccb->chnl_state = CST_W4_L2CAP_DISCONNECT_RSP;
118         btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_DISCONNECT_TOUT);
119         break;
120 
121     case L2CEVT_L2CA_DATA_WRITE:                    /* Upper layer data to send */   // mark l2c
122         //上层将数据发送给下层
123         //我们的音乐数据就是走这个 case(为什么?看整个函数的參数就明确了)
124         //首先将数据入队。以下会展开分析这个函数
125         l2c_enqueue_peer_data (p_ccb, (BT_HDR *)p_data);
126         //终于调用 l2c_link_check_send_pkts 来发送我们的音乐数据包
127         l2c_link_check_send_pkts (p_ccb->p_lcb, NULL, NULL);
128         break;
129 
130     case L2CEVT_L2CA_CONFIG_REQ:                   /* Upper layer config req   */
131         p_ccb->chnl_state = CST_CONFIG;
132         p_ccb->config_done &= ~CFG_DONE_MASK;
133         l2cu_process_our_cfg_req (p_ccb, (tL2CAP_CFG_INFO *)p_data);
134         l2cu_send_peer_config_req (p_ccb, (tL2CAP_CFG_INFO *)p_data);
135         btu_start_timer (&p_ccb->timer_entry, BTU_TTYPE_L2CAP_CHNL, L2CAP_CHNL_CFG_TIMEOUT);
136         break;
137 
138     case L2CEVT_TIMEOUT:
139         /* Process the monitor/retransmission time-outs in flow control/retrans mode */
140         if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_ERTM_MODE)
141             l2c_fcr_proc_tout (p_ccb);
142         break;
143 
144     case L2CEVT_ACK_TIMEOUT:
145         l2c_fcr_proc_ack_tout (p_ccb);
146         break;
147     }
148 }

OK。我们下篇将分析数据包入队列的函数。

(四)

本文讲的是 Bluedroid 中数据包在 L2CAP 层入队列的一系列函数源代码分析。

l2c_enqueue_peer_data 函数的主要作用是将我们的音乐数据包入数据发送队列以及处理 FCR segmentation 和当前 Channel 是否拥堵的检測。我们来详细读一下他的源代码。其主要做了这么几件事:1. 组装好 p_buf 并入 当前 CCB 的 xmit_hold_q 队列。2. 检查当前 Channel 拥堵情况。

3. 当前 Link 支持 RR,则检查当前ACL数据包所在 Channel 的权限,假设当前 CCB 中的权限高于 RR,则把 RR 中的权限设置为跟 CCB 同样。

4. 若 Link 上没有发送窗体,则将 l2cb.check_round_robin 置为TRUE。下一次须要 RR

  1 void l2c_enqueue_peer_data (tL2C_CCB *p_ccb, BT_HDR *p_buf)
  2 {
  3     UINT8       *p;
  4 
  5     if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE)
  6     {
  7         p_buf->event = 0;
  8     }
  9     else
 10     {
 11         /* Save the channel ID for faster counting */
 12         p_buf->event = p_ccb->local_cid;
 13 
 14         /* Step back to add the L2CAP header */
 15         p_buf->offset -= L2CAP_PKT_OVERHEAD;
 16         p_buf->len    += L2CAP_PKT_OVERHEAD;
 17 
 18         /* Set the pointer to the beginning of the data */
 19         p = (UINT8 *)(p_buf + 1) + p_buf->offset;
 20 
 21         /* Now the L2CAP header */
 22         UINT16_TO_STREAM (p, p_buf->len - L2CAP_PKT_OVERHEAD);
 23         UINT16_TO_STREAM (p, p_ccb->remote_cid);
 24     }
 25 
 26     GKI_enqueue (&p_ccb->xmit_hold_q, p_buf);//真正将组装好的 p_buf 入队
 27 
 28     l2cu_check_channel_congestion (p_ccb);  //检測当前 Channel 拥堵情况,以下会继续分析这个函数
 29 
 30 #if (L2CAP_ROUND_ROBIN_CHANNEL_SERVICE == TRUE)
 31     /* if new packet is higher priority than serving ccb and it is not overrun */
 32     if (( p_ccb->p_lcb->rr_pri > p_ccb->ccb_priority ) //当前数据包所在Channel的权限
 33       &&( p_ccb->p_lcb->rr_serv[p_ccb->ccb_priority].quota > 0))
 34     {
 35         /* send out higher priority packet */
 36         p_ccb->p_lcb->rr_pri = p_ccb->ccb_priority;//当前要发送的数据的Channel。设置为ccb_priority,比原来权限要高。
 37     }
 38 #endif
 39 
 40     //假设当前 link 上的 link_xmit_quota ==0(link上的发送窗体为0)。那么有必要做一次 RR
 41     if (p_ccb->p_lcb->link_xmit_quota == 0)
 42         l2cb.check_round_robin = TRUE;
 43 }
 44 
 45 //check if any change in congestion status
 46 
 47 void l2cu_check_channel_congestion (tL2C_CCB *p_ccb)
 48 {
 49     UI