前言
好不容易有时间空闲下来,就想着把之前心心念念的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:
即通过
find命令从srctree目录下找出符合条件的文件打印至标准输出,再由管道输入xargs命令进行构造参数,最后传给rm -rf,执行构建清除。当在顶层编译时,srctree的定义即为.(当前目录)。
第二条指令,执行defconfig:
由于xxxx_defconfig的前缀在不断发生变化,因此通过搜索后缀config找到匹配的目标为
搜索
build变量的定义,位于srctree/scripts/Kbuild.include内
因此实际的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内
依赖
outputmakefile也位于顶层Makefile内
由此可知
licheepi_nano_defconfig目标终究会调用./scripts/Makefile.build,并传入参数obj=scripts/kconfig licheepi_nano_defconfig。
注意到Makefile.build中没有与licheepi_nano_defconfig相匹配的目标,因此先分析obj参数,看源码可知obj参数被做如下处理:
经过转换后,去除特定前缀,则
src=obj=scripts/kconfig。
进一步的,
src被用于组成kbuild-file,展开后得知kbuild-file=./scripts/kconfig/Makefile。并且在57行的include $(kbuild-file)中将文件./scripts/kconfig/Makefile包含进来了。
打开./scripts/kconfig/Makefile文件搜索,即可找到licheepi_nano_defconfig对应的目标如下:
此处第一条命令把licheepi_nano_defconfig文件经过$(CPP)工具预处理生成 generated_defconfig。
之后第二条命令通过 sed去掉每行开头的空白字符。
最后第三条命令调用 $<,即$(obj)/conf并结合KConfig来生成完整的配置信息,最终写入.config文件。
第三条指令:执行make:
U-Boot的默认目标是_all,因此在顶层Makefile中可以搜索到关键字:
此处150行的
_all:没有指定目标所需的依赖,通过搜索找到225行处的以下定义:
得知
_all依赖于all,故查找目标all:
all又依赖于目标.binman_stamp,而目标.binman_stamp又依赖于目标$(INPUTS-y),在980行处看到如下定义:
可知这里便是根据具体配置信息将依赖添加入
$(INPUTS-y),从而使得Makefile能够正确构建出u-boot.bin,spl/u-boot-spl.bin等文件,然而我们的目标是u-boot-sunxi-with-spl.bin,继续搜索关键字,在arch/arm/dts/sunxi-u-boot.dtsi中得到以下内容:
得知其是通过
binman工具整合而来,且依赖于spl/sunxi-spl.bin u-boot-nodtb等文件,查找spl/sunxi-spl.bin依赖,在顶层Makefile中得到以下内容:
可知其依赖于
spl/u-boot-spl,追溯可得:
该命令等价于:
make obj=spl -f ./scripts/Makefile.xpl all
于是在./scripts/Makefile.xpl中,发现目标all的定义如下:
追踪当前文件中定义的
INPUTS-y,得到:
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模式,关闭MMU和D 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_f与board_init_r,同时关注宏CONFIG_XPL_BUILD对ctr0.S中程序在spl阶段与U-boot porper阶段下的编译分支。可以通过编译产物spl/u-boot-spl.map与u-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镜像进行整体划分,得到下图:
有了镜像分布图以及相关程序段的位置信息,此时可以打开
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_PARSER与GD_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的整个过程分析完毕。