SCP Firmware 负责系统中的电源域、时钟、传感器、通信接口等管理工作。为了控制复杂度这些功能被拆分成相互独立的 Module。Module 之间既需要协作又不应该彼此依赖具体实现因此 Framework 提供了几种受控的交互方式其中最重要的异步通信机制就是Event和Notification。本文面向第一次接触 SCP Firmware 的读者从整体运行模型出发逐步介绍 Event 和 Notification 的概念、数据结构、请求与响应机制并结合 Sensor、Power Domain 和 Clock Module 的实际代码说明它们如何工作。本文讨论的是 SCP Firmware 内部 Module 之间的通信不是 SCP 与 AP 之间通过 MHU、SCMI 等协议进行的跨处理器通信。1. 先建立整体认识可以先用两句话概括二者Event 是点对点的异步消息发送者知道接收者是谁希望接收者执行一项工作并且可以要求响应。Notification 是发布/订阅消息发布者只声明“某件事发生了”Framework 将消息发送给所有提前订阅它的对象。Arm® System Control Processor (SCP) Firmware-101Notification 并不是一套独立于 Event 的底层设施。在 Framework 内部Notification 本质上是一种特殊 Event它同样使用struct fwk_event进入相同的事件队列也支持普通响应和延迟响应。二者最主要的区别是消息的寻址方式和所表达的业务语义。2. Event 背后的运行模型理解 Event 之前需要先了解 SCP Firmware 的主循环。默认裸机运行模式下它的核心逻辑可以简化为for(;;){fwk_process_event_queue();if(fwk_log_unbuffer()FWK_SUCCESS){fwk_arch_suspend();}}主循环不断处理队列里的消息当事件和缓存日志都处理完后处理器进入休眠等待中断再次唤醒。Framework 主要维护三类队列free_event_queue 空闲 Event 对象池 event_queue 等待处理的普通 Event isr_event_queue 中断上下文产生的 Event调用fwk_put_event()时Framework 从预分配的对象池中取得一个 Event将调用者提供的内容复制进去再放入普通队列或 ISR 队列。发送函数返回时接收者通常还没有开始处理消息。主循环取出 Event 后根据target_id找到目标 Module然后根据is_notification选择回调processevent-is_notification?module-process_notification:module-process_event;因此默认情况下 SCP Firmware 是一种run-to-completion的协作式事件模型一个普通事件回调开始执行后会一直运行到返回随后主循环才处理下一个 Event。它不是带线程、时间片和任务优先级的 RTOS 调度器。这带来一个直接要求process_event()和process_notification()不应长时间阻塞。耗时操作应交给硬件、中断或后续 Event 完成。3. Event点对点的异步消息3.1 Event 中保存了什么struct fwk_event的核心字段可以简化为structfwk_event{fwk_id_tsource_id;fwk_id_ttarget_id;uint32_tcookie;bool is_response;bool response_requested;bool is_notification;bool is_delayed_response;fwk_id_tid;uint8_tparams[FWK_EVENT_PARAMETERS_SIZE];};各字段的作用如下字段作用source_id消息发送者可以是 Module、Element 或 Sub-elementtarget_id消息接收者idEvent 类型由拥有该 Event 的 Module 定义params请求或响应参数当前固定为 16 字节response_requested发送者是否要求响应is_response当前消息是否是另一个 Event 的响应is_delayed_response响应是否需要推迟发送cookieFramework 分配的关联标识用于匹配请求和延迟响应3.2 Event ID 属于接收方普通请求 Event 有一条非常重要的规则Event ID 由接收该请求的 Module 定义。假设 Module A 向 Module B 发送请求那么event.id应当是 Module B 公开或内部定义的 Event IDtarget_id也必须属于 Module B。它表达的语义是“A 请求 B 执行一种由 B 定义的操作”。一个典型的发送过程如下structfwk_eventrequest{.target_idtarget_id,.idtarget_event_id,.response_requestedtrue,};structrequest_params*params(structrequest_params*)request.params;params-valuevalue;statusfwk_put_event(request);如果代码正在处理另一个 EventFramework 会把当前 Event 的目标实体作为新 Event 的source_id。如果在启动阶段或中断上下文等位置发送消息则通常需要显式提供合法的source_id。由于 Framework 会复制 Event像上面这样使用栈变量是安全的。不过params中如果保存了指针指针所指向对象的生命周期仍然必须覆盖整个异步处理过程。3.3 接收 Event能够接收 Event 的 Module 需要在描述符中提供process_eventstaticintmodule_process_event(conststructfwk_event*event,structfwk_event*resp_event){switch(fwk_id_get_event_idx(event-id)){caseMODULE_EVENT_IDX_REQUEST:returnprocess_request(event,resp_event);default:returnFWK_E_PARAM;}}conststructfwk_modulemodule_example{.event_countMODULE_EVENT_IDX_COUNT,.process_eventmodule_process_event,};process_event接口参数event是收到的消息当请求方要求响应时resp_event是 Framework 准备好的响应对象接收方主要负责填写响应参数。3.4 三种响应方式Event 支持三种常见处理方式。不需要响应发送方保持.response_requestedfalse接收方处理完 Event 后本次交互结束。它适合“触发一次动作但发送者不关心结果”的场景。标准响应发送方设置.response_requestedtrueFramework 会预先构造resp_event将请求的source_id和target_id对调。接收方填写响应参数并返回后Framework 自动设置is_response true再把响应放回事件队列。完整过程是Module A Framework Module B | | | |--- request --------| | | |--- process_event --| | |-- fill response ---| | | | |-- response --------| |响应仍是异步消息不会在 Module B 返回时直接同步调用 Module A。Module A 最终也在自己的process_event()中收到它并通过event-is_response判断消息方向。延迟响应如果接收方暂时得不到结果可以在处理请求时设置resp_event-is_delayed_responsetrue;Framework 此时不会立即发送响应而是把响应对象保存在目标实体的 delayed-response 列表中。等硬件操作或其他异步过程完成后Module 可以通过cookie找回响应对象填写结果再调用fwk_put_event()发回请求者。延迟响应适用于传感器采样、I2C 传输、电源状态切换等无法立即完成的操作。3.5 Light EventFramework 还提供struct fwk_event_light。它只保留source_id、target_id、id和response_requested不包含参数区和延迟响应所需字段。Light Event 适合不需要携带数据、但对构造开销敏感的路径例如部分 DVFS 场景。它不能用于 Notification也不能用于 delayed response。4. Notification一对多的发布/订阅Event 要求发送者明确填写target_id。但有些消息并不是“请某个对象执行操作”而是“我的状态发生了变化感兴趣的对象可以处理”。这正是 Notification 的用途。Notification 的完整生命周期包括两个阶段订阅和发布。4.1 订阅订阅者调用statusfwk_notification_subscribe(notification_id,source_id,target_id);三个参数分别表示notification_id希望接收哪一种通知source_id只接收哪个 Module 或 Element 发出的通知target_id通知到达后交给订阅方的哪个实体处理。订阅通常在 Module 的start()阶段完成此时各 Module 已完成初始化和绑定。4.2 Notification ID 属于发布方Notification 与普通 Event 在 ID 所有权上正好相反Notification ID 由发布通知的源 Module 定义。例如Power Domain Module 定义“电源状态已经改变”通知。Clock Module 可以订阅它但通知 ID 仍然属于 Power Domain因为这条消息描述的是 Power Domain 自身的状态。4.3 发布发布者构造一个struct fwk_event但不需要逐个指定接收者structfwk_eventnotification{.source_idsource_id,.idnotification_id,.response_requestedfalse,};structnotification_params*params(structnotification_params*)notification.params;params-new_statenew_state;statusfwk_notification_notify(notification,subscriber_count);Framework 根据notification_id source_id查询订阅表为每个订阅者生成一份消息副本填写不同的target_id然后逐一放入事件队列。subscriber_count返回成功发送的通知数量。即使没有订阅者发布操作也可以正常完成此时计数为 0。4.4 接收 Notification订阅方通过process_notification()接收staticintmodule_process_notification(conststructfwk_event*event,structfwk_event*resp_event){if(event-is_response){returnprocess_notification_response(event);}if(fwk_id_is_equal(event-id,expected_notification_id)){returnprocess_state_change(event,resp_event);}returnFWK_E_HANDLER;}Notification 同样可以设置response_requested true。这时每个订阅者都会产生独立响应发布者可以结合subscriber_count统计是否已收到全部响应。这类机制特别适合构建依赖链。例如系统进入低功耗状态之前Power Domain 先发出 pre-transition NotificationClock、内存控制器或唤醒模块完成准备后分别响应发布者收到所有响应后才继续真正的状态切换。Notification 是可选构建特性需要启用SCP_ENABLE_NOTIFICATIONS相关代码通常由BUILD_HAS_NOTIFICATION条件编译保护。5. Event 与 Notification 的区别对比项EventNotification通信模型点对点发布/订阅一对多接收者发送时明确指定由 Framework 查询订阅表决定ID 所属方接收方 Module发布方 Module处理入口process_event()process_notification()典型语义请求执行操作、推进状态机宣布状态或事实发生变化响应可选通常只有一个响应方可选每个订阅者分别响应底层承载Event 队列同一个 Event 队列耦合程度发送方知道目标发布方不需要知道订阅者选择时可以使用一个简单判断如果消息表达“请你完成某件事”并且目标明确使用 Event如果消息表达“某件事已经或即将发生”可能有零个、一个或多个关注者使用 Notification。6. Event 实例Sensor 的异步读取Sensor Module 展示了 Event 与 delayed response 如何配合完成一次异步操作。阅读代码前先区分参与流程的三个角色Sensor Client传感器数据的使用者例如 Thermal Management 或 SCMI Sensor ModuleSensor HALmodule/sensor向 Client 提供统一的 Sensor API并协调异步请求Sensor Driver面向具体硬件的驱动例如 Juno 平台的 PVT 温度传感器驱动。下面以一次传感器读取为例说明主流程。代码基于module/sensor/src/mod_sensor.c省略了错误处理和并发控制。6.1 Client 通过 API 发起读取Client 通常在自己的process_event()中调用 Sensor HAL 的get_data()。Sensor HAL 随后调用具体 Driver 的get_value()statusctx-driver_api-get_value(ctx-config-driver_id,ctx-last_read.value);如果数据可以立即取得Driver 返回FWK_SUCCESS调用过程同步结束。对于需要启动硬件采样并等待中断的设备Driver 返回FWK_PENDING。这表示请求已被接受但结果稍后才能提供。Sensor HAL 随后提交一个要求响应的READ_REQUESTEvent并向 Client 返回FWK_PENDINGstructfwk_eventrequest{.target_idsensor_id,.idmod_sensor_event_id_read_request,.response_requestedtrue,};statusfwk_put_event(request);returnFWK_PENDING;虽然这个 Event 由 Sensor HAL 构造并且目标也是 Sensor HAL 的 Sensor Element但它并不是普通的“自己发给自己”。fwk_put_event()会根据当前正在处理的 Event 自动补充source_id因此其逻辑方向是Client - Sensor HAL这里的READ_REQUEST也不是让 Driver 再读取一次硬件。Driver 侧的读取流程已经由前面的get_value()发起这个 Event 的主要作用是为异步结果建立一条返回 Client 的响应路径。6.2 Sensor HAL 延迟响应Framework 发现response_requested为true后会为该请求准备一个响应 Event并自动交换通信双方请求Client - Sensor HAL 响应Sensor HAL - Client当主循环把READ_REQUEST交给sensor_process_event()时硬件采样还没有完成因此 Sensor HAL 不立即发送这个响应而是将其标记为 delayed responsecaseSENSOR_EVENT_IDX_READ_REQUEST:ctx-cookieevent-cookie;resp_event-is_delayed_responsetrue;returnFWK_SUCCESS;Framework 随后保存的是已经准备好的“Sensor HAL 到 Client”的响应 Event而不是原始的请求 Event。cookie用于在读取完成后找到与本次请求对应的响应。6.3 Driver 通知读取完成硬件采样完成后具体 Driver 通常在处理中断或驱动内部 Event 时取得结果然后通过 Sensor HAL 提供的reading_complete()回调上报数据。reading_complete()保存读取结果并向 Sensor HAL 对应的 Sensor Element 提交一个READ_COMPLETEEvent。这个 Event 的方向是Sensor Driver - Sensor HAL因此READ_COMPLETE只是 Driver 与 Sensor HAL 之间的内部完成事件并不会直接发送给 Client。6.4 Sensor HAL 将结果返回 ClientSensor HAL 处理READ_COMPLETE时使用先前保存的cookie取回 delayed response将读取结果写入 Client 提供的数据对象然后提交该响应caseSENSOR_EVENT_IDX_READ_COMPLETE:statusfwk_get_delayed_response(event-target_id,ctx-cookie,read_response);structmod_sensor_event_params*response_params(void*)read_response.params;sensor_data_copy(response_params-sensor_data,ctx-last_read);statusfwk_put_event(read_response);最终Client 在自己的process_event()中收到来自 Sensor HAL 的响应并继续处理传感器数据。完整流程如下Client Sensor HAL Sensor Driver | | | | get_data() | | |-------------------------| get_value() | | |------------------------------| | | FWK_PENDING | | FWK_PENDING |------------------------------| |-------------------------| | | | | | READ_REQUEST | | |-------------------------| 保存 delayed response | | | | | | ...硬件采样 / 中断处理... | | | | | | reading_complete() | | | READ_COMPLETE Event | | |------------------------------| | delayed response | | |-------------------------| |需要注意Client 最终收到的是READ_REQUEST的响应而不是READ_COMPLETE。Framework 沿用原请求的 Event ID并通过is_response标记它是一个响应。实际实现还允许多个 Client 等待同一次硬件读取。第一个请求触发采样后续请求先进入等待状态读取完成后process_pending_requests()使用同一份最新数据依次完成这些请求避免重复启动硬件采样。7. Notification 实例Power Domain 通知 ClockPower Domain 和 Clock 的协作展示了典型的一对多状态传播。以下代码同样省略了上下文获取、条件编译和错误分支只保留订阅与通知的主线。Clock Element 在启动阶段订阅 Power Domain 的状态变化statusfwk_notification_subscribe(pd_transition_notification_id,pd_source_id,clock_element_id);当 Power Domain 完成状态切换后它发布状态变化 Notificationstructfwk_eventnotification_event{.idmod_pd_notification_id_power_state_transition,.response_requestedtrue,.source_idFWK_ID_NONE};structmod_pd_power_state_transition_notification_params*params;params-statenew_state;statusfwk_notification_notify(notification_event,pd-power_state_transition_notification_ctx.pending_responses);mod_pd_notification_id_power_state_transition pd_transition_notification_idif(fwk_id_is_equal(event-id,pd_transition_notification_id)){returnclock_process_pd_transition_notification(ctx,event);}Clock 随后还可以继续发布自己的state_changedNotification让依赖该时钟的其他 Module 感知变化。这样便形成Power Domain | | power-state-transition Notification v Clock | | clock-state-changed Notification v 其他订阅者Power Domain 并不需要知道哪些 Clock 或其他 Module 关注它Clock 也不需要写死后续消费者。这正是 Notification 降低模块耦合的价值。8. 常见误区与使用建议8.1 不要混淆 ID 的归属这是最常见的错误请求 Event 的 ID 属于目标 ModuleNotification 的 ID 属于源 Module。Framework 的调试构建会检查 ID 与 source/target 的 Module 索引是否匹配。8.2fwk_put_event()成功不等于业务完成它通常只表示 Event 已成功复制并进入队列。真正的处理结果需要通过响应 Event、Notification 响应或后续状态查询获得。8.3 注意参数区大小和对象生命周期params只有FWK_EVENT_PARAMETERS_SIZE当前为 16 字节。参数结构必须保证能够放入其中最好在编译期检查大小。栈上的 Event 可以安全发送因为 Framework 会复制它但参数中的裸指针不会自动复制其指向的数据异步处理时尤其要注意悬空指针问题。8.4 Handler 应尽快返回默认事件处理是串行的。一个 Handler 长时间忙等会阻塞后续 Event、Notification 和日志输出。耗时操作应拆成“启动操作”和“完成事件”两个阶段。8.5 Event 池是有限资源Framework 使用预分配的 Event 对象池。若生产速度长期高于消费速度fwk_put_event()可能返回FWK_E_NOMEM。发送方必须检查返回值设计时也应避免无界地生成 Event。8.6 ISR 中只做必要工作ISR 可以产生 EventFramework 会先将其放入isr_event_queue再交给普通事件上下文处理。推荐在 ISR 中完成清中断、读取必要状态和投递 Event把复杂逻辑留给主循环。8.7 Notification 发布者不要依赖固定订阅者fwk_notification_notify()返回的数量可能是 0。除非业务协议明确要求至少有一个订阅者否则发布方不应把“无人订阅”当作异常。9. 源码阅读路径建议按照以下顺序阅读源码framework/include/fwk_event.hEvent 数据结构framework/include/fwk_core.hfwk_put_event()等接口framework/src/fwk_core.c事件池、队列、分发和响应framework/include/fwk_notification.h订阅与发布接口framework/src/fwk_notification.c订阅表和一对多投递module/sensor/src/mod_sensor.cdelayed response 实例module/power_domain/src/mod_power_domain.c与module/clock/src/mod_clock.cNotification 依赖链实例。