【BlueZ 】hci 模块:用户态 HCI 层的核心封装与消息处理

在 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_devlib/hci.c L1049-L1080),它完成两步工作:

  1. socket(AF_BLUETOOTH, SOCK_RAW|SOCK_CLOEXEC, BTPROTO_HCI) 创建 HCI 原始套接字。

  2. 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_devHCI_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_PUBLICBDADDR_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,它 在同一个调用中完成

  1. 设置 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));
  1. 发送指令:hci_send_cmd(dd, r->ogf, r->ocf, r->clen, r->cparam)

  2. 循环 `poll` + `read`:最多尝试 10 次,每次超时时间递减。

  3. 事件分发 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 匹配。

  1. 恢复原始 filter,退出。

3.4 BLE 与经典蓝牙的 OGF 差异

|--------------------------|-------------------------------------------------------|
| OGF | 含义 |
| OGF_LINK_CTL (0x01) | 经典蓝牙 Link Control(Create ConnectionDisconnect 等) |
| 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_enablehci_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(含 bdaddrhandlepscan_rep_mode 等经典字段) | evt_le_conn_complete(含 roleintervallatencysupervision_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_timeoutsrc/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_connectionEVT_CONN_COMPLETE | hci_le_create_connEVT_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 套接字后,数据读写通过 GIOChannelread/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/HCIDEVRESETHCISETAUTH/HCISETENCRYPTHCIBLOCKADDR/HCIUNBLOCKADDR 等。

7.3 MGMT 协议报文结构

src/shared/mgmt.ccan_read_data 按此解析,hdr = mgmt->bufevent = btohs(hdr->opcode)index = btohs(hdr->index)length = btohs(hdr->len)

八、与 adapter/device 模块的联动

8.1 adapter 初始化链路

src/adapter.c 中,典型流程:

  1. mgmt_new_default() 创建 MGMT 会话;

  2. mgmt_register(mgmt, MGMT_EV_INDEX_ADDED, HCI_DEV_NONE, ...) 订阅控制器热插拔;

  3. mgmt_send(mgmt, MGMT_OP_GET_ADAPTER_INFO, ...) 读取适配器信息;

  4. mgmt_send(mgmt, MGMT_OP_SET_POWERED, ...) 上电;

  5. 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.cprofiles/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 初始化最佳实践

  1. 优先使用 MGMT 协议 (mgmt_new_default) 而非 hci_open_dev;

  2. 在 adapter 完整初始化完成前,不要 触发 profile 层的 bt_io_connect,否则会因底层 HCI 未就绪导致 connect 立即被拒;

  3. 对多控制器系统,通过 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.chci_send_reqhdr->evt 分支------增加 bt_log 打印每个事件号;

  • src/shared/mgmt.ccan_read_data/request_complete------打印 opcode、index、status 可快速定位命令阻塞;

  • btio.cconnect_cb------打印 SO_ERROR,区分"连接超时"和"远端主动拒绝"。

11.4 性能调优

  • 高频指令(如连接更新)使用 MGMT 异步接口,避免 hci_send_reqpoll 阻塞;

  • 批量命令(如 Set Event Mask Page 2 一次设置多类事件)可减少 MGMT 往返次数;

  • 对时间敏感的 BLE 扫描,将 scan interval/window 调整到最优,并在扫描期间 避免 发送非必要 MGMT 命令。

11.5 交叉编译注意事项

lib/hci.c 依赖 linux/bt.hsys/ioctl.h 等系统头。在交叉编译 ARM 时:

  • 确保目标 Linux 内核头文件(linux/bluetooth.hlinux/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 蓝牙栈中 最底层、最关键、也最容易出错 的部分。掌握它需要同时理解:

  1. 内核 HCI 协议 的包格式、事件语义、状态机;

  2. Linux Socket 编程 的 filter、poll、ioctl 细节;

  3. 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 的事件分支 → 追踪回调链是否被上层吞没。


相关推荐
计算机编程-吉哥1 小时前
脑肿瘤MRI智能识别系统:基于深度学习的像素级脑肿瘤语义分割平台【计算机毕业设计选题推荐】
人工智能·python·深度学习·算法·毕业设计·课程设计·大数据毕业设计选题推荐
阿里云大数据AI技术1 小时前
Al Search x ES Agent Builder:让数据活起来,从搜索走向行动
人工智能·elasticsearch·agent
Cenxi1 小时前
Python字符串方法练习手册
人工智能·python
摘星星的屋顶1 小时前
2026年8月31日~2026年9月13日周报
人工智能·学习
数字新视界1 小时前
动环监控可视化技术在机房管理智能化中的实际应用剖析
大数据·人工智能·数据中心·微模块机房·模块化机房
武子康2 小时前
小智的 MQTT 已连接,为什么还不能说话?从音频通道看协议选择
人工智能·llm·agent
智塑未来2 小时前
FPGA开发板选型:研发交付能力怎么看
人工智能·fpga开发
朴实赋能2 小时前
AI拒答机制实战:心理咨询危机识别Agent三层防护、五级分层与六类智能体拆解
大数据·人工智能·多agent协同·心理咨询ai·心理危机识别·ai拒答机制·隐私匿名化
史一试2 小时前
Agent开发第6步:实现 SSE 编解码
人工智能