uboot笔记之一:start.s

u-boot-2010.06到ok2440的移植以sbc2410x为模板。先阅读分析sbc2410x的执行过程。

start.s位于arch/arm/cpu/arm920t目录下,是uboot执行的第一段代码。阅读代码,了解uboot的执行过程,顺便学习gnu的arm汇编。

最近发现有人做过类似的工作,互相参考吧:
http://blog.21ic.com/user1/4079/archives/2007/44392.html

 

#include <common.h> #include <config.h> /* ************************************************************************* * * Jump vector table as in table 3.1 in [1] * ************************************************************************* */ .globl _start /* .globl symbol 定义一个全局可见的symbol GNU linker要求用一个全局可见的symbol _start 来指定程序执行的第一条指令(程序入口) */ _start: b start_code 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 /* .word expression GNU汇编伪指令,插入一个32-bit的数据,其内容为expression的值。 与armasm中的DCD功能相同 可以使用.word把标识符作为常量使用: _fiq: .word fiq label _fiq所指示的内存单元中存储的内容为label fiq的地址, 此处之所以将这些汇编代码的label都用.word重新定义一遍,是为了在c语言程序中可以使用这些label所在的地址: 例如编译链接之后 _start的地址是0x33f80000,其中存储的内容是指令b start_code的机器码 _armboot_start的地址是0x33f80044,其中存储的内容是_start的地址0x33f80000 汇编中label只是内存地址的一个标记,代表地址本身而与其中存储的内容无关:ldr r0, =_start会被替换为ldr r0,0x33f80000 而在c语言中,对变量的引用是使用其中所存储的内容,而与变量所存储的地址无关:_armboot_start = 0x12345678会改变内存0x33f80044地址处所 存储的内容为0x12345678(本来是0x33f80000),变量名是指向内存地址的一个指针。 关于这个问题的详细讨论可以参考 http://blog.chinaunix.net/u/26710/showart_403988.html 从U-Boot源码看C语言对汇编代码中的符号引用 http://www.alteraforum.com/forum/showthread.php?t=17189 How to declare linker symbol in C files */ .balignl 16,0xdeadbeef /* .balign[wl] abs-expr, abs-expr, abs-expr 增加位置计数器(在当前子段)使它指向规定的存储边界。 第一个表达式参数(结果必须是纯粹的数字)是必需参数:边界基准,单位为字节。 例如, ‘.balign 8’向后移动位置计数器直至计数器的值等于8的倍数。如果位置计数器已经是8的倍数,则无需移动。 第2个表达式参数(结果必须是纯粹的数字)给出填充字节的值,用这个值填充位置计数器越过的地方。 第2个参数(和逗点)可以省略。如果省略它,填充字节的值通常是0。但在某些系统上,如果本段标识为包含代码,而填充值被省略,则使用no-op指令填充空白区。 第3个参数的结果也必须是纯粹的数字,这个参数是可选的。如果存在第3个参数,它代表本对齐命令允许跳过字节数的最大值。 如果完成这个对齐需要跳过的字节数比规定的最大值还多,则根本无法完成对齐。 可以在边界基准参数后简单地使用两个逗号,以省略填充值参数(第二参数);如果想在适当的时候,对齐操作自动使用no-op指令填充,本方法将非常奏效。 .balignw和.balignl是.balign命令的变化形式。 .balignw使用2个字节来填充空白区。.balignl使用4字节来填充。 例如,.balignw 4,0x368d将地址对齐到4的倍数,如果它跳过2个字节,GAS将使用0x368d填充这2个字节(字节的确切存放位置视处理器的存储方式而定)。 如果它跳过1或3个字节,则填充值不明确。 .balignl 16,0xdeadbeef ARM指令每条4byte,中断向量表占4x8=32byte,后边的标识符赋值占4x7=28byte,当前指针为0x3c(十进制60),若要满足16的倍数对齐,需要移动到0x40(十进制64)正好空余4byte,可以填入0xdeadbeef。deadbeef算是作者放在这的magic number吧。 http://haoyeren.blog.sohu.com/84511571.html认为: "所以在这个.balignl 16,0xdeadbeef指令之前,一共占了4x15=60个字节的内存,所以本代码的作者当时就简单的在15这个数上,加了个1,即16,把当前指针往后移到地址为64的位置,然后在前面插上了0xdeadbeef这个特殊的值" 以下这篇文章对align的用法分析的比较清楚 http://www.phpfans.net/article/htmls/200905/MjcyNzAy.html align 的用法(u-boot源代码分析) */ /* ************************************************************************* * * Startup Code (called from the ARM reset exception vector) * * do important init only if we don't start from memory! * relocate armboot to ram * setup stack * jump to second stage * ************************************************************************* */ _TEXT_BASE: .word TEXT_BASE .globl _armboot_start _armboot_start: .word _start /* * These are defined in the board-specific linker script. */ .globl _bss_start _bss_start: .word __bss_start .globl _bss_end _bss_end: .word _end /* __bss_start和_end在linker script /arch/arm/cpu/arm920t/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 memory (calculated at run-time) */ .globl FIQ_STACK_START FIQ_STACK_START: .word 0x0badc0de #endif /* * the actual start code */ start_code: /* * set the cpu to SVC32 mode */ mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr, r0 /* ARM复位完成后处于SVC模式 mrs 读状态寄存器(spsr,cpsr)指令 bic 位清零 orr 逻辑或 msr 写状态寄存器指令 r0 = cpsr r0 = r0&(!0x1f) r0 = r0|0xd3 cpsr = r0 cpsr被设置为0bxxxxxxxxxxxxxxxxxxxxxxxx11010011 FIQ IRQ禁止,ARM状态,SVC模式 */ bl coloured_LED_init bl red_LED_on /* arch/arm/lib/boaard.c中,空函数 */ #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 /* 将_start开始的16word复制到0x0处 ldr在ARM中既可以作为伪指令也可以作为数据加载指令 作为伪指令 LDR Rn,=expression 作为指令 LDR Rn,<address> 关于ldr伪指令的文章: http://blog.csdn.net/axx1611/archive/2008/04/27/2335410.aspx 说说ARM汇编的LDR伪指令。 */ #ifdef CONFIG_S3C24X0 /* turn off the watchdog */ # if defined(CONFIG_S3C2400) # define pWTCON 0x15300000 # define INTMSK 0x14400008 /* Interupt-Controller base addresses */ # define CLKDIVN 0x14800014 /* clock divisor register */ #else /*s3c2440*/ # 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] /* * mask all IRQs by setting all bits in the INTMR - default */ mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0] # if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK str r1, [r0] # endif /* FCLK:HCLK:PCLK = 1:2:4 */ /* default FCLK is 120 MHz ! */ ldr r0, =CLKDIVN mov r1, #3 str r1, [r0] #endif /* CONFIG_S3C24X0 */ /* * we do sys-critical inits only at reboot, * not when booting from ram! */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif #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 /* 如果_start和_TEXT_BASE是同一个地址,可以判断代码已经在RAM中,程序将不执行代码拷贝部分. adr为小范围的地址读取伪指令,ADR指令将 基于PC相对偏移的地址值 读取到寄存器中,在编译源程序时ADR伪指令被编译器替换成一条合适的指令.通常编译器用一条ADD指令或SUB指令来实现该ADR伪指令的功能,若不能用一条指令实现则产生汇编错误。 ldr为大范围地址读取伪指令,LDR伪指令用于加载32们的立即数或一个地址值到指定寄存器。在汇编编译源程序时,LDR伪指令被编译器替换成一条合适的指令。若加载的常数未超出MOV或者MVN的范围,则使用MOV或MVN指令代替该LDR伪指令,否则汇编器将常量放入字池,并使用一条程序相对偏移的LDR指令从文字池读出常量。 adr r0, _start 汇编器自动通过当前PC的值计算出_start的地址值,放到r0中————当代码在flash中 执行时r0 = _start = 0;当代码在RAM中执行时_start = _TEXT_BASE = 0x33f80000。 ldr r1, _TEXT_BASE 此句执行的结果r1始终是0x33f80000 关于adr ldr 以及ldr伪指令区别的文章: http://coon.blogbus.com/logs/2738861.html ldr与adr的区别 注意不管是arm7还是arm9或者其他,只要是在执行指令时采用的是流水线机制,前3级的都是:取指->译码->执行。 在"执行"阶段,pc总是指向该指令地址加8字节的地址。换句话说,pc总是指向正在执行的指令地址再加2条指令的地址。之所以是2条指令,是因为在"执行"阶段前还有"取指"和"译码"阶段,每个阶段各有一条指令。 */ ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 /* r2 <- size of armboot */ add r2, r0, r2 /* r2 <- source end address */ copy_loop: ldmia r0!, {r3-r10} /* copy from source address [r0] */ stmia r1!, {r3-r10} /* copy to target address [r1] */ cmp r0, r2 /* until source end addreee [r2] */ ble copy_loop /* ldmia r0!, {r3-r10} 将r0指向地址的多字节内容加载到r3-r10中,并更新r0的值 stmia r1!, {r3-r10} 将r3-r10中的数据存储到r1指向的地址,并更新r1的值 */ #endif /* CONFIG_SKIP_RELOCATE_UBOOT */ /* 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 */ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ /* SVC 堆栈指针sp sp_svc = (_TEXT_BASE - CONFIG_SYS_MALLOC_LEN - CONFIG_SYS_GBL_DATA_SIZE - CONFIG_STACKSIZE_IRQ - CONFIG_STACKSIZE_FIQ - 12) & 0xfffffff8 */ 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 ldr pc, _start_armboot /* start_armboot在arch/arm/lib/boaard.c,开始C语言阶段 */ _start_armboot: .word start_armboot /* ************************************************************************* * * CPU_init_critical registers * * setup important registers * setup memory timing * ************************************************************************* */ #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 */ /* MRC{条件} 协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2 MRC指令用于将协处理器寄存器中的数据传送到ARM 处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1 和协处理器操作码2为协处理器将要执行的操作,目的寄存器为ARM 处理器的寄存器,源寄存器1 和源寄存器2 均为协处理器的寄存器。 MCR指令格式相同,用于将ARM处理器寄存器内容传送到协处理器寄存器。 关于cache MMU的设置细节,参考《ARM9TDMI TechnicalReference Manual》 */ /* * 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 /* * before relocating, we have to setup RAM timing * because memory timing is board-dependend, you will * find a lowlevel_init.S in your board directory. */ mov ip, lr /* C语言和ARM汇编的相互调用必须遵AAPCS,也就是ATPCS(ARM-THUMB procedure call standard)的后续版本, ATPCS的寄存器使用规则: 1.子程序通过寄存器R0~R3来传递参数,这时寄存器可以记作:A0~A3,被调用的子程序在返回前无需恢复寄存器R0~R3的内容。 2.在子程序中,使用R4~R11来保存局部变量,这时寄存器R4~R11可以记作:V1~V8。如果在子程序中使用到V1~V8的某些寄存器,子程序进入时必须保存这些寄存器的值,在返回前必须恢复这些寄存器的值,对于子程序中没有用到的寄存器则不必执行这些操作。在THUMB程序中,通常只能使用寄存器R4~R7来保存局部变量。 3.寄存器R12用作子程序间scratch寄存器,记作ip,在子程序间的连接代码段中常有这种使用规则。 4.寄存器R13用作数据栈指针,记做SP,在子程序中寄存器R13不能用做其他用途。寄存器SP在进入子程序时的值和退出子程序 时的值必须相等. 5.寄存器R14用作连接寄存器,记作lr,它用于保存子程序的返回地址,如果在子程序中保存了返回地址,则R14可用作其它的用途。 6.寄存器R15是程序计数器,记作PC,它不能用作其他用途。 */ bl lowlevel_init /* lowlevel_init定义在 /board/sbc2410x/lowlevel_init.S中,其功能是初始化SDRAM控制器 */ mov lr, ip mov pc, lr #endif /* CONFIG_SKIP_LOWLEVEL_INIT */ /* ************************************************************************* * * 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 stmia sp, {r0 - r12} @ Calling r0-r12 ldr r2, _armboot_start sub r2, r2, #(CONFIG_STACKSIZE) sub r2, r2, #(CONFIG_SYS_MALLOC_LEN) /* set base 2 words into abort stack */ sub r2, r2, #(CONFIG_SYS_GBL_DATA_SIZE+8) ldmia r2, {r2 - r3} @ get pc, cpsr /*r2 r3中存储的是存放在svc栈底的lr和spsr*/ add r0, sp, #S_FRAME_SIZE @ restore sp_SVC /*r0发生异常前svc模式的sp*/ add r5, sp, #S_SP mov r1, lr stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr mov r0, sp /*将sp放在r0中作为参数传给后面的异常处理函数,打印存储在堆栈中的寄存器值*/ .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 /* ldm和stm指令中的^ ^is an optional suffix. You must not use it in User mode or System mode.It has two purposes: ◆ If op is LDM and reglist contains the pc (r15), in addition to the normal multiple register transfer, the SPSR is copied into the CPSR. This is for returning from exception handlers. Use this only from exception modes. ◆ Otherwise, data is transferred into or out of the User mode registers instead of the current mode registers. */ 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 /* return & move spsr_svc into cpsr */ subs pc, lr, #4 .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) /* reserve a couple spots in abort stack */ sub r13, r13, #(CONFIG_SYS_GBL_DATA_SIZE+8) /* r13 = _armboot_start - CONFIG_STACKSIZE - CONFIG_SYS_MALLOC_LEN - CONFIG_SYS_GBL_DATA_SIZE - 8 这里r13指向了堆栈的最低端 */ str lr, [r13] @ save caller lr / spsr mrs lr, spsr str lr, [r13, #4] /*存储异常模式的lr和spsr*/ mov r13, #MODE_SVC @ prepare SVC-Mode @ msr spsr_c, r13 msr spsr, r13 mov lr, pc movs pc, lr @指令尾部带有s后缀并且目标寄存器为pc,cpsr将自动从spsr中恢复 .endm /*返回SVC模式*/ .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 /* IRQ FIQ模式的栈顶sp在运行时计算,在/arch/arm/lib/interrupts.c中的int interrupt_init (void)函数: IRQ_STACK_START = _armboot_start - CONFIG_SYS_MALLOC_LEN - CONFIG_SYS_GBL_DATA_SIZE - 4; FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; */ /* * exception handlers */ .align 5 /* .align 5 以2的5次方对齐,也就是4字节对齐 关于align的文章: http://www.eetop.cn/blog/html/45/11145-1211.html 有关arm汇编中的align */ 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

你可能感兴趣的:(exception,汇编,user,存储,编译器,linker)