2015年1月26日 星期一

Android藍芽協議棧BlueDroid分析(3)-a2dp初始化(使能) (轉)

歡迎轉載opendevkit文章, 更好排版和效果, 請關注文章原始地址: http://www.opendevkit.com/?e=113


a2dp作為分析的實例很合適,包括l2cap連接的使用, sdp註冊等,完整的使用了 btif, bta等模組!

0. 背景


藍芽有個特點是,不同種類profile可以在同一時刻在兩個設備間存在連結!所以分析一個profile的過程是可以的,不必考慮會影響其他profile!

android上藍芽操作,搜索和連接是分開的,也就是初始化和連接只需要拿到對應的設備即可。

基本框架:

bt interface (有自己的狀態機,傳遞一個回調給) -> bta (有狀態機,每個狀態都有跳轉表) , bta的請求都是以事件的方式發給btu_task,再根據模組調用回調,回調裡再跳轉到對應的處理函數) -> bta向下直接調用stack的接口 -> bta處理完成後,會調用bt interface的接口傳遞狀態!

1. a2dp profile初始化


(1) A2dpStateMachine類構造的時候,調用了initNative

(2) com_android_bluetooth_a2dp.cpp的initNative獲取了a2dp bt interface

  158     if ( (sBluetoothA2dpInterface = (btav_interface_t *)
  159           btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID))

接着調用了   sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks))
初始化a2dp bt interface!

(3) btif_av.c的init操作

  814 static const btav_interface_t bt_av_interface = {
  815     sizeof(btav_interface_t),
  816     init,                 
  692 bt_status_t btif_av_init(void)
  693 {
  694     if (btif_av_cb.sm_handle == NULL)
  695     {
  696         if (btif_a2dp_start_media_task() != GKI_SUCCESS) // 創建media task
  697             return BT_STATUS_FAIL;       
  698
  699         btif_enable_service(BTA_A2DP_SERVICE_ID); // 使能a2dp service
  700
  701         /* Initialize the AVRC CB */ 
  702         btif_rc_init();
  703                                                                                                                                        
  704         /* Also initialize the AV state machine */
  705         btif_av_cb.sm_handle = btif_sm_init((const btif_sm_handler_t*)btif_av_state_handlers, BTIF_AV_STATE_IDLE); // 創建a2dp bt interface狀態機
  706  
  707         btif_a2dp_on_init();
  708
  709         return BT_STATUS_SUCCESS;                                                                                                      
  710     }                 
  711
  712     return BT_STATUS_DONE;
  713 }

[1] btif_enable_service(BTA_A2DP_SERVICE_ID); 使能服務

btif_enable_service -> btif_transfer_context -> btif_dm_execute_service_request -> btif_in_execute_service_request -> btif_av_execute_service
btif_av_execute_service分別調用:BTA_AvEnable和BTA_AvRegister完成使能和服務註冊。

[2] 創建a2dp bt interface狀態機

btif_av_cb.sm_handle = btif_sm_init((const btif_sm_handler_t*)btif_av_state_handlers, BTIF_AV_STATE_IDLE); // 創建a2dp bt interface狀態機


創建一個狀態機賦值狀態回調和初始化狀態,以後就可以調用類似btif_sm_dispatch(btif_av_cb.sm_handle, event, (void*)p_param);這個接口來事件驅動狀態機了!

Android藍芽協議棧BlueDroid分析(1)-幾個獨立任務 (轉)

歡迎轉載opendevkit文章, 更好排版和效果, 請關注文章原始地址:http://www.opendevkit.com/?e=110

通過分析獨立任務,我們能更清楚的弄明白,協議棧部分到底有多少獨立執行線。


任務個數和作用


grep GKI_create_task發現,

./main/bte_main.c:204:    GKI_create_task((TASKPTR)btu_task, BTU_TASK, BTE_BTU_TASK_STR,

主要任務,處理上層請求,處理下層事件,承上啟下!

./btif/src/btif_core.c:463:    status = GKI_create_task(btif_task, BTIF_TASK, BTIF_TASK_STR,

bt interface任務。

./btif/src/btif_media_task.c:699:    retval =GKI_create_task((TASKPTR)btif_media_task, A2DP_MEDIA_TASK,

媒體任務,處理多媒體事件。

實際上, GKI_create_task也是封裝的pthread_create

grep pthread_create發現,

./gki/ulinux/gki_ulinux.c:806:    if (pthread_create( &timer_thread_id,

gki層提供的timer線程,為各種timer提供支持。

./hci/src/btsnoop.c:589:    if (pthread_create(&thread_id, NULL,

嗅探任務,負責hci的數據包嗅探。

./hci/src/bt_hci_bdroid.c:161:    if (pthread_create(&hc_cb.worker_thread, &thread_attr, \

對hci uart寫隊列處理的任務,包括hci的cmd,sco的數據包。

./hci/src/userial.c:363:    if (pthread_create(&(userial_cb.read_thread), &thread_attr, \

讀hci uart,讀後分發!

./btif/src/btif_sock_thread.c:170:    if( pthread_create(&thread_id, &thread_attr, start_routine, arg)!=0 )

為app層提供操作rfcomm的簡便方法,也就是使用socket通信!android的藍芽操作主要是玩rfcomm目前!

./btif/src/btif_hl.c:5171:    if ( pthread_create(&thread_id, &thread_attr, start_routine, arg)!=0 )

hl profile自己的任務,需要才打開!

./btif/co/bta_hh_co.c:138:    if ( pthread_create(&thread_id, &thread_attr, start_routine, arg)!=0 )

hh profile自己的任務,需要才打開!

./udrv/ulinux/uipc.c:573:    if (pthread_create(&uipc_main.tid, (const pthread_attr_t *) NULL, (void*)uipc_read_task, 
NULL) < 0)


處理a2dp_audio_hw中out_write接口發過來的數據,也就是sco數據處理!

Android藍芽協議棧BlueDroid分析(0)-初始化和enable (轉)

歡迎轉載opendevkit文章, 更好排版和效果, 請關注文章原始地址: http://www.opendevkit.com/?e=111

瞭解初始化,方便從頭捋順整個流程,做到心有框架。

1. java層初始化,引起jni和協議棧部分初始化


(1) AdapterService.java的static塊會在AdapterService整個類初始化的時候調用,static塊裡調用的是classInitNative。


(2) com_android_bluetooth_btservice_AdapterService.cpp的classInitNative接口關鍵部分是:


[1] 拿到java層類接口的回調,以便將來有通知好回調

......
  474     method_discoveryStateChangeCallback = env->GetMethodID(jniCallbackClass,
  475                                                            "discoveryStateChangeCallback", "(I)V");
  476
  477     method_devicePropertyChangedCallback = env->GetMethodID(jniCallbackClass,
  478                                                             "devicePropertyChangedCallback",
  479                                                             "([B[I[[B)V");
  480     method_deviceFoundCallback = env->GetMethodID(jniCallbackClass, "deviceFoundCallback", "([B)V");
  481     method_pinRequestCallback = env->GetMethodID(jniCallbackClass, "pinRequestCallback",
  482                                                  "([B[BI)V");
  483     method_sspRequestCallback = env->GetMethodID(jniCallbackClass, "sspRequestCallback",
  484                                                  "([B[BIII)V");
......

[2] 加載stack庫,後去stack接口

  494     const char *id = (strcmp(value, "1")? BT_STACK_MODULE_ID : BT_STACK_TEST_MODULE_ID);                                               
  495
  496     err = hw_get_module(id, (hw_module_t const**)&module);
  497
  498     if (err == 0) {
  499         hw_device_t* abstraction;
  500         err = module->methods->open(module, id, &abstraction);
  501         if (err == 0) {
  502             bluetooth_module_t* btStack = (bluetooth_module_t *)abstraction;
  503             sBluetoothInterface = btStack->get_bluetooth_interface();

(3) AdapterService這個服務初始化的時候,onCreate被調用,onCreate調用了initNative,initNative也是com_android_bluetooth_btservice_AdapterService.cpp的接口:


[1] 根據(2)裡拿到的interface,初始化interface,傳遞了jni層給interface層的回調,也就是interface層有通知會回調這些接口:
  517     if (sBluetoothInterface) {
  518         int ret = sBluetoothInterface->init(&sBluetoothCallbacks);
  519         if (ret != BT_STATUS_SUCCESS) {
  520             ALOGE("Error while setting the callbacks \n");                                                                             
  521             sBluetoothInterface = NULL;
  522             return JNI_FALSE;
  523         }
[2] 通過bt interface獲取socket interface,這個socket interface是上層使用rfcom通信的關鍵對象。
  524         if ( (sBluetoothSocketInterface = (btsock_interface_t *)
  525                   sBluetoothInterface->get_profile_interface(BT_PROFILE_SOCKETS_ID)) == NULL) {
  526                 ALOGE("Error getting socket interface");
  527         }

(4)  (3).[1]調用了,bt interface的init接口,正是bluetooth.c文件中,bluetoothInterface結構的init成員:


  346 static const bt_interface_t bluetoothInterface = {
  347     sizeof(bt_interface_t),
  348     init,                                                                                                                              
  349     enable,
  350     disable,
  351     cleanup,
這個接口定義在bluetooth.c中,分別:
[1] 保存jni層傳遞下來的回調
bt_hal_cbacks = callbacks;
[2] 調用btif_init_bluetooth();初始化bt interface層;簡單初始化bte, 這個簡單初始化bte實際上有個關鍵的部分:獲取hc接口,下面enable的時候要用到; 獲取本地藍芽地址;創建了一個task,用於bt interface層。
  455     btif_config_init();
   456     bte_main_boot_entry();會調用bt_hc_if = (bt_hc_interface_t *) bt_hc_get_interface()
   457
   458     /* As part of the init, fetch the local BD ADDR */
   459     memset(&btif_local_bd_addr, 0, sizeof(bt_bdaddr_t));
   460     btif_fetch_local_bdaddr(&btif_local_bd_addr);
   461
   462     /* start btif task */
   463     status = GKI_create_task(btif_task, BTIF_TASK, BTIF_TASK_STR,
   464                 (UINT16 *) ((UINT8 *)btif_task_stack + BTIF_TASK_STACK_SIZE),
   465                 sizeof(btif_task_stack));
經過以上步驟,java層帶動jni層初始化了協議棧bt interface接口層,創建了一個任務btif_task。java層有jni層的interface,jni層有java層的回調。jni層有協議棧bt interface,協議棧有jni層的回調。

2. bt使能引起的初始化


(1) bt使能調用過程


bt使能由java層發起,自然要發生在1中的java層和jni層初始化之後。
路線是:BluetoothAdapter.getDefaultAdapter().enable (BluetoothAdapter.java)
-> enable (AdapterService.java)
-> mAdapterStateMachine.obtainMessage(AdapterState.USER_TURN_ON); (AdapterService.java) 
-> mAdapterService.processStart(); (AdapterState.java)
-> mAdapterStateMachine.sendMessage(mAdapterStateMachine.obtainMessage(AdapterState.STARTED)); (AdapterService.java)
-> boolean ret = mAdapterService.enableNative(); (AdapterState.java)
-> enableNative (AdapterService.java)
-> enableNative (android_bluetooth_btservice_AdapterService.cpp)
-> bluetoothInterface->enable (bluetooth.c)
  346 static const bt_interface_t bluetoothInterface = {
  347     sizeof(bt_interface_t),
  348     init,
  349     enable,
bt interface的enable接口,被java經過jni調用下來。

(2)  bluetoothInterface的enable接口


調用btif_enable_bluetooth -> bte_main_enable比較關鍵:
  166     /* Initialize BTE control block */
  167     BTE_Init();
  168
  169     lpm_enabled = FALSE;
  170
  171     if (bt_hc_if)
  172     {
  173         int result = bt_hc_if->init(&hc_callbacks, local_addr); 初始化hc interface,也會同時初始化libbt-vendor.so,也就是hci是libbt-vendor.so相關的。
  174         APPL_TRACE_EVENT1("libbt-hci init returns %d", result);
  175
  176         assert(result == BT_HC_STATUS_SUCCESS);
  177
  178         if (hci_logging_enabled == TRUE)
  179             bt_hc_if->logging(BT_HC_LOGGING_ON, hci_logfile);
  180
  181 #if (defined (BT_CLEAN_TURN_ON_DISABLED) && BT_CLEAN_TURN_ON_DISABLED == TRUE)
  182         APPL_TRACE_DEBUG1("%s  Not Turninig Off the BT before Turninig ON", __FUNCTION__);
  183                                                                                                                                        
  184         /* Do not power off the chip before powering on  if BT_CLEAN_TURN_ON_DISABLED flag
  185          is defined and set to TRUE to avoid below mentioned issue.
  186
  187          Wingray kernel driver maintains a combined  counter to keep track of
  188          BT-Wifi state. Invoking  set_power(BT_HC_CHIP_PWR_OFF) when the BT is already
  189          in OFF state causes this counter to be incorrectly decremented and results in undesired
  190          behavior of the chip.
  191
  192          This is only a workaround and when the issue is fixed in the kernel this work around
  193          should be removed. */
  194 #else
  195         /* toggle chip power to ensure we will reset chip in case
  196            a previous stack shutdown wasn't completed gracefully */
  197         bt_hc_if->set_power(BT_HC_CHIP_PWR_OFF);
  198 #endif
  199         bt_hc_if->set_power(BT_HC_CHIP_PWR_ON);此處比較關鍵,chip已經上電了!
  200
  201         bt_hc_if->preload(NULL);
  202     }
  203
  204     GKI_create_task((TASKPTR)btu_task, BTU_TASK, BTE_BTU_TASK_STR,
  205                     (UINT16 *) ((UINT8 *)bte_btu_stack + BTE_BTU_STACK_SIZE),
  206                     sizeof(bte_btu_stack));
此處又創建了一個任務,btu_task,負責處理bte btu的事件等任務。

(3) btu_task任務初始化部分


  170 #if (defined(HCISU_H4_INCLUDED) && HCISU_H4_INCLUDED == TRUE)
  171     /* wait an event that HCISU is ready */
  172     GKI_wait(0xFFFF, 0);
  173 #endif
  174     /* Initialize the mandatory core stack control blocks
  175        (BTU, BTM, L2CAP, and SDP)
  176      */
  177     btu_init_core();   核心協議棧初始化  -> btm_init,l2c_init,sdp_init
  178
  179     /* Initialize any optional stack components */
  180     BTE_InitStack();    擴展協議初始化RFCOMM_Init,SPP_Init
  181
  182 #if (defined(BTU_BTA_INCLUDED) && BTU_BTA_INCLUDED == TRUE)
  183     bta_sys_init();     bta sys初始化這裡比較關鍵的是bta_sys_cb.task_id = GKI_get_taskid(),我們會看到bta sys這裡保存的是btu_task這個任務的id,也就是將來bta_sys_sendmsg發送的都是發到btu_task裡的!
  184 #endif
  185
  186     /* Initialise platform trace levels at this point as BTE_InitStack() and bta_sys_init()
  187      * reset the control blocks and preset the trace level with XXX_INITIAL_TRACE_LEVEL
  188      */
  189 #if ( BT_USE_TRACES==TRUE )
  190     BTE_InitTraceLevels();
  191 #endif
  192
  193     /* Send a startup evt message to BTIF_TASK to kickstart the init procedure */
  194     GKI_send_event(BTIF_TASK, BT_EVT_TRIGGER_STACK_INIT);給bt interface task發事件BT_EVT_TRIGGER_STACK_INIT,告訴bt interface task協議棧已初始化
  195
  196     raise_priority_a2dp(TASK_HIGH_BTU);

(4) 真正的協議棧使能操作'


bt interface task也就是btif_task處理BT_EVT_TRIGGER_STACK_INIT事件:
btif_task -> BTA_EnableBluetooth接口, 這個接口主要工作是:
    84         p_msg->hdr.event = BTA_DM_API_ENABLE_EVT;
    85         p_msg->p_sec_cback = p_cback;
    86         bta_sys_sendmsg(p_msg);
向btu_task發送BTA_DM_API_ENABLE_EVT事件!
btu_task用下面的代碼:
  517         if (event & TASK_MBOX_2_EVT_MASK)
  518         {
  519             while ((p_msg = (BT_HDR *) GKI_read_mbox(TASK_MBOX_2)) != NULL)
  520             {
  521                 bta_sys_event(p_msg);                                                                                                  
  522             }
  523         }
調用bta_sys_event來處理,bta_sys_event中:
 490 BTA_API void bta_sys_event(BT_HDR *p_msg)
  491 {
  492     UINT8       id;   
  493     BOOLEAN     freebuf = TRUE;  
  494
  495     APPL_TRACE_EVENT1("BTA got event 0x%x", p_msg->event);
  496
  497     /* get subsystem id from event */
  498     id = (UINT8) (p_msg->event >> 8);
  499
  500     /* verify id and call subsystem event handler */
  501     if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id] != NULL))
  502     {
  503         freebuf = (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg);

實際就是,先取得事件對應的子系統id,之後回調對應子系統的回調,而子系統是用類似:

bta_sys_register( BTA_ID_SYS,  &bta_sys_hw_reg);這樣的接口註冊的!

#define BT_EVT_TRIGGER_STACK_INIT   EVENT_MASK(APPL_EVT_0)
#define APPL_EVT_0          8
#define EVENT_MASK(evt)       ((UINT16)(0x0001 << (evt)))
BT_EVT_TRIGGER_STACK_INIT事件對應的子系統就是1,也就是#define BTA_ID_DM           1

搜索BTA_ID_DM是在BTA_EnableBluetooth接口中,bta_sys_register (BTA_ID_DM, &bta_dm_reg );註冊的。也就是:

bta_dm_reg -> bta_dm_sm_execute -> bta_dm_action[0] -> bta_dm_enable:
bta_sys_hw_register( BTA_SYS_HW_BLUETOOTH, bta_dm_sys_hw_cback ); 註冊回調
sys_enable_event->hdr.event = BTA_SYS_API_ENABLE_EVT;發送給btu_task任務事件,經過上邊分析,我們知道這個事件會被註冊的bta_sys_register( BTA_ID_SYS,  &bta_sys_hw_reg);的bta_sys_hw_reg->bta_sys_sm_execute處理,
bta_sys_sm_execute -> bta_sys_action -> bta_sys_hw_api_enable -> bta_sys_hw_co_enable -> bta_sys_hw_ci_enabled( module );這個接口又發送 p_msg->hdr.event = BTA_SYS_EVT_ENABLED_EVT;事件。
bta_sys_sm_execute -> bta_sys_action -> bta_sys_hw_evt_enabled -> BTM_DeviceReset( NULL ); -> btm_dev_reset (); -> btsnd_hcic_reset (LOCAL_BR_EDR_CONTROLLER_ID); -> btu_hcif_send_cmd (local_controller_id,  p); -> HCI_CMD_TO_LOWER(p_buf); -> bte_main_hci_send -> bt_hc_if->transmit_buf(( -> 被bt_hc_worker_thread線程調用p_hci_if->send(sending_msg_que[i]);調用hci_h4_send_msg -> bytes_sent = userial_write(event,(uint8_t *) p,bytes_to_send); 最終寫到了uart上了!

事情實際上還沒有完,chip雖然reset了,但是上邊還是需要處理reset的狀態!


不過初始化部分就算結束了!

Android藍芽協議棧BlueDroid分析(2)-stack結構介紹 (轉)

歡迎轉載opendevkit文章, 更好排版和效果, 請關注文章原始地址: http://www.opendevkit.com/?e=112

瞭解源碼結構,方便快速閲讀學習!

external/bluetooth/bluedroid目錄

  ---------------- audio_a2dp_hw/ 
  a2dp的audio hal,使用藍芽輸出音頻時候,調用這個hal的out_write接口,這裡的輸出並不直接寫串口,而是傳給了stack裡的 task來做的,因為編碼pcm到sbc。跟它通信的是後邊的udrv的內容!
  ---------------- bta/
相對於stack的概念,在stack上抽出一層application用的接口,取名bta,其實並不直接給app調用的,app層調用的是btif,bta是使用stack的接口。也就是被btif調用,當然邏輯並不是唯一確定的,也可能越過bta調用下層接口。主要手段是發送消息,處理事件!
ag/
Android.mk
ar/
av/
dm/
fs/
gatt/
hh/
hl/
include/
jv/
pan/
pb/
sys/  --- 這個是bta控制的和行,其他都是具體實現!
 ---------------- btif/
與android的Bluetooth apk的jni層通信的接口,真正的為app提供interface的接口。
co/ - callout,bt interface 調用 bta,bta調用co的內容,實際就是承接stack的接口。
include/
src/ - 各種profile的interface,給jni使用,並且調用jni回調
  ---------------- conf/
bt配置文件!
  ---------------- embdrv/
不清楚emb取什麼意思,實際的代碼作用是進行sbc編碼和處理,可能embdrv的意思是針對嵌入式環境的編碼驅動?
---------------- gki/
general kernel interface?不知道什麼意思,不過看代碼是針對os的移植層,包括多任務和timer實現,實際就是為stack代碼提供一個抽象的多任務和時間控制環境,達到可移植的目的!
---------------- hci/
host control interface,實現hci的功能!
---------------- include/
頭文件!
---------------- main/
main意味着入口,包括對各個模組的整合,最終目標的定義,各個模組的初始化等!
---------------- stack/
協議棧代碼,
a2dp/
avct/
avdt/                                                                                                                                        
avrc/
bnep/
btm/
btu/
gatt/
hcic/
hid/
include/
l2cap/
mcap/
pan/
rfcomm/
sdp/
smp/
各種profile!
---------------- test/
測試程序目錄,實際上是調用bt interface的接口,實現回調,測試測試!
---------------- tools/
忽略!
---------------- udrv/
userspace driver?實際的代碼作用是跟a2dp端進行socket通信,處理命令和a2dp數據pcm流!media task調用這裡的接口!實際就是跟audio_a2dp_hw 的audio hal通信!

---------------- utils/
just utils!

2015年1月21日 星期三

Bluetooth L2CAP介绍 (轉) (規格詳細說明)

邏輯鏈路控制和適配協議(Logical Link Control and Adaptation Protocol),是藍芽系統中的核心協議 
相應的規範位於Core Version 4.1的vol 3:Part A
111 
L2CAP負責適配基帶中的上層協議。它同LM並行工作,向上層協議提供面向連接和無連接的數據服務,並提供多路復用,分段和重組操作 

允許高層次的協議和應用能夠以64KB的長度發送和接收數據包(L2CAP Serveice Data Units, SDU)。
L2CAP提供了邏輯通道,名為L2CAP Channels,即在一個或多個邏輯鏈路上進行多路復用。
L2CAP可分為兩個部件 
~1 Channel Manager 
~2 Resource Manager
l2cap
總的來說,L2CAP提供了如下功能 
~1 協議/通道多路復用 
~2 分段和重組 
~3 服務質量
tip: 
L2CAP只支持ACL,而不支持SCO/eSCO(用預留寬頻進行實時語音傳輸)   
L2CAP不支持可靠的廣播通道

1. 通用操作

1. L2CAP Channel

L2CAP基於通道的概念,通道的每一個端點被稱為通道標識符(CID) 
不同設備間CID可復用,但本地設備CID不可復用
以下是CID ACL-U和AMP-U鏈路的name space(LE-U未列出)
CIDDescriptionLogical Link Supported
0x0000Null identifier 
0x0001L2CAP Signalling ChannelACL-U
0x0002Connectionless ChannelACL-U
0x0003AMP Manager ProtocolACL-U
0x0004~0x003EReservedACL-U
0x003FAMP Test ManagerACL-U
0x0040~0xFFFFDynamically allocatedACL-U, AMP-U

2. 設備間操作

2323
上圖說明了CID在不同設備對等L2CAP實體間通信中的使用方式。
面向連接的數據通道提供了兩設備間的連接,綁定邏輯鏈路的CID則用於標識通道的每一端。
對於無連接的數據通道,當用於廣播傳輸時限制了傳輸的方向;當用於單播傳輸時則沒有限制(?)
部分通道都保留用做特殊目的,具體如下圖 
如0x0001表示Signalling Channel,用於創建和建立面向連接的數據通道,並可對這些通道的特性變化進行協商(ACL-U) 
1111 

3. 層間操作

121212

4. 操作模式

L2CAP Channels可運行在以下模式之一(~1是默認模式) 
~1 基本L2CAP模式(Basic L2CAP Mode) 
~2 流量控制模式(Flow Control Mode) 
~3 重傳模式(Retransmission Mode) 
~4 加強版重傳模式(Enhanced Retransmission Mode) 
~5 流模式(Streaming Mode) 
~6 LE Credit Based Flow Control Mode

2. 數據包格式(Data Packet Format)

Data Packet Format
L2CAP有以下幾種連接類型: 
~1 Connection-oriented Channels in Basic L2CAP mode 
~2 Connectionless Data Channel in Basic L2CAP mode 
~3 Connection-oriented Channel in Retransmission/Flow Control/Streaming Mode
~4 Connection-oriented Channels in LE Credit Based Flow Control Mode
對於不同的連接類型,數據包格式是不同的 
且Information payload是基於Little Endian byte order

1. B-Frame

bf
Length: 2 bytes,Information payload的位元組數(0~65535) 
Channel ID: 2 bytes, 對端目的通道 
Information payload: 0~65535 bytes

2. G-Frame

gf
Length: 2 bytes,Information payload和PSM的位元組數(0~65535) 
Channel ID: 2 bytes, 對於無連接傳輸使用固定值0x0002 
PSM: >= 2 bytes, Protocol/Servece Multiplexer(具體指參考Channel Identifiers
psm 
Information payload: 0~65535 bytes

3. S-Frame/I-Frame

I-Frame用於在L2CAP實體間進行信息傳輸 
S-Frame則用於確認I-Frame和I-Frame的重傳請求
slf
Length: 2 bytes,除Basic L2CAP外的總位元組數  
Channel ID: 2 bytes, 對端目的通道 
L2CAP SDU Length: 2 bytes, 只出現在Start I-Frame(SAR=0x01)中,表示總的SDU長度 
FCS: 2 bytes, Frame Check Sequence
Control Field有三種模式 
~1 Standard Control Field: 用於Retransmission mode and Flow Control mode 
~2 Enhanced Control Field: 用於Enhanced Retransmission mode and Streaming mode 
~3 Extended Control Field: 用於Enhanced Retransmission mode and Streaming mode
這三種Control Mode格式如下
111  
222 
333
SAR: (2bits)Segmentation and Reassembly,指明該L2CAP是否是分段過,定義如下 
a1 
TxSeq: (6/14bits)Send Sequence Number,對發送的I-Frame計數,用於分段和重組。 
ReqSeq: (6/14bits)Receive Sequence Number,接收方用於應答I-Frame和請求重傳 
R: (1bits)Retransmission Disable Bit,用來實現Flow Control. 
S: (2bits)Supervisory function,表示S-Frame的type,定義如下 
b1
P: (1bits)Poll, 置1表示從接收方徵求相應 
F: (1bits)Final,相應P置1的S-Frame

4. LE-Frame

lef
欄位含義與上類似

3. 信號包格式(Signaling Packet Format)

信號包格式(Signaling Packet Format)
這裡介紹的是在對端設備上兩個L2CAP實體間傳遞的信號命令(Signaling Commands) 
這些信號命令通過Signaling Channel來傳輸 
對於ACL-U邏輯鏈路應該使用CID 0x0001, 而對於LE-U則應該使用CID 0x0005
通用的信號包格式如下
image
Field類似B-Frame,不詳述 
值得一說的是payload長度 
image
另需要注意: 
一個C-Frame通過0x0001通道可以傳遞多個命令 
而一個C-Frame通過0x0005通道則只能傳遞一個命令
image
上圖顯示了信號命令的通用格式 
Code: 1 byte, 指定Command的類別 
Identifier: 1 byte, 用於標識一個Request和Response匹配對 
Length: 2 byte, data欄位的長度 
Data: 0~N byte, Code欄位來決定其格式
下圖顯示了規範所定義的Code類型,至於data的格式請參考規範vol 3-Part A-4
Command_list

4. 參數配置選項(Configuration Parameter Options)

(原文晦澀難懂,哥不甚理解)
image
Type: 1 byte, 定義需要被配置的參數,若不能識別則由最高位決定其行為  
         0表示必須識別該選項,若無法識別則拒絶配置請求  
         1表示可以跳過該選項 
Length: 1 byte, 選項數據的位元組數,若選項數據為空則為0 
Option Data: 由Type決定其內容(不詳述,見規範vol 3-Part A-5)
Type欄位具體含義 
1) Maximum Transmission Unit(MTU),           Type=0x01 
2) Flush Timeout Option,                            Type=0x02 
3) Quality of Service(Qos) Option,               Type=0x03 
4) Retransmission and Flow Control Option,   Type=0x04 
5) Frame Check Sequence(FCS) Option,        Type=0x05 
6) Extended Flow Specification Option,          Type=0x06 
7) Extended Window Size Option,                 Type=0x07

5. 狀態機(State Machine)

這裡指的是面向連接通道(Connection-oriented Channel)狀態機,適用於雙向CID 
介紹了狀態(state), 引起狀態變化的事件(event)及事件相對應的動作(action)

1. 狀態機

如下圖所示,發起請求的一方是客戶機,服務器接收請求,應用層的客戶既可以發起也可以接收請求 
image 
命令規則為: 
兩層之間的界面上(垂直方向)用下層的縮寫名作首碼,為上層提供服務,如L2CA 
兩個同層實體之間的接口(水平方向)則使用協議縮寫作為首碼,如L2CAP。 
來自上層的事件稱作請求Request(Req), 相應的答覆稱為確認Confirm(Cfm) 
來自低層的事件稱為指示Indication(Ind), 相應的答覆稱為相應Response(Rsp)

2. 事件

在L2CAP層中,只有超時事件是由本層產生
事件分為5類: 
來自下層的指示(Indication)和確認(Confirm) 
來自上層的請求(Request)和相應(Response) 
來自對等層的數據 
來自對等層的請求和相應 
超時事件

3. 動作

動作可分為5類: 
對上層的確認(Confirm)和指示(Indication) 
對下層的請求(Request)和相應(Response) 
發給對等層實體的數據傳輸 
發給對等層的請求和相應 
計時器設置

4. 通道操作狀態

1) CLOSED 
2) WAIT_CONNECT 
3) WAIT_CONNECT_RSP 
4) CONFIG 
5) OPEN 
6) WAIT_DISCONNECT 
7) WAIT_CREATE 
8) WAIT_CREATE_RSP 
9) WAIT_MOVE 
10) WAIT_MOVE_RSP 
11) WAIT_MOVE_CONFIRM_RSP 
12) WAIT_CONFIRM_RSP
更多內容見最下方參考網址

bluedroid源碼分析之ACL包發送和接收(五) (轉)

原 http://stackvoid.com/ACL-Send-Recv-Data_5/

ACL 包接收流程

有關 ACL 包接收的過程都是在 btu_task 這個守護線程中處理的。
我們看到 btu_task 處理數據包的過程:
  1. 等待事件,事件到來後,如果是 TASK_MBOX_0_EVT_MASK(是不是 MBOX裡的Task),那麼從 mbox 中取出這個數據包,並判斷是什麼類型的 Event。
  2. 如果是 BT_EVT_TO_BTU_HCI_ACL,說明是 ACL 數據,交給 l2cap 來處理。
  3. 如果是 BT_EVT_TO_BTU_L2C_SEG_XMIT,說明是 L2CAP 的分包數據沒有發送完,那繼續發送分包數據。
 1 //部分 btu_task 源碼
 2 ...........
 3    /* Wait for, and process, events */
 4     for (;;)
 5     {
 6         event = GKI_wait (0xFFFF, 0);
 7 
 8         if (event & TASK_MBOX_0_EVT_MASK)
 9         {
10             /* Process all messages in the queue */
11             while ((p_msg = (BT_HDR *) GKI_read_mbox (BTU_HCI_RCV_MBOX)) != NULL)
12             {
13                 /* Determine the input message type. */
14                 switch (p_msg->event & BT_EVT_MASK)
15                 {
16                     case BT_EVT_TO_BTU_HCI_ACL:
17                         /* All Acl Data goes to L2CAP */
18                         l2c_rcv_acl_data (p_msg);//我們的 ACL 數據來了,關鍵分析這個函數
19                         break;
20 
21                     case BT_EVT_TO_BTU_L2C_SEG_XMIT:
22                         /* L2CAP segment transmit complete */
23                         l2c_link_segments_xmitted (p_msg);
24                         break;
25 
26                     case BT_EVT_TO_BTU_HCI_SCO:
27 #if BTM_SCO_INCLUDED == TRUE
28                         btm_route_sco_data (p_msg);
29                         break;
30 #endif
31 
32                     case BT_EVT_TO_BTU_HCI_EVT:
33                         btu_hcif_process_event ((UINT8)(p_msg->event & BT_SUB_EVT_MASK), p_msg);
34                         GKI_freebuf(p_msg);
35 
36 ....
l2c_rcv_acl_data 這個函數,處理收到的 ACL 包,下面我們來分析一下 l2c_rcv_acl_data 這個函數:
  1. 在收到的 ACL 包中找出 pkt_type(分包的話要另作處理) 和 handle。
  2. 若此 ACL 包是一個完整的數據包:
    • 首先通過 handle 找到 LCB
    • rcv_cid 大於 L2CAP_BASE_APPL_CID(0x0040),說明是上層應用普通數據包,通過 CID 找到當前包的 CCB。
    • hci_len 長度肯定要大於 L2CAP 頭長度,否則肯定頭部出錯了。
    • 如果 rcv_cid 是 L2CAP_SIGNALLING_CID,說明數據包是 創建和建立 Channel 用的(上層應用傳輸數據),使用函數 process_l2cap_cmd 來處理。
    • 如果 rcv_cid 是 L2CAP_CONNECTIONLESS_CID 說明是 廣播或單播,使用函數 tcs_proc_bcst_msg 處理。
    • 如果 rcv_cid 是 L2CAP_BLE_SIGNALLING_CID 說明是 BLE 的signalling包,交給函數 l2cble_process_sig_cmd 處理。
    • 普通數據包,直接交給 L2CAP 的數據流狀態機 l2c_csm_execute (p_ccb,L2CEVT_L2CAP_DATA, p_msg) 來處理,找到 L2CEVT_L2CAP_DATA 的這個 case,原來通過回調,將此數據包交給上層了(如 RFCOMM,最終是交給 RFCOMM_BufDataInd函數作進一步處理)
上述數據包,我們僅僅考慮了最普通的數據流,其他的控制數據包有時間在分析。
  1 void l2c_rcv_acl_data (BT_HDR *p_msg)
  2 {
  3     UINT8       *p = (UINT8 *)(p_msg + 1) + p_msg->offset;
  4     UINT16      handle, hci_len;
  5     UINT8       pkt_type;
  6     tL2C_LCB    *p_lcb;
  7     tL2C_CCB    *p_ccb = NULL;
  8     UINT16      l2cap_len, rcv_cid, psm;
  9 
 10     /* Extract the handle */
 11     STREAM_TO_UINT16 (handle, p);
 12     pkt_type = HCID_GET_EVENT (handle);
 13     handle   = HCID_GET_HANDLE (handle);
 14 
 15     /* Since the HCI Transport is putting segmented packets back together, we */
 16     /* should never get a valid packet with the type set to "continuation"    */
 17     if (pkt_type != L2CAP_PKT_CONTINUE)//數據包一定要是完整的,分包另作處理
 18     {
 19         /* Find the LCB based on the handle */
 20         if ((p_lcb = l2cu_find_lcb_by_handle (handle)) == NULL)
 21         {
 22             UINT8       cmd_code;
 23 
 24             /* There is a slight possibility (specifically with USB) that we get an */
 25             /* L2CAP connection request before we get the HCI connection complete.  */
 26             /* So for these types of messages, hold them for up to 2 seconds.       */
 27             STREAM_TO_UINT16 (hci_len, p);
 28             STREAM_TO_UINT16 (l2cap_len, p);
 29             STREAM_TO_UINT16 (rcv_cid, p);
 30             STREAM_TO_UINT8  (cmd_code, p);
 31 
 32             if ((p_msg->layer_specific == 0) && (rcv_cid == L2CAP_SIGNALLING_CID)
 33                 && (cmd_code == L2CAP_CMD_INFO_REQ || cmd_code == L2CAP_CMD_CONN_REQ))
 34             {
 35                 L2CAP_TRACE_WARNING5 ("L2CAP - holding ACL for unknown handle:%d ls:%d cid:%d opcode:%d cur count:%d",
 36                                     handle, p_msg->layer_specific, rcv_cid, cmd_code,
 37                                     l2cb.rcv_hold_q.count);
 38                 p_msg->layer_specific = 2;
 39                 GKI_enqueue (&l2cb.rcv_hold_q, p_msg);//添加到隊列中,等待 connect 數據包到達,有種可能發生的 case
 40 
 41                 if (l2cb.rcv_hold_q.count == 1)
 42                     btu_start_timer (&l2cb.rcv_hold_tle, BTU_TTYPE_L2CAP_HOLD, BT_1SEC_TIMEOUT);
 43 
 44                 return;
 45             }
 46             else
 47             {
 48                 L2CAP_TRACE_ERROR5 ("L2CAP - rcvd ACL for unknown handle:%d ls:%d cid:%d opcode:%d cur count:%d",
 49                                     handle, p_msg->layer_specific, rcv_cid, cmd_code, l2cb.rcv_hold_q.count);
 50             }
 51             GKI_freebuf (p_msg);
 52             return;
 53         }
 54     }
 55     else
 56     {
 57         L2CAP_TRACE_WARNING1 ("L2CAP - expected pkt start or complete, got: %d", pkt_type);
 58         GKI_freebuf (p_msg);
 59         return;
 60     }
 61     //下面是我們把 ACL 數據包給部分拆包了,即除掉 L2CAP 的控制部分,還原上層的數據包。
 62     /* Extract the length and update the buffer header */
 63     STREAM_TO_UINT16 (hci_len, p);
 64     p_msg->offset += 4;
 65 
 66 #if (L2CAP_HOST_FLOW_CTRL == TRUE)
 67     /* Send ack if we hit the threshold */
 68     if (++p_lcb->link_pkts_unacked >= p_lcb->link_ack_thresh)
 69         btu_hcif_send_host_rdy_for_data();
 70 #endif
 71 
 72     /* Extract the length and CID */
 73     STREAM_TO_UINT16 (l2cap_len, p);
 74     STREAM_TO_UINT16 (rcv_cid, p);
 75 
 76     /* Find the CCB for this CID */
 77     if (rcv_cid >= L2CAP_BASE_APPL_CID)// 說明此 rcv_cid 是上層應用普通數據流的 CID 
 78     {
 79         if ((p_ccb = l2cu_find_ccb_by_cid (p_lcb, rcv_cid)) == NULL)
 80         {
 81             L2CAP_TRACE_WARNING1 ("L2CAP - unknown CID: 0x%04x", rcv_cid);
 82             GKI_freebuf (p_msg);
 83             return;
 84         }
 85     }
 86 
 87     if (hci_len >= L2CAP_PKT_OVERHEAD)  //數據包長度肯定要大於 Head的值,否則必然是個錯包
 88     {
 89         p_msg->len    = hci_len - L2CAP_PKT_OVERHEAD;
 90         p_msg->offset += L2CAP_PKT_OVERHEAD;
 91     }
 92     else
 93     {
 94         L2CAP_TRACE_WARNING0 ("L2CAP - got incorrect hci header" );
 95         GKI_freebuf (p_msg);
 96         return;
 97     }
 98 
 99     if (l2cap_len != p_msg->len) //長度不相等,那數據包傳送過程肯定出現了差錯,丟包吧
100     {
101         L2CAP_TRACE_WARNING2 ("L2CAP - bad length in pkt. Exp: %d  Act: %d",
102                               l2cap_len, p_msg->len);
103 
104         GKI_freebuf (p_msg);
105         return;
106     }
107 
108     /* Send the data through the channel state machine */
109     if (rcv_cid == L2CAP_SIGNALLING_CID)//控制創建和建立 Channel的 signalling
110     {
111         process_l2cap_cmd (p_lcb, p, l2cap_len); //此函數專門處理這個Channel的事件
112         GKI_freebuf (p_msg);
113     }
114     else if (rcv_cid == L2CAP_CONNECTIONLESS_CID)
115     {
116         /* process_connectionless_data (p_lcb); */
117         STREAM_TO_UINT16 (psm, p);
118         L2CAP_TRACE_DEBUG1( "GOT CONNECTIONLESS DATA PSM:%d", psm ) ;
119 #if (TCS_BCST_SETUP_INCLUDED == TRUE && TCS_INCLUDED == TRUE)
120         if (psm == TCS_PSM_INTERCOM || psm == TCS_PSM_CORDLESS)
121         {
122             p_msg->offset += L2CAP_BCST_OVERHEAD;
123             p_msg->len -= L2CAP_BCST_OVERHEAD;
124             tcs_proc_bcst_msg( p_lcb->remote_bd_addr, p_msg ) ;
125             GKI_freebuf (p_msg);
126         }
127         else
128 #endif
129 
130 #if (L2CAP_UCD_INCLUDED == TRUE)
131         /* if it is not broadcast, check UCD registration */
132         if ( l2c_ucd_check_rx_pkts( p_lcb, p_msg ) )
133         {
134             /* nothing to do */
135         }
136         else
137 #endif
138             GKI_freebuf (p_msg);
139     }
140 #if (BLE_INCLUDED == TRUE)
141     else if (rcv_cid == L2CAP_BLE_SIGNALLING_CID) //LE 設備的專用 Channel
142     {
143         l2cble_process_sig_cmd (p_lcb, p, l2cap_len);
144         GKI_freebuf (p_msg);
145     }
146 #endif
147 #if (L2CAP_NUM_FIXED_CHNLS > 0)
148     else if ((rcv_cid >= L2CAP_FIRST_FIXED_CHNL) && (rcv_cid <= L2CAP_LAST_FIXED_CHNL) &&
149              (l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb != NULL) )
150     {
151         /* If no CCB for this channel, allocate one */
152         if (l2cu_initialize_fixed_ccb (p_lcb, rcv_cid, &l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].fixed_chnl_opts))
153         {
154             p_ccb = p_lcb->p_fixed_ccbs[rcv_cid - L2CAP_FIRST_FIXED_CHNL];
155 
156             if (p_ccb->peer_cfg.fcr.mode != L2CAP_FCR_BASIC_MODE)
157                 l2c_fcr_proc_pdu (p_ccb, p_msg);
158             else
159                 (*l2cb.fixed_reg[rcv_cid - L2CAP_FIRST_FIXED_CHNL].pL2CA_FixedData_Cb)(p_lcb->remote_bd_addr, p_msg);
160         }
161         else
162             GKI_freebuf (p_msg);
163     }
164 #endif
165 
166     else
167     {
168         if (p_ccb == NULL)
169             GKI_freebuf (p_msg);
170         else
171         {
172             /* Basic mode packets go straight to the state machine */
173             if (p_ccb->peer_cfg.fcr.mode == L2CAP_FCR_BASIC_MODE)
174             //普通的數據流都是經過這條通路的,下面這個函數覺得很熟悉吧,因為在發送數據包的時候也調用了他。
175                 l2c_csm_execute (p_ccb, L2CEVT_L2CAP_DATA, p_msg);
176             else
177             {
178                 /* eRTM or streaming mode, so we need to validate states first */
179                 if ((p_ccb->chnl_state == CST_OPEN) || (p_ccb->chnl_state == CST_CONFIG))
180                     l2c_fcr_proc_pdu (p_ccb, p_msg);
181                 else
182                     GKI_freebuf (p_msg);
183             }
184         }
185     }
186 }
下面我們分析一個 Event--number-of-completed-packets,是由函數 l2c_link_process_num_completed_pkts 處理。 這個 event 將會更新我們 LCB 上的數據包數量。
我們追蹤這個函數的調用鏈:
btu_task的一個 BT_EVT_TO_BTU_HCI_EVT case --> btu_hcif_process_event 的一個 HCI_NUM_COMPL_DATA_PKTS_EVT case --> l2c_link_process_num_completed_pkts
通過這個調用鏈,我們知道處理事件或數據流都在 btu_task 中進行。
l2c_link_process_num_completed_pkts這個函數都做了些什麼呢?我們來深入代碼瞭解一下整個過程。
  1. 通過 num_sent 來更新數據 controller_xmit_window,sent_not_acked。
  2. 既然又多了 credits(Snoop中的),那麼自然調用 l2c_link_check_send_pkts 去找更多的包發送下去。
  3. l2c_link_process_num_completed_pkts 和 l2c_link_check_send_pkts 形成了一個遞歸式的調用,l2c_link_check_send_pkts 會產生 process num complete 這個event,l2c_link_process_num_completed_pkts找到 Link後在讓 l2c_link_check_send_pkts 發包。
  1 void l2c_link_process_num_completed_pkts (UINT8 *p)
  2 {
  3     UINT8       num_handles, xx;
  4     UINT16      handle;
  5     UINT16      num_sent; //已經發下去的數據,這個數據要更新controller_xmit_window
  6     tL2C_LCB    *p_lcb;
  7 
  8     L2CAP_TRACE_DEBUG0("mike: l2c_link_process_num_completed_pkts");
  9     STREAM_TO_UINT8 (num_handles, p);
 10     L2CAP_TRACE_DEBUG1("mike: l2c_link_process_num_completed_pkts--number_handles:%d", num_handles);
 11     for (xx = 0; xx < num_handles; xx++)//handle 對應着每條邏輯鏈路(Link)
 12     {
 13         STREAM_TO_UINT16 (handle, p);
 14         STREAM_TO_UINT16 (num_sent, p);
 15 
 16         p_lcb = l2cu_find_lcb_by_handle (handle);
 17 
 18         /* Callback for number of completed packet event    */
 19         /* Originally designed for [3DSG]                   */
 20         if((p_lcb != NULL) && (p_lcb->p_nocp_cb))
 21         {
 22             L2CAP_TRACE_DEBUG0 ("L2CAP - calling NoCP callback");
 23             (*p_lcb->p_nocp_cb)(p_lcb->remote_bd_addr);
 24         }
 25 
 26         if (p_lcb)
 27         {
 28 #if (BLE_INCLUDED == TRUE)
 29             if (p_lcb->is_ble_link)
 30             {
 31                 l2cb.controller_le_xmit_window += num_sent;
 32             }
 33             else
 34 #endif
 35             {
 36                 /* Maintain the total window to the controller */
 37                 l2cb.controller_xmit_window += num_sent;
 38             }
 39             /* If doing round-robin, adjust communal counts */
 40             if (p_lcb->link_xmit_quota == 0)
 41             {
 42                 /* Don't go negative */
 43                 if (l2cb.round_robin_unacked > num_sent)
 44                     l2cb.round_robin_unacked -= num_sent;
 45                 else
 46                     l2cb.round_robin_unacked = 0;
 47             }
 48 
 49             /* Don't go negative */
 50             if (p_lcb->sent_not_acked > num_sent)
 51                 p_lcb->sent_not_acked -= num_sent; //更新
 52             else
 53                 p_lcb->sent_not_acked = 0;
 54 
 55             l2c_link_check_send_pkts (p_lcb, NULL, NULL);
 56 
 57             L2CAP_TRACE_DEBUG1("mike:l2c_link_process_num_completed_pkts--l2cb.controller_xmit_window=%d",l2cb.controller_xmit_window);
 58 
 59             /* If we were doing round-robin for low priority links, check 'em */
 60             if ( (p_lcb->acl_priority == L2CAP_PRIORITY_HIGH)
 61               && (l2cb.check_round_robin)
 62               && (l2cb.round_robin_unacked < l2cb.round_robin_quota) )
 63             {
 64               l2c_link_check_send_pkts (NULL, NULL, NULL);
 65             }
 66         }
 67 
 68 #if (L2CAP_HCI_FLOW_CONTROL_DEBUG == TRUE)
 69         if (p_lcb)
 70         {
 71 #if (BLE_INCLUDED == TRUE)
 72             if (p_lcb->is_ble_link)
 73             {
 74                 L2CAP_TRACE_DEBUG5 ("TotalWin=%d,LinkUnack(0x%x)=%d,RRCheck=%d,RRUnack=%d",
 75                     l2cb.controller_le_xmit_window,
 76                     p_lcb->handle, p_lcb->sent_not_acked,
 77                     l2cb.check_round_robin, l2cb.round_robin_unacked);
 78             }
 79             else
 80 #endif
 81             {
 82                 L2CAP_TRACE_DEBUG5 ("TotalWin=%d,LinkUnack(0x%x)=%d,RRCheck=%d,RRUnack=%d",
 83                     l2cb.controller_xmit_window,
 84                     p_lcb->handle, p_lcb->sent_not_acked,
 85                     l2cb.check_round_robin, l2cb.round_robin_unacked);
 86 
 87             }
 88         }
 89         else
 90         {
 91 #if (BLE_INCLUDED == TRUE)
 92             L2CAP_TRACE_DEBUG5 ("TotalWin=%d  LE_Win: %d, Handle=0x%x, RRCheck=%d, RRUnack=%d",
 93                 l2cb.controller_xmit_window,
 94                 l2cb.controller_le_xmit_window,
 95                 handle,
 96                 l2cb.check_round_robin, l2cb.round_robin_unacked);
 97 #else
 98             L2CAP_TRACE_DEBUG4 ("TotalWin=%d  Handle=0x%x  RRCheck=%d  RRUnack=%d",
 99                 l2cb.controller_xmit_window,
100                 handle,
101                 l2cb.check_round_robin, l2cb.round_robin_unacked);
102 #endif
103         }
104 #endif
105     }
106 
107 #if (defined(HCILP_INCLUDED) && HCILP_INCLUDED == TRUE)
108     /* only full stack can enable sleep mode */
109     btu_check_bt_sleep ();
110 #endif
111 }
到此為止,我們已經把普通 ACL 包發送和接收部分源碼分析完了。後續有時間會繼續分析控制類型的ACL包,以及ACL鏈路建立的流程。