ARM结构的CPU在上电后,会从地址0x00000000开始执行。嵌入式开发板中,需要把存储器件ROM或Flash等映射到这个地址,u-boot就存放在这个地址,CPU一上电就先执行u-boot。
u-boot的目标是启动内核,要能够完成两个目标过程:
1.从Flash上把内核读入内存。
a.能读Flash;
b.关看门狗、初始化时钟、内存等。
2.启动内核。
a.设置参数;
b.跳转执行。
根据需要完成的目标过程,需要针对具体的电路板对u-boot做相应的修改设置。
u-boot通常可分为两个阶段,第一个阶段要做的事情有:
使用汇编,完成一些依赖于CPU体系结构的初始化,包括:
a.硬件设备的初始化(包括关WATCHDOG、屏蔽中断、设置CPU的速度和时钟分频系数、SDRAM初始化等,但并非必需);
b.为加载好的bootloader的第二阶段代码准备RAM空间;
c.复制bootloader的第二阶段代码到RAM空间中;
d.设置好栈;
e.跳转到第二阶段代码的C入口点。
首先根据u-boot.lds链接脚本可以知道:
a.指定输出文件是elf格式,32位ARM指令,小端模式;
b.指定输出可执行文件的运行平台为ARM;
c.ENTRY(_start)指定_start函数为程序的入口,代码在u-boot-2012.04.01\arch\arm\cpu\arm920t\start.S中。
e.真正的启动运行地址段在编译时由CONFIG_SYS_TEXT_BASE宏定义,即TEXT_BASE = 0x00000000
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
我们看看代码,u-boot-2012.04.01\arch\arm\cpu\arm920t\start.S:
.globl _start
_start: b start_code /* 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
.balignl 16,0xdeadbeef
_start是一个符号,符号在汇编程序中代表一个地址,汇编程序经过汇编器的处理之后,所有的符号都被替换成它所代表的地址值。
_start就像C程序的main函数一样特殊,是整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址,所以每个汇编程序都要提供一个_start符号并且用.globl声明。
以上代码是设置arm的异常向量表,此处有7种异常,每种异常占用4个字节,每种异常入口处都填写一条跳转指令,直接跳转到相应的异常处理函数中,用ldr将处理函数入口地址加载到pc中。
.balignl 16,0xdeadbeef
该指令为汇编伪操作符,表示以当前地址为开始,找到第一次出现的以第一个参数为整数倍的地址,并将其作为结束地址,在这个结束地址前面存储一个字(4字节)长度的数据,存储内容正是第二个参数。如果当前地址正好是第一个参数的倍数,则没有数据被写入到内存。它们的作用就是为内存标记,表示从这个位置往后的一段有特殊作用的内存,而这个位置往前,禁止访问。
继续看代码
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr /* 将cpsr状态寄存器的值读出来,保存到r0寄存器中 */
bic r0, r0, #0x1f /* 清除r0寄存器的bit[4:0](即cpsr的模式域),并将结果放入r0寄存器中 */
orr r0, r0, #0xd3 /* orr,逻辑或,将r0寄存器中的值或11010011,并将结果存入r0寄存器,bit[4:0] = 10011(设置cpu模式),bit5(工作状态),bit[7:6](FIQ、IRQ中断禁止) */
msr cpsr, r0 /* 将r0寄存器中的值传回到程序状态寄存器中 */
设置cpu为管理模式,关中断完成,继续阅读代码:
/* turn off the watchdog */
/* S3C2440的各个寄存器地址
* 这里要根据自己使用的具体cpu进行相应设置
*/
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
ldr r0, =pWTCON /* 将pWTCON地址处的字(4字节)数据放入r0寄存器中 */
mov r1, #0x0 /* 将立即数0放入r1寄存器 */
str r1, [r0] /* 将r1中的值传送到地址值为r0的内存中 */
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0]
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
/* FCLK:HCLK:PCLK = 1:4:8 */
ldr r0, =CLKDIVN
mov r1, #0x05;
str r1, [r0]
该段代码完成关看门狗,屏蔽中断,设置时钟分频系数。
继续分析代码:
bl cpu_init_crit
cpu_init_crit:
/*
* flush v4 I/D caches
*/
mov r0, #0 /* 将立即数传入r0寄存器中 */
mcr p15, 0, r0, c7, c7, 0 /* 使无效整个icache和dcache */
mcr p15, 0, r0, c8, c7, 0 /* 使无效整个数据和指令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
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
mov pc, lr
关于mcr与mrc指令的搭配可以查看该文章,写的非常清楚,在此也感谢该作者:
原文:https://blog.csdn.net/gameit/article/details/13169405
这段代码关闭icache和dcache,并关闭MMU,并进入lowlevel_init函数对存储控制器进行初始化。
继续分析lowlevel_init:
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1 /* r0的值(SMRDATA地址)减r1的值(_TEXT_BASE地址),结果放入r0寄存器中 */
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4 /* r0的值加上立即数(13个寄存器*每个寄存器的4字节地址)总的地址长度传给r2寄存器 */
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.long 0x22011110 @ BWSCON
.long 0x00000700 @ BANKCON0
.long 0x00000700 @ BANKCON1
.long 0x00000700 @ BANKCON2
.long 0x00000700 @ BANKCON3
.long 0x00000700 @ BANKCON4
.long 0x00000700 @ BANKCON5
.long 0x00018005 @ BANKCON6
.long 0x00018005 @ BANKCON7
.long 0x008C07A3 @ REFRESH
.long 0x000000B1 @ BANKSIZE
.long 0x00000030 @ MRSRB6
.long 0x00000030 @ MRSRB7
完成对s3c2440存储控制器的各个寄存器进行设置,之后存储控制器才可以使用,并开始准备设置堆栈寄存器sp。
后面开始调用C程序,所以需要先进行栈指针sp的初始化:
#define PHYS_SDRAM_1 0x30000000 /* s3c2440的SDRAM6的基地址为0x30000000,SDRAM Bank #1 */
#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_SDRAM_BASE + 0x1000 - GENERATED_GBL_DATA_SIZE)
/* Set stackpointer in internal RAM to call board_init_f */
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) /* sp为栈指针,0x30000f80 */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f
在反汇编u-boot.dis文件中:
00000098 <call_board_init_f>:
98: e59fd3d8 ldr sp, [pc, #984] ; 478 <fiq+0x58>
9c: e3cdd007 bic sp, sp, #7 ; 0x7
a0: e3a00000 mov r0, #0 ; 0x0
a4: eb0007f1 bl 2070 <board_init_f>
/* 在反汇编文件中查找 478 */
478: 30000f80 .word 0x30000f80
然后进入到单板功能board_init_f函数中,board_init_f函数主要是根据配置对全局信息结构体gd进行初始化:
void board_init_f(ulong bootflag)
{
...
/* gd是一个全局结构体 ,用来保存uboot的一些全局信息,需要单独的一块内存
* 在后面的所有代码中要使用gd结构体
* 必须在文件中加入DECLARE_GLOBAL_DATA_PTR宏定义
*/
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07); /* gd指向0x30000f80地址 */
memset((void *)gd, 0, sizeof(gd_t)); /* 用0来填充gd_t结构体大小的一块空间,指针gd就指向该块空间 */
gd->mon_len = _bss_end_ofs;
/* Allow the early environment to override the fdt address */
gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16,
(uintptr_t)gd->fdt_blob);
/* 遍历调用函数数组init_sequence中的各个函数 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
init_sequence定义如下:
init_fnc_t *init_sequence[] = {
...
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f,
#endif
...
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
...
dram_init, /* configure available RAM banks */
NULL,
};
在board_early_init_f函数中,设置系统时钟,设置GPIO端口:
int board_early_init_f(void)
{
struct s3c24x0_clock_power * const clk_power =
s3c24x0_get_base_clock_power();
struct s3c24x0_gpio * const gpio = s3c24x0_get_base_gpio();
/* to reduce PLL lock time, adjust the LOCKTIME register */
writel(0xFFFFFF, &clk_power->locktime);
/* configure MPLL */
writel((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV,
&clk_power->mpllcon);
/* some delay between MPLL and UPLL */
pll_delay(4000);
/* configure UPLL */
writel((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV,
&clk_power->upllcon);
/* some delay between MPLL and UPLL */
pll_delay(8000);
/* set up the I/O ports */
writel(0x007FFFFF, &gpio->gpacon);
writel(0x00044555, &gpio->gpbcon);
writel(0x000007FF, &gpio->gpbup);
writel(0xAAAAAAAA, &gpio->gpccon);
writel(0x0000FFFF, &gpio->gpcup);
writel(0xAAAAAAAA, &gpio->gpdcon);
writel(0x0000FFFF, &gpio->gpdup);
writel(0xAAAAAAAA, &gpio->gpecon);
writel(0x0000FFFF, &gpio->gpeup);
writel(0x000055AA, &gpio->gpfcon);
writel(0x000000FF, &gpio->gpfup);
writel(0xFF95FFBA, &gpio->gpgcon);
writel(0x0000FFFF, &gpio->gpgup);
writel(0x002AFAAA, &gpio->gphcon);
writel(0x000007FF, &gpio->gphup);
return 0;
}
初始化定时器:
int timer_init(void)
{
struct s3c24x0_timers *timers = s3c24x0_get_base_timers();
ulong tmr;
/* use PWM Timer 4 because it has no output */
/* prescaler for Timer 4 is 16 */
writel(0x0f00, &timers->tcfg0);
if (gd->tbu == 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
*/
gd->tbu = get_PCLK() / (2 * 16 * 100);
gd->timer_rate_hz = get_PCLK() / (2 * 16);
}
/* load value for 10 ms timeout */
writel(gd->tbu, &timers->tcntb4);
/* auto load, manual update of timer 4 */
tmr = (readl(&timers->tcon) & ~0x0700000) | 0x0600000;
writel(tmr, &timers->tcon);
/* auto load, start timer 4 */
tmr = (tmr & ~0x0700000) | 0x0500000;
writel(tmr, &timers->tcon);
gd->lastinc = 0;
gd->tbl = 0;
return 0;
}
再进行其他的一些函数初始化之后,回到board_init_f函数中:
void board_init_f(ulong bootflag)
{
...
debug("monitor len: %08lX\n", gd->mon_len);
/*
* Ram is setup, size stored in gd !!
*/
debug("ramsize: %08lX\n", gd->ram_size);
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; /* ram_size初始值是64M,所以addr初始值为0x34000000 */
/* 如果打开了icache和dcache,预留出16K大小的TLB */
if !(defined(CONFIG_SYS_ICACHE_OFF) &&defined(CONFIG_SYS_DCACHE_OFF))
/* reserve TLB table */
addr -= (4096 * 4); /* 0x34000000 - 0x4000 = 0x33FFC000*/
/* round down to next 64 kB limit */
addr &= ~(0x10000 - 1); /* 0x33ffc000 & ~(0xffff)= 0x33ff0000 */
gd->tlb_addr = addr;
debug("TLB table at: %08lx\n", addr);
#endif
/* round down to next 4 kB limit */
addr &= ~(4096 - 1);
debug("Top of RAM usable for U-Boot at: %08lx\n", addr);
/*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len; /* 大小为697KB,gd->mon_len = 0x000ae400,adrr = 0x34ff0000,addr - gd->mon_len = 0x33f41c00 */
addr &= ~(4096 - 1); /* addr = (0x33f41c00 & ~(0xfff)) = 0x33f41000 */
}
其中:
gd->mon_len = _bss_end_ofs;
在start.S文件中,有定义_bss_end_ofs:
.globl _bss_start_ofs
_bss_start_ofs:
.word __bss_start - _start
.globl _bss_end_ofs
_bss_end_ofs:
.word __bss_end__ - _start
通过查看反汇编文件,可以知道
00000000 <__image_copy_start>: /* _start的地址 */
00000048 <_bss_end_ofs>:
48: 000ae4e0 .word 0x000ae4e0 /* _bss_end__的地址 */
预留出了一块uboot大小的内存,(0x34ff0000 - 0x33f41000) = 0xaf000 = 700KB,addr指向该块内存的起始地址0x33f41000,也是后续代码从flash中重定位过来存放的起始地址。
继续看代码:
/*
* reserve memory for malloc() arena
*/
addr_sp = addr - TOTAL_MALLOC_LEN; /* TOTAL_MALLOC_LEN大小为0x400000,4M*/
debug("Reserving %dk for malloc() at: %08lx\n",
TOTAL_MALLOC_LEN >> 10, addr_sp);
/*
* (permanently) allocate a Board Info struct
* and a permanent copy of the "global" data
*/
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
debug("Reserving %zu Bytes for Board Info at: %08lx\n",
sizeof (bd_t), addr_sp);
addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp;
debug("Reserving %zu Bytes for Global Data at: %08lx\n",
sizeof (gd_t), addr_sp);
/* setup stackpointer for exeptions */
gd->irq_sp = addr_sp;
/* leave 3 words for abort-stack */
addr_sp -= 12;
/* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07;
......
}
首先预留malloc len,这里是0x400000.
注释中说明,为bd,gd做一个永久的copy。
留出了全局信息bd_t结构体的空间,首地址存在gd->bd。
留出gd_t结构体的空间。首地址存在id中。
将此地址保存在gd->irq_sp中作为异常栈指针。uboot中我们没有用到中断。
最后留出12字节,for abort stack,这个没看懂。
到这里addr_sp值确定,总结一下addr_sp之上空间分配。
由高到低 :
addr–>malloc len(0x400000)–>bd len–>gd len–>12 byte–>addr_sp(栈往下增长,addr_sp之下空间作为栈空间)。
最后在board_init_f函数中:
...
gd->bd->bi_baudrate = gd->baudrate;
/* Ram ist board specific, so move it to board code ... */
dram_init_banksize();
display_dram_config(); /* and display it */
gd->relocaddr = addr; /* 目标addr */
gd->start_addr_sp = addr_sp; /* 目标addr_sp */
gd->reloc_off = addr - _TEXT_BASE; /* 目标addr和现在实际code起始地址的偏移 */
debug("relocation Offset is: %08lx\n", gd->reloc_off);
memcpy(id, (void *)gd, sizeof(gd_t));
relocate_code(addr_sp, id, addr); /* r0 = addr_sp,r1 = id, r2 = addr */
/* NOTREACHED - relocate_code() does not return */
}
给bd->bi_baudrate赋值gd->baudrate,gd->baudrate是在前面baudrate_init中初始化。
dram_init_banksize()是需要实现的板级函数。根据板上ddrc获取ddr的bank信息。填充在gd->bd->bi_dram[CONFIG_NR_DRAM_BANKS]。
最后将gd结构体的数据拷贝到新的地址id上。
至此,board_init_f函数结束,回到start.S中进行代码的重定位操作。
代码重定向部分单独写了一篇文章:
u-boot的relocate_code部分分析
查看反汇编文件得到:
00000044 <_bss_start_ofs>:
44: 0006b568 .word 0x0006b568
00000048 <_bss_end_ofs>:
48: 000ae4e0 .word 0x000ae4e0
清除bss段:
clear_bss:
ldr r0, _bss_start_ofs /* r0 = 0x0006b568 */
ldr r1, _bss_end_ofs /* r1 = 0x000ae4e0 */
mov r4, r6 /* 将目标地址0x33f41000传入r4寄存器 */
add r0, r0, r4 /* r0 = 0x0006b568 + 0x33f41000 = 0x33fac568 */
add r1, r1, r4 /* r1 = 0x000ae4e0 + 0x33f41000 = 0x33fef4e0 */
mov r2, #0x00000000 /* 由代码重定向得到r2 = 0x00073608,将立即数0传入该地址 */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init
bl red_led_on
接下来分别调用了coloured_LED_init以及red_led_on,很多开发板都会有led指示灯,这里可以实现上电指示灯亮,有调试作用。
到这里,第一阶段已经完成,跳到第二阶段入口处。