18. 调试 AMD Zen 系统¶
18.1. 简介¶
本文档描述了用于调试 AMD Zen 系统问题的有用技术。它旨在供开发人员和技术用户使用,以帮助识别和解决问题。
18.2. S3 与 s2idle¶
在 AMD 系统上,无法同时支持挂起到内存 (S3) 和挂起闲置 (s2idle)。要确认您的系统支持哪种模式,您可以查看 cat /sys/power/mem_sleep。如果显示 s2idle [deep],则支持 S3。如果显示 [s2idle],则支持 s2idle。
在支持 S3 的系统上,将利用固件把所有硬件置于适当的低功耗状态。
在支持 s2idle 的系统上,内核将负责把设备切换到适当的低功耗状态。当所有设备都处于适当的低功耗状态时,硬件将转换进入硬件睡眠状态。
在一个挂起周期之后,您可以通过查看 cat /sys/power/suspend_stats/last_hw_sleep 来了解在硬件睡眠状态中花费了多长时间。
此流程图解释了 AMD s2idle 挂起流程的工作原理。
此流程图解释了 AMD s2idle 恢复流程的工作原理。
18.3. s2idle 调试工具¶
由于可能出现问题的地方很多,因此开发了一个调试工具 amd-debug-tools,它可以帮助测试常见问题并提供建议。
如果您遇到 s2idle 问题,最好从该工具开始并按照其发现的说明操作。如果您仍有问题,请将此脚本生成的报告作为 Bug 提交到 drm/amd gitlab。
18.4. 来自 IRQ 的 s2idle 虚假唤醒¶
虚假唤醒通常会在 /sys/power/pm_wakeup_irq 中设置一个 IRQ。这可以与 /proc/interrupts 进行匹配,以确定是哪个设备唤醒了系统。
如果这还不足以调试该问题,则可以设置以下 sysfs 文件以为唤醒过程增加更多详细信息:
# echo 1 | sudo tee /sys/power/pm_debug_messages
# echo 1 | sudo tee /sys/power/pm_print_times
完成这些更改后,内核将显示可追溯至内核 s2idle 循环代码的消息,并在唤醒时显示任何活动的 GPIO 源。
如果唤醒是由 ACPI SCI 引起的,可能需要进行额外的 ACPI 调试。这些命令可以启用额外的跟踪数据:
# echo enable | sudo tee /sys/module/acpi/parameters/trace_state
# echo 1 | sudo tee /sys/module/acpi/parameters/aml_debug_output
# echo 0x0800000f | sudo tee /sys/module/acpi/parameters/debug_level
# echo 0xffff0000 | sudo tee /sys/module/acpi/parameters/debug_layer
18.5. 来自 GPIO 的 s2idle 虚假唤醒¶
如果在唤醒系统时某个 GPIO 处于活动状态,理想情况下您应查看原理图以确定它与哪个设备相关联。如果原理图不可用,另一种策略是查看 ACPI _EVT() 条目,以确定该 GPIO 处于活动状态时会通知哪个设备。
假设一个假想的例子,即 GPIO 59 唤醒了系统。您可以查看 SSDT 以确定当 GPIO 59 处于活动状态时会通知哪个设备。
首先将 GPIO 编号转换为十六进制。
$ python3 -c "print(hex(59))"
0x3b
接下来确定哪个 ACPI 表包含 _EVT 条目。例如:
$ sudo grep EVT /sys/firmware/acpi/tables/SSDT*
grep: /sys/firmware/acpi/tables/SSDT27: binary file matches
解码此表:
$ sudo cp /sys/firmware/acpi/tables/SSDT27 .
$ sudo iasl -d SSDT27
然后查看该表并找到 GPIO 0x3b 的匹配条目。
Case (0x3B)
{
M000 (0x393B)
M460 (" Notify (\\_SB.PCI0.GP17.XHC1, 0x02)\n", Zero, Zero, Zero, Zero, Zero, Zero)
Notify (\_SB.PCI0.GP17.XHC1, 0x02) // Device Wake
}
在此例中可以看到,当 GPIO 59 处于活动状态时,会通知设备 \_SB.PCI0.GP17.XHC1。很明显这是一个 XHCI 控制器,但为了更进一步,您可以通过将其与 ACPI 匹配来弄清楚具体是哪个 XHCI 控制器。
$ grep "PCI0.GP17.XHC1" /sys/bus/acpi/devices/*/path
/sys/bus/acpi/devices/device:2d/path:\_SB_.PCI0.GP17.XHC1
/sys/bus/acpi/devices/device:2e/path:\_SB_.PCI0.GP17.XHC1.RHUB
/sys/bus/acpi/devices/device:2f/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT1
/sys/bus/acpi/devices/device:30/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT1.CAM0
/sys/bus/acpi/devices/device:31/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT1.CAM1
/sys/bus/acpi/devices/device:32/path:\_SB_.PCI0.GP17.XHC1.RHUB.PRT2
/sys/bus/acpi/devices/LNXPOWER:0d/path:\_SB_.PCI0.GP17.XHC1.PWRS
在这里可以看到它匹配到了 device:2d。查看 physical_node 以确定这实际上是哪个 PCI 设备。
$ ls -l /sys/bus/acpi/devices/device:2d/physical_node
lrwxrwxrwx 1 root root 0 Feb 12 13:22 /sys/bus/acpi/devices/device:2d/physical_node -> ../../../../../pci0000:00/0000:00:08.1/0000:c2:00.4
因此结果很明了:与此 GPIO 唤醒关联的 PCI 设备是 0000:c2:00.4。
amd_s2idle.py 脚本将为您捕获大部分此类排查信息。
18.6. s2idle PM 调试消息¶
在 AMD 系统的 s2idle 流程中,ACPI LPS0 驱动程序负责检查所有 uPEP 约束。未通过 uPEP 约束检查并不会阻止进入 s0i3。这意味着如果某些约束未满足,即使存在某些已知问题,内核也可能会尝试进入 s2idle。
要启用 PM 调试,可以在引导时指定内核命令行选项 pm_debug_messagess,或者向 /sys/power/pm_debug_messages 写入内容。未满足的约束将显示在内核日志中,可以通过处理内核环形缓冲区的日志工具(如 dmesg 或 journalctl)进行查看。”
如果系统在这些消息刷新之前于进入/退出时挂起,一个有用的调试策略是解除绑定 amd_pmc 驱动程序,以防止通知平台开始进入 s0i3。这将阻止系统在进入或退出时挂起,并允许您查看所有未通过的约束。
cd /sys/bus/platform/drivers/amd_pmc
ls | grep AMD | sudo tee unbind
完成此操作后,运行挂起周期并重点查找以下方面的错误:
ACPI: LPI: Constraint not met; min power state:%s current power state:%s
18.7. s2idle 问题的历史示例¶
为了帮助理解可能出现的问题类型以及如何调试它们,以下是一些已经解决的 s2idle 问题历史示例。
18.7.1. 核心离线¶
有最终用户报告称,使某个核心离线(offline)会阻止系统正确进入 s0i3。通过使用 AMD 内部工具进行调试,捕获并显示了来自硬件的指标流,展示了核心离线时的变化。经确定,硬件没有收到离线核心处于最深睡眠状态的通知,从而阻止了 CPU 进入最深状态。该问题经排查是因为缺少一条在离线时将核心置于 C3 状态的命令。
commit d6b88ce2eb9d2 (“ACPI: processor idle: Allow playing dead in C3 state”)
18.7.2. 恢复后损坏¶
Rembrandt 平台出现的一个大问题是恢复后出现图形损坏。这发生的原因是 PSP 和驱动程序职责错位。PSP 会保存和恢复 DMCUB,但驱动程序假定它需要在恢复时重置 DMCUB。实际上早期芯片也存在这种错位,但当时未被发现。
commit 79d6b9351f086 (“drm/amd/display: Don’t reinitialize DMCUB on s0ix resume”)
18.7.3. 连续挂起失败¶
当使用触发 IRQ 唤醒的唤醒源时,pinctrl-amd 驱动程序中的一个 bug 可能会捕获到错误的 IRQ 状态,并阻止系统正确地再次进入睡眠状态。
commit b8c824a869f22 (“pinctrl: amd: Don’t save/restore interrupt status and wake status bits”)
18.7.4. 5 分钟后基于定时器的虚假唤醒¶
HPET 被用于为系统编程唤醒源,然而这会导致 5 分钟后的虚假唤醒。应该使用的正确闹钟是 ACPI 闹钟。
commit 3d762e21d5637 (“rtc: cmos: Use ACPI alarm for non-Intel x86 systems too”)
18.7.5. 恢复后磁盘消失¶
从 s2idle 恢复后,NVMe 磁盘会消失。这是因为 BIOS 没有指定 _DSD StorageD3Enable 属性。这导致 NVMe 驱动程序在挂起时未能将磁盘置于预期状态,从而在恢复时失败。
commit e79a10652bbd3 (“ACPI: x86: Force StorageD3Enable on more products”)
18.7.6. 虚假 IRQ1¶
许多 Renoir、Lucienne、Cezanne 和 Barcelo 平台存在一个平台固件 bug,即在 s0i3 恢复期间会触发 IRQ1。
这已经在平台固件中得到了修复,但许多系统并未收到任何后续的平台固件更新。
commit 8e60615e89321 (“platform/x86/amd: pmc: Disable IRQ1 wakeup for RN/CZN”)
18.7.7. 硬件超时¶
除了接受来自 amd-pmc 驱动程序的值之外,硬件还要执行许多操作。由于与硬件的通信路径是一个邮箱(mailbox),它可能会响应不够及时。此问题表现为挂起失败
PM: dpm_run_callback(): acpi_subsys_suspend_noirq+0x0/0x50 returns -110
amd_pmc AMDI0005:00: PM: failed to suspend noirq: error -110
时序问题是通过比较空闲掩码(idle mask)的值来识别的。
commit 3c3c8e88c8712 (“platform/x86: amd-pmc: Increase the response register timeout”)
18.7.8. 面板开启时未能进入硬件睡眠状态¶
在某些 Strix 系统上,如果内部显示面板在挂起期间保持开启,则会阻止系统进入硬件睡眠状态。
尽管面板在挂起期间会被关闭,但它暴露出一个时序问题:某个中断导致显示硬件唤醒并阻止了低功耗状态的进入。
commit 40b8c14936bd2 (“drm/amd/display: Disable unneeded hpd interrupts during dm_init”)
18.8. 运行时功耗问题¶
运行时功耗受多种因素影响,包括但不限于 PCIe 活动状态电源管理 (ASPM) 的配置、显示屏亮度、CPU 的 EPP 策略以及设备的电源管理。
18.8.1. ASPM¶
为了获得最佳的运行时功耗,应当按照硬件厂商 BIOS 的预期来配置 ASPM。为此,在编译 Linux 内核时应将 CONFIG_PCIEASPM_DEFAULT 设置为 y,且不应修改 sysfs 文件 /sys/module/pcie_aspm/parameters/policy。
最值得注意的是,如果任何设备的 L1.2 未能正确配置,SoC 将无法进入最深的空闲状态。
18.8.2. EPP 策略¶
可以使用 energy_performance_preference sysfs 文件为 CPU 设置能效或性能偏好。当更倾向于性能时,会对电池续航产生直接影响。
18.9. BIOS 调试消息¶
大多数 OEM 机器没有用于输出内核或 BIOS 调试消息的串行 UART。然而,BIOS 调试消息对于理解 BIOS 缺陷以及调用 BIOS AML 的 Linux 内核驱动程序缺陷非常有用。
由于大多数 OEM AMD 系统上的 BIOS 都基于 AMD 参考 BIOS,因此用于导出调试消息的基础设施通常与 AMD 参考 BIOS 相同。
18.9.1. 手动解析¶
通常存在一个 ACPI 方法 \M460,AML 的不同执行路径会调用它来向 BIOS 串口日志输出消息。该方法接受 7 个参数,其中第一个参数是字符串,其余为可选整数
Method (M460, 7, Serialized)
以下是 BIOS AML 可能通过 \M460 调用的字符串示例
M460 (" OEM-ASL-PCIe Address (0x%X)._REG (%d %d) PCSA = %d\n", DADR, Arg0, Arg1, PCSA, Zero, Zero)
通常在执行时,\M460 方法会将附加参数填充到字符串中。为了从 Linux 内核中获取这些消息,已在 ACPICA 中添加了一个钩子(hook),能够捕获发送给 \M460 的参数并将其打印到内核环形缓冲区。例如,可能会向内核环形缓冲区输出以下消息
extrace-0174 ex_trace_args : " OEM-ASL-PCIe Address (0x%X)._REG (%d %d) PCSA = %d\n", ec106000, 2, 1, 1, 0, 0
为了获取这些消息,您需要启用 CONFIG_ACPI_DEBUG 编译内核,然后开启以下 ACPICA 跟踪参数。这可以通过内核命令行或在运行时完成
acpi.trace_method_name=\M460acpi.trace_state=method
注意:这些参数在引导时可能会产生大量日志。如果您在内核命令行中开启了这些参数,请同时考虑将 CONFIG_LOG_BUF_SHIFT 调大(例如设为 17),以避免丢失早期的引导消息。
18.9.2. 工具辅助解析¶
如上所述,手动解析可能会很繁琐,尤其是当消息非常多时。为此,在 amd-debug-tools 开发了一个工具来帮助解析这些消息。
18.10. 随机重启问题¶
当发生随机重启时,导致重启的高级原因会被存储在一个寄存器中,该寄存器会保持到下一次启动。
- 重启原因共有 6 个大类:
软件引发
电源状态转换
引脚引发
硬件引发
远程重置
内部 CPU 事件
位 |
类型(Type) |
原因 |
|---|---|---|
0 |
引脚 |
热引脚 BP_THERMTRIP_L 被触发 |
1 |
引脚 |
电源按钮被按住了 4 秒 |
2 |
引脚 |
关机引脚被触发 |
4 |
远程 |
收到了远程 ASF 关机命令 |
9 |
内部 |
内部 CPU 温度限制被触发 |
16 |
引脚 |
系统重置引脚 BP_SYS_RST_L 被触发 |
17 |
软件 |
软件发出了 PCI 重置 |
18 |
软件 |
软件向重置控制寄存器 0xCF9 写入了 0x4 |
19 |
软件 |
软件向重置控制寄存器 0xCF9 写入了 0x6 |
20 |
软件 |
软件向重置控制寄存器 0xCF9 写入了 0xE |
21 |
ACPI 状态 |
发生了 ACPI 电源状态转换 |
22 |
引脚 |
键盘重置引脚 KB_RST_L 被触发 |
23 |
内部 |
发生了内部 CPU 关机事件 |
24 |
硬件 |
系统在启动失败定时器超时前未能成功启动 |
25 |
硬件 |
硬件看门狗定时器超时 |
26 |
远程 |
收到了远程 ASF 重置命令 |
27 |
内部 |
未纠正的错误导致了数据结构(Data Fabric)同步泛洪(sync flood)事件 |
29 |
内部 |
FCH 和 MP1 热重置握手失败 |
30 |
内部 |
发生了奇偶校验错误 |
31 |
内部 |
发生了软件同步泛洪事件 |
内核会在引导时读取此信息并将其打印到 syslog 中。当发生随机重启时,此消息有助于确定接下来要调试的组件。