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_ADDRgd->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_TYPEgd->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_IRQaddr_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; /* 新的栈地址 */
#endifdebug("New Stack Pointer is: %08lx\n", addr_sp);#ifdef CONFIG_POSTpost_bootmode_init();post_run(NULL, POST_ROM | post_bootmode_get(0));
#endifgd->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, r4adr r0, _startcmp r0, r6beq clear_bss /* skip relocation */mov r1, r6 /* r1 <- scratch for copy_loop */ldr r3, _bss_start_ofsadd 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, #0xffcmp r7, #23 /* relative fixup? */beq fixrelcmp 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, r3blo fixloop
#endif
代码重定位包括将uboot的源码从Flash拷贝到SDRAM里、修改符号表内的信息。
往下是清除bss段、然后调用C语言的board_init_r函数。
clear_bss:
#ifndef CONFIG_SPL_BUILDldr r0, _bss_start_ofsldr r1, _bss_end_ofsmov r4, r6 /* reloc addr */add r0, r0, r4add r1, r1, r4mov r2, #0x00000000 /* clear */clbss_l:str r2, [r0] /* clear loop... */add r0, r0, #4cmp r0, r1bne clbss_lbl 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_SPLldr r0, _nand_boot_ofsmov pc, r0_nand_boot_ofs:.word nand_boot
#elseldr r0, _board_init_r_ofsadr r1, _startadd lr, r0, r1add 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语言。