JZ4770 UBOOT阅读 (MIPS)

环境:JZ4770

启动方式: tf卡


先看DataSheet中描述的启动过程:

1、复位或者上电,CPU默认禁止所有中断,并且都取外部引脚判断启动选项:从Nand启动,从SD卡启动等

2、假若从Nand启动:首先都取NAND开始数据中都取信息:总线宽度、读取时间、扇区大小。然后从Nand中都取8K的数据(失败的话,则都取第二个8K数据),然后从偏移12字节开始执行。

3、假若从SD卡启动:先初始化D0、CLK、CMD。然后加载8K数据(先加载到DCache,然后从DCache拷贝到ICache再跳转到ICache执行),并执行。(需要判断前4个字节为MSPL=0x4d53504c)

4、若从EMMC启动,D0,CLK,CMD先初始化,然后加载16K数据到缓存并执行

6、若从USB启动,首先加载的数据大小未定。

注意:JZ4770的Cache大小为16KB [0x80000000-0x80004000]

本文中使用的应该是EMMC启动方式。



make xxx_config

首先当然是配置,Makefile文件中能找到xxx_config,执行的结果是设置一些宏定义,并指定相应的config.h,这个文件中能找到很多有用的东西


make

编译生成的文件包括: 

uboot.bin             二进制格式

u-boot                   ELF格式

u-boot.srec          S-RECORD格式

u-boot-msc.bin   cat msc_spl/u-boot-spl-pad.bin u-boot.bin > u-boot-msc.bin         由u-boot-spl-pad.bin和u-boot.bin合成  (u-boot-spl-pad.bin用于支持SPL模式,SD卡启动方式)

mbr-u-boot-msc.bin    cat mbr.bin u-boot-msc.bin > mbr-uboot-msc.bin                    由mbr.bin和u-boot-msc.bin合成(mbr.bin分区信息)

system.map         符号表

查看system.map文件,可以看到所有符号的地址信息,这些信息的生成U-boot.lds启到了很大的知道作用。


看到这里,会发现实际上最后生成的执行文件按照扇区顺序是这样分布的:
扇区0: mbr.bin  (分区信息)
扇区1-32:u-boot-spl-pad.bin (总共16K,应该是CPU启动自加载的数据)

扇区33-xx:  u-boot.bin 

可以看出,整个可执行文件包括3个部分

扇区0保存着分区信息,可以用来多整个tf卡进行分区,不过需要注意的是U-Boot和Kernel Image并不保存在分区中,也就是说,整个tf卡实际上为U-Boot和Kernel保留了一些空间(我的项目中为它们分别保留了4M的空间,也就是说第一个分区是从第8M空间开始的)

扇区1-32保存着SPL启动执行文件,所谓的SPL(Secondary Program Loader),简单来说,CPU上电时,自动从tf卡中读取16K数据(CPU有自己的BootRom),这16K数据放在了CPU的DCache中,然后执行。若是Uboot执行文件小于16K的话没有问题,若是大于16K的话,后面的代码怎么执行?CPU从TF卡中启动与从Flash中获启动不一样。读写TF卡是不需要经过地址总线的,也就是说没有地址映射的概念。但是代码中的一些跳转地址(不管跳转的地址是虚拟地址还是物理地址)指的是存储器中的地址。所以需要先将TF卡中的数据拷贝到存储器中,这样,遇到跳转才不会出现问题。

扇区33-xx保存着另一份启动执行文件。


tf卡整个启动过程大概是:

CPU自加载1-32扇区数据到DCache中,执行u-boot-spl-pad.bin中的代码。 最后将u-boot.bin的前512K数据加载到内存中。

CPU跳转到u-boot.bin的开始处,执行u-boot.bin中的代码。


u-boot-spl-pad.bin的生成:

SPL阶段的目的是将u-boot.bin加载到内存中执行,因此只需要对CPU,内存以及串口打印做一些初始化工作即可。

另外DCache的地址范围:[0x80000000-0x8004000],且第一个扇区是mbr信息,所以将启动地址设置为0x8000200。这些信息可以从生成的u-boot-spl.map文件中看出来。

.text           0x0000000080000200     0x1be0
 *(.text)
 .text          0x0000000080000200       0x60 start.o
                0x0000000080000200                _start
 .text          0x0000000080000260      0x7f0 msc_boot_jz4770.o
                0x00000000800006b0                mmc_block_readm
                0x0000000080000374                mmc_init
                0x00000000800008f8                spl_boot
 .text          0x0000000080000a50       0xa0 cpu.o

从上面可以看到text段放置在u-boot-spl-pad.bin的最前面,这个在编译时通过u-boot.lds指定的。并且.text段开始地址是0x8000200,这也是通过编译时指定的

mips-linux-gcc -T u-boot.lds -Ttext 0x8000200  -o u-boot-spl-pad.bin

另外,需要注意的是在编译时指定了宏定义CONFIG_MSC_SPL,这表明当前处于SPL启动阶段。

文件:board/xxx/U-boot.lds

规划生成的Uboot文件的Flash布局和内存布局。

参考:http://blog.sina.com.cn/s/blog_6035432c01010bxh.html

前三行一般为:输出文件格式("elf-littlearm")、输出文件运行平台(arm)、输出文件执行入口(_start)

SECTIONS

SECTIONS{
...
secname start BLOCK(align) (NOLOAD) : AT ( ldadr )
{contents } >region :phdr=fill
...
}
注意:secname和contents是必须有的。


从生成的u-boot-spl.map文件中还可以看到.text段的开始处也就是执行文件的入口是start.o文件中的_start。从Makefile中,可以看到start.o是有start.s生成的,在start.s中可以找到_start标记。

文件:board/xxx/start.S

文件开始处代码:

#define JZ_MSCBOOT_CFG       0x4d53504c("MSPL")

#define RVECENT(f,n) \
   b f; nop
#define XVECENT(f,bev) \
   b f     ;           \
   li k0,bev

	.set noreorder

	.globl _start
	.text
_start:

#if defined(CONFIG_MSC_SPL)
	.word   JZ_MSCBOOT_CFG
#else
	.word JZ4760_NORBOOT_CFG
#endif
.set noreorder 不允许指令重排 (为了防止流水线阻塞,编译器默认允许指令重排以提高指令流速度,若不允许指令重排,则需要在延时槽中显示添加nop指令)

.globl _start     声明全局符号,允许外部访问和获取

.text                  声明.text程序段

_start:           标记

在这个文件中,整个都是汇编代码,主要工作包括:

A、CPU的初始化(诸如:配置寄存器的配置、CPU时钟设置、中断异常的处理等)     

启动标记,用于区分是否使用USB启动方式(USB烧录)

.word   JZ_MSCBOOT_CFG   //0x4d53504c 
如注释所述初始化Status和Cause寄存器(参考See Mips Run 5.8)

/* STATUS register */
	/*
	 * CU0=UM=EXL=IE=0, BEV=ERL=1, IP2~7=1 IM0:IM1=3 IPL=3
	 */
	li	t0, 0x0040FC04
	mtc0	t0, CP0_STATUS

	/* CAUSE register */
	/* IV=1, use the specical interrupt vector (0x200) */
	li	t1, 0x00800000
	mtc0	t1, CP0_CAUSE
B、定位全局数据区,初始化gp寄存器
/* Initialize GOT pointer.
	*/
	bal     1f
	nop
	.word   _GLOBAL_OFFSET_TABLE_
	1:
	move    gp, ra
	lw      t1, 0(ra)
	move	gp, t1
GOT是UBoot自定的段,可以在u-boot.lds中查到

. = ALIGN(16);
	_gp = .;
	__got_start = .;
	.got  : { *(.got) }
	__got_end = .;
bal 1f 将ra赋值为_GLOBAL_OFFSET_TABLE_(编译器自动生成),最后将_GLOBAL_OFFSET_TABLE_存入gp中。

C、简单初始化board信息

对于tf卡启动,会直接跳转到spl_boot

	la	sp, 0x80004000 <span style="font-family: Arial, Helvetica, sans-serif;">//初始化栈顶为0x80400000</span>
	la	t9, spl_boot

	j	t9
	nop

对于FLash启动方式来说,跳转的是board_init_f

	li	t0, CFG_SDRAM_BASE + CFG_INIT_SP_OFFSET //初始化栈顶为0x80400000
	la	sp, 0(t0)

	la	t9, board_init_f
	j	t9
	nop
上述代码,初始化栈顶为0x80400000,同时跳转到board_init_f中。设置sp的目的在于board_init_f是一个c语言函数(board_init_f不能使用malloc,因为没有设置好堆)

spl_boot和board_init_f都是C语言实现的,因此需要栈支持。我们先看spl_boot,因为这是tf卡启动方式支持的。

文件:board/xxx/jz4770.c

void spl_boot(void)
{
	void (*uboot)(void);

	/*
	 * Init hardware
	 */

	__cpm_start_dmac(); //使能dmac
	__cpm_start_ddr();   //使能ddr 
	/* enable mdmac's clock */
	REG_MDMAC_DMACKES = 0x3;

	REG_CPM_MSCCDR = CFG_CPU_SPEED/20000000 - 1;
	REG_CPM_CPCCR |= CPM_CPCCR_CE;

	uart_init(); //初始化uart
	serial_init(); //初始化串口打印

	serial_puts("\n\nMSC Secondary Program Loader\n");


	pll_init(); //pll初始化
	sdram_init(); //内存初始化

	/*
	 * Load U-Boot image from NAND into RAM
	 */
	mmc_load(CFG_MSC_U_BOOT_SIZE, (uchar *)CFG_MSC_U_BOOT_DST);  //如注释所说

	uboot = (void (*)(void))CFG_MSC_U_BOOT_START;

	serial_puts("Starting U-Boot ...\n");

	/*
	 * Flush caches
	 */
	flush_cache_all();//现在内存已经更新了,Cache需要重新初始化

	/*
	 * Jump to U-Boot image
	 */
	(*uboot)(); //跳转到uboot
}
前面提过,CPU开始执行时,所有的指令和数据其实都是在DCache,所以我们需要先将程序从SD卡中先搬移到内存中。

mmc_load从SD卡中第32扇区开始,拷贝1024个扇区的数据到0x80100000。 这些数据就是u-boot.bin的数据。(按道理应该从第33个扇区开始拷贝u-boot.bin的数据,在后面可以看到,我们的代码实际上会从0x80100200开始执行,这样会忽略掉第32个扇区的代码)。


在继续之前,先看看u-boot.bin的如何生成的。


u-boot.bin生成:

这个文件的生成实际上和u-boot-spl-pad.bin的生成没有什么不同,都需要指定u-boot.lds和-Ttext。启动的入口也是_start,不过入口地址却并成了0x80100200。这些信息可以从生成的u-boot.map文件中可看出

.text           0x0000000080100200    0x1b2b0
 *(.text)
 .text          0x0000000080100200      0x160 cpu/mips/start.o
                0x0000000080100230                relocate_code
                0x0000000080100200                _start
 .text          0x0000000080100360      0x630 lib_mips/libmips.a(board.o)

另外,还需要注意的是编译时指定的宏定义CONFIG_MSC_SPL已经消失,这意味着当前阶段并不是SPL阶段。这样做的意义在于:

u-boot.bin不需要知道之前是否进行过SPL


文件:board/xxx/start.S

又看到了这个文件,不过由于没有顶CONFIG_MSC_SPL,这个文件中代码执行的逻辑或发生变化。最明显的地方在于我们不跳转到spl_boot,而是跳转到board_init_f中

	li	t0, CFG_SDRAM_BASE + CFG_INIT_SP_OFFSET //0x80400000
	la	sp, 0(t0)

	la	t9, board_init_f
	j	t9
	nop

文件:Lib_mips/Board.c

void board_init_f(ulong bootflag)
{
	gd_t gd_data, *id;
	bd_t *bd;
	init_fnc_t **init_fnc_ptr;
	ulong addr, addr_sp, len = (ulong)&uboot_end - TEXT_BASE;  //得到uboot执行文件的大小。。
	ulong *s;
#ifdef CONFIG_PURPLE
	void copy_code (ulong);  //忽略
#endif

	/* Pointer is writable since we allocated a register for it.
	 */
	gd = &gd_data;
	/* compiler optimization barrier needed for GCC >= 3.4 */
	__asm__ __volatile__("": : :"memory");//内存屏障

	memset ((void *)gd, 0, sizeof (gd_t));
//初始化函数列表,包括定时器初始化、串口打印、环境变量初始化等。。。
	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}

	/*
	 * Now that we have DRAM mapped and working, we can
	 * relocate the code and continue running from DRAM.
	 *///gd->ram_size在内存初始化中已经被设置好了。
	addr = CFG_SDRAM_BASE + gd->ram_size; //CFG_SDRAM_BASE=0x80000000 include/configs/pisesc.h中定义
//下面对内存进行分配
	/* We can reserve some RAM "on top" here.
	 */
//保留4k空间
	/* round down to next 4 kB limit.
	 */
	addr &= ~(4096 - 1);
	debug ("Top of RAM usable for U-Boot at: %08lx\n", addr);
//LCD使用空间
#ifdef CONFIG_LCD
	/* reserve memory for LCD display (always full pages) */
	addr = lcd_setmem (addr);
	gd->fb_base = addr;
#endif /* CONFIG_LCD */
//U-Boot使用空间(16K for Stage2)
	/* Reserve memory for U-Boot code, data & bss
	 * round down to next 16 kB limit
	 */
	addr -= len;
	addr &= ~(16 * 1024 - 1);

	debug ("Reserving %ldk for U-Boot at: %08lx\n", len >> 10, addr);
//堆空间
	 /* 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);
//Board Global Info
	/*
	 * (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 %d Bytes for Board Info at: %08lx\n",
			sizeof(bd_t), addr_sp);

	addr_sp -= sizeof(gd_t);
	id = (gd_t *)addr_sp;
	debug ("Reserving %d Bytes for Global Data at: %08lx\n",
			sizeof (gd_t), addr_sp);
//启动参数空间
 	/* Reserve memory for boot params.
	 */
	addr_sp -= CFG_BOOTPARAMS_LEN;
	bd->bi_boot_params = addr_sp;
	debug ("Reserving %dk for boot params() at: %08lx\n",
			CFG_BOOTPARAMS_LEN >> 10, addr_sp);
//栈空间
	/*
	 * Finally, we set up a new (bigger) stack.
	 *
	 * Leave some safety gap for SP, force alignment on 16 byte boundary
	 * Clear initial stack frame
	 */
	addr_sp -= 16;       //16byte gap
	addr_sp &= ~0xF;       //16字节对齐
	s = (ulong *)addr_sp;
	*s-- = 0;
	*s-- = 0;
	addr_sp = (ulong)s;
	debug ("Stack Pointer at: %08lx\n", addr_sp);
//设置board information
	/*
	 * Save local variables to board info struct
	 */
	bd->bi_memstart	= CFG_SDRAM_BASE;	/* start of  DRAM memory */
	bd->bi_memsize	= gd->ram_size;		/* size  of  DRAM memory in bytes */
	bd->bi_baudrate	= gd->baudrate;		/* Console Baudrate */

	memcpy (id, (void *)gd, sizeof (gd_t));

	/* On the purple board we copy the code in a special way
	 * in order to solve flash problems
	 */
#ifdef CONFIG_PURPLE
	copy_code(addr); //忽略
#endif
        //重定向
	relocate_code (addr_sp, id, addr);

	/* NOTREACHED - relocate_code() does not return */
}

上面涉及到内存屏障的概念:
1、设置内存屏障后,编译器认为内存中的所有数据发生了改变(而Cache以及Register不知道),所以,接下来的对数据访问都必须首先从内存中读出(因为Cache和Register中的数据失效了)。
2、格式固定为__asm__ __volatile__ ("":::"memory")。
为什么要设置内存屏障?
谁来告诉我。

init_sequence是一个函数指针列表,定义如下:

init_fnc_t *init_sequence[] = {
#if defined(CONFIG_JZSOC)
	jz_board_init,		/* init gpio/clocks/dram etc. */
#endif
	timer_init,
	env_init,		/* initialize environment */
#ifdef CONFIG_INCA_IP
	incaip_set_cpuclk,	/* set cpu clock according to environment variable */
#endif
	init_baudrate,		/* initialze baudrate settings */
	serial_init,		/* serial communications setup */
	console_init_f,
	display_banner,		/* say that we are here */
	checkboard,
	init_func_ram,
	NULL,
};

其中,声明的顺序就是初始化的顺序。注释对各个初始化函数做了简单介绍。
jz_board_init
初始化gpio,例如对第1组GPIO的GPIO27设置为输出:__gpio_as_output0(32*1+27);
初始化PLL
初始化时钟
初始化RTC
timer_init
初始化定时器
env_init 
设置gd->env_addr和gd->env_valid
init_baudrate 
设置gd->baudrate
serial_init  
初始化Uart相关寄存器并清除相关FIFO
console_init_f 
设置gd->have_console=1
display_banner 
打印自己的信息放这里吧
checkboard 
设置gd->mem_clk
init_func_ram
得到ramsize如下:

long int initdram(int board_type)
{
	u32 ddr_cfg;
	u32 rows, cols, dw, banks;
	ulong size;
	ddr_cfg = REG_DDRC_CFG;
	rows = 12 + ((ddr_cfg & DDRC_CFG_ROW_MASK) >> DDRC_CFG_ROW_BIT);
	cols = 8 + ((ddr_cfg & DDRC_CFG_COL_MASK) >> DDRC_CFG_COL_BIT);

	dw = (ddr_cfg & DDRC_CFG_DW) ? 4 : 2;
	banks = (ddr_cfg & DDRC_CFG_BA) ? 8 : 4;

	size = (1 << (rows + cols)) * dw * banks;
	size *= (DDR_CS1EN + DDR_CS0EN);

	return size;
}

它的计算方式是:bank*(1<<(rows+cols))*dw*(csex) , 其中dw是位宽(16bit为2字节),csex表示片选。得到的记过存储在全局结构体中:gd->ram_size



继续往下运行得到内存从高到低的布局(高地址到低地址),注意其中有些地址需要对齐
LCD空间                    //gd->fb_base  
UBOOT空间              //计算uboot_end-TEXT_BASE
MALLOC预留          
Board Info预留          //gd->bd - gd
Global Data预留  
BOOT参数预留          //gd->bd->bi_boot_params
栈预留   


接下来设置BoardInfo内存数据(gd->bd 以及gd)
===========================================
关于malloc相关实现可以参考:dlmalloc-2.6.2源码阅读


从整个函数可以看出,将整个内存我们做了分配,其中最重要的是,我们为整个U-boot.BIN可执行文件分配了内存。

在执行board_init_f时,CPU运行在0x80100000~0x80100000+512K的空间上,而我们现在需要将整个U-boot.BIN拷贝到一个新的内存空间,这就设计到relocate概念。简单说,在编译生成u-boot.bin文件时,认为我们的代码会运行在0x80100000开始的地址空间上,那些全局数据的保存地址都是以这个为前提的。。现在我们将代码拷贝到一个新的内存空间上,这些全局数据的保存地址就会发生变化。


msc_spl/board/xxx/Start.S : relocate_code

传入的参数是栈顶指针,全局设置数据指针和用于存储Uboot代码的内存空间。

总的说来,这个函数的工作是将UBOOT代码从原有的地址中拷贝到内存中,然后跳转到in_ram地址执行,最后调用board_init_r。

这中间涉及到PIC的GOT。

/*
 * void relocate_code (addr_sp, gd, addr_moni)
 *
 * This "function" does not return, instead it continues in RAM
 * after relocating the monitor code.
 *
 * a0 = addr_sp
 * a1 = gd
 * a2 = destination address
 */
	.globl	relocate_code
	.ent	relocate_code
relocate_code:
	move	sp, a0		/* Set new stack pointer		*/
        //准备拷贝,长度(字节):uboot_end_data-TEXT_BASE,源地址:TEXT_BASE,目标地址:a2
	li	t0, TEXT_BASE          //Flash代码
	la	t3, in_ram
	lw	t2, -12(t3)	/* t2 <-- uboot_end_data	*/
	move	t1, a2
	subu	t2, t2, t0	/* total count in bytes */
	srl	t2, t2, 2	/* total count in word */
	li	t4, 0xa0000000
	or	t0, t0, t4	/* 0xA0000000-0xBFFFFFFF 之间的空间是uncached和unmapped 直接*/

	/*
	 * 新gp: (老gp-TEXT_BASE)+RAM_BASE
	 * 偏移:新gp-老gp
	 * 
	 */
	move	t6, gp
	subu	gp, TEXT_BASE
	add	gp, a2			/* gp now adjusted		*/
	subu	t6, gp, t6		/* t6 <-- relocation offset	*/

	/*
	 * t0 = source address (in flash)
	 * t1 = target address (in memory)
	 * t2 = total count in word
	 */
	/* 
	 * 拷贝代码到Ram中
	 */
1:
	lw	t3, 0(t0)
	addiu	t0, 4
	addiu	t2, t2, -1
	sw	t3, 0(t1)
	bgtz	t2, 1b
	addiu	t1, 4			/* delay slot			*/

	/* 初始化DCache和ICache,从0x80000000-(0x80000000+16K)
	   每次地址偏移32字节      */
	/* flush d-cache */
	.set	mips32
	li	t0, KSEG0
	addu	t1, t0, CFG_DCACHE_SIZE
2:
	cache	Index_Writeback_Inv_D, 0(t0)
	bne	t0, t1, 2b
	addi	t0, CFG_CACHELINE_SIZE

	sync

	/* flush i-cache */
	li	t0, KSEG0
	addu	t1, t0, CFG_ICACHE_SIZE
3:
	cache	Index_Invalidate_I, 0(t0)
	bne	t0, t1, 3b
	addi	t0, CFG_CACHELINE_SIZE

	/* Invalidate <span style="color:#ff0000;">BTB 目的</span>*/
	mfc0	t0, CP0_CONFIG, 7
	nop
	ori	t0, 2
	mtc0	t0, CP0_CONFIG, 7
	nop

	.set	mips0

	/* 跳转的目的地址: a+偏移量(偏移量:in_ram-_start,注意in_ram和_start都是flash的地址)
	 */
	addi	t0, a2, in_ram - _start
	j	t0
	nop

	.word	uboot_end_data
	.word	uboot_end
	.word	num_got_entries

in_ram:


现在所有的代码都被搬移到内存中了,我们开始进入了Stage2,为了尽快能够使用C语言完成UBoot接下来的工作,在in_ram下面的代码中,我们为C语言准备了运行环境,这些环境包括:

栈                 这个在board_init_f中就已经设置好了

全局数据     之前只是对gp做了重定向,所有的全局数据地址都必须被重定向

	/* Now we want to update GOT.
	 */
	lw	t3, -4(t0)	/* t3 <-- num_got_entries	*/
	addiu	t4, gp, 8	/* Skipping first two entries.	*/
	li	t2, 2
1:
	lw	t1, 0(t4)
	beqz	t1, 2f
	addu	t1, t6
	sw	t1, 0(t4)
2:
	addiu	t2, 1
	blt	t2, t3, 1b
	addiu	t4, 4		/* delay slot			*/
清除BSS   BSS段全部清空为0,这样临时变量申请后不初始化时,值为0

	/* Clear BSS.
	 */
	lw	t1, -12(t0)	/* t1 <-- uboot_end_data	*/
	lw	t2, -8(t0)	/* t2 <-- uboot_end		*/
	add	t1, t6		/* adjust pointers		*/
	add	t2, t6

	sub	t1, 4
1:	addi	t1, 4
	bltl	t1, t2, 1b
	sw	zero, 0(t1)	/* delay slot			*/

从上面可以看出,要支持PIC,需要注意:

A、修改gp

B、修改got段中所有的数据,因为这里保存的地址都是基于原有的启动地址。


完成了以上工作,就可以进行跳转了,这次是board_init_r,注意和之前的board_init_f之间的区别。


Lib_mips/Board.c : board_init_r

在之前我们做了一些板极初始化工作(ram,波特率,PLL等),并将一些初始化的结果存储在gd中。这些结果在board_init_r是可以直接使用的。

void board_init_r (gd_t *id, ulong dest_addr)
{
	cmd_tbl_t *cmdtp;
	ulong size;
	extern void malloc_bin_reloc (void);
#ifndef CFG_ENV_IS_NOWHERE
	extern char * env_name_spec;
#endif
	char *s, *e;
	bd_t *bd;
	int i;

	gd = id;
	gd->flags |= GD_FLG_RELOC;	/* tell others: relocation done */

	debug ("Now running in RAM - U-Boot at: %08lx\n", dest_addr);

	gd->reloc_off = dest_addr - TEXT_BASE;

	monitor_flash_len = (ulong)&uboot_end_data - dest_addr;

	/*
	 * We have to relocate the command table manually
	 */
 	for (cmdtp = &__u_boot_cmd_start; cmdtp !=  &__u_boot_cmd_end; cmdtp++) {
		ulong addr;

		addr = (ulong) (cmdtp->cmd) + gd->reloc_off;
#if 0
		printf ("Command \"%s\": 0x%08lx => 0x%08lx\n",
				cmdtp->name, (ulong) (cmdtp->cmd), addr);
#endif
		cmdtp->cmd =
			(int (*)(struct cmd_tbl_s *, int, int, char *[]))addr;

		addr = (ulong)(cmdtp->name) + gd->reloc_off;
		cmdtp->name = (char *)addr;

		if (cmdtp->usage) {
			addr = (ulong)(cmdtp->usage) + gd->reloc_off;
			cmdtp->usage = (char *)addr;
		}
#ifdef	CFG_LONGHELP
		if (cmdtp->help) {
			addr = (ulong)(cmdtp->help) + gd->reloc_off;
			cmdtp->help = (char *)addr;
		}
#endif
	}
可以看到函数的参数接口传入了2个参数:id中存储了板极信息(以及核心信息),dest_addr传入的唯一意义是得到gd->reloc_off

注意到gd并没有被声明却能够使用,实际上,在global_data.h中能够找到

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("k0")
所以再去搜索一下DELARE_GLOBAL_DATA_PTR吧。


接下来设置command table,这些数据被认为存储在.u_boot_cmd段中。对照u-boot.lds

. = ALIGN(16);
	__u_boot_cmd_start = .;
	.u_boot_cmd : { *(.u_boot_cmd) }
	__u_boot_cmd_end = .;

	uboot_end_data = .;
	num_got_entries = (__got_end - __got_start) >> 2;
究竟是如何存储到制定段中,毕竟.u_boot_cmd不是编译器定义的标准段。这其中设计到编译器选项__attribute__的使用。我们可以在command.c和command.h中找到:

command.h

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

#ifdef  CFG_LONGHELP

#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}

#else	/* no long help info */

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}

#endif	/* CFG_LONGHELP */
command.c
int
do_version (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
	extern char version_string[];
	printf ("\n%s\n", version_string);
	return 0;
}

U_BOOT_CMD(
	version,	1,		1,	do_version,
 	"version - print monitor version\n",
	NULL
);
编译器编译源码时遇到U_BOOT_CMD定义的数据结构,这个数据结构被指定的__attribute__子选项编译到指定的段中。这样,version命令就被放到了__u_boot_cmd_start和__u_boot_cmd_end之间了。

我们现在不需要关心command table的内容,只是将内存中的地址做了修正。


然后初始化malloc空间相关,注意在board_init_f中为malloc预留了大量的空间,空间不大(大约128K大概就可以了)

/* initialize malloc() area */
	mem_malloc_init();
	malloc_bin_reloc();

注意malloc的空间的顶端正好挨着u-boot代码块,所以直接减去MALLOC_LEN就能得到malloc的空间的起始地址。

注意Dmalloc.c中定义的malloc_bin_reloc函数接口,关于dmalloc的介绍可以参考:dmalloc


接下来还要做一些其他的初始化工作

	/* relocate environment function pointers etc. */
	env_relocate();

	/* board MAC address */
	s = getenv ("ethaddr");
	for (i = 0; i < 6; ++i) {
		bd->bi_enetaddr[i] = s ? simple_strtoul (s, &e, 16) : 0;
		if (s)
			s = (*e) ? e + 1 : e;
	}

	/* IP Address */
	bd->bi_ip_addr = getenv_IPaddr("ipaddr");

#if defined(CONFIG_PCI)
	/*
	 * Do pci configuration
	 */
	pci_init();
#endif

/** leave this here (after malloc(), environment and PCI are working) **/
	/* Initialize devices */
	devices_init ();


	jumptable_init ();

	/* Initialize the console (after the relocation and devices init) */
	console_init_r ();

env_relocate得到环境参数相关

环境变量这些数据都定义在Env_Common.c中

uchar default_environment[] = {
#ifdef	CONFIG_BOOTARGS
	"bootargs="	CONFIG_BOOTARGS			"\0"
#endif
#ifdef	CONFIG_BOOTCOMMAND
	"bootcmd="	CONFIG_BOOTCOMMAND		"\0"
#endif
#ifdef	CONFIG_RAMBOOTCOMMAND
	"ramboot="	CONFIG_RAMBOOTCOMMAND		"\0"
#endif

初始化全局函数env_get_char, env_ptr指针(指向内存中的参数表), gd->env_valid, gd->env_addr.

其他初始化包括

devices_init初始化设备(可能包括:i2c, video, keboard, logbuf, serial_device等)

jumptable_init初始化gd->jt中的跳转函数表,例如:malloc, free, getenv, setenv, get_timer udelay, i2c_write, i2c_read 等

console_init_r这个会查找输入输出设备,从而以后可以使用printf函数准备?  (TO BE DONE)

其他misc_init_r, eth_initialize等


最后我们看到了整个UBoot的核心main_loop



Common/Main.c: main_loop()

从函数的名字可以看出来,这里有一个循环,这是为了能够循环响应用户的输入。不过这里,我们将忽略用户的响应,直接奔向boot流程。


common/cmd_bootm.c:do_bootm()

这个函数是加载启动内核入口,先来看看传入的参数:

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
自动启动时,uboot传入的命令是:bootm,因此argc=1 argv[0]="bootm"

不过对于SD卡启动方式来说,需要注意bootcmd环境变量:msc read 0x80600000 0x400000 0x300000;bootm

这意味着,在boot之前,已经从SD卡的4M偏移位置读取了3M的数据到内存地址0x80600000。

接下来的操作,都是以内核数据在内存0x80600000为前提的。

解析文件头

	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);

#ifdef CONFIG_HAS_DATAFLASH
	if (addr_dataflash(addr)){
		len  = ntohl(hdr->ih_size) + sizeof(image_header_t);
		read_dataflash(addr, len, (char *)CFG_LOAD_ADDR);
		addr = CFG_LOAD_ADDR;
	}
#endif	

A、判断ih_magic是否为0x27051956

B、判断hdr->ih_hcrc是否和数据校验值一致

C、根据需要校验内核数据区

/* 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);
D、判断ih_arch是否等于5(MIPS)

E、判断ih_comp内核文件压缩算法(本文使用的是GZIP),将数据解压到hdr->ih_load内存地址

	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;
F、判断hdr->ih_os进入do_bootm_linux


do_bootm_linux这个函数代表着整个UBoot即将成功

这个函数的前部分做了一些判断,只需要注意:

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

这里将ih_p转成一个函数指针,这个函数在后来就是内核启动的入口,函数的调用参数也是需要设置的。这些参数包括:

linux_argc, linux_argv, linux_env。接下来就是对这些参数值初始化的过程。

最后看到了

	theKernel (linux_argc, linux_argv, linux_env, 0);


可以看出,启动内核时,对于内核文件有严格的要求。所以在使用mkimage制作内核文件时,需要设置好参数。

上面看到theKernel的指针地址,就是由mkimage设置的,下面来跟踪这个值是如何设置,并让UBoot如何正确启动内核的。

Usage: mkimage -l image
          -l ==> list image header information
       mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -d data_file[:data_file...] image
          -A ==> set architecture to 'arch'
          -O ==> set operating system to 'os'
          -T ==> set image type to 'type'
          -C ==> set compression type 'comp'
          -a ==> set load address to 'addr' (hex)
          -e ==> set entry point to 'ep' (hex)
          -n ==> set image name to 'name'
          -d ==> use image data from 'datafile'
          -x ==> set XIP (execute in place)
我在编译内核时,最后能够看到:

mkimage -A mips -O linux -T kernel -C gzip \
		-a 0x80010000 -e 0x80015b40 \
		-n 'Linux-2.6.31.3' \
		-d arch/mips/boot/vmlinux.bin.gz arch/mips/boot/uImage
对应上文设置了

ih_arch(-A mips)

ih_os     (-O linux)

ih_type (-T kernel)

ih_comp (-C gzip)

ih_load  (-a 0x80010000)

ih_ep     (-e 0x80015b40)

ih_name (-n 'Linux-2.6.31.3')



UBoot基本流程大概如此,不过中间忽略了很多细节东西,还需要补充:

1、替换开机LOGO

2、打印输出是如何实现的

3、usb设备

4、网络通信



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值