【mips-uboot】3 mips的U-Boot分析及移植

http://blog.yaabou.com/?p=96

 

要注意mips具有流水線可見性,所以跟在跳轉指令後的下一條指令,在執行跳轉到的地方前,都會執行,這個叫分支延遲。但是編譯器會隱藏該特性,但可以通過設置”.set noreorder”來禁止編譯器重新組織代碼順序。

每個板子都有自己的lds文件。這個主要是用來說明編譯生成的指令,及運行過程中用到的數據放置的位置。這個可以參考ld的手冊。比如board/dbau1x00/u-boot.lds。

OUTPUT_FORMAT(“elf32-tradbigmips”, “elf32-tradbigmips”, “elf32-tradbigmips”)
/* 這裏是生成格式爲elf。大端,mips */
OUTPUT_ARCH(mips)
/* 平臺爲mips */
ENTRY(_start) /* 入口點爲_start */
SECTIONS
{
. = 0×00000000;

. = ALIGN(4);
.text : /* 這個是程序存放的地方 */
{
*(.text)
}

. = ALIGN(4); /* 表示以4字節對齊 */
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

. = ALIGN(4);
.data : { *(.data) }

. = .;
_gp = ALIGN(16) + 0x7ff0;

.got : {
__got_start = .; /* 表示該處地址的值給__got_start */
*(.got)
__got_end = .;
}

.sdata : { *(.sdata) }

.u_boot_cmd : {
__u_boot_cmd_start = .;
*(.u_boot_cmd)
__u_boot_cmd_end = .;
}

uboot_end_data = .;
num_got_entries = (__got_end – __got_start) >> 2;

. = ALIGN(4);
.sbss (NOLOAD) : { *(.sbss) }
.bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
uboot_end = .;
}

下面來分析cpu/mips/start.S
首先從_start開始。前面是128個字(不是字節),是留給異常入口點的。
1. 最前面兩個分別是硬復位和軟復位,這兩個都跳到reset處。
2. 下面就是清一些CP0(協處理器0,mips對CPU的控制都是通過它實現的)的一些主要位。
3. 然後是關閉cache
4. 下面這個比較有意思。爲什麼還非要跳一下呢?這樣就可以知道代碼的位置,而不是標號值。比如可能在RAM中或ROM中。
bal 1f
nop
.word _gp
1:
lw gp, 0(ra)
5. 這裏執行lowlevel_init 。這是第一個需要我們自己定義的函數 。由於沒有初始化堆棧,這裏只能用匯編。我們看到在jalr後跟了個nop,這就是分支延遲槽了,在這裏什麼也沒有執行。
6. 下面執行了mips_cache_reset,它會來清理數據和指令的cache,並設置爲正確的值。然後就可以打開cache了。
7. 由於我們的內存可能還沒有始初化(有些人會有lowlevel_init中初始化,但有的人沒有這樣做)。但我們使用C函數的話,就需要堆棧,所以需要一 個內存空間。於是這裏執行了mips_cache_lock,將cache的地址鎖定,就是將cache當內存用了。然後我們將堆棧的地址設定在我們鎖定 的cache的最高地址(因爲堆棧是向下生長的)。這時我們就可以用C函數了,當然你還用不了malloc,也不可以太多的浪費堆棧。
8. 這裏就跑到C的初始化函數中去了--board_init_f。

對於mips,board_init_f在lib_mips/board.c下。在board_init_f()函數中,主要完成了一些功能初始化,和劃分RAM。
/* 所以最後RAM中是這樣子的
—RAM 0×0000,0000—
…………
— ^ SP ^ —
— boot params — CONFIG_SYS_BOOTPARAMS_LEN bd->bi_boot_params
— Global Data — sizeof(gd_t) gd
— Board Info — sizeof(bd_t) gd->bd = bd
— mallco(+env) — CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE
— uboot code — 16kB
—RAM end —
*/
再看一下都初始化了什麼功能。初始化的函數都在init_fnc_t *init_sequence[]裏。
init_fnc_t *init_sequence[] = {
board_early_init_f, /* 一些必要,需在之前做的初始化,如想使用需定義CONFIG_BOARD_EARLY_INIT_F */
timer_init, /* 初始化時鐘計數,cp0的 */
env_init, /* 環境變量保存在flash中 */
#ifdef CONFIG_INCA_IP
incaip_set_cpuclk, /* 根據cpuclk環境變量設定CPU主頻 */
#endif
init_baudrate, /* 根據baudrate環境變量設定gd->baudrate */
serial_init , /* 設定串口速率,需要我們自己寫(包括其它serial的) */
console_init_f, /* 設置gd->have_console=1,有CONFIG_SILENT_CONSOLE則查看silent */
display_banner, /* 打印uboot信息 */
checkboard , /* 檢測板子,可以在這打印設備信息,需要我們自己寫 */
init_func_ram, /* 設置gd->ram_size,initdram需要我們自己寫 */
NULL, /* 最後這個空必須留着,檢查結束 */
};
最後這個函數調用了relocate_code (addr_sp, id, addr)。注意,這個函數,準確的說不是函數(因爲不能返回),是不返回的。

現在我們又回到start.S中了。我們可以看到,這裏和C語言傳遞參數是用a0,a1,a2。relocate_code的工作就是將代碼搬移到RAM中執行。這裏做的工作是:
1. 移動gp指針
2. 複製代碼到RAM中
3. 刷新一下cache
4. 跳到RAM代碼當中去(in_ram)

in_ram的主要工作是:更新GOT;清空BSS段;最後跳到board_init_r。我們可以看到board_init_r最後一個參數是在分支延遲槽中賦值的。
這其實這裏主要說一下GOTs(global offset tables)這個東東,這是uboot能跳轉到不同空間運行的原理。uboot編譯時用到了PIC(position-independent code)(也可以說成position-independent executable (PIE))。這個其實是很早之前,在沒有MMU的年代引進來的東西。爲了在沒有MMU時,不同進程也能同時運行,就需要他們的運行地址可以改變。 GOTs用來保存所有的全局變量地址,所以我們只要改變GOTs的值就可以了。gp就是指向GOTs位置的指針。這個功能需要在gcc編譯時指定 -fpic。然後就像我們看到的,我們只要改變GOTs裏的值,加上地址偏移就可以了。

下面再看一下board_init_r。這裏的工作:
1. 複製cmd段的信息過來。這裏只複製了cmd,name,usage,usage。幫助信息的字符串還在flash中。
2. 然後是初始化malloc功能。注意這裏env有malloc的方式分配到了空間,並複製到RAM。
3. 再就是stdio,串口,入口函數,以及全局變最根據env的初始化了。
4. 再接着就是網絡的初始化。eth_initialize(gd->bd)。對於mips,如果設了CONFIG_NET_MULTI。我們需要自己寫board_eth_init和cpu_eth_init 兩個函數。 只有前者返回值小於0時,我們才需要執行後者。
5. 最後進入main_loop()。

以太網驅動移植
對於mips,如果設了CONFIG_NET_MULTI。會用到board_eth_init()和cpu_eth_init()兩個函數。只有前者返 回值小於0時,纔會執行後者。這裏要初始化結構體eth_device,然後使用eth_register()註冊網絡設備。主要要設定 init,halt,send,recv四個函數,及其它一些變量。如支持mii讀寫命令還需使用miiphy_register()註冊mii讀寫的兩 個函數。
當註冊完成後,U-Boot不會自動調用init,而是隻有當用到網絡設備時,才後調用。每次使用網絡接口時,是先調用halt之後,才調用init。

當我們使用命令make (型號)_config時,會產生以下效果。
(1) 將include/asm軟鏈接到include/asm-(ARCH)。
(2) 修改include/config.h文件,增加#include <configs/(型號).h>和#include <asm/config.h>。
(3) 在include/config.mk設置變量ARCH,CPU,BOARD,VENDOR。
編譯時,Makefile會包含進幾個目錄的config.mk文件。board/xxx/(xxx/)config.mk在前面已經介紹過了。在 lib_xxx/config.mk中會指定交叉編譯器,及增加平臺相關的編譯參數。在cpu/xxx/config.mk會指定體系結構的相關參數,如 大小端。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章