最近有个读者朋友问我有没有关于SCP的资料可以分享一下,之前刚刚有接触过,于是今天来和大家再一起学习一下。
本文内容来自ARM官网以及一篇前辈的文章,全部内容主要分为:
- SCP是什么
- System Control Processor Firmware的运转流程
- SCP的代码目录
因为时间,属实是很多东西都已经遗忘,如果有什么错误,欢迎指正!!!
话不多说,开始吧。
PART 1:前言
1、SCP是什么?
下面首先来看一下SCP是什么?
SCP-System Control Processor Firmware-系统控制处理器固件-开源电源和系统管理参考固件
行业中有一种强烈的趋势,即在系统中提供微控制器,以从应用处理器(AP)中提取各种电源或其他系统管理任务。
功率控制系统体系结构(PCSA-DEN0050C)描述了如何按照这种方法构建系统。
PCSA定义了系统控制处理器(SCP)的概念,SCP是一种专用处理器,用于将电源和系统管理任务从应用处理器中抽象出来。
与SCP类似,可管理性控制处理器(MCP)遵循相同的方法,目的是为需要可管理性的片上系统(SoC)提供管理入口点,例如在SoC目标服务器上。
SCP固件为几个Arm计算子系统中的系统控制处理器(SCP)和可管理性控制处理器(MCP)组件提供了软件参考实现。
SCP固件最近在GitHub上作为一个开源项目提供,它是根据BSD-3条款许可证提供的。
2、SCP能干什么?
- 该套功能包括:
- 初始化系统以启用应用程序核心引导
- 运行时服务:
- 电源域管理
- 系统电源管理
- 性能域管理(动态电压和频率缩放)
- 时钟管理
- 传感器管理
- 系统控制和管理接口(SCMI,平台侧),基于Arm SCMI规范
- 支持GNU Arm Embedded和Arm Compiler 6工具链
- 支持具有多个控制处理器的平台
假设到这里你就有个简单的认识了,下面话不多说,开始SCP学习。
PART 2:System Control Processor Firmware
1-什么是SCP
- 将电源和系统管理任务从应用处理器(AP)中抽象出来。
- 符合ARM系统控制和管理接口(SCMI)规范。
- 执行环境不固定。可以在RTOS或裸机环境中运行。
2-基本构建块
整个LayOut分为了三层
- 模块:
- 架构不可知
- 模块执行一组定义明确的操作。
- 框架:
- 为所有模块提供通用服务,如初始化、事件、通知和中断处理。
- 依赖于执行环境相关服务的体系结构层
- 体系结构和执行环境不可知
- 推动模块之间的初始化、协调和交互
- 体系结构:
提供依赖于执行环境的功能,如线程、中断、内存管理等。
1-Modules (struct fwk_module)
- Modules的类型
- 硬件抽象层:
- 一类驱动程序的通用接口,例如传感器。
- 其他模块通过HAL API使用平台驱动程序
- 驱动程序:
- 控制特定设备。
- 可以实现HAL模块定义的API。
- 驱动可以选择不使用HAL。
- 协议:
- 为其他模块提供特定于协议的接口,例如消息传递通道的仲裁
- 服务
- 与硬件设备无关的工作或功能。
- 可能是自包含的,不会将任何API暴露给其他模块
•产品由定义一个或多个固件目标的Product.mk文件组成。 •每个固件目标都是在构建产品时构建的二进制映像。 •固件目标完全由其模块集及其配置数据通过结构fwk_module_config定义。
- 结合
- 绑定使模块能够使用另一个模块的一组API。
- 模块提供的每组API都是唯一标识的。
- 模块元素可以提供同一组API的不同实现
2-Elements & Sub-Elements
- 元素
- 由模块拥有和管理的资源。
- 指代设备、协议或服务实例的抽象。
- 例如,驱动程序类型模块的元素可以代表每个硬件设备它控制的实例。
- 元件是可选的。
- 元件描述。
- 每个元件一个。
- 包含元素配置数据。
- 元素定义如下:
- 包含指向名称字符串的指针的结构
- 与元素关联的子元素数量
- 指向模块定义格式的数据的void指针
- 子元素
- 由一个元素拥有和管理的资源。
- 没有描述符。
举个例子:
- SENSOR HAL是一个模块。
- PVT和热敏传感器驱动器是使用传感器HAL的模块。
- PVT和热传感器分为若干组。每个组都是一个具有自己配置的元素。
- 组中的每个传感器都是一个子元素。
3-SCP固件执行步骤
运行前阶段:按固定顺序排列的5个阶段
- 模块初始化:框架使用模块配置数据调用的模块的.init()函数。
- •元素初始化:带有元素配置数据的框架调用的模块的.Element_init()函数。只有当模块具有元素时,此阶段才有效。
- •初始化后:框架调用的模块的.Post_init()函数。元素数据提供给模块后的任何额外初始化。可选阶段。
- •框架调用的模块的Bind:.Bind()函数。模块和元素绑定到其他模块和元素。可选阶段。
- •框架调用的模块的Start:.Start()函数。模块可以使用其他模块的资源来完成初始化。
可选阶段。
- •主要由模块之间的交互引导的正常执行流程。
- •生成和处理的事件、通知和响应。
4-模块间通信
Events & Notifications
Events
事件:交流请求/响应的抽象。
•在被调用者上下文中实现逻辑任务的机制。
•模块提供了.procse_event()处理程序,当找到事件目标模块时,Framework会调用该处理程序。
•当与请求相关的任务完成时,可能会发送响应事件。响应可以作为事件处理的一部分发送,也可以稍后发送。
–延迟响应:稍后发送响应,而不是在处理事件后立即发送
–标准响应:一旦.produce_event()返回,Framework就会生成响应。
–响应是一个设置了响应标志的事件。固件以与事件相同的方式进行处理。
Notifications
通知:设置了通知字段的事件。
•模块可以订阅来自其他模块的通知。
•通知由框架广播到所有订阅的模块。
•可用于实现依赖链。
–例如,如果在系统电源转换之前,我们可能需要更改时钟或设置一些
唤醒处理。模块可以使用来自系统电源模块的通知。
•事件(fwk_event) •需要响应 •延迟响应 •标准响应 •无需响应 •通知(fwk_event) •需要响应 •延迟响应 •标准响应 •无需响应
5-事件处理
创建事件-put_event()
put_event_and_wait()
模块不使用公共/框架线程。
•线程阻塞,直到事件得到处理并生成响应。
处理事件
在框架/公共线程或模块线程上下文中处理的事件
6-线程
混合协作调度模型-调度在具有相同优先级的线程之间是协作的。
•无需锁 •使代码更简单,避免了死锁的情况。 •它消除了对执行上下文/RTOS的依赖,并防止了开销。 •事件在线程上下文中按顺序处理。
SCP线程模型的特点:
- •软实时调度。
- •支持具有等优先级线程的单线程和多线程环境(无抢占)。
- •支持协作调度,如符合CMSIS的RTX RTOS。
- •不支持多处理器。
- •通过框架定义的线程API独立于直接RTOS调用。
- •这些API目前已映射到CMSIS。
CMSIS就是定义了一套芯片外设控制及编写规范的标准
1-单线程模式
- •最简单的操作模式,几乎适用于所有非基于RTO的执行环境。
- •无线程开销。
- •BUILD_HAS_MULTITHREADING未定义。
- •框架线程是唯一的线程,为所有事件提供服务。
- •模块没有自己的线程。
- •用于所有事件、响应和通知的单个事件队列。
- •当中断发生时,它将得到服务。
- •如果部分中断处理需要推迟(下半部分),则会在ISR事件队列中插入一个事件。
- •当事件队列为空时,将从ISR事件队列中提取单个事件,并将其推入事件队列的尾部。
调度模型
执行方式
事件驱动-无优先级
终于执行完毕!!!
2-多线程模式
调度模型
- •具有相同优先级的所有线程。
- •所有线程都具有相同的线程功能——由框架提供。
- •线程等待来自另一个线程的信号唤醒并处理针对它的事件。
- 适用于基于RTOS的环境。
- •BUILD_HAS_MULTITHREADING已定义。
- •一个主线程称为“公共线程”。
- •此外,每个模块都可以创建自己的线程。
- •公共线程处理所有针对没有自己线程的模块的事件。
- •每个线程都有其专用的事件队列。
- •线程用于为事件处理提供单独的模块上下文。
- •不允许运行任意线程函数。
- •只有一个线程被通知在任何时间点运行。
事件驱动-无优先级
•软实时约束(无线程优先级)
7-新模块设计指南
如果你想自己设计一个新的模块可以参考下面的几个步骤。
- 通用模块必须同时支持单线程和多线程模式。
- •特定于产品的模块可以选择支持任何线程模式。
- 模块不应该:
- •在无超时的单线程模式下阻止。
- •在调用方上下文中执行长时间运行的任务。它应该生成一个事件,并通过事件将长时间运行的任务拆分为较小的任务。
- •与其他需要锁定的模块共享上下文。
- •分配以后不会使用的内存。固件没有内存“空闲”API。没有实现动态存储器管理以保持固件的简单和高效。
PART 3:代码目录
这部分的内容我摘了前辈文章的大纲,详细的内容,在参考资料的最后部分可以去查看。
为了强调安全、简单等特性,适配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初始化流程图如下:
3.module对外接口
在scp代码中,所有的功能都由一个个模块提供。每个模块以api枚举及其结构体的方式对外提供该模块的功能,并在模块通用结构体fwk_module中提供。
4. event事件
模块可以给自己或者别的模块发送event事件,事件的参数是结构化消息structfwk_event。
5.motificaiont通知
notification涉及到两个模块的通信,跟event的区别是:
- •event是一个模块发给另外一个模块或者发给自己,比较确定
- •notification是发给订阅了这个模块的所有模块,算广播,需要先进行订阅
notification接口:
- •fwk_notification_subscribe//订阅指定模块指定通知
- •fwk_notification_unsubscribe//取消订阅通知
- •fwk_notification_notify//向订阅该通知的模块发送通知
在实现上notification使用event的消息传递机制,只在发消息和处理消息的时候做微小改动。
6.模块绑定
一个模块或元素可以绑定到另一个模块或模块内的元素。目标是相同的 - 获取指向可在后续阶段使用的 API 的指针。当尝试绑定到模块内的元素(而不是模块本身)时,主要区别在于接收和处理绑定请求的模块能够根据目标元素更改其行为。例如,可以允许请求绑定的模块仅绑定到处理请求的模块内的元素子集。
思路:A模块要与B模块通信,A模块的全局变量要拿到B模块的回调函数。
A模块在初始化的时候,会调用自己的bind函数,
bind–>fwk_module_bind–>B模块的process_bind_request()函数,从而拿到api