weiqi7777

cortex-a8 uboot系列:第十三章 uboot命令体系

0
阅读(3190)

一、            uboot命令实现代码

Uboot命令体系的实现代码在common/cmd_xxx.c中。有若干个.c文件和命令体系有关。(还有command.c  main.c

 

每个命令对应一个函数

每一个uboot的命令背后都对应一个函数。这就是uboot实现命令体系的一种思路和方法。

 

命令参数以argc&argv传给函数

有些uboot的命令还支持传递参数。也就是命令对应的函数接收的参数列表中有argcargv,然后命令体系会把执行执行命令时的命令+参数(如md 0x30000000 10)以argc3)和argvargv[0]=”md” argv[1]=”0x30000000” argv[2]=”10”)的方式传递给执行命令的函数

二、            Help命令为例

使用U_BOOT_CMD宏定义。

clip_image002

对于help命令,长说明有6行,分别用6行字符串表示,中间不能有逗号,gcc编译会自动将6行字符串合并为一个。

 

help命令背后对应的函数名叫:do_help函数(common/command.c

clip_image004

 

1.    不带参数

clip_image006

首先对参数进行判断,如果只有1个参数,说明输入是help,要打印所有的命令信息。

定义一个命令数组指针,然后循环uboot所有命令,依次赋值给数组指针cmd_array

clip_image008

对于这段代码,调试打印出下列信息。

clip_image009

&__u_boot_cmd_end - &__u_boot_cmd_start是一个指针运算,结果为(0xc3e58f88 – 0xc3e58959) / sizeof(cmd_tbl_t) = 1584 / 24 = 66。也就是共有66个命令。

__u_boot_cmd_end__u_boot_cmd_start两个变量是定义在链接脚本中的。对于汇编代码,或者c代码,是可以引用链接脚本中的变量的。

所以新建了66个大小的数组指针cmd_array,因为每一个cmd_array大小是4个字节(只要是指针,大小都是4个自己),所以cmd_array的总大小就是66*4 = 264

 

clip_image011

cmd_tbl_sinclude/command.h)是uboot命令的结构体,里面有多个参数。

clip_image013

使用extern 从外部引入两个变量__u_boot_cmd_start__u_boot_cmd_end。这两个变量是在链接脚本中定义的。

clip_image015

其实__u_boot_cmd_start__u_boot_cmd_end是两个标号,分别对应两个地址,一个uboot命令集的起始地址,另一个对应uboot命令集的结束地址。

C语言中将两个标号引入为两个cmd_tbl_s结构体的变量。在后面的程序中就可以使用这两个变量。

 

clip_image017

执行结果:

clip_image018

反汇编程序中,autoscr命令的执行函数确实是在地址0xc3e09244

clip_image020

 

clip_image022

使用冒泡排序,对数组指针cmd_array,按照命令的名字进行排序。

 

clip_image024

最后打印所有命令的用法。

2.    带参数

help命令可以带参数,表示打印执行命令的help信息。

clip_image026

对于参数超过1个,说明是要打印第二个参数的具体用法。

使用find_cmd函数(common/command.c中)查找指定命令,找到后,返回命令的指针。利用该指针,就可以知道命令的名字,命令的帮助,命令的用法,将之打印。

如果没有找到,就打印未知命令。

clip_image028

 

三、            Uboot命令解析和执行过程分析

main_loop函数(start_armboot函数的最后)开始。main_loop函数在common/main.c中。

clip_image030

 

clip_image032

使用readline函数从串口接收一行数据,接收数据在console_buffer。然后将该数据复制到lastcommand,使用run_command函数去执行命令。

 

clip_image034

 

clip_image036

         clear_ctrlc函数清除上一次输入的ctrl+cuboot中支持输入ctrl+c打断操作。所以在命令之前之前,要将这个清除。

         后面判断命令是否为空以及命令是否太长。

         将命令拷贝到cmdbuf中。

 

clip_image038

parse_line函数把”md 0x30000000 10”解析成argv[0]=”md”, argv[1]=”0x30000000”  argv[2] = ”10”

         find_cmd函数去uboot命令函数集合中搜索有没有argv[0]命令。在这里是找寻md命令。

         检查参数对应命令是否正确。

 

clip_image040

         使用函数指针执行命令函数。cmdtp->cmd

四、            Uboot的命令体系机制

1.    Uboot对命令集的管理

可能的管理方式

数组: 结构体数组,数组中每一个结构体成员就是一个命令的所有信息。

链表: 链表的每个节点data段就是一个命令结构体,所有的命令都放在一条链表上。这样解决了数组方式的不灵活。坏处是需要额外的内存开销,然后各种算法(遍历、插入、删除等)需要一定复杂度的代码执行。

 

Uboot没有使用数组或者链表,而是使用了一种新的方式来实现这个功能。

 

命令结构体:cmd_tbl_s

clip_image042

name     命令的名字,字符串格式

maxargs   命令的最大接收参数个数

repeatable 命令是否可重复执行。重复执行是uboot命令行的一种工作机制,就是直接按回车则执行上一条执行的命令

cmd       命令执行函数指针,指向命令对应的执行函数,将来执行这个命令的函数时使用这个函数指针来调用

usage:       命令的短帮助信息。对命令的简单描述

help:  命令的长帮助信息。命令的细节帮助信息,可以配置没有。

complete:    自动补全函数指针。指向命令的自动补全函数,可以配置没有。

 

总结

Uboot的命令体系在工作时,一个命令对应一个cmd_tbl_s结构体的一个实例,然后uboot支持多少个命令,就需要多少个结构体实例。Uboot的命令体系把这些结构体实例管理起来,当用户输入了一个命令时,uboot会去这些结构体实例中查找(查找方法和存储管理的方法有关)。如果找到则执行命令,如果未找到则提示命令未知。

2.    Uboot实现命令管理的思路

1)         填充1个结构体实例构成一个命令

2)         给命令结构体实例附加特定段属性(用户自定义段),链接时将带有该段属性的内容链接在一起排列(挨着的,不会夹杂其他东西,也不会丢掉一个带有这种段属性的)。

3)         Uboot重定位时将该段整体加载到DDR中。加载到DDR中的uboot镜像中带有特定段属性的这一段其实就是命令结构体的集合,有点像一个命令结构体数组。

4)         段起始地址和段结束地址(链接地址、定义在u-boot.lds中)决定了这些命令的开始地址和结束地址

3.    U_BOOT_CMD

uboot使用U_BOOT_CMD宏来进行uboot命令的定义。这个宏在common/command.h中。

clip_image044

##在宏定义中是连字符。将__u_boot_cmd_name连在一起作为一个整体。

#a是将a转化为字符串

 

如下面这个uboot命令version

clip_image046

U_BOOT_CMD展开后,就成为了

         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_version,赋了初始值,然后放入到最终bin文件的.u_boot_cmd段中。

         这样就定义了一个命令version,和函数do_version关联。最大参数为1,可重复执行,用法是version - print monitor version,没有帮助信息。

 

         .u_boot_cmd段定义在链接脚本中。

clip_image048

 

         总结:

         这个U_BOOT_CMD宏的理解,关键在于结构体变量的名字和段属性。变量名使用##连字符来生成。指定变量存放的段,这样链接器链接时将该变量放入到.u_boot_cmd段中。最后对该变量结构体进行赋值。

4.    find_cmd函数

uboot使用find_cmd函数(common/command.c)来查找uboot命令。

         clip_image050

         函数的任务就是从当前uboot的命令集中查找是否有某个命令,如果找到则返回命令的结构体指针,没有找到返回NULL

clip_image052

定义cmd_tbl_t类型的指针cmdtp_temp,指向__u_boot_cmd_start的地址处。__u_boot_cmd_start的地址为.u_boot_cmd的起始地址。

 

clip_image054

从命令中找寻 . 的位置,因为有的命令是有带 . 的。 md.b命令

最终len得到.之前的字符长度

md.b  0x30000000     len = 2

md    0x30000000    len = 2 + 4 + 10 = 16

 

clip_image056

循环从.u_boot_cmd段中的第一个cmd_tbl_t变量开始寻找,一直找到.u_boot_cmd段中的最后一个cmd_tbl_t

通过strncmp (cmd, cmdtp->name, len),判断输入的命令是否和uboot的命令是否一样。返回0表示一样,即找到命令。

 

clip_image057

如果之前计算得到的长度和uboot的命令长度一致,说明命令匹配,返回uboot命令的cmd_tbl_t类型指针。

 

clip_image059

如果长度不一致,将命令指针保存到cmdtp_temp命令临时指针。

以上的情况是解决md.b 这种类型的命令。

 

clip_image061

当是简短命令,即md.b这种类型,就把cmdtp_temp命令临时指针返回,否则返回NULL

 

总结:

查找命令的思路其实就是for循环遍历数组的思路,不同的是数组的起始位置和结束地址是用地址值来给定的,数组中的元素类型是结构体cmd_tbl_t类型。

5.    U_BOOT_CMD宏详解

这个宏其实就是定义了一个命令对应的结构体变量,这个变量名和宏的第一个参数有关,因此只要调用时传参的第一个参数不同则定义的结构体变量不会重名。并且链接器会将该变量放入到.u_boot_cmd段中。

 

一个简单的C测试。

clip_image063

使用gcc –E 预编译。查看,宏被替换为以下。

clip_image065

五、            Uboot中增加自定义命令

在已有的c文件中直接添加命令

1)         common/command.c中添加一个命令

2)         直接使用U_BOOT_CMD_宏皆可添加命令,给命令提供一个do_xxx对应的函数就可对这个命令进行使用

3)         添加完成后要重新编译编译工程,生成的u-boot.bin即可。

 

下面以实现一个自定义命令为例:

自建一个c文件并添加命令

1)         common目录下新建一个命令文件,名字如cmd_weiqi.c(相应的命名就叫weiqi,对应的函数就叫do_weiqi函数),然后在c文件中添加命令所对应的U_BOOT_CMD宏和函数。

 

clip_image067

 

2)         common里的makefile中添加上对应的cmd_weiqi.o,目的是让make在编译时能够把cmd_weiqi.c编译进去

clip_image069

对于COBJS-$(CONFIG_CMD_AMBAPP) += cmd_ambapp.o

如果宏CONFIG_CMD_AMBAPP定义,COBJS-y就加cmd_ambapp.o

CONFIG_CMD_AMBAPP如果定义,定义为y。没有定义,就是n

 

3)         重新编译烧录执行

clip_image070