ARTICLE DETAIL

资讯详情

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

ARM电源管理实战:SCP、SCMI与PSCI原理及调试指南

ARM电源管理实战:SCP、SCMI与PSCI原理及调试指南 去年我在一块ARMv9开发板上调suspend/resume内核日志里卡在某个SCMI命令上十几秒不响应最后报timeout waiting for SCP response。第一反应是SCP又挂了查了一整天最后发现是共享内存里token匹配出了问题。那次之后我老老实实把SCP这套体系从头啃了一遍才发现很多做BSP的同学跟我一样天天跟psci、scmi打交道但对SCP内部到底怎么工作、电源管理消息是怎么从Linux一路传到硬件控制器的始终是一团模糊。这篇文章就把ARMv9/v8平台上的电源管理工作原理尤其是SCPSystem Control Processor服务这一块结合我实际调过的板子一次讲透。适合正在做内核移植、固件开发、低功耗调优的工程师也适合刚接触ARM体系结构、想搞清楚内核里那些psci/scmi节点到底是什么的同学。1. 先搞清楚为什么芯片里要专门放一个电源管家1.1 AP处理器的死穴一断电就没法干活了应用处理器APApplication Processor跑的是Linux这种复杂系统但它有个天然缺陷一旦某个CPU核进入power down状态或者整个系统要进入suspendAP自己是没法继续执行代码来管理这些状态的。你想啊CPU都断电了代码跑在哪里所以ARM的电源管理从一开始就需要分层。操作系统负责决定什么时候睡、睡多深但真正执行下电动作、操作PMIC调压、锁PLL这些工作必须交给一个独立的、永远在线的处理器来做。这个处理器就是SCP。1.2 SCP在SoC里的具体定位SCP是SoC内部的一颗管理核通常是Cortex-M级别的微控制器跑着一份独立的固件。它和AP是主从关系AP是主人SCP是管家。管家不负责算业务但负责管电、管时钟、管温度还管一些系统级的安全复位。打个比方AP就像一个住大房子的房主SCP就是那位24小时值班的物业管家。房主出门suspend前跟管家说一声我睡了你看着办房主还没回来管家已经把走廊灯PLL关了、空调电压调低了房主按门铃中断唤醒管家再把灯打开、温度调回来。在具体硬件上SCP通常挂在它自己的always-on电源域上连的是独立时钟这样哪怕AP整个簇都断电了SCP依然活蹦乱跳能响应来自外部的唤醒事件再把AP拉起来。1.3 从ARMv8到ARMv9电源管理格局的变化ARMv8时代电源管理接口已经标准化了PSCIPower State Coordination Interface成为操作系统和固件之间的标准通道。到了ARMv9架构上更强调安全比如CCA机密计算电源域的拓扑也更复杂大核小核数量更多PMU电源管理单元和SCP的配合要求更高。但底层逻辑没变内核跑在AP上电源管理需求通过SMC/HVC指令落到EL3固件一般是TF-AEL3再通过SCMI协议把活儿派给SCPSCP直接操作寄存器、I2C、PMIC。理解这条链路后面所有问题都能对号入座。2. 核心概念扫盲PSCI、电源域、SCMI、MHU2.1 PSCI内核和固件的标准交接协议PSCI是ARM定义的电源管理接口标准内核里一般不用直接感知SCP的存在因为它只跟EL3固件打交道。常见的PSCI命令有这些PSCI命令功能ID32位版本作用PSCI_VERSION0x84000000查询PSCI版本PSCI_CPU_SUSPEND0x84000001让CPU进入挂起状态PSCI_CPU_OFF0x84000002关闭一个CPU核PSCI_CPU_ON0x84000003打开一个CPU核PSCI_AFFINITY_INFO0x84000004查询某个层级的亲和状态PSCI_SYSTEM_OFF0x84000008系统关机PSCI_SYSTEM_RESET0x84000009系统复位PSCI_FEATURES0x8400000A查询某命令是否支持PSCI_SYSTEM_SUSPEND0xC400000E系统级挂起内核里的psci驱动会把这些命令包装好。比如你执行echo 0 /sys/devices/system/cpu/cpu5/online内核的cpu hotplug框架会走到psci_cpu_off通过SMC指令进入EL3。注意一个关键点PSCI的CPU_SUSPEND命令里带一个power_state参数这个参数编码了挂起类型是retention还是power down、要操作的亲和层级CPU、簇还是系统以及状态ID。具体每一位的含义每个版本spec略有差异但基本思路一致retention状态表示时钟停了但电源没断power down状态表示电源域真的要断电。2.2 电源域的层级CPU、簇、SoC三级结构ARM把电源管理的粒度分成亲和层级Affinity Level层级0单个CPU核。下电前要保存上下文、关闭本地中断L1 cache被清空。层级1一个簇Cluster通常是一组小核或一组大核。簇下电意味着簇内所有CPU都关了L2 cache也得做处理。层级2整个系统。系统suspend状态下几乎整个SoC都断电或者进入深度低功耗只有SCP所在的always-on域还在。下电一个CPU核和下电整个簇的成本完全不同。sysfs里关闭一个核可能只是把单个核的电源门关掉如果最后一个小核也关了SCP会把整个小核簇的电源也断掉连带L2 cache flush、GIC的 redistributor 配置保存等一堆事情。2.3 SCMI和SCP对话的业务语言PSCI管的是AP自己的电源状态但SCP还能管更多的资源要给某个功耗域调控电压、要给某个设备调时钟频率、要读温度传感器的值这些都不在PSCI范围内。于是ARM定义了SCMISystem Control and Management Interface协议。SCMI是一组协议族常用的有Base协议查询版本、设备能力。Power Domain Management协议管理电源域开关SCP固件里核心中的核心。Performance Management协议管理性能等级CPU的DVFS就走这个。Clock Management协议管理时钟频率。Sensor Management协议读温度、电压等传感器数据热管理依赖它。System Power协议系统级关机、重启、休眠。SCMI命令的格式是一个固定头部加参数载荷。头部32bit里高位是协议ID接着是消息类型命令、响应、通知中间是消息ID低位放一个token用来把SCP的异步响应和请求对上号。这个token在调试超时问题时特别有用我后面会讲。2.4 MHU和共享内存SCP通信的邮局和信件AP和SCP之间物理上怎么通信靠的是MHUMessage Handling Unit一种简单的邮箱硬件。MHU负责传递门铃信号AP往发送寄存器里写一下SCP那边就收到一个中断反过来SCP也可以敲AP的门铃。但门铃只能通知我有事找你具体事由得通过共享内存传递。Linux的SCMI驱动在内存里划分一块共享区域shmem发消息时先把SCMI消息帧写到共享内存再敲MHU门铃SCP收到中断后去读共享内存、解析、执行把响应写回同一块区域再回一个门铃。这就是典型的共享内存邮箱门铃模式理解了这个后面调SCMI超时问题就有方向了。3. SCP固件内部长什么样模块划分与运行环境3.1 运行环境一颗永远在线的M核SCP固件跑在独立的微控制器上最常见的是Cortex-M3、M7或者M55这类核。固件本身一般是一个轻量RTOSFreeRTOS、Zephyr都常见也有很多厂商用裸机状态机。SCP固件的代码量不大但逻辑极其严谨因为它是整个系统里最后一个倒下的软件一旦它挂了系统可能连开机都开不了。从开发角度看SCP固件和AP侧是完全独立的工程有自己独立的编译工具链、独立的git仓库、独立的烧录通道。调试SCP固件不能像调试内核那样直接gdb通常要靠它打的日志。所以SCP固件的日志系统设计非常关键很多厂商会做成内存环形缓冲专用UART输出的组合。3.2 核心模块拆解一个典型的SCP固件内部大概是这些模块传输模块Transport处理MHU中断解析SCMI消息管理收发缓冲区是SCP的前台接待。电源域管理模块Power Domain Manager维护每个电源域的状态机处理POWER_STATE_SET命令执行真正的下电上电序列。时钟管理模块Clock Manager操作PLL、分频器、时钟门控配合DVFS切换频率。性能管理模块Performance Manager管理每个性能域performance domain的OPP工作点包括电压和频率的组合DVFS调频时由它协调。热管理模块Thermal Manager周期读温度传感器温度过高时主动要求降频甚至触发紧急关机。系统电源模块System Power处理系统级shutdown命令控制DDR自刷新、IO保持等。这些模块之间通过一个事件循环协作。比如收到一条设置性能等级的命令性能模块要先把新频率的PLL参数准备好然后跟电源模块确认电压状态最后寄存器切换期间任何一步失败都要能回滚。3.3 启动流程与镜像加载SCP固件有自己的启动流程通常在AP的ROM代码之前就完成了初始化。但SCP固件本身也存在要用一个最小的loader来加载的鸡生蛋问题。常见的方案是SoC内部有一小块ROM上电后ROM先从固定的介质比如片上Flash、BootROM里的镜像、甚至是AP通过安全的接口灌进来的加载SCP固件到它自己的SRAM然后SCP再接管系统的低功耗管理。在调试开发板时一个很有用的动作是检查SCP固件版本是否和ATF、内核的预期版本匹配。版本不匹配是最常见的奇怪问题来源比如内核SCMI驱动用了新特性但SCP固件太老响应里多了或少了字段直接导致解析错位。4. 四个典型电源管理流程从代码到硬件走一遍4.1 CPU热插拔关闭一个核的实际动作执行echo 0 /sys/devices/system/cpu/cpuN/online整个链路是这样的内核的cpu hotplug框架调用PSCI的CPU_OFF。CPU核进入EL3ATF的psci实现接管先把当前核的上下文保存好。ATF通过SCMI的电源域命令告诉SCP我要关这个核所属的电源域。SCP的电源域模块开始执行关电序列确认该核的中断已经迁移、flush cache、关掉该核的时钟最后关电源门。SCP回复已关ATF返回到剩余的活跃核上内核更新在线状态。打开一个核CPU_ON则是反过程。这里最常见的坑是唤醒地址CPU上电后第一件事是跳到某个地址执行这个地址必须在CPU下电前由内核通过PSCI的CPU_ON参数告诉固件。如果地址配置错了核起来后直接跑飞或死循环日志里表现为CPU failed to come online。4.2 DVFS从cpufreq到电压频率切换你在Linux里用cpufreq或者devfreq调频率背后走的通常是SCMI的Performance Management协议。流程大致是cpufreq驱动根据调频策略决定把CPU性能等级performance level从level 3调到level 5。内核SCMI驱动把PERF_LEVEL_SET命令写入共享内存敲MHU门铃。SCP收到后性能模块查表拿到level 5对应的频率和电压开始切换。切换顺序有讲究升频时先升压再升频降频时先降频再降压。顺序错了轻则瞬态电压不足导致系统不稳定重则在最大负载下直接崩溃。切换完成后SCP回响应内核更新当前的频率信息。调DVFS时我踩过一个大坑某个OPP的电压参数在SCP固件侧和PMIC侧对不上升频后高负载跑几分钟就随机重启。查了半个月最后发现是SCP固件里一个电压表的微调参数写错了。所以切记调DVFS不稳定先怀疑电压参数不要上来就怀疑代码逻辑。4.3 系统挂起Suspend整个SoC入睡的过程系统suspend是SCP戏份最重的一个场景。执行echo mem /sys/power/state内核会走标准的suspend序列最后通过PSCI_SYSTEM_SUSPEND进入EL3ATF再让SCP接手内核冻结用户态进程、挂起设备、把DDR切到自刷新模式。AP侧各处理器核进入WFI等状态后最后活跃的核执行PSCI_SYSTEM_SUSPEND。ATF把系统状态交给SCPSCP执行深度睡眠序列关掉大部分PLL、断开DDR时钟或进入gear down、压低IO电平、让整个系统进入近零功耗状态。唤醒事件比如RTC闹钟、GPIO唤醒到达时SCP的唤醒逻辑先启动拉起时钟、恢复DDR、唤醒AP核整个过程对操作系统几乎是透明的。这个流程里最容易出问题的环节是唤醒源配置。很多板子suspend后一睡不醒就是某个唤醒源的寄存器被SCP在休眠时顺手关了或者唤醒中断被路由错了。我现在的习惯是先确认RTC唤醒一定work再往上加GPIO唤醒等复杂路径一步步排除。4.4 热管理SCP的紧急刹车热管理也是SCP服务的重要部分。SCP周期性读取温度传感器当温度超过阈值时会直接介入调低性能等级甚至强制触发系统shutdown。这套机制和Linux的thermal框架是协同关系Linux thermal governor负责常规降频SCP负责兜底。在调试时有个反直觉的点即使Linux的thermal框架完全没配SCP的兜底策略依然在工作。所以如果你发现频率莫名其妙被压低或者温度才60度就自动关机别急着查内核dts先看SCP固件里的温度阈值配置。5. 调试实战与踩坑记录5.1 常见问题速查表现象可能原因排查手段CPU hotplug失败CPU failed to come online唤醒地址错误、SCP电源域状态机卡住查看ATF日志确认CPU_ON参数查SCP日志看电源域是否在预期状态SCMI命令超时内核报timeout waiting for responseSCP固件卡死、共享内存不可达、MHU中断丢失检查SCP的日志输出用debugfs看共享内存内容确认门铃寄存器状态suspend后无法唤醒唤醒源配置丢失、SCP休眠序列漏配先用RTC唤醒做基线测试查SCP日志确认休眠时保留了哪些唤醒源高负载下随机重启电压参数不匹配、热阈值触发查SCP固件电压表和热阈值用trace event抓重启前的最后事件频率切换后系统不稳定DVFS切换顺序问题、PLL锁定时间不足检查SCP固件中frequency switch流程确认升压/升频时序5.2 调试手段与工具SCP调试靠三样东西日志、寄存器、trace。先说日志。SCP日志一般通过专用调试UART输出或者写到共享内存环形缓冲。开发板上通常能找到SCP console对应的串口波特率可能是115200或更高。拿到SCP日志非常关键它比内核日志早一个层级能看到内核看不到的电源域状态机变化。再说寄存器。MHU和共享内存的状态可以直接用devmem读。比如某个SCMI命令一直没响应可以先看MHU的发送寄存器和状态寄存器确认门铃有没有敲出去、有没有回音再看共享内存的前几个字节确认发送的消息头和SCP写回的响应头是什么token是否匹配。最后是trace。内核侧有/sys/kernel/tracing可以打开scmi相关的trace event看到每个SCMI命令的发送时间、协议ID、消息ID、token、耗时。这个在定位哪个命令特别慢的时候极其好用。5.3 我的一些实操心得第一调电源管理问题永远先建立链路视图。遇到一个低功耗bug先问自己这个问题发生在哪一层是Linux层cpuidle策略、设备suspend回调、ATF层PSCI实现、SCP层电源域状态机还是硬件层PMIC时序、上电斜坡。用排除法逐层定位不要想着一步到位。第二共享内存的读写一致性问题很隐蔽。虽然SCMI有内存屏障和握手机制但在某些缓存架构下AP和SCP对共享内存的可见性可能出现差异。如果你在调试中遇到偶发命令失败、重试就成功大概率是共享内存同步或者门铃时序问题而不是业务逻辑问题。第三版本管理要做好。SCP固件、ATF、内核三者的版本必须配套记录。很多开发板跑着看起来正常的系统其实SCP固件和内核SCMI驱动版本早已不匹配。我习惯在每次发布bsp时把三者的commit id写进一个release note出问题先对版本。结语前的一点经验这篇文章写到的每一个流程几乎都是我用实际板子一趟一趟调出来的。SCP这个物业管家看着不起眼但系统能不能睡、睡多久、醒不醒得过来全看它。我个人最大的体会是与其遇到bug再去翻spec不如先把PSCI和SCMI的协议框架装进脑子里知道消息从哪来、到哪去、在哪一层执行排查问题就是一路按图索骥的事。后面如果时间允许我打算再写一篇关于SCP固件里电源域状态机的实现细节那个比协议层的坑要多得多等实际操作再多积累一些素材再动笔。
返回列表