蓝牙开发那些事儿(2)——初始化

路漫漫其修远兮,我们这就上路吧。

 

上电后,Host向controller发出了第一个条指令

所有的蓝牙模块在上电或者需要复位的时候都会收到来自host的这条命令,它开辟鸿蒙,点亮人生。

虽然wireshark已经清晰地把这条命令解析了出来:

第一个字节01表示packet type: HCI Command

第二三字节是03 0c表示opcode: Reset

第四个字节表示parameter长度是0

我们还是去core spec里去寻找一下这条命令。

首先,我们要了解,hci接口流动的数据一共有四种类型:command(0x01)、acl(0x02)、sco(0x03)和event(0x04)。

这4种数据类型,用一个头部信息来表示,参考bluetooth.h:

#define HCI_COMMAND_DATA_PACKET 0x01
#define HCI_ACL_DATA_PACKET       0x02
#define HCI_SCO_DATA_PACKET       0x03
#define HCI_EVENT_PACKET           0x04

 

这里01开头的数据,显然是一条命令。

命令的格式是这样的:

其中opcode(操作码)由ogf(opcode group field,6个bits)和ocf(opcode command field,10个bits)组成,虽然第三个字节的低两位是OCF的高两位,但是因为这两个字节一般是0,所以一般我们的换算方法就很简单了,OCF就是第二个字节,OGF是第三个字节右移两位

所以这里的OCF是0x03,OGF是0xc右移两位,也是0x03

于是我们很容易就可以在core spec找到这条命令:

下面,我们看看btstack是如何发出这一条hci command的。

我们知道协议栈的每层一般都有一个状态机的,btstack也不例外的。

Hci层的核心是一个结构体和一个函数

一个结构体是

static hci_stack_t * hci_stack

hci_stack->state表示“状态”,hci_stack->substate表示“子状态”。为什么需要状态和子状态,接下来的初始化的过程就是个很好的例子。

 

一个函数是

hci_run

它就是根据hci_stack结构体中的这些状态、其他值来收发数据的。

所以,在刚刚上电的时候,状态是HCI_STATE_INITIALIZING ,hci_run会跑到如下代码块

switch (hci_stack->state){

        case HCI_STATE_INITIALIZING:

            hci_initializing_run();

            break;

 

看看hci_initializing_run函数,这又是一个状态机了,其中的状态就是上文提到的初始化的子状态,看看定义子状态的枚举定义,就知道其实初始化就并不简单的,涉及到一系列的命令、事件交互

typedef enum hci_init_state{

    HCI_INIT_SEND_RESET = 0,

    HCI_INIT_W4_SEND_RESET,

    HCI_INIT_SEND_READ_LOCAL_VERSION_INFORMATION,

    HCI_INIT_W4_SEND_READ_LOCAL_VERSION_INFORMATION,

    HCI_INIT_SEND_READ_LOCAL_NAME,

    HCI_INIT_W4_SEND_READ_LOCAL_NAME,



    HCI_INIT_SEND_BAUD_CHANGE,

    HCI_INIT_W4_SEND_BAUD_CHANGE,

    HCI_INIT_CUSTOM_INIT,

    HCI_INIT_W4_CUSTOM_INIT,

    HCI_INIT_SEND_RESET_CSR_WARM_BOOT,

    HCI_INIT_W4_CUSTOM_INIT_CSR_WARM_BOOT,

    HCI_INIT_W4_CUSTOM_INIT_CSR_WARM_BOOT_LINK_RESET,

    HCI_INIT_W4_CUSTOM_INIT_BCM_DELAY,



    HCI_INIT_READ_LOCAL_SUPPORTED_COMMANDS,

    HCI_INIT_W4_READ_LOCAL_SUPPORTED_COMMANDS,



    HCI_INIT_SEND_BAUD_CHANGE_BCM,

    HCI_INIT_W4_SEND_BAUD_CHANGE_BCM,



    HCI_INIT_SET_BD_ADDR,

    HCI_INIT_W4_SET_BD_ADDR,



    HCI_INIT_SEND_RESET_ST_WARM_BOOT,

    HCI_INIT_W4_SEND_RESET_ST_WARM_BOOT,



    HCI_INIT_READ_BD_ADDR,

    HCI_INIT_W4_READ_BD_ADDR,



    HCI_INIT_READ_BUFFER_SIZE,

    HCI_INIT_W4_READ_BUFFER_SIZE,

    HCI_INIT_READ_LOCAL_SUPPORTED_FEATURES,

    HCI_INIT_W4_READ_LOCAL_SUPPORTED_FEATURES,



#ifdef ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL

    HCI_INIT_HOST_BUFFER_SIZE,

    HCI_INIT_W4_HOST_BUFFER_SIZE,

    HCI_INIT_SET_CONTROLLER_TO_HOST_FLOW_CONTROL,

    HCI_INIT_W4_SET_CONTROLLER_TO_HOST_FLOW_CONTROL,

#endif



    HCI_INIT_SET_EVENT_MASK,

    HCI_INIT_W4_SET_EVENT_MASK,



    HCI_INIT_WRITE_SIMPLE_PAIRING_MODE,

    HCI_INIT_W4_WRITE_SIMPLE_PAIRING_MODE,

    HCI_INIT_WRITE_PAGE_TIMEOUT,

    HCI_INIT_W4_WRITE_PAGE_TIMEOUT,

    HCI_INIT_WRITE_DEFAULT_LINK_POLICY_SETTING,

    HCI_INIT_W4_WRITE_DEFAULT_LINK_POLICY_SETTING,

    HCI_INIT_WRITE_CLASS_OF_DEVICE,

    HCI_INIT_W4_WRITE_CLASS_OF_DEVICE,

    HCI_INIT_WRITE_LOCAL_NAME,

    HCI_INIT_W4_WRITE_LOCAL_NAME,

    HCI_INIT_WRITE_EIR_DATA,

    HCI_INIT_W4_WRITE_EIR_DATA,

    HCI_INIT_WRITE_INQUIRY_MODE,

    HCI_INIT_W4_WRITE_INQUIRY_MODE,

    HCI_INIT_WRITE_SECURE_CONNECTIONS_HOST_ENABLE,

    HCI_INIT_W4_WRITE_SECURE_CONNECTIONS_HOST_ENABLE,

    HCI_INIT_WRITE_SCAN_ENABLE,

    HCI_INIT_W4_WRITE_SCAN_ENABLE,



    // SCO over HCI

    HCI_INIT_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE,

    HCI_INIT_W4_WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE,

    HCI_INIT_WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING,

    HCI_INIT_W4_WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING,



    // SCO over HCI Broadcom

    HCI_INIT_BCM_WRITE_SCO_PCM_INT,

    HCI_INIT_W4_BCM_WRITE_SCO_PCM_INT,



#ifdef ENABLE_BLE

    HCI_INIT_LE_READ_BUFFER_SIZE,

    HCI_INIT_W4_LE_READ_BUFFER_SIZE,

    HCI_INIT_WRITE_LE_HOST_SUPPORTED,

    HCI_INIT_W4_WRITE_LE_HOST_SUPPORTED,

    HCI_INIT_LE_SET_EVENT_MASK,

    HCI_INIT_W4_LE_SET_EVENT_MASK,

#endif



#ifdef ENABLE_LE_DATA_LENGTH_EXTENSION

    HCI_INIT_LE_READ_MAX_DATA_LENGTH,

    HCI_INIT_W4_LE_READ_MAX_DATA_LENGTH,

    HCI_INIT_LE_WRITE_SUGGESTED_DATA_LENGTH,

    HCI_INIT_W4_LE_WRITE_SUGGESTED_DATA_LENGTH,

#endif

   

#ifdef ENABLE_LE_CENTRAL

    HCI_INIT_READ_WHITE_LIST_SIZE,

    HCI_INIT_W4_READ_WHITE_LIST_SIZE,



    HCI_INIT_LE_SET_SCAN_PARAMETERS,

    HCI_INIT_W4_LE_SET_SCAN_PARAMETERS,

#endif



    HCI_INIT_DONE,



    HCI_FALLING_ASLEEP_DISCONNECT,

    HCI_FALLING_ASLEEP_W4_WRITE_SCAN_ENABLE,

    HCI_FALLING_ASLEEP_COMPLETE,



    HCI_INIT_AFTER_SLEEP,



    HCI_HALTING_DISCONNECT_ALL_NO_TIMER,

    HCI_HALTING_DISCONNECT_ALL_TIMER,

    HCI_HALTING_W4_TIMER,

    HCI_HALTING_CLOSE,



} hci_substate_t;

好在我们之前是抓过包的,看看我们这次抓包过程中,初始化都经历了哪些:

看到没有,host和controller之间一个command,一个event,一问一答,一唱一和,还挺和谐的。一共16个命令,16个event,完成了这次的初始化进程。

 

好,闲篇不扯远,我们退回到hci_initializing_run这个子状态机:

static void hci_initializing_run(void){

    log_debug("hci_initializing_run: substate %u, can send %u", hci_stack->substate, hci_can_send_command_packet_now());

    switch (hci_stack->substate){

        case HCI_INIT_SEND_RESET:

            hci_state_reset();



#if !defined(HAVE_PLATFORM_IPHONE_OS) && !defined (HAVE_HOST_CONTROLLER_API)

            // prepare reset if command complete not received in 100ms

            btstack_run_loop_set_timer(&hci_stack->timeout, HCI_RESET_RESEND_TIMEOUT_MS);

            btstack_run_loop_set_timer_handler(&hci_stack->timeout, hci_initialization_timeout_handler);

            btstack_run_loop_add_timer(&hci_stack->timeout);

#endif

            // send command

            hci_stack->substate = HCI_INIT_W4_SEND_RESET;

            hci_send_cmd(&hci_reset);

            break;

此时我们应该执行红色代码部分,首先把子状态变成HCI_INIT_W4_SEND_RESET,然后通过hci_send_cmd(&hci_reset);controller下发命令

这里要着重讲一下的是流控,只要是传输层,没有流控肯定不行的,不可能host可以无限制往controller传数据,也不可能controller可以无限制向host传数据。

对于hci transport层的流控,是分成几种情况的。

我们今天遇到的是hci command,就只讲hci command的流控,以后遇到其他的再讲其他的。

一般来说,hostcontroller发送command之后,会收到controllerhci command complete event或者hci command status event,区别在于,hci command status event一般是对于那种异步的命令,但是会返回command status event给host表示已经开始执行command,当工作完成后会通知host一个相关的event

流控的关键点就在command completer event或者command status event中包含一个Num HCI Command Packete字节,用来标识controller目前可以接收的command数。

对于大多数蓝牙模块来说,只能接收单命令,所以这个字节一般是1。当然也有一些controller比较强的,可以接收多个命令的。

btstack中,还是做了简化的处理,只处理单命令的情况。

我们来看一下hci_send_cmd_va_arg这个函数,这个函数一开始先通过hci_can_send_command_packet_now判断一下能不能发送command:

if (!hci_can_send_command_packet_now()){

        log_error("hci_send_cmd called but cannot send packet now");

        return 0;

    }

看一下hci_can_send_command_packet_now这个函数吧:

int hci_can_send_command_packet_now(void){

    if (hci_can_send_comand_packet_transport() == 0) return 0;

    return hci_stack->num_cmd_packets > 0;

}

首先判断一下传输层能不能用,这个是必须的,毕竟是需要用到物理介质去传输,人家如果没工夫搭理你也是不能强求的。

然后hci_stack->num_cmd_packets就是流控的概念了。

这里必须满足hci_stack->num_cmd_packets大于0的条件才可以发送command

那么hci_stack->num_cmd_packets什么情况下会大于0呢?

搜索一下代码,逻辑还是比较清楚的。

hci_send_cmd_packet这个函数的最后有这么一行代码:

hci_stack->num_cmd_packets- -;

意思很明显,当host向controller发送一条command 的时候,host是处在一个“等”的状态,此时hci_stack->num_cmd_packet被置成0,(hci_stack->num_cmd_packets在btstack里简化了,取值只会是1或者0,所以hci_stack->num_cmd_packet- -的效果就是变成0了)

所以如果之后host还要发command的话是发不了的。

那么在什么时候才可以恢复呢?

就是在收到了command complete event或者command status event了以后:

static void handle_command_complete_event(uint8_t * packet, uint16_t size){

    UNUSED(size);



    uint16_t manufacturer;

#ifdef ENABLE_CLASSIC

    hci_con_handle_t handle;

    hci_connection_t * conn;

    uint8_t status;

#endif

    // get num cmd packets - limit to 1 to reduce complexity

    hci_stack->num_cmd_packets = packet[2] ? 1 : 0;

以上是command complete event的部分。

static void event_handler(uint8_t *packet, int size){



    uint16_t event_length = packet[1];



    // assert packet is complete

    if (size != (event_length + 2)){

        log_error("event_handler called with packet of wrong size %d, expected %u => dropping packet", size, event_length + 2);

        return;

    }



    bd_addr_t addr;

    bd_addr_type_t addr_type;

    hci_con_handle_t handle;

    hci_connection_t * conn;

    int i;

    int create_connection_cmd;



#ifdef ENABLE_CLASSIC

    uint8_t link_type;

#endif



    // log_info("HCI:EVENT:%02x", hci_event_packet_get_type(packet));

   

    switch (hci_event_packet_get_type(packet)) {

                       

        case HCI_EVENT_COMMAND_COMPLETE:

            handle_command_complete_event(packet, size);

            break;

           

        case HCI_EVENT_COMMAND_STATUS:

            // get num cmd packets - limit to 1 to reduce complexity

            hci_stack->num_cmd_packets = packet[3] ? 1 : 0;

以上是command status的部分。

从注释也可以看到,为了降低复杂度,btstack是忽略了host num command packet位的,只支持单命令。

那如果controller抽风了,就是不想回command complete event或者command status event的话呢?

这就需要用到定时器了,看一下btstack的框图就知道定时器的地位有多重要。

实际上任何协议栈都是需要定时器的,有一些需要定时执行的任务,还有一些超时处理,超时了该丢弃的丢弃,该重传的重传,等等。

在我们当前的场景,假如command超时没有收到controller回复的话:

static void hci_initialization_timeout_handler(btstack_timer_source_t * ds){

    UNUSED(ds);



    switch (hci_stack->substate){

        case HCI_INIT_W4_SEND_RESET:

            log_info("Resend HCI Reset");

            hci_stack->substate = HCI_INIT_SEND_RESET;

            hci_stack->num_cmd_packets = 1;

            hci_run();

            break;

        case HCI_INIT_W4_CUSTOM_INIT_CSR_WARM_BOOT_LINK_RESET:

            log_info("Resend HCI Reset - CSR Warm Boot with Link Reset");

            if (hci_stack->hci_transport->reset_link){

                hci_stack->hci_transport->reset_link();

            }



            /* fall through */



        case HCI_INIT_W4_CUSTOM_INIT_CSR_WARM_BOOT:

            log_info("Resend HCI Reset - CSR Warm Boot");

            hci_stack->substate = HCI_INIT_SEND_RESET_CSR_WARM_BOOT;

            hci_stack->num_cmd_packets = 1;

            hci_run();

            break;

在超时handler里去把hci_stack->num_cmd_packets重新置位,这样又可以继续发命令了。

笔者工作中曾经有需要自定义非标准hci command的开发场景,当时写完代码后总感觉蓝牙连接哪里慢了,后来抓了hcilog发现,在发完新支持的这条hci command后,因为忘记写对应的hci command complete event了,导致超时后,才能下发下一条命令,坑啊。

 

好,发完命令,我们接着看数据。

我们这一次还是很顺利地收到了command complete event。

hci_initializing_event_handler是初始化状态的事件处理状态机,在收到controller发送的事件后,调整substate,让状态机跑起来吧。

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值