海思(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
发布了164 篇原创文章 · 获赞 229 · 访问量 62万+

猜你喜欢

转载自blog.csdn.net/li_wen01/article/details/103353627