Uboot分析(三)

Bootloader启动大多数都分为两个阶段。第一阶段主要包含依赖于CPU的体系结构硬件初始化的代码,通常都用汇编语言来实现;第二阶段通常用C语言完成,以便实现更复杂的功能,也使程序有更好的可读性和可移植性。

U-Boot的启动代码分布在start.S、low_level_init.S、 board.c和main.c文件中。

start.S                是U-Boot整个程序的入口,该文件使用汇编语言编写,不同体系结构的启动代码是不同的;

low_level_init.S    是特定开发板的设置代码;


board.c               包含开发板底层设备驱动;

main.c                 是一个与平台无关的代码,U- Boot应用程序的入口在此文件中。

第一阶段对应的文件是cpu/XXX/start.S和board/samsung/XXX/lowlevel_init.S

第二阶段对应的文件是lib_arm/board.c,最后跳转到common/main.c,main_loop在标准转入设备中接受命令行,然后分析,查找,执行。

 

一个可执行的image 必须有一个入口点,并且只能有一个全局入口点,所以要通知编译器这个入口在哪里,入口点是通过链接脚本来实现的,由此我们可以找到程序的入口点是在cpu/arm_cortexa8/u-boot.lds 中指定的,其中ENTRY(_start) 说明程序从_start 开始运行,而它指向的是cpu/arm_cortexa8/start.o 文件。

因为我们用的是 cortex-a8 的 cpu 架构,在CPU复位后从iROM地址0x00000000取它的第一条指令,执行iROM代码的功能是把flash中的前16K的代码加载到iRAM中,系统上电后将首先执行 u-boot 程序。

 

首先我们来看一下u-boot.lds链接脚本,通过它我们可以知道它整个程序的各个段是怎么存放的。

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;

 . = ALIGN(4);
 .text :
 {
  cpu/arm_cortexa8/start.o (.text)
  board/samsung/fsc100/lowlevel_init.o
  board/samsung/fsc100/mem_setup.o
  board/samsung/fsc100/nand_cp.o
  *(.text)//所有的其他程序的代码段以四字节对齐放在后面
 }


 . = ALIGN(4);
 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }//只读数据段

 . = ALIGN(4);
 .data : { *(.data) }//指定读/写数据段

 . = 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) }
 _end = .;//把_end赋值为当前位置,即bss段得结束地址
}

1.stage1:cpu/arm_cortexa8/start.S

2.当系统启动时, ARM CPU 会跳到 0x00000000去执行,一般 BootLoader 包括如下几个部分:
                1. 建立异常向量表
                2. 显示的切换到 SVC 且 32 指令模式
                3. 设置异常向量表
                4. 关闭 TLB,MMU,cache,刷新指令 cache 数据 cache
                5. 关闭内部看门狗
                6. 禁止所有的中断
                7. 串口初始化
                8. tzpc(TrustZone Protection Controller)
                9. 配置系统时钟频率和总线频率
                10. 设置内存区的控制寄存器
                11. 设置堆栈
                12. 跳到 C 代码部分执行

 

#include <config.h>//@由顶层的mkconfig生成
#include <version.h>

@设置异常向量

.globl _start    @ 全局变量,_startGNU汇编的默认入口标签
_start: b reset    @0x0,复位向量,直接跳转到reset,并且不返回,正常情况下,系统 reset 后进入的入口
 ldr pc, _undefined_instruction    @0x4,未定义指令,系统出错处理的入口
 ldr pc, _software_interrupt    @0x8,软中断,monitor 程序的入口
 ldr pc, _prefetch_abort    @0x0c,预取中止错误
 ldr pc, _data_abort    @0x10,取数据失中止错误(通常是保护现场)
 ldr pc, _not_used    @0x14 保留
 ldr pc, _irq    @0x18,中断请求
 ldr pc, _fiq    @0x1c 快速中断请求

@8*4 = 32 Byte

_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 */
.global _end_vect
_end_vect:

 .balignl 16,0xdeadbeef

@.word为GNU ARM汇编特有的伪操作,为分配一段字内存单元(分配的单元为字对齐的),可以使用.word把标志符作为常量使用。如_fiq:.word fiq即把fiq存入内存变量_fiq中,也即是把fiq放到地址_fiq中。

@.align伪操作用于表示对齐方式:通过添加填充字节使当前位置,.balignl是.balign的变体,在以当前地址开始,在地址为16的倍数的位置的前面填入四个字节内容为0xdeadbeef;.balignl的最后一个字母l代表4字节对齐,因此地址就是16*4=64,而前面已经占了15*4=60个字节,故在地址60处开始填充0xdeadbeef,0xdeadbeef作用就是为内存做标记,插在那里,就表示从这个位置往后的一段有特殊作用的内存,而这个位置往前,禁止访问。
/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * setup Memory and board specific bits prior to relocation.
 * relocate armboot to ram
 * setup stack
 *当没有从内存启动时做一些重要的初始化,启动内存和板子上特殊位来重映射。重映射armbootRAM,并初始化建立好栈
 *************************************************************************/

_TEXT_BASE:
 .word TEXT_BASE

/*TEXT_BASE这个标号的定义在如下文件中定义: 
 *board/samsung/smdkc100/config.mk 
 *TEXT_BASE = 0x34800000    @本程序运行的基地址为TEXT_BASE
 */

.globl _armboot_start
_armboot_start:
 .word _start    @_start 是uboot的第一行代码的标号,代表的是第一行代码的地址

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
 .word __bss_start

.globl _bss_end
_bss_end:
 .word _end

@在cpu/arm_cortexa8/u-boot.lds中定义,这样赋值是因为代码所在地址非编译时的地址,直接取得该标号对应地址。

#ifdef CONFIG_USE_IRQ    @这个宏没有定义,故不执行
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
 .word 0x0badc0de    @在IRQ_STACK_START处插入0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
 .word 0x0badc0de    @ 在FIQ_STACK_START处插入0x0badc0de
#endif

/*
 * the actual reset code    @真正的复位代码
 */

@CPU进入SVC模式

reset:
 /*CPU一上电以后就是跳到这里执行的
  * set the cpu to SVC32 mode
  */

@更改处理器模式为管理模式
@对状态寄存器的修改要按照:读-改-写的顺序执行

CPSR

31 30 29 28 ---   7   6   -   4      3      2      1      0
N   Z   C  V         I   F         M4    M3   M2    M1   M0
                                        1      0      0      0      0     User模式

                                        1      0      0      0      1     FIQ模式

                                        1      0      0      1      0     IRQ模式

                                        1      0      1      1      1     SVC模式

                                        1      1      0      1      1     Abort模式

                                        1      1      1      1      1     Undef模式

                                        1      0      0      1      1     System模式

                                        1      0      1      1      0     Moniter模式(Cortex)


 mrs r0, cpsr    @将cpsr的值读到r0中
 bic r0, r0, #0x1f    @清除M0~M4
 orr r0, r0, #0xd3    @禁止IRQ,FIQ中断,并将处理器置于管理模式
 msr cpsr,r0

#if (CONFIG_OMAP34XX)    @这个宏没有定义,下面的代码不会预编译
 /* Copy vectors to mask ROM indirect addr */
 adr r0, _start  @ r0 <- current position of code
 add r0, r0, #4  @ skip reset vector
 mov r2, #64   @ r2 <- size to copy
 add r2, r0, r2  @ r2 <- source end address
 mov r1, #SRAM_OFFSET0 @ build vect addr
 mov r3, #SRAM_OFFSET1
 add r1, r1, r3
 mov r3, #SRAM_OFFSET2
 add r1, r1, r3
next:
 ldmia r0!, {r3 - r10}  @ copy from source address [r0]
 stmia r1!, {r3 - r10}  @ copy to   target address [r1]
 cmp r0, r2   @ until source end address [r2]
 bne next   @ loop until equal */
#if !defined(CONFIG_SYS_NAND_BOOT) && !defined(CONFIG_SYS_ONENAND_BOOT)
 /* No need to copy/exec the clock code - DPLL adjust already done
  * in NAND/oneNAND Boot.
  */
 bl cpy_clk_code  @ put dpll adjust code behind vectors
#endif /* NAND Boot */
#endif
 /* the mask ROM code should have PLL and others stable */

#ifndef CONFIG_SKIP_LOWLEVEL_INIT    @这个宏没有定义,条件成立,下面的代码需要执行
 bl cpu_init_crit
#endif

@执行CPU初始化,BL完成跳转的同时会把后面紧跟的一条指令地址保存到连接寄存器LR(R14)中。以使子程序执行完后正常返回。

ldr r0, =0xe03001c0
ldr r1, =0x1111
str r1, [r0]
ldr r0, =0xe03001c4
ldr r1, =0x3
str r1, [r0]

/* added */
ldr r0, =_TEXT_BASE
adr r1, _TEXT_BASE
cmp r0, r1
beq stack_setup

ldr r0, =0xe03001c4
ldr r1, =0xf
str r1, [r0]

#ifdef CONFIG_CMD_NAND
ldr sp, =(0x22000000)
bl copy_uboot_to_ram
b stack_setup
#endif  /* CONFIG_CMD_NAND */

#ifndef CONFIG_SKIP_RELOCATE_UBOOT    @这个宏没有定义,条件成立,下面的代码能够执行
relocate:    @ relocate U-Boot to RAM    U-boot自搬移到RAM
 adr r0, _start  @ r0 <- current position of code     装载_start的地址到r0中
 ldr r1, _TEXT_BASE  @ test if we run from flash or RAM    装载连接地址,这个地址是TEXT_BASE = 0x34800000
 cmp r0, r1   @ don't reloc during debug
 beq stack_setup

@调试阶段的代码是直接在RAM中运行的,而最后需要把这些代码固化到Flash中,因此U-Boot需要自己从Flash转移到RAM中运行,这@也是重定向的目的所在。
@通过adr指令得到当前代码的地址信息:如果U-boot是从TEXT_BASE = 0x34800000,如果U-boot从Flash开始运行,即从处理器对应的地址运行,则r0=0x0000,这时将会执行copy_loop标识的那段代码了。
@判断 当uboot在nand当中引导时,会把前16K的代码放到ram中,ram的地址和连接地址不一致, r0不等于r1的值,beq条件不成立;当从usb引导是这个条件就成立.成立后后面的代码就不在执行了,后面的搬移代码就不在执行.

 ldr r2, _armboot_start    @功能是装载_start的地址

 / * .globl _armboot_start
   * _armboot_start:
   *         .word _start
   * /
 ldr r3, _bss_start    @ 功能是装载

 / *.globl _bss_start
    * _bss_start:
    *        .word __bss_start
    * __bss_start这个标号在cpu/arm_cortexa8/u-boot.lds 中定义,是bss段的开始也是bss段以前的一个结束标志 
    * 因此r3的值是uboot的除去bss的末尾地址,在搬移的时候是不搬移bss段的,bss段放的是未初始化的变量 
    * /
 sub r2, r3, r2  @ r2 <- size of armboot    计算armboot的大小
 add r2, r0, r2  @ r2 <- source end address    计算源代码结束地址

copy_loop:    @ copy 32 bytes at a time
 ldmia r0!, {r3 - r10}  @ copy from source address [r0]

@从源地址[r0]读取8个字节到寄存器,每读一个就更新一次r0地址    ldmia:r0安字节增长

 stmia r1!, {r3 - r10}  @ copy to   target address [r1]
 cmp r0, r2   @ until source end addreee [r2]     等到搬移完成后,r0和r2的值相等
 ble copy_loop
#endif /* CONFIG_SKIP_RELOCATE_UBOOT */

@LDM(STM)用于在寄存器所指的一片连续存储器和寄存器列表的寄存@器间进行数据移动,或是进行压栈和出栈操作。
@格式为:LDM(STM){条件}{类型}基址寄存器{!},寄存器列表{^}
@对于类型有以下几种情况: 

    IA 每次传送后地址加1,用于移动数据块
    IB 每次传送前地址加1,用于移动数据块
    DA 每次传送后地址减1,用于移动数据块
    DB 每次传送前地址减1,用于移动数据块
    FD 满递减堆栈,用于操作堆栈(即先移动指针再操作数据,相当于DB)
    ED 空递减堆栈,用于操作堆栈(即先操作数据再移动指针,相当于DA)
    FA 满递增堆栈,用于操作堆栈(即先移动指针再操作数据,相当于IB)
    EA 空递增堆栈,用于操作堆栈(即先操作数据再移动指针,相当于IA)

 

 /* Set up the stack */    @设置堆栈,规划内存的使用的
stack_setup:

 ldr r0, =0xe03001c4
 ldr r1, =0x0
 str r1, [r0]

 ldr r0, _TEXT_BASE  @ upper 128 KiB: relocated uboot
 sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area    向下内存分配,为malloc预留分配空间

 /* CONFIG_SYS_MALLOC_LEN    include/configs/smdkc100.h
  *#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + (1 << 20)) 
  *环境变量大小#define CONFIG_ENV_SIZE (128 << 10) /* 128KiB, *0x20000  
  * 这句话的功能是r0 的值向低地址减去128K +1M的大小
  */ 
 sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE @ bdinfo    预留初始化的数据的空间

 / * CONFIG_SYS_GBL_DATA_SIZE include/configs/smdkc100.h
   * #define CONFIG_SYS_GBL_DATA_SIZE 128 /* size in bytes */
   * 这句话是把地址继续减去128 bytes
   * /
#ifdef CONFIG_USE_IRQ    @这个宏没有定义,下面的代码不会执行
 sub r0, r0, #(CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ)    @如果定义了中断则还需要向下预留中断空间
#endif
 sub sp, r0, #12  @ leave 3 words for abort-stack    @ 预留3个字给溢出堆栈
 and sp, sp, #~7  @ 8 byte alinged for (ldr/str)d

 /* Clear BSS (if any). Is below tx (watch load addr - need space) */
clear_bss:    @对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

 ldr r0, =0xe03001c4
 ldr r1, =0x1
 str r1, [r0]

 ldr pc, _start_armboot @ jump to C code    进入C代码

_start_armboot: .word start_armboot

@进入lib_arm/board.c文件中的 void start_armboot (void)

@这句话使得pc指针也就从第一阶段的汇编语言跳到了第二阶段的C语言了
/*************************************************************************
 *
 * CPU_init_critical registers    @初始化关键的寄存器
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************/
cpu_init_crit:
 /*
  * Invalidate L1 I/D
  */

@初始化CACHES
 mov r0, #0   @ set up for MCR
 mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
 mcr p15, 0, r0, c7, c5, 0 @ invalidate icache

 /*
  * disable MMU stuff and caches
  */

@关闭MMU和CACHES
 mrc p15, 0, r0, c1, c0, 0
 bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
 bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
 orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
 orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
 mcr p15, 0, r0, c1, c0, 0

 /*
  * Jump to board specific initialization...
  * The Mask ROM will have already initialized
  * basic memory. Go here to bump up clock rate and handle
  * wake up conditions.
  */
 mov ip, lr   @ persevere link reg across call    保存LR,以便正常返回,注意前面是通过BL跳到cpu_init_crit来的
 bl lowlevel_init  @ go setup pll,mux,memory    在重定向代码之前,必须初始化内存时序,重定向时需要将flash中的代码复制到内存中
 @lowlevel_init 这个函数在board/samsung/smdk100/lowlevel_init.S文件当中定义

mov lr, ip   @ restore link  
 mov pc, lr   @ back to my caller
/*
 *************************************************************************
 *
 * Interrupt handling
 *
 *************************************************************************
 */
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72

#define S_OLD_R0 68
#define S_PSR  64
#define S_PC  60
#define S_LR  56
#define S_SP  52

#define S_IP  48
#define S_FP  44
#define S_R10  40
#define S_R9  36
#define S_R8  32
#define S_R7  28
#define S_R6  24
#define S_R5  20
#define S_R4  16
#define S_R3  12
#define S_R2  8
#define S_R1  4
#define S_R0  0

#define MODE_SVC 0x13
#define I_BIT  0x80

/*
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */

 .macro bad_save_user_regs
 sub sp, sp, #S_FRAME_SIZE  @ carve out a frame on current
      @ user stack
 stmia sp, {r0 - r12}   @ Save user registers (now in
      @ svc mode) r0-r12

 ldr r2, _armboot_start
 sub r2, r2, #(CONFIG_SYS_MALLOC_LEN)
 sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ set base 2 words into abort
      @ stack
 ldmia r2, {r2 - r3}   @ get values for "aborted" pc
      @ and cpsr (into parm regs)
 add r0, sp, #S_FRAME_SIZE  @ grab pointer to old stack

 add r5, sp, #S_SP
 mov r1, lr
 stmia r5, {r0 - r3}   @ save sp_SVC, lr_SVC, pc, cpsr
 mov r0, sp    @ save current stack into r0
      @ (param register)
 .endm

 .macro irq_save_user_regs
 sub sp, sp, #S_FRAME_SIZE
 stmia sp, {r0 - r12}   @ Calling r0-r12
 add r8, sp, #S_PC   @ !! R8 NEEDS to be saved !!
      @ a reserved stack spot would
      @ be good.
 stmdb r8, {sp, lr}^   @ Calling SP, LR
 str lr, [r8, #0]   @ Save calling PC
 mrs r6, spsr
 str r6, [r8, #4]   @ Save CPSR
 str r0, [r8, #8]   @ Save OLD_R0
 mov r0, sp
 .endm

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

 .macro get_bad_stack
 ldr r13, _armboot_start  @ setup our mode stack (enter
      @ in banked mode)
 sub r13, r13, #(CONFIG_SYS_MALLOC_LEN) @ move past malloc pool
 sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ move to reserved a couple
      @ spots for abort stack

 str lr, [r13]   @ save caller lr in position 0
      @ of saved stack
 mrs lr, spsr   @ get the spsr
 str lr, [r13, #4]   @ save spsr in position 1 of
      @ saved stack

 mov r13, #MODE_SVC   @ prepare SVC-Mode
 @ msr spsr_c, r13
 msr spsr, r13   @ switch modes, make sure
      @ moves will execute
 mov lr, pc    @ capture return pc
 movs pc, lr    @ jump to next instruction &
      @ switch modes.
 .endm

 .macro get_bad_stack_swi
 sub r13, r13, #4   @ space on current stack for
      @ scratch reg.
 str r0, [r13]   @ save R0's value.
 ldr r0, _armboot_start  @ get data regions start
 sub r0, r0, #(CONFIG_SYS_MALLOC_LEN) @ move past malloc pool
 sub r0, r0, #(CONFIG_SYS_GBL_DATA_SIZE + 8) @ move past gbl and a couple
      @ spots for abort stack
 str lr, [r0]   @ save caller lr in position 0
      @ of saved stack
 mrs r0, spsr   @ get the spsr
 str lr, [r0, #4]   @ save spsr in position 1 of
      @ saved stack
 ldr r0, [r13]   @ restore r0
 add r13, r13, #4   @ pop stack entry
 .endm

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

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

/*
 * exception handlers
 */

@异常向量处理
@每一个异常向量处其实只放了一条跳转指令(因为每个异常向量只有4个字节不能放太多的程序),跳到相应的异常处理程序中。

 .align 5    @.align 5就是25次方对齐
undefined_instruction:
 get_bad_stack
 bad_save_user_regs
 bl do_undefined_instruction

 .align 5
software_interrupt:
 get_bad_stack_swi
 bad_save_user_regs
 bl do_software_interrupt

 .align 5
prefetch_abort:
 get_bad_stack
 bad_save_user_regs
 bl do_prefetch_abort

 .align 5
data_abort:
 get_bad_stack
 bad_save_user_regs
 bl do_data_abort

 .align 5
not_used:
 get_bad_stack
 bad_save_user_regs
 bl do_not_used

#ifdef CONFIG_USE_IRQ

 .align 5
irq:
 get_irq_stack
 irq_save_user_regs
 bl do_irq
 irq_restore_user_regs

 .align 5
fiq:
 get_fiq_stack
 /* someone ought to write a more effective fiq_save_user_regs */
 irq_save_user_regs
 bl do_fiq
 irq_restore_user_regs

#else

 .align 5
irq:
 get_bad_stack
 bad_save_user_regs
 bl do_irq

 .align 5
fiq:
 get_bad_stack
 bad_save_user_regs
 bl do_fiq

#endif

 

posted @ 2012-12-09 16:00  雪中飞  阅读(6705)  评论(0编辑  收藏  举报