4、HCI层驱动开发:HCI传输层接口、UART蓝牙驱动实现、USB蓝牙驱动框架

好,咱们进入第四章节。这一章可以说是蓝牙驱动开发中最「接地气」的部分了。HCI层,说白了就是蓝牙主机和蓝牙控制器之间的「翻译官」。你想想看,你的CPU跑着蓝牙协议栈,蓝牙芯片那边跑着链路层和基带,两边怎么通信?就是靠HCI传输层。

我个人习惯把HCI传输层比作「管道」。这个管道可以是UART、USB、SDIO,甚至SPI。今天咱们重点聊UART和USB,因为这两者在实际产品中占了九成以上。

4.1 HCI传输层接口

先看内核里怎么定义这个接口的。在Linux内核中,HCI传输层驱动需要实现一组标准的回调函数。这些函数定义在 struct hci_transport_ops 结构体中。我直接上代码,你感受一下:

struct hci_transport_ops {
    int (*open)(struct hci_dev *hdev);
    int (*close)(struct hci_dev *hdev);
    int (*flush)(struct hci_dev *hdev);
    int (*send)(struct hci_dev *hdev, struct sk_buff *skb);
    void (*notify)(struct hci_dev *hdev, unsigned int evt);
};

嗯,这里要注意,send 函数是最核心的。所有从主机发往控制器的HCI命令、ACL数据、SCO数据,最终都要经过这个函数。我在项目中遇到过一个问题,某个UART蓝牙模块在高速传输时丢包,最后发现是 send 函数里没有做流控,导致UART FIFO溢出。

核心要点:HCI传输层驱动不关心数据内容,它只负责把数据包从主机搬到控制器,再把响应搬回来。数据包的解析是HCI核心层的事。

除了回调函数,传输层还需要注册一个 struct hci_dev 设备。这个设备会挂到蓝牙子系统中。注册方式很简单:

struct hci_dev *hdev;
hdev = hci_alloc_dev();
if (!hdev)
    return -ENOMEM;

hdev->bus = HCI_UART;  // 或者 HCI_USB
hdev->open  = uart_open;
hdev->close = uart_close;
hdev->send  = uart_send;

ret = hci_register_dev(hdev);
if (ret < 0) {
    hci_free_dev(hdev);
    return ret;
}

这里 hdev->bus 决定了这个设备是走UART还是USB。内核蓝牙子系统会根据这个字段选择对应的协议处理方式。

4.2 UART蓝牙驱动实现

UART蓝牙驱动,说白了就是让蓝牙芯片通过串口和主机通信。最常见的方案是H4协议,也就是在UART数据流中插入HCI数据包类型标识。

H4协议的数据包格式很简单:

字节0 后续数据
类型指示符 HCI数据包内容

类型指示符的值:

  • 0x01 — HCI命令包
  • 0x02 — HCI ACL数据包
  • 0x03 — HCI SCO数据包
  • 0x04 — HCI事件包

我曾经调试过一个UART蓝牙模块,死活连不上手机。抓了UART波形一看,发现发送命令包时类型指示符写成了 0x02,控制器当成ACL数据包解析了,自然不认。这种低级错误,排查起来最费时间。

UART驱动的核心是中断处理函数。当UART收到数据时,中断处理函数会读取一个字节,然后根据当前状态判断是类型指示符还是数据内容。我一般这样实现:

static void uart_recv(struct uart_port *port, unsigned int status)
{
    unsigned char byte;
    struct hci_uart *hu = port->priv_data;

    byte = readb(port->membase + RX_FIFO);

    switch (hu->rx_state) {
    case H4_RX_TYPE:
        hu->rx_skb = bt_skb_alloc(HCI_MAX_FRAME_SIZE, GFP_ATOMIC);
        if (!hu->rx_skb)
            return;
        hu->rx_skb->data[0] = byte;
        skb_put(hu->rx_skb, 1);
        hu->rx_state = H4_RX_DATA;
        hu->rx_len = hci_h4_len(byte);
        break;

    case H4_RX_DATA:
        skb_put_data(hu->rx_skb, &byte, 1);
        hu->rx_len--;
        if (hu->rx_len == 0) {
            hci_recv_frame(hu->hdev, hu->rx_skb);
            hu->rx_state = H4_RX_TYPE;
        }
        break;
    }
}

避坑指南:我曾经在UART驱动中忘了处理 hci_recv_frame 的调用上下文。这个函数不能在中断上下文直接调用,否则会触发内核警告。正确的做法是用工作队列或者tasklet把数据提交推迟到进程上下文。

UART驱动的另一个关键点是波特率。蓝牙芯片通常支持多种波特率,但默认一般是115200。我建议你在驱动初始化时先以默认波特率通信,然后通过HCI命令切换到更高速度,比如921600或2M。这样能显著提升数据传输效率。

4.3 USB蓝牙驱动框架

USB蓝牙驱动比UART要复杂一些,但框架更清晰。USB蓝牙设备通常是一个复合设备,包含一个控制端点(用于HCI命令和事件)和两个或三个批量端点(用于ACL和SCO数据)。

USB蓝牙驱动的核心结构体是 struct hci_usb

struct hci_usb {
    struct hci_dev *hdev;
    struct usb_device *udev;
    struct usb_interface *intf;

    struct usb_endpoint_descriptor *bulk_tx_ep;
    struct usb_endpoint_descriptor *bulk_rx_ep;
    struct usb_endpoint_descriptor *intr_ep;

    struct urb *bulk_tx_urb;
    struct urb *bulk_rx_urb;
    struct urb *intr_urb;
};

USB驱动的初始化流程大致是:

  1. 探测USB设备,获取端点描述符
  2. 分配并初始化 struct hci_usb
  3. 分配并提交接收URB(批量端点和中断端点)
  4. 注册HCI设备

发送数据时,USB驱动需要把HCI数据包封装成USB批量传输。我习惯这样写:

static int usb_send(struct hci_dev *hdev, struct sk_buff *skb)
{
    struct hci_usb *husb = hci_get_drvdata(hdev);
    struct urb *urb;
    int ret;

    urb = usb_alloc_urb(0, GFP_ATOMIC);
    if (!urb) {
        kfree_skb(skb);
        return -ENOMEM;
    }

    usb_fill_bulk_urb(urb, husb->udev,
                      usb_sndbulkpipe(husb->udev,
                                      husb->bulk_tx_ep->bEndpointAddress),
                      skb->data, skb->len,
                      usb_tx_complete, skb);

    ret = usb_submit_urb(urb, GFP_ATOMIC);
    if (ret) {
        kfree_skb(skb);
        usb_free_urb(urb);
    }

    return ret;
}

注意:USB蓝牙驱动中,URB的提交和完成回调都可能在中断上下文执行。不要在回调函数中做耗时操作,比如内存分配或加锁。我见过一个驱动在URB完成回调里调用了 printk,结果导致USB子系统死锁。

接收数据时,USB驱动需要提前提交接收URB。当蓝牙芯片有数据发送时,USB主机会自动触发URB完成回调。在回调中,我们提取数据并调用 hci_recv_frame

static void usb_rx_complete(struct urb *urb)
{
    struct hci_dev *hdev = urb->context;
    struct sk_buff *skb;

    if (urb->status == 0) {
        skb = bt_skb_alloc(urb->actual_length, GFP_ATOMIC);
        if (skb) {
            skb_put_data(skb, urb->transfer_buffer,
                         urb->actual_length);
            hci_recv_frame(hdev, skb);
        }
    }

    // 重新提交URB,继续接收
    usb_submit_urb(urb, GFP_ATOMIC);
}

USB蓝牙驱动还有一个常见问题:设备热插拔。当USB蓝牙适配器被拔出时,驱动需要优雅地清理所有URB和HCI设备。我建议在 disconnect 回调中先调用 hci_unregister_dev,再释放URB,顺序不能反。

总结一下:UART蓝牙驱动适合嵌入式设备,成本低但速度受限;USB蓝牙驱动适合PC和高端嵌入式平台,速度快但实现复杂。无论哪种方式,HCI传输层的核心目标只有一个——可靠、高效地搬运数据包。

嗯,这一章的内容就到这里。下一章我们会深入HCI核心层,看看数据包是怎么被解析和路由的。到时候我会分享一个我调试蓝牙耳机时遇到的经典问题,保证让你印象深刻。