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除錯