u-boot分为两个阶段stage1和stage2,stage1只负责一些与特定CPU体系结构有关的代码,完成简单的初始化工作,使用汇编语言实现。stage2使用C语言实现以完成更多功能。stage1执行完之后会跳转到stage2执行。本文只做启动过程分析,不对代码实现做详细分析,具体u-boot第一阶段如何实现初始化工作、代码搬运以及第二阶段如何实现命令处理、系统启动等等的功能以后有机会再分析。
首先来看一下u-boot的链接脚本文件,简单来说 链接脚本文件就是控制所有(通过编译程序源文件生成的)“.o”文件排布组合成想要的可执行文件。链接脚本文件中指定好了什么地址存放什么文件,编译完成之后使用“ld”链接工具根据链接脚本文件将所有“.o”文件都组合起来生成目标文件。下面是Hi3519的链接脚本文件u-boot.lds:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; 把定位器符号置为0x00000000
. = ALIGN(4); 4字节对齐
.text :
{
__text_start = .;
arch/arm/cpu/hi3519v101/start.o (.text)
drivers/ddr/ddr_training_impl.o (.text)
drivers/ddr/ddr_training_ctl.o (.text)
drivers/ddr/ddr_training_boot.o (.text)
drivers/ddr/ddr_training_custom.o (.text)
__init_end = .;
ASSERT(((__init_end - __text_start) < 0x16000), "init sections too big!");
*(.text)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) }
_end = .;
}
详细的lds文件语法不多做介绍,因为我也不会,但是可以明显看出来几点:
①、程序从0x00000000处开始执行
②、arch/arm/cpu/hi3519v101/start.o放在了输出文件的最开始部分,start.o由start.S编译得到,所以其为start.S最开始运行的代码。
③、start.o之后还有很多ddr相关的文件被链接进来,应该是DRAM初始化部分的程序。
从上面得到的最关键的信息是start.S为最先执行的程序文件,那么下面分析一下start.S的汇编程序,做了一些简单的注释:
#include <config.h>
#include <version.h>
/*
*************************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*************************************************************************
*/
.globl _start
下面是中断向量表
_start: b reset ----复位
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
_pad: .word 0x12345678 /* now 16*4=64 */
__blank_zone_start:
.fill 1024*5,1,0
__blank_zone_end:
.globl _blank_zone_start
_blank_zone_start:
.word __blank_zone_start
.globl _blank_zone_end
_blank_zone_end:
.word __blank_zone_end
.balignl 16,0xdeadbeef
/*************************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!
* setup Memory and board specific bits prior to relocation.
* relocate armboot to ram
* setup stack
*
*************************************************************************/
_TEXT_BASE:
.word TEXT_BASE
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
_clr_remap_fmc_entry:
.word FMC_TEXT_ADRS + do_clr_remap - TEXT_BASE
_clr_remap_ddr_entry:
.word MEM_BASE_DDR + do_clr_remap - TEXT_BASE
_clr_remap_ram_entry:
.word RAM_START_ADRS + do_clr_remap - TEXT_BASE
_copy_abort_code:
.word copy_abort_code
/*
* the actual reset code
*/
reset: ----复位函数/入口函数
/*
* set the cpu to SVC32 mode ----设置CPU进入SVC32模式
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
/*
* Invalidate L1 I/D ----失能这个东西
*/
mov r0, #0 @ set up for MCR
mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
/*
* disable MMU stuff and caches ----关闭MMU和cache缓冲
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* read system register REG_SC_GEN2 ----读取REG_SC_GEN2寄存器并判断ziju标志位
* check if ziju flag
*/
ldr r0, =SYS_CTRL_REG_BASE
ldr r1, [r0, #REG_SC_GEN2]
ldr r2, =0x7a696a75 /* magic for "ziju" */
ldr r3, [r0, #REG_SC_GEN20] /* pcie slave start up flag*/
cmp r3, r2
beq after_ziju
cmp r1, r2
bne normal_start_flow
mov r1, sp /* save sp */
str r1, [r0, #REG_SC_GEN2] /* clear ziju flag */
after_ziju:
/* init PLL/DDRC/pin mux/... */ ----初始化PLL时钟、DDR控制器、引脚复用等功能
ldr r0, _blank_zone_start
ldr r1, _TEXT_BASE
sub r0, r0, r1
cmp r3, r2
beq pcie_slave_addr
ldr r1, =RAM_START_ADRS
ldr sp, =STACK_TRAINING
b ziju_ddr_init
pcie_slave_addr:
ldr r1, =0x0
ldr sp, =PCIE_SLV_STACK
ldr r4, =SYS_CTRL_REG_BASE
str r2, [r4, #HI3519V101_SYSBOOT10]
ziju_ddr_init:
add r0, r0, r1
mov r1, #0x0 /* flags: 0->normal 1->pm */
bl init_registers /* init PLL/DDRC/... */ ----跳转到init_registers函数初始化PLL和DDR控制器
/* after ziju, we need ddr traning */
/*#ifdef CONFIG_DDR_TRAINING_V2*/
ldr r0, =REG_BASE_SCTL
bl start_ddr_training /* DDR training */ ----ziju(?)之后进行DDR测试
/*#endif*/
ldr r0, =SYS_CTRL_REG_BASE
ldr r2, =PCIE_SLV_DDR_INIT_FLG
str r2, [r0, #HI3519V101_SYSBOOT9]
ldr r2, =0x7a696a75
ldr r1, [r0, #HI3519V101_SYSBOOT10]
cmp r1, r2
beq pcie_slave_hold
ldr r1, [r0, #REG_SC_GEN2]
mov sp, r1 /* restore sp */
ldr r1, [r0, #REG_SC_GEN3]
mov pc, r1 /* return to bootrom */
pcie_slave_hold:
ldr r2, [r0, #HI3519V101_SYSBOOT9]
ldr r1, =0x7964616f /* complete flag */
cmp r1, r2
bne pcie_slave_hold
ldr r1, =0x0
str r1, [r0, #HI3519V101_SYSBOOT9]
mov pc, #0x0
nop
nop
nop
nop
nop
nop
nop
nop
b . /* bug here */ ----死循环
normal_start_flow: ----正常启动流程
/* init serial and printf a string. */ ----初始化串口打印功能并使用printf打印一串字符
ldr sp, =STACK_TRAINING
bl uart_early_init ----跳转到uart_early_init串口初始化函数(函数在uart.S文件中)
bl msg_main_cpu_startup ----打印主CPU启动的信息到uart
/*
* enable cci snoop for GSF and VDMA
*/
ldr r0, =CCI_PORT_CTRL_0
mov r3, #CCI_ENABLE_REQ
str r3, [r0]
4: ldr r0, =CCI_CTRL_STATUS
ldr r1, [r0]
tst r1, #1
bne 4b
/*
* enable cci snoop for core 0
*/
ldr r0, =CCI_PORT_CTRL_1
mov r3, #CCI_ENABLE_REQ
str r3, [r0]
5: ldr r0, =CCI_CTRL_STATUS
ldr r1, [r0]
tst r1, #1
bne 5b
@if running not boot from nand/spi/emmc,
@we skipping boot_type checking.
mov r0, pc, lsr#24
cmp r0, #0x0
bne check_bootrom_type
check_boot_type:
ldr r0, =SYS_CTRL_REG_BASE
ldr r0, [r0, #REG_SYSSTAT]
mov r6, r0, lsr#5
and r6, #0x1
cmp r6, #0 @ [5]=0 fmc /* SPI Nor/Nand and Nand */
ldreq pc, _clr_remap_fmc_entry
@otherwise, [31]=1 means boot from bootrom, err
beq bug
check_bootrom_type:
cmp r0, #0x4
/*boot from bootrom,we copy the uboot.bin to ram (0x4010500)*/
ldreq pc, _clr_remap_ram_entry
do_clr_remap:
/* do clear remap */
ldr r4, =SYS_CTRL_REG_BASE
ldr r0, [r4, #REG_SC_CTRL]
@Set clear remap bit.
orr r0, #(1<<8)
str r0, [r4, #REG_SC_CTRL]
/*
* Set ACTLR.SMP to 1
* This is a bug on Cortex-A7 MPCORE. see buglist of Cortex-A7
* The D-caches are disabled when ACTLR.SMP is set to 0 regardless
* of the value of the cache enable bit. so we must set SMP bit of
* ACTLR register before enable D-cache
*/
mrc p15, 0, r0, c1, c0, 1
orr r0, #(1 << 6)
mcr p15, 0, r0, c1, c0, 1
@enable I-Cache now
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
mcr p15, 0, r0, c1, c0, 0
@Check wether I'm running in dynamic mem bank
mov r0, pc, lsr#28
cmp r0, #8
blo ddr_init
no_ddr_init:
adrl r0, _start
b copy_to_ddr
ddr_init:
ldr r0, _blank_zone_start
ldr r1, _TEXT_BASE
sub r0, r0, r1
adrl r1, _start
add r0, r0, r1
mov r1, #0 /* flags: 0->normal 1->pm */
bl init_registers
/*#ifdef CONFIG_DDR_TRAINING_V2*/
ldr sp, =STACK_TRAINING
ldr r0, =REG_BASE_SCTL
bl start_ddr_training /* DDR training */
/*#endif*/
check_boot_mode: ----检测boot模式
ldr r0, =SYS_CTRL_REG_BASE
ldr r0, [r0, #REG_SYSSTAT]
mov r6, r0, lsr#4
and r6, #0x3
cmp r6, #BOOT_FROM_EMMC
bne copy_flash_to_ddr
#ifdef CONFIG_HIMCI_V200
emmc_boot: ----从emmc中boot
ldr r0, _TEXT_BASE
ldr r1, _armboot_start
ldr r2, _bss_start
sub r1, r2, r1
bl emmc_boot_read
b jump_to_ddr
#endif
copy_flash_to_ddr: ----从flash中读取到ddr中
/* relocate SPI nor/nand Boot to DDR */
cmp r6, #BOOT_FROM_DDR
beq copy_to_ddr
ldr r0, =FMC_TEXT_ADRS /* 0x1400_0000 */
copy_to_ddr:
/* now, r0 stores __reset offset from where we get started */
ldr r1, _TEXT_BASE /* r1 stores where we will copy uboot to */
/* compare source and target address, *
*if equal no copy to target address */
cmp r0, r1
beq copy_abort_code
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
/* memcpy(r1, r0, r2) */
bl memcpy
jump_to_ddr: ----跳转到DDR中执行程序
ldr r0, _TEXT_BASE
ldr pc, _copy_abort_code
copy_abort_code:
ldr r1, =0x00000000
mov r2, #0x4000
/* memcpy(r1, r0, r2) */
bl memcpy
/* Set up the stack */ ----初始化栈
stack_setup:
ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot
sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area
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 for abort-stack
and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d
/* Clear BSS (if any). Is below tx (watch load addr - need space) */
clear_bss: ----清零BSS段
ldr r0, _bss_start @ find start of bss segment
ldr r1, _bss_end @ stop here
mov r2, #0x0 @ clear value
clbss_l:
str r2, [r0] @ clear BSS location
cmp r0, r1 @ are we at the end yet
add r0, r0, #4 @ increment clear index pointer
bne clbss_l @ keep clearing till at end
ldr pc, _start_armboot @ jump to C code ----跳转到stage2阶段的start_armboot函数
_start_armboot: .word start_armboot
......
简单看完start.S文件中的程序之后可以发现start.S就是uboot的stage1阶段,主要完成了一些CPU相关的初始化包括时钟系统、DDR控制器、串调试口等,同时将代码从emmc或者flash中搬运到DDR RAM中,最后完成向start_armboot函数的跳转,进入到stage2阶段运行。
在stage1汇编代码中调用stage2的入口函数start_armboot进入第二阶段,这个阶段的程序使用C语言实现,start_armboot函数在arch\arm\lib\board.c文件中:
void start_armboot (void)
{
init_fnc_t **init_fnc_ptr;
char *s;
#ifdef CONFIG_HAS_SLAVE
char *e;
#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 - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier needed for 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));
gd->flags |= GD_FLG_RELOC;
monitor_flash_len = _bss_start - _armboot_start;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
......
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
/* NOTREACHED - no way out of command loop except booting */
}
这个函数很长很长,中间的我不予理睬,前面有一段代码:
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_ARCH_CPU_INIT)
arch_cpu_init, /* basic arch cpu dependent setup */
#endif
timer_init, /* initialize timer before usb init */
board_init, /* basic board dependent setup */
#if defined(CONFIG_USE_IRQ)
interrupt_init, /* set up exceptions */
#endif
// timer_init, /* initialize timer */
#ifdef CONFIG_FSL_ESDHC
get_clocks,
#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
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard, /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
init_func_i2c,
#endif
dram_init, /* configure available RAM banks */
#if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
arm_pci_init,
#endif
/* display_dram_config */
NULL,
};
都是将初始化函数填到数组中,这样的做法是为了方便自己进行代码修改,执行自己需要的初始化函数。start_armboot 函数的最后面的死循环调用main_loop也是很重要的线索,这个main_loop 函数就是u-boot第二阶段的主循环,定义在common/main.c文件中,这个函数也是很长很长,主要就是实现u-boot的命令行的功能以及实现延时自动boot功能等。
void main_loop (void)
{
#ifndef CONFIG_SYS_HUSH_PARSER
static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
int len;
#ifdef CONFIG_BOOT_RETRY_TIME
int rc = 1;
#endif
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)
ulong bmp = 0; /* default bitmap */
extern int trab_vfd (ulong bitmap);
#ifdef CONFIG_MODEM_SUPPORT
if (do_mdm_init)
bmp = 1; /* alternate bitmap */
#endif
trab_vfd (bmp);
#endif /* CONFIG_VFD && VFD_TEST_LOGO */
#ifdef CONFIG_BOOTCOUNT_LIMIT
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 */
......
好,至此u-boot以及启动到了该去的主循环中,接下来可以进行linux内核的启动工作或者接收来自用户的命令并处理这些命令,引用网上一张图片来大致解释一下u-boot的执行过程: