Uboot代碼分析

 

(1)確定鏈接腳本文件:
uboot根目錄下Makefile中的LDSCRIPT宏值,就是指定鏈接腳本(如:arch/arm/cpu/u-boot.lds)路徑用的。
(2)從腳本文件找入口: 
在鏈接腳本中可以看到ENTRY()指定的入口,如:ENTRY(_start), _start就是入口
(3)鏈接腳本簡要分析:
#include <config.h>

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")  /*指定輸出可執行文件是elf格式,32位ARM指令,小端*/
OUTPUT_ARCH(arm)  /*指定輸出可執行文件的平臺爲ARM*/
ENTRY(_start)  /*指定輸出可執行文件的起始代碼段爲_start*/
SECTIONS
{
  /*指定可執行image文件的全局入口點,通常這個地址都放在ROM(flash)0x0位置。必須使編譯器知道這個地址,通常都是修改此處來完成*/
  . = 0x00000000; /*從0x0位置開始*/
  . = ALIGN(4);   /*代碼以4字節對齊*/
  .text :
  {
    *(.__image_copy_start) /*映像文件複製起始地址,*/
    *(.vectors)   /*.vectors標記的代碼段*/
    CPUDIR/start.o (.text*)
    *(.text*)
  }
.....
}

__image_copy_start
arch/arm/lib/sections.c中定義:
char __image_copy_start[0] __attribute__((section(".__image_copy_start"))); //C文件中利用這種方式把一個變量或者函數標記到指定段。
在文件common/board_r.c中被調用:
static int initr_reloc_global_data(void)
{
#ifdef __ARM__
monitor_flash_len = _end - __image_copy_start;

/###################################分###################################割###################################線###################################/
分析文件arch/arm/lib/vectors.S
.globl _start  /*聲明一個符號可被其它文件引用,相當於聲明瞭一個全局變量,.globl與.global相同*/

_start:

b reset /* b是不帶返回的跳轉(bl是帶返回的跳轉),意思是無條件直接跳轉到reset標號處執行程序*/

ldr pc, _undefined_instruction   /*未定義指令異常向量,ldr的作用是,將符號_undefined_instruction指向的地址的內容加載到pc*/
ldr pc, _software_interrupt   /*軟件中斷向量*/
ldr pc, _prefetch_abort       /*預取指令異常向量*/
ldr pc, _data_abort           /*數據操作異常向量*/
ldr pc, _not_used             /*未使用*/
ldr pc, _irq    /*irq中斷向量*/
ldr pc, _fiq    /*fiq中斷向量*/

.globl _undefined_instruction 
.globl _software_interrupt
.globl _prefetch_abort
.globl _data_abort
.globl _not_used
.globl _irq
.globl _fiq

_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq

.balignl 16,0xdeadbeef /*下面的代碼開始16字節對齊,即當上段的代碼運行完後不是16字節對齊,就填充0xdeadbeef,直到使下段的代碼開始處16字節對齊。*/

/* IRQ stack memory (calculated at run-time) + 8 bytes, 不能確定堆棧指針地址,所以填充無效數字,當堆棧指針確定以後,將其加8填充到這裏*/
.globl IRQ_STACK_START_IN
IRQ_STACK_START_IN:
.word 0x0badc0de

/*下面是IRQ中斷,放了一個 IRQ_STACK_START 標號,標號內容爲: 0x0badc0de ,此內容沒有意義,只是爲了佔一個坑。程序剛剛運行,還沒有進入初始化,根本不知
道堆棧指針現在應該放在什麼地方,所以一開始,作者做了一個小技巧,填充一個無效的數字,等初始化以後堆棧指針建立好後,再將實際的堆棧指針寫到這裏。*/
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de

#endif /* CONFIG_USE_IRQ */

/###################################分###################################割###################################線###################################/
vectors.S中第一條指令_start: b reset
reset定義在文件arch/arm/cpu/armv7/start.S

start.S的執行流程如下圖:

reset
/*如果沒有重新定義save_boot_params,則使用<arch/arm/cpu/armv7/start.S>中的save_boot_params。其不做任何事情,直接返回。*/
bl save_boot_params
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr /*將cpsr寄存器的內容傳送到r0寄存器*/
and r1, r0, #0x1f /*標誌位清零*/
teq r1, #0x1a /*測試處理器是否處於HYP模式,HYP是armv-7a爲cortex-A15處理器提供硬件虛擬化引進的管理模式。*/
bicne r0, r0, #0x1f /*工作模式位清零*/
orrne r0, r0, #0x13 /*設置成SVC管理模式*/
orr r0, r0, #0xc0 /*關閉FIQ和IRQ中斷*/
msr cpsr,r0 /*將r0的值賦給cpsr*/

/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD)) 
/* 設置異常向量的基地址,正常異常模式下異常向量的基地址爲0x00000000,高異常模式下異常向量的基地址爲0xffff0000,這裏V=0設置成正常異常模式 */
/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register
  /* 重新設置異常向量的基地址,只有上面V=0的情況下,這裏才能去重新配置異常向量表的基地址!*/
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif

/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15 //初始化協處理器
bl cpu_init_crit //初始化內存和鎖相環
#endif

bl _main //調用c代碼

/###################################分###################################割###################################線###################################/
cpu_init_cp15, arch/arm/cpu/armv7/start.S
/*************************************************************************
*
* cpu_init_cp15
*
* Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
* CONFIG_SYS_ICACHE_OFF is defined.
*
*************************************************************************/
ENTRY(cpu_init_cp15)
/*
* Invalidate L1 I/D
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs  /*禁止從TLB中取地址描述符,也就是禁止虛擬地址到物理地址的轉換,因爲剛開始操作的都是物理寄存器!*/
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache /*關閉指令cache*/
mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array /*關閉分支預測*/
mcr p15, 0, r0, c7, c10, 4 @ DSB /*多核cpu之間進行數據同步*/
mcr p15, 0, r0, c7, c5, 4 @ ISB /*進行指令同步,放棄流水線中已經取到的指令,重新取指令*/

/*
* disable MMU stuff and caches
*/
/*******************************************************
*1、爲什麼要關閉mmu?因爲MMU是把虛擬地址轉化爲物理地址得作用而我們現在是要設置控制寄存器,而控制寄存器本來就是實地址(物理地址),再使能MMU,不就是多此一舉了嗎?
********************************************************/

/*******************************************************
*2、爲什麼要關閉cache?*catch和MMU是通過CP15管理的,剛上電的時候,CPU還不能管理他們。所以上電的時候MMU必須關閉,指令cache可關閉,可不關閉,但數據cache一定要關閉
*否則可能導致剛開始的代碼裏面,去取數據的時候,從catch裏面取,而這時候RAM中數據還沒有cache過來,導致數據預取異常
********************************************************/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-) /*設置成正常異常模式,即異常向量表的基地址爲0x00000000*/
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM) /*關閉指令cache,關閉指令對齊檢測,關閉mmu*/
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align /*使能對齊檢測*/
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB /*使能分支預測*/
#ifdef CONFIG_SYS_ICACHE_OFF
bic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#else
orr r0, r0, #0x00001000 @ set bit 12 (I) I-cache /*時能指令cache*/
#endif
mcr p15, 0, r0, c1, c0, 0

#ifdef CONFIG_ARM_ERRATA_716044
mrc p15, 0, r0, c1, c0, 0 @ read system control register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c1, c0, 0 @ write system control register
#endif

#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 4 @ set bit #4
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif

#ifdef CONFIG_ARM_ERRATA_743622
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 6 @ set bit #6
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif

#ifdef CONFIG_ARM_ERRATA_751472
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 11 @ set bit #11
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_761320
mrc p15, 0, r0, c15, c0, 1 @ read diagnostic register
orr r0, r0, #1 << 21 @ set bit #21
mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif

mov pc, lr @ back to my caller /*程序返回*/
ENDPROC(cpu_init_cp15)

/###################################分###################################割###################################線###################################/
cpu_init_crit
/*************************************************************************
*
* CPU_init_critical registers
*
* setup important registers
* setup memory timing
*
*************************************************************************/
ENTRY(cpu_init_crit)
/*
* Jump to board specific initialization...
* The Mask ROM will have already initialized
* basic memory. Go here to bump up clock rate and handle
* wake up conditions.
*/
b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)

lowlevel_init標號對應的源碼:(這個代碼要初始化內存等,是和具體平臺有關的,所以應該去對應平臺的目錄下找lowlevel_init.S文件/arch/arm/cpu/armv7/rk32xx
ENTRY(lowlevel_init)
/*
* Setup a temporary stack
*/
/**
爲調用c函數準備一個臨時堆棧而已,這個堆棧在cpu的片上內存!
**/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push {ip, lr} /*將ip和lr寄存器壓入堆棧保存*/

/*
* go setup pll, mux, memory
*/
bl s_init   /*針對相應的平臺設置系統時鐘,這是c函數,結合cpu的datasheet閱讀即可*/
pop {ip, pc}   /*將ip和lr寄存器從堆棧彈出*/
ENDPROC(lowlevel_init)
/###################################分###################################割###################################線###################################/

bl _main //調用c代碼, _main實現在 /arch/arm/lib/crt0.S

_main執行流程圖如下

ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
/*
這裏首先爲調用board_init_f準備一個臨時堆棧,CONFIG_SYS_INIT_SP_ADDR這個宏就是cpu片上內存的高地址(片上內存的大小減去GD_SIZE)。然後將堆棧初始的地址保存在r9,所以r9就是gd的起始地址,後面需要靠r9訪問gd的成員。然後將r0賦值成0,r0就是要調用的board_init_f函數的第一個參數!
*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
  ldr sp, =(CONFIG_SPL_STACK)
#else
  ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
  bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
  sub sp, sp, #GD_SIZE /* allocate one GD above SP */
  bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
  mov r9, sp /* GD is above SP */
  mov r0, #0
  bl board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/

/*
*這段代碼的主要功能就是將uboot搬移到內存的高地址去執行,爲kernel騰出低端空間,防止kernel解壓覆蓋uboot。
*adr lr, here
*ldr r0, [r9, #GD_RELOC_OFF]
*add lr, lr, r0 的功能就是,將relocate後的here標號的地址保存到lr寄存器,這樣等到relocate完成後,就可以直接跳到relocate後的here標號去執行了。
*relocate_code函數的原理及流程,是uboot的重要代碼,下面詳解!
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */

adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
relocate_code
here:

/* Set up final (full) environment */
/*
*relocate完成後,uboot的代碼被搬到了內存的頂部,所以必須重新設置異常向量表的
*地址,c_runtime_cpu_setup這個函數的主要功能就是重新設置異常向量表的地址。
*/
bl c_runtime_cpu_setup /* we still call old routine here */
/*
*在relocate的過程中,並沒有去搬移bss段。bss段是auto-relocated的!爲什麼?
*可以自己思考一下,又或許看完我後面介紹的relocate的原理後你會明白!
*/
ldr r0, =__bss_start /* this is auto-relocated! */
ldr r1, =__bss_end /* this is auto-relocated! */

mov r2, #0x00000000 /* prepare zero to clear BSS */
/*
*清空bss段。
*/
clbss_l:cmp r0, r1 /* while not at end of BSS */
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
/*
*這兩行代碼無視之,點燈什麼的,和這裏要講的uboot的原理及過程沒有半毛錢關係。
*/
bl coloured_LED_init
bl red_led_on
/*
*將relocate後的gd的地址保存到r1,然後調用board_init_r函數,進入uboot的新天地!
*/
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
/* call board_init_r */
ldr pc, =board_init_r /* this is auto-relocated! */

/* we should not return here. */

#endif

ENDPROC(_main)
/###################################分###################################割###################################線###################################/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章