您的位置:首页 > 产品设计 > UI/UE

蓝牙之十五-bluedroid enable流程

2016-10-05 11:33 1986 查看
这里代码剖析提出了android的HAL层之上代码的剖析,旨在android蓝牙协议栈的使用方法。
    从安卓的bdt工具说起。当然了是基于bluedroid协议的。



本文就是从bdt命令开始的。bdt命令触发了协议栈初始化:



上图从左到右是bdt命令触发的调用关系,<http://blog.csdn.net/shichaog/article/details/52100954>是更为详细用户空间的代码调用流程。当终端窗口输入enable命令时,bdt_enable()函数间接由process_cmd函数会调用。



为了看看enable做了什么工作,一个是追终代码调用流程,这可以从已经找到的bdt_eanble函数入口,并辅以上图的log信息,第二条线就是蓝牙的核心规范了,梳理核心规范,这一过程无法逾越。

首先从函数追终开始:

void bdt_enable(void)
{
bdt_log("<strong>ENABLE BT</strong>");
if (bt_enabled) {
bdt_log("Bluetooth is already enabled");
return;
}
status = sBtInterface->enable();

check_return_status(status);
}

这里出现了ENABLE BT命令。然后调用的enable方法和init方法类似,都调用bluetooth.c里的函数。



由于c语言非面向对象语言,所以好多函数是通过指针传递的方式实现的,在上述函数中,好多是比较好理解的。

vendor向HCI层发送命令

下面这个函数是厂商代码想向HCI层发送命令是会调用的回调函数。

// Called back from vendor library when it wants to send an HCI command.
static uint8_t transmit_cb(UNUSED_ATTR uint16_t opcode, void *buffer, tINT_CMD_CBACK callback) {
assert(hci != NULL);
hci->transmit_command((BT_HDR *)buffer, transmit_completed_callback, NULL, callback);
return true;
}
hci这个指针的调用位置是:

static bool vendor_open(
const uint8_t *local_bdaddr,
const hci_t *hci_interface) {
assert(lib_handle == NULL);
hci = hci_interface;
在加载HCI module时,调用  module_start_up(get_module(HCI_MODULE));方法,该方法会调用HCI module的start_up方法。

<hci_layer.c>
static void init_layer_interface() {
    LOG_ERROR("Rokid +++ %s ", __func__);
  if (!interface_created) {
    interface.send_low_power_command = low_power_manager->post_command;
    interface.do_postload = do_postload;

    // It's probably ok for this to live forever. It's small and
    // there's only one instance of the hci interface.
    interface.event_dispatcher = data_dispatcher_new("hci_layer");
    if (!interface.event_dispatcher) {
      LOG_ERROR("%s could not create upward dispatcher.", __func__);
      return;
    }

    interface.set_data_queue = set_data_queue;
    interface.transmit_command = transmit_command;
    interface.transmit_command_futured = transmit_command_futured;
    interface.transmit_downward = transmit_downward;
    interface_created = true;
  }

static future_t *start_up(void) {
vendor->open(btif_local_bd_addr.address, &interface);
}
这里有必要插图一张,以跟踪函数对象调用关系:



bluedroid函数在发送命令和数据时使用了线程的概念。还是比较的重要的,有线程就有线程的优先级在,在不同BT的profile需要的优先级是不一样的,如A2DP要求线程优先级较高,以保证实时性。bluedroid中非常重要的一个hci线程是hci_thread。其初始化的过程如下:

<hci_layer.c>
static future_t *start_up(void) {
...
thread = thread_new("hci_thread");
...
fixed_queue_register_dequeue(command_queue, thread_get_reactor(thread), event_command_ready, NULL);
fixed_queue_register_dequeue(packet_queue, thread_get_reactor(thread), event_packet_ready, NULL);
...
}
上述command和packet队列对应于蓝牙的命令和数据两种队列,这在串口也是区分的,见上上一个的插图注释。后面命令和数据会被放到这个队列而由hci_thread线程进行处理。

到这里继续返回该小节最开始的地方:

hci->transmit_command((BT_HDR *)buffer, transmit_completed_callback, NULL, callback);
这个命令是如何完成的。首先根据上图可以知道transmit_command函数,实际上调用的是hci_layer.c文件里的transmit_command函数,基于这一事实,来看看发送命令是如何和线程联系在一起的。整个BT协议栈,很多地方调用了这个方法。

static void transmit_command(
BT_HDR *command,
command_complete_cb complete_callback,
command_status_cb status_callback,
void *context) {
waiting_command_t *wait_entry = osi_calloc(sizeof(waiting_command_t));

//命令填充
uint8_t *stream = command->data + command->offset;
STREAM_TO_UINT16(wait_entry->opcode, stream);
wait_entry->complete_callback = complete_callback;
wait_entry->status_callback = status_callback;
wait_entry->command = command;
wait_entry->context = context;

// Store the command message type in the event field
// in case the upper layer didn't already
command->event = MSG_STACK_TO_HC_HCI_CMD;

//command_queue是上一小段刚刚介绍到的命令队列,wait_entry是command填充数据结构体。下面函数就是将填充好的命令放在等待队列上。
fixed_queue_enqueue(command_queue, wait_entry);
}

为了让函数指针的调用跟踪起来简单些,这里将相关函数指针集和他们的对象绘图如下:



这张图有三个重要信息,第一是文件名,这也指定了函数指针集所在的使用集合,第二是call back(cb)和相关指针是联系在一起的,第三个是指针集合的多有对象。

有了函数调用流程和函数调用关系流程这两张图在手,代码阅读起来会省事多了,剩下的就是将精力放在函数所反应的意义上了。

初始化流程上是先由协议栈调用vendor的接口,即op接口完成的功能包括,芯片上电,firmware配置,串口打开并设置,低功耗使能等,而vendor接口会在初始化过程中调用回调相关回调函数通知上层蓝牙所处的状态,在HCI层通过线程的方式收到命令后,会进一步调用JNI层的方法通知蓝牙service,相关蓝牙状态的情况。

如协议栈调用vendor的op命令来加载firmware时,调用op的如下case语句:

        case BT_VND_OP_FW_CFG:
            {
                hw_config_start();
            }
            break;
而hw_config_start()函数会使用回调通知协议栈

bt_vendor_cbacks->xmit_cb(HCI_RESET, p_buf, hw_config_cback);
<vendor.c>
// Called back from vendor library when it wants to send an HCI command. static uint8_t transmit_cb(UNUSED_ATTR uint16_t opcode, void *buffer, tINT_CMD_CBACK callback) { assert(hci != NULL); hci->transmit_command((BT_HDR *)buffer, transmit_completed_callback, NULL, callback); return true; }
由函数调用图可知,transmit_command调用的是hci_layer.c里的发送函数:

static void transmit_command(
BT_HDR *command,
command_complete_cb complete_callback,
command_status_cb status_callback,
void *context) {
waiting_command_t *wait_entry = osi_calloc(sizeof(waiting_command_t));
if (!wait_entry) {
LOG_ERROR("%s couldn't allocate space for wait entry.", __func__);
return;
}

uint8_t *stream = command->data + command->offset;
STREAM_TO_UINT16(wait_entry->opcode, stream);
wait_entry->complete_callback = complete_callback;
wait_entry->status_callback = status_callback;
wait_entry->command = command;
wait_entry->context = context;

// Store the command message type in the event field
// in case the upper layer didn't already
command->event = MSG_STACK_TO_HC_HCI_CMD;

fixed_queue_enqueue(command_queue, wait_entry);
}
这个函数实现起来比较简单,首先是对发送的命令进行封装,封装完了以后将其放入命令队列就结束了。

在调用fixed_queue_enqueue后,hci_thread线程会接收到命令并调用命令ready函数进行处理。

static void event_command_ready(fixed_queue_t *queue, UNUSED_ATTR void *context) {
if (command_credits > 0) {
waiting_command_t *wait_entry = fixed_queue_dequeue(queue);
command_credits--;

// Move it to the list of commands awaiting response
pthread_mutex_lock(&commands_pending_response_lock);
list_append(commands_pending_response, wait_entry);
pthread_mutex_unlock(&commands_pending_response_lock);

// Send it off
low_power_manager->wake_assert();
packet_fragmenter->fragment_and_dispatch(wait_entry->command);
low_power_manager->transmit_done();

non_repeating_timer_restart_if(command_response_timer, !list_is_empty(commands_pending_response));
}
}
该函数首先将蓝牙从低功耗模式唤醒,然后调用数据包分片并发送接口,packet_fragmenter对应的方法在函数调用图中可以找到。

fragment函数主要就是对数据包进行分包以满足最大包要求,后面还是调用hci_layer.c的回调函数发送。

// Callback for the fragmenter to send a fragment
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);
hal->transmit_data(type, packet->data + packet->offset, packet->len);

if (event != MSG_STACK_TO_HC_HCI_CMD && send_transmit_finished)
buffer_allocator->free(packet);
}


hal层的发送接口实现是

static uint16_t transmit_data(serial_data_type_t type, uint8_t *data, uint16_t length) {
assert(data != NULL);
assert(length > 0);

if (type < DATA_TYPE_COMMAND || type > DATA_TYPE_SCO) {
LOG_ERROR("%s invalid data type: %d", __func__, type);
return 0;
}

// Write the signal byte right before the data
--data;
uint8_t previous_byte = *data;
*(data) = type;
++length;

uint16_t transmitted_length = 0;
while (length > 0) {
ssize_t ret = write(uart_fd, data + transmitted_length, length);
switch (ret) {
case -1:
LOG_ERROR("In %s, error writing to the uart serial port: %s", __func__, strerror(errno));
goto done;
case 0:
// If we wrote nothing, don't loop more because we
// can't go to infinity or beyond
goto done;
default:
transmitted_length += ret;
length -= ret;
break;
}
}

return transmitted_length;
}
明显是像uart_fd文件描述指定的文件(串口)里写入了。

从串口接收数据的线程是在hal open调用时注册的。

eager_reader_register(uart_stream, thread_get_reactor(thread), event_uart_has_bytes, NULL);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: