uboot

从前一篇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   //读取cpsr寄存器
bic r0, r0, #0x1f   //与非
orr r0, r0, #0xd3  //或
msr cpsr, r0  //写入CPSR寄存器 
bl coloured_LED_init   //跳转至C语言函数中执行
bl red_LED_on   //
要说明的是bl 跳转到c语言函数去了,bl指令在运行时,能够自动的保存程序的返回地址,汇编和c混合编程遵循的是AAPCS准则。设置为系统模式。
#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/
ldr r0, =_start   //装载_start到r0寄存器
ldr r1, =0x0     //装载0 到 r1寄存器
mov r2, #16    //把16写入r2寄存器
copyex:
subs r2, r2, #1 //r2 -1 ,s表示把结果写入CPSR
ldr r3, [r0], #4  //从r0寄存器代表的地址中取出值
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  //将看门狗地址写入r0寄存器
mov r1, #0x0  //将0写入r1寄存器
str r1, [r0]  // 将r1中的值存到r0对应的地址空间上
/*
* mask all IRQs by setting all bits in the INTMR - default 关中断
*/
mov r1, #0xffffffff   //将0xffffffff   写入r1寄存器
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 将r0中的数据,传到协处理器P15的C7中,C7寄存器代表cache的控制寄存器*/
    mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB 将r0寄存器的数据,传到协处理器P5的c8中,c8对应TLB寄存器。*/


    /*
     * disable MMU stuff and caches 关闭MMU和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:
ldmia r0!, {r3-r10} /* copy from source address [r0]    */
//从r0地址处获得数据,即从norflash中获得数据
stmia r1!, {r3-r10} /* copy to   target address [r1]    */
//复制到r1地址处即SDRAM的TEXT_BASE处
cmp r0, r2 /* until source end addreee [r2]    */
//复制完否?
ble copy_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,关闭看门狗的原因,为了以后程序的正常运行,这些是必要的条件。


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值