run_main_loop是board_r中函数运行列表init_fnc_t init_sequence_r[]最后一个函数,它又调用了main_loop,且run_main_loop永不返回。
static int run_main_loop(void) { /* main_loop() can return to retry autoboot, if so just run it again */ for (;;) main_loop(); return 0; }main_loop定义在common/main.c中:
void main_loop(void) { const char *s; bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); modem_init(); #ifdef CONFIG_VERSION_VARIABLE setenv("ver", version_string); /* set version variable */ #endif /* CONFIG_VERSION_VARIABLE */ cli_init(); run_preboot_environment_command(); s = bootdelay_process(); if (cli_process_fdt(&s)) cli_secure_boot_cmd(s); autoboot_command(s); cli_loop(); }bootstage_mark_name函数调用了show_boot_progress,利用它显示启动进程(progress),此处为空函数。
setenv设置环境变量ver为version_string,后者在common/cmd_version.c中定义为:
const char __weak version_string[] = U_BOOT_VERSION_STRING;
U_BOOT_VERSION_STRING在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 ,U_BOOT_DATE,U_BOOT_TIME,U_BOOT_TZ均由u-boot构建系统自动产生,
它们分别代表u-boot版本号,编译日期和时间,以及时间区。
如果定义了CONFIG_SYS_HUSH_PARSER,那么配置u-boot使用hush shell来作为执行器。hush shell是一种轻量型的shell。
cli_init用来初始化hush shell使用的一些变量。hush shell的实现机制比较复杂,以下的hush shell相关实现代码都不做详尽跟踪分析。
有兴趣的可参见源代码和相关的网络文章。
run_preboot_environment_command函数从环境变量中获取"preboot"的定义,该变量包含了一些预启动命令,
一般环境变量中不包含该项配置。
bootdelay_process从环境变量中取出"bootdelay"和"bootcmd"的配置值,将取出的"bootdelay"配置值转换成整数,
赋值给全局变量stored_bootdelay,最后返回"bootcmd"的配置值。bootdelay为u-boot的启动延时计数值,计数期间内
如无用户按键输入干预,那么将执行"bootcmd"配置中的命令。
由于没有定义CONFIG_OF_CONTROL,函数cli_process_fdt返回false,接下来执行autoboot_command,
该函数在common/autoboot.c中实现:
void autoboot_command(const char *s) { if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) { run_command_list(s, -1, 0); } }全局变量stored_bootdelay在上面已做说明。静态函数abortboot中包含了CONFIG_AUTOBOOT_KEYED宏预处理分支,该宏定义用来使能用户名密码登录,这里它没有定义,而后调用了abortboot_normal,在执行的时间stored_bootdelay(秒)内,如无用户按键输入干预,那么abortboot_normal函数将返回0,否则返回1。 当无用户按键干预时,接下来将调用run_command_list执行上述从环境变量中读取的"bootcmd"配置值。注意该函数的参数s。run_command_list中调用了hush shell的命令解释器(parse_stream_outer函数),解释bootcmd中的启动命令。环境变量bootcmd中的启动命令,用来设置linux必要的启动环境,然后加载和启动linux内核。u-boot启动linux内核后,将控制权交给linux内核,至此不再返回。
如用户在设定的bootdelay内无按键输入,那么将运行cli_loop执行hush shell命令解释器:
void cli_loop(void) { parse_file_outer(); /* This point is never reached */ for (;;); }parse_file_outer进行必要的初始化后,也将调用hush shell的命令解释器,即parse_stream_outer函数:
static int parse_stream_outer(structin_str*inp,intflag) { do { ... ... run_list(...); } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) && //#define FLAG_EXIT_FROM_LOOP 1 (inp->peek != static_peek || b_peek(inp))); }上面的do-while会循环命令解析器的"命令输入解析--执行"运行模式。
其中的函数run_list执行如下的函数调用流程:
run_list-->run_list_real-->run_pipe_real
最后在函数run_pipe_real中有:
return cmd_process(...);
函数cmd_process最后完成u-boot命令的定位和执行。
二.u-boot命令执行
命令处理函数均在common/command.c中实现,上述函数cmd_process定义如下:
enum command_ret_t cmd_process(int flag, int argc, char * const argv[], int *repeatable, ulong *ticks) { enum command_ret_t rc = CMD_RET_SUCCESS; cmd_tbl_t *cmdtp; /* Look up command in command table */ cmdtp = find_cmd(argv[0]); if (cmdtp == NULL) { printf("Unknown command '%s' - try 'help'\n", argv[0]); return 1; } /* found - check max args */ if (argc > cmdtp->maxargs) rc = CMD_RET_USAGE; /* If OK so far, then do the command */ if (!rc) { if (ticks) *ticks = get_timer(0); rc = cmd_call(cmdtp, flag, argc, argv); if (ticks) *ticks = get_timer(*ticks); *repeatable &= cmdtp->repeatable; } if (rc == CMD_RET_USAGE) rc = cmd_usage(cmdtp); return rc; }u-boot中使用宏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最后一个参数_comp为NULL的特例,_comp表示变量是否自动完成:
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ ll_entry_declare(cmd_tbl_t, _name, cmd) = \ /*注意这里是cmd而非_cmd*/ U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp);其中包含的宏U_BOOT_CMD_MKENT_COMPLETE定义为:
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp) \ { #_name, _maxargs, _rep, _cmd, _usage, \ _CMD_HELP(_help) _CMD_COMPLETE(_comp) }
上面的_CMD_HELP根据配置可选为使用完整或简短帮助说明。_CMD_COMPLETE则根据配置决定是否使用自动完成函数。U_BOOT_CMD_MKENT_COMPLETE宏其实是组织输入参数,对ll_entry_declare进行数据填充。ll_entry_declare在文件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,这里定义一个cmd_tbl_t结构体,并把它放在符号段.u_boot_list_2_"#_list"_2_"#_name中,其中的_list和_name根据宏参数进行字符串替换。
下面,我们举例说明上述宏的实现机制。比如有如下的定义:
U_BOOT_CMD( env, CONFIG_SYS_MAXARGS, 1, do_env, "environment handling commands", env_help_text );即
U_BOOT_CMD_COMPLETE ( env, CONFIG_SYS_MAXARGS, 1, do_env, "environment handling commands", env_help_text,NULL );带入宏参及其展开为 :
U_BOOT_CMD_COMPLETE(env, CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text , NULL ) \ ll_entry_declare(cmd_tbl_t, env , cmd) = \ U_BOOT_CMD_MKENT_COMPLETE(env , CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text , NULL);其中的ll_entry_declare带入宏参及其展开为 :
ll_entry_declare(cmd_tbl_t , env , cmd ) \ cmd_tbl_t _u_boot_list_2_cmd_2_env __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_cmd_2_env )))其中的U_BOOT_CMD_MKENT_COMPLETE带入宏参及其展开为:
U_BOOT_CMD_MKENT_COMPLETE(env , CONFIG_SYS_MAXARGS , 1, do_env , _usage, _help, _comp) \ { "env", CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text ,NULL}那么上述U_BOOT_CMD_COMPLETE最终展开为:
cmd_tbl_t _u_boot_list_2_cmd_2_env __aligned(4) \ __attribute__((unused, section(".u_boot_list_2_cmd_2_env ))) = { "env", CONFIG_SYS_MAXARGS , 1, do_env , "environment handling commands" , env_help_text ,NULL}其中的cmd_tbl_t定义为:
<pre code_snippet_id="1890747" snippet_file_name="blog_20160921_20_3676404" name="code" class="cpp">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 * const []); 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 * const argv[], char last_char, int maxv, char *cmdv[]); #endif };
typedef struct cmd_tbl_s cmd_tbl_t;
该结构体包含了命令名,命令实现函数,命令使用简短说明usage的输出字符串,帮助回调函数,参数变量自动完成函数等。u-boot使用该结构体来描述一个完整的命令。
U_BOOT_CMD_COMPLETE宏用来定义一个cmd_tbl_t结构体变量,初始化该结构体中的相应成员,并把该结构体变量存放在4字节对齐的.u_boot_list_2_cmd_2_env符号段中。如前所述,宏U_BOOT_CMD将最后一个参数_comp置为NULL,对U_BOOT_CMD_COMPLETE做了进一步的封装。所有使用U_BOOT_CMD和U_BOOT_CMD_COMPLETE定义的命令最后都集中放在以.u_boot_list_2_cmd_2开头的符号段中。即.u_boot_list_2_cmd_2_##name,这里的name是命令名。
我们回到函数上述的命令处理函数cmd_process,被其调用的find_cmd将根据命令名查找相应的cmd_tbl_t变量符号段,其实现如下:
cmd_tbl_t *find_cmd(const char *cmd) { cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd); const int len = ll_entry_count(cmd_tbl_t, cmd); return find_cmd_tbl(cmd, start, len); }ll_entry_start定义如下:
#define ll_entry_start(_type, _list) \ ({ \ static char start[0] __aligned(4) __attribute__((unused, \ section(".u_boot_list_2_"#_list"_1"))); \ (_type *)&start; \ })那么,在上述函数find_cmd中,语句
cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);定义一个包含0个字节的数组start[0],且把它放在.u_boot_cmd_2_list_1段中,该段属性为unsued。注意在u-boot.lds中有:
.u_boot_list : { KEEP(*(SORT(.u_boot_list*)));.u_boot_list中所有符号是按字符表的先后顺序排列的,.u_boot_list_2_list_1会放在所有使用U_BOOT_CMD和U_BOOT_CMD_COMPLETE
定义的符号段的最前面,即.u_boot_cmd_2_list_1为以.u_boot_list_2_cmd_2开头的符号段中的第一个。它定义为0个字节的数组start[0],
编译器并不为它分配存储空间,那么它将指向以.u_boot_list_2_cmd_2开头的符号段中的第一个符号。
同理ll_entry_end用end[0]来标识.u_boot_list_2_cmd_2_xxx段的结尾,接下来的函数ll_entry_count返回的就是start - end的值,
即符号段.u_boot_list_2_cmd_2_xxx总字节长度。然后调用find_cmd_tbl,根据传入的.u_boot_list_2_cmd_2_xxx段的首地址和
函数ll_entry_count 返回的长度,根据命令名查找相应的符号段,即相命令对应的cmd_tbl_t结构体变量,然后返回该结构体指针。
find_cmd_tbl的实现如下:
cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len) { cmd_tbl_t *cmdtp; cmd_tbl_t *cmdtp_temp = table; /* Init value */ const char *p; int len; int n_found = 0; if (!cmd) return NULL; len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd); for (cmdtp = table; cmdtp != table + table_len; cmdtp++) { if (strncmp(cmd, cmdtp->name, len) == 0) { if (len == strlen(cmdtp->name)) return cmdtp; /* full match */ cmdtp_temp = cmdtp; /* abbreviated command ? */ n_found++; } } if (n_found == 1) { /* exactly one match */ return cmdtp_temp; } return NULL; /* not found or ambiguous command */ }查找到命令名对应的cmd_tbl_t结构体变量后,cmd_process接下来将调用函数cmd_call执行cmd_tbl_t中的命令。
cmd_process中相应的代码段如下:
if (!rc) { if (ticks) *ticks = get_timer(0); rc = cmd_call(cmdtp, flag, argc, argv); if (ticks) *ticks = get_timer(*ticks); *repeatable &= cmdtp->repeatable; } if (rc == CMD_RET_USAGE) rc = cmd_usage(cmdtp);变量ticks用来记录命令的执行时间,repeatable为命令是否自动重复执行标志。这两个变量都将返回到上层的调用函数。
函数cmd_call利用传入的参数,直接调用cmdtp->cmd,即:
(cmdtp->cmd)(cmdtp, flag, argc, argv);
最后,如果命令执行的返回值为CMD_RET_USAGE,代表命令执行出错,且置标CMD_RET_USAGE ,那么将调用cmd_usage,
输出简短的命令使用帮助信息。cmd_usage实现如下:
int cmd_usage(const cmd_tbl_t *cmdtp) { printf("%s - %s\n\n", cmdtp->name, cmdtp->usage); #ifdef CONFIG_SYS_LONGHELP printf("Usage:\n%s ", cmdtp->name); if (!cmdtp->help) { puts ("- No additional help available.\n"); return 1; } puts(cmdtp->help); putc('\n'); #endif /* CONFIG_SYS_LONGHELP */ return 1; }三.u-boot中的子命令
部分u-boot的命令包含子命令,如env命令,它由子命令save,set,edit等组成。类似的还有sf命令。这些主命令执行时必须指定子命令。
u-boot中子命令的实现不再使用上面的gcc关键字section来指定段,只是直接定义了一个cmd_tbl_t表,并使用子命令及其运行参数初始化该表。对于上述讨论中使用U_BOOT_CMD定义的命令,它们是散放在文件各处的,很难用一个全局的cmd_tbl_t表将这些命令统一定义初始化。而子命令不同,它只定义在一个或少量文件中,该cmd_tbl_t表为static类型,可以在定义时直接填充。当然,主命令还是由宏U_BOOT_CMD来定义引入。