Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)

上一节讲了FSBL主要功能负责对PS端进行初始化、调用bitstream文件对PL端进行配置,加载应用或二级引导程序置DDR中并启动DDR,本节通过对FSBL代码进行分析深入理解代码。
参考资料
SDK版本:Release Version: 2018.3

一、FSBL代码过程

1.打开FSBL工程 fsbl_bsp->ps7_cortexa9_0->libsrc->standalone_v6_8->asm_vectors.S,即启动文件,这个文件生命了一个代码段,位于地址0处。开机后,PS自动开始执行0地址的指令,其第一句话就是一个跳转:B _boot

.org 0
.text
.globl _vector_table
.section .vectors
_vector_table:
B _boot
B Undefined
B SVCHandler
B PrefetchAbortHandler
B DataAbortHandler
NOP /* Placeholder for address exception vector*/
B IRQHandler
B FIQHandler
于是就跳转到boot.S中执行_boot标号下的代码了,期间对包括DDR是否共享状态进行配置、使能了MMU和各级cache、中断初始化,当它执行完后,PS将具备执行C代码的能力,接着在_boot的代码中,再次执行了一个跳转:b _start
在这里插入图片描述

2.在同目录下找到文件xil-crt0.S中可以看到_start标号下的代码,可以看到_start首先执行跳转:bl __cpu_init去执行CPU初始化(复位寄存器)操作:随后对sbss(小段bss)、bss数据、栈进行了初始化;复位全局计时器和计数器,调用bl __libc_init_array完成对c库的初始化,然后跳转至main函数。

Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)_第1张图片
Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)_第2张图片

3.回到FSBL工程,在目录FSBL/src/main.c中找到main函数,可以看到第一步就是调用了ps7_init()函数。

ps7_init()函数位于ps7_init.c文件中,这个C文件是由SDK根据用户的硬件hdf配置自动生成的。这个接口就是根据查看晶元版本对MIO、PLL、Clock、DDR、和外设进行初始化。

然后解锁SLCR系统级控制寄存器(使能系统软件复位功能),刷新D-cache,关闭D-cache,注册中断处理函数。

接下来就是对DDR区进行读写测试检验DDR状态是否正常;

初始化PACP(Processor Configuration Access Port即处理器配置接口,是PS与PL间通信的桥梁);配置FSBL正在执行状态;读取模式寄存器获取启动模式,如果是QSPI模式,则调用InitQspi()对flash进行初始化(flash可以启用);

接下来就是最关键的地方了,LoadBootImage()这个函数做两件事情①分析烧录到qspi中的数据的头的部分②根据分析结果拷贝数据到DDR中.

GetPartitionHeaderInfo()接口分析镜像文件BOOT.bin头部(又称bootrom头,实际上bootrom过程中,bootrom代码也会解析该头部并提取信息),并根据解析出的partition header数据解析出partition的数量,并把对应的信息头(fsbl、bit、应用程序3个)存放在PartitionHeader[MAX_PARTITION_NUMBER]数组中
注:fsbl、bit文件、应用elf通过sdk生成的BOOT.bin镜像文件头部是固定格式,其结构如下:
Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)_第3张图片Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)_第4张图片其中:

  1. 0x000 中断向量表 ,只在XIP模式下有用;
  2. 0x020 固定值 0xaa995566
  3. 0x024 固定值 0x584c4e58 ASCII: XLNX
  4. 0x028 如果是0xa5c3c5a3或者0x3a5c3c5a为加密的
  5. 0x034 从loadimage拷到OCM的长度 【上电后BootRom会主动把FSBL拷贝到OCM中执行】,就是FSBL的大小
  6. 0x040 非安全模式下,值同5;安全模式下,值应该大于FSBL镜像长度。
  7. 0x098 Boot Header Table Offset:image头的表指针
  8. 0x09C QSPI Config Word:Pointer to the Partition Header table
  9. 0x8A0及以上地址:镜像文件的开始,具体包括表中所列内容。
    根据软件开发指南UG821各个partition的Header信息结构体定义:

Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)_第5张图片
Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)_第6张图片

typedef struct StructPartHeader {
u32 ImageWordLen; /* 0x0 加密下分区长度(如果本身未加密则ImageWordLen和DataWordLen相等)/
u32 DataWordLen; /
0x4 未加密分区长度,也就是要搬到RAM中的数据长度*/
u32 PartitionWordLen; /* 0x8 整个(指的是单个)分区长度,,如果使能了RSA签名或者校验,则包括加密+填充+展开
+验证长度,大于等于ImageWordLen*/
u32 LoadAddr; /* 0xC 该分区要加载到RAM的地址*/
u32 ExecAddr; /* 0x10 加载后该分区的执行地址*/
u32 PartitionStart; /* 0x14 分区相对于起始镜像文件偏移*/
u32 PartitionAttr; /* 0x18 * 文件属性,用来判断文件类型如bitstream还是app,具体见下图/
u32 SectionCount; /* 0x1C /
u32 CheckSumOffset; /
0x20 /
u32 Pads1[1];
u32 ACOffset; /
0x28 /
u32 Pads2[4];
u32 CheckSum; /
0x3C */
}PartHeader;
Xilinx ZYNQ 7000学习笔记二(FSBL代码分析)_第7张图片

拿到partition header后应该分别加载各个partition,但由于第0个partition其实就是FSBL,而我们当前其实已经在FSBL执行中了,所以不用加载直接跳过从partitionNum = 1开始加载。

接下来开始加载对各个partition是类似的,主要完成两部分工作:

①解析并检查各个partition header中内容的正确性

②从flash中加载各个partiton到指定的目标地址中。(这里对FPGA.bit和application.elf有所差别)
(实现的拷贝就是由PartitionStart参数获得拷贝的起始地址,由LoadAddr获得加载地址,)

LoadBootImage()搬移工作结束后将第一个PS段的程序(即一般情况下应用程序)的执行地址作为返回值返回。
注意:对于加密的程序会进行解密,加载到DDR中的程序时不加密的。

4.FsblHandoff(HandoffAddress)将上述返回地址作为参数实现向应用程序的跳转。

该接收调用FsblHandoffExit(FsblStartAddr);这个接口使用汇编代码实现的,代码在fsbl_handoff.S文件下:bx lr指令实现了跳转到application开始执行。(期间失能了I-cache和MMU)

FsblHandoffExit:
		mov	 lr, r0	/* move the destination address into link register */

		mcr	 15,0,r0,cr7,cr5,0		/* Invalidate Instruction cache */
		mcr	 15,0,r0,cr7,cr5,6		/* Invalidate branch predictor array */

		dsb
		isb					/* make sure it completes */

	ldr	r4, =0
		mcr	 15,0,r4,cr1,cr0,0		/* disable the ICache and MMU */

		isb					/* make sure it completes */


		bx		lr	/* force the switch, destination should have been in r0 */

.Ldone: b		.Ldone					/* Paranoia: we should never get here */
.end

你可能感兴趣的:(ZYNQ7000系列学习笔记,arm,嵌入式硬件)