uboot


***********************************
*******day:2014/10/14**************
************uboot******************
***********************************
1.為什么要有uboot?
2.uboot是用來干嘛的?
3.uboot是怎么工作的?
4.uboot結束后的結果是怎么樣的?
為了回答以上的問題,或許問題還不止這些,根據我個人的理解來談談,請觀看者注意版權問題哈。
起初龍芯的工程師跟我講uboot的時候,我還搞不明白什么是uboot的,當時不懂事,學習之后我才明白什么是uboot的,我就舉個比喻吧:就好比如你要吃西瓜,那你吃西瓜那好歹得有把西瓜刀菜刀之類的工具吧?難道你直接用啃的嗎?這個西瓜刀就是你在吃到西瓜肉之前的uboot。所以對於為什么要有uboot,你應該懂了吧,只不過這個uboot不是用來切西瓜的,而是用來加載和啟動我們傳說中的內核的。至於什么是內核?等我寫完uboot再睡會,然后再寫關於內核這巨人。

那到底uboot是怎么樣來啟動和加載內核的呢??你看,這里又有新問題了吧?別緊張,在此之前,必須要先了解下uboot的工作原理,天啊,又是工作原理,別暈,其實當初我也暈了。其實,soc一上電,uboot瞬間就開始工作了,IROM里的固化代碼會把nandflash里的前16K代碼拷貝到IRAM里來執行,在這里實現重定位,這個IRAM的0x340000地址就是運行地址,記住這個前16K哈。為什么呢?拿個西瓜啃,慢慢往下看。

在拿到uboot的原始源碼之后,記住,你要看看這個源碼到底有沒有支持你的開發板,如果沒有就得重新找找其他版本的源碼,由於uboot這種小玩意是跟開發板緊密相連的,所以你要根據你的開發板來做一定的移植,也就是針對開發板弄一個特定的uboot,對了,這個uboot的意思我的理解就是:u(you) 啟動。
拿到源碼直接定位到startS,你可以看到異常向量表之類的匯編代碼,其實第一階段就是用匯編寫的,所以總結下第一階段的過程:
1.設置異常向量表 一開始就跳到了reset那里了
2.關閉中斷,主要是關閉F和I位,然后進入SVC32模式,記住,一定要在這種模式下 set the cpu to SVC32 mode
3.cpu初始化 bl cpu_init_crit 在這里主要是關閉緩存和MMU,至於為什么要關閉緩存,你想想,如果不關閉的話,CPU是從緩存里去指令的,這樣做的原因就是因為匹配CPU的速度,為了乖乖的讓CPU從內存里取指令,只好先關閉咯
4.關閉開門狗,關閉VIC,其實這里關閉了VIC的話,上面的F I 就沒什么必要了,你想想,老大都被抓了,小弟還有勇氣活嗎?還有開門狗,其實這個狗真的很厲害,在你程序跑飛的時候可以幫你挽救系統。但在這時,你如果不關的話,它隨時會讓你的CPU一直復位的,除非你喂狗,但是你想想,有可能喂狗嗎?
5.串口初始化,這里是為了調試在終端看信息用的,總不能用望遠鏡吧。
6.時鍾初始化,這個時鍾初始化不是說調現在幾點幾點的,而是為整個開發板提供工作頻率的。
7.內存初始化。這個內存初始化一般都是跟原廠要的代碼,比較復雜,主要是為了后面的重定位用的。
//注意注意:廣大的中國市民:6 和 7 在源碼里默認是沒有幫你初始化的,所以你要做的移植就是:是這樣的:把#if 后面的宏定義改成1就行了。也就是說,這個是沒有被編譯進去的,所以你這時候要做的就是改Makefile,添加:SOBJS := lowlevel_init.o mem_setup.o 但前提是你要修改你的內存初始化的代碼---->跟原廠要。一定要記住要確保這段代碼在nandFlash的前16K,怎么確保呢?這個問題問得好:你可以打開uboot.dis可以看到段的分區:如下:只要把代碼放在text段里就行了
.text :
{
cpu/arm_cortexa8/start.o (.text)
board/samsung/fs210/lowlevel_init.o (.text)
board/samsung/fs210/mem_setup.o (.text)
board/samsung/fs210/nand_cp.o (.text)
*(.text)
}
8.棧的初始化。一定要注意:在進入C語言代碼之前一定要確保棧已經初始化好了,否則,你后果自負。
9.重定位(這個指的是實現uboot的自重定位--->nandflash--->DDRII)這里面有點深奧,扯到Flash和DDRII的內容,在這里你只要先知道有這個步驟就行了,咦?什么,uboot是在Flash里的?沒錯,uboot在制作好的時候就是燒在Flash里的:這里給出平時燒錄的步驟
燒錄uboot://說明:這個是用tftp協議來下載的,所以,你的uboot必須要移植好,使其能支持tftp和nand erase 和nand write等命令,當然,這是比較后的知識了。其實也不是很后啦,就是在第二階段做的事而已。
cp u-boot.bin /tftpboot/
uboot1.3.4:
tftp 0x20008000 u-boot.bin
nand erase 0x0 0x40000
nand write 0x20008000 0x0 0x40000
//當實現重定位之后,(把整個u-boot拷貝考DDRII里)在DDRII里的第一階段的u-boot代碼就不會被重新執行,
//此時的pc指針自動指向第二階段的代碼,然后執行
//之前的在上電啟動的時候,u-boot被拷貝到nandflash里,由於nandflash不具備執行指令的功能,但能保證掉電后不丟失數據,所以在第一階段里要實現u-boot把自己整個拷貝到DDRII里來執行。DDRII具有執行指令的功能
10.清bss段。這個嘛,順應潮流,清吧。就當做是清垃圾吧,人人愛衛生,生活更美好
在進行清bss段之前,其實還有事情要做,也就是在DDRII里分配棧內存,也是預留一些內存,比如預留128K給環境變量
啊,等等。這些預留的空間會在以后用到,在這只要知道就行了。
11.跳轉到第二階段(c語言代碼),//所以在之前說的,一定要在跳轉到C語言代碼之前初始化好棧。不然,、、、
ldr pc, _start_armboot @ jump to C code
_start_armboot: .word start_armboot
*******************************************************************************************************
第二階段:從source insight里跳轉,即可以看到start_armboot是在board.c里面的,那么第二階段做了什么呢?
1.初始化一個全局變量指針 gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));//這兩個指針就是指向了第一階段預留的空間的某一個位置

gd->bd->bi_arch_number = MACH_TYPE_SMDKC100; //開發板的id號碼 1826---》在內核中要用到
//內核里其實也存在一個ID,它會跟你這個id進行比較,如果發現不對,而此時你又沒有通過set machid 來給內核的話,內核就會找不到你的這個開發板,這時候內核就會啟動不起來。而這個的作用主要是確保你的開發板跟內核是保持一致的。
gd->bd->bi_boot_params = PHYS_SDRAM_1 + 0x100; //傳遞給內核的數據所存放的位置
//這個變量就是你的uboot傳遞給內核的參數所在的內存位置,這個就是一個約定,你放在這個位置,內核也知道
//這個位置去取,如果你存放參數的位置不對,這時候內核取不到數據,你想他會怎么樣,它不哭不鬧不上吊,直
//接就死給你看,你拿他沒辦法的。
//這時候的參數又分為兩種情況:(在本節來說,有點扯遠了)
1、默認的參數設置:當內核發現,uboot並沒有通過set bootargs給它傳遞參數時,內核這時它不哭,它就去找
看它本身有沒有默認的參數,比如serveriP 等,如果有,謝天謝地,它直接拿來用。這個默認參數一般都設置在
開發板的配置文件比如smdkc100.h里,那怎么找呢?咦,跟讀代碼你可以發現在初始化序列里(下面)有這樣的一
個函數:env_init, /* initialize environment */
//gd->env_addr = (ulong)&default_environment[0];在這里就是使用了默認的參數設置
//gd->env_valid = 1; 這時候你就要問了?這個參數不是存放在第一階段預留的DDRII里嗎?那一掉電不就是
//丟失了數據了嗎?為什么在下次啟動的時候在沒有傳參的時候還會加載??問得好,其實這個默認參數時在
//uboot的.data段,當nandflash中沒有有效數據時就會優先使用默認的
2.你通過set bootargs給內核傳遞參數
set bootargs console=ttySAC0,115200 root=/dev/nfs nfsroot=192.168.7.99:/opt/filesystem ip=192.168.7.189 init=/linuxrc
參數說明:
console=xxx : 告訴內核printk的調試信息是通過什么設備打印出來
console=ttySAC0,115200
root=xxx: 告訴根文件系統在哪里了
root=/dev/nfs : 根文件系統存在網絡遠端
nfsroot=ip:path : 詳細地址 這個ip地址就是你Ubuntu的地址
ip=192.168.7.189 : 系統啟動之后,靜態分配ip
root=/dev/nfs + nfsroot=xxx + ip=xxx
init=xxx: 告訴內核祖先進程的可執行代碼是什么
init=/linuxrc
//廣大的中國市民:這里參數的設置,其實目的是為了讓你在網絡遠端下載的,其實都是要通過網絡的,但有沒有直接一上電就不需要從網絡下載呢?是有的,其實就是把內核,文件系統直接都燒錄在nandflash里,當然這里只是稍微提下
在跟讀代碼的時候可以看到這樣一個循環:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
這個循環實際上就是初始化序列,而可以看到其實init_sequence是一個函數指針數組(這個有點饒吧:其實就是把指向函數的指針存放在數組里,也就是這個數組存放的都是指向函數的指針)
init_fnc_t *init_sequence[] = {
#if defined(CONFIG_ARCH_CPU_INIT) //這個宏定義顯得很有意思,這種可以一次性判斷多個宏是否被定義,所以要使用該初始化函數,就必須要定義這個宏
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
board_init, /* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
#endif
timer_init, /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#endif
env_init, /* initialize environment */
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */值得注意的是:在此函數之前不允許有任何的打印
#endif
dram_init, /* configure available RAM banks */
...
};

2.nandflash的初始化 前面提到過,你要燒錄uboot,就必須要用nand erase等命令,而這些命令使用的前提就必須
要初始化nandflash ,先看下nandflash的命令用法:
nand命令:
nand read[.jffs2] - addr off|partition size 這個[.jffs2]可以先不用理他 這是一種文件系統。
nand read 0x20008000 0x0 0x100000
ARM地址 nandflash的地址 大小 從nandflash的地址讀大小為0x100000(1M)的代碼量到0x20008000這個地址里
nand erase 0x100000 0x400000
nand write[.jffs2] - addr off|partiton size
nand write 0x20008000 0x100000 0x400000
同nand read
那怎么樣才能初始化呢?跟讀代碼可以發現:
#if defined(CONFIG_CMD_NAND)//必須先定義這個宏
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
這樣算初始化了??有個init??那你就錯了,其實這個代碼跟進去,它里面做的事情就是幫你計算內存是多大而已,
printf("%u MiB\n", size / 1024);
所以,nandflash的代碼和實現uboot重定位的代碼需要自己寫的,因為每個開發板可能寄存器什么的有可能不一樣,所以在這就不列出代碼了,在這里只是要提一點://你重定位的地址一定要跟你鏈接地址要一致,不然內核找不到

在上面提到:那個環境變量為什么在下次啟動的時候在沒有傳參的時候還會加載??
首先你要明確的一點就是:nandflash本身存放的數據在掉電的情況下是不會丟失數據的,
再者,nandflash存放數據和取數據也很方便,只要三個命令即可,即nand erase / nand write /nand read
經過上面的nandflash的移植,就可以使用啦,
所以,這個涉及到環境變量的重定位問題,跟讀代碼我們可以發現這樣的一個函數:
/* initialize environment */
env_relocate ();//環境變量的重定位
|
env_relocate_spec ();
|
if(gd->env_valid == 1) {
env_ptr = tmp_env1;
}
可以看出,其實在DDRII里預留的128K的環境變量等參數其實是在nandflash的CONFIG_ENV_OFFSET為起始地址的128K重定
位到DDRII來執行的,所以每次在掉電后開機,都能執行到環境變量參數,而每次set 什么的都是在DDRII里修改的
這時候通過save就可以保存到nandflash里了,方便吧。
至於nandflash要用到的宏定義我在這里就直接給出了,僅供參考:
#define CONFIG_ENV_IS_IN_NAND 1
#define CONFIG_CMD_NAND 1
#define CONFIG_ENV_SIZE (256 << 10) /* 128KiB, 0x20000 */
#define CONFIG_ENV_OFFSET (512 << 10) /* 512KiB, 0x80000 */
#define CONFIG_SYS_MAX_NAND_DEVICE 1 //nand設備數量
#define CONFIG_SYS_NAND_MAX_CHIPS 1 //芯片的數量
#define CONFIG_SYS_NAND_BASE 0xB0E00000 // nandflash控制器的基地址
#define CONFIG_NAND_S5PV210 1 // mtd支持s5pv210的nand操作,用於控制s5pv210.c的編譯
#define CONFIG_NAND_BL1_8BIT_ECC 1
#define CFG_NAND_HWECC 1
#define CONFIG_NAND_PAGES_IN_BLOCK 64 // 每個block有多少個頁

3.網卡的初始化,使其支持tftp協議來在網絡遠端(pc機Ubuntu的/tftpboot)下載uboot,uIamge,filesystem等
這個板子用的是S5PV210是帶網卡的,DM9000內核也支持,所以只要稍微移植就行了。為什么呢?這是因為在人群
中多看了一眼??開玩笑,跟代碼可以發現這樣的貓膩:
static void dm9000_get_enetaddr(struct eth_device *dev)
{
#if !defined(CONFIG_DM9000_NO_SROM)//其實這個宏定義是有定義的,所以默認情況下DM9000是沒有獲取到mac地址的
int i;
for (i = 0; i < 3; i++)
dm9000_read_srom_word(i, dev->enetaddr + (2 * i));
#else //所以在這里需要自己手動的增加以下代碼
eth_getenv_enetaddr("ethaddr", dev->enetaddr); //從環境變量中獲取mac地址
#endif
}
如果你覺得你只要做到這一關鍵的一步,那你就錯了,其實這只是DM9000的驅動代碼而已,然后以太網呢
?在board.c中調用的eth_initialize中其實調用了board_eth_init,但實際上這個board_eth_init是沒
有的,所以需要我們來移植。在這個函數里來調用dm9000的驅動代碼
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_DRIVER_DM9000
rc = dm9000_initialize(bis);//調用dm9000的驅動
|
/* Load MAC address from EEPROM */
dm9000_get_enetaddr(dev);
#endif
return rc;
}
但是你想,這樣夠了嗎?eth_initialize你跟進去其實可以發現這個函數其實是用來調用dm9000的,但實際上以太網的初始化還是沒有調用的,所以在board.c里需要添加以太網的初始化代碼eth_init(gd->bd);
對於以太網和dm9000,那些宏定義,其實是根據跟源碼來得知要定義哪些宏定義,在此,我就直接給出來啦:
//for dm9000,添加如下內容
#define CONFIG_CMD_NET 1 //支持網絡命令
#define CONFIG_NET_MULTI 1
#define CONFIG_CMD_PING 1 // 支持ping命令
#define CONFIG_DRIVER_DM9000 1 //需要編譯dm9000的驅動
#define CONFIG_DM9000_BASE 0x88000000 // dm9000的基地址 這個基地址是根據不同硬件來確定的
#define DM9000_IO CONFIG_DM9000_BASE //dm9000的IO地址 這個也是同上
#define DM9000_DATA (CONFIG_DM9000_BASE + 4) //dm9000的數據地址 這個也是同上
#define CONFIG_DM9000_USE_16BIT //數據位寬
#define CONFIG_DM9000_NO_SROM 1 // 網卡中不會自帶rom用於存放mac地址

4.進入一個死循環,倒計時,這個倒計時是由系統定時器提供的,提供10ms的基准,
for (;;) {
main_loop ();
}
這個代碼就不跟下去了,太惡心了,有興趣的就跟吧,其實表現出來的就是五秒倒計時,在五秒內按任何鍵中止倒計時,否則,,,,就加載內核啦,當然此時要有內核放在/tftpboot里或者之前燒錄 了內核鏡像在nandflash的分區里面,不然,呵呵、、

至此,我個人覺得已經解決了以上提出來的問題,至於uboot結束后的結果是什么?個人覺得成功引導內核啟動后接下來就沒他的事了,只要啟動了內核或者可以中斷五秒進入可以設置的時候,uboot就應該say ヾ( ̄▽ ̄)Bye~Bye~了
要不然,你的uboot估計要重新檢查了,沒事,醫療費幾十萬而已啦、、、


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM