uboot第一阶段代码分析

来源:互联网 发布:apache tomcat java8 编辑:程序博客网 时间:2024/05/23 16:51
从前一篇u-boot.lds文件分析知,整个代码段放在最前面的是start.o,而入口函数是_start,那么我们就来分析下start.S文件:
  1. /*
  2. *************************************************************************
  3. *
  4. * Jump vector table as in table 3.1 in [1]
  5. *
  6. *************************************************************************
  7. */
  8. .globl _start
  9. _start: b start_code
  10. ldr pc, _undefined_instruction
  11. ldr pc, _software_interrupt
  12. ldr pc, _prefetch_abort
  13. ldr pc, _data_abort
  14. ldr pc, _not_used
  15. ldr pc, _irq
  16. ldr pc, _fiq
  17. _undefined_instruction: .word undefined_instruction
  18. _software_interrupt: .word software_interrupt
  19. _prefetch_abort: .word prefetch_abort
  20. _data_abort: .word data_abort
  21. _not_used: .word not_used
  22. _irq: .word irq
  23. _fiq: .word fiq

  24. .balignl 16,0xdeadbeef
.global伪指令,该伪指令的含义是让global定义过的符号对连接器可见,相当于一个C语言中中的extern,
前面我们还记得u-boot.lds文件中定义的入口函数是_start,这样用.global定义_start的话,
u-boot.lds就可以找到_start函数了。

首先讲解一下arm汇编程序中,实现程序间的三种跳转方法:
1、使用跳转指令b,bl,bx,使用这一系列的指令的好处是执行速度快,只需一个指令周期即可完成跳转
,但该系列指令有一个缺点,他们都不能实现对任意地址的跳转,事实上b指令所能跳转的最大范围是当前
指令地址前后的32M,这是由于ARM指令集是32位等长的,所有的指令都必须在4字节的范围内完成,这样,
一条指令需要附带一个立即数或一个地址值作为参数值时,该立即数或地址值必然小于32位。
2、使用内存装载指令,将存储在内存的某一地址装载到程序寄存器PC中,如ldr指令。ldr指令可以实现任意
地址的跳转。
3、mov指令,mov指令常用于函数返回时的跳转。

说到这里不得不讲一讲ldr指令,ldr伪指令,adr伪指令的区别了:
ldr指令若果作为实际指令出现,表示从内存中读取数据到寄存器中;
如:ldr r1,[r0]
ldr伪指令,是将一个常量装载到寄存器中,因为ARM指令等宽指令格式的限制,不能保证所有的常数都可以通过
一条指令装载到寄存器中,程序在编译时,,如果能将ldr指令展开成一条常量装载指令,则编译器就会用该指令
代替ldr,否则编译器会首先开辟一段空间存储被装载的常数,再使用一条存储器读取指令将该常量读到寄存器中。
adr伪指令,常被称作是地址装载伪指令,与ldr类似,adr伪指令能将一个相对地址写入寄存器中。
如adr r0, .L0 其中.L0是标号。
下面这个博客还有反汇编的解释,可以看一下:
http://blog.csdn.net/denlee/archive/2008/05/31/2499542.aspx
在来谈下自己的理解,首先要明白,ldr的第二个参数前面有=号时,表示ldr是伪指令,否则表示内存访问指令,我们知道ldr伪指令是将常量装载到寄存器中,所以ldr是获得代码的绝对地址,这个绝对地址就是在生成的可执行文件里面的分布地址。
.word 伪指令可以用来在内存中分配一个4字节的空间,并可以同时对其初始化,_undefined_instruction: .word undefined_instruction 这条语句的意思就是在定义_undefined_instruction空间的同时,又向该空间赋予了undefined_instruction这一变量的值,这样,就可以直接在程序中引用_undefined_instruction了。
因为start_code跳转范围比较小所以用b,其余的范围比较大,用的ldr。
.balignl 16,0xdeadbeef是向后移动位置计数器,直到位置计数器等于16的倍数。而0xdeadbeef是用来填充位置计数器越过的地方。
  1. _TEXT_BASE:
  2. .word TEXT_BASE
  3. .globl _armboot_start
  4. _armboot_start:
  5. .word _start
  6. /*
  7. * These are defined in the board-specific linker script.
  8. */
  9. .globl _bss_start
  10. _bss_start:
  11. .word __bss_start
  12. .globl _bss_end
  13. _bss_end:
  14. .word _end
  15. #ifdef CONFIG_USE_IRQ
  16. /* IRQ stack memory (calculated at run-time) */
  17. .globl IRQ_STACK_START
  18. IRQ_STACK_START:
  19. .word 0x0badc0de
  20. /* IRQ stack memory (calculated at run-time) */
  21. .globl FIQ_STACK_START
  22. FIQ_STACK_START:
  23. .word 0x0badc0de
  24. #endif
uboot存储器映射的定义,将uboot.lds 中的值取过来。
由前面的链接过程分析知,uboot被链接到TEXT_BASE地址处,意思就是uboot运行的基地址为TEXT_BASE,即在内存的33F80000地址处开始运行。
接下来就是正式的开始代码了:
  1. /*
  2. * the actual start code
  3. */
  4. start_code:
  5. /*
  6. * set the cpu to SVC32 mode
  7. */
  8. mrs r0, cpsr
  9. bic r0, r0, #0x1f
  10. orr r0, r0, #0xd3
  11. msr cpsr, r0
  12. bl coloured_LED_init
  13. bl red_LED_on
要说明的是bl 跳转到c语言函数去了,bl指令在运行时,能够自动的保存程序的返回地址,汇编和c混合编程遵循的是AAPCS准则。设置为系统模式。
  1. #if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
  2. /*
  3. * relocate exception table
  4. */
  5. ldr r0, =_start
  6. ldr r1, =0x0
  7. mov r2, #16
  8. copyex:
  9. subs r2, r2, #1
  10. ldr r3, [r0], #4
  11. str r3, [r1], #4
  12. bne copyex
  13. #endif
AT91的不予理会
  1. #ifdef CONFIG_S3C24X0
  2. /* turn off the watchdog */
  3. # if defined(CONFIG_S3C2400)
  4. # define pWTCON 0x15300000
  5. # define INTMSK 0x14400008 /* Interupt-Controller base addresses */
  6. # define CLKDIVN 0x14800014 /* clock divisor register */
  7. #else
  8. # define pWTCON 0x53000000
  9. # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
  10. # define INTSUBMSK 0x4A00001C
  11. # define CLKDIVN 0x4C000014 /* clock divisor register */
  12. # endif
  13. ldr r0, =pWTCON
  14. mov r1, #0x0
  15. str r1, [r0]
  16. /*
  17. * mask all IRQs by setting all bits in the INTMR - default
  18. */
  19. mov r1, #0xffffffff
  20. ldr r0, =INTMSK
  21. str r1, [r0]
  22. # if defined(CONFIG_S3C2410)
  23. ldr r1, =0x3ff
  24. ldr r0, =INTSUBMSK
  25. str r1, [r0]
  26. # endif
  27. /* FCLK:HCLK:PCLK = 1:2:4 */
  28. /* default FCLK is 120 MHz ! */
  29. ldr r0, =CLKDIVN
  30. mov r1, #3
  31. str r1, [r0]
  32. #endif /* CONFIG_S3C24X0 */
关闭看门狗,关中断,设置时钟,比较简单。
  1. /*
  2. * we do sys-critical inits only at reboot,
  3. * not when booting from ram!
  4. */
  5. #ifndef CONFIG_SKIP_LOWLEVEL_INIT
  6. bl cpu_init_crit
  7. #endif
进行SDRAM的初始化,查看下cpu_init_crit的代码如下:
  1. /*
  2.  *************************************************************************
  3.  *
  4.  * CPU_init_critical registers
  5.  *
  6.  * setup important registers
  7.  * setup memory timing
  8.  *
  9.  *************************************************************************
  10.  */


  11. #ifndef CONFIG_SKIP_LOWLEVEL_INIT
  12. cpu_init_crit:
  13.     /*
  14.      * flush v4 I/D caches
  15.      */
  16.     mov    r0, #0
  17.     mcr    p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */
  18.     mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB */

  19.     /*
  20.      * disable MMU stuff and caches
  21.      */
  22.     mrc    p15, 0, r0, c1, c0, 0
  23.     bic    r0, r0, #0x00002300    @ clear bits 13, 9:(--V- --RS)
  24.     bic    r0, r0, #0x00000087    @ clear bits 7, 2:(B--- -CAM)
  25.     orr    r0, r0, #0x00000002    @ set bit 2 (A) Align
  26.     orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache
  27.     mcr    p15, 0, r0, c1, c0, 0

  28.     /*
  29.      * before relocating, we have to setup RAM timing
  30.      * because memory timing is board-dependend, you will
  31.      * find a lowlevel_init.S in your board directory.
  32.      */
  33.     mov    ip, lr

  34.     bl    lowlevel_init

  35.     mov    lr, ip
  36.     mov    pc, lr
  37. #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
主要是关闭cache和无效mmu,这里讲解下mrc指令,mrc指令是将协处理器寄存器中的内容传输到ARM通用寄存器中,而mcr指令则完成了相反的操作。 要弄明白这里的具体意思,请参照杜春雷ARM体系结构与编程。
其中又跳转到了 lowlevel_init函数,这个函数在board/samsung/smdk2410/lowlevel_init.S中:
  1. _TEXT_BASE:
  2.     .word    TEXT_BASE

  3. .globl lowlevel_init
  4. lowlevel_init:
  5.     /* memory control configuration */
  6.     /* make r0 relative the current location so that it */
  7.     /* reads SMRDATA out of FLASH rather than memory ! */
  8.     ldr r0, =SMRDATA      
  9.     ldr    r1, _TEXT_BASE 
  10.     sub    r0, r0, r1
  11. //要记住此时你的程序还在norflash上
//这三行用于地址变换,因为这个时候SDRAM中还没有数据,不能那个使用链接程序时确定的地址来读取数据。
//SMRDATA表示将要写入SDRAM中13个寄存器的值存放的开始地址,值为0x33F8XXXX,处于内存中
//ldr    r1, _TEXT_BASE获得代码段的起始地址,即config.mk中定义的0X33F80000
//将两者0x33F8XXXX和0X33F80000相减,就得到了将要写入SDRAM中13个寄存器的值在NORflash上存放的开始地址,即r0的值就是将要写入SDRAM中13个寄存器的值在NORflash上存放的开始地址
  1.     ldr    r1, =BWSCON    /* Bus Width Status Controller */
  2.     add r2, r0, #13*4
  3. //r1为寄存器的地址,r2为最后一个寄存器的地址
  4. 0:
  5.     ldr r3, [r0], #//将r0地址上的值付给r3,即取出要付给SDRAM寄存器的值
  6.     str r3, [r1], #4//将r3的值付给r1地址指向的内存,即将值付给了SDRAM寄存器
  7.     cmp r2, r0       //比较有没有赋值完毕?
  8.     bne 0b

  9.     /* everything is fine now */
  10.     mov    pc, lr   //返回,已经设好SDRAM寄存器

  11.     .ltorg
  12. /* the literal pools origin */

  13. SMRDATA//将要写入SDRAM中13个寄存器的值
  14.     .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
  15.     .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
  16.     .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
  17.     .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
  18.     .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
  19.     .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
  20.     .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
  21.     .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
  22.     .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
  23.     .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
  24.     .word 0x32
  25.     .word 0x30
  26.     .word 0x30
接着往下看,接下来将要把整个uboot的代码赋值到SDRAM中,代码如下:
  1. #ifndef CONFIG_SKIP_RELOCATE_UBOOT
  2. relocate:                /* relocate U-Boot to RAM     */
  3.     adr    r0, _start        /* r0 <- current position of code */
  4. //当前代码的位置,若在flash中,r0=0,若在SDRAM中,则r0=TEXT_BASE
  5.     ldr    r1, _TEXT_BASE        /* test if we run from flash or RAM */
  6. //代码段在SDRAM中存放的位置,无论是在flash还是在SDRAM中,r1都等于TEXT_BASE
  7.     cmp    r0, r1            /* don't reloc during debug */
  8. //如果已经在SDRAM中,则就不需要将uboot进行搬移了,直接进行堆栈的设置
  9.     beq    stack_setup
  10. //否则进行uboot的搬移
  11.     ldr    r2, _armboot_start
  12. //第一条指令的运行地址
  13.     ldr    r3, _bss_start
  14. //代码段的结束地址,在uboot.lds中定义
  15.     sub    r2, r3, r2        /* r2 <- size of armboot */
  16. //r2=代码段的长度
  17.     add    r2, r0, r2        /* r2 <- source end address */
  18. //r2=norflash上代码段的结束地址
  19. copy_loop:
  20. ldmiar0!, {r3-r10}/* copy from source address [r0]    */
  21. //从r0地址处获得数据,即从norflash中获得数据
  22. stmiar1!, {r3-r10}/* copy to   target address [r1]    */
  23. //复制到r1地址处即SDRAM的TEXT_BASE处
  24. cmpr0, r2/* until source end addreee [r2]    */
  25. //复制完否?
  26. blecopy_loop
  27. //没有,则继续复制
  28. #endif /* CONFIG_SKIP_RELOCATE_UBOOT */
  1. /* Set up the stack                         */
  2. stack_setup:
  3.     ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot */
  4. //TEXT_BASE上面是刚刚复制过来的代码段
  5.     sub    r0, r0, #CONFIG_SYS_MALLOC_LEN    /* malloc area */
  6. //代码段下面留出一段内存用来实现malloc
  7.     sub    r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
  8. //再留出一段内存用来存全局参数
  9. #ifdef CONFIG_USE_IRQ
  10.     sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
  11. #endif
  12. //IRQ,FIQ的堆栈
  13.     sub    sp, r0, #12        /* leave 3 words for abort-stack */
  14. //留出12个字节给abort异常
  15.     bic    sp, sp, #7        /* 8-byte alignment for ABI compliance */
  16. //往下的内存就是栈了
这时候的SDRAM和norflash的布局可以参看韦东山的书260页的图,很直观。
//bss段(初始值为0,无初始值的全局变量,静态变量放在bss段)全部清零
  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函数的运行环境已经全部准备好,通过下面的命令跳直接转到(这之后,程序才在内存中执行),它将调用board.c中的start_armboot函数。
  1. ldr    pc, _start_armboot

  2. _start_armboot:    .word start_armboot
程序的运行离不开栈,程序运行时所需的临时变量,数据,函数间调用的参数的传递都需要栈的支持,所以在执行C语言函数时,首先要把栈设置好,同样,为了程序正常的执行,我们也要预先设置好外设和时钟例如时钟设备可以产生正确的国定的频率驱动整个cpu运行,中断处理程序能够使得cpu能够处理非顺序的,突发的执行逻辑,这就是uboot第一阶段为什么要设置堆栈,时钟,初始化SDRAM,关闭看门狗的原因,为了以后程序的正常运行,这些是必要的条件。

好了,第一阶段的分析,到此结束了。

主要参考:
 李无言 一步步写嵌入式操作系统
韦东山 嵌入式linux应用开发完全手册
0 0
原创粉丝点击