IMX6Solo启动流程-从Uboot到kernel 中

2019-04-14 18:56发布

写在前头

*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个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 (" "); 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的启动流程其实不是很复杂,逻辑比较直,就是比较偏底层,需要对硬件有一定的了解,命令的处理方面有一个技巧,这个技巧在内核中也有使用到,后续我们研究到内核的时候再具体说明.

参考

暂无