一、概述
芯片手冊: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代碼做準備;
∗跳轉到第二階段代碼中.