蓝牙驱动基础:协议栈概览、HCI接口、驱动框架入门

各位同学,咱们今天聊聊蓝牙驱动的入门知识。说实话,蓝牙这玩意儿看着简单,真上手做驱动时,坑可不少。我最早接触蓝牙驱动是在一个智能穿戴项目上,当时被协议栈的层次搞得晕头转向。后来摸透了,才发现核心就三块:协议栈怎么分层、HCI接口怎么通信、驱动框架怎么搭。

蓝牙协议栈概览

蓝牙协议栈,说白了就是一套通信规则的集合。它不像WiFi那样简单粗暴,蓝牙分了两个大流派:经典蓝牙(BR/EDR)和低功耗蓝牙(BLE)。我个人习惯把协议栈想象成一座楼,从下往上分三层:

  • 控制器层(Controller):负责射频、基带、链路管理。这是硬件干的活,驱动工程师主要跟它打交道。
  • 主机层(Host):包括L2CAP、SDP、GATT等协议。这部分跑在CPU上,是软件要实现的。
  • 应用层(Application):Profile层,比如HFP(免提)、A2DP(音频)、HID(键盘鼠标)。

你想想看,为什么这么分?因为蓝牙设计之初就考虑到了灵活性。控制器和主机之间通过HCI接口隔开,这样硬件厂商可以专心做射频,软件厂商可以专心写协议。我在项目中遇到过一种情况:某款芯片的控制器只支持BLE,但客户非要跑经典蓝牙的A2DP。嗯,这就尴尬了,只能换方案。

核心要点:蓝牙协议栈的分层设计,让驱动开发可以聚焦在HCI接口这一层。上层协议你不需要全懂,但HCI通信必须吃透。

HCI接口详解

HCI(Host Controller Interface)是主机和控制器之间的通信桥梁。它定义了三种数据传输方式:

传输类型 用途 典型场景
命令(Command) 主机下发指令给控制器 扫描、连接、配对
事件(Event) 控制器上报状态给主机 连接完成、断开、数据到达
数据(Data) 双向传输用户数据 音频流、文件传输

HCI的物理层可以是UART、USB、SDIO等。我最早做的一个项目用的是UART HCI,波特率设到921600,结果丢包严重。后来发现是硬件流控没开,RTS/CTS信号没接对。避坑指南:UART HCI一定要开硬件流控,否则高速传输时必丢数据。

HCI命令包的格式很简单:

typedef struct {
    uint16_t opcode;    // 操作码,高6位是OGF,低10位是OCF
    uint8_t  length;    // 参数长度
    uint8_t  params[];  // 参数数据
} hci_command_packet_t;

举个例子,发送扫描命令:

// 构造HCI命令:LE Set Scan Enable
uint8_t cmd[] = {
    0x01, 0x0C,  // Opcode: OGF=0x01, OCF=0x0C
    0x02,        // 参数长度
    0x01,        // 扫描使能
    0x00         // 过滤策略
};
// 通过UART发送
uart_write(hci_fd, cmd, sizeof(cmd));

为什么opcode要拆成OGF和OCF?因为OGF(Opcode Group Field)用来分组,比如链路控制命令、策略命令等。这样控制器可以快速路由到对应的处理模块。我记得有一次调试,发现命令没响应,查了半天是opcode写反了字节序。嗯,小细节,但能卡你一天。

驱动框架入门

Linux内核里的蓝牙驱动框架,核心是struct hci_dev这个结构体。它代表一个蓝牙控制器,驱动要做的事情就是注册这个结构体,并实现它的回调函数。

先看一个最简单的驱动骨架:

#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>

static int my_hci_open(struct hci_dev *hdev)
{
    // 打开硬件接口(比如UART)
    return 0;
}

static int my_hci_close(struct hci_dev *hdev)
{
    // 关闭硬件接口
    return 0;
}

static int my_hci_send(struct hci_dev *hdev, struct sk_buff *skb)
{
    // 发送HCI数据包到硬件
    // 注意:这里要处理DMA传输
    return 0;
}

static int my_hci_probe(struct platform_device *pdev)
{
    struct hci_dev *hdev;

    hdev = hci_alloc_dev();
    if (!hdev)
        return -ENOMEM;

    hdev->bus = HCI_UART;
    hdev->open  = my_hci_open;
    hdev->close = my_hci_close;
    hdev->send  = my_hci_send;

    // 设置设备地址、厂商信息等
    bacpy(&hdev->bdaddr, BDADDR_ANY);

    return hci_register_dev(hdev);
}

这段代码看着简单,但实际项目中要处理的事情多得多。比如:

  • 中断处理:数据到达时怎么通知上层?
  • DMA传输:大数据量时怎么避免CPU拷贝?
  • 电源管理:蓝牙休眠时怎么保存上下文?

我曾经在一个项目里,蓝牙驱动跑着跑着就死机了。查了三天,发现是中断处理函数里调用了hci_recv_frame,但这个函数会加锁,而发送路径也加了同一把锁——死锁了。解决方案是用工作队列把接收处理延后执行。

个人经验:蓝牙驱动的中断处理函数里,尽量只做最轻量的事——把数据搬到一个缓冲区,然后触发一个工作队列或tasklet。真正的协议解析和上报,放到进程上下文去做。否则你可能会遇到各种诡异的锁问题。

HCI事件处理流程

当控制器有事件上报时(比如连接完成),驱动需要把数据从硬件读出来,然后通过hci_recv_frame交给协议栈。流程如下:

  1. 硬件产生中断,通知CPU有数据可读
  2. 中断处理函数读取HCI事件包
  3. 调用hci_recv_frame将数据送入协议栈
  4. 协议栈解析事件,触发相应的回调

这里有个关键点:HCI事件包的长度是变化的,第一个字节是事件码,第二个字节是参数长度。所以读取时要先读2字节头,再根据长度读剩余数据。我见过有人直接读固定长度,结果事件包解析全乱了。

注意:HCI事件包的最大长度是255字节。如果你的硬件缓冲区不够大,记得做分片处理。否则事件包被截断,协议栈会直接丢弃。

总结与展望

蓝牙驱动的基础,说白了就是理解HCI协议,然后把它挂到Linux的hci_dev框架上。你不需要把整个蓝牙协议栈背下来,但HCI命令和事件的交互流程必须烂熟于心。

下一章我们会深入中断处理,讲讲怎么用DMA来加速HCI数据传输。到时候我会分享一个真实案例:某款芯片的UART FIFO只有16字节,不用DMA的话,高速传输时CPU被中断淹没了。嗯,那场面,想想都头疼。

今天就到这儿。记住:驱动开发,七分在硬件,三分在软件。先把硬件接口调通,再谈优化。