u-boot学习笔记(三)

一、u-boot启动流程分析

以下针对ARMv8、黑芝麻bstA1000b,不启用TPL和SPL,支持动态重定位:

1.SPL:即Secondary Program Loader二级加载器,是一个精简的预加载程序,主要用于初始化硬件并加载主u-boot镜像。硬件系统一上电,会去某个地址取指令(一般是0x00000000),然后软件便开始运行。通常来讲,SoC厂家都会做一个ROM在SoC内部,这个ROM很小,里面固化了上电启动的代码(一经固化,永不能改),这部分代码被称为BootROM。BootROM的作用是将外部存储器上的代码读到内存,这里的内存一般指的是SoC厂家在芯片内部做的小容量SRAM,如果这个SRAM足够大可以直接将u-boot.bin读进去,那么就不需要SPL,在将u-boot.bin读到SRAM之后,执行u-boot,完成DDR的初始化并将内核从外部存储器读取到DDR执行。但是SRAM一般成本很高,容量很小,容不下几百KB的u-boot.bin,这时候就需要SPL,BootROM先将SPL读取到SRAM,然后SPL将u-boot.bin读取到DDR,最后u-boot将内核读取到DDR执行。但也可能SoC的BootROM固化代码只从Flash加载SPL前几KB到片内SRAM,然后执行start.S,初始化时钟、DDR控制器等,将完整SPL从Flash复制到DDR的目标地址,然后跳转到DDR中的SPL执行重定位(通过adr/ldr计算运行时偏移,修正.rel.dyn表中的地址),而后继续加载主U-Boot或内核。如下图所示:

具体的执行过程由不同板子的具体实现情况而定。TPL(Tertiary Program Loader)是比SPL更底层的三级引导加载程序,通常用于资源极度受限的嵌入式场景。当芯片的BootROM功能极为有限或硬件初始化流程特别复杂时,系统会采用三级引导架构:BootROM→TPL→SPL→主u-boot。

2.程序的链接由链接脚本决定(.lds文/件),编译完的u-boot会生成一个u-boot.lds文件。在armv8中,u-boot默认使用arch/arm/cpu/armv8/u-boot.lds进行链接(链接脚本LDSCRIPT在顶层Makefile中指定,也可以通过CONFIG_SYS_LDSCRIPT在xxx_defconfig或Makefile进行指定路径),u-boot-spl和u-boot-tpl使用arch/arm/cpu/armv8/u-boot-spl.lds进行链接(如果需要编译spl或tpl,即CONFIG_SPL或CONFIG_TPL被配置为y,此时不仅会生成u-boot.bin,还会编译生成spl/u-boot-spl.bin或tpl/u-boot-tpl.bin,他们使用的Makefile都是scripts/Makefile.spl,使用的链接脚本都是arch/arm/cpu/armv8/u-boot-spl.lds)。uboot入口地址也是由连接器决定的,在配置文件中可以由CONFIG_SYS_TEXT_BASE指定,这个会在编译时出现在ld连接器的选项-Ttext中。下面的例子是基于u-boot使用arch/arm/cpu/armv8/u-boot.lds进行链接的情况,如下图:

ENTRY指定了程序的入口地址为_start,_start是在/arch/arm/cpu/armv8/start.S文件中定义的。在u-boot.lds文件中有几个重要的地址标记:__image_copy_start、__image_copy_end、__rel_dyn_start、__rel_dyn_end。u-boot启动过程通常包括从存储介质(例如NAND、SD卡、SPI Flash等)加载映像到内存中的过程,为了正确加载u-boot和其他程序,启动代码需要知道映像的起始位置和结束位置。__image_copy_start和__image_copy_end就是用来标记这个区域的符号,__image_copy_start标记了需要重定位时需要进行内存拷贝的映像起始地址,__image_copy_end标记了需要重定位时需要进行内存拷贝的映像结束地址。__rel_dyn_start和__rel_dyn_end这两个符号标记了动态重定位数据的起始和结束位置,它们通常用于描述程序中的动态重定位信息(.rela.dyn段),并在映像重定位时重新写入重定位信息。这些符号其实是在arch/arm/lib/sections.c中定义的,如下图:

另外还有一个重要的段.u_boot_list,.u_boot_list段定义了系统中当前支持的所有命令和设备驱动,此段把散落在各个文件中通过U_BOOT_CMD的一系列拓展宏定义的命令和U_BOOT_DRIVER的拓展宏定义的设备驱动收集到一起,并按照名字排序存放,以便后续在命令行快速检索到命令并执行和检测注册的设备和设备树匹配probe设备驱动初始化。

3./arch/arm/cpu/armv8/start.S文件中的_start标签定义如下:

头文件boot0-linux-kernel-header.h提供Linux内核的标准启动头,确保符合ARM Linux启动协议;而boot0.h则针对特定SoC的定制化启动代码,处理芯片特有的初始化或安全启动流程。如果都不需要,则直接跳转到reset开始执行。然后会执行reset->save_boot_params->save_boot_params_ret。首先执行的是下面这段,判断当前是否使用了SPL并且是BST_A1000,如果是则会先完成SPL代码的拷贝,将其拷贝到_TEXT_SPL_BASE处(其值为PHYS_SYSMEM_SRAM_START即0x18040000),下面框出的部分保证relocation_spl重定位完成后正确返回到重定位后的relocation_spl_return处继续执行(没搞明白这段代码为什么要放在#if CONFIG_POSITION_INDEPENDENT下面,感觉放这可能有点问题,因为SPL一般不会开启CONFIG_POSITION_INDEPENDENT,后续紧接着的重定位应该是为u-boot服务的而不是SPL):

接下来是实现动态重定位段.rela.dyn部分的重定位,会将.rela.dyn段中每个地址值加上运行地址-链接地址的差值,实现动态重定位信息的修改,这里的重定位只是为u-boot镜像重定位前的代码部分继续执行服务的,后续在将整个u-boot镜像重定位时还会重新写入u-boot镜像重定位后的.rela.dyn部分的信息。如下图(开启了宏CONFIG_POSITION_INDEPENDENT才会支持地址无关功能,一般情况下SPL并没有动态重定位段(查看其lds文件可知),在SPL中CONFIG_POSITION_INDEPENDENT应不被定义,所以如果是SPL这部分代码一般不会被执行):

如果定义了CONFIG_SYS_RESET_SCTRL(表示在reset时调整sctrl寄存器的值),则会执行reset_sctrl,会根据当前所处的异常等级调整相应的sctlr_elx寄存的值。然后就是中断向量表的设置(前提是#if defined(CONFIG_ARMV8_SPL_EXCEPTION_VECTORS) || !defined(CONFIG_SPL_BUILD)),会根据当前所处异常等级将vbar_elx设置为中断向量表所在地址vectors(vectors定义在arch/arm/cpu/armv8/exceptions.S,查看该文件可知u-boot中的同步异常会直接导致系统崩溃,会调用do_bad_sync函数并触发panic),并开启浮点和SIMD,如下图:

后面接着执行以下一系列操作:若定义了CONFIG_ARMV8_SET_SMPEN则开启多核缓存一致性、执行apply_core_errata根据当前核心类型修复特定ARM核心的硬件缺陷、执行lowlevel_init进行处理器特定的初始化操作,初始化低级硬件,特别是中断控制器(GIC)、若当前核是主核(master)则跳转到_main函数执行,若是从核则等待下一步指令。

4._main定义在arch/arm/lib/crt0_64.S。执行到_main后首先根据当前环境设置栈指针sp,若定义了SYS_MALLOC_F_LEN,则会在栈中为malloc划分空间(一直向下划分,即低地址方向划分),接着为全局数据结构struct global_data gd在栈中预留(分配)空间并初始化,board_init_f_alloc_reserve函数用于在栈中划分空间供malloc使用并为全局数据struct global_data gd预留空间,board_init_f_init_reserve函数会清零global_data区域,若定义了SYS_MALLOC_F_LEN则会将gd->malloc_base指向所分配的供malloc使用的内存空间的起始地址,以上地址都是16字节对齐的。如下图:

上图中的代码会将全局数据gd的地址存储在x18寄存器中,然后执行board_init_f,u-boot中通过宏定义gd获取全局数据地址时实际就是通过读取x18寄存器实现的,对于不使用SPL和TPL,而直接使用u-boot时,board_init_f定义在/common/board_f.c,如下图:

initcall_run_list函数用于执行初始化函数列表init_sequence_f,它会遍历列表中的每一个函数然后依次执行,init_sequence_f是一个函数指针数组,包含板级初始化的各个步骤,initcall_run_list(init_sequence_f)包括的所有函数如下:

  • setup_mon_len:根据不同架构计算u-boot镜像长度,并将其存储到gd->mon_len中,例如对于ARM架构gd->mon_len的值为(ulong)&__bss_end-(ulong)_start,即为整个u-boot镜像大小
  • fdtdec_setup:如果定义了CONFIG_OF_CONTROL开启了设备树,则会执行该函数确定设备树(FDT)的存储位置,并将当前使用的设备树地址存储到gd->fdt_blob,还会验证设备树部分数据的有效性。
  • trace_early_init:如果定义了CONFIG_TRACE开启了函数追功能,则会执行该函数在内存固定地址(CONFIG_TRACE_EARLY_ADDR)分配一个缓冲区,用来存储函数的调用次数、调用关系、调用时间戳等相关信息。
  • initf_malloc:如果定义了SYS_MALLOC_F_LEN,执行该函数就会初始化全局数据结构gd中的与早期的动态内存分配池相关的数据(这部分供malloc使用的内存已在board_init_f_alloc_reserve函数中分配),该函数会检查gd->malloc_base确保该值已被初始化,并设置动态分配内存的大小gd->malloc_limit为SYS_MALLOC_F_LEN,设置内存分配指针gd->malloc_ptr为0表示从gd->malloc_bas处开始分配。
  • log_init:用于初始化u-boot的日志系统,该函数会遍历所有注册的日志驱动(log_driver,存储在.u_boot_list_2_log_driver_2段中),为每个驱动分配一个运行时设备结构(log_device)。然后初始化全局日志链表,即gd->log_head,将这些log_device挂载到gd->log_head上。然后gd->flags或上GD_FLG_LOG_READY说明日志系统初始化完成,设置默认日志级别gd->default_log_level为CONFIG_LOG_DEFAULT_LEVEL,设置默认日志格式gd->log_fmt为LOGF_DEFAULT。
  • initf_bootstage:用于初始化和恢复启动阶段分析功能bootstage,bootstage是u-boot的一项性能分析工具,用于记录启动过程中各个阶段的耗时,记录时间戳和事件。该函数会初始化bootstage功能,为其分配内存,并将gd->bootstage指向其起始地址,还会根据情况恢复bootstage在SPL阶段记录的时间戳和事件标记。
  • bloblist_init:如果定义了CONFIG_BLOBLIST,则会执行该函数检查bloblist是否存在,不存在会创建一个,主要是初始化bloblist的头部,并将gd->bloblist指向这个头部。bloblist(Binary Large Object List)是u-boot中用于管理动态数据块(blob)的通用数据结构,本质上是一个内存中的键值存储系统。它允许u-boot的不同模块(如启动阶段、驱动、命令等)在运行时共享结构化数据。
  • setup_spl_handoff:用于从bloblist中检索SPL阶段传递的"移交信息"(handoff data),并将其记录到u-boot的全局数据结构中的gd->spl_handoff,以便主u-boot阶段使用这些信息。
  • initf_console_record:用于初始化控制台,如果定义了CONFIG_CONSOLE_RECORD和SYS_MALLOC_F_LEN,则该函数会为控制台输入和输出malloc分配内存,并初始化gd->console_in和gd->console_out结构体,将分配的内存地址写入其中。
  • arch_fsp_init:该函数函数用于Intel架构平台上,与FSP(Firmware Support Package) 配合使用,FSP是Intel提供的一套固件支持包,用于帮助硬件平台在启动时执行一些初始化操作,如初始化CPU、内存、I/O控制器等硬件。ARM架构不需要定义该函数。
  • arch_cpu_init:与架构相关的CPU初始化函数。源码中采用弱定义__weak实现为空,可以根据需求自定义。
  • mach_cpu_init:与特定平台相关的CPU初始化函数。源码中采用弱定义__weak实现为空,可以根据需求自定义。
  • initf_dm:用于设备模型(Device Model, DM)的初始化,如果定义了CONFIG_DM和SYS_MALLOC_F_LEN,则该函数会初始化设备模型,并扫描系统中已经注册的设备。如果定义了CONFIG_TIMER_EARLY,该函数还会初始化与设备模型相关的定时器。
  • arch_cpu_init_dm:与具体CPU相关的设备模型初始化函数。源码中采用弱定义__weak实现为空,可以根据需求自定义。
  • board_early_init_f:如果定义了CONFIG_BOARD_EARLY_INIT_F,则会执行该函数。该函数与硬件平台(board)相关,用于执行板级的早期初始化,源码中并未实现该函数,可以根据需求自定义。
  • get_clocks:定义了CONFIG_PPC或CONFIG_SYS_FSL_CLK或CONFIG_M68K时会执行该函数,该函数用于获取CPU和总线时钟。源码中未实现该函数,可以根据需求自定义。
  • timer_init:定时器初始化函数。源码中采用弱定义__weak实现为空(定义在/lib/time.c),可以根据需求自定义。比如使用黑芝麻A1000时,可在/board/bst/a1000b/board_common.c中重新定义timer_init。
  • board_postclk_init:用于在时钟初始化之后,执行与硬件平台相关的定制配置,可根据需求自定义。
  • env_init:用于初始化环境驱动。该函数会按照优先级从高到低遍历所有的外设(如EEPROM、MMC等,优先级数字越小优先级越高,0比1优先级高),查找外设中是否存有环境驱动初始化函数drv->init()且是否可以成功执行drv->init()。并会将gd->env_load_prio设置为已经初始化的优先级最低的环境驱动的优先级(对应数字最大),将gd->env_has_init或上所有成功初始化的外设对应的位,但这里的gd->env_load_prio会在后面的initr_env函数执行过程中被修改为系统所加载的环境变量对应的环境驱动的优先级。如果没有找到符合要求的可加载环境变量的外设则将gd->env_addr设为默认环境变量地址&default_environment[0],并将gd->env_valid设为ENV_VALID表示环境变量有效。
  • init_baud_rate:该函数用于从环境变量中查询串口波特率的值,并将其记录到gd->baudrate。
  • serial_init:用于为控制台绑定一个可用的串口设备。如果CONFIG_IS_ENABLED(SERIAL_PRESENT)即支持串口,则该函数会绑定当前有效的串口设备作为控制台,并将gd->flags或上GD_FLG_SERIAL_READY表示串口初始化完成。
  • console_init_f:用于初始化控制台。该函数会将gd->have_console设置为1表示控制台初始化完成,并根据环境变量设置修改gd->flags更新控制台的静默(silent)模式状态,然后将预控制台缓冲区(pre-console buffer)中的内容通过串口输出,较新的u-boot版本没有启用预控制台缓冲区功能。
  • display_options:用于打印启动横幅信息。就是每次u-boot开始运行时输出的u-boot版本号、编译时间等信息。
  • display_text_info:用于打印一些文本信息。如果开启u-boot的DEBUG功能的话就会输出text_base、bss_start、bss_end。
  • checkcpu:平台相关的cpu验证函数,主要用于在u-boot启动早期对CPU的核心功能进行基础检查,确保其符合运行要求,它的具体行为因架构而异,可以根据需求自定义。PPC、X86、SH架构的芯片会执行,ARM架构不会执行。
  • print_resetinfo:如果定义了CONFIG_SYSRESET,则会执行该函数查询并打印复位信息。
  • print_cpuinfo:如果定义了CONFIG_DISPLAY_CPUINFO,则会执行该函数打印当前CPU的详细信息。
  • embedded_dtb_select:如果定义了CONFIG_DTB_RESELECT,则会执行该函数重新选择设备树文件。
  • show_board_info:如果定义了CONFIG_DISPLAY_BOARDINFO,则会执行该函数打印当前开发板的相关信息。
  • INIT_FUNC_WATCHDOG_INIT:初始化看门狗。
  • misc_init_f:如果定义了CONFIG_MISC_INIT_F,则会执行该函数,这是一个平台相关的早期通用初始化钩子函数,可以根据需求自定义。
  • INIT_FUNC_WATCHDOG_RESET:复位看门狗。
  • init_func_i2c:如果定义了CONFIG_SYS_I2C,则会执行该函数初始化i2c。
  • init_func_vid:如果定义了CONFIG_VID未定义CONFIG_SPL,则会执行该函数,该函数用于动态调整CPU/SoC核心电压的配置,可以根据需求自定义。
  • dram_init:将可用物理内存大小记录到gd->ram_size。
  • post_init_f:如果定义了CONFIG_POST,则会执行该函数进行硬件自检(POST, Power-On Self-Test)的初始化阶段测试,遍历预定义的测试项列表(post_list),逐个运行测试逻辑,并记录总耗时。
  • testdram:如果定义了CONFIG_SYS_DRAM_TEST,则会执行该函数进行物理内存(DRAM)的完整性测试,验证系统内存是否能够正确读写数据,避免因内存硬件故障导致系统不稳定或崩溃。
  • init_post:如果定义了CONFIG_POST,则会执行该函数初始化并运行上电自检(POST, Power-On Self-Test)的ROM阶段测试,确保基础硬件可靠。
  • setup_dest_addr:用于设置和调整u-boot启动过程中使用的内存地址,包括DRAM的基地址(gd->ram_base)、DRAM顶部地址(gd->ram_top)、可用内存DRAM大小(gd->ram_size)与多核处理器(如果存在)相关的内存区域,还会初步设置gd->relocaddr,这是u-boot用于内存重定位的地址。如果定义了CONFIG_SYS_MEM_TOP_HIDE则会在DRAM末尾保留一片内存(这种情况下gd->ram_top指向这块内存的起始地址),该内存中从上到下依次用于存储不会被u-boot和Linux修改的区域(可选)、内核日志缓冲区、受保护的内存、LCD帧缓存、监控代码、板信息结构。
  • reserve_pram:用于为受保护的RAM(pram)保留一部分内存。它从环境变量中获取pram的大小,并将其从gd->relocaddr中减去。这样做的目的是将内存中的一部分空间专门保留给受保护的RAM,以便后续使用。
  • reserve_round_4k:用于调整gd->relocaddr的值,使其按照4KB对齐。
  • reserve_mmu:用于为MMU相关的内存区域(主要是TLB表)保留内存空间,并向下调整gd->relocaddr的值,该函数会在全局数据结构中记录TLB的大小和起始内存地址。在源码中该函数是弱定义的,可以根据需求自定义。
  • reserve_video:用于为显示相关的内存区域预留空间,确保视频输出、LCD或帧缓冲区等功能所需的内存得到适当分配,会将与显示相关的内存地址信息记录到全局数据gd中,并且向下调整gd->relocaddr 的值。
  • reserve_trace:用于为跟踪数据(trace data)分配内存,会将用于存储trace data的内存首地址记录到gd->trace_buff,并且向下调整gd->relocaddr的值。
  • reserve_uboot:用于为u-boot的代码、数据和BSS段保留内存空间,并根据不同架构的需求调整内存地址。它确保了u-boot自身的内存区域得到预留,并且向下调整gd->relocaddr以容纳u-boot相关的内容。到这里gd->relocaddr就最终确定了。该函数还会用此时gd->relocaddr初始化gd->start_addr_sp(栈空间起始地址)。
  • reserve_malloc:用于为动态内存分配(malloc)预留一块内存区域,并向下调整gd->start_addr_sp的值。
  • reserve_board:用于为板级信息(Board Info)结构bd_t预留内存空间,将其内存地址记录到gd->bd,并初始化该内存区域(清零),然后向下调整gd->start_addr_sp的值。
  • setup_machine:用于为板级信息结构bd_t中的bi_arch_number字段设置一个架构编号(Machine Type),通常是为了向Linux内核提供正确的硬件平台标识,以便在启动时识别该硬件平台。并会向下调整gd->start_addr_sp的值。
  • reserve_global_data:用于为全局数据结构(global data,gd_t)预留内存空间,将其内存地址记录到gd->new_gd,并向下调整gd->start_addr_sp的值。
  • reserve_fdt:用于为设备树(Device Tree Blob,FDT)分配内存,将其内存地址记录到gd->new_fdt,并向下调整gd->start_addr_sp的值。
  • reserve_bootstage:用于为启动阶段的计时信息(bootstage)分配内存,将其内存地址记录到gd->new_bootstage,并向下调整gd->start_addr_sp的值。
  • reserve_bloblist:用于为u-boot的bloblist分配内存,将其内存地址记录到gd->new_bloblist,并向下调整gd->start_addr_sp的值。
  • reserve_arch:用于为特定架构进行架构特定的内存预留,源码中采用弱定义__weak实现为空,可以根据需求自定义。
  • reserve_stacks:用于为系统保留栈空间并确保栈指针(sp)的对齐(16字节对齐),该函数会调用arch_reserve_stacks,根据具体架构进行栈空间的分配,并调整gd->start_addr_sp的值,此时gd->start_addr_sp的值就最终确定了。
  • dram_init_banksize:用于初始化DRAM的大小和起始地址,并将这些信息存储在全局数据gd->bd中。
  • show_dram_config:用于输出系统中所有的DRAM配置信息,包括每块DRAM的起始地址和大小。
  • setup_board_part1:用于设置并保存与硬件相关的系统配置信息,存储到gd-bd中,M68K、MIPS、PPC、SH架构的芯片会执行,ARM架构不会执行。
  • setup_board_part2:用于设置并保存与硬件相关的系统配置信息,存储到gd-bd中,M68K和PPC架构的芯片会执行,ARM架构不会执行。
  • fix_fdt:用于修复设备树。
  • reloc_fdt:用于完成设备树的重定位,将设备树复制到gd->new_fdt处,并修改gd->fdt_blob的值为gd->new_fdt。
  • reloc_bootstage:用于完成bootstage的重定位,将bootstage复制到gd->new_bootstage处,并修改gd->bootstage的值为gd->new_bootstage。
  • reloc_bloblist:用于完成bloblist的重定位,将bloblist复制到gd->new_bloblist处,并修改gd->bloblist的值为gd->new_bloblist。
  • setup_reloc:用于完成全局数据gd的重定位和u-boot映像重定位的设置,会将全局数据复制到gd->new_gd处,并计算并设置u-boot重定位的偏移量gd->reloc_off。但是如果GD_FLG_SKIP_RELOC被设置,则该函数会跳过上述步骤直接返回。

在initcall_run_list(init_sequence_f)中后面还有几个函数与ARM架构无关,所以以上便是init_sequence_f中包含的所有函数,至此initcall_run_list(init_sequence_f)执行完毕。执行完initcall_run_list(init_sequence_f)返回后,因为是ARM架构,所以会直接从board_init_f返回,至此已经完成了串口、i2c、控制台、DRAM等硬件的初始化,完成了环境变量的读取位置设置,完成了内存的区域划分,并完成了除u-boot本身以外其他数据的重定位,并更新了全局数据gd中的数据值。此时内存空间分布如下图所示:

5.board_init_f函数返回后,根据当前是够是SPL阶段执行不同代码,如果是SPL阶段则根据配置进行全局数据gd和栈sp的重定位,如下图:

如果不是SPL阶段,则会执行下面的代码:

这里主要完成将sp重新设置为u-boot重定位后的栈地址,并设置lr地址为u-boot重定位后的relocation_return标记的地址,然后跳转到relocate_code函数,从relocate_code函数返回后就到了重定位之后的relocation_return标记处。relocate_code定义在/arch/arm/lib/relocate_64.S中,如下图:

relocate_code函数主要完成了整个u-boot镜像的重定位,将__image_copy_start和__image_copy_end之间的u-boot镜像拷贝到目标位置gd->relocaddr处,然后向.rela.dyn段中写入实际运行地址,完成动态重定位。从relocate_code返回后进行BSS段的设置,如下图:

该部分将BSS段清零,其中ldr x0, =__bss_start和ldr x1, =__bss_end获取的是已经动态重定完成后实际运行时的物理地址。然后程序跳转到board_init_r函数,对于不使用SPL和TPL,而直接使用u-boot时,board_init_r定义在/common/board_r.c,如下图:

与board_init_f一样,board_init_r会调用initcall_run_list函数执行初始化函数列表init_sequence_r,它会遍历列表中的每一个函数然后依次执行,init_sequence_r是一个函数指针数组,initcall_run_list(init_sequence_r)包括的所有函数如下:

  • initr_trace:如果定义了CONFIG_TRACE,执行该函数会初始化u-boot的函数调用追踪(Tracing)功能,支持记录函数调用次数、调用关系、调用时间戳等相关信息。如果定义了CONFIG_TRACE_EARLY,该函数还会将u-boot映像重定位之前的trace信息拷贝过来。
  • initr_reloc:该函数将gd->flags或上GD_FLG_RELOC和GD_FLG_FULL_MALLOC_INIT说明已经完成了u-boot重定位和初始化动态内存分配器。
  • initr_caches:该函数用于cache初始化,仅在ARM架构时执行,会根据配置初始化指令cache和数据cache。
  • initr_reloc_global_data:用于初始化和调整一些与全局数据的值,将其修改为u-boot镜像重定位之后正确的值。
  • initr_unlock_ram_in_cache:在使用的是E500架构且定义了CONFIG_SYS_INIT_RAM_LOCK时会被执行,用于解锁RAM中的缓存(D-cache),ARM架构不会执行。
  • initr_barrier:该函数用于在PowerPC(PPC)架构上插入内存屏障指令,其他架构该函数为空。
  • initr_malloc:用于初始化malloc系统,把全局变量mem_malloc_start、mem_malloc_end、mem_malloc_brk初始化为正确的内存地址值。
  • log_init:用于初始化u-boot的日志系统,该函数会遍历所有注册的日志驱动(log_driver,存储在.u_boot_list_2_log_driver_2段中),为每个驱动分配一个运行时设备结构(log_device)。然后初始化全局日志链表,即gd->log_head,将这些log_device挂载到gd->log_head上。然后gd->flags或上GD_FLG_LOG_READY说明日志系统初始化完成,设置默认日志级别gd->default_log_level为CONFIG_LOG_DEFAULT_LEVEL,设置默认日志格式gd->log_fmt为LOGF_DEFAULT。
  • initr_bootstage:在bootstage中添加一个名为"board_init_r"的记录,bootstage是u-boot的一项性能分析工具,用于记录启动过程中各个阶段的耗时,记录时间戳和事件。
  • initr_console_record:用于初始化控制台,如果定义了CONFIG_CONSOLE_RECORD,则该函数会为控制台输入和输出malloc分配内存,并初始化gd->console_in和gd->console_out结构体,将分配的内存地址写入其中。
  • initr_noncached:如果定义了CONFIG_SYS_NONCACHED_MEMORY,则会执行该函数初始化非缓存内存区域。
  • bootstage_relocate:该函数用于对bootstage中所有记录中的name字符串重新malloc分配内存,并拷贝name字符串,因为此时malloc可使用的内存区域已经改变了,所以需要重定位。
  • initr_of_live:如果定义了CONFIG_OF_LIVE表明动态构建设备树,则会执行该函数初始化并构建设备树,并在bootstage中添加一个名为"of_live"的记录。
  • initr_dm:如果定义了CONFIG_OF_LIVE,则会执行该函数用于初始化u-boot的设备模型(Device Model, DM)系统,并在bootstage中添加一个名为"dm_r"的记录。
  • board_init:板级初始化函数,执行与硬件平台相关的初始化任务,具体根据所使用的板子自定义。
  • set_cpu_clk_info:如果定义了CONFIG_CLOCKS,则会执行该函数设置和配置CPU时钟信息,以确保系统正确地初始化时钟和频率,则会执行该函数用于,可以根据需求自定义。
  • efi_memory_init:如果定义了CONFIG_EFI_LOADER,则会执行该函数初始化与EFI(Extensible Firmware Interface)相关的内存区域,并为EFI启动加载器(EFI Loader)和其他EFI相关功能准备内存。EFI是一种固件接口标准,它用于在操作系统引导时提供硬件抽象和启动加载功能。
  • stdio_init_tables:用于初始化标准输入输出(stdio)系统,并处理与设备相关的初始化工作。
  • initr_serial:最终会调用serial_init函数,用于为控制台绑定一个可用的串口设备。如果CONFIG_IS_ENABLED(SERIAL_PRESENT)即支持串口,则该函数会绑定当前有效的串口设备作为控制台,并将gd->flags或上GD_FLG_SERIAL_READY表示串口初始化完成。
  • initr_announce:用于在调试时输出重定位后的u-boot内存地址。
  • INIT_FUNC_WATCHDOG_RESET:复位看门狗。
  • initr_manual_reloc_cmdtable:如果定义了CONFIG_EFI_LOADER,则会执行该函数对命令表中的地址进行修正,将这些地址加上gd->reloc_off保证他们指向正确的内存地址。
  • initr_trap:在PowerPC、M68K或MIPS架构下执行,用于初始化陷阱处理程序,ARM架构不会执行。对于ARMv8,它的异常向量在start.S中就已经设置了。
  • initr_addr_map:如果定义了CONFIG_ADDR_MAP,则会执行该函数用于初始化系统的地址映射,以确保系统在启动时正确配置内存地址空间的映射关系,这通常用于配置MMU或设置设备和内存的访问规则,可以根据需求自定义。
  • board_early_init_r:如果定义了CONFIG_BOARD_EARLY_INIT_R,则会执行该函数用于进行后期硬件初始化,可以根据需求自定义。
  • initr_post_backlog:如果定义了CONFIG_POST,则会执行该函数回溯并输出POST(Power-On Self Test)日志,它会检查已经存储在gd->post_log_word和gd->post_log_res中的测试结果,并输出相关的日志信息。
  • initr_pci:如果定义了CONFIG_PCI,则会执行该函数初始化PCI设备,但只有在未启用CONFIG_DM_PCI配置选项的情况下才会执行PCI初始化,并且根据是否定义了CONFIG_SYS_EARLY_PCI_INIT该函数执行的时间会有所差异。
  • arch_early_init_r:如果定义了CONFIG_ARCH_EARLY_INIT_R,则会执行该函数用于架构特定的初始化,执行平台相关的后期硬件设置或准备工作,可以根据需求自定义。
  • power_init_board:在板级初始化过程中进行电源管理相关的初始化。源码中采用弱定义__weak实现为空,可以根据需求自定义。
  • initr_flash:如果定义了CONFIG_MTD_NOR_FLASH,则会执行该函数初始化NOR Flash存储器,设置与其相关的配置参数,并记录在全局数据gd->bd中。
  • cpu_init_r:在PowerPC、M68K或X86架构下执行,用于对CPU高级功能(如时间基准、定时器)的初始化,可以根据需求自定义,ARM架构不会执行。
  • initr_nand:如果定义了CONFIG_CMD_NAND,则会执行该函数检测并初始化NAND闪存设备,并打印其容量信息。
  • initr_onenand:如果定义了CONFIG_CMD_ONENAND,则会执行该函数初始化OneNAND存储,OneNAND是一种NAND类型的存储器,具有比传统NAND Flash更高的性能。
  • initr_mmc:如果定义了CONFIG_MMC,则会执行该函数初始化MMC存储设备。
  • initr_env:用于加载环境变量,它完成了环境变量的加载、设置和一些环境变量的初始化工作。该函数按照优先级从高到低从外设(如EEPROM、MMC等,优先级数字越小优先级越高,0比1优先级高)加载环境变量,他会遍历已经在env_init函数中调用对应的drv->init成功初始化的环境驱动,调用环境驱动对应的drv->load函数加载环境变量,只要加载成功就返回,所以最终加载的就是优先级最高的环境驱动对应的环境变量(数字最小),并将gd->env_load_prio设置为系统所加载的环境变量对应的环境驱动的优先级。如果定义了CONFIG_OF_CONTROL表示使用设备树,则该函数会设置环境变量"fdtcontroladdr"的值设置为设备树内存地址gd->fdt_blob。该函数还会设置环境变量"loadaddr"的值。
  • initr_malloc_bootparams:如果定义了CONFIG_SYS_BOOTPARAMS_LEN,则会执行该函数为内核引导参数(bootparams)分配内存空间,并将其地址记录在全局数据gd->bd->bi_boot_params中。
  • initr_secondary_cpu:在u-boot完成基础硬件(如存储设备、环境变量)初始化后,为多核系统中的辅助CPU执行额外的初始化工作。源码中采用弱定义__weak实现为空,可以根据需求自定义。
  • mac_read_from_eeprom:如果定义了CONFIG_ID_EEPROM或CONFIG_SYS_I2C_MAC_OFFSET,则会执行该函数用于从EEPROM中读取MAC地址的函数,确保系统能够正确配置网络接口的MAC地址,可以根据需求自定义。
  • stdio_add_devices:用于初始化并注册不同类型的输入/输出设备(例如键盘、视频设备、串口、USB等)以便u-boot可以使用它们进行标准输入/输出操作。
  • initr_jumptable:用于malloc分配空间创建并初始化跳转表(Jump Table),并将跳转表地址记录在全局数据gd->jt中。跳转表通常用于在不同的模块或函数之间动态地进行函数调用,通过使用函数指针来实现更灵活的控制。
  • initr_api:如果定义了CONFIG_API,则会执行该函数,该函数通过调用api_init函数来初始化u-boot的API系统。通过填充calls_table数组实现初始化工作,calls_table是一个包含所有API函数指针的表(数组),这些函数指针对应的API实现包括获取字符、打印字符、环境变量操作、设备操作、延迟、显示等功能,这些API_*函数是u-boot系统的接口函数,每个函数都有一个对应的calls_table索引,在初始化时,将这些函数指针按索引放入calls_table中。
  • console_init_r:用于控制台设备的初始化,该函数会根据环境变量中设置的控制台设备类型,初始化标准输入(stdin)、标准输出(stdout)、标准错误(stderr)设备。
  • console_announce_r:如果定义了CONFIG_DISPLAY_BOARDINFO_LATE,则会执行该函数打印控制台的一些公告信息,包括u-boot版本号、编译时间等信息。
  • show_board_info:如果定义了CONFIG_DISPLAY_BOARDINFO_LATE,则会执行该函数打印当前开发板的相关信息。
  • arch_misc_init:如果定义了CONFIG_ARCH_MISC_INIT,则会执行该函数完成一些与特定硬件架构相关的杂项的初始化,可以根据需求自定义。
  • misc_init_r:如果定义了CONFIG_MISC_INIT_R,则会执行该函数负责处理那些不属于标准硬件初始化流程、但与具体开发板或硬件设计紧密相关的定制化操作,可以根据需求自定义。
  • initr_kgdb:如果定义了CONFIG_CMD_KGDB,则会执行该函数初始化KGDB(内核调试器)支持,主要作用是为Linux内核的远程调试提供底层支持。
  • interrupt_init:用于初始化中断,源码中该函数为空,直接返回0。
  • initr_enable_interrupts:只在ARM架构下被执行,用于开启中断,源码中该函数为空,直接返回0。
  • timer_init:如果定义了CONFIG_MICROBLAZE或CONFIG_M68K,则会执行该函数初始化定时器,可以根据需求自定义。
  • initr_status_led:如果定义了CONFIG_LED_STATUS,则会执行该函数初始化和配置状态指示灯(LED)。
  • initr_ethaddr:如果定义了CONFIG_CMD_NET,则会执行该函数初始化和获取以太网接口的MAC地址并存储到相应的环境变量中。
  • board_late_init:如果定义了CONFIG_BOARD_LATE_INIT,则会执行该函数进行一些板级初始化工作,可以根据需求自定义。
  • initr_scsi:如果defined(CONFIG_SCSI) && !defined(CONFIG_DM_SCSI),则会执行该函数初始化SCSI(Small Computer System Interface)硬件接口。
  • initr_bbmii:如果定义了CONFIG_BITBANGMII,则会执行该函数初始化Bit-banged MII (Media Independent Interface),Bit-banging是一种通过CPU的GPIO引脚模拟硬件协议(在这里是MII协议)的技术,通常当硬件不提供硬件支持的MII控制器时,Bit-banging允许使用通用的I/O引脚来实现类似的功能。
  • initr_net:如果定义了CONFIG_CMD_NET,则会执行该函数初始化以太网网络接口。
  • initr_post:如果定义了CONFIG_POST,则会执行该函数运行POST(Power-On Self Test)测试,进行内存(RAM)自检和其他一些测试。
  • initr_pcmcia:如果defined(CONFIG_CMD_PCMCIA) && !defined(CONFIG_IDE),则会执行该函数初始化PCMCIA(Personal Computer Memory Card International Association)控制器和PCMCIA卡,PCMCIA卡通常包括网络卡、存储卡、调制解调器卡等,它们通过PCMCIA接口与计算机进行通信。
  • initr_ide:如果defined(CONFIG_IDE) && !defined(CONFIG_BLK),则会执行该函数初始化IDE(Integrated Drive Electronics)接口,IDE通常用于管理与存储设备(如硬盘、光驱、磁带机等)之间的数据传输。
  • initr_bedbug:如果定义了CONFIG_CMD_BEDBUG,则会执行该函数初始化Bedbug,Bedbug是一种嵌入式系统中的调试工具,通常用于早期引导阶段或资源受限的环境下进行调试。
  • initr_mem:如果定义了CONFIG_PRAM,则会执行该函数设置环境变量"mem"的值为gd->ram_size-CONFIG_PRAM<<10(CONFIG_PRAM为保留的受保护内存大小,单位为KB),即将实际可用内存大小存储在环境变量"mem"中。
  • run_main_loop:到此已经完成了所有初始化工作,该函数会调用main_loop函数。

在initcall_run_list(init_sequence_r)有一部分函数在initcall_run_list(init_sequence_f)中已经执行过了,再次执行一遍是因为,在这两个阶段中,使用malloc分配的内存地址范围是不一样的,所以需要重新执行一下。initcall_run_list(init_sequence_r)中的函数全部执行完之后,便执行到main_loop函数。

6.main_loop函数定义在/common/main.c,如下图:

在main_loop函数中,会根据配置先执行cli_init函数初始化解析器为后续读取u-boot命令行做准备,会根据配置初始化Hush Shell及全局环境变量链表。如果设置了环境变量"preboot",则会先执行该变量中定义的命令,然后是TFTP的自动更新功能。如果定义了CONFIG_AUTOBOOT开启了自动启动功能,则bootdelay_process会获取要执行的命令列表,此时bootdelay_process定义如下图:

可见正常情况下,得到的命令列表来自于环境变量"bootcmd"。而后在main_loop中会判断设备树是否设置了bootsecure表示安全启动,或者设备树中是否定义了"bootcmd",若设备树中设置了bootsecure则必须使用设备树中的"bootcmd"覆盖环境变量中的"bootcmd",否则会启动失败,而且此时一定会执行"bootcmd"而不会被打断,如果没有设置bootsecure但是设备树中定义了"bootcmd",那么原来环境变量中的"bootcmd"也会被覆盖。确定好要执行的命令列表s会,会执行autoboot_command函数,其定义如下图:

在autoboot_command中,会调用abortboot函数来完成倒计时功能,如果要执行的s命令列表不为空且倒计时结束仍没有被打断,则会执行s命令列表中的命令,一般情况下这里面的命令会完成操作系统启动(但是如果在倒计时期间被打断了,则会从autoboot_command函数返回,返回到main_loop中执行cli_lopp函数进入u-boot的命令行模式)。run_command_list函数会根据当前配置选择相应的解释器来解析并执行命令:若定义了CONFIG_HUSH_PARSER则使用Hush解释器,此时调用parse_string_outer函数对命令列表进行解析,parse_string_outer函数会调用parse_stream_outer函数,以分号;为分隔符依次解析并执行命令列表中的函数或shell命令。若未定义CONFIG_HUSH_PARSER而定义了CONFIG_CMDLINE则使用简易命令行解释器,此时调用cli_simple_run_command_list函数对命令列表进行解析,该函数即简易命令行解释器通过\n来分隔命令列表中的每段命令并依次调用cli_simple_run_command函数执行,且会将传入的命令列表中的所有\n修改为\0(所以在使用简单解释器时需要malloc分配内存复制命令列表防止原命令列表被修改),cli_simple_run_command函数会在每段命令中查找分号;分隔出每个命令依次执行(注意是不在单引号''内的分号;)。如果在倒计时期间被crtl+C打断,则会返回main_loop中去执行cli_loop,如下图:

即进入u-boot的命令行模式,cli_loop会根据配置选择Hush解释器或者简易命令行解释器。若是简易命令行解释器,则会执行cli_simple_loop函数,如下图:

该函数会调用cli_readline函数去读取控制台输入,存储到全局数据console_buffer中,并根据返回值修改lastcommand中存储的值,然后调用run_command_repeatable函数,使用简易命令行解释器时该函数会调用cli_simple_run_command函数执行对应的命令。注意到cli_simple_loop函数的结尾会有一个返回值判断,这个判断的作用是,若当前执行的命令是不可重复执行的,即非repeatable的,则将lastcommand置空,因为u-boot提供了一个功能:在控制台直接输入Enter,它会重复执行上一次执行的命令(如果这个命令是repeatable的),对于非repeatable的命令将lastcommand置空后,直接输入Enter就无法重复执行了。若是Hush解释器解释器,则会执行parse_file_outer函数,如下图:

该函数会调用parse_stream_outer函数完成对控制台输入的命令的识别和执行。简易命令行解释器是不支持在"bootcmd"环境变量中加入shell命令的,他不支持解析shell命令,只能解析在u-boot中注册的命令,但是Hush解释器还可以解析shell命令。

二、黑芝麻bstA1000b的Linux启动过程分析

1.黑芝麻bstA1000b"bootcmd"环境变量定义如下图:

可见正常情况下,他会去执行在uEnv.txt中定义的bootcmd_cmd命令,在bootcmd_cmd中通过run 自定义命令(环境变量)命令依次执行了以下命令:

  • bootcmd_heatbeat:heartbeat触发硬件心跳检测、getChipId读取芯片唯一ID,存储到环境变量serialnumber、将serialnumber=$serialnumber添加到环境变量extra_bootargs中,将其作为内核启动参数传递,环境变量extra_bootargs通常用来存储额外的内核启动参数(bootargs),这些参数会在启动时传递给操作系统内核
  • bootcmd_setfdt:根据{enable_hyp}判断当前是否启用Hypervisor模式,然后进行一些与内核和设备树相关的环境变量的设置。如果启用Hypervisor,则设置内核映像kernel为Image.itb,设置设备树(dtb)的加载地址dtb_addr_r为0x80000000,设置内核的加载地址kernel_addr_r为0x90000000,设置内核二进制文件的地址kernel_bin_addr为0x900000ec,设置设备树文件的名称fdt为"board_name".dtb。如果没启用Hypervisor,则设置设备树文件的名称fdt为"$board_name".dtb,设置内核映像kernel为"/Image.itb",设置内核的加载地址kernel_addr_r为0x90000000,设置设备树(dtb)的加载地址dtb_addr_r为0x80000000
  • bootcmd_fastboot:检查分区mmc boot_device:cache_partition中是否存在/fastboot/command文件,如果存在,设置fastboot_flag=1,启动fastboot模式,并复位设备(reset),此时boot_device的值为0,cache_partition的值为5,所以检查的分区是mmc 0:5
  • bootcmd_sotaDebug:如果变量ota_debug为1,设置upgrade_available=1标记需要固件升级,ota_debug的默认值是设为0的
  • bootcmd_sotaEnvLoad:检查分区mmc boot_device:cache_partition中是否存在/global/command文件,如果存在,加载该文件到内存地址$loadaddr,并导入其中的环境变量(env import),此时boot_device的值为0,cache_partition的值为5,所以检查的分区是mmc 0:5,此时loadaddr为默认值0x85000000
  • bootcmd_loadRecoveryEnv:检查分区mmc boot_device:cache_partition中是否存在/recovery/command文件,如果存在,加载该文件到内存地址$loadaddr,并导入其中的环境变量(env import),同时设置recovery_flag=1标记进入恢复模式,此时boot_device的值为0,cache_partition的值为5,所以检查的分区是mmc 0:5,此时loadaddr为默认值0x85000000
  • bootcmd_bootcount_process:用于系统升级。如果upgrade_available=1表示表示系统正在进行固件升级或升级相关的操作,则将bootcounts变量的值增加一个+,如果bootcounts最开始为空或未设置,则它会变为+,每次执行这行命令时,bootcounts会被更新,用于统计系统启动次数或标记某些状态,如果bootcounts等于++++则触发回滚操作,防止固件升级过程中出现的错误反复发生。如果boot_rollback=1也会直接触发回滚。不论是否回滚都会执行bootcmd_sotaEnvSave保存系统的当前环境变量到分区mmc boot_device:cache_partition的/global/command文件中。upgrade_available的默认值为0不进行升级。
  • bootcmd_rollback_process:如果rollback=1(需要回滚)且recovery_flag=1(恢复模式),则设置recovery_rollback=1标记恢复模式下的回滚
  • bootcmd_recovery_cmd:根据不同的条件设置恢复模式,并执行一系列恢复操作,如果回滚标志rollback为1,设置recovery_flag为1,表示进入恢复模式。根据不同的条件(如recovery_rollback和flash_system),选择适当的分区(如3、4或5)作为恢复分区。设置ramdisk为/recovery.gz,指定恢复时使用的ramdisk镜像,然后行一系列恢复相关的命令。
  • bootcmd_boot:根据boot_rollback的值,设置boot_partition的值,若boot_rollback为1表示回滚则设置boot_partition=2,否则设置boot_partition=1。
  • bootcmd_load:若hyp_flag=1表示启用Hypervisor则执行bootcmd_load_hyp,否则执行bootcmd_load_directly。对于bootcmd_load_directly,首先会依次执行bootcmd_args(重新设置bootargs,将extra_bootargs的值添加进bootargs)和bootcmd_otenv_recovery(设置bootm_size的值为0x9800000),然后会使用load命令从存储设备mmc boot_device:boot_partition中(正常启动时为mmc 0:0)分别加载内核镜像和设备树镜像到目标内存地址,最后会执行bootcmd_dtoverlay,该命令会设置环境变量dtoverlay_name的值为/overlay/bst-overlay.dtbo,设置环境变量dtoverlay_addr的值为0x80100000,然后检查MMC分区上是否有文件/overlay/bst-overlay.dtbo,若有则会将其加载到0x80100000处,再执行fdt相关命令设置设备树的内存地址、调整设备树大小、应用设备树文件。
  • bootcmd_run:若hyp_flag=1表示启用Hypervisor则执行bootcmd_run_hyp,否则执行bootcmd_run_directly。对于bootcmd_run_directly,执行bootm {kernel_addr_r} - {dtb_addr_r}命令启动内核。

所以在调用bootcmd之后,系统正常启动时,最终会调用bootm命令启动内核。

2.bootm命令对应的函数为do_bootm(定义在/cmd/bootm.c中),如下图所示:

do_bootm会调用do_bootm_states函数完成内核的启动,传入的BOOTM_STATE_XXX参数表示需要经历的阶段,每个阶段会执行对应的函数,在上面的调用中共经历了以下八个阶段:

  • BOOTM_STATE_START:对应函数bootm_start,主要用于初始化images结构体,调用boot_start_lmb来初始化内存分配,记录引导过程的开始时间点。
  • BOOTM_STATE_FINDOS:对应函数bootm_find_os,用于获取内核映像的头部信息,识别内核格式(如Legacy、FIT、Android),提取内核映像的相关参数,并存储到images全局结构体中。
  • BOOTM_STATE_FINDOTHER:对应函数bootm_find_other,用于获取ramdisk或者设备树信息。
  • BOOTM_STATE_LOADOS:对应函数bootm_load_os,该函数将操作系统内核映像解压到目标地址,并确保在加载过程中没有重叠,并刷新缓存确保数据一致性。
  • BOOTM_STATE_RAMDISK:对应函数boot_ramdisk_high,用于将ramdisk加载到适当的内存位置。ramdisk(也称为内存磁盘或虚拟磁盘)是一种将数据存储在计算机内存(RAM)中的文件系统,在嵌入式系统和操作系统引导过程中,ramdisk通常用作一个临时的文件系统,存储启动时所需的文件、驱动程序和配置文件等,它的优点是速度非常快,因为内存的访问速度远远高于磁盘存储设备。

经过上述几个阶段(函数调用)之后,do_bootm_states会调用bootm_os_get_boot_func函数来查找系统启动函数,参数images->os.os就是系统类型,images->os.os已经在之前的BOOTM_STATE_FINDOS阶段被获取,根据这个系统类型来选择对应的启动函数。然后继续下面几个阶段:

  • BOOTM_STATE_OS_PREP:对应函数为boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images),例如如果是Linux系统,则获取的启动函数boot_fn为do_bootm_linux,所以就会执行do_bootm_linux(BOOTM_STATE_OS_PREP, argc, argv, images),当传入参数是BOOTM_STATE_OS_PREP时,do_bootm_linux会调用boot_prep_linux完成对环境变量"bootargs"的解析并存储到对应全局数据结构中。
  • BOOTM_STATE_OS_FAKE_GO:伪启动阶段,只有定义了CONFIG_TRACE才会进入该阶段,用于调试,对应的函数为boot_selected_os,该函数会调用boot_fn(BOOTM_STATE_OS_FAKE_GO, argc, argv, images),例如如果是Linux系统,则获取的启动函数boot_fn为do_bootm_linux,所以就会执行do_bootm_linux(BOOTM_STATE_OS_FAKE_GO, argc, argv, images),当传入参数是BOOTM_STATE_OS_FAKE_GO时,do_bootm_linux会调用boot_jump_linux,因为传入的参数是BOOTM_STATE_OS_FAKE_GO表示伪启动,所以boot_jump_linux并不会启动Linux。
  • BOOTM_STATE_OS_GO:启动阶段,对应的函数为boot_selected_os,该函数会调用boot_fn(BOOTM_STATE_OS_GO, argc, argv, images),例如如果是Linux系统,则获取的启动函数boot_fn为do_bootm_linux,所以就会执行do_bootm_linux(BOOTM_STATE_OS_GO, argc, argv, images),当传入参数是BOOTM_STATE_OS_GO时,do_bootm_linux会调用boot_jump_linux,因为传入的参数是BOOTM_STATE_OS_GO表示启动,所以boot_jump_linux会进入Linux。

3.对于ARMv8来说boot_jump_linux函数的定义如下图:

boot_jump_linux函数会从images->ep获取内核入口点,内核入口点kernel_entry的具体地址由Linux内核定义,然后根据配置调用汇编函数armv8_switch_to_el2,其中传入的参数包括内核入口点,在armv8_switch_to_el2函数内部,最终会通过eret指令跳转到images->ep,正式进入Linux内核。在进入Linux之前,boot_jump_linux函数还会调用announce_and_cleanup函数完成一些打印和清理操作,如下图:

如上图会打印输出Starting kernel ...表示开始启动Linux内核。在Linux内核启动后,Linux会接管整个系统的硬件和内存管理,并且可能会清理掉u-boot使用的内存区域,一旦进入了Linux就无法再返回u-boot。

4.bootz与bootm命令类似,也可以用于加载内核镜像启动Linux,区别在于bootz专门用于zImage格式的镜像,zImage是Linux内核的一种压缩格式,其头部包含自解压代码,当内核被加载到内存后,由内核自身完成解压(而不需要u-boot完成解压),u-boot只需将zImage原样加载到指定地址,跳转到入口点即可。所以如果使用bootz启动内核,bootz在调用do_bootm_states时比bootm调用do_bootm_states少经历一个BOOTM_STATE_LOADOS阶段。

5.bstA1000b内存地址总结:bstA1000b实际内存有效地址包括:0x8000 0000~0X1 0000 0000(size 8000 0000)、0x1 8000 0000~0X2 0000 0000(size 8000 0000)。系统设置的内核映像kernel为Image.itb,设置的设备树(dtb)的加载地址dtb_addr_r为0x80000000,设置的内核的加载地址kernel_addr_r为0x90000000。一开始u-boot栈的初始化地址CONFIG_SYS_INIT_SP_ADDR=PHYS_SYSMEM_SRAM_START+PHYS_SYSMEM_SRAM_SIZE=0X18040000+768KB,然后从这个地址开始向下划分malloc区域和全局数据gd的区域(见本文第五章第4点)。uboot后期的内存分布首地址gd->ram_base为CONFIG_SYS_SDRAM_BASE=PHYS_SDRAM_1=0x80000000,内存总大小为PHYS_SDRAM_1_SIZE= (SZ_2G - SZ_256M)(参考本文第五章第4点内存分布图)。

相关推荐
海尔辛18 分钟前
学习黑客三次握手快速熟悉
网络·学习·tcp/ip
_Jyuan_1 小时前
镜头内常见的马达类型(私人笔记)
经验分享·笔记·数码相机
丰锋ff3 小时前
考研英一学习笔记 2018年
笔记·学习·考研
1296004523 小时前
pytorch基础的学习
人工智能·pytorch·学习
岂是尔等觊觎3 小时前
软件设计师教程——第一章 计算机系统知识(下)
经验分享·笔记·其他
Oll Correct3 小时前
计算机二级WPS Office第三套电子表格
笔记
睡不着还睡不醒4 小时前
【笔记】unsqueeze
笔记
LouSean4 小时前
Unity按钮事件冒泡
经验分享·笔记·学习·unity·游戏引擎
pq113_64 小时前
OrangePi Zero 3学习笔记(Android篇)4 - eudev编译(获取libudev.so)
android·笔记·学习
AI新视界5 小时前
『Python学习笔记』ubuntu解决matplotlit中文乱码的问题!
linux·笔记·ubuntu