1. 程式人生 > >OK6410開發板學習之一步一步實現精簡BootLoader(BL1部分)

OK6410開發板學習之一步一步實現精簡BootLoader(BL1部分)

眾所周知,ok6410開發板是一塊基於s3c6410晶片的開發板,板載資源豐富。s3c6410是三星電子生產的基於arm11核心的晶片。本文旨在總結一下bootloader操作步驟,用於以後複習、查詢。通過分析bootloader行業老大哥uboot程式碼,總結出要實現OK6410開發板的啟動引導,只要實現如下的操作即可:

1. 設定異常向量表;

2. 設定處理器模式為svc模式;

3. 外設基地址初始化;

4. 關閉看門狗;

5. 關閉所有中斷;

6. 關閉MMU和cache;

7. 初始化系統時鐘;

8. 記憶體初始化;

9. 複製記憶體;

10.棧初始化;

11.清除bss段;

12.跳入c函式(點亮led)。

下面一步一步的介紹如何實現它們。

1. 設定異常向量表:

想要設定arm的異常向量表,首先得知道其有那些異常,檢視arm架構參考手冊Programmers’ Model 章節的A2.6 Exceptions我們知道其共有7中異常,如下圖:


設定異常向量表,按我個人的理解其實就是將異常發生時要處理的地址指向PC即可,所以通過如下程式碼來實現:

.text
.global _start
_start:
	b reset
	ldr pc, _undifined_instruction                 @ 將pc指標指向地址_undefined_instruction處,即當該異常發生時處理器跳到標號undifined_instruction處執行
	ldr pc, _software_interrupt
	ldr pc, _prefetch_abort
	ldr pc, _data_abort
	ldr pc, _not_used
	ldr pc, _irq
	ldr pc, _fiq				
				
_undifined_instruction:  .word undifined_instruction      @ .word 偽指令,用於取地址,該段表示_undefined_instruction是一個指向標號
                                                        @ undifined_instruction處的32位地址
_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 resetundifined_instruction:nopsoftware_interrupt:nopprefetch_abort:nopdata_abort:nopnot_used:nopirq:nopfiq:nop

開頭的.text告訴編譯器下面的內容是程式碼段,.global _start告訴編譯器_start是全域性的。細心的人可能會發現,明明只有7種異常,為什麼會有8個異常向量表,多出來的not_used是啥?從字面理解,它是一個未使用的異常,細心閱讀arm架構參考手冊就會發現有下面這麼一段話


地址0x00000014處向量是一個未使用的,預留的向量表,從異常處理模式圖也能發現0x00000010和0x00000018之間不是連續的,如下圖:


為了異常的連續性,或者說是異常發生能夠正確找到異常向量表,我們需要補全這個鄉里表。

分析程式碼可知,首先跳轉到reset處執行程式碼,那麼在reset處都幹啥呢了,我們可以找到reset處看一下,程式碼如下:

reset:
	bl set_svc              @ 設定cpu為svc模式
	bl set_peri_port        @ 外設基地址初始化
	bl disable_watchdog     @ 關閉看門狗
	bl disable_interrupt    @ 關閉所有中斷
	bl disable_mmu          @ 關閉mmu和cache
	bl init_clock           @ 時鐘初始化
	bl mem_init             @ 記憶體初始化
	bl copy_to_ram          @ 拷貝bl到記憶體
	bl init_stack           @ 棧初始化
	bl clean_bss            @ 初始化bss段
	ldr pc, =main           @ 進入c語言程式設計環境,main函式

原來後面的操作都是從reset處一次執行的,下面一步一步的介紹接下來的操作。

2.設定處理器模式為svc模式:bl set_svc 

通過檢視arm架構參考手冊“A2.2 Processor modes ”章節可以知道,arm共有7種工作模式,如下:


要想cpu工作在svc模式下,首先我們來了解一下arm的程式狀態暫存器CPSR,它可以用來改變工作模式等操作,CPSR格式如下:


它的低5位就是模式選擇,通過檢視模式為設定,可知只要將cpsr的第五位設定成0b10011就可以了,如下:


理論上,到此就可以了,但是,我們發現cpsr的I、F位是關於中斷的,這裡順便也將其配置了,關閉IRQ和FIQ如下圖:


程式碼如下:

	@ set the cpu to SVC32 mode
set_svc:
	mrs	r0,cpsr             @ 將程式狀態暫存器取出儲存在通用暫存器r0中
	bic	r0,r0,#0x1f         @ 將從狀態暫存器取出的值(r0)低5位清零後存入r0中
	orr	r0,r0,#0xd3         @ 將r0中的值的第0 1 4 6 7位置1後存入r0中,關閉中斷與快速中斷,進入svc模式
	msr	cpsr,r0             @ 將r0的中的值寫入程式狀態暫存器
	mov pc, lr                  @ 返回呼叫的地方下一個地址處繼續執行

2.外設基地址初始化:bl set_peri_port 

這一步必須要做,而且必須在這一步做,否則cpu無法正常使用外設。要想初始化s3c6410的外設基地址,首先得知道外設基地址是從0x7000000開始的,同時還得了解一下arm11的協處理器p15的內容,參考arm11核心手冊,找到系統協處理器章節,找到關於Peripheral Port Memory Remap Register的協處理器命令,如下:


跳到page 3-130章節處,檢視其詳細內容。該暫存器格式如下:


[31:12]:表示基地址,這裡我們需要將其設成0x70000000,

[11:5]:不要考慮,保留預設值即可,

[4:0]:外設基地址的大小,s3c6410外設需要256M大小的記憶體。

檢視Peripheral Port Memory Remap Register bit functions ,如下圖:


通過上圖可知只要往p15的c15中寫入0x70000013即可。程式碼如下:

  @ 初始化外設地址,必須做這一步,要不外設無法使用。
set_peri_port:
	ldr r0, =0x70000000     @ 將基地址0x70000000存入暫存器r0中
	orr r0, r0, #0x13       @ 將暫存器r0的值設為0x70000013
	mcr p15,0,r0,c15,c2,4	  @ 將暫存器r0的值傳送到協處理器p15的c15暫存器中,參看arm11核心手冊:c15, Peripheral Port Memory Remap Register
	mov pc, lr              @ 返回呼叫處繼續往下執行

3.關閉看門狗:bl disable_watchdog

要想關閉看門狗,需要找到看門狗控制暫存器地址,通過檢視s3c6410手冊Watch Dog章節可以找到,如下:


看門狗控制暫存器位操作說明如下:


紅框中的就是和關閉看門狗有關的位,只要將這幾個位設定為0,即將看門狗控制暫存器設為0。程式碼如下:

  @ Disable Watchdog 參看s3c6410手冊:Watch Dog章節
#define pWTCON 0x7e004000  @ addr of watchdog timer control register (WTCON)
disable_watchdog:
	ldr r0, =pWTCON         @ 將看門狗控制暫存器地址寫入暫存器r0中
	mov r1, #0x0            @ 將0x0儲存在暫存器r1中
	str r1, [r0]            @ 將0x0傳送到看門狗控制暫存器中
	mov pc, lr              @ 返回呼叫處繼續往下執行

4.關閉所有中斷:bl disable_interrupt 

可能有人會問,在前面設定cpu為svc工作模式時不是已經關閉了IRQ和FIQ嗎,這裡還需做啥操作呢?之前的只是關閉了中斷,但是並沒有清除中斷使能位,這裡就是進行這樣的操作,清除所有中斷的使能位。s3c6410共有兩個中斷控制器VIC0、VIC1。所以需要分別找到這兩個INTERRUPT ENABLE CLEAR 暫存器,並且對其進行配置,如下圖:


通過上圖,可知只要將其設定為0 即可,程式碼如下:

	@ Disable all interrupts (VIC0 and VIC1),參考s3c6410手冊:Vectored Interrupt Controller章節
disable_interrupt:
	mvn r1,#0x0
	ldr r0,=0x71200014      @ VIC0INTENCLEAR地址
	str r1,[r0]             @ 將VIC0INTENCLEAR設為0

	ldr r0,=0x71300014      @ VIC1INTENCLEAR地址
	str r1,[r0]             @ 將VIC1INTENCLEAR設為0	
	mov pc, lr              @ 返回呼叫處繼續往下執行

5.關閉mmu和cache

MMU是MemoryManagement Unit的縮寫,中文名是記憶體管理單元,它是中央處理器(CPU)中用來管理虛擬儲存器、物理儲存器的控制線路,同時也負責虛擬地址對映為實體地址,以及提供硬體機制的記憶體訪問授權,多使用者多程序作業系統。 

Cache:高速緩衝儲存器,是位於CPU和主儲存器DRAM(DynamicRandomAccessMemory)之間,規模較小,但速度很高的儲存器,通常由SRAM(StaticRandomAccessMemory靜態儲存器)組成。它是位於CPU與記憶體間的一種容量較小但速度很高的儲存器。CPU的速度遠高於記憶體,當CPU直接從記憶體中存取資料時要等待一定時間週期,而Cache則可以儲存CPU剛用過或迴圈使用的一部分資料,如果CPU需要再次使用該部分資料時可從Cache中直接呼叫,這樣就避免了重複存取資料,減少了CPU的等待時間,因而提高了系統的效率。3sc6410含有兩個cache,分別是資料cache和指令cache。

這麼好的東西為什麼要關閉呢?對呀,為什麼呀?那是因為相對於s3c6410的啟動引導檔案,並不需要使用到他們,開啟了使用不當反而會導致異常產生。那麼如何來關閉他們呢?這又的操作協處理器p15了。檢視arm架構參考手冊協處理器相應章節,找到cache相關協處理器命令,如下:


協處理器的命令都是死命令,想幹啥找到了直接用就可以,是不是很爽呢?這裡只是讓cache失效了,但是cache具體使能位和mmu使能位還沒有設定,找到協處理器的控制暫存器(c1),其中有這麼一句話,如下紅框中:


協處理器p15的控制暫存器c1格式如下:


詳細說明如下:


因此我們只要設定[2:0]位位0即可,順便關閉位元組對齊檢查功能,程式碼如下:

	@ 關閉MMU和cache	
disable_mmu:
	mcr p15,0,r0,c7,c7,0    @ 使I/D cache全部失效 參考arm11核心手冊:c7, Cache operations
	mrc p15,0,r0,c1,c0,0    @ 取出協處理器p15的控制暫存器(c1)內容,存放在r0中 參考arm11核心手冊:c1, Control Register
	bic r0, r0, #0x00000007 @ 將r0的[2:0]設為0後存入r0
	mcr p15,0,r0,c1,c0,0    @ 將r0的值傳送到協處理器p15的控制暫存器(c1)中,完成mmu和cache配置
	mov pc, lr              @ 返回呼叫處繼續往下執行

6.時鐘初始化:bl init_clock 

S3C6410可以使用外部晶振( XXTIpll (預設為12MHZ)和外部時鐘( XEXTCLK )兩種方式輸入時鐘訊號。它由跳線OM[0]決定。 S3C6410 預設的工作主頻為12MHz(晶振頻率), 3sc6410的系統時鐘邏輯控制器可以產生系統必須的三種時鐘訊號,ARMCLK for CPU, HCLK for AXI/AHB-bus peripherals, and PCLK for the APB bus peripherals。s3c6410共有三種PLL,其中APLL用於產生ARMCLK,MPLL用於產生HCLK和PCLK,EPLL用於產生SCLK,音訊相關的時鐘,如下圖:


框圖下面有一段話,說明CLK_SRC暫存器是需要設定的,用來選擇時鐘輸出源,如下:


下面的框圖說明了系統時鐘配置需要設定那些暫存器和如何設定引數,如下:





除了上面提到的暫存器需要配置,當然也少不了APLL控制暫存器和MPLL控制暫存器了,下面介紹一下這些暫存器。

從上圖Power-on reset sequence中看到有一個Lock time,在這段時間(Lock Time)內,FCLK停振,CPU停止工作。Lock Time的長短由暫存器LOCKTIME設定 ,Lock Time之後,MPLL輸出正常,CPU工作在新的FCLK下。 所以這個引數需要設定,但是由於這個時間很短(只有300us),所以不設定也不影響使用,如下圖:


APLL_CON和MPLL_CON說明如下:


從上圖可知,只需設定SDIV、PDIV、MDIV、ENABLE位就可以了,參考設定值如下:


CLK_DIV0暫存器說明如下:


用於設定ARMCLK、PCLK、HCLK等。

CLK_SRC說明如下:


這裡只需要設定[2:0]就可以。

OTHERS暫存器說明如下:


這裡我們只需設定[7:6]即可。程式碼如下:

	@ 初始化時鐘 參考s3c6410手冊系統控制章節、時鐘相關章節
#define CLK_DIV0 0x7e00f020    @ 時鐘分頻暫存器0地址
#define OTHERS 0x7e00f900      @ 其他控制暫存器地址
#define MPLL_CON 0x7e00f010    @ MPLL控制暫存器地址
#define APLL_CON 0x7e00f00c    @ APLL控制暫存器地址
#define CLK_SRC 0x7e00f01c     @ 時鐘源選擇暫存器地址
#define DIV_VAL ((0x0<<0)|(0x1<<9)|(0x1<<8)|(0x3<<12))  @ 分頻設定值  2b0000_0000_0000_0000_0011_0011_0000_0000 參考uboot設定
#define PLL_VAL ((1<<31)|(266<<16)|(3<<8)|(1<<0))       @ PLL設定值   2b1000_0001_0000_1010_0000_0011_0000_0001  參考uboot設定
init_clock:
	@ 設定時鐘分頻係數
	ldr r0, =CLK_DIV0      @ 將時鐘分頻暫存器0的地址存入暫存器r0中             
	ldr r1, =DIV_VAL       @ 將需要配置到時鐘分頻暫存器0的數值存入暫存器r1中
	str r1, [r0]           @ 將r1的數值傳送到時鐘分頻暫存器0中
	@ 設定cpu為非同步模式,將OTHERS暫存器內容設定為0xc0
	ldr r0, =OTHERS        @ 將OTHERS寄存地址存入暫存器r0中
	ldr r1, [r0]           @ 將其他控制暫存器中的資料讀出,存入r1中
	bic r1,r1,#0xc0        @ 將r1中的數值第6 7 bit位清0後再存入r1中
	str r1, [r0]           @ 將r1中的數值傳送到OTHERS暫存器中,Asynchronous mode 和 MOUTmpll
	@ 設定APLL時鐘頻率 533MHz
	ldr r0, =APLL_CON      @ 將APLL控制暫存器地址寫入r0中
	ldr r1, =PLL_VAL       @ 將需要配置的PLL數值存入暫存器r1中
	str r1, [r0]           @ 將r1中的數值寫入APLl控制暫存器中
	@ 設定MPLL時鐘頻率 533MHz
	ldr r0, =MPLL_CON      @ 將MPLL控制暫存器地址寫入r0中
	ldr r1, =PLL_VAL       @ 將需要配置的PLL數值存入暫存器r1中
	str r1, [r0]           @ 將r1中的數值寫入MPLL控制暫存器中
	@ 設定時鐘源選擇控制暫存器,使用APLL和MPLL輸出時鐘。
	ldr r0, =CLK_SRC       @ 將時鐘源選擇暫存器地址寫入r0中
	mov r1, #0x3           @ 將0x03寫入暫存器r1中
	str r1, [r0]	         @ 將r1中的資料寫入時鐘源選擇控制暫存器中
	mov pc, lr	           @ 返回呼叫處繼續往下執行
7.記憶體初始化:bl mem_init

記憶體由於具備訪問速度快,訪問方式簡單等優點,成為了PC或者是嵌入式硬體平臺上不可或缺的元件。在開始學習如何使用記憶體之前,非常有必要先了解一下記憶體的分類: 

1.DRAM它的基本原件是小電容,電容可以在兩個極板上保留電荷,但是需要定期的充電(重新整理),否則資料會丟失。缺點:由於要定期重新整理儲存介質,存取速度較慢。

2.SRAM它是一種具有靜止存取功能的記憶體,不需要定期重新整理電路就能儲存它內部儲存的資料。其優點:存取速度快; 但是缺點是:功耗大,成本高。常用作儲存容量不高,但存取速度快的場合,比如steppingstone。

在嵌入式硬體體系中,除了CPU內部的墊腳石採用SRAM外,板載記憶體一般會採用DRAM,而DRAM又可以分為SDRAM,DDR,DDR2等。

SDRAM(Synchronous Dynamic Random AccessMemory):同步動態隨機儲存器.
同步: 記憶體工作需要有同步時鐘,內部的命令的傳送與資料的傳輸都以該時鐘為基準。
動態:儲存陣列需要不斷的重新整理來保證資料不丟失。

隨機:是指資料不是線性依次儲存,而是自由指定地址進行資料讀寫。

DDR (Double Data Rate SDRAM),即“雙倍速率同步動態隨機儲存器”。與早期的SDRAM相比,DDR 除了可以在時鐘脈衝的上升沿傳輸資料,還可以在下降沿傳輸訊號,這意味著在相同的工作頻率下,DDR 的理論傳輸速率為SDRAM的兩倍。DDR2 則在DDR 的基礎上再次進行了改進,使得資料傳輸速率在DDR 的基礎上再次翻倍。6410開發板通常採用DDR記憶體 。

記憶體的內部如同表格,資料就存放在每個單元格中。資料讀寫時,先指定行號(行地址),再指定列號(列地址) ,我們就可以準確地找到所需要的單元格。而這張表格的稱為:Logical Bank(L-Bank)。 如下圖:


由於技術、成本等原因,一塊記憶體不可能把所有的單元格都做到一個L-Bank,現在記憶體內部基本都會分割成4
L-Bank S3C6410處理器擁32位地址匯流排,其定址空間為4GB。其中高2GB為保留區,低2GB區域又可劃分

為兩部分:主儲存區和外設區。如下左圖:

             

主儲存去又可以分為:Boot映象區、內部儲存區、靜態儲存區、保留區、動態儲存區,如上右圖 。

Boot映象區:這個區域的作用正如它的名字所述,是用來啟動ARM系統的。但是這個區域並沒有固定的儲存介質與之對應。而是通過修改啟動選項,把不同的啟動介質對映到該區域。比如說選擇了IROM 啟動方式後,就把IROM對映到該區域。

內部儲存區:這個區域對應著內部的記憶體地址,iROMSRAM都是分佈在這個區間。0x08000000~0x0bffffff對應著內部ROM,但是IROM實際只有32KB,選擇從IROM啟動的時候,首先執行就是這裡面的程式BL0,這部分程式碼由三星固化。0x0c000000~0x0fffffff對應內部SRAM,實際就是8KBSteppingstone

靜態儲存區:這個區域用於訪問掛在外部總線上的裝置,比如說NOR flashoneNand等。這個區域被分割為6bank,每個bank128MB,資料寬度最大支援16bit,每個bank由片選Xm0CS[0]~Xm0CS[5] 選中。

動態儲存區:該區域從0x50000000~0x6fffffff,又分為2個區間,分別佔256MB,可以片選Xm1CS[0]~Xm1CS[1]來進行著2個區間的選擇。我們6410開發板上256MBDDR記憶體就安排在這個區域,這也就是為什麼6410的記憶體地址是從0x50000000開始的原因。

OK6410記憶體晶片硬體連線如下:


從上圖可以知道,它是通過兩片記憶體晶片組合成32位資料的,分別使用s3c6410的前16位和後16位資料線。

檢視s3c6410手冊DRAM Controller 章節,可以發現有關於記憶體初始化操作步驟的說明,如下:


所以就可以參考上面的操作步驟來實現記憶體的初始化。

第1步:配置DRAM CONTROLLER COMMAND REGISTER為2b011及為0x4,讓其進入配置模式,暫存器說明如下:


第2步:配置記憶體時序暫存器、片選配置暫存器、使用者配置暫存器;記憶體時序暫存器比較多,這裡不再一一列舉,大家可以檢視s3c6410手冊,片選配置暫存器說明如下:


使用者配置暫存器說明如下:


第3步:等待200us,讓SDRAM電源和時鐘穩定,但是,當cpu啟動時電源和時鐘已經穩定。呵呵,就是不需要了唄,繞這麼大一圈,可能是三星公司有病吧;

第4步:執行記憶體初始化,說了半天才記憶體初始化,之前幹啥啦?不要吵吵,之前是sdram的初始化,因為OK6410使用的是mobile DDR sdram,所以參考5.4.2的ddr/mobile DDR sdram初始化步驟,根據說明,其實就是重複設定P1DIRECTCMD暫存器(直接命令暫存器)的Memory command位,即[19:18]位。分別設定為2b11  2b00 2b01 2b01 2b10 2b10使其執行中nop、Prechargeall 、Autorefresh 、MRS、EMRS。直接命令暫存器說明如下:


第5步:設定P1MEMCCMD暫存器(記憶體控制命令暫存器)的Memc_cmd位位3b000,讓DRAM控制器進入準備好模式,記憶體控制命令暫存器說明如下:


第6步:檢測DRAM控制器是否進入準備模式,詳見程式碼,如下:

.text
.global mem_init
mem_init:    
    ldr r0, =0x7e00f120  @配置記憶體系統子程式暫存器地址 
    mov r1, #0x8         @將0x8存入r1暫存器
    str r1, [r0]         @將r1暫存器裡的資料寫入配置記憶體系統子程式暫存器中

    ldr r0, =0x7e001004  @記憶體控制命令暫存器    
    mov r1, #0x4         @根據手冊得知需要先進入配置模式
    str r1, [r0]         @將暫存器r1內容寫入記憶體控制命令暫存器中

    ldr r0, =0x7e001010  @重新整理暫存器地址
    ldr r1, =( ( 7800 / ( 1000000000/133000000 ) + 1 ) )      @設定重新整理時間
    str r1, [r0]         @將暫存器r1內容寫入重新整理暫存器暫存器中

    ldr r0, =0x7e001014  @CAS latency暫存器
    mov r1, #(3 << 1)    @r1=0x6
    str r1, [r0]         @將暫存器r1內容(0x06)寫入CAS latency暫存器中

    ldr r0, =0x7e001018  @t_DQSS暫存器
    mov r1, #0x1         @r1=0x1
    str r1, [r0]         @將暫存器r1內容(0x01)寫入t_DQSS暫存器中

    ldr r0, =0x7e00101c  @T_MRD暫存器
    mov r1, #0x2         @r1=0x2
    str r1, [r0]         @將暫存器r1內容(0x02)寫入T_MRD暫存器中

    ldr r0, =0x7e001020  @t_RAS暫存器
    ldr r1, =( ( 45 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=6.95
    str r1, [r0]         @將暫存器r1內容(6.95)寫入t_RAS暫存器中

    ldr r0, =0x7e001024  @t_RC暫存器
    ldr r1, =( ( 68 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=10.044
    str r1, [r0]         @將暫存器r1內容(10.044)寫入t_RC暫存器中

    ldr r0, =0x7e001028  @t_RCD暫存器
    ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=3.99
    str r1, [r0]         @將暫存器r1內容(3.99)寫入t_RCD暫存器中

    ldr r0, =0x7e00102c  @t_RFC暫存器
    ldr r1, =( ( 80 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=11.64   
    str r1, [r0]         @將暫存器r1內容(11.64)寫入t_RFC暫存器中

    ldr r0, =0x7e001030  @t_RP暫存器
    ldr r1, =( ( 23 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=3.99
    str r1, [r0]         @將暫存器r1內容(3.99)寫入t_RP暫存器中

    ldr r0, =0x7e001034  @t_rrd暫存器
    ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=2.995
    str r1, [r0]         @將暫存器r1內容(2.995)寫入t_rrd暫存器中

    ldr r0, =0x7e001038  @t_wr暫存器
    ldr r1, =( ( 15 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=2.995
 @  ldr r2, [r0]
    str r1, [r0]         @將暫存器r1內容(2.995)寫入T_MRD暫存器中

    ldr r0, =0x7e00103c  @t_wtr暫存器
    mov r1, #0x07        @r1=0x07
    str r1, [r0]         @將暫存器r1內容(0x07)寫入T_MRD暫存器中

    ldr r0, =0x7e001040  @t_xp暫存器
    mov r1, #0x02        @r1=0x02
    str r1, [r0]         @將暫存器r1內容(0x02)寫入t_xp暫存器中

    ldr r0, =0x7e001044  @t_xsr暫存器
    ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=16.69
    str r1, [r0]         @將暫存器r1內容(16.69)寫入t_xsr暫存器中

    ldr r0, =0x7e001048   @t_esr暫存器
    ldr r1, =( ( 120 / ( 1000000000 / 133000000 ) + 1 ) )   @r1=16.69
    str r1, [r0]         @將暫存器r1內容(16.69)寫入t_esr暫存器中

    ldr r0, =0x7e00100c  @記憶體控制配置暫存器 P1MEMCFC
    ldr r1, =0x00010012  @配置控制器  r1=2b0000_0000_0000_0001_0000_0000_0001_0010
    str r1, [r0]         @將暫存器r1內容(0x10012)寫入P1MEMCFC暫存器中

    ldr r0, =0x7e00104c  @32位DRAM配置控制暫存器
    ldr r1, =0x0b45      @r1=2b0000_0000_0000_0000_0000_1011_0100_0101  選擇Mobile DDR SDRAM
    str r1, [r0]         @將暫存器r1內容(0x0b45)寫入T暫存器中

    ldr r0, =0x7e001200  @片選暫存器
    ldr r1, =0x150f8     @r1=0x150f8  r1=2b0000_0000_0000_0001_0101_0000_1111_1000   Bank-Row-Column organization
    str r1, [r0]         @將暫存器r1內容(0x02)寫入T_MRD暫存器中

    ldr r0, =0x7e001304  @使用者配置暫存器
    mov r1, #0x0         @r1=0x0
    str r1, [r0]         @將暫存器r1內容(0x00)寫入使用者配置暫存器中

    ldr r0, =0x7e001008  @直接命令暫存器
    ldr r1, =0x000c0000  @r1=2b0000_0000_0000_1100_0000_0000_0000_0000   makes DRAM Controller issue ‘NOP’ memory
    str r1, [r0]         @將暫存器r1內容(0xc0000)寫入直接命令暫存器中

    ldr r1, =0x00000000  @r1=0   makes DRAM Controller issue‘PrechargeAll’ memory command. 
    str r1, [r0]         @將暫存器r1內容(0x0)寫入直接命令暫存器中

    ldr r1, =0x00040000  @r1=2b0000_0000_0000_0100_0000_0000_0000_0000  makes DRAM Controller issue ‘Autorefresh’ memory command.
    str r1, [r0]         @將暫存器r1內容(0x40000)寫入直接命令暫存器中

    ldr r1, =0x000a0000  @r1=2b0000_0000_0000_1010_0000_0000_0000_0000  makes DRAM Controller issue ‘MRS’ memory command. 
    str r1, [r0]         @將暫存器r1內容(0xa0000)寫入直接命令暫存器中

    ldr r1, =0x00080032  @r1=2b0000_0000_0000_1000_0000_0000_0011_0010
    str r1, [r0]         @將暫存器r1內容(0x80032)寫入直接命令暫存器中  makes DRAM Controller issue ‘EMRS’ memory command.

    ldr r0, =0x7e001004  @記憶體控制命令暫存器
    mov r1, #0x0         @根據手冊得知需要進入ready模式
    str r1, [r0]         @將暫存器r1內容寫入記憶體控制命令暫存器中
@檢查記憶體初始化是否準備好
check_dmc1_ready:    
    ldr r0, =0x7e001000  @DRAM控制狀態暫存器
    ldr r1, [r0]         @將DRAM控制狀態暫存器中內容寫入暫存器r1中
    mov r2, #0x3         @將0x03寫入暫存器r2中
    and r1, r1, r2       @將r1和r2與運算後存在r1中,即只保持讀出的值的[1:0]位
    cmp r1, #0x1         @將讀出的值與0x01比較
    bne check_dmc1_ready @若不相等,跳轉到標號check_dmc1_ready處繼續執行,檢查記憶體狀態
    nop
    mov pc, lr           @返回呼叫處繼續往下執行

8.拷貝bl到記憶體中:bl copy_to_arm

要想拷貝bl到記憶體中,我們首先的看一下s3c6410的啟動流程圖,如下:


解釋如下:


整個流程如下:首先是執行晶片固化在iROM中的bl0程式碼,該段程式碼將Booting Device中的bl1搬移到Stepping stone中,但是Stepping stone只有8k,一個完整的程式碼程式碼量會超過8K,那樣就沒法運行了,所以bl1這段程式碼其中就有一個功能將大部分程式碼搬移到記憶體中去執行,這裡要實現的就是這個功能,只不過是將從Booting device中bl1開始的的4K程式碼移過去。程式碼如下:

	@ 程式碼搬移,拷貝bl到記憶體中
copy_to_ram:
	ldr r0, =0x0c000000    @ Stepping Stone (Boot Loader)基地址,即要搬移的源地址存入r0   起點
	ldr r1, =0x50008000    @ 記憶體地址,要搬移的目標地址存入r1   終點
	add r3, r0, #1024*4    @ 將r0處的地址加4k大小存入r3,要搬移的記憶體Size 4k, 大小

copy_loop:               @ 等待搬移完成
	ldr r2, [r0], #4       @ 將地址0x0c000000增加4後(0x0c000004)地址資料存入r2
	str r2, [r1], #4       @ 將r2的資料傳送到r1+4地址處(0x500080004)
	cmp r0, r3             @ 比較r0和r3處地址
	bne copy_loop	       @ 若不相等,跳到標號copy_loop處繼續執行
	mov pc, lr	       @ 返回呼叫處繼續往下執行 

9.棧初始化:bl init_stack 

是一種具有後進先出性質的資料組織方式,也就是說後存放的先取出,先存放的後取出。棧底是第一個進棧的資料所處的位置,棧頂是最後一個進棧的資料所處的位置 。如下左圖:

                  

根據SP指標指向的位置,棧可以分為滿棧空棧

1. 滿棧:當堆疊指標SP總是指向最後壓入堆疊的資料

2. 空棧:當堆疊指標SP總是指向下一個將要放入資料的空位置。ARM採用滿棧! 如上圖中:

根據SP指標移動的方向,棧可以分為升棧降棧
1. 升棧:隨著資料的入棧,SP指標從低地址->高地址移動

2. 降棧:隨著資料的入棧,SP指標從高地址->低地址移動。ARM採用降棧! 如下圖:


棧幀(stack frame)就是一個函式所使用的那部分棧,所有函式的棧幀串起來就組成了一個完整的棧。棧幀的兩個邊界分別由fp(r11)sp(r13)來限定。 如下圖:

總結下來,棧主要用來儲存區域性變數、傳遞引數、儲存暫存器值,顯然初始化棧很重要。程式碼如下:

	@	棧初始化 64M記憶體用於棧
init_stack:
	ldr sp, =0x54000000    @ 將0x54000000寫入sp暫存器中,棧大小64M    0x5400000000-0x500000000
	mov pc, lr	           @ 返回呼叫處繼續往下執行

雖然就是這麼一小段程式碼,但是大家不能小看他,沒有他,後面的c語言開發環境就無從說起了。

10.清除bss段:bl clean_bss

bss段主要存放的是未初始化的全域性變數,為了防止影響以後的使用,要將其初始化為0。也是為C語言環境做準備,程式碼如下:

	@ 清除bss段
clean_bss:
	ldr r0, =bss_start     @ bss段開始地址,參見連結器指令碼
	ldr r1, =bss_end       @ bss段結束地址
	cmp r0, r1             @ 比較bss段開始與結束地址
	moveq pc, lr           @ 若相等,返回呼叫處繼續往下執行
  
clean_loop:              @ 若不相等,執行下面的程式碼
	mov r2, #0             @ r2=0
	str r2, [r0], #4       @ 將r2的值(0)寫入r0所指定的地址中,同時r0指定的地址+4後存入r0
	cmp r0, r1             @ 比較bss段新開始與結束地址
	bne clean_loop         @ 若不相等,跳轉到clean_loop標號處執行
	mov pc, lr             @ 否則,返回呼叫處繼續執行

11.進入c程式設計環境:ldr pc, =main

到此,也就是海寬任魚躍,天高任鳥飛了。在c語言環境中,大家不用再為記不住彙編命令感到頭疼了,是不是很爽呢?廢話少說。上一段c語言環境下的點亮led程式碼:

#define GPKCON (volatile unsigned long*)0x7F008820
#define GPKDAT (volatile unsigned long*)0x7F008824

// 延時函式
void delay()
{
	int i = 0;
	int j = 0;

	for(i = 0; i < 10000; i++)
	{
		for(j = 0; j < 110; j++)
		{
			;
		}
	}
}

int main()
{
	int val = 0x1e;
  *(GPKCON) = 0x1111;
	while(1)
	{
    	*(GPKDAT) = val;
			delay();
			val = val<<1;
    	if((val&0x10) != 0x10)
				val = 0x1f;
	}
    return 0;    
}

本節內容也就講完了,最後貼上整體程式碼:

start.S

.text
.global _start
_start:
	b reset
	ldr pc, _undifined_instruction   @ 未知指令異常
	ldr pc, _software_interrupt      @ 軟體中斷
	ldr pc, _prefetch_abort          @ 預取異常
	ldr pc, _data_abort              @ 資料終止異常
	ldr pc, _not_used                @ 未使用,預留
	ldr pc, _irq                     @ 中斷
	ldr pc, _fiq		             @ 快速中斷
				
_undifined_instruction:  .word undifined_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 reset

undifined_instruction:
	nop
				
software_interrupt:
	nop

prefetch_abort:
	nop

data_abort:
	nop
				
not_used:
	nop
irq:
	nop
				
fiq:
	nop

reset:
	bl set_svc              @ 設定cpu為svc模式
	bl set_peri_port        @ 外設基地址初始化
	bl disable_watchdog     @ 關閉看門狗
	bl disable_interrupt    @ 關閉所有中斷
	bl disable_mmu          @ 關閉mmu和cache
	bl init_clock           @ 時鐘初始化
	bl mem_init             @ 記憶體初始化
	bl copy_to_ram          @ 拷貝bl到記憶體中
	bl init_stack           @ 棧初始化
	bl clean_bss            @ 初始化bss段
	ldr pc, =main           @ 進入c語言程式設計環境,main函式
	@bl light_led
	
	@ set the cpu to SVC32 mode
set_svc:
	mrs	r0,cpsr             @ 將程式狀態暫存器取出儲存在通用暫存器r0中,參看am架構參考手冊:A2.5 Program status registers
	bic	r0,r0,#0x1f         @ 將從狀態暫存器取出的值(r0)低5位清零後存入r0中
	orr	r0,r0,#0xd3         @ 將r0中的值的第0 1 4 6 7位置1後存入r0中,關閉中斷與快速中斷,進入svc模式
	msr	cpsr,r0             @ 將r0的中的值寫入程式狀態暫存器
	mov pc, lr              @ 返回呼叫處繼續往下執行

  @ 初始化外設地址,必須做這一步,要不外設無法使用。
set_peri_port:
	ldr r0, =0x70000000     @ 將基地址0x70000000存入暫存器r0中
	orr r0, r0, #0x13       @ 將暫存器r0的值設為0x70000013
	mcr p15,0,r0,c15,c2,4	  @ 將暫存器r0的值傳送到協處理器p15的c15暫存器中,參看arm11核心手冊:c15, Peripheral Port Memory Remap Register
	mov pc, lr              @ 返回呼叫處繼續往下執行

  @ Disable Watchdog 參看s3c6410手冊:Watch Dog章節
#define pWTCON 0x7e004000  @ addr of watchdog timer control register (WTCON)
disable_watchdog:
	ldr r0, =pWTCON         @ 將看門狗控制暫存器地址寫入暫存器r0中
	mov r1, #0x0            @ 將0x0儲存在暫存器r1中
	str r1, [r0]            @ 將0x0傳送到看門狗控制暫存器中
	mov pc, lr              @ 返回呼叫處繼續往下執行
	
	@ Disable all interrupts (VIC0 and VIC1),參考s3c6410手冊:Vectored Interrupt Controller章節
disable_interrupt:
	mvn r1,#0x0
	ldr r0,=0x71200014      @ VIC0INTENCLEAR地址
	str r1,[r0]             @ 將VIC0INTENCLEAR設為0

	ldr r0,=0x71300014      @ VIC1INTENCLEAR地址
	str r1,[r0]             @ 將VIC1INTENCLEAR設為0	
	mov pc, lr              @ 返回呼叫處繼續往下執行
	
	@ 關閉MMU和cache	
disable_mmu:
	mcr p15,0,r0,c7,c7,0    @ 使I/D cache全部失效 參考arm11核心手冊:c7, Cache operations
	mrc p15,0,r0,c1,c0,0    @ 取出協處理器p15的控制暫存器(c1)內容,存放在r0中 參考arm11核心手冊:c1, Control Register
	bic r0, r0, #0x00000007 @ 將r0的[2:0]設為0後存入r0
	mcr p15,0,r0,c1,c0,0    @ 將r0的值傳送到協處理器p15的控制暫存器(c1)中,完成mmu和cache配置
	mov pc, lr              @ 返回呼叫處繼續往下執行
	
	@ 初始化時鐘 參考s3c6410手冊系統控制章節、時鐘相關章節
#define CLK_DIV0 0x7e00f020    @ 時鐘分頻暫存器0地址
#define OTHERS 0x7e00f900      @ 其他控制暫存器地址
#define MPLL_CON 0x7e00f010    @ MPLL控制暫存器地址
#define APLL_CON 0x7e00f00c    @ APLL控制暫存器地址
#define CLK_SRC 0x7e00f01c     @ 時鐘源選擇暫存器地址
#define DIV_VAL ((0x0<<0)|(0x1<<9)|(0x1<<8)|(0x3<<12))  @ 分頻設定值  2b0000_0000_0000_0000_0011_0011_0000_0000 參考uboot設定
#define PLL_VAL ((1<<31)|(266<<16)|(3<<8)|(1<<0))       @ PLL設定值   2b1000_0001_0000_1010_0000_0011_0000_0001  參考uboot設定
init_clock:
	@ 設定時鐘分頻係數
	ldr r0, =CLK_DIV0      @ 將時鐘分頻暫存器0的地址存入暫存器r0中             
	ldr r1, =DIV_VAL       @ 將需要配置到時鐘分頻暫存器0的數值存入暫存器r1中
	str r1, [r0]           @ 將r1的數值傳送到時鐘分頻暫存器0中
	@ 設定cpu為非同步模式,將OTHERS暫存器內容設定為0xc0
	ldr r0, =OTHERS        @ 將OTHERS寄存地址存入暫存器r0中
	ldr r1, [r0]           @ 將其他控制暫存器中的資料讀出,存入r1中
	bic r1,r1,#0xc0        @ 將r1中的數值第6 7 bit位清0後再存入r1中
	str r1, [r0]           @ 將r1中的數值傳送到OTHERS暫存器中,Asynchronous mode 和 MOUTmpll
	@ 設定APLL時鐘頻率 533MHz
	ldr r0, =APLL_CON      @ 將APLL控制暫存器地址寫入r0中
	ldr r1, =PLL_VAL       @ 將需要配置的PLL數值存入暫存器r1中
	str r1, [r0]           @ 將r1中的數值寫入APLl控制暫存器中
	@ 設定MPLL時鐘頻率 533MHz
	ldr r0, =MPLL_CON      @ 將MPLL控制暫存器地址寫入r0中
	ldr r1, =PLL_VAL       @ 將需要配置的PLL數值存入暫存器r1中
	str r1, [r0]           @ 將r1中的數值寫入MPLL控制暫存器中
	@ 設定時鐘源選擇控制暫存器,使用APLL和MPLL輸出時鐘。
	ldr r0, =CLK_SRC       @ 將時鐘源選擇暫存器地址寫入r0中
	mov r1, #0x3           @ 將0x03寫入暫存器r1中
	str r1, [r0]	         @ 將r1中的資料寫入時鐘源選擇控制暫存器中
	mov pc, lr	           @ 返回呼叫處繼續往下執行

	@ 程式碼搬移,拷貝bl到記憶體中
copy_to_ram:
	ldr r0, =0x0c000000    @ Stepping Stone (Boot Loader)基地址,即要搬移的源地址存入r0
	ldr r1, =0x50008000    @ 記憶體地址,要搬移的目標地址存入r1
	add r3, r0, #1024*4    @ 將r0處的地址加4k大小存入r3,要搬移的記憶體Size 4k,

copy_loop:               @ 等待搬移完成
	ldr r2, [r0], #4       @ 將地址0x0c000000處資料存入r2後,在增加4後(0x0c000004)存入r0中
	str r2, [r1], #4       @ 將r2的資料傳送到r1指定的地址處後,在+4(0x500080004)存入r1中
	cmp r0, r3             @ 比較r0和r3處地址
	bne copy_loop	         @ 若不相等,跳到標號copy_loop處繼續執行
	mov pc, lr	           @ 返回呼叫處繼續往下執行 

	@	棧初始化 64M記憶體用於棧
init_stack:
	ldr sp, =0x54000000    @ 將0x54000000寫入sp暫存器中,棧大小64M    0x5400000000-0x500000000
	mov pc, lr	           @ 返回呼叫處繼續往下執行

	@ 清除bss段
clean_bss:
	ldr r0, =bss_start     @ bss段開始地址,參見連結器指令碼
	ldr r1, =bss_end       @ bss段結束地址
	cmp r0, r1             @ 比較bss段開始與結束地址
	moveq pc, lr           @ 若相等,返回呼叫處繼續往下執行
  
clean_loop:              @ 若不相等,執行下面的程式碼
	mov r2, #0             @ r2=0
	str r2, [r0], #4       @ 將r2的值(0)寫入r0所指定的地址中,同時r0指定的地址+4後存入r0
	cmp r0, r1             @ 比較bss段新開始與結束地址
	bne clean_loop         @ 若不相等,跳轉到clean_loop標號處執行
	mov pc, lr             @ 否則,返回呼