Linux下蓝牙框架的数据流

梳理一下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.StartDiscovery
bluetoothd HCI 命令结构 {opcode=0x200C, plen=2, data=[0x01, 0x00]}
HCI Socket 带包类型的 HCI 命令 [0x01] + HCI_LE_Set_Scan_Enable
HCI 核心 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=StartDiscovery

2. 跟踪 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_pipe

4. 跟踪 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_RAW 0 发送/接收原始 HCI 帧 hcitool, btmon
用户通道 HCI_CHANNEL_USER 1 用户空间直接控制控制器 btmgmt, bluetoothd
监控通道 HCI_CHANNEL_MONITOR 2 监控 HCI 流量 btmon, wireshark
控制通道 HCI_CHANNEL_CONTROL 3 控制器管理 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_MONITOR

3. 使用 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_PKT

4. 查看内核中的 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 的本质:一个允许用户空间程序直接与蓝牙控制器通信的原始接口。

核心特点

  1. 原始访问:绕过 bluetoothd,直接发送 HCI 命令

  2. 多通道支持:不同通道满足不同场景(监控、控制、原始)

  3. 双向通信:既可发送命令,也可接收事件

  4. 内核实现 :位于 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_Parameters
HCI 事件 控制器 → 主机 通知主机状态变化 HCI_LE_Advertising_Report
HCI 数据 双向 传输 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 SOCKET和netlink有啥区别,为啥蓝牙不直接用netlink?

这是一个非常深刻的问题。两者都是内核与用户空间的通信机制,但设计哲学和应用场景完全不同。蓝牙选择 HCI Socket 而非 Netlink,有其历史和技术上的深刻原因。


🎯 核心区别一览

维度 HCI Socket Netlink
协议族 AF_BLUETOOTH AF_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_SCO BTPROTO_L2CAP (CID=ATT)
L2CAP 层 CID (Channel ID) 0x0003 (RFCOMM), 0x0001 (SDP) 0x0004 (ATT), 0x0006 (SMP)
HCI 层 连接句柄范围 0x0000 - 0x0EFF 0x0F00 - 0x0FFF
HCI 包类型 ACL 数据包 HCI_ACLDATA_PKT HCI_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   ← 经典蓝牙句柄 0x000A

BLE: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_PKT ACL 数据(经典蓝牙或 BLE) L2CAP 层
0x03 HCI_SCODATA_PKT SCO 音频数据 上层音频应用
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_LINK BLE
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_PKT vs HCI_ACLDATA_PKT
HCI 核心层 事件/数据分流 事件/数据分流 hci_event_packet vs hci_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/rfcomm0 D-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)传递给用户空间。

相关推荐
能不能别报错2 小时前
openclaw-linux部署教程+mimo-v2-pro
linux·运维·服务器
钛态3 小时前
Flutter for OpenHarmony:mockito 单元测试的替身演员,轻松模拟复杂依赖(测试驱动开发必备) 深度解析与鸿蒙适配指南
服务器·驱动开发·安全·flutter·华为·单元测试·harmonyos
济6173 小时前
ARM Linux 驱动开发篇---基于 pinctrl+GPIO 子系统的蜂鸣器驱动开发(设备树版)--- Ubuntu20.04
linux·嵌入式·嵌入式linux驱动开发
AiGuoHou13 小时前
Debian/Ubuntu 各个版本一键更换国内镜像源
linux·ubuntu·国内源·debian·镜像源·换源
蓝羽天空3 小时前
Ubuntu 24.04 安装 Docker
linux·ubuntu·docker
T0uken3 小时前
WSL:离线配置 Ubuntu 开发环境
linux·运维·ubuntu
�羡阳丶3 小时前
ubuntu22.04+5060显卡双系统安装,各种黑屏踩坑记录
linux·经验分享·ubuntu
b_xinjun11203 小时前
树莓派 Ubuntu 24.04.3 LTS 安装 OpenClaw 操作说明
linux·ubuntu·arcgis
’长谷深风‘3 小时前
嵌入式 ARM 开发入门解析
汇编·arm开发·cpu·cortex