一套软硬件跑起来的样子就像上面图里面的一样,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个可真不少。
一个固件只包含一部分module,在Firmware.cmake中定义,gen_module_code.py脚本生成源码
这些module在framework启动时候初始化启动运行。
公共的module比较有通用性,产品自己的module一般是驱动需要进行定制
模块类型及软件协议栈:
这个协议栈就是SCP软件跟外界交互的流程,一般消息都是通过驱动-》HAL层上来,然后处理的过程就是服务-》协议-》HAL-》驱动再操作硬件做出反应,这次交互就算结束了。具体如下:
2.framework框架流程
framework框架负责固件的通用流程实现,包括系统初始化,module初始化,中断服务提供,event服务提供等。这样module就可以专注于自己功能和对外交互api的实现。SCP framework初始化流程图如下:
备注:这里的framework框架流程适用于scp_romfw和scp_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/
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_table和module_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事件
模块可以给自己或者别的模块发送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
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文章
360浏览量
41720 -
耦合器
+关注
关注
8文章
725浏览量
59686 -
FreeRTOS
+关注
关注
12文章
484浏览量
62140 -
状态机
+关注
关注
2文章
492浏览量
27529 -
MCP
+关注
关注
0文章
254浏览量
13900
原文标题:ARM SCP入门-framework框架代码分析
文章出处:【微信号:OS与AUTOSAR研究,微信公众号:OS与AUTOSAR研究】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论