gamer043/bits-amd-smi-latency
GitHub: gamer043/bits-amd-smi-latency
将 Intel BITS 的 SMI 延迟测试移植到 AMD 平台,修复 SMI 计数和 USB 移交测试在 AMD 上静默失效的问题。
Stars: 0 | Forks: 0
# BITS SMI 延迟 — AMD 移植版
让 Intel 的 **BIOS Implementation Test Suite** SMI 延迟测试在 AMD 上真正可用。
BITS 在没有操作系统的情况下启动计算机,并测量 System Management Mode 从 CPU 偷取了多少时间。它是执行此操作的标准工具。但其中有两个部分是 Intel 专属的,在 AMD 上它们会*悄无声息地*失败——你会得到一个看似合理的结果,但唯独缺少了最重要的那个数字。
本仓库包含四个 Python 文件,只需将它们放到 BITS U 盘上即可。**无需重新编译,无需工具链,无需 C 编译器。**
## 哪里出了问题
| | Intel | AMD,原版 BITS | 修复方式 |
|---|---|---|---|
| **SMI 计数** | `MSR_SMI_COUNT` (`0x34`) | 触发 `#GP`。BITS 捕获该错误,返回 `None`,并且 SMI 计数行**被直接忽略且没有任何报错**。你只能看到延迟尖峰,却无法定位原因。 | `smicount.py` — AMD 核心 PMC 事件 `0x2B` *"SMIs Received"* (`LsSmiRx`) |
| **USB 移交测试** | EHCI | 仅扫描 EHCI。Zen 平台仅支持 xHCI,因此它什么也找不到,返回 `False`,并且该测试**不运行任何断言,也不打印任何内容**。 | `usb.py` — xHCI USBLEGSUP BIOS→OS 移交 |
测量路径中的所有其他部分——RDTSC 轮询循环、TSC 校准、SMP 启动、PCI 访问——已经是厂商中立的。该工具原本就有约 95% 的可移植性。此补丁补全了剩下的空缺。
额外奖励:`amdfch.py` 解码了 AMD FCH SMI 模块,让你能看到*哪些*源被启用了,这能告诉你尖峰到底是由 USB 还是其他原因引起的。
## 快速开始
### 1. 制作 U 盘
从 [biosbits/bits](https://github.com/biosbits/bits) 或 下载 BITS ISO(`bits-2079.iso` 或更高版本)。
使用 **Rufus** 写入:
| 设置 | 值 |
|---|---|
| 分区方案 | **MBR** |
| 目标系统 | **BIOS 或 UEFI** |
| 文件系统 | **FAT32** |
| 写入模式 | **ISO Image 模式**(不要选 DD —— DD 会给你一个只读的 ISO9660 分区,你无法进行修补) |
MBR 可以在一个 U 盘上同时保留 BIOS 和 UEFI 的引导路径。GPT 只能用于 UEFI,且没有任何好处。
### 2. 应用补丁
将 `patch/boot/python/` 中的四个文件复制到 U 盘的 `\boot\python\` 目录下,然后**删除过期的字节码**:
```
\boot\python\smilatency.pyc <- delete
\boot\python\usb.pyc <- delete
```
BITS 设置了 `Py_DontWriteBytecodeFlag`,并且在 `sys.path` 中 `/boot/python` 位于 `lib.zip` 之前,因此实际运行的就是这些独立的 `.py` 文件。删除 `.pyc` 只是为了让结果具有确定性。
一行命令 (Git Bash / WSL,U 盘盘符为
```
cp patch/boot/python/*.py /h/boot/python/
rm -f /h/boot/python/smilatency.pyc /h/boot/python/usb.pyc
```
或者,重新制作 ISO 本身(需要 `xorriso`):
```
./remaster.sh bits-2079.iso bits-2079-amd.iso
```
### 3. 运行测试
1. 从 U 盘启动。**UEFI 模式。** 如果固件拒绝引导,请禁用 Secure Boot。
2. **Test Menu → Change Test Verbosity Level → "Force Test Verbose Level 3 Detailed output for PASS / FAIL"**,然后按 Esc 返回。
如果跳过此步骤,通过的测试只会打印 `PASS` —— 没有直方图,也没有 SMI 计数。
3. **Test Menu → "SMI latency test"**。等待 15 秒。**不要触碰键盘** —— 按键会产生自己的 SMI 并导致最大值出现偏差。
4. **Esc 到顶层 → View and Save Log → "Save log to /boot/bits-log.txt"**。写回 U 盘;之后可以在 Windows 中读取。
5. **最后执行,且务必在最后执行:***Test Menu → "SMI latency test with USB disabled via BIOS handoff."* 请先保存你的日志 —— 这个测试可能会导致你的 USB 键盘失效,在传统/CSM 模式引导下它还可能导致 USB 磁盘访问失效,因此你可能无法在测试后保存任何内容。完成后重新开关机。
步骤 5 在上游被标记为 `runall=False`,因此 "Run all tests" **不会**包含它。你必须手动选择它。运行它并与步骤 3 进行比较,是你隔离 USB 作为 SMI 源头的具体方法。
## 解读输出结果
来自一台 AMD 机器的真实输出:
```
==== SMI latency test ====
[assert] SMI latency < 150us to minimize risk of OS timeouts PASS
0 < t <= 1us; average = 6ns; count = 2255605773
Times between first few observations: 10ns 20ns 20ns 10ns 20ns
10us < t <= 100us; average = 67us; count = 897
Times between first few observations: 16ms 16ms 16ms 16ms 16ms
897 SMI detected using AMD PMCx02B [SMIs Received] on PERF_CTL5 (MSR 0xc001020a/0xc001020b)
SMI rate: 59.8/s over 15s
Summary of impact: observed maximum latency = 68us
FCH SMI block at 0xfed80200
SmiTrig0 = 0x3dadf5eb (SMI generation enabled, EOS=1)
9 SMI sources enabled:
#33 =SMI #51 =SMI #53 =SMI #65 =SMI #75 =SMI #76 =SMI
#141=SMI #142=SMI #143=SMI
Summary: 1 passed, 0 failed
```
| 行 | 含义 |
|---|---|
| `0 < t <= 1us; count = 2.25e9` | 采样循环本身。每对 `RDTSC` 约需 6 ns。忽略它。 |
| `10us < t <= 100us; count = 897` | **CPU 被窃取了 10–100 µs 的时间,共发生了 897 次。** |
| `Times between first few observations: 16ms …` | **周期**。16 ms = 62.5 Hz —— 像节拍器一样,即一种*周期性*的固件 SMI。 |
| `897 SMI detected using AMD PMCx02B` | 补丁生效了。注意它与区间统计数完全相等:每次停滞都是一次 SMI,没有其他干扰。 |
| `SMI rate: 59.8/s` | 每秒约 60 次 SMI。 |
| `maximum latency = 68us` | 最严重的一次单次停滞。 |
| `SMI generation enabled` | `SmiEnB` 是**低电平有效**的 —— coreboot 的 `global_smi_enable()` 会将其*清零*。此补丁打印出解码后的含义,而不是原始比特位,以免你理解反了。 |
| `9 SMI sources enabled` | FCH 源编号。编号是特定于 SoC 的;通过 coreboot 中的 `soc/amd//include/soc/smi.h` 或 PPR 进行映射。在 Glinda 映射中 `#33` 是 `PSP`;`#14x` 集群位于风扇/温控模块中(Glinda 上 `FANIN0` = 133)。 |
### PASS 不代表没问题
`PASS` 仅表示 `max_latency ≤ 150 µs`,这是操作系统驱动程序开始错过最后期限的阈值。上面的示例虽然通过了,但它**并不是一台安静的机器**:
- 897 × 67 µs = **15 秒中有 60 毫秒被 SMM 消耗(0.4 %)**
- **每隔 16 ms 就会出现一次 67 µs 的停滞**,永远如此
对于一般桌面使用,这是无法察觉的。但对于音频、竞技游戏、实时控制或任何对 DPC 延迟敏感的事物来说,62.5 Hz 的停滞连发是一个真正的抖动源,无论进行多少操作系统调优都无法消除——因为它发生在操作系统底层。
粗略指南:
| 频率 | 解读 |
|---|---|
| 0–2 次 SMI / 15 s | 安静。无需处理。 |
| ~60 Hz,几十 µs | 周期性固件维护 —— 风扇/温度/EC 轮询,或是 FCH SMI 定时器。很常见,通常可以在 BIOS 中减少。 |
| 呈突发状,与 USB 活动相关 | USB 传统模拟。运行测试 #5 进行确认。 |
| 任何超过 150 µs 的单一事件 | 真正的问题。预计会出现可听见的音频断音和 DPC 尖峰。 |
**周期性 16 ms 的 SMI 几乎永远不可能是 USB 引起的。** USB SMI 是呈突发状的,与设备活动相关;它们不会像时钟一样滴答作响。要确认这一点,请运行 USB 移交变体测试并进行比较——如果计数和周期没有改变,那么 USB 就不是你的问题源头。
如果遇到周期性 SMI,可在 BIOS 中尝试以下操作:禁用 *Fan Smart Control* / *Q-Fan* / *Smart Fan*(将风扇控制移至硬件层)、*ErP*、*Wake on USB*、*USB Legacy Support*、不使用时的 *TPM*,以及任何 *Hardware Monitor* 轮询选项。
## Python 控制台
顶层菜单 → **Python interactive interpreter**:
```
import testsuite; testsuite.set_verbose(3)
import smilatency
smilatency.smi_latency(60) # 60-second run instead of the fixed 15
smilatency.time_io_smi() # cost of one synthetic SMI, port from FADT SMI_CMD
import amdfch
amdfch.show() # enabled SMI sources, no test run
```
`smi_latency(60)` 是一个值得了解的命令 —— 15 秒的时间通常会完全漏掉缓慢的周期性 SMI。
## 文件
| 文件 | |
|---|---|
| `patch/boot/python/smicount.py` | **新增。** 厂商中立的 SMI 计数器。Intel `MSR 0x34`;AMD 核心 PMC 事件 `0x2B`。声明 `En` 位为空的最高计数器,保存并恢复 `PERF_CTL`/`PERF_CTR`。 |
| `patch/boot/python/smilatency.py` | 已修补的测试。使用计数器封装了原生调用。**Intel 的行为没有改变** —— 窗口内的原生 MSR 读取仍然优先;仅当其返回 `None` 时才使用 Python 的差值。 |
| `patch/boot/python/usb.py` | 已修补。添加了 xHCI 移交;还修复了两个上游 Bug(未绑定的 `duration`,未掩码的 BAR)。 |
| `patch/boot/python/amdfch.py` | **新增。** 只读的 AMD FCH SMI 模块解码器。不写入任何内容 —— 在实时的 SMM 处理程序下清除 RW1C 状态是不安全的。 |
| `remaster.sh` | 使用 `xorriso` 重新构建 ISO,保留混合的 BIOS+UEFI 引导记录。 |
| `docs/REVERSE-ENGINEERING.md` | 完整说明文档:BITS 工作原理、每一个 Intel 依赖项及其对应的 AMD 替代方案、寄存器映射、来源。 |
## 使用的寄存器
**AMD SMI 计数器** —— AMD 中没有 `MSR_SMI_COUNT` 的等价物,但有一个专用的 PMC 事件:
已在 Family 15h (libpfm4)、17h Zen1/Zen2 (illumos `amdpmc`) 和 19h (AMD uProf) 上验证。
| | Select | Value | Count | Detect |
|---|---|---|---|---|
| Legacy | `0xC0010000 + n` | `0xC0010004 + n` | 4 | 自 K7 以来的所有 AMD |
| Extended | `0xC0010200 + 2n` | `0xC0010201 + 2n` | 6 | `CPUID Fn8000_0001_ECX[23]` —— 在 Zen 上始终设置 |
`PERF_CTL` = `0x2B | USR(16) | OS(17) | En(22)`。`PERF_CTR` 为 48 位。
**xHCI 移交** — xHCI 1.2 §7.1。`HCCPARAMS1` 位于 BAR0+`0x10`,xECP 位于 bits `[31:16]`(双字)→ 遍历至 capability ID 1 → `USBLEGSUP` bit 16 = BIOS 拥有,bit 24 = OS 拥有;`USBLEGCTLSTS` 位于 +`0x04`。
**AMD FCH SMI 模块** — ACPIMMIO `0xFED80000` + `0x200`。`SmiControl0..9` 位于 `0xA0`–`0xC4`(每个源 2 bits,每个寄存器 16 个源),`SmiStatus0..4` 位于 `0x80`–`0x90`,`SmiTrig0` 位于 `0x98`,`SmiTimer` 位于 `0x96`。
完整的推导和来源见 [`docs/REVERSE-ENGINEERING.md`](docs/REVERSE-ENGINEERING.md)。
## 注意事项
1. **SMM 可能会破坏 PMC。** 性能计数器不属于 SMM 保存状态。使用 `PERF_CTL5` 的处理程序会破坏计数。症状:出现一个不合常理的数字。修复:强制使用不同的计数器索引。
2. **直方图测量的是被窃取的时间,而不是 SMI。** RDTSC 间隙能捕获任何阻止 CPU 运行的事物 —— SMI、NMI、机器检查,以及在 UEFI 下的固件定时器。计数器用于将 SMM 与其他情况区分。如果最大值很大但 SMI 为零,这是一个真实的发现,而不是 Bug。
3. **窗口偏差。** 在 AMD 上,计数器是在原生调用两侧通过 Python 读取的,而不是在其中读取。在任何实际的 SMI 频率下这都无关紧要。
4. **`bits.wrmsr` 是在裸机上进行的实时 MSR 写入**,没有操作系统进行仲裁。此补丁仅涉及性能计数器并对其进行恢复,但这毕竟是设计上较为危险的一环。
5. `xhci_handoff_to_os(disable_smi=True)` 是默认选项,并且*额外*清除了 `USBLEGCTLSTS` 的 SMI 使能位 —— 上游的 EHCI 路径只翻转了所有权信号量。忽略该信号量的固件非常常见,以至于 Linux 内核为此保留了一个永久性补丁。传入 `disable_smi=False` 可进行严格忠实于原意的仅所有权移交。
6. **ACPIMMIO 解码**必须为 `amdfch` 启用。如果窗口返回 `~0`,程序会予以提示,而不会打印乱码。
## 鸣谢
BITS 由 **Intel Corporation** 开发 — 。所有最艰难的部分(GRUB2 分支、嵌入式 CPython、SMP 启动、ACPICA)均归功于他们。
本仓库是一个兼容性补丁,而非分支。采用相同的 BSD-3-Clause 条款授权 — 详见 [`LICENSE`](LICENSE)。
寄存器相关的实际情况已与 Linux 内核、coreboot、libpfm4、illumos 以及 xHCI 1.2 规范进行了交叉核对。
一行命令 (Git Bash / WSL,U 盘盘符为 H:)
```
cp patch/boot/python/*.py /h/boot/python/
rm -f /h/boot/python/smilatency.pyc /h/boot/python/usb.pyc
```
标签:AMD, BIOS, Python, 底层硬件, 性能分析, 无后门, 系统测试, 逆向工具