從uboot開始:
1.1 Uboot的主流程第2階段(C)
該函數進行了一系列的外設初始化,然後調用main_loop (),根據配置來選擇是直接加載Linux內核還是進入等待命令模式。瞭解start_armboot()的主要功能:
圖2
圖3
1.1.1 start_armboot()分析
(1) 爲gd申請並初始化這塊內存
- gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
- /* compiler optimization barrier needed for GCC >= 3.4 */
- __asm__ __volatile__("": : :"memory");
- 這行語句參考http://blog.csdn.net/loongembedded/article/details/43371909
- 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;
/* Pointer is writable since we allocated a register for it */ gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)); /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__("": : :"memory");這行語句參考http://blog.csdn.net/loongembedded/article/details/43371909 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
_armboot_start等的地址是怎麼知道的呢,我們可以使用readelf命令來elf文件的信息,而elf文件分三種類型:目標文件(通常是.o)、可執行文件(我們的運行文件)、動態庫(.so)。
我們這裏可以通過readelf –s u-boot|grep _start來獲取包含_start的標號信息
圖5
gd_t和bd_t是u-boot中兩個重要的數據結構,在初始化操作很多都要靠這兩個數據結構來保存或傳遞
1) gd_t結構體
在bootable\bootloader\uboot\arch\arm\include\asm\global_data.h定義
- * The following data structure is placed in some memory wich is
- * available very early after boot (like DPRAM on MPC8xx/MPC82xx, or
- * some locked parts of the data cache) to allow for a minimum set of
- * global variables during system initialization (until we have set
- * up the memory controller so that we can use RAM).
- *
- * Keep it *SMALL* and remember to set CONFIG_SYS_GBL_DATA_SIZE > sizeof(gd_t)
- */
- typedef struct global_data {
- bd_t *bd;// bd指針指向bd_info這個結構體,保存板子的相關參數
- unsigned long flags; //指示標誌,如設備已經初始化標誌等
- unsigned long baudrate; // 串口的波特率
- unsigned long have_console; /* serial_init() was called */
- 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
- #ifdef CONFIG_FSL_ESDHC
- unsigned long sdhc_clk;
- #endif
- #if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))
- unsigned long tlb_addr;
- #endif
- void **jt; /* jump table */
- } gd_t;
- #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
/* * The following data structure is placed in some memory wich is * available very early after boot (like DPRAM on MPC8xx/MPC82xx, or * some locked parts of the data cache) to allow for a minimum set of * global variables during system initialization (until we have set * up the memory controller so that we can use RAM). * * Keep it *SMALL* and remember to set CONFIG_SYS_GBL_DATA_SIZE > sizeof(gd_t) */typedef struct global_data { bd_t *bd;// bd指針指向bd_info這個結構體,保存板子的相關參數 unsigned long flags; //指示標誌,如設備已經初始化標誌等 unsigned long baudrate; // 串口的波特率 unsigned long have_console; /* serial_init() was called */ 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#ifdef CONFIG_FSL_ESDHC unsigned long sdhc_clk;#endif#if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE)) unsigned long tlb_addr;#endif void **jt; /* jump table */} gd_t;#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
這個聲明告訴編譯器使用寄存器r8來存儲gd_t類型的指針gd,即這個定義聲明瞭一個指針,並且指明瞭它的存儲位置。register表示變量放在機器的寄存器
volatile用於指定變量的值可以由外部過程異步修改
2) bd_t結構體
在bootable\bootloader\uboot\arch\arm\include\asm\u-boot.h。board info數據結構定義,主要用於保存板子的參數
- int bi_baudrate; /* serial console baudrate */
- unsigned long bi_ip_addr; /* IP Address */
- 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[MAX_NR_BANK];
- } bd_t;
- #define bi_env_data bi_env->data
- #define bi_env_crc bi_env->crc
- MAX_NR_BANK在\mediatek\platform\mt6577\uboot\inc\asm\arch\mt65xx.h定義:
- #define MAX_NR_BANK 4
typedef struct bd_info { int bi_baudrate; /* serial console baudrate */ unsigned long bi_ip_addr; /* IP Address */ 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[MAX_NR_BANK];} bd_t;#define bi_env_data bi_env->data#define bi_env_crc bi_env->crcMAX_NR_BANK在\mediatek\platform\mt6577\uboot\inc\asm\arch\mt65xx.h定義:#define MAX_NR_BANK 4
(1) 調用init_sequence()進行板級初始化
- {
- if ((*init_fnc_ptr)() != 0) {
- hang ();
- }
- }
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); } }
init_sequence是一個全局的指針數組,也就是它的每個成員保存的指向init_fnc_t類型的函數指針。
- init_fnc_t *init_sequence[] = {
- cpu_init, /* basic cpu dependent setup */
- dram_init, /* configure available RAM banks */ /* change the original init order */
- 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 */
- display_banner, /* say that we are here */
- #if defined(CONFIG_DISPLAY_CPUINFO)
- print_cpuinfo, /* display cpu info (and speed) */
- #endif
- #if defined(CONFIG_DISPLAY_BOARDINFO)
- checkboard, /* display board info */
- #endif
- display_dram_config,
- NULL,
- };
typedef int (init_fnc_t) (void);init_fnc_t *init_sequence[] = { cpu_init, /* basic cpu dependent setup */ dram_init, /* configure available RAM banks */ /* change the original init order */ 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 */ display_banner, /* say that we are here */#if defined(CONFIG_DISPLAY_CPUINFO) print_cpuinfo, /* display cpu info (and speed) */#endif#if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */#endif display_dram_config, NULL,};
下面依次簡要介紹這些初始化函數
1) cpu_init()
在\bootable\bootloader\uboot\arch\arm\cpu\arm_cortexa9\cpu.c
定義中斷棧和快速中斷棧空間的地址
2) dram_init()
在\mediatek\platform\mt6577\uboot\mt6577.c中定義
DRAM的初始化,這裏只是對gd中的 bi_dram結構中的兩個成員賦值,也即BANK的起始地址和大小。
3) board_init()
在\mediatek\platform\mt6577\uboot\mt6577_board.c中定義
此函數主要是進行串口初始化,看門狗初始化、GPIO引腳初始化、LCD背光初始化、初始化PMIC6329的ISINK0(是一種恆流源,一般可用於控制背光,但是我們的電流設計是用另外一個單獨的IC來控制背光的)、顯示控制器初始化、PMIC6329初始化。
此函數採用下面的代碼爲設備的board id進行了初始化。
gd->bd->bi_arch_number= MACH_TYPE_MT6577; /* board id for linux*/
這是機器的machine ID,uboot中和內核中的必須是一致,否則系統無法啓動。
4) interrupt_init()
在\bootable\bootloader\uboot\arch\arm\lib\interrupts.c中定義,但是實際上什麼都沒有做,直接返回0。
5) env_init()
在\bootable\bootloader\uboot\common\env_nowhere.c下定義
初始化默認的環境變量,如果沒有找到有效的環境變量,就採用這個默認的。
- * Initialize Environment use
- *
- * We are still running from ROM, so data use is limited
- */
- int env_init(void)
- {
- gd->env_addr = (ulong)&default_environment[0];
- gd->env_valid = 0;
- //printf("Env_nowhere.c--->env_init()\n"));
- return (0);
- }
/************************************************************************ * Initialize Environment use * * We are still running from ROM, so data use is limited */int env_init(void){ gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 0; //printf("Env_nowhere.c--->env_init()\n")); return (0);}
6) init_baudrate()
在\bootable\bootloader\uboot\arch\arm\lib\board.c中定義,用於初始化串口波特率。
7) serial_init()
在\mediatek\platform\mt6577\uboot\mtk_serial.c下定義,但這裏實際什麼都沒有做,因爲在前面的board_init()就調用mtk_serial_init()初始化串口了。
8) console_init_f()
在\bootable\bootloader\uboot\common\console.c定義
初始化控制檯,主要是設置全局變量gd的have_console和flags成員。
9) print_cpuinfo()
在\bootable\bootloader\uboot\arch\arm\lib\board.c中定義,但此函數什麼都沒有做。
10) display_dram_config()
在\bootable\bootloader\uboot\arch\arm\lib\board.c中定義,用於顯示DRAM的地址和大小,如此函數輸出的串口信息:
RAM Configuration:
Bank #0: 01600000 490MiB
Bank #1: 20000000512 MiB
(2) env_relocate()
在\bootable\bootloader\uboot\common\env_common.c定義.
u-boot的環境變量用來存儲一些經常使用的參數變量,uboot希望將環境變量存儲在靜態存儲器中(如nand nor eeprom mmc)。其中有一些也是大家經常使用,有一些是使用人員自己定義的,下面的表中我們列出了一些常用的環境變量:
bootdelay 執行自動啓動的等候秒數
baudrate 串口控制檯的波特率
netmask 以太網接口的掩碼
ethaddr 以太網卡的網卡物理地址
bootfile 缺省的下載文件
bootargs 傳遞給內核的啓動參數
bootcmd 自動啓動時執行的命令
serverip 服務器端的ip地址
ipaddr 本地ip地址
stdin 標準輸入設備
stdout 標準輸出設備
stderr 標準出錯設備
上面這些是uboot默認存在的環境變量,uboot本身會使用這些環境變量來進行配置。我們可以自己定義一些環境變量來供我們自己uboot驅動來使用。
Uboot環境變量的設計邏輯是在啓動過程中將env從靜態存儲器中讀出放到RAM中,之後在uboot下對env的操作(如printenv editenv setenv)都是對RAM中env的操作,只有在執行saveenv時纔會將RAM中的env重新寫入靜態存儲器中。
這種設計邏輯可以加快對env的讀寫速度。
env_relocate()主要做了兩件事:
1) 爲環境變量分配buffer。
2) 由於在前面調用的env_init()設置gd->env_valid = 0;,所以這裏會調用set_default_env()來設置默認的環境變量。
- {
- if (sizeof(default_environment) > ENV_SIZE) {
- puts ("*** Error - default environment is too large\n\n");
- return;
- }
- memset(env_ptr, 0, sizeof(env_t));
- memcpy(env_ptr->data, default_environment,
- sizeof(default_environment));
- #ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT
- env_ptr->flags = 0xFF;
- #endif
- env_crc_update ();
- gd->env_valid = 1;
- }
void set_default_env(void){ if (sizeof(default_environment) > ENV_SIZE) { puts ("*** Error - default environment is too large\n\n"); return; } memset(env_ptr, 0, sizeof(env_t)); memcpy(env_ptr->data, default_environment, sizeof(default_environment));#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT env_ptr->flags = 0xFF;#endif env_crc_update (); gd->env_valid = 1;}
(3) stdio_init()
在\bootable\bootloader\uboot\common\stdio.c定義。設置標準的輸出輸入描述符表
(4) mt65xx_bat_init()
在\mediatek\platform\mt6577\uboot\mt6577_bat.c定義,主要關機充電和拔出充電器關機。對於正常的啓動來說,只是執行了關閉LED等和進行過電流檢測(Over-current)。
(5) mt65xx_backlight_on()
在\mediatek\platform\mt6577\uboot\mt65xx_leds.c定義,打開背光
(6) main_loop()
在\bootable\bootloader\uboot\common\main.c定義,主要做的都是與具體平臺無關的工作,主要包括初始化啓動次數限制機制、設置軟件版本號、打印啓動信息、解析命令等,這部分後面作爲單獨的一部分來學習。
參考鏈接:
start_armboot分析
cpu_init, /* basic cpu dependent setup */
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 */
display_banner, /* say that we are here */
dram_init, /* configure available RAM banks */
display_dram_config,
#if defined(CONFIG_VCMA9)
checkboard,
#endif
NULL,
};
{
DECLARE_GLOBAL_DATA_PTR;
init_fnc_t **init_fnc_ptr; ///point initial function's pointer
char *s;
#if defined(CONFIG_VFD)
unsigned long addr;
#endif
#if CFG_LED_FLASH
LED(); ///all leds power on flash once
#endif
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
///This for{} init arry init_sequence[] paraments one by one
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();///f init fail output "error" and go to dead cycle,if that we must reset cpu
}
}
size = flash_init (); ///init flash source code don't understand???
display_flash_config (size); /// print flash's config information
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096 //若沒定義則用4096
# endif
/*
* reserve memory for VFD display (always full pages)
*/
/* armboot_end is defined in the board-specific linker script */
addr = (_bss_start + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_VFD */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
puts ("NAND:");
nand_init(); /* go init the NAND */
#endif
AT91F_DataflashInit();
dataflash_print_info();
#endif
env_relocate ();
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
{
int i;
ulong reg;
char *s, *e;
uchar tmp[64];
s = (i > 0) ? tmp : NULL;
gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
}
/* miscellaneous platform dependent initialisations */
misc_init_r ();
#endif
enable_interrupts ();
#ifdef CONFIG_DRIVER_CS8900
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif
if (getenv ("ethaddr")) {
smc_set_mac_addr(gd->bd->bi_enetaddr);
}
/* eth_hw_init(); */
#endif /* CONFIG_DRIVER_LAN91C96 */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
#if (CONFIG_COMMANDS & CFG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));
}
#endif /* CFG_CMD_NET */
board_late_init ();
#endif
for (;;) {
main_loop ();
}
}
{
puts ("### ERROR ### Please RESET the board ###\n");
for (;;);
}
/* called from main loop (common/main.c) */
extern void dbg(const char *fmt, ...);
int mdm_init (void)
{
char env_str[16];
char *init_str;
int i;
extern char console_buffer[];
static inline void mdm_readline(char *buf, int bufsiz);
extern void enable_putc(void);
extern int hwflow_onoff(int);
init_str = getenv("mdm_flow_control");
if (init_str && (strcmp(init_str, "rts/cts") == 0))
hwflow_onoff (1);
else
hwflow_onoff(-1);
#endif
sprintf(env_str, "mdm_init%d", i);
if ((init_str = getenv(env_str)) != NULL) {
serial_puts(init_str);
serial_puts("\n");
for(;;) {
mdm_readline(console_buffer, CFG_CBSIZE);
dbg("ini%d: [%s]", i, console_buffer);
(strcmp(console_buffer, "ERROR") == 0)) {
dbg("ini%d: cmd done", i);
break;
} else /* in case we are originating call ... */
if (strncmp(console_buffer, "CONNECT", 7) == 0) {
dbg("ini%d: connect", i);
return 0;
}
}
} else
break; /* no init string - stop modem init */
}
for(;i > 1;) { /* if 'i' > 1 - wait for connection
message from modem */
mdm_readline(console_buffer, CFG_CBSIZE);
dbg("ini_f: [%s]", console_buffer);
if (strncmp(console_buffer, "CONNECT", 7) == 0) {
dbg("ini_f: connected");
return 0;
}
}
}
static inline void mdm_readline(char *buf, int bufsiz)
{
char c;
char *p;
int n;
p = buf;
for(;;) {
c = serial_getc();
case '\r':
break;
case '\n':
*p = '\0';
return;
if(n++ > bufsiz) {
*p = '\0';
return; /* sanity check */
}
*p = c;
p++;
break;
}
}
}
#endif /* CONFIG_MODEM_SUPPORT */
1)、gd_t該數據結構保存了u-boot需要的配置信息,註釋簡單明瞭
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? */
#ifdef CONFIG_VFD //我們一般沒有配置這個,這個是frame buffer的首地址
unsigned long fb_base; /* base address of frame buffer */
#endif
} gd_t;
2)、bd_t 保存與板子相關的配置參數
typedef struct bd_info {
int bi_baudrate; /* serial console baudrate */
unsigned long bi_ip_addr; /* IP Address */
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];//在我的板子上是1個
} bd_t;
現在我貼出start_armboot ()的源代碼,然後具體的在其中解釋一些代碼的作用:
void start_armboot (void)
{
DECLARE_GLOBAL_DATA_PTR;
ulong size;
gd_t gd_data;
bd_t bd_data;
init_fnc_t **init_fnc_ptr; //這個是函數的指針,指向一些硬件初始化的函數,見下面
//init_fnc_t *init_sequence[] = {
// cpu_init, /* basic cpu dependent setup */
// 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 */
// display_banner,
// dram_init, /* configure available RAM banks */
// display_dram_config,
// NULL,
// };
//printf("**********Start *************\n");
/* Pointer is writable since we allocated a register for it */
gd = &gd_data;
memset (gd, 0, sizeof (gd_t));//初始化爲0
gd->bd = &bd_data;
memset (gd->bd, 0, sizeof (bd_t));//初始化爲0
//注意,下面的循環是依次調用各個硬件初始化函數,順序見上面的的數組,我們在下一篇中依次解釋每種硬//件的初始化,第六個就是串口的初始化,這時候我們就可以通過串口輸出信息了
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang (); //如果不成功的話,就“輸出信息,然後就進入死循環”
}
}
/* configure available FLASH banks */
size = flash_init (); //flash的初始化
display_flash_config (size);//打印flash的配置信息
/* initialize environment */
env_relocate ();//環境的初始化,代碼在common\env_common.c中
/* IP Address */
bd_data.bi_ip_addr = getenv_IPaddr ("ipaddr");//讀取IP地址,保存到bd_t數據結構中
/* MAC Address */ //MAC地址的初始化
{
int i;
ulong reg;
char *s, *e;
uchar tmp[64];
i = getenv_r ("ethaddr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
bd_data.bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
}
/* enable exceptions */ //允許中斷
enable_interrupts ();
#ifdef CONFIG_DRIVER_CS8900 //配置網卡
if (!getenv ("ethaddr")) {
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
}
#endif
//直接進入main_loop 該函數在common\main.c中,至此,硬件初始化完成
/* 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 */
}