Uboot的shell
4. uboot shell 命令的实现
4.1. 添加命令
以命令 boot
为例(cmd/bootm.c
),添加该命令使用了下面的语句:
U_BOOT_CMD(
boot, 1, 1, do_bootd,
"boot default, i.e., run 'bootcmd'",
""
);
这段语句可以这么理解:给 uboot 添加了一条 shell 命令 boot
,它的作用是引导、启动操作系统(boot default, i.e., run 'bootcmd'
),实现命令的函数是 do_bootd
。
之所以这么一条语句就可以完成添加 shell 命令,可以参考 U_BOOT_CMD 的实现:
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
#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);
#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)))
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
# define _CMD_HELP(x) x,
# define _CMD_COMPLETE(x) x,
通过逐层解析宏 U_BOOT_CMD
,最终会得到:
cmd_tbl_t _u_boot_list_2_cmd_2_boot __aligned(4) __attribute__((unused, section(".u_boot_list_2_cmd_2_boot"))) = {
`boot`,
1,
1,
do_bootd,
"boot default, i.e., run 'bootcmd'",
"",
};
在 u-boot.lds 中会链接 .u_boot_list_2_cmd_2_boot :
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
注:其中 SORT 会按照名称的的顺序进行链接。
cmd_tbl_t 定义如下(成员变量 complete 暂不讨论):
typedef struct cmd_tbl_s cmd_tbl_t;
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
};
综上, U_BOOT_CMD(...)
实际上是定义了一个结构体变量,这个结构体定义了 uboot 的 shell 命令要用到的信息,并且这个结构体变量是保存在指定的位置(section(".u_boot_list_2_cmd_2_boot")
)。 uboot 运行时会主动在该 section 寻找命令并执行。
4.2. 执行命令
uboot 的 shell 入口是 common/board_r.c
的 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()
会重复执行,处理输入的命令
common/main.c
的 main_loop :
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
...
cli_init();
...
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
...
}
其中 cli_loop()
是执行命令的具体函数 :
void cli_loop(void)
{
...
parse_file_outer();
/* This point is never reached */
for (;;);
...
}
parse_file_outer()
会调用 parse_stream_outer()
解析输入的命令并调用 run_list() 执行命令 :
static int parse_stream_outer(struct in_str *inp, int flag)
{
...
do {
...
run_list(ctx.list_head);
...
/* loop on syntax errors, return on EOF */
} while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&
(inp->peek != static_peek || b_peek(inp)));
...
}
接着顺着函数调用链 run_list() -> run_list_real() -> run_pipe_real() -> cmd_process()
uboot 进入到 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 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;
}
其中 find_cmd()
用来在保存命令的 u_boot_list*
段内寻找命令对应的结构体变量,然后 cmd_call()
调用结构体变量对应的函数,到此命令执行完成。
4.3. 命令解析
命令解析有三部分:输入命令、找到命令、执行命令。
4.3.1. 输入命令
uboot shell 的命令都是通过串口输入的,s用户输入字符串后会由 uboot 对字符串进行解析,最终获得命令、命令参数。
4.3.2. 找到命令
通过串口输入获取到命令名称和命令参数后,要在 section .u_boot_list_2_cmd_2* 找到命令的结构体变量,根据结构体变量调用命令背后的函数。
寻找命令的函数是 cmd_tbl_t *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);
}
#define ll_entry_start(_type, _list) \
({ \
static char start[0] __aligned(4) __attribute__((unused, \
section(".u_boot_list_2_"#_list"_1"))); \
(_type *)&start; \
})
#define ll_entry_count(_type, _list) \
({ \
_type *start = ll_entry_start(_type, _list); \
_type *end = ll_entry_end(_type, _list); \
unsigned int _ll_result = end - start; \
_ll_result; \
})
#define ll_entry_end(_type, _list) \
({ \
static char end[0] __aligned(4) __attribute__((unused, \
section(".u_boot_list_2_"#_list"_3"))); \
(_type *)&end; \
})
而命令的结构体变量 .u_boot_list_2_*_2*
正好位于 .u_boot_list_2_"#_list"_1"
和 .u_boot_list_2_"#_list"_3"
之间,这样就获取到了 .u_boot_list_2_*_2*
的长度,然后调用函数 find_cmd_tbl()
遍历该 section 、寻找命令对应的结构体变量并返回给 shell。
cmd_tbl_t *find_cmd_tbl(const char *cmd, cmd_tbl_t *table, int table_len)
{
...
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++;
}
}
...
}
4.3.3. 执行命令
cmd_process() 调用 cmd_call() 执行命令,很简单就是直接调用命令结构体变量的成员函数 :
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
...
result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
....
}