前言
在分析ZYNQ7000启动流程时,发现FSBL工程在其中起到了非常重要的作用。参考了许多别人分析的过程,在这里也总结一下自己的代码分析流程。如有问题,欢迎指正。
1. 在FSBL工程中首先找到main函数,第一眼看到的就是ps7_init();从注释可以看到这里是对MIO, PLL, CLK, DDR进行初始化。
-
int main(void)
-
{
-
u32 BootModeRegister = 0;
-
u32 HandoffAddress = 0;
-
u32 Status = XST_SUCCESS;
-
-
/*
-
* PCW initialization for MIO,PLL,CLK and DDR
-
*/
-
Status = ps7_init();
-
if (Status != FSBL_PS7_INIT_SUCCESS) {
-
fsbl_printf(DEBUG_GENERAL,"PS7_INIT_FAIL : %s\r\n",
-
getPS7MessageInfo(Status));
-
OutputStatus(PS7_INIT_FAIL);
-
/*
-
* Calling FsblHookFallback instead of Fallback
-
* since, devcfg driver is not yet initialized
-
*/
-
FsblHookFallback();
-
}
2. 进入到函数ps7_init();中也可以看到这里通过识别PS_VERSION对外设首先进行初始化
-
// MIO init
-
ret = ps7_config (ps7_mio_init_data);
-
if (ret != PS7_INIT_SUCCESS) return ret;
-
-
// PLL init
-
ret = ps7_config (ps7_pll_init_data);
-
if (ret != PS7_INIT_SUCCESS) return ret;
-
-
// Clock init
-
ret = ps7_config (ps7_clock_init_data);
-
if (ret != PS7_INIT_SUCCESS) return ret;
-
-
// DDR init
-
ret = ps7_config (ps7_ddr_init_data);
-
if (ret != PS7_INIT_SUCCESS) return ret;
-
-
// Peripherals init
-
ret = ps7_config (ps7_peripherals_init_data);
-
if (ret != PS7_INIT_SUCCESS) return ret;
-
//xil_printf ("\n PCW Silicon Version : %d.0", pcw_ver);
-
return PS7_INIT_SUCCESS;
3. 接下来在main函数中清cache关cache以及注册异常处理函数
-
/*
-
* Flush the Caches
-
*/
-
Xil_DCacheFlush();
-
-
/*
-
* Disable Data Cache
-
*/
-
Xil_DCacheDisable();
-
-
/*
-
* Register the Exception handlers
-
*/
-
RegisterHandlers();
4. DDR初始化完成后,在该函数中对DDR进行基本的读写测试
-
u32 DDRInitCheck(void)
-
{
-
u32 ReadVal;
-
-
/*
-
* Write and Read from the DDR location for sanity checks
-
*/
-
Xil_Out32(DDR_START_ADDR, DDR_TEST_PATTERN);
-
ReadVal = Xil_In32(DDR_START_ADDR);
-
if (ReadVal != DDR_TEST_PATTERN) {
-
return XST_FAILURE;
-
}
-
-
/*
-
* Write and Read from the DDR location for sanity checks
-
*/
-
Xil_Out32(DDR_START_ADDR + DDR_TEST_OFFSET, DDR_TEST_PATTERN);
-
ReadVal = Xil_In32(DDR_START_ADDR + DDR_TEST_OFFSET);
-
if (ReadVal != DDR_TEST_PATTERN) {
-
return XST_FAILURE;
-
}
-
-
return XST_SUCCESS;
-
}
5. PCAP的初始化,这里不太清楚具体是什么设备。
-
/*
-
* PCAP initialization
-
*/
-
Status = InitPcap();
-
if (Status == XST_FAILURE) {
-
fsbl_printf(DEBUG_GENERAL,"PCAP_INIT_FAIL \n\r");
-
OutputStatus(PCAP_INIT_FAIL);
-
/*
-
* Calling FsblHookFallback instead of Fallback
-
* since, devcfg driver is not yet initialized
-
*/
-
FsblHookFallback();
-
}
-
-
fsbl_printf(DEBUG_INFO,"Devcfg driver initialized \r\n");
6. 从当前抓到的log版本号为:Silicon Version 3.1
-
void GetSiliconVersion(void)
-
{
-
/*
-
* Get the silicon version
-
*/
-
Silicon_Version = XDcfg_GetPsVersion(DcfgInstPtr);
-
if(Silicon_Version == SILICON_VERSION_3_1) {
-
fsbl_printf(DEBUG_GENERAL,"Silicon Version 3.1\r\n");
-
} else {
-
fsbl_printf(DEBUG_GENERAL,"Silicon Version %lu.0\r\n",
-
Silicon_Version + 1);
-
}
-
}
7. 函数MarkFSBLIn()中设置了REBOOT_STATE,从寄存器BOOT_MODE_REG(0xF8000000U + 0x25C)的低3bit获取启动模式。
-
/*
-
* Store FSBL run state in Reboot Status Register
-
*/
-
MarkFSBLIn();
-
-
/*
-
* Read bootmode register
-
*/
-
BootModeRegister = Xil_In32(BOOT_MODE_REG);
-
BootModeRegister &= BOOT_MODES_MASK;
8. 基于目前调试的ZYNQ7000有以下几种启动模式,在这里我们重点关注以QSPI模式启动
-
/*
-
* Boot Modes
-
*/
-
#define JTAG_MODE 0x00000000 /**< JTAG Boot Mode */
-
#define QSPI_MODE 0x00000001 /**< QSPI Boot Mode */
-
#define NOR_FLASH_MODE 0x00000002 /**< NOR Boot Mode */
-
#define NAND_FLASH_MODE 0x00000004 /**< NAND Boot Mode */
-
#define SD_MODE 0x00000005 /**< SD Boot Mode */
-
#define MMC_MODE 0x00000006 /**< MMC Boot Device */
9. 从代码中可以看出在判断启动模式为QSPI模式后,做了两件事
-
if (BootModeRegister == QSPI_MODE) {
-
fsbl_printf(DEBUG_GENERAL,"Boot mode is QSPI\n\r");
-
InitQspi();
-
MoveImage = QspiAccess;
-
fsbl_printf(DEBUG_INFO,"QSPI Init Done \r\n");
-
} else
(1)首先对QSPI进行初始化,这里我们简单的过一下QSPI初始化的代码
-
u32 InitQspi(void)
-
{
-
XQspiPs_Config *QspiConfig;
-
int Status;
-
u32 ConfigCmd;
-
-
QspiInstancePtr = &QspiInstance;
-
-
/*
-
* Set up the base address for access
-
*/
-
FlashReadBaseAddress = XPS_QSPI_LINEAR_BASEADDR; // 表示QSPI Flash的基地址,从SDK中hdf文件中可以得到相应的硬件配置信息
-
-
/*
-
* Initialize the QSPI driver so that it's ready to use
-
*/
-
QspiConfig = XQspiPs_LookupConfig(QSPI_DEVICE_ID);
-
if (NULL == QspiConfig) {
-
return XST_FAILURE;
-
}
-
-
Status = XQspiPs_CfgInitialize(QspiInstancePtr, QspiConfig,
-
QspiConfig->BaseAddress);
-
if (Status != XST_SUCCESS) {
-
return XST_FAILURE;
-
}
-
-
/*
-
* Set Manual Chip select options and drive HOLD_B pin high.
-
*/
-
XQspiPs_SetOptions(QspiInstancePtr, XQSPIPS_FORCE_SSELECT_OPTION |
-
XQSPIPS_HOLD_B_DRIVE_OPTION);
-
-
/*
-
* Set the prescaler for QSPI clock
-
*/
-
XQspiPs_SetClkPrescaler(QspiInstancePtr, XQSPIPS_CLK_PRESCALE_8);
-
-
/*
-
* Assert the FLASH chip select.
-
*/
-
XQspiPs_SetSlaveSelect(QspiInstancePtr);
-
-
/*
-
* Read Flash ID and extract Manufacture and Size information
-
*/
-
Status = FlashReadID(); // 该函数非常重要,用来获取flash的制造厂商及size信息。
-
if (Status != XST_SUCCESS) {
-
return XST_FAILURE;
-
}
从获取的log信息看当前flash信息如下:
Single Flash Information
FlashID=0x20 0xBB 0x19
MICRON 256M Bits
QSPI is in single flash connection
QSPI is in 4-bit mode
QSPI Init Done
符合我们拿到的flash芯片手册:【N25q256.pdf】
做完以上这些以后,根据拿到的器件信息设置了QSPI Flash的IO模式并使能了控制器。以上完成QSPI Flash的初始化操作
(2)函数指针赋值,把QspiAccess赋值给MoveImage
这里我们主要关注MoveImage()函数。结合函数的注释和函数中的主要代码(即memcpy函数)该函数实现了从flash上搬移一定大小的数据到内存固定的地址上去。
-
/******************************************************************************/
-
/**
-
*
-
* This function provides the QSPI FLASH interface for the Simplified header
-
* functionality.
-
*
-
* @param SourceAddress is address in FLASH data space
-
* @param DestinationAddress is address in DDR data space
-
* @param LengthBytes is the length of the data in Bytes
-
*
-
* @return
-
* - XST_SUCCESS if the write completes correctly
-
* - XST_FAILURE if the write fails to completes correctly
-
*
-
* @note none.
-
*
-
****************************************************************************/
-
u32 QspiAccess( u32 SourceAddress, u32 DestinationAddress, u32 LengthBytes)
-
{
-
u8 *BufferPtr;
-
u32 Length = 0;
-
u32 BankSel = 0;
-
u32 LqspiCrReg;
-
u32 Status;
-
u8 BankSwitchFlag = 1;
-
-
/*
-
* Linear access check
-
*/
-
if (LinearBootDeviceFlag == 1) {
-
/*
-
* Check for non-word tail, add bytes to cover the end
-
*/
-
if ((LengthBytes%4) != 0){
-
LengthBytes += (4 - (LengthBytes & 0x00000003));
-
}
-
-
memcpy((void*)DestinationAddress,
-
(const void*)(SourceAddress + FlashReadBaseAddress),
-
(size_t)LengthBytes);
-
} else {
10. QSPI Flash以后分别是
(1)NAND BOOT MODE
(2)NOR BOOT MODE
(3)SD BOOT MODE
// 以上几种模式的处理流程和QSPI Flash的处理流程类似,都是对设备的初始化以及函数指针的赋值
(4)JTAG BOOT MODE
从代码中看到函数FsblHandoffJtagExit最终顺序执行到FsblHandoffExit,其中bx lr指令就是跳转到用户代码执行。
-
FsblHandoffJtagExit
-
mcr p15,0,r0,c7,c5,0 ;/* Invalidate Instruction cache */
-
mcr p15,0,r0,c7,c5,6 ;/* Invalidate branch predictor array */
-
-
dsb
-
isb ;/* make sure it completes */
-
-
ldr r4, =0
-
mcr p15,0,r4,c1,c0,0 ;/* disable the ICache and MMU */
-
-
isb ;/* make sure it completes */
-
Loop
-
wfe
-
b Loop
-
-
FsblHandoffExit
-
mov lr, r0 ;/* move the destination address into link register */
-
-
mcr p15,0,r0,c7,c5,0 ;/* Invalidate Instruction cache */
-
mcr p15,0,r0,c7,c5,6 ;/* Invalidate branch predictor array */
-
-
dsb
-
isb ;/* make sure it completes */
-
-
ldr r4, =0
-
mcr p15,0,r4,c1,c0,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
11. 首先从我们打印log信息得到:Handoff Address: 0x00100000即ddr的起始地址,在hdf文件中可以查到
初步猜测里面应该完成了FPGA bit文件在PL中的加载,以及搬移裸机的软件elf文件到内存中去。
做完这些工作最终调用了FsblHandoff函数,通过该函数跳转到DDR中执行应用程序。
-
/*
-
* Load boot image
-
*/
-
HandoffAddress = LoadBootImage();
-
-
fsbl_printf(DEBUG_INFO,"Handoff Address: 0x%08lx\r\n",HandoffAddress);
-
-
/*
-
* For Performance measurement
-
*/
-
#ifdef FSBL_PERF
-
XTime tEnd = 0;
-
fsbl_printf(DEBUG_GENERAL,"Total Execution time is ");
-
FsblMeasurePerfTime(tCur,tEnd);
-
#endif
-
-
/*
-
* FSBL handoff to valid handoff address or
-
* exit in JTAG
-
*/
-
FsblHandoff(HandoffAddress);
12. 接下来我们详细分析LoadBootImage()这个函数
因为我们的Silicon_Version不是1,所以我们执行以下代码。这段代码的作用是从multiboot寄存器中读取要执行的image的地址,其实如果就一个image的话可以不用管这个,这个算出来的imagestartaddress一定是0。
结合我们拿到的log:
Multiboot Register: 0x0000C000
Image Start Address: 0x00000000
-
else {
-
/*
-
* 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;
-
}
-
fsbl_printf(DEBUG_INFO,"Image Start Address: 0x%08lx\r\n",ImageStartAddress);
13. 这一段是读取在flash中的每个部分的头文件信息,包括大小起始地址等。也就是在生成BOOT的时候加入的几个文件,比如说先加的是FSBL,然后是BIT,最后是APP,那么这里的部分的数量就是3个。
结合log,这里跳过FSBL了,所以从Partition Number:1 开始加载bit;然后在Partition Number:2加载软件elf文件。
-
/*
-
* Get partitions header information
-
*/
-
Status = GetPartitionHeaderInfo(ImageStartAddress);
-
if (Status != XST_SUCCESS) {
-
fsbl_printf(DEBUG_GENERAL, "Partition Header Load Failed\r\n");
-
OutputStatus(GET_HEADER_INFO_FAIL);
-
FsblFallback();
-
}
14. 从函数Status = GetPartitionHeaderInfo(ImageStartAddress);
->Status = GetFsblLength(ImageBaseAddress, &FsblLength);
在该函数中调用了MoveImage函数,实际上是把BOOT.bin image的0x40地址的4字节赋值给FsblLength,从而获取到Fsbl的size大小。这里需要了解一下BOOT.bin的结构。
在boot.bin中从地址0-0x8BF可以分成17个部分,每个部分都有一定的含义
1. 0x000 中断向量表
2. 0x020 固定值 0xaa995566
3. 0x024 固定值 0x584c4e58 ASCII: XLNX
4. 0x028 如果是0xa5c3c5a3或者0x3a5c3c5a为加密的
5. 0x02C bootrom头版本号,不用管
6. 0x030 从bootrom开始到app地址的总数(bytes)
7. 0x034 从loadimage拷到OCM的长度 【上电后BootRom会主动把FSBL拷贝到OCM中执行】
8. 0x038 目的地址到哪儿拷贝FSBL
9. 0x03C 开始执行的地址
10. 0x040 同7 【此处代码逻辑中其实是把该字段的值赋给FSBL的size】
11. 0x044 0x01为固定值
12. 0x048 校验和(从0x020-0x047)按32-bit word 相加取反
13. 0x04C bootgen相关
14. 0x098 image头的表指针
15. 0x09C partition头的表指针
16. 0x0A0 寄存器初始化的参数
17. 0x8A0 fsbl user defined
18. 0x8C0 fsbl开始的地方
如果是从qspi加载的话,bootrom会把数据从qspi拷贝到OCM中,在OCM中运行,也就是0地址运行。
-
u32 GetFsblLength(u32 ImageAddress, u32 *FsblLength)
-
{
-
u32 Status;
-
-
Status = MoveImage(ImageAddress + IMAGE_TOT_BYTE_LEN_OFFSET,
-
(u32)FsblLength, 4);
-
if (Status != XST_SUCCESS) {
-
fsbl_printf(DEBUG_GENERAL,"Move Image failed reading FsblLength\r\n");
-
return XST_FAILURE;
-
}
-
-
return XST_SUCCESS;
-
}
15. 结合14项中BOOT.bin的头信息,下面函数通过image偏移地址0x9C获取partition头的表指针。还记得13项中提供的log打印:Partition Header Offset:0x00000C80
分析代码基于该partition header开始计算partition count,正常情况下最多遍历15个partition,我们这里实际只有3个,即FSBL, BIT, ELF。
分析代码正常的一个partion的表头信息结构如下:
struct HeaderArray {
u32 Fields[16];
};
最后的一个partion的表头的内容应该如下:
* 0x00000000
* 0x00000000
* ....
* 0x00000000
* 0x00000000
* 0xFFFFFFFF
即前15个元素均为0,最后一个元素全为F,这时判断所有partition遍历完毕,得到当前的partition的个数
-
u32 GetPartitionHeaderStartAddr(u32 ImageAddress, u32 *Offset)
-
{
-
u32 Status;
-
-
Status = MoveImage(ImageAddress + IMAGE_PHDR_OFFSET, (u32)Offset, 4);
-
if (Status != XST_SUCCESS) {
-
fsbl_printf(DEBUG_GENERAL,"Move Image failed\r\n");
-
return XST_FAILURE;
-
}
-
-
return XST_SUCCESS;
-
}
16. 首先我们这里不再解析partition 0即FSBL,直接从partition 1开始解析。在函数HeaderDump中把当前所有partition的大小即其实地址都解析出来。结合如下log我们可以看到FPGA bit和软件的elf文件的信息。
Partition Header Offset:0x00000C80
Partition Count: 3
Partition Number: 1
Header Dump
Image Word Len: 0x00427028 // 这里应该是bit文件的大小,单位是word。转换0x00427028 * 4 / 1024 ≈ 17009KB,查看工程符合当前bit文件的大小
Data Word Len: 0x00427028
Partition Word Len:0x00427028
Load Addr: 0x00000000
Exec Addr: 0x00000000
Partition Start: 0x000055D0
Partition Attr: 0x00000020 // Bitstream
Partition Checksum Offset: 0x00000000
Section Count: 0x00000001
Checksum: 0xFF385746
Bitstream
In FsblHookAfterBitstreamDload function
Partition Number: 2
Header Dump
Image Word Len: 0x00004002
Data Word Len: 0x00004002
Partition Word Len:0x00004002
Load Addr: 0x00100000
Exec Addr: 0x00100000
Partition Start: 0x0042C600 // 0x0042C600 - 0x000055D0 = 0x427030,可以看到bit文件和软件elf基本上是连续的
Partition Attr: 0x00000010 // Application
Partition Checksum Offset: 0x00000000
Section Count: 0x00000001
Checksum: 0xFF9C7788
Application
-
PartitionNum = 1;
-
-
while (PartitionNum < PartitionCount) {
-
-
fsbl_printf(DEBUG_INFO, "Partition Number: %lu\r\n", PartitionNum);
-
-
HeaderPtr = &PartitionHeader[PartitionNum];
-
-
/*
-
* Print partition header information
-
*/
-
HeaderDump(HeaderPtr);
17. 当解析当前partition属性为Bitstream时,设置如下代码中的flag
-
if (PartitionAttr & ATTRIBUTE_PL_IMAGE_MASK) {
-
fsbl_printf(DEBUG_INFO, "Bitstream\r\n");
-
PLPartitionFlag = 1;
-
PSPartitionFlag = 0;
-
BitstreamFlag = 1;
-
if (ApplicationFlag == 1) {
-
#ifdef STDOUT_BASEADDRESS
-
xil_printf("\r\nFSBL Warning !!!"
-
"Bitstream not loaded into PL\r\n");
-
xil_printf("Partition order invalid\r\n");
-
#endif
-
break;
-
}
-
}
通过函数PcapLoadPartition完成了PL的加载,这里不再做深入分析。
-
/*
-
* Load Signed PL partition in Fabric
-
*/
-
if (PLPartitionFlag) {
-
Status = PcapLoadPartition((u32*)PartitionStartAddr,
-
(u32*)PartitionLoadAddr,
-
PartitionImageLength,
-
PartitionDataLength,
-
EncryptedPartitionFlag);
-
if (Status != XST_SUCCESS) {
-
fsbl_printf(DEBUG_GENERAL,"BITSTREAM_DOWNLOAD_FAIL\r\n");
-
OutputStatus(BITSTREAM_DOWNLOAD_FAIL);
-
FsblFallback();
-
}
-
}
-
}
-
-
-
/*
-
* FSBL user hook call after bitstream download
-
*/
-
if (PLPartitionFlag) {
-
Status = FsblHookAfterBitstreamDload();
-
if (Status != XST_SUCCESS) {
-
fsbl_printf(DEBUG_GENERAL,"FSBL_AFTER_BSTREAM_HOOK_FAIL\r\n");
-
OutputStatus(FSBL_AFTER_BSTREAM_HOOK_FAIL);
-
FsblFallback();
-
}
18. 根据partition属性为Application时设置如下的flag
-
if (PartitionAttr & ATTRIBUTE_PS_IMAGE_MASK) {
-
fsbl_printf(DEBUG_INFO, "Application\r\n");
-
PSPartitionFlag = 1;
-
PLPartitionFlag = 0;
-
ApplicationFlag = 1;
-
}
在这里会把elf文件搬移到DDR地址0x00100000上,而bit文件会搬移到0x00000000地址上去。
-
/*
-
* 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();
-
}