我如何帮助修复 Linux 上 AMD GPU 的睡眠 - 唤醒挂起问题

作者 nyanpasu64 于 2024 年 12 月 29 日撰写。其桌面双系统启动 Windows 和 Linux,多年来 Linux 在高内存使用时睡眠电脑会经常崩溃,唤醒后显示黑屏或光标移动,或进入无图像的“植物人”状态,仅对 magic SysRq 或硬重置有响应。作者将此行为归因于 amdgpu 驱动的电源/内存管理错误,花了一年多时间 brainstorm 和实施解决方案。

诊断问题

  • 2023 年 9 月开始调试,设置为 Gigabyte B550M DS3H 主板、AMD RX 570 GPU 和 1TB Kingston A2000 NVMe SSD,运行 Arch Linux 及 systemd-boot 和 Linux 6.4。系统崩溃后首先检查日志,如journalctl --system -b -1可打印上次启动的系统日志,但有时崩溃后日志无记录。电脑进入“不死”状态,能显示 KDE 锁屏时钟实时更新,但登录或交互会锁定,推测是 NVMe 存储驱动在系统唤醒时未重新初始化导致系统冻结和日志停止写入。尝试通过添加内核参数nvme_core.default_ps_max_latency_us=0和启用软件 IOMMU 解决 APST 问题但未成功,安装 SSD 固件升级和升级到 2TB 启动 SSD 也无帮助。系统会连续尝试多种睡眠模式,导致系统日志混乱和内核进一步损坏,关闭此功能后简化了调试但未解决根本问题。还尝试通过echo 1 > /sys/power/pm_trace检查睡眠失败的位置,发现 Linux 会从 amdgpu 挂起失败中恢复而不是进入全系统挂起,pm_trace将睡眠 - 唤醒进度存储在计算机系统时间中。之后启用 systemd 调试 shell,通过添加systemd.debug_shell内核参数或运行systemctl enable debug-shell可在系统崩溃时运行命令,因键盘问题还使用了 PS/2 键盘和后来设置的串行控制台。通过查看崩溃日志,发现崩溃通常发生在 amdgpu 的 TTM 缓冲区驱逐中,查找 amdgpu 的 Gitlab 错误跟踪器发现相关问题,了解到 Linux amdgpu 驱动在高内存使用下有错误,会导致系统内存不足崩溃而不是将内存移动到磁盘交换。

上游调试

  • 认为要在挂起磁盘存储之前将 VRAM 驱逐到系统内存,在讨论后 Mario Limonciello 建议启用/sys/power/pm_print_times/sys/power/pm_debug_messages,日志显示睡眠时 NVMe 和 amdgpu 驱动并行进入pci_pm_suspend。尝试将 GPU 挂起提前到 SSD 挂起之前,通过移动 VRAM 驱逐的位置来解决问题,但仍会失败,因为pm_restrict_gfp_mask()dpm_preparedpm_suspend调用之前禁用了交换,导致 amdgpu 备份 VRAM 时内存不足。测试补丁时“daqiu li”报告挂起极慢并建议使用__GFP_NORETRY进行分配,但作者未体验到问题也未得到 amdgpu 开发者的回复。

中途:使用 Ghidra 调试崩溃

  • 在测试prepare()期间的交换时,遇到 amdgpu 崩溃错误BUG: unable to handle page fault for address: fffffffffffffffc,通过保存并提取amdgpu.ko内核模块,在 Ghidra 中反编译并映射崩溃位置到内核源代码,发现是dm_resume中的空指针解引用错误,原因是drm_atomic_helper_suspend()可能返回错误指针并被直接赋值给指针导致解引用,Mario 修复了此问题。

放弃:在 prepare()期间允许交换?

  • 高内存使用仍会导致睡眠失败,想在dpm_prepare备份 VRAM 后禁用交换,但面临实际挑战,如pm_restrict_gfp_mask()的声明和调用位置问题,以及在dpm_suspend_start中禁用交换的正确性挑战,如 hybrid sleep 的情况,此方法虽减少了失败或崩溃的挂起次数,但未完全解决问题,且作者未继续尝试上游更改。

附注:关机时损坏的控制台

  • 关机时间歇性出现充满 8x16 颜色块的损坏屏幕,通常在之前的睡眠尝试中找到 amdgpu 驱动的错误,作者已多次报告此问题但未单独提交 bug 报告,也未确定原因。

解决方法:电源管理通知器

  • 2024 年 11 月,Mario 要求用户测试一个允许多交换时驱逐的补丁,该补丁通过调用register_pm_notifier()函数在回调结构上,在PM_SUSPEND_PREPARE消息时调用amdgpu_device_evict_resources()来驱逐 VRAM 到系统内存,在enter_state函数中修改流程图,此方法允许 amdgpu 在交换被禁用或磁盘被冻结之前驱逐 VRAM 到系统内存,测试后能在高内存和 VRAM 使用下多次成功挂起,唯一问题是有几秒音频循环,此补丁经过几轮代码审查后合并到 amdgpu 树中,最终解决了该问题,此补丁应会在 2025 年的稳定 Linux 内核 6.14 中推出,并将在发行版更新周期中应用。还发现了另一个 amdgpu 工作区 memreserver,它通过分配系统内存并填充 0xFF 字节来为 VRAM 腾出空间,但作者未测试其功能和性能。

总之,此问题经过一年多的调试和多人尝试才得以解决,预计 2025 年进入稳定 Linux 内核 6.14 并向发行版推出。

阅读 14
0 条评论