0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

ARM SCP入门-framework框架代码分析

yzcdx 来源:OS与AUTOSAR研究 2023-07-03 09:32 次阅读

一套软硬件跑起来的样子就像上面图里面的一样,it works。对应我们的SCP固件中,有那些框架来支撑这个系统运行起来,这里就需要一套基于M核或者单片机的通用框架程序,市面上的这种系统并不少见,例如freeRTOS等。

为了强调安全、简单等特性,适配ARM控制系统固件,ARM又搞了这套通用的框架,适合在M核或者R核上工作,甚至A核的某些特权系统例如OPTEE中。安全的核心就是隔离,隔离就是按功能形成module或者domain,模块之间禁止无权限的访问

1. module介绍

SCP的每个功能都实现为一个单独的module,module间耦合尽量低,确保安全特性,通常固件所需的整体功能应来自模块之间的交互。module间隔离就像上图中的狗咬架,一旦伸手产生交互就祸福不能预测了,所以加上栏杆规定好那些module间可以交互伸手,这都是通过API函数实现的,在系统初始化的时候设定死,下面模块间绑定章节会讲到。

SCP中的module分为两部分:在代码根目录module文件夹下,共77个公共模块,另外每个产品下面还有module,小100个可真不少。

240b4608-192e-11ee-962d-dac502259ad0.png

一个固件只包含一部分module,在Firmware.cmake中定义,gen_module_code.py脚本生成源码

这些module在framework启动时候初始化启动运行。

公共的module比较有通用性,产品自己的module一般是驱动需要进行定制

模块类型及软件协议栈:

2445db74-192e-11ee-962d-dac502259ad0.png

这个协议栈就是SCP软件跟外界交互的流程,一般消息都是通过驱动-》HAL层上来,然后处理的过程就是服务-》协议-》HAL-》驱动再操作硬件做出反应,这次交互就算结束了。具体如下:

2475854a-192e-11ee-962d-dac502259ad0.png

2.framework框架流程

2492c20e-192e-11ee-962d-dac502259ad0.png

framework框架负责固件的通用流程实现,包括系统初始化,module初始化,中断服务提供,event服务提供等。这样module就可以专注于自己功能和对外交互api的实现。SCP framework初始化流程图如下:

251ddaa6-192e-11ee-962d-dac502259ad0.png

备注:这里的framework框架流程适用于scp_romfwscp_ramfw,两者区别只是包含module不同,定义包含了那些module在其目录下的Firmware.cmake文件中。

编译过程中gen_module_code.py脚本会生成module信息和配置信息的代码,过程如下:SCP/MCP 软件构建系统由一个顶级 Makefile :Makefile.cmake和一组 .mk 文件组成,例如juno产品product/juno/product.mk

BS_PRODUCT_NAME := juno

BS_FIRMWARE_LIST := scp_romfw
scp_romfw_bypass
scp_ramfw

模块可以在项目根目录的 modules/ 目录下实现,也可以是产品特定的并在product//modules 目录下实现。

gen_module_code.py脚本会根据

product/juno/scp_romfw/Firmware.cmake中SCP_MODULES变量

list(APPEND SCP_MODULES "juno-ppu")
list(APPEND SCP_MODULES "juno-rom")
list(APPEND SCP_MODULES "juno-soc-clock")
list(APPEND SCP_MODULES "clock")
list(APPEND SCP_MODULES "gtimer")
list(APPEND SCP_MODULES "sds")
list(APPEND SCP_MODULES "bootloader")

生成

fwk_module_idx.h:包含构成固件的模块索引的枚举。fwk_module_idx.h中枚举中模块索引的顺序保证遵循固件firmware.mk 文件中 BS_FIRMWARE_MODULES列表中模块名称的顺序。当执行涉及迭代固件中存在的所有模块的操作时,框架在运行时使用相同的顺序,例如 fwk_module.c 中的 init_modules() 函数。

enum fwk_module_idx {
FWK_MODULE_IDX_JUNO_PPU = 0,
FWK_MODULE_IDX_JUNO_ROM = 1,
FWK_MODULE_IDX_JUNO_SOC_CLOCK = 2,
FWK_MODULE_IDX_CLOCK = 3,
FWK_MODULE_IDX_GTIMER = 4,
FWK_MODULE_IDX_SDS = 5,
FWK_MODULE_IDX_BOOTLOADER = 6,
FWK_MODULE_IDX_COUNT = 7,
};

fwk_module_list.c:包含一个指向模块描述符的指针表,每个模块对应一个作为固件一部分构建的模块。该文件及其内容由框架内部使用,通常不应由其他单元(如模块)使用。

const struct fwk_module *module_table[FWK_MODULE_IDX_COUNT] = {
&module_juno_ppu,
&module_juno_rom,
&module_juno_soc_clock,
&module_clock,
&module_gtimer,
&module_sds,
&module_bootloader,

};
const struct fwk_module_config *module_config_table[FWK_MODULE_IDX_COUNT] = {
&config_juno_ppu,
&config_juno_rom,
&config_juno_soc_clock,
&config_clock,
&config_gtimer,
&config_sds,
&config_bootloader,
};

module_tablemodule_config_table用于模块初始化。

固件的Firmware.cmake文件中可以对配置开关进行声明,例如:

set(SCP_FIRMWARE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}")

set(SCP_GENERATE_FLAT_BINARY TRUE)
set(SCP_ARCHITECTURE "arm-m")

framework.md-固件相关配置文件说明

产品始终包含定义一个或多个固件目标的product.mk文件。

在一个产品中,总会有至少一个固件。

对于每个固件,必须在fmw_memory.h文件中提供链接器信息,例如product/juno/scp_romfw/fmw_memory.h中:

#define FMW_MEM_MODE ARCH_MEM_MODE_DUAL_REGION_RELOCATION

/* ROM */
#define FMW_MEM0_SIZE SCP_ROM_SIZE
#define FMW_MEM0_BASE SCP_ROM_BASE

/* RAM */
#define FMW_MEM1_SIZE (16 * 1024)
#define FMW_MEM1_BASE (SCP_RAM_BASE + SCP_RAM_SIZE - FMW_MEM1_SIZE)

如果使用双区域内存配置,则还必须定义FMW_MEM1_BASE和 FMW_MEM1_SIZE 。

Toolchain-*.cmake 中定义image的体系结构目标

product/juno/scp_romfw/Toolchain-GNU.cmake

set(CMAKE_SYSTEM_PROCESSOR "cortex-m3")
set(CMAKE_TOOLCHAIN_PREFIX "arm-none-eabi-")
...

product/juno/scp_romfw/CMakeLists.txt 定义编译范围和目标,cmake会使用生成makefile文件。

编译选项说明:

cmake_readme.md-构建配置选项:

•SCP_ENABLE_NOTIFICATIONS:启用/禁用 SCP 固件中的通知。

•SCP_ENABLE_SCMI_NOTIFICATIONS:启用/禁用 SCMI 通知。

•SCP_ENABLE_RESOURCE_PERMISSIONS:启用/禁用资源权限设置。

单独配置编译:

配置生效命令:

cmake -B /tmp/build -DSCP_FIRMWARE_SOURCE_DIR:PATH=juno/scp_romfw -DSCP_ENABLE_DEBUG_UNIT=TRUE

然后就是编译命令:

cmake --build /tmp/build

在编译文件中配置,例如在product/juno/scp_romfw/Firmware.cmake中

set(SCP_ENABLE_NOTIFICATIONS TRUE)

修改后需要clean下,再继续编译。

2.1 平台初始化

arch/arm/arm-m/CMakeLists.txt中,arch-arm-m库的入口是arch_exception_reset()函数:

if(CMAKE_C_COMPILER_ID  STREQUAL "ARMClang")     target_link_options(arch-arm-mPUBLIC  "LINKER:--entry=arch_exception_reset")endif()

arch_exception_reset()函数在arch/arm/arm-m/src/arch.ld.S链接文件中也被定位了入口函数

其实现在arch/arm/arm-m/src/arch_handlers.c:

 noreturn  void arch_exception_reset(void) {     
 extern noreturn void __main(void);     
 __main(); 
 }

__main在c运行时调用main函数,对于M核实现来说,arch/arm/arm-m/src/arch_main.c中有main()函数

int main(void)
{
////初始化 ARM Cortex-M 系列芯片的 Configuration Control Register (CCR)。
//其中,通过设置 SCB_CCR_DIV_0_TRP_Msk 来启用除以零的异常处理
arch_init_ccr();

//scp 入口及应用函数
return fwk_arch_init(&arch_init_driver);
}

scp 入口及应用为fwk_arch_init函数,在framework/src/fwk_arch.c中

int fwk_arch_init(const struct fwk_arch_init_driver *driver)
{
//scp 框架初始化,完成module_table、module_config_table所有模块信息的初始化
//scp/module目录下的模块的初始化
fwk_module_init();

//这里构建了一个全局的fwk_io_stdin、 fwk_io_stdout, 在后面的终端输出有用
status = fwk_io_init();

//初始化日志输出方式
status = fwk_log_init();

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

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

//循环等待处理队列事件
__fwk_run_main_loop();
}

2.2 module初始化

fwk_module_init函数,在framework/src/fwk_module.c中实现

在系统构建章节中module_table和module_config_table是由配置文件Firmware.cmake生成的fwk_module_list.c中定义。

module见module介绍章节

module_config_table就是模块的上下文信息

void fwk_module_init(void)
{
for (uint32_t i = 0U; i <  (uint32_t)FWK_MODULE_IDX_COUNT; i++) {
//获取模块的上下文信息
struct fwk_module_context *ctx = &fwk_module_ctx.module_ctx_table[i];

fwk_id_t id = FWK_ID_MODULE(i);
const struct fwk_module *desc = module_table[i];
const struct fwk_module_config *config = module_config_table[i];

//给模块上下文信息赋值
*ctx = (struct fwk_module_context){
.id = id,
.desc = desc,
.config = config,
};

//初始化模块的链表
fwk_list_init(&ctx->delayed_response_list);

if (config->elements.type == FWK_MODULE_ELEMENTS_TYPE_STATIC) {
size_t notification_count = 0;

#ifdef BUILD_HAS_NOTIFICATION
notification_count = desc->notification_count;
#endif

fwk_module_init_element_ctxs(
ctx, config->elements.table, notification_count);
}

#ifdef BUILD_HAS_NOTIFICATION
if (desc->notification_count > 0) {
fwk_module_init_subscriptions(
&ctx->subscription_dlist_table, desc->notification_count);
}
#endif
}
}

2.3 中断初始化

static int fwk_arch_interrupt_init(int (*interrupt_init_handler)(
const struct fwk_arch_interrupt_driver **driver))
{
const struct fwk_arch_interrupt_driver *driver;

status = interrupt_init_handler(&driver);

/* Initialize the interrupt management component */
status = fwk_interrupt_init(driver);

return FWK_SUCCESS;
}

interrupt_init_handler是入参回调函数,对应为arch_init_driver

static const struct fwk_arch_init_driver arch_init_driver = {
.interrupt = arch_nvic_init,
};

在arch_nvic_init中有*driver =&arch_nvic_driver;

static const struct fwk_arch_interrupt_driver arch_nvic_driver = {
.global_enable = global_enable,
.global_disable = global_disable,
.is_enabled = is_enabled,
.enable = enable,
.disable = disable,
.is_pending = is_pending,
.set_pending = set_pending,
.clear_pending = clear_pending,
.set_isr_irq = set_isr_irq,
.set_isr_irq_param = set_isr_irq_param,
.set_isr_nmi = set_isr_nmi,
.set_isr_nmi_param = set_isr_nmi_param,
.set_isr_fault = set_isr_fault,
.get_current = get_current,
.is_interrupt_context = is_interrupt_context,
};

拿到driver的值后,执行fwk_interrupt_init(driver);

int fwk_interrupt_init(const struct fwk_arch_interrupt_driver *driver)
{
//校验driver
fwk_interrupt_driver = driver;
initialized = true;

return FWK_SUCCESS;
}

fwk_interrupt_driver 全局变量用于中断处理。

模块使用中断时,需要调用对外接口在framework/include/fwk_interrupt.h中,

例如开启中断fwk_interrupt_enable函数的实现:

int fwk_interrupt_enable(unsigned int interrupt)
{
if (!initialized) {
return FWK_E_INIT;
}

return fwk_interrupt_driver->enable(interrupt);
}

2.4 module启动

fwk_module_start()在framework/src/fwk_module.c中定义

int fwk_module_start(void)
{
//初始化任务列表
status = __fwk_init(FWK_MODULE_EVENT_COUNT);
fwk_module_ctx.stage = MODULE_STAGE_INITIALIZE;

//从功能方面初始化所有module
fwk_module_init_modules();
fwk_module_ctx.stage = MODULE_STAGE_BIND;

//调用模块.bind回调函数完成所有模块的绑定。(此处共进行两轮调用fwk_module_bind_module(round=0 1),
//每轮都将分别绑定模块module和模块的元素element)
for (bind_round = 0; bind_round <= FWK_MODULE_BIND_ROUND_MAX;
bind_round++) {
status = fwk_module_bind_modules(bind_round);
if (status != FWK_SUCCESS) {
return status;
}
}
fwk_module_ctx.stage = MODULE_STAGE_START;

//启动模块
status = start_modules();
fwk_module_ctx.initialized = true;

return FWK_SUCCESS;
}

fwk_module_init_modules函数调用fwk_module_init_module对每个模块进行功能初始化

//初始化模块元素上下文(element_ctxs),
//调用模块的config->elements.generator,获取element信息,加入模块上下文表
elements = config->elements.generator(ctx->id);
fwk_module_init_element_ctxs(ctx, elements, notification_count);

//调用模块的init函数,传入element_count,config->dat
status = desc->init(ctx->id, ctx->element_count, config->data);

//初始化模块元素(element),调用模块回调函数.element_init将模块element->data配置信息导入到模块内部
fwk_module_init_elements(ctx);

start_modules函数调用fwk_module_start_module对每个模块进行启动

module = fwk_mod_ctx->desc;

//调用模块.start回调函数
module->start(fwk_mod_ctx->id);

例如在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指令)。在product/juno/module/juno_rom/src/mod_juno_rom.c中:

const struct fwk_module module_juno_rom = {
.type = FWK_MODULE_TYPE_SERVICE,
.event_count = (unsigned int)MOD_JUNO_ROM_EVENT_COUNT,
.notification_count = (unsigned int)MOD_JUNO_ROM_NOTIFICATION_COUNT,
.init = juno_rom_init,
.bind = juno_rom_bind,
.start = juno_rom_start,
.process_event = juno_rom_process_event,
.process_notification = juno_rom_process_notification,
};

2.5 运行状态机

scp-firmware在完成了所有的初始化操作后,进入死循环,处理队列里面的事件或者休眠等待事件到来。

noreturn void __fwk_run_main_loop(void)
{
for (;;) {
fwk_process_event_queue();
if (fwk_log_unbuffer() == FWK_SUCCESS) {
fwk_arch_suspend();
}
}
}

fwk_process_event_queue主要处理三个重要的链表:free_event_queue, event_queue, isr_event_queue所有的操作都是围绕这三个队列展开。

void fwk_process_event_queue(void)
{
for (;;) {
while (!fwk_list_is_empty(&ctx.event_queue)) {
process_next_event();
}

if (!process_isr()) {
break;
}
}
}

event_queue中根据target_id找到对应module,然后调用module->process_event进行处理,详细见module中说明。

process_next_event中调用duplicate_event会处理free_event_queue队列中的事件

process_isr从中断isr_event_queue队列中取到事件,然后加入到event_queue中

3.module对外接口

在scp代码中,所有的功能都由一个个模块提供。每个模块以api枚举及其结构体的方式对外提供该模块的功能,并在模块通用结构体fwk_module中提供,例如

module/scmi_power_domain/src/mod_scmi_power_domain.c中,

/* SCMI Power Domain Management Protocol Definition */
const struct fwk_module module_scmi_power_domain = {
.api_count = 1,
.type = FWK_MODULE_TYPE_PROTOCOL,
.init = scmi_pd_init,
.bind = scmi_pd_bind,
.start = scmi_pd_start,
.process_bind_request = scmi_pd_process_bind_request,
.event_count = (unsigned int)SCMI_PD_EVENT_IDX_COUNT,
.process_event = scmi_pd_process_event,
#ifdef BUILD_HAS_MOD_DEBUG
.process_notification = scmi_pd_process_notification,
#endif
};

.init(模块初始化)

.bind(获取绑定别的模块的api)

.process_bind_request(被其他模块依赖的api的获取并绑定请求函数)等通用接口。

.start模块启动

.process_event事件处理

.process_notification通知处理

初始化模块:

模块在初始化时由fwk_module.c 中fwk_module_start函数,调用回调函数.init,.bind,.start

•模块初始化:调用模块API的init()函数指针

•元素初始化:调用框架模块API的element_init()函数指针

•后初始化:元素初始化后,模块交互之前的一些可选处理操作

•绑定:模块必须绑定好才能调用对方的api

•开始

运行时:

一旦运行前阶段成功完成,固件将开始处理模块或中断引发的事件。默认情况下,固件将永远循环等待新事件在运行前阶段结束时处理,但当事件列表为空时,可以在处理未决事件后返回。

模块配置:

模块初始化的时候,模块配置被读入存放到模块上下文中:

const struct fwk_module_config *config = module_config_table[i];

//给模块上下文信息赋值
*ctx = (struct fwk_module_context){
.id = id,
.desc = desc,
.config = config,
};

在module_config_table在fwk_module_list.c中定义,这里以config_juno_ppu为例:

const struct fwk_module_config *module_config_table[FWK_MODULE_IDX_COUNT] = {
&config_juno_ppu,

struct fwk_module_config config_juno_ppu = {
.data =
&(struct mod_juno_ppu_config){
.timer_alarm_id = FWK_ID_SUB_ELEMENT_INIT(
FWK_MODULE_IDX_TIMER,
0,
JUNO_PPU_ALARM_IDX),
},

.elements = FWK_MODULE_DYNAMIC_ELEMENTS(get_element_table),
};

#define FWK_MODULE_DYNAMIC_ELEMENTS(GENERATOR)
{
.type = FWK_MODULE_ELEMENTS_TYPE_DYNAMIC,
.generator = (GENERATOR),
}

如果类型为FWK_MODULE_ELEMENTS_TYPE_STATIC ,框架使用表指针中给出的静态表来访问产品为模块提供的元素表。

如果类型为 FWK_MODULE_ELEMENTS_TYPE_DYNAMIC ,则框架使用生成器函数指针。

get_element_table对应一个配置结构体数组:

static struct fwk_element element_table[] = {
[JUNO_PPU_DEV_IDX_BIG_SSTOP] = {
.name = "",
.data = &(const struct mod_juno_ppu_element_config) {
.reg_base = PPU_BIG_SSTOP_BASE,
.timer_id = FWK_ID_ELEMENT_INIT(FWK_MODULE_IDX_TIMER, 0),
.pd_type = MOD_PD_TYPE_CLUSTER,
},
},
....

enum juno_ppu_idx {
JUNO_PPU_DEV_IDX_BIG_CPU0,
JUNO_PPU_DEV_IDX_BIG_CPU1,
JUNO_PPU_DEV_IDX_BIG_SSTOP,

JUNO_PPU_DEV_IDX_LITTLE_CPU0,
JUNO_PPU_DEV_IDX_LITTLE_CPU1,
JUNO_PPU_DEV_IDX_LITTLE_CPU2,
JUNO_PPU_DEV_IDX_LITTLE_CPU3,
JUNO_PPU_DEV_IDX_LITTLE_SSTOP,

JUNO_PPU_DEV_IDX_GPUTOP,
JUNO_PPU_DEV_IDX_SYSTOP,
JUNO_PPU_DEV_IDX_DBGSYS,

JUNO_PPU_DEV_IDX_COUNT,
};

struct fwk_element结构体表示元素,里面有名字,子元素个数和数据

元素:

元素表示由模块拥有或管理的资源。每个元素将表示模块与之交互和/或负责的对象。

例如,驱动程序类型的模块可能具有表示它所控制的硬件设备的元素。因为元素配置数据灵活多变,使用通用的方式const void *data实现。

子元素表示由元素拥有或管理的资源。子元素仅由它们的索引和/或标识符表示。

索引和标识符:

由于框架设计为模块化,因此需要一种标准化方法来识别和引用模块、元素、子元素、事件、通知和 API。该框架为此定义了两个组件:indices和identifiers。

indices:

模块索引由构建系统为每个固件生成,并放在fwk_module_idx.h头文件中。

enum fwk_module_idx {
FWK_MODULE_IDX_JUNO_PPU = 0,
FWK_MODULE_IDX_JUNO_ROM = 1,
......

identifiers:

标识符有一个类型,这决定了标识符中包含的信息。在内部,标识符始终包含模块的索引,并且可能包含在该模块的上下文中标识项目的附加索引。也在fwk_module_idx.h头文件中,有宏和变量两部分定义,值是一样的:

#define FWK_MODULE_ID_JUNO_PPU FWK_ID_MODULE(FWK_MODULE_IDX_JUNO_PPU)
#define FWK_ID_MODULE(MODULE_IDX) ((fwk_id_t)FWK_ID_MODULE_INIT(MODULE_IDX))
#define FWK_ID_MODULE_INIT(MODULE_IDX)
{
.common = {
.type = (uint32_t)__FWK_ID_TYPE_MODULE,
.module_idx = (uint32_t)MODULE_IDX,
},
}

static const fwk_id_t fwk_module_id_juno_ppu = FWK_MODULE_ID_JUNO_PPU_INIT;
#define FWK_MODULE_ID_JUNO_PPU_INIT FWK_ID_MODULE_INIT(FWK_MODULE_IDX_JUNO_PPU)

可用的标识符类型有:

•模块:仅由模块索引组成

•元素:由模块索引和模块内元素的索引组成

•子元素:由模块索引、模块内元素的索引和该元素拥有的子元素的索引组成。

•API:由模块索引和模块提供的API的索引组成

•事件:由模块索引和模块可能产生的事件的索引组成

•通知:由模块索引和模块可能生成的通知索引组成。

日志:

日志记录功能定义并实现了该组件的公共接口。该接口的文档可以在 fwk_log.h 中找到。

#include
FWK_LOG_ERR("[ROM] ERROR: Failed to turn on LITTLE cluster.");
# define FWK_LOG_ERR(...) fwk_log_printf(__VA_ARGS__)

fwk_log_printf()函数在framework/src/fwk_log.c中定义。

4. event事件

255e9582-192e-11ee-962d-dac502259ad0.png

模块可以给自己或者别的模块发送event事件,事件的参数是结构化消息structfwk_event

static int juno_rom_process_event(
const struct fwk_event *event,
struct fwk_event *resp)
{

truct fwk_event {
struct fwk_slist_node slist_node;
fwk_id_t source_id;
fwk_id_t target_id;
uint32_t cookie;
bool is_response;
bool response_requested;
bool is_notification;
bool is_delayed_response;
fwk_id_t id;
alignas(max_align_t) uint8_t params[FWK_EVENT_PARAMETERS_SIZE];
};

该事件包含一个response_requested 属性,该属性指示源实体是否期望对其事件的响应。为了响应这个事件,接收实体填写响应参数,框架发出一个事件,该事件以发出原始事件的实体为目标。

事件的is_response属性用于指示新生成的事件是对原始事件的响应。

例如在juno_rom固件初始化时,初始化juno_rom模块,product/juno/module/juno_rom/src/mod_juno_rom.c

会执行.start回调函数函数juno_rom_start(),给自己发了一个event,如下:

static int juno_rom_start(fwk_id_t id)
{
struct fwk_event event = {
.source_id = fwk_module_id_juno_rom,
.target_id = fwk_module_id_juno_rom,
.id = mod_juno_rom_event_id_run,
};
.....
return fwk_put_event(&event);
}

#define fwk_put_event(event)
_Generic((event), struct fwk_event *
: __fwk_put_event, struct fwk_event_light *
: __fwk_put_event_light)(event)

fwk_put_event把event分为两类,fwk_event_light 是轻量级的携带不携带额外数据参数。这里我们用fwk_event 则处理函数为:

__fwk_put_event
--》put_event(event,
intr_state, FWK_EVENT_TYPE_STD);
--》fwk_list_push_tail(&ctx.event_queue,
&allocated_event->slist_node);

固件状态机运行的时候会循环执行framework/src/fwk_core.c中process_next_event()函数

static void process_next_event(void)
{
ctx.current_event = event = FWK_LIST_GET(
fwk_list_pop_head(&ctx.event_queue), struct fwk_event, slist_node);

module = fwk_module_get_ctx(event->target_id)->desc;
process_event = event->is_notification ? module->process_notification :
module->process_event;

status = process_event(event, &async_response_event);

这里找到模块juno_rom,然后取出其event处理函数process_event并执行,实际执行的是juno_rom_process_event(),其发了一条通知消息如下:

static int juno_rom_process_event(
const struct fwk_event *event,
struct fwk_event *resp)
{
....
/* Send SYSTOP ON notification */
systop_on_event =
(struct fwk_event){ .response_requested = true,
.id = mod_juno_rom_notification_id_systop,
.source_id = FWK_ID_NONE };

notification_params = (void *)systop_on_event.params;
notification_params->state = (unsigned int)MOD_PD_STATE_ON;

//发notification消息
status = fwk_notification_notify(&systop_on_event, &ctx.notification_count);
if (!fwk_expect(status == FWK_SUCCESS)) {
return FWK_E_PANIC;
}

//通过ctx.bootloader_api->load_image()调用mod_bootloader的api,从安全内存拷贝到指定位置,
//在该bootloader 模块api中加载跳转scp_ramfw。
if (ctx.notification_count == 0) {
return deferred_setup();
}

fwk_notification_notify的解释见notification章节

5.motificaiont通知

notification涉及到两个模块的通信,跟event的区别是:

•event是一个模块发给另外一个模块或者发给自己,比较确定

•notification是发给订阅了这个模块的所有模块,算广播,需要先进行订阅

notification接口:

•fwk_notification_subscribe//订阅指定模块指定通知

•fwk_notification_unsubscribe//取消订阅通知

•fwk_notification_notify//向订阅该通知的模块发送通知

在实现上notification使用event的消息传递机制,只在发消息和处理消息的时候做微小改动。

例如上面例子中使用fwk_notification_notify()函数发送的通知

int fwk_notification_notify(struct fwk_event *notification_event,unsigned int *count){
send_notifications(notification_event, count);

通知的参数沿用event的struct fwk_event ,发送通知的时候,需要先找到订阅链表,然后进行过滤

static void send_notifications(struct fwk_event *notification_event,
unsigned int *count)
{
//根据id和source_id找到订阅的链表
subscription_dlist = get_subscription_dlist(notification_event->id,
notification_event->source_id);
notification_event->is_response = false;
notification_event->is_notification = true;

for (node = fwk_list_head(subscription_dlist); node != NULL;
node = fwk_list_next(subscription_dlist, node)) {
subscription = FWK_LIST_GET(node,
struct __fwk_notification_subscription, dlist_node);

//对比源id如果相同就进行发送
if (!fwk_id_is_equal(
subscription->source_id, notification_event->source_id)) {
continue;
}

notification_event->target_id = subscription->target_id;

status = __fwk_put_notification(notification_event);
if (status == FWK_SUCCESS) {
(*count)++;
}
}
}

get_subscription_dlist函数中source_id 决定是模块上下文还是元素上下文

.id = mod_juno_rom_notification_id_systop,
.source_id = FWK_ID_NONE };

static const fwk_id_t mod_juno_rom_notification_id_systop =
FWK_ID_NOTIFICATION_INIT(
FWK_MODULE_IDX_JUNO_ROM,
MOD_JUNO_ROM_NOTIFICATION_IDX_SYSTOP);

拿到subscription_dlist订阅列表后,就进行过滤发送通知

int __fwk_put_notification(struct fwk_event *event)
{
event->is_response = false;
event->is_notification = true;

return put_event(event, UNKNOWN_STATE, FWK_EVENT_TYPE_STD);
}

这里就使用了event进行实现。然后系统状态机在处理event的时候,

static void process_next_event(void)
{
ctx.current_event = event = FWK_LIST_GET(
fwk_list_pop_head(&ctx.event_queue), struct fwk_event, slist_node);

module = fwk_module_get_ctx(event->target_id)->desc;
process_event = event->is_notification ? module->process_notification :
module->process_event;

根据is_notification 就可以知道是notification 了,然后调用process_notification 进行处理

6.模块绑定

一个模块或元素可以绑定到另一个模块或模块内的元素。目标是相同的 - 获取指向可在后续阶段使用的 API 的指针。当尝试绑定到模块内的元素(而不是模块本身)时,主要区别在于接收和处理绑定请求的模块能够根据目标元素更改其行为。例如,可以允许请求绑定的模块仅绑定到处理请求的模块内的元素子集。

思路:A模块要与B模块通信,A模块的全局变量要拿到B模块的回调函数

A模块在初始化的时候,会调用自己的bind函数,

bind--》fwk_module_bind--》B模块的process_bind_request()函数,从而拿到api

25fd5cda-192e-11ee-962d-dac502259ad0.png

scmi_power_domain模块调用scmi模块的api函数示例图

scmi_pd_ctx.scmi_api赋值为scmi模块的处理函数,在.bind = scmi_pd_bind中,

static int scmi_pd_bind(fwk_id_t id, unsigned int round)
{
status = 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);

fwk_module_bind调用依赖模块提供的process_bind_request函数来获取依赖模块的api,并绑定。

int fwk_module_bind(fwk_id_t target_id, fwk_id_t api_id, const void *api)
{
fwk_mod_ctx = fwk_module_get_ctx(target_id);

status = fwk_mod_ctx->desc->process_bind_request(
fwk_module_ctx.bind_id, target_id, api_id, (const void **)api);

target_id是FWK_MODULE_IDX_SCMI,对应SCMI模块,fwk_mod_ctx 是SCMI模块的上下文

/* SCMI module definition */
const struct fwk_module module_scmi = {
.api_count = (unsigned int)MOD_SCMI_API_IDX_COUNT,
.event_count = 1,
#ifdef BUILD_HAS_NOTIFICATION
.notification_count = (unsigned int)MOD_SCMI_NOTIFICATION_IDX_COUNT,
#endif
.type = FWK_MODULE_TYPE_SERVICE,
.init = scmi_init,
.element_init = scmi_service_init,
.bind = scmi_bind,
.start = scmi_start,
.process_bind_request = scmi_process_bind_request,
.process_event = scmi_process_event,
#ifdef BUILD_HAS_NOTIFICATION
.process_notification = scmi_process_notification,
#endif
};

scmi_process_bind_request调用到

static int scmi_process_bind_request(fwk_id_t source_id, fwk_id_t target_id,
fwk_id_t api_id, const void **api)
{
unsigned int api_idx;
struct scmi_service_ctx *ctx;
enum mod_scmi_api_idx api_id_type;

api_idx = fwk_id_get_api_idx(api_id);

api_id_type = (enum mod_scmi_api_idx)api_idx;

switch (api_id_type) {
case MOD_SCMI_API_IDX_PROTOCOL:
if (!fwk_id_is_type(target_id, FWK_ID_TYPE_MODULE)) {
return FWK_E_SUPPORT;
}

if (scmi_ctx.protocol_count >= scmi_ctx.config->protocol_count_max) {
return FWK_E_NOMEM;
}

scmi_ctx.protocol_table[PROTOCOL_TABLE_RESERVED_ENTRIES_COUNT +
scmi_ctx.protocol_count++].id = source_id;
*api = &scmi_from_protocol_api;
break;

到此scmi_power_domain模块拿到了scmi模块的处理函数,放入&scmi_pd_ctx.scmi_api

几个关键的模块间api调用示例:

mod_scmi_power_domain模块中scmi消息收发:

○scmi模块绑定各个scmi协议模块的protocol_api,根据id来找到此模块api,执行该protocol;

○scmi_power_domain模块绑定scmi模块的api,通过调用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消息通知中断产生和处理)。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • ARM处理器
    +关注

    关注

    6

    文章

    347

    浏览量

    41327
  • 耦合器
    +关注

    关注

    8

    文章

    671

    浏览量

    59260
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61357
  • 状态机
    +关注

    关注

    2

    文章

    486

    浏览量

    27187
  • MCP
    MCP
    +关注

    关注

    0

    文章

    252

    浏览量

    13739

原文标题:ARM SCP入门-framework框架代码分析

文章出处:【微信号:OS与AUTOSAR研究,微信公众号:OS与AUTOSAR研究】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    嵌入式框架Zorb Framework搭建方案

    Zorb Framework是一个基于面向对象的思想来搭建一个轻量级的嵌入式框架
    的头像 发表于 11-05 17:08 1036次阅读
    嵌入式<b class='flag-5'>框架</b>Zorb <b class='flag-5'>Framework</b>搭建方案

    嵌入式框架Zorb Framework状态机的实现

    Zorb Framework是一个基于面向对象的思想来搭建一个轻量级的嵌入式框架
    的头像 发表于 11-29 09:33 396次阅读
    嵌入式<b class='flag-5'>框架</b>Zorb <b class='flag-5'>Framework</b>状态机的实现

    ARM入门基础好书

    `ARM入门基础好书本文从学习者的角度出发,分别描述了下面几部分内容:ARM编程的基本知识,BOOT代码流程和功能分析,OS中断程序的编写举
    发表于 10-31 16:52

    [每周一练]Actor Framework框架(1125-1201)

    。Creat Project里面就大不相同。当时也创建了Actor Framework,体验了一下,感觉看不懂就渐渐淡忘了,也没有注意这个框架的名字(因为不了解)。今年有一次被问到是否知道Actor
    发表于 11-25 00:04

    .NET Micro Framework简介

    的方式实现了源代码完全开放。  1.哪些领域可以采用.NET Micro Framework技术?  .NET Micro Framework技术可以应用到:Sideshow、远程控制、智能家电
    发表于 05-27 16:03

    .Net Micro Framework 快速入门

    ?  .NET Micro Framework对存储器和处理器的要求更低。开发人员可以在低功耗、低成本的ARM7、ARM9、Blackfin和Cortex-M3处理器上使用该框架(不需
    发表于 05-29 10:56

    [资料分享]+Android框架揭秘

    框架入门教材使用。 三、看看目录,了解其主要内容 《android框架揭秘》第1章 android framework概要 11.1 android源代码组成 21.2 通过启动过程
    发表于 09-26 09:47

    Labview Actor Framework 框架使用例子(反馈式蒸发器)

    工作中遇到老外的项目,用的是Actor Framework框架,所以最近这段时间学习了下。但网上这块资料非常少,就算是有也是非常简单的介绍。后面看到这篇博客http://blog.csdn.net
    发表于 11-17 19:36

    Android Framework电源子系统的分析

    系列文章解读&说明:Android Framework 电源子系统 的分析主要分为以下部分:Android Framework 电源子系统(01)PowerManagerService启动
    发表于 12-31 06:51

    CH372DLL.dll在.NET Framework 4.7.2及以上版本框架会闪退如何解决?

    问题:CH372DLL.dll 在 .NET Framework 3.5 及以下版本框架工作正常,.NET Framework 4.7.2 及以上版本框架会闪退请问如何解决
    发表于 07-19 06:18

    ARM入门 Study ARM Step by Step

    ARM入门 Study ARM Step by Step本文从学习者的角度出发,分别描述了下面几个部分内容:ARM编程的基本知识,BOOT代码
    发表于 02-10 10:57 192次下载

    ARM处理器的启动代码分析与设计

    ARM处理器的启动代码分析与设计
    发表于 09-25 08:27 12次下载
    <b class='flag-5'>ARM</b>处理器的启动<b class='flag-5'>代码</b>的<b class='flag-5'>分析</b>与设计

    关于嵌入式应用框架(EAF)的分析

    EAF是Embedded Application Framework 的缩写,即嵌入式应用框架。嵌入式应用框架是 Application framework的一种, 是在嵌入式领域的应
    发表于 01-01 09:50 1177次阅读

    ARM SCP入门-AP与SCP通信

    当Linux想要关机或者休眠的时候,这涉及到整个系统电源状态的变化,为了安全性Linux内核没有权利去直接执行了,需要陷入到EL3等级去执行,可以参考之前文章ARM ATF入门-安全固件软件介绍和代码运行
    的头像 发表于 07-16 11:25 3723次阅读
    <b class='flag-5'>ARM</b> <b class='flag-5'>SCP</b><b class='flag-5'>入门</b>-AP与<b class='flag-5'>SCP</b>通信

    framework框架流程 模块绑定

    framework框架负责固件的通用流程实现,包括系统初始化,module初始化,中断服务提供,event服务提供等。这样module就可以专注于自己功能和对外交互api的实现。SCP
    的头像 发表于 11-02 17:24 322次阅读
    <b class='flag-5'>framework</b><b class='flag-5'>框架</b>流程 模块绑定