第三章 协议栈源码结构解析:HCI层、L2CAP层、SM层、ATT/GATT层、GAP层的代码位置与核心文件说明

好,咱们直接进入正题。上一章我们把协议栈的整体框架搭起来了,这一章要动真格的了——把每一层对应的源码文件扒出来看看。说实话,我第一次接触蓝牙协议栈源码时,面对几百个文件也是一脸懵。后来摸清了门道,发现其实每层就那么几个核心文件。

我个人习惯把协议栈想象成一栋楼,HCI是地基,L2CAP是承重墙,SM是安保系统,ATT/GATT是房间里的插座和开关,GAP则是整栋楼的大门和前台。咱们一层层拆开看。

3.1 HCI层:协议栈的"翻译官"

HCI层,全称Host Controller Interface。说白了,它就是Host和Controller之间的翻译官。Host跑在CPU上,Controller是蓝牙芯片,两者通过HCI通信。

核心文件位置(以Zephyr为例):

  • subsys/bluetooth/host/hci_core.c — HCI核心逻辑,命令发送、事件处理都在这里
  • subsys/bluetooth/host/hci_ecc.c — 加密相关的HCI命令,比如LE Secure Connections
  • include/bluetooth/hci.h — HCI命令、事件、数据包格式的定义
  • include/bluetooth/hci_vs.h — 厂商自定义的HCI命令(这个坑我踩过)

嗯,这里要注意。HCI层最容易出问题的地方是命令和事件的匹配。你发一个命令出去,Controller会回一个Command Complete或者Command Status事件。我曾经在项目中遇到一个bug,发出去的连接命令一直没收到Complete事件,查了两天才发现是HCI命令包的参数长度填错了。

我的小技巧:调试HCI层时,建议先打开HCI日志。Zephyr里配置CONFIG_BT_HCI_RAW_LOG=y,就能看到裸的HCI数据包。这比看应用层日志直观多了。

3.2 L2CAP层:数据的分包与重组

L2CAP,Logical Link Control and Adaptation Protocol。名字很长,功能其实就两个:把上层的大数据包拆成小片发给Controller,再把Controller收到的小片拼回大数据包给上层。

你想想看,蓝牙LE一次最多发251个字节,但应用层可能一次要发1KB的数据。没有L2CAP,这事就干不了。

文件 作用
subsys/bluetooth/host/l2cap.c L2CAP核心实现,包括连接建立、通道管理、分段重组
subsys/bluetooth/host/l2cap_br.c BR/EDR(经典蓝牙)的L2CAP实现,LE用不到
include/bluetooth/l2cap.h L2CAP通道ID、配置参数、API声明

我记得有一次做音频传输项目,数据总是断断续续的。后来发现是L2CAP的MTU(最大传输单元)协商出了问题。两端MTU不匹配,数据包被截断了。从那以后,我每次初始化L2CAP都会显式调用bt_l2cap_chan_connect()时指定MTU,而不是用默认值。

避坑指南:我曾经遇到过L2CAP重传超时导致断连的问题。默认的L2CAP重传超时是40秒,在某些实时性要求高的场景下太长了。建议根据业务需求调整CONFIG_BT_L2CAP_RTX_TIMEOUT

3.3 SM层:蓝牙安全的"守门员"

SM,Security Manager。这一层负责配对、绑定、加密。说白了,就是确保你的蓝牙设备不会被别人偷听或冒充。

SM层的代码量不大,但逻辑复杂。尤其是配对过程中的密钥生成和交换,涉及很多加密算法。

  • subsys/bluetooth/host/smp.c — SM核心实现,包括配对流程、密钥分发
  • subsys/bluetooth/host/smp_null.c — 空实现,用于不需要安全功能的场景
  • include/bluetooth/smp.h — 配对能力、IO能力、密钥大小的定义

为什么SM层容易出问题?因为配对过程是个状态机,而且两端的状态必须同步。我曾经在调试一个智能锁项目时,手机和锁总是配对失败。打开日志一看,手机发起了Just Works配对,但锁端配置的是Passkey Entry。两边IO能力不匹配,自然配不上。

关键配置项:

  • CONFIG_BT_SMP=y — 开启SM支持
  • CONFIG_BT_SMP_SC_ONLY=y — 只允许LE Secure Connections(推荐)
  • CONFIG_BT_BONDABLE=y — 允许绑定,记住配对信息

3.4 ATT/GATT层:数据的"高速公路"

ATT,Attribute Protocol。GATT,Generic Attribute Profile。这两个经常一起说,因为GATT是在ATT之上定义的规范。

ATT负责数据的读写操作,GATT则定义了数据应该怎么组织——用Service、Characteristic、Descriptor这种层级结构。

文件 作用
subsys/bluetooth/host/att.c ATT协议实现,处理Read、Write、Notify、Indicate等操作
subsys/bluetooth/host/gatt.c GATT层实现,管理Service注册、属性表、CCC配置
include/bluetooth/att.h ATT操作码、错误码定义
include/bluetooth/gatt.h GATT API,包括bt_gatt_service_register()

我个人觉得,GATT层是协议栈里最需要理解透彻的一层。因为大部分蓝牙应用开发,本质上就是在操作GATT的Service和Characteristic。

举个例子,你要做一个心率监测设备。需要注册一个Heart Rate Service,里面包含Heart Rate Measurement这个Characteristic。代码大概长这样:

// 定义心率测量Characteristic
BT_GATT_SERVICE_DEFINE(hrs_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_HRS),
    BT_GATT_CHARACTERISTIC(BT_UUID_HRS_MEASUREMENT,
                           BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ,
                           NULL, read_hr_measurement, NULL),
    BT_GATT_CCC(hrs_ccc_cfg_changed),
);

嗯,这里要注意CCC(Client Characteristic Configuration)的配置。CCC决定了设备是否允许发送通知。我见过很多新手忘记注册CCC回调,导致手机收不到数据。

3.5 GAP层:蓝牙设备的"社交礼仪"

GAP,Generic Access Profile。它定义了蓝牙设备如何被发现、如何建立连接、以及连接后的角色管理。

说白了,GAP就是蓝牙设备的社交礼仪——怎么打招呼(广播)、怎么交换名片(扫描)、怎么握手(连接)。

  • subsys/bluetooth/host/gap.c — GAP核心实现,广播、扫描、连接管理
  • subsys/bluetooth/host/conn.c — 连接管理,包括连接参数更新、断开处理
  • include/bluetooth/gap.h — GAP角色定义、广播类型、连接间隔等

GAP层有个容易忽略的点——广播间隔和连接间隔的配合。广播间隔太短,功耗高;太长,设备发现慢。连接间隔太小,带宽高但功耗大;太大,延迟高。

我的经验值:对于低功耗设备,广播间隔建议设在100ms到500ms之间。连接间隔则根据数据量来,如果只是传传感器数据,30ms到50ms就够用了。我曾经在一个穿戴设备项目里把连接间隔设成了7.5ms,结果电池一天就耗光了。

3.6 各层之间的协作关系

说了这么多,你可能觉得各层是独立的。其实不是,它们之间通过回调函数和消息队列紧密协作。

举个例子,当手机发来一个GATT读请求:

  1. Controller收到数据包,通过HCI上报给Host
  2. HCI层解析事件类型,发现是ACL数据,交给L2CAP
  3. L2CAP根据通道ID,判断是ATT通道,交给ATT层
  4. ATT层解析操作码,发现是Read Request,交给GATT层
  5. GATT层查找属性表,找到对应的Characteristic,调用注册的读回调函数

你看,一个简单的读操作,经过了5层。每一层都只做自己的事,然后通过接口传给下一层。这就是分层设计的好处——各层独立,便于移植和定制。

总结一下核心文件清单:

层级 核心源文件 核心头文件
HCI hci_core.c, hci_ecc.c hci.h, hci_vs.h
L2CAP l2cap.c l2cap.h
SM smp.c smp.h
ATT att.c att.h
GATT gatt.c gatt.h
GAP gap.c, conn.c gap.h

下一章,我们会动手搭建一个最小化的协议栈工程,把这些文件组织起来,让代码真正跑起来。到时候你会发现,理解了这些文件的位置和作用,移植工作其实没那么可怕。