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命令:

  1. 主机构造命令包:opcode=0x0C03,plen=0(Reset命令无参数)
  2. 通过传输层发送给控制器
  3. 控制器收到后,回复命令状态事件(0x0F),表示开始处理
  4. 控制器执行复位操作
  5. 复位完成后,回复命令完成事件(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数据包从上层到控制器的完整路径:

  1. 上层协议(如L2CAP)构造数据,调用hci_send_acl()
  2. hci_send_acl()将数据封装成HCI ACL数据包,放入发送队列
  3. HCI核心从队列中取出数据包,调用传输层的发送函数
  4. 传输层(UART/USB/SDIO)将数据发送给控制器
  5. 控制器处理完成后,回复事件
  6. 事件包通过传输层回到HCI核心
  7. HCI核心解析事件,通知上层协议

这个流程看起来简单,但每个环节都有坑。比如队列满了怎么办?传输超时了怎么办?事件和命令不匹配怎么办?这些都是在实际项目中要处理的问题。

我的经验:调试HCI层时,最好的工具就是打开HCI日志。内核里可以通过echo 0xffff > /sys/kernel/debug/hci0/hci_log来开启所有日志。然后看dmesg,就能看到每个命令和事件的交互过程。我曾经靠这个日志,定位到一个蓝牙耳机连接不上的问题——原来是控制器回复的事件里,状态码是0x22(不支持的特性),但上层没处理这个错误码。

好了,HCI层的内容就讲到这里。下一章我们讲L2CAP层,那是蓝牙协议栈里最复杂的一层。到时候我会分享一个我调试L2CAP拆包重组bug的经历,保证精彩。