嗨喽,大家好,我是程序猿老王,程序猿老王就是我。
今天给大家全面的分析一下u-boot启动流程。整理这篇文章花费时间较长,中间很长时间未更新,希望这篇文章对大家有所帮助。
本章主要是详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的。
在看本章之前,个人建议先去看一下前几篇文章。对u-boot的开发环境搭建、u-boot整体移植和u-boot下网络调试有一点了解后,再来看本篇文章,这样可能比较容易看明白。
Linux系统开发环境搭建篇:ubuntu交叉编译工具链安装
Linux系统开发环境搭建 u-boot移植篇:u-boot移植:详细讲解移植u-boot.2022.10版本到imx6ull开发板
u-boot网络移植于调试篇:详细讲解u-boot之网络移植与调试
本章主要是详细的分析一下uboot的启动流程,理清uboot是如何启动的。通过对uboot启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。另外,通过分析uboot的启动流程可以了解Linux内核是如何被启动的。
一、u-boot启动详细函数调用流程
首先给大家先看一下,u-boot启动从入口函数到启动内核的详细函数调用流程于层级关系图,对u-boot启动的整体有一个快速了解,后面回详细介绍各个函数的作用。
u-boot:启动详细的代码调用流程
u-boot.lds:(arch/arm/cpu/u-boot.lds)|-->_start:(arch/arm/lib/vectors.S)|-->reset(arch/arm/cpu/armv7/start.S) |-->save_boot_params(arch/arm/cpu/armv7/start.S)/*将引导参数保存到内存中*/|-->save_boot_params_ret(arch/arm/cpu/armv7/start.S)|-->cpu_init_cp15(arch/arm/cpu/armv7/start.S)/*初始化*/|-->cpu_init_crit(arch/arm/cpu/armv7/start.S)|-->lowlevel_init(arch/arm/cpu/armv7/lowlevel_init.S)|-->_main(arch/arm/lib/crt0.S)|-->board_init_f_alloc_reserve(common/init/board_init.c)/*为u-boot的gd结构体分配空间*/|-->board_init_f_init_reserve(common/init/board_init.c) /*将gd结构体清零*/|-->board_init_f(common/board_f.c)|-->initcall_run_list(include/initcall.h) /*初始化序列函数*/|-->init_sequence_f[](common/board_f.c) /* 初始化序列函数数组 */|-->board_early_init_f(board/freescale/mx6ull_toto/mx6ull_toto.c)/*初始化串口的IO配置*/|-->timer_init(arch/arm/imx-common/timer.c) /*初始化内核定时器,为uboot提供时钟节拍*/|-->init_baud_rate(common/board_f.c) /*初始化波特率*/|-->serial_init(drivers/serial/serial.c) /*初始化串口通信设置*/|-->console_init_f(common/console.c) /*初始化控制台*/|-->...|-->relocate_code(arch/arm/lib/relocate.S) /*主要完成镜像拷贝和重定位*/|-->relocate_vectors(arch/arm/lib/relocate.S)/*重定位向量表*/|-->board_init_r(common/board_r.c)/*板级初始化*/|-->initcall_run_list(include/initcall.h)/*初始化序列函数*/|-->init_sequence_r[](common/board_f.c)/*序列函数*/|-->initr_reloc(common/board_r.c) /*设置 gd->flags,标记重定位完成*/|-->serial_initialize(drivers/serial/serial-uclass.c)/*初始化串口*/|-->serial_init(drivers/serial/serial-uclass.c) /*初始化串口*/|-->initr_mmc(common/board_r.c) /*初始化emmc*/|-->mmc_initialize(drivers/mmc/mmc.c)|-->mmc_do_preinit(drivers/mmc/mmc.c)|-->mmc_start_init(drivers/mmc/mmc.c)|-->console_init_r(common/console.c) /*初始化控制台*/|-->interrupt_init(arch/arm/lib/interrupts.c) /*初始化中断*/|-->initr_net(common/board_r.c) /*初始化网络设备*/|-->eth_initialize(net/eth-uclass.c)|-->eth_common_init(net/eth_common.c)|-->phy_init(drivers/net/phy/phy.c)|-->uclass_first_device_check(drivers/core/uclass.c)|-->uclass_find_first_device(drivers/core/uclass.c)|-->device_probe(drivers/core/device.c)|-->device_of_to_plat(drivers/core/device.c)|-->drv->of_to_plat|-->fecmxc_of_to_plat(drivers/net/fec_mxc.c)/*解析设备树信息*/|-->device_get_uclass_id(drivers/core/device.c)|-->uclass_pre_probe_device(drivers/core/uclass.c)|-->drv->probe(dev)/*drivers/net/fec_mxc.c*/U_BOOT_DRIVER(fecmxc_gem) = {.name = "fecmxc",.id = UCLASS_ETH,.of_match = fecmxc_ids,.of_to_plat = fecmxc_of_to_plat,.probe = fecmxc_probe,.remove = fecmxc_remove,.ops = &fecmxc_ops,.priv_auto = sizeof(struct fec_priv),.plat_auto = sizeof(struct eth_pdata),};|-->fecmxc_probe(drivers/net/fec_mxc.c)/*探测和初始化*/|-->fec_get_miibus(drivers/net/fec_mxc.c)|-->mdio_alloc(drivers/net/fec_mxc.c)|-->bus->read = fec_phy_read;|-->bus->write = fec_phy_write;|-->mdio_register(common/miiphyutil.c)|-->fec_mii_setspeed(drivers/net/fec_mxc.c)|-->fec_phy_init(drivers/net/fec_mxc.c)|-->device_get_phy_addr(drivers/net/fec_mxc.c)|-->phy_connect(drivers/net/phy/phy.c)|-->phy_find_by_mask(drivers/net/phy/phy.c)|-->bus->reset(bus)|-->get_phy_device_by_mask(drivers/net/phy/phy.c)|-->create_phy_by_mask(drivers/net/phy/phy.c)|-->phy_device_create(drivers/net/phy/phy.c)|-->phy_probe(drivers/net/phy/phy.c)|-->phy_connect_dev(drivers/net/phy/phy.c)|-->phy_reset(drivers/net/phy/phy.c)|-->phy_config(drivers/net/phy/phy.c)|-->board_phy_config(drivers/net/phy/phy.c)|-->phydev->drv->config(phydev)/*drivers/net/phy/smsc.c*/static struct phy_driver lan8710_driver = {.name = "SMSC LAN8710/LAN8720",.uid = 0x0007c0f0,.mask = 0xffff0,.features = PHY_BASIC_FEATURES,.config = &genphy_config_aneg,.startup = &genphy_startup,.shutdown = &genphy_shutdown,};|-->genphy_config_aneg(drivers/net/phy/phy.c)|-->phy_reset(需要手动调用)(drivers/net/phy/phy.c)|-->genphy_setup_forced(drivers/net/phy/phy.c)|-->genphy_config_advert(drivers/net/phy/phy.c)|-->genphy_restart_aneg(drivers/net/phy/phy.c)|-->uclass_post_probe_device(drivers/core/uclass.c)|-->uc_drv->post_probe(drivers/core/uclass.c)/*net/eth-uclass.c*/UCLASS_DRIVER(ethernet) = {.name = "ethernet",.id = UCLASS_ETH,.post_bind = eth_post_bind,.pre_unbind = eth_pre_unbind,.post_probe = eth_post_probe,.pre_remove = eth_pre_remove,.priv_auto = sizeof(struct eth_uclass_priv),.per_device_auto = sizeof(struct eth_device_priv),.flags = DM_UC_FLAG_SEQ_ALIAS,};|-->eth_post_probe(net/eth-uclass.c)|-->eth_write_hwaddr(drivers/core/uclass.c)|-->...|-->run_main_loop(common/board_r.c)/*主循环,处理命令*/|-->main_loop(common/main.c)|-->bootdelay_process(common/autoboot.c) /*读取环境变量bootdelay和bootcmd的内容*/|-->autoboot_command(common/autoboot.c) /*倒计时按下执行,没有操作执行bootcmd的参数*/|-->abortboot(common/autoboot.c)|-->printf("Hit any key to stop autoboot: %2d ", bootdelay);/*到这里就是我们看到uboot延时3s启动内核的地方*/|-->cli_loop(common/cli.c) /*倒计时按下space键,执行用户输入命令*/
二、程序入口
U-Boot 源码文件众多,我们如何知道最开始的启动文件(程序入口)是哪个呢?程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口,链接脚本为arch/arm/cpu/u-boot.lds,它描述了如何生成最终的二进制文件,其中就包含程序入口。
三、链接脚本 u-boot.lds 详解
1.u-boot.lds
u-boot.lds,文件所在位置arch/arm/cpu/u-boot.lds
/* SPDX-License-Identifier: GPL-2.0+ */
/** Copyright (c) 2004-2008 Texas Instruments** (C) Copyright 2002* Gary Jennejohn, DENX Software Engineering, <garyj@denx.de>*/#include <config.h>
#include <asm/psci.h>/* 指定输出可执行文件: "elf 32位 小端格式 arm指令" */
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/* 指定输出可执行文件的目标架构:"arm" */
OUTPUT_ARCH(arm)
/* 指定输出可执行文件的起始地址为:"_start" */
ENTRY(_start)
SECTIONS
{
#ifndef CONFIG_CMDLINE/DISCARD/ : { *(__u_boot_list_2_cmd_*) }
#endif
#if defined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)/** If CONFIG_ARMV7_SECURE_BASE is true, secure code will not* bundle with u-boot, and code offsets are fixed. Secure zone* only needs to be copied from the loading address to* CONFIG_ARMV7_SECURE_BASE, which is the linking and running* address for secure code.** If CONFIG_ARMV7_SECURE_BASE is undefined, the secure zone will* be included in u-boot address space, and some absolute address* were used in secure code. The absolute addresses of the secure* code also needs to be relocated along with the accompanying u-boot* code.** So DISCARD is only for CONFIG_ARMV7_SECURE_BASE.*//DISCARD/ : { *(.rel._secure*) }
#endif/* * 指定可执行文件(image)的全局入口地址,通常都放在ROM(flash)0x0位置* 设置 0 的原因是 arm 内核的处理器,上电后默认是从 0x00000000 处启动*/. = 0x00000000;. = ALIGN(4); ``````````/* 中断向量表 */.text :{*(.__image_copy_start) /* u-boot 的设计中需要将 u-boot 的镜像拷贝到 ram(sdram,ddr....)中执行,这里表示复制的开始地址 */*(.vectors) /* 中断向量表 */CPUDIR/start.o (.text*) /* CPUDIR/start.o 中的所有.text 段 */}/* This needs to come before *(.text*) */.__efi_runtime_start : {*(.__efi_runtime_start)}.efi_runtime : {*(.text.efi_runtime*)*(.rodata.efi_runtime*)*(.data.efi_runtime*)}.__efi_runtime_stop : {*(.__efi_runtime_stop)}.text_rest :{*(.text*)}#ifdef CONFIG_ARMV7_NONSEC/* Align the secure section only if we're going to use it in situ */.__secure_start
#ifndef CONFIG_ARMV7_SECURE_BASEALIGN(CONSTANT(COMMONPAGESIZE))
#endif: {KEEP(*(.__secure_start))}#ifndef CONFIG_ARMV7_SECURE_BASE
#define CONFIG_ARMV7_SECURE_BASE
#define __ARMV7_PSCI_STACK_IN_RAM
#endif.secure_text CONFIG_ARMV7_SECURE_BASE :AT(ADDR(.__secure_start) + SIZEOF(.__secure_start)){*(._secure.text)}.secure_data : AT(LOADADDR(.secure_text) + SIZEOF(.secure_text)){*(._secure.data)}#ifdef CONFIG_ARMV7_PSCI.secure_stack ALIGN(ADDR(.secure_data) + SIZEOF(.secure_data),CONSTANT(COMMONPAGESIZE)) (NOLOAD) :
#ifdef __ARMV7_PSCI_STACK_IN_RAMAT(ADDR(.secure_stack))
#elseAT(LOADADDR(.secure_data) + SIZEOF(.secure_data))
#endif{KEEP(*(.__secure_stack_start))/* Skip addreses for stack */. = . + CONFIG_ARMV7_PSCI_NR_CPUS * ARM_PSCI_STACK_SIZE;/* Align end of stack section to page boundary */. = ALIGN(CONSTANT(COMMONPAGESIZE));KEEP(*(.__secure_stack_end))#ifdef CONFIG_ARMV7_SECURE_MAX_SIZE/** We are not checking (__secure_end - __secure_start) here,* as these are the load addresses, and do not include the* stack section. Instead, use the end of the stack section* and the start of the text section.*/ASSERT((. - ADDR(.secure_text)) <= CONFIG_ARMV7_SECURE_MAX_SIZE,"Error: secure section exceeds secure memory size");
#endif}#ifndef __ARMV7_PSCI_STACK_IN_RAM/* Reset VMA but don't allocate space if we have secure SRAM */. = LOADADDR(.secure_stack);
#endif#endif.__secure_end : AT(ADDR(.__secure_end)) {*(.__secure_end)LONG(0x1d1071c); /* Must output something to reset LMA */}
#endif/* * .rodata 段,确保是以4字节对齐 */. = ALIGN(4);.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }/* * data段,确保是以4字节对齐*/. = ALIGN(4);.data : {*(.data*)}. = ALIGN(4);. = .;/* * u_boot_list 段,确保是以 4 字节对齐 * 这里存放的都是 u_boot_list 中的函数*/. = ALIGN(4);__u_boot_list : {KEEP(*(SORT(__u_boot_list*)));}. = ALIGN(4);.efi_runtime_rel_start :{*(.__efi_runtime_rel_start)}.efi_runtime_rel : {*(.rel*.efi_runtime)*(.rel*.efi_runtime.*)}.efi_runtime_rel_stop :{*(.__efi_runtime_rel_stop)}/* * __image_copy_end 也是个符号表示一个结束地址,确保是以4字节对齐 */. = ALIGN(4);.image_copy_end : /* u-boot 的设计中需要将 u-boot 的镜像拷贝到ram(sdram,ddr....)中执行,这里表示复制的结束地址 */{*(.__image_copy_end)}.rel_dyn_start : /* .rel.dyn 段起始地址 */{*(.__rel_dyn_start)}.rel.dyn : {*(.rel*)}.rel_dyn_end : /* .rel.dyn 段结束地址 */{*(.__rel_dyn_end)}.end :{*(.__end)}_image_binary_end = .; /* bin文件结束地址 *//** Deprecated: this MMU section is used by pxa at present but* should not be used by new boards/CPUs.*/. = ALIGN(4096);.mmutable : {*(.mmutable)}/** Compiler-generated __bss_start and __bss_end, see arch/arm/lib/bss.c* __bss_base and __bss_limit are for linker only (overlay ordering)*/.bss_start __rel_dyn_start (OVERLAY) : { /* .bss段起始地址 */KEEP(*(.__bss_start));__bss_base = .;}.bss __bss_base (OVERLAY) : {*(.bss*). = ALIGN(4);__bss_limit = .;}.bss_end __bss_limit (OVERLAY) : { /* .bss段结束地址 */KEEP(*(.__bss_end));}.dynsym _image_binary_end : { *(.dynsym) }.dynbss : { *(.dynbss) }.dynstr : { *(.dynstr*) }.dynamic : { *(.dynamic*) }.plt : { *(.plt*) }.interp : { *(.interp*) }.gnu.hash : { *(.gnu.hash) }.gnu : { *(.gnu*) }.ARM.exidx : { *(.ARM.exidx*) }.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
通过上面的分析可以看出: 由于在链接脚本中规定了文件start.o(对应于start.S)作为整个uboot的起始点,因此启动uboot时会执行首先执行start.S。 一般来说,内存空间可分为代码段、数据段、全局变量段、未初始化变量区、栈区、堆区等.其中,栈区由指针SP决定,堆区实质上是由C代码实现的,其它段则由编译器决定.从上面的分析可以看出,从0x00000000地址开始,编译器首先将代码段放在最开始的位置,然后是数据段,然后是bss段(未初始化变量区)。
2.u-boot.map
u-boot.map 是uboot的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,下面打开 u-boot.map,查看各个段的起始地址和结束分别是多少;
内存配置名称 来源 长度 属性
*default* 0x00000000 0xffffffff链结器命令稿和内存映射段 .text 的地址设置为 0x878000000x00000000 . = 0x00x00000000 . = ALIGN (0x4).text 0x87800000 0x3a8*(.__image_copy_start).__image_copy_start0x87800000 0x0 arch/arm/lib/sections.o0x87800000 __image_copy_start*(.vectors).vectors 0x87800000 0x2e8 arch/arm/lib/vectors.o0x87800000 _start0x87800020 _undefined_instruction0x87800024 _software_interrupt0x87800028 _prefetch_abort0x8780002c _data_abort0x87800030 _not_used0x87800034 _irq0x87800038 _fiq0x87800040 IRQ_STACK_START_INarch/arm/cpu/armv7/start.o(.text*).text 0x878002e8 0xc0 arch/arm/cpu/armv7/start.o0x878002e8 reset0x878002ec save_boot_params_ret0x87800328 c_runtime_cpu_setup0x87800338 save_boot_params0x8780033c cpu_init_cp150x8780039c cpu_init_crit
...
从u-boot.map映射文件种,可以知道__image_copy_start为0X87800000,而.text的起始地址也是0X87800000,.vectors段的起始地址也是0X87800000,可以得出各个段的地址关系表,如下;
变量名 | 地址 | 描述 |
---|---|---|
__image_copy_start | 0x87800000 | u-boot拷贝的起始地址 |
__image_copy_end | 0x87850ff0 | u-boot拷贝的结束地址 |
.vectors | 0x87800000 | 中断向量表的起始地址 |
.text | 0x878002e8 | .text段的起始地址 |
__rel_dyn_start | 0x87850ff0 | .rel_dyn段的起始地址 |
__rel_dyn_end | 0x8785cf30 | .rel_dyn段的结束地址 |
_image_binary_end | 0x8785cf30 | 镜像结束地址 |
__bss_start | 0x87850ff0 | .bss段的起始地址 |
__bss_end | 0x878585c0 | .bss段的结束地址 |
注:表中的变量除了__image_copy_start以外,其他的变量值每次编译的时候可能会变化。修改uboot 代码、配置等都会影响到这些值。所以,一切以实际值为准!
四、_start函数详解
从链接文件(u-boot.lds) 中知道了程序入口是 _start,_start 在文件 arch/arm/lib/vectors.S 中有定义,具体代码如下;
/**************************************************************************** Symbol _start is referenced elsewhere, so make it global***************************************************************************/.globl _start/**************************************************************************** Vectors have their own section so linker script can map them easily***************************************************************************/.section ".vectors", "ax"#if defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
/** Various SoCs need something special and SoC-specific up front in* order to boot, allow them to set that in their boot0.h file and then* use it here.** To allow a boot0 hook to insert a 'special' sequence after the vector* table (e.g. for the socfpga), the presence of a boot0 hook supresses* the below vector table and assumes that the vector table is filled in* by the boot0 hook. The requirements for a boot0 hook thus are:* (1) defines '_start:' as appropriate* (2) inserts the vector table using ARM_VECTORS as appropriate*/
#include <asm/arch/boot0.h>
#else/**************************************************************************** Exception vectors as described in ARM reference manuals** Uses indirect branch to allow reaching handlers anywhere in memory.***************************************************************************/_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endifARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
/**************************************************************************** Indirect vectors table** Symbols referenced here must be defined somewhere else***************************************************************************/.globl _reset.globl _undefined_instruction /* 未定义指令异常 */.globl _software_interrupt /* 软中断异常 */.globl _prefetch_abort /* 预取异常 */.globl _data_abort /* 数据异常 */.globl _not_used /* 未使用 */.globl _irq /* 外部中断请求IRQ */.globl _fiq /* 快束中断请求FIQ */...
从u-boot.map映射文件可以得出.vectors段的最开始就是_start,而从_start定义我们可以知道首先是跳转到reset函数,再设置中断向量表。
五、reset函数详解
1.reset函数讲解
从程序入口_start定义中得出,_start中首先是跳转到reset函数,reset函数在文件arch/arm/cpu/armv7/start.S中有定义,具体代码如下;
/*************************************************************************** Startup Code (reset vector)** Do important init only if we don't start from memory!* Setup memory and board specific bits prior to relocation.* Relocate armboot to ram. Setup stack.**************************************************************************/.globl reset.globl save_boot_params_ret.type save_boot_params_ret,%function
#ifdef CONFIG_ARMV7_LPAE.global switch_to_hypervisor_ret
#endifreset:/* Allow the board to save important registers */b save_boot_params
save_boot_params_ret:...
reset函数只有一句跳转语句,直接跳转到了save_boot_params函数,而save_boot_params函数同样定义在start.S里面,定义如下:
/*************************************************************************** void save_boot_params(u32 r0, u32 r1, u32 r2, u32 r3)* __attribute__((weak));** Stack pointer is not yet initialized at this moment* Don't save anything to stack even if compiled with -O0**************************************************************************/
ENTRY(save_boot_params)b save_boot_params_ret @ back to my caller
####2.save_boot_params_ret函数讲解
同样save_boot_params函数也是只有一句跳转语句,跳转到save_boot_params_ret函数save_boot_params_ret 函数代码如下:
save_boot_params_ret:
#ifdef CONFIG_POSITION_INDEPENDENT/** Fix .rela.dyn relocations. This allows U-Boot to loaded to and* executed at a different address than it was linked at.*/
pie_fixup:/* 获取标号reset的运行地址到r0 */adr r0, reset /* r0 <- Runtime value of reset label *//* 获取标号reset的链接地址到r0 */ldr r1, =reset /* r1 <- Linked value of reset label *//* 计算运行地址和link地址的偏移 */subs r4, r0, r1 /* r4 <- Runtime-vs-link offset *//* 如果为0,说明link地址和运行地址一致,不需要重定位直接退出 */beq pie_fixup_done/* * 下面几行代码的作用是计算运行时rel.dyn段在内存中实际地址,只有获取这个段的* 真实的起使地址才能依据其中的信息进行重定位。*///获取pie_fixup标号的运行地址adr r0, pie_fixup//_rel_dyn_start_ofs链接时rel.dyn段相对pie_fixup标号的偏移ldr r1, _rel_dyn_start_ofsadd r2, r0, r1 /* r2 <- Runtime &__rel_dyn_start *///计算rel.dyn运行时起始地址ldr r1, _rel_dyn_end_ofs//计算rel.dyn运行结束地址add r3, r0, r1 /* r3 <- Runtime &__rel_dyn_end */pie_fix_loop://获取rel.dyn段地址中的内容ldr r0, [r2] /* r0 <- Link location *///获取rel.dyn段地址接下来4个字节中的内容ldr r1, [r2, #4] /* r1 <- fixup *///如果r1等于23则执行重定位cmp r1, #23 /* relative fixup? */bne pie_skip_reloc/* relative fix: increase location by offset */add r0, r4ldr r1, [r0]add r1, r4str r1, [r0]str r0, [r2]add r2, #8
pie_skip_reloc://判断是否所有表项都修改完成,没完成则循环操作cmp r2, r3blo pie_fix_loop
pie_fixup_done:
#endif#ifdef CONFIG_ARMV7_LPAE
/** check for Hypervisor support*/mrc p15, 0, r0, c0, c1, 1 @ read ID_PFR1and r0, r0, #CPUID_ARM_VIRT_MASK @ mask virtualization bitscmp r0, #(1 << CPUID_ARM_VIRT_SHIFT)beq switch_to_hypervisor
switch_to_hypervisor_ret:
#endif/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*//* 将程序状态寄存器读取到通用寄存器R0 */mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP mode/* 清除当前的工作模式 */bicne r0, r0, #0x1f @ clear all mode bits/* 设置SVC模式,即超级管理员权限 */orrne r0, r0, #0x13 @ set SVC mode/* 失能中断FIQ和IRQ */orr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0#if !CONFIG_IS_ENABLED(SYS_NO_VECTOR_TABLE)
/** Setup vector:*//* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Registerbic r0, #CR_V @ V = 0mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register#ifdef CONFIG_HAS_VBAR/* Set vector address in CP15 VBAR register */ldr r0, =_startmcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
#endif/* the mask ROM code should have PLL and others stable */
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7Abl cpu_init_cp15
#endif
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)bl cpu_init_crit
#endif
#endifbl _main
save_boot_params_ret函数主要的操作如下:
-
1.如果定义宏CONFIG_POSITION_INDEPENDENT,则进行修正重定位的问题(pie_fixup、pie_fix_loop、pie_skip_reloc);
-
2.如果定义宏CONFIG_ARMV7_LPAE,LPAE(Large Physical Address Extensions)是ARMv7系列的一种地址扩展技术,可以让32位的ARM最大能支持到1TB的内存空间,由于嵌入式ARM需求的内存空间一般不大,所以一般不使用LPAE技术;
-
3.设置CPU为SVC32模式,除非已经处于HYP模式,同时禁止中断(FIQ和IRQ);
-
4.设置中断向量表地址为_start函数的地址,在map文件中可以看到,为0x87800000;
-
5.进行CPU初始化,调用函数cpu_init_cp15和cpu_init_crit分别初始化CP15和CRIT;
-
6.最后跳转到_main函数。
3.cpu_init_cp15函数讲解
cpu_init_cp15函数,在文件arch/arm/cpu/armv7/start.S中定义,具体代码如下;
/*************************************************************************** cpu_init_cp15** Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless* CONFIG_SYS_ICACHE_OFF is defined.**************************************************************************/
ENTRY(cpu_init_cp15)#if CONFIG_IS_ENABLED(ARMV7_SET_CORTEX_SMPEN)/** The Arm Cortex-A7 TRM says this bit must be enabled before* "any cache or TLB maintenance operations are performed".*/mrc p15, 0, r0, c1, c0, 1 @ read auxilary control registerorr r0, r0, #1 << 6 @ set SMP bit to enable coherencymcr p15, 0, r0, c1, c0, 1 @ write auxilary control register
#endif/** Invalidate L1 I/D*/mov r0, #0 @ set up for MCRmcr p15, 0, r0, c8, c7, 0 @ invalidate TLBsmcr p15, 0, r0, c7, c5, 0 @ invalidate icachemcr p15, 0, r0, c7, c5, 6 @ invalidate BP arraymcr p15, 0, r0, c7, c10, 4 @ DSBmcr p15, 0, r0, c7, c5, 4 @ ISB/** disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002000 @ clear bits 13 (--V-)bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)orr r0, r0, #0x00000002 @ set bit 1 (--A-) Alignorr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB...
cpu_init_cp15函数主要的操作如下:
-
1.失效 L1 I/D Cache;
-
2.禁用MMU和缓存。
4.cpu_init_crit函数讲解
cpu_init_crit在文件arch/arm/cpu/armv7/start.S中定义,具体代码如下;
/*************************************************************************** CPU_init_critical registers** setup important registers* setup memory timing**************************************************************************/
ENTRY(cpu_init_crit)/** Jump to board specific initialization...* The Mask ROM will have already initialized* basic memory. Go here to bump up clock rate and handle* wake up conditions.*/b lowlevel_init @ go setup pll,mux,memory
ENDPROC(cpu_init_crit)
#endif
可以看到函数cpu_init_crit内部又只是一句跳转语句,调用了函数lowlevel_init,接下来就是详细的分析一下lowlevel_init和_main这两个函数。
六、lowlevel_init函数详解
lowlevel_init函数,在文件arch/arm/cpu/armv7/lowlevel_init.S中有定义,具体代码如下;
WEAK(lowlevel_init)/** Setup a temporary stack. Global data is not available yet.*/
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr sp, =CONFIG_SPL_STACK
#elseldr sp, =SYS_INIT_SP_ADDR
#endifbic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DMmov r9, #0
#else/** Set up global data for boards that still need it. This will be* removed soon.*/
#ifdef CONFIG_SPL_BUILDldr r9, =gdata
#elsesub sp, sp, #GD_SIZEbic sp, sp, #7mov r9, sp
#endif
#endif/** Save the old lr(passed in ip) and the current lr to stack*/push {ip, lr}/** Call the very early init function. This should do only the* absolute bare minimum to get started. It should not:** - set up DRAM* - use global_data* - clear BSS* - try to start a console** For boards with SPL this should be empty since SPL can do all of* this init in the SPL board_init_f() function which is called* immediately after this.*/bl s_initpop {ip, pc}
ENDPROC(lowlevel_init)
lowlevel_init函数主要的操作如下:
-
1.设置SP指针为CONFIG_SYS_INIT_SP_ADDR
-
2.对sp指针做8字节对齐处理
-
3.SP减去#GD_SIZE = 248,GD_SIZE同样在generic-asm-offsets.h 中定了
-
4.对 sp 指针做8字节对齐处理
-
5.将SP保存到R9,ip和lr入栈,程序跳转到s_init(对于I.MX6ULL来说,s_init 就是个空函数)
-
6.函数一路返回,直到_main,s_init函数-->函数lowlevel_ini-->cpu_init_crit-->save_boot_params_ret-->_main。
七、_main函数详解
_main函数在文件 arch/arm/lib/crt0.S中有定义 _main函数执行可以大致分为如下4个部分:
-
设置初始化C运行环境并调用board_init_f函数
-
设置新的sp指针和gd指针,设置中间环境位,调用代码重定位
-
重定位向量表
-
设置最后的运行环境并调用board_init_r函数
1.设置初始化C运行环境并调用board_init_f函数
代码部分,具体如下;
/** entry point of crt0 sequence*/ENTRY(_main)/* Call arch_very_early_init before initializing C runtime environment. */
#if CONFIG_IS_ENABLED(ARCH_VERY_EARLY_INIT)bl arch_very_early_init
#endif/** Set up initial C runtime environment and call board_init_f(0).*/#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)ldr r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr r0, =(CONFIG_SPL_STACK)
#elseldr r0, =(SYS_INIT_SP_ADDR)
#endifbic r0, r0, #7 /* 8-byte alignment for ABI compliance */mov sp, r0bl board_init_f_alloc_reservemov sp, r0/* set up gd here, outside any C code */mov r9, r0bl board_init_f_init_reserve#if defined(CONFIG_DEBUG_UART) && CONFIG_IS_ENABLED(SERIAL)bl debug_uart_init
#endif#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)CLEAR_BSS
#endifmov r0, #0bl board_init_f
-
1.设置sp指针为 CONFIG_SYS_INIT_SP_ADDR;
-
2.对sp指针做8字节对齐处理;
-
3.读取sp到寄存器r0里面;
-
4.调用函数board_init_f_alloc_reserve;
-
5.调用函数board_init_f_init_reserve;
-
6.调用函数board_init_f。
1.board_init_f_alloc_reserve函数
board_init_f_alloc_reserve函数,在common/init/board_init.c文件中定义,如下;
ulong board_init_f_alloc_reserve(ulong top)
{/* Reserve early malloc arena */
#ifndef CONFIG_MALLOC_F_ADDR
#if CONFIG_VAL(SYS_MALLOC_F_LEN)top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
#endif/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */top = rounddown(top-sizeof(struct global_data), 16);return top;
}
board_init_f_alloc_reserve函数的作用:
是根据传入参数是栈顶地址,计算出预留空间的底部,并将其返回。 主要是留出早期的 malloc 内存区域和gd内存区域。如果宏CONFIG_MALLOC_F_ADDR没有被定义,则为malloc预留部分内存空间,大小为CONFIG_SYS_MALLOC_F_LEN;其次为GD变量(global_data结构体类型)预留空间,并且对齐到16个字节的倍数。
2.board_init_f_init_reserve函数
board_init_f_init_reserve函数,在common/init/board_init.c文件中定义,如下;
void board_init_f_init_reserve(ulong base)
{struct global_data *gd_ptr;/** clear GD entirely and set it up.* Use gd_ptr, as gd may not be properly set yet.*/gd_ptr = (struct global_data *)base;/* zero the area */memset(gd_ptr, '\0', sizeof(*gd));/* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)arch_setup_gd(gd_ptr);
#endifif (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))board_init_f_init_stack_protection_addr(base);/* next alloc will be higher by one GD plus 16-byte alignment */base += roundup(sizeof(struct global_data), 16);/** record early malloc arena start.* Use gd as it is now properly set for all architectures.*/#if CONFIG_VAL(SYS_MALLOC_F_LEN)/* go down one 'early malloc arena' */gd->malloc_base = base;
#endifif (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))board_init_f_init_stack_protection();
}
board_init_f_init_reserve函数的作用:
是初始化gd,其实就是清零处理;设置了gd->malloc_base为gd基地址+gd 大小,并做16字节对齐处理。
2.设置新的sp指针和gd指针,调用重定位代码,调用代码重定位
代码部分,具体如下;
#if ! defined(CONFIG_SPL_BUILD)/** Set up intermediate environment (new sp and gd) and call* relocate_code(addr_moni). Trick here is that we'll return* 'here' but relocated.*/ldr r0, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */bic r0, r0, #7 /* 8-byte alignment for ABI compliance */mov sp, r0ldr r9, [r9, #GD_NEW_GD] /* r9 <- gd->new_gd */adr lr, here
#if defined(CONFIG_POSITION_INDEPENDENT)adr r0, _mainldr r1, _start_ofsadd r0, r1ldr r1, =CONFIG_SYS_TEXT_BASEsub r1, r0add lr, r1
#endifldr 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 */
#endifldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */b relocate_code
-
1.设置新的栈顶指针为sp = gd->start_addr_sp;
-
2.设置新的gd指针为r9 <- gd->new_gd;
-
3.设置新r0指针为r0 = gd->reloc_off;
-
4.设置r0寄存器的值为gd->relocaddr,跳转到代码重定位relocate_code。
3.重定位向量表
代码部分,具体如下;
here:
/** now relocate vectors*/bl relocate_vectors
代码重定位后返回到here标号处,调用relocate_vectors函数,对中断向量表做重定位。
4.设置最后的运行环境并调用board_init_r函数
代码部分,具体如下;
/* Set up final (full) environment */bl c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)CLEAR_BSS
#endif# ifdef CONFIG_SPL_BUILD/* Use a DRAM stack for the rest of SPL, if requested */bl spl_relocate_stack_gdcmp r0, #0movne sp, r0movne r9, r0
# endif#if ! defined(CONFIG_SPL_BUILD)bl coloured_LED_initbl 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
#elseldr pc, =board_init_r /* this is auto-relocated! */
#endif/* we should not return here. */
#endifENDPROC(_main)
board_init_r函数主要工作:
-
1.调用函数c_runtime_cpu_setup,失效I-cache;
-
2.清除BSS段;
-
3.设置函数board_init_r的两个参数;
-
4.调用函数board_init_r。
八、board_init_f 函数详解
board_init_f函数,在common/board_f.c文件定义,具体代码如下;
void board_init_f(ulong boot_flags)
{gd->flags = boot_flags;gd->have_console = 0;if (initcall_run_list(init_sequence_f))hang();#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \!defined(CONFIG_EFI_APP) && !CONFIG_IS_ENABLED(X86_64) && \!defined(CONFIG_ARC)/* NOTREACHED - jump_to_copy() does not return */hang();
#endif
}
board_init_f函数主要有两个工作:
-
1.初始化gd的各个成员变量
-
2.调用函数initcall_run_list,初始化序列init_sequence_f里面的一系列函数,来初始化一系列外设,比如串口、定时器,或者打印一些消息等。
init_sequence_f数组,在common/board_f.c文件中定义,如下,初始化函数表省略其中部分代码;
static const init_fnc_t init_sequence_f[] = {setup_mon_len,fdtdec_setup,trace_early_init,initf_malloc,log_init,initf_bootstage, /* uses its own timer, so does not need DM */event_init,bloblist_init,setup_spl_handoff,console_record_init,arch_fsp_init,arch_cpu_init, /* basic arch cpu dependent setup */mach_cpu_init, /* SoC/machine dependent CPU setup */initf_dm,board_early_init_f,get_clocks, /* get CPU and bus clocks (etc.) */timer_init, /* initialize timer */board_postclk_init,env_init, /* initialize environment */init_baud_rate, /* initialze baudrate settings */serial_init, /* serial communications setup */console_init_f, /* stage 1 init of console */display_options, /* say that we are here */display_text_info, /* show debugging info if required */checkcpu,print_resetinfo,print_cpuinfo, /* display cpu info (and speed) */embedded_dtb_select,show_board_info,INIT_FUNC_WATCHDOG_INITmisc_init_f,INIT_FUNC_WATCHDOG_RESETinit_func_i2c,init_func_vid,announce_dram_init,dram_init, /* configure available RAM banks */post_init_f,INIT_FUNC_WATCHDOG_RESETtestdram,INIT_FUNC_WATCHDOG_RESETinit_post,INIT_FUNC_WATCHDOG_RESETsetup_dest_addr,fix_fdt,reserve_pram,...
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \!CONFIG_IS_ENABLED(X86_64)jump_to_copy,
#endifNULL,
};
其中比较重要的一些初始化函数如下:
-
1.setup_mon_len函数:设置gd的mon_len成员变量,也就是整个代码的长度;
-
2.initf_malloc函数:设置gd中和malloc有关的成员变量;
-
3.board_early_init_f函数:用来初始化串口的IO配置,在board/freescale/mx6ull_toto/mx6ull_toto.c文件中定义;
-
4.timer_init函数:初始化内核定时器,为uboot提供时钟节拍,在arch/arm/imx-common/timer.c文件中定义;
-
5.get_clocks函数:获取了SD卡外设的时钟(sdhc_clk),在arch/arm/imx-common/speed.c文件中定义;
-
6.init_baud_rate函数:初始化波特率,在common/board_f.c文件中定义;
-
7.serial_init函数:初始化串口通信设置,在drivers/serial/serial.c文件中定义;
-
8.console_init_f函数:初始化控制台,在common/console.c文件中定义:
-
9.display_options函数:打印uboot版本信息和编译信息,在lib/display_options.c文件中定义;
-
10.print_cpuinfo函数:用来显示CPU信息和主频,在arch/arm/imx-common/cpu.c文件中定义;
-
11.show_board_info函数:打印开发板信息,在common/board_info.c文件中定义;
-
12.init_func_i2c函数:用于初始化I2C;
-
13.announce_dram_init函数:此函数很简单,就是输出字符串“DRAM:”;
-
14.dram_init函数:并非真正的初始化DDR,只是设置gd->ram_size的值。
九、relocate_code函数详解
relocate_code函数,在arch/arm/lib/relocate.S文件定义,具体代码如下;
/** void relocate_code(addr_moni)** This function relocates the monitor code.** NOTE:* To prevent the code below from containing references with an R_ARM_ABS32* relocation record type, we never refer to linker-defined symbols directly.* Instead, we declare literals which contain their relative location with* respect to relocate_code, and at run time, add relocate_code back to them.*/ENTRY(relocate_code)
relocate_base:adr r3, relocate_baseldr r1, _image_copy_start_ofsadd r1, r3 /* r1 <- Run &__image_copy_start */subs r4, r0, r1 /* r4 <- Run to copy offset */beq relocate_done /* skip relocation */ldr r1, _image_copy_start_ofsadd r1, r3 /* r1 <- Run &__image_copy_start */ldr r2, _image_copy_end_ofsadd r2, r3 /* r2 <- Run &__image_copy_end */
copy_loop:ldmia r1!, {r10-r11} /* copy from source address [r1] */stmia r0!, {r10-r11} /* copy to target address [r0] */cmp r1, r2 /* until source end address [r2] */blo copy_loop/** fix .rel.dyn relocations*/ldr r1, _rel_dyn_start_ofsadd r2, r1, r3 /* r2 <- Run &__rel_dyn_start */ldr r1, _rel_dyn_end_ofsadd r3, r1, r3 /* r3 <- Run &__rel_dyn_end */
fixloop:ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */and r1, r1, #0xffcmp r1, #R_ARM_RELATIVEbne fixnext/* relative fix: increase location by offset */add r0, r0, r4ldr r1, [r0]add r1, r1, r4str r1, [r0]
fixnext:cmp r2, r3blo fixlooprelocate_done:#ifdef __XSCALE__/** On xscale, icache must be invalidated and write buffers drained,* even with cache disabled - 4.2.7 of xscale core developer's manual*/mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif/* ARMv4- don't know bx lr but the assembler fails to see that */#ifdef __ARM_ARCH_4__mov pc, lr
#elsebx lr
#endifENDPROC(relocate_code)
relocate_code此函数主要作用:
完成镜像拷贝和重定位,镜像地址从__image_copy_start开始,到__image_copy_end结束,拷贝的目标地址由参数传进来,也就是r0寄存器的值。重定位的原理此处不展开,需要了解的自行去学习。
十、relocate_vectors函数详解
relocate_vectors函数,在arch/arm/lib/relocate.S文件定义,具体代码如下;
ENTRY(relocate_vectors)#ifdef CONFIG_CPU_V7M/** On ARMv7-M we only have to write the new vector address* to VTOR register.*/ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ldr r1, =V7M_SCB_BASEstr r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR/** If the ARM processor has the security extensions,* use VBAR to relocate the exception vectors.*/ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
#else/** Copy the relocated exception vectors to the* correct address* CP15 c1 V bit gives us the location of the vectors:* 0x00000000 or 0xFFFF0000.*/ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */ands r2, r2, #(1 << 13)ldreq r1, =0x00000000 /* If V=0 */ldrne r1, =0xFFFF0000 /* If V=1 */ldmia r0!, {r2-r8,r10}stmia r1!, {r2-r8,r10}ldmia r0!, {r2-r8,r10}stmia r1!, {r2-r8,r10}
#endif
#endifbx lrENDPROC(relocate_vectors)
relocate_vectors函数用于重定位向量表,只有一步操作比较重要,就是将uboot重定位完之后的地址,装载到CP15的VBAR寄存器中设置向量表偏移,该寄存器自行去学习。
十一、board_init_r函数详解
board_init_r函数,在common/board_r.c文件定义,具体代码如下;
void board_init_r(gd_t *new_gd, ulong dest_addr)
{/** Set up the new global data pointer. So far only x86 does this* here.* TODO(sjg@chromium.org): Consider doing this for all archs, or* dropping the new_gd parameter.*/if (CONFIG_IS_ENABLED(X86_64) && !IS_ENABLED(CONFIG_EFI_APP))arch_setup_gd(new_gd);#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)gd = new_gd;
#endifgd->flags &= ~GD_FLG_LOG_READY;if (IS_ENABLED(CONFIG_NEEDS_MANUAL_RELOC)) {for (int i = 0; i < ARRAY_SIZE(init_sequence_r); i++)MANUAL_RELOC(init_sequence_r[i]);}if (initcall_run_list(init_sequence_r))hang();/* NOTREACHED - run_main_loop() does not return */hang();
}
board_init_f函数中,会初始化一些外设和gd的成员变量,但并没有初始化所有的外设,还需要一些后续工作,这些工作就是由board_init_r函数完成的,调用initcall_run_list函数执行初始化序列init_sequence_r,init_sequence_r是一个函数表,也定义在该文件中,部分代码如下;
static init_fnc_t init_sequence_r[] = {initr_trace,initr_reloc,event_init,initr_caches,initr_reloc_global_data,initr_barrier,initr_malloc,log_init,initr_bootstage,console_record_init,initr_of_live,board_init, /* Setup chipselects */stdio_init_tables,serial_initialize,initr_announce,INIT_FUNC_WATCHDOG_RESETINIT_FUNC_WATCHDOG_RESETpower_init_board,initr_flash,initr_nand,initr_mmc,initr_env,INIT_FUNC_WATCHDOG_RESETcpu_secondary_init_r,INIT_FUNC_WATCHDOG_RESETstdio_add_devices,jumptable_init,console_init_r, /* fully init console as a device */interrupt_init,board_late_init,INIT_FUNC_WATCHDOG_RESETinitr_net,run_main_loop,
};
其中比较重要的一些初始化函数如下:
-
1.initr_caches函数:初始化cache,使能cache;
-
2.board_init函数:FEC初始化,在board/freescale/mx6ull_toto/mx6ull_toto.c文件中定义;
-
3.initr_mmc函数:初始化emmc,在common/board_r.c文件中定义;
-
4.iinitr_env函数:初始化环境变量;
-
5.console_init_r函数:初始化控制台,在common/console.c文件中定义;
-
6.interrupt_init函数和initr_enable_interrupts函数:初始化中断并使能中断;在arch/arm/lib/interrupts.c文件中定义;
-
7.initr_ethaddr函数:初始化网络地址,获取MAC地址,读取环境变量ethaddr的值;
-
8.initr_net函数:初始化网络设备,函 数 调 用 顺 序 为 :initr_net->eth_initialize->board_eth_init(),在common/board_r.c文件中定义;
-
9.run_main_loop函数:主循环,处理命令。
十二、run_main_loop函数详解
run_main_loop函数,在common/board_r.c文件定义,具体代码如下;
static int run_main_loop(void)
{
#ifdef CONFIG_SANDBOXsandbox_main_loop_init();
#endif/* main_loop() can return to retry autoboot, if so just run it again */for (;;)main_loop();return 0;
}
uboot启动以后会进入3秒倒计时,如果在3秒倒计时结束之前按下按下回车键,那么就会进入uboot的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动Linux内核,这个功能就是由run_main_loop函数来完成的。
main_loop函数,在common/main.c文件中定义,具体代码如下;
/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)
{const char *s;bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");if (IS_ENABLED(CONFIG_VERSION_VARIABLE))env_set("ver", version_string); /* set version variable */cli_init();if (IS_ENABLED(CONFIG_USE_PREBOOT))run_preboot_environment_command();if (IS_ENABLED(CONFIG_UPDATE_TFTP))update_tftp(0UL, NULL, NULL);if (IS_ENABLED(CONFIG_EFI_CAPSULE_ON_DISK_EARLY)) {/* efi_init_early() already called */if (efi_init_obj_list() == EFI_SUCCESS)efi_launch_capsules();}s = bootdelay_process();if (cli_process_fdt(&s))cli_secure_boot_cmd(s);autoboot_command(s);cli_loop();panic("No CLI available");
}
main_loop函数主要工作:
-
1.调用bootstage_mark_name函数,打印出启动进度
-
2.如果宏CONFIG_VERSION_VARIABLE定义了就会执行函数setenv,设置换将变量ver的值为version_string,也就是设置版本号环境变量;
-
3.调用cli_init函数,初始化hushshell相关的变量
-
4.调用bootdelay_process函数,此函数会读取环境变量bootdelay和bootcmd的内容,然后将bootdelay的值赋值给全局变量stored_bootdelay,返回值为环境变量bootcmd的值。
-
5.autoboot_command函数,此函数就是检查倒计时是否结束?倒计时结束之前有没有被打断?
autoboot_command函数,在文件common/autoboot.c文件中定义,具体代码如下;
void autoboot_command(const char *s)
{debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");if (s && (stored_bootdelay == -2 ||(stored_bootdelay != -1 && !abortboot(stored_bootdelay)))) {bool lock;int prev;lock = autoboot_keyed() &&!IS_ENABLED(CONFIG_AUTOBOOT_KEYED_CTRLC);if (lock)prev = disable_ctrlc(1); /* disable Ctrl-C checking */run_command_list(s, -1, 0);if (lock)disable_ctrlc(prev); /* restore Ctrl-C checking */}if (IS_ENABLED(CONFIG_AUTOBOOT_USE_MENUKEY) &&menukey == AUTOBOOT_MENUKEY) {s = env_get("menucmd");if (s)run_command_list(s, -1, 0);}
}
abortboot函数,在文件common/autoboot.c文件中定义,具体代码如下;
static int abortboot(int bootdelay)
{int abort = 0;if (bootdelay >= 0) {if (autoboot_keyed())abort = abortboot_key_sequence(bootdelay);elseabort = abortboot_single_key(bootdelay);}if (IS_ENABLED(CONFIG_SILENT_CONSOLE) && abort)gd->flags &= ~GD_FLG_SILENT;return abort;
}
在倒计时结束之前有按键按下则执行函数 abortboot_single_key,abortboot_single_key函数在common/autoboot.c文件中定义,具体代码如下;
static int abortboot_single_key(int bootdelay)
{int abort = 0;unsigned long ts;printf("Hit any key to stop autoboot: %2d ", bootdelay);/** Check if key already pressed*/if (tstc()) { /* we got a key press */getchar(); /* consume input */puts("\b\b\b 0");abort = 1; /* don't auto boot */}while ((bootdelay > 0) && (!abort)) {--bootdelay;/* delay 1000 ms */ts = get_timer(0);do {if (tstc()) { /* we got a key press */int key;abort = 1; /* don't auto boot */bootdelay = 0; /* no more delay */key = getchar();/* consume input */if (IS_ENABLED(CONFIG_AUTOBOOT_USE_MENUKEY))menukey = key;break;}udelay(10000);} while (!abort && get_timer(ts) < 1000);printf("\b\b\b%2d ", bootdelay);}putc('\n');return abort;
}
abortboot_single_key函数主要工作:
-
1.倒计时的具体实现;
-
2.判断键盘是否有按下,也就是是否打断了倒计时,如果键盘按下的话就执行相应的分支。比如设置abort为 1,设置 bootdelay为0等,最后跳出倒计时循环;
-
3.返回abort的值,如果倒计时自然结束,没有被打断abort就为0,否则的话abort的值就为 1;
-
4.在autoboot_command函数中,如果倒计时自然结束那么就执行函数run_command_list,此函数会执行参数s指定的一系列命令,也就是环境变量bootcmd的命令,bootcmd里面保存着默认的启动命令,因此linux内核启动!
十三、u-boot启动函数调用流程框图
上面给大家详细的讲解了各个函数的作用,以及调用关系。现在给大家总结一下,以流程框图的形式,展示u-boot启动流程;
本期的内容到这就结束了,如果觉得文章不错,可以点赞、收藏和关注哦,谢谢大家收看,下期再见!
关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。