• 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
  • 相关阅读:
    滑动窗口最大值
    uber.zap性能验证
    【spring框架】AOP的Annotation实现(下)
    Spring Boot实战:Spring Boot使用AOP的正确姿势 Spring AOP术语 通知 连接点 切点 切面 引入 织入
    Git极简教程(3)branch级别的操作,合并分支 拉取最新代码git pull 包含( git fetch origin 和 git merge origin/master )
    当使用git branch r是当分支有很多的时候出现end 使用:q可以退出
    Java异常类型及处理 Throwable在异常类层次结构的顶层,在AOP中用到
    Git极简教程(2)remote级别的操作,remote(多个分支的集合体)我们叫做origin,origin/master一般是主分支。origin/dev 开发分支,origin/zhangsan 开发者张三的分支 都应该复制master,基于master进行开发迭代
    spring aop获取方法上与类上的注解
    自定义的异常类,用instanceof判断类型错误
  • 原文地址:https://www.cnblogs.com/Cqlismy/p/12000889.html
Copyright © 2020-2023  润新知