2. Runtime PM 核心概念

Runtime PM(Runtime Power Management,运行时电源管理)是 Linux 内核提供的一套 设备级动态电源管理框架。它允许设备在系统整体运行期间,根据自身的使用状态独立、自动地进入或退出低功耗模式,而无需依赖系统级的挂起/唤醒操作。

2.1 Runtime PM 的定义与目标

定义:Runtime PM 是一种细粒度的、基于设备使用情况的电源管理机制。它通过跟踪每个设备的 活跃度(idle/busy),在设备空闲时自动将其置于低功耗状态(suspended),并在需要使用时快速恢复(resumed)。整个过程对用户空间和大部分内核子系统透明。

核心目标:

  • 降低静态功耗:在设备不工作时,切断或降低其时钟、电源域,减少漏电流和静态功耗。
  • 提升能效比:只在设备真正需要工作时消耗能量,避免“空转”浪费。
  • 延长电池寿命:对于移动和嵌入式设备,这是最直接的收益。
  • 保持系统响应性:挂起和恢复操作是异步、低延迟的,不影响系统整体运行。
💡 关键理解:Runtime PM 不是“省电模式”,而是“按需供电”。它让电源管理从“全有或全无”的系统级策略,进化到“设备级、动态、自动”的精细化管理。

2.2 与传统电源管理的区别

传统电源管理(如系统挂起 Suspend-to-RAM、Suspend-to-Disk)与 Runtime PM 在粒度、触发方式和影响范围上有本质区别。下表总结了关键差异:

对比维度 传统电源管理(System PM) Runtime PM
管理粒度 系统级(整个 SoC / 整机) 设备级(单个外设、IP 核)
触发方式 用户主动触发(按键、合盖、定时器)或内核策略 设备自身空闲/忙碌状态自动触发
影响范围 所有设备、CPU、内存均受影响 仅目标设备及其依赖的电源域
延迟要求 较高(秒级),可接受较长的恢复时间 极低(微秒~毫秒级),必须快速响应
对用户透明性 不透明,用户能感知到系统暂停/恢复 完全透明,用户无感知
典型场景 笔记本合盖、手机待机 I2C 总线空闲时关闭时钟、USB 设备自动挂起
📌 实践要点:在驱动开发中,不要将 Runtime PM 视为系统挂起的替代品。它们是互补的:Runtime PM 负责设备级的动态节能,系统挂起负责整体深度睡眠。一个设计良好的驱动应同时支持两者。

2.3 设备状态模型

Runtime PM 使用一个 三态有限状态机 来管理每个设备。理解这三个状态是编写 Runtime PM 驱动的基础。

  1. ACTIVE(活跃状态):设备正在工作或准备工作。电源已开启,时钟已使能,寄存器可正常访问。这是设备的默认初始状态。
  2. SUSPENDED(挂起状态):设备处于低功耗模式。电源可能被关闭,时钟被门控,寄存器上下文可能丢失。设备无法直接响应外部请求,需要先恢复。
  3. SUSPENDING(正在挂起):设备正在从 ACTIVE 向 SUSPENDED 过渡的中间状态。内核正在执行驱动的 runtime_suspend 回调函数。在此状态下,设备可能正在执行最后的清理工作(如保存寄存器、刷新 FIFO)。

下图展示了设备状态之间的转换关系:

Runtime PM 设备状态模型 ACTIVE (活跃 / 正常工作) SUSPENDING (正在挂起 / 过渡中) SUSPENDED (挂起 / 低功耗) pm_runtime_suspend() 回调成功 pm_runtime_get_sync() / pm_runtime_resume() 回调失败 / 被中断 💡 状态转换由内核 PM 核心自动管理,驱动只需实现回调函数。 驱动通过 pm_runtime_get/put 系列 API 触发状态变迁。

2.4 状态转换的触发机制

设备状态的转换并非随意发生,而是由内核的 Runtime PM 核心通过 引用计数 机制自动管理。每个设备维护一个 usage_count:

  • usage_count > 0:设备被“引用”,必须保持 ACTIVE 状态。
  • usage_count == 0:设备空闲,允许进入 SUSPENDED 状态。

驱动通过以下 API 操作引用计数:

// 增加引用计数,请求设备保持活跃
int pm_runtime_get_sync(struct device *dev);
int pm_runtime_get(struct device *dev);  // 异步版本

// 减少引用计数,释放设备,允许挂起
int pm_runtime_put_sync(struct device *dev);
int pm_runtime_put(struct device *dev);  // 异步版本

// 强制设置设备状态
int pm_runtime_set_active(struct device *dev);
int pm_runtime_set_suspended(struct device *dev);
⚠️ 常见陷阱:引用计数必须成对使用。如果 pm_runtime_get 和 pm_runtime_put 不匹配,设备将永远无法进入 SUSPENDED 状态,导致功耗异常。建议在驱动的 open/close、read/write 等对称操作中配对调用。

2.5 回调函数与状态机交互

驱动需要实现三个核心回调函数,内核在状态转换时自动调用它们:

回调函数 触发时机 驱动职责
runtime_suspend 设备从 ACTIVE → SUSPENDING 时 保存必要上下文,关闭时钟/电源
runtime_resume 设备从 SUSPENDED → ACTIVE 时 恢复上下文,重新使能时钟/电源
runtime_idle 设备引用计数降为 0 时 判断是否真的可以挂起,返回 0 则触发 suspend

下图展示了回调函数在状态机中的位置:

回调函数与状态机交互流程 驱动实现 • runtime_suspend() • runtime_resume() • runtime_idle() (注册到 dev_pm_ops) 内核 PM 核心 引用计数管理 状态机调度 超时自动挂起 (pm_runtime_* API) 硬件设备 时钟门控 电源域开关 寄存器保存/恢复 (由回调操作) 注册回调 操作硬件 状态反馈
🔧 驱动开发要点:
  • runtime_suspend 和 runtime_resume 必须成对实现,且保证幂等性(多次调用不产生副作用)。
  • runtime_idle 通常返回 0 让内核自动挂起,或返回 -EBUSY 阻止挂起(例如设备仍有未处理的数据)。
  • 回调函数中不能调用可能导致睡眠的函数(如 msleep),因为它们可能在原子上下文中被调用。

2.6 小结

Runtime PM 的核心思想可以概括为:

  • 设备自治:每个设备独立管理自己的电源状态。
  • 引用计数驱动:通过 get/put 机制自动触发状态转换。
  • 三态模型:ACTIVE ↔ SUSPENDING ↔ SUSPENDED,回调函数实现具体硬件操作。
  • 透明高效:对用户空间透明,延迟极低,适合嵌入式实时场景。