IMX6Solo启动流程-从Uboot到kernel 上

写在前头

*.版权声明:本篇文章为原创,可随意转载,转载请注明出处,谢谢!另我创建一个QQ群82642304,欢迎加入!
*.目的:整理一下RIotBoard开发板的启动流程,对自己的所学做一个整理总结,本系列Uboot代码基于2009.08版。
*.备注:整个系列只是对我所学进行总结,记录我认为是关键的点,另我能力有限,难免出现疏漏错误,如果读者有发现请多指正,以免我误导他人!


Uboot入口

上一篇我们讲到Uboot的入口地址是定义在cpu/arm_cortexa8/start.S中的_start函数.
start.S里放的是硬件相关的汇编代码,主要是为进入C函数之前搭建环境,例如初始化CPU,初始化堆栈,初始化BSS等.
首先看下_start入口函数的定义:

.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 */

_start入口函数的第一条指令就是跳转到reset函数,所以reset函数是真正的入口地址,接下来的几条指令都是中断向量表,具体可参照ARM的体系结构.
在reset函数中我们看到了许多Uboot里的宏定义,每个宏定义都会导致Uboot执行流程的不同,如果要去判断Uboot里是否开启或者关闭这些宏定义我们需要搜索这些宏定义的位置并且判断该开发板是否开启(许多宏定义在每个开发板中都有定义),这些宏不仅给研究代码带来一些麻烦,而且还容易出错.
我一般是没去搜索这些宏定义,而且结合以下两个途径来看代码:
1. 编译完Uboot之后,会导出Uboot的变量符号、函数符号文件System.map文件,在宏所包含的代码段里面,有一些函数名可以看出跟这个宏相对应的,我们可以根据在System.map里面搜索这个函数名来判断宏是否有定义,例如:

#if defined(CONFIG_CMD_NAND)
    puts ("NAND:  ");
    nand_init();        /* go init the NAND */
#endif

我们可以看出nand_init()函数是与CONFIG_CMD_NAND宏对应的,如果没有定义CONFIG_CMD_NAND,那么System.map里面也不会找到nand_init函数.
2. 一些比较简单的函数可以直接看汇编.先将编译好的Uboot反汇编:

arm-fsl-linux-gnueabi-objdump -D u-boot > u-boot.asm

此处用于反汇编的是u-boot文件而不是u-boot.bin文件,两者的区别就是u-boot.bin是被strip之后的uboot,u-boot.bin里面没有包含函数名称,变量名称等信息.

例如此时的reset函数就可以通过直接看反汇编之后的文件:


27800670 <reset>:
27800670:   e10f0000    mrs r0, CPSR
27800674:   e3c0001f    bic r0, r0, #31
27800678:   e38000d3    orr r0, r0, #211    ; 0xd3
2780067c:   e129f000    msr CPSR_fc, r0
27800680:   eb00000d    bl  278006bc <cpu_init_crit>

27800684 <stack_setup>:
27800684:   e51f002c    ldr r0, [pc, #-44]  ; 27800660 <_end_vect>
27800688:   e2400602    sub r0, r0, #2097152    ; 0x200000
2780068c:   e2400080    sub r0, r0, #128    ; 0x80
27800690:   e240d00c    sub sp, r0, #12
27800694:   e3cdd007    bic sp, sp, #7

27800698 <clear_bss>:
27800698:   e51f0038    ldr r0, [pc, #-56]  ; 27800668 <_bss_start>
2780069c:   e51f1038    ldr r1, [pc, #-56]  ; 2780066c <_bss_end>
278006a0:   e3a02000    mov r2, #0

278006a4 <clbss_l>:
278006a4:   e5802000    str r2, [r0]
278006a8:   e1500001    cmp r0, r1
278006ac:   e2800004    add r0, r0, #4
278006b0:   1afffffb    bne 278006a4 <clbss_l>
278006b4:   e51ff004    ldr pc, [pc, #-4]   ; 278006b8 <_start_armboot>

278006b8 <_start_armboot>:
278006b8:   27802224    strcs   r2, [r0, r4, lsr #4]

稍微看下,reset函数在调用完cpu_init_crit,执行完stack_setup和clear_bss后跳转到_start_armboot,下面我们将逐个分析.


reset函数

1.进入SVC32模式之后调用cpu_init_crit函数,该函数的定义也在start.S里面,主要的功能就是初始化CPU的工作环境,在通用的CPU设置(初始化L1 I/D,关闭MMU和缓存)之后跳转到板级相关的初始化lowlevel_init函数.
lowlevel_init函数的定义在board/freescale/mx6q_riot/lowlevel_init.S,我就不展开了.
2.堆栈指针的设置:

    /* 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

在TEXT_BASE(即Uboot的加载地址)紧挨的地方申请CONFIG_SYS_MALLOC_LEN+CONFIG_SYS_GBL_DATA_SIZE长度的地址,然后sp指针指向该地址
3.清除bss

    /* Clear BSS (if any). Is below tx (watch load addr - need space) */
clear_bss:
    ldr r0, _bss_start      @ find start of bss segment
    ldr r1, _bss_end        @ stop here
    mov r2, #0x00000000     @ 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

上面的汇编语句相当于如下C语句

for(int * i = _bss_start; i < _bss_end; i += 1){
    (*i) = 0x00000000;
}

_bss_start和_bss_end的定义都是在start.S文件中,分别是

.globl _bss_start
_bss_start:
    .word __bss_start

.globl _bss_end
_bss_end:
    .word _end

而__bss_start和_end的定义是在Uboot的链接脚本u-boot.lds中:

 __bss_start = .;
 .bss : { *(.bss) }
 _end = .;

__bbs_start和_end之间的数据是所有的bss段,链接程序ld会根据这个链接脚本,将Uboot里面的所有静态变量,全局变量都放在这个段里面,并且在进入C函数之前将这些全局变量和静态变量的数据都清零.
4. 最后,程序跳转到_start_armboot函数,_start_armboot也是在start.S中定义,实际上就是跳转到start_armboot函数,定义在lib_arm/board.c.


总结

Uboot的入口函数是有一段汇编代码来做相关的初始化,为进入C搭建运行环境,在研究Uboot的代码过程中,可以结合System.map和反汇编来研读代码,这样子花在判断宏是否定义的精力上就可以少一些.


参考

暂无


版权声明:本文为baicaiaichibaicai原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。