分享

uboot-main_loop函数分析

 bbeu4clvu9gy95 2023-11-09 发布于山东

概述

uboot的主要作用是用来启动linux内核。

CPU不能直接从块设备中执行代码,因此需要把块设备中的程序复制到内存中,而复制之前还需要进行很多初始化工作,如初始化时钟、串口、dram等。这些初始化工作,都是由uboot完成的。

这些初始化工作完成后,uboot将块设备中的内核代码复制到某个内存地址,然后再执行“bootm xxx”命令以启动内核代码。

uboot启动后自动运行,打印出很多信息,然后uboot进入倒数bootdelay秒,如下所示。

root@ubuntu:/home/ubuntu/Desktop/u-boot-2017.05-rc2# qemu-system-arm -M vexpress-a9 -m 512M -kernel u-boot -nographic
pulseaudio: set_sink_input_volume() failed
pulseaudio: Reason: Invalid argument
pulseaudio: set_sink_input_mute() failed
pulseaudio: Reason: Invalid argument


U-Boot 2017.05-rc2 (Nov 05 2023 - 04:42:36 -0800)

DRAM: 512 MiB
WARNING: Caches not enabled
Flash: 128 MiB
MMC: MMC: 0
*** Warning - bad CRC, using default environment

In: serial
Out: serial
Err: serial
Net: smc911x-0
Hit any key to stop autoboot: 0
=>

此时,如果用户没有按键操作时,则uboot会读取bootcmd这个环境变量,并使用rum_command函数来执行这个环境变量对应的命令,进入自动启动内核流程。

如果用户打断uboot的自启动过程,则进入uboot的命令行,然后uboot一直工作在命令行下,不断地重复执行“接收命令、解析命令、执行命令”的流程。

main_loop函数

uboot通过调用run_main_loop函数执行循环命令程序

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}

main_loop函数定义如下:

/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;

bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

cli_init();

run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);

autoboot_command(s);

cli_loop();
panic("No CLI available");
}

bootstage_mark_name:打印出启动进度
cli_init():命令初始化有关,初始化 hush shell 相关的变量
run_preboot_environment_command:获取环境变量 perboot 的内容
s = bootdelay_process:此函数会读取环境变量 bootdelay 和 bootcmd 的内容
将 bootdelay 的值赋值给全局变量 stored_bootdelay,返回值为环境变量 bootcmd 的值。
autoboot_command:检查倒计时是否结束
cli_loop:命令行处理函数

autoboot_command

void autoboot_command(const char *s)
{
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
int prev = disable_ctrlc(1); /* disable Control C checking */
#endif

run_command_list(s, -1, 0);

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
disable_ctrlc(prev); /* restore Control C checking */
#endif
}

#ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd");
if (s)
run_command_list(s, -1, 0);
}
#endif /* CONFIG_MENUKEY */
}

大致执行流程:判断2个条件,if成立进入执行run_command_list()启动内核,否则相当于什么都不执行,函数返回,接着执行cli_loop函数
if成立的关键,abortboot()函数,判断键盘是否按下,按下if就不成立,否则成立

abortboot

static int abortboot(int bootdelay)
{
int abort = 0;

if (bootdelay >= 0)
abort = __abortboot(bootdelay);

#ifdef CONFIG_SILENT_CONSOLE
if (abort)
gd->flags &= ~GD_FLG_SILENT;
#endif

return abort;
}

static int __abortboot(int bootdelay)
{
int abort = 0;
unsigned long ts;

#ifdef CONFIG_MENUPROMPT
printf(CONFIG_MENUPROMPT);
#else
printf("Hit any key to stop autoboot: %2d ", bootdelay);
#endif

/*
* Check if key already pressed
*/

if (tstc()) { /* we got a key press */
(void) getc(); /* consume input */
puts("\b\b\b 0");
abort = 1; /* don't auto boot */
}

while ((bootdelay > 0) && (!abort)) {
--bootdelay;
/* delay 1000 ms */
ts = get_timer(0);
do {
if (tstc()) { /* we got a key press */
abort = 1; /* don't auto boot */
bootdelay = 0; /* no more delay */
# ifdef CONFIG_MENUKEY
menukey = getc();
# else
(void) getc(); /* consume input */
# endif
break;
}
udelay(10000);
} while (!abort && get_timer(ts) < 1000);

printf("\b\b\b%2d ", bootdelay);
}

putc('\n');

return abort;
}

最终执行__abortboot函数
__abortboot:判断键盘是否按下,按下返回1,否则0倒计时并输出

从上面函数可以看到假设倒计时结束前终止了,则直接返回,执行cli_loop函数,这就是那个shell环境,我们可以在里面输入命令,设置环境变量等。
如果倒计时完了没有终止,则会执行run_command_list,s是bootcmd的内容。

cli_loop函数

如用户在设定的bootdelay内有按键输入,那么将运行cli_loop执行hush shell命令解释器:

void cli_loop(void)
{
#ifdef CONFIG_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#elif defined(CONFIG_CMDLINE)
cli_simple_loop();
#else
printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
#endif /*CONFIG_HUSH_PARSER*/
}

parse_file_outer进行必要的初始化后,也将调用hush shell的命令解释器,即parse_stream_outer函数:

/* most recursion does not come through here, the exeception is
* from builtin_source() */

static int parse_stream_outer(struct in_str *inp, int flag)
{

struct p_context ctx;
o_string temp=NULL_O_STRING;
int rcode;
#ifdef __U_BOOT__
int code = 1;
#endif
do {
.........
.........
run_list(ctx.list_head);
code = run_list(ctx.list_head);
.........
.........
} while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&
(inp->peek != static_peek || b_peek(inp)));
#ifndef __U_BOOT__
return 0;
#else
return (code != 0) ? 1 : 0;
#endif /* __U_BOOT__ */
}

上面的do-while会循环命令解析器的"命令输入解析--执行"运行模式。
其中的函数run_list执行如下的函数调用流程:
run_list-->run_list_real-->run_pipe_real
最后在函数run_pipe_real中有:
return cmd_process(...);
函数cmd_process最后完成u-boot命令的定位和执行。

run_command_list函数

int run_command_list(const char *cmd, int len, int flag)
{
int need_buff = 1;
char *buff = (char *)cmd; /* cast away const */
int rcode = 0;

if (len == -1) {
len = strlen(cmd);
#ifdef CONFIG_HUSH_PARSER
/* hush will never change our string */
need_buff = 0;
#else
/* the built-in parser will change our string if it sees \n */
need_buff = strchr(cmd, '\n') != NULL;
#endif
}
if (need_buff) {
buff = malloc(len + 1);
if (!buff)
return 1;
memcpy(buff, cmd, len);
buff[len] = '\0';
}
#ifdef CONFIG_HUSH_PARSER
rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON);
#else
/*
* This function will overwrite any \n it sees with a \0, which
* is why it can't work with a const char *. Here we are making
* using of internal knowledge of this function, to avoid always
* doing a malloc() which is actually required only in a case that
* is pretty rare.
*/

#ifdef CONFIG_CMDLINE
rcode = cli_simple_run_command_list(buff, flag);
#else
rcode = board_run_command(buff);
#endif
#endif
if (need_buff)
free(buff);

return rcode;
}

run_command_list会调用过程如下:

此时,流程又走到cmd_process函数。

cmd_process

cmd_process 函数定义在文件 common/command.c 中,函数内容如下:

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 defined(CONFIG_CMD_BOOTD)
/* avoid "bootd" recursion */
else if (cmdtp->cmd == do_bootd) {
if (flag & CMD_FLAG_BOOTD) {
puts("'bootd' recursion detected\n");
rc = CMD_RET_FAILURE;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif

/* 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;
}

cmd_process 函数 调用函数 find_cmd函数 在命令表中找到指定的命令, find_cmd 函数内容如下:

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

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

static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int result;

result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
if (result)
debug("Command failed, result=%d\n", result);
return result;
}

在前面的分析中我们知道, cmd_tbl_t 的 cmd 成员就是具体的命令处理函数,所以,cmd_call 函数中, 调用 cmdtp 的 cmd 成员来处理具体的命令,返回值为命令的执行结果。
cmd_process 中会检测 cmd_tbl 的返回值,如果返回值为 CMD_RET_USAGE ,就会调用
cmd_usage 函数输出命令的用法,其实就是输出 cmd_tbl_t 的 usage 成员变量。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多