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层时,我习惯用 btmon 或 hcidump 抓包。有一次客户说蓝牙连不上,我抓了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开发中最常打交道的层。我建议你熟练掌握 gatttool 或 bluetoothctl 的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数据。嗯,那才是真正动手的时候。