u-boot移植篇——u-boot启动流程分析 上

本系列使用u-boot版本为u-boot-2018.01;

一、U-Boot启动流程详解

通过前面分析上文:u-boot移植篇——了解u-boot的分析,我们可以从u-boot.lds链接脚本入手开始分析u-boot,入口点是_start,_start 在文件 arch/arm/lib/vectors.S 中有定义,节选源码如下:

        .macro ARM_VECTORS
	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
	.endm

前期做了一些异常向量跳转的加载,当一个异常或中断发生时,CPU根据异常,在异常(中断)向量表中找到对应的异常向量,然后执行异常向量处的跳转指令,CPU跳转到对应的异常处理程序执行。复位异常向量的指令“ b reset”决定 u-boot 启动后将自动跳转到标号 reset 处执行。标号reset 在arch/arm/cpu/armv7/start.S中定义,节选代码如下:

	.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
save_boot_params_ret:
#ifdef 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
	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

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

	/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_cp15
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
	bl	cpu_init_crit
#endif
#endif

	bl	_main
arch/arm/cpu/armv7/start.S节选源码--reset
* reset:开始就是标号reset的入口,也可以成为函数; * 从reset函数下来,`b save_boot_params`,跳转到`save_boot_params`函数,函数源码如下:
/*************************************************************************
 *
 * 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
ENDPROC(save_boot_params)
	.weak	save_boot_params

继续跳转save_boot_params_ret函数,save_boot_params_ret函数原型如下:

save_boot_params_ret:
#ifdef 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
	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

这里涉及到一下基础寄存器的读取和配置,一般我们可以不做过于深入的理解,可以从注释可以了解大概做了什么工作,比如:

  • 第16行,讲cpsr寄存器的内容保存到r0寄存器,cpsr寄存器是什么:CPSR也叫程序状态寄存器,CPSR中包含条件码标志、中断禁止位、当前处理器模式以及其他状态和控制信息;
  • 第17行,讲读取到的r0的值与上立即数0x1f,再将结果保存到r1寄存器中,目的是取出cpsr的低5位,而cpsr的低5位刚好又是控制位,用于设置处理器的工作模式的,所以也可以根据上面节选源码的注释进行直接理解即可;
  • 后面就不一一解读了,有兴趣的可以继续深入理解,这就进行模式判断,设置处理器进入超级管理模式SVC模式,然后关闭FIQ和IRQ两种中断;
  • 继续回到arch/arm/cpu/armv7/start.S节选源码–reset
#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

这里涉及到一个宏,CONFIG_SPL_BUILD,如果启动CONFIG_SPL_BUILD宏,一般会生成两个uboot相关的文件,一个为MLO(引导uboot)也叫SPL(Secondary Program Loader 的简称, 也就是第二阶段程序加载器)用于初始化DDR内存并将uboot主体加载到内存中的,剩下一个就是uboot主体镜像,一般TI的芯片比如AM335x、AM572x等芯片会使用CONFIG_SPL_BUILD来编译,而我目前使用zynq好像一般用FSBL(first stage boot loader 第一阶段的加载程序)比较多,FSBL和SPL在功能上是类似的,不用过于纠结。

  • 第3行,读取CP15协处理器中c1寄存器的值到r0,至于CP15协处理器是什么,大家自行补充,CP15东西太多,这里不喧宾夺主介绍一遍了;
  • 第4行,CR_V有段定义#define CR_V (1 << 13) /* Vectors relocated to 0xffff0000 */,这里主要将r0寄存器中的第13位清除,该位代表SCTLR寄存器的V位向量表控制位,清零表示软件可以重定位向量表
  • 第5行,将清零了第13位的r0重写回CP15 SCTLR寄存器中;
  • 第8行,设置 r0 寄存器的值为_start,_start就是整个uboot 的入口地址,其值为0x4000000,相当于uboot的起始地址,因此0x4000000也是向量表的起始地址;
  • 第9行,将r0寄存器的值(向量表值)写入到CP15的c12寄存器中,也就是VBAR寄存器。所以这段程序的作用总的来说就是设置向量表重定位的。

Keep going

	/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_cp15
#ifndef CONFIG_SKIP_LOWLEVEL_INIT_ONLY
	bl	cpu_init_crit
#endif
#endif
  • 两个定义,如果没有定义CONFIG_SKIP_LOWLEVEL_INITCONFIG_SKIP_LOWLEVEL_INIT_ONLY,则跳转执行cpu_init_cp15cpu_init_crit,我是都没有定义的,所以连个函数都需要跳转执行;

cpu_init_cp15

该函数太长了,我在这里做了一部分删减,只分析大概的内容,不进行太细节的,节选源码如下:

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

	/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0
	bic	r0, r0, #0x00002000	@ clear bits 13 (--V-)
	bic	r0, r0, #0x00000007	@ clear bits 2:0 (-CAM)
	orr	r0, r0, #0x00000002	@ set bit 1 (--A-) Align
	orr	r0, r0, #0x00000800	@ set bit 11 (Z---) BTB
#ifdef CONFIG_SYS_ICACHE_OFF
	bic	r0, r0, #0x00001000	@ clear bit 12 (I) I-cache
#else
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-cache
#endif
	mcr	p15, 0, r0, c1, c0, 0

...
...
	mov	pc, r5			@ back to my caller
ENDPROC(cpu_init_cp15)
  • 我们在阅读源码的时候,尽量不要掠过英文注释,一般在工作中需要快速完成工作的情况下,阅读源码自带的注释可以减少很多开发时间,cpu_init_cp15函数主要是对CP15协处理器进行初始化的,比如初始化TLB、icache等,这些一般都不需要更改,有兴趣的可以自行去学习;

cpu_init_crit

#if !defined(CONFIG_SKIP_LOWLEVEL_INIT) && \
	!defined(CONFIG_SKIP_LOWLEVEL_INIT_ONLY)
/*************************************************************************
 *
 * 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
  • 单指令跳转到lowlevel_init,开始设置pll、mux和内存初始化

1.1 lowlevel_init()函数

该函数对不同的CPU有不同的定义,一般在arch/arm/cpu/armv7/lowlevel_init.S,而比如本篇使用的硬件是zynq的,他的lowlevel_init.s定义在arch/arm/mach-zynq/lowlevel_init.S下,像armv7下的,主要做了初始化临时堆栈供全局数据使用,进而调用s_init函数;

arch/arm/cpu/armv7/lowlevel_init.S的节选源码如下:

ENDPROC(s_init)
.popsection

.pushsection .text.lowlevel_init, "ax"
WEAK(lowlevel_init)

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
	ldr	sp, =CONFIG_SPL_STACK
#else
	ldr	sp, =CONFIG_SYS_INIT_SP_ADDR
#endif
	bic	sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
	mov	r9, #0
#else

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

	push	{ip, lr}
	bl	s_init
	pop	{ip, pc}
ENDPROC(lowlevel_init)

此处的s_init其实什么也没做,可以直接返回;

arch/arm/mach-zynq/lowlevel_init.S节选源码:

ENTRY(lowlevel_init)

	/* Enable the the VFP */
	mrc	p15, 0, r1, c1, c0, 2
	orr	r1, r1, #(0x3 << 20)
	orr	r1, r1, #(0x3 << 20)
	mcr	p15, 0, r1, c1, c0, 2
	isb
	fmrx	r1, FPEXC
	orr	r1,r1, #(1<<30)
	fmxr	FPEXC, r1

	/* Move back to caller */
	mov	pc, lr
ENDPROC(lowlevel_init)

zynq的lowlevel_init函数功能函数其实也很简单,使能了VFP浮点架构什么的,我直接跳过了,执行返回。

最后回到arch/arm/cpu/armv7/start.S节选源码–reset的最后,跳转执行_main函数

1.2 _main()函数

该函数在arch/arm/lib/crt0.S定义,开头就写了,设置初始C运行时环境并调用board_init_f(0)。

ENTRY(_main)

/*
 * Set up initial C runtime environment and call board_init_f(0).
 */

#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
	ldr	r0, =(CONFIG_SPL_STACK)
#else
	ldr	r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
	bic	r0, r0, #7	/* 8-byte alignment for ABI compliance */
	mov	sp, r0
	bl	board_init_f_alloc_reserve
	mov	sp, r0
	/* set up gd here, outside any C code */
	mov	r9, r0
	bl	board_init_f_init_reserve

	mov	r0, #0
	bl	board_init_f

#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, r0
	ldr	r9, [r9, #GD_BD]		/* r9 = gd->bd */
	sub	r9, r9, #GD_SIZE		/* new GD is below bd */

	adr	lr, here
	ldr	r0, [r9, #GD_RELOC_OFF]		/* r0 = gd->reloc_off */
	add	lr, lr, r0
#if defined(CONFIG_CPU_V7M)
	orr	lr, #1				/* As required by Thumb-only */
#endif
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr */
	b	relocate_code
here:
/*
 * now relocate vectors
 */

	bl	relocate_vectors

/* Set up final (full) environment */

	bl	c_runtime_cpu_setup	/* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# 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
	ldr	r0, =__bss_start	/* this is auto-relocated! */

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

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

clbss_l:cmp	r0, r1			/* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
	itt	lo
#endif
	strlo	r2, [r0]		/* clear 32-bit BSS word */
	addlo	r0, r0, #4		/* move to next */
	blo	clbss_l
#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)
  • 第7~18行,主要是完成一个全局变量gd的空间分配和初始化等工作,gd的基址和大小可以通过宏去计算的,这个不详细分析了,篇幅太长不好结束,后续用到会分析的;
  • 第21行,跳转执行board_init_f函数,这个是重点。

1.2.1 board_init_f函数

分析该函数的时候,新玩家可能要注意了,如果你的芯片配置了CONFIG_SPL_BUILD选项的话,要注意区分,他有两处定义,第一个是在common/board_f.c (uboot主体执行该函数),另外一个和芯片相关,比如AM335X系列的,在arch/arm/mach-omap2/am33xx/board.c (MLO即SPL执行该函数),两个地方一定要区分开!!
这里直接分析common/board_f.c下的board_init_f函数,该函数主要用来完成初始化DDR、以及部分外设包括串口、定时器等,同时完成gd各个成员变量的初始化,并将自己本体拷贝到DRAM最后面的区域中去。
board_init_f函数原型如下:

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)
	/* NOTREACHED - jump_to_copy() does not return */
	hang();
#endif
}
  • 第3行,初始化gd->flags = boot_flags;,boot_flags参数传进来就是前面的r0,也就是gd->flags = boot_flags = 0
  • 第4行,初始化gd->have_console = 0; gd->have_console置0代表当前控制台数量为0,也就代表串口还未初始化;
  • 重点在第6行,initcall_run_list函数简单来说就是用于遍历执行一个函数数组中的函数的函数,有点拗口,大家理解一下,其函数原型如下,重点放在init_sequence_f中,这个函数数组:
int initcall_run_list(const init_fnc_t init_sequence[])
{
	const init_fnc_t *init_fnc_ptr;

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		unsigned long reloc_ofs = 0;
		int ret;

		if (gd->flags & GD_FLG_RELOC)
			reloc_ofs = gd->reloc_off;
#ifdef CONFIG_EFI_APP
		reloc_ofs = (unsigned long)image_base;
#endif
		debug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs);
		if (gd->flags & GD_FLG_RELOC)
			debug(" (relocated to %p)\n", (char *)*init_fnc_ptr);
		else
			debug("\n");
		ret = (*init_fnc_ptr)();
		if (ret) {
			printf("initcall sequence %p failed at call %p (err=%d)\n",
			       init_sequence,
			       (char *)*init_fnc_ptr - reloc_ofs, ret);
			return -1;
		}
	}
	return 0;
}

1.2.2 init_sequence_f[]

init_fnc_t的定义:typedef int (*init_fnc_t)(void);
init_sequence_f数组的原型,这里面的内容特别多,也包含了很多条件编译的代码,以下是删去了部分注释和不同架构的内容:

static const init_fnc_t init_sequence_f[] = {
	setup_mon_len,
#ifdef CONFIG_OF_CONTROL
	fdtdec_setup,
#endif
#ifdef CONFIG_TRACE
	trace_early_init,
#endif
	initf_malloc,
	log_init,
	initf_bootstage,	/* uses its own timer, so does not need DM */
	initf_console_record,
	...
	arch_cpu_init,		/* basic arch cpu dependent setup */
	mach_cpu_init,		/* SoC/machine dependent CPU setup */
	initf_dm,
	arch_cpu_init_dm,
#if !defined(CONFIG_M68K)
	timer_init,		/* initialize timer */
#endif
	...
	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 */
	...
	INIT_FUNC_WATCHDOG_INIT
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_I2C)
	init_func_i2c,
#endif
	...
	announce_dram_init,
	dram_init,		/* configure available RAM banks */

	INIT_FUNC_WATCHDOG_RESET
	INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
	init_post,
#endif
	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,
	...
	reserve_round_4k,
#ifdef CONFIG_ARM
	reserve_mmu,
#endif
	reserve_video,
	reserve_trace,
	reserve_uboot,
	reserve_malloc,
	reserve_board,
	setup_machine,
	reserve_global_data,
	reserve_fdt,
	reserve_bootstage,
	reserve_arch,
	reserve_stacks,
	dram_init_banksize,
	show_dram_config,
	...
	display_new_sp,
	...
	INIT_FUNC_WATCHDOG_RESET
	reloc_fdt,
	reloc_bootstage,
	setup_reloc,
	...
	NULL,
};

下面只针对函数去解析,关键函数后面会进行二次分析的;

  • 第2行,setup_mon_len函数设置gd的mon_len成员变量, 变量值为__bss_end -_start,也就是整个代码的长度,这里不进行计算了;在这里插入图片描述
  • 第3~4行,CONFIG_OF_CONTROL宏与设备树相关,如果使用设备树一般都要配置该宏,fdtdec_setup函数通过配置gd->fdt_blob指针(即 evice tree所在的存储位置) 。 对于ARM平台,uboot的Makefile会通过连接脚本,将dtb文件打包到u-boot image的__dtb_dt_begin位置处,这里对后续uboot引导kernel启动有帮助的!
int fdtdec_setup(void)
{
#if CONFIG_IS_ENABLED(OF_CONTROL)	// 确保CONFIG_OF_CONTROL宏是打开的
# if CONFIG_IS_ENABLED(MULTI_DTB_FIT)	
	void *fdt_blob;
# endif
# ifdef CONFIG_OF_EMBED	// 当使用CONFIG_OF_EMBED的方式时,也就是dtb集成到uboot的bin文件中时,通过__dtb_dt_begin符号来获取dtb地址
	/* Get a pointer to the FDT */
#  ifdef CONFIG_SPL_BUILD
	gd->fdt_blob = __dtb_dt_spl_begin;
#  else
	gd->fdt_blob = __dtb_dt_begin;		
#  endif
# elif defined(CONFIG_OF_BOARD) || defined(CONFIG_OF_SEPARATE)	// //当使用CONFIG_OF_SEPARATE的方式时,也就是dtb追加到uboot的bin文件后面时,通过_end符号来获取dtb地址	
	/* Allow the board to override the fdt address. */
	gd->fdt_blob = board_fdt_blob_setup();	// 获取fdt文件的地址
# elif defined(CONFIG_OF_HOSTFILE)
	if (sandbox_read_fdt_from_file()) {
		puts("Failed to read control FDT\n");
		return -1;
	}
# endif
# ifndef CONFIG_SPL_BUILD
	/* Allow the early environment to override the fdt address */
#  if CONFIG_IS_ENABLED(OF_PRIOR_STAGE)
	gd->fdt_blob = (void *)prior_stage_fdt_address;
#  else	// 一直到这里才执行, 可以通过环境变量fdtcontroladdr来指定gd->fdt_blob,也就是指定fdt的地址
	gd->fdt_blob = map_sysmem
		(env_get_ulong("fdtcontroladdr", 16,
			       (unsigned long)map_to_sysmem(gd->fdt_blob)), 0);
#  endif
# endif

# if CONFIG_IS_ENABLED(MULTI_DTB_FIT)
	/*
	 * Try and uncompress the blob.
	 * Unfortunately there is no way to know how big the input blob really
	 * is. So let us set the maximum input size arbitrarily high. 16MB
	 * ought to be more than enough for packed DTBs.
	 */
	if (uncompress_blob(gd->fdt_blob, 0x1000000, &fdt_blob) == 0)
		gd->fdt_blob = fdt_blob;

	/*
	 * Check if blob is a FIT images containings DTBs.
	 * If so, pick the most relevant
	 */
	fdt_blob = locate_dtb_in_fit(gd->fdt_blob);
	if (fdt_blob) {
		gd->multi_dtb_fit = gd->fdt_blob;
		gd->fdt_blob = fdt_blob;
	}

# endif
#endif

	return fdtdec_prepare_fdt();	// 检查fdt合法性
}
  • 第6行,CONFIG_TRACE与追踪调试相关,不用的话直接掠过;
  • 第9行,initf_malloc函数主要初始化gd中跟malloc相关的成员变量,比如malloc_limit,此函数会设置
    gd->malloc_limit=CONFIG_SYS_MALLOC_F_LEN=0x800。malloc_limit表示malloc内存池大小。在这里插入图片描述
  • 第10行,log_init函数,没有具体分析,跳过,后面一些比较少见的函数不一一分析了;
  • 第14~15行,是针对架构和SOC相关内容进行初始化;
  • 第16行,这个比较重要,与uboot的驱动模型相关,如果定义了CONFIG_DM宏,执行initf_dm函数的时候就会调用dm_init_and_scan函数初始化并扫描系统所有的device,这一步和内核中对设备驱动的扫描有异曲同工之妙;在这里插入图片描述
  • 第19行,timer_init函数初始化定时器;
  • 第22行,env_init函数和环境变量相关,这里不仅设置gd->env_addr环境变量保存地址,还涉及到default_environment[],这个后续会进行说明;在这里插入图片描述
  • 第23行,init_baud_rate函数是大家比较熟悉的,初始哈串口的波特率的函数,波特率通过获取环境变量来配置; 在这里插入图片描述
  • 第24行,serial_init函数进行串口的初始话配置;
  • 第25行,console_init_f函数,该函数设置gd->have_console为1,代表串口初始化成功,表示有1个控制台,用于将缓冲区的数据通过控制台打印出来;
  • 第26~27行,主要是通过串口打印一些系统信息,包括uboot的版本以及开发板信息之类的,不详细说明;
  • 第29~30、38~39行,初始化看门狗和复位看门狗,跳过;
  • 第32行,init_func_i2c函数初始哈i2c,初始哈完成之后会有相关打印信息通过串口输出;
  • 第35~36行,进行DDR检测配置,根据配置信息填充了gd对应DDR的一些成员,如下是zynq的dram_init原型,如果定义配置了CONFIG_SYS_SDRAM_SIZE宏,则直接进行填充,然后进行初始化说到DDR,有的芯片可能在SPL阶段就进行了SDRAM初始化等操作,在移植过程中是需要根据自己的板子进行修改的,这里要注意作者的板子上DDR大小为1GB,所以gd->ram_size的值为0x40000000;在这里插入图片描述
  • 第57行,setup_dest_addr函数设置目的地址、设置gd->ram_top、gd->relocaddr等值,gd->ram_top=0x40000000,gd->relocaddr=0x40000000,也就是说重定位后的最高地址为0x40000000在这里插入图片描述
  • 第59~73行,主要是对内存进行分配,reserve_round_4k函数用于对gd->relocaddr 做 4KB 对齐,因为 gd->relocaddr=0x40000000,已经是4K对齐了,调整后不变;
  • reserve_mmu函数留出 MMU 的 TLB 表的位置,分配MMU的TLB表内存以后会对 gd->relocaddr做64K 字节对齐;
  • reserve_uboot函数留出重定位后的uboot所占用的内存区域,uboot所占用大小由gd->mon_len所指定,留出uboot的空间以后还要对gd->relocaddr做4K字节对齐,并且重新设置 gd-start_addr_sp;
  • reserve_board函数,留出板子bd所占的内存区,bd是结构体bd_t,bd_t大小为80字节;
  • reserve_fdt留出设备树相关的内存区域;
  • reserve_bootstage保留bootstage空间;
  • reserve_stacks,留出栈空间,先对gd->start_addr_sp减去16,然后做16字节对齐。如果使能IRQ的话还要留出IRQ相应的内存,具体工作是由 arch/arm/lib/stack.c 文件中的函数 arch_reserve_stacks 完成;
  • 第80~82行,reloc_fdt函数对fdt进行重定位,reloc_bootstage函数对bootstage进行重定位,setup_reloc函数更新gd->reloc_off信息,供后续拷贝使用;

到这里board_init_f函数就执行完毕了,下篇将回到1.2 _main函数继续往后分析;

以上是个人工作学习的回顾总结,有帮助的可以点个赞!欢迎指正,谢谢!

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大棋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值