U-Boot 之五 详解 U-Boot 及 SPL 的启动流程

  在之前的博文 Linux 之八 完整嵌入式 Linux 环境介绍及搭建过程详解 中我们说了要一步步搭建整个嵌入式 Linux 运行环境,今天继续介绍 U-Boot 相关的内容。我所使用的硬件平台及整个要搭建的嵌入式 Linux 环境见博文 Linux 之八 完整嵌入式 Linux 环境介绍及搭建过程详解,没有特殊说明都是在以上环境中进行验证的,就不过多说明了。

  这篇博文我们仅仅关注启动过程本身,想要吃透 U-Boot,有太多东西需要学习!最开始我想放到一篇文章中,写着写着内容越来越多,最终超过了 CSDN 编辑器的限制。。。最终决定把内容拆分成多篇文章。你可能需要:

  1. U-Boot 之一 零基础编译 U-Boot 过程详解 及 编译后的使用说明
  2. U-Boot 之二 详解使用 eclipse + J-Link 进行编译及在线调试
  3. U-Boot 之三 U-Boot 源码文件解析及移植过程详解
  4. U-Boot 之四 U-Boot 之四 U-Boot 之四 构建过程(Kconfig 配置 + Kbuild 编译)详解

SPL

  SPL 即 Secondary Program Loader 的缩写,中文就是第二段程序加载器。这里的第二段程序其实就是指的 U-Boot,也就是,SPL 是第一段程序,优先执行,然后他再去加载 U-Boot。

  这里有一点需要注意,一般 MCU 内部还有个固化的引导程序,这个固化的 BootLoader 我在博文 STM32 之十四 System Memory、Bootloader 中有过详细的介绍。这段程序的会初始化部分外设以与外部通信,具体可以参考官方手册。在引入了 SPL 之后,整个启动过程就是如下所示:
在这里插入图片描述
  在 U-Boot 源码中,启动过程没有完全单独出 SPL 的代码,而是复用了大量 U-Boot 里面的代码。在代码中,通过宏 CONFIG_SPL_XXX 来进行区分。因此,SPL 的启动 与 U-Boot 的启动流程是一样的(但是所具体实现的功能是不一样的),下面我们来介绍一下 U-Boot 启动过程。

启动流程

  我们可以将 U-Boot 的启动过程划分为两个阶段:芯片初始化板级初始化。芯片初始化阶段的代码主要是位于 ./arch/架构/cpu 目录下,其中再根据架构的不同来区分,主要以汇编语言为主,下图展示了 ./arch 目录的基本介绍;
在这里插入图片描述
板级初始化阶段的代码主要位于 ./arch/lib./arch/mach-xxx./board 目录下,代码也逐渐由汇编语言过度到 C 语言了。当然这两个阶段都可能引用一些公共的代码(例如平台无关的头文件)。./board 目录基本就是按照厂商来组织(例如 ST),同一厂家的开发板放在同一个目录下。

  U-Boot 源码文件众多,我们如何知道最开始的启动文件(程序入口)是哪个呢?这就需要查看 .\arch\arm\cpu 目录下的 u-boot.lds 文件了(对于 SPL/TPL 对应的就是 .\arch\arm\cpu\u-boot-spl.lds 文件)。.lds 是连接脚本文件,它描述了如何生成最终的二进制文件,其中就包含程序入口。例如,在 u-boot.lds 文件中我们可以看到如下代码:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
#ifndef CONFIG_CMDLINE/DISCARD/ : { *(.u_boot_list_2_cmd_*) }
#endif// 省略一部分. = 0x00000000;. = ALIGN(4);.text :{*(.__image_copy_start)*(.vectors)CPUDIR/start.o (.text*)}
  1. 在部分 .\arch\arm\mach-xxx 目录下面也有 u-boot-spl.lds,这个一般就是针对那些比较特殊的架构单独实现的连接脚本。在编译过程中,会通过 CONFIG_SYS_LDSCRIPT=xxxx 来执行这个特殊的脚本文件,如果不指定默认采用.\arch\arm\cpu\u-boot-spl.lds 文件。
  2. u-boot.lds 文件也同上的说明可能有多个。

  从上面的代码可以看到,ENTRY(_start) 表示最终可执行程序的入口是 _start。第一个节的开始定义了一个名为 __image_copy_start 的符号,它的定义位于 ./arch/arm/lib/sections.c 中:char __image_copy_start[0] __section(".__image_copy_start"); 它仅仅就是个符号,不包含任何内容;接下来就是 vectors,它的定义位于 ./arch/arm/lib/vectors_m.S 中(针对我使用的 STM32F7 这个 ARM 核),这个其实就是 ARM 核中断向量表。

   .section  .vectors
ENTRY(_start).long	CONFIG_SYS_INIT_SP_ADDR		@ 0 - Reset stack pointer.long	reset				@ 1 - Reset.long	__invalid_entry			@ 2 - NMI.long	__hard_fault_entry		@ 3 - HardFault.long	__mm_fault_entry		@ 4 - MemManage.long	__bus_fault_entry		@ 5 - BusFault.long	__usage_fault_entry		@ 6 - UsageFault.long	__invalid_entry			@ 7 - Reserved.long	__invalid_entry			@ 8 - Reserved.long	__invalid_entry			@ 9 - Reserved.long	__invalid_entry			@ 10 - Reserved.long	__invalid_entry			@ 11 - SVCall.long	__invalid_entry			@ 12 - Debug Monitor.long	__invalid_entry			@ 13 - Reserved.long	__invalid_entry			@ 14 - PendSV.long	__invalid_entry			@ 15 - SysTick.rept	255 - 16.long	__invalid_entry			@ 16..255 - External Interrupts.endr

熟悉 ARM 平台的应该知道,它的入口就是中断向量表。因此,_start 也是定义在这里的。接下来我们以比较简单的 u-boot-spl.lds 为例,来完整解析一下连接脚本:

/* SPDX-License-Identifier: GPL-2.0+ */
/** Copyright (c) 2004-2008 Texas Instruments** (C) Copyright 2002* Gary Jennejohn, DENX Software Engineering, *//* 指定输出可执行文件: "elf32 位小端格式" */
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/* 指定输出可执行文件的目标架构:"arm" */
OUTPUT_ARCH(arm)
/* 指定输出可执行文件的入口地址(起始代码段):"_start" */
ENTRY(_start)
SECTIONS
{/* * 设置 0 的原因是 arm 内核的处理器,上电后默认是从 0x00000000 处启动* 1. stm32 片内的 nor-flash 起始地址是0x08000000,上电后,系统会自动将该地址(0x08000000) 映射到 0x00000000(硬件设计实现)*/. = 0x00000000;/* * 代码以 4 字节对齐 .text为代码段* 各个段按先后顺序依次排列 * ARM规定在 cortex-m 的内核中,镜像入口处首地址存放的是主堆栈的地址,其次是复位中断地址,再其后依次存放其他中断地址* 更详细的启动过程可以参见我之前的博文:ARM 之九 Cortex-M/R 内核启动过程 / 程序启动流程(基于ARMCC、Keil)*/. = ALIGN(4);.text :{__image_copy_start = .; 	/* u-boot 的设计中需要将 u-boot 的镜像拷贝到ram(sdram,ddr....)中执行,这里表示复制的开始地址 */*(.vectors)					/* 中断向量表 */CPUDIR/start.o (.text*) 	/* CPUDIR/start.o中的所有.text段 */*(.text*)					/* 其他.o中的所有.text 段 */*(.glue*)					/* 其他.o中的所有.glue 段 */}/* * .rodata 段,确保是以4字节对齐 */. = ALIGN(4);	.rodata : {*(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }	/* 按名称依次存放其他 .o 文件中的.rodata *//* * data段,确保是以4字节对齐*/. = ALIGN(4);	.data : {*(.data*) }/* * u_boot_list 段,确保是以4字节对齐 * 这里存放的都是 u_boot_list 中的函数* 例如:base/bdinfo/blkcache/cmp....* 具体的可参看./u-boot.map .u_boot_list* tips:要想优化编译出来的 u-boot.bin 大小,可以参看此文件进行对照裁剪*/. = ALIGN(4);.u_boot_list : {KEEP(*(SORT(.u_boot_list*)));}/* * binman_sym_table 段,确保是以 4 字节对齐* binman 实现的功能是让 c 代码通过 binman_* 的函数接口字节调用镜像中的个别函数* 具体可参看 binman_sym.h 中的接口*/. = ALIGN(4);.binman_sym_table : {__binman_sym_start = .;KEEP(*(SORT(.binman_sym*)));__binman_sym_end = .;}/* * __image_copy_end 也是个符号表示一个结束地址,确保是以4字节对齐 */. = ALIGN(4);__image_copy_end = .;	/* u-boot 的设计中需要将 u-boot 的镜像拷贝到ram(sdram,ddr....)中执行,这里表示复制的结束地址 */.rel.dyn : {__rel_dyn_start = .;*(.rel*)__rel_dyn_end = .;}.end :{*(.__end)}_image_binary_end = .; /* bin文件结束 */.bss __rel_dyn_start (OVERLAY) : {__bss_start = .;*(.bss*). = ALIGN(4);__bss_end = .;}__bss_size = __bss_end - __bss_start;.dynsym _image_binary_end : {*(.dynsym) }.dynbss : {*(.dynbss) }.dynstr : {*(.dynstr*) }.dynamic : {*(.dynamic*) }.hash : {*(.hash*) }.plt : {*(.plt*) }.interp : {*(.interp*) }.gnu : {*(.gnu*) }.ARM.exidx : {*(.ARM.exidx*) }
}
/* 下面就是检查一些限制 */
#if defined(CONFIG_SPL_MAX_SIZE)
ASSERT(__image_copy_end - __image_copy_start < (CONFIG_SPL_MAX_SIZE), \"SPL image too big");
#endif#if defined(CONFIG_SPL_BSS_MAX_SIZE)
ASSERT(__bss_end - __bss_start < (CONFIG_SPL_BSS_MAX_SIZE), \"SPL image BSS too big");
#endif#if defined(CONFIG_SPL_MAX_FOOTPRINT)
ASSERT(__bss_end - _start < (CONFIG_SPL_MAX_FOOTPRINT), \"SPL image plus BSS too big");
#endif

  这里有一点需要注意,SPL/TPL 与 U-Boot 采用的部分接口是一样的,但是具体实现并不在同一文件中。也就是说,对于同一函数,SPL/TPL 与 U-Boot 在不同的文件中有不同的实现(下面启动过程章节分别说明)。SPL/TPL 的代码是分散在源码目录的各个文件夹下的,那么我们如何知道 SPL/TPL 具体使用了哪些源代码文件呢?

  一个比较简单的方法是:SPL 的编译过程产生的文件会单独放到 SPL 目录下,TPL 的编译过程产生的文件会单独放到 TPL 目录下,我们可以直接查看编译后的 SPL 或者 TPL 文件夹,其中的内容也是按照源码目录组织的。SPL 编译产生的文件(剔除了部分文件)如下所示:

SPL
├── arch
│   └── arm
│       ├── cpu
│       │   ├── armv7m
│       │   │   ├── built-in.o
│       │   │   ├── cache.o
│       │   │   ├── cpu.o
│       │   │   ├── mpu.o
│       │   │   └── start.o
│       │   └── built-in.o
│       ├── lib
│       │   ├── ashldi3.o
│       │   ├── ashrdi3.o
│       │   ├── asm-offsets.s
│       │   ├── bdinfo.o
│       │   ├── bootm-fdt.o
│       │   ├── built-in.o
│       │   ├── cache.o
│       │   ├── crt0.o
│       │   ├── div0.o
│       │   ├── div64.o
│       │   ├── eabi_compat.o
│       │   ├── interrupts_m.o
│       │   ├── lib1funcs.o
│       │   ├── lib.a
│       │   ├── lshrdi3.o
│       │   ├── memcpy.o
│       │   ├── memset.o
│       │   ├── muldi3.o
│       │   ├── psci-dt.o
│       │   ├── reset.o
│       │   ├── sections.o
│       │   ├── setjmp.o
│       │   ├── spl.o
│       │   ├── stack.o
│       │   ├── uldivmod.o
│       │   ├── vectors_m.o
│       │   ├── zimage.o
│       └── mach-stm32
│           ├── built-in.o
│           ├── soc.o
├── board
│   └── st
│       ├── common
│       │   └── built-in.o
│       └── stm32f746-disco
│           ├── built-in.o
│           ├── stm32f746-disco.o
├── boot
│   ├── built-in.o
│   ├── image-board.o
│   ├── image-fdt.o
│   ├── image.o
├── cmd
│   ├── built-in.o
│   ├── nvedit.o
├── common
│   ├── built-in.o
│   ├── cli.o
│   ├── command.o
│   ├── console.o
│   ├── dlmalloc.o
│   ├── fdt_support.o
│   ├── init
│   │   ├── board_init.o
│   │   └── built-in.o
│   ├── malloc_simple.o
│   ├── memsize.o
│   ├── spl
│   │   ├── built-in.o
│   │   ├── spl_legacy.o
│   │   ├── spl.o
│   │   ├── spl_xip.o
│   ├── s_record.o
│   ├── stdio.o
│   ├── xyzModem.o
├── disk
│   ├── built-in.o
│   ├── part.o
├── drivers
│   ├── block
│   │   ├── blk_legacy.o
│   │   └── built-in.o
│   ├── built-in.o
│   ├── clk
│   │   ├── analogbits
│   │   │   └── built-in.o
│   │   ├── built-in.o
│   │   ├── clk_fixed_factor.o
│   │   ├── clk_fixed_rate.o
│   │   ├── clk_stm32f.o
│   │   ├── clk-uclass.o
│   │   ├── imx
│   │   │   └── built-in.o
│   │   ├── tegra
│   │   │   └── built-in.o
│   │   └── ti
│   │       └── built-in.o
│   ├── core
│   │   ├── built-in.o
│   │   ├── device.o
│   │   ├── dump.o
│   │   ├── fdtaddr.o
│   │   ├── lists.o
│   │   ├── of_extra.o
│   │   ├── ofnode.o
│   │   ├── read_extra.o
│   │   ├── root.o
│   │   ├── simple-bus.o
│   │   ├── uclass.o
│   │   ├── util.o
│   ├── gpio
│   │   ├── built-in.o
│   │   ├── gpio-uclass.o
│   │   ├── stm32_gpio.o
│   ├── misc
│   │   ├── built-in.o
│   │   ├── misc-uclass.o
│   │   ├── stm32_rcc.o
│   ├── mtd
│   │   ├── built-in.o
│   │   ├── mtdcore.o
│   │   ├── mtd.o
│   │   ├── mtd_uboot.o
│   │   ├── mtd-uclass.o
│   │   ├── stm32_flash.o
│   ├── pinctrl
│   │   ├── broadcom
│   │   │   └── built-in.o
│   │   ├── built-in.o
│   │   ├── nxp
│   │   │   └── built-in.o
│   │   ├── pinctrl_stm32.o
│   │   ├── pinctrl-uclass.o
│   ├── ram
│   │   ├── built-in.o
│   │   ├── ram-uclass.o
│   │   ├── stm32_sdram.o
│   ├── reset
│   │   ├── built-in.o
│   │   ├── reset-uclass.o
│   │   ├── stm32-reset.o
│   ├── serial
│   │   ├── built-in.o
│   │   ├── serial_stm32.o
│   │   ├── serial-uclass.o
│   ├── soc
│   │   └── built-in.o
│   └── timer
│       ├── built-in.o
│       ├── stm32_timer.o
│       ├── timer-uclass.o
├── dts
│   ├── built-in.o
│   └── dt-spl.dtb
├── env
│   └── built-in.o
├── fs
│   ├── built-in.o
│   ├── fs_internal.o
├── include
│   ├── autoconf.mk
│   └── generated
│       ├── asm-offsets.h
│       └── generic-asm-offsets.h
├── lib
│   ├── abuf.o
│   ├── asm-offsets.s
│   ├── built-in.o
│   ├── crc32.o
│   ├── ctype.o
│   ├── date.o
│   ├── display_options.o
│   ├── div64.o
│   ├── elf.o
│   ├── errno.o
│   ├── fdtdec_common.o
│   ├── fdtdec.o
│   ├── hang.o
│   ├── hash-checksum.o
│   ├── hashtable.o
│   ├── hexdump.o
│   ├── libfdt
│   │   ├── built-in.o
│   │   ├── fdt_addresses.o
│   │   ├── fdt_empty_tree.o
│   │   ├── fdt.o
│   │   ├── fdt_overlay.o
│   │   ├── fdt_ro.o
│   │   ├── fdt_rw.o
│   │   ├── fdt_strerror.o
│   │   ├── fdt_sw.o
│   │   ├── fdt_wip.o
│   ├── linux_compat.o
│   ├── linux_string.o
│   ├── lmb.o
│   ├── membuff.o
│   ├── net_utils.o
│   ├── panic.o
│   ├── qsort.o
│   ├── rand.o
│   ├── rtc-lib.o
│   ├── slre.o
│   ├── string.o
│   ├── strto.o
│   ├── tables_csum.o
│   ├── time.o
│   ├── tiny-printf.o
│   ├── uuid.o
├── u-boot.cfg
├── u-boot-spl
├── u-boot-spl.bin
├── u-boot-spl.dtb
├── u-boot-spl-dtb.bin
├── u-boot-spl.lds
├── u-boot-spl.map
├── u-boot-spl-nodtb.bin
├── u-boot-spl-pad.bin
└── u-boot-spl.sym

从中的 .o 文件我们就可以清除的知道 SPL 使用了那些源代码文件了。U-Boot 本身的编译默认并没有一个统一的目录,产生的中间文件都是直接放到与源文件统一目录下的。

  当然我们可以在使用 make 命令时指定 O=xxx (例如,make O=/tmp/build canyonlands_config,每个命令都需要指定)参数来指定配置及编译输出的位置。例如 make O=build stm32f769-disco_defconfig 就会把配置生成的文件放到 ./build 目录下,再例如最终编译命令:CROSS_COMPILE=arm=none=eabi- ARC=arm make O=build -j8。关于构建及配置我们后面单独说明。

芯片初始化阶段

  这里就以我使用的 STM32F769 这个 MCU 为例来说明一下,该 MCU 是 ARM 核心,指令集架构是 armv7m,因此,我的开发板使用的芯片初始化使用的具体代码就是 ./arch/arm/cpu/armv7m 下的各代码。那么具体是哪个文件呢?

  前面说过,ARM 架构要求,中断向量表,开头依次是 SP 地址,复位中断地址,其他中断地址。获取 SP 后,从复位中断开始执行。看中断向量表,给出了 reset 这个符号,那么我们就需要找到 reset 这个符号。再看看目录中,正好有个 start.S 的文件里就定义了 reset 符号,那么毫无疑问就是它了。

   然而,当我自信满满的打开 start.S 时却发现,其中的代码极其少,对比了一下 armv7 以及 armv8 目录下的 start.S ,完全就不是一个级别!具体如下图对比所示:
在这里插入图片描述
  首先,我们可以确认的是,U-Boot 是支持 armv7m 指令集架构的 CPU 的(根据后面对于源代码的研究,好多功能是不可用的)。至于为什么代码这么少,我也不是很清楚。我们就通过对比的方法并结合 U-Boot 的手册来重点关注一下他们的共同点:

  1. 他们的开头都是直接从 reset 符号下的代码开始执行的(Cortex-M 内核规定)。启动过程中的函数调用如下(忽略在某些宏定义成立时的调用)所示:

    1. armv8: reset -> save_boot_params -> save_boot_params_ret -> apply_core_errata -> lowlevel_init -> _main
    2. armv7: reset -> save_boot_params -> save_boot_params_ret -> cpu_init_crit -> lowlevel_init -> _main
    3. armv7m: reset -> _main

    其中,save_boot_params 是个弱函数,意味着如果需要,我们可以通过自定义同名函数来替代该函数。lowlevel_init() 这个函数定义在 lowlevel_init.S 这个文件中,官方手册中有详细说明具体用途:

    • 来做一些基本的初始化,以使 MCU可以运行到 board_init_f()
    • 没有全局数据或 BSS
    • 没有堆栈(ARMv7 可能有一个,但很快会被移除)
    • 不能设置 SDRAM 或使用控制台
    • 必须仅做最少的事,只要能运行到 board_init_f() 即可
    • 可以不需要它
  2. 他们都定义了一个名为 c_runtime_cpu_setup 符号,并导出为了全局符号,只是该符号内部代码有所不同。但是,该符号均没有在本文件中调用。

  3. 他们经过一些列执行后,最终都会跳转到 _main,下文我们单独详细来介绍 _main

  4. 至于目录下的其他 .s.c 文件,其中都是定义一些供外部使用的接口。

_main

  _main 这个符号定义在 ./arch/arm/lib/crt0.S 文件中。crt0 是 C-runtime Startup Code 的简称,主要就是用来准备 C 运行环境。下图是 _main 的函数调用关系(忽略部分宏值条件,顺序先后为从上到下):
在这里插入图片描述
接下来我们在分析一下 _main 的具体功能:

  1. 为调用 board_init_f() 初始化基本环境。这个环境只提供一个栈和一个存储 GD(Global Data,全局数据)数据结构的地方,两者都位于一些现成的 RAM (SRAM,锁定的缓存……)中。在这种情况下,全局变量无论是否初始化,都是不可用的,只有初始化的常量数据可用。在调用 board_init_f() 之前,全局数据应该清零。
    在这里插入图片描述
      代码一开始就是设置栈指针的位置,宏 CONFIG_SPL_STACK / CONFIG_TPL_STACK / CONFIG_SYS_INIT_SP_ADDR 会在 .\include\configs\使用的开发板.h 中定义(例如,我这里对应的是 stm32f746-disco.h)。
      注意,GD 的数据结构定义在 include\asm-generic\global_data.h 中的 global_data,然后又使用 typedef struct global_data gd_t 进行了重定义。而 gd 这个符号定义在 arch\x86\include\asm\global_data.h 中,如下所示:
    在这里插入图片描述
    由此可见,gd 就是一个 gd_t 类型的地址(指针)。经过上面的一番操作之后,内存数据就如下所示了:
    在这里插入图片描述
    r9 存放的就是 gd 的地址。
  2. 调用 board_init_f()。对于 SPL,board_init_f() 是定义在 .\common\spl\spl.c 中;对于 U-Boot,board_init_f() 是定义在 .\common\board_f.c 中。该函数为从系统 RAM (DRAM, DDR…)执行硬件做准备。由于系统 RAM 可能还不可用,因此 board_init_f() 必须使用当前 GD 来存储必须传递到以后阶段的任何数据。这些数据包括重定位目的地地址、未来栈和未来的 GD 位置。
    在这里插入图片描述
  3. 设置中间环境,其中栈和 GD 是由 board_init_f() 在系统 RAM 中分配的,但 BSS 和初始化的非 const 数据仍然不可用。对于 U-Boot(非 SPL),调用 relocate_code()。这个函数将 U-Boot 从当前位置重定位到由 board_init_f() 计算的重定位位置; 对于 SPL,board_init_f() 直接返回(到 crt0)。在 SPL中没有代码重定位,因此不需要调用 relocate_code()
    在这里插入图片描述
      relocate_code() 定义在 .\arch\arm\lib\relocate.S 中。函数原型:void relocate_code(addr_moni)在进行了代码重定位之后,中断向量表也是需要重定位的。这里需要注意的是,在代码重定位完成之后,后续执行就开始执行重定位之后的代码了。因此,这部分代码中有计算重定位之后的 here 位置。
      对于 U-Boot(非 SPL),一些 cpu 在内存方面还有一些工作要做,所以调用 c_runtime_cpu_setupc_runtime_cpu_setup 就定义在我们的起始文件 start.S 中,很多芯片的实现就是空的,没啥内容。
  4. 为调用 board_init_r() 设置最终环境。这个环境有 BSS(初始化为 0),初始化为非 const 数据(初始化为预期值),以及系统 RAM 中的栈(对于 SPL 来说,将栈和 GD 移动到 RAM 是可选的)。GD 保留了 board_init_f() 设置的值。最终的内存如下所示:
    在这里插入图片描述
  5. 调用 board_init_r()。对于 SPL,board_init_r() 是定义在 .\common\spl\spl.c 中;对于 U-Boot,board_init_r() 是定义在 .\common\board_r.c 中。

板级初始化阶段

  在上面对于 _main 的分析中,我们可以看到,其中调用了很多 board_ 开头的函数。这部分函数主要位于 ./common 目录下。同时需要注意的是,这里指的是 U-Boot 本身,SPL/TPL 的代码主要位于 .\common\spl\ 目录下。

  • ./common/init/board_init.c 其中的接口 U-Boot 与 SPL/TPL 共用。
    • ulong board_init_f_alloc_reserve(ulong top):从“top”地址分配预留的空间作为“globals”使用,并返回已分配空间的“bottom”地址。
      在这里插入图片描述
    • void board_init_f_init_reserve(ulong base):初始化保留的空间(已从 C 运行时环境处理代码安全地在 C 堆栈上分配)。
      在这里插入图片描述
  • void board_init_f(ulong boot_flags):为执行 board_init_r 准备环境。在这里,GD 可用、栈在 RAM 中,BSS 不可用(全局变量、静态变量均不可用)。
    • ./common/board_f.c:U-Boot 使用的接口位于此文件中,调用关系如下图:
      在这里插入图片描述
      void board_init_f(ulong boot_flags) 通过遍历执行 static const init_fnc_t init_sequence_f[] 中定义的各个接口实现各种功能。
    • .\common\spl\spl.c:SPL/TPL 使用的接口位于此文件中,调用关系如下图:
      在这里插入图片描述
  • void board_init_r(gd_t *new_gd, ulong dest_addr):开始执行通用代码。从这里开始,GD 可用、SDRAM 可用、栈在 RAM 中,BSS 可用(全局变量、静态变量均可用),并最终执行位于 .\common\main.c 中的 void main_loop(void)
    • ./common/board_r.c
      在这里插入图片描述
      void board_init_r(gd_t *new_gd, ulong dest_addr) 通过遍历执行 static init_fnc_t init_sequence_r[] 中定义的各个接口实现各种功能。
    • .\common\spl\spl.c:SPL/TPL 使用的接口位于此文件中,调用关系如下图:
      在这里插入图片描述
      load 完镜像后,默认会去调用 spl_board_prepare_for_boot()jump_to_image_no_args() 跳转到 U-Boot。至此,u-boot-spl 的流程就走完了,接下来就是走 u-boot 的流程。

对比其他架构

  这里有必要来对比其他架构来一个说明(就以 armv7 架构 ARM CPU 作为对比 )。如果大家去看网上的一些文章,会发现他们的启动流程和这里的有很大区别,一个典型的启动流程图如下所示:
在这里插入图片描述
至于 armv7m 为啥与上面差别这么大,我也还没搞清楚!

参考

  1. https://askubuntu.com/questions/1243252/how-to-install-arm-none-eabi-gdb-on-ubuntu-20-04-lts-focal-fossa
  2. https://james-hui.com/2021/07/02/building-a-small-uboot-linux-and-rootfs-for-arm-cortex-m7/
  3. https://www.cnblogs.com/dylancao/p/8621789.html
  4. https://wowothink.com/1e031f74/
  5. https://blog.csdn.net/linuxweiyh/article/details/99331659
  6. https://loee.xyz/2021/04/27/uboot-%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/
  7. https://www.twblogs.net/t/5d26d62ebd9eee1ede06f20d
  8. https://mrchen.love/Article/ID/57
  9. https://blog.csdn.net/weixin_39890452/article/details/114470827
  10. https://www.cnblogs.com/cslunatic/archive/2013/03/28/2986146.html
  11. https://wowothink.com/146db8db/
  12. https://www.pianshen.com/article/70672050376/
  13. https://my.oschina.net/u/4232364/blog/3134261
  14. https://adrianalin.gitlab.io/popsblog.me/posts/build-linux-for-stm32f769i-disco-using-buildroot/


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部