uboot移植準備四


第二部分 start_armboot(void)函數簡介
1. 在uboot/lib_arm/board.c中 從327-798。這不是全部,因爲裏面還調用了別的函數。
2. 爲什麼這麼長的函數,怎麼不分成兩三個函數?主要因爲這個函數整個構成了uboot啓動的第二階段。
3. 宏觀分析:uboot第二階段應該做什麼? 概況來講,uboot第一階段主要是初始化soc內部的一些部件(譬如,看門狗,時鐘,)然後初始化DDR並且完成重定位。 由宏觀分析來講,uboot第二階段就是要初始化剩下的還沒被初始化部件。主要是soc外部硬件(譬如iNand,網卡芯片….)uboot本身的一些東西(uboot命令,uboot環境變量)。然後最終初始化完必須的東西后進入uboot的命令行準備接受命令。
4. Uboot第二階段完結於何處? Uboot啓動後自動運行打印出很多信息(這些信息就是uboot在第一和第二階段不斷進行初始化時,打印出來的信息)。然後uboot進入了倒數bootdelay秒,然後執行bootcmd對應的啓動命令。
5. 如果用戶沒有干涉則會執行bootcmd進入自動啓動內核流程。(uboot就會死掉了);此時用戶可以按下回車鍵打斷uboot的自動啓動進入uboot的命令行下。然後uboot就一直工作在命令行下。
6. Uboot的命令行就是一個死循環,循環體內不斷重複,接收命令,解析命令,執行命令,這就是uboot最終歸宿。

2.6.2 start_armboot(void)
1. init_fnc_t 、
typedef int (init_fnc_t) (void);這是一個函數類型。
Init_fnq_ptr是一個二重函數指針,回顧一下高級c語言中講過;二重指針的作用有2個(其中一個是用來指向一重指針),一個是用來指向指針數組。因此這裏的init_func_ptr指向一個函數指針數組。
3. DECLARE_GLOBAL_DATA_PTR
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8") 定義了一個全局變量名字叫gd,這個全局變量是一個指針類型,佔4字節。用volatile修飾表示可變,用register修飾表示這個變量要儘量放到寄存器中,後面的asm(“r8”)是gcc支持的一種語法,意思就是要把gd放在寄存器r8中。
綜合分析,DECLARE_GLOBAL_DATA_PTR就是定義了一個要放在寄存器r8中的全局變量。名字叫gd,類型是一個指向gd_t類型變量的指針。
爲什麼要定義爲register?因爲這個全局變量(gd)是uboot中很重要的一個全局變量(準確的說這個全局變量是一個結構體,裏面有很多內容,這些內容加起來構成的結構體就是uboot中常用的所有的全局變量),這個gd在程序中經常被訪問,因此放在register中提升效率。因此純粹是運行效率方面的考慮,和功能要求無關。
gd_t定義在include/asm-arm/global_data.h 定義了很多全局變量,都是整個uboot使用的,其中一個bd_t類型的指針,指向一個bd_t類型的變量,這個bd是開發板的板級信息的結構體,裏面有不少硬件相關的參數,譬如波特率,IP地址,機器碼,DDR內存分佈。

2.6.3 內存使用排布
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 爲1040KB
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)。 init_sequence在定義時就同時給了初始化,初始化的函數指針都是一些函數名。 Init_fnc_ptr是一個二重函數指針,可以指向init_sequence這個函數指針數組。 用一個for循環肯定是想要去遍歷這個函數指針數組(遍歷的目的也是依次執行這個函數指針數組中的所有函數),思考:如何遍歷一個函數指針數組?有2種,第一種也是最常用的一種,用下標去遍歷,用數組元素個數來截止。
第二種,不常用,但是也可以,就是在數組的有效元素末尾放一個標誌,一次遍歷到標準出即可截止。(有點類似字符串的思路).我們這裏使用了第二種思路,因爲數組中存的全是函數指針,因此我們選用了NULL來作爲標誌,我們遍歷時從開頭依次進行。
Init_fnc_t的這些函數的返回值定義方式一樣的,都是:函數執行正確是返回0,不正確則就掛起。從分析hang函數可知:uboot啓動過程中初始化板級硬件時不能出任何錯誤,只要有一個錯誤整個啓動就終止,除非重啓開發板沒有任何辦法。
Init_sequence中的這些函數,都是board級別的各種硬件初始化。
2.6.4.2 cpu_init
看名字這個函數應該是cpu內部的初始化。所以是空的。
2.6.4.3 board_init
Board_init是在uboot/board ….. 裏面。 DECLARE_GLOBAL_DATA_PTR在這裏聲明是爲了後面使用gd方便,可以看出把gd的聲明定義成一個宏的原因就是我們要到處去使用gd,因此就要到處聲明,定義成宏比較方便。
網卡初始化。CONFIG_DRIVER_DM9000 這個宏是TQ210.h中定義的,這個宏用來配置開發板的網卡的,dm9000_pre_init()這個函數就是對應的DM9000網卡的初始化函數,開發板移植uboot時,如果要移植網卡,主要的工作就在這裏。 這個函數主要是網卡的GPIO和端口的配置,而不是驅動,因爲網卡的驅動都是現成的正確的,移植的時候驅動時不需要改動的,關鍵是這裏的基本初始化,因爲這些基本初始化是硬件相關的。
2.6.5 start_armboot解析3
2.6.5.1 gd->bd->bi_arch_number = MACH_TYPE;
1)bi_arch_number是board_info中的一個元素,含義是:開發板的機器碼。所謂機器碼就是uboot給這個開發板的一個唯一編號。機器碼的主要作用就是在uboot和內核之間進行比對和適配。嵌入式設備中每一個設備的硬件都是定製化的,不能通用。這就告訴我們,這個開發板移植的內核鏡像絕對不能下載另一個開發板去,否則也不能啓動,就算啓動也不能正常工作,有很多隱患。因此linux做了個設置;給每個開發板做個唯一編號(機器碼)。然後再uboot,linux內核中都有一個軟件維護的機器碼編號。然後開發板,uboot,linux三者比對機器碼,如果機器碼對上了就啓動,否則就不啓動,(因爲軟件認爲我和這個硬件不適配)。 MACH_TYPE在TQ210.h中定義,值是2456,並沒有特殊含義,只是當前開發板對應的編號,這個編號代表了TQ210這個開發板的機器碼,將來這個開發板上面移植的linux內核中的機器碼也必須是2456,否則啓動不起來。 Uboot中配置的這個機器碼,會作爲uboot給linux內核的傳參的一部分傳給linux內核,內核啓動過程中會比對這個接收到的機器碼,和自己本身的機器碼相比對,如果相等就啓動,如果不相等就不啓動。 理論上說,一個開發板的機器碼不能自己隨便定。理論來說有權利發放這個機器碼只有uboot官方,所有我們做好一個開發板並且移植了uboot之後,理論上應該提交給uboot官方審覈併發放機器碼(好像是免費的)。但是國內的開發板基本沒有申請。(主要是因爲國內開發者英文都不行,和國外開源社區接觸比較少)。都是自己隨便編號,隨便編號的問題就是有可能和別人的編號衝退,但是隻要保證uboot和kernel中的編號是一致的,就不影響自己的開發板啓動。

gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
board_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。
經過計算得知:TQ210中bi_boot_params值爲0x20000100,這個內存地址就被分配用來做內核傳參了。所以在uboot的其他地方使用內存時要注意,千萬不敢把;這裏給淹沒了。

背景:DDR的配置。
board_init中除了網卡的初始化外,剩下的2行用來初始化DDR。
注意:這裏的初始化DDR和彙編階段lowlevel_init中初始化DDR是不同的,當時是硬件的初始化;目的是讓DDR可以開始工作。現在是軟件結構中一些DDR相關的屬性配置,地址設置的初始化,是純軟件層面的。 軟件層次初始化DDR的原因,對於uboot來說,他怎麼知道開發板上到底有幾片DDR內存,每一片的起始地址,長度這些信息呢?在uboot的設計中採用了一種簡單直接有效的方式,程序員在移植uboot到一個開發板時,程序員在TQ210.h中使用宏定義去配置出來板子上DDR內存信息,然後uboot只要讀取這些信息即可。(實際上還有另外一條思路:就是uboot通過代碼讀取硬件信息來指導DDR配置,但是uboot沒有這樣。實際上PC的BIOS採用的是這種。)
TQ210.h544行中使用了標準的宏定義來配置DDR相關的參數。主要配置了這麼幾條信息:有幾片DDR內存,每一片DDR的起始地址,長度。這裏的配置信息我們在uboot代碼中使用到內存時就可以從這裏提取使用(想象uboot中使用到內存的地方都不是直接用地址數字的,都是用宏定義的)。
2.6.6 start_armboot解析4
2.6.6.1 interrupt_init 看名字函數是和中斷初始化有關的,但實際上不是,實際上這個函數是用來初始化定時器的(實際使用的是Timer4). 裸機中講過:210共有5個PWM定時器,其中Timer0-Timer3都有一個對應的PWM信號輸出的引腳,而Timer4沒有引腳,無法輸出PWM波形。Timer4在設計的時候就不是用來輸出pwm波形的(沒有引腳,沒有TCMPB寄存器),這個定時器被用來做計時。 Timer4用來做計時時要使用到2個寄存器:TCNTB4 TCNTO4.。 TCNTB中存了一個數,這個數就是定時次數,(每一次時間是由時鐘決定的,其實就是由2級時鐘分頻器決定的)。我們定時是隻需把定時時間/基準時間 = 數,這個數放入TCNTB中即可。我們通過TCNTO寄存器即可讀取時間有沒有減到0,讀取到0後就知道定的時間已經到了。 使用Timer4來定時,因爲沒有中斷支持。所以cpu不能做其他事情同時定時,cpu只能使用輪詢方式來不斷查看TCNTO寄存器才能知道定時時間到了沒。因此Timer4定時不能實現微觀上的並行。Uboot中定時是通過Timer4來定時,所以uboot中定時時不能做其他事。(考慮下,典型的就是bootdelay,bootdelay中實現定時並且檢查用戶輸入用輪詢方式實現的,原來參考裸機中按鍵章節的按鍵)。 Interrupt_init函數將timer4設置定時爲10ms。關鍵部位就是get_PCLK函數獲取系統設置的PCLK_PSYS時鐘頻率,然後設置TCFG0和TCFG1進行分頻,然後計算出設置爲10ms時需要要TCNTB中寫入的值,將其寫入TCNTB,然後設置爲autoreload模式,然後開定時器開始計時就沒了。
總結:在學習這個函數時,注意標準代碼和之前裸機代碼中的區別,重點學會:通過定義結構體的方式訪問寄存器,通過函數來自動計算設置值以設置定時器。

2.6.6.2 env_init
1.env_init,看名字就知道是和環境變量有關的的初始化。 爲什麼有很多env_init函數,主要原因是uboot支持各種不同的啓動介質(譬如norflash,nandflash,inand,sd卡….)我們一般從哪裏啓動就會把環境變量env放到哪裏。而各種介質存取操作env的方法都是不一樣的。因此uboot支持了各種不同介質中env操作方法。所有好多個env_xxx開頭的c文件,實際使用的那一個要根據自己開發板使用的存儲介質來定。(這些env_xxx.c同時只有一個會起作用,其他事不能進去的。通過TQ210.h配置的宏來決定誰被包含的。)對於TQ210來說,我們應該看的是env_movi.c函數。經過基本分析,這個函數只是對內存裏維護的那一份uboot的env做了基本的初始化或者說是判定(判定裏面有沒有能用的環境變量).當前我們還沒有進行環境變量SD卡到DDR中的relocate,因此當前環境變量是不能用的。 在start_armboot函數中調用env_relocate才進行環境變量從SD卡去讀取。

2.6.7 start_armboot解析5
1. init_baudrate看名字就是初始化串口通信的波特率的。
2.getenv_r函數用來讀取環境變量的值,用getenv函數讀取環境變量中的”baudrate”的值(注意讀取到的值是字符串類型),然後用simple_strtoul函數將字符串傳換成數字格式的波特率。
3.baudrate初始化時的規則是:先去環境變量中讀取”baudrate”這個環境變量的值。如果讀取成功則使用這個值作爲環境變量,記錄在gd->baudrate和gd->bd->i_baudrate中;如果讀取不成功則使用TQ210.h中的CONFIG_BAUDRATE的值作爲波特率,

2.6.7.2 srial_init
1.初始化串口(疑問:start.S調用了lowlevel_init.S中已經使用匯編初始化過串口了,這裏怎麼又初始化?這兩個初始化是重複的還是各自有不同?)
2.SI中可以看出uboot中有很多個derial_init函數,我們使用uboot/cpu/s5pc11x/serial.c中的serial_init函數。
2.進來後發現serial_init函數其實什麼也沒做。因爲在彙編階段串口已經被初始化過了,因此這裏就不再進行硬件寄存器的初始化了。

2.6.8 start_armboot解析6
2.6.8.1 console_init_f
Console_init_f是console控制檯的第一階段初始化,f表示是第一階段初始化,r表示第二階段初始化,有時候初始化函數不能一次一起完成,中間必須要夾雜一些代碼,因此將完整的一個模塊的初始化分成了2個階段。(我們的uboot中start_armboot的734行進行了console_init_r的初始化。)
Console_init_f 在uboot/common/console.c中,僅僅是對gd->have_console = 1;

2.6.8.2 display_banner函數
1.dispaly_banner用來串口輸出顯示uboot的logo
2.display_banner使用printf函數向串口輸出了version_string這個字符串,那麼上面的分析表示console_init_f並沒有初始化號console怎麼就可以printf了呢?
3.通過追蹤printf的實現,發現printf->pusts,而puts函數中會判斷當前uboot中console有沒有初始化好,如果console初始化好了則調用fputs完成串口發送;如果console尚未初始化好則會調用serial_puts(在調用serial_putc直接操作串口寄存器進行內容發送)。
4.控制檯也是通過串口輸出,非控制檯也是通過串口輸出,究竟什麼是控制檯,和不用控制檯有什麼區別?實際上分析代碼會發現,控制檯是用軟件虛擬出來的設備,這個設備有一套專用的通信函數(發送 接收 …),控制檯的通信函數最終會映射到硬件的通信函數中來實現。Uboot中實際上控制檯的通信函數是直接映射到硬件串口的通信函數中的,也就是說uboot中用沒用控制器其實並沒有本質差別。 但是在別的體系中,控制檯的通信函數映射到硬件通信函數時可以用軟件來做一些中間優化,譬如說緩衝機制。操作系統中的控制檯都使用了緩衝機制,所有有時候我們printf了內容,但是屏幕上並沒有看到輸出信息,就是因爲被緩衝了,我們輸出的信息只是到了console的buffer中,buffer還沒有被刷新到硬件輸出設備上,尤其在輸出設備的LCD屏幕時。
6.U_BOOT_VERSION在uboot源代碼中找不到定義。這個變量實際上是在makefile中定義的,然後再編譯時生成的include/autogenerated.h中用一個宏定義來實現的。

2.6.8.3 print_cpuinfo
1.uboot啓動過程中:

這些信息都是pirnt_cpuinfo打印出來的。
2.回顧ARM裸機中時鐘配置一章的內容,比對這裏調用的函數中計算各種時鐘的方法,自己慢慢分析體會這些代碼的原理和實現方法,這就是學習。
2.6.9 start_armboot解析7
1. checkboard 看名字是檢查,確認開發板的意思。這個函數的作用就是檢查當前開發板時哪個開發板並且打印出開發板的名字。
2.init_func_i2c 這個函數實際沒有被執行。如果將來我們的開發板要擴展I2C來接硬件,則在下TQ210.h中配置相應的宏即可開啓。

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

2.6.10 start_armboot解析8
2.6.10.1 dram_init:看名字是關於DDR的初始化,疑問:在彙編階段已經初始化DDR了否則也無法relocate到第二部分運行,怎麼在這裏又初始化DDR?
Dram_init都是在給gd->bd裏面關於DDR配置部分的全局變量賦值,讓gd->bd數據記錄下當前開發板的DDR的配置信息,以便uboot中使用內存。
從我們代碼來看,其實就是初始化gd->bd->bi_dram這個結構體數組。
2.6.10.2 display_dram_config
1.看名字意思就是打印顯示dram的配置信息。啓動信息中的: (DRAM: 1G)就是在這個函數中打印出來的。 思考:如何在uboot運行中得知uboot的DDR配置信息?uboot中有一個命令叫bdinfo,這個命令可以打印出gd->bd中記錄的所有硬件相關的全局變量的值,因此可以得知DDR的配置信息。

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總容量。

2.6.11 start_armboot解析9
1.雖然NandFlash和NorFlash都是flash,但是一般NandFlash會簡稱爲NandFlash爲Nand而不是Flash,一般講Flash都是指的NorFlash。這裏2行代碼是Norflash相關的。
2.flash_init執行的是開發板中對應Norflash的初始化,display_flash_config打印的也是NorFlash的配置信息 Flash: 8 MB就是這裏打印出來的。但是實際上TQ210中是沒有Norflash的。所以這兩行代碼可以去掉的。(我也不知道爲什麼沒去掉?猜測原因有可能是去掉這兩行代碼導致別的地方工作不正常,需要花時間去移植調試,實際上不去掉除了顯示有8MB flash實際沒有之外也沒有別的影響。)
CONFIG_VFD是顯示相關的,這個是uboot中自帶的LCD顯示相關的,這個是uboot中自帶的LCD顯示的軟件架構。但是實際上我們用LCD而沒有使用uboot中設置這套軟件架構,我們自己在後面自己添加了一個LCD顯示的部分。
2.6.11.2 mem_malloc_init
1.這個函數用來初始化uboot的堆管理器。
2.uboot中自己維護了一段堆內存,肯定自己就有一套代碼來管理這個堆內存。有了這些東西uboot中你也可以malloc,free這套機制來申請內存和釋放內存。我們DDR內存中給堆預留了896KB的內存。
2.6.11.3 代碼實踐,去掉FLASH行不行。
結論:加上CONFIG_NOFLASH宏之後編譯出錯,說明代碼移植的不好,那個文件的包含沒有被這個宏控制,於是乎移植的人就直接放着沒管。
2.6.12 start_armboot解析10
1.開發板獨有的初始化:mmc初始化
從421行開始,開發板獨有的初始化。意思是三星用一套uboot同時滿足了好多個系列型號的開發板,然後再這裏把不同開發板自己獨有的一些初始化寫到了這裏。用#if條件編譯配合CONFIG_XXX宏來選定特定的開發板。
TQ210從510行,是它相關的配置。 mmc_initialize看名字就應該是MMC相關的一些基礎的初始化,其實就是用來初始化SOC內部的SD/MMC控制器的。函數在uboot/drivers/mmc.c裏面的。
Uboot中對硬件的操作(譬如網卡,SD卡)都是借用linux內核中的驅動來實現的,uboot根目錄底下有個drivers文件夾都是從linux內核中移植過來的各種驅動源文件。
Mmc_initialize是具體硬件架構無關的一個MMC初始化函數,所有的使用了這套架構的代碼都調用了這個函數完成MMC初始化。Mmc調用了board_mmc_init()和cpu_mmc_init(),來完成具體的硬件的MMC控制器初始化工作。
cpu_mmc_init在/uboot/cpu/s5pc11x/cpu.c中,在這裏面又間接調用了drivers/mmc/s3c_mmcxxx.c中的驅動代碼來初始化硬件MMC控制器。這裏面分層很多,分層的思想一定要有,否則完全就糊塗了。
2.6.13 start_armboot解析11
1.env_relocate是環境變量的重定位,完成從SD卡中將環境變量讀取到DDR中的任務。
2.環境變量到底從哪裏來?SD卡中有一些(8個)獨立的扇區作爲環境變量存儲區域的。但是我們燒錄/部署系統時,我們只是燒錄了uboot分區,kernel分區和rootfs分區,根本不曾燒錄env分區,所有當我們燒錄完系統第一次啓動時ENV分區是空的,本次啓動uboot嘗試去SD卡的ENV分區讀取環境變量時失敗(讀取回來後進行CRC校驗時失敗),我們uboot選擇從uboot內部代碼中設置的一套默認的環境變量出發來使用(這就是默認的環境變量)。這套默認的環境變量的本次運行時會被讀取到DDR中的環境變量中,然後被寫入(也可能是你savenv時寫入,也可能是uboot設計了第一次讀取默認環境變量後就寫入)SD卡的ENV分區。然後下次再次開機時uboot就會從SD卡的ENV分區讀取環境變量到DDR中,這就是
真正的從SD卡到ddr中重定位ENV的代碼是在env_relocate_spec內部的movi_read_env完成的。

2.6.14 start_armboot解析12
1.IP地址、MAC地址確定 開發板的ip地址是在gd->bd維護的,來源於環境變量。Getenv函數用來獲取字符串格式的IP地址。然後用string_to_ip將字符串格式的IP地址轉成字符串格式的點分十進制格式。
2.IP地址由4個0-255之間的數字組成,因此一個IP地址在程序中最簡單的存儲方法就是一個unsigend int。但是人類容易看懂的並不是這種類型,而是點分十進制類型。這兩種類型可以互相轉換。
2.devices_init
看名字就是設備的初始化,這裏的設備指的就是開發板上的硬件設備。放在這裏初始化的設備都是驅動設備,這個函數本來就是從驅動框架中衍生出來的。Uboot中很多設備的驅動時直接移植linux內核(譬如網卡,SD卡),linux內核中的驅動都有相應的設備初始化函數。Linux內核在啓動過程中就有一個devices_init(名字不一定完全對,但是差不多),作用就是集中執行各種硬件驅動的init函數。
Uboot的這個函數其實就是從Linux內核移植過來的,它的作用也是去執行所有的從linux內核中繼承

4. Jumptable_init jumptable跳轉表,本身是一個函數指針數組,裏面記錄了很多函數的函數名。看這陣勢是要實現一個函數指針到具體函數的映射關係,將來通過跳轉表中的函數指針就可以執行具體的函數。這個其實就是在用c語言實現面向對象編程。在linux內核中有很多這種技巧。 通過分析發現跳轉表只是被賦值從未被引用,因此在uboot中沒有用到。

2.6.14 start_armboot解析13
1.console_init_r console_init_f是控制檯的第一階段初始化,console_init_r是第二階段初始化。第一階段並沒有做實質性東西,第二階段初始化纔開始實質性東西。
2.uboot中有很多同名函數,使用SI工具去索引到不對的函數出。自動索引到時錯誤的,真正的反而根本沒找到。
3.console_init_r就是console的純軟件架構方面的初始化。(說白了就是去給console相關的數據結構中填充相應的值),所以屬於純軟件配置類型的初始化。
4.uboot的console實際上並沒有幹什麼意義的轉化,它就是直接調用的串口通信函數。所以用不用console實際並沒有什麼分別。

5. enable_interrupts 看名字應該是中斷初始化代碼。這裏指的是cpsr中總中斷標誌位的使能。因爲我們uboot中沒有使用中斷,因此沒有定義CONFIG_USE_IRQ宏,因此我們這裏這個函數是空殼子。
6. uboot中經常出現一種情況就是根據一個宏是否定義了來條件編譯決定是否調用一個函數內部的代碼。Uboot中有2中解決方案來處理這種情況:方案一:在調用函數處使用條件編譯,然後函數體實際完全提供代碼。方案二;在調用函數處直接調用,然後再函數體處提供2個函數體,一個有實體的一個是空殼子。
2.6.15.3 loadaddr bootfile兩個環境變量
這兩個環境變量都是內核啓動有關的,在啓動linux內核時會參考這兩個環境變量的值。
2.6.15.4 board_late_init 看名字這個函數就是開發板級別的一些初始化裏比較晚的了,就是晚期初始化。所有晚期就是前面該初始化的都初始化過了,剩下的一些必須放在後面初始化的就在這裏了。側面說明了開發板級別的硬件軟件初始化告一段落了。。對於TQ210來說這個函數是空的。

2.6.16 start_armboot解析
1.eth_initialize 看名字應該是網卡相關的初始化,這裏不是soc與網卡芯片鏈接時soc這邊的初始化,而是網卡芯片本身的一些初始化。 對於TQ210來說,這個函數是空的。


第三部分 隨堂記錄
1.uboot和內核到底是什麼 uboot是一個裸機程序。
Uboot的本質就是一個複雜點的裸機程序。和我們在ARM裸機全集中學習的每個裸機程序並沒有本質區別。
ARM裸機第十六部分寫了個簡單的shell,這東西其實就是個mini型的uboot。
2. 內核本身也是一個“裸機程序”
操作系統內核本身就是一個裸機程序,和uboot,和其他裸機程序並沒有本質區別。
區別就是操作系統運行起來後在軟件上分爲內核層和應用層,分層後兩層的權限不同,內存訪問和設備操作的管理上更加精細(內核可以隨便訪問各種硬件,而應用程序只能限制的訪問硬件和內存地址)。
直觀來看,uboot的鏡像是u-boot.bin。linux系統的鏡像是zImage,這兩個東西其實都是兩個裸機程序鏡像。從系統啓動角度來講,內核其實就是一個大的複雜點裸機程序。

2.7.1.3 部署在SD卡中特定分區內
一個完整的軟件+硬件的嵌入式系統,靜止時bootloader,kernel,rootfs等必須的軟件都以鏡像的形式存儲在啓動介質中;運行時都是DDR內存中運行的,與存儲介質無關。上面2個狀態都是穩定的,第3個狀態是動態過程,即從靜止態到運行態的過程,也就是啓動過程。
動態的啓動過程就是一個從SD卡逐步搬移到DDR內存,並且運行啓動代碼進行相關硬件初始化和軟件架構的建立,最終達到運行時穩定狀態。
靜止時u-boot.bin zImage roots 都在SD卡中,它們不可能隨意存在SD卡任意位置,因此需要對SD卡進行分區,然後將各種鏡像各自存在各自的分區中,這樣在啓動過程中uboot,內核等就知道到哪裏去找誰。(uboot和kernel中的分區表必須一致),同時和SD卡的實際使用分區要一致。

2.7.1.4 運行時必須先加載到DDR中鏈接地址處
Uboot在第一階段中進行重定位時將第二階段(整個uboot鏡像)加載到DDR的0XC3E00000地址處,這個地址就是uboot的鏈接地址。內核也有類似的要求,uboot啓動內核時將內存從SD卡讀取放到DDR中(其實就是重定位的過程),不能隨意放置,必須放在內核的鏈接地址處,否則啓動不起來。譬如我們使用的內核鏈接地址爲0x20008000.

2.7.1.5 內核啓動需要必須的啓動參數
1. uboot是無條件啓動的,從零開始啓動的。
2. 內核時不能開機自動完全從零開始啓動的,內核啓動要別人幫忙,uboot要幫助內核實現重定位(從SD卡到DDR),uboot還要給內核提供啓動參數。
2.7.2 啓動內核第一步,加載內核到DDR中。
Uboot要啓動內核,分爲2個步驟,第一步是將內核鏡像從啓動介質中加載到DDR中,第二步是去DDR中啓動內核鏡像。
(內核代碼根本就沒有考慮重定位,因爲內核知道會有uboot之類的把自己加載到ddr中鏈接地址處的,所以內核直接就是從鏈接地址處開始運行的)

2.7.2.1 靜態內核鏡像在哪裏?
1. SD卡/inand /nand/norflash等:raw分區
常規啓動時各種鏡像都在SD卡中,因此uboot只需要從SD卡的kernel分區讀取內核鏡像到DDR中即可。讀取要使用uboot的命令來讀取(譬如TQ210的iNand版本是nand命令,)。
2. 這種啓動方式來加載ddr,使用命令 nand read kernel 30008000. 其中kernel指的是uboot中的kernel分區(就是uboot中規定的SD卡中的一個區域範圍,這個範圍被設計來存放kernel分區)。
3. Tftp nfs 等網絡下載方式從遠端服務器獲取鏡像
Uboot還支持遠程啓動,也就是內核鏡像不燒錄到開發板的SD卡中,而是放在主機的服務器中,然後需要啓動時uboot通過網絡從服務器中下載鏡像到開發板的ddr中。

分析總結:最終結果要的是內核鏡像到ddr中特定地址即可,不管內核鏡像是怎麼到ddr中的,以上2中方式各有優勢,產品出廠時會設置爲從SD卡中啓動,tftp下載遠程啓動這種方式一般用來開發
發佈了59 篇原創文章 · 獲贊 5 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章