1、前言
Linux系统的启动需要一个bootloader程序,该bootloader程序会先初始化DDR等外设,然后将Linux内核从Flash中拷贝到DDR中,最后启动Linux内核,uboot的全称为Universal Boot Loader,Linux系统中常用的bootloader就是uboot,接下来,将会进行简单的uboot启动流程分析,uboot的源码为uboot-imx-rel_imx_4.15_2.1.0。
2、uboot入口
在分析之前,需要对整个uboot工程进行编译,生成一些分析时需要用到的文件,例如链接文件uboot.lds和uboot映射文件uboot.map,通过链接文件,可以找到uboot的入口,找到uboot启动后运行的第一行代码。
在uboot源码根目录下找到链接文件uboot.lds,如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") OUTPUT_ARCH(arm) ENTRY(_start) //当前入口_start SECTIONS { . = 0x00000000; . = ALIGN(4); .text : { *(.__image_copy_start) //入口 *(.vectors) //中断向量表 arch/arm/cpu/armv7/start.o (.text*) //arch/arm/cpu/armv7/start.S代码段 *(.text*) } . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . = ALIGN(4); .data : { *(.data*) } . = ALIGN(4); . = .; . = ALIGN(4); .u_boot_list : { KEEP(*(SORT(.u_boot_list*))); } . = ALIGN(4); .image_copy_end : { *(.__image_copy_end) } .rel_dyn_start : { *(.__rel_dyn_start) } .rel.dyn : { *(.rel*) } .rel_dyn_end : { *(.__rel_dyn_end) } .end : { *(.__end) } _image_binary_end = .; . = ALIGN(4096); .mmutable : { *(.mmutable) } .bss_start __rel_dyn_start (OVERLAY) : { KEEP(*(.__bss_start)); __bss_base = .; } .bss __bss_base (OVERLAY) : { *(.bss*) . = ALIGN(4); __bss_limit = .; } .bss_end __bss_limit (OVERLAY) : { KEEP(*(.__bss_end)); } .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.*) } }
在上面的链接文件中,可以确定uboot的入口为_start,该定义在arch/arm/lib/vectors.S文件中:
_start: #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG .word CONFIG_SYS_DV_NOR_BOOT_CFG #endif b reset //中断向量表,跳转到reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq
进入到_start后,运行b reset语句,跳转到reset中运行,b reset后面跟着中断向量表,reset的定义,对于不同的CPU架构不一样,对于NXP的i.mx6ul芯片,该定义在arch/arm/cpu/armv7/start.S文件中:
.globl reset .globl save_boot_params_ret reset: /* Allow the board to save important registers */ b save_boot_params //跳到save_boot_params save_boot_params_ret: /* * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode, * except if in HYP mode already */ mrs r0, cpsr //读取cpsr寄存器的值到r0寄存器中(cpsr:bit0~bit4保存处理器工作模式) and r1, r0, #0x1f @ mask mode bits //r0的值与0x1f相与,结果保存到r1寄存器 teq r1, #0x1a @ test for HYP mode //判断当前处理器模式是否是HYP模式 bicne r0, r0, #0x1f @ clear all mode bits //如果CPU不处于HYP模式,则清除bit0~bit4 orrne r0, r0, #0x13 @ set SVC mode //设置为SVC模式 orr r0, r0, #0xc0 @ disable FIQ and IRQ //禁止FIQ和IRQ msr cpsr,r0 //将当前r0寄存器的值回写到cpsr寄存器 /* * Setup vector: * (OMAP4 spl TEXT_BASE is not 32 byte aligned. * Continue to use ROM code vector only in OMAP4 spl) */ #if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD)) /* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */ mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register //读取SCTLR寄存器 bic r0, #CR_V @ V = 0 //设置V = 0 mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register /* Set vector address in CP15 VBAR register */ ldr r0, =_start //设置vector地址到CP15 VBAR寄存器 mcr p15, 0, r0, c12, c0, 0 @Set VBAR #endif /* the mask ROM code should have PLL and others stable */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_cp15 //跳转到cpu_init_cp15 bl cpu_init_crit //跳转到cpu_init_crit #endif bl _main //跳转到_main
在上面的代码中主要是对arm架构处理器的运行模式进行设置,对一些寄存器进行赋值操作,对于cpu_init_crit函数的实现如下:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT /************************************************************************* * * CPU_init_critical registers * * setup important registers * setup memory timing * *************************************************************************/ ENTRY(cpu_init_crit) /* * Jump to board specific initialization... * The Mask ROM will have already initialized * basic memory. Go here to bump up clock rate and handle * wake up conditions. */ b lowlevel_init @ go setup pll,mux,memory //跳到lowlevel_init,设置pll、mux和memory ENDPROC(cpu_init_crit) #endif
在cpu_init_crit函数中,跳到了lowlevel_init函数中运行,接下来,将详细分析一下lowlevel_init和_main函数。
3、lowlevel_init函数
在uboot源码中lowlevel_init的定义在arch/arm/cpu/armv7/lowlevel_init.S文件中,该定义如下所示:
ENTRY(lowlevel_init) /* * Setup a temporary stack. Global data is not available yet. */ ldr sp, =CONFIG_SYS_INIT_SP_ADDR //设置sp指针指向CONFIG_SYS_INIT_SP_ADDR(i.mx6ul内部RAM) bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ //sp指针8字节对齐处理 #ifdef CONFIG_SPL_DM mov r9, #0 #else /* * Set up global data for boards that still need it. This will be * removed soon. */ #ifdef CONFIG_SPL_BUILD ldr r9, =gdata #else sub sp, sp, #GD_SIZE //sp = sp - 248 bic sp, sp, #7 mov r9, sp //将sp指针保存到r9寄存器 #endif #endif /* * Save the old lr(passed in ip) and the current lr to stack */ push {ip, lr} //将ip和lr进行压栈 /* * Call the very early init function. This should do only the * absolute bare minimum to get started. It should not: * * - set up DRAM * - use global_data * - clear BSS * - try to start a console * * For boards with SPL this should be empty since SPL can do all of * this init in the SPL board_init_f() function which is called * immediately after this. */ bl s_init //跳转到s_init(对与im6ul啥也没干,直接返回) pop {ip, pc} //将ip和pc指针出栈 ENDPROC(lowlevel_init)
函数进来后,首先设置了sp指针的值为CONFIG_SYS_INIT_SP_ADDR,该值为一个宏定义,对于i.mx6ul芯片定义在文件include/configs/mx6ul_14x14_evk.h,如下:
#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR //0x00900000(OCRAM的基地址) #define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE //0x00020000(OCRAM的大小,容量为128KB) #define CONFIG_SYS_INIT_SP_OFFSET (CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE) //0x00020000 - 256 = 0x1FF00 #define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET) //0x00900000 + 0x1FF00 = 0x0091FF00
OCRAM的基地址和大小可以在imx6ul的数据手册中的系统内存映射表中查到:
对于GENERATED_GBL_DATA_SIZE宏的定义在include/generated/generic-asm-offsets.h文件中:
#define GENERATED_GBL_DATA_SIZE 256 /* (sizeof(struct global_data) + 15) & ~15 @ */ #define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15 @ */ #define GD_SIZE 248 /* sizeof(struct global_data) @ */ #define GD_BD 0 /* offsetof(struct global_data, bd) @ */ #define GD_MALLOC_BASE 192 /* offsetof(struct global_data, malloc_base) @ */ #define GD_RELOCADDR 48 /* offsetof(struct global_data, relocaddr) @ */ #define GD_RELOC_OFF 68 /* offsetof(struct global_data, reloc_off) @ */ #define GD_START_ADDR_SP 64 /* offsetof(struct global_data, start_addr_sp) @ */
因此,当前的sp指针指向如下所示:
继续返回到lowlevel_init.S文件中分析,sp指针减去GD_SIZE的大小,并sp指针进行8字节对齐,在上面可以知道GD_SIZE的大小为248,因此,此时的OCRAM分配如下所示:
接下来,则是将sp指针的值保存到了r9寄存器,然后跳转到s_init函数里面执行,s_init函数的定义在文件arch/arm/cpu/armv7/mx6/soc.c文件中,定义如下:
void s_init(void) { ... ... if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) || is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL)) return; ... ... }
该函数,会判断CPU的类型,如果是imx6ul的话,函数将直接返回,s_init函数返回后,回到low_level_init函数,此时,lowlevel_init函数也执行完了,继续回到cpu_init_crit函数,函数执行完成,最终返回到save_boot_params_ret,继续往下运行bl _main这句代码。
4、小结
找到了uboot的入口后,并对save_boot_params_ret函数简单分析后,总结一下其调用流程,如下所示:
save_boot_params_ret | cpu_init_crit | | | lowlevel_init | | | s_init | _main