一、TF-A概述
对传统ARM 处 理 而 言 , Linux 系 统 的 启 动 流 程 就 是 : 内部BootROM→Uboot→kernel→rootfs,整个启动过程是一个链式结构,启动过程其实是没有安全校验的。加入 TF-A 固件以后, TF-A 就可以对 uboot、 kernel进行校验,如果还要使用 TEE OS(Trusted Execution Environment, TEE),那么 TF-A 还要完成对TEE OS 的校验。
STM32MP1 使用 ECDSA(Elliptic Curve Digital Signature,椭圆曲线数字签名算法)验证算法来完成鉴权,ECDSA 相比 RSA 效果好,而且秘钥更小,STM32MP1 使用 256 位的 ECDSA 秘钥。一共有两个算法用于计算 ECDSA:P-256 NIST 和 Brainpool 256。可以在 STM32MP1 头部信息中定义使用哪个算法,启动的时候,内部 Boot 代码会对 FSBL 进行鉴权,然后 FSBL 会对后续的内容进行鉴权,这个 FSBL 就是 TF-A。
对于STM32MP1 而言, TF-A 一共有三部分:设备树、 BL2 和 BL32,外加一个头部文件。
二、Arm-v7工作模式
其中Monitor和Hyp为新引入的工作模式。只有 User 模式处于 nonprivilege,也就是非特权级,剩下的 8 个模式都是 privilege(特权级)。系统启动以后应用软件都是运行在 User 模式,也就是非特权级,这个时候处理器对于敏感资源的访问是受限的,如果要访问这些敏感资源就需要切换到对应的工作模式下。
三、Arm-v8工作模式
ARMv8 没有 Privilege level 的概念,取而代之的是 Exception level(异常级别), 简称为 EL,
用于描述特权级别,一共有 4 个级别。
EL0:一般的应用程序
EL1: 操作系统,比如 Linux。
EL2:虚拟化(Hypervisor),虚拟机管理器。
EL3: 最底层的安全固件(安全监视器),比如 ARM Trusted Firmware(ARM 安全固件,TF-A)
在Arm-v8的AArch32模式下:
EL0: 对应 ARMv7 的 User 工作模式。
EL1: 对应 ARMv7 的 SVC、 ABT、 IRQ、 IRQ、 UND 和 SYS 这 6 中工作模式。
EL2: 对应 ARMv7 的 Hyp 工作模式。
EL3: 对应 ARMv7 的 Mon 工作模式。
所以 TF-A 主要工作在 EL3 下。
BL1:存储在核内固化的ROM中,上电时启动,加载到核内RAM中,用来初始化DDR、时钟等。
BL2:存储在外置Flash,被BL1一通操作然后加载到外置内存中。BL2将剩余的第三阶段镜像文件加载到指定的RAM中,完成启动。
BL31/BL32/BL33:BL31包含运行时固件(Runtime Firware),BL32包含安全操作系统(OP-TEE),BL33包含uboot。
四、移植ST官方TF-A
1.修改直接解压的ST官方TF-A源码包,将Makefile.sdk中的交叉编译链修改,之后进行编译。
此处编译遇到了一个问题,编译报错:
/bin/sh: 6: fiptool-stm32mp: not found
安装ST官网下载stm32的en.SDK-x86_64-stm32mp1-openstlinux-5.10-dunfell-mp1-21-11-17.tar.xz工具包(名称可能会随版本更新有些许不同)再次编译报错:
Missing u-boot-stm32mp157a-dk1-trusted.dtb file in folder: '$FIP_DEPLOYDIR_UBOOT' or '$FIP_DEPLOYDIR_ROOT/u-boot'
解决办法:
1.在编译前使用export FIP_DEPLOYDIR_ROOT=****/stm32mp1-openstlinux-5.15-yocto-kirkstone-mp1-v22.06.15/sources/arm-ostl-linux-gnueabi/FIP_artifacts命令将FIP路径声明
2.安装官方推荐的arm-none-linux-guneabihf-交叉编译工具(不确定,为了排除不可控因素换的工具,可自行验证别的工具链),不要将TF-A的文件夹移到别的位置,直接在原目录下编译。可以少出很多错误,也可以节省很多步骤。
在此使用第二种,一劳永逸。
五、官方TF-A修改
官方的TF-A直接烧写会运行崩溃,需要针对板子做一些修改。
此部分对V2.6版本TF-A的修改参考了ATK-DLMP157开发板的5.15内核的移植-OpenEdv-开源电子网的移植案例。
1.创建板子对应的设备树
在xxx/tf-a-stm32mp-v2.6-stm32mp1-r1/fdts文件夹下的stm32mp157d-ev1.dts即为板子的设备树源文件,此源文件直接引用了stm32mp157d-ed1.dts,所以在此基础上直接使用,然后添加板子的相应信息即可。
#复制dts文件作为自己的模板
cp stm32mp157d-ed1.dts stm32mp157d-my.dts
#复制dtsi文件作为头文件引用
cp stm32mp15xx-edx.dtsi stm32mp157d-my.dtsi
#在*-my.dts中修改相关的.dtsi文件引用
cp stm32mp157d-ev1-fw-config.dts stm32mp157d-my-fw-config.dts
#此文件和FIP相关不用修改直接可以使用
2.修改设备树电源管理
默认的电源芯片使用的是PMIC电源芯片,价格比较昂贵。在实际项目中使用分离电源设计,使用IIC接口来控制多个较廉价的电源芯片,所以需要修改对应的芯片接口。
将stm32mp157d-my.dtsi中61行左右有关stpmic的代码删除(忘记此部分是否为i2c4节点的代码了,如果是,将i2c4节点全部删除),在21行左右的vin节点中添加自己使用的电源芯片信息。
另外,在V2.6版本中此文件的末尾还有很多与电压有关的描述,直接参考上述的移植案例全部删除即可。
#描述 VDDCORE 电源,也就是 STM32MP157 的内核电源,最小为 1.2V,最大为 1.35V。
vddcore: regulator-vddcore {
compatible = "regulator-fixed";
regulator-name = "vddcore";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1350000>;
regulator-off-in-suspend;
regulator-always-on;
};
#描述 3.3V 电源,最小和最大都是 3.3V
v3v3: regulator-3p3v {
compatible = "regulator-fixed";
regulator-name = "v3v3";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-off-in-suspend;
regulator-always-on;
};
#描述 VDD 电源,这是一个 3.3V 的电源,所以最小和最大都为 3.3V
vdd: regulator-vdd {
compatible = "regulator-fixed";
regulator-name = "vdd";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-off-in-suspend;
regulator-always-on;
};
#描述 VDD_USB 电源,为 3.3V,所以最小和最大都为 3.3V
vdd_usb: regulator-vdd-usb {
compatible = "regulator-fixed";
regulator-name = "vdd_usb";
regulator-min-microvolt = <3300000>;
regulator-max-microvolt = <3300000>;
regulator-off-in-suspend;
regulator-always-on;
};
除此部分外,源码中还有一些关于pmic的描述代码,在源码目录中使用以下命令进行删除:
#在源码目录下使用此命令,把pmic相关的注册干掉
grep -rns fixed_regulator_register
#此文件的502行,干掉
gedit plat/st/stm32mp1/sp_min/sp_min_setup.c
#此文件的351行的函数段,干掉
gedit plat/st/stm32mp1/bl2_plat_setup.c
3.修改TF卡和EMMC设备树
继续操作 stm32mp157d-my.dtsi 这个文件,修改“sdmmc1”和“sdmmc2”节点。
&sdmmc1 {
pinctrl-names = "default";
pinctrl-0 = <&sdmmc1_b4_pins_a &sdmmc1_dir_pins_a>;
st,neg-edge;
broken-cd;
bus-width = <4>;
vmmc-supply = <&v3v3>;
status = "okay";
};
&sdmmc2 {
pinctrl-names = "default";
pinctrl-0 = <&sdmmc2_b4_pins_a &sdmmc2_d47_pins_a>;
non-removable;
st,neg-edge;
bus-width = <8>;
vmmc-supply = <&v3v3>;
vqmmc-supply = <&v3v3>;
status = "okay";
};
4.修改USB OTG设备树
&usbotg_hs {
phys = <&usbphyc_port1 0>;
phy-names = "usb2-phy";
usb-role-switch;
status = "okay";
};
5.修改usbphyc节点
修改里面的“status”属性值,看起来就是向 stm32mp157d-my.dtsi文件里面添加了一个名为“usbphyc”的节点。文件中原本存在一个&usbphyc_port0和&usbphyc_port1节点,不做修改,直接添加上述新节点即可。
&usbphyc {
status = "okay";
};
添加完成后直接在源码目录下编译。
编译命令:
make CROSS_COMPILE=arm-none-linux-gnueabihf- PLAT=stm32mp1 ARCH=aarch32 ARM_ARCH_MAJOR=7 DTB_FILE_NAME=stm32mp157d-my.dtb STM32MP_SDMMC=1 STM32MP_EMMC=1 V=1 DEBUG=1
注:此部分不使用Makefile.sdk脚本,使用官方文档中推荐的编译命令。可以按照需求针对某一特定版本的设备进行编译,较少编译时长和输出结果。输出结果在:tf-a源码目录/build/stm32mp1/debug/下
另外,参数中指定了SDMMC和EMMC,所以虽然只编译出来一个.stm32文件,但是在SD卡和EMMC中通用。另外V2.6版本对编译产物做了一些划分和精简,旧版本中tf-a-stm32mp157d-dk1.stm32和tf-a-stm32mp157d-dk1-truest.stm32为同一文件,仅修改了文件名。此版本中统一为tf-a-stm32mp157d-xxx.stm32,所以直接使用该文件烧写即可。
6.编译出xxx-serialboot.stm32
在V2.6版本中,对serial boot模式的编译方式做了一些修改。serial boot模式有两种烧写方式:uart和usb口烧写。正点原子开发板使用的serial boot模式为usb模式,所以直接编译出usb模式的BL2镜像文件即可。
#清空原本的编译产物
make distclean
#编译USB模式的BL2镜像
make CROSS_COMPILE=arm-none-linux-gnueabihf- PLAT=stm32mp1 ARCH=aarch32 ARM_ARCH_MAJOR=7 DTB_FILE_NAME=stm32mp157d-my.dtb STM32MP_USB_PROGRAMMER=1 V=1 DEBUG=1
编译出的文件与之前的EMMC版本镜像同名,所以先将之前的.stm32移走,清空后重新编译。
至此,正点原子教程中的xxxx-serialboot.stm32和xxxx-atk.stm32镜像都编译完成
但是直接烧写后,启动时会出现如下错误,原因为对应版本的u-boot没有烧写,与正点原子使用的版本存在一些出入。后续编译出V2.6版本的U-Boot烧写即可。