1. 一场由 USB 控制权引发的键盘惨案
那天下午我本来只想干一件很简单的事:在自制的 x86 小主机上,把 USB 键盘的初始化流程从 BIOS 的 Legacy USB Support 手里抢过来,自己接管 EHCI 控制器,做一个轻量的 USB HID 驱动实验。结果代码烧进去、重启、屏幕亮起的一瞬间,键盘灯闪了一下就彻底黑了。不是系统里不能用,是连 BIOS 界面都进不去,按 Del、按 F2、按 F12 全部石沉大海。那一刻我就知道,事情大条了。
这篇文章记录的就是接下来 11 天里,我怎么从一个“键盘当场去世”的砖头状态,一步步把它救回来的完整过程。涉及的核心关键词包括BIOS、USB、8042、PS/2、EHCI,也会顺带聊到 USB 协议、USB 抓包、USB 转串口这些周边话题。如果你正在玩 BIOS 魔改、自己做 USB 设备、或者单纯对“为什么键盘会在 BIOS 阶段失灵”这件事好奇,这篇内容应该能帮你少走很多弯路。我踩过的坑、量过的波形、翻过的 spec 页码,都会尽量写清楚。
先说结论性的背景:现代 x86 平台在开机阶段,键盘输入其实有两条完全不同的路径。一条是古老的8042 控制器 + PS/2 接口,另一条是BIOS 通过 SMM 或运行时服务模拟出来的 USB Legacy 支持。当你“抢”了 USB 控制权却没处理好交接,BIOS 会以为键盘还在,操作系统会以为键盘不在,最后就是两头都不认,键盘直接变成摆设。我这次就是栽在这个交接逻辑上。
2. 为什么抢 USB 控制权会把键盘搞死
2.1 BIOS、8042 与 USB Legacy 的三方关系
要理解键盘为什么“去世”,得先搞清楚开机到进系统这段时间,键盘输入到底是谁在管。传统 PC 架构里,键盘控制器是一个叫8042(也叫 i8042、KBC)的芯片,它负责 PS/2 键盘和鼠标的中断与数据搬运。BIOS 在 POST 阶段会初始化 8042,设置好中断向量,这样你按 Del 才能进 BIOS 设置界面。
但问题是,现在谁还用 PS/2 键盘?大家都是 USB 键盘。USB 键盘本身是 USB 设备,需要 USB 主机控制器(比如EHCI,USB 2.0 的控制器)和对应的驱动才能工作。BIOS 阶段根本没有完整的 USB 协议栈,那怎么办?于是就有了USB Legacy Support这个东西。
它的原理是:BIOS 在 POST 时接管 EHCI 控制器,枚举 USB 键盘,然后通过SMI(系统管理中断)在后台轮询 USB 键盘的数据,一旦有按键,就把它翻译成 8042 的 PS/2 扫描码,塞进 8042 的输出缓冲区,让上层以为这是一个 PS/2 键盘。这样 BIOS 设置界面和 DOS 环境都能用 USB 键盘。
我做的事情,就是在这个链条中间插了一刀:我在自己的代码里重新初始化了 EHCI,把 BIOS 已经配置好的 USB 键盘设备状态给冲掉了。BIOS 的 SMI 处理程序还在跑,但它去读 USB 键盘的时候,发现设备状态不对,读回来的是错误数据或者干脆超时。而 8042 那边因为长时间没有数据,上层就认为键盘不存在。结果就是:BIOS 进不去,系统也进不去,键盘彻底失联。
2.2 EHCI 接管时的交接陷阱
这里有个非常关键的细节:EHCI 控制器的所有权交接。在 ACPI 规范里,EHCI 有一个EHCI Owned Semaphore机制,用来在 BIOS 和操作系统之间协商谁拥有控制器。BIOS 在退出时会把 ownership 交给 OS,OS 驱动接管后会设置自己的配置。
但如果你在 BIOS 还没完全退出、或者 SMM 还在活跃的时候强行去写 EHCI 的寄存器,就会破坏这个协商状态。我当时就是直接在自己的代码里对 EHCI 的 MMIO 基址做了writel,把USBCMD和USBSTS寄存器改了,还重置了端口。BIOS 的 SMI 处理程序下一次进来,发现USBSTS里的状态位全乱了,它可能就卡在某个循环里,或者直接放弃了对键盘的轮询。
更麻烦的是,有些平台的 BIOS 会把 USB 键盘的枚举信息缓存在 SMRAM 里。你从外部改了 EHCI 状态,SMM 里的缓存和实际硬件状态不一致,SMI 处理程序就会做出错误判断。这种不一致不会报错,只会让键盘静默失效。
2.3 为什么连 BIOS 界面都进不去
很多人会问:就算 USB 键盘挂了,BIOS 不是还有 PS/2 兼容路径吗?理论上是的,但实际平台上,很多 BIOS 在检测到 USB 键盘存在后,会禁用 8042 的键盘通道,或者把 8042 的中断屏蔽掉,只走 USB Legacy 路径。这样做的目的是避免同一个按键被上报两次。
当我把 USB 路径搞坏之后,BIOS 仍然以为 USB 键盘是活的(因为 SMM 缓存还在),所以它不会去恢复 8042 通道。而 USB 路径实际上已经死了,于是就没有任何键盘输入能到达 BIOS 设置界面。这就是“键盘当场去世”的完整逻辑链。
注意:不同厂商的 BIOS 行为差异很大。有些平台会在 USB 键盘失效后自动回退到 8042,有些则不会。我这次遇到的属于后者,所以才会这么惨。
3. 抢救第一步:确认键盘到底死在哪一层
3.1 用最小系统法排除硬件问题
键盘黑了之后,我第一反应是硬件坏了。于是换了一个 PS/2 键盘,发现能进 BIOS。这说明主板和 8042 本身没问题,问题出在 USB 路径上。接着我又换了一个 USB 键盘,还是不行,说明不是单个键盘的问题。
然后我做了几件事来定位故障层级:
- 用万用表量了 USB 端口的 VBUS 和 D+/D- 对地阻值,确认没有短路或断路。
- 用 USB 协议分析仪抓了开机阶段的 USB 流量,发现 EHCI 在 POST 早期确实枚举了键盘,但之后就没有任何 SOF 包了。
- 用示波器看了 8042 的时钟和数据线,发现 BIOS 在检测到 USB 键盘后,确实把 8042 的时钟线拉低了,相当于禁用了 PS/2 通道。
这几步下来,基本确认了:EHCI 控制器被我的代码搞进了异常状态,BIOS 的 SMM 轮询失效,同时 8042 通道被禁用,导致键盘输入完全中断。
3.2 用 USB 抓包看 EHCI 到底怎么了
USB 抓包是这次排查里最有用的手段。我用的是硬件协议分析仪,接在 EHCI 和 USB 键盘之间。正常开机时,抓到的流量是这样的:
- EHCI 复位端口,发送
SET_ADDRESS给键盘。 - 读取设备描述符、配置描述符。
- 设置配置,启用中断端点。
- 之后每隔 1ms 左右,EHCI 发送一个 IN token 到中断端点,键盘返回 NAK 或数据。
我出问题后的抓包结果是:前 3 步都正常,但第 4 步完全消失了。EHCI 不再发送任何 IN token,端口状态寄存器显示端口处于Disabled或者Suspended。这说明我的代码在重置端口后,没有正确重新启用端口,也没有重新设置中断端点。
更细的寄存器分析发现,USBCMD里的Run/Stop位被清零了,USBSTS里的HCHalted位被置位。也就是说,EHCI 控制器被我停掉了,而且没有重新启动。BIOS 的 SMM 处理程序可能尝试过重新启动,但因为 ownership 状态混乱,没有成功。
3.3 为什么 8042 通道也被关了
8042 通道被禁用这件事,是我在抓 8042 波形时发现的。正常开机时,8042 的时钟线应该是周期性波动的,表示它在扫描键盘矩阵。但出问题后,时钟线一直被拉低,数据线也是高电平。这是典型的“控制器被禁用”状态。
BIOS 为什么要禁用 8042?因为它在 POST 时检测到了 USB 键盘,并且成功枚举了。按照设计,它会设置一个标志位,表示“键盘输入走 USB Legacy 路径”,然后关闭 8042 通道以避免冲突。这个标志位存在 SMRAM 或者 CMOS 里,我改 EHCI 状态的时候并没有清除这个标志位,所以 BIOS 仍然认为 USB 键盘是活的。
这就形成了一个死锁:BIOS 认为 USB 键盘活着,所以不开 8042;USB 键盘实际上死了,所以没有输入。要打破这个死锁,要么让 BIOS 重新认为 USB 键盘死了,要么强制恢复 8042 通道。
4. 11 天抢救实录:从清 CMOS 到重刷 BIOS
4.1 第一天到第三天:清 CMOS、拔电池、找跳线
最开始我尝试的是最常规的招数:清 CMOS。拔掉电源,取下主板上的 CR2032 电池,短接 CLR_CMOS 跳线,等了几分钟再装回去。结果开机后键盘还是没反应。这说明 BIOS 的键盘路径标志位不是存在 CMOS 里,而是存在 SMRAM 或者 SPI Flash 的某个区域。
接着我尝试了盲操作进 BIOS。有些 BIOS 支持在启动时按特定组合键强制恢复默认设置,比如长按电源键或者按 F1 到 F12 的组合。我试了十几种组合,都没有效果。因为没有键盘输入,BIOS 根本收不到任何按键事件。
第三天,我开始查主板的 SPI Flash 引脚定义,准备用编程器直接读固件。这块主板用的是 SOIC-8 封装的 SPI Flash,型号是 Winbond 的 25Q 系列。我用夹子夹住 Flash 的引脚,接上 CH341A 编程器,成功读出了固件。对比官方固件后,发现 NVRAM 区域确实有几个字节被改了,应该是 BIOS 在 POST 时写入的键盘路径标志。
4.2 第四天到第六天:用编程器改 NVRAM 标志位
读出固件后,我用 UEFITool 解析了 BIOS 区域,找到了 NVRAM 里的KeyboardPath变量。这个变量是一个字节,0x00表示走 8042,0x01表示走 USB Legacy。我出问题后,这个值被设成了0x01,但 USB 路径已经失效。
我尝试把这个字节改回0x00,然后重新刷回 Flash。刷完后开机,键盘还是没反应。后来发现,BIOS 在 POST 时会重新检测 USB 键盘,如果检测到,又会把标志位设回0x01。所以单纯改 NVRAM 不够,还得让 BIOS 检测不到 USB 键盘。
于是我把 USB 键盘拔掉,只插 PS/2 键盘,然后刷回改过的固件。这次开机,PS/2 键盘能用了,BIOS 界面也能进了。但问题是,我不能一直用 PS/2 键盘,而且 USB 端口在系统里也全部失效了,因为 EHCI 控制器还是异常状态。
4.3 第七天到第九天:修复 EHCI 控制器状态
进了 BIOS 之后,我第一件事就是恢复默认设置,然后关闭 USB Legacy Support。这样 BIOS 就不会再去接管 EHCI,也不会禁用 8042。接着我进系统,发现 USB 端口还是不能用。设备管理器里 EHCI 控制器显示黄色感叹号,错误代码是 10(设备无法启动)。
这说明 EHCI 控制器的硬件状态还是乱的。我尝试了以下几种方法:
- 在系统里卸载 EHCI 驱动,重新扫描硬件。无效。
- 用
devcon命令重置 PCI 设备。无效。 - 进 Linux Live USB,用
lspci看 EHCI 的 BAR 和命令寄存器。发现Command寄存器里的Memory Space Enable位被清零了,Bus Master Enable位也是零。
我用setpci手动把这些位设回去,然后重新加载 EHCI 驱动。这次控制器终于被识别了,USB 端口也恢复了。但键盘还是不能用,因为端口状态寄存器里还有残留的错误状态。
4.4 第十天到第十一天:重刷官方 BIOS 并重建 NVRAM
最后两天,我决定彻底重刷官方 BIOS。用编程器把官方固件完整写入 SPI Flash,然后清空 NVRAM 区域,让 BIOS 在第一次开机时重新初始化所有设置。刷完后第一次开机,我只插了 PS/2 键盘,进 BIOS 后手动设置好启动项和 USB 配置,保存重启。
第二次开机,我插上 USB 键盘,BIOS 正常枚举,键盘灯亮了,按 Del 也能进 BIOS。进系统后,USB 端口全部正常,EHCI 控制器工作稳定。至此,键盘算是彻底救活了。
这 11 天里,我最大的教训就是:不要在没有完整交接逻辑的情况下,去抢 BIOS 已经接管的 USB 控制器。如果你只是想做实验,一定要在 BIOS 完全退出、操作系统接管之后再动手,或者至少保留一条 PS/2 键盘作为后路。
5. 常见问题与排查速查表
5.1 键盘在 BIOS 阶段失灵的典型原因
| 现象 | 可能原因 | 排查方法 | 解决思路 |
|---|---|---|---|
| USB 键盘灯不亮,按 Del 无反应 | EHCI 控制器被停用或端口被禁用 | 用 USB 抓包看是否有 SOF 和 IN token | 恢复 EHCI 寄存器,重新启用端口 |
| PS/2 键盘能用,USB 键盘不能用 | BIOS 禁用了 8042 通道,只走 USB Legacy | 量 8042 时钟线是否被拉低 | 关闭 USB Legacy Support,恢复 8042 |
| 键盘在系统里能用,BIOS 里不能用 | BIOS 未启用 USB Legacy Support | 进 BIOS 检查 USB 配置 | 开启 Legacy USB Support |
| 键盘时好时坏,偶尔失灵 | SMM 轮询与 OS 驱动冲突 | 抓 SMI 和 USB 流量对比 | 关闭 BIOS 的 USB SMI 轮询 |
| 清 CMOS 后键盘恢复,重启又失灵 | NVRAM 标志位被 BIOS 重写 | 读 SPI Flash 看 NVRAM 区域 | 修改标志位并阻止 BIOS 重写 |
5.2 抢救过程中的避坑清单
- 不要只清 CMOS:很多平台的键盘路径标志位存在 SMRAM 或 SPI Flash 里,清 CMOS 没用。
- 不要盲目重刷 BIOS:重刷前一定要备份原固件,尤其是 NVRAM 区域,里面可能有主板序列号、网卡 MAC 等关键信息。
- 不要忽略 PS/2 后路:如果你要做 USB 控制器实验,手边一定要有一块 PS/2 键盘,关键时刻能救命。
- 不要用软件刷写工具救砖:如果 BIOS 已经无法启动,软件刷写工具基本没用,必须上硬件编程器。
- 不要反复插拔 USB 键盘:在 EHCI 状态异常时,反复插拔可能触发端口保护,进一步锁死控制器。
5.3 常用工具与命令速查
# 查看 PCI 设备信息 lspci -vv -s 00:1d.7 # 手动启用 PCI 设备的内存空间和总线主控 setpci -s 00:1d.7 COMMAND=0x06 # 查看 EHCI 寄存器 devmem 0xfed1c000 # 用 CH341A 编程器读 SPI Flash flashrom -p ch341a_spi -r backup.bin # 用 UEFITool 解析 BIOS 固件 uefitool backup.bin提示:
setpci和devmem操作需要 root 权限,且地址和寄存器偏移要参考具体平台的 datasheet,不要照搬。
6. 从这次事故里学到的 USB 与 BIOS 交互经验
6.1 USB Legacy Support 到底该不该开
很多人为了进 BIOS 方便,会一直开着 USB Legacy Support。但这次事故让我意识到,这个功能在实验环境下是个双刃剑。它确实能让 USB 键盘在 BIOS 阶段工作,但它也意味着 BIOS 会接管 EHCI 控制器,并在 SMM 里跑轮询逻辑。一旦你从外部干扰了 EHCI 状态,SMM 轮询就会失效,而且不会自动恢复。
我的建议是:如果你只是普通用户,开着没问题;如果你要做 USB 控制器实验、写自己的 USB 驱动、或者玩 BIOS 魔改,最好在实验前关闭 USB Legacy Support,并且准备一块 PS/2 键盘。这样 BIOS 不会碰 EHCI,你可以完全掌控控制器的初始化流程。
6.2 EHCI 与 XHCI 的交接差异
这次事故也让我重新审视了 EHCI 和 XHCI 的差异。EHCI 是 USB 2.0 控制器,它的 ownership 交接机制相对简单,主要靠USBCMD和USBSTS寄存器。XHCI 是 USB 3.0 控制器,它的交接机制更复杂,涉及xHC Extended Capabilities里的USB Legacy Support寄存器组。
在 XHCI 平台上,BIOS 和 OS 的交接是通过xECP里的USBLEGSUP寄存器完成的。OS 驱动会设置OS Owned Semaphore,BIOS 在 SMI 里检查这个信号,然后释放控制器。如果你在 OS 还没设置 semaphore 的时候就去写 XHCI 寄存器,同样会导致键盘失灵,而且恢复起来比 EHCI 更麻烦。
6.3 8042 通道的恢复技巧
如果你不幸遇到了 8042 通道被禁用的情况,可以尝试以下方法恢复:
- 进 BIOS 后关闭 USB Legacy Support,保存重启。
- 如果进不了 BIOS,用编程器改 NVRAM 里的键盘路径标志位。
- 如果 NVRAM 改不了,尝试短接 8042 芯片的复位引脚(需要查主板原理图)。
- 最后的手段是重刷官方 BIOS 并清空 NVRAM。
我在第十天才找到 8042 的复位引脚,短接后 PS/2 键盘立刻恢复了。但这个方法风险很高,不建议新手尝试。
6.4 给做 USB 设备开发的人的建议
如果你是在做 STM32 或者 RT-Thread 的 USB 设备开发,比如把 STM32 做成一个 USB HID 键盘,那这次事故的教训同样适用:不要在主机的 BIOS 阶段去枚举你的设备。BIOS 的 USB 栈非常简陋,很多标准请求它都不支持,而且它会在枚举后把设备状态缓存起来。如果你的设备在枚举后行为异常,BIOS 可能会卡死或者禁用端口。
我的建议是:在设备端加一个延时,等主机进入操作系统后再开始响应非标准请求。或者干脆用 USB 转串口芯片(比如 FT231X)来做调试通道,避免和 HID 键盘抢枚举时机。
7. 最后的个人体会
这次抢救花了 11 天,其中大部分时间不是在修硬件,而是在理解 BIOS、SMM、EHCI、8042 这几层之间的交互逻辑。我最大的体会是:BIOS 阶段的 USB 支持是一个“黑盒”,你看到的键盘能用,背后是 SMM 在默默轮询。一旦你打破了这个黑盒的假设,恢复起来非常麻烦。
如果让我重新做一次那个实验,我会先在 BIOS 里关闭 USB Legacy Support,插上 PS/2 键盘,然后在操作系统里用vfio或者uio把 EHCI 控制器绑定到用户态驱动,完全绕过 BIOS 的干预。这样即使实验失败,也不会影响 BIOS 阶段的键盘输入。
另外,USB 抓包设备在这次排查里帮了大忙。如果你经常玩 USB 底层,建议入手一个硬件协议分析仪,哪怕是最便宜的版本,也比纯软件抓包能看到更多物理层和链路层的信息。我用的那台虽然不贵,但抓 SOF 包和端口状态变化非常准。
最后再分享一个小技巧:如果你怀疑 BIOS 的 SMM 在干扰 USB 控制器,可以在 Linux 里用rdmsr读一下SMI_EN和SMI_STS寄存器,看看 SMM 是否在频繁触发。如果 SMI 频率很高,那基本可以确定是 BIOS 的 USB Legacy 轮询在作祟。这时候关闭 USB Legacy Support 或者更新 BIOS 通常能解决问题。