注意:由於文檔是去年寫的,內有多個圖片。上傳圖片非常麻煩(須要截圖另存插入等等)。我把文章的PDF版本號上傳到了CSDN下載資源中。為了給自己賺點積分。所以標價2分,沒有積分的同學能夠直接留言跟我要,記得留下郵箱。
下面是文章內容,由於我懶得編輯圖片了,所以文章看來會非常不爽,強烈推薦點擊以上紅色鏈接下載pdf版。
文件編號:DCC01
版本號號:1.0
ARM上電啟動及Uboot代碼分析
部 門: |
|
作 者: |
|
聯系方式: |
|
日 期: |
2013.03.08 |
文件修訂記錄
時間 |
作者 |
主要修訂內容 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
文件夾
1.1.1norflash和nandflash的異同... 5
摘要
網上關於ARM的bootloader(以Uboot為例)的啟動順序的資料有好多,可是對於Uboot的地址映射、體系結構級操作介紹非常少,都是直接開始Start.s代碼的閱讀。
本文擬詳細分析Uboot從上電。到第一條指令的執行。同一時候分析代碼對於cache、TLB等部件的操作過程。
下面內容以u-boot-2012.04.01源代碼為例,從網上非常easy下載該版本號。
1 ARM上電取第一條指令流程
1.1 上電后的第一條指令在哪里?
首先明白:對於ARM芯片。啟動時pc值由CPU設計者規定,不同的ARM CPU有不同的值。比如S3C2440芯片上電后PC值被硬件設計者規定為0x0;其它ARM芯片不一定是0x0。
第一章講述的上電取第一條指令過程以S3C2440為例,該芯片是ARMv4T架構,其它芯片在原理上相似。
S3C2440的啟動時讀取的第一條指令是在內存0x00地址處,無論是從nand flash還是nor flash啟動。
可是上電后內存中是沒有數據的,那么0x00地址處的指令是怎樣放進去的?針對不同的flash(nandflash、norflash),操作方式是不同的。下面講述從nandflash和norflash啟動的不同流程。
1.1.1 norflash和nandflash的異同
nandflash:價格低,容量大,適合大容量數據存儲,地址線和數據線共用I/O線。全部信息都通過一條線傳送,類比於PC的硬盤。
norflash:價格貴。容量小。適合小容量的程序或數據存儲,相似硬盤,可是能在當中執行程序;有獨立地址線、數據線
sdram:主要用於程序執行時的程序存儲、執行或計算。類比於PC的內存;
綜上:norflash比較適合頻繁隨即讀寫的場合。通經常使用於存儲代碼並直接在當中執行。nandflash用於存儲資料。
僅僅要知道以上大概差別即可。下面說明ARM從兩種flash啟動方式的異同。
1.1.1.1 ARM從nandFlash啟動
若從nandflash啟動,上電后nandflash控制器自己主動把nandflash存儲器中的0——4K內容載入到芯片內的起步石(Steppingstone。起步石這個機制是處理器中集成的功能,對程序猿透明),即內部SRAM緩沖器中,同一時候把內部SRAM的起始地址設置為0x0(不同的CPU上電后的PC值不盡同樣。對不同的CPU該值也不盡同樣)。然后把這段片內SRAM映射到nGCS0片選的空間。進而CPU開始從內部SRAM的0x0處開始取得第一條指令,該過程全部是硬件自己主動完畢,不須要程序代碼控制。
也許你有個疑問,為什么不能直接把nandflash映射到0x0地址處?非要經過內部SRAM緩沖?
答案是。nandflash根本沒有地址線,沒法直接映射,必須使用SRAM做一個載體。通過SRAM把剩余的nandflash代碼(即剩余的uboot啟動代碼)復制到SDRAM中執行。
若想從nandflash啟動,那么uboot最核心的代碼必須放在前4k完畢。這4k代碼要完畢ARM CPU的核心配置以及將剩余的代碼復制到SDRAM中(若從norflash啟動則沒有4k這個大小的限制,可是還會在完畢最基本的設置后進入SDRAM中執行)。
1.1.1.2 ARM從norflash啟動
若從norflash啟動,則norflash直接被映射到內存的0x0地址處(就是nGCS0。這里就不須要片內SRAM來輔助了。所以片內SRAM的起始地址不變,還是0x40000000)。然后cpu從0x00000000開始執行(也就是在Norfalsh中執行)。
須要說明的是。uboot代碼段(.text段)起始位置必須是與上電后PC值一致,即編譯uboot時,TEXT_BASE宏必須設置成0x0 。反匯編uboot文件后,文本段第一條指令的地址也是0.
總結:
1、從norflash還是從nandflash啟動。是由ARM的OM1和OM0引腳組合決定
2、無論從norflash還是nandflash啟動,S3C2440上電后的pc值為0x0
3、假設某芯片上電后PC值不是0x0。假如是0x38ff0000,那么從norflash啟動時,硬件就要自己主動將其映射到0x38ff0000地址處;假設從nandflash啟動。那么硬件就要自己主動將nandflash中的前4K內容載入到0x38ff0000地址處。
2 Uboot.lds鏈接腳本分析
2.1 為什么要分析uboot鏈接腳本?
由於u-boot.lds決定了u-boot可執行映像的鏈接方式,以及各個段的裝載地址(裝載域)和執行地址(執行域),也就是說。Uboot.lds文件指定uboot.bin可執行文件放到ROM中的哪個地址、在執行時在RAM中執行的起始地址。詳細內容涉及裝載域和執行域的概念。這里不詳述。
2.2 連接代碼詳細分析
以u-boot-2012.04.01版本號為例。
總結:
1、SECTION后面的段都是依照順序放到內存中的。比如text段后面跟着rodata段
2、該文件里沒有指定段的載入地址(用AT命令),沒指定的情況下載入地址和執行地址是同樣的,也就是說在uboot.bin在rom和ram中的地址同樣。
3 Uboot中start.S文件分析
3.1 start.S詳細解釋
上面分析的鏈接腳本中已經規定,首先啟動的文件是arch/arm/cpu/armv7/start.S。
對於uboot的start.S,主要做的事情就是系統的各個方面的初始化。然后復制剩余代碼到RAM中繼續執行。
(1) 設置CPU模式
(2) 關閉cache, MMU, TLBs
(3) 設置棧,pll, mux, memory
(4) 設置watchdog, muxing, and clocks
(5) 板級初始化
(6) 自我復制到RAM中。並跳轉到RAM中繼續執行。
下面內容依照程序執行流程進行解說。以ARMv7架構為例。
3.1.1 _start
文本段第一條指令就是一條跳轉指令:
依據1.1章的分析。假設uboot是燒寫到norflash中的,那么一上電的_start標號肯定是在0x0處。假設uboot已經啟動。程序執行過了relocate(見后面),那么該標號就會被移動到TEXT_BASE標號處,該標號是編譯uboot時程序猿指定的,詳細數值見開發板的board/~/config.mk文件。
3.1.2 reset
CPSR的位域見ARM手冊。下圖截圖方便參考:
3.1.3 cpu_init_cp15
3.1.4 cpu_init_crit
3.1.5 lowlevel_init
3.1.6 s_init
3.1.7 call_board_init_f
3.1.8 board_init_f
3.1.9 relocate_code
為什么uboot代碼須要relocate?見問題總結及解答。
3.1.10 clear_bss
3.1.11 jump_2_ram
3.2 本章小結
主要分析了uboot啟動的第一階段代碼。
4 板級初始化及跳入Linux內核執行
4.1 board_init_r
該函數在u-boot-2012.04.01\arch\arm\cpu\armv7\omap-common\spl.c中。
其功能是:
(1) 初始化內存分配函數
(2) 假設系統有mmc設備。則初始化mmc設備
(3) 假設系統有nand設備。則初始化nand設備
(4) 進入uboot命令循環或者直接開始執行linux內核。
眼下臨時不須要詳細分析該部分代碼。后期若須要會加上。
千萬不要刪除行尾的分節符,此行不會被打印。
“結論”曾經的全部正文內容都要編寫在此行之前。
5 Uboot異常處理
5.1 Uboot異常向量表
緊跟b reset后面的就是異常向量表,發生異常后pc會被自己主動置為對應的值,進入對應異常處理程序。
5.1.1 異常處理入口函數
5.1.2 異常處理函數跳轉
/*
* exception handlers
*/
.align 5
undefined_instruction:
get_bad_stack
bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
get_bad_stack_swi
bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
get_bad_stack
bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
get_bad_stack
bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ //假設在uboot中啟用了用戶中斷。則跳入對應處理函
//數執行
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effective fiq_save_user_regs*/
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else //假設沒有配置。則走還有一條路徑。
.align 5
irq:
get_bad_stack
bad_save_user_regs
bl do_irq
.align 5
fiq:
get_bad_stack
bad_save_user_regs
bl do_fiq
#endif /* CONFIG_USE_IRQ */
#endif /* CONFIG_SPL_BUILD*/
5.1.3 異常真正處理函數
// u-boot-2012.04.01\arch\arm\lib\Interrupts.c
void do_undefined_instruction (struct pt_regs *pt_regs)
{
printf ("undefinedinstruction\n");
show_regs (pt_regs);
bad_mode ();
}
void do_software_interrupt (struct pt_regs *pt_regs)
{
printf ("software interrupt\n");
show_regs (pt_regs);
bad_mode ();
}
void do_prefetch_abort (struct pt_regs *pt_regs)
{
printf ("prefetchabort\n");
show_regs (pt_regs);
bad_mode ();
}
void do_data_abort (struct pt_regs *pt_regs)
{
printf ("dataabort\n");
show_regs (pt_regs);
bad_mode ();
}
void do_not_used (struct pt_regs *pt_regs)
{
printf ("notused\n");
show_regs (pt_regs);
bad_mode ();
}
void do_fiq (struct pt_regs *pt_regs)
{
printf ("fastinterrupt request\n");
show_regs (pt_regs);
bad_mode ();
}
#ifndef CONFIG_USE_IRQ
void do_irq (struct pt_regs *pt_regs)
{
printf ("interruptrequest\n");
show_regs (pt_regs);
bad_mode ();
}
#endif
void bad_mode (void) //以上異常處理函數都跳轉到bad_mode。該函數僅僅是
//掛起CPU,木有詳細處理。
{
panic ("Resetting CPU ...\n");
reset_cpu (0);
}
5.2 本章總結
總結uboot下異常處理流程。發現ARMv7下的uboot沒有實現異常處理,ARM的其它架構有。有可能是由於uboot代碼不夠新的原因。Uboot中的ARM異常處理流程都同樣。
[1]
[2]
[3]
千萬不要刪除行尾的分節符。此行不會被打印。
1、對於載入時地址和執行時地址不同的段。執行時它是怎么跳轉到執行時地址的?
答: 連接地址<==>執行地址
存儲地址<==>載入地址
(1)對於有操作系統時,執行地址與載入地址不同。在載入過程中裝載器就把段載入到它應該去的連接地址處(也就是生成該段時的執行地址)
(2)對於uboot,執行地址與載入地址不同一時候。須要它自己(比如前4k代碼)將自己載入到執行地址處執行。
Uboot.lds文件里起始地址是0x00,可是config.mk中的TEXT_BASE是0x57e00000,可是生成的uboot反匯編文件里。為什么start.s的第一條指令地址也是0x57e00000?不應該是0x00么?由於start.s的載入地址和執行地址都是0x00啊。?
答:Uboot.lds的0x00:
跟在SECTION后面的第一條
location counter,總是默認初始化為0。config.mk中的TEXT_BASE就是ROM在CPU上的地址。也就是說。不同的CPU已經規定了不同的ROM地址
2、關於為何不能直接用mov指令,而非要用adr偽指令?
把全部uboot代碼復制到內存新地址處。
在分析uboot的start.S中,看到一些指令,比方:
adr r0, _start
認為好像能夠直接用mov指令實現即可。為啥還要這么麻煩地,去用ldr去實現?
關於此處的代碼。為何要用adr指令:
adr r0, _start
其被編譯器編譯后。會被翻譯成:sub r0, pc, #172
而不直接用mov指令直接將_start的值賦值給r0,相似於這樣:
mov r0, _start
呢?
其原因主要是,
sub r0, pc, #172
這種代碼。所處理的值。都是相對於PC的偏移量來說的。這種代碼中,沒有絕對的物理地址值。都是相對的值,利用產生位置無關代碼。由於假設用mov指令:
mov r0, _start
那么就會被編譯成這種代碼:
mov r0, 0x33d00000
假設用了上面這種代碼:
mov r0, 0x33d00000
那么,假設整個代碼,即要執行的程序的指令。被移動到其它位置,那么
mov r0, 0x33d00000
這行指令,執行的功能,就是跳轉到絕對的物理地址,而不是跳轉到相對的_start的位置了,就不能實現我們想要的功能了,這樣包括了絕對物理地址的代碼,也就不是位置無關的代碼了。
與此相對,這行指令:
sub r0, pc, #172
即使程序被移動到其它位置,那么該行指令還是能夠跳轉到相對PC往前172字節的地方。也還是我們想要的_start的位置。這樣包括的都是相對的偏移位置的代碼,就叫做位置無關代碼。其長處就是不用操心你的代碼被移動,即使程序的基地址變了,全部的代碼的相對位置還是固定的。程序還是能夠正常執行的。
關於,之所以不用上面的:
mov r0, 0x33d00000
相似的代碼。除了上面說的,不是位置無關的代碼之外,其還有個潛在的問題,那就是。關於mov指令的源操作數。此處即為0x33d00000,不一定是合法的mov 指令所同意的值。
【總結】
之所以用adr而不用mov。主要是為了生成地址無關代碼。以及由於不方便推斷一個數,是否是有效的mov的操作數。
3、為什么uboot代碼須要relocate?
由於uboot啟動時不在片外RAM中,為了加快執行,須要將uboot又一次復制到RAM中執行。
千萬不要刪除行尾的分節符,此行不會被打印!