今年暑假实习时,笔者参与了 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 系列产品:

▲ 图片来源:https://www.st.com/en/microcontrollers-microprocessors/stm32mp157.html

  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 开发板。

▲ 左为 ST 官方开发板,右为正点原子开发板

0x01 验货

  收货之后,笔者为两个开发板刷写了各自的官方固件。ST 的 SDK 更新比较频繁,内核版本是 6.1.28:

  正点原子官方提供的固件,是从 ST 的版本 fork 的,内核版本 5.4.31:

  跑通了手册上的一些用例,各个组件均无问题。下面,我们自行编译 TF-A、U-Boot 和 Linux 内核,点亮这两块开发板。

0x02 STM32MP1 启动流程

  Linux 的通用启动流程如下图所示:

▲ 图片来源:https://wiki.st.com/stm32mpu/wiki/Boot_chain_overview
  • 系统上电后,首先执行 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

  编译完成后,产出在 deployFIP_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:删除并置空。

  PDP 的区别在于,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-afip-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 填写 boot1boot2,而非偏移量。

字段 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"

【本文更新中……】