一、前言
uboot是一套小系统开源系统,在网络上我们能够找到对这个系统较多的分析,展锐团队在此基础
上适配了对应芯片的开机流程。采用了 uboot 系统, 和其它平台类似,虽然 u boot 本身就是双启动流程即( s pl -->uboot ),但是展锐依然 脱离 uboot 实现 自己 S PL 镜像, 因此小系统的组成变为 s pl/uboot 组合,和高通 xbl/uefi 及 M TK 平台 PBL /lk 类似 。 唯一的区别在于 U BOOT 的跳转过程。展锐平台的启动流程如下:

OnChipRom -->SPL -->SML -->TEEOS -->UBOOT -->KERNEL
SPL,仅 1 6KB ,其功能在于初始化 D DR ,并加载 S ML 、 T EEOS 及 U BOOT 镜像,随后跳转 S ML 镜像。
SML 相当于海思 B L31 ,负责安全上下文环境初始化和切换,在其正常处理后,将会跳转 T EEOS 。
TEEOS 即华为的 T EEOS 系统,负责 T A 等相关安全服务,初始化完成后即跳转 U BOOT 。
展锐的S PL 不同于 P BL 或者 X BL ,其体量较小,涉及功能较少,因此预计后续我们大部分代码修改应该在 u boot 中完成 ,本文着重探索 u boot 的启动流程 。
Uboot 为 开源代码,为了兼容多平台, 因此 涉及封装抽象的概念, U BOOT 依靠 如下抽象调用关系 cpu -->arch -->mach -->board -->common 层级调用 进行启动。
注:
SPL
代码路径
bsp/bootloader/chipram
UBOOT 代码路径
bsp/bootloader/u boot15
二、UBOOT 启动
1、 s tart
跳过一些编译的问题,我们直接来到能够接触到较早的开机阶段
cpu 初始化流程,其代码路径如下:
bsp/bootloader/u boot15/arch/arm/cpu/armv8/start.S
由于展讯本次的SOC 是 armv8 架构,因此 uboot 启动时会先跳转到对应 _start 启动位置开始启动,这部分的代码并非展锐适配, uboot 根据平台架构已经支持的代码 。 这里不展示代码,而直接展示调用关系,注意 这部分代码大部分为汇编代码;
_start
-
reset
-
apply_core_errata
-
lowlevel_init
-
->>-->_main
实际代码中有很多
A RM 芯片设置,甚至 a pply_core_errata 及 lowlevel_init 中还存在大量调用关系,这
里就不在展开,此部分代码均和 A RM 芯片规范强相关,通常我们并不会改动,了解即可。 这部分代码功能,大致 包含校验、 设置中断向量表、设置 M MU 、设置 C ACHE 等工作。
2、 main
_main 会的主流程在如下路径:
bsp
/bootloader/u boot15/arch/arm/lib crt0_64.S
这里已经从具体的C PU 架构 A RMV8 调用到外层 A RM 架构的通用接口了, 启动流程中存在大量这种调用关系,体现代码解耦适应多架构多平台的能力也就是上面 C PU -->ARCH 的设计理念。
_main
-
board_init_ f
-
c_runtime_cpu_setup
-
board_init_r
_
main 函数会初始化 C 语言环境,随后运行 b oard_init_f 函数,该函数是一个 C 语言函数包含众多芯片初始化行为, 展锐的芯片适配就在这里面完成。 crt0_64.S 代码注释的描述,较为详细。
3、 b oard_init_f
board_init_f 主流程在如下代码路径:
bootloader/uboot15/arch/arm/lib /board.c
board_init_f 已经是我们熟悉的代码了,开始进行 各类资源的初始化。
board_init_f
-
arch_cpu_init
-
mark_bootstage
-
fdtdec_check_fdt
-
board_early_init_f
-
timer_init
-
board_postclk_init
-
get_clocks
-
env_init
-
init_baudrate
-
serial_init
-
console_init_f
-
init_log_buffer
-
display_banner
-
print_cpuinfo
-
checkboard
-
init_func_i2c
-
dram_init
-
post_bootmode_init
-
post_run
-
dram_init_banksize
-
display_dram_config
board_init_f 函数中初始化了内存、 定时器、设置环境变量、初始化维测系统等等功能,这里
特别要注意 d ram_init ,内存的初始化已经架构提供的接口了,会跳转到具体芯片的实现。
bsp/bootloader/uboot15/board/spreadtrum/ums512_1h10
在我们使用的芯片路径下存在很多接口的重定义,这里面包含了模式选择,
D DR 初始化以及板级的初始化, cpu -->arch -->mach -->board 在这里得到体现。

board_init_f 执行完成后将会跳转会 main ,随后调用 b oard_init_r 进行进一步初始化。
4、 board_init_ r
board_init_r 主流程在如下代码路径:
bootloader/uboot15/arch/arm/lib /board.c
board_init_r更侧重于初始化 板机的资源,涉及的内容更多。
board_init_r
-
-> enable_caches
-
-> board_init
-
-> set_cpu_clk_info
-
-> serial_initialize
-
-> logbuff_init_ptrs
-
-> post_output_backlog
-
-> mem_malloc_init
-
-> arch_early_init_r
-
-> power_init_board
-
-> flash_init ufs_init mmc_initialize nand_init board_mmc_initialize
-
-> scsi_init
-
env_relocate
-
fdt_fixup_memory_region
-
stdio_init
-
jumptable_init
-
api_init
-
console_init_r
-
display_fdt_model checkboard
-
arch_misc_init misc_init_r
-
interrupt_init
-
enable_interrupts
-
board_late_init
-
bb_miiphy_init
-
eth_initialize
-
reset_phy
-
post_run
-
init_write_log
-main_loop
board_init_r 会初始化音频、存储等外设,最重要是调用 board_init 接口,这是 展锐在该 芯
片主要适配的 代码内容 board_init_r 最后会陷入死循环,因此开机流程在这里就不会再跳
转回 main 了 。
5、 b oard_init
board_init主流程在如下代码路径:
bsp/bootloader/uboot15/board/spreadtrum/ums512_1h10 ums512_1h10.c
board_init 为芯片特殊适配的初始化流程,类似 d ram_init 函数 功能,属于芯片特有代码
board_init
-
->>direct_acc_prot_enabledirect_acc_prot_enable
-
->>setup_chipram_envsetup_chipram_env
-
->>ADI_initADI_init
-
->>misc_initmisc_init
-
->>regulator_initregulator_init
-
->>pmic_adc_Initpmic_adc_Init
-
->>pin_initpin_init
-
->>sprd_eic_initsprd_eic_init
-
->>sprd_gpio_initsprd_gpio_init
-
->>sprd_led_initsprd_led_init
-
->>sprd_pmu_lowpower_initsprd_pmu_lowpower_init
-
->>TDPllRefConfigTDPllRefConfig
-
->>enable_global_clocksenable_global_clocks
-
->>thm_overheate_enthm_overheate_en
-
->>sci_glb_setsci_glb_set
这里面设置主要是芯片时钟、PPMUMU、、GGPIOPIO、、ppinin角管理等芯片特殊资源初始化。
6、 main_loop
代码位置:
bsp/bootloader/u
boot15/common /main.c
main loop 实际并非启动流程的结束,关键的开机阶段在此之后包括模式选择,加载启动等
过程。 main _loop 流程中主要决定当前是进启动模式还是加载模式。

代码执行至run_preboot_environment_command 函数中将会进行跳转。

获取pre boot 参数值,该数值等于 role ”,随后调用 run_command_list 进行跳转, 跳转至
d o_role 函数。该函数决定当前启动流程是进入下载模式还是启动模式。

无论进入什么模式均是简单设置b ootcmd 环境变量,这个环境变量决定了后续函数跳转的
方向。 现在再回到 m ain loop 函数,继续执行 bootdelay_process 函数 ,该函数会获取
b ootcmd 环境变量,并将其字符串返回给 main _loop 函数, 即 s 值为‘ cboot ’,随后调用
autoboot_command函数执行 跳转到 do_cboot 函数 ,正常启动情况下 后续 执行流程将不会
再回到 m ain_loop 函数 。
7、 do _cboot
do _cboot 是非常关键的 函数 代码位置如下:
bsp/bootloader/uboot15/common cmd_cboot.c
do_cboot 函数 主要功能 是启动模式选择及镜像加载和跳转的入口流程。
do_cboot
-
boot_pwr_check
-
-> get_mode_from_arg s_boot_func_array
-
board_boot_mode_regist
-
-> boot_mode_array[bootmode]()
通过多种途径获取启动模式 ,这里的多种途径指的是 s_boot_func_array 函数数组,判断启
动模式就是遍历 s_boot_func_array 函数数组中的各类检测函数,从多方面决策 本次启动的
模式,这里需要注意是一旦确定一个有效启动模式将会中断后续检测过程。

确定启动模式后,调用 board_boot_mode_regist 获取当前平台各 类 模式 的 引导入口 并保存
在 boot_mode_array 函数指针数组 。 该代码在如下路径:
bsp/bootloader/uboot15/board/spreadtrum/ums512_1h10 ums512_1h10.c

这里我们仅分析正常重启流程,其它模式大家可以根据情况进行分析。

太容易了,在完成震动后调用v lx_nand_boot 接口,并传入 B OOT_PART 开始 boot 镜像的
加载了。
8、如何跳转呢?
在介绍v lx_nand_boot 之前,我们还是要再回顾一下,刚才的跳转是怎么回事run_command_list 函数是什么作用, 这个机制非常重要 。先回顾一下 d o _cboot 函数,我们在其中发现一个非常关键的信息。
U_BOOT_CMD(
cboot, CONFIG_SYS_MAXARGS, 1, do_cboot,
"choose boot
"mode: nrecovery, fastboot, dloader, charge, normal, vlx, caliberation. n"
"cboot could enter a mode specified b y the mode descriptor. n"
"it also could enter a proper mode automatically depending on
"the environment n"
这句代码很像是讲d o_cboot 函数注册到什么地方,然后再调用 run_command_list 进行回调
的感觉。
#define ll_entry_declare(_type, _name, _list)
_type _u_boot_list_2_##_list##_2_##_name __aligned(4)
__attribute__((unused,
section(".u_boot_list_2_"#_list"_2_"#_name)))
通过一系列的调用我们可以 定位到 注册的函数指针均在 u_boot_list段, 所有使用U_BOOT_CMD 命令注册的函数接口,均会将函数入口保存在 u_boot_list段,便于后续查找。这里的逻辑和内核 m odule 以及海思平台小系统的 m odule 设计理念相同。通过函数run_command 和 run_command list 可以一次执行一个或连续执行多个注册函数。这里的知识点还很多,当前不做进一步分析,有兴趣的同事可以参看如下文章:
https://www.cnblogs.com/idyllcheung/p/11344546.html
9、 vlx_nand_boot
代码位置:
bsp/bootloader/u-boot15/common/loader/loader_nvm.c
功能说明:启动boot、recovery或erecovery的主流程。
关键调用关系:
vlx_nand_boot ->drv_lcd_init(LCD初始化 ) ->lcd_enable(LCD显示 ) ->set_backlight(背光设置 ) ->set_vibrator(马达震动设置 ) ->init_fblockflag(FBLOCK安全设置 ) ->tos_status_check ->tos_start_notify teeos模式进入)->secboot_init boot、 recovery、 erecovery镜像 AVB校验, CONFIG_SECURE_BOOT宏关闭,关注流程)->while _boot_load_required_image(主流程确认协议相关镜像,并将镜像数据加载到对应 DDR区域并完成校验,镜像在 s_boot_image_table数组 ) ->_boot_load_kernel_ramdisk_image(load kernel和 ramdisk镜像,重点函数 ) ->load_sp_boot_code(Pmic镜像加载校验 ) ->vboot_secure_process_flow_cm4(Pmic镜像加载校验 )->secboot_terminal(安全校验结束 ) ->pass_chip_uid_to_tos(chip_uid保存 ) ->uboot_set_rpmb_size(rpmb相关设置 ) ->uboot_is_wr_rpmb_key(rpmb相关设置 ) ->uboot_check_rpmb_key(rpmb相关设置 ) ->tos_end_notify teeos模式退出)->vlx_entry
10、 _boot_load_kernel_ramdisk_image
该函数用于加载kernel 、 recovery 及 erecovery 相关镜像。 这里涉及的镜像如下:
正常启动:
boot 、 r amdisk 、 d tbo 镜像
Recovery
启动: r ecovery 、 r ecovery_ramdisk 、 recovery _vendor 镜像;
Erecovery
启动: erecovery 、 erecovery_ ramdisk 、 e recovery_vendor 镜像;
这里注意
recovery 及 erecovery 的 dtbo 镜像已经合并至 r ecovery 及 e recovery 镜像本身。
整个加载过程中涉及kernel 、 boardid 及 ramdisk 三个方面的加载,是关键启动过程。 下面进行代码走读。

确定待加载的镜像。这里以 b oot 镜像为例进行说明。

调用_get_kernel_ramdisk_dt_offset 函数 获取 b oot 或 r ecovery 镜像各类数据的偏移地址 ,调
用 load_kernel_image 函数加载 k ernel 数据。 通过 load_fixup_dt_img 函数加载 d tb 数据, 随
后调用 load_and_merge_dtbo 或者 merge _dtbo 函数加载 d tbo 数据并和 d tb 做 overlay 动
作, load_and_merge_dtbo 用于 r ecovery 镜像 d tbo 加载, m erge_dtbo 用于 b oot 镜像 d tbo
加载。 在此之后处理 ramdisk 数据,对于加载 r ecovery 或 e recovery 镜像时,会加载
r ecovery_ramdisk/ er ecovery_ramdisk 和 r ecovery_vendor/ er ecovery_vendor 镜像并进行合并
对于 加载 b oot 镜像,则直接加载 b oot_ramdisk 分区数据即可 。 整个加载过程结束。
_boot_load_kernel_ramdisk_image
-
->>_get_kernel_ramdisk_dt_offset boot 或 r ecovery 镜像数据解析
-
load_kernel_image 加载 Kernel
-
load_fixup_dt_img 加载 d tb
-
load_and_merge_dtbo 加载 d tbo ,并做 o verylay
-
merge_dtbo 加载 d tbo ,并做 o verylay
-
get_ramdisk_img_hdr 获取 r amdisk 信息
-
fdt_initrd_norsvmem 获取 r amdisk 信息
-
ramdisk 加载 加载 r amdisk 数据上述是一个加载的大致流程,下文将会对一些函数实现原理进行说明。
_get_kernel_ramdisk_dt_offset
该函数解析
boot 或 r ecovery 镜像的数据 并记录 同时 对头信息做了简单校验,为后续数据
加载做好数据准备,介绍这里的代码较为枯燥,我们将其翻译成图便于理解。

首先明确正常加载启动kernel 实际走的是加载 b oot 镜像这条路径,相关的镜像包括b oot.img 、 dtbo.img 及 b oot_ramdisk.img ,原生方案并没有 b oot_ramdisk.img 镜像,而是将r amdisk 直接放到 b oot 镜像。 Recovery 就更为特殊,除了和 b oot_ramdisk.img 匹配的recovery_ramdisk.img 镜像外,还包含了一个 r ecovery_vendor.img 的 r amdisk 镜像, recovery的 r amdisk 实际是这两个 r amdisk 合并而成 ,另一方面 r ecovery 没有单独的 d tbo 镜像, d tbo镜像数据已经打在 recovery 镜像里面,为了支撑这个特性,因此 r ecovery 镜像头部较为复杂,增加了 b oot_im g_hdr_v1 和 b oot_img_hdr_v2 头信息。无论怎么样为了后续能够顺利的加载这些信息,_get_kernel_ramdisk_dt_offset 函数帮助我们完成了镜像的分析,并 记录各种 offset 用于获取获取数据使用 ,在了解了上述内容后再去看这部分代码可能更容易理解 。

load_kernel_image

加载k ernel 的动作非常简单,即根据 k ernel_offset 将对应 k ernel 数据全部加载在
K ERNEL_ADR 内存地址处即完成 k ernel 加载。
load_fixup_dt_img
该函数功能将d tb 数据加载到 DT_ADR 内存地址,完成 d tb 加载。 在 b oot 镜像中的 d tb 是
一个完整镜像,其包含头信息 。

load_fixup_dt_img
函数将会解析头信息,并将有效 f dt 数据载入内存。 这里需要注意load_fixup_dt_img 函数在解析 b oot 镜像 dtb 和 r ecovery/erecovery 镜像 d tb 方式不相同,这主要是由于两个镜像差异导致,但是其本质解析方式相同。
merge_dtbo
dtb 镜像加载后,就需要加载合适的 dtbo 镜像。 这里主要介绍 merge _dtbo 函数实现,
recovery 模式将会调用 load_and_merge_dtbo 接口加载 dtbo 镜像,原理类似。dtbo 作为一个独立镜像,也有自身的头信息 格式,这个格式和 dtb 镜像并不相同。

merge_dtbo 函数的流程是先根据 d t_table_header 找到需要遍历多少 d t_table_entry ,随后
开始遍历 d t_table_entry 并调用 look_for_matching_by_boardid 接口,将 d t_table_entry 中
id 值和 b oardid 进行对比,如果相同则找到对应 b oardid 号的 d tbo ,随后通过d t_table_entry 中数据找到 d tbo 实际数据偏移,即对应 f dt_header 的 起始地址,将f dt_header 数据取出,进行基础校验,随后获取 f dt 数据并调用 fdt_overlay_apply 函数和之前的 d tb 数据进行 o verylay 形成最终的 d t 数据用于后续 k ernel 使用。
ramdisk 加载

ramdisk加载较为简洁, 上述流程是 r ecovery 流程中 ramdisk 加载的方式, ramdisk 分区使
用 m kboot 打包因此头信 息均为 b oot_img_hdr 格式 ,解析 r ecovery_ramdisk 和
r ecovery_vendor 分区,调用 f dt_initrd_norsvmem 向 d tb 传入 r amdisk 加载地址等参数,随
后再将 r ecovery_ramdisk 和 r ecovery_vendor 分区的实际数据读入内存,完成加载

上图为b oot 正常启动流程 r amdisk 的加载,可以看到较为简单,即从 ramdisk 分区读入相
关数据进行处理即可。
11、 vlx_entry
在上述过程中,小系统已经将l inux 启动所需要的相关数据全部加载到内存,并对运行环境
和外设做了初始化, v lx_entry 函数主要功能就是启动 l inux 内核。
vlx_entry
-
write_log_last uboot 阶段最后一次输出日志
-
usb_driver_exit USB 驱动关闭
-
smp_kick_all_cpus 体系架构相关 A RM 核设置
-
start_linux_armv8 启动 l inux 系统
start_linux_armv8函数最后完成 Linux 内核跳转。
12 、 start_linux_armv8
start_linux_armv8
-
-> theKernel = (void (*)(void *, int, int, int))KERNEL_ADR 设置内核跳转起始函数
-
-> flush_dcache_range 清除 D CACHE
-
-> cleanup_before_linux 关闭 m mu 和 d cache
-
-> modem_entry 启动 m odem
-
-> trustzone_entry 启动 t z
-
-> armv8_switch_to_el2 切换为 e l2 工作级别
-
-> theKernel 跳转
起始获取kernel 跳转的代码入口,就是其内核代码段起始函数,通常是 kernel_entry 汇编函
数,其最后会调用到 start _kernel 开始我们熟知的内核启动流程。
本文详细分析了展锐平台上的UBoot启动流程,包括OnChipRom到SPL、SML、TEEOS、UBoot以及Kernel的启动步骤。着重介绍了SPL的职责,UBoot的启动层次调用,以及board_init_f和board_init_r在芯片初始化中的作用。在board_init_r阶段,执行了包括内存、定时器、外设等资源的初始化,最后进入main_loop,根据启动模式选择进入加载或启动流程。



4358

被折叠的 条评论
为什么被折叠?



