移植u-boot到mini2440--初始化代码分析

  本文简单的分析下u-boot2016 的初始化汇编代码,并且能从openjtag 加载启动。

代码从 arch/arm/lib/vectors.S 开始执行全局标号 _start:

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
        .word   CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

        b       reset
        ldr     pc, _undefined_instruction
           ......
        ldr     pc, _fiq

  关于 ldr 伪指令,可见前文分析,这里直接执行b指令跳转到reset位置处。关于b指令可见这里。

  下面我们再来看看 reset 代码段,reset定义在:arch/arm/cpu/arm920t/start.S文件中,reset(英文复位),系统复位时也会从这里开始,处理硬件上的复位,软件复位命令时通过看门狗操作实现。reset开始的代码段实现切换到超级用户模式(SVC 模式)。为什么要在初始化的时候设置为svc模式,可见这里。

  接下来的重定位异常向量表,因为宏没有定义,所以没编译进去,关于这一点可以从生成的u-boot.dis 文件看出来。

  然后就是常说的几个例行操作:关看门狗、屏蔽中断、设置系统时钟,因为现有的系统代码没有对s3c2440设置的支持,所以这里需要加上(直接上代码,具体原理可以见s3c2440芯片手册):

#ifdef CONFIG_S3C24X0
    /* turn off the watchdog */

# if defined(CONFIG_S3C2400)
# define pWTCON 0x15300000
# define INTMSK 0x14400008 /* Interrupt-Controller base addresses */
# define CLKDIVN 0x14800014 /* clock divisor register */
#else
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */
# endif

    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]
# elif defined(CONFIG_S3C2440)
    ldr r1, =0x7fff
    ldr r0, =INTSUBMSK
    str r1, [r0]
# endif

# if defined(CONFIG_S3C2440) 
# define MPLLCON 0x4C000004 /* 系统主频配置寄存器 */
# define UPLLCON 0x4C000008 /* USB频率配置寄存器 */
# define CAMDIVN 0x4C000018 /* CAMERA时钟分频寄存器 */
# define MMDIV_405 (0x7f<<12)
# define MPSDIV_405 0x21
# define UMDIV_48 (0x38<<12)
# define UPSDIV_48 0X22

        ldr r0, =CAMDIVN
        mov r1, #0 
        str r1, [r0]  

    /* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    ldr r0, =CLKDIVN
    mov r1, #0x05 
    str r1, [r0]  

    /* 如果HDIVN不等于0,CPU必须设置为异步总线模式 */
    mrc p15, 0, r0, c1, c0, 0
    orr r0, r0, #0xC0000000
    mcr p15, 0, r0, c1, c0, 0

    ldr r0, =UPLLCON
    mov r1, #UMDIV_48 /* USB时钟48MHz */
    add r1, r1, #UPSDIV_48
    str r1, [r0] 
    /* * When you set MPLL&UPLL values, you have to set the UPLL * value first and then the MPLL value. (Needs intervals * approximately 7 NOP) */           
    nop           
    nop           
    nop           
    nop           
    nop           
    nop           
    nop           
    ldr r0, =MPLLCON
    mov r1, #MMDIV_405 /* cpu时钟 400MHz */
    add r1, r1, #MPSDIV_405
    str r1, [r0]
# else
    /* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    ldr r0, =CLKDIVN
    mov r1, #3
    str r1, [r0]
#endif

#endif /* CONFIG_S3C24X0 */

  然后就是 cpu_init_crit ,这里主要完成dram初始化操作,因为现阶段目标是让u-boot用openjtag加载跑起来,因此先不考虑cpu_init_crit。

  如果不执行 cpu_init_crit ,就会调用函数 _main。来到了arch/arm/lib/crt0.S 文件中。

ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr sp, =(CONFIG_SPL_STACK)
#else
    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
    mov r3, sp
    bic r3, r3, #7
    mov sp, r3
#else
    bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
    mov r0, sp
    bl  board_init_f_mem
    mov sp, r0

    mov r0, #0
    bl  board_init_f

#if ! defined(CONFIG_SPL_BUILD)

/*
 * Set up intermediate environment (new sp and gd) and call
 * relocate_code(addr_moni). Trick here is that we'll return
 * 'here' but relocated.
 */

    ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */

  宏定义 CONFIG_SYS_INIT_SP_ADDR 在文件 mini2440.h 中。注意这两行:

    mov r0, sp
    bl  board_init_f_mem

   C函数 board_init_f_mem(ulong top) 的参数top,就是上面的寄存器r0,这个函数主要完成 global_data 的简单初始化,global_data 是一个很大的数据结构,放在栈中,这里先不讲解,等以后用到了再说。并且把栈减去一个合适的数值返回。

  然后就来到了函数 void board_init_f(ulong boot_flags), 在文件 common/board_f.c ,这个函数主要功能就是完成 init_sequence_f 函数指针数组每个函数的调用。

1> 首先看 setup_mon_len :

gd->mon_len = (ulong)&__bss_end - (ulong)_start;

  这行代码是获取u-boot代码的大小。

2> 然后就是 timer_init : arch/arm/cpu/arm920t/s3c24x0/timer.c 可以看到这个函数做了很多的初始化工作。

3> 接着是 serial_init : drivers/serial/serial.c

int serial_init(void)
{
        gd->flags |= GD_FLG_SERIAL_READY;
        return get_current()->start();
}

  在函数 get_current() 中返回的 dev 是 default_serial_console();最后 get_current()->start(); 调用的函数是 serial_init_dev – drivers/serial/serial_s3c24x0.c,做一些特定串口初始化。

4> 接着是dram_init :board/samsung/mini2440/mini2440.c,简单的初始化 ram_size 为64M。

int dram_init(void)
{
        /* dram_init must store complete ramsize in gd->ram_size */
        gd->ram_size = PHYS_SDRAM_1_SIZE;
        return 0;
}

dram_init实现可以通过配置文件定义宏定义来实现,也可以通过对ddrc控制器读获取dram信息。

5> setup_dest_addr 这个函数初始化 gd->ram_top(可用内存空间顶部)然后赋值

gd->relocaddr = gd->ram_top;

后面的一系列函数会对调整relocaddr 对 sdram 空间进行规划。

        /*
         * now that we have dram mapped and working, we can
         * relocate the code and continue running from dram.
         *
         * reserve memory at end of ram for (top down in that order):
         *  - area that won't get touched by u-boot and linux (optional)
         *  - kernel log buffer
         *  - protected ram
         *  - lcd framebuffer
         *  - monitor code
         *  - board info struct
         */
        setup_dest_addr,
static int setup_dest_addr(void)
{
    gd->ram_top = CONFIG_SYS_SDRAM_BASE;
    ///#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1
    ///#define PHYS_SDRAM_1 0x30000000 /* SDRAM Bank #1 */
    gd->ram_top += gd->ram_size;
    ///到了可用sdram的顶端
    gd->relocaddr = gd->ram_top;
}

6 > 根据 reserve_mmu 的宏定义判断,当 icache 和 dcache 有任意一个打开的时候则预留出PATABLE_SIZE大小的tlb空间,tlb存放首地址赋值给gd->arch.tlb_addr。

#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
                defined(CONFIG_ARM)
static int reserve_mmu(void)
{
        /* reserve TLB table */
        gd->arch.tlb_size = PGTABLE_SIZE;
        gd->relocaddr -= gd->arch.tlb_size;

        /* round down to next 64 kB limit */
        gd->relocaddr &= ~(0x10000 - 1);

        gd->arch.tlb_addr = gd->relocaddr;
        debug("TLB table from %08lx to %08lx\n", gd->arch.tlb_addr,
              gd->arch.tlb_addr + gd->arch.tlb_size);
        return 0;
}
#endif

7 > reserve_uboot

static int reserve_uboot(void)
{
        /*
         * reserve memory for U-Boot code, data & bss
         * round down to next 4 kB limit
         */
        gd->relocaddr -= gd->mon_len;
        gd->relocaddr &= ~(4096 - 1);
        gd->start_addr_sp = gd->relocaddr;
}

  这里 减去 gd->mon_len为 uboot 的code留出空间,到这里addr的值就确定,addr作为uboot relocate的目标addr。

  先总结一下从setup_dest_addr之后到reserve_uboot 之间的函数实现了 addr 之上 sdram 空间的划分,由高到低 (根据宏定义可能还保留有其它的内存块):
top–>hide mem–>tlb space(16K)–>uboot code space–>addr

  然后从 reserve_malloc 开始的函数就是调整 start_addr_sp 保留内存空间了。

8 > reserve_malloc 分配堆空间,这里是 TOTAL_MALLOC_LEN。

9 > reserve_board 为 bd_info 结构体预留空间。
10 > reserve_global_data 为 global_data 结构体预留空间。
11 > reserve_stacks() 首先栈指针16字节对齐

        /* make stack pointer 16-byte aligned */
        gd->start_addr_sp -= 16;
        gd->start_addr_sp &= ~0xf;

  然后调用 arch_reserve_stacks() 这个函数为abort stack分配空间,并且初始化irq_sp,作为异常栈指针。
12 > setup_dram_config() 调用 dram_init_banksize() 后者声明为 __weak 类型,我们可以有自己的实现。这个函数初始化内存的首地址和大小。

13> show_dram_config() 打印 ram 区域分配。

14> setup_reloc() 计算出要重定位的偏移大小:

gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;

  gd->relocaddr 为目标addr,gd->start_addr_sp 为目标 addr_sp ,gd->reloc_off 为目标addr和现在实际code起始地址的偏移。reloc_off非常重要,会作为后面 relocate_code 函数的参数,来实现code的拷贝。
   board_init_f 函数将 sdram 空间重新进行了划分,可以看出栈空间和堆空间是分开的。

board_init_f 初始化过程至此结束。

   然后就是 代码重定位了,关于u-boot 的代码重定位涉及到的知识点很多,包括了编译器选项,反汇编分析,arm寻址方式 其中一位大牛已经写的很详细了,直接附上他讲解的地址吧。

  这里总结几点:
1)对于函数调用用的是相对跳转,这个不受重定位影响。
2)对于全局变量,包括指针变量(指向函数,指向全局变量)在函数的尾部存放着这些变量的地址(叫做LABEL),当要访问这些变量(或者指针)的时候 用PC+相对偏移获取(全局变量,或者指针)的地址。
  如果重定位,对于全局变量只要让函数尾部的(LABEL+相对偏移)就可以 就可以获取到全局变量了。
  但如果这个全局变量是个指针,上面的一步完成之后,那么指针的值也要修改。这个也是通过 rel.dyn 段来完成的。也就是说对于全局指针变量 rel.dyn 有2项数据成员来完成它的重定位操作。

  重定位代码之后就是 relocate_vectors 重定位异常向量,

        /* * Copy the relocated exception vectors to the * correct address * CP15 c1 V bit gives us the location of the vectors: * 0x00000000 or 0xFFFF0000. */
        ldr     r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
        mrc     p15, 0, r2, c1, c0, 0   /* V bit (bit[13]) in CP15 c1 */
        ands    r2, r2, #(1 << 13)
        ldreq   r1, =0x00000000         /* If V=0 */
        ldrne   r1, =0xFFFF0000         /* If V=1 */
        ldmia   r0!, {r2-r8,r10}
        stmia   r1!, {r2-r8,r10}
        ldmia   r0!, {r2-r8,r10}
        stmia   r1!, {r2-r8,r10}

  通过注释可以看出,这里通过读取协处理器,获取异常地址,这里正好要把异常向量放到0x0地址处,这个位置是SRAM空间。(如果是从norflash启动的时候,0x0 地址处 就无法写了)

  然后就是 clbss_l 清除 bss 段,bss 段存放程序中未初始化的全局变量和静态变量。特点是可读写的,在程序执行之前BSS段应该清0。

  接着是 coloured_LED_init ,这个可以适当控制LED灯,实现调试作用。

  最后就是 board_init_r : dest_addr 是新code地址

void board_init_r(gd_t *new_gd, ulong dest_addr)

你可能感兴趣的:(汇编,代码分析,u-boot,初始化代码)