6.3 U-boot 启动流程详解

news2024/11/17 23:25:51

  通过对 uboot 启动流程的梳理,我们就可以掌握一些外设是在哪里被初始化的,这样当我们需要修改这些外设驱动的时候就会心里有数。

一、链接脚本 u-boot.lds

  分析 uboot 的启动流程,首先要找到“入口”,找到第一行程序在哪里。程序的链接是由链接脚本来决定的,所以通过链接脚本可以找到程序的入口。如果没有编译过 uboot 的话链接脚本为 arch/arm/cpu/u-boot.lds。但是这个不是最终使用的链接脚本,最终的链接脚本是在这个链接脚本的基础上生成的。编译一下 uboot,编译完成以后就会在 uboot 根目录下生成 u-boot.lds文件。跟上次一样的编译方法。

  打开 u-boot.lds:

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)        # 代码当前入口点:_start
SECTIONS
{
 . = 0x00000000;
 . = ALIGN(4);
 .text :
 {
  *(.__image_copy_start)
  *(.vectors)                                # vectors 段保存中断向量表
  arch/arm/cpu/armv7/start.o (.text*)        # 编译出来的代码放到中断向量表后面
}
......
 .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.*) }
}

  ENTRY(_start) 为代码当前入口点: _start, _start 在文件 arch/arm/lib/vectors.S 中有定义:

.globl _start
    .section ".vectors", "ax"

_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    ARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */

    .globl _reset
    .globl _undefined_instruction
    .globl _software_interrupt
    .globl _prefetch_abort
    .globl _data_abort
    .globl _not_used
    .globl _irq
    .globl _fiq

  _start 后面就是中断向量表,.section:是一个汇编器指令,用于指定代码的存储段;".vectors":这是存储段的名称,被双引号括起来表示它是一个字符串;"ax":这是存储段的属性。在这种情况下,"ax"表示该段是可执行(executable)且可写(writable)的。

   使用如下命令在 uboot 中查找“__image_copy_start”: 

grep -nR "__image_copy_start"

  打开 u-boot.map,找到该位置。

  u-boot.map 是 uboot 的映射文件,可以从此文件看到某个文件或者函数链接到了哪个地址,从上图的 1629 行可以看到__image_copy_start 为 0Xc0100000,而.text 的起始地址也是0Xc0100000。

  我们知道了 vectors.S 的代码是存在 vectors 段中的。从上图可以看出, vectors 段的起始地址也是 0Xc0100000,说明整个 uboot 的起始地址就是 0Xc0100000。 

二、U-Boot 启动流程 

2.1  reset 函数源码

  从 u-boot.lds 中我们已经知道了入口点是 arch/arm/lib/vectors.S 文件中的_start。

.globl _start
    .section ".vectors", "ax"

_start:                                # 开始中断向量表
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
    .word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
    ARM_VECTORS
#endif /* !defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK) */

    .globl _reset                     #....globl指示告诉汇编器,_reset这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号
    .globl _undefined_instruction
    .globl _software_interrupt
    .globl _prefetch_abort
    .globl _data_abort
    .globl _not_used
    .globl _irq
    .globl _fiq                        #...都是中断向量表

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

reset:
	/* Allow the board to save important registers */
	b	save_boot_params                # 从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        # 也只有一个跳转语句

  save_boot_params_ret函数代码如下:

save_boot_params_ret:
#ifdef CONFIG_ARMV7_LPAE					// 由于没有定义CONFIG_ARMV7_LPAE,所以下面的都不会运行
/*
 * check for Hypervisor support
 */
	mrc	p15, 0, r0, c0, c1, 1		@ read ID_PFR1
	and	r0, r0, #CPUID_ARM_VIRT_MASK	@ mask virtualization bits
	cmp	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
	 */
	mrs	r0, cpsr		// 读取寄存器cpsr的值并保存到r0寄存器
	and	r1, r0, #0x1f		@ mask mode bits	// 将寄存器r0的值与0x1f进行运算,结果保存到r1寄存器,目的是为了提取cpsr的bit0~bit4 的5位,五位分别为M4 M3 M2 M1 M0,M[4:0]这五位用来设置处理器的工作模式
	teq	r1, #0x1a		@ test for HYP mode		// 判断r1寄存的值是否等于0X1a(11010),也就是判断当前处理器是否是Hyp模式
	bicne	r0, r0, #0x1f		@ clear all mode bits	// 如果如果 r1 和 0X1A 不相等,也就是 CPU 不处于 Hyp 模式的话就将 r0 寄存器的 bit0~5 进行清零,其实就是清除模式位
	orrne	r0, r0, #0x13		@ set SVC mode		// 如果处理器不处于 Hyp 模式的话就将 r0 的寄存器的值与 0x13 进行或运算,0x13(10011),也就是处理器进入 SVC 模式。
	orr	r0, r0, #0xc0		@ disable FIQ and IRQ  // r0 寄存器的值再与 0xC0 进行或运算,那么 r0 寄存器此时的值就是 0xD3, cpsr的 I 位和 F 位分别控制 IRQ 和 FIQ 这两个中断的开关,设置为 1 就关闭了 FIQ 和 IRQ!
	msr	cpsr,r0		// 将 r0 寄存器写回到 cpsr 寄存器中。完成设置 CPU 处于 SVC32 模式,并且关闭FIQ 和 IRQ 这两个中断。
Cortex-A7工作模式
M[4:0]模式
10000User(usr)
10001FIQ(fiq)
10010IRQ(irq)
10011Supervisor(svc)
10110Monitor(mon)
10111Abort(abt)
11010Hyp(hyp)
11011Undefined(und)
11111System(sys)

  代码继续往下执行:

/* 这段代码其实就是在设置向量表重定位 */
/*
 * Setup vector:
 * (OMAP4 spl TEXT_BASE is not 32 byte aligned.
 * Continue to use ROM code vector only in OMAP4 spl)
 */
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))		// 如果没有定义CONFIG_OMAP44XX和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			// 读取从 CP15 的 c1, c0, 0 寄存器中读取值,并将其存储到 r0 寄存器中
	bic	r0, #CR_V		@ V = 0						// 清除 SCTLR 寄存器中的 bit13
	mcr	p15, 0, r0, c1, c0, 0	@ Write CP15 SCTLR Register		// 将 r0 寄存器的值重新写入到寄存器 SCTLR 中

#ifdef CONFIG_HAS_VBAR
	/* Set vector address in CP15 VBAR register */
	ldr	r0, =_start				    //  r0寄存器的值为_start, _start就是整个uboot的入口地址,其值为0XC0100000,相当于 uboot 的起始地址,因此 0Xc0100000 也是向量表的起始地址。
	mcr	p15, 0, r0, c12, c0, 0	@Set VBAR		// 将 r0 寄存器的值(向量表起始地址)写入到 CP15 的 c12 寄存器中,也就是 VBAR 寄存器
#endif
#endif

  CR_V 在 arch/arm/include/asm/system.h 中有如下所示定义 :

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

  SCTLR 寄存器结构如下图所示: 

  从上图可以看出, bit13 为 V 位,此位是向量表控制位,当为 0 的时候向量表基地址为 0X00000000,软件可以重定位向量表。为 1 的时候向量表基地址为 0XFFFF0000,软件不能重定位向量表。这里将 V 清零,目的就是为了接下来的向量表重定位。

  代码继续往下执行:

	/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT		// 如果没有定义CONFIG_SKIP_LOWLEVEL_INIT条件成立。我们没有定义CONFIG_SKIP_LOWLEVEL_INIT所以条件成立。
#ifdef CONFIG_CPU_V7A					//.config定义了CONFIG_CPU_V7A,所以往下执行
	bl	cpu_init_cp15		// bl:进行函数调用或跳转
#endif
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY	// 没有定义CONFIG_SKIP_LOWLEVEL_INIT_ONLY,往下执行
	bl	cpu_init_crit
#endif
#endif

  函数 cpu_init_cp15 用来设置 CP15 相关的内容,此函数同样在 start.S文件中定义的,代码如下: 

ENTRY(cpu_init_cp15)
	/*
	 * Invalidate L1 I/D
	 */
	mov	r0, #0			@ set up for MCR
	mcr	p15, 0, r0, c8, c7, 0	@ invalidate TLBs
	mcr	p15, 0, r0, c7, c5, 0	@ invalidate icache
	mcr	p15, 0, r0, c7, c5, 6	@ invalidate BP array
	mcr     p15, 0, r0, c7, c10, 4	@ DSB
	mcr     p15, 0, r0, c7, c5, 4	@ ISB
......
	mov	pc, r5			@ back to my caller
ENDPROC(cpu_init_cp15)

  函数 cpu_init_crit 也在是定义在 start.S 文件中,函数内容如下: 

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。 

2.2   lowlevel_init 函数

   函数 lowlevel_init主要完成RAM的初始化,也就是通过写控制RAM的寄存器,对寄存器的存取方式进行控制。

  函数 lowlevel_init 在文件 arch/arm/cpu/armv7/lowlevel_init.S 中定义,内容如下: 

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

.pushsection .text.s_init, "ax"
WEAK(s_init)
	bx	lr
ENDPROC(s_init)
.popsection

.pushsection .text.lowlevel_init, "ax"
WEAK(lowlevel_init)
	/*
	 * Setup a temporary stack. Global data is not available yet.
	 */
#if defined(CONFIG_SPL_BUILD) && defin	ed(CONFIG_SPL_STACK)
	ldr	sp, =CONFIG_SPL_STACK
#else
	ldr	sp, =CONFIG_SYS_INIT_SP_ADDR	/* 用于将寄存器sp(堆栈指针寄存器)加载为一个特定的地址 */
#endif
	bic	sp, sp, #7 /* 8-byte alignment for ABI compliance */		/* 寄存器sp的值进行8字节对齐 */
#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	/* sp 指针减去 GD_SIZE */
	bic	sp, sp, #7			/* 对寄存器sp做8个字节对齐,此时的sp地址为:0XC1000000 - 240 = 0XC00FFDC0 */
	mov	r9, sp		/* 将sp的值放在r9寄存器中 */
#endif
#endif
	/*
	 * Save the old lr(passed in ip) and the current lr to stack
	 */
	push	{ip, lr}	/* 将寄存器 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		/* 调用s_init,但s_init没用 */
	pop	{ip, pc}	/* 入栈的 ip 和 lr 进行出栈,并将 lr 赋给 pc */
ENDPROC(lowlevel_init)
.popsection		/* 当前的程序段从汇编器的堆栈中弹出 */

  CONFIG_SYS_INIT_SP_ADDR 在include/configs/stm32mp1.h 文件中,在 stm32mp1.h 中有如下所示定义: 

#define CONFIG_SYS_SDRAM_BASE			STM32_DDR_BASE
#define CONFIG_SYS_INIT_SP_ADDR			CONFIG_SYS_TEXT_BASE

  CONFIG_SYS_TEXT_BASE 的定义在.config 文件中定义:

  所以 CONFIG_SYS_INIT_SP_ADDR 的值为 0XC0100000。此时 sp 指向0XC0100000。 

    GD_SIZE地址:

#define GENERATED_GBL_DATA_SIZE 240 /* (sizeof(struct global_data) + 15) & ~15	@ */
#define GENERATED_BD_INFO_SIZE 80 /* (sizeof(struct bd_info) + 15) & ~15	@ */
#define GD_SIZE 240 /* sizeof(struct global_data)	@ */
#define GD_BD 0 /* offsetof(struct global_data, bd)	@ */
#define GD_MALLOC_BASE 164 /* offsetof(struct global_data, malloc_base)	@ */
#define GD_RELOCADDR 64 /* offsetof(struct global_data, relocaddr)	@ */
#define GD_RELOC_OFF 84 /* offsetof(struct global_data, reloc_off)	@ */
#define GD_START_ADDR_SP 80 /* offsetof(struct global_data, start_addr_sp)	@ */
#define GD_NEW_GD 88 /* offsetof(struct global_data, new_gd)	@ */

  lowlevel_init 运行完后,就返回函数 cpu_init_crit,函数 cpu_init_crit 也执行完成了,最终返回到 save_boot_params_ret,接下来要执行的是save_boot_params_ret 中的_main 函数,接下来分析_main 函数。 

2.3  _main 函数 

  _main 函数定义在文件 arch/arm/lib/crt0.S 中,函数内容如下: 

ENTRY(_main)

/*
 * 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)
#else
	ldr	r0, =(CONFIG_SYS_INIT_SP_ADDR)	/* 前面条件全部不满足, sp 指向 0XC0100000 */
#endif
	bic	r0, r0, #7	/* sp 8字节对齐 */
	mov	sp, r0		/* 把r0的值放在sp寄存器中 */
	bl	board_init_f_alloc_reserve	/* 调用这个函数 */
	mov	sp, r0		/* 将r0的值写入sp寄存器中, r0 保存着函数board_init_f_alloc_reserve 的返回值,所以这一句也就是设置 sp=0XC00FCF10。 */
	/* set up gd here, outside any C code */
	mov	r9, r0		/* 将 r0 寄存器的值写到寄存器 r9 里面,因为 r9 寄存器存放着全局变量 gd 的地址 */
	bl	board_init_f_init_reserve	/* 调用这个函数 */

#if defined(CONFIG_SPL_EARLY_BSS)
	SPL_CLEAR_BSS
#endif

	mov	r0, #0
	bl	board_init_f		/* 调用此函数,此函数定义在common/board_f.c 中, 主要用来初始化平台相关的 API,定时器,完成代码拷贝等 */

#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 */	/* 重新设置环境(sp 和 gd),获取 gd->start_addr_sp 的值赋给 sp,在函数 board_init_f中会初始化 gd 的所有成员变量,最终 gd->start_addr_sp=0XF3AEDD20,所以这里相当于设置sp=gd->start_addr_sp=0XF3AEDD20。 GD_START_ADDR_SP=80 */
	bic	r0, r0, #7	/* sp 8字节对齐 */
	mov	sp, r0
	ldr	r9, [r9, #GD_NEW_GD]		/* r9 <- gd->new_gd */	/*获取 gd->new_gd 的地址赋给 r9,此时 r9 存放的是老的 gd,这里通过获取gd->new_gd 的地址来计算出新的 gd 的位置。 GD_BD=0 */

	adr	lr, here	/* 设置 lr 寄存器为 here,这样后面执行其他函数返回的时候就返回到了 here 位置处 */
	ldr	r0, [r9, #GD_RELOC_OFF]		/* r0 = gd->reloc_off */	/* 读取 gd->reloc_off 的值复制给 r0 寄存器, GD_RELOC_OFF=84 */
	add	lr, lr, r0	/* lr 寄存器的值加上 r0 寄存器的值,重新赋值给 lr 寄存器 因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的 uboot 存放的起始地址为 0XC0100000,下面要将 uboot 拷贝到 DDR 最后面的地址空间出,将 0XC0100000 开始的内存空出来),其中就包括here,因此 lr 中的 here 要使用重定位后的位置。*/
#if defined(CONFIG_CPU_V7M)
	orr	lr, #1				/* As required by Thumb-only */		/* 与1进行或操作返回lr寄存器中 */
#endif
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr */	/* 读取 gd->relocaddr 的值赋给 r0 寄存器,此时 r0 寄存器就保存着 uboot 要拷贝的目的地址,为 0XF5C46000。 GD_RELOCADDR=40 */
	b	relocate_code	/* 调用函数 relocate_code,也就是代码重定位函数,此函数负责将 uboot 拷贝到新的地方去 */

here:
/*
 * now relocate vectors
 */
	bl	relocate_vectors	/* 调用函数 relocate_vectors,对中断向量表做重定位,此函数定义在文件 arch/arm/lib/relocate.S */

/* 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_EARLY_BSS)
	SPL_CLEAR_BSS
#endif

# ifdef CONFIG_SPL_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_SPL_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)

  其中 common/init/board_init.c 函数定义在文件中 board_init_f_alloc_reserve 函数代码如下:

ulong board_init_f_alloc_reserve(ulong top)
{
	/* Reserve early malloc arena */
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
	top -= CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif
	/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
	top = rounddown(top-sizeof(struct global_data), 16);

	return top;
}

  malloc内存区域:用于在堆(heap)区域动态分配内存。通过调用 malloc 函数,可以请求指定大小的连续内存块,并返回该内存块的首地址。 malloc 内存区域通常用于动态分配内存,以满足程序运行时不确定内存需求的情况。它在堆区分配的内存块可被手动释放,也可以在程序结束时由操作系统自动回收。

  gd(Global Data)内存区域是指程序在运行时存储全局变量和静态变量的内存区域。gd 内存区域中的变量具有全局作用域,在整个程序执行过程中都存在,并且会在程序加载时就进行内存分配。

  函数 board_init_f_alloc_reserve 主要是留出早期的 malloc 内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X3000(在文件 include/generated/autoconf.h 中定义),另外sizeof(struct global_data)=240,也就是 GD_SIZE 值。内存结构如下图所示: 

  函数 board_init_f_alloc_reserve 是有返回值的,返回值为新的 top 值,此时 top=0XC00FCF10(地址刚好是 16 字节对齐)。 

  _main 函数中的r9在文件 arch/arm/include/asm/global_data.h  中有定义:

  从上图可以看出, uboot 中定义了一个指向 gd_t 结构体类型的指针: gd, gd 存放在寄存器 r9 里面的,因此 gd 是个全局变量。 gd_t 是个结构体,在 include/asm-generic/global_data.h里面有定义, gd_定义如下: 

typedef struct global_data {
	bd_t *bd;
	unsigned long flags;
	unsigned int baudrate;
	unsigned long cpu_clk;		/* CPU clock in Hz!		*/
	unsigned long bus_clk;
	/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
	unsigned long pci_clk;
	unsigned long mem_clk;
......
#if defined(CONFIG_TRANSLATION_OFFSET)
	fdt_addr_t translation_offset;	/* optional translation offset */
#endif
#if CONFIG_IS_ENABLED(WDT)
	struct udevice *watchdog_dev;
#endif
} gd_t;

  因此这一行代码就是设置 gd 所指向的位置,也就是 gd 指向 0XC00FCF10。 

    _main 函数中的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);
#endif
	/* 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;
	/* next alloc will be higher by one 'early malloc arena' size */
	base += CONFIG_VAL(SYS_MALLOC_F_LEN);
#endif

	if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
		board_init_f_init_stack_protection();
}

  此函数用于初始化 gd,其实就是清零处理。另外,此函数还设置了gd->malloc_base为gd基地址+gd大小=0XC00FCF10+240=0XC00FD000,这个也就是early malloc的起始地址。 

2.3   board_init_f  函数 

  _main 中会调用 board_init_f 函数, board_init_f 函数主要有两个工作: 

1、初始化一系列外设,比如串口、定时器,或者打印一些消息等。

2、初始化 gd 的各个成员变量, uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。这么做的目的是给 Linux 腾出空间,防止 Linux kernel 覆盖掉 uboot,将 DRAM 前面的区域完整的空出来。在拷贝之前肯定要给 uboot 各部分分配好内存位置和大小,比如 gd 应该存放到哪个位置, malloc 内存池应该存放到哪个位置等等。 这些信息都保存在 gd 的成员变量中,因此要对 gd 的这些成员变量做初始化。最终形成一个完整的内存“分配图”,在后面重定位 uboot 的时候就会用到这个内存“分配图”。 

  board_init_f 函数定义在文件 common/board_f.c 中定义,代码如下:

void board_init_f(ulong boot_flags)
{
	gd->flags = boot_flags;		// 初始化gd->flags = boot_flags
	gd->have_console = 0;		// 设置gd->have_console = 0

	if (initcall_run_list(init_sequence_f))		// 通过函数 initcall_run_list 来运行初始化序列 init_sequence_f 里面的一些列函数, init_sequence_f 里面包含了一系列的初始化函数, 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
}

  init_sequence_f 也定义在文件 common/board_f.c 中 ,代码如下(条件编译代码删除): 

static const init_fnc_t init_sequence_f[] = {
	setup_mon_len,        // 设置gd的mon_len成员变量,此处为__bss_end -_start,也就是整个代码的长度。 0XC01B966C-0XC0100000=0XB966C,这个就是代码长度
	fdtdec_setup,        // 函数设置 gd 的 fdt_dlob 指针变量, fdt_blob 保存着设备树(.dtb)文件地址。此处为_image_binary_end,也就是我们的设备地址为 0XC01C2938
	initf_malloc,        // initf_malloc 函数初始化 gd 中跟 malloc 有关的成员变量,比如 malloc_limit,此函数会设置 gd->malloc_limit = CONFIG_SYS_MALLOC_F_LEN=0X3000。malloc_limit 表示 malloc内存池大小。
	log_init,            // log_init 函数将 struct log_driver 结构体加入到 gd->log_head 的循环链表中,并初始化 gd->default_log_level。
	initf_bootstage,	// initf_bootstage 函数为 gd->bootstage 分配空间,并初始化 gd->bootstage。
	setup_spl_handoff,    // setup_spl_handoff 函数和 SPL 相关, STM32MP1 是使用 TF-A 引导 U-BOOT,此函数什么都没有做。
	initf_console_record,    // STM32MP1的 uboot 没有定义宏 CONFIG_CONSOLE_RECORD,所以此函数直接返回 0
	arch_cpu_init,		// 初始化架构相关的内容, CPU 级别的操作
	mach_cpu_init,		// 初始化 SOC 相关的内容, CPU 级别的操作
	initf_dm,    // 驱动模型有关的初始化操作。如果定义了 CONFIG_DM,则调用 dm_init_and_scan 初始化并扫描系统所有的 device。如果定义了 CONFIG_TIMER_EARLY,调用 dm_timer_init 初始化 driver model 所需的 timer 。 STM32MP1 定义了 CONFIG_DM,会扫描所有的 device
	arch_cpu_init_dm,    // 空函数
	/* get CPU and bus clocks according to the environment variable */
	get_clocks,		// 获取时钟值
	timer_init,	   // timer_init,初始化定时器。主要用来初始化 gd->arch.timer_rate_hz,设置定时器频率
	env_init,	    // env_init 函数是和环境变量有关的,设置 gd 的成员变量 env_addr,也就是环境变量的保存地址
	init_baud_rate,		// 用于初始化波特率,根据环境变量 baudrate 来初始化gd->baudrate
	serial_init,		// 初始化串口,设置 gd 的 flags 成员变量为 GD_FLG_SERIAL_READY,开启当前选中的串口
	console_init_f,		// 设置 gd->have_console 为 1,表示有个控制台,此函数也将前面暂存在缓冲区中的数据通过控制台打印出来
	display_options,	// 通过串口输出一些信息,比如 uboot 版本号,编译时间等
	display_text_info,	// 打印一些文本信息,如果开启 UBOOT 的 DEBUG 功能的话就会输出 text_base、 bss_start、 bss_end。
#if defined(CONFIG_PPC) || defined(CONFIG_SH) || defined(CONFIG_X86)
	checkcpu,        // PPC、X86、SH 架构的芯片会执行, ARM 架构不会执行
#endif
	print_cpuinfo,		// 用于打印 CPU 信息
	show_board_info,    // 打印板子信息,如果在 uboot 中使用了设备树,那么此函数会先从设备树里面获取到 model 属性信息并且打印出出来,然后调用 checkboard 函数获取板子信息并打印出来
	INIT_FUNC_WATCHDOG_INIT    // 初始化看门狗,对于STM32MP1是空函数
	INIT_FUNC_WATCHDOG_RESET    // 复位看门狗,是空函数 
	init_func_vid,    // 一个空函数,返回值为 0
	announce_dram_init,    // 输出字符串"DRAM:"
	dram_init,		// dram_init,并非真正的初始化 DDR,只是设置 gd->ram_size 的值,对于正点原子 STM32MP1 开发板核来说, DDR 大小为 1G,所以 gd->ram_size 的值为 0X40000000
	INIT_FUNC_WATCHDOG_RESET
	testdram,    // 测试 DRAM,空函数
	INIT_FUNC_WATCHDOG_RESET
	init_post,
	INIT_FUNC_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
	 */
	setup_dest_addr,    // 设置目的地址,设置 gd->ram_base 、 gd->ram_size、gd->ram_top、 gd->relocaddr 这四个的值

	reserve_pram,

	reserve_round_4k,    // 对 gd->relocaddr 对4KB对齐 , 因为gd->relocaddr=0XF6000000,已经是 4K 对齐了,所以调整后不变

	reserve_mmu,    // 留出 MMU 的 TLB 表的位置,分配 MMU 的 TLB 表内存以后会对 gd->relocaddr 做 64K 字节对齐

	reserve_video,    // 留出显示的内存
	reserve_trace,    // 留出跟踪调试的内存, STM32MP1 没有用到最终功能,因此无需留出对应的内存区域
	reserve_uboot,    // 留出重定位后的 uboot 所占用的内存区域, uboot 所占用大小由gd->mon_len 所指定,留出 uboot 的空间以后还要对 gd->relocaddr 做 4K 字节对齐,并且重新设置 gd->start_addr_sp
	reserve_malloc,    // reserve_malloc,留出 malloc 区域以及 MMU 相关内存
	reserve_board,    // 留出板子 bd 所占的内存区, bd 是结构体 bd_t, bd_t 大小为 80 字节
	setup_machine,    // 设置机器 ID, linux 启动的时候会和这个机器 ID 匹配,如果匹配的话 linux 就会启动正常。但是!! STM32MP1 不用这种方式了,这是以前老版本的 uboot 和linux 使用的,新版本使用设备树了,因此此函数无效
	reserve_global_data,    // 保留出新的 gd_t 的内存区域, gd_t 结构体大小为 240B
	reserve_fdt,    // 留出设备树相关的内存区域
	reserve_bootstage,    // 保留 bootstage 的内存区域
	reserve_bloblist,    // 不执行
	reserve_arch,        // 空函数
	reserve_stacks,    // 留出栈空间。如果使能 IRQ 的话还要留出 IRQ 相应的内存,具体工作是由 arch/arm/lib/stack.c 文件中的函数 arch_reserve_stacks 完成
/* 以上,uboot重定位完成 */
    
    dram_init_banksize,    // 设置 dram 信息,就是设置 gd->bd->bi_dram[0].start 和gd->bd->bi_dram[0].size,后面会传递给 linux 内核,告诉 linux DRAM 的起始地址和大小
	show_dram_config,    // 用于显示 DRAM 的配置
	display_new_sp,    // 显示新的 sp 位置,也就是 gd->start_addr_sp

	reloc_fdt,        // 重定位 fdt,使用 memcpy 函数将原来存储在 gd->fdt_blob 中的设备树文件拷贝到 gd->new_fdt 处,然后将 gd->new_fdt 赋值给 gd->fdt_blob。经过这一步设备树拷贝到了新地址,而且 gd->fdt_blob 也保存了新的设备树地址
	reloc_bootstage,    // 用于 bootstage 重定位,使用 memcpy 函数将 gd->bootstage 中原来的 bootstage 拷贝到新的 gd->new_bootstage 处。然后将 gd->new_bootstage 赋值给gd->bootstage
	reloc_bloblist,
	setup_reloc,    // 设置 gd 的其他一些成员变量,供后面重定位的时候使用,并且将以前的 gd 拷贝到 gd->new_gd 处

	NULL,
};

  至此, board_init_f 函数就执行完成了,最终的内存分配如下图所示: 

2.3   relocate_code  函数 

  relocate_code 函数是用于代码拷贝的,此函数定义在文件 arch/arm/lib/relocate.S 中,代码如下: 

先暂时不看了,这代码看得我头有点疼。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1298823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Delphi】FMX开发 ios 和 android 异同点(踩坑记)

目录 一、前言 二、补充下基础知识 1. APP程序事件&#xff1a;TApplicationEvent 2. APP内置Web服务器或者UDP服务端或者TCP服务端 三、iOS 和 android 平台的不同点 1. TApplicationEvent的不同点&#xff1a;以下不同点&#xff0c;请仔细阅读&#xff01; 2. APP内置…

嵌入式培训-Linux系统及C编程高级-DAY6-linux shell脚本编程

Shell脚本概述 Shell脚本是利用 shell 的功能所写的一个程序。这个程序是使用纯文本文件&#xff0c;将一些 shell 的语法与命令&#xff08;含外部命令&#xff09;写在里面&#xff0c;搭配正则表达式、管道命令与数据流重定向等功能 Shell脚本编写流程 Shell脚本的文件扩展名…

luceda ipkiss教程 45:在版图上加LOGO

**在设计版图时往往需要加上公司或者学校的LOGO,只需要LOGO的图片&#xff0c;通过代码就可以将LOGO加到版图上&#xff0c;比如&#xff1a; ** 通过代码可以得到版图上的LOGO: ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/8daea33f74f342ed9f506ae5d8cea711.…

求导公式,求导的四则运算,复合函数求导

求导公式 求导的四则运算 复合函数求导

原码、反码、补码、大端、小端

原码、反码、补码 计算机中的整数有三种2进制表示方法&#xff0c;即原码、反码和补码。 三种表示方法均有符号位和数值位两部分&#xff0c;符号位都是用0表示“正”&#xff0c;用1表示“负”&#xff0c; 而数值位&#xff1a; 正数的原、反、补码都相同。负整数的三种表…

接触huggingface

接触huggingface finetuning llama 按照https://github.com/samlhuillier/code-llama-fine-tune-notebook/tree/main中的教程一步一步了解。 pip install !pip install githttps://github.com/huggingface/transformers.gitmain bitsandbytes # we need latest transforme…

QGIS003:【06工程工具栏】-新建打开保存工程、新建打印布局、布局管理器、样式管理器

摘要:QGIS工程工具栏包括新建工程、打开工程、保存工程、新建打印布局、布局管理器、样式管理器等选项,本文介绍各选项的基本操作。 实验数据: 链接:https://pan.baidu.com/s/1f8tteqbum-Ekc7ZPdQRuEg?pwd=0s1i 提取码:0s1i 一、新建工程 【工具功能】:该功能用于创…

JAVA+SSM+springboot+MYSQL企业物资库存进销存管理系统

。该系统从两个对象&#xff1a;由管理员和员工来对系统进行设计构建。主要功能包括首页、个人中心、员工管理、项目信息管理、仓库信息管理、供应商管理、项目计划管理、物资库存管理、到货登记管理、物资出库管理、物资入库管理等功能进行管理。本企业物资管理系统方便员工快…

12.8 作业 C++

使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff0c;在槽函数中判断ui界面上输入的账号是否为"admin"&#xff0c;密码是否为…

SQL语句---创建索引

介绍 使用sql语句实现创建索引。使用索引的优点是提升查询效率&#xff0c;使查询速度更快。 命令 alter table 表名 add 索引类型 索引名 (字段名);用alter table添加索引与create index区别&#xff0c;可以参考文章&#xff1a;https://www.cnblogs.com/jelly12345/p/173…

Arduino驱动MPX5700AP气压传感器(压力传感器)

目录 1、传感器特性 2、硬件原理图 3、控制器和传感器连线图 4、驱动程序 4.1、采集数据 4.2、校准传感器 MPX5700AP测量范围15~700kPa&#xff0c;支持I2C数字输出&#xff0c;可以根据已知气压值进行标定&#xff0c;可以快速、准确的测量管路或其他环境中的气压值。…

mysql的BIT数值类型

MySQL :: MySQL 8.2 Reference Manual :: 11.1.5 Bit-Value Type - BIT MySQL :: MySQL 8.2 Reference Manual :: 9.1.5 Bit-Value Literals BIT类型用来存放bit值&#xff0c;每一位是0或者1&#xff0c;允许1-64位。 例如&#xff0c;下面表定义了new这列的类型为8位的BIT…

线性回归实战

3.1 使用正规方程进行求解 3.1.1 简单线性回归 公式 &#xff1a; y w x b y wx b ywxb 一元一次方程&#xff0c;在机器学习中一元表示一个特征&#xff0c;b表示截距&#xff0c;y表示目标值。 使用代码进行实现&#xff1a; 导入包 import numpy as np import matp…

普冉(PUYA)单片机开发笔记(8): ADC-DMA多路采样

概述 上一个实验完成了基于轮询的多路 ADC 采样&#xff0c;现在尝试跑一下使用 DMA 的 ADC 多路采样。厂家例程中有使用 DMA 完成单路采样的&#xff0c;根据这个例程提供的模板&#xff0c;再加上在 STM32 开发同样功能的基础&#xff0c;摸索着尝试。 经过多次修改和测试&…

stm32使用多串口不输出无反应的问题(usart1、usart2)

在使用stm32c8t6单片机时&#xff0c;由于需要使用两个串口usart1 、usart2。usart1用作程序烧录、调试作用&#xff0c;串口2用于与其它模块进行通信。 使用串口1时&#xff0c;正常工作&#xff0c;使用串口2时&#xff0c;无反应。查阅了相关资料串口2在PA2\PA3 引脚上。RX…

Tomcat部署开源站点JPress

前言 JPress使用Java开发&#xff0c;是我们常见的开源博客系统。JPress是一个开源的WordPress插件&#xff0c;它提供了一个简单而强大的方式来创建企业级站点。该插件包括许多特性&#xff0c;例如主题定制、页面构建器、性能优化、SEO、安全、电子商务和社交媒体整合等。使用…

【无标题】安装环境

这里写目录标题 清华镜像加速 安装cuda11.3 PyTorch 1.10.1https://pytorch.org/get-started/previous-versions/[如果没有可以点Previous pyTorch Versions&#xff0c;这里面有更多的更早的版本](https://pytorch.org/get-started/locally/) 复制非空文件夹cp: -r not specif…

【calcitonin ; 降钙素 ;降钙素原】

Parathyroid_Hormone -甲状旁腺激素 PTH &#xff1b; 特立帕肽&#xff1b;

在git使用SSH密钥进行github身份认证学习笔记

1.生成ssh密钥对 官网文档&#xff1a;Https://docs.github.com/zh/authentication&#xff08;本节内容对应的官方文档&#xff0c;不清晰的地方可参考此内容&#xff09; 首先&#xff0c;启动我们的git bush&#xff08;在桌面右键&#xff0c;点击 Git Bush Here &#xf…

Qt Creator设置IDE的字体、颜色、主题样式

Qt是一款开源的、跨平台的C开发框架&#xff0c;支持Windows、Linux、Mac系统&#xff0c;从1995发布第一版以来&#xff0c;发展迅猛&#xff0c;最开始是用于Nokia手机的Symbian(塞班)系统和应用程序开发&#xff0c;现在是用于嵌入式软件、桌面软件(比如WPS、VirtualBox)、A…