在介绍了 Linux 内核之后,该花点时间来考虑 Linux 启动过程,也就是当一个Linux嵌入式系统上电的时候,在屏幕背后所发生的事情的顺序了。我们会从一台 Linux 台机的启动过程的高层概述开始,进而考虑启动的各个阶段。然后我们要通过用于 Zynqmp 的 Linux 的启动过程来看嵌入式 Linux 的启动过程
一、 引导 Zynq
起点是上电时初始化,根据拨码开关引导ROM,定义FSBL 从哪个接口装载 ——JTAG、NAND Flash、NOR Flash、QSPI Flash 还是 SD 卡 ,读入引导头和给定的配置参数,验证了这个 FSBL 映像之后,把它从指定的接口装载到 OCM 中。一旦映像装入到 OCM 中,CPU的控制就转交给 FSBL了
二、启动流程:
PMU电源管理单元 CSU安全启动管理管理单元(加密安全启动和非加密非安全启动)
上电PMU释放,CSU复位
读取拨码开关,获取启动模式,从相应的内存中获取启动文件
从Boot image里面获取Boot header信息
验证FSBL身份信息及是否加密
将FSBL加载到OCM中
确认相关的配置文件是否PS和PL相匹配
加载RPU和APU
引导过程
①、电源管理单元(PMU)执行PMU ROM固化程序来初始化系统,重置和唤醒相关的进程,
②、根据拨码开关从相应内存位置将引导加载程序(PS的FSBL代码)加载到芯片RAM (OCM)中并执行、
③、运行ATF之后启动uboot第一阶段引导程序初始化内存等基础硬件,将uboot第二阶段和kernel加载到DDR中
④、从内存DDR中加载运行uboot第二阶段初始化相关设备,配置kernel启动的环境并引导kernel从而启动rootfs
FSBL负责安全的加载arm、固件ATF、u-boot和PL端的bistream文件,EL3上执行的FSBL负责的所有安全检查,以及分区的实际身份验证或者解密, PS 用了可编程逻辑中的硬件模块来做 AES-256 和 HMAC(SHA-256)解密和认证,在安全引导过程中 PL 必须被加电。芯片加密认证是用户可选择的,或者是用片内的 eFUSE 单元,或者是用带有备用电池的 RAM (Battery Backup RAM,BBRAM)。
三、启动模式
将FSBL、U-Boot、A53 Image(.elf)、KEY、.bif文件通过Bootgen制作成Boot.bin文件,将它同kernel Image、DeviceTree file文件放入SD卡的Fat32格式的分区中,文件系统放入SD卡另外一个分区EXT4中,片上Rom程序根据拨码开关,选择从SD卡加载启动文件(安全启动模式)
先捋一下流程,FSBL取代传统的uboot第一阶段SPL,当执行完成之后我们需要调用u-boot.lds链接整个uboot文件,uboot文件的入口start.S
四、FSBL启动流程
1、在zynq上运行程序的时候,加载过程中肯定需要用到一个文件,那就是fsbl,fsbl的全称为first stage boot loader,从字面上就能够看出这是zynq启动第一阶段的加载程序,经过了fsbl这一阶段,后面系统才能够运行裸奔程序或者是引导操作系统的u-boot。启动过程如下图:
2、在上图中,Boot Rom是直接固化在zynq硬件中的,开发者无法更改,fsbl.elf可以在Xilinx的SDK中进行修改。打开z_dts/zynqmp_fsbl/zynqmp_fsbl_bsp/
psu_cortexa53_0/libsrc/standalone_v7_1/src/asm_vectors.S文件夹,里面有一个asm_vector.S文件,这个文件声明了一个代码段,位于地址0处。开机之后,PS自动执行地址0处的指令,其第一句话就是一个跳转:B _boot。如下:
┌───────────────────────┐
│ 向量表入口 │
│ (0x00000000开始) │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ 异常类型判断 │
│ (通过向量表偏移跳转) │
└──────────┬────────────┘
│
├────────────────┬─────────────┬──────────────┬────────────────┐
▼ ▼ ▼ ▼ ▼
┌───────────────────┐ ┌───────────┐ ┌───────────┐ ┌─────────────┐ ┌────────────┐
│ Reset │ │ Undefined │ │ SVC调用 │ │PrefetchAbort│ │ DataAbort │
│ (跳转_boot) │ │ 异常处理 │ │ 处理 │ │ 处理 │ │ 处理 │
└───────────────────┘ └─────┬─────┘ └─────┬─────┘ └─────┬────────┘ └─────┬──────┘
│ │ │ │ │
├────────────────┴─────────────┴─────────────┴─────────────────┘
▼
┌───────────────────────┐
│ IRQ/FIQ中断处理 │
└──────────┬────────────┘
│
▼
┌───────────────────────┐
│ 公共处理流程 │
├───────────────────────┤
│ 1. 保存现场 │
│ (r0-r3,r12,lr入栈) │
├───────────────────────┤
│ 2. FPU状态保存 │
│ (如果启用FPU) │
├───────────────────────┤
│ 3. 调用C处理函数 │
│ (如IRQInterrupt) │
├───────────────────────┤
│ 4. FPU状态恢复 │
│ (如果启用FPU) │
├───────────────────────┤
│ 5. 恢复现场 │
│ (寄存器出栈) │
├───────────────────────┤
│ 6. 异常返回地址调整 │
│ (PC ← LR - 偏移量) │
└───────────────────────┘
关键特性说明:
异常向量表结构:
8个32位入口分别对应:
Reset
Undefined Instruction
SVC
Prefetch Abort
Data Abort
Reserved
IRQ
FIQ
3、将vitis.zip解压到当前目录并创建工程ctags –R
vim ./z_dts/zynqmp_fsbl/zynqmp_fsbl_bsp/psu_cortexa53_0/libsrc/standalone_v7_1/src/asm_vectors.S
跳转到boot.S里面,初始化处理器的各种模式,初始化C语言执行的环境
使能I cache、MMU、SP堆栈之后跳入start
清bss,构造全局结构体,重启定时器之后跳转到main函数开始执行
跳入./z_dts/zynqmp_fsbl/xfsbl_main.c主要做下面四个阶段
4.1FsblStatus = XFsbl_Initialize(&FsblInstance);
4.1a、得到复位的原因
FsblInstancePtr - > ResetReason = XFsbl_GetResetReason ();
4.1b、系统初始化
Status = XFsbl_SystemInit(FsblInstancePtr);
——》Status = XFsbl_HookPsuInit();
——》Status = (u32)psu_init();
调用了psu_init()函数,该函数根据PS的类型进行MIO,PLL,CLOCK,DDR一系列参数的设定
4.1c、处理器初始化
Status = XFsbl_ProcessorInit (FsblInstancePtr);
4.1d、DDR初始化
Status = XFsbl_DdrEccInit();
4.1e、板初始化
Status = XFsbl_BoardInit();
4.1f、重置验证
Status = XFsbl_ResetValidation();
4.2、引导设备安装和image验证
u32 XFsbl_BootDeviceInitAndValidate(XFsblPs * FsblInstancePtr)
4.2a 主要引导设备初始化
Status = XFsbl_PrimaryBootDeviceInit(FsblInstancePtr);
./z_dts/zynqp_fsbl/xfsbl_initialization.c中switch描述怎么去不同引导设备获取FSBL
读取引导模式的值,即拨码开关选择的启动模式
根据不同的引导模式选择不同的操作方式,例如SD卡模式启动,通过XFsbl_SdInit初始化SD卡和XFsbl_SdCopy拷贝SD卡中的数据内容
先看XFsbl_SdInit函数
①先通过XFsbl_MakeSdFileName 函数将SD卡中的BOOT.BIN文件名通过字符串指针传入boot_file中
②在通过f_open去打开BOOT.BIN
XFsbl_SdCopy函数
①通过f_read将文件读到DestAddress中
这里根据启动设备读取FSBL中的文件到OCM中,并验证eFUSE RSA
4.2b header验证
Status = XFsbl_ValidateHeader(FsblInstancePtr);
4.2c 二次启动设备初始化
FsblInstancePtr - > SecondaryBootDevice =FsblInstancePtr - >ImageHeader.ImageHeaderTable.PartitionPresentDevice;
4.3、加载并验证分区
FsblStatus = XFsbl_PartitionLoad(&FsblInstance,PartitionNum);
4.3a 分区头验证
Status = XFsbl_PartitionHeaderValidation(FsblInstancePtr, PartitionNum)
4.3b 分区复制
Status = XFsbl_PartitionCopy(FsblInstancePtr, PartitionNum);
4.4、 执行完FSBL,往uboot第二阶段切换
FsblStatus = XFsbl_Handoff(&FsblInstance, PartitionNum, EarlyHandoff);
恢复SD卡检测信号
XFsbl_Out32(IOU_SLCR_SD_CDN_CTRL, 0X0U);
删除PS-PL隔离以允许u-boot和linux访问PL
刷新L1和L2缓存,禁用数据缓存
Xil_DCacheDisable();
4.4a PM 初始化
Status = XFsbl_PmInit();
4.4b 配置
Status = XFsbl_ProtectionConfig();
4.4c 切换到CPU
Status = XFsbl_HookBeforeHandoff(EarlyHandoff);
五、uboot启动流程
上面的FSBL取代传统的SPL,运行了uboot在ocm里面第一阶段所需要做的工作,初始化C运行环境将uboot加载到DDR中并运行,
1、先运行uboot中的链接脚本arch/arm/cpu/armv8/u-boot.lds,链接起始文件start.S的起始代码段
2、进入arch\arm\cpu\armv8\start.S汇编文件中
提到了_start符号是整个程序的入口,链接器在链接时会查找目标文件中的_start符号代表的地址,把它设置为整个程序的入口地址。并且我们也知道start.S的代码段也是位于整个spl-uboot代码段最开始的位置,而_start符号对于Armv8架构来说位于则位于 arch\arm\cpu\armv8\start.S文件内 ,接下来我们将重点分析start.S都做了些什么。
上面这部分内容注释的很清楚,对于某些芯片需要特殊的一些初始配置的话,可以通过配置CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK并执行相应boot0.h中的代码来进行一些boot之前的操作。对于绝大多数CPU来说,这部分都是不需要的。
3、_start 符号后紧跟的第一条指令指令为跳转指令,跳转到reset标号处,暂时接下往下看。
随后进行2^3 也就是8字节对齐。并声明若干个全局符号,这些符号的地址也是位于代码段中,后续反汇编便可以看到。我们先看下
_TEXT_BASE,该符号地址处存放的是一个8字节(.quad定义的是一个8字节的数据)的数 CONFIG_SYS_TEXT_BASE, 而CONFIG_SYS_TEXT_BASE对于zynqmp cpu来说定义如下:
接下来重点分析上面提到的b reset后执行的代码:
跟进save_boot_params里面
CONFIG_POSITION_INDEPENDENT和CONFIG_SYS_RESET_SCTRL都没定义,这里不执行,往下走
继续往下走,最后根进_main中
_main在arch/arm/lib/crto_64.s中,主要设置初始的C运行时环境并调用board_init_f(0)
前面判断都是不执行,直接运行CONFIG_SYS_INIT_SP_ADDR
uboot链接基地址在这里定义为0x8000000
设置一下16字节对齐及gd全局数据结构体之外直接调用board_init_f
board_init_f在common/board_f.c中,主要功能是设置初始环境,这个环境只提供了一个堆栈和一个存储的地方
继续往下走,我们进入init_sequence_f这个函数指针数组中,运行init_sequense_f里得函数指针进行uboot前半段初始化动作
继续往下走,我们可以看到这个函数指针数组里面有个show_board_info
继续往下走,先打印Model在进入checkboard函数中打印Board
继续往下看,init_sequence_f中有show_dram_config中打印DRAM:4 GiB
继续往下看,在board/xilinx/zynqmp/zynqmp.c文件board_early_init_f函数打印PMUFW:
执行完board_init_f后返回执行,b relocate_code代码重定向
进入arch/arm/lib/relocate_64.S中,拷贝代码到ARM的内存中去
设置最终完整的环境,继续往下走,跳进board_init_r中
继续往下走进入common/board_r.c中的board_init_r函数中,CONFIG_NEEDS_MANUAL_RELOC没有定义,运行initcall_run_list函数,调用init_sequense_r里得函数指针进行部分硬件得初始化动作,跟进去往下走
我们在init_sequence_r函数指针数组的每个成员中添加打印logo
保存退出,运行编译脚本,进行重新编译
将BOOT.bin装载到SD卡中,上电查看logo
继续往下走执行在U-Boot初始化并准备好处理命令之后,运行run_main_loop
继续往下走,进入到main_loop函数中
bootstage_mark_name函数调用了show_boot_progress,利用它显示启动进程(progress),此处为空函数
cli_init用来初始化hush shell使用的一些变量
函数从环境变量中获取"preboot"的定义,该变量包含了一些预启动命令,一般环境变量中不包含该项配置。
一般定义在每块板卡自己的头文件中,我的在u-boot-xlnx/include/configs/xilinx_zynqmp.h文件中,如果是sd卡启动preboot就是sdboot
网络相关的设备定义
bootdelay_process从环境变量中取出"bootdelay"和"bootcmd"的配置值
将取出的"bootdelay"配置值转换成整数,赋值给全局变量stored_bootdelay 最后返回"bootcmd"的配置值
bootdelay为u-boot的启动延时计数值,计数期间内如无用户按键输入干预,那么将执行"bootcmd"配置中的命令
autoboot_command,倒数计时实现,计时到-1前,按下了执行menu_main()
进入启动菜单界面,q退出菜单界面,进入uboot操作界面
若没有按下通过run_command_list运行s环境变量
最后在arch/arm/lib/bootm.c中announce_and_cleanup函数是uboot阶段的最后时刻,后面将Image解压到内存里面去