U-boot启动流程

正常宿主机对u-boot完成编译后,要对编译好的u-boot进行移植烧录,将一编译好的u-boot.bin文件烧写到sd卡或者是开发板的flash存储器中,

  1. 在开发板上电后,板载cpu来引导internal iROM(含有烧写的代码)引导flash开始执行工作(因为在iRAM中封装了flash的启动函数),波动拨码开关来告诉系统从哪个flash内存中哪个存储器启动(nand、sd/mmc、eMMC、usb),检测boot loader是否有效并将boot loader读取到SRAM中,
  2. 在SRAM将boot loader启动的同时初始化DRAM存储器以扩展内存,之后再boot loader的引导下将u-boot的镜像读取到DRAM中,u-boot启动开始等待指令输出或者加载启动内核。
  3. 所对应的函数存放在arch/arm/cpu/u-boot.lds链接脚本中

uboot启动流程如下:

 1.首先查看arch/arm/cpu/u-boot.lds链接脚本

  如下图所示,看到uboot最开始会进入_start:

OUTPUT_FORMAT(“elf32-littlearm”, “elf32-littlearm”,” elf32-littlearm)
OUTPUT_ARCH(arm)  //设置输出文件的体系结构
ENTRY(_start)         //将 _start这个全局变量设置为入口地址
SECTIONS
{
  . =0x00000000;
  . =ALIGN(4);
  .text:
}

 2. _start位于arch/arm/cpu/arm920t/start.S

  所以,我们从start.S开始分析uboot启动流程:

.globl _start                                //声明_start全局符号,这个符号会被lds链接脚本用到

_start:   

b     start_code                            //跳转到start_code符号处,0x00

       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                                      //0x20



_undefined_instruction:  .word undefined_instruction

           //定义_undefined_instruction指向undefined_instruction(32位地址)



_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        //balignl使用,参考http://www.cnblogs.com/lifexy/p/7171507.html

  其中符号保存的地址都在顶层目录/system.map中列出来了

 3. 从上面看到, _start会跳转到start_code处

start_code:



    /*设置CPSR寄存器,让CPU进入管理模式*/

       mrs  r0, cpsr                 //读出cpsr的值

       bic   r0, r0, #0x1f           //清位

       orr   r0, r0, #0xd3          //位或

       msr  cpsr, r0                 //写入cpsr



#if   defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)

       /*

        * relocate exception table

        */

       ldr   r0, =_start           

       ldr   r1, =0x0                //r1等于异常向量基地址

       mov r2, #16

copyex:

       subs       r2, r2, #1           //减16次,s表示每次减都要更新条件标志位

       ldr   r3, [r0], #4      

       str   r3, [r1], #4      //将_start标号后的16个符号存到异常向量基地址0x0~0x3c处

       bne  copyex             //直到r2减为0

#endif



#ifdef CONFIG_S3C24X0



       /* 关看门狗*/

#  define pWTCON       0x53000000

#  define INTMSK 0x4A000008    /* Interrupt-Controller base addresses */

#  define INTSUBMSK  0x4A00001C

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



       ldr   r0, =pWTCON      

       mov r1, #0x0       

       str   r1, [r0]           //关看门狗,使WTCON寄存器=0



       /*关中断*/

       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 ,而FCLK默认为120Mhz*/

       ldr   r0, =CLKDIVN

       mov r1, #3

       str   r1, [r0]



 #ifndef CONFIG_SKIP_LOWLEVEL_INIT

       bl    cpu_init_crit                         //关闭mmu,并初始化各个bank



#endif



call_board_init_f:

       ldr   sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80

       bic   sp, sp, #7         //sp=0x30000f80

       ldr   r0,=0x00000000

       bl    board_init_f  

  上面的CONFIG_SYS_INIT_SP_ADDR =0x30000f80,是通过arm-linux-objdump -D u-boot>u-boot.dis生成反汇编,然后从u-boot.dis得到的,如下图所示:

00000098 <call _board_init_f>:
      98: e59fd3d8  ldr  sp, [pc, #984] ; 478 <.text+0x478>
      478: 30000f80  andcc r0, r0, r0, lsl, #31

 4.然后进入第一个C函数:board_init_f()

  该函数主要工作是:

  清空gd指向的结构体、通过init_sequence函数数组,来初始化各个函数以及逐步填充gd结构体,最后划分内存区域,将数据保存在gd里,然后调用relocate_code()对uboot重定位

  (gd是用来传递给内核的参数)

  1)具体代码如下所示:


void board_init_f(ulong bootflag) // bootflag=0x00000000

{

       bd_t *bd;

       init_fnc_t **init_fnc_ptr;         //函数指针

       gd_t *id;

       ulong addr, addr_sp;

#ifdef CONFIG_PRAM

       ulong reg;

#endif



       bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");

       /* Pointer is writable since we allocated a register for it */

       gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);

  其中gd是一个全局变量,用来传递给内核的参数用的,如下图所示,在arch/arn/include/asm/global_data.h中定义,*gd指向r8寄存器,所以r8专门提供给gd使用

  而CONFIG_SYS_INIT_SP_ADDR,在6节里得到=0x30000f80,所以gd=0x30000f80

  2)继续来看board_init_f():

      __asm__ __volatile__("": : :"memory");           //memory:让cpu重新读取内存的数据



      memset((void *)gd, 0, sizeof(gd_t));        //将0x30000f80地址上的gd_t结构体清0



      gd->mon_len = _bss_end_ofs; 

         // _bss_end_ofs =__bss_end__ - _start,在反汇编找到等于0xae4e0,所以mon_len等于uboot的数据长度

      gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob);



       for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)

            //调用init_sequence[]数组里的各个函数

      {

              if ((*init_fnc_ptr)() != 0)     //执行函数,若函数执行出错,则进入hang()

             {               hang ();   //打印错误信息,然后一直while

             }



       }

  上面的init_sequence[]数组里存了各个函数,比如有:

  board_early_init_f():设置系统时钟,设置各个GPIO引脚

  timer_init():初始化定时器

  env_init():设置gd的成员变量

  init_baudrate():设置波特率

  dram_init():设置gd->ram_size= 0x04000000(64MB)

  3)继续来看board_init_f():

addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;  // addr=0x34000000

// CONFIG_SYS_SDRAM_BASE:  SDRAM基地址,为0X30000000

// gd->ram_size:          等于0x04000000





#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))

       /* reserve TLB table */

       addr -= (4096 * 4);        //addr=33FFC000



       addr &= ~(0x10000 - 1);  // addr=33FF0000,  



       gd->tlb_addr = addr;   //将64kB分配给TLB,所以TLB地址为33FF0000~33FFFFFF

#endif



       /* round down to next 4 kB limit */

       addr &= ~(4096 - 1);                    //4kb对齐, addr=33FF0000

       debug("Top of RAM usable for U-Boot at: %08lx\n", addr);

       /*

        * reserve memory for U-Boot code, data & bss

        * round down to next 4 kB limit

        */

       addr -= gd->mon_len; // 在前面分析过gd->mon_len=0xae4e0,

                           //所以addr=33FF0000 -0xae4e0=33F41B20,



       addr &= ~(4096 - 1);  //4095=0xfff,4kb对齐, addr=33F41000

                             //所以分配给uboot各个段的重定位地址为33F41000~33FFFFFF

       debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);



#ifndef CONFIG_SPL_BUILD

       addr_sp = addr - TOTAL_MALLOC_LEN; //分配一段malloc空间给addr_sp

                       //TOTAL_MALLOC_LEN=1024*1024*4,所以malloc空间为33BF1000~33F40FFF



       addr_sp -= sizeof (bd_t);            //分配一段bd_t结构体大的空间

    bd = (bd_t *) addr_sp;               //bd指向刚刚分配出来的bd_t结构体

    gd->bd = bd;                         // 0x30000f80处的gd变量的成员bd等于bd_t基地址



    addr_sp -= sizeof (gd_t);              //分配一个gd_t结构体大的空间

    id = (gd_t *) addr_sp;                 //id指向刚刚分配的gd_t结构体

    gd->irq_sp = addr_sp;                 //0x30000f80处的gd变量的成员irq_sp等于gd_t基地址

    addr_sp -= 12;

    addr_sp &= ~0x07;

    ... ...



    relocate_code(addr_sp, id, addr);  //进入relocate_code()函数,重定位代码,以及各个符号

    // addr_sp: 栈顶,该栈顶向上的位置用来存放gd->irq_sp、id 、gd->bd、malloc、uboot、TLB(64kb),

    //id:       存放 gd_t结构体的首地址

    // addr:    等于存放uboot重定位地址33F41000

}

  执行完board_init_f()后,最终DRAM内存会划分如下图所示:

  其实此时uboot还在flash中运行,然后会进入start.S的relocate_code()里进行uboot重定位

 5.接下来进入重定位

  1)start.S的relocate_code()代码如下所示

relocate_code:

       mov r4, r0      /* save addr_sp */              // addr_sp栈顶值

       mov r5, r1      /* save addr of gd */           // id值

       mov r6, r2      /* save addr of destination */  // addr值:uboot重定位地址



       /* Set up the stack        */

stack_setup:

       mov sp, r4                //设置栈addr_sp

       adr  r0, _start           //在顶层目录下system.map符号文件中找到_start =0,所以r0=0

       cmp r0, r6                //判断_start(uboot重定位之前的地址)和addr(重定位地址)是否一样

       beq clear_bss             /* skip relocation */

       mov r1, r6             /* r1 <- scratch for copy_loop */ //r1= addr(重定位地址)

       ldr   r3, _bss_start_ofs               //_bss_start_ofs=__bss_start - _start(uboot代码大小)

       add r2, r0, r3         /* r2 <- source end address*/   //r2= uboot重定位之前的结束地址



copy_loop:

       ldmia      r0!, {r9-r10}  /* copy from source address [r0] */

                              //将r0处的两个32位数据拷到r9-r10中,然后r0+=8



       stmia      r1!, {r9-r10}  /* copy to   target address [r1]*/

                             //将拷出来的两个数据放入r1(重定位地址)处,然后r1+=8



       cmp r0, r2  /* until source end address [r2]*/   //判断拷贝的数据是否到结束地址

       blo  copy_loop

  上面只是把代码复制到SDRAM上,而链接地址内容却没有改变,比如异常向量0x04的代码内容还是0x1e0,

  我们以异常向量0x04为例,来看它的反汇编:

00000000 <_image_copy_start>:
       0: ea000013 b 54 ‹start code›
       4: e59ff014 ldr pc, [pc, #20] ; 20 < undefined instruction>
       8: e59ff014 ldr pc, [pc, #20] ; 24 < software interrupt›
       c: e59ff014 ldr pc, [pc, #20] ; 28 <_prefetch_abort>
      10: e59ff014 ldr pc, [pc, #20] ; 2c <_data_abort>
      14: e59ff014 ldr pc, [pc, #20] ; 30 < not used>
      18: e59ff014 ldr pc, [pc, #20] ; 34 < irg>
      1c: e59ff014 1dr pc, [pc, #20] ; 38 <_fig>
     /*1.读出0x20的内容为0x1e0,PC即跳到0x1e0*/

00000020 < undefined instruction>:
      20: 000001e0 andeq r0, r0, r0, ror #3

       如上图所示,即使uboot在SDRAM运行,由于代码没修改,PC也会跳到0x1e0(flash地址)上

  和之前老的uboot有很大区别,以前老的uboot直接是使用的SDRAM链接地址,如下图所示:

 

  所以,新的uboot采用了动态链接地址的方法,在链接脚本uboot.lds中,可以看到这两个段(.rel.dyn、.dynsym):

. rel.dyn:
{
  rel_dyn_start = .;
  *(.rel*)
  rel_dyn_end = .;
}
. dynsym: 
{
  _dynsym_start
  *(. dynsym)
}

       该两个段里,便是保存了各个文件的相对动态信息(.rel.dyn)、动态链接地址的符号(.dynsym)

  以上图的.rel.dyn段为例来分析,找到__rel_dyn_start符号处:

00066568 <rel_dyn_start>:
   66568: 00000020 andeq r0, r0, r0, lsr #32
   6656c: 00000017 andeq r0, r0, r7, 1sl r0
   66570: 00000024 andeq r0, r0, r4, 1sr #32
   66574: 00000017 andeq r0, r0, r7, 1sl r0
   66578: 00000028 andeq r0, r0, r8, 1sr #32
   6657c: 00000017 andeq r0, r0, r7, lsl r0
   66580: 0000002c andeq r0, r0, ip, 1sr #32
   66584: 00000017 andeq r0, r0, r7, 1sl r0
   66588: 00000030 andeg r0, r0, re, lsr r0
   6658c: 00000017 andeq r0, r0, r7, lsl r0

  如上图所示,其中0x17表示的是符号的结束标志位,我们以0x20为例来讲解:

  在之前,我们讲过0x20里面保存的是异常向量0x04跳转的地址(0x1e0),如下图所示:

  所以,接下来的代码,便会根据0x20里的值0x1e0(flash地址),将SDRAM的33F41000+0x20的内容改为33F41000+0x1e0(SDRAM地址),来改变uboot的链接地址

  2)重定位的剩余代码,如下所示:

#ifndef CONFIG_SPL_BUILD

       /*

        * fix .rel.dyn relocations

        */

       ldr   r0, _TEXT_BASE             /* r0 <- Text base */  //r0=text段基地址=0

       sub  r9, r6, r0         /* r9 <- relocation offset */   //r9= 重定位后的偏移值=33F41000

       ldr   r10, _dynsym_start_ofs  /* r10 <- sym table ofs */

                                          //_dynsym_start_ofs =__dynsym_start - _start=0x73608

                                          //所以r10=动态符号表的起始偏移值=0x73608



       add r10, r10, r0            /* r10 <- sym table in FLASH */

                                       //r10=flash上的动态符号表基地址=0x73608



       ldr   r2, _rel_dyn_start_ofs     /* r2 <- rel dyn start ofs */

                                          //r2=__rel_dyn_start - _start=0x6b568

                                          //所以r2=相对动态信息的起始偏移值=0x6b568



       add r2, r2, r0         /* r2 <- rel dyn start in FLASH */

                                      //r2=flash上的相对动态信息基地址=0x6b568



       ldr   r3, _rel_dyn_end_ofs      /* r3 <- rel dyn end ofs */

                                          // _rel_dyn_end_ofs=__rel_dyn_end - _start=00073608

                                          //所以r3=相对动态信息的结束偏移值=00073608

       add r3, r3, r0         /* r3 <- rel dyn end in FLASH */

                                    //r3=flash上的相对动态信息结束地址=0x6b568

fixloop:

       ldr   r0, [r2]           /* r0 <- location to fix up, IN FLASH! */

                               //以0x20为例,r0=0x6b568地址处的内容= 0x20



       add r0, r0, r9         /* r0 <- location to fix up in RAM */

                                     //r0=33F41000+0x20=33F41020



       ldr   r1, [r2, #4]             //r1= 33F41024地址处的内容=0x17

       and  r7, r1, #0xff      

       cmp r7, #23                  /* relative fixup? */  //0x17=23,所以相等

       beq fixrel                                       //跳到:fixerl



       cmp r7, #2                    /* absolute fixup? */

       beq fixabs

       /* ignore unknown type of fixup */

       b     fixnext

fixabs:

       /* absolute fix: set location to (offset) symbol value */

       mov r1, r1, LSR #4         /* r1 <- symbol index in .dynsym */

       add r1, r10, r1              /* r1 <- address of symbol in table */

       ldr   r1, [r1, #4]             /* r1 <- symbol value */

       add r1, r1, r9         /* r1 <- relocated sym addr */

       b     fixnext



fixrel:

       /* relative fix: increase location by offset */

       ldr   r1, [r0]                  //r1=33F41020地址处的内容=0x1e0

       add r1, r1, r9                //r1=0x1e0+33F41000= 33F411e0



fixnext:

       str   r1, [r0]             //改变链接地址里的内容, 33F41020=33F411e0  (之前为0x1e0)  

       add r2, r2, #8             //r2等于下一个相对动态信息(0x24)的地址

       cmp r2, r3                //若没到尾部__rel_dyn_end,便继续执行: fixloop

       blo  fixloop                

#endif

  6.清除bss段

/*重定位完成后,清除bss段*/

clear_bss:

 #ifndef CONFIG_SPL_BUILD

       ldr   r0, _bss_start_ofs                        //获取flash上的bss段起始位置

       ldr   r1, _bss_end_ofs                          //获取flash上的bss段结束位置

       mov r4, r6                    /* reloc addr */     //获取r6(SDRAM上的uboot基地址)

       add r0, r0, r4                                  //加上重定位偏移值,得到SDRAM上的bss段起始位置

       add r1, r1, r4                                     //得到SDRAM上的bss段结束位置

       mov r2, #0x00000000           /* clear*/



clbss_l:

    str    r2, [r0]           /* clear loop...       */                 //开始清除SDRAM上的bss段

       add r0, r0, #4

       cmp r0, r1

       bne  clbss_l

       bl coloured_LED_init

       bl red_led_on

#endif

 7. 跳转到board_init_r()函数,启动流程结束

#ifdef CONFIG_NAND_SPL                   //未定义,所以不执行

  ... ...                         

#else                                   //执行else



       ldr   r0, _board_init_r_ofs         //r0=flash上的board_init_r()函数地址偏移值

       adr  r1, _start                    //0

       add lr, r0, r1                     //返回地址lr=flash上的board_init_r()函数

       add lr, lr, r9                     //加上重定位偏移值(r9)后,lr=SDRAM上的board_init_r()函数



       /* setup parameters for board_init_r */

       mov r0, r5             /* gd_t */              //r0=id值

       mov r1, r6             /* dest_addr */         //r1=uboot重定位地址

       /* jump to it ... */

       mov pc, lr              //跳转:  board_init_r()函数



_board_init_r_ofs:

       .word board_init_r - _start        //获取在flash上的board_init_r()函数地址偏移值



#endif

  从上面代码看出, 接下来便会进入uboot的board_init_r()函数,该函数会对各个外设初始化、环境变量初始化等.

uboot的启动过程到此便结束了.


总结

看图说话

 

  • 9
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值