ARM SCP-firmware 代码解析

本文文档链接:

ARMscp代码解析+参考资料-C文档类资源-CSDN下载1.scp代码结构1.1scp目录结构2scpmodule2.1.所有module信更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/ty1121466568/85932181

目录

1.scp 代码结构

1.1 scp 目录结构

2 scp module

2.1.所有module信息的维护

2.2.module功能的提供

2.2.1 module的绑定

3.scp 应用初始化及boot流程

3.1 scp 应用初始化流程

3.2 scp boot

3.2.1 ARM Trusted Firmware

3.2.2 scp firmware boot 流程

4.scmi 消息接收、处理、执行流程

4.1 agent到platform的消息流程

4.2 scmi-smt配置绑定

5.通知产生

5.1 notification接口

5.2 notification流程

5.3 scmi notification

6.scp拓扑结构

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


1.scp 代码结构

1.1 scp 目录结构

scp 代码结构在文档 doc/framework.md 中有描述。

scp 目录结构如下:

├── arch

├── cmake

├── contrib

├── debugger

├── doc

├── docker

├── framework

├── module

├── product

└── tools

doc:项目文档

framework:存放scp架构的定义和实现,是scp 的代码的主干。

module:存放scp的各个通用功能模块的代码,例如power_domain, smt,scmi,scmi_power_domain,scmi_reset_domain,scmi_sensordeng,scmi_system_power,scmi_voltage_domain等。

product:

a.存放具体的产品代码,每个具体product中目录如下:

root/product/productname/

├── include

├── module

├── firmware_a       //eg:mcp_ramfw_fvp

├── firmware_b       //eg:mcp_romfw

├── firmware_c       //eg:scp_ramfw_fvp

└── src

b. product中module/目录结构和根目录下的一致,但是是该product的私有模块。编译哪些模块由firmware_x/firmware.mk 文件中的BS_FIRMWARE_MODULES变量指定module的文件夹名或由firmware_x/Firmware.cmake文件指定。注:root/product/productname/module也可重新实现root/module中已实现的模块,只需要修改productname/module/下的该文件夹名即可,例root/product/morello/morello_smt/和root/product/smt/。

tools:存放编译脚本等工具

2 scp module

2.1.所有module信息的维护

fwk_module_idx.h中包含了所有模块的枚举,定义如下:

enum fwk_module_idx {

    FWK_MODULE_IDX_TEST0,

    FWK_MODULE_IDX_TEST1,

    FWK_MODULE_IDX_TEST2,

    FWK_MODULE_IDX_COUNT,

};

static const fwk_id_t fwk_module_id_test0 =

    FWK_ID_MODULE_INIT(FWK_MODULE_IDX_TEST0);

static const fwk_id_t fwk_module_id_test1 =

    FWK_ID_MODULE_INIT(FWK_MODULE_IDX_TEST1);

static const fwk_id_t fwk_module_id_test2 =

    FWK_ID_MODULE_INIT(FWK_MODULE_IDX_TEST2);

注:fwk_id_t = FWK_ID_MODULE(fwk_module_idx),FWK_ID_MODULE为转换id的宏定义

fwk_module_list.c 中包含了所有模块的结构体表及其配置结构体表。定义如下:

const struct fwk_module *module_table[FWK_MODULE_IDX_COUNT] = {

@SCP_MODULE_GEN@

};

const struct fwk_module_config *module_config_table[FWK_MODULE_IDX_COUNT] = {

@SCP_MODULE_CONFIG_GEN@

};

fwk_module_idx.h、fwk_module_list.c为自动生成的文件。其中

Makefile 中BS_FIRMWARE_MODULES变量经过处理变为FIRMWARE_MODULES_LIST,并入传入到gen_module_code.py脚本自动生成文件.

2.2.module功能的提供

在scp代码中,所有的功能都由一个个模块提供。每个模块以api枚举及其结构体的方式对外提供该模块的功能,,并在模块通用结构体fwk_module中提供.init(模块初始化)、.bind(该模块获取并绑定其依赖模块的api)、process_bind_request(该模块被其他模块依赖的api的获取并绑定请求函数)等通用接口。

2.2.1 module的绑定

模块在初始化时由fwk_module.c 调用回调函数.init,.bind,在该模块bind依赖模块api时,调用fwk_module_bind函数来实现。fwk_module_bind调用依赖模块提供的process_bind_request函数来获取依赖模块的api,并绑定。由此实现a模块bind b模块的api。

一个模块依赖关系例下:

mod_scmi_power_domain.c/scmi_api------>mod_scmi.c/FWK_MODULE_IDX_SCMI,MOD_SCMI_API_IDX_PROTOCOL

mod_scmi_power_domain.c/pd_api-------->mod_power_domain.c/fwk_module_id_power_domain,mod_pd_api_id_restricted

mod_scmi.c/transport_api------------------->mod_xxx.c/ctx->config->transport_id,ctx->config->transport_api_id        //eg:mod_smt

mod_smt.c/driver_api------------------------>mod_xxx.c/channel_ctx->config->driver_id,channel_ctx->config->driver_api_id 

其中mod_scmi_power_domain处理scmi 中power_domain protocol消息,并通过pd_api调用mod_power_domain来执行该protocol;通过调用scmi_api调用mod_scmi.c来回复该protocol;

mod_scmi.c通过transport_api调用mod_smt中的transport相关功能来完成scmi协议的transport层(scmi 数据收发及解析);

mod_smt.c/driver_api调用scmi更下一级的channel来产生中断(scmi 消息通知中断产生和处理)。

注:smt: Shared Memory Transport

3.scp 应用初始化及boot流程

3.1 scp 应用初始化流程

scp 入口及应用函数为:fwk_arch_init,包含如下内容:

fwk_module_init();                                    //scp 框架初始化;

status = fwk_io_init();

status = fwk_log_init();

/* Initialize interrupt management */

status = fwk_arch_interrupt_init(driver->interrupt);  //中断初始化

status = fwk_module_start();                          //模块初始化,开始任务

其中,fwk_module_init完成module_table、module_config_table所有模块信息的初始化

fwk_module_start完成所有模块的初始化工作:

  1. 初始化模块元素上下文(element_ctxs)。调用模块的config->elements.generator,获取element信息,加入模块上下文表
  2. 调用模块.init 回调函数,传入element_count,config->dat
  3. 初始化模块元素(element),调用模块回调函数.element_init将模块element->data配置信息导入到模块内部
  4. 调用模块.bind回调函数完成所有模块的绑定。(此处共进行两轮调用fwk_module_bind_module(round=0 1),每轮都将分别绑定模块module和模块的元素element)
  5. 调用模块.start回调函数
  6. 进入scp应用(while(1) 轮询是否有中断消息,并处理)

3.2 scp boot

3.2.1 ARM Trusted Firmware

scp firmware的加载及boot 由arm Arm Trusted Firmware-A (TF-A)来完成。TF(Trusted Firmware)是ARM在Armv8引入的安全解决方案,为安全提供了整体解决方案。它包括启动和运行过程中的特权级划分,对Armv7中的TrustZone(TZ)进行了提高,补充了启动过程信任链的传导,细化了运行过程的特权级区间。TF实际有两种Profile,对ARM Profile A的CPU应用TF-A,对ARM Profile M的CPU应用TF-M。我们一般接触的都是TF-A,又因为这个概念是ARM提出的,有时候也缩写做ATF(ARM Trusted Firmware),所以本文对ATF和TF-A不再做特殊说明,ATF也是TF-A。

ATF启动流程分为BL1、BL2、BL31、BL32、BL33。详细如下图所示:

其中,scp firmware 在BL2中加载。

3.2.2 scp firmware boot 流程

对于一个scp 代码中product的firmware,有romfw,和ramfw。其中romfw主要是用于加载ramfw,并跳转执行。ramfw为scp主要应用代码,提升scp各种服务(例scmi)。

  • scp_romfw.bin 在TF-A别名为 scp_bl1
  • scp_ramfw.bin 在TF-A别名为 scp_bl2

scp_bl1用于加载和执行scp_bl2。

以juno和morello为例,其目录结构如下:

product/juno/

├── include

├── module

├── scp_ramfw

├── scp_romfw

│   ├── config_bootloader.c

│   ├── config_clock.c

│   ├── config_juno_ppu.c

│   ├── config_juno_rom.c

│   ├── config_juno_soc_clock.c

│   ├── config_pl011.c

│   ├── config_sds.c

│   ├── config_timer.c

|   └──...

├── scp_romfw_bypass

└── src

product/morello/

├── include

├── mcp_ramfw_fvp

├── mcp_romfw

├── module

├── scp_ramfw_fvp

├── scp_romfw

│   ├── config_clock.c

│   ├── config_morello_rom.c

│   ├── config_pl011.c

│   ├── Firmware.cmake

│   ├── firmware.mk

│   ├── fmw_cmsis.h

│   ├── fmw_memory.h

│   └──...

└── src

从目录结构中可以看出,其romfw的应用config较少,其主要目的服务于加载运行ramfw。

scp boot 流程如下:

  1. ' SCP_BL1'是复位时在SCP上运行的ROM固件映像。可以和ATF的AP_BL1 and AP_BL2同时运行。
  2. scp_romfw中运行其scp firmware代码。执行3.1 scp 应用初始化流程的初始流程,在这过程中,调用<product>_rom模块的.start回调函数
  • 在morello_rom的.start回调函数函数中,发送event到本模块,在相应事件回调函数中,从flash拷贝scp_ramfw的代码到config_morello_rom.c中配置的地址,并跳转执行;
  • 在juno_rom的.start回调函数函数中,通过event和notification机制,到达juno_rom模块的相应回调函数,在juno_rom中,通过ctx.bootloader_api->load_image()调用mod_bootloader的api,从安全内存拷贝到指定位置,在该bootloader 模块api中加载跳转scp_ramfw。(注mod_bootloader_boot为汇编实现,依赖arm指令)。
  1. scp romfw 跳转执行ramfw。

在morello中,ATF该平台代码没有BL2实现,故ramfw直接从flash拷贝scp_ramfw的代码到指定位置.

在juno中,ATF的BL2中通过SDS(Shared-Data-Structure,在Juno平台中替代之前Boot-Over_MHU (BOM)协议)和SCP的romfw通信(mod_bootloader调用sds API),发送ramfw到安全内存,然后再由romfw从安全内存加载到其他位置并执行。

ATF中,BL2的编译选项在如下位置定义plat/arm/board/juno/platform.mk中的BL2_SOURCES,morello ATF plat/arm/board/morello/platform.mk中未定义BL2_SOURCES,不包含BL2.是否包含BL2相关通用代码及配置由CSS_LOAD_SCP_IMAGES决定

老的Boot-Over_MHU (BOM)传输流程图如下:(SDS的启动传输流程类似,可参考下图)

4.scmi 消息接收、处理、执行流程

4.1 agent到platform的消息流程

  1. scmi 底层对应的硬件收到agent的消息,产生中断,调用smt_channel->api->signal_message函数,传入channel 的id,发送消息。

            例mhu中的中断处理函数mhu_isr。在该函数中通过中断源查表获取对应的设备和smt channel。然后调用smt模块的api(mod_smt_driver_input_api的signal_message)发送消息。

  1. smt signal_message中通过channel id获取channel 上下文信息,比如smt的mailbox_address。然后判断消息是否合法,是否是scmi通道(channel_ctx->is_scmi_channe)。然后调用scmi模块的api 进行下一步处理(channel_ctx->smt_signal.scmi_api->signal_message)。
  2. scmi signal_message中将该消息封装成事件,通过fwk_thread_put_event发送。(事件中.target_id = service_id,而service_id为上一级smt 中channel_ctx->service_id,为scmi模块的模块向smt模块注册的module_idx+element_idx(用于标识scmi模块+scmi内的element)。让该事件发送给scmi模块自身)。
  3. 在scmi的.process_event回调函数中处理上面的事件。通过scmi维护的service_ctx_table获取transport信息,通过get_agent_id(event->target_id, &agent_id)获取该scmi 协议的agent_id(也是通过service_ctx_table获取),调用protocol->message_handler(protocol->id, event->target_id,payload, payload_size, ctx->scmi_message_id)执行相对应的protocol的消息处理函数。其中agent_id作为message_handler的service_id,用于标识agent。
  4. 例如protocol为scmi_power_domain,message为MOD_SCMI_PD_POWER_STATE_SET。 .message_handle回调函数将进行权限判断,然后查表调用具体的消息处理函数handler_table[message_id](service_id, payload)--->scmi_pd_power_state_set_handler。该函数中将会进行策略判断(大多数模块为空),然后调用scmi_pd_ctx.pd_api->set_state(pd_id, pd_power_state)进行power domain 的set,最后调用scmi_pd_ctx.scmi_api->respond(service_id, &return_values,....)到scmi 模块。
  5. scmi中api的respond函数将 会通过service_id查表service_ctx_table获取transport信息,然后调用smt 中respond api(ctx->respond(ctx->transport_id, payload, size))(注transport_id在config_scmi.c 中配置。指定transport为smt模块+smt内的具体channel element元素))。
  6. smt 中的respond api通过上一级传入的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。

4.2 scmi-smt配置绑定

scmi到smt底层channel 绑定关系如下(以product juno为例):

1.  config_scmi.c 中配置scmi的元素表,指定每个元素的transport_id,transport_api_id,scmi_agent_id,完成scmi模块中对transport的依赖配置

static const struct fwk_element element_table[] = {

    [JUNO_SCMI_SERVICE_IDX_PSCI_A2P] = {

        .name = "PSCI",

        .data = &(struct mod_scmi_service_config) {

            .transport_id = FWK_ID_ELEMENT_INIT(

                FWK_MODULE_IDX_SMT,

                JUNO_SCMI_SERVICE_IDX_PSCI_A2P),

            .transport_api_id = FWK_ID_API_INIT(

                FWK_MODULE_IDX_SMT,

                MOD_SMT_API_IDX_SCMI_TRANSPORT),

            .transport_notification_init_id =

                FWK_ID_NOTIFICATION_INIT(FWK_MODULE_IDX_SMT,

                    MOD_SMT_NOTIFICATION_IDX_INITIALIZED),

            .scmi_agent_id = (unsigned int) JUNO_SCMI_AGENT_IDX_PSCI,

            .scmi_p2a_id = FWK_ID_NONE_INIT,

        },

    },

....

}

2.  config_smt.c 中配置smt的元素表,每个元素为一个smt channel。指定其type(master/slave),policies,mailbox_address,mailbox_size,driver_id,driver_api_id,pd_source_id。完成smt自身和对mhu依赖的配置

static const struct fwk_element element_table[] = {

    [JUNO_SCMI_SERVICE_IDX_PSCI_A2P] = {

        .name = "",

        .data = &(struct mod_smt_channel_config) {

            .type = MOD_SMT_CHANNEL_TYPE_SLAVE,

            .policies = (MOD_SMT_POLICY_INIT_MAILBOX | MOD_SMT_POLICY_SECURE),

            .mailbox_address = (uintptr_t)SCMI_PAYLOAD_S_A2P_BASE,

            .mailbox_size = SCMI_PAYLOAD_SIZE,

            .driver_id = FWK_ID_SUB_ELEMENT_INIT(

                FWK_MODULE_IDX_MHU,

                JUNO_MHU_DEVICE_IDX_S,

                0),

            .driver_api_id = FWK_ID_API_INIT(FWK_MODULE_IDX_MHU, 0),

            .pd_source_id = FWK_ID_ELEMENT_INIT(

                FWK_MODULE_IDX_POWER_DOMAIN,

                POWER_DOMAIN_IDX_SYSTOP),

        }

    },

....

}

3.  在round == 0的scmi_power_domain模块的.bind回调函数中。调用fwk_module_bind(FWK_ID_MODULE(FWK_MODULE_IDX_SCMI),FWK_ID_API(FWK_MODULE_IDX_SCMI, MOD_SCMI_API_IDX_PROTOCOL),&scmi_pd_ctx.scmi_api)绑定scmi api;调用fwk_module_bind(fwk_module_id_power_domain, mod_pd_api_id_restricted,&scmi_pd_ctx.pd_api) 绑定power domain 的api。

scmi 的.process_bind_request回调函数中,会对scmi的protocol_table的id号进行维护,并将全局的scmi_ctx.protocol_count++。scmi_ctx.protocol_table[PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT + scmi_ctx.protocol_count++].id = source_id;

4.    在round == 0的scmi模块的.bind回调函数中。对每个scmi模块config_scmi.c中配置的elemen(对应一个smt的transport)t执行.bind函数,进而调用fwk_module_bind(ctx->config->transport_id,ctx->config->transport_api_id, &transport_api)绑定api;scmi模块的.bind回调函数中也会对每个在上一步注册的protocol_table的id号依次进行protocol_api的获取。(protocol->message_handler = protocol_api->message_handler;)。

fwk_module_bind将调用smt的回调函数.process_bind_request。smt的.process_bind_request回调函数中配置smt 的channel_ctx->service_id为scmi模块module_idx+element_idx(用于标识scmi模块+scmi内的element)。(在下一轮执行smt的.bind回调函数时,会判断该id是否是scmi 模块,并赋值给channel_ctx->is_scmi_channel = true)。

5.  在round == 0的smt模块的.bind回调函数中。该函数对每个smt channel调用fwk_module_bind(channel_ctx->config->driver_id,channel_ctx->config->driver_api_id, &channel_ctx->driver_api)绑定mhu的api。

fwk_module_bind将调用mhu的回调函数.process_bind_request。在该函数中配置device_ctx->smt_channel_table[slot].id 为smt模块id。

6.    在round == 1的mhu模块的.bind回调函数中。该函数通过fwk_module_bind(smt_channel->id,FWK_ID_API(FWK_MODULE_IDX_SMT, MOD_SMT_API_IDX_DRIVER_INPUT),&smt_channel->api)获取smt 模块的消息处理api,以便在中断中使用channel_ctx->smt_signal.scmi_api->signal_message处理scmi消息。

5.通知产生

5.1 notification接口

fwk_notification_subscribe

订阅指定模块指定通知

fwk_notification_unsubscribe

取消订阅通知

fwk_notification_notify

向订阅该通知的模块发送通知

5.2 notification流程

fwk_notification_notify函数调用send_notifications发送通知

send_notifications函数为每个订阅该通知的模块生成通知event,放入scp 全局event_queue/isr_event_queue(isr_event_queue最终会在scp 任务处理中放入到event_queue中)

scp 任务处理循环中调用event_queue目标模块的process_notification回调函数

5.3 scmi notification

由编译宏BUILD_HAS_SCMI_NOTIFICATIONS打开,各个scmi protocol 模块发送通知是通过调用scmi 的api scmi_notification_notify完成发送的。

scmi 通知,可通过notification机制到对应scmi protocol的process_notification函数发送通知。或者该scmi protocol有scmi 消息的set/change 等操作时自行发送通知

例:

1.module_reset_domain(.process_event)------>module_scmi_reset_domain(.process_notification)

注:module_reset_domain process_event的事件产生路径如下例:module_scmi_reset_domain(scmi_reset_mod_scmi_to_protocol_api的message_handler)---->mod_reset_domain(driver_api->set_reset_state)---->module_juno_reset_domain(juno_reset_domain_drv_api的set_reset_state产生事件)

2.module_scmi_power_domain 的debug 模式(BUILD_HAS_MOD_DEBUG),或scmi_pd_mod_scmi_to_protocol_api的message_handler

6.scp拓扑结构

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

1.一个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 channel,channel是双向的,但是协议发起者(主)-接收者(从)关系是固定的。故若要使能通知功能,除了一个A2P channel外,还需要一个P2A channel分配给这个agent.

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值