uboot-启动流程

下面以u-boot 2016为例,一行一行分析armv7架构cpu的uboot启动流程,用到的soc是imx6ull为例。总体流程如下:分为2部分:
①arch级初始化(架构)
②板级初始化
image

1 reset 函数#

1.1 初始化异常向量表#

我们知道启动入口是arch/arm/lib/vectors.S文件中的_start
image
从函数入口_start可以看到,入口的指令存放的就是中断向量表,0地址偏移存放reset, 0x4地址存放_undefined_instruction0x8地址存放_software_interrupt...
CPU上电最开始进入_start,进而进入reset函数,该函数定义在arch/arm/cpu/armv7/start.S 里面。
image
image
函数调用关系如下:reset->save_boot_params->save_boot_params_ret

1.2 save_boot_params_ret#

1.2.1 关中断,进入SVC模式#

save_boot_params_ret 函数代码如下:
image
该函数功能如注释所写主要是关闭IRQ和FIQ, 进入SVC模式,都是控制cpsr寄存器。

1
2
3
4
5
6
//第一行读取 cpsr寄存器
//第二行对r0和0x1a进行与运算,目的是取出cpu模式。
//第三行如果 r1 和 0X1A 不相等,也就是 CPU 不处于 Hyp 模式的话就将 r0 寄存器的低5位清零,清除模式位
//第4行如果处理器不处于 Hyp 模式的话就将 r0 的寄存器的值与 0x13 进行或运算, 0x13=0b10011,也就是设置处理器进入 SVC 模式、
//第5行与 0xC0或运算,那么 r0 寄存器此时的值就是 0xD3,cpsr 的 I 为和 F 位分别控制 IRQ 和 FIQ 这两个中断的开关,设置为 1 就关闭了 FIQ 和 IRQ。
//第6行r0写入cpsr

image
image

1.2.2 cp15配置,设置中断向量表偏移VBAR#

image
这里一般2个宏都没有定义,进入,这段作用是设置中断向量表偏移VBAR
这里是把CP15 SCTLR Register读出来,bic是位清0,CR_V arch/arm/include/asm/system.h 中定义了:

1
define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */

那么就是把CP15 SCTLR Register读出来,对bit13清0,再写进去SCTLR Register

如下图是SCTLR 寄存器bit13 为 V 位,此位是向量表控制位,当为 0 的时候向量表基地址 为 0X00000000,软件可以重定位向量表。为 1 的时候向量表基地址为 0XFFFF0000,软件不能 重定位向量表。
这里将 V 清零,目的就是为了接下来的向量表重定位。
image
最后将_start给到r0, r0写入到 CP15 的 c12 寄存器中,也就是VBAR寄存器,完成中断向量表偏移的设定。
中断向量表的初始定义在_start入口位置,0x87800000 就是向量表的起始地址。
image

1.2.3 cp15配置,关MMU,ICACHE#

image
bl cpu_init_cp15一看就是初始化协处理器寄存器CP15, 比如关闭MMU, ICACHE等。cpu_init_cp15函数如下:都是一些CP15协寄存器操作指令,就不再一一解释。
image
总结save_boot_params_ret做的事情:
①初始化中断向量表
②关FIQ,IRQ
③进入SVC模式
④设置中断向量表偏移VBAR
⑤初始化CP15,关MMU,ICACHE等。

1.3 lowlevel_init对sram内存布局#

初始化CP15,关MMU,ICACHE后,会继续执行进入cpu_init_critcpu_init_crit也是直接跳到lowlevel_init函数。
image
lowlevel_init 在文件arch/arm/cpu/armv7/lowlevel_init.S中定义:
image
前面2行定义sp 指向 CONFIG_SYS_INIT_SP_ADDR设置SP指针,CONFIG_SYS_INIT_SP_ADDR include/configs/mx6ullevk.h
image
IRAM_BASE_ADDR IRAM_SIZE在 文 件arch/arm/include/asm/arch-mx6/imx-regs.h中有定义,imx6ull这颗芯片内部有一个sram叫做ocram,基地址就是0x00900000,size为128k(0x20000)
image
image
再来看GENERATED_GBL_DATA_SIZE的值:该定义是编译生成的include/generated/generic-asm-offsets.h中:
image
这里提一句,GENERATED_GBL_DATA_SIZE 的含义为 (sizeof(struct global_data) + 15) & ~15,该结构体后面会讲到。
综上所述,CONFIG_SYS_INIT_SP_ADDR 值如下:

1
2
CONFIG_SYS_INIT_SP_OFFSET = 0x00020000256 = 0x1FF00
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0X1FF00 = 0X0091FF00

image
从图可知:
内部sram的地址范围:0x00900000~0x0091ffff (128k)
SP指向:0x0091ff00
继续分析汇编代码,sp又继续减了一个GD_SIZE,然后8字节对齐,赋值给r9
GD_SIZE 同样在 generic-asm-offsets.h 中定义了,大小为 248。最后SP指针位置就定义好了,将ip,lr入栈,进入函数s_init函数,s_init函数返回后,ip和lr出栈,并将lr赋给pc。最终内存指向如下:SP指向:0x0091fe08
image
总结:lowlevel_init函数就是在内部sram下设置SP指针,来为c语言的运行配置环境。

1.4 s_init函数#

s_init 函数定义在文件 arch/arm/cpu/armv7/mx6/soc.c
image
由于我们的cpu是mx6ull,那么满足最开始的if判断,那么s_init函数直接返回,什么也没做。
总结_reset过程:

1
s_init返回了,lowlevel_init也返回了,cpu_init_crit也返回了,回到 save_boot_params_ret。

总结下整个系统上电的reset过程:
image

2 _main函数#

_main 函数定义在文件arch/arm/lib/crt0.S中:
image

2.1 sram中指定SP#

如注释写的,第76行,初始化c语言运行环境,SP设置到CONFIG_SYS_INIT_SP_ADDR,也就是0x0091ff00。第83行,8字节对齐。

2.2 board_init_f_alloc_reserve(指定malloc/gd指针)#

86 行,调用数 board_init_f_alloc_reserve,r0作为形参传给该函数。
image
该函数主要是留出早期的malloc内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400( 在 文 件 include/generated/autoconf.h 中定义, CONFIG_SYS_MALLOC_F也同样定义成1) , sizeof(struct global_data)=248(GD_SIZE 值)。rounddown是一个向下对齐的函数。
image
因此最终的内存分布如下图:最终top=0X0091FA00
image
回到87行r0存储了函数的返回值,进入89行,把top的值继续存到r9。记住r9寄存器,对uboot很重要,它是记录了全局变量gd的地址。
在文件arch/arm/include/asm/global_data.h中有定义:
image
可以看到gd是一个全局的指针变量,gd_t结构体类型,在include/asm-generic/global_data.h里面有定义:
image
image

2.3 board_init_f_init_reserve初始化gd,gd内存清0#

回到_main函数继续执行board_init_f_init_reserve,在文件common/init/board_init.c中有定义:
image
此函数用于初始化 gd,其实就是清零处理。再设置了 gd->malloc_base gd 基地址+gd 大小=0X0091FA00+248=0X0091FAF8,在做 16 字节对齐,最 终 gd->malloc_base=0X0091FB00,这个也就是early malloc的起始地址。
image

2.4 board_init_f(外设初始化和DRAM划分)#

此函数定义在文件 common/board_f.c,该函数非常重要,主要功能如下:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分 分配好内存位置和大小,比如 gd 应该存放到哪个位置,malloc 内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。
image
所有外设模块初始化详见initcall_run_list函数:
image
init_sequence_f 也是定义在文件common/board_f.c中,init_sequence_f 的内容比较长,简要概括如下:实际list里面函数更多。
image

  1. setup_mon_len 函数设置 gd mon_len 成员变量,此处为__bss_end -_start,也就 是整个代码的长度。0X878A8E74-0x87800000=0XA8E74,这个就是代码长度。
  2. initf_malloc 函数初始化gd中跟 malloc 有关的成员变量,比如 malloc_limit,此函 数会设置 gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400malloc_limit 表示 malloc 内存池大小。
  3. initf_console_record ,如果定义了宏CONFIG_CONSOLE_RECORD和 宏 CONFIG_SYS_MALLOC_F_LEN 的话此函数就会调用函数 console_record_init,但是 IMX6ULL 的 uboot 没有定义宏 CONFIG_CONSOLE_RECORD,所以此函数直接返回 0。
  4. arch_cpu_init
  5. initf_dm 函数,驱动模型的一些初始化。
  6. arch_cpu_init_dm 函数未实现
  7. board_early_init_f 函数,板子相关的早期的一些初始化设置,I.MX6ULL 用来初始 化串口的 IOMUX 配置
  8. timer_init,初始化定时器
  9. board_postclk_init,对于 I.MX6ULL 来说是设置 VDDSOC 电压
  10. env_init 函数是和环境变量有关的,设置gd的成员变量 env_addr`,也就是环境变量的保存地址。
  11. init_baud_rate 函数用于初始化波特率,根据环境变量baudrate来初始化 gd->baudrate
  12. serial_init,初始化串口。
  13. console_init_f初始化控制台。
  14. display_options,通过串口输出一些信息:
    image
  15. display_text_info,打印一些文本信息,如果开启 UBOOT 的 DEBUG 功能的话就 会输出 text_base、bss_start、bss_end,形式如下:
    debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base, bss_start, bss_end);
    image
  16. print_cpuinfo 函数用于打印 CPU 信息。
    image
  17. show_board_info 函数用于打印板子信息。
    image
  18. INIT_FUNC_WATCHDOG_INIT, INIT_FUNC_WATCHDOG_RESET表示初始化,复位看门狗。对于 I.MX6ULL 来说是空函数。
  19. init_func_i2c 函数用于初始化 I2C。
    image
  20. announce_dram_init,dram_init初始化DDR,其实只是设置gd->ram_size,I.MX6ULL 开发板 EMMC 版本核心板来说就是 512MB。
  21. post_init_f,此函数用来完成一些测试,初始化 gd->post_init_f_time
  22. setup_dest_addr,设置目的地址。主要设置gd->ram_size,gd->ram_top,gd->relocaddr 这三个的值。修改 uboot 代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c的setup_dest_addr函数:
    image
    烧录运行打印出来如下:
    image
    可以看出:
    gd->ram_size = 0X20000000 //ram 大小为 0X20000000=512MB
    gd->ram_top = 0XA0000000 //ram 最高地址为 0X80000000+0X20000000=0XA0000000
    gd->relocaddr = 0XA0000000 //重定位后最高地址为 0XA0000000
  23. reserve_round_4k, 对 gd->relocaddr 做 4KB 对齐,因为 gd->relocaddr=0XA0000000,已经是 4K 对齐了,所以调整后不变。
  24. reserve_mmu,留出 MMU 的 TLB 表的位置,分配 MMU 的 TLB 表内存以后会 对gd->relocaddr做 64K 字节对齐。完成以后 gd->arch.tlb_size、gd->arch.tlb_addr 和 gd->relocaddr如下图:
    image
    可以看出:
    gd->arch.tlb_size= 0X4000 //MMU 的 TLB 表大小
    gd->arch.tlb_addr=0X9FFF0000 //MMU 的 TLB 表起始地址,64KB 对齐以后
    gd->relocaddr=0X9FFF0000 //relocaddr 地址
  25. reserve_trace 函数,留出跟踪调试的内存。
  26. reserve_uboot,留出重定位后的 uboot 所占用的内存区域,uboot 所占用大小由gd->mon_len所指定,留出 uboot 的空间以后还要对gd->relocaddr做 4K 字节对齐,并且重新设 置 gd->start_addr_sp
    image
    可以看出, relocaddr一直再减:
    gd->mon_len = 0XA8EF4
    gd->start_addr_sp = 0X9FF47000//0X9FFF0000 -0XA8EF4=9FF4710C,4k再对齐一下就是0X9FF47000
    gd->relocaddr = 0X9FF47000
  27. reserve_malloc,留出 malloc 区域,调整 gd->start_addr_sp 位置。malloc 区域由宏TOTAL_MALLOC_LEN定义:
    #define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
    mx6ull_alientek_emmc.h 文件中定义宏 CONFIG_SYS_MALLOC_LEN 为16MB=0X1000000, 宏 CONFIG_ENV_SIZE=8KB=0X2000,因此 TOTAL_MALLOC_LEN=0X1002000。调整以后gd->start_addr_sp如下图:
    image
    可以看出:
    TOTAL_MALLOC_LEN=0X1002000
    gd->start_addr_sp=0X9EF45000 //0X9FF47000-16MB-8KB=0X9EF45000
  28. reserve_board, 预留bd 内存bd 是结构体 bd_t,bd_t 大小为 80 字节:
    gd->start_addr_sp=0X9EF44FB0//0X9EF45000 -80
    gd->bd=0X9EF44FB0
  29. setup_machine,设置机器 ID,linux 启动的时候会和这个机器 ID 匹配,如果匹 配的话 linux 就会启动正常。以前老版本的 uboot 和 linux 使用的,新版本使用设备树了,因此此函数无效。
  30. reserve_global_data,保留出 gdgd_t 结构体大小为 248B
    gd->start_addr_sp=0X9EF44EB8 //0X9EF44FB0-248=0X9EF44EB8
    gd->new_gd=0X9EF44EB8
  31. reserve_fdt,留出设备树相关的内存。
  32. reserve_arch 是个空函数。
  33. reserve_stacks,留出栈空间,先对 gd->start_addr_sp 减去 16,然后做 16 字节对齐。如果使能 IRQ 的话还要留出 IRQ 相应的内存,具体工作是由arch/arm/lib/stack.c 文件中的 函数arch_reserve_stacks完成。这里uboot启动没有enable IRQ,因此不会留出 IRQ 相应的内存:
    gd->start_addr_sp=0X9EF44E90
  34. setup_dram_config,设置 dram bank信息,后面会传递给 linux 内核,告诉 linux DRAM 的起始地址和大小:
    image
  35. show_dram_config,打印dram信息:
    image
  36. display_new_sp,显示新的sp位置,也就是 gd->start_addr_sp,要开启宏 DEBUG才能看到:
    image
  37. reloc_fdt,数用于重定位 fdt,没有用到。
  38. setup_reloc,设置 gd 的其他一些成员变量,供后面重定位的时候使用,并且将以 前的 gd 拷贝到 gd->new_gd 处。需要使能 DEBUG 才能看到相应的信息输出:
    image
    可以看出,uboot 重定位后的偏移为 0X18747000,重定位后的新地址为 0X9FF4700,新的 gd 首地址为 0X9EF44EB8,最终的sp 0X9EF44E90
    至此,board_init_f 函数就执行完成了,最终的内存分配如下:
    image

2.5 relocate_code(代码重定位)#

既然dram划分好了,那么就开始需要对代码重定位了。

2.5.1 重定位后的LR设置#

image
回到_main第 103 行,获取gd->start_addr_sp的值赋给 spboard_init_f 中会初始化gd的所有成员进行内存预留,其中
gd->start_addr_sp=0X9EF44E90
所以这里sp=0X9EF44E90
第 109 行,sp 做 8 字节对齐。
第 111 行,获取gd->bd的地址赋给 r9,之前r9存放的是老的 gdGD_BD=0来自include\generated\generic-asm-offsets.hbd就是gd结构体的首个成员,因此offset是0:
image
那么新gd地址(board_init_f时确定了gd->bd的地址为0X9EF44FB0)也是它。
第 112 行,r9再减一个GD_SIZE,得到gd->new_gd,也就是0X9EF44EB8:
image
重点来了:
第 114 行,设置lr寄存器为 here,这样后面执行其他函数返回的时候就返回到了第 122 行 的here位置处(比如执行完relocate_code就直接返回到here)。
后面第115行~116行用来让lr指向重定位后的here位置。
第 115,读取gd->reloc_off的值复制给r0寄存器,GD_RELOC_OFF=64:
image
第 116 行,lr 寄存器的值加上r0寄存器的值,重新赋值给lr寄存器。 此时lr 就存放了重定位后的here位置,因为重定位完就去重定位后的DDR去执行了,无需返回sram了。

s3c2440裸机-代码重定位(1.重定位的引入,为什么要代码重定位) - fuzidage - 博客园 (cnblogs.com)
s3c2440裸机-代码重定位(2.编程实现代码重定位) - fuzidage - 博客园 (cnblogs.com)

s3c2440裸机编程-代码重定位和清bss | Hexo (fuzidage.github.io)

关键词:位置无关码、相对跳转指令,相对寻址

第 120 行,读取gd->relocaddr的值赋给 r0 寄存器,此时r0寄存器就保存着 uboot 要拷贝的目的地址,我们打印出来为0X9FF47000r0作为形参给relocate_code函数。

2.5.2 代码段重定位#

重定位的具体实现在arch/arm/lib/relocate.S:
image
image

  1. 80-83行是设置重定位r4偏移量,源拷贝地址,判断是否要拷贝。u-boot.map获取符号的地址:
__image_copy_start 0x87800000 uboot拷贝的首地址
__image_copy_end 0x8785dd54 uboot拷贝的结束地址

如果r4等于0(也就是目标地址r0就是uboot.map中的地址),那么无需拷贝,跳转到relocate_done即可。
2. 85-89是循环拷贝,多寄存器加载存储指令LDMIA,STMIA

arm汇编和cpu运行模式 - fuzidage - 博客园 (cnblogs.com)
Tag: arm汇编 | Hexo (fuzidage.github.io)

88行判断是否拷贝结束,r1会每次加4,当r1等于r2表示拷贝完成了,否则继续执行copy_loop

2.5.3 rel.dyn段重定位#

编译uboot使用-pie选项以后会生成一个.rel.dyn 段,需要对这个段进行重定位和拷贝:
image

1
2
3
4
5
6
7
8
94-97行,从.rel.dyn 段开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器中,r0 存放低 4 字节的数据,也就是 Label 地址;r1 存放高 4 字节的数据,也就是 Label 标志。
第 98 行,取 r1 的低 8 位。
第 99 行,判断 r1 中的值是否等于 23(0X17)。
第 100 行,如果 r1 不等于 23 的话就说明不是描述 Label 的,执行函数 fixnext,否则的话继续执行下面的代码。
103-104行,r0 保存着 Label 值,r4 保存着重定位的偏移量,r0+r4 就得到了重定位后的Label 值。
105-106行,重定位后的变量地址写入到重定位后的 Label 中。
第 108 行,比较 r2 和 r3,查看.rel.dyn 段重定位是否完成。
第 109 行,如果 r2 和 r3 不相等,说明.rel.dyn 重定位还未完成,因此跳到 fixloop 继续重定位.rel.dyn 段。

2.6 relocate_vectors(向量表重定位)#

relocate.S 中同时也定义了中断向量表的重定位:
image

1
2
3
4
29 行,如果定义了 CONFIG_CPU_V7M 的话就执行第 30~36 行的代码,对于 I.MX6ULL 来说不是ARMv7-M,不执行。
由于在.config 里面定义了 CONFIG_HAS_VBAR,因此执行下面的代码。
43 行,r0=gd->relocaddr,重定位后 uboot 的首地址,也就是向量表地址,因为异常向量表就在uboot代码段最开始的地方。
44行,CP15 的 VBAR寄存器写入r0。这样重定位后的向量表就设置好了。

2.7 board_init_r#

2.7.1 外设初始化#

前面board_init_f初始化了芯片级外设(uart,Timer,console等)并且进行了DRAM划分,为代码重定位做准备。接下来board_init_r进行板级的外设初始化。
init_sequence_r 定义在文件 common/board_r.c,负责初始化各个硬件外设,比如flash,网卡,i2c,sdio等等:
image

initr_trace 函数,如果定义了宏CONFIG_TRACE的话就会调用函数 trace_init, 初始化和调试跟踪有关的内容。
initr_reloc 函数用于设置 gd->flags,标记重定位完成。
initr_caches 函数用于初始化 cache,使能 cache。
initr_reloc_global_data 函数,初始化重定位后gd的一些成员变量。
initr_barrier 函数,I.MX6ULL 未用到。
initr_malloc 函数,初始化 malloc。
initr_console_record 函数,初始化控制台相关的内容,I.MX6ULL 未用到,空函数。
bootstage_relocate 函数,启动状态重定位。
initr_bootstage 函数,初始化bootstage什么的。
board_init 函数,板级初始化,包括FEC、USB 和 QSPI 等。 这里执行的是 mx6ull_alientek_emmc.c 文件中的 board_init 函数。
stdio_init_tables 函数,stdio 相关初始化。
initr_serial 函数,初始化串口。
power_init_board 函数,初始化电源芯片,正点原子的 I.MX6ULL 开发板没有用到。
initr_flash 函数,对于 I.MX6ULL 而言,没有定义宏 CONFIG_SYS_NO_FLASH 的话函数initr_flash才有效。但是 mx6_common.h 中定义了宏 CONFIG_SYS_NO_FLASH,所以 此函数无效。
initr_nand 函数,初始化 NAND,如果使用 NAND 版本核心板的话就会初始化 NAND。
initr_mmc 函数,初始化 EMMC,如果使用 EMMC 版本核心板的话就会初始化 EMMC:
image
image
initr_env 函数,初始化环境变量。
initr_secondary_cpu 函数,初始化其他 CPU 核,I.MX6ULL 只有一个核,因此此函数没用。
stdio_add_devices 函数,各种输入输出设备的初始化,如 LCD driver,I.MX6ULL 使用 drv_video_init 函数初始化 LCD:
image
initr_jumptable 函数,初始化跳转表。
console_init_r 函数 , 控制台初始化, 初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备:
image
interrupt_init 函数,初始化中断。
initr_enable_interrupts 函数,使能中断。
initr_ethaddr 函数,初始化网络地址,也就是获取 MAC 地址。读取环境变量 ethaddr的值。
board_late_init 函数,板子后续初始化,此函数定义在文件mx6ull_alientek_emmc.c中,如果环境变量存储在 EMMC 或者 SD 卡中的话此函数会调用 board_late_mmc_env_init 函数 初始化 EMMC/SD。会切换到正在时候用的 emmc 设备,如下图所示:
image
cmd构造成mmc dev 1,切换到正在使用的 EMMC 设备。因为dev0为sd卡,dev1为emmc:
image
initr_net 函数,初始化网络设备,调用关系initr_net->eth_initialize->board_eth_init()
最后**run_main_loop**,主循环,处理命令。

2.7.2 run_main_loop 函数#

2.7.2.1 Uboot version环境变量设置#

image
第 48 行,调用 bootstage_mark_name 函数,打印出启动进度。
第 57 行,如果定义了宏 CONFIG_VERSION_VARIABLE,设置版本号环境变量,cmd/version.c 中定义了version_string字符串,version.h定义了U_BOOT_VERSION_STRING
image
U_BOOT_VERSION 定义在文件include/generated/version_autogenerated.h中:
image
比如我的板子uboot命令行输入version打印版本信息:打印uboot版本,编译日期时间,CONFIG_IDENT_STRING为板子identity信息,我这里是cvitek_athena2,如下图:
image
最后打印出toolchain信息是因为编译生成version_autogenerated.h,输入version命令打印如下do_version函数:
image
Uboot中的命令都是调用cmd_process函数来解析执行。(会面有具体分析cmd_process)

2.7.2.2 bootdelay和bootcmd获取#

第 60 行,cli_init()cli是一个命令行接口,一般裸机开发都喜欢移植cli,来注册各种命令参数。
第 62 行,run_preboot_environment_command()获取系统环境变量preboot的值,我们没有定义该环境变量。
第 68 行,bootdelay_process()设置倒计时全局变量。我的板子bootdelay环境变量是1,.config也配置的1。
该函数是获取bootcmd环境变量,返回bootcmd环境变量,如下图:
image

2.7.2.3 autoboot_command(执行bootcmd)#

main_loop的第72行,自动执行bootcmd。当然我们在命令行输入run bootcmd也会执行bootcmd里面指令,如下图:
image
autoboot_command函数定义在common/autoboot.c
image
我这里stored_bootdelay全局变量等于1,s字符串是bootcmd环境变量不为空。最后abortboot函数的返回值也为0,因此执行run_command_list,执行bootcmd里面的命令,启动kernel rootfs。

2.7.2.3.1 bootdelay倒计时#

分析abortboot()函数:
abortboot调用abortboot_normal,该函数就是判断bootdelay倒计时内是否有按键按下,倒计时结束没有按键按下,那么执行bootcmd,否则uboot命令行等待用户输入,代码逻辑如下:
image
image
总结:有按键输入,abortboot()函数返回1,否则返回0, 这样autoboot_command才会执行bootcmd

2.7.2.4 cli_loop(uboot命令行)#

再来回到main_loop,如果倒计时结束有按键按下,abortboot返回1,那么autoboot_command函数什么都不干直接返回,执行cli_loop()
image
cli_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,就是cli_loop 来处理的,此函数定义在文件 common/cli.c:
image
这里我们有定义CONFIG_SYS_HUSH_PARSER,因此执行parse_file_outer->parse_stream_outercommon/cli_hush.c定义了该函数:
image
该函数调用parse_stream 进行命令解析。然后调用run_list 函数来执行解析出来的命令。

2.7.2.4.1 cmd_process 函数分析#

当命令行有用户输入命令,并且解析到匹配的uboot命令,执行run_list->run_list_real->run_pipe_real->cmd_process
前面提到Uboot中的命令都是调用cmd_process函数来解析执行,如version命令:
image
image
①先调用find_cmd,返回cmd_tbl_t指针:
image
传入的cmd 字符串就是所查找的命令名字,uboot 中的命令表其实就是cmd_tbl_t结构体数组。
ll_entry_start 得到数组的第一个元素,也就是命令表起始地址。通过函数ll_entry_count得到数组长度,也就是命令表的长度。
find_cmd_tbl 在命令表中找到所需的命令,每个命令都有一个 name 成员,如下图根据字符串名字去匹配对应的命令:
image
②调用cmd_call,执行cmd_tbl_t 结构里面对应的处理函数:
image

2.7.2.4.2 uboot命令定义(dhcp为例)#

uboot 使用宏 U_BOOT_CMD 来定义命令,宏 U_BOOT_CMD 定义在 include/command.h:
image
①**ll_entry_declar**
定义在文件 include/linker_lists.h:
image
_type cmd_tbl_t,因此ll_entry_declare就是定义了一个cmd_tbl_t变量,这里用到了 C 语 言中的##连接符。其中的##_list表示用_list 的值来替换,##_name就是用_name 的值来替换。
比如dhcp命令代入进去:

ll_entry_declare(cmd_tbl_t, dhcp, cmd)展开后

1
2
cmd_tbl_t _u_boot_list_2_##cmd##_2_##dhcp __aligned(4)		\
__attribute__((unused,section(".u_boot_list_2_cmd_2_dhcp)))

②**U_BOOT_CMD_MKENT_COMPLETE**
# 表示将 _name 传 递 过 来 的 值 字 符 串 化。
继续以dhcp命令代入:
image

1
2
3
4
U_BOOT_CMD_MKENT_COMPLETE(dhcp, 3, 1, do_dhcp, \
"boot image via network using DHCP/TFTP protocol", \
"[loadAddress] [[hostIPaddr:]bootfilename]", \
NULL);

继续代入:

1
2
3
4
{ "dhcp", 3, 1, do_dhcp, \
"boot image via network using DHCP/TFTP protocol", \
"[loadAddress] [[hostIPaddr:]bootfilename]",\
NULL }

最后宏 U_BOOT_CMDdhcp命令展开如下:

1
2
3
4
5
6
cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \
__attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) = \
{ "dhcp", 3, 1, do_dhcp, \
"boot image via network using DHCP/TFTP protocol", \
"[loadAddress] [[hostIPaddr:]bootfilename]",\
NULL }

可以看到就是定义了一个cmd_tbl_t结构体变量,对它进行初始化。这个变量名为_u_boot_list_2_cmd_2_dhcp,此变量 4 字节对齐。
使 用 __attribute__ 关 键 字 设 置 变 量_u_boot_list_2_cmd_2_dhcp存 储 在.u_boot_list_2_cmd_2_dhcp 段中。u-boot.lds 链接脚本中有一个名为.u_boot_list的段专门存放uboot命令表:
image