U-boot主循环main_loop分析

最近在写cortex-M3的一个裸机程序,写的过程中突然想到,虽然这个程序很简单
但是我并没有关心cortex-M3的启动代码,很多时候,我只关心主循环在干什么
甚至连初始化CPU部分也不需要很细致,这些代码在启动时只执行一遍,之后就再也没有执行了,
更多时候,需要关心的是一遍又一遍执行的主循环
于是,我觉得,u-boot程序如果不关心初始化,只关注主循环会怎么样呢?
想到就做,于是就有了下面的主循环代码运行分析,这个分析是基于A10的u-boot代码,当然其他芯片应该也适用

 

 

分析环境:WindowsXP + SourceInsight3.5

.....

主循环代码位置:/u-boot-sunxi-sunxi/common/main.c的main_loop函数里面的
前面的初始化和其他部分均忽略,假设在U-boot启动时操作键盘,将进入如下循环代码:
  ......

#ifdef CONFIG_SYS_HUSH_PARSER
     parse_file_outer();  //采用“hush”方式的主循环
 /* This point is never reached */
     for (;;);
#else
     for (;;) {          //一般裸机代码形式的主循环
#ifdef CONFIG_BOOT_RETRY_TIME
     if (rc >= 0) {
   /* Saw enough of a valid command to
    * restart the timeout.
    */
       reset_cmd_timeout();
    }
#endif
    len = readline (CFG_PROMPT);  //读取一行命令输入

    flag = 0; /* assume no special flags for now */
    if (len > 0)
     strcpy (lastcommand, console_buffer);
    else if (len == 0)
     flag |= CMD_FLAG_REPEAT; //直接回车,将会重复执行上次命令
#ifdef CONFIG_BOOT_RETRY_TIME
    else if (len == -2) {
   /* -2 means timed out, retry autoboot
    */
     puts ("\nTimed out waiting for command\n");
# ifdef CONFIG_RESET_TO_RETRY
   /* Reinit board to run initialization code again */
     do_reset (NULL, 0, 0, NULL);
# else
     return;  /* retry autoboot */
# endif
    }
#endif

    if (len == -1)
     puts ("\n");
    else
     rc = run_command (lastcommand, flag); //运行用户输入命令

    if (rc <= 0) {
     /* invalid command or not repeatable, forget it */
     lastcommand[0] = 0;  //命令无效,则不记录该命令
    }
 }


一、主循环方式一 

一般循环方式,假设未定义宏CONFIG_SYS_HUSH_PARSER

将多余的宏汇编去掉,假设均不打开宏汇编内的功能,则简化一下如下:
.......
 for (;;) {

  len = readline (CFG_PROMPT);  //读取一行命令输入,从串口输入

  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)
   puts ("\n");
  else
   rc = run_command (lastcommand, flag); //正常输入时,flag=0;空命令时,flag |= CMD_FLAG_REPEAT

  if (rc <= 0) {  //若调用返回值小于等于零,则清除命令记录
   /* invalid command or not repeatable, forget it */
   lastcommand[0] = 0;
  }
 }
.......

可见,主循环是很简单的,只做两件事情:
1、循环查询接收的一行命令输入
2、执行输入命令

 

1、循环查询接收的一行命令输入
进一步阅读代码,只需要理解串口输入模式和命令执行模式,即可看到u-boot在交互模式下是如何工作的了。
首先来看这句代码:
  len = readline (CFG_PROMPT);

宏定义:
#define CFG_PROMPT    "Andy# " /* Monitor Command Prompt */
CFG_PROMPT是命令提示符,可以自己修改字符串内容,显示不同的提示符。

 

函数readline;代码位置:/u-boot-sunxi-sunxi/common/main.c
int readline (const char *const prompt)
{
 /*
  * If console_buffer isn't 0-length the user will be prompted to modify
  * it instead of entering it from scratch as desired.
  */
 console_buffer[0] = '\0';

 return   readline_into_buffer(prompt, console_buffer, 0); //将一行串口输入读入全局变量console_buffer
}


再看函数readline_into_buffer;代码位置:/u-boot-sunxi-sunxi/common/main.c
将多余的宏汇编去掉,因为那些功能都没有使能

int readline_into_buffer(const char *const prompt, char *buffer, int timeout)
{
    char *p = buffer;

    char * p_buf = p;
    int n = 0;    /* buffer index  */
    int plen = 0;   /* prompt length */
    int col;    /* output column cnt */
    char c;

    /* print prompt */ 
    if (prompt) {
         plen = strlen (prompt);
         puts (prompt);  //打印提示符  len = readline (CFG_PROMPT) 的 CFG_PROMPT
    }
    col = plen;  //光标的位置,在提示符之后

    for (;;) {   //这里又是一个循环,即等待一行输入,不回车或换行将一直循环

        WATCHDOG_RESET();  /* Trigger watchdog, if needed */  //看门狗,实际是空的宏,没使用;
  
         c = getc();   //获取串口输入一个字符,假设要将输入命令移植到另一个裸机程序内,则需要实现这个接口,
                             //我将readline函数移植到了cortex-M3的控制器STM32F103VC上,实现串口与超级中断交互

       /*
        * Special character handling  //特别字符处理,都是ASCC-II码,回车、换行...等等特殊字符
       */
       switch (c) {
       case '\r':    /* Enter  */  //收到回车符,认为一行输入完成,返回执行命令
       case '\n':                        //收到换行符
            *p = '\0';
              puts ("\r\n");
              return (p - p_buf);

       case '\0':    /* nul   */  //收到空字符,啥也不做
             continue;

       case 0x03:    /* ^C - break  */    
             p_buf[0] = '\0'; /* discard input */
             return (-1);

       case 0x15:    /* ^U - erase line */ 
             while (col > plen) {
                 puts (erase_seq);
                 --col;
             }
             p = p_buf;
            n = 0;
            continue;

       case 0x17:    /* ^W - erase word */
             p=delete_char(p_buf, p, &col, &n, plen);
             while ((n > 0) && (*p != ' ')) {
                  p=delete_char(p_buf, p, &col, &n, plen);
              }
              continue;

        case 0x08:    /* ^H  - backspace */   //backspace输入
        case 0x7F:    /* DEL - backspace */   //delete键输入
               p=delete_char(p_buf, p, &col, &n, plen);
               continue;

         default:    //普通字符输入,存入全局字符串变量console_buffer
         /*
        * Must be a normal character then
         */
              if (n < CONFIG_SYS_CBSIZE-2) {
                    if (c == '\t') { /* expand TABs  */
                            puts (tab_seq+(col&07));
                            col += 8 - (col&07);
                    } else {
                            ++col;  /* echo input  */
                             putc (c);   //将输入打印出来,这样PC上的超级终端或minicom就能看到输入命令了
                    }
                    *p++ = c;
                    ++n;
            } else {   /* Buffer full  */
                    putc ('\a');
            }
       }
   }
}

由上述代码可见,与硬件相关代码就是
      c = getc();
一般来说是串口,实现getc从串口读取数据即可,

早期版本arm9的U-boot的实现方式:在/u-boot-sunxi-sunxi/common/console.c
int getc (void)
{
        if (gd->flags & GD_FLG_DEVINIT) {
                /* Get from the standard input */
                return fgetc (stdin);  //若实现了标准输入,从标准输入读取数据
        }

        /* Send directly to the handler */
        return serial_getc ();  //从串口读取数据
}

int serial_getc (void) //根据CPU实现
{
        S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);  //获取s3c24x0串口寄存器指针

        /* wait for character to arrive */
        while (!(uart->UTRSTAT & 0x1));   //循环等待串口接收完成

         return uart->URXH & 0xff;
}
这是早期版本arm9的U-boot的实现方式,其实就是循环等待串口一个字节输入,
在u-boot-sunxi-sunxi中原理一致,具体实现细节比这个复杂,可以参阅代码,说道这里,其实输入字符的方法已经清晰了。


2、执行输入命令

在主循环中,通过一下函数执行一个命令:
   rc = run_command (lastcommand, flag); 
   
看看run_command函数,在/u-boot-sunxi-sunxi/common/main.c里面:

int run_command(const char *cmd, int flag)
{
  #ifndef CONFIG_SYS_HUSH_PARSER  //未定义
            /*
             * builtin_run_command can return 0 or 1 for success, so clean up
             * its result.
             */
         if (builtin_run_command(cmd, flag) == -1)  //实际执行的代码
                 return 1;

         return 0;
  #else
         return parse_string_outer(cmd,
                                                         FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP);
  #endif
}

//下面这个函数负责调用解析命令模块和命令执行模块

static int builtin_run_command(const char *cmd, int flag)
{
      char cmdbuf[CONFIG_SYS_CBSIZE]; /* working copy of cmd  */
      char *token;   /* start of token in cmdbuf */
      char *sep;   /* end of token (separator) in cmdbuf */
      char finaltoken[CONFIG_SYS_CBSIZE];
      char *str = cmdbuf;
      char *argv[CONFIG_SYS_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) >= CONFIG_SYS_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;
       }

      if (cmd_process(flag, argc, argv, &repeatable))      //执行命令
              rc = -1;

        /* Did the user stop this? */
       if (had_ctrlc ())                                    //执行时按ctrl+C,将会退出执行
             return -1; /* if stopped then not repeatable */
    }

     return rc ? rc : repeatable;
}
#endif
该函数最后调用到cmd_process函数,执行相应的命令


二、主循环方式二 ----> “hush”方式

定义了宏CONFIG_SYS_HUSH_PARSER,在文件/u-boot-sunxi-sunxi/include/configs/sunxi-common.h内

主循环变成如下方式:
#ifdef CONFIG_SYS_HUSH_PARSER
 parse_file_outer();
 /* This point is never reached */
 for (;;);
#else
 .....

函数parse_file_outer,在/u-boot-sunxi-sunxi/common/hush.c内

宏__U_BOOT__没有定义,因此调用parse_file_outer不需要传参数

#ifndef __U_BOOT__
static int parse_file_outer(FILE *f)
#else
int parse_file_outer(void)
#endif
{
      int rcode;
      struct in_str input;
#ifndef __U_BOOT__
      setup_file_in_str(&input, f);
#else
      setup_file_in_str(&input);   //输入初始化
#endif
      rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
      return rcode;
}

再调用parse_stream_outer函数,也在/u-boot-sunxi-sunxi/common/hush.c内
函数parse_stream_outer是这种方式的循环主体,就是其中的do-while循环体。

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 = 0;
#endif
     do {  //主循环体
              ctx.type = flag;
              initialize_context(&ctx);
              update_ifs_map();
              if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING))         mapset((uchar *)";$&|", 0);
              inp->promptmode=1;
               rcode = parse_stream(&temp, &ctx, inp, '\n');  //读取一行输入命令,并解析之
#ifdef __U_BOOT__
              if (rcode == 1) flag_repeat = 0;
#endif
              if (rcode != 1 && ctx.old_flag != 0) {
                   syntax();
#ifdef __U_BOOT__
                   flag_repeat = 0;
#endif
               }
             if (rcode != 1 && ctx.old_flag == 0) {
                   done_word(&temp, &ctx);
                   done_pipe(&ctx,PIPE_SEQ);
#ifndef __U_BOOT__
                   run_list(ctx.list_head);
#else
              code = run_list(ctx.list_head);  //运行命令
              if (code == -2) { /* exit */
                    b_free(&temp);
                    code = 0;
                    /* XXX hackish way to not allow exit from main loop */
                    if (inp->peek == file_peek) {
                           printf("exit not allowed from main input shell.\n");
                          continue;
                      }
                     break;
               }
               if (code == -1)
                      flag_repeat = 0;
#endif
                } else {
                     if (ctx.old_flag != 0) {
                             free(ctx.stack);
                             b_reset(&temp);
                      }
#ifdef __U_BOOT__
                if (inp->__promptme == 0)      printf("\n");
                inp->__promptme = 1;
#endif
                temp.nonnull = 0;
                temp.quote = 0;
                inp->p = NULL;
                free_pipe_list(ctx.list_head,0);
         }
         b_free(&temp);
   } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP));   /* loop on syntax errors, return on EOF */
#ifndef __U_BOOT__
   return 0;
#else
    return (code != 0) ? 1 : 0;
#endif /* __U_BOOT__ */
}

循环方式二也是两个过程:
1、读取一行输入
2、解析并执行输入命令


1、读取数据
parse_file_outer函数内,输入初始化:
         struct in_str input;
         setup_file_in_str(&input);
 
看看setup_file_in_str函数干了啥:
#ifndef __U_BOOT__
static void setup_file_in_str(struct in_str *i, FILE *f)
#else
static void setup_file_in_str(struct in_str *i)
#endif
{
      i->peek = file_peek;  //输入方式peek
      i->get = file_get;    //输入方式get
      i->__promptme=1;
      i->promptmode=1;
#ifndef __U_BOOT__
      i->file = f;
#endif
      i->p = NULL;
}
看看数据结构struct in_str
struct in_str {
     const char *p;
#ifndef __U_BOOT__
     char peek_buf[2];
#endif
     int __promptme;
     int promptmode;
#ifndef __U_BOOT__
     FILE *file;
#endif
     int (*get) (struct in_str *);
     int (*peek) (struct in_str *);
};

可以看出,函数指针get和peek就指向输入方式,在初始化时:
 i->peek = file_peek;
 i->get = file_get;

函数file_peek和file_get就是实现输入的函数
file_peek调用了fgetc,实现输入,这个需要查看控制台文件console.c
file_get调用get_user_input函数,get_user_input调用readline,又回到了方式一的输入啦
具体内容请参看这几个函数代码
file_peek,file_get,get_user_input都在文件/u-boot-sunxi-sunxi/common/hush.c里面

 

虽然初始化时将 i->get = file_get ,但是在哪使用呢???

初始化之后,parse_file_outer函数内调用主循环函数:
                 rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
 将初始化之后的input传给了主循环,主循环继续调用
                 rcode = parse_stream(&temp, &ctx, inp, '\n');
 将inp(即input)传递给parse_stream
 
 在parse_stream函数内循环读取输入:
                while ((ch=b_getch(input))!=EOF) {
 b_getch是一个宏: #define b_getch(input) ((input)->get(input))
 
 实际就是 while ( ( ch = input->get(input) ) != EOF) {
 如此以来就调用到了readline,然后调用关系:readline-->readline_into_buffer-->getc
 getc再调用serial_getc,实现与串口输入关联,与主循环方式一相同。

2、执行命令

主循环通过代码:
           run_list(ctx.list_head);
执行用户输入的命令。

run_list函数,在/u-boot-sunxi-sunxi/common/hush.c里面:
static int run_list(struct pipe *pi)
{
      int rcode=0;
#ifndef __U_BOOT__
      if (fake_mode==0) {
#endif
          rcode = run_list_real(pi);
#ifndef __U_BOOT__
      }
#endif
 /* free_pipe_list has the side effect of clearing memory
  * In the long run that function can be merged with run_list_real,
  * but doing that now would hobble the debugging effort. */
     free_pipe_list(pi,0);
     return rcode;
}

再调用run_list_real函数,在/u-boot-sunxi-sunxi/common/hush.c里面:
static int run_list_real(struct pipe *pi)
{
    ......
      rcode = run_pipe_real(pi);
    .......
}

再调用run_pipe_real函数,在/u-boot-sunxi-sunxi/common/hush.c里面:
static int run_pipe_real(struct pipe *pi)
{
      ......
     /* Process the command */
     return cmd_process(flag, child->argc, child->argv,
                                           &flag_repeat);
    ......
}
以上代码粗一看,似乎在实现管道,具体的不在本文分析,大致意思就是将收到的指令通过一系列处理加入一个执行列表,然后执行这个列表
还有一大堆的控制、出错处理.......

最后,循环方式二也调用到cmd_process函数,这又与循环方式一相同。


三、两种循环方式命令的执行方式

两种循环方式执行命令的模式相同,都通过cmd_process函数进行,在/u-boot-sunxi-sunxi/common/command.c里面:
在cmd_process中调用关系如下
1、cmd_process -> find_cmd-> find_cmd_tbl,通过用户输入找到需要执行的命令代码
2、cmd_process -> cmd_call,执行指令代码
这些函数都在command.c文件内,下面分析一下这几个函数,就可以看见执行一个命令的方式了:

enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
          int *repeatable)
{
     enum command_ret_t rc = CMD_RET_SUCCESS;
     cmd_tbl_t *cmdtp;

 //参数argc,argv就是超级终端、minicom等软件通过串口输入的一行经过处理的命令
 //例如:用户输入 nand write 50000000 10000 20000     
 //将内存0x500000000的内容写入nandflash,从nand地址0x10000开始,长度0x20000,下面都用这个例子来说明
 //则经过前面的解析之后,结果如下:跟系统编程相同
 //argc    = 5
 //argv[0] = "nand"
 //argv[1] = "write"
 //argv[2] = "50000000"
 //argv[3] = "10000"
 //argv[4] = "20000"
 
     /* Look up command in command table */
     cmdtp = find_cmd(argv[0]);     //通过命令找到要执行的代码,例子中的“nand”
     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) {
               rc = cmd_call(cmdtp, flag, argc, argv);  //运行命令,再次将用户的输入传递给命令代码
               *repeatable &= cmdtp->repeatable;
       }
       if (rc == CMD_RET_USAGE)
                rc = cmd_usage(cmdtp);
       return rc;
}


//下面这两个函数负责查找要执行的命令
cmd_tbl_t *find_cmd (const char *cmd)
{
     int len = &__u_boot_cmd_end - &__u_boot_cmd_start;    //命令段的长度
     return find_cmd_tbl(cmd, &__u_boot_cmd_start, len);   //从__u_boot_cmd_start开始往后搜索
}


到了这里,就需要了解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
};

typedef   struct    cmd_tbl_s     cmd_tbl_t;

extern    cmd_tbl_t    __u_boot_cmd_start;
extern    cmd_tbl_t    __u_boot_cmd_end;

还有三个重要的宏:
#define Struct_Section     __attribute__((unused, section(".u_boot_cmd"), \
                                              aligned(4)))

#define U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \
 cmd_tbl_t __u_boot_cmd_##name Struct_Section = \
       U_BOOT_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp)
  
  
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
       U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,NULL)


举个命令定义的例子,就用nand命令:

U_BOOT_CMD(
      nand, CONFIG_SYS_MAXARGS, 1, do_nand,
      "NAND sub-system",
      "info - show available NAND devices\n"
      "nand device [dev] - show or set current device\n"
      "nand read - addr off|partition size\n"
      "nand write - addr off|partition size\n"
      "    read/write 'size' bytes starting at offset 'off'\n"
      "    to/from memory address 'addr', skipping bad blocks.\n"
      "nand read.raw - addr off|partition [count]\n"
      "nand write.raw - addr off|partition [count]\n"
      "    Use read.raw/write.raw to avoid ECC and access the flash as-is.\n"
);
name       -> nand,当用户输入“nand”时,就会通过这个命令,找到要执行的函数“do_nand”
maxargs  -> CONFIG_SYS_MAXARGS,参数的最大个数,默认是256
rep            -> 1,执行次数,一般是1次
cmd          -> do_nand,执行代码,即do_nand是一个函数
usage       -> "NAND sub-system",用途
help           -> "......",后面一大堆就是帮助信息,提示用户操作,输入“nand help”可以看到

这些字段将会按数据结构struct cmd_tbl_s的方式存储,存储在代码段内,从下面两个宏,可以看出
#define Struct_Section     __attribute__((unused, section(".u_boot_cmd"), \
                                              aligned(4)))
#define U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \
 cmd_tbl_t __u_boot_cmd_##name Struct_Section = \
         U_BOOT_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp)
可以nand命令将以标号 __u_boot_cmd_nand进行编译,编译后,__u_boot_cmd_nand最后将以一个地址存在.u_boot_cmd段内,
只要找到这个地址,通过结构体 struct cmd_tbl_s,就能把所有的信息读出来

.u_boot_cmd又是如何定义的呢??这就要看u-boot.lds这个文件了,打开\u-boot-sunxi-sunxi\arch\arm\cpu\u-boot.lds
   ..........
 . = .;
 __u_boot_cmd_start = .;
 .u_boot_cmd : { *(.u_boot_cmd) }
 __u_boot_cmd_end = .;

 . = ALIGN(4);
  ..........
 
看一下,原来__u_boot_cmd_start和 __u_boot_cmd_end直接定义在这里,
所以用extern来引用,链接器在u-boot.lds中会找到这两个指针
而且,所有通过U_BOOT_CMD定义的命令都存在.u_boot_cmd段内,就在__u_boot_cmd_start和__u_boot_cmd_end之间,
那么需要搜索一个命令理所当然就应在这个范围内寻找啦,再看下面的代码,就可以理解为什么这么做了。

看看搜索的过程:调用方式  find_cmd_tbl(cmd, &__u_boot_cmd_start, len);

cmd_tbl_t *find_cmd_tbl (const char *cmd, cmd_tbl_t *table, int table_len)
{
     cmd_tbl_t *cmdtp;
     cmd_tbl_t *cmdtp_temp = table; /*Init value */
     const char *p;
     int len;
     int n_found = 0;

  //参数cmd就是argv[0],即例子的“nand”
  //参数table就是命令列表的首地址,即&__u_boot_cmd_start
  //参数table_len就是命令列表的长度,搜索的范围即&__u_boot_cmd_end - &__u_boot_cmd_start

     if (!cmd)
            return NULL;
 /*
  * Some commands allow length modifiers (like "cp.b");
  * compare command name only until first dot.
  */
     len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);  //命令的长度,例如“nand”长度为4

     for ( cmdtp = table;                            //从&__u_boot_cmd_start开始
             cmdtp != table + table_len;     //到&__u_boot_cmd_end结束
             cmdtp++) {                                 //按结构体struct cmd_tbl_t移动指针
                   if ( strncmp (cmd, cmdtp->name, len) == 0) {          //只需要比较命令名字是否相同
                          if (len == strlen (cmdtp->name))
                                   return cmdtp; /* full match */                               //完全匹配的情况,找到命令,直接返回

                          cmdtp_temp = cmdtp; /* abbreviated command ? */
                          n_found++;                                 //部分匹配的情况,假设有一个命令“nand_check”并在在“nand”之前,就是这种情况,继续寻找
                    }
      }
      if (n_found == 1) {   /* exactly one match */   //找到一个部分匹配的命令,也可以执行,因此,输入“nand”、“nan”、“na”,都可能执行nand命令
              return cmdtp_temp;
      }

      return NULL; /* not found or ambiguous command */ //没找到或者找到多个部分匹配的命令,则不执行
}

//执行一个命令,直接调用这个命令内的函数指针指向的函数
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
      int   result;

     result = (cmdtp->cmd)(cmdtp, flag, argc, argv);  //对于“nand”命令,就是do_nand(cmdtp, flag, argc, argv);
     if (result)
          debug("Command failed, result=%d", result);
     return result;
}

说了一大堆,基本将主循环输入和执行一条命令的方式描述了一遍,很难说得面面俱到

//=================================================================
下面举例说说如何在U-boot内加入自己的命令

1、在\u-boot-sunxi-sunxi\common下建立一个文件,就叫cmd_my_command.c
2、修改\u-boot-sunxi-sunxi\common\Makefile,在command那一段加一句:
       COBJS-$(CONFIG_CMD_MY_COMMAND) += cmd_my_command.o
  
3、在\u-boot-sunxi-sunxi\include\config_cmd_all.h末尾加一句,使编译生效
       #define CONFIG_CMD_MY_COMMAND     

4、文件cmd_my_command.c内容:
  
#include      //头文件必须包含
#include     //头文件必须包含

//命令函数,按照u-boot惯例,将函数名写成do_xxxxx
int do_test(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
      int i;
 
      printf("this is a test command\n");
      printf("argc = %d\n", argc);
      for(i = 0; i < argc; i++){
           printf( "argv[%d] = %s\n", i, argv[i]);
      }
      return 0;
}
//定义命令 
U_BOOT_CMD(
     test, CONFIG_SYS_MAXARGS, 1, do_test,
     "test my test command",
    "test [anything]\n"
); 

5、编译u-boot,写入SD启动卡,上电启动之

输入
test 1 a b

输出:

argc = 4
argv[0] = test
argv[0] = 1
argv[0] = a
argv[0] = b

 

你可能感兴趣的:(u-boot学习)