我们先来看初始化函数表: init_sequence
lib_arm/board.c:
typedef int (init_fnc_t) (void);
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 */
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) || defined (CONFIG_CMC_PU2)
checkboard,
#endif
NULL,
};
这些初始化函数会依次执行,我们一个个的来看
arm920t/Cpu.c:
int cpu_init (void)
{
/*
* setup up stacks if necessary
*/
#ifdef CONFIG_USE_IRQ
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
return 0;
}
对于smdk2410来说这个宏CONFIG_USE_IRQ没定义,实际上就是把IRQ_STACK_START, FIQ_STACK_START指到RAM中的IRQ stuff区域。
在看board_init:
board/Smdk2410.c:
/*
* Miscellaneous platform dependent initialisations
*/
int board_init (void)
{
DECLARE_GLOBAL_DATA_PTR;
//获取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 */
//配置MPLL,同样可参考data sheet
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
/* some delay between MPLL and UPLL */
delay (4000);
/* configure UPLL */
//配置UPLL
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);
/* some delay between MPLL and UPLL */
delay (8000);
/* set up the I/O ports */
//配置每个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;
/* arch number of SMDK2410-Board */
//保存arch number
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;
}
这个函数是和特定板子相关的,因此一般都是自己添加的,使能cache很简单,只要把协处理器15的相关位打开就行了,代码就不列出来了,可以参考datasheet。
接下来该看初始化函数: interrupt_init,我们的CPU是arm920t系列的s3c2410
cpu/arm920t/s3c24x0:
int interrupt_init (void)
{
S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();
/* use PWM Timer 4 because it has no output */
/* 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);
}
/* load value for 10 ms timeout */
lastdec = timers->TCNTB4 = timer_load_val;
/* auto load, manual update of Timer 4 */
timers->TCON = (timers->TCON & ~0x0700000) | 0x600000;
/* auto load, start 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下
common/Env_flash.c:
int env_init(void)
{
DECLARE_GLOBAL_DATA_PTR; /*这个还记得吗?*/
#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;
return(0);
}
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 0;
return (0);
}
这个函数主要是在gd里保存环境变量的存放地址。一般使用默认的环境变量值即default_environment数组,
uchar default_environment[] = {
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "/0"
#endif
#ifdef CONFIG_BOOTCOMMAND
"bootcmd=" CONFIG_BOOTCOMMAND "/0"
#endif
……
}
可见环境变量以如下的方式存放在数组中
Name=value
并且以”/0”结束, 而类似CONFIG_BOOTARGS的宏都定义在板子自己的配置文件中即smdk2410.h里。
接下来看init_baudrate
lib_arm/Board.c:
static int init_baudrate (void)
{
DECLARE_GLOBAL_DATA_PTR;
//从环境变量中获取波特率值
uchar 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:
/*
* Initialise the serial port with the given baudrate. The settings
* are always 8 data bits, no parity, 1 stop bit, no start bits.
*
*/
int serial_init (void)
{
serial_setbrg (); //设置波特率,停止位等
return (0);
}
cpu/arm920t/s3c24x0:
void serial_setbrg (void)
{
DECLARE_GLOBAL_DATA_PTR;
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);//UART寄存器地址
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; //Rx,Tx FIFO reset, 使能FIFO。
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:
/* Called before relocation - use serial functions */
int console_init_f (void)
{
DECLARE_GLOBAL_DATA_PTR;
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的版本信息
printf ("U-Boot code: %08lX -> %08lX BSS: -> %08lX/n",
_armboot_start, _bss_start, _bss_end); //打印U-BOOT代码位置
#ifdef CONFIG_MODEM_SUPPORT
puts ("Modem Support enabled/n");
#endif
#ifdef CONFIG_USE_IRQ
printf ("IRQ Stack: %08lx/n", IRQ_STACK_START);
printf ("FIQ Stack: %08lx/n", FIQ_STACK_START);
#endif
return (0);
}
这个函数就是在控制台上打印一些系统信息。
接着看dram_init:
board/smdk2410/Smdk2410.c:
int dram_init (void)
{
DECLARE_GLOBAL_DATA_PTR;
gd->bd->bi_dram[0].start = PHYS_SDRAM_1; //RAM起始地址
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; //RAM大小
return 0;
}
RAM的起始地址和大小都是和特定板子相关的,因此这两个宏都在Smdk2410.h中根据实际情况定义的,
再看display_dram_config
lib_arm/Board.c:
/*
* WARNING: this code looks "cleaner" than the PowerPC version, but
* has the disadvantage that you either get nothing, or everything.
* On PowerPC, you might see "DRAM: " before the system hangs - which
* gives a simple yet clear indication which part of the
* initialization if failing.
*/
static int display_dram_config (void)
{
DECLARE_GLOBAL_DATA_PTR;
int i;
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");
}
return (0);
}
呵呵仅仅是打印系统RAM的信息。
接着说初始化函数, 对于Smdk2410来说不存在checkboard这个函数,
这样整个初始化函数表的函数都看完了,总结一下主要做了如下过程:
1. cpu, borad, interrupt的初始化,包括cache等,这些都于特定板子的配置有关。
2. 环境变量的初始化,
3. 串口,控制台,RAM的初始化,
4. 在控制台上实时的显示系统配置等相关参数。
最后需要说明的是,大部分的配置参数都是预先在include/configs/board_name.h下定义的,因此如果我们要移植我们自己的板子的话,这个文件必不可少,它描述了我们板子的配置情况如CPU型号,RAM大小等。
//---------------------------------------------------------------------------------------
/*配置可用的Flash */
size = flash_init ();
……
/* 初始化堆空间 */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
#if (CONFIG_COMMANDS & CFG_CMD_NAND)
puts ("NAND: ");
nand_init(); /* go init the NAND */
#endif
/* 重新定位环境变量, */
env_relocate ();
/* 从环境变量中获取IP地址 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 以太网接口MAC 地址 */
……
devices_init (); /* 设备初始化 */
jumptable_init (); //跳转表初始化
console_init_r (); /* 完整地初始化控制台设备 */
enable_interrupts (); /* 使能中断处理 */
/* 通过环境变量初始化 */
if ((s = getenv ("loadaddr")) != NULL) {
oad_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop()循环不断执行 */
for (;;) {
main_loop (); /* 主循环函数处理执行用户命令 -- common/main.c */
}
//----------------------
之后就进入到了main_loop()延时3秒,检测用户的输入,如果没有用户的输入,那么就直接
run_command()运行bootcmd中的bootm命令,这时就进入到了common/cmd_bootm.c中的do_bootm(),在 do_bootm中判断是哪种内核,如果是linux内核则调用lib_mips/mips_linux.c中do_bootm_linux()函数把参 数传给内核,当然这期间还有很多工作要做的比如:验证Magic Number,验证Header Checksum,判断是不是initrd image,解压缩,等等工作,之后就是theKernel (linux_argc, linux_argv, linux_env, 0);传参数给内核,这样u-boot的任务就完成了。接下来就是内核启动的过程了。