导读:本文以S5PV210为例,从uboot源代码角度分析uboot命令体系架构,并举例说明如何在uboot代码中添加自己的命令。为了展示方便,文章中的代码有进行删减,主要删除了无效的宏定义,可能有误删。
目录
1、uboot命令存储
1.1 uboot命令结构体
每个uboot命令都对应一个结构体,添加一个命令就要添加一个结构体,并初始化结构体。该结构体主要包含命令的名字、命令最大可接受参数个数、命令执行函数和帮助信息等,具体定义如下:
struct cmd_tbl_s {
char *name; /* 命令名字 */
int maxargs; /* 命令最大接收参数个数 */
int repeatable; /* 是否允许按回车重复执行 */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); //命令执行函数
char *usage; /* 短帮助信息字符串 */
char *help; /* 长帮助信息字符串 */
};
typedef struct cmd_tbl_s cmd_tbl_t;
1.2 uboot命令段声明
uboot命令都申明成cmd_tbl_t结构体,这些结构体都会放在u_boot_cmd段中,链接脚本中关于u_boot_cmd声明如下:
注:__u_boot_cmd_start 和 __u_boot_cmd_end为命令结构体起始地址和结束地址,被find_cmd函数调用,方便函数查找uboot支持的命令。
2、uboot如何解析命令
2.1 main_loop主循环
在main_loop函数最后有一个死循环,该循环就是命令行的读取和解析,工作流程如下:
- 读取命令:调用readline函数,该函数循环读取用户输入的字符,将输入的字符串缓存到全局变量console_buff中,当用户输入了回车就退出,并返回读到的长度。
- 读取结果判断:对返回值判断,大于0表示读取正常,将console_buffer赋值给lastcommand;等于0表示用户按下ctrl+C终止输入;
- 执行命令:调用run_command执行用户输入的命令
#define CFG_PROMPT "x210 # " /* Monitor Command Prompt */
static char lastcommand[256] = { 0, };
for (;;) {
len = readline(CFG_PROMPT); //读取用户输入到全局变量console_buffer,遇到\n或\r就返回
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy(lastcommand, console_buffer); //将用户输入的命令放到lastcommand
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
if (len == -1) //按下了ctrl + C
puts("<INTERRUPT>\n");
else
rc = run_command(lastcommand, flag); //执行命令
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
2.2 run_command执行命令
run_command首先校验cmd,然后处理\ ; ( {等特殊字符,再将cmd中的命令提取出来,再调用find_cmd查找命令,最后对查找到的命令尽心校验,最后执行命令。
int run_command (const char *cmd, int flag)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CFG_CBSIZE];
char *str = cmdbuf;
char *argv[CFG_MAXARGS + 1]; /* NULL terminated */
int argc, inquotes;
int repeatable = 1;
int rc = 0;
clear_ctrlc(); /* forget any previous Control C */
if (!cmd || !*cmd) { //命令行非空判断
return -1; /* empty command */
}
if (strlen(cmd) >= CFG_CBSIZE) { //命令行太长无法处理
puts ("## Command too long!\n");
return -1;
}
strcpy(cmdbuf, cmd);
/* Process separators and check for invalid, repeatable commands */
while (*str) { //处理命令
/* Find separator, or string end
* Allow simple escape of ';' by writing "\;" */
for (inquotes = 0, sep = str; *sep; sep++) { //处理 \ ;等字符
if ((*sep=='\'') &&
(*(sep-1) != '\\'))
inquotes=!inquotes;
if (!inquotes &&
(*sep == ';') && /* separator */
( sep != str) && /* past string start */
(*(sep-1) != '\\')) /* and NOT escaped */
break;
}
/* Limit the token to data between separators*/
token = str;
if (*sep) {
str = sep + 1; /* start of command for next pass */
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
/* find macros in this token and replace them */
process_macros(token, finaltoken); //已经输入了\ ( {等字符,换行继续等待输入
/* Extract arguments */
if ((argc = parse_line(finaltoken, argv)) == 0) { //将命令提取出来,放到argv里
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) { //查找命令
printf("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
}
/* found - check max args */
if (argc > cmdtp->maxargs) {
printf("Usage:\n%s\n", cmdtp->usage);
rc = -1;
continue;
}
/* avoid "bootd" recursion */
if (cmdtp->cmd == do_bootd) {
if (flag & CMD_FLAG_BOOTD) {
puts("'bootd' recursion detected\n");
rc = -1;
continue;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) { //执行命令
rc = -1;
}
repeatable &= cmdtp->repeatable;
/* Did the user stop this? */
if (had_ctrlc())
return -1; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
2.3 find_comd查找命令
在run_command函数中会调用find_comd函数查找命令,该函数会去链接脚本申明的uboot命令空间查找,如果有匹配上的就返回该命令的结构体指针,如果没有就返回空,同时该函数支持用户输入简写(简写必须唯一匹配uboot命令,否则返回空)。
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; //链接脚本中指定的uboot命令结构体起始地址
const char *p;
int len;
int n_found = 0;
/* Some commands allow length modifiers (like "cp.b");
* compare command name only until first dot. */
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd); //处理带.的命令
for (cmdtp = &__u_boot_cmd_start; //uboot命令结构体起始地址,在链接脚本中定义
cmdtp != &__u_boot_cmd_end; //uboot命令结构体结束地址
cmdtp++) {
if (strncmp (cmd, cmdtp->name, len) == 0) {
if (len == strlen(cmdtp->name)) //字符串前len字节内容相等且长度相当
return cmdtp; //找到了命令,返回命令结构体指针
cmdtp_temp = cmdtp; //字符串前len个字节相等,但长度不相等,输入可能是简写
n_found++;
}
}
if (n_found == 1) { //输入命令为简写,只有一个匹配才返回
return cmdtp_temp;
}
return NULL; //没找到,返回空
}
3 、uboot如何定义一个新命令
- U_BOOT_CMD宏定义
U_BOOT_CMD就是定义cmd_tbl_t结构体,调用一个U_BOOT_CMD就声明一个uboot命令结构体,并将该结构体放入u_boot_cmd段中。
//声明段属性,将uboot命令数组都放到.u_boot_cmd段中(u_boot_cmd在链接脚本中申明)
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
- U_BOOT_CMD宏展开举例
U_BOOT_CMD通过##name避免重复定义,例如在4.1中U_BOOT_CMD宏展开结果为:
cmd_tbl_t __u_boot_cmd_mycmd\
__attribute__ ((unused,section (".u_boot_cmd")))= \
{mycmd, 3, 1, do_mycmd, "Short help of mycmd\n", "Long help of mycmd\n"}
4、如何添加自己定义的uboot命令
4.1 添加C文件
在common/文件夹下新建mycmd.c,然后添加以下代码内容(声明命令执行函数,定义命令并初始化)
#include <common.h>
#include <command.h>
int do_mycmd(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) //命令执行函数
{
printf("This is my command\n");
for(int i = 0; i < argc; i++)
{
printf("argv[%d] = %s\n", i, argv++); //打印传参
}
return 0;
}
U_BOOT_CMD( //定义命令并初始化
mycmd, 3, 1, do_mycmd, //命令名字,最大接收参数,允许重复执行,指定命令执行函数
"Short help of mycmd\n", //帮助信息
"Long help of mycmd\n" //自动补全函数省略
);
4.2 修改Makefile
在/common/Makefile文件中添加下面一行代码,让新添加的C文件被编译进来。然后回到主目录make distclean,然后配置编译就能使用自己定义的uboot命令了。
COBJS-y += mycmd.o