ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

RK3568 USB鼠标驱动开发实战:从设备树到input子系统

RK3568 USB鼠标驱动开发实战:从设备树到input子系统 接到“RK3568 USB鼠标驱动开发”这个需求的时候我第一反应是确认一下需求方到底想要什么。因为在Linux系统下一个标准USB鼠标插到RK3568开发板上理论上根本不需要“开发”驱动——内核里usbhid那一整套机制早就把这件事给你干完了。真正需要动手的情况往往是鼠标是定制的、内核被裁剪过、或者干脆跑的不是Linux。这篇文章把我在RK3568平台上做USB鼠标驱动相关工作的完整思路写出来。会先帮你搞清楚所谓“开发”到底卡在哪一步再讲清楚USB鼠标从插上到屏幕光标移动的整条链路然后给出RK3568上设备树和内核配置的具体做法最后附上一份可以直接参考的usb_driver驱动代码框架以及实际排障时常用的工具和坑。内容主要面向做嵌入式Linux驱动、BSP移植的开发者也适合准备在RK3568上接非标USB HID设备的同学。1. 先搞清楚“开发USB鼠标驱动”到底在开发什么很多刚接触嵌入式Linux的人会把“驱动开发”理解成“从零写一个能从USB口读数据的驱动”。但放到RK3568这种跑完整Linux内核的平台上情况完全不同。内核里的USB协议栈把枚举、地址分配、配置选择这些脏活累活全包了标准鼠标又是USB HID类设备usbhid驱动接住它以后再往input子系统一送应用层就能通过evdev读到事件。所以谈“RK3568 USB鼠标驱动开发”之前先分清楚你遇到的是下面哪一类场景因为每一类的做法完全不同。1.1 四种常见场景决定了你实际要做的工作场景一跑Linux标准USB鼠标系统里没有鼠标事件。这是最常见“伪需求”。板子明明插了鼠标但桌面上没有光标或者应用层收不到事件。这种时候你要做的不是写驱动而是查两件事第一内核配置里有没有把USB HID和evdev相关的选项编进去第二设备树里USB Host控制器节点有没有被正确使能。绝大多数所谓“写驱动”的需求最后都落到了这两个环节上。场景二跑Linux鼠标是定制的非标HID设备。比如你的产品鼠标里有特殊功能键、触控板、或者带了自定义协议的数据通道。标准usbhid能把它当鼠标枚举出来但特殊按键的事件不会出现在常规的按键码里或者设备希望走vendor-defined usage page上报数据。这种时候就需要你写一个HID设备驱动注册自己的usage page解析逻辑或者挂在usb_driver层面自己做输入上报。这也是“USB鼠标驱动开发”最实在的工作内容。场景三跑Linux但内核被裁剪过。很多RK3568产品为了省空间和启动时间会把内核减得很狠。如果裁剪时去掉的正好是CONFIG_USB_HID或者CONFIG_INPUT_EVDEV那么鼠标插上去灯亮、USB枚举也正常但就是没事件上报。这时候补配置重编内核即可算不上“开发”但排查思路得对。场景四跑RTOS或者裸机。这属于真正的从头开发USB鼠标驱动。RK3568可能跑RT-Thread或者自研BSP没有Linux内核帮你。你得自己初始化USB Host控制器EHCI或xHCI自己实现枚举流程自己解析HID报告描述符再自己周期性地发中断IN请求读坐标数据。这部分的工程量大很多我会在后面的章节单独讲一下。1.2 一个合格的项目开发需要具备哪些知识储备做这类工作你得大概掌握四块知识缺一块都可能被卡住。第一是USB协议基础至少要明白枚举过程、四种传输类型、描述符树的结构。鼠标这种设备走的是中断传输Interrupt Transfer数据量小但要求实时性。第二是HID协议重点是HID Report Descriptor。每款鼠标的报告格式都不同你要能从描述符里读出Report Size、Report Count、Usage这些字段才能正确解析收到的二进制数据。第三是Linux input子系统。驱动最后要把数据上报到input子系统的核心涉及input_dev的注册、事件类型和事件码的设置、input_event上报流程。不理解这一层你就算从USB口读到了数据应用层也拿不到。第四是设备树。RK3568的地址空间、中断号、控制器类型这些信息都通过设备树传给内核。你对USB Host节点做的使能、别名、电源管理配置直接决定控制器能不能跑起来。经常有人问能不能跳过设备树直接写驱动。我的建议是别跳RK3568的SDK内核里设备树是硬件资源描述的唯一标准USB、电源、中断全部由它来管。逆着这套机制干活最后只会给自己找麻烦。2. 一条从USB引脚到桌面光标的完整数据链路理解数据链路是写好任何外设驱动的基础。我习惯把这条链路分成硬件枚举、数据传输、内核协议栈三个阶段讲每个阶段都有各自的调试抓手。2.1 枚举阶段USB设备怎么被内核发现鼠标插进USB口之后控制器会检测到D/D-上的电平变化这相当于设备在说“我来了”。接下来主机对设备做总线枚举大致流程是这样的主机复位设备设备地址还是默认的0。主机发Get_Device_Descriptor拿前8个字节从里面解析出设备端点0的最大包长度。主机再发Set_Address给设备分配一个地址。拿到地址后完整地读一遍设备描述符、配置描述符、接口描述符、端点描述符。主机对设备配置取接口执行Set_Configuration设备进入正常工作状态。关键的一点是鼠标的输入端点Endpoint在接口描述符里要带一个bInterval字段表示主机轮询这个端点的时间间隔。标准鼠标通常是10ms也就是100Hz的轮询频率。主机在配置完成后就会按这个周期向设备发IN令牌包索取鼠标报告。对于驱动开发者来说枚举阶段的调试手段主要是dmesg。如果能打印出类似“new full-speed USB device number 4”这样的一行并且后面跟着input: USB HID v1.11 Mouse这类日志说明枚举和驱动匹配都成功了。如果卡在枚举中途那问题基本在硬件或者物理链路上。2.2 报告数据到底长什么样鼠标通过中断IN端点每次向主机发送一份固定长度的报告Report。一个标准三键滚轮鼠标的典型报告长这样字节偏移内容说明Byte 0按键状态位图Bit0左键、Bit1右键、Bit2中键Byte 1X轴相对位移带符号int8向右为正Byte 2Y轴相对位移带符号int8向下为正Byte 3滚轮位移带符号int8向上为正这份报告对应的HID Report Descriptor大体是一个输入项把Button按键分成5个bit左右把X和Y定义为相对位移Relative把滚轮定义为数值。内核的usbhid驱动会解析这个描述符动态生成一个hid_field结构然后把报告值转换成input事件。假如鼠标是多键游戏鼠标或者有特殊功能键报告长度会更多按键位也不止5个。比如很多鼠标有前进后退键报告可能是6字节甚至8字节。你在写驱动前第一件事就是把目标鼠标的HID Report Descriptor抓出来搞清楚每字节的含义否则解析出来的数据永远是乱的。2.3 从usbhid到input子系统的软件接力鼠标报告到了Linux内核后会按下面的路径传递USB Host控制器通过DMA把数据放到内存触发中断。usb_hcd层完成去包处理把URB完成的回调交给usbhid驱动。usbhid驱动拿到原始报告后调用hid_input_report经过hid-core解析出input_event。hid-generic作为最通用的HID驱动把input事件分发到input子系统的input_handle_event。event设备evdev在/dev/input/eventX上把事件发给应用层。整个链路中任何一个模块缺失或者被裁剪数据都到不了用户态。这也是为什么我一直强调要先确认内核配置而不是急着写代码。一条链路上六个环节配置缺一环所有努力都白搭。对于应用层开发来说最常用的接口是/dev/input/eventX你可以用cat /proc/bus/input/devices看到当前系统里有哪些输入设备分别对应哪个event节点。如果想看原始输入事件流用evtest工具它能直观读到事件类型、事件码和值是我调试鼠标驱动用得最多的工具。3. RK3568设备树与内核配置让USB控制器先把设备认出来到了RK3568这颗芯片上第一步不是写代码而是确保USB控制器本身被正确配置。瑞芯微的SDK里设备树已经带得很全了但很多定制板卡拿回来硬件团队改过引脚复用、或者裁过电源域之后USB就不工作了。这种问题用驱动代码解决不了必须回到设备树里查。3.1 RK3568的USB控制器结构与设备树节点RK3568的USB控制器大致有几类USB 2.0 Host控制器EHCI/OHCI各对应一个节点、USB 3.0 Host控制器xHCI、以及支持OTG的DRD控制器。对应的设备树节点名称不同SDK版本可能有差异但你在内核设备树目录里grep一下usb2phy、usb_host、usbdrd基本就能找到。以典型SDK为例鼠标插在USB 2.0 Host口的情况下你需要关注的节点是类似这样的usb2phy0 { status okay; }; usb_host0_ehci { status okay; }; usb_host0_ohci { status okay; };这里有个很关键的坑RK3568的USB 2.0 Host口EHCI和OHCI是两个独立控制器节点它们的时钟和电源是共用的。如果只把EHCI节点设为okay而OHCI节点保持disabled某些情况下也能工作但热插拔兼容性会变差。我经历过一次奇怪的故障鼠标插上去能点亮但每次插拔后要等很久才识别最后发现是OHCI相关的时钟没开。把两个节点都打开之后问题就消失了。USB 3.0口则由xHCI节点管理如果你的鼠标插在USB 3.0口上就需要保证usb_host0_xhci或者类似的节点是okay状态。另外要注意USB PHY的配置。USB2PHY除了做信号收发还管着拔插检测和复位。设备树里经常有rockchip,usb2-phy的属性配套的vbus-supply、phy-supply之类电源属性如果配置不对会导致控制器没有电源枚举直接失败。这个优先级非常高很多“驱动不对”的问题最后都是供电没配好。3.2 把内核选项挨个过一遍设备树配置好了接着检查内核配置。在RK3568的内核里OpenSource的SDK默认配置一般都会带全但如果你用的是自己裁剪的配置一定要确认下面这几个选项配置项功能依赖CONFIG_USBUSB总线核心支持必选CONFIG_USB_XHCI_HCDUSB 3.0 Host控制器驱动使用USB3.0口时必选CONFIG_USB_EHCI_HCDUSB 2.0高速Host控制器驱动使用USB 2.0口时必选CONFIG_USB_OHCI_HCDUSB 2.0全速/低速Host控制器驱动使用USB 2.0口时建议打开CONFIG_HIDHID协议核心必选CONFIG_USB_HIDUSB传输层到HID的桥接必选CONFIG_HID_GENERIC通用HID驱动处理标准鼠标关键CONFIG_INPUTinput子系统核心必选CONFIG_INPUT_EVDEV/dev/input/eventX接口应用层读取必需典型的内核配置命令在核心里执行make ARCHarm64 rockchip_linux_defconfig make ARCHarm64 menuconfigmenuconfig里对应的路径长这样。USB HID在Device Drivers - HID support - USB HID transport layerevdev在Device Drivers - Input device support - Event interface。你可以在menuconfig里直接按/搜索CONFIG_USB_HID、CONFIG_INPUT_EVDEV快速定位。配置完之后检查生成的.configgrep -E CONFIG_USB_HID|CONFIG_INPUT_EVDEV|CONFIG_HID_GENERIC .config确认都是y或者m再编译烧写。如果你用的配置是模块记得把内核模块也装进根文件系统里不然一样加载不上。3.3 验证设备树和内核配置是否生效烧完系统插入鼠标按下面的顺序检查。这套顺序我每次都会走一遍能省掉很多无用的猜测。第一步看USB总线枚举lsusb dmesg | grep -i usb如果看到了类似new low-speed USB device number 6的日志说明枚举成功。如果lsusb里能看到设备但dmesg里没有任何input相关的日志问题基本在HID/input这一层。第二步看input设备节点cat /proc/bus/input/devices这一步会列出所有input设备。如果你的鼠标识别正常会看到一个Name类似Logitech USB Optical Mouse的设备Handlers里带着mouse0和eventX。看到这个就说明从USB到input的整条链路已经通了。第三步启动时如果有桌面环境移动鼠标看光标如果没有桌面环境直接evtest /dev/input/eventX看有没有事件。这个测试我在后面会展开讲。检查完这三步如果一切正常那你要做的就不是“开发”而是验收。反之如果卡在USB枚举之前那问题大概率在硬件设计、设备树、电源域、时钟域跟驱动代码没关系。4. 手写一个只认自家鼠标的usb_driver从匹配到输入上报现在说真正的“开发”。如果你的项目里用的是定制鼠标或者你想绕开标准HID驱动自己控制整个上报逻辑那典型的做法是写一个usb_driver。这个驱动挂在内核USB子系统中匹配上特定VID/PID后接管这个USB设备的全部输入解析工作。这里我给出一个极简但能跑的框架核心逻辑你在自己的驱动里可以直接照搬。4.1 工作流程与关键结构体整个驱动要做的事情可以归纳为四步定义一个struct usb_driver填写probe、disconnect、id_table并注册到USB子系统。在probe里找到目标接口的中断IN端点拿到它的地址、包长和bInterval。注册一个input_dev设置好它支持的事件类型EV_KEY、EV_REL以及具体的按键码、相对轴。提交一个中断URB到端点每次URB完成回调里解析数据并调用input事件上报函数然后重新提交URB形成循环读取。匹配设备的id_table长这样static const struct usb_device_id my_mouse_id_table[] { { USB_DEVICE(0x1234, 0x5678) }, // 替换成你鼠标的实际VID/PID { } }; MODULE_DEVICE_TABLE(usb, my_mouse_id_table);如果你希望这个驱动接管所有鼠标设备可以把匹配条件放宽比如用USB_INTERFACE_INFO(HID_DEVICE_CLASS, HID_SUBCLASS_BOOT, HID_PROTOCOL_MOUSE)这样它会匹配所有具备鼠标boot协议接口的HID设备。但这种做法实际项目中慎用因为它会覆盖掉内核标准的usbhid驱动容易引发连锁反应。我建议只在有明确需求时用PID/VID精确匹配。4.2 probe函数找到端点并注册input设备probe是整个驱动的重头戏。先看一下代码框架static int my_mouse_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct usb_device *udev interface_to_usbdev(intf); struct usb_host_interface *iface intf-cur_altsetting; struct usb_endpoint_descriptor *ep; struct input_dev *input_dev; int pipe; int ret; int i; // 第一步找到中断IN端点 for (i 0; i iface-desc.bNumEndpoints; i) { ep iface-endpoint[i].desc; if (usb_endpoint_is_int_in(ep)) break; } if (i iface-desc.bNumEndpoints) { dev_err(intf-dev, no interrupt IN endpoint found\n); return -ENODEV; } // 第二步分配并注册input_dev input_dev input_allocate_device(); if (!input_dev) return -ENOMEM; input_dev-name My Custom USB Mouse; input_dev-id.bustype BUS_USB; input_dev-id.vendor udev-descriptor.idVendor; input_dev-id.product udev-descriptor.idProduct; set_bit(EV_KEY, input_dev-evbit); set_bit(EV_REL, input_dev-evbit); set_bit(BTN_LEFT, input_dev-keybit); set_bit(BTN_RIGHT, input_dev-keybit); set_bit(BTN_MIDDLE, input_dev-keybit); set_bit(REL_X, input_dev-relbit); set_bit(REL_Y, input_dev-relbit); set_bit(REL_WHEEL, input_dev-relbit); ret input_register_device(input_dev); if (ret) goto err_free_input; // 第三步申请URB和缓冲区准备开始读取 // 实际项目中使用 usb_alloc_urb 和 usb_alloc_coherent 完成这里省略细节 // 然后 usb_fill_int_urb 填充URB后 usb_submit_urb 提交 usb_set_intfdata(intf, my_private_data); return 0; err_free_input: input_free_device(input_dev); return ret; }probe函数里最容易错的有几个点第一不要用intf-cur_altsetting-endpoint之外的端点。有些鼠标接口有多个Altsetting默认获取的是index 0足够覆盖绝大多数情况如果你要支持高速HID设备需要另外做Set_Interface这里不展开。第二input_dev注册成功后如果后面申请URB失败一定要先input_unregister_device再释放顺序反了会留下一个幽灵设备节点。另外input_register_device成功后会接管input_dev的所有权之后错误路径里只用input_unregister_device不要再手动input_free_device。第三缓冲区必须用usb_alloc_coherent分配保证DMA一致性。有些新手图省事直接用kmalloc结果数据偶尔缺失排查半天才发现是DMA缓存一致性问题。这个坑很隐蔽务必注意。4.3 URB完成回调解析报告并上报事件URB完成回调是驱动的心脏每次鼠标上报数据都会进到这里。标准三键鼠标的报告是4字节最简单的解析代码如下static void my_mouse_irq(struct urb *urb) { struct my_device *mouse urb-context; unsigned char *data urb-transfer_buffer; int ret; if (urb-status 0) { input_report_key(mouse-input, BTN_LEFT, data[0] 0x01); input_report_key(mouse-input, BTN_RIGHT, data[0] 0x02); input_report_key(mouse-input, BTN_MIDDLE, data[0] 0x04); input_report_rel(mouse-input, REL_X, (s8)data[1]); input_report_rel(mouse-input, REL_Y, (s8)data[2]); input_report_rel(mouse-input, REL_WHEEL, (s8)data[3]); input_sync(mouse-input); } ret usb_submit_urb(urb, GFP_ATOMIC); if (ret ret ! -EPERM) { dev_err(mouse-intf-dev, resubmit urb failed: %d\n, ret); } }这里有两个细节值得强调。第一data[1]和data[2]是带符号数必须强转成(s8)否则右移一位的位移量会被当成255鼠标光标就会乱飞。第二一轮事件上报结束一定要调input_sync它告诉input子系统和应用层这批事件是一个完整的原子帧应用层会以SYN_REPORT事件作为结构边界。漏掉它应用层的坐标会一卡一卡。URB回调里重新提交URB时用GFP_ATOMIC因为在中断上下文不能用可能睡眠的GFP_KERNEL。这也是个很容易被编译器放过的低级错误运行时候系统会直接报错。在USB驱动开发的场景里这类上下文用错的问题最常见排查起来也最快。4.4 驱动加载与验证驱动代码写完后编译成内核模块obj-m : my_mouse.o KERNEL_DIR : /path/to/rk3568/kernel all: $(MAKE) -C $(KERNEL_DIR) ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- M$(PWD) modules交叉编译器前缀根据你SDK里的工具链调整。编出来的my_mouse.ko放到板子上后先卸载或者禁用内核里同VID/PID的标准驱动再加载你的模块rmmod usbhid insmod my_mouse.ko加载完拔出鼠标重新插入让probe生效。然后用evtest测试evtest /dev/input/eventX如果看到类似type 2 (EV_REL), code 0 (REL_X), value -1这样的输出说明你的驱动已经成功读取到数据并且正确上报了。到这里一次完整的USB鼠标驱动“开发”就闭环了。5. 实战排查鼠标没反应、坐标乱跳、热插拔掉驱动的完整链路开发完驱动不等于完事实际板卡上会遇到各种奇奇怪怪的问题。这一章把我这些年遇到的最典型的三个问题串起来讲排查链路是通用的你可以照搬。5.1 问题一鼠标插上去灯亮但完全没事件这个现象说明USB物理链路和供电是好的设备也被枚举了。排查顺序是这样的第一步用dmesg看内核日志。重点看两行一行是枚举usb 2-1: new full-speed USB device number 4 using ehci-platform另一行是input设备注册input: USB HID v1.11 Mouse [1234:5678] on usb-xxxx如果只有第一行没有第二行说明枚举成功但HID驱动没匹配上。可能的原因是HID驱动没编译进内核或者设备树/配置里的HID节点被裁剪。第二种可能是你的鼠标不是标准HID设备接口类不是3HID类需要查看描述符确认。如果第一行都没有说明USB控制器层面就没枚举成功。这时候重点查设备树里对应Host节点的status、PHY的供电以及硬件上D/D-有没有接反或者少上拉。这一步最容易让人误以为要写驱动其实压根不需要。5.2 问题二坐标乱跳左键右键反了或者滚轮没反应坐标乱跳最常见的原因就是我前面提过的符号问题data[1]和data[2]没强转成有符号数。除此之外还有两种情况要特别注意。第一种是HID Report Descriptor跟你以为的不一样。你以为鼠标报告是4字节实际它可能是8字节坐标在字节2和字节3滚轮在字节6。这种时候必须把真实报告描述符抓出来。方法是用usbhid-dump工具usbhid-dump -m 1234:5678它会把HID Report Descriptor完整列出来。对照Usage Table逐一算出Report Size、Report Count和逻辑最小值/最大值你就能知道每个bit的准确含义。第二种是坐标乱跳但按键正常同时报告里位移值很大。这种情况往往是设备每帧上报了多个Report ID。有些鼠标支持“低延迟模式”报告里带了Report ID字节前面的解析就全错位了。处理办法是解析描述符时留意Report ID有Report ID的话报告的第一个字节就是ID后面的数据才是真正的button/XY。左键右键反了相对简单通常是按键位图定义和你的解析反了。在代码里把BTN_LEFT和BTN_RIGHT对应的位交换一下就行。5.3 问题三热插拔几次后event节点消失或者内核崩溃热插拔是USB设备的常态你的驱动必须处理好disconnect。最常见的崩溃原因是在disconnect里释放了URB和相关资源但URB回调还没跑完导致悬垂指针访问。我的习惯是严格按照下面的顺序做清理usb_kill_urb杀掉正在排队的URB这一步会等回调返回。从接口数据里分离私有数据结构让回调不再能访问到它。input_unregister_device注销input设备。释放URB缓冲区和URB本身。同时URB回调里重新提交URB前必须先判断urb-status如果状态是-ESHUTDOWN或者-ENOENT说明设备已经断开或者URB被kill了直接返回不要再提交。很多热插拔崩溃都是因为这个细节没处理好。用usbmon抓一下断开瞬间的URB状态也能辅助定位。方法很简单modprobe usbmon cat /sys/kernel/debug/usb/usbmon/2u /tmp/usbmon.log 抓完日志后用Wireshark读取usbmon的pcap能看到断开时URB返回的错误码。如果出现大量-EPROTO可能是信号质量问题检查PCB布线如果突然出现-ESHUTDOWN则说明host controller已经主动停止了这个端点正常。5.4 一个通用排查速查表把上面三个问题整理成一张表方便你排查时对照现象优先检查项备用手段插上灯亮但无任何事件dmesg中input注册行、CONFIG_USB_HIDlsusb -v确认接口类只有USB枚举无HID注册内核裁剪、HID配置依赖cat /proc/bus/input/devices坐标乱跳数据符号、HID描述符解析usbhid-dump抓描述符按键错乱位图顺序、Report ID逐字节打印原始报告热插拔崩溃disconnect顺序、URB状态usbmon抓URB错误码移动时事件有延迟bInterval值、端点轮询间隔usbmon抓包时间戳这张表不是万能的但覆盖了RK3568上USB鼠标开发90%的问题。实际操作时我最常用的一句话是先确认事件到底有没有到input层再往下查哪层丢了。evtest就是分界点事件在它这里能出来问题就不在USB驱动它这里没输出才需要看USB层。6. 给RTOS和U-Boot留一条路无Linux环境下的鼠标驱动思路最后聊一个很多人会遇到但资料很少的方向如果RK3568上跑的不是Linux而是RTOS、裸机或者U-Boot鼠标驱动应该怎么做。这个问题在调试早期启动阶段特别常见比如你想在U-Boot stage用鼠标操作菜单或者你的产品固件本身就不带Linux。6.1 没有内核之后你反而回到了USB协议本身Linux把USB的复杂性都封装好了到了RTOS环境所有这些都得自己来。第一步是确认你的BSP有没有现成的Host Controller驱动。瑞芯微的U-Boot和部分RT-Thread BSP里其实已经有EHCI/xHCI驱动你首先要做的是把它们使能起来而不是从零写。USB主机控制器跑起来之后剩下的工作可以分成三步第一步实现枚举。你有没有现成的枚举函数没有的话按标准流程做总线复位、Get_Device_Descriptor、Set_Address、再完整读描述符。这跟我在第二章里讲的一模一样。第二步解析HID描述符。枚举完成后你找到键盘/鼠标接口的中断IN端点然后读HID Report Descriptor从里面提取Report Size和Report Count。鼠标的位移字段通常是相对值定义为“Relative”的Usage Page在Generic Desktop的Pointer下面。你得自己算出报告长度和每个字段的偏移。第三步周期轮询端点。用控制器的调度器或者你自己定一个定时器按bInterval的间隔向端点发IN令牌收到报告后按解析出来的格式提取数据然后调用显示层接口更新光标位置。U-Boot里如果有显示框架往往需要你手动把鼠标坐标转换成屏幕坐标再画一个光标bitmap上去。这一套东西的实现量不小所以我的建议很直接能复用现成库就复用。U-Boot源码里drivers/usb/host和common/usb_kbd.c是现成的参考RT-Thread的drivers/usb/usb_host也值得仔细读。自己从零写一个xHCI驱动在项目周期上划不来除非你纯粹是为了学习。6.2 U-Boot阶段最实际的需求让鼠标能动RK3568上做U-Boot阶段的鼠标支持最常见的需求是让用户能在bootloader菜单里用鼠标操作。U-Boot的USB驱动框架DM USB已经支持EHCI和xHCI你只需要做两件事第一在U-Boot配置里打开USB支持。相关配置项在U-Boot的menuconfig里主要包括CONFIG_USB、CONFIG_USB_EHCI_HCD、CONFIG_USB_XHCI_HCD以及CONFIG_USB_HID一类的输入设备支持。第二实现或者使能HID鼠标协议。U-Boot键盘代码里有usb_kbd驱动但鼠标的完整支持在不同版本里差异很大。如果你只是想让鼠标光标动起来可以在board代码里自己写一个极简的鼠标处理函数枚举完成后从中断端点读报告解析相对位移然后调用显示驱动的video_display_bitmap或者类似接口去移动光标。这个阶段我的经验是不要追求花哨先把“动起来”的目标达成。U-Boot里鼠标只是辅助操作不是产品核心功能稳定比功能丰富重要得多。6.3 方案选型建议先想清楚在哪一层实现做USB鼠标驱动之前先问自己一个问题这个需求必须在驱动层解决还是可以在应用层解决如果鼠标是标准HID设备Linux自带支持那么你要做的就是内核配置和设备树检查。如果鼠标是定制设备但你可以接受应用层直接打开/dev/hidrawX读原始报告那驱动层的修改可以完全不做直接在应用层解析数据这往往省事得多。只有当你需要让系统把定制鼠标当普通输入设备用所有应用都能直接共享时才值得把逻辑下沉到驱动层。驱动层越靠近硬件灵活性越小调试成本越高维护成本也越高。选型要克制不要为了“写驱动”而写驱动。还有一个实用建议开发过程中准备两三款不同品牌的标准USB鼠标作为对照。当你怀疑自己的驱动解析逻辑有问题时拿标准鼠标跑同一套代码如果标准鼠标工作正常那问题确定在你的目标鼠标报告格式上如果标准鼠标也乱跳那问题多半出在内核USB配置或者endpoint匹配上。这一招在排障时非常好用比反复看代码快得多。做RK3568平台上的USB鼠标驱动本质上比拼的不是写代码的速度而是对USB协议、内核框架、设备树和硬件调试工具的熟练程度。你把这些链路吃透了换任何一台开发板、任何一款USB HID设备思路都是通的。
返回列表