本文从hci, l2cap,到 btm 和 bta,最后到 btif,分析了连接过程发生的状态变化。需要指出的是,本文对连接的具体细节并没有过多描述,仅仅起到了穿针引线的导读的作用,目的是对协议栈的层次结构和代码机制有一个基础的了解。具体连接的细节,还需要结合spec和代码,针对问题来做更进一步的深入分析。
协议栈接收和处理HCI Event
手机来连接设备BLE,设备蓝牙controller向host发了一个HCI LE Connection Complete 的 event,对端地址为:53:F9:xx:xx:3F:1C,是 Resolvable 的随机地址。
HCI的数据,是在哪里执行的呢?引用[1] 中的一张图,可以看到HCI Event是在btu_task执行的。
btu承载了BTA和HCI,btm负责蓝牙配对和链路管理,它们都属于核心stack逻辑。
那btu_task又是如何处理HCI事件的呢?
我们看下btu_task的启动流程。
1 | void BTU_StartUp(void) { |
创建线程后,在btu_task_start_up()
中继续enable:
1 | void btu_task_start_up(UNUSED_ATTR void* context) { |
btu_task_start_up
中主要是:
- 初始化必须的核心协议栈控制块,BTU,BTM,L2CAP,和 SDP
- 初始化一些各类的协议栈组件,如RFCOMM,A2DP等
- bta初始化(bta是蓝牙应用层,是相对于stack来说的,它承载了各profile的逻辑实现和处理)
- 最主要的是,创建了一个btu的message looper
1 | void bte_main_boot_entry(void) { |
中可以看到,bte_main_boot_entry中,给hci注册了data_cb,收到数据就post到btu_task进行处理。
所以hci数据的处理函数为btu_hci_msg_process()
。
1 | void btu_hci_msg_process(BT_HDR* p_msg) { |
btu_hci_msg_process()
中判断消息类型,是l2cap,l2cap发送,sco,hci event,还是 hci command。
最终再根据 event code 判断对各类事件进行处理:
1 | void btu_hcif_process_event(UNUSED_ATTR uint8_t controller_id, BT_HDR* p_msg) { |
关于HCI的LE Meta event的定义,参考Core spec 5.3的Vol 4, Part E,7.7.65小节
可以看到,HCI_BLE_CONN_COMPLETE_EVENT这个event的处理函数是:btu_ble_ll_conn_complete_evt()
前面提到,BTU是承接BTA和HCI的,而蓝牙连接和配对是由BTM负责的。
所以,BTU直接调用了BTM的方法进行处理。
1 | static void btu_ble_ll_conn_complete_evt(uint8_t* p, uint16_t evt_len) { |
小结:
通过本小节,我们简单了bluedroid的btu,bta,btm和hci的关系。我们知道,hci命令和事件,是在btu_task进行处理的。btu_task承载bta和hci,不做核心的控制逻辑,而将链接和配对的主要工作交给btm来完成。
BLE地址类型介绍及协议栈的处理
一共四种地址类型:
- public address
- random address
- static address
- private address
- non-resolvable address
- resolvable address
其中,要产生resolvable private address(也叫rpa),需要设备有Identity Resolving Key(IRK)。
$$
hash = ah(IRK, prand)
$$
1 | void btm_ble_conn_complete(uint8_t* p, UNUSED_ATTR uint16_t evt_len, |
btm_ble_conn_complete()
中,对ble的连接地址做了一些处理,如果是rpa,则对rpa地址进行解析。
这里面有个细节:
对端的rpa地址是会定时刷新的。第一次连接的时候,我们还没有设备记录,那么就把当前的bda上报上去。
如果后续对端的rpa地址刷新了,也是会把第一次的rpa地址上报上去。
另外,不妨看下rpa是怎么解析的:
1 | tBTM_SEC_DEV_REC* btm_ble_resolve_random_addr(const RawAddress& random_bda) { |
可以看到,rpa的解析与前面介绍的rpa的定义是一致的。我们是首次连接,所以没有匹配记录。
小结:
本小节介绍了BLE地址类型及一些相关知识点,我们看到RPA地址是如何解析的,以及RPA上报的时候即使刷新也会把最开始的RPA上报上去。
两个设备连接,stack中需要保存一些相关设备记录的信息。其中,BTM是管理连接和配对的,L2CAP是管理数据链路通道的,因此它们需要对连接做一些数据记录和管理的工作。
在btm_ble_conn_complete()
最后,会调用btm_ble_connected()
和 l2cble_conn_comp()
进行下一步的处理。
BTM创建设备记录及L2CAP连接管理
1 | void btm_ble_connected(const RawAddress& bda, uint16_t handle, uint8_t enc_mode, |
- btm收到 BLE connected事件后,为这个新的连接分配一个record
- 设置connection handle,地址类型,地址等信息,现在这些都是传上来的rpa
1 | tBTM_SEC_DEV_REC* btm_sec_alloc_dev(const RawAddress& bd_addr) { |
btm_sec_allocate_dev_rec()
是申请一个新的dev_record,并添加到 btm_cb.sec_dev_rec 这个链表中。其规则是:
- 如果超过了最大的sec rec数量,则找到最老的设备删除
- 优先删除没有paired的设备
这样,btm中就记录了一条secure device record了。
接着分析l2cble_conn_comp()
这个函数。
1 | void l2cble_conn_comp(uint16_t handle, uint8_t role, const RawAddress& bda, |
1 | void l2cble_advertiser_conn_comp(uint16_t handle, const RawAddress& bda, |
- 创建一个l2cap的cb
- 初始化状态,保存handle和连接参数等信息
- 告诉btm,acl建立好了。btm继续做acl连接相关工作
现在,又回到btm继续进行处理。
1 | void btm_acl_created(const RawAddress& bda, DEV_CLASS dc, BD_NAME bdn, |
- 在btm_cb的acl_db中分配一个节点
- 更新一些信息,btm_cb的acl节点和l2cb的lcb,通过handle关联起来
- btm触发读取remote feature,获取更完整的连接参数和安全特性参数
小结:
btm保存了一个acl_db的池子,管理acl连接。l2cb中也有lcb_pool。它们之间通过acl_handle关联了起来。
ACL状态上报到BTA和应用层
回到hci log,可以看到,手机来连接设备时,设备发的第一个hci command,就是HCI LE Read Remote Used Features。
接着,手机应答了该命令,然后hci event上来,抛到btu_task进行处理。
1 | void btu_hcif_process_event(UNUSED_ATTR uint8_t controller_id, BT_HDR* p_msg) { |
1 | void btm_read_remote_features_complete(uint8_t* p) { |
- 根据handle查找得到acl idx
- 保存remote features
- 如果支持extended fetures,则去读extended features
- btm针对feature做一些处理
- 继续hci connection的建立过程
处理remote的features,主要是一些secure相关的特性:
1 | void btm_process_remote_ext_features(tACL_CONN* p_acl_cb, |
接着是btm_establish_continue()
:
1 | void btm_establish_continue(tACL_CONN* p_acl_cb) { |
btm_establish_continue()
会给关心连接事件变化的人回调。我们知道。btu是承接bta和hci的,btm的上层就是bta。接下来可以看到,这个事件就是回调给bta进行处理了。
btm_cb.p_bl_changed_cb
是在蓝牙初始化的时候注册的:
1 | void bta_dm_enable(tBTA_DM_MSG* p_data) { |
因此,BTM_BL_CONN_EVT将在bta_dm_bl_change_cback()
中进行处理。
1 | static void bta_dm_bl_change_cback(tBTM_BL_EVENT_DATA* p_data) { |
通过 action 数组找到BTA_DM_ACL_CHANGE_EVT对应的处理函数为bta_dm_acl_change
:
1 | const tBTA_DM_ACTION bta_dm_action[] = { |
- 如果是新的连接,则在bta_dm_cb.device_list中查找设备,找不到则分配一个
- 保存相关信息,对端地址、连接状态,传输类型等
- 继续
bta_dm_cb.p_sec_cback()
向上回调 - 如果不是新设备,则做另外的处理
bta_dm_cb.p_sec_cback()
是在BTA_EnableBluetooeh() 时注册进来的,注册的函数是 bte_dm_evt()。
因此,BTA_DM_LINK_UP_EVT 在 bte_dm_evt()
中被处理,而它只是转发到 btif 线程。
1 | void bte_dm_evt(tBTA_DM_SEC_EVT event, tBTA_DM_SEC* p_data) { |
1 | static void btif_dm_upstreams_evt(uint16_t event, char* p_param) { |
小结:
- bta也有dm相关控制块,叫做bta_dm_cb。这里面的device_list记录着acl连接的设备
- btu 通过发消息把连接事件转给bta_dm模块处理,bta_dm创建了相应的设备节点
- bta继续回调把acl连接事件转给btif线程,btif线程通过acl_state_changed_cb通知给应用
参考资料: