为启动ARM Linux系统,BootLoader需要初始化多种设备,最终调用Linux内核,并向内核传递硬件相关的信息。
BootLoader最终需要提供以下功能:
a. 建立和初始化内存
b. 初始化一个串口
c. 检测设备类型
d. 设置内核tagged列表(描述硬件参数)
e. 加载initramfs
f. 调用内核镜像
调用内核时,需要满足以下要求:
(1)CPU寄存器设置
r0 =0,
r1 = 机器类型,
r2 =tagged list在内存中的物理地址或设备树块(dtb)在内存中的物理地址。
(2)CPU模式
所有形式的中断必须被禁止;
对于不包含ARM虚拟化扩展的CPU,CPU必须处于SVC模式;
包含虚拟化扩展支持的CPU必须进入HYP模式。
(3) 缓存,MMU
MMU必须关闭;
指令缓存可以是打开或者关闭;
数据缓存必须关闭。
(4)BootLoader应该通过直接跳至内核镜像的第一条指令地址来调用内核。
Bootloader
代码运行流程如下:
首先,可以看到Bootloader的源码目录如下:
U-boot 的启动流程包括两个阶段,第一阶段进行一些基本的初始化动作,为启动第二阶段的主体做准备,此阶段代码由汇编代码写成。第二阶段是进行系统的初始化工作,并准备引导操作系统。下面对这两个阶段进行详细的分析。
从链接脚本文件u-boot.lds(uboot_nanopi2-nanopi2-lollipop-mr1archarmcpuslsiapu-boot.lds)中可以找到代码的起始:
从中知道程序的入口点是_start,定位于
arch/arm/cpu/slsiap/s5p4418/start.o
(即u-boot启动的第一阶段),我们通过分析start.S来看第一阶段进行了怎样的设置.
在start.S中,有:
在上面代码中,设置了u-boot的主入口,跳入了后面的reset。还有对PC赋值,即是实现代码跳转,设置发生“未定义指令”, “软件中断”,“预取指错误”,“数据错误”,“未定义”,“(普通)中断”,“快速中断”的时候,系统所要去执行的代码地址。
Uboot代码从这里开始执行,让系统进入SVC管理模式。首先mrs r0, cpsr 初始化cpsr,而bic r0,
r0, #0x1f 使用r0[0:5] = 0, bic=位清零。在orr r0, r0, #0xd3
中,11010011为cpsr的最终值,其中中断关闭,
模式设置为SVC模式 。最后一句代码将CPSR的值赋给R0寄存器。
上述代码是对cp15协处理器进行初始化设置,进行了TLBs、icache、BP数组的无效处理,并禁止MMU代理和caches。
在这段代码中,跳入了主板确切的初始化位置来进行各种参数的设置。在这里调用board/lowlevel_init.S中的lowlevel_init函数,对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor
Flash、SDRAM才可以被系统使用。
在上述代码中,比较r0和r1,如果不相等,就把代码从flash中复制到ram中。然后进行BSS数据段清理。如果MMU被设置为可使用的话,则可以跳到mmu_turn_on中进行打开处理。
在上面代码中,设置了第二阶段的入口位置,即第二阶段入口函数board_init_r,下面我们进入board.c文件中分析:
在这个源代码文件中,完成的主要工作包括:
(1) 为U-boot内部私有数据分配存储空间,并清零;
(2) 依次调用函数指针数组init_sequence
中定义的函数进行
一系列的初始化;
(3) 如果系统支持 NOR Flash,调用flash_init ()和display_flash_config()初始化并显示检测到的器件信息(AT91SAM9260EK 不需要);
(4) 如果系统支持 LCD
或VFD,调用lcd_setmem()或vfd_setmem()计算帧缓冲(Framebuffer)
大小,然后在BSS 数据段之后为Framebuffer 分配空间,初始化gd->fb_base为Framebuffer的起始地址(AT91SAM9260EK 不需要);
(5) 调用mem_malloc_init()进行存储分配系统(类似于C 语言中的堆)的初始化和空间分配;
(6) 如果系统支持 NAND Flash,调用nand_init ()进行初始化;
(7) 如果系统支持 DataFlash,调用AT91F_DataflashInit()和dataflash_print_info()进行初始化并显示检测到的器件信息;
(8) 调用 env_relocate()进行环境变量的重定位,即从Flash 中搬移到RAM 中;
(9) 如果系统支持 VFD,调用drv_vfd_init()进行VFD 设备初始化(AT91SAM9260EK不需要);
(10)调用 jumptable_init()进行跳转表初始化,跳转表在global_data中;
(11)调用 console_init_r()进行控制台初始化;
(12)如果需要,调用 misc_init_r ()进行杂项初始化;
(13)调用 enable_interrupts()打开中断;
(14)如果需要,调用board_late_init()
进行单板后期初始化;
(15)进入主循环:根据用户的选择启动 linux,或者进入命令循环执行用户输入的命令;
进行源代码流程分析,首先进行一些基本设置:
在上面,第一个方法进行波特率的初始化;第二个方法显示_STACK_START等的地址,比如_start, _bss_start,_bss_end 这些值。
上面的函数用来显示内存的配置,打印出DRAM的大小。
在上面,定义一个初始化的整型指针数组,将在后面被调用。在调用过程中,会依次进行一系列的初始化,分别是初始化环境变量、初始化波特率设置、串口通讯设置、控制台初始化阶段1、打印u-boot信息、显示CPU的配置大小、根据需要显示板的信息、配置可用的RAM。
以下函数内容:
该函数是启动阶段1结束后开始执行的函数,进行主板的初始化配置。我们从上面可以获悉该函数给全局数据变量gd分配内存,对FDT进行地址和空间分配,并且顺序执行init_sequence数组中的初始化函数。该数组在上面分析过,它将依次触发init_sequence定义的函数来进行一系列的初始化操作。
接着有这样一个函数should_load_env:
该函数用来判断是否进行完了uboot的环境加载设置,如果是的话返回1,否则返回0。当进行完环境配置后,会被调用来进行判断。如果是成功的话,进行下一部分的初始化,即进入board_init_r(gd_t *id, ulong dest_addr)函数。
从上面可以看出来,主要有以下几个作用:
a) itor_flash_len =(ulong)&__rel_dyn_end - (ulong)_start: 设置了flash的长度;
b) enable_caches():使缓存可以使用;
c) set_cpu_clk_info():根据需要启动计时器;
d) serial_initialize():串口初始化;
e) mem_malloc_init(malloc_start, TOTAL_MALLOC_LEN):初始化堆空间;
f) flash_size =flash_init():根据需要配置可用的flash;
g) nand_init():根据命令初始化nandflash;
h)
mmc_initialize(gd->bd):根据需要MMC初始化;
i) env_relocate():重新定位环境变量;
j) stdio_init():调用相应驱动函数对硬件设备进行初始化;
k) jumptable_init():初始化跳转表;
l) api_init():如果配置了接口,初始化各个接口;
m) console_init_r():完整地初始化控制台设备;
n) interrupt_init():中断初始化;enable_interrupts():能使中断处理;
o) board_late_init():进行单板后期初始化;
p) eth_initialize(gd->bd):以太网初始化;
在board_init_r(gd_t *id, ulong dest_addr)函数的最后,调用一个for循环,跳入main.c中的main_loop()中去进行uboot启动后的操作,如下:
main_loop()在uboot初始化后,等待进程命令来执行。在该函数里面,进行了Modem功能的设置,打开该功能可以接受其他用户通过电话网络的拨号请求。同时设置U-Boot的版本号,启动延迟功能(s= bootdelay_process())。如果用户按下任意键打断,启动流程,会向终端打印出一个启动菜单。最后自动运行引导内核的命令(autoboot_command(s))。