imx6ull uboot启动流程

首先,NXP提供的uboot经过编译最终烧写进存储介质中的是uboot.imx文件,这个imx后缀的文件不同于传统的比如S3C2440最终烧写的uboot.bin文件。

imx文件是在bin文件的基础上加上了一个头部,IMX6ULL芯片内部自带的BOOTROM程序会根据拨码开关的高低电平选择对应的启动介质,从中读取这个头部信息,然后对头部信息进行解析,头部中最重要的是一个叫DCD表的东西。

DCD表中包含了时钟寄存器的地址和寄存器的值,引脚复用寄存器地址和寄存器的值,DDR控制器的寄存器地址和寄存器的值。imx6ull内部的BOOTROM程序会根据DCD表的内容打开时钟,初始化外部DDR。因此NXP提供的uboot代码的汇编阶段没有初始化时钟和初始化DDR的相关汇编代码!这也是NXP的uboot和传统的三星提供的uboot的重大区别。

还有一个区别就是,IMX6ULL的BOOTROM程序会根据解析出来的链接起始地址在一开始就把整个Uboot源码读取到DDR中去,也就是说Uboot的第一行代码就运行在DDR中,这是不同于三星的传统Uboot的,传统Uboot的第一句代码是运行在片内SRAM上的

由于上述的区别,Uboot的重定位过程也就不同了,IMX6ULL的重定位过程是把Uboot整体从DDR的起始地址给挪到DDR的后端地址上去,给Linux内核腾位置。而三星Uboot中重定位是从Flash中把Uboot加载到DDR中去,这也算是一个不同之处。

除了上述提到了几点不同以外,Uboot代码的其他部分功能基本大差不差。

为什么uboot先是在DDR起始地址运行,再挪到后面,不能把uboot一开始就搬到后面去?

DDR的起始地址(CONFIG_SYS_TEXT_BASE)程序运行前就可以知道,重定位后的地址 gd->relocaddr 需要根据自身大小等信息计算出来

为什么uboot要挪到DDR后端地址去

给kernel腾空间,kernel 运行的起始地址一般也是写死在DDR起始地址,因为这个地址对于kernel来说也是可以在运行前确定

IMX6ULL在uboot的汇编阶段要做的事情仅仅包含了:

一、设置CPU运行模式为SVC模式,关闭FIQ和IRQ中断。

设置成SVC模式是为了让CPU可以使用SoC的各种资源。

关中断因为启动过程不允许被打断,否则可能发生错误。

二、设置CP15协处理器的若干个寄存器,包括了设置异常向量表重定位并写入异常向量表的地址,失效Cache,关闭MMU,清除Cache。

重定位异常向量表是因为,我们知道异常向量表的地址是0x00000000,而Uboot是在DDR中运行的,DDR的起始地址肯定不是0x00000000,那么异常向量表必须重定位,这里只是配置好了重定位所需要的寄存器内容,实际的向量表重定位由后续的relocate_vector函数完成。

关闭MMU是因为,MMU负责虚拟地址到实际物理地址的转换,此时还没有加载操作系统,操作的都是实际物理地址,不需要MMU来转换。

清除Cache是因为,Cache的内容是从DDR中缓存过来的,起始阶段DDR中还没有我们加载的内容,此时从DDR中缓存内容到Cache中,如果CPU从Cache中取数据可能导致错误。

三、设置芯片内部的IRAM

划分出一部分用来作为堆栈(方便后续调用C函数),划分一部分用来存储uboot中的重要变量:struct global_data,这个结构体中包含了cpu的时钟频率信息,总线的时钟频率,uboot重定位的地址,外部DDR的大小,Uboot本身的大小的起始地址与终止地址,malloc内存池的大小和位置等等众多重要数据,这些结构体内部的变量是在后续的board_init_f()函数中被初始化的,这些变量被初始化完成以后,Uboot会根据其值进行重定位和一系列对外设的操作。

上述就是imx6ull的uboot在汇编阶段做的事情,下面在arch/arm/lib/crt0.S文件中的_main函数中会调用若干个C函数。

一、board_init_f_alloc_reserve,用于设置内部IRAM,划分出malloc区和存储global_data变量的区域,并将这个变量的地址写入R9寄存器中。

二、board_init_f_init_reserve,将上述函数划分的存储空间进行清零,把早期malloc区的地址写入到global_data结构体变量的malloc_base成员中去。

三、board_init_f,用于初始化部分外设和初始化global_data,这个函数里面有一个函数数组,函数数组中的函数会被依次执行,以此来实现初始化部分外设和global_data,这个函数数组在common/board_f.c文件中定义。这里初始化的外设主要是串口,定时器,初始化global_data主要是初始化其中的地址成员,比如uboot重定位以后的地址,malloc区的基地址,新的global_data变量的地址(因为刚开始global_data是放在内部的IRAM中的)。这个函数执行以后,外部DDR由原本的一张白纸变成了一段一段划分好的区域,每一段用于存储不同的内容,如下图所示:

 

 

四、 relocate_code,就是重定位代码,这个重定位是从DDR到DDR的重定位,因为对于imx6ull来说,一开始uboot就被加载到了DDR上去运行,重定位就是为了把DDR前面的位置空出来以加载Linux内核,该函数定义在文件 arch/arm/lib/relocate.S中。

五、 relocate_vectors,重定位向量表。

六、board_init_r函数,该函数和board_init_f一样,其中有一个函数数组,其中的函数会依次执行,这个函数的作用也是初始化外设,初始化那些在board_init_f函数中没有初始化过的外设, 比如该函数会初始化中断,网络信息,控制台以及存储设备等等。注意:函数数组中有一个叫board_init的函数,这个函数就是imx6ull的板级初始化函数,该函数定义在board/freescale/mx6ullevk/mx6ullevk.c文件中。我们进行Uboot移植的时候如果需要增减代码,基本就是在这个文件中进行代码的编辑。

至此,uboot启动的主要部分就结束了。

接下来进入交互界面等待命令,主要包含三个函数。

一、run_main_loop()--->main_loop()--->autoboot_command(),如果stored_bootdelay秒(默认是3秒)倒计时按下任意键,进入uboot的命令模式,否则启动Linux内核。

二、cli_loop,解析命令行命令函数,执行此函数说明进入uboot命令模式。

三、cmd_process,执行相应的命令。

uboot启动Linux内核的过程:

自动模式

如果倒计时结束未按下任意键,会执行环境变量bootcmd内的命令启动Linux内核,由下图可知,bootcmd的内容是宏CONFIG_BOOTCOMMAND定义的

 

而CONFIG_BOOTCOMMAND是在XXX_defconfig内定义的

 

 

 run findfdt,使用的是 uboot 的 run 命令来运行 findfdt, findfdt 是 NXP 自行添加的环境变量,定义如下图。 findfdt 是用来查找开发板对应的设备树文件(.dtb)

最终分析下来,CONFIG_BOOTCOMMAND干的事情浓缩为以下四步:

 

 

 CONFIG_BOOTCOMMAND还干了一件事,设置环境变量bootargs,传递给linux kernel

root=/dev/mmcblk1p2”表示根文件系统存储在 /dev/mmcblk1p2 中(即SD卡控制器2控制的SD卡或EMMC等设备的分区2)

命令模式

一般进入到uboot命令以后,我们会使用tftp命令或者nfs命令把zImage加载到DDR中去,然后使用bootz命令启动,使用bootz命令就调用了第一个函数。

do_bootz:这个函数主要干三件事。

第一,调用bootz_start函数,这个函数会调用do_bootm_states执行BOOTM_STATE_START阶段;

设置images的ep变量,即系统镜像的地址。

调用bootz_setup去验证镜像;最后调用bootm_find_images查找设备树文件,放在images->ft_addr成员变量中。

第二,关中断

第三,设置images结构体变量的os成员,这个成员也是个结构体变量,设置它为IN_OS_LINUX。然后执行do_bootm_states函数,该函数使用参数标识不同的启动阶段,此时的启动阶段为BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |BOOTM_STATE_OS_GO。

do_bootm_states中会根据images.os.os这个系统类型来查找对应的系统启动函数,这里找到的是do_bootm_linux;do_bootm_linux函数最终会调用boot_jump_linux函数,这是uboot跳转到linux执行的最后一个函数。

boot_jump_linux函数调用了一个叫做kernel_entry的函数,这个函数是Linux内核定义的,kernel_entry是Linux内核镜像文件的第一行代码,地址为images->ep,该函数有三个参数,第一个参数是0,第二个参数是机器ID,第三个参数是ATAGS或者设备树首地址。

一旦开始执行kernel_entry,uboot的生命周期就结束了。

 

 

参考资料——《正点原子Linux驱动开发手册》

具体代码分析

                                       

 

 

 

编译完 uboot,uboot源代码根目录会生成链接脚本:u-boot.lds,里面指明了执行的第一个函数是_start

 

 

 _start 位于arch\arm\lib\vectors.S,其最开始的代码如下:

 所以先跳转到 reset 函数,位于arch\arm\cpu\armv7\start.S,第一段代码如下:

 

 设置CPU工作模式为SVC(管理模式),禁止FIQ和IRQ中断

接下来执行:

 

设置 _start 作为异常向量表的首地址(由前面可知,_start其实就是复位函数,异常向量表就是记录各种异常中断函数指针,复位函数是第一个中断函数)

接下来执行:

 

 无效数据和指令TLB,无效指令缓存,无效分支预测,无效 MUU等

 

posted @ 2022-12-07 20:00  流水灯  阅读(827)  评论(0编辑  收藏  举报