uboot啟動分析第一階段(start.S)
前面分析了啟動指令碼、Makefile、mkconfig,接下來就是uboot的start.S這個啟動程式碼了,下面是本章的平臺介紹:
單板:迅為4412開發板(Exynos 4412)
SDRAM:1G
EMMC:4G
Exynos 4412的啟動過程可以在資料手冊的Booting Sequence找到,下面只擷取關鍵部分:
Exynos 4412 has 64 KB ROM (iROM) and 256 KB SRAM (iRAM) as internal memory.
You can select the booting device from the following list:
1. General NAND flash memory
2. SD/MMC memory card
3. eMMC memory
4. USB deviceAt the system reset, the program execution starts at iROM. The system reset may be asserted not
only on booting time, but also on wakeup from low power modes. Therefore, the boot loader code executes appropriate processes according to the reset status. Refer to Figure 5-1 for more information.The boot loader is comprises the first and the second boot loaders. The characteristics of these boot loaders are:
1. iROM: It is a small and simple code to initiate SOC. It is implemented on internal ROM of SOC.
2. First boot loader (BL1): It is chip-specific and stored in external memory device.
3. Second boot loader (BL2): It is platform-specific and stored in external memory device. User should build and store this in an external memory device. It is not provided by Samsung.
從文字和圖片可以總結出如下幾點:
BL0:這段是固化的程式碼放置在 64K 的 iROM 裡面,負責初始化基本的系統功能比如時鐘和棧,並且載入 BL1 到內部 256KB 的 SRAM。
BL1:這段程式碼由三星提供,也更改不了,BL0 會根據 OM 引腳來判斷當前 booting 裝置是哪個,可以是 NAND_FLASH、SD卡、EMMC、USB裝置,從選定好的裝置中載入 BL1,BL1 主要負責初始化系統時鐘和 DRAM 控制器,然後從 booting 裝置中載入 OS 到 DRAM 中去執行,這裡的 OS 其實就是指 BL2,DRAM 指的則是外部的 SDRAM。
BL2:BL2 的主要功能是去載入我們的 UBOOT 程式碼,此後 UBOOT 執行在 DRAM上, 同樣載入的時候也需要校驗,這裡需要將 uboot.bin 合併(merge)進BL2裡面,在迅為的啟動腳本里面有描述,如下:
cat E4412.S.BL1.SSCR.EVT1.1.bin E4412.BL2.TZ.SSCR.EVT1.1.bin all00_padding.bin u-boot.bin E4412.TZ.SSCR.EVT1.1.bin > u-boot-iTOP-4412.bin
做好了一切準備以後就開始執行 start.S 了,這裡標題指的第一階段基本上都是用匯編去實現的,主要負責硬體的初始化,第二階段都是C程式碼,實現一些比較複雜的內容,下一章節描述。
以 start.S 的程式碼開始描述:
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
/*
globl就是相當於C語言中的Extern,宣告此變數,並且告訴連結器此變數是全域性的,外部可以訪問
指定入口為_start
u-boot.lds裡面定義了ENTRY(_start),即指定入口為_start
*/
.globl _start
/* 跳轉到reset,這裡的程式碼地址是00000010 */
_start: b reset
/*
ARM是RISC結構,資料從記憶體到CPU之間的移動只能通過L/S指令來完成,也就是ldr/str指令。
比如想把資料從記憶體中某處讀取到暫存器中,只能使用ldr
將_undefined_instruction這個地址處的word(一位元組)定義的值賦給pc。
ARM體系結構規定在上電覆位的起始位置必須有8條連續的跳轉指令,
通過硬體來實現。它們就是異常向量表。ARM在上電覆位後是從0x0開始啟動,
如果bootloader存在,則是從_start開始執行上面的跳轉沒有執行。
設定異常向量表的作用是識別bootloader,以後每當系統有異常出現時,
cpu會根據異常號從記憶體0x0處開始查詢並做相應的處理
下面8條即設定異常中斷向量表
*/
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
/*
當有異常出現ARM會自動執行以下步驟:
1 將下一條指令的地址存放在連線暫存器LR(通常是R14).---儲存位置
2 將相應的CPSR(當前程式狀態暫存器)複製到SPSR(備份的程式狀態暫存器)中
3 根據異常型別,強制設定CPSR執行模式位
4 強制PC從相應異常向量地址取出下一條指令執行,從而跳轉到異常處理函式中執行
*/
_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//快速中斷
_pad:
.word 0x12345678 /* now 16*4=64 */
.global _end_vect
_end_vect:
/* 接下來的程式碼,都要16位元組對齊,不足之處,用0xdeadbeef填充 */
.balignl 16,0xdeadbeef
_TEXT_BASE:
/* _TEXT_BASE是一個標號地址,在board\samsung\smdkc210中定義,通過反彙編或路徑可得知為"0xc3e00000" */
.word TEXT_BASE
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE /* 反彙編得知為:43e00000 */
.globl _armboot_start
_armboot_start:
/* 此含義可用C語言表示為:*(_armboot_start) = _start */
.word _start
/* 以下這些地址跟u-boot.lds一一對應,宣告地址標號 */
.globl _bss_start
_bss_start:
/* bss段的起始地址 */
.word __bss_start
.globl _bss_end
_bss_end:
/* bss段的結束地址 */
.word _end
/* 相當於一個無引數的巨集cache_invalidate_dcache_v7,也就相當於一個函數了,似乎和cache有關,暫不細究 */
.macro cache_invalidate_dcache_v7
MRC p15, 1, r0, c0, c0, 1 @ read Cache Level ID register (clidr)
ANDS r3, r0, #0x7000000 @ extract level of coherency from clidr
MOV r3, r3, lsr #23 @ left align level of coherency bit field
BEQ finished_inval @ if loc is 0, then no need to clean
MOV r10, #0 @ start clean at cache level 0 (in r10)
loop_1:
ADD r2, r10, r10, lsr #1 @ work out 3x current cache level
MOV r1, r0, lsr r2 @ extract cache type bits from clidr
AND r1, r1, #7 @ mask of the bits for current cache only
CMP r1, #2 @ see what cache we have at this level
BLT skip_inval @ skip if no cache, or just i-cache
MCR p15, 2, r10, c0, c0, 0 @ select current cache level in cssr
MOV r1, #0
MCR p15, 0, r1, c7, c5, 4 @ prefetchflush to synch the new cssr&csidr
MRC p15, 1, r1, c0, c0, 0 @ read the new csidr
AND r2, r1, #7 @ extract the length of the cache lines
ADD r2, r2, #4 @ add 4 (line length offset)
LDR r6, =0x3ff
ANDS r6, r6, r1, lsr #3 @ find maximum number on the way size
CLZ r5,r6 @ DCI 0xE16F5F16 , find bit position of way size increment
LDR r7, =0x7fff
ANDS r7, r7, r1, lsr #13 @ extract max number of the index size
loop_2:
MOV r8, r6 @ create working copy of max way size
loop_3:
ORR r11, r10, r8, lsl r5 @ factor way and cache number into r11
ORR r11, r11, r7, lsl r2 @ factor index number into r11
MCR p15, 0, r11, c7, c6, 2 @ invalidate by set/way
SUBS r8, r8, #1 @ decrement the way
BGE loop_3
SUBS r7, r7, #1 @ decrement the index
BGE loop_2
skip_inval:
ADD r10, r10, #2 @ increment cache number
CMP r3, r10
BGT loop_1
finished_inval:
.endm
/*
* the actual reset code
*/
reset:
/* 首先進入SVC管理模式,為什麼要進行SVC管理模式而不是其它模式,主要因為SVC模式比其他模式有更多的硬體訪問許可權,並且多了影子暫存器,可以訪問的硬體資源更多,詳情:http://www.360doc.com/content/13/0514/11/7245213_285318786.shtml */
/*
* MRS{條件} 通用暫存器,程式狀態暫存器(CPSR或SPSR)
* mrs :程式狀態暫存器訪問指令
* 通用暫存器 程式狀態暫存器(CPSR或SPSR)
* 讀取CPSR程式狀態暫存器,儲存到R0中
*/
mrs r0, cpsr
/*
* bic :BIC{條件}{S} 目的暫存器,運算元1,運算元2,
* BIC指令用於清除運算元1的某些位,並把結果放置到目的暫存器中。運算元1應是一個暫存器
* 運算元2可以是一個暫存器,被移位的暫存器,或一個立即數。運算元2為32位的掩碼,如果在掩碼中設定了某一
* 位,則清除這一位。未設定的掩碼位保持不變。
* 0x1f=00011111,相當於清除低5位,剛好是模式位。
*/
bic r0, r0, #0x1f
/*
* ORR{條件}{S} 目的暫存器,運算元1,運算元2
* ORR指令用於在兩個運算元上進行邏輯或運算,並把結果放置到目的暫存器中。運算元1應是一個暫存器,運算元
* 2可以是一個暫存器,被移位的暫存器,或一個立即數。該指令常用於設定運算元1的某些位。
* 0xd3=11010011
* 將r0與0xd3算數或運算,然後將結果給r0,即把r0的bit[7:6]和bit[4]和bit[2:0]置為1。
*/
orr r0, r0, #0xd3
/*
* MSR{條件} 程式狀態暫存器(CPSR或SPSR)_<域>,運算元
* MSR指令用於將運算元的內容傳送到程式狀態暫存器的特定域中
* 將r0中的值賦給狀態暫存器cpsr
*/
msr cpsr,r0
cache_init:
/* mrc: 從協處理器讀暫存器資料到ARM處理器的R0裡面 */
mrc p15, 0, r0, c0, c0, 0 @ read main ID register
and r1, r0, #0x00f00000 @ variant
and r2, r0, #0x0000000f @ revision
orr r2, r2, r1, lsr #20-4 @ combine variant and revision
cmp r2, #0x30
mrceq p15, 0, r0, c1, c0, 1 @ read ACTLR
orreq r0, r0, #0x6 @ Enable DP1(2), DP2(1)
mcreq p15, 0, r0, c1, c0, 1 @ write ACTLR
/*
CP15系統控制協處理器,CP15有很多個暫存器分別叫做暫存器0(Register 0),到暫存器15(Register 15)
CP15 —系統控制協處理器 (the system control coprocessor)他通過協處理器指令MCR和MRC提供具體的暫存器來配置和控制caches、MMU、保護系統、配置時鐘模式(在bootloader時鐘初始化用到)
CP15的暫存器只能被MRC和MCR(Move to Coprocessor from ARM Register )指令訪問
*/
/* Invalidate L1 I/D */
mov r0, #0 @ set up for MCR
/* 清空指令和資料的TLB */
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
/* 清除指令快取ICache */
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
/* disable MMU stuff and caches */
mrc p15, 0, r0, c1, c0, 0
/* 此行程式碼是將r0的值,即0,寫入到CP15的暫存器1中,向bit[0]寫入0,即關MMU */
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)/* 清除bit[13],異常暫存器基地址 */
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)/* 清除bit[2-0],關閉Dcache */
orr r0, r0, #0x00001000 @ set bit 12 (---I) Icache/* 設定bit[12],開啟指令快取Icache */
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align/* 設定bit[1],開啟資料地址對齊的錯誤檢查,即如果資料地址為非法(奇數?)地址,就報錯 */
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
#endif
/* Read booting information */
/* 這個暫存器就是去讀取OM(從哪啟動)暫存器的值 */
ldr r0, =POWER_BASE /* 將地址0x10020000中的值放入r0 */
ldr r1, [r0,#OMR_OFFSET] /* 將儲存器地址為r0+OMR_OFFSET(0x10020000)的字資料讀入暫存器R1 */
bic r2, r1, #0xffffffc1 /* 清除r1這個暫存器的bit[0]和bit[6:31],把結果放入r2 */
/* NAND BOOT */
cmp r2, #0xA
moveq r3, #BOOT_ONENAND//如果等於0xA了,執行這個將BOOT_ONENAND(0x1)存到R3
/* SD/MMC_CH2 BOOT */
cmp r2, #0x4
moveq r3, #BOOT_MMCSD //如果等於0x4了,執行這個將BOOT_MMCSD(0x3)存到R3
/* eMMC43_CH0/USB BOOT */
cmp r2, #0x6
moveq r3, #BOOT_EMMC43 //如果等於0x6了,執行這個將BOOT_EMMC43(0x6)存到R3
/* eMMC44_CH4/SDMMC_CH2 BOOT */
cmp r2, #0x28
moveq r3, #BOOT_EMMC441 //如果等於0x28了,執行這個將BOOT_EMMC43(0x7)存到R3
/*
User-defined information register. By asserting XnRESET pin, PMU clears INFORM0 to 3 registers
*/
ldr r0, =INF_REG_BASE /* 將配置寫入相應的暫存器 */
str r3, [r0, #INF_REG3_OFFSET]
/*
* Go setup Memory and board specific bits prior to relocation.
*/
/* 這段等下單獨截出來 */
bl lowlevel_init /* go setup pll,mux,memory */
ldr r0, =0x1002330C /* PS_HOLD_CONTROL register */
ldr r1, =0x00005300 /* PS_HOLD output high */
str r1, [r0]
/* get ready to call C functions */
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer 43e00000*/
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
/* 如果已經在DRAM裡面跑了就不需要過載了,實際上SDRAM在BL1的時候就初始化了,所以uboot已經執行在了SDRAM上,但具體在哪執行可能不是我們想定義的地址,所以還是得過載。下面會進行判斷是否過載
*/
ldr r0, =0xff000fff
/*
bic指令用於清除運算元1的某些位,並把結果放置到目的暫存器中
清除pc指標的低24位和高16位,其他位保持不變,即[24:47]不變
判斷uboot是不是在我們想要的位置上,如果不是就過載uboot到指定位置
*/
bic r1, pc, r0 /* r0 <- current base addr of code 當前PC地址*/
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram 程式碼段在記憶體中的位置*/
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip flash copy */
/*
這裡不相等代表當前PC不在該有的位置上,可能是因為UBOOT程式碼前面還有BL2的程式碼,現在不需要BL2了,所以過載uBOOT到0地址
*/
/* light led2 如果需要過載則點亮LED作為指示 */
ldr r0, =0x11000104 /* GPL2(0) */
ldr r1, =0x00000001 /* GPL2(0 set output */
str r1, [r0]
ldr r0, =0x11000100 /* GPL2(0) */
ldr r1, =0x00000001 /* GPL2(0 output high */
str r1, [r0]
/* wait us 延時 */
mov r1, #0x10000
9: subs r1, r1, #1
bne 9b
ldr r0, =INF_REG_BASE /* 讀出暫存器值,可知從哪裡啟動的 */
ldr r1, [r0, #INF_REG3_OFFSET]
/* 這裡以EMMC啟動為例,不一一列出各個選項了 */
....
/* eMMC43_CH0/USB BOOT */
cmp r1, #BOOT_EMMC43
beq emmc_boot
....
emmc_boot:
#if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
ldr r0, =CMU_BASE
ldr r2, =CLK_DIV_FSYS1_OFFSET /* 設定分頻 */
ldr r1, [r0, r2]
orr r1, r1, #0x3 /* 進行或運算 DOUTMMC0 = MOUTMMC0/(3 + 1) */
str r1, [r0, r2]
#endif
bl emmc_uboot_copy /* 確定重定位地址及大小 */
b after_copy /* 重定位程式碼 */
#if defined(CONFIG_ENABLE_MMU)
enable_mmu: /* 使能MMU */
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @load domain access register
/* Set the TTB register */
ldr r0, _mmu_table_base
ldr r1, =CFG_PHY_UBOOT_BASE
ldr r2, =0xfff00000
bic r0, r0, r2
orr r1, r0, r1
mcr p15, 0, r1, c2, c0, 0
/* Enable the MMU */
mmu_on:
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1
mcr p15, 0, r0, c1, c0, 0
nop
nop
nop
nop
#endif
#ifdef CONFIG_EVT1
/* store DMC density information in u-boot C level variable */
ldr r0, = CFG_UBOOT_BASE /* 把uboot地址0x43e00000寫入r0 */
sub r0, r0, #4 /* uboot的程式碼基地址減去4後的字資料寫入r1*/
ldr r1, [r0]
ldr r0, _dmc_density /* 0xFFFFFFFF = 4G的EMMC */
str r1, [r0] /* C表示: *r0 = r1 將這個字資料儲存到EMMC? */
#endif
skip_hw_init:
/* Set up the stack */
stack_setup:
/* 記憶體中uboot的區域空間(2M),減去的4K用於存放bd資訊結構體資料 */
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)//0x43e00000 + 2M - 4K
/* 請 BSS 段 */
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
/* 迴圈清除BSS段的內容,即置0 */
clbss_l:
str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
_start_armboot:
.word start_armboot /* 進入這個C函式後就屬於第二階段的程式碼了,彙編到此結束 */
補充說明一下 lowlevel_init 做了什麼:
#define check_mem /* 判斷是否需要過載的巨集 */
.globl lowlevel_init
lowlevel_init:
/* use iRAM stack in bl2 */
/* 設定成iram的棧保險一些,因為不用初始化,uboot剛開始並不知道dram是否初始化了,所以使用iram */
ldr sp, =0x02060000
push {lr}
/* check reset status */
ldr r0, =(INF_REG_BASE + INF_REG1_OFFSET)//INFORM1: 0x10020800+4
ldr r1, [r0]
/* AFTR wakeup reset */
ldr r2, =S5P_CHECK_DIDLE //0xBAD00000
cmp r1, r2
beq exit_wakeup
/* Sleep wakeup reset */
ldr r2, =S5P_CHECK_SLEEP//0x00000BAD
cmp r1, r2
beq wakeup_reset
/* PS-Hold high PS_HOLD_CONTROL */
ldr r0, =0x1002330c
ldr r1, [r0]
orr r1, r1, #0x300 /* r1和0x300或運算 */
str r1, [r0] /* r1寫回地址r0 */
/* 0x0 = Disables Pull-up/Pull-down */
ldr r0, =0x11000c08
ldr r1, =0x0
str r1, [r0]/* r1寫回地址r0 */
/* Clear MASK_WDT_RESET_REQUEST */
ldr r0, =0x1002040c
ldr r1, =0x00
str r1, [r0]
#ifdef check_mem /* 檢測是否需要過載 */
/* when we already run in ram, we don't need to relocate U-Boot.
* and actually, memory controller must be configured before U-Boot
* is running in ram.
*/
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq 1f /* r0 == r1 then skip sdram init */
#endif
/* 這裡需要過載所以要進行如下初始化 */
/* Memory initialize */
bl mem_ctrl_asm_init
/* init system clock */
bl system_clock_init
bl tzpc_init
b 1f
1:
/*wenpin.cui: headphone and sw uart switch init*/
ldr r0, =0x11000C44
ldr r1, [r0]
and r1, r1, #0x4
cmp r1, #0x4 /*uart*/
beq out
ldr r0, =0x11400084 /* GPC1(0) */
ldr r1, [r0] /* read GPC1DAT status*/
orr r1, r1, #0x1 /* GPC1(0) output high */
str r1, [r0]
ldr r0, =0x11400080 /* GPC1(0) */
ldr r1, [r0]
and r1, r1, #0xfffffff0
orr r1, r1, #0x1 /* GPC1(0) output */
str r1, [r0]
out:
/* for UART */
bl uart_asm_init
bl onenandcon_init
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
/* 2010.08.27 by icarus : for temporary 3D clock fix */
ldr r1, =0x1
ldr r2, =0x1003C22C
str r1, [r2]
ldr r1, =0x2
ldr r2, =0x1003C52C
str r1, [r2]
/* 2010.10.17 by icarus : for temporary MFC clock fix */
ldr r1, =0x3
ldr r2, =0x1003C528
str r1, [r2]
/*
其中儲存的暫存器中,也包括lr的值(因為用bl指令進行跳轉的話,那麼之前的pc的值是存在lr中的),
然後在子程式執行完畢的時候,再把堆疊中的lr的值pop出來,賦值給pc,這樣就實現了子函式的正確的返回。
*/
pop {pc}
上面的程式碼都進行了註解,可以總結uboot啟動第一階段的所做的事如下:
中斷向量地址定義
uboot程式碼段、資料段、bss段定義
上電進入復位中斷處理
把CPU的工作模式設定為SVC32模式
清空指令和資料的TLB
清除指令快取ICache
關MMU
關Dcache
讀取boot啟動方式
選擇相應boot(NAND/SD/EMMC)啟動模式
進入 lowlevel_init :set sp in bl2、Memory initialize、init system clock 、uart_asm_init、onenandcon_init
設定SDRAM棧,判斷是否需要過載,需要過載則點燈,讀取啟動資訊判斷從哪裡複製bin,進行過載,複製uboot到指定地址即重定位uboot,使能MMU,儲存 dmc density 資訊,設定棧,騰出4K存放bd資訊,清BSS段,執行 “start_armboot”。
針對上面可能還有些疑問:
問:根據手冊,IROM初始化了系統時鐘和棧,BL1初始化了系統時鐘和DRAM,是不是初始化這些後 UBOOT 可以不用再次初始化了呢?直接操作即可?
答:雖然說BL1初始化了DRAM和時鐘,但是這些設定我們並不清楚,也不知道是不是處在最優狀態,所以最好的解決方式就是再初始化一遍,設定為我們想要的配置值
不瞭解各個段區別的可以看下下面的 TIPS
TIPS:
- BSS段
在採用段式記憶體管理的架構中,BSS段(bss segment)通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域。BSS是英文Block Started by Symbol的簡稱。BSS段屬於靜態記憶體分配。- 資料段
在採用段式記憶體管理的架構中,資料段(data segment)通常是指用來存放程式中已初始化的全域性變數的一塊記憶體區域。資料段屬於靜態記憶體分配。- 程式碼段
在採用段式記憶體管理的架構中,程式碼段(text segment)通常是指用來存放程式執行程式碼的一塊記憶體區域。這部分割槽域的大小在程式執行前就已經確定,並且記憶體區域屬於只讀。在程式碼段中,也有可能包含一些只讀的常數變數,例如字串常量等