u-boot-2009.08在mini2440上的移植(一)-建立mini2440工程環境(2)
在真正開始移植Uboot之前,這裡還是先分析一下uboot的啟動流程吧!很有利于之後對移植的了解,這裡分析的是未經修改的u-boot源碼
根據cpu/arm920t/u-boot.lds中指定的連接配接方式:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
_end = .;
}
第一個連結的是cpu/arm920t/start.o,是以u-boot.bin的入口代碼在cpu/arm920t/start.o中,其源代碼在cpu/arm920t/start.S中。下面我們來分析cpu/arm920t/start.S的執行,下面就開始分段分析start.S這個檔案
.globl _start
_start: b start_code //複位
ldr pc, _undefined_instruction //未定義指令響向量
ldr pc, _software_interrupt //軟體中斷向量
ldr pc, _prefetch_abort //讀取指令異常向量
ldr pc, _data_abort //資料操作異常向量
ldr pc, _not_used //未使用
ldr pc, _irq //irq中斷向量
ldr pc, _fiq //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
globl是個關鍵字,是以,意思很簡單,就是相當于C語言中的Extern,聲明此變量,并且告訴連結器此變量是全局的,外部可以通路,是以,你可以看到u-boot.lds中,有用到此變量:
ENTRY(_start)
即指定入口為_start,而由下面的_start的含義可以得知,_start就是整個start.S的最開始,即整個uboot的代碼的開始。
_start後面加上一個冒号’:’,表示其是一個标号Label,類似于C語言goto後面的标号。
而同時,_start的值,也就是這個代碼的位置了,此處即為代碼的最開始,相對的0的位置。而此處最開始的相對的0位置,在程式開始運作的時候,如果是從NorFlash啟動,那麼其位址是0,_stat=0,如果是重新relocate代碼之後,就是我們定義的值了,即在u-boot-2009.08\board\samsung\config.mk中的:
TEXT_BASE = 0x33F80000
表示是代碼段的基位址,即_start=TEXT_BASE=0x33F80000
而_start标号後面的:
b reset
就是跳轉到對應的标号為reset的位置。
上面那些ldr的作用,以第一個_undefined_instruction為例,就是将位址為_undefined_instruction中的一個word的值,指派給pc。
是以上面的含義,以_undefined_instruction為例,就是,此處配置設定了一個word=32bit=4位元組的位址空間,裡面存放的值是undefined_instruction。
而此處_undefined_instruction也就是該位址空間的位址了。用C語言來表達就是:
_undefined_instruction = &undefined_instruction
或
*_undefined_instruction = undefined_instruction
在後面的代碼,我們可以看到,undefined_instruction也是一個标号,即一個位址值,對應着就是在發生“未定義指令”的時候,系統所要去執行的代碼。(其他幾個對應的“軟體中斷”,“預取指錯誤”,“資料錯誤”,“未定義”,“(普通)中斷”,“快速中斷”,也是同樣的做法,跳轉到對應的位置執行對應的代碼。)
是以:
ldr pc, 标号1
......
标号1:.word 标号2
......
标号2:
......(具體要執行的代碼)
的意思就是,将位址為标号1中内容載入到pc,而位址為标号1中的内容,正好裝的是标号2。
用C語言表達其實很簡單:
PC = *(标号1) = 标号2
對PC指派,即是實作代碼跳轉,是以整個這段彙編代碼的意思就是:
跳轉到标号2的位置,執行對應的代碼。
接下來的代碼,都要16位元組對齊,不足之處,用0xdeadbeef填充。覺得這樣解釋會更加合理些:
此處0xdeadbeef本身沒有真正的意義,但是很明顯,字面上的意思是,(壞)死的牛肉。雖然其本身沒有實際意義,但是其是在十六進制下,能表示出來的,為數不多的,可讀的單詞之一了。另外一個相對常見的是:0xbadc0de,意思是bad code,壞的代碼,注意其中的o是0,因為十六進制中是沒有o的。這些“單詞”,相對的作用是,使得讀代碼的人,以及在檢視程式運作結果時,容易看懂,便于引起注意。而關于自己之前,随意杜撰出來的,希望起到搞笑作用,表示good beef(好的牛肉)的0xgoodbeef,實際上,在十六進制下,會出錯的,因為十六進制下沒有o和 g這兩個字母。
在cpu/arm920t/start.S中還有這些異常對應的異常處理程式。當一個異常産生時,CPU根據異常号在異常向量表中找到對應的異常向量,然後執行異常向量處的跳轉指令,CPU就跳轉到對應的異常處理程式執行。
其中複位異常向量的指令“b start_code”決定了U-Boot啟動後将自動跳轉到标号“start_code”處執行。
在系統上電的時候首先執行的就是複位動作,是以start_code也就是第一個要執行的代碼
在真正開始運作之前,我們還是很有必要再分析一下之後的這一小段代碼:
_TEXT_BASE:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#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
此處和上面的類似,_TEXT_BASE是一個标号位址,此位址中是一個word類型的變量,變量名是TEXT_BASE,此值見名知意,是text的base,即代碼的基位址,可以在 u-boot-2009.08\board\samsung\config.mk中找到他的定義:
TEXT_BASE = 0x33F80000
然後分别定義了全局變量_armboot_start,_bss_start,__bss_end,IRQ_STACK_START,FIQ_STACK_START,
1._start的值經過上面的分析已經得出,可用c語言的形式表示為*(_armboot_start) = _start
2.關于_bss_start和_bss_end都隻是兩個标号,對應着此處的位址。而兩個位址裡面分别存放的值是__bss_start和_end,這兩個的值,根據注釋所說,是定義在開發闆相關的連結腳本裡面的,我們此處的開發闆相關的連結腳本也就是最開始提到的u-boot.lds了,看上面的代碼可以知道這兩個變量的定義,而關于_bss_start和_bss_end定義為.glogl即全局變量,是因為uboot的其他源碼中要用到這兩個變量,詳情請自己去搜尋源碼。
3.後面就是直接的指派語句了,用c語言的形式表示如下:*(IRQ_STACK_START)= 0x0badc0de和*(FIQ_STACK_START)= 0x0badc0de,IRQ_STACK_START和FIQ_STACK_START,将會在cpu_init中用到。不過此處,是隻有當定義了宏CONFIG_USE_IRQ的時候,才用到這兩個變量,其含義也很明顯,隻有用到了中斷IRQ,才會用到中斷的堆棧,才有中端堆棧的起始位址。快速中斷FIQ,同理。
第一步:
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0,cpsr /*将CPSR寄存器位址交給RO*/
bic r0,r0,#0x1f /*工作模式位清零,bic将r0的後五位清理并儲存在r0中*/
orr r0,r0,#0xd3 /*工作模式位設定為“10011”(管理模式),并将中斷禁止位和快中斷禁止位置1*/
msr cpsr,r0
bl coloured_LED_init /*在編譯的時候産生錯誤,已經注釋掉了*/
bl red_LED_on /*在編譯的時候産生錯誤,已經注釋掉了*/
上面方法設定cpu進入SVC模式,其實要做動作很簡單,擷取到CPSR寄存器的位址,儲存到ro中,然後清除這個寄存器中值的後五位,然後将這五位填充為“10011”,然後再将儲存到r0中的計算結果儲存到CSPR寄存器中去,下面是s3c2440的官方手冊中的說明,後面的五位M4,M3,M2,M1,M0就是我們要修改的地方,這五位決定的模式狀态的選擇,具體如何選擇下面也有貼出:
CPSR 是目前的程式狀态寄存器(Current Program Status Register),而 SPSR 是儲存的程式狀态寄存器(Saved Program Status Register)。
MRS指令的格式為:
MRS{條件} 通用寄存器,程式狀态寄存器(CPSR或SPSR)
MRS指令用于将程式狀态寄存器的内容傳送到通用寄存器中。該指令一般用在以下兩種情況:
Ⅰ.當需要改變程式狀态寄存器的内容時,可用MRS将程式狀态寄存器的内容讀入通用寄存器,修改後再寫回程式狀态寄存器。
Ⅱ.當在異常處理或程序切換時,需要儲存程式狀态寄存器的值,可先用該指令讀出程式狀态寄存器的值,然後儲存。
指令示例:
MRS R0,CPSR ;傳送CPSR的内容到R0
MRS R0,SPSR ;傳送SPSR的内容到R0”
是以,上述彙編代碼含義為,将CPSR的值賦給R0寄存器。
BIC指令的格式為:
BIC{條件}{S} 目的寄存器,操作數1,操作數2
BIC指令用于清除操作數1的某些位,并把結果放置到目的寄存器中。操作數1應是一個寄存器,
操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。操作數2為32位的掩碼,如果在掩碼中設定了某一位,則清除這一位。未設定的掩碼位保持不變。
0x1f=11111b,是以,此行代碼的含義就是,清除r0的bit[4:0]位。
ORR指令的格式為:
ORR{條件}{S} 目的寄存器,操作數1,操作數2
ORR指令用于在兩個操作數上進行邏輯或運算,并把結果放置到目的寄存器中。操作數1應是一個寄存器,操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。該指令常用于設定操作數1的某些位。
指令示例:
ORR R0,R0,#3 ; 該指令設定R0的0、1位,其餘位保持不變。
是以此行彙編代碼的含義為:
而0xd3=1101 0111[4:0]位。
将r0與0xd3算數或運算,然後将結果給r0,即把r0的bit[7:6]和bit[4]和bit[2:0]置為1。
MSR指令的格式為:
MSR{條件} 程式狀态寄存器(CPSR或SPSR)_<域>,操作數
MSR指令用于将操作數的内容傳送到程式狀态寄存器的特定域中。其中,操作數可以為通用寄存器或立即數。<域>用于設定程式狀态寄存器中需要操作的位,32位的程式狀态寄存器可分為4個域:
位[31:24]為條件标志位域,用f表示;
位[23:16]為狀态位域,用s表示;
位[15:8]為擴充位域,用x表示;
位[7:0]為控制位域,用c表示;
該指令通常用于恢複或改變程式狀态寄存器的内容,在使用時,一般要在MSR指令中指明将要操作的域。
指令示例:
MSR CPSR,R0 ;傳送R0的内容到CPSR
MSR SPSR,R0 ;傳送R0的内容到SPSR
MSR CPSR_c,R0 ;傳送R0的内容到SPSR,但僅僅修改CPSR中的控制位域
此行彙編代碼含義為,将r0的值賦給CPSR。

第二步:
/*這裡是為AT91RM9200寫的代碼,暫時先不關注*/
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/
ldr r0, =_start
ldr r1, =0x0
mov r2, #16
copyex:
subs r2, r2, #1
ldr r3, [r0], #4
str r3, [r1], #4
bne copyex
#endif
第三步:
#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
/* turn off the watchdog */
/*設定控制寄存器位址*/
# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000 /* 看門狗timer寄存器位址*/
# define INTMSK 0x4A000008 /* 中斷控制寄存器 */
# define INTSUBMSK 0x4A00001C /* SUB中斷控制寄存器*/
# define CLKDIVN 0x4C000014 /* 時鐘分頻寄存器 */
# endif
/* 關閉看門狗*/
ldr r0, =pWTCON /* 将WTCON寄存器位址儲存到r0中*/
mov r1, #0x0 /* R1中儲存的值為0*/
str r1, [r0] /* 用str将r1中的值寫入到r0所儲存的位址中*/
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
/* 屏蔽所有中斷*/
mov r1, #0xffffffff /* R1中儲存的值為0xffffffff*/
ldr r0, =INTMSK /* 将INTMSK寄存器位址儲存到r0中*/
str r1, [r0] /* 用str将r1中的值寫入到r0所儲存的位址中*/
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff /* R1中儲存的值為0x3ff*/
ldr r0, =INTSUBMSK /* 将INTSUBMSK寄存器位址儲存到r0中*/
str r1, [r0] /* 用str将r1中的值寫入到r0所儲存的位址中*/
# endif
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN /* 将CLKDIVN寄存器位址儲存到r0中*/
mov r1, #3 /* R1中儲存的值為3*/
str r1, [r0] /* 用str将r1中的值寫入到r0所儲存的位址中*/
#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
上面幾個宏定義所對應的位址,都可以在對應的datasheet中找到對應的定義:
其中,S3C2410和TQ2440開發闆所用的CPU S3C2440,兩者在這部分的寄存器定義,都是一樣的,是以此處,采用CONFIG_S3C2410所對應的定義。
關于S3C2440相關的軟硬體資料,這個網站提供的很全面:http://just4you.springnote.com/pages/1052612
1.先是用r0寄存器存pWTCON的值,然後r1=0,再将r1中的0寫入到pWTCON中,其實就是pWTCON = 0;
看門狗控制器的最低位為0時,看門狗不輸出複位信号,以上代碼向看門狗控制寄存器寫入0,關閉看門狗。否則在U-Boot啟動過程中,CPU将不斷重新開機。
僞指令,就是“僞”的指令,是針對“真”的指令而言的。
真的指令就是那些常見的指令,比如上面說的arm的ldr,bic,msr等等指令,是arm體系架構中真正存在的指令,你在arm彙編指令集中找得到對應的含義。
而僞指令是寫出來給彙程式設計式看的,彙程式設計式能看的僞指令具體表示的是啥意思,然後将其翻譯成真正的指令或者進行相應的處理。
僞指令ldr文法和含義:http://blog.csdn.net/lihaoweiV/archive/2010/11/24/6033003.aspx
另外還有一個就是ldr僞指令,雖然ldr僞指令和ARM的ldr指令很像,但是作用不太一樣。ldr僞指令可以在立即數前加上=,以表示把一個位址寫到某寄存器中,比如:
ldr r0, =0x12345678
這樣,就把0x12345678這個位址寫到r0中了。是以,ldr僞指令和mov是比較相似的。
隻不過mov指令後面的立即數是有限制的,這個立即數,能夠必須由一個8位的二進制數,即屬于0x00-0xFF内的某個值,經過偶數次右移後得到,這樣才是合法資料,而ldr僞指令沒有這個限制。
那為何ldr僞指令的操作數沒有限制呢,那是因為其是僞指令,寫出來的僞指令,最終會被編譯器解釋成為真正的,合法的指令的,一般都是對應的mov指令。
這樣的話,寫彙程式設計式的時候,使用MOV指令是比較麻煩的,因為有些簡單的資料比較容易看出來,有些資料即不容易看出來是否是合法資料。是以,對此,ldr僞指令的出現,就是為了解決這個問題的,你隻管放心用ldr僞指令,不用關心操作數,而寫出的ldr僞指令,編譯器會幫你翻譯成對應的真正的彙編指令的。
而關于編譯器是如何将這些ldr僞指令翻譯成為真正的彙編指令的,我的了解是,其自動會去算出來對應的操作數,是否是合法的mov 的操作數,如果是,就将該ldr僞指令翻譯成mov指令,否則就用别的方式處理,我所觀察到的,其中一種方式就是,單獨申請一個4位元組的空間用于存放操作數,然後用ldr指令實作。
在uboot中,最後make完畢之後,會生産u-boot,
通過:
arm-linux-objdump –d u-boot > dump_u-boot.txt
就可以把對應的彙編代碼輸出到該txt檔案了,其中就能找到僞指令:
ldr r0, =0x53000000
所對應的,真正的彙編代碼:
33d00068: e3a00453 mov r0, #1392508928 ; 0x53000000
是以被翻譯成了mov指令。
而經過我的嘗試,故意将0x53000000改為0x53000010,對應的生産的彙編代碼為:
33d00068: e59f0408 ldr r0, [pc, #1032] ; 33d00478 <fiq+0x58>
......
33d00478: 53000010 .word 0x53000010
其中可以看到,由于0x53000010不是有效的mov的操作數,沒法找到合适的0x00-0Xff去通過偶數次循環右移而得到,是以隻能換成此處這種方式,即在另外申請一個word的空間用于存放這個值:
33d00478: 53000010 .word 0x53000010
然後通過計算出相對目前PC的偏移,得到的位址,用ldr指令去除該位址中的值,即0x53000010,送給r0,比起mov指令,要複雜的多,也多消耗了一個word的空間。
對應地,其他的方式,個人了解,好像也可以通過MVN指令來實作,具體細節,有待進一步探索。
而這裡的:ldr r0, =pWTCON
意思就很清楚了,就是把宏pWTCON的值指派給r0寄存器,即
r0=0x53000000
MOV指令的格式為:
MOV{條件}{S} 目的寄存器,源操作數
MOV指令可完成從另一個寄存器、被移位的寄存器或将一個立即數加載到目的寄存器。其中S選項決定指令的操作是否影響CPSR中條件标志位的值,當沒有S時指令不更新CPSR中條件标志位的值。
指令示例:
MOV R1,R0 ;将寄存器R0的值傳送到寄存器R1
MOV PC,R14 ;将寄存器R14的值傳送到PC,常用于子程式傳回
MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位後傳送到R1
不過對于MOV指令多說一句,那就是,一般可以用類似于:
MOV R0,R0
的指令來實作NOP操作。
上面這句mov指令很簡單,就是把0x0指派給r1,即r1=0x0
STR指令的格式為:
STR{條件} 源寄存器,<存儲器位址>
STR指令用于從源寄存器中将一個32位的字資料傳送到存儲器中。該指令在程式設計中比較常用,且尋址方式靈活多樣,使用方式可參考指令LDR。
指令示例:
STR R0,[R1],#8 ;将R0中的字資料寫入以R1為位址的存儲器中,并
将新位址R1+8寫入R1。
STR R0,[R1,#8] ;将R0中的字資料寫入以R1+8為位址的存儲器中。
是以這句str的作用也很簡單,那就是将r1寄存器的值,傳送到位址值為r0的(存儲器)記憶體中。
用C語言表示就是:*r0 = r1
2.INTMSK是主中斷屏蔽寄存器,每一位對應SRCPND(中斷源引腳寄存器)中的一位,表明SRCPND相應位代表的中斷請求是否被CPU所處理。根據手冊,INTMSK寄存器是一個32位的寄存器,每位對應一個中斷,向其中寫入0xffffffff就将INTMSK寄存器全部位置一,進而屏蔽所有的中斷。
此處,關于mask這個詞,解釋一下。mask這個單詞,是面具的意思,而中斷被mask了,就是中斷被掩蓋了,即雖然硬體上中斷發生了,但是此處被屏蔽了,是以從效果上來說,就相當于中斷被禁止了,硬體上即使發生了中斷,CPU也不會去執行對應中斷服務程式ISR了。
各位所控制的中斷,下面隻貼出來一部分,可以自己檢視手冊:
3.INTSUBMSK每一位對應SUBSRCPND中的一位,表明SUBSRCPND相應位代表的中斷請求是否被CPU所處理。根據手冊,INTSUBMSK寄存器是一個32位的寄存器,但是隻使用了低15位。向其中寫入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置一,進而屏蔽對應的中斷。
此處是将2410的INTSUBMSK設定為0x3ff。開始時很是不能了解,為什麼要屏蔽掉所有中斷,而這裡隻屏蔽掉了後面十位,從手冊上看是低15位啊,的确此處設定的0x3ff,是不嚴謹的。因為,根據2410的datasheet中關于INTSUBMSK的解釋,bit[10:0]共11位,雖然預設reset的每一位都是1,但是此處對應的mask值,應該是11位全為1=0x7ff。即寫成0x3ff,雖然是可以正常工作的,但是卻不夠嚴謹的。此處CPU是是S3C2440,是以用到0x7fff這段代碼。其意思也很容易看懂,就是将INTSUBMSK寄存器的值設定為0x7fff。
4.設定時鐘分頻寄存器
這裡隻是設定相應的位,不做過多說明:
第四步:
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit
#endif
關于bl指令的含義:
b指令,是單純的跳轉指令,即CPU直接跳轉到某位址繼續執行。
而BL是Branch with Link,帶分支的跳轉,而Link指的是Link Register,連結寄存器R14,即lr,是以,bl的含義是,除了包含b指令的單純的跳轉功能,在跳轉之前,還把r15寄存器=PC=cpu位址,指派給r14=lr,然後跳轉到對應位置,等要做的事情執行完畢之後,再用
mov pc, lr
使得cpu再跳轉回來,是以整個邏輯就是調用子程式的意思。
BL指令的格式為:
BL{條件} 目标位址
BL 是另一個跳轉指令,但跳轉之前,會在寄存器R14中儲存PC的目前内容,是以,可以通過将R14 的内容重新加載到PC中,來傳回到跳轉指令之後的那個指令處執行。該指令是實作子程式調用的一個基本但常用的手段。以下指令:
BL Label ;當程式無條件跳轉到标号Label處執行時,同時将目前的PC值儲存到R14中
對于上面的代碼來說,意思就很清晰了,就是當沒有定義CONFIG_SKIP_LOWLEVEL_INIT的時候,就掉轉到cpu_init_crit的位置,而在後面的代碼cpu_init_crit中,你可以看到最後一行彙編代碼就是
mov pc, lr,
又将PC跳轉回來,是以整個的含義就是,調用子程式cpu_init_crit,等cpu_init_crit執行完畢,再傳回此處繼續執行下面的代碼。
于此對應地b指令,就隻是單純的掉轉到某處繼續執行,而不能再通過mov pc, lr跳轉回來了。
cpu_init_crit這段代碼在U-Boot正常啟動時才需要執行,若将U-Boot從RAM中啟動則應該注釋掉這段代碼。跳入 cpu_init_crit ,這是一個系統初始化函數,他還會調用 board/*/lowlevel_init.S 中的lowlevel_init 函數。 主要是對系統總線的初始化,初始化了連接配接存儲器的位寬、速度、重新整理率等重要參數。經過這個函數的正确初始化,Nor Flash、SDRAM才可以被系統使用。下面的代碼重定向就依賴它。下面分析一下cpu_init_crit到底做了什麼:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
/*
* flush v4 I/D caches
*/
/* 使資料cache與指令cache無效 */
mov r0, #0
/* 向c7寫入0将使ICache與DCache無效*/
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
/* 向c8寫入0将使TLB失效 */
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
/*
* disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0 /*讀出控制寄存器到r0中*/
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0 /*儲存r0到控制寄存器*/
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
調用 board/*/lowlevel_init.S 中的 lowlevel_init函數,對系統總線的初始化,初始化了連接配接存儲器的位寬、速度、重新整理率等重要參數。經過這個函數的正确初始化,Nor Flash、SDRAM才可以被系統使用。 代碼中的c0,c1,c7,c8都是ARM920T的協處理器CP15的寄存器。其中c7是cache控制寄存器,c8是TLB控制寄存器。将0寫入c7、c8,使Cache,TLB内容無效。關閉了MMU。這裡還有很多的不了解,接下來還是要重點研究一下啊
先看CP15的c1寄存器的格式(僅列出代碼中用到的位):
各個位的意義如下:
V : 表示異常向量表所在的位置,0:異常向量在0x00000000;1:異常向量在 0xFFFF0000
I : 對ICaches的配置, 0 :關閉ICaches;1 :開啟ICaches
R,S : 用來與頁表中的描述符一起确定記憶體的通路權限
B : 0 :CPU為小位元組序;1 : CPU為大位元組序
C : 0:關閉DCaches;1:開啟DCaches
A : 0:資料通路時不進行位址對齊檢查;1:資料通路時進行位址對齊檢查
M : 0:關閉MMU;1:開啟MMU
之後跳轉到lowlevel_init中運作,其中的lowlevel_init就完成了記憶體初始化的工作,由于記憶體初始化是依賴于開發闆的,是以lowlevel_init的代碼一般放在board下面相應的目錄中。對于mini2410,lowlevel_init在board/samsung/smdk2410/lowlevel_init.S中定義如下:
_TEXT_BASE:
.word TEXT_BASE
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.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))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
這裡上面針對ldr的說明還有遺漏的地方,加在這裡了
指令 | 說明 |
LDR R0,[R1] | 将位址R1處字資料讀入R0 |
LDR R0, [R1, R2] | 将位址R1+R2處字資料讀入R0 |
LDR R0,[R1, #8] | 将位址R1+8處字資料讀入R0 |
LDR R0, [R1, R2]! | 将位址R1+R2處字資料讀入R0,并将新位址R1+R2寫入R1 |
LDR R0, [R1, #8]! | 将位址R1+8處字資料讀入R0,并将新位址R1+8寫入R1 |
LDR R0, [R1], R2 | 将位址R1的字資料讀入R0,并将新位址R1+R2寫入R1 |
LDR R0, [R1, R2,LSL #2]! | 将位址R1+R2x4處字資料讀入R0,新址R1+R2x4寫入R1 |
LDR R0,[R1],R2,LSL #2 | 将位址R1處字資料寫入R0,新址R1+R2x4寫入R1 |
STR r0,[r1,#-12] | 将r0寫入到位址r1-12處 |
是以上面 ldr r3, [r0], #4語句的作用是将R0所指源資料區的4個位元組(1個字)資料裝載至R3,然後R0=R0+4;同樣的道理, str r3, [r1], #4這條語句的作用是将R3中4個位元組(1個字)資料存到R1所指目的資料區,然後R1=R1+4
然後cmp r2, r0對r2和r0進行比較,不等則跳轉到上面标号為0處的位址繼續執行bne 0b 跳回到傳回位址中繼續,實作了資料的搬移,知道吧定義的十三個寄存器的内容都搬移完成傳回調用的地方繼承往下執行
第五步:
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
代碼重定向,它首先檢測自己是否已經在記憶體中: 如果是直接跳到下面的堆棧初始化代碼 stack_setup。 如果不是就将自己從 Nor Flash 中拷貝到記憶體中 ,進行自循環拷貝
ADR是小範圍的位址讀取僞指令.ADR 指令将基于PC 相對偏移的位址值讀取到寄存器中.在彙編編譯源程式時,ADR 僞指令被編譯器替換成一條合适的指令.通常,編譯器用一條ADD 指令或SUB 指令來實作該ADR 僞指令的功能,若不能用一條指令實作,則産生錯誤,編譯失敗.上邊的u-boot代碼中的"adr r0,_start",就是把PC的目前值賦給r0;先是比較目前位址和_TEXT_BASE這兩個符号的位址是否相等,_TEXT_BASE這個符号取值為TEXT_BASE,它的值為0x33F80000。其實就是判斷u-boot是否已經在SDRAM中,如果u-boot已經在SDRAM中,那麼也就不必拷貝了,直接跳到堆棧設定。如果u-boot沒有在SDRAM中,那麼就将u-boot拷貝到SDRAM中,CPU是可以從nor flash中取指執行的,但是在SDRAM中執行速度更快,是以将代碼拷貝到SDRAM中。那麼到底這裡是怎樣實作代碼拷貝的呢??由上面知道,目前位址也就是u-boot代碼段起始位址已經存放在寄存器r0了,然後計算代碼段的結束位址,通過(_bss_start - _armboot_start)相減得到,結束位址放在寄存器r2中,然後使用多寄存器加載指令将代碼複制到SDRAM中,代碼放在SDRAM中的什麼地方的呢,就是放在r1指向的位址,也就是TEXT_BASE這個地方的,也就是0x33F80000這個地方。
下面還是很有比較說明一下ldmia和stmia這兩個多寄存器加載指令,一開始很是不了解啊
批量資料加載/存儲指令ARM微處理器所支援批量資料加載/存儲指令可以一次在一片連續的存儲器單元和多個寄存器之間傳送資料,批量加載指令用于将一片連續的存儲器中的資料傳送到多個寄存器,批量資料存儲指令則完成相反的操作。常用的加載存儲指令如下:
LDM(或STM)指令
LDM(或STM)指令的格式為:
LDM(或STM){條件}{類型} 基址寄存器{!},寄存器清單{∧}
LDM(或STM)指令用于從由基址寄存器所訓示的一片連續存儲器到寄存器清單所訓示的多個寄存器之間傳送資料,該指令的常見用途是将多個寄存器的内容入棧或出棧。
其中,{類型}為以下幾種情況:
IA 每次傳送後位址加1;
IB 每次傳送前位址加1;
DA 每次傳送後位址減1;
DB 每次傳送前位址減1;
FD 滿遞減堆棧;
ED 空遞減堆棧;
FA 滿遞增堆棧;
EA 空遞增堆棧;
是以上面ldmia的意思就是每次傳送位址加一,并且是從r0開始的一片連續的存儲器的資料拷貝到後面寄存器清單所訓示的寄存器中,也就是r3-r10這八個寄存器中,是以就是把r0---r0+4*7這段記憶體中的資料依次拷貝到r3-r10中
另外上面stmia的意思也是每次傳送位址加一,并且是把後面寄存器清單所訓示的寄存器,也就是r3-r10這八個寄存器中的資料拷貝到r1開始的一片連續的存儲器中,是以就是把r3-r10中的資料依次拷貝到r1---r1+4*7這段記憶體中
是以r3-r10就是中轉站,另外中間還有一個“!”呢,是以接着分析
{!}為可選字尾,若選用該字尾,則當資料傳送完畢之後,将最後的位址寫入基址寄存器,否則基址寄存器的内容不改變。是以這裡ldmia資料傳送完成之後相當于會将做下面這個動作r0=r0+4*7,是以這裡才可以實作循環,原因就在這裡了
基址寄存器不允許為R15,寄存器清單可以為R0~R15的任意組合。
{∧}為可選字尾,當指令為LDM且寄存器清單中包含R15,選用該字尾時表示:除了正常的資料傳送之外,還将SPSR複制到CPSR。同時,該字尾還表示傳入或傳出的是使用者模式下的寄存器,而不是目前模式下的寄存器。
第六步:
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
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
ldr pc, _start_armboot
_start_armboot: .word start_armboot
前三條語句的含義是,把位址為_TEXT_BASE的記憶體中的内容給r0,即,而檢視前面的相關部分的代碼,即:
_TEXT_BASE:
.word TEXT_BASE
得知,位址為_TEXT_BASE的記憶體中的内容,就是
TEXT_BASE = 0x33F80000
是以,此處即:r0= TEXT_BASE= 0x33F80000
而關于sub指令:SUB : 減法
SUB{條件}{S} <dest>, <op 1>, <op 2>
相當于算法:dest = op_1 - op_2
SUB 用操作數 one 減去操作數 two,把結果放置到目的寄存器中。操作數 1 是一個寄存器,操作數 2 可以是一個寄存器,被移位的寄存器,或一個立即值:
SUB R0, R1, R2 ; R0 = R1 - R2
SUB R0, R1, #256 ; R0 = R1 - 256
SUB R0, R2, R3,LSL#1 ; R0 = R2 - (R3 << 1)
減法可以在有符号和無符号數上進行。
是以對應含義為:
r0 = r0 - #CONFIG_SYS_MALLOC_LEN
r0 = r0 - #CONFIG_SYS_GBL_DATA_SIZE
其中,對應的兩個宏的值在:u-boot-2009.08/include/configs/smdk2410.h
/*
* Size of malloc() pool
*/
#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + 128*1024)
#define CONFIG_SYS_GBL_DATA_SIZE 128 /* size in bytes reserved for initial data */
#define CONFIG_ENV_SIZE 0x10000 /* Total Size of Environment Sector */
是以,此三行的含義就是算出r0的值:
r0= (r0 - #CONFIG_SYS_MALLOC_LEN) - #CONFIG_SYS_GBL_DATA_SIZE= r0 - 0x20000-0x100000 – 128
如果定義了CONFIG_USE_IRQ,即如果使用中斷的話,那麼再把r0的值減去IRQ和FIQ的堆棧的值,而對應的宏的值也是在剛剛的路徑下
#define CONFIG_STACKSIZE (128*1024) /* regular stack */
#ifdef CONFIG_USE_IRQ
#define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */
#define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */
#endif
最後,再減去終止異常所用到的堆棧大小,即12個位元組。這裡計算方法我就不再說了
下面的代碼将從_bss_start到_bss_end的區域清空,_bss_start的位置,是bss的start開始位置,同時也是text+rodata+data的結束位置,即代碼段,隻讀資料和已初始化的可寫的資料的最末尾的位置,實作方法說明如下:
首先将_bss_start位址資訊儲存到r0,_bss_end位址資訊儲存到r1,指派r2為0,第一次進入clbss_l這個循環時,将r0位址的寄存器的值指派為0,然後r0的位址增加4個位元組,然後與r1儲存的位址比較,如果不不相等則傳回到clbss_l循環标示處繼續循環,知道這個區間内所有内容都别清空,結束循環,接着往下運作。
上面代碼的最後兩條語句,跳入第二階段的 C 語言代碼入口_start_armboot (已經被重定向到記憶體) 這裡c語言的代碼分析單獨做一部分進行講解
第七步:
從這裡開始異常中斷處理程式,剛開始看到就蒙了,不知道從哪裡下手了,其實此處很簡單,隻是一些宏定義而已。
.macro和後面的.endm相對應,是以,此處就相當于一個無參數的宏bad_save_user_regs,也就相當于一個函數了。是以我們一個個去分析這幾個函數就可以了
.macro bad_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE)
sub r2, r2, #(CONFIG_SYS_MALLOC_LEN)
sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
ldmia r2, {r2 - r3} @ get pc, cpsr
add r0, sp, #S_FRAME_SIZE @ restore sp_SVC
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp
.endm
這個函數bad_save_user_regs,先将sp-72再儲存到sp中,然後将r0-r12這13個寄存器中的值批量拷貝到sp處,是以S_FRAME_SIZE一定要大于13*4=52,這個就就是為了提供足夠的空間來儲存這些寄存器的值,然後将_armboot_start的位址儲存到r2中,讓後r2-128*1024 - 256*1024-(128 + 8) = 0x33C9FF78,這裡我沒有去計算,直接拿來用的,然後分别将位址為r2和r2+4的内容,即位址為0x33C9FF78和0x33C9FF7C中的内容,load載入給r2和r3寄存器,然後将r0的值再指向sp最初的位置,這裡要知道定義的S_SP=52,那麼sp+52,剛好就在剛剛空間指派之後的位址,剛剛提供了72個位元組,用掉52個,還有20個,r5就指向緊跟那第52個位元組的後面,将lr傳給r1,讓後把r0-r3這四個寄存器中的值保持到熱r5所指向位址的連續四個寄存器中,其實儲存的這四個寄存器分别是sp_SVC,lr_SVC,pc,spsr,最後将sp的值再付給r0,結束這個函數,我一直不是很明白,這個函數是做什麼用的,實作什麼樣的功能??
接着看下一個函數:
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
add r7, sp, #S_PC
stmdb r7, {sp, lr}^ @ Calling SP, LR
str lr, [r7, #0] @ Save calling PC
mrs r6, spsr
str r6, [r7, #4] @ Save CPSR
str r0, [r7, #8] @ Save OLD_R0
mov r0, sp
.endm
分析一下這段代碼:方法其實跟上面的方法很是類似,首先sp自減S_FRAME_SIZE,首先先提供72位元組的空間用于之後存儲資料,然後将r0-r12這13個寄存器的資料拷貝到sp指向的新位置,然後sp自加60,移動到剛剛拷貝資料的末尾,除了剛剛使用了52=13*4個位元組,中間也就是說還有8個位元組,放心,這個8個位元組沒有被浪費掉,這個stmdb sp, {sp, lr}^,就是r7的位址自減1後再把sp的資料先存放下來,然後再次減一,再把lr的資料存放到這個位址,是以sp存放到相當于52的位置,lr存放到相當于56的位置.stm這個指令不再多說了,上面已經說過他的用法了,
待續。。。