本篇主要以I.MX6ULL的”U-Boot 22.04”版本作为基础进行分析,关于如何分析启动流程,主要关注两个部分。
本节目录如下。
在学习U-Boot前,先大致理解U-Boot目录功能。
目录 | 功能说明 |
---|---|
api | 用于支持uboot支持app运行的api接口定义 |
arch | 包含了针对不同系统架构的特定代码,启动文件,执行库,目前主要涉及的arm,arm64和rsic-v等,开发中维护的设备树也在此目录下 |
board | 包含了特定开发板的配置文件和初始化代码,在移植时主要实现部分的平台代码 |
boot | 通常用于存放引导(Boot)代码和相关文件,这些文件对于系统的启动至关重要 |
cmd | 包含了u-boot支持的命令行命令的代码,在此目录下可以扩展自定义命令 |
common | 通用的代码,用于管理引导和启动过程的共用代码,执行启动时序 |
configs | 配置文件,用于编译时的配置选项,管理U-Boot支持功能 |
disk | 处理磁盘启动方式的代码 |
doc | u-boot文档和说明 |
drivers | 包含了各种外设的驱动程序,比如网络设备,存储设备等,如果希望自己实现驱动也添加到此目录 |
env | 包含管理环境变量的相关代码和配置 |
examples | 测试u-boot api的应用例程 |
fs | 管理文件系统的相关代码,例如FAT,ext等文件系统的支持 |
include | 包含了所有头文件的目录,定义u-boot中需要的头文件,数据结构和宏 |
lib | 包含系统编译需要的各类库,例如算法库ae,crypto, bzip2, 安全库optee等 |
Licenses | 相关的法律条款声明 |
net | 处理网络相关的代码,主要用于支持tcp/ip协议 |
post | 上电自检的相关代码 |
scripts | 用于构建和配置u-boot的脚本文件 |
test | 系统单元测试的相关目录 |
tools | 用于系统编译的工具,处理中间代码或者生成最终文件 |
在移植时,主要修改的内容如下。
对于启动流程分析,首先可通过生成的uboot.map来分析, 另外本文中对于代码只解析跳转或者功能关键部分。如果有需要,可以去相应文件查看。
启动文件,路径为”arch/arm/cpu/armv7/start.S“,内部定义了起始地址,这里在链接文件中有说明,其中链接文件为uboot.lds。
# LD链接文件
ENTRY(_start) #定义程序的入口地址
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
arch/arm/cpu/armv7/start.o (.text*)
}
#.....
}
start.S主要执行启动相关的处理,其中reset即为上电复位的入口函数,主要实现流程如下所示。
1. 配置cache,mmu,tlbs等信息
2. 配置时钟,存储,串口基础功能
3. 完成后,跳转到_main入口,进行下一步执行动作。
```c
# ------------------ reset函数 -------------------------
.globl reset
#.....
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT)
#ifdef CONFIG_CPU_V7A
bl cpu_init_cp15 # 配置CP15寄存器(包含cache,mmu,tlbs)
#endif
#if !CONFIG_IS_ENABLED(SKIP_LOWLEVEL_INIT_ONLY)
bl cpu_init_crit #配置重要寄存器,包含pull,mux和memory
#endif
#endif
bl _main
# ------------------ cpu_init_crit函数 -------------------------
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)
lowlevel_init.S位于地址”arch/arm/cpu/armv7/lowlevel_init.S””, 主要功能为进行栈的初始化,用于C接口的调用,另外也进一步调用初始化硬件的接口。
.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 # 将栈顶地址赋值给sp指针
#endif
/*
* 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)
crt0.S的地址为”arch/arm/lib/crt0.S“主要完成系统必要的初始化和C运行时的支持,其中_main也是系统的入口函数。
ENTRY(_main)
/*
* Set up initial C runtime environment and call board_init_f(0).
*/
ldr r0, =(CONFIG_SYS_INIT_SP_ADDR)
bic r0, r0, #7 /* 8-byte alignment for ABI compliance */
mov sp, r0
bl board_init_f_alloc_reserve /* common/init/board_init.c 从top地址分配预留地址,通过CONFIG_SYS_MALLOC_F_LEN定义 */
mov sp, r0
/* set up gd here, outside any C code */
mov r9, r0
bl board_init_f_init_reserve /* common/init/board_init.c 实现内存布局 */
mov r0, #0
bl board_init_f /* common/board_f.c 板级序列初始化接口 */
/*
* 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_NEW_GD] /* r9 <- gd->new_gd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
here:
bl relocate_vectors
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
CLEAR_BSS
bl coloured_LED_init /* led初始化, __weak未定义 */
bl red_led_on
/* 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 */
ldr pc, =board_init_r /* this is auto-relocated! */
board_init.c的地址为common/init/board_init.c, 主要实现内容排布,计算top地址,实例化内存管理指针gb,完成相应初始化,包含函数有board_init_f_alloc_reserve,board_init_f_init_reserve。
// top为栈顶的地址,申请global_data空间用于后续管理
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;
}
// 将上述申请的global_data的范围,进行初始化管理
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
if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
board_init_f_init_stack_protection_addr(base);
/* 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;
#endif
if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
board_init_f_init_stack_protection();
}
board_f.c地址在common/board_f.c中,主要执行一系列初始化序列,调用函数为board_init_f,执行序列为init_sequence_f,具体实现功能如下。
static const init_fnc_t init_sequence_f[] = {
setup_mon_len, /* 配置用于监视的内存空间 */
#ifdef CONFIG_OF_CONTROL /* 支持设备树的宏定义 */
fdtdec_setup, /* lib/fdtdec.c、解析设备树,并配置相应硬件,(获取memory容量也是在此文件,读取系统中memory节点) */
#endif
#ifdef CONFIG_TRACE_EARLY
trace_early_init, /* 初始化跟踪系统,用于早期的系统监测 */
#endif
initf_malloc, /* common/dlmalloc.c, 用于申请系统堆,CONFIG_SYS_MALLOC_F_LEN定义系统堆大小 */
log_init, /* common/log.c,初始化log链表,用于后续log调用 */
initf_bootstage, /* board_f.c,记录u-boot的运行阶段 */
event_init, /* common/event.c, 定义u-boot支持的事件 */
bloblist_maybe_init, /* common/bloblist.c, 用于多阶段u-boot可以传输数据 */
setup_spl_handoff, /* board_f.c, 配合bloblist查询SPL传递到U-Boot的数据 */
#if defined(CONFIG_CONSOLE_RECORD_INIT_F)
console_record_init, /* common/console.c, 配合bloblist查询SPL传递到U-Boot的数据 */
#endif
INITCALL_EVENT(EVT_FSP_INIT_F),
arch_cpu_init, /* 依赖cpu特性的配置 */
mach_cpu_init, /* soc/机器相关的cpu配置 */
initf_dm, /* board_f.c, 初始化驱动程序模型 */
#if defined(CONFIG_BOARD_EARLY_INIT_F)
board_early_init_f, /* board_f.c, 早期初始化,初始化关键硬件 */
#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, /* 获取cpu和总线时钟 */
#endif
#if !defined(CONFIG_M68K) || (defined(CONFIG_M68K) && !defined(CONFIG_MCFTMR))
timer_init, /* 初始化定时器 */
#endif
#if defined(CONFIG_BOARD_POSTCLK_INIT)
board_postclk_init,
#endif
env_init, /* env/env.c 初始化系统环境变量 */
init_baud_rate, /* 初始化波特率,根据环境变量或默认值 */
#ifndef CONFIG_ANDROID_AUTO_SUPPORT
serial_init, /* 串口通讯支持 */
#endif
console_init_f, /* common/console.c 第一阶段console初始化 */
display_options, /* board_f.c, 打印U-boot版本和编译信息,起始第一步 */
display_text_info, /* board_f.c, 打印U-Boot地址信息 */
checkcpu, /* 检查cpu信息 */
#if defined(CONFIG_SYSRESET)
print_resetinfo, /* 查询复位信息 */
#endif
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo, /* 打印CPU信息和运行频率 */
#endif
#if defined(CONFIG_DTB_RESELECT)
embedded_dtb_select, /* 重新切换dtb支持 */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
show_board_info, /* common/board_info.c,打印板级信息,设备树中的model配置 */
#endif
INIT_FUNC_WATCHDOG_INIT /* 初始化watchdog */
INITCALL_EVENT(EVT_MISC_INIT_F),
INIT_FUNC_WATCHDOG_RESET /* 复位watchdog,避免复位 */
#if CONFIG_IS_ENABLED(SYS_I2C_LEGACY)
init_func_i2c, /* 启动遗留的i2c子系统和驱动 */
#endif
announce_dram_init, /* 打印DRAM: */
dram_init, /* 查询设备树,初始化sdram节点,加载设备树中父节点 */
#ifdef CONFIG_POST
post_init_f, /* 自检相关时钟初始化 */
#endif
INIT_FUNC_WATCHDOG_RESET
#if defined(CFG_SYS_DRAM_TEST)
testdram, /* 用于dram自检测试 */
#endif /* CFG_SYS_DRAM_TEST */
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
*/
/* 后续代码功能为重新定位代码到SDRAM,然后跳转SDRAM运行 */
setup_dest_addr,
#ifdef CONFIG_OF_BOARD_FIXUP
fix_fdt,
#endif
#ifdef CFG_PRAM
reserve_pram,
#endif
reserve_round_4k,
setup_relocaddr_from_bloblist,
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, /* 显示dram配置信息 */
INIT_FUNC_WATCHDOG_RESET
setup_bdinfo, /* 配置板级信息 */
display_new_sp,
INIT_FUNC_WATCHDOG_RESET
reloc_fdt,
reloc_bootstage,
reloc_bloblist,
setup_reloc,
#if defined(CONFIG_X86) || defined(CONFIG_ARC)
copy_uboot_to_ram,
do_elf_reloc_fixups,
#endif
clear_bss,
/*
* Deregister all cyclic functions before relocation, so that
* gd->cyclic_list does not contain any references to pre-relocation
* devices. Drivers will register their cyclic functions anew when the
* devices are probed again.
*
* This should happen as late as possible so that the window where a
* watchdog device is not serviced is as small as possible.
*/
cyclic_unregister_all,
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX)
jump_to_copy,
#endif
NULL,
};
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) && \
!defined(CONFIG_ARC)
/* NOTREACHED - jump_to_copy() does not return */
hang();
#endif
//返回到crt0.S,继续执行
}
直接开始下一小节: Linux kernel移植