基于ARM的嵌入式Linux移植真实体验(2)――BootLoader

2019-07-13 01:58发布

基于ARM的嵌入式Linux移植真实体验(2――BootLoader 宋宝华 [email]21cnbao@21cn.com[/email] 出处:dev.yesky.com BootLoader指系统启动后,在操作系统内核运行之前运行的一段小程序。通过BootLoader,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。通常,BootLoader是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 BootLoader 几乎是不可能的。尽管如此,我们仍然可以对BootLoader归纳出一些通用的概念来,以指导用户特定的BootLoader设计与实现。 BootLoader 的实现依赖于CPU的体系结构,因此大多数 BootLoader 都分为stage1 stage2 两大部分。依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在 stage1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而stage2 则通常用C 语言来实现,这样可以实现更复杂的功能,而且代码会具有更好的可读性和可移植性。 BootLoader stage1 通常包括以下步骤: Ø         硬件设备初始化; Ø         为加载Boot Loaderstage2准备 RAM 空间; Ø         拷贝Boot Loaderstage2 RAM空间中; Ø         设置好堆栈; Ø         跳转到 stage2 C 入口点。 Boot Loaderstage2通常包括以下步骤:             Ø         初始化本阶段要使用到的硬件设备; Ø         检测系统内存映射(memory map) Ø         kernel 映像和根文件系统映像从flash上读到 RAM 空间中; Ø         为内核设置启动参数; Ø         调用内核。 本系统中的BootLoader参照韩国mizi公司的vivi进行修改。 1.开发环境 我们购买了武汉创维特信息技术有限公司开发的具有自主知识产权的应用于嵌入式软件开发的集成软、硬件开发平台ADTARM Development Tools)它为基于ARM 核的嵌入式应用提供了一整套完备的开发方案,包括程序编辑、工程管理和设置、程序编译、程序调试等。 ADT嵌入式开发环境由ADT Emulator for ARM ADT IDE for ARM组成。ADT Emulator for ARM 通过JTAG 实现主机和目标机之间的调试支持功能。它无需目标存储器,不占用目标系统的任何端口资源。目标程序直接在目标板上运行,通过ARM 芯片的JTAG 边界扫描口进行调试,属于完全非插入式调试,其仿真效果接近真实系统。 ADT IDE for ARM 为用户提供高效明晰的图形化嵌入式应用软件开发环境,包括一整套完备的面向嵌入式系统的开发和调试工具:源码编辑器、工程管理器、工程编译器(编译器、汇编器和连接器)、集成调试环境、ADT Emulator for ARM 调试接口等。其界面同Microsoft Visual Studio 环境相似,用户可以在ADT IDE for ARM 集成开发环境中创建工程、打开工程,建立、打开和编辑文件,编译、连接、设置、运行、调试嵌入式应用程序。 ADT嵌入式软件开发环境采用主机-目标机交叉开发模型。ADT IDE for ARM 运行于主机端,而ADT Emulator for ARM 实现ADT IDE for ARM 与目标机之间的连接。开发时,首先由ADT IDE for ARM 编译连接生成目标代码,然后建立与ADT Emulator for ARM 之间的调试通道,调试通道建立成功后,就可以在ADT IDE for ARM 中通过ADT Emulator for ARM 控制目标板实现目标程序的调试,包括将目标代码下载到目标机中,控制程序运行,调试信息观察等等。   2.ARM汇编 ARM本身属于RISC指令系统,指令条数就很少,而其编程又以C等高级语言为主,我们仅需要在Bootloader的第一阶段用到少量汇编指令: 1+-运算 ADD    r0,  r1,  r2  ―― r0  := r1  +  r2 SUB   r0,  r1,  r2   ―― r0  :=  r1  -  r2 其中的第二个操作数可以是一个立即数: ADD r3, r3, #1 ―― r3 := r3 + 1 第二个操作数还可以是位移操作后的结果: ADD r3, r2, r1, LSL #3 ―― r3 := r2 + 8.r1 2)位运算 AND  r0, r1, r2   ―― r0 := r1 and r2 ORR  r0, r1, r2 ―― r0 := r1 or    r2 EOR  r0, r1, r2 ―― r0 := r1 xor  r2 BIC   r0, r1, r2  ―― r0 := r1 and not r2 3)寄存器搬移 MOV  r0,  r2 ―― r0  :=  r2 MVN  r0,  r2           ―― r0  :=  not  r2 4)比较 CMP r1, r2 ―― set cc on r1 - r2 CMN r1, r2 ―― set cc on r1 + r2 TST r1, r2 ―― set cc on r1 and r2 TEQ r1, r2 ―― set cc on r1 or r2 这些指令影响CPSR寄存器中的 (N, Z, C, V) 5)内存操作 LDR r0, [r1] ――  r0 := mem [r1] STR r0, [r1] ――  mem [r1] := r0 LDR r0, [r1, #4] ――  r0 := mem [r1+4] LDR r0, [r1, #4] ! ――  r0 := mem [r1+4]     r1 := r1 + 4 LDR r0, [r1], #4 ――  r0 := mem [r1]       r1 := r1 +4 LDRB r0 , [r1] ――  r0 := mem8 [r1] LDMIA r1, {r0, r2, r5} ――  r0 := mem [r1]    r2 := mem [r1+4]    r5 := mem [r1+8] {..} 可以包括r0~r15中的所有寄存器,若包括r15 (PC)将导致程序的跳转。 6)控制流 1: MOV r0, #0 ; initialize counter LOOP:    ADD r0, r0, #1 ; increment counter    CMP r0, #10 ; compare with limit    BNE LOOP ; repeat if not equal 2: CMP r0, #5 ADDNE r1, r1, r0 SUBNE r1, r1, r2 ―― if (r0 != 5) { r1 := r1 + r0 - r2 } 3.BootLoader第一阶段 3.1硬件设备初始化 基本的硬件初始化工作包括: Ø         屏蔽所有的中断; Ø         设置CPU的速度和时钟频率; Ø         RAM初始化; Ø         初始化LED ARM的中断向量表设置在0地址开始的8个字空间中,如下表: 每当其中的某个异常发生后即将PC值置到相应的中断向量处,每个中断向量处放置一个跳转指令到相应的中断服务程序去进行处理,中断向量表的程序如下: @ 0x00: Reset        b     Reset @ 0x04: Undefined instruction exception UndefEntryPoint:        b     HandleUndef @ 0x08: Software interrupt exception SWIEntryPoint:        b     HandleSWI @ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort) PrefetchAbortEnteryPoint:        b     HandlePrefetchAbort @ 0x10: Data Access Memory Abort DataAbortEntryPoint:        b     HandleDataAbort @ 0x14: Not used NotUsedEntryPoint:        b     HandleNotUsed @ 0x18: IRQ(Interrupt Request) exception IRQEntryPoint:        b     HandleIRQ @ 0x1c: FIQ(Fast Interrupt Request) exception FIQEntryPoint:        b     HandleFIQ 复位时关闭看门狗定时器、屏蔽所有中断: Reset:        @ disable watch dog timer        mov r1, #0x53000000        mov r2, #0x0        str   r2, [r1]          @ disable all interrupts        mov r1, #INT_CTL_BASE        mov r2, #0xffffffff        str   r2, [r1, #oINTMSK]        ldr   r2, =0x7ff        str   r2, [r1, #oINTSUBMSK] 设置系统时钟:     @init clk     @ 1:2:4     mov r1, #CLK_CTL_BASE     mov r2, #0x3      str   r2, [r1, #oCLKDIVN]     mrc p15, 0, r1, c1, c0, 0             @ read ctrl register     orr   r1, r1, #0xc0000000             @ Asynchronous      mcr p15, 0, r1, c1, c0, 0             @ write ctrl register     @ now, CPU clock is 200 Mhz     mov r1, #CLK_CTL_BASE     ldr   r2, mpll_200mhz     str   r2, [r1, #oMPLLCON] 点亮所有的用户LED        @ All LED on        mov r1, #GPIO_CTL_BASE        add  r1, r1, #oGPIO_F        ldr   r2,=0x55aa        str   r2, [r1, #oGPIO_CON]        mov r2, #0xff        str   r2, [r1, #oGPIO_UP]        mov r2, #0x00        str   r2, [r1, #oGPIO_DAT] 设置(初始化)内存映射: ENTRY(memsetup)        @ initialise the static memory          @ set memory control registers        mov r1, #MEM_CTL_BASE        adrl  r2, mem_cfg_val        add  r3, r1, #52 1:     ldr   r4, [r2], #4        str   r4, [r1], #4        cmp r1, r3        bne  1b          mov pc, lr 设置(初始化)UART        @ set GPIO for UART        mov r1, #GPIO_CTL_BASE        add  r1, r1, #oGPIO_H        ldr   r2, gpio_con_uart          str   r2, [r1, #oGPIO_CON]        ldr   r2, gpio_up_uart        str   r2, [r1, #oGPIO_UP]            bl     InitUART   @ Initialize UART @ @ r0 = number of UART port InitUART:        ldr   r1, SerBase        mov r2, #0x0        str   r2, [r1, #oUFCON]        str   r2, [r1, #oUMCON]        mov r2, #0x3        str   r2, [r1, #oULCON]        ldr   r2, =0x245        str   r2, [r1, #oUCON] #define UART_BRD ((50000000 / (UART_BAUD_RATE * 16)) - 1)        mov r2, #UART_BRD        str   r2, [r1, #oUBRDIV]        mov r3, #100        mov r2, #0x0 1:     sub  r3, r3, #0x1        tst    r2, r3        bne  1b   #if 0        mov r2, #'U'        str   r2, [r1, #oUTXHL]   1:     ldr   r3, [r1, #oUTRSTAT]        and  r3, r3, #UTRSTAT_TX_EMPTY        tst    r3, #UTRSTAT_TX_EMPTY        bne  1b             mov r2, #'0'        str   r2, [r1, #oUTXHL]   1:     ldr   r3, [r1, #oUTRSTAT]        and  r3, r3, #UTRSTAT_TX_EMPTY        tst    r3, #UTRSTAT_TX_EMPTY        bne  1b    #endif          mov pc, lr 此外,vivi还提供了几个汇编情况下通过串口打印字符的函数PrintCharPrintWordPrintHexWord @ PrintChar : prints the character in R0 @   r0 contains the character @   r1 contains base of serial port @   writes ro with XXX, modifies r0,r1,r2 @   TODO : write ro with XXX reg to error handling PrintChar: TXBusy:        ldr   r2, [r1, #oUTRSTAT]        and  r2, r2, #UTRSTAT_TX_EMPTY        tst    r2, #UTRSTAT_TX_EMPTY        beq  TXBusy         str   r0, [r1, #oUTXHL]        mov pc, lr   @ PrintWord : prints the 4 characters in R0 @   r0 contains the binary word @   r1 contains the base of the serial port @   writes ro with XXX, modifies r0,r1,r2 @   TODO : write ro with XXX reg to error handling PrintWord:        mov r3, r0        mov r4, lr        bl     PrintChar          mov r0, r3, LSR #8              /* shift word right 8 bits */        bl     PrintChar          mov r0, r3, LSR #16             /* shift word right 16 bits */        bl     PrintChar               mov r0, r3, LSR #24             /* shift word right 24 bits */        bl     PrintChar          mov r0, #' '        bl     PrintChar          mov r0, #' '        bl     PrintChar          mov pc, r4   @ PrintHexWord : prints the 4 bytes in R0 as 8 hex ascii characters @   followed by a newline @   r0 contains the binary word @   r1 contains the base of the serial port @   writes ro with XXX, modifies r0,r1,r2 @   TODO : write ro with XXX reg to error handling PrintHexWord:        mov r4, lr        mov r3, r0        mov r0, r3, LSR #28        bl     PrintHexNibble        mov r0, r3, LSR #24        bl     PrintHexNibble        mov r0, r3, LSR #20        bl     PrintHexNibble        mov r0, r3, LSR #16        bl     PrintHexNibble        mov r0, r3, LSR #12        bl     PrintHexNibble        mov r0, r3, LSR #8        bl     PrintHexNibble