Bluedroid协议栈HCI线程分析
蓝牙进程中有多个线程,其中HCI 线程是负责处理蓝牙主机端和控制器的数据处理和收发的工作。
本篇文章就是分析一下该线程的数据处理流程。
1.跟HCI相关的接口
首先看看hci的相关的接口:在hci_layer.c中:
const hci_t *hci_layer_get_interface() {
buffer_allocator = buffer_allocator_get_interface();
hal = hci_hal_get_interface();//hal模块
btsnoop = btsnoop_get_interface();
hci_inject = hci_inject_get_interface();
packet_fragmenter = packet_fragmenter_get_interface();//组装分块
vendor = vendor_get_interface();//vendor模块
low_power_manager = low_power_manager_get_interface();
init_layer_interface();
return &interface;
}
主要是结构是:hal,packet_fragmenter以及vendor,下面看看这个接口的结构:
hal模块接口
static const hci_hal_t interface = {
hal_init,
hal_open,//通过vendor模块发送指令VENDOR_OPEN_USERIAL,打开host与controller的通信节点,并且在hci线程中一直poll该节点,有数据就传上层协议栈
hal_close,
read_data,
packet_finished,
transmit_data,
}; const hci_hal_t *hci_hal_h4_get_interface() {
vendor = vendor_get_interface();//获取了vendor接口
return &interface;
}
分析代码发现hal_open主要是通过vendor来和底层的模块通信的。可见hal层在vendor的上面。
packet_fragmenter模块接口
static const packet_fragmenter_t interface = {
init,
cleanup,
fragment_and_dispatch,//分片,然后回调到hci_layer,通过hal层发送
reassemble_and_dispatch//重装,然后回调到hci_layer,塞到btu_hci_queue队列里面
}; const packet_fragmenter_t *packet_fragmenter_get_interface() {
controller = controller_get_interface();//获取控制器的接口
buffer_allocator = buffer_allocator_get_interface();
return &interface;
}
该模块主要负责数据的分片和重组,当hci向下发送数据的时候,会将数据放置到packet_queue,然后调用到该模块的fragment_and_dispatch,然后经过HAL模块发送到vendor,最后抵达controller
当controller有数据上传的时候,底层的bt driver会将数据发送到host与controller的通信节点。hci_thread会一直poll这个节点,然后读出数据,经过hal以及fragment_and_dispatch,最后送到btu线程。
vendor模块接口
static const vendor_t interface = {
vendor_open,//加载libbt-vendor模块并对模块初始化
vendor_close,
send_command,//通过libbt-vendor进行发送op命令,非hci opcode
send_async_command,
set_callback,
}; const vendor_t *vendor_get_interface() {
buffer_allocator = buffer_allocator_get_interface();
return &interface;
}
vendor模块主要是初始化libbt-vendor模块,一些与厂商相关的接口定义。具体的实现是厂商自己的实现,比如打开底层的通信节点,downloaf 卡片的patch等等。
2.线程的创建
线程的创建在hci_layer.c里面,在hci 模块的start_up函数里面:
static future_t *start_up(void) {
LOG_INFO("%s", __func__);
...
command_queue = fixed_queue_new(SIZE_MAX);//创建命令队列,用于发送命令
packet_queue = fixed_queue_new(SIZE_MAX);//创建数据队列,用于发送数据
thread = thread_new("hci_thread");//创建hci线程
...
packet_fragmenter->init(&packet_fragmenter_callbacks);//初始化“组装分块”模块 fixed_queue_register_dequeue(command_queue, thread_get_reactor(thread), event_command_ready, NULL);//hci_thread绑定命令队列
fixed_queue_register_dequeue(packet_queue, thread_get_reactor(thread), event_packet_ready, NULL);//hci_thread 绑定数据队列
...
vendor->open(btif_local_bd_addr.address, &interface);//调用vendor模块的open
hal->init(&hal_callbacks, thread);//初始化hal模块
...
thread_post(thread, event_finish_startup, NULL);//继续完成hci模块的启动工作,这里主要做的是继续初始化vendor模块
...
这里主要关注一下队列的绑定,当往command_queue里面塞数据的时候,event_command_ready就会被调用来处理这个数据,注意这里都是在hci_thread 里面执行的。同理往数据队列里面塞数据,event_packet_ready就会被执行。
3.数据的发送和接收
3.1数据的发送
看代码可以发现,event_command_ready和event_packet_ready 他们都会调用同一个接口来发送数据,packet_fragmenter模块里面的:
packet_fragmenter->fragment_and_dispatch(wait_entry->command);
也就是说,所有的数据都会先进行fragment以及dispatch的过程,我们这里主要关注数据的流向,那么也就是dispatch的流程:
static void fragment_and_dispatch(BT_HDR *packet) {
...
callbacks->fragmented(packet, true);
}
发现最后是通过回调函数来发送,packet_fragmenter_callbacks_t:,看看其结构:
typedef struct {
packet_fragmented_cb fragmented;
packet_reassembled_cb reassembled;
transmit_finished_cb transmit_finished;
} packet_fragmenter_callbacks_t;
static void transmit_fragment(BT_HDR *packet, bool send_transmit_finished) {
uint16_t event = packet->event & MSG_EVT_MASK;
serial_data_type_t type = event_to_data_type(event);
btsnoop->capture(packet, false);//记录btsnoop数据
hal->transmit_data(type, packet->data + packet->offset, packet->len);//调用hal接口发送数据
}
我们继续看hal的相关的接口:
static uint16_t transmit_data(serial_data_type_t type, uint8_t *data, uint16_t length) {
...
while (length > ) {
ssize_t ret = write(uart_fd, data + transmitted_length, length);
...
return transmitted_length;
}
hal层调用的接口很简单,主要就是往hal_open返回的节点描述符写数据,这个数据最终会经过内核抵达硬件设备端。
发送数据的流程就结束了。
3.2数据的接收
这里应该首先分析一下hal_open的流程:该流程是在event_finish_startup函数里执行,是hci_thread线程一开始就执行的函数:
static void event_finish_startup(UNUSED_ATTR void *context) {
LOG_INFO("%s", __func__);
if(!hal->open())
return;
vendor->send_async_command(VENDOR_CONFIGURE_FIRMWARE, NULL);
}
static bool hal_open() {
int fd_array[CH_MAX];
int number_of_ports = vendor->send_command(VENDOR_OPEN_USERIAL, &fd_array);//通过vendor接口去打开底层的设备节点,存储在fd_array中 uart_fd = fd_array[];
uart_stream = eager_reader_new(uart_fd, &allocator_malloc, HCI_HAL_SERIAL_BUFFER_SIZE, SIZE_MAX, "hci_single_channel");
eager_reader_register(uart_stream, thread_get_reactor(thread), event_uart_has_bytes, NULL);//hci_thread线程一直poll设备节点,有数据就会调用event_uart_has_bytes来处理
return true;
}
现在我们知道,只要底层有数据传上来,那么hal层的函数event_uart_has_bytes就会去处理这些数据,那么看看event_uart_has_bytes的实现:
static void event_uart_has_bytes(eager_reader_t *reader, UNUSED_ATTR void *context) {
if (stream_has_interpretation) {
callbacks->data_ready(current_data_type);//最终调用该函数
} else {
uint8_t type_byte;
if (eager_reader_read(reader, &type_byte, , true) == ) {
LOG_ERROR("%s could not read HCI message type", __func__);
return;
}
...
stream_has_interpretation = true;
current_data_type = type_byte;
}
}
最终调用:
static const hci_hal_callbacks_t hal_callbacks = {
hal_says_data_ready
};
这个函数之前有分析过,这里简单介绍其流程:
static void hal_says_data_ready(serial_data_type_t type) {
packet_receive_data_t *incoming = &incoming_packets[PACKET_TYPE_TO_INBOUND_INDEX(type)];
uint8_t byte;
while (hal->read_data(type, &byte, , false) != ) {
switch (incoming->state) {
case BRAND_NEW:
...
case BODY:
incoming->buffer->data[incoming->index] = byte;
...
break;
case IGNORE:
incoming->bytes_remaining--;
...
hal->packet_finished(type);
return;
}
break;
case FINISHED:
LOG_ERROR("%s the state machine should not have been left in the finished state.", __func__);
break;
} if (incoming->state == FINISHED) {
incoming->buffer->len = incoming->index;
btsnoop->capture(incoming->buffer, true);//保存btsnoop文件 if (type != DATA_TYPE_EVENT) {
packet_fragmenter->reassemble_and_dispatch(incoming->buffer);//acl data处理流程
} else if (!filter_incoming_event(incoming->buffer)) {//event 处理流程
// Dispatch the event by event code
uint8_t *stream = incoming->buffer->data;
uint8_t event_code;
STREAM_TO_UINT8(event_code, stream); data_dispatcher_dispatch(
interface.event_dispatcher,
event_code,
incoming->buffer
);
} // We don't control the buffer anymore
incoming->buffer = NULL;
incoming->state = BRAND_NEW;
hal->packet_finished(type); // We return after a packet is finished for two reasons:
// 1. The type of the next packet could be different.
// 2. We don't want to hog cpu time.
return;
}
} }
从上面的代码我们发现,主要是经过两个路径来上报数据的:
- packet_fragmenter->reassemble_and_dispatch(incoming->buffer);
data_dispatcher_dispatch(interface.event_dispatcher,event_code,incoming->buffer);
首先看一下 第一个路径:
static void reassemble_and_dispatch(UNUSED_ATTR BT_HDR *packet) {
...
callbacks->reassembled(packet);
}
上面的callback 定义在hci_layer.c
static void dispatch_reassembled(BT_HDR *packet) {
...
if (upwards_data_queue) {
fixed_queue_enqueue(upwards_data_queue, packet);//把数据放到upwards_data_queue,这个队列其实就是btu_hci_msg_queue
}
}
这个队列是在bte_main_boot_entry 时候注册的:
hci = hci_layer_get_interface();
btu_hci_msg_queue = fixed_queue_new(SIZE_MAX);
data_dispatcher_register_default(hci->event_dispatcher, btu_hci_msg_queue);
hci->set_data_queue(btu_hci_msg_queue);//设置upwards_data_queue
从这里我们知道,最终的数据送到了btu_hci_msg_queue,那么就由btu 线程继续处理了。
下面继续看看data_dispatcher_dispatch(interface.event_dispatcher,event_code,incoming->buffer);
bool data_dispatcher_dispatch(data_dispatcher_t *dispatcher, data_dispatcher_type_t type, void *data) {
fixed_queue_t *queue = hash_map_get(dispatcher->dispatch_table, (void *)type);
if (!queue)
queue = dispatcher->default_queue;//这里的queue其实也是btu_hci_msg_queue if (queue)
fixed_queue_enqueue(queue, data); return queue != NULL;
}
上面的queue也是在bte_main_boot_entry 里面注册的。
data_dispatcher_register_default(hci->event_dispatcher, btu_hci_msg_queue);
void data_dispatcher_register_default(data_dispatcher_t *dispatcher, fixed_queue_t *queue) {
assert(dispatcher != NULL);
dispatcher->default_queue = queue;
}
我们知道hci->event_dispatcher->default_queue = btu_hci_msg_queue
那和上面的第一种case一样:最终的数据送到了btu_hci_msg_queue,那么就由btu 线程继续处理了。
总结
最后总结一下hci_thread处理的数据流程:
当hci向下发送数据的时候,会将数据放置到packet_queue,然后调用到fragment_and_dispatch,然后经过HAL模块发write到vendor,最后抵达controller
当controller有数据上传的时候,底层的bt driver会将数据发送到host与controller的通信节点。hci_thread会一直poll这个节点,调用 hal_says_data_ready读出数据,数据经过hal以及fragment_and_dispatch(或者data_dispatcher_dispatch),将数据送到btu_hci_msg_queue,然后由btu 线程继续处理。
Bluedroid协议栈HCI线程分析的更多相关文章
- Bluedroid协议栈BTU线程处理HCI数据流程分析
在蓝牙enable的过程中会进行多个线程的创建以及将线程与队列进行绑定的工作.该篇文章主要分析一下处理hci数据这个 线程. void BTU_StartUp(void) { ... btu_bta_ ...
- Android Bluetooth hci 命令分析
Android在连接BLE设备的时候,遇到连接没多久就自动断开的情况.通过HCI来分析一下. BLE设备发送连接参数更新请求 3909 15:53:01.224737 TexasIns_f0:d3:4 ...
- TDA - Thread Dump Analyzer (Java线程分析工具)
TDA - Thread Dump Analyzer (Java线程分析工具)http://automationqa.com/forum.php?mod=viewthread&tid=2351 ...
- 【JVM】jstack和dump线程分析(2)
一:jstack jstack命令的语法格式: jstack <pid>.可以用jps查看java进程id.这里要注意的是:1. 不同的 JAVA虚机的线程 DUMP的创建方法和文件格式 ...
- JVM调优-Jstack线程分析
jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使 ...
- TI BLE协议栈软件框架分析
看源代码的时候,一般都是从整个代码的入口处开始,TI BLE 协议栈源码也不例外.它的入口main()函数就是整个程序的入口,由系统上电时自动调用. 它主要做了以下几件事情: (一)底层硬件初始化配 ...
- java jdk自带程序分析(内存分析/线程分析)
周末看到一个用jstack查看死锁的例子.昨天晚上总结了一下jstack(查看线程).jmap(查看内存)和jstat(性能分析)命令. 1.1.Jstack 1.1 jstack能得到运行jav ...
- jstack生成的Thread Dump日志线程 分析
文章转载自: https://www.javatang.com/archives/2017/10/25/36441958.html 前面文章中只分析了Thread Dump日志文件的结构,今天针对日志 ...
- Java 使用命令对堆线程分析
一.dump基本概念 在故障定位(尤其是out of memory)和性能分析的时候,经常会用到一些文件来帮助我们排除代码问题.这些文件记录了JVM运行期间的内存占用.线程执行等情况,这就是我们常说的 ...
随机推荐
- mysql内存管理
1 内存管理结构 mysql有自己的内存申请和释放机制 mysql层有mem_root innodb层有mem_heap,mem_pool,buf_pool 它们的结构图如下 2 mem_root m ...
- HTML table固定表头
最近尝试了几种HTML的table固定表头的方法..额...各有利弊,但很尴尬..... 1.thead和tbody的display设置为block; 这种可以实现,但是需要提前设置好每个th和td的 ...
- Jboss7或者wildfly部署war包的问题 1
1.Jboss的日志系统(standalone模式) 在介绍案例分析之前先来介绍一下Jboss提供的日志系统,下载EAP的zip包解压后的结构如下: 在standalone目录下有两个文件standa ...
- 【Git】从服务器搭建到提交分支使用——初学者轻松上手篇
GitHub就是一个免费托管开源代码的远程仓库,个人可以把代码寄存处上面,不过会被公开.对于商业公司来说在Linux上搭建一台Git服务器作为私有仓库使用.开发人员在本地下载仓库代码,协同开发.本篇介 ...
- 实战演示疑惑 mysql insert到底加什么锁
innodb的事务隔离级别是可重复读级别且innodb_locks_unsafe_for_binlog禁用,也就是说允许next-key lock 实验来自网上. ( 如果你没有演示出来,请check ...
- 一个服务器多个tomcat的配置
下面我们把配置的详细过程写在下面,以供参考:(此例以配置三个Tomcat为例)1. 下载apache-tomcat-7.0.63,下载下来的文件为apache-tomcat-7.0.63.zip.2. ...
- 【爬坑】在 IDEA 中运行 Hadoop 程序 报 winutils.exe 不存在错误解决方案
0. 问题说明 环境为 Windows 10 在 IDEA 中运行 Hadoop 程序报 winutils.exe 不存在 错误 1. 解决方案 [1.1 解压] 解压 hadoop-2.7.3 ...
- opensuse编译安装Python3后缺少zlib
目录 opensuse编译安装Python3后缺少zlib 前言 编译安装 python导入zlib 重新编译python并指定zlib opensuse编译安装Python3后缺少zlib 前言 由 ...
- 服务器上u盘装机centos7.2
说明: 截止目前CentOS 7.x最新版本为CentOS 7.2.1511,下面介绍CentOS 7.2.1511的具体安装配置过程 服务器相关设置如下: 操作系统:CentOS 7.2.1511 ...
- python class根据配置自定义函数
今天看到了一种有趣的定义函数的方式: class Test(object): def define_get_methods(cls, method_name, path): def inner_get ...