原文: https://blog.csdn.net/kernel_yx/article/details/53045424?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-1.control
最近一段時間一直在做uboot移植相關的工作,需要將uboot-2016-7移植到單位設計的ARMv7的處理器上。正好元旦放假三天閑來無事,有段完整的時間來整理下最近的工作成果。之前在學習uboot時,在網上看了很多文章,很多都是基於老版本的的uboot,並且很多都是直接從代碼開始分析,並沒有將uboot與ARM處理器體系結構結合起來。畢竟很多時候做一件事情,你知道怎么去做這件事和你知道這件事為什么要這么去做兩個不同的概念。即英文中常說的:how do?和 why do?
將2016年7月發布的新版本uboot移植到單位的板子上並進行了調試,在此也將我的學習成果分享給大家。文中有很多地方加入了我自己的理解,可能不太准確,希望大家提出修改意見。
開始深入了解處理器啟動流程前,請准備好兩個工具:
1.uboot-2016-7源碼包
(開源的隨便下載 官方下載地址:ftp://ftp.denx.de/pub/u-boot/ 下載u-boot-2016.07.tar.bz2)
2.代碼閱讀軟件source insight
(推薦! 可以想象uboot源碼包有10000多個文件,每個文件都有幾百行甚至上千行代碼。沒有專業的代碼閱讀器如何查找函數原型。 當然不是全都需要看的! 根據自己的需要去查看)
同時下載好這幾個技術文檔
(筆者在學習uboot時閱讀的幾個文檔 可以根據需求去查找閱讀 完全沒必要重頭讀到尾)
1.arm公司官方提供的ARMv7-A體系結構文檔
《ARM® Architecture Reference Manual ARM®v7-A and ARM®v7-R 》
( 官方 品質保證! 如果覺得英文看着費勁 可以去看 杜春雷 《ARM體系結構與編程》 )
2.《Uboot中start.S源碼的指令級的詳盡解析_v1.6.pdf》(好吧 中文滴)
(對理解gnu arm匯編極其有幫助)
3.《ARM指令集快速查詢手冊.pdf》(中文滴)
(工具手冊,快速查找各種arm指令用法)
4.《ARM指令詳解[ARM標准].pdf》(還是中文滴)
(arm匯編的一些規范和常用形式,很多例子非常有用:子程序調用、散轉、數據塊復制等。對寫裸機程序很有幫助,同樣對理解uboot和內核啟動代碼有很大幫助)
5.《程序員的自我修養—鏈接、裝載與庫.pdf》(全是中文滴)
(里面會介紹ELF文件 程序鏈接的過程 最重要的部分就是幫助理解uboot的核心重定位!這個真的很重要!!)
好了,現在進入正題。
什么是uboot?
在嵌入式操作系統中,Boot Loader是在操作系統內核運行之前運行。可以初始化硬件設備、建立內存空間映射圖,從而將系統的軟硬件環境帶到一個合適狀態,以便為最終調用操作系統內核准備好正確的環境。在嵌入式系統中,通常並沒有像BIOS那樣的固件程序(注,有的嵌入式CPU也會內嵌一段短小的啟動程序),因此整個系統的加載啟動任務就完全由Boot Loader來完成。 uboot就是bootloader的一種,全稱Universal Boot Loader。
目標:用uboot來啟動單位設計的ARMv7的處理器。
問題:如何啟動? 啟動流程是什么?
首先,我先來描述一下需要移植uboot的這塊板子上使用到的存儲器(處理器架構可以暫時先不考慮,后面我會根據arm公司官方提供的ARMv7-A體系結構文檔去分析uboot初始化處理器的匯編代碼。無論是arm9 arm11 armv7 armv8都是相同的套路,首先需要根據存儲器資源,去定制自己的啟動流程)。在我手上的這塊板子上使用存儲器資源:Norflash(16MB) SRAM(512KB) SDRAM(512MB)。
看到這里應該想到兩個問題:
1. Norflash SRAM SDRAM 作為存儲器它們之間有何異同?
2. 如何通過它們去啟動arm處理器?(重要的事情說三遍:先一定要理解啟動流程 然后再去看uboot源碼 要不然看了也無法理解 嵌入式的代碼都是與硬件息息相關 了解硬件結構再去讀代碼 事半功倍)
下面我將對以上問題依次做出解析:
一. Norflash SRAM SDRAM 異同
norflash:norfalsh是非易失存儲器(失去電源供電后norflash里存儲的數據依然存在),NOR flash帶有SRAM接口,有足夠的地址引腳來尋址,可以很容易地讀取其內部的每一個字節(注意是讀取!對於flash不是隨意可以寫入,一般寫入NOR flash流程是:解保護->擦除->寫入數據。由於flash特性只能從1翻轉到0,無法從0翻轉到1。擦除過程就是將flash中的某一個扇區全寫0xFFFFFFF,再寫入數據),代碼指令可以直接在norflash上運行。(重要! 上電后可以讀取norfalsh中的數據 寫操作無法直接進行 前面我已經說得很清楚了)
SRAM:靜態隨機訪問存儲器(Static Random Access Memory)它是一種類型的半導體存儲器。“靜態”是指只要不掉電,存儲在SRAM中的數據就不會丟失。這一點與動態RAM(DRAM)不同,DRAM需要進行周期性的刷新操作。然后,我們不應將SRAM與只讀存儲器(ROM)和Flash Memory相混淆,因為SRAM是一種易失性存儲器,它只有在電源保持連續供應的情況下才能夠保持數據。“隨機訪問”是指存儲器的內容可以以任何順序訪問,而不管前一次訪問的是哪一個位置。 (重要!上電后就可以讀寫SRAM中的數據,無需初始化操作)
SDRAM:同步動態隨機存取存儲器(Synchronous Dynamic Random Access Memory)需要不斷的刷新,才能保存數據。而且是行列地址復用的,許多都有頁模式。(重要! 需要對ddr控制器進行初始化<配置寄存器>,才能去讀寫SDRAM中的數據)
最后用表格來歸納總結下:
存儲器 | 上電后存儲器訪問狀態 | 掉電后存儲器中數據狀態 |
NORFLASH | 可以讀取數據,無法直接寫人 | 數據存在 |
SRAM | 可以直接讀取寫入數據 | 數據不存在 |
SDRAM | 上電后沒有初始化DDR控制器 無法進行數據讀寫 |
數據不存在 |
前面介紹了Norflash SRAM SDRAM存儲特性,下面就來聊聊他們各自的功能和完成的任務(重要!!Norflash SRAM SDRAM在arm處理器啟動階段完成的任務,都是根據他們各自的存儲特性來的。如果對於我前面介紹的Norflash SRAM SDRAM存儲特性還不太理解的同學可以自行百度再深入理解。)
Norflash特性:掉電非易失(失去電源供電后norflash里存儲的數據依然存在),可以直接<讀取>數據,無法直接<寫入>數據。
Norflash作為arm處理器程序存儲器。可以試想一下,如果程序存儲器掉電以后里面的數據沒有了。那么你的電腦如何自啟動,難道每次開機前都要重新燒寫一次代碼。在此處可以思考一個問題,在上電后norflash可以看作一個可以隨機讀取的只讀存儲器。但是我們運行的程序,一般情況下.text段(代碼段)是只讀(ok),.rodata(只讀數據段)是只讀(也ok)。那么問題來了,對於.data段(數據段)和.bss段(未初始化的全局變量和靜態變量)在程序運行的過程中變量的值是需要改變的(改變一個變量的值在底層硬件完成操作<在相應的地址(變量在物理地址上存儲地址)上寫入數據>),很可惜Norflash只能直接讀取無法直接進行寫操作。(重要! 怎么解決這個問題? 這時就需要SRAM 因為SRAM上電后就可以直接去讀寫,下面我就解釋下SRAM的功能和作用)
SRAM特性:掉電易失(失去電源供電后SRAM里存儲的數據不存在了),可以隨意<讀寫>數據。(重要!容量小:512KB 程序運行速度快 價格貴!)。
在實際運行時,SRAM可以作為c語言運行時的堆棧空間。把arm處理器的sp(堆棧指針寄存器)設置在sram中,為c語言運行提供環境。關於全局變量的問題,我單獨提一下,uboot在重定位前(將uboot鏡像從flash搬運到ddr中繼續運行前),無論是匯編還是c程序中沒有定義全局變量。只是定義了一個結構體指針gd_t *gd_ptr用於存儲uboot鏡像的長度,重定位地址等信息,將gd_ptr的地址存儲在r9中,r9中存儲的地址值為sram頂端減去一個sizeof(gd_t )。(存儲在sram里就可以隨意讀寫了嘛 后面分析uboot代碼時我會詳細講解)
SDRAM特性:掉電易失(失去電源供電后SDRAM里存儲的數據不存在了),上電后沒有初始化DDR控制器,無法進行數據讀寫。(重要!容量大:512MB 程序運行速度快 價格自己猜一下<必須價格便宜>)
既然需要使用大容量的SDRAM,必須配置ddr時鍾和ddr控制器的寄存器。這一步在哪完成呢?(思考一下)
沒錯就是在norflash和SRAM搭建的程序運行環境中完成。完成什么呢?
1.完成對處理器時鍾的初始化
2. DDR的初始化
3.給gd_t *gd_ptr賦值 (用於存儲uboot鏡像的長度,重定位地址,重定位偏移量等信息)
在uboot搬運到DDR中運行前進行最小系統的初始化,之后就將uboot搬運到ddr中進行運行。
(重要!此時Norfalsh和SRAM的任務就完成了(這倆就沒用了),現在uboot就在ddr中運行了)
二. arm處理器啟動流程分析
前面講解了各個存儲器在處理器啟動階段的作用,下面就歸納總結一下啟動過程。
有時候描述說不清楚,直接上圖。下圖為手上這塊ARM處理器的存儲器地址空間(包括各存儲器在arm處理器上的地址空間 以及容量大小)
那么啟動流程可以總結為:
step1.uboot鏡像(也就是bin文件 重要!不是elf文件 bin文件 和 elf文件異同 自行百度)存儲在norflash中,上電后ARM默認從地址0x00000000處取出第一條指令。當運行C程時,形參和局部變量需要入棧,棧頂設置SRAM的頂端,為C程序提供運行環境。完成對gd_t *gd_ptr賦值(存儲uboot鏡像的長度,重定位地址等信息)。
step2.在第一階段會完成arm處理器的時鍾和DDR的配置以及對gd_t *gd_ptr賦值(用於存儲uboot鏡像的長度,重定位地址等信息)。此時SDRAM就可以正常進行數據讀寫。將uboot鏡像復制到SDRAM中(重定位地址由gd_t *gd_ptr提供),根據重定位偏移量得到新的PC指針值,在DDR中繼續運行uboot。
這里我想多提一下,uboot在ddr中運行后的地址跟其鏈接地址不一致(原來norflash中text_base = 0x0 在ddr中運行后text_base = 0x70453670 ),編譯器會在鏈接時確定了其中變量以及函數的絕對地址,鏈接地址 加載地址 運行地址應該一致的(只是寫裸機程序時大多數情況下)。
(1)如何對函數進行尋址調用(執行函數的過程就是跳轉到某個地址上去執行一段代碼)
(2)如何對全局變量進行尋址操作(全局變量的地址變了如何找到進行讀寫)
(3)對於全局指針變量中存儲的其他變量或函數地址在重定位到DDR中之后如何操作?
問題先放在這 后面我會專門寫一篇博客 將一個可執行程序靜態鏈接過程 動態鏈接過程 與uboot重定位過程放在一起比較 感興趣的可以直接去閱讀這篇博客。
step3.在DDR中繼續運行的uboot完成對ARM處理器各外圍硬件的初始化,如SPI、I2C、網卡等。最終uboot進入main_loop,阻塞等待串口終端輸入命令或者去啟動內核。始終要清楚一點uboot的主要作用是通過 Flash和Sram初始化時鍾和DDR(arm最小系統),最終uboot是需要在ddr中運行進行裸機調試或者去引導操作系統內核啟動。
(如何編寫uboot命令進行裸機調試 以及uboot如何引導操作系統 后面我會依次介紹)
最后上還是上圖<啟動流程圖>:
上電后arm默認從地址0x00000000處取出第一條指令。SP指向SRAM的頂端,當運行C程序時,形參和局部變量需要入棧,SP依次減4入棧。在這部分運行的程序完成對時鍾和ddr的配置(arm最小系統)以及對gd_t *gd_ptr賦值(用於存儲uboot鏡像的長度,重定位地址,重定位偏移量等信息)。此時ddr已經可以成功進行數據讀寫,將uboot鏡像從flash復制到DDR,根據重定位偏移量獲得新的PC位置 繼續運行uboot。
step3
uboot已經在ddr中運行了,也就不存在任何硬件上限制了。step3完成對ARM處理器各外圍硬件的初始化,如SPI、I2C、網卡等。最終uboot進入main_loop,阻塞等待串口終端輸入命令或者去啟動內核。