U-Boot支持很多命令,通过命令我们可以实现很多操作。这里介绍U-Boot命令的实现。
一、U-Boot命令的操作
1.进入命令
U-Boot 自带命令行接口,在 U-Boot 启动期间,按任意键(如空格键)可进入 U-Boot Shell 命令行,在Shell 界面可输入 U-Boot 支持的命令,使用 U-Boot 提供的各种功能:
U-Boot 2009.08-dirty ( 5 月 18 2015 - 10:22:36)
Freescale i.MX28 family
CPU: 454 MHz
BUS: 151 MHz
EMI: 205 MHz
GPMI: 24 MHz
DRAM: 128 MB
NAND: proton id: c2 f1 80 1d c2 f1
.......
MX28 U-Boot >
2.查看命令列表
在 MX28 U-Boot >提示符下,输入?或者 help 可以查看 U-Boot 所支持的全部命令以及介绍。一个具体的 U-Boot 支持的命令的多寡,由编译时候的配置决定。事实上,由于硬件
差异,一个 U-Boot 不可能同时使用 U-Boot 所支持的全部命令。各命令的功能如下:
MX28 U-Boot > ?
? - alias for 'help'
base - print or set address offset
bdinfo - print Board Info structure
bootm - boot application image from memory
bootp - boot image via network using BOOTP/TFTP protocol
chpart - change active partition
cmp - memory compare
cp - memory copy
crc32 - checksum calculation
dhcp - boot image via network using DHCP/TFTP protocol
echo - echo args to console
go - start application at address 'addr'
help - print command description/usage
i2c - I2C sub-system
loadb - load binary file over serial line (kermit mode)
loads - load S-Record file over serial line
loady - load binary file over serial line (ymodem mode)
loop - infinite loop on address range
md - memory display
mm - memory modify (auto-incrementing address)
mtdparts - define flash/nand partitions
mtest - simple RAM read/write test
mw - memory write (fill)
nand - NAND sub-system
nboot - boot from NAND device
nm - memory modify (constant address)
ping - send ICMP ECHO_REQUEST to network host
printenv - print environment variables
rarpboot - boot image via network using RARP/TFTP protocol
reset - Perform RESET of the CPU
run - run commands in an environment variable
saveenv - save environment variables to persistent storage
saves - save S-Record file over serial line
setenv - set environment variables
tftpboot - boot image via network using TFTP protocol
ubi - ubi commands
ubifsload - load file from an UBIFS filesystem
ubifsls - list files in a directory
ubifsmount - mount UBIFS volume
version - print monitor version
输入“? 命令”或者“help 命令”能够获取相应命令的使用方法。
3.命令子类
U-Boot 命令中,有一些命令归类在某一个子类中,如 I2C、NAND FLASH、UBI 等,直接输入命令类名称,将会显示该类下面的子命令及使用说明,这类命令的使用方法是“类
+子命令”。以 ubi 子系统为例,输入 ubi 将会得到:
MX28 U-Boot > ubi
ubi - ubi commands
Usage:
ubi part [part] [offset]
- Show or set current partition (with optional VID header offset)
ubi info [l[ayout]] - Display volume and ubi layout information
ubi create[vol] volume [size] [type] - create volume name with size
ubi write[vol] address volume size - Write volume from address with size
ubi read[vol] address volume [size] - Read volume to address with size
ubi remove[vol] volume - Remove volume
[Legends]
volume: character name
size: specified in bytes
type: s[tatic] or d[ynamic] (default=dynamic)
二、U-Boot命令的实现
下面以u-boot-2009.08版本介绍U-Boot命令的实现。
1、相关文件
(1)include/command.h
定义了表示命令的数据结构struct cmd_tbl_s等。
(2)common/main.c
函数run_command。
(3)common/cmd.xxx.c
各个命令的实现。
2、U-Boot命令的配置
(1)CONFIG_SYS_MAXARGS
定义命令参数的最大数量。
(2)CONFIG_SYS_LONGHELP
如果定义了宏CONFIG_SYS_LONGHELP,则表示命令支持帮助说明。
(3)CONFIG_AUTO_COMPLETE
如果定义了宏CONFIG_AUTO_COMPLETE,则表示命令支持自动补全功能。
(4)CONFIG_CMDLINE_EDITING
如果定义了宏CONFIG_CMDLINE_EDITING,则表示命令支持命令历史功能。
3、数据结构
在include/command.h 中定义了表示命令的数据结构。
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 CONFIG_SYS_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
};
name是指向命令名字的的指针。
maxargs是命令支持的命令参数的最大个数。
repeatable指示命令是否允许自动重复执行。
cmd是指向命令具体实现的函数指针。命令实现函数有4个参数:cmdtp - 指向cmd_tbl_s结构体的指针,flag - 标志(很多命令函数可能用不上),argc - 命令参数个数,argv - 命令参数值(注意,argv[0]是命令名字,argv[1]等是命令参数。argc实际是命令参数加1)。命令实现函数定义在common目录下的cmd_xxx文件的do_xxx函数中,比如:common/cmd_eeprom.c的do_eeprom函数。
usage是指向命令的短帮助信息,即对命令的简单描述的指针。
help是指向命令的长帮助信息,即细节的帮助信息的指针。
complete是指向自动补全参数函数的指针。
4、命令的执行过程
U-Boot的运行过程是上电执行start.s后跳转至函数start_armboot,函数start_armboot完成一些初始化工作后在一个死循环main_loop中不断从控制台接收命令并进行解释执行。
void start_armboot (void)
{
......
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
}
main_loop函数中会先执行getenv(“bootcmd”),如果bootcmd环境变量设置的是启动kenel的命令,则在自动倒计时结束后如果没有字符输入,则uboot会自动执行bootcmd的命令,默认即执行启动kernel。如果自动倒计时结束前有字符输入,则进入命令行提示符状态阻塞等待用户输入命令。
s = getenv ("bootcmd");
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CONFIG_SYS_HUSH_PARSER
run_command (s, 0);
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
然后,函数main_loop不断从控制台接收命令并进行解释执行。函数main_loop有两种方式去接收命令并进行解释执行。
/*
* Main Loop for Monitor Command Processing
*/
#ifdef CONFIG_SYS_HUSH_PARSER
parse_file_outer();
/* 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 (CONFIG_SYS_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 ("<INTERRUPT>\n");
else
rc = run_command (lastcommand, flag);
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
(1)一般循环方式(未定义宏CONFIG_SYS_HUSH_PARSER)
run_command函数负责命令的解析和执行。run_command函数位于common/main.c。
run_command函数的执行过程是,分离多个命令 ----> 扩展宏定义 ----> 分析命令及其参数 ----> 查找命令 ----> 执行命令。
以组合命令upkernel为例,upkernel命令能够完成从tftp服务器加载uImage内核文件,并完成相应NAND Flash擦除、烧写内核以及设置内核参数的工作,upkernel命令相当于以下命令(tftp命令、nand erase命令、nand write命令)的组合:
tftp $(loadaddr) $(serverip):$(kernel);nand erase clean $(kerneladdr) $(kernelsize);nand write.jffs2 $(loadaddr) $(kerneladdr) $(kernelsize);
其中各个命令是以;分割开的。
run_command函数首先要做的是把各个命令分离开来。
/*
* 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 */
然后函数process_macros对$(xxx)进行扩展,即把$(xxx)替换为实际的值。
/* find macros in this token and replace them */
process_macros (token, finaltoken);
然后函数parse_line提取命令的名字和参数。
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) {
rc = -1; /* no command at all */
continue;
}
然后通过函数find_cmd在命令表中查找命令结构体cmd_tbl_s。
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) {
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
}
函数find_cmd通过命令名字,从存放命令结构体cmd_tbl_s的内存块中查找出相应的命令结构体cmd_tbl_s的地址。函数find_cmd的代码如下所示:
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和__u_boot_cmd_end定义在u-boot.lds中,__u_boot_cmd_start表示存放命令结构体cmd_tbl_s的内存块的首地址,__u_boot_cmd_end表示存放命令结构体cmd_tbl_s的内存块的结束地址。
函数find_cmd实现的关键在于如何把各命令结构体cmd_tbl_s统一连续地放在一个内存块中。各命令结构体cmd_tbl_s统一连续地放在一个内存块中是在u-boot.lds文件中实现的。
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
......
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
......
}
上面的语句.u_boot_cmd : { *(.u_boot_cmd) } 表示.u_boot_cmd段都放在.u_boot_cmd段(内存块)中。.u_boot_cmd段定义在include/command.h中。
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
凡是带有__attribute__ ((unused,section (".u_boot_cmd")))属性声明的变量都存放在.u_boot_cmd段中,并且即使该变量没有在代码中显示使用编译器也不会产生警告。
这样只要将u-boot所有命令对应的cmd_tbl_s变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_s变量时只要在 __u_boot_cmd_start 与 __u_boot_cmd_end 之间查找就可以了。
u-boot命令对应的cmd_tbl_s变量是通过宏U_BOOT_CMD实现“.u_boot_cmd”声明的。
#ifdef CONFIG_SYS_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}
#define U_BOOT_CMD_MKENT(name,maxargs,rep,cmd,usage,help) \
{#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}
#define U_BOOT_CMD_MKENT(name,maxargs,rep,cmd,usage,help) \
{#name, maxargs, rep, cmd, usage}
#endif /* CONFIG_SYS_LONGHELP */
每个命令对应的common/cmd_xxx.c文件中都用U_BOOT_CMD定义了一个与之相对应的cmd_tbl_s变量。 这里以reset命令为例。
U_BOOT_CMD(
reset, 1, 0, do_reset,
"Perform RESET of the CPU",
""
);
最后调用命令结构体cmd_tbl_s的cmd函数执行命令。
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
rc = -1;
}
(2)“hush”方式(定义宏CONFIG_SYS_HUSH_PARSER)
如果定义了CONFIG_SYS_HUSH_PARSER,命令接收和解析讲采用busybox中的hush(对应common/hush.c)工具来实现,与uboot原始的命令解析方法相比,该工具更加智能。具体分析可以参看博文https://blog.csdn.net/andy_wsj/article/details/8614905。
三、U-Boot自定义命令的添加
要在U-Boot中添加自定义命令,可以参考博文:
https://blog.csdn.net/czg13548930186/article/details/76356019?utm_source=blogxgwz0
https://blog.csdn.net/HowieXue/article/details/79836382