一、uboot命令体系基础
1.uboot命令体系实现代码在哪里
(1)uboot命令体系的实现代码在common/cmd_xxx.c中,有若干个.c文件和命令体系有关。(还有command.c、main.c也和命令有关)
2.每个命令对应一个函数
(1)每一个uboot的命令背后都对应一个函数,这就是uboot实现命令体系的一种思路和方法。
(2)要找到每一个命令背后所对应的那个函数,而且要分析这个函数和这个命令是怎样对应起来的。
3.命令参数以argc&argv传给函数
(1)有些uboot命令还支持传递参数,也就是命令背后对应的函数接收的参数列表中有argc和argv,然后命令体系会把我们执行命令时的命令+参数以argc和argv的方式传递给执行命令的函数。例如命令:md 30000000 10,argc=3,argv(argv[0]=md,argv[1]=30000000,argv[2]=10)
以help命令为例:help命令背后对应的函数名叫:do_help,在common/command.c的236行有如下定义:
int do_help (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
二、uboot命令解析和执行过程分析
1.从main_loop说起
(1)uboot启动的第二阶段,在初始化了所有该初始化的东西后,进入一个死循环,死循环的循环体就是main_loop。
(2)main_loop函数执行一次就是一个获取命令、解析命令、执行命令的过程。
(3)run_command函数用来执行命令的函数。
2.run_command函数详解
代码:1284 ~ 1407行(common/main.c)
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;
#ifdef DEBUG_PARSER
printf ("[RUN_COMMAND] cmd[%p]=\"", cmd);
puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */
puts ("\"\n");
#endif
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
*/
#ifdef DEBUG_PARSER
printf ("[PROCESS_SEPARATORS] %s\n", cmd);
#endif
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 */
#ifdef DEBUG_PARSER
printf ("token: \"%s\"\n", token);
#endif
/* find macros in this token and replace them */
process_macros (token, finaltoken);
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
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;
}
#if defined(CONFIG_CMD_BOOTD)
/* avoid "bootd" recursion */
if (cmdtp->cmd == do_bootd) {
#ifdef DEBUG_PARSER
printf ("[%s]\n", finaltoken);
#endif
if (flag & CMD_FLAG_BOOTD) {
puts ("'bootd' recursion detected\n");
rc = -1;
continue;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif
/* 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;
}
(1)控制台命令获取。
(2)命令解析:parse_line函数把"md 30000000 10"解析成argv[0]=md、argv[1]=30000000、argv[2]=10。
(3)在命令集合中查找命令:find_cmd(argv[0])函数去uboot的命令集合中搜索有没有argv[0]这个命令。
(4)执行命令:最后用函数指针的方式调用相应的函数。
思考:关键点就在于find_cmd函数如何查找到这个命令是不是uboot的合法支持的命令?这取决于uboot的命令体系机制(uboot是如何设计的,命令是如何注册、存储、管理、索引的)。
三、uboot如何处理命令集
1.可能的管理方式
(1)数组:结构体数组,数组中每一个结构体成员就是一个命令的所有信息。
(2)链表:链表的每个节点的data段就是一个命令结构体,所有的命令都放在一条链表上,这样就解决了数组方式的不灵活,坏处是需要额外的内存开销,然后各种算法(遍历、插入、删除等)需要一定复杂度的代码执行。
(3)有第三种吗?uboot没有使用数组或者链表,而是使用了一种新的方式来实现这个功能。
2.命令结构体cmd_tbl_t
/*
* Monitor Command Table
*/
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 *[]);
char *usage; /* Usage message (short) */
#ifdef CFG_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
extern cmd_tbl_t __u_boot_cmd_start;
extern cmd_tbl_t __u_boot_cmd_end;
(1)name:命令名字,字符串格式
(2)maxargs:命令最多可以接收多少个参数
(3)repeatable:表示这个命令是否可以重复执行。重复执行是uboot命令行的一种工作机制,就是按回车键执行上一条执行的命令。
(4)cmd:函数指针,命令对应的函数的函数指针,将来执行这个命令的函数时,使用这个函数指针来调用
(5)usage:命令的短帮助信息,命令的简单描述
(6)help:命令的长帮助信息,命令的详细描述
(7)complete:函数指针,执行这个命令的自动补全函数
总结:uboot的命令体系在工作时,一个命令对应一个cmd_tbl_t结构体的实例,uboot支持多少命令,就需要多少个结构体的实例。uboot的命令体系把这些结构体实例管理起来,当用户输入命令后,uboot就去这些结构体实例中查找(查找方法和存储管理的方法有关),如果找到就执行,找不到就提示命令未知。
3.uboot实现命令管理的思路
(1)填充一个结构体实例对应一个命令。
(2)给命令结构体实例附加特定段属性(用户自定义段),链接时将带有该段属性的内容链接在一起排列(挨着的,不会夹杂其他东西,也不会丢掉一个带有这种段属性的,但是顺序是乱的)。
(3)uboot重定位时将该段整体加载到DDR中。加载到DDR中的uboot镜像中带有特定段属性的这一段其实就是命令结构体的结合,有点像命令结构体数组。
(4)段起始地址和结束地址(链接地址定义在uboot.lds中)决定了命令集的开始和结束地址。
4.U_BOOT_CMD
它的定义位于common/command.h文件中,如下所示:
#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}
(1)这个宏其实就是定义了命令对应的结构体变量,这个变量名和宏的第一个参数有关,因此只要宏调用时传参的第一个参数不同,那么定义的结构体变量不会重名。
(2)举例说明
U_BOOT_CMD(
version, 1, 1, do_version,
"version - print monitor version\n",
NULL
);
宏替换完成后
cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = \
{"version", 1, 1, do_version, "version - print monitor version\n", NULL};
总结:对于U_BOOT_CMD宏的理解,关键在于结构体变量的名字和段属性,名字使用##作为连字符,附加了用户自定义段属性,以保证链接时将这些数据结构链接在一起排布。
5.find_cmd函数
代码:344 ~ 374行(common/command.c)
/***************************************************************************
* find command table entry for a command
*/
cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */
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;
cmdtp != &__u_boot_cmd_end;
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 */
}
(1)find_cmd函数的任务是从当前uboot的命令集中查找是否有某个命令,如果找到则返回这个命令结构体的指针,如果未找到则返回NULL。
(2)函数的实现思路很简单,如果不考虑命令带点的情况(md.b、md.w这种)就更简单了,查找命令的思路其实就是for循环遍历数组的思路,不同的是数组的起始地址和结束地址是用地址值来给定的,数组中的元素个数是结构体变量类型。
四、uboot中增加自定义命令
1.在已有的.c文件中直接添加命令
(1)在common/command.c中添加一个命令,叫:mycmd
(2)在已有的.c文件中添加命令比较简单,直接使用U_BOOT_CMD宏即可添加命令,然后再给命令提供一个对应的do_xxx函数就齐活了。
(3)添加完成后要重新编译工程(make distclean; make 210_sd_config; make),然后烧录新的uboot去运行即可体验新命令。
(4)还可以在函数中使用argc和argv来验证传参。
2.自建一个.c文件并添加命令
(1)在common目录下新建一个命令文件,叫cmd_aston.c(对应的命令名就叫aston,对应的函数就叫do_aston),然后在.c文件中添加对应的U_BOOT_CMD宏和函数,注意头文件包含不要漏掉。
(2)在common/Makefile中添加cmd_aston.o,目的是让Make在编译时能办cmd_astion.c编译链接进去。
(3)重新编译烧录,重新编译步骤:make distclean; make 210_sd_config; make。
3.体会:uboot命令体系的优点
(1)uboot的命令体系本身稍微复杂,但是写好之后就不用动了。在移植uboot时也不会去动uboot的命令体系,最多就是想uboot中添加命令。
(2)向uboot中添加命令非常简单。