2、HCI层详解:HCI命令与事件、HCI传输层(UART/USB/SDIO)、HCI核心数据结构
好,咱们今天来啃HCI层这块硬骨头。说实话,HCI是蓝牙子系统中我最喜欢的一层——它像个翻译官,把上层的蓝牙协议栈和底层的硬件控制器完美地隔离开。你想想看,没有HCI,上层应用就得直接跟寄存器打交道,那得多痛苦。
2.1 HCI命令与事件:一问一答的默契
HCI层的工作模式,说白了就是「命令-事件」的对话机制。主机发送命令,控制器回复事件。就这么简单,但细节里全是魔鬼。
2.1.1 HCI命令包格式
每个HCI命令包,结构非常清晰。我直接上代码,你们感受一下:
struct hci_command_hdr {
__le16 opcode; /* 操作码,高6位是OGF,低10位是OCF */
__u8 plen; /* 参数长度,单位字节 */
} __packed;
这里有个坑,我当年第一次写蓝牙驱动时就踩过。opcode字段是16位的,但它的高6位是OGF(Opcode Group Field),低10位是OCF(Opcode Command Field)。OGF用来分组,比如0x01是链路控制命令,0x03是控制器与基带命令。OCF才是具体的命令编号。
核心要点:OGF + OCF 唯一确定一个HCI命令。比如HCI_Reset命令,OGF=0x03,OCF=0x0003,组合起来opcode就是0x0C03。
2.1.2 HCI事件包格式
控制器收到命令后,会回复事件。事件包的结构更简单:
struct hci_event_hdr {
__u8 evt; /* 事件码 */
__u8 plen; /* 参数长度 */
} __packed;
事件码0x0E是命令完成事件,0x0F是命令状态事件。这两个事件最常用,也最容易搞混。我个人习惯是:命令完成事件表示命令已经执行完毕,而命令状态事件只是告诉你「命令我已经收到了,正在处理中」。嗯,这里要注意,有些命令会先回复命令状态事件,等执行完再回复命令完成事件。
2.1.3 典型的命令-事件交互流程
我举个例子,主机发送HCI_Reset命令:
- 主机构造命令包:opcode=0x0C03,plen=0(Reset命令无参数)
- 通过传输层发送给控制器
- 控制器收到后,回复命令状态事件(0x0F),表示开始处理
- 控制器执行复位操作
- 复位完成后,回复命令完成事件(0x0E),携带状态参数
避坑指南:我曾经在调试时发现,有些控制器对Reset命令只回复命令完成事件,不回复命令状态事件。后来查规范才知道,这是允许的。所以写代码时,两种事件都要能处理。
2.2 HCI传输层:UART/USB/SDIO
HCI命令和事件怎么在主机和控制器之间传递?这就靠传输层了。常见的传输方式有三种:UART、USB、SDIO。每种都有自己的脾气。
2.2.1 HCI UART传输
UART传输是最古老也最灵活的方式。它用H4协议来封装HCI数据包。H4协议很简单,就是在每个包前面加一个字节的类型指示:
/* H4 类型指示 */
#define HCI_H4_TYPE_COMMAND 0x01
#define HCI_H4_TYPE_ACL_DATA 0x02
#define HCI_H4_TYPE_SCO_DATA 0x03
#define HCI_H4_TYPE_EVENT 0x04
比如发送命令包时,先发0x01,再发命令包内容。接收事件时,先收到0x04,再收事件包。就这么简单。
但UART有个问题——没有流控。我曾经在一个项目里,UART波特率设得太高,控制器来不及处理,数据就丢了。后来加了硬件流控(RTS/CTS),问题才解决。所以我的建议是:UART一定要开流控,除非你确定数据量很小。
2.2.2 HCI USB传输
USB传输就复杂多了。它用等时端点传SCO数据,用中断端点传事件,用批量端点传命令和ACL数据。每个端点都有自己的管道。
USB的HCI驱动里,最核心的是urb(USB Request Block)的管理。我给你们看一段关键代码:
static int hci_usb_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
struct usb_ctrlrequest *dr;
struct urb *urb;
int pipe;
urb = usb_alloc_urb(0, GFP_ATOMIC);
if (!urb)
return -ENOMEM;
/* 根据HCI包类型选择端点 */
switch (hci_skb_pkt_type(skb)) {
case HCI_COMMAND_PKT:
pipe = hdev->bulk_tx->pipe;
break;
case HCI_ACLDATA_PKT:
pipe = hdev->bulk_tx->pipe;
break;
case HCI_SCODATA_PKT:
pipe = hdev->isoc_tx->pipe;
break;
}
/* 填充urb并提交 */
usb_fill_bulk_urb(urb, hdev->udev, pipe,
skb->data, skb->len,
hci_usb_tx_complete, skb);
return usb_submit_urb(urb, GFP_ATOMIC);
}
注意:USB传输中,urb的分配和释放一定要配对。我曾经见过一个bug,urb分配后没释放,导致内存泄漏,系统跑几天就挂了。排查起来特别痛苦。
2.2.3 HCI SDIO传输
SDIO传输在嵌入式设备里很常见,尤其是WiFi+蓝牙二合一芯片。SDIO的HCI驱动,核心是处理SDIO的块传输和函数选择。
SDIO的HCI包格式和UART类似,也是用类型指示来区分。但SDIO支持多块传输,吞吐量比UART高很多。
我记得有个项目,用SDIO接口的蓝牙芯片,传输ACL数据时总是丢包。后来发现是SDIO时钟频率太高,信号质量不行。降到50MHz后,问题就解决了。嗯,硬件问题有时候就是这么玄学。
2.3 HCI核心数据结构
HCI层的核心数据结构,就是hci_dev和hci_conn。这两个结构体,你写蓝牙驱动时天天都要打交道。
2.3.1 hci_dev结构体
hci_dev代表一个蓝牙控制器。它包含了控制器的所有信息:
struct hci_dev {
struct list_head list; /* 全局链表 */
struct mutex lock; /* 保护hci_dev的互斥锁 */
char name[8]; /* 设备名,如hci0 */
struct device *dev; /* 对应的设备结构体 */
/* 传输层相关 */
struct hci_transport_ops *transport;
/* 命令队列 */
struct sk_buff_head cmd_q;
/* 连接管理 */
struct list_head conn_list; /* 所有连接 */
struct list_head pend_le_conns; /* 待处理的LE连接 */
/* 控制器信息 */
__u8 features[8]; /* 控制器特性 */
__u8 hci_ver; /* HCI规范版本 */
__le16 hci_rev; /* HCI修订号 */
__u8 lmp_ver; /* LMP版本 */
__le16 manufacturer; /* 制造商ID */
/* 状态标志 */
unsigned long flags;
};
这个结构体很大,我只列了最核心的字段。实际代码里,它还有几十个字段。我个人习惯是,调试时重点关注flags字段,它指示了控制器的各种状态,比如是否已初始化、是否正在扫描等。
2.3.2 hci_conn结构体
hci_conn代表一个蓝牙连接。无论是经典蓝牙的ACL连接,还是BLE的连接,都用这个结构体表示:
struct hci_conn {
struct list_head list; /* 挂在hci_dev的conn_list上 */
struct hci_dev *hdev; /* 所属控制器 */
__u8 type; /* 连接类型:ACL/SCO/LE */
__u8 state; /* 连接状态 */
bdaddr_t dst; /* 对端设备地址 */
__u16 handle; /* 连接句柄 */
/* ACL相关 */
__u16 pkt_type; /* 支持的分组类型 */
__u16 interval; /* 连接间隔 */
/* SCO相关 */
__u8 tx_len; /* SCO发送长度 */
/* LE相关 */
__le16 conn_interval; /* 连接间隔 */
__le16 conn_latency; /* 连接延迟 */
__le16 supervision_timeout; /* 监督超时 */
};
关键点:handle字段是连接句柄,所有ACL和SCO数据包都通过句柄来标识属于哪个连接。句柄是16位的,由控制器分配。我曾经在调试时发现,句柄用完了,新连接就建立不了。后来查规范,句柄范围是0x0000到0x0EFF,总共3840个。一般够用,但如果你做多连接设备,还是要留意。
2.3.3 数据包的生命周期
一个HCI数据包从上层到控制器的完整路径:
- 上层协议(如L2CAP)构造数据,调用hci_send_acl()
- hci_send_acl()将数据封装成HCI ACL数据包,放入发送队列
- HCI核心从队列中取出数据包,调用传输层的发送函数
- 传输层(UART/USB/SDIO)将数据发送给控制器
- 控制器处理完成后,回复事件
- 事件包通过传输层回到HCI核心
- HCI核心解析事件,通知上层协议
这个流程看起来简单,但每个环节都有坑。比如队列满了怎么办?传输超时了怎么办?事件和命令不匹配怎么办?这些都是在实际项目中要处理的问题。
我的经验:调试HCI层时,最好的工具就是打开HCI日志。内核里可以通过echo 0xffff > /sys/kernel/debug/hci0/hci_log来开启所有日志。然后看dmesg,就能看到每个命令和事件的交互过程。我曾经靠这个日志,定位到一个蓝牙耳机连接不上的问题——原来是控制器回复的事件里,状态码是0x22(不支持的特性),但上层没处理这个错误码。
好了,HCI层的内容就讲到这里。下一章我们讲L2CAP层,那是蓝牙协议栈里最复杂的一层。到时候我会分享一个我调试L2CAP拆包重组bug的经历,保证精彩。