IMX6Solo启动流程-从Uboot到kernel 中

写在前头

*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列Uboot代码基于2009.08版。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!


Uboot的C函数入口

上一篇讲述了Uboot的入口到C函数入口之间的一段汇编代码,执行完这段汇编代码之后Uboot就跳转到C函数的入口start_armboot.
每个开发板的初始化流程都不一样,在这里我就不重点罗列出板子初始化的各个细节,我也没有研究的那么具体,我只记录以下我认为比较重要的点:
1. 基本的初始化:在start_armboot函数的开头有这么一段代码

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }

init_sequence的定义就在start_armboot的上方,程序顺序调用了这些函数接口,初始化了板子、时间、环境变量、波特率、串口等。
执行完这些比较基本的初始化和其他初始化之后,Uboot进入main_loop函数
2. main_loop主要是接收和处理命令,如果在Uboot进入等待命令延时的时候按下任意键,Uboot就会进入命令交互模式,否则Uboot将启动内核。
main_loop先从环境变量bootdelay中取出延时启动时长:

s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

然后再取出默认执行的命令,如果在延时时长内没有任意键输入,则执行该命令

s = getenv ("bootcmd");

然后等待用户的输入:

if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
...
    run_command (s, 0);
...
}

abortboot函数就是在bootdelay时间内判断用户是否有输入,如果没有就直接执行bootcmd的命令。如果用户有输入,则跳出该循环,进入下一个循环:

for (;;) {
    ...
    len = readline (CONFIG_SYS_PROMPT);
    ...
    if (len == -1)
        puts ("\n");
    else
        rc = run_command (lastcommand, flag);
    ...
}

即进入死循环,从串口读命令,然后执行命令.
3. 无论是用户输入的命令还是默认执行的命令,都是通过run_command来执行的.
每一条命令都是一个struct cmd_tbl_s的结构体,定义如下:

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 *[]);
    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 *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};

其中name是命令名称,不能有两个一样的命令名称.
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);是命令的接口函数,调用该接口执行命令对应的功能.
在Uboot下我们是通过U_BOOT_CMD宏来定义一条命令,例如:

U_BOOT_CMD(
    version,    1,      1,  do_version,
    "print monitor version",
    ""
);

展开宏实际上就是

cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = {
    "version",1,1,do_version,"print monitor version",""
}

命令的名称是”version”,对应的接口函数是do_version,也就是说你在Uboot下面输入”version”,Uboot就会去调用do_version函数.
每个命令的段属性都是 “.u_boot_cmd”,这一点很重要,下面我们会提到

run_command在对命令名称进行必要的处理之后,调用find_cmd函数来查找命令.

cmd_tbl_t *find_cmd (const char *cmd)
{
    int len = &__u_boot_cmd_end - &__u_boot_cmd_start;
    return find_cmd_tbl(cmd, &__u_boot_cmd_start, len);
}

__u_boot_cmd_end和__u_boot_cmd_start这两个变量,它们的定义在Uboot的链接脚本u-boot.lds中.

 __u_boot_cmd_start = .;
 .u_boot_cmd : { *(.u_boot_cmd) }
 __u_boot_cmd_end = .;

我们看到在__u_boot_cmd_start和__u_boot_cmd_end之间存放的是段属性为.u_boot_cmd的数据,而我们刚才上面说到每条命令的段属性都被定义成.u_boot_cmd,也就是说每一条命令都会被链接程序链接在__u_boot_cmd_start和__u_boot_cmd_end之间.
理解里这一点,我们就不会难理解find_cmd_tbl函数,就是在__u_boot_cmd_start和__u_boot_cmd_end之间比较每条命令的名称,如果一致就返回命令的数据结构的指针.
在find_cmd有正确找到命令对应的数据结构指针后,run_command就会通过调用该命令的接口来执行命令:


    /* OK - call function to do the command */
    if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
        rc = -1;
    }

执行完命令之后,就又回到main_loop等待新的命令(如果用户进入命令交互界面)或者启动内核(命令就没有返回).
下一篇我们将研究一下默认执行bootcmd命令来启动内核的流程.


总结

Uboot的启动流程其实不是很复杂,逻辑比较直,就是比较偏底层,需要对硬件有一定的了解,命令的处理方面有一个技巧,这个技巧在内核中也有使用到,后续我们研究到内核的时候再具体说明.

参考

暂无

你可能感兴趣的:(飞思卡尔i.MX6系列)