**SoC**上有很多核ATF和Linux占据了**A核**SCP占据了一个**M核**当遇到Linux没有权限的事情的时候**SMC**进入EL3转**PSCI**协议例如电源管理就需要给SCP**打报告**SCP审批完**批条子**后去执行。这其中涉及到了**异构核间通信**估计第一时间会想到**mailbox**不过mailbox算是一个**传输层**面向的是bit位数据的传输可以把这些传输数据组织成一个**协议层**在AP与SCP的核间通信中那就是**SCMI**。**1. SMC系统调用与PSCI协议**当Linux想要关机或者休眠的时候这涉及到整个系统电源状态的变化为了**安全性**Linux内核没有权利去直接执行了需要陷入到**EL3等级**去执行可以参考之前文章[ARM ATF入门-安全固件软件介绍和代码运行](http://mp.weixin.qq.com/s?__bizMzUzMDMwNTg2Nwmid2247484384idx1snc6a2c66b967a28f8f46430263bad7df6chksmfa5285c4cd250cd27a333f15bfcef80e8a8f92ac9afe8ac766f93e75a0dbc7500de2d4df0effscene21#wechat_redirect)在EL3中处理的程序是**BL31**把SMC系统调用的参数转化为PSCI协议去执行这时如果有SCP那A核就憋屈了自己没权利执行需要通过**SCMI**协议上报给SCP了。这就是整个过程的软件协议栈如上图中用户层首先用户发起的一些操作通过用户空间的各service处理会经过内核提供的sysfs操作cpu hotplug、device pm、EAS、IPA等。内核层在linux内核中EASenergy aware scheduling通过感知到当前的负载及相应的功耗经过cpu idle、cpu dvfs及调度选择idle等级、cpu频率及大核或者小核上运行。IPAintrlligent power allocation经过与EAS的交互做热相关的管理。ATF层Linux kernel中发起的操作会经过电源状态协调接口Power State Coordination Interface简称PSCI由操作系统无关的frameworkARM Trusted Firmware简称ATF做相关的处理后通过系统控制与管理接口System Control and Management Interface简称SCMI向系统控制处理器system control processor简称SCP发起低功耗操作。SCP****层SCP系统控制处理器system control processor最终会控制芯片上的sensor、clock、power domain、及板级的pmic做低功耗相关的处理。**总结**用户进程 --sysfs– 内核EAS、IPA–PSCI– ATF --SCMI–SCP --LPI– 功耗输出器件**1.1 SMC指令**上面看完有一个整体的认识下面进入正题先介绍下什么是SMC指令为什么走SMC就是**安全通道**Linux直接给SCP通信就是**非安全通道**这两种通道怎么去区分 首先看SMC规范ARM官方文档地址https://developer.arm.com/documentation/den0028/latest《DEN0028E_SMC_Calling_Convention_1.4》本文档定义了一种通用的调用机制可与Armv7和Armv8架构中的安全监视器调用SMC和系统监控程序调用HVC指令一起使用。SMC指令用于生成一个同步异常该异常由运行在EL3中的安全监视器代码处理。参数和返回值将在寄存器中传递。在由安全监视器处理之后由指令产生的调用可以传递到受信任的操作系统或安全软件堆栈中的其他实体。HVC指令用于生成由在**EL2**中运行的管理程序处理的同步异常。参数和返回值将在寄存器中传递。管理程序还可以捕获由客户操作系统在EL1发出的SMC调用这允许适当地模拟、传递或拒绝调用。 本规范旨在简化集成和减少软件层之间的碎片化例如操作系统、系统管理程序、受信任的操作系统、安全监视器和系统固件。具体的各种定义可以自己看手册我们在Linux代码中执行smc调用的时候的函数例如**关机**为#definePSCI_0_2_FN_BASE 0x84000000#definePSCI_0_2_FN(n)(PSCI_0_2_FN_BASE(n))#definePSCI_0_2_FN_SYSTEM_OFFPSCI_0_2_FN(8)staticvoidpsci_sys_poweroff(void){invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF,0,0,0);}PSCI_0_2_FN_SYSTEM_OFF的值计算为0x840000008,在规范的表6-2分配给不同服务的功能标识符的子范围中表中的各种功能就是走安全通道的不是SMC或者HVC命令的功能就是非安全通道的当然也可以根据自己的**需求**选择一般**PSCI协议**中的功能都是走安全通道。1.2 PSCI协议PSCI协议官方地址https://developer.arm.com/documentation/den0022/d/《Power_State_Coordination_Interface_PDD_v1_1_DEN0022D》本文档定义了一个**电源管理**的标准接口操作系统供应商可用于在ARM设备上使用不同特权级别的监控软件。该接口旨在在以下电源管理场景中代码通用化内核空闲管理。动态添加和删除核心以及辅助核心引导。系统关闭和复位。该接口不包括动态电压和频率缩放DVFS或设备电源管理例如对图形处理器等外设的管理。为什么需要PSCI具有电源管理感知的操作系统动态地改变核心的电源状态平衡可用的计算容量以匹配当前的工作负载同时努力使用最小的功率量。其中一些技术可以动态地打开和关闭内核或将它们置于静止状态在静止状态下它们不再执行计算。这意味着它们消耗的能量很少。这些技术的主要例子是空闲管理当操作系统中的内核在核心上没有线程可以调度时它会将该核心置于时钟门控、保留状态甚至是完全电源门控状态。然而该核心仍然可用于操作系统。热插拔当计算需求低时核心会物理关闭当需求增加时恢复在线。该操作系统将迁移所有远离离线的核心的中断和线程并在它们重新联机时重新平衡负载。具体包含那些功能可以自己去看规范文档这里截图算个记录比如关机就是5.10里面的内容。**2. SCMI协议**现在继续聊SCP里面的东西上来就是**SCMI协议**同样还是去ARM官网找《DEN0056B_System_Control_and_Management_Interface_v2_0》这个协议在哪里用到我们来看一个图SCP会以服务的方式来支持AP参与运行管理这也就需要SCP和AP之间有一个**通信****接口**。这个通信接口在硬件上可以通过共享存储和**MHU**Message Handling Unit实现在软件上通过定义一组通信协议来实现。主要涉及的模块如下mhu模块Message Handling Unit (MHU)在module/mhu/src/mod_mhu.c中实现msg_smt模块Shared Memory Transport 是一种用于描述系统内存拓扑的数据结构。在ARM 架构中SCP 固件使用 Shared Memory Transport来提供有关系统内存的信息如地址范围、类型、属性等。System Memory Tables 通常由系统固件在启动过程中生成并由SCP 固件和其他系统组件使用。它们允许系统软件了解和管理系统中可用的内存资源。SCMI模块System Control Management Interface (SCMI)业务处理模块为scmi protocol模块例如scmi_power_domainSCMI抽象出协议****和传输两层协议层描述能够支持的命令传输层定义了命令通过什么方式传输发送命令方称为agent。有个限制每个agent的传输通道必须一个或者多个然后如果有安全需求那安全AP必须使用安全的通道进行传输数据。协议层通道channel必须是分开独立的各个agent不能使用同一个。避免platform无法识别message对应方agent必须是独立的操作系统通道支持双向通讯另外也能够支持中断、polling两种方式让agent选择从agent到platform的消息分为两种同步和异步为A2P通道同步synchronousagent返回的时候对应的platform操作就已经完成了。platform返回操作结果命令也是通过agent到platform的通道同一个通道完成这些操作异步asynchronoous当platform完成后会发送 delayed response给到agent告知对方工作完成这是P2A通道。agent发送完消息后立马得到platform的返回然后释放通道继续做下一次传输SCMI协议的整体应用框图从SCMI规范截图如下scmi transport,channel,agent的对应关系一个scp可以有多个agent,agent是运行在操作系统安全固件的软件或者一个使用scmi协议的设备。例如juno有如下代理0保留给平台。enum juno_scmi_agent_idx {/* 0 is reserved for the platform */JUNO_SCMI_AGENT_IDX_OSPM 1,JUNO_SCMI_AGENT_IDX_PSCI,JUNO_SCMI_AGENT_IDX_COUNT,};2. transport定义了scmi协议如何传输。比如shared memory。一个agent可以有多个A2P或P2A channelchannel是双向的但是协议发起者主-接收者从关系是固定的。故若要使能通知功能除了一个A2P channel外还需要一个P2A channel分配给这个agent.SCMI协议的message header定义如下对应代码module/scmi/include/mod_scmi_std.h中定义[protocol_id][message id]message id是二级功能区分id算cmd例如设置状态、获取状态等具体操作。如果有新增的协议那里面0/1/2这三个message都必须按照协议走。[message type]Commands 的message type都是0。对于不支持的协议和message类型platform都要回复 NOT_SUPPORTEDDelayed responses 类型都是2Notifications 为3传输层传输层文档也就定义了一种方式**mailbox**方式核间通讯的一种ip。这种通讯的前提是系统能够在agents和platform之间存在**共享内存**ddr和片上flash都行最好是片上flash。mailebox能够完美支持前面提到的通道的需求中断、内存和完成中断等都能够而且是软件可操控。比如下面流程指出的中断和polling方式mailbox通讯怎么定义在flash里面的layout:3. Agent scmi消息处理流程这里我们以一个protocol\_id为**0x11**的**power domain**控制消息为例子进行说明scp中scmi消息处理时序图**mhu模块-**中断产生scmi底层硬件对应的模块是mhu模块当硬件收到agent的消息时候会产生中断中断处理函数为mhu_isr。在该函数中通过中断源查表获取对应的设备和smt channel。然后调用transport模块的api(调用transport_channel-api-signal_message(transport_channel-id);)发送消息。**transport模块-**获取通道上下文:signal_message api中通过channel id获取channel上下文信息检查通道是否ready和locked调用scmi模块的api 处理channel_ctx-scmi_api-signal_message(channel_ctx-scmi_service_id);。**scmi模块-**产生处理事件•scmi的api函数signal_message中将该消息封装成事件通过fwk_put_event发送一个fwk_event_light。事件中source_id为scmi模块.target_id 为上一级smt 中channel_ctx-scmi_service_id也是scmi。所以让该事件是自己发给自己的。因为event有队列中断调用的api是实时的。在scmi的.process_event回调函数中处理上面的事件。•首先通过scmi维护的scmi_ctx.service_ctx_table获取transport信息找到transport_apimsg_smt模块提供然后读出scmi消息的头部scmi_protocol_id、scmi_message_id、scmi_message_type、scmi_token。•然后通过get_agent_id(event-target_id, agent_id)获取该scmi 协议的agent_idOSPM、PSCI等根据agent_id获取到agent_typepsci、ospi等。•最后根据scmi_protocol_id找到protocol例如0x11是power domain处理调用protocol-message_handler(protocol-id, event-target_id,payload, payload_size, ctx-scmi_message_id)执行相对应的protocol的消息处理函数。message_handler函数执行到了scmi_power_domain模块。scmi_power_domain模块-解析scmi****消息.message_handle函数对消息进行检验将进行权限判断然后查表调用具体的消息处理函数handler_table[message_id](service_id, payload)。例如scmi_protocol_id为scmi_power_domainscmi_message_type为MOD_SCMI_PD_POWER_STATE_SET则处理函数为scmi_pd_power_state_set_handler。该函数中将会进行策略判断大多数模块为空然后调用scmi_pd_ctx.pd_api-set_state(pd_id, pd_power_state)进行power domain的set,pd_api对应power_domain模块中对外api函数。**power_domain模块-调用driver****处理**power_domain模块的api set_state函数先组装了一个event发给pd_id也就是自己。pd_process_event函数进行处理process_set_state_request按照pd的树形结构对状态进行设置然后调用initiate_power_state_transition执行status pd-driver_api-set_state(pd-driver_id, state);更新pd的状态并拿到执行结果status 。这里driver_api是在product/juno/scp_ramfw/config_power_domain.c的struct fwk_element element_table变量中定义可以看到为FWK_MODULE_IDX_JUNO_PPU中提供**juno_ppu模块-****寄存器设置**根据ppu_id拿到ppu的上下文ppu_ctx按照传入的state值on或者off执行status ppu_set_state_and_wait(ppu_ctx, mode);最后执行reg-POWER_POLICY (uint32_t)mode;进行寄存器设置生效。**scmi_power_domain模块-****返回结果**最后调用scmi_pd_ctx.scmi_api-respond(service_id, return_values,…)到scmi 模块。**scmi****模块**scmi中api的respond函数将会通过service_id查表service_ctx_table获取transport信息然后调用ctx-respond(ctx-transport_id, payload, size)为msg_smt模块中respond api()注transport_id在config_scmi.c 中配置。指定transport为smt模块smt内的具体channel element元素)。9.**transport****模块**msg_smt模块中的respond api为smt_respond函数。通过上一级传入的transport_id/channel_id的element_idx部分查表smt_ctx.channel_ctx_table获取channel消息。然后填充Shared Memory并调用channel_ctx-driver_api-raise_interrupt(channel_ctx-driver_id)产生中断通知agent。mhu****模块产生中断raise_interrupt函数中根据slot_id找到设备上下文然后对寄存器进行设置reg-SET | (1U slot);。从上面可以看到scmi的处理流程基本是通用的涉及到不同平台的就是最后硬件的设置需要新建一个**juno_ppu模块-**寄存器设置及其配置文件。SCP中scmi****协议处理:系统支持两种agent**PSCI**和**OSPM**发来的SCMI消息根据protocol\_id进行分类然后根据message\_id子命令找到合适的处理函数最后根据message\_type决定是否进行回复。 关于SCMI协议的一些参数定义可以参考代码module/scmi/include/mod_scmi_std.h例如上面我们介绍过0x11 power domain其他的处理过程相似可以通过下面表速查到相关模块从模块的static int (\*handler\_table中根据message\_id下标迅速找到处理函数protocol_id描述涉及模块及处理代码0x10Base protocolmodule/scmi/src/mod_scmi_base.c0x11Power domain management protocolmodule/scmi_power_domain/src/mod_scmi_power_domain.c0x12System power management protocolmodule/scmi_system_power/src/mod_scmi_system_power.c0x13Performance domain management protocolmodule/scmi_perf/src/mod_scmi_perf.c0x14Clock management protocolmodule/scmi_clock/src/mod_scmi_clock.c0x15Sensor management protocolmodule/scmi_sensor/src/mod_scmi_sensor.c0x16Reset domain management protocolmodule/scmi_reset_domain/src/mod_scmi_reset_domain.c0x17Voltage domain management protocolmodule/scmi_voltage_domain/src/mod_scmi_voltage_domain.c0x18Power capping and monitoring protocol不支持0x19Pin Control protocol不支持4. PPU的电源控制0x11Power domain management protocolmodule/scmi_power_domain/src/mod_scmi_power_domain.c0x12System power management protocolmodule/scmi_system_power/src/mod_scmi_system_power.c0x11pd0x12system是通过power domain模块然后到PPU模块进行电源控制的。关于PPU可以去PCSA规范中查看PPU是一个硬件模块SCP通过PPU去控制具体的时钟、电源等硬件。PPU类型如下所示enum mod_pd_type {MOD_PD_TYPE_CORE,MOD_PD_TYPE_CLUSTER,MOD_PD_TYPE_DEVICE,MOD_PD_TYPE_DEVICE_DEBUG,MOD_PD_TYPE_SYSTEM,MOD_PD_TYPE_COUNT};这里举例CPU COER****的电源硬件控制其他的自己看代码。MOD_PD_TYPE_CORE的处理api为core_pd_driver_api如下static const struct mod_pd_driver_api core_pd_driver_api {.set_state core_set_state,.get_state pd_get_state,.reset core_reset,.prepare_core_for_system_suspend core_prepare_core_for_system_suspend,};core_set_state****首先根据ppu_id拿到上下文参数config_juno_pp****u.c中定义然后根据要设置的state进行分开处理static int core_set_state(fwk_id_t ppu_id, unsigned int state) {get_ctx(ppu_id, ppu_ctx);dev_config ppu_ctx-config;mode pd_state_to_ppu_mode[state];switch ((enum mod_pd_state)state) {case MOD_PD_STATE_OFF://设置PPU状态并等待生效status ppu_set_state_and_wait(ppu_ctx, mode);//清空这个PPU对应的中断消息status clear_pending_wakeup_irq(dev_config);//关闭这个PPU对应的中断消息status disable_wakeup_irq(dev_config);//关闭软重启中断消息status fwk_interrupt_disable(dev_config-warm_reset_irq);break;case MOD_PD_STATE_SLEEP:status ppu_set_state_and_wait(ppu_ctx, mode);status clear_pending_wakeup_irq(dev_config);status enable_wakeup_irq(dev_config);status fwk_interrupt_disable(dev_config-warm_reset_irq);break;case MOD_PD_STATE_ON:status fwk_interrupt_clear_pending(dev_config-warm_reset_irq);status fwk_interrupt_enable(dev_config-warm_reset_irq);status ppu_set_state_and_wait(ppu_ctx, mode);break;default:fwk_unexpected();status FWK_E_PANIC;break;}//power_domain模块中api调用对这个pd进行订阅的模块会收到电源变化通知status ppu_ctx-pd_api-report_power_state_transition(ppu_ctx-bound_id,state);return FWK_SUCCESS;}·ppu_set_state_and_wait(ppu_ctx, mode);中设置PPU的mode首先mode的转化如下static enum ppu_mode pd_state_to_ppu_mode[] {[MOD_PD_STATE_OFF] PPU_MODE_OFF,[MOD_PD_STATE_SLEEP] PPU_MODE_OFF,[MOD_PD_STATE_ON] PPU_MODE_ON,[MOD_SYSTEM_POWER_POWER_STATE_SLEEP0] PPU_MODE_MEM_RET,};ppu_set_state_and_wait函数中对于mode的设置static int ppu_set_state_and_wait(struct ppu_ctx *ppu_ctx, enum ppu_mode mode){//对寄存器进行设置reg ppu_ctx-reg;reg-POWER_POLICY (uint32_t)mode;//根据配置信息等待PPU设置完成dev_config ppu_ctx-config;params.mode mode;params.reg reg;if (fwk_id_is_equal(dev_config-timer_id, FWK_ID_NONE)) {/* Wait for the PPU to set */while (!set_power_status_check(params)) {continue;}}对于中断的控制通过framework/src/fwk_interrupt.c中对外函数int fwk_interrupt_disable(unsigned int interrupt){if (!initialized) {return FWK_E_INIT;}return fwk_interrupt_driver-disable(interrupt);}fwk_interrupt_driver在arch/arm/arm-m/src/arch_nvic.c中实现static int disable(unsigned int interrupt){if (interrupt irq_count) {return FWK_E_PARAM;}NVIC_DisableIRQ((enum IRQn)interrupt);return FWK_SUCCESS;}__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn){if ((int32_t)(IRQn) 0){NVIC-ICER[(((uint32_t)IRQn) 5UL)] (uint32_t)(1UL (((uint32_t)IRQn) 0x1FUL));__DSB();__ISB();}}对硬件寄存器进行了设置。其他SCP入门系列就算讲完了**有规范有源码**有一点缺陷就是没用**qmeu运行**起来官方也没给出只说用ARM的Fixed Virtual Platform (**FVP**)能运行不熟悉操作起来估计有点费劲对PC要求也高这个SCP也比较小众在大规模的SoC上才有应用提出的挺早但是应用的还是不多。其实找一个qemu支持的板子把代码改一改应该也能运行起来有兴趣的可以自己尝试下。后记**英文规范****源码**才是**一手资料****看二手资料永远都跟不上别人**比如知乎、CSDN、公众号、bilibili等中文的总结文档甚至我这篇博客。为什么会这样因为英文规范很全面总结出来的二手中文文档只是翻译了其中一部分但是那个**写二手文档的人肯定把一手的都看了**所以你看二手的因为**不全**而永远落后别人**二手****好处就是入门快**要精通还是看一手的吧。 不过我这里尽量是简介和汇总文档而不是大篇幅的摘抄翻译让大家好找到出处**知道去看什么英文文档去哪里找**一般就是**ARM官网**本文的SMC、PSCI、SCMI或者**github**。搞一些有点技术含量的研发特别是靠近底层软件和芯片技术的**英文**是一道坎中国没有只能**学学老外5-10前**的技术已经算先进的了这些领域国内基本还是**海归**或者**外企**待过的人把持说话都夹杂着满嘴的**英文单词**和**行业术语缩写****不装逼还真不是一个level****的了**现在都是把电脑系统和常用软件都换英文显示的了努力看英文无障碍。“啥都懂一点啥都不精通干啥都能干干啥啥不是专业入门劝退堪称程序员杂家”。后续会继续更新纯干货分析无广告不打赏欢迎转载欢迎评论交流公众号“那路谈OS与SoC嵌入式软件”欢迎关注个人文章汇总https://thatway1989.github.io