u-boot启动流程分析-史上最全最详细

news2024/11/25 8:20:38

        嗨喽,大家好,我是程序猿老王,程序猿老王就是我。

        今天给大家全面的分析一下u-boot启动流程。整理这篇文章花费时间较长,中间很长时间未更新,希望这篇文章对大家有所帮助。

        本章主要是详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的。

        在看本章之前,个人建议先去看一下前几篇文章。对u-boot的开发环境搭建、u-boot整体移植和u-boot下网络调试有一点了解后,再来看本篇文章,这样可能比较容易看明白。

Linux系统开发环境搭建篇:ubuntu交叉编译工具链安装

Linux系统开发环境搭建 u-boot移植篇:u-boot移植:详细讲解移植u-boot.2022.10版本到imx6ull开发板

u-boot网络移植于调试篇:详细讲解u-boot之网络移植与调试

        本章主要是详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的。

一、u-boot启动详细函数调用流程

        首先给大家先看一下,u-boot启动从入口函数到启动内核的详细函数调用流程于层级关系图,对u-boot启动的整体有一个快速了解,后面回详细介绍各个函数的作用。

u-boot:启动详细的代码调用流程
u-boot.lds:(arch/arm/cpu/u-boot.lds)
    |-->_start:(arch/arm/lib/vectors.S)
        |-->reset(arch/arm/cpu/armv7/start.S)    
            |-->save_boot_params(arch/arm/cpu/armv7/start.S)/*将引导参数保存到内存中*/
                |-->save_boot_params_ret(arch/arm/cpu/armv7/start.S)
                    |-->cpu_init_cp15(arch/arm/cpu/armv7/start.S)/*初始化*/
                    |-->cpu_init_crit(arch/arm/cpu/armv7/start.S)
                        |-->lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)
                    |-->_main(arch/arm/lib/crt0.S)
                        |-->board_init_f_alloc_reserve(common/init/board_init.c)/*为u-boot的gd结构体分配空间*/
                        |-->board_init_f_init_reserve(common/init/board_init.c)    /*将gd结构体清零*/
                        |-->board_init_f(common/board_f.c)
                            |-->initcall_run_list(include/initcall.h)    /*初始化序列函数*/
                                |-->init_sequence_f[](common/board_f.c)    /* 初始化序列函数数组 */
                                    |-->board_early_init_f(board/freescale/mx6ull_toto/mx6ull_toto.c)/*初始化串口的IO配置*/
                                    |-->timer_init(arch/arm/imx-common/timer.c)    /*初始化内核定时器,为uboot提供时钟节拍*/
                                    |-->init_baud_rate(common/board_f.c)        /*初始化波特率*/
                                    |-->serial_init(drivers/serial/serial.c)    /*初始化串口通信设置*/
                                    |-->console_init_f(common/console.c)        /*初始化控制台*/
                                    |-->...
                        |-->relocate_code(arch/arm/lib/relocate.S)    /*主要完成镜像拷贝和重定位*/
                        |-->relocate_vectors(arch/arm/lib/relocate.S)/*重定位向量表*/
                        |-->board_init_r(common/board_r.c)/*板级初始化*/
                            |-->initcall_run_list(include/initcall.h)/*初始化序列函数*/
                                |-->init_sequence_r[](common/board_f.c)/*序列函数*/
                                    |-->initr_reloc(common/board_r.c)    /*设置 gd->flags,标记重定位完成*/
                                    |-->serial_initialize(drivers/serial/serial-uclass.c)/*初始化串口*/
                                        |-->serial_init(drivers/serial/serial-uclass.c)     /*初始化串口*/
                                    |-->initr_mmc(common/board_r.c)                         /*初始化emmc*/
                                        |-->mmc_initialize(drivers/mmc/mmc.c)
                                            |-->mmc_do_preinit(drivers/mmc/mmc.c)
                                                |-->mmc_start_init(drivers/mmc/mmc.c)
                                    |-->console_init_r(common/console.c)                /*初始化控制台*/
                                    |-->interrupt_init(arch/arm/lib/interrupts.c)        /*初始化中断*/
                                    |-->initr_net(common/board_r.c)                        /*初始化网络设备*/
                                        |-->eth_initialize(net/eth-uclass.c)
                                            |-->eth_common_init(net/eth_common.c)
                                                |-->phy_init(drivers/net/phy/phy.c)
                                            |-->uclass_first_device_check(drivers/core/uclass.c)
                                                |-->uclass_find_first_device(drivers/core/uclass.c)
                                                |-->device_probe(drivers/core/device.c)
                                                    |-->device_of_to_plat(drivers/core/device.c)
                                                        |-->drv->of_to_plat
                                                            |-->fecmxc_of_to_plat(drivers/net/fec_mxc.c)/*解析设备树信息*/
                                                    |-->device_get_uclass_id(drivers/core/device.c)
                                                    |-->uclass_pre_probe_device(drivers/core/uclass.c)
                                                    |-->drv->probe(dev)
                                                        /*drivers/net/fec_mxc.c*/
                                                        U_BOOT_DRIVER(fecmxc_gem) = {
                                                            .name    = "fecmxc",
                                                            .id    = UCLASS_ETH,
                                                            .of_match = fecmxc_ids,
                                                            .of_to_plat = fecmxc_of_to_plat,
                                                            .probe    = fecmxc_probe,
                                                            .remove    = fecmxc_remove,
                                                            .ops    = &fecmxc_ops,
                                                            .priv_auto    = sizeof(struct fec_priv),
                                                            .plat_auto    = sizeof(struct eth_pdata),
                                                        };
                                                        |-->fecmxc_probe(drivers/net/fec_mxc.c)/*探测和初始化*/
                                                            |-->fec_get_miibus(drivers/net/fec_mxc.c)
                                                                |-->mdio_alloc(drivers/net/fec_mxc.c)
                                                                |-->bus->read = fec_phy_read;
                                                                |-->bus->write = fec_phy_write;
                                                                |-->mdio_register(common/miiphyutil.c)
                                                                |-->fec_mii_setspeed(drivers/net/fec_mxc.c)
                                                            |-->fec_phy_init(drivers/net/fec_mxc.c)
                                                                |-->device_get_phy_addr(drivers/net/fec_mxc.c)
                                                                |-->phy_connect(drivers/net/phy/phy.c)
                                                                    |-->phy_find_by_mask(drivers/net/phy/phy.c)
                                                                        |-->bus->reset(bus)
                                                                        |-->get_phy_device_by_mask(drivers/net/phy/phy.c)
                                                                            |-->create_phy_by_mask(drivers/net/phy/phy.c)
                                                                                |-->phy_device_create(drivers/net/phy/phy.c)
                                                                                    |-->phy_probe(drivers/net/phy/phy.c)
                                                                    |-->phy_connect_dev(drivers/net/phy/phy.c)
                                                                        |-->phy_reset(drivers/net/phy/phy.c)
                                                                |-->phy_config(drivers/net/phy/phy.c)
                                                                    |-->board_phy_config(drivers/net/phy/phy.c)
                                                                        |-->phydev->drv->config(phydev)
                                                                            /*drivers/net/phy/smsc.c*/
                                                                            static struct phy_driver lan8710_driver = {
                                                                                .name = "SMSC LAN8710/LAN8720",
                                                                                .uid = 0x0007c0f0,
                                                                                .mask = 0xffff0,
                                                                                .features = PHY_BASIC_FEATURES,
                                                                                .config = &genphy_config_aneg,
                                                                                .startup = &genphy_startup,
                                                                                .shutdown = &genphy_shutdown,
                                                                            };
                                                                            |-->genphy_config_aneg(drivers/net/phy/phy.c)
                                                                                |-->phy_reset(需要手动调用)(drivers/net/phy/phy.c)
                                                                                |-->genphy_setup_forced(drivers/net/phy/phy.c)
                                                                                |-->genphy_config_advert(drivers/net/phy/phy.c)
                                                                                |-->genphy_restart_aneg(drivers/net/phy/phy.c)
                                                    |-->uclass_post_probe_device(drivers/core/uclass.c)
                                                        |-->uc_drv->post_probe(drivers/core/uclass.c)
                                                            /*net/eth-uclass.c*/
                                                            UCLASS_DRIVER(ethernet) = {
                                                                .name        = "ethernet",
                                                                .id        = UCLASS_ETH,
                                                                .post_bind    = eth_post_bind,
                                                                .pre_unbind    = eth_pre_unbind,
                                                                .post_probe    = eth_post_probe,
                                                                .pre_remove    = eth_pre_remove,
                                                                .priv_auto    = sizeof(struct eth_uclass_priv),
                                                                .per_device_auto    = sizeof(struct eth_device_priv),
                                                                .flags        = DM_UC_FLAG_SEQ_ALIAS,
                                                            };
                                                            |-->eth_post_probe(net/eth-uclass.c)
                                                                |-->eth_write_hwaddr(drivers/core/uclass.c)
                                    |-->...
                                    |-->run_main_loop(common/board_r.c)/*主循环,处理命令*/
                                        |-->main_loop(common/main.c)
                                            |-->bootdelay_process(common/autoboot.c)    /*读取环境变量bootdelay和bootcmd的内容*/
                                            |-->autoboot_command(common/autoboot.c)        /*倒计时按下执行,没有操作执行bootcmd的参数*/
                                                |-->abortboot(common/autoboot.c)
                                                    |-->printf("Hit any key to stop autoboot: %2d ", bootdelay);
                                                    /*到这里就是我们看到uboot延时3s启动内核的地方*/
                                            |-->cli_loop(common/cli.c)    /*倒计时按下space键,执行用户输入命令*/

二、程序入口

        U-Boot 源码文件众多,我们如何知道最开始的启动文件(程序入口)是哪个呢?程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口,链接脚本为arch/arm/cpu/u-boot.lds,它描述了如何生成最终的二进制文件,其中就包含程序入口。

三、链接脚本 u-boot.lds 详解

1.u-boot.lds

        u-boot.lds,文件所在位置arch/arm/cpu/u-boot.lds

/* SPDX-License-Identifier: GPL-2.0+ */
/*
 * Copyright (c) 2004-2008 Texas Instruments
 *
 * (C) Copyright 2002
 * Gary Jennejohn, DENX Software Engineering, <garyj@denx.de>
 */

#include <config.h>
#include <asm/psci.h>

/* 指定输出可执行文件: "elf 32位 小端格式 arm指令" */
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/* 指定输出可执行文件的目标架构:"arm" */
OUTPUT_ARCH(arm)
/* 指定输出可执行文件的起始地址为:"_start" */
ENTRY(_start)
SECTIONS
{
#ifndef CONFIG_CMDLINE
    /DISCARD/ : { *(__u_boot_list_2_cmd_*) }
#endif
#if defined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)
    /*
     * If CONFIG_ARMV7_SECURE_BASE is true, secure code will not
     * bundle with u-boot, and code offsets are fixed. Secure zone
     * only needs to be copied from the loading address to
     * CONFIG_ARMV7_SECURE_BASE, which is the linking and running
     * address for secure code.
     *
     * If CONFIG_ARMV7_SECURE_BASE is undefined, the secure zone will
     * be included in u-boot address space, and some absolute address
     * were used in secure code. The absolute addresses of the secure
     * code also needs to be relocated along with the accompanying u-boot
     * code.
     *
     * So DISCARD is only for CONFIG_ARMV7_SECURE_BASE.
     */
    /DISCARD/ : { *(.rel._secure*) }
#endif
    /* 
     * 指定可执行文件(image)的全局入口地址,通常都放在ROM(flash)0x0位置
     * 设置 0 的原因是 arm 内核的处理器,上电后默认是从 0x00000000 处启动
     */
    . = 0x00000000;

    . = ALIGN(4);                     ``````````/* 中断向量表 */
    .text :
    {
        *(.__image_copy_start)         /* u-boot 的设计中需要将 u-boot 的镜像拷贝到 ram(sdram,ddr....)中执行,这里表示复制的开始地址 */
        *(.vectors)                    /* 中断向量表 */
        CPUDIR/start.o (.text*)        /* CPUDIR/start.o 中的所有.text 段 */
    }

    /* This needs to come before *(.text*) */
    .__efi_runtime_start : {
        *(.__efi_runtime_start)
    }

    .efi_runtime : {
        *(.text.efi_runtime*)
        *(.rodata.efi_runtime*)
        *(.data.efi_runtime*)
    }

    .__efi_runtime_stop : {
        *(.__efi_runtime_stop)
    }

    .text_rest :
    {
        *(.text*)
    }

#ifdef CONFIG_ARMV7_NONSEC

    /* Align the secure section only if we're going to use it in situ */
    .__secure_start
#ifndef CONFIG_ARMV7_SECURE_BASE
        ALIGN(CONSTANT(COMMONPAGESIZE))
#endif
    : {
        KEEP(*(.__secure_start))
    }

#ifndef CONFIG_ARMV7_SECURE_BASE
#define CONFIG_ARMV7_SECURE_BASE
#define __ARMV7_PSCI_STACK_IN_RAM
#endif

    .secure_text CONFIG_ARMV7_SECURE_BASE :
        AT(ADDR(.__secure_start) + SIZEOF(.__secure_start))
    {
        *(._secure.text)
    }

    .secure_data : AT(LOADADDR(.secure_text) + SIZEOF(.secure_text))
    {
        *(._secure.data)
    }

#ifdef CONFIG_ARMV7_PSCI
    .secure_stack ALIGN(ADDR(.secure_data) + SIZEOF(.secure_data),
                CONSTANT(COMMONPAGESIZE)) (NOLOAD) :
#ifdef __ARMV7_PSCI_STACK_IN_RAM
        AT(ADDR(.secure_stack))
#else
        AT(LOADADDR(.secure_data) + SIZEOF(.secure_data))
#endif
    {
        KEEP(*(.__secure_stack_start))

        /* Skip addreses for stack */
        . = . + CONFIG_ARMV7_PSCI_NR_CPUS * ARM_PSCI_STACK_SIZE;

        /* Align end of stack section to page boundary */
        . = ALIGN(CONSTANT(COMMONPAGESIZE));

        KEEP(*(.__secure_stack_end))

#ifdef CONFIG_ARMV7_SECURE_MAX_SIZE
        /*
         * We are not checking (__secure_end - __secure_start) here,
         * as these are the load addresses, and do not include the
         * stack section. Instead, use the end of the stack section
         * and the start of the text section.
         */
        ASSERT((. - ADDR(.secure_text)) <= CONFIG_ARMV7_SECURE_MAX_SIZE,
               "Error: secure section exceeds secure memory size");
#endif
    }

#ifndef __ARMV7_PSCI_STACK_IN_RAM
    /* Reset VMA but don't allocate space if we have secure SRAM */
    . = LOADADDR(.secure_stack);
#endif

#endif

    .__secure_end : AT(ADDR(.__secure_end)) {
        *(.__secure_end)
        LONG(0x1d1071c);    /* Must output something to reset LMA */
    }
#endif
    /* 
     * .rodata 段,确保是以4字节对齐 
     */
    . = ALIGN(4);
    .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

    /* 
     * data段,确保是以4字节对齐
     */
    . = ALIGN(4);
    .data : {
        *(.data*)
    }

    . = ALIGN(4);

    . = .;

    /* 
     * u_boot_list 段,确保是以 4 字节对齐 
     * 这里存放的都是 u_boot_list 中的函数
     */
    . = ALIGN(4);
    __u_boot_list : {
        KEEP(*(SORT(__u_boot_list*)));
    }

    . = ALIGN(4);

    .efi_runtime_rel_start :
    {
        *(.__efi_runtime_rel_start)
    }

    .efi_runtime_rel : {
        *(.rel*.efi_runtime)
        *(.rel*.efi_runtime.*)
    }

    .efi_runtime_rel_stop :
    {
        *(.__efi_runtime_rel_stop)
    }

    /* 
     * __image_copy_end 也是个符号表示一个结束地址,确保是以4字节对齐 
     */
    . = ALIGN(4);

    .image_copy_end :        /* u-boot 的设计中需要将 u-boot 的镜像拷贝到ram(sdram,ddr....)中执行,这里表示复制的结束地址 */
    {
        *(.__image_copy_end)
    }

    .rel_dyn_start :        /*  .rel.dyn 段起始地址 */
    {
        *(.__rel_dyn_start)
    }

    .rel.dyn : {
        *(.rel*)
    }

    .rel_dyn_end :            /*  .rel.dyn 段结束地址 */
    {
        *(.__rel_dyn_end)
    }

    .end :
    {
        *(.__end)
    }

    _image_binary_end = .;    /* bin文件结束地址 */

    /*
     * Deprecated: this MMU section is used by pxa at present but
     * should not be used by new boards/CPUs.
     */
    . = ALIGN(4096);
    .mmutable : {
        *(.mmutable)
    }

/*
 * Compiler-generated __bss_start and __bss_end, see arch/arm/lib/bss.c
 * __bss_base and __bss_limit are for linker only (overlay ordering)
 */

    .bss_start __rel_dyn_start (OVERLAY) : {    /* .bss段起始地址 */
        KEEP(*(.__bss_start));
        __bss_base = .;
    }

    .bss __bss_base (OVERLAY) : {
        *(.bss*)
         . = ALIGN(4);
         __bss_limit = .;
    }

    .bss_end __bss_limit (OVERLAY) : {            /* .bss段结束地址 */
        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.*) }
}

通过上面的分析可以看出: 由于在链接脚本中规定了文件start.o(对应于start.S)作为整个uboot的起始点,因此启动uboot时会执行首先执行start.S。 一般来说,内存空间可分为代码段、数据段、全局变量段、未初始化变量区、栈区、堆区等.其中,栈区由指针SP决定,堆区实质上是由C代码实现的,其它段则由编译器决定.从上面的分析可以看出,从0x00000000地址开始,编译器首先将代码段放在最开始的位置,然后是数据段,然后是bss段(未初始化变量区)。

2.u-boot.map

        u-boot.map 是uboot的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,下面打开 u-boot.map,查看各个段的起始地址和结束分别是多少;

内存配置

名称           来源             长度             属性
*default*        0x00000000         0xffffffff

链结器命令稿和内存映射

段 .text 的地址设置为 0x87800000
                0x00000000                . = 0x0
                0x00000000                . = ALIGN (0x4)

.text           0x87800000      0x3a8
 *(.__image_copy_start)
 .__image_copy_start
                0x87800000        0x0 arch/arm/lib/sections.o
                0x87800000                __image_copy_start
 *(.vectors)
 .vectors       0x87800000      0x2e8 arch/arm/lib/vectors.o
                0x87800000                _start
                0x87800020                _undefined_instruction
                0x87800024                _software_interrupt
                0x87800028                _prefetch_abort
                0x8780002c                _data_abort
                0x87800030                _not_used
                0x87800034                _irq
                0x87800038                _fiq
                0x87800040                IRQ_STACK_START_IN
 arch/arm/cpu/armv7/start.o(.text*)
 .text          0x878002e8       0xc0 arch/arm/cpu/armv7/start.o
                0x878002e8                reset
                0x878002ec                save_boot_params_ret
                0x87800328                c_runtime_cpu_setup
                0x87800338                save_boot_params
                0x8780033c                cpu_init_cp15
                0x8780039c                cpu_init_crit
...

        从u-boot.map映射文件种,可以知道__image_copy_start为0X87800000,而.text的起始地址也是0X87800000,.vectors段的起始地址也是0X87800000,可以得出各个段的地址关系表,如下;

变量名地址描述
__image_copy_start0x87800000u-boot拷贝的起始地址
__image_copy_end0x87850ff0u-boot拷贝的结束地址
.vectors0x87800000中断向量表的起始地址
.text0x878002e8.text段的起始地址
__rel_dyn_start0x87850ff0.rel_dyn段的起始地址
__rel_dyn_end0x8785cf30.rel_dyn段的结束地址
_image_binary_end0x8785cf30镜像结束地址
__bss_start0x87850ff0.bss段的起始地址
__bss_end0x878585c0.bss段的结束地址

        注:表中的变量除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化。修改uboot 代码、配置等都会影响到这些值。所以,一切以实际值为准!

四、_start函数详解

        从链接文件(u-boot.lds) 中知道了程序入口是 _start,_start 在文件 arch/arm/lib/vectors.S 中有定义,具体代码如下;

/*
 *************************************************************************
 *
 * Symbol _start is referenced elsewhere, so make it global
 *
 *************************************************************************
 */

.globl _start

/*
 *************************************************************************
 *
 * Vectors have their own section so linker script can map them easily
 *
 *************************************************************************
 */

    .section ".vectors", "ax"

#if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
/*
 * Various SoCs need something special and SoC-specific up front in
 * order to boot, allow them to set that in their boot0.h file and then
 * use it here.
 *
 * To allow a boot0 hook to insert a 'special' sequence after the vector
 * table (e.g. for the socfpga), the presence of a boot0 hook supresses
 * the below vector table and assumes that the vector table is filled in
 * by the boot0 hook.  The requirements for a boot0 hook thus are:
 *   (1) defines '_start:' as appropriate
 *   (2) inserts the vector table using ARM_VECTORS as appropriate
 */
#include <asm/arch/boot0.h>
#else

/*
 *************************************************************************
 *
 * Exception vectors as described in ARM reference manuals
 *
 * Uses indirect branch to allow reaching handlers anywhere in memory.
 *
 *************************************************************************
 */

_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word   CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    ARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */

#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
/*
 *************************************************************************
 *
 * Indirect vectors table
 *
 * Symbols referenced here must be defined somewhere else
 *
 *************************************************************************
 */

    .globl  _reset
    .globl  _undefined_instruction     /* 未定义指令异常 */
    .globl  _software_interrupt        /* 软中断异常 */
    .globl  _prefetch_abort            /* 预取异常 */
    .globl  _data_abort                /* 数据异常 */
    .globl  _not_used                  /* 未使用 */
    .globl  _irq                       /* 外部中断请求IRQ */
    .globl  _fiq                       /* 快束中断请求FIQ */
    ...

        从u-boot.map映射文件可以得出.vectors段的最开始就是_start,而从_start定义我们可以知道首先是跳转到reset函数,再设置中断向量表。

五、reset函数详解

1.reset函数讲解

        从程序入口_start定义中得出,_start中首先是跳转到reset函数,reset函数在文件arch/arm/cpu/armv7/start.S中有定义,具体代码如下;

/*************************************************************************
 *
 * Startup Code (reset vector)
 *
 * Do important init only if we don't start from memory!
 * Setup memory and board specific bits prior to relocation.
 * Relocate armboot to ram. Setup stack.
 *
 *************************************************************************/

    .globl  reset
    .globl  save_boot_params_ret
    .type   save_boot_params_ret,%function
#ifdef CONFIG_ARMV7_LPAE
    .global switch_to_hypervisor_ret
#endif

reset:
    /* Allow the board to save important registers */
    b   save_boot_params
save_boot_params_ret:
    ...

        reset函数只有一句跳转语句,直接跳转到了save_boot_params函数,而save_boot_params函数同样定义在start.S里面,定义如下:

/*************************************************************************
 *
 * void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)
 *  __attribute__((weak));
 *
 * Stack pointer is not yet initialized at this moment
 * Don't save anything to stack even if compiled with -O0
 *
 *************************************************************************/
ENTRY(save_boot_params)
    b   save_boot_params_ret        @ back to my caller
####2.save_boot_params_ret函数讲解
同样save_boot_params函数也是只有一句跳转语句,跳转到save_boot_params_ret函数save_boot_params_ret 函数代码如下:
save_boot_params_ret:
#ifdef CONFIG_POSITION_INDEPENDENT
    /*
     * Fix .rela.dyn relocations. This allows U-Boot to loaded to and
     * executed at a different address than it was linked at.
     */
pie_fixup:
    /* 获取标号reset的运行地址到r0 */
    adr r0, reset   /* r0 <- Runtime value of reset label */
    /* 获取标号reset的链接地址到r0 */
    ldr r1, =reset  /* r1 <- Linked value of reset label */
    /* 计算运行地址和link地址的偏移 */
    subs    r4, r0, r1  /* r4 <- Runtime-vs-link offset */
    /* 如果为0,说明link地址和运行地址一致,不需要重定位直接退出 */
    beq pie_fixup_done

    /* 
     * 下面几行代码的作用是计算运行时rel.dyn段在内存中实际地址,只有获取这个段的
     * 真实的起使地址才能依据其中的信息进行重定位。
     */
    //获取pie_fixup标号的运行地址
    adr r0, pie_fixup
    //_rel_dyn_start_ofs链接时rel.dyn段相对pie_fixup标号的偏移
    ldr r1, _rel_dyn_start_ofs
    add r2, r0, r1  /* r2 <- Runtime &__rel_dyn_start */
    //计算rel.dyn运行时起始地址
    ldr r1, _rel_dyn_end_ofs
    //计算rel.dyn运行结束地址
    add r3, r0, r1  /* r3 <- Runtime &__rel_dyn_end */

pie_fix_loop:
    //获取rel.dyn段地址中的内容
    ldr r0, [r2]    /* r0 <- Link location */
    //获取rel.dyn段地址接下来4个字节中的内容
    ldr r1, [r2, #4]    /* r1 <- fixup */
    //如果r1等于23则执行重定位
    cmp r1, #23     /* relative fixup? */
    bne pie_skip_reloc

    /* relative fix: increase location by offset */
    add r0, r4
    ldr r1, [r0]
    add r1, r4
    str r1, [r0]
    str r0, [r2]
    add r2, #8
pie_skip_reloc:
    //判断是否所有表项都修改完成,没完成则循环操作
    cmp r2, r3
    blo pie_fix_loop
pie_fixup_done:
#endif

#ifdef CONFIG_ARMV7_LPAE
/*
 * check for Hypervisor support
 */
    mrc p15, 0, r0, c0, c1, 1       @ read ID_PFR1
    and r0, r0, #CPUID_ARM_VIRT_MASK    @ mask virtualization bits
    cmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)
    beq switch_to_hypervisor
switch_to_hypervisor_ret:
#endif
    /*
     * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
     * except if in HYP mode already
     */
    /* 将程序状态寄存器读取到通用寄存器R0 */
    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
    /* 设置SVC模式,即超级管理员权限 */
    orrne   r0, r0, #0x13       @ set SVC mode
    /* 失能中断FIQ和IRQ */
    orr r0, r0, #0xc0       @ disable FIQ and IRQ
    msr cpsr,r0

#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
/*
 * Setup vector:
 */
    /* 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

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

    /* the mask ROM code should have PLL and others stable */
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7A
    bl  cpu_init_cp15
#endif
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)
    bl  cpu_init_crit
#endif
#endif

    bl  _main

save_boot_params_ret函数主要的操作如下:

  • 1.如果定义宏CONFIG_POSITION_INDEPENDENT,则进行修正重定位的问题(pie_fixup、pie_fix_loop、pie_skip_reloc);

  • 2.如果定义宏CONFIG_ARMV7_LPAE,LPAE(Large Physical Address Extensions)是ARMv7系列的一种地址扩展技术,可以让32位的ARM最大能支持到1TB的内存空间,由于嵌入式ARM需求的内存空间一般不大,所以一般不使用LPAE技术;

  • 3.设置CPU为SVC32模式,除非已经处于HYP模式,同时禁止中断(FIQ和IRQ);

  • 4.设置中断向量表地址为_start函数的地址,在map文件中可以看到,为0x87800000;

  • 5.进行CPU初始化,调用函数cpu_init_cp15和cpu_init_crit分别初始化CP15和CRIT;

  • 6.最后跳转到_main函数。

3.cpu_init_cp15函数讲解

        cpu_init_cp15函数,在文件arch/arm/cpu/armv7/start.S中定义,具体代码如下;

/*************************************************************************
 *
 * cpu_init_cp15
 *
 * Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless
 * CONFIG_SYS_ICACHE_OFF is defined.
 *
 *************************************************************************/
ENTRY(cpu_init_cp15)

#if CONFIG_IS_ENABLED(ARMV7_SET_CORTEX_SMPEN)
    /*
     * The Arm Cortex-A7 TRM says this bit must be enabled before
     * "any cache or TLB maintenance operations are performed".
     */
    mrc p15, 0, r0, c1, c0, 1   @ read auxilary control register
    orr r0, r0, #1 << 6     @ set SMP bit to enable coherency
    mcr p15, 0, r0, c1, c0, 1   @ write auxilary control register
#endif

    /*
     * 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
    ...

cpu_init_cp15函数主要的操作如下:

  • 1.失效 L1 I/D Cache;

  • 2.禁用MMU和缓存。

4.cpu_init_crit函数讲解

        cpu_init_crit在文件arch/arm/cpu/armv7/start.S中定义,具体代码如下;

/*************************************************************************
 *
 * 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
ENDPROC(cpu_init_crit)
#endif

        可以看到函数cpu_init_crit内部又只是一句跳转语句,调用了函数lowlevel_init,接下来就是详细的分析一下lowlevel_init和_main这两个函数。

六、lowlevel_init函数详解

        lowlevel_init函数,在文件arch/arm/cpu/armv7/lowlevel_init.S中有定义,具体代码如下;

WEAK(lowlevel_init)
    /*
     * Setup a temporary stack. Global data is not available yet.
     */
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr sp, =CONFIG_SPL_STACK
#else
    ldr sp, =SYS_INIT_SP_ADDR
#endif
    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函数主要的操作如下:

  • 1.设置SP指针为CONFIG_SYS_INIT_SP_ADDR

  • 2.对sp指针做8字节对齐处理

  • 3.SP减去#GD_SIZE = 248,GD_SIZE同样在generic-asm-offsets.h 中定了

  • 4.对 sp 指针做8字节对齐处理

  • 5.将SP保存到R9,ip和lr入栈,程序跳转到s_init(对于I.MX6ULL来说,s_init 就是个空函数)

  • 6.函数一路返回,直到_main,s_init函数-->函数lowlevel_ini-->cpu_init_crit-->save_boot_params_ret-->_main。

七、_main函数详解

        _main函数在文件 arch/arm/lib/crt0.S中有定义 _main函数执行可以大致分为如下4个部分:

  • 设置初始化C运行环境并调用board_init_f函数

  • 设置新的sp指针和gd指针,设置中间环境位,调用代码重定位

  • 重定位向量表

  • 设置最后的运行环境并调用board_init_r函数

1.设置初始化C运行环境并调用board_init_f函数

代码部分,具体如下;

/*
 * entry point of crt0 sequence
 */

ENTRY(_main)

/* Call arch_very_early_init before initializing C runtime environment. */
#if CONFIG_IS_ENABLED(ARCH_VERY_EARLY_INIT)
    bl  arch_very_early_init
#endif

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)
    ldr r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
    ldr r0, =(CONFIG_SPL_STACK)
#else
    ldr r0, =(SYS_INIT_SP_ADDR)
#endif
    bic r0, r0, #7  /* 8-byte alignment for ABI compliance */
    mov sp, r0
    bl  board_init_f_alloc_reserve
    mov sp, r0
    /* set up gd here, outside any C code */
    mov r9, r0
    bl  board_init_f_init_reserve

#if defined(CONFIG_DEBUG_UART) && CONFIG_IS_ENABLED(SERIAL)
    bl  debug_uart_init
#endif

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
    CLEAR_BSS
#endif

    mov r0, #0
    bl  board_init_f
  • 1.设置sp指针为 CONFIG_SYS_INIT_SP_ADDR;

  • 2.对sp指针做8字节对齐处理;

  • 3.读取sp到寄存器r0里面;

  • 4.调用函数board_init_f_alloc_reserve;

  • 5.调用函数board_init_f_init_reserve;

  • 6.调用函数board_init_f。

1.board_init_f_alloc_reserve函数

        board_init_f_alloc_reserve函数,在common/init/board_init.c文件中定义,如下;

ulong board_init_f_alloc_reserve(ulong top)
{
    /* Reserve early malloc arena */
#ifndef CONFIG_MALLOC_F_ADDR
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
    top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
#endif
    /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
    top = rounddown(top-sizeof(struct global_data), 16);

    return top;
}

board_init_f_alloc_reserve函数的作用:

        是根据传入参数是栈顶地址,计算出预留空间的底部,并将其返回。 主要是留出早期的 malloc 内存区域和gd内存区域。如果宏CONFIG_MALLOC_F_ADDR没有被定义,则为malloc预留部分内存空间,大小为CONFIG_SYS_MALLOC_F_LEN;其次为GD变量(global_data结构体类型)预留空间,并且对齐到16个字节的倍数。

2.board_init_f_init_reserve函数

        board_init_f_init_reserve函数,在common/init/board_init.c文件中定义,如下;

void board_init_f_init_reserve(ulong base)
{
    struct global_data *gd_ptr;

    /*
     * clear GD entirely and set it up.
     * Use gd_ptr, as gd may not be properly set yet.
     */

    gd_ptr = (struct global_data *)base;
    /* zero the area */
    memset(gd_ptr, '\0', sizeof(*gd));
    /* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)
    arch_setup_gd(gd_ptr);
#endif

    if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
        board_init_f_init_stack_protection_addr(base);

    /* next alloc will be higher by one GD plus 16-byte alignment */
    base += roundup(sizeof(struct global_data), 16);

    /*
     * record early malloc arena start.
     * Use gd as it is now properly set for all architectures.
     */

#if CONFIG_VAL(SYS_MALLOC_F_LEN)
    /* go down one 'early malloc arena' */
    gd->malloc_base = base;
#endif

    if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
        board_init_f_init_stack_protection();
}

board_init_f_init_reserve函数的作用:

        是初始化gd,其实就是清零处理;设置了gd->malloc_base为gd基地址+gd 大小,并做16字节对齐处理。

2.设置新的sp指针和gd指针,调用重定位代码,调用代码重定位

代码部分,具体如下;

#if ! defined(CONFIG_SPL_BUILD)

/*
 * Set up intermediate environment (new sp and gd) and call
 * relocate_code(addr_moni). Trick here is that we'll return
 * 'here' but relocated.
 */

    ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
    bic r0, r0, #7  /* 8-byte alignment for ABI compliance */
    mov sp, r0
    ldr r9, [r9, #GD_NEW_GD]        /* r9 <- gd->new_gd */

    adr lr, here
#if defined(CONFIG_POSITION_INDEPENDENT)
    adr r0, _main
    ldr r1, _start_ofs
    add r0, r1
    ldr r1, =CONFIG_SYS_TEXT_BASE
    sub r1, r0
    add lr, r1
#endif
    ldr r0, [r9, #GD_RELOC_OFF]     /* r0 = gd->reloc_off */
    add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
    orr lr, #1              /* As required by Thumb-only */
#endif
    ldr r0, [r9, #GD_RELOCADDR]     /* r0 = gd->relocaddr */
    b   relocate_code
  • 1.设置新的栈顶指针为sp = gd->start_addr_sp;

  • 2.设置新的gd指针为r9 <- gd->new_gd;

  • 3.设置新r0指针为r0 = gd->reloc_off;

  • 4.设置r0寄存器的值为gd->relocaddr,跳转到代码重定位relocate_code。

3.重定位向量表

代码部分,具体如下;

here:
/*
 * now relocate vectors
 */

    bl  relocate_vectors

        代码重定位后返回到here标号处,调用relocate_vectors函数,对中断向量表做重定位。

4.设置最后的运行环境并调用board_init_r函数

代码部分,具体如下;

/* Set up final (full) environment */

    bl  c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)

#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
    CLEAR_BSS
#endif

# ifdef CONFIG_SPL_BUILD
    /* Use a DRAM stack for the rest of SPL, if requested */
    bl  spl_relocate_stack_gd
    cmp r0, #0
    movne   sp, r0
    movne   r9, r0
# endif

#if ! defined(CONFIG_SPL_BUILD)
    bl coloured_LED_init
    bl red_led_on
#endif
    /* call board_init_r(gd_t *id, ulong dest_addr) */
    mov     r0, r9                  /* gd_t */
    ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
    /* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
    ldr lr, =board_init_r   /* this is auto-relocated! */
    bx  lr
#else
    ldr pc, =board_init_r   /* this is auto-relocated! */
#endif
    /* we should not return here. */
#endif

ENDPROC(_main)

board_init_r函数主要工作:

  • 1.调用函数c_runtime_cpu_setup,失效I-cache;

  • 2.清除BSS段;

  • 3.设置函数board_init_r的两个参数;

  • 4.调用函数board_init_r。

八、board_init_f 函数详解

        board_init_f函数,在common/board_f.c文件定义,具体代码如下;

void board_init_f(ulong boot_flags)
{
    gd->flags = boot_flags;
    gd->have_console = 0;

    if (initcall_run_list(init_sequence_f))
        hang();

#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
        !defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \
        !defined(CONFIG_ARC)
    /* NOTREACHED - jump_to_copy() does not return */
    hang();
#endif
}

board_init_f函数主要有两个工作:

  • 1.初始化gd的各个成员变量

  • 2.调用函数initcall_run_list,初始化序列init_sequence_f里面的一系列函数,来初始化一系列外设,比如串口、定时器,或者打印一些消息等。

        init_sequence_f数组,在common/board_f.c文件中定义,如下,初始化函数表省略其中部分代码;

static const init_fnc_t init_sequence_f[] = {
    setup_mon_len,
    fdtdec_setup,
    trace_early_init,
    initf_malloc,
    log_init,
    initf_bootstage,    /* uses its own timer, so does not need DM */
    event_init,
    bloblist_init,
    setup_spl_handoff,
    console_record_init,
    arch_fsp_init,
    arch_cpu_init,      /* basic arch cpu dependent setup */
    mach_cpu_init,      /* SoC/machine dependent CPU setup */
    initf_dm,
    board_early_init_f,
    get_clocks,     /* get CPU and bus clocks (etc.) */
    timer_init,     /* initialize timer */
    board_postclk_init,
    env_init,       /* initialize environment */
    init_baud_rate,     /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,     /* stage 1 init of console */
    display_options,    /* say that we are here */
    display_text_info,  /* show debugging info if required */
    checkcpu,
    print_resetinfo,
    print_cpuinfo,      /* display cpu info (and speed) */
    embedded_dtb_select,
    show_board_info,
    INIT_FUNC_WATCHDOG_INIT
    misc_init_f,
    INIT_FUNC_WATCHDOG_RESET
    init_func_i2c,
    init_func_vid,
    announce_dram_init,
    dram_init,      /* configure available RAM banks */
    post_init_f,
    INIT_FUNC_WATCHDOG_RESET
    testdram,
    INIT_FUNC_WATCHDOG_RESET
    init_post,
    INIT_FUNC_WATCHDOG_RESET
    setup_dest_addr,
    fix_fdt,
    reserve_pram,
    ...
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \
        !CONFIG_IS_ENABLED(X86_64)
    jump_to_copy,
#endif
    NULL,
};

其中比较重要的一些初始化函数如下:

  • 1.setup_mon_len函数:设置gd的mon_len成员变量,也就是整个代码的长度;

  • 2.initf_malloc函数:设置gd中和malloc有关的成员变量;

  • 3.board_early_init_f函数:用来初始化串口的IO配置,在board/freescale/mx6ull_toto/mx6ull_toto.c文件中定义;

  • 4.timer_init函数:初始化内核定时器,为uboot提供时钟节拍,在arch/arm/imx-common/timer.c文件中定义;

  • 5.get_clocks函数:获取了SD卡外设的时钟(sdhc_clk),在arch/arm/imx-common/speed.c文件中定义;

  • 6.init_baud_rate函数:初始化波特率,在common/board_f.c文件中定义;

  • 7.serial_init函数:初始化串口通信设置,在drivers/serial/serial.c文件中定义;

  • 8.console_init_f函数:初始化控制台,在common/console.c文件中定义:

  • 9.display_options函数:打印uboot版本信息和编译信息,在lib/display_options.c文件中定义;

  • 10.print_cpuinfo函数:用来显示CPU信息和主频,在arch/arm/imx-common/cpu.c文件中定义;

  • 11.show_board_info函数:打印开发板信息,在common/board_info.c文件中定义;

  • 12.init_func_i2c函数:用于初始化I2C;

  • 13.announce_dram_init函数:此函数很简单,就是输出字符串“DRAM:”;

  • 14.dram_init函数:并非真正的初始化DDR,只是设置gd->ram_size的值。

九、relocate_code函数详解

        relocate_code函数,在arch/arm/lib/relocate.S文件定义,具体代码如下;

/*
 * void relocate_code(addr_moni)
 *
 * This function relocates the monitor code.
 *
 * NOTE:
 * To prevent the code below from containing references with an R_ARM_ABS32
 * relocation record type, we never refer to linker-defined symbols directly.
 * Instead, we declare literals which contain their relative location with
 * respect to relocate_code, and at run time, add relocate_code back to them.
 */

ENTRY(relocate_code)
relocate_base:
    adr r3, relocate_base
    ldr r1, _image_copy_start_ofs
    add r1, r3          /* r1 <- Run &__image_copy_start */
    subs    r4, r0, r1      /* r4 <- Run to copy offset      */
    beq relocate_done       /* skip relocation               */
    ldr r1, _image_copy_start_ofs
    add r1, r3          /* r1 <- Run &__image_copy_start */
    ldr r2, _image_copy_end_ofs
    add r2, r3          /* r2 <- Run &__image_copy_end   */
copy_loop:
    ldmia   r1!, {r10-r11}      /* copy from source address [r1] */
    stmia   r0!, {r10-r11}      /* copy to   target address [r0] */
    cmp r1, r2          /* until source end address [r2] */
    blo copy_loop

    /*
     * fix .rel.dyn relocations
     */
    ldr r1, _rel_dyn_start_ofs
    add r2, r1, r3      /* r2 <- Run &__rel_dyn_start */
    ldr r1, _rel_dyn_end_ofs
    add r3, r1, r3      /* r3 <- Run &__rel_dyn_end */
fixloop:
    ldmia   r2!, {r0-r1}        /* (r0,r1) <- (SRC location,fixup) */
    and r1, r1, #0xff
    cmp r1, #R_ARM_RELATIVE
    bne fixnext

    /* relative fix: increase location by offset */
    add r0, r0, r4
    ldr r1, [r0]
    add r1, r1, r4
    str r1, [r0]
fixnext:
    cmp r2, r3
    blo fixloop

relocate_done:

#ifdef __XSCALE__
    /*
     * On xscale, icache must be invalidated and write buffers drained,
     * even with cache disabled - 4.2.7 of xscale core developer's manual
     */
    mcr p15, 0, r0, c7, c7, 0   /* invalidate icache */
    mcr p15, 0, r0, c7, c10, 4  /* drain write buffer */
#endif

    /* ARMv4- don't know bx lr but the assembler fails to see that */

#ifdef __ARM_ARCH_4__
    mov pc, lr
#else
    bx  lr
#endif

ENDPROC(relocate_code)

relocate_code此函数主要作用:

        完成镜像拷贝和重定位,镜像地址从__image_copy_start开始,到__image_copy_end结束,拷贝的目标地址由参数传进来,也就是r0寄存器的值。重定位的原理此处不展开,需要了解的自行去学习。

十、relocate_vectors函数详解

        relocate_vectors函数,在arch/arm/lib/relocate.S文件定义,具体代码如下;

ENTRY(relocate_vectors)

#ifdef CONFIG_CPU_V7M
    /*
     * On ARMv7-M we only have to write the new vector address
     * to VTOR register.
     */
    ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
    ldr r1, =V7M_SCB_BASE
    str r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR
    /*
     * If the ARM processor has the security extensions,
     * use VBAR to relocate the exception vectors.
     */
    ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
    mcr     p15, 0, r0, c12, c0, 0  /* Set VBAR */
#else
    /*
     * Copy the relocated exception vectors to the
     * correct address
     * CP15 c1 V bit gives us the location of the vectors:
     * 0x00000000 or 0xFFFF0000.
     */
    ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
    mrc p15, 0, r2, c1, c0, 0   /* V bit (bit[13]) in CP15 c1 */
    ands    r2, r2, #(1 << 13)
    ldreq   r1, =0x00000000     /* If V=0 */
    ldrne   r1, =0xFFFF0000     /* If V=1 */
    ldmia   r0!, {r2-r8,r10}
    stmia   r1!, {r2-r8,r10}
    ldmia   r0!, {r2-r8,r10}
    stmia   r1!, {r2-r8,r10}
#endif
#endif
    bx  lr

ENDPROC(relocate_vectors)

        relocate_vectors函数用于重定位向量表,只有一步操作比较重要,就是将uboot重定位完之后的地址,装载到CP15的VBAR寄存器中设置向量表偏移,该寄存器自行去学习。

十一、board_init_r函数详解

        board_init_r函数,在common/board_r.c文件定义,具体代码如下;

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
    /*
     * Set up the new global data pointer. So far only x86 does this
     * here.
     * TODO(sjg@chromium.org): Consider doing this for all archs, or
     * dropping the new_gd parameter.
     */
    if (CONFIG_IS_ENABLED(X86_64) && !IS_ENABLED(CONFIG_EFI_APP))
        arch_setup_gd(new_gd);

#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)
    gd = new_gd;
#endif
    gd->flags &= ~GD_FLG_LOG_READY;

    if (IS_ENABLED(CONFIG_NEEDS_MANUAL_RELOC)) {
        for (int i = 0; i < ARRAY_SIZE(init_sequence_r); i++)
            MANUAL_RELOC(init_sequence_r[i]);
    }

    if (initcall_run_list(init_sequence_r))
        hang();

    /* NOTREACHED - run_main_loop() does not return */
    hang();
}

        board_init_f函数中,会初始化一些外设和gd的成员变量,但并没有初始化所有的外设,还需要一些后续工作,这些工作就是由board_init_r函数完成的,调用initcall_run_list函数执行初始化序列init_sequence_r,init_sequence_r是一个函数表,也定义在该文件中,部分代码如下;

static init_fnc_t init_sequence_r[] = {
    initr_trace,
    initr_reloc,
    event_init,
    initr_caches,
    initr_reloc_global_data,
    initr_barrier,
    initr_malloc,
    log_init,
    initr_bootstage,
    console_record_init,
    initr_of_live,
    board_init, /* Setup chipselects */
    stdio_init_tables,
    serial_initialize,
    initr_announce,
    INIT_FUNC_WATCHDOG_RESET
    INIT_FUNC_WATCHDOG_RESET
    power_init_board,
    initr_flash,
    initr_nand,
    initr_mmc,
    initr_env,
    INIT_FUNC_WATCHDOG_RESET
    cpu_secondary_init_r,
    INIT_FUNC_WATCHDOG_RESET
    stdio_add_devices,
    jumptable_init,
    console_init_r,     /* fully init console as a device */
    interrupt_init,
    board_late_init,
    INIT_FUNC_WATCHDOG_RESET
    initr_net,
    run_main_loop,
};

其中比较重要的一些初始化函数如下:

  • 1.initr_caches函数:初始化cache,使能cache;

  • 2.board_init函数:FEC初始化,在board/freescale/mx6ull_toto/mx6ull_toto.c文件中定义;

  • 3.initr_mmc函数:初始化emmc,在common/board_r.c文件中定义;

  • 4.iinitr_env函数:初始化环境变量;

  • 5.console_init_r函数:初始化控制台,在common/console.c文件中定义;

  • 6.interrupt_init函数和initr_enable_interrupts函数:初始化中断并使能中断;在arch/arm/lib/interrupts.c文件中定义;

  • 7.initr_ethaddr函数:初始化网络地址,获取MAC地址,读取环境变量ethaddr的值;

  • 8.initr_net函数:初始化网络设备,函 数 调 用 顺 序 为 :initr_net->eth_initialize->board_eth_init(),在common/board_r.c文件中定义;

  • 9.run_main_loop函数:主循环,处理命令。

十二、run_main_loop函数详解

        run_main_loop函数,在common/board_r.c文件定义,具体代码如下;

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
    sandbox_main_loop_init();
#endif
    /* main_loop() can return to retry autoboot, if so just run it again */
    for (;;)
        main_loop();
    return 0;
}

        uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内核,这个功能就是由run_main_loop函数来完成的。

         main_loop函数,在common/main.c文件中定义,具体代码如下;

/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{
    const char *s;

    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

    if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
        env_set("ver", version_string);  /* set version variable */

    cli_init();

    if (IS_ENABLED(CONFIG_USE_PREBOOT))
        run_preboot_environment_command();

    if (IS_ENABLED(CONFIG_UPDATE_TFTP))
        update_tftp(0UL, NULL, NULL);

    if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) {
        /* efi_init_early() already called */
        if (efi_init_obj_list() == EFI_SUCCESS)
            efi_launch_capsules();
    }

    s = bootdelay_process();
    if (cli_process_fdt(&s))
        cli_secure_boot_cmd(s);

    autoboot_command(s);

    cli_loop();
    panic("No CLI available");
}

main_loop函数主要工作:

  • 1.调用bootstage_mark_name函数,打印出启动进度

  • 2.如果宏CONFIG_VERSION_VARIABLE定义了就会执行函数setenv,设置换将变量ver的值为version_string,也就是设置版本号环境变量;

  • 3.调用cli_init函数,初始化hushshell相关的变量

  • 4.调用bootdelay_process函数,此函数会读取环境变量bootdelay和bootcmd的内容,然后将bootdelay的值赋值给全局变量stored_bootdelay,返回值为环境变量bootcmd的值。

  • 5.autoboot_command函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?

        autoboot_command函数,在文件common/autoboot.c文件中定义,具体代码如下;

void autoboot_command(const char *s)
{
    debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    if (s && (stored_bootdelay == -2 ||
         (stored_bootdelay != -1 && !abortboot(stored_bootdelay)))) {
        bool lock;
        int prev;

        lock = autoboot_keyed() &&
            !IS_ENABLED(CONFIG_AUTOBOOT_KEYED_CTRLC);
        if (lock)
            prev = disable_ctrlc(1); /* disable Ctrl-C checking */

        run_command_list(s, -1, 0);

        if (lock)
            disable_ctrlc(prev);    /* restore Ctrl-C checking */
    }

    if (IS_ENABLED(CONFIG_AUTOBOOT_USE_MENUKEY) &&
        menukey == AUTOBOOT_MENUKEY) {
        s = env_get("menucmd");
        if (s)
            run_command_list(s, -1, 0);
    }
}

        abortboot函数,在文件common/autoboot.c文件中定义,具体代码如下;

static int abortboot(int bootdelay)
{
    int abort = 0;

    if (bootdelay >= 0) {
        if (autoboot_keyed())
            abort = abortboot_key_sequence(bootdelay);
        else
            abort = abortboot_single_key(bootdelay);
    }

    if (IS_ENABLED(CONFIG_SILENT_CONSOLE) && abort)
        gd->flags &= ~GD_FLG_SILENT;

    return abort;
}

        在倒计时结束之前有按键按下则执行函数 abortboot_single_key,abortboot_single_key函数在common/autoboot.c文件中定义,具体代码如下;

static int abortboot_single_key(int bootdelay)
{
    int abort = 0;
    unsigned long ts;

    printf("Hit any key to stop autoboot: %2d ", bootdelay);

    /*
     * Check if key already pressed
     */
    if (tstc()) {   /* we got a key press   */
        getchar();  /* consume input    */
        puts("\b\b\b 0");
        abort = 1;  /* don't auto boot  */
    }

    while ((bootdelay > 0) && (!abort)) {
        --bootdelay;
        /* delay 1000 ms */
        ts = get_timer(0);
        do {
            if (tstc()) {   /* we got a key press   */
                int key;

                abort  = 1; /* don't auto boot  */
                bootdelay = 0;  /* no more delay    */
                key = getchar();/* consume input    */
                if (IS_ENABLED(CONFIG_AUTOBOOT_USE_MENUKEY))
                    menukey = key;
                break;
            }
            udelay(10000);
        } while (!abort && get_timer(ts) < 1000);

        printf("\b\b\b%2d ", bootdelay);
    }

    putc('\n');

    return abort;
}

abortboot_single_key函数主要工作:

  • 1.倒计时的具体实现;

  • 2.判断键盘是否有按下,也就是是否打断了倒计时,如果键盘按下的话就执行相应的分支。比如设置abort为 1,设置 bootdelay为0等,最后跳出倒计时循环;

  • 3.返回abort的值,如果倒计时自然结束,没有被打断abort就为0,否则的话abort的值就为 1;

  • 4.在autoboot_command函数中,如果倒计时自然结束那么就执行函数run_command_list,此函数会执行参数s指定的一系列命令,也就是环境变量bootcmd的命令,bootcmd里面保存着默认的启动命令,因此linux内核启动!

十三、u-boot启动函数调用流程框图

        上面给大家详细的讲解了各个函数的作用,以及调用关系。现在给大家总结一下,以流程框图的形式,展示u-boot启动流程;

e00138b94a85475b5740031e05eb4be9.png

        

        本期的内容到这就结束了,如果觉得文章不错,可以点赞、收藏和关注哦,谢谢大家收看,下期再见!


  关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/596379.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

摆脱低工资!80%的高薪人会的动态大屏技巧,工具和教程都给你!

在汇报上下功夫已经是职业人基本的素养&#xff0c;看了某鹅厂的产品组朋友做的汇报才知道他们已经卷到了这种程度&#xff01;把静态的图表和文字&#xff0c;图片做成了3D动画&#xff0c;你别说&#xff0c;真就很出彩&#xff01;既有产品的仿真&#xff0c;又有数据的支撑…

Python编程技巧

当涉及到Python编程时&#xff0c;以下是一些技巧和惯用法&#xff0c;可以帮助您编写更加高效、可维护和优雅的代码&#xff1a; 1.使用描述性的变量名&#xff1a;选择具有描述性的变量和函数名&#xff0c;以便于理解代码的含义。避免使用单个字母或无意义的名称。 2.编写清…

Razor代码复用

1.布局&#xff08;Layout&#xff09;复用 Layout的使用&#xff0c;就像WebForm的模板页一样&#xff0c;甚至会更加简单&#xff0c;更加方便和明了。 要使用Layout&#xff0c;首先要在模板页相应的位置添加RenderBody()方法&#xff1a; <!DOCTYPE html><html la…

2.7 编译型和解释型

2.7 编译型和解释型 前面我们使用java和javac命令把Hello&#xff0c;World&#xff01;在控制台输出。那为什么输出&#xff0c;这里我们需要掌握两个知识点。编译型语言和解释型语言。在计算机的高级编程语言就分为编译型语言和解释型语言。而我们的Java既有编译型的特点也有…

docker compose部署ELK

1、准备下载相关镜像 docker pull logstash:7.6.2 docker pull kibana:7.6.2 docker pull elasticsearch:7.6.2 docker pull elastic/filebeat:7.6.22、创建相关文件夹 新建文件夹使用命令&#xff1a;mkdir /opt/docker_elk 在/opt/docker_elk/elasticsearch新建plugins和da…

chatgpt赋能python:Python代码怎么用?一个10年编程经验工程师的实践总结

Python代码怎么用&#xff1f;一个10年编程经验工程师的实践总结 如果你正在学习Python或已经是一名Python开发者&#xff0c;你需要知道如何正确地使用Python代码以实现项目需求。在本文中&#xff0c;我将分享我的10年Python编程经验&#xff0c;并介绍一些关于如何使用Pyth…

零基础认识java-后端 项目结构搭建、目录概况

后端项目构建 1、创建数据库表 2、创建项目 打开 idea&#xff0c;新建一个项目&#xff0c;在 new 一个新项目的时候&#xff0c;选择 Spring Initializr&#xff0c;在选择项目依赖的时候勾选 web下的 Spring Web Starter 和 SQL下的 MySQL Driver 和 MyBatis Framework &am…

Prop type `object` is forbidden

这种错一般是ESLint的配置的规则 项目里搜一下react/forbid-prop-types 可以看到把any和object禁用了&#xff0c;根据需要删除object即可

2023 CCF-百度松果基金正式启动申报!大语言模型、AIGC等热点课题首次公布

5 月 31 日&#xff0c;2023 年 CCF-百度松果基金&#xff08;简称“松果基金”&#xff09;正式启动申报&#xff0c;面向全球高校及科研院所青年学者开放&#xff0c;入选项目将获得松果基金百万课题基金及千万级支持与服务。申报截至 2023 年 7 月 10 日。 本届松果基金共设…

Nginx通过用户IP获取所在国家及地理位置

文章目录 前言一、GeoLite2-Country是什么&#xff1f;二、使用步骤2.1 下载GeoLite2-Country数据库文件&#xff0c;并导入Nginx2.2 配置Nginx模块2.3 使用变量来获取国家信息2.4 验证配置 总结 前言 Nginx是一款高性能、轻量级的Web服务器和反向代理服务器。它最初设计目的是…

JavaScript了解unshift,push在头部尾部添加元素的代码

以下为JavaScript了解unshift&#xff0c;push在头部尾部添加元素的程序代码和运行截图 目录 前言 一、unshift在头部添加元素 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 二、push在尾部添加元素 2.1 运行流程及思想 2.2 代码段 2.3 JavaS…

Javascript 俄罗斯方块 游戏代码

本俄罗斯方块代码采用 JavaScript 脚本代码写成&#xff0c;简单易懂&#xff1b; 全代码采用静态类及静态变量成员组成&#xff1b; 全脚本通过实现代码全局配置 OLSFK.Options {...} 定义方块起始坐标及定义各自的旋转点&#xff1b; 从初始化俄罗斯方块界面开始&#x…

【C++】指针 - 定义和使用,所占内存空间,空指针,野指针,const 修饰指针,指针和数组,指针和函数

文章目录 1. 定义和使用2. 所占内存空间3. 空指针4. 野指针5. const 修饰指针6. 指针和数组7. 指针和函数 1. 定义和使用 数据类型 * 变量名; 指针的作用是&#xff0c;可以通过指针间接访问内存。 内存编号是从 0 开始记录的&#xff0c;一般用十六进制数字表示。可以利用指…

关于如何用好线程池的一些建议

文章目录 1. 线程的使用场景2. 线程池创建3. 参数的配置建议常见的拒绝策略其他的拒绝策略 4. 线程池的任务处理流程5. 线程的状态6. 线程池的监控 1. 线程的使用场景 异步任务 简单来说就是某些不需要同步返回业务处理结果的场景&#xff0c;比如&#xff1a;短信、邮件等通…

一款IP渗透小工具

MoreFind 一款用于快速导出URL、Domain和IP的小工具 快速安装 方式一: 通过Go包管理安装 go install github.com/mstxq17/MoreFindlatest 方式二: 直接安装二进制文件 wget --no-check-certificate https://ghproxy.com/https://github.com/mstxq17/MoreFind/releases/…

软件测评师2012年下半年考试真题

基础知识&#xff1a; 解析&#xff1a;死锁就是运行不下去了&#xff0c;但是这里它说资源是同类型的&#xff0c;也就是说&#xff0c;我多出来的一个资源&#xff08;11个资源5个进程&#xff0c;每个进程分配2个&#xff09;给其中一个进程之后&#xff0c;运行完了资源释放…

【源码篇】基于SpringBoot+thymeleaf实现的图书管理系统

系统介绍 基于SpringBootthymeleaf实现的图书管理系统分为管理员、读者两个登录角色&#xff0c;一共是8个功能模块 管理员权限 图书管理&#xff1a; 添加图书&#xff1a;书名、作者、出版社、ISBM、简介、价格、出版日期、分类、数量查询图书&#xff1a;根据书名或分类…

Tomcat发布成服务

一、配置服务 1.配置bin目录下的service.bat&#xff08;用于生成新服务&#xff09; 配置内容&#xff1a; rem Tomcat解压根目录 set CATALINA_HOMED:\apache-tomcat-7.0.55-8080 rem 服务显示名称&#xff08;服务中对外显示的名称&#xff09; set PR_DISPLAYNAMEapache…

ChunJun FTP Connector 功能扩展解读

本文将从 FTP Connector 的功能详解&#xff0c;自定义文件切割及自定义 FileFormat 三个方面为大家带来 ChunJun FTP Connector 的功能扩展分享。 FTP Connector 详解 FTP 是用于在网络上进行文件传输的一套标准协议&#xff0c;它工作在 OSI 模型的第七层&#xff0c; TCP …

从0到1搭建自己的脚手架(java后端) | 京东云技术团队

一、脚手架是什么 脚手架是一种基础设施工具&#xff0c;用于快速生成项目的框架代码和文件结构。它是一种标准化的开发工具&#xff0c;使开发人员能够在项目的早期阶段快速搭建出一个具备基本功能和结构的系统。 二、脚手架的意义 主流的微服务架构体系下很多公司会将原有…