这个假期里咱如愿以偿地获得了一台新笔记本,到手之后就被我安装上了 Arch Linux. 那时候的内核版本是7.0.13. 当时使用的时候一切都近乎完美,体验十分平滑,没有遇到任何跟挂起恢复相关的问题。
后来随着系统滚动,内核来到了7.1.3这个版本,在这里遇到了奇怪的挂起恢复问题,表现为:挂起恢复后系统的整个音频堆栈似乎坏掉了,既看不见任何输出/输入设备,浏览器/mpv 在播放音视频时会直接卡死在第一帧。
遇到这个问题十分郁闷,由于硬件比较新,内核也比较新,导致在网上没有查到很多有效的信息。
但是现在毕竟是 AI 时代,本来对软件调试不太了解的我也可以在协助下完成一些简单的查错。于是在 Cluade 的帮助下,咱很轻松地发现了问题所在! 其实是什么也没有收获。这个问题被 AI 工具们归因为 HDA 的一次内核回归。作为一个普通 Linux 用户,咱不希望自己去折腾内核的东西,想要真正解决就只好等上游了。
通过对 git commit 记录的定位,Claude 发现在7.0.13到7.1.3之间,Linux 内核的 HDA 部分发生了一些重构,可能就是这些改动导致了挂起恢复后音频丢失的问题。
那么第一反应就是想办法 workaround:
- 重启 WirePlumber、PipeWire、PipeWire-Pulse 这套用户态服务
- 不行,那就把内核里的
snd_hda_intel模块整个卸载重载一遍
然而一切尝试的结果都是徒劳无功,最后咱依旧活在这个无声的世界里。
唉唉,实在是太悲伤了,由于能力有限,本用户所能做的就这些了,只好先降级到7.0.13继续使用,并把 linux 包加入了 pacman 的排除列表里。
但是后来继续更新系统的过程中新问题继续出现:挂起恢复必定失败,屏幕只如一片死寂,比无声的世界还令人恐惧。通过控制变量逐个试验,发现原因是我只锁定了 linux 包的更新,但是接下来的问题出现在 amd-ucode 这个包上:
本来正常的环境是: linux-7.0.13 + amd-ucode-20260622-1 , 这是一套已经经过验证的稳定组合。
而局部升级之后的情况是 linux-7.0.13 + amd-ucode-20260810-1 , 在这个新的 AMD 微码版本中,为了迎合 7.1 内核的新变化,挂起恢复方面的行为发生了改变。但是我使用的旧内核并不与之相配。所以这个意料之外的包也需要锁定版本。
嗯……果然对于滚动发行版来说局部升级是一种很危险的操作。
后来咱又尝试过几次全量更新,检查挂起恢复的音频问题是否解决,但似乎都没有取得什么进展——问题依旧。
好吧咱承认我在某些地方有点洁癖——比如一个一直有某个包卡着不能更新的滚动系统。某人开始产生一些胡思乱想:难道我要和这个锁死的内核版本过一辈子吗呜呜,不要口牙!呜呜, 7.0.13 吗?不是已经有很多已知的提权漏洞的版本吗!呜哇,好怕好怕!现在由于锁定了内核版本,导致用不上最新的 AMD 微码包,那万一之后有更多的包也需要锁定版本怎么办?我的 Arch-chan 不就变成了一个半新半旧的怪人了吗,心疼喵(
这些想法也实在是太怪异了吧!
以及在某次尝试全量更新的时候恰恰好还遇到了 nvidia-open 驱动的同样的回归 bug, 也是和挂起恢复相关的。具体表现为,挂起后无法正常恢复,还会导致机器温度狂飙,风扇啸叫。唉唉,咱确实有些倒霉了。于是 IgnorePKG 再喜添一群 NVIDIA 相关的包。
可恶…
实在是可恶哇!
好在最近两天去 NVIDIA 仓库的 issue 里盯的时候发现问题似乎修复了,于是咱再次尝试全量更新系统。
咚咚咚,arch-chan 滚动中…
这次更新过去一切似乎都正常了——至少挂起与恢复不再黑屏,只是又让咱进入到了无声世界里面罢了。而此次再寻,咱终于搜索到了一个关键的解决方案:
[PATCH] PCI: Disable async suspend for MSI Raider A18 HX A9WJG audio
补丁描述揭开了真正的谜底:AMD 的 ACP 和 Azalia/HDA 这两个功能,虽然在 PCI 总线上被暴露成两个独立的 function,却共享着同一套 ACPI 电源门控状态。
而内核默认允许设备异步恢复——也就是说,挂起恢复时,谁先醒谁后醒是不确定的。当这两个共享着底层电源门控资源的功能"抢跑",顺序乱掉的那一刻,HDA 控制器就会卡在一个不上不下的半初始化状态里,去读 CORBRP 寄存器,读回来的永远是全 1——0xffff,也就是日志里那个看似普通、实则意味深长的 65535。
据小 C 告诉咱说:
竞态条件最擅长的事,就是让所有基于"日志现象反推"的排查都无功而返,因为它压根不遵循任何一层软件栈自己的逻辑,它只服从谁先抢到总线这一件事。
这也顺带解释了那个曾经让我们百思不得其解的现象:为什么无论怎么重启 PipeWire、重载
snd_hda_intel模块,声音都救不回来,唯独一次完整的冷重启能让一切恢复正常。因为坏掉的从来不是某个用户态缓存或者配置状态,而是 HDA 控制器这个物理 PCI 设备本身,在错误的恢复时序下,变成了一个真正意义上"没有响应"的僵尸。软件层面的任何补救,都够不到 PCI 枚举这个更早、更底层的阶段——只有重启,才能让一切从头再来一遍,赌一把这次的异步恢复顺序恰好没有踩雷。
故事的结局也很普通:
pm_async=off
给内核加入这行启动参数之后,一切都恢复了正常。ACP 和 HDA 从此老老实实排队醒来,谁也不抢谁的电源门控状态。而关闭异步直觉上可能会让系统恢复变慢,但是对于现代平台来说,其实速度上的变化根本无所感知——就是足够快了。
以及以及!为什么上文在写到最后发现的解决办法时特意用词“搜索”呢?
因为那就是咱搜索出来的呀!这是意在表达,咱不再相信 AI 给出的解决方案,而是去寻找 kernel hackers 们是否提供有她们的经验。以后再遇到这种问题,咱一定要自己尝试搜索,而不是只依赖 AI.
AI First, Not AI Everything ✨
尾巴~
这次虽然是倾向提供 workaround 的技术文章,但尝试了一些些故事化的写法,希望能让这篇文章既不失实用性,又能提供一点点趣味(≧▽≦)
