uboot源码分析十 uboot启动流程七 run_main_loop 函数 cli_loop 函数

41 篇文章 6 订阅
16 篇文章 5 订阅

run_main_loop

uboot 启动以后会进入 3 秒倒计时,如果在 3 秒倒计时结束之前按下按下回车键,那么就会进入 uboot 的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux 内核 , 这 个 功 能 就 是 由 run_main_loop 函 数 来 完 成 的 。 run_main_loop 函 数 定 义 在 文 件common/board_r.c 中

753 static int run_main_loop(void)
754 {
755 #ifdef CONFIG_SANDBOX
756 sandbox_main_loop_init();
757 #endif
758 /* main_loop() can return to retry autoboot, if so just run it
again */
759 for (;;)
760 main_loop();
761 return 0;
762 }

死循环里面就一个main_loop 函数, main_loop 函数定义在文件 common/main.c 里面

43 /* We come here after U-Boot is initialised and ready to process
commands */
44 void main_loop(void)
45 {
46 const char *s;
47
48 bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
49
50 #ifndef CONFIG_SYS_GENERIC_BOARD
51 puts("Warning: Your board does not use generic board. Please
read\n");
52 puts("doc/README.generic-board and take action. Boards not\n");
53 puts("upgraded by the late 2014 may break or be removed.\n");
54 #endif
55
56 #ifdef CONFIG_VERSION_VARIABLE
57 setenv("ver", version_string); /* set version variable */
58 #endif /* CONFIG_VERSION_VARIABLE */
59
60 cli_init();
61
62 run_preboot_environment_command();
63
64 #if defined(CONFIG_UPDATE_TFTP)
65 update_tftp(0UL, NULL, NULL);
66 #endif /* CONFIG_UPDATE_TFTP */
67
68 s = bootdelay_process();
69 if (cli_process_fdt(&s))
70 cli_secure_boot_cmd(s);
71
72 autoboot_command(s);
73
74 cli_loop();
75 }

第 48 行,调用 bootstage_mark_name 函数,打印出启动进度。
第 57 行,如果定义了宏 CONFIG_VERSION_VARIABLE 的话就会执行函数 setenv,设置环境变量 ver 的值为 version_string,也就是设置版本号环境变量。 version_string 定义在文件cmd/version.c 中,定义如下:

const char __weak version_string[] = U_BOOT_VERSION_STRING;

U_BOOT_VERSION_STRING 是个宏, 定义在文件 include/version.h,如下:

#define U_BOOT_VERSION_STRING U_BOOT_VERSION " (" U_BOOT_DATE " - " \
U_BOOT_TIME " " U_BOOT_TZ “)” CONFIG_IDENT_STRING

U_BOOT_VERSION 定 义 在 文 件 include/generated/version_autogenerated.h 中 , 文 件version_autogenerated.h 内容如下

1 #define PLAIN_VERSION "2016.03"
2 #define U_BOOT_VERSION "U-Boot " PLAIN_VERSION
3 #define CC_VERSION_STRING "arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2017.01) 4.9.4"
4 #define LD_VERSION_STRING "GNU ld (Linaro_Binutils-2017.01)2.24.0.20141017 Linaro 2014_11-3-git"

可以看出, U_BOOT_VERSION 为“U-boot 2016.03”,U_BOOT_DATE 、 U_BOOT_TIME 和 U_BOOT_TZ 这 定 义 在 文 件include/generated/timestamp_autogenerated.h 中,如下所示

1 #define U_BOOT_DATE "Apr 25 2019"
2 #define U_BOOT_TIME "21:10:53"
3 #define U_BOOT_TZ "+0800"
4 #define U_BOOT_DMI_DATE 10/25/2019

第 60 行, cli_init 函数,跟命令初始化有关,初始化 hush shell 相关的变量。
第 62 行, run_preboot_environment_command 函数,获取环境变量 perboot 的内容, perboot是一些预启动命令,一般不使用这个环境变量。
第 68 行, bootdelay_process 函数,此函数会读取环境变量 bootdelay 和 bootcmd 的内容,然后将 bootdelay 的值赋值给全局变量 stored_bootdelay,返回值为环境变量 bootcmd 的值。
第 69 行,如果定义了 CONFIG_OF_CONTROL 的话函数 cli_process_fdt 就会实现,如果没有定义 CONFIG_OF_CONTROL 的话函数 cli_process_fdt 直接返回一个 false。在本 uboot 中没有定义 CONFIG_OF_CONTROL,因此 cli_process_fdt 函数返回值为 false。
第 72 行, autoboot_command 函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?此函数定义在文件 common/autoboot.c 中

1 void autoboot_command(const char *s)
2 {
3 if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
4 run_command_list(s, -1, 0);
5 }
6 }

当一下三条全部成立的话,就会执行函数 run_command_list。
①、 stored_bootdelay 不等于-1。
②、 s 不为空。
③、函数 abortboot 返回值为 0。键盘按下导致为一。
stored_bootdelay 等于环境变量 bootdelay 的值; s 是环境变量 bootcmd 的值,一般不为空,因 此 前 两 个 成 立 , 就剩 下 了 函 数abortboot 的 返 回 值 , abortboot 函数 也 定 义 在 文 件common/autoboot.c 中

283 static int abortboot(int bootdelay)
284 {
285 #ifdef CONFIG_AUTOBOOT_KEYED
286 return abortboot_keyed(bootdelay);
287 #else
288 return abortboot_normal(bootdelay);
289 #endif
290 }

因为宏 CONFIG_AUTOBOOT_KEYE 未定义,因此执行函数 abortboot_normal,接着来看函数 abortboot_normal,此函数也定义在文件 common/autoboot.c 中,如下:

225 static int abortboot_normal(int bootdelay)
226 {
227 int abort = 0;
228 unsigned long ts;
229
230
231 printf(CONFIG_MENUPROMPT);
232 #else
233 if (bootdelay >= 0)
234 printf("Hit any key to stop autoboot: %2d ", bootdelay);
235 #endif
236
237 #if defined CONFIG_ZERO_BOOTDELAY_CHECK
238 /*
239 * Check if key already pressed
240 * Don't check if bootdelay < 0
241 */
242 if (bootdelay >= 0) {
243 if (tstc()) { /* we got a key press */
244 (void) getc(); /* consume input */
245 puts("\b\b\b 0");
246 abort = 1; /* don't auto boot */
247 }
248 }
249 #endif
250
251 while ((bootdelay > 0) && (!abort)) {
252 --bootdelay;
253 /* delay 1000 ms */
254 ts = get_timer(0);
255 do {
256 if (tstc()) { /* we got a key press */
257 abort = 1; /* don't auto boot */
258 bootdelay = 0; /* no more delay */
259 # ifdef CONFIG_MENUKEY
260 menukey = getc();
261 # else
262 (void) getc(); /* consume input */
263 # endif
264 break;
265 }
266 udelay(10000);
267 } while (!abort && get_timer(ts) < 1000);
268
269 printf("\b\b\b%2d ", bootdelay);
270 }
271
272 putc('\n');
273
274 #ifdef CONFIG_SILENT_CONSOLE
275 if (abort)
276 gd->flags &= ~GD_FLG_SILENT;
277 #endif
278
279 return abort;
280 }

第 3 行的变量 abort 是函数 abortboot_normal 的返回值,默认值为 0。
第 7 行通过串口输出“Hit any key to stop autoboot”字样
第 9~21 行就是倒计时的具体实现。
第 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 是个空函数。
回到 main_loop 函数中,如果倒计时结束之前按下按键,
那么就会执行第 74 行的 cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令。

cli_loop 函数

cli_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,进行各种操作就是有 cli_loop 来处理的,此函数定义在文件 common/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。因此宏 CONFIG_SYS_HUSH_PARSER 有定义。
第 205 行调用函数 parse_file_outer。
第 207 行是个死循环,永远不会执行到这里。
函数 parse_file_outer 定义在文件 common/cli_hush.c 中

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 }

第 3 行调用函数 setup_file_in_str 初始化变量 input 的成员变量。
第 4 行调用函数 parse_stream_outer,这个函数就是 hush shell 的命令解释器,负责接收命令行输入,然后解析并执行相应的命令,函数 parse_stream_outer 定义在文件 common/cli_hush.c中,

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 函数来执行解析出来的命令。函数 run_list 会经过一系列的函数调用,最终通过调用 cmd_process 函数来处理命令

1 static int run_list(struct pipe *pi)
2 {
3 int rcode=0;
4 
5 rcode = run_list_real(pi);
6 ......
7 return rcode;
8 }
9
10 static int run_list_real(struct pipe *pi)
11 {
12 char *save_name = NULL;
13 ......
14 int if_code=0, next_if_code=0;
15 ......
16 rcode = run_pipe_real(pi);
17 ......
18 return rcode;
19 }
20
21 static int run_pipe_real(struct pipe *pi)
22 {
23 int i;
24
25 int nextin;
26 int flag = do_repeat ? CMD_FLAG_REPEAT : 0;
27 struct child_prog *child;
28 char *p;
29 ......
30 if (pi->num_progs == 1) child = & (pi->progs[0]);
31 ......
32 return rcode;
33 } else if (pi->num_progs == 1 && pi->progs[0].argv != NULL) {
34 ......
35 /* Process the command */
36 return cmd_process(flag, child->argc, child->argv,
37 &flag_repeat, NULL);
38 }
39
40 return -1;
41 }

第 5 行, run_list 调用 run_list_real 函数。
第 16 行, run_list_real 函数调用 run_pipe_real 函数。
第 36 行, run_pipe_real 函数调用 cmd_process 函数。最终通过函数 cmd_process 来处理命令,接下来就是分析 cmd_process 函数

cmd_process 函数

在学习cmd_process 之前先看一下uboot中命令是如何定义的。uboot使用宏U_BOOT_CMD来定义命令,宏 U_BOOT_CMD 定义在文件 include/command.h 中

#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 的 特 例 , 将U_BOOT_CMD_COMPLETE 的 最 后 一 个 参 数 设 置 成 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);

宏 U_BOOT_CMD_COMPLETE 又 用 到 了 ll_entry_declare 和U_BOOT_CMD_MKENT_COMPLETE。 ll_entry_declar 定义在文件 include/linker_lists.h 中,定义如下

#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) }

上 述 代 码 中 的 “ # ” 表 示 将 _name 传 递 过 来 的 值 字 符 串 化 ,U_BOOT_CMD_MKENT_COMPLETE 又用到了宏_CMD_HELP 和_CMD_COMPLETE,这两个宏的定义如下

1 #ifdef CONFIG_AUTO_COMPLETE
2 # define _CMD_COMPLETE(x) x,
3 #else
4 # define _CMD_COMPLETE(x)
5 #endif
6 #ifdef CONFIG_SYS_LONGHELP
7 # define _CMD_HELP(x) x,
8 #else
9 # define _CMD_HELP(x)
10 #endif

可以看出,如果定义了宏 CONFIG_AUTO_COMPLETE 和 CONFIG_SYS_LONGHELP 的话 , _CMD_COMPLETE 和 _CMD_HELP 就 是 取 自 身 的 值 , 然 后 在 加 上 一 个 ‘ , ’。
CONFIG_AUTO_COMPLETE 和 CONFIG_SYS_LONGHELP 这 两 个 宏 有 定 义 在 文 件mx6_common.h 中。
以一个具体的命令为例,来看一下 U_BOOT_CMD 经过展开以后究竟是个什么模样的。
以命令 dhcp 为例

U_BOOT_CMD(
dhcp, 3, 1, do_dhcp,
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
1、将 U_BOOT_CMD 展开后为:
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 展开后为:
ll_entry_declare(cmd_tbl_t, dhcp, cmd) = \
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 展开后为:
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}

第 1 行定义了一个 cmd_tbl_t 类型的变量,变量名为_u_boot_list_2_cmd_2_dhcp,此变量 4字节对齐。
第 2 行 , 使 用 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 中
在这里插入图片描述
因此,第 2 行就是设置变量_u_boot_list_2_cmd_2_dhcp 的存储位置。
第 3~6 行, cmd_tbl_t 是个结构体,因此第 3-6 行是初始化 cmd_tbl_t 这个结构体的各个成员变量。 cmd_tbl_t 结构体定义在文件 include/command.h 中

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 };
45
46 typedef struct cmd_tbl_s cmd_tbl_t;

在 uboot 的命令行中输入“dhcp”这个命令的时候,最终执行的是 do_dhcp 这个函数。总结一下, uboot 中使用 U_BOOT_CMD 来定义一个命令,最终的目的就是为了定义一个cmd_tbl_t 类型的变量,并初始化这个变量的各个成员。 uboot 中的每个命令都存储在.u_boot_list段中,每个命令都有一个名为 do_xxx(xxx 为具体的命令名)的函数,这个 do_xxx 函数就是具体的命令处理函数。

_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

cmd_process 函数的处理过程

500 enum command_ret_t cmd_process(int flag, int argc,
501 char * const argv[],int *repeatable, ulong *ticks)
502 {
503 enum command_ret_t rc = CMD_RET_SUCCESS;
504 cmd_tbl_t *cmdtp;
505
506 /* Look up command in command table */
507 cmdtp = find_cmd(argv[0]);
508 if (cmdtp == NULL) {
509 printf("Unknown command '%s' - try 'help'\n", argv[0]);
510 return 1;
511 }
512
513 /* found - check max args */
514 if (argc > cmdtp->maxargs)
515 rc = CMD_RET_USAGE;
516
517 #if defined(CONFIG_CMD_BOOTD)
518 /* avoid "bootd" recursion */
519 else if (cmdtp->cmd == do_bootd) {
520 if (flag & CMD_FLAG_BOOTD) {
521 puts("'bootd' recursion detected\n");
522 rc = CMD_RET_FAILURE;
523 } else {
524 flag |= CMD_FLAG_BOOTD;
525 }
526 }
527 #endif
528
529 /* If OK so far, then do the command */
530 if (!rc) {
531 if (ticks)
532 *ticks = get_timer(0);
533 rc = cmd_call(cmdtp, flag, argc, argv);
534 if (ticks)
535 *ticks = get_timer(*ticks);
536 *repeatable &= cmdtp->repeatable;
537 }
538 if (rc == CMD_RET_USAGE)
539 rc = cmd_usage(cmdtp);
540 return rc;
541 }

第 507 行,调用函数 find_cmd 在命令表中找到指定的命令

118 cmd_tbl_t *find_cmd(const char *cmd)
119 {
120 cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
121 const int len = ll_entry_count(cmd_tbl_t, cmd);
122 return find_cmd_tbl(cmd, start, len);
123 }

参数 cmd 就是所查找的命令名字, uboot 中的命令表其实就是 cmd_tbl_t 结构体数组,通过函数 ll_entry_start 得到数组的第一个元素,也就是命令表起始地址。通过函数 ll_entry_count得到数组长度,也就是命令表的长度。最终通过函数 find_cmd_tbl 在命令表中找到所需的命
令,每个命令都有一个 name 成员,所以将参数 cmd 与命令表中每个成员的 name 字段都对比一下,如果相等的话就说明找到了这个命令,找到以后就返回这个命令。
第 533 行调用函数 cmd_call 来执行具体的命令

490 static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char *
const argv[])
491 {
492 int result;
493
494 result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
495 if (result)
496 debug("Command failed, result=%d\n", result);
497 return result;
498 }

cmd_tbl_t 的 cmd 成员就是具体的命令处理函数,所以第 494 行调用 cmdtp 的 cmd 成员来处理具体的命令,返回值为命令的执行结果。
cmd_process 中会检测 cmd_tbl 的返回值,如果返回值为 CMD_RET_USAGE 的话就会调用cmd_usage 函数输出命令的用法,其实就是输出 cmd_tbl_t 的 usage 成员变量。

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值