嵌入式linux开发uboot移植(五)——uboot命令体系

    本文将根据SMDKV210开发板的三星官方uboot源码分析uboot的命令体系。内容 包括uboot的命令体系的实现机制,uboot命令是如何执行的,以及如何在uboot中添加一个自定义的命令。

一、uboot命令体系简介

        uboot命令体系代码放在uboot/common中,包括cmd_xxx.ccommand.c main.c源码文件。uboot实现命令体系的方法每一个uboot命令对应一个函数,与shell的实现是一致的

    uboot命令体系没有采用数组、链表来实现,而是每一个命令对应一个cmd_tbl_t命令类型结构体,通过对cmd_tbl_t命令类型结构体的段属性设置,将命令集存储在了程序中的自定义段.u_boot_cmd中,程序在链接阶段会将命令集分配在程序中的自定义段。链接脚本命令集自定义段如下:

__u_boot_cmd_start = .;//命令集段起始地址

.u_boot_cmd : { *(.u_boot_cmd) }//命令集中的命令

__u_boot_cmd_end = .;//命令集段的结束地址

cmd_tbl_t命令类型结构体的段属性设置如下:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))
#ifdef  CFG_LONGHELP
#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}
#else/* no long help info */
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
#endif/* CFG_LONGHELP */

    U_BOOT_CMD实际上定义了一个cmd_tbl_t类型命令结构体U_BOOT_CMD宏的六个参数就是cmd_tbl_t类型命令结构体对应的六个成员。

使用实例如下:

U_BOOT_CMD(hello, 0, 0, do_hello, "hello world help info");

宏展开后为:

cmd_tbl_t __u_boot_cmd_hello __attribute___((unused, section(".u_boot_cmd"))) = {"hello", 0, 0, do_hello, "hello world help info"}

    通过将每个命令的cmd_tbl_t命令类型结构体的段属性的设置为.u_boot_cmd,可以确保uboot命令集中的所有命令在链接阶段都会链接分配到.u_boot_cmd自定义段,当然命令在.u_boot_cmd自定义段内是随机排序的。

    uboot命令集中的每个命令对应一个cmd_tbl_t类型变量,用户输入一个命令时,uboot命令体系会到命令集中查找输入的命令,如果找到就执行,没有找到就提示命令没有找到信息。

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_scmd_tbl_t;

二、uboot命令的解析

    uboot在启动进入BL2阶段后最终执行在main_loop函数,如果在自动倒计时时没有按下字符键,uboot将自动启动kernel;如果按下了字符键,uboot将进入人机交互命令行的主循环,执行读取、解析、执行命令。

    main_loop函数中会先执行getenv ("bootcmd"),如果bootcmd环境变量设置的是启动kenel的命令,则在自动倒计时结束后如果没有字符输入,则uboot会自动执行bootcmd的命令,默认即执行启动kernel。如果自动倒计时结束前有字符输入,则进入命令行提示符状态阻塞等待用户输入命令。readline函数读取用户输入命令,进而通过run_command函数解析、运行命令。run_command函数会将接收的命令用parse_line函数解析,主要是将接收的命令字符串根据空格、分号分割成几部分,利用find_cmd函数遍历查找命令集,看uboot中是否有输入的命令,如果没有输入的命令,打印提示符。如果有当前输入的命令,调用当前输入命令的命令结构体的函数指针成员cmd执行命令对应的函数。

run_command函数源码如下:

int run_command (const char *cmd, int flag)
{
.......................
while (*str) {
//对命令字符串进行简单分割
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);
//解析命令字符串
if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1;/* no command at all */
continue;
}
//遍历查找命令集中是否有当前输入命令
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
 
//调用命令结构体的成员函数指针cmd对应的命令函数
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;
}

命令的遍历查找:

uboot命令集实际是分配在自定义段.u_boot_cmd中的,通过在uboot程序中声明引用自定义段.u_boot_cmd的开始地址__u_boot_cmd_start和结束地址__u_boot_cmd_endfind_cmd函数就可以通过指针访问命令集中的命令。

cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start;//命令集的首地址
const char *p;
int len;
int n_found = 0;
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 */
}



三、uboot命令的执行

run_command函数中解析、遍历查找命令后,如果找到会通过调用命令结构体的成员cmd函数指针调用当前命令对应的命令函数do_xxxxuboot命令的定义模板示例如下:

#if defined(CONFIG_CMD_ECHO)
int do_echo (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
int i, putnl = 1;
for (i = 1; i < argc; i++) 
{
    char *p = argv[i], c;
    if (i > 1)
    putc(' ');
    while ((c = *p++) != '\0')
     {
        if (c == '\\' && *p == 'c') 
        {
        putnl = 0;
        p++;
        } 
        else 
        {
        putc(c);
         }
           }
}
if (putnl)
putc('\n');
return 0;
}
U_BOOT_CMD(
echo,CFG_MAXARGS,1,do_echo,
"echo    - echo args to console\n",
"[args..]\n"
"    - echo args to console; \\c suppresses newline\n"
);
#endif

CONFIG_CMD_ECHO宏可以决定定义的命令是否编译进当前uboot中,一般需要在开发板头文件中定义。命令定义必须包括命令结构体的定义和命令函数的定义。U_BOOT_CMD宏定义了命令结构体,do_echo函数则是命令的具体执行函数。


四、uboot命令添加编程实践

    uboot中添加命令可以在common/commmand.c中添加,也可以重新添加一个cmd_xxxx.c文件添加。

1、command.c文件中添加命令

int do_hello (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
printf ("\n%s\n", "hello world");
return 0;
}
U_BOOT_CMD(
hello,1,1,do_hello,
"hello - print hello world help info\n",
NULL
);

command.c文件中添加命令会导致命令集混乱,因此不推荐。

2、添加cmd_xxxx.c文件添加命令

A、创建cmd_xxxx.c文件

B、添加头文件

#include <common.h>

#include <command.h>

C、添加do_xxx()函数和U_BOOT_CMD

int do_hello (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
printf ("\n%s\n", "hello world");
return 0;
}
U_BOOT_CMD(
hello,1,1,do_hello,
"hello - print hello world help info\n",
NULL
);

D、在common/Makefile中添加cmd_xxxx.o目标文件

COBJS-y+=cmd_xxxx.o

E、编译工程,测试hello命令

common目录下添加cmd_xxx.c命令文件是比较规范的操作,便于uboot命令集的规范化,推荐方式。


    uboot的命令体系本身较为复杂,但开发者在uboot中添加命令是很简单的,只需要添加cmd_xxx.c文件,修改相应Makefile文件就行。