zoukankan      html  css  js  c++  java
  • u-boot-1.1.6第1阶段分析之start.S、lowlevel_init.S文件

    学习目标:

    1. 对start.S中每一行代码,都有基本了解
    2. 通过对start.S文件分析,对ARM920T架构的CPU的启动过程,有更清楚理解

    U-boot属于两个阶段的Bootloader,第一阶段的文件为cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S,前者是平台相关的,后者是开发板相关的。U-boot的第一阶段主要的任务是一些系统的初始化工作,从大的方面可以分为以下几个部分:

    ①设置CPU的模式

    ②关闭看门狗

    ③关闭中断

    ④关闭MMU、设置RAM时序

    ⑤代码重定位、设置堆栈SP指针

    ⑥清除BSS段

    ⑦异常中断处理

    下面对start.S文件做出详细分析,研究每一部分内容如何实现。


     1 设置CPU的模式

    110 reset:
    111     /*
    112      * set the cpu to SVC32 mode
    113      */
    114     mrs    r0,cpsr
    115     bic    r0,r0,#0x1f
    116     orr    r0,r0,#0xd3
    117     msr    cpsr,r0

    cpsr为当前状态寄存器,它的格式如下所示:

    cpsr是32位寄存器,寄存器[31:28]位为状态标志位,[27:8]位保留未被使用,[7:0]位为控制位。控制位中的第7位和第6位是用来设置是否使能中断请求和快速中断请求,第5位是设置CPU操作状态,当设置为1处理器执行在Thumb状态,为0时执行在ARM状态,第0~4位一起用来决定CPU的工作模式,模式位具体说明如下图所示:

    由上图可知我们把M[4:0]设置为0b10011时,CPU工作在管理模式下,下面来分析代码如何实现。

    第114行,将当前程序状态寄存器内容,复制到r0寄存器内

    第115行,将r0寄存器内低5位清零

    第116行,将r0寄存器内容与立即数0xd3进行或运算,并把运算结果保存到r0寄存器,CPSR位域和含义如下表所示:

    CPSR位域 7 6 5 4 3 2 1 0
    位域含义 I F T M4 M3 M2 M1 M0
    0xd3 1 1 0 1 0 0 1 1

    即:

    bit[7]=1->设置I位为1->关闭中断IQR,bit[6]=1->设置F位为1->关闭FIQ中断

    bit[4:0]=0b10011->设置CPU位SVC管理模式

    第117行,将r0寄存器内容,复制到cpsr寄存器中


     2 关看门狗

    119 /* turn off the watchdog */
    120 #if defined(CONFIG_S3C2400)
    121 # define pWTCON        0x15300000
    122 # define INTMSK        0x14400008    /* Interupt-Controller base addresses */
    123 # define CLKDIVN    0x14800014    /* clock divisor register */
    124 #elif defined(CONFIG_S3C2410)
    125 # define pWTCON        0x53000000
    126 # define INTMSK        0x4A000008    /* Interupt-Controller base addresses */
    127 # define INTSUBMSK    0x4A00001C
    128 # define CLKDIVN    0x4C000014    /* clock divisor register */
    129 #endif
    130 
    131 #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410)
    132     ldr     r0, =pWTCON
    133     mov     r1, #0x0
    134     str     r1, [r0]

    分析代码之前,先来介绍如何查看CONFIG_S3C2400、CONFIG_S3C2410宏是否定义。在start.S文件开头部分,可以看到如下图所示的头文件的引用

    打开config.h文件,可以看到config.h内容如下

    所以#include <config.h>可以替换为#include <configs/smdk2410.h>,查看CONFIG_S3C2400、CONFIG_S3C2410宏是否定义,在config/smdk2410.h文件中直接搜索即可。

    (config.h文件是根据配置U-boot命令自动生成,关于U-boot配置命令介绍,可以参考这篇文章:https://www.cnblogs.com/053179hu/p/9266553.html)

    下面来分析代码:

    第120行,使用#if语句进行判断是否定义了CONFIG_S3C2400这个宏,若定义了这个宏,则第121~123处语句有效

    第124行,若120行#if语句为假,执行#elif处代码,判断是否定义CONFIG_S3C2410这个宏,则125~128处语句有效

    第131行,使用#if语句判断是否定义CONFIG_S3C2400、CONFIG_S3C2410宏的任何一个,若定义则执行下面代码

    这里我们引用的是smdk2410.h头文件,在该头文件中CONFIG_S3C2410宏被定义,编译时#elif下语句被编译。125~128行是对硬件外设寄存器地址宏定义,以pWTCON为例,编译器进行预处理时遇到pWTCON即把它换为0x53000000,进行宏定义的目的,是使编写代码更加方便。

            

    第132行,将WTCON寄存器地址,复制到r0寄存器中

    第133行,将立即数0移入寄存器r1中

    第134行,将r1内容0,写入到r0地址中,即WTCON寄存器地址

    从硬件手册可以看出当WTCON寄存器第0bit内容为0,即关闭定时器0复位功能,与上面代码相吻合。


     3 关闭中断

    139     mov    r1, #0xffffffff
    140     ldr    r0, =INTMSK
    141     str    r1, [r0]
    142 # if defined(CONFIG_S3C2410)
    143     ldr    r1, =0x3ff
    144     ldr    r0, =INTSUBMSK
    145     str    r1, [r0]
    146 # endif
    147 
    148     /* FCLK:HCLK:PCLK = 1:2:4 */
    149    /* default FCLK is 120 MHz ! */
    150     ldr    r0, =CLKDIVN
    151     mov    r1, #3
    152    str    r1, [r0]
    153 #endif    /* CONFIG_S3C2400 || CONFIG_S3C2410 */        

     

    第139~145行,向INMSK和INTSUBMSK寄存器相应为位写1,屏蔽中断源

    第148~152,设置时钟分频系数

    4 关闭MMU、设置RAM时序

    159 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
    160     bl    cpu_init_crit
    161 #endif

    bl是跳转指令,除了包含b指令的单纯的跳转功能,在跳转之前,还把r15寄存器=PC=CPU地址,赋值给r14=lr,然后跳转到对应位置,等要做的事情执行完毕后,再用mov pc, lr使得cpu再跳转回来,所以整个逻辑就是调用子程序的意思。

    上面的代码意思很清晰,就是当没有定义CONFIG_SKIP_LOWLEVEL_INIT的时候,就跳转到cpu_init_crit的位置,头文件中未定义CONFIG_SKIP_LOWLEVEL_INIT,CPU将跳转到cpu_init_crit处执行程序,cpu_init_crit入口地址处代码如下:

    240 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
    241 cpu_init_crit:
    242     /*
    243     * flush v4 I/D caches
    244     */
    245     mov    r0, #0
    246     mcr    p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */
    247     mcr    p15, 0, r0, c8, c7, 0    /* flush v4 TLB */
    248
    249     /*
    250      * disable MMU stuff and caches
    251      */
    252     mrc    p15, 0, r0, c1, c0, 0
    253     bic    r0, r0, #0x00002300    @ clear bits 13, 9:8 (--V- --RS)
    254     bic    r0, r0, #0x00000087    @ clear bits 7, 2:0 (B--- -CAM)
    255     orr    r0, r0, #0x00000002    @ set bit 2 (A) Align
    256     orr    r0, r0, #0x00001000    @ set bit 12 (I) I-Cache
    257     mcr    p15, 0, r0, c1, c0, 0
    258 
    259     /*
    260      * before relocating, we have to setup RAM timing
    261     * because memory timing is board-dependend, you will
    262      * find a lowlevel_init.S in your board directory.
    263      */
    264     mov    ip, lr
    265     bl    lowlevel_init
    266     mov    lr, ip
    267     mov    pc, lr
    268 #endif /* CONFIG_SKIP_LOWLEVEL_INIT */

     MCR 指令用于将ARM 处理器寄存器中的数据传送到协处理器寄存器中,格式为:

            MCR 协处理器编号,协处理器操作码1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2。
            其中协处理器操作码1 和协处理器操作码2 为协处理器将要执行的操作,
            源寄存器为ARM 处理器的寄存器,目的寄存器1 和目的寄存器2 均为协处理器的寄存器。

    第242~246行,使I/D cache失效: 协处理寄存器操作,将r0中的数据写入到协处理器p15的c7中,c7对应cp15的cache控制寄存器

    第247行,使TLB操作寄存器失效:将r0数据送到cp15的c8、c7中。C8对应TLB操作寄存器

    第252行,将c1、c0的值写入到r0中

    第257行,将设置好的r0值写入到协处理器p15的c1、c0中,关闭MMU

    第264行,将lr寄存器内容保存到ip寄存器中,用于子程序调用返回

    第265行,跳转到lowlevel_init入口地址执行,lowlevel_init在lowlevel_init.S文件中,代码如下:

    133 lowlevel_init:
    134     /* memory control configuration */
    135     /* make r0 relative the current location so that it */
    136    /* reads SMRDATA out of FLASH rather than memory ! */
    137     ldr     r0, =SMRDATA
    138     ldr    r1, _TEXT_BASE
    139     sub    r0, r0, r1
    140     ldr    r1, =BWSCON    /* Bus Width Status Controller */
    141    add     r2, r0, #13*4
    142 0:
    143     ldr     r3, [r0], #4
    144     str     r3, [r1], #4
    145    cmp     r2, r0
    146     bne     0b
    147 
    148     /* everything is fine now */
    149     mov    pc, lr
    150 
    151     .ltorg
    152 /* the literal pools origin */
    153
    154 SMRDATA:
    155     .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
    156     .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
    157     .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
    158     .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
    159     .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
    160     .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
    161     .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
    162     .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
    163     .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
    164     .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
    165     .word 0x32
    166     .word 0x30
    167     .word 0x30

    此处代码实现内存控制器初始化,为后面代码重定位做准备。

    第137行,将保存设置内存控制器的参数的初始地址(连接地址),保存到r0寄存器中。

    第138行,将程序连接的入口地址存入到r1寄存器中

    第139行,r0=r0-r1,获取保存设置内存控制器的参数的初始地址(此处代码未进行定位,代码处于加载地址中,获取的是在存储器存放的物理地址)

    第140行,将内存控制器的第一个寄存器地址存到r1寄存器中

    第141行,获取保存设置内存控制器的参数的结束地址地址(此处代码未进行定位,代码处于加载地址中,获取的是在存储器存放的物理地址)

    第142~146行,将标号SMRDATA地址处存放的参数,写入到相应寄存器中,设置内存控制器工作方式

    第149行,程序调用返回,返回调用节点

    第266~267,程序调用返回,返回调用节点(PC寄存器内容为bl cpu_init_crit指令地址+4)

    5 代码重定位、设置堆栈SP指针

    163 #ifndef CONFIG_SKIP_RELOCATE_UBOOT
    164 relocate:                /* relocate U-Boot to RAM        */
    165     adr    r0, _start        /* r0 <- current position of code   */
    166     ldr    r1, _TEXT_BASE        /* test if we run from flash or RAM */
    167     cmp     r0, r1                  /* don't reloc during debug         */
    168    beq     stack_setup
    169 
    170     ldr    r2, _armboot_start
    171     ldr    r3, _bss_start
    172     sub    r2, r3, r2        /* r2 <- size of armboot            */
    173     add    r2, r0, r2        /* r2 <- source end address         */
    174 
    175 copy_loop:
    176     ldmia    r0!, {r3-r10}        /* copy from source address [r0]    */
    177     stmia    r1!, {r3-r10}        /* copy to   target address [r1]    */
    178     cmp    r0, r2            /* until source end addreee [r2]    */
    179     ble    copy_loop
    180 #endif    /* CONFIG_SKIP_RELOCATE_UBOOT */
    181 
    182     /* Set up the stack                            */
    183 stack_setup:
    184     ldr    r0, _TEXT_BASE        /* upper 128 KiB: relocated uboot   */
    185     sub    r0, r0, #CFG_MALLOC_LEN    /* malloc area                      */
    186     sub    r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
    187 #ifdef CONFIG_USE_IRQ
    188     sub    r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
    189 #endif
    190     sub    sp, r0, #12        /* leave 3 words for abort-stack    */

    第165行,获取当前代码存放起始地址,存入r0寄存器

    第166行,获取代码连接的初始地址,存入r1寄存器

    第167~168行,比较代码当前存放初始地址和设置连接地址是否相等,如果代码当前存放地址等于连接地址,则跳转stack_setup入口地址,设置堆栈;否则,执行重定位操作

    第170~173行,获取U-boot代码长度,并进长度值存入r2寄存器中

    第175~179行,循环操作,将代码从加载地址,复制到连接地址。

      ldmia r0!, {r3-r10}  从源地址[r0]读取4个字节到寄存器(低地址存入低编号寄存器,高地址存入高编号寄存器),每读一次就更新一次r0地址 ,r0=r0+4

        存放形式 [r0]-->r3 r0=r0+4;[r0]-->r4 r0=r0+4;........................[r0]-->r10 r0=r0+4

           stmia r1!, {r3-r10}  拷贝寄存器r3-r10的值保存到 [r1]指明的地址(低地址存入低编号寄存器,高地址存入高编号寄存器),每写一个字节,r1=r1+4,

          存放形式 r3-->[r1]  r1=r1+4;r4-->[r1]  r1=r1+4;........................r10-->[r1]  r1=r1+4

    第183~190行,设置堆栈,CFG_MALLOC_LEN 、CFG_GBL_DATA_SIZE、CONFIG_STACKSIZE_IRQ、CONFIG_STACKSIZE_FIQ等宏在smdk2410.h中有定义,内存使用如下图所示:

    6 清除bss段

    85 .globl _bss_start
    86 _bss_start:
    87     .word __bss_start
    88 
    89 .globl _bss_end
    90 _bss_end:
    91     .word _end
    192 clear_bss:
    193     ldr    r0, _bss_start        /* find start of bss segment        */
    194     ldr    r1, _bss_end        /* stop here                        */
    195     mov     r2, #0x00000000        /* clear                            */
    196 
    197 clbss_l:str    r2, [r0]        /* clear loop...                    */
    198     add    r0, r0, #4
    199     cmp    r0, r1
    200     ble    clbss_l

    第85~91行,_bss_start,_bss_end为标号地址中存放bss段起始地址和结束地址,_bss_start和_end在连接脚本中定义u-boot-1.1.6oardsmdk2410u-boot.lds,程序连接时动态确定。

    第193~194行,把bss段起始地址存入r0寄存器中,结束地址存放到r1寄存器中。

    第197~200行,循环操作,将bss段中的内存清零

    7 跳转到u-boot第二阶段入口

    223 ldr    pc, _start_armboot
    224 
    225 _start_armboot:    .word start_armboot

     初始化外设完成之后,程序跳转到u-boot第二阶段入口函数start_armboot。ldr pc,_start_armboot为绝对跳转命令,pc值等于_start_armboot的连接地址,程序跳到SDRAM中执行,再辞之前程序都是在flash中运行的,绝对跳转必须在初始SDRAM,执行代码重定位之后才能进行。

    8 异常中断处理

    ARM920T架构CPU异常向量表如下图所示:

    当CPU发生异常时,程序计数器跳到相应异常向量表地址处读取指令。由上图很容易看出,不同异常入口地址之间只有4个字节,在这里肯定不能存放异常处理函数。因此我们在中断向量地址处存放相应跳转指令,当发生异常时CPU跳到相应异常地址,读取跳转指令,跳转到相应异常处理函数处执行异常处理。start.S代码处理如下:

    41 .globl _start
    42 _start:    b       reset
    43     ldr    pc, _undefined_instruction
    44     ldr    pc, _software_interrupt
    45     ldr    pc, _prefetch_abort
    46     ldr    pc, _data_abort
    47     ldr    pc, _not_used
    48     ldr    pc, _irq
    49     ldr    pc, _fiq
    50 
    51 _undefined_instruction:    .word undefined_instruction
    52 _software_interrupt:    .word software_interrupt
    53 _prefetch_abort:    .word prefetch_abort
    54 _data_abort:        .word data_abort
    55 _not_used:        .word not_used
    56 _irq:            .word irq
    57 _fiq:            .word fiq
    58 
    59     .balignl 16,0xdeadbeef

     globl是个关键字,意思很简单,就是相当亍C语言中的extern,声明此变量,并且告诉链接器此变量是全局的,外部可以访问。 

    第41行,是上电或者复位后执行第一题指令,通过b命令跳转到reset地址处进行一系列初始化操作,reset地址标号后的代码已经在上面分析了。

    第43~57行,以_undefined_instruction为例,就是,此处分配了一个word=32bit=4字节的地址空间,里面存放的值是undefined_instruction。

    而此处_undefined_instruction也就是该地址空间的地址了。用C语言来表达就是:

                                 _undefined_instruction = &undefined_instruction

                                 或 *_undefined_instruction = undefined_instruction

    在后面的代码,我们可以看到,undefined_instruction也是一个标号,即一个地址值,对应着就是在发生“未定义指令”的时候,系统所要去执行的代码。(其他几个对应的“软件中断”,“预取指错误”,“数据错误”,“未定义”,“(普通)中断”,“快速中断”,也是同样的做法,跳转到对应的位置执行对应的代码。)

    第59行,意思就是,接下来的代码,都要16字节对齐,不足之处,用0xdeadbeef填充。 

    总结:uboot第一阶段

    1、完成了硬件设备初始化操作

    2、为加载Bootloader的第二阶段准备好RAM空间

    3、实现代码重定位

    4、设置好栈,并跳转到第二阶段入口函数

  • 相关阅读:
    基督山伯爵---大仲马
    数据结构
    11. 标准库浏览 – Part II
    python 标准库
    Python 官方文件
    Python 函数
    学员名片管理系统
    如何进入多级菜单
    Python 文件操作
    Python 字符串 (isdigit, isalnum,isnumeric)转
  • 原文地址:https://www.cnblogs.com/053179hu/p/9313548.html
Copyright © 2011-2022 走看看