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