U-Boot _main函数
_main函数主要完成的工作:
- 先设置用于调用board_init_f()函数的初始环境,该环境仅仅是提供了堆栈和存储位置GD('global data')结构,两者都是位于可以使用的RAM(SRAM,locked cache...)中,在调用board_init_f()函数前,GD应该被清0;
- 调用board_init_f()函数,该函数的功能为从system RAM(DRAM,DDR...)中执行准备硬件,当system RAM还不能够使用的时,必须要使用目前的GD存储传递到后续阶段的所有数据,这些数据包括重定位的目标,将来的堆栈和GD的位置;
- 设置中间环境,其中堆栈和GD是由board_init_f()函数在system RAM中进行分配的,但此时的bss和初始化的非常量仍然不能使用;
- 对于正常的uboot引导(非SPL),调用relocate_code()函数,该函数的功能将uboot从当前的位置重新转移到由board_init_f()函数计算的目标位置;
- 对于SPL,board_init_f()函数仅仅是返回(crt0),没有代码的重定位;
- 设置用于调用board_init_r()函数的最终环境,该环境将有bss段(初始化为0),初始化非常量数据(初始化为预期值),并入栈到system RAM中,GD保留了board_init_f()函数设置的值;
- 为了使uboot正常运行(非SPL),某些CPU还有一些关于内存的工作要做,调用c_runtime_cpu_setup()函数;
- 调用board_init_r()函数。
//调用spl_relocate_stack_gd函数 重定位新的gd栈指针
//在定义了CONFIG_SPL_STACK_R等情况下,将片内SRAM中的GD复制到系统RAM中。
#endif
1、初始栈指针设置:
2、board_init_f_alloc_reserve
从“top”(这里指sp指针)地址分配保留空间用作“全局变量”,返回分配空间的“top”地址(返回sp指针地址)
GD 在 16 字节边界上向下对齐。早期的 malloc area 未对齐,因此它遵循堆栈我们正在构建的架构的对齐约束。GD 是最后分配的,所以这个函数的返回值是保留区的底部和GD的地址,都应该调用上下文需要它。
top参数是汇编语言所对应的r0,作为指针为globle_data保留内存区域。内存分配在高位区域,保留区域内存指针减小。
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
}
3、board_init_f_init_reserve
board_init_f_alloc_reserve预留了global_data结构体空间,未进行初始化处理,board_init_f_init_reserve现在可以开始初始化gd结构了。board_init_f_init_stack_protection_addr为gd->start_addr_sp设置了栈指针一个保护边沿,然后调整向上以对齐16字节方式回到栈顶。将早期malloc空间的基地址保存到gd->malloc_base;board_init_f_init_stack_protection设置栈指针底部,保留20字节对于栈区进行memset设置。
4、CONFIG_SYS_MALLOC_F
如果定义了CONFIG_SYS_MALLOC_F,会先预留出early malloc所需的空间。会初始化gd->malloc_base。
5、设置初始的堆栈
基址由CONFIG_SYS_INIT_SP_ADDR定义。
6、清除BBS段
7、uboot的重定向动作
#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 r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */ bic r0, r0, #7 /* 8-byte alignment for ABI compliance */ mov sp, r0 ldr r9, [r9, #GD_NEW_GD] /* r9 <- gd->new_gd */ @@ 把新的global_data地址放在r9寄存器中 adr lr, here ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */ add lr, lr, r0
@@ 计算返回地址在新的uboot空间中的地址。b调用函数返回之后,就跳到了新的uboot代码空间中。 #if defined(CONFIG_CPU_V7M) orr lr, #1 /* As required by Thumb-only */ #endif ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
@@ 把uboot的新的地址空间放到r0寄存器中,作为relocate_code的参数
b relocate_code
@@ 跳转到relocate_code中,在这里面实现了
@@ (1)relocate旧的uboot代码空间到新的空间上去
@@ (2)修改relocate之后全局变量的label
@@ 注意,由于上述已经把lr寄存器重定义到uboot新的代码空间中了,所以返回之后,就已经跳到了新的代码空间了!!!!!!
here:
/* * now relocate vectors */ bl relocate_vectors @@ relocate中断向量表 /* Set up final (full) environment */ bl c_runtime_cpu_setup /* we still call old routine here */ #endif
1)从global data中取出relocation之后的堆栈基址,16-byte对齐后,保存到sp中。
2)将新的global data的指针,保存在r9寄存器中。
3)计算relocation之后的执行地址(relocation_return处),计算的方法就是当前的relocation_return位置加上gd->reloc_off。
4)以relocation的目的地址(gd->relocaddr)为参数,调用relocate_code执行实际的relocation动作,就是将u-boot的代码段、data段、bss段等数据,拷贝到新的位置(gd->relocaddr)。
@@ 在board_init_f里面实现了
@@ (1)对relocate进行空间规划
@@ (2)计算uboot代码空间到relocation的位置的偏移
@@ (3)relocate旧的global_data到新的global_data的空间上
7-1、和relocate空间规划的函数
在board_init_f中,会依次执行init_sequence_f数组里面函数。其中,和relocate空间规划的函数如下:
/* * 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, #ifdef CONFIG_OF_BOARD_FIXUP fix_fdt, #endif #ifdef CONFIG_PRAM reserve_pram, #endif reserve_round_4k, arch_reserve_mmu, reserve_video, reserve_trace, reserve_uboot, reserve_malloc, reserve_board, reserve_global_data, reserve_fdt, reserve_bootstage, reserve_bloblist, reserve_arch, reserve_stacks, dram_init_banksize, show_dram_config, INIT_FUNC_WATCHDOG_RESET setup_bdinfo, display_new_sp, INIT_FUNC_WATCHDOG_RESET reloc_fdt, reloc_bootstage, reloc_bloblist, setup_reloc, #if defined(CONFIG_X86) || defined(CONFIG_ARC) copy_uboot_to_ram, do_elf_reloc_fixups, #endif clear_bss, #if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \ !CONFIG_IS_ENABLED(X86_64) jump_to_copy, #endif NULL,
7.2 setup_dest_addr
static int setup_dest_addr(void)
{
debug("Monitor len: %08lX\n", gd->mon_len);
// gd->mon_len表示了整个uboot代码空间的大小,如下
// gd->mon_len = (ulong)&__bss_end - (ulong)_start;
// 在uboot代码空间relocate的时候,relocate的size就是由这里决定
/*
* Ram is setup, size stored in gd !!
*/
debug("Ram size: %08lX\n", (ulong)gd->ram_size);
// gd->ram_size表示了ram的size,也就是可使用的ddr的size,在board.c中定义如下
// int dram_init(void)
// {
// gd->ram_size = PHYS_SDRAM_1_SIZE;也就是0x2000_0000
// return 0;
// }
#if defined(CONFIG_SYS_MEM_TOP_HIDE) /* * Subtract specified amount of memory to hide so that it won't * get "touched" at all by U-Boot. By fixing up gd->ram_size * the Linux kernel should now get passed the now "corrected" * memory size and won't touch it either. This should work * for arch/ppc and arch/powerpc. Only Linux board ports in * arch/powerpc with bootwrapper support, that recalculate the * memory size from the SDRAM controller setup will have to * get fixed. */ gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE; #endif #ifdef CONFIG_SYS_SDRAM_BASE gd->ram_base = CONFIG_SYS_SDRAM_BASE; #endif gd->ram_top = gd->ram_base + get_effective_memsize(); gd->ram_top = board_get_usable_ram_top(gd->mon_len);
// gd->ram_top计算ddr的顶端地址 gd->relocaddr = gd->ram_top;
// 从gd->relocaddr 的位置开始分配
debug("Ram top: %08lX\n", (ulong)gd->ram_top); #if defined(CONFIG_MP) && (defined(CONFIG_MPC86xx) || defined(CONFIG_E500))
/* * We need to make sure the location we intend to put secondary core * boot code is reserved and not used by any part of u-boot */
if (gd->relocaddr > determine_mp_bootpg(NULL)) {
gd->relocaddr = determine_mp_bootpg(NULL);
debug("Reserving MP boot page to %08lx\n", gd->relocaddr); }
#endif
return 0; }
setup_dest_addr函数,设置目的地址,设置 gd->ram_size,gd->ram_top,gd->relocaddr这三个的值。
设置gd->ram_top和gd->relocaddr
int dram_init(void):
reserve_round_4k
reserve_round_4k 函 数 用 于 对 gd->relocaddr 做 4KB 对 齐 , 因 为gd->relocaddr=0X40000000,已经是 4K 对齐了,所以调整后不变。
reserve_mmu
留出 MMU 的 TLB 表的位置,分配 MMU 的 TLB 表内存以后会对 gd->relocaddr 做 64K 字节对齐。完成以后 gd->arch.tlb_size、gd->arch.tlb_addr 和 gd->relocaddr
reserve_video
reserve_malloc
留出 malloc 区域,调整 gd->start_addr_sp 位置, malloc 区域由宏
TOTAL_MALLOC_LEN 定义,宏定义如下:
#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN +
CONFIG_ENV_SIZE)
reserve_uboot
留出重定位后的 uboot 所占用的内存区域, uboot 所占用大小由
gd->mon_len 所指定,留出 uboot 的空间以后还要对 gd->relocaddr 做 4K 字节对齐,并且重新设
置 gd->start_addr_sp
reserve_global_data
函数保留出 gd_t 的内存区域, gd_t 结构体大小为
setup_dram_config 函数设置 dram 信息,就是设置 gd->bd->bi_dram[0].start 和
gd->bd->bi_dram[0].size,后面会传递给 linux 内核,告诉 linux DRAM 的起始地址和大小。
show_dram_config 函数,用于显示 DRAM 的配置
setup_reloc
计算uboot代码空间到relocation的位置的偏移
同样在board_init_f中,调用init_sequence_f数组里面的setup_reloc实现。
static int setup_reloc(void) { if (gd->flags & GD_FLG_SKIP_RELOC) { debug("Skipping relocation due to flag\n"); return 0; } #ifdef CONFIG_SYS_TEXT_BASE #ifdef ARM gd->reloc_off = gd->relocaddr - (unsigned long)__image_copy_start;
// gd->relocaddr表示新的uboot代码空间的起始地址,__image_copy_start表示旧的uboot代码空间的起始地址,二者算起来就是偏移了。 #elif defined(CONFIG_M68K) /* * On all ColdFire arch cpu, monitor code starts always * just after the default vector table location, so at 0x400 */ gd->reloc_off = gd->relocaddr - (CONFIG_SYS_TEXT_BASE + 0x400); #elif !defined(CONFIG_SANDBOX) gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE; #endif #endif memcpy(gd->new_gd, (char *)gd, sizeof(gd_t)); debug("Relocation Offset is: %08lx\n", gd->reloc_off); debug("Relocating to %08lx, new gd at %08lx, sp at %08lx\n", gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd), gd->start_addr_sp); return 0; }