目录
从上篇分析可得,此时Uboot进入_main()中执行,以下详细分析一下_main()的执行过程。
1._main()函数分析
arch/arm/lib/crt0.S
/*
* entry point of crt0 sequence
*/
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //设置 sp 指针为 CONFIG_SYS_INIT_SP_ADDR,也就是 sp 指向 0X0091FF00
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 //sp 做 8 字节对齐
#endif
mov r0, sp //读取 sp 到寄存器 r0 里面,此时 r0=0X0091FF00
bl board_init_f_alloc_reserve
mov sp, r0 //将 r0 写入到 sp 里面, r0 保存着函数board_init_f_alloc_reserve 的返回值,所以这一句也就是设置 sp=0X0091FA00
/* set up gd here, outside any C code */
mov r9, r0 //将 r0 寄存器的值写到寄存器 r9 里面,因为 r9 寄存器存放着全局变量 gd 的地址(gd 指向 0X0091FA00)
bl board_init_f_init_reserve //调用函数 board_init_f_alloc_reserve,此函数有一个参数,参数为 r0 中的值,也就是 0X0091FF00,
mov r0, #0 //设置 r0 为 0
bl board_init_f //主要用来初始化 DDR,定时器,完成代码拷贝等等
#if ! defined(CONFIG_SPL_BUILD)
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ //重新设置环境(sp 和 gd) sp=0X9EF44E90,0X9EF44E90 是 DDR 中的地址,说明新的 sp 和 gd 将会存放到 DDR 中,而不是内部的 RAM 了
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 //sp 做 8 字节对齐
#endif
ldr r9, [r9, #GD_BD] //获取 gd->bd 的地址赋给 r9,此时 r9 存放的是老的 gd,这里通过获取 gd->bd 的地址来计算出新的 gd 的位置。 /* r9 = gd->bd */
sub r9, r9, #GD_SIZE //新的 gd 在 bd 下面,所以 r9 减去 gd 的大小就是新的 gd 的位置 /* new GD is below bd */
adr lr, here 设置 lr 寄存器为 here,这样后面执行其他函数返回的时候就返回到 here 位置处。
ldr r0, [r9, #GD_RELOC_OFF] //读取 gd->reloc_off 的值复制给 r0 寄存器, GD_RELOC_OFF=68 /* r0 = gd->reloc_off */
add lr, lr, r0 //lr 寄存器的值加上 r0 寄存器的值,重新赋值给 lr 寄存器。
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] //读取 gd->relocaddr 的值赋给 r0 寄存器,此时 r0 寄存器就保存着 uboot 要拷贝的目的地址,为 0X9FF47000/* r0 = gd->relocaddr */
b relocate_code //调用函数 relocate_code,也就是代码重定位函数,此函数负责将 uboot 拷贝到新的地方去
here:
/*
* now relocate vectors
*/
bl relocate_vectors //继续回到示例代码 32.2.4.1 第 127 行,调用函数 relocate_vectors,对中断向量表做重定位
/* Set up final (full) environment */
bl c_runtime_cpu_setup //调用函数 c_runtime_cpu_setup/* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
# endif
//清除 BSS 段。
ldr r0, =__bss_start /* this is auto-relocated! */
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /* this is auto-relocated! */
mov r1, #0x00000000 /* prepare zero to clear BSS */
subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
#endif
#if ! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
//设置函数 board_init_r 的两个参数
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r *///调用函数 board_init_r
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
其中调用了board_init_f_alloc_reserve()函数:此时sp 指向 0X0091FF00,即r0 = 0X0091FF00,传入的参数top=0x0091FF00:
//留出早期的 malloc 内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
}
#define CONFIG_SYS_MALLOC_F_LEN 0x400
此时的Top指向:
当从该函数返回后,将r0的值赋给了sp:
mov r9, r0,将r0的值赋值给r9,r9是gd的位置:uboot 中定义了一个指向 gd_t 的指针 gd, gd 存放在寄存器 r9 里面的,因此 gd 是个全局变量。 gd_t 是个结构体。
#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
#endif
接下来调用board_init_f_init_reserve()初始化gd:
//用于初始化 gd
void board_init_f_init_reserve(ulong base)
{
struct global_data *gd_ptr;
#ifndef _USE_MEMCPY
int *ptr;
#endif
/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/
gd_ptr = (struct global_data *)base;
/* zero the area */
#ifdef _USE_MEMCPY //清零处理
memset(gd_ptr, '\0', sizeof(*gd));
#else
for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
*ptr++ = 0;
#endif
/* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
arch_setup_gd(gd_ptr);
#endif
/* next alloc will be higher by one GD plus 16-byte alignment */
base += roundup(sizeof(struct global_data), 16); //16字节对齐
/*
* record early malloc arena start.
* Use gd as it is now properly set for all architectures.
*/
//设置了gd->malloc_base 为 gd 基地址+gd 大小=0X0091FA00+248=0X0091FAF8,
#if defined(CONFIG_SYS_MALLOC_F)
/* go down one 'early malloc arena' */
gd->malloc_base = base;
/* next alloc will be higher by one 'early malloc arena' size */
base += CONFIG_SYS_MALLOC_F_LEN;
#endif
}
_mian的执行流程:
2.board_init_f()函数详解
(1)初始化一系列外设,比如串口、定时器,或者打印一些消息等;
(2)初始化 gd 的各个成员变量, uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。
void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA //没有定义CONFIG_SYS_GENERIC_GLOBAL_DATA
/*
* For some archtectures, global data is initialized and used before
* calling this function. The data should be preserved. For others,
* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
* here to host global data until relocation.
*/
gd_t data;
gd = &data;
/*
* Clear global data before it is accessed at debug print
* in initcall_run_list. Otherwise the debug print probably
* get the wrong vaule of gd->have_console.
*/
zero_global_data();
#endif
gd->flags = boot_flags; //初始化 gd->flags=boot_flags=r0=0。
gd->have_console = 0;
if (initcall_run_list(init_sequence_f)) //initcall_run_list 来运行初始化序列 init_sequence_f 里面的一些
//列函数, init_sequence_f 里面包含了一系列的初始化函数
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
/* Light up LED1 */
imx6_light_up_led1();
}
通过调用initcall_run_list()函数运行了一系列函数:
int initcall_run_list(const init_fnc_t init_sequence[])
{
const init_fnc_t *init_fnc_ptr;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
unsigned long reloc_ofs = 0;
int ret;
if (gd->flags & GD_FLG_RELOC)
reloc_ofs = gd->reloc_off;
#ifdef CONFIG_EFI_APP
reloc_ofs = (unsigned long)image_base;
#endif
debug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs);
if (gd->flags & GD_FLG_RELOC)
debug(" (relocated to %p)\n", (char *)*init_fnc_ptr);
else
debug("\n");
ret = (*init_fnc_ptr)();
if (ret) {
printf("initcall sequence %p failed at call %p (err=%d)\n",
init_sequence,
(char *)*init_fnc_ptr - reloc_ofs, ret);
return -1;
}
}
return 0;
}
static init_fnc_t init_sequence_f[] = {
#ifdef CONFIG_SANDBOX
setup_ram_buf,
#endif
setup_mon_len, //设置 gd 的 mon_len 成员变量,此处为__bss_end -_start,也就是整个代码的长度。 0X878A8E74-0x87800000=0XA8E74
#ifdef CONFIG_OF_CONTROL
fdtdec_setup,
#endif
#ifdef CONFIG_TRACE
trace_early_init,
#endif
initf_malloc, //初始化 gd 中跟 malloc 有关的成员变量,比如 malloc_limit,
//此函数会设置 gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400。 malloc_limit 表示 malloc内存池大小。
initf_console_record,
#if defined(CONFIG_MPC85xx) || defined(CONFIG_MPC86xx)
/* TODO: can this go into arch_cpu_init()? */
probecpu,
#endif
#if defined(CONFIG_X86) && defined(CONFIG_HAVE_FSP)
x86_fsp_init,
#endif
arch_cpu_init, /* basic arch cpu dependent setup */
initf_dm, //驱动模型的一些初始化
arch_cpu_init_dm, //未实现
mark_bootstage, //应该是和啥标记有关的/* need timer, go after init dm */
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f, //,板子相关的早期的一些初始化设置, I.MX6ULL 用来初始化串口的 IO 配置
#endif
/* TODO: can any of this go into arch_cpu_init()? */
#if defined(CONFIG_PPC) && !defined(CONFIG_8xx_CPUCLK_DEFAULT)
get_clocks, /* get CPU and bus clocks (etc.) */
#if defined(CONFIG_TQM8xxL) && !defined(CONFIG_TQM866M) \
&& !defined(CONFIG_TQM885D)
adjust_sdram_tbs_8xx,
#endif
/* TODO: can we rename this to timer_init()? */
init_timebase,
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \
defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \
defined(CONFIG_SPARC)
timer_init, //初始化定时器, Cortex-A7 内核有一个定时器,这里初始化的就是 CortexA 内核的那个定时器/* initialize timer */
#endif
#ifdef CONFIG_SYS_ALLOC_DPRAM
#if !defined(CONFIG_CPM2)
dpram_init,
#endif
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
board_postclk_init, // I.MX6ULL 来说是设置 VDDSOC 电压
#endif
#if defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
get_clocks, //用于获取一些时钟值, I.MX6ULL 获取的是 sdhc_clk 时钟,也就是 SD 卡外设的时钟
#endif
env_init, //设置 gd 的成员变量 env_addr,也就是环境变量的保存地址。/* initialize environment */
#if defined(CONFIG_8xx_CPUCLK_DEFAULT)
/* get CPU and bus clocks according to the environment variable */
get_clocks_866,
/* adjust sdram refresh rate according to the new clock */
sdram_adjust_866,
init_timebase,
#endif
init_baud_rate, //用于初始化波特率,根据环境变量 baudrate 来初始化 gd->baudrate。/* initialze baudrate settings */
serial_init, //初始化串口 /* serial communications setup */
console_init_f, //设置 gd->have_console 为 1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。 /* stage 1 init of console */
#ifdef CONFIG_SANDBOX
sandbox_early_getopt_check,
#endif
#ifdef CONFIG_OF_CONTROL
fdtdec_prepare_fdt,
#endif
display_options, //通过串口输出一些信息/* say that we are here */
display_text_info, //打印一些文本信息,如果开启 UBOOT 的 DEBUG 功能的话就会输出 text_base、 bss_start、 bss_end/* show debugging info if required */
#if defined(CONFIG_MPC8260)
prt_8260_rsr,
prt_8260_clks,
#endif /* CONFIG_MPC8260 */
#if defined(CONFIG_MPC83xx)
prt_83xx_rsr,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K)
checkcpu,
#endif
print_cpuinfo, //print_cpuinfo 函数用于打印 CPU 信息 /* display cpu info (and speed) */
#if defined(CONFIG_MPC5xxx)
prt_mpc5xxx_clks,
#endif /* CONFIG_MPC5xxx */
#if defined(CONFIG_DISPLAY_BOARDINFO)
show_board_info,
#endif
INIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)
misc_init_f,
#endif
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
init_func_i2c, //初始化 I2C,初始化完成以后会输出
#endif
#if defined(CONFIG_HARD_SPI)
init_func_spi,
#endif
announce_dram_init, //输出字符串“DRAM:”
/* TODO: unify all these dram functions? */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)
dram_init, //设置 gd->ram_size 的值 /* configure available RAM banks */
#endif
#if defined(CONFIG_MIPS) || defined(CONFIG_PPC) || defined(CONFIG_M68K)
init_func_ram,
#endif
#ifdef CONFIG_POST
post_init_f, //用来完成一些测试,初始化 gd->post_init_f_time
#endif
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_POST
init_post,
#endif
INIT_FUNC_WATCHDOG_RESET
/*
* Now that we have DRAM mapped and working, we can
* relocate the code and continue running from DRAM.
*
* Reserve memory at end of RAM for (top down in that order):
* - area that won't get touched by U-Boot and Linux (optional)
* - kernel log buffer
* - protected RAM
* - LCD framebuffer
* - monitor code
* - board info struct
*/
setup_dest_addr,//设置目的地址,设置gd->ram_size, gd->ram_top, gd->relocaddr
#if defined(CONFIG_BLACKFIN)
/* Blackfin u-boot monitor should be on top of the ram */
reserve_uboot,
#endif
#if defined(CONFIG_SPARC)
reserve_prom,
#endif
#if defined(CONFIG_LOGBUFFER) && !defined(CONFIG_ALT_LB_ADDR)
reserve_logbuffer,
#endif
#ifdef CONFIG_PRAM
reserve_pram,
#endif
reserve_round_4k, //用 于 对 gd->relocaddr 做 4KB 对 齐
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
defined(CONFIG_ARM)
reserve_mmu,//留出 MMU 的 TLB 表的位置,分配 MMU 的 TLB 表内存以后会对 gd->relocaddr 做 64K 字节对齐。
#endif
#ifdef CONFIG_DM_VIDEO
reserve_video,
#else
# ifdef CONFIG_LCD
reserve_lcd,
# endif
/* TODO: Why the dependency on CONFIG_8xx? */
# if defined(CONFIG_VIDEO) && (!defined(CONFIG_PPC) || defined(CONFIG_8xx)) && \
!defined(CONFIG_ARM) && !defined(CONFIG_X86) && \
!defined(CONFIG_BLACKFIN) && !defined(CONFIG_M68K)
reserve_legacy_video,
# endif
#endif /* CONFIG_DM_VIDEO */
reserve_trace, //留出跟踪调试的内存
#if !defined(CONFIG_BLACKFIN)
reserve_uboot, //留出重定位后的 uboot 所占用的内存区域,
//uboot 所占用大小由gd->mon_len 所指定,留出 uboot 的空间以后还要对 gd->relocaddr 做 4K 字节对齐,并且重新设
//置 gd->start_addr_sp
#endif
#ifndef CONFIG_SPL_BUILD
reserve_malloc, //留出 malloc 区域,调整 gd->start_addr_sp 位置
reserve_board, //留出板子 bd 所占的内存区, bd 是结构体 bd_t, bd_t 大小为80B
#endif
setup_machine, //设置机器 ID, linux 启动的时候会和这个机器 ID 匹配,如果匹配的话 linux 就会启动正常,IMX使用设备树
reserve_global_data, //保留出 gd_t 的内存区域, gd_t 结构体大小为 248B
reserve_fdt, //留出设备树相关的内存区域, I.MX6ULL 的 uboot 没有用到
reserve_arch,
reserve_stacks,//留出栈空间,先对 gd->start_addr_sp 减去 16
setup_dram_config, //设置 dram 信息,就是设置 gd->bd->bi_dram[0].start 和
//gd->bd->bi_dram[0].size,后面会传递给 linux 内核,告诉 linux DRAM 的起始地址和大小
show_dram_config, //显示 DRAM 的配置
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_MIPS)
setup_board_part1,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K)
INIT_FUNC_WATCHDOG_RESET
setup_board_part2,
#endif
display_new_sp, //显示新的 sp 位置,也就是 gd->start_addr_sp
#ifdef CONFIG_SYS_EXTBDINFO
setup_board_extra,
#endif
INIT_FUNC_WATCHDOG_RESET
reloc_fdt, //reloc_fdt 函数用于重定位 fdt
setup_reloc, //设置 gd 的其他一些成员变量,供后面重定位的时候使用,并且将以
//前的 gd 拷贝到 gd->new_gd 处
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
copy_uboot_to_ram,
clear_bss,
do_elf_reloc_fixups,
#endif
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)
jump_to_copy,
#endif
NULL,
};
最终形成的内存布局:
3. relocate_code 函数详解
主要作用是拷贝代码。
arch/arm/lib/relocate.S
ENTRY(relocate_code)
ldr r1, =__image_copy_start //r1=__image_copy_start=0x87800000/* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 //r4=r0-r1=0X9FF47000-0X87800000=0X18747000 计算偏移/* r4 <- relocation offset */
beq relocate_done //源地址和目的地址是相同的,不用拷贝 /* skip relocation */
ldr r2, =__image_copy_end //__image_copy_end =0x8785dd54/* r2 <- SRC &__image_copy_end */
copy_loop: //完成代码拷贝工作
ldmia r1!, {r10-r11} //从 r1,也就是__image_copy_start 开始,读取 uboot 代码保存到 r10 和 r11 中/* copy from source address [r1] */
stmia r0!, {r10-r11} //0X9FF47000 /* copy to target address [r0] */
cmp r1, r2 //直到拷贝完 /* until source end address [r2] */
blo copy_loop
//重定位.rel.dyn 段, .rel.dyn 段是存放.text 段中需要重定位地址的
//集合。重定位就是 uboot 将自身拷贝到 DRAM 的另一个地放去继续运行
/*
* fix .rel.dyn relocations
*/
ldr r2, =__rel_dyn_start //.rel.dyn 段的起始地址/* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end //.rel.dyn 段的终止地址/* r3 <- SRC &__rel_dyn_end */
fixloop:
//从.rel.dyn 段起始地址开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器
//中, r0 存放低 4 字节的数据,也就是 Label 地址; r1 存放高 4 字节的数据,也就是 Label 标志
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff //取 r1 的低 8 位。
cmp r1, #23 //r1 中的值是否等于 23(0X17) /* relative fixup? */
bne fixnext //执行函数 fixnext
/* relative fix: increase location by offset */
add r0, r0, r4 //r0+r4 就得到了重定位后的Label 值
ldr r1, [r0] //读取重定位后 Label 所保存的变量地址
add r1, r1, r4 // 得 到 重 定 位 后 的 变 量 地 址
str r1, [r0] //重定位后的变量地址写入到重定位后的 Label
fixnext:
cmp r2, r3 //.rel.dyn 段重定位是否完成
blo fixloop
relocate_done:
#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers drained,
* even with cache disabled - 4.2.7 of xscale core developer's manual
*/
mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif
/* ARMv4- don't know bx lr but the assembler fails to see that */
#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif
ENDPROC(relocate_code)
4.relocate_vectors 函数详解
重定位向量表:
ENTRY(relocate_vectors)
#ifdef CONFIG_CPU_V7M
/*
* On ARMv7-M we only have to write the new vector address
* to VTOR register.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
ldr r1, =V7M_SCB_BASE
str r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
ldr r0, [r9, #GD_RELOCADDR] //r0=gd->relocaddr,也就是重定位后 uboot 的首地址/* r0 = gd->relocaddr */
mcr p15, 0, r0, c12, c0, 0 //r0 的值写入到 CP15 的 VBAR 寄存器/* Set VBAR */
#else
/*
* Copy the relocated exception vectors to the
* correct address
* CP15 c1 V bit gives us the location of the vectors:
* 0x00000000 or 0xFFFF0000.
*/
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
ands r2, r2, #(1 << 13)
ldreq r1, =0x00000000 /* If V=0 */
ldrne r1, =0xFFFF0000 /* If V=1 */
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
ldmia r0!, {r2-r8,r10}
stmia r1!, {r2-r8,r10}
#endif
#endif
bx lr
ENDPROC(relocate_vectors)
5.board_init_r 函数详解
board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的。
common/board_r.c
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOC
int i;
#endif
#ifdef CONFIG_AVR32
mmu_init_r(dest_addr);
#endif
#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
gd = new_gd;
#endif
#ifdef CONFIG_NEEDS_MANUAL_RELOC
for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
init_sequence_r[i] += gd->reloc_off;
#endif
if (initcall_run_list(init_sequence_r))//调用 initcall_run_list 函数来执行初始化序列 init_sequence_r
hang();
/* NOTREACHED - run_main_loop() does not return */
hang();
}
init_fnc_t init_sequence_r[] = {
initr_trace, //初始化和调试跟踪有关的内容
initr_reloc, //用于设置 gd->flags,标记重定位完成
/* TODO: could x86/PPC have this also perhaps? */
#ifdef CONFIG_ARM
initr_caches, //初始化 cache,使能 cache
/* Note: For Freescale LS2 SoCs, new MMU table is created in DDR.
* A temporary mapping of IFC high region is since removed,
* so environmental variables in NOR flash is not availble
* until board_init() is called below to remap IFC to high
* region.
*/
#endif
initr_reloc_global_data, //初始化重定位后 gd 的一些成员变量
#if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500)
initr_unlock_ram_in_cache,
#endif
initr_barrier,
initr_malloc, //初始化 malloc
initr_console_record, //初始化控制台相关的内容
#ifdef CONFIG_SYS_NONCACHED_MEMORY
initr_noncached,
#endif
bootstage_relocate, //启动状态重定位
#ifdef CONFIG_DM
initr_dm,
#endif
initr_bootstage, //初始化 bootstage 什么的
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32)
board_init, /* Setup chipselects */ //板级初始化,包括 74XX 芯片, I2C、 FEC、 USB 和 QSPI 等
#endif
/*
* TODO: printing of the clock inforamtion of the board is now
* implemented as part of bdinfo command. Currently only support for
* davinci SOC's is added. Remove this check once all the board
* implement this.
*/
#ifdef CONFIG_CLOCKS
set_cpu_clk_info, /* Setup clock information */
#endif
stdio_init_tables, //stdio 相关初始化
initr_serial, //初始化串口
initr_announce, //与调试有关,通知已经在 RAM 中运行
INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_NEEDS_MANUAL_RELOC
initr_manual_reloc_cmdtable,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K)
initr_trap,
#endif
#ifdef CONFIG_ADDR_MAP
initr_addr_map,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_R)
board_early_init_r,
#endif
INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_LOGBUFFER
initr_logbuffer,
#endif
#ifdef CONFIG_POST
initr_post_backlog,
#endif
INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_SYS_DELAYED_ICACHE
initr_icache_enable,
#endif
#if defined(CONFIG_PCI) && defined(CONFIG_SYS_EARLY_PCI_INIT)
/*
* Do early PCI configuration _before_ the flash gets initialised,
* because PCU ressources are crucial for flash access on some boards.
*/
initr_pci,
#endif
#ifdef CONFIG_WINBOND_83C553
initr_w83c553f,
#endif
#ifdef CONFIG_ARCH_EARLY_INIT_R
arch_early_init_r,
#endif
power_init_board,
#ifndef CONFIG_SYS_NO_FLASH
initr_flash,
#endif
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_X86) || \
defined(CONFIG_SPARC)
/* initialize higher level parts of CPU like time base and timers */
cpu_init_r,
#endif
#ifdef CONFIG_PPC
initr_spi,
#endif
#ifdef CONFIG_CMD_NAND
initr_nand, //初始化 NAND
#endif
#ifdef CONFIG_CMD_ONENAND
initr_onenand,
#endif
#ifdef CONFIG_GENERIC_MMC
initr_mmc, //初始化 EMMC
#endif
#ifdef CONFIG_HAS_DATAFLASH
initr_dataflash,
#endif
initr_env, //初始化环境变量
#ifdef CONFIG_SYS_BOOTPARAMS_LEN
initr_malloc_bootparams,
#endif
INIT_FUNC_WATCHDOG_RESET
initr_secondary_cpu, //初始化其他 CPU 核
#if defined(CONFIG_ID_EEPROM) || defined(CONFIG_SYS_I2C_MAC_OFFSET)
mac_read_from_eeprom,
#endif
INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI) && !defined(CONFIG_SYS_EARLY_PCI_INIT)
/*
* Do pci configuration
*/
initr_pci,
#endif
stdio_add_devices, //各种输入输出设备的初始化,如 LCD drive
initr_jumptable, //初始化跳转表
#ifdef CONFIG_API
initr_api,
#endif
console_init_r, //控 制 台 初 始 化/* fully init console as a device */
#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
show_board_info,
#endif
#ifdef CONFIG_ARCH_MISC_INIT
arch_misc_init, /* miscellaneous arch-dependent init */
#endif
#ifdef CONFIG_MISC_INIT_R
misc_init_r, /* miscellaneous platform-dependent init */
#endif
INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_KGDB
initr_kgdb,
#endif
interrupt_init, //初始化中断
#if defined(CONFIG_ARM) || defined(CONFIG_AVR32)
initr_enable_interrupts, //使能中断
#endif
#if defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) || defined(CONFIG_M68K)
timer_init, /* initialize timer */
#endif
#if defined(CONFIG_STATUS_LED)
initr_status_led,
#endif
/* PPC has a udelay(20) here dating from 2002. Why? */
#ifdef CONFIG_CMD_NET
initr_ethaddr, //初始化网络地址,也就是获取 MAC 地址
#endif
#ifdef CONFIG_BOARD_LATE_INIT
board_late_init, //板子后续初始化
#endif
#ifdef CONFIG_FSL_FASTBOOT
initr_fastboot_setup,
#endif
#if defined(CONFIG_CMD_AMBAPP)
ambapp_init_reloc,
#if defined(CONFIG_SYS_AMBAPP_PRINT_ON_STARTUP)
initr_ambapp_print,
#endif
#endif
#ifdef CONFIG_CMD_SCSI
INIT_FUNC_WATCHDOG_RESET
initr_scsi,
#endif
#ifdef CONFIG_CMD_DOC
INIT_FUNC_WATCHDOG_RESET
initr_doc,
#endif
#ifdef CONFIG_BITBANGMII
initr_bbmii,
#endif
#ifdef CONFIG_CMD_NET
INIT_FUNC_WATCHDOG_RESET
initr_net, //初 始 化 网 络 设 备
#endif
#ifdef CONFIG_POST
initr_post,
#endif
#if defined(CONFIG_CMD_PCMCIA) && !defined(CONFIG_CMD_IDE)
initr_pcmcia,
#endif
#if defined(CONFIG_CMD_IDE)
initr_ide,
#endif
#ifdef CONFIG_LAST_STAGE_INIT
INIT_FUNC_WATCHDOG_RESET
/*
* Some parts can be only initialized if all others (like
* Interrupts) are up and running (i.e. the PC-style ISA
* keyboard).
*/
last_stage_init,
#endif
#ifdef CONFIG_CMD_BEDBUG
INIT_FUNC_WATCHDOG_RESET
initr_bedbug,
#endif
#if defined(CONFIG_PRAM) || defined(CONFIG_LOGBUFFER)
initr_mem,
#endif
#ifdef CONFIG_PS2KBD
initr_kbd,
#endif
#if defined(CONFIG_SPARC)
prom_init,
#endif
#ifdef CONFIG_FSL_FASTBOOT
initr_check_fastboot,
#endif
/* Light up LED2 */
imx6_light_up_led2,
run_main_loop,//主循环,处理命令。
};
初始完设备后,进入主循环run_main_loop():uboot 启动以后会进入 3 秒倒计时,如果在 3 秒倒计时结束之前按下按下回车键,那么就会进入 uboot 的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux 内核 , 这 个 功 能 就 是 由 run_main_loop 函 数 来 完 成 的 。 run_main_loop 函 数 定 义 在 文 件common/board_r.c 中:
static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");//打印出启动进度
#ifndef CONFIG_SYS_GENERIC_BOARD
puts("Warning: Your board does not use generic board. Please read\n");
puts("doc/README.generic-board and take action. Boards not\n");
puts("upgraded by the late 2014 may break or be removed.\n");
#endif
#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable *///setenv,设置将变量 ver 的值为 version_string,也就是设置版本号环境变量
#endif /* CONFIG_VERSION_VARIABLE */
cli_init();
//获取环境变量 perboot 的内容, perboot
//是一些预启动命令,一般不使用这个环境变量
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
//读取环境变量 bootdelay 和 bootcmd 的内容,
//然后将 bootdelay 的值赋值给全局变量 stored_bootdelay,返回值为环境变量 bootcmd 的值
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);//检查倒计时是否结束?倒计时结束之前有没有被打断?
cli_loop();
}
void autoboot_command(const char *s)
{
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
//stored_bootdelay 等于环境变量 bootdelay 的值; s 是环境变量 bootcmd 的值
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
int prev = disable_ctrlc(1); /* disable Control C checking */
#endif
run_command_list(s, -1, 0);
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
disable_ctrlc(prev); /* restore Control C checking */
#endif
}
#ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd");
if (s)
run_command_list(s, -1, 0);
}
#endif /* CONFIG_MENUKEY */
}
static int abortboot(int bootdelay)
{
#ifdef CONFIG_AUTOBOOT_KEYED //无定义
return abortboot_keyed(bootdelay);
#else
return abortboot_normal(bootdelay); //执行normal
#endif
}
static int abortboot_normal(int bootdelay)
{
int abort = 0; //变量 abort 是函数 abortboot_normal 的返回值,默认值为 0
unsigned long ts;
#ifdef CONFIG_MENUPROMPT
printf(CONFIG_MENUPROMPT);
#else
if (bootdelay >= 0) //通过串口输出“Hit any key to stop autoboot”字样
printf("Hit any key to stop autoboot: %2d ", bootdelay);
#endif
#if defined CONFIG_ZERO_BOOTDELAY_CHECK
/*
* Check if key already pressed
* Don't check if bootdelay < 0
*/
if (bootdelay >= 0) {
if (tstc()) { /* we got a key press *///判断键盘是否有按下,也就是是否打断了倒计时
(void) getc(); /* consume input */
puts("\b\b\b 0");
abort = 1; /* don't auto boot */ //设置 abort 为 1,设置 bootdelay 为 0 等,最后跳出倒计时循环
}
}
#endif
while ((bootdelay > 0) && (!abort)) {
--bootdelay;
/* delay 1000 ms */
ts = get_timer(0);
do {
if (tstc()) { /* we got a key press */
abort = 1; /* don't auto boot */
bootdelay = 0; /* no more delay */
# ifdef CONFIG_MENUKEY
menukey = getc();
# else
(void) getc(); /* consume input */
# endif
break;
}
udelay(10000);
} while (!abort && get_timer(ts) < 1000);
printf("\b\b\b%2d ", bootdelay);
}
putc('\n');
#ifdef CONFIG_SILENT_CONSOLE
if (abort)
gd->flags &= ~GD_FLG_SILENT;
#endif
return abort; //返回 abort 的值,如果倒计时自然结束,没有被打断 abort 就为 0
}
如果倒计时自然结束那么就执行函数run_command_list,此函数会执行参数 s 指定的一系列命令,也就是环境变量 bootcmd 的命令,bootcmd 里面保存着默认的启动命令,因此 linux 内核启动。如果倒计时结束之前按下按键,那么就会执行main_loop的 cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令。
void cli_loop(void)
{
#ifdef CONFIG_SYS_HUSH_PARSER
parse_file_outer(); //调用函数 parse_file_outer
/* This point is never reached */
for (;;); //永远不会执行到这里
#else
cli_simple_loop();
#endif /*CONFIG_SYS_HUSH_PARSER*/
}
#ifndef __U_BOOT__
static int parse_file_outer(FILE *f)
#else
int parse_file_outer(void)
#endif
{
int rcode;
struct in_str input;
#ifndef __U_BOOT__
setup_file_in_str(&input, f); //初始化变量 input 的成员变量
#else
setup_file_in_str(&input);
#endif
rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON); //hush shell 的命令解释器,负责接收命令行输入,然后解析并执行相应的命令,
return rcode;
}
static int parse_stream_outer(struct in_str *inp, int flag)
{
struct p_context ctx;
o_string temp=NULL_O_STRING;
int rcode;
#ifdef __U_BOOT__
int code = 1;
#endif
do {
ctx.type = flag;
initialize_context(&ctx);
update_ifs_map();
if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset((uchar *)";$&|", 0);
inp->promptmode=1;
rcode = parse_stream(&temp, &ctx, inp,
flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n');//parse_stream 进行命令解析
#ifdef __U_BOOT__
if (rcode == 1) flag_repeat = 0;
#endif
if (rcode != 1 && ctx.old_flag != 0) {
syntax();
#ifdef __U_BOOT__
flag_repeat = 0;
#endif
}
if (rcode != 1 && ctx.old_flag == 0) {
done_word(&temp, &ctx);
done_pipe(&ctx,PIPE_SEQ);
#ifndef __U_BOOT__
run_list(ctx.list_head);
#else
code = run_list(ctx.list_head); //执行解析出来的命令
if (code == -2) { /* exit */
b_free(&temp);
code = 0;
/* XXX hackish way to not allow exit from main loop */
if (inp->peek == file_peek) {
printf("exit not allowed from main input shell.\n");
continue;
}
break;
}
if (code == -1)
flag_repeat = 0;
#endif
} else {
if (ctx.old_flag != 0) {
free(ctx.stack);
b_reset(&temp);
}
#ifdef __U_BOOT__
if (inp->__promptme == 0) printf("<INTERRUPT>\n");
inp->__promptme = 1;
#endif
temp.nonnull = 0;
temp.quote = 0;
inp->p = NULL;
free_pipe_list(ctx.list_head,0);
}
b_free(&temp);
/* loop on syntax errors, return on EOF */
} while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&
(inp->peek != static_peek || b_peek(inp)));
#ifndef __U_BOOT__
return 0;
#else
return (code != 0) ? 1 : 0;
#endif /* __U_BOOT__ */
}
函数 run_list 会经过一系列的函数调用,最终通过调用 cmd_process 函数来处理命令。先看Uboot中的命令是如何定义的?先看U_BOOT_CMD:
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
可以看出U_BOOT_CMD是U_BOOT_CMD_COMPLETE的一个特例:
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
引用了ll_eatry_declar和U_BOOT_CMD_MKENT_COMPLETE:
/* Example:
* ll_entry_declare(struct my_sub_cmd, my_sub_cmd, cmd_sub) = {
* .x = 3,
* .y = 4,
* };
*/
#define ll_entry_declare(_type, _name, _list) \
_type _u_boot_list_2_##_list##_2_##_name __aligned(4) \
__attribute__((unused, \
section(".u_boot_list_2_"#_list"_2_"#_name)))
_type 为 cmd_tbl_t,因此 ll_entry_declare 就是定义了一个 cmd_tbl_t 变量,这里用到了 C 语 言中的“##”连接符符。其中的“##_list”表示用_list 的值来替换,“##_name”就是用_name 的 值来替换。
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
#ifdef CONFIG_AUTO_COMPLETE
# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif
Uboot命令例子:以dhcp为例
U_BOOT_CMD(
dhcp, 3, 1, do_dhcp,
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
1.将U_BOOT_CMD展开后:
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(dhcp, 3, 1,do_dhcp,
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]", NULL)
2.将U_BOOT_CMD_COMPLETE展开后:
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, dhcp, do_dhcp) = \
U_BOOT_CMD_MKENT_COMPLETE(dhcp, 3, 1,do_dhcp, \
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]", NULL);
3.将ll_entry_declare和U_BOOT_CMD_MKENT_COMPLETE展开后:
#define ll_entry_declare(_type, _name, _list) \
cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \
__attribute__((unused, \
section( .u_boot_list_2_cmd_2_dhcp)))
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ "dhcp", 3, 1,do_dhcp,
"boot image via network using DHCP/TFTP protocol", \
"[loadAddress] [[hostIPaddr:]bootfilename]", NULL }
4.最终结果:
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 }
使 用 __attribute__ 关 键 字 设 置 变 量 _u_boot_list_2_cmd_2_dhcp 存 储在.u_boot_list_2_cmd_2_dhcp 段中。 u-boot.lds 链接脚本中有一个名为“.u_boot_list”的段,所有.u_boot_list 开头的段都存放到.u_boot.list 中,cmd_tbl_t是一个结构体:
struct cmd_tbl_s {
char *name; /* Command Name */
int maxargs; /* maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
char *usage; /* Usage message (short) */
#ifdef CONFIG_SYS_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
调用command_process()函数处理命令:
enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
int *repeatable, ulong *ticks)
{
enum command_ret_t rc = CMD_RET_SUCCESS;
cmd_tbl_t *cmdtp;
/* Look up command in command table */
cmdtp = find_cmd(argv[0]); //find_cmd 在命令表中找到指定的命令
if (cmdtp == NULL) {
printf("Unknown command '%s' - try 'help'\n", argv[0]);
return 1;
}
/* found - check max args */
if (argc > cmdtp->maxargs)
rc = CMD_RET_USAGE;
#if defined(CONFIG_CMD_BOOTD)
/* avoid "bootd" recursion */
else if (cmdtp->cmd == do_bootd) {
if (flag & CMD_FLAG_BOOTD) {
puts("'bootd' recursion detected\n");
rc = CMD_RET_FAILURE;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif
/* If OK so far, then do the command */
if (!rc) {
if (ticks)
*ticks = get_timer(0);
rc = cmd_call(cmdtp, flag, argc, argv);//执行具体的命令
if (ticks)
*ticks = get_timer(*ticks);
*repeatable &= cmdtp->repeatable;
}
if (rc == CMD_RET_USAGE)
rc = cmd_usage(cmdtp);
return rc;
}
cmd_tbl_t *find_cmd(const char *cmd)
{
cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);//得到命令表数组的第一个元素
const int len = ll_entry_count(cmd_tbl_t, cmd);//命令表的长度
return find_cmd_tbl(cmd, start, len);//将参数 cmd 与命令表中每个成员的 name 字段都对比
}
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int result;
result = (cmdtp->cmd)(cmdtp, flag, argc, argv); //具体的命令处理函数
if (result)
debug("Command failed, result=%d\n", result);
return result;
}