zoukankan      html  css  js  c++  java
  • ZT Android 4.2 BT系统之蓝牙关闭过程全跟踪

    Android 4.2 BT系统之蓝牙关闭过程全跟踪

    分类: android 2252人阅读 评论(10) 收藏 举报
    代码位置:
          frameworks/base/services/java/com/android/server/BluetoothManagerService.java
          这部分代码,生成libandroid_runtime.so
    完成功能,中转BluetoothAdapter和Bluetooth.apk,所有来自其他应用的请求,都通过IBluetooth接口,转发到Bluetooth.apk
         启动方式:
         Intent i = new Intent(IBluetooth.class.getName());
                    if (!mContext.bindService(i, mConnection,Context.BIND_AUTO_CREATE,
                                              UserHandle.USER_CURRENT)) {
         这里IBluetooth.class.getName实际返回android.bluetooth.IBluetooth
         这个action正好是packages/app/Bluetooth/AdapterService要处理的action。
         
         BluetoothManagerService保持一个BluetoothServiceConnection的回调,当AdapterService启动时,就可以拿到IBluetooth接口了。

    代码位置:
         packages/apps/Bluetooth
         这部分代码,生成Bluetooth.apk
    完成功能:
    1、IBluetooh.aidl的服务端,由AdapterService的内部类AdapterServiceBinder实现
    2、在BluetoothManagerService调用IBluetooth接口时,实际上是在AdapterServiceBinder端来处理。
    3、IBluetooth.enable -> AdapterServiceBinder->enable -> BluetoothManagerService.enable
         BluetoothManagerService初始化了一个蓝牙状态机AdapterState实例,mAdapterStateMachine,调用enable是给状态机发一个消息AdapterState.USER_TURN_ON
    4、再往下层的调用是通过,AdapterService的JNI接口enableNative

    代码位置:
          packages/apps/Bluetooth/jni
          这部分代码,是AdapterService的JNI实现
     完成功能:
    1、获取bluetooth.default接口,这个接口是android获取HAL的通用方式,  
        err = hw_get_module(id, (hw_module_t const**)&module);
    2、因此调用JNI enableNative实际是调用bluetooth.default的实现enable

    蓝牙关闭过程:
    1、客户端调用AdapterService.java的disable接口
    2、AdapterService给AdapterStateMachine发送一个USER_TURN_OFF的Message
    3、AdapterStateMachine调用AdapterProperties的onBluetoothDisable接口
    4、AdapterProperties把scan mode设置为AbstractionLayer.BT_SCAN_MODE_NONE
    5、AdapterProperties调用AdapterService的setAdapterPropertyNative接口,往底层调用
    6、在JNI层com_android_bluetooth_btservice_AdapterServices的 setAdapterPropertyNative,调用蓝牙HAL接口 sBluetoothInterface->set_adapter_property(&prop);
    7、蓝牙HAL接口的实现,在external/bluetooth/bluedroid/btif/src/bluetooth.c中
    8、bluetooth.c :: set_adapter_property -> btif_core.c :: btif_set_adapter_property
    9、btif_set_adapter_property对属性 BT_PROPERTY_ADAPTER_SCAN_MODE设置的处理,先保存discovery mode为BTA_DM_NON_DISC,表示不可发现,保存connecable mode为BTA_DM_NON_CONN,表示不可连接。然后,调用external/bluetooth/bluedroid/bta/dm /bta_dm_api.c :: BTA_DmSetVisibility方法
    10、BTA_DmSetVisibility构造一个tBTA_DM_API_SET_VISIBILITY    *p_msg,填入discovery mode, connetiable mode等参数,然后调用external/bluetooth/bluedroid/bta/sys/bta_sys_main.c :: bta_sys_sendmsg
    11、bta_sys_sendmsg调用external/bluetooth/bluedroid/gki /common/gki_buffer.c :: GKI_send_msg发送一条GKI信息到BTA,GKI_send_msg有三个参数,第一个参数是线程id,也作为task id, 通过bta_sys_init获得,第二个参数是mailbox id,第三个是上一步封装好的p_msg
    12、GKI_send_msg首先对p_msg进一步封装成event,通过链表存到mailbox id对应的任务队列中,调用external/bluetooth/bluedroid/gki/ulinux/gki_ulinux.c :: GKI_send_event进行发送
    13、GKI_send_event设置事件掩码gki_cb.com.OSWaitEvt[task_id] |= event;, 通过pthread_cond_signal(&gki_cb.os.thread_evt_cond[task_id]);通知另外线程。
    14、这样,在另外一个等待线程函数中gki_ulinux.c :: GKI_wait可以返回了,现在来看这个等待线程是怎么起来的

    在调用HAL接口bluetoothInterface集合中的init的时候,
    1、btif/src/bluetooth.c :: init 会调用 btif/src/btif_core.c :: btif_init_bluetooth 启动一个BTIF任务
    2、btif_init_bluetooth 调用gki/ulinux/gki_ulinux.c :: GKI_create_task(btif_task, BTIF_TASK, BTIF_TASK_STR,
                    (UINT16 *) ((UINT8 *)btif_task_stack + BTIF_TASK_STACK_SIZE),
                    sizeof(btif_task_stack)); 启动这个任务,第一个参数btif_task是任务处理函数,第二个参数是task id,第三个是对应string表示形式,第四个是任务列表的起始指针,第四个是任务栈最大深度。
    3、GKI_create_task 启动一个线程,等待任务事件     ret = pthread_create( &gki_cb.os.thread_id[task_id], //保存了线程id
                  &attr1,
                  (void *)gki_task_entry,
                  &gki_pthread_info[task_id]); //gki_pthread_info保存了任务信息:task_id,task_entry。

    可向而知,刚刚创建的任务线程,就是那个等待线程,来看gki_task_entry,即是btif_task的实现:
    btif/src/btif_core.c
    1、因为我们已经要开始等待事件了,因此要通知JAVA/JNI层,记得刚刚我们有注册了回调,那么就通过宏HAL_CBACK(bt_hal_cbacks, thread_evt_cb, ASSOCIATE_JVM);来通知JNI
    2、进入for(;;)
    3、调用GKI_wait,等待一个事件的返回
    4、判断事件 event == BT_EVT_TRIGGER_STACK_INIT,如果是,就调用BTA_EnableBluetooth(bte_dm_evt)初始化蓝牙芯片组
    5、判断事件event & EVENT_MASK(GKI_SHUTDOWN_EVT,如果是,就对出任务循环
    6、判断事件event & TASK_MBOX_1_EVT_MASK 判断是否是1好mbox里面的事件。
    7、如果第6步满足判断事件,那么判断事件 event  & BT_EVT_CONTEXT_SWITCH_EVT,如果是,调用btif_context_switched(p_msg)切换上下文
    8、回第三步

    到这里,很奇怪,为什么没有我们要处理的事件,难道在另外的任务线程里面处理?
    在回过头来,看GKI对TASK_MBOX, TASK_MBOX_EVT_MASK的规划
    TIMER事件,TASK事件,APPL事件(应用程序请求事件?)

    gki/common/gki.h
    #define TASK_MBOX_0    0
    #define TASK_MBOX_1    1
    #define TASK_MBOX_2    2
    #define TASK_MBOX_3    3
                       
    #define NUM_TASK_MBOX  4

    #define MAX_EVENTS              16
                       
    #define TASK_MBOX_0_EVT_MASK   0x0001
    #define TASK_MBOX_1_EVT_MASK   0x0002
    #define TASK_MBOX_2_EVT_MASK   0x0004
    #define TASK_MBOX_3_EVT_MASK   0x0008

    /* Definitions of task IDs for inter-task messaging */
    #ifndef BTU_TASK
    #define BTU_TASK                0
    #endif

    #ifndef BTIF_TASK
    #define BTIF_TASK               1
    #endif

    #ifndef A2DP_MEDIA_TASK
    #define A2DP_MEDIA_TASK         2
    #endif

    /* The number of GKI tasks in the software system. */
    #ifndef GKI_MAX_TASKS
    #define GKI_MAX_TASKS               3
    #endif

    在第11步bta_sys_sendmsg调用GKI_send_msg(bta_sys_cb.task_id, p_bta_sys_cfg->mbox, p_msg);
    他的TASK_ID就是bta_sys_cb.task_id,现在看下这个task_id的初始化过程,也就是BTA系统的初始化过程
    BTA的初始化,要从在BTU的任务处理函数(btu_task)中开始的,那么btu_task又是怎么起来的,
    这就要从bt开启的时候说起了。
    1、在开启蓝牙之时,JNI调用HAL接口bluetoothInterface的enable函数,即btif/src/btif_core.c :: btif_enable_bluetooth
    2、btif_enable_bluetooth调用main/bte_main.c的BTE API函数bte_main_enable(btif_local_bd_addr.address); 其从bte task,这个address怎么来的?
    3、bte_main_enable,首先,初始化BTE控制块(HCI相关回调)
    4、调用hci接口,(替代4.1的hciattach进程?),给bt设备上电。
    5、GKI_create_task((TASKPTR)btu_task, BTU_TASK, BTE_BTU_TASK_STR,
                        (UINT16 *) ((UINT8 *)bte_btu_stack + BTE_BTU_STACK_SIZE),
                        sizeof(bte_btu_stack)); //启动BTU任务
    6、pthread_create( &timer_thread_id,
                  &timer_attr,
                  timer_thread,
                  NULL); //根据NO_GKI_RUN_RETURN是否执行timer_thread线程,NO_GKI_RUN_RETURN是什么意思?LINUX是否需要定义此宏?

    再来看btu_task的实现:
    1、btu_init_core(); 初始化核心control block,比如BTU, BTM, L2CAP, and SDP
    2、BTE_InitStack();初始化BTE控制块,比如RFCOMM, DUN, SPP, HSP2, HFP, OBX, BIP
    3、#if (defined(BTU_BTA_INCLUDED) && BTU_BTA_INCLUDED == TRUE)  //初始化BTA
         bta_sys_init();
         #endif 
    4、#if ( BT_USE_TRACES==TRUE )  //此宏用于调试,是否设置
         BTE_InitTraceLevels();  
         #endif
    5、进入for(;;)
    6、处理事件的列表://为什么不是全部?
         TASK_MBOX_0_EVT_MASK
         TIMER_0_EVT_MASK
         TIMER_1_EVT_MASK
         TIMER_2_EVT_MASK
         RPCGEN_MSG_EVT
         TASK_MBOX_2_EVT_MASK
         EVENT_MASK(APPL_EVT_7) //APPL_EVT_7事件
        
    再来看bta_sys_init的实现
    bta/sys/bta_sys_main.c
    bta_sys_init //注意到这里并没有建一个bta_task
    1、bta_sys_cb.task_id = GKI_get_taskid(); //获取task id,是什么?
    这里的GKI_get_taskid()仅是调用pthread_self()获取pthread_t,那么这段代码很明显是跟btu_task是同一个pthread,因此发给bta_sys_cb.task_id自然而然的由btu_task来处理了。

    再回过头来看bta_sys_sendmsg的实现
    void bta_sys_sendmsg(void *p_msg)
    {
        GKI_send_msg(bta_sys_cb.task_id, p_bta_sys_cfg->mbox, p_msg);
    }
    这里GKI_send_msg的第一个参数已经确定,第二个参数见BTA相关参数的映射:
         
    BTA相关映射关系如下:
    /* GKI task mailbox event for BTA. */
    #ifndef BTA_MBOX_EVT
    #define BTA_MBOX_EVT                TASK_MBOX_2_EVT_MASK
    #endif

    /* GKI task mailbox for BTA. */
    #ifndef BTA_MBOX
    #define BTA_MBOX                    TASK_MBOX_2
    #endif

    /* GKI timer id used for protocol timer for BTA. */
    #ifndef BTA_TIMER
    #define BTA_TIMER                   TIMER_1
    #endif

    const tBTA_SYS_CFG bta_sys_cfg =
    {
        BTA_MBOX_EVT,               /* GKI mailbox event */
        BTA_MBOX,                   /* GKI mailbox id */
        BTA_TIMER,                  /* GKI timer id */
        APPL_INITIAL_TRACE_LEVEL    /* initial trace level */
    }; 

    tBTA_SYS_CFG *p_bta_sys_cfg = (tBTA_SYS_CFG *)&bta_sys_cfg;
              
    那么第二个参数也确定下来了,就是TASK_MBOX_2


    再回过头来看btu_task处理的时间列表,里面正包含了TASK_MBOX_2_EVT_MASK

    这样,我们终于找到处理设置BT_PROPERTY_ADAPTER_SCAN_MODE的债主了,就是btu_task
    再来看btu_task对TASK_MBOX_2_EVT_MASK的实现
            if (event & TASK_MBOX_2_EVT_MASK)
            {
                while ((p_msg = (BT_HDR *) GKI_read_mbox(TASK_MBOX_2)) != NULL)  //取出p_msg
                {
                    bta_sys_event(p_msg); //处理p_msg
                }
            }

    也就是说bta_sys_event会调用子系统回调函数去处理p_msg,看怎么实现的。bta/sys/bta_sys_main.c ::  bta_sys_event
    BTA_API void bta_sys_event(BT_HDR *p_msg)  //这里名字也正好与BTA对应上
    1、UINT8 id = (UINT8) (p_msg->event >> 8); 获取id 右移8位
    2、    if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id] != NULL))
             {
                 freebuf = (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg);
             } 
    3、再看当初event的形成
         void BTA_DmSetVisibility(tBTA_DM_DISC disc_mode, tBTA_DM_CONN conn_mode, UINT8 pairable_mode, UINT8 conn_filter )
              p_msg->hdr.event = BTA_DM_API_SET_VISIBILITY_EVT;
    4、BTA_DM_API_SET_VISIBILITY_EVT的取值定义在bta/dm/bta_dm_int.h
    enum
    {  
        /* device manager local device API events */
        BTA_DM_API_ENABLE_EVT = BTA_SYS_EVT_START(BTA_ID_DM),  //注意这里BTA_SYS_EVT_START(BTA_ID_DM)实现#define BTA_SERVICE_ID_TO_SERVICE_MASK(id)       (1 << (id));因为BTA_ID_DM是1,
        BTA_DM_API_DISABLE_EVT,
        BTA_DM_API_SET_NAME_EVT,
        BTA_DM_API_SET_VISIBILITY_EVT,
        BTA_DM_API_SET_AFH_CHANNELS_EVT,
        BTA_API_DM_SIG_STRENGTH_EVT,
        BTA_DM_API_VENDOR_SPECIFIC_COMMAND_EVT, 
        BTA_DM_API_TX_INQPWR_EVT,
        BTA_DM_ACL_CHANGE_EVT,
        BTA_DM_API_ADD_DEVICE_EVT,
         ...
    };
    5、因此计算刚才的id,也是1

    BTA_ID_DM的回调函数的注册的过程
    1、还记得btif_task刚起来的时候,会等待一个BT_EVT_TRIGGER_STACK_INIT的事件,对那个事件的处理,就是调用BTA_EnableBluetooth进行初始化硬件。
            if (event == BT_EVT_TRIGGER_STACK_INIT)
            {
                BTIF_TRACE_DEBUG0("btif_task: received trigger stack init event");
                BTA_EnableBluetooth(bte_dm_evt);
            }
    2、bta/dm/bta_dm_api.c :: tBTA_STATUS BTA_EnableBluetooth(tBTA_DM_SEC_CBACK *p_cback)
         在这里面,注册两个子系统处理回调
         bta_sys_register (BTA_ID_DM, &bta_dm_reg );
         bta_sys_register (BTA_ID_DM_SEARCH, &bta_dm_search_reg );
         那么,我们来看他的定义
         static const tBTA_SYS_REG bta_dm_reg =
         {
             bta_dm_sm_execute,
             bta_dm_sm_disable
         };
         那么,bta_dm_sm_execute正是bta_sys_event要调用的实现

    bta_dm_sm_execute的实现:
    1、UINT16  event = p_msg->event & 0x00ff; //获得event事件,BTA_DM_API_SET_VISIBILITY_EVT
    2、 (*bta_dm_action[event])( (tBTA_DM_MSG*) p_msg); 调用这个函数来处理
    3、bta_dm_action列表:
         const tBTA_DM_ACTION bta_dm_action[] =                                       
    {                                                                            
       
        /* device manager local device API events */
        bta_dm_enable,            /* 0  BTA_DM_API_ENABLE_EVT */
        bta_dm_disable,           /* 1  BTA_DM_API_DISABLE_EVT */
        bta_dm_set_dev_name,      /* 2  BTA_DM_API_SET_NAME_EVT */
        bta_dm_set_visibility,    /* 3  BTA_DM_API_SET_VISIBILITY_EVT */
        bta_dm_set_afhchannels,   /* 4  BTA_DM_API_SET_AFH_CHANNELS_EVT */       
        bta_dm_signal_strength,   /* 5  BTA_API_DM_SIG_STRENGTH_EVT */
        bta_dm_vendor_spec_command,/* 6  BTA_DM_API_VENDOR_SPECIFIC_COMMAND_EVT */
        bta_dm_tx_inqpower,       /* 7  BTA_DM_API_SIG_STRENGTH_EVT */           
        bta_dm_acl_change,        /* 8  BTA_DM_ACL_CHANGE_EVT */
        bta_dm_add_device,        /* 9  BTA_DM_API_ADD_DEVICE_EVT */             
        ...
    }
    4、也就是说调用bta_dm_set_visibility来实现。

    bta_dm_set_visibility的实现bta/dm/bta_dm_act.c
    1、BTM_SetDiscoverability((UINT8)p_data->set_visibility.disc_mode,
                                    bta_dm_cb.inquiry_scan_window,
                                    bta_dm_cb.inquiry_scan_interval); //调用这个实现设置discovery mode
              BTM_SetDeviceClass (cod);//DEV_CLASS cod封装好discory mode
                   btsnd_hcic_write_dev_class (dev_class); //stack/hcic/hcicmds.c
    2、BTM_SetConnectability((UINT8)p_data->set_visibility.conn_mode,
                                    bta_dm_cb.page_scan_window,
                                    bta_dm_cb.page_scan_interval); //调用这个实现设置can mode
              btsnd_hcic_write_scan_enable (scan_mode);  //stack/hcic/hcicmds.c
    3、btsnd_hcic_write_dev_class 和 btsnd_hcic_write_scan_enable 都会将参数封包,通过cmd方式,调用btu_hcif_send_cmd发送出去。

    btu_hcif_send_cmd的实现:
    1、首先取出命令控制块
         tHCI_CMD_CB * p_hci_cmd_cb = &(btu_cb.hci_cmd_cb[controller_id]);
    2、p_hci_cmd_cb->cmd_window = p_hci_cmd_cb->cmd_xmit_q.count + 1; //打开发送窗口
    3、while (p_hci_cmd_cb->cmd_window != 0) {
              ...
              p_buf = (BT_HDR *)GKI_dequeue (&(p_hci_cmd_cb->cmd_xmit_q)); //从链表中取出一个消息
              btu_hcif_store_cmd(controller_id, p_buf); //保存发出去的消息,用于超时机制
                   GKI_enqueue(&(p_hci_cmd_cb->cmd_cmpl_q), p_cmd); // 拷贝到链表中
                   btu_start_timer (&(p_hci_cmd_cb->cmd_cmpl_timer),
                             (UINT16)(BTU_TTYPE_BTU_CMD_CMPL + controller_id),
                             BTU_CMD_CMPL_TIMEOUT); //启动计时器,  cmd_cmpltimer与cmd_cmpl_q的关系?
                p_hci_cmd_cb->cmd_window--; //计数器减一
                if (controller_id == LOCAL_BR_EDR_CONTROLLER_ID)
                {
                    HCI_CMD_TO_LOWER(p_buf); //发送到底层,是一个宏
                }
              ...
         }
    4、HCI_CMD_TO_LOWER的宏定义
          bluedroid/include/bt_target.h:#define HCI_CMD_TO_LOWER(p)         bte_main_hci_send((BT_HDR *)(p), BT_EVT_TO_LM_HCI_CMD);
         
    来看bte_main_hci_send的实现:
    1、UINT16 sub_event = event & BT_SUB_EVT_MASK;//利用子事件掩码,BT_EVT_TO_LM_HCI_CMD & BT_SUB_EVT_MASK = 0
    2、    if((sub_event == LOCAL_BR_EDR_CONTROLLER_ID) ||
           (sub_event == LOCAL_BLE_CONTROLLER_ID))
                bt_hc_if->transmit_buf((TRANSAC)p_msg,
                                           (char *) (p_msg + 1),
                                            p_msg->len);  //调用transmit_buf接口发送出去。
         
    bt_hc_if是在HCI初始化的过程中得到的。

    HCI初始化过程
    在调用HAL接口bluetoothInterface集合中的init的时候,
    1、btif/src/bluetooth.c :: init 会调用 btif/src/btif_core.c :: btif_init_bluetooth
    2、btif_init_bluetooth 会调用main/bte_main.c ::  bte_main_boot_entry //初始化BT芯片的入口
    3、bte_main_boot_entry 调用 bte_main_in_hw_init 或许HCI接口
              static bt_hc_interface_t * bt_hc_if = (bt_hc_interface_t *) bt_hc_get_interface();
              static const bt_hc_callbacks_t bt_hc_callbacks; //回调,跟bt_hc_if调用方向刚好相反
    4、hci/src/bt_hci_bdroid.c中
         const bt_hc_interface_t *bt_hc_get_interface(void)
         {
             return &bluetoothHCLibInterface; //返回接口列表
         }
    4、在使能蓝牙调用bte_main_enable的时候,开始btu_stack之前,会按顺序初始化蓝牙设备。
         bt_hc_if->init(&hc_callbacks, local_addr);  //hc_callbacks是hci回调
         bt_hc_if->set_power(BT_HC_CHIP_PWR_ON);
         bt_hc_if->preload(NULL);
    5、bt_hc_if->init也就是调用bluetoothHCLibInterface的init方法
         
    来看bluetoothHCLibInterface的init方法 hci/src/bt_hci_bdroid.c
    1、bt_hc_callbacks_t *bt_hc_cbacks = (bt_hc_callbacks_t *) p_cb; //保存回调
    2、init_vnd_if(local_bdaddr);//调用厂商库里面的bt_vendor_interface_t *接口,初始化蓝牙设备
    3、p_hci_if = &hci_h4_func_table; //使用HCI H4接口回调
    4、p_hci_if->init(); //调用hci_h4_func_table的init方法,初始化H4模块
    5、userial_init();//初始化uart数据结构
    6、lpm_init();//初始化低功耗模块,调用bt_vendor_interface_t的op接口
    7、utils_queue_init(&tx_q); //初始化发送队列
    8、pthread_create(&hc_cb.worker_thread, &thread_attr,
                           bt_hc_worker_thread, NULL) != 0) //起工作线程

    在回过头来看transmit_buf,其实就是调用bluetoothHCLibInterface的transmit_buf方法
    看transmit_buf的实现:hci/src/bt_hci_bdroid.c
    static int transmit_buf(TRANSAC transac, char *p_buf, int len)
    {
        utils_enqueue(&tx_q, (void *) transac);  //链接到发送队列

        bthc_signal_event(HC_EVENT_TX); //广播事件

        return BT_HC_STATUS_SUCCESS;
    }

    另外一边的工作线程bt_hc_worker_thread,接收到上面的广播事件之后会判断事件类型,如果是HC_EVENT_TX,首先将目前所有的时间加入到数组sending_msg_que里面
          for(i = 0; i < sending_msg_count; i++)
                    p_hci_if->send(sending_msg_que[i]); //调用p_hci_if的send接口发送出去。

    上面已经提到p_hci_if的send接口在hci_h4_func_table中实现,hci/src/hci_h4.c
    来看其send回调的实现
    void hci_h4_send_msg(HC_BT_HDR *p_msg) {
         ...
         if (event == MSG_STACK_TO_HC_HCI_CMD)
            type = H4_TYPE_COMMAND; //设定cmd类型
         if (sub_event == LOCAL_BR_EDR_CONTROLLER_ID)
         {
            acl_data_size = h4_cb.hc_acl_data_size;
            acl_pkt_size = h4_cb.hc_acl_data_size + HCI_ACL_PREAMBLE_SIZE;  //设置数据大小
         }
         ...
         bytes_sent = userial_write(event,(uint8_t *) p, bytes_to_send);//写串口,是否会阻塞
         ...
         bt_hc_cbacks->tx_result((TRANSAC) p_msg, (char *) (p_msg + 1),
                                            BT_HC_TX_SUCCESS); //将发送结果通过回调发回去,注意这里只是成功发送到串口,有没有成功,应该是从内核往上发,由userial_read_thread来完成。

    来看读线程userial_read_thread的实现
    static void *userial_read_thread(void *arg) {
        while (userial_running) {
             //调用回调申请一段消息BUFFER
                  p_buf = (HC_BT_HDR *) bt_hc_cbacks->alloc(
                                                    BTHC_USERIAL_READ_MEM_SIZE);
            //select读取串口消息
            rx_length = select_read(userial_cb.fd, p, READ_LIMIT);
            //将消息发到rx队列,并广播出去
            utils_enqueue(&(userial_cb.rx_q), p_buf);
            bthc_signal_event(HC_EVENT_RX);
            //调用回调释放内存
            bt_hc_cbacks->dealloc((TRANSAC) p_buf, (char *) (p_buf + 1));
       }
    }

    工作线程bt_hc_worker_thread得到广播之后
            if (events & HC_EVENT_RX)
            {
                p_hci_if->rcv(); //调用rcv,也就是hci_h4_func_table的hci_h4_receive_msg回调
            }
    hci_h4_receive_msg有一个rcv_state变量,表征当前数据帧的收取状态
    typedef enum {
        H4_RX_MSGTYPE_ST,
        H4_RX_LEN_ST,
        H4_RX_DATA_ST,
        H4_RX_IGNORE_ST
    } tHCI_H4_RCV_STATE;
    通过条件p_cb->rcv_msg_type和acl_rx_frame_end_chk()两个条件,判断当前是否收完一帧数据,如果是,则调用bt_hc_cbacks的data_ind回调把数据上发:
                    bt_hc_cbacks->data_ind((TRANSAC) p_cb->p_rcv_msg,
                                           (char *) (p_cb->p_rcv_msg + 1),
                                           p_cb->p_rcv_msg->len + BT_HC_HDR_SIZE);

    bt_hc_cbacks的data_ind回调,就是main/bte_main.c hc_callbacks的data_ind函数,来看它的实现
    static int data_ind(TRANSAC transac, char *p_buf, int len)                 
    {                                                                          
        BT_HDR *p_msg = (BT_HDR *) transac;                                   
        GKI_send_msg (BTU_TASK, BTU_HCI_RCV_MBOX, transac);  //发到btu task中去了,而且mbox为TASK_MBOX_0
        //因为如下宏
        //#define BTU_HCI_RCV_MBOX        TASK_MBOX_0     /* Messages from HCI  */
        return BT_HC_STATUS_SUCCESS;                                           
    }                                                                          

    那么在btu task中:
    比较事件验码为BT_EVT_TO_BTU_HCI_EVT后,
    进而调用btu_hcif.c的btu_hcif_process_event处理来自底层的事件上报,在
    会取出event code,不同的code,调用不同的函数来处理,以HCI_PAGE_SCAN_REP_MODE_CHNG_EVT为例子,它的处理函数为btu_hcif_page_scan_rep_mode_chng_evt,这样,再一层层,
    这里应该报上两个事件btif_adapter_properties_evt,一个是discovery mode一个是connectiable mode,是否保证到JAVA的通路是畅通的?


    在写属性的过程中,往串口写是一方面,在写完串口之后,还需要更新本地数据库。(要是BT模块写拒绝怎么办?)
    在btif/src/btif_core.c :: btif_set_adapter_property 中,最后一步
        if (storage_req_id != BTIF_CORE_STORAGE_NO_ACTION)  //判断是否需要更新数据库
        {
            int btif_status;
            /* pass on to storage for updating local database */

            memset(&(req.write_req.bd_addr), 0, sizeof(bt_bdaddr_t));
            memcpy(&(req.write_req.prop), property, sizeof(bt_property_t));
            // btif_transfer_context会将一个消息到BTU_BTIF_MBO,也就是TASK_MBOX_1信箱,并且这个消息
            // 由btif_task来处理
            return btif_transfer_context(execute_storage_request, 
                                         storage_req_id,
                                         (char*)&req,
                                         sizeof(btif_storage_req_t)+property->len,
                                         btif_in_storage_request_copy_cb);  
                   btif_sendmsg(p_msg)
                        GKI_send_msg(BTIF_TASK, BTU_BTIF_MBOX, p_msg);
        }

    来看btif的处理:
    if(event & TASK_MBOX_1_EVT_MASK)  { //计算掩码,符合
         while((p_msg = GKI_read_mbox(BTU_BTIF_MBOX)) != NULL) { //取出消息
              switch (p_msg->event)
                    {
                        case BT_EVT_CONTEXT_SWITCH_EVT:  //判断消息类型,符合
                            btif_context_switched(p_msg); //处理消息
                            break;
                        ...
                   }
         }
    }

    来看btif_context_switched的实现:
    static void btif_context_switched(void *p_msg)
    {
        tBTIF_CONTEXT_SWITCH_CBACK *p = (tBTIF_CONTEXT_SWITCH_CBACK *) p_msg;
        /* each callback knows how to parse the data */
        if (p->p_cb)
            p->p_cb(p->event, p->p_param);  //取出事件处理回调函数,也就是execute_storage_request
    }

    来看execute_storage_request的实现
    static void execute_storage_request(UINT16 event, char *p_param) {
        switch(event){
            case BTIF_CORE_STORAGE_ADAPTER_WRITE: //判断事件类型,符合
            {
                btif_storage_req_t *p_req = (btif_storage_req_t*)p_param;
                bt_property_t *p_prop = &(p_req->write_req.prop);
                status = btif_storage_set_adapter_property(p_prop);   //是否会阻塞?
                HAL_CBACK(bt_hal_cbacks, adapter_properties_cb, status, 1, p_prop);  //调用JNI测的回调
              }
              ...
          }
    }

    而在JAVA一侧,通过AdapterProperties.java的回调adapterPropertyChangedCallback,可以得知刚才property设置的结果(是否从串口发上来的?):
    case: AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE:
          mService.startBluetoothDisable
    1、调用AdapterService的startBluetoothDisable方法
    2、AdapterService给AdapterStateMachine发送AdaterState.BEGIN_DISABLE的Message
    3、AdapterStateMachine调用AdapterService的disableNative接口

    adapterPropertyChangedCallback回调,通过com_android_bluetooth_btservice_AdapterService.cpp调用JAVA的方式,调用
    在JNI一层,有一组回调接口,是通过Android HAL层组织的
    bt_callbacks_t sBluetoothCallbacks = {                                       
        sizeof(sBluetoothCallbacks),                                             
        adapter_state_change_callback,                                           
        adapter_properties_callback,                                             
        remote_device_properties_callback,                                       
        device_found_callback,                                                   
        discovery_state_changed_callback,                                        
        pin_request_callback,                                                    
        ssp_request_callback,                                                    
        bond_state_changed_callback,                                             
        acl_state_changed_callback,                                              
        callback_thread_event,                                                   
    }; 
    这组回调,通过sBluetoothInterface->init(&sBluetoothCallbacks);注册。
    而sBluetoothInterface的实现,在external/bluetooth/bluedroid/btif/src
    const bt_interface_t* bluetooth__get_bluetooth_interface ()
    {
        return &bluetoothInterface;
    }
    static const bt_interface_t bluetoothInterface = {
        sizeof(bt_interface_t),
        init,
        enable,
        disable,
        cleanup,
        get_adapter_properties,
        get_adapter_property,
        set_adapter_property,
        get_remote_device_properties,
        get_remote_device_property,
        set_remote_device_property,
        get_remote_service_record,
        get_remote_services,
        start_discovery,
        cancel_discovery,
        create_bond,
        remove_bond,
        cancel_bond,
        pin_reply,
        ssp_reply,
        get_profile_interface,
        dut_mode_configure,
        dut_mode_send
    };
    在init里面,会将sBluetoothInterface保存在bluetoothInterface中,

    因此,反响调用的顺序是这样的
    1、bluetoothInterface->adapter_properties_cb也就是com_android_bluetooth_btservice_AdapterService的adapter_properties_cb
    2、adapter_properties_callback调用AdapterProperties的adapterPropertyChangedCallback方法

    现在看btif_storage_set_adapter_property的实现,研究一下事件是怎么写入NVRAM中去。
    btif/src/btif_storage.c
    bt_status_t btif_storage_set_adapter_property(bt_property_t *property)
    {                             
        return prop2cfg(NULL, property) ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
    }           
    btif/src/btif_storage.c
    static int prop2cfg(bt_bdaddr_t *remote_bd_addr, bt_property_t *prop)
    {
         switch(prop->type)
        {
            case BT_PROPERTY_ADAPTER_SCAN_MODE:
                btif_config_set_int("Local", "Adapter",
                                    BTIF_STORAGE_KEY_ADAPTER_SCANMODE, *(int*)prop->val);  
                break;
              ...
         }
         ...
    }
    btif/src/btif_config.c
    int btif_config_set_int(const char* section, const char* key, const char* name, int value)
    {  
        return btif_config_set(section, key, name, (char*)&value, sizeof(value), BTIF_CFG_TYPE_INT); //加上config类型
    }  
    btif/src/btif_config.c
    int btif_config_set(const char* section, const char* key, const char* name, const char*  value, int bytes, int type)
    {
        ...
        set_node(section, key, name, value, (short)bytes, (short)type); //找到一个cfg_node*(配置节点)
        btsock_thread_post_cmd(pth, CFG_CMD_SAVE, NULL, 0, 0); // pth是线程槽(thread slots)的序号。
        ...
    }
    config node数据结构,root节点是所有配置的起始节点。
    typedef struct cfg_node_s
    {
        const char* name;
        union
        {
            struct cfg_node_s* child;
            char* value;
        };
        short bytes;
        short type;
        short used;
        short flag;
    } cfg_node;

    我理解的线程槽,BT中有很多业务,每一类的业余需要一个(或多个)额外的线程来处理一些事情,可以通过写线程槽的socket一端,那么读端在获取消息后,可以处理这些事情。
    btif_init_bluetooth在创建btif_task之前,先会调用btif_config_init,初始config相关数据结构

    btif/src/btif_config.c
    1、stat(CFG_PATH, &st)  //确认配置文件,路径:/data/misc/bluedroid/
    2、btsock_thread_init();  //初始化thread_slogt ts数组
    3、init_slot_lock(&slot_lock);//初始化线程锁
    4、   root.name = "Bluedroid";
            alloc_node(&root, CFG_GROW_SIZE);
            dump_node("root", &root);
            pth = btsock_thread_create(NULL, cfg_cmd_callback);
         创建一个跟配置节点,并创捷一个取用一个线程槽,pth为ts数组里面对应的序号。
    5、load_cfg(); //导入配置文件,读取xml文件到内存,以config_node形式组织起来。

    先看btsock_thread_create的线程创建过程:
    int btsock_thread_create(btsock_signaled_cb callback, btsock_cmd_cb cmd_callback)
    1、int h = alloc_thread_slot(); //获取一个空闲槽,卧槽,这是什么节奏
    2、init_poll(h);  //初始化这个槽点,创建一个socketpair,读写端分别是ts[h].cmd_fdr和ts[h].cmd_fdw,并且将cmd_fdr放入监听槽...
    3、ts[h].thread_id = create_thread(sock_poll_thread, (void*)h); //给这个槽点创建线程,线程函数为sock_poll_thread
    4、ts[h].callback = callback;
         ts[h].cmd_callback = cmd_callback; //sock_poll_thread将会调用它来处理写config

    那么先来看写端的处理,也就是btsock_thread_post_cmd是怎么做的
    btif/src/btif_sock_thread.c
    int btsock_thread_post_cmd(int h, int type, const unsigned char* data, int size, uint32_t user_id)
         sock_cmd_t cmd = {CMD_USER_PRIVATE, 0, type, size, user_id};
         //将cmd包装到sock_cmd_t* cmd_send中
         send(ts[h].cmd_fdw, cmd_send, size_send, 0)  //直接往cmd_fdw写

    再看来cmd_fdr,也就是sock_poll_thread的处理
    btif/src/btif_sock_thread.c
    {
        for(;;)
        {
            prepare_poll_fds(h, pfds);
            int ret = poll(pfds, ts[h].poll_count, -1); //开始监听
            if(ret != 0)
            {
                int need_process_data_fd = TRUE;
                if(pfds[0].revents) //cmd fd always is the first one
                {
                    asrt(pfds[0].fd == ts[h].cmd_fdr);
                    if(!process_cmd_sock(h))  //先尝试处理cmd命令,即上面提到的CMD_USER_PRIVATE
                    {
                        APPL_TRACE_DEBUG1("h:%d, process_cmd_sock return false, exit...", h);
                        break;
                    }
                    if(ret == 1)
                        need_process_data_fd = FALSE;
                    else ret--; //exclude the cmd fd
                }
                if(need_process_data_fd)
                    process_data_sock(h, pfds, ret); //再处理数据。
            }
            else {APPL_TRACE_DEBUG1("no data, select ret: %d", ret)};
        }
        ts[h].thread_id = -1;
    }

    看process_cmd_sock的实现
    btif/src/btif_sock_thread.c
    static int process_cmd_sock(int h)
    {
        sock_cmd_t cmd = {-1, 0, 0, 0, 0};
        int fd = ts[h].cmd_fdr;
        if(recv(fd, &cmd, sizeof(cmd), MSG_WAITALL) != sizeof(cmd))
        {
            APPL_TRACE_ERROR1("recv cmd errno:%d", errno);
            return FALSE;
        }
        switch(cmd.id)
        {
            case CMD_USER_PRIVATE:
                asrt(ts[h].cmd_callback);
                if(ts[h].cmd_callback)
                    ts[h].cmd_callback(fd, cmd.type, cmd.flags, cmd.user_id);  //调用回调处理,即上文的cfg_cmd_callback
                break;
            ...
        }
        return TRUE;
    }

    再来看cfg_cmd_callback的实现:
    static void cfg_cmd_callback(int cmd_fd, int type, int size, uint32_t user_id)
    {
      //BTIF_TRACE_DEBUG2("cmd type:%d, size:%d", type, size);
        switch(type)
        {
            case CFG_CMD_SAVE:
                lock_slot(&slot_lock);
                save_cfg();    //保存所有的config到配置文件中,从根config node:root开始遍历
                unlock_slot(&slot_lock);
                break;
        }
    }

    到目前为止蓝牙的关闭过程,才进行一般不到。
    来看AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE被JNI调用到AdapterProperties.java的adapterPropertyChangedCallback之后怎么处理的
    AdapterProperties.java
    adapterPropertyChangedCallback
                        case AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE:
                            int mode = Utils.byteArrayToInt(val, 0);
                            mScanMode = mService.convertScanModeFromHal(mode);
                            intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
                            intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mScanMode);
                            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
                            mService.sendBroadcast(intent, mService.BLUETOOTH_PERM); 
                            debugLog("Scan Mode:" + mScanMode);
                            if (mBluetoothDisabling) {
                                mBluetoothDisabling=false;
                                mService.startBluetoothDisable();  //调用服务的startBluetoothDisable停掉蓝牙设备
                            }
                            break;
    AdapterService——com_android_bluetooth_btservice_AdapterService.cpp——bluetooth.c——btif_core.c
    最终调用到btif_core.c的btif_disable_bluetooth,看btif_disable_bluetooth的实现
    bt_status_t btif_disable_bluetooth(void)
    {
         btif_dm_on_disable();//终止配对
         btif_core_state = BTIF_CORE_STATE_DISABLING; //将状态设置为BTIF_CORE_STATE_DISABLING
         btif_sock_cleanup();  //清理rfcomm & l2cap 
         btif_pan_cleanup(); 
         BTA_DisableBluetooth()//关闭蓝牙
         btif_config_flush();//保存配置
    }
    先看btif_dm_on_disable的实现
    btif/src/btif_sock.c
    void btif_dm_on_disable()
    {
        if (pairing_cb.state == BT_BOND_STATE_BONDING)
        {
            bt_bdaddr_t bd_addr;
            bdcpy(bd_addr.address, pairing_cb.bd_addr);
            btif_dm_cancel_bond(&bd_addr);  如果正在配对,则取消配对
        }
    }
    btif_sock_cleanup和btif_pan_cleanup略过,
    来看BTA_DisableBluetooth的实现:
    tBTA_STATUS BTA_DisableBluetooth(void)
    {
       
        BT_HDR    *p_msg;
       
        if ((p_msg = (BT_HDR *) GKI_getbuf(sizeof(BT_HDR))) != NULL)  //太暴力了,直接从BTA中拿出一条消息,
        {
            p_msg->event = BTA_DM_API_DISABLE_EVT; //然后改写事件。
            bta_sys_sendmsg(p_msg);
        }
        else
        {  
            return BTA_FAILURE;
        }  
       
        return BTA_SUCCESS;
    }   

    这个消息按照之前介绍过的,最终会被btu_task处理。来看处理过程
            if (event & TASK_MBOX_2_EVT_MASK)
            {
                while ((p_msg = (BT_HDR *) GKI_read_mbox(TASK_MBOX_2)) != NULL)
                {
                    bta_sys_event(p_msg);
                }
            }
    上面依然讲过,bta_sys_event的消息处理回调是bta_dm_reg 的bta_dm_sm_execute,
    BOOLEAN bta_dm_sm_execute(BT_HDR *p_msg)
    {
        UINT16  event = p_msg->event & 0x00ff;

        APPL_TRACE_EVENT1("bta_dm_sm_execute event:0x%x", event);

        /* execute action functions */
        if(event < BTA_DM_NUM_ACTIONS)
        {
            (*bta_dm_action[event])( (tBTA_DM_MSG*) p_msg); //BTA_DM_API_DISABLE_EVT对应的处理函数是bta_dm_disable
        }

        return TRUE;
    }

    来看bta_dm_disable的实现
    void bta_dm_disable (tBTA_DM_MSG *p_data)
    {  
        /* Set l2cap idle timeout to 0 (so BTE immediately disconnects ACL link after last channel is closed) */
        L2CA_SetIdleTimeoutByBdAddr((UINT8 *)BT_BD_ANY, 0);    //设置L2CAP通道超时时间
       
        /* disable all active subsystems */
        bta_sys_disable(BTA_SYS_HW_BLUETOOTH);  //停掉BTA DM 子系统
       
        BTM_SetDiscoverability(BTM_NON_DISCOVERABLE, 0, 0); 
        BTM_SetConnectability(BTM_NON_CONNECTABLE, 0, 0); //这两者上文解析过了

        bta_dm_disable_pm();  
       
        bta_dm_cb.disabling = TRUE;
       
        bta_dm_search_cancel(NULL);  //停止搜寻设备
       
        if(BTM_GetNumAclLinks()==0)
        {
    #if (defined(BTA_DISABLE_DELAY) && BTA_DISABLE_DELAY > 0)
            /* If BTA_DISABLE_DELAY is defined and greater than zero, then delay the shutdown by
             * BTA_DISABLE_DELAY milliseconds
             */
            APPL_TRACE_WARNING2("%s BTA_DISABLE_DELAY set to %d ms",
                                __FUNCTION__, BTA_DISABLE_DELAY);
            bta_sys_stop_timer(&bta_dm_cb.disable_timer);
            bta_dm_cb.disable_timer.p_cback = (TIMER_CBACK*)&bta_dm_disable_conn_down_timer_cback;
            bta_sys_start_timer(&bta_dm_cb.disable_timer, 0, BTA_DISABLE_DELAY);
    #else
            bta_dm_disable_conn_down_timer_cback(NULL);
    #endif
        }
        else
        {
            bta_dm_cb.disable_timer.p_cback = (TIMER_CBACK*)&bta_dm_disable_timer_cback;
            bta_sys_start_timer(&bta_dm_cb.disable_timer, 0, 5000);   //终止所有定时器的操作。
        }
    }

    先看 bta_sys_disable(BTA_SYS_HW_BLUETOOTH)的实现,它调用子系统回调的 bta_sys_cb.reg[bta_id]->disable方法,也就是上文的bta_dm_reg的其中的 bta_dm_sm_disable方法
    static const tBTA_SYS_REG bta_dm_reg =
    {
        bta_dm_sm_execute,
        bta_dm_sm_disable
    };
    来看它的实现:
    void bta_dm_sm_disable( )
    {
        bta_sys_deregister( BTA_ID_DM );
    }   
    void bta_sys_deregister(UINT8 id)
    {
        bta_sys_cb.is_reg[id] = FALSE;//只是简单的将is_reg赋值为FALSE
    }   

    再来看bta_dm_search_cancel的实现
    void bta_dm_search_cancel (tBTA_DM_MSG *p_data)
    {

        tBTA_DM_MSG * p_msg;

        if(BTM_IsInquiryActive())  
        {
            BTM_CancelInquiry();   //取消搜寻
            bta_dm_search_cancel_notify(NULL);

            if ((p_msg = (tBTA_DM_MSG *) GKI_getbuf(sizeof(tBTA_DM_MSG))) != NULL)
            {
                p_msg->hdr.event = BTA_DM_SEARCH_CMPL_EVT;
                p_msg->hdr.layer_specific = BTA_DM_API_DISCOVER_EVT;
                bta_sys_sendmsg(p_msg);  //给btu_task发消息

            }
        }
        /* If no Service Search going on then issue cancel remote name in case it is active */
        else if (!bta_dm_search_cb.name_discover_done)
        {
            BTM_CancelRemoteDeviceName();
        }
    #if ((BLE_INCLUDED == TRUE) && (defined BTA_GATT_INCLUDED) && (BTA_GATT_INCLUDED == TRUE))
        if (bta_dm_search_cb.gatt_disc_active)
        {
            bta_dm_cancel_gatt_discovery(bta_dm_search_cb.peer_bdaddr);
        }
    #endif
    }

    上面几步掠过,因为都是一些写过的消息线程间转发,到此为止停止蓝牙设备的工作已经接近尾声了
    最后一步,是收尾工作,也就是btif_config_flush,来看它的实现:
    void btif_config_flush()
    {
        lock_slot(&slot_lock);
        if(cached_change > 0)
            save_cfg();  //保存配置到文件
        unlock_slot(&slot_lock);
    }

    static int save_cfg()
    {
        const char* file_name = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT;
        const char* file_name_new = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT_NEW;
        const char* file_name_old = CFG_PATH CFG_FILE_NAME CFG_FILE_EXT_OLD;
        int ret = FALSE;
        if(access(file_name_old,  F_OK) == 0)
            unlink(file_name_old);  //删除缓存文件
        if(access(file_name_new, F_OK) == 0)
            unlink(file_name_new);
       if(btif_config_save_file(file_name_new))  //保存最新配置到file_name_new,上文讲过了,就是根节点遍历config node数据结构,然后写入xml文件中。
        {
            cached_change = 0;
            chown(file_name_new, -1, AID_NET_BT_STACK);
            chmod(file_name_new, 0660);
            rename(file_name, file_name_old);
            rename(file_name_new, file_name);   //更新配置到file_name
            ret = TRUE;
        }
        else BTIF_TRACE_ERROR0("btif_config_save_file failed");
        return ret;
    }







    更多 1
     
    查看评论
    9楼 andger032 2013-11-12 09:52发表 [回复]
    上面:“4、调用hci接口,(替代4.1的hciattach进程?),给bt设备上电。”
    我觉得应该不是调用hci接口,bt_hc_if->set_power这类的应该是调用蓝牙芯片厂商自己定义的接口。
    Re: woyaoxiazaibiefanwo 2013-12-28 17:03发表 [回复]
    回复andger032:9楼理解的是对的bt_hc_if->set_power会进入芯片厂商给的一个.so的库文件,有的人能够拿到,能够跟到。
    8楼 xyp5299 2013-10-12 16:28发表 [回复]
    重新又看了一次,写的太好了。请问还会有蓝牙的继续更新吗?

    谢谢。
    7楼 xyp5299 2013-09-26 21:57发表 [回复]
    还会继续 更新 吗?
    6楼 gordon1986 2013-09-17 09:38发表 [回复]
    希望继续更新,谢谢
    5楼 Limit87 2013-09-13 16:18发表 [回复]
    楼主分析的很透彻,但是你能继续分析btstack到驱动的过程吗,中间还有一段路
    4楼 xyp5299 2013-09-08 23:16发表 [回复]
    写的精辟,需要多读几次才能完整的看明白。

    希望继续分享。
    谢谢。
    3楼 xyp5299 2013-09-01 15:01发表 [回复]
    非常精彩,请问还有后续文章吗?
    谢谢。
    2楼 xyp5299 2013-08-17 12:57发表 [回复]
    非常不错,请继续分享,谢谢。
    1楼 dearpenguin 2013-08-06 16:55发表 [回复]
    博主,请教下bluedroid的transport layer 只能用H4吗?如果要用BCSP的话应该从何下手呢?多谢分享
  • 相关阅读:
    python笔记2-python常见数据类型(一)
    python笔记1-环境安装和基本语法
    selenium自动化脚本错误总结
    Postman-Post请求示例
    用PHP删除ftp下载导致的文件空行
    JS实现鼠标悬浮,显示内容
    asp.net中处理程序调用HttpContext.Current.Session获取值出错
    自动化创建tornado项目
    fabric运维
    Python3虚拟环境安装:virtualenv、virtualenvwralpper
  • 原文地址:https://www.cnblogs.com/jeanschen/p/3550735.html
Copyright © 2011-2022 走看看