嵌入式Linux——分析u-boot运行过程(4):u-boot第四阶段代码

简介:

        本文主要介绍在u-boot-1.1.6中代码的运行过程,以此来了解在u-boot中如何实现引导并启动内核。这里我们主要介绍u-boot第四阶段的代码,即讲解的是在u-boot中我们如何运用命令行来加载并启动内核。

声明:

        本文主要是看了韦东山老师的视频后所写,希望对你有所帮助。

u-boot版本 : u-boot-1.1.6

开发板 : JZ2440

Linux内核 : Linux-2.6.22.6

bootm : 命令行启动内核

        我在上面一篇文章:嵌入式Linux——分析u-boot运行过程(3):u-boot第三阶段代码中讲解了u-boot通过U_BOOT_CMD宏将各种命令放到命令列表中,而在本章中我们要讲的是如何通过命令行来启动内核,这个命令名为:bootm 。同时我们在u-boot中搜"bootm"可以找到其对应的命令定义。他的代码为:

#define	CFG_MAXARGS		16		/* max number of command args	*/

U_BOOT_CMD(
 	bootm,	CFG_MAXARGS,	1,	do_bootm,
 	"bootm   - boot application image from memory\n",
 	"[addr [arg ...]]\n    - boot application image stored in memory\n"
 	"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
 	"\t'arg' can be the address of an initrd image\n"
);

        通过前面我们对U_BOOT_CMD的介绍大家可以知道,上面命令的名称为bootm,他的参数个数最大为16,而他是不可重复的。同时我们还可以知道这个命令的操作函数为do_bootm,而下面的几行字符则是bootm命令的帮助信息。好了,解释到这里我想大家都明白了,这个命令的重点是操作函数:do_bootm。所以我们直接分析do_bootm函数:

#define	CFG_LOAD_ADDR		0x33000000	/* default load address	*/

ulong load_addr = CFG_LOAD_ADDR;		/* Default Load Address */

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	ulong	iflag;
	ulong	addr;
	ulong	data, len, checksum;
	ulong  *len_ptr;
	uint	unc_len = CFG_BOOTM_LEN;
	int	i, verify;
	char	*name, *s;
	int	(*appl)(int, char *[]);
	image_header_t *hdr = &header;

	s = getenv ("verify");
	verify = (s && (*s == 'n')) ? 0 : 1;

	if (argc < 2) {
		addr = load_addr;
	} else {
		addr = simple_strtoul(argv[1], NULL, 16);
	}

	SHOW_BOOT_PROGRESS (1);
	printf ("## Booting image at %08lx ...\n", addr);

	/* Copy header so we can blank CRC field for re-calculation */
	memmove (&header, (char *)addr, sizeof(image_header_t));

	if (ntohl(hdr->ih_magic) != IH_MAGIC) {
	    {
		puts ("Bad Magic Number\n");
		SHOW_BOOT_PROGRESS (-1);
		return 1;
	    }
	}
	SHOW_BOOT_PROGRESS (2);

	data = (ulong)&header;
	len  = sizeof(image_header_t);

	checksum = ntohl(hdr->ih_hcrc);
	hdr->ih_hcrc = 0;

	if (crc32 (0, (uchar *)data, len) != checksum) {
		puts ("Bad Header Checksum\n");
		SHOW_BOOT_PROGRESS (-2);
		return 1;
	}
	SHOW_BOOT_PROGRESS (3);


	/* for multi-file images we need the data part, too */
	print_image_hdr ((image_header_t *)addr);

	data = addr + sizeof(image_header_t);
	len  = ntohl(hdr->ih_size);

	if (verify) {
		puts ("   Verifying Checksum ... ");
		if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
			printf ("Bad Data CRC\n");
			SHOW_BOOT_PROGRESS (-3);
			return 1;
		}
		puts ("OK\n");
	}
	SHOW_BOOT_PROGRESS (4);

	len_ptr = (ulong *)data;

	if (hdr->ih_arch != IH_CPU_ARM)
	{
		printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
		SHOW_BOOT_PROGRESS (-4);
		return 1;
	}
	SHOW_BOOT_PROGRESS (5);

	switch (hdr->ih_type) {
	case IH_TYPE_STANDALONE:
		name = "Standalone Application";
		/* A second argument overwrites the load address */
		if (argc > 2) {
			hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
		}
		break;
	case IH_TYPE_KERNEL:
		name = "Kernel Image";
		break;
	case IH_TYPE_MULTI:
		name = "Multi-File Image";
		len  = ntohl(len_ptr[0]);
		/* OS kernel is always the first image */
		data += 8; /* kernel_len + terminator */
		for (i=1; len_ptr[i]; ++i)
			data += 4;
		break;
	default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
		SHOW_BOOT_PROGRESS (-5);
		return 1;
	}
	SHOW_BOOT_PROGRESS (6);

	/*
	 * 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();

	switch (hdr->ih_comp) {
	case IH_COMP_NONE:
		if(ntohl(hdr->ih_load) == data) {
			printf ("   XIP %s ... ", name);
		} else {
			memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
		}
		break;
	case IH_COMP_GZIP:
		printf ("   Uncompressing %s ... ", name);
		if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
			    (uchar *)data, &len) != 0) {
			puts ("GUNZIP ERROR - must RESET board to recover\n");
			SHOW_BOOT_PROGRESS (-6);
			do_reset (cmdtp, flag, argc, argv);
		}
		break;

	default:
		if (iflag)
			enable_interrupts();
		printf ("Unimplemented compression type %d\n", hdr->ih_comp);
		SHOW_BOOT_PROGRESS (-7);
		return 1;
	}
	puts ("OK\n");
	SHOW_BOOT_PROGRESS (7);

	switch (hdr->ih_type) {
	case IH_TYPE_STANDALONE:
		if (iflag)
			enable_interrupts();

		/* load (and uncompress), but don't start if "autostart"
		 * is set to "no"
		 */
		if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
			char buf[32];
			sprintf(buf, "%lX", len);
			setenv("filesize", buf);
			return 0;
		}
		appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
		(*appl)(argc-1, &argv[1]);
		return 0;
	case IH_TYPE_KERNEL:
	case IH_TYPE_MULTI:
		/* handled below */
		break;
	default:
		if (iflag)
			enable_interrupts();
		printf ("Can't boot image type %d\n", hdr->ih_type);
		SHOW_BOOT_PROGRESS (-8);
		return 1;
	}
	SHOW_BOOT_PROGRESS (8);

	switch (hdr->ih_os) {
	default:			/* handled by (original) Linux case */
	case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
	    fixup_silent_linux();
#endif
	    do_bootm_linux  (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;
	case IH_OS_NETBSD:
	    do_bootm_netbsd (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;

	case IH_OS_RTEMS:
	    do_bootm_rtems (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;

	}

	SHOW_BOOT_PROGRESS (-9);
	return 1;
}

        分析上面程序,我们可以将上面程序所做的事情分为以下几块:

1.   检测参数个数或确定加载地址
2.   将内核映射的头部拷贝到header
3.   检测uImage的头部参数ih_magic是否正确
4.   检测头部的CRC校验值是否正确
5.   对于多文件的image,需要检测镜像的CRC值
6.   检验uImage的架构是否为arm架构
7.   检测uImage的镜像类型
8.   关中断
9.   检测uImage的压缩类型
10. 检测操作系统类型,针对不同的类型有不同的启动方式

        而对于上面的不同步骤,我们可以发现其实他们就是:用不同的方式来检验uImage头部64字节的信息。因此我们知道上面函数的重点就是uImage头部的信息了。在介绍uImage这64字节头部信息之前,我想先为大家讲解一下这64字节数据的由来。大家应该知道内核在编译成功后会生成image和zimage两种文件,他们分别为内核映像文件和内核映像压缩文件,一般image文件的大小为4M左右,而zimage的大小为2M左右。所以引入压缩文件可以节省很多的空间。但u-boot在加载zimage的时候还需要获得关于加载地址,入口地址,操作系统类型以及最重要的解压方式等的一些参数,而这些数据的传输使得程序变得十分复杂。而这个时候 Legacy uImage的64字节头部信息的引入就为u-boot加载内核提供了足够的信息。因此关于头部信息的检测对于加载和启动内核是个很重要的过程。下面我们来看uImage的头部都包含哪些信息:

typedef struct image_header {
	uint32_t	ih_magic;	/* 幻数头,用来校验是否是一个Legacy-uImage */
	uint32_t	ih_hcrc;	/* 头部的CRC校验值	*/
	uint32_t	ih_time;	/* 镜像创建的时间戳	*/
	uint32_t	ih_size;	/* 镜像数据长度 */
	uint32_t	ih_load;	/* 加载地址 */
	uint32_t	ih_ep;		/* 入口地址 */
	uint32_t	ih_dcrc;	/* 镜像的CRC校验 */
	uint8_t		ih_os;		/*  操作系统类型 */
	uint8_t		ih_arch;	/* CPU架构		*/
	uint8_t		ih_type;	/* 镜像类型 */
	uint8_t		ih_comp;	/* 压缩类型		*/
	uint8_t		ih_name[IH_NMLEN];	/* 镜像名 */
} image_header_t;

        了解完头部的信息后,下面我们就要结合着头部的信息来看do_bootm函数中到底是如何对头部信息进行检测的,同时我们也可以结合do_bootm的代码对头部的信息有一个更加深入的了解。,例如不同的压缩类型,不同操作系统类型,不同的CPU架构等。

检测参数个数或确定加载地址:

        我在上一篇文章中讲到在命令行中可以敲入字符串命令来控制单板做一些特定的事情,而命令要通过空格或者制表符来分离为几个分离的字符串,而这几个字符串就是该命令的参数,在u-boot中我们可以使用单独的bootm命令来启动内核,也可以通过“bootm  内核加载地址”的方式来启动内核。因此程序要在这里检验参数的个数是否小于2,如果参数为1(参数不可能为0,为0就进不了bootm命令了),这说明在命令行中只输入了字符串“bootm”,那么内核的加载地址就是用默认的参数。而如果命令行的参数个数大于等于2,那么第二个参数就是加载地址了(对于bootm命令有一定的命令规范第二个参数只能是加载地址),这时候程序将我们键入的字符型加载地址转化为长整型数字并赋值给变量addr。同时需要强调一点的是:这里的加载地址也是uImage头部的首地址。该步骤的操作代码为:

	if (argc < 2) {
		addr = load_addr;
	} else {
		addr = simple_strtoul(argv[1], NULL, 16);
	}

	SHOW_BOOT_PROGRESS (1);
	printf ("## Booting image at %08lx ...\n", addr);

将内核映射的头部数据拷贝到header:

        其实将内核头部数据拷贝到header位置的代码还是很简单的,就是使用memmove函数将源地址的内容拷贝指定长度到目的地址。他的代码为

	memmove (&header, (char *)addr, sizeof(image_header_t));

        在上面的代码中源为addr(内核头部首地址),目的地址为header的地址,而拷贝的长度为image_header_t结构体的数据长度。

检测uImage的头部参数ih_magic是否正确:

        这步要检验的是ih_magic参数(幻数头),用来校验uImage是否是一个Legacy-uImage,在前面我们已经介绍了Legacy-uImage可以有效的帮助u-boot加载并启动内核,而Legacy-uImage印象文件有他自己特有的幻数头,因此我们可以用幻数头来检验这个image是否为一个uImage。对于本u-boot来说幻数头为:

#define IH_MAGIC	0x27051956	/* Image Magic Number		*/

        而检验幻数头的代码为:

	if (ntohl(hdr->ih_magic) != IH_MAGIC) {
		puts ("Bad Magic Number\n");
		SHOW_BOOT_PROGRESS (-1);
		return 1;
	}
	SHOW_BOOT_PROGRESS (2);

检测头部的CRC校验值是否正确:

        这里需要计算头部信息的CRC校验值,并用这个CRC校验值与存储在头部的CRC校验值进行比较,如果相同则表示正确程序继续向下运行。如果不同则表示错误,这时do_bootm函数将终止执行。而计算头部的CRC校验值需要uImage头部信息的首地址以及image_header_t结构体的大小(即64字节)。而在本次检验中使用了CRC32算法来生成校验码。而关于CRC32算法的详细解释大家可以看:crc32 算法与实现 。而关于头部的CRC检验代码为:

	data = (ulong)&header;
	len  = sizeof(image_header_t);

	checksum = ntohl(hdr->ih_hcrc);
	hdr->ih_hcrc = 0;

	if (crc32 (0, (uchar *)data, len) != checksum) {
		puts ("Bad Header Checksum\n");
		SHOW_BOOT_PROGRESS (-2);
		return 1;
	}
	SHOW_BOOT_PROGRESS (3);

对于多文件的image,需要检测镜像的CRC值:

        对于多文件的image,除了头部我们还要对uImage的数据部分做CRC检验,程序与上面相似。而不同的地方是数据开始位置变为了真正内核数据开始的位置即uImage头部信息的首地址加上头部所占的64字节空间。同时这里的数据大小变为了内核数据的大小。他的实现代码为:

	/* for multi-file images we need the data part, too */
	print_image_hdr ((image_header_t *)addr);

	data = addr + sizeof(image_header_t);
	len  = ntohl(hdr->ih_size);

	if (verify) {
		puts ("   Verifying Checksum ... ");
		if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
			printf ("Bad Data CRC\n");
			SHOW_BOOT_PROGRESS (-3);
			return 1;
		}
		puts ("OK\n");
	}
	SHOW_BOOT_PROGRESS (4);

检验uImage的架构是否为arm架构:

        下面就要检测uImage是否为arm架构的CPU所编译。如果是则进行下一步,而如果不是就要检测是否为其他的架构所编译,对于不同架构的cpu会有不同的uImage。而对于我们的开发板而言,如果没有找到为arm架构所编译的uImage,那么程序运行到这里就会返回而不去执行下一步的操作。对于arm架构CPU他的定义为:

#define IH_CPU_ARM		2	/* ARM		*/

        而检验uImage架构的代码为:

	if (hdr->ih_arch != IH_CPU_ARM)
	{
		printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
		SHOW_BOOT_PROGRESS (-4);
		return 1;
	}
	SHOW_BOOT_PROGRESS (5);

检测uImage的镜像类型:

        下面程序就要检验uImage的镜像类型了,这里其实就是查看uImage中的真正数据是由什么组成的。而对于我们开发板上的uImage是由kernel编译生成的。所以我们看代码:

	switch (hdr->ih_type) {
	case IH_TYPE_STANDALONE:
		name = "Standalone Application";
		/* A second argument overwrites the load address */
		if (argc > 2) {
			hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
		}
		break;
	case IH_TYPE_KERNEL:
		name = "Kernel Image";
		break;
	case IH_TYPE_MULTI:
		name = "Multi-File Image";
		len  = ntohl(len_ptr[0]);
		/* OS kernel is always the first image */
		data += 8; /* kernel_len + terminator */
		for (i=1; len_ptr[i]; ++i)
			data += 4;
		break;
	default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
		SHOW_BOOT_PROGRESS (-5);
		return 1;
	}
	SHOW_BOOT_PROGRESS (6);

        因为我们知道开发板上的uImage是由kernel生成,所以从上面的程序中我们知道 name = "Kernel Image";

关中断:

        在这里关闭中断其实就是通过关闭中断来防止因为某种意想不到的中断发生来打断下面的解压过程(下面的解压过程是不可被打断的)。而关中断的代码其实就是用在C语言中嵌入汇编代码的方式来操作当前程序状态寄存器来关中断。关中断的代码为:

/*
 * disable IRQ/FIQ interrupts
 * returns true if interrupts had been enabled before we disabled them
 */
int disable_interrupts (void)
{
	unsigned long old,temp;
	__asm__ __volatile__("mrs %0, cpsr\n"
			     "orr %1, %0, #0xc0\n"
			     "msr cpsr_c, %1"
			     : "=r" (old), "=r" (temp)
			     :
			     : "memory");
	return (old & 0x80) == 0;
}

 检测uImage的压缩类型:

        通过检验不同的压缩类型进而根据这些压缩类型来找到对应的解压函数,来将映像压缩文件转化为内核的真正代码。在uImage中我们使用gzip的方式来对内核文件进行压缩操作。所以u-boot使用gunzip函数来为内核解压,检验压缩方式并针对不同方式进行解压的代码为:

	switch (hdr->ih_comp) {
	case IH_COMP_NONE:
		if(ntohl(hdr->ih_load) == addr) {
			printf ("   XIP %s ... ", name);
		} else {
			size_t l = len;
			void *to = (void *)ntohl(hdr->ih_load);
			void *from = (void *)data;

			printf ("   Loading %s ... ", name);

			while (l > 0) {
				size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
				WATCHDOG_RESET();
				memmove (to, from, tail);
				to += tail;
				from += tail;
				l -= tail;
			}
		}
		break;
	case IH_COMP_GZIP:
		printf ("   Uncompressing %s ... ", name);
		if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
			    (uchar *)data, &len) != 0) {
			puts ("GUNZIP ERROR - must RESET board to recover\n");
			SHOW_BOOT_PROGRESS (-6);
			do_reset (cmdtp, flag, argc, argv);
		}
		break;
	default:
		if (iflag)
			enable_interrupts();
		printf ("Unimplemented compression type %d\n", hdr->ih_comp);
		SHOW_BOOT_PROGRESS (-7);
		return 1;
	}
	puts ("OK\n");
	SHOW_BOOT_PROGRESS (7);

        从上面的代码看,如果程序在解压的过程中失败,即gunzip函数返回非0值时,u-boot会放弃当前操作并重启u-boot。


检测操作系统类型,针对不同的类型有不同的启动方式:

        根据不同的操作系统类型u-boot有不同的启动函数,而不同的启动函数对应不同的启动方式。因为我们的uImage是由Linux的代码生成,所以我们会在下面的代码中选择Linux系统,然后使用do_bootm_linux函数来加载和启动内核。他的代码为:

	switch (hdr->ih_os) {
	default:			/* handled by (original) Linux case */
	case IH_OS_LINUX:

	    do_bootm_linux  (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;
	case IH_OS_NETBSD:
	    do_bootm_netbsd (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;


	case IH_OS_RTEMS:
	    do_bootm_rtems (cmdtp, flag, argc, argv,
			     addr, len_ptr, verify);
	    break;

	}

        讲到这里关于uImage头部信息的检测就讲完了。如果上面的这些检测都没有问题,那么下一步我们就要进入do_bootm_linux函数来了解真正的加载和启动内核了。

        我想大家在看上面的代码的时候会发现在每一步完成之后都会有一个SHOW_BOOT_PROGRESS 函数,而该函数真正的功能是什么那?我们可以看下他的代码:


# define SHOW_BOOT_PROGRESS(arg)	show_boot_progress(arg)

void show_boot_progress (int status)
{
	switch (status) {
	case 1:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_OFF, STATUS_LED_ON);
		break;
	case 2:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_ON, STATUS_LED_OFF);
		break;
	case 3:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_ON, STATUS_LED_ON);
		break;
	case 4:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_OFF, STATUS_LED_OFF);
		break;
	case 5:
	case 6:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_OFF, STATUS_LED_ON);
		break;
	case 7:
	case 8:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_ON, STATUS_LED_OFF);
		break;
	case 9:
	case 10:
	case 11:
	case 12:
	case 13:
	case 14:
	case 15:
		stamp_led_set (STATUS_LED_OFF, STATUS_LED_OFF,
			       STATUS_LED_OFF);
		break;
	default:
		stamp_led_set (STATUS_LED_ON, STATUS_LED_ON, STATUS_LED_ON);
		break;
	}
}

        从上面的代码中我们可以看出,其实SHOW_BOOT_PROGRESS函数所做的工作就是通过判断不同的输入参数来控制不同的led的亮灭,依次来为我们显示程序运行进度。同时如果程序在那部分出现了错误也可以通过查看LED的亮灭来确定程序是在那部分出现了错误。因此SHOW_BOOT_PROGRESS函数在这里起到一个进度提示的作用。

加载并启动内核:

        做完上面对于uImage头部的检测工作之后,下面我们就要介绍如何加载并启动内核了,可以说前面的检测是一个准备工作,只有前面的工作做好了,做正确了,这一步加载和启动内核的程序才能正常运行。同时你会发现do_bootm_linux函数的很多输入参数都是由do_bootm函数所传入。好了,我们先全面的看一下do_bootm_linux函数,而我会在后面再分开详细的讲解他的组成。do_bootm_linux函数的代码为:

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
		     ulong addr, ulong *len_ptr, int verify)
{
	ulong len = 0, checksum;
	ulong initrd_start, initrd_end;
	ulong data;
	void (*theKernel)(int zero, int arch, uint params);
	image_header_t *hdr = &header;
	bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG
	char *commandline = getenv ("bootargs");
#endif

	theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

	/*
	 * Check if there is an initrd image
	 */
	if (argc >= 3) {
		SHOW_BOOT_PROGRESS (9);

		addr = simple_strtoul (argv[2], NULL, 16);

		printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

		/* Copy header so we can blank CRC field for re-calculation */

		memcpy (&header, (char *) addr,
				sizeof (image_header_t));

		if (ntohl (hdr->ih_magic) != IH_MAGIC) {
			printf ("Bad Magic Number\n");
			SHOW_BOOT_PROGRESS (-10);
			do_reset (cmdtp, flag, argc, argv);
		}

		data = (ulong) & header;
		len = sizeof (image_header_t);

		checksum = ntohl (hdr->ih_hcrc);
		hdr->ih_hcrc = 0;

		if (crc32 (0, (unsigned char *) data, len) != checksum) {
			printf ("Bad Header Checksum\n");
			SHOW_BOOT_PROGRESS (-11);
			do_reset (cmdtp, flag, argc, argv);
		}

		SHOW_BOOT_PROGRESS (10);

		print_image_hdr (hdr);

		data = addr + sizeof (image_header_t);
		len = ntohl (hdr->ih_size);

		if (verify) {
			ulong csum = 0;

			printf ("   Verifying Checksum ... ");
			csum = crc32 (0, (unsigned char *) data, len);
			if (csum != ntohl (hdr->ih_dcrc)) {
				printf ("Bad Data CRC\n");
				SHOW_BOOT_PROGRESS (-12);
				do_reset (cmdtp, flag, argc, argv);
			}
			printf ("OK\n");
		}

		SHOW_BOOT_PROGRESS (11);

		if ((hdr->ih_os != IH_OS_LINUX) ||
		    (hdr->ih_arch != IH_CPU_ARM) ||
		    (hdr->ih_type != IH_TYPE_RAMDISK)) {
			printf ("No Linux ARM Ramdisk Image\n");
			SHOW_BOOT_PROGRESS (-13);
			do_reset (cmdtp, flag, argc, argv);
		}

#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
		/*
		 *we need to copy the ramdisk to SRAM to let Linux boot
		 */
		memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
		data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */

		/*
		 * Now check if we have a multifile image
		 */
	} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
		ulong tail = ntohl (len_ptr[0]) % 4;
		int i;

		SHOW_BOOT_PROGRESS (13);

		/* skip kernel length and terminator */
		data = (ulong) (&len_ptr[2]);
		/* skip any additional image length fields */
		for (i = 1; len_ptr[i]; ++i)
			data += 4;
		/* add kernel length, and align */
		data += ntohl (len_ptr[0]);
		if (tail) {
			data += 4 - tail;
		}

		len = ntohl (len_ptr[1]);

	} else {
		/*
		 * no initrd image
		 */
		SHOW_BOOT_PROGRESS (14);

		len = data = 0;
	}

	if (data) {
		initrd_start = data;
		initrd_end = initrd_start + len;
	} else {
		initrd_start = 0;
		initrd_end = 0;
	}

	SHOW_BOOT_PROGRESS (15);


#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) || \
    defined (CONFIG_INITRD_TAG) || \
    defined (CONFIG_SERIAL_TAG) || \
    defined (CONFIG_REVISION_TAG) || \
    defined (CONFIG_LCD) || \
    defined (CONFIG_VFD)
	setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
	setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
	setup_revision_tag (&params);
#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 (initrd_start && initrd_end)
		setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
	setup_videolfb_tag ((gd_t *) gd);
#endif
	setup_end_tag (bd);
#endif

	/* we assume that the kernel is in place */
	printf ("\nStarting kernel ...\n\n");

#ifdef CONFIG_USB_DEVICE
	{
		extern void udc_disconnect (void);
                //udc_disconnect (); // cancled by www.100ask.net
	}
#endif

	cleanup_before_linux ();

	theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}

        我将上面的代码大致分为以下几步:

1. 获得命令行参数
2. 将函数指针指向内核的入口地址
3. 检测是否从内存盘初始化内核
    a. 获得内核在内存盘中的地址
    b. 将uImage的头部信息从内存盘中读出
    c. 检测uImage的头部参数ih_magic是否正确

    d. 检测头部的CRC校验值是否正确

    e. 检测镜像的CRC校验值是否正确
    f. 检验uImage的架构是否为arm架构
    g. uImage的镜像类型,以及内存盘的类型

    h. 将内核盘中的内核数据拷贝到内存的加载地址
4. 设置TAG参数
5. 启动Linux前的准备清除工作
6. 启动Linux内核

获得命令行参数:

        这里获得命令行参数来为内核传入启动参数,这里的命令行参数可以使用默认的参数,也可以使用我们在u-boot命令行中设置的参数。当我们定义了宏:CONFIG_CMDLINE_TAG的时候,程序就可以通过getenv来获得命令行参数了。而当我们没有定义宏:CONFIG_CMDLINE_TAG时,程序会使用默认的启动参数。因此我们可以知道宏CONFIG_CMDLINE_TAG是决定启动参数到底选哪个的关键。而这个启动参数还将在下面的TAG参数中使用。获得启动参数的代码为:

#ifdef CONFIG_CMDLINE_TAG
	char *commandline = getenv ("bootargs");
#endif

        上面的 bootargs 就是在u-boot中设置命令行参数时使用的变量。常用的设置启动参数的方式为:set  bootargs  console=ttySAC0 115200 root=/dev/mtdblock3 rootfstype=jffs2 。而上面的"console=ttySAC0 115200 root=/dev/mtdblock3 rootfstype=jffs2"就是我们常用的命令行参数。

将函数指针指向内核的入口地址:

        这里程序将一个函数指针指向内核的入口地址,而通过函数指针的输入参数我们知道要向内核中传递的参数分别为0,机器ID以及TAG参数的首地址。而我们将函数指针指向这个入口地址,而当我们在下面调用这个函数指针的时候就可以直接直接跳转到内核中了,从而实现启动内核的目的。而指针函数的定义以及将函数指针指向内核的入口地址的代码为:

	void (*theKernel)(int zero, int arch, uint params);

	theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

检测是否从内存盘初始化内核 :
    a. 获得内核在内存盘中的地址
    b. 将uImage的头部信息从内存盘中读出
    c. 检测uImage的头部参数ih_magic是否正确

    d. 检测头部的CRC校验值是否正确

    e. 检测镜像的CRC校验值是否正确
    f. 检验uImage的架构是否为arm架构
    g. uImage的镜像类型,以及内存盘的类型

        其实上面关于uImage头部信息检测的代码我在前面讲解do_bootm函数的时候已经讲解了,这里就不讲了。大家可以看看上面关于这部分内容的介绍。而这部分的代码也与前面讲解do_bootm函数时的代码十分相似,所以这里也就不贴他们的代码了。

设置TAG参数:

        这部分代码同样是这篇文章的重点之一, 我们知道kernel在启动的过程中要读取一些u-boot为其传递的参数来设置kernel的启动参数,而这些参数都是什么,他们以一种什么样的方式传递到kernel中,这些都是我们要讨论的。我们知道u-boot为内核传递参数是有一定的规律性的,而不是想怎么传就怎么传,这样就不符合内核的思想了。而内核的思想是模块化,同时也便于移植,所以他就有一定的规范,而这个规范就是TAG参数,u-boot与内核之间约定用过这种方式来传递参数,即u-boot通过TAG参数的方式将要传输的数据放到指定的地址中,而内核则通过TAG参数的方式将存放在指定地点中的数据读出。这就体现了TAG的参数的重要性了。

        而在u-boot中有很多TAG参数,这里我们选择4个比较重要的参数来向大家说明TAG参数的格式,他们分别为:开始TAG,结束TAG,内存TAG和命令行TAG。我在下图中将它们画出:

        而他们的代码为:

static void setup_start_tag (bd_t *bd)
{
	params = (struct tag *) bd->bi_boot_params;

	params->hdr.tag = ATAG_CORE;
	params->hdr.size = tag_size (tag_core);

	params->u.core.flags = 0;
	params->u.core.pagesize = 0;
	params->u.core.rootdev = 0;

	params = tag_next (params);
}


#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
	int i;

	for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
		params->hdr.tag = ATAG_MEM;
		params->hdr.size = tag_size (tag_mem32);

		params->u.mem.start = bd->bi_dram[i].start;
		params->u.mem.size = bd->bi_dram[i].size;

		params = tag_next (params);
	}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */


static void setup_commandline_tag (bd_t *bd, char *commandline)
{
	char *p;

	if (!commandline)
		return;

	/* eat leading white space */
	for (p = commandline; *p == ' '; p++);

	/* skip non-existent command lines so the kernel will still
	 * use its default command line.
	 */
	if (*p == '\0')
		return;

	params->hdr.tag = ATAG_CMDLINE;
	params->hdr.size =
		(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

	strcpy (params->u.cmdline.cmdline, p);

	params = tag_next (params);
}

static void setup_end_tag (bd_t *bd)
{
	params->hdr.tag = ATAG_NONE;
	params->hdr.size = 0;
}

        其中开始TAG和结束TAG分别标识着TAG参数的开始和结束位置,而内存TAG是向内核传递内存的首地址和内核的大小,最后命令行TAG就是我们在前面获得的命令行信息。而这个信息在内核的启动中非常有用。


启动Linux前的准备清除工作:

        启动Linux前的准备工作其实主要是关中断以及关I/D cache。他的代码为:

int cleanup_before_linux (void)
{
	/*
	 * this function is called just before we call linux
	 * it prepares the processor for linux
	 *
	 * we turn off caches etc ...
	 */

	unsigned long i;

	disable_interrupts ();

	/* turn off I/D-cache */
	asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));
	i &= ~(C1_DC | C1_IC);
	asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));

	/* flush I/D-cache */
	i = 0;
	asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));

	return (0);
}

启动Linux内核:

        而完成上面的所有操作之后我们就可以启动Linux了。而启动的代码也很简单就是直接调用函数指针就可以了。他的代码为:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

        好了,讲到这里关于u-boot的知识就都讲完了,希望对您有所帮助。

参考文章:

zImage和uImage的区别

kernel编译生成Image zImage uImage的区别

uboot 命令分析(一) — bootm

u-boot源代码

[uboot] uboot启动kernel篇(三)——uboot解析uImage的kernel信息

ARM uboot Legacy uImage 和 fit img (Flattened uImage Tree)原理介绍

 

 

 

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值