MIPS Uboot流程

先熟悉下Mips架構

最近在學習MIPS架構,在系統計算機研究所的 網上讀了不少關於MIPS的好文,下面的筆記就是基於上面的好文的摘抄。

一: MIPS寄存器別名記憶:
這一段在學習MIPSCPU架構,一直對mips的32個寄存器的約定 俗成的別名感到迷惑,今天在系統計算機研究所的網(http://www.xtrj.org/) 上看到一篇文章裏有這方面的介紹,一下子豁然開朗原來這裏的v,a,t前綴就是英文單詞的縮寫呀。

(呵呵,以前害得俺在<<see mips run>>書上都沒有找到有助於理解的介紹)

;REGISTER NAME USAGE
$0 $zero        常量0(constant value 0)
$2-$3 $v0-$v1   函數調用返回值(values for results and expression evaluation)
$4-$7 $a0-$a3   函數調用參數(arguments)

$8-$15 $t0-$t7  暫時的(或隨便用的)
$16-$23 $s0-$s7 保存的(或如果用,需要SAVE/RESTORE的)(saved)
$24-$25 $t8-$t9 暫時的(或隨便用的)
$28 $gp         全局指針(Global Pointer)
$29 $sp         堆棧指針(Stack Pointer)
$30 $fp         幀指針(Frame Pointer)
$31 $ra         返回地址(return address)


二: MIPS 存儲空間分配
MIPS將存儲空間分爲4塊分別是:
kuseg, kseg0,kseg1 and kseg2
1. 0xFFFF FFFF mapped            kseg2
2. 0xC000 0000 unmapped uncached kseg1
3. 0xA000 0000 unmapped cached   kseg0
4. 0x8000 0000 2G                kuseg

   呵呵可以直觀的看到只有kseg1是不需要映射(物理虛擬轉換),沒有被緩存的,也就是說只有kseg1的內存區域可以做引導的存儲區(在這裏放置引導用
flash 存儲器).被cached區域必須等到MMU 的TLB被初始化後纔可以使用的。


三: MIPS的CPU運行有3個態
1. User Mode.
2. Supervisor Mode.
3. and Kernel Mode.
   For simplicity, let's just talk about User Mode and Kernel Mode.
Please always keep this in mind:
CPU can ONLY access kuseg memory area when running in User Mode
CPU MUST be in kernel mode or supervisor mode when visiting kseg0, kseg1
and kseg2 memory area。
   呵呵,可以看出MIPS的CPU運行態和x86尤其是ARM基本都是一樣的。就是用戶層對物理空間地址的訪問是也是受限制的(現代操作系統的先進之處 嗎),必須通過使用驅動方式把操作代碼運行在覈心態。

四: MMU TLB
  MIPS CPU通過TLB來translates all virtual addresses generated by the CPU.下面談談ASID(Address Space Identifier). Basically, ASID, plus the VA(Virtual Address) are composed of the primary key of an TLB entry. 換句話說,虛擬 地址本身是不能唯一確定一個TLB entry的。一般而言,ASID的值就是相應的process ID. Note that ASID can minimized TLB re-loads, since several TLBentries can have the same virtual page number, but different ASID's. 對於一個多任務操 作系統來講,每個任務都有 自己的4G虛擬空間

五: MMU 控制寄存器
對於一個Kernel Engineer來說,對MMU的處理主要是通過MMU的一些控制寄存器來完成的。MIPS體系結構中集成了一個叫做System Control Coprocessor (CP0)的部件。CP0就是我們常說的MMU控制器。在CP0中,除了TLB entry(例如,對RM5200,有48pair,96個TLB entry),一些控制寄存器提供給OS KERNEL來控制MMU的行爲。 每個CP0控制寄存器都對應一個唯一的寄存器號。MIPS提供特殊的指令來對CP0進行操作。
mfc0 reg. CP0_REG
mtc0 reg. CP0_REG
我們通過上述的兩條指令來把一個GPR寄存器的值assign給一個CP0寄存器,從而達到控制MMU的目的。

面簡單介紹幾個與TLB相關的CP0控制寄存器。
Index Register
這個寄存器是用來指定TLB entry的,當你進行TLB讀寫的時候。我們已經知道,例如,MIPS R5提供48個TLB pair,所以index寄存器的值是從0到47。換句話說,每次TLB寫的行爲是對一個pair發生的。這一點是與其他的CPU MMU TLB 讀寫不同的。 EntryLo0, EntryLo1 這兩個寄存器是用來specify 一個TLB pair的偶(even)和奇(odd)物理(Physical)頁面
地址。
一定要注意的是:
EntryLo0 is used for even pages; EntryLo1 is used for odd pages.
Otherwise, the MMU will get exception fault.
Entry Hi
Entry Hi寄存器存放VPN2,或一個TLB的虛擬地址部分。注意的是:ASID value也是在這裏被體現。
Page Mask
MIPS TLB提供可變大小的TLB地址映射。一個PAGE可以是4K,16K,64K,256K,1M,4M或16M。這種可變PAGE SIZE提供了很好的靈活性,特別是對Embedded System Software. 對於Embedded System Softare,一個很大的區別就是:不允許大量的Page Fault. 這一點是傳統OS或General OS在Embedded OS上的致命缺陷。也是爲什麼POSIX 1。B的目的所在。傳統OS存儲管理的一個原則就是:Page On Demand.這對大多Embedded System是不允許的。 For embedded system,往往是需要在系統初始化的時刻就對所有的 存儲進行configuration, 以確保在系統運行時不會有Page Fault.

上述幾個寄存器除了MAP一個虛擬頁面之外,還包括設置一個頁面的屬性。其中包括:
writable or not; invalide or not; cache write back or write through

下面簡單談談MIPS的JTLB。

在MIPS中,如R5000, JTLB is provided. JTLB stands for Joint TLB. 什麼意思呢?
就 是 TLB buffer中包含的mixed Instruction and Data TLB 映射。有的CPU的Instruction
TLB 和Data TLB buffer 是分開的。
當然MIPS(R5000)確實還有兩個小的,分開的Instruction TLB和Data TLB。但其大小很小。
主要是爲了Performance,而且是對系統軟件透明的。
在這裏再談談MMU TLB和CPU Level 1 Cache的關係。
我們知道,MIPS,或大多數CPU,的Level 1 Cache都是採用Virtually Indexed and Physicall tagged. 通過這個機制,OS就不需要在每次進程切換的時候去flush CACHE。
爲什麼呢?
舉一個例子吧:
進程A的一個虛擬地址 Addr1,其對應的物理地址是addre1;
進程B的一個虛擬地址Addr1,其對應的物理地址是addre2;
在某個時刻,進程 A在運行中,並且Addr1在Level 1 CACHE中。
這時候,OS does a context swith and bring process B up, having process A sleep. Now, let's assume that the first instruction/data fetch process B does is to access its own virtual address Addr1.
這時候CPU會錯誤的把進程A在Level 1中的Addr1的addr1返回給CPU嗎?
我們的回答 應該是:不會的。
原因是:
當進程切換時,OS會將進程B的ASID或PID填入ASID寄存器中。請記住:對TLB的訪問,
(ASID + VPN)纔是Primary Key. 由於MIPS的CACHE屬性是Virtually Indexed,
Physically tagged.所以,任何地址的訪問,CPU都會issue the request to MMU for
 TLB translation to get the correct physical address, which then will be used
 for level cache matching.
與此同時,CPU會把虛擬地址信號傳給Level 1 Cache 控制器。然後,我們必須等待MMU的Physical Address數據。只有physical tag也 匹配上了,我們才能說一個:Cache Hit. 所以,我們不需要擔心不同的進程有相同的虛擬地址的事情。 弟兄們可以重溫一下我們講過的Direct Mapped; Full Associative, and Set Associative. 從而理解爲什麼Cache中可以存在多個具有相同虛擬地址的entry. For example,the above Addr1 for proccess A and Addr1 for process B



u-boot的啓動過程比較簡單,大致做下面的工作:
1.  cpu初始化
2.  時鐘,串口,內存(ddr ram)初始化
3.  內存劃分,分配棧,數據,配置參數,以及u-boot代碼在內存中的位置。
4.  對u-boot代碼做relocate
5.  初始化 malloc,flash,pci 以及外設(比如,網口)
6.  進入命令行或者直接啓動Linux kernel

基本上,這就是u-boot的啓動要做的事情,我也曾經大致看過arm的啓動代碼,也是類似。
不過,這裏以mips作爲例子進行介紹:

一、啓動涉及到幾個文件: start.S,cache.S, lowlevel_init.Sboard.c
前三個都是彙編代碼。

二、程序從start.S的_start開始執行。首先,初始化中斷向量寄存器清零,大致包括32個通用寄存器reg0-reg31和協處理器的一些寄存器:CP0_WATCHLO, CP0_WATCHHI, CP0_CAUSE, CP0_COUNT, CP0_COMPARE等等。

之後,配置寄存器CP0_STATUS設置所使用的協處理器中斷以及cpu運行級別(核心級)

配置gp寄存器,把GOT段的地址賦給gp寄存器。(gp寄存器的用處會在後面relocatecode的部分詳細解釋)

三、這時,開始執行lowlevel_init.S的lowlevel_init,主要目的是工作頻率配置,比如cpu的主頻、總線(AHB)、DDR工作頻率等。
然後,調用cache.S的mips_cache_reset對cache進行初始化。接着調用cache.S的mips_cache_lock:這個調用的目的,起初讓我不解,後來才知道,這時ddr ram並沒有配置好,而如果直接調用c語言的函數必須完成棧的設置,而棧必定要在ram中。所以,只有先把一部分cache拿來當ram用。做法就是把一部分cache配置爲棧的地址,鎖定。這樣,當讀寫棧的內存空間時,只會訪問cache,而不會訪問真的ram地址了。

這時,配置棧的地址,進行調用函數board_init_f(board.c)
進入函數board_init_f後,首先做一系列初始化:
timer_init 時鐘初始化
env_init 環境變量初始化(取得環境變量存放的地址)
init_baudrate 串口速率
serial_init 串口初始化
console_init_f 配置控制檯
display_banner 顯示u-boot啓動信息,版本號等
checkboard 執行board相關的操作。
init_func_ram 初始化內存,配置ddr controller
這一系列工作完成後,串口和內存都已經可以用了。然後,就要把內存進行劃分,
在內存的最後一部分,留出u-boot代碼大小的空間,準備把u-boot代碼從flash搬移到這裏然後,是堆的空間,malloc的內存就來自於這裏。緊接着放兩個全局數據結構bd_info、global_data和環境變量boot_params。最後,是棧的空間。

內存劃分好,就準備進行relocate code了。

上次講到,內存劃分好,準備進行relocate code。
relocate code的意思是這樣的。通常u-boot的執行代碼肯定是在flash上(當調試的時候也可以放在ram上)。當啓動起來以後,要把它從flash上搬移到ram裏運行。這個工作就叫做relocate code。

但是,問題在於,flash上的地址和ram上的地址是不同的。當我們把代碼從flash上搬移到ram上以後,當執行函數跳轉時,代碼裏的函數地址還是flash上的地址,所以一跳就跳回去了。
這怎麼辦呢?
在u-boot裏面用的是PIC(position-independent code)的方式解決這個問題。
簡單介紹一下其原理。當你用PIC方式時,在用gcc編譯時需加上 -fpic的選項。編譯器會爲你的可執行代碼建立一個GOT(global offset table)的段。一個地址在GOT表中有一項,裏面存放地址的信息,而在使用這個地址時,只要根據這個地址的編號(也可以叫做偏移量offset)找到表中相應的項目,就可以取得那個地址了。
而如果位置發生變化,只要對GOT表中的地址進行修改就可以了。
我們可以通過反彙編,看一個簡單的函數調用例子:
lw t9,1088(gp)
jalr t9

這裏,gp存放的就是GOT表的起始地址,而1088就是要調用函數的offset,也就是說GOT表的那個位置存放着它的地址。lw t9,1088(gp) 把函數地址放入t9, 然後調用就可以了。

知道了PIC的原理,解釋u-boot relocate code的方法就簡單了。
簡單的說就把u-boot的執行代碼直接從flash裏copy到ram的相應區域。
然後,把GOT表中的地址都加上一個偏移量,這個偏移量就是flash裏的地址與ram裏的地址的差。
還有其他一些工作比如:設置新的棧指針,從flash代碼裏跳轉到ram代碼裏等等。

之後,就進入board.c的board_init_r函數,在這個函數裏初始化 malloc,flash,pci 以及外設(比如,網口),最後進入命令行或者直接啓動Linuxkernel。
這樣,u-boot的啓動工作就完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章