基於OK6410開發板Uboot原始碼簡單分析
2018-04-07
OK6410開發板是基於三星S3C6410晶片設計的一款開發板,資源比較豐富,可是想要使用這些資源就需要編寫相應的啟動載入程式,即BootLoader。當然,想要自己憑空寫出BootLoader那簡直就是天方夜譚,所以我們需要參考行業中現有的BootLoader,在其基礎上再結合實際的控制晶片和開發板相關硬體資源編寫適合自己的BootLoader程式碼。如今市面上常用的嵌入式BootLoader有如下幾種: U-Boot, Blob, VIVI,RedBoot 和 ARMboot 等。下面我們就簡單分析一下BootLoader行業的龍頭老大,U-boot的與S3C6410晶片相關的程式碼。
首先我們需要找到S3C6410晶片相關的Uboot程式碼在整個工程中的位置,我們先將下載的原始碼通過linux解壓後,按如下路徑(board\samsung\smdk6410)找到連結檔案u-boot.lds,開啟連結檔案,如下圖:
連線文字相關資料請大家自行百度,從程式碼段發現,S3C6410的uboot是先從cpu/s3c64xx/start.S檔案開始的,其中ENTRY(_start)說明入口是從檔案start.S的.globl _start處開始的,所以我們需要找到檔案start.S的.globl _start處。使用Source Insight開啟所有的uboot工程,具體方法自行百度。開啟後如下圖:
在右邊搜尋欄中輸入start.S,然後找到相對應的路徑,如上圖。這就是S3C6410的uboot的入口位置,下面看一下uboot都幹了啥,能夠引導S3C6410啟動過程。
入口處程式碼如下:
這是在配置異常中斷向量表,S3C6410共有7種異常中斷,分別是復位、未定義指令、軟體中斷(SWI)、預取終止、資料終止、IRQ、FIQ。_not_used我也不知幹啥的,可能是為了相容性吧。
從上面程式碼我們發現第一步是跳轉到reset位置,我們找到該位置。如下圖:
從註釋我們知道這是在將cpu設定成Supervisor (svc)模式。但是它是如何實現的呢?步驟如下:
1. 使用程式狀態暫存器操作指令
2.使用位清除指令bic將通用暫存器r0中的低5位清除,其餘為保持不變(0x1f= 2b0001 1111);
3. 使用邏輯或指令orr將通用暫存器r0的第0、1、4、6、7位置1,其餘位保持不變(0xd3 = 1101 0011);
4. 使用程式狀態暫存器操作指令msr將通用暫存器r0內容寫入程式狀態暫存器cpsr中。
此時,程式狀態暫存器cpsr中值就是0xd3,對應二進位制為2b1101 0011。根據ARM架構參考指導手冊A2.5節介紹,第6位代表FIQ狀態位、第7位代表IRQ狀態位,cpsr的最低五位代表著cpu執行模式,如下圖:
所以當前cpu狀態就為svc模式、關閉FIQ和IRQ。
我們繼續往下面看,如下圖:
從註釋我們知道接下來是要做的就是在重啟系統時初始化重要的暫存器的操作,首先就是關閉I/D(指令/資料) 快取記憶體器。步驟如下:
1. 將通用暫存器r0賦值為0 ;
2.協處理器p15把r0設定為0並且賦值給第7個暫存器c7;
3. 協處理器p15把r0設定為0並且賦值給第8個暫存器c7。
上面三步主要就是配置訪問caches和TLB的許可權,詳請參考Arm1176jzfs核心說明System Control Coprocessor章節,部分說明如下:
mcr p15, 0, r0, c7, c7, 0指令關閉cache,通過查詢Arm1176jzfs核心說明3.2.1 Register allocation章節,如下圖紅框所示:
跳轉到3-71檢視該條指令詳細資訊。
mcr p15, 0, r0, c8, c7, 0 關閉TLB,如上,查詢指令表如下圖:
跳轉到page 3-86檢視該條指令詳請。
通過上面分析可知,操作協處理器很簡單,就是查詢Arm1176jzfs核心說明3.2.1 Register allocation章節中列出的指令表,然後根據實際情況編寫命令。都是“王八的屁股-規定”的東西。
接著往下看,如下圖:
關閉MMU和caches。步驟如下:
1. 協處理器p15把第一個暫存器c0經過操作的值賦值給通用暫存器r0;
2. 使用位清除指令bic將通用暫存器r0中的第8、9、13位清除,其餘位保持不變(0x00002300 = 2b0000 0000 00000000 0010 0011 0000 0000);
3. 使用位清除指令bic將通用暫存器r0中的第0、1、2、7位清除,其餘位保持不變(0x00002300= 2b0000 0000 0000 0000 0000 0000 1000 0111);
4. 使用邏輯或指令orr將通用暫存器r0的第1位置1,其餘位保持不變(0x 0x00000002 = 2b0000 0000 00000000 0000 0000 0000 0010);
5. 使用邏輯或指令orr將通用暫存器r0的第12位置1,其餘位保持不變(0x 0x00001000 = 2b0000 0000 00000000 0001 0000 0000 0000);
6. 協處理器p15把r0的值賦值給第1個暫存器的c0(控制暫存器)。
mrc p15, 0, r0, c1, c0, 0
mcr p15, 0, r0, c1, c0, 0查詢指令表如下圖:
跳轉到page 3-44檢視該條指令詳請。控制暫存器bit位說明如下:
檢視Operation of the ControlRegister並且對照上面的操作,第0位和12位為0 ,所以關閉了MMU和caches,如下圖:
當然,同時也開啟了位元組對齊檢測功能和流量預報功能,不過流量預報功能不止幹啥的。有興趣的小夥伴可以深入研究一下,研究好了希望可以共享出來。
接著往下看,如下圖:
配置外設埠地址重對映,步驟如下:
1. 通過偽指令ldr將記憶體地址0x70000000賦值給通用暫存器r0,注意:是記憶體地址不是數值;
2. 使用邏輯或指令orr將通用暫存器r0的第0、1、4位置1,其餘位保持不變(0x13 = 2b00010011),即r0中的值為0x70000013;
3. 協處理器p15把r0的值賦值給第15個暫存器的c2(Write Peripheral Port Memory Remap Register)。
mcr p15,0,r0,c15,c2,4查詢指令表如下圖:
跳轉到page 3-130檢視該條指令詳請。外設埠地址重對映暫存器bit位說明如下:
檢視Peripheral Port Memory Remap Registerbit functions並且對照上面的操作,可知基地址為0x70000000,連續256m記憶體,如下圖:
即0x70000000~0x7fffffff全部用於外設地址,參見S3C6410使用者手冊MEMORY MAP章節。
2018-04-08 接上
接著往下看,我們發現有一段關於ONENAND的預編譯,這裡不管他,因為我們採用的是NAND Flash啟動。跳過這段程式碼,找到如下程式碼:
跳轉至lowlevel_init,用於配置PLL,Mux,memory等特殊位,我們進入lowlevel_init,看其是如何實現的,到底幹了啥?lowlevel_init在lowlevel_init.S檔案中。如下圖:
首先將lr暫存器的值儲存到通用暫存器r12中,用於以後返回跳轉位置使用。lr(r14)一般來說有兩個作用:
1.當使用bl或者blx跳轉到子過程的時候,r14儲存了返回地址,可以在呼叫過程結尾恢復。
2.異常中斷髮生時,這個異常模式特定的物理R14被設定成該異常模式將要返回的地址。
接著往下看,程式碼如下圖:
這段程式碼主要用來設定GPK、GPL、GPF的型別及資料等。實現步驟如下:
第1步,使用偽指令將ELFIN_GPIO_BASE地址賦給通用暫存器r0,其中ELFIN_GPIO_BASE在標頭檔案s3c6410.h中定義,如下:
第2步,使用偽指令將地址0x55555555賦給通用暫存器r1;
第3步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPKCON0_OFFSET地址處;GPKCON0_OFFSET定義如下:即將地址0x7f008800設定為0x55555555;
第4步,執行第2步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPKCON1_OFFSET地址處;GPKCON0_OFFSET定義如下:即將地址0x7f008804設定為0x55555555;
通過檢視s3c6410使用者手冊GPIO章節,可知地址0x7f008800對應著GPKCON0暫存器,0x7f008804對應著GPKCON1暫存器,如下圖:
將這兩個暫存器設定為0x55555555,也就是將Port K配置為DATA_CF功能,如下圖:
第5步,使用偽指令將地址0x22222666賦給通用暫存器r1;
第6步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPLCON0_OFFSET地址處;GPLCON0_OFFSET定義如下:即將地址0x7f008810設定為0x22222666;
地址0x7f008810對應著GPLCON0暫存器,如下圖:
將這個暫存器設定為0x22222666,也就是將Port L配置為ADDR_CF功能,如下圖:
第7步,使用偽指令將地址0x04000000賦給通用暫存器r1;
第8步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPFCON_OFFSET地址處;GPFCON_OFFSET定義如下:,即將地址0x7f0080A0)設定為0x04000000;
第9步,使用偽指令將地址0x2000賦給通用暫存器r1;
第10步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPFDAT_OFFSET地址處;GPFCON_OFFSET定義如下:,即將地址0x7f0080A4)設定為0x2000;
地址0x7f0080A0對應著GPFCON暫存器,地址0x7f0080A4對應著GPFCON暫存器,如下圖:
將GPFCON暫存器設定為0x04000000,也就是將PF13配置為輸出功能,如下圖:
將GPFDAT暫存器設定為0x2000,也就是將PF13配置高電平,如下圖:
至於為什麼要這麼設定,為什麼要設定這幾個暫存器,我想應該是和smdk6410板卡硬體使用資源有關吧。
接著往下看,程式碼如下:
用於點亮led小燈,步驟如下:
第1步,使用偽指令將ELFIN_GPIO_BASE地址賦給通用暫存器r0,其中ELFIN_GPIO_BASE的定義參考上面;
第2步,使用偽指令將地址0x00111111賦給通用暫存器r1;
第3步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPMCON_OFFSET地址處;GPKCON0_OFFSET定義如下:即將地址0x7f008820設定為0x00111111;
第4步,使用偽指令將地址0x00000555賦給通用暫存器r1;
第5步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPMPUD_OFFSET地址處;GPMPUD_OFFSET定義如下:即將地址0x7f008828設定為0x00000555;
第6步,使用偽指令將地址0x002a賦給通用暫存器r1;
第7步,使用str指令將r1存放的值賦給ELFIN_GPIO_BASE+GPMDAT_OFFSET地址處;GPMDAT_OFFSET定義如下:,即將地址0x7f008824設定為0x002a;
0x7f008820對應著GPMCON暫存器,0x7f008824對應著GPMDAT暫存器,0x7f008828對應著GPMPUD暫存器,如下圖:
將GPMCON暫存器設定為0x00111111,也就是將PM0~5配置為輸出功能,如下圖:
將GPMDAT暫存器設定為0x002a,也就是將PM1、PM3、PM5配置高電平,如下圖:
將GPMPUD暫存器設定為0x00000555,也就是將PM0~5配置為下拉模式,如下圖:
我想這裡的設定應該也是和smdk6410板卡硬體使用資源有關吧。
2018-04-09
接上文:
接著往下看,程式碼如下:
呵呵,完全不知幹啥的,暫時跳過,等明白了再來完善,請知道的能夠分享一下。
接著往下看,如下:
關閉看門狗,如何實現的呢?
第1步,使用偽指令將地址0x7e000000賦給通用暫存器r0;
第2步,使用邏輯或指令將r0的數值改為0x7e004000,該地址對應的就是關門狗配置暫存器,如下:
第3步,將0賦給通用暫存器r1,;
第4步,使用str指令將r1存放的值賦給r0所存的地址處,即關門狗配置暫存器,及關閉看門狗,如下圖:
接著往下看,程式碼如下:
清除外部中斷位,實現步驟如下:
第1步,使用偽指令將地址ELFIN_GPIO_BASE+EINTPEND_OFFSET賦給通用暫存器r0,ELFIN_GPIO_BASE定義如下:,EINTPEND_OFFSET定義如下:,即將0x7f008924存放在r0中;
第2步,使用ldr指令(這裡與第一步同名不同功能),將R0中的數所指定的地址的內容傳輸到r1中;
第3步,使用str指令將r1存放的值賦給r0所存的地址處,為毛需要這樣,因為讀一次外部中斷引腳,讀完之後中斷訊號就都被清除,上面已經禁止了中斷,所以讀完之後就不會再產生中斷了。
地址0x7f008924對應的是EINT0PEND,如下圖:
繼續往下讀程式碼,如下:
這段程式碼主要就是禁止所有中斷,清除中斷標誌位,實現步驟如下:
第1步,使用偽指令ldr分別將中斷控制器基地址存放到r0、r1中,定義如下;
第2步,將0x0取反後賦給通用暫存器r3,即r3 = 0xffff;
第3步,分別使用str指令r3存放的值賦給r0所存的地址+ oINTMSK處與r10所存的地址+ oINTMSK處,oINTMSK定義如下,即地址為0x71200014和0x71300014:
0x71200014和0x71300014對應的暫存器如下:
中斷使能清除暫存器全部設定1,清除所有中斷使能,如下:
第4步,將0x0賦給通用暫存器r3,即r3 = 0x0;
第5步,分別使用str指令將r3存放的值賦給r0所存的地址+ oINTMOD處與r10所存的地址+ oINTMOD處,oINTMOD定義如下,即地址為0x7120000c和0x7130000c:
0x7120000c和0x7130000c對應的暫存器如下:
中斷選擇暫存器全部設清0,將所有中斷設定為IRQ中斷,如下:
第6步,將0x0賦給通用暫存器r3,即r3 = 0x0;
第7步,分別使用str指令將r3存放的值賦給r0所存的地址+ oVECTADDR處與r10所存的地址+ oVECTADDR處,oVECTADDR定義如下,即地址為0x71200F00和0x71300F00:
0x71200 F00和0x71300 F00對應的暫存器如下:
設定向量地址暫存器(當前中斷的向量地址)為全0。
繼續往下度程式碼,如下:
初始化系統時鐘,需要跳轉到函式system_clock_init處執行,找到這個函式,在相同檔案的第181行,如下:
首先是使用偽指令ldr將地址ELFIN_CLOCK_POWER_BASE存放到通用暫存器r0中,ELFIN_CLOCK_POWER_BASE定義如下: 0x7e00f000對應著系統控制基地址,APLL_CLOCK暫存器,如下:
接著下面有一個預編譯判斷,因為不是同步模式,跳到#else後代碼執行,如下:
先是5個nop,用於延時。
接著使用ldr指令(這裡不同於偽指令),將R0+ OTHERS_OFFSET地址處的內容傳輸到r1中,OTHERS_OFFSET定義如下:,即將地址0x7e00f900處的內容存放在r1中;
然後使用位清除指令將地址0x7e00f900處的內容第6、7位清零,繼續存放在r1中;
再使用邏輯或指令將存放在r1的數值第6位置1;
最後使用str指令將r1中存放的數值賦給R0+ OTHERS_OFFSET地址處,即地址0x7e00f900處。
地址0x7e00f900對應著其他控制暫存器,如下:
將其第7位清0,第6位置1,就是配置系統時鐘為非同步模式、由APLL提供時鐘,如下:
下面的程式碼是同步模式的,跳過不看,直接看#endif後面的程式碼,如下:
由註釋可知這是配置系統時鐘APLL MPLL EPLLd的,來看一下如何實現的:
第1步,將0xff00存放在通用暫存器r1中;
第2步,使用邏輯或指令將r1中存放的值低8位置,繼續存放在r1中,此時r1=0xffff;
第3步,使用str指令分別將r1中的值賦給r0+APLL_LOCK_OFFSET地址處,r0+ MPLL_LOCK_OFFSET地址處和r0+ EPLL_LOCK_OFFSET地址處,定義如下:
地址分別是0x7e00f000、0x7e00f004、x7e00f008地址處,分別對應的暫存器如下:、
它們全部置1後,即它們的預設值,如下:
未完待續。。。。。。