U-BOOT-2016.07移植 (第一篇) 初步分析
U-BOOT-2016.07移植 (第二篇) 添加单板
U-BOOT-2016.07移植 (第三篇) 代码重定位
目录
1. 分析board_init_f()
在我写的第一篇文章中,已经对u-boot-2016.07的启动流程有了初步的了解,现在我们开始分析crt0.S中,_main函数在设置好栈和GD后调用的board_init_f(),从而了解u-boot是如何对内存空间进行分配,然后进行重定位的。
1.1 common/ board_f.c (1035 ~ 1067):
void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA //没有定义这个宏,不关心
/*
* For some archtectures, global data is initialized and used before
* calling this function. The data should be preserved. For others,
* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack
* here to host global data until relocation.
*/
gd_t data;
gd = &data;
/*
* Clear global data before it is accessed at debug print
* in initcall_run_list. Otherwise the debug print probably
* get the wrong vaule of gd->have_console.
*/
zero_global_data();
#endif
gd->flags = boot_flags;
gd->have_console = 0;
if (initcall_run_list(init_sequence_f)) //调用initcall_run_list函数,
//这个函数在lib/initcall.c中定义,
//作用就是调用init_sequence_f函数数组中
//存放的各初始化函数
hang();
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \ //我们有定义CONFIG_ARM,不关心
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}
下面我们分析init_sequence_f数组中的各函数
1.2 common/ board_f.c (829 ~ 1033):
这里我直接将被宏开关关掉的函数剔除掉,留下最终会被调用的函数:
static init_fnc_t init_sequence_f[] = {
setup_mon_len, //gd->mon_len = (ulong)&__bss_end - CONFIG_SYS_MONITOR_BASE;
//CONFIG_SYS_MONITOR_BASE = _start = 0
//设置gd->mon_len为编译出来的u-boot.bin+bss段的大小
arch_cpu_init, /* basic arch cpu dependent setup */
//这个函数应该是留给移植人员使用的,里面什么都没做,而且被__weak修饰,
//所以我们可以在别的地方重新定义这个函数来取代它
arch_cpu_init_dm, //同上
mark_bootstage, /* need timer, go after init dm */
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f, //在smdk2440.c中定义,初始化CPU时钟和各种IO(待修改)
#endif
/* TODO: can any of this go into arch_cpu_init()? */
#if defined(CONFIG_ARM) || defined(CONFIG_MIPS) || \
defined(CONFIG_BLACKFIN) || defined(CONFIG_NDS32) || \
defined(CONFIG_SPARC)
timer_init, /* 初始化定时器 */
#endif
env_init, /* 初始化环境变量 */
init_baud_rate, /* 初始化波特率为: 115200 */
serial_init, /* 设置串口通讯 */
console_init_f, /* stage 1 init of console */
display_options, /* 打印版本信息,你可以修改include/version.h中的CONFIG_IDENT_STRING选项,
* 加入你的身份信息
*/
display_text_info, /* show debugging info if required */ //打印bss段信息及text_base,
//需要 #define DEBUG
print_cpuinfo, /* 打印CPUID和时钟频率 */
INIT_FUNC_WATCHDOG_INIT
INIT_FUNC_WATCHDOG_RESET
announce_dram_init, //输出"DRAM: " 然后在下面进行SDRAM参数设置
/* TODO: unify all these dram functions? */
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \
defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32)
dram_init, /* 在smdk2440.c中定义,配置SDRAM大小,大家可根据实际进行修改 */
#endif
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
INIT_FUNC_WATCHDOG_RESET
/*
* 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, //将gd->relocaddr、gd->ram_top指向SDRAM最顶端
reserve_round_4k, //gd->relocaddr 4K对齐
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) && \
defined(CONFIG_ARM)
reserve_mmu, //gd->arch.tlb_size = PGTABLE_SIZE; 预留16kb的MMU页表
//gd->relocaddr -= gd->arch.tlb_size;
//gd->relocaddr &= ~(0x10000 - 1); 64kb对齐
//gd->arch.tlb_addr = gd->relocaddr;
#endif
reserve_trace,
#if !defined(CONFIG_BLACKFIN)
reserve_uboot, //gd->relocaddr -= gd->mon_len; 一开始设置的u-boot.bin + bss段长度
//gd->relocaddr &= ~(4096 - 1); 4k对齐,这是最终重定位地址
//gd->start_addr_sp = gd->relocaddr; 设置重定位后的栈指针
#endif
#ifndef CONFIG_SPL_BUILD
reserve_malloc, //gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN;
//预留4MB MALLOC内存池
reserve_board, //gd->start_addr_sp -= sizeof(bd_t); 预留空间给重定位后的gd_t->bd
//gd->bd = (bd_t *)gd->start_addr_sp; 指定重定位bd地址
//memset(gd->bd, '\0', sizeof(bd_t)); 清零
#endif
setup_machine, //gd->bd->bi_arch_number = CONFIG_MACH_TYPE;
//对于S3C2440来说就是MACH_TYPE_S3C2440 (arch/arm/include/asm/mach-types.h)
reserve_global_data, //gd->start_addr_sp -= sizeof(gd_t);
//gd->new_gd = (gd_t *)gd->start_addr_sp; 指定重定位GD地址
reserve_fdt,
reserve_arch,
reserve_stacks, //gd->start_addr_sp -= 16; 栈指针16字节对齐
//gd->start_addr_sp &= ~0xf;
setup_dram_config, //gd->bd->bi_dram[i].start = addr; 设置sdram地址和大小
//gd->bd->bi_dram[i].size = size;
show_dram_config, //打印SDRAM大小,与上面的announce_dram_init相对应
display_new_sp, //若 #define DEBUG 则打印新的栈地址
INIT_FUNC_WATCHDOG_RESET
reloc_fdt,
setup_reloc, //gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE; 计算重定位偏移地址
//memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));
//将原来的gd复制到重定位后的gd地址上去
NULL,
};
1.3 内存分配图
根据1.1、1.2的分析,可以得出以下的内存分配图:
2. 分析relocate_code
2.1 arch/arm/lib/crt0.S (95 ~ 122)
同样,这里将无关紧要的宏开关去掉
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
/*
* 上面这一段代码是将board_init_f中设置好的start_addr_sp地址值赋给栈指针,使其指向重定位后的栈顶
* 8字节对齐后,将r9设为新的GD地址(对照内存分配图: gd地址=bd地址-sizeof(gd_t))
*/
adr lr, here //设置返回地址为下面的here,重定位到sdram后返回here运行
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off 取重定位地址偏移值 */
add lr, lr, r0 //返回地址加偏移地址等于重定位后在sdram中的here地址
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr 传入参数为重定位地址 */
b relocate_code //跳到arch/arm/lib/relocate.S中执行
here: //返回后跳到sdram中运行
2.2 arch/arm/lib/relocate.S (79 ~ 130)
ENTRY(relocate_code)
ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start 这是u-boot.bin起始链接地址,
* 定义在u-boot.lds中 (编译后在顶层目录生成)
* 原文件是arch/arm/cpu/u-boot.lds,大家可以自行分析
*/
subs r4, r0, r1 /* r4 <- relocation offset r0是crt0.S中传入的重定位地址,
* 这里是算出偏移值
*/
beq relocate_done /* skip relocation 如果r4为0,则认为重定位已完成 */
ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end 同第一条指令,在u-boot.lds中定义*/
copy_loop: /* r1是源地址__image_copy_start,r0是目的地址relocaddr,
* size = __image_copy_start - __image_copy_end
*/
ldmia r1!, {r10-r11} /* copy from source address [r1] */
stmia r0!, {r10-r11} /* copy to target address [r0] */
cmp r1, r2 /* until source end address [r2] */
blo copy_loop
/*
* fix .rel.dyn relocations 定义了"-PIE"选项就会执行下面这段代码
* 目的是为了让位置相关的资源(代码、参数、变量)的地址在重定位后仍然能被寻址到,所以让他们加上偏移地址,
* 即等于他们重定位后的真正地址
* 这些 "存放(资源的地址)的地址" 存放在.rel.dyn这个段中,每个参数后面都会跟着一个起标志作用的参数,
* 如果这个标志参数为23,即0x17,则表示这个 (资源的地址) 是位置相关的,需要加上重定位偏移值
* 这一段代码首先让.rel.dyn这个段中的存放的地址值加上偏移值,使其在sdram中取出(资源的地址)
* 然后再让这些(资源的地址)加上偏移值,存回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) r0为"存放(资源的地址)的地址",
* 这个地址里存放的是需要用到的(资源的地址),r1为标志值
*/
and r1, r1, #0xff //r1取低八位
cmp r1, #23 /* relative fixup? 和23比较,如果相等则继续往下,否则跳到fixnext */
bne fixnext
/* relative fix: increase location by offset */
add r0, r0, r4 //r4存放的是重定位偏移值,r0这个地址存放的是位置相关的(资源的地址),
//r4+r0即为重定位后的"存放(资源的地址)的地址",
ldr r1, [r0] //在sdram中取出还未修改的(资源的地址)
add r1, r1, r4 //加上偏移值
str r1, [r0] //存回去
fixnext: //跳到下一个继续检测是否需要重定位
cmp r2, r3
blo fixloop
relocate_done:
/* ARMv4- don't know bx lr but the assembler fails to see that */
#ifdef __ARM_ARCH_4__
mov pc, lr //ARM920T用的汇编指令集是ARMv4,所以使用这条返回指令,
//返回重定位后的here标志
#else
bx lr
#endif
ENDPROC(relocate_code)
- 原谅博主表达能力太渣,我发一下图吧
到这里,代码重定位的分析就结束了,下一篇内容将开始修改代码使u-boot能从nor flash启动