1. 程式人生 > >《5.SDRAM和重定位relocate》

《5.SDRAM和重定位relocate》

《5.SDRAM和重定位relocate》

第一部分、章節目錄
1.5.1.彙編寫啟動程式碼之關看門狗
1.5.2.彙編寫啟動程式碼之設定棧和呼叫C語言1
1.5.3.彙編寫啟動程式碼之設定棧和呼叫C語言2
1.5.4.彙編寫啟動程式碼之開iCache
1.5.5.重定位引入和連結指令碼1
1.5.6.重定位引入和連結指令碼2
1.5.7.重定位引入和連結指令碼3
1.5.8.程式碼重定位實戰1
1.5.9.程式碼重定位實戰2
1.5.10.SDRAM引入
1.5.11.SDRAM初始化
1.5.12.彙編初始化SDRAM詳解1
1.5.13.彙編初始化SDRAM詳解2

第二部分、章節介紹
1.5.1.彙編寫啟動程式碼之關看門狗
使用匯編在啟動程式碼中關閉看門狗,以防止啟動過程中不喂狗導致復位。目的是讓大家認識看門狗這個外設,同時進一步熟悉ARM彙編程式編寫
1.5.2.彙編寫啟動程式碼之設定棧和呼叫C語言1
本節主要講解C語言執行時環境和棧的重要性,然後分析了S5PV210的棧暫存器SP,進一步查閱文件確定啟動程式碼中規劃的棧記憶體區間,最終使用匯編程式設計設定棧以便呼叫C語言程式
1.5.3.彙編寫啟動程式碼之設定棧和呼叫C語言2
本節接上節將之前第三部分中的led程式由彙編更改為C程式,然後被彙編呼叫。通過實驗告訴大家彙編程式是如何呼叫C程式進行互動的。
1.5.4.彙編寫啟動程式碼之開iCache
本節主要講解cache的概念和作用,為什麼需要cache,以及在210中如何通過彙編操作cp15開開啟/關閉cache。
1.5.5.重定位引入和連結指令碼1
本節講解了幾個重要概念,包括:位置無關碼PIC、連結地址和執行地址,然後再次結合S5PV210的啟動過程分析,最終目的是讓大家明白為什麼需要重定位
1.5.6.重定位引入和連結指令碼2
本節首先講了連結地址和執行地址各自由什麼決定,然後簡單講述程式碼編譯連結的步驟,最後重點講了各種段,如程式碼段、資料段、bss段等的含義。
1.5.7.重定位引入和連結指令碼3
本節接上節講述各種段的含義,最後以一個簡單的連結指令碼為例講述了連結指令碼的構成和解讀方法。
1.5.8.程式碼重定位實戰1
本節開始重定位實戰,首先明確任務(在sram內進行重定位),然後重點講解了實現思路及步驟,為下節課寫程式碼打好基礎。
1.5.9.程式碼重定位實戰2
本節講解SRAM內部重定位的程式碼,著重講了adr與ldr偽指令的區別、重定位的copy彙編程式碼、清除bss段的程式碼等模組,目的是讓大家徹底掌握重定位。
1.5.10.SDRAM引入
本節首先講解SDRAM和DDR的聯絡和區別,然後粗略講了SDRAM的特性,最後帶大家簡單讀了SDRAM晶片的手冊,為後面寫程式碼時查閱手冊打好了基礎。
1.5.11.SDRAM初始化
本節首先從原理圖出發,帶領大家詳細分析X210核心板原理圖中DDR SDRAM晶片的相關部分,得出一些引數;然後再結合資料手冊內容得到另一些引數,這些引數在之後的程式碼中都會用得到。

1.5.12.彙編初始化SDRAM詳解1
本節開始講解SDRAM初始化程式碼,首先引導大家找到資料手冊上27步初始化DRAM的部分,然後逐項分析程式碼。本節主要分析了設定引腳驅動能力,和DLL鎖存部分。
1.5.13.彙編初始化SDRAM詳解2
本節接上節繼續講解SDRAM初始化程式碼,主要講了幾個關鍵性暫存器的引數值設定。最後在DRAM初始化後將程式碼重定位到DRAM上執行,完成本章內容。

第三部分、隨堂記錄
1.5.1.彙編寫啟動程式碼之關看門狗
1.5.1.1、什麼是看門狗?
看門狗(watch dog timer 看門狗定時器)。大家想象這樣一個場景:家門口有一隻狗,這個狗定時會餓(譬如說2小時一餓),夠餓了會胡亂咬死人。人進進出出要想保證安全必須提前喂狗(必須在上次餵過後的2小時內喂狗才行)。如果超時沒喂狗就會被咬死,如果提前喂狗沒關係,但是本次喂狗時間就會從這裡開始計算。
現實中因為一些外部因素,電子裝置經常會跑飛或者宕機(譬如極端炎熱、極端寒冷、工業複雜場合)。在這種情況下我們希望裝置自動復位而不需要人工干預(無人值守)。看門狗用來完成這個工作。看門狗其實是我們SoC內部的一個定時器(類似於鬧鐘,類似於門口的狗),定好時間之後看門狗定時器會去計時,時間到之前(狗餓了之前)必須去重新置位看門狗定時器(喂狗),如果沒有喂狗則系統會被強制復位。
系統在正常工作時,系統軟體會自己去喂狗,所以看門狗定時器不會復位。但是系統一旦故障跑飛啥的,看門狗就沒人餵了,然後下一個週期就會自動復位,達到我們期望的效果。

1.5.1.2、分析硬體物理特性、原理圖、資料手冊
物理特性上看門狗其實是個定時器(跟現實中的鬧鐘類似),硬體上就是SoC內部的一個內部外設。
原理圖:看門狗不用分析原理圖,因為看門狗屬於內部外設,且沒有外部相關的原件與他有關,所以不需要原理圖分析,原理圖上根本找不到和看門狗有關的地方。
資料手冊:在資料手冊的Section7.3,大家可以詳細來看。如果直接看不懂資料手冊,可以百度看門狗,然後看別人的部落格來學習。

1.5.1.3、找到關鍵性操作SFR(特殊功能暫存器)
WTCON(0xE2700000),其中bit5是看門狗的開關:0代表關,1代表開
1.5.1.4、編寫彙編程式碼

1.5.1.5、總結210中看門狗特性(iROM中已經關看門狗)
為什麼要關看門狗?
一般CPU設計,在CPU啟動後看門狗預設是工作的(為什麼預設不關閉而要工作?我猜測是因為怕你的程式在啟動程式碼前端就宕機了或者跑飛了沒人管),好處就是沒有空當和漏洞,壞處就是在啟動程式碼段我們不方便去喂狗(或者說懶得去喂狗)時看門狗會復位,所以為了偷懶我們就在啟動程式碼前端先去關閉看門狗,然後在後面系統啟動起來之後再根據需要決定是否要開啟看門狗(一旦開啟就必須同時提供喂狗)。
在S5PV210內部的iROM程式碼(BL0)中,其實已經關過看門狗了。所以我們的啟動程式碼實際上是不用去關也沒事的,也就是說今天寫的關閉看門狗的程式碼執行後沒有任何現象(沒有現象就是正常現象).
很多CPU內部是沒有BL0的,因此也沒人給你關看門狗,都要在啟動程式碼前段自己寫程式碼關看門狗,所以今天學習的內容也是有價值的。

1.5.2.彙編寫啟動程式碼之設定棧和呼叫C語言1
1.5.2.1、C語言執行時需要和棧的意義
“C語言執行時(runtime)”需要一定的條件,這些條件由彙編來提供。C語言執行時主要是需要棧
C語言與棧的關係:C語言中的區域性變數都是用棧來實現的。如果我們彙編部分沒有給C部分預先設定合理合法的棧地址,那麼C程式碼中定義的區域性變數就會落空,整個程式就死掉了。
我們平時在編寫微控制器程式(譬如51微控制器)或者編寫應用程式時並沒有去設定棧,但是C程式還是可以執行的。原因是:在微控制器中由硬體初始化時提供了一個預設可用的棧,在應用程式中我們編寫的C程式其實並不是全部,編譯器(gcc)在連結的時候會幫我們自動新增一個頭,這個頭就是一段引導我們的C程式能夠執行的一段彙編實現的程式碼,這個程式碼中就幫我們的C程式設定了棧及其他的執行時需要。
1.5.2.2、CPU模式和各種模式下的棧
在ARM中37個暫存器中,每種模式下都有自己的獨立的SP暫存器(r13),為什麼這麼設計?
如果各種模式都使用同一個SP,那麼就意味著整個程式(作業系統核心程式、使用者自己編寫的應用程式)都是用一個棧的。你的應用程式如果一旦出錯(譬如棧溢位),就會連累作業系統的棧也損壞,整個作業系統的程式就會崩潰。這樣的作業系統設計是非常脆弱的,不合理的。
解決方案就是各種模式下用不同的棧。我的作業系統核心使用自己的棧,每個應用程式也使用自己獨立的棧,這樣各是各的,一個損壞不會連累其他人。
我們現在要設定棧,不可能也懶的而且也沒有必要去設定所有的棧,我們先要找到自己的模式,然後設定自己的模式下的棧到合理合法的位置,即可。
注意:系統在復位後預設是進入SVC模式的
我們如何訪問SVC模式下的SP呢?很簡單,先把模式設定為SVC,再直接操作SP。但是因為我們復位後就已經是SVC模式了,所以直接設定SP即可。

1.5.2.3、查閱文件並設定棧指標至合法位置
棧必須是當前一段可用的記憶體(可用的意思是這個地方必須有被初始化過可以訪問的記憶體,而且這個記憶體只會被我們用作棧,不會被其他程式徵用)
當前CPU剛復位(剛啟動),外部的DRRAM尚未初始化,目前可用的記憶體只有內部的SRAM(因為它不需初始化即可使用)。因此我們只能在SRAM中找一段記憶體來作為SVC的棧。
棧有四種:滿減棧 滿增棧 空減棧 空增棧
滿棧:進棧:先移動指標再存; 出棧:先出資料再移動指標
空棧:xxx
減棧:進棧:指標向下移動; 出棧:指標向上移動
增棧:xxx
在ARM中,ATPCS(ARM關於程式應該怎麼實現的一個規範)要求使用滿減棧,所以不出意外都是用滿減棧
結合iROM_application_note中的memory map,可知SVC棧應該設定為0xd0037D80
1.5.2.4、彙編程式和C程式互相呼叫
bl cfuncion

1.5.3.彙編寫啟動程式碼之設定棧和呼叫C語言2
1.5.3.1、C函式的編寫和被彙編呼叫
在工程中新建並且新增一個C語言原始檔(led.c),注意新增時要修改Makefile
在彙編啟動程式碼中設定好棧後,使用bl xxx的方式來呼叫C中的函式xxx

1.5.3.2、使用C語言來訪問暫存器的語法
暫存器的地址類似於記憶體地址(IO與記憶體統一編址的),所以這裡的問題是用C語言讀寫暫存器,就是用C語言來讀寫記憶體地址。用C語言來訪問記憶體,就要用到指標
unsigned int *p = (unsigned int *)0x0xE0200240;
p = 0x11111111;
上面這兩句其實可以簡化為1句:
((unsigned int *)0x0xE0200240) = 0x11111111;

1.5.3.3、神奇的volatile
volatile的作用是讓程式在編譯時,編譯器不對程式做優化。優化有時候是ok的,但是有時候是自作聰明會造成程式不對。如果你的一個變數是易變的,不希望編譯器幫我們做優化,就在這個變數定義時加volatile。
加不加有沒有差別,取決於編譯器。如果編譯器做了優化則有差異;如果編譯器本身沒做優化,那就沒有差別。
在我們這裡(編譯器是arm-2009q3),實際測試加不加效果是一樣的。
1.5.3.4、總結:
C和彙編函式的互相呼叫(函式名和彙編標號的真實意義)
C語法對記憶體訪問的封裝方式(使用指標來訪問記憶體的技巧)
彙編的意義(起始程式碼&效率關鍵部位)

1.5.3.5、編譯報錯(實際上是連線階段報錯):undefined reference to `__aeabi_unwind_cpp_pr1’
解決方法:把錯誤資訊直接貼到baidu搜尋(baidu搜尋不到找google),根據搜尋到的內容一個一個看,一個一個嘗試,直到解決。
解決:在編譯時新增-nostdlib這個編譯選項即可解決。nostdlib就是不使用標準函式庫。標準函式庫就是編譯器中自帶的函式庫,用-nostdlib可以讓編譯器連結器優先選擇我程式內自己寫的函式庫。

1.5.4.彙編寫啟動程式碼之開iCache
1.5.4.1、什麼是cache,有什麼用
cache是一種記憶體,叫快取記憶體。
從容量來說:CPU < 暫存器 < cache < DDR
從速度來說:CPU > 暫存器 > cache > DDR
cache的存在,是因為暫存器和ddr之間速度差異太大,ddr的速度遠不能滿足暫存器的需要(不能滿足cpu的需要,所以沒有cache會拉低整個系統的整體速度)
整個系統中CPU的供應鏈由:暫存器+cache+DDR+硬碟/flash四階組成,這是綜合考慮了效能、成本後得到的妥協的結果。
210內部有32KB icache和32kb dcache。icache是用來快取指令的;dcache是用來快取資料的。

cache的意義:指令平時是放在硬碟/flash中的,執行時讀取到DDR中,再從DDR中讀給暫存器,再由暫存器送給cpu。但是DDR的速度和暫存器(代表的就是CPU)相差太大,如果CPU執行完一句再去DDR讀取下一句,那麼CPU的速度完全就被DDR給拖慢了。解決方案就是icache。
icache工作時,會把我們CPU正在執行的指令的旁邊幾句指令事先給讀取到icache中(CPU設計有一個基本原理:程式碼執行時,下一句執行當前一句程式碼旁邊程式碼的可能性要大很多)。當下一句CPU要指令時,cache首先檢查自己事先準備的快取指令中有沒這句,如果有就直接拿給CPU,如果沒有則需要從DDR中重新去讀取拿給CPU,並同時做一系列的動作:清快取、重新快取。

1.5.4.2、iROM中BL0對cache的操作
首先,icache的一切動作都是自動的,不需人為干預。我們所需要做的就是開啟/關閉icache。
其次,在210的iROM中BL0已經打開了icache。所以之前看到的現象都是icache開啟時的現象。
1.5.4.3、彙編程式碼讀寫cp15以開關icache
mrc p15,0,r0,c1,c0,0; // 讀出cp15的c1到r0中
bic r0, r0, #(1<<12) // bit12 置0 關icache
orr r0, r0, #(1<<12) // bit12 置1 開icache
mcr p15,0,r0,c1,c0,0;
1.5.4.4、實驗驗證
我們來看三種情況下的實驗現象:
1 直接使用BL0中對icache的操作
2 關icache
3 開icache
實驗結果分析:
結論1:irom中確實是打開了icache的。
結論2:icache關閉確實比icache開啟時led閃爍變慢,說明指令執行速度變慢。

1.5.5.重定位引入和連結指令碼1
1.5.5.1、一個事實:大部分指令是位置有關編碼
位置無關編碼(PIC,position independent code):彙編原始檔被編碼成二進位制可執行程式時編碼方式與位置(記憶體地址)無關。
位置有關編碼:彙編原始碼編碼成二進位制可執行程式後和記憶體地址是有關的。

我們在設計一個程式時,會給這個程式指定一個執行地址(連結地址)。就是說我們在編譯程式時其實心裡是知道我們程式將來被執行時的地址(執行地址)的,而且必須給編譯器連結器指定這個地址(連結地址)才行。最後得到的二進位制程式理論上是和你指定的執行地址有關的,將來這個程式被執行時必須放在當時編譯連結時給定的那個地址(連結地址)下才行,否則不能執行(就叫位置有關程式碼)。但是有個別特別的指令他可以跟指定的地址(連結地址)沒有關係,也就是說這些程式碼實際執行時不管放在哪裡都能正常執行。

對比:位置無關程式碼要好一些,適應性強,放在哪裡都能正常執行;位置有關程式碼就必須執行在連結時指定的地址上,適應性差。位置無關碼有一些限制,不能完成所有功能,有時候不得不使用位置有關程式碼。

1.5.5.2、連結地址和執行地址:可能相同也可能不同
對於位置有關程式碼來說:最終執行時的執行地址和編譯連結時給定的連結地址必須相同,否則一定出錯。
我們之前的裸機程式中,Makefile中用 -Ttext 0x0 來指定連結地址是0x0。這意味著我們認為這個程式將來會放在0x0這個記憶體地址去執行。
但是實際上我們執行時的地址是0xd0020010(我們用dnw下載時指定的下載地址)。這兩個地址看似不同,但是實際相同。這是因為S5PV210內部做了對映,把SRAM對映到了0x0地址去。

分清楚這兩個概念:
連結地址:連結時指定的地址(指定方式為:Makefile中用-Ttext,或者連結指令碼)
執行地址:程式實際執行時地址(指定方式:由實際執行時被載入到記憶體的哪個位置說了算)

1.5.5.3、再解S5PV210的啟動過程:三星推薦和uboot的實現是不同的
三星推薦的啟動方式中:bootloader必須小於96KB並大於16KB,假定bootloader為80KB,啟動過程是這樣子:先開機上電後BL0執行,BL0會載入外部啟動裝置中的bootloader的前16KB(BL1)到SRAM中去執行,BL1執行時會載入BL2(bootloader中80-16=64KB)到SRAM中(從SRAM的16KB處開始用)去執行;BL2執行時會初始化DDR並且將OS搬運到DDR去執行OS,啟動完成。
uboot實際使用的方式:uboot大小隨意,假定為200KB。啟動過程是這樣子:先開機上電後BL0執行,BL0會載入外部啟動裝置中的uboot的前16KB(BL1)到SRAM中去執行,BL1執行時會初始化DDR,然後將整個uboot搬運到DDR中,然後用一句長跳轉(從SRAM跳轉到DDR)指令從SRAM中直接跳轉到DDR中繼續執行uboot直到uboot完全啟動。uboot啟動後在uboot命令列中去啟動OS。

1.5.5.4、現在明白為什麼要重定位了吧?
原因:
連結地址和執行地址有時候必須不相同,而且還不能全部用位置無關碼,這時候只能重定位。
擴充套件:
分散載入:把uboot分成2部分(BL1和整個uboot),兩部分分別指定不同的連結地址。啟動時將兩部分載入到不同的地址(BL1載入到SRAM,整個uboot載入到DDR),這時候不用重定位也能啟動。
評價:分散載入其實相當於手工重定位。重定位是用程式碼來進行重定位,分散載入是手工操作重定位的。

1.5.6.重定位引入和連結指令碼2
1.5.6.1、執行時地址由什麼決定?
執行時的地址是由執行時決定的(編譯連結時是無法絕對確定執行時地址的)
1.5.6.2、連結地址由什麼決定?
連結地址是由程式設計師在編譯連結的過程中,通過Makefile中-Ttext xxx或者在連結指令碼中指定的。程式設計師事先會預知自己的程式的執行要求,並且有一個期望的執行地址,並且會用這個地址來做連結地址。
舉例:1、linux中的應用程式。gcc hello.c -o hello,這時使用預設的連結地址就是0x0,所以應用程式都是連結在0地址的。因為應用程式執行在作業系統的一個程序中,在這個程序中這個應用程式獨享4G的虛擬地址空間。所以應用程式都可以連結到0地址,因為每個程序都是從0地址開始的。(編譯時可以不給定連結地址而都使用0)
2、210中的裸機程式。執行地址由我們下載時確定,下載時下載到0xd0020010,所以就從這裡開始執行。(這個下載地址也不是我們隨意定的,是iROM中的BL0載入BL1時事先指定好的地址,這是由CPU的設計決定的)。所以理論上我們編譯連結時應該將地址指定到0xd0020010,但是實際上我們在之前裸機程式中都是使用位置無關碼PIC,所以連結地址可以是0。

1.5.6.3、從原始碼到可執行程式的步驟:預編譯、編譯、連結、strip
預編譯:預編譯器執行。譬如C中的巨集定義就是由預編譯器處理,註釋等也是由預編譯器處理的。
編譯: 編譯器來執行。把原始碼.c .S程式設計機器碼.o檔案。
連結: 連結器來執行。把.o檔案中的各函式(段)按照一定規則(連結指令碼來指定)累積在一起,
形成可執行檔案。
strip: strip是把可執行程式中的符號資訊給拿掉,以節省空間。(Debug版本和Release版本)
objcopy:由可執行程式生成可燒錄的映象bin檔案。

1.5.6.4、程式段的概念:程式碼段、資料段、bss段(ZI段)、自定義段
段就是程式的一部分,我們把整個程式的所有東西分成了一個一個的段,給每個段起個名字,然後在連結時就可以用這個名字來指示這些段。也就是說給段命名就是為了在連結指令碼中用段名來讓段站在核實的位置。

段名分為2種:一種是編譯器連結器內部定好的,先天性的名字;一種是程式設計師自己指定的、自定義的段名。
先天性段名:
程式碼段:(.text),又叫文字段,程式碼段其實就是函式編譯後生成的東西
資料段:(.data),資料段就是C語言中有顯式初始化為非0的全域性變數
bss段:(.bss),又叫ZI(zero initial)段,就是零初始化段,對應C語言中初始化為0的全域性變數。
後天性段名:
段名由程式設計師自己定義,段的屬性和特徵也由程式設計師自己定義。

分析一些問題,跟這裡結合,然後試圖明白一些本質:
1、C語言中全域性變數如果未顯式初始化,值是0。本質就是C語言把這類全域性變數放在了bss段,從而保證了為0
2、C執行時環境如何保證顯式初始化為非0的全域性變數的值在main之前就被賦值了?就是因為它把這類變數放在了.data段中,而.data段會在main執行之前被處理(初始化)。

1.5.6.5、連結指令碼究竟要做什麼?
連結指令碼其實是個規則檔案,他是程式設計師用來指揮連結器工作的。連結器會參考連結指令碼,並且使用其中規定的規則來處理.o檔案中那些段,將其連結成一個可執行程式。
連結指令碼的關鍵內容有2部分:段名 + 地址(作為連結地址的記憶體地址)
連結指令碼的理解:
SECTIONS {} 這個是整個連結指令碼
. 點號在連結指令碼中代表當前位置。
= 等號代表賦值

1.5.8.程式碼重定位實戰1
1.5.8.1、任務:在SRAM中將程式碼從0xd0020010重定位到0xd0024000
任務解釋:本來程式碼是執行在0xd0020010的,但是因為一些原因我們又希望程式碼實際是在0xd0024000位置執行的。這時候就需要重定位了。
註解:本練習對程式碼本身執行無實際意義,我們做這個重定位純粹是為了練習重定位技能。但是某些情況重定位就是必須的,譬如在uboot中。

1.5.8.2、思路:
第一點:通過連結指令碼將程式碼連結到0xd0024000
第二點:dnw下載時將bin檔案下載到0xd0020010
第一點加上第二點,就保證了:程式碼實際下載執行在0xd0020010,但是卻被連結在0xd0024000。從而為重定位奠定了基礎。
當我們把程式碼連結地址設定為0xd0024000時,實際隱含意思就是我這個程式碼將來必須放在0xd0024000位置才能正確執行。如果實際執行地址不是這個地址就要出事(除非程式碼是PIC位置無關碼),當以上都明白了後,就知道重定位程式碼的作用就是:在PIC執行完之前(在程式碼中第一句位置有關碼執行之前)必須將整個程式碼搬移到0xd0024000位置去執行,這就是重定位。
第三點:程式碼執行時通過程式碼前段的少量位置無關碼將整個程式碼搬移到0xd0024000
第四點:使用一個長跳轉跳轉到0xd0024000處的程式碼繼續執行,重定位完成
長跳轉:首先這句程式碼是一句跳轉指令(ARM中的跳轉指令就是類似於分支指令B、BL等作用的指令),跳轉指令通過給PC(r15)賦一個新值來完成程式碼段的跳轉執行。長跳轉指的是跳轉到的地址和當前地址差異比較大,跳轉的範圍比較寬廣。
當我們執行完程式碼重定位後,實際上在SRAM中有2份程式碼的映象(一份是我們下載到0xd0020010處開頭的,另一份是重定位程式碼複製到0xd0024000處開頭的),這兩份內容完全相同,僅僅地址不同。重定位之後使用ldr pc, =led_blink這句長跳轉直接從0xd0020010處程式碼跳轉到0xd0024000開頭的那一份程式碼的led_blink函式處去執行。(實際上此時在SRAM中有2個led_blink函式映象,兩個都能執行,如果短跳轉bl led_blink則執行的就是0xd0020010開頭的這一份,如果長跳轉ldr pc, =led_blink則執行的是0xd0024000開頭處的這一份)。這就是短跳轉和長跳轉的區別。

當連結地址和執行地址相同時,短跳轉和長跳轉實際效果是一樣的;但是當連結地址不等於執行地址時,短跳轉和長跳轉就有差異了。這時候短跳轉實際執行的是執行地址處的那一份,而長跳轉執行的是連結地址處那一份。

總結:重定位實際就是在執行地址處執行一段位置無關碼PIC,讓這段PIC(也就是重定位程式碼)從執行地址處把整個程式映象拷貝一份到連結地址處,完了之後使用一句長跳轉指令從執行地址處直接跳轉到連結地址處去執行同一個函式(led_blink),這樣就實現了重定位之後的無縫連線。
1.5.8.3、連結指令碼分析講解

1.5.9.程式碼重定位實戰2
1.5.9.1、adr與ldr偽指令的區別
ldr和adr都是偽指令,區別是ldr是長載入、adr是短載入。
重點:adr指令載入符號地址,載入的是執行時地址;ldr指令在載入符號地址時,載入的是連結地址。

深入分析:只要知道adr和ldr分別用於載入執行地址和連結地址,從而可以判斷是否需要重定位即可;根本不需知道為什麼adr和ldr是這樣子,但是我們還是給大家擴充套件講下為什麼adr和ldr可以載入不同的地址。

1.5.9.2、重定位(程式碼拷貝)
重定位就是彙編程式碼中的copy_loop函式,程式碼的作用是使用迴圈結構來逐句複製程式碼到連結地址。
複製的源地址是SRAM的0xd0020010,複製目標地址是SRAM的0xd0024000,複製長度是bss_start減去_start
所以複製的長度就是整個重定位需要重定位的長度,也就是整個程式中程式碼段+資料段的長度。
bss段(bss段中就是0初始化的全域性變數)不需要重定位。
1.5.9.3、清bss段
清除bss段是為了滿足C語言的執行時要求(C語言要求顯式初始化為0的全域性變數,或者未顯式初始化的全域性變數的值為0,實際上C語言編譯器就是通過清bss段來實現C語言的這個特性的)。一般情況下我們的程式是不需要負責清零bss段的(C語言編譯器和連結器會幫我們的程式自動新增一段頭程式,這段程式會在我們的main函式之前執行,這段程式碼就負責清除bss)。但是在我們程式碼重定位了之後,因為編譯器幫我們附加的程式碼只是幫我們清除了執行地址那一份程式碼中的bss,而未清除重定位地址處開頭的那一份程式碼的bss,所以重定位之後需要自己去清除bss。
1.5.9.4、長跳轉
清理完bss段後重定位就結束了。然後當前的狀況是:
1、當前執行地址還在0xd0020010開頭的(重定位前的)那一份程式碼中執行著。
2、此時SRAM中已經有了2份程式碼,1份在d0020010開頭,另一份在d0024000開頭的位置。
然後就要長跳轉了。

1.5.10.SDRAM引入
1.5.10.1、SDRAM:Syncronized Dynamic Ramdam Access Memory,同步動態隨機儲存器
DDR:DDR就是DDR SDRAM,是SDRAM的升級版。(DDR:double rate,雙倍速度的SDRAM)
DDR有好多代:DDR1 DDR2 DDR3 DDR4 LPDDR

1.5.10.2、SDRAM的特性(容量大、價格低、掉電易失性、隨機讀寫、匯流排式訪問)
SDRAM/DDR都屬於動態記憶體(相對於靜態記憶體SRAM),都需要先執行一段初始化程式碼來初始化才能使用
不像SRAM開機上電後就可以直接執行。
類似於SDRAM和SRAM的區別的,還有NorFlash和NandFlash(硬碟)這兩個。
正是因為硬體本身特性有限制,所以才導致啟動程式碼比較怪異、比較複雜。而我們研究裸機是為了研究uboot,在uboot中就充分利用了硬體的各種特性,處理了硬體複雜性。

1.5.10.3、SDRAM資料手冊帶讀
SDRAM在系統中屬於SoC外接裝置(外部外設。以前說過隨著半導體技術發展,很多東西都逐漸整合到SoC內部去了。現在還長期在外部的一般有:Flash、SDRAM/DDR、網絡卡晶片如DM9000、音訊Codec。現在有一些高整合度的晶片也試圖把這幾個整合進去,做成真正的單晶片解決方案。)
SDRAM通過地址匯流排和資料匯流排介面(匯流排介面)與SoC通訊。

開發板原理圖上使用的是K4T1G164QQ,但是實際開發板上貼的不是這個,是另一款。但是這兩款是完全相容的,進行軟體程式設計分析的時候完全可以參考K4T1G164QQ的文件。

全球做SDRAM的廠商不多,二線廠家做的產品引數都是向一線廠家(三星、KingSton)看齊,目的是相容一線廠家的設計,然後讓在意成本的廠商選擇它的記憶體晶片替代一線廠家的記憶體晶片。SDRAM的這個市場特徵就導致這個東西比較標準化,大部分時候細節引數官方(晶片原廠家)都會給你一個參考值。

K4T1G164QE:
K表示三星產品,4表示是DRAM,T表示產品號碼,1G表示容量(1Gb,等於128MB,我們開發板X210上一共用了4片相同的記憶體,所以總容量是128×4=512MB)16表示單晶片是16位寬的,4表示是4bank,

三星官方的資料手冊上其實沒有晶片相關的引數設定信心,都是晶片選型與外觀封裝方面的資訊,選型是給產品經理來看的,封裝和電壓等資訊是給硬體工程師看的。軟體工程師最關注的是工作引數資訊,但是資料手冊沒有。

1.5.11.SDRAM初始化
1.5.11.1、原理圖中SDRAM相關部分
S5PV210共有2個記憶體埠(就好象有2個記憶體插槽)。再結合查閱資料手冊中記憶體對映部分,可知:兩個記憶體埠分別叫DRAM0和DRAM1:
DRAM0:記憶體地址範圍:0x20000000~0x3FFFFFFF(512MB),對應引腳是Xm1xxxx
DRAM1: 記憶體地址範圍:0x40000000~0x7FFFFFFF(1024MB),對應引腳是Xm2xxxx
結論:
(1)整個210最多支援記憶體為1.5GB,如果給210更多的記憶體CPU就無法識別。
(2)210最多支援1.5GB記憶體,但是實際開發板不一定要這麼多,譬如我們X210開發板就只有512MB記憶體,連線方法是在DRAM0埠分佈256MB,在DRAM1埠分佈了256MB。
(3)由2可知,X210開發板上記憶體合法地址是:0x20000000~0x2FFFFFFF(256MB) + 0x40000000~0x4FFFFFFF(256MB)。當板子上DDR初始化完成之後,這些地址都是可以使用的;如果使用了其他地址譬如0x30004000就是死路一條。

原理圖中每個DDR埠都由3類匯流排構成:地址匯流排(Xmn_ADDR0~XMnADDR13共14根地址匯流排) + 控制匯流排(中間部分,自己看原理圖) + 資料匯流排(Xmn_DATA0~XMnDATA31共32根資料線)
分析:從資料匯流排的位數可以看出,我們用的是32位的(物理)記憶體。

原理圖中畫出4片記憶體晶片的一頁,可以看出:X210開發板共使用了4片記憶體(每片1Gb=128MB,共512MB),每片記憶體的資料匯流排都是16位的(單晶片是16位記憶體)。如何由16位記憶體得到32位記憶體呢?可以使用並聯方法。在原理圖上橫向的2顆記憶體晶片就是並聯連線的。並聯時地址匯流排接法一樣,但是資料匯流排要加起來。這樣連線相當於在邏輯上可以把這2顆記憶體晶片看成是一個(這一個晶片是32位的,接在Xm1埠上)。

1.5.11.2、資料手冊中SDRAM相關部分
看資料手冊《NT5TU64M16GG-DDR2-1G-G-R18-Consumer》第10頁的block diagram。這個框圖是128Bb×8結構的,這裡的8指的是8bank,每bank128Mbit。
210的DDR埠訊號中有BA0~BA2,接在記憶體晶片的BA0~BA2上,這些引腳就是用來選擇bank的。
每個bank內部有128Mb,通過row address(14位) + column address(10位)的方式來綜合定址。
一共能定址的範圍是:2的14次方+2的10次方 = 2的24次方。對應16MB(128Mbit)記憶體。

1.5.12.彙編初始化SDRAM詳解1
1.5.12.1、初始化程式碼框架介紹(函式呼叫和返回、步驟等)
SDRAM初始化使用一個函式sdram_asm_init,函式在sdram_init.S檔案中實現,是一個彙編函式。
強調:彙編實現的函式在返回時需要明確使用返回指令(mov pc, lr)
1.5.12.2、27步初始化DDR2
(1)首先,DDR初始化和SoC(準確說是和SoC中的DDR控制器)有關,也和開發板使用的DDR晶片有關,和開發板設計時DDR的連線方式也有關。
(2)S5PV210的DDR初始化步驟在SoC資料手冊:1.2.1.3 DDR2這個章節。可知初始化DDR共需27個步驟。
(3)之前分析過X210的記憶體連線方式是:在DRAM0上連線256MB,在DRAM1上連線了256MB。所以初始化DRAM時分為2部分,第一部分初始化DRAM0,第二部分初始化DRAM1.
(4)我們的程式碼不是自己寫的,這個程式碼來自於:第一,九鼎官方的uboot中;第二,參考了九鼎的裸機教程中對DDR的初始化;第三,有些引數是我根據自己理解修改過的。
1.5.12.3、設定IO埠驅動強度
因為DDR晶片和S5PV210之間是通過很多匯流排連線的,匯流排的物理表現就是很多個引腳,也就是說DDR晶片和S5PV210晶片是通過一些引腳連線的。DDR晶片工作時需要一定的驅動訊號,這個驅動訊號需要一定的電平水平才能抗干擾,所以需要設定這些引腳的驅動能力,使DDR正常工作。
DRAM控制器對應的引腳設定為驅動強度2X(我也不知道為什麼是2X,什麼時候設定成3X 4X?,這東西只能問DDR晶片廠商或者SoC廠商,我們一般是參考原廠給的程式碼)
1.5.12.4、DRAM port 時鐘設定
從程式碼第128行到154行。主要是開啟DLL(dram pll)然後等待鎖存。
這段程式碼對應27步中的第2到第4步。

1.5.13.彙編初始化SDRAM詳解2
1.5.13.1、DMC0_MEMCONTROL
burst length=4,1chip,······ 對應值是0x00202400
1.5.13.2、DMC0_MEMCONFIG_0
DRAM0通道中memory chip0的引數設定暫存器
1.5.13.3、DMC0_MEMCONFIG_1
DRAM0通道中memory chip1的引數設定暫存器
總結:我猜測(推論):三星設定DRAM0通道,允許我們接2片256MB的記憶體,分別叫memory chip0和memory chip1,分別用這兩個暫存器來設定它的引數。按照三星的設計,chip0的地址應該是0x20000000到0x2FFFFFFF,然後chip1的地址應該是0x30000000~0x3FFFFFFF.各自256MB。
但是我們X210開發板實際在DRAM0埠只接了256MB的記憶體,所以只用了chip0,沒有使用chip1.(我們雖然是2片晶片,然後這兩片是並聯形成32位記憶體的,邏輯上只能算1片)。按照這個推論,DMC0_MEMCONFIG_0有用,而DMC0_MEMCONFIG_1無用,所以我直接給他了預設值。
1.5.13.4、DMC_DIRECTCMD
這個暫存器是個命令暫存器,我們210通過向這個暫存器寫值來向DDR晶片傳送命令(通過命令匯流排),這些命令應該都是用來配置DDR晶片工作引數。

總結:DDR配置過程比較複雜,基本上是按照DDR控制器的時序要求來做的,其中很多引數要結合DDR晶片本身的引數來定,還有些引數是時序引數,要去詳細計算。所以DDR配置非常繁瑣、細緻、專業。所以我們對DDR初始化的態度就是:學會這種思路和方法,結合文件和程式碼能看懂,會算一些常見的引數即可。
1.5.13.5、重定位程式碼到SDRAM中
DRAM初始化之後,實際上重定位程式碼過程和之前重定位到SRAM中完全相同。