在上一节系统初始化之start.S源码分析详解中,我们分析了上电后的代码执行流程,实际上就是对系统特权模式、CP15、向量表等进行配置。最后一步就是进入_main
函数了,这个就是U-Boot的主程序了,它完成了对系统内存、堆栈、全局结构体、外设的初始化和代码重定位的操作,这篇文章就来分析一下这个函数。
文章目录
- 1 _main完整代码
- 2 board_init_f_xxxxx函数
- 2.1 board_init_f_alloc_reserve
- 2.2 board_init_f_init_reserve
- 2.3 board_init_f
- 2.3.1 initcall_run_list
- 3 代码重定位
- 3.1 准备工作
- 3.2 relocate_code分析
- 3.2.1 __image_copy_start到__image_copy_end的拷贝
- 3.2.2 __rel_dyn_start到__rel_dyn_end的拷贝
- 3.2.2.1 ARM中的全局变量表
- 3.2.2.1 .rel.dyn段
- 3.2.2.3 代码分析
- 4 relocate_vectors和c_runtime_cpu_setup
- 4.1 relocate_vectors
- 4.2 c_runtime_cpu_setup
- 5 board_init_r
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"的保留空间从给定的顶部地址分配内存。
- 函数的参数
top
即r0
的值CONFIG_SYS_INIT_SP_ADDR
。
(1)如果启用SYS_MALLOC_F_LEN
,则减去相应长度 CONFIG_VAL(SYS_MALLOC_F_LEN)
为早期分配的内存空间进行预留。
(2)为全局数据(GD
)保留足够的空间,并按照16字节向下对齐。GD
实际上是global_data
类型的结构体。
由前面的分析可知,在此函数返回后,top
的值将赋给r0
、sp
和r9
。
2.2 board_init_f_init_reserve
这里大致总结一下这个函数完成的操作:
1、清零保留的空间
使用memset
函数将保留的空间从 base
到 base + sizeof(struct global_data) - 1
清零,确保在使用之前不会包含任何旧数据。
2、 初始化内存保护
如果配置了 SYS_REPORT_STACK_F_USAGE
,则调用 board_init_f_init_stack_protection_addr
和 board_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
函数为例:
来看一下这个函数的反汇编:
- 首先我们可以注意到这里的函数跳转确实都是使用的相对跳转指令
bl
和ble
我们现在重点关注这段汇编如何获取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字节到r0
和r1
中,再取出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_r
和board_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,
};
这里面的初始化太多了,就不具体地一个个分析了,简单地说明一下:
initr_trace
:初始化追踪功能。在调试或跟踪执行流程时可能会用到。initr_reloc
:初始化重定位过程。在U-Boot启动时,可能需要将U-Boot的二进制代码从加载地址移动到实际运行地址。initr_caches
:如果是ARM或RISC-V架构,初始化缓存。initr_reloc_global_data
:初始化全局数据的重定位。initr_unlock_ram_in_cache
:在E500平台上,解锁缓存中的RAM。initr_barrier
:执行一些同步屏障操作。initr_malloc
:初始化内存分配器。log_init
:初始化日志系统。initr_bootstage
:初始化引导阶段信息,可能涉及内存分配等。console_record_init
:如果启用了控制台记录功能,进行相关初始化。noncached_init
:如果配置了非缓存内存,进行初始化。initr_of_live
:初始化设备树(DeviceTree)。initr_dm
:如果启用了设备模型(DeviceModel),进行相关初始化。init_addr_map
:如果配置了地址映射,进行相关初始化。board_init
:针对特定板级硬件的初始化,例如设置芯片选择器(chipselects)。set_cpu_clk_info
:如果启用了时钟信息功能,设置CPU时钟信息。efi_memory_init
:如果启用了EFILoader,初始化内存。initr_binman
:初始化二进制管理。arch_fsp_init_r
:如果启用了FSP(FirmwareSupportPackage),进行相关初始化。initr_dm_devices
:初始化设备模型中的设备。stdio_init_tables
:初始化标准输入输出的数据结构。serial_initialize
:初始化串口。initr_announce
:在启动期间宣告初始化。dm_announce
:宣告设备模型的初始化。initr_watchdog
:初始化看门狗。blkcache_init
:如果需要手动重定位并启用块缓存,进行相关初始化。initr_manual_reloc_cmdtable
:如果需要手动重定位,初始化命令表。arch_initr_trap
:初始化陷阱。board_early_init_r
:在板级初始化的早期阶段进行初始化。post_output_backlog
:如果启用了POST,输出POST的日志。pci_init
:初始化PCI。arch_early_init_r
:在体系结构早期初始化阶段进行初始化。power_init_board
:板级电源初始化。initr_flash
:如果启用了NORFlash,进行相关初始化。cpu_init_r
:初始化CPU的高级部分,如时间基准和定时器。initr_nand
:如果启用了NANDFlash,进行相关初始化。initr_onenand
:如果启用了OneNANDFlash,进行相关初始化。initr_mmc
:如果启用了MMC(多媒体卡),进行相关初始化。xen_init
:如果启用了Xen,进行相关初始化。initr_pvblock
:初始化pvblock。initr_env
:初始化环境变量。initr_malloc_bootparams
:初始化引导参数的内存分配。cpu_secondary_init_r
:初始化辅助CPU。mac_read_from_eeprom
:从EEPROM读取MAC地址。pci_init
:初始化PCI。stdio_add_devices
:添加标准输入输出设备。jumptable_init
:初始化跳转表。api_init
:初始化API。console_init_r
:完全初始化控制台作为设备。console_announce_r
:如果启用了显示板信息的延迟配置,进行相关初始化。arch_misc_init
:初始化与体系结构相关的杂项功能。misc_init_r
:初始化与平台相关的杂项功能。kgdb_init
:如果启用了KGDB(内核调试器),进行相关初始化。interrupt_init
:初始化中断。timer_init
:初始化定时器。initr_status_led
:如果启用了LED状态指示功能,进行相关初始化。initr_ethaddr
:初始化以太网地址。gpio_hog_probe_all
:如果启用了GPIOHOG,进行相关初始化。board_late_init
:板级后期初始化。initr_fastboot_setup
:如果启用了快速启动(Fastboot),进行相关初始化。initr_scsi
:如果启用了SCSI,进行相关初始化。initr_ide
:如果启用了IDE,进行相关初始化。last_stage_init
:最后阶段初始化。initr_mem
:初始化内存。efi_init_obj_list
:如果启用了EFI设置早期初始化,进行相关初始化。initr_avbkey
:如果启用了AVB(AndroidVerifiedBoot),进行相关初始化。initr_tee_setup
:如果启用了TrustyOS,进行相关初始化。initr_check_fastboot
:如果启用了快速启动,进行相关检查。initr_check_spl_recovery
:如果启用了双引导加载器,进行相关检查。run_main_loop
:运行主循环。
我们注意到,在最后会进入run_main_loop
函数中执行,实际上这就是U-Boot的命令行解析函数,也就是我们上电后看到的倒计时,然后可以输入一些U-Boot命令以执行一些特定的操作。这部分代码都是C语言实现的,比较简单,就不分析了。
最后当然就是U-Boot跳转内核了,U-boot是如何跳转到内核的呢,需不需要传递一些参数给内核呢?我们下一节分析。