U-BOOT簡潔版-快速入門uboot


一、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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM