16. 电源管理移植:Linux电源管理框架(PM/QoS)、Android WakeLock机制、Suspend/Resume调试、功耗测量与优化
电源管理,说白了就是让设备该睡的时候睡,该醒的时候醒,别在不该干活的时候瞎忙活。我在做i.MX平台移植时,遇到过最头疼的问题不是驱动跑不起来,而是设备睡下去就醒不过来,或者醒过来发现功耗比睡着前还高。嗯,这章我们就来把这些坑一个个填上。
16.1 Linux电源管理框架:PM Core与QoS
Linux的电源管理框架,我习惯把它分成三层来看:
- PM Core:内核最底层的电源管理基础设施,负责suspend/resume流程的调度
- PM QoS:一个约束机制,告诉系统“我现在需要多快的响应,别给我睡太死”
- Driver PM:每个驱动自己实现的suspend/resume回调
先看PM Core。它的核心数据结构是struct dev_pm_ops,每个设备驱动都得实现它:
static const struct dev_pm_ops imx_uart_pm_ops = {
.suspend = imx_uart_suspend,
.resume = imx_uart_resume,
.freeze = imx_uart_freeze,
.thaw = imx_uart_thaw,
.poweroff = imx_uart_poweroff,
.restore = imx_uart_restore,
};
这里有个细节——freeze/thaw和suspend/resume有什么区别?我刚开始也搞混过。简单说:suspend/resume是标准休眠流程,freeze/thaw用于hibernate(休眠到磁盘)。在Android Automotive上,我们主要用suspend/resume。
核心要点:PM Core通过dpm_list维护所有设备的顺序,suspend时从后往前遍历,resume时从前往后遍历。这个顺序决定了设备间的依赖关系。
再来说PM QoS。这玩意儿很有意思——它允许驱动或用户空间程序告诉内核:“我现在需要CPU频率不低于xxx MHz”或者“内存延迟不能超过xxx us”。
PM QoS有四种约束类型:
| 约束类型 | 描述 | 典型场景 |
|---|---|---|
| CPU_DMA_LATENCY | CPU从idle状态唤醒的最大延迟 | 音频播放时要求低延迟 |
| CPU_FREQ_MIN | CPU最低频率约束 | 视频解码时保证性能 |
| CPU_FREQ_MAX | CPU最高频率约束 | 散热限制时降频 |
| NETWORK_LATENCY | 网络设备唤醒延迟 | 车载以太网实时通信 |
在i.MX8上,我曾经遇到一个问题:车载音频播放时总有卡顿。查了半天,发现是PM QoS没设置好,CPU在播放音频时进入了太深的idle状态,唤醒延迟超过了音频buffer的阈值。解决办法就是在音频驱动里加一行:
pm_qos_add_request(&audio_qos_req, PM_QOS_CPU_DMA_LATENCY, 100);
这行代码告诉内核:“音频播放期间,CPU唤醒延迟不能超过100us”。问题就解决了。
实战技巧:调试PM QoS时,可以查看/sys/devices/system/cpu/cpu0/cpuidle/下的状态,看看CPU到底进入了哪级idle。如果发现不该进深睡眠时进了,八成是QoS约束没设对。
16.2 Android WakeLock机制
WakeLock是Android在Linux PM之上封装的一套机制。说白了,就是让App能告诉系统:“我现在有事,别睡!”
WakeLock分两种类型:
- Partial WakeLock:阻止系统进入suspend,但CPU可以降频。App后台下载时常用。
- Full WakeLock:保持屏幕和CPU全速运行。游戏、导航时用。
在Android Automotive上,WakeLock的使用要格外小心。车载系统不像手机,用户不会频繁操作,但后台服务(如导航、语音助手)需要持续运行。滥用WakeLock会导致电池快速耗尽。
我见过一个案例:某第三方导航App在后台持有一个Full WakeLock不释放,导致车机在停车后一晚上耗掉了30%的电。后来我们在系统层加了一个WakeLock超时机制——任何App持有的WakeLock超过30分钟,系统自动回收。
WakeLock在驱动层的实现,其实是通过/sys/power/wake_lock和/sys/power/wake_unlock这两个节点操作的。Android的PowerManagerService就是通过写这两个节点来控制休眠的。
// 内核中WakeLock的实现
struct wakeup_source *ws;
ws = wakeup_source_register(NULL, "my_wakelock");
__pm_stay_awake(ws); // 持有WakeLock
__pm_relax(ws); // 释放WakeLock
注意:在i.MX平台上,WakeLock的注册名称不要超过45个字符,否则会被截断。我踩过这个坑——调试时死活找不到某个WakeLock,后来发现是名字太长被截了。
16.3 Suspend/Resume调试
调试suspend/resume,我有一套固定的流程:
- 检查设备树:确保所有设备的
power-domains属性正确 - 打开PM调试日志:
echo 15 > /sys/module/suspend/parameters/pm_test_delay - 测试suspend:
echo mem > /sys/power/state - 查看唤醒源:
cat /sys/kernel/debug/wakeup_sources
我最常用的调试手段是pm_test。它可以把suspend流程拆成几个阶段,逐个测试:
# 只测试freeze阶段
echo freezer > /sys/power/pm_test
echo mem > /sys/power/state
# 只测试设备suspend阶段
echo devices > /sys/power/pm_test
echo mem > /sys/power/state
# 关闭pm_test
echo none > /sys/power/pm_test
为什么要这么做?因为如果整个suspend流程失败了,你根本不知道是哪个阶段出的问题。分段测试可以快速定位。
我曾经遇到一个i.MX8QM平台的bug:suspend后系统立即唤醒。用wakeup_sources一看,发现是GPIO1_3这个唤醒源一直处于active状态。查硬件发现,这个GPIO在suspend时被外部上拉了,导致系统认为有唤醒事件。解决方案是在设备树中把这个GPIO的唤醒功能关掉:
&gpio1 {
wakeup-source;
gpio1_3: gpio1_3 {
// 添加这一行
wakeup-disable;
};
};
调试口诀:suspend失败查设备,resume失败查唤醒源。设备suspend失败看dmesg,resume失败看wakeup_sources。
16.4 功耗测量与优化
功耗测量,我分三步走:
- 硬件测量:用万用表或功率计测整板电流
- 软件估算:通过
/sys/class/power_supply/读取电池信息 - 细分测量:逐个关闭外设,找出功耗大户
在i.MX平台上,我常用regulator框架来查看各个电源域的功耗:
# 查看所有regulator的状态
cat /sys/kernel/debug/regulator/regulator_summary
# 输出示例
regulator use open bypass voltage current min max
----------------------------------------------------------------------------
vdd_soc 0 0 0 800mV 0mA 800mV 900mV
vdd_arm 0 0 0 900mV 0mA 900mV 1000mV
vdd_dram 0 0 0 1200mV 0mA 1200mV 1200mV
vdd_usb 0 0 0 5000mV 0mA 5000mV 5000mV
优化功耗,我总结了几条经验:
- 关闭未使用的时钟:很多驱动在probe时打开了时钟,但suspend时没关。用
clk_summary检查。 - 降低CPU频率:在idle时用
cpufreq降到最低频率。 - 关闭未使用的电源域:通过
regulator_disable()关掉不用的regulator。 - 优化唤醒源:只保留必要的唤醒源,其他的在suspend时禁用。
我的习惯:每次做完功耗优化,都会跑一个24小时的待机测试。记录每小时的功耗曲线,看看有没有异常波动。有一次发现凌晨3点功耗突然飙升,查日志发现是某个App在后台做数据同步。后来我们把这个同步任务改到了充电时执行。
16.5 本章知识体系
下面这张图总结了电源管理移植的核心脉络:
这张图从左到右展示了电源管理的三个核心模块,中间是调试手段,下面是优化目标。你在做移植时,可以按这个顺序来:先理解PM Core和QoS,再搞定WakeLock,然后用调试手段定位问题,最后做功耗优化。
最后提醒一句:电源管理移植最忌讳的就是“差不多就行”。我见过太多项目,因为suspend/resume流程没调好,导致车机在用户停车后偷偷耗电,第二天电瓶亏电打不着火。每个唤醒源、每个regulator、每个时钟,都得仔细检查。
公众号:蓝海资料掘金营,微信deep3321