首先需要明确如下几个概念
XIP的概念
XIP即片上执行,它的含义是CPU上电后无需任何别的操作,就可以直接读取该XIP设备上的第一条指令开始执行
号称支持Nand启动、支持SD卡启动、支持USB启动、支持UART启动的芯片,里面必定有BootROM
- BootROM:硬件初始化、把程序从非XIP设备复制进RAM,从RAM里执行
这些其他的启动方式,读取其存储介质里面的指令都必须先初始化对应的控制器,所以需要使用到BootROM来把程序复制到RAM,然后再跳转到RAM开始执行
BootROM的作用
BootROM:硬件初始化,把程序从非XIP设备复制进RAM,从RAM里执行
如何支持多种启动方式(SD卡、EMMC、USB、UART启动)
- 方法1:芯片有boot pin,决定使用哪个外设。bootrom根据引脚决定读取哪个设备的程序
- 方法2:芯片有boot pin,决定多种外设的尝试顺序
- 示例顺序1:SD、EMMC、USB
- 示例顺序2:EMMC、SD、USB
- 示例顺序3:USB
如何将完整的u-boot复制进内存
BootROM被用来启动用户程序,用户程序可能有几百KB、几MB,但是片内的RAM只有几KB:
所以出现了两种方法
- 方法1:
- BootROM从启动设备读取用户程序的前几KB到SRAM
- 这前几KB的代码必须保证初始化好容量大些的DDR,把完整的u-boot复制到DDR,并且跳转执行
- 方法2:
- BootROM从启动设备读取SPL到RAM------SPL(second program loader二级程序加载器)
- SPL:负责初始化DDR,把完整的u-boot复制到DDR,并且跳转执行
我用的韦东山的imx6ull板子使用的是方法1
重定位的2种方法
绝对重定位
从ROM复制程序到DDR的时候,直接重新指定重定位地址(比如DDR的头部地址),复制过来后,DDR的高地址就整块空出来
程序内部的链接地址则在复制过程中或者运行过程中,按照指定的重定位地址,进行修改,改为DDR里面的地址
下面讲解的U-Boot代码就采用了这种绝对重定位的方式
相对重定位
从ROM复制程序到DDR的时候,不强行指定DDR重定位地址,(不指定的话好像是中间地址)
而是复制过去后,按照从头部的偏移量,对其进行访问,即访问的是头部,但是物理地址是中间
ps:这个目前没遇到过,应该是这样hh~~
我用的板子是韦东山的imx6ull
U-Boot的大致加载流程
imx6ull对应的start.S文件是./arch/arm/cpu/armv7/start.S
该文件还调用了别的外部汇编文件

首先bootRom根据启动引脚,初始化EMMC外设,并去EMMC读取imx文件前面的几KB代码到SOC的SRAM上运行,imx6ull并没有把SPL单独拿出来编译,而是把所有这些初始化的函数放在了imx映像文件的头部几KB,仅仅是在board_init_r这里借用了SPL的代码框架回调厂商自己的函数
1.完成对cp15这个协处理器的初始化
U-Boot 需要干净、可预测的环境,所以需要实现以下功能:禁用 MMU(用物理地址跑代码)、禁用 cache(避免 stale data)、把向量表搬到 RAM(relocatable)
2.cpu_init_crit清理干净CPU
3.然后crt0.S文件中定义了__main函数,跳转到该函数执行
3.1.__main头部设置了栈顶指针sp,引导过程中的全局变量GD
3.2.该__main函数的开头位置,调用了下面SOC厂商提供的board_init_f函数

3.3.board_init_f函数执行结束后,DDR就能用了,所以设置载入u-boot时候的在DDR中的重定位地址(通常是DDR顶部)
后续复制u-boot到DDR的时候会将u-boot内部的链接地址也进行修改,改为符合DDR的地址

3.4.board_init_r 则借用了SPL的框架,内部塞了imx6ull对应的回调函数,负责在初始化一些子系统后真正加载u-boot到DDR中开始执行
U-Boot初始化的两个阶段
u-boot的源码初始化阶段大致可以分为2个阶段:
- board_init_f:f的意思是"running from read-only flash"
- 说是flash,其实还是加载到RAM中执行的
- 作用:初始化硬件(比如DDR、UART),为各个功能预留内存(比如U-boot、Framebuffer、设备树)
- board_init_r:r的意思是"relocated",意思是重定位过了
- 它这个重定位指的是后续复制U-Boot到DDR的时候,重定位会起作用,把U-Boot放到DDR头部
- 作用:初始化各个子系统(各个存储设备、环境变量、网络),进入main_loop
board_init_f
找到我当前板子,imx6ull的对应board_init_f函数,在文件Uboot-2017.03/board/freescale/mx6ul_14x14_evk/mx6ul_14x14_evk.c里面


我当前这块板子的默认配置文件是mx6ull_14x14_ddr3_arm2_emmc_defconfig,他没有使用SPL放在U-boot程序头的位置
所以和上面的代码就对上了,这里是在SOC厂商的代码里面使用的spl_dram_init函数来初始化DDR作为二级加载的位置,并没有使用SPL
现在开始分析board_init_f做了什么事情
ccgr_init()内部向ccm的CCGR寄存器写入11,即打开所有外设的时钟

arch_cpu_init()
设置了AIPS---Advanced IC Peripheral Bus 高级外设总线的开启
并且将其分频数值等设置好
并且关闭了一上电就默认开启的看门狗,防止打断后续过程
board_early_init_f()
设置复用uart

timer_init()
初始化U-Boot系统时钟,GPT是general purpose timer
该时钟提供整个 U-Boot 运行期间的时间基准

preloader_console_init()
这里串口被选为console
打开串口,方便后续输出启动前的所有信息到串口中
并且可以在U-Boot运行过程中通过串口进行交互

spl_dram_init()
初始化imx6ull自带的ddr3
memset(__bss_start, 0, __bss_end - __bss_start);
清零ZI-data的区域,因为SRAM上电后是随机值
静态RAM有这种特性
board_init_r(NULL, 0)
最后跳转到board_init_r开始执行
board_init_r
board_init_r:r的意思是"relocated",意思是重定位过了(设置了复制u-boot代码到DDR中的重定位位置)
该函数在/common/spl.c中定义,SOC厂商则提供回调函数给改函数
- 作用:初始化各个子系统(各个存储设备、环境变量、网络),进入main_loop

spl_boot_list是spl从对应设备加载u-boot的顺序列表
按理说从哪个设备载入程序执行是由bootRom决定的,但是bootRom不够灵活,所以这里可以自己定义更灵活的设备引导顺序
和bootRom的设计理念也不冲突,因为bootRom决定的是上电完成初始化后的第一行应用程序代码从哪里载入
这个应用程序的第一行代码跑起来后(往往是SPL或者SOC厂家自定义的初始化DDR代码),具体后面从哪里载入u-boot就不是bootRom能决定的了
spl_image是spl要载入的u-boot映像文件信息,包含os类型,入口地址,大小等
290行则是如果板子定义了 SPL 专用的 malloc 区域,则为其在ddr中申请这块区域,同时设置全局标志 GD_FLG_FULL_MALLOC_INIT,表示 malloc 已就绪(后续代码可以用 malloc)
295行则是检查是否调用过spl_init,没有的话就调用spl_init
spi_init中则会用到设备树,为spl要使用的设备进行初始化
307行,则是可以由SOC厂商加一点自定义代码进去.比如点个灯啥的

312行,获取到SPL加载u-boot的ROM设备顺序
这个board_boot_order内部会调用SOC厂商提供的回调函数,获取到加载顺序
321行,根据OS的类型,uboot,则跳出到334行继续执行
334行的条件,在.config文件中满足了,这里是一条调试信息,输出SPL阶段malloc了多少字节的数据
340行 ,imx6ull的u-boot文件中没写同名函数覆盖,这是个弱定义,所以啥都不做
因为关看门狗,初始化时钟,分频时钟,这些早都在board_init_f中完成了

341行,根据spl_image的入口地址跳转到u-boot执行,跳转后,SPL 阶段结束,控制权交给完整 U-Boot
该函数在imx-common/spl.c中定义,即所有imx.系列都用这个函数

参考
韦东山老师的直播公开课【u-boot完全分析与移植】 https://www.bilibili.com/video/BV1L24y187cK/?share_source=copy_web\&vd_source=a487d2d970df1f64a42c4883b55cb49f