5 TF-A
一、TF-A的使用
TF-A是什么? TF-A 全称是 Arm Trusted Firmware,TF-A是为了保证安全,arm退出的可信固件,简称TF-A。它的作用就是隔离硬件,为硬件提供一个安全环境并提供安全服务。
1. 系统源码获取
STM32MP1Dev - STM32MP1 OpenSTLinux开发套件 - 意法半导体STMicroelectronics
先在用户目录下的linux目录里创建名为atk-mpl的目录。用FileZilla把文件传输过去。
解压文件 tar -xvf en.SOURCES-stm32mp1-openstlinux-5-4-dunfell-mp1-20-06-24.tar.xz
进入解压后的目录。
u-boot-stm32mp-2020.01-r0 | uboot 源码,版本号为 2020.01 |
linux-stm32mp-5.4.31-r0 | linux 源码,版本号为 5.4.31 |
tf-a-stm32mp-2.2.r1-r0 | tf-a 源码,版本号为 2.2,我们用到的 |
tf-a-stm32mp-ssp-2.2.r1-r0 | tf-a 源码, ssp 全称为 secure secret provisioning, 安全相关的内容 |
optee-os-stm32mp-3.9.0.r1-r0 | optee 系统源码,版本为 3.9.0 |
tf-a-stm32mp-2.2.r1-r0的目录文件如下:
0001-st-update-v2.2-r2.0.0.patch | TF-a 补丁文件。 |
Makefile.sdk | 编译 TF-A 用的 Makefile |
README.HOW_TO.txt | ST 官方 TF-A 的编译文档,编译方法看此文档即可。 |
series | 存放补丁名字的文件 |
tf-a-stm32mp-2.2.r1-r0.tar.gz | TF-A 的源码压缩包 |
2. TF-A源码打补丁
0001-st-update-v2.2-r2.0.0.patch这个文件其实就是ST为TF-A官方源码做的补丁。
打补丁需要两个文件:
① 补丁文件:0001-st-update-v2.2-r2.0.0.patch;
② 需要补丁的源文件:tf-a-stm32mp-2.2.r1-r0.tar.gz。
先对源文件进行压缩:tar -vxf tf-a-stm32mp-2.2.r1-r0.tar.gz
接下来给TF-A源码打补丁。
for p in `ls -1 ../*.patch`; do patch -p1 < $p; done
上面这条命令的意思是把上一层目录下的所有“.patch”后缀的文件都通过 patch 命令打补丁到 TF-A 的源码目录,在这里就是将 001-st-update-v2.2-r2.0.0.patch 这个补丁打入到 TF-A源码里面。
在atk-mpl目录下创建"tf-a"目录。
cd tf-a-stm32mp-2.2.r1-r0/
cp * /home/zuozhongkai/linux/atk-mp1/tf-a/ -rf
3. stm32wrapper4dbg工具安装
在编译TF-A或者Uboot需要用到这个工具,否则编译会报错。
下载地址:https://github.com/STMicroelectronics/stm32wrapper4dbg
用这个命令解压:unzip stm32wrapper4dbg-master.zip
进入stm32wrapper4dbg-master这个文件夹后,输入命令 make
,就会得到
这个stm32wrapper4dbg就是我们需要的工具,把它拷贝在bin目录下,命令如下:
sudo cp stm32wrapper4dbg /usr/bin
检测是否安装成功,输入命令:stm32wrapper4dbg -s
,如果出现以下内容则安装成功。
4. 编译正点原子官方的TF-A
① 准备正点原子出厂的TF-A源码
首先安装设备树编译相关命令:sudo apt-get install device-tree-compiler
在atk-mpl目录下新建一个"alientek_tf-a"的子目录,把 tf-a-stm32mp-2.2.r1-g463d4d8-v1.0.tar.bz2 这个文件解压,输入 tar -xvf tf-a-stm32mp-2.2.r1-g463d4d8-v1.0.tar.bz2
解压完成后如图。
② 修改Makefile
Makefile.sdk定义了一些编译属性,比如交叉编译器、编译的一些选项。默认Makefile.sdk使用的是ST官方的交叉编译器,需要改为我们现在使用的。
③ 编译TF-A
cd tf-a-stm32mp-2.2.r1/ //进入到正点原子出厂 TF-A 的源码目录
make -f ../Makefile.sdk all //编译 TF-A
-f:重新指定Makefile,这里指Makefile.sdk。
编译成功后,alientek_tk-a会多出来一个子目录,build。
进入build目录下,会看见三个子目录,我们只看trusted目录下的文件,此目录下保存了MP1所有型号的TF-A固件。
4. 编译正点原子官方的TF-A
4.1 准备烧写材料
这两个都是正点原子官方提供。
1、tf-a-stm32mp157d-atk-serialboot;
2、u-boot.stm32;
3、之前编译的build文件夹里面的tf-a-stm32mp157d-atk-trusted。
4.2 准备FlashLayout
现在正点原子包里找到:atk_emmc-stm32mp157d-atk-qt,重命名为tf-a;
.tsv文件是需要Notepad++来打开,下载地址:Downloads | Notepad++ (notepad-plus-plus.org)
tsv 语法要求只能用 TAB 键,不能用空格!
Opt | 选项字段,可以设置为“-”、“P”、“D”或“E” |
Id | 会根据这个 id 来决定烧写分区 |
Name | 分区名字 |
Type | 制定烧写的类型,仅 uboot 使用。 |
Device | 指定烧写的设备类型与编号,比如 emmc0、 emmc1、 nand0 等,如果 opt 为‘-’ ,那么此字段就为 none |
Offset | 分区的起始位置,如果为“boot1”表示 EMMC 的第一个分区,如果为“boot2”就表示 EMMC 第二个分区。如果是数字就表示需要偏移的字节数。 |
Binary | 要烧录的文件 |
① Opt域:
'-' : none,也就是空选项,分区或者设备无需修改,如果 Device 域为 none,那么 Opt强制为‘-’。
'P' : 向分区或者设备烧写固件。
'E' : 空分区或设备,表示对应的分区或设备不更新,相关的 Id 项会被跳过。
'D' : 删除分区或设备。
允许组合选项:
'PE' : 不更新,也就是指定某个分区或者设备不需要烧写固件,这样我们就可以单独只更新 tf-a、 uboot、 kernel 或者 rootfs。
'PD' : 删除并更新,也可以写作 DP。
'PDE' : 删除并且保持为空,也可以写作 PED/DPE/DEP/EPD/EDP。
② ID域:
STM32CubeProgrammer 通过 Id 域来确定烧写方法,会通过 Id 域来识别下一个要烧写到设备里面的二进制文件:
0x01~0x0F | 带有 STM32 头部信息的 Boot 分区,如 SSBL、 FSBL、其他(TEE 或 M4 固件 |
0x10~0xF0 | 不带头部的用户编程分区,如 uimge、 dtb、 rootfs、 vendorfs、 userfs |
③ Name域:
Name 域为一段字符串,也就是目标内存段的名字。
④ Type域:
Type 域仅仅用于 uboot,用来选择需要更新的 Flash 区域。SD 卡或者 EMMC 设备对应 GPT 分区。原始的 Flash 设备,如 NAND、 NOR 等对应 MTD 分区。
EMMC类型的Type域:
Binary: 原始的二进制文件。
FileSystem: linux 文件系统,为 ext2/ext4/fat 格式。
System: Linux 内核。
⑤ Device域:
Device 域指定 Uboot 设备树定义的设备和索引(从 0 开始),不同的设备其设备名字和索引不同。
mmc+索引:如 mmc0、 mmc1、 mmc2 等,对应 SD 卡或 EMMC。比如 SD 卡和 EMMC分别接到 MP1 的 SDMMC1 和 SDMMC2 接口上,那么 SD 卡和 EMMC 分别为 mmc0 和 mmc1。
nor+索引:如 nor0,对应 NOR 或者 QUADSPI Flash。
mmc+索引:如 nand0,对应连接到 FMC 总线上的并行 NAND Flash。
spi-nand+索引:如 spi-nand0,对应连接到 QSPI 上的串行 NAND Flash。
none: RAM,也就是将固件加载到 RAM 里面,仅允许启动阶段使用,而且 Type 域要为 Binary, Offset 域要为 0, Opt 域为‘-’。
ram+索引:如 ram0,烧写服务讲固件加载到 RAM 中运行。
⑥ Offset域:
Offset 就是偏移,支持的值如下:
boot1: EMMC 的第一个启动区域分区。
boot2: EMMC 的第二个启动区域分区。
数字: 具体的偏移值,单位为字节。
⑦ Binary域:
STM32CubeProgrammer 软件要使用的二进制文件。
5. USB烧写TF-A
首先先将两个Type-C的线连接到STM32MP157上,会出现下面这两个就正确。
6. TF-A的运行
接下来就是测试一下看能不能运行,打开MobaXterm 软件,设置好与开发板连接的串口,波特率选择 115200。设置开发板拨码开关为 010,也就是从 EMMC 启动,然后复位开发板!注意,由于我们开发板默认已经烧写了整套 Linux 系统,所以直接启动的话会启动整个系统,包括我们刚刚烧写的自己编译的 TF-A,原有的 uboot、 Linux 系统等。 TF-A 是最先启动的,也就是最前的就是我们自己编译的 TF-A。
设置开发板拨码为010,EMMC启动,然后复位开发板。
二、TF-A 初探
1. ARMv7-A 工作模式
以前的 ARMv7 处理器有 7 中运行模型: User、 FIQ、 IRQ、 Supervisor(SVC)、 Abort、 Undef和 System。但新的 ARMv7-A 架构加入了 TrustZone 安全扩展,所以就新加了一种运行模式:Monitor,新的处理器架构还支持虚拟化扩展,因此又加入了另一个运行模式: Hyp,所以 CortexA7 处理器有 9 种处理模式。
User(USR):用户模式,非特权模式,大部分程序运行的时候就处于此模式。
FIQ: 快速中断模式,进入 FIQ 中断异常。
IRQ: 一般中断模式。
Supervisor(SVC): 超级管理员模式,特权模式,供操作系统使用。
Monitor(MON): 监视模式?这个模式用于安全扩展模式。
Abort(ABT): 数据访问终止模式,用于虚拟存储以及存储保护。
Hyp(HYP): 超级监视模式?用于虚拟化扩展。
Undef(UND): 未定义指令终止模式。
System(SYS): 系统模式,用于运行特权级的操作系统任务。
不同的处理器模式下, CPU 对于硬件的访问权限不同,叫做 Privilege Level(特权等级),一共有两个特权级别: Privilege(特权级)和 non-privilege(非特权级)。其中只有 User 模式处于nonprivilege,也就是非特权级,剩下的 8 个模式都是 privilege(特权级)。系统启动以后应用软件都是运行在 User 模式,也就是非特权级,这个时候处理器对于敏感资源的访问是受限的,如果要访问这些敏感资源就需要切换到对应的工作模式下。
ARMv7-A 对 Privilege Level 进行了命名: PL0 和 PL1,后来也出现了 PL2,用于虚拟扩展。ARMv7-A 新增的 Monitor 模式就是针对安全扩展的,为了支持 TEE 而引入的。
2. ARMv8 工作模式
ARMv8 没有 Privilege level 的概念,取而代之的是 Exception level(异常级别),简称为 EL,用于描述特权级别,一共有 4 个级别: EL0、 EL1、 EL2 和 EL3,数字越大,级别越高,权限越大!这四个 EL 级别对应的应用场合如下:
EL0:一般的应用程序。
EL1: 操作系统,比如 Linux。
EL2:虚拟化(Hypervisor),虚拟机管理器。
EL3: 最底层的安全固件(安全监视器),比如 ARM Trusted Firmware(ARM 安全固件, ATF,也就是 TF-A)
ARMv8 提供了两种安全状态: Secure 和 Non-secure,也就是安全和非安全, Non-secure 也就是正常世界(Normal World)。我们可以在 Non-secure 运行通用操作系统,比如 Linux,在 Secure运行可信操作系统,比如 OP-TEE,这两个操作系统可以同时运行,这个需要处理器支持 ARM的 TrustZone 功能。
只有 EL3 是用于安全监视器的,所以 TF-A 主要工作在 EL3 下,在看 TFA 源码的时候会看到大量的“EL3”字样的文件或代码。
3. TF-A 不同启动阶段
当芯片复位以后首先运行 bl1 代码, bl1 一般是芯片内部的 ROM 代码, bl1 主要工作就是将外置 Flash 中的 bl2 固件加载到指定的 RAM 中,然后跳转到 bl2 部分。
bl2 为安全启动固件, bl2 会将剩余的三个启动阶段 bl31、 bl32 和 bl33 对应的镜像文件加载到指定的内存中。比如 bl32 中的安全操作系统(OP-TEE), bl31 中的 EL3 运行时固件(RuntimeFirware), bl33 中的 uboot。 bl2 将这些固件加载完成以后就会启动相应的固件,也就是进入到第三启动阶段。
TF-A 启动流程就是: bl1→ bl2→( bl31/ bl32/ bl33)。注意, bl31、 bl32 和 bl33 对应的镜像不需要全部都有,但是 bl33 一般是必须的,因为 bl33 一般是 uboot,这个是很重要的!
① bl1
bl1 是 TF-A 的第一个启动阶段,芯片复位以后就会运行 bl1 镜像, TF-A 提供了 bl1 源码。但是,实际上 bl1 一般是半导体厂商自己编写的内部 Boot ROM 代码,并没有使用 TF-A 提供的bl1 镜像,比如 STM32MP1 的内部 ROM 代码就是 bl 1。因此 bl1 部分的实现就千差万别,不同的半导体厂商有不同的实现方法。
一般 bl1 要做的就是初始化 CPU,如果芯片支持不同的启动设备,那么还需要初始化不同的启动设置,比如 NAND、 EMMC、 SD、 USB 或串口等。然后根据 BOOT 引脚的高低电平来判断当前所选择的启动设备,从对应的启动设备中加载 bl2 镜像,并放到对应的内存中,最后跳转到 bl2 镜像并运行。
② bl2
bl 2 会进一步的初始化芯片,比如初始化 DDR、 MMU、串口等。 bl2 会将剩下三个阶段(bl31、 bl32 和 bl33)对应的镜像加载到指定的内存中,最后根据实际情况来启动剩下三个阶段的镜像。
③ bl31
在 AArch64 中, bl31 主要是 EL3 的 Runtime 固件。
④ bl32
bl32 一般为安全系统(TEE OS)固件,比如 OP-TEE。TF-A 为 AArch32 提供了 EL3 的 Runtime软件,这个 Runtime 软件就是 bl32 固件, sp_min 就是这个 Runtime 软件。
⑤ bl33
bl33 就是 Normal World 下的镜像文件,比如 uboot。
3. STM32MP1 TF-A 框架
STM32MP1 软件架构如下图:
左到右分为三部分: Cortex-A7 Secure、 Cortex-A7 Non-Secure 和 Cortex-M4。我们只看 Cortex-A7 Secure、 Cortex-A7 Non-Secure,也就是 A7的安全和非安全两种情况。
bl1: 第 1 个阶段,一般为芯片内部 ROM 代码。
bl2:第 2 个阶段,可信启动固件。
bl32: EL3 运行时(Runtime)软件。
bl33:非安全固件,比如 uboot。
其中 bl1、 bl2 和 bl32 都属于 TF-A 的一部分(如果你使用 TF-A 提供的 bl1 的话)。
3.1 STM32MP1下的bl1
bl1 部分是可选的,在编译 STM32MP1 的 TF-A 的时候可以通过添加 BL2_AT_EL3 编译选项来移除 bl1,默认情况下 ST 提供的 TF-A 源码是有添加 BL2_AT_EL3 编译选项的,在 TF-A源码里面找到 tf-a-stm32mp-2.2.r1/plat/st/stm32mp1/platform.mk。
platform.mk 文件定了 BL2_AT_EL3 为 1,因此在编译 STM32MP1平台对应的 TF-A 的时候不会编译 bl1 部分, STM32MP1 内部 ROM 代码完成了 TF-A 中的 bl1部分的工作,主要就是将外部 Flash 中的 bl2 代码加载到内部 RAM 中并运行。
3.2 STM32MP1下的bl2
bl2 为可信启动固件,在 STM32MP1 中就是 TF-A 的 bl2 部分, bl2 的主要功能就是加载下面几个阶段的固件到内存中,因此 bl2 需要初始化所要用到的外设。
首先是安全部分, STM32MP1 的 bl2 部分会初始化的外设如下:
① BOOT、安全和 OTP 控制器,也就是 BSEC 外设。
② 扩展的 TrustZone 保护控制器,也就是 ETZPC 外设。
③ TrustZone 针对 DDR 的地址空间保护控制器,也就是 TZC 外设。
由于 bl2 需要从外部 flash 中加载下一阶段的镜像,因此还需要初始化一些外部 flash:
① SD 卡。
② EMMC
③ NAND。
④ NOR。
STM32MP1 的 bl2 部分还要初始化一些其他的外设:
① DDR 内存。
② 时钟。
③ 串口,用于调试以及使用 STM32CubeProgrammer 的时候通过串口下载系统。
④ USB,用 STM32CubeProgrammer 通过 USB 烧写系统的时候需要用到。
bl2 还需要对镜像进行验证和鉴权,鉴权是通过调用内部 ROM 代码的鉴权服务来完成。最后, bl2 会加载 bl32 和 bl33 的固件到指定的内存区域,并跳转到 bl32, bl32 接着运行。
3.3 STM32MP1下的bl32
bl32 提供运行时安全服务,在 TF-A 中默认使用 sp_min。bl32 充当安全监控(secure monitor),因此它向非安全系统(non-secure os,比如 linux)提供了一些安全服务。非安全的应用软件可以通过安全监控调用(secure monitor calls)来使用这些安全服务。
3.4 STM32MP1下的bl34
其实就是传统Uboot。
简单总结一下,默认情况下 TF-A 有 bl1、 bl2、 bl31、 bl32 和 bl33 这几个启动阶段。如果bl32 使用 sp_min 的话那么 bl1、 bl2、 bl31 和 bl32 都属于 TF-A。但是对于 STM23MP1 而言,因为其使用的是 AArch32,因此没有 bl31 部分。而 bl1 部分 ST 又没有用 TF-A 提供的,采用的是STM32MP1 内部 ROM 代码,因此就只剩下了 bl2 和 bl32。所以对于 STM32MP1 而言, TF-A就两个固件: bl2 和 bl32(sp_min), TF-A 源码也采用了设备树(device tree)来设备信息,因此对于 STM32MP1 而言, TF-A 一共有三部分:设备树、 bl2 和 bl32,这三部分在编译的时候会被合并成一个二进制文件。在最前面加上重要的头部信息,最终这 4 部分就组成了我们烧写到外部 flash中的TF-A 镜像。
1、复位以后内部 ROM 加载 TF-A 整个镜像,然后运行 bl2 镜像。
2、 bl2 将 bl32 镜像加载到指定内存区域。
3、 bl2 将 bl33 镜像加载到指定内存区域。
4、 bl2 执行完毕以后就会跳转到 bl32 镜像。
5、 bl32 镜像执行完以后跳转到 bl33 镜像,也就是 uboot。
三、TF-A 移植
所谓的移植就是让半导体官方提供的软件在自己的硬件平台上运行起来,准确的说应该是将自己的硬件添加到官方软件包。TF-A 是 ARM 官方出品的一个软件包,半导体厂商会从 ARM官方下载这个最正宗的 TF-A 软件包,然后将自己公司的 SOC 芯片添加进去,最终打包好提供给 SOC 用户,这个就是所谓的 SDK 包。
既然硬件参考了 ST 官方的开发板,那么软件肯定也是在官方提供的软件包基础上修改的,所以 TF-A 的移植就是在 ST 官方提供的 TF-A 源码上进行。首先肯定要先编译一下 ST 官方提供的 TF-A 源码,目的如下:
① 掌握半导体官方软件编译方法。
② 验证开发环境搭建是否正确,比如交叉编译器设置是否正确,所依赖的第三方库有没有安装等。
③ 观察编译结果,比如编译完成以后的可执行文件保存在哪个目录下,都有哪些可执行文件,其区别是啥等。
④ 在自己的开发板上运行编译出来的可执行文件,所谓的移植就是改 bug 的过程,将编译出来的 STM32MP157 官方开发板可执行文件在自己的板子上运行,然后观察运行过程有没有错误,没错误最好(基本上不可能没错,除非完全参考原厂开发板设计的),有错误就改错误,直到能在自己的开发板上正常运行,这就是移植过程。
1. 编译TF-A
首先在/linux/atk-mpl/tf-a这个目录下创建子目录'my-tfa'来保存并编译ST官方的TF-A源码。 Makefile.sdk 和 tf-a-stm32mp-2.2.r1, Makefile.sdk 是我们一会编译要用到的 Makefile,其实 Makefile.sdk 主要是配置编译选项,最终是通过调用 tf-a-stm32mp-2.2.r1 下的 Makefile 来完成具体编译过程的。 tf-a-stm32mp-2.2.r1 就是打完补丁后的 ST 官方 TFA 源码文件,也就是我们要编译的。
1.1 修改Makefile.sdk
这个文件是定义编译选项,包括交叉编译器,我们要使用自己的交叉编译器。跟上面修改交叉编译器一样的,打开Makfile.sdk。
cd tf-a-stm32mp-2.2.r1/ //进入到 tf-a 源码目录下
make -f ../Makefile.sdk all //编译
编译完成后会在Makefile.sdk同级目录下生成build文件夹。
进入build目录可以看见三个子目录
optee 是提供了一个可信执行环境(TEE),用于在 ARM 架构的处理器上运行安全和受保护的应用程序。OP-TEE 的源代码存储在名为 "optee" 的文件夹中。
serialboot 是和串行启动有关的,也就是通过串口或者 USB 烧写系统的时候需要用到 serialboot 下的可执行文件。
trusted 目录下的就是烧写到开发板中的 TF-A 镜像。
tf-a-stm32mp157d-ev1.bin:使用 STM32MP157D 芯片的 EV1 开发板对应的 TF-A 二进制可执行文件。
tf-a-stm32mp157d-ev1.elf:生成 bin 文件所使用的 elf 文件。
tf-a-stm32mp157d-ev1.ld:连接信息。
tf-a-stm32mp157d-ev1.map:内存映射文件。
tf-a-stm32mp157d-ev1.stm32:添加好头部信息的 bin 文件,可以直接烧写到开发板中。
tf-a-stm32mp157d-ev1-trusted.stm32和 tf-a-stm32mp157d-ev1.stm32 文件一模一样,只是重命名了一下,为了和 optee 以及 serialboot 目录下的 tfa 固件进行区分。
1.2 烧录ST官方TF-A镜像
将编译出来的 tf-a-stm32mp157d-ev1-trusted.stm32 烧写到正点原子开发板 EMMC里面,烧写方法参考上面所讲讲解的。烧写完成以后打开串口,设置好串口调试软件 MobaXterm,设置开发板从 EMMC 启动,按下复位按键,观察 MobaXterm 软件接收到的 log 信息。
意思就是运行崩溃了,说明 ST 官方开发板对应的 TF-A 不能直接在我们的开发板上运行,需要进行修改。
1. TF-A移植
1.1 新建开发板对应的设备树
① 创建设备树
设备树英文名字叫做 Device tree,用来描述板子硬件信息的,比如你的板子上的 CPU 有几个核、每个 CPU 核主频是多少, IIC、 SPI 这些外设的寄存器范围是多少, IIC 接口下都挂了哪些设备等等。设备树文件是一种文本格式的文件,方便阅读与修改,文件后缀为.dts,设备树也有头文件,头文件后缀为.dtsi。
打开tf-a-stm32mp-2.2.r1下的fdts就是设备树文件,包含了STM32MP1系列。用VS Code打开stm32mp157d-ev1.dts,如下图。
在设备树里, 可以直接通过 include 引用另外一个.dts 文件,不仅限于.dtsi 文件,也就是说.dts 文件也可以作为头文件使用。
进入fdts目录下:
cd fdts
cp stm32mp157d-ed1.dts stm32mp157d-atk-luo.dts //复制
未完待续....