6.3 电源管理:休眠、唤醒与运行时电源


6.3 电源管理:休眠、唤醒与运行时电源

本节摘要:电源管理分两层——系统级休眠唤醒的回调契约,与设备级的运行时自动省电。本节讲清休眠链的执行顺序、回调的对称写法、唤醒源的注册,以及运行时电源的引用计数机制,并复盘一次"唤醒后设备失联"的典型故障。

电池供电的设备每天都要经历几十次休眠唤醒:合盖即睡、开盖即醒,用户期待这一切在三秒内无缝完成。驱动的责任是在睡与醒的两个瞬间兑现契约——睡眠前把设备带到安全状态、唤醒后把它原样带回工作状态。看似简单的两件事,藏着一整套状态机与顺序协议。

系统休眠:一条严格排序的链

用户按下休眠,内核做的事可以概括成四幕:冻结进程与任务;按设备树依赖关系从叶子到根逐台调用驱动的挂起回调;把系统内存留在供电、其余全部断电;醒来时逆序调用恢复回调,最后解冻进程。

顺序是理解一切的钥匙。挂起必须从叶子设备开始:网卡依赖总线桥、总线桥依赖时钟控制器——先把末端设备挂起,再关上游供电,不会出现"上游已断电、下游还在发访问"的惨剧。恢复严格逆序:先唤醒上游供上电,末端设备才能正常初始化。这套顺序由电源管理核心按设备依赖图自动编排,驱动只管写好自己的回调

#include <linux/pm.h> static int led_suspend(struct device *dev) { /* 入睡前:停止闪烁定时器,熄灯,寄存器状态已由硬件自保持 */ blink_stop(); iowrite32(0, led_gpio_base); return 0; } static int led_resume(struct device *dev) { /* 醒来后:硬件已断过电的寄存器要全部重写 */ led_hw_reinit(); /* 重映射检查、方向寄存器重配 */ if (user_wants_blink) blink_start(); return 0; } static const struct dev_pm_ops led_pm_ops = { .suspend = led_suspend, .resume = led_resume, .freeze = led_suspend, /* 休眠到内存与休眠到盘共用逻辑 */ .restore = led_resume, RUNTIME_PM_OPS(led_runtime_suspend, led_runtime_resume, NULL) }; /* 挂进平台驱动:.driver.pm = &led_pm_ops */

回调命名里藏着历史包袱:不同休眠模式对应不同回调组,多数驱动用同一套逻辑填满它们即可。要点是对称——挂起停了什么,恢复就重启什么;挂起前硬件状态是否丢失,决定恢复要不要全量重配。

⚠️ 常见坑:唤醒后设备失联。挂起回调里只停了软件没保存硬件上下文,而这块设备在休眠中真断了电——醒来寄存器一片空白,驱动却以为一切如旧。判断依据是硬件手册的"电源域"说明:跨电源域的寄存器,恢复路径必须全量重写。7.1 节的排错实录里有这个案例的完整破案过程。

电源状态机与回调触发点

电源状态机与回调触发点

运行时电源:空闲即省电

系统级休眠是"整机大睡",运行时电源管理是"设备随时小憩":一台传感器三十秒没人访问,框架自动让它进入低功耗态;下一次访问到来前,再把它唤醒。驱动侧的三件事:

/* 一:probe 末尾启用运行时电源并置初始空闲 */ pm_runtime_enable(dev); pm_runtime_mark_last_busy(dev); /* 二:访问设备前后管理使用计数 */ pm_runtime_get_sync(dev); /* 计数加一并确保设备在线 */ val = ioread32(base); pm_runtime_put_autosuspend(dev); /* 计数减一,启动空闲计时 */ /* 三:实现挂起与恢复回调 */ static int led_runtime_suspend(struct device *dev) { clk_disable_unprepare(led_clk); /* 关时钟:省电大头 */ return 0; } static int led_runtime_resume(struct device *dev) { clk_prepare_enable(led_clk); led_hw_reinit(); /* 时钟断过的寄存器要重配 */ return 0; }

使用计数是核心机制:计数非零设备必须在线,归零后框架才开始考虑挂起。getput 的配对比锁的加解锁还要严格——漏一个 put,设备永远醒着白耗电;漏一个 get,访问砸在睡着的设备上读回一堆垃圾。

自动延迟的存在是为了防抖:频繁"挂起又唤醒"比稳定开着更费电也更伤延迟,所以默认等一段空闲再挂起。吞吐型设备(网卡、存储)还要配合"用满预算再挂"的节奏策略,这部分第 6.5 节性能视角再提。

唤醒源:睡着的哨兵

有一类设备特权加身:整机深睡时它必须保留侦测能力——电源按键、盖开关、网络的魔术包唤醒。这类设备要注册为唤醒源,电源管理框架为它保留最低限度供电。注册后还有一条纪律:作为唤醒源的设备,其驱动挂起回调里不能关闭唤醒事件的侦测路径,否则哨兵自己先聋了。

实战清单:给驱动做一次"电源审计"

把本节内容落成一张可执行的审计清单,新驱动合入前逐条过:

  • 回调对称:挂起停掉的每个东西(定时器、队列、中断使能),恢复里是否逐项重启?
  • 上下文重配:对照硬件手册的电源域表,跨域寄存器恢复路径是否全量重写?
  • 唤醒源声明:设备是否需要深睡侦测?挂起回调是否保留了侦测路径?
  • 运行时计数配平:代码走查每一对获取与释放,错误路径是否也配平?
  • 依赖顺序:设备是否依赖别台设备(时钟、总线)?声明是否让框架能排出正确顺序?

审计中最高频的发现是第三条——大量驱动作者从没读过电源域表,全凭"测试没发现问题"支撑假设。7.1 节案例二的教训就是这条的代价:偶发失灵的根因早在设计阶段就能从手册里读出来。

常见追问

追问一:驱动不做电源回调,设备休眠时会发生什么? 框架按"没意见"处理:设备被留在上电状态进入系统级挂起,时钟停摆、电源域按序下电,醒来后寄存器回到复位值——而驱动还以为状态一切照旧,于是按键无响应、配置凭空消失。也就是说,不写回调不是"不参与",而是"缺席审判":硬件照样被断电,只是没人替它善后。这也是"休眠后设备失灵"类故障的第一嫌疑:查驱动有没有实现挂起恢复对,比查硬件问题便宜得多。

追问二:运行时电源计数每收发包都要获取释放一次,开销可忽略吗? 单次开销确实是纳秒级的原子操作,但高频路径上还有第二笔账:频繁的获取释放会让空闲检测永远不成立,设备反而不进低功耗态;反过来若计数器设计得过于激进,又会在活跃与挂起间反复横跳——每次横跳都是一次真实的硬件状态切换,微秒级的延时毛刺就这么来的。工程上常用的折中是延迟挂起:计数归零后先等一个宽限期再真正下电,把"省电"与"稳定"摆在同一个旋钮上调。

本节要点回顾

  • 休眠链按依赖排序:挂起从叶到根、恢复逆序,驱动只写回调不排顺序。
  • 对称与全量重配:断过电的寄存器醒来必须重写,别赌硬件状态还在。
  • 运行时电源靠计数:get 与 put 严格配对,空闲自动小憩。
  • 唤醒源保留哨兵:深睡仍要能叫醒系统的设备别关侦测路径。

电源管好了,驱动还剩两门修养课:面对恶意输入的安全设计,以及让设备跑得更快的性能优化——下一节先讲安全。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U