1.在檔案的最開始有這樣的注釋
/*
*************************************************************************
*
* 啟動代碼 (被 ARM 的 reset 調用,不包括中斷)
*
* 隻有不從 memory 啟動的時候才做重要的初始化
* 重定位代碼到 ram
* 設定棧
* 跳轉到啟動第二階段
*
*************************************************************************
*/
上面已經大緻交代了這個start.S檔案要做的事情 在 u-boot.lds檔案裡面指明了入口是 _start标号,是以開始會先加載 _start的内容,而此函數入口在vector.s裡面( 與以往核心不同的地方)。裡面會設定中斷向量表等等,當然,這些在編譯的時候已經加載過了,晶片複位的時候會跳到reset處繼續執行代碼 # 2.首先設定cpu處于管理模式
reset:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr, r0
# 3.關看門狗,屏蔽中斷
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif
/* 關看門狗 */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/* 屏蔽所有中斷 */
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
# if defined(CONFIG_S3C2410)
ldr r1, =
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
# 4.初始化時鐘分頻系數
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
# 5.CPU初始化
/* 擦除DCache與ICache */
mov r0, #0
mcr p15, , r0, c7, c7, /* flush v3/v4 cache */
mcr p15, , r0, c8, c7, /* flush v4 TLB */
/* 禁止MMU與Cache */
mrc p15, , r0, c1, c0,
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, , r0, c1, c0,
/* 重定位之前, 通過lowlevel_init函數設定RAM時間參數 */
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
# 6.調用 \_main 這裡開始有點疑惑,怎麼沒有記憶體設定以及重定位代碼就直接開始 \_main了,SourceInsight全局搜尋**_main**發現在 crt0.S裡面有**_main**函數體 **我們先看下_main函數的介紹**
/*
* 1. 為調用 board_init_f() C函數設定初始化環境
* 環境僅僅提供棧以及 GD ('global data') 資料結構的存放空間, 隻有已經初始化的靜态變量可以在該階段使用
*
* 2. 調用 board_init_f()。為系統從RAM啟動準備硬體環境, board_init_f() 必須使用 GD 來
* 存放在最後階段需要用到的資料。資料包括重定位的目标位址, 未來要使用的棧, 以及未來要使用的 GD 的新位址
*
* 3. 為 stack and GD 設定中轉環境
*
* 4. 調用 relocate_code(). 函數重定位u-boot到 board_init_f() 指定的位置
*
* 5. 為調用 board_init_r() 設定最新的環境。初始化BSS,非靜态的全局變量和系統RAM中的棧
* GD保留被 board_init_f() 設定的值。 有些CPU還有一些關于記憶體的操作沒有進行, 是以要調用 call c_runtime_cpu_setup
*
* 6. 跳轉到 board_init_r().
*/
7.設定C運作環境并且調用 board_init_f(0)
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) /* CONFIG_SYS_INIT_SP_ADDR = 0x30000000 + 0x1000 - GENERATED_GBL_DATA_SIZE = 0x30000f50 */
bic sp, sp, #7 /* 8位元組對齊 */
mov r2, sp
sub sp, sp, #GD_SIZE /* 在棧上面配置設定GD的空間,GD_SIZE = 168 */
bic sp, sp, #7 /* 8位元組對齊 */
mov r9, sp /* GD is above SP */
mov r1, sp
mov r0, #0
/* 清空GD空間,以待重新指派 */
clr_gd:
cmp r1, r2
strlo r0, [r1] /* clear 32-bit GD word */
addlo r1, r1, #4 /* move to next */
blo clr_gd
bl board_init_f /* 調用board_init_f對GD等等進行初始化,SP = 30000E80 */
值得說的是上面的GENERATED_GBL_DATA_SIZE宏定義,它是由DEFINE(GENERATED_GBL_DATA_SIZE,(sizeof(struct global_data) + 15) & ~15);獲得的,也就是相當于#define GENERATED_GBL_DATA_SIZE ((sizeof(struct global_data) + 15) & ~15)。
#define DEFINE(sym, val) \
asm volatile("\n.ascii \"->" #sym " %0 " #val "\"" : : "i" (val))
這個語句在編譯的時候會被轉換為.h檔案以供别的檔案包含,這個宏定義的作用大概應該是讓形如#define GENERATED_GBL_DATA_SIZE ((sizeof(struct global_data) + 15) & ~15)可以随用随定義(此處存在疑問,為什麼不直接宏定義呢),補充:這個宏可以使全局變量gd_t可以在.S檔案裡面使用彙編代碼直接通路到,類似 ldr sp, [r9, #GD_START_ADDR_SP] 這樣的語句
另外由于 mov r9, sp 這句話的作用,以後要想在u-boot其它檔案裡面通路此時的SP(也就是gd的位置),就需要在檔案頭部加上DECLARE_GLOBAL_DATA_PTR,原型是
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
//在2015版的裡面使用下面的函數得到,原理是一樣的
static inline gd_t *get_gd(void)
{
gd_t *gd_ptr;
__asm__ volatile("mov %0, r9\n" : "=r" (gd_ptr));
return gd_ptr;
}
8.單闆初始化board_init_f
(需要注意的是,本處選擇的board_init_f為board.c裡面的,但是從2014以後就預設編譯的是board_f裡面的函數了,這裡為了學習友善,暫且選擇board.c裡面的函數進行分析,以後再去分析另一個分支代碼,切換代碼到board.c裡面的方法在以後的真正移植過程中會說到)
初始化全局變量gd,調用函數隊列
memset((void *)gd, , sizeof(gd_t)); /* 初始化gd填充為0 */
gd->mon_len = (ulong)&__bss_end - (ulong)_start; /* 整個u-boot代碼與資料段的總大小,可以由u-boot.lds檔案得出 */
/* 調用一個初始化函數隊列 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != ) {
hang ();
}
}
下面是init_sequence函數對列裡面的函數
board_early_init_f /* 時鐘初始化,引腳初始化,不同的單闆要進行不同的設定 */
timer_init /* 定時器初始化 */
env_init, /* initialize environment */
init_baudrate, /* 波特率初始化,預設CONFIG_BAUDRATE為115200 */
gd->baudrate = getenv_ulong("baudrate", , CONFIG_BAUDRATE);
serial_init, /* 序列槽初始化 */
console_init_f, /* console初始化 */
display_banner, /* 表明代碼運作到這裡了 */
print_cpuinfo, /* 列印cpu資訊 */
dram_init, /* 配置可用的RAM大小,預設PHYS_SDRAM_1_SIZE為64M */
//dram_init 函數内部有 gd->ram_size = PHYS_SDRAM_1_SIZE;
初始化程式最終存放位址addr
addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize(); /* addr指向SDRAM的結尾,0x34000000處,get_effective_memsize() = 64M */
/* 保留 TLB table,PGTABLE_SIZE預設為4K */
gd->arch.tlb_size = PGTABLE_SIZE;
addr -= gd->arch.tlb_size; /* addr減去TLB大小 */
/* 下移到下一個64K開始處,相當于64KB對齊 */
addr &= ~( - );
gd->arch.tlb_addr = addr; /* TLB的指向目前的addr */
/* 4KB對齊 */
addr &= ~( - );
debug("Top of RAM usable for U-Boot at: %08lx\n", addr);
/*
* 為 U-Boot 代碼, 資料與bss保留白間,gd->mon_len;在函數剛開始的時候就指派了,大小就是u-boot整個編譯出來的檔案大小
* 4KB對齊
*/
addr -= gd->mon_len;
addr &= ~( - );
/*
* 為 malloc() 函數保留堆區
*/
addr_sp = addr - TOTAL_MALLOC_LEN; /* addr的棧等于addr減去堆的大小 */
/*
* 保留 Board Info 結構體的空間與一個gd_t的副本
*/
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
/* 設定棧指針 */
addr_sp -= sizeof (gd_t); /* 棧減去gd_t副本空間 */
id = (gd_t *) addr_sp; /* id指派為目前addr_sp的值 */
gd->irq_sp = addr_sp;
/* 為中斷棧留3個位元組 */
addr_sp -= ;
/* 8位元組對齊 */
addr_sp &= ~;
gd->relocaddr = addr; /* 重定位的目标位址為代碼區的起始位址 */
gd->start_addr_sp = addr_sp; /* 棧位址為上面的addr_sp */
gd->reloc_off = addr - (ulong)&_start; /* 重定位的偏移量是代碼區存放位址減去0位址 */
memcpy(id, (void *)gd, sizeof(gd_t)); /* 拷貝gd_t到重定位之後的預留gd_t副本的位址 */
重定位代碼之前的準備
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp,sp為重定位代碼棧區起始位址 */
bic sp, sp, #7 /* 8位元組對齊 */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* 新的GD在bd下方,參見記憶體分布圖 */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off,r0為重定位偏移量 */
add lr, lr, r0 /* 重定位之後代碼的傳回位址 */
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr,r0為重定位的目标位址 */
b relocate_code
開始重定位代碼
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs r4, r0, r1 /* r4 <- relocation offset */
beq relocate_done /* skip relocation */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
/* 到現在為止,比較重要的幾個寄存器的值為
* r0 = gd->reloc_off,r0為重定位偏移量,本處也就是目标位址
* r1 = __image_copy_start,r1為需要重定位代碼目前的起始位址,也就是代碼段的開始0
* r4 = r0 - r1,r4為重定位的偏移值,偏移值減去0還是0
* r2 =__image_copy_end,r2為需要重定位代碼的結束位址,r2 - r1就是需要重定位代碼長度了
*/
copy_loop:
ldmia r1!, {r10-r11} /* 從源位址 [r1] 開始拷貝,pop到r10與r11裡面,一次8個位元組 */
stmia r0!, {r10-r11} /* 拷貝到目标位址 [r0] */
cmp r1, r2 /* 一直到 [r1] 等于 [r2], 說明代碼拷貝結束 */
blo copy_loop
/*
* 重定位修正 .rel.dyn
*/
ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
9.跳轉運作第二第階段代碼
到這裡已經完成了整個uboot的重定位以及一些基本裝置的初始化,接下來就是調用board_init_r,這個是uboot啟動的第二階段,包括nand flash的初始化,nor flash 等等裝置初始化,以及各種指令的初始化。
最終記憶體的規劃如下(雖然不同版本的uboot記憶體分布有所不同,但是大體上都是相同的):
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsISN5QTM0EzM3AzMxYDM2EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)