天天看點

uboot啟動流程概述_Uboot啟動流程分析(一)

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的資料手冊中的系統記憶體映射表中查到:

uboot啟動流程概述_Uboot啟動流程分析(一)

對于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指針指向如下所示:

uboot啟動流程概述_Uboot啟動流程分析(一)

繼續傳回到lowlevel_init.S檔案中分析,sp指針減去GD_SIZE的大小,并sp指針進行8位元組對齊,在上面可以知道GD_SIZE的大小為248,是以,此時的OCRAM配置設定如下所示:

uboot啟動流程概述_Uboot啟動流程分析(一)

接下來,則是将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