韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程

读出并运行启动命令

uboot 在main.c下图432行的位置, main_loop() 函数,读取了环境变量"bootcmd" ,也就是启动命令。并 run_command(),即执行bootcmd。
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第1张图片
这个环境变量可以在uboot 中使用print命令查看:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第2张图片

分析启动命令

启动命令如下:
nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
启动命令的含义:

第一条:

nand read.jffs2 0x30007FC0 kernel
从nand flash的 kernel 分区读取内核到地址0x30007FC0。

kernel 分区的定义在 include/configs/100ask24x0.h 中:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第3张图片
上面分区的名字不重要,重要的是起始地址和大小。在uboot 中可以使用mtd 命令查看分区:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第4张图片
可见kernel 分区的起始地址是0x00060000,大小是0x00200000。
因此,以下两条命令有一样的效果:
nand read.jffs2 0x30007FC0 kernel
nand read.jffs2 0x30007FC0 0x00060000 0x00200000

第二条

bootm 0x30007FC0
bootm 命令的功能可以分为两部分:

  1. 移动内核到内核加载地址(内核头部中定义了ih_load)。
  2. 启动内核。调用 do_boot_linux() 函数。该函数会首先设置启动参数,如RAM大小等,然后跳到入口地址。

flash 上存的内核是 uImage,其结构是:头部+真正的内核。
头部包括:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第5张图片
上面重要的是加载地址 ih_load 和 入口地址 ih_ep 这两个值。bootm这个命令会将内核移动到 ih_load处,然后跳到 ih_ep 处执行。
入口地址 ih_ep 是0x30008000,头部是64字节,所以在加载的时候要将内核(包括头部)放到地址0x30007FC0 (0x30008000-64)处,这样可以避免bootm 再次移动内核,节省时间。

0x30008000 的由来可以参考这里:
简言之就是datasheet 中定义的内存映射基地址是0x3000 0000,且有32k(0x8000)用来存放内核页表。代码中定义如下:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第6张图片
上图代码中可以看出,SDRAM 的起始地址是0x30000000,大小是64M。

判断 加载地址(ih_load) 和 真正内核地址(data) 是否相同的代码如下:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第7张图片

do_boot_linux() 分析

do_boot_linux() 最终通过调用

设置启动参数

启动参数首先被存储到RAM 的某个约定好的位置,内核启动后再从这个约定好的位置读取启动参数。
do_bootm() -> do_boot_linux 中有如下代码

#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

代码中setup_XXXX_tag()_bd 就是设置各种启动参数的。

参数配置举例

例一:设置启动参数保存的地址等 setup_start_tag (bd);

static void setup_start_tag (bd_t *bd)
{
	// params 是一个不断移动的指针,指向当前启动参数将要保存到的地址
	// 100ask24x0.c : gd->bd->bi_boot_params = 0x30000100;
	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 指针指向下一块内存区域
	params = tag_next (params);
	// #define tag_next(t)	((struct tag *)((u32 *)(t) + (t)->hdr.size))
}

tag 结构体:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第8张图片

例二:设置启动参数保存的地址等setup_memory_tags (bd_t *bd);

#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);
		
		// 此处设置的RAM 的基地址,以及大小,代码如下
		//100ask24x0.c : gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
		//100ask24x0.c : gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
		// 100ask24x0.h : #define PHYS_SDRAM_1		0x30000000 /* SDRAM Bank #1 */
		// 100ask24x0.h : #define PHYS_SDRAM_1_SIZE	0x04000000 /* 64 MB */
		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 */

启动参数配置结果

启动参数配置完成后,内存上的数据如下图所示,从0x30000100 位置开始保存了所有的启动参数:
韦东山 jz2440 学习笔记 —— uboot(五)—— uboot启动内核过程_第9张图片

跳到内核处,启动内核

armlinux.c -> do_bootm_linux():

... 
// 给函数指针theKernel 赋值
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
...
// 启动内核,第二个参数是机器id,第三个参数就是启动参数所在的地址
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
...

你可能感兴趣的:(uboot)