首页 » 智能 » 移植u-boot-2016.03到Jz2440之启动过程分析_函数_地址

移植u-boot-2016.03到Jz2440之启动过程分析_函数_地址

神尊大人 2025-01-22 08:11:38 0

扫一扫用手机浏览

文章目录 [+]

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") #指定输出可实行文件是elf格式,32位ARM指令,小端OUTPUT_ARCH(arm) #指定输出可实行文件平台是armENTRY(_start) #指定输出可实行文件的起始代码段为_start,这里跳转到vectors.S实行SECTIONS #SECTIONS 便是全体链接脚本的指定{ . = 0x00000000; #链接地址 . = ALIGN(4); #代码以4字节对齐 .text : #定义代码段 { (.__image_copy_start) # 映像文件赋值起始地址,它在文件 arch/arm/lib/sections.c 中定义: char __image_copy_start[0] __attribute__((section(".__image_copy_start"))); (.vectors) #在arch/arm/lib/vectors.S有代码:.section ".vectors", "ax",以是链接时首先存放的是vectors.S的二进制文件 arch/arm/cpu/arm920t/start.o (.text) #存放start.S代码 (.text) #接着存放其他文件的代码段 } . = ALIGN(4); #4字节对齐 .rodata : { (SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata))) } #存放只读数据段 . = ALIGN(4); #4字节对齐 .data : { #存放普通的数据段,存放的数据可读可写 (.data) } . = ALIGN(4); . = .; . = ALIGN(4); .u_boot_list : { #存放U-BOOT命令段 KEEP((SORT(.u_boot_list))); } . = ALIGN(4); .image_copy_end : { (.__image_copy_end) #映像文件赋值结束地址 } / __rel_dyn_start和__rel_dyn_end,这两个符号之间的区域存放着动态链接符号,只要给这里面的符号加上一定的偏移,拷贝到内存中相应位置处,就可以在绝对跳转中找到精确的函数。
/ .rel_dyn_start : { (.__rel_dyn_start) } .rel.dyn : { (.rel) } .rel_dyn_end : { (.__rel_dyn_end) } .end : { (.__end) } _image_binary_end = .; . = ALIGN(4096); .mmutable : { #存放MMU 表项 (.mmutable) } / bss 段,.bss节包含了程序中所有未初始化的全局变量 / / 由链接指令(OVERLAY)可见,.bss_start与__rel_dyn_start,.bss与__bss_base,.bss_end与__bss_limit是重叠的。
/ .bss_start __rel_dyn_start (OVERLAY) : { KEEP((.__bss_start)); # bss段起始地址 __bss_base = .; } .bss __bss_base (OVERLAY) : { (.bss) . = ALIGN(4); __bss_limit = .; } .bss_end __bss_limit (OVERLAY) : { KEEP((.__bss_end)); # bss段结束地址 } .dynsym _image_binary_end : { (.dynsym) } .dynbss : { (.dynbss) } .dynstr : { (.dynstr) } .dynamic : { (.dynamic) } .plt : { (.plt) } .interp : { (.interp) } .gnu.hash : { (.gnu.hash) } .gnu : { (.gnu) } .ARM.exidx : { (.ARM.exidx) } .gnu.linkonce.armexidx : { (.gnu.linkonce.armexidx.) }}

通过剖析u-boot.lds可以得出以下结论: (1) 程序的入口是_start,它在vector.S文件里; (2) 程序的链接地址是0,对付Jz2440开拓把,程序只能从Nor Flash启动

    make编译uboot成功后,除了会自动天生u-boot.lds外,还会天生System.map和u-boot.map这两个文件。
System.map文件按链接地址由小到大的顺序列出了所有符号与其代表的链接地址,u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址。
打开System.map,有如下代码:

移植u-boot-2016.03到Jz2440之启动过程分析_函数_地址 移植u-boot-2016.03到Jz2440之启动过程分析_函数_地址 智能

00000000 T __image_copy_start00000000 T _start00000020 T _undefined_instruction00000024 T _software_interrupt00000028 T _prefetch_abort0000002c T _data_abort00000030 T _not_used00000034 T _irq00000038 T _fiq00000040 T IRQ_STACK_START_IN00000060 t undefined_instruction000000c0 t software_interrupt00000120 t prefetch_abort00000180 t data_abort000001e0 t not_used00000240 t irq000002a0 t fiq00000300 T reset00000348 T c_runtime_cpu_setup0000034c t cpu_init_crit...

打开u-boot.map,有如下代码:

移植u-boot-2016.03到Jz2440之启动过程分析_函数_地址 移植u-boot-2016.03到Jz2440之启动过程分析_函数_地址 智能
(图片来自网络侵删)

Linker script and memory mapAddress of section .text set to 0x0 0x00000000 . = 0x0 0x00000000 . = ALIGN (0x4).text 0x00000000 0x613ec (.__image_copy_start) .__image_copy_start 0x00000000 0x0 arch/arm/lib/built-in.o 0x00000000 __image_copy_start (.vectors) .vectors 0x00000000 0x300 arch/arm/lib/built-in.o 0x00000030 _not_used 0x0000002c _data_abort 0x00000034 _irq 0x00000040 IRQ_STACK_START_IN 0x00000028 _prefetch_abort 0x00000000 _start 0x00000038 _fiq 0x00000020 _undefined_instruction 0x00000024 _software_interrupt arch/arm/cpu/arm920t/start.o(.text) .text 0x00000300 0x90 arch/arm/cpu/arm920t/start.o 0x00000300 reset 0x00000348 c_runtime_cpu_setup (.text)

从上面System.map和u-boot.map的也证明了程序的链接地址是0,程序的入口是 start,以是接下来便是从start开始剖析启动过程。

2.2 uboot启动过程剖析2.2.1 _start 入口

从前面的剖析可知,uboot程序的入口是 _start,它在vectors.S文件中,在vectors.S有如下代码:(删除了部分注释)

.globl _start.section ".vectors", "ax"_start:#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG /没有定义/ .word CONFIG_SYS_DV_NOR_BOOT_CFG /以是不会定义该变量/#endif b reset / 0x00000000 复位非常 / ldr pc, _undefined_instruction / 0x00000004 未定义指令非常 / ldr pc, _software_interrupt / 0x00000008 软件中断非常 / ldr pc, _prefetch_abort / 0x0000000C 指令预取中止非常 / ldr pc, _data_abort / 0x00000010 数据访问中止非常 / ldr pc, _not_used / 0x00000014 未利用非常,多余的指令 / ldr pc, _irq / 0x00000018 外部中断非常 / ldr pc, _fiq / 0x00000018 快速中断非常 // Indirect vectors table Symbols referenced here must be defined somewhere else / .globl _undefined_instruction .globl _software_interrupt .globl _prefetch_abort .globl _data_abort .globl _not_used .globl _irq .globl _fiq_undefined_instruction: .word undefined_instruction_software_interrupt: .word software_interrupt_prefetch_abort: .word prefetch_abort_data_abort: .word data_abort_not_used: .word not_used_irq: .word irq_fiq: .word fiq .balignl 16,0xdeadbeef

从地址0x00~0x1c是CPU非常的入口,这些地址保存着b reset、ldr pc, _undefined_instruction、ldr pc, _software_interrupt等函数跳转指令,当发生个中一种非常时,CPU跳到对应非常的入口地址实行这些跳转指令,从而跳转到非常处理函数。
当CPU上电时,会产生一个复位非常,CPU会跳转到地址0实行b reset指令,从而跳转到复位处理函数reset。
##### 2.2.2 reset 函数剖析 (1) Jz2440开拓板利用的s3c2440这款SoC,它对应的CPU是arm920t,reset 函数在arch/arm/cpu/arm920t/start.S,有如下代码:

.globl resetreset: / set the cpu to SVC32 mode / mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr, r0#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK) / relocate exception table / ldr r0, =_start ldr r1, =0x0 mov r2, #16copyex: subs r2, r2, #1 ldr r3, [r0], #4 str r3, [r1], #4 bne copyex#endif#ifdef CONFIG_S3C24X0 / turn off the watchdog /# if defined(CONFIG_S3C2400)# define pWTCON 0x15300000# define INTMSK 0x14400008 / Interrupt-Controller base addresses /# define CLKDIVN 0x14800014 / clock divisor register /#else# define pWTCON 0x53000000# define INTMSK 0x4A000008 / Interrupt-Controller base addresses /# define INTSUBMSK 0x4A00001C# define CLKDIVN 0x4C000014 / clock divisor register /# endif ldr r0, =pWTCON mov r1, #0x0 str r1, [r0] / mask all IRQs by setting all bits in the INTMR - default / mov r1, #0xffffffff ldr r0, =INTMSK str r1, [r0]# if defined(CONFIG_S3C2410) ldr r1, =0x3ff ldr r0, =INTSUBMSK str r1, [r0]# endif / FCLK:HCLK:PCLK = 1:2:4 / / default FCLK is 120 MHz ! / ldr r0, =CLKDIVN mov r1, #3 str r1, [r0]#endif / CONFIG_S3C24X0 / / we do sys-critical inits only at reboot, not when booting from ram! /#ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit#endif bl _main...

从上面的reset函数可知,该函数的运行过程如下: ① set the cpu to SVC32 mode (设置CPU为SVC32 模式) ② turn off the watchdog(关看门狗) ③ mask all IRQs by setting all bits in the INTMR (屏蔽所有中断) ④ 设置时钟比例 (设置FCLK、HCLK和PCLK时钟的比例) ⑤ 实行bl cpu_init_crit指令跳转到 cpu_init_crit函数    a.跳转到cpuinitcrit函数进行CPU初始化(刷新指令cache 和 数据cache、关闭MMU 和caches)    b.再跳转到底层初始化函数lowlevel_init(初始化内存,也便是设置内存掌握器) ⑥ 实行bl _main指令跳转到 _main函数

(2) cpuinitcrit 函数剖析:同样在arch/arm/cpu/arm920t/start.S文件中,cpu_init_crit 函数的代码如下:

cpu_init_crit: / flush v4 I/D caches / mov r0, #0 mcr p15, 0, r0, c7, c7, 0 / flush v3/v4 cache / mcr p15, 0, r0, c8, c7, 0 / flush v4 TLB / / disable MMU stuff and caches / mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM) orr r0, r0, #0x00000002 @ set bit 1 (A) Align orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache mcr p15, 0, r0, c1, c0, 0 / before relocating, we have to setup RAM timing because memory timing is board-dependend, you will find a lowlevel_init.S in your board directory. / mov ip, lr bl lowlevel_init mov lr, ip mov pc, lr /返回reset函数,连续实行_main函数/

从上面的cpu_init_crit 函数可知,实现的功能有: ① 进行CPU初始化(刷新指令cache 和 数据cache、关闭MMU 和caches) ② 实行bl lowlevel_init指令,跳转到lowlevel_init函数

(3) lowlevelinit函数剖析:lowlevelinit函数定义在对应的单板目录下(board/samsung/jz2440/lowlevelinit.S)的lowlevelinit.S,lowlevel_init函数代码如下:

.globl lowlevel_initlowlevel_init: / memory control configuration / / make r0 relative the current location so that it / / reads SMRDATA out of FLASH rather than memory ! / ldr r0, =SMRDATA / 将SMRDATA的首地址(第一个.word)内存单元数据放置到r0寄存器中 / ldr r1, =CONFIG_SYS_TEXT_BASE / CONFIG_SYS_TEXT_BASE=0x0(include/configs/jz2440.h中默认定义) / sub r0, r0, r1 /得到SMRDATA的偏移地址/ ldr r1, =BWSCON / Bus Width Status Controller / /获取BWSCON寄存器的地址0x48000000/ add r2, r0, #134 /内存设置干系的寄存器有13个,每一寄存器占4字节,打算出SMRDATA的结束地址,赋值给r2// r0:SMRDATA的首地址;r1:BWSCON寄存器的地址;r2:SMRDATA的结束地址/0: ldr r3, [r0], #4 /取SMRDATA保存的第一个数据赋给r3,然后地址r0加4,跳到第二个数据的地址/ str r3, [r1], #4 /把r3的数据赋值个BWSCON寄存器,然后地址r1加4,指向下一个寄存器/ cmp r2, r0 /比较r2是否与r0相等,如果不相等,实行bne 0b指令,跳转到0:连续循环/ bne 0b / everything is fine now / mov pc, lr /返回到cpu_init_crit函数/ .ltorg/ the literal pools origin /SMRDATA: .word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28)) .word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) .word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) .word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) .word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) .word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) .word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) .word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT) .word 0x32 .word 0x30 .word 0x30

lowlevelinit函数的功能紧张是初始化存储掌握器,经由此初始化之后,内存才可以利用,末了经由两次的返回,返回到reset函数,连续往下实行 main函数。
##### 2.2.3 _main 函数剖析 在reset中实行到了bl _main,跳转到main,main入口在arch/arm/lib/crt0.S文件中,有如下代码:

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) /实行该指令 CONFIG_SYS_INIT_SP_ADDR = 0x30000f50/ #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 / /设置栈后,8字节对齐 sp=0x30000f50/#endif mov r0, sp /把sp复制给r0,作为board_init_f_alloc_reserve函数的参数/ bl board_init_f_alloc_reserve mov sp, r0 /r0是board_init_f_alloc_reserve函数的返回值, 以是此时 sp = 0x‭3000 0EA0/ / set up gd here, outside any C code / mov r9, r0 /r9 寄存器存放着全局变量 gd 的地址,r9=0x‭3000 0EA0,r0作为board_init_f_init_reserve函数的参数/ 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_codehere:/ 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 32-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. /#endifENDPROC(_main)

进入 main函数后的第一件事是设置栈,把CONFIGSYSINITSPADDR(在上一节创建的include/configs/jz2440.h有定义)的值赋给sp,通过u-boot的反汇编(arm-linux-objdump -D u-boot > u-boot.dis)文件u-boot.dis可以得知sp=0x30000f50,接着是8字节对齐,然后调用C函数boardinitfallocreserve。
###### 2.2.3.1 board
initfallocreserve函数剖析 boardinitfallocreserve函数定义在common/init/boardinit.c,有如下代码:

/ include/linux/kernei.h中有如下定义:#define rounddown(x, y) ( \{ \ typeof(x) __x = (x); \ / 定义x类型的__x变量即是x / __x - (__x % (y)); \ / 栈向下增长,以是向下16字节对齐 /} \)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 16 bytes) / top = rounddown(top-sizeof(struct global_data), 16); /对付Jz2440,top = 0x30000f50/ /top = (top -168)&(~0xf)=(0x30000f50-168)&(~0xf)= 0x‭3000 0EA0‬/ return top;}

调用boardinitfallocreserve函数之前,指令mov r0, sp设置了传入boardinitfallocreserve函数的参数r0=sp=0x30000f50(汇编的传参问题可以理解一下ATPCS-ARM寄存器及值通报规则),经由rounddown函数的对齐后top=0x‭30000EA0,boardinitfallocreserve函数的返回值top即汇编中的r0,以是 main函数调用完 boardinitfalloc_reserve函数后,r0=0x‭30000EA0。

2.2.3.2 boardinitfinitreserve函数剖析

同样 boardinitfinitreserve函数也定义在common/init/board_init.c,有如下代码:

void board_init_f_init_reserve(ulong base) /base = 0x‭3000 0EA0/{ struct global_data gd_ptr; / 定义global_data构造体类型指针 /#ifndef _USE_MEMCPY int 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; / global_data构造体指针指向base即栈SP地址处,即0x‭3000 0EA0 / / 清0 global_data构造体 /#ifdef _USE_MEMCPY memset(gd_ptr, '\0', sizeof(gd));#else for (ptr = (int )gd_ptr; ptr < (int )(gd_ptr + 1); ) ptr++ = 0;#endif ... ... / 无关代码 /}

调用boardinitfinitreserve函数之前,指令mov r9, r0设置了全局变量 gd 的地址r9=r0=0x‭30000EA0,r0作为boardinitfinitreserve函数的参数;boardinitfinitreserve函数的功能只是将栈SP往上的globaldata构造体大小空间清0。
关于r9 寄存器为什么存放的全局变量 gd 的地址,在arch/arm/include/asm/global
data.h的83行有定义:

#ifdef CONFIG_ARM64#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t gd asm ("x18")#else#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t gd asm ("r9")#endif2.2.3.3 boardinitf函数剖析

boardinitf函数定义在common/boardf.c中,去掉无关代码后的boardinit_f函数如下:

void board_init_f(ulong boot_flags){ gd->flags = boot_flags; gd->have_console = 0; if (initcall_run_list(init_sequence_f)) hang();}

boardinitf函数的紧张功能是调用initcallrunlist函数循环调用initsequencef数组里的初始化函数,以达到初始化硬件的目的。
假如有硬件初始化失落败,会调用hang函数,打印### ERROR ### Please RESET the board ###后,进入去世循环。
initsequencef数组去掉无用宏代码后如下(判断是否定义了宏方法:查看根目录的.config是否定义)

static init_fnc_t init_sequence_f[] = { / setup_mon_len函数是设置gd构造体成员gd->mon_len的函数; 在setup_mon_len函数中:gd->mon_len = (ulong)&__bss_end - (ulong)_start; gd->mon_len即是uboot.bin大小加上bss段的大小,_start为0 从反汇编的setup_mon_len函数可知:(ulong)&__bss_end = 0x000c636c; 以是,gd->mon_len = 0x000c636c; / setup_mon_len, / 1.在initf_malloc函数里,由于CONFIG_SYS_MALLOC_F_LEN没定义, 直接返回0,相称于一个空函数 2.initf_console_record函数,同理 / initf_malloc, initf_console_record,/ 空函数 / arch_cpu_init, / 空函数 / / basic arch cpu dependent setup / initf_dm, / 空函数 / arch_cpu_init_dm, / 空函数 / mark_bootstage, / 标记名字 // need timer, go after init dm / board_early_init_f, / 设置系统时钟,设置各个GPIO引脚 / timer_init, / initialize timer / env_init, / 设置gd的成员,初始化环境变量 // initialize environment / init_baud_rate, / initialze baudrate settings / serial_init, / serial communications setup / console_init_f, / stage 1 init of console / display_options, / 打印uboot版本等信息 // say that we are here / display_text_info, / 打印uboot代码信息 // show debugging info if required / print_cpuinfo, / 打印uboot时钟频率信息 // display cpu info (and speed) / announce_dram_init, / 打印“ DRAM: ” / / TODO: unify all these dram functions? / dram_init, / 设置gd->ram_size= 0x04000000(64MB) // configure available RAM banks / setup_dest_addr, / 将gd->relocaddr、gd->ram_top指向SDRAM最顶端 / reserve_round_4k, / gd->relocaddr 4KB对齐 / reserve_mmu, / 预留16KB的MMU页表并且64KB对齐 / reserve_trace, / 空函数 / /reserve_uboot的浸染是在SDRAM预留存放u-boot的空间(加上bss段) gd->relocaddr -= gd->mon_len; gd->relocaddr &= ~(4096 - 1); gd->start_addr_sp = gd->relocaddr; / reserve_uboot, / reserve_malloc函数: gd->start_addr_sp = gd->start_addr_sp - TOTAL_MALLOC_LEN; 由于jz2440.h默认定义了CONFIG_ENV_ADDR,以是此时在include/common.h中 实行#define TOTAL_MALLOC_LEN (CONFIG_SYS_MALLOC_LEN + CONFIG_ENV_SIZE) 也便是TOTAL_MALLOC_LEN=410241024+0x10000=4MB+64KB 预留4MB+64KB MALLOC内存池 / reserve_malloc, / reserve_board函数: gd->start_addr_sp -= sizeof(bd_t); 预留bd_t构造体空间,查看反汇编可知为80字节 gd->bd = (bd_t )gd->start_addr_sp; 指定重定位bd地址 memset(gd->bd, '\0', sizeof(bd_t)); 清零 / reserve_board, /setup_machine函数: gd->bd->bi_arch_number = CONFIG_MACH_TYPE; / board id for Linux / setup_machine, reserve_global_data,/ 预留gd构造体空间,查看反汇编可知为168字节。
并设置gd->new_gd / reserve_fdt, / 如果设置了gd->new_fdt则预留fdt设备树空间,这里没有设置,不用管 / reserve_arch,/ 空函数 / / reserve_stacks函数: gd->start_addr_sp -= 16; gd->start_addr_sp &= ~0xf; return arch_reserve_stacks();这里调用的不是board_f.c里的arch_reserve_stacks函数 由于该函数被__weak润色符声明,调用的是arch/arm/lib/stack.c里的arch_reserve_stacks函数 gd->irq_sp = gd->start_addr_sp; gd->start_addr_sp -= 16; / reserve_stacks, setup_dram_config,/ 设置gd构造体的SDRAM地址与大小 / show_dram_config,/ 打印SDRAM信息 / display_new_sp, / 打印新的栈地址 / reloc_fdt, /没有设置设备树,忽略/ /setup_reloc函数: gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;打算重定位地址与链接地址偏移值,CONFIG_SYS_TEXT_BASE在jz2440.h定义为0,gd->reloc_off = gd->relocaddr memcpy(gd->new_gd, (char )gd, sizeof(gd_t)); 把旧的gd复制到新的gd地址里 / setup_reloc, NULL,};

进过上述的剖析,实行了boardinitf函数后,可以得出SDRAM的内存分布图,如下所所示:(下图是未经修正的u-boot-2016.03源码实行smdk2410_defconfig配置的内存分布)

实行完boardinitf函数,uboot启动的第一阶段结束,返回到crt0.S文件连续实行汇编代码。

2.2.3.4 重新设置栈

实行完boardinitf函数后,重新设置栈,代码如下:

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 /

上述的代码涉及了汇编访问构造体成员变量的问题,由前面的剖析可知r9是gd构造体的地址,r9 = 0x30000EA0,个中: ① ldr sp, [r9, #GD_START_ADDR_SP],GD_START_ADDR_SP是gd成员变量gd->start_addr_sp在gd的偏移量,从反汇编代码可知,    GD_START_ADDR_SP = 60;从前面的uboot内存分布图可知,gd->start_addr_sp = 0x33B18EE0,sp = gd->start_addr_sp = 0x33B18EE0; ② ldr r9, [r9, #GD_BD]同理,从前面的uboot内存分布图可知,r9 = gd->bd = 0x33B18FB0; ③ sub r9, r9, #GD_SIZE,从内存分布图可知,gdt构造体在gdt构造体下面,r9减去GD_SIZE恰好得到gd_t构造体的地址,    即 r9 = gd->new_gd = 0x33B18F08。

2.2.3.5 uboot代码重定位

设置完栈空间之后,连续往下剖析代码:

adr lr, here /链接寄存器lr指向下面的here,此时的here的地址是重定位之前的地址/ ldr r0, [r9, #GD_RELOC_OFF] / r0 = gd->reloc_off,在setup_reloc函数中有 gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE = 0x33F29000 - 0 = 0x33F29000/ add lr, lr, r0 / 链接指向地址指向重定位后的here,此时指向地址在SDRAM中 /#if defined(CONFIG_CPU_V7M) /未定义/ orr lr, #1 / As required by Thumb-only /#endif ldr r0, [r9, #GD_RELOCADDR] / r0 = gd->relocaddr = 0x33F29000 设置重定位地址/ b relocate_codehere:/ now relocate vectors / bl relocate_vectors

上面的这段代码的浸染是:在代码重定位之前设置好程序返回的链接地址(SDRAM中的地址),当代码重定位完成之后,直接去到SDRAM中的here连续实行程序。
(1) relocate_code函数剖析:(该函数定义在arch/arm/lib/relocate.S)

ENTRY(relocate_code) ldr r1, =__image_copy_start / r1=__image_copy_start = 0/ subs r4, r0, r1 /得到重定位的偏移地址 r4 = 0x33F29000 / /通过判断r4是否即是0来判断uboot是否已经在重定位地址上了, 如果r4=0表示uboot已经在重定位地址上了,不须要再重定位了直接跳到lr指向的地方去(也便是here标号处) / beq relocate_done / 重定位完成,跳到lr指向的地方去(也便是here标号处) / ldr r2, =__image_copy_end /r2=__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 / /r2 = __rel_dyn_start,也便是.rel.dyn 段的起始地址/ ldr r3, =__rel_dyn_end / r3 <- SRC &__rel_dyn_end / /r3 = __rel_dyn_end,也便是.rel.dyn 段的终止地址 /fixloop: /从.rel.dyn 段起始地址开始,每次读取两个4字节的数据存放到 r0 和 r1 寄存器中, r0 存放低4字节的数据,也便是 Label 地址;r1 存放高4字节的数据,也便是 Label 标志, 从反汇编的.rel.dyn段开始可以看到如下数据: 123251 Disassembly of section .rel.dyn: 123252 123253 00077794 <__rel_dyn_end-0x9570>: 123254 77794: 00000020 .word 0x00000020 123255 77798: 00000017 .word 0x00000017 123256 7779c: 00000024 .word 0x00000024 123257 777a0: 00000017 .word 0x00000017 123258 777a4: 00000028 .word 0x00000028 123259 777a8: 00000017 .word 0x00000017 123260 777ac: 0000002c .word 0x0000002c 123261 777b0: 00000017 .word 0x00000017 由此可见,Label的标记是0x17 / ldmia r2!, {r0-r1} / (r0,r1) <- (SRC location,fixup) / and r1, r1, #0xff / r1 中给的值与 0xff 进行与运算,实在便是取 r1 的低 8 位/ cmp r1, #23 / relative fixup? / /判断 r1 中的值是否即是 23(0X17)/ bne fixnext /如果 r1 不即是23的话就解释不是描述 Label 的,实行函数 fixnext,否则的话连续实行下面的代码/ / relative fix: increase location by offset / add r0, r0, r4 /r4 是偏移地址,重定位的偏移地址r4 = 0x33F29000 / ldr r1, [r0] /把r0里的数据取取出来放到r1中/ add r1, r1, r4 /然后加上偏移地址/ str r1, [r0] /修正完后,重新写回r0地址处/fixnext: cmp r2, r3 blo fixlooprelocate_done:#ifdef __XSCALE__ / On xscale, icache must be invalidated and write buffers drained, even with cache disabled - 4.2.7 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#endifENDPROC(relocate_code)

relocate_code函数紧张分为两个部分: ① 把uboot拷贝到SDRAM中,完成代码的重定位; ② 修正动态链接地址数据:地址存放的是Label:Label实在便是一个地址,大略的说便是地址存放的数据是一个地址,所以为了防止代码运行出错,把这个Label拷贝到链接地址后,还需加上一个偏移地址修正这个Label。

(2) relocate_vectors函数剖析:(该函数也定义在arch/arm/lib/relocate.S)

ENTRY(relocate_vectors) / 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 //取出重定位后uboot的地址:r0=0x33F29000 / /mrc p15, 0, r2, c1, c0, 0指令是CPU协处理器指令 该指令将 CP15 中 C1 寄存器的值读取到 R2 寄存器, 紧张是掌握 bit[13]: V 对付支持高端非常向量表的系统,本掌握位掌握向量表的位置: bit[13]=0 :选择低端非常中断向量 0x0~0x1c bit[13]=1 :选择高端非常中断向量0xffff0000~ 0xffff001c / mrc p15, 0, r2, c1, c0, 0 / V bit (bit[13]) in CP15 c1 / / ands 后面的 s 会影响CPSR状态的寄存器的标志位 若 相与的 结果为0,则CPSR的状态标志位 Z = 1;反之,Z = 0 / ands r2, r2, #(1 << 13) ldreq r1, =0x00000000 / If V=0,则Z=1,可实行 ldreq指令 / ldrne r1, =0xFFFF0000 / If V=1,则Z=0,可实行 ldrne指令/ ldmia r0!, {r2-r8,r10} /r2,r3,r4,r5,r6,r7,r8,r10对应着重定位后的8个非常向量/ stmia r1!, {r2-r8,r10} /把这8个非常向量拷贝到0地址里/ ldmia r0!, {r2-r8,r10} /此时的R0 = 0x33F29020,把0x33F29020~0x33F2903存放的非常处理函数的地址取出来/ stmia r1!, {r2-r8,r10} /然后再把这些函数的地址存放到地址0x20~0x3c/ bx lrENDPROC(relocate_vectors)

relocate_vectors函数的内容如下: ① 判断系统利用的是低端非常中断向量(0x0~0x1c),还是低高端非常中断向量(0xFFFF0000 ~ 0xFFFF001C); ② 进行非常中断向量重定位:把代码重定位后的非常中断向量复制到0地址;

(3) 非常中断向量重定位前后的差异: 我们可以从uboot的反汇编的开头可以看到非常中断向量是放在最前面的地址0x00~0x1C,该地址存放着非常的跳转指令,当发生CPU发生某种非常时,会通过对应的跳转指令跳转到指定的非常处理函数,如下图所示:

非常处理函数的地址又存放在0x20~0x38的地方,如下图所示:

这些非常处理函数属于Label,编译uboot之后,它们也会被存放在.rel.dyn段中(如下图所示)用于代码重地位之后修正代码;

有前面relocate_vectors函数的剖析可知,uboot在进行代码重定位时,会把.rel.dyn段里存放的地址数据取出来加上一个偏移地址(0x33F29000)得到一个新的地址,然后再取新地址里面的数据加上这个偏移地址,就可以得到重定位后非常处理函数的地址;以是,.rel.dyn段里的数据 加0x33F29000后的地址里的数据对应如下:

0x33F29020 存放 0x33F290600x33F29024 存放 0x33F290C00x33F29028 存放 0x33F291200x33F2902C 存放 0x33F291800x33F29030 存放 0x33F291E00x33F29034 存放 0x33F292400x33F29038 存放 0x33F292A0

以是,uboot重定位后,互异常处理函数所在的地址如下:

0x33F29060 -> _undefined_instruction0x33F290C0 -> _software_interrupt0x33F29120 -> _prefetch_abort0x33F29180 -> _data_abort0x33F291E0 -> _not_used0x33F29240 -> _irq0x33F292A0 -> _fiq

以是,uboot非常向量重定位后,地址0x00~0x1c的内容不变,仍旧如下:

当存放非常向量处理函数地址0x20~0x3c的内容发生了变革,它们存放的内容如下:

0x00000020 存放 0x33F290600x00000024 存放 0x33F290C00x00000028 存放 0x33F291200x0000002C 存放 0x33F291800x00000030 存放 0x33F291E00x00000034 存放 0x33F292400x00000038 存放 0x33F292A00x0000003C 存放 0xdeadbeef

因此,非常向量重定位之后,当CPU发生非常时,会跳转到存放在SDRAM的非常处理函数实行,而不是实行存放在Flash的非常处理函数。

2.2.3.6 清bss段

bl relocate_vectors之后(去掉无关代码)的代码如下 (arch/arm/lib/crt0.S文件中):

bl c_runtime_cpu_setup / mov pc, lr,跳到下一条指令 / ldr r0, =__bss_start / this is auto-relocated! / 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 / strlo r2, [r0] / clear 32-bit BSS word / addlo r0, r0, #4 / move to next / blo clbss_l bl coloured_LED_init bl red_led_on / call board_init_r(gd_t id, ulong dest_addr) / mov r0, r9 / gd_t / / r0即是新的gd构造体 / ldr r1, [r9, #GD_RELOCADDR] / dest_addr / / r1即是gd->relocaddr也便是重定位地址 / /把r0作为参数id, r1作为参数dest_addr传给board_init_r函数/ / call board_init_r / ldr pc, =board_init_r / this is auto-relocated! /

经由对上述代码的剖析,这部分代码的紧张功能如下: ① 清bss段; ② 初始化LED,点亮LED; ③ 调用 boardinitr函数,进入uboot初始化的第二阶段。

2.2.3.7 boardinitr函数剖析

board_init_r函数去掉无关代码后如下 (common/board_f.c文件中):

void board_init_r(gd_t new_gd, ulong dest_addr){ if (initcall_run_list(init_sequence_r)) hang(); / NOTREACHED - run_main_loop() does not return / hang();}

从上面的代码可知,board_init_r与board_init_f函数类似,它也是通过函数initcall_run_list循环调用init_sequence_r数组里的初始化函数,init_sequence_r数组去掉无用宏代码后如下:

init_fnc_t init_sequence_r[] = { initr_trace, / 空函数 / initr_reloc, / gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT; 标记重定位完成/#ifdef CONFIG_ARM initr_caches, / 打印"WARNING: Caches not enabled\n" /#endif initr_reloc_global_data, / monitor_flash_len = _end - __image_copy_start; //打算uboot映像的大小/ initr_barrier, / 空函数 / / initr_malloc函数: malloc_start = gd->relocaddr - TOTAL_MALLOC_LEN;//TOTAL_MALLOC_LEN = 64MB+64kB,算出malloc内存池的起始地址 mem_malloc_init((ulong)map_sysmem(malloc_start, TOTAL_MALLOC_LEN),TOTAL_MALLOC_LEN); / initr_malloc, initr_console_record, / 空函数 / bootstage_relocate, initr_bootstage, / 标记名字 /#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) / board_init函数: gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; gd->bd->bi_boot_params = 0x30000100; icache_enable(); dcache_enable(); / board_init, #endif stdio_init_tables, initr_serial, / 调用serial_initialize() / initr_announce, / debug "Now running in RAM - U-Boot at: %08lx\n" / power_init_board, / 空函数 /#ifndef CONFIG_SYS_NO_FLASH initr_flash, / 初始化NOR Flash /#endif#ifdef CONFIG_CMD_NAND initr_nand, / 初始化NAND Flash /#endif initr_env, / 初始化环境变量 / initr_secondary_cpu, / 空函数 / stdio_add_devices, /添加标准io设备,例如I2C、LCD、键盘等/ / initr_jumptable函数: gd->jt = malloc(sizeof(struct jt_funcs)); #include <_exports.h> / initr_jumptable, console_init_r, / 掌握台初始化 / interrupt_init, / 中断初始化 /#if defined(CONFIG_ARM) || defined(CONFIG_AVR32) initr_enable_interrupts, / 使能中断 /#endif#ifdef CONFIG_CMD_NET initr_ethaddr, / eth_getenv_enetaddr("ethaddr", bd->bi_enetaddr); /#endif#ifdef CONFIG_CMD_NET INIT_FUNC_WATCHDOG_RESET initr_net, / 网卡初始化 /#endif run_main_loop, / 进入main_loop /};

实行完boardinitr函数,uboot第二阶段的初始化完成,uboot启动完成,末了调用runmainloop进入main_loop循环函数,若掌握台有任意输入,则进入掌握台命令解析-实行的循环。
若无,则U-Boot将启动内核。

标签:

相关文章