4、电源管理策略配置:PM策略文件详解、策略优先级、策略切换规则

好,咱们接着聊电源管理。前面几章我们把QNX的电源管理框架、硬件抽象层都过了一遍。今天要聊的,是真正落地的东西——PM策略文件

说白了,策略文件就是告诉系统:什么时候该睡,什么时候该醒,睡多深,醒多快。我刚开始接触QNX电源管理时,觉得这玩意儿不就是个配置文件嘛,随便写写就行。结果第一次上车实测,屏幕闪得跟迪斯科似的……嗯,从那以后我再也不敢小看这个文件了。

4.1 PM策略文件(pm_policy.conf)详解

策略文件默认路径是 /etc/system/pm_policy.conf。当然,你也可以指定其他位置。我个人习惯把它放在 /etc/ 下,方便统一管理。

先看一个典型的策略文件长什么样:

# PM Policy Configuration
# 策略名称: 每个策略对应一个电源状态

policy {
    name = "active"
    latency = 0
    power = 100
    wakeup_sources = "all"
}

policy {
    name = "idle"
    latency = 50
    power = 60
    wakeup_sources = "touch,key"
}

policy {
    name = "sleep"
    latency = 200
    power = 10
    wakeup_sources = "power_key,alarm"
}

policy {
    name = "deep_sleep"
    latency = 500
    power = 1
    wakeup_sources = "power_key"
}

每个 policy 块定义了一个电源状态。这里有四个关键字段:

  • name:策略名称,你自己起名。但建议跟硬件支持的电源状态对应起来。
  • latency:从该状态恢复到完全可用的时间,单位是毫秒。这个值越小,恢复越快。
  • power:该状态下的功耗百分比。100表示全功耗,0表示完全断电。
  • wakeup_sources:哪些外设可以唤醒系统。比如触摸屏、按键、RTC闹钟等。

我的经验:latency 和 power 是一对冤家。你想省电,就得接受更长的唤醒时间。我在做某款车机时,客户要求待机功耗低于5mA,但唤醒时间不能超过300ms。折腾了好久,最后在硬件上加了快速唤醒电路才搞定。

4.2 策略优先级

策略之间是有优先级的。QNX PM框架怎么决定用哪个策略?看两个东西:

  1. 策略在文件中的顺序:越靠前优先级越高。
  2. 系统当前的约束条件:比如某个驱动要求最低功耗不能低于某个值。

举个例子:

# 优先级从高到低
policy { name = "active";     priority = 100 }
policy { name = "idle";       priority = 80  }
policy { name = "sleep";      priority = 50  }
policy { name = "deep_sleep"; priority = 10  }

优先级数值越大,越优先被考虑。但注意,不是优先级高的就一定会被选中。系统会综合所有约束条件,选一个最省电且满足所有约束的策略。

核心原则:策略选择是「约束满足」问题,不是「优先级排序」问题。优先级只是参考,最终决策权在PM框架手里。

我记得有一次,某个音频驱动注册了一个约束,要求系统不能进入deep_sleep。结果车机在播放音乐时,明明屏幕关了,系统却一直停留在idle状态,功耗下不去。查了半天才发现是驱动忘了释放约束。嗯,这种坑踩过一次就记住了。

4.3 策略切换规则

策略切换不是你想切就能切的。QNX有一套严格的切换规则:

4.3.1 切换触发条件

  • 用户态请求:应用程序调用 pm_set_policy() 主动切换。
  • 内核态触发:比如CPU空闲时间超过阈值,内核自动发起切换。
  • 外设事件:比如按键按下、触摸事件,系统从低功耗状态唤醒。
  • 定时器超时:比如屏幕无操作5分钟后,自动进入sleep。

4.3.2 切换流程

系统切换策略时,会走这么几步:

  1. 检查约束:当前所有驱动是否允许切换到目标策略。
  2. 通知驱动:调用驱动的 pm_policy_change() 回调,让驱动做好准备。
  3. 硬件操作:PM框架执行具体的硬件操作,比如关闭时钟、降低电压。
  4. 确认状态:等待所有驱动确认切换完成。
  5. 更新状态:系统正式进入新策略。

注意:如果某个驱动在步骤2中返回错误,整个切换会被取消。我曾经遇到一个网卡驱动,在进入sleep前没有正确关闭DMA,导致切换失败,系统一直卡在idle状态。所以驱动开发时,一定要把pm_policy_change()回调写完整。

4.3.3 切换延迟与超时

切换不是瞬间完成的。每个策略都有一个 switch_latency 参数:

policy {
    name = "sleep"
    switch_latency = 100  # 切换过程最多允许100ms
    ...
}

如果切换超时,PM框架会怎么做?

  • 默认行为:强制切换,忽略超时的驱动。
  • 安全行为:回滚到之前的策略。

我个人建议用安全行为。强制切换可能导致驱动状态不一致,下次唤醒时出问题。你想想看,车机在高速上突然黑屏了,多吓人。

4.4 实战配置建议

说了这么多理论,来点实际的。我总结了几条配置策略文件的经验:

场景 推荐策略 latency power 说明
用户正在操作 active 0 100 全速运行,不省电
屏幕亮但无操作 idle 50 60 降低CPU频率,关闭部分外设
屏幕灭,音乐播放 audio_sleep 100 30 保留音频通路,其他都关
熄火停车 deep_sleep 500 1 只保留RTC和CAN唤醒

避坑指南:我曾经在某个项目中,把idle策略的latency设成了10ms。结果系统频繁在active和idle之间切换,功耗反而比一直active还高。后来把latency调到50ms,加了个滞回时间,问题就解决了。说白了,切换本身也是有代价的。

4.5 策略文件的调试技巧

配置写好了,怎么验证对不对?我常用的方法:

  1. 查看当前策略pm status 命令可以看系统当前在哪个策略。
  2. 跟踪策略切换pm trace -e policy 可以实时看到切换事件。
  3. 检查约束pm constraint list 列出所有驱动注册的约束。
  4. 模拟切换pm set_policy sleep 手动强制切换,看驱动反应。

嗯,调试时最好连上串口或者网络,不然系统睡死了你都不知道。我有一次在实验室里,车机进入deep_sleep后怎么都唤不醒,最后发现是唤醒源的GPIO配置错了。从那以后,我每次改策略文件都会先做一次完整的唤醒测试。

好了,策略配置这块就聊到这儿。下一章我们讲具体的驱动开发,看看怎么让驱动乖乖听PM框架的话。