註:本文是學習朱老師課程整理的筆記,基於uboot-1.3.4和s5pc11x分析。
前部分的分析見uboot之start.s分析1 lowlevel_init
/* Go setup Memory and board specific bits prior to relocation.*/ ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */ sub sp, sp, #12 /* set stack */ mov fp, #0 bl lowlevel_init /* go setup pll,mux,memory */
這次設定棧是在SRAM中設定的,因為當前整個代碼還在SRAM中運行,此時DDR還未被初始化還不能用。棧地址0xd0036000是自己指定的,指定的原則就是這塊空間只給棧用,不會被別人佔用。
在調用函數前初始化棧,主要原因是BL只會將返回地址儲存到LR中,但是我們只有一個LR,所以在第二層調用函數前要先將LR入棧,否則函數返回時,第一層的返回地址就丟了。
關於lowlevel_init函數的分析見start.s之lowlevel_init分析 供電鎖存
/* To hold max8698 output before releasing power on switch, * set PS_HOLD signal to high */ ldr r0, =0xE010E81C /* PS_HOLD_CONTROL register */ ldr r1, =0x00005301 /* PS_HOLD output high */ str r1, [r0]
在lowlevel_init中其實已經設定過供電鎖存。 再次設定棧
/* get ready to call C functions */ ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */ sub sp, sp, #12 mov fp, #0 /* no previous frame, so fp=0 */
之前在調用lowlevel_init程式前設定過1次棧,那時候因為DDR尚未初始化,因此程式執行都是在SRAM中,所以在SRAM中分配了一部分記憶體作為棧。
本次設定棧是因為DDR已經被初始化了,因此要把棧挪移到DDR中,所以要重新設定棧;這裡實際設定的棧的地址是33E00000,剛好在uboot的程式碼片段的下面緊挨著。
為什麼放在uboot代碼的下面呢。因為arm是滿減棧,放在下面就不會覆蓋掉uboot的代碼。
為什麼要再次設定棧。DDR已經初始化了,已經有大片記憶體可以用了,沒必要再把棧放在SRAM中,棧放在那裡要注意不能使用過多的棧否則棧會溢出,我們及時將棧遷移到DDR中也是為了儘可能避免棧使用時候的小心翼翼。 再次判斷是否重定位
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 after_copy /* r0 == r1 then skip flash copy */
在lowlevel_init中判斷過一次重定位,這裡再次用相同的代碼判斷運行地址是在SRAM中還是DDR中,不過本次判斷的目的不同,上次判斷是為了決定是否要執行初始化時鐘和DDR的代碼,這次判斷是為了決定是否進行uboot的relocate。
冷啟動時,當前情況是uboot的前一部分(16kb或者8kb),開機自動從SD卡載入到SRAM中運行,uboot的第二部分(其實第二部分是整個uboot)還躺在SD卡的某個扇區開頭的N個扇區中。
此時uboot的第一階段已經即將結束了,結束之前要把第二部分載入到DDR中連結地址處(33e00000),這個載入過程就叫重定位。 重定位
/* SD/MMC BOOT */ cmp r2, #0xc moveq r3, #BOOT_MMCSD ……/*將#BOOT_MMCSD寫入了INF_REG3寄存器中儲存著*/ ldr r0, =INF_REG_BASE str r3, [r0, #INF_REG3_OFFSET] ……/*再將INF_REG3_OFFSET讀出來,再和#BOOT_MMCSD去比較,確定是從MMCSD啟動*/ ldr r0, =INF_REG_BASE ldr r1, [r0, #INF_REG3_OFFSET] cmp r1, #BOOT_MMCSD beq mmcsd_boot
bl movi_bl2_copy
真正的重定位是通過調用movi_bl2_copy函數完成的,在uboot/cpu/s5pc11x/movi.c中是一個C語言的函數
ch = *(volatile u32 *)(0xD0037488); copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98)); ……if (ch == 0xEB000000) { ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT, CFG_PHY_UBOOT_BASE, 0); ……if (ch == 0xEB200000) { ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT, CFG_PHY_UBOOT_BASE, 0);
0xD0037488這個記憶體位址在SRAM中,這個地址中的值是被硬體自動化佈建的。硬體根據我們實際電路中SD卡在哪個通道中,會將這個地址中的值設定為相應的數字。譬如我們從SD0通道啟動時,這個值為0xEB000000;從SD2通道啟動時,這個值為0xEB200000。
copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0);
分析參數:
2:表示通道2;
MOVI_BL2_POS:是uboot的第二部分在SD卡中的開始扇區,這個扇區數字必須和燒錄uboot時燒錄的位置相同;
MOVI_BL2_BLKCNT:是uboot的長度佔用的扇區數;
CFG_PHY_UBOOT_BASE:是重定位時將uboot的第二部分複製到DDR中的起始地址(33E00000)。 使能域訪問(cp15的c3寄存器)
after_copy:#if defined(CONFIG_ENABLE_MMU)enable_mmu: /* enable domain access */ ldr r5, =0x0000ffff mcr p15, 0, r5, c3, c0, 0 @load domain access register
cp15副處理器內部有c0到c15共16個寄存器,這些寄存器每一個都有自己的作用。我們通過mrc和mcr指令來訪問這些寄存器。所謂的操作cp副處理器其實就是操作cp15的這些寄存器。
c3寄存器在mmu中的作用是控制域訪問。域訪問是和MMU的存取控制有關的。 設定TTB(cp15的c2寄存器)
/* 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
轉換表放置在記憶體中的,放置時要求起始地址在記憶體中要xx位對齊。轉換表不需要軟體去幹涉使用,而是將基地址TTB設定到cp15的c2寄存器中,然後MMU工作時會自動去查轉換表。
關於_mmu_table_base所指向的內容見lowlevel_init之mmu_table分析 使能MMU單元(cp15的c1寄存器)
/* Enable the MMU */mmu_on: mrc p15, 0, r0, c1, c0, 0 orr r0, r0, #1 mcr p15, 0, r0, c1, c0, 0
cp15的c1寄存器的bit0控制MMU的開關。只要將這一個bit置1即可開啟MMU。開啟MMU之後上層軟體層的地址就必須經過TT的轉換才能發給下層物理層去執行。
MMU就是memory management unit,記憶體管理單元。MMU實際上是SoC中一個硬體單元,它的主要功能就是實現虛擬位址到物理地址的映射。
MMU在CP15副處理器中進行控制,也就是說要操控MMU進行虛擬位址映射,方法就是對cp15副處理器的寄存器進行編程。
關於地址映射 存取控制就是:在管理上對記憶體進行分塊,然後每塊進行獨立的虛擬位址映射,然後在每一塊的映射關係中同時還實現了存取控制(對該塊可讀、可寫、唯讀、唯寫、不可訪問等控制)。段錯誤(Segmentation fault)實際上就和MMU實現的存取控制有關。當前程式只能操作自己有權操作的位址範圍(若干個記憶體塊),如果當前程式指標出錯訪問了不該訪問的記憶體塊則就會觸發段錯誤。
cache的工作和虛擬位址映射有關係。
cache是快速緩衝,意思就是比CPU慢但是比DDR塊。CPU嫌DDR太慢了,於是乎把一些DDR中常用的內容事先讀取緩衝在cache中,然後CPU每次需要找東西時先在cache中找。如果cache中有就直接用cache中的;如果cache中沒有才會去DDR中尋找。
關於MMU的分析見arm MMU詳解
再次設定棧
stack_setup:#if defined(CONFIG_MEMORY_UPPER_CODE) ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)#else ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ sub r0, r0, #CFG_MALLOC_LEN /* malloc area */ sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */#if defined(CONFIG_USE_IRQ) sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)#endif sub sp, r0, #12 /* leave 3 words for abort-stack */#endif
這次設定棧還是在DDR中,之前雖然已經在DDR中設定過一次棧了,但是本次設定棧的目的是將棧放在比較合適(安全,緊湊而不浪費記憶體)的地方。
我們實際將棧設定在uboot起始地址上方2MB處,這樣安全的棧空間是:2MB-uboot大小-0x1000=1.8MB左右。這個空間既沒有太浪費記憶體,又足夠安全。 清理bss
clear_bss: ldr r0, _bss_start /* find start of bss segment */ ldr r1, _bss_end /* stop here */ mov r2, #0x00000000 /* clear */clbss_l: str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp r0, r1 ble clbss_l
bss段的開頭和結尾地址的符號是從連結指令碼u-boot.lds得來的。 跳到第二階段
ldr pc, _start_armboot_start_armboot: .word start_armboot
start_armboot是在uboot/lib_arm/board.c中。
這是一個C語言實現的函數。這個函數就是uboot的第二階段。這句代碼的作用就是將uboot第二階段執行的函數的地址傳給pc,實際上就是使用一個遠跳轉直接跳轉到DDR中的第二階段開始地址處。
遠跳轉的含義就是這句話載入的地址和當前運行地址無關,而和連結地址有關。因此這個遠跳轉可以實現從SRAM中的第一階段跳轉到DDR中的第二階段。
到此uboot啟動第一階段的程式碼分析完畢。
總結:uboot的第一階段做了哪些工作
(1)構建異常向量表
(2)設定CPU為SVC模式
(3)關看門狗
(4)開發板供電置鎖
(5)時鐘初始化
(6)DDR初始化
(7)串口初始化並列印”OK”
(8)重定位
(9)建立映射表並開啟MMU
(10)跳轉到第二階段
第二階段程式碼分析見uboot之start_armboot分析1