全景介绍
前面十三篇里,我们已经把功耗子系统的各个模块拆开了:CCF 管时钟、regulator 管供电、reset 管复位、genpd 管电源域、OPP 管性能点、runtime PM 管设备运行期状态、devfreq 管设备 DVFS、CPUFreq/CPUIdle 管 CPU 的频率和空闲、PM QoS 管约束、thermal 管温度、DPM 管设备遍历顺序。
这些模块解决的都是"系统还活着的时候怎么省电"。
system sleep 面对的是另一个层次的问题:当用户合上盖子、按下电源键、或者系统空闲超时后,能不能把整台机器推进一个功耗极低的全局低功耗状态,然后在用户重新操作时恢复回来。
ACPI S 状态与 Linux 的对应
| | | | |
|---|
| | | | |
| | | | |
| | | 自刷新 | echo mem |
| | | 断电 | echo disk |
| | | | poweroff |
S3 和 S4 的关键区别:S3 的 RAM 进入 self-refresh(几 mA),S4 的 RAM 写入磁盘后断电(功耗为零)。

DPM 框架:全局设备遍历的骨架
system sleep 的设备遍历由DPM(Device Power Management) 框架驱动。DPM 维护五条链表追踪设备当前所处的阶段:dpm_list →dpm_prepared_list →dpm_suspended_list →dpm_late_early_list →dpm_noirq_list。
实际情况
从源码角度看,system sleep 的实现中有两个关键设计决策直接影响最终行为。
第一,没搞清楚什么时候用哪个睡眠状态。/sys/power/mem_sleep 里[s2idle] 纯软件路径,恢复快但省电有限;deep 把 DDR 推进 self-refresh,省电好但恢复慢。
第二,没搞清楚设备 suspend 的多个阶段分别能做什么。PM Core 拆成五个阶段:prepare、suspend、suspend_late、suspend_noirq、poweroff。驱动实现中最容易犯的错是把 I2C/SPI 操作放在了 noirq 阶段。
第三个容易忽略的点是 wakeup 路径的保留。
S3 的内存状态:RAM 自刷新
S3 的核心机制:Linux 保存 CPU 寄存器到 RAM → 调用psci_cpu_suspend() → ATF 协调 DDR 进入 self-refresh → 关闭 CPU 时钟/PLL → 整机低功耗。唤醒时 ATF 从 resume 地址恢复。
S4 的磁盘格式:hibernation image
S4 把 RAM 内容写入磁盘。内核维护swsusp_header 元数据(镜像起始扇区、签名)。hibernation image 生成:freeze_processes() →create_image() → 按页写入磁盘 →poweroff()。恢复时 bootloader 加载内核,内核检测签名后从磁盘恢复内存页面。

抽象对象
pm_states
内核定义全局睡眠状态表。disk 走hibernate() 而非pm_suspend()。每个状态通过suspend_ops 区分:freeze 不调 ops;standby 部分平台调enter();mem 调enter();disk 走 hibernate。
dev_pm_ops(DPM 回调)
struct dev_pm_ops 分四组:suspend/resume(mem)、freeze/thaw(s2idle)、poweroff/restore(hibernation)、runtime。回调查找优先级:pm_domain > type > class > bus > driver。
wakeup_source
struct wakeup_source 定义在include/linux/pm_wakeup.h。工作机制:注册(设备树wakeup-source 或device_init_wakeup())→ 激活(pm_wakeup_event() 标记唤醒事件)→ 检查(suspend_check_wakeup_irqs() 中止 suspend)。/sys/kernel/debug/wakeup_sources 显示统计信息。
suspend_ops
平台级休眠接口。ARM 上enter() 调用psci_cpu_suspend(),ATF/SCP 协调 power domain、clock、PMIC、DDR self-refresh。
freezer
freeze_processes() 通过TIF_FREEZE 标志让进程进 refrigerator。冻结失败是 suspend 中止的常见原因。

模型
核心三原则:多阶段、全局顺序、唤醒优先。
Suspend 阶段链:dpm_suspend_start(prepare) →dpm_suspend →dpm_suspend_late →dpm_suspend_noirq →syscore_suspend → CPU 控制序列 →suspend_ops->enter()。
顺序保证:设备 suspend 从叶子到根,resume 从根到叶子。唤醒优先:wakeup 设备带dev->power.wakeup_path = true。三种路径:freeze 不调enter();mem 调enter();disk 走 hibernate。

数据流
echo mem > /sys/power/state
→ pm_suspend(state) → enter_state()
→ suspend_prepare() // sync FS + freeze processes
→ suspend_devices_and_enter()
→ dpm_suspend_start → dpm_suspend → suspend_late → noirq
→ syscore_suspend + disable_nonboot_cpus
→ suspend_ops->enter() // PSCI → ATF → DDR self-refresh
// --- wake ---
→ syscore_resume + enable_nonboot_cpus
→ dpm_resume_noirq → dpm_resume_early → dpm_resume → dpm_complete
→ suspend_finish()
运行
s2idle 不调enter(),设备走 freeze/thaw,靠 CPUIdle 省电。deep 走完整 suspend/resume,enter() 调 PSCI/ATF/SCP,DDR 进 self-refresh。
案例
s2idle 待机电流偏高:wakeup_sources 显示 RTC 闹钟不停唤醒。关闭wakealarm 后降到 8mA。
noirq 阶段卡死:backlight 驱动在suspend_noirq 里试图通过 I2C 写 PWM 寄存器——I2C 控制器已在suspend_late 被 suspend。修复:把 I2C 操作移到suspend。


总结
两句话说清楚 runtime PM 和 system sleep:
- 1.runtime PM 是"设备层面"的——设备 idle 就 suspend,中断、调度器、FS 一切正常。
- 2.system sleep 是"系统层面"的——冻结用户态、全局依赖顺序 suspend 设备、下线 CPU、调平台固件进 DDR self-refresh。
system sleep 的复杂性在于它把十几条子系统链路串成一条全局状态迁移——任何一个节点的状态不一致,整条链路都会受影响。