Uboot的入口在uboot根目录下的uboot.lds(只有编译u-boot才会出现)
Uboot.lds用来描述输出文件的内存布局,也就是确认程序的运行地址
Ubbot.lds为代码入口点:_start,_start在文件arch/arm/lib/vectors.S中有定义。
Vectors段保存了中断向量表,vector.S的代码保存在vectors段中。
Arch/arm/cpu/armv7/start.s编译出来的代码放在中断向量表后。
其他的代码放在text段中。
Uboot.map是uboot的映像文件,可以从此文件看到某个文件或者函数链接到那个地址。
_start开始的是中断向量表
_start(arch/arm/lib/vectors.S)跳转到 reset函数(arch/arm/cpu/armv7/start.S中),
reset函数跳转到save_boot_params函数(同样定义在arch/arm/cpu/armv7/start.S)
save_boot_params函数跳转到save_boot_params_ret函数中(同上文件)
此函数先是完成设置cpu处于svc32模式,并且关闭FIQ/RIQ这两个中断。
然后清除SCTLR寄存器中bit13位,此位是向量表控制位,置0可设置向量表重定位。
最后分别调用cpu_init_cp15、cpu_init_crit和_main
其中Cpu_init_cp15是用来设置cp15相关内容,比如关闭MMU等,
Cpu_init_crit仅调用lowlevel_init函数(此函数在arch/arm/cpu/armv7/lowlevel_init.S)。
1、设置sp指针指向CONFIG_SYS_INIT_SP_ADDR(在include/configs/mx6ullevk.h中定义)
CONFIG_SYS_INIT_RAM_ADDR=IRAM_BASE_ADDR=0X00900000
CONFIG_SYS_INIT_RAM_SIZE=IRAM_SIZE=0X20000=128KB
GENERATED_GBL_DATA_SIZE=256
所以
CONFIG_SYS_INIT_SP_OFFSET=0X00020000-256=0X1FF00
CONFIG_SYS_INIT_SP_ADDR=0X00900000+0X1FF00=0X0091FF00(这属于imx6ull的内部ram)
2、sp指针减去GD_SIZE(248),此时sp指针的地址为0x91ff00-248=0x0091fe08
3、将sp指针地址放在r9寄存器中,将lr赋给pc,调用s_init函数
S_init函数定义在在arch/arm/cpu/armv7/mx6/soc.c中
该函数主要判断当前CPU类型,然后直接返回,相当于是个空函数,从s_init返回到lowlevel_init函数,再返回到cpu_init_crit,再返回到save_boot_params_ret中。接下来执行save_boot_params_ret中_main函数
_main函数(arch/arm/lib/ct0.S)
1、设置sp指针指向0x0091ff00,并保存到r0中,即r0=0x0091ff00
调用函数borad_init_f_alloc_reserve(common/init/board_init.c中),传入参数r0
该函数主要是留出早期的malloc内存区域和gd内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400,global_data=248;最后返回top=0X0091FA00。完成之后内存分布如图:
2、r0保存着函数的返回值,将sp指针指向0X0091FA00
3、将r0的值写入到r9中,因为r9存放着全局变量gd的地址。Uboot定义了一个指向gd_t的指针,gd存放在r9中,因此gd是个全局变量。gd_t是个结构体。因此gd指向0x0091FA00。
4、调用board_init_f_init_reserve函数(common/init/board_init.c),此函数是初始化gd,其实就是清零处理。此函数还设置gd->malloc_base为0X0091FB00,就是early malloc的起始地址。
5、设置r0为0,调用board_init_f函数(在common/board_f.c中),主要初始化DDR,定时器,完成代码的拷贝等。调用board_init_f函数主要实现以下两个工作:
①初始化一些外设,比如串口、定时器、或者打印一些信息等。
②初始化gd的各个成员变量,uboot会将自己重定位到DRAM最后面的地址区域,也就是将自己拷贝到DRAM最后面的内存区域中,这么做是为了给linux腾出空间,防止linux kernel覆盖掉uboot。再拷贝前肯定要给uboot各个部分分配内存位置和大小,比如gd应该放在哪个位置,malloc内存池应该放在那个位置等,这些信息都要保存在gd的成员变量值,最终形成一个完整的内存分配图,在后面重定位uboot的时候需要使用到这个内存图。
在board_init_f函数中调用了初始化序列init_sequence_f里面的一系列函数。
(1)setup_mon_len函数设置了gd的mon_len成员变量,即整个代码对的长度 0xa8e74
(2)initf_malloc函数初始化gd的malloc相关的变量,其中malloc_limit表示内存池的大小0x400
(3)arch_cpu_init函数初始化架构相关的内容,cpu级别的操作
(4)initf_dm驱动模块初始化
(5)board_early_init_f板子早期的一些初始化设置
(6)timer_init初始化定时器,内核时钟,给uboot提供时间
(7)board_postclk_init函数设置板子电压
(8)get_clocks函数获取时钟值,sd卡外设的时钟
(9)env_init函数设置gd的env_addr,就是环境变量的保存地址
(10)init_board_rate函数设置波特率、serial_init初始化串口
(11)concole_init_f设置gd->have_consloe为1,表示有个控制台,此函数将暂存在缓冲区的数据打印到控制台
(12)display_options通过串口输出信息,display_text_info打印文本信息
(13)printf_cpuinfo打印CPU信息 show_board_info打印板子信息
(14)init_func_i2c初始化i2c
(15)dram_init 设置gd->ram_size,并非真正的初始化ddr,对于i.m6ull emmc就是512MB
(16)post_init_f完成一些测试,初始化gd->post_init_f_time
(17)setup_dest_addr 设置目的地址,设置gd->ram_size、gd->ram_top、gd->relocaddr这三个值。reserve_round_4k函数用于对gd->relocaddr做4KB对齐。调整后不变。
(18)reserve_mmu 留出MMU的TLB表的位置,分配MMU的TLB表内存以后会对gd->relocaddr做64KB字节对齐,完成以后gd->arch.tlb_size、gd_.arch.tlb_addr和gd->relocaddr如图所示:
(19)reserve_uboot 留出重定位后的uboot所占的内存区域,uboot所占字节大小有ge->mon_len所之巅,并且重新设置gd->start_addr_sp
(20)reserve_malloc留出malloc区域调整gd->start_addr_sp位置,malloc区域有宏TOTAL_MALLOC_LEN定义,调整后的gd->start_addr_sp为
(21)reserve_board留出板子bd所占的内存区,bd是结构体bd_t,大小为80字节
(22)reserve_global_data 保留gd_t的内存区域,gd_t结构体的大小为248B
(23)reserve_stacks留出栈空间,先对gd->start_addr_sp减去16,然后做16字节对齐,如果使能IRQ的话还要留出IRQ相应的内存
(24)setup_dram_config设置dram信息,就是设置gd->bd->bi_dram[0].start和gd->bd->bi_dram[0].size后面传递个linux内核,告诉linux dram的起始地址和大小
(25)display_new_sp 显示新的sp位置,也就是gd->start_addr_sp
(26)setup_reloc,设置gd的其他成员变量,供后面定位使用,并且将一起的gd拷贝到gd->new_gd处
6、重新设置环境(sp和gd)、获取gd->start_addr_sp的值赋给sp,在函数board_init_f中会初始化gd的所有成员变量,其中gd->start_addr_sp=0x9ef44e90。所以相当于设置sp= start_addr_sp=0x9ef44e90。0x9ef44e90是ddr中的地址,说明新的sp和gd将会存在DDR中,GD_START_ADDR_SP=64。
7、获取gd->bd的地址赋给r9,此时r9存放的是老的gd,新的gd在bd下面,所以r9减去gd的大小就是新的gd的位置,获取到新的gd赋值给r9
8、设置lr寄存器为here,这样后面执行其他函数返回时就返回到here处。
9、读取gd->reloc_off的值给r0,gd_reloc_off=68,lr寄存器的值加上r0的值,重新赋给lr,因为接下来要重新定位代码,也就是把代码拷贝到新的地方去(现在uboot的存放的起始地址为0x87800000,下面要把uboot拷贝ddr最后面的地址空间,将0x87800000开始的内存空出来),其中就包括here,因此lr中here要使用重定位的位置。
10、读取gd->relocaddr的值赋给r0,此时r0寄存器就保存着uboot拷贝到的目的地址,为0x9ff47000,GD_RELOCADDR=48
11、调用函数relocate_code,也就是代码重定位函数,此函数负责将uboot拷贝到新的地方去。
函数relocate_code:
①首先将r1=_image_copy_start,r1保存原地址,即0x878000000,r2保存代码拷贝之前的结束地址。r0=0x9FF47000,这个地址就是拷贝的目标地址,r4=r0-r1=0x18747000,即为偏移地址。通过r10和r11来进行数据的传输。比较r1与r2的值,检查拷贝是否完成。
②重定位段.rel.dyn段:
重定位就是uboot将自身拷贝到dram的另一个地址去继续运行(DRAM的高地址),一个可执行文件.bin文件,其链接地址和运行地址必须相等,就是链接到哪个地址,在运行之前就要拷贝到哪个地址去,现在进行了重地位,运行地址和链接地址不同了,这样寻址就会出现问题。
对于这个问题,采用第三方偏移地址,称为label,这就是重定位后运行不会出错的原因
12、调用函数relocate_vectors对中断向量表做重定位。
该函数实现对中断向量表的重定位,r0=gd->relocaddr,也就是重定位后uboot的首地址,向量表肯定是从这个地址开始存放的,然后将r0的值写入到cp15的VB寄存器中,也就是将新的向量表首地址写入到寄存器VBAR,设置向量表偏移。
13、调用函数c_runtime_cpu_setup函数。
14、设置函数board_init_r的两个参数,第一个参数是gd,因此,读取gd保存到r0中。第二个参数是目标地址,因此r1=gd->rellocaddr。
Board_init_f初始化了一些外设和gd的成员变量,但并没有初始化所有的外设,这时候就需要board_init_r函数来实现后续的工作。在board_init_r函数中调用了initcall_run_list函数来执行初始化序列init_sequence,这是一个函数的集合。