4. HCI层核心概念:命令、事件、数据包格式与编程接口
HCI(Host Controller Interface)是蓝牙协议栈中主机(Host)与控制器(Controller)之间的通信桥梁。在Linux蓝牙驱动中,HCI层负责将上层协议栈的请求转换为控制器可执行的命令,并将控制器的状态变化和事件通知给上层。理解HCI层的核心概念是进行低功耗蓝牙(BLE)参数配置与性能调优的基础。
4.1 HCI数据包格式
HCI层定义了四种数据包类型,每种类型通过特定的包指示符(Packet Indicator)进行区分。主机与控制器之间的数据交换均遵循此格式。
| 包类型 | 指示符(字节) | 方向 | 用途 |
|---|---|---|---|
| HCI Command | 0x01 | Host → Controller | 主机下发命令给控制器 |
| HCI ACL Data | 0x02 | 双向 | 传输ACL(异步无连接)数据 |
| HCI SCO Data | 0x03 | 双向 | 传输SCO(同步面向连接)语音数据 |
| HCI Event | 0x04 | Controller → Host | 控制器向主机报告事件 |
4.1.1 HCI命令包结构
HCI命令包由操作码(OpCode)、参数总长度和参数三部分组成。操作码又分为OGF(OpCode Group Field)和OCF(OpCode Command Field)。
+--------+--------+--------+--------+--------+--------+
| 0x01 | OGF | OCF | Length | Param0 | Param1 | ...
+--------+--------+--------+--------+--------+--------+
1 byte 2 bytes 1 byte Length bytes
- OGF(6 bits):命令组,如链路控制(0x01)、链路策略(0x02)、控制器与基带(0x03)等。
- OCF(10 bits):具体命令码,如LE Set Advertising Parameters的OCF为0x0006。
- Length:后续参数的总字节数。
4.1.2 HCI事件包结构
事件包是控制器主动上报给主机的异步消息,用于通知命令完成、连接状态变化、LE广播接收等。
+--------+--------+--------+--------+--------+
| 0x04 | Event | Length | Param0 | Param1 | ...
+--------+--------+--------+--------+--------+
1 byte 1 byte 1 byte Length bytes
- Event Code:事件类型,如Command Complete(0x0E)、LE Meta Event(0x3E)等。
- Length:后续参数的总字节数。
- Parameters:事件携带的具体数据,如命令完成状态、连接句柄等。
关键点:LE Meta Event(0x3E)是BLE子事件的容器,其第一个参数为子事件码(Subevent Code),如LE Advertising Report(0x02)、LE Connection Complete(0x01)等。
4.2 HCI命令与事件交互流程
主机下发命令后,控制器必须返回一个事件作为响应。典型的交互流程如下:
性能提示:在BLE调优中,频繁下发HCI命令会导致事件响应延迟增加。建议将多个参数配置合并到一次命令中(如使用LE Set Extended Advertising Parameters),减少命令-事件交互次数。
4.3 HCI Socket编程接口
Linux内核提供了基于Socket的HCI用户空间编程接口,允许应用程序直接与蓝牙控制器交互。HCI Socket使用AF_BLUETOOTH协议族,协议类型为BTPROTO_HCI。
4.3.1 创建与绑定HCI Socket
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
int main() {
int sk;
struct sockaddr_hci addr;
// 创建HCI Socket
sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
if (sk < 0) {
perror("socket");
return -1;
}
// 绑定到指定控制器(hci0)
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = 0; // hci0
addr.hci_channel = HCI_CHANNEL_RAW;
if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sk);
return -1;
}
// 发送HCI命令
// ...
close(sk);
return 0;
}
4.3.2 发送HCI命令与接收事件
通过send()和recv()系统调用直接操作HCI数据包。
// 发送LE Set Advertising Parameters命令
struct hci_request rq;
le_set_advertising_parameters_cp cp;
uint8_t status;
memset(&cp, 0, sizeof(cp));
cp.interval_min = htobs(0x00A0); // 160ms
cp.interval_max = htobs(0x00F0); // 240ms
cp.advtype = 0x00; // 可连接可发现广播
memset(&rq, 0, sizeof(rq));
rq.ogf = OGF_LE_CTL;
rq.ocf = OCF_LE_SET_ADVERTISING_PARAMETERS;
rq.cparam = &cp;
rq.clen = sizeof(cp);
rq.rparam = &status;
rq.rlen = 1;
if (hci_send_req(sk, &rq, 1000) < 0) {
perror("hci_send_req");
close(sk);
return -1;
}
if (status) {
printf("命令失败,状态码: 0x%02x\n", status);
}
注意事项:HCI Socket编程需要root权限或
CAP_NET_RAW能力。在生产环境中,建议使用BlueZ提供的更高层API(如hci_send_cmd),而非直接操作原始Socket。
4.4 hci_dev结构体
在内核中,每个蓝牙控制器对应一个struct hci_dev结构体实例。该结构体是HCI层的核心数据结构,包含了控制器的所有状态、配置和操作函数指针。
4.4.1 关键字段
| 字段 | 类型 | 说明 |
|---|---|---|
name |
char[8] | 设备名称,如"hci0" |
bus |
__u8 | 总线类型(USB、UART、SDIO等) |
dev_type |
__u8 | 设备类型(BR/EDR、LE、AMP) |
flags |
unsigned long | 设备标志位(如HCI_UP、HCI_AUTO_OFF等) |
adv_instance |
struct list_head | LE广播实例链表 |
conn_hash |
struct hci_conn_hash | 连接哈希表,管理所有活动连接 |
ops |
struct hci_ops * | 硬件操作回调函数指针 |
4.4.2 设备状态机
4.5 hci_ops回调函数
struct hci_ops是硬件驱动层与HCI核心层之间的契约。每个蓝牙控制器驱动必须实现这些回调函数,HCI核心层通过调用这些函数与具体硬件交互。
4.5.1 核心回调函数
| 回调函数 | 原型 | 触发时机 |
|---|---|---|
open |
int (*open)(struct hci_dev *hdev) | 设备打开时,初始化硬件 |
close |
int (*close)(struct hci_dev *hdev) | 设备关闭时,释放硬件资源 |
send |
int (*send)(struct hci_dev *hdev, struct sk_buff *skb) | 发送HCI数据包到控制器 |
flush |
int (*flush)(struct hci_dev *hdev) | 清空发送缓冲区 |
setup |
int (*setup)(struct hci_dev *hdev) | 设备初始化时,发送厂商特定命令 |
set_bdaddr |
int (*set_bdaddr)(struct hci_dev *hdev, const bdaddr_t *bdaddr) | 设置蓝牙设备地址 |
4.5.2 回调函数调用链
4.5.3 驱动实现示例(UART驱动片段)
static int h5_send(struct hci_dev *hdev, struct sk_buff *skb)
{
struct h5 *h5 = hci_get_drvdata(hdev);
struct sk_buff *nskb;
int err;
// 添加H4头部(包指示符)
nskb = skb_clone(skb, GFP_ATOMIC);
if (!nskb)
return -ENOMEM;
// 通过串口发送数据
err = serdev_device_write_buf(h5->serdev, nskb->data, nskb->len);
if (err < 0) {
bt_dev_err(hdev, "Failed to send data (%d)", err);
kfree_skb(nskb);
return err;
}
kfree_skb(nskb);
return 0;
}
static const struct hci_ops h5_ops = {
.open = h5_open,
.close = h5_close,
.send = h5_send,
.flush = h5_flush,
.setup = h5_setup,
.set_bdaddr = h5_set_bdaddr,
};
调优要点:在实现
send回调时,应避免在中断上下文中执行耗时操作。对于UART驱动,建议使用DMA传输或环形缓冲区来减少CPU占用,提升BLE数据吞吐量。
4.6 本章小结
- HCI数据包是主机与控制器通信的最小单元,命令包和事件包构成请求-响应模型。
- HCI Socket提供了用户空间直接操作控制器的能力,适用于调试和定制化开发。
- hci_dev是内核中蓝牙控制器的抽象,管理设备状态、连接和广播实例。
- hci_ops是驱动与核心层的接口,驱动开发者必须实现这些回调以支持硬件。