2、蓝牙驱动框架:Linux蓝牙子系统架构、BlueZ协议栈核心组件

好,咱们进入第二章。这一章我打算聊聊蓝牙驱动框架的骨架——Linux蓝牙子系统和BlueZ协议栈。说实话,很多做蓝牙开发的工程师,干了三五年可能都没把整个框架串起来。我个人习惯是,先看架构图,再动手调代码。这样出了问题,你才知道该去哪个模块找原因。

2.1 Linux蓝牙子系统整体架构

Linux下的蓝牙子系统,说白了就是一套分层设计。从最底层的硬件,到最上层的应用,中间隔了好几层。我画个简化的分层图给你看:

+---------------------+
|   Bluetooth Apps    |  ← bluez工具、obexd、gatt工具等
+---------------------+
|   BlueZ Protocol    |  ← hci、l2cap、rfcomm、sdp、gap、gatt
+---------------------+
|   Bluetooth Core    |  ← 内核中的蓝牙核心层 (net/bluetooth/)
+---------------------+
|   HCI UART/USB/...  |  ← 硬件接口驱动 (drivers/bluetooth/)
+---------------------+
|   Bluetooth Chip    |  ← 实际硬件 (BCM、QCA、Intel、Realtek等)
+---------------------+

嗯,这里要注意:内核里的蓝牙核心层,它不直接管硬件。它只负责协议栈的调度、socket接口、以及各种管理。真正跟硬件打交道的,是底下的HCI驱动。

2.2 BlueZ协议栈核心组件

BlueZ是Linux官方的蓝牙协议栈。从2.4内核开始就内置了。我最早接触BlueZ是在2009年,那时候还是BlueZ 4.x,后来5.x重构了很多东西。你想想看,一个协议栈能活这么久,说明它的设计确实经得起考验。

2.2.1 HCI层(Host Controller Interface)

HCI是蓝牙的核心接口。它定义了主机和控制器之间的通信协议。HCI有三种传输方式:

  • UART:最常见,尤其是嵌入式设备。我遇到过很多坑,比如波特率不匹配、流控没开,导致数据错乱。
  • USB:PC上用的多,插个USB蓝牙适配器就是走这个。
  • SDIO/SPI:一些WiFi+蓝牙 combo 芯片会用。

HCI的数据包类型就四种:

类型用途长度
Command主机发命令给控制器3字节头 + 数据
Event控制器回复事件给主机2字节头 + 数据
ACL Data普通数据通道(如音频、文件传输)4字节头 + 数据
SCO/eSCO Data同步数据通道(如语音通话)3字节头 + 数据

小技巧:调试HCI层时,我习惯用 btmonhcidump 抓包。有一次客户说蓝牙连不上,我抓了HCI日志一看,发现是Controller返回了Command Disallowed,原来是芯片还在初始化状态。这种问题,看代码是看不出来的,必须看HCI交互。

2.2.2 L2CAP层(Logical Link Control and Adaptation Protocol)

L2CAP在HCI上面。它负责把上层的数据拆成HCI能处理的包,也负责多路复用。说白了,它就是个适配层。

L2CAP有几个关键概念:

  • CID(Channel Identifier):每个L2CAP通道有个唯一ID,类似TCP的端口号。
  • MTU(Maximum Transmission Unit):最大传输单元。这个值协商不对,会导致数据传不过去。我踩过坑,两个设备MTU不一致,结果传文件总是断。
  • Segmentation & Reassembly:大包拆小包,接收端再拼回去。

2.2.3 RFCOMM层

RFCOMM模拟串口。很多老设备(比如蓝牙打印机、GPS模块)都用它。它基于L2CAP,提供串口仿真。

RFCOMM的通道号范围是1到30。我记得有一次调试一个蓝牙串口模块,发现通道号配错了,两边对不上,折腾了半天。

2.2.4 SDP层(Service Discovery Protocol)

SDP用来发现对方设备支持哪些服务。比如你的手机支持A2DP(蓝牙音频),还是支持HFP(免提),都是通过SDP查出来的。

SDP的核心是服务记录(Service Record)。每个服务记录包含:

  • Service Class ID(服务类型)
  • Protocol Descriptor(协议栈信息)
  • Attribute列表(各种属性)

避坑指南:我曾经遇到一个问题,手机连不上车载蓝牙。查了半天,发现是车载的SDP服务记录里,A2DP的协议描述符写错了。这种问题,用 sdptool browse 命令一看就露馅。

2.2.5 GAP层(Generic Access Profile)

GAP定义了蓝牙设备如何发现、连接、广播。它分两种角色:

  • Central(主机):主动扫描、发起连接。
  • Peripheral(从机):广播自己、等待连接。

GAP还定义了广播数据格式。广播包最多31字节,里面可以塞设备名、UUID、厂商自定义数据等。嗯,这里要注意:广播包不能太大,否则会被截断。

2.2.6 GATT层(Generic Attribute Profile)

GATT是BLE(低功耗蓝牙)的核心。它定义了数据如何组织、如何读写。GATT把数据组织成Service和Characteristic:

  • Service:一组功能的集合。比如「电池服务」包含电量百分比。
  • Characteristic:具体的数据点。比如电量百分比这个值,就是一个Characteristic。
  • Descriptor:对Characteristic的描述,比如单位、范围。

GATT的操作有四种:Read、Write、Notify、Indicate。Notify和Indicate的区别在于,Indicate需要对方确认收到。

重点:GATT是BLE开发中最常打交道的层。我建议你熟练掌握 gatttoolbluetoothctl 的GATT命令。很多蓝牙设备的问题,其实就是GATT读写流程不对。

2.3 内核中的蓝牙核心代码结构

Linux内核的蓝牙代码在 net/bluetooth/ 目录下。我列几个关键文件:

net/bluetooth/
├── af_bluetooth.c    # 蓝牙socket层,提供AF_BLUETOOTH协议族
├── hci_core.c        # HCI核心,管理HCI设备、连接、命令
├── hci_conn.c        # HCI连接管理
├── hci_event.c       # HCI事件处理
├── l2cap_core.c      # L2CAP协议实现
├── l2cap_sock.c      # L2CAP socket接口
├── rfcomm/           # RFCOMM协议
├── sco.c             # SCO/eSCO协议
├── mgmt.c            # 管理接口(供用户空间bluez使用)
└── smp.c             # 安全管理协议(配对、加密)

我个人习惯是,遇到蓝牙问题,先看 hci_event.c。因为所有的事件都在这里处理。比如连接断开、配对完成、命令超时,都能在这里找到线索。

2.4 用户空间与内核的交互

BlueZ的用户空间部分通过socket与内核通信。主要有两种socket:

  • HCI socket:直接操作HCI命令和事件。调试时用 hcitool 就是走这个。
  • Management socket:通过 mgmt 接口管理蓝牙适配器。比如打开蓝牙、扫描、配对。

你想想看,为什么要有两套?因为HCI socket太底层了,很多操作需要自己拼命令。Management socket封装了常见操作,方便上层调用。

调试技巧:如果你想看内核和BlueZ的交互细节,可以打开内核的蓝牙调试日志:

echo 'file net/bluetooth/* +p' > /sys/kernel/debug/dynamic_debug/control

然后看 dmesg。我曾经靠这个日志,定位到一个HCI命令超时的问题,原因是芯片固件卡住了。

2.5 总结

这一章我们聊了Linux蓝牙子系统的分层架构,以及BlueZ的六大核心组件:HCI、L2CAP、RFCOMM、SDP、GAP、GATT。每个组件都有自己的职责,但最终都服务于一个目标——让蓝牙设备能稳定、高效地通信。

下一章,我会带你手把手搭建一个蓝牙驱动的调试环境。包括怎么编译内核、怎么打补丁、怎么用逻辑分析仪抓HCI数据。嗯,那才是真正动手的时候。