Linux-uboot-學習筆記(7):uboot啓動第二階段源碼分析

Linux-uboot-學習筆記(7):uboot啓動第二階段源碼分析

uboot啓動的第二階段主要是執行第一階段跳轉到的start_armboot函數,該BL2階段在DDR中初始化第一階段未完成的任務和SoC各種外設。

start_armboot函數分析

start_armboot函數位於Board.c文件中,從文件名可以看出,該函數主要針對板級初始化的。

定義全局變量數據結構體(70)

在這裏插入圖片描述
定義了一個全局變量名字叫gd,這個全局變量是一個指針類型,佔4字節。用volatile修飾表示可變的,用register修飾表示這個變量要儘量放到寄存器中(因爲這些內容加起來構成的結構體就是uboot中常用的所有的全局變量,放到寄存器中提升效率),後面的asm(“r8”) 是gcc支持的一種語法,意思就是要把gd放到寄存器r8中
DECLARE_GLOBAL_DATA_PTR就是定義了一個要放在寄存器r8中的全局變量,名字叫gd,類型是一個指向gd_t類型變量的指針。

內存使用排布(462-472)

DECLARE_GLOBAL_DATA_PTR只能定義了一個指針,也就是說gd裏的這些全局變量並沒有被分配內存,我們在使用gd之前要給他分配內存,否則gd也只是一個野指針而已。
gd和bd需要內存,內存當前沒有被人管理(因爲沒有操作系統統一管理內存),大片的DDR內存散放着可以隨意使用(只要使用內存地址直接去訪問內存即可)。但是因爲uboot中後續很多操作還需要大片的連着內存塊,因此這裏使用內存要本着夠用就好,緊湊排布的原則。所以我們在uboot中需要有一個整體規劃。
在這裏插入圖片描述
uboot區 CFG_UBOOT_BASE-xx(長度xx爲uboot的實際長度)
堆區 長度爲CFG_MALLOC_LEN,實際爲912KB
棧區 長度爲CFG_STACK_SIZE,實際爲512KB
gd 長度爲sizeof(gd_t),實際36字節
bd 長度爲sizeof(bd_t),實際爲44字節左右

通過init_fnc_ptr對函數指針數組進行訪問(446,483-487)

在這裏插入圖片描述
init_sequence是一個函數指針數組,數組中存儲了很多個函數指針,這些指針指向的函數都是init_fnc_t類型(特徵是接收參數是void類型,返回值是int)。
init_fnc_ptr是一個二重函數指針,可以指向init_sequence這個函數指針數組。
用for循環肯定是想要去遍歷這個函數指針數組(遍歷的目的也是去依次執行這個函數指針數組中的所有函數)。我們採用了一種特殊的遍歷方法。因爲數組中存的全是函數指針,因此我們選用了NULL來作爲標誌。我們遍歷時從開頭依次進行,直到看到NULL標誌截至。這種方法的優勢是不用事先統計數組有多少個元素。
在這裏插入圖片描述
init_fnc_t的這些函數的返回值定義方式一樣的,都是:函數執行正確時返回0,不正確時返回-1。所以我們在遍歷時去檢查函數返回值,如果遍歷中有一個函數返回值不等於0則hang()掛起。從分析hang函數可知:uboot啓動過程中初始化板級硬件時不能出任何錯誤,只要有一個錯誤整個啓動就終止,除了重啓開發板沒有任何辦法。

init_sequence數組內部函數分析(416-442)

(1)cpu_init [cpu內部初始化]
在這裏插入圖片描述
cpu內部的初始化,所以這裏是空的,cpu相關初始化在start.S中都結束了。

(2)board_init [開發板相關初始化]
在這裏插入圖片描述
bi_arch_number是board_info中的一個元素,含義是:開發板的機器碼。 所謂機器碼就是uboot給這個開發板定義的一個唯一編號。機器碼的主要作用就是在uboot和linux內核之間進行比對和適配。
嵌入式設備中每一個設備的硬件都是定製化的,不能通用。嵌入式設備的高度定製化導致硬件和軟件不能隨便適配使用。這就告訴我們:這個開發板移植的內核鏡像絕對不能下載到另一個開發板去,否則也不能啓動,就算啓動也不能正常工作,有很多隱患。因此linux做了個設置:給每個開發板做個唯一編號(機器碼),然後在uboot、linux內核中都有一個軟件維護的機器碼編號。然後開發板、uboot、linux三者去比對機器碼,如果機器碼對上了就啓動,否則就不啓動(因爲軟件認爲我和這個硬件不適配)。
uboot中配置的這個機器碼,會作爲uboot給linux內核的傳參的一部分傳給linux內核,內核啓動過程中會比對這個接收到的機器碼,和自己本身的機器碼相對比,如果相等就啓動,如果不相等就不啓動。

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。
經過計算得知:X210中bi_boot_params的值爲0x30000100,這個內存地址就被分配用來做內核傳參了。所以在uboot的其他地方使用內存時要注意。

(3)interrupt_init [初始化定時器]
在這裏插入圖片描述
interrupt_init函數將timer4設置爲定時10ms。關鍵部位就是get_PCLK函數獲取系統設置的PCLK_PSYS時鐘頻率,然後設置TCFG0和TCFG1進行分頻,然後計算出設置爲10ms時需要向TCNTB中寫入的值,將其寫入TCNTB,然後設置爲auto reload模式,然後開定時器開始計時就沒了。

(4)env_init [初始化環境變量]
在這裏插入圖片描述
經過基本分析,這個函數只是對內存裏維護的那一份uboot的env做了基本的初始化或者說是判定(判定裏面有沒有能用的環境變量)。當前因爲我們還沒進行環境變量從SD卡到DDR中的relocate,因此當前環境變量是不能用的。
在start_armboot函數中(776行)調用env_relocate才進行環境變量從SD卡中到DDR中的重定位。重定位之後需要環境變量時纔可以從DDR中去取,重定位之前如果要使用環境變量只能從SD卡中去讀取。

(5)init_baudrate [初始化波特率]
在這裏插入圖片描述
getenv_r函數用來讀取環境變量的值。用getenv函數讀取環境變量中“baudrate”的值(注意讀取到的不是int型而是字符串類型),然後用simple_strtoul函數將字符串轉成數字格式的波特率
baudrate初始化時的規則是:先去環境變量中讀取"baudrate"這個環境變量的值。如果讀取成功則使用這個值作爲環境變量,記錄在gd->baudrate和gd->bd->bi_baudrate中;如果讀取不成功則使用x210_sd.h中的的CONFIG_BAUDRATE的值作爲波特率。從這可以看出:環境變量的優先級是很高的。

(6)serial_init [初始化串口]
在這裏插入圖片描述
進來後發現serial_init函數其實什麼都沒做。因爲在彙編階段串口已經被初始化過了,因此這裏就不再進行硬件寄存器的初始化了。

(7)console_init_f [控制檯初始化第一階段]
在這裏插入圖片描述
console_init_f在uboot/common/console.c中,僅僅是對gd->have_console設置爲1而已,其他事情都沒做。

(8)display_banner [串口顯示LOGO]
在這裏插入圖片描述
在這裏插入圖片描述
通過追蹤printf的實現,發現printf->puts,而puts函數中會判斷當前uboot中console有沒有被初始化好。如果console初始化好了則調用fputs完成串口發送(這條線纔是控制檯);如果console尚未初始化好則會調用serial_puts(再調用serial_putc直接操作串口寄存器進行內容發送)。
控制檯也是通過串口輸出,非控制檯也是通過串口輸出。究竟什麼是控制檯?和不用控制檯的區別?實際上分析代碼會發現,控制檯就是一個用軟件虛擬出來的設備,這個設備有一套專用的通信函數(發送、接收···),控制檯的通信函數最終會映射到硬件的通信函數中來實現。uboot中實際上控制檯的通信函數是直接映射到硬件串口的通信函數中的,也就是說uboot中用沒用控制器其實並沒有本質差別。

(9)print_cpuinfo [打印CPU信息]
在這裏插入圖片描述
通過宏定義的方式能夠直接對CPU的一整套時鐘進行設置,而print_cpuinfo即可將這些時鐘信息打印出來。
在這裏插入圖片描述
(10)checkboard [檢查開發板]
檢查、確認開發板的意思。這個函數的作用就是檢查當前開發板是哪個開發板並且打印出開發板的名字。

(11)init_func_i2c [初始化I2C]
這個函數實際沒有被執行,X210的uboot中並沒有使用I2C。如果將來我們的開發板要擴展I2C來接外接硬件,則在x210_sd.h中配置相應的宏即可開啓。

(12)dram_init [初始化DDR]
在這裏插入圖片描述
dram_init都是在給gd->bd裏面關於DDR配置部分的全局變量賦值,讓gd->bd數據記錄下當前開發板的DDR的配置信息,以便uboot中使用內存。
從代碼來看,其實就是初始化gd->bd->bi_dram這個結構體數組,通過基地址+內存大小的方式,即可指定內存的起始位置。

(13)display_dram_config [打印DRAM配置信息]
在這裏插入圖片描述

init_sequence總結:
網卡初始化、機器碼(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總容量。

mem_malloc_init初始化堆內存(525-529)

在這裏插入圖片描述
mem_malloc_init函數用來初始化uboot的堆管理器。uboot中自己維護了一段堆內存,肯定自己就有一套代碼來管理這個堆內存。有了這些東西uboot中你也可以malloc、free這套機制來申請內存和釋放內存。我們在DDR內存中給堆預留了896KB的內存。
start堆起始,end堆結束,CFG)MALLOC_LEN是896K預留的堆內存大小。

mmc_initialize初始化MMC(599-632)

在這裏插入圖片描述
mmc_initialize是MMC相關的一些基礎的初始化,其實就是用來初始化SoC內部的SD/MMC控制器的。函數在uboot/drivers/mmc/mmc.c裏。
uboot中對硬件的操作(譬如網卡、SD卡···)都是借用的linux內核中的驅動來實現的,uboot根目錄底下有個drivers文件夾,這裏面放的全都是從linux內核中移植過來的各種驅動源文件。
mmc_initialize是具體硬件架構無關的一個MMC初始化函數,所有的使用了這套架構的代碼都掉用這個函數來完成MMC的初始化。mmc_initialize中再調用board_mmc_init和cpu_mmc_init來完成具體的硬件的MMC控制器初始化工作
cpu_mmc_init在uboot/cpu/s5pc11x/cpu.c中,這裏面又間接的調用了drivers/mmc/s3c_mmcxxx.c中的驅動代碼來初始化硬件MMC控制器。

env_relocate環境變量重定位(776)

在這裏插入圖片描述
env_relocate是環境變量的重定位,完成從SD卡中將環境變量讀取到DDR中的任務。
環境變量到底從哪裏來?SD卡中有一些(8個)獨立的扇區作爲環境變量存儲區域的。但是我們燒錄/部署系統時,我們只是燒錄了uboot分區、kernel分區和rootfs分區,根本不曾燒錄env分區。所以當我們燒錄完系統第一次啓動時ENV分區是空的,本次啓動uboot嘗試去SD卡的ENV分區讀取環境變量時失敗(讀取回來後進行CRC校驗時失敗),我們uboot選擇從uboot內部代碼中設置的一套默認的環境變量出發來使用(這就是默認環境變量);這套默認的環境變量在本次運行時會被讀取到DDR中的環境變量中,然後被寫入(也可能是你saveenv時寫入,也可能是uboot設計了第一次讀取默認環境變量後就寫入)SD卡的ENV分區。然後下次再次開機時uboot就會從SD卡的ENV分區讀取環境變量到DDR中,這次讀取就不會失敗了。
真正的從SD卡到DDR中重定位ENV的代碼是在env_relocate_spec內部的movi_read_env完成的。
最後將env_ptr中的內容賦給gd->env_addr,完成重定位。

設置IP地址(788)

在這裏插入圖片描述
(1)開發板的IP地址是在gd->bd中維護的,來源於環境變量ipaddr。getenv函數用來獲取字符串格式的IP地址,然後用string_to_ip將字符串格式的IP地址轉成字符串格式的點分十進制格式。
IP地址由4個0-255之間的數字組成,因此一個IP地址在程序中最簡單的存儲方法就是一個unsigend int。但是人類容易看懂的並不是這種類型,而是點分十進制類型(192.168.1.2)。這兩種類型可以互相轉換。
例如在用戶看的時候爲:192.168.1.2 ;在存儲時爲0xC0AB0102。

設備初始化(817)

在這裏插入圖片描述
這裏的設備指的就是開發板上的硬件設備。放在這裏初始化的設備都是驅動設備,這個函數本來就是從驅動框架中衍生出來的。uboot中很多設備的驅動是直接移植linux內核的(譬如網卡、SD卡),linux內核中的驅動都有相應的設備初始化函數。linux內核在啓動過程中就有一個devices_init(名字不一定完全對,但是差不多),作用就是集中執行各種硬件驅動的init函數。
uboot的這個函數其實就是從linux內核中移植過來的,它的作用也是去執行所有的從linux內核中繼承來的那些硬件驅動的初始化函數

跳轉表(824)

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

控制檯第二階段初始化(826)

在這裏插入圖片描述
console_init_f是控制檯的第一階段初始化,console_init_r是第二階段初始化。
console_init_r就是console的純軟件架構方面的初始化(說白了就是去給console相關的數據結構中填充相應的值),所以屬於純軟件配置類型的初始化。
uboot的console實際上並沒有幹有意義的轉化,它就是直接調用的串口通信的函數。所以用不用console實際並沒有什麼分別。(在linux內console就可以提供緩衝機制等不用console不能實現的東西)。

中斷初始化(835)

在這裏插入圖片描述
這裏指的是CPSR中總中斷標誌位的使能。因爲我們uboot中沒有使用中斷,因此沒有定義CONFIG_USE_IRQ宏,因此我們這裏這個函數是個空殼子。
uboot中經常出現一種情況就是根據一個宏是否定義了來條件編譯決定是否調用一個函數內部的代碼。uboot中有2種解決方案來處理這種情況:方案一:在調用函數處使用條件編譯,然後函數體實際完全提供代碼。方案二:在調用函數處直接調用,然後在函數體處提供2個函數體,一個是有實體的一個是空殼子,用宏定義條件編譯來決定實際編譯時編譯哪個函數進去。

初始化兩個環境變量(856-862)

在這裏插入圖片描述
這兩個環境變量都是內核啓動有關的,在啓動linux內核時會參考這兩個環境變量的值。

最後的一些初始化(866)

在這裏插入圖片描述
看名字這個函數就是開發板級別的一些初始化裏比較晚的了,就是晚期初始化。所以晚期就是前面該初始化的都初始化過了,剩下的一些必須放在後面初始化的就在這裏了。側面說明了開發板級別的硬件軟件初始化告一段落了。(但在這裏是空的,因爲沒有其餘需要初始化的)

eth_initialize [網卡初始化](872)

網卡相關的初始化。這裏不是SoC與網卡芯片連接時SoC這邊的初始化,而是網卡芯片本身的一些初始化
對於X210(DM9000)來說,這個函數是空的。X210的網卡初始化在board_init函數中,網卡芯片的初始化在驅動中。

x210_preboot_init [LOGO顯示](887)

LCD屏幕上的logo顯示。

檢查自動更新(892-900)

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

死循環(903-905)

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

總結start_armboot函數都做了什麼?

	定義全局變量數據結構體(70)
	init_sequence		函數指針數組(416-442)
		cpu_init		cpu內部初始化,空的
		board_init		開發板相關初始化
			dm9000_pre_init			網卡
			gd->bd->bi_arch_number	機器碼
			gd->bd->bi_boot_params	內存傳參地址
		interrupt_init	初始化定時器
		env_init		初始化環境變量
		init_baudrate	初始化波特率
		serial_init		初始化串口,空的
		console_init_f	初始化控制檯第一階段,空的
		display_banner	串口顯示LOGO
		print_cpuinfo	打印CPU時鐘設置信息
		checkboard		檢驗開發板名字
		dram_init		gd數據結構中DDR信息
		display_dram_config	打印DDR配置信息表
	mem_malloc_init		初始化uboot自己維護的堆管理器的內存(525-529)
	mmc_initialize		inand/SD卡的SoC控制器和卡的初始化(599-632)
	env_relocate		環境變量重定位(776)
	gd->bd->bi_ip_addr	gd數據結構賦值(788)
	gd->bd->bi_enetaddr	gd數據結構賦值
	devices_init		設備初始化,空的(817)
	jumptable_init		跳轉表(824)
	console_init_r		真正的控制檯初始化(826)
	enable_interrupts	中斷初始化,空的(835)
	loadaddr、bootfile 	環境變量讀出初始化全局變量(856-862)
	board_late_init		最後的初始化,空的(866)
	eth_initialize		網卡初始化,空的(872)
	x210_preboot_init	LCD初始化和顯示logo(887)
	check_menu_update_from_sd	檢查自動更新(892-900)
	main_loop			主循環(903-905)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章