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 协议栈支持:
分层架构:将 HID 处理分为插件层、Profile 层、协议层、驱动层,层次清晰
双通道设计:控制通道和中断通道分离,提高传输效率
灵活的内核对接:支持传统 HIDP 内核模块和现代 UHID 用户态两种模式
完善的容错机制:超时重传、错误码处理、字节序修复等
智能重连策略:根据设备能力选择合适的重连模式
BLE 支持:独立的 HOG Profile 实现低功耗蓝牙 HID
通过深入理解 BlueZ input 模块的源码实现,开发者可以更好地:
-
移植蓝牙输入设备到新平台
-
调试 HID 相关问题
-
扩展 HID 功能(如自定义报告处理)
-
优化连接稳定性和延迟