路漫漫其修远兮,我们这就上路吧。
上电后,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的流控,以后遇到其他的再讲其他的。
一般来说,host向controller发送command之后,会收到controller的hci 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,让状态机跑起来吧。