前言
- 这只是个人的一些经验,见解,有点杂,也难免有错误,欢迎指正;
- uboot用的2016.01;
- SOC以ARM920,S3C2410作为例子;
1 uboot启动过程
reset(CPU设为SVC模式,关闭看门狗,禁用所有中断,设置时钟分频) arch/arm/cpu/arm920t/start.S
cpu_init_crit(关闭MMU和I/DCache) arch/arm/cpu/arm920t/start.S
lowlevel_init(初始化SDRAM) board/samsung/smdk2410/lowlevel_init.S(注1)
_main(设置栈指针,内存搬移,C环境初始化,部分硬件初始化等) arch/arm/lib/crt0.S
board_init_f_mem(给gd(注2)分配一段全零的内存) common/init/board_init.c
如果是SPL:
board_init_f(注3)
spl_relocate_stack_gd(重新设置栈指针) common/spl/spl.c
一堆汇编(清BSS区域) arch/arm/lib/crt0.S
board_init_r(注4)
如果是uboot:
board_init_f(执行init_sequence_f(注5)中的所有函数(注6)) common/board_f.c
一堆汇编代码(重新设置栈指针,重新给gd分配空间) arch/arm/cpu/arm920t/start.S
relocate_code(搬移uboot以及uboot的全局变量区到SDRAM) arch/arm/lib/relocate.S
relocate_vectors(注7)(重定位中断向量表,好像是把0重定位到0xFFFF0000)arch/arm/lib/relocate.S
c_runtime_cpu_setup arch/arm/cpu/arm920t/start.S
一堆汇编(清BSS区域) arch/arm/lib/crt0.S
coloured_LED_init 弱定义函数,用户可以自定义
red_led_on 弱定义函数,用户可以自定义
board_init_r(执行init_sequence_r中的所有函数(注8)) common/board_r.c
注1:这个位置并不统一。有些在arch/arm/mach-XXX/lowlevel_init.S,有些在arch/arm/cpu/.../lowlevel_init.S
注2:gd就是global-data的意思,它的定义是:register gd_t *gd asm ("r13")。用来存储uboot在前期启动时用到的全局变量。
它刚开始使用了R13这个寄存器(也就是栈指针寄存器)作为自己的地址,但是后面它会被分配到内存控制中去。
注3:
board_init_f这个函数的定义是这样的:
#if defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
使用SPL提供的弱定义,这个函数会跳转到board_init_r arch/arm/lib/spl.c(用户可以自定义)
#else
使用uboot提供的定义 common/board_f.c
#endif
这个函数的作用:做一些必要的初始化,为board_init_r做准备,不同的SOC驱动各不相同
注4:
board_init_r这个函数的定义是这样的:
#ifdef CONFIG_SPL_BUILD
#ifdef CONFIG_SPL_FRAMEWORK
使用uboot自带的board_init_r,位于common/spl/spl.c
#else
用户自定义
#endif
#else
使用uboot的定义,位于common/board_r.c
#endif
SPL定义的board_init_r的调用过程:
board_init_r
spl_init
timer_init
board_boot_order
spl_boot_device(获取启动介质,比如NandFlash,SDCARD) 位置由用户决定
spl_load_image common/spl/spl.c
spl_<存储介质>_load_image common/spl/spl_<存储介质>.c
如果启动linux:
spl_board_prepare_for_linux common/spl/spl.c(本身是弱定义,用户可以自定义)
jump_to_image_linux(启动linux) arch/arm/lib/spl.c
如果启动uboot:
jump_to_image_no_args(启动uboot) common/spl/spl.c(本身是弱定义,用户可以自定义)
注5:init_sequence_f是一个函数指针数组,包含大量初始化函数
注6:init_sequence_f常用函数(uboot不规定这些函数放在哪):
board_early_init_f(做什么由用户自定义)
timer_init(不带中断的定时器初始化)
show_board_info(打印板子信息)
misc_init_f(杂项初始化)
dram_init(向gd->ram_size中写入SDRAM的大小)
testdram(测试RAM)
注7:这个函数是弱定义,用户驱动可以重定义
注8:init_sequence_r常用函数(uboot不规定这些函数放在哪):
board_init(板子初始化)
initr_serial(串口初始化)
board_early_init_r(板子早期初始化。说是早期,它却在board_init之后)
arch_early_init_r(架构早期初始化)
initr_nand,initr_onenand,initr_mmc,initr_dataflash(名字写得很清楚了)
mac_read_from_eeprom(从EEPROM中读取MAC地址)
show_board_info(打印板子信息)
arch_misc_init(架构相关的杂项初始化)
misc_init_r(杂项初始化)
interrupt_init(中断初始化)
initr_enable_interrupts(使能中断)
initr_status_led(状态灯初始化)
initr_ethaddr(网络初始化)
board_late_init(板子后期初始化)
initr_net(网卡初始化)
run_main_loop(uboot主循环)
2 部分重要函数
run_main_loop common/board_r.c
main_loop(各种uboot组件初始化) common/main.c
setenv("ver", version_string)(把版本信息写进ver环境变量)
run_preboot_environment_command(读取preboot环境变量并自动执行里面的动作) common/main.c
update_tftp(读取updatefile环境变量自动更新文件) common/update.c
bootdelay_process(读取bootdelay,bootretry,bootcmd等环境变量) common/autoboot.c
menu_show((注9))
cli_secure_boot_cmd(如果设备树里有关于启动的环境变量则读取出来) common/cli.c
autoboot_command(执行启动倒计时,如果检测到空格键输出则执行bootcmd命令) common/autoboot.c
cli_loop common/cli.c
parse_file_outer common/cli_hush.c
cli_simple_loop(循环处理uboot命令行) common/cli_simple.c
注9:menu_show这个函数允许用户自己创建自己的菜单,帮助文档:doc/README.menu
do_bootm common/cmd_bootm.c
do_bootm_states common/bootm.c
...
bootm_find_os(根据镜像头部信息识别操作系统) common/bootm.c
...
bootm_os_get_boot_func(根据不同的操作系统返回各自的启动函数) common/bootm_os.c
...
boot_selected_os common/bootm_os.c
arch_preboot_os(用户驱动在启动内核之前可以做一些自定义动作) bootm_os.c
do_bootm_linux arch/arm/lib/bootm.c
boot_prep_linux(读取bootargs环境变量并通过
setup_XXX_tag准备好ATAGS,如果使用设备树则
准备好设备树) arch/arm/lib/bootm.c
boot_jump_linux arch/arm/lib/bootm.c
一堆代码(从machid环境变量读取机器码,并传给R1寄存器)
announce_and_cleanup common/cmd_bootm.c
cleanup_before_linux(关中断,关闭并清除cache) arch/arm/cpu/arm920t/cpu.c
启动内核(kernel_entry(0, machid, r2)。
r0恒为0,r1等于机器码,r2等于ATAG地址或者设备树地址)
3 一些概念
SPL
SPL就是SecondaryProgramLoader的意思,用来把真正的uboot搬移到内存执行。
大多数系统启动的时候都是把uboot从存储器搬移到内存执行的。这个过程是由系统内部的BootROM完成的。
然而,出于内存大小的限制或者BootROM本身的限制,这个搬移的量各不相同。有些可以把整个uboot搬移,有些则不能。
对于那些不能把整个uboot搬移的系统,uboot提出了SPL这个方案。
SPL属于uboot的一部分,按说应该是FirstProgramLoader才对,但是uboot把BootROM也算进去了,所以是SecondaryProgramLoader。
SPL只能做一些简单而必要的事情,大概就是初始化SDRAM,存储器,搬移uboot。它的使能由*defconfig中的CONFIG_SPL=y
控制。
SPL和uboot同时编译,链接的时候分开链接。由于共享一样的配置文件,所以配置文件中使用#ifdef CONFIG_SPL_BUILD
这样的预编译来区分SPL和uboot,但是注意CONFIG_SPL_BUILD
这个宏是uboot编译系统自动生成的,不能手动修改。
SPL和uboot也可以合成为一份固件,定义这个宏#define CONFIG_SPL_TARGET "u-boot-with-spl.bin"
即可。
SPL的固件大小需要被限制,不然可能不能运行。用这个宏:CONFIG_SPL_STACK
。
4 杂项
- 某些低版本uboot(大约是2020以下)要有ethact这个环境变量才能用网卡;
- uboot把bootcmd这个环境变量删除它就不会跳内核;
- uboot在SDRAM中的地址是
CONFIG_SYS_TEXT_BASE
,小心操作SDRAM的时候不要把这部分内存给改了; - 内核
CONFIG_DEBUG_USER=y
,uboot的bootargs中添加 user_debug=31可以看到更多打印; run
命令很有用,你可以自定义一个变量setenv burnkernel <nand erase ... && tftpboot ... && nand write ...>
然后执行run burnkernel
来简化烧录内核的过程;- uboot恢复env为默认设置:
env default -a
; - uboot的配置文件有两份,一个是
configs/*defconfig
,一个是include/configs/<board_name>.h
; - uboot大概从2014年以后才支持menuconfig,在此之前uboot的配置跟linux很不一样;
- uboot可以直接修改dtb;
- 自动生成的配置文件:include/generated/autoconf.h;
- 特殊环境变量:
- ver 版本信息
- preboot 一连串动作,uboot在进入命令后立即使用run执行的命令
- updatefile uboot启动时自动使用TFTP更新的文件
- loadaddr uboot的TFTP默认下载地址
- bootargs 内核的启动命令行
- bootcmd 这个变量只是指定一连串的用来启动内核的动作,相当于一个脚本
- bootdelay 启动倒计时,可以被空格键打断
- machid uboot启动内核时传的机器码
- 可以在代码里搜索"getenv"来查看uboot都会读取哪些环境变量
- gd中的常用成员
- flags 启动阶段
- have_console console串口是否初始化完成
- bi_arch_number 传给内核的机器号
- SOC驱动位置:arch/arm/mach-XXX,arch/arm/cpu/,board/
- uboot支持简写:bootm可以写成boot
- 怎样开启debug打印:#define DEBUG
- 默认环境变量怎么设置
#define CONFIG_EXTRA_ENV_SETTINGS \ /* 或者CONFIG_EXTRA_ENV_BOARD_SETTINGS */
"bootargs=console=ttyO2,115200n8\0" \
"root=/dev/mmcblk0p2 rw rootwait\0" \
"uservar=hello\0" /* 用户自定义环境变量 */
- 常用命令:
bootm <uImage_addr> // 无设备树,bootm 0x30007FC0
bootm <uImage_addr> <initrd_addr> <dtb_addr> // 有设备树。initrd_addr没有则为-