日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

S5PV210-uboot源码分析-第二阶段

發(fā)布時間:2023/12/20 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 S5PV210-uboot源码分析-第二阶段 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.


由第一階段,已經(jīng)將在SD卡的整個uboot重定位拷貝到了DDR的鏈接地址中去后,又回到了調(diào)用這個函數(shù)的位置

。接著往下執(zhí)行遇到了這個start_armboot函數(shù)(uboot啟動的第二階段)。


uboot啟動的第二階段分析:


6、1、start_armboot函數(shù)

(1)一個很長的函數(shù),從444行到908行。這個函數(shù)不僅僅只有這么長,這個函數(shù)的內(nèi)部還調(diào)用了一些函數(shù),所以說這個函數(shù)是很龐大的。這個函數(shù)構(gòu)成了整個uboot啟動的第二個階段。

(2)宏觀上分析,uboot啟動的第二階段應(yīng)該做什么?

(3)uboot的第一階段主要完成的任務(wù)就是,初始化SOC內(nèi)部的一些部件(關(guān)看門狗,初始化時鐘等),也初始化了DDR并且將整個uboot重定位到了DDR中去。

(4)所以uboot的第二階段應(yīng)該要做的就是,去初始化一些在uboot第一階段沒有初始化的硬件,主要是初始化SOC外部的硬件(,如iNand、網(wǎng)卡芯片····),還有uboot本身的東西(uboot的命令,環(huán)境變量等·····),然后最終完成這些任務(wù)時,進(jìn)入到uboot的命令行,準(zhǔn)備接受命令,人機(jī)開始交互。

(5)uboot啟動時是開機(jī)自動啟動的,在啟動的過程中,會打印出來很多的信息(這些信息就是uboot在第一階段和第二階段不斷進(jìn)行初始化時,打印出來的信息,我通過這些信息也可以跟蹤uboot的軌跡,看uboot執(zhí)行是否成功運(yùn)行。)然后uboot開始bootdelay倒數(shù),如果在倒數(shù)的過程中,我們敲了回車,則會進(jìn)入到uboot的命令行下,如果我們沒有干涉bootdelay月的倒數(shù),倒數(shù)結(jié)束后,將直接執(zhí)行bootcmd對應(yīng)的命令(一般是啟動內(nèi)核命令),uboot就會死掉了。不管內(nèi)核這次是否啟動成功,uboot這一生都已經(jīng)死掉了。

(6)uboot的命令行就是一個死循環(huán),這個循環(huán)體內(nèi),不斷的接受命令,解析命令,執(zhí)行命令,直到接收解析執(zhí)行了啟動內(nèi)核命令,uboot就會死掉。


6、2、start_armboot函數(shù)解析1

1、init_fnc_t 通過搜索可以知道這是一個函數(shù)類型。

(1)typedef int (init_fnc_t) (void); 這是將一個返回值為int的,參數(shù)為void的函數(shù)類型,重命名為init_fnc_t,所以這個init_fuc_t是一個函數(shù)類型。

(2)init_fnc_t **init_fnc_ptr; 所以這句話的意思就是定義了一個二重的函數(shù)指針。

init_fnc_ptr就是一個二重函數(shù)指針?;仡檶W(xué)過的高級C語言,二重指針有兩個作用,一個是用來指向一重指針,一個是用來指向指針數(shù)組(數(shù)組中的沒個元素都是指針)。

所以這個init_fnc_ptr這個二重函數(shù)指針,可以用來指向一個函數(shù)指針數(shù)組(數(shù)組里面的元素全是函數(shù)指針)


2、board.c文件中,開始的內(nèi)容有個下面的東西

(1)DECLARE_GLOBAL_DATA_PTR;

//#define DECLARE_GLOBAL_DATA_PTR ? ? register volatile gd_t *gd asm ("r8")

(2)這個宏的意思就是,定義了一個全局變量gd,這個全局變量gd是一個指針,用volatile修飾,表示這個變量是可變的,在編譯之外的情況下,這個變量的值是可以改變的,不讓編譯對這個變量做優(yōu)化,用register修飾,表示讓這個變量盡量放在寄存器中,提高速度,asm ("r8")意思是,讓這個變量放在寄存器的r8中,就是不僅讓這個變量放在了寄存器中,還要讓這個變量放到的寄存器是寄存器r8中。

(3)綜合分析:DECLARE_GLOBAL_DATA_PTR 這個宏的意思就是,定義了一個放在寄存器r8中,并且值是可以在編譯之外改變的全局變量,名字叫g(shù)d,并且這個全局變量是一個指針類型,指向gd_t類型變量的指針。

(4)為什么要用register修飾這個全局變量呢,因為這個全局變量是一個gd_t * 類型的,是一個指向結(jié)構(gòu)體的指針。同時這個全局變量gd(global data)指向的那個結(jié)構(gòu)體中放了很多內(nèi)容,組成了這個結(jié)構(gòu)體,這些內(nèi)容就是uboot中常用的全局變量。

也就是說,這個gd全局變量所指向的那個結(jié)構(gòu)體中放的內(nèi)容是uboot常用的全局變量。也就是說,那個把那個放了uboot常用的全局變量的結(jié)構(gòu)體的地址放再了gd這個全局變量指針中。gd這個指針本身占4個字節(jié)。

所以這個gd全局變量是會經(jīng)常被訪問的,所以為了提升程序的效率,用register來修飾

(5)gd_t這個結(jié)構(gòu)體類型定義在include/asm-arm/global_data.h中,這個結(jié)構(gòu)體類型中,第一個又是定義了一個結(jié)構(gòu)體類型的指針變量,這個結(jié)構(gòu)體類型bd_t定義在include/asm-arm/U-boot.h中,找到bd_t這個結(jié)構(gòu)體類型的封裝,可以看著個封裝的名字bd_info,知道這個結(jié)構(gòu)體封裝的是開發(fā)板相關(guān)的硬件信息。

(6)typedef struct global_data {

bd_t *bd; //結(jié)構(gòu)體類型指針變量,指向一個struct bd_info結(jié)構(gòu)體,里面封裝了開發(fā)板的

//硬件相關(guān)信息

unsigned long flags; //標(biāo)志位,干嘛的現(xiàn)在不清楚

unsigned long baudrate; //波特率 因為uboot要用串口發(fā)送到人機(jī)界面上,實現(xiàn)人機(jī)交互,所以需要波特率

//都是全局的,因為都是由gd這個全局變量指針開始指向的??刂婆_的波特率

unsigned long have_console; /* serial_init() was called */ //bool類型的變量,雖然定義是四字節(jié),但

//是后面會知道只用了一個位,可以看出應(yīng)該是代表有無控制臺的,就是能否進(jìn)行printf scanf ,在控制臺建立起來以后就可以printf scanf了,不然只能用串口的putc等

unsigned long reloc_off; /* Relocation Offset */ //重定位時候的偏移量

unsigned long env_addr; /* Address ?of Environment struct */ //封裝環(huán)境變量那個結(jié)構(gòu)體的偏移量

//地址

unsigned long env_valid; /* Checksum of Environment valid? */ ?//在內(nèi)存里的環(huán)境變量當(dāng)前是可以使用的

//還是不可以使用的。也是一個bool類型的變量,如果是1就表示

//內(nèi)存中的那一份環(huán)境變量已經(jīng)可以使用了。

//為什么要檢查內(nèi)存中那一份環(huán)境變量是否可以使用呢,因為開始的時候我們的

//環(huán)境變量是放在SD卡中,當(dāng)我們把在SD卡中的環(huán)境變量讀取到內(nèi)存中的時候,這些個環(huán)境

//變量是不能馬上建立好的,因為內(nèi)存中開始沒有的,所以要檢查,等所有的環(huán)境變量從SD卡

//讀取到內(nèi)存中,建立好后,我們才可以使用內(nèi)存中那一份環(huán)境變量

unsigned long fb_base; /* base address of frame buffer */ //顯存空間的起始地址,因為在裸機(jī)中的LCD

//那一章我們知道顯存空間中的數(shù)據(jù)圖像,會通過lcd控制器自動硬件刷到lcd液晶屏上顯示,

//所以需要一個顯存空間的起始地址

#ifdef CONFIG_VFD //這個宏沒有定義

unsigned char vfd_type; /* display type */

#endif

#if 0 //用0注釋掉了

unsigned long cpu_clk; /* CPU clock in Hz! */

unsigned long bus_clk;

phys_size_t ram_size; /* RAM size */

unsigned long reset_status; /* reset status register at boot */

#endif

void **jt; /* jump table */ //調(diào)轉(zhuǎn)表,在uboot中應(yīng)該是沒有用到

} gd_t;


/***************************************************************************/


typedef struct bd_info {

? ? int bi_baudrate; /* serial console baudrate */ //開發(fā)板上的波特率,這個波特率和上面的那

//個波特率設(shè)置為一模一樣的,可以能是重復(fù)了

//bi表示是在bd_info這個結(jié)構(gòu)體中的。也有

//告訴我們這些是跟開發(fā)板板上有關(guān)的

? ? unsigned long bi_ip_addr; /* IP Address */ //開發(fā)板的IP地址

? ? unsigned char bi_enetaddr[6]; /* Ethernet adress */ //開發(fā)板的MAC地址

? ? struct environment_s ? ? ? *bi_env; //封裝環(huán)境變量的結(jié)構(gòu)體指針,跟上面那個好像也重復(fù)

? ? ulong ? ? ? ?bi_arch_number; /* unique id for this board */ //開發(fā)板的機(jī)器碼

? ? ulong ? ? ? ?bi_boot_params; /* where this board expects params */ //啟動參數(shù)地址

? ? struct /* RAM configuration */

? ? {

ulong start;

ulong size;

? ? } bi_dram[CONFIG_NR_DRAM_BANKS]; //開發(fā)板上DDR的信息,定義了一個結(jié)構(gòu)體數(shù)組變量

//這個結(jié)構(gòu)體數(shù)組bi_dram有兩個元素,CONFIG_NR_DRAM_BANKS值

//是看代碼知道的,為什么是2呢,因為我們有兩個DDR,每一個

//元素是一個結(jié)構(gòu)體,里面有兩個信息一個是在哪里個地址開始,

//一個是有多大。一個元素表示一個DDR

#ifdef CONFIG_HAS_ETH1 //這個宏我們有定義

? ? /* second onboard ethernet port */ //兩個網(wǎng)卡的意思,但是上面的宏沒有定義

? ? unsigned char ? bi_enet1addr[6];

#endif

} bd_t; //將這封裝了很多東西的結(jié)構(gòu)體類型重命名為bd_t





6、3、內(nèi)存使用排布

1、為什么要分配內(nèi)存給gd和bd

(1)#define DECLARE_GLOBAL_DATA_PTR ? ? register volatile gd_t *gd asm ("r8")

gd這個只是一個指針變量,而且他不在占內(nèi)存,被分配到寄存器中去了,也就是說gd所指向的那個結(jié)構(gòu)體中環(huán)境變量并沒有被分配內(nèi)存。所以我們在使用gd之前,要給他分配內(nèi)存,否則gd也只是一個野指針而已,但是現(xiàn)在是在裸機(jī)程序下,不是在操作系統(tǒng)下,所以沒有辦法使用malloc去給gd分配內(nèi)存。裸機(jī)程序下沒有malloc管理器,沒有操作系統(tǒng),無法用malloc分配內(nèi)存。

(2)gd和bd需要內(nèi)存,但是當(dāng)前沒有操作系統(tǒng)去管理內(nèi)存,不能malloc去申請內(nèi)存。在DDR中現(xiàn)在有大片的內(nèi)存可以使用,我們只需要直接去用內(nèi)存地址訪問內(nèi)存即可。但是DDR中的內(nèi)存我們在uboot中不能隨意的去使用,因為uboot會被反復(fù)使用的,比如如果我們在uboot中用fastboot命令將操作系統(tǒng)的內(nèi)核下載到DDR的指定的內(nèi)存中去,如果我們在uboot中隨意使用了這段內(nèi)存,那么結(jié)果可想而知,所以我們在uboot中使用DDR的內(nèi)存的時候,不能隨意的去使用。uboot的后續(xù)操作中,會需要用到緊湊排布的內(nèi)存塊空間,所以這里我們使用內(nèi)存的時候,要盡量的本著我們申請的內(nèi)存夠用就好,而且要緊湊排布,不能這里一塊那里一塊,避免以后的uboot的操作和內(nèi)存相關(guān)的造成不便。

(3)所以我們uboot中,使用內(nèi)存的時候要有一個整體的規(guī)劃。

在DDR中,共有512M,DDR1和DDR2兩個DDR分別是256M,一共512M,DDR的起始地址是0X30000000,uboot的被重定位到了DDR的0X33E00000地址中去了,我在重定位到uboot的時候,我們給了uboot的大小是2M,也就是說從0X33E00000開始向上的2M空間是我們整個uboot所擁有的,在這空間中,實際上我們的uboot用不了那么大的空間,在這2M的空間內(nèi),緊挨著實際uboot的大小上面的地方放的是我們在DDR中設(shè)置的棧。

(4)gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);

gd_base就是這個gd的內(nèi)存起始地址。

6、4、內(nèi)存排布

1、uboot區(qū): 從CFG_UBOOT_BASE起始地址開始,到一個地方。CFG_UBOOT_BASE+XX(長度為實際uboot的大小)

CFG_UBOOT_BASE是uboot在DDR中的起始地址0X33E00000

CFG_UBOOT_SIZE是uboot的在DDR中的整個大小

2、堆區(qū):CFG_MALLOC_LEN 堆區(qū)的長度

在uboot中,我們也想建立一個堆去供我們使用,所以我們也建造了一個堆區(qū),這個堆區(qū)的長度是CFG_MALLOC_LEN,

#define CFG_MALLOC_LEN (CFG_ENV_SIZE + 896*1024)

#define CFG_ENV_SIZE 0x4000

所以可以知道堆區(qū)的長度大小是CFG_MALLOC_LEN = 0X4000 + 896*1024 = 16KB + 896KB = 912KB

3、棧區(qū):#define CFG_STACK_SIZE 512*1024 //512KB

4、gd :sizeof(gd_t) 就是gd所指向的那個結(jié)構(gòu)體的大小,可以知道是36B

5、bd :sizeof(bd) 就是bd所指向的那個結(jié)構(gòu)體的大小,大概是44B.

6、所以大概可以知道gd內(nèi)存起始地址的位置了,gd大概是在uboot在DDR中起始地址0X33E00000開始的位置往上走了2M的長度,又減掉了堆區(qū)912KB的長度,又減去了棧區(qū)512KB的長度,又減去了gd那個結(jié)構(gòu)體的大小36B的位置。

uboot實際的大小很小,只有200或者300多KB,而gd的位置應(yīng)該在uboot實際大小的上面200或者300KB的位置,不會踩到uboot,如果uboot寫的很大,怕會踩到gd的內(nèi)存部分,我們可以改變CFG_UBOOT_SIZE這個值,把2M改的更大,這樣就不會踩到了gd了。

(1)gd = (gd_t*)gd_base;//將算好規(guī)劃好的gd應(yīng)該在DDR中內(nèi)存的起始地址位置賦值給gd,此時gd就已經(jīng)有了好的安身

//也可叫將gd實例化,由單純的指針,變成了有指向?qū)嶋H內(nèi)存的指針

(2)memset ((void*)gd, 0, sizeof (gd_t));//怕這段gd所用的內(nèi)存不干凈,全部清零

(3)gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));//因為bd也是指針,此時也沒有確切的身體,所以我們也要將bd實例化,給他一個內(nèi)存,因為DDR內(nèi)存是向上生長的,所以我們讓gd這個內(nèi)存起始的地址位置,減去了一個bd大小的空間,讓這個空間的起始地址作為bd的內(nèi)存起始位置即可。

總結(jié):所以總的來說就是為了給gd和bd原先沒有指向確切內(nèi)存,讓他指向一個確切的可用安全的內(nèi)存空間的起始位置去。這樣gd和bd就被實例化了,有了身體了。可以使用了。

7、內(nèi)存間隔: __asm__ __volatile__("": : :"memory");

__asm__ 是在C語言中內(nèi)嵌匯編的意思,就是在C語言中內(nèi)嵌了匯編,讓C語言和匯編可以交疊工作。

知道這句代碼的意思就行,就是為了不讓GCC過度優(yōu)化造成錯誤。為了防止高版本的gcc優(yōu)化造成錯誤,一般在我們的gcc交叉編譯工具大于3.4版本的時候就要加上這句話,因為我們GCC已經(jīng)是4.4以上的了,所以一定要加。


6、4 start_armboot函數(shù)的解析2

1.board.c中483行的for循環(huán)

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

if ((*init_fnc_ptr)() != 0) {

hang ();

}

}

(1)init_fnc_ptr 可以知道這是一個二重函數(shù)指針

(2)init_sequence 是一個函數(shù)指針數(shù)組,數(shù)組中有很多函數(shù)指針,指針指向的函數(shù)都是init_fnc_t類型的(typedef (3)int (init_fnc_t) (void); )返回值是int類型的,參數(shù)是void

(4)*init_fnc_ptr for循環(huán)的終止條件, 解引用這個二重函數(shù)指針指向的數(shù)組元素的地址,也就是解引用后,得到數(shù)組偏移地址所對應(yīng)的那個函數(shù)指針

(5)++init_fnc_ptr for循環(huán)的增量 指針遍歷函數(shù)指針數(shù)組的元素地址,+1表示指向下一個元素的地址

(6)init_sequence 這個數(shù)組在board.c的416行,定義的同時初始化了,初始化的內(nèi)容都是函數(shù)名(函數(shù)名就是函數(shù)指針,代碼段的首地址)。

(7)我們怎么遍歷一個函數(shù)指針數(shù)組呢?一般由兩種方法,一個是將終止條件設(shè)置為數(shù)組的大小,當(dāng)不滿足時,則循環(huán)結(jié)束。另一種是在數(shù)組的內(nèi)部弄一個元素讓其為終止的條件,如果遇到了這個元素則其終止,這里我們用到的就是第二種方法。

init_sequence數(shù)組中的函數(shù)指針,這些函數(shù)在運(yùn)行成功的時候都會返回一個0,返回0表示運(yùn)行成功了。如果在遍歷這個函數(shù)指針數(shù)組時,里面有一個函數(shù)運(yùn)行不成功,不返回0,則會執(zhí)行hang();掛起,就是證明啟動過程執(zhí)行失敗了

(8)init_sequence里面的函數(shù)初始化,都是board級別的,板級的硬件相關(guān)的初始化。cpu外面的,開發(fā)板里面的。

2、cpu_init 里面是空的,直接返回了0,代表確實這個init_sequence里面的函數(shù)是初始化CPU外面的,開發(fā)板里面相關(guān)的硬件的東西,并且cpu初始化在前面的start.S中已經(jīng)完成了

3、board_init 函數(shù)體在board/samsung/X210/X210.c中

(1)DECLARE_GLOBAL_DATA_PTR; 在board_init函數(shù)中的一句話,意思是聲明gd的意思,因為下面要用到gd。定義的同時不初始化就是聲明。

宏定義的變量也是放在頭文件中的,在調(diào)用時也是需要包含頭文件的。只是說這個宏如果不使用,這個變量就不會聲明。

(2)網(wǎng)卡初始化

接著這個宏CONFIG_DRIVER_DM9000

這個宏是在x210.h中定義的,定義了這個宏就表示我們的開發(fā)板要配置這個DM9000這個網(wǎng)卡,下面就是這個網(wǎng)卡的初始化了。dm9000_pre_init();這個函數(shù)就是對應(yīng)的DM9000這個網(wǎng)卡的初始化函數(shù),如果你用的網(wǎng)卡在uboot中沒有,那么就需要你自己去移植了,怎么移植呢,也是用一個宏來進(jìn)行條件編譯的選擇,完了將你對應(yīng)的網(wǎng)卡的初始化函數(shù)放在這個宏下,可以看到,這些個函數(shù)都是放在,x210.c中的。

dm9000_pre_init();這個函數(shù)主要是GPIO和端口的配置,而不是驅(qū)動,因為網(wǎng)卡驅(qū)動是現(xiàn)成的,移植的時候驅(qū)動是不需要改動的,關(guān)鍵就是這個函數(shù)中的基本的初始化,因為這些基本初始化是和硬件相關(guān)的。

?

6、5 start_armboot函數(shù)解析3

背景:

(1)初始化了DDR

注意:這里初始化DDR和我們在匯編階段在lowlevel_init函數(shù)中初始化DDR是不同的。

當(dāng)時DDR的初始化是DDR硬件下的初始化,為了讓DDR可以工作,而現(xiàn)在的初始化是和軟件層面相關(guān)的,一些DDR的屬性配置、地址設(shè)置的初始化。

為什么要在軟件層次上初始化DDR,因為對于uboot來說,我們要知道開發(fā)板上有幾片DDR,每一片DDR的起始地址和長度是多大這些信息。uboot采用了一個簡單有效的方式:程序員在移植uboot到一個開發(fā)板上時,程序員在x210_sd.h這個文件中,自己用宏定義的方式配置出板子上DDR的相關(guān)信息,然后uboot只要讀取這些信息即可。(實際上還有另外一種方式,就是uboot通過代碼的方式直接讀取硬件中的硬件相關(guān)的信息來知道DDR的配置,但是uboot沒有這么做,在我們PC機(jī)上,BIOS用的是這種方式,比如我們的電腦是4G的內(nèi)存,當(dāng)我加了一條內(nèi)存條的時候,電腦就識別到了內(nèi)存變成了8G了,這就是直接讀取硬件相關(guān)的信息,來知道DDR相關(guān)的信息)

(2)在x210_sd.h中的499行到505行,用標(biāo)準(zhǔn)宏定義的方式配置了我們開發(fā)板上DDR的相關(guān)的信息,為什么說是標(biāo)準(zhǔn)的宏定義呢,因為這些宏定義的名字是不可以被改變的,我們只能改變這些宏定義的值。所以叫做標(biāo)準(zhǔn)的宏定義。

#define CONFIG_NR_DRAM_BANKS ? ?2 ? ? ? ? ?/* we have 2 bank of DRAM */

#define SDRAM_BANK_SIZE ? ? ? ? 0x10000000 ? ?//實際256MB ?/* 512 MB lqm 這個512MB的注釋純屬瞎寫 */

//#define SDRAM_BANK_SIZE ? ? ? ? 0x20000000 ? ?/* 1GB lqm*/ //這個可能是以前的,但實際應(yīng)該是512MB

#define PHYS_SDRAM_1 ? ? ? ? ? ?MEMORY_BASE_ADDRESS /* SDRAM Bank #1 base address */

#define PHYS_SDRAM_1_SIZE ? ? ? SDRAM_BANK_SIZE //我們用的DDR一片是256MB

#define PHYS_SDRAM_2 ? ? ? ? ? ?MEMORY_BASE_ADDRESS2 /* SDRAM Bank #2 */

#define PHYS_SDRAM_2_SIZE ? ? ? SDRAM_BANK_SIZE //我們用的DDR第二片也是256MB


1、gd->bd->bi_arch_number = MACH_TYPE;

(1)bi_arch_number是bd_info結(jié)構(gòu)體中的一個成員,他的意思就是開發(fā)板的機(jī)器碼,所謂機(jī)器碼就是uboot給我們的這個開發(fā)板定義的唯一編號。

(2)機(jī)器碼的作用,就是uboot和linux內(nèi)核之間進(jìn)行比對和適配。

因為嵌入式設(shè)備的硬件都是定制化的,每一個開發(fā)板的機(jī)器碼都不同,在這個開發(fā)板上跑起來的內(nèi)核,同樣的內(nèi)核鏡像在另一個開發(fā)板上就跑不起來,不能通用,這就是嵌入式設(shè)備和PC機(jī)的不同。

(3)因此linux做了個設(shè)置:就是給每個開發(fā)板做了一個唯一的編號(機(jī)器碼),然后在uboot、linux內(nèi)核中都有一個軟件維護(hù)機(jī)器碼的編號,然后開發(fā)板、uboot、linux三者比較適配機(jī)器碼,如果機(jī)器碼對上了就啟動,對不上就不啟動(因為軟件認(rèn)為,我和這個硬件不匹配,不能啟動了把他給坑了,因為如果沒有這個機(jī)器碼和軟件去維護(hù),那么隨意的去的啟動,可能啟動不起來,如果啟動起來了,就可能帶來無法預(yù)料的損失,金錢,生命都有可能)。

(4)MACH_TYPE 在x210_sd.h中123行定義,值是2456,沒有特殊的含義,只是當(dāng)前開發(fā)板對應(yīng)的編號。這個編號就代表x210這個開發(fā)板的機(jī)器碼,將來這個開發(fā)板上一直的linux內(nèi)核的機(jī)器碼也必須是這個2456的值。不然比對適配不上啟動不起來。

(5)uboot中配置的這個機(jī)器碼,將來會在啟動linux內(nèi)核的時候,作為傳參的一部分傳參給linux內(nèi)核,這樣linux內(nèi)核就知道uboot的機(jī)器碼了。內(nèi)核啟動過程中,會將這個傳參過來的機(jī)器碼和自己的機(jī)器碼進(jìn)行想比對。

(6)在國內(nèi),這個機(jī)器碼我們可以隨便給,但是如果你是在公司中做產(chǎn)品的話,就要慎重了,盡量不要和別人的重了

2、gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

(1)bi_boot_params,也是bd_info中的一個成員,含義是uboot給linuxkernel啟動時傳參的內(nèi)存地址。

也就是說,uboot在給linux內(nèi)核傳遞參數(shù)的時候是這么傳的:uboot事先將準(zhǔn)備好的傳參(字符串。bootargs)放在內(nèi)存的一個地址處(bi_boot_params),然后uboot就啟動了內(nèi)核(uboot在啟動內(nèi)核時真正是通過r0、r1、r2、寄存器來直接傳遞參數(shù)的,這三個寄存器中,其中有一個就是這個bd_boot_params參數(shù),這個參數(shù)就是內(nèi)存的地址),然后內(nèi)核在啟動的時候,通過讀取這個寄存器中的值就可以知道uboot傳遞過來的參數(shù)在內(nèi)存中哪里。然后自己去內(nèi)存的那個地方去找bootargs

(2)經(jīng)過計算知道這個bi_boot_params的值是0x30000100,所以可以知道uboot將這個內(nèi)存地址連續(xù)的一定空間內(nèi),作為給內(nèi)核傳參的地方了,所以我們在uboot的其他代碼中,就不去這個地址附近玩耍了,將來linux內(nèi)核啟動的時候,也會通過讀取到那三個寄存器中的某一個后,知道了參數(shù)在內(nèi)存的哪個位置了,完了去那個內(nèi)存的位置去讀取相應(yīng)的uboot傳給linux內(nèi)核的參數(shù)。



6、6 start_armboot函數(shù)解析4

1、interrupt_init;

(1)這個函數(shù)指針指向的函數(shù),跳到這個函數(shù),發(fā)現(xiàn)代表和函數(shù)的名字含義不相關(guān)。實際上這個函數(shù)是用來初始化定時器的(使用的是timer4定時器)。

(2)在裸機(jī)中,我們知道,我們x210開發(fā)板,一共有五個PWM定時器,中timer0-timer3這個四個PWM定時器有信號的輸出引腳,和timer4沒有信號的輸出引腳,無法輸出PWM波形。timer4在設(shè)計的時候就不是用來輸出PWM波形的(第一。沒有輸出引腳,第二、沒有TCMPB這個用來計算PWM波的寄存器),所以這個定時器在設(shè)計的時候是用來做計時用的(因為有TCNTB這個用來存放計數(shù)的寄存器)。

(3)timer4用來做計時時,需要用到2個寄存器,一個是TCNTB、一個是TCNTO。

TCNTB寄存器中,存的是一個數(shù),這個數(shù)代表著定時次數(shù)(每一次的時間是由時鐘決定的,實際上是由PCLKPSYS過來的時鐘頻率,經(jīng)過預(yù)分頻器和分頻器得到的最終給了這個定時器的一個時鐘頻率,這個寄存中的數(shù)乘以過來的時鐘頻率就是定時的時間長度,每一個時鐘頻率過來,這里面的數(shù)就會減1,當(dāng)減到0的時候,證明定時時間到了)。我們定時的時候只需要將我們要定的時間/這個定時器的時鐘記一次數(shù)的時間=要放在這個寄存中的數(shù)。將這個數(shù)放入這個TCNTB寄存器中即可。

TCNTO寄存器,這個寄存器是用來觀察TCNTB寄存器中的數(shù)的(實際上是TCNTB背后那個寄存器的數(shù)),我們通過TCNTO寄存器可以知道TCNTB中的數(shù)有沒有減到0,當(dāng)TCNTO讀取到TCNTB寄存器中數(shù)減到0后,就知道定時間到了。(因為通過TCNTB這個寄存器,我們把數(shù)放到里面后,是看不到這個寄存器中數(shù)在減的,這個寄存器相當(dāng)于一個buff。將數(shù)傳到這個寄存器的背后里面那個寄存中,那個寄存器中的數(shù)是會減的,但是我們看不到,那個寄存器是看不見的,所以要通過TCNTO這個寄存器去觀察那個寄存器中的數(shù)有沒有減到0,定時時間有沒有到)

(4)使用timer4定時器,因為這個timer4沒有中斷支持,不支持中斷的,所以當(dāng)時間到了以后,不會有中斷標(biāo)志位的改變,我們無法知道時間是否定時到了,所以要用TCNTO這個寄存器,通過讀,取里面的值,來知道timer4定時器的定時時間是否到了,并且我們的CPU不能在做其實事情的后,來知道定時器時間到了,因為不支持中斷,不會有中斷通知我們時間到了,所以我們只能用查詢的方式來讀取TCNTO寄存器中的值來知道定時時間是否到了,因為timer4的定時是無法實現(xiàn)微觀上的并行的,用它來定時,只能不斷的輪詢查看這個寄存器,來知道時間是否到了。

(5)uboot中是用這個timer4來實現(xiàn)定時的,所以uboot在使用這個定時器定時的時候,是無法做其他的事情的。只能看著這個定時器的定時時間到(這不就是bootdelay的原理嘛,為什么uboot在啟動的時候,bootdelay在倒計時,在進(jìn)行等到,程序并沒有執(zhí)行其他的事情,因為這個定時器實現(xiàn)不了并且,定時時程序只能停留在那個位置,查看定時時間是否到了,所以bootdelay在實現(xiàn)定時的時候是用的輪詢的方式,檢查用戶有沒有輸入回車鍵,如果沒有繼續(xù)定時)。

(6)interrupt_init;函數(shù)就是將timer4設(shè)置為定時10ms,用get_PCLK函數(shù)來獲取系統(tǒng)設(shè)置的PCLK_PSYS的時鐘頻率。然后進(jìn)行TCFG0,TCFG1分頻和預(yù)分頻,然后計算10ms應(yīng)該向TCNTB寄存器中寫入的值。最后寫入,完了設(shè)置是自動刷新數(shù)到TCNTB背后的寄存器中,還是手動刷新。最后在開啟timer4。

總結(jié):學(xué)習(xí)這個函數(shù)的時候,重點(diǎn)要知道這種標(biāo)準(zhǔn)代碼和之前裸機(jī)代碼的區(qū)別,并且要學(xué)會怎么用結(jié)構(gòu)體封裝寄存器的方式,用結(jié)構(gòu)體的首地址經(jīng)過偏移去訪問一類的寄存器,因為一類寄存器的內(nèi)存地址是4字節(jié)連續(xù)向上生長的,所以可以將某一類寄存器的基地址給結(jié)構(gòu)體的指針,用結(jié)構(gòu)體指針的方式去訪問,因為結(jié)構(gòu)體的是內(nèi)存對齊的,如果成員如果都是4個字節(jié)的話,那么成員和成員之前就相差了四個字節(jié)的內(nèi)存,正好一類寄存器的內(nèi)存地址也是4字節(jié)4字節(jié)相連續(xù)的,還要學(xué)會用函數(shù)的方式來獲取設(shè)置值的方式。這樣可以讓代碼具有可移植性。


2、env_init

(1)看名字就知道是和環(huán)境變量的初始化有關(guān)。

(2)為什么有很多env_init函數(shù)呢?因為我們uboot支持很多種啟動介質(zhì)(比如,從iNand、norflash、nandflash、sd卡啟動····很多種啟動),當(dāng)我們用不同的啟動介質(zhì)啟動uboot的時候,從啟動介質(zhì)操作env環(huán)境變量到內(nèi)存中,或者從內(nèi)存中存到啟動介質(zhì)中的的方法都是不一樣的,操作方法都是不一樣的。因此uboot支持各種啟動介質(zhì)中,env的存取操作方法,所以有很多env_xxx名字的.c文件。(當(dāng)我們編譯鏈接的時候,只會有一個env_xxx.c文件中的env_init函數(shù)被鏈接,因為在x210_sd.h頭文件中,已經(jīng)用宏定義的方式來配置了,有條件編譯的控制,所以只會有一個會起作用),對于我們x210iNand版本的開發(fā)板來說,我們應(yīng)該看是env_movi.c這個文件中的env_init函數(shù)。

(3)這個env_init函數(shù)做的操作就是對我們內(nèi)存中維護(hù)的那一份uboot的env環(huán)境變量進(jìn)行了判定(判定內(nèi)存中有沒有環(huán)境變量),當(dāng)前因為我們還沒進(jìn)行環(huán)境從SD卡到DDR的relocate,因此當(dāng)前的環(huán)境變量是不能用。

(4)在start_armboot這個函數(shù)的776行調(diào)用env_relocate才將環(huán)境變量從SD卡中relocate到DDR中,那時才可以用。重定位之后,在需要環(huán)境變量的時候,才能從DDR中去使用環(huán)境變量,在重定位之前,我們只能從SD卡中去讀取環(huán)境變量。內(nèi)存中并沒有。



6、6 start_armboot函數(shù)解析5

1、init_baudrate

(1)看名字就知道是初始化串口通信波特率的,uboot在啟動時,就是通過串口和我們主機(jī)進(jìn)行通信的。通過串口形成的控制臺來控制我們的uboot。

(2)getenv_r函數(shù),讀取環(huán)境變量的值的。因為我們uboot希望不改變源代碼的情況下,直接在uboot的命令行下,通過更改baudrate這個環(huán)境變量的值來設(shè)置uboot的串口波特率,所以我們將把baudrate弄成了環(huán)境變量。

int i = getenv_r ("baudrate", tmp, sizeof (tmp)); 這個函數(shù)的目的就是將baudrate這個字符串代表的環(huán)境變量的值讀取到tmp數(shù)組中,注意,這個環(huán)境變量的值是一個字符串,因為tmp是一個字符數(shù)組,并且uboot界面中的東西都是字符串。函數(shù)執(zhí)行成功后返回一個大于1的數(shù)給i。

(3)int) simple_strtoul (tmp, NULL, 10) 這句話的意思就是tmp數(shù)組中放的把baudrate的值(字符串)轉(zhuǎn)換成數(shù)字。

(4)baudrate初始化的規(guī)則:首先先從環(huán)境變量中讀取這個環(huán)境變量的值放在tmp字符數(shù)組中,如果讀取成功,則將這個值轉(zhuǎn)換成數(shù)字給我們gd->baudrate和gd->bd->baudrate,如果讀取失敗了,就用x210_sd.h中的CONFIG_BAUDRATE這個宏的值(115200)給這兩個成員,作為波特率的值。從這里可以看出來,環(huán)境變量的優(yōu)先級是很高的,環(huán)境變量如果有則用環(huán)境變量的,環(huán)境變量中沒有則在考慮用別的。

2、serial_init

(1)看名字知道是串口的初始化,疑問(我們start.S中的lowlevel_init中,已經(jīng)初始化串口了,為什么這里還要初始化呢?和之前在匯編階段的串口初始化有什么不同嗎)。

(2)和我們這個開發(fā)板有關(guān)系的串口初始化serial_init函數(shù)是在uboot/uboot_jiuding/cpu/s5pc11x/serial.c中。

(3)看代碼知道,我們這個函數(shù)什么都沒有做,緊緊就是用for循環(huán)延時了一小小下,所以就說明我們在匯編階段初始化了串口,這里就沒有在進(jìn)行硬件寄存器的初始化了。



6、6 start_armboot函數(shù)解析6

1、console_init_f函數(shù)

(1)初始化控制臺的函數(shù),_f表示這個初始化是第一階段的初始化,_r表示是第二階段的初始化,因為有的初始化一個階段初始化不完,所以會有第二階段的初始化。有時候初始化函數(shù)不能一次一起完成初始化,中間還會夾雜這一些代碼,所以需要將一個完成的初始化模塊的代碼分成了2個階段(我們uboot中的,start_armboot函數(shù)的第826行進(jìn)行了console_init_r第二階段的初始化)。

(2)console_init_f函數(shù)在uboot/common/console.c中,common表示這個文件中放的是與硬件無關(guān)的普遍使用的代碼。

(3)這個函數(shù)的功能僅僅是將我們gd->have_console設(shè)置為1了。


2、display_banner函數(shù)?

(1)這個函數(shù)是用來串口輸出顯示uboot的logo的。在uboot剛開始啟動的時候,OK是在lowlevel_init函數(shù)中初始化完串口后,執(zhí)行完畢lowlevel_init函數(shù)后打印的OK字樣,U-Boot 1.3.4 (Aug ?7 2013 - 14:53:08) for x210是uboot的logo,是在uboot啟動的第二階段,start_armboot函數(shù)中調(diào)用disolay_banner函數(shù)顯示的。

(2)在這個函數(shù)中開始用到了printf,但是我們的控制臺還沒有初始化好呢,上面的那個console_init_f初始化函數(shù)中,相當(dāng)于什么都沒有,那這里為什么可以進(jìn)行printf呢?

(3)我們追到printf這函,發(fā)現(xiàn)這個函數(shù)實際上是通過調(diào)用puts函數(shù),接著在puts函數(shù)中判斷我們控制臺是否初始化好,如果初始化好了則調(diào)用fputs函數(shù)(這樣發(fā)送才是控制臺的路),如果控制臺console沒有初始化則調(diào)用了serial_puts函數(shù),我們此時的控制臺console還沒有初始化成功,所以我們走到了這個函數(shù)serial_puts中,這個函數(shù)中又調(diào)用serial_putc函數(shù),最后在serial_putc中是通過,將串口寄存器組的基地址給了封裝串口寄存器的結(jié)構(gòu)體的指針變量,通過這個指針變量將要通過串口發(fā)送的數(shù)據(jù)放到了串口的發(fā)送寄存器中。所以是跟控制臺無關(guān)的。

(4)我們的控制臺是用串口輸出,控制臺沒有初始化好,我們用的也是串口輸出,那么控制臺究竟有什么好處呢?什么是控制臺呢?實際上如果通過代碼的分析,會發(fā)現(xiàn),控制臺實際上是通過軟件虛擬出來的設(shè)備,這個設(shè)備有一套專用的通信函數(shù)(發(fā)送。接收···),控制臺的通信函數(shù),最終會映射到硬件的通信函數(shù)中來實現(xiàn)。也就是說uboot的控制臺的通信函數(shù)實際上是映射到硬件的串口通信函數(shù)總的,也就是說uboot用沒用控制臺,其實并沒有本質(zhì)的差別。但是在別的體系中,控制臺的通信函數(shù)映射到硬件的通信函數(shù)中,可以用軟件來做一些優(yōu)化,比如說是緩沖機(jī)制。(在操作系統(tǒng)中,用的就是這樣控制臺的方式,這樣可以將我們要發(fā)送的很多字節(jié)放到console的buff中進(jìn)行緩沖,如果不這樣的話,因為硬件的串口本身是一個字節(jié)一個字節(jié)的發(fā)送的,會很慢,而CPU的速度很快,這樣去將發(fā)送的東西放到控制臺的buff中去緩沖就會提供通信的速度。尤其是輸出到lcd的輸出設(shè)備上,如果沒有這個console的緩沖機(jī)制,那么輸出一個字節(jié)就要去刷新一次屏幕,如果有這個console的緩沖機(jī)制就可以將發(fā)送的東西放到console的緩沖中,一次刷到lcd屏幕上)

(5)printf函數(shù)要發(fā)送的是version_string這個字符數(shù)組。

const char version_string[] =

U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;

數(shù)組中的U_BOOT_VERSION在源代碼中是找不到定義的,這個變量數(shù)組中的U_BOOT_VERSION在源代碼中是找不到定義的,這個變量(字符串)實際上是在makefile中定義的makefile的365行,在配置編譯的時候才能生成,在include/version_autogenerated.h中的一個宏。CONFIG_IDENT_STRING是在x210_sd.h中的宏,配置的信息。DATE是編譯的日期,TIME是時間


3、print_cpuinfo函數(shù)

(1)打印CPU的信息。uboot啟動過程中,

CPU: ?S5PV210@1000MHz(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打印出來的


6、6、start_armboot函數(shù)解析7

1、checkboard函數(shù)

(1)檢查當(dāng)前開發(fā)板是哪一個開發(fā)板,并且打印出來開發(fā)板的名字

2、#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)

(1)CONFIG_HARD_I2C是硬件的I2C(就是SOC中有I2C控制器),這個如果定義了,或者CONFIG_SOFT_I2C軟件I2C(就是兩個GPIO來模擬I2C的),兩個中一個成立就會執(zhí)行init_func_i2c函數(shù),看似我們的硬件的I2C的宏是被定義的,但是追蹤發(fā)現(xiàn)我們這個硬件I2C的宏沒有定義,被條件編譯給攔住了。

3、init_func_i2c函數(shù)

所以可以知道這個函數(shù)實際沒有被執(zhí)行的,因為硬件和軟件的I2C宏都沒有定義。所以串口中也沒有打印I2C的信息。


4、uboot學(xué)習(xí)實踐

(1)對uboot源碼進(jìn)行修改(按照我們的需要進(jìn)行修改)、然后make distclean將之前編譯生成的東西刪除掉、然后在make x210_sd_config進(jìn)行配置、然后在make編譯

(2)之后編譯完成后生成的u-boot.bin,在linux下用dd命令的方法進(jìn)行燒寫。

(3)燒寫的過程:

第一步:進(jìn)入到uboot根目錄下的sd_fusing文件夾中,這里面有一個燒寫腳本,我們當(dāng)前是不可以用的,因為之前九鼎是在64位的Ubuntu中用的,而我們現(xiàn)在的Ubuntu是32位,file mkbll可以看出來

第二部:make clean 將原先的跟燒寫腳本相關(guān)的東西刪掉,在make,因為我們的Ubuntu是32位的,所以這個時候make的時候就是我們能用的了。因為是在32位Ubuntu中make的,在file mkbll可以看出來。

第三步:看sd_fusing.sh腳本中的燒錄uboot的路徑和燒錄到SD卡的扇區(qū)(和我們從SD卡重定位到DDR中的那個SD卡起始扇區(qū)位置一致,因為我源碼中的扇區(qū)是哪里,我們在燒寫uboot到SD卡的時候就應(yīng)該燒寫到哪里,不然將來從SD卡重定位到DDR中,就不能運(yùn)行了,因為源碼我們是那么寫的)對不對。

第四步:插上SD卡,Ubuntu中識別到之后執(zhí)行 ./sd_fusing.sh /dev/sdb 燒錄到SD卡中。


注意:破壞linux平臺下的SD0通道所連啟動介質(zhì)中的bootloader:在linux平臺下輸入

busybox dd if=/dev/zero of=/dev/mmcblk0 bs=512 seek=1 count=1 conv=sync

在執(zhí)行sync確保破壞的前一塊數(shù)據(jù)有效。


6、6 start_armboot函數(shù)解析8

1、dram_init函數(shù)

(1)在匯編階段我們已經(jīng)初始化DDR了,不然也不能relocate到這里運(yùn)行了,這里初始化DDR是初始化DDR軟件相關(guān)的,就是初始化DDR的信息,將DDR相關(guān)的信息,比如DDR起始地址,DDR的大小的放到gd全局變量下的DDR相關(guān)的成員中。將DDR的相關(guān)信息記錄到這兩個成員中,以便于以后用到。這些相關(guān)的信息在x210_sd.h中可以進(jìn)行配置。

(2)從代碼來看,這個函數(shù)的目的就是初始化了gd->bd->bi_dram這個結(jié)構(gòu)體數(shù)組,內(nèi)容就是我們開發(fā)板用的DDR的相關(guān)信息,起始地址和大小。

2、display_dram_config函數(shù)

(1)打印DDR的信息。打印顯示DDR的配置信息。

(2)啟動過程中的 DRAM: ? ?512 MB 就是在這個函數(shù)中被打印顯示出來的。

(3)如何在uboot運(yùn)行的時候,知道DDR的配置信息呢,有一個命令 bdinfo,這個命令可以打印出來gd->bd->bi_dram成員中的配置信息,就是DDR的配置信息。并且這個bdinfo命令還可以打印出來所有硬件相關(guān)的全局變量的值


3、init_sequence函數(shù)指針數(shù)組中做的事情的總結(jié):

總結(jié):

(1)都是板級的初始化,gd全局變量中的成員的初始化,cpu_init是沒有的,網(wǎng)卡的初始化、機(jī)器碼的初始化(gd->bd->bi_arch_number)、內(nèi)存?zhèn)鲄⒌刂返某跏蓟?gd->bd->bi_boot_params)。

(2)定時器的初始化,使用的是定時器timer4,這個定時器沒有PWM輸出引腳,不支持中斷,所以在用這個定時器進(jìn)行定時的時候,我們CPU要不斷的進(jìn)行輪詢?nèi)ゲ檎夷莻€TCNTO寄存器中的值去觀察定時時間是否到了。bootdelay的延時就是由這個實現(xiàn)的,所以在bootdelay倒計時時,uboot不干別的,只會看時間是否到了,和是否有回車鍵按下。

(3)看內(nèi)存中有無環(huán)境變量,有無的信息記錄在gd->env_valid成員中

(4)波特率的初始化,串口的初始在uboot匯編階段(uboot第一階段已經(jīng)初始化好了,否則OK也不會打印出來),串口初始化中沒有實質(zhì)的內(nèi)容。

(5)控制臺初始化的第一部分,將gd->console成員賦值我1

(6)顯示uboot的logo

(7)打印顯示CPU相關(guān)的信息

(8)檢查打印是哪個開發(fā)板

(9)硬件和軟件的i2c都沒有進(jìn)行。

(10)將DDR的相關(guān)配置信息放在gd->bd->bi_dram的數(shù)據(jù)結(jié)構(gòu)中。

(11)打印DDR的大小


總結(jié):至此我們到了start_armboot的487行。


6、6 start_armboot函數(shù)解析9

1、CFG_NO_FLASH

(1)Nandflash和norflash都是flash,但是一般的情況下,flash指的norflash,nandflash簡稱為nand。

(2)491行,執(zhí)行的flash_init是norflash的初始化的,display_flash_config打印的是,norflash的大小。uboot啟動信息中的flash 8M 就是在這里打印的。


2、CONFIG_VFD和CONFIG_LCD lcd顯示相關(guān)的,uboot中自帶的lcd顯示架構(gòu),我們沒有用這個,我們在后面中,自己添加了一個LCD

顯示的部分


3、mem_malloc_init函數(shù)

(1)用來初始化uboot的堆管理器的。

(2)在uboot中,開始是沒有堆管理器的,但是uboot在這里自己維護(hù)了一段堆內(nèi)存,肯定有自己的一套代碼來管理這個堆內(nèi)存,有了這些東西以后,我們就可以使用malloc、free這套機(jī)制來申請內(nèi)存,釋放內(nèi)存了。

(3)我們在DDR中,建立的堆內(nèi)存大小是912KB,我們用代碼寫出來的堆管理器,來管理DDR中的這段堆內(nèi)存,當(dāng)我們malloc申請堆內(nèi)存的時候,堆管理器就會去這個堆內(nèi)存中分配給我們。并且把那段申請的堆內(nèi)存的起始地址給我們。這個函數(shù)就是將堆內(nèi)存的部分內(nèi)容請0。


6、6 start_armboot函數(shù)解析10

1、接著的在start_armboot的599行-632行是我們x210相關(guān)的代碼。530-769行是開發(fā)板獨(dú)有的初始化代碼

(1)mmc_initialize,開發(fā)板MMC相關(guān)的初始化,初始化SOC內(nèi)部SD/MMC控制器的。函數(shù)在uboot/drivers/mmc/mmc.c中。

(2)uboot對硬件的操作,如網(wǎng)卡、SD卡···都是借用的linux內(nèi)核中的驅(qū)動來實現(xiàn)的,uboot根目錄下的drivers文件夾,這里面放的就全是linux內(nèi)核中移植過來的各種驅(qū)動文件

(3)mmc_initialize,所有使用MMC架構(gòu)的MMC卡,都是使用了這個函數(shù)來初始化MMC的。

(4)board_mmc_init這個函數(shù)在mmc_initialize返回了一個 -1(這個函數(shù)開發(fā)板級別的MMC初始化,意思就是說,如果我們SOC內(nèi)部沒MMC控制器的話,是開發(fā)板上提供的MMC控制器,外部擴(kuò)展的,所用就用這個函數(shù),所以這個函數(shù)的優(yōu)先級要高于cpu_mmc_init),實際干活的函數(shù)是cpu_mmc_init(如果我們的SOC內(nèi)部有MMC控制器的話,就可以執(zhí)行這個函數(shù),顯然我們x210開發(fā)板,內(nèi)部SOC上有MMC控制器,所以我們可以跳過board_mmc_init函數(shù),執(zhí)行cpu_mmc_init函數(shù))

(5)cpu_mmc_init函數(shù)在uboot/cpu/s5pc11x/cpu.c中,這里面又間接的調(diào)用了uboot/driver/mmc/s3c_mmcxxx.c的驅(qū)動代碼來實現(xiàn)mmc控制器的初始化。


6、6 start_armboot函數(shù)解析11

1、start_armboot函數(shù)中到了770行

2、776行 env_relocate函數(shù)

(1)環(huán)境變量的重定位,將SD卡中的環(huán)境變量重定位到DDR中。

(2)環(huán)境變量到底重哪里來?雖然SD卡中我們在劃分的時候有些扇區(qū)是放我們的env的,但是我們在燒錄的時候,不曾燒錄env分區(qū),只燒錄了uboot分區(qū)、kernel分區(qū)、rootfs分區(qū),在SD卡的那些放env扇區(qū)的位置是沒有env的,因為沒有燒錄env過去,在第一次啟動的時候。所以第一次uboot啟動的時候,去SD卡的env分區(qū)讀取環(huán)境變量的時候是失敗(讀取回來后,做CRC校驗時是失敗的),env分區(qū)是空的,所以第一次啟動的時候,我們uboot選擇從uboot內(nèi)部代碼中設(shè)置的一套默認(rèn)的環(huán)境變量出發(fā)來使用(這套代碼的ENV,在第一次啟動的時候,被弄到了DDR中,當(dāng)我們將內(nèi)存中的環(huán)境變量改變后,在saveenv后會被寫入到sd卡的env分區(qū)中,當(dāng)我們下次啟動的時候,在sd卡的env分區(qū)中就有了環(huán)境變量了,所以下次讀取的時候就成功了。也可能是uboot在第一次讀取內(nèi)部默認(rèn)的環(huán)境變量后,就直接寫入到SD卡的env分區(qū)中了)。這就是為什么我們的SD卡燒錄好后,雖然沒有燒錄環(huán)境變量分區(qū),但是在第一次啟動的時候,uboot界面中是可以看到環(huán)境變量的,并且在本次我們將改動的環(huán)境變量改動好,保存起來后,我們重新燒錄SD卡,但是還是沒有燒錄env分區(qū),但是下一次啟動的時候,發(fā)現(xiàn)環(huán)境變量的值確是我們上一次更改后的環(huán)境變量的值。


總結(jié):第一次啟動的時候,SD卡的env分區(qū)是沒有環(huán)境變量的,uboot代碼中的gd->env_valid 的值是等于0的,所以這個時候uboot讀取的是uboot代碼內(nèi)置的一套環(huán)境變量到DDR中,將這套環(huán)境變量通過代碼寫入到SD卡的env分區(qū)中(或者我們改變這套環(huán)境變量的值后,saveenv保存到SD卡的env分區(qū)中),并且計算出CRC校驗,將gd->env_valid 值賦值為1,這樣在下次啟動的時候,uboot重SD卡的env分區(qū)讀取環(huán)境變量的時候,CRC校驗就可以通過了,gd->env_valid 值就不為0 了, 就可以成功的重SD卡的env分區(qū)中將環(huán)境變量讀取到DDR中了。

(3)真正的環(huán)境變量從SD卡重定位到DDR中的代碼是在env_relocate_spec函數(shù)中的movi_read_env函數(shù)



6、6 start_armboot函數(shù)解析12

1、IP地址。MAC地址的獲取

(1)IP地址的獲取,優(yōu)先從環(huán)境變量中獲取,來源于ipaddr這個環(huán)境變量,存放在gd->bd->bi_ipaddr中

(2)getenv_IPaddr ("ipaddr")函數(shù)獲取ipaddr這個環(huán)境變量的值,這個函數(shù)中的getenv用來獲取ip地址的值(注意是字符串格式的),string_to_ip函數(shù)用來將這個字符串格式的IP地址值轉(zhuǎn)換為正常的數(shù)字的IP地址值(點(diǎn)分的十進(jìn)制的數(shù)字),然后最后返回這個數(shù),存放在那個gd中的ip地址成員中。

(3)IP地址是由4個0-255的數(shù)字組成的,所以最簡單的存儲格式,就是用一個unsigned int的大小32位。一個存儲單元。但是我們?nèi)四芸炊腎P地址是點(diǎn)分十進(jìn)制格式的(192.168.1.10),這兩種類型可以進(jìn)行互相的轉(zhuǎn)換,一般給我們?nèi)丝吹脑捑褪寝D(zhuǎn)換成點(diǎn)分十進(jìn)制類型的,給機(jī)器用就是unsigned int類型的。所以get_env函數(shù)得到的IP地址應(yīng)該是unsigned int類型的,因為這樣的在內(nèi)存中存儲是比較省內(nèi)存空間的,機(jī)器也好用。


2、MAC地址(網(wǎng)卡的硬件的地址)

(1)ethaddr這個環(huán)境變量中放,放的就是MAC地址,MAC地址是由6為0-255的數(shù)字組成的。

(2)環(huán)境變量的值,在第一次啟動的時候,我們可以通過x210_sd.h中的宏配置來決定。在我們更改后,下一次啟動,環(huán)境變量的值以我們改動過的在SD卡env分區(qū)中那一份環(huán)境變量為準(zhǔn)。


3、devices_init函數(shù)

(1)設(shè)備的初始化,指的是,開發(fā)板上的硬件設(shè)備的初始化。這里初始化的是前面沒有初始化的一部分的設(shè)備初始化,只是把名字弄成了設(shè)備初始化的名字,放在這個函數(shù)中初始化的設(shè)備,都是驅(qū)動設(shè)備。這個函數(shù)本來就是從驅(qū)動框架中延生出來的。uboot中的很多設(shè)配的驅(qū)動是直接移植linux內(nèi)核中的驅(qū)動的。如網(wǎng)卡,SD卡··。

(2)這個函數(shù)在linux內(nèi)核中,就是集中的將一些設(shè)備進(jìn)行初始化。在uboot中也是如此,但是在uboot中,這個函數(shù)從linux內(nèi)核中拿過來后,有好多設(shè)備的初始化都沒有,幾乎一個都沒有用,因為在前面的階段,我們能用到的設(shè)備基本都初始化了,uboot畢竟用的少。


4、jumptable_init函數(shù)

(1)jumptable跳轉(zhuǎn)表,在uboot中,這個函數(shù)中的相關(guān)內(nèi)容,只是最為了左值,在整個uboot中,并沒有做右值被使用,所以也可以說是空有起名的。本身是一個函數(shù)指針數(shù)組,里面記錄了很多函數(shù)的函數(shù)名(函數(shù)指針),看這個陣勢,是要實現(xiàn)一個函數(shù)指針到真正函數(shù)的一個映射。將來通過跳轉(zhuǎn)表中的函數(shù)指針就可以執(zhí)行具體的函數(shù)。這個其實就是在用C語言實現(xiàn)面向?qū)ο蟮木幊?#xff0c;在linux內(nèi)核中有很多這種技巧,所以我們以前有一個說法,C語言不是面向?qū)ο蟮?#xff0c;但是linux內(nèi)核是面向?qū)ο蟮摹?/p>

(2)通過分析返現(xiàn)這個跳轉(zhuǎn)表只是被賦值,但是從來沒有被引用,因此這個跳轉(zhuǎn)表,在uboot中,根本就沒有使用他


6、6 start_armboot函數(shù)解析13

1、console_init_r函數(shù)

(1)console_init_f函數(shù)是我們console第一階段的初始化,console_init_r函數(shù)是我們第二階段的初始化,實際上我們的console_init_f第一階段的初始化并沒有做什么,只是將gd->have_console賦值為了1.第二階段的初始化才進(jìn)行了實質(zhì)性的工作。

(2)uboot中有很多同名的函數(shù),我們SI進(jìn)行索引的時候,經(jīng)常搜到的是一些不對的函數(shù)。有是自動索引找到的是錯的,真正卻沒有找到,一般情況下是一搜有很多個,一定要判斷是哪個。

(3)console函數(shù),其實是純軟件架構(gòu)方面的初始化(說白了其實就是給我們console相關(guān)的數(shù)據(jù)結(jié)構(gòu)體中,填充一個些相應(yīng)的值),比如標(biāo)準(zhǔn)輸入配置成什么,標(biāo)準(zhǔn)錯誤,標(biāo)準(zhǔn)輸出。

(4)但是實際上,我們uboot中console實際上并沒有干有意義的初始化,他就是直接調(diào)用的串口通信的函數(shù)進(jìn)行通信的,所以用不用consoleinit實際上并沒有什么分別。但是在linux內(nèi)核中console就可以提供緩沖機(jī)制,不用console就不能實現(xiàn)的東西。


2、enable_interrupts函數(shù)

(1)這個函數(shù)如果定義了那個USE_IRQ的宏,就是值的CPSR中的總中斷使能,但是我們因為我們的uboot中是用不到中斷的,所以我們沒有定義這個宏,看代碼知道,如果我們有定義這個宏那么這個函數(shù)就是一個空函數(shù),所以在這里我們的這個函數(shù)是一個空函數(shù)。

(2)因為我們的uboot中沒有使用中斷,因此沒有定義這個USE_IIRQ宏,因此我們這個函數(shù)是一個函數(shù),為的是在編譯的時候防止報錯,因為你沒有定義這個宏,你還調(diào)用了這個函數(shù),那個宏既然沒有包含,這個函數(shù)的函數(shù)體就不會存在,所以uboot中為我們提供了一個空函數(shù),在沒有定義那個宏的時候,這個函數(shù)被調(diào)用的時候,執(zhí)行的就是那個空函數(shù)。

總結(jié):uboot中經(jīng)常出現(xiàn)一個情況,就是根據(jù)一個宏是否定義了來條件編譯決定是否調(diào)用這個函數(shù),這種情況有兩種解決方案來處理這種情況,方案一就是在調(diào)用函數(shù)處使用條用編譯,函數(shù)體實際完全提供代碼。方案二就是我們在函數(shù)處直接調(diào)用函數(shù),然后在函數(shù)體處,提供兩個函數(shù)體,一個是有實體的函數(shù),一個是空函數(shù),用宏定義的條件編譯來決定實際編譯時編譯哪個函數(shù)進(jìn)去。


3、loadaddr、bootfile兩個環(huán)境變量

(1)這兩個環(huán)境變量都是內(nèi)核啟動有關(guān)的,在啟動linux內(nèi)核時會參考這兩個環(huán)境變量的值,這里猜測應(yīng)該是用在tftpboot命令下,下載內(nèi)核鏡像到內(nèi)存地址中的兩個參數(shù),一個是要下載到內(nèi)存地址中的地址,一個是從tftp服務(wù)器中下載的內(nèi)核的名字,下載到內(nèi)存地址的內(nèi)核鏡像的名字。


4、board_late_init函數(shù)

(1)實際是空的,看名字是開發(fā)板的初始化里比較晚期的初始化,就是前面該初始化的基本都初始化完了,剩下的一些必須放在后面初始化的就放在這里了,這也側(cè)面說明了,開發(fā)板級別的硬件軟件初始化告一段落了,基本結(jié)束了。對于x210來說,這個函數(shù)是空的,說明都沒有干。


6、6 start_armboot函數(shù)解析14

1、eth_initialize函數(shù)

(1)看名字是網(wǎng)卡相關(guān)的初始化,在這之前的那個函數(shù)指針數(shù)組中的boar_init中,已經(jīng)初始化了網(wǎng)卡,那這里的這個是什么呢。

(2)實際上這個函數(shù)應(yīng)該是對網(wǎng)卡芯片本身的初始化,這里不是SOC與網(wǎng)卡芯片鏈接時,我們SOC這邊的初始化。而是網(wǎng)卡芯片本身的初始化,

(3)對于x210來說,我們使用的是DM9000網(wǎng)卡來說,這個函數(shù)是空的,我們x210開發(fā)板的網(wǎng)卡DM9000的初始化在前面的那個函數(shù)指針數(shù)組中的board_init中初始化的,網(wǎng)卡芯片的初始化在驅(qū)動中,所以這個函數(shù)對于我們來說是空的。


2、x210_preboot_init

(1)x210開發(fā)板在啟動起來之前的一些初始化,也就是說開發(fā)板在運(yùn)行uboot時,進(jìn)入到uboot的命令行之前的初始化

(2)這個函數(shù)中調(diào)用了mpadfb_init函數(shù)

(3)mpadfb_init函數(shù),看名字是和LCD相關(guān)的,以及LCD屏幕上的LOGO顯示


3、check_menu_update_from_sd函數(shù)

(1)uboot啟動的最后階段設(shè)計了一個自動更新的功能,就是將我們要升級的鏡像放到SD卡的固定目錄中,然后開機(jī)時,在uboot啟動的最后階段,檢查升級標(biāo)志(是一個按鍵,按鍵中標(biāo)志為"LEFT"的那個按鍵,這個按鍵如果按鍵按下,則表示Uupdate mode 如果沒有按下,則表示boot mode),如果是update mode,則uboot會自動從SD卡中的那個固定目錄下讀取鏡像文件燒錄到iNand中,如果進(jìn)入到了boot mode中,則uboot不執(zhí)行update 直接啟動,正常運(yùn)行

(2)這種機(jī)制,能夠幫助我們快速的燒錄系統(tǒng),常用于量產(chǎn)時,用SD卡對系統(tǒng)的燒錄和部署。


4、死循環(huán)

(1)start_armboot進(jìn)入了死循環(huán),去main_loop函數(shù)執(zhí)行了。

(2)解析器

(3)開機(jī)倒數(shù)自動執(zhí)行

(4)命令的補(bǔ)全



















轉(zhuǎn)載于:https://blog.51cto.com/whylinux/1898785

總結(jié)

以上是生活随笔為你收集整理的S5PV210-uboot源码分析-第二阶段的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。