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

您所在的位置:网站首页 公司送礼物怎么发朋友圈 超详细【Uboot驱动开发】(四)Uboot命令行模式分析

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

#超详细【Uboot驱动开发】(四)Uboot命令行模式分析| 来源: 网络整理| 查看: 265

前几篇文章,我们也了解了Uboot的启动流程,那么这节就主要讲讲Uboot的命令行模式。另外,文章末尾还提供eMMC5.1官方标准协议.pdf和eMMC4.51官方标准协议-中文.pdf下载渠道,方便深入了解底层协议。 正文如下:4.1 如何进入命令行模式

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

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

打开我们准备好一份Uboot源码,进入menuconfig配置菜单,主要设置下列几个配置信息!CONFIG_CMDLINE:命令行模式开关CONFIG_SYS_PROMPT:命令行模式提示符CONFIG_HUSH_PARSER:使用hush shell 来对命令进行解析BOOTDELAY:设置启动延时Tip:meneconfig中查找苦难?实时/符号,输入1或2或3,直接查找指定标识。

打开之后,重新编译,并将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支持的命令大都远远超过显示的,还有好多没有打开,可以在menuconfig中,打开相应的功能,如mmc相关的,md内存相关的。

常用命令如下

version #查看uboot版本 reset #重启Uboot printenv #打印uboot环境变量 setenv name value #设置环境变量 md addr #查看内存指令 nm addr #修改内存值 mm addr #自增修改内存值 mmc dev id #选择mmc卡 mmc rescan #扫描卡 echo $name #打印环境变量 更多指令使用,可以见文末整理的文档 4.3 命令行模式代码执行流程分析 结合下面的程序执行流程图,代码,一起分析。

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

static int abortboot(int bootdelay) { int abort = 0; if (bootdelay >= 0) { if (IS_ENABLED(CONFIG_AUTOBOOT_KEYED)) abort = abortboot_key_sequence(bootdelay); else abort = 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); //延时1S printf("\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目录,没错,所有的命令实现都存放在该目录下。

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

U_BOOT_CMD:用来定义一个命令

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

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

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

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) */ 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_s cmd_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 123 Cmd test ok! argc = 3 argv = 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) #x int 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##y int 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 = 123 xy = 123456 VAR(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文件,我们可以查找得到。

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

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

4.6 Uboot命令响应流程

命令响应流程见图

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

void cli_loop(void) { bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP); #ifdef CONFIG_HUSH_PARSER parse_file_outer(); /* This point is never reached */ for (;;); #elif defined(CONFIG_CMDLINE) cli_simple_loop(); #else printf("## 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.7 推荐文档

[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官方标准协议.pdf和eMMC4.51官方标准协议-中文.pdf



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3