当前位置: > 华清远见教育科技集团 > 嵌入式学习 > 讲师博文 > U-BOOT 第一阶段分析(续)
U-BOOT 第一阶段分析(续)
时间:2016-12-12作者:华清远见

一、概述

芯片手册:S5PC100_UM_REV1.02.pdf,有如下描述:



S5PC100有32K ROM(iROM),96K SRAM(IRAM),复位后,程序在iROM运行。Bootloader分为2个阶段BL0和BL1。

BL0程序在iROM中,三星保密。功能:从启动设备(比如nand flash)加载BL1(u-boot的前16K)到iRAM。

BL1:IRAM中16K代码执行,会完成内存控制器的初始化把uboot从nand flash拷贝到DRAM,即第一次搬运,经常设定为搬到内存的0x27e00000地址。

u-boot终运行在高端地址,所以,有时u-boot剩下的代码,把u-boot代码做了个第二次搬运,搬到高端地址去。还把OS镜像,拷贝到了内存中。

二、u-boot第一阶段分析(续)

由汇编转到了C,分析board_init_f函数:
         void board_init_f(ulong bootflag)
         {
                 bd_t *bd;
                 init_fnc_t **init_fnc_ptr;
                 gd_t *id;
                 ulong addr, addr_sp;
         #ifdef CONFIG_PRAM
 &nbsnbsp;               ulong reg;
         #endif

涉及到两个重要的数据结构:1)bd_t结构体,关于开发板信息(波特率,ip, 平台号,启动参数)。2)gd_t结构体成员主要是一些全局的系统初始化参数。需要用到时,用宏定义DECLARD_GLOBAL_DATA_PTT,指定占用寄存器r8,具体定义如下:

typedef struct bd_info {
                 int                  bi_baudrate; /* serial console baudrate串口波特率 */
                 unsigned long                  bi_ip_addr; /* IP Address IP 地址*/
                 ulong                  bi_arch_number; /* unique id for this board 板子的id */
                 ulong                  bi_boot_params; /* where this board expects params 启动参数*/
                 struct                  /* RAM configuration RAM 配置*/
                 {
                 ulong start;
                 ulong size;
                 }                  bi_dram[CONFIG_NR_DRAM_BANKS];
         } bd_t;

Gd_t结构体定义,如下:
        typedef struct global_data {
                bd_t *bd;
                unsigned long flags; //指示标志,如设备已经初始化标志等
                unsigned long baudrate; //串行口通信速率
                unsigned long have_console; /* serial_init() was called */
          #ifdef CONFIG_PRE_CONSOLE_BUFFER
                unsigned long precon_buf_idx; /* Pre-Console buffer index */
          #endif
                unsigned long env_addr; /* Address of Environment struct 环境参数地址 */
                     unsigned long env_valid; /* Checksum of Environment valid? 环境参数CRC检验有效标志*/
                unsigned long fb_base; /* base address of frame buffer 帧缓冲区基地址*/
         ……
         } gd_t;
        gd=(gd_t *)(CONFIG_SYS_INIT_SP_ADDR) &~0x07) .

CONFIG_SYS_INIT_SP_ADDR的定义如下:
        #define CONFIG_SYS_INIT_SP_ADDR (0x22000)
        0x22000 & ~0x07,为了保证结果八字节对齐。
        查芯片手册,可知0x0002_0000----0x0003_8000范围,为96K,是IRAM的空间。
        即gd指向IRAM的一个地址。

在本文中前面有声明DECLARE_GLOBAL_DATA_PTR
跟踪定义可看到下面形式:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8") 这个声明告诉编译器使用寄存器r8来存储gd_t类型的指针gd,即这个定义声明了一个指针,并且指明了它的存储位置。(即gd值放在寄存器r8中)
register表示变量放在机器的寄存器
volatile用于指定变量的值可以由外部过程异步修改
并且这个指针现在被赋值为CONFIG_SYS_INIT_SP_ADDR) &~0x07, __asm__ __volatile__("": : :"memory"); memset((void *)gd, 0, sizeof(gd_t));

memory 强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。

__asm__用于指示编译器在此插入汇编语句。

__volatile__用于告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里的汇编。 memory强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单元中的数据将作废。cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去优化指令,而避免去访问内存。

"":::表示这是个空指令。

gd->mon_len = _bss_end_ofs; _bss_end_ofs的定义在start.S中: .globl _bss_end_ofs

_bss_end_ofs:
        .word __bss_end__ - _start
                .word就是在当前地址_bss_end_ofs放值 __bss-end__ - _start。
        看到是所以段的大小,此时,新开终端。打开u-boot.lds,找__bss_end__ Gd->mon_len存的值就是u-boot的实际大小。

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
                if ((*init_fnc_ptr)() != 0) {
            &nnbsp;   hang ();
                }
        }

init_fnc_t **init_fnc_ptr。跟踪init_fnc_t, 如下:
        typedef int (init_fnc_t) (void);
        是个函数指针类型。

当前这个文件里有init_sequence数组:
        init_fnc_t *init_sequence[] = {
        …
              timer_init, /* initialize timer */
        …
              env_init,       /* initialize environment */
              init_baudrate,       /* initialze baudrate settings */
              serial_init,       /* serial communications setup */
              console_init_f,       /* stage 1 init of console */
              display_banner,       /* say that we are here */
        …
              dram_init,       /* configure available RAM banks */
              NULL,
        };

通过循环,调用了函数指针数组中的一系列初始化函数:arch_cpu_init,timer_init,env_int,init_baudrate, serial_init,console_init_f,         display_banner, dram_init。
        串口初始化函数调用后,就可以用串口了,可以用printf来调试,串口初始化之前,串口可以工作之前,可用点灯的方式。

继续分析board_init_f:
        addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;

这样addr是内存的高地址,0x20000000+256M
        关于gd->ram_size,在初始化函数dram_init中,已经给过值了

#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
                /* reserve TLB table */
                addr -= (4096 * 4);
        //留给TLB,16K
                /* round down to next 64 kB limit */
                addr &= ~(0x10000 - 1);
        //64K对齐
                gd->tlb_addr = addr;
                debug("TLB table at: %08lx\n", addr);
         #endif

         /* round down to next 4 kB limit */
                addr &= ~(4096 - 1);
          // K对齐,此处前面已经64K对齐了,就不需改动

addr -= gd->mon_len;
                addr &= ~(4096 - 1);
        预留u-boot实际大小空间,4K对齐。

#ifndef CONFIG_SPL_BUILD
                addr_sp = addr - TOTAL_MALLOC_LEN;
                /*
                     * (permanently) allocate a Board Info struct
                    * and a permanent copy of the "global" data
                */
               addr_sp -= sizeof (bd_t);
               bd = (bd_t *) addr_sp;
               gd->bd = bd;

宏CONFIG_SPL_BUILD,用ctags找,在spl/Makefile中能找到,如下:
        CONFIG_SPL_BUILD := y
        export CONFIG_SPL_BUILD
        但是从u-boot根目录的Makefile中,可以看到,ALL-$(CONFIG_SPL),如下:
        ALL-$(CONFIG_SPL) += $(obj)spl/u-boot-spl.bin
        CONFIG_SPL没有定义(include/configs/fsc100.h中没这个宏),因此,相当于spl/Makefile没有执行。

分析TOTAL_MALLOC_LEN:
        #define TOTAL_MALLOC_LEN CONFIG_SYS_MALLOC_LEN
        #define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + (1 << 20))
        #define CONFIG_ENV_SIZE (128 << 10) /* 128KiB, 0x20000 */

addr_sp = addr – TOTAL_MALLOC_LEN;
        =addr-CONFIG_ENV_SIZE + (1 << 20)
        = 128 << 10+1M
        =128K+1M
        128K是预留环境变量空间,1M是预留对空间。

addr_sp -= sizeof (bd_t);
                bd = (bd_t *) addr_sp;
                gd->bd = bd;
        …
        addr_sp -= sizeof (gd_t);
                id = (gd_t *) addr_sp;
                debug("Reserving %zu Bytes for Global Data at: %08lx\n",
        sizeof (gd_t), addr_sp);

/* setup stackpointer for exeptions */
                gd->irq_sp = addr_sp;
                没用
        …
                /* leave 3 words for abort-stack */
                addr_sp -= 12;

/* 8-byte alignment for ABI compliance */
                addr_sp &= ~0x07;
                //8字节对齐

gd->bd->bi_baudrate = gd->baudrate;
/* Ram ist board specific, so move it to board code ... */
dram_init_banksize();
display_dram_config(); /* and display it */

gd->relocaddr = addr;
        //u-boot重新搬运后的起始地址。

gd->start_addr_sp = addr_sp;
        //堆栈指针,堆向下长??

gd->reloc_off = addr - _TEXT_BASE;
        //偏移量=搬后起始地址-27e00000(u-boot下载地址)

debug("relocation Offset is: %08lx\n", gd->reloc_off);

memcpy(id, (void *)gd, sizeof(gd_t));
        // gd在0x22000,SRAM中,此函数给gd的结构体赋值了,拷贝到内存中留的gd结构体中。(或者说把寄存器r8拷贝到内存?)

relocate_code(addr_sp, id, addr);
relocate_code(addr_sp, id, addr);在start.S中定义,C又回到了汇编
(栈指针,全局数据的地方, 搬后起始地址)
经过该函数的处理,内存分配图如下:


继续分析
relocate_code(addr_sp, id, addr);在start.S中定义,C又回到了汇编
(栈指针,全局数据的地方, 搬后起始地址)
完成了第二次搬运过程,把u-boot从27e00000搬到了addr.
/*
* void relocate_code (addr_sp, gd, addr_moni)
*
* This "function" does not return, instead it continues in RAM
* after relocating the monitor code.
*
*/
          .globl relocate_code
relocate_code:
         mov r4, r0 /* save addr_sp */
         mov r5, r1 /* save addr of gd */
         mov r6, r2          /* save addr of destination */
……
/* Set up the stack */
stack_setup:
         mov sp, r4
adr r0, _start
//得到u-boot的链接地址

cmp r0, r6
//判断u-boot是否已经搬移到终地址

moveq r9, #0               /* no relocation. relocation offset(r9) = 0 */
         beq clear_bss               /* skip relocation */
//若不需要再次搬移,直接清bss段。

... mov r1, r6 /* r1 <- scratch for copy_loop */
              ldr r3, _image_copy_end_ofs
//需要搬移的u-boot的大小。

add r2, r0, r3 /* r2 <- source end address */
//r0和r2之间的内容全部搬走。

copy_loop:
              ldmia r0!, {r9-r10} /* copy from source address [r0] */
              stmia r1!, {r9-r10} /* copy to target address [r1] */
              cmp r0, r2 /* until source end address [r2] */
              blo copy_loop
//若u-boot需要自搬移,即不在终地址运行,把u-boot复制到了SDRAM的高端地址

#ifndef CONFIG_SPL_BUILD
               /*
               * fix .rel.dyn relocations
               */
               ldr r0, _TEXT_BASE        /* r0 <- Text base */
               sub r9, r6, r0        /* r9 <- relocation offset */
        //由于u-boot第一阶段用的是绝对地址,所以搬运后继续运行,需要加一个偏移量r9。

       ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
               add r10, r10, r0 /* r10 <- sym table in FLASH */
               ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
               add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
               ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
               add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
        ……
        ldr r0, _board_init_r_ofs
               adr r1, _start
               add lr, r0, r1
               add lr, lr, r9
               /* setup parameters for board_init_r */
               mov r0, r5 /* gd_t */
               mov r1, r6 /* dest_addr */
               /* jump to it ... */
               mov pc, lr
       _board_init_r_ofs:
               .word board_init_r - _start


至此,第一阶段分析完成。
        ∗概括起来,第一阶段主要功能为:
        ∗初始化基本的硬件;
        ∗把bootloader自搬运到内存中;
        ∗设置堆栈指针并将bss段清零。为后续执行C代码做准备;
        ∗跳转到第二阶段代码中.

发表评论
评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)