uboot第一阶段代码分析
来源:互联网 发布:apache tomcat java8 编辑:程序博客网 时间:2024/05/23 16:51
从前一篇u-boot.lds文件分析知,整个代码段放在最前面的是start.o,而入口函数是_start,那么我们就来分析下start.S文件:
- /*
- *************************************************************************
- *
- * Jump vector table as in table 3.1 in [1]
- *
- *************************************************************************
- */
- .globl _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
- .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是用来填充位置计数器越过的地方。
- _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
- #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
uboot存储器映射的定义,将uboot.lds 中的值取过来。
由前面的链接过程分析知,uboot被链接到TEXT_BASE地址处,意思就是uboot运行的基地址为TEXT_BASE,即在内存的33F80000地址处开始运行。
接下来就是正式的开始代码了:
- /*
- * 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
- bl coloured_LED_init
- bl red_LED_on
要说明的是bl 跳转到c语言函数去了,bl指令在运行时,能够自动的保存程序的返回地址,汇编和c混合编程遵循的是AAPCS准则。设置为系统模式。
- #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
AT91的不予理会
- #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
- # 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
进行SDRAM的初始化,查看下cpu_init_crit的代码如下:
- /*
- *************************************************************************
- *
- * 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 */
- /*
- * 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
- bl lowlevel_init
- mov lr, ip
- mov pc, lr
- #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
主要是关闭cache和无效mmu,这里讲解下mrc指令,mrc指令是将协处理器寄存器中的内容传输到ARM通用寄存器中,而mcr指令则完成了相反的操作。 要弄明白这里的具体意思,请参照杜春雷ARM体系结构与编程。
其中又跳转到了 lowlevel_init函数,这个函数在board/samsung/smdk2410/lowlevel_init.S中:
- _TEXT_BASE:
- .word TEXT_BASE
- .globl lowlevel_init
- lowlevel_init:
- /* memory control configuration */
- /* make r0 relative the current location so that it */
- /* reads SMRDATA out of FLASH rather than memory ! */
- ldr r0, =SMRDATA
- ldr r1, _TEXT_BASE
- sub r0, r0, r1
- //要记住此时你的程序还在norflash上
//这三行用于地址变换,因为这个时候SDRAM中还没有数据,不能那个使用链接程序时确定的地址来读取数据。
//SMRDATA表示将要写入SDRAM中13个寄存器的值存放的开始地址,值为0x33F8XXXX,处于内存中
//ldr r1, _TEXT_BASE获得代码段的起始地址,即config.mk中定义的0X33F80000
//将两者0x33F8XXXX和0X33F80000相减,就得到了将要写入SDRAM中13个寄存器的值在NORflash上存放的开始地址,即r0的值就是将要写入SDRAM中13个寄存器的值在NORflash上存放的开始地址
- ldr r1, =BWSCON /* Bus Width Status Controller */
- add r2, r0, #13*4
- //r1为寄存器的地址,r2为最后一个寄存器的地址
- 0:
- ldr r3, [r0], #4 //将r0地址上的值付给r3,即取出要付给SDRAM寄存器的值
- str r3, [r1], #4//将r3的值付给r1地址指向的内存,即将值付给了SDRAM寄存器
- cmp r2, r0 //比较有没有赋值完毕?
- bne 0b
- /* everything is fine now */
- mov pc, lr //返回,已经设好SDRAM寄存器
- .ltorg
- /* the literal pools origin */
- SMRDATA: //将要写入SDRAM中13个寄存器的值
- .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
- .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
- .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
- .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
- .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
- .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
- .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
- .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
- .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
- .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
- .word 0x32
- .word 0x30
- .word 0x30
接着往下看,接下来将要把整个uboot的代码赋值到SDRAM中,代码如下:
- #ifndef CONFIG_SKIP_RELOCATE_UBOOT
- relocate: /* relocate U-Boot to RAM */
- adr r0, _start /* r0 <- current position of code */
- //当前代码的位置,若在flash中,r0=0,若在SDRAM中,则r0=TEXT_BASE
- ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
- //代码段在SDRAM中存放的位置,无论是在flash还是在SDRAM中,r1都等于TEXT_BASE
- cmp r0, r1 /* don't reloc during debug */
- //如果已经在SDRAM中,则就不需要将uboot进行搬移了,直接进行堆栈的设置
- beq stack_setup
- //否则进行uboot的搬移
- ldr r2, _armboot_start
- //第一条指令的运行地址
- ldr r3, _bss_start
- //代码段的结束地址,在uboot.lds中定义
- sub r2, r3, r2 /* r2 <- size of armboot */
- //r2=代码段的长度
- add r2, r0, r2 /* r2 <- source end address */
- //r2=norflash上代码段的结束地址
- copy_loop:
- ldmiar0!, {r3-r10}/* copy from source address [r0] */
- //从r0地址处获得数据,即从norflash中获得数据
- stmiar1!, {r3-r10}/* copy to target address [r1] */
- //复制到r1地址处即SDRAM的TEXT_BASE处
- cmpr0, r2/* until source end addreee [r2] */
- //复制完否?
- blecopy_loop
- //没有,则继续复制
- #endif /* CONFIG_SKIP_RELOCATE_UBOOT */
- /* Set up the stack */
- stack_setup:
- ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
- //TEXT_BASE上面是刚刚复制过来的代码段
- sub r0, r0, #CONFIG_SYS_MALLOC_LEN /* malloc area */
- //代码段下面留出一段内存用来实现malloc
- sub r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* bdinfo */
- //再留出一段内存用来存全局参数
- #ifdef CONFIG_USE_IRQ
- sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
- #endif
- //IRQ,FIQ的堆栈
- sub sp, r0, #12 /* leave 3 words for abort-stack */
- //留出12个字节给abort异常
- bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
- //往下的内存就是栈了
这时候的SDRAM和norflash的布局可以参看韦东山的书260页的图,很直观。
//bss段(初始值为0,无初始值的全局变量,静态变量放在bss段)全部清零
- 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函数的运行环境已经全部准备好,通过下面的命令跳直接转到(这之后,程序才在内存中执行),它将调用board.c中的start_armboot函数。
- ldr pc, _start_armboot
- _start_armboot: .word start_armboot
程序的运行离不开栈,程序运行时所需的临时变量,数据,函数间调用的参数的传递都需要栈的支持,所以在执行C语言函数时,首先要把栈设置好,同样,为了程序正常的执行,我们也要预先设置好外设和时钟例如时钟设备可以产生正确的国定的频率驱动整个cpu运行,中断处理程序能够使得cpu能够处理非顺序的,突发的执行逻辑,这就是uboot第一阶段为什么要设置堆栈,时钟,初始化SDRAM,关闭看门狗的原因,为了以后程序的正常运行,这些是必要的条件。
好了,第一阶段的分析,到此结束了。
主要参考:
李无言 一步步写嵌入式操作系统
韦东山 嵌入式linux应用开发完全手册
0 0
- uboot第一阶段代码分析
- uboot第一阶段代码概要分析
- uboot第一阶段详细分析
- uboot第一阶段详细分析
- uboot第一阶段详细分析
- uboot第一阶段详细分析
- uboot第一阶段详细分析
- uboot第一阶段详细分析
- uboot第一阶段详细分析
- uboot分析第一阶段
- UBOOT第一阶段分析
- uboot启动第一阶段分析
- 五、uboot第一阶段代码分析 (2011-03-11 09:51)
- UBoot启动代码第一阶段流程
- uboot第一阶段代码(3)
- uboot启动第一阶段详细分析
- uboot分析第一阶段学习笔记
- uboot分析之第一阶段源码
- 【Android】Android适配器之ArrayAdapter、SimpleAdapter和BaseAdapter的简单用法
- PS中磁性套索工具实现算法
- 中文TWRP recovery TTF字体版代码共享
- Chrome内核解析 -- 背景篇:Chrome, Chromium, WebKit, WebKit2, Blink
- 必备C语言参考手册
- uboot第一阶段代码分析
- linux外设权限:hokuyo权限设置(udev文件) ubuntu USB外设重映射思路
- Linux常用命令(二十八) - diff
- 2014年工作中遇到的20个问题:120-140
- UML中的图
- SIM900A超长短信发送方法
- 六、uboot2010.09移植
- 六、uboot第二阶段代码简要分析
- [小技巧] gnome terminal 里查找终端输出的内容