Linux 内核功耗子系统(一):从 PM Core 看功耗框架的分层设计
Linux 内核没有把功耗管理实现成一个单独的 power manager。它更像一组围绕 struct device、CPU、时钟、电源资源和固件接口展开的框架集合:PM Core 负责设备电源状态的统一入口,runtime PM 处理设备运行期状态,generic PM domain 把一组设备放进同一个 PM domain,OPP 描述性能点,regulator/clock/reset 连接到具体硬件资源,CPUIdle 和 CPUFreq 分别处理 CPU 空闲态和运行频率。
读这组源码之前,先看全景图。它把功耗子系统拆成几条主线:device model 里的 PM Core,设备运行期状态机 runtime PM,整机睡眠路径 system sleep,domain 聚合层 genpd,资源 provider 框架 clock/regulator/reset,性能点框架 OPP/devfreq,CPU 侧 CPUIdle/CPUFreq,约束输入 thermal、PM QoS、wakeup source,以及向下连接 PSCI/SCMI/platform ops 的固件边界。

这张图的重点不是把所有模块画在一张大表里,而是标出它们在内核里的职责边界。PM Core 组织设备电源状态;runtime PM 和 system sleep 是两套状态路径;genpd 聚合多个 device 的 PM domain 状态;clock、regulator、reset 负责硬件资源引用;OPP/devfreq 描述性能点;CPUIdle/CPUFreq 处理 CPU 侧 idle state 和 performance state;thermal、PM QoS、wakeup source 作为约束输入影响状态选择。平台相关动作则继续通过 PSCI、SCMI 或 provider ops 进入固件和硬件。
后面按这个全景图逐个拆开。
1. PM Core:设备电源状态的公共入口

设备功耗管理的核心入口在 driver model。struct device 里挂着 dev_pm_info power,它记录 runtime PM 状态、系统睡眠状态、回调执行状态、usage counter、autosuspend 参数、wakeup 信息等。驱动不需要各自定义一套电源状态机,而是把 suspend/resume、runtime_suspend/runtime_resume 等回调注册到统一的 PM 框架里。
PM Core 的价值在于统一调度,而不是替代具体驱动。系统睡眠时,它沿着 device hierarchy 组织 prepare、suspend、suspend_late、suspend_noirq、resume_noirq、resume_early、resume、complete 等阶段;运行期省电时,它通过 runtime PM 状态机调用 driver、bus、class、type、PM domain 的回调。不同层级都可以参与,但入口和顺序由 PM Core 维护。
这里有一个重要设计:PM 回调不是只属于 device driver。bus type、device type、class、PM domain 都可能提供回调。PM Core 会按优先级选择和组合这些回调,让通用层处理共性逻辑,让具体 driver 只负责设备私有状态。
2. system sleep:整机睡眠路径也落在 dev_pm_info 上

system sleep 处理的是整机 suspend/resume,但它并没有脱离 device model 另建一套对象。PM Core 在进入 suspend 时会遍历系统里的 struct device,按 device hierarchy 和 PM 回调阶段组织 prepare、suspend、suspend_late、suspend_noirq;resume 时再按相反方向执行 resume_noirq、resume_early、resume、complete。
这里和 dev_pm_info 的关系很直接。struct device 中的 power 字段就是 struct dev_pm_info,它不只服务 runtime PM,也保存 system sleep 相关状态:设备是否已经 prepare、是否处在 suspend 流程中、是否允许 direct complete、是否有 wakeup 能力、回调执行时的错误状态,以及和 async suspend 相关的控制信息。
runtime PM 和 system sleep 共用同一个 dev_pm_info,但语义不同。runtime PM 处理设备运行期 active/suspended 状态,system sleep 处理整机睡眠阶段里的设备回调顺序。系统进入 suspend 时,PM Core 还会考虑 runtime PM 的当前状态:已经 runtime suspended 的设备可能走 direct complete,避免重复执行 suspend/resume;仍处于 active 的设备则继续走系统睡眠回调。
所以 dev_pm_info 可以看成 PM Core 挂在每个 device 上的状态账本。runtime PM 在里面维护运行期 usage counter 和 runtime status;system sleep 在里面维护整机睡眠阶段状态、wakeup 属性和回调控制信息。两个路径共享 device model,却通过不同字段和回调阶段表达不同层次的电源状态。
3. 固件边界:Linux 表达请求,平台完成动作

ARM SoC 上的功耗控制经常跨过 Linux。CPU idle state 可能通过 PSCI 进入 EL3;性能点切换可能通过 SCMI performance protocol 交给 SCP;某些 PM domain 的 power on/off 可能最终落到平台电源控制器、PMIC 或固件服务。Linux 侧的框架负责表达状态、约束和请求,硬件动作未必全部出现在内核源码中。
这也是 Linux 功耗框架和单片机式电源控制代码最大的不同。它不把“关电源”写成一个固定函数,而是把设备状态、资源引用、domain 层级、性能点、唤醒能力和固件接口拆成可组合的对象。对象之间的边界清楚,平台才能在不改 PM Core 的情况下接入不同硬件。
4. runtime PM:设备运行期状态机

system sleep 处理整机 suspend/resume。runtime PM 处理系统运行时单个设备的空闲和恢复。这个拆分很关键:设备不需要等整机 suspend 才能省电,display、ISP、VPU、USB、I2C controller、GPU 等设备都可以在没有请求时进入低功耗状态。
runtime PM 的状态机围绕 struct dev_pm_info 展开。常见状态包括 active、suspending、suspended、resuming;usage counter 表示当前是否还有使用者;autosuspend 允许设备在引用释放之后延迟进入 suspend,避免频繁开关。驱动通过 pm_runtime_get*() 和 pm_runtime_put*() 表达使用关系,PM Core 再决定是否调用 runtime_suspend 或 runtime_resume。
这个设计把“设备是否有人使用”和“设备如何进入低功耗”分开了。前者由 runtime PM core 维护计数和状态,后者由 driver、bus 或 PM domain 回调完成。driver 可以专注保存寄存器、停止 DMA、处理设备私有 clock;通用层负责并发、引用计数、父子设备关系和回调顺序。
5. generic PM domain:一组设备的电源边界

很多 SoC 的设备并不是一个 device 对应一个独立电源开关。一组 IP 可能共享一个 PM domain,domain 本身还可能有父子层级。generic PM domain,也就是 genpd,就是内核为这种结构提供的通用抽象。
在源码里,genpd 的核心对象是 struct generic_pm_domain。它保存 domain 状态、provider 回调、domain 内设备列表、子 domain、性能状态、power on/off 延迟、governor 等信息。设备通过 device tree 的 power-domains 关联到 provider,probe 后被挂入对应 PM domain。
PM domain 和 runtime PM 的关系不是替代关系。runtime PM 关心单个 device 的运行期状态,genpd 关心 domain 级状态是否可以变化。当某个 device runtime_suspend 后,genpd 还要看同一个 domain 内其他 device、子 domain、wakeup 配置、governor 策略和 provider 能力。满足条件后,才会调用 provider 的 power_off 回调。
6. OPP / regulator:性能点和供电约束

OPP 和 regulator 是另一组问题。OPP table 描述 frequency/voltage pairs,regulator framework 管理 voltage/current regulator 及 consumer constraints。它们会和 PM domain 协作,但不是 PM domain 的同义词。PM domain 管的是设备电源状态边界,OPP/regulator 管的是性能点和供电约束。
OPP 的核心不是“调频”本身,而是把 device 可用的性能点组织成表:频率、微伏、电流、availability、supported hardware、required OPP 等信息都可以挂在 OPP entry 上。cpufreq、devfreq 或具体 device driver 可以通过 OPP framework 查询可用性能点,再交给底层 driver 或固件接口执行。
regulator framework 则把供电资源抽象成 provider/consumer。provider 注册 struct regulator_dev,consumer 通过 supply 名称获取 regulator,再声明 enable、disable、电压范围、电流需求等约束。core 层负责合并 consumer constraints、处理 supply 级联、维护 enable 状态,并调用 regulator ops 落到平台实现。
7. clock / reset:资源 provider 的共性结构

clock、regulator、reset、PM domain、interconnect 这些框架的形态很像:底层平台 driver 注册 provider,上层设备 driver 作为 consumer 获取资源句柄,中间 core 维护通用状态和引用关系。
clock framework 里的 struct clk_core 维护 parent、rate、prepare_count、enable_count、notifier 等状态;reset framework 维护 reset controller 和 reset line;regulator framework 维护 constraints、supply 级联、enable 状态和电压电流选择;genpd 维护 domain 层级和 provider 回调。每个框架都把平台差异压到 provider ops 里,把 consumer API 保持成相对稳定的内核接口。
这种 provider/consumer 结构有两个结果。向上,设备驱动不用直接理解某颗 SoC 的 PMIC 寄存器、clock gate 位或 power controller 协议;向下,平台 driver 可以把硬件细节封装成标准 ops。中间的 core 则负责引用计数、状态缓存、拓扑关系、回调顺序和约束合并。
8. CPUIdle:CPU idle state 的选择框架

CPU 侧也没有用一个统一框架处理所有功耗问题。CPU 没有 runnable task 时,进入 CPUIdle。CPUIdle 的核心对象包括 struct cpuidle_driver、struct cpuidle_device、struct cpuidle_state 和 governor。
driver 描述平台支持的 idle states,包括 exit latency、target residency、flags 和 enter 回调;device 保存每个 CPU 的状态统计;governor 根据下一次 timer event、latency constraint 和历史信息选择目标 idle state。进入深层 idle state 时,ARM 平台通常会通过 PSCI 把控制权交给固件。
CPUIdle 的问题域是 idle loop 之后能进入哪个 idle state。它和 scheduler、tick、PM QoS、interrupt、firmware 都有关系,但它本身不处理 runnable workload 下的频率选择。
9. CPUFreq:CPU performance state 的策略框架

CPU 运行任务时,根据负载和策略调整频率,进入 CPUFreq。CPUFreq 的核心对象是 struct cpufreq_policy、governor、driver 和 frequency table。policy 表示一组共享频率控制的 CPU,governor 根据利用率或策略给出目标频率,driver 负责把目标频率映射到硬件动作。
现代 ARM 平台上,这个硬件动作可能是 clock/regulator,也可能是 SCMI performance level。schedutil governor 又会把 scheduler utilization 转换为频率请求,使 CPUFreq 和调度器、thermal、PM QoS 之间形成约束关系。
CPUIdle 和 CPUFreq 经常在同一套功耗目标下协作,但源码分层很明确。CPUIdle 的输入来自 idle loop 和 latency constraint;CPUFreq 的输入来自 workload、policy、governor 和平台 driver。两者都可能进入固件接口,却不应该被合并成一个 CPU power framework 来理解。
10. wakeup source:系统睡眠路径里的约束对象

系统睡眠不只是把设备按顺序 suspend。内核还需要知道哪些事件允许把系统唤醒,哪些设备在 suspend 期间保留 wakeup 能力。wakeup source 就是这个约束在 device model 里的表达。
struct wakeup_source 记录一个唤醒源的状态、计数和时间信息,struct device 可以通过 power/wakeup 相关字段关联 wakeup source。系统进入 suspend 前,PM Core 会处理 wakeup 约束;设备 suspend 回调也会根据 wakeup enable 状态决定保留哪部分硬件能力。
从架构上看,wakeup source 把“设备能否被关闭”和“设备是否需要保留唤醒能力”拆开了。同一个设备,在普通运行期可以 runtime suspend;在系统睡眠期间,如果它是允许唤醒系统的设备,就可能保留部分中断、低功耗检测逻辑或固件侧唤醒路径。这个差异如果放进单个 driver 私有逻辑里,会很难被系统睡眠路径统一处理。
11. thermal / PM QoS:功耗决策的约束输入

thermal 和 PM QoS 不直接替代 runtime PM、genpd、CPUIdle 或 CPUFreq。它们更像约束输入:thermal 会限制可用性能点、触发 cooling device,影响 cpufreq/devfreq 的上限;PM QoS 会把 latency、frequency、flags 等需求传给相关框架,影响 idle state 或性能策略选择。
这类约束之所以单独成框架,是因为它们经常来自不同对象。一个 device、一个 CPU、一个用户态请求、一个 thermal zone 都可能影响最终状态选择。把约束从具体 driver 中抽出来,PM Core、CPUIdle、CPUFreq、devfreq、thermal 才能在统一规则下组合。
12. 从源码目录看功耗框架的边界

Linux 功耗相关源码分散在多个目录,这本身也反映了架构边界。
PM Core 和 runtime PM 主要在 drivers/base/power/,它们依附于 driver model;generic PM domain 在 drivers/base/power/domain.c,通过 provider/consumer 方式接入设备;OPP 在 drivers/opp/,为 cpufreq、devfreq 和设备 driver 提供性能点抽象;regulator 在 drivers/regulator/,clock 在 drivers/clk/,reset 在 drivers/reset/,它们都是资源 provider 框架;CPUIdle 在 drivers/cpuidle/,CPUFreq 在 drivers/cpufreq/;thermal 在 drivers/thermal/;PM QoS 在 kernel/power/qos.c、drivers/base/power/qos.c 等位置。
这些目录不是随意分散。它们对应不同抽象层:driver model 处理设备电源状态,资源框架处理硬件资源引用,性能框架处理 frequency/voltage/performance state,CPU 框架处理处理器空闲和运行频率,thermal 和 PM QoS 作为约束输入影响决策。内核没有把它们合并,是因为这些抽象的生命周期、锁、拓扑和调用上下文并不相同。
13. 回到 ARM SoC 的功耗链路

站在 ARM SoC 视角,Linux 功耗子系统可以看成三层协作。
上层是内核通用框架:PM Core、runtime PM、genpd、OPP、CPUIdle、CPUFreq、thermal、PM QoS。它们维护设备状态、domain 状态、性能点、idle state、policy 和约束。
中间是平台和固件接口:PSCI、SCMI、platform PM ops、clock provider、regulator provider、power controller provider。它们把通用框架的请求转换成平台可执行的操作。
底层是硬件资源:clock tree、regulator、power controller、PMIC、CPU cluster、interconnect、DDR、外设 IP。硬件资源有自己的时序、依赖和状态保持要求,Linux 通过 provider ops 间接控制它们。
这个分层解释了为什么功耗源码看起来分散,却能在运行时形成一条完整路径。设备 driver 只表达使用关系;PM Core 组织状态转换;genpd 判断 domain 边界;clock/regulator/reset/OPP 处理资源和性能点;CPUIdle/CPUFreq 处理 CPU 侧状态;PSCI/SCMI 和平台 provider 把请求继续交给固件或硬件。
后面继续展开设备资源底座。clock、regulator、reset、genpd 和 OPP 不是 PM Core 的附属细节,它们是 Linux 把功耗状态落到 SoC 硬件上的基础抽象。