在分析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();
}