1 前言
我们在《小猫爪:i.MX RT1050学习笔记1-启动》中介绍了一下RT系列芯片的启动过程与一般MCU的启动机制不一样,提到RT的bin文件是经过加工之后的镜像文件,接下就让我们看看RT的镜像文件进行了怎么的加工,才能让RT正常启动。
2 RT启动过程
之前在介绍RT的启动,就简单介绍了RT的正常启动流程,接下来再简单介绍一下:
首先BootROM根据Boot Mode引脚选择从哪里启动,再根据具体的CFG引脚或者eFUSE的相关配置决定从什么存储设备开始启动,随后BootROM才会从该指定的存储设备读取镜像文件,然后再开始分析镜像并执行镜像中的APP。
我们这一章的重点在于BootROM读取镜像的过程。
3 头信息组成
在介绍读取镜像的过程前,我们首先得了解一下镜像的组成。为了让RT正常启动,我们需要在传统意义上的bin文件(也就是APP主体)增加一个头,那这个头具体内容是什么。
这个头信息包含了以下部分:
①FCB(存储器接口配置数据)
②IVT(Image Vector Table)
③Boot Data
④DCD(Device configuration data)
下面我们根据具体的实例来对每一个部分进行分解。我打开了NXP的官方的demo Hello World生成的镜像来进行详细的介绍。(环境:IAR;链接文件为:MIMXRT1052xxxxx_flexspi_nor_sdram.icf;使能SDRAM)
3.1 FCB
该部分是可选的,也是整个镜像最开始部分,其本身没有统一的结构,具体结构根据启动FLASH的接口类型而定,其一般是用来存储RT存储器接口配置数据以及当前连接的FLASH的具体特性参数,BootROM首先会使用通用且可靠的FLASH接口控制器配置(即BootROM中默认参数配置、CFG引脚以及eFUSE的配置,一般是比较低速通用的配置)去访问外接FLASH并获取这部分数据,然后根其参数去重新配置FLASH接口控制器再去进一步访问FLASH。占的空间大小随着外部存储器类型而改变,下表为不同类型存储器对应的所占空间大小:
启动设备类型 | 大小 |
---|---|
FlexSPI NOR/SEMC NOR | 4 Kbyte |
SD/MMC/eSD/eMMC/SDXC | 1 Kbyte |
SPI NOR/EEPROM/SEMC NAND/ FlexSPI NAND | 1 Kbyte |
这一部分数据对于不同的存储器类型是不一样的,我们知道RT有FlexSPI,SEMC,uSDHC 三种外部存储器接口,所以这一部分数据是根据不同的存储器接口来进行划分的。
①对于FlexSPI,存储器接口配置数据则包括两个部分,分别是FlexSPI初始化配置数据和存储器配置数据。这部分数据的具体定义大家可参考官方参考指南《i.MX RT1050 Processor Reference Manual》的Serial NAND Flash Boot over FlexSPI。
②对于SEMC,存储器接口配置数据则包括两个部分,分别是SEMC初始化配置数据和存储器配置数据。这部分数据的具体定义大家可参考官方参考指南《i.MX RT1050 Processor Reference Manual》的Parallel NOR and NAND configuration based on SEMC interface。
③对于uSDHC ,这一部分则是不需要了。因为对于SD协议来说,有相关的信息来区别不同的存储器。
(对于NAND而言,这一部分还包括关于坏块处理的FCB和DBBT数据,后续将具体介绍。)
以通过FlexSPI NOR为启动存储设备为例,在NXP官方demo程序中,是通过一个结构体来实现这部分数据,代码如下:
/*
* Serial NOR configuration block
*/
typedef struct _flexspi_nor_config
{
flexspi_mem_config_t memConfig; //!< Common memory configuration info via FlexSPI
uint32_t pageSize; //!< Page size of Serial NOR
uint32_t sectorSize; //!< Sector size of Serial NOR
uint8_t ipcmdSerialClkFreq; //!< Clock frequency for IP command
uint8_t isUniformBlockSize; //!< Sector/Block size is the same
uint8_t reserved0[2]; //!< Reserved for future use
uint8_t serialNorType; //!< Serial NOR Flash type: 0/1/2/3
uint8_t needExitNoCmdMode; //!< Need to exit NoCmd mode before other IP command
uint8_t halfClkForNonReadCmd; //!< Half the Serial Clock for non-read command: true/false
uint8_t needRestoreNoCmdMode; //!< Need to Restore NoCmd mode after IP commmand execution
uint32_t blockSize; //!< Block size
uint32_t reserve2[11]; //!< Reserved for future use
} flexspi_nor_config_t;
3.2 IVT
IVT里面包含了一系列的地址信息,这些地址信息按照固定的序列存放着,对于RT启动至关重要, 大小为32个字节。
启动设备类型 | IVT偏移 |
---|---|
FlexSPI NOR/SEMC NOR | 4 Kbyte = 0x1000 bytes |
SD/MMC/eSD/eMMC/SDXC | 1 Kbyte = 0x400 bytes |
SPI NOR/EEPROM/SEMC NAND/ FlexSPI NAND | 1 Kbyte = 0x400 bytes |
IVT的具体组成如下: |
打开Hello World.bin,因为链接文件MIMXRT1052xxxxx_flexspi_nor_sdram.icf决定了程序从FlexSPI NOR启动,我们可以知道IVT的偏移地址为0x10000。我们找到Bin文件的0x10000处:
再对应IVT结构我们可以得到:
IVT结构 | 数据 | 描述 | 大小 |
---|---|---|---|
header | 0X402000D1 | 第一个字节Tag为0XD1,第二三这两个字节为 IVT大小0X0020=32字节。第四个字节为 0X40 | 4(Byte) |
entry | 0X60002000 | APP的入口链接地址 | 4(Byte) |
reserved1 | 0X00000000 | 未使用,保留 | 4(Byte) |
dcd | 0X60001030 | DCD链接地址 | 4(Byte) |
boot data | 0X60001020 | boot data链接地址 | 4(Byte) |
self | 0X60001000 | IVT链接地址 | 4(Byte) |
csf | 0X00000000 | CSF链接地址,不使用时为0 | 4(Byte) |
reserved2 | 0X00000000 | 保留,未使用。 | 4(Byte) |
(注:链接地址就是当前数据被存储到RT的映射地址位置, 可以理解成其在RT的memory map中所在的绝对地址,通过查询可得到FlexSPI NOR的映射地址为0x60000000。) |
在NXP官方demo程序中,是通过一个结构体来实现这部分数据,代码如下:
/*************************************
* IVT Data
*************************************/
typedef struct _ivt_ {
/** @ref hdr with tag #HAB_TAG_IVT, length and HAB version fields
* (see @ref data)
*/
uint32_t hdr;
/** Absolute address of the first instruction to execute from the
* image
*/
uint32_t entry;
/** Reserved in this version of HAB: should be NULL. */
uint32_t reserved1;
/** Absolute address of the image DCD: may be NULL. */
uint32_t dcd;
/** Absolute address of the Boot Data: may be NULL, but not interpreted
* any further by HAB
*/
uint32_t boot_data;
/** Absolute address of the IVT.*/
uint32_t self;
/** Absolute address of the image CSF.*/
uint32_t csf;
/** Reserved in this version of HAB: should be zero. */
uint32_t reserved2;
} ivt;
3.3 Boot Data
Boot Data即启动数据,包含了镜像要拷贝到哪个地址,拷贝的大小是多少,其必须紧跟在IVT的后面,位置不能随意变动。所占空间大小为12个字节,其结构如下图。
我们从IVT中可以得到Boot Data的链接地址为0X60001020,所以我们在bin文件中找到0x1020处:
再对应Boot Data结构我们可以得到:
名称 | 数据 | 描述 | 大小 |
---|---|---|---|
start | 0x60000000 | 整个镜像的链接地址,包括头信息 | 4(byte) |
length | 0x04000000 | 整个镜像的大小,这里设置64M | 4(byte) |
plugin | 0x00000000 | 插件标志,imx6原生支持的Boot Device是有限的,如果我们想使用其他的Boot源(如Ethernet、CDROM、USB等),则需要提供对应的驱动程序,来完成Boot过程。具体的驱动程序路径则在DCD配置文件设置 | 4(byte) |
在NXP官方demo程序中,是通过一个结构体来实现这部分数据,代码如下:
typedef struct _boot_data_ {
uint32_t start; /* boot start location */
uint32_t size; /* size */
uint32_t plugin; /* plugin flag - 1 if downloaded application is plugin */
uint32_t placeholder; /* placehoder to make even 0x10 size */
}BOOT_DATA_T;
最后一项placeholder不是必须的,只是用来凑数,让数据对齐用的,可省略。
3.4 DCD
DCD即设备配置信息,其实就是RT启动前初始化,目的就是为了初始化SEMC,所以能通过DCD初始化的寄存器都是和SEMC有关的寄存器。原理就是里面存储着寄存器地址和数据对,读取地址和数据后,再讲相应的数据写到对应的地址。(注:我们在《小猫爪:i.MX RT1050学习笔记8-SEMC》就曾经介绍过可以使用DCD来提前初始化SEMC。)
DCD的结构如下图所示:
其中Header的格式如下:
Tag为0xD2,长度为整个DCD的长度,Version为0x41。
我们从IVT中可以得到DCD的链接地址为0X60001030,所以我们在bin文件中找到0x1030处:
从图中,0x1030处的数据0x411004D2, 可以得到DCD大小为0x0410 = 1040Byte,我们找到DCD结束的位置如下:
所以DCD的大小为0x1440-0x1030=0x0410,和上面对应正确,所以这个推断没有错。
CMD即为指令,一般有三种:写指令,检查指令和空指令。每一种指令都有自己的格式,具体的格式大家可以参考文章:《IMX头部详细解析之一 头部组成》,在这里我就不展开说了。
在NXP官方demo程序中,是通过一个数组来实现DCD数据,我们可以直接通过修改这个数组来更改DCD数据,根据所使用的SDRAM、FLASH特性来初始化SEMC,具体代码如下:
const uint8_t dcd_data[] = {
/* HEADER */
/* Tag */
0xD2,
/* Image Length */
0x04, 0x10,
/* Version */
0x41,
/* COMMANDS */
/* group: 'Imported Commands' */
/* #1.1-113, command header bytes for merged 'Write - value' command */
0xCC, 0x03, 0x8C, 0x04,
/* #1.1, command: write_value, address: CCM_CCGR0, value: 0xFFFFFFFF, size: 4 */
0x40, 0x0F, 0xC0, 0x68, 0xFF, 0xFF, 0xFF, 0xFF,
/* #1.2, command: write_value, address: CCM_CCGR1, value: 0xFFFFFFFF, size: 4 */
0x40, 0x0F, 0xC0, 0x6C, 0xFF, 0xFF, 0xFF, 0xFF,
.........
/* #9, command: write_value, address: SEMC_SDRAMCR3, value: 0x50210A09, size: 4 */
0xCC, 0x00, 0x0C, 0x04, 0x40, 0x2F, 0x00, 0x4C, 0x50, 0x21, 0x0A, 0x09
};
到这里,我们给镜像添加的头文件分解完毕。我们只要把这个头信息和APP合二为一就是一个最简单的镜像文件了,这样可以确保RT的正常运行。
除了上面介绍的头信息部分和APP部分,一个镜像文件还可以有CSF(Command Sequence File)和 KeyBlob两个部分,这两个部分主要用于安全启动的认证相关特性。后面我们接触到再对其进行深入学习。
3 镜像文件的生成
对于镜像文件的生成可以通过NXP的elftosb工具来生成,实例命令如下:
elftosb.exe -f imx -V -c APP.bd -o APP.bin APP.out
APP.bin就是我们最终的镜像文件;APP.out就是你的APP工程编译链接生成的ELF文件;APP.bd是用户配置文件,该文件主要是指示elftosb工具如何在Application binary基础上添加头信息等其他信息数据从而构成镜像文件,bd文件有专门语法格式,在\Flashloader_i.MXRT1050_GA\Flashloader_RT1050_1.1\Tools\bd_file\imx10xx目录下给了很多bd文件示例。
不过这样太过麻烦。现在NXP官方在例程中都添加了XIP文件,在文件中,我们可以非常方便的修改这些信息,在修改相关的链接文件,就可以按照我们的想法做出相关镜像文件。最后生成的bin文件即为最终的镜像文件,每一个部分在代码中怎么去实现,前面已经介绍的非常清楚。
END