uboot移植準備工作三


1.5.1 start.S引入
u-boot.lds中找到start.S入口
1. c語言中整個項目的入口就是main函數(這是c語言規定的),所以譬如說一個有10000個.c文件的項目,第一個要分析的文件就是包含了main函數的那個文件。
2. 在uboot中因爲有彙編階段參與,因此不能直接找main.c。整個程序入口取決於鏈接器腳本中ENTRY聲明的地方。ENTRY(_start)因此_start符號所在的文件就是整個程序的起始文件,_start所在處的代碼就是整個程序的起始代碼。
3. 利用SI工具搜索共_start,然後分析那個是我們所需要的文件。然後進入start.S文件中,然後進入76,77行,就是_start標號的定義處,我們就找到了整個uboot的入口代碼。
4. 我們開始從start.S文件分析uboot的第一階段代碼。
5. 在SI中,如果我們知道我們要找的文件的名字,但是我們又不知道它在哪個目錄下,我們要怎樣找到並打開這個文件?方法是在SI中先打開右邊的工程項目管理欄目,然後點擊最左邊那個(這個是以文件未單位來瀏覽的),然後再上面輸入欄中輸入要找的文件名字。我們在輸入時,SI在不斷幫我們進行匹配,即使你不記得文件名只能記得大概名字,也能幫你找到你要的文件。

1.5.2 start.S解析1
不簡單的頭文件包含
#include <config.h> config.h是在include目錄下,這個文件不是源碼中本身存在的文件。而是配置過程中生成的文件。(詳見mkconfig腳本)。這個文件的內容其實又包含一個頭文件:#include <configs/TQ210.h>
經過分析後,發現start.S中包含的第一個頭文件就是include/configs/TQ210,這個文件是整個uboot移植時配置文件。這裏面好多宏。因此這個頭文件包含將include/configs/TQ210.h文件和start.S文件關聯了起來。因此之後再分析start.S文件時,主要考慮就是TQ210.h
#include <version.h> include/version.h中包含了include/version_autogenerated.h,這個文件就是配置過程中自動生成的。裏面就一行內容:#define U_BOOT_VERSION "U-Boot 1.3.4" 這裏面定義的宏U_BOOT_VERSION的值是一個字符串,字符串中的版本號信息來至於Makefile中的配置值。這個宏在程序中會被調用,在uboot啓動過程中會串口打印出uboot的版本號。那個版本號信息就是從這來的。
#include <asm/proc/domain.h>。asm目錄不是uboot中的原生目錄,uboot中本來是沒有這個目錄的。asm目錄是在配置的創建的一個符號鏈接,實際指向的是asm-arm。 經過分析後發現,實際文件是/include/asm-arm/proc-armv/domain.h 從這裏可以看出之前配置時穿件的符號鏈接的作用,如果沒有這些符號鏈接則編譯時根本通不過,因爲找不到頭文件。
思考:爲什麼start.S不直接包含asm-arm/proc-armv/domain.h而要用asm/proc/domain.h。這樣的設計是爲了可移植性。因爲如果直接包含,則start.S文件和CPU架構(和硬件)有關。可移植性就差了。譬如我要把uboot移植到mips架構下,則start.S源代碼中所有的頭文件包含全部要修改。我們用了符號鏈接之後,則start.S中源代碼不用改,只需要在具體的硬件移植時配置不同,創建符號鏈接指向的不同,則可以具有可移植性
1.5.3 start.S 解析2
啓動代碼的16字節頭部:在裸機中講過,在SD卡啓動/Nand啓動等整個鏡像開頭需要16字節的校驗頭。(mkv210image.c中。
1. 就是爲了計算這個校驗頭),我們以前做裸機程序時根本沒考慮這16字節校驗頭,因爲:1.如果我們是usb啓動直接下載的方式啓動的則不需要16字節校驗頭(irom application note)2.如果是SD卡啓動mkv210image.c中會給原鏡像前加16字節的校驗頭。
2. Uboot這裏start.S在開頭位置放了16字節的填充佔位,這個佔位的16字節只是保證正式的image的頭部確實有16字節,但是這16字節的內容是不對的,還是需要後面計算和重新填充的。
3. 異常向量表的構建:異常向量表是硬件決定的,軟件只是參照硬件的設計來實現它。 異常向量表中每種異常都應該被處理,否則真遇到了這種異常就跑飛。但是我們在uboot中並非常細緻的處理各種異常。 復位異常處的代碼是: b reset,因此在CPU復位後真正去執行的有效代碼是reset處的代碼,因此rese符號處纔是真正的有意義的代碼開始的地方。
1.5.4 有點意思:.balignl 16,0xdeadbeef
這一句指令是讓當前地址對齊排布,如果當前地址不對齊則自動向後走地址直到對齊,並且向後走的那些內存要用0xdeadbeef來填充。 0xdeadbeef這個一個十六制的數字,這個數字有意思,組成這個數字的16制全是abcdef之中的字母,而這8個字母剛好組成了英文的dead beef這個兩個單詞,字面意思是壞牛肉。
爲什麼要對齊:有時候是效率的要求,有時候是硬件的特殊要求。

1.5.5 TESX_BASE等
這個TEXT_BASE就是上個課程中分析Makefile時講到的那個配置階段的TEXT_BASE,其實就是我們鏈接時指定的uboot的鏈接地址(值就是c3e00000) 源代碼中和配置Makefile中很多變量是可以相互運算的。簡單來說這些符號的值可以從Makefile中傳遞到源代碼中。


第三部分:start.S解析3
1. CFG_PHY_UBOOT_BASE 23e00000 uboot在DDR中的物理地址,虛擬地址是C3E00000.這兩個地址相對應。
2.設置Cpu爲SVC模式
msr cpsr_c, #0xd3 將cpu設置爲禁止FIQ IRQ, ARM狀態svc模式。其實ARM cput在復位時默認就會進入svc模式,但是這裏還是使用軟件將其置爲svc模式。Uboot在工作是cpu一直處於svc模式。
2. 設置L2,L1cache和MMU
bl disable_l2cache 禁止L2 cache
bl set_l2cache_auxctrl_cycle L2 cache 相關初始化
bl enable_l2cache 使能L2 cache
刷新L1 cache的的icache 和dcache.
關閉mmu。 上面這5步都是和CPU的cache和mmu有關的。
2.5.3.3 識別並暫存啓動介質選擇
1. 從哪裏啓動有soc的OM5:OM0這6個引腳高低電平決定的。
2. 實際上在210內部的一個寄存器(地址是0xe0000004),這個寄存器中的值是硬件根據OM引腳的設置而自動設置值的。這個值反應的就是OM引腳的接法。(電平高低),也就是真正的啓動介質是誰。
3. 我們代碼中可以通過讀取這個寄存器的值然後判斷其值確定當選中啓動介質是NAND還是sd還是其他。
4. start.S的225-227行執行完後,在r2寄存器中存儲一個數字,這個數字等於某個特定值時就等於SD卡啓動。等於另一個特定值就是nand啓動。
5. 260行中給r3中賦值#BOOT_MMCSD (0x03),這個在sd啓動時實際會被執行,此執行完這一段代碼後r3中存儲0x3,以後備用。


2.5.3.4 設置棧(SRAM中的棧)並調用lowlevel_init
1. 第317-319行第一次設置棧。這次設置棧是在SRAM中設置的,因爲當前整個代碼還在SRAM中運行,此時DDR還未被初始化,此時DDR還未被初始化還不能用。棧地址0xd003600是自己指定的,指定的原則就是這塊空間只給棧用,不會被別人佔用。
2. 在調用函數前初始化棧,主要原因是在被調用的函數內還有再次調用函數,而BL只會將返回地址存儲到LR中,但是我們只有一個LR,所以在第二次調用函數時前要先將LR入棧,否則函數返回時第一層的返回地址就丟了。

第三部分:start.S解析4
1. 使用SI的reference 功能,找到lowlevel_init函數真正的地方,是\\192.168.64.139\root\home\S5-driver\TQ210\opt\EmbedSky\TQ210\uboot_TQ210_1.3.4\board\EmbedSky\TQ210/lowlevel_init.S中。
2. 檢查復位狀態 : 複雜cpu允許多種復位情況,譬如直接冷上電,熱啓動,睡眠(低功耗)狀態下的喚醒等,這些情況都屬於復位。所以我們在復位代碼中要去檢測復位狀態,來判斷到底是哪種情況。 判斷哪種復位的意義在於:冷上電時DDR是需要初始化才能用的;而熱啓動或者低功耗狀態下的復位則不需要再次初始化DDR。
3. IO狀態恢復:這個和上一個主線代碼都無關,因此不用去管它。
4. 關閉看門狗。
5. 一些SRAM SROM 相關GPIO設置
與主線啓動代碼無關,不用管。
6. 供電鎖存
Lowlevel_init.S的第100-104行,開發板的供電鎖存。
總結:在lowlevel_init.S 前100行,中並沒有做太多有意義的事情(除了關看門狗,供電鎖存外),然後下面從100行纔開始進行有意義的操作。


2.5.6 start.S解析5
/* 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 */
這幾行代碼的作用就是判定當前代碼執行的位置在SRAM中還是在DDR中。爲什麼要做這個判定?原因:BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份。因此如果是冷啓動那麼當前代碼應該是在SRAM中運行BL1,如果是低功耗狀態的復位這時候應該就是在DDR中運行的。原因二:我們判定當前運行代碼的地址是有用的,可以知道後面代碼的運行,譬如在lowlevel_init.S中判定當前代碼的運行地址,就是爲了確定要不要執行時鐘初始化和初始化DDR的代碼。如果當前代碼是在SRAM中,說明冷啓動,那麼時鐘和DDR都需要初始化;如果當前代碼在DDR中,說明是熱啓動,那麼不需要DDR和時鐘都不用再次初始化。
bic r1 ,pc,r0 這句代碼的意義是:將pc的值中的某些bit位清0,剩下一些特殊bit位賦值給r1(r0中爲1的那些位清零)相當於: r1 = pc & ~(0xff000fff) ; ldr r2 ,_TEXT_BASE 加載鏈接地址到r2,然後將r2的相應位清0剩下特定位。
最後比較r1, r2 總結:這一段代碼是通過讀取當前運行地址和鏈接地址,然後處理兩個地址後對比是否相等,來判定當前運行是在SRAM(不相等)還是在DDR中(相等)。從而決定是否跳過下面的時鐘和DDR初始化。

2.5.6.2 system_clock_init:
1.使用SI搜索功能,確定這個函數就在當前文件的256行,。這個初始化時鐘的過程和裸機中初始化的過程一樣的,只是更加完整而且用匯編代碼寫的。
2.在TQ210.h中的345行。都是和時鐘相關的配置值。這些宏定義就決定了210的時鐘配置是多少。也就是說代碼在lowle_init.S中都寫好了,但是代碼的設置都被宏定義在TQ210.h中了。因此,如果移植時需要更改cpu時鐘設置,根本不需要更改代碼,只需在TQ210.h更改配置值。

2.5.7 start.S解析6
1. mem_ctrl_asm_init
1.該函數用來初始化DDR,函數的位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S文件中。 2)該函數和裸機中初始化DDR代碼是一樣的。實際上裸機中初始化DDR的代碼就是從這裏抄的。配置值也可以從這裏抄,但是當時我們自己根據自己理解+抄襲整出來的一份。3)配置值中其他配置值參考裸機中的解釋即可明白,有一個和裸機中講的不一樣。DMC0_MEMCONFIG_0,在裸機中配置爲0x20e01323,在uboot中配置爲0x30f01313,這個配置不同導致結果不用。 在裸機中DMC0的256MB內存地址範圍是0X20000000-0X2FFFFFFF; 在uboot中DMC0的256MB內存地址範圍爲0X30000000-0X3FFFFFFF. 之前在裸機中時配置爲2開頭的地址,當時並沒有說可以配置3開頭。從分析九鼎移植的uboot可以看出;DMC0上允許的地址範圍:20000000-3ffffffff(一共512MB),而我們實際只接了256MB物理內存,SOC允許我們給這256MB挑選地址範圍。

總結一下:在uboot中,可用的物理地址範圍爲:0x30000000-0x4fffffff。一共512MB,其中30000000-3fffffff爲DMC0,40000000-4FFFFFFF爲DMC1。 我們需要的內存配置值在TQ210.h在468行之後,分析的時候要注意條件編譯的條件,配置頭文件中考慮不同時鐘配置下的內存配置值,這個的主要目的是讓不同時鐘需求的客戶都能找到合適自己的內存配置值。 在uboot中DMC0和DMC1都工作了,所以在裸機中只要把uboot中的配置值和配置代碼全部移植過去,應該是能夠讓DMC0和DMC1都工作的。

2.5.7.2 uart_asm_init
1.這個函數用來初始化串口 ,初始化完了後通過串口發送一個’O’
2.5.7.3 tzpc_init 這個沒用過。

2.5.7.4 pop {pc} 以返回
1.返回前通過串口打印’K’
分析:lowlevel_init.S執行完如果每次那麼就會串口打印出“OK”字樣。這應該是我們uboot中看到的最早的輸出信息。


2.5.8 start.S解析7
1. lowlevel_init.S中總共做了哪些事情:
檢查復位狀態,IO恢復 關看門狗,開發板供電鎖存,時鐘初始化,ddr初始化 。串口初始化並打印’O’ TZPC初始化。 打印’K’ .

2.5.8.1 再次設置棧(ddr中的棧)
1. 再次開發板供電鎖存。第一次,做2次是不會錯的。第二,做2次則第2次無意義;做代碼移植時有一個古怪謹慎保守策略就是儘量添加代碼而不要刪除代碼。
2. 之前在調用lowlevel_init程序前設置過1次棧,那時候因爲DDR尚未初始化,因此程序執行都在SRAM中,所以在SRAM中分配了一部分內存作爲棧,本次因爲DDR已經被初始化了,因此要把棧挪移到DDR中,所以要重新設置棧,這是第二次(331),這裏實際設置的棧地址爲33e00000,剛好在uboot的代碼段的下面緊挨着。
3. 爲什麼要再次設置棧?ddr已經初始化了,已經有大片內存可以用了,沒必要再把棧放在SRAM中;原來SRAM中內存大小空間有限,棧放在那裏要注意不能使用過多的棧,否則棧會溢出,我們及時將棧遷移到DDR中也是爲了儘可能避免棧使用時候的小心翼翼。
感慨:uboot的啓動階段主要技巧在於小範圍內有限條件下的輾轉騰挪。
2.5.8.2 再次判斷當前地址以決定是否重定位
1. 再次用相同的代碼判斷運行地址是在SRAM中還是DDR中,不過本次判斷的目的不同,這次判斷時爲了決定是否進行uboot的relocate。
2. 冷啓動時當前情況是uboot的前一部分(16kb或者8kb)開機自動從sd卡加載到sram中正在運行,uboot的第二部分(其實是整個buoot)還躺在SD卡的某個扇區開頭的N個扇區中,此時uboot的第一階段已經即將結束(第一階段該做的事基本做完了),結束之前要把第二部分加載到DDR中鏈接地址處(33e00000),整個加載過程就叫重定義。
2.5.9 uboot重定位詳解:
1. 0xD0037488這個內存地址在SRAM中,這個地址中的值是被硬件自動設置的。硬件根據我們世界電路中SD卡在哪個通道中,會將這個地址中的值設置爲相應的數字。譬如我們從SD0通道啓動時,這個值爲EB00000;從SD2通道啓動時,這個值爲EB2000000.
2. 我們在start.S的260行確定了從MMCSD啓動,然後又在278行將#boot_mmcsd寫入INF_REG3寄存器中存儲着。然後再322行讀出來,在和#boot_mmcsd去比較,確定是從mmcsd啓動。最終調到mmcsd_boot函數去執行重定位動作。
3. 真正的重定位是通過調用movi_b12_copy函數。在uboot/cpu/s5pc11x/movi.c中。是一個c語言的函數
4. copy_bl2(2,MOVI_BL2_POS,MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0) 參數分析:2表示通道; MOVI_BL2_ POS:是uboot的第二部分在SD卡中的開始扇區,這個扇區數字必須和燒錄uboot時燒錄的位置相同;MOVI_BL2_BLKCNT是uboot的長度佔用的扇區數;CFG_PHY_UBOOT_BASE是重定位時將uboot的第二部分複製到DDR中的起始地址(33E00000)。
2.5.10 start.S解析9
1. 物理地址就是物理設備設計生產時賦予的地址。像裸機中使用的寄存器的地址就是CPU設計時指定的,這個就是物理地址。物理地址是硬件編碼的,是設計生產確定好的,一旦確定了就不能改了。
2. 一個事實就是:寄存器的物理地址無法通過編程修改的,是多少就是多少,只能通過查詢數據手冊獲得並操作。壞處就是不夠靈活。一個解決方案就是使用虛擬地址。
3. 虛擬地址意思就是我們軟件操作和硬件操作之間增加一個層次,叫做虛擬地址映射層。有了虛擬地址映射後,軟件操作只需要給虛擬地址,硬件操作還是用原來的物理地址,映射層建立一個虛擬地址到物理地址的映射表。當我們軟件運行時,軟件中使用的虛擬地址在映射表中查詢得到對應的物理地址再發給硬件去執行。(虛擬地址到物理地址的映射是不可能通過軟件來實現的)
2.5.10.2 mmu單元的作用
1. mmu就是memory management unit 內存管理單元。Mmu實際上就是soc中一個硬件單元,它的主要功能就是實現虛擬地址到物理地址的映射。
2.mmu單元在cp15協處理器進行控制,也就是說要操控MMU進行虛擬地址映射,方法就是對CP15協處理器的寄存器進行編程。
2,5,10.3 地址映射的額外收益1:訪問控制
1. 訪問控制就是在管理上對內存進行分塊,然後每塊進行獨立的虛擬地址映射,然後再每一塊的映射關係中同時還實現了訪問控制(對該塊可讀,可寫,只讀,只寫,不可訪問等控制)。
2. 回想在c語言中編程中經常會出現一個錯誤: segmentation fault。實際上這個段錯誤就和mmu實現的訪問控制有關。當前程序只能操作自己有權操作的地址範圍(若干個內存塊),如果當前程序指針出錯訪問了不該訪問的內存塊則就會觸發段錯誤。
2.5.10.4 地址映射的額外收益2:cache
1. cache的工作和虛擬地址映射有關係。
2. cache是快速緩存,意思就是比cpu慢但是比ddr塊。CPU嫌DDR太慢,於是乎把一些DDR中常用的內容事先讀取緩存在CACHE中,然後cpu每次需要找東西時先在cache中找。如果cache中就直接用cache中的;如果cache中沒有才會去DDR中尋找。
2.5.11 start.S解析8
2.5.11.1 使能域訪問(cp15的c3寄存器)
1. cp15協處理器內部有c0到c15共16個寄存器,這些寄存器每一個都有自己的作用。我們通過mrc和mcr指令來訪問這些寄存器。所謂的操作cp協處理器其實就是操作cp15的這些寄存器。
2. c3寄存器在mmu中的作用是控制域訪問。與訪問是和mmu的訪問控制有關的。

2.5.11.2 設置TTB(cp15的c2寄存器)
1. TTB就是translation table base 轉換表基地址。首先要明白什麼是TT(translation table轉換表),TTB其實就是轉換表的基地址。
2. 轉換表是建立一套虛擬地址映射的關鍵。轉換表分2部分,表索引和表項。表索引對應虛擬地址,表項對應物理地址。一對錶索引和表項構成一個轉換表單元,能夠對一個內存塊進行虛擬地址轉換。(映射中基本規定中規定了內存映射和管理是以塊爲單位的,至於塊有多大,要看你的MMU的支持和你自己選擇。在ARM中支持3中塊大小,1kb (細表)4kb(粗表) 1MB(段))。真正的轉換表就是由若干個轉換表單元構成,每個單元負責1個內存塊,總體的轉換表負責整個內存空間(0-4G)的映射。
3. 整個建立虛擬地址映射的主要工作就是建立這張轉換表
4. 轉換表放置在內存中的,放置時要求起始地址在內存中要xx位對齊。轉換表不需要軟件去幹涉使用,而是將基地址TTB設置到cp15的c2寄存器中,然後MMU工作時會自動去查轉換表。

2.5.11.3 使能MMU單元(cp15的從c1寄存器)
1. cp15的寄存器的bit0控制MMU的開關。只要將這一個bit置1即可開啓MMU。開啓MMU之後上層軟件層的地址就必須經過TT的轉換才能發給下層物理層去執行。

2.5.12 start.S解析10
2.5.12.1 宏
ARM的段式映射中長度爲1MB,因此一個映射單元只能管1MB內存,那我們整個4G範圍內需要4G/1MB=4096個映射單元,也就是說這個數組的元素個數是4096.實際上我們做的時候並沒有依次單個處理這4096個單元,而是把4096個分成幾部分,然後每部分用for循環做相同的處理。

宏觀上理解轉換表:整個轉換表可以看作是一個int類型的數組,數組的一個元素就是一個表索引和表項的單元。數組中的元素值就是表項,這個元素的數組下標就是表索引。

結論:虛擬地址映射只是把虛擬地址的c0000000開頭的256MB映射到了DMC0的30000000開頭的256MB物理內存上去了。其他的虛擬地址空間根本沒有動,還是原樣映射的。
思考:爲什麼配置時將連接地址設置爲C3E00000,因爲這個地址將來會被映射到33e00000這個物理地址。

2.5.13 start.S 解析
1.再次設置棧 第三次設置棧。這次設置棧還是在DDR中,之前雖然已經在DDR中設置棧了,但是本次設置棧的目的是將棧放在比較合適(安全,緊湊而不浪費)的地方。
2.我們實際將棧設置在uboot起始地址上方2MB,這樣安全的棧空間是2MB-UBOOT大小-0x1000 = 1.8MB
3.清理bss段代碼和裸機中一樣,這樣bss段的開頭和結尾是從鏈接器腳本u-boot.lds得來的。
4.start_armboot是uboot/lib_arm/board.c 中,這是一個c語言實現的函數。這個函數就是uboot的第二階段。這句代碼的作用就是將uboot第二階段執行的函數的地址傳給pc,實際上就是使用一個遠眺轉直接跳轉到DDR中的第二階段開始地址處。
5.遠眺轉的含義就是這句話加載的地址和當前運行地址無關,而和鏈接地址有關。因此這個遠眺轉可以實現從SRAM中的第一階段跳轉到DDR中的第二階段。
3.這裏這個遠眺轉就是uboot第一階段和第二階段的分界線。
總結:uboot第一階段做了哪些工作:
1. 構建異常向量表
2. 設置CPU爲SVC模式
3. 關看門狗
4. 開發板供電自鎖
5. 時鐘初始化
6. DDR初始化
7. 串口初始化並打印“OK”
8. 重定位
9. 建立映射表並開啓MMU
10. 跳轉第二階段。
發佈了59 篇原創文章 · 獲贊 5 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章