文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

ARM SCP入门-AP与SCP通信

2024-11-30 08:45

关注

1. SMC系统调用与PSCI协议

图片

    当Linux想要关机或者休眠的时候,这涉及到整个系统电源状态的变化,为了安全性Linux内核没有权利去直接执行了,需要陷入到EL3等级去执行,可以参考之前文章ARM ATF入门-安全固件软件介绍和代码运行,在EL3中处理的程序是BL31,把SMC系统调用的参数转化为PSCI协议去执行,这时如果有SCP那A核就憋屈了,自己没权利执行需要通过SCMI协议上报给SCP了。这就是整个过程的软件协议栈如上图中:

总结:用户进程 --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调用的时候的函数例如关机为:

#define PSCI_0_2_FN_BASE   0x84000000
#define PSCI_0_2_FN(n) (PSCI_0_2_FN_BASE + (n))
#define PSCI_0_2_FN_SYSTEM_OFF PSCI_0_2_FN(8)
static void psci_sys_poweroff(void)
{
  invoke_psci_fn(PSCI_0_2_FN_SYSTEM_OFF, 0, 0, 0);
}

PSCI_0_2_FN_SYSTEM_OFF的值计算为:0x84000000+8,在规范的表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)实现;在软件上,通过定义一组通信协议来实现。

主要涉及的模块如下:

    SCMI抽象出协议和传输两层,协议层描述能够支持的命令,传输层定义了命令通过什么方式传输,发送命令方称为agent。有个限制,每个agent的传输通道必须一个或者多个,然后如果有安全需求,那安全AP必须使用安全的通道进行传输数据。

图片

协议层:

从agent到platform的消息分为两种,同步和异步,为A2P通道:

SCMI协议的整体应用框图,从SCMI规范截图如下:

图片

scmi transport,channel,agent的对应关系:

1. 一个scp可以有多个agent,agent是运行在操作系统,安全固件的软件或者一个使用scmi协议的设备。例如juno有如下代理,0保留给平台。

enum  juno_scmi_agent_idx {
     
     JUNO_SCMI_AGENT_IDX_OSPM = 1,
     JUNO_SCMI_AGENT_IDX_PSCI,
     JUNO_SCMI_AGENT_IDX_COUNT,
 };

2. transport定义了scmi协议如何传输。比如shared memory。一个agent可以有多个A2P或P2A channel,channel是双向的,但是协议发起者(主)-接收者(从)关系是固定的。故若要使能通知功能,除了一个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_SUPPORTED

Delayed responses 类型都是2

Notifications 为3

传输层:

    传输层文档也就定义了一种方式,mailbox方式(核间通讯的一种ip)。这种通讯的前提是系统能够在agents和platform之间存在共享内存(ddr和片上flash都行,最好是片上flash)。mailebox能够完美支持前面提到的通道的需求,中断、内存和完成中断等都能够,而且是软件可操控。比如下面流程指出的中断和polling方式:

图片

mailbox通讯怎么定义在flash里面的layout:

图片

 3. Agent scmi消息处理流程

    这里我们以一个protocol_id为0x11的power domain控制消息为例子进行说明:

图片

scp中scmi消息处理时序图

1) mhu模块-中断产生:scmi底层硬件对应的模块是mhu模块,当硬件收到agent的消息时候会产生中断,中断处理函数为mhu_isr。在该函数中通过中断源查表获取对应的设备和smt channel。然后调用transport模块的api(调用transport_channel->api->signal_message(transport_channel->id);)发送消息。

2)transport模块-获取通道上下文:signal_message api中通过channel id获取channel上下文信息,检查通道是否ready和locked,调用scmi模块的api 处理(channel_ctx->scmi_api->signal_message(channel_ctx->scmi_service_id);)。

3) 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_api(msg_smt模块提供),然后读出scmi消息的头部(scmi_protocol_id、scmi_message_id、scmi_message_type、scmi_token)。

•然后通过get_agent_id(event->target_id, &agent_id)获取该scmi 协议的agent_id(OSPM、PSCI等),根据agent_id获取到agent_type(psci、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模块。

4)scmi_power_domain模块-解析scmi消息:.message_handle函数对消息进行检验,将进行权限判断,然后查表调用具体的消息处理函数handler_table[message_id](service_id, payload)。例如scmi_protocol_id为scmi_power_domain,scmi_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函数。

5)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中提供

6) 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;进行寄存器设置生效。

7) scmi_power_domain模块-返回结果:最后调用scmi_pd_ctx.scmi_api->respond(service_id, &return_values,....)到scmi 模块。

8) 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。

10.)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

描述

涉及模块及处理代码

0x10

Base protocol

module/scmi/src/mod_scmi_base.c

0x11

Power domain management protocol

module/scmi_power_domain/src/mod_scmi_power_domain.c

0x12

System power management protocol

module/scmi_system_power/src/mod_scmi_system_power.c

0x13

Performance domain management protocol

module/scmi_perf/src/mod_scmi_perf.c

0x14

Clock management protocol

module/scmi_clock/src/mod_scmi_clock.c

0x15

Sensor management protocol

module/scmi_sensor/src/mod_scmi_sensor.c

0x16

Reset domain management protocol

module/scmi_reset_domain/src/mod_scmi_reset_domain.c

0x17

Voltage domain management protocol

module/scmi_voltage_domain/src/mod_scmi_voltage_domain.c

0x18

Power capping and monitoring protocol

不支持

0x19

Pin Control protocol

不支持

4. PPU的电源控制

0x11

Power domain management protocol

module/scmi_power_domain/src/mod_scmi_power_domain.c

0x12

System power management protocol

module/scmi_system_power/src/mod_scmi_system_power.c

0x11 pd,0x12 system是通过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_ppu.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)) {
         
         while  (!set_power_status_check(¶ms)) {
             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支持的板子,把代码改一改应该也能运行起来,有兴趣的可以自己尝试下。

本文转载自微信公众号「OS与AUTOSAR研究」,可以通过以下二维码关注。转载本文请联系公众号。

来源:OS与AUTOSAR研究内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯