一、概述
芯片手册:S5PC100_UM_REV1.02.pdf,有如下描述:
S5PC100有32K ROM(iROM),96K SRAM(IRAM),复位后,程序在iROM运行。Bootloader分为2个阶段BL0和BL1。
BL0程序在iROM中,三星保密。功能:从启动设备(比如nand flash)加载BL1(u-boot的前16K)到iRAM。
BL1:IRAM中16K代码执行,会完成内存控制器的初始化把uboot从nand flash拷贝到DRAM,即第一次搬运,经常设定为搬到内存的0x27e00000地址。
u-boot终运行在高端地址,所以,有时u-boot剩下的代码,把u-boot代码做了个第二次搬运,搬到高端地址去。还把OS镜像,拷贝到了内存中。
二、u-boot第一阶段分析(续)
由汇编转到了C,分析board_init_f函数:
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
&nbsnbsp; ulong reg;
#endif
涉及到两个重要的数据结构:1)bd_t结构体,关于开发板信息(波特率,ip, 平台号,启动参数)。2)gd_t结构体成员主要是一些全局的系统初始化参数。需要用到时,用宏定义DECLARD_GLOBAL_DATA_PTT,指定占用寄存器r8,具体定义如下:
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate串口波特率 */
unsigned long bi_ip_addr; /* IP Address IP 地址*/
ulong bi_arch_number; /* unique id for this board 板子的id */
ulong bi_boot_params; /* where this board expects params 启动参数*/
struct /* RAM configuration RAM 配置*/
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
Gd_t结构体定义,如下:
typedef struct global_data {
bd_t *bd;
unsigned long flags; //指示标志,如设备已经初始化标志等
unsigned long baudrate; //串行口通信速率
unsigned long have_console; /* serial_init() was called */
#ifdef CONFIG_PRE_CONSOLE_BUFFER
unsigned long precon_buf_idx; /* Pre-Console buffer index */
#endif
unsigned long env_addr; /* Address of Environment struct 环境参数地址 */
unsigned long env_valid; /* Checksum of Environment valid? 环境参数CRC检验有效标志*/
unsigned long fb_base; /* base address of frame buffer 帧缓冲区基地址*/
……
} gd_t;
gd=(gd_t *)(CONFIG_SYS_INIT_SP_ADDR) &~0x07) .
CONFIG_SYS_INIT_SP_ADDR的定义如下:
#define CONFIG_SYS_INIT_SP_ADDR (0x22000)
0x22000 & ~0x07,为了保证结果八字节对齐。
查芯片手册,可知0x0002_0000----0x0003_8000范围,为96K,是IRAM的空间。
即gd指向IRAM的一个地址。
在本文中前面有声明DECLARE_GLOBAL_DATA_PTR
跟踪定义可看到下面形式:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
这个声明告诉编译器使用寄存器r8来存储gd_t类型的指针gd,即这个定义声明了一个指针,并且指明了它的存储位置。(即gd值放在寄存器r8中)
register表示变量放在机器的寄存器
volatile用于指定变量的值可以由外部过程异步修改
并且这个指针现在被赋值为CONFIG_SYS_INIT_SP_ADDR) &~0x07,
__asm__ __volatile__("": : :"memory");
memset((void *)gd, 0, sizeof(gd_t));
memory 强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
__asm__用于指示编译器在此插入汇编语句。
__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。 memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。
"":::表示这是个空指令。
gd->mon_len = _bss_end_ofs;
_bss_end_ofs的定义在start.S中:
.globl _bss_end_ofs
_bss_end_ofs:
.word __bss_end__ - _start
.word就是在当前地址_bss_end_ofs放值 __bss-end__ - _start。
看到是所以段的大小,此时,新开终端。打开u-boot.lds,找__bss_end__
Gd->mon_len存的值就是u-boot的实际大小。
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
&nnbsp; hang ();
}
}
init_fnc_t **init_fnc_ptr。跟踪init_fnc_t, 如下:
typedef int (init_fnc_t) (void);
是个函数指针类型。
当前这个文件里有init_sequence数组:
init_fnc_t *init_sequence[] = {
…
timer_init, /* initialize timer */
…
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_banner, /* say that we are here */
…
dram_init, /* configure available RAM banks */
NULL,
};
通过循环,调用了函数指针数组中的一系列初始化函数:arch_cpu_init,timer_init,env_int,init_baudrate, serial_init,console_init_f, display_banner, dram_init。
串口初始化函数调用后,就可以用串口了,可以用printf来调试,串口初始化之前,串口可以工作之前,可用点灯的方式。
继续分析board_init_f:
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
这样addr是内存的高地址,0x20000000+256M
关于gd->ram_size,在初始化函数dram_init中,已经给过值了
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
/* reserve TLB table */
addr -= (4096 * 4);
//留给TLB,16K
/* round down to next 64 kB limit */
addr &= ~(0x10000 - 1);
//64K对齐
gd->tlb_addr = addr;
debug("TLB table at: %08lx\n", addr);
#endif
/* round down to next 4 kB limit */
addr &= ~(4096 - 1);
// K对齐,此处前面已经64K对齐了,就不需改动
addr -= gd->mon_len;
addr &= ~(4096 - 1);
预留u-boot实际大小空间,4K对齐。
#ifndef CONFIG_SPL_BUILD
addr_sp = addr - TOTAL_MALLOC_LEN;
/*
* (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;
宏CONFIG_SPL_BUILD,用ctags找,在spl/Makefile中能找到,如下:
CONFIG_SPL_BUILD := y
export CONFIG_SPL_BUILD
但是从u-boot根目录的Makefile中,可以看到,ALL-$(CONFIG_SPL),如下:
ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin
CONFIG_SPL没有定义(include/configs/fsc100.h中没这个宏),因此,相当于spl/Makefile没有执行。
分析TOTAL_MALLOC_LEN:
#define TOTAL_MALLOC_LEN CONFIG_SYS_MALLOC_LEN
#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + (1 << 20))
#define CONFIG_ENV_SIZE (128 << 10) /* 128KiB, 0x20000 */
addr_sp = addr – TOTAL_MALLOC_LEN;
=addr-CONFIG_ENV_SIZE + (1 << 20)
= 128 << 10+1M
=128K+1M
128K是预留环境变量空间,1M是预留对空间。
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
…
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);
/* setup stackpointer for exeptions */
gd->irq_sp = addr_sp;
没用
…
/* leave 3 words for abort-stack */
addr_sp -= 12;
/* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07;
//8字节对齐
gd->bd->bi_baudrate = gd->baudrate;
/* Ram ist board specific, so move it to board code ... */
dram_init_banksize();
display_dram_config(); /* and display it */
gd->relocaddr = addr;
//u-boot重新搬运后的起始地址。
gd->start_addr_sp = addr_sp;
//堆栈指针,堆向下长??
gd->reloc_off = addr - _TEXT_BASE;
//偏移量=搬后起始地址-27e00000(u-boot下载地址)
debug("relocation Offset is: %08lx\n", gd->reloc_off);
memcpy(id, (void *)gd, sizeof(gd_t));
// gd在0x22000,SRAM中,此函数给gd的结构体赋值了,拷贝到内存中留的gd结构体中。(或者说把寄存器r8拷贝到内存?)
relocate_code(addr_sp, id, addr);
relocate_code(addr_sp, id, addr);在start.S中定义,C又回到了汇编
(栈指针,全局数据的地方, 搬后起始地址)
经过该函数的处理,内存分配图如下:
继续分析
relocate_code(addr_sp, id, addr);在start.S中定义,C又回到了汇编
(栈指针,全局数据的地方, 搬后起始地址)
完成了第二次搬运过程,把u-boot从27e00000搬到了addr.
/*
* void relocate_code (addr_sp, gd, addr_moni)
*
* This "function" does not return, instead it continues in RAM
* after relocating the monitor code.
*
*/
.globl relocate_code
relocate_code:
mov r4, r0 /* save addr_sp */
mov r5, r1 /* save addr of gd */
mov r6, r2 /* save addr of destination */
……
/* Set up the stack */
stack_setup:
mov sp, r4
adr r0, _start
//得到u-boot的链接地址
cmp r0, r6
//判断u-boot是否已经搬移到终地址
moveq r9, #0 /* no relocation. relocation offset(r9) = 0 */
beq clear_bss /* skip relocation */
//若不需要再次搬移,直接清bss段。
...
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r3, _image_copy_end_ofs
//需要搬移的u-boot的大小。
add r2, r0, r3 /* r2 <- source end address */
//r0和r2之间的内容全部搬走。
copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
stmia r1!, {r9-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end address [r2] */
blo copy_loop
//若u-boot需要自搬移,即不在终地址运行,把u-boot复制到了SDRAM的高端地址
#ifndef CONFIG_SPL_BUILD
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
//由于u-boot第一阶段用的是绝对地址,所以搬运后继续运行,需要加一个偏移量r9。
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
add r10, r10, r0 /* r10 <- sym table in FLASH */
ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
……
ldr r0, _board_init_r_ofs
adr r1, _start
add lr, r0, r1
add lr, lr, r9
/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */
mov r1, r6 /* dest_addr */
/* jump to it ... */
mov pc, lr
_board_init_r_ofs:
.word board_init_r - _start
至此,第一阶段分析完成。
∗概括起来,第一阶段主要功能为:
∗初始化基本的硬件;
∗把bootloader自搬运到内存中;
∗设置堆栈指针并将bss段清零。为后续执行C代码做准备;
∗跳转到第二阶段代码中.