【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614
在这里插入图片描述

第五章 STM32MP1启动详解

STM32单片机是直接将程序下载到内部Flash中,上电以后直接运行内部Flash中的程序。STM32MP157内部没用供用户使用的Flash,系统都是存放在外部Flash里面的,比如EMMC、NAND等,因此STM32MP157上电以后需要从外部Flash加载程序到内存中。而且STM32MP157支持多种启动方式,这些启动方式都是怎么运行的,这都涉及到STM32MP1的启动方式,本章我们就来详细讲解一下STM32MP1的启动过程。

5.1 STM32MP1启动模式

STM32MP1支持从多种设备启动,比如EMMC、SD、NAND、NOR、USB、UART等。STM32MP1内部有一段ROM来存放ST自己编写的程序,这段ROM空间是不开放给用户使用的,仅供ST存放自己的ROM代码,ROM空间如图5.1.1所示:
【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_第1张图片

图5.1.1 内部ROM空间
图5.1.1中CA7是Cortex-A7的缩写,可以看出A7内核有128KB的ROM空间,起始地址为0X00000000,STM32MP1上电以后会先运行这段ROM代码。STM32MP157有三个BOOT引脚:BOOT0~BOOT2,这三个BOOT引脚通过拉高/拉低来设置从哪种设备启动,正点原子STM32MP157开发板上的拨码开关就是控制这三个BOOT引脚的,大家可以打开底板原理图,找到BOOT相关原理图,如图5.1.2所示:
【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_第2张图片

图5.1.2 BOOT原理图
从图5.1.1可以看出,当BOOT0~BOOT1拨到“ON”的时候就会接到3.3V上,此时就是 逻辑1,拨到OFF的时候BOOT0~BOOT1就悬空(也可以外接下拉电阻),此时就是逻辑0(悬空或接地都为逻辑0),三个引脚不同电平对应的启动模式,如表5.1.1所示:
BOOT2 BOOT1 BOOT0 启动模式 描述
0 0 0 UART/USB - USART2/3/6、UART4/5/6/7/8。

  • USB接口。
    0 0 1 串行NOR 串行NOR或者QUADSPI
    0 1 0 EMMC 连接到SDMMC2上的EMMC设备。
    0 1 1 并行NAND 连接到FMC上的并行SLC NAND
    1 0 0 MCU 启动内部的M4内核。
    1 0 1 SD 连接到SDMMC1上的SD卡。
    1 1 0 UART/USB 和000效果一样,从UART/USB启动。
    1 1 1 串行NAND 连接到QUADSPI上的串行NAND
    表5.1.1 启动模式
    注:STM32MP1的引脚具有复用功能,因此一个外设有很多不同引脚可以使用,比如SDMMC2的D0引脚就可以使用PB14或PE6。STM32MP1内部ROM代码肯定会有个默认的引脚,比如内部默认使用PB14,如果你自己绘制的板子用了PE6,那么就会出问题。当然了,可以通过修改OTP来设置启动设备所使用的引脚,但是笔者强烈不建议用户修改OTP,因为OTP只能改一次,一旦修改错误芯片就废了。所以大家在绘制自己板子的时候启动设备所占用的引脚一定要和ST官方的开发板一致!
    正点原子STM32MP157开发板上通过拨码开关来选择启动模式,开发板上有丝印提示如何选择不同的启动方式,如图5.1.3所示:
    【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_第3张图片

图5.1.3 开发板拨码开关设置
大家可以根据开发板上的丝印来选择启动模式,拨上去为ON,也就是‘1’,拨下来就是OFF,也就是‘0’。
但是要注意一点,开发板拨码开关3个开关从左到右依次对应:BOOT0,BOOT1和BOOT2,因此图5.1.2中丝印从左到右依次是BOOT0、BOOT1和BOOT2,顺序刚好和表5.1.1是相反的,表5.1.1中从左到右是BOOT2、BOOT1和BOOT0,所以大家看到开发板上丝印和ST数据手册里面不同的时候不要以为是丝印标错了。

5.2 STM32MP1启动流程详解

上一小节我们说了,STM32MP1内部有一段ST自己编写的ROM代码,这段ROM代码上电以后就会自动运行,ROM代码会读取BOOT0~BOOT2这三个引脚电平,获取启动模式信息,比如读取到是从EMMC启动的,那么ROM代码就会从EMMC中读取相关程序。说起来很简单,但是实际操作比较复杂,本节我们就来详细学习一下这个启动过程。

5.2.1 内部ROM代码
内部ROM代码支持如下功能:
①、Secure boot(安全启动),不管是串行启动还是从Flash设备启动。
②、Engineering boot(工程启动?),当BOOT2~BOOT0设置为100的时候,我们就可以通过STLINK访问A7或者M4内核。一般是通过此方法来调试M4内核代码。
③、Secondary core boot(第二个内核启动),复位以后,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裸机例程,因为无需考虑多核情况。
④、RMA boot,RMA是Return Material Authorization的缩写,笔者没研究过此种启动方式。
⑤、低功耗唤醒。
⑥、提供安全相关服务。
内部ROM启动流程如图5.2.1.1所示:
【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_第4张图片

图5.2.1.1 ROM流程图
我们只关心图5.2.1.1中红色部分,因为这个是最常用的启动流程,也就是上电或复位以后运行流程:
①、首先检查当前是不是CPU0运行,如果不是的话就启动CPU1,正常上电肯定是CPU0在运行。
②、如果是CPU0在运行,检查复位原因。
③、检查是否为退出Standby而导致的复位,如果不是的话就进入RMA检查。
③、检查是否为RMA启动,不是的话是否为ENGI启动。
④、如果不是ENGI启动的话直接进去冷启动。
⑤、进入冷启动以后就从flash中加载系统,并且进行鉴权,如果鉴权成功的话就运行系统。
5.2.2 安全启动
首先了解两个概念:
FSBL:全称为First stage boot loader,也就是第一阶段启动文件。
SSBL:全称为Second stage boot loader,也就是第二阶段启动文件。
当我们设置好BOOT2~BOOT0,选择从外部Flash,比如EMMC、NAND或NOR等启动的时候就会进入安全启动流程。STM32MP157的安全启动流程比较复杂,这里我们就简单了解一下安全启动的基本流程:
①、首先ROM代码从选定的Flash设备中加载FSBL镜像文件,FSBL镜像就是ROM加载的第一个用户编写的可执行程序,一般是TF-A镜像,但是我们可以换成自己编写的程序,比如A7裸机代码。当然了,这个FSBL镜像是有要求的,不是简单的把bin文件丢过来就可以,而是需要在bin文件前面添加一个头部信息,否则内部ROM代码不知道如何处理这个bin文件,这个头部信息以后稍后讲解。
②、FSBL镜像加载以后需要对其进行鉴权。
③、如果鉴权成功,那么就会跳转到FSBL镜像入口地址,开始运行FSBL固件。
内部ROM首先从选定的Flash设备中读取FSBL镜像文件并运行,但是此时DDR还没有初始化,那么FSBL镜像在哪里运行呢?肯定是内部RAM啊,ST32MP1内部有256KB的SYSRAM,如图5.2.2.1所示:
【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_第5张图片

图5.2.2.1 STM32MP1内部SYSRAM
从图5.2.2.1中可以看出,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。
boot上下文我们不需要管,但是还要简单讲一下,boot上下文会保存到SYSRAM的前512字节里面,boot上下文包含了boot信息,比如选定的boot设备,还有一些和安全启动鉴权有关的服务。结构体boot_api_context_t定义了boot上下文结构,boot_api_context_t结构体是定义在TF-A源码里面的(plat/st/stm32mp1/include/boot_api.h),内容如下所示:

示例代码5.2.2.1 boot上下文结构
1  typedef struct {
2   	/*
3    	* Boot interface used to boot : take values from defines
4    	* BOOT_API_CTX_BOOT_INTERFACE_SEL_XXX above
5    	*/
6   	uint16_t boot_interface_selected;
7   	uint16_t boot_interface_instance;
8   	uint32_t reserved1[12];
9   	uint32_t usb_context;
10  	uint32_t otp_afmux_values[3];
11  	uint32_t reserved[2];
12  	/*
13   	* Log to boot context, what was the kind of boot action
14   	* takes values from defines BOOT_API_BOOT_ACTION_XXX above
15   	*/
16  	uint32_t boot_action;
17  	/*
18   	 * STANDBY Exit status to be checked by FSBL in case
19   	* field 'boot_action' == BOOT_API_CTX_BOOT_ACTION_WAKEUP_STANDBY
20   	* take values from defines above 'BOOT_API_CTX_STBY_EXIT_STATUS_XXX'
21   	* depending on encountered situation
22   	*/
23  	uint32_t stby_exit_status;
24  	/*
25   	* CSTANDBY Exit status to be checked by FSBL in case
26   	* boot_action == BOOT_API_CTX_BOOT_ACTION_WAKEUP_CSTANDBY
27      * take values from defines above 'BOOT_API_CTX_CSTBY_EXIT_STATUS_XXX'
28   	* depending on encountered situation
29   	*/
30  	uint32_t cstby_exit_status;
31  	uint32_t auth_status;
32 
33  	/*
34   	* Pointers to bootROM External Secure Services
35   	* - ECDSA check key
36   	* - ECDSA verify signature
37   	* - ECDSA verify signature and go
38   	*/
39  	uint32_t (*bootrom_ecdsa_check_key)(uint8_t *pubkey_in,
40                      uint8_t *pubkey_out);
41  	uint32_t (*bootrom_ecdsa_verify_signature)(uint8_t *hash_in,
42                         uint8_t *pubkey_in,
43                         uint8_t *signature,
44                         uint32_t ecc_algo);
45  	uint32_t (*bootrom_ecdsa_verify_and_go)(uint8_t *hash_in,
46                      uint8_t *pub_key_in,
47                      uint8_t *signature,
48                      uint32_t ecc_algo,
49                      uint32_t *entry_in);
50 
51  	/*
52   	* Information specific to an SD boot
53   	* Updated each time an SD boot is at least attempted,
54   	* even if not successful
55  	 * Note : This is useful to understand why an SD boot failed
56   	* in particular
57   	*/
58  	uint32_t sd_err_internal_timeout_cnt;
59  	uint32_t sd_err_dcrc_fail_cnt;
60  	uint32_t sd_err_dtimeout_cnt;
61  	uint32_t sd_err_ctimeout_cnt;
62  	uint32_t sd_err_ccrc_fail_cnt;
63  	uint32_t sd_overall_retry_cnt;
64 	 	/*
65   	* Information specific to an eMMC boot
66   	* Updated each time an eMMC boot is at least attempted,
67   	* even if not successful
68   	* Note : This is useful to understand why an eMMC boot failed
69  	* in particular
70   	*/
71  	uint32_t emmc_xfer_status;
72  	uint32_t emmc_error_status;
73  	uint32_t emmc_nbbytes_rxcopied_tosysram_download_area;
74  	uint32_t hse_clock_value_in_hz;
75  	/*
76   	* Boot partition :
77   	* ie FSBL partition on which the boot was successful
78   	*/
79  	uint32_t boot_partition_used_toboot;
80  	/*
81   	* Address of SSP configuration structure :
82   	* given and defined by bootROM
83   	* and used by FSBL. The structure is of type
84   	* 'boot_api_ssp_config_t'
85   	*/
86  	boot_api_ssp_config_t *p_ssp_config;
87  	/*
88   	* boot context field containing bootROM updated SSP Status
89   	* Values can be of type BOOT_API_CTX_SSP_STATUS_XXX
90   	*/
91  	uint32_t    ssp_status;
92 
93  	/* Pointer on ROM constant containing ROM information */
94  	const boot_api_rom_version_info_t *p_rom_version_info;
95 
96 } 	__packed boot_api_context_t;

boot_api_context_t结构体目前不需要去研究,后面学习TF-A的时候根据实际情况在看是否有必要学习。
5.2.3 串行启动
当我们设置BOOT2~BOOT0为串行启动,也就是从USB或UART启动的时候就会进入此模式。当选择串行启动以后ROM代码就会并行扫描所有可以启动的UART以及USB OTG接口。当扫描到某个活动的串行接口以后,ROM代码就会使用此串行接口,并且忽略掉其他的串行接口。
1、USB启动
内部ROM代码支持USB OTG启动,我们一般使用STM32CubeProgrammer软件通过USB OTG接口来向STM32MP1烧写系统。USB OTG需要一个48M和60M的时钟,这两个时钟由HSE生成。ROM代码支持的HSE时钟值如下:
8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40, 48 MHz
正点原子STM32MP157开发板使用24M有源晶振作为HSE时钟源。我们可以通过设置OTP来更改ROM代码的HSE晶振大小,设置如表5.2.3.1所示:
OTP WORD 3值
(2bit) 描述
00 默认模式,ROM代码自动检测HSE频率,HSE频率必须为8, 10, 12, 14, 16, 20, 24, 25, 26, 28, 32, 36, 40或 48MHz,如果没有检测到的话就直接认为是24MHz
01 HSE=24MHz
10 HSE=25MHz
11 HSE=26MHz
表5.2.3.1 HSE时钟OTP设置表
从表可以看出,默认情况下HSE选择24MHz,虽然可以通过修改OTP来更改HSE,但是强烈不建议!因为OTP只能修改一次,一旦修改错误芯片就废了!所以大家在自己做核心板的时候外部HSE时钟最好选择24MHz,不要特立独行!
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
表5.2.3.2 ROM代码串口默认IO
大家在做板子的时候,如果要使用串口启动,那么相关串口IO一定要参考表5.2.3.2中定义的IO引脚,比如正点原子开发板UART4的RX引脚使用PB2,TX引脚使用PG11。
5.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分区数量、分区最小空间、分区存放的内容等,如表5.3.1所示:
尺寸 分区 描述
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 用户自行使用的剩余空间
表5.3.1 ST官方的Flash分区建议
5.3.1 从NAND启动
NAND前几个块(block)里面包含了多份FSBL,ROM代码会从第一个块开始扫描,并且加载第一个有效块里面的FSBL。ROM代码支持并行NAND和串行NAND,并行NAND连接到FMC总线上,串行NAND连接到QSPI上。
ROM代码支持的并行NAND要求如表5.3.1.1所示:
块大小(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)
表5.3.1.1 并行NAND参数要求
ROM代码支持串行NAND要去如表5.3.1.2所示:
块大小(KB) 页大小(KB)
128 2
256 4
512 4
512 8
表5.3.1.2 串行NAND参数要求
所以大家在制作STM32MP1硬件的时候,NAND Flash选型一定要符合表5.3.1.1中的参数。
5.3.2 从EMMC启动
EMMC在物理结构上有boot1、boot2、RPMB(Replay Protected Memory Block)、GPP(General Purpose Partitions,GPP最多4个分区)以及UDA(User Data Area)这5种分区,比如三星的KLM系列EMMC 5.1的分区结构如图5.3.2.1所示:
在这里插入图片描述

图5.3.2.1 三星KLM系列EMMC分区结构
我们一般知道和常用的就是UDA分区,也就是用户数据区域,很少会关心boot1、boot2这样的分区。boot1、boot2、RPMB这三个分区代销是固定的,用户不能修改,boot1、boot2分区存在的意义就是用于引导系统。正点原子STM32MP157开发板所使用的EMMC型号为KLM8G1GETF,这是三星的一颗8GB EMMC 5.1芯片,boot1、boot2和RPMB分区大小如图5.3.2.2所示:
在这里插入图片描述

图5.3.2.2 KLM系列EMMC分区
从图5.3.2.2中可以看出,对于三星的8GB的EMMC而言,boot1和boot2分区默认大小为4096KB,RPMB为512KB。
ST会使用EMMC的boot1和boot2这两个分区作为FSBL,但是同一时间只有一个有效,ROM代码会加载有效的哪个FSBL。ROM代码使用单bit模式来操作EMMC,默认情况下ROM代码使用连接到SDMMC2上的EMMC,可以通过OTP来修改EMMC所使用的SDMMC接口,但是这里不建议!
5.3.3 从SD卡启动
SD卡也包含两个FSBL,但是SD卡没有boot1和boot2这样的物理分区。ROM代码默认尝试加载第一个FSBL,如果第一个FSBL加载失败,那么ROM代码就会加载第二个FSBL。
ROM代码首先在SD卡上查找GPT分区,如果找到的话就查找名字以“fsbl”开始的两个FSBL分区。如果没有找到GPT分区的话就直接根据物理地址查找两个FSBL,第一个FSBL的起始偏移地址为LBA34,地址位34512=17408=0X4400,所以第一个FSBL的起始地址为0X4400。第二个FSBL的起始偏移地址为LBA546,地址为 546512=279552=0X44400,所以第二个FSBL的起始地址为0X44400。
ROM代码默认也是使用单bit模式操作SD卡,并且默认使用连接到SDMMC1接口上的SD卡。
5.4 STM32MP1二进制头部信息
前面讲了STM32MP1内部的ROM代码会先读取FSBL代码,一般是TF-A或者Uboot的SPL,也可以是A7裸机代码。比如TF-A我们之间编译生成二进制bin文件,但是这个bin文件不能直接拿来用,需要在前面添加一段头部信息,这段头部信息也包含了鉴权内容。加入头部信息以后的FSBL代码结构如图5.4.1所示:
【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_第6张图片

图5.4.1 FSBL镜像组织结构
头部信息一共是256字节,这256个字节的头部信息具体含义如表5.4.1所示:
名字 长度 偏移(B) 描述
Magic number 32bits 0 魔术数,4字节,为‘S’、‘T’、‘M’和0X32的组合,固定为0X53544D32。注意,这4个字节是大端模式,也就是高字节存储在低地址处,低字节数据在高地址处。
Image signature 512bits 4 ECDSA签名,用于镜像鉴权。
Image checksum 32bits 68 镜像校验和。
Header version 32bits 72 头部版本信息,V1.0的话为0X00010000,含义:
Byte0:保留
Byte1:主版本号为0X01。
Byte2:次版本号为0X00。
Byte3:保留
Image length 32bits 76 镜像长度,不包含头部,单位为字节。
Image entry Point 32bits 80 镜像入口地址。
Reserved1 32bits 84 保留。
Load address 32bits 88 镜像加载地址,ROM代码不使用此地址。
Reserved2 32bits 92 保留。
Version number 32bits 96 镜像版本信息。
Option flags 32bits 100 可选字段,b0=1的话表示不需要验证签名。
ECDSA algorithm 32bits 104 ECDSA算法,1:P-256 NIST;2:brainpool 256
ECDSA pubilc key 512bits 108 ECDSA公共秘钥,签名的时候使用。
Padding 83Bytes 172 保留的填充区域,必须全部为0
Binary type 1Byte 255 二进制文件类型:
0X00:U-Boot
0X10-0X1F:TF-A
0X20-0X2F:OPTEE
0X30:Copro
表5.4.1 头部信息含义
头部信息不需要我们自己手动添加,我们在编译ST官方提供的TF-A或者Uboot的时候会自动添加,因为ST提供了个名为“stm32image”的工具专门用于在bin文件前面添加头部信息。我们已经从TF-A源码中提取出来了stm32image并放到了开发板光盘中,路径为:开发板光盘5、开发工具2、ST官方开发工具stm32imagestm32image.c。在编写A7裸机的时候需要自己使用stm32image工具在bin文件前面添加头部信息,stm32image是在Ubuntu下运行的,所以需要先编译,将stm32image.c发送到Ubuntu下,然后输入如下命令编译:
gcc stm32image.c -o stm32image
编译成功以后就会生成一个名为stm32image的可执行文件,如不5.4.2所示:
在这里插入图片描述

图5.4.2 stm32image工具
运行图5.4.2编译出来的stm32image工具,输入“-s”选项可以查看使用方法,如图5.4.3所示:
在这里插入图片描述

图5.4.3 stm32image使用方法
从图5.4.3可以看出,stm32image在使用的时候需要搭配一系列的参数:
-s:指定源文件。
-d:生成的目标文件。
-l:加载地址。
-e:入口地址。
-m:出版本号。
-n:次版本号。
大家在开发板光盘里面找到正点原子出厂的tf-a固件,路径为:开发板光盘8、系统镜像2、出厂系统镜像1、STM32CubeProg烧录固件包tf-atf-a-stm32mp157d-atk-trusted.stm32,这个就是加入了头部信息的TF-A可执行文件。使用winhex软件打开tf-a-stm32mp157d-atk-trusted.stm32,winhex软件已经放到了开发板光盘中,路径为:开发板光盘 3、软件 winhexv19.7.zip,大家自行安装即可。安装完成以后打开winhex,然后点击:文件->打开,找到tf-a-stm32mp157d-atk-trusted.stm32并打开,如图5.4.4所示:
在这里插入图片描述

图5.4.4 tf-a-stm32mp157d-atk-trusted.stm32文件内容
图5.4.4就是tf-a-stm32mp157d-atk-trusted.stm32文件原始数据,其中前256个字节就是头部信息。这里我们根据图5.4.4中的内容,分析一下tf-a头部信息中几个比较重要的参数:
Magic number:起始偏移地址为0,长度为4个字节,值依次为:0X53、0X54、0X4D、0X32,合起来就是0X53544D32,这个就是表5.4.1中的魔术数,注意这四个字节的顺序是大端模式。
Header Version:起始偏移地址为72,长度为4个字节,也就是图5.4.4中第72~75这4个字节的数据,分别为:0X00、0X00、0X01和0X00,此时有的朋友将这四个字节拼起来发现是0X00000100,发现并不是表5.1.1中的0X00010000!这不是弄错了?肯定不是的,整个头部信息中,除了Magic number采用大端模式存储以外,其他都是小端模式存储,也就是低字节数据存放在底地址处,高字节数据存放在高字节处。因此0X00、0X00、0X01和0X00这四个字节的数据正确的拼出结果为0X00010000。
Image length:起始偏移地址为76,长度为4个字节,也就是图76~79这4个字节的数据,为:0X40、0XB0、0X03和0X00,按照小端模式拼起来就是0X0003B040=241728≈236.1KB,说明此TF-A的bin镜像大小为236.1KB。
Image entry Point:起始偏移地址为80,长度为4个字节,也就是图80~83这4个字节的数据,为:0X00、0X60、0XFD和0X2F,按照小端模式拼起来就是0X2FFD6000,说明入口地址为0X2FFD6000。
Load address:起始偏移地址为88,长度为4个字节,也就是图88~91这4个字节的数据,为:0X00、0X25、0XFC和0X2F,按照小端模式拼起来就是0X2FFC2500,说明加载地址为0X2FFC2500,这个不正是我们前面在5.2.2小节中分析的FSBL镜像起始地址。
Binary type:起始偏移地址为255,也就是最后一个字节,为0X10,表示当前二进制文件是TF-A。
5.5 STM23MP1 Linux系统启动过程
前面已经对STM32MP1的启动流程做了详细的讲解,STM32MP1是面向Linux领域的,因此所以的这些启动过程都是为了启动Linux内核。STM32MP1xil启动Linux内核的流程如图5.5.1所示:
【正点原子MP157连载】 第五章 STM32MP1启动详解-摘自【正点原子】【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7_第7张图片

图5.5.1 MP1 Linux启动流程
从图5.5.1可以看出,STM32MP1启动linux内核一共分为5个步骤,我们依次来看一下这五个步骤的内容:
①、ROM代码
前面说了很多次了,这是ST自己编写的代码,在STM32MP1出厂的时候就已经烧写进去的,不能被修改的。ROM代码因为保存在STM32内部ROM里面,因此也就直接简单明了的叫做“ROM代码”了。它是处理器上电以后首先执行的程序,ROM代码的主要工作就是读取STM32MP1的BOOT引脚电平,然后根据电平判断当前启动设备,最后从选定的启动设备里面读取FSBL代码,并将FSBL代码放到对应的RAM空间。
现在很多产品对设备上运行的应用都提出了安全要求,从图5.5.1中可以看出,STM32MP1启动Linux内核的过程是一个链式结构:ROM CodeFSBLSSBLLinux kernelrootfs,系统启动的过程中要保证整个链式结构都是安全的。ROM代码作为第一链,首先要对FSBL代码进行鉴权,同样的,FSBL以及后面的每一链都要对下一个阶段的镜像进行鉴权,直到设备系统正确启动。
②、FSBL
FSBL代码初始化时钟树、初始化外部RAM控制器,也就是DDR。最终FSBL将SSBL加载到DDR里面并运行SSBL代码。
一般FSBL代码是TF-A或者Uboot的SPL代码,前面我们说了,也可以将FSBL换成我们自己编写的STM32MP1 A7内核裸机代码。
③、SSBL
由于SSBL代码运行在DDR里面,无需担心空间不够,因此SSBL代码的功能就可以做的很全面,比如使能USB、网络、显示等等。这样我们就可以在SSBL中灵活的加载linux内核,比如从Flash设备上读取,或者通过网络下载下载等,用户使用起来也非常的友好。SSBL一般是Uboot,用来启动Linux内核。
③、Linux内核
SSBL部分的Uboot就一个使命,启动Linux内核,Uboot会将Linux内核加载到DDR上并运行。Linux内核启动过程中会初始化板子上的各种外设。
④、Linux用户空间
系统启动的时候会通过init进程切换到用户空间,在这个过程中会初始化根文件系统里面的各种框架以及服务。

你可能感兴趣的:(正点原子,stm32,linux,驱动开发)