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

时间:2021-09-30 04:45:00

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的文件启动过程(2)