认识u-boot七、U-boot源码start.S详细分析

时间:2021-05-05 16:45:30

转载自:http://blog.chinaunix.net/uid-21714580-id-145312.html 


_start是整个u-boot程序的入口点,即链接后,该处是整个程序的第一条指令。如果从flash启动,就是0x0,如果从SDRAM中这姓,则是TEXT_BASE=0x33F80000。

    程序的入口点是由链接脚本所指定,比如对于smdk2410的板子(下面都以smdk241为例),脚本文件位于board\smdk2140\u-boot.lds。在该脚本文件中:ENTRY(_start) 即指定程序的入口地址。

    globl _start 定义一个外部可以引用的变量,比如说,在其它源代码文档中,就可以直指引用_start这个变量。如int entry=_start; 那此处entry值将是多少呢?因为_start相当于一个变量,entry的值就是_start处存储的值,即 b reset机器码值。关于globl定义的变量得注意的地方,后面还有记录。

  1. #include <common.h> //位于\include目录下是一个包含其他头文件的头文件
  2. #include <config.h> //位于\include\linux目录下

  3. .globl _start

    下面这段代码处理中断向量表。关于balignal 16,0xdeadbeef的网上资料:.align伪操作用于表示对齐方式:通过添加填充字节使当前位置满足一定的对齐方式。.balign的作用同.align。.align {alignment} {,fill} {,max}其中:alignment用于指定对齐方式,可能的取值为2的次幂,缺省为4。fill是填充内容,缺省用0填充。max是填充字节数最大值,如果填充字节数超过max,就不进行对齐.例如:.align 4, 指定对齐方式为字对齐deadbeef一般用来表示没用的或是已经被释放掉的内存。

  1. _start:    b start_code
  2.     ldr    pc, _undefined_instruction     // ldr语句的意思是将第二个操作数指向的地址数据传给PC
  3.     ldr    pc, _software_interrupt        // .word 为定义一个4字节的空间undefined_instruction 
  4.                                           //为地址, 即后面标号所对的偏移地址数据
  5.     ldr    pc, _prefetch_abort
  6.     ldr    pc, _data_abort
  7.     ldr    pc, _not_used
  8.     ldr    pc, _irq
  9.     ldr    pc, _fiq

  10. _undefined_instruction:    .word undefined_instruction
  11. _software_interrupt:    .word software_interrupt
  12. _prefetch_abort:    .word prefetch_abort
  13. _data_abort:        .word data_abort
  14. _not_used:        .word not_used
  15. _irq:            .word irq
  16. _fiq:            .word fiq

  17.     .balignl 16,0xdeadbeef            

    以下这几个由.word伪操作符定义变量的作用及其取值。

    _TEXT_BASE:此处定义一汇编语言标签,更好的理解就是:告诉编译器,为_TEXT_BASE分配存储空间,该空间的名字就叫_TEXT_BASE,该空间中存储的值就是由.word后面确定的TEXT_BASEC(即0x33F80000),相当于C语言中 long _TEXT_BASE=TEXT_BASE; TEXT_BASE定义在board\smdk2410\config.mk文件中。该值的作用是告诉链接器,本程序运行的基地址为TEXT_BASE。

    U-boot编译后,烧在FLASH的第一个块中,CPU复位上电后,PC寄存器为0x0000。怎么会跑到TEXT_BASE处执行呢?事实上,CPU上电后,从地址0x0000处执行,而U-BOOT的最起始代码,即本文件中从_start开始的代码是与地址不相关的,这段代码放在任何空间执行的结果都是一样(当然不是绝对,假设u-boot代码段是100K,则放在TEXT_BASE-80K处,搬运时就会把u-boot代码后面20K部分覆盖为最前面的20K)。

  1. _TEXT_BASE:
  2.     .word    TEXT_BASE

    定义外部可以引用的变量_armboot_start,。即相当于C long _armboot_start=&_start; _armboot_start值是多少?是TEXT_BASE,即0x33F80000!等价的那条C语句,取的是_start变量地址,而不是_start本身。

    在C语言中,定义一变量 int x=100;就是告诉编译器。给我一个int大小的存储空间,该空间存储的值就是100,这个空间在哪呢?即空间地址是多少呢?我们可以通过&x知道。在汇编语言中,理解上有点不一样。上面三行语句:

    第一句,告诉编译器,向外面输出变量_armboot_start;

    第二句,_armboot_start变量在此处,到底在哪,要到链接时才能确定,凡正现在知道有这么一个变量了。

    第三句,_armboot_start变量空间放的数据为_start标签的值。这点与C语言的理解有点不一样了。此处引用的是_start标签对应处的地址。在汇编语言中,标签代表的就是那行所在的地址。图1是从u-boot编译后生成的u-boot.map截图的。从此文件中知道,_armboot_start这个变量地址为0x33f80044,

  1. .globl _armboot_start
  2. _armboot_start:
  3.     .word _start

    下面三句,特别要备注的是__bss_start这个符号,它的值为多少?该值定义在board\smdk2410\u-boot.lds文件

  1. . = ALIGN(4);
  2.     __bss_start = .;
  3.     .bss : { *(.bss) }
  4.     _end = .

   上面的__bss_start=.; 表示__bss_start值就是当前位置的值。当前位置是多少呢?从下面一句.bss:{*(.bss)}知道。紧接该位置后面马上就是放bss段数据了。所以,当然就是bss段的起始地址。_end就是bss段的结束地址。参考:http://blog.chinaunix.net/u1/58780/showart_462971.html
    bss是这个链接脚本的最后一个段。start.S就是以这个段的起始地址来计算要搬运u-boot大小的代码的。即,这个段前面的所有数据都将被搬到TEXT_BASE处。然后跑到start_armboot处,即C语言的入口代码。__bss_start这个值是多少? Smdk2410我编译后值是0x33f96f20。可以从图2的map文件中查到,bss段从0x33f96f20开始分配的。再次验证一下0x33f96f20就是__bss_start值,我们可以从图1处可以看到有个变量_bss_start该变量就是由下面三条语句所定义,_bss_start处保存的值就是__bss_start:
用UltraEdit打开生成的u-boot.bin,定位到文件偏移0x48(0x48由_bss_start所在地址0x33f80048-TEXT_BASE得到),如图3,此处值确实是20 6F F9 33(注意大小端)

  1. .globl _bss_start
  2. _bss_start:
  3.     .word __bss_start

  4. .globl _bss_end
  5. _bss_end:
  6.     .word _end

  7. #ifdef CONFIG_USE_IRQ
  8. /* IRQ stack memory (calculated at run-time) */
  9. .globl IRQ_STACK_START
  10. IRQ_STACK_START:
  11.     .word    0x0badc0de

  12. /* IRQ stack memory (calculated at run-time) */
  13. .globl FIQ_STACK_START
  14. FIQ_STACK_START:
  15.     .word 0x0badc0de
  16. #endif

    代码从这里开始。下面几行进入SVC模式,通过装入CPSR到R0,修改相应位后再装入到CPSR

  1. start_code:
  2.     /*
  3.      * set the cpu to SVC32 mode
  4.      */
  5.     mrs    r0,cpsr
  6.     bic    r0,r0,#0x1f
  7.     orr    r0,r0,#0xd3
  8.     msr    cpsr,r0

  9. /*
  10.  *以下这些都是为AT91RM9200写的
  11.  */
  12.     bl coloured_LED_init
  13.     bl red_LED_on

  14. #if    defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
  15.     /*
  16.      * relocate exception table
  17.      */
  18.     ldr    r0, =_start
  19.     ldr    r1, =0x0
  20.     mov    r2, #16
  21. copyex:
  22.     subs    r2, r2, #1
  23.     ldr    r3, [r0], #4
  24.     str    r3, [r1], #4
  25.     bne    copyex
  26. #endif

  27. #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)

  28. /* 
  29.  *关闭看门狗
  30.  */

  31. # if defined(CONFIG_S3C2400)
  32. # define pWTCON        0x15300000
  33. # define INTMSK        0x14400008    /* Interupt-Controller base addresses */
  34. # define CLKDIVN    0x14800014    /* clock divisor register */
  35. #else
  36. # define pWTCON        0x53000000
  37. # define INTMSK        0x4A000008    /* Interupt-Controller base addresses */
  38. # define INTSUBMSK    0x4A00001C
  39. # define CLKDIVN    0x4C000014    /* clock divisor register */
  40. # endif

  41.     ldr r0, =pWTCON
  42.     mov r1, #0x0
  43.     str r1, [r0]

  44.     /*
  45.      * 设置中断屏蔽寄存器相应位,关闭所有的中断
  46.      */
  47.     mov    r1, #0xffffffff
  48.     ldr    r0, =INTMSK
  49.     str    r1, [r0]
  50. # if defined(CONFIG_S3C2410)
  51.     ldr    r1, =0x3ff
  52.     ldr    r0, =INTSUBMSK
  53.     str    r1, [r0]
  54. # endif

  55.     /*
  56.      *通过设置CLKDIVN寄存器,时钟分频比为1:2:4默认的FCLK为120MHz
  57.      */     
  58.     ldr    r0, =CLKDIVN
  59.     mov    r1, #3
  60.     str    r1, [r0]
  61. #endif    /* CONFIG_S3C2400 || CONFIG_S3C2410 */

    跳入cpu_init_crit ,这是一个系统初始化函数,他还会调用board/*/lowlevel_init.S中的lowlevel_init函数。主要是对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。下面的代码重定向就依赖它。

  1. #ifndef CONFIG_SKIP_LOWLEVEL_INIT
  2.     bl    cpu_init_crit
  3. #endif

    代码重定向,它首先检测自己是否已经在内存中:如果是直接跳到下面的堆栈初始化代码stack_setup。如果不是就将自己从Nor Flash中拷贝到内存中(到TEXT_BASE地址)

    adr r0,_start这条指令网上讲得也很多,翻译过来就是 add r0,r0,[PC+#offset], 就是把通过一个地址来知道 _start 处的地址,注意是地址,即 TEXT_BASE=0x33F80000, 这步在链接的时候就已经确定了,或者你不用管那么多,你知道链接完成之后,这条指令相当于 mov r0,0x33F80000(sdram) 或者 mov r0,0x0(flash) 就行了。
    ldr r1,_TEXT_BASE 注意,这里的 ldr 不是伪指令,伪指令表示时, ldr r1,=_TEXT_BASE这两个的区别在于,伪指令是直接把 _TEXT_BASE 写入到 r1 中,这里 _TEXT_BASE 就代表一个地址,而 ldr r1,_TEXT_BASE, 是把 _TEXT_BASE 中存放的内容,也就是 TEXT_BASE=0x33F80000 写入到了 r1.
    ldr    r2, _armboot_start 结合上面讲的,应该知道,这条语句实际上是将 _armboot_start 中的内容,也就是 _start 的地址写入到了 r2 中,而非网上很多人问的是 _armboot_start 的地址。
    ldr    r3, _bss_start这跟上面一样分析了,定义见 cpu/arm920t/start.S

  1. .globl _bss_start
  2. _bss_start:
  3.   .word __bss_start

    下面这两条语句也就好理解了:
    sub       r2, r3, r2         // r2 <- armboot 大小 
    add r2, r0, r2               // r2 <- 代码结束地址  

    到底 armboot 的大小都包含了哪些东西,结合 u-boot.lds ,见下图:

认识u-boot七、U-boot源码start.S详细分析

    那为什么TEXT_BASE的值是0x33F80000呢?先看一个SDRAM的内存映射图,结合上面的u-boot.lds

0x33F80000
0x30000000
0x33ffffff
映射前
映射后

bss 段、 u-boot cmd 段、 .data 段 .rodata

及 .text 段及中断向量表

malloc 区域,见 start.S  sub r0,r0, #CFG_MALLOC_LEN

全局变量,见 start.S sub  r0, r0, #CFG_GBL_DATA_SIZE

IRQ:sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

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

 

bss 段

u-boot cmd 段

.data 数据段

.rodata 只读数据段,

入口 20 字节中断向量表 .text(start.o 及 *(.text))

    在S3C2440 中,查看 datasheet , 64M SDRAM 地址空间即为 0x30000000 到 0x33ffffff ,在 bank6 中,而 flash 映射地址为 0x0 开始。
    TEXT_BASE=0x33F80000 即为程序加载起始地址,可以使用的空间大小即为 0x33F80000 到 0x33FFFFFF 共 512K ,如果你 u-boot 包含的功能太多,觉得不够用,你可以把 0x33F80000 调小一点,即和往低地址移一些,移的过程中注意 memory page 对齐就行了,一般是 4KB.

  1. #ifndef CONFIG_SKIP_RELOCATE_UBOOT
  2. relocate:                /* relocate U-Boot to RAM     */
  3.     adr    r0, _start        /* r0 <- current position of code */
  4.     ldr    r1, _TEXT_BASE        /* test if we run from flash or RAM */
  5.     cmp r0, r1 /* don't reloc during debug */
  6.     beq stack_setup

  7.     ldr    r2, _armboot_start
  8.     ldr    r3, _bss_start
  9.     sub    r2, r3, r2        /* r2 <- size of armboot */
  10.     add    r2, r0, r2        /* r2 <- source end address */

    下面几行将r0地址处的代码复制到r1处,即从0x0000搬到0x33f80000(假设CPU复位从0x0000执行的)。

  1. copy_loop:
  2.     ldmia     {r3-r10}        /* copy from source address [r0] */
  3.     stmia     {r3-r10}        /* copy to target address [r1] */
  4.     cmp    r0, r2            /* until source end addreee [r2] */
  5.     ble    copy_loop
  6. #endif    /* CONFIG_SKIP_RELOCATE_UBOOT */

    堆栈初始化代码(为第二阶段的C语言做准备)

  1. stack_setup:
  2.     ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot */
  3.     sub    r0, r0, #CONFIG_SYS_MALLOC_LEN    /* malloc area */
  4.     sub    r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
  5. #ifdef CONFIG_USE_IRQ
  6.     sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
  7. #endif
  8.     sub    sp, r0, #12        /* leave 3 words for abort-stack */

    对BSS段清零(为第二阶段的C语言做准备)BSS段(bss segment)通常是用来存放程序中未初始化的全
局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。在编译时,编译器已经为他们分配好了空间,只不过他们的值为0,为了节省空间,在bin或ELF文件中不占空间。
    编译器会计算出_bss_start和_bss_end的值,不是定义的

  1. clear_bss:
  2.     ldr    r0, _bss_start        /* find start of bss segment */
  3.     ldr    r1, _bss_end        /* stop here */
  4.     mov    r2, #0x00000000        /* clear */

  5. clbss_l:str    r2, [r0]        /* clear loop... */
  6.     add    r0, r0, #4
  7.     cmp    r0, r1
  8.     ble    clbss_l

    跳入第二阶段的C语言代码入口_start_armboot (已经被重定向到内存)

  1. ldr    pc, _start_armboot

  2. _start_armboot:    .word start_armboot

    系统初始化函数,初始化重要的寄存器,初始化内存时序,*他还会调用board/*/lowlevel_init.S中的lowlevel_init函数。主要是对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。下面的代码重定向就依赖它。操作CP15协处理器

  1. #ifndef CONFIG_SKIP_LOWLEVEL_INIT
  2. cpu_init_crit:
  3.     /*
  4.      * flush v4 I/D caches
  5.      */
  6.     mov    r0, #0
  7.     mcr    p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */
  8.     mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB */

  9.     /*
  10.      * disable MMU stuff and caches
  11.      */
  12.     mrc    p15, 0, r0, c1, c0, 0
  13.     bic    r0, r0, #0x00002300    @ clear bits 13, 9:(--V- --RS)
  14.     bic    r0, r0, #0x00000087    @ clear bits 7, 2:(B--- -CAM)
  15.     orr    r0, r0, #0x00000002    @ set bit 2 (A) Align
  16.     orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache
  17.     mcr    p15, 0, r0, c1, c0, 0

    调用board/*/lowlevel_init.S中的lowlevel_init函数,对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。

  1. mov    ip, lr

  2.     bl    lowlevel_init

  3.     mov    lr, ip
  4.     mov    pc, lr
  5. #endif /* CONFIG_SKIP_LOWLEVEL_INIT */

    后面的代码略,主要是中断相关代码,但是U-boot基本不使用中断所以暂且略过

  1. @
  2. @ IRQ stack frame.
  3. @
  4. #define S_FRAME_SIZE    72

  5. #define S_OLD_R0    68
  6. #define S_PSR        64
  7. #define S_PC        60
  8. #define S_LR        56
  9. #define S_SP        52

  10. #define S_IP        48
  11. #define S_FP        44
  12. #define S_R10        40
  13. #define S_R9        36
  14. #define S_R8        32
  15. #define S_R7        28
  16. #define S_R6        24
  17. #define S_R5        20
  18. #define S_R4        16
  19. #define S_R3        12
  20. #define S_R2        8
  21. #define S_R1        4
  22. #define S_R0        0

  23. #define MODE_SVC 0x13
  24. #define I_BIT     0x80

  25. /*
  26.  * use bad_save_user_regs for abort/prefetch/undef/swi ...
  27.  * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
  28.  */

  29.     .macro    bad_save_user_regs
  30.     sub    sp, sp, #S_FRAME_SIZE
  31.     stmia    sp, {r0 - r12}            @ Calling r0-r12
  32.     ldr    r2, _armboot_start
  33.     sub    r2, r2, #(CONFIG_STACKSIZE)
  34.     sub    r2, r2, #(CONFIG_SYS_MALLOC_LEN)
  35.     sub    r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
  36.     ldmia    r2, {r2 - r3}            @ get pc, cpsr
  37.     add    r0, sp, #S_FRAME_SIZE        @ restore sp_SVC

  38.     add    r5, sp, #S_SP
  39.     mov    r1, lr
  40.     stmia    r5, {r0 - r3}            @ save sp_SVC, lr_SVC, pc, cpsr
  41.     mov    r0, sp
  42.     .endm

  43.     .macro    irq_save_user_regs
  44.     sub    sp, sp, #S_FRAME_SIZE
  45.     stmia    sp, {r0 - r12}            @ Calling r0-r12
  46.     add r7, sp, #S_PC
  47.     stmdb r7, {sp, lr}^ @ Calling SP, LR
  48.     str lr, [r7, #0] @ Save calling PC
  49.     mrs r6, spsr
  50.     str r6, [r7, #4] @ Save CPSR
  51.     str r0, [r7, #8] @ Save OLD_R0
  52.     mov    r0, sp
  53.     .endm

  54.     .macro    irq_restore_user_regs
  55.     ldmia    sp, {r0 - lr}^            @ Calling r0 - lr
  56.     mov    r0, r0
  57.     ldr    lr, [sp, #S_PC]            @ Get PC
  58.     add    sp, sp, #S_FRAME_SIZE
  59.     subs    pc, lr, #4            @ return & move spsr_svc into cpsr
  60.     .endm

  61.     .macro get_bad_stack
  62.     ldr    r13, _armboot_start        @ setup our mode stack
  63.     sub    r13, r13, #(CONFIG_STACKSIZE)
  64.     sub    r13, r13, #(CONFIG_SYS_MALLOC_LEN)
  65.     sub    r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack

  66.     str    lr, [r13]            @ save caller lr / spsr
  67.     mrs    lr, spsr
  68.     str lr, [r13, #4]

  69.     mov    r13, #MODE_SVC            @ prepare SVC-Mode
  70.     @ msr    spsr_c, r13
  71.     msr    spsr, r13
  72.     mov    lr, pc
  73.     movs    pc, lr
  74.     .endm

  75.     .macro get_irq_stack            @ setup IRQ stack
  76.     ldr    sp, IRQ_STACK_START
  77.     .endm

  78.     .macro get_fiq_stack            @ setup FIQ stack
  79.     ldr    sp, FIQ_STACK_START
  80.     .endm

  81. /*
  82.  * exception handlers
  83.  */
  84.     .align 5
  85. undefined_instruction:
  86.     get_bad_stack
  87.     bad_save_user_regs
  88.     bl    do_undefined_instruction

  89.     .align    5
  90. software_interrupt:
  91.     get_bad_stack
  92.     bad_save_user_regs
  93.     bl    do_software_interrupt

  94.     .align    5
  95. prefetch_abort:
  96.     get_bad_stack
  97.     bad_save_user_regs
  98.     bl    do_prefetch_abort

  99.     .align    5
  100. data_abort:
  101.     get_bad_stack
  102.     bad_save_user_regs
  103.     bl    do_data_abort

  104.     .align    5
  105. not_used:
  106.     get_bad_stack
  107.     bad_save_user_regs
  108.     bl    do_not_used

  109. #ifdef CONFIG_USE_IRQ

  110.     .align    5
  111. irq:
  112.     get_irq_stack
  113.     irq_save_user_regs
  114.     bl    do_irq
  115.     irq_restore_user_regs

  116.     .align    5
  117. fiq:
  118.     get_fiq_stack
  119.     /* someone ought to write a more effiction fiq_save_user_regs */
  120.     irq_save_user_regs
  121.     bl    do_fiq
  122.     irq_restore_user_regs

  123. #else

  124.     .align    5
  125. irq:
  126.     get_bad_stack
  127.     bad_save_user_regs
  128.     bl    do_irq

  129.     .align    5
  130. fiq:
  131.     get_bad_stack
  132.     bad_save_user_regs
  133.     bl    do_fiq

  134. #endif