痞子衡嵌入式:恩智浦i.MX RT1xxx系列MCU启动那些事(6)- Bootable image格式与加载(elftosb/.bd)
大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家介绍的是恩智浦i.MX RT1xxx系列MCU的Bootable image格式与加载过程。
在 i.MXRT1xxx 启动系列第三篇文章 Serial Downloader模式(sdphost, mfgtool) 里痞子衡在介绍使用 sdphost 引导启动 Flashloader 时使用过一个名叫 ivt_flashloader.bin 的 image 文件,其实这个 image 文件就是 Bootable image 的一种,虽然痞子衡简单分析过 ivt_flashloader 的组成,但介绍得并不详尽,今天痞子衡会为大家系统地讲解 i.MXRT Bootable image。
一、什么是Bootable image?
如果你是一个有经验的嵌入式开发者,肯定对 image 格式有所了解,我们通常开发的 Application 都是针对含内部 FLASH 的 MCU 而言的,比如 Kinetis、LPC、STM32 等 MCU,其内部集成了一块 Parallel NOR FLASH,且 FLASH 地址是映射在 ARM 4GB system address 内的(一般从 0x0 地址开始),FLASH 里存储的直接就是我们编译链接后生成的原始 Application binary(.bin),没有任何多余的数据组成。或许你会说还有 .hex, .srec 等其他image 格式,是的,但这些带地址信息的 image 格式是为编程器或下载器服务的,这些 image 格式经过编程器或者下载器解析后真正下载进 MCU 内部 FLASH 的数据还是原始 Application binary。这类 MCU 上电后 CPU 能直接从内部 FLASH 获取 Application 代码并原地执行(XIP),所以对这类 MCU 而言,Bootable image 就是存储在内部 FLASH 的 Application binary(.bin)。
但是以上经验在开发 i.MXRT 时遇到了问题,i.MXRT 没有内部 FLASH,需要外接 FLASH 存储器以存储 image。众所周知,FLASH 从结构上分为 NOR 和 NAND,i.MXRT 启动同时支持这两种 FLASH,NOR FLASH 可以实现 XIP,NAND FLASH 不可以 XIP,为了兼容所有 FLASH,在设计 i.MXRT bootable image 格式时必须以非 XIP 这种情况为基准。既然是非 XIP 执行,即意味着 i.MXRT 上电时会将 image 从外接 FLASH 拷贝到内部 SRAM 中去执行,在拷贝时避不可免要知道两个重要的数据:image 链接起始地址(决定 image 被拷贝到 SRAM 哪个地址)、image 总长度(决定要从外部 FLASH 拷贝多长的 image 数据进 SRAM),实际上除了这两个最基本的数据外还有其他更高级的数据(配置、安全等特性),因此存储在外接 FLASH 的 i.MXRT Bootable image 除了含有 Application binary 数据之外还必须含有额外的信息,这些额外的信息数据与 Application binary 共同组成 i.MXRT Bootable image。至于这些额外的信息在 Bootable image 里是如何组织的,痞子衡在后面会继续聊。
二、Bootable image链接空间
一个 image 的链接空间分两种,一种是只读段(readonly code,data)的链接空间,另一种是读写段(readwrite data, STACK)的链接空间,这两种链接空间要求的存储介质特性不一样,痞子衡逐一讲解:
前面讲了 i.MXRT 同时支持外接 NOR 和 NAND FLASH,其中 NAND FLASH 无法 XIP,那么存储在 NAND FLASH 中的 image 只读段必须要链接在 SRAM 里。i.MXRT 内部有三种 SRAM,分别是 ITCM, DTCM, OCRAM,是不是这三种 SRAM 都可以被随意链接呢?答案并不是!因为在 Boot 期间,BootROM 也需要占用 SRAM,用于存放 BootROM 的读写段,所以被 BootROM 占用的 SRAM 无法用于链接 image 的只读段,如果强行链接,会导致 BootROM 在拷贝image 只读段时破坏自身读写段,从而发生不可预料的行为。下图是 RT1050 BootROM 的 memory map,从图中可以得知 BootROM 占用的是 0x20200000 开始的 OCRAM,并且看起来是整块 OCRAM 都被占用了,所以不推荐使用 OCRAM 去链接 image 只读段。
黑科技:如果有朋友表示不服,RT1060/RT1050/RT1020 的 OCRAM 是 1MB/512KB/256KB,BootROM 读写段不可能有这么大,是的,痞子衡告诉你,其实BootROM 数据段只要 32KB(0x20200000 - 0x20207FFF),另外还需要 4KB 用来加载 initial non-XIP image(0x20208000 - 0x20208FFF),所以对于存储在 non-XIP FLASH 的 image 你可以从 0x20209000 之后的空间里链接 image 只读段,而对于存储在 XIP FLASH 的 image 你可以从 0x20208000 之后的空间里链接 image 读写段,这个秘密一般人痞子衡是不会告诉他的。
前面讲了存储在 NAND FLASH 中的 image 只读段链接注意事项,而对于可以 XIP 的 NOR FLASH,除了跟 NAND 一样可以将只读段链接在 SRAM 外,还可以链接在 i.MXRT 分配给外接存储器的 XIP 映射空间里,下表给出了Serial NOR(QSPI) 和 Parallel NOR(SEMC)各自的映射起始地址,需要注意的是Serial NOR 支持的最大 XIP 空间为504MB,但是 Parallel NOR 支持的最大 XIP 空间只有 16MB,别问痞子衡是怎么知道的,痞子衡无所不知。
至于 image 的读写段,在链接时就不用区别 Non-XIP/XIP FLASH 了,都只能放在 SRAM 里,并且不用考虑 BootROM 对 SRAM 的占用问题(因为不在一个时间域里被使用),只要注意不和 image 自身只读段冲突就行。
黑科技:有朋友注意到了 SDRAM,是的 i.MXRT 也支持 SDRAM,通过 SEMC 接口去实现 SDRAM 读写,所以如果外接了 SDRAM 并且使能的话,也可以将 image 只读段/读写段放入 SDRAM,关于 SDRAM 的使用,痞子衡会在后面文章里介绍。
三、Bootable image七大组成
Bootable image 是由一些额外的信息数据与 Application binary 共同组成的,那些额外的信息数据按功能分有6类,但这6类信息数据并不都是必须的,其中有4类是可选的,因此一个 Bootable image 最多由7部分组成,最少由3部分组成。下面痞子衡按在 FLASH 里存储位置从低到高的顺序逐一介绍组成 Bootable image 的7大部分:
3.1 偏移0x0000: FDCB(Flash Device Configuration Block)
第一个组成部分叫 FDCB,是个可选组成,目前只用于 Serial/Parallel NOR FLASH,FDCB 是从 FLASH 的起始地址处开始存放的,也是 Bootable image 最开始部分。FDCB 最大4KB,其本身没有统一的与 FLASH 无关的 structure,具体 structure 根据启动 FLASH 的接口类型(Serial/Parallel)而定,其一般是用来存储当前连接的 FLASH 的具体特性参数,BootROM 上电会使用通用且可靠的 FLASH 接口控制器配置(即 BootROM 中默认参数配置,一般是比较低速的配置)去访问外接 FLASH 并获取FDCB,然后根据 FDCB 存储的参数去重新配置 FLASH 接口控制器再去进一步访问 FLASH。下面的结构体是 Serial NOR 的 FDCB 原型,此处痞子衡不会展开介绍这个结构体,留到后续介绍 Serial NOR 启动再详细介绍。
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 偏移0x0400/0x1000: IVT(Image Vector Table)
第二个组成部分叫 IVT,是个必备组成,也是6类信息数据里的最核心数据,IVT 是一个统一的与 FLASH 无关的 structure,其原型如下面结构体所示,从结构体定义我们得知,IVT 中记录了 Application、DCD、BD、CSF 的位置信息,这些信息对 BootROM 加载启动至关重要。IVT 大小固定为 32byte,其在 Bootable image 中的偏移位置也是固定的(对于 XIP FLASH 而言偏移是 0x1000,对于 Non-XIP FLASH 而言偏移是 0x400)。有朋友会疑问为何 IVT 偏移地址是固定的?其实答案很简单,因为 BootROM 必须要首先获取 IVT 才能进一步找到其他信息数据,而 IVT 本身的位置信息没有在其他地方被标明,所以只能在 BootROM 里用一个常量来记录。
#define HAB_TAG_IVT0 0xd1 /**< Image Vector Table V0 */
/** @ref hab_header structure */
typedef struct hab_hdr {
uint8_t tag; /**< Tag field */
uint8_t len[2]; /**< Length field in bytes (big-endian) */
uint8_t par; /**< Parameters field */
} hab_hdr_t;
/** @ref ivt structure */
struct hab_ivt_v0 {
/** @ref hdr with tag #HAB_TAG_IVT0, length and HAB version fields */
hab_hdr_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;
};
3.3 偏移0x0420/0x1020: BD(Boot Data)
第三个组成部分叫 BD,是个必备组成,是仅次于 IVT 的核心数据,BD 也是一个统一的与 FLASH 无关的 structure,其原型如下面结构体所示,BD 中记录了 Bootable image 的起始地址与总长度。BD 大小固定为 16byte,BD 信息虽然记录在了 IVT 中,但其在 Bootable image 中的偏移位置并不是任意的,BD 是紧挨着 IVT 的。
/** @ref boot_data structure */
typedef struct boot_data{
uint32_t start; /* Start address of the image */
uint32_t size; /* Size of the image */
uint32_t plugin; /* Plugin flag */
uint32_t placeholder; /* placehoder to make even 0x10 size */
} BOOT_DATA_T;
3.4 DCD(Device Configuration Data)
第四个组成部分叫 DCD,是个可选组成,目前主要用于 SDRAM 接口控制器(SEMC)的配置。由于 i.MXRT 内部 SRAM size 通常是够用的,且访问速度也很快,所以 SDRAM 并不一定要被使能,Bootable image 常常不会包含 DCD,所以痞子衡在这里先不做展开,后续有必要会再介绍。下面是 SDK_2.3.1_EVKB-IMXRT1050 包里 hello_world 工程(flexspi_nor)所使用 DCD 示例:
#define DCD_TAG_HEADER (0xD2)
const uint8_t dcd_data[] = {
/*0000*/ DCD_TAG_HEADER,
0x04,0x30,0x41,0xCC,0x03,0xAC,0x04,0x40,0x0F,0xC0,0x68,0xFF,0xFF,0xFF,0xFF,
/*0010*/ 0x40,
0x0F,0xC0,0x6C,0xFF,0xFF,0xFF,0xFF,0x40,0x0F,0xC0,0x70,0xFF,0xFF,0xFF,0xFF,
...
/*0420*/ 0x00,
0x00,0x00,0x01,0xCC,0x00,0x0C,0x04,0x40,0x2F,0x00,0x4C,0x50,0x21,0x0A,0x09,
};
3.5 偏移0x2000: Application Binary
第五个组成部分是你最熟悉的 Application binary,当然是个必备组成,其在 Bootable image 中的偏移位置是固定的(0x2000),关于 Application 本身这里就不再赘述了。只特别提一点,那就是i.MXRT 的 Application 只读段(主要指 ARM 中断向量表)并不可以从任意地址开始链接,有一个小小的限制,必须从选定的存储器地址空间偏移 0x2000 之后开始链接(如选中 ITCM,则必须要链接在 0x00002000 之后;如选中 DTCM,则必须链接在 0x20002000 之后...),因为要预留至少8KB空间给 IVT、BD、DCD 等数据,这个限制是 BootROM 自身决定的,务必要注意。
3.6 CSF(Command Sequence File)
第六个组成部分叫 CSF,是个特性组成,主要用于安全启动的认证相关特性,痞子衡会在安全启动里进一步介绍。
3.7 KeyBlob
第七个组成部分叫 KeyBlob,是个特性组成,主要用于安全启动的加密相关特性,痞子衡会在安全启动里进一步介绍。
上图是包含 IVT、BD、DCD、Application、CSF 的 Bootable image 的 layout,这张图很好地诠释了 IVT 的作用。
四、Bootable image三种分类
前面介绍了 Bootable image 最多有7大组成,有些是必备,有些是可选,有的是特性。而在实际应用中,主要是必备+特性的组合形成如下三种常用分类:
- Unsigned Image: 这是最简单的 image 类型,由 IVT+BD+Application 组成,主要用于产品开发阶段。
- Signed Image: 这是较复杂的 image 类型,由 IVT+BD+Application+CSF 组成,一般用于产品发布阶段。
- Encrypted Image: 这是最复杂的 image 类型,由 IVT+BD+Application+CSF+KeyBlob 组成,主要用于对安全要求较高的产品中。
五、使用elftosb生成Bootable image
恩智浦官方提供了一个用于生成 Bootable image 的工具,名叫 elftosb,这个工具就在\Flashloader_i.MXRT1050_GA\Flashloader_RT1050_1.1\Tools\elftosb 目录下,这个工具可以用来生成所有类型的 Bootable image,命令格式固定如下:
elftosb.exe -f imx -V -c config_application.bd -o ivt_application.bin application.out
其中 ivt_application.bin 就是最终生成的 Bootable image,命令所需要的2个输入文件分别是 application.out、config_application.bd,application.out 就是你的 Application 工程编译链接生成的 ELF 文件,config_application.bd 是用户配置文件,这个 .bd 文件主要是指示 elftosb 工具如何在 Application binary 基础上添加 IVT、BD 等其他信息数据从而形成 Bootable image,所以编写 .bd 文件是关键步骤,bd 文件有专门语法格式,但 \Flashloader_i.MXRT1050_GA\Flashloader_RT1050_1.1\Tools\bd_file\imx10xx 目录下给了很多 bd 文件示例,我们只需要在某一个 bd 文件基础上修改即可。
如果你追过痞子衡博客文章,你应该知道痞子衡曾经实测过 RT1052 的 coremark 性能,coremark 工程已经上传到痞子衡的 github https://github.com/JayHeng/cortex-m_app,工程路径在\cortex-m_app\apps\coremark_imxrt1052\bsp\build\coremark.eww,编译此工程可得到 coremark_a000.out 和 coremark_a000.bin 文件,coremark 程序只读段链接在 ITCM 地址(0x0000a000),我们来试着使用 elftosb 将 coremark 程序转换成 bootable image,bd 文件可参考 imx-itcm-unsigned.bd,打开这个参考 bd 文件:
options {
flags = 0x00;
# Note: This is an example address, it can be any non-zero address in ITCM region
startAddress = 0x8000;
ivtOffset = 0x400;
initialLoadSize = 0x2000;
# Note: This is required if the default entrypoint is not the Reset_Handler
# Please set the entryPointAddress to Reset_Handler address
// entryPointAddress = 0x60002411;
}
sources {
elfFile = extern(0);
}
section (0)
{
}
ivtOffset 和 initialLoadSize 不用改,分别代表 IVT 和 Application 在 Bootable image 中的偏移地址,startAddress 即 BOOT_DATA_T.start,这个是可以修改的,牢记下面公式:
startAddress + initialLoadSize = Application只读段起始链接地址
coremark_a000.out 是链接在 0xa000 地址处的,0x8000 + 0x2000 = 0xa000,所以此处startAddress 也无需改,唯一需要确认的是 entryPointAddress,保险起见统一将entryPointAddress 设成 Application 的复位中断地址,即 entryPointAddress = 0x0000ecd1。bd 文件修改完成之后另存为 config_coremark_a000.bd,让我们试着执行下面命令:
elftosb.exe -f imx -V -c config_coremark_a000.bd -o ivt_coremark_a000.bin coremark_a000.out
分别打开 coremark_a000.bin 和 ivt_coremark_a000.bin,可以看到 ivt_coremark_a000.bin 比 coremark_a000.bin 多了前 8KB 的数据,这前 8KB 里包含了有效的 IVT(偏移0x400)和 BD(偏移0x420)。
六、Bootable image的加载过程
知道了 Bootable image 的构成,痞子衡最后再简要为大家介绍一下 i.MXRT BootROM 是如何从外部存储器中加载 Bootable image 进 SRAM 内存的。以 non-XIP image 加载为例(image 链接在 ITCM 里),下图显示了 i.MXRT 加载 image 的四个阶段:
- 第一个阶段即加载前,此时 Bootable image 完全存储在外部 Flash 中,SRAM 中没有任何 image 数据;
- 第二阶段即初始加载,BootROM 首先会从外部 Flash 读取 Bootable image 前 4KB 数据进 SRAM 临时缓存区(OCRAM:0x20208000 - 0x20208FFF),我们知道这 4KB 数据里包含了 IVT 和 BD,BootROM 从 IVT 和 BD 里获取到 Bootable image 的目标地址(BOOT_DATA_T.start)以及总长度(BOOT_DATA_T.size),此时便可以开始做进一步加载;
- 第三阶段即内部转移,由于 BootROM 已经从外部 Flash 读取了 4KB 进 SRAM 临时缓存区,为了避免重复读取,BootROM 会把这 4KB 数据首先复制到 Bootable image 的目标地址(ITCM);
- 第四阶段即加载完成,BootROM 会接着将剩下的 Bootable image(BOOT_DATA_T.size - 4KB)从外部 Flash 中全部读取出来存到目标区域(ITCM)完成全部加载。
至此,恩智浦 i.MX RT1xxx 系列 MCU 的 Bootable image 格式与加载过程痞子衡便介绍完毕了,掌声在哪里~~~
欢迎订阅
文章会同时发布到我的 博客园主页、CSDN主页、知乎主页、微信公众号 平台上。
微信搜索"痞子衡嵌入式"或者扫描下面二维码,就可以在手机上第一时间看了哦。
最后欢迎关注痞子衡个人微信公众号【痞子衡嵌入式】,一个专注嵌入式技术的公众号,跟着痞子衡一起玩转嵌入式。
衡杰(痞子衡),目前就职于某全球顶级半导体原厂MCU系统部门,担任高级嵌入式系统应用工程师。
专栏内所有文章的转载请注明出处:http://www.cnblogs.com/henjay724/
与痞子衡进一步交流或咨询业务合作请发邮件至 hengjie1989@foxmail.com
可以关注痞子衡的Github主页 https://github.com/JayHeng,有很多好玩的嵌入式项目。
关于专栏文章有任何疑问请直接在博客下面留言,痞子衡会及时回复免费(划重点)答疑。
痞子衡邮箱已被私信挤爆,技术问题不推荐私信,坚持私信请先扫码付款(5元起步)再发。