系统移植-STM32MP1启动详解(BootROM)

文章目录

  • [1 STM32MP1启动详解](#1 STM32MP1启动详解)
    • [1.1 STM32MP1启动模式](#1.1 STM32MP1启动模式)
    • [1.2 STM32MP1启动流程详解](#1.2 STM32MP1启动流程详解)
      • [1.2.1 内部ROM代码](#1.2.1 内部ROM代码)
      • [1.2.2 安全启动](#1.2.2 安全启动)
      • [1.2.3 串行启动](#1.2.3 串行启动)
        • [1.2.3.1 USB启动](#1.2.3.1 USB启动)
        • [1.2.3.2 UART启动](#1.2.3.2 UART启动)
    • [1.3 Flash设备启动要求](#1.3 Flash设备启动要求)
      • [1.3.1 从NAND启动](#1.3.1 从NAND启动)
      • [1.3.2 从EMMC启动](#1.3.2 从EMMC启动)
      • [1.3.3 从SD卡启动](#1.3.3 从SD卡启动)
    • [1.4 STM32MP1二进制头部信息](#1.4 STM32MP1二进制头部信息)
  • [2 STM32MP1 Linux系统启动过程](#2 STM32MP1 Linux系统启动过程)

1 STM32MP1启动详解

STM32单片机是直接将程序下载到内部Flash中,上电以后直接运行内部Flash中的程序。STM32MP157内部没有供用户使用的Flash,系统都是存放在外部Flash里面的,比如EMMC、NAND等,因此STM32MP157上电以后需要从外部Flash加载程序到内存中。STM32MP157支持多种启动方式,这些启动方式都是怎么运行的?

1.1 STM32MP1启动模式

STM32MP1支持从多种设备启动,比如EMMC、SD、NAND、NOR、USB、UART等。STM32MP1内部有一段ROM来存放ST自己编写的程序,这段ROM空间是不开放给用户使用的,仅供ST存放自己的ROM代码,ROM空间如下图所示:

上图中CA7是Cortex-A7的缩写,可以看出A7内核有128KB的ROM空间,起始地址为0X00000000,STM32MP1上电以后会先运行这段ROM代码。STM32MP157有三个BOOT引脚:BOOT0~BOOT2,这三个BOOT引脚通过拉高/拉低来设置从哪种设备启动,正点原子STM32MP157开发板上的拨码开关就是控制这三个BOOT引脚的,如下所示:

从上图可以看出,当BOOT0~BOOT1拨到"ON"的时候就会接到3.3V上,此时就是逻辑1,拨到OFF的时候BOOT0~BOOT1就悬空(也可以外接下拉电阻),此时就是逻辑0(悬空或接地都为逻辑0),三个引脚不同电平对应的启动模式,如表所示:

1.2 STM32MP1启动流程详解

STM32MP1内部有一段ST自己编写的ROM代码,这段ROM代码上电以后就会自动运行,ROM代码会读取BOOT0~BOOT2这三个引脚电平,获取启动模式信息,比如读取到是从EMMC启动的,那么ROM代码就会从EMMC中读取相关程序。

1.2.1 内部ROM代码

内部ROM代码支持如下功能:

  1. Secure boot(安全启动),不管是串行启动还是从Flash设备启动;
  2. Engineeringboot(工程启动??),当BOOT2~BOOT0设置为100的时候,我们就可以通过STLINK访问A7或者M4内核。一般是通过此方法来调试M4内核代码;
  3. Secondarycoreboot(第二个内核启动),复位以后STM32MP157的每个A7内核都会启动,并且运行相同的指令。内部ROM代码会分离执行流,只有Core0才会运行ROM代码,另外一个内核会处于一个死循环状态,等待应用程序发送信号来进行下一步操作。这个信号是由SGI(软中断)和另外两个BACKUP寄存器:MAGIC_NUMBER、BRANCH_ADDRESS组成的。如果要启动Core1,运行在Core0的应用程序需要:
    • 将跳转地址写入BRANCH_ADDRESS寄存器;
    • 将0xCA7FACE1这个值写入到MAGIC_NUMBER寄存器;
    • 向Core1和发送SGI中断;
    • 总结一下,只要不开启第二个核,那么由于内部ROM代码的作用,此时STM32MP157就相当于单核A7;这样有利于我们编写的STM32MP157的A7裸机例程,因为无需考虑多核情况;
  4. RMAboot,RMA是Return Material Authorization的缩写;
  5. 低功耗唤醒;
  6. 提供安全相关服务。

内核ROM启动流程如下图所示:

红色部分就是最常用的启动流程,也就是上电或复位以后运行流程:

  1. 首先检查当前是不是CPU0运行,如果不是的话就启动CPU1,正常上电肯定是CPU0在运行;
  2. 如果是CPU0在运行,检查复位原因;
  3. 检查是否为退出Standby而导致的复位,如果不是的话就进入RMA检查;
  4. 检查是否为RMA启动,不是的话是否为ENGI启动;
  5. 如果不是ENGI启动的话直接进去冷启动;
  6. 进入冷启动以后就从flash中加载系统,并且进行鉴权,如果鉴权成功的话就运行系统。

1.2.2 安全启动

FSBL全称为 First stage boot loader,也就是**第一阶段启动文件**。

SSBL全称为 Second stage boot loader,也就是**第二阶段启动文件**。

当设置好BOOT2~BOOT0,选择从外部Flash,比如EMMC、NAND或NOR等启动的时候就会进入安全启动流程。STM32MP157的安全启动流程比较复杂,简单了解一下安全启动的基本流程:

  1. 首先ROM代码从选定的Flash设备中加载FSBL镜像文件,FSBL镜像就是ROM加载的第一个用户编写的可执行程序,一般是TF-A镜像,但是可以换成用户编写的程序,比如A7裸机代码。FSBL镜像是有要求的,不是简单的把bin文件丢过来就可以,而是需要在bin文件前面添加一个[[#^4d6a3d|头部信息]],否则内部ROM代码不知道如何处理这个bin文件;
  2. FSBL镜像加载以后需要对其进行鉴权;
  3. 如果鉴权成功,那么就会跳转到FSBL镜像入口地址,开始运行FSBL固件。

内部ROM首先从选定的Flash设备中读取FSBL镜像文件并运行,但是此时DDR还没有初始化,那么FSBL镜像在哪里运行呢?ST32MP1内部有256KB的SYSRAM,如图所示:

分析上图,SYSRAM地址范围为:0X2FFC0000~0X2FFFFFFF,一共是256KB。ROM代码会将FSBL镜像拷贝到0X2FFC2400地址,但是要注意,FSBL镜像的起始地址不是0X2FFC2400,因为FSBL镜像前面还有一个256(0X100)字节的头部信息,因此FSBL镜像的真正起始地址为0X2FFC2400+0X100=0X2FFC2500。将FSBL镜像换成A7裸机例程,编译A7裸机例程的时候要指定链接起始地址,这个链接起始地址就是0X2FFC2500。由此可以计算出整个FSBL镜像大小不能超过0X30000000 - 0X2FFC2500 = 252672B = 246.75KB,编写裸机程序大小也不能超过246.75KB。

FSBL镜像鉴权成功以后,ROM代码会boot上下文的起始地址保存到R0寄存器,然后跳转到FSBL镜像的入口地址,这个入口地址会定义到头部里面,其实就是0X2FFC2500。

1.2.3 串行启动

当设置BOOT2~BOOT0为串行启动,也就是从USB或UART启动的时候就会进入此模式。当选择串行启动以后ROM代码就会并行扫描所有可以启动的UART以及USB OTG接口。当扫描到某个活动的串行接口以后,ROM代码就会使用此串行接口,并且忽略掉其他的串行接口。

1.2.3.1 USB启动

内部ROM代码支持USB OTG启动,使用STM32CubeProgrammer软件通过USB OTG接口来向STM32MP1烧写系统。USB OTG需要一个48M和60M的时钟,这两个时钟由HSE生成。ROM代码支持的HSE时钟值如下:

tex 复制代码
8,10,12,14,16,20,24,25,26,28,32,36,40,48MHz

正点原子STM32MP157开发板使用24M有源晶振作为HSE时钟源。可以通过设置OTP来更改ROM代码的HSE晶振大小。但是不建议修改,可能会导致芯片报废。

1.2.3.2 UART启动

如果要选择UART启动,也就是通过UART烧写系统,那么只能使用USART2、USART3、UART4、UART5、USART6、UART7或UART8,此时串口工作模式为1位起始位、8位数据位、偶校验、1位停止位、波特率115200。由于STM32的IO复用功能,1个串口可能有多个IO可以使用,比如UART4的RX(接收)可以使用PI10、PH14、PA1、PA11、PB2、PB8、PC11、PD0或PD2,一共9个IO可以用作UART4_RX引脚,但是ROM代码里面的UART4_RX引脚肯定只会使用这个9个里面的其中一个,所以板子的串口引脚要和ROM代码里面的一致,否则就无法使用串口启动。ROM代码里面串口使用的引脚如表5.2.3.2所示:

串口 串口引脚 所使用的IO 复用编号(AF)
USART2 USART2_RX PA3 AF07
USART2_TX PA2 AF07
USART3 USART3_RX PB12 AF08
USART3_TX PB10 AF07
UART4 UART4_RX PB2 AF08
UART4_TX PG11 AF06
UART5 UART5_RX PB5 AF12
UART5_TX PB13 AF14
USART6 USART6_RX PC7 AF07
USART6_TX PC6 AF07
UART7 UART7_RX PF6 AF07
UART7_TX PF7 AF07
UART8 UART8_RX PE0 AF08
UART8_TX PE1 AF08

1.3 Flash设备启动要求

STM32MP1支持从SD、EMMC、NAND或NOR等Flash设备启动,但是不同的Flash设备在启动的时候有不同的要求。linux系统不像单片机那样,就一个bin文件,烧写进去就可以启动并运行,linux系统自身编译出来就是一个镜像文件,但是这个镜像文件要运行是需要一大堆的工具来辅助。比如需要uboot来启动,启动以后还需要根文件系统(rootfs),传统的嵌入式linux有三巨头:uboot、kernel和rootfs,但是对于STM32MP1而言,又多了几个工具,比如TF-A、TEE、vendorfs等,所有这一大堆构成了最终的系统镜像。系统镜像是要烧写到Flash设备中的,这些不同的文件肯定要按照一定的要求,分门别类的烧写,一个萝卜一个坑,TF-A应该放到哪里、uboot应该放到哪里等等。

针对Flash设备,可以通过创建不同的分区来存放不同的文件,ST针对STM32MP1系列给出了官方分区建议,这些建议包含了Flash分区数量、分区最小空间、分区存放的内容等,如表所示:

尺寸 分区 描述
256KB~512KB fsbl 第一阶段启动代码,此分区存放TF-A或者uboot的SPL部分,如果写A7裸机例程的话此分区也用来存放裸机代码
2MB ssbl 第二阶段启动代码,一般是uboot,如果uboot使用设备树的话,设备树添加到后面
64MB bootfs boot文件分区,可以存放如下内容: - init ram文件系统,可以将此文件系统拷贝到RAM中,在linux内核挂载正式根文件系统之前可以使用init ram文件系统; - linux内核设备树; - linux内核; - uboot显示的启动界面; - uboot发行配置文件extlinux.conf。
16MB vendorfs 此分区存放第三方的版权信息,确保他们不会受到任何开源许可的污染,比如GPL V3
768MB rootfs linux根文件系统
剩余空间 userfs 用户自行使用的剩余空间

1.3.1 从NAND启动

NAND前几个块(block)里面包含了多份FSBL,ROM代码会从第一个块开始扫描,并且加载第一个有效块里面的FSBL。ROM代码支持并行NAND和串行NAND,并行NAND连接到FMC总线上,串行NAND连接到QSPI上。

ROM代码支持的并行NAND要求如下:

块大小(KB) 页大小(KB) 数据宽度 ECC(bit数和编码)
128 2 8,16 4(bch),8(bch),1(hamming)
256 4 8,16 4(bch),8(bch),1(hamming)
512 4 8,16 4(bch),8(bch),1(hamming)
512 8 8,16 4(bch),8(bch),1(hamming)

ROM代码支持串行NAND的要求如表所示:

块大小(KB) 页大小(KB)
128 2
256 4
512 4
512 8

1.3.2 从EMMC启动

EMMC在物理结构上有boot1、boot2、RPMB(Replay Protected Memory Block)、GPP(General Purpose Partitions,GPP最多4个分区)以及UDA(User Data Area)这5种分区,比如三星的KLM系列EMMC5.1的分区结构如图所示:

常用的就是UDA分区,也就是用户数据区域,很少会关心boot1、boot2这样的分区。boot1、boot2、RPMB这三个分区大小是固定的,用户不能修改,boot1、boot2分区存在的意义就是用于引导系统。正点原子STM32MP157开发板所使用的EMMC型号为KLM8G1GETF,这是三星的一颗8GB EMMC5.1芯片,boot1、boot2和RPMB分区大小如图所示:

从上图可以看出,对于三星的8GB的EMMC而言,boot1和boot2分区默认大小为4096KB,RPMB为512KB。

ST会使用EMMC的boot1和boot2这两个分区作为FSBL,但是同一时间只有一个有效,ROM代码会加载有效的哪个FSBL。ROM代码使用单bit模式来操作EMMC,默认情况下ROM代码使用连接到SDMMC2上的EMMC。

1.3.3 从SD卡启动

SD卡也包含两个FSBL,但是SD卡没有boot1和boot2这样的物理分区。ROM代码默认尝试加载第一个FSBL,如果第一个FSBL加载失败,那么ROM代码就会加载第二个FSBL。

ROM代码首先在SD卡上查找GPT分区,如果找到的话就查找名字以"fsbl"开始的两个FSBL分区。如果没有找到GPT分区的话就直接根据物理地址查找两个FSBL,第一个FSBL的起始偏移地址为LBA34,地址为34 * 512 = 17408 = 0X4400,所以第一个FSBL的起始地址为0X4400。第二个FSBL的起始偏移地址为LBA546,地址为546 * 512 = 279552 = 0X44400,所以第二个FSBL的起始地址为0X44400。

ROM代码默认也是使用单bit模式操作SD卡,并且默认使用连接到SDMMC1接口上的SD卡。

1.4 STM32MP1二进制头部信息

^4d6a3d

STM32MP1内部的ROM代码会先读取FSBL代码,一般是TF-A或者Uboot的SPL,也可以是A7裸机代码。比如TF-A编译生成二进制bin文件,但是这个bin文件不能直接拿来用,需要在前面添加一段头部信息,这段头部信息也包含了鉴权内容。加入头部信息以后的FSBL代码结构如图所示:

Name Length Byte Offset Description
Magic number 32 bits 0 4字节,大端序: 'S', 'T', 'M', 0x32 = 0x53544D32
Image signature 512 bits 4 ECDSA签名,用于镜像鉴权
Image checksum 32 bits 68 镜像校验和
Header version 32 bits 72 头部版本信息,V1.0的话为0X00010000,含义: Byte0:保留 Byte1:主版本号为0X01 Byte2:次版本号为0X00 Byte3:保留
Image length 32 bits 76 镜像长度,不包含头部,单位为字节
Image entry Point 32 bits 80 镜像入口地址
Reserved1 32 bits 84 保留
Load address 32 bits 88 镜像加载地址,ROM代码不使用此地址
Reserved2 32 bits 92 保留
Version number 32 bits 96 镜像版本信息
Option flags 32 bits 100 可选字段,b0=1 的话表示不需要验证签名
ECDSA algorithm 32 bits 104 ECDSA算法,1:P-256NIST;2:brainpool 256
ECDSA public key 512 bits 108 ECDSA公共密钥,签名的时候使用
Padding 83 Bytes 172 保留的填充区域,必须全部为0
Binary type 1 Byte 255 二进制文件类型: 0X00:U-Boot 0X10-0X1F:TF-A 0X20-0X2F:OPTEE 0X30:Copro
头部信息不需要手动添加,在编译官方提供的TF-A或Uboot的时候会自动添加。

2 STM32MP1 Linux系统启动过程

STM32MP1是面向Linux领域的,因此所以的这些启动过程都是为了启动Linux内核。STM32MP1启动Linux内核的流程如图所示:

上图中,STM32MP1启动Linux内核共分为5个步骤:

  1. ROM代码:ST自己编写的代码,在STM32MP1出厂的时候就已经烧写进去的,不能被修改的。ROM代码因为保存在STM32内部ROM里面,因此也就直接简单明了的叫做"ROM代码"了。处理器上电以后首先执行的程序,ROM代码的主要工作就是读取STM32MP1的BOOT引脚电平,然后根据电平判断当前启动设备,最后从选定的启动设备里面读取FSBL代码,并将FSBL代码放到对应的RAM空间。现在很多产品对设备上运行的应用都提出了安全要求,上图中,STM32MP1启动Linux内核的过程是一个链式结构:ROMCode→FSBL→SSBL→Linuxkernel→rootfs,系统启动的过程中要保证整个链式结构都是安全的。ROM代码作为第一链,首先要对FSBL代码进行鉴权,同样的,FSBL以及后面的每一链都要对下一个阶段的镜像进行鉴权,直到设备系统正确启动;
  2. FSBL:FSBL代码初始化时钟树、初始化外部RAM控制器,也就是DDR。最终FSBL将SSBL加载到DDR里面并运行SSBL代码。一般FSBL代码是TF-A或者Uboot的SPL代码,也可以将FSBL换成A内核裸机代码;
  3. SSBL:由于SSBL代码运行在DDR里面,无需担心空间不够,因此SSBL代码的功能就可以做的很全面,比如使能USB、网络、显示等等。这样就可以在SSBL中灵活的加载linux内核,比如从Flash设备上读取,或者通过网络下载等,用户使用起来也非常的友好。SSBL一般是Uboot,用来启动Linux内核;
  4. Linux内核:SSBL部分的Uboot就一个使命,启动Linux内核,Uboot会将Linux内核加载到DDR上并运行。Linux内核启动过程中会初始化板子上的各种外设;
  5. Linux用户空间:系统启动的时候会通过init进程切换到用户空间,在这个过程中会初始化根文件系统里面的各种框架以及服务。
相关推荐
春日见2 小时前
自驾算法的日常工作?如何提升模型性能?
linux·人工智能·机器学习·计算机视觉·自动驾驶
李彦亮老师(本人)2 小时前
【Linux系统】Rocky Linux 9.7操作系统简介
linux·运维·服务器·docker·kubernetes
minji...3 小时前
Linux 进程信号(二)信号的保存,sigset_t,sigprocmask,sigpending
linux·运维·服务器·网络·数据结构·c++·算法
A.A呐3 小时前
【Linux第二十章】socket
linux
何中应3 小时前
Grafana如何重置密码
linux·运维·服务器·grafana
charlie1145141914 小时前
2026年正点原子开发板移植方案——从0开始的Rootfs之路(3)inittab 与 init 系统:Linux 启动的“第一号进程“全解析
linux·驱动开发·学习·嵌入式开发·嵌入式linux
xlp666hub4 小时前
深度剖析 Linux Input 子系统(3):从零写一个 Input 驱动,最详细手把手(附完整代码)
linux·面试
RisunJan5 小时前
Linux命令-modprobe(自动处理可载入模块)
linux·运维
何朴尧5 小时前
OpenClaw Linux 完整安装指南
linux