RK3568 USB鼠标驱动开发实战:从HID协议到内核调试全流程

发布时间:2026/9/17 5:51:11
RK3568 USB鼠标驱动开发实战:从HID协议到内核调试全流程 RK3568 跑 Linux 系统USB 鼠标插上去没反应dmesg 里连设备枚举日志都没有这种问题我排查过好几回。很多人一听“USB 鼠标驱动开发”就觉得要自己从零写一个 HID 驱动其实大多数场景下根本不是这么回事。这篇文章我不打算只讲代码而是把 RK3568 上做 USB 鼠标驱动开发从原理、协议栈、设备树、代码实现到抓包调试验证的完整链路都过一遍顺便把我在实际项目中踩过的坑也翻出来说说希望能帮你少走点弯路。这个项目内容适合两类人看一是刚接触 Linux 驱动开发想拿 USB 鼠标这种相对简单的设备当入门练手项目的朋友二是已经在做 RK3568 相关项目遇到 USB 外设识别不稳定、鼠标卡顿、坐标异常这类问题需要一套排查思路的工程师。文中涉及的内核版本以 Linux 5.10 为主这也是当前 RK3568 各 BSP 里比较常见的版本。1. 项目整体设计思路与方案选型1.1 核心需求解析我们到底要开发什么先把这个项目的边界划清楚。所谓“RK3568 USB 鼠标驱动开发”在实际工作场景里通常对应三种完全不同的需求第一种也是最常见的一种开发板或嵌入式产品上接 USB 鼠标、键盘、触摸板等 HID 设备系统起来之后鼠标完全不工作需要把驱动跑通。这种情况往往不是没有驱动而是内核配置里没把 HID 相关的选项编进去或者设备树里 USB 控制器没有正确使能又或者是 rootfs 里缺少必要的 udev 规则。第二种需要在驱动层面做定制。比如你要在鼠标事件送给应用层之前对坐标做归一化处理要把某个自定义按键映射成特定键值要把同一型号的鼠标断电重连后保持同一个事件节点名甚至要把鼠标上报的数据和系统内部的某个业务逻辑做联动。这时候就需要写符合 input 子系统规范的过滤驱动或者虚拟驱动。第三种更深入一层在 RK3568 平台上移植或优化某个厂商的 USB HID 设备驱动。这种设备可能不是标准鼠标而是带扩展功能的 HID 采集设备、带 IMU 的无线鼠、还有游戏鼠标里那个几百 Hz 的回报率调节功能。用户要求的是 Linux 端驱动开发支持你就得从 HID 协议层去对接。我的建议是不管需求落在哪一层都得先把标准 HID 鼠标的内核路径彻底吃透。因为即便你只是做配置和调参不了解 USB 枚举过程、中断端点、HID 报告描述符这些基础遇到问题连排查方向都找不到。这篇文章所描述的“开发”并不只是写代码而是覆盖了“配置使能、内核驱动解析、代码裁剪/定制、验证与调试”的完整闭环。1.2 整体方案选型标准 HID 还是自定义驱动在 Linux 内核中USB 鼠标驱动这个位置有一个历史遗留的分叉drivers/hid/usbhid/usbmouse.c和drivers/hid/hid-generic.c都能处理标准鼠标。其中usbmouse.c是一个比较老的、极简的 USB 鼠标驱动注册了usb_driver直接通过input_report_key和input_report_rel把事件上报给 input 子系统。而hid-generic.c是更通用的方案需要配合 HID 子系统解析报告描述符把描述符中的 Usage Page、Usage ID 转换成 input 子系统能够识别的事件。现代内核里绝大多数标准鼠标走的是后一条路径也就是usb_hid_driver - hid_input_report - input_report_abs/rel/key。这里涉及一个开发层面的选型问题如果你要在 RK3568 上做一个“给公司内部产品用的鼠标”驱动到底该改哪个模块我的经验是优先保留内核标准 HID 方案通过自定义hid_ll_driver或hid_driver结构体挂到系统的 HID 总线上去。除非你确实需要绕开 HID 报告描述符解析直接用最原始的usb_fill_int_urb读取中断端点数据否则完全没必要从零写 USB 驱动。举个实际中的对比方案优点缺点适用场景标准 HID 内核配置无需写代码稳定性高定制能力弱大多数量产产品自定义 hid_driver 过滤可精确控制键值映射和事件过滤需要理解 HID 协议栈特殊按键、坐标变换自写 usbmouse 风格驱动代码量小逻辑直白不通用无法处理多接口设备学习实践、极简场景基于 usbmon 抓包 应用层处理不碰内核风险低实时性差轮询开销大快速原型验证在 RK3568 这类性能不算非常紧张的嵌入式平台我通常建议方案一和方案二组合使用内核配置留足 HID 功能业务侧的定制逻辑放在自定义 hid_driver 里做。这样能最大程度避免自己造轮子。1.3 RK3568 平台特点与开发环境准备RK3568 是瑞芯微推出的一款四核 Cortex-A55 处理器主频最高 2.0GHz 左右集成了丰富的多媒体接口和多个 USB 控制器。和树莓派这类开发板相比RK3568 的优势更多体现在工业级场景、多路显示、AI 处理盒以及需要扩展多种外设的嵌入式产品上。所以 RK3568 上的 USB 鼠标驱动开发场景很少是“桌面办公”更多是触控一体机、自助终端、工业 HMI、广告机这类产品。开发环境上主要的工具链和仓库包括# 交叉编译工具链 git clone https://github.com/kenny-1975/rk3588_toolchain.git # 或者直接在 SDK 里使用预编译工具链 export PATHsdk/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin:$PATH # 内核源码以 rockchip SDK 为例 git clone https://github.com/rockchip-linux/kernel.git cd kernel git checkout develop-5.10其实对驱动开发来说更重要的是先把内核编译环境跑通能够在你自己的分支上编译出可启动的 boot.img。RK3568 的 BSP 通常由 Rockchip 官方维护各大开发板厂商会基于它发布基础 SDK。编译命令一般是这样# 清理并生成默认配置 make ARCHarm64 rockchip_linux_defconfig # 编译内核 make ARCHarm64 CROSS_COMPILEaarch64-none-linux-gnu- -j16如果你是第一次接触 RK3568建议不要直接上来就改驱动先把官方 SDK 的镜像烧录到板子上跑一遍确认系统起来后 USB 鼠标默认能不能用。如果默认系统能用说明硬件和内核基础配置没问题你只需要改自己的部分如果默认系统就不能用那就说明问题出在系统配置层面而不是你的驱动代码上。2. 核心原理剖析从 USB 总线到鼠标事件2.1 一条完整的数据通路USB 控制器到应用层USB 鼠标虽然看起来简单但它的数据通路实际上跨越了 Linux 内核好几个子系统。我把这条链路按层拆开你要是能把这几个层次记在脑子里后面排查问题会顺手得多。第一层是 USB 控制器驱动HCDHost Controller Driver。RK3568 内部集成了多个 USB 控制器在 Linux 下通常对应dwc3或ehci/ohci驱动。鼠标这类低速/全速设备经常会跑在 EHCI 或 DWC3 的兼容模式下这一层的职责是完成 USB 协议层面的传输比如控制传输、批量传输、中断传输。第二层是 USB Core也就是drivers/usb/core目录下的代码。USB Core 负责枚举设备、解析设备描述符、分配接口、匹配驱动。你在 dmesg 里看到的New USB device found日志就是这一层打印的。第三层是 HID 子系统drivers/hid目录。USB 设备被识别为标准 HID 设备后usbhid驱动会接管它读取它的 HID 报告描述符然后把报告里的数据转换成 input 子系统能理解的事件比如REL_X、REL_Y、BTN_LEFT。第四层是 input 子系统drivers/input。HID 层或者自定义驱动把事件写入input_devinput 子系统通过input_event通知到/dev/input/eventX节点。应用层程序、桌面环境或者你的业务进程读这个节点就能拿到鼠标事件。这条链路可以用一句话概括USB 中断端点的数据包 - HID 报告解析 - input 事件上报 -/dev/input/eventX- 应用层。2.2 USB 描述符与 HID 报告描述符先学会“读懂设备”接到一个 USB 鼠标系统在枚举阶段会从设备里读取一组描述符。设备描述符Device Descriptor说明的是整体信息比如idVendor、idProduct、bDeviceClass这里是 0表示由接口描述符定义类别。配置描述符里会包含接口信息一个 USB 鼠标通常是一个接口里面有一个中断输入端点和一个可选的中断输出端点用于设置 LED 之类的功能。然后是最重要的 HID 报告描述符。标准鼠标的报告描述符一般长这样0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x02, // Usage (Mouse) 0xA1, 0x01, // Collection (Application) 0x09, 0x01, // Usage (Pointer) 0xA1, 0x00, // Collection (Physical) 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (1) 0x29, 0x03, // Usage Maximum (3) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x95, 0x03, // Report Count (3) 0x75, 0x01, // Report Size (1) 0x81, 0x02, // Input (Data, Variable, Absolute) 0x95, 0x01, // Report Count (1) 0x75, 0x05, // Report Size (5) 0x81, 0x03, // Input (Constant) 0x05, 0x01, // Usage Page (Generic Desktop) 0x09, 0x30, // Usage (X) 0x09, 0x31, // Usage (Y) 0x15, 0x81, // Logical Minimum (-127) 0x25, 0x7F, // Logical Maximum (127) 0x75, 0x08, // Report Size (8) 0x95, 0x02, // Report Count (2) 0x81, 0x06, // Input (Data, Variable, Relative) 0xC0, // End Collection 0xC0 // End Collection这段描述符的意思很好理解鼠标有 3 个按键每个按键 1 bit后面是 5 bit 的补齐位最后是 X 轴和 Y 轴的相对位移各 8 bit取值范围 -127 到 127。在 Linux 下可以用lsusb -v来查看这个描述符。这个命令打出来的信息非常关键不管你是写驱动还是调 bug第一步都应该先把设备描述符、配置描述符、HID 报告描述符完整拉出来看一遍。2.3 内核里的事件上报机制input 子系统到底怎么工作HID 层解析完报告描述符后会把鼠标的位移和按键状态映射到 input 子系统定义的事件类型上EV_REL类型下REL_X表示水平位移REL_Y表示垂直位移EV_KEY类型下BTN_LEFT、BTN_RIGHT、BTN_MIDDLE分别代表鼠标左键、右键、中键滚轮通常映射为REL_WHEEL在高精度 HID 设备上会是REL_WHEEL_HI_RES从内核的角度看一次鼠标移动一厘米你会在内核日志路径里看到类似下面这样的调用序列如果开了CONFIG_INPUT_DEBUG或 ftracehid_input_report() - hid_report_raw_event() - input_event() - input_handle_event()最终input_handle_event会把事件写入 evdev 客户端缓冲区也就是/dev/input/eventX这个字符设备。应用层read()这个节点拿到的是固定格式的struct input_eventstruct input_event { struct timeval time; // 事件时间戳 __u16 type; // 事件类型如 EV_REL __u16 code; // 事件代码如 REL_X __s32 value; // 事件值如 -3 };有时候你会看到应用层程序读鼠标数据不用read()而是用ioctl去拿EVIOCGABS之类的绝对轴信息那是因为那类设备是绝对坐标触摸屏。相对坐标鼠标没有绝对轴只有相对事件这个区别在驱动开发时最容易忽略。3. 设备树配置与平台适配3.1 RK3568 设备树中的 USB 控制器节点很多朋友刚开始接触 RK3568 驱动开发第一个反应就是“我是不是要在设备树里给鼠标加一个节点”。这里我先给个明确结论标准 USB 鼠标是即插即用设备不需要在设备树里单独为鼠标写任何节点。鼠标能不能工作取决于 RK3568 的 USB 控制器节点是否使能以及内核里对应控制器的驱动是否正常工作。RK3568 芯片手册里通常有多个 USB 控制器其中 USB 3.0 OTG 控制器和 USB 2.0 HOST 控制器在设备树里分别对应不同节点名。以 Rockchip SDK 的设备树为例主要看这几个节点usbdrd3_0 { status okay; }; usbdrd_dwc3_0 { dr_mode otg; status okay; }; usbhost3_0 { status okay; };如果status是disabled对应的 USB 口就不会被初始化鼠标插上去自然没反应。所以遇到鼠标不工作的问题第一步就是打开设备树检查这些节点。另外需要特别注意dr_mode的配置。在 RK3568 上如果 USB 控制器配置成peripheral模式那么它只能作为从设备插入鼠标是不会被枚举的。很多开发板默认 OTG 口配置是otg实际使用时可能因为硬件设计或者固件参数冲突导致 HOST 功能没开。你可以用cat /sys/bus/platform/devices/usbdrd_dwc3_0/dr_mode查看当前模式。3.2 设备树改动场景不是每个方案都不需要改设备树虽然标准鼠标不需要在设备树里加节点但如果你要做下面这几类开发设备树还是绕不开第一类接线约束。如果你的鼠标是通过 USB Hub 扩展出来的一路接口而这个 Hub 挂在一个usbfe800000这类节点的下游那么你必须保证这个节点及其phys、vbus-supply都配置正确否则上游不供电或者 PHY 没有被初始化Hub 都枚举不出来鼠标自然也没戏。第二类高速/全速模式切换需求。有些项目要求把 USB 口固定成 USB 1.1 全速模式以便兼容某些老的 HID 设备需要在设备树里配置对应的 PHY 参数。这种场景我不建议你这么干除非有非常强的兼容性要求因为把整个控制器限速会影响所有挂在这个控制器下的设备性能。第三类带供电控制的接口。量产产品里经常有vbus-supply vcc5v0_otg这样的属性。如果 VBUS 电源没有在设备树里声明和控制鼠标插上后可能既不供电也不被识别。这种情况在自制底板的 RK3568 项目中非常常见因为很多底板设计是照着公板抄的但电源控制 GPIO 不一样。3.3 实操示例在现有 BSP 上验证 USB HOST 配置有几个命令在板子上敲一下就能快速判断当前系统的 USB HOST 配置是否健康# 查看 USB 总线上的设备拓扑 lsusb # 查看各 root hub 下的设备 cat /sys/bus/usb/devices/usb*/product # 查看 USB 控制器状态 cat /sys/bus/usb/devices/usb*/speed正常插入鼠标后lsusb输出里应该能看到类似Bus 001 Device 002: ID 046d:c077 Logitech, Inc. M105 Optical Mouse这样的行。如果lsusb什么都没有说明设备枚举没成功问题在底层硬件或控制器驱动如果lsusb能看到设备但/dev/input/mouseX和/dev/input/eventX都没有生成说明问题在内核 HID 配置。我遇到过一个比较隐蔽的问题lsusb能看到鼠标但编译内核时把CONFIG_HID_GENERIC关掉了导致没有任何驱动绑定这个 USB HID 接口。系统会打印no driver found之类的提示但很多人压根不会去dmesg看满世界找问题。所以内核对设备树和驱动的匹配关系很大一部分要靠日志判断后面我会专门讲怎么排查。4. 驱动代码实现与编译4.1 从最小可运行示例开始写一个 USB 鼠标驱动原型前面说了标准鼠标通用方案不需要自研驱动。但这个项目既然叫“驱动开发”我还是给出一个简洁的、可运行的 USB 鼠标驱动原型帮你理解usb_driver和input子系统是怎么协作的。这种写法参考了内核里的usbmouse.c适合用来做学习实验也适合在某些特殊情况下绕开 HID 层直接读取鼠标数据。#include linux/kernel.h #include linux/module.h #include linux/usb/input.h #include linux/hid.h #define MOUSE_MINOR_BASE 128 #define MOUSE_MAX_PACKET 8 static struct input_dev *mouse_input_dev; static struct usb_device *mouse_udev; static struct urb *mouse_urb; static dma_addr_t mouse_dma_addr; static unsigned char *mouse_data_buf; static void mouse_irq(struct urb *urb) { int status urb-status; switch (status) { case 0: /* 成功收到数据 */ input_report_key(mouse_input_dev, BTN_LEFT, mouse_data_buf[0] 0x01); input_report_key(mouse_input_dev, BTN_RIGHT, mouse_data_buf[0] 0x02); input_report_key(mouse_input_dev, BTN_MIDDLE, mouse_data_buf[0] 0x04); input_report_rel(mouse_input_dev, REL_X, (signed char)mouse_data_buf[1]); input_report_rel(mouse_input_dev, REL_Y, (signed char)mouse_data_buf[2]); input_sync(mouse_input_dev); break; case -ECONNRESET: case -ENOENT: case -ESHUTDOWN: return; /* 设备断开或停止 */ default: break; } usb_submit_urb(urb, GFP_ATOMIC); } static int mouse_open(struct input_dev *dev) { if (usb_submit_urb(mouse_urb, GFP_KERNEL)) return -EIO; return 0; } static void mouse_close(struct input_dev *dev) { usb_kill_urb(mouse_urb); } static int mouse_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_host_interface *interface intf-cur_altsetting; struct usb_endpoint_descriptor *endpoint; int pipe; mouse_udev interface_to_usbdev(intf); /* 查找鼠标接口的中断输入端点 */ endpoint interface-endpoint[0].desc; mouse_input_dev input_allocate_device(); if (!mouse_input_dev) return -ENOMEM; /* 设置 input 设备支持的事件 */ set_bit(EV_KEY, mouse_input_dev-evbit); set_bit(EV_REL, mouse_input_dev-evbit); set_bit(BTN_LEFT, mouse_input_dev-keybit); set_bit(BTN_RIGHT, mouse_input_dev-keybit); set_bit(BTN_MIDDLE, mouse_input_dev-keybit); set_bit(REL_X, mouse_input_dev-relbit); set_bit(REL_Y, mouse_input_dev-relbit); mouse_input_dev-open mouse_open; mouse_input_dev-close mouse_close; mouse_input_dev-name rk3568-usb-mouse; if (input_register_device(mouse_input_dev)) { input_free_device(mouse_input_dev); return -EIO; } /* 分配数据缓冲区并提交 URB */ mouse_data_buf usb_alloc_coherent(mouse_udev, MOUSE_MAX_PACKET, GFP_ATOMIC, mouse_dma_addr); mouse_urb usb_alloc_urb(0, GFP_KERNEL); pipe usb_rcvintpipe(mouse_udev, endpoint-bEndpointAddress); usb_fill_int_urb(mouse_urb, mouse_udev, pipe, mouse_data_buf, MOUSE_MAX_PACKET, mouse_irq, NULL, endpoint-bInterval); mouse_urb-transfer_dma mouse_dma_addr; mouse_urb-transfer_flags | URB_NO_TRANSFER_DMA_MAP; return 0; } static void mouse_disconnect(struct usb_interface *intf) { usb_kill_urb(mouse_urb); input_unregister_device(mouse_input_dev); usb_free_urb(mouse_urb); usb_free_coherent(mouse_udev, MOUSE_MAX_PACKET, mouse_data_buf, mouse_dma_addr); } static const struct usb_device_id mouse_id_table[] { { USB_DEVICE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } }; MODULE_DEVICE_TABLE(usb, mouse_id_table); static struct usb_driver mouse_driver { .name rk3568_usb_mouse, .probe mouse_probe, .disconnect mouse_disconnect, .id_table mouse_id_table, }; module_usb_driver(mouse_driver); MODULE_LICENSE(GPL);这段代码虽然短但它把 USB 驱动和 input 子系统的核心交互都覆盖到了probe里分配并注册input_devopen时提交 URBirq回调里解析数据并上报disconnect时释放资源。4.2 Makefile 与交叉编译注意点写好了驱动接下来就是编译。如果你不是编译进内核而是以模块方式加载Makefile 一般写成这样obj-m rk3568_usb_mouse.o KDIR : rk3588-sdk/kernel CROSS : aarch64-none-linux-gnu- all: $(MAKE) ARCHarm64 CROSS_COMPILE$(CROSS) -C $(KDIR) M$(PWD) modules clean: $(MAKE) ARCHarm64 CROSS_COMPILE$(CROSS) -C $(KDIR) M$(PWD) clean编译的时候有几点特别容易踩坑我单独列出来第一KDIR指向的内核源码必须和你板子上正在运行的内核是同一个版本或至少同一次make出来的产物。很多人用 SDK 里默认配置编出来的内核但后来又改了设备树重新编了一个 boot.img导致模块和内核版本不匹配insmod的时候直接报Invalid module format。第二RK3568 的内核默认开了 Linux 内核模块签名或者强制模块版本验证吗一般而言 Rockchip BSP 默认不开启但你自己裁剪内核时可能会打开CONFIG_MODULE_SIG和CONFIG_MODVERSIONS。如果开了insmod会报签名错误解决方法是把这两个选项关掉重新编内核或者给模块签名建议学习阶段直接关掉。第三交叉编译工具链的版本会影响模块加载。AArch64 内核模块的 ABI 兼容性相对好但如果你用 9.x 的 GCC 编的内核却用 10.x 的 GCC 编模块偶尔也会出问题。稳妥做法是模块编译和内核对齐同一套编译器。模块编出来后拷到板子上加载试一下insmod rk3568_usb_mouse.ko # 查看是否加载成功 lsmod | grep rk3568_usb_mouse # 查看是否生成了 input 节点 ls -l /dev/input/event* # 使用 evtest 查看上报事件 evtest /dev/input/eventX4.3 用 evtest 和 hexdump 验证驱动是否真的在干活evtest是嵌入式 Linux 上验证输入设备最常用的工具。它既能列出所有 input 设备也能实时打印事件流。在没有evtest的板子上可以直接用hexdump读设备节点hexdump -C /dev/input/event2鼠标动一下你应该能在输出里看到类似这样的数据00000000 64 9d 76 65 98 3c 06 00 02 00 00 00 01 00 00 00 |d.ve...........| 00000010 02 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 |................|这个二进制格式对应的就是struct input_eventtype2表示EV_RELcode0x01表示REL_Yvalue1表示向上移动了 1 个单位。如果能看到这样的持续事件流说明你的驱动已经正常上报了。如果evtest里能看到设备但移动没反应通常有三种可能中断端点解析错了读上来的数据不是鼠标数据包的起始位置input_sync()没调用应用层拿不到完整事件批次键盘控制台捕获了事件比如在一个不显示 GUI 的终端环境里事件被内核的vt层消费了前两个是代码问题第三个多半是因为你打开了CONFIG_INPUT_MOUSEDEV并且mousedev和evdev同时存在应用在等mousedev节点而驱动上报只发给了evdev。这种情况下在应用层监听/dev/input/mice和/dev/input/eventX的结果会不一样。5. 抓包、日志与问题排查5.1 dmesg 关键日志每个阶段代表什么USB 设备从物理插入到 input 节点生成内核会在不同阶段打印日志。熟悉这些日志的位置和含义能帮你快速定位问题出在哪一层。设备枚举阶段的日志usb 2-1: new full-speed USB device number 3 using xhci-hcd usb 2-1: New USB device found, idVendor046d, idProductc077, bcdDevice0.00 usb 2-1: New USB device strings: Mfr1, Product2, SerialNumber0 usb 2-1: Product: USB Optical Mouse看到这几行说明 USB 控制器已经把设备枚举成功了。如果连这几行都没有就要查硬件连接、控制器模式、供电。接下来 HID 驱动绑定接口时的日志input: USB Optical Mouse as /devices/platform/usbdrd_dwc3_0/xhci-hcd.0/usb2/2-1/2-1:1.0/0003:046D:C077.0003/input/input47 hid-generic 0003:046D:C077.0003: input,hidraw0: USB HID v1.11 Mouse [USB Optical Mouse] on usb-xhci-hcd.0-1/input0这一行出现说明 HID 驱动已经把报告描述符解析完并注册了input节点。注意日志里的input47后面的hidraw0是 hidraw 设备节点应用层也可以直接用hidraw接口读写原始 HID 报告很多厂商的调参工具就走hidraw。如果设备枚举成功但没有任何 HID 日志重点检查内核CONFIG_HID和CONFIG_HID_GENERIC是否开启以及设备树里该 USB 口有没有被设置成disabled。5.2 usbmon 抓包USB 层面数据怎么看应用层和内核层拿到的都是“解释后”的事件。如果怀疑驱动解析逻辑有问题或者设备上报的数据本身就有异常就需要在 USB 层面看原始的数据包。Linux 下最常用的手段是usbmon配合 Wireshark。首先加载 usbmon 模块modprobe usbmon然后抓取特定总线的数据。先看鼠标挂在哪个总线# 假设鼠标在 Bus 2 cat /sys/kernel/debug/usb/usbmon/2u实际抓包时我习惯直接用文本格式监控cat /sys/kernel/debug/usb/usbmon/2u /tmp/usbmon_log.txt然后拔插鼠标、移动鼠标结束后去看日志。日志里会看到中断传输的 UR B尤其是类似I2:...:1:037:...这样的行表示在端点0x81上收到一个中断 IN 数据包后面就是 8 个字节的原始 HID 报告。如果你需要更直观地看解析过程可以把 usbmon 的文本日志导入 Wireshark 的usbmon接口。Wireshark 能自动识别 USB 描述符和各种 HID 报告还能按设备过滤对分析鼠标回报率、坐标跳变这类问题非常有用。需要提醒的是usbmon 抓包本身可能会抢占设备的中断传输数据特定的 URB 在抓包期间可能不会被上报到 input 层。不过这只是暂时的不影响硬件。在量产产品上做长时间抓包验证时注意别把 usbmon 一直开着避免影响实时性。5.3 常见异常鼠标不动、漂移、乱跳、回报率异常我在 RK3568 上实际遇到过的鼠标相关异常大致可以归纳成几类这里列一个排查速查表方便你对照处理异常现象可能原因排查方向插上完全无反应lsusb 看不到设备供电不足、控制器未使能、PHY 异常检查设备树 status、Vbus 供电、USB 线材lsusb 能看到设备但无 input 节点内核 HID 配置缺失、驱动没绑定检查 CONFIG_HID_GENERIC、dmesginput 节点存在但鼠标移动没反应驱动解析错误、应用层监听错节点evtest 验证事件检查事件代码鼠标动一下光标乱跳触发了绝对坐标模式、报告描述符解析错误查看 HID 报告描述符、检查坐标轴 event code鼠标偶尔断开重连供电不稳、EMC 干扰、USB 线材差抓 usbmon 日志看 disconnect/reenumerate回报率低移动不跟手中断端点间隔太大、控制器带宽不足查看 bInterval、同一 Hub 下其他设备占用鼠标按键没反应按键映射不对、HID 描述符里按键 count 不对用 evtest 按各个按键确认 key code睡眠唤醒后鼠标失灵控制器电源管理、设备未重新初始化检查 runtime PM、usb_autosuspend 设置其中最让我记忆深刻的是一个“鼠标动一下光标乱跳”的问题。应用层反馈鼠标往右移动光标会先跳到屏幕边缘然后再回来。查了半天最后发现是设备上报的不只是REL_X和REL_Y还带了一个EV_ABS的绝对坐标而且这个绝对坐标和相对坐标的数据源不一致导致图形界面同时收到两种坐标后行为错乱。这种情况通常会发生在一些兼容性差的国产鼠标上。解决办法是在 HID 层过滤掉EV_ABS事件或者只保留相对坐标。在自定义hid_driver里可以直接在raw_event回调里判断并丢弃错误报告。5.4 调试心得给我配置内核时留后门在 RK3568 上做 USB 驱动开发我强烈建议你从一开始就开启一组调试相关的内核配置别等到出了问题再重新编内核那太耽误时间。我一般会开这么几个CONFIG_USB_DEBUGy CONFIG_USB_ANNOUNCE_NEW_DEVICESy CONFIG_DYNAMIC_DEBUGy CONFIG_INPUT_EVDEVy CONFIG_HIDRAWy CONFIG_USB_MONyCONFIG_USB_MON对应 usbmon 功能没有它后面的抓包无从谈起。CONFIG_DYNAMIC_DEBUG可以让你在运行时动态打开drivers/hid/usbhid/下的调试日志echo file drivers/hid/usbhid/* p /sys/kernel/debug/dynamic_debug/control打开之后内核会持续打印hid_input_report相关的解析日志对排查报告描述符解析异常特别有用。另外一个细节是如果你的产品面向量产USB 设备名或 vendor/product ID 往往需要固定。这时候你可以通过 udev 规则把某个 vendor/product 的鼠标固定绑定到固定的 event 节点或者干脆在内核驱动里用input_dev-name统一改名避免应用层动态识别导致逻辑混乱。比如cat /etc/udev/rules.d/99-mouse.rules # 内容示例 KERNELevent*, SUBSYSTEMinput, ATTRS{idVendor}046d, ATTRS{idProduct}c077, SYMLINKusb_mouse这样不管设备在物理上是插在哪个 USB 口只要 vendor/product 对上/dev/usb_mouse这个节点就固定指向它。6. 进阶扩展方向与个人经验总结到这里标准 USB 鼠标驱动开发和问题排查的核心流程已经走完了。最后我再聊几个项目后期容易遇到的扩展点以及这段时间以来我积累的一点体会。第一个扩展点HID 上报数据的深度定制。很多嵌入式产品要求鼠标滚轮滚动一格对应特定的业务操作或者要屏蔽鼠标的某些按键这种需求不建议在应用层拦截直接在驱动层过滤效率更高。你可以写一个hid_driver重写raw_event回调把需要屏蔽的报告内容清零再放行给 input 层整个过程对应用层完全透明。第二个扩展点多鼠标设备管理。触控一体机上经常同时接一个 USB 鼠标和一个 USB 触控屏或者接两个 USB 鼠标。这种情况下两个设备的事件会同时上报如果不做区分光标会乱跳。解决方案是在驱动里根据struct usb_device的bus-busnum和devnum给设备打标签再通过 input 设备的dev-devno区分事件来源或者用input_mt相关 API 做多点触控和鼠标事件的优先调度。第三个扩展点性能优化。游戏外设类的公司可能会要求在 RK3568 上实现 1000Hz 回报率的鼠标这涉及到中断端点间隔bInterval和 USB 控制器中断处理开销的平衡。我们做过的实测中标准鼠标在 125Hz 回报率下基本占用不了多少 CPU但切到 1000Hz 后中断次数会显著增加如果系统里还有网络和显示任务可能引起调度抖动。这种场景下建议把 USB 中断线程化打开也就是CONFIG_USB_SUPPORT相关选项再配合threaded irq使用。我个人在实际操作中的一个最大体会是做 RK3568 上的 USB 鼠标驱动开发真正困难的不是写代码而是建立完整的分层调试意识。很多人一上来就盯着驱动源码看却忽略了lsusb、dmesg、evtest、usbmon这一整套工具链能提供的巨大信息量。先把设备枚举、描述符解析、事件上报这三层链路各自该看什么日志、什么数据搞明白再动手碰代码效率会高很多。有一次我排查一个“鼠标偶尔断开”的问题前前后后怀疑过内核驱动、设备树供电、硬件走线最后用 usbmon 抓包发现是某根 USB 延长线内部接触不良导致设备在链路层反复复位直接换线解决。很多时候问题不在你想的那个方向先看数据再下判断比什么都重要。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询