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
#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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM