uboot源碼分析1-啟動第一階段
1、starts.S是我們uboot源碼的第一階段:
從u-boot.lds鏈接腳本中也可以看出start.S是我們整個程序的入口處,怎么看出的呢,因為在鏈接腳本中有個ENTRY(_start)聲明了_start是程序的入口。所以_start符號所在的文件,就是我們整個程序的起始文件,_start所在處的代碼就是我們整個程序的起始代碼。
2、我們知道了程序的入口是_start這個符號,但是卻不知道是在哪一個文件中,所以要SI進行查找搜索,點擊SI的大R進行搜索,lookup reference ,快捷鍵是CTRL + / 由鏈接腳本也可知道是在uboot/cpu/s5pc11x/
3、start.S解析1
(1)開始包含了#include<config.h>頭文件,我們知道,這個config.h的頭文件是在mkconfig腳本中配置在配置時生成的,echo "#include <configs/$1.h>" >>config.h,并且里面的內容是包含了一個configs下的x210_sd.h這個頭文件,所以意思就是包含了這個configs下的x210_sd.h頭文件。
我們在住makefile中的x210_nand_config類似的目標,_config除去后就成了我們mkconfig腳本中的$1,所以在mkconfig腳本中最后那個在config.h中寫入的信息#include<configs/$1.h>中的$1就會不同,不同的配置,這里就會導致我們在start.S包含config.h這個頭文件最終包含的頭文件不同。而這個不同的頭文件中的內容就是我們移植uboot的關鍵所在。
(2)#include <version.h> 這個頭文件中,包含了這個信息#include "version_autogenerated.h",這個里面是uboot版本號的宏,這個版本號來自于makefile中開始的配置。將來在啟動uboot的時候,串口打印出來的uboot版本號
(3)#include <asm/proc/domain.h>
asm是我們在mkconfig腳本配置的時候,一個符號鏈接,它指向了asm-arm,所以實際是這個文件夾下的。proc一樣是符號鏈接。所以uboot不能在我們Windows共享文件夾下去編譯,因為Windows中是沒有符號鏈接的
(4)#include <regs.h> 其實就是我們的S5PC110.h 是在mkconfig腳本配置時,用符號鏈接的方式指向的,實際是指向的,uboot/include/s5pc110.h這個文件
4、start.S解析2
(1)#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
.word 0x2000
.word 0x0
.word 0x0
.word 0x0
#endif
.word是我們GNU匯編中的偽指令,相當于定義了一個變量,內存地址就當前位置的內存地址。連續四個word相當于定義了一個int類型的數據,里面有四個元素,每個元素占4個四節,所以這里占了16個字節
在裸機中,如果我們用SD/NAND等啟動話,鏡像的頭部是需要16B的校驗頭的。我們那時是用,mk210vp_w_picpath.c去計算校驗頭填充到鏡像的頭部的,
但是在uboot中,鏡像頭部的16B沒有去計算,是占位的,是放了16字節的東西去占位的,這16字節的內容是不對的,還是需要我們去計算校驗并且重新填充的。
這占位的十六個字節,決定了,我們在將來計算校驗和時,是否考慮前16個字節,在我們的uboot的sd_fusing下的E什么.c那個文件中,其實和我們之前的那個mk210_p_w_picpath.c很像,可以說就是
(2)異常向量表的構建(可以叫做一級中斷向量表)
b reset //復位異常
ldr pc, _undefined_instruction //未定義指令異常
ldr pc, _software_interrupt //軟中斷異常
ldr pc, _prefetch_abort //預處理異常
ldr pc, _data_abort //數據異常
ldr pc, _not_used //
ldr pc, _irq //IRQ中斷
ldr pc, _fiq //快速中斷
當發生異常時,CPU就會長跳轉到異常向量對應的的地方去運行執行代碼。
異常向量表是硬件決定的,我們軟件只是參照硬件的設計,去實現它。
正常情況下,每種異常我們都應該考慮,并且進行處理,但是uboot中,并沒有完全的做好,因為uboot在內存中執行的時間很短,并且uboot的主要任務是啟動內核,而且uboot就算真的遇到了異常,跑飛了,我們也可以進行復位,重啟。
可以看見,我們復位異常時 b reset 所以當我們在復位的時候,真正開始執行的代碼應該是reset異常對應的代碼。
(3).balignl 16,0xdeadbeef
.balignl 是一個偽指令,目的是為了讓當前內存地址對齊排布。如果沒有按照16對齊,則內存地址向后走,直到對齊為止,并且內存地址向后走的同時,地址中的內容用0xdeadbeef進行填充。
為什么要對齊:一方面是我為了效率,另一方面是硬件的特殊需求
(4)TEXT_BASE
在uboot源代碼沒有配置編譯的時候,這個TEXT_BASE是找不到定義的地方的在源碼中,只能找到引用他的地方。
這個TEXT_BASE其實就是我們在makefile中配置時出現的,就是我們代碼在鏈接時指定的鏈接地址,值就是那個0XC3E00000,
_TEXT_BASE:
.word TEXT_BASE 其實這兩句代碼就相當于定義了一個指針,_TEXT_BASE就相當于這個指針(因為指針本身就是地址,在匯編中,標號也就是相當于地址),.word相當于int類型的,TEXT_BASE相當于這個指針的值,也就是說,這個指針指向了一個int類型的東西,指針的值是TEXT_BASE。也就是說,這個_TEXT_BASE的起始地址開始的地方連續4個字節地址,里面放了一個int類型的,4字節的數TEXT_BASE
5、start.S解析3
(5)_TEXT_PHY_BASE:
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
字面意思是物理地址,查找這個CFG_PHY_UBOOT_BASE值,可以看到這個值是DDR的起始地址0x30000000 + 0x3e00000,所以這個_TEXT_PHY_BASE為起始地址的連續四個字節的地址中放的數是0x33e00000,所以uboot在DDR中的物理地址就是這個值,那個0XC3E00000是虛擬地址,這個0x33e00000是物理地址,兩者相對應的
將來我們可以直接用ldr的方式加載_TEXT_BASE或_TEXT_PHY_BASE
(5)在uboot中我們一般是不用中斷的
(6)msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
像cpsr狀態寄存器中的c位 寫入0xd3
意思就是進制IRQ FIR中斷,arm狀態,SVC模式(特權模式)
(7)設置L2 L1 cache MMU
bl disable_l2cache //禁止 L2 cache 在CPU初始化時
bl set_l2cache_auxctrl_cycle // L2 cache 相關的
bl enable_l2cache // 使能了 L2 cache
Invalidate L1 I/D icache (指令cache) dcache(數據cache)
/*
* disable MMU stuff and caches 關MMU,因為剛開始還沒有做虛擬地址映射呢,所以先關掉
*/
(8)225行(OM引腳相關高低電平的寄存器,0xe0000004,這個寄存器的值反應了OM引腳的接法,啟動介質的選擇)
ldr r0, =PRO_ID_BASE
ldr r1, [r0,#OMR_OFFSET]
bic r2, r1, #0xffffffc1
PRO_ID_BASE 是因為你包含了regs.h得到的,也就是包含了s5pc110.h得到的,這個值是0xe0000000,OMR_OFFSET是一個偏移量,偏移量的值是0x4,r1寄存器的值是0xe0000004,可以看下加載這個地址(對應的寄存器)的內容是什么。
將這個寄存器中的0XFFFFFFC1這些位清0后的值放入了 r2中,r2中的值就記錄了其啟動方式是哪一種,這個寄存器我們的數據手冊中是找不到的,只要三星公司的工程師知道
(9)260行
/* SD/MMC BOOT */
cmp r2, #0xc
moveq r3, #BOOT_MMCSD
最終通過比較r2和一個數字的值,來判定哪種啟動,將MMCSD啟動的值,放在r3寄存中保存起來,將來以后用。
(10) 設置棧284行并調用函數
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub sp, sp, #12 /* set stack */
mov fp, #0
我們知道給sp加載進去值,就相當于在設置棧,值是0xd0036000,第一次設置棧,這個棧是在我們SRAM中的,因為我當前的代碼還沒有到DDR中,還在SRAM中運行。
我們在調用函數的時候,要先設置棧,因為我們bl 到這個函數去運行的時候,這個函數內部可能又調用了函數,bl 去第一層函數時,lr中保存了第一層的返回地址,也就是我們最終返回到當前位置的地址,但是因為我們可能會在這個第一層函數內部去在調用一個函數,所以也要保存那時的當前地址,以便返回,因為現在你是在匯編中,所以你要考慮這些問題,不然程序就跑飛了,所以我們在調用函數時,要先將棧設置好,把我們lr中保存的返回地址先入棧,接著這個函數內部在調用函數的時候,就將那個返回地址也入棧,這樣當我們執行完函數時,出棧的時候,也就最終找到我們lr返回地址,徹底的返回到我們調用函數的位置,要時刻記得棧的樣子,入棧和出棧。
6、start.S解析4(lowlevel_init函數)
(1)lowlevel_init函數(和我們板子相對的那個函數哦,可不是隨意的一個板子的函數哦,是s5pc110中的哦)
push {lr} //壓棧這個返回地址,入棧
//剛進來這個函數,因為lr中保存的是我們在調用這個時的返回地址,所以我們要將這個地址壓棧。
(2)、檢查復位狀態
為什么要檢查復位狀態呢,因為現在的復雜CPU,都有很多種復位狀態,如:冷啟動(直接開機上電),熱啟動,睡眠(低功耗)喚醒等等情況,都屬于復位,所以我們要在復位代碼中檢查CPU是在哪種復位狀態下,因為在不同的復位狀態下,我們要做的工作是不一樣的,比如如果是冷上電(開機上電啟動)的話,我們就要需要做很多工作,如我們的DDR進行初始化,如果是在熱啟動和睡眠低功耗下的啟動,我們就不需要做這個初始化DDR的工作了,因為之前已經初始化好了啊
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
//ELFIN_CLOCK_POWER_BASE = 0xE0100000 RST_STAT_OFFSET = 0x8300 這個寄存器在我們的數據手冊中也沒有找到,三星隱瞞的太多了
(3)、I0狀態的恢復
上面兩個過程知道就好
(4)、關看門狗
(5)、SROM、SRAM一些相關的設置初始化
因為從三星的這個CPU的設計可以看出,這個CPU的可以在SROM的地址處,外接SRAM或者SROM,這個SRAM并不是我們內部的SRAM,是外接的,九鼎應該是沒有用,因為沒看出來用。
(6)供電鎖存(這個肯定非常的不陌生了,自己都在C代碼下,操作內存業就是操作寄存實現了根據數據手冊)
7、start.S解析5(lowlevel_init函數)
(1)110-115行(判斷我們當前的代碼是執行在SRAM中的,還是執行在DDR中的)
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq 1f /* r0 == r1 then skip sdram init */
這幾行代碼就是在判斷判斷我們當前的代碼是執行在SRAM中的,還是執行在DDR中的,為什么要判定呢?
原因1、因為我們的高級CPU,他在復位的時候,有很多種復位情況,如果是在熱啟動,或者低功耗下進行的復位,我們復位后的代碼就是執行在DDR中的,而在我們冷啟動的時候,我們的代碼就是SRAM中運行的。所以要進行判定。如果我們是在冷啟動的情況下,那么開機上電的時候,首先在IROM中的代碼BL0就會去我們放uboot的啟動介質中,將我們uboot的BL1那部分代碼加載到SRAM內存中去運行,去初始化SD卡,初始化DDR,重定位,將我們整個uboot的代碼(BL1、BL2)復制到DDR中,那么就會在我們SRAM中有一份BL1,在我們的DDR中也會有一份BL1,所以如果我們是冷啟動,那么當前運行的BL1代碼就是在我們的SRAM中,如果是熱啟動等方式的話,我們當前代碼的運行位置是在我們的DDR中的,所以確實有可能是在兩個不同的地方運行的,所以我們要判斷到底當前運行的位置到底是在SRAM中,還是DDR中。
原因2、判斷了我們當前的代碼的運行位置,可以指導我們接下來的代碼到底是怎么運行的,比如我們可以通過判定當前代碼的運行位置在哪里,可以指導我們的代碼到底要不要在進行時鐘初始化和初始化DDR的操作。如果我們的當前代碼是在SRAM中運行的,就是冷啟動造成的,所以我們重新進行時鐘的初始化和DDR的初始化,如果我們當前代碼是在DDR中運行的,則說明是熱啟動或者低功耗下的復位,我們就不需要在進行時鐘的初始化和DDR的初始化,直接跳過這段代碼即可。所以更加需要判定了。
(2)看上面的代碼知道,前四行代碼是加載當前的運行地址到r1中,加載鏈接地址到r2中,第五行則比較r1和r2中的地址是否相等,如果相等則說明,我們的當前代碼是運行在DDR中的,beq 1f 的意思就是,如果cmp相等,則會去1這個標號去運行,f表示向下找這個標號。如果不相等,則說明我們當前代碼運行在SRAM中的,所以要跳過beq這一句代碼執行下面的代碼初始化時鐘,和DDR。
/* init system clock */
bl system_clock_init
/* Memory initialize */
bl mem_ctrl_asm_init
(3)是怎么比較判定的呢,在我們裸機中,我們是用adr指令加載,我們當前的_start處的運行地址的,用ldr加載我們的—鏈接地址的。用當前的運行地址和鏈接地址進行比較的,在uboot中略有不同,但也是89不離十了。
(4)bic r1, pc, r0 上面代碼的這句話是,將我們pc的值,也就是當前pc運行時的值,將這個值的一些bit清0(r0寄存器中為1的bit清0)。
(5)系統時鐘的初始化system_clock_init
當前函數的205行到385行
應該在x210_sd.h的300行到428行的宏定義,是跟我們時鐘的配置值相關的,所以我在移植的時候,更改我們時鐘的設置值時,不需要動我們的時鐘初始化代碼,只需要更改我們x210_sd.h中跟時鐘相關的宏定義的設置值即可。
8、start.S解析6(繼續分析lowlevel_init函數)
(1)
mem_ctrl_asm_init函數,是用來初始化我們的DDR,動態內存的。說明了在uboot的BL1代碼階段,確實有初始化DDR了。
(2)看著個函數知道,這個函數中的內容,和我們裸機中的初始化DDR的代碼是一樣的,所以我們裸機中初始化DDR的代碼就是從uboot中抄過來的。但是有一個寄存的值我們不是抄的:
DMC0_MEMCONFIG_0寄存器,我們在裸機中配置的值為0x20F01323,
而在uboot中配置的值為0x30F01313,為什么不同呢,因為我們的SOC內存地址范圍是512M的,DMC0,DMC1加起來一共512M,在裸機中我們配置的時候只用了DMC0,也就是內存的一半,我們用的是0x20000000-0x2fffffff,這256MB的內存空間,但是我們SOC支持的內存地址范圍是0x20000000-0x3fffffff,因為我們只用DMC0,或者DMC1其中的一個,所以我們可以選擇在這個地址范圍內的256MB去使用。
(3)從uboot的這個代碼可以看出,我們在uboot中的物理內存地址范圍是0X30000000-0X4FFFFFFF,一共512MB,其中0X30000000-0X3FFFFFFF為我們的DMC0的內存地址范圍,其中0X40000000-0X4FFFFFFF為我們DMC1的內存地址范圍。
(4)uboot中的內存配置值,是和我們的時鐘配置有關系的,都是通過條件編譯實現的,我們用的時鐘是1000MB主頻,所以我們通過定義了這個宏,找到了我們所對應的內存配置值是在430行到452行,在x210_sd.h中
(5)uart_asm_init
初始化串口的函數,對照也應該會發現和我們裸機中的一樣.
這個串口初始化完了以后給UTXH_OFFSET寄存器,寫入了0x4f4f4f4f,意思就是初始化好了以后發送O,
(6)tzpc_init
這個函數老師也不搞過,不清楚,不管。
(7)pop {pc}以返回
當我們初始化都差不多的時候,返回前還通過串口發送了一個0x4b4b4b4b,打印了一個K。
總結,在我們lowlevel_init.S函數執行完時,會通過串口打印"OK"字樣,所以我們也可以知道,當我們uboot在啟動的時候,最開始應該會看見有OK字樣打印出來
9、start.S解析6
(1)總結:總結一下lowlevel_init.S中,都做了哪些事情:
檢查復位狀態、IO恢復、關看門狗、電源開關自鎖、代碼處在SRAM和DDR中的兩種不同處理初始化的方法(時鐘初始化、內存DDR初始化、串口初始化,tzpc_init初始化),串口初始化后打印'O',整體初始化結束后打印'K'
其中值得關注的就是:關看門狗、電源開關自鎖、時鐘初始化、內存DDR初始化、串口初始化、串口最終是否打印了"OK"
(2)從lowlevel_init.S中出來后,回到start.S中,繼續向下分析代碼
又進行了一次電源的開關自鎖,這僅僅是為了防止電源沒有自鎖成功,在上面的步驟,又重新來了一遍。
(3)又再次的設置棧(設置DDR中的棧)
之前在調用lowlevel_init.S之前,設置過一次棧第一次設置棧(start.S中284-286行),那個時候因為我們的DDR還沒有初始化,我們的代碼是運行在SRAM中的,所以那次設置的棧,設置的是SRAM中的棧。為了函數調用函數,讓返回地址一次入棧,好從函數中返回用的?,F在設置的這次棧是給DDR中設置的(第二次設置棧),因為我們DDR已經初始化好了,所以需要將棧挪移到DDR中(297-299)
ldr sp, _TEXT_PHY_BASE 代碼段的物理地址的基礎地址
地址是#define CFG_PHY_UBOOT_BASE MEMORY_BASE_ADDRESS + 0x3e00000 0x33e00000
所以第二次設置棧的實際地址是0x33e00000 剛好和我們uboot的代碼段下面緊挨著的,uboot的代碼段是在0x33e00000之上的,而arm中的棧是滿減棧,所以地址是在0x33e00000這個地址向下生長的。所以不會沖掉uboot的代碼段
(4)為什么要第二次設置棧呢?因為我們DDR已經初始化好了,有大片的內存可以使用,而且我們的sram中,內存很少,棧在sram中棧不能用的太多,用的太多會棧溢出,所以我們可以將棧移到DDR中,不用在sram中小心翼翼的使用棧了。
(5)再次判斷當前地址是在sram中運行的還是在ddr中運行的,來確定是否要重定位。
在冷啟動時,當前的情況是,我們的uboot的第一部分(uboot的前16kb或者是8kb)在sram中運行著呢,同時我們的uboot第二部分(指的是整個uboot)還在sd卡的某個扇區的開頭躺著呢(這是在我們將程序下載到sd卡時決定的),此時uboot的第一階段要結束了,但是在結束之前必須把后事處理好,不能自己死了,徹底死了,所以此時要將在sd卡中某個扇區開始的N個扇區中的uboot第二部分代碼(整個uboot)加載到DDR中的鏈接地址處去,這個加載的過程就是重定位。
10、uboot的重定位
(1)在start.S的314行,0xD0037488 這個值,我們查找irom的手冊,可以發現,這個地址中的值是被硬件設置的,這個地址中的值代表的是我們在sd卡啟動時,是用的哪個SD卡通道啟動,比如如果我們用的是SD卡的通道0,這地址中的值就是0xeb000000,如果我們是在sd卡的通道2啟動的,那么這個地址中的值就是0xeb200000,這個地址中的值,是硬件通過看我們用的哪個sd卡通道來決定的,是硬件進行設置的。
(2)我們在260行,已經確定了是以什么啟動介質啟動的了,并且在278行,已經將這個值寫入到了某個寄存器中了,在317行,又再次的判斷了是在SD卡的哪個通道啟動的(這里是條件編譯),在后面的322行,又將這個寄存器中的值讀出來了又再一次的判斷是哪種方式啟動的(主要是和#BOOT_MMCSD進行比較執行mmcsd_boot)。最終確定跳轉到了mmcsd_boot去執行重定位了,接著就執行到了movi_bl2_copy這個函數,這個函數就執行了重定位,C好寫,進到movi_bl2_copy函數中去
(3)movi_bl2_copy函數
(從sd中,我們放uboot第二部分也就是整個uboot的開始的N個扇區重定位也就是復制到DDR中,復制的函數是在一個地址中用函數指針的形式表達的。這個地址在irom中可以找到)
ch = *(volatile u32 *)(0xD0037488); 這個函數中,開始將這個地址中的內容存放到了ch中,我們通過查找irom手冊可以知道,在上面也分析到了,這個地址中的值就是存放的我們在哪個sd卡通道啟動的數字,在sd2啟動就是0xeb200000,在sd0啟動就是0xeb000000。、
接著下面的代碼就應該不用在介紹了,在裸機中我都分析過了。應該沒什么問題了。
copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,
CFG_PHY_UBOOT_BASE, 0);
參數: 2 表示是SD卡通道2, MOVI_BL2_POS的值是我們uboot在SD卡開始扇區的位置,這個扇區的位置必須和我們燒錄uboot時的uboot在SD卡扇區開始扇區位置一致。、
MOVI_BL2_BLKCNT 是uboot的長度,就是uboot在SD卡中,占了多少個扇區。
CFG_PHY_UBOOT_BASE 將uboot第二部分,其實是整個uboot在SD卡中重定位拷貝到DDR中的哪個地址中去,這個地址要和我們鏈接地址一致,其實就是0x33e00000
11、虛擬地址映射
(1)什么是物理地址?物理設備設計生產時賦予的地址,我們裸機中使用的寄存器地址,就是CPU設計時指定的。物理地址是硬件編碼,是設計生產時確定好的,一旦我們確定了就不能改了。事實就是,我們寄存器的一個物理地址是無法通過編程修改的,只能通過查詢數據手冊獲得并操作,壞處就是不夠靈活。一個解決方案就是使用虛擬地址,虛擬地址意思就是在我們軟件操作和硬件被操作之間增加一個層次,叫做虛擬地址映射層。就是在用軟件訪問一個虛擬地址層,來達到訪問到硬件的物理地址。
一般時軟件用的全是虛擬地址,硬件用的全是物理地址(虛擬地址到物理地址的映射是不能通過軟件來實現的)
12、MMU單元的作用
(1)內存管理單元,MMU實際上就是一個SOC中的內部外設,SOC中的一個硬件單元,它的主要功能就是實現虛擬地址到物理地址的映射,把虛擬地址翻譯成物理地址
(2)MMU單元在CP15協處理器中進行控制,也就是說要操控MMU單元要對CP15協處理器中的寄存器進行操作編程
13、地址映射額外的收益1,。訪問控制
(3)同時,虛擬MMU單元除了能映射到物理地址方面外,還提供了我們對每一塊的映射關系的訪問權限。訪問控制。
就是可以控制這個映射關系是可讀的還是可寫的,還是可讀可寫的
(4)回想一下C語言中我們在編程的時候,有一種錯誤,叫做段錯誤,這種錯誤實際上就是由于MMU實現的訪問控制實現的,當我們的指針訪問了我們沒有權限訪問的內存塊就會出現段錯誤,這個就是由MMU單元實現的
14、地址映射額外的收益2:cache
(1)cache的工作和虛擬地址是有關系的,cache的意思就告訴緩存的意思,意思就是比CPU慢,但是比DDR快。
(2)cpu嫌棄內存工作的速度太慢了,所以有了cache,我們內存中常用的部分放到了cache中,進行緩存,cache的速度很快,相對于內存來說,所以CPU在需要東西的時候,會現在cache中找,如果找到了,那么就使用cache中緩存的,如果沒有找到的話,就會使用內存中的。所以存在的cache的命中的關系,cpu在cache中找到了,就命中了。
所以cache越大,電腦什么的東西性能就會越好,越快,所以緩存越大越好。
(3)cache也是要在cp15協處理器中進行控制的,所以在處理MMU的時候一般都順帶的把cache也處理了,因為都是用cp15協處理器進行控制的
參考閱讀:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=22891521&id=2109284
15、使用能域訪問(cp15協處理器中的c3寄存器)
(1)CP15協處理器中一共有C0-C15,16個寄存器。mcr指令是用來操作這些寄存器的,用來向這些寄存器中寫值,mrc指令是用來從這些寄存器中讀取數值的。
(2)其中C3寄存器中有對16個域的訪問進行控制,對MMU的作用是,控制域訪問的。所以其中一些域是和MMU的訪問控制有關的
16、TTB(CP15協處理器中的C2寄存器)
(1)TTB就是轉換表基地址。(TT,translation table )轉換表:就是軟件通過虛擬地址訪問到物理地址時,虛擬地址到物理地址的轉化。轉換表中左面的就是我們滴虛擬地址,右邊呢就是對應的物理地址的值,所以我們可以通過虛擬地址去查找這個表左面的內容,來達到找到右面對應的物理地址,所以這個表就叫做轉換表,實現的任務就是,將虛擬地址翻譯成物理地址,所以虛擬地址映射的關鍵,就是要建立這個映射表。
轉換表基地址就是TTB,translation table base,
(2)轉換表是建立我們虛擬地址映射的一個關鍵,轉換表分為兩個部分,一個表的索引,和一個表項,表索引對應的虛擬地址,表項對應于物理地址。一對表索引,表項,構成一個轉換表單元,能夠對一個內存塊進行虛擬地址映射(我們在映射中,基本規定內存映射和管理是以塊為單位的,至于塊有多大,要看MMU的支持和自己的選擇,在ARM中支持三種塊大小,1KB叫細表,4KB叫粗表,1MB叫段式映射),真正的一個轉換表,就是由若干個轉換表單元構成的,每個轉換表單元負責一個內存塊的映射,整個轉換表構成了對整個內存空間的映射(0-4G,32位cpu)。
(3)所以建立虛擬地址的映射關鍵就是建立這張轉換表。
(4)轉換表放在內存中,放置的時候起始地址要求內存對齊,可能是64B對齊,多少M對齊,不一定是4B對齊轉換表不需要軟件干涉使用。而是將轉換表的基地址(TTB),設置到我們CP15的C2寄存器中,然后MMU工作的時候會自動去查轉換表。
17、使能MMU(CP15協處理器的C1寄存器)
(1)當我們把轉換表的基地址寫入到c2寄存器中時,就可以將C2寄存器中的bit0設置為1,就開啟了MMU,當MMU開啟之后。
我們上層軟件層發下來的地址,就是虛擬地址,就通過TT轉換表查找對應的物理地址去執行。轉換表在lowle_init.S中的500多行左右
18、宏觀上來理解轉換表,整個轉換表可以看成是一個數組,轉換表的索引就是數組的下標。
ARM中的段式映射,一個映射單元只能管理1MB的內存,所以在32位中,4G的內存空間需要4G/1MB=4096個映射單元,所以如果用數組理解的話,就是數組中的元素個數是4096個。
但是實際上,我們的處理方法是,將這些要映射單元,很多個組成一個塊,用一個類似于for循環的方法
.rept 0x100
FL_SECTION_ENTRY __base,3,0,0,0
.set __base,__base+1
.endr
.rept是一個偽指令,他和endr一起配合使用,.rept是重復的意思,相當于for循環,中間的兩行代碼相當于For循環的循環體,循環次數就是0x100
.macro FL_SECTION_ENTRY base,ap,d,c,b
.word (\base << 20) | (\ap << 10) | \
(\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.macro 定義宏的,在匯編中,FL_SECTION_ENTRY是宏名,后面五個是這個宏接受的五個參數,.word是定義一個uint類型的四字節的數,這個數是后面的內容就是構成這個數,就構建這個表項
.endm 用來終止這個宏
總結:前面那些我了解的也是不多,以后有需要的話,在結合老師的課程,在結合百度上的資料進行學習吧,
但是可以初步的總結出來,在x210開發板中,九鼎的板子上的DDR是兩片,每片大小是256MB,九鼎的uboot中,將DDR1的起始地址配置成0x30000000,到0x3fffffff,DDR2是0x40000000-0x4fffffff,共256MB,九鼎的uboot中又將MMU的虛擬地址映射中的,虛擬地址,也就是軟件訪問的地址,0xc0000000-0xcfffffff這256MB的地址空間,映射到了0x30000000-0x3fffffff這256MB的空間,從九鼎的uboot中的MMU虛擬地址映射的TTB中的轉換表可以看出來。
這也就是我們用九鼎的uboot的時候,要將鏈接地址,鏈接到DDR的0x33e00000或者0xc3e00000這兩個地址都可以,就是因為他們兩個地址實際上都是相同的。
當MMU開啟了以后就不能使用物理地址了,但是我們為什么還能鏈接到0x33e00000這個物理地址中去呢,實際上這個地址也是虛擬地址映射的,因為在虛擬地址映射的表中,我們通過代碼可以看出來,其中0x200-0x600這段的虛擬地址空間被映射到了0x200開始的地方,因為這段是原樣映射,所以我們也是可以用0x33e00000這個地址的。
11、第三次設置棧
(1)前面已經設置了兩次棧了,一次設置的棧是在sram中的,一次設置是在ddr中的,這次設置的棧還是設置在DDR中的,為什么要第三次在ddr中重新設置棧呢,這個棧,通過代碼可以看出來,不是設置的特別隨便的,而是設置在了uboot起始的地址到為整個uboot劃分的2M空間,又減去了0x1000,這么做的目的是為了讓內存排列的比較緊湊,同時也安全,不會被其他的內容沖掉。arm中的棧是滿減棧,所以棧是向下生長的。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。