uboot启动流程分析
进行uboot移植之前,我们需要对uboot有一个较为详细的了解,详细可以看这篇文章:
uboot启动流程分析_Bin Watson的博客-CSDN博客
初始uboot源码分析——s3c2410
start.S
同启动流程,s3c2410的uboot的源码内容和启动流程中的4410的uboot大同小异,现在我们需要深入细节进行分析。我们依然从start.S开始分析:
顺着start_code入口标号,我们一路往下可以看到,在上图中的start_code前部分代码进行的操作有:
- 设置CPU为SVC32模式
- 关看门狗
- 关中断
- 设置时钟比例
沿start_code继续往下追踪:
我们可以看到cpu_init_crit,在启动流程中我们说过,其cpu_init_crit主要是进行初始化时钟、初始化网卡、初始化串口等操作。这里我们详细看看cpu_init_crit里面的细节是怎么样的:
从上面的cpu_init_crit的代码,我们可以看到这里有点稍微不同于启动流程的地方,这将cache和MMU的关闭移到了cpu_init_crit里面。接着,在第353行调用了lowlevel_init:
值得注意的是,lowlevel_init的代码存放在board\samsung\smdk2410里面,这个很关键。这说明lowlevel_init是板级相关的初始化工作,如果我们需要移植uboot到我们自己的开发板上面,那么就可以在这里添加我们客制化的,用于初始化我们的板端的代码。
我们并没有看见有关于初始化串口、网卡之类的代码。这是因为这些代码可能是需要我们自己来实现的,也就是添加在board目录下面。这里三星只默认提供了一个模板,需要我们进行补充。
然后从lowlevel_init回到我们是start.S,继续沿cpu_init_crit往下追踪:
在start.S的第187行处,指定了栈地址,我们溯源下去后可以推测出其地址为
0
x
300
0
′
0
f
80
0x3000'0f80
0x3000′0f80,并且可以推测出
0
x
300
0
′
0
f
80
0x3000'0f80
0x3000′0f80 ~
0
x
300
0
′
0000
0x3000'0000
0x3000′0000 存放的 gd_t,也就是我们的全局数据GD
。(这里我们不详细展开推测过程)
其内存分布大致如下图所示:
到这里,我们就初步指定了global_data存放地址和栈地址。接着我们就可以调用C语言的函数,也就是board_init_f 函数。
在启动流程分析时我们说,board_init_f 函数主要是对GD
进行初始化工作,同时准备进行代码的自搬移操作的准备。
下面我们深入board_init_f 函数进行分析:
board_init_f的一开始是对gd
处的存储空间进行清空。在第290行处调用了init_sequence数组里面的一些列初始化函数,下面是init_sequence的内部细节:
从上图的函数名,我们大概可以猜到了,在这里完成的工作有:
-
board_early_init_f:CPU工作频率、GPIO引脚的设置(我们移植时,可能需要修改这里)
-
timer_init:定时器的初始化工作
-
env_init:环境初始化
-
init_baudrate:波特率的初始化
-
serial_init:串口的初始化(可见这里的串口并不是在lowlevel_init处进行初始化的)
-
console_init_t:终端的一些操作
-
display_banner:显存的操作
-
print_cpuinfo:打印cpu的信息
-
dram_init:进行DRAM的初始化工作
如果我们需要添加自定义的初始化代码,也可以考虑在这里面添加一个函数。
回到board_init_f,再往下的代码就是一些列对gd
的初始化工作。我们详细来看看如何初始化的:(为了方便看,这里剔除一些调试用代码和与主体无关的代码)
/* ram_size中记录的大小为64M
* 这里addr的地址为0x3400'0000
* addr变量指定的是uboot重定位后的存放地址
*/
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; /* 0X3000'0000 + 64M = 0x3400'0000 */
/* 分配TLB table的存储空间 */
/* reserve TLB table */
addr -= (4096 * 4); /* 0X3000'0000 - 4*4K = 0x33FF'C000 */
/* round down to next 64 kB limit 进行64KB的对齐 */
addr &= ~(0x10000 - 1); /* 0X3000'0000 ^ 0xffff = 0x33FF'0000 */
gd->tlb_addr = addr;
/* round down to next 4 kB limit */
addr &= ~(4096 - 1); /* 对齐 */
/* 如果使用了LCD,就需要在内存中划出一块显存空间 */
#ifdef CONFIG_LCD
#ifdef CONFIG_FB_ADDR
gd->fb_base = CONFIG_FB_ADDR;
#else
/* reserve memory for LCD display (always full pages) */
addr = lcd_setmem(addr);
gd->fb_base = addr;
#endif /* CONFIG_FB_ADDR */
#endif /* CONFIG_LCD */
/*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/
addr -= gd->mon_len; /* 0x33FF'0000 - 0x000a'e4e0 = 0x33F4'1B20 */
addr &= ~(4096 - 1); /* U-Boot重定位后的地址 0x33F4'0000 */
#ifndef CONFIG_SPL_BUILD
/* 开始设置栈地址,先设置堆地址
* reserve memory for malloc() arena
*/
addr_sp = addr - TOTAL_MALLOC_LEN; /* 0x33F4'0000 */
debug("Reserving %dk for malloc() at: %08lx\n",
TOTAL_MALLOC_LEN >> 10, addr_sp);
/* 接着分配bd的存储空间,board_info记录了板端的一些信息
* (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;
#ifdef CONFIG_MACH_TYPE
gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
#endif
/* 再往下,指定GD新的存放位置 */
addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp;
/* setup stackpointer for exeptions */
gd->irq_sp = addr_sp;
/* 如果在uboot中配置了使用中断,
* 则需要在这里分配IRQ和FIQ的中断栈空间
*/
#ifdef CONFIG_USE_IRQ
addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",
CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif
/* leave 3 words for abort-stack */
addr_sp -= 12;
/* 8-byte alignment for ABI compliance */
addr_sp &= ~0x07; /* 新的栈地址 */
#endif
debug("New Stack Pointer is: %08lx\n", addr_sp);
#ifdef CONFIG_POST
post_bootmode_init();
post_run(NULL, POST_ROM | post_bootmode_get(0));
#endif
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; /* 指定重定位地址 */
gd->start_addr_sp = addr_sp; /* 指定新的栈地址 */
gd->reloc_off = addr - _TEXT_BASE; /* 指针重定位地址 外加偏移地址,这里为0 */
debug("relocation Offset is: %08lx\n", gd->reloc_off);
memcpy(id, (void *)gd, sizeof(gd_t)); /* 将旧的gd拷贝的新的gd位置 */
relocate_code(addr_sp, id, addr); /* 进行代码重定位 */
/* NOTREACHED - relocate_code() does not return */
}
根据对源码的分析,我们大致可以画出下面这张内存分布图,从上往下:
- 首先是TLB table占 4096 * 4 个字节的空间。
- 如果使用了LCD,那么紧接着需要划出一块显存的空间。暂不考虑LCD的,所以这里没有划出来。
- 接着是uboot代码新的存放空间,大小为 0x000a’e4e0。可以通过使用启动流程中使用的arm-linux-nm查找符号_bss_end_ofs的地址,再在反汇编代码中找到该地址,其值就是uboot所需要的空间。
- 往下是malloc的使用的堆空间,大小不好推测,这里我们就不细究。
- 往下存放的是board_info结构体,其记录的是单板相关的信息。
- 再往下是
gd
新的存放位置,在上面代码的第94行,使用了memcpy(id, (void *)gd, sizeof(gd_t));
将旧gd
拷贝到新的位置。 - 如果需要在uboot里使用中断,就需要指定一个中断栈,在gd往下就是分配中断栈空间。每个栈的大小都是4K;
- 剩余往下到0x3000’0000的空间就是栈的空间。
需要注意的是,这个内存分配图在uboot被指定不同了配置的情况下,可能会造成最终内存分布的不相同。例如:如果开启了调试功能,那么会在TLB表前面划出一块用于调试打印使用的空间。
在board_init_f的最后一行调用了一个relocate_code(addr_sp, id, addr);
的函数,而这个函数定义在start.S里面,我们随着relocate_code继续追踪。根据函数名我们就可以猜出,其是进行代码的重定位操作:
/*------------------------------------------------------------------------------*/
/*
* 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
cmp r0, r6
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */
ldr r3, _bss_start_ofs
add r2, r0, r3 /* r2 <- source end address */
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
#ifndef CONFIG_SPL_BUILD
/*
* fix .rel.dyn relocations
*/
ldr r0, _TEXT_BASE /* r0 <- Text base */
sub r9, r6, r0 /* r9 <- relocation offset */
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 */
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
add r0, r0, r9 /* r0 <- location to fix up in RAM */
ldr r1, [r2, #4]
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */
beq fixrel
cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext
fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0]
add r1, r1, r9
fixnext:
str r1, [r0]
add r2, r2, #8 /* each rel.dyn entry is 8 bytes */
cmp r2, r3
blo fixloop
#endif
代码重定位包括将uboot的源码从Flash拷贝到SDRAM里、修改符号表内的信息。
往下是清除bss段、然后调用C语言的board_init_r函数。
clear_bss:
#ifndef CONFIG_SPL_BUILD
ldr r0, _bss_start_ofs
ldr r1, _bss_end_ofs
mov r4, r6 /* reloc addr */
add r0, r0, r4
add r1, r1, r4
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init /* 未实现 */
bl red_led_on /* 未实现 */
#endif
/*
* We are done. Do not return, instead branch to second part of board
* initialization, now running from RAM.
*/
#ifdef CONFIG_NAND_SPL
ldr r0, _nand_boot_ofs
mov pc, r0
_nand_boot_ofs:
.word nand_boot
#else
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进入main_loop循环 */
_board_init_r_ofs:
.word board_init_r - _start
#endif
至此,s3c2410的uboot源码就大致分析完成了。
s3c2410 uboot总结
对于s3c2440/s3c2410来说,机器上电后可以从两个不同的地方读取uboot,分别是NorFlash和NandFlash。
对于Nand启动,会将Nand Flash的前4KB的代码拷贝到内部的SRAM中,然后从SRAM的0地址处开始执行;而对于Nor启动,则直接从Nor Flash的零地址处开始执行,这时的Internal SRAM就放在内存的最高处。
程序开始执行后,会依次进行:
-
设置CPU为SVC32模式,关看门狗,关中断,设置时钟比例。
-
调用cpu_init_crit初始化、关闭cacahe、关闭MMU、调用lowlevel_init进行初始化;
-
设置全局数据
gd
,利用gd
进行代码重定位。无论是Nor启动还是Nand启动,为了提高程序的运行效率,我们都需要将uboot从Flash里面拷贝到SDRAM里面,其起始地址为0x3000 000;
-
设置C语言运行环境,调用board_init_r进入C语言。