一、u-boot目錄

一般移植U-BOOT會修改綠色部分的代碼,U-BOOT中各目錄間也是有層次結構的,雖然這種分法不是絕對的,但是在移植過程中可以提供一些指導意義。

可以通過“內容一的舉例”來看看移植過程中需要更改哪些文件,我將其放在文末。
二、U-BOOT功能
要學習u-boot之前,最好先了解一塊板子的裸板程序啟動的過程,因為u-boot其實就是裸板程序的集大成者。
U-boot的主線目標功能是從flash中讀出內核,放到內存中,啟動內核。為了要實現這個功能,u-boot分為兩個階段,主要在start.s文件中。
2.1第一階段
硬件設備初始化;
為加載 Bootloader 的第二階段代碼准備 RAM 空間;
復制 Bootloader 的第二階段代碼到 RAM 空間中;
設置好棧;
跳轉到第二階段代碼的 C 入口點(start_armboot);
備注:在第一階段進行的硬件初始化一般包括:關閉 WATCHDOG、關中斷、設置 CPU的速度和時鍾頻率、 RAM 初始化等。這些並不都是必須的,比如 S3C2410/S3C2440的開發板所使用的 U-Boot 中,就將 CPU的速度和時鍾頻率放在第二階段進行設置。
2.2第二階段
初始化本階段要使用到的硬件設備;
檢測系統內存映射( Memory map );
將內核映像和根文件系統映像從 Flash上讀到 RAM 空間中;
為內核設置啟動參數;
調用內核;
備注:為了方便開發,初始化一個串口以便程序員與 Bootloader 進行交互。部分內容的解釋在文末,名為“內容二的解釋”。
三、u-boot源碼查看前置步驟
將u-boot源碼放在linux系統下編譯(需要配置交叉編譯工具),設置為板子的配置(如make smdk2410_config),再編譯(make),最好的辦法是拿一個開發板的u-boot源碼進行編譯,因為開發板廠商一般都會提供編譯環境等(這些問題不該在初學時就遇到,否則會極大降低學習的積極性),編譯成功后,將代碼復制到windows下用source insight查看代碼(這樣看代碼之間的互聯性比較方便)。
以我的板子(jz2440為例),將全部目錄加進SI后,要去掉部分不是2440板子的板級文件(注:加入文件時,如果不能加入.S文件,需要更改SI的設置)
board目錄只留samsung/smdk2410;
arch目錄只留:(注意要加入各層的通用文件(未在文件夾內的))
a、arm/cpu/arm920t/s3c24x0以及各層的通用文件(未在文件夾內的)
b、arm/cpu/dts
c、arm/include/asm/arch-s3c24x0和proc-armv以及各層的通用文件(未在文件夾內的)
d、arm/lib
include/config目錄只加smdk2410.h
再同步(project-synchronize files)
四、源碼分析(第一階段)
最傳統的方法是從makefile開始分析,但是較為復雜,先直接從網上找到結論:從文件層面上看主要流程是在兩個文件中:cpu/arm920t/start.s,lib_arm/board.c。
打開cpu/arm920t/start.S
4.1建立異常向量表
.globl _start // .globl定義一個全局符號"_start",表明_start這個符號要被鏈接器用到
_start: //_start:系統復位設置,以下共8種不同的異常處理
b reset //復位異常 0x0
ldr pc, _undefined_instruction //未定義的指令異常 0x4
ldr pc, _software_interrupt // 軟件中斷異常 0x8
ldr pc, _prefetch_abort //內存操作異常 0xc
ldr pc, _data_abort //數據異常 0x10
ldr pc, _not_used //未使用 0x14
ldr pc, _irq //中斷IRQ異常 0x18
ldr pc, _fiq //快速中斷FIQ異常 0x1c
_undefined_instruction: .word undefined_instruction //0x20
_software_interrupt: .word software_interrupt //0x24
_prefetch_abort: .word prefetch_abort // 0x28
_data_abort: .word data_abort //0x2c
_not_used: .word not_used //0x30
_irq: .word irq //0x34
_fiq: .word fiq //0x38
.balignl 16,0xdeadbeef //0x3c
在第1行中".globl _start":使用.globol聲明全局符號_start,在 board/100ask24x0/u-boot.lds中ENTRY(_start)這里用到。
其中符號保存的地址都在頂層目錄/system.map中列出來了
在第2行中_start之所以有8種不同的異常處理,是在2440芯片手冊中已經規定好了的。
問題1:后面的異常處理為什么用ldr 不用b指令?
ldr是絕對跳轉,b是相對跳轉,它的地址與代碼位置無關。復位異常在CPU運行前,並未初始化SDRAM(故不能使用0x3000 0000以上的地址),在正常工作后也可能觸發復位,這時由於CPU已經對SDRAM、MMU(虛擬內存管理)等初始化了,
此時的虛擬地址和物理地址完全不同,所以reset使用b指令相對跳轉。
2.后面的異常處理是怎么執行的?執行后異常處理又怎么退出的?
在2440芯片手冊上給出,例如當處理一個中斷IRQ異常時:
a.保存當前PC現場到寄存器R14,
b.把當前程序狀態寄存器(CPSR)保存到備份程序狀態寄存器(SPSR)中.從異常退出的時候,就可以由SPSR來恢復CPSR。
c.根據中斷IRQ異常處理,強制將 CPSR 模式位設為中斷模式
d.強制 PC 從相關異常向量處取下條指令。跳轉到0x18實現中斷異常處理.
當退出中斷IRQ異常時:
a). 將中斷IRQ所對應的是R14_irq寄存器並放入到 PC 中,如下圖,中斷IRQ所對應的是R14_irq寄存器,執行MOVS R14_svc .
b). 復制 SPSR 的內容返回給 CPSR 中。
c). 如果在異常進入時置位了中斷禁止標志位異常,清除中斷禁止標志位
問題3. 第12行中 .word: 類似於(unsigend long)
以第12行中 _undefined_instruction: .word undefined_instruction為例
_undefined_instruction和_undefined_instruction都是一個標號,
表示_undefined_instruction指向一個32位(4字節)地址,該地址用undefined_instruction符號變量代替。
用C語言表示就是:undefined_instruction = &undefined_instruction
相當於PC從_undefined_instruction取值時,即undefined_instruction地址存到了PC中。
問題4. 第20行中 .balignl 16,0xdeadbeef:
它的意思就是在以當前地址開始,在地址為16的倍數的指令位置的上一個指令填入為0xdeadbeef的內容。
此時當前地址剛好0x3c=60,由於ARM每個指令間隔4個字節,且64%16=0,所以在0x3c中填入0xdeadbeef。
它們的作用就是為內存做標記,插在那里,這個位置往前有特殊作用的內存,禁止訪問。
4.2設置管理模式、關看門狗、屏蔽中斷
reset--->start.o
reset:
/* 設置CPSR程序程序狀態寄存器為管理模式 */
mrs r0,cpsr //MRS讀出CPSR寄存器值到R0
bic r0,r0,#0x1f //將R0低5位清空
orr r0,r0,#0xd3 //R0與b'110 10011按位或,禁止IRQ和FIQ中斷,10011:復位需要設為管理模式(圖1)
msr cpsr,r0 //MSR寫入CPSR寄存器
/* 關看門狗 */
# define pWTCON 0x53000000 //(WitchDog Timer)看門狗定時器寄存器WTCON,設為0X0表示關閉看門狗
# define INTMOD 0X4A000004 //(Interrupt Mode)中斷模式寄存器INTMOD,相應位=0:IRQ模式,相應位=1:IRQ模式,
# define INTMSK 0x4A000008 //(Interrupt Mask)中斷屏蔽寄存器INTMSK,相應位=0:開啟中斷服務,相應位=1:關閉中斷服務
# define INTSUBMSK 0x4A00001C //中斷次級屏蔽寄存器,相應位=0:開啟中斷服務,相應位=1:關閉中斷服務
# define CLKDIVN 0x4C000014 //時鍾分頻寄存器
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) //宏定義CONFIG_S3C2410已定義
ldr r0, =pWTCON //R0等於WTCON地址
mov r1, #0x0 //R1=0x0
str r1, [r0] //關閉WTCON寄存器,pWTCON=0;
/* 關中斷 */
mov r1, #0xffffffff //R1=0XFFFF FFFF
ldr r0, =INTMSK //R0等於INTMSK地址
str r1, [r0] //*0x4A000008=0XFFFF FFFF(關閉所有中斷)
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff //R1=0x3FF
ldr r0, =INTSUBMSK //R0等於INTSUBMSK地址
str r1, [r0] //*0x4A00001C=0x3FF(關閉次級所有中斷)
# endif
/*
判斷系統是從nand啟動的還是直接將程序下載到SDRAM中運行,
若系統從nand啟動,這里得到r0和r1值是不一樣的,r1=0x33f80000,
而r0=0x00000000。說明沒初始化SDRAM,ne(no equal)標識符為真,所以bl cpu_init_crit執行跳轉.
*/
adr r0, _start
ldr r1, _TEXT_BASE
cmp r0, r1
blne cpu_init_crit
CPU復位后是從這里開始執行,這里初始化了:
1.執行設置CPSR程序程序狀態寄存器為管理模式
2.關看門狗
3.屏蔽中斷
4.進入cpu_init_crit函數關閉MMU,進入lowlevel_init初始化13個BANK寄存器來初始化SDR
4.3進入cpu_init_crit函數(關閉MMU)
cpu_init_crit:
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 //關閉ICaches(指令緩存,關閉是為了降低MMU查表帶來的開銷)和DCaches(數據緩存,DCaches使用的是虛擬地址,開啟MMU之前必須關閉)
mcr p15, 0, r0, c8, c7, 0 //使無效整個數據TLB和指令TLB(TLB就是負責將虛擬內存地址翻譯成實際的物理內存地址)
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) //bit8:系統不保護,bit9:ROM不保護,bit13:設置正常異常模式0x0~0x1c,即異常模式基地址為0X0
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) //bit0~2:禁止MMU,禁止地址對齊檢查,禁止數據Cache.bit7:設為小端模式
orr r0, r0, #0x00000002 @ set bit 2 (A) Align //bit2:開啟數據Cache
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache //bit12:開啟指令Cache
mcr p15, 0, r0, c1, c0, 0
/*
mcr/mrc:
Caches:是一種高速緩存存儲器,用於保存CPU頻繁使用的數據。在使用Cache技術的處理器上,當一條指令要訪問內存的數據時,
首先查詢cache緩存中是否有數據以及數據是否過期,如果數據未過期則從cache讀出數據。處理器會定期回寫cache中的數據到內存。
根據程序的局部性原理,使用cache后可以大大加快處理器訪問內存數據的速度。
其中DCaches和ICaches分別用來存放數據和執行這些數據的指令
TLB:就是負責將虛擬內存地址翻譯成實際的物理內存地址,TLB中存放了一些頁表文件,文件中記錄了虛擬地址和物理地址的映射關系。
當應用程序訪問一個虛擬地址的時候,會從TLB中查詢出對應的物理地址,然后訪問物理地址。TLB通常是一個分層結構,
使用與Cache類似的原理。處理器使用一定的算法把最常用的頁表放在最先訪問的層次。
這里禁用MMU,是方便后面直接使用物理地址來設置控制寄存器
*/
mov ip, lr //臨時保存當前子程序返回地址,因為接下來執行bl會覆蓋當前返回地址.
bl lowlevel_init //跳轉到lowlevel_init(位於u-boot-1.1.6/board/100ask24x0/lowlevel_init.S)
mov lr, ip //恢復當前返回地址
mov pc, lr //退出
4.4進入lowlevel_init函數 (初始化各個bank和SDRAM)
lowlevel_init:
ldr r0, =SMRDATA //將SMRDATA的首地址(0x33F806C8)存到r0中
ldr r1, _TEXT_BASE //r1等於_TEXT_BASE內容,也就是TEXT_BASE(0x33F80000)
sub r0, r0, r1 //將0x33F806C8與0x33F80000相減,得到現在13個寄存器值在NOR Flash上存放的開始地址
ldr r1, =BWSCON //將BWSCON寄存器地址值存到r1中 (第一個存儲器寄存器首地址)
add r2, r0, #13*4 //每個寄存器4字節,r2=r0+13*4=NOR Flash上13個寄存器值最后一個地址
0:
ldr r3, [r0], #4 //將r0的內容存到r3的內容中(r3等於SMRDATA里面值), 同時r0地址+=4;
str r3, [r1], #4 //將r3的內容存到r1所指的地址中(向寄存器地址里寫入r3值),同時r1地址+=4;
cmp r2, r0 // 判斷r2和r0
bne 0b //不等則跳轉到第6行繼續執行
mov pc, lr //跳回到返回地址中繼續執行
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
//設置每個BWSCON,注意BANK0由硬件連線決定了
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
//設置BANKCON0~BANKCON5
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
//設置BANKCON6~BANKCON7
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
//設置REFRESH,在S3C2440中11~17位是保留的,也即(Tchr<<16)無意義
.word 0xb1 //設置BANKSIZE,對於容量可以設置大寫,多出來的空內存會被自動檢測出來
.word 0x30 //設置MRSRB6
.word 0x30 //設置MRSRB7
4.5返回start.s -- 設置棧
stack_setup: //設置棧,方便調用C函數
ldr r0, _TEXT_BASE //代碼段的初始地址:r0=0x33f80000
sub r0, r0, #CFG_MALLOC_LEN //留出一段內存以實現malloc:r0=0x33f50000
sub r0, r0, #CFG_GBL_DATA_SIZE //再留出一段存一些全局參數的變量:r0=0x33F4FF80
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) //中斷與快中斷的棧:r0=0x33F4DF7C
#endif
sub sp, r0, #12 //留出12字節內存給abort異常 設置棧頂sp=r0-12;
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl clock_init //進入clock_init函數
#endif
4.6進入clock_init函數設置時鍾
void clock_init(void)
{
S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000; //定義一個S3C24X0_CLOCK_POWER型結構體指針,clk_power->LOCKTIME=0x4C000000
if (isS3C2410) //isS3C2410為0,執行else
{... ...}
else
{
/* FCLK:HCLK:PCLK = 1:4:8 */
clk_power->CLKDIVN = S3C2440_CLKDIV; //S3C2440_CLKDIV=0X05
/* change to asynchronous bus mod */
__asm__( "mrc p15, 0, r1, c1, c0, 0\n" /* read ctrl register */
"orr r1, r1, #0xc0000000\n" //使其從快總線模式改變為異步總線模式,在2440手冊上看到
"mcr p15, 0, r1, c1, c0, 0\n" /* write ctrl register */
:::"r1" //:::"r1" 向GCC聲明:我對r1作了改動
);
/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFFFF; //PLL 鎖定時間計數寄存器
/* configure UPLL */
clk_power->UPLLCON = S3C2440_UPLL_48MHZ; //UCLK=48Mhz
/* some delay between MPLL and UPLL */
delay (4000); //等待UCLK時鍾波形穩定
/* configure MPLL */
clk_power->MPLLCON = S3C2440_MPLL_400MHZ; //FCLK=400Mhz
/* some delay between MPLL and UPLL */
delay (8000); //等待FCLK時鍾波形穩定
}
}
4.7重定位、清bss段、跳轉到start_armboot函數
relocate: /* 拷貝u-boot到SDRAM */
adr r0, _start //r0:當前代碼開始地址
ldr r1, _TEXT_BASE //r1:代碼段連接地址(0X3FF8 0000)
cmp r0, r1 //測試現在在FLASH中還是RAM中
beq clear_bss //若_start==_TEXT_BASE,表示已經進行代碼從Flash拷貝SDRAM了(通常是調試時直接下載到RAM中)
ldr r2, _armboot_start //r2等於_armboot_start里的內容,也就是_start
ldr r3, _bss_start //r3等於_bss_start里的內容,(在連接腳本u-boot.lds中定義,是代碼段的結束地址)
sub r2, r3, r2 //r2等於代碼段長度
bl CopyCode2Ram // r0: source, r1: dest, r2: size 將從NOR FLASH上代碼段(r0~r0+r2)拷貝到sdram地址(r3)0x3ff80000代碼段地址上
clear_bss:
ldr r0, _bss_start //r0=__bss_start
ldr r1, _bss_end //r0等於_bss_end里的內容,也就是_end(在u-boot.lds里定義,是存bss的結束地址)
mov r2, #0x00000000 //r2=0;用來清bss所有段
clbss_l:
str r2, [r0] //*r0=0;
add r0, r0, #4 //r0+=4;
cmp r0, r1
ble clbss_l //小於等於一直執行clbss_l
ldr pc, _start_armboot //pc等於_start_armboot里的內容,也就是跳轉到start_armboot函數
_start_armboot: .word start_armboot *(_start_armboot)=start_armboot
小結:
uboot-第一階段硬件初始化主要實現了:
1.執行設置CPSR程序程序狀態寄存器為管理模式
2.關看門狗
3.屏蔽中斷
4.關閉MMU,初始化SDRAM
5.設置棧
6.時鍾設置
7.重定位(代碼從Flash拷貝至SDRAM中)
8.清bss段(未初始的全局/靜態變量)
9.跳轉到start_armboot函數(位於u-boot-1.1.6/lib_arm/borad.c,用來實現第2階段硬件相關的初始化)
接下來開始分析uboot-第二階段硬件初始化。
五、源碼分析(第二階段)
第二階段的功能:
初始化本階段所需的硬件設備(主要設置系統時鍾、初始化串口、Flash、網卡、USB)
檢測系統內存映射(memory map)
將內核映像和根文件系統映象從Flash上讀到RAM空間中
為內核設置啟動參數
調用內核
從start_armboot開始,程序流程如圖

六、附錄
內容一的舉例:
比如common/cmd_nand.c文件提供了操作NAND Flash的各種命令,這些命令調用drivers/nand/nand_base.c中的擦除、讀寫函數來實現;而這些函數是針對NAND Flash的共性做的封裝,與平台/開發板相關的代碼用宏或外部函數代替;平台相關則在cpu/xxx,開發板相關則在board/xxx。
以增加yaffs文件系統映像功能為例,先在common下的cmd_nand.c增加命令,比如nand write.yaffs,這個命令調用/drivers/nand/nand_util.c中的函數,而這些函數依賴於drivers/nand/nand_base.c、cpu/arm920t/s3c24x0/nand_flash.c文件中的相關函數。
內容二的解釋:
所謂檢測內存映射,就是確定板上使用了多少內存、他們的地址空間是什么。由於嵌入式開發中的 Bootloader 多是針對某類板子進行編寫,所以可以根據板子的情況直接設置,不需要考慮可以適用於各類情況的復雜算法。
Flash上的內核映像有可能是經過壓縮的,在讀到 RAM 之后,還需要進行解壓。當然,對於有自解壓功能的內核,不需要 Bootloader 來解壓。
將根文件系統映像復制到RAM中並不是必須的,這取決於是什么類型的根文件系統,以及內核訪問它的方法。
將內核放在適當的位置后,在跳入執行內核之前,需要根據內核啟動的需求,配置相應的啟動參數。
調用內核的函數是什么還沒找到,待會再找
參考鏈接:
第1階段——uboot分析之硬件初始化start.S(4) :https://www.cnblogs.com/lifexy/p/7309791.html
U-BOOT啟動流程分析--start_armboot函數(二):https://www.cnblogs.com/y4247464/p/10597504.html
