在 BlueZ 5.x 整体架构中,HCI(Host Controller Interface)层是连接 内核蓝牙协议栈 与 用户态蓝牙守护进程 bluetoothd 的核心枢纽。它向下通过 HCI Socket 与 Linux 内核 net/bluetooth/ 下的 HCI 协议栈交互,向上为 adapter、device、profile等模块提供统一的指令发送/事件接收/数据收发能力。
目录
[一、HCI 模块总体架构与文件组织](#一、HCI 模块总体架构与文件组织)
[二、HCI 套接字管理:从hci_open_dev到 mgmt_new_default](#二、HCI 套接字管理:从hci_open_dev到 mgmt_new_default)
[三、HCI 指令封装:从`ogf/ocf` 到字节流](#三、HCI 指令封装:从ogf/ocf 到字节流)
[六、数据报文收发:ACL / SCO / ISO](#六、数据报文收发:ACL / SCO / ISO)
[七、用户态 HCI 与内核的交互机制](#七、用户态 HCI 与内核的交互机制)
[八、与 adapter/device 模块的联动](#八、与 adapter/device 模块的联动)
许多工程师在移植 BlueZ 或调试蓝牙问题时,会困惑于以下几个典型现象:
HCI 指令 发出后为何没有响应? ------ 阻塞/非阻塞 I/O 模式、poll超时、socket filter 设置是关键。
设备扫描到一半突然停止? ------ 指令状态机、Command Status 与 Command Complete 事件的差异化处理、LE Meta Event 子事件分发是重点。
为什么 5.x 之后推荐走 MGMT 协议而不是直接 hci_open_dev? ------ 两套 API 在同步/异步模型、队列机制、超时处理上存在本质差异。
经典蓝牙 (BR/ EDR ) 与 BLE 在 HCI 层面到底差在哪里? ------ OGF/OCF 编码、事件类型、数据包格式、Meta Event 处理皆有不同。
本文以 BlueZ 5.87 源码 为蓝本,聚焦以下三套核心实现:
|-------------------------------------------|-------------------|-----------------------------------------------|
| 模块 | 源文件 | 角色 |
| lib/hci.c + lib/hci_lib.h | 传统 HCI 工具库 | 同步指令收发、hci_open_dev/hci_send_req 等阻塞式 API |
| src/shared/mgmt.c + src/shared/mgmt.h | 现代 MGMT 协议封装 | 异步命令队列、事件注册、通知分发,供 bluetoothd 使用 |
| btio/btio.c + btio/btio.h | GLib 主循环上的 I/O 封装 | L2CAP/RFCOMM/SCO 套接字的 connect/listen 异步操作 |
一、 HCI 模块总体架构与文件组织
1.1 三层架构视图

-
lib/hci.c:最底层的即时同步 API,工具层(hciconfig、hcitool)大量使用。调用者自己维护 hci_filter、自己 poll 等待响应。
-
src/shared/mgmt.c:更上层的异步队列 API,bluetoothd 主流程使用。内部维护 request_queue/pending_list/notify_list 三套队列。
-
btio/btio.c :面向 GLib 主循环的套接字封装,不直接处理 HCI Command/Event,而是专注于 L2CAP/RFCOMM/SCO 数据通道 的 connect/listen/accept。
1.2 核心数据结构一览
(1) struct hci_request ------ 同步请求描述符(lib/hci_lib.h)
cpp
struct hci_request {
uint16_t ogf; /* 指令组 (OGF),如 OGF_LINK_CTL / OGF_LE_CTL */
uint16_t ocf; /* 指令码 (OCF) */
int event; /* 期望接收的事件类型,如 EVT_CMD_COMPLETE */
void *cparam; /* 指令参数 */
int clen; /* 参数长度 */
void *rparam; /* 响应缓冲区 */
int rlen; /* 响应缓冲区大小 */
};
(2) struct mgmt ------ MGMT 会话上下文( src /shared/mgmt.c)
cpp
struct mgmt {
int ref_count;
int fd; /* HCI socket fd */
bool close_on_unref;
struct io *io; /* 基于 fd 的 GLib/ell I/O 抽象 */
bool writer_active;
struct queue *request_queue; /* 待发送请求队列 */
struct queue *reply_queue; /* 高优先级回复队列 */
struct queue *pending_list; /* 已发送、等待响应的请求 */
struct queue *notify_list; /* 事件/通知注册列表 */
unsigned int next_request_id;
unsigned int next_notify_id;
bool need_notify_cleanup;
bool in_notify;
void *buf;
uint16_t len;
uint16_t mtu;
/* 可选的 debug 回调 */
mgmt_debug_func_t debug_callback;
mgmt_destroy_func_t debug_destroy;
void *debug_data;
};
(3) struct mgmt_request / struct mgmt_notify
cpp
struct mgmt_request {
struct mgmt *mgmt;
unsigned int id;
uint16_t opcode; /* MGMT opcode(不是 HCI opcode) */
uint16_t index; /* 控制器 index (hci0=0, ...) */
void *buf;
uint16_t len;
mgmt_request_func_t callback; /* 异步响应回调 */
mgmt_destroy_func_t destroy;
void *user_data;
int timeout; /* 超时秒数,0=无限等待 */
unsigned int timeout_id; /* timeout_add_seconds 返回的定时器 id */
};
struct mgmt_notify {
unsigned int id;
uint16_t event; /* 订阅的 MGMT 事件号 */
uint16_t index; /* 订阅的控制器 index */
bool removed;
mgmt_notify_func_t callback;
mgmt_destroy_func_t destroy;
void *user_data;
};
(4) struct set_opts(btio 层套接字选项集合)
cpp
struct set_opts {
bdaddr_t src, dst;
BtIOType type; /* BT_IO_L2CAP / BT_IO_RFCOMM / BT_IO_SCO */
uint8_t src_type, dst_type;
int defer;
int sec_level;
uint8_t channel;
uint16_t psm, cid;
uint16_t mtu, imtu, omtu;
int central;
uint8_t mode;
int flushable;
uint32_t priority;
uint16_t voice;
};
二、HCI 套接字管理:从hci_open_dev到 mgmt_new_default
2.1 传统路径:hci_open_dev
传统 HCI 访问的起点是 hci_open_dev(lib/hci.c L1049-L1080),它完成两步工作:
-
socket(AF_BLUETOOTH, SOCK_RAW|SOCK_CLOEXEC, BTPROTO_HCI)创建 HCI 原始套接字。 -
bind()到指定hci_dev,将套接字与具体适配器绑定。
cpp
int hci_open_dev(int dev_id)
{
struct sockaddr_hci a;
int dd, err;
if (dev_id < 0) { errno = ENODEV; return -1; }
dd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
if (dd < 0) return dd;
memset(&a, 0, sizeof(a));
a.hci_family = AF_BLUETOOTH;
a.hci_dev = dev_id; /* 绑定到 hciX */
if (bind(dd, (struct sockaddr *) &a, sizeof(a)) < 0)
goto failed;
return dd;
}
关键点:
-
SOCK_RAW 决定了用户态直接面对的是 原始 HCI 报文(包类型 + header + payload)。
-
hci_dev为HCI_DEV_NONE (0xFFFF)时绑定的是通用 HCI,可用于发送 MGMT 协议报文。
2.2 现代路径:mgmt_new_default
bluetoothd 默认采用 MGMT 协议 与内核交互(src/shared/mgmt.c)。它的套接字构造方式与传统路径不同:
cpp
struct mgmt *mgmt_new_default(void)
{
struct sockaddr_hci addr;
int fd;
fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,
BTPROTO_HCI);
if (fd < 0) return NULL;
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = HCI_DEV_NONE; /* 不绑定具体适配器 */
addr.hci_channel = HCI_CHANNEL_CONTROL; /* ← 关键:控制通道 */
if (bind(fd, &addr.common, sizeof(addr.hci)) < 0) {
close(fd);
return NULL;
}
mgmt = mgmt_new(fd);
/* ... */
mgmt->close_on_unref = true;
return mgmt;
}
差异对比:
|-------------|------------------------------|------------------------------------------------|
| 对比项 | hci_open_dev | mgmt_new_default |
| socket type | SOCK_RAW | SOCK_CLOEXEC | SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK |
| 绑定对象 | hci_dev = dev_id | hci_dev = HCI_DEV_NONE + channel = CONTROL |
| 工作模式 | 阻塞式,调用者自己 poll | 非阻塞,配合 io_set_read_handler 进入主循环 |
| 报文协议 | 直接 HCI Command/Event/ACL/SCO | MGMT 协议(封装在 HCI 报文之上) |
| 适用场景 | 工具 (hciconfig)、简单脚本 | bluetoothd 主流程、复杂应用 |
2.3 btio 层套接字:bt_io_connect / bt_io_listen
btio 层不直接打开 HCI socket,而是在上层建立 L2CAP/RFCOMM/SCO 数据通道。以 bt_io_connect 为例(btio/btio.c)
cpp
/* 省略参数解析,仅展示核心连接流程 */
static int l2cap_connect(int sock, const bdaddr_t *dst, uint8_t dst_type,
uint16_t psm, uint16_t cid)
{
struct sockaddr_l2 addr;
memset(&addr, 0, sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, dst);
if (cid)
addr.l2_cid = htobs(cid); /* BLE 用 CID */
else
addr.l2_psm = htobs(psm); /* 经典蓝牙用 PSM */
addr.l2_bdaddr_type = dst_type;
err = connect(sock, (struct sockaddr *) &addr, sizeof(addr));
if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS))
return -errno;
return 0;
}
调试要点:
连接失败若
errno = EINPROGRESS,说明在等待远端响应------此时在 GLib 主循环上g_io_add_watch监听G_IO_OUT即可。
dst_type决定地址解析方式:BDADDR_BREDR(经典)、BDADDR_LE_PUBLIC、BDADDR_LE_RANDOM。
三、HCI 指令封装:从`ogf/ocf` 到字节流
3.1 HCI Command 包格式

type = HCI_COMMAND_PKT (0x01)。
3.2hci_send_cmd:最简发送实现
lib/hci.c 展示了一条指令如何被打包:
cpp
int hci_send_cmd(int dd, uint16_t ogf, uint16_t ocf, uint8_t plen, void *param)
{
uint8_t type = HCI_COMMAND_PKT;
hci_command_hdr hc;
struct iovec iv[3];
int ivn;
hc.opcode = htobs(cmd_opcode_pack(ogf, ocf)); /* OGF<<10 | OCF */
hc.plen = plen;
iv[0].iov_base = &type; iv[0].iov_len = 1;
iv[1].iov_base = &hc; iv[1].iov_len = HCI_COMMAND_HDR_SIZE;
ivn = 2;
if (plen) {
iv[2].iov_base = param;
iv[2].iov_len = plen;
ivn = 3;
}
while (writev(dd, iv, ivn) < 0) {
if (errno == EAGAIN || errno == EINTR)
continue;
return -1;
}
return 0;
}
要点:
-
cmd_opcode_pack(ogf, ocf)宏将 OGF 左移 10 位与 OCF 组合为 16-bit opcode。 -
使用
writev分散写,减少一次系统调用。 -
只有
EAGAIN/EINTR会重试,其他错误直接返回。
3.3hci_send_req:同步"发-等-收"模型
hci_send_req(lib/hci.c)是工具层使用最频繁的 API,它 在同一个调用中完成:
- 设置 socket filter,只放行与本指令相关的事件:
cpp
hci_filter_clear(&nf);
hci_filter_set_ptype(HCI_EVENT_PKT, &nf);
hci_filter_set_event(EVT_CMD_STATUS, &nf);
hci_filter_set_event(EVT_CMD_COMPLETE, &nf);
hci_filter_set_event(EVT_LE_META_EVENT, &nf); /* BLE 必备 */
hci_filter_set_event(r->event, &nf); /* 调用者指定 */
hci_filter_set_opcode(opcode, &nf);
setsockopt(dd, SOL_HCI, HCI_FILTER, &nf, sizeof(nf));
-
发送指令:hci_send_cmd(dd, r->ogf, r->ocf, r->clen, r->cparam)
-
循环 `poll` + `read`:最多尝试 10 次,每次超时时间递减。
-
事件分发 switch:
-
EVT_CMD_STATUS:指令已接受(早期回执),若status != 0立即返回错误。 -
EVT_CMD_COMPLETE:指令执行完成,ptr += EVT_CMD_COMPLETE_SIZE跳过 status+opcode,拷贝剩余参数。 -
EVT_REMOTE_NAME_REQ_COMPLETE:特殊 case,用cparam里的 BD_ADDR 匹配。 -
EVT_LE_META_EVENT:BLE 核心,me->subevent必须与r->event匹配。
- 恢复原始 filter,退出。
3.4 BLE 与经典蓝牙的 OGF 差异
|--------------------------|-------------------------------------------------------|
| OGF | 含义 |
| OGF_LINK_CTL (0x01) | 经典蓝牙 Link Control(Create Connection、Disconnect 等) |
| OGF_LINK_POLICY (0x02) | 经典蓝牙 Link Policy |
| OGF_HOST_CTL (0x03) | 经典蓝牙 Host Controller |
| OGF_INFO_PARAM (0x04) | 版本、特性读取(经典/BLE 通用) |
| OGF_LE_CTL (0x08) | BLE 专属:LE Set Scan Enable、LE Create Connection等 |
因此 hci_le_set_scan_enable、hci_le_create_conn 等函数(lib/hci_lib.h L105-L116)都走 OGF_LE_CTL,这是区分经典/BLE HCI 指令的关键。
四、事件上报与消息分发机制
4.1 内核 → 用户态事件流
HCI socket 上可读的包类型:
cpp
#define HCI_COMMAND_PKT 0x01 /* 主机→控制器 */
#define HCI_ACLDATA_PKT 0x02
#define HCI_SCODATA_PKT 0x03
#define HCI_EVENT_PKT 0x04 /* 控制器→主机 */
#define HCI_ISODATA_PKT 0x05 /* BLE ISO */
#define HCI_VENDOR_PKT 0xff
Event 包结构:type(1) + evt(1) + plen(1) + parameters
常用事件号(节选):
-
EVT_CMD_STATUS (0x0F)、EVT_CMD_COMPLETE (0x0E) -
EVT_CONN_COMPLETE (0x03)、EVT_DISCONN_COMPLETE (0x05) -
EVT_LE_META_EVENT (0x3E)← BLE 所有 LE 子事件的"容器" -
EVT_LE_CONN_COMPLETE (0x3E + 0x01)、EVT_LE_ADVERTISING_REPORT (0x3E + 0x02)等子事件
4.2 MGMT 层事件分发核心:can_read_data
src/shared/mgmt.c 的 can_read_data 是 MGMT 的读回调。它实现了完整的 报文解析 → 命令响应匹配 → 通知回调 链路。
步骤 1:读取并校验报文头
cpp
bytes_read = read(mgmt->fd, mgmt->buf, mgmt->len);
if (bytes_read < MGMT_HDR_SIZE) return true; /* 半包,等下次 */
hdr = mgmt->buf;
event = btohs(hdr->opcode); /* MGMT header 的 opcode 字段实际承载事件/命令号 */
index = btohs(hdr->index);
length = btohs(hdr->len);
if (bytes_read < length + MGMT_HDR_SIZE)
return true; /* 报文未收齐 */
步骤 2:按事件类型分发
cpp
switch (event) {
case MGMT_EV_CMD_COMPLETE:
cc = mgmt->buf + MGMT_HDR_SIZE;
opcode = btohs(cc->opcode);
request_complete(mgmt, cc->status, opcode, index,
length - 3, mgmt->buf + MGMT_HDR_SIZE + 3);
break;
case MGMT_EV_CMD_STATUS: /* ... */ break;
default:
process_notify(mgmt, event, index, length - MGMT_HDR_SIZE,
mgmt->buf + MGMT_HDR_SIZE);
break;
}
步骤 3:request_complete ------ 请求/响应匹配
cpp
static void request_complete(struct mgmt *mgmt, uint8_t status,
uint16_t opcode, uint16_t index,
uint16_t length, const void *param)
{
struct opcode_index match = { .opcode = opcode, .index = index };
struct mgmt_request *request;
/* 先按 opcode+index 精确匹配 */
request = queue_remove_if(mgmt->pending_list,
match_request_opcode_index, &match);
/* 找不到就降级为仅按 index 匹配 */
if (!request)
request = queue_remove_if(mgmt->pending_list,
match_request_index,
UINT_TO_PTR(index));
if (request) {
if (request->callback)
request->callback(status, length, param, request->user_data);
destroy_request(request);
}
wakeup_writer(mgmt);
}
步骤 4:process_notify ------ 订阅式事件推送
cpp
static void process_notify(struct mgmt *mgmt, uint16_t event, uint16_t index,
uint16_t length, const void *param)
{
struct event_index match = { .event = event, .index = index,
.length = length, .param = param };
mgmt->in_notify = true;
queue_foreach(mgmt->notify_list, notify_handler, &match);
mgmt->in_notify = false;
/* 清理被标记 removed 的 notify */
if (mgmt->need_notify_cleanup) {
queue_remove_all(mgmt->notify_list, match_notify_removed,
NULL, destroy_notify);
mgmt->need_notify_cleanup = false;
}
}
4.3 经典 vs BLE 事件分发差异
|--------|-----------------------------------------------------------------|----------------------------------------------------------------------------------------|
| 维度 | 经典蓝牙 | BLE |
| 连接完成事件 | EVT_CONN_COMPLETE (0x03) | EVT_LE_CONN_COMPLETE(嵌套在 EVT_LE_META_EVENT 0x3E 下) |
| 扫描结果 | EVT_INQUIRY_RESULT (0x02) | EVT_LE_ADVERTISING_REPORT(LE Meta Event 子事件) |
| 过滤规则 | hci_filter_set_event 直接匹配 | 需同时设置 EVT_LE_META_EVENT,再在应用层解析 subevent |
| 参数结构 | evt_conn_complete(含 bdaddr、handle、pscan_rep_mode 等经典字段) | evt_le_conn_complete(含 role、interval、latency、supervision_timeout 等 BLE 专属字段) |
五、异步回调与指令队列机制
5.1 MGMT 写队列的三级优先级
src/shared/mgmt.c 的发送路径采用 reply_queue → request_queue 的两级队列(reply 优先),并用 can_write_data 作为可写判断函数:
cpp
static bool can_write_data(struct io *io, void *user_data)
{
struct mgmt *mgmt = user_data;
struct mgmt_request *request;
bool can_write;
/* 高优先级:先取 reply_queue */
request = queue_pop_head(mgmt->reply_queue);
if (!request) {
if (!queue_isempty(mgmt->pending_list))
return false; /* 有未完成请求时暂缓发送 */
request = queue_pop_head(mgmt->request_queue);
if (!request) return false;
can_write = false;
} else {
can_write = !queue_isempty(mgmt->reply_queue);
}
if (!send_request(mgmt, request))
return true;
return can_write;
}
设计意图:
-
reply(通常是对 Event 的立即应答)不应被普通命令阻塞;
-
pending_list非空时,普通请求应等待,避免命令乱序。
5.2 mgmt_send`完整链路
cpp
unsigned int mgmt_send(struct mgmt *mgmt, uint16_t opcode, uint16_t index,
uint16_t length, const void *param,
mgmt_request_func_t callback,
void *user_data, mgmt_destroy_func_t destroy)
{
struct mgmt_request *request;
/* 构造 request:mgmt、id、opcode、index、buf、callback、timeout=0 */
request = new0(struct mgmt_request, 1);
request->mgmt = mgmt;
request->id = mgmt->next_request_id++;
request->opcode = opcode;
request->index = index;
request->buf = memcpy(malloc(length + MGMT_HDR_SIZE + 3),
&hdr, MGMT_HDR_SIZE + 3) + MGMT_HDR_SIZE + 3;
/* ... */
/* 先入 request_queue,等可写时由 can_write_data 弹出发送 */
queue_push_tail(mgmt->request_queue, request);
wakeup_writer(mgmt);
return request->id;
}
5.3 超时重传/超时回调逻辑
request_timeout(src/shared/mgmt.c )在定时器到期时触发:
cpp
static bool request_timeout(void *data)
{
struct mgmt_request *request = data;
request->timeout_id = 0;
/* 从 pending_list 中摘掉 */
queue_remove_if(request->mgmt->pending_list, NULL, request);
if (request->callback)
request->callback(MGMT_STATUS_TIMEOUT, 0, NULL, request->user_data);
destroy_request(request);
return false;
}
注意 :BlueZ 5.x 的 MGMT 层 并没有实现自动重传,超时只会向上层报告 MGMT_STATUS_TIMEOUT。应用层(adapter/device 模块)需要自己决定是否重试。这是一个容易踩坑的设计决策------盲目重发可能触发控制器状态错乱。
5.4 mgmt_register:事件订阅与回调
cpp
unsigned int mgmt_register(struct mgmt *mgmt, uint16_t event, uint16_t index,
mgmt_notify_func_t callback,
void *user_data, mgmt_destroy_func_t destroy)
{
struct mgmt_notify *notify;
notify = new0(struct mgmt_notify, 1);
notify->id = mgmt->next_notify_id++;
notify->event = event;
notify->index = index;
notify->callback = callback;
/* ... */
queue_push_tail(mgmt->notify_list, notify);
return notify->id;
}
notify_handler 在事件到达时遍历 notify_list 并按 event + index 匹配:
cpp
static void notify_handler(void *data, void *user_data)
{
struct mgmt_notify *notify = data;
struct event_index *match = user_data;
if (notify->removed) return;
if (notify->event != match->event) return;
if (notify->index != match->index && notify->index != MGMT_INDEX_NONE)
return;
if (notify->callback)
notify->callback(match->index, match->length, match->param,
notify->user_data);
}
调试要点:
-
若回调不触发,先用
mgmt_register返回的 id 确认注册未被mgmt_unregister提前撤销; -
MGMT_INDEX_NONE表示"订阅所有适配器",notify->index != MGMT_INDEX_NONE时才做严格匹配。
六、数据报文收发:ACL / SCO / ISO
6.1 数据通道类型回顾
cpp
#define HCI_COMMAND_PKT 0x01
#define HCI_ACLDATA_PKT 0x02 /* 经典 ACL,异步无连接 */
#define HCI_SCODATA_PKT 0x03 /* 经典 SCO,同步语音 */
#define HCI_EVENT_PKT 0x04
#define HCI_ISODATA_PKT 0x05 /* BLE ISO(5.2+,ISO 通道) */
6.2 ACL 数据包格式

-
handle:连接句柄(12 bit),由EVT_CONN_COMPLETE/EVT_LE_CONN_COMPLETE事件返回。 -
flags:包含Packet Status Flag(0=完整包,1=分段包首包,2=分段包续包)、Broadcast Flag等。
6.3 经典 vs BLE 数据路径差异
|--------|-----------------------------------------------|-----------------------------------------------|
| 维度 | 经典 ACL | BLE ACL(LE ACL) |
| 建立方式 | hci_create_connection → EVT_CONN_COMPLETE | hci_le_create_conn → EVT_LE_CONN_COMPLETE |
| 数据类型标识 | HCI_ACLDATA_PKT(与 BLE ACL 共用) | 同样 HCI_ACLDATA_PKT,但 handle 来自 LE 事件 |
| SCO 语音 | 走 HCI_SCODATA_PKT | BLE Audio(ISO)走 HCI_ISODATA_PKT,5.2+ |
| 流控 | 基于 Host Number Of Completed Packets | 同样基于 completed packets,但 LL 层机制不同 |
6.4 btio 层的实际数据收发
btio.c 不直接面向 HCI,它建立 L2CAP/RFCOMM/SCO 套接字后,数据读写通过 GIOChannel 的 read/write 完成。典型的读取回调(在 server_cb 中可见):
cpp
static gboolean server_cb(GIOChannel *io, GIOCondition cond, gpointer user_data)
{
struct server *server = user_data;
int srv_sock = g_io_channel_unix_get_fd(io);
int cli_sock = accept(srv_sock, NULL, NULL);
if (cli_sock < 0) return TRUE; /* 继续监听 */
GIOChannel *cli_io = g_io_channel_unix_new(cli_sock);
g_io_channel_set_close_on_unref(cli_io, TRUE);
g_io_channel_set_flags(cli_io, G_IO_FLAG_NONBLOCK, NULL);
if (server->confirm)
server->confirm(cli_io, server->user_data); /* 询问是否接受 */
else
server->connect(cli_io, NULL, server->user_data);
g_io_channel_unref(cli_io);
return TRUE;
}
实战要点:
-
SOCK_SEQPACKET(RFCOMM/SCO)保证报文边界; -
SOCK_STREAM(L2CAP 部分场景)需要应用层自行拆包; -
POLLNVAL检查(check_nval)用于快速识别套接字异常。
七、用户态 HCI 与内核的交互机制
7.1 HCI Socket 三种通道
Linux 内核在 HCI Socket 上定义了三种 channel:
|---------------------------|-----------------------------|
| Channel | 用途 |
| HCI_CHANNEL_RAW (0) | 传统原始 HCI,hci_open_dev 使用 |
| HCI_CHANNEL_CONTROL (1) | MGMT 协议控制通道,bluetoothd 使用 |
| HCI_CHANNEL_MONITOR (2) | 只读监控,btmon 使用 |
7.2 ioctl 路径
lib/hci.c 中大量使用 ioctl 与内核交互:
cpp
ioctl(sk, HCIGETDEVLIST, (void *) dl); /* 枚举所有 HCI 设备 */
ioctl(sk, HCIGETDEVINFO, (void *) di); /* 查询单个设备详情 */
ioctl(dd, HCIINQUIRY, (unsigned long) buf); /* 同步 Inquiry */
常用 ioctl:HCIDEVUP/HCIDEVDOWN/HCIDEVRESET、HCISETAUTH/HCISETENCRYPT、HCIBLOCKADDR/HCIUNBLOCKADDR 等。
7.3 MGMT 协议报文结构

src/shared/mgmt.c 的 can_read_data 按此解析,hdr = mgmt->buf,event = btohs(hdr->opcode),index = btohs(hdr->index),length = btohs(hdr->len)。
八、与 adapter/device 模块的联动
8.1 adapter 初始化链路
在 src/adapter.c 中,典型流程:
mgmt_new_default()创建 MGMT 会话;
mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, HCI_DEV_NONE, ...)订阅控制器热插拔;
mgmt_send(mgmt, MGMT_OP_GET_ADAPTER_INFO, ...)读取适配器信息;
mgmt_send(mgmt, MGMT_OP_SET_POWERED, ...)上电;
mgmt_send(mgmt, MGMT_OP_SET_LE, ...)开启 LE。
关键点 :所有操作都是 异步 的,通过回调串起状态机。这与工具层的 `hci_send_req` 完全不同。
8.2 device 模块对 HCI 的依赖
src/device.c 中:
扫描 :通过
mgmt_register(MGMT_EV_LE_ADVERTISING_REPORT, ...)或经典 Inquiry 结果通知获取设备;连接 :
mgmt_send(MGMT_OP_LE_CREATE_CONN, ...),回调里拿到MGMT_EV_LE_CONN_COMPLETE;配对 :
mgmt_send(MGMT_OP_PAIR_DEVICE, ...),回调里处理MGMT_EV_PAIR_COMPLETE。
8.3 BTIO 在 profile 层的应用
profiles/audio/a2dp.c、profiles/input/hog.c 等在建立 L2CAP/RFCOMM 通道时会调用 bt_io_connect/bt_io_listen,从而进入 GLib 主循环驱动的数据路径。
九、异常处理机制与常见坑
9.1hci_send_req 的 filter 污染
hci_send_req 会临时修改 socket filter。如果在多线程/多任务环境中调用,必须确保同一 fd 上没有其他调用者,否则会出现:
-
自己发的指令被其他线程覆盖 filter;
-
收到的事件被其他指令消耗。
修复:优先使用 mgmt 异步接口,或自己在 hci_send_req 外增加互斥锁。
9.2 poll 超时与 EAGAIN 处理
cpp
while ((n = poll(&p, 1, to)) < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
goto failed;
}
if (!n) { errno = ETIMEDOUT; goto failed; }
坑点:若控制器响应超过 to 毫秒,将返回 ETIMEDOUT,此时 hci_filter 可能已被污染------failed 路径会尝试恢复 filter,但恢复失败会残留错误 filter。
9.3 MGMT 超时后必须重试的场景
-
MGMT_STATUS_TIMEOUT发生时,控制器可能已经执行了命令但响应包丢失; -
应用层应先通过
Get Flags/List Adapters等幂等命令确认控制器状态,再决定是否重试; -
切勿盲目重发写类命令(如 Set Powered、Set LE),可能导致状态机紊乱。
9.4 经典/BLE 指令混用的错误
-
经典蓝牙扫描用
OGF_LINK_CTL / OCF_INQUIRY(0x01/0x01); -
BLE 扫描用
OGF_LE_CTL / OCF_LE_SET_SCAN_ENABLE(0x08/0x0C); -
两者不可混用。错误代码会返回
HCI_STATUS_UNKNOWN_HCI_COMMAND。
9.5 EVT_LE_META_EVENT子事件漏处理
hci_send_req 的默认 filter 包含 EVT_LE_META_EVENT,但如果你又用 hci_filter_set_event(r->event, &nf) 设置了具体子事件,仍然要通过 me->subevent != r->event 过滤------漏了这步会导致接收到其他 LE 子事件时错误匹配。
十、核心函数调用链路速查
10.1 同步指令路径(工具/简单场景)
cpp
hci_open_dev(dev_id) // lib/hci.c L1049
│
▼
hci_send_req(dd, req, timeout) // lib/hci.c L1120
│
├── hci_filter_set_* // 设置过滤
├── hci_send_cmd // lib/hci.c L1090,写 socket
├── poll + read 循环
└── switch(evt) 匹配事件 → memcpy 到 rparam
10.2 异步 MGMT 路径(bluetoothd 主流程)
cpp
mgmt_new_default() // src/shared/mgmt.c L491
│
├── socket + bind(HCI_CHANNEL_CONTROL)
├── io_set_read_handler(can_read_data)
│
▼
mgmt_send(mgmt, opcode, ...) // L?
│
├── 构造 mgmt_request
├── queue_push_tail(request_queue)
└── wakeup_writer → io_set_write_handler(can_write_data)
│
▼
can_write_data 弹出请求 → send_request
│
├── io_send(mgmt->io, &iov, 1)
├── timeout_add_seconds → request_timeout
└── queue_push_tail(pending_list)
│
▼
can_read_data 收到报文
│
├── MGMT_EV_CMD_COMPLETE → request_complete → callback
├── MGMT_EV_CMD_STATUS → request_complete(仅状态)
└── 其他事件 → process_notify → notify_handler
10.3 btio 数据通道路径
cpp
bt_io_connect(connect_cb, ..., BT_IO_OPT_DEST, dst,
BT_IO_OPT_PSM, psm, BT_IO_OPT_SOURCE, src, ...)
│
├── bt_io_accept_connect → l2cap_connect / rfcomm_connect / sco_connect
├── g_io_add_watch(io, G_IO_OUT, connect_cb, ...)
└── GLib 主循环触发 connect_cb(io, cond, user_data)
十一、实战开发与调试要点
11.1 初始化最佳实践
-
优先使用 MGMT 协议 (mgmt_new_default) 而非 hci_open_dev;
-
在 adapter 完整初始化完成前,不要 触发 profile 层的 bt_io_connect,否则会因底层 HCI 未就绪导致 connect 立即被拒;
-
对多控制器系统,通过
index参数明确区分hci0/hci1,避免误操作。
11.2 调试工具链
|------------------------------------|------------------------------------|
| 工具 | 用途 |
| hciconfig hciX | 查询/设置 HCI 参数(走 ioctl) |
| hcitool cmd <ogf> <ocf> [params] | 发送任意 HCI 指令 |
| btmon -d hciX | 抓 HCI 报文(使用 HCI_CHANNEL_MONITOR) |
| btmgmt info | MGMT 协议查询(基于 MGMT socket) |
| bluetoothctl monitor | 观察 bluetoothd 的 D-Bus 与事件 |
11.3 关键日志点
-
lib/hci.c中hci_send_req的hdr->evt分支------增加bt_log打印每个事件号; -
src/shared/mgmt.c的can_read_data/request_complete------打印 opcode、index、status 可快速定位命令阻塞; -
btio.c的connect_cb------打印SO_ERROR,区分"连接超时"和"远端主动拒绝"。
11.4 性能调优
-
高频指令(如连接更新)使用 MGMT 异步接口,避免
hci_send_req的poll阻塞; -
批量命令(如
Set Event Mask Page 2一次设置多类事件)可减少 MGMT 往返次数; -
对时间敏感的 BLE 扫描,将 scan interval/window 调整到最优,并在扫描期间 避免 发送非必要 MGMT 命令。
11.5 交叉编译注意事项
lib/hci.c 依赖 linux/bt.h、sys/ioctl.h 等系统头。在交叉编译 ARM 时:
-
确保目标 Linux 内核头文件(
linux/bluetooth.h、linux/hci.h)在 sysroot 中可用; -
lib/hci.c不需要蓝牙相关库(纯 socket 调用),可单独交叉编译。
附录:核心函数索引
|--------------------------|---------------------|--------------------------|
| 函数 | 文件 | 作用 |
| hci_open_dev | lib/hci.c | 打开并绑定 HCI socket |
| hci_send_cmd | lib/hci.c | 同步发送单条 HCI Command |
| hci_send_req | lib/hci.c | 同步发-收完整请求 |
| hci_create_connection | lib/hci.c | 经典蓝牙建连封装 |
| hci_disconnect | lib/hci.c | 经典断连封装 |
| hci_le_set_scan_enable | lib/hci.c | BLE 扫描开关封装 |
| hci_le_create_conn | lib/hci.c | BLE 建连封装 |
| mgmt_new_default | src/shared/mgmt.c | 创建默认 MGMT 会话 |
| mgmt_send | src/shared/mgmt.c | 异步发送 MGMT 请求 |
| mgmt_register | src/shared/mgmt.c | 注册 MGMT 事件通知 |
| request_complete | src/shared/mgmt.c | 响应匹配与回调 |
| process_notify | src/shared/mgmt.c | 事件订阅分发 |
| request_timeout | src/shared/mgmt.c | 超时处理 |
| can_write_data | src/shared/mgmt.c | 写队列调度 |
| can_read_data | src/shared/mgmt.c | 读回调与解析 |
| bt_io_connect | btio/btio.c | 异步建立 L2CAP/RFCOMM/SCO 连接 |
| bt_io_listen | btio/btio.c | 异步监听入站连接 |
| l2cap_connect | btio/btio.c | L2CAP connect 封装 |
| server_cb | btio/btio.c | 监听 accept 回调 |
结语
BlueZ 的 HCI 用户态封装是 Linux 蓝牙栈中 最底层、最关键、也最容易出错 的部分。掌握它需要同时理解:
内核 HCI 协议 的包格式、事件语义、状态机;
Linux Socket 编程 的 filter、poll、ioctl 细节;
BlueZ 自身的分层设计:工具层(lib/hci.c)→ 协议层(src/shared/mgmt.c)→ 数据通道层(btio/btio.c)→ 业务层(adapter/device/profile)。
本文以 5.87 版本源码为蓝本,重点拆解了 套接字管理、指令封装、事件分发、异步队列、超时机制、经典/BLE 差异 六大核心话题,并给出了大量实战调试要点。结合btmon、btmgmt、hciconfig 等工具交叉验证,可有效定位从"指令不执行"到"事件不回调"的各类问题。
> 下次遇到 HCI 类问题时的建议排查路径:btmon抓包 → 确认指令是否发出 → 确认是否收到对应事件 → 对应 lib/hci.c 或 src/shared/mgmt.c 的事件分支 → 追踪回调链是否被上层吞没。