> 文档中心 > 超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

文章目录

    • 4.1 如何进入命令行模式
    • 4.2 `Uboot`基本命令解析
    • 4.3 命令行模式代码执行流程分析
    • 4.4 如何添加Uboot命令
      • 第一步:照葫芦
      • 第二步:画瓢
      • 第三步:优雅
    • 4.5 Uboot命令底层实现分析
      • 4.5.1 U_BOOT_CMD
        • 在这里,不得不提到`#`和`##`的区别
        • 定义的命令存放在哪里呢?
    • 4.6 Uboot命令响应流程
    • 4.6 推荐文档

💖 作者简介:大家好,我是董哥,嵌入式领域新星创作者。😜
🧿 创作目标:认真对待每一篇文章,让每一位读者读有所得!🥇
🏰 个人主页:董哥聊技术


前几篇文章,我们也了解了Uboot的启动流程,那么这节就主要讲讲Uboot的命令行模式。

另外,文章末尾还提供eMMC5.1官方标准协议.pdfeMMC4.51官方标准协议-中文.pdf下载渠道,方便深入了解底层协议。

正文如下

 

4.1 如何进入命令行模式

我们正常启动流程,默认是直接跳过Uboot命令行模式的,因为Uboot主要的作用是引导Kernel,一般我们不进行uboot开发时,都默认跳过进入命令行模式。

 

那么,我们要想进入Uboot命令行模式,需要进行哪些配置呢?

打开我们准备好一份Uboot源码,进入menuconfig配置菜单,主要设置下列几个配置信息!

  • CONFIG_CMDLINE:命令行模式开关
  • CONFIG_SYS_PROMPT:命令行模式提示符
  • CONFIG_HUSH_PARSER:使用hush shell 来对命令进行解析
  • BOOTDELAY:设置启动延时

Tipmeneconfig中查找苦难?实时/符号,输入1或2或3,直接查找指定标识。

 

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

 

打开之后,重新编译,并将Uboot镜像烧录到开发板中,再次启动,我们就能够看到倒计时。

[2022-03-02:13:33:47]U-Boot 2020.10-rc1-00043-ge62a6d17c6-dirty (Feb 08 2022 - 10:14:14 +0800)[2022-03-02:13:33:47][2022-03-02:13:33:47]Model: xxxxxx[2022-03-02:13:33:47]MMC:   mmc1@xxxxxx: 1[2022-03-02:13:33:47]In:    serial[2022-03-02:13:33:47]Out:   serial[2022-03-02:13:33:47]Err:   serial[2022-03-02:13:33:47]Model: xxxxxx[2022-03-02:13:33:49]Hit any key to stop autoboot:  2 

Hit any key to stop autoboot:我们在倒计时结束前,任意键入一个按键,即可进入!

 

4.2 Uboot基本命令解析

进入Uboot命令行模式后,键入help或者?,可以查看所有支持的Uboot命令。

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

注意Uboot支持的命令大都远远超过显示的,还有好多没有打开,可以在menuconfig中,打开相应的功能,如mmc相关的,md内存相关的。

 

常用命令如下

version#查看uboot版本reset #重启Ubootprintenv#打印uboot环境变量setenv name value#设置环境变量md addr#查看内存指令nm addr#修改内存值mm addr#自增修改内存值mmc dev id#选择mmc卡mmc rescan#扫描卡echo $name#打印环境变量

更多指令使用,可以见文末整理的文档

 

4.3 命令行模式代码执行流程分析

结合下面的程序执行流程图,代码,一起分析。

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

上图为Uboot命令行模式的代码具体执行流程,结合 专栏系列(二)uboot启动流程分析,文章内已经详细分析函数内部实现。

 

static int abortboot(int bootdelay){int abort = 0;if (bootdelay >= 0) {if (IS_ENABLED(CONFIG_AUTOBOOT_KEYED))abort = abortboot_key_sequence(bootdelay);elseabort = abortboot_single_key(bootdelay);//按键检测}if (IS_ENABLED(CONFIG_SILENT_CONSOLE) && abort)gd->flags &= ~GD_FLG_SILENT;return abort;}static int abortboot_single_key(int bootdelay){int abort = 0;unsigned long ts;printf("Hit any key to stop autoboot: %2d ", bootdelay);//打印倒计时/* * Check if key already pressed */if (tstc()) {/* we got a key press*///获取按键(void) getc();  /* consume input*/puts("\b\b\b 0");abort = 1;/* don't auto boot*/}while ((bootdelay > 0) && (!abort)) {--bootdelay;/* delay 1000 ms */ts = get_timer(0);do {if (tstc()) {/* we got a key press*///获取按键int key;abort  = 1;/* don't auto boot*/bootdelay = 0;/* no more delay*/key = getc(); /* consume input*/if (IS_ENABLED(CONFIG_USE_AUTOBOOT_MENUKEY))menukey = key;break;}udelay(10000);} while (!abort && get_timer(ts) < 1000);//延时1Sprintf("\b\b\b%2d ", bootdelay);}putc('\n');return abort;}

abortboot_single_key:该函数主要用于while循环检测按键,如果有按键按下,将abort标志位置1,最后运行cli_loop命令行模式的函数。

如果按键不按下,标志位abort不起作用,直接运行run_command_list(s, -1, 0);s = env_get("bootcmd");,直接跳转到我们设置的环境变量bootcmd所设定的指令,而不执行cli_loop函数。

对照运行流程图看代码,容易理解!!!

 

4.4 如何添加Uboot命令

如何自定义一个Uboot命令呢?

我们暂且先不考虑实现的原理,就仅仅照葫芦画瓢来实现一个简单的Uboot命令!

 

第一步:照葫芦

我们打开Uboot的源码文件,进入cmd目录,没错,所有的命令实现都存放在该目录下。

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

有没有看到help.C这个文件呢,我们就拿help这个文件来类比。

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

U_BOOT_CMD:用来定义一个命令

help:用于命令行键入的指令

do_help:键入指令后,执行的函数

要想进一步使用该命令,我们不得不去了解每个参数的含义。

struct cmd_tbl_s {char*name;/* Command Name*/intmaxargs;/* maximum number of arguments*/intrepeatable;/* autorepeat allowed?*//* Implementation function*/int(*cmd)(struct cmd_tbl_s *, int, int, char *[]);char*usage;/* Usage message(short)*/char*help;/* Help  message(long)*//* do auto completion on the arguments */int(*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);};typedef struct cmd_tbl_scmd_tbl_t;

每个参数分别对应了:命令名、可接收的最大参数、命令可重复、响应函数、使用示例、帮助信息。

 

第二步:画瓢

弄明白这个道理,假如我们想加入一个helpme的指令,该怎么做?

  • 定义一个指令
U_BOOT_CMD(helpme,CONFIG_SYS_MAXARGS,1,do_helpme,"helpme dong","\n""- print brief description of all commands\n""helpme command ...\n""- print detailed usage of 'command'");
  • 定义一个执行函数
static int do_helpme(struct cmd_tbl *cmdtp, int flag, int argc,   char *const argv[]){printf("Cmd test ok!\r\n");printf("argc = %d\r\n", argc);printf("argv = ");for(int i = 0; i < argc; ++i) {printf("%s\t", argv[i]);}printf("\r\n");}

这样,就可以编译->烧录->运行了。

进入Uboot命令行,键入help查看添加的命令helpme

  • 键入命令测试
=> helpme 123456 123Cmd test ok!argc = 3argv = helpme   123456  123

 

第三步:优雅

如果我们只是暂时测试,这样添加无伤大雅;如果我们需要投入正规项目使用,这么做有点激进了。

更加合理的做法是:

  • uboot/cmd目录下,建立一个文件XXX.c
  • 将要添加的命令写入XXX.c该文件中
  • 修改Makefile文件,编译该文件:obj-y += XXX.o
  • 重新编译,烧录

说白了,就是创建一个文件,将自定义指令添加进去,尽量不修改源码!

 

4.5 Uboot命令底层实现分析

上面写了傻瓜式添加命令的方法,对于进行Uboot开发,当然我们需要去了解一下内部的实现原理。

4.5.1 U_BOOT_CMD

查看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(struct cmd_tbl, _name, cmd) =\U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,\_usage, _help, _comp);
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,\_usage, _help, _comp)\{ #_name, _maxargs,\ _rep ? cmd_always_repeatable : cmd_never_repeatable,\ _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_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 TO_STR(x) #xint main(){    int value = 123;    printf("TO_STR(value) = %s\n", TO_STR(value));    printf("TO_STR(123) = %s\n", TO_STR(123));}//打印TO_STR(value) = value;TO_STR(123) = 123;
  • ##:两个字符拼接
#define CONNECT(x,y) x##y#define VAR(y) data##yint main(){    int xy = 123;    printf("xy = %d\n", CONNECT(x, y));    CONNECT(x, y) = 123456;    printf("xy = %d\n", CONNECT(x, y));    int VAR(1) = 100;    printf("VAR(1) = data1 = %d\n", data1);}//打印xy = 123xy = 123456VAR(1) = data1 = 100

回到正文

上面的宏定义,简单来看,转换流程就是

U_BOOT_CMD -> U_BOOT_CMD_COMPLETE -> ll_entry_declare = U_BOOT_CMD_MKENT_COMPLETE -> _type xxx = {aaa, bbb, ccc , ...}

其本质就是: struct my_struct test = {1, 2, 3};结构体赋值语句

 

help命令为例

U_BOOT_CMD(help,CONFIG_SYS_MAXARGS,1,do_help,"print command description/usage","\n""- print brief description of all commands\n""help command ...\n""- print detailed usage of 'command'");

直接展开来看

struct cmd_tbl _u_boot_list_2_cmd_2_help __aligned(4) __attribute__((unused, section(".u_boot_list_2_cmd_2_help"))) = {"help", CONFIG_SYS_MAXARGS, cmd_always_repeatable, do_help, "xxx", "xxx"};

也就相当于,我们定义一个命令,给其赋值。

定义的命令存放在哪里呢?

根据上面展开来看,section(".u_boot_list_2_cmd_2_help"),存放在段.u_boot_list_2_cmd_2_help中,打开u-boot.map文件,我们可以查找得到。

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

有没有觉得很熟悉,没错,跟前面讲过的驱动模型很像。

我们定义的命令,被u_boot_list_2_cmd_1u_boot_list_2_cmd_3两个段所包括,用于遍历,最终查找得到我们想要的命令。

4.6 Uboot命令响应流程

命令响应流程见图

超详细【Uboot驱动开发】(四)Uboot命令行模式分析(续)

 

根据4.3 命令行模式代码执行流程分析,我们可以知道,命令行模式最终执行cli_loop函数,实现与用户的交互。

void cli_loop(void){bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP);#ifdef CONFIG_HUSH_PARSERparse_file_outer();/* This point is never reached */for (;;);#elif defined(CONFIG_CMDLINE)cli_simple_loop();#elseprintf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");#endif /*CONFIG_HUSH_PARSER*/}

通过分析代码,Uboot的命令行有两种模式:一种是HUSH解析,另一种是通用解析

  • HUSH解析:调用parse_file_outer并不断循环
  • 通用解析:调用cli_simple_loop并不断循环。

 

无论哪种命令行解析,说白了就是输入输出的处理,必定会读取数据,执行相应命令,打印出对应数据

 

HUSH模式

  • 输入数据处理parse_stream
  • 输出数据处理run_list

通用模式

  • 输入数据处理cli_readline
  • 输出数据处理run_command_repeatable

具体实现流程,参照上面的流程图!

命令行模式的深入解析,准备在下节详细介绍!

目前,我们已经对命令行的整体运行流程进行梳理,熟悉整体的运行逻辑,并且能够添加自定义命令喽。

 

4.6 推荐文档

[1]:https://www.pianshen.com/article/21471247431/

[2]:https://blog.csdn.net/weixin_44895651/article/details/108211268

[3]:https://blog.51cto.com/u_2847568/4917530?b=totalstatistic

[4]:https://blog.csdn.net/SilverFOX111/article/details/86892231

[5]:https://blog.csdn.net/andy_wsj/article/details/8614905

 
另外,如果有同学想了解Emmc协议的,可以【戳这里】下载eMMC5.1官方标准协议.pdfeMMC4.51官方标准协议-中文.pdf