首页 > 其他分享 >u-boot启动流程(好难理解啊!!!)

u-boot启动流程(好难理解啊!!!)

时间:2024-05-27 22:57:23浏览次数:30  
标签:r0 函数 流程 好难 boot start init gd CONFIG

链接脚本 u-boot.lds 详解

要分析 uboot 的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过 uboot 的话链接脚本为 arch/arm/cpu/u-boot.lds。
在这里插入图片描述

打开u-boot.lds可以看到下图所示的内容。
在这里插入图片描述

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  *(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
  *(.text*)
 }
 . = 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*)));
 }
 . = ALIGN(4);
 .image_copy_end :
 {
  *(.__image_copy_end)
 }
 .rel_dyn_start :
 {
  *(.__rel_dyn_start)
 }
 .rel.dyn : {
  *(.rel*)
 }
 .rel_dyn_end :
 {
  *(.__rel_dyn_end)
 }
 .end :
 {
  *(.__end)
 }
 _image_binary_end = .;
 . = ALIGN(4096);
 .mmutable : {
  *(.mmutable)
 }
 .bss_start __rel_dyn_start (OVERLAY) : {
  KEEP(*(.__bss_start));
  __bss_base = .;
 }
 .bss __bss_base (OVERLAY) : {
  *(.bss*)
   . = ALIGN(4);
   __bss_limit = .;
 }
 .bss_end __bss_limit (OVERLAY) : {
  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.*) }
}

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

指定输出文件的格式为 elf32-littlearm,这是一个针对32位ARM架构的ELF文件格式。

OUTPUT_ARCH(arm)

指定输出文件的目标架构为ARM。

ENTRY(_start)

定义程序的入口点是 _start 符号。这通常是执行开始的地方。

SECTIONS
{

开始定义各个段的布局。

 . = 0x00000000;

设置当前位置计数器(location counter,.)的初始值为0x00000000。

 . = ALIGN(4);

确保下一个段地址是4字节对齐的。

 .text :
 {
  *(.__image_copy_start)
  *(.vectors)
  arch/arm/cpu/armv7/start.o (.text*)
  *(.text*)
 }

定义 .text 段,通常包含程序的执行代码:

  • *(.__image_copy_start) 包含所有 .__image_copy_start 段的内容。
  • *(.vectors) 包含所有 .vectors 段的内容,这通常用于存放中断向量等。
  • arch/arm/cpu/armv7/start.o (.text*) 仅包含指定对象文件中的 .text 段。
  • *(.text*) 包含所有 .text 段的内容。
 . = ALIGN(4);

再次确保地址对齐。

 .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

定义 .rodata 段,存放只读数据。使用 SORT_BY_ALIGNMENTSORT_BY_NAME 来排序,以优化访问速度和存储效率。

 . = ALIGN(4);
 .data : {
  *(.data*)
 }

定义 .data 段,存放初始化的全局变量和静态变量。

 . = ALIGN(4);
 . = .;
 . = ALIGN(4);

连续的地址对齐,确保后续段的地址对齐。

 .u_boot_list : {
  KEEP(*(SORT(.u_boot_list*)));
 }

定义 .u_boot_list 段,可能用于U-Boot引导程序。KEEP 指令确保链接器不会在优化时移除这些符号。

 .image_copy_end :
 {
  *(.__image_copy_end)
 }

定义 .image_copy_end 段,包含所有 .__image_copy_end 段的内容。

接下来的部分定义了动态重定位、结束符号、不初始化的数据段(BSS)、动态链接表等相关段。这些定义确保了程序在运行时能正确处理动态链接、异常表和其他运行时必需的信息。

 .bss_start __rel_dyn_start (OVERLAY) : {
  KEEP(*(.__bss_start));
  __bss_base = .;
 }

定义 .bss_start 段,并使用 OVERLAY 指令与其他段共享地址空间,KEEP 保证不被优化移除。

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

定义 .bss 段,存放未初始化的数据。使用 OVERLAY 指令共享地址空间。

 .bss_end __bss_limit (OVERLAY) : {
  KEEP(*(.__bss_end));
 }

定义 .bss_end 段。

最后的部分定义了动态符号表、动态字符串表、动态节表和其他特定于ARM的段,如异常索引表(.ARM.exidx)等。
_start 在文件 arch/arm/lib/vectors.S 中有定义,如下图所示:

在这里插入图片描述.section ".vectors", "ax"这行代码是一个汇编指令,用于告诉编译器和链接器将随后的代码段放入特定的内存区域(段)。段名为 .vectors,而 "ax" 指定了该段的属性,所以此代码存放在.vectors 段里面。
a: 表示该段是可分配的(Allocatable)。在链接时,链接器会为此段分配内存空间。
x: 表示该段是可执行的(Executable)。这意味着存放在这一段的代码可以被处理器执行。
使用如下命令在 uboot 中查找“__image_copy_start”:

grep -nR "__image_copy_start"

搜索完成后可以得到下图。u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,我们打开u-boot.map进一步查看其中内容。

在这里插入图片描述
找到下图的位置:
在这里插入图片描述

vectors 段的起始地址也是 0X87800000,说明整个 uboot 的起始地址就是 0X87800000,这也是为什么我们裸机例程的链接起始地址选择 0X87800000 了,目的就是为了和 uboot 一致,一致的原因如下:

1:U-Boot的起始地址为 0X87800000:这个地址是U-Boot开始的地方,通常包括了其启动时的中断向量表。选择这个地址可能是基于硬件要求,或者为了优化系统性能和兼容性。
2:裸机程序使用相同的起始地址:当U-Boot将控制权转交给裸机程序时,如果两者的起始地址相同,这种过渡就会更加顺畅。这样做的好处包括:
3:无需重新配置内存映射:如果裸机程序使用和U-Boot相同的起始地址,它可以直接利用已经由U-Boot设置好的内存配置。
4:简化系统设计:保持一致的内存起始地址可以减少设计复杂性,避免在从U-Boot到裸机程序的过渡中出现错误。
5:提高系统启动效率:处理器不需要重新定位中断向量表,可以直接继续使用U-Boot时的配置
在这里插入图片描述在这里插入图片描述

U-Boot 启动流程详解

reset 函数源码详解

#include <config.h>

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

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

_start:

#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
	.word	CONFIG_SYS_DV_NOR_BOOT_CFG
#endif

	b	reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq

  1. 全局标签声明:

    .globl _start
    

    这一行声明了 _start 符号为全局符号,使得它可以被链接器脚本中其他模块或者代码引用。在嵌入式系统中,_start 通常是程序的入口点。

  2. 节(Section)声明:

    .section ".vectors", "ax"
    

    这一行指定接下来的代码放在名为 .vectors 的段中,同时给这个段指定了“ax”属性,表示这个段是可执行的(a 代表可分配,x 代表可执行)。这是因为异常向量表需要被CPU执行。

  3. 异常向量表定义:

    _start:
    

    这里 _start 标签标记了异常向量表的开始位置,它是程序的入口点。

    • 可选的启动配置字:

      #ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
      .word CONFIG_SYS_DV_NOR_BOOT_CFG
      #endif
      

      这部分代码是条件编译的一部分,只有当 CONFIG_SYS_DV_NOR_BOOT_CFG 宏被定义时,才会在向量表中包含一个字(word)的配置信息。这通常用于配置启动时的某些硬件特性。

    • 异常处理跳转指令:

      b reset
      ldr pc, _undefined_instruction
      ldr pc, _software_interrupt
      ldr pc, _prefetch_abort
      ldr pc, _data_abort
      ldr pc, _not_used
      ldr pc, _irq
      ldr pc, _fiq
      

      这些指令设置了ARM处理器的主要异常处理向量:

      • b reset:无条件跳转到 reset 标签处,通常用于处理系统复位。
      • ldr pc, ...:加载指令,用于将程序计数器(PC)设置到各种异常处理程序的地址。这些处理程序包括未定义指令异常、软件中断、预取中止、数据中止、未使用、普通中断请求(IRQ)和快速中断请求(FIQ)。

上面跳转涉及到ret函数。ret函数在arch/arm/cpu/armv7/start.S 里面。
在这里插入图片描述
reset 函数跳转到了 save_boot_params 函数,而 save_boot_params 函数同样定义,在 start.S 里面。

ENTRY(save_boot_params)
	b	save_boot_params_ret		@ back to my caller

save_boot_params 函数也是只有一句跳转语句,跳转到 save_boot_params_ret 函数,save_boot_params_ret 函数代码如下:

save_boot_params_ret:
	/*
	 * disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
	 * except if in HYP mode already
	 */
	mrs	r0, cpsr
	and	r1, r0, #0x1f		@ mask mode bits
	teq	r1, #0x1a		@ test for HYP mode
	bicne	r0, r0, #0x1f		@ clear all mode bits
	orrne	r0, r0, #0x13		@ set SVC mode
	orr	r0, r0, #0xc0		@ disable FIQ and IRQ
	msr	cpsr,r0

这段代码是用于在ARM架构的处理器上设置处理器状态的典型代码,主要目的是为了在启动或异常处理流程中禁用中断和设置正确的处理器模式。这里的代码主要通过操作CPSR(当前程序状态寄存器)来实现这些功能。下面是对代码的逐行解释:

  1. 读取当前程序状态寄存器(CPSR)到寄存器r0:

    mrs r0, cpsr
    

    mrs 指令用于将CPSR的值移动到寄存器r0中。CPSR寄存器包含了当前处理器的状态信息,例如中断禁用位和当前处理器模式。

  2. 将r0中的模式位与0x1F进行AND操作,结果存入r1:

    and r1, r0, #0x1f
    

    这行代码使用AND操作来屏蔽掉r0中除了最低5位之外的所有位。最低的5位定义了处理器的模式(如用户模式、系统模式等)。将寄存器 r0 中的值与 0X1F 进行与运算,结果保存到 r1 寄存器中,目的就是提取 cpsr 的 bit0~bit4 这 5 位,这 5 位为 M4 M3 M2 M1 M0, M[4:0]这五位用来设置处理器的工作模式,具体工作模式如下图所示:

在这里插入图片描述

  1. 比较r1与0x1A(HYP模式的模式值):

    teq r1, #0x1a
    

    teq 指令用于测试r1和0x1A是否相等,但不更新任何寄存器,只更新条件标志。0x1A是HYP模式(Hypervisor模式)的模式值。

  2. 如果不是HYP模式,清除r0中的模式位:

    bicne r0, r0, #0x1f
    

    bicne 指令在条件“不等于”(NE)满足时执行。它将r0与0x1F的位取反后的值进行AND操作,实际上是清除了r0的最低5位。如果 r1 和 0X1A 不相等,也就是 CPU 不处于 Hyp 模式的话就将 r0 寄存器的bit0~5 进行清零,其实就是清除模式位。

  3. 如果不是HYP模式,将处理器设置为SVC(超级用户)模式:

    orrne r0, r0, #0x13
    

    orrne 指令在条件“不等于”满足时执行。这里它将r0与0x13进行OR操作,0x13是SVC模式的模式值。如果处理器不处于 Hyp 模式的话就将 r0 的寄存器的值与 0x13 进行或运算,0x13=0b10011,也就是设置处理器进入 SVC 模式。

  4. 无论当前模式如何,禁用FIQ和IRQ中断:

    orr r0, r0, #0xc0
    

    这行代码将r0与0xC0进行OR操作。0xC0的位模式是11000000,其中第6位和第7位分别对应于禁用IRQ和FIQ中断。r0 寄存器的值再与 0xC0 进行或运算,那么 r0 寄存器此时的值就是 0xD3, cpsr的 I 为和 F 位分别控制 IRQ 和 FIQ 这两个中断的开关,设置为 1 就关闭了 FIQ 和 IRQ。

  5. 将修改后的值写回CPSR:

    msr cpsr, r0
    

    msr 指令用于将r0的值移动回CPSR寄存器,更新处理器的状态。

接下来来看这部分代码

#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
	/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
	mrc	p15, 0, r0, c1, c0, 0	@ Read CP15 SCTLR Register
	bic	r0, #CR_V		@ V = 0
	mcr	p15, 0, r0, c1, c0, 0	@ Write CP15 SCTLR Register

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

这段代码用于ARM处理器上配置系统控制寄存器(SCTLR)和向量基地址寄存器(VBAR),以便正确设置异常处理向量的位置。代码中使用了条件编译指令来控制特定配置下的代码编译。我们来逐行解释这段代码:

#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))

这行代码检查是否定义了CONFIG_OMAP44XXCONFIG_SPL_BUILD宏。如果两者都未同时定义,那么编译器将会编译接下来的代码块。这通常用于在特定的硬件配置或构建阶段中排除或包含特定的代码。

  1. 读取SCTLR寄存器到r0:
    mrc p15, 0, r0, c1, c0, 0
    

其中各部分的含义如下:

mrc <coproc>, <opcode_1>, <Rd>, <CRn>, <CRm>, <opcode_2>

<coproc>: 指定协处理器的编号。在ARM体系结构中,最常用的系统控制协处理器为p15。
<opcode_1>: 操作码,用于指定协处理器内部的特定操作,这里为0。
<Rd>: 目标寄存器,用于接收从协处理器读出的数据,这里是r0。
<CRn>: 源寄存器(协处理器中的寄存器),这里为c1,通常关联到系统控制寄存器(SCTLR)。
<CRm>: 另一个协处理器寄存器,用于进一步指定操作,这里为c0。
<opcode_2>: 第二操作码,进一步细化指令的行为,这里为0。
指令功能
对于指令 mrc p15, 0, r0, c1, c0, 0:
p15 表明这是与系统控制相关的操作,使用的是系统控制协处理器。
c1 表示操作的是系统控制寄存器(SCTLR)。SCTLR寄存器用于控制处理器的多种功能,如缓存、分支预测、异常处理等。
r0 是目标寄存器,用于存储从SCTLR读取的值,这里使用mrc(Move to Register from Coprocessor)指令从协处理器p15的c1寄存器(系统控制寄存器,SCTLR)读取值到通用寄存器r0中。这个寄存器主要用于控制处理器的一些基本功能,如缓存、分支预测等。

  1. 修改SCTLR寄存器中的V位(V=0):
    bic r0, #CR_V
    
    bic(Bit Clear)指令用于将r0寄存器中的特定位清零。

在这里插入图片描述

这里CR_V是一个宏,代表SCTLR寄存器中控制向量地址重定向的位。设置V=0意味着异常向量表的地址由VBAR寄存器决定。
CR_V 在 arch/arm/include/asm/system.h 中有如下所示定义:

#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */

在这里插入图片描述

  1. 将修改后的值写回SCTLR寄存器:

    mcr p15, 0, r0, c1, c0, 0
    

    使用mcr(Move to Coprocessor from Register)指令将r0的值写回到协处理器p15的c1寄存器中。

  2. 加载_start地址到r0:

    ldr r0, =_start
    

设置r0寄存器的值为_start, _start就是整个uboot的入口地址,其值为0X87800000,相当于 uboot 的起始地址,因此 0x87800000 也是向量表的起始地址。

  1. 写入VBAR寄存器:
    mcr p15, 0, r0, c12, c0, 0
    
    这条指令将r0中的地址(即_start的地址)写入到协处理器p15的c12寄存器(向量基地址寄存器,VBAR)中。这样设置后,所有的异常(如中断、系统调用等)都会跳转到_start指定的地址去处理。

接下来再往底下看代码。

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_cp15
	bl	cpu_init_crit
#endif

	bl	_main

上面的代码就是分别调用函数 cpu_init_cp15、 cpu_init_crit 和_main。
函数 cpu_init_cp15 用来设置 CP15 相关的内容,比如关闭 MMU 啥的,此函数同样在 start.S文件中定义的,
可以看出函数 cpu_init_crit 内部仅仅是调用了函数 lowlevel_init,接下来就是详细的分析一下 lowlevel_init 和_main 这两个函数。

lowlevel_init 函数详解

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

ENTRY(lowlevel_init)
	/*
	 * Setup a temporary stack. Global data is not available yet.
	 */
	ldr	sp, =CONFIG_SYS_INIT_SP_ADDR
	bic	sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
	mov	r9, #0
#else
	/*
	 * Set up global data for boards that still need it. This will be
	 * removed soon.
	 */
#ifdef CONFIG_SPL_BUILD
	ldr	r9, =gdata
#else
	sub	sp, sp, #GD_SIZE
	bic	sp, sp, #7
	mov	r9, sp
#endif
#endif
	/*
	 * Save the old lr(passed in ip) and the current lr to stack
	 */
	push	{ip, lr}

	/*
	 * Call the very early init function. This should do only the
	 * absolute bare minimum to get started. It should not:
	 *
	 * - set up DRAM
	 * - use global_data
	 * - clear BSS
	 * - try to start a console
	 *
	 * For boards with SPL this should be empty since SPL can do all of
	 * this init in the SPL board_init_f() function which is called
	 * immediately after this.
	 */
	bl	s_init
	pop	{ip, pc}
ENDPROC(lowlevel_init)

详细代码如上图所示。这段代码是嵌入式系统(特别是基于 ARM 的系统)启动过程中的低级初始化代码。代码来自于 U-Boot 的启动阶段,主要负责设置堆栈、全局数据以及调用最初的初始化函数。
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
设置 sp 指向 CONFIG_SYS_INIT_SP_ADDR, CONFIG_SYS_INIT_SP_ADDR 在include/configs/mx6ullevk.h 文件中,在 mx6ullevk.h 中有如下所示定义:
在这里插入图片描述
上面的的 IRAM_BASE_ADDR 和 IRAM_SIZE 在 文 件arch/arm/include/asm/arch-mx6/imx-regs.h 中有定义,如下所示,其实就是 IMX6UL/IM6ULL 内部 ocram 的首地址和大小。
示例代码 32.2.2.3 imx-regs.h 代码段

71 #define IRAM_BASE_ADDR 0x00900000
......
408 #if !(defined(CONFIG_MX6SX) || defined(CONFIG_MX6UL) || \
409 defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
410 #define IRAM_SIZE 0x00040000
411 #else
412 #define IRAM_SIZE 0x00020000
413 #endif

如果 408 行的条件成立的话 IRAM_SIZE=0X40000,当定义了 CONFIG_MX6SX、CONFIG_MX6UL、 CONFIG_MX6SLL 和 CONFIG_MX6SL 中的任意一个的话条件就不成立,在.config 中定义了 CONFIG_MX6UL,所以条件不成立,因此 IRAM_SIZE=0X20000=128KB。可以得到如下值:

CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000。
CONFIG_SYS_INIT_RAM_SIZE = 0x00020000 =128KB。

还需要知道GENERATED_GBL_DATA_SIZE的值,在文件include/generated/generic-asm-offsets.h中有定义,如下:

1 #ifndef __GENERIC_ASM_OFFSETS_H__
2 #define __GENERIC_ASM_OFFSETS_H__
3 /*
4 * DO NOT MODIFY.
5 *
6 * This file was generated by Kbuild
7 */
8 
9  #define GENERATED_GBL_DATA_SIZE 256
10 #define GENERATED_BD_INFO_SIZE 80
11 #define GD_SIZE 248
12 #define GD_BD 0
13 #define GD_MALLOC_BASE 192
14 #define GD_RELOCADDR 48
15 #define GD_RELOC_OFF 68
16 #define GD_START_ADDR_SP 64
17
18 #endif

CONFIG_SYS_INIT_SP_ADDR 值如下:

CONFIG_SYS_INIT_SP_OFFSET = 0x00020000 – 256 = 0x1FF00。
CONFIG_SYS_INIT_SP_ADDR = 0x00900000 + 0X1FF00 = 0X0091FF00,

在这里插入图片描述
此时 sp 指向 0X91FF00,这属于 IMX6UL/IMX6ULL 的内部 ram。

bic	sp, sp, #7 /* 8-byte alignment for ABI compliance */

上面这一句进行八字节对齐。堆栈指针 sp 对齐到 8 字节边界意味着它的地址在二进制表示中的最后三位应该是 000。这是因为 8 字节(即 2 的 3 次方)对齐的地址总是以 8 的倍数计算,而 8 的倍数的二进制表示总是以 000 结尾。当使用 bic sp, sp, #7 指令时,实际上是将 sp 的最低三位清零,因为 1111 1000(掩码取反后的结果)与任何数值进行“与”操作都会将这个数值的最低三位清零。这样,sp 就被强制对齐到最接近的低位 8 字节边界。

#ifdef CONFIG_SPL_BUILD
	ldr	r9, =gdata
#else
	sub	sp, sp, #GD_SIZE
	bic	sp, sp, #7
	mov	r9, sp
#endif
#endif

sp 指针减去 GD_SIZE, GD_SIZE 同样在 generic-asm-offsets.h 中定了,大小为248,此时 sp 的地址为 0X0091FF00-248=0X0091FE08,此时 sp 位置如图 32.2.2.2 所示:
在这里插入图片描述

将 sp 地址保存在 r9 寄存器中。将 ip 和 lr 压栈,调用函数 s_init,得,将入栈的 ip 和 lr 进行出栈,并将 lr 赋给 pc。
接下来来看s_init函数,s_init 函数定义在文件arch/arm/cpu/armv7/mx6/soc.c 中:

808 void s_init(void)
809 {
810 struct anatop_regs *anatop = (struct anatop_regs
*)ANATOP_BASE_ADDR;
811 struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
812 u32 mask480;
813 u32 mask528;
814 u32 reg, periph1, periph2;
815
816 if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||
817 is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL))
818 return;
819
820 /* Due to hardware limitation, on MX6Q we need to gate/ungate
821 * all PFDs to make sure PFD is working right, otherwise, PFDs
822 * may not output clock after reset, MX6DL and MX6SL have added
823 * 396M pfd workaround in ROM code, as bus clock need it
824 */
825
826 mask480 = ANATOP_PFD_CLKGATE_MASK(0) |
827 ANATOP_PFD_CLKGATE_MASK(1) |
828 ANATOP_PFD_CLKGATE_MASK(2) |
829 ANATOP_PFD_CLKGATE_MASK(3);
830 mask528 = ANATOP_PFD_CLKGATE_MASK(1) |
831 ANATOP_PFD_CLKGATE_MASK(3);
832
833 reg = readl(&ccm->cbcmr);
834 periph2 = ((reg & MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_MASK)
835 >> MXC_CCM_CBCMR_PRE_PERIPH2_CLK_SEL_OFFSET);
836 periph1 = ((reg & MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_MASK)
837 >> MXC_CCM_CBCMR_PRE_PERIPH_CLK_SEL_OFFSET);
838
839 /* Checking if PLL2 PFD0 or PLL2 PFD2 is using for periph clock */
840 if ((periph2 != 0x2) && (periph1 != 0x2))
841 mask528 |= ANATOP_PFD_CLKGATE_MASK(0);
842
843 if ((periph2 != 0x1) && (periph1 != 0x1) &&
844 (periph2 != 0x3) && (periph1 != 0x3))
845 mask528 |= ANATOP_PFD_CLKGATE_MASK(2);
846
847 writel(mask480, &anatop->pfd_480_set);
848 writel(mask528, &anatop->pfd_528_set);
849 writel(mask480, &anatop->pfd_480_clr);
850 writel(mask528, &anatop->pfd_528_clr);
851 }

第 816 行会判断当前 CPU 类型,如果 CPU 为 MX6SX、 MX6UL、 MX6ULL 或 MX6SLL中 的 任 意 一 种 , 那 么 就 会 直 接 返 回 , 相 当 于 s_init 函 数 什 么 都 没 做 。 所 以 对 于I.MX6UL/I.MX6ULL 来说, s_init 就是个空函数。从 s_init 函数退出以后进入函数 lowlevel_init,但是 lowlevel_init 函数也执行完成了,返回到了函数 cpu_init_crit,函数 cpu_init_crit 也执行完成了,最终返回到 save_boot_params_ret,函数调用路径如图所示:
在这里插入图片描述

_main 函数详解

_main 函数定义在文件 arch/arm/lib/crt0.S 。

示例代码 32.2.4.1 crt0.S 代码段
63 /*
64 * entry point of crt0 sequence
65 */
66
67 ENTRY(_main)
68
69 /*
70 * Set up initial C runtime environment and call board_init_f(0).
71 */
72
73 #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
74 ldr sp, =(CONFIG_SPL_STACK)
75 #else
76 ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
77 #endif
78 #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC
destination */
79 mov r3, sp
80 bic r3, r3, #7
81 mov sp, r3
82 #else
83 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
84 #endif
85 mov r0, sp
86 bl board_init_f_alloc_reserve
87 mov sp, r0
88 /* set up gd here, outside any C code */
89 mov r9, r0
90 bl board_init_f_init_reserve
91
92 mov r0, #0
93 bl board_init_f
94
95 #if ! defined(CONFIG_SPL_BUILD)
96
97 /*
98 * Set up intermediate environment (new sp and gd) and call
99 * relocate_code(addr_moni). Trick here is that we'll return
100 * 'here' but relocated.
101 */
102
103 ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
104 #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC
destination */
105 mov r3, sp
106 bic r3, r3, #7
107 mov sp, r3
108 #else
109 bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
110 #endif
111 ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
112 sub r9, r9, #GD_SIZE /* new GD is below bd */
113
114 adr lr, here
115 ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
116 add lr, lr, r0
117 #if defined(CONFIG_CPU_V7M)
118 orr lr, #1 /* As required by Thumb-only */
119 #endif
120 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
121 b relocate_code
122 here:
123 /*
124 * now relocate vectors
125 */
126
127 bl relocate_vectors
128
129 /* Set up final (full) environment */
130
131 bl c_runtime_cpu_setup /* we still call old routine here */
132 #endif
133 #if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
134 # ifdef CONFIG_SPL_BUILD
135 /* Use a DRAM stack for the rest of SPL, if requested */
136 bl spl_relocate_stack_gd
137 cmp r0, #0
138 movne sp, r0
139 movne r9, r0
140 # endif
141 ldr r0, =__bss_start /* this is auto-relocated! */
142
143 #ifdef CONFIG_USE_ARCH_MEMSET
144 ldr r3, =__bss_end /* this is auto-relocated! */
145 mov r1, #0x00000000 /* prepare zero to clear BSS */
146
147 subs r2, r3, r0 /* r2 = memset len */
148 bl memset
149 #else
150 ldr r1, =__bss_end /* this is auto-relocated! */
151 mov r2, #0x00000000 /* prepare zero to clear BSS */
152
153 clbss_l:cmp r0, r1 /* while not at end of BSS */
154 #if defined(CONFIG_CPU_V7M)
155 itt lo
156 #endif
157 strlo r2, [r0] /* clear 32-bit BSS word */
158 addlo r0, r0, #4 /* move to next */
159 blo clbss_l
160 #endif
161
162 #if ! defined(CONFIG_SPL_BUILD)
163 bl coloured_LED_init
164 bl red_led_on
165 #endif
166 /* call board_init_r(gd_t *id, ulong dest_addr) */
167 mov r0, r9 /* gd_t */
168 ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
169 /* call board_init_r */
170 #if defined(CONFIG_SYS_THUMB_BUILD)
171 ldr lr, =board_init_r /* this is auto-relocated! */
172 bx lr
173 #else
174 ldr pc, =board_init_r /* this is auto-relocated! */
175 #endif
176 /* we should not return here. */
177 #endif
178
179 ENDPROC(_main)

第 76 行,设置 sp 指针为 CONFIG_SYS_INIT_SP_ADDR,也就是 sp 指向 0X0091FF00。
第 83 行, sp 做 8 字节对齐。
第 85 行,读取 sp 到寄存器 r0 里面,此时 r0=0X0091FF00。
第 86 行,调用函数 board_init_f_alloc_reserve,此函数有一个参数,参数为 r0 中的值,也
就是 0X0091FF00,此函数定义在文件 common/init/board_init.c 中,内容如下:

56 ulong board_init_f_alloc_reserve(ulong top)
57 {
58 /* Reserve early malloc arena */
59 #if defined(CONFIG_SYS_MALLOC_F)
60 top -= CONFIG_SYS_MALLOC_F_LEN;
61 #endif
62 /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
63 top = rounddown(top-sizeof(struct global_data), 16);
64
65 return top;
66 }

函数 board_init_f_alloc_reserve

函数 board_init_f_alloc_reserve 主要是留出早期的 malloc 内存区域和 gd 内存区域,其中
CONFIG_SYS_MALLOC_F_LEN=0X400( 在 文 件 include/generated/autoconf.h 中 定 义 ) ,
sizeof(struct global_data)=248(GD_SIZE 值),完成以后的内存分布如图 32.2.4.1 所示:
在这里插入图片描述
函数 board_init_f_alloc_reserve 是有返回值的,返回值为新的 top 值,从图 32.2.4.1 可知,此时 top=0X0091FA00。将 r0 写入到 sp 里面, r0 保存着函数board_init_f_alloc_reserve 的返回值,所以这一句也就是设置 sp=0X0091FA00。第 89 行,将 r0 寄存器的值写到寄存器 r9 里面,因为 r9 寄存器存放着全局变量 gd 的地址。在文件 arch/arm/include/asm/global_data.h 中有如图 32.2.4.2 所示宏定义:
在这里插入图片描述
uboot 中定义了一个指向 gd_t 的指针 gd, gd 存放在寄存器 r9 里面的,因此 gd 是个全局变量。 gd_t 是个结构体,在 include/asm-generic/global_data.h 里面有定义,gd_定义如下:

示例代码 32.2.4.3 global_data.h 代码段
27 typedef struct global_data {
28 bd_t *bd;
29 unsigned long flags;
30 unsigned int baudrate;
31 unsigned long cpu_clk; /* CPU clock in Hz! */
32 unsigned long bus_clk;
33 /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
34 unsigned long pci_clk;
35 unsigned long mem_clk;
36 #if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
37 unsigned long fb_base; /* Base address of framebuffer mem */
38 #endif
......
121 #ifdef CONFIG_DM_VIDEO
122 ulong video_top; /* Top of video frame buffer area */
123 ulong video_bottom; /* Bottom of video frame buffer area */
124 #endif
125 } gd_t;

因此这一行代码就是设置 gd 所指向的位置,也就是 gd 指向 0X0091FA00。继续回到示例代码 32.2.4.1 中,第 90 行调用函数 board_init_f_init_reserve,此函数在文件common/init/board_init.c 中有定义,函数内容如下:

示例代码 32.2.4.4 board_init.c 代码段
110 void board_init_f_init_reserve(ulong base)
111 {
112 struct global_data *gd_ptr;
113 #ifndef _USE_MEMCPY
114 int *ptr;
115 #endif
116
117 /*
118 * clear GD entirely and set it up.
119 * Use gd_ptr, as gd may not be properly set yet.
120 */
121
122 gd_ptr = (struct global_data *)base;
123 /* zero the area */
124 #ifdef _USE_MEMCPY
125 memset(gd_ptr, '\0', sizeof(*gd));
126 #else
127 for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
128 *ptr++ = 0;
129 #endif
130 /* set GD unless architecture did it already */
131 #if !defined(CONFIG_ARM)
132 arch_setup_gd(gd_ptr);
133 #endif
134 /* next alloc will be higher by one GD plus 16-byte alignment */
135 base += roundup(sizeof(struct global_data), 16);
136
137 /*
138 * record early malloc arena start.
139 * Use gd as it is now properly set for all architectures.
140 */
141
142 #if defined(CONFIG_SYS_MALLOC_F)
143 /* go down one 'early malloc arena' */
144 gd->malloc_base = base;
145 /* next alloc will be higher by one 'early malloc arena' size */
146 base += CONFIG_SYS_MALLOC_F_LEN;
147 #endif
148 }

可以看出来上面的函数可以看出,此函数用于初始化 gd,其实就是清零处理。在你提供的代码段中,循环的停止条件是:

ptr < (int *)(gd_ptr + 1)

这个条件是基于指针算术的。让我们详细解释一下这个条件的意思和它如何工作:

127 for (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )
128 *ptr++ = 0;
  1. 指针初始化:循环首先将 ptr 初始化为 gd_ptr 的地址,但被转换为 int * 类型。这意味着 ptr 将按照 int 类型的大小(通常是4字节)来递增。

  2. 循环条件:循环的条件是 ptr 必须小于 gd_ptr + 1 的地址,同样转换为 int * 类型。这里的 gd_ptr + 1 表示地址增加了整个 struct global_data 结构的大小。将这个地址转换为 int * 意味着,ptr 在递增时将按照 int 类型的大小逐步逼近 gd_ptr + 1 的地址。

  3. 递增操作:在每次循环迭代中,ptr 递增,即 ptr++。由于 ptrint * 类型,这个递增是以 int 的大小(通常是4字节)为单位进行的。

  4. 循环执行:在每次迭代中,*ptr 被设置为 0,这样逐步将 struct global_data 结构中的每个 int 单位清零,直到 ptr 达到 gd_ptr + 1 的位置。

  5. 停止条件:当 ptr 达到或超过 gd_ptr + 1 的地址时,循环停止。这意味着整个 struct global_data 结构已被清零。
    这个循环确保了 struct global_data 结构的每个部分都被正确初始化为0,从而为系统的后续操作提供了一个干净、预期的状态。此函数还设置了gd->malloc_base 为 gd 基地址+gd 大=0X0091FA00+248=0X0091FAF8,在做 16 字节对齐,最终 gd->malloc_base=0X0091FB00,这个也就是 early malloc 的起始地址。
    第 92 行设置 R0 为 0。
    第 93 行,调用 board_init_f 函数,此函数定义在文件 common/board_f.c 中!主要用来初始化 DDR,定时器,完成代码拷贝等等,
    第 103 行,重新设置环境(sp 和 gd)、获取 gd->start_addr_sp 的值赋给 sp,在函数 board_init_f中会初始化 gd 的所有成员变量,其中 gd->start_addr_sp=0X9EF44E90, 所以这里相当于设置sp=gd->start_addr_sp=0X9EF44E90。 0X9EF44E90 是 DDR 中的地址,说明新的 sp 和 gd 将会存放到 DDR 中,而不是内部的 RAM 了。 GD_START_ADDR_SP=64,参考示例代码 32.2.2.4。
    第 109 行, sp 做 8 字节对齐。
    第 111 行,获取 gd->bd 的地址赋给 r9,此时 r9 存放的是老的 gd,这里通过获取 gd->bd 的地址来计算出新的 gd 的位置。 GD_BD=0,参考示例代码 32.2.2.4。在 U-Boot 中使用 [r9, #GD_BD] 这种形式的地址访问,是为了从全局数据结构 gd_t 中取出特定的成员变量。这里的 #GD_BD 是一个偏移量,它指向 gd_t 结构中的 bd 成员(即板级信息结构 bd_info 的指针)。让我们详细解析这个过程的原因和背景。
    全局数据结构 (gd_t)
    U-Boot 使用一个全局数据结构 gd_t 来存储整个启动过程中需要的状态信息和配置参数。这个结构包括了如下一些关键信息:

  • 内存大小
  • 时钟频率
  • 环境变量
  • 网络配置
  • 板级信息(通过 bd_info 结构)
    在 ARM 架构中,r9 寄存器在某些情况下被用作全局数据指针(Global Data Pointer, GDP)。在 U-Boot 的启动代码中,r9 被初始化为指向 gd_t 结构的起始地址。这样,任何时候需要访问全局数据时,可以直接通过 r9 进行。
    第 112 行,新的 gd 在 bd 下面,所以 r9 减去 gd 的大小就是新的 gd 的位置,获取到新的 gd的位置以后赋值给 r9。
    第 114 行,设置 lr 寄存器为 here,这样后面执行其他函数返回的时候就返回到了第 122 行的 here 位置处。
    第 115,读取 gd->reloc_off 的值复制给 r0 寄存器, GD_RELOC_OFF=68,参考示例代码32.2.2.4。
    第 116 行, lr 寄存器的值加上 r0 寄存器的值,重新赋值给 lr 寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的 uboot 存放的起始地址为 0X87800000,下面要将 uboot 拷贝到 DDR 最后面的地址空间出,将 0X87800000 开始的内存空出来),其中就包括here,因此 lr 中的 here 要使用重定位后的位置。
    第 120 行,读取 gd->relocaddr 的值赋给 r0 寄存器,此时 r0 寄存器就保存着 uboot 要拷贝的目的地址,为 0X9FF47000。 GD_RELOCADDR=48,参考示例代码 32.2.2.4。
    第 121 行,调用函数 relocate_code,也就是代码重定位函数,此函数负责将 uboot 拷贝到新的地方去,此函数定义在文件 arch/arm/lib/relocate.S 中稍后会详细分析此函数。
    继续回到示例代码 32.2.4.1 第 127 行,调用函数 relocate_vectors,对中断向量表做重定位,此函数定义在文件 arch/arm/lib/relocate.S 中,稍后会详细分析此函数。
    继续回到示例代码 32.2.4.1 第 131 行,调用函数 c_runtime_cpu_setup,此函数定义在文件arch/arm/cpu/armv7/start.S 中。
    第 141~159 行,清除 BSS 段。
    第 167 行,设置函数 board_init_r 的两个参数,函数 board_init_r 声明如下:board_init_r(gd_t *id, ulong dest_addr)第一个参数是 gd,因此读取 r9 保存到 r0 里面。
    第 168 行,设置函数 board_init_r 的第二个参数是目的地址,因此 r1= gd->relocaddr。
    第 174 行、调用函数 board_init_r,此函数定义在文件 common/board_r.c 中,稍后会详细的
    分析此函数。
    这个就是_main 函数的运行流程,在_main 函数里面调用了 board_init_f、 relocate_code、relocate_vectors 和 board_init_r 这 4 个函数,

board_init_f 函数详解

main 中会调用 board_init_f 函数, board_init_f 函数主要有两个工作:
①、初始化一系列外设,比如串口、定时器,或者打印一些消息等。
②、初始化 gd 的各个成员变量, uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linuxkernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比如 gd 应该存放到哪个位置, malloc 内存池应该存放到哪个位置等等。这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。
在这里插入图片描述

relocate_code 函数详解

79 ENTRY(relocate_code)
80 ldr r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
81 subs r4, r0, r1 /* r4 <- relocation offset */
82 beq relocate_done /* skip relocation */
83 ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end */
84
85 copy_loop:
86 ldmia r1!, {r10-r11} /* copy from source address [r1] */
87 stmia r0!, {r10-r11} /* copy to target address [r0] */
88 cmp r1, r2 /* until source end address [r2] */
89 blo copy_loop
90
91 /*
92 * fix .rel.dyn relocations
93 */
94 ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
95 ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
96 fixloop:
97 ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
98 and r1, r1, #0xff
99 cmp r1, #23 /* relative fixup? */
100 bne fixnext
101
102 /* relative fix: increase location by offset */
103 add r0, r0, r4
104 ldr r1, [r0]
105 add r1, r1, r4
106 str r1, [r0]
107 fixnext:
108 cmp r2, r3
109 blo fixloop
110
111 relocate_done:
112
113 #ifdef __XSCALE__
114 /*
115 * On xscale, icache must be invalidated and write buffers
116 * drained, even with cache disabled - 4.2.7 of xscale core
117 developer's manual */
118 mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */
119 mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
120 #endif
121
122 /* ARMv4- don't know bx lr but the assembler fails to see that */
123
124 #ifdef __ARM_ARCH_4__
125 mov pc, lr
126 #else
127 bx lr
128 #endif
129
130 ENDPROC(relocate_code)

第 80 行, r1=__image_copy_start,也就是 r1 寄存器保存源地址,由表 31.4.1.1 可知,
__image_copy_start=0X87800000。
第 81 行, r0=0X9FF47000,这个地址就是 uboot 拷贝的目标首地址。 r4=r0-r1=0X9FF47000-
0X87800000=0X18747000,因此 r4 保存偏移量。
第 82 行,如果在第 81 中, r0-r1 等于 0,说明 r0 和 r1 相等,也就是源地址和目的地址是一样的,那肯定就不需要拷贝了!执行 relocate_done 函数
第 83 行, r2=__image_copy_end, r2 中保存拷贝之前的代码结束地址,由表 31.4.1.1 可知_image_copy_end =0x8785dd54。第 83 行ldr r2, =__image_copy_end /* r2 <- SRC &__image_copy_end /
r2 寄存器加载了源代码的结束地址 __image_copy_end。
第 85-89 行 - copy_loop
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
这是一个循环,用于将数据从源地址 (r1) 复制到目标地址 (r0)。每次循环复制两个寄存器的数据(r10 和 r11),这相当于复制 8 个字节。
ldmia 和 stmia 是多重加载/存储指令,用于从内存地址读取/写入多个寄存器的数据。r1! 和 r0! 表示在数据传输后自动更新这些寄存器的值。
cmp 和 blo 指令用于检查是否已经复制到源代码的结束地址,如果没有,则继续循环。
在嵌入式系统中,尤其是在使用像 U-Boot 这样的引导加载器时,重定位是一个关键步骤,确保代码能在物理内存中的正确位置执行。这里的代码段(第 94 行到第 109 行)处理的是 .rel.dyn 段,这是动态重定位信息的一部分,用于调整程序运行时的内存引用,以确保它们指向正确的地址。
第 94-95 行

ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
  • 这两行加载 .rel.dyn 段的开始和结束地址到 r2r3 寄存器。.rel.dyn 段包含了需要进行动态重定位的所有项。

第 96-109 行 - fixloop

fixloop:
ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
and r1, r1, #0xff
cmp r1, #23 /* relative fixup? */
bne fixnext
add r0, r0, r4
ldr r1, [r0]
add r1, r1, r4
str r1, [r0]
fixnext:
cmp r2, r3
blo fixloop
  • ldmia r2!, {r0-r1}: 从 r2 指向的地址加载两个寄存器 r0r1,其中 r0 是需要修正的内存位置,r1 是修正类型和值。r2! 表示加载后 r2 的值自动增加,以指向下一个条目。
  • and r1, r1, #0xffcmp r1, #23: 这两行代码检查修正的类型是否为相对地址修正(类型 23 是 ELF 格式中常见的相对地址修正类型)。
  • bne fixnext: 如果不是相对地址修正,跳过此次修正。
  • add r0, r0, r4: 将偏移量 r4 加到原始位置 r0,更新位置到新的地址。
  • ldr r1, [r0]: 加载 r0 指向的地址的内容到 r1
  • add r1, r1, r4: 将偏移量 r4 加到 r1,进行地址修正。
  • str r1, [r0]: 将修正后的值存回 r0 指向的地址。
  • fixnextblo fixloop: 检查是否已经处理完所有重定位项,如果没有,则继续循环。

uboot 对于重定位后链接地址和运行地址不一致的解决方法就是采用位置无关码,在使用 ld 进行链接的时候使用选项“ -pie”生成位置无关的可执行文件。在文件arch/arm/config.mk 下有如下代码:

示例代码 32.2.6.4 config.mk 文件代码段
82 # needed for relocation
83 LDFLAGS_u-boot += -pie

使用“-pie”选项以后会生成一个.rel.dyn 段, uboot 就是靠这个.rel.dyn 来解决重定位问题的,在 u-bot.dis 的.rel.dyn 段中有如下所示内容:

示例代码 32.2.6.5 .rel.dyn 段代码段
1 Disassembly of section .rel.dyn:
2 3
8785da44 <__rel_dyn_end-0x8ba0>:
4 8785da44: 87800020 strhi r0, [r0, r0, lsr #32]
5 8785da48: 00000017 andeq r0, r0, r7, lsl r0
6 ......
7 8785dfb4: 87804198 ; <UNDEFINED> instruction: 0x87804198
8 8785dfb8: 00000017 andeq r0, r0, r7, lsl r0

第 7 行值为 0X87804198,第 8 行为 0X00000017,说明第 7 行的 0X87804198 是个 Label,
这个正是示例代码 32.2.6.3 中存放变量 rel_a 地址的那个 Label。根据前面的分析,只要将地址
0X87804198+offset 处的值改为重定位后的变量 rel_a 地址即可。我们猜测的是否正确,看一下
uboot 对.rel.dyn 段的重定位即可(示例代码代码 32.2.6.1 中的第 94~109 行), .rel.dyn 段的重定位
代码如下:
第 7 行值为 0X87804198,第 8 行为 0X00000017,说明第 7 行的 0X87804198 是个 Label,这个正是示例代码 32.2.6.3 中存放变量 rel_a 地址的那个 Label。根据前面的分析,只要将地址0X87804198+offset 处的值改为重定位后的变量 rel_a 地址即可。我们猜测的是否正确,看一下uboot 对.rel.dyn 段的重定位即可(示例代码代码 32.2.6.1 中的第 94~109 行), .rel.dyn 段的重定位代码如下:

示例代码 32.2.6.6 relocate.S 代码段
91 /*
92 * fix .rel.dyn relocations
93 */
94 ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */
95 ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
96 fixloop:
97 ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */
98 and r1, r1, #0xff
99 cmp r1, #23 /* relative fixup? */
100 bne fixnext
101
102 /* relative fix: increase location by offset */
103 add r0, r0, r4
104 ldr r1, [r0]
105 add r1, r1, r4
106 str r1, [r0]
107 fixnext:
108 cmp r2, r3
109 blo fixloop

第 94 行, r2=__rel_dyn_start,也就是.rel.dyn 段的起始地址。
第 95 行, r3=__rel_dyn_end,也就是.rel.dyn 段的终止地址。
第 97 行,从.rel.dyn 段起始地址开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器中, r0 存放低 4 字节的数据,也就是 Label 地址; r1 存放高 4 字节的数据,也就是 Label 标志。
第 98 行, r1 中给的值与 0xff 进行与运算,其实就是取 r1 的低 8 位。
第 99 行,判断 r1 中的值是否等于 23(0X17)。
第 100 行,如果 r1 不等于 23 的话就说明不是描述 Label 的,执行函数 fixnext,否则的话继续执行下面的代码。
第 103 行, r0 保存着 Label 值, r4 保存着重定位后的地址偏移, r0+r4 就得到了重定位后的Label 值。此时 r0 保存着重定位后的 Label 值,相当于 0X87804198+0X18747000=0X9FF4B198。
第 104,读取重定位后 Label 所保存的变量地址,此时这个变量地址还是重定位前的(相当于 rel_a 重定位前的地址 0X8785DA50),将得到的值放到 r1 寄存器中。
第 105 行 , r1+r4 即 可 得 到 重 定 位 后 的 变 量 地 址 , 相 当 于 rel_a 重 定 位 后 的0X8785DA50+0X18747000=0X9FFA4A50。
第 106 行,重定位后的变量地址写入到重定位后的 Label 中,相等于设置地址 0X9FF4B198处的值为 0X9FFA4A50。
第 108 行,比较 r2 和 r3,查看.rel.dyn 段重定位是否完成。
第 109 行,如果 r2 和 r3 不相等,说明.rel.dyn 重定位还未完成,因此跳到 fixloop 继续重定位.rel.dyn 段。
可以看出, uboot 中对.rel.dyn 段的重定位方法和我们猜想的一致。 .rel.dyn 段的重定位比较复杂一点,有点绕,因为涉及到链接地址和运行地址的问题。

relocate_vectors 函数详解

函数 relocate_vectors 用于重定位向量表,此函数定义在文件 relocate.S 中, 函数源码如下

示例代码 32.2.7.1 relocate.S 代码段
27 ENTRY(relocate_vectors)
28
29 #ifdef CONFIG_CPU_V7M
30 /*
31 * On ARMv7-M we only have to write the new vector address
32 * to VTOR register.
33 */
34 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
35 ldr r1, =V7M_SCB_BASE
36 str r0, [r1, V7M_SCB_VTOR]
37 #else
38 #ifdef CONFIG_HAS_VBAR
39 /*
40 * If the ARM processor has the security extensions,
41 * use VBAR to relocate the exception vectors.
42 */
43 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
44 mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
45 #else
46 /*
47 * Copy the relocated exception vectors to the
48 * correct address
49 * CP15 c1 V bit gives us the location of the vectors:
50 * 0x00000000 or 0xFFFF0000.
51 */
52 ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
53 mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */
54 ands r2, r2, #(1 << 13)
55 ldreq r1, =0x00000000 /* If V=0 */
56 ldrne r1, =0xFFFF0000 /* If V=1 */
57 ldmia r0!, {r2-r8,r10}
58 stmia r1!, {r2-r8,r10}
59 ldmia r0!, {r2-r8,r10}
60 stmia r1!, {r2-r8,r10}
61 #endif
62 #endif
63 bx lr
64
65 ENDPROC(relocate_vectors)

第 29 行,如果定义了 CONFIG_CPU_V7M 的话就执行第 30~36 行的代码,这是 Cortex-M
内核单片机执行的语句,因此对于 I.MX6ULL 来说是无效的。
第 38 行,如果定义了 CONFIG_HAS_VBAR 的话就执行此语句,这个是向量表偏移, CortexA7 是支持向量表偏移的。而且,在.config 里面定义了 CONFIG_HAS_VBAR,因此会执行这个分支。
第 43 行, r0=gd->relocaddr,也就是重定位后 uboot 的首地址,向量表肯定是从这个地址开始存放的。
第 44 行,将 r0 的值写入到 CP15 的 VBAR 寄存器中,也就是将新的向量表首地址写入到寄存器 VBAR 中,设置向量表偏移。

board_init_r 函数详解

board_init_f 函数,在此函数里面会调用一系列的函数来初始化一些外设和 gd 的成员变量。但是 board_init_f 并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数 board_init_r 来完成的, board_init_r 函数定义在文件 common/board_r.c中,在 U-Boot 的启动过程中,board_init_fboard_init_r 是两个关键的函数,它们在系统初始化阶段扮演不同的角色。board_init_f(通常定义在 board.c 文件中)主要负责早期硬件的初始化,包括设置最基本的运行环境以便系统能够继续加载和执行更多的代码。一旦 board_init_f 完成,控制权就会传递给 board_init_r 来完成更为复杂的初始化任务。
board_init_f 的主要职责包括:

  1. 低级硬件初始化:包括时钟、内存、CPU 核心配置等。
  2. 早期串行控制台的设置:以便可以输出启动过程中的调试信息。
  3. 栈的设置:在某些架构中,这一步非常关键,因为后续的代码需要一个工作的栈。
  4. 全局数据结构 (gd) 的初始化:设置全局数据区,这对后续的功能至关重要。
  5. 重定位到 RAM:将 U-Boot 的代码从非易失性存储器(如 NOR/NAND Flash)复制到 RAM 中。

board_init_r 的职责:
board_init_r 函数开始执行时,大部分硬件已经处于可用状态,此函数的任务是完成 U-Boot 的高级初始化,并准备过渡到操作系统的加载。board_init_r 的具体职责通常包括:

  1. 完整的设备初始化

    • 初始化剩余的外设和驱动,例如网络接口、USB 控制器等。
    • 在某些板子上,可能还包括图形显示接口的初始化。
  2. 环境变量的加载

    • 从存储介质(如 EEPROM、Flash)中加载环境变量。
    • 设置网络配置,如 IP 地址、服务器地址等。
  3. 内存管理系统的配置

    • 设置动态内存分配器(如 malloc)。
  4. 启动脚本的执行

    • 执行在环境变量中定义的启动脚本,这些脚本可以包括加载操作系统映像、执行系统检查等操作。
  5. 操作系统映像的加载

    • 加载并准备启动操作系统,比如 Linux、VxWorks 等。
    • 这可能包括从网络、硬盘、SD 卡或其他存储介质读取系统映像。
  6. 命令行界面的提供

    • 如果启动脚本没有配置为自动启动操作系统,U-Boot 会提供一个命令行界面供用户手动操作。
  7. 系统服务的启动

    • 启动可能需要的背景服务,如网络时间协议(NTP)客户端等。

run_main_loop 函数详解

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

示例代码 32.2.9.1 board_r.c 文件代码段
753 static int run_main_loop(void)
754 {
755 #ifdef CONFIG_SANDBOX
756 sandbox_main_loop_init();
757 #endif
758 /* main_loop() can return to retry autoboot, if so just run it
again */
759 for (;;)
760 main_loop();
761 return 0;
762 }

第 759 行和第 760 行是个死循环,“for(;;)”和“while(1)”功能一样,死循环里面就一个main_loop 函数, main_loop 函数定义在文件 common/main.c 里面,代码如下:

示例代码 32.2.9.2 main.c 文件代码段
43 /* We come here after U-Boot is initialised and ready to process
commands */
44 void main_loop(void)
45 {
46 const char *s;
47
48 bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
49
50 #ifndef CONFIG_SYS_GENERIC_BOARD
51 puts("Warning: Your board does not use generic board. Please
read\n");
52 puts("doc/README.generic-board and take action. Boards not\n");
53 puts("upgraded by the late 2014 may break or be removed.\n");
54 #endif
55
56 #ifdef CONFIG_VERSION_VARIABLE
57 setenv("ver", version_string); /* set version variable */
58 #endif /* CONFIG_VERSION_VARIABLE */
59
60 cli_init();
61
62 run_preboot_environment_command();
63
64 #if defined(CONFIG_UPDATE_TFTP)
65 update_tftp(0UL, NULL, NULL);
66 #endif /* CONFIG_UPDATE_TFTP */
67
68 s = bootdelay_process();
69 if (cli_process_fdt(&s))
70 cli_secure_boot_cmd(s);
71
72 autoboot_command(s);
73
74 cli_loop();
75 }

这段代码是 U-Boot 启动完成后执行的主循环(main_loop),它处理用户输入的命令和一些自动化的启动任务。下面是对代码中每个关键部分的详细解释:

第 43-75 行:main_loop 函数定义

第 48 行

bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

这行代码标记了启动阶段的一个重要时刻,即主循环的开始。这有助于性能分析和调试,可以追踪 U-Boot 启动过程中的不同阶段。

第 50-54 行

#ifndef CONFIG_SYS_GENERIC_BOARD
puts("Warning: Your board does not use generic board. Please read\n");
puts("doc/README.generic-board and take action. Boards not\n");
puts("upgraded by the late 2014 may break or be removed.\n");
#endif

这些行是一个预处理指令,检查是否定义了 CONFIG_SYS_GENERIC_BOARD。如果没有定义,会输出一条警告信息,提示开发者他们的板子不使用通用板架构。这是提醒开发者可能需要更新他们的板级支持包(BSP)以适应 U-Boot 的更新。

第 56-58 行

#ifdef CONFIG_VERSION_VARIABLE
setenv("ver", version_string); /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

如果定义了 CONFIG_VERSION_VARIABLE,这段代码会在环境变量中设置版本信息。这允许在运行时通过环境变量获取 U-Boot 的版本。

第 60 行

cli_init();

初始化命令行界面(CLI)。这是为了准备接收和处理来自用户的命令。

第 62 行

run_preboot_environment_command();

执行在环境变量中设置的预启动命令。这些命令通常用于设置网络、测试硬件或其他启动前必要的配置。

第 64-66 行

#if defined(CONFIG_UPDATE_TFTP)
update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

如果定义了 CONFIG_UPDATE_TFTP,这段代码会尝试通过 TFTP 更新固件或其他资源。这是一种远程更新系统的方法,常用于无需物理访问设备的场景。

第 68 行

s = bootdelay_process();

处理启动延迟,通常用于给用户一定时间来中断自动启动过程。s 变量可能会包含用户在启动延迟期间输入的命令。

第 69-70 行

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

这里首先处理可能来自启动延迟的命令(如修改设备树的命令)。如果有命令被处理,然后执行安全启动相关的命令。

第 72 行

autoboot_command(s);

尝试自动启动系统,通常这会加载和启动操作系统,除非用户已经通过启动延迟输入了其他命令。

第 74 行

cli_loop();

进入持续的命令行界面循环,这允许用户输入和执行更多命令。

cli_loop 函数详解

cli_loop 函数是 uboot 的命令行处理函数,我们在 uboot 中输入各种命令,进行各种操作就是有 cli_loop 来处理的,此函数定义在文件 common/cli.c 中,函数内容如下:

示例代码 32.2.10.1 cli.c 文件代码段
202 void cli_loop(void)
203 {
204 #ifdef CONFIG_SYS_HUSH_PARSER
205 parse_file_outer();
206 /* This point is never reached */
207 for (;;);
208 #else
209 cli_simple_loop();
210 #endif /*CONFIG_SYS_HUSH_PARSER*/
211 }

这段代码是 cli_loop 函数的实现,它是 U-Boot 命令行界面(CLI)的主循环部分。这个函数负责处理用户输入的命令,并根据配置决定使用哪种解析器来处理这些命令。这里涉及到两种可能的命令解析器:Hush shell 和一个简单的命令行解析器。下面是对代码的详细解释:

第 202-211 行:cli_loop 函数定义

第 204 行

#ifdef CONFIG_SYS_HUSH_PARSER

这是一个预处理指令,检查是否定义了 CONFIG_SYS_HUSH_PARSER。这个宏定义决定了 U-Boot 是否使用 Hush shell 作为命令解析器。Hush shell 是一个相对复杂的 shell,支持更多的脚本功能和语法,如条件判断、循环等。

第 205 行

parse_file_outer();

如果使用 Hush parser,这个函数被调用来解析和执行命令。parse_file_outer 是处理多行输入和复杂脚本的函数,它能够处理来自不同输入源的命令,包括直接的用户输入、环境变量或者通过网络接收的脚本。

第 206-207 行

/* This point is never reached */
for (;;);

parse_file_outer 函数执行之后,理论上这部分代码是不会被执行的,因为 parse_file_outer 应当持续处理输入,直到系统重启或关闭。for (;;); 是一个无限循环,确保如果出现任何异常导致 parse_file_outer 返回,系统不会继续向下执行并退出 cli_loop 函数,这是一种安全措施。

第 208-210 行

#else
cli_simple_loop();
#endif /*CONFIG_SYS_HUSH_PARSER*/

如果没有定义 CONFIG_SYS_HUSH_PARSER,则使用一个更简单的命令行解析器。cli_simple_loop 函数是一个更基础的命令处理循环,它处理用户输入但不支持 Hush shell 的高级特性。这种模式适用于资源受限或对 shell 功能要求不高的环境。

bootz 启动 Linux 内核过程

images 全局变量

不管是 bootz 还是 bootm 命令,在启动 Linux 内核的时候都会用到一个重要的全局变量:images, images 在文件 cmd/bootm.c 中有如下定义:

示例代码 32.3.1.1 images 全局变量
43 bootm_headers_t images; /* pointers to os/initrd/fdt images */

images 是 bootm_headers_t 类型的全局变量, bootm_headers_t 是个 boot 头结构体,在文件
include/image.h 中的定义如下(删除了一些条件编译代码):

示例代码 32.3.1.2 bootm_headers_t 结构体
304 typedef struct bootm_headers {
305 /*
306 * Legacy os image header, if it is a multi component image
307 * then boot_get_ramdisk() and get_fdt() will attempt to get
308 * data from second and third component accordingly.
309 */
310 image_header_t *legacy_hdr_os; /* image header pointer */
311 image_header_t legacy_hdr_os_copy; /* header copy */
312 ulong legacy_hdr_valid;
313
......
333
334 #ifndef USE_HOSTCC
335 image_info_t os; /* OS 镜像信息 */
336 ulong ep; /* OS 入口点 */
337
338 ulong rd_start, rd_end; /* ramdisk 开始和结束位置 */
339
340 char *ft_addr; /* 设备树地址 */
341 ulong ft_len; /* 设备树长度 */
342
343 ulong initrd_start; /* initrd 开始位置 */
344 ulong initrd_end; /* initrd 结束位置 */
345 ulong cmdline_start; /* cmdline 开始位置 */
346 ulong cmdline_end; /* cmdline 结束位置 */
347 bd_t *kbd;
348 #endif
349
350 int verify; /* getenv("verify")[0] != 'n' */
351
352 #define BOOTM_STATE_START (0x00000001)
353 #define BOOTM_STATE_FINDOS (0x00000002)
354 #define BOOTM_STATE_FINDOTHER (0x00000004)
355 #define BOOTM_STATE_LOADOS (0x00000008)
356 #define BOOTM_STATE_RAMDISK (0x00000010)
357 #define BOOTM_STATE_FDT (0x00000020)
358 #define BOOTM_STATE_OS_CMDLINE (0x00000040)
359 #define BOOTM_STATE_OS_BD_T (0x00000080)
360 #define BOOTM_STATE_OS_PREP (0x00000100)
361 #define BOOTM_STATE_OS_FAKE_GO (0x00000200)/*'Almost' run the OS*/
362 #define BOOTM_STATE_OS_GO (0x00000400)
363 int state;
364
365 #ifdef CONFIG_LMB
366 struct lmb lmb; /* 内存管理相关,不深入研究 */
367 #endif
368 } bootm_headers_t;

第 335 行的 os 成员变量是 image_info_t 类型的,为系统镜像信息。
第 352~362 行这 11 个宏定义表示 BOOT 的不同阶段。
接下来看一下结构体 image_info_t,也就是系统镜像信息结构体,此结构体在文件include/image.h 中的定义如下:

示例代码 32.3.1.3 image_info_t 结构体
292 typedef struct image_info {
293 ulong start, end; /* blob 开始和结束位置*/
294 ulong image_start, image_len; /* 镜像起始地址(包括 blob)和长度 */
295 ulong load; /* 系统镜像加载地址*/
296 uint8_t comp, type, os; /* 镜像压缩、类型, OS 类型 */
297 uint8_t arch; /* CPU 架构 */
298 } image_info_t;

全局变量 images 会在 bootz 命令的执行中频繁使用到,相当于 Linux 内核启动的“灵魂”。

bootz 命令

bootz 命令的执行函数为 do_bootz,在文件 cmd/bootm.c 中有如下定义:
示例代码 32.3.2.1 do_bootz 函数

622 int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const
argv[])
623 {
624 int ret;
625
626 /* Consume 'bootz' */
627 argc--; argv++;
628
629 if (bootz_start(cmdtp, flag, argc, argv, &images))
630 return 1;
631
632 /*
633 * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
634 * disable interrupts ourselves
635 */
636 bootm_disable_interrupts();
637
638 images.os.os = IH_OS_LINUX;
639 ret = do_bootm_states(cmdtp, flag, argc, argv,
640 BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
641 BOOTM_STATE_OS_GO,
642 &images, 1);
643
644 return ret;
645 }

第 629 行,调用 bootz_start 函数,
第 636 行,调用函数 bootm_disable_interrupts 关闭中断。
第 638 行,设置 images.os.os 为 IH_OS_LINUX,也就是设置系统镜像为 Linux,表示我们要启动的是 Linux 系统!后面会用到 images.os.os 来挑选具体的启动函数。
第 639 行,调用函数 do_bootm_states 来执行不同的 BOOT 阶段,这里要执行的 BOOT 阶
段有: BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO 和BOOTM_STATE_OS_GO。

bootz_start 函数

bootz_srart 函数也定义在文件 cmd/bootm.c 中,函数内容如下:

示例代码 32.3.3.1 bootz_start 函数
578 static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,
579 char * const argv[], bootm_headers_t *images)
580 {
581 int ret;
582 ulong zi_start, zi_end;
583
584 ret = do_bootm_states(cmdtp, flag, argc, argv,
585 BOOTM_STATE_START, images, 1);
586
587 /* Setup Linux kernel zImage entry point */
588 if (!argc) {
589 images->ep = load_addr;
590 debug("* kernel: default image load address = 0x%08lx\n",
591 load_addr);
592 } else {
593 images->ep = simple_strtoul(argv[0], NULL, 16);
594 debug("* kernel: cmdline image address = 0x%08lx\n",
595 images->ep);
596 }
597
598 ret = bootz_setup(images->ep, &zi_start, &zi_end);
599 if (ret != 0)
600 return 1;
601
602 lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
603
604 /*
605 * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
606 * have a header that provide this informaiton.
607 */
608 if (bootm_find_images(flag, argc, argv))
609 return 1;
610
......
619 return 0;
620 }

第 584 行,调用函数 do_bootm_states,执行 BOOTM_STATE_START 阶段。
第 593 行,设置 images 的 ep 成员变量,也就是系统镜像的入口点,使用 bootz 命令启动系统的时候就会设置系统在 DRAM 中的存储位置,这个存储位置就是系统镜像的入口点,因此 images->ep=0X80800000。
第 598 行,调用 bootz_setup 函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像文件,并且会打印出镜像相关信息, bootz_setup 函数稍后会讲解。
第 608 行,调用函数 bootm_find_images 查找 ramdisk 和设备树(dtb)文件,但是我们没有用到 ramdisk,因此此函数在这里仅仅用于查找设备树(dtb)文件。
bootz_setup 函数,此函数定义在文件 arch/arm/lib/bootm.c 中,函数内容如下:

示例代码 32.3.3.2 bootz_setup 函数
370 #define LINUX_ARM_ZIMAGE_MAGIC 0x016f2818
371
372 int bootz_setup(ulong image, ulong *start, ulong *end)
373 {
374 struct zimage_header *zi;
375
376 zi = (struct zimage_header *)map_sysmem(image, 0);
377 if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
378 puts("Bad Linux ARM zImage magic!\n");
379 return 1;
380 }
381
382 *start = zi->zi_start;
383 *end = zi->zi_end;
384
385 printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image,
386 *start, *end);
387
388 return 0;
389 }

这段代码是 bootz_setup 函数的实现,它用于设置并验证 ARM 架构下的 Linux 内核 zImage。这个函数确保加载的 zImage 是有效的,并提取出内核映像的起始和结束地址。下面是对这段代码的详细解释:

第 370 行

#define LINUX_ARM_ZIMAGE_MAGIC 0x016f2818

这行定义了一个宏 LINUX_ARM_ZIMAGE_MAGIC,它是一个魔数(magic number),用于验证 zImage 的有效性。这个特定的值是 Linux ARM zImage 文件的标准标识符,用来确认文件确实是一个有效的 ARM zImage。

第 372 行

int bootz_setup(ulong image, ulong *start, ulong *end)

这是 bootz_setup 函数的定义。函数接收三个参数:

  • image:这是 zImage 的内存起始地址。
  • startend:这两个是指针,用于存放解析出的内核映像的起始和结束地址。

第 374-375 行

struct zimage_header *zi;

定义了一个指向 zimage_header 结构体的指针 zi。这个结构体通常包含了 zImage 的元数据,如魔数、起始地址和结束地址等。

第 376 行

zi = (struct zimage_header *)map_sysmem(image, 0);

调用 map_sysmem 函数将物理地址 image 映射为虚拟地址,并将结果转换为 zimage_header 结构体指针。这允许代码直接通过结构体访问 zImage 的头部信息。map_sysmem 的第二个参数 0 表示没有特定的大小限制。

第 377-380 行

if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) {
    puts("Bad Linux ARM zImage magic!\n");
    return 1;
}

这里检查 zImage 的魔数是否与预定义的 LINUX_ARM_ZIMAGE_MAGIC 相匹配。如果不匹配,输出错误信息并返回 1,表示错误。

第 382-383 行

*start = zi->zi_start;
*end = zi->zi_end;

如果魔数检查通过,则从 zImage 头部读取内核映像的起始和结束地址,并通过指针参数返回这些值。

第 385-386 行

printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image,
       *start, *end);

输出内核映像的地址信息,包括映射的起始地址和解析出的起始和结束地址。这对于调试和验证加载过程非常有用。

第 388 行

return 0;

如果一切正常,函数返回 0,表示成功。
接下来看一下函数 bootm_find_images,此函数定义在文件 common/bootm.c 中,函数内容如下:

示例代码 32.3.3.3 bootm_find_images 函数
225 int bootm_find_images(int flag, int argc, char * const argv[])
226 {
227 int ret;
228
229 /* find ramdisk */
230 ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,
231 &images.rd_start, &images.rd_end);
232 if (ret) {
233 puts("Ramdisk image is corrupt or invalid\n");
234 return 1;
235 }
236
237 #if defined(CONFIG_OF_LIBFDT)
238 /* find flattened device tree */
239 ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,
240 &images.ft_addr, &images.ft_len);
241 if (ret) {
242 puts("Could not find a valid device tree\n");
243 return 1;
244 }
245 set_working_fdt_addr((ulong)images.ft_addr);
246 #endif
......
258 return 0;
259 }

第 230~235 行是跟查找 ramdisk,但是我们没有用到 ramdisk,因此这部分代码不用管。
第 237~244 行是查找设备树(dtb)文件,找到以后就将设备树的起始地址和长度分别写到images 的 ft_addr 和 ft_len 成员变量中。我们使用 bootz 启动 Linux 的时候已经指明了设备树在DRAM 中的存储地址,因此 images.ft_addr=0X83000000,长度根据具体的设备树文件而定,比如我现在使用的设备树文件长度为 0X8C81,因此 images.ft_len=0X8C81。

do_bootm_states 函数

do_bootz 最 后 调 用 的 就 是 函 数 do_bootm_states , 而 且 在 bootz_start 中 也 调 用 了do_bootm_states 函数 ,看来 do_bootm_states 函数 还是 个香饽 饽。 此函数 定义 在文 件common/bootm.c 中,函数代码如下:

bootm_os_get_boot_func 函数

do_bootm_linux 函数

在这里插入图片描述

标签:r0,函数,流程,好难,boot,start,init,gd,CONFIG
From: https://www.cnblogs.com/bathwind/p/18216751

相关文章

  • netcore构建webservice以及调用的完整流程
    目录构建前置准备编写服务挂载服务处理SoapHeader调用添加服务调用服务补充内容构建前置准备框架版本要求:netcore3.1以上引入nuget包SoapCore编写服务1.编写服务接口示例usingSystem.ServiceModel;namespaceServices;[ServiceContract(Namespace="http://Demo.WebService......
  • springboot项目中数据库连接加密方法
    1、maven添加相应版本的依赖,比如com.github.ulisesbocchiojasypt-spring-boot-starter2.1.22、设置项目启动参数,此参数作为加密的盐值,比如-Djasypt.encryptor.password=盐值3、下载jasypt-xxx.jar包,用此jar生成加密后的数据库连接密码从这里下载http://www.jasypt.org/do......
  • 基于SpringBoot+Vue+uniapp的IT技术交流和分享平台的详细设计和实现(源码+lw+部署文档
    文章目录前言具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • SpringBoot继承JWT token实现权限的验证(从头开始)
    目录概述前提:我们需要知道的文件的用处第1步:数据库的连接第2步:定义一个标准化响应对象的类第3步:编写请求数据库数据代码第4步:自定义异常处理第5步:导入依赖第6步:自定义拦截器第7步:配置拦截器第8步:生成token第9步:开始测试代码第10步:vue请求示例扩展:自定义注解AuthAc......
  • 升鲜宝供应链管理系统重构版发布(技术点:Java8、mysql8.0 uniapp、vue、android、web 框
    升鲜宝供应链管理系统重构版发布(技术点:Java8、mysql8.0uniapp、vue、android、web框架:Vue3+SpringBoot3),界面功能(一)  升鲜宝供应链管理系统重构版发布(技术点:Java8、mysql8.0uniapp、vue、android、web框架:Vue3+SpringBoot3),界面功能(一) 1.登录与申请试用界......
  • SpringBoot_03
    测试环境IDEA2020.1.2SpringBoot2.7.17jdk 8postman测试工具一.报错分析:1.400 报错分析:正常情况下如果程序报错400则是前台传到后台的数据与后台接收的数据类型不相同    如: 前台传Spring------>后天接收Data     添加的时候传时间......
  • 计算机Java项目|基于SpringBoot的IT技术交流和分享平台的设计与实现
    作者主页:编程指南针作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师主要内容:Java项目、Python项目、前端项目、人工智能与大数据、简历模板、学习资料、面试题库、技术互......
  • 计算机Java项目|基于Springboot的“衣依”服装销售平台的设计与实现
    作者主页:编程指南针作者简介:Java领域优质创作者、CSDN博客专家、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、腾讯课堂常驻讲师主要内容:Java项目、Python项目、前端项目、人工智能与大数据、简历模板、学习资料、面试题库、技术互......
  • Springboot概述
    随着互联网的兴起,Spring势如破竹地占据了Java领域轻量级开发的王者之位。随着Java语言的发展以及市场开发的需求,Spring推陈出新,推出了全新的SpringBoot框架。SpringBoot是Spring家族的一个子项目,其设计初衷是为了简化.Spring配置,从而让用户可以轻松构建独立运行的......
  • Windows系统使用Docker部署Focalboard团队协作工具详细流程
    文章目录前言1.使用Docker本地部署Focalboard1.1在Windows中安装Docker1.2使用Docker部署Focalboard2.安装Cpolar内网穿透工具3.实现公网访问Focalboard4.固定Focalboard公网地址前言本篇文章将介绍如何使用Docker本地部署Focalboard项目管理工具,并且结合cp......