[A133]uboot启动流程
hongxi.zhu 2024-6-21
1. 第一阶段
lds描述
从u-boot.lds
中能找到程序的汇编入口ENTRY(_start)
brandy/brandy-2.0/u-boot-2018/u-boot.lds
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
...
ENTRY(_start)
的实现在start.S
中,根据平台架构, 有对应的实现,当前平台是armv8
_start
brandy/brandy-2.0/u-boot-2018/arch/arm/cpu/armv8/start.S
.globl _start
_start:
#if defined(LINUX_KERNEL_IMAGE_HEADER)
#include <asm/boot0-linux-kernel-header.h>
#elif defined(CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK)
/** Various SoCs need something special and SoC-specific up front in* order to boot, allow them to set that in their boot0.h file and then* use it here.*/
#include <asm/arch/boot0.h>
#elseb reset /* 跳转到reset块 */
#endif
reset
brandy/brandy-2.0/u-boot-2018/arch/arm/cpu/armv8/start.S
reset:/* Allow the board to save important registers */b save_boot_params /*空实现,并跳转回save_boot_params_ret*/
.globl save_boot_params_ret
save_boot_params_ret:/** Could be EL3/EL2/EL1, Initial State:* Little Endian, MMU Disabled, i/dCache Disabled*/adr x0, vectors /*将异常向量表基地址写到x0*/switch_el x1, 3f, 2f, 1f /*根据异常等级选择el3/el2/el1情况处理*/
3: msr vbar_el3, x0 /*el3*/mrs x0, scr_el3orr x0, x0, #0xf /* SCR_EL3.NS|IRQ|FIQ|EA */msr scr_el3, x0msr cptr_el3, xzr /* Enable FP/SIMD */b 0f /*设置完上述相关寄存器,跳出*/
2: msr vbar_el2, x0 /*el2*/mov x0, #0x33ffmsr cptr_el2, x0 /* Enable FP/SIMD */b 0f
1: msr vbar_el1, x0 /*el1*/mov x0, #3 << 20msr cpacr_el1, x0 /* Enable FP/SIMD */
0: /* 空执行,相当于跳出 *//* Apply ARM core specific erratas */bl apply_core_errata /*arm核的特殊配置*//** Cache/BPB/TLB Invalidate* i-cache is invalidated before enabled in icache_enable()* tlb is invalidated before mmu is enabled in dcache_enable()* d-cache is invalidated before enabled in dcache_enable()*//* Processor specific initialization */bl lowlevel_init /* A133看起来没做啥事情 */master_cpu:bl _main /*跳转到_main*/
_main
brandy/brandy-2.0/u-boot-2018/arch/arm/lib/crt0_64.S
ENTRY(_main)/** Set up initial C runtime environment and call board_init_f(0).*//*清除x0寄存器的最低四位(#0xf即二进制1111)并赋值给sp栈指针, x0寄存器的值是调用方传递*/bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */mov x0, sp /*将栈指针sp的值写入x0寄存器作为下一条bl命令的参数,这个参数就是global区域的顶部地址top*/bl board_init_f_alloc_reserve /*跳转到C中的board_init_f_alloc_reserve,给全局gd变量分配内存*/mov sp, x0 /*上面的bl命令执行后,它的返回值存放在x0中,将这个新的栈指针值赋值给sp指针*//* set up gd here, outside any C code */mov x18, x0 /*x18寄存器用作全局数据(Global Data, gd)的指针,上面board_init_f_alloc_reserve的返回值就是需要设置的gd的指针地址*/bl board_init_f_init_reserve /*跳转到C中的board_init_f_init_reserve, 初始化gd变量的内容为0,并确定后续分配的gd变量内容偏移基地址*/mov x0, #0 /*x0清零,下一个bl命令传入参数值为0*/bl board_init_f /*跳转到C中的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 x0, [x18, #GD_START_ADDR_SP] /* x0 <- gd->start_addr_sp */bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */ldr x18, [x18, #GD_NEW_GD] /* x18 <- gd->new_gd */adr lr, relocation_return/* Add in link-vs-relocation offset */ldr x9, [x18, #GD_RELOC_OFF] /* x9 <- gd->reloc_off */add lr, lr, x9 /* new return address after relocation */ldr x0, [x18, #GD_RELOCADDR] /* x0 <- gd->relocaddr */b relocate_coderelocation_return:/** Set up final (full) environment*/bl c_runtime_cpu_setup /* still call old routine */
#endif /* !CONFIG_SPL_BUILD *//** Clear BSS section*/ldr x0, =__bss_start /* this is auto-relocated! */ldr x1, =__bss_end /* this is auto-relocated! */
clear_loop:str xzr, [x0], #8cmp x0, x1b.lo clear_loop/* call board_init_r(gd_t *id, ulong dest_addr) */mov x0, x18 /* gd_t */ldr x1, [x18, #GD_RELOCADDR] /* dest_addr */b board_init_r /* PC relative jump *//* NOTREACHED - board_init_r() does not return */ENDPROC(_main)
board_init_f_alloc_reserve
brandy/brandy-2.0/u-boot-2018/common/init/board_init.c
ulong board_init_f_alloc_reserve(ulong top)
{.../* LAST : reserve GD (rounded up to a multiple of 16 bytes) */// 从global区域分配一块大小为struct global_data大小的16字节对齐的内存存放全局的global_data变量// top就是x0寄存器传入的值(具体值需要调试才知道)top = rounddown(top-sizeof(struct global_data), 16);return top;
}
board_init_f_init_reserve
brandy/brandy-2.0/u-boot-2018/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; // 拿到寄存器x18里存的gd变量内存地址/* zero the area */memset(gd_ptr, '\0', sizeof(*gd)); //将这块内存数据,初始化为0/* set GD unless architecture did it already */.../* next alloc will be higher by one GD plus 16-byte alignment */base += roundup(sizeof(struct global_data), 16); //内存向上对齐16字节,这个地址就是gd变量后续分配内容的偏移基地址/** record early malloc arena start.* Use gd as it is now properly set for all architectures.*/...
}
board_init_f
brandy/brandy-2.0/u-boot-2018/common/board_f.c
void board_init_f(ulong boot_flags)
{gd->flags = boot_flags; // 这里汇编传入的x0是0,所以boot_flags = 0gd->have_console = 0;//执行init_sequence_f数组中的每一个函数指针,会依次初始化cpu/dm/外设总线/串口/optee/等if (initcall_run_list(init_sequence_f))hang();
}
init_sequence_f数组中特别注意的是下面的函数,将接下来的链接重定位息息相关:
static const init_fnc_t init_sequence_f[] = {setup_mon_len, /*设置内存区域长度 gd->mon_len = __bss_end - __image_copy_start;*/
#ifdef CONFIG_OF_CONTROLfdtdec_setup, /*设置dtb的地址gd->fdt_blob*/
#endif
#ifdef CONFIG_TRACE_EARLYtrace_early_init,
#endifinitf_malloc, //看起来这里并没有实现f_malloc的区域,也许别的地方实现了log_init,initf_bootstage, /* uses its own timer, so does not need DM */initf_console_record,
#if defined(CONFIG_HAVE_FSP)arch_fsp_init,
#endifarch_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,
#endifenv_init, /* initialize environment */ //初始化env驱动init_baud_rate, /* initialze baudrate settings */ //从env读取出设置的串口波特率serial_init, /* serial communications setup */ //初始化串口console_init_f, /* stage 1 init of console */ //从uboot的设备树中获取调试等级debug_mode等display_options, /* say that we are here */ //"打印 U-Boot 2018.07 (xxxx) Allwinner Technology"display_text_info, /* show debugging info if required *///打印bss段的内存起止地址(未初始化全局变量数据段)、text段内存起始地址(代码段)
#if defined(CONFIG_PPC) || defined(CONFIG_SH) || defined(CONFIG_X86)checkcpu,
#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,
#endifINIT_FUNC_WATCHDOG_INIT
#if defined(CONFIG_MISC_INIT_F)misc_init_f,
#endifINIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_I2C)init_func_i2c,
#endif#if defined(CONFIG_VID) && !defined(CONFIG_SPL)init_func_vid,
#endif
#if defined(CONFIG_HARD_SPI)init_func_spi,
#endif
#if defined(CONFIG_OPTEE25)smc_init,
#endifannounce_dram_init,dram_init, /* configure available RAM banks */
#ifdef CONFIG_POSTpost_init_f,
#endifINIT_FUNC_WATCHDOG_RESET
#if defined(CONFIG_SYS_DRAM_TEST)testdram,
#endif /* CONFIG_SYS_DRAM_TEST */INIT_FUNC_WATCHDOG_RESET#ifdef CONFIG_POSTinit_post,
#endifINIT_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, //获取内存区域的top地址
#ifdef CONFIG_PRAMreserve_pram,
#endifreserve_round_4k, //内存4K对齐
#ifdef CONFIG_ARMreserve_mmu,
#endifreserve_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,
#if defined(CONFIG_M68K) || defined(CONFIG_MIPS) || defined(CONFIG_PPC) || \defined(CONFIG_SH)setup_board_part1,
#endif
#if defined(CONFIG_PPC) || defined(CONFIG_M68K)INIT_FUNC_WATCHDOG_RESETsetup_board_part2,
#endifdisplay_new_sp,
#ifdef CONFIG_OF_BOARD_FIXUPfix_fdt,
#endifINIT_FUNC_WATCHDOG_RESETreloc_fdt,reloc_bootstage,setup_reloc,
#if defined(CONFIG_X86) || defined(CONFIG_ARC)copy_uboot_to_ram,do_elf_reloc_fixups,clear_bss,
#endif
#if defined(CONFIG_XTENSA)clear_bss,
#endif
#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \!CONFIG_IS_ENABLED(X86_64)jump_to_copy,
#endifNULL,
};
init_sequence_f数组中特别注意的是下面的函数,将接下来的链接重定位内存结构息息相关
setup_dest_addr, //获取内存区域的top地址reserve_round_4k, //内存4K对齐reserve_mmu, //在内存中为MMU TLB页表分配16KB内存reserve_video, //fb相关,这里空实现reserve_uboot, //在内存中为U-Boot text, data & bss段分配790KB内存reserve_malloc, //在内存中为malloc区域预留500MB内存reserve_board, //在内存中为board info预留96 Byte内存reserve_global_data, // 在内存中为global_data预留336 Byte内存reserve_fdt, // 在内存中为fdt 设备树预留149KB内存reserve_stacks, // 为栈空间预留空间(IRQ stack和stack)。sp指针指向的base地址dram_init_banksize, //空实现
相关打印
Ram size: 40000000 // 1GB
Ram top: 80000000 //高地址
TLB table from 7fff0000 to 7fff4000
Reserving 790k for U-Boot at: 7ff2a000
Reserving 512128k for malloc() at: 60b0a000
Reserving 96 Bytes for Board Info at: 60b09fa0
Reserving 336 Bytes for Global Data at: 60b09e50
Reserving 153216 Bytes for FDT at: 60ae47d0
Reserving 8192 Bytes for IRQ stack at: 60ae27c0
New Stack Pointer is: 60ae27b0
[01.588]Relocation Offset is: 35f2a000
Relocating to 7ff2a000, new gd at 60b09e50, sp at 60ae27b0
为即将重定位做的内存分配,此时结构如下
reloc_fdt
重定位设备树文件
brandy/brandy-2.0/u-boot-2018/common/board_f.c
static int reloc_fdt(void)
{
#ifndef CONFIG_OF_EMBED,..if (gd->new_fdt) { //前面的reserved fdt就分配了内存区域memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size); //将fdt文件拷贝到内存的新地址中gd->fdt_blob = gd->new_fdt; //修改gd中fdt地址指向}
#endifreturn 0;
}
setup_reloc
重定位uboot text&bss&data段到内存中的新地址
brandy/brandy-2.0/u-boot-2018/common/board_f.c
static int setup_reloc(void)
{//gd->relocaddr就是前面内存模型中给uboot分配的空间的基地址//__image_copy_start是lds文件中描述拷贝uboot的起始地址gd->reloc_off = gd->relocaddr - (unsigned long)__image_copy_start; //重新设置gd中uboot重定位地址偏移memcpy(gd->new_gd, (char *)gd, sizeof(gd_t)); //将gd从flash中拷贝内存中的global data区域, 后续都使用new_gdtick_printf("Relocation Offset is: %08lx\n", gd->reloc_off);debug("Relocating to %08lx, new gd at %08lx, sp at %08lx\n",gd->relocaddr, (ulong)map_to_sysmem(gd->new_gd),gd->start_addr_sp);return 0;
}
到这里重定位的准备工作都做完了,board_init_f
函数执行结束并返回到_main
汇编标签往下执行
/** 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 x0, [x18, #GD_START_ADDR_SP] /* x0 <- gd->start_addr_sp */bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */ldr x18, [x18, #GD_NEW_GD] /* x18 <- gd->new_gd */adr lr, relocation_return/* Add in link-vs-relocation offset */ldr x9, [x18, #GD_RELOC_OFF] /* x9 <- gd->reloc_off */add lr, lr, x9 /* new return address after relocation */ldr x0, [x18, #GD_RELOCADDR] /* x0 <- gd->relocaddr */b relocate_coderelocation_return:
逐行解析:
ldr x0, [x18, #GD_START_ADDR_SP] /* x0 <- gd->start_addr_sp */
使用ARM64的
ldr
指令从寄存器x18
指向的结构体中加载GD_START_ADDR_SP
偏移量处的值到寄存器x0
。实际上x18
是gd的指针。
GD_START_ADDR_SP
从下面的汇编到C的map可知是gd->start_addr_sp
a133_linux/brandy/brandy-2.0/u-boot-2018/lib/asm-offsets.c
DEFINE(GD_START_ADDR_SP, offsetof(struct global_data, start_addr_sp));
bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */
bic
(位清除)指令将x0
(即gd->start_addr_sp
)的低4位清零,以确保栈指针(sp)是16字节对齐的。这是为了满足ABI的要求
ldr x18, [x18, #GD_NEW_GD] /* x18 <- gd->new_gd */
更新
x18
寄存器,使其指向gd->new_gd
。代码重定位后,全局描述符的gd变量指向需要改变
adr lr, relocation_return
使用
adr
(地址寄存器)指令获取relocation_return
标签的当前地址,并将其存储在链接寄存器(lr)中。这将是代码重定位后的返回地址。
ldr x9, [x18, #GD_RELOC_OFF] /* x9 <- gd->reloc_off */
从
gd(new_gd)
结构体中加载GD_RELOC_OFF
(可能是重定位偏移量)到x9
add lr, lr, x9 /* new return address after relocation */
将
x9
(重定位偏移量)加到lr
寄存器中,以得到重定位后的最终返回地址
ldr x0, [x18, #GD_RELOCADDR] /* x0 <- gd->relocaddr */
从
gd(new_gd)
结构体中加载GD_RELOCADDR
到x0
b relocate_code
跳转到
relocate_code
标签执行,传入的参数为GD_RELOCADDR
relocate_code
/** void relocate_code (addr_moni)** This function relocates the monitor code.* x0 holds the destination address.*/
ENTRY(relocate_code)stp x29, x30, [sp, #-32]! /* create a stack frame */mov x29, spstr x0, [sp, #16]/** Copy u-boot from flash to RAM*/adr x1, __image_copy_start /* x1 <- Run &__image_copy_start */subs x9, x0, x1 /* x8 <- Run to copy offset */b.eq relocate_done /* skip relocation *//** Don't ldr x1, __image_copy_start here, since if the code is already* running at an address other than it was linked to, that instruction* will load the relocated value of __image_copy_start. To* correctly apply relocations, we need to know the linked value.** Linked &__image_copy_start, which we know was at* CONFIG_SYS_TEXT_BASE, which is stored in _TEXT_BASE, as a non-* relocated value, since it isn't a symbol reference.*/ldr x1, _TEXT_BASE /* x1 <- Linked &__image_copy_start */subs x9, x0, x1 /* x9 <- Link to copy offset */adr x1, __image_copy_start /* x1 <- Run &__image_copy_start */adr x2, __image_copy_end /* x2 <- Run &__image_copy_end */
copy_loop:ldp x10, x11, [x1], #16 /* copy from source address [x1] */stp x10, x11, [x0], #16 /* copy to target address [x0] */cmp x1, x2 /* until source end address [x2] */b.lo copy_loopstr x0, [sp, #24]/** Fix .rela.dyn relocations*/adr x2, __rel_dyn_start /* x2 <- Run &__rel_dyn_start */adr x3, __rel_dyn_end /* x3 <- Run &__rel_dyn_end */
fixloop:ldp x0, x1, [x2], #16 /* (x0,x1) <- (SRC location, fixup) */ldr x4, [x2], #8 /* x4 <- addend */and x1, x1, #0xffffffffcmp x1, #R_AARCH64_RELATIVEbne fixnext/* relative fix: store addend plus offset at dest location */add x0, x0, x9add x4, x4, x9str x4, [x0]
fixnext:cmp x2, x3b.lo fixlooprelocate_done:switch_el x1, 3f, 2f, 1fbl hang
3: mrs x0, sctlr_el3b 0f
2: mrs x0, sctlr_el2b 0f
1: mrs x0, sctlr_el1
0: tbz w0, #2, 5f /* skip flushing cache if disabled */tbz w0, #12, 4f /* skip invalidating i-cache if disabled */ic iallu /* i-cache invalidate all */isb sy
4: ldp x0, x1, [sp, #16]bl __asm_flush_dcache_range
5: ldp x29, x30, [sp],#32ret
ENDPROC(relocate_code)
这个的内容比较繁杂涉及复杂的栈操作和链接操作,主要做了两件事:
- 拷贝flash上面的uboot代码段到内存中
- 重置链接中的
.rela.dyn
内容(与重定位相关)
完成重定位后,返回到_main
中,进入uboot第二阶段
2. 第二阶段
relocation_return:/** Set up final (full) environment*/bl c_runtime_cpu_setup /* still call old routine */ /*重定向向量表基址寄存器*/
#endif /* !CONFIG_SPL_BUILD *//** Clear BSS section 重定向后需要清除bss段内容,为接下来运行重定向的uboot做准备*/ldr x0, =__bss_start /* this is auto-relocated! */ldr x1, =__bss_end /* this is auto-relocated! */
clear_loop:str xzr, [x0], #8cmp x0, x1b.lo clear_loop/* call board_init_r(gd_t *id, ulong dest_addr) */mov x0, x18 /* gd_t */ /*传递新的gd的地址*/ldr x1, [x18, #GD_RELOCADDR] /* dest_addr */ /*gd->relocaddr就是重定向后,uboot在内存上运行的入口地址*/b board_init_r /* PC relative jump */ /*入口函数就是board_init_r函数,且不再返回*//* NOTREACHED - board_init_r() does not return */ENDPROC(_main)
board_init_r
brandy/brandy-2.0/u-boot-2018/common/board_r.c
void board_init_r(gd_t *new_gd, ulong dest_addr)
{gd->flags &= ~GD_FLG_LOG_READY;if (initcall_run_list(init_sequence_r))hang();/* NOTREACHED - run_main_loop() does not return */hang();
}
static init_fnc_t init_sequence_r[] = {initr_trace,initr_reloc, // 更新fdt的地址到env fdtaddr字段中/* TODO: could x86/PPC have this also perhaps? */
#ifdef CONFIG_ARMinitr_caches, //配置cpu smp和使能icache和dcache/* 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.*/
#endifinitr_reloc_global_data, //更新gd中一些地址偏移,A133这里基本空实现initr_barrier, //空实现initr_malloc, //初始化malloc区域log_init,initr_bootstage, /* Needs malloc() but has its own timer */initr_console_record, //空实现bootstage_relocate,
#ifdef CONFIG_DMinitr_dm, //初始化dm驱动框架
#endif
#if defined(CONFIG_ARM) || defined(CONFIG_NDS32) || defined(CONFIG_RISCV)board_init, /* Setup chipselects */ //全志平台相关的初始化,如CPU/axp Power/rtc/dma等
#endifstdio_init_tables, //stdio相关的配置initr_serial, //串口驱动初始化initr_announce, //打印说明当前uboot是运行在ram中
#ifdef CONFIG_SUNXI_LEDCinitr_ledc, // led demo
#endifpower_init_board, //空实现
#ifdef CONFIG_MMCinitr_mmc, //初始化mmc设备驱动,空实现
#endifinitr_secondary_cpu, //初始化第二个cpu,一般uboot都是只运行在cpu0上,这里空实现stdio_add_devices, //注册标准输入输出的各种函数,如puts/gets/tstcinitr_jumptable, //初始化函数跳转表,例如上面的stdio的这些函数console_init_r, /* fully init console as a device */ //初始化终端输出,可以是串口或者lcd,并映射stdout/stdin/stderrinterrupt_init, //空实现
#ifdef CONFIG_ARMinitr_enable_interrupts, //空实现
#endif
#ifdef CONFIG_SUNXI_FAST_BURN_KEYsunxi_fast_burn_key, //快速烧key相关功能
#endif
#ifdef CONFIG_ARCH_SUNXIinitr_sunxi_plat, //初始化flash相关
#endifboard_env_late_init, //获取pmu bootreason和keybox初始化
#ifdef CONFIG_ARCH_SUNXIsunxi_burn_key, //烧key功能
#endifrun_main_loop, //进入主循环,引导内核或者进入cli
};
因为uboot相当于重新运行了,所以init_sequence_r
中首先需要做各种初始化,最后进入引导linux内核启动流程或者进入cli_loop循坏中。