转载自: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定义的变量得注意的地方,后面还有记录。
- #include <common.h> //位于\include目录下是一个包含其他头文件的头文件
- #include <config.h> //位于\include\linux目录下
- .globl _start
下面这段代码处理中断向量表。关于balignal 16,0xdeadbeef的网上资料:.align伪操作用于表示对齐方式:通过添加填充字节使当前位置满足一定的对齐方式。.balign的作用同.align。.align {alignment} {,fill} {,max}其中:alignment用于指定对齐方式,可能的取值为2的次幂,缺省为4。fill是填充内容,缺省用0填充。max是填充字节数最大值,如果填充字节数超过max,就不进行对齐.例如:.align 4, 指定对齐方式为字对齐deadbeef一般用来表示没用的或是已经被释放掉的内存。
- _start: b start_code
- ldr pc, _undefined_instruction // ldr语句的意思是将第二个操作数指向的地址数据传给PC
- ldr pc, _software_interrupt // .word 为定义一个4字节的空间undefined_instruction
- //为地址, 即后面标号所对的偏移地址数据
- 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
- .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)。
- _TEXT_BASE:
- .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,
- .globl _armboot_start
- _armboot_start:
- .word _start
下面三句,特别要备注的是__bss_start这个符号,它的值为多少?该值定义在board\smdk2410\u-boot.lds文件
- . = ALIGN(4);
- __bss_start = .;
- .bss : { *(.bss) }
- _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(注意大小端)
- .globl _bss_start
- _bss_start:
- .word __bss_start
- .globl _bss_end
- _bss_end:
- .word _end
- #ifdef CONFIG_USE_IRQ
- /* IRQ stack memory (calculated at run-time) */
- .globl IRQ_STACK_START
- IRQ_STACK_START:
- .word 0x0badc0de
- /* IRQ stack memory (calculated at run-time) */
- .globl FIQ_STACK_START
- FIQ_STACK_START:
- .word 0x0badc0de
- #endif
代码从这里开始。下面几行进入SVC模式,通过装入CPSR到R0,修改相应位后再装入到CPSR
- start_code:
- /*
- * set the cpu to SVC32 mode
- */
- mrs r0,cpsr
- bic r0,r0,#0x1f
- orr r0,r0,#0xd3
- msr cpsr,r0
- /*
- *以下这些都是为AT91RM9200写的
- */
- bl coloured_LED_init
- bl red_LED_on
- #if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
- /*
- * relocate exception table
- */
- ldr r0, =_start
- ldr r1, =0x0
- mov r2, #16
- copyex:
- subs r2, r2, #1
- ldr r3, [r0], #4
- str r3, [r1], #4
- bne copyex
- #endif
- #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
- /*
- *关闭看门狗
- */
- # if defined(CONFIG_S3C2400)
- # define pWTCON 0x15300000
- # define INTMSK 0x14400008 /* Interupt-Controller base addresses */
- # define CLKDIVN 0x14800014 /* clock divisor register */
- #else
- # define pWTCON 0x53000000
- # define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
- # define INTSUBMSK 0x4A00001C
- # define CLKDIVN 0x4C000014 /* clock divisor register */
- # endif
- ldr r0, =pWTCON
- mov r1, #0x0
- str r1, [r0]
- /*
- * 设置中断屏蔽寄存器相应位,关闭所有的中断
- */
- mov r1, #0xffffffff
- ldr r0, =INTMSK
- str r1, [r0]
- # if defined(CONFIG_S3C2410)
- ldr r1, =0x3ff
- ldr r0, =INTSUBMSK
- str r1, [r0]
- # endif
- /*
- *通过设置CLKDIVN寄存器,时钟分频比为1:2:4默认的FCLK为120MHz
- */
- ldr r0, =CLKDIVN
- mov r1, #3
- str r1, [r0]
- #endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */
跳入cpu_init_crit ,这是一个系统初始化函数,他还会调用board/*/lowlevel_init.S中的lowlevel_init函数。主要是对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。下面的代码重定向就依赖它。
- #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- bl cpu_init_crit
- #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
- .globl _bss_start
- _bss_start:
- .word __bss_start
下面这两条语句也就好理解了:
sub r2, r3, r2 // r2 <- armboot 大小
add r2, r0, r2 // r2 <- 代码结束地址
到底 armboot 的大小都包含了哪些东西,结合 u-boot.lds ,见下图:
那为什么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.
- #ifndef CONFIG_SKIP_RELOCATE_UBOOT
- relocate: /* relocate U-Boot to RAM */
- adr r0, _start /* r0 <- current position of code */
- ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
- cmp r0, r1 /* don't reloc during debug */
- beq stack_setup
- ldr r2, _armboot_start
- ldr r3, _bss_start
- sub r2, r3, r2 /* r2 <- size of armboot */
- add r2, r0, r2 /* r2 <- source end address */
下面几行将r0地址处的代码复制到r1处,即从0x0000搬到0x33f80000(假设CPU复位从0x0000执行的)。
- copy_loop:
- ldmia {r3-r10} /* copy from source address [r0] */
- stmia {r3-r10} /* copy to target address [r1] */
- cmp r0, r2 /* until source end addreee [r2] */
- ble copy_loop
- #endif /* CONFIG_SKIP_RELOCATE_UBOOT */
堆栈初始化代码(为第二阶段的C语言做准备)
- 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 */
对BSS段清零(为第二阶段的C语言做准备)BSS段(bss segment)通常是用来存放程序中未初始化的全
局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。在编译时,编译器已经为他们分配好了空间,只不过他们的值为0,为了节省空间,在bin或ELF文件中不占空间。
编译器会计算出_bss_start和_bss_end的值,不是定义的
- clear_bss:
- ldr r0, _bss_start /* find start of bss segment */
- ldr r1, _bss_end /* stop here */
- mov r2, #0x00000000 /* clear */
- clbss_l:str r2, [r0] /* clear loop... */
- add r0, r0, #4
- cmp r0, r1
- ble clbss_l
跳入第二阶段的C语言代码入口_start_armboot (已经被重定向到内存)
- ldr pc, _start_armboot
- _start_armboot: .word start_armboot
系统初始化函数,初始化重要的寄存器,初始化内存时序,*他还会调用board/*/lowlevel_init.S中的lowlevel_init函数。主要是对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。下面的代码重定向就依赖它。操作CP15协处理器
- #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- cpu_init_crit:
- /*
- * flush v4 I/D caches
- */
- mov r0, #0
- mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */
- mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
- /*
- * disable MMU stuff and caches
- */
- mrc p15, 0, r0, c1, c0, 0
- bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
- bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
- orr r0, r0, #0x00000002 @ set bit 2 (A) Align
- orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
- mcr p15, 0, r0, c1, c0, 0
调用board/*/lowlevel_init.S中的lowlevel_init函数,对系统总线的初始化,初始化了连接存储器的位宽、速度、刷新率等重要参数。经过这个函数的正确初始化,Nor Flash、SDRAM才可以被系统使用。
- mov ip, lr
- bl lowlevel_init
- mov lr, ip
- mov pc, lr
- #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
后面的代码略,主要是中断相关代码,但是U-boot基本不使用中断所以暂且略过
- @
- @ 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
- stmia sp, {r0 - r12} @ Calling r0-r12
- ldr r2, _armboot_start
- sub r2, r2, #(CONFIG_STACKSIZE)
- 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 pc, cpsr
- add r0, sp, #S_FRAME_SIZE @ restore sp_SVC
- add r5, sp, #S_SP
- mov r1, lr
- stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
- mov r0, sp
- .endm
- .macro irq_save_user_regs
- sub sp, sp, #S_FRAME_SIZE
- stmia sp, {r0 - r12} @ Calling r0-r12
- add r7, sp, #S_PC
- stmdb r7, {sp, lr}^ @ Calling SP, LR
- str lr, [r7, #0] @ Save calling PC
- mrs r6, spsr
- str r6, [r7, #4] @ Save CPSR
- str r0, [r7, #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
- sub r13, r13, #(CONFIG_STACKSIZE)
- sub r13, r13, #(CONFIG_SYS_MALLOC_LEN)
- sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack
- str lr, [r13] @ save caller lr / spsr
- mrs lr, spsr
- str lr, [r13, #4]
- mov r13, #MODE_SVC @ prepare SVC-Mode
- @ msr spsr_c, r13
- msr spsr, r13
- mov lr, pc
- movs pc, lr
- .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
- */
- .align 5
- undefined_instruction:
- get_bad_stack
- bad_save_user_regs
- bl do_undefined_instruction
- .align 5
- software_interrupt:
- get_bad_stack
- 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 effiction 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