AI 技术博客
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.cMakefile

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. 踩坑提醒

  1. suspend 漏关时钟/regulator:最常见的待机功耗 bug(L25)。suspend 里该关的电源/时钟都要关。
  2. resume 没恢复寄存器:suspend 时 IC 掉电丢了寄存器配置,resume 没重新写 → 唤醒后设备行为异常。
  3. runtime PM 没配对 get/put:用设备前 pm_runtime_get_sync,用完 pm_runtime_put,不配对 → 要么一直不下电(费电),要么用的时候是关的(崩)。
  4. wakeup source 没管好:该让系统睡的时候持着 wakelock 不放 → 系统睡不下去(L25 待机掉电)。
  5. remove 返回 int:6.11+ platform remove 要 void(同 L15)。

9. 常见问题分析与定位(电源管理实战)

9.1 常见问题清单

  1. 待机掉电快(suspend 没省到电)
  2. 唤醒后设备异常(resume 没恢复对)
  3. 系统睡不下去(suspend 失败)
  4. runtime PM 不下电(一直 active)
  5. 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. 小结

  1. 省电是 BSP 硬指标,驱动负责自己外设的关/恢复。
  2. 两类:system suspend/resume(整机息屏)+ runtime PM(单设备空闲自动)。
  3. dev_pm_ops 挂回调,SET_*_PM_OPS 宏;runtime PM 用 autosuspend 自动省电。
  4. suspend 漏关时钟/regulator = 待机功耗高(L25 最常见根因)。
  5. 排查口诀:待机掉电查 regulator_summary 谁还开;睡不下去查 wakeup_sources;runtime 不下电查 get/put 配对。

下节预告

L19:Android 内核特性 —— binder 驱动、dma-buf、lmkd,Android 区别于纯 Linux 内核的部分(呼应 L1)。

评论