U-Boot启动过程[转]

来源:互联网 发布:笔记本摄像头监控软件 编辑:程序博客网 时间:2024/05/29 14:09

U-Boot启动过程

尽管有了调试跟踪手段,甚至也可以通过串口打印信息了,但是不一定能够判断出错原因。如果能够充分理解代码的启动流程,那么对准确地解决和分析问题很有帮助。
开发板上电后,执行U-Boot的第一条指令,然后顺序执行U-Boot启动函数。函数调用顺序如图6.3所示。
看一下board/smsk2410/u-boot.lds这个链接脚本,可以知道目标程序的各部分链接顺序。第一个要链接的是 cpu/arm920t/start.o,那么U-Boot的入口指令一定位于这个程序中。下面详细分析一下程序跳转和函数的调用关系以及函数实现。
1.cpu/arm920t/start.S
这个汇编程序是U-Boot的入口程序,开头就是复位向量的代码。
_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      //中断向量
 /* the actual reset code  */
reset:          //复位启动子程序
       /* 设置CPU为SVC32模式 */
       mrs   r0,cpsr
       bic   r0,r0,#0x1f
       orr   r0,r0,#0xd3
       msr   cpsr,r0
/* 关闭看门狗 */
 
/* 这些初始化代码在系统重起的时候执行,运行时热复位从RAM中启动不执行 */
#ifdef CONFIG_INIT_CRITICAL
       bl    cpu_init_crit
#endif
 
relocate:                       /* 把U-Boot重新定位到RAM */
       adr   r0, _start          /* r0是代码的当前位置 */
       ldr   r1, _TEXT_BASE      /* 测试判断是从Flash启动,还是RAM */
       cmp     r0, r1          /* 比较r0和r1,调试的时候不要执行重定位 */
       beq     stack_setup    /* 如果r0等于r1,跳过重定位代码 */
       /* 准备重新定位代码 */
       ldr   r2, _armboot_start
       ldr   r3, _bss_start
       sub   r2, r3, r2          /* r2 得到armboot的大小   */
       add   r2, r0, r2          /* r2 得到要复制代码的末尾地址 */
copy_loop: /* 重新定位代码 */
       ldmia r0!, {r3-r10}   /*从源地址[r0]复制 */
       stmia r1!, {r3-r10}   /* 复制到目的地址[r1] */
       cmp   r0, r2          /* 复制数据块直到源数据末尾地址[r2] */
       ble   copy_loop
 
       /* 初始化堆栈等    */
stack_setup:
       ldr   r0, _TEXT_BASE              /* 上面是128 KiB重定位的u-boot */
       sub   r0, r0, #CFG_MALLOC_LEN     /* 向下是内存分配空间 */
       sub   r0, r0, #CFG_GBL_DATA_SIZE /* 然后是bdinfo结构体地址空间  */
#ifdef CONFIG_USE_IRQ
       sub   r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
       sub   sp, r0, #12     /* 为abort-stack预留3个字 */
clear_bss:
       ldr   r0, _bss_start      /* 找到bss段起始地址 */
       ldr   r1, _bss_end        /*  bss段末尾地址   */
       mov   r2, #0x00000000     /* 清零 */
clbss_l:str r2, [r0]        /* bss段地址空间清零循环...  */
       add   r0, r0, #4
       cmp   r0, r1
       bne   clbss_l
       /* 跳转到start_armboot函数入口,_start_armboot字保存函数入口指针 */
       ldr   pc, _start_armboot
_start_armboot: .word start_armboot     //start_armboot函数在lib_arm/board.c中实现
/* 关键的初始化子程序 */
cpu_init_crit:
……  //初始化CACHE,关闭MMU等操作指令
       /* 初始化RAM时钟。
       * 因为内存时钟是依赖开发板硬件的,所以在board的相应目录下可以找到memsetup.S文件。
       */
       mov   ip, lr
       bl    memsetup        //memsetup子程序在board/smdk2410/memsetup.S中实现
       mov   lr, ip
       mov   pc, lr
 
2.lib_arm/board.c
start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。
 
 
void start_armboot (void)
{
       DECLARE_GLOBAL_DATA_PTR;
       ulong size;
       init_fnc_t **init_fnc_ptr;
       char *s;
       /* Pointer is writable since we allocated a register for it */
       gd = (gd_t*)(_armboot_start - CFG_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));
       monitor_flash_len = _bss_start - _armboot_start;
       /* 顺序执行init_sequence数组中的初始化函数 */
       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
              if ((*init_fnc_ptr)() != 0) {
                      hang ();
              }
       }
       /*配置可用的Flash */
       size = flash_init ();
       display_flash_config (size);
       /* _armboot_start 在u-boot.lds链接脚本中定义 */
       mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
       /* 配置环境变量,重新定位 */
       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) {
               load_addr = simple_strtoul (s, NULL, 16);
       }
       /* main_loop()总是试图自动启动,循环不断执行 */
       for (;;) {
               main_loop ();      /* 主循环函数处理执行用户命令 -- common/main.c */
       }
       /* NOTREACHED - no way out of command loop except booting */
}
 
3.init_sequence[]
init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。
 
init_fnc_t *init_sequence[] = {
       cpu_init,             /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */
       board_init,           /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */
       interrupt_init,       /* 初始化例外处理 -- cpu/arm920t/s3c24x0/interrupt.c */
       env_init,             /* 初始化环境变量 -- common/cmd_flash.c */
       init_baudrate,        /* 初始化波特率设置 -- lib_arm/board.c */
       serial_init,          /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */
       console_init_f,       /* 控制台初始化阶段1 -- common/console.c */
       display_banner,       /* 打印u-boot信息 -- lib_arm/board.c */
       dram_init,            /* 配置可用的RAM -- board/smdk2410/smdk2410.c */
       display_dram_config,  /* 显示RAM的配置大小 -- lib_arm/board.c */
       NULL,
};
 

U-BOOT start_armboot浅析

start_armboot浅析
ARM920t架构的CPU在完成基本的初始化后(ARM汇编代码),就进入它的C语言代码,而C语言代码的入口就是start_armboot, start_armboot在lib_arm/board.c中。start_armboot将完成以下工作。
1.全局数据结构的初始化
比如gd_t结构的初始化:
251         gd = (gd_t*)(_armboot_start – CFG_MALLOC_LEN – sizeof(gd_t));
_armboot_start是u-boot在RAM中的开始地址(对于u-boot最终搬移到RAM中运行的情况),CFG_MALLOC_LEN在include/configs/<board name>.h中定义。
 
bd_t结构的初始化:
272         gd->bd = (bd_t*)((char*)gd-sizeof(bd_t));
u-boot把bd_t结构紧接着gd_t结构存放。
 
内存分配的初始化
316         mem_malloc_init(_armboot_start-CFG_MALLOC_LEN);
经过以上的初始化后,u-boot在内存中的布局为(在底端为低地址)
-----------------------------
BSS
-----------------------------
U-BOOT TEXT/DATA
-----------------------------
CFG_MALLOC_LEN
-----------------------------
gd_t
-----------------------------
bd_t
-----------------------------
STACK
-----------------------------
U-Boot启动分析C语言部分(一)


本文还是以u-boot-1.1.4为例,以make smdk2410_config命令配置源代码之后进行分析。
对于C语言部分代码的调用出现在cpu/arm920t/start.S的第223行:
[code]
        ldr pc, _start_armboot
 
_start_armboot: .word start_armboot
[/code]
这里的start_armboot就是lib_arm/board.c中第207行的start_armboot()函数,由此U-Boot开始执行C语言部分的代码。
start_armboot()函数一开始首先是一个宏调用:
[code]
DECLARE_GLOBAL_DATA_PTR;
[/code]
这个宏的定义在include/asm-arm/global_data.h文件的第64行:
[code]
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
[/code]
大概的意思是声明一个指向gd_t结构体变量的指针gd,并固定使用寄存器r8来存放该指针。而对gd_t结构体的定义从上面的第36行开始:
[code]
typedef struct global_data {
    bd_t  *bd;
    unsigned long flags;
    unsigned long baudrate;
    unsigned long have_console; /* serial_init() was called */
    unsigned long reloc_off; /* Relocation Offset */
    unsigned long env_addr; /* Address  of Environment struct */
    unsigned long env_valid; /* Checksum of Environment valid? */
    unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
    unsigned char vfd_type; /* display type */
#endif
#if 0
    unsigned long cpu_clk; /* CPU clock in Hz!  */
    unsigned long bus_clk;
    unsigned long ram_size; /* RAM size */
    unsigned long reset_status; /* reset status register at boot */
#endif
    void  **jt;  /* jump table */
} gd_t;
[/code]
在lib_arm/board.c文件中之所以能够直接使用这个宏和gd_t结构体是因为包含了include/common.h头文件,在common.h的第115行包含了include/asm-arm/global_data.h头文件。
继续来看start_armboot()函数的执行。lib_arm/board.c第229行开始的一个for循环:
[code]
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
    if ((*init_fnc_ptr)() != 0) {
        hang ();
    }
}
[/code]
这里的init_sequence是定义在上面第190行处的一个指针数组:
[code]
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,
};
[/code]
数组类型init_fnc_t则是一个定义在第188行的函数指针类型:
[code]
typedef int (init_fnc_t) (void);
[/code]
由此可知,init_sequence[]数组当中的所有元素都是函数指针了,而这个for循环的作用就是遍历这个数组的所有元素,然后用“(*init_fnc_ptr)()”就依次调用了这些函数来进行初始化的工作

2.调用通用初始化函数
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
              if ((*init_fnc_ptr)() != 0) {
                     hang ();
              }
       }
init_sequence[]是init_fnc_t函数指针数组,这个数组包含了众多初始化函数,比如cpu_init,board_init等。
 
3.初始化具体设备
这一部分包括对Flash,LCD,网络的初始化等,例如
318 #if (CONFIG_COMMANDS & CFG_CMD_NAND)
       puts ("NAND: ");
       nand_init();            /* go init the NAND */
#endif
 
367 devices_init();
 
386 #ifdef CONFIG_DRIVER_CS8900
       cs8900_get_enetaddr (gd->bd->bi_enetaddr);
#endif


接上回,我们依次来看看init_sequence[]数组当中的各个元素。
 
首先是cpu_init()函数,定义于lib_arm/arm920t/cpu.c第88行。
 
接下来的board_init()函数定义于board/smdk2410/smdk2410.c第68行:
原创粉丝点击