u-boot启动代码分析
u-boot版本为u-boot-2009.08,平台smdk2410
一、第一阶段启动代码分析
在学习ARM时就知道,ARM在上电复位时将PC指针修改为0,即ARM是从0地址开始读取指令执行的。在cpu/arm920t/目录下有个u-boot.lds链接脚本,首先看代码段定义:
. =0x00000000;
. =ALIGN(4);
.text :
{
cpu/arm920t/start.o (.text)
*(.text)
}
我们看start.o就是放在0地址处的,所以它就是我们要找的启动代码,再看程序的入口点在哪:
ENTRY(_start)
程序入口点是由ENTRY伪指令指定的,所以程序的入口点就是_start,最终我们找到了程序的入口点,cpu/arm920t/start.S中的_start。
1.异常向量表定义
.globl_start
_start: b start_code
ldr pc,_undefined_instruction
ldr pc,_software_interrupt
ldr pc,_prefetch_abort
ldr pc,_data_abort
ldr pc,_not_used
ldr pc,_irq
ldr pc,_fiq
_undefined_instruction: .word undefined_instruction
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
上电复位后,一条跳转指令跳转到start_code
2.设置ARM工作模式
start_code:
/*
*set the cpu to SVC32 mode
*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
设置ARM工作模式为管理模式,并禁止所有中断。
3.关闭看门狗
ldr r0,=pWTCON
mov r1,#0x0
str r1,[r0]
4.屏蔽所有中断
/*
*mask all IRQs by setting all bits in the INTMR - default
*/
mov r1,#0xffffffff
ldr r0,=INTMSK
str r1,[r0]
# if defined(CONFIG_S3C2410)
ldr r1,=0x3ff
ldr r0,=INTSUBMSK
str r1,[r0]
# endif
5.设置ARM时钟频率分频比
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0,=CLKDIVN
mov r1,#3
str r1,[r0]
6.清除cache和禁止MMU
bl cpu_init_crit
一条跳转指令跳转到cpu_init_crit
cpu_init_crit:
/*
*flush v4 I/D caches
*/
mov r0,#0
mcr p15,0, r0, c7, c7, 0 /* flush v3/v4 cache*/
mcr p15,0, r0, c8, c7, 0 /* flush v4 TLB */
/*
*disable MMU stuff and caches
*/
mrc p15,0, r0, c1, c0, 0
bic r0,r0, #0x00002300 @ clear bits 13, 9:8(--V- --RS)
bic r0,r0, #0x00000087 @ clear bits 7, 2:0(B--- -CAM)
orr r0,r0, #0x00000002 @ set bit 2 (A) Align
orr r0,r0, #0x00001000 @ set bit 12 (I)I-Cache
mcr p15,0, r0, c1, c0, 0
7.初始化ARM存储控制器
bl lowlevel_init
跳转到lowlevel_init,这个符号定义在board/samsung/smdk2410/lowlevel_init.S中
8.拷贝代码到SDRAM中
#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate: /* relocate U-Boot to RAM */
adr r0,_start /* r0 <- current positionof code */
ldr r1,_TEXT_BASE /* test if we run fromflash or RAM */
cmp r0, r1 /* don'treloc during debug */
beq stack_setup
ldr r2,_armboot_start
ldr r3,_bss_start
sub r2,r3, r2 /* r2 <- size ofarmboot */
add r2,r0, r2 /* r2 <- source endaddress */
copy_loop:
ldmia r0!,{r3-r10} /* copy from sourceaddress [r0] */
stmia r1!,{r3-r10} /* copy to target address [r1] */
cmp r0,r2 /* until source endaddreee [r2] */
ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */
先是比较_start和_TEXT_BASE这两个符号的地址是否相等,_TEXT_BASE这个符号取值为TEXT_BASE,它的值为0x33F80000。其实就是判断u-boot是否已经在SDRAM中,如果u-boot已经在SDRAM中,那么也就不必拷贝了,直接跳到堆栈设置。如果u-boot没有在SDRAM中,那么就将u-boot拷贝到SDRAM中,CPU是可以从nor flash中取指执行的,但是在SDRAM中执行速度更快,所以将代码拷贝到SDRAM中。
u-boot代码段起始地址是放在寄存器r0中的,然后计算代码段的结束地址,结束地址放在寄存器r2中,然后使用多寄存器加载指令将代码复制到SDRAM中,代码放在SDRAM中的什么地方的呢,就是放在TEXT_BASE这个地方的,也就是0x33F80000这个地方。
9.设置栈
/* Set up the stack */
stack_setup:
ldr r0,_TEXT_BASE /* upper 128 KiB:relocated uboot */
sub r0,r0, #CONFIG_SYS_MALLOC_LEN /* mallocarea */
sub r0,r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0,r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp,r0, #12 /* leave 3 words forabort-stack */
0x33F80000是代码段的起始地址,将这个地址减去堆空间大小192K,其中堆空间包括64K的环境变量空间。再减去全局数据区的128字节,将栈指针指向这里。其实就是将栈指针指向一段空闲的内存区。
10.bss段清零
clear_bss:
ldr r0,_bss_start /* find start of bsssegment */
ldr r1,_bss_end /* stop here */
mov r2,#0x00000000 /* clear */
clbss_l:str r2, [r0] /*clear loop... */
add r0,r0, #4
cmp r0,r1
ble clbss_l
11.跳转到第二阶段执行
ldr pc, _start_armboot
二、第二阶段启动代码分析
第二阶段启动代码是从lib_arm/board.c的start_armboot函数开始的
/* Pointer is writable since we allocateda register for it */
gd = (gd_t*)(_armboot_start -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier neededfor 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));
首先将全局数据指针指向全局数据区,清零这个内存区,全局数据区后为板级数据区,将板级数据指针指向这里。
SDRAM空间分配图如下:
for (init_fnc_ptr = init_sequence;*init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
然后一个for循环执行init_sequence函数指针数组中的函数。
1.板级初始化
int board_init (void)
{
S3C24X0_CLOCK_POWER * const clk_power =S3C24X0_GetBase_CLOCK_POWER();
S3C24X0_GPIO * const gpio =S3C24X0_GetBase_GPIO();
/* to reduce PLL lock time, adjust theLOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;
/*设置FCLK和UCLK时钟频率*/
/* 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);
/* set up the I/O ports */
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;
/*设置板子机器码,要和Linux中机器码对应*/
/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number =MACH_TYPE_SMDK2410;
/*设置启动参数地址*/
/* adress of boot parameters */
gd->bd->bi_boot_params =0x30000100;
icache_enable();
dcache_enable();
return 0;
}
2.时钟初始化
int timer_init (void)
{
S3C24X0_TIMERS * const timers =S3C24X0_GetBase_TIMERS();
/* use PWM Timer 4 because it has nooutput */
/* prescaler for Timer 4 is 16 */
timers->TCFG0 = 0x0f00;
if (timer_load_val == 0)
{
/*
* for 10 ms clock period @ PCLK with 4 bitdivider = 1/2
* (default) and prescaler = 16. Should be10390
* @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);
}
这个函数做时钟的初始化,在移植到S3C2440的时候要注意,S3C2440和S3C2410的时钟频率设置是不一样的,所以这个地方要针对S3C2440做特别的修改。
3.第三个函数是env_init,环境变量初始化,这要看我们是将环境变量放在什么地方了,如果是放在nor flash中,那么执行的是common/env_flash.c中的函数,如果是放在nand flash中,那么执行的是common/env_nand.c中的函数。我们只看common/env_flash.c中的env_init函数。
int env_init(void)
{
if (crc32(0, env_ptr->data, ENV_SIZE)== env_ptr->crc) {
gd->env_addr = (ulong)&(env_ptr->data);
gd->env_valid = 1;
return(0);
}
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 0;
return (0);
}
首先是判断flash是否存在环境变量,如果flash存在环境变量,那就将全局数据结构中环境变量起始地址设置为include/configs/smdk2410.h由宏CONFIG_ENV_ADDR定义的值。如果flash不存在环境变量,那就将这个地址设置为default_environment数组地址值,表示采用默认环境变量。
4.串口波特率初始化
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);
}
首先是从环境变量中去获取,如果环境变量中没有定义,就使用配置文件中include/configs/smdk2410.h中定义的波特率值。
5.串口初始化
/*Initialise the serial port. The settings are always 8 data bits, no parity,
* 1 stop bit, no start bits.
*/
static int serial_init_dev(const int dev_index)
{
S3C24X0_UART * const uart =S3C24X0_GetBase_UART(dev_index);
/* 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;
#ifdef CONFIG_HWFLOW
uart->UMCON = 0x1; /* RTS up */
#endif
/* FIXME: This is sooooooooooooooooooougly */
#if defined(CONFIG_ARCH_GTA02_v1) || defined(CONFIG_ARCH_GTA02_v2)
/* we need auto hw flow control on thegsm and gps port */
if (dev_index == 0 || dev_index == 1)
uart->UMCON = 0x10;
#endif
_serial_setbrg(dev_index);
return (0);
}
#if !defined(CONFIG_SERIAL_MULTI)
/*Initialise the serial port. The settings are always 8 data bits, no parity,
* 1 stop bit, no start bits.
*/
int serial_init (void)
{
return serial_init_dev(UART_NR);
}
#endif
6.第一阶段控制台初始化
/* Calledbefore relocation - use serial functions */
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;
}
7.显示u-boot版本信息
static int display_banner (void)
{
printf ("\n\n%s\n\n", version_string);
debug ("U-Boot code: %08lX ->%08lX BSS: -> %08lX\n",
_armboot_start, _bss_start, _bss_end);
#ifdef CONFIG_MODEM_SUPPORT
debug ("Modem Supportenabled\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);
}
在u-boot启动起来后,显示的第一条信息
8.内存初始化
int dram_init (void)
{
/*SDRAM起始地址和SDRAM大小*/
gd->bd->bi_dram[0].start =PHYS_SDRAM_1;
gd->bd->bi_dram[0].size =PHYS_SDRAM_1_SIZE;
return 0;
}
9.显示SDRAM相关信息
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);
}
init_sequence数组中的函数也就执行完了。
/* armboot_start is defined in theboard-specific linker script */
mem_malloc_init (_armboot_start -CONFIG_SYS_MALLOC_LEN);
接下来是将堆的内存空间清零。
10.显示flash相关信息
static void display_flash_config (ulong size)
{
puts ("Flash: ");
print_size (size, "\n");
}
11.nand flash初始化
void nand_init(void)
{
int i;
unsigned int size = 0;
for (i = 0; i <CONFIG_SYS_MAX_NAND_DEVICE; i++) {
nand_init_chip(&nand_info[i],&nand_chip[i], base_address[i]);
size += nand_info[i].size / 1024;
if (nand_curr_device == -1)
nand_curr_device = i;
}
printf("%u MiB\n", size /1024);
#ifdef CONFIG_SYS_NAND_SELECT_DEVICE
/*
*Select the chip in the board/cpu specific driver
*/
board_nand_select_device(nand_info[nand_curr_device].priv,nand_curr_device);
#endif
}
12.加载环境变量
void env_relocate (void)
{
DEBUGF ("%s[%d] offset = 0x%lx\n",__FUNCTION__,__LINE__,
gd->reloc_off);
#ifdef CONFIG_AMIGAONEG3SE
enable_nvram();
#endif
#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(CONFIG_ENV_SIZE);
DEBUGF ("%s[%d] malloced ENV at%p\n", __FUNCTION__,__LINE__,env_ptr);
#endif
/*gd->env_valid== 0表示flash没有环境变量,从而采用默认的环境变量*/
if (gd->env_valid == 0) {
#if defined(CONFIG_GTH) || defined(CONFIG_ENV_IS_NOWHERE) /*Environment not changable */
puts ("Using defaultenvironment\n\n");
#else
puts ("*** Warning - bad CRC,using default environment\n\n");
show_boot_progress (-60);
#endif
/*拷贝默认的环境变量到相关内存中*/
set_default_env();
}
else {
/*将环境变量拷贝到内存中*/
env_relocate_spec ();
}
/*将全局数据结构中环境变量指针指向环境变量内存空间处*/
gd->env_addr =(ulong)&(env_ptr->data);
#ifdef CONFIG_AMIGAONEG3SE
disable_nvram();
#endif
}
如果flash中有环境变量,则将flash中环境变量拷贝到SDRAM中环境变量空间中。但是如果flash没有环境变量,就采用默认的环境变量,也要将默认的环境变量拷贝到环境变量空间中,所以说在新烧写的u-boot启动的时候,会显示一条警告信息,表示采用默认的环境变量。
13.以太网的初始化
eth_initialize(gd->bd);
14.最后死循环,处理用户命令
/* main_loop() can return to retryautoboot, if so just run it again. */
for (;;) {
main_loop ();
}
第二阶段启动代码主要是外围设备的初始化,比如nor flash、nand flash、串口和以太网等等。