ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ARM SoC电源管理核心SCP:原理、PSCI/SCMI协作与调试

ARM SoC电源管理核心SCP:原理、PSCI/SCMI协作与调试 我入行做嵌入式SoC平台软件那几年遇到过不少让人抓瞎的问题其中最典型的一个是AP这边明明已经执行了WFI电流波形还是掉不下来或者系统报告进入深度睡眠了结果一测温度反而还在慢慢往上爬。很多同事第一反应是查kernel的cpuidle配置把菜单翻来覆去改好几轮也没用。后来才明白真正卡住系统降功耗的往往是那颗藏在AP旁边的“小核心”——SCP。这篇文章就以ARMv9/v8为背景把SCP在电源管理里的工作原理完整梳理一遍包括它和AP、PSCI、SCMI之间的协作链路固件内部的运行机制以及我在实际项目里踩过的坑。ARMv8和ARMv9这两代架构在指令集层面上的差异对应用开发者来说可能不痛不痒但做到底层电源管理时你会发现SCP的职责边界一直在扩大。从早期的简单休眠控制到现在的DVFS、热管理、传感器汇聚、系统异常复位SCP几乎成了SoC上的“第二个大脑”。如果没有它AP的idle状态设计得再漂亮也只是空中楼阁。这篇文章适合BSP工程师、固件开发者、对低功耗设计感兴趣的底层软件同学也适合刚从单片机转过来、想搞懂SoC电源域管理的新人。1. SCP是什么一颗永远在线的“小管家”1.1 SCP的硬件定位与角色边界SCP的全称是System Control Processor在ARM SoC里通常是一个Cortex-M级别的核心比如Cortex-M3、Cortex-M7或者更小的M0当然不同厂商的实现各有差异。它和APApplication Processor最大的区别在于AP可以进入各种深度睡眠甚至完全掉电但SCP必须保证永远在线因为整个系统的电源状态切换、时钟启停、电压调节都依赖它来执行。这就好比一个大宅院的管家主人AP可以安心睡觉但管家得守着门房随时处理夜间访客、检查锅炉、调整室温。SCP本身也跑固件固件通常存放在SoC内部的一段SRAM里或者由AP侧在启动早期加载给SCP。它要管理的对象包括各个电源域power domain的上电下电、DVFS调频调压、时钟树中各路clock的开关、复位信号的产生与释放、热传感器数据的收集与温度控制策略等等。有些SoC还会让SCP承担secure boot相关的一部分职责或者作为系统watchdog的兜底这些都属于扩展功能。1.2 为什么必须由SCP来管电源而不是AP直接操作硬件很多人会问既然AP是主处理器性能强、跑着Linux为什么不能直接在kernel驱动里控制PMIC的稳压器和clock控制器答案其实很朴素AP会睡。你让一个已经深度睡眠的Cortex-A核心去执行“拉高某个GPIO”的指令它连取指都做不到。即便可以不真正关机只进入WFI状态可为了响应中断而频繁唤醒AP功耗也根本压不下来。所以必须有一颗独立的、状态机足够简单可靠的小核心专门待在低功耗后台等AP或者其它代理发来请求再去操作电源相关硬件。另外还有安全性和隔离方面的考虑。电源管理直接涉及芯片的物理状态如果普通世界的软件可以直接乱写电源寄存器一个漏洞就可能导致整机损坏或者被恶意关机。把硬件控制权收归SCPAP侧只通过一组明确定义的接口发请求本身就是一种权限收窄。这种设计思路跟用户态/内核态隔离有点像只不过这里的边界是物理上的多核隔离。1.3 SCP与AP的通信模型SCP和AP之间的通信有几个层次。最底层的是共享内存Shared Memory和门铃中断Doorbell InterruptAP往共享内存里写一份请求然后敲一下门铃触发一个硬件中断给SCPSCP读到请求后处理再把结果写回共享内存并通过另一个门铃中断通知AP。在此之上软件协议层面一般会定义消息格式ARM官方的标准协议是SCMISystem Control and Management Interface后面会详细讲。需要注意一点SCP侧没有MMU也不能跑完整的Linux它通常是一个裸机程序或者基于某个轻量RTOS的固件。因此跨核通信的代码必须写得极其小心缓存一致性、内存屏障、原子操作全都要手动管理。这个在调试的时候很容易出问题因为你在AP侧看到的数据可能因为cache没有回写而变得“不可见”。2. 电源管理的核心链路从OS到硬件的指挥链2.1 指令级入口WFI/WFE与idle状态在讲PSCI和SCMI之前先从最靠近CPU的一条路径说起。ARMv8/v9架构里CPU省电最基础的机制就是WFIWait For Interrupt和WFEWait For Event指令。执行WFI后当前核心会停止执行指令进入一种等待外部中断的状态功耗会明显下降但醒来延迟也很低。Linux kernel的cpuidle框架底层就是靠执行这两条指令来进入不同的C-state。当然WFI只能让单核进入浅睡眠。系统真正想进入更深层次的idle比如把整个cluster的电源都断掉那就不是WFI能搞定的了。这时候需要让AP侧的固件通常是ATF/Trusted Firmware-A调用PSCI协议里的CPU_SUSPEND接口把请求转给SCP去执行进一步的电源域下电。也就是说指令级WFI是“我自己睡着了”而PSCI是把“我要把我的家断电”这件事委托给管家。2.2 PSCI电源状态协调的标准协议PSCI全称是Power State Coordination Interface定义在ARM DEN 0022文档里。它的核心思想是建立一个通用的软件接口让OS能够请求电源状态转换而不需要关心底层SoC的具体寄存器。比如Linux里用到的psci_ops.cpu_suspend、psci_ops.cpu_on、psci_ops.system_reset最终都会通过smc指令陷入ATF再由ATF通过一定机制通常是一个SDEI中断或者一个共享内存消息请求SCP来执行实际的硬件操作。PSCI把电源状态分了几个层级core level、cluster level、system level。每个层级可以有不同的state ID由SoC厂商自定义。Linux的psci driver配合DTB里的arm,psci节点会在启动时探测支持哪些state并映射到cpuidle的深度。这里容易踩坑的地方在于state ID的定义必须跟SCP固件里的电源状态编号严格对应一旦两边不一致系统可能以为进入了深度睡眠实际上SCP只是把灯关了但风扇还在转。2.3 SCMISCP对外提供服务的标准消息协议如果说PSCI解决的是“CPU核怎么睡”那SCMI解决的就是“系统资源怎么管”。SCMI是ARM DEN 0056定义的协议它把SCP的服务分成若干protocolbase protocol、power domain management、clock management、performance management、sensor management、reset management等等。每个protocol里定义了相应的命令比如clk_set_rate、perf_limits_set、sensor_reading_get。SCMI在设计上做了几件聪明事第一消息格式是统一的头部有protocol id和message id方便扩展第二通道类型分两种——doorbell通知和delayed response前者用于异步触发后者用于需要SCP花时间处理的操作第三AP侧驱动可以通过scmi framework注册成一个标准的clock或者regulator或者cpufreq驱动对系统其它模块屏蔽掉底层细节。所以在Linux里看到scmi-cpufreq这个driver它本质上就是把cpufreq的调频请求翻译成一条SCMI的perf_level_set消息发给SCP。2.4 硬件层面电源域、时钟域与电压调节协议层的需求最终要落到硬件寄存器上。SCP固件里最核心的数据结构是电源域状态表每一行记录一个电源域的编号、当前状态ON/OFF/RET、受它影响的设备列表、以及上下电时涉及的延迟。SCP操作硬件时通常遵循一套固定序列先查表判断目标状态是否合法再处理依赖关系比如某个子域必须先关才能关父域然后写寄存器和PMIC的I2C/SPI接口最后等待硬件状态反馈更新软件状态机。这些硬件的操作很多具有极高风险性。比如DVFS调压时如果先降频率再升电压或者顺序反过来芯片都可能工作在超出规格的边界上导致系统不稳定甚至物理损坏。真正靠谱的固件实现里每一档电压和频率的配对关系都是经过验证写死在表里的SCP只管查表执行不做实时计算。3. SCP固件内部结构与工作流程3.1 固件启动流程SCP固件的入口一般是一段reset vector代码做的事情非常有限初始化基础时钟、关掉看门狗、设置栈指针、拷贝代码到SRAM如果需要、然后跳转到主函数。主函数里做几件事初始化串口如果有、初始化中断控制器、创建消息队列、注册各个protocol的处理函数然后进入主循环等待事件。以ARM官方提供的SCP固件框架SCP Firmware基于CMake构建为例它的启动过程还分两步首先运行的可能是ROM里的一段最小引导代码负责从外部存储加载真正的主固件主固件起来后再做完整初始化。这个分步设计是为了应对“SCP固件坏了”的场景总不能因为固件损坏就让整个设备变砖。SCP运行时的内存布局值得重点关注。因为SCP没有MMU所有地址都是物理地址代码、数据、堆栈、消息缓冲区、共享内存都需要在链接脚本里精确分配。尤其要注意共享内存区域的cache属性通常要配置成非cacheable或者使用clean-by-address操作否则跨核通信时效性和一致性都是问题。3.2 事件驱动模型与低功耗等待SCP固件的工作模式不是轮询而是事件驱动。主循环通常长这样等待下一个事件可能是中断、定时器、或者来自AP的消息事件来了之后根据类型分发到对应handler。这样设计最大的好处是多路请求可以串行化处理避免并发冲突也让SCP自己能在没事干的时候执行一条WFI把自己也维持在低功耗状态。但注意SCP的“低功耗”不能低到把自己关掉否则没有人来服务。所以绝大多数SoC的SCP都运行在always-on电源域里使用一颗独立的低功耗振荡器比如32kHz或者专门的FCLK作为时钟源。SCP自己的功耗通常在毫瓦级别和整个SoC可能几百毫瓦的漏电相比可以接受。3.3 典型消息流一次深度睡眠的完整旅行为了更直观地理解SCP的协同工作我来走一遍标准旅程。假设一个四核SoC跑着Linux用户拔掉电源系统空闲cpuidle governor决定把所有CPU都推进深度睡眠每个CPU核心在进入WFI前kernel通过PSCI_CPU_SUSPEND请求ATF把该核心的电源域关掉。这一步还会调用CPU_OFF把core上的代码执行权全部释放。最后一个活跃的CPU在进入系统级睡眠前会通过SCMI的system_power_state_set接口告诉SCP“我要让整个AP子系统都睡过去”。SCP收到消息后先核对系统里还有没有阻塞睡眠的条件比如某个DMA还在跑某个外设有pending的中断确认无碍后再执行一系列硬件操作逐级关闭peripheral的clock、切断cluster的电源域、降PMIC的电压。整个AP域的功耗降到接近0SCP自己继续运行等待唤醒事件。唤醒源可能是RTC定时器、PON按键、USB插入产生的always-on域中断。唤醒事件触发SCP中断SCP快速恢复AP域电源释放复位ATF接管启动流程最终回到Linux继续执行。这条链路里任何一环出了问题表现就是“睡不下去”或者“一睡不起”。我在实际项目中遇到最多的就是第3步里外设未清理干净导致睡眠失败这种问题往往要靠SCP侧打日志、逐一核对每个peripheral的状态寄存器才能定位。4. ARMv9/v8时代的SCP职责扩展4.1 ARMv9新增特性的影响ARMv9相对v8引入的显著变化包括SVE2扩展、内存标记MTE、机密计算架构CCA/RME等。这些特性的侧重点更多在计算、安全、虚拟化但间接影响到了SCP的固件设计。以RMERealm Management Extension为例它引入了Realm world这样一个新的安全状态系统里可能同时存在Normal world、Secure world和Realm world三种执行环境。SCP作为always-on核心有时候需要具备区分不同安全域请求的能力防止低权限的请求干扰到Realm的安全边界。MTE对SCP的影响更实际一些因为MTE要求内存操作打上标签做检查AP侧传给SCP的共享内存缓冲区是否满足MTE对齐要求、是否带标签这些在早期设计阶段就得考虑好。我在做新平台预研时发现如果AP侧映射共享内存时启用了特定MTE配置SCP侧裸机代码读写这块内存前需要做特殊处理否则可能读到异常数据。4.2 更多服务热管理、传感器汇聚与安全控制ARMv9时代SoC规模越来越大SCP承担的服务也越来越多。除了最基础的电源域和时钟管理现代SCP固件里通常还包括热管理Thermal ManagementSCP轮询温度传感器按照厂商定义的温度阈值动态调整频率或触发中断这个可以做到比AP侧软件更快速、更可靠。传感器管理Sensor ManagementSPI/I2C接口的温湿度计、心率传感器等数据由SCP统一采集AP可以sleep但SCP始终保持采集数据累积在共享内存里AP醒来后一次性读取。系统健壮性管理Watchdog/Error HandlingSCP可以监控AP死锁、异常复位等情况执行系统重启或者降级策略这比AP自己看自己更加客观。角色变了固件复杂度也水涨船高。以前SCP固件可能只有几千行代码现在动辄几万行、多个模块交织。维护难度也因此提升没有良好的模块化设计和日志体系遇到诡异问题基本只能盲调。5. 常见痛点与调试心得5.1 Linux侧SCP相关配置速查对于大多数BSP开发来说接触SCP最多的场景是配置设备树和拉日志。几个常用的查看手段我列个表关注点常用手段说明SCP固件版本scmi_info节点或SCP串口打印确认版本是否能和ATF/kernel对齐PSCI是否生效dmesgpsci: probing conduit没看到这个说明ATF没跑起来cpuidle状态映射/sys/devices/system/cpu/cpuidle/检查每个idle state的latency和residencySCMI通道状态cat /sys/kernel/debug/scmi内核开启debugfs后能看到通道统计DVFS调频日志echo 1 /sys/kernel/debug/dri...或ftrace抓cpufreq事件来跟踪调频频率一个容易忽略的配置点是kernel编译时没开CONFIG_ARM_SCMI_PROTOCOL导致明明SCP固件支持SCMI系统里却看不到对应驱动。这种问题查起来很耗时因为驱动不会报错只是没有注册设备你以为是设备树写错了其实kernel压根没编进来。5.2 实操经验SCP串口日志的有效利用SCP固件一般会预留一个串口打印调试日志。由于SCP固件本身小、轮询少串口日志的打印位置和格式有时会很简陋但关键时刻比任何工具都管用。我建议从项目初期就建立SCP日志规范每个模块的入口和出口都打一条低等级日志电源状态变化必须打外设异常必须打。这样AP侧报告“系统睡眠失败”时直接把SCP侧日志拉出来看哪一步卡住定位很快。实践中经常遇到的一个问题是“SCP串口没输出”。排查顺序检查SCP固件是否真的在跑用JTAG连上看看PC指针检查串口管脚配置检查波特率。还有一个容易犯的低级错误SCP串口和AP串口用了同一个调试终端两边交替打印互相干扰看起来就像系统错乱。5.3 典型故障睡眠唤醒异常排查思路我遇到过的一个真实case可以分享一下。一台平板设备上报“偶尔无法从睡眠中唤醒”屏幕点不亮电流处于低功耗状态。看过ATF日志、kernel日志都没有panic最后的突破口是SCP侧的log在唤醒事件到达后SCP开始恢复电源域但等待DDR电源稳定时超时了。原来是该平台的DDR调压由PMIC负责而SCP等待PMIC的PGPower Good信号时该信号在低温环境下延迟超过了固件超时阈值。这类问题说明了SCP固件里超时设计的重要性。SCP的很多操作是查状态寄存器或者等状态位的如果超时时间写得过短环境一变化比如温度、电压纹波就容易出问题写得过长又会导致睡眠请求迟迟不响应系统测得的睡眠延迟超标。常见的做法是至少留1.5倍典型时间余量并且在不同工艺角芯片上做验证。另外一个小技巧如果AP侧怀疑SCP固件有bug千万不要急着改SCP代码先在AP侧确认发出去的PSCI/SCMI消息参数是否合法。我有次发现kernel传了一个完全不存在的power domain ID给SCPSCP状态机里根本没有这个分支直接挂起结果所有人都以为SCP固件崩了。后来查了一圈才发现是device tree里一个数字写错了。6. 工具链与下一步学习建议6.1 常用调试工具组合SCP相关的调试工作手里有这几样东西基本就够了JTAG调试器连SCP的调试端口看寄存器、断点、单步。适合固件刚开发阶段CPU core级的调试。逻辑分析仪抓电源域控制信号的时序验证SCP固件输出的信号和板级实际响应是否一致。功耗分析仪测整机电流和电压用来验证系统级睡眠功耗是否达到设计目标。串口/加密狗拉日志看状态机转换。如果是ARM官方SCP固件开源框架还可以用它自带的单元测试框架在PC平台上跑模拟测试不用下板就能验证部分逻辑。对于快速定位问题很有帮助。6.2 从入门到实战的学习路径如果你刚接触这个方向我的建议是先吃透概念再动手实践。概念方面把ARM DEN 0022PSCI和ARM DEN 0056SCMI通读一遍这两份文档虽然官方味很浓但确实是最权威的。再配合Linux kernel里drivers/firmware/arm_scmi和drivers/cpuidle的源码阅读理解软件侧如何调用协议。然后找一块支持SCP的开发板比如某些基于ARMv8的评估板把官方SCP固件编译并烧录进去自己动手尝试在SCP侧加一个定时的开关电源域逻辑观察AP侧的反应。这个过程虽然简单但能把“消息怎么从AP到SCP”“SCP怎么处理再写寄存器”整个链路打通。最后再逐步深入DVFS、热管理等复杂度更高的模块。6.3 我做技术选型时的判断标准说了这么多最后讲讲我对SCP技术栈的个人判断。如果你所在的团队只是做上层BSP不碰芯片底层那SCP相关知识更多是“遇到问题时需要具备的诊断能力”未必需要完整掌握固件开发。但如果你参与的是新平台bring-up那SCP固件就是必选项而且建议尽早介入因为等系统跑起来才发现电源链路有问题返工成本会非常大。我的经验是SCP相关工作的核心价值在于“跨层沟通”。电源管理的每个现象背后都可能横跨kernel、ATF、SCP固件、PMIC硬件四个层面。懂一点SCP不是为了把每一层都精通而是当系统功耗表现异常时你能在正确的时间、给正确的层发出正确的诊断请求。这个能力比单纯多会写几个驱动更有价值。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进