上片博文總結出了u-boot的工作流程,今天我們來分析,u-boot的兩個比較重要的內容
1. U-boot命令的實現
2. U-boot如何啓動內核
l 命令實現
我們的u-boot可以解析輸入的命令,比如print、setenv、saveenv等命令,我們下來對其的實現進行分析。
我們昨天分析到BL2最後停在了main_loop處,那麼我們輸入的命令肯定也是在這個函數中實現的,我們找到該函數,在main_loop函數中run_command函數很容易引起我們的關注,跳到該函數進行分析,在該函數中有下面幾個比較重要的點
1. 從註釋我們很容易知道這段代碼是在對命令進行分離,並且u-boot支持’;’分離命令。
1 /* 2 * Find separator, or string end 3 * Allow simple escape of ';' by writing "\;" 4 */ 5 for (inquotes = 0, sep = str; *sep; sep++) { 6 if ((*sep=='\'') && 7 (*(sep-1) != '\\')) 8 inquotes=!inquotes; 9 10 if (!inquotes && 11 (*sep == ';') && /* separator */ 12 ( sep != str) && /* past string start */ 13 (*(sep-1) != '\\')) /* and NOT escaped */ 14 break; 15 }
2. 分離參數
1 /* Extract arguments */ 2 if ((argc = parse_line (finaltoken, argv)) == 0) { 3 rc = -1; /* no command at all */ 4 continue; 5 }
3. 用第一個參數argv[0]在命令列表中尋找對應的命令,並返回一個cmd_tbl_t類型的實例。我們可以猜到這個結構體應該保函了有關命令的一系列內容。
1 /* Look up command in command table */ 2 if ((cmdtp = find_cmd(argv[0])) == NULL) { 3 printf ("Unknown command '%s' - try 'help'\n", argv[0]); 4 rc = -1; /* give up after bad command */ 5 continue; 6 }
n 我們先看find_cmd,通過代碼跟蹤我們會在find_cmd_tbl函數中找到核心代碼
1 for (cmdtp = table; 2 cmdtp != table + table_len; 3 cmdtp++) { 4 if (strncmp (cmd, cmdtp->name, len) == 0) { 5 if (len == strlen (cmdtp->name)) 6 return cmdtp; /* full match */ 7 8 cmdtp_temp = cmdtp; /* abbreviated command ? */ 9 n_found++; 10 } 11 }
通過上面代碼我們知道了其查找方法,但是相信很多人和我一樣很疑惑這個命令表到底在什麼地方。
按照我們對上面代碼的閱讀,和猜測我們可以知道這個表的開始地址是table,我們可以輕鬆的找到table的來源。
1 cmd_tbl_t *find_cmd (const char *cmd) 2 { 3 int len = &__u_boot_cmd_end - &__u_boot_cmd_start; 4 return find_cmd_tbl(cmd, &__u_boot_cmd_start, len); 5 }
通過上面代碼我們知道table等於__u_boot_cmd_start,通過全局搜索,我們找到這個地址的來源是\arch\arm\cpu\armv7\u-boot.lds
1 __u_boot_cmd_start = .; 2 .u_boot_cmd : { *(.u_boot_cmd) } 3 __u_boot_cmd_end = .;
在__u_boot_cmd_start和__u_boot_cmd_end之間放了一個.u_boot_cmd段,我們再對這個段名進行搜索找到了下面的宏
1 #define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))//強制設置段屬性爲.u_boot_cmd
其肯定通過該宏又定義了什麼東西,再經過搜索我們找到以下內容
1 #define U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,comp) \ 2 cmd_tbl_t __u_boot_cmd_##name Struct_Section = \ 3 U_BOOT_CMD_MKENT_COMPLETE(name,maxargs,rep,cmd,usage,help,comp)
其又定義了一個宏,通過對宏的閱讀我們可以知道,通過U_BOOT_CMD_COMPLETE這個宏可以定義一個cmd_tbl_t類型的結構體,並且將該結構體的段屬性強制設置爲.u_boot_cmd,再對這個宏搜索,找到的只有幾個命令,完全對不上,但是我們又找到下面的宏
1 #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \ 2 U_BOOT_CMD_COMPLETE(name,maxargs,rep,cmd,usage,help,NULL)
通過對U_BOOT_CMD的搜索我們找到了大量的命令定義。
至此我們可以完全清楚了這個命令表示怎麼來的,其是通過U_BOOT_CMD這樣一個宏去定義命令。隨便可以找到例子:
1 U_BOOT_CMD( 2 help, CONFIG_SYS_MAXARGS, 1, do_help, 3 "print command description/usage", 4 "\n" 5 " - print brief description of all commands\n" 6 "help command ...\n" 7 " - print detailed usage of 'command'" 8 );
n 再來看cmd_tbl_t結構體,其中保函了命令名,最大參數,以及對應函數等內容。
1 struct cmd_tbl_s { 2 char *name; /* Command Name */ 3 int maxargs; /* maximum number of arguments */ 4 int repeatable; /* autorepeat allowed? */ 5 /* Implementation function */ 6 int (*cmd)(struct cmd_tbl_s *, int, int, char * const []); 7 char *usage; /* Usage message (short) */ 8 #ifdef CONFIG_SYS_LONGHELP 9 char *help; /* Help message (long) */ 10 #endif 11 #ifdef CONFIG_AUTO_COMPLETE 12 /* do auto completion on the arguments */ 13 int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]); 14 #endif 15 };
至此我們可以通過上面的內容實現我們自己的簡單u-boot命令,下面是我實現的hello命令
1 #include <common.h> 2 #include <command.h> 3 4 int do_hello(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[]) 5 { 6 printf("hello u-boot"); 7 return 0; 8 } 9 10 U_BOOT_CMD( 11 hello, CONFIG_SYS_MAXARGS, 1, do_hello, 12 "print hello",/*短幫助信息*/ 13 "\n hello cmd ............"//長幫幫助信息 14 );
加入上面內容,並且修改common目錄下的makefile,然後重新編譯u-boot,就會完成我們自己的u-boot命令,至此我們的u-boot命令實現分析完畢。
l 啓動內核
我們的u-boot可以通過nand、tftp等方式,將我們的內核加載至內存,對於這個過程今天就不重點去分析了,今天我們重點分析從內存中如何啓動內核,我們都知道啓動內核的時候要用到bootm命令,按照我們上面分析命令實現的經驗,可以猜出其必然會去運行do_bootm函數。下面我們主要分析這個函數的實現。
我們的bootm只能啓動uImage,然而uImage = zImage(真正的內核) + 頭信息。所以我們首先來看看頭信息:
typedef struct image_header { uint32_t ih_magic; /* Image Header Magic Number */ uint32_t ih_hcrc; /* Image Header CRC Checksum */ uint32_t ih_time; /* Image Creation Timestamp */ uint32_t ih_size; /* Image Data Size */ uint32_t ih_load; /* Data Load Address */ uint32_t ih_ep; /* Entry Point Address */ uint32_t ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN]; /* Image Name */ } image_header_t;
這裏面存放了大量的內核信息,我們也可以找到do_bootm中用這些信息進行內核的校驗,加載地址的校驗等工作。
假設我們啓動的是linux(u-boot支持多種系統啓動,下面代碼列出),我們的u-boot會執行到do_bootm_linux函數
1 static boot_os_fn *boot_os[] = { 2 #ifdef CONFIG_BOOTM_LINUX 3 [IH_OS_LINUX] = do_bootm_linux, 4 #endif 5 #ifdef CONFIG_BOOTM_NETBSD 6 [IH_OS_NETBSD] = do_bootm_netbsd, 7 #endif 8 #ifdef CONFIG_LYNXKDI 9 [IH_OS_LYNXOS] = do_bootm_lynxkdi, 10 #endif 11 #ifdef CONFIG_BOOTM_RTEMS 12 [IH_OS_RTEMS] = do_bootm_rtems, 13 #endif 14 #if defined(CONFIG_BOOTM_OSE) 15 [IH_OS_OSE] = do_bootm_ose, 16 #endif 17 #if defined(CONFIG_CMD_ELF) 18 [IH_OS_VXWORKS] = do_bootm_vxworks, 19 [IH_OS_QNX] = do_bootm_qnxelf, 20 #endif 21 #ifdef CONFIG_INTEGRITY 22 [IH_OS_INTEGRITY] = do_bootm_integrity, 23 #endif 24 };
下面我們來分析do_bootm_linux函數主要有以下內容
a) 和內核進行交接工作,爲內核設置啓動參數
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \ defined (CONFIG_CMDLINE_TAG) || \ defined (CONFIG_INITRD_TAG) || \ defined (CONFIG_SERIAL_TAG) || \ defined (CONFIG_REVISION_TAG) setup_start_tag (bd); #ifdef CONFIG_SERIAL_TAG setup_serial_tag (¶ms); #endif #ifdef CONFIG_REVISION_TAG setup_revision_tag (¶ms); #endif #ifdef CONFIG_SETUP_MEMORY_TAGS setup_memory_tags (bd); #endif #ifdef CONFIG_CMDLINE_TAG setup_commandline_tag (bd, commandline); #endif #ifdef CONFIG_INITRD_TAG if (images->rd_start && images->rd_end) setup_initrd_tag (bd, images->rd_start, images->rd_end); #endif setup_end_tag(bd); #endif
這些代碼主要是將參數,按照固定的格式寫到固定的地方。內核啓動後將會去這個地址讀取參數。
b) 跳到入口地址,啓動內核
1 kernel_entry = (void (*)(int, int, uint))images->ep; 2 kernel_entry(0, machid, bd->bi_boot_params);
machid:我們的機器ID
bd->bi_boot_params:剛纔提到的參數的地址
至此我們今天的工作全部結束。