uboot移植之啟動過程詳解2
/*******************************************************************************
uboot啟動過程之第二階段的分析(board.c的分析)
時間:201812月中旬
者:cryil_先森
以朱有鵬課程中uboot-samsung-dev為分析物件
*********************************************************************************/
巨集觀分析:
初始化第一階段結束還沒被初始化的硬體;SOC的外部硬體(inand,網絡卡等);uboot本身的一些東西(uboot的命令,環境變數),最終初始化完進入uboot的命令列準備接受命令。
在這個C語言程式之中,大部分是對於環境變數的初始化,對於函式的定義和初始化,真正這些變數和函式使用的地方是在void start_armboot(void) 函式之中,統籌了板子的硬體的初始化。
uboot的啟動概述:
主要是對開發板級別的硬體的初始化、軟體的資料結構進行初始化。uboot在啟動完後打印出很多的資訊,然後進入倒數bootdelay秒後執行bootcmd對應的啟動命令。如果使用者沒有進行干涉則會執行bootcmd進入自動啟動核心流程(此時uboot就死掉了),此時使用者可以按下回車鍵打斷uboot的自啟動過程進入uboot的命令列下,然後uboot就會一直工作在命令列下。而uboot的命令列就是一個死迴圈,迴圈體內不斷地重複:接受命令,解析命令,執行命令。
***1.一個重要的函式指標陣列init_sequence[]:
cpu_init, 空的
reloc_init,
board_init, 網絡卡,機器碼,記憶體傳參地址
interrupt_init, 定時器
env_init,
init_baudrate, 波特率資料結構
serial_init, 串列埠初始化(空的)
console_init_f, 空的
display_banner, 列印啟動資訊
print_cpuinfo, 打印出CPU時鐘設定資訊
checkboard, 檢驗開發板名字
init_func_i2c,
dram_init, 初始化gd資料結構的
display_dram_config, 打印出DDR的配置資訊
***2.其他
mem_malloc_init 初始化堆記憶體
mmc_initialize inand/SD卡的SOC控制器和卡的初始化
env_relocate (); 環境變數的重定位
devices_init (); 基本為空的
jumptable_init (); 無用的
console_init_r() 真正的控制檯初始化
enable_interrupts 基本為空的
loadaddr和bootfile 環境變數的讀出,初始化全域性變數
board_late_init() 最後的補充初始化函式,基本為空
eth_initialize() 網絡卡晶片本身的一些硬體初始化
main_loop() uboot工作的死迴圈。
*******************************************************************************************************************************************
具體的分析如下:
***1.引用了一個全域性變數:
DECLARE_GLOBAL_DATA_PTR
這個全域性變數在Global_data.h標頭檔案之中被定義
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
DECLARE_GLOBAL_DATA_PTR其實是一個全域性變數gd,這個全域性變數在asm暫存器r8位被使用 同時這個全域性變數是一個指向gd_t型別變數的指標。
***2.引出了兩個結構體gd_t和bd_t (Global_data.h中)
**1.gd_t
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console; /* serial_init() was called */
unsigned long reloc_off; /* Relocation Offset */
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Checksum of Environment valid? */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
#if 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 */
} gd_t;
分析:這個全域性變數實際上是一個結構體,結構體內是uboot中常用的所有全域性變數。而bd_t 則是一個和開發板版級資訊直接相關的結構體,結構體內包含:板子的硬體資訊,IP地址,機器碼,
以及DDR記憶體分佈情況的結構體bi_dram[CONFIG_NR_DRAM_BANKS]等。
**2.bd_t:
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
定義了波特率
unsigned long bi_ip_addr; /* IP Address */
定義了IP地址
unsigned char bi_enetaddr[6]; /* Ethernet adress */
定義了一個數組,用來存在記憶體塊
struct environment_s *bi_env;
定義了環境變數
ulong bi_arch_number; /* unique id for this board */
定義了機器碼
ulong bi_boot_params; /* where this board expects params */
定義了核心的傳參地址
struct /* RAM configuration */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
定義了一個關於DDR的結構體
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
***3.一系列初始化函式的定義:(在後面的程式之中會給出初始化函式的執行順序)
**1.version_string[]陣列:
const char version_string[] =
U_BOOT_VERSION" (" __DATE__ " - " __TIME__ ")"CONFIG_IDENT_STRING;
分析:定義了一個字串陣列,這個陣列內記錄了板子的資訊和燒錄資訊。baudratebaudrateversion_string 這個引數的一部分是主Makefile傳過來的,代表板子的版本號; " (" __DATE__ " - " __TIME__ ")"是燒錄時的時間資訊;CONFIG_IDENT_STRING是板子對應的晶片資訊,而這個字串陣列在之後被引用 display_banner, /* say that we are here */用串列埠列印顯示出uboot的logo,(此時控制檯還未完全初始化完成,通過一系列函式的呼叫直接控制串列埠暫存器,列印輸出uboot的logo)
static int init_baudrate (void) 波特率的初始化函式
static int display_banner (void) 串列埠列印初始化函式
**2一個重要的函式指標陣列init_sequence[]
大致進行一下操作:板級硬體的初始化,gd、gd-bd的初始化,網絡卡的初始化,機器碼(gd-bd-bi_arch_number),核心傳參DDR的地址(gd->bd->bi_boot_params),波特率,打印出CPU的啟動資訊,打印出CPU的相關設定資訊,檢查並列印當前開發板名字,DDR的配置資訊的初始化。
定義了一個init_fnc_t型別的陣列,陣列記憶體放的是指標(實際上是陣列內函式的首地址)
陣列內儲存了很多個函式指標,這些指向的函式都是init_fnc_t型別的(接受void型別引數返回int型引數)。init_sequence在定義之初同時被初始化,初始化的函式指標都是一些函式名(函式名可以做指標使用),而這些函式都是board級別的硬體初始化函式。
typedef int (init_fnc_t) (void);
int print_cpuinfo (void); /* test-only */
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */
初始化CPU
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
reloc_init, /* Set the relocation done flag, must
do this AFTER cpu_init(), but as soon
as possible */
#endif
board_init, /* basic board dependent setup */
詳見下面
interrupt_init, /* set up exceptions */
初始化定時器
env_init, /* initialize environment */
初始化環境變數
init_baudrate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
控制檯初始化
注:在初始化時不能一次初始化完成時分兩步進行,_f第一階段 _r第二階段
display_banner, /* say that we are here */
打印出version_string
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
打印出CPU的資訊(波特率)
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
打印出板子的資訊
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
關於DDR的初始化,其實在彙編階段已經進行了一次初始化(從硬體的角度)
檢視原函式可知此次的初始化規定了各個記憶體塊的起始地址和大小
display_dram_config,
NULL,
};
列印顯示DDR的配置資訊
**3.板子的初始化細究(board_init):
int board_init(void)
{
DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911X
smc9115_pre_init();
#endif
#ifdef CONFIG_DRIVER_DM9000
dm9000_pre_init();
#endif
分析:再次引用了gd這個結構體,主要完成網絡卡的GPIO和埠的配置,而不是驅動,移植時驅動不用修改,只用改動初始化函式。
gd->bd->bi_arch_number = MACH_TYPE;
分析:bi_arch_number:開發板的機器碼,是board.info的一個元素。在uboot和Linux核心之間進行比對和配置。由於嵌入式裝置的高度定製化,導致軟體和硬體不能隨意適配使用。板子的機器碼和uboot、Linux的核心進行比對,然後決定是否啟動。uboot中配置的機器碼會作為引數傳給Linux核心,核心啟動時會進行再次比對決定是否啟動。
gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);
return 0;
}
分析:bi_boot_params是uboot傳給Linux的引數,代表Linux核心啟動時的記憶體地址,之後uboot就能啟動核心(啟動過程其實是暫存器R0 R1 R2來直接傳遞引數的) uboot事先準備好引數bootargs(字串)放在記憶體之中,通過uboot的傳參,核心啟動時就會知道bootargs的記憶體地址(0x20000100)。
注:初始化DDR(和彙編階段lowlever_init中的初始化DDR是不同的,當時只是進行硬體的初始化,使得DDR能夠開始工作),進行軟體結構中的一些DDR相關的屬性配置、地址設定的初始化。
在uboot設計時,開發者在***.h(板載標頭檔案)中使用巨集定義去配置出來DDR的記憶體資訊,然後uboot只需讀取這些資訊即可知道DDR的記憶體資訊。
PHYS_SDRAM_1是DDR的地址資訊,同時在它定義的地方之後也定義了DDR的個數,大小。
同時也對DDR的各個分割槽做出了定義,定義了DDR各個分割槽大小,起始地址
int dram_init(void)
{
DECLARE_GLOBAL_DATA_PTR;
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
#if defined(PHYS_SDRAM_2)
gd->bd->bi_dram[1].start = PHYS_SDRAM_2;
gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
#endif
return 0;
}
***4.板子初始化啟動過程的整體架構 void start_armboot (void)
**1.定義了一個二重函式指標init_fnc_ptr:
(這裡用來指向一個函式指標陣列)
init_fnc_t **init_fnc_ptr;
typedef int (init_fnc_t) (void);
char *s;
int mmc_exist = 0;
**2.一系列的判斷是否初始化:
#if !defined(CFG_NO_FLASH) || defined (CONFIG_VFD) || defined(CONFIG_LCD)
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
#if defined(CONFIG_BOOT_MOVINAND)
uint *magic = (uint *) (PHYS_SDRAM_1);
#endif
/* Pointer is writable since we allocated a register for it */
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */
ulong gd_base;
**3.uboot的記憶體排布:
gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
由於gd和bd都是一個指標型別的全域性變數,gd和bd變數在使用前需要被分配記憶體,而此時是裸機狀態,系統不會自動的進行記憶體的分配,只能人為的為其指定記憶體區域。而gd_base規定了我們指標開始訪問的地址,為指標找到了一塊安全的記憶體空間。(詳見資料夾內的圖片)
分析:gd_base是gd這段記憶體存放在記憶體之中的起始地址。
CFG_UBOOT_BASE:uboot程式碼的起始地址。(uboot實際大小為200kb左右)
CFG_UBOOT_SIZE:系統為uboot分配的記憶體空間(2MB)
CFG_MALLOC_LEN:堆區,長度為912KB
CFG_STACK_SIZE:棧區,長度為512kb
sizeof(gd_t):gd全域性變數的記憶體空間,長度為36位元組
bd:bd全域性變數的記憶體空間,長度為位元組。
綜上所述:gd的記憶體地址在uboot起始地址+600kb左右的空間內,而一般情況下uboot只有200kb左右,兩者之間有著400kb左右的間隔。
#ifdef CONFIG_USE_IRQ
gd_base -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
#endif
gd = (gd_t*)gd_base;
分析:將gd_base代表的數字強制型別轉化為指標型別然後賦值給gd,此時gd就指向了這段存放gd全域性變數的記憶體地址。
#else //CONFIG_USE_IRQ
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
#endif //CONFIG_MEMORY_UPPER_CODE
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;
分析:在使用之前清除棧中的變數。
**4.板子初始化的真正開始:
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
for迴圈內從第一個函式cpu_init開始,遍歷整個函式陣列,從而依次呼叫每一個函式,而陣列內的每一個函式的返回值都是0,依次將每一個函式的返回值和0進行判斷,從而決定是否執行hang ()。
而hang ()函式為:
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
hang ()是一個啟動過程錯誤判斷函式,如果某一個函式執行錯誤,uboot的啟動就會中止,然後列印錯誤。
***5.一些零碎的初始化、定義。
**1.CFG_NO_FLASH
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
#endif /* CFG_NO_FLASH */
分析:在啟動時列印顯示出nand的記憶體大小。
**2.CONFIG_VFD和CONFIG_LCD都是顯示相關的
分析:uboot中自帶的lcd顯示架構,但是在啟動時實際沒有使用,我們在後面自己添加了一個lcd顯示的部分。
**3.DDR堆的初始化
mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);
分析:具體看mem_malloc_init函式可知,括號內傳入的引數是堆的起始地址,在初始化函式中申請了堆記憶體,並規定了堆的大小。從而達到自己維護堆記憶體的目的。
**4.判斷適配出和自己板子相同的檔案然後進行下一步的操作。
#if defined(CONFIG_SMDKC110)
#if defined(CONFIG_GENERIC_MMC)
puts ("SD/MMC: ");
mmc_exist = mmc_initialize(gd->bd);
if (mmc_exist != 0)
{
puts ("0 MB\n");
}
#endif
#if defined(CONFIG_MTD_ONENAND)
puts("OneNAND: ");
onenand_init();
/*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
#else
//puts("OneNAND: (FSR layer enabled)\n");
#endif
#if defined(CONFIG_CMD_NAND)
puts("NAND: ");
nand_init();
#endif
分析:mmc_exist = mmc_initialize(gd->bd);
初始化SOC內部的SD/MMC控制器,實際上再次呼叫了board_mmc_init和cpu_mmu_init函式來完成具體的硬體MMC控制器的初始化。
注:uboot對硬體的初始化操作(網絡卡,SD卡..)都是通過借用Linux核心中的驅動來實現的在uboot/drivers內都是從核心移植過來的各種驅動的原始碼。
**5.環境變數的匯入
#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();
dataflash_print_info();
#endif
/* initialize environment */
env_relocate ();
分析:由於uboot中使用的是SD卡,不是DATAFLASH,所以直接進行endif的語句,進行環境變數的重定位,將SD卡的環境變數讀取到DDR中。
#ifdef CONFIG_VFD和#ifdef CONFIG_SERIAL_MULT均與我們的板子無關
**6.IP地址和MAC地址的獲取
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
IPaddr_t getenv_IPaddr (char *var)
{
return (string_to_ip(getenv(var)));
}
分析:開發板的IP地址用環境變數gd->bd->bi_ip_addr來表示。
getenv_IPaddr函式用來獲取字串格式的IP地址,然後用string_to_ip函式字串格式的IP地址轉換成點分式十進位制格式。(也就是通常意義的IP地址)
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
分析:MAC Address是網絡卡的硬體地址。 用環境變數gd->bd->bi_enetaddr來表示。
可以看出來,網絡卡的硬體地址由六部分構成,由函式getenv_r獲取,在經過非零判斷最後將其賦值給gd->bd->bi_enetaddr。
**7.驅動裝置的初始化
devices_init (); /* get the devices list going. */
分析:直接從驅動架構框架衍生出來的,來源於Linux核心中驅動的原始碼,作用就是集中執行各種硬體驅動的init函式。
**8.定義了一個跳轉表
jumptable_init ();
分析:其實本身是一個函式指標陣列,裡面記錄了很多函式名,將來可以使用跳轉表內的函式指標執行相應的函式
(相當於init_fnc_t *init_sequence[])這就實現了用C語言完成面向物件的程式設計。但是分析可知跳轉表只是被賦值,並沒有被使用過。
**9.控制檯的第二階段的初始化
#if !defined(CONFIG_SMDK6442)
console_init_r (); /* fully init console as a device */
#endif
分析:console_init_r是console(控制檯)純軟體配置架構方面的初始化,屬於純軟體配置型別的初始化。他直接呼叫串列埠通訊的函式。
**10.中斷的初始化函式
enable_interrupts ();
分析:CPSR總中斷標誌位的使能。
**11.兩個環境變數loadaddr和bootfile
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if defined(CONFIG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif
分析:核心啟動有關的環境變數,在啟動核心時會參考這兩個環境變數的值。
**12.開發板級別的最後部分的初始化
#ifdef BOARD_LATE_INIT
board_late_init ();
#endif
**13.網絡卡晶片本身的一些硬體初始化
eth_initialize(gd->bd);
分析:這裡的初始化不是SOC與網絡卡連線時SOC的初始化,只是本身的初始化。
**14.關於IDE的初始化
#if defined(CONFIG_CMD_IDE)
puts("IDE: ");
ide_init();
#endif
**15.啟動成功,uboot進入死迴圈
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
/* NOTREACHED - no way out of command loop except booting */
}
分析:uboot進入死迴圈,不間斷的進行檢測命令,執行命令的操作。
main_loop的功能:解析器;開機倒數執行;命令的補全
**16.啟動錯誤列印函式
void hang (void)
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
分析:在之前的start_armboot函式中有使用,當啟動過程有任意一個初始化出錯時,就會打印出### ERROR ### Please RESET the board ###,一些初始化歸零,啟動失敗。
注:DDR內的一部分記憶體分佈情況: