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 挂起流程的工作原理。

../../_images/suspend.svg

此流程图解释了 AMD s2idle 恢复流程的工作原理。

../../_images/resume.svg

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 写入内容。未满足的约束将显示在内核日志中,可以通过处理内核环形缓冲区的日志工具(如 dmesgjournalctl)进行查看。”

如果系统在这些消息刷新之前于进入/退出时挂起,一个有用的调试策略是解除绑定 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=\M460

  • acpi.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 中。当发生随机重启时,此消息有助于确定接下来要调试的组件。