uboot-2015-07的start.S的文件启动过程

Posted YellowMax2001

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uboot-2015-07的start.S的文件启动过程相关的知识,希望对你有一定的参考价值。

1.在文件的最开始有这样的注释

/*
 *************************************************************************
 *
 * 启动代码 (被 ARM 的 reset 调用,不包括中断)
 *
 * 只有不从 memory 启动的时候才做重要的初始化
 * 重定位代码到 ram
 * 设置栈
 * 跳转到启动第二阶段
 *
 *************************************************************************
 */
 
上面已经大致交代了这个start.S文件要做的事情 在 u-boot.lds文件里面指明了入口是 _start标号,所以开始会先加载 _start的内容,而此函数入口在vector.s里面( 与以往内核不同的地方)。里面会设置中断向量表等等,当然,这些在编译的时候已经加载过了,芯片复位的时候会跳到reset处继续执行代码 # 2.首先设置cpu处于管理模式
reset:
    /*
     * set the cpu to SVC32 mode
     */
    mrs r0, cpsr
    bic r0, r0, #0x1f
    orr r0, r0, #0xd3
    msr cpsr, r0
# 3.关看门狗,屏蔽中断
#  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]
    /* 屏蔽所有中断 */
    mov r1, #0xffffffff
    ldr r0, =INTMSK
    str r1, [r0]
# if defined(CONFIG_S3C2410)
    ldr r1, =0x3ff
    ldr r0, =INTSUBMSK
    str r1, [r0]
# endif
# 4.初始化时钟分频系数
    /* FCLK:HCLK:PCLK = 1:2:4 */
    /* default FCLK is 120 MHz ! */
    ldr r0, =CLKDIVN
    mov r1, #3
    str r1, [r0]
# 5.CPU初始化
    /* 擦除DCache与ICache */
    mov r0, #0
    mcr p15, 0, r0, c7, c7, 0   /* flush v3/v4 cache */
    mcr p15, 0, r0, c8, c7, 0   /* flush v4 TLB */

    /* 禁止MMU与Cache */
    mrc p15, 0, r0, c1, c0, 0
    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

    /* 重定位之前, 通过lowlevel_init函数设置RAM时间参数 */
    mov ip, lr

    bl  lowlevel_init

    mov lr, ip
    mov pc, lr
# 6.调用 \\_main 这里开始有点疑惑,怎么没有内存设置以及重定位代码就直接开始 \\_main了,SourceInsight全局搜索**_main**发现在 crt0.S里面有**_main**函数体 **我们先看下_main函数的介绍**
/*
 * 1. 为调用 board_init_f() C函数设置初始化环境
 *    环境仅仅提供栈以及 GD ('global data') 数据结构的存放空间, 只有已经初始化的静态变量可以在该阶段使用
 *
 * 2. 调用 board_init_f()。为系统从RAM启动准备硬件环境, board_init_f() 必须使用 GD 来
 *    存放在最后阶段需要用到的数据。数据包括重定位的目标地址, 未来要使用的栈, 以及未来要使用的 GD 的新地址
 *
 * 3. 为 stack and GD 设置中转环境
 *
 * 4. 调用 relocate_code(). 函数重定位u-boot到 board_init_f() 指定的位置
 *
 * 5. 为调用 board_init_r() 设置最新的环境。初始化BSS,非静态的全局变量和系统RAM中的栈
 *    GD保留被 board_init_f() 设置的值。 有些CPU还有一些关于内存的操作没有进行, 因此要调用 call c_runtime_cpu_setup
 *
 * 6. 跳转到 board_init_r().
 */

7.设置C运行环境并且调用 board_init_f(0)

ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)  /* CONFIG_SYS_INIT_SP_ADDR = 0x30000000 + 0x1000 - GENERATED_GBL_DATA_SIZE = 0x30000f50 */
bic sp, sp, #7  /* 8字节对齐 */
mov r2, sp
sub sp, sp, #GD_SIZE    /* 在栈上面分配GD的空间,GD_SIZE = 168 */
bic sp, sp, #7  /* 8字节对齐 */
mov r9, sp      /* GD is above SP */
mov r1, sp
mov r0, #0
/* 清空GD空间,以待重新赋值 */
clr_gd:
    cmp r1, r2  
strlo   r0, [r1]        /* clear 32-bit GD word */
    addlo   r1, r1, #4      /* move to next */
    blo clr_gd
bl  board_init_f    /* 调用board_init_f对GD等等进行初始化,SP = 30000E80 */

值得说的是上面的GENERATED_GBL_DATA_SIZE宏定义,它是由DEFINE(GENERATED_GBL_DATA_SIZE,(sizeof(struct global_data) + 15) & ~15);获得的,也就是相当于#define GENERATED_GBL_DATA_SIZE ((sizeof(struct global_data) + 15) & ~15)

#define DEFINE(sym, val) \\
    asm volatile("\\n.ascii \\"->" #sym " %0 " #val "\\"" : : "i" (val))

这个语句在编译的时候会被转换为.h文件以供别的文件包含,这个宏定义的作用大概应该是让形如#define GENERATED_GBL_DATA_SIZE ((sizeof(struct global_data) + 15) & ~15)可以随用随定义(此处存在疑问,为什么不直接宏定义呢),补充:这个宏可以使全局变量gd_t可以在.S文件里面使用汇编代码直接访问到,类似 ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ 这样的语句

另外由于 mov r9, sp 这句话的作用,以后要想在u-boot其它文件里面访问此时的SP(也就是gd的位置),就需要在文件头部加上DECLARE_GLOBAL_DATA_PTR,原型是

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r9") 
//在2015版的里面使用下面的函数得到,原理是一样的
static inline gd_t *get_gd(void)
{
    gd_t *gd_ptr;

    __asm__ volatile("mov %0, r9\\n" : "=r" (gd_ptr));

    return gd_ptr;
}

8.单板初始化board_init_f

(需要注意的是,本处选择的board_init_f为board.c里面的,但是从2014以后就默认编译的是board_f里面的函数了,这里为了学习方便,暂且选择board.c里面的函数进行分析,以后再去分析另一个分支代码,切换代码到board.c里面的方法在以后的真正移植过程中会说到)

初始化全局变量gd,调用函数队列

memset((void *)gd, 0, sizeof(gd_t));    /* 初始化gd填充为0 */
gd->mon_len = (ulong)&__bss_end - (ulong)_start;    /* 整个u-boot代码与数据段的总大小,可以由u-boot.lds文件得出 */
/* 调用一个初始化函数队列 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }

下面是init_sequence函数对列里面的函数

    board_early_init_f  /* 时钟初始化,引脚初始化,不同的单板要进行不同的设置 */
    timer_init          /* 定时器初始化 */
    env_init,           /* initialize environment */
    init_baudrate,      /* 波特率初始化,默认CONFIG_BAUDRATE为115200 */
        gd->baudrate = getenv_ulong("baudrate", 10, CONFIG_BAUDRATE);
    serial_init,        /* 串口初始化 */
    console_init_f,     /* console初始化 */
    display_banner,     /* 表明代码运行到这里了 */
    print_cpuinfo,      /* 打印cpu信息 */
    dram_init,      /* 配置可用的RAM大小,默认PHYS_SDRAM_1_SIZE为64M */
        //dram_init 函数内部有 gd->ram_size = PHYS_SDRAM_1_SIZE; 

初始化程序最终存放地址addr

addr = CONFIG_SYS_SDRAM_BASE + get_effective_memsize(); /* addr指向SDRAM的结尾,0x34000000处,get_effective_memsize() = 64M */
/* 保留 TLB table,PGTABLE_SIZE默认为4K */
gd->arch.tlb_size = PGTABLE_SIZE;
addr -= gd->arch.tlb_size;  /* addr减去TLB大小 */

/* 下移到下一个64K开始处,相当于64KB对齐 */
addr &= ~(0x10000 - 1);

gd->arch.tlb_addr = addr;   /* TLB的指向当前的addr */

/* 4KB对齐 */
addr &= ~(4096 - 1);
debug("Top of RAM usable for U-Boot at: %08lx\\n", addr);

/*
 * 为 U-Boot 代码, 数据与bss保留空间,gd->mon_len;在函数刚开始的时候就赋值了,大小就是u-boot整个编译出来的文件大小
 * 4KB对齐
 */
addr -= gd->mon_len;
addr &= ~(4096 - 1);

/*
 * 为 malloc() 函数保留堆区
 */
addr_sp = addr - TOTAL_MALLOC_LEN;  /* addr的栈等于addr减去堆的大小 */

/*
 * 保留 Board Info 结构体的空间与一个gd_t的副本
 */
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;

/* 设置栈指针 */
addr_sp -= sizeof (gd_t);   /* 栈减去gd_t副本空间 */
id = (gd_t *) addr_sp;      /* id赋值为当前addr_sp的值 */
gd->irq_sp = addr_sp;
/* 为中断栈留3个字节    */
addr_sp -= 12;

/* 8字节对齐 */
addr_sp &= ~0x07;

gd->relocaddr = addr;   /* 重定位的目标地址为代码区的起始地址 */
gd->start_addr_sp = addr_sp;    /* 栈地址为上面的addr_sp */
gd->reloc_off = addr - (ulong)&_start;  /* 重定位的偏移量是代码区存放地址减去0地址 */
memcpy(id, (void *)gd, sizeof(gd_t));   /* 拷贝gd_t到重定位之后的预留gd_t副本的地址 */

重定位代码之前的准备

ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp,sp为重定位代码栈区起始地址 */
bic sp, sp, #7  /* 8字节对齐 */
ldr r9, [r9, #GD_BD]        /* r9 = gd->bd */
sub r9, r9, #GD_SIZE        /* 新的GD在bd下方,参见内存分布图 */

adr lr, here
ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off,r0为重定位偏移量 */
add lr, lr, r0                  /* 重定位之后代码的返回地址 */
ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr,r0为重定位的目标地址 */
b   relocate_code

开始重定位代码

ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
subs    r4, r0, r1      /* r4 <- relocation offset */
beq relocate_done       /* skip relocation */
ldr r2, =__image_copy_end   /* r2 <- SRC &__image_copy_end */

/* 到现在为止,比较重要的几个寄存器的值为
 * r0 = gd->reloc_off,r0为重定位偏移量,本处也就是目标地址
 * r1 = __image_copy_start,r1为需要重定位代码当前的起始地址,也就是代码段的开始0
 * r4 = r0 - r1,r4为重定位的偏移值,偏移值减去0还是0
 * r2 =__image_copy_end,r2为需要重定位代码的结束地址,r2 - r1就是需要重定位代码长度了
 */

copy_loop:
    ldmia   r1!, {r10-r11}      /* 从源地址 [r1] 开始拷贝,pop到r10与r11里面,一次8个字节 */
    stmia   r0!, {r10-r11}      /* 拷贝到目标地址 [r0]    */
    cmp r1, r2          /* 一直到 [r1] 等于 [r2], 说明代码拷贝结束    */
    blo copy_loop

    /*
     * 重定位修正 .rel.dyn 
     */
    ldr r2, =__rel_dyn_start    /* r2 <- SRC &__rel_dyn_start */
    ldr r3, =__rel_dyn_end  /* r3 <- SRC &__rel_dyn_end */
fixloop:
    ldmia   r2!, {r0-r1}        /* (r0,r1) <- (SRC location,fixup) */
    and r1, r1, #0xff
    cmp r1, #23         /* relative fixup? */
    bne fixnext

    /* relative fix: increase location by offset */
    add r0, r0, r4
    ldr r1, [r0]
    add r1, r1, r4
    str r1, [r0]
fixnext:
    cmp r2, r3
    blo fixloop

9.跳转运行第二第阶段代码

到这里已经完成了整个uboot的重定位以及一些基本设备的初始化,接下来就是调用board_init_r,这个是uboot启动的第二阶段,包括nand flash的初始化,nor flash 等等设备初始化,以及各种命令的初始化。
最终内存的规划如下(虽然不同版本的uboot内存分布有所不同,但是大体上都是相同的):
内存分布

以上是关于uboot-2015-07的start.S的文件启动过程的主要内容,如果未能解决你的问题,请参考以下文章

uboot 代码流程分析---start.S

uboot移植之start.S分析

C语言版本LED灯实验

C语言版本LED灯实验

记录一次docker服务启不起来的案子

Ubuntu 设置开机自启服务