1. 程式人生 > >Zynq Linux移植學習筆記之四 fsbl

Zynq Linux移植學習筆記之四 fsbl

               

這一篇講一講FSBL

1、  FSBL簡介

在zynq上執行程式的時候,載入過程中肯定需要用到一個檔案,那就是fsbl,fsbl的全稱為first stage boot loader,從字面上就能夠看出這是zynq啟動第一階段的載入程式,經過了fsbl這一階段,後面系統才能夠執行裸奔程式或者是引導作業系統的u-boot。啟動過程如下圖:

在上圖中,Boot Rom是直接固化在zynq硬體中的,開發者無法更改,fsbl.elf可以在Xilinx的SDK中進行修改。

2、  FSBL程式碼過程(參考FSBL程式碼導讀)

開啟zynq_fsbl_bsp——>ps7_cortexa9_0——>libsrc——>standalone_v3_07_a——>src資料夾,裡面有一個asm_vector.S檔案,這個檔案

聲明瞭一個程式碼段,位於地址0處。開機之後,PS自動執行地址0處的指令,其第一句話就是一個跳轉:B  _boot。如下:

於是就跳轉到boot.S中執行_boot標號下的程式碼了,_boot會對系統做初始化,當它執行完後,PS將具備執行C程式碼的能力,接著在_boot的程式碼中,再次執行了一個跳轉:

_start標號位於xil.crt0.S中,仍然對系統進行設定,我們看到,第一句話就是跳轉到_cpu_init去執行cpu初始化。程式碼部分截圖如下:

  在_start的末尾,BSP終於完成了自己的工作,PS將跳轉到main函式開始執行。如下:

終於系統進入了FSBL階段。我們開啟zynq_fsbl——>src資料夾,然後開啟main函式:

Main函式首先是一些巨集定義,接下來就是執行ps7_int()函式。SDK是一個很智慧的工具,圖中的灰色陰影部分是SDK判斷出了PEEP_CODE這個巨集沒有定義,所以用灰顏色提示讀者這段程式碼不用執行。

ps7_init函式位於ps7_init.c檔案中。這個C檔案是由XPS根據使用者的配置自動生成的。我們進入ps7_init函式看一下,這個函式很短:

根據程式碼,很明顯可以猜到,ps7_init函式其實執行了mio,pll,clock,ddr和某些外設的初始化。

我們接著看FSBL的main函式,根據XPS自動生成的ps7_init.c執行完初始化之後,FSBL將根據啟動狀態暫存器判斷是採用的哪種啟動模式。有四種啟動模式,分別是QSPI,NOR,JTAG,SD卡等模式。每種模式都有一段獨立的程式碼,舉個例子,我們看SD卡模式的執行程式碼:

可以看到,系統先對SD卡初始化,並且要求SD卡中必須要有BOOT.BIN檔案。如果沒有,那麼從SD卡啟動板子就會失敗。這也是為什麼我們生成啟動映象必須命名為BOOT.BIN的原因。需要注意的是NAND啟動模式被禁用了,因為FSBL中有一個巨集沒有定義,所以處於灰色狀態。接下來的程式碼我們只看一下注釋就大概知道幹什麼了:

 在明確了啟動模式之後,PS將在相應的flash中去尋找.bit檔案和使用者程式,通過遍歷一些partition(最多15個),如果找到了.bit檔案,那麼就不重啟,直接配置PL,然後再找使用者程式。如果沒找到.bit就軟體復位一下,然後調整地址,接著驗證下一個partition。充分體現這個過程的,是這個函式:

PartitionMove函式很複雜,我們只看註釋:

這個註釋說的就是找到了.bit檔案就配置PL,找到了使用者程式就載入到記憶體。這個函式執行完之後,返回值就是使用者程式的執行地址。這個執行地址位於使用者檔案的檔案頭中,是由編譯器或者ISE自己生成的。

找到了使用者程式的執行地址,那麼FSBL函式就該交接了,完成這個過程的是FsblHandoff函式:

這個函式完成交接,並且一去不復返,再也不會返回,從此PS就開始執行了使用者程式碼。那麼交接究竟是怎麼完成的?其實我們直觀上很容易猜到肯定是一個跳轉指令。帶著猜測,我們深入FsblHandoff函式,最後果然找到了:

其中bx    lr指令就是跳轉到使用者程式碼執行。

3、  FSBL中對DDR的初始化

在fsbl main函式執行過程中很重要的一步是對DDR進行初始化,這裡呼叫了ps7_init這個函式,該函式根據PS的型別進行MIO,PLL,CLOCK,DDR一系列引數的設定,程式碼如下:
Int ps7_init() // Get the PS_VERSION on run time  unsigned long si_ver = ps7GetSiliconVersion ();  int ret;  if (si_ver == PCW_SILICON_VERSION_1){    ps7_mio_init_data = ps7_mio_init_data_1_0;    ps7_pll_init_data = ps7_pll_init_data_1_0;    ps7_clock_init_data = ps7_clock_init_data_1_0;    ps7_ddr_init_data = ps7_ddr_init_data_1_0;    ps7_peripherals_init_data = ps7_peripherals_init_data_1_0;  } else if (si_ver == PCW_SILICON_VERSION_2) {    ps7_mio_init_data = ps7_mio_init_data_2_0;    ps7_pll_init_data = ps7_pll_init_data_2_0;    ps7_clock_init_data = ps7_clock_init_data_2_0;    ps7_ddr_init_data = ps7_ddr_init_data_2_0;    ps7_peripherals_init_data = ps7_peripherals_init_data_2_0;  }else {    ps7_mio_init_data = ps7_mio_init_data_3_0;    ps7_pll_init_data = ps7_pll_init_data_3_0;    ps7_clock_init_data = ps7_clock_init_data_3_0;    ps7_ddr_init_data = ps7_ddr_init_data_3_0;    ps7_peripherals_init_data = ps7_peripherals_init_data_3_0;  }  // 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;  return PS7_INIT_SUCCESS;}

以DDR為例,這裡定義了一個表(ps7_ddr_init_data_1_0)用於根據PS的型別進行不同初始化引數配置,在檔案中能夠找到該表具體的內容:

unsigned long ps7_ddr_init_data_1_0[] = {    // START: top    // .. START: DDR INITIALIZATION    // .. .. START: LOCK DDR    // .. .. reg_ddrc_soft_rstb = 0    // .. .. ==> 0XF8006000[0:0] = 0x00000000U    // .. ..     ==> MASK : 0x00000001U    VAL : 0x00000000U    // .. .. reg_ddrc_powerdown_en = 0x0    // .. .. ==> 0XF8006000[1:1] = 0x00000000U    // .. ..     ==> MASK : 0x00000002U    VAL : 0x00000000U    // .. .. reg_ddrc_data_bus_width = 0x0    // .. .. ==> 0XF8006000[3:2] = 0x00000000U    // .. ..     ==> MASK : 0x0000000CU    VAL : 0x00000000U    // .. .. reg_ddrc_burst8_refresh = 0x0    // .. .. ==> 0XF8006000[6:4] = 0x00000000U    // .. ..     ==> MASK : 0x00000070U    VAL : 0x00000000U    // .. .. reg_ddrc_rdwr_idle_gap = 0x1    // .. .. ==> 0XF8006000[13:7] = 0x00000001U    // .. ..     ==> MASK : 0x00003F80U    VAL : 0x00000080U    // .. .. reg_ddrc_dis_rd_bypass = 0x0    // .. .. ==> 0XF8006000[14:14] = 0x00000000U    // .. ..     ==> MASK : 0x00004000U    VAL : 0x00000000U    // .. .. reg_ddrc_dis_act_bypass = 0x0    // .. .. ==> 0XF8006000[15:15] = 0x00000000U    // .. ..     ==> MASK : 0x00008000U    VAL : 0x00000000U    // .. .. reg_ddrc_dis_auto_refresh = 0x0    // .. .. ==> 0XF8006000[16:16] = 0x00000000U    // .. ..     ==> MASK : 0x00010000U    VAL : 0x00000000U    // .. ..     EMIT_MASKWRITE(0XF8006000, 0x0001FFFFU ,0x00000080U),     EMIT_MASKWRITE(0XF8006004, 0x1FFFFFFFU ,0x00081081U),       EMIT_MASKWRITE(0XF8006008, 0x03FFFFFFU ,0x03C0780FU),   EMIT_MASKWRITE(0XF800600C, 0x03FFFFFFU ,0x02001001U),//以下省略…

這裡對DDR進行初始化其實就是配置ARM內DDR控制器的對應暫存器,從註釋可以看到對暫存器每一位都進行了配置。

在fsbl.h中能夠找到DDR暫存器的起始地址和結束地址:

#define DDR_START_ADDR   XPAR_PS7_DDR_0_S_AXI_BASEADDR

#define DDR_END_ADDR        XPAR_PS7_DDR_0_S_AXI_HIGHADDR

xparameters.h中能找到上面列出了的0XF8006000

/* Definitions for peripheral PS7_DDRC_0 */

#define XPAR_PS7_DDRC_0_S_AXI_BASEADDR0xF8006000

#define XPAR_PS7_DDRC_0_S_AXI_HIGHADDR0xF8006FFF

經過配置後DDR才能夠使用,接下來fsbl將後續要執行的程式放入記憶體中。

4、  對FSBL的一點疑惑

按照流程圖來看,要讓zynq跑起來肯定需要fsbl,但是在通過jtag模式載入linux的過程中並沒有fsbl.elf這個檔案,而是直接dow u-boot.eld,uimage,devicetree.dtb檔案。同時執行簡單的裸奔程式helloworld時也是直接用SDK通過jtag讓zynq跑起來。對於這種現象,我的猜想是fsbl.elf這個檔案可能已經存放在arm內部的on-chip memory中了,硬體的boot rom啟動後立刻執行的是這個內部的fsbl,然後再載入後續程式。當然,我們也可以建立自己的fsbl.elf,但是也是執行完內部的fsbl.elf後再執行使用者自定義的fsbl.elf。

使用JTAG方式確實不需要FSBL

附:FSBL除錯