U-Boot启动第一阶段代码分析

    xiaoxiao2021-03-25  143

    U-Boot依赖于CPU体系结构的代码(如设备初始化代码等)通常都放在第一阶段,且用汇编语言来实现。 以uboot中的smdk2410 demo板相应文件分析,第一阶段对应的文件是cpu/arm920t/start.S和board/smdk2140/lowlevel_init.S。

    1:设置异常向量(Exception Vector)。

    1:cpu自身的初始化:包括MMU,catch,时钟系统,SDRAM控制系统的初始话。

    2:重定位:把自己从flash中搬到SDRAM 中

    3:分配堆栈空间,设置堆栈指针

    4:清零BSS数据段

    5:跳转到第二阶段入口函数。

    根据cpu/arm920t/u-boot.lds中指定的连接方式:

    OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

    ;指定输出可执行文件是elf格式,32位ARM指令,小端

    /*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/

    OUTPUT_ARCH(arm)

    ;指定输出可执行文件的平台为ARM

    ENTRY(_start)

    ;指定输出可执行文件的起始代码段为_start

    SECTIONS

    {

           . = 0x00000000; ;从0x0位置开始

           . = ALIGN(4); ;代码以4字节对齐

           .text      :  ;指定代码段

           {

             cpu/arm920t/start.o    (.text)    ;代码的第一个代码部分   

             *(.text)      ;其它代码部分

           }

           . = ALIGN(4);

           .rodata : { *(.rodata) }    ;指定只读数据段

           . = ALIGN(4);

           .data : { *(.data) }     ;指定读\写数据段

           . = ALIGN(4);

           .got : { *(.got) }    ;指定got段,got段是u-boot自定义的一个段,非标准段

           . = .;

           __u_boot_cmd_start = .;    ;把__u_boot_cmd_start赋值为当前位置,即起始位置

           .u_boot_cmd : { *(.u_boot_cmd) }  ;指定u_boot_cmd段,uboot把所有的uboot命令放在该段

           __u_boot_cmd_end = .;    ;把__u_boot_cmd_end赋值为当前位置,即bss段的开始位置

           . = ALIGN(4);

           __bss_start = .;    ;把__bss_start赋值为当前位置,即bss段的开始位置

           .bss : { *(.bss) }   ;指定bss段

           _end = .;   ;把_end赋值为当前位置,即bss段的结束位置

    }

    第一个链接的是cpu/arm920t/start.o,因此u-boot.bin的入口代码在cpu/arm920t/start.o中,其源代码在cpu/arm920t/start.S中。下面我们来分析cpu/arm920t/start.S的执行。

    1.硬件设备初始化

    (1)设置异常向量

           cpu/arm920t/start.S开头有如下的代码:

    #include

    #include

    /*

     *************************************************************************

     *

     * Jump vector table as in table 3.1 in [1]

     *

     *************************************************************************

     */

    注:ARM微处理器支持字节(8位)、半字(16位)、字(32位)3种数据类型。 @ 向量跳转表,每条占四个字节(一个字),地址范围为0x0000 0000~0x0000 0020 @ ARM体系结构规定在上电复位后的起始位置,必须有8条连续的跳转指令,通过硬件实现,他们就是异常向量表。ARM在上电复位后,是从0x00000000开始启动的,其实如果bootloader存在,在执行下面第一条指令后,就无条件跳转到start_code,下面一部分并没执行。

    @ 设置异常向量表的作用是识别bootloader。以后系统每当有异常出现,则CPU会根据异常号,从内存的0x00000000处开始查表并做相应的处理。

    /******************************************************

    ;当一个异常出现以后,ARM会自动执行以下几个步骤: 1.把下一条指令的地址放到连接寄存器LR(通常是R14。---保存位置 2.将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。---保存CPSR 3.根据异常类型,强制设置CPSR的运行模式位。 4.强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中。

    .globl _start    /*_start是GNU 汇编的默认入口标签。.globl将_start声明为外部程序可访问的标签,.globl是GNU汇编的保留关键字,前面加点是GNU汇编的语法*/

    _start:     b       start_code            /* 系统复位位置,整个程序入口 0x00*/

    @ARM上电后执行的第一条指令,也即复位向量,跳转到start_code

    @start_code用b,就是因为start_code在MMU建立前后都有可能发生 @其他的异常只有在MMU建立之后才会发生

    ldr pc,_undefined_instruction /*未定义指令异常,0x04*/ ldr pc, _software_interrupt   /*软中断异常,0x08*/ ldr pc, _prefetch_abort    /*内存操作异常,0x0c*/ ldr pc, _data_abort     /*数据异常,0x10*/ ldr pc, _not_used     /*未使用,0x14*/ ldr pc, _irq      /*慢速中断异常,0x18*/ ldr pc, _fiq      /*快速中断异常,0x1c*/

    @对于ARM数据从内存到CPU之间的移动只能通过L/S指令,如:ldr r0,0x1234为把0x1234内存中的数据写到r0中,还有一个就是ldr伪指令,如:ldr r0,=0x1234为把0x1234地址写到r0中,mov只能完成寄存器间数据的移动,而且立即数的长度限制在8位。

    /*  中断向量表入口地址 */

    @.word为GUN 汇编分配一段字内存单元,下面几句话相当于是C语言中的变量名和变量值。

    _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

     @.word为GNU ARM汇编特有的伪操作,为分配一段字内存单元(分配的单元为字对齐的),可以使用.word把标志符作为常量使用。如_fiq:.word fiq即把fiq存入内存变量_fiq中,也即是把fiq放到地址_fiq中。

           .balignl 16,0xdeadbeef

    @.balignl是.balign的变体

    @ .align伪操作用于表示对齐方式:通过添加填充字节使当前位置

    @满足一定的对齐方式。.balign的作用同.align。 @ .align {alignment} {,fill} {,max} @  其中:alignment用于指定对齐方式,可能的取值为2的次

    @幂,缺省为4。fill是填充内容,缺省用0填充。max是填充字节@数最大值,如果填充字节数超过max,  就不进行对齐,例如: @  .align 4  /* 指定对齐方式为字对齐 */

    【参考好野人的窝,于关u-boot中的.balignl 16,0xdeadbeef的理解 http://haoyeren.blog.sohu.com/84511571.html

    以上代码设置了ARM异常向量表,各个异常向量介绍如下:

      

        在cpu/arm920t/start.S中还有这些异常对应的异常处理程序。当一个异常产生时,CPU根据异常号在异常向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU就跳转到对应的异常处理程序执行。

    其中复位异常向量的指令“b start_code”决定了U-Boot启动后将自动跳转到标号“start_code”处执行。

    /*

     *************************************************************************

     *

     * Startup Code (reset vector)

     *

     * do important init only if we don't start from memory!

     * relocate armboot to ram

     * setup stack

     * jump to second stage

     *

     *************************************************************************

     */

    @保存变量的数据区,保存一些全局变量,用于BOOT程序从FLASH拷贝到RAM,或者其它的使用。 @还有一些变量的长度是通过连接脚本里得到,实际上由编译器算出来的。

    _TEXT_BASE:

    @因为linux开始地址是0x30000000,我这里是64M SDRAM,所以@TEXT_BASE =0x33F80000  .word TEXT_BASE /*uboot映像在SDRAM中的重定位地址*/ @TEXT_BASE在开发板相关的目录中的config.mk文档中定义, 他定义了代码在运行时所在的地址, 那么_TEXT_BASE中保存了这个地址.

    .globl _armboot_start

    _armboot_start:

           .word _start

    /*

     * These are defined in the board-specific linker script.

    */

    @下面主要在u-boot.lds链接脚本中定义

    .globl _bss_start

    _bss_start:

           .word __bss_start

    @__bss_start定义在和开发板相关的u-boot.lds中,_bss_start保存的是__bss_start标号所在的地址。

    .globl _bss_end

    _bss_end:

           .word _end

    @_bss_end定义在和开发板相关的u-boot.lds中,_bss_end保存的是_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

    (2)设置CPU进入SVC模式

    /*

     * the actual reset code

    系统上电或reset后,cpu的PC一般都指向0x0地址,在0x0地址上的指令是

     */

    start_code:

           /*

            * set the cpu to SVC32 mode

            */

    @更改处理器模式为管理模式. @对状态寄存器的修改要按照:读出-修改-写回的顺序来执行.     31 30 29 28 ---   7   6  -   4    3   2   1   0     N  Z  C V        I  F       M4  M3  M2 M1 M0                                   0   0   0  0   0    User26 模式                                   0   0   0  0   1    FIQ26 模式                                   0   0   0  1   0    IRQ26 模式                                   0   0   0  1   1    SVC26 模式                                   1   0   0  0   0    User 模式                                   1   0   0  0   1    FIQ 模式                                   1   0   0  1   0    IRQ 模式                                   1   0   0  1   1    SVC 模式                                   1   0   1  1   1    ABT 模式                                   1   1   0  1   1    UND 模式                                   1   1   1  1  1     SYS 模式

           mrs  r0,cpsr     //将CPSR状态寄存器读取,保存到r0中

           bic   r0,r0,#0x1f     /*工作模式位M0~M4清零 */

           orr   r0,r0,#0xd3    /*工作模式位设置为“10011”(管理模式),并将中断禁止位和快中断禁止位置1 */

           msr  cpsr,r0

    以上代码将CPU的工作模式位设置为管理模式,并将中断禁止位和快中断禁止位置‘1’,从而屏蔽了IRQ和FIQ中断。

    (3)设置控制寄存器地址

    @针对AT91RM9200进行特殊处理 #if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)  /*   * relocate exception table   */  ldr r0, =_start  ldr r1, =0x0  mov r2, #16 copyex:  subs r2, r2, #1 @sub带上了s用来更改进位标志,对于sub来说,若发生借位则C标志置0,没有则为1,这跟adds指令相反!要注意。  ldr r3, [r0], #4  str r3, [r1], #4  bne copyex #endif

    /* turn off the watchdog */

    #if defined(CONFIG_S3C2400)

    # define pWTCON        0x15300000

    # define INTMSK         0x14400008    /* Interupt-Controller base addresses */

    # define CLKDIVN       0x14800014    /* clock divisor register */

    #elif defined(CONFIG_S3C2410)  /* s3c2410与s3c2440下面4个寄存器地址相同 */

    # define pWTCON        0x53000000  /* WATCHDOG控制寄存器地址 */

    # define INTMSK         0x4A000008/*中断子屏蔽寄存器地址*/

    # define INTSUBMSK   0x4A00001C /* 中断子屏蔽寄存器地址 */

    # define CLKDIVN       0x4C000014   /* clock divisor register CLKDIVN寄存器地址, 用来设置FCLK,HCLK,PCLK三者的比例。*/

    #endif

           对与s3c2410开发板,以上代码完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四个寄存器的地址的设置。各个寄存器地址参见数据手册。这里如果移植到2440,将#elif defined(CONFIG_S3C2410)修改为#elif defined(CONFIG_S3C2440)即可,因为s3c2410与s3c2440这4个寄存器地址是相同的。

    (4)关闭看门狗

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

           ldr     r0, =pWTCON

           mov     r1, #0x0

           str     r1, [r0]  /* 看门狗控制器的最低位为0时,看门狗不输出复位信号 */

    以上代码向看门狗控制寄存器写入0,关闭看门狗。否则在U-Boot启动过程中,CPU将不断重启。如果移植到2440,这里一样。

    (5)屏蔽中断

           /*

            * mask all IRQs by setting all bits in the INTMR - default

            */

           mov r1, #0xffffffff  /* 某位被置1则对应的中断被屏蔽 */

           ldr   r0, =INTMSK

           str    r1, [r0]

    INTMSK是主中断屏蔽寄存器,每一位对应SRCPND(中断源引脚寄存器)中的一位,表明SRCPND相应位代表的中断请求是否被CPU所处理。

    根据数据手册,INTMSK寄存器是一个32位的寄存器,每位对应一个中断,向其中写入0xffffffff就将INTMSK寄存器全部位置一,从而屏蔽对应的中断。

    # if defined(CONFIG_S3C2410)

           ldr   r1, =0x3ff

           ldr   r0, =INTSUBMSK

           str    r1, [r0]

    # endif

    INTSUBMSK每一位对应子中断屏蔽寄存器SUBSRCPND中的一位,表明SUBSRCPND相应位代表的中断请求是否被CPU所处理。

    根据数据手册,INTSUBMSK寄存器是一个32位的寄存器,但是只使用了低10位。向其中写入0x3ff就是将INTSUBMSK寄存器全部有效位(低10位)置一,从而屏蔽对应的中断。注意:对于2440,这里写入的是0x7fff,根据2440数据手册,它使用了低15位。向其中写入0x7fff就是将INTSUBMSK寄存器全部有效位(低15位)置一,从而屏蔽对应的中断。

     (6) 设置时钟分频控制寄存器。

     /* FCLK:HCLK:PCLK = 1:2:4 * /FCLK为核心提供时钟,HCLK为AHB(ARM920T,内存控制器,中断控制器,LCD控制器,DMA和主USB模块)提供时钟,PCLK为APB(看门狗、IIS、I2C、PWM、MMC、ADC、UART、GPIO、RTC、SPI)提供时钟

           /* FCLK:HCLK:PCLK = 1:2:4 */

           /* default FCLK is 120 MHz ! */

           ldr   r0, =CLKDIVN

           mov r1, #3

           str    r1, [r0]

    #endif     /* CONFIG_S3C2400 || CONFIG_S3C2410 */

     @时钟分频设置,FCLK为核心提供时钟,HCLK为AHB(ARM920T,内存@控制器,中断控制器,LCD控制器,DMA和主USB模块)提供时钟,@PCLK为APB(看门狗、IIS、I2C、PWM、MMC、ADC、UART、GPIO、@RTC、SPI)提供时钟。分频数一般选择1:4:8,所以HDIVN=2,PDIVN=1,@CLKDIVN=5,这里仅仅是配置了分频寄存器,关于MPLLCON的配置肯@定写在lowlevel_init.S中了 @归纳出CLKDIVN的值跟分频的关系: @0x0 = 1:1:1  ,  0x1 = 1:1:2 , 0x2 = 1:2:2  ,  0x3 =1:2:4,  0x4 = 1:4:4,  0x5 = 1:4:8, 0x6 = 1:3:3,  0x7 = 1:3:6 @S3C2440的输出时钟计算式为:Mpll=(2*m*Fin)/(p*2^s) S3C2410的输出时钟计算式为:Mpll=(m*Fin)/(p*2^s) m=M(the value for divider M)+8;p=P(the value for divider P)+2 M,P,S的选择根据datasheet中PLL VALUE SELECTIONTABLE表格进行,   我的开发板晶振为16.9344M,所以输出频率选为:399.65M的话M=0x6e,P=3,S=1 @s3c2440增加了摄像头,其FCLK、HCLK、PCLK的分频数还受到CAMDIVN[9](默认为0),CAMDIVN[8](默认为0)的影响

    (7)关闭MMU,cache,执行CPU初始话。

           接着往下看:

           /*

            * we do sys-critical inits only at reboot,

            * not when booting from ram!

            */

    #ifndef CONFIG_SKIP_LOWLEVEL_INIT

           bl    cpu_init_crit @如果没有定义CONFIG_SKIP_LOELEVEL_INIT,则执行cpu_init_crit.

     #endif

    @cpu_init_crit这段代码在U-Boot正常启动时才需要执行,若将U-Boot从RAM中启动则应该注释掉这段代码。后面再对它分析。

    @重新定位u-boot到SDRAM中

    @调试阶段的代码是直接在RAM中运行的,而最后需要把这些代码 @固化到Flash中,因此U-Boot需要自己从Flash转移到RAM中运行,这也是重定向的目的所在。 @通过adr指令得到当前代码的地址信息:如果U-boot是从RAM @开始运行,则从adr,r0,_start得到的地址信息为r0=_start=_TEXT_BASE=TEXT_BASE=0x33F80000; 

    @如果U-boot从Flash开始运行,即从处理器对应的地址运行,则r0=0x0000,这时将会执行copy_loop标识的那段代码了。 @ _TEXT_BASE 定义在board/smdk2410/config.mk中

    #ifndef CONFIG_SKIP_RELOCATE_UBOOT

    relocate:                       /* relocate U-Boot to RAM将U-Boot复制到RAM中          */

           adr   r0, _start        /* r0 <- current position of code  r0 = 当前代码的开始地址  */

    @判断U-Boot是否是下载到RAM中运行,若是,则不用再复制到RAM中了,这种情况通常在调试U-Boot时才发生,通过adr指令得到当前代码的地址信息:如果U-boot是从RAM开始运行,则从adr,r0,_start得到的地址信息为r0=_start=_TEXT_BASE=TEXT_BASE=0x3ff80000;如果U-boot从Flash开始运行,即从处理器对应的地址运行,则r0=0x0000,这时将会执行copy_loop标识的那段代码了。

           ldr   r1, _TEXT_BASE         /* r1 =代码段的连接地址test if we run from flash or RAM */

           cmp     r0, r1      /*测试现在是在Flash中还是在RAM中,_start等于_TEXT_BASE说明是在RAM中运行,路过搬移代码 */

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

     

           ldr   r2, _armboot_start   @_armboot_start在前面定义,是第一条指令的运行地址即_start的内容写入r2

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

           sub  r2, r3, r2        /* @计算armboot所占字节大小 ,r2=代码段长度*/

           add  r2, r0, r2        /* r2 <- source end address @armboot结束地址,r2 = NOR Flash上代码段的结束地址*/

     

    copy_loop:   @实现从flash中拷贝到_TEXT_BASE(0x3ff80000)所在的地址中去

           ldmia      r0!, {r3-r10}         /* copy from source address [r0] ,从地址[r0]处获得数据,即从地址为[r0]的NOR Flash中读入8个字的数据  */

           stmia       r1!, {r3-r10}         /* copy to target address [r1] 将r3至r10寄存器的数据复制给地址为[r1]处  */

    @LDM(STM)用于在寄存器所指的一片连续存储器和寄存器列表的寄存@器间进行数据移动,或是进行压栈和出栈操作。 @格式为:LDM(STM){条件}{类型}基址寄存器{!},寄存器列表{^} @对于类型有以下几种情况: IA 每次传送后地址加1,用于移动数

    @据块     IB 每次传送前地址加1,用于移动数据块     DA 每次传送后地址减1,用于移动数据块     DB 每次传送前地址减1,用于移动数据块     FD 满递减堆栈,用于操作堆栈(即先移动指针再操作数据,相当于DB)     ED 空递减堆栈,用于操作堆栈(即先操作数据再移动指针,相当于DA)     FA 满递增堆栈,用于操作堆栈(即先移动指针再操作数据,相当于IB)     EA 空递增堆栈,用于操作堆栈(即先操作数据再移动指针,相当于IA)

           cmp r0, r2                    /* until source end addreee [r2]   判断是否复制完毕*/

           ble   copy_loop  /* 没复制完,则继续 */

    #endif     /* CONFIG_SKIP_RELOCATE_UBOOT */

     

           /* Set up the stack        

    @设置堆栈                             */

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

    stack_setup:    

           ldr   r0, _TEXT_BASE         /* _TEXT_BASE为代码段的开始地址,值为0x33F80000 upper 128 KiB: relocated uboot*/

           sub  r0, r0, #CFG_MALLOC_LEN      /* malloc area@代码段下面,留出一段内存以实现malloc */

           sub  r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo再留出一段内存,存一些全局参数,这里跳过这个全局数据区 */

    #ifdef CONFIG_USE_IRQ

           sub  r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) @为IRQ,FIQ留出空间,分配IRQ、FIQ模式的栈

    #endif

           sub  sp, r0, #12             /* leave 3 words for abort-stack @最后,留出12字节的内存给abort异常,往下的内存就都是栈了,将当前的地址赋给sp,这样就为内存栈设置好了,之后如果在u-boot中运行程序时需要使用栈的时候就从这里开始。   */

    到了这一步,读者可以知道内存的使用情况了,如下图所示(图中与上面的划分稍有不同,这是因为在cpu/arm920t/cpu.c中的cpu_init函数中才真正为IRQ、FIQ模式划分了栈)

    U-boot内存使用情况:

    (5)跳转到第二阶段代码的C入口点。

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

    clear_bss:  @清空用户堆区

           ldr   r0, _bss_start         /* find start of bss segment ,BSS段的开始地址,它的值在连接脚本u-boot.lds中确定 */

           ldr   r1, _bss_end          /* stop here ,BSS段的结束地址,它的值在连接脚本u-boot.lds中确定*/

           mov       r2, #0x00000000           /* clear */往r2中写入0值

     

    clbss_l:str       r2, [r0]           /* clear loop...   往BSS段中写入0值                  */

           add  r0, r0, #4

           cmp r0, r1    /*判断是否清空完毕*/

           ble   clbss_l  /* 没清空完,则继续 */

     

    #if 0

           /* try doing this stuff after the relocation */

           ldr     r0, =pWTCON

           mov     r1, #0x0

           str     r1, [r0]

     

           /*

            * mask all IRQs by setting all bits in the INTMR - default

            */

           mov r1, #0xffffffff

           ldr   r0, =INTMR

           str    r1, [r0]

     

           /* FCLK:HCLK:PCLK = 1:2:4 */

           /* default FCLK is 120 MHz ! */

           ldr   r0, =CLKDIVN

           mov r1, #3

           str    r1, [r0]

           /* END stuff after relocation */

    #endif

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

     

           ldr   pc, _start_armboot  

    _start_armboot:      .word start_armboot

     

      下面分析一下cpu_init_crit到底做了什么:

    cpu_init_crit这段代码在U-Boot正常启动时才需要执行,若将U-Boot从RAM中启动则应该注释掉这段代码。

    #ifndef CONFIG_SKIP_LOWLEVEL_INIT

    cpu_init_crit:

           /*

            * flush v4 I/D caches  使数据cache与指令cache无效

            */

           mov r0, #0

           mcr  p15, 0, r0, c7, c7, 0       /* flush v3/v4 cache 向c7写入0将使ICache与DCache无效*/

           mcr  p15, 0, r0, c8, c7, 0       /* flush v4 TLB 向c8写入0将使TLB失效*/

    代码中的c0,c1,c7,c8都是ARM920T的协处理器CP15的寄存器。其中c7是cache控制寄存器,c8是TLB控制寄存器。上面二行代码将0写入c7、c8,使Cache,TLB内容无效。

     

           /*

            * disable MMU stuff and caches关闭mmu和cache

        */

    @MRC指令的格式为:

    @MRC{条件} 协处理器编码,协处理器操作码1,目的寄存器,源寄存器1,源寄存器2,协处理器操作码2。

    @MRC指令用于将协处理器寄存器中的数据传送到ARM处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,目的寄存器为ARM处理器的寄存器,源寄存器1和源寄存器2均为协处理器的寄存器。  指令示例: MRC   P3,3,R0,C4,C5,6    ;该指令将协处理器P3的寄存器中的数据传送到ARM处理器寄存器中。

            */

           mrc  p15, 0, r0, c1, c0, 0

           bic   r0, r0, #0x00002300      @ clear bits 13, 9:8 (--V- --RS), 将13,9,8bit清零(13—异常向量表基地址:0x0, 9—Disable System Protection, 8—Disable ROM Protection)

           bic   r0, r0, #0x00000087      @ clear bits 7, 2:0 (B--- -CAM), 将7,2,1,0bit清零(7—为0 的时候表示小端字节序,2-- Data Cache Disabled,1-- Alignment Fault checking disabled,0—为0的话MMU disabled)

           orr   r0, r0, #0x00000002      @ set bit 2 (A) Align, 将bit 1 设置为1表示Fault checking enabled

           orr   r0, r0, #0x00001000      @ set bit 12 (I) I-Cache, 将bit 12设置为1表示使能 I-Cache。

           mcr  p15, 0, r0, c1, c0, 0   @保存r0到控制寄存器

       这几行代码关闭了MMU。这是通过修改CP15的c1寄存器来实现的,将c1的 M位置零,关闭了MMU。先看CP15的c1寄存器的格式(仅列出代码中用到的位):

    表 2.3 CP15的c1寄存器格式(部分)

           各个位的意义如下:

    V :  表示异常向量表所在的位置,0:异常向量在0x00000000;1:异常向量在 0xFFFF0000

    I :  0 :关闭ICaches;1 :开启ICaches

    R、S : 用来与页表中的描述符一起确定内存的访问权限

    B :  0 :CPU为小字节序;1 : CPU为大字节序

    C :  0:关闭DCaches;1:开启DCaches

    A :  0:数据访问时不进行地址对齐检查;1:数据访问时进行地址对齐检查

    M :  0:关闭MMU;1:开启MMU

           /*

            * 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.

            */

    在将u-boot搬移到ram前,先要完成其中内存初始化的工作,lowlevel_init就完成了内存初始化的工作,由于内存初始化是依赖于开发板的,因此lowlevel_init的代码一般放在board下面相应的目录中。对于2410,lowlevel_init在board/smdk2410/lowlevel_init.S中。

           mov ip, lr   @保存当前链接寄存器中的值

           bl    lowlevel_init   @主要是初始话存储控制器件即初始化内存,共13个。只需要设置BWSCON和BANKCONx(x为0-5),而BANK6,BANK7接SDRAM,除了设置 BWSCON和BANKCONx(x为6,7),还需要设置其他四个寄存器,而这13个寄存器的地址是连续的,BWSCON是第一个寄存器。

           mov lr, ip

           mov pc, lr  @返回执行relocate

    #endif /* CONFIG_SKIP_LOWLEVEL_INIT */

    下面分析一下lowlevel_init,代码如下:

    #include

    #include

    /* some parameters for the board */

    /*

     *

     * Taken from linux/arch/arm/boot/compressed/head-s3c2410.S

     *

     * Copyright (C) 2002 Samsung Electronics SW.LEE 

     *

     */

    #define BWSCON  0x48000000   /* 13个存储控制器的开始地址 */

     

    /* BWSCON */

    #define DW8              (0x0)

    #define DW16                   (0x1)

    #define DW32                   (0x2)

    #define WAIT             (0x1<<2)

    #define UBLB             (0x1<<3)

     

    #define B1_BWSCON         (DW32)

    #define B2_BWSCON         (DW16)

    #define B3_BWSCON         (DW16 + WAIT + UBLB)

    #define B4_BWSCON         (DW16)

    #define B5_BWSCON         (DW16)

    #define B6_BWSCON         (DW32)

    #define B7_BWSCON         (DW32)

     

    /* BANK0CON */

    #define B0_Tacs                 0x0  /*  0clk */

    #define B0_Tcos                 0x0  /*  0clk */

    #define B0_Tacc                 0x7  /* 14clk */

    #define B0_Tcoh                0x0  /*  0clk */

    #define B0_Tah                  0x0  /*  0clk */

    #define B0_Tacp                0x0

    #define B0_PMC                0x0  /* normal */

     

    /* BANK1CON */

    #define B1_Tacs                 0x0  /*  0clk */

    #define B1_Tcos                 0x0  /*  0clk */

    #define B1_Tacc                 0x7  /* 14clk */

    #define B1_Tcoh                0x0  /*  0clk */

    #define B1_Tah                  0x0  /*  0clk */

    #define B1_Tacp                0x0

    #define B1_PMC                0x0

     

    #define B2_Tacs                 0x0

    #define B2_Tcos                 0x0

    #define B2_Tacc                 0x7

    #define B2_Tcoh                0x0

    #define B2_Tah                  0x0

    #define B2_Tacp                0x0

    #define B2_PMC                0x0

     

    #define B3_Tacs                 0x0  /*  0clk */

    #define B3_Tcos                 0x3  /*  4clk */

    #define B3_Tacc                 0x7  /* 14clk */

    #define B3_Tcoh                0x1  /*  1clk */

    #define B3_Tah                  0x0  /*  0clk */

    #define B3_Tacp                0x3     /*  6clk */

    #define B3_PMC                0x0  /* normal */

     

    #define B4_Tacs                 0x0  /*  0clk */

    #define B4_Tcos                 0x0  /*  0clk */

    #define B4_Tacc                 0x7  /* 14clk */

    #define B4_Tcoh                0x0  /*  0clk */

    #define B4_Tah                  0x0  /*  0clk */

    #define B4_Tacp                0x0

    #define B4_PMC                0x0  /* normal */

     

    #define B5_Tacs                 0x0  /*  0clk */

    #define B5_Tcos                 0x0  /*  0clk */

    #define B5_Tacc                 0x7  /* 14clk */

    #define B5_Tcoh                0x0  /*  0clk */

    #define B5_Tah                  0x0  /*  0clk */

    #define B5_Tacp                0x0

    #define B5_PMC                0x0  /* normal */

     

    #define B6_MT                  0x3  /* SDRAM */

    #define B6_Trcd               0x1

    #define B6_SCAN              0x1  /* 9bit */

     

    #define B7_MT                  0x3  /* SDRAM */

    #define B7_Trcd                0x1  /* 3clk */

    #define B7_SCAN              0x1  /* 9bit */

     

    /* REFRESH parameter */

    #define REFEN                 0x1  /* Refresh enable */

    #define TREFMD               0x0  /* CBR(CAS before RAS)/Auto refresh */

    #define Trp                 0x0  /* 2clk */

    #define Trc                 0x3  /* 7clk */

    #define Tchr               0x2  /* 3clk */

    #define REFCNT               1113 /* period=15.6us, HCLK=60Mhz, (2048+1-15.6*60) */

    /**************************************/

     

    _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    /* SMRDATA减 _TEXT_BASE就是13个寄存器的偏移地址 */

           ldr   r1, =BWSCON      /* Bus Width Status Controller */

           add     r2, r0, #13*4

    0:

           ldr     r3, [r0], #4   /*将13个寄存器的值逐一赋值给对应的寄存器*/

           str     r3, [r1], #4

           cmp     r2, r0

           bne     0b

     

           /* everything is fine now */

           mov pc, lr

     

           .ltorg

    /* the literal pools origin */

    /*  下面是13个寄存器的值  */

    SMRDATA:

        .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

     

    lowlevel_init初始化了13个寄存器来实现RAM时钟的初始化。lowlevel_init函数对于U-Boot从NAND Flash或NOR Flash启动的情况都是有效的。

    board/samsung/mini2440/lowlevel_init.o将被链接到cpu/arm920t/start.o 后面,因此board/samsung/mini2440/lowlevel_init.o也在U-Boot的前4KB的代码中。

           U-Boot.lds链接脚本有如下代码:

           .text :

           {

                       cpu/arm920t/start.o    (.text)

                    board/samsung/mini2440/lowlevel_init.o (.text)

                     board/samsung/mini2440/nand_read.o (.text)

                  … …

     

           }

           U-Boot在NAND Flash启动时,lowlevel_init.o将自动被读取到CPU内部4KB的内部RAM中。因此lowlevel_init函数代码将从CPU内部RAM中复制寄存器的值到相应的寄存器中。

     

           对于U-Boot在NOR Flash启动的情况,由于U-Boot连接时确定的地址是U-Boot在内存中的地址,而此时U-Boot还在NOR Flash中,因此还需要在NOR Flash中读取数据到RAM中。

     

           由于NOR Flash的开始地址是0,而U-Boot的加载到内存的起始地址是TEXT_BASE,SMRDATA标号在Flash的地址就是SMRDATA-TEXT_BASE。

     

           综上所述,lowlevel_init的作用就是将SMRDATA开始的13个值复制给开始地址[BWSCON]的13个寄存器,从而完成了存储控制器的设置。

     

    Start.S文件之后是

    @.macro伪操作符标识宏定义的开始,.endm标识宏定义的结束。二者包含的一段代码,称为宏定义体,这样在程序中就可通过宏指令多次调用该代码段。格式:

    .macro macroname {parameter{,parameter}...}

      ...

     .endm

    宏的参数可直接使用斜线“/字符”来引用,如下“/reg”所示。

     

    @以下就是各种中断的处理。

    /**************** 异常处理程序 *******************/

     .align  5

    undefined_instruction:               //未定义指令

     get_bad_stack

     bad_save_user_regs

     bl  do_undefined_instruction

     

     .align 5

    software_interrupt:                   //软件中断

     get_bad_stack

     bad_save_user_regs

     bl  do_software_interrupt\

    @可知start.S的流程为:异常向量——上电复位后进入复位异常向量——跳到启动代码处——设置处理器进入管理模式——关闭看门狗——关闭中断——设置时钟分频——关闭MMU和CACHE——进入lowlever_init.S——检查当前代码所处的位置,如果在FLASH中就将代码搬移到RAM中

    转载请注明原文地址: https://ju.6miu.com/read-8859.html

    最新回复(0)