• 汇编 bic 指令,位清除指令,bic    sp, sp, #7,将sp和#7取反做与运算,结果存入sp中,也就是将sp低3位清0,这个是用于sp做8字节对齐的作用
  • arm复位时执行中断向量表的reset符号,而reset符号会调用save_boot_params_ret:,而该符号内部执行过程如下:
    嵌入式杂记(uboot启动流程,简单易懂!!!)_嵌入式
    嵌入式杂记(uboot启动流程,简单易懂!!!)_嵌入式_02

    嵌入式杂记(uboot启动流程,简单易懂!!!)_uboot_03
  • 因为接下来要重定位
    代码,也就是把代码拷贝到新的地方去(现在的 uboot 存放的起始地址为 0X87800000,下面要
    将 uboot 拷贝到 DDR 最后面的地址空间出,将 0X87800000 开始的内存空出来)
  • 这个就是_main 函数的运行流程,在_main 函数里面调用了:
  • board_init_f、(cpu各种初始化)
  • relocate_code、(代码搬运后,需要重定位的段,这里具体处理一下,具体看我这篇文章程序运行代码动态重定位原理)
  • relocate_vectors、(中断向量表也得重定位一下)
    43 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
    44 mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
  •  board_init_r、
  • 这 4 个函数,接下来依次看一下这 4 个函数都是干啥的。

 

  • board_init_f 函数主要有两个工作:
    ①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
    ②、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就
    是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux
    kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。

嵌入式杂记(uboot启动流程,简单易懂!!!)_嵌入式_04

里面会调用一个initcall_run_list(init_sequence_f)函数,而init_sequence_r是个函数指针数组,里面存了大量需要依次初始化执行的函数。
 

  •  board_init_f 函数,在此函数里面会调用一系列的函数来初始化一些外
    设和 gd 的成员变量。但是 board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的

里面也会调用一个initcall_run_list(init_sequence_r)函数,而init_sequence_r是个函数指针数组,里面存了大量需要依次初始化执行的函数。

示例代码 32.2.8.2 board_r.c 代码段
1 init_fnc_t init_sequence_r[] = {
2 initr_trace,
3 initr_reloc,
4 initr_caches,
5 initr_reloc_global_data,
6 initr_barrier,
7 initr_malloc,
8 initr_console_record,
9 bootstage_relocate,
10 initr_bootstage,
11 board_init, /* Setup chipselects */
12 stdio_init_tables,
13 initr_serial,

。。。。。。

38 initr_net,
39 INIT_FUNC_WATCHDOG_RESET
40 run_main_loop,        //里面是个无限循环,会不断检测是否有输入命令,以及倒计时
41 };

 

run_main_loop->main_loop,里面会循环调用两函数:

  1. abortboot_normal(int bootdelay)        //实现倒计时,按键输入检测等功能,一旦检测到了输入,转入函数2
  2. cli_loop(void)        //用于处理输入的命令,里面主要会调用函数
    1. parse_stream_outer(struct in_str *inp, int flag)里面主要会调用
      1. cmd_process(int flag, int argc, char * const argv[],int *repeatable, ulong *ticks)        //该函数来对命令进行具体的处理了

 

命令的定义如下:以dhcp命令为例,

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

将其展开,对应一个命令结构体,结果如下:

30 struct cmd_tbl_s {
31 char *name; /* Command Name */
32 int maxargs; /* maximum number of arguments */
33 int repeatable; /* autorepeat allowed? */
34 /* Implementation function */
35 int (*cmd)(struct cmd_tbl_s *, int, int, char * const []);
36 char *usage;  /* Usage message (short) */
37 #ifdef CONFIG_SYS_LONGHELP
38 char *help; /* Help message (long) */
39 #endif
40 #ifdef CONFIG_AUTO_COMPLETE
41 /* do auto completion on the arguments */
42 int (*complete)(int argc, char * const argv[], char
last_char, int maxv, char *cmdv[]);
43 #endif
44 };

 

命令对应赋值后,结果如下:

_u_boot_list_2_cmd_2_dhcp.name = "dhcp"
_u_boot_list_2_cmd_2_dhcp.maxargs = 3
_u_boot_list_2_cmd_2_dhcp.repeatable = 1
_u_boot_list_2_cmd_2_dhcp.cmd = do_dhcp
_u_boot_list_2_cmd_2_dhcp.usage = "boot image via network using DHCP/TFTP protocol"
_u_boot_list_2_cmd_2_dhcp.help = "[loadAddress] [[hostIPaddr:]bootfilename]"
_u_boot_list_2_cmd_2_dhcp.complete = NULL

当我们在 uboot 的命令行中输入“dhcp”这个命令的时候,最终执行的是 do_dhcp 这个函
数(因此,自己想做一个命令行解释器,没必要搞这么复杂这么绕(uboot为了做得通用,所以非常绕,条件编译和各种判断非常多),因为最终就是为了执行do_xx函数嘛,自己直接执行就行了)。总结一下,uboot 中使用 U_BOOT_CMD 来定义一个命令,最终的目的就是为了定义一个
cmd_tbl_t 类型的变量,并初始化这个变量的各个成员。uboot 中的每个命令都存储在.u_boot_list
段中,每个命令都有一个名为 do_xxx(xxx 为具体的命令名)的函数,这个 do_xxx 函数就是具体
的命令处理函数。

 

 

DDR 最高地址为 0X80000000+0X20000000(512MB)=0XA0000000

注:一个程序通过链接地址,能知道自己的起始地址(这是自己设计好的),那怎么知道自己的结束地址呢?

答:就是通过链接脚本里的自己定义标识符,比如__image_copy_end,那gcc编译完后会给这个标识符赋值,我们在程序里访问这个标识符就可以了。