嵌入式系统学习(六)-bootloader基础及分析

2019-04-15 15:04发布

为启动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 中,11010011cpsr的最终值,其中中断关闭, 模式设置为SVC模式 。最后一句代码将CPSR的值赋给R0寄存器。 上述代码是对cp15协处理器进行初始化设置,进行了TLBs、icache、BP数组的无效处理,并禁止MMU代理和caches。 在这段代码中,跳入了主板确切的初始化位置来进行各种参数的设置。在这里调用board/lowlevel_init.S中的lowlevel_init函数,对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor FlashSDRAM才可以被系统使用。
在上述代码中,比较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():根据需要配置可用的flashg)    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))。