Chinaunix首页 | 论坛 | 博客
  • 博客访问: 169945
  • 博文数量: 63
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 181
  • 用 户 组: 普通用户
  • 注册时间: 2016-02-25 15:50
文章分类
文章存档

2020年(1)

2016年(62)

我的朋友

分类: 嵌入式

2016-03-11 10:17:03

原文地址:ARM启动程序Bootloader设计 作者:njustysq

一 基本原理
1.1 可执行文件组成及内存映射
1.1.1 可执行文件的组成
   在ADS下,可执行文件有两种,一种是.axf文件,带有调试信息,可供AXD调试工具使用.另一种是.bin文件,可执行的二进制代码文件。我们重点是讲描.bin文件的组成。我们把可执行文件分为两种情况:分别为存放态和运行态。
1. 存放态
    存放态是指可执行文件通过fromelf产生后,在存储介质(flash或磁盘)上的分布. 此时可执行文件一
般由两部分组成:分别是代码段和数据段。代码段又分为可执行代码段(.text)和只读数据段(.rodata),
数据段又分为初始化数据段(.data)和未初始化数据段(.bss)。可执行文件的存放态如下:
+-------------+-----------
| .bss |
+-------------+-- 数据段
| .data |
+-------------+-----------
| .rodata |
|_____________| 代码段
| .text |
+-------------+-----------
2. 运行态
可执行文件通过装载过程, 搬入到RAM中运行, 这时候可执行文件就变成运行态。在ADS下对可执行代
码各段有另一个名称:
| ... |
+-------------+-----------
| .bss | ZI 段
+-------------+-- 数据段
| .data | RW 段
+-------------+-----------
| .rodata |
|_____________| 代码段(RO 段)
| .text |
+-------------+-----------
| ... |
(装载前)
当可执行文件装载后, 在RAM中的分布如下:
| ... |
+-------------+-- ZI段结束地址
| ZI 段 |
+-------------+-- ZI段起始地址
| 保留区2 |
+-------------+-- RW段结束地址
| RW 段 |
+-------------+-- RW段起始地址
| 保留区1 |
+-------------+-- RO段结束地址
| RO 段 |
+-------------+-- RO段起始地址
| ... |
(装载后)
所以装载过程必须完成把执行文件的各个段从存储介质上搬到RAM指定的位置。而这个装载过程由谁来完
成呢?由我们的启动程序来完成.
 
1.1.2 装载过程
    在ADS中,可以通过两种方式来指定可执行代码各段在RAM中的位置,一个是用armlink来指定,一种是
用scatter文件来指定.RAM区的起始地址:0x30000000.
1. armlink指定代码段地址
我们通常的代码,只用指定两个段开始地址, RO段的起始地址和RW段的起始地址, ZI段紧接在RW段之
后.示例见该部分的1.1.3.
2. scatter指定代码段地址
我们也可以通过scatter文件指定可执行文件各段的详细地址. Scatter文件如下:
MYLOADER 0x30000000
;MYLOADER: 为可执行文件的名称, 可自定义
;0x3000000: 起始地址
{
RO 0x30000000
;RO 只读代码段的名称
;0x30000000: 只读代码段的起始地址
{
init.o (Init, +First)
; Init代码段为可执行文件的第一部分.
* (+RO) ;所有其它的代码段和只读数据段放在该部分
}
RW +0
;RW: RW段的名称
;+0: 表示RW段紧接着RO段
{
* (+RW) ;所有RW段放在该部分
}
ZI +0
;ZI: ZI段的名称
;+0: 表示ZI段紧接着RW段
{
*(+ZI) ;所有ZI段放在该部分
}
}
3. ADS产生的各代码段宏
|Image$$RO$$Base| /* RO代码段起始地址 */
|Image$$RO$$Limit| /* RO 代码段结束地址 */
|Image$$RW$$Base| /* RW代码段起始地址 */
|Image$$RW$$Limit| /* RW 代码段结束地址 */
|Image$$ZI$$Base| /* ZI 代码段起始地址 */
|Image$$ZI$$Limit| /* ZI 代码段结束地址 */
注意:在两个$$之间的名称, 与scatter中指定的段的名称相同.
4. 装载过程说明
当从NorFlash启动时, 要把flash芯片的首地址映射到0x00000000位置, 系统启动后, 启动程序本身把自己从
flash中搬到RAM中运行. 搬移后的各段起始地址, 由以上宏来确定.
当从NandFlash启动时, S3C2410会自动把前NandFlash的前4k搬到S3C2410的内部RAM中,并把内部
RAM的首地址设为0x00000000,CPU从0x00000000开始执行. 所以, 在nandFlash的前4k程序中,必须包含从
NandFlash把BootLoader的其余部分装入RAM的程序.
 
1.1.3 启动过程的汇编部分
当系统启动时, ARM CPU会跳到0x00000000去执行。一般BootLoader都包括如下几个部分:
1. 建立中断向量异常表
2. 显示的切换到SVC且32指令模式
3. 关闭S3C2410的内部看门狗
4. 禁止所有的中断
5. 配置系统时钟频率和总线频率
6. 设置内存区的控制寄存器
7. 初始化中断
8. 安装中断向表量
9. 把可执行文件的各个段搬到运行态的各个位置
10. 跳到C代码部分执行
 
1.1.4 启动过程的C部分
1. 初始化MMU
2.初始化外部端口
3. 中断处理程序表初始化
4. 串口初始化
5. 其它部分初始化(可选)
6. 主程序循环
 
二 启动源码分析
2.1 第一阶段启动代码
    下面是一个基于S3C2410芯片的嵌入式系统引导(启动)程序Startup.s文件中的汇编源程序。通过对这段程序的分析,巩固所学的汇编指令知识,并学习嵌入式系统引导程序的编写方法。
;Startup Code for S3C2410 : Startup.s
;下面指令包含2410addr.s文件,该文件中定义了S3C2410内部寄存器地址对应的变量。
GET 2410addr.s          
; 某些ARM920T CPSR寄存器位的定义,定义了部分常量。
USERMODE      EQU       0x10
FIQMODE       EQU       0x11
IRQMODE       EQU       0x12
SVCMODE       EQU       0x13
ABORTMODE     EQU       0x17
UNDEFMODE     EQU       0x1b
MODEMASK      EQU       0x1f
NOINT         EQU       0xc0
I_Bit          *        0x80
F_Bit          *        0x40
; MMU寄存器定义。
CtrlMMU                    *     1
CtrlAlign                  *     2
CtrlCache                  *     4
CtrlWBuff                  *     8
CtrlBigEnd                 *     128
CtrlSystem                 *     256
CtrlROM                    *     512
TLB_L0_INIT                *     0x0C02
 
;AREA指示汇编器汇编一段新的代码,为保证下面的代码为起始代码,应在ARM连接器的layout选项中指明Startup.o(Init) (如图1-33所示),或用scatter格式的描述性文件说明。
AREA     Init,CODE,READONLY
;IMPORT提供汇编器在当前汇编中未曾定义的符号名。
                   IMPORT __use_no_semihosting_swi
                   IMPORT Enter_UNDEF
                   IMPORT Enter_SWI
                   IMPORT Enter_PABORT
                   IMPORT Enter_DABORT
                   IMPORT Enter_FIQ
;下面ENTRY指明了程序的入口,在应用程序中有且只有一个程序入口。
                   ENTRY
;下面是异常向量表,第一条语句是复位异常对应的跳转指令。
                   b     ColdReset              ;复位
                   b     Enter_UNDEF            ;未定义指令错误
                   b     Enter_SWI              ;软件中断
                   b     Enter_PABORT           ;预取指令错误
                   b     Enter_DABORT           ;数据存取错误
                   b     .                      ;一个保留的中断向量
                   b     IRQ_Handler            ;IRQHandler
                   b     Enter_FIQ              ;FIQHandler
;deal with IRQ interrupt
                   EXPORT IRQ_Handler
IRQ_Handler
                   IMPORT ISR_IrqHandler
           STMFD  sp!, {r0-r12, lr}
                   BL   ISR_IrqHandler
                   LDMFD  sp!, {r0-r12, lr}
                   SUBS      pc, lr,      #4
;系统上电或复位后跳转到此处开始进行运行。   
                   EXPORT ColdReset
ColdReset
;关看门狗定时器
            ldr   r0,=WTCON        
                   ldr   r1,=0x0         
                   str   r1,[r0]
;关所有中断
                   ldr   r0,=INTMSK
                   ldr   r1,=0xffffffff        
                   str   r1,[r0]                  
                   ldr   r0,=INTSUBMSK
                   ldr   r1,=0x7ff                     ;all sub interrupt disable
                   str   r1,[r0]
;初始化堆栈
                   bl     InitStacks                     ;Stack Setup for each MODE
;复制excption table到SRAM0x0地址处
                   IMPORT |Load$$EXCEPTION_EXEC$$Base|
                   IMPORT |Image$$EXCEPTION_EXEC$$Base|
                   IMPORT |Image$$EXCEPTION_EXEC$$Length|                   
                   ldr   r0,   =|Load$$EXCEPTION_EXEC$$Base|         ;源数据
                   ldr   r1,   =|Image$$EXCEPTION_EXEC$$Base| ;目的地址处
                   ldr   r2,   =|Image$$EXCEPTION_EXEC$$Length|
exception_cploop
                   sub  r2,   r2,   #4
                   ldmia       r0!,  {r3}
                   stmia       r1!,  {r3}
                   cmp r2, #0
bge exception_cploop
;下面调转到C语言的主函数处
                   IMPORT __main
                   BL   __main               ;通常不能用main()作为主函数名
                   B     .
;下面是初始化堆栈的子函数
      IMPORT UserStack
                   IMPORT SVCStack
                   IMPORT UndefStack
            IMPORT IRQStack
            IMPORT AbortStack
      IMPORT FIQStack
     InitStacks
                   mrs  r0,cpsr
                   bic   r0,r0,#MODEMASK
                   orr   r1,r0,#UNDEFMODE|NOINT
                   msr  cpsr_cxsf,r1          ;UndefMode
                   ldr   sp,=UndefStack
                   orr   r1,r0,#ABORTMODE|NOINT
                   msr  cpsr_cxsf,r1          ;AbortMode
                   ldr   sp,=AbortStack
                   orr   r1,r0,#IRQMODE|NOINT
                   msr  cpsr_cxsf,r1          ;IRQMode
                   ldr   sp,=IRQStack
                   orr   r1,r0,#FIQMODE|NOINT
                   msr  cpsr_cxsf,r1          ;FIQMode
                   ldr   sp,=FIQStack
                   orr   r1,r0,#SVCMODE|NOINT
                   msr  cpsr_cxsf,r1          ;SVCMode
                   ldr   sp,=SVCStack
                  
                   mov pc,lr                     ;The LR register may be not valid for   the mode changes.
                   END                            ;Stratup.s程序结束
上述程序代码中,ENTRY指明了程序的入口。因为,ARM920T要求中断向量表必须设置在从0x0地址开始,连续8×4字节的空间中,因此,在ENTRY的后面紧接着8条跳转指令,分别对应复位、未定义指令错误、软件中断、预取指令错误、数据存取错误、一个保留的中断向量、IRQ和FIQ等异常的处理。
系统上电或复位后,首先执行的是“b     ColdReset”指令,系统跳转到标号为ColdReset处接着执行,在完成了关看门狗定时器、关中断、初始化各模式的堆栈、初始化存储器等功能后,执行指令“BL       __main”跳转到C语言的主函数处执行。本例中,初始化堆栈的功能编写成了子函数,在该子函数中,没有对用户模式下的堆栈进行初始化。
2.1.2 第二阶段启动代码
C语言源代码说明
void bdmain(void)
{
          /* 禁止Cache 和MMU */
          cache_disable();
          mmu_disable();
          /* 端口初始化 */
          port_init();
          /* 中断处理程序 */
          isr_init();
          /* 串口初始化 */
          serial_init(0, 115200);
          /* 输出信息进行主循环 */
          serial_printf("is ok!\n");
          while(1) {
               .......
          }
}
阅读(1282) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~