第 14 行判断键盘是否有按下,也就是是否打断了倒计时,如果键盘按下的话就执行相应的
分支。比如设置 abort 为 1,设置 bootdelay 为 0 等,最后跳出倒计时循环。
第 26 行,返回 abort 的值,如果倒计时自然结束,没有被打断 abort 就为 0,否则的话 abort
的值就为 1。
回到示例代码 32.2.9.6 的 autoboot_command 函数中,如果倒计时自然结束那么就执行函数
run_command_list,此函数会执行参数 s 指定的一系列命令,也就是环境变量 bootcmd 的命令,
bootcmd 里面保存着默认的启动命令,因此 linux 内核启动!这个就是 uboot 中倒计时结束以后
自动启动 linux 内核的原理。如果倒计时结束之前按下了键盘上的按键,那么 run_command_list
函数就不会执行,相当于 autoboot_command 是个空函数。
回到“遥远”的示例代码 32.2.9.2 中的 main_loop 函数中,如果倒计时结束之前按下按键,
那么就会执行第 74 行的 cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令。
回到“遥远”的示例代码 32.2.9.2 中的 main_loop 函数中,如果倒计时结束之前按下按键,
那么就会执行第 74 行的 cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令。
32.2.10 cli_loop 函数详解
cli_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,进行各种操作就
是有 cli_loop 来处理的,此函数定义在文件 common/cli.c 中,函数内容如下:
示例代码 32.2.10.1 cli.c 文件代码段
202 void cli_loop(void)
203 {
204 #ifdef CONFIG_SYS_HUSH_PARSER
205 parse_file_outer();
206 /* This point is never reached */
207 for (;;);
208 #else
209 cli_simple_loop();
210 #endif /*CONFIG_SYS_HUSH_PARSER*/
211 }
在文件 include/configs/mx6_common.h 中有定义宏 CONFIG_SYS_HUSH_PARSER,而正点
原子的 I.MX6ULL 开发板配置头文件 mx6ullevk.h 里面会引用 mx_common.h 这个头文件,因此
宏 CONFIG_SYS_HUSH_PARSER 有定义。
第 205 行调用函数 parse_file_outer。
第 207 行是个死循环,永远不会执行到这里。
函数 parse_file_outer 定义在文件 common/cli_hush.c 中,去掉条件编译内容以后的函数内容
如下:
示例代码 32.2.10.2 parse_file_outer 函数精简
1 int parse_file_outer(void)
2 {
3 int rcode;
4 struct in_str input;
5
6 setup_file_in_str(&input);
7 rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
8 return rcode;
9 }
原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com
823
I.MX6U 嵌入式 Linux 驱动开发指南
第 3 行调用函数 setup_file_in_str 初始化变量 input 的成员变量。
第 4 行调用函数 parse_stream_outer,这个函数就是 hush shell 的命令解释器,负责接收命
令行输入,然后解析并执行相应的命令,函数 parse_stream_outer 定义在文件 common/cli_hush.c
中,精简版的函数内容如下:
示例代码 32.2.10.3 parse_stream_outer 函数精简
1 static int parse_stream_outer(struct in_str *inp, int flag)
2 {
3 struct p_context ctx;
4 o_string temp=NULL_O_STRING;
5 int rcode;
6 int code = 1;
7 do {
8 ......
9 rcode = parse_stream(&temp, &ctx, inp,
10 flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n');
11 ......
12 if (rcode != 1 && ctx.old_flag == 0) {
13 ......
14 run_list(ctx.list_head);
15 ......
16 } else {
17 ......
18 }
19 b_free(&temp);
20 /* loop on syntax errors, return on EOF */
21 } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&
22 (inp->peek != static_peek || b_peek(inp)));
23 return 0;
24 }
第 7~21 行中的 do-while 循环就是处理输入命令的。
第 9 行调用函数 parse_stream 进行命令解析。
第 14 行调用调用 run_list 函数来执行解析出来的命令。
u-boot为了做得通用,能启动所有的操作系统,所以它加了很多很多的通用化判断,配置,初值赋值,选择,等过程,使得代码非常非常复杂和庞大,各种封装,各种宏定义,跳转。我举得反而失去了易懂的初衷了。
第 295 行,函数 kernel_entry,看名字“内核_进入”,说明此函数是进入 Linux 内核的,也
就是最终的大 boos!!此函数有三个参数:zero,arch,params,第一个参数 zero 同样为 0;第
二个参数为机器 ID;第三个参数 ATAGS 或者设备树(DTB)首地址,ATAGS 是传统的方法,用
于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。
第 299 行,获取 kernel_entry 函数,函数 kernel_entry 并不是 uboot 定义的,而是 Linux 内
核定义的,Linux 内核镜像文件的第一行代码就是函数 kernel_entry,而 images->ep 保存着 Linux
内核镜像的起始地址,起始地址保存的正是 Linux 内核第一行代码!
继续回到示例代码 32.3.6.2 的函数 boot_jump_linux,第 315~318 行是设置寄存器 r2 的值?
为什么要设置 r2 的值呢?Linux 内核一开始是汇编代码,因此函数 kernel_entry 就是个汇编函
数。向汇编函数传递参数要使用 r0、r1 和 r2(参数数量不超过 3 个的时候),所以 r2 寄存器就是
函数 kernel_entry 的第三个参数。
第 316 行,如果使用设备树的话,r2 应该是设备树的起始地址,而设备树地址保存在 images
的 ftd_addr 成员变量中。
第 317 行,如果不使用设备树的话,r2 应该是 uboot 传递给 Linux 的参数起始地址,也就
是环境变量 bootargs 的值,
第 328 行,调用 kernel_entry 函数进入 Linux 内核,此行将一去不复返,uboot 的使命也就
完成了,它可以安息了!