Android全栈10 分钟阅读5967 字
【全栈第18课】电源管理 runtime PM + suspend/resume
Android 全栈工程师进阶教程 第18课。电源管理 runtime PM + suspend/resume。基于公开 AOSP 知识整理,含原理、可编译示例、常见问题定位。
> 本文是《Android 全栈工程师进阶教程》系列第 18 课。完整 26 课见 GitHub 仓库。
【全栈第18课】电源管理 runtime PM + suspend/resume
0. 这节课你将做出什么
给驱动加电源管理:系统息屏深睡时关外设、唤醒时恢复(suspend/resume);运行时空闲自动下电、用时自动上电(runtime PM)。 学完你能回答:息屏后驱动怎么省电?为什么有些手机待机一晚掉很多电(往往是驱动 suspend 漏关了外设)?runtime PM 和 system suspend 有什么区别?
1. 背景:省电是 BSP 的硬指标
手机续航是核心体验。每个外设(触控、传感器、各种 IC)在不用时都该关掉省电。这是驱动的责任——驱动最懂自己的硬件该怎么关、怎么恢复。
电源管理分两个层次:
- system suspend/resume:整机息屏深睡/唤醒,所有设备一起睡/醒。
- runtime PM:单个设备运行时,空闲就自动下电,要用了自动上电(更细粒度)。
驱动漏关一个时钟/regulator,可能就导致待机一晚掉电几个百分点——这是 L25 功耗实战的常见根因。
2. 全局图:两类电源管理
┌─ system suspend(整机息屏深睡)
驱动的 dev_pm_ops ─────┤ suspend(): 关时钟/下电/保存寄存器
│ resume(): 上电/恢复时钟/恢复寄存器
│
└─ runtime PM(单设备运行时空闲)
runtime_suspend(): 空闲自动下电
runtime_resume(): 要用自动上电
| 类型 | 触发时机 | 粒度 |
|---|---|---|
| system suspend | 整机息屏深睡(echo mem > /sys/power/state) | 全设备一起 |
| runtime PM | 单设备空闲一段时间 | 单设备,自动 |
3. 真实印证
真实驱动如触控:
- system suspend(息屏)→ suspend 回调关触控 IC 省电
- system resume(亮屏)→ resume 回调重新初始化触控 IC
- 漏关 → 待机功耗高(L25 查 regulator_summary 能看到哪个外设还开着)
4. 前置准备
- 依赖 L15(platform driver)。
- 真编译:本机 Linux 6.17(已验证)。
- 文件:
hello_pm.c、Makefile。
5. 动手:给驱动加电源管理
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/of.h>
#include <linux/mod_devicetable.h>
struct hello_pm_data { struct device *dev; bool powered; };
// 真实驱动里 pon/poff 会操作 regulator/clock,这里用日志示意
static void pon(struct hello_pm_data *d) { d->powered = true; dev_info(d->dev, "power ON\n"); }
static void poff(struct hello_pm_data *d) { d->powered = false; dev_info(d->dev, "power OFF\n"); }
// ---- system suspend/resume(整机息屏深睡)----
static int hello_suspend(struct device *dev) {
dev_info(dev, "system suspend: 整机要睡,关外设\n");
poff(dev_get_drvdata(dev)); // 关时钟/下电/保存状态
return 0;
}
static int hello_resume(struct device *dev) {
dev_info(dev, "system resume: 整机醒,恢复外设\n");
pon(dev_get_drvdata(dev)); // 上电/恢复
return 0;
}
// ---- runtime PM(运行时空闲省电)----
static int hello_rt_suspend(struct device *dev) { poff(dev_get_drvdata(dev)); return 0; }
static int hello_rt_resume(struct device *dev) { pon(dev_get_drvdata(dev)); return 0; }
// dev_pm_ops:把回调挂上(用 SET_*_PM_OPS 宏,自动处理编译条件)
static const struct dev_pm_ops hello_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(hello_suspend, hello_resume) // 整机睡/醒
SET_RUNTIME_PM_OPS(hello_rt_suspend, hello_rt_resume, NULL) // 运行时省电
};
static int hello_probe(struct platform_device *pdev) {
struct hello_pm_data *d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
if (!d) return -ENOMEM;
d->dev = &pdev->dev;
platform_set_drvdata(pdev, d);
pon(d); // 上电
// 启用 runtime PM:空闲 2 秒后自动 runtime_suspend
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 2000);
pm_runtime_use_autosuspend(&pdev->dev);
dev_info(&pdev->dev, "probe ok, runtime PM enabled\n");
return 0;
}
static void hello_remove(struct platform_device *pdev) { // ★6.11+ void
pm_runtime_disable(&pdev->dev);
}
static const struct of_device_id m[] = { { .compatible = "xiaomi,hello-pm" }, { } };
MODULE_DEVICE_TABLE(of, m);
static struct platform_driver drv = {
.probe = hello_probe, .remove = hello_remove,
.driver = { .name = "hello-pm", .of_match_table = m, .pm = &hello_pm_ops }, // 挂上 pm_ops
};
module_platform_driver(drv);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Lesson18 runtime PM suspend resume");
逐块讲为什么:
.pm = &hello_pm_ops:把电源回调挂到 driver,内核 PM 框架在合适时机调你的 suspend/resume。SET_SYSTEM_SLEEP_PM_OPS宏:自动处理"只在 CONFIG_PM_SLEEP 开启时挂",省得你写一堆#ifdef。- runtime PM 的 autosuspend:
pm_runtime_enable+autosuspend_delay(2000)= 设备空闲 2 秒后,内核自动调runtime_suspend下电。要用时(pm_runtime_get_sync)自动runtime_resume上电。这样不用驱动自己计时。 - suspend 里做什么(真实):关 IC 电源(regulator_disable)、关时钟(clk_disable)、保存寄存器状态、disable_irq。resume 逆操作。漏关 = 待机功耗高。
6. 看底层:整机 suspend 时发生什么
用户息屏 → 系统决定深睡 → echo mem > /sys/power/state(或自动)
→ 内核 PM 框架遍历所有设备,从叶子到根依次调它们的 .suspend
→ 调到你的 hello_suspend → 你关 IC 省电
→ 所有设备 suspend 完 → CPU 进低功耗状态
唤醒(按键/来电)→ 逆序调 .resume → 你的 hello_resume 恢复 IC
7. 编译 & 运行(✅ 真编译过)
make # 真编译出 hello_pm.ko
# 触发 system suspend 看效果:
adb shell "echo mem > /sys/power/state" # 模拟深睡(需 root)
adb shell dmesg | grep -i "power\|hello" # 看 suspend/resume 时的 power OFF/ON
# runtime PM:空闲 2s 自动 runtime_suspend
adb shell cat /sys/devices/.../hello-pm/power/runtime_status # active/suspended
真编译结果(本机 6.17):
CC [M] hello_pm.o
LD [M] hello_pm.ko ← void remove,编译通过
> 验证状态(诚实):本机 6.17 真编译通过(remove void)。suspend/resume 真触发需设备/root。
8. 踩坑提醒
- suspend 漏关时钟/regulator:最常见的待机功耗 bug(L25)。suspend 里该关的电源/时钟都要关。
- resume 没恢复寄存器:suspend 时 IC 掉电丢了寄存器配置,resume 没重新写 → 唤醒后设备行为异常。
- runtime PM 没配对 get/put:用设备前
pm_runtime_get_sync,用完pm_runtime_put,不配对 → 要么一直不下电(费电),要么用的时候是关的(崩)。 - wakeup source 没管好:该让系统睡的时候持着 wakelock 不放 → 系统睡不下去(L25 待机掉电)。
- remove 返回 int:6.11+ platform remove 要 void(同 L15)。
9. 常见问题分析与定位(电源管理实战)
9.1 常见问题清单
- 待机掉电快(suspend 没省到电)
- 唤醒后设备异常(resume 没恢复对)
- 系统睡不下去(suspend 失败)
- runtime PM 不下电(一直 active)
- resume 慢导致唤醒卡顿
9.2 分析与定位
① 待机掉电快
- 原因:某驱动 suspend 没关 regulator/clock,外设一直耗电。
- 定位:
adb shell "echo mem > /sys/power/state" # 进 suspend
# 看哪些 regulator/clock 在 suspend 后还开着(本该关的):
adb shell cat /d/regulator/regulator_summary
adb shell cat /d/clk/clk_summary
adb shell cat /sys/power/suspend_stats # suspend 成功了吗
- regulator_summary 里待机还 enable 的外设 = 漏电源,找对应驱动 suspend 补关。
② 唤醒后设备异常
- 原因:resume 没重新初始化掉电丢失的寄存器。
- 定位:resume 后读 IC 寄存器看是不是默认值(说明没恢复);对比初始化序列。
③ 系统睡不下去
- 原因:某个 wakeup source 一直 active,或某驱动 suspend 返回错误阻止了 suspend。
- 定位:
adb shell cat /d/wakeup_sources # 谁活跃(active_count/active_since)
adb shell dmesg | grep -iE "suspend|PM:.*fail" # 哪个设备 suspend 失败
④ runtime PM 不下电
- 原因:get/put 不配对,引用计数一直 >0;或 autosuspend 没配。
- 定位:
cat .../power/runtime_status(一直 active);cat .../power/runtime_usage(引用计数)。
⑤ resume 慢
- 原因:resume 里做了耗时操作(重新加载固件、长延时)。
- 定位:
dmesg看 PM 各设备 resume 耗时(initcall/PM trace);优化慢的。
9.3 定位决策树
电源管理出问题
├─ 待机掉电快 → suspend 后 regulator_summary/clk_summary 看谁还开 → 驱动 suspend 补关
├─ 唤醒异常 → resume 后读 IC 寄存器是否默认值 → resume 补恢复
├─ 睡不下去 → /d/wakeup_sources 谁活跃 + dmesg suspend fail → 释放 wakelock/修 suspend
├─ runtime 不下电 → runtime_status/usage → get/put 配对+autosuspend
└─ resume 慢 → dmesg PM resume 耗时 → 优化慢操作
10. 小结
- 省电是 BSP 硬指标,驱动负责自己外设的关/恢复。
- 两类:system suspend/resume(整机息屏)+ runtime PM(单设备空闲自动)。
- dev_pm_ops 挂回调,SET_*_PM_OPS 宏;runtime PM 用 autosuspend 自动省电。
- suspend 漏关时钟/regulator = 待机功耗高(L25 最常见根因)。
- 排查口诀:待机掉电查 regulator_summary 谁还开;睡不下去查 wakeup_sources;runtime 不下电查 get/put 配对。
下节预告
L19:Android 内核特性 —— binder 驱动、dma-buf、lmkd,Android 区别于纯 Linux 内核的部分(呼应 L1)。