U-Boot学习(6):初始化之_main函数源码分析

在上一节系统初始化之start.S源码分析详解中,我们分析了上电后的代码执行流程,实际上就是对系统特权模式、CP15、向量表等进行配置。最后一步就是进入_main函数了,这个就是U-Boot的主程序了,它完成了对系统内存、堆栈、全局结构体、外设的初始化和代码重定位的操作,这篇文章就来分析一下这个函数。

1 _main完整代码

函数在crt0.S中定义,这里去掉了不会执行的宏定义部分的代码,以让代码结构更清晰:
在这里插入图片描述

下面就分块分析一下上面的代码。

2 board_init_f_xxxxx函数

在这里插入图片描述

简单分析一下:
(1)首先将CONFIG_SYS_INIT_SP_ADDR的值按照8字节对齐赋值给当前的SP堆栈。

  • CONFIG_SYS_INIT_SP_ADDR值的分析,可以参考上一篇文章

(2)调用board_init_f_alloc_reserve函数,r0一般保存函数的返回值,调用完后调用mov sp, r0将函数的返回值r0赋值给sp堆栈。

(3)接着我们把R0赋值给R9。在global_data.h中,有如下的定义:

#define DECLARE_GLOBAL_DATA_PTR		register volatile gd_t *gd asm ("r9")

也就是说我们声明了一个gd_t 类型的全局数据的变量指针,然后我们希望将这个指针保存在r9寄存器中。所以在执行这行代码之前,R0的值应该是为这个全局变量分配的空间的首地址。
(4)调用board_init_f_init_reserve函数
(5)设置R0为0,然后调用board_init_f,这里R0为函数的参数。

下面就来看一下这三个board_init_f_xxxxx函数。

2.1 board_init_f_alloc_reserve

这个函数的主要目的是为全局数据(GD)和用作"globals"的保留空间从给定的顶部地址分配内存。

在这里插入图片描述

  • 函数的参数topr0的值CONFIG_SYS_INIT_SP_ADDR

(1)如果启用SYS_MALLOC_F_LEN,则减去相应长度 CONFIG_VAL(SYS_MALLOC_F_LEN)为早期分配的内存空间进行预留。
(2)为全局数据(GD)保留足够的空间,并按照16字节向下对齐。GD实际上是global_data类型的结构体。

由前面的分析可知,在此函数返回后,top的值将赋给r0spr9

2.2 board_init_f_init_reserve

在这里插入图片描述

这里大致总结一下这个函数完成的操作:

1、清零保留的空间
使用memset函数将保留的空间从 basebase + sizeof(struct global_data) - 1 清零,确保在使用之前不会包含任何旧数据。
2、 初始化内存保护
如果配置了 SYS_REPORT_STACK_F_USAGE,则调用 board_init_f_init_stack_protection_addrboard_init_f_init_stack_protection 函数,为U-Boot内部的堆栈保护机制留出一些内存。
3、更新基址
base 更新为下一个分配区域的起始地址。这个区域包括GD结构和可能的其他数据结构,并确保新的base满足16字节对齐的要求。
4、记录早期malloc区域的起始地址
如果启用了CONFIG_VAL(SYS_MALLOC_F_LEN),则记录早期malloc区域的起始地址到gd->malloc_base中。

2.3 board_init_f

在这里插入图片描述
在调用这个函数前,r0的值赋为了0,所以boot_flags为0。这里实际上就执行了一个initcall_run_list(init_sequence_f)函数,从函数名可以看出,这里就是根据init_sequence_f做一些初始化操作,如果初始化失败就调用hang()挂起程序。下面就来分析一下这个函数。

2.3.1 initcall_run_list

为了简介,这里把没有定义到的宏定义里的代码和不相关的代码都去掉了,函数其实很简单:
在这里插入图片描述
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_EARLY
	trace_early_init,
#endif
	initf_malloc,
	log_init,
	initf_bootstage,	/* uses its own timer, so does not need DM */
#ifdef CONFIG_BLOBLIST
	bloblist_init,
#endif
	setup_spl_handoff,
#if defined(CONFIG_CONSOLE_RECORD_INIT_F)
	console_record_init,
#endif
#if defined(CONFIG_HAVE_FSP)
	arch_fsp_init,
#endif
	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_BOARD_EARLY_INIT_F)
	board_early_init_f,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_SYS_FSL_CLK) || defined(CONFIG_M68K)
	/* get CPU and bus clocks according to the environment variable */
	get_clocks,		/* get CPU and bus clocks (etc.) */
#endif
#if !defined(CONFIG_M68K)
	timer_init,		/* initialize timer */
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
	board_postclk_init,
#endif
	env_init,		/* initialize environment */
	init_baud_rate,		/* initialze baudrate settings */
#ifndef CONFIG_ANDROID_AUTO_SUPPORT
	serial_init,		/* serial communications setup */
#endif
	console_init_f,		/* stage 1 init of console */
	display_options,	/* say that we are here */
	display_text_info,	/* show debugging info if required */
	checkcpu,
#if defined(CONFIG_SYSRESET)
	print_resetinfo,
#endif
#if defined(CONFIG_DISPLAY_CPUINFO)
	print_cpuinfo,		/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DTB_RESELECT)
	embedded_dtb_select,
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
	show_board_info,
#endif
	INIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)
	misc_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
	init_func_i2c,
#endif
#if defined(CONFIG_VID) && !defined(CONFIG_SPL)
	init_func_vid,
#endif
	announce_dram_init,
	dram_init,		/* configure available RAM banks */
#ifdef CONFIG_POST
	post_init_f,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)
	testdram,
#endif /* CONFIG_SYS_DRAM_TEST */
	INIT_FUNC_WATCHDOG_RESET

#ifdef CONFIG_POST
	init_post,
#endif
	INIT_FUNC_WATCHDOG_RESET
	setup_dest_addr,
#ifdef CONFIG_OF_BOARD_FIXUP
	fix_fdt,
#endif
#ifdef CONFIG_PRAM
	reserve_pram,
#endif
	reserve_round_4k,
	arch_reserve_mmu,
	reserve_video,
	reserve_trace,
	reserve_uboot,
	reserve_malloc,
	reserve_board,
	reserve_global_data,
	reserve_fdt,
	reserve_bootstage,
	reserve_bloblist,
	reserve_arch,
	reserve_stacks,
	dram_init_banksize,
	show_dram_config,
	INIT_FUNC_WATCHDOG_RESET
	setup_bdinfo,
	display_new_sp,
	INIT_FUNC_WATCHDOG_RESET
	reloc_fdt,
	reloc_bootstage,
	reloc_bloblist,
	setup_reloc,
	clear_bss,
	NULL,
};

这里不对每个函数进行分析了,一个个分析里面的函数的意义不大。上面的函数初始化列表涵盖了从底层硬件初始化(如设备树解析、时钟设置、CPU初始化)到高级功能(如内存初始化、环境变量配置、串口通信设置)的一系列操作,还涉及设备模型和早期的引导阶段的初始化,还有内存保护、定时器、看门狗、以及一些特定的平台或功能的初始化。

3 代码重定位

后面的代码就是完成代码重定位了:

3.1 准备工作

在跳转到代码重定位的函数之前,我们需要完成一些准备工作:
在这里插入图片描述
具体完成的操作参考上面的注释。相关的宏定义都在generic-asm-offsets.h中有声明,实际上就是global_data结构体中对应变量的偏移。

#define GD_START_ADDR_SP 72      /* offsetof(struct global_data, start_addr_sp)	@ */
#define GD_NEW_GD        80      /* offsetof(struct global_data, new_gd)	    @ */
#define GD_RELOC_OFF     76      /* offsetof(struct global_data, reloc_off)	    @ */
#define GD_RELOCADDR     56      /* offsetof(struct global_data, relocaddr)	    @ */

这里我就不详细地分析在init_sequence_f中如何计算这些代码重定位的值了,我们只要知道GD结构体中有一些变量包含绝对地址,有一些变量包含重定位的信息,这里面都帮我们计算好了重定位后的地址或重定位的相关信息了。

实际上这些变量的值都在init_sequence_f一系列的函数执行完后,设置相关变量为代码重定位后对应的地址的值,而我们将把他们重定位到DDR内存的末尾。由前几篇文章的分析可知,U-Boot被链接到0x87800000处运行,那为什么我们要将0x87800000开始的变量和代码链接到整个内存的末尾呢?

  • 0x80000000开始我们后面要放内核,防止内核大小超过0x7800000大小而覆盖U-Boot内核。

那为什么我们不把U-Boot直接在u-boot.lds中就链接到DDR的末尾呢?
因为U-Boot需要兼容不同的平台,而且编译出来的U-Boot的大小也不确定,所以我们一般就把U-Boot链接到DDR的中间位置,然后在U-Boot程序中计算程序的大小,然后变量和代码重定位到DDR内存的最后,而不浪费更多的内存。防止后续内核的代码覆盖掉U-Boot的代码,导致U-Boot启动Linux内核的时候出错。

3.2 relocate_code分析

代码如下:
在这里插入图片描述
u-boot.lds链接脚本分析中,我们知道__image_copy_start__image_copy_end在链接脚本中声明了,其中__image_copy_start实际上是0x87800000,__image_copy_end实际上就是加上中间那些段的大小后的内存地址,我们需要拷贝这一块内存区域。同样还声明了__rel_dyn_start__rel_dyn_end

relocate.S文件最后还声明了_ofs结尾的变量,我们在relocate_code中用的正是这些变量。这些值都声明为其在链接脚本中对应的变量减去relocate_code函数的首地址。

_image_copy_start_ofs:
	.word	__image_copy_start - relocate_code
_image_copy_end_ofs:
	.word	__image_copy_end - relocate_code
_rel_dyn_start_ofs:
	.word	__rel_dyn_start - relocate_code
_rel_dyn_end_ofs:
	.word	__rel_dyn_end - relocate_code

3.2.1 __image_copy_start到__image_copy_end的拷贝

现在我们来分析第一部分的代码:
在这里插入图片描述
在调用函数之前r0已经设置为了GD结构体中的relocaddr变量,即我们重定位要拷贝数据的目标地址。

同时,我们发现我们引用xxx_ofs变量后,又加上了relocate_code标号的地址,实际上就是引用原来链接脚本中的地址。这样的设计可能是为了方便处理和维护代码,如果在多个地方需要使用相同的偏移值,只需引用相应的标签即可,而不必在每次使用时硬编码偏移值。

下面就是代码的拷贝操作了,将_image_copy_start_image_copy_end部分的代码,拷贝到gd->relocaddr地址处。
在这里插入图片描述

3.2.2 __rel_dyn_start到__rel_dyn_end的拷贝

我们的代码重定位实际上把代码链接到了另一个位置,但是我们编译的时候这个链接地址就已经固定了,那在我们重定位代码之后,对一些变量的访问也就会出错。那有没有什么解决方法呢?请参考我的这篇文章:位置无关码PIC详解:原理、动态链接库、代码重定位,虽然我使用的不是ARM的编译器,但是在实现上也大同小异。

总得来说,就是如果我们加上了-pie等位置无关的编译器选项后:

  • 函数:函数之间的跳转都会使用相对的跳转指令,如bl,而不是绝对的跳转,如ldr pc,=0x12345678。这就保证了函数代码段在重定位后的正常访问。
  • 变量:全局变量就会使用一个GOT全局变量表来存储每个变量的地址,如果我们代码重定位的话,只需要修改这个表中的地址即可。
3.2.2.1 ARM中的全局变量表

前面说了,我那篇文章分析的是Linux平台GCC编译器的重定位,那ARM中实现肯定类似,但我们必须知道里面的细节才能实现重定位,所以现在就来看看反汇编代码分析一下。

首先我们可以看到我们U-Boot的编译是有-pie选项的:
在这里插入图片描述
首先我们反汇编一下u-boot的源码:

arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis

我们以mem.c中的do_mem_base函数为例:
在这里插入图片描述
来看一下这个函数的反汇编:
在这里插入图片描述

  • 首先我们可以注意到这里的函数跳转确实都是使用的相对跳转指令blble

我们现在重点关注这段汇编如何获取base_address这个全局变量的,很容易看出ldr r4, [pc, #24]这个操作就是把base_address全局变量的地址加载到r4的。而PC+0x24的值为0x8790a800,这个地址在整个函数的最后,这个地址的值为0x87950974,我们看一下这个地址保存的是什么:
在这里插入图片描述
在BSS段中(base_address初始值为0)发现了,实际上这个地址指向的就是base_address的地址。

总结:ARM中函数使用到的全局变量,会在每个函数的最后留一段内存空间来保存使用到的变量的地址。如果我们要让重定位后的代码可以正常访问全局变量的话,我们只需要修改函数最后这段内存空间里对应的全局变量的地址,给这个地址加上重定位偏移即可。

3.2.2.1 .rel.dyn段

前面我们总结出了,需要把每个函数最后的指向全局变量的内存空间加上一个偏移,但是我们怎么获取每个函数的这段内存空间呢?我们发现重定位代码中有一个.rel.dyn段,从代码实现来看,我们可以猜测所有函数最后的指示全局变量的内存应该就在这个段里,我们搜一下0x8790a800
在这里插入图片描述
果然在.rel.dyn段中,我们发现这个段中的内存分布大致就是:一行全局变量的地址,一行0x17。实际上这个0x17是一个标识符,指示前4字节的内存为某个函数最后的保存其要使用的全局变量的地址。知道了这个特性,我们就可以继续往下分析代码了。

3.2.2.3 代码分析

在这里插入图片描述
一开始我们将r2赋值为rel_dyn段的起始地址,将r3赋值为rel_dyn段的结束地址。然后从r2读取两个32字节到r0r1中,再取出r1的低8位,判断它是否等于23(0x17):

  • 不等于23:跳转到fixnext,判断是否检查到rel_dyn结尾了,否则继续往下拷贝。
  • 等于23:r0所保存的地址为某个函数中用到的标号,我们需要重定位这个标号所指向的地址,就继续往下执行代码

我们分析一下等于23时,执行的那四行代码。

add	r0, r0, r4
ldr	r1, [r0]
add	r1, r1, r4
str	r1, [r0]

3.2.1中,我们知道r4就是重定位的偏移。所以我们给r0加上这个偏移然后保存到r0,现在r0即重定位后的标号的地址。这个地址所指向的值即为实际变量的地址,但这个地址还没有重定位。所以将变量的地址加载到r1中,然后加上偏移r4,再保存回标号重定位后的地址r0

这样就完成了代码的重定位。最后我们bx lr,我们知道lr在调用relocate_code前已经保存为_main函数中here标号了。也就是最后几行代码了:
在这里插入图片描述

4 relocate_vectors和c_runtime_cpu_setup

4.1 relocate_vectors

为了简洁,我去掉了不执行的代码分支,实际上就执行以下语句。
在这里插入图片描述
实际上就是设置协处理器中的向量表首地址为GD中的relocaddr,即前面我们提到的重定位要拷贝数据的目标地址,也是我们向量表所保存的地址。然后由于前面bl relocate_vectors,所以LR寄存器即为_main中下一条指令的地址,我们的程序就跳回去了。

4.2 c_runtime_cpu_setup

这个函数也很简单,就是如果使能了ICache,则将缓存中的所有有效数据标记为无效,这意味着下次访问这些数据时,系统将不会从缓存中读取。
在这里插入图片描述

5 board_init_r

最后就是执行board_init_r函数了:
在这里插入图片描述
这个函数接受两个参数,第一个参数为GD全局数据,第二个参数为重定位后的地址。这里删减掉不会执行的分支,实际上board_init_rboard_init_f一样,也是执行一系列的初始化函数:
在这里插入图片描述
init_sequence_r的定义如下:

static init_fnc_t init_sequence_r[] = {
	initr_trace,
	initr_reloc,
	/* TODO: could x86/PPC have this also perhaps? */
#if defined(CONFIG_ARM) || defined(CONFIG_RISCV)
	initr_caches,
	/* Note: For Freescale LS2 SoCs, new MMU table is created in DDR.
	 *	 A temporary mapping of IFC high region is since removed,
	 *	 so environmental variables in NOR flash is not available
	 *	 until board_init() is called below to remap IFC to high
	 *	 region.
	 */
#endif
	initr_reloc_global_data,
#if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500)
	initr_unlock_ram_in_cache,
#endif
	initr_barrier,
	initr_malloc,
	log_init,
	initr_bootstage,	/* Needs malloc() but has its own timer */
#if defined(CONFIG_CONSOLE_RECORD)
	console_record_init,
#endif
#ifdef CONFIG_SYS_NONCACHED_MEMORY
	noncached_init,
#endif
	initr_of_live,
#ifdef CONFIG_DM
	initr_dm,
#endif
#ifdef CONFIG_ADDR_MAP
	init_addr_map,
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) || defined(CONFIG_RISCV) || \
	defined(CONFIG_SANDBOX)
	board_init,	/* Setup chipselects */
#endif
	/*
	 * TODO: printing of the clock inforamtion of the board is now
	 * implemented as part of bdinfo command. Currently only support for
	 * davinci SOC's is added. Remove this check once all the board
	 * implement this.
	 */
#ifdef CONFIG_CLOCKS
	set_cpu_clk_info, /* Setup clock information */
#endif
#ifdef CONFIG_EFI_LOADER
	efi_memory_init,
#endif
	initr_binman,
#ifdef CONFIG_FSP_VERSION2
	arch_fsp_init_r,
#endif
	initr_dm_devices,
	stdio_init_tables,
	serial_initialize,
	initr_announce,
	dm_announce,
#if CONFIG_IS_ENABLED(WDT)
	initr_watchdog,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_NEEDS_MANUAL_RELOC) && defined(CONFIG_BLOCK_CACHE)
	blkcache_init,
#endif
#ifdef CONFIG_NEEDS_MANUAL_RELOC
	initr_manual_reloc_cmdtable,
#endif
	arch_initr_trap,
#if defined(CONFIG_BOARD_EARLY_INIT_R)
	board_early_init_r,
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_POST
	post_output_backlog,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI_INIT_R) && defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do early PCI configuration _before_ the flash gets initialised,
	 * because PCU resources are crucial for flash access on some boards.
	 */
	pci_init,
#endif
#ifdef CONFIG_ARCH_EARLY_INIT_R
	arch_early_init_r,
#endif
	power_init_board,
#ifdef CONFIG_MTD_NOR_FLASH
	initr_flash,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_X86)
	/* initialize higher level parts of CPU like time base and timers */
	cpu_init_r,
#endif
#ifdef CONFIG_CMD_NAND
	initr_nand,
#endif
#ifdef CONFIG_CMD_ONENAND
	initr_onenand,
#endif
#ifdef CONFIG_MMC
	initr_mmc,
#endif
#ifdef CONFIG_XEN
	xen_init,
#endif
#ifdef CONFIG_PVBLOCK
	initr_pvblock,
#endif
	initr_env,
#ifdef CONFIG_SYS_BOOTPARAMS_LEN
	initr_malloc_bootparams,
#endif
	INIT_FUNC_WATCHDOG_RESET
	cpu_secondary_init_r,
#if defined(CONFIG_ID_EEPROM)
	mac_read_from_eeprom,
#endif
	INIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_PCI_INIT_R) && !defined(CONFIG_SYS_EARLY_PCI_INIT)
	/*
	 * Do pci configuration
	 */
	pci_init,
#endif
	stdio_add_devices,
	jumptable_init,
#ifdef CONFIG_API
	api_init,
#endif
	console_init_r,		/* fully init console as a device */
#ifdef CONFIG_DISPLAY_BOARDINFO_LATE
	console_announce_r,
	show_board_info,
#endif
#ifdef CONFIG_ARCH_MISC_INIT
	arch_misc_init,		/* miscellaneous arch-dependent init */
#endif
#ifdef CONFIG_MISC_INIT_R
	misc_init_r,		/* miscellaneous platform-dependent init */
#endif
	INIT_FUNC_WATCHDOG_RESET
#ifdef CONFIG_CMD_KGDB
	kgdb_init,
#endif
	interrupt_init,
#if defined(CONFIG_MICROBLAZE) || defined(CONFIG_M68K)
	timer_init,		/* initialize timer */
#endif
#if defined(CONFIG_LED_STATUS)
	initr_status_led,
#endif
	/* PPC has a udelay(20) here dating from 2002. Why? */
#ifdef CONFIG_CMD_NET
	initr_ethaddr,
#endif
#if defined(CONFIG_GPIO_HOG)
	gpio_hog_probe_all,
#endif
#ifdef CONFIG_BOARD_LATE_INIT
	board_late_init,
#endif
#ifdef CONFIG_FSL_FASTBOOT
	initr_fastboot_setup,
#endif
#if defined(CONFIG_SCSI) && !defined(CONFIG_DM_SCSI)
	INIT_FUNC_WATCHDOG_RESET
	initr_scsi,
#endif
#ifdef CONFIG_BITBANGMII
	bb_miiphy_init,
#endif
#ifdef CONFIG_PCI_ENDPOINT
	pci_ep_init,
#endif
#ifdef CONFIG_CMD_NET
	INIT_FUNC_WATCHDOG_RESET
	initr_net,
#endif
#ifdef CONFIG_POST
	initr_post,
#endif
#if defined(CONFIG_IDE) && !defined(CONFIG_BLK)
	initr_ide,
#endif
#ifdef CONFIG_LAST_STAGE_INIT
	INIT_FUNC_WATCHDOG_RESET
	/*
	 * Some parts can be only initialized if all others (like
	 * Interrupts) are up and running (i.e. the PC-style ISA
	 * keyboard).
	 */
	last_stage_init,
#endif
#if defined(CONFIG_PRAM)
	initr_mem,
#endif
#ifdef CONFIG_EFI_SETUP_EARLY
	(init_fnc_t)efi_init_obj_list,
#endif
#if defined(AVB_RPMB) && !defined(CONFIG_SPL)
	initr_avbkey,
#endif
#ifdef CONFIG_IMX_TRUSTY_OS
	initr_tee_setup,
#endif
#ifdef CONFIG_FSL_FASTBOOT
	initr_check_fastboot,
#endif
#ifdef CONFIG_DUAL_BOOTLOADER
	initr_check_spl_recovery,
#endif
	run_main_loop,
};

这里面的初始化太多了,就不具体地一个个分析了,简单地说明一下:

  1. initr_trace:初始化追踪功能。在调试或跟踪执行流程时可能会用到。
  2. initr_reloc:初始化重定位过程。在U-Boot启动时,可能需要将U-Boot的二进制代码从加载地址移动到实际运行地址。
  3. initr_caches:如果是ARM或RISC-V架构,初始化缓存。
  4. initr_reloc_global_data:初始化全局数据的重定位。
  5. initr_unlock_ram_in_cache:在E500平台上,解锁缓存中的RAM。
  6. initr_barrier:执行一些同步屏障操作。
  7. initr_malloc:初始化内存分配器。
  8. log_init:初始化日志系统。
  9. initr_bootstage:初始化引导阶段信息,可能涉及内存分配等。
  10. console_record_init:如果启用了控制台记录功能,进行相关初始化。
  11. noncached_init:如果配置了非缓存内存,进行初始化。
  12. initr_of_live:初始化设备树(DeviceTree)。
  13. initr_dm:如果启用了设备模型(DeviceModel),进行相关初始化。
  14. init_addr_map:如果配置了地址映射,进行相关初始化。
  15. board_init:针对特定板级硬件的初始化,例如设置芯片选择器(chipselects)。
  16. set_cpu_clk_info:如果启用了时钟信息功能,设置CPU时钟信息。
  17. efi_memory_init:如果启用了EFILoader,初始化内存。
  18. initr_binman:初始化二进制管理。
  19. arch_fsp_init_r:如果启用了FSP(FirmwareSupportPackage),进行相关初始化。
  20. initr_dm_devices:初始化设备模型中的设备。
  21. stdio_init_tables:初始化标准输入输出的数据结构。
  22. serial_initialize:初始化串口。
  23. initr_announce:在启动期间宣告初始化。
  24. dm_announce:宣告设备模型的初始化。
  25. initr_watchdog:初始化看门狗。
  26. blkcache_init:如果需要手动重定位并启用块缓存,进行相关初始化。
  27. initr_manual_reloc_cmdtable:如果需要手动重定位,初始化命令表。
  28. arch_initr_trap:初始化陷阱。
  29. board_early_init_r:在板级初始化的早期阶段进行初始化。
  30. post_output_backlog:如果启用了POST,输出POST的日志。
  31. pci_init:初始化PCI。
  32. arch_early_init_r:在体系结构早期初始化阶段进行初始化。
  33. power_init_board:板级电源初始化。
  34. initr_flash:如果启用了NORFlash,进行相关初始化。
  35. cpu_init_r:初始化CPU的高级部分,如时间基准和定时器。
  36. initr_nand:如果启用了NANDFlash,进行相关初始化。
  37. initr_onenand:如果启用了OneNANDFlash,进行相关初始化。
  38. initr_mmc:如果启用了MMC(多媒体卡),进行相关初始化。
  39. xen_init:如果启用了Xen,进行相关初始化。
  40. initr_pvblock:初始化pvblock。
  41. initr_env:初始化环境变量。
  42. initr_malloc_bootparams:初始化引导参数的内存分配。
  43. cpu_secondary_init_r:初始化辅助CPU。
  44. mac_read_from_eeprom:从EEPROM读取MAC地址。
  45. pci_init:初始化PCI。
  46. stdio_add_devices:添加标准输入输出设备。
  47. jumptable_init:初始化跳转表。
  48. api_init:初始化API。
  49. console_init_r:完全初始化控制台作为设备。
  50. console_announce_r:如果启用了显示板信息的延迟配置,进行相关初始化。
  51. arch_misc_init:初始化与体系结构相关的杂项功能。
  52. misc_init_r:初始化与平台相关的杂项功能。
  53. kgdb_init:如果启用了KGDB(内核调试器),进行相关初始化。
  54. interrupt_init:初始化中断。
  55. timer_init:初始化定时器。
  56. initr_status_led:如果启用了LED状态指示功能,进行相关初始化。
  57. initr_ethaddr:初始化以太网地址。
  58. gpio_hog_probe_all:如果启用了GPIOHOG,进行相关初始化。
  59. board_late_init:板级后期初始化。
  60. initr_fastboot_setup:如果启用了快速启动(Fastboot),进行相关初始化。
  61. initr_scsi:如果启用了SCSI,进行相关初始化。
  62. initr_ide:如果启用了IDE,进行相关初始化。
  63. last_stage_init:最后阶段初始化。
  64. initr_mem:初始化内存。
  65. efi_init_obj_list:如果启用了EFI设置早期初始化,进行相关初始化。
  66. initr_avbkey:如果启用了AVB(AndroidVerifiedBoot),进行相关初始化。
  67. initr_tee_setup:如果启用了TrustyOS,进行相关初始化。
  68. initr_check_fastboot:如果启用了快速启动,进行相关检查。
  69. initr_check_spl_recovery:如果启用了双引导加载器,进行相关检查。
  70. run_main_loop:运行主循环。

我们注意到,在最后会进入run_main_loop函数中执行,实际上这就是U-Boot的命令行解析函数,也就是我们上电后看到的倒计时,然后可以输入一些U-Boot命令以执行一些特定的操作。这部分代码都是C语言实现的,比较简单,就不分析了。

最后当然就是U-Boot跳转内核了,U-boot是如何跳转到内核的呢,需不需要传递一些参数给内核呢?我们下一节分析。

  • 21
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
所有资料来源网上,与朋友分享 u-boot-1.1.6之cpu/arm920t/start.s分析 2 u-boot中.lds连接脚本文件的分析 12 分享一篇我总结的uboot学习笔记(转) 15 U-BOOT内存布局及启动过程浅析 22 u-boot中的命令实现 25 U-BOOT环境变量实现 28 1.相关文件 28 2.数据结构 28 3.ENV 的初始化 30 3.1env_init 30 3.2 env_relocate 30 3.3*env_relocate_spec 31 4. ENV 的保存 31 U-Boot环境变量 32 u-boot代码链接的问题 35 ldr和adr在使用标号表达式作为操作数的区别 40 start_armboot浅析 42 1.全局数据结构的初始化 42 2.调用通用初始化函数 43 3.初始化具体设备 44 4.初始化环境变量 44 5.进入主循环 44 u-boot编译过程 44 mkconfig文件的分析 47 从NAND闪存中启动U-BOOT的设计 50 引言 50 NAND闪存工作原理 51 从NAND闪存启动U-BOOT的设计思路 51 具体设计 51 支持NAND闪存的启动程序设计 51 支持U-BOOT命令设计 52 结语 53 参考文献 53 U-boot给kernel传参数和kernel读取参数—struct tag (以及补充) 53 1 、u-boot 给kernel 传RAM 参数 54 2 、Kernel 读取U-boot 传递的相关参数 56 3 、关于U-boot 中的bd 和gd 59 U-BOOT源码分析及移植 60 一、u-boot工程的总体结构: 61 1、源代码组织 61 2.makefile简要分析 61 3、u-boot的通用目录是怎么做到与平台无关的? 63 4、smkd2410其余重要的文件 : 63 二、u-boot的流程、主要的数据结构、内存分配 64 1、u-boot的启动流程: 64 2、u-boot主要的数据结构 66 3、u-boot重定位后的内存分布: 68 三、u-boot的重要细节 。 68 关于U-boot中命令相关的编程 : 73 四、U-boot在ST2410的移植,基于NOR FLASH和NAND FLASH启动。 76 1、从smdk2410到ST2410: 76 2、移植过程: 76 3、移植要考虑的问题: 77 4、SST39VF1601: 77 5、我实现的flash.c主要部分: 78 6、增加从Nand 启动的代码 : 82 7、添加网络命令。 87
在了解 `efi_main` 函数之前,先简单介绍一下 EFI(Extensible Firmware Interface)。 EFI 是一种固件接口,是替代传统 BIOS 的新一代系统启动接口。EFI 提供了一个标准的、可扩展的、可定制的接口,可以在启动时加载硬件驱动程序和操作系统。相比于传统 BIOS,EFI 更加灵活、安全和可靠。 在 Linux 中,EFI 主要用于系统引导和启动。当计算机开机时,UEFI 固件会自动启动 EFI 程序,这个程序就是 `efi_main` 函数。`efi_main` 函数是 EFI 程序的入口点,它的原型如下: ``` EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) ``` 其中,`ImageHandle` 是该 EFI 程序的句柄,`SystemTable` 是 EFI 系统表,包含了各种 EFI 服务和数据结构。 `efi_main` 函数的作用是初始化 EFI 环境,加载 Linux 内核,并跳转到内核的入口点开始执行。具体的流程如下: 1. 获取 EFI 系统表中的 Boot Services,这些服务包含了各种操作系统启动所需的函数。 2. 使用 Boot Services 中的函数加载 Linux 内核到内存中。 3. 构造 Linux 内核启动参数结构体 `struct boot_params`,这个结构体包含了 Linux 内核启动所需的各种参数。 4. 使用 Boot Services 中的函数将 `struct boot_params` 结构体复制到内存中,然后将控制权转移到内核的入口点。 5. 内核启动后,会解析 `struct boot_params` 结构体,获取各种启动参数,并开始执行内核初始化流程。 总之,`efi_main` 函数Linux EFI 启动的关键,它负责初始化 EFI 环境和启动 Linux 内核。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tilblackout

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

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

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

打赏作者

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

抵扣说明:

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

余额充值