• (一):U-BOOT启动分析---start.s(1)


    分析uboot第一个执行函数_start(cpu/arm920t/start.S) 

    打开cpu/arm920t/start.S

    复制代码
    1 .globl _start                      // .globl定义一个全局符号"_start",表明_start这个符号要被链接器用到 
    2 _start:                            //_start:系统复位设置,以下共8种不同的异常处理
    3 b reset                             //复位异常 0x0
    4 ldr    pc, _undefined_instruction                 //未定义的指令异常 0x4
    5 ldr    pc, _software_interrupt                 // 软件中断异常 0x8 
    6 ldr    pc, _prefetch_abort                 //内存操作异常 0xc
    7 ldr    pc, _data_abort                 //数据异常 0x10
    8 ldr    pc, _not_used                 //未使用 0x14
    9 ldr    pc, _irq                   //中断IRQ异常 0x18
    10 ldr    pc, _fiq                 //快速中断FIQ异常 0x1c
    11 
    12 _undefined_instruction:    .word undefined_instruction                 //0x20
    13 _software_interrupt:    .word software_interrupt                       //0x24
    14 _prefetch_abort:    .word prefetch_abort             // 0x28
    15 _data_abort:    .word data_abort                     //0x2c
    16 _not_used:    .word not_used                        //0x30
    17 _irq:    .word irq                                  //0x34
    18 _fiq:    .word fiq                                  //0x38
    19 
    20 .balignl 16,0xdeadbeef                              //0x3c
    复制代码

    在第1行中".globl _start":使用.globol声明全局符号_start,在 board/100ask24x0/u-boot.lds中ENTRY(_start)这里用到
    其中符号保存的地址都在顶层目录/system.map中列出来了

    system.map文件开头部分如下:

    复制代码
    33f80000 t $a 
    33f80000 T _start                   //_start符号被链接在33f80000,其中33f80000是生成bin文件的运行启始地址.
    33f80020 t $d 
    33f80020 t _undefined_instruction   //_undefined_instruction符号被链接在33f80020
    ...
    33f80160 t undefined_instruction //_undefined_instruction指向的undefined_instruction符号被链接在33f80160
    33f801c0 t software_interrupt
    33f80220 t prefetch_abort
    33f80280 t data_abort
    33f802e0 t not_used
    33f80340 T Launch
    33f803b0 t On_Steppingstone
    33f80400 t irq
    ...
    复制代码

    在第2行中_start之所以有8种不同的异常处理,是在2440芯片手册中已经规定好了的,如下图1:

                                                                图1

    从上图可以看出复位异常处理需要进入管理模式(0X00000000),所以start.S 中“b reset”跳转到设置管理模式。

    在linux中的异常向量地址是经过MMU(虚拟内存管理)产生的虚拟地址,比如中断地址:
    0x18映射到物理地址是0xc000 0018(映射地址由自己设定),所以linux把中断向量放在0xc000 0018就行了。

    CPU一上电设置了入口地址"ENTRY(_start)"后,就会进入"_start"全局符号中执行上面第3行跳转到复位异常字段: "b reset".
    1.那么后面的异常处理为什么用ldr不用b指令?
    之所以第一句使用b reset,是因为ldr指令属于绝对跳转,而b属于相对跳转,它的地址与代码位置无关。
    因为复位异常在CPU运行前是没有初始化SDRAM(不能使用0X30000000以上地址),

    在正常工作后也可能触发复位,这时由于CPU已经对SDRAM、MMU(虚拟内存管理)等初始化了,
    此时的虚拟地址和物理地址完全不同,所以reset使用b指令相对跳转。

    2.后面的异常处理是怎么执行的?执行后异常处理又怎么退出的?
    在2440芯片手册上给出,例如当处理一个中断IRQ异常时:
    a.保存当前PC现场到寄存器R14,  
    b.把当前程序状态寄存器(CPSR)保存到备份程序状态寄存器(SPSR)中.从异常退出的时候,就可以由SPSR来恢复CPSR。
    c.根据中断IRQ异常处理,强制将 CPSR 模式位设为中断模式,如下图

    d.强制 PC 从相关异常向量处取下条指令。跳转到0x18实现中断异常处理.
    退出中断IRQ异常时:
    a. 将中断IRQ所对应的是R14_irq寄存器并放入到 PC 中,如下图,中断IRQ所对应的是R14_irq寄存器,执行MOVS R14_svc .

    b. 复制 SPSR 的内容返回给 CPSR 中。
    c. 如果在异常进入时置位了中断禁止标志位异常,清除中断禁止标志位


    3. 第12行中 .word:        类似于(unsigend long)      

    以第12行中 _undefined_instruction: .word undefined_instruction为例
    _undefined_instruction和undefined_instruction都是一个标号,
    表示_undefined_instruction指向一个32位(4字节)地址,该地址用undefined_instruction符号变量代替。

    用C语言表示就是:_undefined_instruction = &undefined_instruction

    相当于PC从_undefined_instruction取值时,即undefined_instruction地址存到了PC中。
    4.第20行中 .balignl 16,0xdeadbeef:
    它的意思就是在以当前地址开始,在地址为16的倍数的指令位置的上一个指令填入为0xdeadbeef的内容。
    此时当前地址刚好0x3c=60,由于ARM每个指令间隔4个字节,且64%16=0,所以在0x3c中填入0xdeadbeef。
    它们的作用就是为内存做标记,插在那里,这个位置往前有特殊作用的内存,禁止访问。

    接下来继续往下看start.o

    复制代码
    reset: 
    /* 设置CPSR程序程序状态寄存器为管理模式   */                           
      mrs    r0,cpsr                   //MRS读出CPSR寄存器值到R0
      bic    r0,r0,#0x1f               //将R0低5位清空
      orr    r0,r0,#0xd3               //R0与b'110 10011按位或,禁止IRQ和FIQ中断,10011:复位需要设为管理模式(图1)
      msr    cpsr,r0                   //MSR写入CPSR寄存器
    
    /* 关看门狗   */      
    # define pWTCON        0x53000000          //(WitchDog Timer)看门狗定时器寄存器WTCON,设为0X0表示关闭看门狗
    # define INTMOD     0X4A000004        //(Interrupt Mode)中断模式寄存器INTMOD,相应位=0:IRQ模式,相应位=1:IRQ模式,
    # define INTMSK        0x4A000008            //(Interrupt Mask)中断屏蔽寄存器INTMSK,相应位=0:开启中断服务,相应位=1:关闭中断服务
    # define INTSUBMSK    0x4A00001C        //中断次级屏蔽寄存器,相应位=0:开启中断服务,相应位=1:关闭中断服务
    # define CLKDIVN    0x4C000014            //时钟分频寄存器
    
    #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)        //宏定义CONFIG_S3C2410已定义
        ldr     r0, =pWTCON                                         //R0等于WTCON地址
        mov     r1, #0x0                                            //R1=0x0
        str     r1, [r0]                                            //关闭WTCON寄存器,pWTCON=0;
    /* 关中断   */  
        mov    r1, #0xffffffff                                         //R1=0XFFFF FFFF
        ldr    r0, =INTMSK                                             //R0等于INTMSK地址
        str    r1, [r0]                                                //*0x4A000008=0XFFFF FFFF(关闭所有中断)
    # if defined(CONFIG_S3C2410)
        ldr    r1, =0x3ff                                          //R1=0x3FF
        ldr    r0, =INTSUBMSK                                      //R0等于INTSUBMSK地址
        str    r1, [r0]                                            //*0x4A00001C=0x3FF(关闭次级所有中断)
    # endif
    

    /* 判断系统是从nand启动的还是直接将程序下载到SDRAM中运行, 若系统从nand启动,这里得到r0和r1值是不一样的,r1=0x33f80000, 而r0=0x00000000。说明没初始化SDRAM,ne(no equal)标识符为真,所以bl cpu_init_crit执行跳转. */ adr r0, _start ldr r1, _TEXT_BASE cmp r0, r1 blne cpu_init_crit
    复制代码

    CPU复位后是从这里开始执行,这里初始化了:
    1.执行设置CPSR程序程序状态寄存器为管理模式
    2.关看门狗
    3.屏蔽中断
    4.进入cpu_init_crit函数关闭MMU,进入lowlevel_init初始化13个BANK寄存器来初始化SDRAM

    进入cpu_init_crit函数(关闭MMU):

    复制代码
    cpu_init_crit:
    
    mov    r0, #0
    mcr    p15, 0, r0, c7, c7, 0    //关闭ICaches(指令缓存,关闭是为了降低MMU查表带来的开销)和DCaches(数据缓存,DCaches使用的是虚拟地址,开启MMU之前必须关闭)
    mcr    p15, 0, r0, c8, c7, 0    //使无效整个数据TLB和指令TLB(TLB就是负责将虚拟内存地址翻译成实际的物理内存地址)
    
    mrc    p15, 0, r0, c1, c0, 0
    bic    r0, r0, #0x00002300    @ clear bits 13, 9:8 (--V- --RS) //bit8:系统不保护,bit9:ROM不保护,bit13:设置正常异常模式0x0~0x1c,即异常模式基地址为0X0
    bic    r0, r0, #0x00000087    @ clear bits 7, 2:0 (B--- -CAM) //bit0~2:禁止MMU,禁止地址对齐检查,禁止数据Cache.bit7:设为小端模式
    orr    r0, r0, #0x00000002    @ set bit 2 (A) Align //bit2:开启数据Cache
    orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache //bit12:开启指令Cache
    mcr    p15, 0, r0, c1, c0, 0
    /*
    mcr/mrc:
    Caches:是一种高速缓存存储器,用于保存CPU频繁使用的数据。在使用Cache技术的处理器上,当一条指令要访问内存的数据时,
    首先查询cache缓存中是否有数据以及数据是否过期,如果数据未过期则从cache读出数据。处理器会定期回写cache中的数据到内存。
    根据程序的局部性原理,使用cache后可以大大加快处理器访问内存数据的速度。
    其中DCaches和ICaches分别用来存放数据和执行这些数据的指令
    TLB:就是负责将虚拟内存地址翻译成实际的物理内存地址,TLB中存放了一些页表文件,文件中记录了虚拟地址和物理地址的映射关系。 当应用程序访问一个虚拟地址的时候,会从TLB中查询出对应的物理地址,然后访问物理地址。TLB通常是一个分层结构, 使用与Cache类似的原理。处理器使用一定的算法把最常用的页表放在最先访问的层次。 这里禁用MMU,是方便后面直接使用物理地址来设置控制寄存器 */ mov ip, lr //临时保存当前子程序返回地址,因为接下来执行bl会覆盖当前返回地址. bl lowlevel_init //跳转到lowlevel_init(位于u-boot-1.1.6/board/100ask24x0/lowlevel_init.S) mov lr, ip //恢复当前返回地址 mov pc, lr //退出
    复制代码

    进入lowlevel_init函数 (初始化各个bank和SDRAM)、

    复制代码
    lowlevel_init:
    ldr r0, =SMRDATA //将SMRDATA的首地址(0x33F806C8)存到r0中 
    ldr    r1, _TEXT_BASE //r1等于_TEXT_BASE内容,也就是TEXT_BASE(0x33F80000)
    sub    r0, r0, r1 //将0x33F806C8与0x33F80000相减,得到现在13个寄存器值在NOR Flash上存放的开始地址
    ldr    r1, =BWSCON //将BWSCON寄存器地址值存到r1中 (第一个存储器寄存器首地址)
    add r2, r0, #13*4 //每个寄存器4字节,r2=r0+13*4=NOR Flash上13个寄存器值最后一个地址
    0: 
    ldr r3, [r0], #4 //将r0的内容存到r3的内容中(r3等于SMRDATA里面值), 同时r0地址+=4;
    str r3, [r1], #4 //将r3的内容存到r1所指的地址中(向寄存器地址里写入r3值),同时r1地址+=4;
    cmp r2, r0 // 判断r2和r0
    bne 0b //不等则跳转到第6行继续执行
    
    mov pc, lr //跳回到返回地址中继续执行
    
    SMRDATA:
    .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) 
    //设置每个BWSCON,注意BANK0由硬件连线决定了
    .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))
    //设置BANKCON0~BANKCON5 
    .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
    //设置BANKCON6~BANKCON7
    .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    //设置REFRESH,在S3C2440中11~17位是保留的,也即(Tchr<<16)无意义
    .word 0xb1 //设置BANKSIZE,对于容量可以设置大写,多出来的空内存会被自动检测出来
    .word 0x30 //设置MRSRB6
    .word 0x30 //设置MRSRB7
    复制代码

    返回到start.S继续往下看

    复制代码
    stack_setup: //设置栈,方便调用C函数 
    ldr    r0, _TEXT_BASE    //代码段的初始地址:r0=0x33f80000
    sub    r0, r0, #CFG_MALLOC_LEN      //留出一段内存以实现malloc:r0=0x33f50000
    sub    r0, r0, #CFG_GBL_DATA_SIZE  //再留出一段存一些全局参数的变量:r0=0x33F4FF80
    
    #ifdef CONFIG_USE_IRQ
    sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) //中断与快中断的栈:r0=0x33F4DF7C
    #endif
    sub    sp, r0, #12                 //留出12字节内存给abort异常 设置栈顶sp=r0-12;
    
    #ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl clock_init                  //进入clock_init函数
    #endif 
    复制代码

    这里初始化了:

    5.设置栈

    6.进入clock_init函数设置时钟

    进入clock_init函数

    复制代码
    void clock_init(void)
    {
    S3C24X0_CLOCK_POWER *clk_power = (S3C24X0_CLOCK_POWER *)0x4C000000; //定义一个S3C24X0_CLOCK_POWER型结构体指针,clk_power->LOCKTIME=0x4C000000
    
    if (isS3C2410) //isS3C2410为0,执行else
    {... ...}
    else
    {
    /* FCLK:HCLK:PCLK = 1:4:8 */
    clk_power->CLKDIVN = S3C2440_CLKDIV; //S3C2440_CLKDIV=0X05
    
    /* change to asynchronous bus mod */
    __asm__( "mrc p15, 0, r1, c1, c0, 0
    " /* read ctrl register */ 
    "orr r1, r1, #0xc0000000
    " //使其从快总线模式改变为异步总线模式,在2440手册上看到
    "mcr p15, 0, r1, c1, c0, 0
    " /* write ctrl register */ 
    :::"r1" //:::"r1" 向GCC声明:我对r1作了改动
    );
    
    /* to reduce PLL lock time, adjust the LOCKTIME register */
    clk_power->LOCKTIME = 0xFFFFFFFF; //PLL 锁定时间计数寄存器
    
    /* configure UPLL */
    clk_power->UPLLCON = S3C2440_UPLL_48MHZ; //UCLK=48Mhz
    
    /* some delay between MPLL and UPLL */
    delay (4000); //等待UCLK时钟波形稳定
    
    /* configure MPLL */
    clk_power->MPLLCON = S3C2440_MPLL_400MHZ; //FCLK=400Mhz
    
    /* some delay between MPLL and UPLL */ 
    delay (8000); //等待FCLK时钟波形稳定
    }
    }
    复制代码

    返回到start.S继续往下看

    复制代码
    relocate:    /* 拷贝u-boot到SDRAM */
    adr    r0, _start    //r0:当前代码开始地址
    ldr    r1, _TEXT_BASE    //r1:代码段连接地址(0X3FF8 0000)
    cmp r0, r1 //测试现在在FLASH中还是RAM中
    beq clear_bss //若_start==_TEXT_BASE,表示已经进行代码从Flash拷贝SDRAM了(通常是调试时直接下载到RAM中)
    
    ldr    r2, _armboot_start //r2等于_armboot_start里的内容,也就是_start
    ldr    r3, _bss_start //r3等于_bss_start里的内容,(在连接脚本u-boot.lds中定义,是代码段的结束地址)
    sub    r2, r3, r2    //r2等于代码段长度
    
    bl CopyCode2Ram    // r0: source, r1: dest, r2: size 将从NOR FLASH上代码段(r0~r0+r2)拷贝到sdram地址(r3)0x3ff80000代码段地址上
    
    
    
    
    clear_bss:
    ldr    r0, _bss_start    //r0=__bss_start
    ldr    r1, _bss_end    //r0等于_bss_end里的内容,也就是_end(在u-boot.lds里定义,是存bss的结束地址)
    mov r2, #0x00000000    //r2=0;用来清bss所有段
    
    clbss_l:
    str    r2, [r0]    //*r0=0;
    add    r0, r0, #4 //r0+=4;
    cmp    r0, r1 
    ble    clbss_l //小于等于一直执行clbss_l
    
    
    ldr    pc, _start_armboot //pc等于_start_armboot里的内容,也就是跳转到start_armboot函数
    _start_armboot:    .word start_armboot *(_start_armboot)=start_armboot
    复制代码

    这里初始化了:

    7.重定位(代码从Flash拷贝至SDRAM中)
    8.清bss段(未初始的全局/静态变量)
    9.跳转到start_armboot函数(位于u-boot-1.1.6/lib_arm/borad.c,用来实现第2阶段硬件相关的初始化)

    本章小结:
    uboot-第一阶段硬件初始化主要实现了:
    1.执行设置CPSR程序程序状态寄存器为管理模式
    2.关看门狗
    3.屏蔽中断
    4.关闭MMU,初始化SDRAM
    5.设置栈
    6.时钟设置
    7.重定位(代码从Flash拷贝至SDRAM中)
    8.清bss段(未初始的全局/静态变量)
    9.跳转到start_armboot函数(位于u-boot-1.1.6/lib_arm/borad.c,用来实现第2阶段硬件相关的初始化)
    接下来开始分析uboot-第二阶段硬件初始化。

  • 相关阅读:
    单向链表
    字符串的碎片整理。。。
    刷夜有感
    C中的枚举类型及一些用法
    hdu 1001(无赖的一种方法)
    加法器和布尔运算符
    《C和指针》学习笔记(4)
    Java中创建对象的5种方式 -[转] http://www.codeceo.com/article/5-ways-java-create-object.html
    程序员转行为什么这么难--[转]
    Tomcat 性能优化之APR插件安装 -- [转]
  • 原文地址:https://www.cnblogs.com/tjy1805099/p/12572714.html
Copyright © 2020-2023  润新知