参考资料:Zynq-7000 SoC Software Developers Guide (UG821)
ps7_init()函数位于ps7_init.c文件中,这个C文件是由SDK根据用户的硬件hdf配置自动生成的。这个接口就是根据查看处理器版本对MIO、PLL、Clock、DDR、和外设进行初始化。
然后解锁SLCR系统级控制寄存器(使能系统软件复位功能),刷新D-cache,关闭D-cache,注册中断处理函数。
关于注册中断函数RegisterHandlers()接口我们仔细看的话起始上一篇汇编代码中的中断向量表对应的中断处理函数在XExc_VectorTable[]有初始定义,
XExc_VectorTableEntry XExc_VectorTable[XIL_EXCEPTION_ID_LAST + 1] =
{
{Xil_ExceptionNullHandler, NULL},
{Xil_UndefinedExceptionHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
{Xil_PrefetchAbortHandler, NULL},
{Xil_DataAbortHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
{Xil_ExceptionNullHandler, NULL},
};
C代码这里重新进行了替换,替换了原来汇编代码时使用的默认中断处理函数。
接下来就是通过对DDR区进行读写测试检验DDR状态是否正常;
初始化PACP(Processor Configuration Access Port即处理器配置接口,是PS与PL间通信的桥梁),为之后的初始化bit文件配置PL做准备。我们理解只需要是PS端AXI总线与PCAP接口转换的一个桥接短路,作用是用来解密或验证bit文件的即可。
数据手册中关于PCAP的结构框图和描述如下:
接下来是通过读取BOOT_MODE_REG确认当前系统的启动模式,关于启动模式第一节博客里面又讲,是根据pin引脚上电采样后存入该寄存器的,忘了的再去看看。启动模式按大类分为由BootRom启动的主动模式和JTAG启动的模式,主动模式我们选用QSPI_MODE模式举例,即使用的串行nor flash作为存储设备为例:
首先,调用InitQspi()对flash进行初始化(flash可以启用);
我们查看InitQspi()代码,实际上也就是如果flash容量小于等于16M,那么flash controller对flash的访问会采用线性模式访问,如果大于16M,那么会采用IO模式访问.(关于flash操作详细内容可参见《qspi flash读写操作》的博客),不过加载只涉及读flash然后写到DDR,所以相对比较简单的,通过函数QspiAccess接口实现读写。
接下来就是加载flash中的镜像文件了,也是最关键的地方了,LoadBootImage()这个函数做两件事情①分析烧录到qspi中的数据的头的部分②根据分析结果拷贝数据到DDR中.
先对以下这段代码进行解释:
/*
* read the multiboot register
*/
MultiBootReg = XDcfg_ReadReg(DcfgInstPtr->Config.BaseAddr,
XDCFG_MULTIBOOT_ADDR_OFFSET);
fsbl_printf(DEBUG_INFO,"Multiboot Register: 0x%08lx\r\n",MultiBootReg);
/*
* Compute the image start address
*/
ImageStartAddress = (MultiBootReg & PCAP_MBOOT_REG_REBOOT_OFFSET_MASK)
* GOLDEN_IMAGE_OFFSET;
*
XDcfg_ReadReg(DcfgInstPtr->Config.BaseAddr,
XDCFG_MULTIBOOT_ADDR_OFFSET);读取的寄存器是XDCFG_MULTIBOOT_ADDR_OFFSET寄存器,这个寄存器是干什么用的呢,是用来实现我们多级启动的,比如我们在flash中烧录了boot0.bin和boot1.bin两个镜像文件,可以通过boot0.bin中的app配置XDCFG_MULTIBOOT_ADDR_OFFSET寄存器,然后执行软件系统复位后再加载fsbl会加载boot1.bin实现启动boot1.bin的作用。有一篇博客写的不错可以参考下ZYNQ多重加载原理。
我们正常状态只有一个Image,XDCFG_MULTIBOOT_ADDR_OFFSET寄存器返回的值是默认值0,也就是说执行完ImageStartAddress值当前为0.
我们先说下整个boot.bin文件的结构类型,首部为一个BootRom头(BootRom Header) 的结构,该BootRom头一片区域存放着若干个分区头(partition header),其中每个分区头包含一个单独如fsbl或bit或者应用程序配置信息。
GetPartitionHeaderInfo()接口就是要获取fsbl、bit、应用程序3个分区头,逻辑为先读取整个文件的BootRom头,在 偏移地址0x09C 获取到存放分区头的表地址,然后将信息头放在PartitionHeader[MAX_PARTITION_NUMBER]数组中(FSBL支持最大14个分区头)。
其中:
`u32 GetPartitionHeaderInfo(u32 ImageBaseAddress)
{
u32 PartitionHeaderOffset;
u32 Status;
/*
* Get the length of the FSBL from BootHeader
* 0x040偏移地址获取FSBL镜像文件的大小
*/
Status = GetFsblLength(ImageBaseAddress, &FsblLength);
if (Status != XST_SUCCESS) {
fsbl_printf(DEBUG_GENERAL, "Get Header Start Address Failed\r\n");
return XST_FAILURE;
}
/*
* Get the start address of the partition header table
* 0x09C 获取分区头所在的位置
*/
Status = GetPartitionHeaderStartAddr(ImageBaseAddress,
&PartitionHeaderOffset);
if (Status != XST_SUCCESS) {
fsbl_printf(DEBUG_GENERAL, "Get Header Start Address Failed\r\n");
return XST_FAILURE;
}
/*
* Header offset on flash
* flash单个镜像文件系下ImageBaseAddress为0
*/
PartitionHeaderOffset += ImageBaseAddress;
fsbl_printf(DEBUG_INFO,"Partition Header Offset:0x%08lx\r\n",
PartitionHeaderOffset);
/*
* Load all partitions header data in to global variable
* 将各个分区头放入PartitionHeader[]数组
*/
Status = LoadPartitionsHeaderInfo(PartitionHeaderOffset,
&PartitionHeader[0]);
if (Status != XST_SUCCESS) {
fsbl_printf(DEBUG_GENERAL, "Header Information Load Failed\r\n");
return XST_FAILURE;
}
/*
* Get partitions count from partitions header information
* 通过分区头的0x3C处校验和信息判定有几个有效信息头
*/
PartitionCount = GetPartitionCount(&PartitionHeader[0]);
fsbl_printf(DEBUG_INFO, "Partition Count: %lu\r\n", PartitionCount);
/*
* Partition Count check
*/
if (PartitionCount >= MAX_PARTITION_NUMBER) {
fsbl_printf(DEBUG_GENERAL, "Invalid Partition Count\r\n");
return XST_FAILURE;
#ifndef MMC_SUPPORT
} else if (PartitionCount <= 1) {
fsbl_printf(DEBUG_GENERAL, "There is no partition to load\r\n");
return XST_FAILURE;
#endif
}
return XST_SUCCESS;
}`
拿到partition header后应该分别加载各个partition,但由于第0个partition其实就是FSBL,而我们当前其实已经在FSBL执行中了,所以不用加载直接跳过从partitionNum = 1开始加载。
/*
* First partition header was ignored by FSBL
* As it contain FSBL partition information
*/
PartitionNum = 1;
接下来开始加载对各个partition是类似的,主要完成两部分工作:
①解析并检查各个partition header中内容的正确性,查看正确性过程中使用分区头的0x18字段的属性位7:4bit区分当前是PS(即应用程序)还是PL(即bit文件)
②从flash中加载各个partiton到指定的目标地址中。(这里对FPGA.bit和application.elf有所差别)
实现的拷贝就是由PartitionStart(分区头0x14偏移)参数获得拷贝的起始地址也就是相对整个boot.bin的偏移,由LoadAddr(分区头0x0C偏移)获得加载地址。
程序通过PartitionMove接口实现将bit或应用elf文件从flash搬移到DDR中,需要注意的是,FSBL的bit文件也是先搬移到了DDR中,不过只是临时存储,FSBL的bit文件会再调用PcapLoadPartition()接口借助PCAP实现数据的传输,具体怎么FPGA启动不太了解
/*
* Move partitions from boot device
*/
Status = PartitionMove(ImageStartAddress, HeaderPtr);
if (Status != XST_SUCCESS) {
fsbl_printf(DEBUG_GENERAL,"PARTITION_MOVE_FAIL\r\n");
OutputStatus(PARTITION_MOVE_FAIL);
FsblFallback();
}
到此,LoadBootImage()搬移工作结束,完成了FPGA.bit文件的加载和应用elf文件写入DDR。LoadBootImage执行完毕后将第一个PS段的程序(即一般情况下应用程序)的执行地址作为返回值返回。
注意:对于加密的程序会进行解密,加载到DDR中的程序时不加密的。
该接收调用FsblHandoffExit(FsblStartAddr);这个接口使用汇编代码实现的,代码在fsbl_handoff.S文件下:bx lr指令实现了跳转到application开始执行。(期间失能了I-cache、D-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