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#endifb 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:b save_boot_params//跳到save_boot_params
save_boot_params_ret: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寄存器
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
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, [email protected] Write CP15 SCTLR Registerldr r0,=_start //設定vector位址到CP15 VBAR寄存器
mcr p15, 0, r0, c12, c0, [email protected] VBAR#endif
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15//跳轉到cpu_init_cp15
bl cpu_init_crit //跳轉到cpu_init_crit
#endifbl _main//跳轉到_main
在上面的代碼中主要是對arm架構處理器的運作模式進行設定,對一些寄存器進行指派操作,對于cpu_init_crit函數的實作如下:
#ifndef CONFIG_SKIP_LOWLEVEL_INITENTRY(cpu_init_crit)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)ldr sp,=CONFIG_SYS_INIT_SP_ADDR //設定sp指針指向CONFIG_SYS_INIT_SP_ADDR(i.mx6ul内部RAM)
bic sp, sp, #7 //sp指針8位元組對齊處理
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
#ifdef CONFIG_SPL_BUILD
ldr r9,=gdata#elsesub sp, sp, #GD_SIZE//sp = sp - 248
bic sp, sp, #7mov r9, sp//将sp指針儲存到r9寄存器
#endif
#endif
push {ip, lr}//将ip和lr進行壓棧
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
#define GENERATED_BD_INFO_SIZE 80
#define GD_SIZE 248
#define GD_BD 0
#define GD_MALLOC_BASE 192
#define GD_RELOCADDR 48
#define GD_RELOC_OFF 68
#define GD_START_ADDR_SP 64
是以,目前的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