1. 程式人生 > >《2.uboot和系統移植-第6部分-2.6.uboot原始碼分析2-啟動第二階段》

《2.uboot和系統移植-第6部分-2.6.uboot原始碼分析2-啟動第二階段》

《2.uboot和系統移植-第6部分-2.6.uboot原始碼分析2-啟動第二階段》

第一部分、章節目錄
2.6.1.start_armboot函式簡介
2.6.2.start_armboot解析1
2.6.3.記憶體使用排布
2.6.4.start_armboot解析2
2.6.5.start_armboot解析3
2.6.6.start_armboot解析4
2.6.7.start_armboot解析5
2.6.8.start_armboot解析6
2.6.9.start_armboot解析7
2.6.10.start_armboot解析8
2.6.11.start_armboot解析9
2.6.12.start_armboot解析10
2.6.13.start_armboot解析11
2.6.14.start_armboot解析12
2.6.15.start_armboot解析13
2.6.16.start_armboot解析14
2.6.17.uboot啟動2階段總結

第二部分、章節介紹
2.6.1.start_armboot函式簡介
本節課簡要介紹start_armboot函式,回顧uboot啟動第一階段的同時展望了第二階段要做的事情,並且分析了uboot啟動最終的歸宿。
2.6.2.start_armboot解析1
本節開始解析start_armboot函式,主要解析了init_fnc_t和DECLARE_GLOBAL_DATA_PTR這兩個表示式。
2.6.3.記憶體使用排布
本節接應上節講的gd和bd,介紹uboot中設計的記憶體使用和排布方法。
2.6.4.start_armboot解析2
本節開始分析init_sequence,並從頭開始分析其中的板級初始化函式。
2.6.5.start_armboot解析3
本節主要講解board_init中的DDR配置和初始化部分。
2.6.6.start_armboot解析4
本節主要講解interrupt_init和env_init,分別是定時器初始化和環境變數的初始化。
2.6.7.start_armboot解析5
本節主要講解init_baudrate和serial_init兩個函式,都是串列埠相關的初始化。
2.6.8.start_armboot解析6
本節主要講解console_init_f、display_banner和print_cpuinfo這兩個初始化函式,分別是控制檯初始化、列印啟動logo和cpu資訊的函式。
2.6.9.start_armboot解析7
本節介紹了check_board函式,同時重點給大家演示瞭如何進行uboot的程式碼實踐和學習。
2.6.10.start_armboot解析8
本節介紹了init_sequence中最後的2個dram相關初始化函式,並且對整個init_sequence中所有初始化函式做了總結和回顧。
2.6.11.start_armboot解析9
本節講解flash初始化和堆管理器記憶體初始化兩個函式,並且進行了程式碼實踐。
2.6.12.start_armboot解析10
本節主要講解MMC卡初始化相關程式碼,這是對驅動學習最好的啟蒙。
2.6.13.start_armboot解析11
本節主要講解env_relocate函式,詳細介紹了環境變數在uboot的整體實現。
2.6.14.start_armboot解析12
本節主要講解IP地址、MAC地址的獲取及確定,devices_init和jumptable的初始化。
2.6.15.start_armboot解析13
本節主要講解console_init_f、enable_interrupts、board_late_init這幾個初始化函式。
2.6.16.start_armboot解析14
本節講解剩下的幾個初始化函式,並且結束了start_armboot函式。
2.6.17.uboot啟動2階段總結
本節對本課程中講到的start_armboot函式進行整體總結回顧,幫助大家加深記憶。

第三部分、隨堂記錄
2.6.1.start_armboot函式簡介
2.6.1.1、一個很長的函式
(1)這個函式在uboot/lib_arm/board.c的第444行開始到908行結束。
(2)450行還不是全部,因為裡面還呼叫了別的函式。
(3)為什麼這麼長的函式,怎麼不分成兩三個函式?主要因為這個函式整個構成了uboot啟動的第二階段。

2.6.1.2、一個函式組成uboot第二階段

2.6.1.3、巨集觀分析:uboot第二階段應該做什麼
(1)概括來講uboot第一階段主要就是初始化了SoC內部的一些部件(譬如看門狗、時鐘),然後初始化DDR並且完成重定位。
(2)由巨集觀分析來講,uboot的第二階段就是要初始化剩下的還沒被初始化的硬體。主要是SoC外部硬體(譬如iNand、網絡卡晶片····)、uboot本身的一些東西(uboot的命令、環境變數等····)。然後最終初始化完必要的東西后進入uboot的命令列準備接受命令。

2.6.1.4、思考:uboot第二階段完結於何處?
(1)uboot啟動後自動執行打印出很多資訊(這些資訊就是uboot在第一和第二階段不斷進行初始化時,打印出來的資訊)。然後uboot進入了倒數bootdelay秒然後執行bootcmd對應的啟動命令。
(2)如果使用者沒有干涉則會執行bootcmd進入自動啟動核心流程(uboot就死掉了);此時使用者可以按下回車鍵打斷uboot的自動啟動進入uboot的命令列下。然後uboot就一直工作在命令列下。
(3)uboot的命令列就是一個死迴圈,迴圈體內不斷重複:接收命令、解析命令、執行命令。這就是uboot最終的歸宿。

2.6.2.start_armboot解析1
2.6.2.1、init_fnc_t
(1)typedef int (init_fnc_t) (void); 這是一個函式型別
(2)init_fnc_ptr是一個二重函式指標,回顧高階C語言中講過:二重指標的作用有2個(其中一個是用來指向一重指標),一個是用來指向指標陣列。因此這裡的init_fuc_ptr可以用來指向一個函式指標陣列。

2.6.2.DECLARE_GLOBAL_DATA_PTR
(1)#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm (“r8”)
定義了一個全域性變數名字叫gd,這個全域性變數是一個指標型別,佔4位元組。用volatile修飾表示可變的,用register修飾表示這個變數要儘量放到暫存器中,後面的asm(“r8”)是gcc支援的一種語法,意思就是要把gd放到暫存器r8中。
(2)綜合分析,DECLARE_GLOBAL_DATA_PTR就是定義了一個要放在暫存器r8中的全域性變數,名字叫gd,型別是一個指向gd_t型別變數的指標。
(3)為什麼要定義為register?因為這個全域性變數gd(global data的簡稱)是uboot中很重要的一個全域性變數(準確的說這個全域性變數是一個結構體,裡面有很多內容,這些內容加起來構成的結構體就是uboot中常用的所有的全域性變數),這個gd在程式中經常被訪問,因此放在register中提升效率。因此純粹是執行效率方面考慮,和功能要求無關。並不是必須的。
(4)gd_t定義在include/asm-arm/global_data.h中。
gd_t中定義了很多全域性變數,都是整個uboot使用的;其中有一個bd_t型別的指標,指向一個bd_t型別的變數,這個bd是開發板的板級資訊的結構體,裡面有不少硬體相關的引數,譬如波特率、IP地址、機器碼、DDR記憶體分佈。

2.6.3.記憶體使用排布
2.6.3.1、為什麼要分配記憶體
(1)DECLARE_GLOBAL_DATA_PTR只能定義了一個指標,也就是說gd裡的這些全域性變數並沒有被分配記憶體,我們在使用gd之前要給他分配記憶體,否則gd也只是一個野指標而已。
(2)gd和bd需要記憶體,記憶體當前沒有被人管理(因為沒有作業系統統一管理記憶體),大片的DDR記憶體散放著可以隨意使用(只要使用記憶體地址直接去訪問記憶體即可)。但是因為uboot中後續很多操作還需要大片的連著記憶體塊,因此這裡使用記憶體要本著夠用就好,緊湊排布的原則。所以我們在uboot中需要有一個整體規劃。

2.6.3.2、記憶體排布
(1)uboot區 CFG_UBOOT_BASE-xx(長度為uboot的實際長度)
(2)堆區 長度為CFG_MALLOC_LEN,實際為912KB
(3)棧區 長度為CFG_STACK_SIZE,實際為512KB
(4)gd 長度為sizeof(gd_t),實際36位元組
(5)bd 長度為sizeof(bd_t),實際為44位元組左右
(6)記憶體間隔 為了防止高版本的gcc的優化造成錯誤。

2.6.4.start_armboot解析2
2.6.4.1、for迴圈執行init_sequence
(1)init_sequence是一個函式指標陣列,陣列中儲存了很多個函式指標,這些指向指向的函式都是init_fnc_t型別(特徵是接收引數是void型別,返回值是int)。
(2)init_sequence在定義時就同時給了初始化,初始化的函式指標都是一些函式名。(C語言高階專題中講過:函式名的實質)
(3)init_fnc_ptr是一個二重函式指標,可以指向init_sequence這個函式指標陣列。
(4)用for迴圈肯定是想要去遍歷這個函式指標陣列(遍歷的目的也是去依次執行這個函式指標陣列中的所有函式)。思考:如何遍歷一個函式指標陣列?有2種方法:第一種也是最常用的一種,用下標去遍歷,用陣列元素個數來截至。第二種不常用,但是也可以。就是在陣列的有效元素末尾放一個標誌,依次遍歷到標準處即可截至(有點類似字串的思路)。
我們這裡使用了第二種思路。因為陣列中存的全是函式指標,因此我們選用了NULL來作為標誌。我們遍歷時從開頭依次進行,直到看到NULL標誌截至。這種方法的優勢是不用事先統計陣列有多少個元素。
(5)init_fnc_t的這些函式的返回值定義方式一樣的,都是:函式執行正確時返回0,不正確時返回-1.所以我們在遍歷時去檢查函式返回值,如果遍歷中有一個函式返回值不等於0則hang()掛起。從分析hang函式可知:uboot啟動過程中初始化板級硬體時不能出任何錯誤,只要有一個錯誤整個啟動就終止,除了重啟開發板沒有任何辦法。
(6)init_sequence中的這些函式,都是board級別的各種硬體初始化。

2.6.4.2、cpu_init
(1)看名字這個函式應該是cpu內部的初始化,所以這裡是空的。
2.6.4.3、board_init
(1)board_init在uboot/board/samsung/x210/x210.c中,這個看名字就知道是x210開發板相關的初始化。
(2)DECLARE_GLOBAL_DATA_PTR在這裡宣告是為了後面使用gd方便。可以看出把gd的宣告定義成一個巨集的原因就是我們要到處去使用gd,因此就要到處宣告,定義成巨集比較方便。
(3)網絡卡初始化。CONFIG_DRIVER_DM9000這個巨集是x210_sd.h中定義的,這個巨集用來配置開發板的網絡卡的。dm9000_pre_init函式就是對應的DM9000網絡卡的初始化函式。開發板移植uboot時,如果要移植網絡卡,主要的工作就在這裡。
(4)這個函式中主要是網絡卡的GPIO和埠的配置,而不是驅動。因為網絡卡的驅動都是現成的正確的,移植的時候驅動是不需要改動的,關鍵是這裡的基本初始化。因為這些基本初始化是硬體相關的。

2.6.5.start_armboot解析3
2.6.5.1、gd->bd->bi_arch_number
(1)bi_arch_number是board_info中的一個元素,含義是:開發板的機器碼。所謂機器碼就是uboot給這個開發板定義的一個唯一編號。
(2)機器碼的主要作用就是在uboot和linux核心之間進行比對和適配。
(3)嵌入式裝置中每一個裝置的硬體都是定製化的,不能通用。嵌入式裝置的高度定製化導致硬體和軟體不能隨便適配使用。這就告訴我們:這個開發板移植的核心映象絕對不能下載到另一個開發板去,否則也不能啟動,就算啟動也不能正常工作,有很多隱患。因此linux做了個設定:給每個開發板做個唯一編號(機器碼),然後在uboot、linux核心中都有一個軟體維護的機器碼編號。然後開發板、uboot、linux三者去比對機器碼,如果機器碼對上了就啟動,否則就不啟動(因為軟體認為我和這個硬體不適配)。
(4)MACH_TYPE在x210_sd.h中定義,值是2456,並沒有特殊含義,只是當前開發板對應的編號。這個編號就代表了x210這個開發板的機器碼,將來這個開發板上面移植的linux核心中的機器碼也必須是2456,否則就啟動不起來。
(5)uboot中配置的這個機器碼,會作為uboot給linux核心的傳參的一部分傳給linux核心,核心啟動過程中會比對這個接收到的機器碼,和自己本身的機器碼相對比,如果相等就啟動,如果不想等就不啟動。
(6)理論上來說,一個開發板的機器碼不能自己隨便定。理論來說有權利去發放這個機器碼的只有uboot官方,所以我們做好一個開發板並且移植了uboot之後,理論上應該提交給uboot官方稽核併發放機器碼(好像是免費的)。但是國內的開發板基本都沒有申請(主要是因為國內開發者英文都不行,和國外開源社群接觸比較少),都是自己隨便編號的。隨便編號的問題就是有可能和別人的編號衝突,但是隻要保證uboot和kernel中的編號是一致的,就不影響自己的開發板啟動。

2.6.5.2、gd->bd->bi_boot_params
(1)bd_info中另一個主要元素,bi_boot_params表示uboot給linux kernel啟動時的傳參的記憶體地址。也就是說uboot給linux核心傳參的時候是這麼傳的:uboot事先將準備好的傳參(字串,就是bootargs)放在記憶體的一個地址處(就是bi_boot_params),然後uboot就啟動了核心(uboot在啟動核心時真正是通過暫存器r0 r1 r2來直接傳遞引數的,其中有一個暫存器中就是bi_boot_params)。核心啟動後從暫存器中讀取bi_boot_params就知道了uboot給我傳遞的引數到底在記憶體的哪裡。然後自己去記憶體的那個地方去找bootargs。
(2)經過計算得知:X210中bi_boot_params的值為0x30000100,這個記憶體地址就被分配用來做核心傳參了。所以在uboot的其他地方使用記憶體時要注意,千萬不敢把這裡給淹沒了。

背景:關於DDR的配置:
(1)board_init中除了網絡卡的初始化之外,剩下的2行用來初始化DDR。
(2)注意:這裡的初始化DDR和彙編階段lowlevel_init中初始化DDR是不同的。當時是硬體的初始化,目的是讓DDR可以開始工作。現在是軟體結構中一些DDR相關的屬性配置、地址設定的初始化,是純軟體層面的。
(3)軟體層次初始化DDR的原因:對於uboot來說,他怎麼知道開發板上到底有幾片DDR記憶體,每一片的起始地址、長度這些資訊呢?在uboot的設計中採用了一種簡單直接有效的方式:程式設計師在移植uboot到一個開發板時,程式設計師自己在x210_sd.h中使用巨集定義去配置出來板子上DDR記憶體的資訊,然後uboot只要讀取這些資訊即可。(實際上還有另外一條思路:就是uboot通過程式碼讀取硬體資訊來知道DDR配置,但是uboot沒有這樣。實際上PC的BIOS採用的是這種)
(4)x210_sd.h的496行到501行中使用了標準的巨集定義來配置DDR相關的引數。主要配置了這麼幾個資訊:有幾片DDR記憶體、每一片DDR的起始地址、長度。這裡的配置資訊我們在uboot程式碼中使用到記憶體時就可以從這裡提取使用(想象uboot中使用到記憶體的地方都不是直接用地址數字的,都是用巨集定義的)

2.6.6.start_armboot解析4
2.6.6.1、interrupt_init
(1)看名字函式是和中斷初始化有關的,但是實際上不是,實際上這個函式是用來初始化定時器的(實際使用的是Timer4)。
(2)裸機中講過:210共有5個PWM定時器。其中Timer0-timer3都有一個對應的PWM訊號輸出的引腳。而Timer4沒有引腳,無法輸出PWM波形。Timer4在設計的時候就不是用來輸出PWM波形的(沒有引腳,沒有TCMPB暫存器),這個定時器被設計用來做計時。
(3)Timer4用來做計時時要使用到2個暫存器:TCNTB4、TCNTO4。TCNTB中存了一個數,這個數就是定時次數(每一次時間是由時鐘決定的,其實就是由2級時鐘分頻器決定的)。我們定時時只需要把定時時間/基準時間=數,將這個數放入TCNTB中即可;我們通過TCNTO暫存器即可讀取時間有沒有減到0,讀取到0後就知道定的時間已經到了。
(4)使用Timer4來定時,因為沒有中斷支援,所以CPU不能做其他事情同時定時,CPU只能使用輪詢方式來不斷檢視TCNTO暫存器才能知道定時時間到了沒。因為Timer4的定時是不能實現微觀上的並行。uboot中定時就是通過Timer4來實現定時的。所以uboot中定時時不能做其他事(考慮下,典型的就是bootdelay,bootdelay中實現定時並且檢查使用者輸入是用輪詢方式實現的,原理參考裸機中按鍵章節中的輪詢方式處理按鍵)
(5)interrupt_init函式將timer4設定為定時10ms。關鍵部位就是get_PCLK函式獲取系統設定的PCLK_PSYS時鐘頻率,然後設定TCFG0和TCFG1進行分頻,然後計算出設定為10ms時需要向TCNTB中寫入的值,將其寫入TCNTB,然後設定為auto reload模式,然後開定時器開始計時就沒了。
總結:在學習這個函式時,注意標準程式碼和之前裸機程式碼中的區別,重點學會:通過定義結構體的方式來訪問暫存器,通過函式來自動計算設定值以設定定時器。

2.6.6.2、env_init
(1)env_init,看名字就知道是和環境變數有關的初始化。
(2)為什麼有很多env_init函式,主要原因是uboot支援各種不同的啟動介質(譬如norflash、nandflash、inand、sd卡·····),我們一般從哪裡啟動就會把環境變數env放到哪裡。而各種介質存取操作env的方法都是不一樣的。因此uboot支援了各種不同介質中env的操作方法。所以有好多個env_xx開頭的c檔案。實際使用的是哪一個要根據自己開發板使用的儲存介質來定(這些env_xx.c同時只有1個會起作用,其他是不能進去的,通過x210_sd.h中配置的巨集來決定誰被包含的),對於x210來說,我們應該看env_movi.c中的函式。
(3)經過基本分析,這個函式只是對記憶體裡維護的那一份uboot的env做了基本的初始化或者說是判定(判定裡面有沒有能用的環境變數)。當前因為我們還沒進行環境變數從SD卡到DDR中的relocate,因此當前環境變數是不能用的。
(4)在start_armboot函式中(776行)呼叫env_relocate才進行環境變數從SD卡中到DDR中的重定位。重定位之後需要環境變數時才可以從DDR中去取,重定位之前如果要使用環境變數只能從SD卡中去讀取。

2.6.7.start_armboot解析5
2.6.7.1、init_baudrate
(1)init_baudrate看名字就是初始化串列埠通訊的波特率的。
(2)getenv_r函式用來讀取環境變數的值。用getenv函式讀取環境變數中“baudrate”的值(注意讀取到的不是int型而是字串型別),然後用simple_strtoul函式將字串轉成數字格式的波特率。
(3)baudrate初始化時的規則是:先去環境變數中讀取"baudrate"這個環境變數的值。如果讀取成功則使用這個值作為環境變數,記錄在gd->baudrate和gd->bd->bi_baudrate中;如果讀取不成功則使用x210_sd.h中的的CONFIG_BAUDRATE的值作為波特率。從這可以看出:環境變數的優先順序是很高的。

2.6.7.2、serial_init
(1)serial_init看名字是初始化串列埠的。(疑問:start.S中呼叫的lowlevel_init.S中已經使用匯編初始化過串列埠了,這裡怎麼又初始化?這兩個初始化是重複的還是各自有不同?)
(2)SI中可以看出uboot中有很多個serial_init函式,我們使用的是uboot/cpu/s5pc11x/serial.c中的serial_init函式。
(3)進來後發現serial_init函式其實什麼都沒做。因為在彙編階段串列埠已經被初始化過了,因此這裡就不再進行硬體暫存器的初始化了。

2.6.8.start_armboot解析6
2.6.8.1、console_init_f
(1)console_init_f是console(控制檯)的第一階段初始化。_f表示是第一階段初始化,_r表示第二階段初始化。有時候初始化函式不能一次一起完成,中間必須要夾雜一些程式碼,因此將完整的一個模組的初始化分成了2個階段。(我們的uboot中start_armboot的826行進行了console_init_r的初始化)
(2)console_init_f在uboot/common/console.c中,僅僅是對gd->have_console設定為1而已,其他事情都沒做。

2.6.8.2、display_banner
(1)display_banner用來串列埠輸出顯示uboot的logo
(2)display_banner中使用printf函式向串列埠輸出了version_string這個字串。那麼上面的分析表示console_init_f並沒有初始化好console怎麼就可以printf了呢?
(3)通過追蹤printf的實現,發現printf->puts,而puts函式中會判斷當前uboot中console有沒有被初始化好。如果console初始化好了則呼叫fputs完成串列埠傳送(這條線才是控制檯);如果console尚未初始化好則會呼叫serial_puts(再呼叫serial_putc直接操作串列埠暫存器進行內容傳送)。
(4)控制檯也是通過串列埠輸出,非控制檯也是通過串列埠輸出。究竟什麼是控制檯?和不用控制檯的區別?實際上分析程式碼會發現,控制檯就是一個用軟體虛擬出來的裝置,這個裝置有一套專用的通訊函式(傳送、接收···),控制檯的通訊函式最終會對映到硬體的通訊函式中來實現。uboot中實際上控制檯的通訊函式是直接對映到硬體串列埠的通訊函式中的,也就是說uboot中用沒用控制器其實並沒有本質差別。
(5)但是在別的體系中,控制檯的通訊函式對映到硬體通訊函式時可以用軟體來做一些中間優化,譬如說緩衝機制。(作業系統中的控制檯都使用了緩衝機制,所以有時候我們printf了內容但是螢幕上並沒有看到輸出資訊,就是因為被緩衝了。我們輸出的資訊只是到了console的buffer中,buffer還沒有被重新整理到硬體輸出裝置上,尤其是在輸出裝置是LCD螢幕時)
(6)U_BOOT_VERSION在uboot原始碼中找不到定義,這個變數實際上是在makefile中定義的,然後在編譯時生成的include/version_autogenerated.h中用一個巨集定義來實現的。

2.6.8.3、print_cpuinfo
(1)uboot啟動過程中:
CPU: [email protected](OK)
APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
MPLL = 667MHz, EPLL = 96MHz
HclkDsys = 166MHz, PclkDsys = 83MHz
HclkPsys = 133MHz, PclkPsys = 66MHz
SCLKA2M = 200MHz
Serial = CLKUART
這些資訊都是print_cpuinfo打印出來的。
(2)回顧ARM裸機中時鐘配置一章的內容,比對這裡呼叫的函式中計算各種時鐘的方法,自己去慢慢分析體會這些程式碼的原理和實現方法。這就是學習。

2.6.9.start_armboot解析7
2.6.9.1、checkboard
(1)checkboard看名字是檢查、確認開發板的意思。這個函式的作用就是檢查當前開發板是哪個開發板並且打印出開發板的名字。
2.6.9.2、init_func_i2c
(1)這個函式實際沒有被執行,X210的uboot中並沒有使用I2C。如果將來我們的開發板要擴充套件I2C來接外接硬體,則在x210_sd.h中配置相應的巨集即可開啟。

2.6.9.3、uboot學習實踐
(1)對uboot原始碼進行完修改(修改內容根據自己的理解和分析來修改)
(2)make distclean然後make x210_sd_config然後make
(3)編譯完成得到u-boot.bin,然後去燒錄。燒錄方法按照裸機第三部分講的linux下使用dd命令來燒寫的方法來燒寫。
(4)燒寫過程:
第一步:進入sd_fusing目錄下
第二步:make clean
第三步:make
第四步:插入sd卡,ls /dev/sd*得到SD卡在ubuntu中的裝置號(一般是/dev/sdb,注意SD卡要連線到虛擬機器ubuntu中,不要接到windows中)
第五步:./sd_fusing.sh /dev/sdb完成燒錄(注意不是sd_fusing2.sh)
(5)總結:uboot就是個龐大點複雜點的裸機程式而已,我們完全可以對他進行除錯。除錯的方法就是按照上面步驟,根據自己對程式碼的分析和理解對程式碼進行更改,然後重新編譯燒錄執行,根據執行結果來學習。

2.6.10.start_armboot解析8
2.6.10.1、dram_init
(1)dram_init看名字是關於DDR的初始化。疑問:在彙編階段已經初始化過DDR了否則也無法relocate到第二部分執行,怎麼在這裡又初始化DDR?
(2)dram_init都是在給gd->bd裡面關於DDR配置部分的全域性變數賦值,讓gd->bd資料記錄下當前開發板的DDR的配置資訊,以便uboot中使用記憶體。
(3)從程式碼來看,其實就是初始化gd->bd->bi_dram這個結構體陣列。

2.6.10.2、display_dram_config
(1)看名字意思就是列印顯示dram的配置資訊。
(2)啟動資訊中的:(DRAM: 512 MB)就是在這個函式中打印出來的。
(3)思考:如何在uboot執行中得知uboot的DDR配置資訊?uboot中有一個命令叫bdinfo,這個命令可以打印出gd->bd中記錄的所有硬體相關的全域性變數的值,因此可以得知DDR的配置資訊。
DRAM bank = 0x00000000
-> start = 0x30000000
-> size = 0x10000000
DRAM bank = 0x00000001
-> start = 0x40000000
-> size = 0x10000000

2.6.10.3、init_sequence總結
(1)都是板級硬體的初始化以及gd、gd->bd中的資料結構的初始化。譬如:
網絡卡初始化、機器碼(gd->bd->bi_arch_number)、核心傳參DDR地址(gd->bd->bi_boot_params)、Timer4初始化為10ms一次、波特率設定(gd->bd->bi_baudrate和gd->baudrate)、console第一階段初始化(gd->have_console設定為1)、列印uboot的啟動資訊、列印cpu相關設定資訊、檢查並列印當前開發板名字、DDR配置資訊初始化(gd->bd->bi_dram)、列印DDR總容量。

總結回顧:本節課結束後已經到了start_armboot的第487行。下節課開始繼續往下看。

2.6.11.start_armboot解析9
2.6.11.1、CFG_NO_FLASH
(1)雖然NandFlash和NorFlash都是Flash,但是一般NandFlash會簡稱為Nand而不是Flash,一般講Flash都是指的Norflash。這裡2行程式碼是Norflash相關的。
(2)flash_init執行的是開發板中對應的NorFlash的初始化、display_flash_config列印的也是NorFlash的配置資訊(Flash: 8 MB就是這裡打印出來的)。但是實際上X210中是沒有Norflash的。所以著兩行程式碼是可以去掉的(我也不知道為什麼沒去掉?猜測原因有可能是去掉著兩行程式碼會導致別的地方工作不正常,需要花時間去移植除錯,然後移植的人就懶得弄。實際上不去掉除了顯示有8MB Flash實際沒用之外也沒有別的影響)

CONFIG_VFD和CONFIG_LCD是顯示相關的,這個是uboot中自帶的LCD顯示的軟體架構。但是實際上我們用LCD而沒有使用uboot中設定的這套軟體架構,我們自己在後面自己添加了一個LCD顯示的部分。

2.6.11.2、mem_malloc_init
(1)mem_malloc_init函式用來初始化uboot的堆管理器。
(2)uboot中自己維護了一段堆記憶體,肯定自己就有一套程式碼來管理這個堆記憶體。有了這些東西uboot中你也可以malloc、free這套機制來申請記憶體和釋放記憶體。我們在DDR記憶體中給堆預留了896KB的記憶體。

2.6.11.3、程式碼實踐,去掉Flash看會不會出錯。
結論:加上CONFIG_NOFLASH巨集之後編譯出錯,說明程式碼移植的不好,那個檔案的包含沒有被這個巨集控制。於是乎移植的人就直接放這沒管。

2.6.12.start_armboot解析10
2.6.12.1、開發板獨有初始化:mmc初始化
(1)從536到768行為開發板獨有的初始化。意思是三星用一套uboot同時滿足了好多個系列型號的開發板,然後在這裡把不同開發板自己獨有的一些初始化寫到了這裡。用#if條件編譯配合CONFIG_xxx巨集來選定特定的開發板。
(2)X210相關的配置在599行到632行。
(3)mmc_initialize看名字就應該是MMC相關的一些基礎的初始化,其實就是用來初始化SoC內部的SD/MMC控制器的。函式在uboot/drivers/mmc/mmc.c裡。
(4)uboot中對硬體的操作(譬如網絡卡、SD卡···)都是借用的linux核心中的驅動來實現的,uboot根目錄底下有個drivers資料夾,這裡面放的全都是從linux核心中移植過來的各種驅動原始檔。
(5)mmc_initialize是具體硬體架構無關的一個MMC初始化函式,所有的使用了這套架構的程式碼都掉用這個函式來完成MMC的初始化。mmc_initialize中再呼叫board_mmc_init和cpu_mmc_init來完成具體的硬體的MMC控制器初始化工作。
(6)cpu_mmc_init在uboot/cpu/s5pc11x/cpu.c中,這裡面又間接的呼叫了drivers/mmc/s3c_mmcxxx.c中的驅動程式碼來初始化硬體MMC控制器。這裡面分層很多,分層的思想一定要有,否則完全就糊塗了。

2.6.13.start_armboot解析11
2.6.13.1、env_relocate
(1)env_relocate是環境變數的重定位,完成從SD卡中將環境變數讀取到DDR中的任務。
(2)環境變數到底從哪裡來?SD卡中有一些(8個)獨立的扇區作為環境變數儲存區域的。但是我們燒錄/部署系統時,我們只是燒錄了uboot分割槽、kernel分割槽和rootfs分割槽,根本不曾燒錄env分割槽。所以當我們燒錄完系統第一次啟動時ENV分割槽是空的,本次啟動uboot嘗試去SD卡的ENV分割槽讀取環境變數時失敗(讀取回來後進行CRC校驗時失敗),我們uboot選擇從uboot內部程式碼中設定的一套預設的環境變量出發來使用(這就是預設環境變數);這套預設的環境變數在本次執行時會被讀取到DDR中的環境變數中,然後被寫入(也可能是你saveenv時寫入,也可能是uboot設計了第一次讀取預設環境變數後就寫入)SD卡的ENV分割槽。然後下次再次開機時uboot就會從SD卡的ENV分割槽讀取環境變數到DDR中,這次讀取就不會失敗了。
(3)真正的從SD卡到DDR中重定位ENV的程式碼是在env_relocate_spec內部的movi_read_env完成的。

2.6.14.start_armboot解析12
2.6.14.1、IP地址、MAC地址的確定
(1)開發板的IP地址是在gd->bd中維護的,來源於環境變數ipaddr。getenv函式用來獲取字串格式的IP地址,然後用string_to_ip將字串格式的IP地址轉成字串格式的點分十進位制格式。
(2)IP地址由4個0-255之間的數字組成,因此一個IP地址在程式中最簡單的儲存方法就是一個unsigend int。但是人類容易看懂的並不是這種型別,而是點分十進位制型別(192.168.1.2)。這兩種型別可以互相轉換。

2.6.14.2、devices_init
(1)devices_init看名字就是裝置的初始化。這裡的裝置指的就是開發板上的硬體裝置。放在這裡初始化的裝置都是驅動裝置,這個函式本來就是從驅動框架中衍生出來的。uboot中很多裝置的驅動是直接移植linux核心的(譬如網絡卡、SD卡),linux核心中的驅動都有相應的裝置初始化函式。linux核心在啟動過程中就有一個devices_init(名字不一定完全對,但是差不多),作用就是集中執行各種硬體驅動的init函式。
(2)uboot的這個函式其實就是從linux核心中移植過來的,它的作用也是去執行所有的從linux核心中繼承來的那些硬體驅動的初始化函式。

2.6.14.3、jumptable_init
(1)jumptable跳轉表,本身是一個函式指標陣列,裡面記錄了很多函式的函式名。看這陣勢是要實現一個函式指標到具體函式的對映關係,將來通過跳轉表中的函式指標就可以執行具體的函式。這個其實就是在用C語言實現面向物件程式設計。在linux核心中有很多這種技巧。
(2)通過分析發現跳轉表只是被賦值從未被引用,因此跳轉表在uboot中根本就沒使用。

2.6.15.start_armboot解析13
2.6.15.1、console_init_r
(1)console_init_f是控制檯的第一階段初始化,console_init_r是第二階段初始化。實際上第一階段初始化並沒有實質性工作,第二階段初始化才進行了實質性工作。
(2)uboot中有很多同名函式,使用SI工具去索引時經常索引到不對的函式處(回憶下當時start.S中找lowlevel_init.S時,自動索引找到的是錯誤的,真正的反而根本沒找到。)
(3)console_init_r就是console的純軟體架構方面的初始化(說白了就是去給console相關的資料結構中填充相應的值),所以屬於純軟體配置型別的初始化。
(4)uboot的console實際上並沒有幹有意義的轉化,它就是直接呼叫的串列埠通訊的函式。所以用不用console實際並沒有什麼分別。(在linux內console就可以提供緩衝機制等不用console不能實現的東西)。

2.6.15.2、enable_interrupts
(1)看名字應該是中斷初始化程式碼。這裡指的是CPSR中總中斷標誌位的使能。
(2)因為我們uboot中沒有使用中斷,因此沒有定義CONFIG_USE_IRQ巨集,因此我們這裡這個函式是個空殼子。
(3)uboot中經常出現一種情況就是根據一個巨集是否定義了來條件編譯決定是否呼叫一個函式內部的程式碼。uboot中有2種解決方案來處理這種情況:方案一:在呼叫函式處使用條件編譯,然後函式體實際完全提供程式碼。方案二:在呼叫函式處直接呼叫,然後在函式體處提供2個函式體,一個是有實體的一個是空殼子,用巨集定義條件編譯來決定實際編譯時編譯哪個函式進去。

2.6.15.3、loadaddr、bootfile兩個環境變數
(1)這兩個環境變數都是核心啟動有關的,在啟動linux核心時會參考這兩個環境變數的值。

2.6.15.4、board_late_init
(1)看名字這個函式就是開發板級別的一些初始化裡比較晚的了,就是晚期初始化。所以晚期就是前面該初始化的都初始化過了,剩下的一些必須放在後面初始化的就在這裡了。側面說明了開發板級別的硬體軟體初始化告一段落了。
(2)對於X210來說,這個函式是空的。

2.6.16.start_armboot解析14
2.6.16.1、eth_initialize
(1)看名字應該是網絡卡相關的初始化。這裡不是SoC與網絡卡晶片連線時SoC這邊的初始化,而是網絡卡晶片本身的一些初始化。
(2)對於X210(DM9000)來說,這個函式是空的。X210的網絡卡初始化在board_init函式中,網絡卡晶片的初始化在驅動中。

2.6.16.2、x210_preboot_init(LCD和logo顯示)
(1)x210開發板在啟動起來之前的一些初始化,以及LCD螢幕上的logo顯示。

2.6.16.3、check menukey to update from sd
(1)uboot啟動的最後階段設計了一個自動更新的功能。就是:我們可以將要升級的映象放到SD卡的固定目錄中,然後開機時在uboot啟動的最後階段檢查升級標誌(是一個按鍵。按鍵中標誌為"LEFT"的那個按鍵,這個按鍵如果按下則表示update mode,如果啟動時未按下則表示boot mode)。如果進入update mode則uboot會自動從SD卡中讀取映象檔案然後燒錄到iNand中;如果進入boot mode則uboot不執行update,直接啟動正常執行。
(2)這種機制能夠幫助我們快速燒錄系統,常用於量產時用SD卡進行系統燒錄部署。

2.6.16.4、死迴圈
(1)解析器
(2)開機倒數自動執行
(3)命令補全

2.6.17.uboot啟動2階段總結
2.6.17.1、啟動流程回顧、重點函式標出
(1)第二階段主要是對開發板級別的硬體、軟體資料結構進行初始化。
(2)
init_sequence
cpu_init 空的
board_init 網絡卡、機器碼、記憶體傳參地址
dm9000_pre_init 網絡卡
gd->bd->bi_arch_number 機器碼
gd->bd->bi_boot_params 記憶體傳參地址
interrupt_init 定時器
env_init
init_baudrate gd資料結構中波特率
serial_init 空的
console_init_f 空的
display_banner 列印啟動資訊
print_cpuinfo 列印CPU時鐘設定資訊
checkboard 檢驗開發板名字
dram_init gd資料結構中DDR資訊
display_dram_config 列印DDR配置資訊表
mem_malloc_init 初始化uboot自己維護的堆管理器的記憶體
mmc_initialize inand/SD卡的SoC控制器和卡的初始化
env_relocate 環境變數重定位
gd->bd->bi_ip_addr gd資料結構賦值
gd->bd->bi_enetaddr gd資料結構賦值
devices_init 空的
jumptable_init 不用關注的
console_init_r 真正的控制檯初始化
enable_interrupts 空的
loadaddr、bootfile 環境變數讀出初始化全域性變數
board_late_init 空的
eth_initialize 空的
x210_preboot_init LCD初始化和顯示logo
check_menu_update_from_sd 檢查自動更新
main_loop 主迴圈

2.6.17.2、啟動過程特徵總結
(1)第一階段為彙編階段、第二階段為C階段
(2)第一階段在SRAM中、第二階段在DRAM中
(3)第一階段注重SoC內部、第二階段注重SoC外部Board內部

2.6.17.3、移植時的注意點
(1)x210_sd.h標頭檔案中的巨集定義
(2)特定硬體的初始化函式位置(譬如網絡卡)