IMX6ULL-uboot启动内核流程分析(2)

uboot版本2016.03

uboot启动流程详解

从上一节了解到,uboot启动入口点是符号_start所在的位置,_start定义在/arch/arm/lib/vector.S中,如下所示:

_start函数

#include <config.h>

.globl _start

    .section ".vectors", "ax"

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word    CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

    b    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,首先遇到的就是中断向量表(代码13~20行),随后执行指令b reset进行跳转。reset函数定义在/arch/arm/cpu/armv7/start.S中,如下所示:

reset函数

    .globl    reset
    .globl    save_boot_params_ret

reset:
    /* Allow the board to save important registers */
    b    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
    and    r1, r0, #0x1f        @ mask mode bits
    teq    r1, #0x1a        @ test for HYP mode
    bicne    r0, r0, #0x1f        @ clear all mode bits
    orrne    r0, r0, #0x13        @ set SVC mode
    orr    r0, r0, #0xc0        @ disable FIQ and IRQ
    msr    cpsr,r0

第4行就是reset函数,随后代码执行到第6行,执行指令b save_boot_params进行跳转,save_boot_params在该文件的定义如下所示:

ENTRY(save_boot_params)
    b    save_boot_params_ret        @ back to my caller
ENDPROC(save_boot_params)
    .weak    save_boot_params

从上图可以得知,实际是程序会继续跳转到 save_boot_params_ret函数,save_boot_params_ret函数代码如下:

save_boot_params_ret函数

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
    and    r1, r0, #0x1f        @ mask mode bits
    teq    r1, #0x1a        @ test for HYP mode
    bicne    r0, r0, #0x1f        @ clear all mode bits
    orrne    r0, r0, #0x13        @ set SVC mode
    orr    r0, r0, #0xc0        @ disable FIQ and IRQ
    msr    cpsr,r0

在第6行中,读取cpsr寄存器的值到r0寄存器。

第7行中,将r0寄存器的值和0x1f做与运算,并将结果保存到r1寄存器

第8行中,判断与运算结果是否等于0x1a,如果相等就将cpsr条件标志位置位。本行指令的目的就是判断cpu是否处于hyp模式。

第9行中,如果不处于hyp模式,就将r0寄存器的低5位清零。

第10行中,将r0寄存器的低5位设置为10011b。

第11行中,将r0寄存器的值与0xc0进行或运算,最后r0寄存器的值变为0xd0。

第12行中,将r0寄存器的值写入cpsr寄存器中。

总结:以上代码的目的就是,设置cpu为svc特权模式,并关闭fiq快速中断和irq中断。

继续执行以下代码:

#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
    bic    r0, #CR_V        @ V = 0
    mcr    p15, 0, r0, c1, c0, 0    @ Write CP15 SCTLR Register

    /* Set vector address in CP15 VBAR register */
    ldr    r0, =_start
    mcr    p15, 0, r0, c12, c0, 0    @Set VBAR
#endif

此时条件编译成立。

第3行,从cp15协处理器中读取SCTLR寄存器的值到r0寄存器。

SCTLR寄存器示意图

第4行,从CR_V的定义可知:

#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */

指令的作用是将r0寄存器的bit13位进行复位。bit13为是中断向量表控制为,如果为0表示向量表的基地址为0x00000000并且可以通过设置VBAR寄存器的值确定向量表偏移地址。如果为1表示向量表基地址为0xFFFF0000,并且不能软件修改偏移地址。

第5行,将r0寄存器的值写回SCTLR寄存器。

第8行,将_start的值0x87800000读入到r0寄存器。

第9行,将r0的寄存器写入到VBAR寄存器。

总结:以上代码的功能是设置中断向量表示重定位。

继续执行以下代码:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_cp15
    bl    cpu_init_crit
#endif

    bl    _main

此处没用定义CONFIG_SKIP_LOWLEVEL_INIT,因此会依次执行cpu_init_cp15和cpu_init_crit函数。

cpu_init_cp15函数

ENTRY(cpu_init_cp15)
    /*
     * Invalidate L1 I/D
     */
    mov    r0, #0            @ set up for MCR
    mcr    p15, 0, r0, c8, c7, 0    @ invalidate TLBs
    mcr    p15, 0, r0, c7, c5, 0    @ invalidate icache
    mcr    p15, 0, r0, c7, c5, 6    @ invalidate BP array
    mcr     p15, 0, r0, c7, c10, 4    @ DSB
    mcr     p15, 0, r0, c7, c5, 4    @ ISB

    /*
     * disable MMU stuff and caches
     */
    mrc    p15, 0, r0, c1, c0, 0
    bic    r0, r0, #0x00002000    @ clear bits 13 (--V-)
    bic    r0, r0, #0x00000007    @ clear bits 2:0 (-CAM)
    orr    r0, r0, #0x00000002    @ set bit 1 (--A-) Align
    orr    r0, r0, #0x00000800    @ set bit 11 (Z---) BTB
#ifdef CONFIG_SYS_ICACHE_OFF
    bic    r0, r0, #0x00001000    @ clear bit 12 (I) I-cache
#else
    orr    r0, r0, #0x00001000    @ set bit 12 (I) I-cache
#endif
    mcr    p15, 0, r0, c1, c0, 0

/***********省略************/

    mov    pc, r5            @ back to my caller
ENDPROC(cpu_init_cp15)

cpu_init_cp15代码的作用主要是关闭cache和mmu。因为cache的作用是加快cpu从内存取指令的速度,但是在上电之初,在cpu初始化完成以后,内存还没有初始化完成,如果此时堆cache进行指令读取可能会发生取指令异常。关闭mmu是因为在上电之初访问的都是实际地址,和mmu没有关系,为了防止出错所以关闭。

cpu_init_crit函数:

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
ENDPROC(cpu_init_crit)

从代码中可以发现,cpu_init_crit函数直接调用了lowlevel_init函数,lowlevel_init函数定义在/arch/arm/cpu/armv7/lowlevel_init.S中,代码如下:

lowlevel_init函数

/*
 * A lowlevel_init function that sets up the stack to call a C function to
 * perform further init.
 *
 * (C) Copyright 2010
 * Texas Instruments, <www.ti.com>
 *
 * Author :
 *    Aneesh V    <[email protected]>
 *
 * SPDX-License-Identifier:    GPL-2.0+
 */

#include <asm-offsets.h>
#include <config.h>
#include <linux/linkage.h>

ENTRY(lowlevel_init)
    /*
     * Setup a temporary stack. Global data is not available yet.
     */
    ldr    sp, =CONFIG_SYS_INIT_SP_ADDR
    bic    sp, sp, #7 /* 8-byte alignment for ABI compliance */
#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
    bic    sp, sp, #7
    mov    r9, sp
#endif
#endif
    /*
     * Save the old lr(passed in ip) and the current lr to stack
     */
    push    {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
    pop    {ip, pc}
ENDPROC(lowlevel_init)

lowlevel_init函数,第22行将sp指针指向CONFIG_SYS_INIT_SP_ADDR,其中,依次解析宏定义:

CONFIG_SYS_INIT_SP_ADDR = CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET
                        = IRAM_BASE_ADDR + CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE
                        = IRAM_BASE_ADDR + IRAM_SIZE - 256
                        = 0x00900000 + 0x00020000 - 256
                        = 0X0091FF00

因此sp指针将指向0x0091FF00处。(0X00900000~0X0091FFFF是内部ram地址)

第23行,对sp指针8字节对齐。

第34行,将sp指针减去GD_SIZE大小。

第35行,对sp指针8字节对齐。

sp指针地址图

第36行,将 sp 地址保存在 r9 寄存器中。

第42行,将 ip 和 lr 压栈

第57行,调用函数 s_init,得,又来了一个函数。

第58行,将第 36 行入栈的 ip 和 lr 进行出栈,并将 lr 赋给 pc。

总结:lowlevel_init函数完成了对sp指针的设置,r9寄存器设置,以及ip和lr寄存器入栈,调用了s_init函数。

继续执行s_init函数,定义在/arch/arm/cpu/armv7/mx6/soc.c,代码如下:

s_init函数

void s_init(void)
{
    struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
    struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
    u32 mask480;
    u32 mask528;
    u32 reg, periph1, periph2;

    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是否为以上4种型号,如果是则直接返回,因此s_init函数相当于是一个空函数,会直接返回。

s_init退出以后会返回到lowlevel_init,再返回到cpu_init_crit,最终返回到save_boot_params_ret,因此函数调用路径如下所示:

--->_start函数

--->reset函数

--->save_boot_params_ret函数

--->cpu_init_cp15函数

--->cpu_init_crit函数

--->lowlevel_init函数

--->s_init函数

s_init函数执行完毕进行返回,返回顺序s_init->cpu_init_crit->save_boot_params_ret。至此代码将执行下一阶段_main函数

猜你喜欢

转载自blog.csdn.net/qq_42174306/article/details/128800812