环境: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启到了很大的知道作用。
扇区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段中所有的数据,因为这里保存的地址都是基于原有的启动地址。
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、网络通信