自制工业控制系统:键盘与鼠标驱动实战——USB HID Boot协议解析与输入事件管道
Contents
起因
EHCI 立起来之后,这篇写键盘和鼠标的驱动——USB HID class 实战。键鼠对工控系统不是小事:没有它们就没有参数输入和菜单操作。这两样东西的历史也比 USB 长:1987 年 IBM 在 PS/2 机型上把键盘鼠标定成中断驱动的扫描码接口,驱动几十行就写完;USB 时代它们变成通用总线上的一个设备类,为了描述千奇百怪的输入设备,HID 规范设计了自描述的报表描述符——通用性的代价是把解析器变成一个小型语言实现。这篇走的是Boot Protocol 路线:绕开报表描述符解析,把 HID 驱动从"大工程"变"小函数"。
一、HID 的两条路线:选 Boot Protocol
HID 设备的"正统"用法是解析报表描述符(Report Descriptor)——一种自描述的栈式语言,一个完整解析器写下来不小。BIOS 当年也懒得解析它,所以 HID 规范为键盘鼠标留了后门:Boot Protocol 子集——子类代码 1、协议代码 1(键盘)/2(鼠标),数据格式固定死,8 字节一个报表,零解析。主板固件走的就是这条路,我们跟它走同一条。
通过控制传输切换(上一篇的 8 字节请求包):
|
|
从接口描述符里找中断 IN 端点(bEndpointAddress bit7=IN,bInterval=轮询毫秒数,典型 8ms),挂进 EHCI 的周期列表——之后每 8ms,最新的键鼠状态自动落进我们给的缓冲区。
坑:设备复位后协议会退回 Report Protocol。 USB 复位、重新枚举、拔插之后,SET_PROTOCOL 白设了,收到的报表长度对不上(键盘变 16 字节之类的怪形状)就要想到这一层:枚举流程里把 SET_PROTOCOL/SET_IDLE 固定做进去,别只在第一次开机做。
键盘还有个小尾巴:三个 LED(Caps Lock / Num Lock / Scroll Lock)。不发输出报表功能照常,但用户按了 Caps Lock 灯不亮,现场会当成"键盘坏了"报修。补一条 SET_REPORT(bmRequestType=0x21、bRequest=0x09、wValue=0x0200 报告类型=输出)把 LED 位图(bit0 Num、bit1 Caps、bit2 Scroll)写回去,几十行内的事。
遇到极少数不支持 boot 的设备再说——工控键鼠是固定选型,为 1% 的兼容性写一个报表描述符解析器,不划算。
二、键盘驱动
Boot 键盘报表固定 8 字节:
| 字节 | 内容 |
|---|---|
| 0 | 修饰键位图:bit0 左Ctrl、bit1 左Shift、bit2 左Alt、bit3 左GUI、bit4-7 右侧对应 |
| 1 | 保留(OEM 用) |
| 2–7 | 按键数组:当前按下的 6 个键的 Usage 码,0 = 空 |
注意 bytes2-7 是"当前按着谁"的数组快照,不是事件流——按下/弹起要靠前后两帧对比:上一帧没有、这一帧有的 Usage = 按下;反之 = 弹起。超过 6 键同时按会收到 Usage 0x01(rollover 错误码),全帧丢弃。
Usage→字符映射表(节选,全表照 USB HID Usage Tables 抄):
| Usage | 基本字符 | +Shift |
|---|---|---|
| 0x04–0x1D | a–z | A–Z |
| 0x1E–0x26 | 1–9、0 | !@#…) |
| 0x28 / 0x29 / 0x2A | 回车 / Esc / 退格 | — |
| 0x2C | 空格 | — |
| 0x2D–0x38 | - = [ ] \ ; ’ ` , . / | _ + { } | : " ~ < > ? |
|
|
按键重复(长按连发)在 UI 层用定时器做,不在驱动做——驱动只报事实,不做策略。中文输入法属于应用层,路线:英文键码 → UI 焦点控件 → 输入法状态机(后续应用篇)。
三、鼠标驱动
Boot 鼠标报表(3 字节起步,带滚轮 4 字节):
| 字节 | 内容 |
|---|---|
| 0 | 按键位图:bit0 左键、bit1 右键、bit2 中键 |
| 1 | dx(有符号 int8,相对位移) |
| 2 | dy(int8) |
| 3 | 滚轮(可选) |
|
|
两个坑:dx/dy 是补码有符号数,as i8 转换别丢(快速移动会有负值);相对位移的光标坐标由 UI 维护并夹在屏幕范围内,驱动不管绝对位置。
四、事件管道与时间戳
EHCI 周期传输完成中断 → 底半部调 parse_* → InputEvent 进 SPSC 队列 → 图形系统篇的 ui_step 消费。事件结构带 TSC 时间戳(APIC 篇):
|
|
时间戳在工控现场是真有用的:审计"什么时刻按了什么键"——加工参数修改记录、误操作追责,都靠它。
五、工控安全边界
- 软件按键永远不承担安全职能:急停必须是硬件回路(按钮串进断丝/超程检测电路,电平直进 FPGA 卡的安全中断,走 IST1 那条路)。键盘上的"急停"最多算"请求停止"
- 加工运行中的权限锁:自动运行状态下输入事件默认丢弃,仅放行停止/急停/坐标显示——在事件管道的分发层实现,不散落在各控件里
- 键盘去抖:HID 报表自带硬件去抖,软件层可不做;UI 层对关键操作做双击确认
总结
实战清单:SET_PROTOCOL(0x0B) 切 boot 协议 + SET_IDLE、枚举流程固定重设(复位回退坑)、SET_REPORT 点亮键盘 LED、接口描述符里找中断 IN 端点挂周期列表、键盘 8 字节报表的前后帧对比法 + Usage 映射表、鼠标 int8 位移 + 按键边沿、InputEvent 带 TSC 进 SPSC、运行态输入锁在管道层收口。跑通标志:QEMU 里 -device usb-kbd -device usb-mouse,串口打出键码流,图形篇的 UI 能用键盘切焦点、鼠标点按钮(usb-tablet 是绝对坐标的报表设备,不走 boot 协议,测鼠标用 usb-mouse)。下一篇:NVMe 固态硬盘。
参考链接
- USB HID 规范(Device Class Definition for HID)——Boot Protocol 与报表格式
- USB HID Usage Tables——键盘 Usage 码全表
- OSDev Wiki: USB Human Interface Devices
- 本系列 EHCI 篇:《USB 2.0主机控制器——EHCI驱动与传输队列》
- 本系列第一篇:自制工业控制系统:从UEFI到内核
Author 软件开发大郭
LastMod 2026-09-11