ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

BIOS与USB控制权争夺战:键盘失灵11天抢救实录

BIOS与USB控制权争夺战:键盘失灵11天抢救实录 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 主机控制器比如EHCIUSB 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 交给 OSOS 驱动接管后会设置自己的配置。但如果你在 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 键盘活着所以不开 8042USB 键盘实际上死了所以没有输入。要打破这个死锁要么让 BIOS 重新认为 USB 键盘死了要么强制恢复 8042 通道。4. 11 天抢救实录从清 CMOS 到重刷 BIOS4.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表示走 80420x01表示走 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 COMMAND0x06 # 查看 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 SemaphoreBIOS 在 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 通常能解决问题。
返回列表