19、Bootloader(3) -- U-Boot第一阶段代码start.S分析

时间:2021-02-01 04:04:10

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

Bootloader第一阶段的功能:(运行在Flash当中)

        -- 硬件设备初始化  (cpu/arm920t/start.S下的reset函数

        -- 为加载Bootloader的第二阶段代码准备RAM空间   (board/smdk2410/lowlevel_init.S)

        -- 复制Bootloader的第二阶段代码到RAM空间中    cpu/arm920t/start.S下的relocate函数

        -- 设置好栈(原因:为下一步准备的,因为C语言的运行需要堆栈)cpu/arm920t/start.S下的stack_setup函数

        -- 跳转到第二阶段代码的C入口点  cpu/arm920t/start.S下的clear_bss函数

硬件初始化包括:将CPU的工作模式设为管理模式(SVC),关闭WATCHDOG、设置FCLK、HCLK、PCLK的比例(即设置CLKDIVN寄存

                器),关闭MMU、CACHE

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

   调用内核前,下列条件要满足:

       (1)CPU寄存器的设置

              R0 = 0

              R1 = 机器类型ID

              R2 = 启动参数标记列表在RAM中起始基地址

       (2)CPU工作模式

              必须禁止中断(IRQs和FIQs)

              CPU必须为SVC模式

       (3)Cache和MMU的设置

              MMU必须关闭

              指令Cache可以打开也可以关闭,数据Cache必须关闭

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

            19、Bootloader(3) -- U-Boot第一阶段代码start.S分析  

                这个图特别重要,在网上流传比较经典的一个图

 

 

第一阶段代码-----源代码“/arch/arm920t/cpu/start.s”分析

 

1.中断向量表的设置

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

 .globl _start

_start:    b       reset

    ldr pc, _undefined_instruction

    ldr pc, _software_interrupt

    ldr pc, _prefetch_abort

    ldr pc, _data_abort

    ldr pc, _not_used

    ldr pc, _irq

    ldr pc, _fiq

 

_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

 

    .balignl 16,0xdeadbeef    //在当前地址处放入一个16bits值

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

 

   中断向量表放在0x0开始的地方,系统上电后,会从0x0处取指令,而0x0处放置的是reset标签,直接就跳去reset标签处去启动系统了

    利用.word来在当前位置放置一个值,这个值实际上就用对应的中断处理函数的地址
     _undefined_instruction:    .word undefined_instruction

undefined_instruction在后面给出了具体的操作:

     undefined_instruction:

            get_bad_stack         //宏:对stack的操作

            bad_save_user_regs    //宏:用户regs的保存

            bl    do_undefined_instruction

    在跳转到中断服务子程序前,先有两个宏代码,一个是对stack的操作,另外一个是用户regs的保存,然后跳转到中断服务子程序中执行。中断子程序都在“cpu/arm920t/interrupts.c”中定义

 

2.U-Boot存储器映射定义

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

_TEXT_BASE:                                                                             

   .word   TEXT_BASE       //编译器指定的内存运行地址

 

.globl _armboot_start                                                                            

_armboot_start:                                                                                   

    .word _start           //_start是程序的入口地址是实际运行的地址     

 

/*                                                                                                 

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

 */                                                                                                

.globl _bss_start         //未初始化的数据段开始地址_bss_start:                                                                                            

    .word __bss_start                                                                             

                                                                                                 

.globl _bss_end           //结束地址                                                                           

_bss_end:                                                                                    

    .word _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                                                                                        

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

        如上图“U-Boot存储器映射”所示                                                        

 

3.上电后CPU为svc模式

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

  reset:                                                                                       

    /*                                                                                     

     * set the cpu to SVC32 mode                                                                 

     */                                                                                      

    mrs r0,cpsr                                                                               

    bic r0,r0,#0x1f    //0x1f=00011111,相当于清除低5位,刚好是模式位      

    orr r0,r0,#0xd3    //0xd3=11010011,设置5,6,7位的状态位。禁止 FIQ,IRQ,处于arm状态 

    msr cpsr,r0                                                                                 

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

CPSR 寄存器(和保存它的 SPSR 寄存器)中的位分配如下:                                        

  31 30 29 28  ---   7   6   5   4   3   2   1   0                                       
  N  Z  C  V         I   F   T   M4  M3  M2  M1  M0                                        
                                                                                            
                                                                                     
                                 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 模式                               

I=1:禁止IRQ中断  F=1:禁止FIQ中断                                                      

T=0:为ARM状态  T=1:为THUMB状态                                                          

                                                                                         

4.关闭看门狗、屏蔽中断、修改时钟除数寄存器

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

 /* 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)                                                                  

# define pWTCON      0x53000000   //喂狗寄存器                                                               

# define INTMSK      0x4A000008   //中断屏蔽寄存器  1:屏蔽中断                                    

# define INTSUBMSK   0x4A00001C   //中断次级屏蔽寄存器  1:屏蔽中断                                                             

# define CLKDIVN     0x4C000014   //时钟分频控制寄存器                                       

#endif                                                                                       

                                                                                                 

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

    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, =INTMSK                                                                             

    str r1, [r0]                                                                                 

# if defined(CONFIG_S3C2410)                                                                                  

    ldr r1, =0x3ff                                                                          

    ldr r0, =INTSUBMSK                                                                             

    str r1, [r0]                                                                             

# endif                                                                                   

 

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

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

 

5.调用cpu_init_crit

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

                                                                                               

#ifndef CONFIG_SKIP_LOWLEVEL_INIT  //防止重复引用cpu_init_crit函数 

    bl  cpu_init_crit                                                                         

#endif                                                                                        

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

                                                                                             

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

#ifndef CONFIG_SKIP_LOWLEVEL_INIT

cpu_init_crit:

    /*

     * flush v4 I/D caches

     */

    mov r0, #0

    mcr p15, 0, r0, c7, c7, 0    /* flush v3/v4 cache */

    mcr p15, 0, r0, c8, c7, 0    /* flush v4 TLB */

 

    /*

     * disable MMU stuff and caches

     */

    mrc p15, 0, r0, c1, c0, 0   //把协处理器c1写入r0中,查看寄存器c1各控制位,《ARM体系结构与编程》174页

   bic r0, r0, #0x00002300  @ clear bits 13, 9:8 (--V- --RS)                               

    bic r0, r0, #0x00000087  @ clear bits 7, 2:0 (B--- -CAM)                                 

    orr r0, r0, #0x00000002  @ set bit 2 (A) Align                                                 

    orr r0, r0, #0x00001000  @ set bit 12 (I) I-Cache                                              

    mcr p15, 0, r0, c1, c0, 0                                                            

 

    /*                                                                                        

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

     */                                                                                        

    mov ip, lr                                                                                

    bl  lowlevel_init                                                                  

    mov lr, ip

    mov pc, lr                                                                                           

#endif /* CONFIG_SKIP_LOWLEVEL_INIT */   

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

mov ip, lr说明

    为了使单独编译的C语言程序和汇编程序之间能相互调用,必须为子程序间的调用规定一定的规则,这就是所谓的ATPCS规则。在ATPCS规则中,寄存器R12作用于子程序间的scratch寄存器,记作ip。如果这里使用别的通用寄存器来代替ip,实现的功能是一样的 

 

6.调用lowlevel_init

    cpu_init_crit函数又调用了lowlevel_init函数,lowlevel_init函数用来设置存储器控制器,使得外接的SDRAM可用,对SDRAM进行初始化,代码在

board/smdk2410/lowlevel_init.S”                                                                 

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

_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    // SMRDATA表示13个寄存器值的存放开始地址(链接地址),处于内存中 

    ldr r1, _TEXT_BASE       // _TEXT_BASE表示编译器指定的内存运行地址 

    sub r0, r0, r1           //r0 = SMRDATA - TEXT_BASE+0x0,r0就是13个寄存器在NOR FLASH上存放的起始地址

                         

    ldr r1, =BWSCON          // BWSCON = 0x48000000                                       

    add     r2, r0, #13*4    //r2 = r0 + 13*4

0:

    ldr     r3, [r0], #4     // [r0]->r3, r0 = r0 + 4                                     

    str     r3, [r1], #4     // r3->[r1], r1 = r1 + 4                                 

                                r1 = BWSCON的地址=0x48000000

                                BANKCON0 = 0x48000004

                                BANKCON1 = 0x48000008

                                BANKCON2 = 0x4800000C

                                BANKCON3 = 0x48000010

                                BANKCON4 = 0x48000014

                                BANKCON5 = 0x48000018

                             

    cmp     r2, r0

    bne     0b

 

    /* everything is fine now */

    mov pc, lr

 

    .ltorg

/* the literal pools origin */

 

SMRDATA:   //13个寄存器的值保存在文字池中

    .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函数中的代码、数据都只保存在NOR FLASH上,内存中还没有,所以读取数据时要变换地址

   关于地址SMRDATA和TEXT_BASE

       SMRDATA:表示这13个寄存器存放的起始地址(连接地址),处于内存中,因为此时代码、数据都只保存在NOR FALSH上,内

                存中还没有,所以要计算出相对在FLASH上对应的地址,然后读取其值。ARM上电后,假设从NAND FLASH运行,它

                会把从NAND FLASH的前4K加载到内存中运行

       TEXT_BASE: 编译器指定的内存运行地址,其在内存中,因为从FLASH中复制代码到内存中时,是从FLASH的0x0地址处开始复

                  制到内存的TEXT_BASE开始处,代码复制过程如下:

       FLASH                  SDRAM(内存)

       0x0  -----------------> TEXT_BASE

   所以 SMRDATA - TEXT_BASE + 0x0 为13个寄存器相对于在FLASH中的起始地址

 

7.代码搬移

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

#ifndef CONFIG_SKIP_RELOCATE_UBOOT

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

    adr r0, _start        //_start是程序的入口地址是实际运行的地址

    ldr r1, _TEXT_BASE    //编译器指定的内存运行地址 

    cmp     r0, r1        //查看当前代码地址信息,若从RAM中运行,则_start = _TEXT_BASE,否则_start = 0x0        

    beq     stack_setup   //若相等,则设置栈

 

    ldr r2, _armboot_start //_armboot_start:第一条指令的运行地址

    ldr r3, _bss_start     //代码段的结束地址

    sub r2, r3, r2         //r2 = 代码长度

    add r2, r0, r2         //r2 = NOR FLASH上代码段的结束地址

copy_loop:

    ldmia  r0!, {r3-r10}     //复制代码,r0为代码的起始地址,r1为ram中地址,r2为代码的终止地址,每次copy后

                              将r0的值递增同r2比较来判断是否复制完成

 

    stmia  r1!, {r3-r10}    

    cmp r0, r2       

    ble copy_loop

#endif /* CONFIG_SKIP_RELOCATE_UBOOT */

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

搬移代码的原因

1.运行速度的考虑

   flash的读写速度远小于SDRAM的读写速度,搬移到SDRAM后,可提高运行效率

2.空间的考虑  

   如果是NANDFLASH启动模式,那么只有4KB的空间供用户使用,实际的代码是远大于4KB的,因此需要重新开辟空间来进行代码的运行工作

 

8.栈空间的设置

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

 

   /* Set up the stack   */

stack_setup:

    ldr r0, _TEXT_BASE          //代码段开始地址

    sub r0, r0, #CFG_MALLOC_LEN  //代码段下面,留出一段实现malloc

    sub r0, r0, #CFG_GBL_DATA_SIZE // 再下面一段内存,存一些全局参数

#ifdef CONFIG_USE_IRQ

    sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)   //IRQ、FIQ模式的栈

#endif

    sub sp, r0, #12          //留12字节的内存给abort异常,往下的内存就是栈了

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

 

9.BSS段的清零

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

clear_bss:

    ldr r0, _bss_start       //BSS段的起始地址

    ldr r1, _bss_end         //BSS段的结束地址

    mov    r2, #0x00000000     

 

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

    add r0, r0, #4

    cmp r0, r1

    ble clbss_l

ldr pc, _start_armboot       //进入C代码执行,_start_armboot函数在lib_arm/board.c

 

_start_armboot:   .word start_armboot

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