nicolasyang's blog
调试 Linux 设备电源管理,解决笔记本发热和续航问题

长期以来,在笔记本上装 Linux 都会面临发热和电池续航下降(相比于在同一台笔记本上装 Windows)的问题。这个主要是驱动的电源管理不完善,很多设备的省电功能没有启动造成的。不过最近随着主要的厂商(Intel, AMD, Qualcomm 等)拥抱开源,主动编写开源驱动,大部分问题都得到了解决。但由于还是有 bug 存在,并且电源管理的 bug 很容易造成整个系统 hang up, 所以发行版一般都把策略配置得比较保守。现在一般在接入电源是都会停用大部分电源管理功能,使用电池供电时才会启用。这样插电运行的时候就还是发热和耗电很厉害;而如果电源管理有 bug, 就会出现插电运行一切正常,不插电就 hang up 的现象。

前段时间在一台 ThinkPad p15 gen2 上装了 Linux, 就遇到了这个问题,断断续续调了很久,才解决问题。感觉这里的调试方法有参考意义,这里记录一下调试问题的过程。

背景

ThinkPad p15 gen2 是一个使用 Intel Core CPU 和 Nvidia Quadro 专业显卡的图形工作站。作为图形工作站,它的显卡接口连接方式和一般的笔记本不同。一般的笔记本是核显连接显示输出,独显只能做计算渲染,渲染结果拷贝回核显再进行输出。现在大多数的显卡切换方案,也是按照这个设计来的。但这台笔记本是 intel 核显连接内置显示屏,nvidia 独显连接外置显示屏。这个配置很难配好能适应各种情况的切换方案,由于我主要是连接外置显示器工作的,就在 UEFI 里面把核显禁用了,只用独显。只有在长期出差,没有外置显示器的情况下,才会使用核显。

除了显卡的问题,另外一个更严重的问题就是,这台机器在不接电源的时候,就会在开机进入桌面环境后不久就 hang 住,连关机都关不了,只能按 alt + sysrq + reisub 重启。由于这个机器的网卡声卡什么全都是 intel 的,就只有显卡和 NVMe 硬盘是第三方品牌,我就直接怀疑这两个设备有问题,把它们的电源管理禁用。但问题并没有解决,最后只好把全部 PCIe 设备的电源管理关掉,才没有问题。不过这样就完全没有续航了,2 个小时就耗尽电池。

在这期间,我还遇到了 type-c 供电驱动程序的 bug, 表现也是系统 hang up. 多个问题交织到一起更加增大了调试难度。

x86 电源管理概况

x86 平台上的电源管理大概分为 3 部分:

  1. CPU 电源管理:包括 freqency scaling, C-state 这些
  2. 平台电源管理:包括待机、休眠、关机、唤醒、电池充放电管理这些
  3. 外设设备的电源管理:目前的 x86 上的外设设备主要就分为 PCIe 和 USB 两大类

其中,CPU 就只有 Intel AMD 两家,并且文档资料齐全,厂商也支持开源驱动开发,电源管理支持得非常好。平台这块则是 firmware 通过 ACPI 向系统提供接口。这里台式机一般没有问题,但笔记本由于多了合盖检测、背光、电池这些,更加复杂,出问题的可能性比较大,不过老牌厂商,如 ThinkPad, Dell 这些,都经过了内核驱动和 ACPI 固件的长期磨合,一般也问题不大。外设设备中,USB 的规范比较具体,比如 HID 键盘鼠标、摄像头、U 盘这些都有通用协议和驱动(比如通用的 Mass Storage 驱动就可以适用于所有品牌的 U 盘),也不容易出问题。问题最大的就是 PCIe 设备,这类设备都是显卡、网卡这类的复杂设备,固件和驱动都很复杂,如果有 bug, 就很容易造成内核错误。

PCIe 设备电源管理

PCIe 设备的电源管理有统一的规范和通信协议。一个设备的电源状态分为 D0, D1, D2, D3 4 个状态。其中 D0 是完全启动正常工作,D3 是最深的省电状态。这两个状态是所有设备都要支持的,D1 和 D2 是可选的。CPU 可以通过写入设备的配置寄存器来改变电源状态。PCIe 设备在进入省电状态时,会关闭一部分电路,缓存、寄存器之类的状态信息可能会丢失。这样,在唤醒设备时,就需要重新初始化这部分状态。这需要驱动的密切配合,如果这个过程有 bug, 设备就会进入一个非预期的状态,驱动无法正常和设备通信,就会造成 hang up 之类的问题。

Linux 下的配置

内核

Linux 下,每个 PCIe 的电源管理 RUNTIME_PM 可以通过 sysfs 中的开关 /sys/bus/pci/devices/<address>/power/control 单独开启或关闭。开关的值可以是

  • on: 设备保持开启状态,即禁用电源管理
  • auto: 自动控制,即启用电源管理

用户空间

有个 tlp 命令可以根据 /etc/tlp.conf 中的配置来调整 sysfs 中的各个开关。在多数发行版中,这个命令会通过 udev 规则来触发,在启动过程中、插拔电源的情况下执行。在多数发行版的默认配置中,tlp 会在电池运行时打开 更多省电功能。

另外有个命令是 Intel 开发的 powertop. 这是一个 TUI 应用,可以监控 CPU 的电源、外设状态,以及调整 sysfs 中的电源管理开关。

tlp 适合保存长期、持久化的配置,而 powertop 适合在调试过程中来使用。

调试过程

这台笔记本最严重的问题就是拔掉电源、电池供电时,会随机 hang 住。例如,不插电源开机,一般是可以正常进入桌面环境,但进入桌面环境后,尝试启动一个应用,如 firefox, 就会启动不了。这个时候其它部分看起来都是正常的,但在终端里尝试 kill 掉 firefox, 也会卡住。尝试执行 sudo journcalctl -kdmesg 来检查日志也会卡住。甚至执行 sudo reboot 也会卡住。

这种已经启动的程序运行正常,新程序无法启动的问题,让我地第一时间怀疑问题是硬盘 I/O 相关的,于是第一时间通过 tlp 禁用了 NVMe 的电源管理,但是没有效果。考虑到 N 卡是闭源驱动,也禁用了电源管理,也没有效果。

这个时候我还不太了解 powertop 这个工具,就开始对比 tlp 的配置在电源供电和电池供电的状态下有哪些区别。最明显的区别就是,插电状态的默认 PCIe 电源管理是 on, 电池状态是 auto. 这里我把电池状态也改成 on 后,问题基本解决。

但这只能算是个能用的方案。在没有开启设备省电的情况下,机器发热比较厉害,并且电池续航只有大概 2.5 小时。

为什么说是基本解决呢?

因为这里还穿插了一个 type-c 供电驱动的 bug, 这个 bug 和 PCIe 电源管理的 bug 混在一起,很难调试。但根据最后的结论,这里关闭 PCIe runtime power management 后,就没有因为 PCIe 设备导致 hang up 的问题了。

这个 type-c bug, 会导致 kernel oops. 在过了一个多月,这个 kernel oops 被修复以后,我才重新找时间去定位 PCIe 的问题。

到了这一步,我能想到的就是通过二分法去查找因为问题的设备,即每次禁用一半设备的电源管理,看看问题还是否复线。但看到除了 NVMe 和显卡,其它设备全都是 Intel 的,我都怀疑是否有多个设备同时有问题,用 tlp 来配置又还是比较麻烦的,这个问题就脱了很久。

直到两个月后,有几天比较闲,我尝试去 powertop 这个工具里去看一下。这就发现 powertop 可以通过 TUI 列出所有外设并 toggle 每个设备的电源管理开关。于是我决定在插电运行,默认关闭所有 PCIe 设备电源管理的情况下,一个个打开电源管理开关,看看什么时候会出故障。

于是一个终端运行 dmesg 看日志,另一个终端开 powertop. 首先把我没有用的设备打开,看了一下,好像完全没用的就只有有线网卡。按下 Enter 打开电源管理,系统立刻表现出异常,过了十几秒中,耳机里的音乐也停了。所以这就找到问题了?

重启电脑,在 tlp 里面把有线网卡的电源管理禁用,尝试一下拔掉电源,好像没有出现任何问题。再继续使用电脑,多开几个新应用,也都没有问题。所以引起问题的竟然是一个 Intel 的,我根本没有使用的有线网卡???因为有线网卡这种设备,在我用过的环境中,从来没有出现过问题;Intel 的设备,也是从来没有出现过问题。

后续配置

把有线网卡加入 tlp 黑名单后,在电池模式下开启省电,大概有 5.5 小时的续航,续航时间翻了一倍。后面我顺便把插电状态下的 PCIe runtime power management 也打开了。虽然插电时没有电池续航的问题,但打开电源管理能让空闲时的 CPU 温度降低大约 7 度。

想到这个有线网卡我也用不上,后面我干脆在 UEFI 里面把网卡禁用了。

总结

  1. 如果一个问题只在不插电的时候出现,那么它大概率和省电配置有关,这时应该去配置 tlp.conf.
  2. Intel 的驱动也可能出问题,不能完全信任。
  3. 发行版默认在插电状态下会禁用一下省电配置,如果想要省电/静音/冷却,应该去修改 tlp.conf 把它打开。(包括台式机)
  4. powertop 是个调试省电配置的好工具。

最后修改于 2022-09-10

Comments powered by Disqus