【BlueZ 】input 模块:蓝牙鼠标/键盘等输入设备的基础适配逻辑

BlueZ 作为 Linux 平台上的官方蓝牙协议栈,其 input 模块承担着蓝牙 HID(Human Interface Device,人机接口设备)协议栈的用户态实现。本文将深度拆解 BlueZ 5.x input 模块的源码实现,涵盖经典蓝牙 HIDP 协议和 BLE HOG 协议的完整适配逻辑,为蓝牙输入设备的开发提供实战参考。


目录

一、概述

[二、HID 协议规范基础](#二、HID 协议规范基础)

三、模块初始化与插件注册

[四、HID 服务端实现](#四、HID 服务端实现)

五、输入设备核心数据结构

六、设备注册与连接管理

[七、SDP 记录解析](#七、SDP 记录解析)

[八、UHID 接口与内核对接](#八、UHID 接口与内核对接)

[九、HID 报文解析与事件上报](#九、HID 报文解析与事件上报)

十、键盘与鼠标差异化适配

十一、断线重连机制

[十二、BLE HOG 实现](#十二、BLE HOG 实现)

十三、协议容错机制

十四、模块依赖关系

十五、开发调试要点

十六、核心函数索引


一、概述

1.1 input 模块核心职责

  • 设备管理:管理蓝牙鼠标、键盘、游戏手柄等 HID 设备

  • 协议处理:实现 HIDP(经典蓝牙)和 HOG(BLE)协议栈

  • 内核对接:通过 UHID 接口与 Linux 内核输入子系统交互

  • 连接维护:处理设备配对、连接、断线重连等生命周期

1.2 源码 文件组织

cpp 复制代码
profiles/input/
├── manager.c          # 插件入口,配置加载
├── device.c           # 设备管理,核心实现
├── device.h           # 设备接口定义
├── server.c           # 服务端监听实现
├── server.h           # 服务端接口
├── hidp_defs.h        # HIDP 协议常量定义
├── hog.c              # BLE HOG 实现
├── hog-lib.c          # HOG 库封装
├── hog-lib.h          # HOG 库接口
├── suspend.c          # 系统挂起处理
└── input.conf         # 配置文件

lib/
├── hidp.h             # HIDP 内核接口定义

src/shared/
├── uhid.c             # UHID 用户态封装
└── uhid.h             # UHID 接口定义

二、 HID 协议规范基础

2.1 HID 协议架构

蓝牙 HID 协议分为两层:

  • HIDP( HID Profile):经典蓝牙 BR/EDR 实现,基于 L2CAP 通道

  • HOG( HID over GATT):低功耗蓝牙 BLE 实现,基于 GATT 协议

2.2 HIDP 协议通道

HIDP 使用两个 L2CAP 通道:

cpp 复制代码
// device.h
#define L2CAP_PSM_HIDP_CTRL     0x11    // 控制通道(Command/Response)
#define L2CAP_PSM_HIDP_INTR     0x13    // 中断通道(异步数据传输)

控制通道:用于 HID 报告的获取、设置、协议切换等命令交互

中断通道:用于传输 HID 输入报告(如按键事件、鼠标移动等)

2.3 HIDP 报文格式

cpp 复制代码
// hidp_defs.h - 报文类型掩码
#define HIDP_HEADER_TRANS_MASK      0xf0    // 传输类型
#define HIDP_HEADER_PARAM_MASK      0x0f    // 参数

// HIDP 事务类型
#define HIDP_TRANS_HANDSHAKE        0x00    // 握手
#define HIDP_TRANS_HID_CONTROL      0x10    // HID 控制
#define HIDP_TRANS_GET_REPORT       0x40    // 获取报告
#define HIDP_TRANS_SET_REPORT       0x50    // 设置报告
#define HIDP_TRANS_GET_PROTOCOL     0x60    // 获取协议
#define HIDP_TRANS_SET_PROTOCOL     0x70    // 设置协议
#define HIDP_TRANS_DATA             0xa0    // 数据传输

2.4 HID 报告类型

cpp 复制代码
// hidp_defs.h - 报告类型
#define HIDP_DATA_RTYPE_INPUT       0x01    // 输入报告
#define HIDP_DATA_RTYPE_OUTPUT      0x02    // 输出报告
#define HIDP_DATA_RTYPE_FEATURE     0x03    // 特征报告

三、模块初始化与插件注册

3.1 插件入口实现

BlueZ input 模块以插件形式加载,通过 BLUETOOTH_PLUGIN_DEFINE 宏注册:

cpp 复制代码
// manager.c
static int input_init(void)
{
    GKeyFile *config;
    GError *err = NULL;

    // 加载配置文件
    config = load_config_file(CONFIGDIR "/input.conf");
    if (config) {
        int idle_timeout;
        gboolean uhid_enabled, classic_bonded_only;

        // 读取空闲超时时间(分钟)
        idle_timeout = g_key_file_get_integer(config, "General",
                            "IdleTimeout", &err);
        if (!err) {
            input_set_idle_timeout(idle_timeout * 60);  // 转换为秒
        }

        // 启用用户态 HID(UHID)
        uhid_enabled = g_key_file_get_boolean(config, "General",
                            "UserspaceHID", &err);
        if (!err) {
            input_enable_userspace_hid(uhid_enabled);
        }

        // 限制仅允许已配对设备连接
        classic_bonded_only = g_key_file_get_boolean(config, "General",
                            "ClassicBondedOnly", &err);
        if (!err) {
            input_set_classic_bonded_only(classic_bonded_only);
        }
    }

    // 注册 input profile
    btd_profile_register(&input_profile);

    if (config)
        g_key_file_free(config);

    return 0;
}

BLUETOOTH_PLUGIN_DEFINE(input, VERSION, 
                        BLUETOOTH_PLUGIN_PRIORITY_DEFAULT,
                        input_init, input_exit)

3.2 Profile 结构体定义

input 模块通过 btd_profile 结构体向 BlueZ 核心注册服务:

cpp 复制代码
// manager.c
static struct btd_profile input_profile = {
    .name           = "input-hid",          // Profile 名称
    .local_uuid     = HID_UUID,             // 本地 UUID (0x1124)
    .remote_uuid    = HID_UUID,             // 远端 UUID
    .auto_connect   = true,                 // 自动连接
    .connect        = input_device_connect, // 连接回调
    .disconnect     = input_device_disconnect, // 断开回调
    .device_probe   = input_device_register, // 设备探测
    .device_remove  = input_device_unregister, // 设备移除
    .adapter_probe  = hid_server_probe,     // 适配器探测(启动服务端)
    .adapter_remove = hid_server_remove,    // 适配器移除(停止服务端)
};

3.3 配置文件说明

cpp 复制代码
# input.conf
[General]

# 空闲超时时间(分钟),默认 0 表示不超时
#IdleTimeout=30

# 启用用户态 HID(UHID),默认 false
# 当设为 true 时,HID 协议在用户态处理
# 当设为 false 时,依赖内核 HIDP 模块
#UserspaceHID=true

# 限制仅允许已配对设备连接
# 默认 false 以最大化设备兼容性
#ClassicBondedOnly=true

# BLE 自动安全升级
#LEAutoSecurity=true

四、HID 服务端实现

4.1 服务端启动流程

当蓝牙适配器准备就绪时,hid_server_probe 被调用:

cpp 复制代码
// manager.c
static int hid_server_probe(struct btd_profile *p, 
                            struct btd_adapter *adapter)
{
    return server_start(btd_adapter_get_address(adapter));
}

4.2 服务端核心实现

server_start 函数创建两个 L2CAP 监听通道:

cpp 复制代码
// server.c
int server_start(const bdaddr_t *src)
{
    struct input_server *server;
    GError *err = NULL;
    BtIOSecLevel sec_level;

    // 根据配置选择安全级别
    sec_level = input_get_classic_bonded_only() ?
                BT_IO_SEC_MEDIUM : BT_IO_SEC_LOW;

    server = g_new0(struct input_server, 1);
    bacpy(&server->src, src);

    // 1. 创建控制通道监听 (PSM=0x11)
    server->ctrl = bt_io_listen(connect_event_cb, NULL,
                server, NULL, &err,
                BT_IO_OPT_SOURCE_BDADDR, src,
                BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
                BT_IO_OPT_SEC_LEVEL, sec_level,
                BT_IO_OPT_INVALID);
    if (!server->ctrl) {
        error("Failed to listen on control channel");
        g_free(server);
        return -1;
    }

    // 2. 创建中断通道监听 (PSM=0x13)
    server->intr = bt_io_listen(NULL, confirm_event_cb,
                server, NULL, &err,
                BT_IO_OPT_SOURCE_BDADDR, src,
                BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
                BT_IO_OPT_SEC_LEVEL, sec_level,
                BT_IO_OPT_INVALID);
    if (!server->intr) {
        error("Failed to listen on interrupt channel");
        g_io_channel_unref(server->ctrl);
        g_free(server);
        return -1;
    }

    servers = g_slist_append(servers, server);
    return 0;
}

4.3 连接事件处理

4.3.1 控制通道连接

cpp 复制代码
// server.c
static void connect_event_cb(GIOChannel *chan, GError *err, gpointer data)
{
    uint16_t psm;
    bdaddr_t src, dst;
    char address[18];
    GError *gerr = NULL;
    int ret;

    if (err) {
        error("%s", err->message);
        return;
    }

    // 获取连接信息
    bt_io_get(chan, &gerr,
            BT_IO_OPT_SOURCE_BDADDR, &src,
            BT_IO_OPT_DEST_BDADDR, &dst,
            BT_IO_OPT_PSM, &psm,
            BT_IO_OPT_INVALID);

    ba2str(&dst, address);
    DBG("Incoming connection from %s on PSM %d", address, psm);

    // 设置设备通道
    ret = input_device_set_channel(&src, &dst, psm, chan);
    if (ret == 0)
        return;

    // 处理 Sixaxis 手柄特殊逻辑
    if (ret == -ENOENT && dev_is_sixaxis(&src, &dst)) {
        sixaxis_browse_sdp(&src, &dst, chan, psm);
        return;
    }

    // 拒绝未知设备
    error("Refusing input device connect: %s", strerror(-ret));
    g_io_channel_shutdown(chan, TRUE, NULL);
}

4.3.2 中断通道连接

中断通道连接需要经过授权确认:

cpp 复制代码
// server.c
static void confirm_event_cb(GIOChannel *chan, gpointer user_data)
{
    struct input_server *server = user_data;
    bdaddr_t src, dst;
    GError *err = NULL;
    char addr[18];
    guint ret;

    // 获取连接信息
    bt_io_get(chan, &err,
            BT_IO_OPT_SOURCE_BDADDR, &src,
            BT_IO_OPT_DEST_BDADDR, &dst,
            BT_IO_OPT_INVALID);

    ba2str(&dst, addr);

    // 验证设备是否已知
    if (!input_device_exists(&src, &dst) && !dev_is_sixaxis(&src, &dst)) {
        error("Refusing connection from unknown device");
        goto drop;
    }

    // 保存确认数据
    server->confirm = g_new0(struct confirm_data, 1);
    server->confirm->io = g_io_channel_ref(chan);
    bacpy(&server->confirm->dst, &dst);

    // 请求授权
    ret = btd_request_authorization(&src, &dst, HID_UUID,
                    auth_callback, server);
    if (ret != 0) {
        error("input: authorization for device %s failed", addr);
        input_device_close_channels(&src, &dst);
        g_io_channel_shutdown(chan, TRUE, NULL);
    }
}

五、输入设备核心数据结构

5.1 input_device 结构

cpp 复制代码
// device.c
struct input_device {
    struct btd_service      *service;           // 服务引用
    struct btd_device       *device;            // 设备引用
    char                    *path;               // D-Bus 路径
    bdaddr_t                src;                // 本机蓝牙地址
    bdaddr_t                dst;                // 远端设备地址
    uint32_t                handle;             // SDP 记录句柄
    
    // L2CAP 通道
    GIOChannel              *ctrl_io;           // 控制通道 IO
    GIOChannel              *intr_io;           // 中断通道 IO
    
    // GLib 事件监听
    guint                   ctrl_watch;         // 控制通道监听 ID
    guint                   intr_watch;         // 中断通道监听 ID
    guint                   sec_watch;          // 安全握手监听 ID
    
    // HIDP 连接请求
    struct hidp_connadd_req *req;               // 内核 HIDP 连接请求
    
    // 配置选项
    bool                    disable_sdp;        // 禁用 SDP 查询
    enum reconnect_mode_t   reconnect_mode;     // 重连模式
    
    // 断线重连
    unsigned int            reconnect_timer;    // 重连定时器
    uint32_t                reconnect_attempt;  // 重连尝试次数
    
    // UHID 相关
    struct bt_uhid          *uhid;              // UHID 句柄
    bool                    uhid_created;       // UHID 设备是否已创建
    
    // 报告请求状态
    uint8_t                 report_req_pending; // 待处理的报告请求
    unsigned int            report_req_timer;   // 报告请求定时器
    uint32_t                report_rsp_id;     // 报告响应 ID
    
    // 虚拟电缆拔出标志
    bool                    virtual_cable_unplug;
};

5.2 hidp_connadd_req 结构

cpp 复制代码
// lib/hidp.h
struct hidp_connadd_req {
    int         ctrl_sock;      // 已连接的控制通道 socket
    int         intr_sock;      // 已连接的中断通道 socket
    uint16_t    parser;         // HID 解析器版本
    uint16_t    rd_size;        // 报告描述符大小
    uint8_t     *rd_data;       // 报告描述符数据
    uint8_t     country;        // 国家代码
    uint8_t     subclass;       // 设备子类
    uint16_t    vendor;         // 厂商 ID
    uint16_t    product;        // 产品 ID
    uint16_t    version;        // 版本号
    uint32_t    flags;          // 标志位
    uint32_t    idle_to;        // 空闲超时
    char        name[128];      // 设备名称
};

5.3 reconnect_mode 枚举

cpp 复制代码
// device.c
enum reconnect_mode_t {
    RECONNECT_NONE = 0,     // 不重连
    RECONNECT_DEVICE,       // 设备主动重连
    RECONNECT_HOST,         // 主机主动重连
    RECONNECT_ANY           // 双方都可重连
};

六、设备注册与连接管理

6.1 设备注册流程

当发现新的 HID 设备时,input_device_register 被调用:

cpp 复制代码
// device.c
int input_device_register(struct btd_service *service)
{
    struct btd_device *device = btd_service_get_device(service);
    const char *path = device_get_path(device);
    struct input_device *idev;

    DBG("%s", path);

    // 创建 input_device 结构
    idev = input_device_new(service);
    if (!idev)
        return -EINVAL;

    // 初始化 UHID(如果启用)
    if (uhid_enabled) {
        idev->uhid = bt_uhid_new_default();
        if (!idev->uhid) {
            error("bt_uhid_new_default: failed");
            input_device_free(idev);
            return -EIO;
        }
    }

    // 注册 D-Bus 接口
    if (g_dbus_register_interface(btd_get_dbus_connection(),
                    idev->path, INPUT_INTERFACE,
                    NULL, NULL,
                    input_properties, idev,
                    NULL) == FALSE) {
        error("Unable to register %s interface", INPUT_INTERFACE);
        input_device_free(idev);
        return -EINVAL;
    }

    // 设置服务用户数据
    btd_service_set_user_data(service, idev);
    // 启用唤醒支持
    device_set_wake_support(device, true);

    return 0;
}

6.2 设备创建

cpp 复制代码
// device.c
static struct input_device *input_device_new(struct btd_service *service)
{
    struct btd_device *device = btd_service_get_device(service);
    struct btd_profile *p = btd_service_get_profile(service);
    const char *path = device_get_path(device);
    const sdp_record_t *rec = btd_device_get_record(device, p->remote_uuid);
    struct btd_adapter *adapter = device_get_adapter(device);
    struct input_device *idev;

    if (!rec)
        return NULL;

    idev = g_new0(struct input_device, 1);
    bacpy(&idev->src, btd_adapter_get_address(adapter));
    bacpy(&idev->dst, device_get_address(device));
    idev->service = btd_service_ref(service);
    idev->device = btd_device_ref(device);
    idev->path = g_strdup(path);
    idev->handle = rec->handle;
    idev->disable_sdp = is_device_sdp_disable(rec);

    // 提取 HID 属性(重连模式等)
    extract_hid_props(idev, rec);

    if (idev->disable_sdp)
        device_set_refresh_discovery(device, false);

    return idev;
}

6.3 主动连接流程

6.3.1 启动控制通道连接

cpp 复制代码
// device.c
int input_device_connect(struct btd_service *service)
{
    struct input_device *idev = btd_service_get_user_data(service);

    if (idev->ctrl_io)
        return -EBUSY;

    if (is_connected(idev))
        return -EALREADY;

    return dev_connect(idev);
}

static int dev_connect(struct input_device *idev)
{
    GError *err = NULL;
    GIOChannel *io;
    BtIOSecLevel sec_level;

    // 根据配对状态选择安全级别
    if (input_device_bonded(idev))
        sec_level = BT_IO_SEC_MEDIUM;
    else
        sec_level = BT_IO_SEC_LOW;

    // 连接控制通道 (PSM=0x11)
    io = bt_io_connect(control_connect_cb, idev,
            NULL, &err,
            BT_IO_OPT_SOURCE_BDADDR, &idev->src,
            BT_IO_OPT_DEST_BDADDR, &idev->dst,
            BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL,
            BT_IO_OPT_SEC_LEVEL, sec_level,
            BT_IO_OPT_INVALID);
    idev->ctrl_io = io;

    if (err == NULL)
        return 0;

    return -EIO;
}

6.3.2 控制通道连接回调

cpp 复制代码
// device.c
static void control_connect_cb(GIOChannel *chan, GError *conn_err,
                            gpointer user_data)
{
    struct input_device *idev = user_data;
    GIOChannel *io;
    GError *err = NULL;
    GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL;

    if (conn_err) {
        error("%s", conn_err->message);
        goto failed;
    }

    // 连接中断通道 (PSM=0x13)
    io = bt_io_connect(interrupt_connect_cb, idev,
            NULL, &err,
            BT_IO_OPT_SOURCE_BDADDR, &idev->src,
            BT_IO_OPT_DEST_BDADDR, &idev->dst,
            BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR,
            BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
            BT_IO_OPT_INVALID);
    if (!io) {
        error("%s", err->message);
        goto failed;
    }

    idev->intr_io = io;

    // 设置 IO 监听
    if (idev->uhid)
        cond |= G_IO_IN;

    idev->ctrl_watch = g_io_add_watch(idev->ctrl_io, cond, 
                                    ctrl_watch_cb, idev);
    return;

failed:
    btd_service_connecting_complete(idev->service, -EIO);
    g_io_channel_unref(idev->ctrl_io);
    idev->ctrl_io = NULL;
}

6.3.3 中断通道连接回调

cpp 复制代码
// device.c
static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err,
                            gpointer user_data)
{
    struct input_device *idev = user_data;
    GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL;
    int err;

    if (conn_err) {
        err = -EIO;
        goto failed;
    }

    // 完成设备连接(调用 HIDP 添加连接)
    err = input_device_connected(idev);
    if (err < 0)
        goto failed;

    // 设置中断通道监听
    if (idev->uhid)
        cond |= G_IO_IN;

    idev->intr_watch = g_io_add_watch(idev->intr_io, cond, 
                                    intr_watch_cb, idev);
    return;

failed:
    btd_service_connecting_complete(idev->service, err);
    g_io_channel_unref(idev->intr_io);
    idev->intr_io = NULL;

    if (idev->ctrl_io) {
        g_io_channel_unref(idev->ctrl_io);
        idev->ctrl_io = NULL;
    }
}

6.4 连接建立完成

hidp_add_connection 是连接建立的核心函数,负责与内核 HIDP 模块或 UHID 交互:

cpp 复制代码
// device.c
static int hidp_add_connection(struct input_device *idev)
{
    struct hidp_connadd_req *req;
    sdp_record_t *rec;
    char src_addr[18], dst_addr[18];
    char filename[PATH_MAX];
    GKeyFile *key_file;
    char handle[11], *str;
    GError *gerr = NULL;
    int err;

    req = g_new0(struct hidp_connadd_req, 1);
    req->ctrl_sock = g_io_channel_unix_get_fd(idev->ctrl_io);
    req->intr_sock = g_io_channel_unix_get_fd(idev->intr_io);
    req->flags     = 0;
    req->idle_to   = idle_timeout;

    // 从 SDP 缓存加载设备信息
    ba2str(&idev->src, src_addr);
    ba2str(&idev->dst, dst_addr);
    snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", 
            src_addr, dst_addr);
    
    sprintf(handle, "0x%8.8X", idev->handle);
    key_file = g_key_file_new();
    if (!g_key_file_load_from_file(key_file, filename, 0, &gerr)) {
        error("Unable to load key file from %s: (%s)", 
                filename, gerr->message);
        g_clear_error(&gerr);
    }
    str = g_key_file_get_string(key_file, "ServiceRecords", 
                        handle, NULL);
    g_key_file_free(key_file);

    if (!str) {
        error("Rejected connection from unknown device %s", dst_addr);
        err = -EPERM;
        goto cleanup;
    }

    // 解析 SDP 记录提取 HID 参数
    rec = record_from_string(str);
    g_free(str);

    err = extract_hid_record(rec, req);
    sdp_record_free(rec);
    if (err < 0) {
        error("Could not parse HID SDP record: %s (%d)", 
                strerror(-err), -err);
        goto cleanup;
    }

    req->vendor = btd_device_get_vendor(idev->device);
    req->product = btd_device_get_product(idev->device);
    req->version = btd_device_get_version(idev->device);

    // 根据配置选择 UHID 或内核 HIDP
    if (idev->uhid)
        err = uhid_connadd(idev, req);      // 用户态 HID
    else
        err = ioctl_connadd(req);           // 内核 HIDP

cleanup:
    g_free(req->rd_data);
    g_free(req);
    return err;
}

七、SDP 记录解析

7.1 提取 HID 参数

extract_hid_record 从 SDP 服务记录中解析 HID 相关属性:

cpp 复制代码
// device.c
static int extract_hid_record(sdp_record_t *rec, 
                            struct hidp_connadd_req *req)
{
    sdp_data_t *pdlist;
    uint8_t attr_val;
    int err;

    // 获取设备名称
    err = create_hid_dev_name(rec, req);
    if (err < 0)
        DBG("No valid Service Name or Service Description found");

    // 解析 HID 解析器版本
    pdlist = sdp_data_get(rec, SDP_ATTR_HID_PARSER_VERSION);
    req->parser = pdlist ? pdlist->val.uint16 : 0x0100;

    // 解析设备子类
    pdlist = sdp_data_get(rec, SDP_ATTR_HID_DEVICE_SUBCLASS);
    req->subclass = pdlist ? pdlist->val.uint8 : 0;

    // 解析国家代码
    pdlist = sdp_data_get(rec, SDP_ATTR_HID_COUNTRY_CODE);
    req->country = pdlist ? pdlist->val.uint8 : 0;

    // 虚拟电缆支持
    pdlist = sdp_data_get(rec, SDP_ATTR_HID_VIRTUAL_CABLE);
    attr_val = pdlist ? pdlist->val.uint8 : 0;
    if (attr_val)
        req->flags |= (1 << HIDP_VIRTUAL_CABLE_UNPLUG);

    // Boot 协议支持
    pdlist = sdp_data_get(rec, SDP_ATTR_HID_BOOT_DEVICE);
    attr_val = pdlist ? pdlist->val.uint8 : 0;
    if (attr_val)
        req->flags |= (1 << HIDP_BOOT_PROTOCOL_MODE);

    // 提取 HID 描述符(报告描述符)
    err = extract_hid_desc_data(rec, req);
    if (err < 0)
        return err;

    return 0;
}

7.2 提取 HID 描述符

HID 描述符是 HID 设备的核心,包含报告格式等信息:

cpp 复制代码
// device.c
static int extract_hid_desc_data(sdp_record_t *rec,
                            struct hidp_connadd_req *req)
{
    sdp_data_t *d;

    // 获取 HIDDescriptorList 属性
    d = sdp_data_get(rec, SDP_ATTR_HID_DESCRIPTOR_LIST);
    if (!d)
        goto invalid_desc;

    if (!SDP_IS_SEQ(d->dtd))
        goto invalid_desc;

    // 遍历描述符列表(通常只有一个 HID 描述符)
    d = d->val.dataseq;
    if (!SDP_IS_SEQ(d->dtd))
        goto invalid_desc;

    // 获取描述符类型
    d = d->val.dataseq;
    if (d->dtd != SDP_UINT8)
        goto invalid_desc;

    // 获取描述符数据(报告描述符)
    d = d->next;
    if (!d || !SDP_IS_TEXT_STR(d->dtd))
        goto invalid_desc;

    req->rd_data = g_try_malloc0(d->unitSize);
    if (req->rd_data) {
        memcpy(req->rd_data, d->val.str, d->unitSize);
        req->rd_size = d->unitSize;
        // 修复某些设备的字节序问题
        epox_endian_quirk(req->rd_data, req->rd_size);
    }

    return 0;

invalid_desc:
    error("Missing or invalid HIDDescriptorList SDP attribute");
    return -EINVAL;
}

7.3 重连模式解析

cpp 复制代码
// device.c
static void extract_hid_props(struct input_device *idev,
                            const sdp_record_t *rec)
{
    bool reconnect_initiate, normally_connectable;
    sdp_data_t *pdlist;

    // HIDReconnectInitiate 表示谁发起重连
    pdlist = sdp_data_get(rec, SDP_ATTR_HID_RECONNECT_INITIATE);
    reconnect_initiate = pdlist ? pdlist->val.uint8 : TRUE;

    // HIDNormallyConnectable 表示设备是否通常可连接
    pdlist = sdp_data_get(rec, SDP_ATTR_HID_NORMALLY_CONNECTABLE);
    normally_connectable = pdlist ? pdlist->val.uint8 : FALSE;

    // 计算重连模式
    idev->reconnect_mode =
        hid_reconnection_mode(reconnect_initiate, normally_connectable);
}

static enum reconnect_mode_t hid_reconnection_mode(
        bool reconnect_initiate, bool normally_connectable)
{
    if (!reconnect_initiate && !normally_connectable)
        return RECONNECT_NONE;        // 双方都不重连
    else if (!reconnect_initiate && normally_connectable)
        return RECONNECT_HOST;        // 主机重连
    else if (reconnect_initiate && !normally_connectable)
        return RECONNECT_DEVICE;      // 设备重连
    else
        return RECONNECT_ANY;         // 双方都可重连
}

八、UHID 接口与内核对接

8.1 UHID 架构概述

UHID(User-Space HID)是 Linux 内核提供的接口,允许用户态程序实现 HID 设备。BlueZ 通过 UHID 实现完整的用户态 HID 处理。

cpp 复制代码
// src/shared/uhid.h
struct bt_uhid;

// 创建默认 UHID 实例(使用 /dev/uhid)
struct bt_uhid *bt_uhid_new_default(void);

// 从指定 fd 创建 UHID 实例
struct bt_uhid *bt_uhid_new(int fd);

// 引用计数管理
struct bt_uhid *bt_uhid_ref(struct bt_uhid *uhid);
void bt_uhid_unref(struct bt_uhid *uhid);

// 注册事件回调
typedef void (*bt_uhid_callback_t)(struct uhid_event *ev, void *user_data);
unsigned int bt_uhid_register(struct bt_uhid *uhid, uint32_t event,
                bt_uhid_callback_t func, void *user_data);
bool bt_uhid_unregister(struct bt_uhid *uhid, unsigned int id);
bool bt_uhid_unregister_all(struct bt_uhid *uhid);

// 发送事件到内核
int bt_uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev);

8.2 UHID 实现核心

cpp 复制代码
// src/shared/uhid.c
#define UHID_DEVICE_FILE "/dev/uhid"

struct bt_uhid {
    int ref_count;
    struct io *io;              // IO 封装
    unsigned int notify_id;     // 通知 ID 计数器
    struct queue *notify_list;  // 通知回调队列
};

struct uhid_notify {
    unsigned int id;            // 通知 ID
    uint32_t event;             // 事件类型
    bt_uhid_callback_t func;    // 回调函数
    void *user_data;            // 用户数据
};

// 创建默认 UHID 实例
struct bt_uhid *bt_uhid_new_default(void)
{
    struct bt_uhid *uhid;
    int fd;

    fd = open(UHID_DEVICE_FILE, O_RDWR | O_CLOEXEC);
    if (fd < 0)
        return NULL;

    uhid = bt_uhid_new(fd);
    if (!uhid) {
        close(fd);
        return NULL;
    }

    io_set_close_on_destroy(uhid->io, true);
    return uhid;
}

// 读取事件并分发
static bool uhid_read_handler(struct io *io, void *user_data)
{
    struct bt_uhid *uhid = user_data;
    int fd = io_get_fd(io);
    ssize_t len;
    struct uhid_event ev;

    memset(&ev, 0, sizeof(ev));
    len = read(fd, &ev, sizeof(ev));
    if (len < (ssize_t) sizeof(ev.type))
        return false;

    // 遍历所有注册的通知回调
    queue_foreach(uhid->notify_list, notify_handler, &ev);
    return true;
}

// 发送事件到内核
int bt_uhid_send(struct bt_uhid *uhid, const struct uhid_event *ev)
{
    ssize_t len;
    struct iovec iov;

    if (!uhid->io)
        return -ENOTCONN;

    iov.iov_base = (void *) ev;
    iov.iov_len = sizeof(*ev);
    len = io_send(uhid->io, &iov, 1);
    if (len < 0)
        return -errno;

    return len != sizeof(*ev) ? -EIO : 0;
}

8.3 UHID 设备创建

cpp 复制代码
// device.c
static int uhid_connadd(struct input_device *idev, 
                        struct hidp_connadd_req *req)
{
    int err;
    struct uhid_event ev;

    if (idev->uhid_created)
        return 0;

    // 创建 UHID 设备
    memset(&ev, 0, sizeof(ev));
    ev.type = UHID_CREATE;
    strncpy((char *) ev.u.create.name, req->name, 
            sizeof(ev.u.create.name));
    ba2strlc(&idev->src, (char *) ev.u.create.phys);
    ba2strlc(&idev->dst, (char *) ev.u.create.uniq);
    ev.u.create.vendor = req->vendor;
    ev.u.create.product = req->product;
    ev.u.create.version = req->version;
    ev.u.create.country = req->country;
    ev.u.create.bus = BUS_BLUETOOTH;
    ev.u.create.rd_data = req->rd_data;
    ev.u.create.rd_size = req->rd_size;

    err = bt_uhid_send(idev->uhid, &ev);
    if (err < 0) {
        error("bt_uhid_send: %s", strerror(-err));
        return err;
    }

    // 注册事件回调
    bt_uhid_register(idev->uhid, UHID_OUTPUT, 
                    hidp_send_output, idev);
    bt_uhid_register(idev->uhid, UHID_GET_REPORT, 
                    hidp_send_get_report, idev);
    bt_uhid_register(idev->uhid, UHID_SET_REPORT, 
                    hidp_send_set_report, idev);

    idev->uhid_created = true;
    return err;
}

8.4 UHID 事件处理

8.4.1 输出报告处理(键盘 LED、震动等)

cpp 复制代码
// device.c
static void hidp_send_output(struct uhid_event *ev, void *user_data)
{
    struct input_device *idev = user_data;
    uint8_t hdr = HIDP_TRANS_DATA | HIDP_DATA_RTYPE_OUTPUT;

    // 通过中断通道发送输出报告
    hidp_send_intr_message(idev, hdr, 
                        ev->u.output.data, ev->u.output.size);
}

8.4.2 获取报告请求

cpp 复制代码
// device.c
static void hidp_send_get_report(struct uhid_event *ev, void *user_data)
{
    struct input_device *idev = user_data;
    uint8_t hdr;
    bool sent;

    if (idev->report_req_pending) {
        // 已有请求在处理中,返回忙状态
        uhid_send_get_report_reply(idev, NULL, 0, 
                                ev->u.get_report.id, EBUSY);
        return;
    }

    // 根据报告类型构造 HIDP 报文
    switch (ev->u.get_report.rtype) {
    case UHID_FEATURE_REPORT:
        hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_FEATURE;
        break;
    case UHID_INPUT_REPORT:
        hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_INPUT;
        break;
    case UHID_OUTPUT_REPORT:
        hdr = HIDP_TRANS_GET_REPORT | HIDP_DATA_RTYPE_OUTPUT;
        break;
    default:
        return;
    }

    // 发送到控制通道
    sent = hidp_send_ctrl_message(idev, hdr, 
                    &ev->u.get_report.rnum,
                    sizeof(ev->u.get_report.rnum));
    if (sent) {
        // 设置超时定时器
        idev->report_req_pending = hdr;
        idev->report_req_timer =
            timeout_add_seconds(REPORT_REQ_TIMEOUT,
                    hidp_report_req_timeout, idev, NULL);
        idev->report_rsp_id = ev->u.get_report.id;
    } else {
        uhid_send_get_report_reply(idev, NULL, 0, 
                                ev->u.get_report.id, EIO);
    }
}

8.4.3 设置报告请求

cpp 复制代码
// device.c
static void hidp_send_set_report(struct uhid_event *ev, void *user_data)
{
    struct input_device *idev = user_data;
    uint8_t hdr;
    bool sent;

    if (idev->report_req_pending) {
        uhid_send_set_report_reply(idev, ev->u.set_report.id, EBUSY);
        return;
    }

    switch (ev->u.set_report.rtype) {
    case UHID_FEATURE_REPORT:
        hdr = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_FEATURE;
        break;
    case UHID_INPUT_REPORT:
        hdr = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_INPUT;
        break;
    case UHID_OUTPUT_REPORT:
        hdr = HIDP_TRANS_SET_REPORT | HIDP_DATA_RTYPE_OUTPUT;
        break;
    default:
        return;
    }

    sent = hidp_send_ctrl_message(idev, hdr, 
                    ev->u.set_report.data, ev->u.set_report.size);
    if (sent) {
        idev->report_req_pending = hdr;
        idev->report_req_timer =
            timeout_add_seconds(REPORT_REQ_TIMEOUT,
                    hidp_report_req_timeout, idev, NULL);
        idev->report_rsp_id = ev->u.set_report.id;
    } else {
        uhid_send_set_report_reply(idev, ev->u.set_report.id, EIO);
    }
}

九、HID 报文解析与事件上报

9.1 中断通道数据接收

中断通道主要接收 HID 输入报告(如按键、鼠标移动):

cpp 复制代码
// device.c
static bool hidp_recv_intr_data(GIOChannel *chan, 
                                struct input_device *idev)
{
    int fd;
    ssize_t len;
    uint8_t hdr;
    uint8_t data[UHID_DATA_MAX + 1];

    fd = g_io_channel_unix_get_fd(chan);
    len = read(fd, data, sizeof(data));
    if (len < 0) {
        error("BT socket read error: %s (%d)", strerror(errno));
        return false;
    }

    if (len == 0) {
        DBG("BT socket read returned 0 bytes");
        return true;
    }

    hdr = data[0];
    // 检查是否为输入报告
    if (hdr != (HIDP_TRANS_DATA | HIDP_DATA_RTYPE_INPUT)) {
        DBG("unsupported HIDP protocol header 0x%02x", hdr);
        return true;
    }

    if (len < 2) {
        DBG("received empty HID report");
        return true;
    }

    // 发送输入报告到 UHID
    uhid_send_input_report(idev, data + 1, len - 1);
    return true;
}

9.2 输入报告上报内核

cpp 复制代码
// device.c
static bool uhid_send_input_report(struct input_device *idev,
                                const uint8_t *data, size_t size)
{
    struct uhid_event ev;
    int err;

    if (data == NULL)
        size = 0;

    if (size > sizeof(ev.u.input.data))
        size = sizeof(ev.u.input.data);

    if (!idev->uhid_created) {
        DBG("HID report (%zu bytes) dropped", size);
        return false;
    }

    memset(&ev, 0, sizeof(ev));
    ev.type = UHID_INPUT;
    ev.u.input.size = size;

    if (size > 0)
        memcpy(ev.u.input.data, data, size);

    err = bt_uhid_send(idev->uhid, &ev);
    if (err < 0) {
        error("bt_uhid_send: %d", -err);
        return false;
    }

    DBG("HID report (%zu bytes)", size);
    return true;
}

9.3 控制通道消息处理

cpp 复制代码
// device.c
static bool hidp_recv_ctrl_message(GIOChannel *chan, 
                                struct input_device *idev)
{
    int fd;
    ssize_t len;
    uint8_t hdr, type, param;
    uint8_t data[UHID_DATA_MAX + 1];

    fd = g_io_channel_unix_get_fd(chan);
    len = read(fd, data, sizeof(data));
    if (len < 0) {
        error("BT socket read error: %s (%d)", strerror(errno));
        return false;
    }

    if (len == 0)
        return true;

    hdr = data[0];
    type = hdr & HIDP_HEADER_TRANS_MASK;
    param = hdr & HIDP_HEADER_PARAM_MASK;

    // 根据消息类型分发处理
    switch (type) {
    case HIDP_TRANS_HANDSHAKE:
        hidp_recv_ctrl_handshake(idev, param);
        break;
    case HIDP_TRANS_HID_CONTROL:
        hidp_recv_ctrl_hid_control(idev, param);
        break;
    case HIDP_TRANS_DATA:
        hidp_recv_ctrl_data(idev, param, data, len);
        break;
    default:
        error("unsupported HIDP control message");
        break;
    }

    return true;
}

9.4 握手响应处理

cpp 复制代码
// device.c
static void hidp_recv_ctrl_handshake(struct input_device *idev, 
                                    uint8_t param)
{
    uint8_t pending_req_type = idev->report_req_pending & 
                            HIDP_HEADER_TRANS_MASK;
    bool pending_req_complete = false;

    switch (param) {
    case HIDP_HSHK_SUCCESSFUL:
        if (pending_req_type == HIDP_TRANS_SET_REPORT) {
            DBG("SET_REPORT successful");
            pending_req_complete = true;
        }
        break;

    case HIDP_HSHK_NOT_READY:
    case HIDP_HSHK_ERR_INVALID_REPORT_ID:
    case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST:
    case HIDP_HSHK_ERR_INVALID_PARAMETER:
    case HIDP_HSHK_ERR_UNKNOWN:
    case HIDP_HSHK_ERR_FATAL:
        if (pending_req_type == HIDP_TRANS_GET_REPORT) {
            DBG("GET_REPORT failed (%u)", param);
            uhid_send_get_report_reply(idev, NULL, 0,
                        idev->report_rsp_id, EIO);
            pending_req_complete = true;
        } else if (pending_req_type == HIDP_TRANS_SET_REPORT) {
            DBG("SET_REPORT failed (%u)", param);
            uhid_send_set_report_reply(idev, idev->report_rsp_id, EIO);
            pending_req_complete = true;
        }
        break;

    default:
        hidp_send_ctrl_message(idev, HIDP_TRANS_HANDSHAKE |
                HIDP_HSHK_ERR_INVALID_PARAMETER, NULL, 0);
        break;
    }

    if (pending_req_complete) {
        idev->report_req_pending = 0;
        if (idev->report_req_timer > 0)
            timeout_remove(idev->report_req_timer);
        idev->report_rsp_id = 0;
    }
}

十、键盘与鼠标差异化适配

10.1 设备类型识别

BlueZ 通过 SDP 记录中的 HID Device Subclass 属性区分设备类型:

cpp 复制代码
// 设备子类定义(来自 HID Profile 规范)
#define HID_SUBCANT_MOUSE       0x00    // 鼠标
#define HID_SUBCANT_JOYSTICK    0x01    // 操纵杆
#define HID_SUBCANT_GAMEPAD     0x02    // 游戏手柄
#define HID_SUBCANT_KEYBOARD    0x40    // 键盘
#define HID_SUBCANT_POINTER     0x80    // 指针设备
#define HID_SUBCANT_TOUCHSCREEN 0xC0    // 触摸屏

10.2 键盘特殊处理

键盘设备需要强制加密连接:

cpp 复制代码
// device.c - hidp_add_connection
if (classic_bonded_only || req->subclass & 0x40) {
    // 键盘设备强制使用 MEDIUM 安全级别
    if (!bt_io_set(idev->intr_io, &gerr,
                BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
                BT_IO_OPT_INVALID)) {
        error("btio: %s", gerr->message);
        err = -EFAULT;
        goto cleanup;
    }

    idev->req = req;
    // 设置加密完成监听
    idev->sec_watch = g_io_add_watch(idev->intr_io, 
                            G_IO_OUT, encrypt_notify, idev);
    return 0;
}

10.3 输入报告差异

键盘和鼠标的输入报告格式不同,但 BlueZ 透明处理这些差异:

  • 键盘报告:通常包含修饰键状态、按键码数组

  • 鼠标报告:通常包含按键状态、X/Y 位移、滚轮数据

  • 游戏手柄报告:包含摇杆位置、按钮状态、方向键等

这些差异由 HID 报告描述符定义,BlueZ 将原始报告数据直接传递给内核,由内核解析。

十一、断线重连机制

11.1 重连模式

cpp 复制代码
// device.c
static const char * const _reconnect_mode_str[] = {
    "none",      // 不重连
    "device",    // 设备主动重连
    "host",      // 主机主动重连
    "any"        // 双方都可重连
};

11.2 进入重连模式

cpp 复制代码
// device.c
static void input_device_enter_reconnect_mode(struct input_device *idev)
{
    // 检查是否需要配对
    if (classic_bonded_only && !input_device_bonded(idev))
        return;

    // 检查重连模式
    if (idev->reconnect_mode != RECONNECT_ANY &&
            idev->reconnect_mode != RECONNECT_HOST)
        return;

    // 检查设备状态
    if (device_is_temporary(idev->device) ||
                    btd_device_is_connected(idev->device))
        return;

    if (idev->reconnect_timer > 0)
        timeout_remove(idev->reconnect_timer);

    DBG("registering auto-reconnect");
    idev->reconnect_attempt = 0;
    // 每 30 秒尝试重连一次
    idev->reconnect_timer = timeout_add_seconds(30,
                input_device_auto_reconnect, idev, NULL);
}

11.3 自动重连实现

cpp 复制代码
// device.c
static bool input_device_auto_reconnect(gpointer user_data)
{
    struct input_device *idev = user_data;

    DBG("path=%s, attempt=%d", idev->path, idev->reconnect_attempt);

    // 停止条件检查
    if (device_is_temporary(idev->device) ||
                    btd_device_is_connected(idev->device))
        goto bail;

    // 最多重连 6 次(3 分钟)
    if (idev->reconnect_attempt >= 6)
        goto bail;

    // 检查是否已连接
    if (idev->ctrl_io)
        goto bail;

    if (is_connected(idev))
        goto bail;

    idev->reconnect_attempt++;
    dev_connect(idev);

    return TRUE;

bail:
    idev->reconnect_timer = 0;
    return FALSE;
}

11.4 连接断开处理

cpp 复制代码
// device.c - 中断通道断开回调
static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, 
                            gpointer data)
{
    struct input_device *idev = data;
    char address[18];

    if (cond & G_IO_IN) {
        if (hidp_recv_intr_data(chan, idev) && (cond == G_IO_IN))
            return TRUE;
    }

    ba2str(&idev->dst, address);
    DBG("Device %s disconnected", address);

    // 处理断开
    if ((cond & (G_IO_HUP | G_IO_ERR)) && idev->ctrl_watch)
        g_io_channel_shutdown(chan, TRUE, NULL);

    idev->intr_watch = 0;

    if (idev->intr_io) {
        g_io_channel_unref(idev->intr_io);
        idev->intr_io = NULL;
    }

    // 断开控制通道
    if (idev->ctrl_io && !(cond & (G_IO_NVAL | G_IO_ERR)))
        g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL);

    btd_service_disconnecting_complete(idev->service, 0);

    // 进入自动重连模式
    input_device_enter_reconnect_mode(idev);

    // 处理虚拟电缆拔出
    if (!idev->ctrl_io && idev->virtual_cable_unplug)
        virtual_cable_unplug(idev);

    // 断开 UHID
    if (idev->uhid_created)
        uhid_disconnect(idev);

    return FALSE;
}

十二、BLE HOG 实现

12.1 HOG 架构

BLE HOG(HID over GATT)用于低功耗蓝牙设备,实现更简洁:

cpp 复制代码
// hog.c
struct hog_device {
    struct btd_device  *device;     // 设备引用
    struct bt_hog      *hog;        // HOG 协议实例
};

12.2 HOG Profile 注册

cpp 复制代码
// hog.c
static struct btd_profile hog_profile = {
    .name           = "input-hog",
    .remote_uuid    = HOG_UUID,           // 0x1812
    .device_probe   = hog_probe,
    .device_remove  = hog_remove,
    .accept         = hog_accept,
    .disconnect     = hog_disconnect,
    .auto_connect   = true,
};

12.3 HOG 设备接受

cpp 复制代码
// hog.c
static int hog_accept(struct btd_service *service)
{
    struct hog_device *dev = btd_service_get_user_data(service);
    struct btd_device *device = btd_service_get_device(service);
    struct gatt_db *db = btd_device_get_gatt_db(device);
    GAttrib *attrib = btd_device_get_attrib(device);

    // 创建 HOG 实例(如果尚未创建)
    if (!dev->hog) {
        hog_device_accept(dev, db);
        if (!dev->hog)
            return -EINVAL;
    }

    // HOGP 1.0 要求配对
    if (!device_is_bonded(device, btd_device_get_bdaddr_type(device))) {
        struct bt_gatt_client *client;

        if (!auto_sec)
            return -ECONNREFUSED;

        client = btd_device_get_gatt_client(device);
        // 自动升级安全级别
        if (!bt_gatt_client_set_security(client,
                        BT_ATT_SECURITY_MEDIUM))
            return -ECONNREFUSED;
    }

    // 附加到 GATT 层
    bt_hog_attach(dev->hog, attrib);
    btd_service_connecting_complete(service, 0);

    return 0;
}

12.4 HOG 库接口

cpp 复制代码
// hog-lib.h
struct bt_hog;

// 创建 HOG 实例
struct bt_hog *bt_hog_new_default(const char *name, uint16_t vendor,
                    uint16_t product, uint16_t version,
                    struct gatt_db *db);
struct bt_hog *bt_hog_new(int fd, const char *name, uint16_t vendor,
                    uint16_t product, uint16_t version,
                    struct gatt_db *db);

// 引用计数
struct bt_hog *bt_hog_ref(struct bt_hog *hog);
void bt_hog_unref(struct bt_hog *hog);

// 附加/分离 GATT
bool bt_hog_attach(struct bt_hog *hog, void *gatt);
void bt_hog_detach(struct bt_hog *hog);

// 控制与数据
int bt_hog_set_control_point(struct bt_hog *hog, bool suspend);
int bt_hog_send_report(struct bt_hog *hog, void *data, size_t size, int type);

12.5 HOG 与 HIDP 对比

|--------|-----------------|---------------|
| 特性 | HIDP (经典蓝牙) | HOG (BLE) |
| 协议层 | L2CAP | GATT |
| 通道数 | 2(控制+中断) | 1(单一连接) |
| 功耗 | 较高 | 较低 |
| 延迟 | 较低 | 稍高 |
| 适用场景 | 高性能设备 | 低功耗设备 |
| 实现复杂度 | 较高 | 较低 |

十三、协议容错机制

13.1 报告请求超时

cpp 复制代码
// device.c
#define REPORT_REQ_TIMEOUT  3

static bool hidp_report_req_timeout(gpointer data)
{
    struct input_device *idev = data;
    uint8_t pending_req_type;

    pending_req_type = idev->report_req_pending & 
                        HIDP_HEADER_TRANS_MASK;

    switch (pending_req_type) {
    case HIDP_TRANS_GET_REPORT:
        // 超时返回 ETIMEDOUT
        uhid_send_get_report_reply(idev, NULL, 0,
                    idev->report_rsp_id, ETIMEDOUT);
        break;
    case HIDP_TRANS_SET_REPORT:
        uhid_send_set_report_reply(idev, idev->report_rsp_id,
                                ETIMEDOUT);
        break;
    }

    DBG("Device HIDP request timed out");
    idev->report_req_pending = 0;
    idev->report_req_timer = 0;
    idev->report_rsp_id = 0;

    return FALSE;
}

13.2 错误码定义

cpp 复制代码
// HIDP 握手错误码
#define HIDP_HSHK_SUCCESSFUL            0x00    // 成功
#define HIDP_HSHK_NOT_READY             0x01    // 设备未就绪
#define HIDP_HSHK_ERR_INVALID_REPORT_ID 0x02   // 无效报告 ID
#define HIDP_HSHK_ERR_UNSUPPORTED_REQUEST 0x03 // 不支持的请求
#define HIDP_HSHK_ERR_INVALID_PARAMETER  0x04  // 无效参数
#define HIDP_HSHK_ERR_UNKNOWN           0x0e    // 未知错误
#define HIDP_HSHK_ERR_FATAL             0x0f    // 致命错误

13.3 字节序修复

cpp 复制代码
// device.c
static void epox_endian_quirk(unsigned char *data, int size)
{
    // 修复某些设备报告描述符的字节序问题
    unsigned char pattern[] = { 
        0x05, 0x07, 0x19, 0x00, 0x2a, 0x00, 0xff,
        0x15, 0x00, 0x26, 0x00, 0xff 
    };
    unsigned int i;

    if (!data)
        return;

    for (i = 0; i < size - sizeof(pattern); i++) {
        if (!memcmp(data + i, pattern, sizeof(pattern))) {
            // 交换字节序
            data[i + 5] = 0xff;
            data[i + 6] = 0x00;
            data[i + 10] = 0xff;
            data[i + 11] = 0x00;
        }
    }
}

13.4 虚拟电缆管理

cpp 复制代码
// device.c
static void virtual_cable_unplug(struct input_device *idev)
{
    // 移除设备绑定
    device_remove_bonding(idev->device,
                btd_device_get_bdaddr_type(idev->device));
    idev->virtual_cable_unplug = false;
}

// 发送虚拟电缆拔出指令
if (hdr == (HIDP_TRANS_HID_CONTROL | HIDP_CTRL_VIRTUAL_CABLE_UNPLUG))
    idev->virtual_cable_unplug = true;

十四、模块依赖关系

14.1 与 device 模块

  • 设备发现:通过 btd_adapter_find_device 查找设备

  • 设备属性:获取 Vendor ID、Product ID、Version 等

  • 设备状态:检查配对状态、连接状态等

  • D-Bus 路径:注册 Input1 接口的 D-Bus 属性

14.2 与 profile 模块

  • Profile 注册:通过 btd_profile_register 注册 HID Profile

  • 回调机制:实现 connect、disconnect、device_probe 等回调

  • 服务管理:通过 btd_service 管理连接生命周期

14.3 与 hci 模块

  • L2CAP 通道:通过 bt_io_listen 和 bt_io_connect 创建 L2CAP 通道

  • 安全设置:设置链路安全级别(LOW/MEDIUM)

  • 适配器操作:通过 btd_adapter_get_address 获取适配器信息

14.4 与 dbus 模块

  • 接口注册:注册 org.bluez.Input1 D-Bus 接口

  • 属性暴露:暴露 ReconnectMode 等属性

  • 授权请求:通过 btd_request_authorization 请求用户授权

14.5 与 storage 模块

  • SDP 缓存:从缓存加载设备 SDP 记录

  • 持久化存储:设备绑定信息持久化

14.6 与内核接口

  • HIDP 内核模块:通过 ioctl 与内核 HIDP 交互

  • UHID 接口:通过 /dev/uhid 与内核 HID 子系统交互

十五、开发调试要点

15.1 启用调试日志

在 BlueZ 源码中启用调试日志:

cpp 复制代码
# 编译时启用调试
./configure --enable-debug

# 运行时设置环境变量
export BLUETOOD_DEBUG=input:*

15.2 使用 btmon 抓包

cpp 复制代码
# 安装 btmon
sudo apt install bluez-tools

# 启动抓包
sudo btmon

# 过滤 HIDP 相关事件
sudo btmon | grep -i hidp

15.3 使用 hcidump 分析

cpp 复制代码
# 安装 hcidump
sudo apt install bluez-hcidump

# 抓取 HCI 日志
sudo hcidump -t > hci_log.txt

# 分析 HIDP 报文
grep -A5 "HIDP" hci_log.txt

15.4 检查 UHID 设备

cpp 复制代码
# 查看所有 UHID 设备
ls -la /dev/uhid*

# 查看 HID 设备
cat /sys/class/input/input*/name

# 使用 hidraw 测试
cat /dev/hidraw0 | xxd | head -20

15.5 检查 HIDP 连接

cpp 复制代码
# 查看 HIDP 连接状态
cat /proc/net/bluetooth

# 使用 bluetoothctl 测试
bluetoothctl
# 在 bluetoothctl 中:
# info <device_address>  # 查看设备信息
# uuid <device_address>  # 查看支持的 UUID

15.6 常见问题排查

问题1:设备无法连接

cpp 复制代码
# 检查 dmesg 日志
dmesg | grep -i hid

# 检查 bluetoothd 日志
sudo systemctl status bluetooth
sudo journalctl -u bluetooth -f

问题2:输入报告丢失

cpp 复制代码
// 在 device.c 中添加调试
static bool uhid_send_input_report(struct input_device *idev, ...)
{
    // 添加详细日志
    DBG("UHID_INPUT: size=%zu, data=%s", size, 
        bytes_to_hex(data, size));
    
    // 检查 UHID 设备是否创建
    if (!idev->uhid_created) {
        DBG("UHID device not created!");
        return false;
    }
    
    // ...
}

问题3:报告请求超时

cpp 复制代码
# 检查设备响应
sudo btmon -w trace.log
# 分析 trace.log 查看 HIDP 交互

问题4:键盘加密失败

cpp 复制代码
// 检查加密设置
if (classic_bonded_only || req->subclass & 0x40) {
    // 添加调试日志
    DBG("Enforcing encryption for keyboard device");
    
    // 确保使用正确的安全级别
    if (!bt_io_set(idev->intr_io, &gerr,
                BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
                BT_IO_OPT_INVALID)) {
        error("Encryption failed: %s", gerr->message);
    }
}

十六、核心函数索引

16.1 初始化相关

|-------------------------|-----------|------------|
| 函数 | 文件 | 说明 |
| input_init | manager.c | 插件初始化入口 |
| server_start | server.c | 启动 HID 服务端 |
| input_device_register | device.c | 注册输入设备 |
| input_device_new | device.c | 创建输入设备结构 |

16.2 连接管理

|---------------------------|----------|-------------|
| 函数 | 文件 | 说明 |
| input_device_connect | device.c | 主动连接设备 |
| input_device_disconnect | device.c | 断开设备连接 |
| dev_connect | device.c | 发起 L2CAP 连接 |
| hidp_add_connection | device.c | 添加 HIDP 连接 |
| hidp_send_message | device.c | 发送 HIDP 报文 |

16.3 协议处理

|----------------------------|----------|----------|
| 函数 | 文件 | 说明 |
| hidp_recv_intr_data | device.c | 接收中断通道数据 |
| hidp_recv_ctrl_message | device.c | 接收控制通道消息 |
| hidp_recv_ctrl_handshake | device.c | 处理握手响应 |
| hidp_recv_ctrl_data | device.c | 处理数据响应 |

16.4 UHID 相关

|--------------------------|----------|--------------|
| 函数 | 文件 | 说明 |
| bt_uhid_new_default | uhid.c | 创建默认 UHID 实例 |
| uhid_connadd | device.c | 创建 UHID 设备 |
| uhid_disconnect | device.c | 断开 UHID 连接 |
| uhid_send_input_report | device.c | 发送输入报告到内核 |
| hidp_send_output | device.c | 处理输出报告 |
| hidp_send_get_report | device.c | 处理获取报告请求 |
| hidp_send_set_report | device.c | 处理设置报告请求 |

16.5 SDP 解析

|-------------------------|----------|---------------|
| 函数 | 文件 | 说明 |
| extract_hid_record | device.c | 解析 HID SDP 记录 |
| extract_hid_desc_data | device.c | 提取 HID 描述符 |
| extract_hid_props | device.c | 提取 HID 属性 |
| create_hid_dev_name | device.c | 创建设备名称 |

16.6 断线重连

|-------------------------------------|----------|--------|
| 函数 | 文件 | 说明 |
| input_device_enter_reconnect_mode | device.c | 进入重连模式 |
| input_device_auto_reconnect | device.c | 自动重连实现 |
| hid_reconnection_mode | device.c | 计算重连模式 |

16.7 BLE HOG

|----------------------|-----------|-----------|
| 函数 | 文件 | 说明 |
| hog_accept | hog.c | 接受 HOG 连接 |
| hog_disconnect | hog.c | 断开 HOG 连接 |
| bt_hog_new_default | hog-lib.c | 创建 HOG 实例 |
| bt_hog_attach | hog-lib.c | 附加 GATT 层 |
| bt_hog_detach | hog-lib.c | 分离 GATT 层 |
| bt_hog_send_report | hog-lib.c | 发送 HOG 报告 |

16.8 服务端管理

|-------------------------------|----------|----------|
| 函数 | 文件 | 说明 |
| server_start | server.c | 启动服务端监听 |
| server_stop | server.c | 停止服务端监听 |
| connect_event_cb | server.c | 控制通道连接回调 |
| confirm_event_cb | server.c | 中断通道确认回调 |
| input_device_set_channel | device.c | 设置设备通道 |
| input_device_close_channels | device.c | 关闭设备通道 |


附录:关键源文件路径

cpp 复制代码
BlueZ 5.x 源码路径:
├── profiles/input/
│   ├── manager.c          # 插件入口、配置加载
│   ├── device.c           # 设备管理核心实现
│   ├── device.h           # 设备接口定义
│   ├── server.c           # L2CAP 服务端实现
│   ├── server.h           # 服务端接口
│   ├── hidp_defs.h        # HIDP 协议常量
│   ├── hog.c              # BLE HOG 实现
│   ├── hog-lib.c          # HOG 库封装
│   ├── hog-lib.h          # HOG 库接口
│   └── input.conf         # 配置文件
├── lib/
│   └── hidp.h             # HIDP 内核接口
└── src/shared/
    ├── uhid.c             # UHID 用户态封装
    └── uhid.h             # UHID 接口定义

总结

BlueZ input 模块通过精心设计的架构实现了完整的蓝牙 HID 协议栈支持:

  1. 分层架构:将 HID 处理分为插件层、Profile 层、协议层、驱动层,层次清晰

  2. 双通道设计:控制通道和中断通道分离,提高传输效率

  3. 灵活的内核对接:支持传统 HIDP 内核模块和现代 UHID 用户态两种模式

  4. 完善的容错机制:超时重传、错误码处理、字节序修复等

  5. 智能重连策略:根据设备能力选择合适的重连模式

  6. BLE 支持:独立的 HOG Profile 实现低功耗蓝牙 HID

通过深入理解 BlueZ input 模块的源码实现,开发者可以更好地:

  • 移植蓝牙输入设备到新平台

  • 调试 HID 相关问题

  • 扩展 HID 功能(如自定义报告处理)

  • 优化连接稳定性和延迟


相关推荐
autotian2 小时前
神经网络及其应用
人工智能·深度学习·神经网络
雾屿_Mistisle2 小时前
AI安全设计总结
人工智能·机器学习·数据分析
aichitang20242 小时前
前端小skill
前端·人工智能·算法·ai·前端框架
来两个炸鸡腿2 小时前
【Datawhale2609】算子开发实战 task02-TileLang Add 与 NineToothed Vector Add
人工智能·大模型·算子
Hotchip_MEMS2 小时前
传统咪头与MEMS硅麦:雾化器气流传感方案对比
人工智能·笔记·物联网·电脑·制造
4SAPI2 小时前
大模型接口管理平台推荐:多模型时代的API Gateway架构与选型分析
人工智能·agent
皇儒无上2 小时前
智慧矿山-关于推进山西省煤矿灾害差异化智能化建设强化 AI 风险防控的政策建议
人工智能·机器学习·区块链
byte轻骑兵2 小时前
【LE Audio】PBP精讲[4]: 公共广播通告的设计逻辑与数据交互流程
人工智能·音视频·le audio·低功耗蓝牙音频
正经教主2 小时前
【FDE系列】阶段2:Day 28:FastAPI 入门 — 把你的函数变成 API 服务
人工智能·python·fde