uboot 启动流程分析

来源:互联网 发布:御彩轩2015计划软件 编辑:程序博客网 时间:2024/05/01 03:01

CPU:      arm920t

在这里先感谢不知名的同仁们的帮助,参考了一些人的博客,自己整理了下,最近也在做这方面的工作,总结下!!

 

1     关于加载地址和运行地址

加载地址就是代码在FLASH上存储的地址,也叫加载域,或者存储地址,或者LMA(load address),bin文件的存储地址运行地址就是代码在RAM上运行时地址,也叫运行时域,或者VMA(virtual address),若在NOR flash中运行,这个运行应该和加载地址是一样的.运行地址在连接文件u-boot.lds中指定,这个文件其实同时指定了加载地址,那就是默认和运行地址一样,除非用AT命令显式指定加载地址另外运行地址也可以通过Ttext命令行指定,这就是为什么在u-boot.lds中指定的运行地址是0x0,而实际运行是偏移了0x33f80000,因为在makefile中通过:

LDFLAGS += -Bstatic -T $(LDSCRIPT)-Ttext $(TEXT_BASE)

翻译下就是arm-linux-ld–Ttext _TEXT_BASE,这句指定了运行地址。

 

2   u-boot系统启动流程

大多数bootloader都分为stage1stage2两部分,u-boot也不例外。依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在stage1且可以用汇编语言来实现,而stage2则通常用C语言来实现,这样可以实现复杂的功能,而且有更好的可读性和移植性。
2.1 
Stage1  start.S代码结构 
u-boot
stage1代码通常放在start.S文件中,他用汇编语言写成,其主要代码部分如下:
1)定义入口。由于一个可执行的Image必须有一个入口点,并且只能有一个全局入口,通常这个入口放在ROMFlash)的0x0地址,因此,必须通知编译器以使其知道这个入口,该工作可通过修改连接器脚本来完成。
2)设置异常向量(Exception Vector)。
3)设置CPU的速度、时钟频率及终端控制寄存器。
4)初始化内存控制器。
5)将ROM中的程序复制到RAM中。
6)初始化堆栈。
7)转到RAM中执行,该工作可使用指令ldr pc来完成。

 

还是由具体的形式说明下:

Reset:

              --------设置处理器模式为super

              --------关闭watchdog

              --------屏蔽所有中断

bl     cpu_init_crit

--------清除I/D cache 关闭MMU

--------lowlevel_init,配置SDRAM访问时序

 

Relocate

              --------复制u-boot RAM

stack_setup

              --------配置堆栈段

clear_bss

              --------清楚bss

ldr   pc,_start_armboot---跳转到RAM去执行(c代码中执行也就是第二阶段)

interrupthanding

 

exceptionhanders

 

2.2  Stage2  C语言代码部分
lib_arm/board.c中的start arm bootC语言开始的函数也是整个启动代码中C语言的主函数,同时还是整个u-bootarmboot)的主函数,该函数只要完成如下操作:
1)调用一系列的初始化函数。
2)初始化Flash设备。
3)初始化系统内存分配函数。
4)如果目标系统拥有NAND设备,则初始化NAND设备。
5)如果目标系统有显示设备,则初始化该类设备。
6)初始化相关网络设备,填写IPMAC地址等。
7)进去命令循环(即整个boot的工作循环),接受用户从串口输入的命令,然后进行相应的工作。

 

3     U-Boot.lds 代码分析

 

       /*******************************************************/

OUTPUT_FORMAT("elf32-littlearm","elf32-littlearm", "elf32-littlearm"")
    ;
指定输出可执行文件是elf格式,32ARM指令,小端
OUTPUT_ARCH(arm)
    ;
指定输出可执行文件的平台为ARM
ENTRY(_start)
    ;
指定输出可执行文件的起始代码段为_start.
SECTIONS
{
        . = 0x00000000 ;
指明目标代码的起始地址从0x0位置开始,"."代表的是当前位置
        . = ALIGN(4)   ;
代码以4字节对齐
        .text : ;
指定代码段
        {
          cpu/arm920t/start.o(.text) ;
代码的第一个代码部分,指明start.s是入口程序代码,被放到代码段的开头
          *(.text) ;
其它代码部分
        }
        . = ALIGN(4) 
        .rodata : { *(.rodata) } ;
指定只读数据段,RO
        . = ALIGN(4);
        .data : { *(.data)}     ;
指定读/写数据段,RW
        . = ALIGN(4);
        .got : { *(.got)}       ;
指定got, got段式是uboot自定义的一个段, 非标准段
        __u_boot_cmd_start =.   ;
__u_boot_cmd_start赋值为当前位置, 即起始位置
        .u_boot_cmd : { *(.u_boot_cmd)} ;
指定u_boot_cmd, uboot把所有的uboot命令放在该段.
        __u_boot_cmd_end = .;
__u_boot_cmd_end赋值为当前位置,即结束位置
        . = ALIGN(4);
        __bss_start = .;
__bss_start赋值为当前位置,bss段的开始位置
        .bss : { *(.bss) };
指定bss
        _end = .;
_end赋值为当前位置,bss段的结束位置
}

/****************************************/

       从这里可以看出start.s是程序的入口点

4     start.S 代码分析

//global声明一个符号可被其它文件引用,相当于声明了一个全局变量,.globl.global相同。
//
该部分为处理器的异常处理向量表。地址范围为0x0000 0000 ~ 0x0000 0020,刚好8条指令。

.globl _start
_start:    b      reset     /*
跳转到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


// .word
伪操作用于分配一段字内存单元(分配的单元都是字对齐的),并用伪操作中的expr初始化。.long.int作用与之//相同。
_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

/*
* Startup Code (reset vector)
*
* do important init only if we don't start from RAM!
* - relocate armboot to ram
* - setup stack
* - jump to second stage
*/

// TEXT_BASE
在开发板相关的目录中的config.mk文件中定义, 它定义了
//
代码在运行时所在的地址, 那么_TEXT_BASE中保存了这个地址
_TEXT_BASE:
    .word    TEXT_BASE

//
声明 _armboot_start 并用 _start 来进行初始化,在board/u-boot.lds中定义。
.globl _armboot_start
_armboot_start:
    .word _start

/*
* These are defined in the board-specific linker script.
*/

//
声明_bss_start并用__bss_start来初始化,其中__bss_start定义在与板相关的u-boot.lds中。
// _bss_start
保存的是__bss_start这个标号所在的地址, 这里涉及到当前代码所在
//
的地址不是编译时的地址的情况, 这里直接取得该标号对应的地址, 不受编译时
//
地址的影响. _bss_end也是同样的道理.
.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

//  MRS {<cond>}Rd,CPSR|SPSR CPSRSPSR传送到Rd
//  
使用这两条指令将状态寄存器传送到一般寄存器,只修改必要的位,再将结果传送回状态寄存器,这样可以最好地完成对CRSP或者SPSR的修改
//  MSR {<cond>} CPSR_<field>|SPSR_<field>,Rm
或者是 MSR{<cond>} CPSR_f|SPSR_f,#<32-bit immediate>
//  MRS
MSR配合使用,作为更新PSR读取--修改--写回序列的一部分
//   bic r0,r1,r2  ;r0:=r1 and not r2
//   orr ro,r1,r2  ;r0:=r1 or r2
//  
这几条指令执行完毕后,进入SVC模式,该模式主要用来处理软件中断(SWI)
reset:
    mrs    r0,cpsr        /*set the cpu to SVC32 mode        */
    bic    r0,r0,#0x1f        /*(superviser mode, M=10011)        */
    orr    r0,r0,#0xb3   /*disable irq and frq SVC mode*/
    msr    cpsr,r0

/* 关闭看门狗,S3C2410手则 */
#if defined(CONFIG_S3C2400)
# define pWTCON        0x15300000
# define INTMSK       0x14400008    /* Interupt-Controller base addresses */
# define CLKDIVN    0x14800014    /* clockdivisor register */
#elif defined(CONFIG_S3C2410)
# define pWTCON        0x53000000
# define INTMSK       0x4A000008    /* Interupt-Controller base addresses */
# define INTSUBMSK    0x4A00001C
# define CLKDIVN    0x4C000014    /* clockdivisor register */
#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
    ldr     r0, =pWTCON
    mov     r1, #0x0
    str     r1, [r0]

    /*
     *
屏蔽所有中断
     */
    mov    r1, #0xffffffff
    ldr    r0, =INTMSK
    str    r1, [r0]
# if defined(CONFIG_S3C2410)
    ldr    r1, =0x3ff
    ldr    r0, =INTSUBMSK
    str    r1, [r0]
# endif

    /*
设置FCLK:HCLK:PCLK = 1:2:4 */
    /*FCLK
默认为120 MHz ! */
    ldr    r0, =CLKDIVN
    mov    r1, #3
    str    r1, [r0]
#endif    /* CONFIG_S3C2400 || CONFIG_S3C2410 */

    /*该语句首先调用cpu_init_crit进行CPU的初始化,并把下一条指令的地址保存在LR中,以使得执行完后能够正常返回。后面会分析*/
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_crit
#endif

//调试阶段的代码是直接在RAM中运行的,而最后需要把这些代码固化到Flash中,因此U-Boot需要自己从Flash转移到
//RAM
中运行,这也是重定向的目的所在。
//
通过adr指令得到当前代码的地址信息:如果U-boot是从RAM开始运行,则从adr,r0,_start得到的地址信息为
//r0=_start=_TEXT_BASE=TEXT_BASE=0xa3000000;
如果U-bootFlash开始运行,即从处理器对应的地址运行,
//
r0=0x0000,这时将会执行copy_loop标识的那段代码了。
// _TEXT_BASE
定义在board/pxa255_idp/config.mk

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:               /* relocate U-Boot toRAM        */
    adr    r0, _start       /* r0 <- current position of code   */
    ldr    r1, _TEXT_BASE       /* test if we run from flash or RAM */
    cmp     r0,r1                 /* don't reloc 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 source address[r0]    */
    stmia    r1!, {r3-r10}       /* copy to   target address [r1]   */
    cmp    r0, r2           /* until source end addreee[r2]    */
    ble    copy_loop
#endif    /* CONFIG_SKIP_RELOCATE_UBOOT */

    /* Set up the stack                          */
stack_setup:
    ldr    r0, _TEXT_BASE       /* upper 128 KiB: relocated uboot   */
    sub    r0, r0,#CFG_MALLOC_LEN    /* mallocarea                     */
    sub    r0, r0, #CFG_GBL_DATA_SIZE /*bdinfo                       */

// 这里如果需要使用IRQ, 还有给IRQ保留堆栈空间

#ifdef CONFIG_USE_IRQ
    sub    r0, r0,#(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif

//这里是直接减去12,但是在后面board.c中的cpu_init中代码是减8再减4(如下),大家看到后面再反过来看看,会觉得很有意//
/*

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;
}

*/

   sub    sp, r0,#12        /* leave 3 words forabort-stack    */

//该部分将未初始化数据段_bss_start----_bss_end中的数据清零。

clear_bss:
    ldr    r0, _bss_start       /* find start of bsssegment        */
    ldr    r1, _bss_end       /* stophere                       */
    mov     r2, #0x00000000       /*clear                           */

clbss_l:str    r2, [r0]        /*clearloop...                   */
    add    r0, r0, #4
    cmp    r0, r1
    ble    clbss_l

#if 0
    /* try doing this stuff after the relocation */
    ldr     r0, =pWTCON
    mov     r1, #0x0
    str     r1, [r0]

    /*
     * mask all IRQs by setting all bits in the INTMR -default
     */
    mov    r1, #0xffffffff
    ldr    r0, =INTMR
    str    r1, [r0]

    /* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    ldr    r0, =CLKDIVN
    mov    r1, #3
    str    r1, [r0]
    /* END stuff after relocation */
#endif
//
通过该语句跳转到C代码执行,stage1的使命也算完成。
    ldr    pc, _start_armboot

_start_armboot:    .word start_armboot

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
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 */

    /*
     *
关闭MMU和缓存,这里用到了协处理器P15,参看手则,哈哈,这里有的很多看了
     */
    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

    /*
进入lowlevel_init,这里主要是初始化存储控制器,S3C2410的是Bank0-bank6,比如位宽等,这个要根据自己的板子来进行相应的配置,比如网卡放在第几个bank,位宽多少,SDRAM放在哪里,多大等,移植UBOOT的时候就有的学了*/
    mov    ip, lr
    bl    lowlevel_init
    mov    lr, ip
    mov    pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */

//程序后面的都是中断的处理,定义了一系列的宏,然后就是中断的处理函数和异常处理函数,这里就不在列出了。