1. 电源管理基础:Linux电源管理子系统概述、ACPI与设备树、动态电源管理(DPM)概念

各位同学,咱们今天聊聊电源管理。说实话,这玩意儿在嵌入式开发里,经常被当成「锦上添花」的东西。但我做过几个项目后,发现这其实是「生死攸关」的——尤其是做电池供电的设备,功耗降不下来,产品根本没法用。

我个人习惯把电源管理分成两个层面来看:一个是系统级的,比如整个SoC怎么休眠、怎么唤醒;另一个是设备级的,比如某个外设不用了,怎么把它的时钟关掉。今天这一讲,咱们先把基础打牢。

1.1 Linux电源管理子系统:一个三层架构

Linux的电源管理,说白了就是一套「省电机制」。它从上到下分三层:

  • 用户空间层:你写个脚本,echo mem > /sys/power/state,系统就休眠了。这是最上层的接口。
  • 内核核心层:PM core,负责调度、状态机管理。它像个大管家,协调各个设备。
  • 设备驱动层:这才是咱们的战场。每个驱动都要实现suspend/resume回调。

关键点:驱动开发者主要跟设备驱动层打交道。你写的probe、remove、suspend、resume,就是电源管理的「最后一公里」。

我记得刚入行时,有个前辈跟我说:「驱动写得好不好,看suspend/resume写得干不干净。」当时不理解,后来踩了坑才明白——系统休眠时,设备没关干净,唤醒后直接死机。嗯,这种问题查起来特别痛苦。

1.2 ACPI与设备树:两种「硬件说明书」

你想想看,内核怎么知道你的板子上有哪些设备?哪些设备支持休眠?这就需要一种描述硬件的方式。

目前主流的有两种:

特性 ACPI 设备树(Device Tree)
主要应用 x86、服务器、笔记本 ARM、嵌入式、RISC-V
描述方式 AML字节码,像个微型程序 dts文本,编译成dtb
电源管理 提供_PSC、_PS0/_PS3等方法 通过power-domains、regulator等属性
运行时修改 支持动态加载 通常固定,需重新编译

ACPI 这边,我做过一个x86平板项目。ACPI表里定义了设备的电源状态,比如D0(全速)、D3hot(关闭主电源但保留Vaux)、D3cold(完全断电)。驱动里要调用acpi_device_set_power()来切换状态。说实话,ACPI的AML代码调试起来挺烦的,我建议你备好iasl反编译工具。

设备树 这边,嵌入式开发更常见。比如你有个I2C触摸屏,在dts里这样描述:

i2c_touch: touch@38 {
    compatible = "goodix,gt911";
    reg = <0x38>;
    interrupt-parent = <&gpio1>;
    interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
    power-domains = <&pd_core>;
    /* 这个属性告诉内核:触摸屏属于哪个电源域 */
};

我曾经在项目里遇到一个坑:设备树里忘了加power-domains属性,结果触摸屏休眠后唤不醒。查了两天才发现,是电源域没关联上。所以,设备树里的电源相关属性,一个都不能少

1.3 动态电源管理(DPM):让设备「随用随开」

DPM这个概念,说白了就是「按需供电」。设备在用的时候全速跑,不用的时候立刻降频或断电。跟家里关灯一个道理——人走灯灭。

Linux内核里,DPM主要通过两种机制实现:

  1. 系统睡眠:整个系统一起睡。suspend-to-RAM、suspend-to-disk。
  2. 运行时电源管理(Runtime PM):单个设备独立管理。这是更细粒度的控制。

我的建议:优先用Runtime PM。系统睡眠是「大炮打蚊子」,Runtime PM才是「精准打击」。比如一个USB鼠标,5分钟不动,内核自动把它挂起;你一动,立刻唤醒。用户根本感觉不到。

Runtime PM的核心API就几个:

// 通知内核:设备正在忙,别挂起
pm_runtime_get_sync(dev);

// 通知内核:设备空闲了,可以挂起
pm_runtime_put_sync(dev);

// 在驱动中实现挂起/恢复回调
static int mydev_runtime_suspend(struct device *dev) {
    // 关闭时钟、降低电压
    clk_disable(mydev->clk);
    regulator_disable(mydev->vcc);
    return 0;
}

static int mydev_runtime_resume(struct device *dev) {
    // 重新上电、恢复时钟
    regulator_enable(mydev->vcc);
    clk_enable(mydev->clk);
    return 0;
}

这里有个容易踩的坑:pm_runtime_get_sync和pm_runtime_put_sync必须成对出现。我见过一个同事,get了三次但只put了两次,结果设备永远挂不起来——引用计数没归零。嗯,这种bug查起来特别隐蔽。

1.4 电源管理中的「状态机」思维

做电源管理驱动,你脑子里要有一张状态图。每个设备无非就这几个状态:

  • ACTIVE:正常工作,该干啥干啥。
  • SUSPENDING:正在准备休眠,保存上下文。
  • SUSPENDED:已经休眠,功耗极低。
  • RESUMING:正在唤醒,恢复上下文。

驱动里要处理状态切换时的「边界情况」。比如,设备正在传输数据时,突然来了个suspend请求怎么办?正确的做法是:等当前传输完成,再进入suspend。强行打断会导致数据损坏。

警告:不要在suspend回调里调用可能引起阻塞的函数,比如msleep()。内核在suspend流程中持有锁,你这一睡,整个系统都卡住了。我早期犯过这个错,结果系统休眠要等30秒——用户差点把设备砸了。

1.5 小结:这一章你该带走什么?

好了,咱们捋一下今天的内容:

  • Linux电源管理分三层,驱动层是咱们的主战场。
  • ACPI和设备树是两种硬件描述方式,嵌入式开发重点掌握设备树。
  • DPM的核心思想是「按需供电」,Runtime PM是实现它的利器。
  • 状态机思维很重要,处理好suspend/resume的边界条件。

下一章,咱们会深入Runtime PM的源码实现,看看内核到底是怎么管理设备电源的。到时候我会带你看几个实际项目里的案例——那些让我熬夜查bug的案例。

先消化这些,有问题咱们群里聊。