版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
1、<p><b> 嵌</b></p><p><b> 入</b></p><p><b> 式</b></p><p><b> Linux</b></p><p><b> 課</b></p>&
2、lt;p><b> 程</b></p><p><b> 設</b></p><p><b> 計</b></p><p><b> 報</b></p><p><b> 告</b></p><p&
3、gt; 課題:嵌入式bootloader的移植分析</p><p><b> 一 概述:</b></p><p> BootLoader介紹</p><p> 系統上電之后,需要一段程序來進行初始化:關閉WATCHDOG、改變系統時鐘、初始化存儲控制器、將更多的代碼復制到內存中等。如果它能將操作系統內核復制到內存中運行,無論從本地,比如
4、Flash;還是從遠端,比如網絡,就稱這段程序為Bootloader。</p><p> Bootloader 是可以添加功能的,比如網絡功能。通過串口或網絡從PC下載燒寫文件、將存儲在Flash 上壓縮的文件解壓后再運行等,這樣的Bootloader是比較強大的,也稱為Monitor。實際上,在最終產品中用戶并不需要使用這些功能,它們只是為了方便開發(fā)。</p><p> Bootlo
5、ader 的實現非常依賴于具體硬件,在嵌入式系統中硬件配置千差萬別,即使是相同的CPU,它的外設也可能不同,比如Flash不同,所以不可能有一個Bootloader 支持所有的CPU、所有的電路板。即使是支持CPU 架構比較多的U-Boot,也不是一拿來就可以使用的,需要進行一些移植。</p><p> 引導加載程序是系統加電后運行的第一段軟件代碼。PC機中的引導加載程序由BIOS(其本質就是一段固件程序)和位
6、于硬盤MBR中的OS BootLoader(比如,LILO和GRUB等)一起組成。BIOS在完成硬件檢測和資源分配后,將硬盤MBR中的BootLoader讀到系統的 RAM中,然后將控制權交給OS BootLoader。BootLoader的主要運行任務就是將內核映象從硬盤上讀到 RAM 中,然后跳轉到內核的入口點去運行,也即開始啟動操作系統。</p><p> 而在嵌入式系統中,通常并沒有像BIOS那樣的固件
7、程序(注,有的嵌入式CPU也會內嵌一段短小的啟動程序),因此整個系統的加載啟動任務就完全由BootLoader來完成。比如在一個基于ARM7TDMI core的嵌入式系統中,系統在上電或復位時通常都從地址0x00000000處開始執(zhí)行,而在這個地址處安排的通常就是系統的BootLoader程序。</p><p> 簡單地說,Boot Loader 就是在操作系統內核運行之前運行的一段小程序。通過這段小程序,我們
8、可以初始化硬件設備、建立內存空間的映射圖,從而將系統的軟硬件環(huán)境帶到一個合適的狀態(tài),以便為最終調用操作系統內核準備好正確的環(huán)境。在嵌入式系統中,通常并沒有像BIOS那樣的固件程序,因此整個系統的加載啟動任務就完全由BootLoader來完成。</p><p> 二:系統總體設計和模塊結構知識:</p><p> (一) 嵌入式Linux軟件結構與分布 在一般情況下嵌入式Linux系
9、統中的軟件主要分為以下及部分:(1)引導加載程序:其中包括內部ROM中的固化啟動代碼和Boot Loader兩部分。而這個內部固化ROM是廠家在芯片生產時候固化的,作用基本上是引導Boot Loader。有的芯片比較復雜,比如Omap3,他在flash中沒有代碼的時候有許多啟動方式:USB、UART或以太網等等。而S3C24x0則很簡單,只有Norboot和Nandboot。(2)Linux kernel 和drivers。(3
10、)文件系統。包括根文件系統和建立于Flash內存設備之上的文件系統(EXT4、UBI、CRAMFS等等)。它是提供管理系統的各種配置文件以及系統執(zhí)行用戶應用程序的良好運行環(huán)境的載體。(4)應用程序。用戶自定義的應用程序,存放于文件系統之中。在Flash 存儲器中,他們的 一般分布如下:</p><p> ?。ǘ︰-Boot主要目錄結構- board 目標板相關文件,主要包含SDRAM、FLASH驅動;-
11、 common 獨立于處理器體系結構的通用代碼,如內存大小探測與故障檢測;- cpu 與處理器相關的文件。如mpc8xx子目錄下含串口、網口、LCD驅動及中斷初始化等文件;- driver 通用設備驅動,如CFI FLASH驅動(目前對INTEL FLASH支持較好)- doc U-Boot的說明文檔;- examples可在U-Boot下運行的示例程序;如hello_world.c,timer.c;- include U-B
12、oot頭文件;尤其configs子目錄下與目標板相關的配置頭文件是移植過程中經常要修改的文件;- lib_xxx 處理器體系相關的文件,如lib_ppc, lib_arm目錄分別包含與PowerPC、ARM體系結構相關的文件;- net 與網絡功能相關的文件目錄,如bootp,nfs,tftp;- post 上電自檢文件目錄。尚有待于進一步完善;- rtc RTC驅動程序;- tools 用于創(chuàng)建U-Boot S-RECORD
13、和BIN鏡像文件</p><p> (三)U-Boot啟動過程:</p><p> Stage1工作流程:</p><p> Stage1的代碼都是與平臺相關的,使用匯編語言編寫占用空間小而且執(zhí)行速度快。以ARM920為例,Stage1階段主要是設置各模式程序異常向量表,初始化處理器相關的關鍵寄存器以及系統內存。Stage1負責建立Stage1階段使用的堆棧和
14、代碼段,然后復制Stage2階段的代碼到內存。</p><p> Stage2工作流程:</p><p> Stage2階段一般包括:初始化Flash器件、檢測系統內存映射、初始化網絡設備、進入命令循環(huán),接收用戶從串口發(fā)送的命令然后進行相應的處理。Stage2使用C語言編寫,用于加載操作系統內核,該階段主要是board.c中的start_armboot()函數實現。</p>
15、<p> 三 原理介紹與系統實現:</p><p> (一)工作原理及代碼分析:</p><p> 1 第一階段(Stage 1) </p><p><b> 中斷向量表的設置 </b></p><p> .globl _start </p><p> _start
16、:b reset </p><p> ldr pc, _undefined_instruction </p><p> ldr pc, _software_interrupt </p><p> ldr pc, _prefetch_abort </p><p> ldr pc, _data_abort </p&g
17、t;<p> ldr pc, _not_used </p><p> ldr pc, _irq </p><p> ldr pc, _fiq </p><p> _undefined_instruction: .word undefined_instruction </p><p> _software_interru
18、pt: .word software_interrupt </p><p> _prefetch_abort: .word prefetch_abort </p><p> _data_abort: .word data_abort </p><p> _not_used: .word not_used </p>
19、<p> _irq: .word irq </p><p> _fiq: .word fiq </p><p> .balignl 16,0xdeadbeef </p><p> Start.s 文件一開始,就定義了_start 的全局變量。也即,在別的文件,照 </p><p>
20、樣能引用這個_start 變量。這段代碼驗證了我們之前學過的 arm 體系的理論知 </p><p> 識:中斷向量表放在從 0x0 開始的地方。其中,每個異常中斷的擺放次序,是 </p><p> 事先規(guī)定的。比如第一個必須是 reset 異常,第二個必須是未定義的指令異常等等。需要注意的是,在這里,我們也可以理解:為何系統一上電,會自動運行代碼。因為系統上電后,會從 0x0 地方取
21、指令,而 0x0 處放置的是 reset 標簽,直接就跳去 reset 標簽處去啟動系統了。另外,這里使用了 ldr 指令。而 ldr 指令中的 label,分別用一個.word偽操 作來定義。比如: </p><p> _undefined_instruction: .word undefined_instruction </p><p> 我們用 source insight 跟蹤
22、代碼后,發(fā)現,undefined_instruction 在 </p><p> start.s 的后面給出了具體的操作,如下: </p><p> undefined_instruction: </p><p> get_bad_stack </p><p> bad_save_user_regs </p><p
23、> bl do_undefined_instruction </p><p> 在跳轉到中斷服務子程序之前,先有兩個宏代碼,一個是對stack的操作, </p><p> 一個是用戶regs的保存。然后才能跳轉如中斷服務子程序中執(zhí)行。讀者若不理 </p><p> 解進入中斷之前做的“現場保護”,請參考《ARM體系結構與編程》等相關書籍, </p
24、><p> 自然能獲得詳細的答案。 </p><p> 值得一提的是,當發(fā)生異常時,都將執(zhí)行 u-boot-1.2.0\cpu\arm920t\ </p><p> interrupts.c 中定義的中斷函數。也就是說,start.s 中要跳轉的這些中斷子程 </p><p> 序的代碼,均在 u-boot-1.2.0\cpu\arm92
25、0t\ interrupts.c 中定義。 </p><p> U-Boot存儲器映射定義 </p><p> 該代碼段主要是定義u-boot 需要使用的一些映射區(qū)的label,比如用戶堆區(qū)、用戶棧區(qū)、全局數據結構區(qū)等。</p><p> _TEXT_BASE: </p><p> .word TEXT_BASE </p&g
26、t;<p> .globl _armboot_start </p><p> _armboot_start: </p><p> .word _start </p><p> * These are defined in the board-specific linker script. </p><p><b&g
27、t; */ </b></p><p> .globl _bss_start </p><p> _bss_start: </p><p> .word __bss_start </p><p> .globl _bss_end </p><p> _bss_end: </p>&l
28、t;p> .word _end </p><p> #ifdef CONFIG_USE_IRQ </p><p> /* IRQ stack memory (calculated at run-time) */ </p><p> .globl IRQ_STACK_START </p><p> IRQ_STACK_START
29、: </p><p> .word 0x0badc0de </p><p> /* IRQ stack memory (calculated at run-time) */ </p><p> .globl FIQ_STACK_START </p><p> FIQ_STACK_START: </p><p&g
30、t; .word 0x0badc0de </p><p><b> #endif </b></p><p> 從上圖也可以清晰地發(fā)現,堆和棧是有區(qū)別的。而且可以看到,用戶棧區(qū)是 </p><p> 向下遞減的,即地址減少的方向生長。 </p><p> 上電后CPU為SVC模式 </p><
31、p><b> reset: </b></p><p><b> /* </b></p><p> * set the cpu to SVC32 mode </p><p><b> */ </b></p><p> mrs r0,cpsr </p&
32、gt;<p> bic r0,r0,#0x1f </p><p> orr r0,r0,#0xd3 </p><p> msr cpsr,r0 </p><p> 這是系統復位后執(zhí)行的“第一個代碼段”(嚴格來說不是)。CPU 復位后,系 </p><p> 統會立即被設置成 SVC 模式。記得之前有網友發(fā)帖咨詢
33、這個問題,問系統復位 </p><p> 后,cpu 處于哪個處理器模式。這個代碼,就回答了這個問題。 </p><p> 從這個代碼中,我們也可以得到一個對寄存器操作的經驗:讀—修改--寫。 </p><p> 這里先把 cpsr 的值讀到r0 中,清除掉我們想修改的bit 位,然后用 orr 指令來 </p><p> 保證其他
34、bit 位不被改動,并達到修改寄存器低 5 位值的目的。最后用 msr 指 </p><p> 令把 r0 的值給cpsr 寄存器達到我們的修改目的。 </p><p><b> 關閉看門狗 </b></p><p> #if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) </
35、p><p> ldr r0, =pWTCON </p><p> mov r1, #0x0 </p><p> str r1, [r0] </p><p> 根據 S3C2440 的 datasheet 文檔,系統啟動后,看門狗寄存器是被使能 </p><p> 的,所以,如果不在預計的時間
36、內“喂狗”,就有“被狗咬”的可能。別說啥了, </p><p> 趕緊先喂狗。上面這段代碼即為喂狗代碼。u-boot 代碼編寫者把它放在 CPU </p><p> 上電修改 SVC 模式后的第一個代碼,是可以理解的。 </p><p> 這個代碼,也是修改寄存器的代碼,它的思路依舊是:讀—修改—寫。 </p><p> 實際上,u-
37、boot-1.2.0 代碼在喂狗代碼之前,還有一段代碼,如下: </p><p> #if defined(CONFIG_S3C2400) </p><p> # define pWTCON 0x15300000 </p><p> # define INTMSK 0x14400008 /* Interu
38、pt-Controller base </p><p> addresses */ </p><p> # define CLKDIVN 0x14800014 /* clock divisor register */ </p><p> #elif defined(CONFIG_S3C2410) </p&
39、gt;<p> # define pWTCON 0x53000000 /* 喂狗寄存器*/ </p><p> # define INTMSK 0x4A000008 /* Interupt-Controller base </p><p> addresses */ </p>
40、<p> # define INTSUBMSK 0x4A00001C </p><p> # define CLKDIVN 0x4C000014 /* clock divisor register */ </p><p><b> #endif </b></p><p>
41、 這是定義寄存器用的。比如根據 S3C2440 的 datasheet 文檔,喂狗寄存 </p><p> 器 pWTCON 的寄存器地址是0x15300000,需要定義后才能使用。同理,這 </p><p> 里還定義了時鐘除數寄存器 CLKDIVN 和中斷掩碼的 INTMSK 寄存器的地址。 </p><p> 在后續(xù)代碼中會陸續(xù)用到。 </p&g
42、t;<p><b> 關掉中斷 </b></p><p> * mask all IRQs by setting all bits in the INTMR - default </p><p><b> */ </b></p><p> mov r1, #0xffffffff </p&
43、gt;<p> ldr r0, =INTMSK </p><p> str r1, [r0] </p><p> # if defined(CONFIG_S3C2410) </p><p> ldr r1, =0x3ff </p><p> ldr r0, =INTSUBMSK </p><p>
44、; str r1, [r0] </p><p><b> # endif </b></p><p> 從注釋可以看出此段代碼的作用:屏蔽掉所有的 irq 中斷。為了屏蔽這些中斷,我們只要把INTMSK 的所有的bit 位都置 1 即可。INTMSK 寄存器共 32bit 位,每個 bit 對應著不同的中斷源。事實上,筆者認為這個代碼是多余的,只是 為了“心里更踏
45、實”而已。因為S3C2440 的datasheet 文檔里明確指出,cpu 在復位的時候,這個寄存器的值就是 0XFFFFFFFF,以防止發(fā)生異常中斷。 </p><p> 3.2.6 修改時鐘除數寄存器 </p><p> /* FCLK:HCLK:PCLK = 1:2:4 */ </p><p> /* default FCLK is 120 MHz !
46、 */ </p><p> ldr r0, =CLKDIVN </p><p> mov r1, #0 /* 原先的值是 3 ,現在是 1:1:1*/ </p><p> str r1, [r0] </p><p> 在 u-boot-1.2.0 源碼中,給 CLKDIVN 寄存器賦值的是#0x3,表示 </p
47、><p> FCLK:HCLK:PCLK = 1:2:4,這里筆者將其比例改為 1:1:1,沒啥特殊的目的, </p><p> 調試代碼的時候試驗用的,后來調試完畢,就沒有再修改了。 </p><p> 調用cpu_init_crit </p><p> #ifndef CONFIG_SKIP_LOWLEVEL_INIT </p
48、><p> bl cpu_init_crit </p><p><b> #endif </b></p><p> 此段代碼指明:若未定義 CONFIG_SKIP_LOWLEVEL_INIT ,就執(zhí)行 </p><p> cpu_init_crit。我們當然不會跳過底層的初始化。因為LOWLEVEL_INIT 會對
49、我 </p><p> 們的 SDRAM 進行初始化,這對我們的cpu 是必要的。根據source insight 的索引, </p><p> 我們轉到了cpu_init_crit 的代碼中: </p><p> #ifndef CONFIG_SKIP_LOWLEVEL_INIT </p><p> cpu_init_crit: &
50、lt;/p><p><b> /* </b></p><p> * flush v4 I/D caches </p><p><b> */ </b></p><p> mov r0, #0 </p><p> mcr p15, 0, r0, c7, c
51、7, 0 /* flush v3/v4 cache */ </p><p> mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */ </p><p><b> /* </b></p><p> * disable MMU stuff and caches </p><p>
52、;<b> */ </b></p><p> mrc p15, 0, r0, c1, c0, 0 </p><p> bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS) </p><p> bic r0, r0, #0x00000087 @
53、 clear bits 7, 2:0 (B--- -CAM) </p><p> orr r0, r0, #0x00000002 @ set bit 2 (A) Align </p><p> orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache </p><p> mcr p15
54、, 0, r0, c1, c0, 0 </p><p><b> /* </b></p><p> * before relocating, we have to setup RAM timing </p><p> * because memory timing is board-dependend, you will </p&g
55、t;<p> * find a lowlevel_init.S in your board directory. </p><p><b> */ </b></p><p> mov ip, lr </p><p> bl lowlevel_init </p><p> mov
56、lr, ip </p><p> mov pc, lr </p><p> #endif /* CONFIG_SKIP_LOWLEVEL_INIT */ </p><p> 非常符合我們的思維,我們無效掉了指令cache和數據cache,并禁止MMU </p><p> 與 cache。為什么會有這一步呢?筆者曾經深受cach
57、e 的傷害。在調試代碼的 </p><p> 時候,下載完修改的bin 文件后,如果只按復位鍵,而不關掉板子重新上電,就 </p><p> 會造成 cache 中可能殘留之前對 cache 操作的數據。我們稱之為“臟數據”, </p><p> 它會映像我們的調試結果,造成假象。 </p><p> 當然,在這里無效cache 和
58、MMU 肯定還有別的原因。比如在初始化階段, </p><p> 可以認為我們只有一個任務在跑,沒有必要,也不允許使用地址變換。因此最好 </p><p> 應該無效掉 MMU。 </p><p> 由于在cpu_init_cri 子程序中又一次調用子程序 lowlevel_init,因此,需 </p><p> 要事先保護好 lr
59、寄存器的內容。當返回時候,再恢復它。在進入 lowlevel_init </p><p> 之前,有必要詳細說一下 mov ip, lr,這個語句的ip。 </p><p> 為了使單獨編譯的C 語言程序和匯編程序之間能相互調用,必須為子程序間 </p><p> 的調用規(guī)定一定的規(guī)則。這就是ATPCS 規(guī)則。它規(guī)定了一些子程序間調用的基 </p&g
60、t;<p> 本規(guī)則。在寄存器的使用規(guī)則里,寄存器 R12 作用子程序間的 scratch 寄存器, </p><p> 記做 ip。mov ip, lr 語句的 ip 由此而來。筆者認為,這里使用別的通用寄存器 </p><p> 來代替 ip,實現的功能也是一樣的。詳情請參考《ARM 體系結構與編程》第 6 </p><p> 章 AT
61、PCS 介紹。 </p><p> 調用lowlevel_init </p><p> 這個函數在 u-boot-1.2.0\board\smdk2410\lowlevel_init.S 文件中。 </p><p> 這是對 SDRAM 的初始化。 </p><p> _TEXT_BASE: </p><p>
62、 .word TEXT_BASE </p><p> .globl lowlevel_init </p><p> lowlevel_init: </p><p> /* memory control configuration */ </p><p> /* make r0 relative the current locat
63、ion so that it */ </p><p> /* reads SMRDATA out of FLASH rather than memory ! */ </p><p> ldr r0, =SMRDATA </p><p> ldr r1, _TEXT_BASE </p><p> sub r0, r0, r
64、1 </p><p> ldr r1, =BWSCON /* Bus Width Status Controller */ </p><p> add r2, r0, #13*4 </p><p><b> 0: </b></p><p> ldr r3, [r0], #4 <
65、;/p><p> str r3, [r1], #4 </p><p> cmp r2, r0 </p><p> bne 0b </p><p> /* everything is fine now */ </p><p> mov pc, lr </p><
66、;p><b> .ltorg </b></p><p> /* the literal pools origin */ </p><p><b> SMRDATA: </b></p><p><b> .word </b></p><p> (0+(B1_BWS
67、CON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+( </p><p> B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B </p><p> 7_BWSCON<<28)) </p><p><b
68、> .word </b></p><p> ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+ </p><p> (B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC)) </p><p><
69、b> .word </b></p><p> ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+ </p><p> (B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC)) </p><p><
70、;b> .word </b></p><p> ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+ </p><p> (B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC)) </p><p>&l
71、t;b> .word </b></p><p> ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+ </p><p> (B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC)) </p><p>&
72、lt;b> .word </b></p><p> ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+ </p><p> (B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC)) </p><p>
73、<b> .word </b></p><p> ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+ </p><p> (B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC)) </p><p>
74、; .word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)) </p><p> .word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)) </p><p><b> .word </b></p><p> ((REFEN<&l
75、t;23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<1 </p><p> 6)+REFCNT) </p><p> .word 0x32 </p><p> .word 0x30 </p><p> .word 0x30 </p>
76、<p> 該段代碼是對 SDRAM 控制器相關的寄存器賦值,賦值過程中,采用了一個 巧妙的做法,把SDRAM 控制器初始化需要用到的 13個寄存器的值先保存在文 字池(literal pools)中,然后通過 LDR 偽指令以及.ltorg 來訪問這個文字池, 獲取寄存器的值賦值到對應的寄存器地址中去。很多同學對此代碼的兩個地址不理解:SMRDATA 與_TEXT_BASE。不理 解這兩個地址相減之后,到底是一個什么值。為什
77、么要相減呢? 其實編譯器進行編譯,是按照鏈接文件進行的。也就是說,編譯的時候所有 的地址都是相對于這個TEXT_BASE 計算出來的。而我們的程序是存放在 Flash 中的,ARM 上電后,假設從 nandflash 模式啟動,那么它會把 Nandflash 的前 4K 加載到內存中開始運行,當然是從 0x0 這個地址開始運行,所以要求我們的 代碼在還沒有搬移到 TEXT_BASE (0x38f00000)這個位置以前是不能使用這 些
78、label 的,只能找到一個相對于0x0 的地址出來,才能得到真正的數據。而且 這時候,我們編譯出來的 bin 文件是存放在 0x00000</p><p><b> 碼的搬移 </b></p><p> #ifndef CONFIG_SKIP_RELOCATE_UBOOT </p><p> relocate:
79、 /* relocate U-Boot to RAM */ </p><p> adr r0, _start /* r0 <- current position of code */ </p><p> ldr r1, _TEXT_BASE /* test if we run from
80、flash or RAM */ </p><p> cmp r0, r1 /* don't reloc during debug */ </p><p> beq stack_setup </p><p> ldr r2, _armboot_start </p><p&
81、gt; ldr r3, _bss_start </p><p> sub r2, r3, r2 /* r2 <- size of armboot */ </p><p> add r2, r0, r2 /* r2 <- source end address */ <
82、;/p><p> copy_loop: </p><p> ldmia r0!, {r3-r10} /* copy from source address [r0] */ </p><p> stmia r1!, {r3-r10} /* copy to target address [r1] */ &
83、lt;/p><p> cmp r0, r2 /* until source end addreee [r2] */ </p><p> ble copy_loop </p><p> #endif /* CONFIG_SKIP_RELOCATE_UBOOT */ </p><p> 在 SD
84、RAM 初始化完畢后,我們開始搬移代碼,把代碼從原先的 0x0 開始 </p><p> 的位置搬移到內存中的適當的位置繼續(xù)執(zhí)行。為啥要搬移代碼?原因可能如下: </p><p> 1、運行速度的考慮。 </p><p> flash 的讀寫速度遠小于SDRAM 的讀寫速度,搬移到SDRAM 后,可提高 </p><p><b&g
85、t; 運行效率。 </b></p><p><b> 2、空間的考慮。 </b></p><p> 如果是 nandflash 啟動模式,那么只有4KB 的空間供用戶使用,實際的代 </p><p> 碼是永遠大于 4KB 的,因此需要重新開辟空間來進行代碼的運行工作。 </p><p> 一時間只
86、能想起這兩個原因。 有些版本的 u-boot 的代碼搬移用C 語言來實現:bl CopyCode2Ram, </p><p> 也是可以的。因為這時候,我們完全搭建好了 C 環(huán)境。 在這段代碼中,還有一個子程序:beq stack_setup,用來設置棧空 </p><p> 間的,我們在
87、下節(jié)中講解。 </p><p><b> ??臻g的設置 </b></p><p> stack_setup: </p><p> ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ </p><p> sub r0
88、, r0, #CFG_MALLOC_LEN /* malloc area */ </p><p> sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */ </p><p> #ifdef CONFIG_USE_IRQ </p><p> sub
89、 r0, r0, </p><p> #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) </p><p><b> #endif </b></p><p> sub s
90、p, r0, #12 /* leave 3 words for abort-stack */ </p><p> 這段代碼是用來分配各個棧空間的。包括分配動態(tài)內存區(qū),全局數據區(qū),IRQ </p><p> 和 FIQ 的??臻g等。 </p><p><b> BSS段的清零 </b></p>
91、<p> clear_bss: </p><p> ldr r0, _bss_start /* find start of bss segment */ </p><p> ldr r1, _bss_end /* stop here */ </
92、p><p> mov r2, #0x00000000 /* clear */ </p><p><b> clbss_l: </b></p><p> str r2, [r0] /* clear loop... */ </p><p
93、> add r0, r0, #4 </p><p> cmp r0, r1 </p><p> ble clbss_l </p><p> 本段代碼先設置了 BSS 段的起始地址與結束地址,然后循環(huán)清楚所有的 </p><p> BSS 段。至此,所有的 cpu 初始化工作(stage1 階段)已經全部結束了。
94、后 </p><p> 面的代碼,將通過 ldr pc, _start_armboot,進入C 代碼執(zhí)行。這個 C 入口 </p><p> 的函數,是在 u-boot-1.1.6\lib_arm\board.c 文件中。它標志著后續(xù)將全面 </p><p> 啟動C 語言程序,同時它也是整個 u-boot 的主函數。 </p&g
95、t;<p> 2 第二階段 stage2:C代碼分析 </p><p> 上節(jié)提到,start_armboot 函數不僅標志著后續(xù)將全面啟動 C 語言程序, </p><p> 同時它也是整個u-boot 的主函數。那么該函數完成什么操作呢? </p><p> 為gd與bd分配空間 </p><p> /* Poin
96、ter is writable since we allocated a register for it */ </p><p> gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - </p><p> sizeof(gd_t)); </p><p> /* comp
97、iler optimization barrier needed for GCC >= 3.4 */ </p><p> __asm__ __volatile__("": : :"memory"); </p><p> memset ((void*)gd, 0, sizeof (gd_t)); </p><p>
98、gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); </p><p> memset (gd->bd, 0, sizeof (bd_t)); </p><p> 如同使用變量之前,需要聲明定義一樣,這里使用全局變量 gd 和 bd 之前, </p><p> 我們需要先設置它的地址,并用 memset 函數為它分
99、配合適的空間。u-boot </p><p> 的注釋告知我們,gd 和 bd 是一個可寫的指針,實際上不過是一個地址而已。 </p><p> 代碼中的這句話:__asm__ __volatile__("": : :"memory"); 目的就是 </p><p> 告訴編譯器內存被修改過了 </p>&
100、lt;p> 執(zhí)行初始化列表函數 </p><p> for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { </p><p> if ((*init_fnc_ptr)() != 0) { </p><p><b> hang (); </b></
101、p><p><b> } </b></p><p><b> } </b></p><p> 這是一個for 語句,卻完成了板子初始化列表函數的功能。我們先來看一下 </p><p> for 語句的初始值:init_sequence。用source insight 跟蹤后發(fā)現,它是一個 <
102、;/p><p><b> 指針數組: </b></p><p> init_fnc_t *init_sequence[] = { </p><p> cpu_init, /* basic cpu dependent setup */ </p><p> board_init, /* basic b
103、oard dependent setup */ </p><p> interrupt_init, /* set up exceptions */ </p><p> env_init, /* initialize environment */ </p><p> init_baudrate, /* initialze baudra
104、te settings */ </p><p> serial_init, /* serial communications setup */ </p><p> console_init_f, /* stage 1 init of console */ </p><p> display_banner, /* say tha
105、t we are here */ </p><p> #if defined(CONFIG_DISPLAY_CPUINFO) </p><p> print_cpuinfo, /* display cpu info (and speed) */ </p><p><b> #endif </b></p><p&
106、gt; #if defined(CONFIG_DISPLAY_BOARDINFO) </p><p> checkboard, /* display board info */ </p><p><b> #endif </b></p><p> dram_init, /* configure available R
107、AM banks */ </p><p> display_dram_config, </p><p><b> NULL, </b></p><p><b> }; </b></p><p> 指針數組的每個成員都對應著一個函數名(函數指針),指向的是 init_fnc_t </p&
108、gt;<p> 類型的函數。For 語句每次都會判斷當前的函數是不是 NULL,如果是,則跳出 </p><p> for 語句,完成當前的板子初始化列表函數的功能。 </p><p> 可能大家都注意到了一個類型:init_fnc_t,它表示什么意思呢?我們看到 </p><p> 了在初始化列表函數之前,有一個新的數據類型,它是個typed
109、ef 語句: </p><p> typedef int (init_fnc_t) (void); </p><p> 可能有的同學對此不太理解,為啥非得用一個 typedef 呢?筆者認為,可以不 </p><p> 用 typedef,但是用了 init_fnc_t 后,團隊中別的成員來看代碼的時候,會很 </p><p> 輕松
110、地知道,這是一個初始化(init)的函數(fnc),增加了代碼的可讀性。 </p><p> Cpu_init 函數,并沒有做實質性的工作,而且我們現在暫時沒有定義 </p><p> CONFIG_USE_IRQ,因此,代碼執(zhí)行到這里,直接就return 0; </p><p> Board_init 函數,是初始化與硬件平臺有關的函數。它的工作很明顯:時
111、 </p><p> 鐘的設置,引腳 IO 口的設置,并且在這里把數據cache 和指令 cache 也打開 </p><p> 了。以上工作的完成,標志著板子已經準備好工作了。當然,考慮到可能系統會 </p><p> 發(fā)生意外中斷,所以我們還需要初始化中斷,讓中斷也準備好工作,因此 u-boot </p><p> 代碼中下一步就
112、開始了中斷的初始化。 </p><p> interrupt_init 函數,這實際上是定時器的中斷初始化。和我們之前的培 </p><p> 訓課程相符的是,我們看到了中斷初始化中的那幾個熟悉的寄存器,首先是兩個 </p><p> 配置寄存器 TCFG0 和 TCFG1。暈倒,怎么代碼中只有TCFG0 的設置,沒有 </p><p>
113、; TCFG1 的設置?很明顯,TCFG1 采用的是默認值。然后配置寄存器的下載值, </p><p> 最后打開啟動開關,啟動定時器工作。 </p><p> env_init 函數,這是對我們板子的環(huán)境做出初始化配置。順便提一下,我 </p><p> 們修改的配置文件里,用的是 nand flash 來存放環(huán)境變量的值。 </p><
114、;p> #define CFG_ENV_IS_IN_NAND 1 </p><p> #define CFG_ENV_OFFSET 0x40000 </p><p> #define CFG_ENV_SIZE64 0xc000 /* Total Size of
115、 </p><p> Environment Sector */ </p><p> #define CFG_ENV_SIZE 0x20000 /* Total Size of </p><p> Environment Sector */ </p><p> 因此,我們在進入
116、u-boot 命令行之后,運行的關于環(huán)境變量的操作,只要 </p><p> 它被保存,saveenv,肯定是save 在 nandflash 中的某個位置。 </p><p> init_baudrate 函數,初始化波特率。我們心里要很明確,初始化波特率, </p><p> 目的只有一個:讓串口打印調試信息。因此,下一個函數,肯定是串口的初始化 <
117、/p><p> 函數。所以,我們可以在調試的時候,先算好波特率的值,直接賦值給 </p><p> gd->bd->bi_baudrate,注釋掉該函數中的其他代碼。調試完畢,再恢復出原 </p><p> 先的代碼。這樣,我們可以不用考慮別的因素導致串口打印不出信息。 </p><p> serial_init 函數,串口的
118、初始化函數。這里調用了另一個函數來配置串口 </p><p> 寄存器:serial_setbrg();在這個函數中,我們看到了關于串口的 5 個寄存器的 </p><p> 配置。關于每個寄存器的更詳細的配置信息,請參考ARM 技術交流網推出的串 </p><p><b> 口課程講解部分。 </b></p><p&
119、gt; console_init_f 函數,這個函數的功能只有一個,就是指出我們目前是使 </p><p> 用串口的,因此有此句:gd->have_console = 1;然后直接返回 0。 </p><p> display_banner 函數。OK,現在串口初始化完畢,我們可以打印信息 </p><p> 了。這是u-boot 代碼中第一次打印
120、信息。我們可以在這里加入我們自己的代碼, </p><p> 比如筆者移植的u-boot 代碼中,就加入了如下“歡迎”的代碼信息: </p><p> printf ("\n\n"); </p><p> printf("********************************************* </p>
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 眾賞文庫僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 嵌入式課程設計--linux系統的裁剪與移植
- 基于嵌入式Linux的BootLoader的設計與應用.pdf
- 嵌入式課程設計報告--qtopia移植
- 嵌入式課程設計
- 嵌入式課程設計
- 嵌入式Arm Linux系統的Bootloader的研究與實現.pdf
- 嵌入式Linux實時性研究及Bootloader的設計與實現.pdf
- 嵌入式課程設計
- 嵌入式課程設計
- 嵌入式課程設計
- 嵌入式課程設計
- MiniGUI在嵌入式Linux的移植.pdf
- 基于arm7的uclinux移植嵌入式課程設計
- LINUX嵌入式系統移植與應用.pdf
- 嵌入式Linux系統移植與實現.pdf
- 嵌入式Linux操作系統移植.pdf
- 嵌入式Linux的移植與實時優(yōu)化.pdf
- 嵌入式課程設計---嵌入式網絡視頻監(jiān)控系統
- 嵌入式課程設計報告---嵌入式文件鎖應用
- 嵌入式系統BOOTLOADER的設計與實現.pdf
評論
0/150
提交評論