您的位置:首页 > 其它

U-boot在开发板上移植过程详解(2)---U-boot实现源码分析(第一阶段)

2012-03-04 18:35 621 查看
前边,我们说了,一般的bootloader都分为两个阶段。我在讲U-boot实现源码分析时,也是按照这连个阶段来分析,如果对这两个阶段不清楚,请看前边的博客。好了,开始今天的主题:U-boot在开发板上移植过程详解(2)---U-boot实现源码分析(start.S分析)

第一阶段:

1)一些基本的硬件初始化工作

u-boot对应的第一阶段代码放在cpu/arm920t/start.S文件中,入口代码如下:

.globl _start
;global声明一个符号可被其它文件引用,相当于声明了一个全局变量,.globl与.global相同

_start: b reset ;b是不带返回的跳转(bl是带返回的跳转),意思是无条件直接跳转到reset标号出执行程序

ldr pc, _undefined_instruction ;ldr相当于mov操作

ldr pc, _software_interrupt

ldr pc, _prefetch_abort

ldr pc, _data_abort

ldr pc, _not_used

ldr pc, _irq

ldr pc, _fiq

;.word伪操作用于分配一段字内存单元(分配的单元都是字对齐的),并用伪操作中的expr初始化。

_undefined_instruction: .word undefined_instruction
;就是在当前地址,即_undefined_instruction 处存放 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

这部分就是异常向量表。当系统上电或复位后,将执行第一条指令,即跳转到标签为reset的代码处执行,具体如下:

reset:
;设置CPU为SVC32管理模式

mrs r0,cpsr ;mrs将状态寄存器cpsr(current program status register)的内容传送至通用寄存器

bic r0,r0,#0x1f ;r0和0x1f(00011111)的反码进行位与,是把 r0后面5位清零

orr r0,r0,#0xd3
;r0和0xd3(11010011)进行位或,最后得到r0=11010011,目的是设置r0的后5位为10011,让ARM进入SVC特权模式

msr cpsr,r0

#if defined(CONFIG_S3C2400)
;关闭看门狗

# define pWTCON 0x15300000 ;看门狗寄存器

# define INTMSK 0x14400008 ;中断屏蔽寄存器

# define CLKDIVN 0x14800014 ;时钟分频寄存器

#elif defined(CONFIG_S3C2410)

# define pWTCON 0x53000000

# define INTMSK 0x4A000008

# define INTSUBMSK 0x4A00001C ;次级中断屏蔽寄存器

# define CLKDIVN 0x4C000014 ;时钟分频寄存器

#endif

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)

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

ldr r0, =CLKDIVN ;设置时钟

mov r1, #3

str r1, [r0]

#endif /* CONFIG_S3C2400 || CONFIG_S3C2410 */

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

bl cpu_init_crit ;跳转并把转移后面紧接的一条指令地址保存到链接寄存器LR(R14)中,以此来完成子程序的调用

#endif

上面的代码将CPU设为管理模式,关闭看门狗,屏蔽中断并设置中断,最后调用cpu_init_crit函数进行cpu的初始化,代码如下:

cpu_init_crit:
;清除指令和数据缓存

mov r0, #0

mcr p15, 0, r0, c7, c7, 0 ;mcr{条件} 协处理器编码, 协处理器操作码1, 目的寄存器, 源寄存器1, 源寄存器2, 协处理器操作码2

mcr p15, 0, r0, c8, c7, 0
;mcr指令用于将ARM处理器寄存器的数据传送到协处理器寄存器中,若协处理器不能成功完成操作,则

;产生未定义指令异常。其中协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,目的寄存器

;为ARM处理器的寄存器,源寄存器1和源寄存器2均为协处理器的寄存器。

mrc p15, 0, r0, c1, c0, 0
;mrc 协处理器寄存器到ARM处理器寄存器的数据传送指令

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

mov ip, lr
;设置SDRAM控制器,与具体的目标板相关

bl lowlevel_init

mov lr, ip

mov pc, lr

在这个函数中做了一下工作:清除指令与数据缓存,禁用MMU与数据指令缓存,最后调用lowlevel_init函数设置SDRAM控制器。该函数的实现与具体的目标板有关的。

2)准备RAM空间

所谓准备RAM空间,就是初始化内存芯片,使它可用。 在board/smdk2410/lowlevel.init.S就是这个作用,要注意这时的代码,数据都保存在NOR Flash上,内存中还没有,所以读取数据时要变换地址,如下:

_TEXT_BASE:

.word TEXT_BASE

.globl lowlevel_init

lowlevel_init:

;现在起三行进行地址变化,因为这时候内存中还没有数据,不能使用连接程序时确定的地址来读取数据

ldr r0, =SMRDATA ;SMBRDATA表示这13个寄存器的值存放在开始地址(连接地址),值为0x33F8XXXX,处于内存中

ldr r1, _TEXT_BASE ;获得代码段的起始地址(_TEXT_BASE=0X33F80000)

sub r0, r0, r1
;将r0和r1相减,这就是13个寄存器值在Nor Flash上存放的开始地址

ldr r1, =BWSCON ;Bus Width Status Controller

add r2, r0, #13*4

0:

ldr r3, [r0], #4

str r3, [r1], #4

cmp r2, r0

bne 0b

mov pc, lr

.ltorg

SMRDATA:
;13个寄存器的值

.word … …

.word … …

这里做完以后,就要将整个U-boot的代码都复制到SDRAM中,这些又都在start.S中实现,如下:

relocate:
;将u-boot复制到RAM中

adr r0, _start
;r0:当前代码的开始地址

ldr r1, _TEXT_BASE ;r1:代码段的连接地址

cmp r0, r1
;测试现在是在Flash中还是在RAM中

beq stack_setup ;如果已经在RAM中(这通常是调试时直接下载到RAM中),则不需要复制

ldr r2, _armboot_start
;_armboot_start在前边已经定义,是第一条指令的运行地址

ldr r3, _bss_start ;在连接脚本u-boot.lds中定义,是代码的结束地址

sub r2, r3, r2
;r2=代码段的长度

add r2, r0, r2
;r2=NOR Flash上代码段的结束地址

copy_loop:

ldmia r0!, {r3-r10} ;从地址[r0]处获得数据

stmia r1!, {r3-r10} ;复制到地址[r1]处

cmp r0, r2
;复制是否复制完毕

ble copy_loop ;没复制完,则继续

接下来,就要设置栈,栈的设置灵活性很大,只要让sp寄存器指向一段没有使用的内存即可。

stack_setup:

ldr r0, _TEXT_BASE
;_TEXT_BASE为代码段的开始地址,值为0x33F80000

sub r0, r0, #CFG_MALLOC_LEN
;代码段下面,留出一段内存以实现malloc

sub r0, r0, #CFG_GBL_DATA_SIZE
;再留出一段内存,存一些全局参数

#ifdef CONFIG_USE_IRQ

sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
;IRQ,FIQ模式的栈

#endif

sub sp, r0, #12 ;最后,留下12字节的内存给abort异常,往下的内存就都是栈了

3)跳转到第二阶段代码的C入口点

在跳转之前,还要清除BSS段(初始值为0,无初始值的全局变量,静态变量放在BSS段),代码如下:

clear_bss:

ldr r0, _bss_start ;BSS段的开始地址,它的值在连接脚本U-boot.lds中确定

ldr r1, _bss_end ;BSS段的结束地址,它的值也在连接脚本u-boot.lds确定

mov r2, #0x00000000

clbss_l:str r2, [r0]
;往BSS段中写入0值

add r0, r0, #4

cmp r0, r1

ble clbss_l

现在,c函数的运行环境已经完全准备好了,通过如下命令直接跳转(这之后,程序才在内存中执行),它将调用lib_arm/board.c中的start_armboot函数(这是一个C语言函数),这是第二阶段的入口点:

ldr pc, _start_armboot

_start_armboot: .word start_armboot

在第二阶段代码中,将进行更多的初始化工作,如对各种设备和接口的初始化,串口终端的初始化等。如果没有设置自动运行,则最终将进入一个循环,在循环内读取用户输入的命令并执行,这些会在下一节详细介绍。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: