3、蓝牙核心数据结构:hci_dev结构体、hci_conn结构体、hci_cb回调机制
好,咱们今天来啃三块硬骨头。这三块东西,说白了就是蓝牙驱动的骨架和血脉。你搞懂了它们,写驱动的时候心里就有底了。我当年刚接触蓝牙协议栈时,对着这些结构体看了三天,才慢慢摸出门道来。
3.1 hci_dev:蓝牙控制器的“身份证”
hci_dev 这个结构体,你可以把它理解成蓝牙控制器的完整档案。每个蓝牙芯片,无论是 USB 的还是 UART 的,在内核里都会对应一个 hci_dev 实例。
它长什么样?我挑几个最关键的字段给你看:
struct hci_dev {
struct list_head list; // 链表节点,所有hci_dev串在一起
struct mutex lock; // 互斥锁,保护结构体不被乱改
char name[8]; // 设备名,比如"hci0"
struct device *parent; // 父设备,指向实际的硬件设备
__u8 bus; // 总线类型:USB、UART、SDIO等
__u8 dev_type; // 设备类型:BR/EDR还是LE
bdaddr_t bdaddr; // 蓝牙地址,每个设备唯一的MAC
__u8 dev_name[248]; // 本地设备名称
__u8 features[8]; // 支持的特性集
__u16 manufacturer; // 制造商ID
__u8 hci_ver; // HCI规范版本
__u16 hci_rev; // HCI修订版本
__u8 lmp_ver; // LMP版本
__u16 lmp_subver; // LMP子版本
// 发送和接收队列
struct sk_buff_head rx_q; // 接收队列
struct sk_buff_head raw_q; // 原始数据队列
struct sk_buff_head cmd_q; // 命令队列
// 连接管理
struct list_head conn_list; // 所有连接的链表
__u16 conn_hash[16]; // 连接哈希表,快速查找
// 回调函数
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);
};
核心要点:每个蓝牙控制器在内核中只有一个 hci_dev 实例。它贯穿了整个蓝牙驱动的生命周期——从注册、初始化、运行到卸载。
我记得有一次调试一个 USB 蓝牙适配器,死活注册不上。后来发现是 open 回调里没有正确初始化 USB 端点。嗯,这种问题排查起来特别费劲,因为错误信息往往很模糊。
3.2 hci_conn:连接的“户口本”
如果说 hci_dev 是控制器本身,那 hci_conn 就是控制器上建立的每一条连接。一个蓝牙耳机连上来,就会创建一个 hci_conn。
来看看它的核心字段:
struct hci_conn {
struct list_head list; // 链表节点,挂在hci_dev的conn_list上
struct hci_dev *hdev; // 所属的控制器
bdaddr_t dst; // 对端设备地址
__u8 dst_type; // 地址类型:公开地址还是随机地址
__u16 handle; // 连接句柄,唯一标识这条连接
__u16 state; // 连接状态:OPEN、CLOSED、CONNECTING等
__u8 link_mode; // 链路模式
__u8 enc_key_size; // 加密密钥长度
__u8 key_type; // 密钥类型
// ACL连接相关
__u16 pkt_type; // 包类型
__u16 tx_len; // 发送缓冲区长度
struct sk_buff_head data_q; // 数据队列
// SCO/eSCO连接相关
__u16 sco_tx_len; // SCO发送长度
__u8 sco_mode; // SCO模式
// 定时器和回调
struct timer_list disc_timer; // 断开连接定时器
struct timer_list idle_timer; // 空闲定时器
struct work_struct work; // 工作队列
// 连接建立回调
void (*connect_cfm_cb)(struct hci_conn *conn, __u8 status);
void (*security_cfm_cb)(struct hci_conn *conn, __u8 status);
void (*disconn_cfm_cb)(struct hci_conn *conn, __u8 reason);
};
个人经验:我建议你在调试连接问题时,先打印 handle 和 state 这两个字段。它们能告诉你连接是否真的建立起来了,以及当前处于什么阶段。我曾经花了一整天查一个断连问题,最后发现是 disc_timer 超时时间设得太短了。
你想想看,一个蓝牙耳机同时连手机和电脑,那就会有两个 hci_conn 实例,分别挂在各自控制器的 conn_list 上。每个连接都有自己的状态机,从 CONNECTING 到 OPEN,再到 DISCONNECTING。
3.3 hci_cb:回调机制的“总控开关”
hci_cb 是个全局结构体,它定义了一组回调函数。这些回调是协议栈和驱动之间的桥梁。当底层硬件有事件发生时,驱动通过回调通知上层协议栈。
struct hci_cb {
struct list_head list;
char *name;
// 连接相关回调
void (*connect_cfm)(struct hci_conn *conn, __u8 status);
void (*disconn_cfm)(struct hci_conn *conn, __u8 reason);
// 安全相关回调
void (*security_cfm)(struct hci_conn *conn, __u8 status, __u8 encrypt);
// 密钥相关回调
void (*key_change_cfm)(struct hci_conn *conn, __u8 status);
// 角色切换回调
void (*role_switch_cfm)(struct hci_conn *conn, __u8 status, __u8 role);
};
避坑指南:我曾经在注册回调时忘记检查 hci_cbconnect_cfm 回调,导致连接建立时回调被调用了两次,状态机直接乱掉了。所以,注册回调前一定要检查是否已被占用。
回调机制的工作流程其实很简单:
- 上层协议栈(比如 L2CAP、SMP)调用
hci_register_cb()注册回调 - 底层驱动在事件发生时(比如收到连接完成事件),调用对应的回调函数
- 回调函数里,协议栈根据事件类型做进一步处理
说白了,这就是个观察者模式。驱动是事件源,协议栈是观察者。事件来了,驱动喊一嗓子,协议栈们各自处理。
3.4 三者如何协同工作?
我画个简单的流程给你看:
// 1. 驱动初始化时,创建hci_dev
struct hci_dev *hdev = hci_alloc_dev();
hdev->bus = HCI_USB;
hdev->open = usb_open;
hdev->close = usb_close;
hdev->send = usb_send;
// 2. 注册到协议栈
hci_register_dev(hdev);
// 3. 当有设备连接时,创建hci_conn
struct hci_conn *conn = hci_conn_add(hdev, ACL_LINK, &bdaddr, ACL_LINK);
conn->state = BT_CONNECTED;
// 4. 连接建立后,触发回调
if (hdev->connect_cfm_cb)
hdev->connect_cfm_cb(conn, 0x00);
// 5. 协议栈收到回调,做进一步处理
void l2cap_connect_cfm(struct hci_conn *conn, __u8 status) {
if (status == 0x00) {
// 连接成功,开始L2CAP层处理
l2cap_conn_add(conn);
}
}
关键理解:hci_dev 是控制器,hci_conn 是连接,hci_cb 是通知机制。三者缺一不可,构成了蓝牙驱动的核心骨架。
我个人习惯在调试时,把这三个结构体的关键字段打印出来。比如:
// 调试打印示例
printk("hci_dev: name=%s, bus=%d, state=%d\n",
hdev->name, hdev->bus, hdev->dev_type);
printk("hci_conn: handle=0x%04x, state=%d, dst=%pMR\n",
conn->handle, conn->state, &conn->dst);
printk("hci_cb: connect_cfm=%p, disconn_cfm=%p\n",
hdev->connect_cfm_cb, hdev->disconn_cfm_cb);
嗯,今天就先讲到这里。这三个结构体你得多看几遍源码,最好自己动手写个简单的驱动,注册一个 hci_dev,然后看看它怎么和协议栈交互。下一章我们讲蓝牙驱动的初始化流程,到时候你会看到这些结构体是怎么被创建和串联起来的。
课后练习:去内核源码里找到 net/bluetooth/hci_core.c,看看 hci_register_dev() 函数是怎么初始化 hci_dev 的。重点关注 conn_list 和 hci_cb 的初始化过程。