FSBL的數(shù)據(jù)段和代碼段如何鏈接
搞懂數(shù)據(jù)段和代碼段是如何被鏈接成一個二進制文件的,這應該是每一個ARM程序員必須搞清楚的一個事情。它會幫助程序員更加透徹的知道ARM是怎么被安排去工作的,所以數(shù)據(jù)段你和代碼段如何鏈接在一起,是我們搞懂FSBL的第一步。
建個Example工程,不要光顧著看,自己動動手掌握的更快。
要回答這個問題其實必須要建一個工程,相關的軟件操作流程可以參考各種開發(fā)板的實驗手冊,我這里見得描述一下:
現(xiàn)在VIVADO里面新建一個PL工程,可以自己搭,也可以用范例,本小節(jié)所涉及的PL來自范例,如下所示,整個PL實際上由:
1.1 ARM部分(硬核+外設),如圖中所示的processing_system,其中就包含了除APU以外,還有DDR,以及FIXED_IO。DDR好理解,就是連接外部DDR存儲器唄,那這個FIXED_IO是個啥呢?這個實際上就是arm的外設,包含了Q-spi的必要引腳,也包括了Debug Info所需的串口。總而言之都是ARM的外設
1.2 復位部分,看名字就很好理解,該模塊專門用于所以Zynq的PL部分部件的復位
1.3 AXI Interconnect,這個模塊非常重要,簡單地說這就是一個總線解析器,一主(一個master AXI4)多從(兩個slave AXI4)。我們之前提到過,AXI4將會用于連接Zynq的PS(ARM部分)和PL(FPGA部分),這里就是一個例子,后面每一個Slave AXI4都連著一個 Xilinx 的IP,或者是用戶自定義的具備AXI4的IP。這樣就簡單了,只要用戶定義的IP包含AXI4接口,同時將必要的可讀或者可寫數(shù)據(jù)映射到這個AXI4接口上,那么Zynq的ARM就能夠通過總線接觸到這些映射到總線上的數(shù)據(jù),it means the ARM could read/write its content mapped on the Bus of AXI4.
1.4 AXI GPIO & AXI BRAM Controller, 這兩個就是上述的Xilinx的IP,自帶有AXI4總線接口,這樣ARM就能夠通過總線解析器控制他們
1.5 實際的應用,其實也不會比這個在復雜太多,只是再加一些自定義的IP
2 利用這個范例,我們進一步建立BSP,然后基于BSP建立APP(用戶程序),以及FSBL(范例,Zynq的加載程序),如下圖所示,其包含了app, bsp, platform, fsbl。通過任何一個開發(fā)版的用戶手冊都可以獲得完整的工程建立流程,這里不再贅述。
3 其中bsp和fsbl里面,包含加載過程中所用到的所有源碼,下面一一解析。
查看鏈接文件,原來存儲空間是這樣有條不紊的被分配
點擊FSBL->src->lscript.ld,界面上將會呈現(xiàn)(這里的SDK是2017.2版本):
感謝這個SDK的開發(fā)工具,使得用戶能夠以圖表的方式去查看數(shù)據(jù)段和代碼段的具體分布(以前都是通過直接看源碼,畢竟科技進步了~),不過老程序員可能更喜歡看源碼,那我們就結合的看吧
這個圖主要呈現(xiàn)了三部分內容:
定義了兩個存儲空間,包括offset和length,其源碼表達如下
接下來定義了堆棧的大小,忘了啥是堆棧的可以自行百度復習一下
接下來就是將FSBL編譯完成后的所有數(shù)據(jù)和代碼,按照一定的順序鏈接生成二進制文件,舉個例子:
上面的源碼的作用是:
(1)定義FSBL的程序入口在== _vector_table ==
(2)將代碼段(.text*)鏈接到ps7_ram_0_S_AXI_BASEADDR的最前頭,而這里的代碼段實際包含了.vector等等內容,我們查看一下.vectors到底是個啥吧,搜索一下把,結果就在bsp的asm_vectors.S(匯編文件里面)
進到這個匯編程序,如下所示:
這里先關注兩個名字,一個就是==.vectors==,另一個就是==_vector_table==
看下面的源碼可知,.vectors就是一個.section,相當于下面所有的匯編源碼取了一個別名,叫做.vectors,這些源碼最終被放置到了上述位置!
第二個需要關注的是_vector_table,其實際上就是全局變量(看下面的源碼.globl _vector_table ),這個全局變量在這里就是一個指針,指向了B_boot 這個操作。
同時回過頭看上面的源碼ENTRY(_vector_table),這就是定義了FSBL的程序入口,也就是cpu執(zhí)行的第一條指令保存在 _vector_table -----> B_boot
這里可以簡單的小結一下, FSBL執(zhí)行的第一條指令就是B_boot,這是通過查看(編寫)FSBL->src->lscript.ld才獲悉的,可想而知這個鏈接文件有多重要,后期等我們更加熟悉,可以嘗試一下取修改它,這里做個記號,繼續(xù)往下走!
ARM要開始運行FSBL了,然而并不是main()
上面已經提及實際FSBL程序最先被執(zhí)行的語句是B_boot,這是一條匯編指令,意思就是說跳轉到 _boot程序塊,同時轉跳指令B是無需返回的,所以后續(xù)BUndefined啥的實際上并不會被執(zhí)行,看一下**_boot**是什么:
匯編語言不是筆者的強項,因此只能大概說明一下(有興趣的可以自己逐條查看作用,過程會比較痛苦。方式能收獲更多CPU底層的細節(jié),這里不展開):
1 針對CPU0和CPU1有不同的程序,基本就是CPU0干活,CPU1就是WFE
2 CPU干的活就是初始化MMU和TLB等等,其中比較關鍵的就是初始化堆棧(SP寄存器指向棧頂),前面也提及過,在鏈接的時候分配了堆棧空間,而堆棧對C語言函數(shù)是非常重要的。棧的作用:一般來說函數(shù)調用或者中斷,都會涉及到現(xiàn)場保護和現(xiàn)場恢復,被保護的現(xiàn)場實際上就是CPU的幾個專用的reg,以及正在執(zhí)行的函數(shù)的局部變量等數(shù)據(jù),這些數(shù)據(jù)會被推進棧內,其相應的SP寄存器也會加上入棧數(shù)據(jù)的長度,在函數(shù)執(zhí)行返回揮著中斷返回時,棧內的數(shù)據(jù)按順序再次出來,總體來說就是先進后出。而堆的作用一般就是給系統(tǒng)動態(tài)分配存儲空間的,包括用戶經常調用的malloc說分配的空間,就是在堆里。簡而言之,堆棧的完成初始化是為了c語言函數(shù)提供了環(huán)境。否則C語言是無法正確被執(zhí)行的。
3 完成上面一系列的功能后,開始一執(zhí)行去第一條C語言函數(shù)**_start**,見下面的匯編代碼
一樣的,我們不仔細展開這段匯編,其實通過注釋就能夠明白,這里的主要功能就是初始化各種數(shù)據(jù),包括bss等等。最后,匯編來到了main,這個main就是FSBL的主函數(shù),也就是大家比較熟悉的c語言函數(shù)。
小結,實際上BSP在背后干了好多事情(上述所有的匯編都是bsp提供的),這是為了讓用戶能夠忽略一些技術細節(jié),直奔主題main。而這些技術細節(jié)已經有Xilinx官方為我們完整無誤的準備好了,所以FSBL我們其實只用聚焦在main函數(shù)即可,其他的臟活累活BSP已經替我們完成了,我們用不用太操心。不過通過上面的一些展開,大伙兒應該也有了一個模糊的概念,也就是說雖然我們寫的所有的函數(shù)都是從main函數(shù)開始,然后CPU執(zhí)行的第一條指令,絕對不是main,而是最基礎的匯編。這個匯編會替你搞定c語言環(huán)境,讓我們的main能夠很ojbk的運行。下次把目光回到main函數(shù)
終于要開始進入main()了,激動不?
費話不多講,直接懟源碼,如下所示
逐條懟:
一開始就定義了三個變量,這三個變量的作用請看下面的注釋
next, 接下來開始初始化MIO,PLL,CLK和DDR,調用的函數(shù)就是ps7_init()
如果看過我們上一篇blog應該有個印象,MIO不是已經被初始化過一遍嗎,怎么又要?是的,就是這么靈活,也就是說你的FSBL可以在Qspi(這樣BootROM只會初始化Qspi的接口MIO)里,你的BitStream可以保存在eMMC上,那這個多出來的eMMC的MIO也需要在初始化一下了。不多講,直接看ps7_init()
該函數(shù)主要完成:
讀取PS版本,估計一些老料子的方式略有不同吧
根據(jù)PS版本,賦予相對應初始化數(shù)組的指針。這個數(shù)組基本上構成舉例(ps7_mio_init_data_1_0)如下
可以簡單的理解為,這個ps7_mio_init_data_1_0數(shù)組中的而每一個元素都是一種操作,這個操作包含了EMIT_WRITE,EMIT_READ等等。比如說EMIT_WRITE,為了完成這個操作,實際上包含了3個元素,操作指令碼+地址+數(shù)據(jù)(不同的操作包含的數(shù)據(jù)不同,有些操作會有四個元素)。
其想要實現(xiàn)的功能就是往addr write val,比如說EMIT_WRITE(0XF8000008, 0x0000DF0DU),其想要實現(xiàn)的功能就是將地址0XF8000008上的數(shù)據(jù)寫為0x0000DF0DU
而0XF8000008這個地址,通過查看TRM,實際上就是給SCLR_UNLOCK寄存器寫入0xDF0D,目的就是為了解鎖SCLR所有的寄存器,使其可寫,也就是說沒有完成這一步的話,SCLR的其余寄存器使不允許寫操作的!
Xilinx希望通過這種比較奇怪的方式完成了一系列操作(EMIT_WRITE和其他操作)的封裝成一個組合(ps7_mio_init_data_1_0),這一些列的操作共同完成了比如說MIO的初始化,DDR的初始化等等。同時Xilinx提供了一個函數(shù)去解讀這些操作,**ps7_config ()**正是為了實現(xiàn)這個功能,如下所示,利用ps7_config ()和ps7_mio_init_data來完成MIO的初始化
下面來看一下ps7_config()
該函數(shù)很簡單,實際上就是EMIT_WRITE,EMIT_EXIT,EMIT_READ等一系列操作的解包過程,有興趣的可以深入查看一下。需要注意的是,最后一個操作一定是EMIT_EXIT,也就是說不管是ps7_mio_init_data還是ps7_pll_init_data,這些數(shù)組的最后一個元素(操作)一定是EMIT_EXIT,讀者可自行檢查。
小節(jié)
Xilinx利用了一種非常不常見的方式完成了部分(MIO或者DDR)初始化,究其原因可能是這部分初始化工作是固定的,所以什么可讀性啊都不需要了?既然xilinx這么干了,我們看得懂就行了,這種方式極其不推薦。
審核編輯:劉清
-
ARM
+關注
關注
134文章
9046瀏覽量
366816 -
FPGA設計
+關注
關注
9文章
428瀏覽量
26484 -
存儲器
+關注
關注
38文章
7452瀏覽量
163605 -
DDR
+關注
關注
11文章
711瀏覽量
65228
原文標題:FPGA - Zynq - 加載 - FSBL源碼解析1
文章出處:【微信號:zhuyandz,微信公眾號:FPGA之家】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論