下面以u-boot 2016
为例,一行一行分析armv7
架构cpu的uboot启动流程,用到的soc是imx6ull
为例。总体流程如下:分为2部分:
①arch级初始化(架构)
②板级初始化
1 reset 函数#
1.1 初始化异常向量表#
我们知道启动入口是arch/arm/lib/vectors.S
文件中的_start
:
从函数入口_start
可以看到,入口的指令存放的就是中断向量表,0地址偏移存放reset
, 0x4地址存放_undefined_instruction
,0x8地址存放_software_interrupt...
CPU上电最开始进入_start
,进而进入reset
函数,该函数定义在arch/arm/cpu/armv7/start.S
里面。
函数调用关系如下:reset->save_boot_params->save_boot_params_ret
。
1.2 save_boot_params_ret#
1.2.1 关中断,进入SVC模式#
save_boot_params_ret
函数代码如下:
该函数功能如注释所写主要是关闭IRQ和FIQ
, 进入SVC
模式,都是控制cpsr
寄存器。
1 | //第一行读取 cpsr寄存器 |
1.2.2 cp15配置,设置中断向量表偏移VBAR#
这里一般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 清零,目的就是为了接下来的向量表重定位。
最后将_start
给到r0, r0写入到 CP15 的 c12 寄存器中
,也就是VBAR
寄存器,完成中断向量表偏移的设定。
中断向量表的初始定义在_start
入口位置,0x87800000
就是向量表的起始地址。
1.2.3 cp15配置,关MMU,ICACHE
#
bl cpu_init_cp15
一看就是初始化协处理器寄存器CP15
, 比如关闭MMU, ICACHE
等。cpu_init_cp15
函数如下:都是一些CP15
协寄存器操作指令,就不再一一解释。
总结:save_boot_params_ret
做的事情:
①初始化中断向量表
②关FIQ,IRQ
③进入SVC
模式
④设置中断向量表偏移VBAR
⑤初始化CP15
,关MMU,ICACHE
等。
1.3 lowlevel_init对sram内存布局#
初始化CP15
,关MMU,ICACHE
后,会继续执行进入cpu_init_crit
:cpu_init_crit
也是直接跳到lowlevel_init
函数。lowlevel_init
在文件arch/arm/cpu/armv7/lowlevel_init.S
中定义:
前面2行定义sp
指向 CONFIG_SYS_INIT_SP_ADDR
设置SP
指针,CONFIG_SYS_INIT_SP_ADDR
在 include/configs/mx6ullevk.h
IRAM_BASE_ADDR
和IRAM_SIZE
在 文 件arch/arm/include/asm/arch-mx6/imx-regs.h
中有定义,imx6ull这颗芯片内部有一个sram叫做ocram
,基地址就是0x00900000
,size为128k(0x20000)
。
再来看GENERATED_GBL_DATA_SIZE
的值:该定义是编译生成的include/generated/generic-asm-offsets.h
中:
这里提一句,GENERATED_GBL_DATA_SIZE
的含义为 (sizeof(struct global_data) + 15) & ~15
,该结构体后面会讲到。
综上所述,CONFIG_SYS_INIT_SP_ADDR
值如下:
1 | CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 – 256 = 0x1FF00 |
从图可知:
内部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
。
总结:lowlevel_init
函数就是在内部sram下设置SP
指针,来为c语言的运行配置环境。
1.4 s_init函数#
s_init
函数定义在文件 arch/arm/cpu/armv7/mx6/soc.c
。
由于我们的cpu是mx6ull
,那么满足最开始的if
判断,那么s_init
函数直接返回,什么也没做。
总结_reset
过程:
1 | s_init返回了,lowlevel_init也返回了,cpu_init_crit也返回了,回到 save_boot_params_ret。 |
总结下整个系统上电的reset过程:
2 _main
函数#
_main
函数定义在文件arch/arm/lib/crt0.S
中:
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
作为形参传给该函数。
该函数主要是留出早期的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
是一个向下对齐的函数。
因此最终的内存分布如下图:最终top=0X0091FA00
。
回到87行
,r0
存储了函数的返回值,进入89行
,把top的值继续存到r9
。记住r9
寄存器,对uboot很重要,它是记录了全局变量gd
的地址。
在文件arch/arm/include/asm/global_data.h
中有定义:
可以看到gd
是一个全局的指针变量,gd_t
结构体类型,在include/asm-generic/global_data.h
里面有定义:
2.3 board_init_f_init_reserve
(初始化gd,gd内存清0
)#
回到_main
函数继续执行board_init_f_init_reserve
,在文件common/init/board_init.c
中有定义:
此函数用于初始化 gd
,其实就是清零处理。再设置了 gd->malloc_base
为 gd 基地址+gd 大小=0X0091FA00+248=0X0091FAF8
,在做 16 字节对齐,最 终 gd->malloc_base=0X0091FB00
,这个也就是early malloc
的起始地址。
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 的时候就会用到这个内存“分配图”。
所有外设模块初始化详见initcall_run_list
函数:init_sequence_f
也是定义在文件common/board_f.c
中,init_sequence_f
的内容比较长,简要概括如下:实际list里面函数更多。
setup_mon_len
函数设置gd
的mon_len
成员变量,此处为__bss_end -_start
,也就 是整个代码的长度。0X878A8E74-0x87800000=0XA8E74
,这个就是代码长度。initf_malloc
函数初始化gd
中跟 malloc 有关的成员变量,比如malloc_limit
,此函 数会设置gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400
。malloc_limit
表示 malloc 内存池大小。initf_console_record
,如果定义了宏CONFIG_CONSOLE_RECORD
和 宏CONFIG_SYS_MALLOC_F_LEN
的话此函数就会调用函数console_record_init
,但是 IMX6ULL 的 uboot 没有定义宏CONFIG_CONSOLE_RECORD
,所以此函数直接返回 0。arch_cpu_init
initf_dm
函数,驱动模型的一些初始化。arch_cpu_init_dm
函数未实现board_early_init_f
函数,板子相关的早期的一些初始化设置,I.MX6ULL 用来初始 化串口的 IOMUX 配置timer_init
,初始化定时器board_postclk_init
,对于 I.MX6ULL 来说是设置 VDDSOC 电压env_init
函数是和环境变量有关的,设置gd
的成员变量 env_addr`,也就是环境变量的保存地址。init_baud_rate
函数用于初始化波特率,根据环境变量baudrate
来初始化gd->baudrate
。serial_init
,初始化串口。console_init_f
初始化控制台。display_options
,通过串口输出一些信息: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);
print_cpuinfo
函数用于打印 CPU 信息。show_board_info
函数用于打印板子信息。INIT_FUNC_WATCHDOG_INIT, INIT_FUNC_WATCHDOG_RESET
表示初始化,复位看门狗。对于 I.MX6ULL 来说是空函数。init_func_i2c
函数用于初始化 I2C。announce_dram_init,dram_init
初始化DDR,其实只是设置gd->ram_size
,I.MX6ULL 开发板 EMMC 版本核心板来说就是 512MB。post_init_f
,此函数用来完成一些测试,初始化gd->post_init_f_time
。setup_dest_addr
,设置目的地址。主要设置gd->ram_size,gd->ram_top,gd->relocaddr
这三个的值。修改 uboot 代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c的setup_dest_addr
函数:
烧录运行打印出来如下:
可以看出:gd->ram_size = 0X20000000 //ram 大小为 0X20000000=512MB
gd->ram_top = 0XA0000000 //ram 最高地址为 0X80000000+0X20000000=0XA0000000
gd->relocaddr = 0XA0000000 //重定位后最高地址为 0XA0000000
reserve_round_4k
, 对gd->relocaddr
做 4KB 对齐,因为gd->relocaddr=0XA0000000
,已经是 4K 对齐了,所以调整后不变。reserve_mmu
,留出 MMU 的 TLB 表的位置,分配 MMU 的 TLB 表内存以后会 对gd->relocaddr
做 64K 字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr 和 gd->relocaddr
如下图:
可以看出:gd->arch.tlb_size= 0X4000 //MMU 的 TLB 表大小
gd->arch.tlb_addr=0X9FFF0000 //MMU 的 TLB 表起始地址,64KB 对齐以后
gd->relocaddr=0X9FFF0000 //relocaddr 地址
reserve_trace
函数,留出跟踪调试的内存。reserve_uboot
,留出重定位后的 uboot 所占用的内存区域,uboot 所占用大小由gd->mon_len
所指定,留出 uboot 的空间以后还要对gd->relocaddr
做 4K 字节对齐,并且重新设 置gd->start_addr_sp
:
可以看出,relocaddr
一直再减:gd->mon_len = 0XA8EF4
gd->start_addr_sp = 0X9FF47000//0X9FFF0000 -0XA8EF4=9FF4710C,4k再对齐一下就是0X9FF47000
gd->relocaddr = 0X9FF47000
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
如下图:
可以看出:TOTAL_MALLOC_LEN=0X1002000
gd->start_addr_sp=0X9EF45000 //0X9FF47000-16MB-8KB=0X9EF45000
reserve_board
, 预留bd 内存
,bd
是结构体bd_t,bd_t 大小为 80 字节
:gd->start_addr_sp=0X9EF44FB0//0X9EF45000 -80
gd->bd=0X9EF44FB0
setup_machine
,设置机器 ID,linux 启动的时候会和这个机器 ID 匹配,如果匹 配的话 linux 就会启动正常。以前老版本的 uboot 和 linux 使用的,新版本使用设备树了,因此此函数无效。reserve_global_data
,保留出gd
,gd_t 结构体大小为 248B
:gd->start_addr_sp=0X9EF44EB8 //0X9EF44FB0-248=0X9EF44EB8
gd->new_gd=0X9EF44EB8
reserve_fdt
,留出设备树相关的内存。reserve_arch
是个空函数。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
setup_dram_config
,设置dram bank
信息,后面会传递给 linux 内核,告诉 linux DRAM 的起始地址和大小:show_dram_config
,打印dram
信息:display_new_sp
,显示新的sp
位置,也就是gd->start_addr_sp
,要开启宏DEBUG
才能看到:reloc_fdt
,数用于重定位fdt
,没有用到。setup_reloc
,设置gd
的其他一些成员变量,供后面重定位的时候使用,并且将以 前的gd
拷贝到gd->new_gd
处。需要使能DEBUG
才能看到相应的信息输出:
可以看出,uboot 重定位后的偏移为0X18747000
,重定位后的新地址为0X9FF4700
,新的gd
首地址为0X9EF44EB8
,最终的sp
为0X9EF44E90
。
至此,board_init_f
函数就执行完成了,最终的内存分配如下:
2.5 relocate_code(代码重定位)#
既然dram划分好了,那么就开始需要对代码重定位了。
2.5.1 重定位后的LR设置#
回到_main
第 103 行,获取gd->start_addr_sp
的值赋给 sp
, board_init_f
中会初始化gd
的所有成员进行内存预留,其中gd->start_addr_sp=0X9EF44E90
所以这里sp=0X9EF44E90
。
第 109 行,sp
做 8 字节对齐。
第 111 行,获取gd->bd
的地址赋给 r9
,之前r9
存放的是老的 gd
,GD_BD=0
来自include\generated\generic-asm-offsets.h
,bd
就是gd
结构体的首个成员,因此offset是0
:
那么新gd
地址(board_init_f
时确定了gd->bd
的地址为0X9EF44FB0
)也是它。
第 112 行,r9
再减一个GD_SIZE
,得到gd->new_gd
,也就是0X9EF44EB8
:
重点来了:
第 114 行,设置lr
寄存器为 here
,这样后面执行其他函数返回的时候就返回到了第 122 行 的here
位置处(比如执行完relocate_code
就直接返回到here
)。
后面第115行~116行用来让lr指向重定位后的here
位置。
第 115,读取gd->reloc_off
的值复制给r0
寄存器,GD_RELOC_OFF=64
:
第 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 要拷贝的目的地址,我们打印出来为0X9FF47000
。r0
作为形参给relocate_code
函数。
2.5.2 代码段重定位#
重定位的具体实现在arch/arm/lib/relocate.S
:
- 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 段
,需要对这个段进行重定位和拷贝:
1 | 94-97行,从.rel.dyn 段开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器中,r0 存放低 4 字节的数据,也就是 Label 地址;r1 存放高 4 字节的数据,也就是 Label 标志。 |
2.6 relocate_vectors(向量表重定位)#
relocate.S
中同时也定义了中断向量表的重定位:
1 | 第 29 行,如果定义了 CONFIG_CPU_V7M 的话就执行第 30~36 行的代码,对于 I.MX6ULL 来说不是ARMv7-M,不执行。 |
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
等等:
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:initr_env
函数,初始化环境变量。initr_secondary_cpu
函数,初始化其他 CPU 核,I.MX6ULL 只有一个核,因此此函数没用。stdio_add_devices
函数,各种输入输出设备的初始化,如 LCD driver,I.MX6ULL 使用 drv_video_init
函数初始化 LCD:initr_jumptable
函数,初始化跳转表。console_init_r
函数 , 控制台初始化, 初始化完成以后此函数会调用stdio_print_current_devices
函数来打印出当前的控制台设备: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 设备,如下图所示:cmd
构造成mmc dev 1
,切换到正在使用的 EMMC 设备。因为dev0为sd卡,dev1为emmc:initr_net
函数,初始化网络设备,调用关系initr_net->eth_initialize->board_eth_init()
。
最后**run_main_loop
**,主循环,处理命令。
2.7.2 run_main_loop 函数#
2.7.2.1 Uboot version环境变量设置#
第 48 行,调用 bootstage_mark_name
函数,打印出启动进度。
第 57 行,如果定义了宏 CONFIG_VERSION_VARIABLE
,设置版本号环境变量,cmd/version.c
中定义了version_string
字符串,version.h
定义了U_BOOT_VERSION_STRING
:U_BOOT_VERSION
定义在文件include/generated/version_autogenerated.h
中:
比如我的板子uboot命令行输入version
打印版本信息:打印uboot版本,编译日期时间,CONFIG_IDENT_STRING
为板子identity
信息,我这里是cvitek_athena2
,如下图:
最后打印出toolchain
信息是因为编译生成version_autogenerated.h
,输入version
命令打印如下do_version
函数:
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
环境变量,如下图:
2.7.2.3 autoboot_command(执行bootcmd)#
main_loop
的第72行,自动执行bootcmd
。当然我们在命令行输入run bootcmd
也会执行bootcmd
里面指令,如下图:autoboot_command
函数定义在common/autoboot.c
:
我这里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命令行等待用户输入,代码逻辑如下:
总结:有按键输入,abortboot()
函数返回1,否则返回0, 这样autoboot_command
才会执行bootcmd
。
2.7.2.4 cli_loop(uboot命令行)#
再来回到main_loop
,如果倒计时结束有按键按下,abortboot
返回1,那么autoboot_command
函数什么都不干直接返回,执行cli_loop()
:cli_loop
函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,就是cli_loop
来处理的,此函数定义在文件 common/cli.c
:
这里我们有定义CONFIG_SYS_HUSH_PARSER
,因此执行parse_file_outer->parse_stream_outer
,common/cli_hush.c
定义了该函数:
该函数调用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
命令:
①先调用find_cmd
,返回cmd_tbl_t
指针:
传入的cmd
字符串就是所查找的命令名字,uboot 中的命令表其实就是cmd_tbl_t
结构体数组。ll_entry_start
得到数组的第一个元素,也就是命令表起始地址。通过函数ll_entry_count
得到数组长度,也就是命令表的长度。find_cmd_tbl
在命令表中找到所需的命令,每个命令都有一个 name 成员
,如下图根据字符串名字去匹配对应的命令:
②调用cmd_call
,执行cmd_tbl_t
结构里面对应的处理函数:
2.7.2.4.2 uboot命令定义(dhcp为例)#
uboot 使用宏 U_BOOT_CMD
来定义命令,宏 U_BOOT_CMD
定义在 include/command.h
:
①**ll_entry_declar
**
定义在文件 include/linker_lists.h
:_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 | cmd_tbl_t _u_boot_list_2_# |
②**U_BOOT_CMD_MKENT_COMPLETE
**#
表示将 _name
传 递 过 来 的 值 字 符 串 化。
继续以dhcp
命令代入:
1 | U_BOOT_CMD_MKENT_COMPLETE(dhcp, 3, 1, do_dhcp, \ |
继续代入:
1 | { "dhcp", 3, 1, do_dhcp, \ |
最后宏 U_BOOT_CMD
对dhcp
命令展开如下:
1 | cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \ |
可以看到就是定义了一个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命令表: