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

来源:互联网 发布:淘宝外卖粮票口令分享 编辑:程序博客网 时间:2024/05/17 05:01

  本文简单的分析下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)
0 0