梳理一下linux下蓝牙框架的数据流
下行数据流
下行数据流指的是从应用程序到蓝牙硬件的数据路径。我将从用户空间的最上层开始,逐层向下剖析。
🏗️ 整体下行架构
应用层 (bluetoothctl / 自定义应用) ↓ D-Bus 调用 bluetoothd (BlueZ 守护进程) ↓ HCI Socket (sendto) 内核 HCI Socket 层 (hci_sock.c) ↓ HCI 核心层 (hci_core.c) ↓ 命令队列 驱动层 (btusb.c / hci_uart.c) ↓ USB URB / UART 帧 蓝牙硬件
📡 第一层:用户空间应用
1.1 bluetoothctl 示例
bluetoothctl [bluetooth]# scan on当用户输入
scan on时,bluetoothctl 的代码流程:
// client/main.c - bluetoothctl 源码简化 static void cmd_scan(int argc, char *argv[]) { // 1. 获取 D-Bus 连接 DBusConnection *conn = get_dbus_connection(); // 2. 构造 D-Bus 方法调用 DBusMessage *msg = dbus_message_new_method_call( "org.bluez", // 服务名 "/org/bluez/hci0", // 对象路径 "org.bluez.Adapter1", // 接口 "StartDiscovery" // 方法名 ); // 3. 发送 D-Bus 调用 dbus_connection_send(conn, msg, NULL); }1.2 直接使用 HCI Socket 的应用
有些应用(如
hcitool)会绕过 D-Bus,直接使用 HCI Socket:
// 直接打开 HCI Socket 的示例 int sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); struct sockaddr_hci addr = { .hci_family = AF_BLUETOOTH, .hci_dev = 0, // hci0 .hci_channel = HCI_CHANNEL_USER }; bind(sk, (struct sockaddr *)&addr, sizeof(addr)); // 构造 HCI 命令:LE Set Scan Enable struct { struct hci_command_hdr hdr; uint8_t enable; uint8_t filter_dup; } cmd = { .hdr = { .opcode = htobs(0x200C), .plen = 2 }, // HCI_LE_Set_Scan_Enable .enable = 0x01, // 开启扫描 .filter_dup = 0x00 }; // 发送命令 send(sk, &cmd, sizeof(cmd), 0);
🔌 第二层:bluetoothd (BlueZ 守护进程)
bluetoothd 接收 D-Bus 调用,进行策略检查后转换为 HCI 命令。
// src/adapter.c - bluetoothd 源码简化 static DBusMessage *start_discovery(DBusConnection *conn, DBusMessage *msg, void *user_data) { struct btd_adapter *adapter = user_data; // 1. 权限检查 if (!check_permission(msg)) { return dbus_message_new_error(msg, "org.bluez.Error.NotAuthorized", NULL); } // 2. 状态检查 if (adapter->discovering) { return dbus_message_new_error(msg, "org.bluez.Error.InProgress", NULL); } // 3. 调用内核接口 if (adapter_start_discovery(adapter) < 0) { return dbus_message_new_error(msg, "org.bluez.Error.Failed", NULL); } adapter->discovering = TRUE; return dbus_message_new_method_return(msg); } // 实际发送 HCI 命令到内核 static int adapter_start_discovery(struct btd_adapter *adapter) { // 通过 HCI Socket 发送命令 struct hci_request rq = { .ogf = OGF_LE_CTL, .ocf = OCF_LE_SET_SCAN_ENABLE, .cparam = &scan_enable, .clen = sizeof(scan_enable) }; return hci_send_req(adapter->hdev, &rq, NULL); }
🎯 第三层:HCI Socket 层 (hci_sock.c)
内核中的 HCI Socket 层负责接收用户空间的数据。
// net/bluetooth/hci_sock.c static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; struct hci_dev *hdev; struct sk_buff *skb; struct hci_filter *filter; // 1. 从用户空间拷贝数据到内核 skb = bt_skb_sendmsg(sock, msg, len); if (IS_ERR(skb)) return PTR_ERR(skb); // 2. 获取 HCI 设备 hdev = hci_sock_dev(sk); if (!hdev) { kfree_skb(skb); return -EBADFD; } // 3. 设置包类型(命令/数据/事件) bt_cb(skb)->pkt_type = *((unsigned char *)skb->data); skb_pull(skb, 1); // 去掉包类型字节 // 4. 根据包类型分发 switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: // 检查命令权限 if (!hci_sock_gen_cookie(sk, skb, &cookie)) { kfree_skb(skb); return -EINVAL; } // 发送命令到 HCI 核心层 err = hci_send_cmd(hdev, opcode, plen, param); break; case HCI_ACLDATA_PKT: // 发送 ACL 数据(经典蓝牙或 BLE 数据) err = hci_send_acl(hdev, skb, flags); break; case HCI_SCODATA_PKT: // 发送 SCO 数据(音频) err = hci_send_sco(hdev, skb); break; default: kfree_skb(skb); return -EINVAL; } return err; }
⚙️ 第四层:HCI 核心层 (hci_core.c)
HCI 核心层负责命令排队、超时管理、流控等。
4.1 命令发送入口
// net/bluetooth/hci_core.c int hci_send_cmd(struct hci_dev *hdev, __u16 opcode, __u32 plen, void *param) { struct sk_buff *skb; // 1. 分配 HCI 命令帧 skb = hci_prepare_cmd(hdev, opcode, plen, param); if (!skb) return -ENOMEM; // 2. 设置包类型 bt_cb(skb)->pkt_type = HCI_COMMAND_PKT; // 3. 加入命令队列 skb_queue_tail(&hdev->cmd_q, skb); // 4. 调度工作队列发送 queue_work(hdev->workqueue, &hdev->cmd_work); return 0; }4.2 命令工作队列
// net/bluetooth/hci_core.c static void hci_cmd_work(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, cmd_work); struct sk_buff *skb; // 1. 检查控制器是否准备好 if (test_bit(HCI_UP, &hdev->flags)) { // 2. 从队列取出命令 skb = skb_dequeue(&hdev->cmd_q); if (!skb) return; // 3. 更新命令计数器 atomic_inc(&hdev->cmd_cnt); // 4. 调用驱动的发送函数 hdev->send(hdev, skb); // 5. 启动命令超时定时器 mod_timer(&hdev->cmd_timer, jiffies + HCI_CMD_TIMEOUT); } }4.3 数据发送(ACL 数据)
// net/bluetooth/hci_core.c int hci_send_acl(struct hci_dev *hdev, struct sk_buff *skb, u16 flags) { struct hci_conn *conn = hci_conn_hash_lookup_handle(hdev, handle); // 1. 检查连接状态 if (!conn || conn->state != BT_CONNECTED) return -ENOTCONN; // 2. 设置包类型 bt_cb(skb)->pkt_type = HCI_ACLDATA_PKT; // 3. 加入 ACL 数据队列 skb_queue_tail(&conn->data_q, skb); // 4. 调度发送 queue_work(hdev->workqueue, &conn->tx_work); return 0; }
🔌 第五层:驱动层 (btusb.c)
驱动层实现
hdev->send回调,负责实际的数据传输。5.1 驱动注册时的 send 回调
// drivers/bluetooth/btusb.c static int btusb_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct btusb_data *data; struct hci_dev *hdev; // 分配 HCI 设备 hdev = hci_alloc_dev(); if (!hdev) return -ENOMEM; // 设置 send 回调 hdev->send = btusb_send_frame; hdev->open = btusb_open; hdev->close = btusb_close; // 注册 HCI 设备 hci_register_dev(hdev); }5.2 send 回调实现
// drivers/bluetooth/btusb.c static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb) { struct btusb_data *data = hci_get_drvdata(hdev); struct usb_ctrlrequest *dr; struct urb *urb; // 根据包类型选择端点 switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: // 命令包走控制端点 urb = usb_alloc_urb(0, GFP_ATOMIC); if (!urb) return -ENOMEM; dr = kmalloc(sizeof(*dr), GFP_ATOMIC); dr->bRequestType = USB_TYPE_CLASS | USB_DIR_OUT; dr->bRequest = HCI_REQ_HCI_COMMAND; dr->wValue = 0; dr->wIndex = 0; dr->wLength = cpu_to_le16(skb->len); usb_fill_control_urb(urb, data->udev, usb_sndctrlpipe(data->udev, 0), (unsigned char *)dr, skb->data, skb->len, btusb_tx_complete, skb); break; case HCI_ACLDATA_PKT: // ACL 数据包走批量端点 urb = usb_alloc_urb(0, GFP_ATOMIC); if (!urb) return -ENOMEM; usb_fill_bulk_urb(urb, data->udev, usb_sndbulkpipe(data->udev, data->bulk_tx_ep), skb->data, skb->len, btusb_tx_complete, skb); break; case HCI_SCODATA_PKT: // SCO 音频数据走等时端点 urb = usb_alloc_urb(0, GFP_ATOMIC); if (!urb) return -ENOMEM; usb_fill_int_urb(urb, data->udev, usb_sndintpipe(data->udev, data->intr_tx_ep), skb->data, skb->len, btusb_tx_complete, skb, data->intr_interval); break; default: return -EINVAL; } // 提交 URB 到 USB 核心层 return usb_submit_urb(urb, GFP_ATOMIC); }5.3 发送完成回调
// drivers/bluetooth/btusb.c static void btusb_tx_complete(struct urb *urb) { struct sk_buff *skb = urb->context; struct hci_dev *hdev = (struct hci_dev *)skb->dev; // 检查发送状态 if (urb->status == 0) { // 发送成功 hci_sent_cmd_data(hdev, skb->data); } else { // 发送失败 hci_dev_err(hdev, "urb %p failed to submit: %d", urb, urb->status); } // 释放资源 kfree(urb->setup_packet); usb_free_urb(urb); kfree_skb(skb); }
📊 下行数据流完整时序图(文本版)
应用层 bluetoothd HCI Socket HCI核心层 驱动层 硬件 ───────────────────────────────────────────────────────────────────────────────────── │ │ │ │ │ │ │ D-Bus: │ │ │ │ │ │ StartDiscovery │ │ │ │ │ │───────────────→│ │ │ │ │ │ │ │ │ │ │ │ │ 构造 HCI命令 │ │ │ │ │ │ HCI_LE_Set_ │ │ │ │ │ │ Scan_Enable │ │ │ │ │ │ │ │ │ │ │ │ sendto() │ │ │ │ │ │──────────────→│ │ │ │ │ │ │ │ │ │ │ │ │ hci_sock_sendmsg() │ │ │ │ │ ↓ │ │ │ │ │ │ 检查权限 │ │ │ │ │ │ ↓ │ │ │ │ │ │ hci_send_cmd() │ │ │ │ │ │───────────────→│ │ │ │ │ │ │ │ │ │ │ │ │ 入队cmd_q │ │ │ │ │ │ ↓ │ │ │ │ │ │ hci_cmd_work │ │ │ │ │ │ ↓ │ │ │ │ │ │ 调用驱动 │ │ │ │ │ │ hdev->send() │ │ │ │ │ │─────────────→│ │ │ │ │ │ │ │ │ │ │ │ │ btusb_send_ │ │ │ │ │ │ frame() │ │ │ │ │ │ ↓ │ │ │ │ │ │ 填充URB │ │ │ │ │ │ ↓ │ │ │ │ │ │ usb_submit_ │ │ │ │ │ │ urb() │ │ │ │ │ │─────────────→│ │ │ │ │ │ │ │ │ │ │ │ 硬件发送 │
📦 数据包在各层的格式变化
层级 数据格式 示例数据 应用层 D-Bus 方法调用 org.bluez.Adapter1.StartDiscoverybluetoothd HCI 命令结构 {opcode=0x200C, plen=2, data=[0x01, 0x00]}HCI Socket 带包类型的 HCI 命令 [0x01] + HCI_LE_Set_Scan_EnableHCI 核心 struct sk_buff内核套接字缓冲区 驱动层 USB URB USB 请求块 硬件 USB 批量传输 原始 USB 数据包
🛠️ 调试技巧
1. 跟踪 D-Bus 调用
# 监控 D-Bus 消息 dbus-monitor --system "interface='org.bluez.Adapter1'" # 输出示例: method call time=... sender=:1.10 -> destination=org.bluez path=/org/bluez/hci0; interface=org.bluez.Adapter1; member=StartDiscovery2. 跟踪 HCI Socket
# 使用 strace 跟踪 bluetoothd strace -e sendto -p $(pidof bluetoothd) # 输出示例: sendto(3, "\x01\x0c\x20\x02\x01\x00", 6, 0, ...) # ^^ ^^^^^^ ^^ ^^^^^ # | | | └─ 参数 # | | └──── 参数长度 # | └─────────── 操作码 (0x200C = LE Set Scan Enable) # └────────────── 包类型 (0x01 = HCI_COMMAND_PKT)3. 跟踪内核函数
# 使用 ftrace 跟踪 hci_send_cmd echo 'p:hci_send_cmd opcode=+0x4(%x0):u16' > /sys/kernel/debug/tracing/kprobe_events echo 1 > /sys/kernel/debug/tracing/events/kprobes/enable cat /sys/kernel/debug/tracing/trace_pipe4. 跟踪 USB 传输
# 开启 USB 监控 usbmon -i 1 cat /sys/kernel/debug/usb/usbmon/1u
🎯 总结
下行数据流的核心要点:
阶段 关键函数 主要工作 应用 → bluetoothd D-Bus 调用 策略检查、权限验证 bluetoothd → HCI Socket sendto()构造 HCI 命令 HCI Socket 层 hci_sock_sendmsg()用户空间数据拷贝、权限检查 HCI 核心层 hci_send_cmd()命令排队、超时管理、流控 驱动层 hdev->send()硬件传输、URB 提交 需要我继续讲解上行数据流(硬件 → 应用)吗?
HCI Socket
HCI Socket 是 Linux 蓝牙子系统中用户空间与内核空间的桥梁,它允许用户程序直接发送和接收 HCI(Host Controller Interface)数据包,实现对蓝牙控制器的底层操作。
🎯 什么是 HCI Socket
基本定义
HCI Socket 是一种特殊的 AF_BLUETOOTH 协议族的 Socket,它提供原始 HCI 数据的传输通道。
#include <sys/socket.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> // 创建 HCI Socket int sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);在架构中的位置
┌─────────────────────────────────────────────┐ │ 用户空间 │ │ ┌─────────────┐ ┌─────────────────────┐ │ │ │ bluetoothd │ │ hcitool / btmon │ │ │ │ (BlueZ) │ │ (调试工具) │ │ │ └──────┬──────┘ └──────────┬──────────┘ │ │ │ │ │ │ └────────┬───────────┘ │ │ │ │ │ HCI Socket (AF_BLUETOOTH) │ ├──────────────────┼──────────────────────────┤ │ 内核空间 │ │ ┌──────▼──────┐ │ │ │ hci_sock.c │ │ │ │ (HCI Socket │ │ │ │ 实现层) │ │ │ └──────┬──────┘ │ │ ┌──────▼──────┐ │ │ │ hci_core.c │ │ │ │ (HCI核心层) │ │ │ └──────┬──────┘ │ │ ┌──────▼──────┐ │ │ │ btusb.c │ │ │ │ (驱动) │ │ │ └──────┬──────┘ │ ├────────────────┼──────────────────────────┤ │ 硬件 │ │ ┌──────▼──────┐ │ │ │ 蓝牙控制器 │ │ │ └─────────────┘ │ └─────────────────────────────────────────────┘
📡 HCI Socket 的类型
HCI Socket 支持多种通道(channel),用于不同用途:
通道 宏定义 值 用途 使用程序 RAW 通道 HCI_CHANNEL_RAW0 发送/接收原始 HCI 帧 hcitool, btmon 用户通道 HCI_CHANNEL_USER1 用户空间直接控制控制器 btmgmt, bluetoothd 监控通道 HCI_CHANNEL_MONITOR2 监控 HCI 流量 btmon, wireshark 控制通道 HCI_CHANNEL_CONTROL3 控制器管理 bluetoothd
// 创建不同类型的 HCI Socket struct sockaddr_hci addr = {0}; addr.hci_family = AF_BLUETOOTH; addr.hci_dev = 0; // hci0 addr.hci_channel = HCI_CHANNEL_RAW; // 选择通道 int sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); bind(sk, (struct sockaddr *)&addr, sizeof(addr));
🔧 核心数据结构
1. HCI Socket 地址结构
// include/net/bluetooth/hci.h struct sockaddr_hci { sa_family_t hci_family; // AF_BLUETOOTH unsigned short hci_dev; // 设备号: 0 = hci0, 1 = hci1... unsigned short hci_channel; // 通道类型 };2. HCI 命令头
// 每个 HCI 命令的开头 struct hci_command_hdr { __le16 opcode; // 操作码 __u8 plen; // 参数长度 } __packed;3. 发送数据时的包格式
┌───────────┬─────────────────────────┐ │ 包类型(1B)│ HCI 数据包 │ ├───────────┼─────────────────────────┤ │ 0x01 │ HCI 命令 │ │ 0x02 │ HCI ACL 数据 │ │ 0x03 │ HCI SCO 数据 │ │ 0x04 │ HCI 事件 │ └───────────┴─────────────────────────┘
💻 实际代码示例
示例1:通过 HCI Socket 发送扫描命令
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> int main() { int sk; struct sockaddr_hci addr; // 1. 创建 HCI Socket sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); if (sk < 0) { perror("socket"); return 1; } // 2. 绑定到 hci0 的 RAW 通道 memset(&addr, 0, sizeof(addr)); addr.hci_family = AF_BLUETOOTH; addr.hci_dev = 0; // hci0 addr.hci_channel = HCI_CHANNEL_RAW; if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; } // 3. 构造 HCI 命令: LE Set Scan Enable (开启 BLE 扫描) struct { struct hci_command_hdr hdr; uint8_t enable; // 1 = 开启扫描 uint8_t filter_dup; // 0 = 不过滤重复 } cmd; cmd.hdr.opcode = htobs(0x200C); // HCI_LE_Set_Scan_Enable cmd.hdr.plen = 2; cmd.enable = 0x01; cmd.filter_dup = 0x00; // 4. 发送命令(注意:前面要加包类型字节) uint8_t pkt_type = HCI_COMMAND_PKT; struct iovec iov[2]; iov[0].iov_base = &pkt_type; iov[0].iov_len = 1; iov[1].iov_base = &cmd; iov[1].iov_len = sizeof(cmd); if (writev(sk, iov, 2) < 0) { perror("writev"); return 1; } printf("Scan command sent!\n"); // 5. 接收 HCI 事件(扫描结果) uint8_t buf[1024]; int n = read(sk, buf, sizeof(buf)); if (n > 0) { printf("Received %d bytes\n", n); // 解析 HCI 事件... } close(sk); return 0; }示例2:使用 libbluetooth 库(更高级的封装)
#include <stdio.h> #include <bluetooth/bluetooth.h> #include <bluetooth/hci.h> #include <bluetooth/hci_lib.h> int main() { int dev_id = 0; // hci0 int sk = hci_open_dev(dev_id); if (sk < 0) { perror("hci_open_dev"); return 1; } // 使用库函数发送命令 struct hci_request rq = {0}; uint8_t enable = 1; uint8_t filter_dup = 0; rq.ogf = OGF_LE_CTL; rq.ocf = OCF_LE_SET_SCAN_ENABLE; rq.cparam = &enable; rq.clen = 2; rq.rparam = NULL; rq.rlen = 0; if (hci_send_req(sk, &rq, 1000) < 0) { perror("hci_send_req"); return 1; } hci_close_dev(sk); return 0; }
🔍 HCI Socket 在内核中的实现
关键文件:
net/bluetooth/hci_sock.c
// 协议族注册 static const struct proto_ops hci_sock_ops = { .family = PF_BLUETOOTH, .owner = THIS_MODULE, .bind = hci_sock_bind, .sendmsg = hci_sock_sendmsg, .recvmsg = hci_sock_recvmsg, .ioctl = hci_sock_ioctl, }; // Socket 创建函数 static int hci_sock_create(struct net *net, struct socket *sock, int protocol) { struct sock *sk; // 检查协议类型 if (protocol != BTPROTO_HCI) return -EPROTONOSUPPORT; // 分配 socket 结构 sk = sk_alloc(net, PF_BLUETOOTH, GFP_ATOMIC, &hci_sk_proto, 0); if (!sk) return -ENOMEM; sock->state = SS_UNCONNECTED; sock->ops = &hci_sock_ops; sock_init_data(sock, sk); sk->sk_protocol = protocol; return 0; }发送数据的内核处理
// net/bluetooth/hci_sock.c static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; struct sk_buff *skb; struct hci_dev *hdev; unsigned char pkt_type; // 1. 从用户空间拷贝数据 skb = bt_skb_sendmsg(sock, msg, len); if (IS_ERR(skb)) return PTR_ERR(skb); // 2. 获取包类型(第一个字节) pkt_type = *((unsigned char *)skb->data); skb_pull(skb, 1); // 移除包类型字节 // 3. 获取 HCI 设备 hdev = hci_sock_dev(sk); if (!hdev) { kfree_skb(skb); return -EBADFD; } // 4. 根据包类型分发到不同处理函数 switch (pkt_type) { case HCI_COMMAND_PKT: // 发送 HCI 命令 err = hci_send_cmd(hdev, opcode, plen, param); break; case HCI_ACLDATA_PKT: // 发送 ACL 数据 err = hci_send_acl(hdev, skb, flags); break; case HCI_SCODATA_PKT: // 发送 SCO 音频数据 err = hci_send_sco(hdev, skb); break; default: kfree_skb(skb); return -EINVAL; } return err; }
🛠️ 调试 HCI Socket
1. 查看 HCI Socket 连接
# 查看所有 HCI Socket ss -A bluetooth -p # 输出示例: Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port hci ESTAB 0 0 hci0:1 *:*2. 使用 btmon 监控
# btmon 内部使用 HCI_CHANNEL_MONITOR btmon # 等价于手动创建监控 Socket # socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI) # bind with HCI_CHANNEL_MONITOR3. 使用 strace 跟踪
# 查看 bluetoothd 发送的命令 strace -e sendto -p $(pidof bluetoothd) # 输出: sendto(4, "\x01\x0c\x20\x02\x01\x00", 6, 0, NULL, 0) = 6 # ^^ ^^^^^^ ^^ ^^^^^ # | | | └─ 参数: 开启扫描 # | | └──── 参数长度: 2 # | └─────────── 操作码: 0x200C # └────────────── 包类型: HCI_COMMAND_PKT4. 查看内核中的 Socket 状态
# 查看 HCI Socket 的内核状态 cat /proc/net/bluetooth/hci_sock # 输出示例: sk laddr raddr channel ffff8800a1234567 00:00:00:00:00:00 00:00:00:00:00:00 0
📊 HCI Socket 与其他通信方式的对比
通信方式 接口 用途 优点 缺点 HCI Socket socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)底层控制、调试 灵活、完整控制 需要了解 HCI 协议 RFCOMM Socket socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)串口模拟、文件传输 类似 TCP,易用 仅限经典蓝牙 L2CAP Socket socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)数据通信 支持 ACL 和 LE 较底层 D-Bus D-Bus 系统总线 应用层管理 高级抽象、策略控制 性能开销大
🎯 HCI Socket 的典型应用
程序 使用的通道 用途 bluetoothd HCI_CHANNEL_CONTROL+HCI_CHANNEL_USER管理蓝牙设备 btmon HCI_CHANNEL_MONITOR监控 HCI 流量 hcitool HCI_CHANNEL_RAW发送原始 HCI 命令 bluetoothctl D-Bus (间接使用 HCI Socket) 用户交互 wireshark HCI_CHANNEL_MONITOR抓包分析
💡 总结
HCI Socket 的本质:一个允许用户空间程序直接与蓝牙控制器通信的原始接口。
核心特点:
原始访问:绕过 bluetoothd,直接发送 HCI 命令
多通道支持:不同通道满足不同场景(监控、控制、原始)
双向通信:既可发送命令,也可接收事件
内核实现 :位于
net/bluetooth/hci_sock.c何时使用 HCI Socket:
开发调试工具(如 btmon)
实现自定义蓝牙协议
需要底层控制(如直接修改蓝牙参数)
学习蓝牙协议(发送原始 HCI 命令观察响应)
区分不同场景的HCI概念
HCI SOCKET 和 蓝牙协议栈主机和控制器之间的HCI接口是同一个东西吗?
答案是:不是同一个东西,但密切相关。 它们共享"HCI"这个名字,但一个是概念层面的接口标准 ,另一个是Linux内核中的具体实现机制。
🎯 核心区别一览
维度 HCI 接口(协议标准) HCI Socket(Linux实现) 本质 蓝牙规范定义的通信协议 Linux 内核的编程接口 位置 主机与控制器之间的逻辑接口 Linux 用户空间与内核的接口 作用 定义数据包格式和命令集 提供程序访问 HCI 的通道 实现者 蓝牙芯片厂商(固件) Linux 内核开发者 使用者 蓝牙驱动、蓝牙协议栈 用户空间程序(bluetoothd等)
📡 一、HCI 接口(蓝牙规范中的概念)
定义
HCI(Host Controller Interface)是蓝牙规范定义的标准接口,位于主机(Host)和控制器(Controller)之间。
┌─────────────────────────────────────────────┐ │ 主机 │ │ (Host - 运行蓝牙协议栈的CPU) │ │ ┌─────────────────────────────────────┐ │ │ │ L2CAP │ RFCOMM │ SDP │ ATT │ ... │ │ │ └─────────────────────────────────────┘ │ │ ┌─────────────────────────────────────┐ │ │ │ HCI 主机层 │ │ │ └─────────────────────────────────────┘ │ ├─────────────────────────────────────────────┤ │ ▲ HCI 接口(规范定义)▲ │ │ │ (物理/逻辑传输) │ │ ├─────────────────────────────────────────────┤ │ ┌─────────────────────────────────────┐ │ │ │ HCI 控制器层 │ │ │ └─────────────────────────────────────┘ │ │ 控制器 │ │ (Controller - 蓝牙硬件+固件) │ │ Baseband │ Link Manager │ RF │ └─────────────────────────────────────────────┘HCI 接口的三类数据包
包类型 方向 用途 示例 HCI 命令 主机 → 控制器 控制蓝牙硬件 HCI_LE_Set_Scan_ParametersHCI 事件 控制器 → 主机 通知主机状态变化 HCI_LE_Advertising_ReportHCI 数据 双向 传输 ACL/SCO 数据 L2CAP 数据包 HCI 数据包格式
HCI 命令包格式: ┌──────────────┬──────────────┬─────────────────┐ │ 操作码(2B) │ 参数长度(1B) │ 参数(变长) │ ├──────────────┼──────────────┼─────────────────┤ │ 0x200C │ 0x02 │ 0x01 0x00 │ └──────────────┴──────────────┴─────────────────┘ HCI 事件包格式: ┌──────────────┬──────────────┬─────────────────┐ │ 事件码(1B) │ 参数长度(1B) │ 参数(变长) │ ├──────────────┼──────────────┼─────────────────┤ │ 0x3E │ 0x0C │ ... │ └──────────────┴──────────────┴─────────────────┘HCI 的物理传输方式
HCI 是逻辑接口,可以通过多种物理总线实现:
传输类型 物理接口 典型应用 HCI USB USB 批量/中断端点 大部分 USB 蓝牙适配器 HCI UART UART 串口 嵌入式模块(如你的 AIC8800) HCI SDIO SDIO 接口 WiFi/蓝牙 Combo 芯片 HCI SPI SPI 总线 某些 BLE 模块
🔌 二、HCI Socket(Linux 内核实现)
定义
HCI Socket 是 Linux 内核提供的编程接口,允许用户空间程序通过标准的 socket API 发送和接收 HCI 数据包。
用户空间程序 (bluetoothd / hcitool / btmon) │ │ socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI) │ bind() with channel ▼ ┌─────────────────────────────────────────────┐ │ HCI Socket 接口 │ │ (net/bluetooth/hci_sock.c) │ └─────────────────────────────────────────────┘ │ │ ▼ ┌─────────────────────────────────────────────┐ │ HCI 核心层 │ │ (net/bluetooth/hci_core.c) │ └─────────────────────────────────────────────┘ │ │ hdev->send() ▼ ┌─────────────────────────────────────────────┐ │ 蓝牙驱动 (btusb.c / hci_uart.c) │ └─────────────────────────────────────────────┘ │ │ USB / UART / SDIO ▼ ┌─────────────────────────────────────────────┐ │ 蓝牙控制器(硬件) │ └─────────────────────────────────────────────┘
🔗 三、两者的关系
关系总结
层级 组件 说明 标准定义 HCI 接口 蓝牙规范定义的协议,定义了数据包格式和命令 物理传输 USB/UART/SDIO HCI 数据包的承载介质 内核实现 HCI Socket 提供用户空间访问 HCI 的通道 数据内容 HCI 命令/事件/数据 HCI Socket 传输的就是 HCI 协议数据包
💻 四、代码层面的体现
HCI Socket 传输的就是 HCI 协议包
// 用户空间通过 HCI Socket 发送 HCI 命令 int sk = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI); // 构造 HCI 命令包(符合蓝牙 HCI 规范) struct { uint8_t type; // HCI_COMMAND_PKT struct hci_command_hdr hdr; // opcode + plen uint8_t param[2]; // 参数 } cmd = { .type = HCI_COMMAND_PKT, .hdr = { .opcode = htobs(0x200C), .plen = 2 }, .param = { 0x01, 0x00 } }; // 发送:这个数据包会原样传递给蓝牙控制器 write(sk, &cmd, sizeof(cmd));驱动层发送的就是 HCI 协议包
// drivers/bluetooth/btusb.c static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb) { // skb 中的数据就是完整的 HCI 协议包 // 通过 USB 发送给控制器 usb_fill_bulk_urb(urb, data->udev, usb_sndbulkpipe(data->udev, data->bulk_tx_ep), skb->data, skb->len, // HCI 数据包 btusb_tx_complete, skb); return usb_submit_urb(urb, GFP_ATOMIC); }
📊 五、类比理解
概念 类比 HCI 接口(规范) HTTP 协议 HCI Socket curl 命令行工具 物理传输(USB/UART) TCP/IP 网络 HCI 命令包 HTTP GET / POST 请求 就像 curl 使用 TCP/IP 传输 HTTP 请求一样,HCI Socket 通过 USB/UART 传输 HCI 协议包。
🎯 六、在你的 AIC8800 系统中
根据你之前的原理图和日志,AIC8800D40 的架构是:
┌─────────────────────────────────────────────────────────────┐ │ 主控芯片 (CPU) │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 用户空间: bluetoothd / bluetoothctl │ │ │ │ ↓ 使用 HCI Socket │ │ │ │ 内核: HCI Socket → HCI核心 → hci_uart驱动 │ │ │ └─────────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────────┤ │ 物理连接:UART │ │ (HCI UART 传输) │ ├─────────────────────────────────────────────────────────────┤ │ ┌─────────────────────────────────────────────────────┐ │ │ │ AIC8800D40 芯片 │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ 固件: 实现 HCI 控制器层 │ │ │ │ │ │ - 解析 HCI 命令 │ │ │ │ │ │ - 返回 HCI 事件 │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ │ │ 硬件: BR/EDR + BLE 射频 │ │ │ │ │ └─────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘
💡 总结
问题 答案 是同一个东西吗? 不是 HCI 接口 蓝牙规范定义的标准,规定主机和控制器如何通信 HCI Socket Linux 内核提供的编程接口,让程序可以发送/接收 HCI 数据 它们的关系 HCI Socket 传输的内容就是 HCI 协议定义的数据包 物理传输 USB/UART/SDIO 是 HCI 数据包的载体 你的系统中 AIC8800D40 通过 UART 接收 HCI 命令,固件解析后执行 简单来说:HCI 接口是"说什么"(协议),HCI Socket 是"怎么说"(Linux 的实现方式),UART 是"用什么说"(物理介质)。
HCI和Netlink
HCI SOCKET和netlink有啥区别,为啥蓝牙不直接用netlink?
这是一个非常深刻的问题。两者都是内核与用户空间的通信机制,但设计哲学和应用场景完全不同。蓝牙选择 HCI Socket 而非 Netlink,有其历史和技术上的深刻原因。
🎯 核心区别一览
维度 HCI Socket Netlink 协议族 AF_BLUETOOTHAF_NETLINK设计用途 专为蓝牙设计 通用内核-用户通信 数据模型 流式/数据报(HCI 帧) 消息式(nlmsghdr) 多路复用 通过通道(channel) 通过协议族(NETLINK_*) 性能 更高(直接传递 HCI 帧) 有封装开销 历史 Linux 2.4 时代引入 Linux 2.0 时代引入
📡 一、设计哲学的根本差异
Netlink:通用消息总线
Netlink 的设计目标是成为内核与用户空间的通用通信总线,类似于"内核版的 D-Bus"。
用户空间 内核空间 │ │ │ ┌─────────────────────┐ │ │ │ 标准消息头 nlmsghdr │ │ │ │ ┌─────────────────┐ │ │ │ │ │ 协议特定数据 │ │ │ │ │ └─────────────────┘ │ │ │ └─────────────────────┘ │ │ ↓ │ │ Netlink Socket │ │ ↓ │ │ ┌─────────────────────────┐│ │ │ 多路复用 (协议族) ││ │ │ - NETLINK_ROUTE ││ │ │ - NETLINK_GENERIC ││ │ │ - NETLINK_NETFILTER ││ │ └─────────────────────────┘│HCI Socket:蓝牙专用通道
HCI Socket 的设计目标是直接、高效地传递 HCI 协议帧,不做额外封装。
用户空间 内核空间 │ │ │ ┌─────────────────────────┐ │ │ │ HCI 协议包(原始) │ │ │ │ ┌─────────────────────┐ │ │ │ │ │ 包类型(1B) │ │ │ │ │ │ HCI 命令/事件/数据 │ │ │ │ │ └─────────────────────┘ │ │ │ └─────────────────────────┘ │ │ ↓ │ │ HCI Socket │ │ (AF_BLUETOOTH) │ │ ↓ │ │ ┌─────────────────────────┐│ │ │ HCI 核心层 ││ │ │ (直接处理 HCI 协议) ││ │ └─────────────────────────┘│
📊 二、数据格式对比
Netlink 消息格式(有封装)
// Netlink 强制要求的消息头 struct nlmsghdr { __u32 nlmsg_len; // 消息总长度 __u16 nlmsg_type; // 消息类型 __u16 nlmsg_flags; // 标志位 __u32 nlmsg_seq; // 序列号 __u32 nlmsg_pid; // 发送者端口 ID }; // 实际数据必须封装在这个头后面 struct my_netlink_data { // ... 协议特定数据 }; // 用户空间发送时需要构造完整消息 struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, }; // 封装 nlmsghdr + 实际数据HCI Socket 数据格式(无额外封装)
// HCI Socket 直接发送 HCI 协议包 // 不需要额外的消息头! uint8_t hci_packet[] = { 0x01, // 包类型: HCI_COMMAND_PKT 0x0C, 0x20, // 操作码: 0x200C (LE_Set_Scan_Enable) 0x02, // 参数长度 0x01, 0x00 // 参数: 开启扫描 }; // 直接发送 send(sk, hci_packet, sizeof(hci_packet), 0);
🔍 三、为什么蓝牙不直接用 Netlink?
原因1:历史原因(最重要)
蓝牙子系统在 Linux 中的发展早于 Netlink 的成熟:
时间线 事件 1999 蓝牙规范发布 2001 Linux 蓝牙协议栈 BlueZ 项目启动 2002 Netlink 开始用于路由(NETLINK_ROUTE) 2004 BlueZ 被 Linux 内核主线收录,使用 HCI Socket 2005+ Netlink 才广泛用于各种子系统 当 BlueZ 被引入内核时,HCI Socket 已经是成熟的接口。Netlink 当时主要用于网络路由,还没有形成通用的"内核-用户通信"标准。
原因2:协议匹配度
HCI 本身就是一个"帧协议",天然适合 socket 模型:
蓝牙规范定义的 HCI 帧结构: ┌────────────┬────────────┬─────────────────┐ │ 包类型(1B) │ 头(变长) │ 有效载荷(变长) │ └────────────┴────────────┴─────────────────┘ HCI Socket 直接传递这个帧: socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI) write(sk, hci_frame, len) // 完美匹配!如果用 Netlink,需要额外封装:
Netlink 需要的结构: ┌─────────────────────────────────────────────┐ │ nlmsghdr (16B) │ ├─────────────────────────────────────────────┤ │ 包类型(1B) │ 头(变长) │ 有效载荷(变长) │ └─────────────────────────────────────────────┘这增加了 16 字节的固定开销,且破坏了 HCI 帧的原始结构。
原因3:多路复用方式
机制 Netlink 多路复用 HCI Socket 多路复用 方式 不同协议族(如 NETLINK_ROUTE, NETLINK_GENERIC) 同一 socket 的不同通道(channel) 灵活性 需要注册新的协议族 在 bind() 时指定 channel 开销 每个协议族独立管理 共享同一个 socket 类型 HCI Socket 的设计更轻量:
// 同一个 socket 类型,不同用途 struct sockaddr_hci addr; addr.hci_channel = HCI_CHANNEL_RAW; // 原始 HCI bind(sk, &addr, sizeof(addr)); addr.hci_channel = HCI_CHANNEL_MONITOR; // 监控 bind(sk, &addr, sizeof(addr)); addr.hci_channel = HCI_CHANNEL_CONTROL; // 控制 bind(sk, &addr, sizeof(addr));原因4:性能考虑
蓝牙是实时性要求高的协议,尤其是在音频传输场景:
// HCI Socket 路径:零拷贝 用户空间 send() → hci_sock_sendmsg() → hci_send_cmd() → 驱动 // Netlink 路径(如果采用):多次拷贝 用户空间 send() → netlink_sendmsg() → 封装 → 解析 → 目标子系统蓝牙的 ACL 数据包(高达 1024 字节)和 SCO 音频数据包(周期性发送)需要高效传输,Netlink 的封装开销在这些场景下不可忽视。
原因5:与蓝牙规范的对应关系
蓝牙规范本身就是分层设计,每一层都有对应的 socket 类型:
蓝牙层 Linux Socket 类型 用途 HCI BTPROTO_HCI控制器通信 L2CAP BTPROTO_L2CAP数据通道 RFCOMM BTPROTO_RFCOMM串口模拟 SCO BTPROTO_SCO音频传输 这种一一对应的设计非常直观,开发者可以自然地选择合适层级的 socket。
🔄 四、WiFi 为什么用 Netlink(nl80211)?
有趣的是,WiFi 子系统选择了 Netlink(nl80211)而不是 HCI 风格的 socket。这恰恰说明了两种设计的适用场景:
维度 蓝牙 (HCI Socket) WiFi (nl80211) 控制协议 HCI 命令(固定格式) nl80211 命令(可扩展) 数据路径 统一通过 HCI Socket 数据走 netdev(wlan0),控制走 nl80211 扩展性 蓝牙规范定义,扩展慢 Netlink 可灵活添加新命令 复杂性 HCI 命令集庞大但稳定 WiFi 标准迭代快,需要灵活扩展 WiFi 选择 Netlink 的原因:
802.11 标准迭代快(n/ac/ax/be),需要灵活扩展命令
控制和数据分离:数据走网络栈,控制走 Netlink,逻辑清晰
与其他网络子系统统一:路由、iptables 等都使用 Netlink
📋 五、总结对比
问题 答案 为什么蓝牙用 HCI Socket? HCI 是帧协议,天然匹配 socket 模型;历史原因,早于 Netlink 成熟 为什么 WiFi 用 Netlink? 需要灵活扩展;控制和数据分离更清晰 能用 Netlink 实现蓝牙吗? 技术上可以,但需要额外封装,增加开销,破坏直观性 能用 HCI Socket 实现 WiFi 吗? 不适合,WiFi 控制命令复杂且变化快,需要更灵活的机制
💡 一句话总结
蓝牙用 HCI Socket 是因为它直接对应蓝牙规范定义的 HCI 帧协议,无需额外封装;WiFi 用 Netlink 是因为需要灵活扩展的命令集,且与网络子系统的设计统一。两者都是各自领域最合适的选择。
流程中的经典蓝牙和BLE
在数据下行的过程中,经典蓝牙和BLE的数据流向有什么区别吗,如何区分二者?
在数据下行过程中,经典蓝牙和BLE共享同一套传输通道,但在多个层次通过不同机制进行区分。下面我从上到下逐层分析。
🎯 核心答案:同路不同车
┌─────────────────────────────────────────────────────────────────────┐ │ 用户空间 │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │ │ 经典蓝牙应用 │ │ BLE 应用 │ │ │ │ (A2DP, HFP, SPP) │ │ (GATT, 传感器) │ │ │ └───────────┬─────────────┘ └───────────┬─────────────┘ │ │ ↓ ↓ │ │ ┌─────────────────────────┐ ┌─────────────────────────┐ │ │ │ RFCOMM / SCO / BNEP │ │ ATT / GATT / SMP │ │ │ └───────────┬─────────────┘ └───────────┬─────────────┘ │ ├──────────────┼──────────────────────────────┼───────────────────────┤ │ ↓ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ L2CAP 层 │ │ │ │ 通过 CID (Channel ID) 区分上层协议 │ │ │ │ CID 0x0003 → RFCOMM (经典) CID 0x0004 → ATT (BLE) │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ HCI 层 │ │ │ │ 统一使用 HCI_ACLDATA_PKT │ │ │ │ 通过连接句柄(Handle)区分连接类型 │ │ │ │ Handle 0x0000-0x0EFF → 经典蓝牙 Handle 0x0F00-0x0FFF → BLE│ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 驱动层 (btusb.c) │ │ │ │ 不区分,统一通过批量端点发送 │ │ │ └─────────────────────────────────────────────────────────────┘ │ │ ↓ ↓ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 蓝牙控制器 (硬件) │ │ │ │ 通过 Handle 和 CID 自动路由 │ │ │ └─────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────┘
📡 第一层:用户空间应用的区分
经典蓝牙:使用 RFCOMM Socket
// 经典蓝牙 - RFCOMM Socket 示例 int sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); struct sockaddr_rc addr = { .rc_family = AF_BLUETOOTH, .rc_bdaddr = *BDADDR_ANY, .rc_channel = 1, // RFCOMM 通道号 }; connect(sk, (struct sockaddr *)&addr, sizeof(addr)); // 发送数据 write(sk, "hello", 5);BLE:使用 L2CAP Socket (ATT)
// BLE - L2CAP Socket 示例 int sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); struct sockaddr_l2 addr = { .l2_family = AF_BLUETOOTH, .l2_bdaddr = *BDADDR_ANY, .l2_cid = 4, // L2CAP_CID_ATT (BLE ATT 协议) }; connect(sk, (struct sockaddr *)&addr, sizeof(addr)); // 发送 ATT 协议数据 write(sk, att_data, att_len);
🔀 第二层:L2CAP 层的区分
L2CAP 层通过 CID (Channel ID) 区分上层协议:
CID 协议 类型 用途 0x0001 SDP 经典蓝牙 服务发现 0x0003 RFCOMM 经典蓝牙 串口模拟 0x0004 ATT BLE 属性协议 0x0005 SMP (BR/EDR) 经典蓝牙 安全管理 0x0006 SMP (LE) BLE BLE 安全管理 0x0007-0x003F 动态分配 两者都有 上层协议
// net/bluetooth/l2cap_core.c static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) { struct l2cap_hdr *lh = (void *)skb->data; u16 cid = __le16_to_cpu(lh->cid); // 根据 CID 分发到不同处理函数 switch (cid) { case L2CAP_CID_RFCOMM: // 经典蓝牙:RFCOMM rfcomm_recv_frame(conn, skb); break; case L2CAP_CID_ATT: // BLE:ATT 协议 att_recv_frame(conn, skb); break; case L2CAP_CID_LE_SMP: // BLE:安全管理 smp_recv_frame(conn, skb); break; default: // 动态分配的通道 l2cap_recv_acldata(chan, skb); break; } }
🔗 第三层:HCI 层的区分
连接句柄(Handle)是关键!
HCI 层通过 连接句柄 区分经典蓝牙和 BLE 连接:
// net/bluetooth/hci_core.h #define hci_conn_handle_type(handle) \ (((handle) & 0x0f00) == 0x0f00 ? LE_LINK : ACL_LINK)连接句柄分配规则:
经典蓝牙 ACL 连接句柄: ┌──────────────────────────────────────────────┐ │ 位15-12: 保留 (0) │ 位11-0: 句柄值 │ │ 实际范围: 0x0000 - 0x0EFF (最多 3840 个) │ └──────────────────────────────────────────────┘ BLE 连接句柄: ┌──────────────────────────────────────────────┐ │ 位15-12: 固定为 0x0F │ 位11-0: 句柄值 │ │ 实际范围: 0x0F00 - 0x0FFF (最多 256 个) │ └──────────────────────────────────────────────┘内核中如何区分
// net/bluetooth/hci_core.c static void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_acl_hdr *hdr = (void *)skb->data; u16 handle = __le16_to_cpu(hdr->handle); u16 flags = (handle >> 12); u16 conn_handle = handle & 0x0fff; // 查找连接 struct hci_conn *conn = hci_conn_hash_lookup_handle(hdev, conn_handle); if (conn) { // 根据连接类型分发 switch (conn->type) { case ACL_LINK: // 经典蓝牙 l2cap_recv_acldata(conn, skb, flags); break; case LE_LINK: // BLE l2cap_recv_acldata(conn, skb, flags); break; } } }
🔌 第四层:驱动层的处理
驱动层(如 btusb.c)完全不区分经典蓝牙和 BLE:
// drivers/bluetooth/btusb.c static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb) { // 驱动只关心包类型,不关心是经典蓝牙还是 BLE switch (bt_cb(skb)->pkt_type) { case HCI_COMMAND_PKT: // 命令包:可能是经典蓝牙命令或 BLE 命令 // 硬件通过 OGF 自动区分 usb_fill_control_urb(...); break; case HCI_ACLDATA_PKT: // ACL 数据包:可能是经典蓝牙或 BLE // 硬件通过连接句柄自动路由 usb_fill_bulk_urb(...); break; case HCI_SCODATA_PKT: // SCO 数据包:只用于经典蓝牙音频 usb_fill_int_urb(...); break; } return usb_submit_urb(urb, GFP_ATOMIC); }
🎵 特殊情况:音频数据的区别
经典蓝牙音频(SCO/eSCO)有专用的 HCI 包类型和端点:
// 经典蓝牙音频:使用 HCI_SCODATA_PKT case HCI_SCODATA_PKT: // 走 USB 等时端点(高实时性) usb_fill_int_urb(urb, ..., btusb_tx_complete, skb, interval); break;BLE 音频(LE Audio)则通过 ACL 数据包传输,没有专用 HCI 包类型。
📊 完整下行数据流对比表
层次 区分机制 经典蓝牙 BLE 应用层 Socket 类型 BTPROTO_RFCOMM/BTPROTO_SCOBTPROTO_L2CAP(CID=ATT)L2CAP 层 CID (Channel ID) 0x0003 (RFCOMM), 0x0001 (SDP) 0x0004 (ATT), 0x0006 (SMP) HCI 层 连接句柄范围 0x0000 - 0x0EFF 0x0F00 - 0x0FFF HCI 包类型 ACL 数据包 HCI_ACLDATA_PKTHCI_ACLDATA_PKT驱动层 端点类型 批量端点(数据)+ 等时端点(音频) 批量端点 硬件 内部路由 通过连接句柄 + CID 区分 通过连接句柄 + CID 区分
🔍 如何从 HCI 日志中区分
经典蓝牙:Inquiry 命令和 ACL 数据
< HCI Command: Inquiry (0x01|0x0001) plen 5 Lap: 0x9e8b33, Length: 8, Num_responses: 0 > HCI Event: Inquiry Complete (0x01) plen 1 Status: Success (0x00) < HCI Command: Create Connection (0x01|0x0005) plen 13 Address: AA:BB:CC:DD:EE:FF, Packet type: 0xcc18 > HCI Event: Connection Complete (0x03) plen 11 Status: Success (0x00), Handle: 10 ← 经典蓝牙句柄 0x000ABLE:LE Scan 命令和 LE 事件
< HCI Command: LE Set Scan Enable (0x08|0x200C) plen 2 Scanning: Enabled (0x01) > HCI Event: LE Meta Event (0x3e) plen 12 LE Advertising Report (0x02) Handle: 0x0F01 ← BLE 句柄(高位 0x0F) < HCI Command: LE Create Connection (0x08|0x200D) plen 25 > HCI Event: LE Meta Event (0x3e) plen 19 LE Connection Complete (0x01) Handle: 0x0F02 ← BLE 句柄(高位 0x0F)关键区别点
特征 经典蓝牙 BLE 命令 OGF 0x01, 0x02, 0x03... 0x08 (LE Ctrl) 事件码 0x02, 0x03... 0x3E (LE Meta Event) 连接句柄 0x0000-0x0EFF 0x0F00-0x0FFF
💡 总结
数据流向相同点
都通过 HCI Socket 从用户空间进入内核
都使用 HCI_ACLDATA_PKT 类型传输数据
都走驱动的批量端点(USB/UART)
共享同一套驱动代码
数据流向不同点
维度 区别 应用层入口 不同 Socket 类型(RFCOMM vs L2CAP/ATT) L2CAP 层 不同 CID(0x0003 vs 0x0004) HCI 层 不同连接句柄范围(0x0000-0x0EFF vs 0x0F00-0x0FFF) 音频传输 SCO 专用包 vs ACL 数据包 一句话总结
经典蓝牙和 BLE 在数据下行过程中共享同一套传输通道(HCI ACL 数据包),但在 L2CAP 层通过 CID 区分协议,在 HCI 层通过连接句柄范围区分连接类型,最终由硬件根据这些信息自动路由到正确的处理单元。
上行数据流
上行数据流指的是从蓝牙硬件到应用程序 的数据路径。与下行数据流共享同一套传输通道,但处理流程更为复杂,因为需要解析、分流和向上传递。
🎯 上行数据流全景图
📥 第一步:驱动层接收数据
硬件中断触发
当蓝牙控制器收到空中数据包时,通过物理接口(USB/UART/SDIO)触发中断:
// drivers/bluetooth/btusb.c static void btusb_urb_complete(struct urb *urb) { struct btusb_data *data = urb->context; struct hci_dev *hdev = data->hdev; struct sk_buff *skb = urb->transfer_buffer; // 1. 检查传输状态 if (urb->status == 0) { // 2. 识别 HCI 包类型(数据包的第一个字节) // 0x01 = HCI_COMMAND_PKT (命令完成) // 0x02 = HCI_ACLDATA_PKT (ACL数据) // 0x03 = HCI_SCODATA_PKT (SCO音频) // 0x04 = HCI_EVENT_PKT (事件) u8 pkt_type = *((u8 *)skb->data); bt_cb(skb)->pkt_type = pkt_type; // 3. 去掉包类型字节,交给 HCI 核心层 skb_pull(skb, 1); hci_recv_frame(hdev, skb); } // 4. 重新提交 URB 以接收下一个数据包 usb_submit_urb(urb, GFP_ATOMIC); }数据包类型识别
包类型值 宏定义 来源 去向 0x01 HCI_COMMAND_PKT控制器响应命令 HCI 核心层(命令完成事件) 0x02 HCI_ACLDATA_PKTACL 数据(经典蓝牙或 BLE) L2CAP 层 0x03 HCI_SCODATA_PKTSCO 音频数据 上层音频应用 0x04 HCI_EVENT_PKT控制器自发事件 HCI 核心层(事件处理)
⚙️ 第二步:HCI 核心层处理
接收入口
// net/bluetooth/hci_core.c int hci_recv_frame(struct hci_dev *hdev, struct sk_buff *skb) { // 1. 设置接收设备 skb->dev = (void *)hdev; // 2. 加入接收队列 skb_queue_tail(&hdev->rx_q, skb); // 3. 调度接收工作队列 queue_work(hdev->workqueue, &hdev->rx_work); return 0; } // 接收工作队列 static void hci_rx_work(struct work_struct *work) { struct hci_dev *hdev = container_of(work, struct hci_dev, rx_work); struct sk_buff *skb; while ((skb = skb_dequeue(&hdev->rx_q))) { // 根据包类型分发 switch (bt_cb(skb)->pkt_type) { case HCI_EVENT_PKT: hci_event_packet(hdev, skb); // 事件包处理 break; case HCI_ACLDATA_PKT: hci_acldata_packet(hdev, skb); // ACL数据包处理 break; case HCI_SCODATA_PKT: hci_scodata_packet(hdev, skb); // SCO音频包处理 break; default: kfree_skb(skb); break; } } }
📨 第三步:事件包处理 (hci_event_packet)
经典蓝牙事件 vs BLE 事件
// net/bluetooth/hci_event.c static void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_event_hdr *hdr = (void *)skb->data; u8 event = hdr->evt; switch (event) { // ========== 经典蓝牙事件 ========== case HCI_EV_INQUIRY_COMPLETE: // 扫描完成 hci_inquiry_complete_evt(hdev, skb); break; case HCI_EV_CONN_COMPLETE: // 连接完成 hci_conn_complete_evt(hdev, skb); break; case HCI_EV_DISCONN_COMPLETE: // 断开连接 hci_disconn_complete_evt(hdev, skb); break; case HCI_EV_REMOTE_NAME_REQ_COMPLETE: // 远程名称获取完成 hci_remote_name_evt(hdev, skb); break; // ========== BLE 事件(通过 LE Meta Event) ========== case HCI_EV_LE_META_EVENT: // BLE 元事件,需要进一步解析子事件 hci_le_meta_evt(hdev, skb); break; default: break; } }BLE 元事件处理
// net/bluetooth/hci_event.c static void hci_le_meta_evt(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_ev_le_meta *ev = (void *)skb->data; switch (ev->subevent) { case HCI_EV_LE_ADVERTISING_REPORT: // BLE 广播报告(扫描结果) hci_le_adv_report_evt(hdev, skb); break; case HCI_EV_LE_CONN_COMPLETE: // BLE 连接完成 hci_le_conn_complete_evt(hdev, skb); break; case HCI_EV_LE_CONN_UPDATE_COMPLETE: // BLE 连接参数更新完成 hci_le_conn_update_complete_evt(hdev, skb); break; case HCI_EV_LE_LTK_REQUEST: // BLE 长期密钥请求 hci_le_ltk_request_evt(hdev, skb); break; } }
📦 第四步:ACL 数据包处理 (hci_acldata_packet)
这是区分经典蓝牙和 BLE 的关键环节:
// net/bluetooth/hci_core.c static void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb) { struct hci_acl_hdr *hdr = (void *)skb->data; u16 handle = __le16_to_cpu(hdr->handle); u16 flags = (handle >> 12); u16 conn_handle = handle & 0x0fff; // 提取连接句柄 // 根据连接句柄查找连接 struct hci_conn *conn = hci_conn_hash_lookup_handle(hdev, conn_handle); if (!conn) { kfree_skb(skb); return; } // 关键:根据连接类型分发 switch (conn->type) { case ACL_LINK: // 经典蓝牙 ACL 连接 l2cap_recv_acldata(conn, skb, flags); break; case LE_LINK: // BLE 连接 l2cap_recv_acldata(conn, skb, flags); break; default: kfree_skb(skb); break; } }连接句柄的区分规则
连接句柄范围 连接类型 说明 0x0000 - 0x0EFF ACL_LINK经典蓝牙 0x0F00 - 0x0FFF LE_LINKBLE 0x0100 - 0x01FF SCO_LINK经典蓝牙音频
// 内核中的判断宏 #define hci_conn_handle_type(handle) \ (((handle) & 0x0f00) == 0x0f00 ? LE_LINK : ACL_LINK)
🔀 第五步:L2CAP 层分流
接收 ACL 数据
// net/bluetooth/l2cap_core.c void l2cap_recv_acldata(struct hci_conn *hconn, struct sk_buff *skb, u16 flags) { struct l2cap_conn *conn = hconn->l2cap_data; struct l2cap_hdr *lh; u16 cid; while (skb->len >= L2CAP_HDR_SIZE) { lh = (void *)skb->data; cid = __le16_to_cpu(lh->cid); // 根据 CID 分流到不同的上层协议 switch (cid) { // ========== 经典蓝牙 ========== case L2CAP_CID_SDP: // 0x0001 l2cap_recv_frame(conn, skb, SDP); break; case L2CAP_CID_RFCOMM: // 0x0003 rfcomm_recv_frame(conn, skb); break; // ========== BLE ========== case L2CAP_CID_ATT: // 0x0004 att_recv_frame(conn, skb); break; case L2CAP_CID_LE_SMP: // 0x0006 smp_recv_frame(conn, skb); break; // ========== 动态通道 ========== default: l2cap_recv_frame(conn, skb, cid); break; } skb_pull(skb, L2CAP_HDR_SIZE + lh_len); } }CID 分配表
CID 协议 类型 说明 0x0001 SDP 经典蓝牙 服务发现协议 0x0002 保留 - - 0x0003 RFCOMM 经典蓝牙 串口模拟 0x0004 ATT BLE 属性协议 0x0005 SMP (BR/EDR) 经典蓝牙 安全管理 0x0006 SMP (LE) BLE BLE 安全管理 0x0007-0x003F 动态分配 两者都有 上层协议通道
📤 第六步:上层协议处理
经典蓝牙:RFCOMM 层
// net/bluetooth/rfcomm/core.c void rfcomm_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) { struct rfcomm_session *s; struct rfcomm_hdr *hdr = (void *)skb->data; u8 dlci = hdr->dlci; // 根据 DLCI 查找会话 s = rfcomm_session_lookup(conn, dlci); if (s) { // 通过 TTY 设备发送给用户空间 rfcomm_dev_recv_frame(s, skb); } } // 最终通过 TTY 层传递 static int rfcomm_tty_write(struct tty_struct *tty, const u8 *buf, int count) { // 数据到达 /dev/rfcomm0 return rfcomm_dev_write(tty->driver_data, buf, count); }BLE:ATT 层
// net/bluetooth/att.c void att_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb) { struct att_hdr *hdr = (void *)skb->data; u8 opcode = hdr->opcode; switch (opcode) { case ATT_OP_READ_RSP: // ATT 读响应 att_read_rsp(conn, skb); break; case ATT_OP_WRITE_RSP: // ATT 写响应 att_write_rsp(conn, skb); break; case ATT_OP_HANDLE_NOTIFY: // 通知(如传感器数据) att_handle_notify(conn, skb); break; case ATT_OP_HANDLE_IND: // 指示(需要确认) att_handle_ind(conn, skb); break; } // 通过 HCI Socket 传递给用户空间 hci_send_to_channel(conn->hdev, HCI_CHANNEL_USER, skb, 0); }
🖥️ 第七步:用户空间接收
bluetoothd 通过 HCI Socket 接收
// bluetoothd 中的 HCI Socket 处理 static void hci_socket_handler(void *data) { struct sockaddr_hci addr; uint8_t buf[4096]; // 从 HCI Socket 读取数据 int n = recvfrom(sk, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addrlen); if (n > 0) { // 解析包类型 uint8_t pkt_type = buf[0]; switch (pkt_type) { case HCI_EVENT_PKT: // 处理事件(扫描结果、连接状态等) handle_hci_event(buf, n); break; case HCI_ACLDATA_PKT: // 处理 ACL 数据(ATT 响应等) handle_acldata(buf, n); break; } } } // 通过 D-Bus 通知应用 static void handle_hci_event(uint8_t *buf, int len) { struct hci_event_hdr *hdr = (void *)(buf + 1); switch (hdr->evt) { case HCI_EV_LE_META_EVENT: // BLE 扫描结果 dbus_message_new_signal("/org/bluez/hci0", "org.bluez.Adapter1", "DeviceFound"); dbus_connection_send(conn, msg, NULL); break; } }
📊 上行数据流完整对比表
层次 经典蓝牙 BLE 区分机制 硬件 接收空中数据 接收空中数据 硬件自动识别 驱动层 识别包类型 识别包类型 HCI_EVENT_PKTvsHCI_ACLDATA_PKTHCI 核心层 事件/数据分流 事件/数据分流 hci_event_packetvshci_acldata_packet事件处理 独立事件码 HCI_EV_LE_META_EVENT+ 子事件事件码 0x3E 特殊处理 数据分流 连接句柄 0x0000-0x0EFF 连接句柄 0x0F00-0x0FFF conn->type区分L2CAP 层 CID 0x0003 (RFCOMM) CID 0x0004 (ATT) switch (cid)上层协议 RFCOMM → TTY ATT → HCI Socket 不同传递路径 用户空间 /dev/rfcomm0D-Bus / HCI Socket 不同接口
🔍 从 btmon 日志中观察上行数据流
经典蓝牙:RFCOMM 数据
> HCI Event: Number of Completed Packets (0x13) plen 5 Handle: 10, Packets: 1 > HCI ACL data packet (0x02) plen 10 Handle: 10 (0x000A) ← 经典蓝牙句柄 Flags: 0x02 Data: 48 65 6c 6c 6f ← "Hello"BLE:ATT 数据
> HCI Event: LE Meta Event (0x3e) plen 12 LE Advertising Report (0x02) Handle: 0x0F01 ← BLE 句柄(高位 0x0F) Address: 11:22:33:44:55:66 > HCI ACL data packet (0x02) plen 15 Handle: 0x0F02 ← BLE 句柄 Flags: 0x02 Data: 04 02 00 01 02 03 04 ← ATT 协议数据
💡 总结
上行数据流的关键区别
维度 经典蓝牙 BLE 事件识别 独立事件码 LE Meta Event + 子事件 连接句柄 0x0000-0x0EFF 0x0F00-0x0FFF L2CAP CID 0x0003 (RFCOMM) 0x0004 (ATT) 用户空间接口 TTY ( /dev/rfcomm0)D-Bus / HCI Socket 一句话总结
上行数据流中,经典蓝牙和 BLE 共享同一套接收路径(驱动→HCI核心→L2CAP),但通过连接句柄范围区分连接类型,通过 L2CAP CID 区分上层协议,最终通过不同的接口(TTY vs D-Bus)传递给用户空间。


