U-Boot第一階段的啓動流程。(nandflash啓動,把nand的4k代碼考到sram中,因爲nand沒址線,不能映射到內存,所以通過sram進行過度,sram中4k代碼把整個uboot拷貝到sdram上,初始化好堆棧,爲c語言提供條件,進入uboot的第二階段! )這個階段主要是初始化硬件設備,爲加載U-Boot的第二階段代碼準備RAM空間最後跳轉到lib_arm/board.c中start_armboot函數,這是第二階段的入口點。
在上一篇文章中,我們介紹了u-boot啓動的時候彙編語言的部分,當時我們進行了一些簡單的初始化,並且爲C語言的執行建立的環境(堆棧),現在我們看看當從彙編語言轉到C語言的時候執行的第一個函數( start_armboot (),在lib_arm\board.c中),該函數進行了一系列的外設初始化,然後調用main_loop (),根據配置來選擇是直接加載Linux內核還是進入等待命令模式。
1、在介紹該函數之前,我們需要看一看幾個數據結構,這些是u-boot中幾個重要的數據結構:
(1)gd_t結構體
U-Boot使用了一個結構體gd_t來存儲全局數據區的數據,這個結構體在include/asm-arm/global_data.h中定義如下:
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 //我們一般沒有配置這個,這個是frame buffer的首地址
unsigned char vfd_type; /* display type */
#endif
#if 0
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
unsigned long ram_size; /* RAM size */
unsigned long reset_status; /* reset status register at boot */
#endif
void **jt; /* jump table */
} gd_t;
/*
* Global Data Flags
*/
#define GD_FLG_RELOC 0x00001 /* Code was relocated to RAM */
#define GD_FLG_DEVINIT 0x00002 /* Devices have been initialized */
#define GD_FLG_SILENT 0x00004 /* Silent mode */
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
在global_data.h中U-Boot使用了一個存儲在寄存器中的指針gd來記錄全局數據區的地址:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
DECLARE_GLOBAL_DATA_PTR定義一個gd_t全局數據結構的指針,這個指針存放在指定的寄存器r8中。這個聲明也避免編譯器把r8分配給其它的變量。任何想要訪問全局數據區的代碼,只要代碼開頭加入“DECLARE_GLOBAL_DATA_PTR”一行代碼,然後就可以使用gd指針來訪問全局數據區了。
根據U-Boot內存使用圖中可以計算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)
2)bd_t 保存與板子相關的配置參數
bd_t在include/asm-arm/u-boot.h中定義如下:
typedef struct bd_info {
int bi_baudrate; /* 串口通訊波特率 */
unsigned long bi_ip_addr; /* 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配置信息 */
{
ulong start;
ulong size;
} bi_dram[CONFIG_NR_DRAM_BANKS]; //在我的板子上DRAM配置是1個
#ifdef CONFIG_HAS_ETH1
/* second onboard ethernet port */
unsigned char bi_enet1addr[6];
#endif
} bd_t;
#define bi_env_data bi_env->data
#define bi_env_crc bi_env->crc
U-Boot啓動內核時要給內核傳遞參數,這時就要使用gd_t,bd_t結構體中的信息來設置標記列表。
3). 初始化函數列表(以數組的形式)
相關代碼在lib-arm/board.c中定義
typedef int (init_fnc_t) (void); /*這是使用typedef定義一個init_fnc_t爲函數類型,該函數返回int型,無參數*/
int print_cpuinfo (void); /* test-only */
init_fnc_t *init_sequence[]定義一個init_fnc_t指針類型的數組。簡單的說就是定義了個函數指針數組,指向一系列cpu初始化函數。
init_fnc_t *init_sequence[] = { /*全局變量init_sequence的定義,*/
cpu_init, /* basic cpu dependent setup CPU的相關配置,如初始化IRQ/FIQ模式的棧cpu/arm920t/cpu.c*/
board_init, /* basic board dependent setup開發板相關配置,是對板子的初始化,設置MPLL,改變系統時鐘,以及一些GPIO寄存器的值,還設置了U-Boot機器碼和內核啓動參數地址,它是開發板相關的函數,比如2410是在:board/smdk2410/smdk2410.c中實現*/
interrupt_init, /* set up exceptions 初始化中斷,在cpu/arm920t/s3c24x0/interrupts.c實現*/
env_init, /* initialize environment 初始化環境變量,檢查flash上的環境變量是否有效common/env_flash.c 或common/env_nand.c */
init_baudrate, /* initialze baudrate settings 初始化波特率lib_arm/board.c */
serial_init, /* serial communications setup串口初始化串口初始化後我們就可以打印信息了cpu/arm920t/s3c24x0/serial.c */
console_init_f, /* stage 1 init of console 控制檯初始化第一階段common/console.c */
display_banner, /* say that we are here通知代碼已經運行到該處,打印U-Boot版本、編譯的時間-- lib_arm/board.c */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
dram_init, /* configure available RAM banks 配置可用的內存區,檢測系統內存映射,設置內存的起始地址和大小board/smdk2410/smdk2410.c*/
display_dram_config,
NULL,
};
可以看出這裏定義了一個指針數組,它的每一個元素都是指針變量,這些指針變量指向的類型爲init_fnc_t,在C語言中函數的入口地址就是函數名,所以這裏使用一系列函數名來初始化這個數組。
現在來看看到底做了哪些初始化工作
int cpu_init (void)
{
/*
* setup up stacks if necessary
*/
#ifdef CONFIG_USE_IRQ
IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
return 0;
}
這個函數cpu/arm920t/cpu.c在中實現,其實這個函數沒有做任何工作,因爲CONFIG_USE_IRQ這個宏沒有定義,那麼怎麼知道這個宏是否被定義了呢,在使用SourceInsight的搜索功能時,發現有些宏會在很多頭文件被定義,而我們又很難判斷這些頭文件是否被當前的c文件包含了,我使用一個簡便的方法,利用編譯器的預處理功能,
#ifdef CONFIG_USE_IRQ
#error CONFIG_USE_IRQ_xxxxxx
DECLARE_GLOBAL_DATA_PTR;
IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
這樣如果這個宏被定義了,那麼編譯時就會報錯輸出#error CONFIG_USE_IRQ_xxxxxx,並終止編譯.對於smdk2410來說這個宏CONFIG_USE_IRQ沒定義,實際上就是把IRQ_STACK_START, FIQ_STACK_START指到RAM中的IRQ stuff區域。
int board_init (void) 這個函數board/smdk2410/smdk2410.c在中實現
{
//獲取power和clock及GPIO方面的寄存器地址,稍後的操作會對這些寄存器操作,需要看到的是,象S3C24X0_CLOCK_POWER裏面的field對象都是按照實際寄存器的地址來安排的
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();
/* to reduce PLL lock time, adjust the LOCKTIME register */
//降低PLL的lock time的值,具體含義可參考data sheet
clk_power->LOCKTIME = 0xFFFFFF;
/* configure MPLL */
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
/* some delay between MPLL and UPLL */
delay (4000);
/* configure UPLL */
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
/* some delay between MPLL and UPLL */
delay (8000);
/* 配置每個GPIO的功能,輸入輸出,等參數 */
gpio->GPACON = 0x007FFFFF;
gpio->GPBCON = 0x00044555;
gpio->GPBUP = 0x000007FF;
gpio->GPCCON = 0xAAAAAAAA;
gpio->GPCUP = 0x0000FFFF;
gpio->GPDCON = 0xAAAAAAAA;
gpio->GPDUP = 0x0000FFFF;
gpio->GPECON = 0xAAAAAAAA;
gpio->GPEUP = 0x0000FFFF;
gpio->GPFCON = 0x000055AA;
gpio->GPFUP = 0x000000FF;
gpio->GPGCON = 0xFF95FFBA;
gpio->GPGUP = 0x0000FFFF;
gpio->GPHCON = 0x002AFAAA;
gpio->GPHUP = 0x000007FF;
/* SMDK2410開發板的機器碼 */ */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;
/* adress of boot parameters內核啓動參數地址,運行時在linux內核之下/
gd->bd->bi_boot_params = 0x30000100;
//使能指令cache和數據cache
icache_enable();
dcache_enable();
return 0;
}
現在說一下icache_enable、dcache_enable函數,它定義在cpu/arm920t/cpu.c中,這兩個函數是通過修改CP15的c1寄存器來實現的,使能cache很簡單,只要把協處理器15的相關位打開就行了,這裏來是將c1的I、C位置1,來開啓Icache、DCaches。我這裏只分析icache_enable,dcache_enable類似。icache_enable具體實現如下:
void icache_enable (void)
{
ulong reg;
reg = read_p15_c1 (); /* get control reg. 獲取CP15的c1寄存器值存到reg中*/
cp_delay ();
write_p15_c1 (reg | C1_IC);/*這裏將C1寄存器的I、C位置1,來開啓Icache、Dcaches,關於CP15的c1寄存器的格式可參看前面u-boot啓動第一階段分析的cpu_init_crit函數部分。
}
這裏須要理解的是read_p15_c1與write_p15_c1函數,它們分別在cpu/arm920t/cpu.c中定義如下:
static unsigned long read_p15_c1 (void)
{
unsigned long value;
__asm__ __volatile__(
"mrc p15, 0, %0, c1, c0, 0 @ read control reg\n",%0是參數傳遞時R0寄存器,其功能是讀取CP15的c1寄存器值放到r0中。
: "=r" (value)
:
: "memory");
#ifdef MMU_DEBUG
printf ("p15/c1 is = %08lx\n", value);
#endif
return value; 返回讀取CP15的c1寄存器的值
}
/* write to co-processor 15, register #1 (control register) */
static void write_p15_c1 (unsigned long value)
{
#ifdef MMU_DEBUG
printf ("write %08lx to p15/c1\n", value);
#endif
__asm__ __volatile__(
"mcr p15, 0, %0, c1, c0, 0 @ write it back\n" @保存r0的值到控制寄存器CP15的c1寄存器中,因爲函數參數傳遞時,第一個參數都是放在r0寄存器中的。
:
: "r" (value)
: "memory");
read_p15_c1 ();
}
接下來該看初始化函數: interrupt_init,在cpu/arm920t/s3c24x0/interrupts.c實現interrupt_init代碼如下:
int timer_load_val = 0;
static ulong timestamp;
static ulong lastdec;
int interrupt_init (void) 初始化timer4相關寄存器,用於產生10ms定時中斷信號。
{
S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();//返回定時器配置寄存器地址0x51000000,即TCFG0寄存器地址。
/*這裏使用定時器4,定時器4有隻有一個內部定器沒有輸出管腳*/
/* prescaler for Timer 4 is 16 */
timers->TCFG0 = 0x0f00;
if (timer_load_val == 0)
{
/*
* for 10 ms clock period @ PCLK with 4 bit divider = 1/2
* (default) and prescaler = 16. Should be 10390
* @33.25MHz and 15625 @ 50 MHz
*/
timer_load_val = get_PCLK()/(2 * 16 * 100); //PCLK(返回PCLK頻率
}
/* load value for 10 ms timeout */
lastdec = timers->TCNTB4 = timer_load_val;//設置計數緩存寄存器初始值。
/*設置定時器4手動更新,自動加載模式,並關閉定時器4*/
timers->TCON = (timers->TCON & ~0x0700000) | 0x600000;
/* auto load, 啓動Timer 4 */
timers->TCON = (timers->TCON & ~0x0700000) | 0x500000;
timestamp = 0;
return (0);
}
對着datasheet來看這個函數, 實際上這個函數使用timer 4來作爲系統clock, 即時鐘滴答, 10ms一次,到點就產生一箇中斷,但由於此時中斷還沒打開所以這個中斷不會響應。
接着看env_init: 由於我們在inculde/configs/smdk2410.h下定義了CFG_ENV_IS_IN_FLASH,因此該函數位於common/env_flash.c下
int env_init(void)
{
#ifdef CONFIG_OMAP2420H4
int flash_probe(void);
if(flash_probe() == 0)
goto bad_flash;
#endif
if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
gd->env_addr = (ulong)&(env_ptr->data);
gd->env_valid = 1;使用include/configs/smdk2410.h配置的環境變量則設置環境變量可用標誌
return(0);
}
/* env_ptr在前面定義爲env_t *env_ptr = (env_t *)CFG_ENV_ADDR;而CFG_ENV_ADDR被定義在include/configs/smdk2410.h中了,這裏判斷如果校驗正確(即CFG_ENV_ADDR被定義了)則環境變量的存放地址使用smdk2410.h中定義的,否則使用後面的默認的環境變量值default_environment數組*/
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 0; 使用默認的環境配置變量則設置環境變量不可用標誌
return (0);
}
這個函數主要是在gd裏保存環境變量的存放地址。一般使用默認的環境變量值即default_environment數組,它在common/env_commom.c中定義(關於u-boot環境變量更詳細的說明請看U-BOOT環境變量實現一文檔。地址:http://blog.csdn.net/hxliu_leo/article/details/5315011)
uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
"bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0"
#endif
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
#ifdef CONFIG_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
};
可見環境變量以如下的方式存放在數組中
Name=value
並且以”/0”結束, 而類似CONFIG_BOOTARGS的宏都定義在板子自己的配置文件中即smdk2410.h裏。
接下來看init_baudrate,定義在lib_arm/board.c
static int init_baudrate (void)
{
char tmp[64]; /* long enough for environment variables */
int i = getenv_r ("baudrate", tmp, sizeof (tmp)); //從環境變量中獲取波特率值
gd->bd->bi_baudrate = gd->baudrate = (i > 0)
? (int) simple_strtoul (tmp, NULL, 10)
: CONFIG_BAUDRATE;
return (0);
}
該函數從上面剛初始化好的環境變量列表裏找波特率值,如果沒有就賦初始值爲CONFIG_BAUDRATE。
繼續往下看serial_init,cpu/arm920t/s3c24x0/serial.c
int serial_init (void)
{
serial_setbrg ();//設置波特率,停止位等
return (0);
}
void serial_setbrg (void) cpu/arm920t/s3c24x0/serial.c
{
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
int i;
unsigned int reg = 0;
/* value is calculated so : (int)(PCLK/16./baudrate) -1 */
reg = get_PCLK() / (16 * gd->baudrate) - 1;
/* FIFO enable, Tx/Rx FIFO clear */
uart->UFCON = 0x07;
uart->UMCON = 0x0;
/* Normal,No parity,1 stop,8 bit */
uart->ULCON = 0x3;
/*
* tx=level,rx=edge,disable timeout int.,enable rx error int.,
* normal,interrupt or polling
*/
uart->UCON = 0x245;
uart->UBRDIV = reg;
#ifdef CONFIG_HWFLOW
uart->UMCON = 0x1; /* RTS up */
#endif
for (i = 0; i < 100; i++);
}
上面這個函數對着datasheet看,無非是設置波特率,起始位,檢驗中斷類型等等。
接着看初始化函數:console_init_f,common/console.c
int console_init_f (void)
{
gd->have_console = 1;
#ifdef CONFIG_SILENT_CONSOLE
if (getenv("silent") != NULL)
gd->flags |= GD_FLG_SILENT; //設置控制檯模式
#endif
return (0);
}
該函數初始化了幾個控制檯相關的標記。
接着看display_banner: lib_arm/board.c
static int display_banner (void)
{
printf ("\n\n%s\n\n", version_string); //打印U-BOOT的版本信息
debug ("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end); //打印U-BOOT代碼位置
#ifdef CONFIG_MODEM_SUPPORT
debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
return (0);
}
這個函數就是在控制檯上打印一些系統信息。
接着看dram_init:
int dram_init (void) 這個函數board/smdk2410/smdk2410.c在中實現
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1; //RAM起始地址
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; //RAM大小
return 0;
}
2410使用2片32MB的SDRAM組成了64MB的內存,接在存儲控制器的BANK6,地址空間是0x30000000~0x34000000。在include/configs/smdk2410.h中PHYS_SDRAM_1和PHYS_SDRAM_1_SIZE 分別被定義爲0x30000000和0x04000000(64M)。
再看display_dram_config lib_arm/board.c
static int display_dram_config (void)
{
int i;
#ifdef DEBUG
puts ("RAM Configuration:\n");
for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);
print_size (gd->bd->bi_dram[i].size, "\n");
}
#else
ulong size = 0;
for (i=0; i<CONFIG_NR_DRAM_BANKS; i++) {
size += gd->bd->bi_dram[i].size;
}
puts("DRAM: ");
print_size(size, "\n");
#endif
return (0);
}
呵呵僅僅是打印系統RAM的信息。
這樣整個初始化函數表的函數都看完了,總結一下主要做了如下過程:
1. cpu, borad, interrupt的初始化,包括cache等,這些都於特定板子的配置有關。
2. 環境變量的初始化,
3. 串口,控制檯,RAM的初始化,
4. 在控制檯上實時的顯示系統配置等相關參數。
最後需要說明的是,大部分的配置參數都是預先在include/configs/board_name.h下定義的,因此如果我們要移植我們自己的板子的話,這個文件必不可少,它描述了我們板子的配置情況如CPU型號,RAM大小等。
2.start_armboot()源碼分析
分析完上述的數據結構,下面來分析start_armboot函數,現在我貼出start_armboot ()的源代碼,然後具體的在其中解釋一些代碼的作用。
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr; /*它是指向指針的指針變量,變量的類型爲init_fnc_t,這是一個使用typedef定義的函數類型, 其中init_fnc_ptr將在後面指向一個數組指針*/
char *s;
#ifndef CFG_NO_FLASH
ulong size;
#endif
#if defined(CONFIG_VFD) || defined(CONFIG_LCD)
unsigned long addr;
#endif
/* Pointer is writable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));//計算全局數據結構的地址gd
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t)); //初始化全局數據區爲0;
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); //爲bd_t分配空間,並賦值到gd
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start; //代碼段的大小
/* 逐個調用init_sequence數組中的初始化函數 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/* CFG_NO_FLASH 表示沒有flash,如果沒定義該常量則表示板子上有flash,此時調用flash_init()對其進行初始化.這裏是norflash初始化 */
#ifndef CFG_NO_FLASH
/* configure available FLASH banks */
size = flash_init (); 詳見後分析
display_flash_config (size); //打印flash的信息,僅是其大小;
#endif /* CFG_NO_FLASH */
#ifdef CONFIG_VFD //smdk2410沒定義
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# endif
/*
* reserve memory for VFD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_VFD */
#ifdef CONFIG_LCD
# ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# endif
/*
* reserve memory for LCD display (always full pages)
*/
/* bss_end is defined in the board-specific linker script */
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = lcd_setmem (addr);
gd->fb_base = addr;
#endif /* CONFIG_LCD */
/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); //初始化malloc區域,_armboot_start=0x33f80000,CFG_MALLOC_LEN=0x30000,mem_malloc_init對 (0x33f80000-0x30000)~(0x33f80000)這段存儲區清零
//如果定義了命令和NAND命令,初始化nand
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /*探測NAND flash並根據NAND flash的類型填充相應的數據結構,全局結構體變量nand_dev_desc[0](類型struct nand_chip) */
#endif
#ifdef CONFIG_HAS_DATAFLASH //沒定義
AT91F_DataflashInit();
dataflash_print_info();
#endif
/* initialize environment配置環境變量,重新定位 */ */
env_relocate ();這個函數在common\env_common.c中
#ifdef CONFIG_VFD 沒定義
/* must do this after the framebuffer is allocated */
drv_vfd_init();
#endif /* CONFIG_VFD */
/* IP Address 從環境變量中獲取IP地址 */ */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* MAC Address */
{
int i;
ulong reg;
char *s, *e;
char tmp[64];
//從環境變量獲取網卡的MAC地址並填充gd結構體變量
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;
}
#ifdef CONFIG_HAS_ETH1
i = getenv_r ("eth1addr", tmp, sizeof (tmp));
s = (i > 0) ? tmp : NULL;
for (reg = 0; reg < 6; ++reg) {
gd->bd->bi_enet1addr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
if (s)
s = (*e) ? e + 1 : e;
}
#endif
}
devices_init (); /* get the devices list going. 對設備初始化,在common/devices.c文件中,會根據配置來完成各個設備的初始化,其中會調用函數drv_system_init(),這個函數對串口設備初始化,然後註冊串口設備*/
#ifdef CONFIG_CMC_PU2 //沒定義
load_sernum_ethaddr ();
#endif /* CONFIG_CMC_PU2 */
jumptable_init ();//跳轉表初始化,函數在common\exports.c文件中
console_init_r (); /* fully init console as a device函數在common\console.c */
#if defined(CONFIG_MISC_INIT_R) //沒有定義
/* miscellaneous platform dependent initialisations */
misc_init_r ();
#endif
/* enable exceptions */
enable_interrupts ();//使能中斷,cpu/arm920t/interrupts.c smdk2410是個空函數void enable_interrupts (void){return;} */
/* Perform network card initialisation if necessary */
#ifdef CONFIG_DRIVER_CS8900 /*smdk2410沒定義*/
cs8900_get_enetaddr (gd->bd->bi_enetaddr); //獲取網卡的MAC地址
#endif
#if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
if (getenv ("ethaddr")) {
smc_set_mac_addr(gd->bd->bi_enetaddr);
}
#endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 沒定義 */
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);//讀取環境變量loadaddr到全局變量load_addr中
}
#if (CONFIG_COMMANDS & CFG_CMD_NET)
if ((s = getenv ("bootfile")) != NULL) {
copy_filename (BootFile, s, sizeof (BootFile));//讀取環境變量bootfile到全局變量BootFile中
}
#endif /* CFG_CMD_NET */
#ifdef BOARD_LATE_INIT //沒定義
board_late_init ();
#endif
#if (CONFIG_COMMANDS & CFG_CMD_NET)
#if defined(CONFIG_NET_MULTI) // smdk2410沒定義
puts ("Net: ");
#endif
eth_initialize(gd->bd);
#endif
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();//直接進入main_loop 該函數在common\main.c中,至此,硬件初始化完成
}
/* NOTREACHED - no way out of command loop except booting */
}
對start_armboot中相關調用的函數分析
這裏flash_init定義在board/smdk2410/flash.c:
ulong flash_init (void)
{
int i, j;
ulong size = 0;
for (i = 0; i < CFG_MAX_FLASH_BANKS; i++) {
ulong flashbase = 0;
flash_info[i].flash_id = /*保存flash ID*/
#if defined(CONFIG_AMD_LV400)
(AMD_MANUFACT & FLASH_VENDMASK) |
(AMD_ID_LV400B & FLASH_TYPEMASK);
#elif defined(CONFIG_AMD_LV800)
(AMD_MANUFACT & FLASH_VENDMASK) |
(AMD_ID_LV800B & FLASH_TYPEMASK);
#else
#error "Unknown flash configured"
#endif
/*保存每個flash blank的大小,sector數量,起始地址等信息*/
flash_info[i].size = FLASH_BANK_SIZE;
flash_info[i].sector_count = CFG_MAX_FLASH_SECT;
memset (flash_info[i].protect, 0, CFG_MAX_FLASH_SECT);
if (i == 0)
flashbase = PHYS_FLASH_1;
else
panic ("configured too many flash banks!\n");
/*爲每個sector分配不同的大小,作爲不同的用途*/
for (j = 0; j < flash_info[i].sector_count; j++) {
if (j <= 3) {
/* 1st one is 16 KB */
if (j == 0) {
flash_info[i].start[j] =
flashbase + 0;
}
/* 2nd and 3rd are both 8 KB */
if ((j == 1) || (j == 2)) {
flash_info[i].start[j] =
flashbase + 0x4000 + (j -
1) *
0x2000;
}
/* 4th 32 KB */
if (j == 3) {
flash_info[i].start[j] =
flashbase + 0x8000;
}
} else {
flash_info[i].start[j] =
flashbase + (j - 3) * MAIN_SECT_SIZE;
}
}
size += flash_info[i].size;
}
/*對flash上保存有RO, RW的地方進行保護,monitor_flash_len = RO + RW的長度*/
flash_protect (FLAG_PROTECT_SET,
CFG_FLASH_BASE,
CFG_FLASH_BASE + monitor_flash_len - 1,
&flash_info[0]);
//對flash上保存有環境變量的地方進行保護
flash_protect (FLAG_PROTECT_SET,
CFG_ENV_ADDR,
CFG_ENV_ADDR + CFG_ENV_SIZE - 1, &flash_info[0]);
return size;
}
該函數就是記錄下flash的大小,數量,sector的大小數量等,並對flash上重要的數據進行保護
display_flash_config 定義在lib_arm/board.c:
#ifndef CFG_NO_FLASH
static void display_flash_config (ulong size)
{
puts ("Flash: ");
print_size (size, "\n");
}
#endif
很簡單,打印flash的大小信息。
mem_malloc_init定義在lib_arm/board.c:
static ulong mem_malloc_start = 0;
static ulong mem_malloc_end = 0;
static ulong mem_malloc_brk = 0;
static void mem_malloc_init (ulong dest_addr)
{
mem_malloc_start = dest_addr;
mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
mem_malloc_brk = mem_malloc_start;
memset ((void *) mem_malloc_start, 0,
mem_malloc_end - mem_malloc_start);
}
這個函數就是保存malloc區域的起始地址,結束地址,並清0這塊區域,這塊區域在RAM中的具體位置可查看前面的圖。
接着看env_relocate
Uboot在完成彙編部分的初始化之後,將跳到start_armboot()去執行,其中便會執行env_relocate()初始化環境變量。
第一步,初始化一個全局指針,它被定義爲:
env_t *env_ptr = 0;
第二步,重新初始化函數指針,
static uchar env_get_char_init (int index);
uchar (*env_get_char)(int) = env_get_char_init;
該函數指針原來被初始化爲env_get_char_init,現在改爲env_get_char_memory。對於nand flash,這兩個函數是一樣的。
第三步,如果flash沒有參數表,則使用默認參數,這裏是通過memcpy (env_ptr->data,default_environment, sizeof(default_environment));來加載。
第四步,如果flash上有參數表可用,則從flash上加載,通過env_relocate_spec()來實現:
第五步,gd->env_addr = (ulong)&(env_ptr->data)
即將環境變量的值賦值給全局變量gd->env_addr,這樣只要通過這個全局變量就可以訪問這些變量了。值得一提的是,字符串數組data裏面的變量與變量之間是通過’/0’來分割的。
common/env_common.c:
env_t *env_ptr = 0;
void env_relocate (void)
{
DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
gd->reloc_off);
#ifdef CONFIG_AMIGAONEG3SE //沒有定義
enable_nvram();
#endif
/*env_ptr指向存放環境變量的區域*/
#ifdef ENV_IS_EMBEDDED
/*
* The environment buffer is embedded with the text segment,
* just relocate the environment pointer
*/
env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);
DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#else
/*
* We must allocate a buffer for the environment
*/
env_ptr = (env_t *)malloc (CFG_ENV_SIZE);//爲環境變量分配內存,env_ptr是全局變量,在下面的env_relocate_spec ()函數中將環境變量讀取到env_ptr指向的內存中
DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif
/*
* After relocation to RAM, we can always use the "memory" functions
*/
env_get_char = env_get_char_memory;
/*如果在env_init中沒有初始化合適的環境變量則使用默認的環境變量來作爲環境變量值,否則使用env_init中定義好的環境變量值*/
在env_init函數中如果使用環境變量地址是include/configs/smdk2410.h配置的gd->env_valid被初始化爲1,即flash上有參數表,如果使用環境變量是默認的環境變量default_environment,則gd->env_valid被初始化爲0了。
if (gd->env_valid == 0) { //如果flash沒有參數表,則使用默認參數,這裏是通過下面
來加載。
#if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */
puts ("Using default environment\n\n");
#else
puts ("*** Warning - bad CRC, using default environment\n\n");
SHOW_BOOT_PROGRESS (-1);
#endif
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)); /*把默認值存入RAM相應區域*/
#ifdef CFG_REDUNDAND_ENVIRONMENT
env_ptr->flags = 0xFF;
#endif
env_crc_update ();/*更新crc校驗值*/
gd->env_valid = 1; /*標記環境變量有效*/
}
else {
env_relocate_spec ();/*如果flash上有參數表可用,則從flash上加載,通過env_relocate_spec()來實現*/
}
gd->env_addr = (ulong)&(env_ptr->data); //填充結構體變量, 即將環境變量的值賦值給全局變量gd->env_addr,這樣只要通過這個全局變量就可以訪問這些變量了。值得一提的是,字符串數組data裏面的變量與變量之間是通過’/0’來分割的。
#ifdef CONFIG_AMIGAONEG3SE //沒定義
disable_nvram();
#endif
}
該函數重新分配了一塊區域用於存放環境變量,並在gd_t區域保存了這個地址值。
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED) //如果不是使用嵌入參數的形式,即爲參數表的形式
ulong total;
int ret;
total = CFG_ENV_SIZE;//參數表大小,包括參數表頭部
/*從nand flash 中讀取環境變量到全局變量env_ptr指向的內存中,這裏需要注意的是CFG_ENV_OFFSET、//CFG_ENV_SIZE這兩個宏定義,它們表示從NAND flash中偏移爲CFG_ENV_OFFSET處讀取大小爲CFG_ENV_SIZE字節//的數據,注意這兩個宏定義決定了環境變量在nand flash中保存的位置和大小,不能和nand flash 中的其他分區(kernel分區、rootfs分區)相沖突,不然在執行saveenv命令時可能會覆蓋其他分區的內容,導致系統無法啓動.*/
ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr); //讀出操作,flash設備爲nand_info,偏移爲CFG_ENV_OFFSET,讀出的大小爲total,目標地址由env_ptr所指。
if (ret || total != CFG_ENV_SIZE)
return use_default();//如果讀出的長度不對或出錯,則使用默認值
if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
return use_default();//如果校驗出錯,使用默認值
#endif /* ! ENV_IS_EMBEDDED */
}
#endif /* CFG_ENV_OFFSET_REDUND */
該函數實現真正的重定位功能,先從NAND flash中讀取環境變量,如果讀取成功,並且crc校驗正確的話,就使用NAND flash中讀取出來的環境變量,否則使用默認的環境變量
此外,uboot的參數表還支持一種被稱爲CFG_ENV_OFFSET_REDUND的冗餘模式,它會在flash上保存兩個參數表副本,這樣在一個副本出錯的時候,還可以從另一個副本中去讀取,通過這種方式,提高了數據的安全性。
接着看devices_init: common/devices.c:
int devices_init (void)
{
#ifndef CONFIG_ARM /* already relocated for current ARM implementation */
ulong relocation_offset = gd->reloc_off;
int i;
/* relocate device name pointers */
for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) {
stdio_names[i] = (char *) (((ulong) stdio_names[i]) +
relocation_offset);
}
#endif
/* Initialize the list 創建一個保存device的列表*/*/
devlist = ListCreate (sizeof (device_t));
if (devlist == NULL) {
eputs ("Cannot initialize the list of devices!\n");
return -1;
}
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C) //沒定義
i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);
#endif
#ifdef CONFIG_LCD//沒定義
drv_lcd_init ();
#endif
#if defined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE) //沒定義
drv_video_init ();
#endif
#ifdef CONFIG_KEYBOARD//沒定義
drv_keyboard_init ();
#endif
#ifdef CONFIG_LOGBUFFER//沒定義
drv_logbuff_init ();
#endif
drv_system_init ();
#ifdef CONFIG_SERIAL_MULTI//沒定義
serial_devices_init ();
#endif
#ifdef CONFIG_USB_TTY//沒定義
drv_usbtty_init ();
#endif
#ifdef CONFIG_NETCONSOLE //沒定義
drv_nc_init ();
#endif
return (0);
}
這個函數實際上就是根據板子的配置初始化各種設備,並調用device_register()註冊到系統中去,在這個中函數僅對串口設備初始化,然後註冊串口設備.這裏我們以drv_system_init爲例解釋一下,定義也在common/devices.c,其他代碼類似。
static void drv_system_init (void)
{
device_t dev;
memset (&dev, 0, sizeof (dev));
strcpy (dev.name, "serial"); /*串口設備*/
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM; /*設備屬性*/
#ifdef CONFIG_SERIAL_SOFTWARE_FIFO
/*註冊設備操作函數集(輸入函數,輸出函數等)*/
dev.putc = serial_buffered_putc;
dev.puts = serial_buffered_puts;
dev.getc = serial_buffered_getc;
dev.tstc = serial_buffered_tstc;
#else
dev.putc = serial_putc;
dev.puts = serial_puts;
dev.getc = serial_getc;
dev.tstc = serial_tstc;
#endif
device_register (&dev); /*把該設備註冊進系統中去,即把dev添加進上面創建的設備列表中去*/
#ifdef CFG_DEVICE_NULLDEV
memset (&dev, 0, sizeof (dev));
strcpy (dev.name, "nulldev");
dev.flags = DEV_FLAGS_OUTPUT | DEV_FLAGS_INPUT | DEV_FLAGS_SYSTEM;
dev.putc = nulldev_putc;
dev.puts = nulldev_puts;
dev.getc = nulldev_input;
dev.tstc = nulldev_input;
device_register (&dev);
#endif
}
該函數註冊了一個串口設備和一個空設備(根據配置而定)。其他的設備初始化函數以此大同小異,主要就是初始化好相關設備的設備信息,並註冊到系統中去,詳細代碼大家可以自己去分析。
接下來看jumptable_init
common/exports.c
void jumptable_init (void)
{
int i;
/*分配一塊buffer用於存放跳轉函數地址*/
gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
for (i = 0; i < XF_MAX; i++)
gd->jt[i] = (void *) dummy; /*默認跳轉函數地址,是一個空函數的地址*/
/*爲每種功能定義各自的跳轉函數*/
gd->jt[XF_get_version] = (void *) get_version;
gd->jt[XF_malloc] = (void *) malloc;
gd->jt[XF_free] = (void *) free;
gd->jt[XF_getenv] = (void *) getenv;
gd->jt[XF_setenv] = (void *) setenv;
gd->jt[XF_get_timer] = (void *) get_timer;
gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
gd->jt[XF_udelay] = (void *) udelay;
#if defined(CONFIG_I386) || defined(CONFIG_PPC)
gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
#endif /* I386 || PPC */
#if (CONFIG_COMMANDS & CFG_CMD_I2C)
gd->jt[XF_i2c_write] = (void *) i2c_write;
gd->jt[XF_i2c_read] = (void *) i2c_read;
#endif /* CFG_CMD_I2C */
}
static void dummy(void)
{
}
以看出該函數主要就是爲不同的功能安裝了不同的功能函數。
繼續看console_init_r()函數
common/console.c
/* Called after the relocation - use desired console functions */
int console_init_r (void)
{
/*1. 首先獲取由device_init裏註冊的input,output設備*/
device_t *inputdev = NULL, *outputdev = NULL;
int i, items = ListNumItems (devlist);
#ifdef CONFIG_SPLASH_SCREEN //沒定義
/* suppress all output if splash screen is enabled and we have
a bmp to display */
if (getenv("splashimage") != NULL)
outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");
#endif
#ifdef CONFIG_SILENT_CONSOLE //沒定義
/* Suppress all output if "silent" mode requested */
if (gd->flags & GD_FLG_SILENT)
outputdev = search_device (DEV_FLAGS_OUTPUT, "nulldev");
#endif
/* Scan devices looking for input and output devices */
/*尋找系統上存在的輸入,輸出設備,別忘了上面註冊的串口設備就是輸入,輸出設備*/
for (i = 1;
(i <= items) && ((inputdev == NULL) || (outputdev == NULL));
i++
) {
device_t *dev = ListGetPtrToItem (devlist, i);
if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
inputdev = dev; /*找到輸入設備,參考drv_system_init */
}
if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
outputdev = dev; /*找到輸出設備,參考drv_system_init */
}
}
/* Initializes output console first */
/*由drv_system_init可知,我們可以找到輸入,輸出設備,而且都是同一個serial設備*/
if (outputdev != NULL) {
console_setfile (stdout, outputdev); /*設置標準輸出設備*/
console_setfile (stderr, outputdev); /*設置標準的錯誤輸出設備*/
}
/* Initializes input console */
if (inputdev != NULL) {
console_setfile (stdin, inputdev); /*設置標準輸入設備*/
}
gd->flags |= GD_FLG_DEVINIT; /* device initialization completed 設置初始化完成標記*/
#ifndef CFG_CONSOLE_INFO_QUIET
/* Print information 打印相關設備信息*/
puts ("In: ");
if (stdio_devices[stdin] == NULL) {
puts ("No input devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdin]->name);
}
puts ("Out: ");
if (stdio_devices[stdout] == NULL) {
puts ("No output devices available!\n");
} else {
printf ("%s\n", stdio_devices[stdout]->name);
}
puts ("Err: ");
if (stdio_devices[stderr] == NULL) {
puts ("No error devices available!\n");
} else {
printf ("%s\n", stdio_devices[stderr]->name);
}
#endif /* CFG_CONSOLE_INFO_QUIET */
/* Setting environment variables 設置環境變量*/
for (i = 0; i < 3; i++) {
setenv (stdio_names[i], stdio_devices[i]->name);
}
#if 0
/* If nothing usable installed, use only the initial console */
if ((stdio_devices[stdin] == NULL) && (stdio_devices[stdout] == NULL))
return (0);
#endif
return (0);
}
該函數主要是設置好了標準輸入,標準輸出,標準錯誤輸出設備,並定義好相關輸入,輸出函數,以使後面的如puts(),printf()等函數可以運行,這個應該不陌生的。 爲了一解疑惑我們可以繼續分析下去:
static int console_setfile (int file, device_t * dev) common/console.c
{
int error = 0;
if (dev == NULL)
return -1;
switch (file) {
case stdin:
case stdout:
case stderr:
/* Start new device */
if (dev->start) {
error = dev->start ();/*在drv_system_init下沒定義這個函數*/
/* If it's not started dont use it */
if (error < 0)
break;
}
/* Assign the new device (leaving the existing one started) */
stdio_devices[file] = dev; /*保存標準輸入,標準輸出,標準錯誤輸出設備*/
/*
* Update monitor functions
* (to use the console stuff by other applications)
*/
設置好標準輸入,標準輸出,標準錯誤輸出設備的輸入,輸出函數,可以從drv_system_init下查到
switch (file) {
case stdin:
gd->jt[XF_getc] = dev->getc;
gd->jt[XF_tstc] = dev->tstc;
break;
case stdout:
gd->jt[XF_putc] = dev->putc;
gd->jt[XF_puts] = dev->puts;
gd->jt[XF_printf] = printf;
break;
}
break;
default: /* Invalid file ID */
error = -1;
}
return error;
}
這個函數就是初始化好標準輸入,標準輸出,標準錯誤輸出設備,這以後就可以調用如puts,printf等函數了. 我們以puts爲例繼續分析:
common/console.c:
void puts (const char *s)
{
#ifdef CONFIG_SILENT_CONSOLE
if (gd->flags & GD_FLG_SILENT)
return;
#endif
if (gd->flags & GD_FLG_DEVINIT) {//這個標記前面設置過了
/* Send to the standard output */
fputs (stdout, s); /*就是調用這個函數*/
} else {
/* Send directly to the handler */
serial_puts (s);
}
}
void fputs (int file, const char *s)
{
if (file < MAX_FILES)
stdio_devices[file]->puts (s); 這裏就是調用我們初始化時設置的了即serial_puts()函數,可以在drv_system_init查到
}
serial_puts函數就是和具體設備相關了,對於smdk2410的代碼如下
cpu/arm920t/s3c24x0/serial.c:
void serial_puts (const char *s)
{
while (*s) {
serial_putc (*s++);
}
}
void serial_putc (const char c) cpu/arm920t/s3c24x0/serial.c
{
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);
#ifdef CONFIG_MODEM_SUPPORT
if (be_quiet)
return;
#endif
/* wait for room in the tx FIFO */
while (!(uart->UTRSTAT & 0x2));
#ifdef CONFIG_HWFLOW
/* Wait for CTS up */
while(hwflow && !(uart->UMSTAT & 0x1))
;
#endif
uart->UTXH = c;
/* If \n, also do \r */
if (c == '\n')
serial_putc ('\r');
}
這些函數對照着datasheet就很好理解了。
printf函數最終也是調用puts函數。
至此我們對打印的來龍去脈瞭解了。
接下來主要分析main_loop(),common/main.c
void main_loop (void)
{
#ifndef CFG_HUSH_PARSER /*smdk2410沒定義*/
static char lastcommand[CFG_CBSIZE] = { 0, };
int len;
int rc = 1;
int flag;
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
char *s;
int bootdelay;
#endif
#ifdef CONFIG_PREBOOT
char *p;
#endif
#ifdef CONFIG_BOOTCOUNT_LIMIT
unsigned long bootcount = 0;
unsigned long bootlimit = 0;
char *bcs;
char bcs_set[16];
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO) /*smdk2410沒定義*/
ulong bmp = 0; /* default bitmap */
extern int trab_vfd (ulong bitmap);
#ifdef CONFIG_MODEM_SUPPORT /*smdk2410沒定義*/
if (do_mdm_init)
bmp = 1; /* alternate bitmap */
#endif
trab_vfd (bmp);
#endif /* CONFIG_VFD && VFD_TEST_LOGO */
#ifdef CONFIG_BOOTCOUNT_LIMIT /*smdk2410沒定義*/
bootcount = bootcount_load();
bootcount++;
bootcount_store (bootcount);
sprintf (bcs_set, "%lu", bootcount);
setenv ("bootcount", bcs_set);
bcs = getenv ("bootlimit");
bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */
#ifdef CONFIG_MODEM_SUPPORT /*smdk2410沒定義*/
debug ("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init);
if (do_mdm_init) {
char *str = strdup(getenv("mdm_cmd"));
setenv ("preboot", str); /* set or delete definition */
if (str != NULL)
free (str);
mdm_init(); /* wait for modem connection */
}
#endif /* CONFIG_MODEM_SUPPORT */
#ifdef CONFIG_VERSION_VARIABLE /*smdk2410沒定義*/
{
extern char version_string[];
setenv ("ver", version_string); /* set version variable */
}
#endif /* CONFIG_VERSION_VARIABLE */
#ifdef CFG_HUSH_PARSER /*smdk2410沒定義*/
u_boot_hush_start ();
#endif
#ifdef CONFIG_AUTO_COMPLETE /*smdk2410沒定義*/
install_auto_complete();
#endif
#ifdef CONFIG_PREBOOT /*smdk2410沒定義*/
if ((p = getenv ("preboot")) != NULL) {
# ifdef CONFIG_AUTOBOOT_KEYED /*smdk2410沒定義*/
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER /*smdk2410沒定義*/
run_command (p, 0);
# else
parse_string_outer(p, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED /*smdk2410沒定義*/
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
#endif /* CONFIG_PREBOOT */
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
s = getenv ("bootdelay");
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
# ifdef CONFIG_BOOT_RETRY_TIME
init_cmd_timeout ();
# endif /* CONFIG_BOOT_RETRY_TIME */
#ifdef CONFIG_BOOTCOUNT_LIMIT
if (bootlimit && (bootcount > bootlimit)) {
printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
(unsigned)bootlimit);
s = getenv ("altbootcmd");
}
else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
s = getenv ("bootcmd"); /*smdk2410下這個環境變量是空的*/
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
# ifdef CONFIG_AUTOBOOT_KEYED
int prev = disable_ctrlc(1); /* disable Control C checking */
# endif
# ifndef CFG_HUSH_PARSER
run_command (s, 0);
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
# ifdef CONFIG_AUTOBOOT_KEYED
disable_ctrlc(prev); /* restore Control C checking */
# endif
}
# ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd");
if (s) {
# ifndef CFG_HUSH_PARSER
run_command (s, 0);
# else
parse_string_outer(s, FLAG_PARSE_SEMICOLON |
FLAG_EXIT_FROM_LOOP);
# endif
}
}
#endif /* CONFIG_MENUKEY */
#endif /* CONFIG_BOOTDELAY */
#ifdef CONFIG_AMIGAONEG3SE
{
extern void video_banner(void);
video_banner();
}
#endif
/*
* Main Loop for Monitor Command Processing 最核心的就是這個死循環
*/
#ifdef CFG_HUSH_PARSER
parse_file_outer();
/* This point is never reached */
for (;;);
#else
for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME
if (rc >= 0) {
/* Saw enough of a valid command to
* restart the timeout.
*/
reset_cmd_timeout();
}
#endif
len = readline (CFG_PROMPT);
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
#ifdef CONFIG_BOOT_RETRY_TIME
else if (len == -2) {
/* -2 means timed out, retry autoboot
*/
puts ("\nTimed out waiting for command\n");
# ifdef CONFIG_RESET_TO_RETRY
/* Reinit board to run initialization code again */
do_reset (NULL, 0, 0, NULL);
# else
return; /* retry autoboot */
# endif
}
#endif
if (len == -1)
puts ("<INTERRUPT>\n");
else
rc = run_command (lastcommand, flag);
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
#endif /*CFG_HUSH_PARSER*/
}
該函數比較長,但核心就是不停的從串口獲取命令並解析執行此命令,這個功能就在函數尾部的那個死循環裏, 我們重點就是分析這個循環。
先看readline()
common/main.c
/****************************************************************************/
/*
* Prompt for input and read a line.
* If CONFIG_BOOT_RETRY_TIME is defined and retry_time >= 0,
* time out when time goes past endtime (timebase time in ticks).
* Return: number of read characters
* -1 if break
* -2 if timed out
*/上面的註釋說的很清楚了,就是提示用戶輸入命令,並讀取這個命令
int readline (const char *const prompt)
{
#ifdef CONFIG_CMDLINE_EDITING /*sdmk2410沒定義*/
char *p = console_buffer; /* console_buffer 是個全局變量*/
unsigned int len=MAX_CMDBUF_SIZE;
int rc;
static int initted = 0;
if (!initted) {
hist_init();
initted = 1;
}
puts (prompt);
rc = cread_line(p, &len);
return rc < 0 ? rc : len;
#else
char *p = console_buffer;
int n = 0; /* buffer index */
int plen = 0; /* prompt length */
int col; /* output column cnt */
char c;
/* print prompt */
if (prompt) { /*如果要輸出提示符,則輸出*/
plen = strlen (prompt);
puts (prompt);
}
col = plen;
for (;;) {
#ifdef CONFIG_BOOT_RETRY_TIME /*sdmk2410沒定義*/
while (!tstc()) { /* while no incoming data */
if (retry_time >= 0 && get_ticks() > endtime)
return (-2); /* timed out */
}
#endif
WATCHDOG_RESET(); /* Trigger watchdog, if needed */
#ifdef CONFIG_SHOW_ACTIVITY /*sdmk2410沒定義*/
while (!tstc()) {
extern void show_activity(int arg);
show_activity(0);
}
#endif
c = getc();/*從串口獲取用戶輸入的命令*/
/*
* Special character handling
*/ /*下面這個switch就是處理不同的字符了*/
switch (c) {
case '\r': /* Enter */
case '\n':
*p = '\0';
puts ("\r\n");
return (p - console_buffer); /*輸入結束*/
case '\0': /* nul */ /*忽略,繼續*/
continue;
case 0x03: /* ^C - break linux下的ctrl+c功能*/
console_buffer[0] = '\0'; /* discard input */
return (-1);
case 0x15: /* ^U - erase line刪除 */
while (col > plen) {
puts (erase_seq);
--col;
}
p = console_buffer;
n = 0;
continue;
case 0x17: /* ^W - erase word刪除 */
p=delete_char(console_buffer, p, &col, &n, plen);
while ((n > 0) && (*p != ' ')) {
p=delete_char(console_buffer, p, &col, &n, plen);
}
continue;
case 0x08: /* ^H - backspace */
case 0x7F: /* DEL - backspace */
p=delete_char(console_buffer, p, &col, &n, plen); 刪除
continue;
default: /*獲取常規字符*/
/*
* Must be a normal character then
*/
if (n < CFG_CBSIZE-2) {
if (c == '\t') { /* expand TABs */
#ifdef CONFIG_AUTO_COMPLETE
/* if auto completion triggered just continue */
/*自動補全功能,熟悉linux的肯定知道*/
*p = '\0';
if (cmd_auto_complete(prompt, console_buffer, &n, &col)) {
p = console_buffer + n; /* reset */
continue;
}
#endif
puts (tab_seq+(col&07));
col += 8 - (col&07);
} else {
++col; /* echo input */
putc (c);
}
*p++ = c; /*把字符保存在buffer中*/
++n;
} else { /* Buffer full */
putc ('\a');
}
}
}
#endif /* CONFIG_CMDLINE_EDITING */
}
上面這個函數就是提示用戶輸入,然後一直等待用戶輸入,指到用戶輸入完成, 然後獲取用戶數據,並存入全局變量console_buffer中。
再來回顧下那個for循環, 核心中的核心就是run_command函數了,一猜就知道是用戶執行所有用戶下達命令的函數
/****************************************************************************
* returns:
* 1 - command executed, repeatable
* 0 - command executed but not repeatable, interrupted commands are
* always considered not repeatable
* -1 - not executed (unrecognized, bootd recursion or too many args)
* (If cmd is NULL or "" or longer than CFG_CBSIZE-1 it is
* considered unrecognized)
*
* WARNING:
*
* We must create a temporary copy of the command since the command we get
* may be the result from getenv(), which returns a pointer directly to
* the environment data, which may change magicly when the command we run
* creates or modifies environment variables (like "bootp" does).
*/
int run_command (const char *cmd, int flag)
{
cmd_tbl_t *cmdtp;
char cmdbuf[CFG_CBSIZE]; /* working copy of cmd */
char *token; /* start of token in cmdbuf */
char *sep; /* end of token (separator) in cmdbuf */
char finaltoken[CFG_CBSIZE];
char *str = cmdbuf;
char *argv[CFG_MAXARGS + 1]; /* NULL terminated */
int argc, inquotes;
int repeatable = 1;
int rc = 0;
#ifdef DEBUG_PARSER
printf ("[RUN_COMMAND] cmd[%p]=\"", cmd);
puts (cmd ? cmd : "NULL"); /* use puts - string may be loooong */
puts ("\"\n");
#endif
clear_ctrlc(); /* forget any previous Control C */
if (!cmd || !*cmd) { /*先是對命令的有效性進行檢測*/
return -1; /* empty command */
}
if (strlen(cmd) >= CFG_CBSIZE) {
puts ("## Command too long!\n");
return -1;
}
strcpy (cmdbuf, cmd); /*備份command*/
/* Process separators and check for invalid
* repeatable commands
*/
#ifdef DEBUG_PARSER
printf ("[PROCESS_SEPARATORS] %s\n", cmd);
#endif
while (*str) { /*看前面的定義,str指向cmdbuf */
/*
* Find separator, or string end
* Allow simple escape of ';' by writing "\;"
*/ /*下面這個for是對u-boot的特殊語法的解析,這裏就不分析了*/
for (inquotes = 0, sep = str; *sep; sep++) {
if ((*sep=='\'') &&
(*(sep-1) != '\\'))
inquotes=!inquotes;
if (!inquotes &&
(*sep == ';') && /* separator */
( sep != str) && /* past string start */
(*(sep-1) != '\\')) /* and NOT escaped */
break;
}
/*
* Limit the token to data between separators
*/
token = str;
if (*sep) {
str = sep + 1; /* start of command for next pass */
*sep = '\0';
}
else
str = sep; /* no more commands for next pass */
#ifdef DEBUG_PARSER
printf ("token: \"%s\"\n", token);
#endif
/* find macros in this token and replace them */
process_macros (token, finaltoken);
/* Extract arguments */
if ((argc = parse_line (finaltoken, argv)) == 0) { /*獲取參數*/
rc = -1; /* no command at all */
continue;
}
/* Look up command in command table */
if ((cmdtp = find_cmd(argv[0])) == NULL) { /*獲取對應的command*/
printf ("Unknown command '%s' - try 'help'\n", argv[0]);
rc = -1; /* give up after bad command */
continue;
}
/* found - check max args */
if (argc > cmdtp->maxargs) { /*檢測command語法是否正確*/
printf ("Usage:\n%s\n", cmdtp->usage);
rc = -1;
continue;
}
#if (CONFIG_COMMANDS & CFG_CMD_BOOTD)
/* avoid "bootd" recursion */
if (cmdtp->cmd == do_bootd) {
#ifdef DEBUG_PARSER
printf ("[%s]\n", finaltoken);
#endif
if (flag & CMD_FLAG_BOOTD) {
puts ("'bootd' recursion detected\n");
rc = -1;
continue;
} else {
flag |= CMD_FLAG_BOOTD;
}
}
#endif /* CFG_CMD_BOOTD */
/* OK - call function to do the command */
if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) { /*執行具體的command*/
rc = -1;
}
repeatable &= cmdtp->repeatable;
/* Did the user stop this? */
if (had_ctrlc ())
return 0; /* if stopped then not repeatable */
}
return rc ? rc : repeatable;
}
這個函數主要是對用戶輸入的命令進行語法分析,從中獲取命令,參數等信息,並查找一張系統保存的命令表,找到該命令對應的處理函數。並調用它來處理這個命令。
下面先詳細分析這張系統的命令表是如何生成的,以添加一個命令爲例,來詳細說明。
下面先詳細分析這張系統的命令表是如何生成的,以添加一個命令爲例,來詳細說明。
U-Boot添加命令
一、相關數據定義說明
首先看的是命令表中存的每個命令的屬性,結構體cmd_tbl_t在include/command.h中定義如下:
struct cmd_tbl_s {
char *name; /* Command Name命令名 */
int maxargs; /* maximum number of arguments最大參數個數 */
int repeatable; /* autorepeat allowed? 是否自動重複 */
/* Implementation function */
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]); /* 響應函數 */
char *usage; /* Usage message (short) 簡短的幫助信息 */
#ifdef CFG_LONGHELP
char *help; /* Help message (long) 較詳細的幫助信息*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments自動補全參數*/
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
內存中保存命令的help字段會佔用一定的內存,通過配置U-Boot可以選擇是否保存help字段。若在include/configs/smdk2410.h中定義了CFG_LONGHELP宏,則在U-Boot中使用help命令查看某個命令的幫助信息時將顯示 usage和help字段的內容,否則就只顯示usage字段的內容。
接着看如何定義命令:
include/command.h
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
凡是帶有__attribute__ ((unused,section (".u_boot_cmd"))屬性聲明的變量都將被存放在".u_boot_cmd"段中,並且即使該變量沒有在代碼中顯式的使用編譯器也不產生警告信息。
#ifdef CFG_LONGHELP
/*每個命令就用這個宏來定義,並加進系統命令表中*/
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
##”與“#”都是預編譯操作符,“##”有字符串連接的功能,“#”表示後面緊接着的是一個字符串。它定義一個command變量,並把它放入".u_boot_cmd"節中*/
#else /* no long help info */
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage}
#endif /* CFG_LONGHELP */
#endif /* __COMMAND_H */
U_BOOT_CMD宏在include/command.h中定義,
其中U_BOOT_CMD命令格式如下:
U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)
各個參數的意義如下:
name:命令名,非字符串,但在U_BOOT_CMD中用“#”符號轉化爲字符串
maxargs:命令的最大參數個數
rep:是否自動重複(按Enter鍵是否會重複執行)
cmd:該命令對應的響應函數
usage:簡短的使用說明(字符串)
help:較詳細的使用說明(字符串)
二、添加定義一個“hello”命令
1.建立common/cmd_hello.c
習慣上通用命令源代碼放在common目錄下,並且習慣以“cmd_<命令名>.c”爲文件名。
2.定義“hello”命令
在cmd_hello.c中使用如下的代碼定義“hello”命令:
U_BOOT_CMD(
hello, 3, 0, do_hello,
"hello \n",
" - test cmd..."
);
這定義了一個名字爲hello的command, 對應的函數爲do_hello, 也就是說如果用戶輸入命令: hello,u-boot將執行do_hello函數。
至於爲什麼要用這種方式來定義一個命令列表,大家可以看一下這個文件:
doc/README.commands:
**** Behinde the scene ******
The structure created is named with a special prefix (__u_boot_cmd_)
and placed by the linker in a special section.
This makes it possible for the final link to extract all commands
compiled into any object code and construct a static array so the
command can be found in an array starting at __u_boot_cmd_start.
If a new board is defined do not forget to define the command section
by writing in u-boot.lds ($(TOPDIR)/board/boardname/u-boot.lds) these
3 lines:
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
創建的結構用一個特殊的前綴命名(__u_boot_cmd_)並放置在一個特殊的連接區。
這使得所有命令被編譯成目標作爲一個靜態數組結構,使命令在__u_boot_cmd_start開始的數組中可以找到。
如果定義一個新的板子,不要忘記定義的命令段,主要是在u-boot.lds ($(TOPDIR)/board/boardname/u-boot.lds)文件中添加下面三行。
在U-Boot連接腳本u-boot.lds中定義了".u_boot_cmd"段:
. = .;
__u_boot_cmd_start = .; /*將 __u_boot_cmd_start指定爲當前地址 */
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .; /* 將__u_boot_cmd_end指定爲當前地址 */
這表明帶有“.u_boot_cmd”聲明的函數或變量將存儲在“u_boot_cmd”段。這樣只要將U-Boot所有命令對應的 cmd_tbl_t變量加上“.u_boot_cmd”聲明,編譯器就會自動將其放在“u_boot_cmd”段,查找cmd_tbl_t變量時只要在 __u_boot_cmd_start與__u_boot_cmd_end之間查找就可以了。
因此“hello”命令的定義經過宏展開後如下:
cmd_tbl_t __u_boot_cmd_hello __attribute__ ((unused,section (".u_boot_cmd"))) = {hello, 3, 0, do_hello, " hello \n ", " - test cmd..."}
實質上就是用U_BOOT_CMD宏定義的信息構造了一個cmd_tbl_t類型的結構體。編譯器將該結構體放在“u_boot_cmd”段,執行命令時就可以在“u_boot_cmd”段查找到對應的cmd_tbl_t類型結構體。
3、建立common/cmd_hello.c
習慣上通用命令源代碼放在common目錄下,並且習慣以“cmd_<命令名>.c”爲文件名。
4.實現命令的函數
在cmd_hello.c中添加“hello”命令的響應函數的實現。具體的實現代碼略:
int do_hello (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
/* 實現代碼略 */
}
5.將common/cmd_hello.c編譯進u-boot.bin
在common/Makefile中加入如下代碼:
COBJS = main.o ACEX1K.o altera.o bedbug.o circbuf.o \
cmd_ace.o cmd_autoscript.o cmd_hello.o \
重新編譯下載U-Boot就可以使用hello命令
6.對於u-boot2011.03將common/cmd_hello.c編譯進u-boot.bin
在common/Makefile中加入如下代碼:
COBJS-$(CONFIG_BOOT_MENU) += cmd_hello.o
在include/configs/smdk2410.h加入如代碼:
#define CONFIG_BOOT_MENU 1
重新編譯下載U-Boot就可以使用menu命令
======================================================================
hello命令執行的過程:
(1)在U-Boot中輸入“hello”命令執行時,U-Boot接收輸入的字符串“hello”,傳遞給run_command函數。
run_command函數在common/main.c中定義。
(2)run_command函數調用common/command.c中實現的find_cmd函數在__u_boot_cmd_start與__u_boot_cmd_end間查找命令,並返回hello命令的cmd_tbl_t結構。
find_cmd函數在common/command.c定義。
(3)然後run_command函數使用返回的cmd_tbl_t結構中的函數指針調用hello命令的響應函數do_hello,從而完成了命令的執行。
下面我們就以引導linux內核的命令bootm爲例,說一下u-boot到linux過渡及參數傳遞的整個過程, common/cmd_bootm.c:
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory\n",
"[addr [arg ...]]\n - boot application image stored in memory\n"
"\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
"\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif
);
從註釋中可以看到這個命令的作用是從memory中引導application,我們這裏就是引導linux內核,前提就是linux kernel已經在memory中了。這條命令的處理函數就是do_bootm()。
ulong load_addr = CFG_LOAD_ADDR; /* Default Load Address */
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong iflag;
ulong addr;
ulong data, len, checksum;
ulong *len_ptr;
uint unc_len = CFG_BOOTM_LEN;
int i, verify;
char *name, *s;
int (*appl)(int, char *[]);
image_header_t *hdr = &header;
s = getenv ("verify");
verify = (s && (*s == 'n')) ? 0 : 1;
if (argc < 2) {
addr = load_addr;
} else {
addr = simple_strtoul(argv[1], NULL, 16);
}
SHOW_BOOT_PROGRESS (1);
printf ("## Booting image at %08lx ...\n", addr);
/* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
read_dataflash(addr, sizeof(image_header_t), (char *)&header);
} else
#endif
memmove (&header, (char *)addr, sizeof(image_header_t));
if (ntohl(hdr->ih_magic) != IH_MAGIC) {
#ifdef __I386__ /* correct image format not implemented yet - fake it */
if (fake_header(hdr, (void*)addr, -1) != NULL) {
/* to compensate for the addition below */
addr -= sizeof(image_header_t);
/* turnof verify,
* fake_header() does not fake the data crc
*/
verify = 0;
} else
#endif /* __I386__ */
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-1);
return 1;
}
}
SHOW_BOOT_PROGRESS (2);
data = (ulong)&header;
len = sizeof(image_header_t);
checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (uchar *)data, len) != checksum) {
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
len = ntohl(hdr->ih_size) + sizeof(image_header_t);
read_dataflash(addr, len, (char *)CFG_LOAD_ADDR);
addr = CFG_LOAD_ADDR;
}
#endif
/* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr);
data = addr + sizeof(image_header_t);
len = ntohl(hdr->ih_size);
if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-3);
return 1;
}
puts ("OK\n");
}
SHOW_BOOT_PROGRESS (4);
len_ptr = (ulong *)data;
#if defined(__PPC__)
if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
{
printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
SHOW_BOOT_PROGRESS (-4);
return 1;
}
SHOW_BOOT_PROGRESS (5);
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
name = "Standalone Application";
/* A second argument overwrites the load address */
if (argc > 2) {
hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
}
break;
case IH_TYPE_KERNEL:
name = "Kernel Image";
break;
case IH_TYPE_MULTI:
name = "Multi-File Image";
len = ntohl(len_ptr[0]);
/* OS kernel is always the first image */
data += 8; /* kernel_len + terminator */
for (i=1; len_ptr[i]; ++i)
data += 4;
break;
default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
SHOW_BOOT_PROGRESS (-5);
return 1;
}
SHOW_BOOT_PROGRESS (6);
/*
* We have reached the point of no return: we are going to
* overwrite all exception vector code, so we cannot easily
* recover from any failures any more...
*/
iflag = disable_interrupts();
#ifdef CONFIG_AMIGAONEG3SE
/*
* We've possible left the caches enabled during
* bios emulation, so turn them off again
*/
icache_disable();
invalidate_l1_instruction_cache();
flush_data_cache();
dcache_disable();
#endif
switch (hdr->ih_comp) {
case IH_COMP_NONE:
if(ntohl(hdr->ih_load) == addr) {
printf (" XIP %s ... ", name);
} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
size_t l = len;
void *to = (void *)ntohl(hdr->ih_load);
void *from = (void *)data;
printf (" Loading %s ... ", name);
while (l > 0) {
size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
WATCHDOG_RESET();
memmove (to, from, tail);
to += tail;
from += tail;
l -= tail;
}
#else /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
}
break;
case IH_COMP_GZIP:
printf (" Uncompressing %s ... ", name);
if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
(uchar *)data, &len) != 0) {
puts ("GUNZIP ERROR - must RESET board to recover\n");
SHOW_BOOT_PROGRESS (-6);
do_reset (cmdtp, flag, argc, argv);
}
break;
#ifdef CONFIG_BZIP2
case IH_COMP_BZIP2:
printf (" Uncompressing %s ... ", name);
/*
* If we've got less than 4 MB of malloc() space,
* use slower decompression algorithm which requires
* at most 2300 KB of memory.
*/
i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
&unc_len, (char *)data, len,
CFG_MALLOC_LEN < (4096 * 1024), 0);
if (i != BZ_OK) {
printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
SHOW_BOOT_PROGRESS (-6);
udelay(100000);
do_reset (cmdtp, flag, argc, argv);
}
break;
#endif /* CONFIG_BZIP2 */
default:
if (iflag)
enable_interrupts();
printf ("Unimplemented compression type %d\n", hdr->ih_comp);
SHOW_BOOT_PROGRESS (-7);
return 1;
}
puts ("OK\n");
SHOW_BOOT_PROGRESS (7);
switch (hdr->ih_type) {
case IH_TYPE_STANDALONE:
if (iflag)
enable_interrupts();
/* load (and uncompress), but don't start if "autostart"
* is set to "no"
*/
if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
char buf[32];
sprintf(buf, "%lX", len);
setenv("filesize", buf);
return 0;
}
appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
(*appl)(argc-1, &argv[1]);
return 0;
case IH_TYPE_KERNEL:
case IH_TYPE_MULTI:
/* handled below */
break;
default:
if (iflag)
enable_interrupts();
printf ("Can't boot image type %d\n", hdr->ih_type);
SHOW_BOOT_PROGRESS (-8);
return 1;
}
SHOW_BOOT_PROGRESS (8);
/*看到了吧,u-boot是如何支持多個操作系統引導的*/
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify); /*linux引導的函數*/
break;
case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#ifdef CONFIG_LYNXKDI
case IH_OS_LYNXOS:
do_bootm_lynxkdi (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#if (CONFIG_COMMANDS & CFG_CMD_ELF)
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif /* CFG_CMD_ELF */
#ifdef CONFIG_ARTOS
case IH_OS_ARTOS:
do_bootm_artos (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
#endif
}
SHOW_BOOT_PROGRESS (-9);
#ifdef DEBUG
puts ("\n## Control returned to monitor - resetting...\n");
do_reset (cmdtp, flag, argc, argv);
#endif
return 1;
}
U-Boot使用命令bootm來啓動已經加載到內存中的內核。而bootm命令實際上調用的是do_bootm函數。do_bootm()函數很大, 但是大部分代碼都很簡單,就是做一些檢測,看是否是正確的image,做的檢測有:magic number, crc校驗,體系結構是否正確等等,如果是壓縮的還會先解壓縮(這裏的壓縮和linux編譯出來的壓縮沒關係,應該是application是否被gzip等壓縮過的概念)。對於Linux內核,最後do_bootm函數會調用do_bootm_linux函數來設置標記列表和啓動內核。do_bootm_linux函數在lib_arm/armlinux.c中定義如下:
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params); /*定義一個函數指針*/
image_header_t *hdr = &header; /*linux image的頭*/
bd_t *bd = gd->bd; /*用來存放傳遞給linux kernel參數的地址*/
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");/* U-Boot環境變量bootargs */
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); /* 獲取內核加載地址 */
/*
* Check if there is an initrd image
*/
// 用戶自定義了initrd之後需要加載進來
// 整個過程需要進行頭部以及整個數據內部校驗
// 類似於內核的加載校驗。
if (argc >= 3) { /*存在initrd image*/
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL, 16); /*獲取地址*/
printf ("## Loading Ramdisk Image at %08lx ...\n", addr);
/* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (addr, sizeof (image_header_t),
(char *) &header);
} else
#endif
memcpy (&header, (char *) addr,
sizeof (image_header_t)); /*獲取initrd的頭*/
if (ntohl (hdr->ih_magic) != IH_MAGIC) {
printf ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-10);
do_reset (cmdtp, flag, argc, argv);
}
data = (ulong) & header;
len = sizeof (image_header_t);
checksum = ntohl (hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (unsigned char *) data, len) != checksum) { /*合法性檢測*/
printf ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-11);
do_reset (cmdtp, flag, argc, argv);
}
SHOW_BOOT_PROGRESS (10);
print_image_hdr (hdr);
data = addr + sizeof (image_header_t);
len = ntohl (hdr->ih_size);
#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash (addr)) {
read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
data = CFG_LOAD_ADDR;
}
#endif
if (verify) { /*合法性檢測*/
ulong csum = 0;
printf (" Verifying Checksum ... ");
csum = crc32 (0, (unsigned char *) data, len);
if (csum != ntohl (hdr->ih_dcrc)) {
printf ("Bad Data CRC\n");
SHOW_BOOT_PROGRESS (-12);
do_reset (cmdtp, flag, argc, argv);
}
printf ("OK\n");
}
SHOW_BOOT_PROGRESS (11);
if ((hdr->ih_os != IH_OS_LINUX) ||
(hdr->ih_arch != IH_CPU_ARM) ||
(hdr->ih_type != IH_TYPE_RAMDISK)) { /*合法性檢測*/
printf ("No Linux ARM Ramdisk Image\n");
SHOW_BOOT_PROGRESS (-13);
do_reset (cmdtp, flag, argc, argv);
}
#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
/*
*we need to copy the ramdisk to SRAM to let Linux boot
*/
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */
/*
* Now check if we have a multifile image
*/
} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
ulong tail = ntohl (len_ptr[0]) % 4;
int i;
SHOW_BOOT_PROGRESS (13);
/* skip kernel length and terminator */
data = (ulong) (&len_ptr[2]);
/* skip any additional image length fields */
for (i = 1; len_ptr[i]; ++i)
data += 4;
/* add kernel length, and align */
data += ntohl (len_ptr[0]);
if (tail) {
data += 4 - tail;
}
len = ntohl (len_ptr[1]);
} else {
/*
* no initrd image
*/
SHOW_BOOT_PROGRESS (14);
len = data = 0;
}
上面的代碼主要是檢查是否有initrd image,如有則記住它在內存的地址。
#ifdef DEBUG
if (!data) {
printf ("No initrd\n");
}
#endif
if (data) { /*保存initrd的地址*/
initrd_start = data;
initrd_end = initrd_start + len;
} else {
initrd_start = 0;
initrd_end = 0;
}
SHOW_BOOT_PROGRESS (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
/*下面這些實際上就是設置好參數*/
// 在smdk2410.h文件中定義瞭如下宏
// #define CONFIG_CMDLINE_TAG
// #define CONFIG_SETUP_MEMORY_TAGS
// #define CONFIG_INITRD_TAG
// 根據上面不同的宏加載不同的TAG
// 注意的是必須定義CONFIG_CMDLINE_TAG和CONFIG_SETUP_MEMORY_TAGS
// 除非內核已經根據系統初始化了這些值, 否則必須定義, 不定義將導致無法啓動.
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
defined (CONFIG_CMDLINE_TAG) || \
defined (CONFIG_INITRD_TAG) || \
defined (CONFIG_SERIAL_TAG) || \
defined (CONFIG_REVISION_TAG) || \
defined (CONFIG_LCD) || \
defined (CONFIG_VFD)
setup_start_tag (bd); /* 設置ATAG_CORE標誌 */
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd); /* 設置內存標記 */
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline); /* 設置命令行標記 */
#endif
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd); /* 設置ATAG_NONE標誌 */
#endif
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux (); /* 啓動內核前對CPU作最後的設置 */
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); /*正式跳轉到linux kernel*/
}
其中的setup_start_tag,setup_memory_tags,setup_end_tag函數在lib_arm/armlinux.c中定義如下:
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params; /* 內核的參數的開始地址 */
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size (tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params);
}
標記列表必須以ATAG_CORE開始,setup_start_tag函數在內核的參數的開始地址設置了一個ATAG_CORE標記。
#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
int i;
/*設置一個內存標記 */
for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);
params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;
params = tag_next (params);
}
}
#endif /* CONFIG_SETUP_MEMORY_TAGS */
setup_memory_tags函數設置了一個ATAG_MEM標記,該標記包含內存起始地址,內存大小這兩個參數。
#ifdef CONFIG_SERIAL_TAG
void setup_serial_tag (struct tag **tmp)
{
struct tag *params = *tmp;
struct tag_serialnr serialnr;
void get_board_serial(struct tag_serialnr *serialnr);
get_board_serial(&serialnr);
params->hdr.tag = ATAG_SERIAL;
params->hdr.size = tag_size (tag_serialnr);
params->u.serialnr.low = serialnr.low;
params->u.serialnr.high= serialnr.high;
params = tag_next (params);
*tmp = params;
}
#endif
setup_serial_tag函數傳進來的參數是¶ms,而params在setup_start_tag()裏已經被初始化爲
params = (struct tag *) bd->bi_boot_params;
因此該函數對params裏變量的賦值實際上就是在對bd->bi_boot_params下賦值,因此bi_boot_params地址處存放了所有相關的參數。最後這個地址被傳給了linux kernel。
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
標記列表必須以標記ATAG_NONE結束,setup_end_tag函數設置了一個ATAG_NONE標記,表示標記列表的結束。
U-Boot設置好標記列表後就要調用內核了。但調用內核前,CPU必須滿足下面的條件:
(1) CPU寄存器的設置
Ø r0=0
Ø r1=機器碼
Ø r2=內核參數標記列表在RAM中的起始地址
(2) CPU工作模式
Ø 禁止IRQ與FIQ中斷
Ø CPU爲SVC模式
(3) 使數據Cache與指令Cache失效
do_bootm_linux中調用的cleanup_before_linux函數完成了禁止中斷和使Cache失效的功能。cleanup_before_linux函數在cpu/arm920t/cpu.c中定義:
int cleanup_before_linux (void)
{
/*
* this function is called just before we call linux
* it prepares the processor for linux
*
* we turn off caches etc ...
*/
unsigned long i;
disable_interrupts (); /* 禁止FIQ/IRQ中斷 */
/* turn off I/D-cache /*關掉數據和指令的cache*/ */
asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));
i &= ~(C1_DC | C1_IC);
asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));
/* flush I/D-cache 復位/刷新數據和指令的cache */
i = 0;
asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));
return (0);
}
由於U-Boot啓動以來就一直工作在SVC模式,因此CPU的工作模式就無需設置了。
do_bootm_linux中:
void (*theKernel)(int zero, int arch, uint params);
theKernel = (void (*)(int, int, uint))images->ep;
theKernel (0, machid, bd->bi_boot_params);
第2行代碼將內核的入口地址“images->ep”強制類型轉換爲函數指針。根據ATPCS規則,函數的參數個數不超過4 個時,使用r0~r3這4個寄存器來傳遞參數。因此第3行的函數調用則會將0放入r0,機器碼machid放入r1,內核參數地址 bd->bi_boot_params放入r2,從而完成了寄存器的設置,最後轉到內核的入口地址。
到這裏,U-Boot的工作就結束了,系統跳轉到Linux內核代碼執行。後面會再分析linux啓動順序,這樣可以有個銜接。