今年暑假实习时,笔者参与了 arm 可信启动的研发工作。这两个月的经历,日后再写文详谈。笔者负责的模块接近物理层,向上提供一些安全功能;经过层层包装,这些功能最终由位于「安全世界」的 OP-TEE 之 TA 使用,而位于「非安全世界」的 app 则可以透过 OS 提供的接口,与 TA 通讯。
笔者的工作中并未直接接触 OP-TEE,但对 OP-TEE 产生了一些兴趣。另,笔者以前做嵌入式开发,都是基于 esp32、stm32 等 MCU,它们不具有 MMU,故无法运行完整的 Linux 系统,功能有限。笔者亦希望步入嵌入式 Linux 领域,进行一番学习。两方面因素影响下,笔者决定今年下半年投入一些时间,研究嵌入式 Linux。
本篇博客可以算作是第一篇学习笔记。我们将在本文中启动一个 embedded Linux。
0x00 挑选开发板
笔者用了几个小时时间,才决定要采用哪一款开发板。这其中有不少的待考虑因素,其中最主要的是各开源组件的支持程度。众所周知,许多性价比很高的国产开发板,未获得足够的社区支持,官方也懒于更新 SDK,以至于 Linux 内核版本被迫停留在较古老的时代。这是我们要尽量避免的问题。
笔者手头现有的 Cortex-A 设备,只有树莓派 4B 和一些旧手机。手机当然不能用于学习嵌入式 Linux 开发——光是引出串口和 GPIO 就是麻烦事。而树莓派 4B,尽管它拥有庞大且友好的社区,可以运行各种 Linux 发行版,但并未被 OP-TEE 开发团队官方支持。OP-TEE 文档写道,其支持树莓派 3B/3B+,不支持树莓派 4B。另一方面,树莓派采用了极为特殊的启动方式:它不是从 CPU 启动,而是从 GPU 启动。这导致树莓派的启动流程与主流嵌入式 Linux 区别过大,也不适合入门研究。
查阅 OP-TEE 文档,发现开发团队官方支持的平台主要来自 NXP(恩智浦)、Rockchip(瑞芯微)、ST(意法)等厂商。在网购平台搜索,发现这三家的代表性产品是恩智浦的 i.MX 6ULL、ST 的 STM32MP157 以及瑞芯微的 RK3399。考虑到 OP-TEE 在开源之前便是 ST 公司的内部项目,且支持 STM32MP157 全系列官方开发板,故最终选择了 STM32MP157 SoC 来学习 OP-TEE。
笔者之前入手过 ST 官方的 STM32 F3 Discovery 开发板,用于学习嵌入式开发,文档颇全面。本次也购买了 ST 出品的 STM32MP157D-DK1 开发板。在这里先简述 STM32MP157 系列产品:
STM32MP157 拥有两个 Cortex-A7 核、一个 Cortex-M4 核。A7 核拥有较强的性能,可用于运行 Linux;M4 核是 MCU 核,可以运行一些要求强实时性的任务。其中 A7 核的指令集是 armv7,只支持 32 位。开发板自带一个板载的 ST-Link v2。
上图中,有蓝色锁标记的 MPU 拥有密码学加速单元,支持安全启动。我们选用的 D 型号没有这些支持,不过似乎不影响我们使用 OP-TEE。
ST 为官方开发板维护了拿来即用的 TF-A、U-Boot 和主线内核。然而嵌入式 Linux 的学习,一项重要内容便是把这三个组件移植到自己的环境上。出于此目的,笔者另购买了正点原子的 157 mini 开发板。
0x01 验货
收货之后,笔者为两个开发板刷写了各自的官方固件。ST 的 SDK 更新比较频繁,内核版本是 6.1.28:
正点原子官方提供的固件,是从 ST 的版本 fork 的,内核版本 5.4.31:
跑通了手册上的一些用例,各个组件均无问题。下面,我们自行编译 TF-A、U-Boot 和 Linux 内核,点亮这两块开发板。
0x02 STM32MP1 启动流程
Linux 的通用启动流程如下图所示:
- 系统上电后,首先执行 ROM 中固化的代码。这部分代码的主要工作是将 FSBL(第一阶段 bootloader)加载到内部 RAM 中,并跳转执行。
- FSBL 的任务包括初始化时钟树和外部 RAM 控制器。接下来,FSBL 将 BSBL(第二阶段 bootloader)加载到外部 RAM,跳转到 BSBL。
- BSBL 拥有很大量的内存资源,它可以进行一些复杂的任务,例如控制显示器。BSBL 负责加载 Linux 内核(可以从文件系统或网络获取)。
- Linux 内核负责初始化一些其他外设,挂载 rootfs,把控制流交给用户空间,启动 init 进程。
- init 进程完成剩余的初始化任务,并启动用户程序。
而 STM32MP15x 在此基础上,还要支持 OP-TEE 和 M4 核。扩展后的启动链如下:
Secure Monitor 在 FSBL 阶段被加载。目前要么是 OP-TEE,要么是一个 demo 性质的 sp_min(负责一些电源管理功能)。上图中的蓝色方块,「协处理器」,指的是 MP15x 产品线的 M4 核,它默认是由 Linux kernel 启动,但也可以由 SSBL 启动。
MP15x 的详细启动流程如下:
- 系统上电,开始执行 ST 写死的一段 ROM 代码,它负责将 TF-A 载入 sysram。
- TF-A 担任 FSBL。它将 U-Boot 载入 DDR 内存,将 OP-TEE 载入 sysram,并启动 OP-TEE。OP-TEE 启动 U-Boot。
- U-Boot 担任 SSBL。它载入并启动 Linux kernel。
- Linux kernel 进行一些初始化,并将控制流交给用户空间。
综上所述,我们要跑起来 Linux 系统,首先需要有 TF-A 和 U-Boot 固件。接下来,我们自己动手编译它们。
0x03 关于 TF-A
TF-A 是 arm 公司开源的项目。它本来是为 armv8 设计的,ST 将其移植到了 armv7 上。
TF-A 本身也是分阶段的。在 aarch64 上,分为以下阶段:
- Boot Loader stage 1 (BL1) AP Trusted ROM
- Boot Loader stage 2 (BL2) Trusted Boot Firmware
- Boot Loader stage 3-1 (BL31) EL3 Runtime Software
- Boot Loader stage 3-2 (BL32) Secure-EL1 Payload (optional)
- Boot Loader stage 3-3 (BL33) Non-trusted Firmware
其中,BL 1 负责打出一段文字,然后载入 BL2。由于 STM32MP1 采用了 ST 公司的 ROM 代码,故 TF-A 提供的 BL1 事实上并未被使用到。
BL2 负责一些很底层的初始化,例如使能 MMU、进行一些安全设置。它载入 BL31(EL3 runtimie software,在 aarch32 上没有这个步骤),载入 BL32(Secure-EL1 Payload,运行在安全世界),载入 BL33(Non-trusted Firmware,例如 UEFI,运行在非安全世界)。在 aarch64 上,BL2 跳转到 BL31,BL31 再跳转到 BL32;在 aarch32 上,BL2 直接跳转到 BL33。
总结一下:BL1 已经被 ST 的 ROM 取代了;aarch32 上没有 BL31 这个组件;BL33 是 U-Boot。所以现在 TF-A 中还剩下 BL2、BL32。其中 BL32 可以使用 sp_min 或 OP-TEE。
ST Wiki 提供了一张图:
简而言之,ROM 启动 BL2;BL2 载入 BL32(OP-TEE)和 BL33(U-Boot),然后跳转到 BL32,由 BL32 跳转到 BL33。
接下来,我们在 ST 和正点的开发板上分别编译 TF-A。
0x04 为 ST 开发板编译 TF-A
先来给 ST 官方开发板编译 TF-A。将 USB CN7 连接到电脑(提供 USB DFU)、USB CN6 连接到 5V3A 电源、USB CN11 连接到电脑(ST-Link,提供串口),如下所示:
笔者使用 Debian testing 作为编译环境。按照 ST Wiki 的指引安装 SDK,其中包含交叉编译器。
进入 Developer Package,观看 TF-A 的编译指南:
首先加载 SDK 的各项环境变量:
# 加载环境变量
. /root/tools/stm32mp1sdk/environment-setup-cortexa7t2hf-neon-vfpv4-ostl-linux-gnueabi
# 查看 CROSS_COMPILE 环境变量
set | grep CROSS
# 输出:CROSS_COMPILE=arm-ostl-linux-gnueabi-
下载 TF-A 的源码:
# 下载 ST 维护的 TF-A 源码
git clone https://github.com/STMicroelectronics/arm-trusted-firmware.git
cd arm-trusted-firmware/
编译:
# 设置 FIP
export FIP_DEPLOYDIR_ROOT=$PWD/../../FIP_artifacts
# 编译
make -f $PWD/../Makefile.sdk all -j8
编译完成后,产出在 deploy
和 FIP_artifacts/fip
两个目录下。用 deploy 目录下的 TF-A 和 fip 目录下的 OP-TEE 覆盖 Starter Package 中的镜像,烧录 linux 以外的分区:
启动:
TF-A 和 U-Boot 都正常启动,但是没进入 Linux。看一眼分区表:
原来 Linux boot 分区被擦掉了。不过我们本来这一次也就是验证 TF-A 能否跑起来,所以目标达成。下面我们为正点原子开发板编译 TF-A。
0x05 eMMC 分区表
在编译 TF-A 之前,我们先看一眼正点原子版本的 emmc 分区表 atk_emmc-stm32mp157d-atk-qt.tsv
:
#Opt | Id | Name | Type | Device | Offset | Binary |
---|---|---|---|---|---|---|
- | 0x01 | fsbl1-boot | Binary | none | 0x0 | tf-a/tf-a-stm32mp157d-atk-serialboot.stm32 |
- | 0x03 | ssbl-boot | Binary | none | 0x0 | uboot/u-boot.stm32 |
P | 0x04 | fsbl1 | Binary | mmc1 | boot1 | tf-a/tf-a-stm32mp157d-atk-trusted.stm32 |
P | 0x05 | fsbl2 | Binary | mmc1 | boot2 | tf-a/tf-a-stm32mp157d-atk-trusted.stm32 |
PD | 0x06 | ssbl | Binary | mmc1 | 0x00080000 | uboot/u-boot.stm32 |
P | 0x21 | boot | System | mmc1 | 0x00280000 | atk-image-bootfs.ext4 |
P | 0x22 | rootfs | FileSystem | mmc1 | 0x04280000 | atk-image-qt5.12.9-rootfs.ext4 |
首先了解一下 tsv 文件的规则。根据 ST Wiki,tsv 文件是为 STM32CubeProgrammer 准备的,它定义 flash 分区表,并选择要烧录到分区的文件。
ST 修改了 U-Boot,提供了一个自定义的指令 stm32prog
,它能与 CubeProgrammer 通讯,烧录开发板上的 flash 分区。因此,开发板在烧录时,首先载入一个 tf-a-serialboot.stm32
和一个 u-boot.stm32
,进入魔改版 U-Boot 后利用 stm32prog
指令完成剩余的烧录过程。
为何在载入魔改版 U-Boot 之前,需要先送一个 serialboot TF-A 进去呢?我们之前提到,U-Boot 是一个功能十分完备的程序,它要运行在 DDR 内存上;但 MP1 内置的 ROM 代码不会做 DDR 等外设的初始化。因此,先送进去一个 TF-A 来启动 U-Boot,然后 CubeProgrammer 再和 U-Boot 通讯,以完成烧写工作。当然,我们临时送进去的 TF-A 和 U-Boot,唯一工作就是烧写 emmc,所以它们全程位于 sysram/DDR 上,不会驻留到 flash。重启之后,它们就不存在了。系统将从我们烧写到 emmc 的那个 TF-A 启动。
关于烧录过程,ST 提供了详细的文档。根据文档,在开发板上存在 TF-A 和 U-Boot 的条件下,我们可以不烧录临时的 TF-A 和 U-Boot,而借用开发板上已有的固件。试验一下,在开机过程中按任意键进入 U-Boot 命令行,然后输入 stm32prog usb 0
,果然能与 PC 上的 CubeProgrammer 建立连接:
现在我们已经解释了分区表的前两个项目,即 fsbl1-boot
(TF-A)、 ssbl-boot
(U-Boot)。表中还剩下五项:
#Opt | Id | Name | Type | Device | Offset | Binary |
---|---|---|---|---|---|---|
P | 0x04 | fsbl1 | Binary | mmc1 | boot1 | tf-a/tf-a-stm32mp157d-atk-trusted.stm32 |
P | 0x05 | fsbl2 | Binary | mmc1 | boot2 | tf-a/tf-a-stm32mp157d-atk-trusted.stm32 |
PD | 0x06 | ssbl | Binary | mmc1 | 0x00080000 | uboot/u-boot.stm32 |
P | 0x21 | boot | System | mmc1 | 0x00280000 | atk-image-bootfs.ext4 |
P | 0x22 | rootfs | FileSystem | mmc1 | 0x04280000 | atk-image-qt5.12.9-rootfs.ext4 |
接下来,我们按照 ST Wiki 学习这些字段。
字段 1:opt
#opt
属性指定了将要进行的行为:
-
:表示不修改这个分区。若device=none
,则 opt 必须也为-
。P
:表示烧写这个分区。U-Boot 会从 CubeProgrammer 那里获取镜像并烧录。- 修饰符
E
:表示这是个空分区。不更新。 - 修饰符
D
:表示删除这个分区。
- 修饰符
因此,可行的选择项组合如下:
-
:无操作。P
:烧录。PE
:不更新。允许 GPT 将其分区为 empty partition,但在 raw flash 上等价于「-
」。PD
:删除并更新。PDE
:删除并置空。
PD
与 P
的区别在于,PD
会在更新之前先擦除一遍。
字段 2:Id
id 这个字段仅用于指定烧录顺序。范围是 0x01 - 0xf0。有一些值被保留:
值 | 用途 | 解释 |
---|---|---|
0x01 | FSBL | loaded by ROM code in embedded RAM, file with STM32 header |
0x02 | SSBL | Reserved for future use |
0x03 | SSBL | loaded by FSBL in external RAM for serial boot |
低于 0x10 的 id 被认为是带有 stm32 header 或 fip header 的 FSBL 或 SSBL。不带这些 header 的用户自定义分区,例如 rootfs,应当放到 0x10 及以后。
字段 3:Name
这个字段是分区名。有一些要求:
- 对于 SD 卡启动,FSBL 分区名必须以
fsbl
开头,例如fsbl1, fsbl2
。 - 对于包含 SSBL 的 fip 镜像,若 TF-A 不支持固件升级,则是
fip
;否则是fip-a
或fip-b
。 - Linux rootfs 分区应当叫做
rootfs
。
目前的 MP1 手册建议使用 fip 来打包 OP-TEE 和 U-Boot。但是 2020 年的版本中,还没有支持 fip。当时的 Wiki 写道,SSBL 镜像的分区应该叫做 ssbl
,这也是正点原子开发板采用的方式。
字段 4:Type
type 字段是给 U-Boot 用于选择需要更新的 Flash 区域。支持列表如下:
例如,其中的 FileSystem 是指 Linux 的文件系统,如 ext2/ext4/fat。
字段 5:Device
指定目标设备。例如 mmc0
表示 SD 卡、mmc1
表示 eMMC。
none
仅用于把 programming service 加载到 RAM。前文所述的临时 TF-A 和 U-Boot 便是采用了 option=-, type=binary, device=none, offset=0x0
的配置。
ram0
可以用于把一些东西加载到内存,例如在调试时加载并启动 Linux 内核。
字段 6:Offset
这个字段指定了偏移量,以字节为单位。
特殊地,eMMC 内部包含有固定的两个 boot 分区用于启动:
对于 eMMC 启动,若想要将 TF-A 装进这两个分区,则 Offset 填写 boot1
或 boot2
,而非偏移量。
字段 7:Binary
CubeProgrammer 所使用的 PC 文件位置,包含要烧录的镜像。若 opt 字段有 E
修饰符,则可以不填写。
以上,我们了解了 tsv 文件的格式。来重新解释一遍正点原子开发板的分区表:
#Opt | Id | Name | Type | Device | Offset | Binary |
---|---|---|---|---|---|---|
- | 0x01 | fsbl1-boot | Binary | none | 0x0 | tf-a/tf-a-stm32mp157d-atk-serialboot.stm32 |
- | 0x03 | ssbl-boot | Binary | none | 0x0 | uboot/u-boot.stm32 |
P | 0x04 | fsbl1 | Binary | mmc1 | boot1 | tf-a/tf-a-stm32mp157d-atk-trusted.stm32 |
P | 0x05 | fsbl2 | Binary | mmc1 | boot2 | tf-a/tf-a-stm32mp157d-atk-trusted.stm32 |
PD | 0x06 | ssbl | Binary | mmc1 | 0x00080000 | uboot/u-boot.stm32 |
P | 0x21 | boot | System | mmc1 | 0x00280000 | atk-image-bootfs.ext4 |
P | 0x22 | rootfs | FileSystem | mmc1 | 0x04280000 | atk-image-qt5.12.9-rootfs.ext4 |
它描述的操作是:
- 将用于 programming service 的 TF-A 和 U-Boot 载入到 RAM 中,id 为 0x01 和 0x03。
- 将 TF-A 镜像写入 emmc 的 boot1、boot2 分区。
- 将 U-Boot 镜像写入 80000h 位置。
- 将 bootfs 写入 280000h 位置。
- 将 rootfs 写入 4280000h 位置。
另一个例子,来源于 2020 年版本的 ST 文档,为 SD 卡启动设计:
#opt | Id | Name | Type | Device | Offset | Binary |
---|---|---|---|---|---|---|
P | 0x01 | fsbl1 | Binary | mmc0 | 0x00004400 | fsbl.stm32 |
P | 0x02 | fsbl2 | Binary | mmc0 | 0x00044400 | fsbl.stm32 |
P | 0x03 | ssbl | Binary | mmc0 | 0x00084400 | ssbl.stm32 |
P | 0x10 | bootfs | System | mmc0 | 0x00284400 | bootfs.ext4 |
P | 0x11 | rootfs | FileSystem | mmc0 | 0x08284400 | rootfs.ext4 |
PE | 0x12 | userfs | FileSystem | mmc0 | 0x28284400 | none |
它描述的操作是:
- 烧录 TF-A 和 U-Boot。按照 2020 年的文档,当时 id 0x01 是「用于 programming service 的 TF-A」,而 id 0x02 是「将要被写入 flash 的 TF-A」;id 0x03 是「用于 programming service 及烧录的 U-Boot」。
- 从 id 0x10 开始,分别是 bootfs、rootfs 和 userfs。
而现代版本的 ST eMMC 范例如下,令人眼花缭乱:
#Opt | Id | Name | Type | IP | Offset | Binary |
---|---|---|---|---|---|---|
- | 0x01 | fsbl-boot | Binary | none | 0x0 | arm-trusted-firmware/tf-a-stm32mp157d-ev1-usb.stm32 |
- | 0x03 | fip-boot | FIP | none | 0x0 | fip/fip-stm32mp157d-ev1-optee.bin |
P | 0x04 | fsbl1 | Binary | mmc1 | boot1 | arm-trusted-firmware/tf-a-stm32mp157d-ev1-emmc.stm32 |
P | 0x05 | fsbl2 | Binary | mmc1 | boot2 | arm-trusted-firmware/tf-a-stm32mp157d-ev1-emmc.stm32 |
P | 0x06 | metadata1 | FWU_MDATA | mmc1 | 0x00080000 | arm-trusted-firmware/metadata.bin |
P | 0x07 | metadata2 | FWU_MDATA | mmc1 | 0x00100000 | arm-trusted-firmware/metadata.bin |
P | 0x08 | fip-a | FIP | mmc1 | 0x00180000 | fip/fip-stm32mp157d-ev1-optee.bin |
PED | 0x09 | fip-b | FIP | mmc1 | 0x00580000 | none |
PED | 0x0A | u-boot-env | ENV | mmc1 | 0x00980000 | none |
P | 0x10 | bootfs | System | mmc1 | 0x00A00000 | st-image-bootfs-openstlinux-weston-stm32mp1.ext4 |
P | 0x11 | vendorfs | FileSystem | mmc1 | 0x04A00000 | st-image-vendorfs-openstlinux-weston-stm32mp1.ext4 |
P | 0x12 | rootfs | FileSystem | mmc1 | 0x05A00000 | st-image-weston-openstlinux-weston-stm32mp1.ext4 |
P | 0x13 | userfs | FileSystem | mmc1 | 0xC5A00000 | st-image-userfs-openstlinux-weston-stm32mp1.ext4 |
这样的变化主要是 fip 的引入导致的。增加了 TF-A metadata、fip 分区和 u-boot-env。
0x06 为正点原子开发板编译 TF-A
正点原子的 TF-A 在 BL32 阶段不使用 OP-TEE,而是使用 sp_min。然而,ST 官方的开发生态从 v5.0.0 开始,便不再支持 sp_min。按照追上游的原则,我们迟早需要把 OP-TEE 移植到正点原子的开发板上。不过笔者现在先按照正点原子的指引编译他们的 TF-A。
把正点原子的开发板连接到 PC 上:
文档称,若连接了显示屏等部件,则建议使用 DC 供电。我们这里没有多少功耗需求,故使用 USB 供电即可。
按照正点原子的手册,以 stm32mp157d-ed1.dts
为模板,创建一个 ruan.dts
:
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* Copyright (C) STMicroelectronics 2019 - All Rights Reserved
* Author: Alexandre Torgue <alexandre.torgue@st.com> for STMicroelectronics.
*/
/dts-v1/;
#include "stm32mp157.dtsi"
#include "stm32mp15xd.dtsi"
#include "stm32mp15-pinctrl.dtsi"
#include "stm32mp15xxaa-pinctrl.dtsi"
#include "ruan-atk.dtsi"
#include <dt-bindings/soc/st,stm32-etzpc.h>
/ {
model = "STM32MP157D - mod Ruan v0.15";
compatible = "st,stm32mp157d-ed1", "st,stm32mp157";
chosen {
stdout-path = "serial0:115200n8";
};
aliases {
serial0 = &uart4;
};
};
&cpu1 {
cpu-supply = <&vddcore>;
};
&etzpc {
st,decprot = <
DECPROT(STM32MP1_ETZPC_USART1_ID, DECPROT_NS_RW, DECPROT_UNLOCK)
DECPROT(STM32MP1_ETZPC_SPI6_ID, DECPROT_NS_RW, DECPROT_UNLOCK)
DECPROT(STM32MP1_ETZPC_I2C4_ID, DECPROT_NS_RW, DECPROT_UNLOCK)
DECPROT(STM32MP1_ETZPC_I2C6_ID, DECPROT_NS_RW, DECPROT_UNLOCK)
DECPROT(STM32MP1_ETZPC_RNG1_ID, DECPROT_NS_RW, DECPROT_UNLOCK)
DECPROT(STM32MP1_ETZPC_HASH1_ID, DECPROT_NS_RW, DECPROT_UNLOCK)
DECPROT(STM32MP1_ETZPC_DDRCTRL_ID, DECPROT_S_RW, DECPROT_LOCK)
DECPROT(STM32MP1_ETZPC_DDRPHYC_ID, DECPROT_S_RW, DECPROT_LOCK)
DECPROT(STM32MP1_ETZPC_STGENC_ID, DECPROT_S_RW, DECPROT_LOCK)
DECPROT(STM32MP1_ETZPC_BKPSRAM_ID, DECPROT_S_RW, DECPROT_LOCK)
DECPROT(STM32MP1_ETZPC_IWDG1_ID, DECPROT_S_RW, DECPROT_LOCK)
>;
};
以
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/*
* Copyright (C) STMicroelectronics 2017 - All Rights Reserved
* Author: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
*/
#include <dt-bindings/clock/stm32mp1-clksrc.h>
#include <dt-bindings/power/stm32mp1-power.h>
#include "stm32mp15-ddr3-2x4Gb-1066-binG.dtsi"
/ {
memory@c0000000 {
device_type = "memory";
reg = <0xC0000000 0x40000000>;
};
vddcore: regulator-vddcore {
compatible = "regulator-fixed";
regulator-name = "vddcore";
regulator-min-microvolt = <1200000>;
regulator-max-microvolt = <1350000>;
regulator-off-in-suspend;
regulator-always-on;
};
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: 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: 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;
};
};
&bsec {
board_id: board_id@ec {
reg = <0xec 0x4>;
st,non-secure-otp;
};
};
&clk_hse {
st,digbypass;
};
&cpu0{
cpu-supply = <&vddcore>;
};
&hash1 {
status = "okay";
};
&i2c4 {
pinctrl-names = "default";
pinctrl-0 = <&i2c4_pins_a>;
i2c-scl-rising-time-ns = <185>;
i2c-scl-falling-time-ns = <20>;
clock-frequency = <400000>;
status = "okay";
secure-status = "okay";
};
&iwdg2 {
timeout-sec = <32>;
status = "okay";
secure-status = "okay";
};
&nvmem_layout {
nvmem-cells = <&cfg0_otp>,
<&part_number_otp>,
<&monotonic_otp>,
<&nand_otp>,
<&uid_otp>,
<&package_otp>,
<&hw2_otp>,
<&pkh_otp>,
<&board_id>;
nvmem-cell-names = "cfg0_otp",
"part_number_otp",
"monotonic_otp",
"nand_otp",
"uid_otp",
"package_otp",
"hw2_otp",
"pkh_otp",
"board_id";
};
&pwr_regulators {
system_suspend_supported_soc_modes = <
STM32_PM_CSLEEP_RUN
STM32_PM_CSTOP_ALLOW_LP_STOP
STM32_PM_CSTOP_ALLOW_LPLV_STOP
STM32_PM_CSTOP_ALLOW_STANDBY_DDR_SR
>;
system_off_soc_mode = <STM32_PM_CSTOP_ALLOW_STANDBY_DDR_OFF>;
vdd-supply = <&vdd>;
vdd_3v3_usbfs-supply = <&vdd_usb>;
};
&rcc {
st,hsi-cal;
st,csi-cal;
st,cal-sec = <60>;
st,clksrc = <
CLK_MPU_PLL1P
CLK_AXI_PLL2P
CLK_MCU_PLL3P
CLK_PLL12_HSE
CLK_PLL3_HSE
CLK_PLL4_HSE
CLK_RTC_LSE
CLK_MCO1_DISABLED
CLK_MCO2_DISABLED
>;
st,clkdiv = <
1 /*MPU*/
0 /*AXI*/
0 /*MCU*/
1 /*APB1*/
1 /*APB2*/
1 /*APB3*/
1 /*APB4*/
2 /*APB5*/
23 /*RTC*/
0 /*MCO1*/
0 /*MCO2*/
>;
st,pkcs = <
CLK_CKPER_HSE
CLK_FMC_ACLK
CLK_QSPI_ACLK
CLK_ETH_DISABLED
CLK_SDMMC12_PLL4P
CLK_DSI_DSIPLL
CLK_STGEN_HSE
CLK_USBPHY_HSE
CLK_SPI2S1_PLL3Q
CLK_SPI2S23_PLL3Q
CLK_SPI45_HSI
CLK_SPI6_HSI
CLK_I2C46_HSI
CLK_SDMMC3_PLL4P
CLK_USBO_USBPHY
CLK_ADC_CKPER
CLK_CEC_LSE
CLK_I2C12_HSI
CLK_I2C35_HSI
CLK_UART1_HSI
CLK_UART24_HSI
CLK_UART35_HSI
CLK_UART6_HSI
CLK_UART78_HSI
CLK_SPDIF_PLL4P
CLK_FDCAN_PLL4R
CLK_SAI1_PLL3Q
CLK_SAI2_PLL3Q
CLK_SAI3_PLL3Q
CLK_SAI4_PLL3Q
CLK_RNG1_LSI
CLK_RNG2_LSI
CLK_LPTIM1_PCLK1
CLK_LPTIM23_PCLK3
CLK_LPTIM45_LSE
>;
/* VCO = 1066.0 MHz => P = 266 (AXI), Q = 533 (GPU), R = 533 (DDR) */
pll2: st,pll@1 {
compatible = "st,stm32mp1-pll";
reg = <1>;
cfg = <2 65 1 0 0 PQR(1,1,1)>;
frac = <0x1400>;
};
/* VCO = 417.8 MHz => P = 209, Q = 24, R = 11 */
pll3: st,pll@2 {
compatible = "st,stm32mp1-pll";
reg = <2>;
cfg = <1 33 1 16 36 PQR(1,1,1)>;
frac = <0x1a04>;
};
/* VCO = 594.0 MHz => P = 99, Q = 74, R = 74 */
pll4: st,pll@3 {
compatible = "st,stm32mp1-pll";
reg = <3>;
cfg = <3 98 5 7 7 PQR(1,1,1)>;
};
};
&rng1 {
status = "okay";
secure-status = "okay";
};
&rtc {
status = "okay";
secure-status = "okay";
};
&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";
};
&timers15 {
secure-status = "okay";
st,hsi-cal-input = <7>;
st,csi-cal-input = <8>;
};
&uart4 {
pinctrl-names = "default";
pinctrl-0 = <&uart4_pins_a>;
status = "okay";
};
&usbotg_hs {
phys = <&usbphyc_port1 0>;
phy-names = "usb2-phy";
usb-role-switch;
status = "okay";
};
&usbphyc {
status = "okay";
};
&usbphyc_port0 {
phy-supply = <&vdd_usb>;
};
&usbphyc_port1 {
phy-supply = <&vdd_usb>;
};
编译:
make -f ../Makefile.sdk all -j8
产出在 build
目录下,可以拿去烧写了。tsv 描述如下:
#Opt Id Name Type Device Offset Binary
- 0x01 fsbl1-boot Binary none 0x0 tf-a-ruan-serialboot.stm32
- 0x03 ssbl-boot Binary none 0x0 u-boot.stm32
P 0x04 fsbl1 Binary mmc1 boot1 tf-a-ruan-trusted.stm32
P 0x05 fsbl2 Binary mmc1 boot2 tf-a-ruan-trusted.stm32
0x07 分步编译 TF-A
我们想将新版的 ecosystem 5.0.0 移植到正点原子的开发板上。然而,正点原子的出厂固件是基于 ecosystem v2 的,包含 Linux 5.4.31、TF-A 2.2、U-Boot 2020.01。现在的 ecosystem v5 包含 Linux 6.1、TF-A 2.8、U-Boot 2022.10,跨越了几个大版本。主要区别是:
- ecosystem 3.0.0 引入了 FIP。于是,TF-A 的 BL2 被单独打包成
.stm32
文件,而 BL32(sp_min 或 OP-TEE)、BL33(U-Boot)被合并打包成 fip 文件。 - ecosystem 5.0.0 不再支持 sp_min 作为 BL32,而要求使用 OP-TEE。
那么,我们先尝试将 DK1 开发板的部署方式降级——采用 sp_min,以贴近正点原子开发板。
编译 TF-A(采用 sp_min 作为 BL32):
#!/usr/bin/bash
export CROSS_COMPILE=arm-none-eabi-
make realclean
make DEBUG=1 LOG_LEVEL=40 PLAT=stm32mp1 ARCH=aarch32 ARM_ARCH_MAJOR=7 AARCH32_SP=sp_min DTB_FILE_NAME=stm32mp157d-dk1.dtb STM32MP_SDMMC=1 -j8
编译成功:
烧录看看:
我们这样编译出来的 TF-A,报告找不到 fip 分区。那么我们找个官方版 fip 来,移除 OP-TEE,把我们的 sp_min 打包进去:
fiptool info origin-fip-stm32mp157d-dk1-optee.bin
# Secure Payload BL32 (Trusted OS): offset=0x128, size=0x1C, cmdline="--tos-fw"
# Secure Payload BL32 Extra1 (Trusted OS Extra1): offset=0x144, size=0x36B68, cmdline="--tos-fw-extra1"
# Non-Trusted Firmware BL33: offset=0x36CAC, size=0xF5D3C, cmdline="--nt-fw"
# FW_CONFIG: offset=0x12C9E8, size=0x1EA, cmdline="--fw-config"
# HW_CONFIG: offset=0x12CBD2, size=0x1EE40, cmdline="--hw-config"
【本文更新中……】