在uboot啓動第二階段的最後,程序進入了一個死循環,實際是在等待超時和等待用戶命令的輸入,然後根據不同的命令去執行uboot的不同功能,實際uboot就是一個單片機程序,只有一個進程在運行。uboot引導kernel的啓動,首先是從環境變量bootcmd中獲取啓動命令,然後通過執行bootcmd裏面的命令來實現kernel的啓動的。
uboot第二階段啓動可以查看博客《uboot啓動第二階段start_armboot函數分析》,uboot其它內容可以查看博客《序言和目錄》
(一)命令獲取
main_loop 函數解析,這裏函數非常的長,將hi3521a默認沒有定義的功能代碼刪除,得到下面的代碼:
void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, }; //①
int len;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#ifdef CONFIG_VERSION_VARIABLE //定義CONFIG_VERSION_VARIABLE=1
{
extern char version_string[];
setenv ("ver", version_string); /* set version variable */ ②
}
#endif /* CONFIG_VERSION_VARIABLE */
#ifdef CONFIG_AUTO_COMPLETE
install_auto_complete(); //③
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
#ifdef CONFIG_HI3536_A7
s = getenv("slave_cmd");
#else
s = getenv("bootcmd");
#endif
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) { //④
run_command (s, 0); //⑤
}
#endif /* CONFIG_BOOTDELAY */
/*
* Main Loop for Monitor Command Processing
*/
#ifdef CONFIG_SYS_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#else
for (;;) { //⑥
#ifdef CONFIG_PRODUCT_HDMI_PHY
{
extern void update_hdmi_status(void);
update_hdmi_status();
}
#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;
if (len == -1)
puts ("<INTERRUPT>\n");
else
#ifdef CONFIG_BOOT_RETRY_TIME
rc = run_command(lastcommand, flag);
#else
run_command(lastcommand, flag);
#endif
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
#endif /*CONFIG_SYS_HUSH_PARSER*/
}
①定義一個靜態局部變量,用來記錄我們的歷史操作命令
②添加一個環境變量ver,用來記錄uboot的版本信息
③添加自動補全的功能
④從環境變量中讀取bootdelay的值和bootcmd的值,如果它們不爲空,那麼進入abortboot (bootdelay)函數執行,我們看abortboot的函數體實現
static __inline__ int abortboot(int bootdelay)
{
int abort = 0;
#ifdef CONFIG_MENUPROMPT
printf(CONFIG_MENUPROMPT);
#else
printf("Hit any key to stop autoboot: %2d ", bootdelay);
#endif
#if defined CONFIG_ZERO_BOOTDELAY_CHECK
/*
* Check if key already pressed
* Don't check if bootdelay < 0
*/
if (bootdelay >= 0) {
if (tstc()) { /* we got a key press */
(void) getc(); /* consume input */
puts ("\b\b\b 0");
abort = 1; /* don't auto boot */
}
}
#endif
while ((bootdelay > 0) && (!abort)) {
int i;
--bootdelay;
/* delay 100 * 10ms */
for (i=0; !abort && i<100; ++i) {
if (tstc()) { /* we got a key press */ //(1)
abort = 1; /* don't auto boot */
bootdelay = 0; /* no more delay */
# ifdef CONFIG_MENUKEY
menukey = getc();
# else
(void) getc(); /* consume input */
# endif
break;
}
udelay(10000);
}
printf("\b\b\b%2d ", bootdelay);
}
putc('\n');
#ifdef CONFIG_SILENT_CONSOLE
if (abort)
gd->flags &= ~GD_FLG_SILENT;
#endif
return abort;
}
- 從標準輸入中獲取1個字符,
- 如果有獲取到,直接退出,
- 如果沒有獲取到,則進入下面的while循環繼續獲取字符,直到獲取到字符或是有延時已經到了,退出循環
- ⑤執行bootcmd命令
- 如果沒有檢測到字符輸入,則執行bootcmd命令,讓uboot去引導kernel啓動,這個詳細見後面介紹。
- ⑥循環獲取命令
- 循環獲取命令,以回車爲結束符,獲取到命令調用run_command去執行命令。
(二)bootcmd命令解析
2.1U_BOOT_CMD命令格式:
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各個參數的意義如下:
- name:命令名,非字符串,但在U_BOOT_CMD中用“#”符號轉化爲字符串
- maxargs:命令的最大參數個數
- repeatable:是否自動重複(按Enter鍵是否會重複執行)
- command:該命令對應的響應函數指針
- usage:簡短的使用說明(字符串)
- help:較詳細的使用說明(字符串)
U_BOOT_CMD宏在include/command.h中定義:
#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}
- “##”與“#”都是預編譯操作符,“##”有字符串連接的功能,“#”表示後面緊接着的是一個字符串
其中Struct_Section在include/command.h中定義如下:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
- 凡是帶有attribute ((unused,section (“.u_boot_cmd”))屬性聲明的變量都將被存放在”.u_boot_cmd”段中,並且即使該變量沒有在代碼中顯式的使用編譯器也不產生警告信息。
.u_boot_cmd
在u-Boot連接腳本 u-boot.lds中定義了.u_boot_cmd段:
. = .;
__u_boot_cmd_start = .; /*將 __u_boot_cmd_start指定爲當前地址 */
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .; /* 將__u_boot_cmd_end指定爲當前地址 */
- 這表明帶有“.u_boot_cmd”聲明的函數或變量將存儲在“u_boot_cmd”段。
- 這樣只要將u-boot所有命令對應的cmd_tbl_t變量加上“.u_boot_cmd”聲明,編譯器就會自動將其放在“u_boot_cmd”段,查找cmd_tbl_t變量時只要在 __u_boot_cmd_start 與 __u_boot_cmd_end 之間查找就可以了。
cmd_tbl_t
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;
一個cmd_tbl_t結構體變量包含了調用一條命令的所需要的信息。
- 對於環境變量bootcmd,執行run_command(bootcmd, flag)之後,最終是將bootcmd中的參數解析爲命令,海思hi3521a中默認參數是bootcmd=bootm 0x82000000
- 相當於執行bootm 0x82000000 命令
- 最終將調用do_bootm函數,do_bootm函數在cmd_bootm.c中實現
(三)bootm命令解析
/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong iflag;
ulong load_end = 0;
int ret;
boot_os_fn *boot_fn;
/* determine if we have a sub command */
if (argc > 1) { //(1)
char *endp;
simple_strtoul(argv[1], &endp, 16);
/* endp pointing to NULL means that argv[1] was just a
* valid number, pass it along to the normal bootm processing
*
* If endp is ':' or '#' assume a FIT identifier so pass
* along for normal processing.
*
* Right now we assume the first arg should never be '-'
*/
if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
return do_bootm_subcommand(cmdtp, flag, argc, argv);
}
if (bootm_start(cmdtp, flag, argc, argv)) //(2)
return 1;
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts(); //(3)
usb_stop(); //(4)
ret = bootm_load_os(images.os, &load_end, 1); //(5)
if (ret < 0) { //(6)
if (ret == BOOTM_ERR_RESET)
do_reset (cmdtp, flag, argc, argv);
if (ret == BOOTM_ERR_OVERLAP) {
if (images.legacy_hdr_valid) {
if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
puts ("WARNING: legacy format multi component "
"image overwritten\n");
} else {
puts ("ERROR: new format image overwritten - "
"must RESET the board to recover\n");
show_boot_progress (-113);
do_reset (cmdtp, flag, argc, argv);
}
}
if (ret == BOOTM_ERR_UNIMPLEMENTED) {
if (iflag)
enable_interrupts();
show_boot_progress (-7);
return 1;
}
}
lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load)); //(7)
if (images.os.type == IH_TYPE_STANDALONE) {
if (iflag)
enable_interrupts();
/* This may return when 'autostart' is 'no' */
bootm_start_standalone(iflag, argc, argv);
return 0;
}
show_boot_progress (8);
boot_fn = boot_os[images.os.os]; //(8)
if (boot_fn == NULL) {
if (iflag)
enable_interrupts();
printf ("ERROR: booting os '%s' (%d) is not supported\n",
genimg_get_os_name(images.os.os), images.os.os);
show_boot_progress (-8);
return 1;
}
arch_preboot_os();
boot_fn(0, argc, argv, &images); //(9)
show_boot_progress (-9);
do_reset (cmdtp, flag, argc, argv); //(10)
return 1;
}
- (1)默認啓動命令爲:bootm 0x82000000;argc=1;argv[0]=bootm;argv[1]=0x82000000
- 所以這個判斷語句不會進去
- (2) bootm_start 在這裏主要是獲取內核鏡像的頭文件和內核鏡像的一些參數
- (3)關閉中斷
- (4)關閉usb設備
- (5)這裏主要是將uImage文件的頭結構去掉,後面的內核數據往前面移動image_header_t結構體長度
- (6)上面一步出錯的異常處理
- (7)lmb_reserve 這個函數在hi3521a中沒有實現,實際是一個空函數
- (8)獲取linux啓動啓動內核的函數地址
- (9)調用啓動內核的函數,在海思設備上,實際上是調用函數 do_bootm_linux, 由do_bootm_linux 實現uboot參數傳遞到kernel並且跳轉到kernel的開始位置去執行。
- (10)正常情況在上一步就已經將控制權移交到kernel去運行了,不會運行到這裏,如果運行到這裏表示出現錯誤了,執行系統復位操作,實際執行的是\arch\arm\lib\reset.c下的復位函數。
3.1 bootm_start函數解析
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
void *os_hdr;
int ret;
memset ((void *)&images, 0, sizeof (images));
images.verify = getenv_yesno ("verify"); //(1)
bootm_start_lmb();
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len); //(2)
if (images.os.image_len == 0) {
puts ("ERROR: can't get kernel image!\n");
return 1;
}
/* get image parameters */
switch (genimg_get_format (os_hdr)) {
case IMAGE_FORMAT_LEGACY: //(3)
images.os.type = image_get_type (os_hdr);
images.os.comp = image_get_comp (os_hdr);
images.os.os = image_get_os (os_hdr);
images.os.end = image_get_image_end (os_hdr);
images.os.load = image_get_load (os_hdr);
break;
default:
puts ("ERROR: unknown image format type!\n");
return 1;
}
/* find kernel entry point */
if (images.legacy_hdr_valid) {
images.ep = image_get_ep (&images.legacy_hdr_os_copy);
} else {
puts ("Could not find kernel entry point!\n");
return 1;
}
if (((images.os.type == IH_TYPE_KERNEL) ||
(images.os.type == IH_TYPE_MULTI)) &&
(images.os.os == IH_OS_LINUX)) {
/* find ramdisk */
ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end); //(4)
if (ret) {
puts ("Ramdisk image is corrupt or invalid\n");
return 1;
}
}
images.os.start = (ulong)os_hdr;
images.state = BOOTM_STATE_START; //(5)
return 0;
}
- (1)從環境變量中獲取verify的值
- (2)boot_get_kernel 在這裏面主要是解析內核進項頭文件的一些參數,並且校驗kernel鏡像文件是否有效
- (3)鏡像文件參數賦值,uImage鏡像文件是IMAGE_FORMAT_LEGACY 這個類型(舊類型),下面有一個CONFIG_FIT宏定義被刪除了,這個是設備樹類型傳參數才使用的,在海思設備上沒有配置。
- (4)解析ramdisk鏡像,用來判斷是否有虛擬磁盤,在海思設備上沒有這個。
- (5)啓動狀態賦值
- 在這裏需要注意一個,在uboot中鏡像參數的獲取,使用了宏定義來實現,這個宏定義使用了連接符號,所以比較難看出來。
- 宏定義及數據結構定義如下:
/*******************************************************************/
/* Legacy format specific code (prefixed with image_) */
/*******************************************************************/
static inline uint32_t image_get_header_size (void)
{
return (sizeof (image_header_t));
}
#define image_get_hdr_l(f) \
static inline uint32_t image_get_##f(const image_header_t *hdr) \
{ \
return uimage_to_cpu (hdr->ih_##f); \
}
image_get_hdr_l (magic); /* image_get_magic */
image_get_hdr_l (hcrc); /* image_get_hcrc */
image_get_hdr_l (time); /* image_get_time */
image_get_hdr_l (size); /* image_get_size */
image_get_hdr_l (load); /* image_get_load */
image_get_hdr_l (ep); /* image_get_ep */
image_get_hdr_l (dcrc); /* image_get_dcrc */
#define image_get_hdr_b(f) \
static inline uint8_t image_get_##f(const image_header_t *hdr) \
{ \
return hdr->ih_##f; \
}
image_get_hdr_b (os); /* image_get_os */
image_get_hdr_b (arch); /* image_get_arch */
image_get_hdr_b (type); /* image_get_type */
image_get_hdr_b (comp); /* image_get_comp */
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;
有一個uImage文件,它的數據頭如下:
解析出來就是:
- ih_magic = 0x27051956
- ih_hcrc = 0x93CE3402
- ih_time = 0x5DC23726
- ih_size = 0x38EAB8
- ih_load = 0x80008000
- ih_ep = 0x80008000
- ih_dcrc = 0xD3C0AAAA
- ih_os = 0x05 /**IH_OS_LINUX**/
- ih_arch = 0x02 /**IH_ARCH_ARM**/
- ih_type = 0x02 /**IH_TYPE_KERNEL**/
- ih_comp = 0x00 /**IH_COMP_NONE**/
- ih_name = Linux-3.18.20
3.2do_bootm_linux函數解析
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;
void (*theKernel)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
#ifdef CONFIG_HI3536_A7
char *commandline = getenv("slave_bootargs");
#else
char *commandline = getenv("bootargs"); //(1)
#endif
#endif
if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
return 1;
theKernel = (void (*)(int, int, uint))images->ep; //(2)
s = getenv ("machid"); //(3)
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
setup_start_tag (bd); //(4)
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline); //(5)
if (images->rd_start && images->rd_end)
setup_initrd_tag (bd, images->rd_start, images->rd_end);
setup_eth_use_mdio_tag(bd, getenv("use_mdio"));
setup_eth_mdiointf_tag(bd, getenv("mdio_intf"));
setup_ethaddr_tag(bd, getenv("ethaddr"));
setup_end_tag (bd); //(6)
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux (); //(7)
theKernel (0, machid, bd->bi_boot_params); //(8)
/* does not return */
return 1;
}
- (1)獲取環境變量bootargs中的值,該環境變量用來傳遞參數給kernel
- (2)images->ep的地址是kernel的程序的入口地址,也就是將函數指針theKernel指向kernel最先執行的地方。
- (3)獲取環境變量machid,這個應該是機器碼,海思設備沒有定義在環境變量中
- (4)這裏是建立一個鏈表用來存放傳遞給內核的參數,在board_init函數中有賦值 gd->bd->bi_boot_params = CFG_BOOT_PARAMS;
- CFG_BOOT_PARAMS = 0x80000000 + 0x0100 = 0x80000100
- (5)將commandline的值添加到鏈表中
- (6)結束參數的填充
- (7)啓動linux內核前的一個清除操作,主要是關閉中斷,關閉緩存等操作
- (8)由前面我們知道theKernel實際指向的是kernel的入口地址,執行這一句之後,uboot就結束了運行,kernel正式運行就從這裏開始。
到這裏就完成了uboot自己的啓動和引導內核啓動的整個過程,其它內容可以參考博客《序言和目錄》
goodbye,有緣再見 ~
參考內容:
- https://www.cnblogs.com/cslunatic/p/3891752.html
- https://blog.csdn.net/qq_33894122/article/details/86129765