LV04-01-IMX6ULL启动流程-03-映像文件
本文主要是I.MX6ULL启动流程——映像文件介绍的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows版本 | windows11 |
Ubuntu版本 | Ubuntu16.04的64位版本 |
VMware® Workstation 16 Pro | 16.2.3 build-19376536 |
终端软件 | MobaXterm(Professional Edition v23.0 Build 5042 (license)) |
Linux开发板 | 正点原子 i.MX6ULL Linux 阿尔法开发板 |
uboot | NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) |
linux内核 | linux-4.15(NXP官方提供) |
Win32DiskImager | Win32DiskImager v1.0 |
点击查看本文参考资料
分类 | 网址 | 说明 |
官方网站 | https://www.arm.com/ | ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档 |
https://www.nxp.com.cn/ | NXP官方网站 | |
https://www.nxpic.org.cn/ | NXP 官方社区 | |
https://u-boot.readthedocs.io/en/latest/ | u-boot官网 | |
https://www.kernel.org/ | linux内核官网 |
点击查看相关文件下载
分类 | 网址 | 说明 |
NXP | https://github.com/nxp-imx | NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库 |
nxp-imx/linux-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP linux内核仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
nxp-imx/uboot-imx/releases/tag/rel_imx_4.1.15_2.1.0_ga | NXP u-boot仓库tags中的rel_imx_4.1.15_2.1.0_ga | |
I.MX6ULL | i.MX 6ULL Applications Processors for Industrial Products | I.MX6ULL 芯片手册(datasheet,可以在线查看) |
i.MX 6ULL Applications ProcessorReference Manual | I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网) | |
Source Code | https://elixir.bootlin.com/linux/latest/source | linux kernel源码 |
https://elixir.bootlin.com/u-boot/latest/source | uboot源码 |
假设使用 SD/TF 卡启动,卡上的程序有多大?它应该被复制到 DDR 哪里去? 这一部分我们主要参考《i.MX 6ULL Applications Processor Reference Manual》的8.7 Program image 一节。
一、格式概述
1. 不能烧写bin文件?
前边我们学习过单片机的话,我们知道程序有很多的格式,它们包含的信息不同。比如 lcd.bin 可以直接烧写到 Flash 上(需要注意的是平时调试STM32单片机程序用的其实是Hex文件,而在IAP升级固件时要用到Bin文件,hex文件比bin文件多了一些地址信息,这些地址信息是下载工具解析的时候要用到的)。它们是自启动的,这什么意思?比如一上电,运行的是 lcd.bin 前面的代码,它会初始化内存,把自己从 Flash 上复制到内存里去执行。要注意:是自己把自己复制到内存。
但是对于 IMX6ULL,烧写在 EMMC、 SD/TF 卡上的程序,并不能“自己复制自己”,是“别人把它复制到内存里”。一上电首先运行的是 boot ROM 上的程序,它从 EMMC、 SD/TF 卡上把程序复制进内存里。所以: boot ROM 程序需要知道从启动设备哪个位置读程序,读多大的程序,复制到哪里去。
所以,在启动设备上,不能仅仅烧写 bin 文件,还需要再添加额外的信息。
2. Device Configuration Data 是什么?
还有一个问题, IMX6ULL 的 boot ROM 程序可以把程序读到 DDR 里,那需要先初始化 DDR。每种板子接的 DDR 可能不一样, boot ROM 程序需要初始化这些不同的 DDR。 boot ROM 从哪里得到这些不同的参数?
还有, IMX6ULL 支持各种启动设备,比如各种 Nor Flash。为了通用, bootROM 程序将会使用最保守的参数,也就是最慢的时序来访问 Nor Flash。为加快启动程序, boot ROM 程序可以根据我们提供的信息初始化硬件,让它以更优的参数运行。
这些参数信息,被称为“ Device Configuration Data”,设备配置数据(DCD),这些 DCD 将会跟 bin 文件一起打包烧写在启动设备上。 boot ROM 程序会从启动设备上读出 DCD 数据,根据 DCD 来写对应的寄存器以便初始化芯片。DCD 中列出的是对某些寄存器的读写操作,我们可以在 DCD 中设置 DDR 控制器的寄存器值,可以在 DCD 中使用更优的参数设置必需的硬件。这样 boot ROM 程序就会帮我们初始化 DDR 和其他硬件,然后才可以把 bin 程序读到 DDR 中并运行。
3. 总结一下?
总结起来,烧写在 EMMC、 SD 卡或是 TF 卡上的,除了程序本身,还有位置信息、 DCD 信息,这些内容合并成一个映像文件,如下图
I.MX6U 的最终可烧写文件组成如下:
- (1) Image vector table,简称 IVT, IVT 里面包含了一系列的地址信息,这些地址信息在ROM 中按照固定的地址存放着。
- (2)Boot data,启动数据,包含了映像要拷贝到哪个地址,拷贝的大小是多少等等。
- (3)Device configuration data,简称 DCD,设备配置信息,重点是 DDR3 的初始化配置。
- (4)用户代码可执行文件,比如 led.bin。
这 4 部分内容合并成为一个映像文件,烧写在 EMMC、 SD 卡或 TF 卡等启动设备的某个固定地址, boot ROM 程序去这个固定地址读出映像文件。启动设备不同,固定地址不同。所以其实最终烧写到 I.MX6U 中的程序组成为: IVT + Boot data + DCD + .bin,这些最终会组成一个 .imx 格式为文件,这个 .imx 文件才是我们最终烧写到I.MX6U中的可运行的程序。
以正点原子的 led.bin 裸机程序为例,我们将会在ubuntu中通过一个 imxdownload 的工具(正点原子提供的,但是后面发现u-boot源码中有个工具也可以生成imx文件,后边会再分析这个工具)以 led.bin 为基础的 load.imx 文件,然后将 load.imx 文件烧写到开发板中,这样,开发板就会可以运行这个裸机程序了。load.imx 就是在 led.bin 前面加上 IVT+Boot data+DCD。内部 BootROM 会将 load.imx 拷贝到 DDR 中,用户代码是要一定要从 0X87800000 这个地方开始的,因为链接地址为 0X87800000, load.imx 在用户代码前面又有 3KByte(为什么是3KB?后面会拿到答案的) 的 IVT+Boot Data+DCD 数据 。因此 load.imx 在 DDR 中的起始地址就是 0X87800000-3072 = 0X877FF400。
二、格式详解
1. Image Vector Table和Boot Data
【说明】下边有些图中有 C 语言格式的结构体,这些结构体来源于 u-boot源码 的tools 目录下的 imximage.h(这个是当时刚学习的时候用的2016版本的buoot中的 imximage.h - tools/imximage.h - U-boot source code v2016.03 - Bootlin) 后来这个文件换目录了,至少在2019.04版本中已经挪到了source/include目录下:imximage.h - include/imximage.h - U-boot source code v2019.04 - Bootlin
1.1 概述
映像向量表(Image Vector Table, IVT)是ROM从提供程序映像的引导设备中读取的数据结构,该程序映像包含成功启动所需的数据组件。IVT包括程序映像入口点、指向设备配置数据(DCD)的指针和ROM在引导过程中使用的其他指针。内部 boot ROM 要求 IVT 应该放到指定的地址 ,该地址由连接到芯片的引导设备确定,而 IVT 在整个 .imx 文件的最前面,其实就相当于要求 .imx 文件在烧写的时候应该烧写到存储设备的指定位置去 。每个引导设备类型的IVT相对基址(存储设备的起始地址)的偏移量和初始加载区域大小在下表中定义:
以 SD/EMMC 为例, IVT 偏移为 1Kbyte, IVT+Boot data+DCD 的总大小为 4KByte-1KByte=3KByte。假如 SD/EMMC 每个扇区为 512 字节,那么 load.imx 应该从第三个扇区开始烧写,前两个扇区要留出来。也就是说,后面我们用到的 .imx 从第 3KByte 开始才是真正的.bin 文件(.imx文件开头就是IVT数据)。那么 IVT 里面究竟存放着什么东西呢?后边会再详细说明。
IVT的位置是ROM唯一固定的要求,其余的或映像存储器映射是灵活的,由IVT的内容决定。
1.2 Image Vector Table(IVT)
IVT 会被放在固定的地址, IVT 中是一系列的地址, boot ROM 程序会根据这些地址来确定映像文件中其他部分在哪里。
图二-1-1.2-1 IVT格式
要注意的是上图中这 4 个部分:
- (1)header:里面有 3 项: tag、 length、 version。 length 表示 IVT 的大小,它是 32 字节。要注意是的,它是大字节序的。
图二-1-1.2-2 IVT header格式
其中Tag 为一个字节长度,固定为 0XD1, Length 是两个字节,保存着 IVT 长度,为大端模式,也就是高字节保存在低内存中。最后的 Version 是一个字节,为 0X40 或者 0X41。
- (2)entry:用户程序运行时第 1 条指令的地址,就是程序的链接地址、程序被复制到内存哪里。
- (3)dcd:映像被复制到内存后,其中的 DCD 数据的地址。
- (4)boot data:映像被复制到内存后,其中的 boot data 的地址。
- (5)self:映像被复制到内存后, IVT 自己所在的地址。
1.3 Boot data
映像被复制到内存后,整个映像文件(IVT 之前还有几个扇区数据,比如分区表)所在的地址。 Boot Data 的数据格式如图 :
图二-1-1.3-1 Boot data格式
- (1)start:这是映像文件在内存中的地址
以 SD/TF 卡为例:映像文件=(1K 数据,内含分区表等信息)+IVT+BootData+DCD+用户数据(.bin 文件)。
注意, IVT 并不在映像文件的最前面, start也不是 IVT 在内存中的地址,而是整个映像文件在内存中的地址:start = IVT 在内存中的地址 - IVT offset。什么意思?假设 IVT 被保存在启动设备 TF 卡 1024 偏移地址处, IVT 被复制到内存地址 0x87000000,那么 start = 0x87000000 -1024。
所以 start 表示的是启动设备开头的数据,被复制到内存哪里去。从它的含义也可以推理出: boot ROM 程序会把启动设备开头的数据,复制到内存;而不仅仅是从 IVT 开始复制。
- (2)length:保存在启动设备上的整个映像文件的长度,从 0 地址开始(不是从 IVT 开始)。
- (3)plugin:这是一个标记位,当它为 1 时表示这个映像文件是“plugin”,即插件。
boot ROM 程序可以支持有限的启动设备,如果想支持更多的启动设备比如网络启动、 CDROM 启动,就需要提供对应的驱动。这些驱动就是“ plugin ”,暂时还没学习那么深入的东西,先不管这里,该标记位为 0。
**Boot data 就是用来表示映像文件应该被复制到哪里去,以及它的大小。boot ROM 程序就是根据它来把整个映像文件复制到内存去的。 **
1.4 实际情况分析
那实际情况是怎样的呢?我们使用 winhex 软件(或者可以以十六进制阅读二进制文件的软件都行)打开 load.imx(正点原子裸机程序里边的,其实u-boot编译后生成的imx程序也一样) ,winhex 可以直接查看一个文件的二进制格式数据,但是这个软件还需要安装,我们其实还可以通过notepad++,只是需要在安装一个 Hex-Editor 的软件,这个在软件的插件选项中搜索安装即可。
图二-1-1.4-1 load.imx文件
我们将前 44 个字节的数据按照 4 个字节一组组合在一起就是: 0X402000D1、0X87800000、 0X00000000、 0X877FF42C、0X877FF420、 0X877FF400、 0X00000000、 0X00000000、0X877FF000、 0X00200000、 0X00000000。这 44 个字节的数据就是 IVT 和 Boot Data 数据,与上边对应起来就是:
- IVT数据分析
IVT 结构 | 数据 | 描述 |
---|---|---|
header | 0X402000D1 | 根据上边 图二-1-1.2-2 中 header 格式,第一个字节 Tag 为 0XD1,第二和第三这两个字节为 IVT 大小,为大端模 式,所以 IVT 大小为 0X20=32 字节。第四个字节 为 0X40。完全符合上边 图二-1-1.2-2中header的格式。 |
entry | 0X87800000 | 入口地址,也就是映像第一行指令所在的位置, 0X87800000 就是我们的链接地址。 |
reserved1 | 0X00000000 | 未使用,保留。 |
dcd | 0X877FF42C | DCD 地址,映像地址为 0X87800000, IVT+Boot Data+DCD 整个大小为 3KByte。因此 load.imx 的起始地址就是 0X87800000-0XC00=0X877FF400。因 此 DCD 起始地址相对于 load.imx 起始地址的偏移 就是0X877FF42C-0X877FF400=0X2C,也就是说从 图1.4.1-1 中的 0X2C 这个地址开始就是 DCD 数据 了。 |
boot data | 0X877FF420 | boot 地址, header 里面已经设置了 IVT 大小是 32 个 字 节 , 所 以 boot data 的地址就是 0X877FF400 + 32 = 0X877FF420。 |
self | 0X877FF400 | IVT 复制到 DDR 中以后的首地址。 |
csf | 0X00000000 | CSF 地址。 |
reserved2 | 0X00000000 | 保留,未使用。 |
- Boot Data数据分析
Boot Data 结构 | 数据 | 描述 |
---|---|---|
start | 0X877FF000 | 整个 load.imx 的起始地址,包括前面 1KByte 的地址偏移 |
length | 0X00200000 | 映像大小,这里设置 2MByte,映像大小不能超过 2MByte |
plugin | 0X00000000 | 插件 |
上边的这两个表中写出了 load.imx 的 IVT+Boot Data 每 32 位数据所代表的意义。这些数据都是由 imxdownload 这个软件添加进去的(但其实应该是u-boot中有工具可以完成的)。
2. Device Configuration Data (DCD)
2.1 概述
复位后,芯片使用系统中所有外设的默认寄存器值。但是,这些设置对于实现最佳系统性能来说通常并不理想,甚至有些外设在使用之前必须进行配置。为此 I.MX6U 提出了 DCD(DeviceConfig Data)的概念,和 IVT、 Boot Data 一样, DCD 也是添加到 load.imx 里面的,紧跟在 IVT和 Boot Data 后面, IVT 里面也指定了 DCD 的位置。
简单地说DCD就是设备的配置信息,就是 I.MX6U 寄存器地址和对应的配置信息集合, Boot ROM 会使用这些寄存器地址和配置集合来初始化相应的寄存器,比如开启某些外设的时钟、初始化 DDR 等等 。实际上 DCD 还可以更复杂,它支持多种命令: write data、 check data、nop、 unlock。我们可以通过 write data 命令写寄存器,通过 check data 命令等待寄存器就绪。
2.2 DCD的格式
图二-2-2.2-1 DCD数据格式
- (1)DCD 以 Header 开始
其中 Tag 是单字节,固定为 0XD2, Length 为两个字节,表示 DCD 区域的大小,包含 header,同样是大端模式, Version 是单字节,固定为 0X40 或者 0X41。
- (2)接下来就是各个“ CMD”
我们可以在一个“ CMD”里操作多个寄存器,比如在一个“write data command”中,写多个寄存器。
2.3 DCD的CMD介绍
以“ write data command”为例简单介绍一下,它的格式为:
图二-2-2.3-1
其中Tag 为一个字节,固定为 0XCC。 Length 是两个字节,包含写入的命令数据长度,包含 header,同样是大端模式。 Parameter 为一个字节,这个字节的每个位含义如图 :
Parameter 中 b[2:0]用来表示写操作的字节数,是以字节、半字(2 byte),还是字(4 byte)来操作。而 b[4]、 b[3]决定了是写值(write value),清位(clearbitmask),还是设位(set bitmask)。
既然是写命令,那自然就有“地址、值”,上图中就是多个“ Address、Value/Mask”。
2.4 实际情况分析
有前边分析可知,DCD 数据是从0X2C 地址开始的。根据我们分析的 DCD 结构可以得到 load.imx 的 DCD数据 :
从这个表中可以看出, DCD 里面的初始化配置主要包括三方面:
①、设置 CCGR0~CCGR6 这 7 个外设时钟使能寄存器,默认打开所有的外设时钟。
②、配置 DDR3 所用的所有 IO。
③、配置 MMDC 控制器,初始化 DDR3。
3. User code and data
就是用户程序或数据,原原本本地添加到映像文件里就可以。
三、实例分析
我们制作映像文件的目的什么?目的就是把我们自己的程序烧写到启动设备,让boot ROM 程序启动它。 这一部分我用NXP官方的u-boot的imx镜像文件为例进行分析。我用的文件是 mfgtools-with-rootfs-mine/mfgtools/Profiles/Linux/OS Firmware/files 目录中的 u-boot-imx6ull14x14evk_emmc.imx 文件(这个文件应该是当时学习自定义烧写工具的时候复制正点原子出厂uboot的那个文件,不过自己随便编一个都一样的,这里主要是学习一下映像文件各个部分怎么分析)。
1. 各项值的计算
制作映像文件的起点是:我们编写的程序(这里以u-boot为例)。 制作过程中各项值的计算方法如下图所示:
我们不需要手工去计算,一个 mkimage 命令就可以完成,这个工具在u-boot源码的tools目录下就有。
- (1)确定入口地址 entry
我们的程序运行时要放在内存中哪一个位置,这是我们决定的。它被称为入口地址、链接地址。
- (2)确定映像文件在内存中的地址 start
boot ROM 程序启动时,会把“ Initial Load Region”读出来,“ Initial load Region”里含有 IVT、 Boot data、 DCD。 boot ROM 根据 DCD 初始化设备后,再把整个映像文件读到内存。
在启动设备上,“ Initial Load Region”之后紧跟着我们的程序,反过来说就是我们程序的前面,放着“ Initial Load Region”。假设“ Initial LoadRegion”的大小为 load_size,那么在内存中“ Initial Load Region”的位置 start = entry – load_size。
注意:“ Initial Load Region ” 位于启动设备 0 位置,它的头部并不是IVT,而是一些无用的数据(或是分区信息)。
在 IMX6ULL参考手册中有一个表格,列出了不同启动设备对应的“ Initial LoadRegion Size”:
- (3)确定 IVT 在内存中的地址 self
我们知道 IVT 在启动设备上某个固定的位置,上图中的“ Image Vector Table Offset”: ivt_offset。那么在内存中它的位置可以如下计算:
1 | self = start + ivt_offset = entry - load_size + ivt_offset |
- (4)确定 Boot data 在内存中的地址 boot_data
IVT 的大小是 32 字节, IVT 之后就是 Boot data,而 IVT 中的 boot_data值表示 Boot data 在内存中的位置,计算如下:
1 | boot_data = self + 32 = entry - load_size + ivt_offset + 32 |
- (5)确定 DCD 在内存中的地址 dcd
Boot data 的大小是 12 字节, Boot data 之后就是 DCD,而 IVT 中的 dcd值表示 DCD 在内存中的位置,计算如下:
1 | dcd = boot_data + 12 = entry - load_size + ivt_offset + 44 |
- (6)写入 DCD 的数据
DCD 是用初始化硬件的,特别是初始化 DDR。而 DDR 的初始化非常的复杂、专业,我们一般是使用硬件厂家提供的代码。 在正点原子的代码中,提供了一个名为 imxdownload 的程序,这个程序会写入相应的DCD数据,但其实后来看了另外几家的教程,发现其实这些DCD数据厂家是有提供的,我们在u-boot源码的tools目录下有工具可以帮助使我们生成imx文件,同时也会将相应的DCD数据写入,比如我们可以使用类似下面的指令来制作映像文件:
1 | cd u-boot # 进入u-boot源码目录 |
imximage.cfg.cfgtmp文件(这个文件好像是通过imximage.cfg生成的)是被拷贝到tools目录下的,我们在NXP官方原版的u-boot源码中,i.mx6ull的imximage.cfg文件位于u-boot源码的 board/freescale/mx6ullevk/ 目录下,
从上图也可以看到 imximage.cfg.cfgtmp 文件中基本是对寄存器的写操作。mkimage 程序来自 u-boot,它会把 imximage.cfg.cfgtmp 中的内容转换为 DCD数据。我们只需要了解它的大概作用:
(a)设置时钟: DDR 也需要时钟。
(b)设置引脚: DDR 需要很多引脚。
(c)设置 DDR 控制器: Multi-mode DDR controller (MMDC)。
- (7)写入用户程序
- (8)经过上述 7 个步骤,整个映像文件就构造出来了,可以把它烧入启动设备。
2. u-boot-imx6ull14x14evk_emmc.imx
对于.imx文件而言,文件开头就是 IVT,我们可以把它烧写到 TF 卡 1024 偏移位置处,可以通过 USB 把 imx 文件直接下载到板子上,并运行。用notepad++打开后以十六进制显示,低地址存放的是低位,如下图:
比如第一行,组合起来就是0x402000d1、 0X87800000、 0X00000000、 0X877ff42c,一行相当于是16个字节,连续4个字节算作一组。格式分析如下,前边每一部分其实都分析过了,这里就不详细分析了:
四、映像的烧写和运行
我们编译出来的映像文件有 2 类后缀: imx、 img。 imx 文件开头就是 IVT,可以把它烧写到 TF 卡 1024 偏移位置处(这就意味着我们烧写到TF卡的时候需要先做一个1KB的空文件); img 文件开头是 1024 字节的 0 值数据,后面才是 IVT 等,它可以直接烧写到 TF 卡 0 偏移位置处。
另外,我们可以通过 USB 把 imx 文件直接下载 到板子上,并运行。注意:通过 USB 下载方式,可以烧写程序到 EMMC、 TF 卡上,但是并非“直接烧写”。它的过程如下:
(1)通过 USB 下载 u-boot 到内存。
(2)通过 USB 下载用户程序到内存。
(3)通过 USB 发送命令运行 u-boot。
(4)用 u-boot 烧写把内存中的用户程序烧写到 EMMC、 TF 卡上。