S3C2440 Boot Loader引导代码功能简述

引用请注明出处:http://blog.csdn.net/int64ago/article/details/7028797

【前言】开始学习ARM的时候,基本上都要从裸机编程开始。为了减低入门的门槛,很多时候只要修改模板里的主函数main.c,可是,久而久之,就会产生些疑问,问什么下载了这些C代码编译链接生成出来的BIN就能在ARM上跑了呢?原因就在于,有几个文件已经不声不响的帮我们提前干了很多的事,而这些事C语言是干不了的,只能由汇编完成,美其名曰:ARM汇编引导代码。其实不光“裸奔”需要,Boot Loader也同样需要。那么到底这些汇编帮我干了些什么呢?笔者就结合S3C2440的Boot Loader引导代码简单分析整理下。


【一】变量及相关宏定义

开始首先用GET(相当于C语言里的#include)伪指令包含进来了三个头文件option.inc、memcfg.inc、2440addr.inc,其中option.inc里定义芯片相关的配置,memcfg.inc里定义存储器配置,2440addr.inc里定义了寄存器符号。

USERMODE    EQU 0x10

FIQMODE      EQU0x11

IRQMODE      EQU0x12

SVCMODE      EQU0x13

ABORTMODE   EQU 0x17

UNDEFMODE   EQU 0x1b

MODEMASK    EQU 0x1f 

NOINT         EQU0xc0

上面的几行进行了一些处理器模式的定义,下面定义了一些各模式下的常量,等到了【四】这一块再详细说。

UserStack   EQU(_STACK_BASEADDRESS-0x3800);0x33ff4800 ~  

SVCStack    EQU(_STACK_BASEADDRESS-0x2800);0x33ff5800 ~

UndefStack  EQU (_STACK_BASEADDRESS-0x2400);0x33ff5c00 ~

AbortStack  EQU (_STACK_BASEADDRESS-0x2000);0x33ff6000 ~

IRQStack    EQU(_STACK_BASEADDRESS-0x1000);0x33ff7000 ~

FIQStack    EQU(_STACK_BASEADDRESS-0x0);0x33ff8000 ~

注:_STACK_BASEADDRESS在option.inc中有相关定义

接下来的定义要到最后才能用到,THUMBCODE作为全局变量,其实就是一个指示的作用,在跳转到main前进行模式的切换。

下面的宏定义可能就不太好理解了,这个是一个中断跳转的工具,到【二】这再解释。

MACRO

$HandlerLabel HANDLER $HandleLabel

$HandlerLabel        ;标号

sub sp,sp,#4          ;(1)减少sp(用于存放转跳地址)

stmfd sp!,{r0}         ;(2)把工作寄存器压入栈

ldr     r0,=$HandleLabel   ;将HandleXXX的址址放入r0

ldr     r0,[r0]              ;把HandleXXX所指向的内容(也就是中断程序的入口)放入r0

str     r0,[sp,#4]            ;(3)把中断服务程序(ISR)压入栈

ldmfd   sp!,{r0,pc} ;(4)用出栈方式恢复r0原值和为pc设定新值(即完成了到ISR的转跳)

MEND

还有一些,留到后面用到的时候再说。

 

【二】中断向量表以及其相关跳转设置

在上述定义完成之后就算真正意义来到了函数的入口处,这里处理的比较复杂,会有大小

端的处理,因为对我们理解引导代码没多少作用,暂且将其简化处理掉。省略这些之后,其实入口就是这几行代码:

b ResetHandler   ;上电复位中断;0x00

b HandlerUndef   ;handlerfor Undefined mode  ;0x04

b HandlerSWI     ;handler for SWI interrupt  ;0x08

b HandlerPabort ;handler for PAbort    ;0x0c

b HandlerDabort ;handler for DAbort    ;0x10

b .                ;其实是个死循环   ;0x14

b HandlerIRQ     ;handler for IRQ interrupt  ;0x18

b HandlerFIQ      ;handler for FIQ interrupt  ;0x1c

这就是我们有名的中断向量表!中断向量表必须位于启动代码的开始部分连续8*4字节的连续空间,它是用户程序与启动代码之间以及启动代码的各部分之间联系的纽带。它由一个一个的跳转函数组成,它就象一个普通的散转函数,只不过散转的过程中有硬件机制参与,当系统发生异常时,ARM 处理器会通过硬件机制强制将PC 指针指向中断向量表中对应的异常跳转函数存储的地址,然后程序会跳转到相应的中断服务程序去执行。因为我们开机的第一个中断是上电复位,所以进来之后首先是跳转到ResetHandler中断函数里去进行一些必要的系统设置,故在0x00处就是bResetHandler。

对于ARM的中断,其实有两种模式(可通过相关寄存器设置):向量中断模式和普通中断模式。简单的区分这两个就是:对于向量中断模式,当中断发生时,CPU会跳转到向量表中相应中断类型的表项,直接把中断服务例程的起始地址送到PC,这个有个优点就是速度快;对于普通中断模式,在跳转到中断向量表之后还要进行一次跳转查询,最红由返回ISR的最红中断处理函数的地址给PC,现在就可以说说【一】中宏定义$HandlerLabel HANDLER $HandleLabel的作用了。这个宏是用于第一次查表过程的实现中断向量的重定向,在_ISR_STARTADDRESS里定义的第一级中断向量表是采用型如Handle###的方式的,而在程序的开始处采用的是b Handler###的方式,在这里Handler###就是通过HANDLER这个宏和Handle###建立联系的.所以在后面其实还有一段初始化程序作为宏展开。

HandlerFIQ      HANDLER HandleFIQ

HandlerIRQ      HANDLER HandleIRQ

HandlerUndef    HANDLER HandleUndef

HandlerSWI      HANDLER HandleSWI

HandlerDabort   HANDLER HandleDabort

HandlerPabort   HANDLER HandlePabort

这种方式的优点就是正真定义的向量数据在内存空间里,而不是在ENTRY处的ROM空间里,这样就可以在程序里灵活的改动向量的数据了.这段程序用于把中断服务程序的首地址装载到pc中,也可以称之为“加载程序”。

接着跳转那一块继续说,因为外部中断几乎都是通过IRQ引入的(其实FIQ理论上也可以,但是在linux几乎用不到),于是便跳到了HandleIRQ,但是此时HandleIRQ又是多少呢,在程序的下面还有一段也必须拿上来说:

ldr r0,=HandleIRQ        ;This routine is needed

ldr r1,=IsrIRQ             ;if there isn't 'subs pc,lr,#4' at 0x18, 0x1c

str r1,[r0]

可见,HandleIRQ和IsrIRQ其实等价了!于是可以把IsrIRQ处的处理函数拿来分析一下:

IsrIRQ

sub sp,sp,#4            ;给PC寄存器保留 reserved for PC

stmfd sp!,{r8-r9}              ;把r8-r9压入栈

;把INTOFFSET的地址装入r9, INTOFFSET是一个内部的寄存器,存着中断的偏移

ldr r9,=INTOFFSET

ldr r9,[r9]                  ;I_ISR

ldr r8,=HandleEINT0    ;这就是我们第二个中断向量表的入口的,先装入r8

add r8,r8,r9,lsl #2  ;地址对齐,每个中断向量占4个字节,即isr = IvectTable + Offeset * 4

ldr r8,[r8]    ;装入中断服务程序的入口

str r8,[sp,#8]   ;把入口也入栈,准备用旧招

ldmfd sp!,{r8-r9,pc} ;弹出栈,顺便把r8弹出到PC了,跳转成功!

 

【三】初始化硬件

终于可以开始对硬件真正的干涉了,ARM要能形成一个可以供C语言工作的环境,还要

要干下面的几件事:

1、 关看门狗,看门狗是用来解决软件崩溃的,这里不需要

ldr r0,=WTCON     

ldr r1,=0x0        

str r1,[r0]

2、关中断,引导代码里不需要处理中断事件,除了上电复位中断其它都交给C的主函数完成

ldr r0,=INTMSK

ldr r1,=0xffffffff 

str r1,[r0]

3、关子中断,同上

ldr r0,=INTSUBMSK

ldr r1,=0x7fff  

str r1,[r0]

4、减少PLL的lock time,调整LOCKTIME寄存器

ldr r0,=LOCKTIME

ldr r1,=0xffffff

str r1,[r0]

5、设定PLL,这个直接关系到板子的快慢,不过也不是越快越好,除了要考虑功耗外还要满足下面的公式:

Fpllo=(m*Fin)/(p*2^s)

m=MDIV+8,p=PDIV+2,s=SDIV(1<=P<=62, 1<=M<=248)

Fpllo必须大于200Mhz小于600Mhz

Fpllo*2^s必须小于1.2GHz

PLLCON设定中的M_DIV P_DIV S_DIV是取自option.inc中的

6、设置系统存储寄存器,其中SMRDATA在程序段的后面有详细描述,这里知道作用就好

adrl r0, SMRDATA 

ldr r1,=BWSCON  ;BWSCON Address

add r2, r0, #52  ;SMRDATA数据的结束地址,共有52字节的数据

0

ldr r3, [r0], #4

str r3, [r1], #4

cmp r2, r0

bne %B0  ;%表示搜索,B表示反向-back(F表示向前-forward),0为局部标号(0~99)

 

【四】 初始化堆栈

ARM 有7 种模式,用户模式,快速中断模式,中断模式,管理模式,中止模式,未定义模式和系统模式。系统堆栈的初始化主要是给各个处理器模式分配堆栈空间。堆栈是为中断或程序跳转服务的,当发生中断或程序跳转时,需要将当前处理器的状态及一些参数保持在堆栈中,当中断处理完毕以后或程序执行完后返回时,再将堆栈保存的现场数据进行恢复,以保证原来的程序正确运行。在【一】中已经提到了一些与堆栈有关的变量定义。可以这样简单说,堆栈的初始化分为两个步骤:1、指定堆栈的位置和大小,这些在【一】中已经完成了;2、将各个模式下的堆栈指针指向相应的栈,下面做的就是这个工作。

InitStacks

mrs r0,cpsr

bic r0,r0,#MODEMASK

orr r1,r0,#UNDEFMODE|NOINT

msr cpsr_cxsf,r1  ;UndefMode

ldr sp,=UndefStack  ; UndefStack=0x33FF_5C00

orr r1,r0,#ABORTMODE|NOINT

msr cpsr_cxsf,r1  ;AbortMode

ldr sp,=AbortStack ; AbortStack=0x33FF_6000

orr r1,r0,#IRQMODE|NOINT

msr cpsr_cxsf,r1  ;IRQMode

ldr sp,=IRQStack  ; IRQStack=0x33FF_7000

orr r1,r0,#FIQMODE|NOINT

msr cpsr_cxsf,r1 ;FIQMode

ldr sp,=FIQStack ; FIQStack=0x33FF_8000

bic r0,r0,#MODEMASK|NOINT

orr r1,r0,#SVCMODE

msr cpsr_cxsf,r1 ;SVCMode

ldr sp,=SVCStack  ; SVCStack=0x33FF_5800

注:仔细看看发现没有初始化user模式下的堆栈,为什么呢?很明显嘛,你一开始就运行在了user模式下了!

 

 

【五】C主函数接管前的数据搬移及入口设定

其实一直还有个东西没说,这个在进入代码段前就定义了,我提到了,后面用到会详细说,

现在是时候了。在【一】时用IMPORT伪指令引入了|Image$$RO$$Base|  |Image$$RO$$Limit|...这些变量是通过ADS、RVDS,MDK等工具的工程设置里面设定的RO Base和RW Base设定的,这个应该有印象,可能很多人感觉这个没用,其实很有用呢!那为什么要引入这玩意呢,最简单的用处是可以根据它们拷贝自己,这些变量是编译器生成的。

RO,RW, ZI这三个段都保存在Flash中,但RW,ZI在Flash中的地址肯定不是程序运行时变量所存储的位置,因此我们的程序在初始化时应该把Flash中的RW,ZI拷贝到RAM的对应位置。一般情况下,我们可以利用编译器替我们实现这个操作。比如我们跳转到main()时,使用 b  __Main,编译器就会在__Main和Main之间插入一段汇编代码,来替我们完成RW,ZI段的初始化。 如果我们使用b  Main, 那么初始化工作要我们自己做。编译器会生成如下变量告诉我们RO,RW,ZI三个段应该位于什么位置,但是它并没有告诉我们RW,ZI在Flash中存储在什么位置,实际上RW,ZI在Flash中的位置就紧接着RO存储。

IMPORT  |Image$$RO$$Base| ; Base of ROM code

IMPORT  |Image$$RO$$Limit|  ; End of ROM code (=start of ROM data)

IMPORT  |Image$$RW$$Base|   ; Baseof RAM to initialise

IMPORT  |Image$$ZI$$Base|   ; Base and limit of area

IMPORT  |Image$$ZI$$Limit| ; to zero initialize

在程序的最后,通过下面的代码就可以进入main()了。

[ :LNOT:THUMBCODE;ifthumbcode={false} bl main   L代表logic变量

     bl Main       ;Don't use main() because ......

     b .           ;注意小圆点         

 ]

 [ THUMBCODE        ;for start-up code for Thumb mode

     orr lr,pc,#1

     bx lr

     CODE16

     bl Main        ;Don't use main() because ......

     b .          ;注意小圆点

     CODE32

 ]

现在,就可以顺便回顾下【一】中提到的THUMBCODE了,这不就是一个指示的作用吗?!

 

【后记】总的来说,ARM光初始化都要这样折腾,如果这个都折腾会了,后面的就慢慢来吧!










  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值