1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号公众号,获取更多资料:正点原子
.4_main函数详解
_main函数定义在文件arch/arm/lib/crt0.S中,函数内容如下:
示例代码 crt0.S代码段
/*
* entry point of crt0 sequence
*/
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
#if defined(CONFIG_SPL_BUILD)&& defined(CONFIG_SPL_STACK)
ldr sp,=(CONFIG_SPL_STACK)
#else
ldr sp,=(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M)/* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7/* 8-byte alignment for ABI compliance */
#endif
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve
mov r0, #0
bl board_init_f
#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 sp,[r9, #GD_START_ADDR_SP]/* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M)/* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7/* 8-byte alignment for ABI compliance */
#endif
ldr r9,[r9, #GD_BD]/* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0,[r9, #GD_RELOC_OFF]/* r0 = gd->reloc_off */
add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr lr, #1/* As required by Thumb-only */
#endif
ldr r0,[r9, #GD_RELOCADDR]/* r0 = gd->relocaddr */
b relocate_code
here:
/*
* now relocate vectors
*/
bl relocate_vectors
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
#endif
#if!defined(CONFIG_SPL_BUILD)|| defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD
/* Use a DRAM stack for the rest of SPL, if requested */
bl spl_relocate_stack_gd
cmp r0, #0
movne sp, r0
movne r9, r0
# endif
ldr r0,=__bss_start /* this is auto-relocated! */
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3,=__bss_end /* this is auto-relocated! */
mov r1, #0x00000000/* prepare zero to clear BSS */
subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1,=__bss_end /* this is auto-relocated! */
mov r2, #0x00000000/* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2,[r0]/* clear -bit BSS word */
addlo r0, r0, #4/* move to next */
blo clbss_l
#endif
#if! defined(CONFIG_SPL_BUILD)
bl coloured_LED_init
bl red_led_on
#endif
/* call board_init_r(gd_t *id, ulong dest_addr) */
mov r0, r9 /* gd_t */
ldr r1,[r9, #GD_RELOCADDR]/* dest_addr */
/* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr lr,=board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc,=board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif
ENDPROC(_main)
第行,设置sp指针为CONFIG_SYS_INIT_SP_ADDR,也就是sp指向0X0091FF00。
第行,sp做8字节对齐。
第行,读取sp到寄存器r0里面,此时r0=0X0091FF00。
第行,调用函数
board_init_f_alloc_reserve,此函数有一个参数,参数为r0中的值,也就是0X0091FF00,此函数定义在文件common/init/board_init.c中,内容如下:
示例代码 board_init.c代码段
ulong board_init_f_alloc_reserve(ulong top)
{
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of bytes) */
top = rounddown(top-sizeof(struct global_data),);
return top;
}
函数
board_init_f_alloc_reserve主要是留出早期的malloc内存区域和gd内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400(在文件
include/generated/autoconf.h中定义),sizeof(struct global_data)=(GD_SIZE值),完成以后的内存分布如图所示:
函数
board_init_f_alloc_reserve是有返回值的,返回值为新的top值,从图可知,此时top=0X0091FA00。
继续回到示例代码中,第行,将r0写入到sp里面,r0保存着函数
board_init_f_alloc_reserve的返回值,所以这一句也就是设置sp=0X0091FA00。
第行,将r0寄存器的值写到寄存器r9里面,因为r9寄存器存放着全局变量gd的地址,在文件
arch/arm/include/asm/global_data.h中有如图所示宏定义:
图 DECLARE_GLOBAL_DATA_PTR宏定义
从图可以看出,uboot中定义了一个指向gd_t的指针gd,gd存放在寄存器r9里面的,因此gd是个全局变量。gd_t是个结构体,在
include/asm-generic/global_data.h里面有定义,gd_定义如下:
示例代码 global_data.h代码段
typedefstruct global_data {
bd_t *bd;
29unsignedlong flags;
30unsignedint baudrate;
31unsignedlong cpu_clk;/* CPU clock in Hz! */
32unsignedlong bus_clk;
/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
34unsignedlong pci_clk;
35unsignedlong mem_clk;
#if defined(CONFIG_LCD)|| defined(CONFIG_VIDEO)
37unsignedlong fb_base;/* Base address of framebuffer mem */
#endif
......
#ifdef CONFIG_DM_VIDEO
ulong video_top;/* Top of video frame buffer area */
ulong video_bottom;/* Bottom of video frame buffer area */
#endif
} gd_t;
因此这一行代码就是设置gd所指向的位置,也就是gd指向0X0091FA00。
继续回到示例代码中,第行调用函数board_init_f_init_reserve,此函数在文件common/init/board_init.c中有定义,函数内容如下:
示例代码 board_init.c代码段
110void board_init_f_init_reserve(ulong base)
{
112struct global_data *gd_ptr;
#ifndef _USE_MEMCPY
114int*ptr;
#endif
/*
* clear GD entirely and set it up.
* Use gd_ptr, as gd may not be properly set yet.
*/
gd_ptr =(struct global_data *)base;
/* zero the area */
#ifdef _USE_MEMCPY
memset(gd_ptr,'\0',sizeof(*gd));
#else
for(ptr =(int*)gd_ptr; ptr <(int*)(gd_ptr +1);)
*ptr++=0;
#endif
/* set GD unless architecture did it already */
#if!defined(CONFIG_ARM)
arch_setup_gd(gd_ptr);
#endif
/* next alloc will be higher by one GD plus -byte alignment */
base += roundup(sizeof(struct global_data),);
/*
* record early malloc arena start.
* Use gd as it is now properly set for all architectures.
*/
#if defined(CONFIG_SYS_MALLOC_F)
/* go down one 'early malloc arena' */
gd->malloc_base = base;
/* next alloc will be higher by one 'early malloc arena' size */
base += CONFIG_SYS_MALLOC_F_LEN;
#endif
}
可以看出,此函数用于初始化gd,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0X0091FA00=0X0091FAF8,在做字节对齐,最终gd->malloc_base=0X0091FB00,这个也就是earlymalloc的起始地址。
继续回到示例代码中,第行设置R0为0。
第行,调用board_init_f函数,此函数定义在文件common/board_f.c中!主要用来初始化DDR,定时器,完成代码拷贝等等,此函数我们后面在详细的分析。
第行,重新设置环境(sp和gd)、获取gd->start_addr_sp的值赋给sp,在函数board_init_f中会初始化gd的所有成员变量,其中gd->start_addr_sp=0X9EF44E90,所以这里相当于设置sp=gd->start_addr_sp=0X9EF44E90。0X9EF44E90是DDR中的地址,说明新的sp和gd将会存放到DDR中,而不是内部的RAM了。GD_START_ADDR_SP=,参考示例代码。
第行,sp做8字节对齐。
第行,获取gd->bd的地址赋给r9,此时r9存放的是老的gd,这里通过获取gd->bd的地址来计算出新的gd的位置。GD_BD=0,参考示例代码。
第行,新的gd在bd下面,所以r9减去gd的大小就是新的gd的位置,获取到新的gd的位置以后赋值给r9。
第行,设置lr寄存器为here,这样后面执行其他函数返回的时候就返回到了第行的here位置处。
第,读取gd->reloc_off的值复制给r0寄存器,GD_RELOC_OFF=,参考示例代码。
第行,lr寄存器的值加上r0寄存器的值,重新赋值给lr寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的uboot存放的起始地址为0X87800000,下面要将uboot拷贝到DDR最后面的地址空间出,将0X87800000开始的内存空出来),其中就包括here,因此lr中的here要使用重定位后的位置。
第行,读取gd->relocaddr的值赋给r0寄存器,此时r0寄存器就保存着uboot要拷贝的目的地址,为0X9FF47000。GD_RELOCADDR=,参考示例代码。
第行,调用函数relocate_code,也就是代码重定位函数,此函数负责将uboot拷贝到新的地方去,此函数定义在文件arch/arm/lib/relocate.S中稍后会详细分析此函数。
第行,调用函数relocate_vectors,对中断向量表做重定位,此函数定义在文件arch/arm/lib/relocate.S中,稍后会详细分析此函数。
第行,调用函数c_runtime_cpu_setup,此函数定义在文件
arch/arm/cpu/armv7/start.S中,函数内容如下:
示例代码 start.S代码段
ENTRY(c_runtime_cpu_setup)
/*
* If I-cache is enabled invalidate it
*/
#ifndef CONFIG_SYS_ICACHE_OFF
mcr p15,0, r0, c7, c5,0 @ invalidate icache
mcr p15,0, r0, c7, c10,4 @ DSB
mcr p15,0, r0, c7, c5,4 @ ISB
#endif
bx lr
ENDPROC(c_runtime_cpu_setup)
第~行,清除BSS段。
第行,设置函数board_init_r的两个参数,函数board_init_r声明如下:
board_init_r(gd_t *id, ulong dest_addr)
第一个参数是gd,因此读取r9保存到r0里面。
第行,设置函数board_init_r的第二个参数是目的地址,因此r1=gd->relocaddr。
第行、调用函数board_init_r,此函数定义在文件common/board_r.c中,稍后会详细的分析此函数。
这个就是_main函数的运行流程,在_main函数里面调用了board_init_f、relocate_code、relocate_vectors和board_init_r这4个函数,接下来依次看一下这4个函数都是干啥的。
board_init_f函数详解
_main中会board_init_f函数,board_init_f函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化gd的各个成员变量,uboot会将自己重定位到DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中。这么做的目的是给Linux腾出空间,防止Linuxkernel覆盖掉uboot,将DRAM前面的区域完整的空出来。在拷贝之前肯定要给uboot各部分分配好内存位置和大小,比如gd应该存放到哪个位置,malloc内存池应该存放到哪个位置等等。这些信息都保存在gd的成员变量中,因此要对gd的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位uboot的时候就会用到这个内存“分配图”。
此函数定义在文件common/board_f.c中定义,代码如下:
示例代码 board_f.c代码段
1035void 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))
hang();
#if!defined(CONFIG_ARM)&&!defined(CONFIG_SANDBOX)&& \
!defined(CONFIG_EFI_APP)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
}
因为没有定义
CONFIG_SYS_GENERIC_GLOBAL_DATA,所以第~行代码无效。
第行,初始化gd->flags=boot_flags=0。
第行,设置gd->have_console=0。
重点在第行!通过函数initcall_run_list来运行初始化序列init_sequence_f里面的一些列函数,init_sequence_f里面包含了一系列的初始化函数,init_sequence_f也是定义在文件common/board_f.c中,由于init_sequence_f的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的init_sequence_f定义如下:
示例代码 board_f.c代码段
/*****************去掉条件编译语句后的init_sequence_f***************/
1static init_fnc_t init_sequence_f[]={
2 setup_mon_len,
3 initf_malloc,
4 initf_console_record,
5 arch_cpu_init, /* basic arch cpu dependent setup */
6 initf_dm,
7 arch_cpu_init_dm,
8 mark_bootstage, /* need timer, go after init dm */
9 board_early_init_f,
timer_init, /* initialize timer */
board_postclk_init,
get_clocks,
env_init, /* initialize environment */
init_baud_rate, /* initialze baudrate settings */
serial_init, /* serial communications setup */
console_init_f, /* stage 1 init of console */
display_options, /* say that we are here */
display_text_info, /* show debugging info if required */
print_cpuinfo, /* display cpu info (and speed) */
show_board_info,
INIT_FUNC_WATCHDOG_INIT
INIT_FUNC_WATCHDOG_RESET
init_func_i2c,
announce_dram_init,
/* TODO: unify all these dram functions? */
dram_init, /* configure available RAM banks */
post_init_f,
INIT_FUNC_WATCHDOG_RESET
testdram,
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,
reserve_round_4k,
reserve_mmu,
reserve_trace,
reserve_uboot,
reserve_malloc,
reserve_board,
setup_machine,
reserve_global_data,
reserve_fdt,
reserve_arch,
reserve_stacks,
setup_dram_config,
show_dram_config,
display_new_sp,
INIT_FUNC_WATCHDOG_RESET
reloc_fdt,
setup_reloc,
NULL,
};
接下来分析以上函数执行完以后的结果:
第2行,setup_mon_len函数设置gd的mon_len成员变量,此处为__bss_end -_start,也就是整个代码的长度。0X878A8E74-0x87800000=0XA8E74,这个就是代码长度
第3行,initf_malloc函数初始化gd中跟malloc有关的成员变量,比如malloc_limit,此函数会设置gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X400。malloc_limit表示malloc内存池大小。
第4行,initf_console_record,如果定义了宏CONFIG_CONSOLE_RECORD和宏CONFIG_SYS_MALLOC_F_LEN的话此函数就会调用函数console_record_init,但是IMX6ULL的uboot没有定义宏CONFIG_CONSOLE_RECORD,所以此函数直接返回0。
第5行,arch_cpu_init函数,初始化架构相关的内容,CPU级别的操作。
第6行,initf_dm函数,驱动模型的一些初始化。
第7行,arch_cpu_init_dm函数未实现。
第8行,mark_bootstage函数应该是和啥标记有关的
第9行,board_early_init_f函数,板子相关的早期的一些初始化设置,I.MX6ULL用来初始化串口的IO配置
第行,timer_init,初始化定时器,Cortex-A7内核有一个定时器,这里初始化的就是Cortex-A内核的那个定时器。通过这个定时器来为uboot提供时间。就跟Cortex-M内核Systick定时器一样。关于Cortex-A内部定时器的详细内容,请参考文档《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition.pdf》的“Chapter B8 The Generic Timer”章节。
第行,board_postclk_init,对于I.MX6ULL来说是设置VDDSOC电压。
第行,get_clocks函数用于获取一些时钟值,I.MX6ULL获取的是sdhc_clk时钟,也就是SD卡外设的时钟。
第行,env_init函数是和环境变量有关的,设置gd的成员变量env_addr,也就是环境变量的保存地址。
第行,init_baud_rate函数用于初始化波特率,根据环境变量baudrate来初始化gd->baudrate。
第行,serial_init,初始化串口。
第行,console_init_f,设置gd->have_console 为1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来。
第行、display_options,通过串口输出一些信息,如图所示:
第行,display_text_info,打印一些文本信息,如果开启UBOOT的DEBUG功能的话就会输出text_base、bss_start、bss_end,形式如下:
debug("U-Boot code: %08lX -> %08lX BSS: -> %08lX\n",text_base, bss_start, bss_end);
结果如图所示:
第行,print_cpuinfo函数用于打印CPU信息,结果如图所示:
图 CPU信息
第行,show_board_info函数用于打印板子信息,会调用checkboard函数,结果如图所示:
第行,INIT_FUNC_WATCHDOG_INIT,初始化看门狗,对于I.MX6ULL来说是空函数
第行,INIT_FUNC_WATCHDOG_RESET,复位看门狗,对于I.MX6ULL来说是空函数
第行,init_func_i2c函数用于初始化I2C,初始化完成以后会输出如图所示信息:
第行,announce_dram_init,此函数很简单,就是输出字符串“DRAM:”
第行,dram_init,并非真正的初始化DDR,只是设置gd->ram_size的值,对于正点原子I.MX6ULL开发板EMMC版本核心板来说就是512MB。
第行,post_init_f,此函数用来完成一些测试,初始化gd->post_init_f_time
第行,testdram,测试DRAM,空函数。
第行,setup_dest_addr函数,设置目的地址,设置gd->ram_size,gd->ram_top,gd->relocaddr这三个的值。接下来我们会遇到很多跟数值有关的设置,如果直接看代码分析的话就太费时间了,我可以修改uboot代码,直接将这些值通过串口打印出来,比如这里我们修改文件common/board_f.c,因为setup_dest_addr函数定义在文件common/board_f.c中,在setup_dest_addr函数输入如图所示内容:
设置好以后重新编译uboot,然后烧写到SD卡中,选择SD卡启动,重启开发板,打开SecureCRT,uboot会输出如图所示信息:
从图可以看出:
gd->ram_size = 0X20000000 //ram大小为0X20000000=512MB
gd->ram_top = 0XA0000000 //ram最高地址为0X80000000+0X20000000=0XA0000000
gd->relocaddr = 0XA0000000 //重定位后最高地址为0XA0000000
第行,reserve_round_4k函数用于对 gd->relocaddr做4KB对齐,因为gd->relocaddr=0XA0000000,已经是4K对齐了,所以调整后不变。
第行,reserve_mmu,留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对gd->relocaddr做64K字节对齐。完成以后gd->arch.tlb_size、gd->arch.tlb_addr和gd->relocaddr如图所示:
从图可以看出:
gd->arch.tlb_size= 0X4000 //MMU的TLB表大小
gd->arch.tlb_addr=0X9FFF0000 //MMU的TLB表起始地址,64KB对齐以后
gd->relocaddr=0X9FFF0000 //relocaddr地址
第行,reserve_trace函数,留出跟踪调试的内存,I.MX6ULL没有用到!
第行,reserve_uboot,留出重定位后的uboot所占用的内存区域,uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置gd->start_addr_sp,结果如图所示:
从图可以看出:
gd->mon_len = 0XA8EF4
gd->start_addr_sp = 0X9FF47000
gd->relocaddr = 0X9FF47000
第行,reserve_malloc,留出malloc区域,调整gd->start_addr_sp位置,malloc区域由宏TOTAL_MALLOC_LEN定义,宏定义如下:
#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE)
mx6ull_alientek_emmc.h文件中定义宏CONFIG_SYS_MALLOC_LEN为16MB=0X1000000,宏CONFIG_ENV_SIZE=8KB=0X2000,因此TOTAL_MALLOC_LEN=0X1002000。调整以后gd->start_addr_sp如图所示:
图 信息输出
从图可以看出:
TOTAL_MALLOC_LEN=0X1002000
gd->start_addr_sp=0X9EF45000 //0X9FF47000-16MB-8KB=0X9EF45000
第行,reserve_board函数,留出板子bd所占的内存区,bd是结构体bd_t,bd_t大小为字节,结果如图所示:
从图可以看出:
gd->start_addr_sp=0X9EF44FB0
gd->bd=0X9EF44FB0
第行,setup_machine,设置机器ID,linux启动的时候会和这个机器ID匹配,如果匹配的话linux就会启动正常。但是!!I.MX6ULL不用这种方式了,这是以前老版本的uboot和linux使用的,新版本使用设备树了,因此此函数无效。
第行,reserve_global_data函数,保留出gd_t的内存区域,gd_t结构体大小为248B,结果如图所示:
gd->start_addr_sp=0X9EF44EB8 //0X9EF44FB0-=0X9EF44EB8
gd->new_gd=0X9EF44EB8
第行,reserve_fdt,留出设备树相关的内存区域,I.MX6ULL的uboot没有用到,因此此函数无效。
第行,reserve_arch是个空函数。
第行,reserve_stacks,留出栈空间,先对gd->start_addr_sp减去,然后做字节对其。如果使能IRQ的话还要留出IRQ相应的内存,具体工作是由arch/arm/lib/stack.c文件中的函数arch_reserve_stacks完成。结果如图所示:
在本uboot中并没有使用到IRQ,所以不会留出IRQ相应的内存区域,此时:
gd->start_addr_sp=0X9EF44E90
第行,setup_dram_config函数设置dram信息,就是设置gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size,后面会传递给linux内核,告诉linux DRAM的起始地址和大小。结果如图所示:
从图可以看出,DRAM的起始地址为0X80000000,大小为0X20000000(512MB)。
第行,show_dram_config函数,用于显示DRAM的配置,如图所示:
第行,display_new_sp函数,显示新的sp位置,也就是gd->start_addr_sp,不过要定义宏DEBUG,结果如图所示:
图中的gd->start_addr_sp值和我们前面分析的最后一次修改的值一致。
第行,reloc_fdt函数用于重定位fdt,没有用到。
第行,setup_reloc,设置gd的其他一些成员变量,供后面重定位的时候使用,并且将以前的gd拷贝到gd->new_gd处。需要使能DEBUG才能看到相应的信息输出,如图所示:
从图可以看出,uboot重定位后的偏移为0X18747000,重定位后的新地址为0X9FF4700,新的gd首地址为0X9EF44EB8,最终的sp为0X9EF44E90。
至此,board_init_f函数就执行完成了,最终的内存分配如图所示:
relocate_code函数详解
relocate_code函数是用于代码拷贝的,此函数定义在文件arch/arm/lib/relocate.S中,代码如下:
示例代码 relocate.S代码段
/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an
* R_ARM_ABS32 relocation record type, we never refer to linker-
* defined symbols directly. Instead, we declare literals which
* contain their relative location with respect to relocate_code,
* and at run time, add relocate_code back to them.
*/
ENTRY(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 */
copy_loop:
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
*/
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, # /* 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
relocate_done:
#ifdef __XSCALE__
/*
* On xscale, icache must be invalidated and write buffers
* drained, even with cache disabled - of xscale core
developer's manual */
mcr p15,0, r0, c7, c7,0 /* invalidate icache */
mcr p15,0, r0, c7, c10,4 /* drain write buffer */
#endif
/* ARMv4- don't know bx lr but the assembler fails to see that */
#ifdef __ARM_ARCH_4__
mov pc, lr
#else
bx lr
#endif
ENDPROC(relocate_code)
第行,r1=__image_copy_start,也就是r1寄存器保存源地址,由表可知,__image_copy_start=0X87800000。
第行, r0=0X9FF47000,这个地址就是uboot拷贝的目标首地址。r4=r0-r1=0X9FF47000-0X87800000=0X18747000,因此r4保存偏移量。
第行,如果在第中,r0-r1等于0,说明r0和r1相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了!执行relocate_done函数
第行,r2=__image_copy_end,r2中保存拷贝之前的代码结束地址,由表可知,__image_copy_end =0x8785dd54。
第行,函数copy_loop完成代码拷贝工作!从r1,也就是__image_copy_start开始,读取uboot代码保存到r10和r11中,一次就只拷贝这2个位的数据。拷贝完成以后r1的值会更新,保存下一个要拷贝的数据地址。
第行,将r10和r11的数据写到r0开始的地方,也就是目的地址。写完以后r0的值会更新,更新为下一个要写入的数据地址。
第行,比较r1是否和r2相等,也就是检查是否拷贝完成,如果不相等的话说明没有拷贝完成,没有拷贝完成的话就跳转到copy_loop接着拷贝,直至拷贝完成。
接下来的第行~行是重定位.rel.dyn段,.rel.dyn段是存放.text段中需要重定位地址的集合。重定位就是uboot将自身拷贝到DRAM的另一个地放去继续运行(DRAM的高地址处)。我们知道,一个可执行的bin文件,其链接地址和运行地址要相等,也就是链接到哪个地址,在运行之前就要拷贝到哪个地址去。现在我们重定位以后,运行地址就和链接地址不同了,这样寻址的时候不会出问题吗?为了分析这个问题,我们需要在mx6ull_alientek_emmc.c中输入如下所示内容:
示例代码 mx6ull_alientek_emmc.c新添代码段
1staticint rel_a =0;
2
3void rel_test(void)
4{
5 rel_a =;
6 printf("rel_test\r\n");
7}
最后还需要在mx6ullevk.c文件中的board_init函数里面调用rel_test函数,否则rel_reset不会被编译进uboot。修改完成后的mx6ullevk.c如图所示:
board_init函数会调用rel_test,rel_test会调用全局变量rel_a,使用如下命令编译uboot:
./mx6ull_alientek_emmc.sh
编译完成以后,使用
arm-linux-gnueabihf-objdump将u-boot进行反汇编,得到u-boot.dis这个汇编文件,命令如下:
arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis
在u-boot.dis文件中找到rel_a、rel_rest和board_init,相关内容如下所示:
示例代码 汇编文件代码段
<rel_test>:
: e59f300c ldr r3,[pc, #] ;<rel_test+0x14>
: e3a02064 mov r2, # ;0x64
48780418c: e59f0008 ldr r0,[pc, #8] ;8780419c<rel_test+0x18>
: e5832000 str r2,[r3]
: ea00d668 b 87839b3c<printf>
:8785da50;<UNDEFINED> instruction:0x8785da50
88780419c:878426a2 strhi r2,[r4, r2, lsr #]
9
10878041a0<board_init>:
11878041a0: e92d4010 push {r4, lr}
12878041a4: ebfffff6 bl <rel_test>
......
168785da50<rel_a>:
178785da50: andeq r0, r0, r0
第行是borad_init调用rel_test函数,用到了bl指令,而bl指令时位置无关指令,bl指令是相对寻址的(pc+offset),因此uboot中函数调用是与绝对位置无关的。
再来看一下函数rel_test对于全局变量rel_a的调用,第2行设置r3的值为pc+地址处的值,因为ARM流水线的原因,pc寄存器的值为当前地址+8,因此pc=0X87804184+8=0X8780418C,r3=0X8780418C+=0X87804198,第7行就是0X87804198这个地址,0X87804198处的值为0X8785DA50。根据第行可知,0X8785DA50正是变量rel_a的地址,最终r3=0X8785DA50。
第3行,r2=。
第5行,将r2内的值写到r3地址处,也就是设置地址0X8785DA50的值为,这不就是示例代码代码中的第5行:rel_a = 。
总结一下rel_a=的汇编执行过程:
①、在函数rel_test末尾处有一个地址为0X87804198的内存空间(示例代码第7行),此内存空间保存着变量rel_a的地址。
②、函数rel_test要想访问变量rel_a,首先访问末尾的0X87804198来获取变量rel_a的地址,而访问0X87804198是通过偏移来访问的,很明显是个位置无关的操作。
③、通过0X87804198获取到变量rel_a的地址,对变量rel_a进行操作。
④、可以看出,函数rel_test对变量rel_a的访问没有直接进行,而是使用了一个第三方偏移地址0X87804198,专业术语叫做Label。这个第三方偏移地址就是实现重定位后运行不会出错的重要原因!
uboot重定位后偏移为0X18747000,那么重定位后函数rel_test的首地址就是0X87804184+0X18747000=0X9FF4B184。保存变量rel_a地址的Label就是0X9FF4B184+8+=0X9FF4B198(既:0X87804198+0X18747000),变量rel_a的地址就为0X8785DA50+0X18747000=0X9FFA4A50。重定位后函数rel_test要想正常访问变量rel_a就得设置0X9FF4B198(重定位后的Label)地址出的值为0X9FFA4A50(重定位后的变量rel_a地址)。这样就解决了重定位后链接地址和运行地址不一致的问题。
可以看出,uboot对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码,在使用ld进行链接的时候使用选项“-pie”生成位置无关的可执行文件。在文件arch/arm/config.mk下有如下代码:
示例代码 config.mk文件代码段
# needed for relocation
LDFLAGS_u-boot +=-pie
第行就是设置uboot链接选项,加入了“-pie”选项,编译链接uboot的时候就会使用到“-pie”,如图所示:
使用“-pie”选项以后会生成一个.rel.dyn段,uboot就是靠这个.rel.dyn来解决重定位问题的,在u-bot.dis的.rel.dyn段中有如下所示内容:
示例代码 .rel.dyn段代码段
1 Disassembly of section .rel.dyn:
2
38785da44<__rel_dyn_end-0x8ba0>:
48785da44: strhi r0,[r0, r0, lsr #]
58785da48: andeq r0, r0, r7, lsl r0
6......
78785dfb4:;<UNDEFINED> instruction:0x87804198
88785dfb8: andeq r0, r0, r7, lsl r0
先来看一下.rel.dyn段的格式,类似第7行和第8行这样的是一组,也就是两个4字节数据为一组。高4字节是Label地址标识0X17,低4字节就是Label的地址,首先判断Label地址标识是否正确,也就是判断高4字节是否为0X17,如果是的话高4字节就是Label值。
第7行值为0X87804198,第8行为0X00000017,说明第7行的0X87804198是个Label,这个正是示例代码中存放变量rel_a地址的那个Label。根据前面的分析,只要将地址0X87804198+offset处的值改为重定位后的变量rel_a地址即可。我们猜测的是否正确,看一下uboot对.rel.dyn段的重定位即可(示例代码代码中的第~行),.rel.dyn段的重定位代码如下:
示例代码 relocate.S代码段
/*
* fix .rel.dyn relocations
*/
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, # /* 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
第行,r2=__rel_dyn_start,也就是.rel.dyn段的起始地址。
第行,r3=__rel_dyn_end,也就是.rel.dyn段的终止地址。
第行,从.rel.dyn段起始地址开始,每次读取两个4字节的数据存放到r0和r1寄存器中,r0存放低4字节的数据,也就是Label地址;r1存放高4字节的数据,也就是Label标志。
第行,r1中给的值与0xff进行与运算,其实就是取r1的低8位。
第行,判断r1中的值是否等于(0X17)。
第行,如果r1不等于的话就说明不是描述Label的,执行函数fixnext,否则的话继续执行下面的代码。
第行,r0保存着Label值,r4保存着重定位后的地址偏移,r0+r4就得到了重定位后的Label值。此时r0保存着重定位后的Label值,相当于0X87804198+0X18747000=0X9FF4B198。
第,读取重定位后Label所保存的变量地址,此时这个变量地址还是重定位前的(相当于rel_a重定位前的地址0X8785DA50),将得到的值放到r1寄存器中。
第行,r1+r4即可得到重定位后的变量地址,相当于rel_a重定位后的0X8785DA50+0X18747000=0X9FFA4A50。
第行,重定位后的变量地址写入到重定位后的Label中,相等于设置地址0X9FF4B198处的值为0X9FFA4A50。
第行,比较r2和r3,查看.rel.dyn段重定位是否完成。
第行,如果r2和r3不相等,说明.rel.dyn重定位还未完成,因此跳到fixloop继续重定位.rel.dyn段。
可以看出,uboot中对.rel.dyn段的重定位方法和我们猜想的一致。.rel.dyn段的重定位比较复杂一点,有点绕,因为涉及到链接地址和运行地址的问题。
relocate_vectors函数详解
函数relocate_vectors用于重定位向量表,此函数定义在文件函数源码如下:
示例代码 relocate.S代码段
ENTRY(relocate_vectors)
#ifdef CONFIG_CPU_V7M
/*
* On ARMv7-M we only have to write the new vector address
* to VTOR register.
*/
ldr r0,[r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
ldr r1,=V7M_SCB_BASE
str r0,[r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
/*
* If the ARM processor has the security extensions,
* use VBAR to relocate the exception vectors.
*/
ldr r0,[r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mcr p15,0, r0, c12, c0,0 /* Set VBAR */
#else
/*
* Copy the relocated exception vectors to the
* correct address
* CP15 c1 V bit gives us the location of the vectors:
* 0x00000000 or 0xFFFF0000.
*/
ldr r0,[r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
mrc p15,0, r2, c1, c0,0 /* V bit (bit[]) in CP15 c1 */
ands r2, r2, #(1<<)
ldreq r1,=0x00000000 /* If V=0 */
ldrne r1,=0xFFFF0000 /* If V=1 */
ldmia r0!,{r2-r8,r10}
stmia r1!,{r2-r8,r10}
ldmia r0!,{r2-r8,r10}
stmia r1!,{r2-r8,r10}
#endif
#endif
bx lr
ENDPROC(relocate_vectors)
第行,如果定义了CONFIG_CPU_V7M的话就执行第~行的代码,这是Cortex-M内核单片机执行的语句,因此对于I.MX6ULL来说是无效的。
第行,如果定义了CONFIG_HAS_VBAR的话就执行此语句,这个是向量表偏移,Cortex-A7是支持向量表偏移的。而且,在.config里面定义了CONFIG_HAS_VBAR,因此会执行这个分支。
第行,r0=gd->relocaddr,也就是重定位后uboot的首地址,向量表肯定是从这个地址开始存放的。
第行,将r0的值写入到CP15的VBAR寄存器中,也就是将新的向量表首地址写入到寄存器VBAR中,设置向量表偏移。