[Linux学习笔记]F1C100s的u-boot学习记录

167 阅读54分钟

前言

好不容易有时间空闲下来,就想着把之前心心念念的u-boot再深入研究一下,毕竟U-boot总是在用,却不知其所以然,顺带进行记录。

Makefile分析

我们对整个u-boot-sunxi-with-spl进行编译的时候,一般需要执行以下几条命令:

make distclean
make ARCH=arm CROSS_COMPILE=/home/ender/sunxi/gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi- licheepi_nano_defconfig
make ARCH=arm CROSS_COMPILE=/home/ender/sunxi/gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi- CFLAGS=-g -j16

为了分析对应的指令的执行过程,可以在顶层Makefile中查找相应指令。

第一条指令,执行distclean:

image.png 即通过find命令从srctree目录下找出符合条件的文件打印至标准输出,再由管道输入xargs命令进行构造参数,最后传给rm -rf,执行构建清除。当在顶层编译时,srctree的定义即为.(当前目录)。

第二条指令,执行defconfig:

由于xxxx_defconfig的前缀在不断发生变化,因此通过搜索后缀config找到匹配的目标为 image.png 搜索build变量的定义,位于srctree/scripts/Kbuild.includeimage.png 因此实际的make ARCH=arm CROSS_COMPILE=/home/ender/sunxi/gcc-linaro-7.2.1-2017.11-x86_64_arm-linux-gnueabi/bin/arm-linux-gnueabi- licheepi_nano_defconfig在Makefile中的依赖可以化简为以下:

licheepi_nano_defconfig: scripts_basic outputmakefile FORCE
    make -f ./scripts/Makefile.build obj=scripts/kconfig licheepi_nano_defconfig

FORCE使得目标被强制执行,其中依赖scripts_basic位于顶层Makefile内

image.png 依赖outputmakefile也位于顶层Makefile内

image.png 由此可知licheepi_nano_defconfig目标终究会调用./scripts/Makefile.build,并传入参数obj=scripts/kconfig licheepi_nano_defconfig

注意到Makefile.build中没有与licheepi_nano_defconfig相匹配的目标,因此先分析obj参数,看源码可知obj参数被做如下处理:

image.png 经过转换后,去除特定前缀,则src=obj=scripts/kconfig

image.png 进一步的,src被用于组成kbuild-file,展开后得知kbuild-file=./scripts/kconfig/Makefile。并且在57行的include $(kbuild-file)中将文件./scripts/kconfig/Makefile包含进来了。 打开./scripts/kconfig/Makefile文件搜索,即可找到licheepi_nano_defconfig对应的目标如下:

image.png

此处第一条命令把licheepi_nano_defconfig文件经过$(CPP)工具预处理生成 generated_defconfig。 之后第二条命令通过 sed去掉每行开头的空白字符。 最后第三条命令调用 $<,即$(obj)/conf并结合KConfig来生成完整的配置信息,最终写入.config文件。

第三条指令:执行make:

U-Boot的默认目标是_all,因此在顶层Makefile中可以搜索到关键字:

image.png 此处150行的_all:没有指定目标所需的依赖,通过搜索找到225行处的以下定义:

image.png 得知_all依赖于all,故查找目标all:

image.png all又依赖于目标.binman_stamp,而目标.binman_stamp又依赖于目标$(INPUTS-y),在980行处看到如下定义:

image.png 可知这里便是根据具体配置信息将依赖添加入$(INPUTS-y),从而使得Makefile能够正确构建出u-boot.bin,spl/u-boot-spl.bin等文件,然而我们的目标是u-boot-sunxi-with-spl.bin,继续搜索关键字,在arch/arm/dts/sunxi-u-boot.dtsi中得到以下内容:

image.png 得知其是通过binman工具整合而来,且依赖于spl/sunxi-spl.bin u-boot-nodtb等文件,查找spl/sunxi-spl.bin依赖,在顶层Makefile中得到以下内容:

image.png 可知其依赖于spl/u-boot-spl,追溯可得:

image.png 该命令等价于:

    make obj=spl -f ./scripts/Makefile.xpl all

于是在./scripts/Makefile.xpl中,发现目标all的定义如下:

image.png 追踪当前文件中定义的INPUTS-y,得到:

image.png

U-boot源码分析:

程序的根源,启动文件start.S:

由于F1C100S的CPU是ARM926EJS,因此它的启动文件在arch/arm/cpu/arm26ejs/start.S,内容如下

/* SPDX-License-Identifier: GPL-2.0+ */
/*
 *  armboot - Startup Code for ARM926EJS CPU-core
 *
 *  Copyright (c) 2003  Texas Instruments
 *
 *  ----- Adapted for OMAP1610 OMAP730 from ARM925t code ------
 *
 *  Copyright (c) 2001	Marius Gröger <mag@sysgo.de>
 *  Copyright (c) 2002	Alex Züpke <azu@sysgo.de>
 *  Copyright (c) 2002	Gary Jennejohn <garyj@denx.de>
 *  Copyright (c) 2003	Richard Woodruff <r-woodruff2@ti.com>
 *  Copyright (c) 2003	Kshitij <kshitij@ti.com>
 *  Copyright (c) 2010	Albert Aribaud <albert.u.boot@aribaud.net>
 */

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

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

reset:
	/* Allow the board to save important registers */
	b	save_boot_params                       //跳转执行save_boot_params
save_boot_params_ret:
	/*
	 * 设置cpu进入SVC32模式
	 */
	mrs	r0,cpsr                                //mrs指令读cpsr寄存器到r0
	bic	r0,r0,#0x1f                            //清空低5位
	orr	r0,r0,#0xd3                            //置高指定位
	msr	cpsr,r0                                //msr指令写r0到cpsr寄存器

	/*
	 * we do sys-critical inits only at reboot,
	 * not when booting from ram!
	 */
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)             //如果不跳过LOWLEVEL_INIT
	bl	cpu_init_crit                          //调用cpu_init_crit,同时保存lr
#endif

	bl	_main                                  //进入_main

/*------------------------------------------------------------------------------*/

	.globl	c_runtime_cpu_setup
c_runtime_cpu_setup:

	bx	lr

/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
cpu_init_crit:
	/*
	 * 在禁用D cache前强制刷新D cache
	 */
	mov	r0, #0
flush_dcache:
	mrc	p15, 0, r15, c7, c10, 3
	bne	flush_dcache

	mcr	p15, 0, r0, c8, c7, 0	           /* invalidate TLB */
	mcr	p15, 0, r0, c7, c5, 0	           /* invalidate I Cache */

	/*
	 * 禁止MMU and D cache
	 * 使能I cache 如果SYS_ICACHE_OFF未被定义
	 */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00000300	           /* clear bits 9:8 (---- --RS) */
	bic	r0, r0, #0x00000087	           /* clear bits 7, 2:0 (B--- -CAM) */
#ifdef CFG_SYS_EXCEPTION_VECTORS_HIGH
	orr	r0, r0, #0x00002000	           /* set bit 13 (--V- ----) */
#else
	bic	r0, r0, #0x00002000	           /* clear bit 13 (--V- ----) */
#endif
	orr	r0, r0, #0x00000002	           /* set bit 1 (A) Align */
#if !CONFIG_IS_ENABLED(SYS_ICACHE_OFF)
	orr	r0, r0, #0x00001000	           /* set bit 12 (I) I-Cache */
#endif
	mcr	p15, 0, r0, c1, c0, 0

#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)    //如果不跳过SKIP_LOWLEVEL_INIT_ONLY
	/*
	 * Go setup Memory and board specific bits prior to relocation.
	 */
	mov	r4, lr		                  //设计多级调用,保存lr
	bl	lowlevel_init	                  //调用lowlevel_init,执行板级低层初始化
	mov	lr, r4		                  //恢复lr
#endif
	mov	pc, lr	                          //返回调用者
#endif /* CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT) */

/*************************************************************************
 *
 * 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
 *
 *************************************************************************/
WEAK(save_boot_params)
	b	save_boot_params_ret	                  //跳转执行save_boot_params_ret
ENDPROC(save_boot_params)

start.S中所做的事情是使CPU进入SVC模式,关闭MMUD CACHE,设置中断向量表,是否关闭I CACHE,是否调用lowlevel_init执行板级低层初始化,进入_main等等。由于.config文件里定义了SKIP_LOWLEVEL_INIT_ONLY,因此不会执行lowlevel_init,而直接进入_main函数,该函数位于文件arch/arm/lib/crt0.S中,其内容如下:

/* SPDX-License-Identifier: GPL-2.0+ */
/*
 *  crt0 - C-runtime startup Code for ARM U-Boot
 *
 *  Copyright (c) 2012  Albert ARIBAUD <albert.u.boot@aribaud.net>
 */

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

/*
 * This file handles the target-independent stages of the U-Boot
 * start-up where a C runtime environment is needed. Its entry point
 * is _main and is branched into from the target's start.S file.
 *
 * _main execution sequence is:
 *
 * 1. Set up initial environment for calling board_init_f().
 *    This environment only provides a stack and a place to store
 *    the GD ('global data') structure, both located in some readily
 *    available RAM (SRAM, locked cache...). In this context, VARIABLE
 *    global data, initialized or not (BSS), are UNAVAILABLE; only
 *    CONSTANT initialized data are available. GD should be zeroed
 *    before board_init_f() is called.
 *
 * 2. Call board_init_f(). This function prepares the hardware for
 *    execution from system RAM (DRAM, DDR...) As system RAM may not
 *    be available yet, , board_init_f() must use the current GD to
 *    store any data which must be passed on to later stages. These
 *    data include the relocation destination, the future stack, and
 *    the future GD location.
 *
 * 3. Set up intermediate environment where the stack and GD are the
 *    ones allocated by board_init_f() in system RAM, but BSS and
 *    initialized non-const data are still not available.
 *
 * 4a.For U-Boot proper (not SPL), call relocate_code(). This function
 *    relocates U-Boot from its current location into the relocation
 *    destination computed by board_init_f().
 *
 * 4b.For SPL, board_init_f() just returns (to crt0). There is no
 *    code relocation in SPL.
 *
 * 5. Set up final environment for calling board_init_r(). This
 *    environment has BSS (initialized to 0), initialized non-const
 *    data (initialized to their intended value), and stack in system
 *    RAM (for SPL moving the stack and GD into RAM is optional - see
 *    CONFIG_SPL_STACK_R). GD has retained values set by board_init_f().
 *
 * 6. For U-Boot proper (not SPL), some CPUs have some work left to do
 *    at this point regarding memory, so call c_runtime_cpu_setup.
 *
 * 7. Branch to board_init_r().
 *
 * For more information see 'Board Initialisation Flow in README.
 */

/*
 * Macro for clearing BSS during SPL execution. Usually called during the
 * relocation process for most boards before entering board_init_r(), but
 * can also be done early before entering board_init_f() on plaforms that
 * can afford it due to sufficient memory being available early.
 */

.macro CLEAR_BSS
	ldr	r0, =__bss_start	/* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
	ldr	r3, =__bss_end		/* this is auto-relocated! */
	mov	r1, #0x00000000		/* prepare zero to clear BSS */

	subs	r2, r3, r0		/* r2 = memset len */
	bl	memset
#else
	ldr	r1, =__bss_end		/* this is auto-relocated! */
	mov	r2, #0x00000000		/* prepare zero to clear BSS */

clbss_l:cmp	r0, r1			/* while not at end of BSS */
	strlo	r2, [r0]		/* clear 32-bit BSS word */
	addlo	r0, r0, #4		/* move to next */
	blo	clbss_l
#endif
.endm

/*
 * 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 CONFIG_IS_ENABLED(HAVE_INIT_STACK)
	ldr	r0, =CONFIG_VAL(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_XPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
	CLEAR_BSS
#endif

	mov	r0, #0
	bl	board_init_f

#if ! defined(CONFIG_XPL_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_TEXT_BASE
	sub	r1, r0
	add	lr, r1
#if defined(CONFIG_SYS_RELOC_GD_ENV_ADDR)
	ldr	r0, [r9, #GD_ENV_ADDR]		/* r0 = gd->env_addr */
	add	r0, r0, r1
	str	r0, [r9, #GD_ENV_ADDR]
#endif
#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
here:
/*
 * now relocate vectors
 */

	bl	relocate_vectors

/* Set up final (full) environment */

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

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

# ifdef CONFIG_XPL_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_XPL_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)

_start_ofs:
	.word	_start - _main

重点关注两个函数board_init_fboard_init_r,同时关注宏CONFIG_XPL_BUILDctr0.S中程序在spl阶段与U-boot porper阶段下的编译分支。可以通过编译产物spl/u-boot-spl.mapu-boot.map查找不同阶段下程序入口源码所在的文件。

U-boot SPL阶段下的ctr0.S:

保留SPL阶段下的代码部分:

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

.macro CLEAR_BSS                        //清空bss段操作
	ldr	r0, =__bss_start	/* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
	ldr	r3, =__bss_end		/* this is auto-relocated! */
	mov	r1, #0x00000000		/* prepare zero to clear BSS */

	subs	r2, r3, r0		/* r2 = memset len */
	bl	memset
#else
	ldr	r1, =__bss_end		/* this is auto-relocated! */
	mov	r2, #0x00000000		/* prepare zero to clear BSS */

clbss_l:cmp	r0, r1			/* while not at end of BSS */
	strlo	r2, [r0]		/* clear 32-bit BSS word */
	addlo	r0, r0, #4		/* move to next */
	blo	clbss_l
#endif
.endm

/*
 * 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 CONFIG_IS_ENABLED(HAVE_INIT_STACK)
	ldr	r0, =CONFIG_VAL(STACK)
#else
	ldr	r0, =(SYS_INIT_SP_ADDR)            //r0存放SP指针
#endif
	bic	r0, r0, #7	/* 8-byte alignment for ABI compliance */
	mov	sp, r0
	bl	board_init_f_alloc_reserve         //在栈中为gd留出空间,返回新的栈顶到r0
	mov	sp, r0                             //设置新的栈顶
	/* set up gd here, outside any C code */
	mov	r9, r0                             //gd地址保存在r9
	bl	board_init_f_init_reserve          //清空gd内容,划分malloc空间

#if defined(CONFIG_DEBUG_UART) && CONFIG_IS_ENABLED(SERIAL)
	bl	debug_uart_init                    //初始化调试串口
#endif

#if defined(CONFIG_XPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)
	CLEAR_BSS                                  //如果有必要(CONFIG_SPL_EARLY_BSS),清bss
#endif

	mov	r0, #0
	bl	board_init_f                       //调用board_init_f(0)

#if !defined(CONFIG_XPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)

#if !defined(CONFIG_XPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
	CLEAR_BSS                                  //清bss
#endif

# ifdef CONFIG_XPL_BUILD
	/* Use a DRAM stack for the rest of SPL, if requested */
	bl	spl_relocate_stack_gd             //搬运gd到DDR
	cmp	r0, #0                            //非0表示成功
	movne	sp, r0                            //重新设置SP指针指向DDR
	movne	r9, r0                            //重新设置r9执行新的gd地址
# endif

	/* call board_init_r(gd_t *id, ulong dest_addr) */
	mov     r0, r9                            //gd地址,SPL阶段下被视为dummy
	ldr	r1, [r9, #GD_RELOCADDR]	          //U-boot本体重定位目标地址,SPL阶段下被视为dummy
        
	/* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)            //根据目标指令集选择分支
	ldr	lr, =board_init_r
	bx	lr                                //调用board_init_r(&gd, gd->relocaddr)
#else
	ldr	pc, =board_init_r                 //调用board_init_r(&gd, gd->relocaddr)
#endif
	/* we should not return here. */
#endif

ENDPROC(_main)

_start_ofs:
	.word	_start - _main

可以看到在SPL阶段下_main在为C语言构造好运行栈环境后,调用board_init_f()

U-boot SPL阶段下的board_init_f():

位于arch/arm/mach-sunxi/board.c

void board_init_f(ulong dummy)
{
	sunxi_sram_init();

	/* Enable non-secure access to some peripherals */
	tzpc_init();

	clock_init();                    //初始化系统时钟,debug串口时钟
	timer_init();                    //初始化定时器
	gpio_init();                     //主要初始化串口控制台引脚

	spl_init();                      //Uboot SPL框架初始化
	preloader_console_init();        //控制台打印SPL调试信息,即"U-Boot SPL 2018.01 (Aug 30 2023 - 23:17:42)"

#if CONFIG_IS_ENABLED(I2C) && CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
	/* Needed early by sunxi_board_init if PMU is enabled */
	i2c_init_board();
	i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
#endif
	sunxi_board_init();              //配置电源,DDR,打印"DRAM: 64 MiB"
}

可见此阶段是SPL程序对单板硬件的初始化,并在这个过程中在串口终端打印出了

U-Boot SPL 2018.01 (Aug 30 2023 - 23:17:42)
DRAM: 64 MiB

U-boot SPL阶段下的board_init_r():

位于common/spl/spl.c

void board_init_r(gd_t *dummy1, ulong dummy2)
{
	u32 spl_boot_list[] = {
		BOOT_DEVICE_NONE,
		BOOT_DEVICE_NONE,
		BOOT_DEVICE_NONE,
		BOOT_DEVICE_NONE,
		BOOT_DEVICE_NONE,
	};                                                            //定义备选的启动设备列表
	spl_jump_to_image_t jump_to_image = &jump_to_image_no_args;   //spl_jump_to_image_t函数指针用于定义跳转函数类型
	struct spl_image_info spl_image;                              //定义spl镜像结构
	int ret, os;

	debug(">>" PHASE_PROMPT "board_init_r()\n");

	spl_set_bd();                                                 //将bd设置到全局变量gd

	if (IS_ENABLED(CONFIG_SPL_SYS_MALLOC)) {                      //初始化内存堆
		mem_malloc_init(SPL_SYS_MALLOC_START, SPL_SYS_MALLOC_SIZE);
		gd->flags |= GD_FLG_FULL_MALLOC_INIT;
	}
	if (!(gd->flags & GD_FLG_SPL_INIT)) {                         //初始化Uboot SPL框架
		if (spl_init())
			hang();
	}
	timer_init();                                                 //初始化定时器
	if (CONFIG_IS_ENABLED(BLOBLIST)) {                            //初始化bloblist
		ret = bloblist_init();
		if (ret) {
			debug("%s: Failed to set up bloblist: ret=%d\n",
			      __func__, ret);
			puts(PHASE_PROMPT "Cannot set up bloblist\n");
			hang();
		}
	}
	if (CONFIG_IS_ENABLED(HANDOFF)) {                              //初始化数据移交结构
		int ret;

		ret = setup_spl_handoff();
		if (ret) {
			puts(PHASE_PROMPT "Cannot set up SPL handoff\n");
			hang();
		}
	}

	if (CONFIG_IS_ENABLED(SOC_INIT))                                //初始化SOC
		spl_soc_init();

	if (IS_ENABLED(CONFIG_SPL_WATCHDOG) && CONFIG_IS_ENABLED(WDT))  //初始化看门狗
		initr_watchdog();

	if (IS_ENABLED(CONFIG_SPL_OS_BOOT) || CONFIG_IS_ENABLED(HANDOFF) ||
	    IS_ENABLED(CONFIG_SPL_ATF) || IS_ENABLED(CONFIG_SPL_NET))
		dram_init_banksize();                                   //在gd中记录DDR信息

	if (IS_ENABLED(CONFIG_SPL_LMB))                                 //初始化lmb
		lmb_init();

	if (CONFIG_IS_ENABLED(PCI) && !(gd->flags & GD_FLG_DM_DEAD)) {  //初始化pci设备
		ret = pci_init();
		if (ret)
			puts(PHASE_PROMPT "Cannot initialize PCI\n");
		/* Don't fail. We still can try other boot methods. */
	}

	if (CONFIG_IS_ENABLED(BOARD_INIT))                              //初始化单板
		spl_board_init();

	bootcount_inc();                                                //启动次数增加

	/* Dump driver model states to aid analysis */
	if (CONFIG_IS_ENABLED(DM_STATS)) {                              //获取设备驱动模型
		struct dm_stats mem;

		dm_get_mem(&mem);
		dm_dump_mem(&mem);
	}

	memset(&spl_image, '\0', sizeof(spl_image));                    //清空spl镜像结构
	if (IS_ENABLED(CONFIG_SPL_OS_BOOT))
		spl_image.arg = (void *)SPL_PAYLOAD_ARGS_ADDR;          //如果要启动系统,则设置系统启动时所需参数所在地址
	spl_image.boot_device = BOOT_DEVICE_NONE;                       //启动设备设置为BOOT_DEVICE_NONE
	board_boot_order(spl_boot_list);                                //获取启动顺序并填充至spl_boot_list

	ret = boot_from_devices(&spl_image, spl_boot_list,
				ARRAY_SIZE(spl_boot_list));             //根据spl_boot_list去选择启动设备,"Trying to boot from XXX"便是打印自此处
	if (ret) {
		if (CONFIG_IS_ENABLED(SHOW_ERRORS))
			printf(PHASE_PROMPT "failed to boot from all boot devices (err=%d)\n",
			       ret);
		else
			puts(PHASE_PROMPT "failed to boot from all boot devices\n");
		hang();
	}

	spl_perform_fixups(&spl_image);

	os = spl_image.os;                                              //选择镜像OS,SPL阶段为0,之前清空过
	if (os == IH_OS_U_BOOT) {
		debug("Jumping to %s...\n", xpl_name(xpl_next_phase()));
	} else if (CONFIG_IS_ENABLED(ATF) && os == IH_OS_ARM_TRUSTED_FIRMWARE) {
		debug("Jumping to U-Boot via ARM Trusted Firmware\n");
		spl_fixup_fdt(spl_image_fdt_addr(&spl_image));
		jump_to_image = &spl_invoke_atf;
	} else if (CONFIG_IS_ENABLED(OPTEE_IMAGE) && os == IH_OS_TEE) {
		debug("Jumping to U-Boot via OP-TEE\n");
		spl_board_prepare_for_optee(spl_image_fdt_addr(&spl_image));
		jump_to_image = &jump_to_image_optee;
	} else if (CONFIG_IS_ENABLED(OPENSBI) && os == IH_OS_OPENSBI) {
		debug("Jumping to U-Boot via RISC-V OpenSBI\n");
		jump_to_image = &spl_invoke_opensbi;
	} else if (CONFIG_IS_ENABLED(OS_BOOT) && os == IH_OS_LINUX) {
		debug("Jumping to Linux\n");
		if (IS_ENABLED(CONFIG_SPL_OS_BOOT))
			spl_fixup_fdt((void *)SPL_PAYLOAD_ARGS_ADDR);
		spl_board_prepare_for_linux();
		jump_to_image = &jump_to_image_linux;
	} else {
		debug("Unsupported OS image.. Jumping nevertheless..\n");
	}
	if (CONFIG_IS_ENABLED(SYS_MALLOC_F) &&
	    !IS_ENABLED(CONFIG_SPL_SYS_MALLOC_SIZE))
		debug("SPL malloc() used 0x%x bytes (%d KB)\n",
		      gd_malloc_ptr(), gd_malloc_ptr() / 1024);

	bootstage_mark_name(get_bootstage_id(false), "end phase");     //bootstage系列函数用于调试打标记
	ret = bootstage_stash_default();
	if (ret)
		debug("Failed to stash bootstage: err=%d\n", ret);

	if (IS_ENABLED(CONFIG_SPL_VIDEO_REMOVE)) {                     //移除显示器设备节点
		struct udevice *dev;
		int rc;

		rc = uclass_find_device(UCLASS_VIDEO, 0, &dev);
		if (!rc && dev) {
			rc = device_remove(dev, DM_REMOVE_NORMAL);
			if (rc)
				printf("Cannot remove video device '%s' (err=%d)\n",
				       dev->name, rc);
		}
	}
	if (CONFIG_IS_ENABLED(HANDOFF)) {                              //写入移交数据
		ret = write_spl_handoff();
		if (ret)
			printf(PHASE_PROMPT
			       "SPL hand-off write failed (err=%d)\n", ret);
	}
	if (CONFIG_IS_ENABLED(UPL_OUT) && (gd->flags & GD_FLG_UPL)) {  //将spl_image信息写入移交数据结构,供下一级程序接管
		ret = spl_write_upl_handoff(&spl_image);
		if (ret) {
			printf(PHASE_PROMPT
			       "UPL hand-off write failed (err=%d)\n", ret);
			hang();
		}
	}
	if (CONFIG_IS_ENABLED(BLOBLIST)) {
		ret = bloblist_finish();                               //结束blob_list,届时hand_off信息被打包成blob传递
		if (ret)
			printf("Warning: Failed to finish bloblist (ret=%d)\n",
			       ret);
	}

	spl_board_prepare_for_boot();                                  //启动前单板准备工作

	if (CONFIG_IS_ENABLED(RELOC_LOADER)) {                         //如果U-boot porper需要重定位
		int ret;

		ret = spl_reloc_jump(&spl_image, jump_to_image);       //搬运U-boot porper到DDR并跳转执行
		if (ret) {
			if (xpl_phase() == PHASE_VPL)
				printf("jump failed %d\n", ret);
			hang();                                        //失败终止
		}
	}

	jump_to_image(&spl_image);                                     //不需要重定位的U-boot则直接跳过去执行
}

可见此阶段SPL程序尝试对U-boot Proper进行重定位,并移交相关数据给下一阶段,在这个过程中在串口终端打印出了

    Trying to boot from sunxi SPI

U-boot porper阶段下的crt0.S:

保留U-Boot porper阶段下的代码如下:

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

.macro CLEAR_BSS                                                          //清空bss段操作
	ldr	r0, =__bss_start	/* this is auto-relocated! */

#ifdef CONFIG_USE_ARCH_MEMSET
	ldr	r3, =__bss_end		/* this is auto-relocated! */
	mov	r1, #0x00000000		/* prepare zero to clear BSS */

	subs	r2, r3, r0		/* r2 = memset len */
	bl	memset
#else
	ldr	r1, =__bss_end		/* this is auto-relocated! */
	mov	r2, #0x00000000		/* prepare zero to clear BSS */

clbss_l:cmp	r0, r1			/* while not at end of BSS */
	strlo	r2, [r0]		/* clear 32-bit BSS word */
	addlo	r0, r0, #4		/* move to next */
	blo	clbss_l
#endif
.endm

/*
 * 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 CONFIG_IS_ENABLED(HAVE_INIT_STACK)
	ldr	r0, =CONFIG_VAL(STACK)
#else
	ldr	r0, =(SYS_INIT_SP_ADDR)            //r0存放SP指针
#endif
	bic	r0, r0, #7	/* 8-byte alignment for ABI compliance */
	mov	sp, r0
	bl	board_init_f_alloc_reserve         //在栈中为gd留出空间,返回新的栈顶到r0
	mov	sp, r0                             //设置新的栈顶
	/* set up gd here, outside any C code */
	mov	r9, r0                             //gd地址保存在r9
	bl	board_init_f_init_reserve          //清空gd内容,划分malloc空间

#if defined(CONFIG_DEBUG_UART) && CONFIG_IS_ENABLED(SERIAL)
	bl	debug_uart_init                    //初始化调试串口
#endif

	mov	r0, #0
	bl	board_init_f                       //调用board_init_f(0)

/*
 * 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]	   //r0 = gd->start_addr_sp
	bic	r0, r0, #7	                   //8字节对齐
	mov	sp, r0                             //设置新的SP为r0
	ldr	r9, [r9, #GD_NEW_GD]		   //r9 = gd->new_gd

	adr	lr, here
#if defined(CONFIG_POSITION_INDEPENDENT)           //如果使用位置无关的编译
	adr	r0, _main                          //r0 = _main的地址       (当前运行时的_main地址)
	ldr	r1, _start_ofs                     //r1 = _start_ofs       (即_start-_main,负数)
	add	r0, r1                             //r0 = r0 + r1          (当前运行时的_start 地址)
	ldr	r1, =CONFIG_TEXT_BASE              //r1 = CONFIG_TEXT_BASE (编译时指定的_start地址)
	sub	r1, r0                             //r1 = r1 - r0,计算偏移量
	add	lr, r1                             //把lr修正到重定位后对应的值
#if defined(CONFIG_SYS_RELOC_GD_ENV_ADDR)          //如果GD中保存环境变量
	ldr	r0, [r9, #GD_ENV_ADDR]		   //r0 = gd->env_addr
	add	r0, r0, r1                         //r0 = r1
	str	r0, [r9, #GD_ENV_ADDR]             //gd->env_addr = r0
#endif
#endif
	ldr	r0, [r9, #GD_RELOC_OFF]		   /* r0 = gd->reloc_off */
	add	lr, lr, r0
        
#if defined(CONFIG_CPU_V7M)
	orr	lr, #1				   //Thumb指令集最低位处理
#endif
	ldr	r0, [r9, #GD_RELOCADDR]		   //r0 = gd->relocaddr
	b	relocate_code                      //重定位U-Boot porper代码
        
here:                                              //relocate_code将返回到此处
/*
 * now relocate vectors
 */
	bl	relocate_vectors                   //重定位向量表

/* Set up final (full) environment */

	bl	c_runtime_cpu_setup	          //创建C运行时环境
        
	CLEAR_BSS                                 //清空bss段

	bl coloured_LED_init                      //彩色LED初始化
	bl red_led_on                             //打开红色LED

	/* call board_init_r(gd_t *id, ulong dest_addr) */
	mov     r0, r9                            //gd地址
	ldr	r1, [r9, #GD_RELOCADDR]	          //U-boot本体重定位后的目标地址
        
	/* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)            //根据目标指令集选择分支
	ldr	lr, =board_init_r
	bx	lr                                //调用board_init_r(&gd, gd->relocaddr)
#else
	ldr	pc, =board_init_r                 //调用board_init_r(&gd, gd->relocaddr)
#endif
	/* we should not return here. */

ENDPROC(_main)

_start_ofs:
	.word	_start - _main

U-boot porper阶段下的board_init_f():

位于common/board_f.c:

void board_init_f(ulong boot_flags)
{
	struct board_f boardf;

	gd->flags = boot_flags;
	gd->flags &= ~GD_FLG_HAVE_CONSOLE;
	gd->boardf = &boardf;

	initcall_run_f();                                //调用initcall_run_f(),运行流水线进行初始化

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

找到函数initcall_run_f():

static void initcall_run_f(void)
{
	/*
	 * Please do not add logic to this function (variables, if (), etc.).
	 * For simplicity it should remain an ordered list of function calls.
	 */
	INITCALL(setup_mon_len);
#if CONFIG_IS_ENABLED(OF_CONTROL)
	INITCALL(fdtdec_setup);
#endif
#if CONFIG_IS_ENABLED(TRACE_EARLY)
	INITCALL(trace_early_init);
#endif
	INITCALL(initf_malloc);
	INITCALL(initf_upl);
	INITCALL(log_init);
	INITCALL(initf_bootstage); /* uses its own timer, so does not need DM */
	INITCALL(event_init);
	INITCALL(bloblist_maybe_init);
	INITCALL(setup_spl_handoff);
#if CONFIG_IS_ENABLED(CONSOLE_RECORD_INIT_F)
	INITCALL(console_record_init);
#endif
	INITCALL_EVT(EVT_FSP_INIT_F);
	INITCALL(arch_cpu_init);	/* basic arch cpu dependent setup */
	INITCALL(mach_cpu_init);	/* SoC/machine dependent CPU setup */
	INITCALL(initf_dm);
#if CONFIG_IS_ENABLED(BOARD_EARLY_INIT_F)
	INITCALL(board_early_init_f);
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
	/* get CPU and bus clocks according to the environment variable */
	INITCALL(get_clocks);		/* get CPU and bus clocks (etc.) */
#endif
#if !defined(CONFIG_M68K) || (defined(CONFIG_M68K) && !defined(CONFIG_MCFTMR))
	INITCALL(timer_init);		/* initialize timer */
#endif
#if CONFIG_IS_ENABLED(BOARD_POSTCLK_INIT)
	INITCALL(board_postclk_init);
#endif
	INITCALL(env_init);		/* initialize environment */
	INITCALL(init_baud_rate);	/* initialze baudrate settings */
	INITCALL(serial_init);		/* serial communications setup */
	INITCALL(console_init_f);	/* stage 1 init of console */
	INITCALL(display_options);	//打印Uboot标题,编译时间,厂商信息:"U-Boot 2018.01 (Aug 30 2023 - 23:17:42 +0800) Allwinner Technology"
	INITCALL(display_text_info);	/* show debugging info if required */
	INITCALL(checkcpu);
#if CONFIG_IS_ENABLED(SYSRESET)
	INITCALL(print_resetinfo);      //打印复位信息
#endif
	/* display cpu info (and speed) */
#if CONFIG_IS_ENABLED(DISPLAY_CPUINFO)
	INITCALL(print_cpuinfo);        //打印CPU信息:"CPU:   Allwinner F Series (SUNIV)\n"
#endif
#if CONFIG_IS_ENABLED(DTB_RESELECT)
	INITCALL(embedded_dtb_select);
#endif
#if CONFIG_IS_ENABLED(DISPLAY_BOARDINFO)
	INITCALL(show_board_info);      //打印单板信息:"Model: XXXXXX"
#endif
	WATCHDOG_INIT();
	INITCALL_EVT(EVT_MISC_INIT_F);
	WATCHDOG_RESET();
#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
	INITCALL(init_func_i2c);
#endif
	INITCALL(announce_dram_init);   //打印"DRAM:  "
	INITCALL(dram_init);		/* configure available RAM banks */
#if CONFIG_IS_ENABLED(POST)
	INITCALL(post_init_f);
#endif
	WATCHDOG_RESET();
#if defined(CFG_SYS_DRAM_TEST)
	INITCALL(testdram);
#endif /* CFG_SYS_DRAM_TEST */
	WATCHDOG_RESET();
#if CONFIG_IS_ENABLED(POST)
	INITCALL(init_post);
#endif
	WATCHDOG_RESET();
	/*
	 * Now that we have DRAM mapped and working, we can
	 * relocate the code and continue running from DRAM.
	 *
	 * Reserve memory at end of RAM for (top down in that order):
	 *  - area that won't get touched by U-Boot and Linux (optional)
	 *  - kernel log buffer
	 *  - protected RAM
	 *  - LCD framebuffer
	 *  - monitor code
	 *  - board info struct
	 */
	INITCALL(setup_dest_addr);
#if CONFIG_IS_ENABLED(OF_BOARD_FIXUP) && \
    !CONFIG_IS_ENABLED(OF_INITIAL_DTB_READONLY)
	INITCALL(fix_fdt);
#endif
#ifdef CFG_PRAM
	INITCALL(reserve_pram);
#endif
	INITCALL(reserve_round_4k);
	INITCALL(setup_relocaddr_from_bloblist);
	INITCALL(arch_reserve_mmu);
	INITCALL(reserve_video);
	INITCALL(reserve_trace);
	INITCALL(reserve_uboot);
	INITCALL(reserve_malloc);
	INITCALL(reserve_board);
	INITCALL(reserve_global_data);
	INITCALL(reserve_fdt);
#if CONFIG_IS_ENABLED(OF_BOARD_FIXUP) && \
    CONFIG_IS_ENABLED(OF_INITIAL_DTB_READONLY)
	INITCALL(reloc_fdt);
	INITCALL(fix_fdt);
#endif
	INITCALL(reserve_bootstage);
	INITCALL(reserve_bloblist);
	INITCALL(reserve_arch);
	INITCALL(reserve_stacks);
	INITCALL(dram_init_banksize);
	INITCALL(show_dram_config);    //打印DRAM的实际大小
	WATCHDOG_RESET();
	INITCALL(setup_bdinfo);
	INITCALL(display_new_sp);
	WATCHDOG_RESET();
#if !CONFIG_IS_ENABLED(OF_BOARD_FIXUP) || \
    !CONFIG_IS_ENABLED(INITIAL_DTB_READONLY)
	INITCALL(reloc_fdt);
#endif
	INITCALL(reloc_bootstage);
	INITCALL(reloc_bloblist);
	INITCALL(setup_reloc);
#if CONFIG_IS_ENABLED(X86) || CONFIG_IS_ENABLED(ARC)
	INITCALL(copy_uboot_to_ram);
	INITCALL(do_elf_reloc_fixups);
#endif
	INITCALL(clear_bss);
	/*
	 * Deregister all cyclic functions before relocation, so that
	 * gd->cyclic_list does not contain any references to pre-relocation
	 * devices. Drivers will register their cyclic functions anew when the
	 * devices are probed again.
	 *
	 * This should happen as late as possible so that the window where a
	 * watchdog device is not serviced is as small as possible.
	 */
	INITCALL(cyclic_unregister_all);
#if !CONFIG_IS_ENABLED(ARM) && !CONFIG_IS_ENABLED(SANDBOX)
	INITCALL(jump_to_copy);
#endif
}

可见此处对定时器,DDR,gd结构体,fdtdec,DM等重要硬件或数据结构进行了初始化,并在这个过程中在串口终端打印出了

U-Boot 2018.01 (Aug 30 2023 - 23:17:42 +0800) Allwinner Technology

CPU: Allwinner F Series (SUNIV) 
Model: PZK Nand Test 
DRAM: 64 MiB

U-boot porper阶段下的relocate_code()与relocate_vector():

为分析relocate_code()与relocate_vector()的过程,需要先了解U-boot proper的镜像组成,打开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>

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
#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
	. = 0x00000000;

	. = ALIGN(4);
	__image_copy_start = ADDR(.text);
	.text :
	{
		*(.vectors)
		CPUDIR/start.o (.text*)
	}

	/* This needs to come before *(.text*) */
	.efi_runtime : {
		__efi_runtime_start = .;
		*(.text.efi_runtime*)
		*(.rodata.efi_runtime*)
		*(.data.efi_runtime*)
		__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 __ARMV7_SECURE_BASE
#define __ARMV7_PSCI_STACK_IN_RAM
#else
#define __ARMV7_SECURE_BASE	CONFIG_ARMV7_SECURE_BASE
#endif

	.secure_text __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

	. = 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*)));
	}

	.efi_runtime_rel : {
		__efi_runtime_rel_start = .;
		*(.rel*.efi_runtime)
		*(.rel*.efi_runtime.*)
		__efi_runtime_rel_stop = .;
	}

	. = ALIGN(4);
	__image_copy_end = .;

	/*
	 * if CONFIG_USE_ARCH_MEMSET is not selected __bss_end - __bss_start
	 * needs to be a multiple of 4 and we overlay .bss with .rel.dyn
	 */
	.rel.dyn ALIGN(4) : {
		__rel_dyn_start = .;
		*(.rel*)
		__rel_dyn_end = .;
	}

	_end = .;
	_image_binary_end = .;

/*
 * These sections occupy the same memory, but their lifetimes do
 * not overlap: U-Boot initializes .bss only after applying dynamic
 * relocations and therefore after it doesn't need .rel.dyn any more.
 */
	.bss ADDR(.rel.dyn) (OVERLAY): {
		__bss_start = .;
		*(.bss*)
		. = ALIGN(4);
		__bss_end = .;
	}

	/DISCARD/ : { *(.dynsym) }
	/DISCARD/ : { *(.dynbss) }
	/DISCARD/ : { *(.dynstr*) }
	/DISCARD/ : { *(.dynamic*) }
	/DISCARD/ : { *(.plt*) }
	/DISCARD/ : { *(.interp*) }
	/DISCARD/ : { *(.gnu.hash) }
	/DISCARD/ : { *(.gnu*) }
	/DISCARD/ : { *(.ARM.exidx*) }
	/DISCARD/ : { *(.gnu.linkonce.armexidx.*) }
}

通过分析文件u-boot.lds,可以对U-boot proper镜像进行整体划分,得到下图:

image.png 有了镜像分布图以及相关程序段的位置信息,此时可以打开arch/arm/lib/relocate.S进行分析:

/* SPDX-License-Identifier: GPL-2.0+ */
/*
 *  relocate - common relocation function for ARM U-Boot
 *
 *  Copyright (c) 2013  Albert ARIBAUD <albert.u.boot@aribaud.net>
 */

#include <asm-offsets.h>
#include <asm/assembler.h>
#include <config.h>
#include <elf.h>
#include <linux/linkage.h>
#ifdef CONFIG_CPU_V7M
#include <asm/armv7m.h>
#endif

/*
 * Default/weak exception vectors relocation routine
 *
 * This routine covers the standard ARM cases: normal (0x00000000),
 * high (0xffff0000) and VBAR. SoCs which do not comply with any of
 * the standard cases must provide their own, strong, version.
 */

	.section	.text.relocate_vectors,"ax",%progbits

WEAK(relocate_vectors)

#ifdef CONFIG_CPU_V7M                          //考虑arm_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              //r1 = V7M_SCB_BASE(即内核中SCB寄存器组地址)
	str	r0, [r1, V7M_SCB_VTOR]         //SCB->VTOR = r1(即向SCB寄存器组中的VTOR寄存器写入r1)
#else
#ifdef CONFIG_HAS_VBAR                         //否则看看是否有类似VBAR的寄存器组,如果有,则如法炮制,去操作CP15协处理器
	/*
	 * 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	       //查看CP15 c1 中的V位(bit[13])
	ands	r2, r2, #(1 << 13)
        
	ldreq	r1, =0x00000000		       //如果V = 0,r1 = 0x00000000
	ldrne	r1, =0xFFFF0000		       //如果V = 1,r1 = 0xFFFF0000
        
        /* 拷贝向量表到新地址 */
	ldmia	r0!, {r2-r8,r10}
	stmia	r1!, {r2-r8,r10}
	ldmia	r0!, {r2-r8,r10}
	stmia	r1!, {r2-r8,r10}
#endif
#endif
	ret	lr                             //返回

ENDPROC(relocate_vectors)

/*
 * 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           //r3 = relocate_base函数入口地址
	ldr	r1, _image_copy_start_ofs   //r1 = relocate_base相对于_image_copy_start的偏移
	add	r1, r3			    //r1 = r1 + r3,计算得到实际_image_copy_start的地址
	subs	r4, r0, r1		    //r4 = r0 - r1,即gd->relocaddr - _image_copy_start
	beq	relocate_done		    //如果为0,则跳转至relocate_done,说明无需重定位
	ldr	r2, _image_copy_end_ofs     //r2 = relocate_base相对于_image_copy_end_ofs的偏移
	add	r2, r3			    //r2 = r1 + r2,计算得到实际_image_copy_end的地址
copy_loop:
	ldmia	r1!, {r10-r11}		    //从r1地址处加载内容到r10和r11,且r1每加载完一次就自增
	stmia	r0!, {r10-r11}		    //从r10和r11中保存内容到r0,且r0每保存完一次就自增
	cmp	r1, r2			    //比较r1与r2,看看r1是否已经从_image_copy_start走到_image_copy_end
	blo	copy_loop                   //如果r1 < r2,回到copy_loop循环

	/*
	 * fix .rel.dyn relocations
	 */
	ldr	r1, _rel_dyn_start_ofs      //r1 = relocate_base相对于_rel_dyn_start的偏移
	add	r2, r1, r3		    //r2 = r1+ r3,计算得到实际_rel_dyn_start的地址
	ldr	r1, _rel_dyn_end_ofs        //r1 = relocate_base相对于_rel_dyn_end的偏移
	add	r3, r1, r3		    //r3 = r1+ r3,计算得到实际_rel_dyn_end的地址
fixloop:
	ldmia	r2!, {r0-r1}		    //从r2地址处加载内容到r0和r1,且r2每加载完一次就自增
	and	r1, r1, #0xff               //r1 = r1 & 0xff(提取低8位)
	cmp	r1, #R_ARM_RELATIVE         //看看是不是R_ARM_RELATIVE
	bne	fixnext                     //如果不是,就准备去修正下一项,否则就是需要修正的项

	/* relative fix: increase location by offset */
	add	r0, r0, r4                  //r0 = r0 + r4,即r0加上(gd->relocaddr - _image_copy_start),补偿代码搬运后的相对偏移量
	ldr	r1, [r0]                    //r1 = *(r0),从新地址的表中取出内容
	add	r1, r1, r4                  //真正修改表中的项
	str	r1, [r0]                    //存回表中
fixnext:
	cmp	r2, r3                      //比较r2与r3,看看r2是否已经从_rel_dyn_start走到_rel_dyn_end
	blo	fixloop                     //如果r2 < r3,回到fix_loop循环

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	    //禁止 icache
	mcr	p15, 0, r0, c7, c10, 4	    /* drain write buffer */
#endif

	ret	lr                          //全部完成后返回

ENDPROC(relocate_code)

_image_copy_start_ofs:
	.word	__image_copy_start - relocate_code        //记录relocate_code函数相对于__image_copy_start标号的偏移
_image_copy_end_ofs:
	.word	__image_copy_end - relocate_code          //记录relocate_code函数相对于__image_copy_end标号的偏移
_rel_dyn_start_ofs:
	.word	__rel_dyn_start - relocate_code           //记录relocate_code函数相对于__rel_dyn_start标号的偏移
_rel_dyn_end_ofs:
	.word	__rel_dyn_end - relocate_code             //记录relocate_code函数相对于__rel_dyn_end标号的偏移

上述代码修复重定位后跳转表的前提是.rel.dyn段的数据结构为以下,参考include/elf.h:

 /* ARM relocs */
#define R_ARM_NONE		0	/* No reloc */
#define R_ARM_RELATIVE		23	/* Adjust by program base */

/* Relocation entry with implicit addend */
typedef struct {
	Elf32_Addr	r_offset;	/* offset of relocation */
	Elf32_Word	r_info;		/* symbol table index and type */
} Elf32_Rel;

U-boot porper阶段下的board_init_r():

位于common/board_r.c:

void board_init_r(gd_t *new_gd, ulong dest_addr)
{
	/*
	 * The pre-relocation drivers may be using memory that has now gone
	 * away. Mark serial as unavailable - this will fall back to the debug
	 * UART if available.
	 *
	 * Do the same with log drivers since the memory may not be available.
	 */
	gd->flags &= ~(GD_FLG_SERIAL_READY | GD_FLG_LOG_READY);

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

	initcall_run_r();

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

追踪函数initcall_run_r():

/*
 * Over time we hope to remove most of the driver-related init and do it
 * if/when the driver is later used.
 *
 * TODO: perhaps reset the watchdog in the initcall function after each call?
 */

static void initcall_run_r(void)
{
	/*
	 * Please do not add logic to this function (variables, if (), etc.).
	 * For simplicity it should remain an ordered list of function calls.
	 */
	INITCALL(initr_trace);
	INITCALL(initr_reloc);
	INITCALL(event_init);
	/* TODO: could x86/PPC have this also perhaps? */
#if CONFIG_IS_ENABLED(ARM) || CONFIG_IS_ENABLED(RISCV)
	INITCALL(initr_caches);
	/* Note: For Freescale LS2 SoCs, new MMU table is created in DDR.
	 *	 A temporary mapping of IFC high region is since removed,
	 *	 so environmental variables in NOR flash is not available
	 *	 until board_init() is called below to remap IFC to high
	 *	 region.
	 */
#endif
	INITCALL(initr_reloc_global_data);
#if CONFIG_IS_ENABLED(SYS_INIT_RAM_LOCK) && CONFIG_IS_ENABLED(E500)
	INITCALL(initr_unlock_ram_in_cache);
#endif
	INITCALL(initr_barrier);
	INITCALL(initr_malloc);
	INITCALL(log_init);
	INITCALL(initr_bootstage); /* Needs malloc() but has its own timer */
#if CONFIG_IS_ENABLED(CONSOLE_RECORD)
	INITCALL(console_record_init);
#endif
#if CONFIG_IS_ENABLED(SYS_HAS_NONCACHED_MEMORY)
	INITCALL(noncached_init);
#endif
	INITCALL(initr_of_live);
#if CONFIG_IS_ENABLED(DM)
	INITCALL(initr_dm);
#endif
#if CONFIG_IS_ENABLED(ADDR_MAP)
	INITCALL(init_addr_map);
#endif
#if CONFIG_IS_ENABLED(ARM) || CONFIG_IS_ENABLED(RISCV) || \
    CONFIG_IS_ENABLED(SANDBOX)
	INITCALL(board_init);	/* Setup chipselects */
#endif
	/*
	 * TODO: printing of the clock inforamtion of the board is now
	 * implemented as part of bdinfo command. Currently only support for
	 * davinci SOC's is added. Remove this check once all the board
	 * implement this.
	 */
#if CONFIG_IS_ENABLED(CLOCKS)
	INITCALL(set_cpu_clk_info);                //设置CPU主频
#endif
	INITCALL(initr_lmb);
#if CONFIG_IS_ENABLED(EFI_LOADER)
	INITCALL(efi_memory_init);
#endif
#if CONFIG_IS_ENABLED(BINMAN_FDT)
	INITCALL(initr_binman);
#endif
#if CONFIG_IS_ENABLED(FSP_VERSION2)
	INITCALL(arch_fsp_init_r);
#endif
	INITCALL(initr_dm_devices);
	INITCALL(stdio_init_tables);
	INITCALL(serial_initialize);
	INITCALL(initr_announce);
	INITCALL(dm_announce);
#if CONFIG_IS_ENABLED(WDT)
	INITCALL(initr_watchdog);
#endif
	WATCHDOG_RESET();
	INITCALL(arch_initr_trap);
#if CONFIG_IS_ENABLED(BOARD_EARLY_INIT_R)
	INITCALL(board_early_init_r);
#endif
	WATCHDOG_RESET();
#if CONFIG_IS_ENABLED(POST)
	INITCALL(post_output_backlog);
#endif
	WATCHDOG_RESET();
#if CONFIG_IS_ENABLED(PCI_INIT_R) && CONFIG_IS_ENABLED(SYS_EARLY_PCI_INIT)
	/*
	 * Do early PCI configuration _before_ the flash gets initialised,
	 * because PCU resources are crucial for flash access on some boards.
	 */
	INITCALL(pci_init);
#endif
#if CONFIG_IS_ENABLED(ARCH_EARLY_INIT_R)
	INITCALL(arch_early_init_r);
#endif
	INITCALL(power_init_board);
#if CONFIG_IS_ENABLED(MTD_NOR_FLASH)
	INITCALL(initr_flash);
#endif
	WATCHDOG_RESET();
#if CONFIG_IS_ENABLED(PPC) || CONFIG_IS_ENABLED(M68K) || CONFIG_IS_ENABLED(X86)
	/* initialize higher level parts of CPU like time base and timers */
	INITCALL(cpu_init_r);
#endif
#if CONFIG_IS_ENABLED(EFI_LOADER)
	INITCALL(efi_init_early);
#endif
#if CONFIG_IS_ENABLED(CMD_NAND)
	INITCALL(initr_nand);
#endif
#if CONFIG_IS_ENABLED(CMD_ONENAND)
	INITCALL(initr_onenand);
#endif
#if CONFIG_IS_ENABLED(MMC)
	INITCALL(initr_mmc);                       //初始化MMC控制器,打印"MMC: XXXXX"
#endif
#if CONFIG_IS_ENABLED(XEN)
	INITCALL(xen_init);
#endif
#if CONFIG_IS_ENABLED(PVBLOCK)
	INITCALL(initr_pvblock);
#endif
	INITCALL(initr_env);                      //初始化环境变量,"*** Warning - bad CRC, using default environment"打印自此
#if CONFIG_IS_ENABLED(SYS_MALLOC_BOOTPARAMS)
	INITCALL(initr_malloc_bootparams);
#endif
	WATCHDOG_RESET();
	INITCALL(cpu_secondary_init_r);
#if CONFIG_IS_ENABLED(ID_EEPROM)
	INITCALL(mac_read_from_eeprom);
#endif
	INITCALL_EVT(EVT_SETTINGS_R);
	WATCHDOG_RESET();
#if CONFIG_IS_ENABLED(PCI_INIT_R) && !CONFIG_IS_ENABLED(SYS_EARLY_PCI_INIT)
	/*
	 * Do pci configuration
	 */
	INITCALL(pci_init);
#endif
	INITCALL(stdio_add_devices);
	INITCALL(jumptable_init);
#if CONFIG_IS_ENABLED(API)
	INITCALL(api_init);
#endif
	INITCALL(console_init_r);	         //彻底初始化控制台,绑定串口stdin,stdout,stdout,并打印输出串口地址
#if CONFIG_IS_ENABLED(DISPLAY_BOARDINFO_LATE)
	INITCALL(console_announce_r);
	INITCALL(show_board_info);
#endif
	/* miscellaneous arch-dependent init */
#if CONFIG_IS_ENABLED(ARCH_MISC_INIT)
	INITCALL(arch_misc_init);
#endif
	/* miscellaneous platform-dependent init */
#if CONFIG_IS_ENABLED(MISC_INIT_R)
	INITCALL(misc_init_r);
#endif
	WATCHDOG_RESET();
#if CONFIG_IS_ENABLED(CMD_KGDB)
	INITCALL(kgdb_init);
#endif
	INITCALL(interrupt_init);
#if defined(CONFIG_MICROBLAZE) || defined(CONFIG_M68K)
	INITCALL(timer_init);		/* initialize timer */
#endif
	INITCALL(initr_status_led);
	INITCALL(initr_boot_led_blink);
	/* PPC has a udelay(20) here dating from 2002. Why? */
#if CONFIG_IS_ENABLED(BOARD_LATE_INIT)
	INITCALL(board_late_init);
#endif
#if CONFIG_IS_ENABLED(PCI_ENDPOINT)
	INITCALL(pci_ep_init);
#endif
#if CONFIG_IS_ENABLED(NET) || CONFIG_IS_ENABLED(NET_LWIP)
	WATCHDOG_RESET();
	INITCALL(initr_net);                            //初始化网络,"Net: No ethernet found."信息出处
#endif
#if CONFIG_IS_ENABLED(POST)
	INITCALL(initr_post);
#endif
	WATCHDOG_RESET();
	INITCALL_EVT(EVT_LAST_STAGE_INIT);
#if defined(CFG_PRAM)
	INITCALL(initr_mem);
#endif
	INITCALL(initr_boot_led_on);
	INITCALL(run_main_loop);                        //进入主循环run_main_loop()
}

可见board_init_r()进一步初始化单板上的硬件系统,并最终进入主循环run_main_loop(),这一阶段下的终端打印出了:

MMC: SUNXI SD/MMC: 0 
SPI-NAND: MX35LF1GE4AB is found size: 128MB. 
*** Warning - bad CRC, using default environment

In: serial@1c25000 
Out: serial@1c25000 
Err: serial@1c25000 
Net: No ethernet found.

U-boot porper阶段下的run_main_loop():

位于文件common/board_r.c:

static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOX
	sandbox_main_loop_init();
#endif

	event_notify_null(EVT_MAIN_LOOP);

	/* main_loop() can return to retry autoboot, if so just run it again */
	for (;;)
		main_loop();                         //调用该函数
	return 0;
}

追踪函数main_loop():

/* 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");    //记录启动阶段

        /* 设置ver版本环境变量(如果有) */
	if (IS_ENABLED(CONFIG_VERSION_VARIABLE))
		env_set("ver", version_string);  /* set version variable */

	cli_init();                                                   //初始化CLI,并根据.config中的配置选择初始化HUSH_MODERN_PARSER(新版HUSH解析器)还是HUSH_OLD_PARSER(老版HUSH解析器)

        /* 运行preboot命令(如果有) */
	if (IS_ENABLED(CONFIG_USE_PREBOOT))
		run_preboot_environment_command();

        /* TFTP更新(如果有) */
	if (IS_ENABLED(CONFIG_UPDATE_TFTP))
		update_tftp(0UL, NULL, NULL);
        
        /* EFI升级Capsule(如果有) */
	if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) {
		/* efi_init_early() already called */
		if (efi_init_obj_list() == EFI_SUCCESS)
			efi_launch_capsules();
	}

	process_button_cmds();                                      //检查按键命令

	s = bootdelay_process();                                    //进行倒计时并打印提示信息
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);

	autoboot_command(s);                                        //检查用户按键输入并倒计时,无输入则执行自动启动指令

        /* 使用bootstd启动(如果有) */
	/* if standard boot if enabled, assume that it will be able to boot */
	if (IS_ENABLED(CONFIG_BOOTSTD_PROG)) {
		int ret;

		ret = bootstd_prog_boot();
		printf("Standard boot failed (err=%dE)\n", ret);
		panic("Failed to boot");
	}

	cli_loop();                                                 //如果auto boot被打断了,则进入CLI命令行交互循环

	panic("No CLI available");                                  //失败,进入panic
}

cli_loop()命令行交互系统中,当用户输入类似bootm之类的指令后,系统将启动。

从main_loop()到cmd_process():

在函数cli_loop()中,根据GD_FLG_HUSH_MODERN_PARSERGD_FLG_HUSH_OLD_PARSER标志位判断使用新版hush shell还是旧版hush shell,由于.config中使用的是GD_FLG_HUSH_OLD_PARSER,因此查看分支parse_file_outer(),其位于文件common/cli.c

void cli_loop(void)
{
    bootstage_mark(BOOTSTAGE_ID_ENTER_CLI_LOOP);
#if CONFIG_IS_ENABLED(HUSH_PARSER)
    if (gd->flags & GD_FLG_HUSH_MODERN_PARSER)
            parse_and_run_file();
    else if (gd->flags & GD_FLG_HUSH_OLD_PARSER)
            parse_file_outer();

    /* 出现不可预知的错误,则在此处循环 */
    printf("Problem\n");
    /* This point is never reached */
    for (;;);
#elif defined(CONFIG_CMDLINE)
    cli_simple_loop();
#else
    printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n");
#endif /*CONFIG_HUSH_PARSER*/
}

追踪函数parse_file_outer(),位于文件common/cli_hush.c

#ifndef __U_BOOT__
static int parse_file_outer(FILE *f)
#else
int parse_file_outer(void)
#endif
{
    int rcode;

    /* 归一化用户输入层至input数据结构,实现透明读取 */
    struct in_str input;
#ifndef __U_BOOT__
    setup_file_in_str(&input, f);                    //读文件输入归一化处理
#else
    setup_file_in_str(&input);                       //读缓冲输入归一化处理
#endif
      /* 解析用户输入 */
      rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);

    return rcode == -2 ? last_return_code : rcode;
}

因此hush shell的核心解析循环位于函数parse_stream_outer(),追踪其原型:

/* most recursion does not come through here, the exeception is
* from builtin_source() */
static int parse_stream_outer(struct in_str *inp, int flag)
{

    struct p_context ctx;
    o_string temp=NULL_O_STRING;
    int rcode;
#ifdef __U_BOOT__
    int code = 1;
#endif
    do {
           /* 初始化上下文结构体ctx */
            ctx.type = flag;
            initialize_context(&ctx);

            /* 建立词典,通过词典快速分类用户输入 */
            update_ifs_map();
            if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset((uchar *)";$&|", 0);
            inp->promptmode=1;

            /* 正式解析用户输入内容 */
            rcode = parse_stream(&temp, &ctx, inp, flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n');

#ifdef __U_BOOT__
            if (rcode == 1) flag_repeat = 0;
#endif
            /* 提示语法错误 */
            if (rcode != 1 && ctx.old_flag != 0) {
                    syntax();
#ifdef __U_BOOT__
                    flag_repeat = 0;
#endif
            }

            /* 如果解析结果正常,则完成命令链表构造 */
            if (rcode != 1 && ctx.old_flag == 0) {

                    /* 解析命令并封装完整节点至ctx->list_head */
                    done_word(&temp, &ctx);
                    done_pipe(&ctx,PIPE_SEQ);

                    /* 逐一执行链表中的命令串 */
#ifndef __U_BOOT__
                    run_list(ctx.list_head);
#else
                    code = run_list(ctx.list_head);

                   /* 判断命令返回值 */
                    if (code == -2) {	/* exit */
                            b_free(&temp);
                            code = 0;
                            /* XXX hackish way to not allow exit from main loop */
                            if (inp->peek == file_peek) {
                                    printf("exit not allowed from main input shell.\n");
                                    continue;
                            }
                            /*
                             * DANGER
                             * Return code -2 is special in this context,
                             * it indicates exit from inner pipe instead
                             * of return code itself, the return code is
                             * stored in 'last_return_code' variable!
                             * DANGER
                             */
                            return -2;
                    }
                    if (code == -1)
                        flag_repeat = 0;
#endif
            } else {
                    /* 否则解析结果失败,清空上下文 */
                    if (ctx.old_flag != 0) {
                            free(ctx.stack);
                            b_reset(&temp);
                    }

                   /* 命令被终止提示 */
#ifdef __U_BOOT__
                    if (inp->__promptme == 0) printf("<INTERRUPT>\n");
                    inp->__promptme = 1;
#endif
                   /* 析构内容 */
                    temp.nonnull = 0;
                    temp.quote = 0;
                    inp->p = NULL;
                    free_pipe_list(ctx.list_head,0);
            }

           /* 释放临时空间 */
            b_free(&temp);

    /* loop on syntax errors, return on EOF */
    } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) &&
            (inp->peek != static_peek || b_peek(inp)));

#ifndef __U_BOOT__
    return 0;
#else
    return (code != 0) ? 1 : 0;
#endif /* __U_BOOT__ */
}

分析可知,函数parse_stream()真正对用户输入进行语义识别,并将结果放入链表,最后通过run_list()函数真正去执行那些命令,故追踪函数run_list():

/* Select which version we will use */
static int run_list(struct pipe *pi)
{
	int rcode=0;
#ifndef __U_BOOT__
	if (fake_mode==0) {
#endif
		rcode = run_list_real(pi);
#ifndef __U_BOOT__
	}
#endif
	/* free_pipe_list has the side effect of clearing memory
	 * In the long run that function can be merged with run_list_real,
	 * but doing that now would hobble the debugging effort. */
	free_pipe_list(pi,0);
	return rcode;
}

发现封装了一层run_list_real(),追踪函数run_list_real():

static int run_list_real(struct pipe *pi)
{
    char *save_name = NULL;
    char **list = NULL;
    char **save_list = NULL;
    struct pipe *rpipe;
    int flag_rep = 0;
#ifndef __U_BOOT__
    int save_num_progs;
#endif
    int rcode=0, flag_skip=1;
    int flag_restore = 0;
    int if_code=0, next_if_code=0;  /* need double-buffer to handle elif */
    reserved_style rmode, skip_more_in_this_rmode=RES_XXXX;

     /* 针对 fon x in xxx命令行语法的处理流程 */
     /* check syntax for "for" */
     for (rpipe = pi; rpipe; rpipe = rpipe->next) {
            if ((rpipe->r_mode == RES_IN ||
                rpipe->r_mode == RES_FOR) &&
                (rpipe->next == NULL)) {
                            syntax();
#ifdef __U_BOOT__
                            flag_repeat = 0;
#endif
                            return 1;
            }
            if ((rpipe->r_mode == RES_IN &&
                    (rpipe->next->r_mode == RES_IN &&
                    rpipe->next->progs->argv != NULL))||
                    (rpipe->r_mode == RES_FOR &&
                    rpipe->next->r_mode != RES_IN)) {
                            syntax();
#ifdef __U_BOOT__
                            flag_repeat = 0;
#endif
                            return 1;
            }
    }

     /* 针对正常命令的处理流程 */
     for (; pi; pi = (flag_restore != 0) ? rpipe : pi->next) {    //逐一迭代链表pi的各个节点
            if (pi->r_mode == RES_WHILE || pi->r_mode == RES_UNTIL ||
                    pi->r_mode == RES_FOR) {
#ifdef __U_BOOT__
                            /* check Ctrl-C */
                            ctrlc();
                            if ((had_ctrlc())) {
                                    return 1;
                            }
#endif
                            flag_restore = 0;
                            if (!rpipe) {
                                    flag_rep = 0;
                                    rpipe = pi;
                            }
            }
            rmode = pi->r_mode;
            debug_printf("rmode=%d  if_code=%d  next_if_code=%d skip_more=%d\n", rmode, if_code, next_if_code, skip_more_in_this_rmode);
            if (rmode == skip_more_in_this_rmode && flag_skip) {
                    if (pi->followup == PIPE_SEQ) flag_skip=0;
                    continue;
            }
            flag_skip = 1;
            skip_more_in_this_rmode = RES_XXXX;
            if (rmode == RES_THEN || rmode == RES_ELSE) if_code = next_if_code;
            if (rmode == RES_THEN &&  if_code) continue;
            if (rmode == RES_ELSE && !if_code) continue;
            if (rmode == RES_ELIF && !if_code) break;
            if (rmode == RES_FOR && pi->num_progs) {
                    if (!list) {
                            /* if no variable values after "in" we skip "for" */
                            if (!pi->next->progs->argv) continue;
                            /* create list of variable values */
                            list = make_list_in(pi->next->progs->argv,
                                    pi->progs->argv[0]);
                            save_list = list;
                            save_name = pi->progs->argv[0];
                            pi->progs->argv[0] = NULL;
                            flag_rep = 1;
                    }
                    if (!(*list)) {
                            free(pi->progs->argv[0]);
                            free(save_list);
                            list = NULL;
                            flag_rep = 0;
                            pi->progs->argv[0] = save_name;
#ifndef __U_BOOT__
                            pi->progs->glob_result.gl_pathv[0] =
                                    pi->progs->argv[0];
#endif
                            continue;
                    } else {
                            /* insert new value from list for variable */
                            free(pi->progs->argv[0]);
                            pi->progs->argv[0] = *list++;
#ifndef __U_BOOT__
                            pi->progs->glob_result.gl_pathv[0] =
                                    pi->progs->argv[0];
#endif
                    }
            }
            if (rmode == RES_IN) continue;
            if (rmode == RES_DO) {
                    if (!flag_rep) continue;
            }
            if (rmode == RES_DONE) {
                    if (flag_rep) {
                            flag_restore = 1;
                    } else {
                            rpipe = NULL;
                    }
            }
            if (pi->num_progs == 0) continue;
#ifndef __U_BOOT__
            save_num_progs = pi->num_progs; /* save number of programs */
#endif
            rcode = run_pipe_real(pi);                           //真正运行该命令节点
            debug_printf("run_pipe_real returned %d\n",rcode);   //打印运行结果
#ifndef __U_BOOT__
            if (rcode!=-1) {
                    /* We only ran a builtin: rcode was set by the return value
                     * of run_pipe_real(), and we don't need to wait for anything. */
            } else if (pi->followup==PIPE_BG) {
                    /* XXX check bash's behavior with nontrivial pipes */
                    /* XXX compute jobid */
                    /* XXX what does bash do with attempts to background builtins? */
                    insert_bg_job(pi);
                    rcode = EXIT_SUCCESS;
            } else {
                    if (interactive) {
                            /* move the new process group into the foreground */
                            if (tcsetpgrp(shell_terminal, pi->pgrp) && errno != ENOTTY)
                                    perror_msg("tcsetpgrp-3");
                            rcode = checkjobs(pi);
                            /* move the shell to the foreground */
                            if (tcsetpgrp(shell_terminal, getpgid(0)) && errno != ENOTTY)
                                    perror_msg("tcsetpgrp-4");
                    } else {
                            rcode = checkjobs(pi);
                    }
                    debug_printf("checkjobs returned %d\n",rcode);
            }
            last_return_code=rcode;
#else
            if (rcode < -1) {
                    last_return_code = -rcode - 2;
                    return -2;	/* exit */
            }
            last_return_code = rcode;
#endif
#ifndef __U_BOOT__
            pi->num_progs = save_num_progs; /* restore number of programs */
#endif
            if ( rmode == RES_IF || rmode == RES_ELIF )
                    next_if_code=rcode;  /* can be overwritten a number of times */
            if (rmode == RES_WHILE)
                    flag_rep = !last_return_code;
            if (rmode == RES_UNTIL)
                    flag_rep = last_return_code;
            if ( (rcode==EXIT_SUCCESS && pi->followup==PIPE_OR) ||
                 (rcode!=EXIT_SUCCESS && pi->followup==PIPE_AND) )
                    skip_more_in_this_rmode=rmode;
#ifndef __U_BOOT__
            checkjobs(NULL);
#endif
    }
    return rcode;
}

该函数中run_pipe_real()是各指令节点真正被执行的地方,追入查看该函数:

static int run_pipe_real(struct pipe *pi)
{
    int i;
#ifndef __U_BOOT__
    int nextin, nextout;
    int pipefds[2];				/* pipefds[0] is for reading */
    struct child_prog *child;
    struct built_in_command *x;
    char *p;
# if __GNUC__
    /* Avoid longjmp clobbering */
    (void) &i;
    (void) &nextin;
    (void) &nextout;
    (void) &child;
# endif
#else
    int nextin;
    int flag = do_repeat ? CMD_FLAG_REPEAT : 0;
    struct child_prog *child;
    char *p;
# if __GNUC__
    /* Avoid longjmp clobbering */
    (void) &i;
    (void) &nextin;
    (void) &child;
# endif
#endif	/* __U_BOOT__ */

    nextin = 0;
#ifndef __U_BOOT__
    pi->pgrp = -1;
#endif

    /* Check if this is a simple builtin (not part of a pipe).
     * Builtins within pipes have to fork anyway, and are handled in
     * pseudo_exec.  "echo foo | read bar" doesn't work on bash, either.
     */
    if (pi->num_progs == 1) child = & (pi->progs[0]);
#ifndef __U_BOOT__
    if (pi->num_progs == 1 && child->group && child->subshell == 0) {
            int squirrel[] = {-1, -1, -1};
            int rcode;
            debug_printf("non-subshell grouping\n");
            setup_redirects(child, squirrel);
            /* XXX could we merge code with following builtin case,
             * by creating a pseudo builtin that calls run_list_real? */
            rcode = run_list_real(child->group);
            restore_redirects(squirrel);
#else
            if (pi->num_progs == 1 && child->group) {
            int rcode;
            debug_printf("non-subshell grouping\n");
            rcode = run_list_real(child->group);
#endif
            return rcode;
    } else if (pi->num_progs == 1 && pi->progs[0].argv != NULL) {
            for (i=0; is_assignment(child->argv[i]); i++) { /* nothing */ }
            if (i!=0 && child->argv[i]==NULL) {
                    /* assignments, but no command: set the local environment */
                    for (i=0; child->argv[i]!=NULL; i++) {

                            /* Ok, this case is tricky.  We have to decide if this is a
                             * local variable, or an already exported variable.  If it is
                             * already exported, we have to export the new value.  If it is
                             * not exported, we need only set this as a local variable.
                             * This junk is all to decide whether or not to export this
                             * variable. */
                            int export_me=0;
                            char *name, *value;
                            name = xstrdup(child->argv[i]);
                            debug_printf("Local environment set: %s\n", name);
                            value = strchr(name, '=');
                            if (value)
                                    *value=0;
#ifndef __U_BOOT__
                            if ( get_local_var(name)) {
                                    export_me=1;
                            }
#endif
                            free(name);
                            p = insert_var_value(child->argv[i]);
                            set_local_var(p, export_me);
                            if (p != child->argv[i]) free(p);
                    }
                    return EXIT_SUCCESS;   /* don't worry about errors in set_local_var() yet */
            }
            for (i = 0; is_assignment(child->argv[i]); i++) {
                    p = insert_var_value(child->argv[i]);
#ifndef __U_BOOT__
                    putenv(strdup(p));
#else
                    set_local_var(p, 0);
#endif
                    if (p != child->argv[i]) {
                            child->sp--;
                            free(p);
                    }
            }
            if (child->sp) {
                    char * str = NULL;

                    str = make_string(child->argv + i,
                                      child->argv_nonnull + i);
                    parse_string_outer(str, FLAG_EXIT_FROM_LOOP | FLAG_REPARSING);
                    free(str);
                    return last_return_code;
            }
#ifndef __U_BOOT__
            for (x = bltins; x->cmd; x++) {
                    if (strcmp(child->argv[i], x->cmd) == 0 ) {
                            int squirrel[] = {-1, -1, -1};
                            int rcode;
                            if (x->function == builtin_exec && child->argv[i+1]==NULL) {
                                    debug_printf("magic exec\n");
                                    setup_redirects(child,NULL);
                                    return EXIT_SUCCESS;
                            }
                            debug_printf("builtin inline %s\n", child->argv[0]);
                            /* XXX setup_redirects acts on file descriptors, not FILEs.
                             * This is perfect for work that comes after exec().
                             * Is it really safe for inline use?  Experimentally,
                             * things seem to work with glibc. */
                            setup_redirects(child, squirrel);

                            child->argv += i;  /* XXX horrible hack */
                            rcode = x->function(child);
                            /* XXX restore hack so free() can work right */
                            child->argv -= i;
                            restore_redirects(squirrel);
                    }
                    return rcode;
            }
#else
            /* check ";", because ,example , argv consist from
             * "help;flinfo" must not execute
             */
            if (strchr(child->argv[i], ';')) {
                    printf("Unknown command '%s' - try 'help' or use "
                                    "'run' command\n", child->argv[i]);
                    return -1;
            }

            /* 处理指令 */
            return cmd_process(flag, child->argc - i, child->argv + i,
                               &flag_repeat, NULL);
#endif
    }
#ifndef __U_BOOT__

    for (i = 0; i < pi->num_progs; i++) {
            child = & (pi->progs[i]);

            /* pipes are inserted between pairs of commands */
            if ((i + 1) < pi->num_progs) {
                    if (pipe(pipefds)<0) perror_msg_and_die("pipe");
                    nextout = pipefds[1];
            } else {
                    nextout=1;
                    pipefds[0] = -1;
            }

            /* XXX test for failed fork()? */
            if (!(child->pid = fork())) {
                    /* Set the handling for job control signals back to the default.  */
                    signal(SIGINT, SIG_DFL);
                    signal(SIGQUIT, SIG_DFL);
                    signal(SIGTERM, SIG_DFL);
                    signal(SIGTSTP, SIG_DFL);
                    signal(SIGTTIN, SIG_DFL);
                    signal(SIGTTOU, SIG_DFL);
                    signal(SIGCHLD, SIG_DFL);

                    close_all();

                    if (nextin != 0) {
                            dup2(nextin, 0);
                            close(nextin);
                    }
                    if (nextout != 1) {
                            dup2(nextout, 1);
                            close(nextout);
                    }
                    if (pipefds[0]!=-1) {
                            close(pipefds[0]);  /* opposite end of our output pipe */
                    }

                    /* Like bash, explicit redirects override pipes,
                     * and the pipe fd is available for dup'ing. */
                    setup_redirects(child,NULL);

                    if (interactive && pi->followup!=PIPE_BG) {
                            /* If we (the child) win the race, put ourselves in the process
                             * group whose leader is the first process in this pipe. */
                            if (pi->pgrp < 0) {
                                    pi->pgrp = getpid();
                            }
                            if (setpgid(0, pi->pgrp) == 0) {
                                    tcsetpgrp(2, pi->pgrp);
                            }
                    }

                    pseudo_exec(child);
            }

            /* put our child in the process group whose leader is the
               first process in this pipe */
            if (pi->pgrp < 0) {
                    pi->pgrp = child->pid;
            }
            /* Don't check for errors.  The child may be dead already,
             * in which case setpgid returns error code EACCES. */
            setpgid(child->pid, pi->pgrp);

            if (nextin != 0)
                    close(nextin);
            if (nextout != 1)
                    close(nextout);

            /* If there isn't another process, nextin is garbage
               but it doesn't matter */
            nextin = pipefds[0];
    }
#endif
    return -1;
}

发现cmd_process()是最终的终点,胜利在望,继续追入:

enum command_ret_t cmd_process(int flag, int argc, char *const argv[],
                           int *repeatable, ulong *ticks)
{
    enum command_ret_t rc = CMD_RET_SUCCESS;
    struct cmd_tbl *cmdtp;

    /* 命令追踪选项,显示命令调用流程 */
#if defined(CONFIG_SYS_XTRACE)
    char *xtrace;

    xtrace = env_get("xtrace");
    if (xtrace) {
            puts("+");
            for (int i = 0; i < argc; i++) {
                    puts(" ");
                    puts(argv[i]);
            }
            puts("\n");
    }
#endif

    /* 尝试在命令列表中查找该命令 */
    cmdtp = find_cmd(argv[0]);
    if (cmdtp == NULL) {
            printf("Unknown command '%s' - try 'help'\n", argv[0]);    //未找到该命令,打印帮助信息
            return 1;
    }

    /* 找到了,首先检查最大入参 */
    if (argc > cmdtp->maxargs)
            rc = CMD_RET_USAGE;                                        //返回用法错误

#if defined(CONFIG_CMD_BOOTD)
    /* 避免"bootd"被递归调用 */
    else if (cmdtp->cmd == do_bootd) {
            if (flag & CMD_FLAG_BOOTD) {
                    puts("'bootd' recursion detected\n");
                    rc = CMD_RET_FAILURE;
            } else {
                    flag |= CMD_FLAG_BOOTD;
            }
    }
#endif

    /* 如果到目前位置都正常,则执行命令*/
    if (!rc) {
            int newrep;

            if (ticks)
                    *ticks = get_timer(0);
            rc = cmd_call(cmdtp, flag, argc, argv, &newrep);    //命令的调用入口
            if (ticks)
                    *ticks = get_timer(*ticks);
            *repeatable &= newrep;
    }

    /* 如果命令的用法错误了,打印该命令的帮助信息 */
    if (rc == CMD_RET_USAGE)
            rc = cmd_usage(cmdtp);

    /* 返回值 */
    return rc;
}

分析可知,find_cmd()函数从命令列表中找出与用户输入相匹配的对应命令,经过入参检查无误后,将调用该命令在命令列表中对应的入口函数,同时传入用户参数,例如对于命令bootz 0x80008000 - 0x80C00000,经过sh解析器得到argv[0]bootz,通过find_cmd(argv[0])从命令列表中查询相应指令,匹配后返回do_bootz()函数指针cmdtp,后续经过参数校验合法后调用cmdtp,并附带入参数。

通过命令bootz启动内核:

在源码中找到函数do_bootz()的定义,位于文件cmd/bootz.c

int do_bootz(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
{
    struct bootm_info bmi;                                  //bootm信息结构体
    int ret;

    /* Consume 'bootz' */
    argc--; argv++;                                         //省略"bootz"程序名参数。

    if (bootz_start(cmdtp, flag, argc, argv, &images))      //解析用户传入参数以初始化images结构头
            return 1;

    /*
     * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
     * disable interrupts ourselves
     */
    bootm_disable_interrupts();                             //失能中断

    images.os.os = IH_OS_LINUX;                             //设置镜像的OS类型为IH_OS_LINUX

    bootm_init(&bmi);                                       //初始化bmi结构体
    if (argc)
            bmi.addr_img = argv[0];                         //提取Zimage地址
    if (argc > 1)
            bmi.conf_ramdisk = argv[1];                     //提取ramdisk地址
    if (argc > 2)
            bmi.conf_fdt = argv[2];                         //提取fdt地址
    bmi.cmd_name = "bootz";

    ret = bootz_run(&bmi);                                  //正式运行bootz启动流程

    return ret;                                             //只有启动失败才返回
}

先看看函数bootz_start()干了什么:

/*
 * zImage booting support
 */
static int bootz_start(struct cmd_tbl *cmdtp, int flag, int argc,
		       char *const argv[], struct bootm_headers *images)
{
    ulong zi_start, zi_end;
    struct bootm_info bmi;
    int ret;

    bootm_init(&bmi);                                             //初始化bmi结构体
    if (argc)
            bmi.addr_img = argv[0];                               //提取Zimage地址
    if (argc > 1)
            bmi.conf_ramdisk = argv[1];                           //提取ramdisk地址
    if (argc > 2)
            bmi.conf_fdt = argv[2];                               //提取fdt地址
    /* do not set up argc and argv[] since nothing uses them */

    ret = bootm_run_states(&bmi, BOOTM_STATE_START);              //根据提取到的bmi信息,配合BOOTM_STATE_START去执行bootm在相应阶段的操作,bootz作为bootm的前端需要帮助bootm完成一些事情

    /* Setup Linux kernel zImage entry point */
    if (!argc) {
            images->ep = image_load_addr;                          //使用默认加载地址
            debug("*  kernel: default image load address = 0x%08lx\n",
                            image_load_addr);
    } else {
            images->ep = hextoul(argv[0], NULL);                   //使用用户从命令行传入的加载地址
            debug("*  kernel: cmdline image address = 0x%08lx\n",
                    images->ep);
    }

    ret = bootz_setup(images->ep, &zi_start, &zi_end);             //检查Zimage镜像合法性,并提取zi_start与zi_end地址,不包含自解压程序
    if (ret != 0)
            return 1;                                              //失败返回1

    lmb_reserve(images->ep, zi_end - zi_start, LMB_NONE);

    /*
     * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
     * have a header that provide this informaiton.
     */
    if (bootm_find_images(image_load_addr, cmd_arg1(argc, argv),   //复用bootm接口去检查ramdisk,fdt,FPGA / FIT image等组件的合法性
                          cmd_arg2(argc, argv), images->ep,
                          zi_end - zi_start))
            return 1;

    return 0;
}

这个过程中的bootz_setup(),位于文件:arch/arm/lib/zimage.c:

// SPDX-License-Identifier: GPL-2.0+
/*
 * Copyright (C) 2016
 * Ladislav Michl <ladis@linux-mips.org>
 *
 * bootz code:
 * Copyright (C) 2012 Marek Vasut <marek.vasut@gmail.com>
 */
#include <image.h>

#define	LINUX_ARM_ZIMAGE_MAGIC	0x016f2818
#define	BAREBOX_IMAGE_MAGIC	0x00786f62

struct arm_z_header {
    uint32_t	code[9];
    uint32_t	zi_magic;
    uint32_t	zi_start;
    uint32_t	zi_end;
} __attribute__ ((__packed__));                                    //捏造Zimage头部结构信息

int bootz_setup(ulong image, ulong *start, ulong *end)
{
    struct arm_z_header *zi = (struct arm_z_header *)image;

    if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC &&                  //检查ZImage幻数
        zi->zi_magic != BAREBOX_IMAGE_MAGIC) {
            if (!IS_ENABLED(CONFIG_XPL_BUILD))
                    puts("zimage: Bad magic!\n");                  //失败打印提示
            return 1;
    }

    *start = zi->zi_start;                                         //提取zi_start信息
    *end = zi->zi_end;                                             //提取zi_end信息
    if (!IS_ENABLED(CONFIG_XPL_BUILD))
            printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n",
                   image, *start, *end);

    return 0;
}

可以理解为函数bootz_start()检查了Zimage,ramdisk,fdt,FPGA/FIT Image的合法性,为后面的bootz_run()函数正式启动内核做好准备,因此接下来分析函数bootz_run(),其位于文件boot/bootm.c:

int bootz_run(struct bootm_info *bmi)
{
    return boot_run(bmi, "bootz", 0);                          //调用boot_run()
}

查看函数boot_run(),在同一文件:

int boot_run(struct bootm_info *bmi, const char *cmd, int extra_states)
{
    int states;

    bmi->cmd_name = cmd;
    states = BOOTM_STATE_MEASURE | BOOTM_STATE_OS_PREP |
            BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO;      //设置bootm阶段标志
    if (IS_ENABLED(CONFIG_SYS_BOOT_RAMDISK_HIGH))
            states |= BOOTM_STATE_RAMDISK;
    states |= extra_states;

    return bootm_run_states(bmi, states);                    //调用bootm_run_states,并传入bmi与states
}

最后查看函数bootm_run_states(),一个事件驱动状态机,依然位于同一文件:

int bootm_run_states(struct bootm_info *bmi, int states)
{
    struct bootm_headers *images = bmi->images;
    boot_os_fn *boot_fn;
    ulong iflag = 0;
    int ret = 0, need_boot_fn;

    images->state |= states;

    /*
     * Work through the states and see how far we get. We stop on
     * any error.
     */
    if (states & BOOTM_STATE_START)                     //如果BOOTM_STATE_START阶段标志位被置高,执行该阶段对应任务
            ret = bootm_start();

    if (!ret && (states & BOOTM_STATE_PRE_LOAD))        //如果前一阶段执行结果无误,且BOOTM_STATE_PRE_LOAD阶段标志位被置高,执行该阶段对应任务
            ret = bootm_pre_load(bmi->addr_img);

    if (!ret && (states & BOOTM_STATE_FINDOS))          //如果前一阶段执行结果无误,且BOOTM_STATE_FINDOS阶段标志位被置高,执行该阶段对应任务
            ret = bootm_find_os(bmi->cmd_name, bmi->addr_img);  

    if (!ret && (states & BOOTM_STATE_FINDOTHER)) {
            ulong img_addr;

            img_addr = bmi->addr_img ? hextoul(bmi->addr_img, NULL)
                    : image_load_addr;
            ret = bootm_find_other(img_addr, bmi->conf_ramdisk,
                                   bmi->conf_fdt);
    }

    if (IS_ENABLED(CONFIG_MEASURED_BOOT) && !ret &&    //如果前一阶段执行结果无误,且CONFIG_MEASURED_BOOT阶段标志位被置高,执行该阶段对应任务
        (states & BOOTM_STATE_MEASURE))
            bootm_measure(images);                     //安全启动相关,对内核镜像,设备树,initrd做哈希/签名校验等。

    /* Load the OS */
    if (!ret && (states & BOOTM_STATE_LOADOS)) {
            iflag = bootm_disable_interrupts();
            ret = bootm_load_os(images, 0);
            if (ret && ret != BOOTM_ERR_OVERLAP)
                    goto err;
            else if (ret == BOOTM_ERR_OVERLAP)
                    ret = 0;
    }

    /* Relocate the ramdisk */
#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
    if (!ret && (states & BOOTM_STATE_RAMDISK)) {
            ulong rd_len = images->rd_end - images->rd_start;

            ret = boot_ramdisk_high(images->rd_start, rd_len,
                                    &images->initrd_start,
                                    &images->initrd_end);
            if (!ret) {
                    env_set_hex("initrd_start", images->initrd_start);
                    env_set_hex("initrd_end", images->initrd_end);
            }
    }
#endif
#if CONFIG_IS_ENABLED(OF_LIBFDT) && CONFIG_IS_ENABLED(LMB)
    if (!ret && (states & BOOTM_STATE_FDT)) {
            boot_fdt_add_mem_rsv_regions(images->ft_addr);
            ret = boot_relocate_fdt(&images->ft_addr, &images->ft_len);
    }
#endif

    /* From now on, we need the OS boot function */
    if (ret)
            return ret;
            
    boot_fn = bootm_os_get_boot_func(images->os.os);    //根据要启动的镜像类型去获取正确的启动函数
    
    need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |   //再次检查本次是否需要启动
                    BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
                    BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
                    
    /* 如果没有合法的启动方式,且本次启动命令“states”异常,则报错退出 */
    if (boot_fn == NULL && need_boot_fn) {
            if (iflag)
                    enable_interrupts();
            printf("ERROR: booting os '%s' (%d) is not supported\n",
                   genimg_get_os_name(images->os.os), images->os.os);
            bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
            return 1;
    }

    /* Call various other states that are not generally used */
    if (!ret && (states & BOOTM_STATE_OS_CMDLINE))
            ret = boot_fn(BOOTM_STATE_OS_CMDLINE, bmi);
    if (!ret && (states & BOOTM_STATE_OS_BD_T))
            ret = boot_fn(BOOTM_STATE_OS_BD_T, bmi);
            
    if (!ret && (states & BOOTM_STATE_OS_PREP))         //如果前一阶段执行结果无误,且BOOTM_STATE_OS_PREP阶段标志位被置高,执行该阶段对应任务
    {
            int flags = 0;
            /* For Linux OS do all substitutions at console processing */
            if (images->os.os == IH_OS_LINUX)
                    flags = BOOTM_CL_ALL;
            ret = bootm_process_cmdline_env(flags);        //整理内核启动命令行,完善内核环境变量等操作
            if (ret) {
                    printf("Cmdline setup failed (err=%d)\n", ret);
                    ret = CMD_RET_FAILURE;
                    goto err;
            }
            ret = boot_fn(BOOTM_STATE_OS_PREP, bmi);
    }

#ifdef CONFIG_TRACE
    /* Pretend to run the OS, then run a user command */
    if (!ret && (states & BOOTM_STATE_OS_FAKE_GO))        //如果前一阶段执行结果无误,且BOOTM_STATE_OS_FAKE_GO阶段标志位被置高,执行该阶段对应任务
    {
            char *cmd_list = env_get("fakegocmd");

            ret = boot_selected_os(BOOTM_STATE_OS_FAKE_GO, bmi, boot_fn);    //假启动,用于调试
            if (!ret && cmd_list)
                    ret = run_command_list(cmd_list, -1, 0);
    }
#endif

    /* Check for unsupported subcommand. */
    if (ret) {
            printf("subcommand failed (err=%d)\n", ret);
            return ret;
    }

    /* Now run the OS! We hope this doesn't return */
    if (!ret && (states & BOOTM_STATE_OS_GO))        //如果前一阶段执行结果无误,且BOOTM_STATE_OS_GO阶段标志位被置高,执行该阶段对应任务
            ret = boot_selected_os(BOOTM_STATE_OS_GO, bmi, boot_fn);    //通过boot_fn去真启动所选中的系统镜像

    /* Deal with any fallout */
err:
    if (iflag)
            enable_interrupts();

    if (ret == BOOTM_ERR_UNIMPLEMENTED) {
            bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL);
    } else if (ret == BOOTM_ERR_RESET) {
            printf("Resetting the board...\n");
            reset_cpu();
    }

    return ret;
}

分析可知,boot_selected_os()最终会去调用boot_fn()来启动内核,函数boot_selected_os()位于文件boot/bootm_os.c内容如下:

int boot_selected_os(int state, struct bootm_info *bmi, boot_os_fn *boot_fn)
{
    arch_preboot_os();                                 //正式启动前架构相关操作,用户自定义
    board_preboot_os();                                //正式启动前板级相关操作,用户自定义

    boot_fn(state, bmi);                               //调用函数boot_fn,传入参数state与bmi

    /* Stand-alone may return when 'autostart' is 'no' */
    if (bmi->images->os.type == IH_TYPE_STANDALONE ||
        IS_ENABLED(CONFIG_SANDBOX) ||
        state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */
            return 0;
            
    /* 内核不应该返回,否则出错 */
    bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED);
    debug("\n## Control returned to monitor - resetting...\n");

    return BOOTM_ERR_RESET;
}

最后回过头看看函数指针boot_fn的来历,函数bootm_os_get_boot_func()位于文件boot/bootm_os.c:

boot_os_fn *bootm_os_get_boot_func(int os)
{
	return boot_os[os];                         //根据os号查表
}

boot_os内容如下:

static boot_os_fn *boot_os[] = {
	[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX
	[IH_OS_LINUX] = do_bootm_linux,             //显然是这我们所需要的启动函数
#endif
#ifdef CONFIG_BOOTM_NETBSD
	[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_BOOTM_RTEMS
	[IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
	[IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_BOOTM_PLAN9)
	[IH_OS_PLAN9] = do_bootm_plan9,
#endif
#if defined(CONFIG_BOOTM_VXWORKS) && \
	(defined(CONFIG_PPC) || defined(CONFIG_ARM) || defined(CONFIG_RISCV))
	[IH_OS_VXWORKS] = do_bootm_vxworks,
#endif
#if defined(CONFIG_CMD_ELF)
	[IH_OS_QNX] = do_bootm_qnxelf,
#endif
#ifdef CONFIG_INTEGRITY
	[IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
#ifdef CONFIG_BOOTM_OPENRTOS
	[IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
#ifdef CONFIG_BOOTM_OPTEE
	[IH_OS_TEE] = do_bootm_tee,
#endif
#ifdef CONFIG_BOOTM_EFI
	[IH_OS_EFI] = do_bootm_efi,
#endif
#if defined(CONFIG_BOOTM_ELF)
	[IH_OS_ELF] = do_bootm_elf,
#endif
};

溯源函数do_bootm_linux(),位于文件:arch/arm/lib/bootm.c:

/* Main Entry point for arm bootm implementation
 *
 * Modeled after the powerpc implementation
 * DIFFERENCE: Instead of calling prep and go at the end
 * they are called if subcommand is equal 0.
 */
int do_bootm_linux(int flag, struct bootm_info *bmi)
{
    struct bootm_headers *images = bmi->images;

    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)     //ARM平台无需处理这些命令,直接返回
            return -1;

    if (flag & BOOTM_STATE_OS_PREP) {                                    //如果是准备阶段
            boot_prep_linux(images);                                     //设置内核运行环境,如清理缓存,设置内核启动参数,保留内存区域等工作
            return 0;
    }

    if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {           //如果是跳转启动阶段
            boot_jump_linux(images, flag);                               //跳去执行linux
            return 0;
    }

    /* 如果没有显示指定flag中的标志位,则先准备好内核环境,后直接跳去执行linux */
    boot_prep_linux(images);
    boot_jump_linux(images, flag);
    return 0;
}

因此最后的最后,我们来到了函数boot_jump_linux(),位于文件arch/arm/lib/bootm.c:

static void boot_jump_linux(struct bootm_headers *images, int flag)
{
#ifdef CONFIG_ARM64                                                       //如果是ARM64架构,显然我们目前不是
    void (*kernel_entry)(void *fdt_addr, void *res0, void *res1,
                    void *res2);
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO);

    kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
                            void *res2))images->ep;

    debug("## Transferring control to Linux (at address %lx)...\n",
            (ulong) kernel_entry);
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);

    announce_and_cleanup(fake);

    if (!fake) {
#ifdef CONFIG_ARMV8_PSCI
            armv8_setup_psci();
#endif
            do_nonsec_virt_switch();

            update_os_arch_secondary_cores(images->os.arch);

#ifdef CONFIG_ARMV8_SWITCH_TO_EL1
            armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,
                                (u64)switch_to_el1, ES_TO_AARCH64);
#else
            if ((IH_ARCH_DEFAULT == IH_ARCH_ARM64) &&
                (images->os.arch == IH_ARCH_ARM))
                    armv8_switch_to_el2(0, (u64)gd->bd->bi_arch_number,
                                        (u64)images->ft_addr, 0,
                                        (u64)images->ep,
                                        ES_TO_AARCH32);
            else
                    armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0,
                                        images->ep,
                                        ES_TO_AARCH64);
#endif
    }
#else    //#ifdef CONFIG_ARM64
    unsigned long machid = gd->bd->bi_arch_number;                      //获取机器id
    char *s;                                                            //环境变量指针
    void (*kernel_entry)(int zero, int arch, uint params);              //定义32位内核入口函数指针
    unsigned long r2;                                                   //第2个入参
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO);                         //是否是模拟启动?

    kernel_entry = (void (*)(int, int, uint))images->ep;                //赋值内核入口地址
#ifdef CONFIG_CPU_V7M
    ulong addr = (ulong)kernel_entry | 1;                               //ARMV7M CPU要求地址最低位置1
    kernel_entry = (void *)addr;                                        //更新函数指针
#endif
    s = env_get("machid");                                              //从环境变量获取machid
    if (s) {
            if (strict_strtoul(s, 16, &machid) < 0) {                   //解析字符串为数值
                    debug("strict_strtoul failed!\n");                  //打印调试信息
                    return;                                             //失败返回
            }
            printf("Using machid 0x%lx from environment\n", machid);    //打印使用的machid
    }

    debug("## Transferring control to Linux (at address %08lx)" \
            "...\n", (ulong) kernel_entry);                             //打印调试信息,显示内核入口地址
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);                                //标记bootstage为RUN_OS
    announce_and_cleanup(fake);                                         //打印信息,清理环境,如果fake=1只打印不跳转

    if (CONFIG_IS_ENABLED(OF_LIBFDT) && images->ft_len)                 //如果存在FDT
            r2 = (unsigned long)images->ft_addr;                        //通过r2传递FDT地址
    else
            r2 = gd->bd->bi_boot_params;                                //否则r2传递启动参数地址

    if (!fake) {                                                        //如果不是模拟启动
#ifdef CONFIG_ARMV7_NONSEC
            if (armv7_boot_nonsec()) {                                  //判断是否使用非安全模式启动
                    armv7_init_nonsec();                                //初始化非安全环境
                    secure_ram_addr(_do_nonsec_entry)(kernel_entry,
                                                      0, machid, r2);   //跳转到非安全入口执行内核
            } else
#endif
            kernel_entry(0, machid, r2);                                //调用内核入口函数,传递zero,machid,r2,U-boot使命到此完结
    }
#endif
}

至此整个U-boot从SPL到启动linux的整个过程分析完毕。