Xilinx ZYNQ 7000学习笔记三(FSBL代码分析-C代码)

参考资料:Zynq-7000 SoC Software Developers Guide (UG821)

1.承接上一篇,回到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,注册中断处理函数。
关于注册中断函数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的结构框图和描述如下:
Xilinx ZYNQ 7000学习笔记三(FSBL代码分析-C代码)_第1张图片接下来是通过读取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个分区头)。

关于BootRoM头和partition 头的格式说明:
Xilinx ZYNQ 7000学习笔记三(FSBL代码分析-C代码)_第2张图片

其中:

  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 存放分区头在BootRom头中的地址
  9. 0x8A0及以上地址:镜像文件的开始,具体包括表中所列内容。
    根据软件开发指南UG821各个partition的Header信息结构体定义: 其中重要的是0x0,0x4表示分区的大小(软件通过判断这两个值是否相等确认是否是加密的),0x0c为分区要加载到的地址,如PS加载到DDR的地址,0x10为执行地址,也就是我们的应用程序在加载到DDR后执行跳转指令启动应用程序的地址。
    在这里插入图片描述
    在这里插入图片描述
    GetPartitionHeaderInfo()接口代码如下:
`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中的程序时不加密的。

2.FsblHandoff(HandoffAddress)将上述返回地址作为参数实现通过调用汇编FsblHandoffExit实现向应用程序的跳转。

该接收调用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

你可能感兴趣的:(ZYNQ7000系列学习笔记,学习,笔记,c语言)