十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ARM电源架构解析:PPU如何实现SoC电源域的精细管理

ARM电源架构解析:PPU如何实现SoC电源域的精细管理 1. 为什么芯片要“一块一块”断电说 ARM 电源架构之前先从一个最朴素的问题聊起芯片为什么不能像家里的总闸一样“啪”一下全断掉因为大多数芯片在“看起来没干活”的时候其实一直在悄悄耗电。CMOS 电路的功耗主要分两块——动态功耗和静态功耗。动态功耗是和翻转频率绑定的公式是 P_dynamic α·C·V²·f意思是你跑的频率越高、电压越高翻转的电路越多功耗就越大。而静态功耗主要来源于漏电流哪怕一条电路今天一整秒都没翻转只要它还通着电漏电就一直在烧。工艺越先进、晶体管尺寸越小漏电在总功耗里占的比重反而越大这也是为什么先进工艺节点上大家疯狂搞电源关断。所以省电的思路就变成了这不用的电路干脆别给它供电。但问题来了一颗 SoC 里功能实在太多了——CPU 核、GPU、NPU、ISP、Modem、DDR 控制器、还有一堆挂在总线上的外设。如果整颗芯片一起断电那唤醒源也被断掉了系统就再也醒不过来了。现实的做法是参考居民楼的分户供电整栋楼一个总闸但每户还有自己的电表、自己的空气开关某户人家拉闸不影响别家照明保安室永远有电。这个“每户的空气开关”落到 SoC 里就是我们常说的电源域Power Domain。而负责按“户”去控电的“物业管理员”在 ARM 的体系里就有一部分是 PPU 干的活。PPU 的全称在不同文档里有点出入常见的是 Power Policy Unit 或 Power and Performance Unit反正核心职责就一个根据软件下发的请求把某个电源域安全地断开、保持或恢复供电。理解了“为什么要逐个断电”才能明白后面那么多硬件模块、协议和驱动代码存在的意义。这篇文章就围绕“怎么断”“谁来断”“断了之后怎么恢复”这三件事把 ARM 电源架构和 PPU 的工作方式拆开讲一遍。适合正在搞嵌入式 Linux 低功耗、BSP 电源管理、以及好奇 SoC 内部怎么协同工作的同学。2. 硬件侧电源域、电压域与断电的关键单元2.1 电源域Power Domain和电压域Voltage Domain有什么区别很多刚接触电源管理的同学会把电源域和电压域搞混这两个概念确实容易绕但它们管的是不同维度的事。电压域强调的是“同一路供电轨”这些模块共用同一个电压值。比如某个 SoC 有四颗 Cortex-A78 CPU 核它们通常位于同一个电压域共享同一路 VDD_CPU通过 DVFS 一起升压降压。为什么一个核要升频其他核也得跟着动就是因为它们在同一个电压域里只要有一个核需要高频整个域的电压就得拉上去否则那个核的时序就hold不住。电源域强调的是“能否独立通断电源”。一个电源域里面可以包含多个电压域也可以与它们重叠交叠。比如那四颗 CPU 核整体的电源域叫 CPU 电源域但可能每颗核又能进一步拆成自己的小域实现单核关断。再比如 GPU 域、NPU 域、ISP 域各自都有独立的电源开关。更细一点的划分里还有一个叫 power switch 的东西。一个电源域能不能真正被断开靠的就是在电源网络上插入的电源开关单元。开关闭合域上电开关断开域下电。这个开关不是家里那种继电器而是由大尺寸 PMOS/NMOS 管实现的专用 cell。开关本身也有功耗损耗所以用来做开关的 cell 要选低导通电阻的但低导通电阻通常意味着更大的面积这里面就有取舍。除了开关一个完整的电源域还离不开隔离单元isolation cell和保持单元retention cell。隔离单元的作用是防止域断电后输出去的信号变成不确定电平把其他还活着的域的逻辑搞乱。保持单元则是用一颗比主电源更小的常供电源给寄存器里的关键状态“吊着一口气”保证复位后能快速恢复现场。2.2 一个电源域的典型组成我带你看一个典型的、可关断的电源域内部长什么样。电源进来后第一道是 power switch。开关后面是整个域的“本地地”或“本地电源”所有逻辑 cell 都挂在这条网络上。域边界上所有输出信号必须经过 isolation cell防止输出悬空电平输入信号倒不一定需要隔离但如果对面是不同电压域则需要 level shifter 做电平转换。域内那些需要在断电后仍保留少数状态的寄存器要换成 retention flip-flop它的供电来自另一边常开的电源。再往下整个域还挂着一个状态机专门管理上电顺序。为什么需要顺序因为一坨电路如果 VDD 还没稳定就收到时钟或者某个模块还没复位就开始工作会出现亚稳态甚至物理损坏。一般的上电流程是先开启 power switch等待电压稳定需要满足上电斜坡时间然后撤销 isolation再释放复位最后才能给时钟。下电则是反过来先断时钟、再复位、再打开隔离、最后关 power switch。这一整套动作硬件上可以由纯逻辑状态机自动完成也可以由固件比如 ATF、SCMI 固件通过写寄存器一步一步完成。而 PPU 在这里的角色就是那个“乖巧的执行者”接收软件发来的请求然后按硬件手册规定好的时序去拉那些控制信号。2.3 一个电源域怎么断电的完整时序简化版假定我们要把一个 Cortex-A53 集群cluster的电源断掉手机上常见的流程如下第一步内核通过 PSCI 协议后面细讲调用固件告诉它“我要关掉 CPU 集群的电源”。第二步固件会先把该集群里所有核心的上下文保存好包括通用寄存器、系统寄存器这些内容要么保存在内存里要么保存在上一级仍供电的缓存里。第三步固件把核心的异常向量调到一段始终在线的 SRAM 里的唤醒代码并把 GIC 的中断配置调好保证将来给这个域发唤醒中断时能正确路由。第四步固件等待所有核心进入 WFIWait For Interrupt状态确认核心已停住然后开始往下走时序隔离、关时钟、延时等待、最后关 power switch。这还只是一个 CPU 集群关电的流程GPU、NPU 这些大域关电时还要考虑内部 cache 写回、外部总线请求正在传输的数据不能丢。所以每个大域的关断代码都像跳一支排练过很多遍的集体舞谁也不许抢拍子。3. PPU 到底在电源架构里扮演什么角色3.1 PPU 是硬件模块不是单纯的软件策略很多人对“PPU”这个词产生疑惑是因为在不同上下文里看到的含义不一样。在 MCU 圈子里比如 STM32 的文档里很少叫 PPU而是叫 PWR 模块负责管理 Sleep/Stop/Standby 模式。在带 MMU 的应用处理器 SoC 里ARM 官方文档描述的 PPU有时指挂在 interconnect 上的一个电源控制单元类似一个小型的微控制器或硬件状态机专门管理若干个电源域的开关。从本质上看PPU 是一个硬件实体它有一组寄存器软件通过对这组寄存器写入命令来请求电源状态切换它也有与 PMIC 或片上 LDO 的握手信号用来控制外部供电轨的电压和通断它还有一组功耗传感器接口基于硬件反馈做动态调压调频。换句话说PPU 是“做决策的那只手”而真正的大决策比如去哪一级 idle、要不要触发 DVFS通常还是由 OS 的 framework 来做。可以这样类比Linux 内核是公司的 CEO决定要裁员还是扩招PPU 是 HR 部门负责落实裁员和招聘流程包括发通知、走流程、办手续。你让 HR 决定公司战略不合适但没有 HRCEO 的决策也无法落地。3.2 ARM SoC 中常见的一层“PPU Orchestration”ARM 官方的参考实现里电源管理的硬件部分通常不只有一个 PPU。以某个带 DynamIQ 架构的 SoC 为例每个 CPU cluster 有一个 cluster PPU每个核心有一个 core PPUGPU/NPU 也有各自的 PPU。这些 PPU 再由一个更高层的“系统控制处理器”或电源管理固件统一协同。为什么搞这么复杂因为电源状态切换涉及的不仅仅是电源域本身还牵扯到时钟、复位、中断路由、调试接口甚至总线 QoS。比如你关掉一个 CPU 核心之前得确保没有 pending 的中断要发给它而中断控制器本身必须在域断电后仍然活着否则没人能唤醒它。这种跨模块的依赖关系不是单一 PPU 一拍脑袋能决定的必须由一个全局视角的管理者来仲裁。在软件层面上ARM 把这种“全局视角”抽象成了 PSCIPower State Coordination Interface标准和 SCMISystem Control and Management Interface标准。如果你看过 Linux 内核里 CPU hotplug 和 cpuidle 的代码你会发现它们到一定程度就不再往下操作硬件了而是把请求打包成一个 SMC/HVC 指令扔给 EL3 或 EL2 的固件。固件再根据请求里的参数去配置对应的 PPU。所以ARM 生态里真正负责“断电”的执行链条是内核 → PSCI 固件 → PPU 硬件 → power switch 单元。PPU 不是孤军奋战它只是这条链上最终动手的那一环。3.3 PSCI 协议软件与 PPU 之间的通用语言PSCI 是在 ARM 的文档里定义的一套接口用来统一不同 SoC 之间电源管理操作的差异。内核里你看到的 psci_cpu_suspend、psci_cpu_off、psci_cpu_on 这些函数它们发送的其实是一个个有编号的函数调用。比较常用的 PSCI 功能有PSCI_VERSION查询固件实现的 PSCI 规范版本。CPU_SUSPEND让指定的核心进入低功耗状态分为进入待机和深度睡眠两种。CPU_OFF彻底下线一个核心相当于电脑的“关机”再恢复要通过 CPU_ON。CPU_ON把某个核心从断电状态拉起来并设置它的启动地址。AFFINITY_INFO查询某个层级cluster/core当前是否处于关断状态。SYSTEM_SUSPEND让整个系统进入深度睡眠类似 PC 的 S3。正因为有了这层抽象底层的 PPU 无论是哪家半导体公司实现的只要固件遵循 PSCILinux 内核就无需修改电源管理的核心代码。你换了一款开发板电源管理驱动需要改的是固件配置和设备树而不是内核主干的电源管理框架。我个人调试 RK3588、高通等平台时经常需要看 ATFARM Trusted Firmware和 SCXI 驱动里的 SCMI 消息。设备树里经常能看到类似 /dsu/sram/psci 之类的节点其实就是在描述 PSCI 固件使用哪块 SRAM 做数据交换。4. 一次真实的系统休眠流程从 Linux cpuidle 到 PPU 断电4.1 CPU idle 和 CPU hotplug 是两回事很多初学者会把 cpuidle 和 CPU hotplug 混在一起但它们在电源域开关的层级上差别很大。cpuidle 是让 CPU 在“无事可做”时进入低功耗状态。最浅的 idle 状态通常只是执行 WFI 指令处理器停下来了但电源和时钟大概率还在保持深一点的 idle 状态会把该核心的时钟断掉但电源还挂着这样可以保留 cache 的上下文唤醒延迟比较低更深的 idle 状态才真正把整个 CPU 电源域断掉这种状态唤醒时需要从内存恢复上下文延迟高但省电也最可观。CPU hotplug 则是主动下线一个 CPU 核心流程比 idle 重得多。它要先把该 CPU 上的进程迁移走、中断移走再走完整的 teardown 流程把电源域关掉。一般系统不会频繁触发 CPU hotplug除非你在运行一个功耗敏感场景或者做 CPU 在线调频测试。内核里的 menu governor 会基于系统负载、唤醒源预测、硬件 CPUSTATE 等参数决定进入哪一级 idle。如果 menu governor 判断后面大概率会有一段时间的空白比如屏幕关了用户在看视频它会更激进地选择深 idle 状态并最终调用到底层的 platform 代码走 PSCI。4.2 调用链跟踪一把以 ARM64 Linux 系统为例一条渲染进程没事干了调度器发现该 CPU 没有 runnable 任务了最终会进入 cpu_startup_entry 里的 idle loop。idle loop 调 cpuidle_select选择要进入的 idle state然后 cpuidle_enter 调到底层换成 machine 相关的 opsARM64 平台通常会走到 psci_cpu_suspend这个函数会构造一个 PSCI 的参数结构然后执行 smc 指令陷入 EL3。EL3 固件里的 psci 实现会根据请求判断这是哪个 CPU 和 cluster然后到对应的 PPU 寄存器里写命令。PPU 硬件收到命令后开始自动执行关断时序隔离、等待总线事务结束、断电。整个过程对 Linux 内核来说就是“调了一个函数然后感觉时间像断片了一样等我醒过来已经是几微秒或者几毫秒以后”。这里有一个关键点进入深 idle 时睡眠期间的唤醒源必须提前配置好。如果一个中断在深 idle 期间来了但中断控制器不知道如何把 CPU 唤醒那系统就真的“睡死”过去了。实际项目中排查“睡死”问题百分之八十都需要检查 GIC 的唤醒路由和 PSCI AFFINITY_INFO 返回的状态。4.3 外设的电源管理runtime PM 与 genpdCPU 的电源管理只是整个 SoC 电源架构的一面外设的电源管理同样重要。Linux 里有一套叫 runtime PM 的机制它允许设备在没有人使用时自动关闭对应的电源域。这套机制在设备树里体现得很明显。例如一个 MIPI DSI 控制器设备树会把它放到某个 power domain 节点下类似 power-domains pd_display。当内核发现这个外设的 refcount 降为 0就会通过 genpdGeneric Power Domain框架调用电源域控制回调最终关闭对应的电源域。genpd 框架是理解 Linux 电源管理一个很重要的中间层。它维护了每个电源域的状态、使用计数、是否允许关闭等信息。你可以通过 debugfs 快速查看一个系统里所有电源域的实时状态cat /sys/kernel/debug/pm_genpd/pm_genpd_summary输出结果里会列出每个 power domain 的状态、是否 active、里面挂了哪些设备。调试外设频繁睡死、无法进入低功耗等问题时这个命令是首选的快速排查工具。我曾经遇到一个触摸屏唤醒失败的 case排查了半天最后发现是触摸屏的 IRQ 引脚被挂在一个准备关闭的电源域上而触摸屏本身却属于另一个电源域。IRQ 引脚所在的域一断电电平变成不确定状态中断控制器误触发了无意义的唤醒。这种跨域依赖问题在电源管理调试里非常经典。5. 实操怎么在嵌入式平台观察和调试电源域的通断5.1 打开内核配置查看电源管理状态大多数嵌入式 Linux 发行版默认关闭了电源管理的调试接口。你需要在内核配置里打开 CONFIG_PM_ADVANCED_DEBUG、CONFIG_PM_GENERIC_DOMAINS_DEBUG、CONFIG_DEBUG_FS 这些选项重编内核后才有下面这些调试手段。挂载 debugfsmount -t debugfs none /sys/kernel/debug查看所有电源域汇总cat /sys/kernel/debug/pm_genpd/pm_genpd_summary cat /sys/kernel/debug/pm_genpd/pm_genpd_summary | grep -E domain|audio|display查看当前 CPU 的 cpuidle 状态以及进入各状态次数cat /sys/devices/system/cpu/cpu0/cpuidle/state0/usage cat /sys/devices/system/cpu/cpu0/cpuidle/state1/usage cat /sys/devices/system/cpu/cpu0/cpuidle/state1/latencyusage 表示历史上进入该状态的次数。如果深 idle 的 usage 一直不涨说明系统根本没能进入深睡要么是被频繁中断打断要么是固件拒绝了请求。查看 CPU online/offline 状态cat /sys/devices/system/cpu/online cat /sys/devices/system/cpu/offline5.2 抓取 PSCI 调用用 ftrace 跟到底当你想确认某次“断电”请求是否真的走到了固件可以借助内核的 ftrace跟踪 cpuidle 到 psci 的关键函数。cd /sys/kernel/debug/tracing echo 0 tracing_on echo function current_tracer echo psci_cpu_suspend cpuidle_enter cpuidle_enter_state set_ftrace_filter echo 1 tracing_on sleep 2 echo 0 tracing_on cat trace这里列出的函数名在不同内核版本里略有差异但思路通用看 cpuidle 是否真的进入了更深的状态还是在中途被某个短中断拉回。更底层的观察方式是借助 ftrace 的 function_graph配合 funcgraph-abstime 和 funcgraph-proc看到每个函数耗时。如果 PSCI_SUSPEND 调用后返回很快比如几十微秒说明固件很可能只做了浅睡眠没有真正断电源域。5.3 排查“系统睡不下去”的经典套路实际项目中“省电模式打不开”“功耗偏高”这类问题大部分不是硬件坏了而是软件里有东西在阻挠电源域关闭。常见原因如下第一某个设备没有进入 runtime idle。Linux 的 genpd 对每个挂载在电源域上的设备都维护一个使用计数。只要有一个驱动没正确调用 pm_runtime_put这个设备的 refcount 就不会归零电源域就关不掉。排查方法就是看上面提到的 pm_genpd_summary找到还处于 active 的设备。第二中断唤醒配置不对。深 idle 之前内核会将当前 CPU 允许的中断唤醒配置到 GIC。如果一个 GPIO 中断没有正确设置 GIC 的路由它可能无法唤醒 CPU或者会把 CPU 从浅睡眠中唤醒但进不了深睡眠。排查时可以临时关掉某些设备中断测试比如echo 0 /proc/sys/kernel/printk然后把某些外设驱动卸载观察功耗曲线变化。第三时钟未关断。芯片的时钟管理通常和电源管理联动如果某个时钟控制器的 refcount 没归零时钟树底层一直运行即便电源域断了漏电和时钟动态功耗也没省下来。排查方法是在固件或内核里打开 clock framework 的 debugfs查看每个 clock 的 prepare/enable 状态。第四固件 PSCI 版本不匹配。某些老固件对 CPU_SUSPEND 的深层状态没有实现内核请求深度睡眠固件却只做浅睡眠数据上看是功耗没降下来。这种只能刷新的 ATF 固件或者检查内核的 PSCI 版本协商逻辑。5.4 用 powertop 和功率计辅助验证软件状态确认后还需要用实测数据验证断电是否真的发生。手持设备开发中常用到的工具有Powertop查看 CPU 和各设备的活动状态能看到“Top Power Consumers”。I2C 转接的 PMIC 工具比如瑞萨的 RAJ 系列 PMIC 调试工具、高通平台的 qpnp 工具直接读 PMIC 寄存器确认各 LDO/BUCK 输出电压是否按要求切到更低档或关闭。高精度功率计测整板电流用长时间记录功能看有没有周期性尖峰。如果条件允许直接在断电前后量该电源域对应的某颗电容两端的电压变化能最直观地看到 power switch 是否真的把电断了。这个操作在早期硬件验证阶段经常用。6. 经验分享ARM 平台电源管理的几条“土办法”踩过不少坑之后总结几条对新手特别友好的经验。第一条在调电源管理之前先把系统里的所有打印关掉尤其是 debug fs 里那些会周期性唤醒 CPU 的工具。很多时候你觉得“系统睡不下去”其实只是因为你开着 USB 串口DMA 一直有数据在刷。拔掉串口、关掉网口功耗数据才会回归真实水平。第二条遇到“断电后起不来”的问题不要先怀疑 PPU 硬件先查 firmware 里有没有配置好 RAM 保持区域。CPU 深度睡眠后代码从 DDR 或 SRAM 恢复执行如果 DDR 控制器在自刷新模式但唤醒代码写在 DDR 里那 CPU 醒来第一件事就去读内存结果内存控制器还没退出自刷新直接 hang 住。这个坑我见过好几次解决方式是把唤醒代码放在 always-on 的 SRAM 里并确保 DDR 控制器先退出自刷新再跳转。第三条善用 PSCI 的 AFFINITY_INFO。如果固件实现的比较完整你可以通过设备树或者 debugfs 里的 psci 节点查询集群的每一个核心当前处于什么状态。这套信息对于判断“某个 CPU 是不是真的已经断电”非常有帮助。比单纯读内核的 online/offline 标志更接近真相。第四条做电源管理开发时别把 Linux 内核态当成唯一战场。你会发现真正难的问题往往出在固件侧。比如 ATF 的 power state coordination、SCMI 的电源域控制、vendor 私有的 PPU 寄存器配置。内核只能拿到“行或不行”的结果至于为什么不行得去读固件日志和硬件寄存器。所以开发环境里最好配上 JTAG 调试器和 ATF 的串口日志输出否则很多底层的 power domain 问题会把你卡到怀疑人生。再分享一个比较新颖的思路现在一些 SoC 引入了更细粒度的“功耗管理单元”不仅管电源域还把 DVFS、AVSAdaptive Voltage Scaling以及 thermal throttle 都统一到了同一个硬件调度器里。这种架构的好处是电压调节和断电控制可以更平滑地协同避免出现“先升压再降频”这种互相打架的操作。如果你在做新平台的 BSP看到固件里有类似“power controller firmware”的概念别排斥它它其实是在帮 Linux 内核把最底层的那点脏活累活接过去。做电源管理调试最磨人的不是某一刻断电失败而是功耗比预期高了几十毫瓦、却怎么都定位不到元凶。这时候一套好的工具链、对 PSCI/PPU 调用链路的理解加上前面说的这些排查套路通常比拍脑袋改驱动靠谱得多。最后再提一个小技巧在设备树里给关键外设的 power domain 加一个固定的状态例如把 display 的 domain 设置为 always-on可以快速验证“是不是这个外设导致系统无法休眠”。这类手段虽然不优雅但在项目排期紧的时候能帮你快速砍掉变量、锁定根因比对着文档一行行看寄存器高效多了。
返回列表