海思(Hi3521a)uboot詳細分析(8)——bootm啓動命令解析

    在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

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章