嵌入式Linux学习:u-boot源码分析(6)--AM335X系列的2014.10版

题外话

    之前的5篇博文简单介绍了AM335X启动过程中关于SPL阶段的代码,可以看到SPL实际上没干什么活,它主要是初始化了SDRAM,并根据实际情况将MMC或者其他Flash中的uboot镜像一股脑的加载到SDRAM中的 低位位置。然后将cpu的指针指向了 SDRAM低位在SDRAM低位中执行uboot代码
    这是为了最大化的合理分配SDRAM的内存空间,让uboot可以更好的运行,也给后期linux内核腾出更多的空间,所以必须要将uboot放置到SDRAM的高位再执行。之前的博客中介绍了,很多变量以及代码段都是位置相关的,也就是运行地址在连接的时候就已经确定了,只有将相应的代码放置到特定的位置才可以保证没有错误的运行。所以讲低位的uboot赋值到SDRAM高位以后,必须要做重定位!    所以后续的代码任务就是:
    1. 准备一个临时的gd和sp
    2. 将SDRAM的高位人为的划分出一部分空间作为uboot区(类似的还有gd区,bd区,malloc区)
    3. 然后将原本保存到低位的uboot复制到高位,也就是上述的uboot区
    4. 重定位
    5.跳到高位的uboot中运行(已经重定位,所以运行没有问题)

汇编部分


    我们再次回到了start.S的reset处,还记得SPL的入口吗,实际上也是在这里,只是SPL和uboot分别是根据不同的条件做的编译,所以其函数定义也会有所不同,我们继续看这么一段汇编的代码:
reset:
	bl	save_boot_params /*lowlevel_init.S (arch\arm\cpu\armv7\omap-common)*/
	/*
	 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
	 * except if in HYP mode already
	 */
	mrs	r0, cpsr
	and	r1, r0, #0x1f		@ mask mode bits
	teq	r1, #0x1a		@ test for HYP mode
	bicne	r0, r0, #0x1f		@ clear all mode bits
	orrne	r0, r0, #0x13		@ set SVC mode
	orr	r0, r0, #0xc0		@ disable FIQ and IRQ
	msr	cpsr,r0

/*
 * Setup vector:
 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
 * Continue to use ROM code vector only in OMAP4 spl)
 */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
	/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
	mrc	p15, 0, r0, c1, c0, 0	@ Read CP15 SCTRL Register
	bic	r0, #CR_V		@ V = 0
	mcr	p15, 0, r0, c1, c0, 0	@ Write CP15 SCTRL Register

	/* Set vector address in CP15 VBAR register */
	ldr	r0, =_start
	mcr	p15, 0, r0, c12, c0, 0	@Set VBAR
#endif

	/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT/*this branch will only work in SPL*/
	bl	cpu_init_cp15 /*wlg: find out in this file, we do not explain in detial*/
	bl	cpu_init_crit /*wlg: find out in this file, please jump*/
#endif

	bl	_main	/*wlg: jump to arch/arm/lib/crt0.s*/
    没毛病,上面的代码和这个博客(1)中是一样的,至少看起来是一样的,它主要执行了以下功能:
    1. bl save_boot_params 这是第一个不同于SPL阶段的代码,之前save_boot_params保存了r0寄存器中的数据到SRAM中,在uboot中,它实际上只是:
ENTRY(save_boot_params)
	bx	lr			@ back to my caller
ENDPROC(save_boot_params)
	.weak	save_boot_params
        可以看到,此时的r0早就已经被SPL用了很多次,上次已经不再保存启动信息了,而且这时候启动信息也已经不再重要了,因为我们已经正在执行uboot了,所以上面的代码只是做了简单的返回。
    2.  请注意,这个时候CONFIG_SPL_BUILD不再被定义,所以后面的很多条件编译请自觉忽略。关闭了中断,设置了SVC后
    3. 这个时候我们会定义CONFIG_SKIP_LOWLEVEL_INIT,所以其后面的cpu_init_cp15等代码实际上不会被执行,而是直接来到了_main, arch/arm/lib/crt0.s

    接下来继续看_main代码,实际上也是和SPL一样的,只是部分条件编译不一样,如下:
ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
	ldr	sp, =(CONFIG_SPL_STACK)/* wlg: in spl, it seems be 0x40310000-sizeof(global_data)*/
#else
	ldr	sp, =(CONFIG_SYS_INIT_SP_ADDR)/* wlg: in uboot, it will be ?*/
#endif
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance		|		      	SPL			|			uboot		|*/
	mov	r2, sp	/* wlg: we record the end of address of the initial 	|sp is useful, r9 (gdata) will |sp is useful,  a new temp 	|*/
	sub	sp, sp, #GD_SIZE	/* allocate one GD above SP 				|be redefined in board_init_f	|GD(pointed by r9) will be  	|*/			
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance		|so next text is only to clear	| set above on sp,   gdata 	|*/
	mov	r9, sp		/* GD is above SP 							|a invalid memory			|be discarded 			|*/
	mov	r1, sp	/* wlg: we record the start address of the initial*/
	mov	r0, #0 	/*wlg : the num of initialition*/
clr_gd:
	cmp	r1, r2			/* while not at end of GD */
	strlo	r0, [r1]		/* clear 32-bit GD word */ /*wlg: ro >> [r1]*/
	addlo	r1, r1, #4		/* move to next */
	blo	clr_gd
#if defined(CONFIG_SYS_MALLOC_F_LEN) && !defined(CONFIG_SPL_BUILD)
	sub	sp, sp, #CONFIG_SYS_MALLOC_F_LEN
	str	sp, [r9, #GD_MALLOC_BASE]
#endif
	/* mov r0, #0 not needed due to above code */
	bl	board_init_f 	/*wlg: SPL: board_init_f - Function in spl.c (arch\arm\lib) , and it will not return, it will jump to uboot's start's*/
						/*wlg: Uboot: board_init_f - Function in Board.c (arch\arm\lib) at line 263 (199 lines), it will return*/
    上述代码主要完成了:
    1. 设置一个临时的sp,为后面的C语言函数调用做准备
    2. 在这个临时的sp上分配出一部分空间专门用来保存全局变量,并把这部分空间清0,并将r9指向这个全局变量!请注意,这里的全局变量已经不同于SPL阶段的gdata(全局变量),这里的全局变量是一个临时的用来保存关键数据的。所以请记住,在uboot的前期所用到的全局变量(会用gd指针表示)实际上指的就是建立在SRAM上的这部分空间,而且已经初始化为0,不再继承SPL阶段的全局变量(SPL阶段的全局变量虽然也是保存在SRAM中,但是是预先定义好的.data中)
    3. 定义一个malloc空间,并将malloc空间的地址赋值给最新的临时全局变量(因为r9所指就是全局变量的开头,其加上一个偏移量后就是gd->malloc_base)
    4. 万事俱备(有了临时sp和全局变量,为什么说是临时的呢?因为这两个玩意都还建立在SRAM上,而我们cpu目前试运行在SDRAM上的,我们最终希望sp和全局变量都是指向SDRAM的!),那么接下来就可以转跳到从语言函数board_init_f,他的定义位置和SPL不同,在Board.c (arch\arm\lib)

C语言部分

    这部分代码很长,我们分几次将其贴上
void board_init_f(ulong bootflag)
{
	bd_t *bd;
	init_fnc_t **init_fnc_ptr;
	gd_t *id;
	ulong addr, addr_sp;
#ifdef CONFIG_PRAM
	ulong reg;
#endif
	void *new_fdt = NULL;
	size_t fdt_size = 0;

	memset((void *)gd, 0, sizeof(gd_t));//DECLARE_GLOBAL_DATA_PTR make gd >> r9,
	//wlg:  all we now used gd is a point which saved in r9, so it is point to SRAM
	gd->mon_len = (ulong)&__bss_end - (ulong)_start;//wlg:  it will be the sum size of uboot, it should be made at ld?
#ifdef CONFIG_OF_EMBED
	/* Get a pointer to the FDT */
	gd->fdt_blob = __dtb_dt_begin;
#elif defined CONFIG_OF_SEPARATE
	/* FDT is at end of image */
	gd->fdt_blob = &_end;
#endif
	/* Allow the early environment to override the fdt address */
	gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
						(uintptr_t)gd->fdt_blob);
	//initial function sequence as defined before, containing serial_init()
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}

#ifdef CONFIG_OF_CONTROL
	/* For now, put this check after the console is ready */
	if (fdtdec_prepare_fdt()) {
		panic("** CONFIG_OF_CONTROL defined but no FDT - please see "
			"doc/README.fdt-control");
	}
#endif

	debug("monitor len: %08lX\n", gd->mon_len);
	/*
	 * Ram is setup, size stored in gd !!
	 */
	debug("ramsize: %08lX\n", gd->ram_size);
    这部分代码的主要工作是对对全局变量进行赋值,也就是上文gd所指向的建立在SRAM上的临时全局变量;然后利用函数指针进行各种初始化,展示如下:
init_fnc_t *init_sequence[] = {
	arch_cpu_init,		/* basic arch cpu dependent setup */
	mark_bootstage,
#ifdef CONFIG_OF_CONTROL
	fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
	board_early_init_f,
#endif
	timer_init,		/* initialize timer */
#ifdef CONFIG_BOARD_POSTCLK_INIT
	board_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHC
	get_clocks,
#endif
	env_init,		/* initialize environment */
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */ 	//wlg: at very first of SPL, we have no serial, so we get the default serial as cerrent
	console_init_f,		/* stage 1 init of console */			//wlg: while in boot, 
	display_banner,		/* say that we are here */ //wlg: now we have the first information printed out: U-Boot 2014.10...
	print_cpuinfo,		/* display cpu info (and speed) */
#if defined(CONFIG_DISPLAY_BOARDINFO)
	checkboard,		/* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
	init_func_i2c,
#endif
	dram_init,		/* configure available RAM banks */// wlg: make initialing to gd->ram-size
	NULL,
};
    将上面的函数指针数组中的元素所指向的函数全部执行一遍,每一个函数都比较简单,其作用在注释中都有所记录。初始化过程也会对上述全局变量gd中元素做修改,初始化过程也大量用到了环境变量,这些默认的环境变量在编译时就已经确定,下次专门开一篇来介绍环境变量!
	addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize();
	//wlg: now, we could divide the menory to parts as follow
#ifdef CONFIG_LOGBUFFER
#ifndef CONFIG_ALT_LB_ADDR
	/* reserve kernel log buffer */
	addr -= (LOGBUFF_RESERVE);
	debug("Reserving %dk for kernel logbuffer at %08lx\n", LOGBUFF_LEN,
		addr);
#endif
#endif

#ifdef CONFIG_PRAM
	/*
	 * reserve protected RAM
	 */
	reg = getenv_ulong("pram", 10, CONFIG_PRAM);
	addr -= (reg << 10);		/* size is in kB */
	debug("Reserving %ldk for protected RAM at %08lx\n", reg, addr);
#endif /* CONFIG_PRAM */

#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
	/* reserve TLB table */
	gd->arch.tlb_size = PGTABLE_SIZE;//wlg: tlb will be 64KB
	addr -= gd->arch.tlb_size;

	/* round down to next 64 kB limit */
	addr &= ~(0x10000 - 1);

	gd->arch.tlb_addr = addr;//wlg: record the addr in global_data
	debug("TLB table from %08lx to %08lx\n", addr, addr + gd->arch.tlb_size);
#endif

	/* round down to next 4 kB limit */
	addr &= ~(4096 - 1);
	debug("Top of RAM usable for U-Boot at: %08lx\n", addr);

#ifdef CONFIG_LCD
#ifdef CONFIG_FB_ADDR
	gd->fb_base = CONFIG_FB_ADDR;
#else
	/* reserve memory for LCD display (always full pages) */
	addr = lcd_setmem(addr);
	gd->fb_base = addr;
#endif /* CONFIG_FB_ADDR */
#endif /* CONFIG_LCD */

	/*
	 * reserve memory for U-Boot code, data & bss
	 * round down to next 4 kB limit
	 */
	addr -= gd->mon_len;
	addr &= ~(4096 - 1);

	debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);
 
////////////////////////////the different between SPL and uboot ///////////////////////////////
#ifndef CONFIG_SPL_BUILD//////////////wlg: in this branch, we put the global_data and board_data into SDRAM
	/*
	 * reserve memory for malloc() arena
	 */
	addr_sp = addr - TOTAL_MALLOC_LEN;
	debug("Reserving %dk for malloc() at: %08lx\n",
			TOTAL_MALLOC_LEN >> 10, addr_sp);
	/*
	 * (permanently) allocate a Board Info struct
	 * and a permanent copy of the "global" data
	 */
	addr_sp -= sizeof (bd_t);
	bd = (bd_t *) addr_sp;
	gd->bd = bd;
	debug("Reserving %zu Bytes for Board Info at: %08lx\n",
			sizeof (bd_t), addr_sp);

#ifdef CONFIG_MACH_TYPE
	gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
#endif

	addr_sp -= sizeof (gd_t);
	id = (gd_t *) addr_sp;
	debug("Reserving %zu Bytes for Global Data at: %08lx\n",
			sizeof (gd_t), addr_sp);

#if defined(CONFIG_OF_SEPARATE) && defined(CONFIG_OF_CONTROL)
	/*
	 * If the device tree is sitting immediate above our image then we
	 * must relocate it. If it is embedded in the data section, then it
	 * will be relocated with other data.
	 */
	if (gd->fdt_blob) {
		fdt_size = ALIGN(fdt_totalsize(gd->fdt_blob) + 0x1000, 32);

		addr_sp -= fdt_size;
		new_fdt = (void *)addr_sp;
		debug("Reserving %zu Bytes for FDT at: %08lx\n",
		      fdt_size, addr_sp);
	}
#endif

#ifndef CONFIG_ARM64
	/* setup stackpointer for exeptions */
	gd->irq_sp = addr_sp;
#ifdef CONFIG_USE_IRQ
	addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
	debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",
		CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif
	/* leave 3 words for abort-stack    */
	addr_sp -= 12;

	/* 8-byte alignment for ABI compliance */
	addr_sp &= ~0x07;
#else	/* CONFIG_ARM64 */
	/* 16-byte alignment for ABI compliance */
	addr_sp &= ~0x0f;
#endif	/* CONFIG_ARM64 */

 上面的代码完成了SDRAM高位地址的划分,从SDRAM顶上往下依次是:
    a. TLB,放置TLB。请注意这里只是划分出这么个空间作为TLB的区域,里面并没有数据!
    b.FB LCD,放置frame buffer 缓冲(一般没有)
    c. Uboot  .text .data .bss,放置完整的uboot代码段和bss数据。请注意这里只是划分出这么个空间作为uboot的区域,里面并没有数据!
    d. malloc 放置molloc空间。请注意这里只是划分出这么个空间作为malloc的区域,里面并没有数据!
    e. bd 放置board data 结构体。请注意这里只是划分出这么个空间作为bd的区域,里面并没有数据!
    f. gd 放置 global data结构体,这个gd结构体才是真正在后期uboot要用的全局变量。请注意这里只是划分出这么个空间作为gd的区域,里面并没有数据!
    g. IRQ stack。请注意这里只是划分出这么个空间作为IRQ的区域,里面并没有数据!
    h. sp,这个sp才是后期uboot要用的堆栈。请注意这里只是划分出这么个空间作为sp的区域,里面并没有数据!
    请注意,上述代码只是简单的划分了SDRAM的内存区域,比如说uboot区域,实际上当前正在执行的就是uboot程序,只是当前代码目前保存在SDRAM的低位,上述的uboot区是在SDRAM的高端,后期我们会将低位的uboot复制到高位的uboot区,再执行重定位,然后代码就可以安全的在高位执行了!

    在完后看
	gd->relocaddr = addr;	//wlg: the uboot start here
	gd->start_addr_sp = addr_sp; //wlg: the stack is here
	gd->reloc_off = addr - (ulong)&_start; //wlg:  record the offset, we will relocation.
	debug("relocation Offset is: %08lx\n", gd->reloc_off);
	if (new_fdt) {
		memcpy(new_fdt, gd->fdt_blob, fdt_size);
		gd->fdt_blob = new_fdt;
	}
	memcpy(id, (void *)gd, sizeof(gd_t));	//wlg: copy the temp global_data the the static global_data in SDRAM
    划分完SDRAM后,需要将关键参数保存到gd中,此时的gd仍是SRAM中的临时全局变量,最后那一句,就将在SRAM中的全局变量,复制到了处在SDRAM中的全局变量中。也就是说现在的全局变量有两个备份,一个在SRAM中,另一个在SDRAM中!

-------------------------------返回到_main------------------------
	ldr	sp, [r9, #GD_START_ADDR_SP]	/* sp = gd->start_addr_sp */	/*wlg: because r9 point to SRAM and keep gd*/
	bic	sp, sp, #7	/* 8-byte alignment for ABI compliance */			/*wlg: and also we have a copy in SDRAM*/
	ldr	r9, [r9, #GD_BD]		/* r9 = gd->bd */ 				/*wlg: we should change r9 to point to SDRAM*/
	sub	r9, r9, #GD_SIZE		/* new GD is below bd */			/*wlg:  <<<<reloc_off */		/*wlg:  now r9 is point to SDRAM*/
	add	lr, lr, r0												/*wlg:  lr will be the here function which run in SDRAM*/
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr */		/*wlg: so when we return, the program will run on SDRAM*/
	b	relocate_code			/*the function is locate in relocate.S (arch\arm\lib)*/
here:
    这段代码在注视中解释的很清晰了,其作用就是:
    a. 更新r9,让其指向SDRAM中的全局变量!因为以前面已经完成了SRAM到SDRAM的全局变量复制
        i.第一句中的r9还是指向SRAM,其作用就是将SRAM中的全局变量中的start_addr_sp赋值给sp,这样就完成了sp指向SDRAM中正确位置的工作!后做对齐
        ii.第三句的r9还是指向SRAM,其作用就是将SRAM中的全局变量中的bd赋值给r9
        iii.将此时的r9减去GD_SIZE的(即全局变量的size)后,再赋值给r9,此时的r9已经正确的指向了SDRAM中的全局变量(而且已经将SRAM中的相应数据复制进去)
    b. 将此时的here标号的地址赋值给lr,将r0赋值为重定位的偏移地址r0 = gd->reloc_off,将lr加上这个r0,这样就相当于完成了here(lr)的重定位!
    c.将r0赋值为gd->relocaddr,这个地址就是在SDRAM中重新分配的uboot的起始地址(这个uboot区域目前还没有数据)
    d. 跳到relocate_code进行重定位!注意这里不是用bl,所以实际上没有记录返回地址。前面我们已经将here的重定位地址赋给了lr,所以relocate_code返回的话,就会返回到重定位后的SDRAM中区执行,也就是在上述SDRAM中的uboot段中区执行。

    注意:uboot原本也是运行在SDRAM中,但是从SDRAM的地址分配来看,我们无法提前预知uboot的最佳运行位置。所以uboot的前期都是在SDRAM的低位运行的,而且前期的代码都是位置无关的,所以执行起来没有问题。直到重定位完成以后,才将uboot搬移到SDRAM中的uboot区(人为划分的,实际运行的最佳位置),这个时候开始,uboot才开始复杂的功能!所以接下来的重定位非常的关键。


    可看之前的博客-讲解重定位的那篇

你可能感兴趣的:(嵌入式Linux--uboot)