篇首
最近突发奇想,Xilinx 的集成开发环境已经很好了,很多必要的代码都直接生成了,这给开发者带来了巨大便利的同时,也让人错过了很多代码的精彩,可能有很多人用了很多年了,都还无法清楚的理解其中过程。博主准备以FSBL为例,与大家深入探讨一番,从而加深对ZYNQ的加载过程的理解,以便大家作出更精彩的设计!
LoadBootImage() 解读
本文以Zynq7000 FSBL工程代码为基础,分析启动流程核心函数LoadBootImage()LoadBootImage()LoadBootImage()的执行逻辑与关键技术细节。
一、函数调用框架
int LoadBootImage(void) {
FsblHookBeforeBitstreamDdr(); // 钩子函数
Status = XFsbl_LoadPartitions(...); // 核心加载
FsblHookBeforeHandoff(); // 移交前预处理
return Status;
}
二、 函数执行全流程分解
** 函数入口与预处理**
int LoadBootImage(void) {
u32 Status = XFSBL_SUCCESS;
XTime tStart, tEnd; // 64位计时器(若启用性能分析)
- 硬件依赖:
- 依赖
psu_init.c
完成的PS端基础初始化(时钟、MIO、SLCR锁等) - DDR物理层已通过
Xil_DDRInit()
完成训练(psu_ddr_phyinit.c
)
- 依赖
** FsblHookBeforeBitstreamDdr()FsblHookBeforeBitstreamDdr()FsblHookBeforeBitstreamDdr() 钩子函数**
#ifdef FSBL_PERF
XTime_GetTime(&tStart); // 记录TSC起始值(AXI Timer 0)
#endif
/* 用户自定义扩展点:可插入DDR重配置代码 */
- 关键寄存器操作:
- DDRC控制:通过
Xil_Out32(0xFD070000, 0x00040010)
设置DDRC_ADDRMAP0
调整地址映射 - OCM重映射:关闭OCM缓存(
SLCR.OCM_CFG
寄存器位3置1)
- DDRC控制:通过
XFsblLoadPartitions()XFsbl_LoadPartitions()XFsblLoadPartitions() 核心加载
阶段1:Boot Header解析
XFsblPs_BootHdr Header;
XFsbl_CheckBootHeader(ImageAddr, &Header); // 从QSPI/NAND读取头部
- 头部结构体(
xfsbl_ps_boothdr.h
):typedef struct { u32 ImageID; // 魔数0xAA995566 u32 NumPartitions; // 分区总数(含PL比特流+应用) u32 AuthType; // 加密类型:0=None, 1=RSA-2048 u32 Checksum; // 头部的CRC32校验 // ... 其他字段(分区表偏移、证书偏移等) } XFsblPs_BootHdr;
阶段2:安全认证(以RSA-2048为例)
XSecure_Sha3Init(&Sha3Instance); // 初始化SHA-3引擎
XSecure_Sha3Update(&Sha3Instance, (u8*)ImageAddr, Header.HashLength);
XSecure_Sha3Final(&Sha3Instance, CalculatedHash); // 计算哈希
XSecure_VerifySignature(CalculatedHash, StoredSignature); // RSA验签
- 硬件加速:
- 使用PS内置的CSU模块(Crypto Subsystem)
- RSA密钥存储在eFUSE或BBRAM中(通过
XSecure_GetEfuseKek()
读取)
阶段3:分区加载循环
for (u8 i=0; i<Header.NumPartitions; i++) {
XFsblPs_PartitionHdr PartHdr;
XFsbl_ReadPartitionHdr(ImageAddr + Offset, &PartHdr);
if (PartHdr.Attr & PART_ATTR_PL) { // PL比特流分区
XFsbl_LoadPlBitstream(PartHdr.LoadAddr, PartHdr.Size);
} else { // PS应用程序分区
XFsbl_LoadElf(PartHdr.LoadAddr, PartHdr.Size); // ELF解析
}
}
- 关键操作细节:
- PL加载:通过DevCfg接口(
XDcfg_CfgInitialize()
)写入PCAP - ELF加载:解析Program Headers,使用
Xil_Out32()
逐段写入DDR - 地址对齐:通过
XLAT_FSBL_TABLE
处理非32位对齐访问(触发Data Abort时自动转换)
- PL加载:通过DevCfg接口(
FsblHookBeforeHandoff()FsblHookBeforeHandoff()FsblHookBeforeHandoff() 移交前处理
Xil_DCacheFlush(); // 数据缓存刷新(确保DDR数据一致性)
Xil_Out32(CRL_APB_BASE + 0x24, 0x01000F00); // 配置时钟分频
- 寄存器详解:
- CRL_APB (0xFF5E0024): 设置
RPLL_CTRL
分频系数(CPU=1.3GHz, DDR=1066MHz) - SLCR_UNLOCK (0xF8000008): 写入
0xDF0D
解锁保护寄存器
- CRL_APB (0xFF5E0024): 设置
三、关键子函数解析
-
FsblHookBeforeBitstreamDdr()FsblHookBeforeBitstreamDdr()FsblHookBeforeBitstreamDdr()
- 作用:DDR初始化前的预处理钩子
- 执行内容:
#ifdef FSBL_PERF XTime_GetTime(&tStart); // 性能计数器启动 #endif
-
XFsblLoadPartitions()XFsbl_LoadPartitions()XFsblLoadPartitions()
- 流程分解:
- XFsblCheckBootHeader()XFsbl_CheckBootHeader()XFsblCheckBootHeader()
验证BIN文件头结构(含sizeof(XFsblPsBootHdr)sizeof(XFsblPs_BootHdr)sizeof(XFsblPsBootHdr)) - XFsblAuthentication()XFsbl_Authentication()XFsblAuthentication()
执行RSA-2048签名验证(通过XSecureSha3Init()XSecure_Sha3Init()XSecureSha3Init()等加密API) - 分区加载循环
遍历分区表加载:for(u8 PartNum=0; PartNum<Header.NumPartitions; PartNum++){ XFsbl_LoadPartition(...); // 加载单个分区 #ifdef FSBL_DEBUG xil_printf("Partition %d Loaded\r\n", PartNum); #endif }
- XFsblCheckBootHeader()XFsbl_CheckBootHeader()XFsblCheckBootHeader()
- 流程分解:
-
FsblHookBeforeHandoff()FsblHookBeforeHandoff()FsblHookBeforeHandoff()
- 执行DDR刷新操作(XilDCacheFlush()Xil_DCacheFlush()XilDCacheFlush())
- 配置时钟分频器(通过XilOut32()Xil_Out32()XilOut32()写CRL_APB寄存器)
四、核心宏定义
- $FSBL_DEBUG
控制调试输出(默认关闭) - KaTeX parse error: Double subscript at position 15: XPAR_PSU_DDR_0_̲S_AXI_BASEADDR
DDR控制器基地址宏(值0x001000000x001000000x00100000) - XLATFSBLTABLEXLAT_FSBL_TABLEXLATFSBLTABLE
地址转换表(处理非对齐访问)
五、执行流程图
初始化硬件→验证头部↓↓DDR预处理→加载分区↘↓移交控制权 \begin{array}{ccc} \text{初始化硬件} & \rightarrow & \text{验证头部} \\ \downarrow & & \downarrow \\ \text{DDR预处理} & \rightarrow & \text{加载分区} \\ & \searrow & \downarrow \\ & & \text{移交控制权} \end{array} 初始化硬件↓DDR预处理→→↘验证头部↓加载分区↓移交控制权
六、 关键数据流与硬件交互
数据加载路径
QSPI Flash→AXI Quad-SPI控制器OCM缓存→DMADDR3 \text{QSPI Flash} \xrightarrow{\text{AXI Quad-SPI控制器}} \text{OCM缓存} \xrightarrow{\text{DMA}} \text{DDR3} QSPI FlashAXI Quad-SPI控制器OCM缓存DMADDR3
- 性能优化:
- 启用DMA传输(
XQspiPs_DmaTransfer()
) - 使用线性突发模式(QSPI配置为DDR模式,时钟速率83MHz)
- 启用DMA传输(
安全认证流程
原始镜像→SHA-3/384哈希值哈希值→RSA-2048签名验签结果 \begin{aligned} &\text{原始镜像} \xrightarrow{\text{SHA-3/384}} \text{哈希值} \\ &\text{哈希值} \xrightarrow{\text{RSA-2048签名}} \text{验签结果} \end{aligned} 原始镜像SHA-3/384哈希值哈希值RSA-2048签名验签结果
- 抗攻击设计:
- 哈希计算前会清空CSU的密钥缓存(
XSecure_CsuAesKcvClear()
) - 签名失败触发安全锁定(通过
XSecure_SetTamperConfig()
)
- 哈希计算前会清空CSU的密钥缓存(
七、调试与错误处理
调试宏启用
#define FSBL_DEBUG // 启用调试输出
- 典型输出:
XFsbl_Debug: Partition 0 Loaded at 0x00100000 (Size 1MB) XFsbl_Debug: PL Bitstream CRC Check Passed
** 错误码定义**
#define XFSBL_ERROR_BOOTHEADER 0x1000 // 头部校验失败
#define XFSBL_ERROR_AUTHFAIL 0x1001 // RSA验签错误
#define XFSBL_ERROR_PLLLOCK 0x1002 // 时钟锁相环失锁
- 错误处理:
- 记录错误到PMU全局状态寄存器(
XFsbl_WriteReg(PMU_GLOBAL_GLOB_GEN_STORAGE, errCode)
) - 触发系统复位(
XFsbl_FallbackReset()
)
- 记录错误到PMU全局状态寄存器(
**八、 总结 **
LoadBootImage()LoadBootImage()LoadBootImage()作为Zynq7000启动链的核心,其执行涵盖硬件初始化、安全认证、多阶段加载三大模块。函数首先通过FsblHookBeforeBitstreamDdr()FsblHookBeforeBitstreamDdr()FsblHookBeforeBitstreamDdr()完成DDR时序微调与性能监控启动,随后XFsblLoadPartitions()XFsbl_LoadPartitions()XFsblLoadPartitions()深度解析Boot Header结构,利用CSU硬件模块实现RSA-2048/SHA-3安全认证,并依据分区属性(PL比特流或PS应用)选择PCAP配置或ELF加载机制。关键点包括:通过DevCfg接口的PL动态重配置、基于XLAT表的非对齐地址访问补偿、以及DMA加速的QSPI数据传输。移交控制权前,函数会强制刷新数据缓存(确保内存一致性)并通过CRL_APB寄存器组重配时钟域。调试方面,FSBL_DEBUG宏可实时输出分区加载状态,而错误处理机制将异常状态固化至PMU寄存器,为后续故障分析提供关键日志。该函数的设计充分体现了Zynq架构中PS-PL协同、硬件安全加速、以及多级启动链的技术特点。
注:具体实现细节需参考对应版本的fsbl_hooks.cfsbl\_hooks.cfsbl_hooks.c和xfsbl_partitionload.cxfsbl\_partition_load.cxfsbl_partitionload.c源码文件。