uboot 啟動以後會進入 3 秒倒計時,如果在 3 秒倒計時結束之前按下按下確認鍵,那麼就會進入 uboot 的命令模式,如果倒計時結束以後都沒有按下確認鍵,那麼就會自動啟動 Linux 核心,這段過程就是由 run_main_loop 函數實現,函數程式碼如下:
static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
sandbox_main_loop_init();
#endif
/* main_loop() can return to retry autoboot, if so just run it again */
for (;;)
main_loop();
return 0;
}
函數執行在 main_loop() 死迴圈,函數程式碼如下:
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
#ifndef CONFIG_SYS_GENERIC_BOARD
puts("Warning: Your board does not use generic board. Please read\n");
puts("doc/README.generic-board and take action. Boards not\n");
puts("upgraded by the late 2014 may break or be removed.\n");
#endif
#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */
cli_init();
run_preboot_environment_command();
#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */
s = bootdelay_process();
if (cli_process_fdt(&s))
cli_secure_boot_cmd(s);
autoboot_command(s);
cli_loop();
}
其中 bootstage_mark_name 函數,列印出啟動進度
定義了宏 CONFIG_VERSION_VARIABLE 的話就會執行函數 setenv,設定換將變數 ver 的值為 version_string,也就是設定版本號環境變數
cli_init 函數,初始化 shell 相關的變數
run_preboot_environment_command 函數,獲取環境變數 perboot 的內容,perboot是一些預啟動命令,一般不使用這個環境變數
bootdelay_process 函數,此函數會讀取環境變數 bootdelay 和 bootcmd 的內容,然後將 bootdelay 的值賦值給全域性變數 stored_bootdelay,返回值為環境變數 bootcmd 的值
如果 定義了 CONFIG_OF_CONTROL 的話函數 cli_process_fdt 就會實現,如果沒有定義 CONFIG_OF_CONTROL 的話函數 cli_process_fdt 直接返回一個 false。在本 uboot 中沒有定義 CONFIG_OF_CONTROL,因此 cli_process_fdt 函數返回值為 false
autoboot_command 函數,此函數就是檢查倒計時是否結束,如果倒計時自然結束那麼就執行函數
run_command_list,此函數會執行引數 s 指定的一系列命令,也就是環境變數 bootcmd 的命令,bootcmd 裡面儲存著預設的啟動命令,因此 linux 核心啟動,如果倒計時結束之前按下按鍵,那麼就會執行 cli_loop 函數,這個就是命令處理常式,負責接收好處理輸入的命令,執行對應的指令
cli_loop 函數是 uboot 的命令列處理常式,在 uboot 中輸入各種命令就是由 cli_loop 來處理的,函數原型如下:
void cli_loop(void)
{
#ifdef CONFIG_SYS_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#else
cli_simple_loop();
#endif /*CONFIG_SYS_HUSH_PARSER*/
}
定義宏 CONFIG_SYS_HUSH_PARSER,後面呼叫函數 parse_file_outer,後面是個死迴圈,不會執行到
parse_file_outer 函數如下:
#ifndef __U_BOOT__
static int parse_file_outer(FILE *f)
#else
int parse_file_outer(void)
#endif
{
int rcode;
struct in_str input;
#ifndef __U_BOOT__
setup_file_in_str(&input, f);
#else
setup_file_in_str(&input);
#endif
rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
return rcode;
}
先呼叫函數 setup_file_in_str 初始化變數 input 的成員變數,之後呼叫函數 parse_stream_outer,這個函數就是 hush shell 的命令直譯器,負責接收命令列輸入,然後解析並執行相應的命令
函數 parse_stream_outer 中使用 do-while 迴圈就是處理輸入命令,在迴圈中呼叫函數 parse_stream 進行命令解析,之後呼叫 run_list 函數來執行解析出來的命令,而函數 run_list 會經過一系列的函數呼叫,最終通過呼叫 cmd_process 函數來處理命令
cmd_process 函數用來處理命令列的命令,uboot 使用宏 U_BOOT_CMD 來定義命令,宏 U_BOOT_CMD 定義在檔案 include/command.h 中,
#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \
U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
宏 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);
ll_entry_declar 定義在檔案 include/linker_lists.h 中,定義如下
#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 中,內容如下:
#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,這兩個宏的定義如下
#ifdef CONFIG_AUTO_COMPLETE
# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif
_CMD_COMPLETE 和 _CMD_HELP 就 是 取 自 身 的 值 , 然 後 在 加 上 一 個 ‘ , ‘,
以一個具體的命令為例,來看一下 U_BOOT_CMD 經過展開以後究竟是個什麼模樣的,比如 dhcp 命令:
U_BOOT_CMD(
dhcp, 3, 1, do_dhcp,
"boot image via network using DHCP/TFTP protocol",
"[loadAddress] [[hostIPaddr:]bootfilename]"
);
將其每一步執行函數都展開
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}
dhcp 命令最終展開結果:
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}
先定義了一個 cmd_tbl_t 型別的變數,變數名為_u_boot_list_2_cmd_2_dhcp,此變數 4位元組對齊
使用 _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 中
cmd_tbl_t 是個結構體
{ "dhcp", 3, 1, do_dhcp, \
"boot image via network using DHCP/TFTP protocol", \
"[loadAddress] [[hostIPaddr:]bootfilename]",\
NULL}
就是初始化這結構體,cmd_tbl_t 結構體定義在檔案 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 * const []);
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 * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
上面的程式碼初始化後,結構體的數值如下:
_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 函數就是具體的命令處理常式
下面看一下 cmd_process 具體程式碼:
enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
int *repeatable, ulong *ticks)
{
enum command_ret_t rc = CMD_RET_SUCCESS;
cmd_tbl_t *cmdtp;
/* Look up command in command table */
cmdtp = find_cmd(argv[0]);
if (cmdtp == NULL) {
printf("Unknown command '%s' - try 'help'\n", argv[0]);
return 1;
}
/* found - check max args */
if (argc > cmdtp->maxargs)
rc = CMD_RET_USAGE;
#if defined(CONFIG_CMD_BOOTD)
/* avoid "bootd" recursion */
else if (cmdtp->cmd == do_bootd) {
if (flag & CMD_FLAG_BOOTD) {
puts("'bootd' recursion detected\n");
rc = CMD_RET_FAILURE;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif
/* If OK so far, then do the command */
if (!rc) {
if (ticks)
*ticks = get_timer(0);
rc = cmd_call(cmdtp, flag, argc, argv);
if (ticks)
*ticks = get_timer(*ticks);
*repeatable &= cmdtp->repeatable;
}
if (rc == CMD_RET_USAGE)
rc = cmd_usage(cmdtp);
return rc;
}
先呼叫函數 find_cmd 在命令表中找到指定的命令,函數原型如下:
cmd_tbl_t *find_cmd(const char *cmd)
{
cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
const int len = ll_entry_count(cmd_tbl_t, cmd);
return find_cmd_tbl(cmd, start, len);
}
傳入 cmd 命令,然後通過函數 ll_entry_start 得到陣列的第一個元素,也就是命令表起始地址,然後通過函數 ll_entry_count 得到陣列長度,也就是命令表的長度,最終通過函數 find_cmd_tbl 在命令表中找到所需的命令,每個命令都有一個 name 成員,所以將引數 cmd 與命令表中每個成員的 name 欄位都對比一下,如果相等的話就說明找到了這個命令,找到以後就返回這個命令
最後呼叫函數 cmd_call 來執行具體的命令,執行指令函數如下:
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
int result;
result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
if (result)
debug("Command failed, result=%d\n", result);
return result;
}
所以具體的呼叫流程就是處理字串,建立對應的 cmd_tbl_t 儲存單元,初始化 cmd_tbl_t 變數,然後 cmd_process 中會查詢檢測 cmd_tbl 的返回值,然後呼叫 cmd_call 函數執行指令,如果返回值為 CMD_RET_USAGE 的話就會呼叫 cmd_usage 函數輸出命令的用法,其實就是輸出 cmd_tbl_t 的 usage 成員變數
以上就是 U-Boot 流程分析的最後一部分!