1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号公众号,获取更多资料:正点原子
第32.2.5小节讲解了board_init_f函数,在此函数里面会调用一系列的函数来初始化一些外设和gd的成员变量。但是board_init_f并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数board_init_r来完成的,board_init_r函数定义在文件common/board_r.c中,代码如下:
示例代码32.2.8.1 board_r.c代码段
991void board_init_r(gd_t *new_gd, ulong dest_addr)
992{
993 #ifdef CONFIG_NEEDS_MANUAL_RELOC
994int i;
995 #endif
996
997 #ifdef CONFIG_AVR32
998 mmu_init_r(dest_addr);
999 #endif
1000
1001 #if!defined(CONFIG_X86)&&!defined(CONFIG_ARM)&&!defined(CONFIG_ARM64)
1002 gd = new_gd;
1003 #endif
1004
1005 #ifdef CONFIG_NEEDS_MANUAL_RELOC
1006for(i =0; i < ARRAY_SIZE(init_sequence_r); i++)
1007 init_sequence_r[i]+= gd->reloc_off;
1008 #endif
1009
1010if(initcall_run_list(init_sequence_r))
1011 hang();
1012
1013/* NOTREACHED - run_main_loop() does not return */
1014 hang();
1015}
第1010行调用initcall_run_list函数来执行初始化序列init_sequence_r,init_sequence_r是一个函数集合,init_sequence_r也定义在文件common/board_r.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_r定义如下:
示例代码32.2.8.2 board_r.c代码段
1 init_fnc_t init_sequence_r[]={
2 initr_trace,
3 initr_reloc,
4 initr_caches,
5 initr_reloc_global_data,
6 initr_barrier,
7 initr_malloc,
8 initr_console_record,
9 bootstage_relocate,
10 initr_bootstage,
11 board_init,/* Setup chipselects */
12 stdio_init_tables,
13 initr_serial,
14 initr_announce,
15 INIT_FUNC_WATCHDOG_RESET
16 INIT_FUNC_WATCHDOG_RESET
17 INIT_FUNC_WATCHDOG_RESET
18 power_init_board,
19 initr_flash,
20 INIT_FUNC_WATCHDOG_RESET
21 initr_nand,
22 initr_mmc,
23 initr_env,
24 INIT_FUNC_WATCHDOG_RESET
25 initr_secondary_cpu,
26 INIT_FUNC_WATCHDOG_RESET
27 stdio_add_devices,
28 initr_jumptable,
29 console_init_r,/* fully init console as a device */
30 INIT_FUNC_WATCHDOG_RESET
31 interrupt_init,
32 initr_enable_interrupts,
33 initr_ethaddr,
34 board_late_init,
35 INIT_FUNC_WATCHDOG_RESET
36 INIT_FUNC_WATCHDOG_RESET
37 INIT_FUNC_WATCHDOG_RESET
38 initr_net,
39 INIT_FUNC_WATCHDOG_RESET
40 run_main_loop,
41};
第2行,initr_trace函数,如果定义了宏CONFIG_TRACE的话就会调用函数trace_init,初始化和调试跟踪有关的内容。
第3行,initr_reloc函数用于设置gd->flags,标记重定位完成。
第4行,initr_caches函数用于初始化cache,使能cache。
第5行,initr_reloc_global_data函数,初始化重定位后gd的一些成员变量。
第6行,initr_barrier函数,I.MX6ULL未用到。
第7行,initr_malloc函数,初始化malloc。
第8行,initr_console_record函数,初始化控制台相关的内容,I.MX6ULL未用到,空函数。
第9行,bootstage_relocate函数,启动状态重定位。
第10行,initr_bootstage函数,初始化bootstage什么的。
第11行,board_init函数,板级初始化,包括74XX芯片,I2C、FEC、USB和QSPI等。这里执行的是mx6ull_alientek_emmc.c文件中的board_init函数。
第12行,stdio_init_tables函数,stdio相关初始化。
第13行,initr_serial函数,初始化串口。
第14行,initr_announce函数,与调试有关,通知已经在RAM中运行。
第18行,power_init_board函数,初始化电源芯片,正点原子的I.MX6ULL开发板没有用到。
第19行,initr_flash函数,对于I.MX6ULL而言,没有定义宏CONFIG_SYS_NO_FLASH的话函数initr_flash才有效。但是mx6_common.h中定义了宏CONFIG_SYS_NO_FLASH,所以此函数无效。
第21行,initr_nand函数,初始化NAND,如果使用NAND版本核心板的话就会初始化NAND。
第22行,initr_mmc函数,初始化EMMC,如果使用EMMC版本核心板的话就会初始化EMMC,串口输出如图32.2.8.1所示信息:
图32.2.8.1 EMMC信息输出
从图32.2.8.1可以看出,此时有两个EMCM设备,FSL_SDHC:0和FSL_SDHC:1。
第23行,initr_env函数,初始化环境变量。
第25行,initr_secondary_cpu函数,初始化其他CPU核,I.MX6ULL只有一个核,因此此函数没用。
第27行,stdio_add_devices函数,各种输入输出设备的初始化,如LCD driver,I.MX6ULL使用drv_video_init函数初始化LCD。会输出如图32.2.8.2所示信息:
图32.2.8.2 LCD信息
第28行,initr_jumptable函数,初始化跳转表。
第29行,console_init_r函数,控制台初始化,初始化完成以后此函数会调用stdio_print_current_devices函数来打印出当前的控制台设备,如图32.2.8.3所示:
图32.2.8.3 控制台信息
第31行,interrupt_init函数,初始化中断。
第32行,initr_enable_interrupts函数,使能中断。
第33行,initr_ethaddr函数,初始化网络地址,也就是获取MAC地址。读取环境变量“ethaddr”的值。
第34行,board_late_init函数,板子后续初始化,此函数定义在文件mx6ull_alientek_emmc.c中,如果环境变量存储在EMMC或者SD卡中的话此函数会调用board_late_mmc_env_init函数初始化EMMC/SD。会切换到正在时候用的emmc设备,代码如图32.2.8.4所示:
图32.2.8.4 board_late_mmc_env_init函数
图32.2.8.4中的第46行和第47行就是运行“mmcdevxx”命令,用于切换到正在使用的EMMC设备,串口输出信息如图32.2.8.5所示:
图32.2.8.5 切换mmc设备
第38行,initr_net函数,初始化网络设备,函数调用顺序为:initr_net->eth_initialize->board_eth_init(),串口输出如图32.2.8.6所示信息:
图32.2.8.6 网络信息输出
第40行,run_main_loop行,主循环,处理命令。
32.2.9 run_main_loop函数详解uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内核,这个功能就是由run_main_loop函数来完成的。run_main_loop函数定义在文件common/board_r.c中,函数内容如下:
示例代码32.2.9.1 board_r.c文件代码段
753staticint run_main_loop(void)
754{
755 #ifdef CONFIG_SANDBOX
756 sandbox_main_loop_init();
757 #endif
758/* main_loop() can return to retry autoboot, if so just run it again */
759 for(;;)
760 main_loop();
761 return0;
762}
第7行和第8行是个死循环,“for(;;)”和“while(1)”功能一样,死循环里面就一个main_loop函数,main_loop函数定义在文件common/main.c里面,代码如下:
示例代码32.2.9.2 main.c文件代码段
43/* We come here after U-Boot is initialised and ready to process commands */
44void main_loop(void)
45{
46 constchar*s;
47
48 bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP,"main_loop");
49
50 #ifndef CONFIG_SYS_GENERIC_BOARD
51 puts("Warning: Your board does not use generic board. Please read\n");
52 puts("doc/README.generic-board and take action. Boards not\n");
53 puts("upgraded by the late 2014 may break or be removed.\n");
54 #endif
55
56 #ifdef CONFIG_VERSION_VARIABLE
57 setenv("ver", version_string);/* set version variable */
58 #endif /* CONFIG_VERSION_VARIABLE */
59
60 cli_init();
61
62 run_preboot_environment_command();
63
64 #if defined(CONFIG_UPDATE_TFTP)
65 update_tftp(0UL,NULL,NULL);
66 #endif /* CONFIG_UPDATE_TFTP */
67
68 s = bootdelay_process();
69 if(cli_process_fdt(&s))
70 cli_secure_boot_cmd(s);
71
72 autoboot_command(s);
73
74 cli_loop();
75}
第48行,调用bootstage_mark_name函数,打印出启动进度。
第57行,如果定义了宏CONFIG_VERSION_VARIABLE的话就会执行函数setenv,设置换将变量ver的值为version_string,也就是设置版本号环境变量。version_string定义在文件cmd/version.c中,定义如下:
const char __weak version_string[] = U_BOOT_VERSION_STRING;
U_BOOT_VERSION_STRING是个宏,定义在文件include/version.h,如下:
#define U_BOOT_VERSION_STRING U_BOOT_VERSION " (" U_BOOT_DATE " - " \
U_BOOT_TIME " " U_BOOT_TZ "电脑;)" CONFIG_IDENT_STRING
U_BOOT_VERSION 定义在文件include/generated/version_autogenerated.h中,文件version_autogenerated.h内如如下:
示例代码32.2.9.4 version_autogenerated.h文件代码
1 #define PLAIN_VERSION "2016.03"
2 #define U_BOOT_VERSION "U-Boot " PLAIN_VERSION
3 #define CC_VERSION_STRING "arm-linux-gnueabihf-gcc (Linaro GCC 4.9-2017.01) 4.9.4"
4 #define LD_VERSION_STRING "GNU ld (Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git"
可以看出,U_BOOT_VERSION为“U-boot 2016.03”,
U_BOOT_DATE、U_BOOT_TIME和U_BOOT_TZ这定义在文件include/generated/timestamp_autogenerated.h中,如下所示:
示例代码32.2.9.5 timestamp_autogenerated.h文件代码
1 #define U_BOOT_DATE "Apr 25 2019"
2 #define U_BOOT_TIME "21:10:53"
3 #define U_BOOT_TZ "+0800"
4 #define U_BOOT_DMI_DATE "04/25/2019"
宏CONFIG_IDENT_STRING为空,所以U_BOOT_VERSION_STRING为“U-Boot 电脑 2016.03 (Apr 25 2019 - 21:10:53 +0800)”,进入uboot命令模式,输入命令“version”查看版本号,如图32.2.9.1所示:
图32.2.9.1 版本查询
图32.2.9.1中的第一行就是uboot版本号,和我们分析的一致。
接着回到示例代码32.2.9.2中,第60行,cli_init函数,跟命令初始化有关,初始化hushshell相关的变量。
第62行,run_preboot_environment_command函数,获取环境变量perboot的内容,perboot是一些预启动命令,一般不使用这个环境变量。
第68行,bootdelay_process函数,此函数会读取环境变量bootdelay和bootcmd的内容,然后将bootdelay的值赋值给全局变量stored_bootdelay,返回值为环境变量bootcmd的值。
第69行,如果定义了CONFIG_OF_CONTROL的话函数cli_process_fdt就会实现,如果没有定义CONFIG_OF_CONTROL的话函数cli_process_fdt直接返回一个false。在本uboot中没有定义CONFIG_OF_CONTROL,因此cli_process_fdt函数返回值为false。
第72行,autoboot_command函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?此函数定义在文件common/autoboot.c中,内容如下:
示例代码32.2.9.5 auboboot.c文件代码段
380void 电脑 autoboot_command(constchar*s)
381{
382 debug("### main_loop: bootcmd=\"%s\"\n", s ? s :"<UNDEFINED>");
383
384if(stored_bootdelay !=-1&& s &&!abortboot(stored_bootdelay)){
385 #if defined(CONFIG_AUTOBOOT_KEYED)&&!defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
386int prev = disable_ctrlc(1);/* disable Control C checking */
387 #endif
388
389 run_command_list(s,-1,0);
390
391 #if defined(CONFIG_AUTOBOOT_KEYED)&&!defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
392 disable_ctrlc(prev);/* restore Control C checking */
393 #endif
394}
395
396 #ifdef CONFIG_MENUKEY
397if(menukey == CONFIG_MENUKEY){
398 s = getenv("menucmd");
399if(s)
400 run_command_list(s,-1,0);
401}
402 #endif /* CONFIG_MENUKEY */
403}
可以看出,autoboot_command函数里面有很多条件编译,条件编译一多就不利于我们阅读程序(所以正点原子的例程基本是不用条件编译的,就是为了方便大家阅读源码)!宏CONFIG_AUTOBOOT_KEYED、CONFIG_AUTOBOOT_KEYED_CTRLC和CONFIG_MENUKEY这三个宏在I.MX6ULL里面没有定义,所以讲示例代码32.2.9.5进行精简,得到如下代码:
示例代码32.2.9.6 autoboot_command函数精简版本
1void autoboot_command(constchar*s)
2{
3if(stored_bootdelay !=-1&& s &&!abortboot(stored_bootdelay)){
4 run_command_list(s,-1,0);
5}
6}
当一下三条全部成立的话,就会执行函数run_command_list。
①、stored_bootdelay不等于-1。
②、s不为空。
③、函数abortboot返回值为0。
stored_bootdelay等于环境变量bootdelay的值;s是环境变量bootcmd的值,一般不为空,因此前两个成立,就剩下了函数abortboot的返回值,abortboot函数也定义在文件common/autoboot.c中,内容如下:
示例代码32.2.9.7 abortboot函数
283staticint abortboot(int bootdelay)
284{
285 #ifdef CONFIG_AUTOBOOT_KEYED
286return abortboot_keyed(bootdelay);
287 #else
288return abortboot_normal(bootdelay);
289 #endif
290}
因为宏CONFIG_AUTOBOOT_KEYE未定义,因此执行函数abortboot_normal,好吧,绕来绕去的!接着来看函数abortboot_normal,此函数也定义在文件common/autoboot.c中,内容如下:
示例代码32.2.9.8 abortboot_normal函数
225staticint abortboot_normal(int bootdelay)
226{
227int abort =0;
228unsignedlong ts;
229
230 #ifdef CONFIG_MENUPROMPT
231 printf(CONFIG_MENUPROMPT);
232 #else
233if(bootdelay >=0)
234 printf("Hit any key to stop autoboot: %2d ", bootdelay);
235 #endif
236
237 #if defined CONFIG_ZERO_BOOTDELAY_CHECK
238/*
239 * Check if key already pressed
240 * Don't check if bootdelay < 0
241 */
242if(bootdelay >=0){
243if(tstc()){/* we got a key press */
244(void) getc();/* consume input */
245 puts("\b\b\b 0");
246 abort =1;/* don't auto boot */
247}
248}
249 #endif
250
251while((bootdelay >0)&&(!abort)){
252--bootdelay;
253/* delay 1000 ms */
254 ts = get_timer(0);
255do{
256if(tstc()){/* we got a key press */
257 abort =1;/* don't auto boot */
258 bootdelay =0;/* no more delay */
259 # ifdef CONFIG_MENUKEY
260 menukey = getc();
261 # else
262(void) getc();/* consume input */
263 # endif
264break;
265}
266 udelay(10000);
267}while(!abort && get_timer(ts)<1000);
268
269 printf("\b\b\b%2d ", bootdelay);
270}
271
272 putc('\n');
273
274 #ifdef CONFIG_SILENT_CONSOLE
275if(abort)
276 gd->flags &=~GD_FLG_SILENT;
277 #endif
278
279return abort;
280}
函数abortboot_normal同样很多条件编译,删除掉条件编译相关代码后abortboot_normal函数内容如下:
示例代码32.2.9.9 abortboot_normal函数精简
1staticint abortboot_normal(int bootdelay)
2{
3 int abort =0;
4 unsignedlong ts;
5
6 if(bootdelay >=0)
7 printf("Hit any key to stop autoboot: %2d ", bootdelay);
8
9 while((bootdelay >0)&&(!abort)){
10 --bootdelay;
11 /* delay 1000 ms */
12 ts = get_timer(0);
13 do{
14 if(tstc()){/* we got a key press */
15 abort =1;/* don't auto boot */
16 bootdelay =0;/* no more delay */
17 (void) getc();/* consume input */
18 break;
19 }
20 udelay(10000);
21 }while(!abort && get_timer(ts)<1000);
22
23 printf("\b\b\b%2d ", bootdelay);
24 }
25 putc('\n');
26 return abort;
27}
第3行的变量abort是函数abortboot_normal的返回值,默认值为0。
第7行通过串口输出“Hit any key to stop autoboot”字样,如图32.2.9.2所示:
图32.2.9.2 倒计时
第9~21行就是倒计时的具体实现。
第14行判断键盘是否有按下,也就是是否打断了倒计时,如果键盘按下的话就执行相应的分支。比如设置abort为1,设置bootdelay为0等,最后跳出倒计时循环。
第26行,返回abort的值,如果倒计时自然结束,没有被打断abort就为0,否则的话abort的值就为1。
回到示例代码32.2.9.6的autoboot_command函数中,如果倒计时自然结束那么就执行函数run_command_list,此函数会执行参数s指定的一系列命令,也就是环境变量bootcmd的命令,bootcmd里面保存着默认的启动命令,因此linux内核启动!这个就是uboot中倒计时结束以后自动启动linux内核的原理。如果倒计时结束之前按下了键盘上的按键,那么run_command_list函数就不会执行,相当于autoboot_command是个空函数。
回到“遥远”的示例代码32.2.9.2中的main_loop函数中,如果倒计时结束之前按下按键,那么就会执行第74行的cli_loop函数,这个就是命令处理函数,负责接收好处理输入的命令。
32.2.10 cli_loop函数详解cli_loop函数是uboot的命令行处理函数,我们在uboot中输入各种命令,进行各种操作就是有cli_loop来处理的,此函数定义在文件common/cli.c中,函数内容如下:
示例代码32.2.10.1 cli.c文件代码段
202void cli_loop(void)
203{
204 #ifdef CONFIG_SYS_HUSH_PARSER
205 parse_file_outer();
206/* This point is never reached */
207for(;;);
208 #else
209 cli_simple_loop();
210 #endif /*CONFIG_SYS_HUSH_PARSER*/
211}
在文件include/configs/mx6_common.h中有定义宏CONFIG_SYS_HUSH_PARSER,而正点原子的I.MX6ULL开发板配置头文件mx6ullevk.h里面会引用mx_common.h这个头文件,因此宏CONFIG_SYS_HUSH_PARSER有定义。
第205行调用函数parse_file_outer。
第207行是个死循环,永远不会执行到这里。
函数parse_file_outer定义在文件common/cli_hush.c中,去掉条件编译内容以后的函数内容如下:
示例代码32.2.10.2 parse_file_outer函数精简
1int parse_file_outer(void)
2{
3 int rcode;
4 struct in_str input;
5
6 setup_file_in_str(&input);
7 rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
8 return rcode;
9}
第3行调用函数setup_file_in_str初始化变量input的成员变量。
第4行调用函数parse_stream_outer,这个函数就是hushshell的命令解释器,负责接收命令行输入,然后解析并执行相应的命令,函数parse_stream_outer定义在文件common/cli_hush.c中,精简版的函数内容如下:
示例代码32.2.10.3 parse_stream_outer函数精简
1staticint parse_stream_outer(struct in_str *inp,int flag)
2{
3struct p_context ctx;
4 o_string temp=NULL_O_STRING;
5int rcode;
6int code =1;
7do{
8......
9 rcode = parse_stream(&temp,&ctx, inp,
10 flag & FLAG_CONT_ON_NEWLINE ?-1:'\n');
11......
12if(rcode !=1&& ctx.old_flag ==0){
13......
14 run_list(ctx.list_head);
15......
16}else{
17......
18}
19 b_free(&temp);
20/* loop on syntax errors, return on EOF */
21}while(rcode !=-1&&!(flag & FLAG_EXIT_FROM_LOOP)&&
22(inp->peek != static_peek || b_peek(inp)));
23return0;
24}
第7~21行中的do-while循环就是处理输入命令的。
第9行调用函数parse_stream进行命令解析。
第14行调用调用run_list函数来执行解析出来的命令。
函数run_list会经过一系列的函数调用,最终通过调用cmd_process函数来处理命令,过程如下:
示例代码32.2.10.4 run_list执行流程
1staticint run_list(struct pipe *pi)
2{
3 int rcode=0;
4
5 rcode = run_list_real(pi);
6 ......
7 return rcode;
8}
9
10staticint run_list_real(struct pipe *pi)
11{
12 char*save_name =NULL;
13 ......
14 int if_code=0, next_if_code=0;
15 ......
16 rcode = run_pipe_real(pi);
17 ......
18 return rcode;
19}
20
21staticint run_pipe_real(struct pipe *pi)
22{
23 int i;
24
25 int nextin;
26 int flag = do_repeat ? CMD_FLAG_REPEAT :0;
27 struct child_prog *child;
28 char*p;
29 ......
30 if(pi->num_progs ==1) child =&(pi->progs[0]);
31 ......
32 return rcode;
33 }elseif(pi->num_progs ==1&& pi->progs[0].argv !=NULL){
34 ......
35 /* Process the command */
36 return cmd_process(flag, child->argc, child->argv,
37 &flag_repeat,NULL);
38 }
39
40 return-1;
41}
第5行,run_list调用run_list_real函数。
第16行,run_list_real函数调用run_pipe_real函数。
第36行,run_pipe_real函数调用cmd_process函数。
最终通过函数cmd_process来处理命令,接下来就是分析cmd_process函数。
32.2.11cmd_process函数详解在学习cmd_process之前先看一下uboot中命令是如何定义的。uboot使用宏U_BOOT_CMD来定义命令,宏U_BOOT_CMD定义在文件include/command.h中,定义如下:
示例代码32.2.11.1 U_BOOT_CMD宏定义
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
可以看出U_BOOT_CMD是U_BOOT_CMD_COMPLETE的特例,将U_BOOT_CMD_COMPLETE的最后一个参数设置成NULL就是U_BOOT_CMD。宏U_BOOT_CMD_COMPLETE如下:
示例代码32.2.11.2 U_BOOT_CMD_COMPLETE宏定义
#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \
ll_entry_declare(cmd_tbl_t, _name, cmd) = \
U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp);
宏U_BOOT_CMD_COMPLETE又用到了ll_entry_declare和U_BOOT_CMD_MKENT_COMPLETE。ll_entry_declar定义在文件include/linker_lists.h中,定义如下:
示例代码32.2.11.3 ll_entry_declare宏定义
#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)))
_type为cmd_tbl_t,因此ll_entry_declare就是定义了一个cmd_tbl_t变量,这里用到了C语言中的“##”连接符符。其中的“##_list”表示用_list的值来替换,“##_name”就是用_name的值来替换。
宏U_BOOT_CMD_MKENT_COMPLETE定义在文件include/command.h中,内容如下:
示例代码32.2.11.4 U_BOOT_CMD_MKENT_COMPLETE宏定义
#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \
_usage, _help, _comp) \
{ #_name, _maxargs, _rep, _cmd, _usage, \
_CMD_HELP(_help) _CMD_COMPLETE(_comp) }
上述代码中的“#”表示将_name传递过来的值字符串化,U_BOOT_CMD_MKENT_COMPLETE又用到了宏_CMD_HELP和_CMD_COMPLETE,这两个宏的定义如下:
示例代码32.2.11.5 _CMD_HELP和_CMD_COMPLETE宏定义
1 #ifdef CONFIG_AUTO_COMPLETE
2 # define _CMD_COMPLETE(x) x,
3 #else
4 # define _CMD_COMPLETE(x)
5 #endif
6 #ifdef CONFIG_SYS_LONGHELP
7 # define _CMD_HELP(x) x,
8 #else
9 # define _CMD_HELP(x)
10 #endif
可以看出,如果定义了宏CONFIG_AUTO_COMPLETE和CONFIG_SYS_LONGHELP的话,_CMD_COMPLETE和_CMD_HELP就是取自身的值,然后在加上一个‘,’。CONFIG_AUTO_COMPLETE和CONFIG_SYS_LONGHELP这两个宏有定义在文件mx6_common.h中。
U_BOOT_CMD宏的流程我们已经清楚了(一个U_BOOT_CMD宏就如此的绕来绕去的!),我们就以一个具体的命令为例,来看一下U_BOOT_CMD经过展开以后究竟是个什么模样的。以命令dhcp为例,dhcp命令定义如下:
示例代码32.2.11.6 dhcp命令宏定义
U_BOOT_CMD(
dhcp,3,1, do_dhcp,
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
将其展开,结果如下:
示例代码32.2.11.7 dhcp命令展开
U_BOOT_CMD(
dhcp,3,1, do_dhcp,
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
1、将U_BOOT_CMD展开后为:
U_BOOT_CMD_COMPLETE(dhcp,3,1, do_dhcp,
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]",
NULL)
2、将U_BOOT_CMD_COMPLETE展开后为:
ll_entry_declare(cmd_tbl_t, dhcp, cmd)= \
U_BOOT_CMD_MKENT_COMPLETE(dhcp,3,1, do_dhcp, \
"boot image via network using DHCP/TFTP protocol", \
"[loadAddress] [[hostIPaddr:]bootfilename]", \
NULL);
3、将ll_entry_declare和U_BOOT_CMD_MKENT_COMPLETE展开后为:
cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \
__attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) \
{"dhcp",3,1, do_dhcp, \
"boot image via network using DHCP/TFTP protocol", \
"[loadAddress] [[hostIPaddr:]bootfilename]",\
NULL}
从示例代码32.2.11.7可以看出,dhcp命令最终展开结果为:
示例代码32.2.11.8 dhcp命令最终结果
1 cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \
2 __attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) \
3{"dhcp",3,1, do_dhcp, \
4"boot image via network using DHCP/TFTP protocol", \
5"[loadAddress] [[hostIPaddr:]bootfilename]",\
6NULL}
第1行定义了一个cmd_tbl_t类型的变量,变量名为_u_boot_list_2_cmd_2_dhcp,此变量4字节对齐。
第2行,使用__attribute__关键字设置变量_u_boot_list_2_cmd_2_dhcp存储在.u_boot_list_2_cmd_2_dhcp段中。u-boot.lds链接脚本中有一个名为“.u_boot_list”的段,所有.u_boot_list开头的段都存放到.u_boot.list中,如图32.2.11.1所示:
图32.2.11.1 u-boot.lds中的.u_boot_list段
因此,第2行就是设置变量_u_boot_list_2_cmd_2_dhcp的存储位置。
第3~6行,cmd_tbl_t是个结构体,因此第3-6行是初始化cmd_tbl_t这个结构体的各个成员变量。cmd_tbl_t结构体定义在文件include/command.h中,内容如下:
示例代码32.2.11.9 cmd_tbl_t结构体
30struct cmd_tbl_s {
31 char *name; /* Command Name */
32 int maxargs; /* maximum number of arguments */
33 int repeatable; /* autorepeat allowed? */
34 /* Implementation function */
35 int(*cmd)(struct cmd_tbl_s *,int,int,char*const[]);
36 char*usage; /* Usage message (short) */
37 #ifdef CONFIG_SYS_LONGHELP
38 char*help; /* Help message (long) */
39 #endif
40 #ifdef CONFIG_AUTO_COMPLETE
41 /* do auto completion on the arguments */
42 int(*complete)(int argc,char*const argv[],char last_char,int maxv,char*cmdv[]);
43 #endif
44};
45
46typedefstruct cmd_tbl_s cmd_tbl_t;
结合实例代码32.2.11.8,可以得出变量_u_boot_list_2_cmd_2_dhcp的各个成员的值如下所示:
_u_boot_list_2_cmd_2_dhcp.name = "dhcp"
_u_boot_list_2_cmd_2_dhcp.maxargs = 3
_u_boot_list_2_cmd_2_dhcp.repeatable = 1
_u_boot_list_2_cmd_2_dhcp.cmd = do_dhcp
_u_boot_list_2_cmd_2_dhcp.usage = "boot image via network using DHCP/TFTP protocol"
_u_boot_list_2_cmd_2_dhcp.help = "[loadAddress] [[hostIPaddr:]bootfilename]"
_u_boot_list_2_cmd_2_dhcp.complete = NULL
当我们在uboot的命令行中输入“dhcp”这个命令的时候,最终执行的是do_dhcp这个函数。总结一下,uboot中使用U_BOOT_CMD来定义一个命令,最终的目的就是为了定义一个cmd_tbl_t类型的变量,并初始化这个变量的各个成员。uboot中的每个命令都存储在.u_boot_list段中,每个命令都有一个名为do_xxx(xxx为具体的命令名)的函数,这个do_xxx函数就是具体的命令处理函数。
了解了uboot中命令的组成以后,再来看一下cmd_process函数的处理过程,cmd_process函数定义在文件common/command.c中,函数内容如下:
示例代码32.2.11.10 command.c文件代码段
500enum command_ret_t cmd_process(int flag,int argc,
501char*const argv[],int*repeatable, ulong *ticks)
502{
503enum command_ret_t rc = CMD_RET_SUCCESS;
504 cmd_tbl_t *cmdtp;
505
506/* Look up command in command table */
507 cmdtp = find_cmd(argv[0]);
508if(cmdtp ==NULL){
509 printf("Unknown command '%s' - try 'help'\n", argv[0]);
510return1;
511}
512
513/* found - check max args */
514if(argc > cmdtp->maxargs)
515 rc = CMD_RET_USAGE;
516
517 #if defined(CONFIG_CMD_BOOTD)
518/* avoid "bootd" recursion */
519elseif(cmdtp->cmd == do_bootd){
520if(flag & CMD_FLAG_BOOTD){
521 puts("'bootd' recursion detected\n");
522 rc = CMD_RET_FAILURE;
523}else{
524 flag |= CMD_FLAG_BOOTD;
525}
526}
527 #endif
528
529/* If OK so far, then do the command */
530if(!rc){
531if(ticks)
532*ticks = get_timer(0);
533 rc = cmd_call(cmdtp, flag, argc, argv);
534if(ticks)
535*ticks = get_timer(*ticks);
536*repeatable &= cmdtp->repeatable;
537}
538if(rc == CMD_RET_USAGE)
539 rc = cmd_usage(cmdtp);
540return rc;
541}
第507行,调用函数find_cmd在命令表中找到指定的命令,find_cmd函数内容如下:
示例代码32.2.11.10 command.c文件代码段
118 cmd_tbl_t *find_cmd(constchar*cmd)
119{
120 cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
121constint len = ll_entry_count(cmd_tbl_t, cmd);
122return find_cmd_tbl(cmd, start, len);
123}
参数cmd就是所查找的命令名字,uboot中的命令表其实就是cmd_tbl_t结构体数组,通过函数ll_entry_start得到数组的第一个元素,也就是命令表起始地址。通过函数ll_entry_count得到数组长度,也就是命令表的长度。最终通过函数find_cmd_tbl在命令表中找到所需的命令,每个命令都有一个name成员,所以将参数cmd与命令表中每个成员的name字段都对比一下,如果相等的话就说明找到了这个命令,找到以后就返回这个命令。
回到示例代码32.2.11.10的cmd_process函数中,找到命令以后肯定就要执行这个命令了,第533行调用函数cmd_call来执行具体的命令,cmd_call函数内容如下:
示例代码32.2.11.11 command.c文件代码段
490staticint cmd_call(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[])
491{
492int result;
493
494 result =(cmdtp->cmd)(cmdtp, flag, argc, argv);
495if(result)
496 debug("Command failed, result=%d\n", result);
497return result;
498}
在前面的分析中我们知道,cmd_tbl_t的cmd成员就是具体的命令处理函数,所以第494行调用cmdtp的cmd成员来处理具体的命令,返回值为命令的执行结果。
cmd_process中会检测cmd_tbl的返回值,如果返回值为CMD_RET_USAGE的话就会调用cmd_usage函数输出命令的用法,其实就是输出cmd_tbl_t的usage成员变量。