【ARM-Linux開發】U-Boot啟動過程--詳細版的完全分析
-------------------------------------------------------------------------------------------------------------------------------------------
我們知道,bootloader是系統上電後最初載入執行的程式碼。它提供了處理器上電覆位後最開始需要執行的初始化程式碼。
在PC機上載入程式一般由BIOS開始執行,然後讀取硬碟中位於MBR(Main Boot Record,主引導記錄)中的Bootloader(例如LILO或GRUB),並進一步引導作業系統的啟動。
然而在嵌入式系統中通常沒有像BIOS那樣的韌體程式,因此整個系統的載入啟動就完全由bootloader來完成。它主要的功能是載入與引導核心映像
一個嵌入式的儲存裝置通過通常包括四個分割槽:
第一分割槽:存放的當然是u-boot
第二個分割槽:存放著u-boot要傳給系統核心的引數
第三個分割槽:是系統核心(kernel)
第四個分割槽:則是根檔案系統
如下圖所示:
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
u-boot是一種普遍用於嵌入式系統中的Bootloader。
Bootloader介紹
Bootloader是進行嵌入式開發必然會接觸的一個概念,它是嵌入式學院<嵌入式工程師職業培訓班>二期課程中嵌入式linux系統開發方面的重要內容。本篇文章主要講解Bootloader的基本概念以及內部原理,這部分內容的掌握將對嵌入式linux系統開發的學習非常有幫助!
Bootloader的定義:Bootloader是在作業系統執行之前執行的一小段程式,通過這一小段程式,我們可以初始化硬體裝置、建立記憶體空間的對映表,從而建立適當的系統軟硬體環境,為最終呼叫作業系統核心做好準備。意思就是說如果我們要想讓一個作業系統在我們的板子上運轉起來,我們就必須首先對我們的板子進行一些基本配置和初始化,然後才可以將作業系統引導進來執行。具體在Bootloader中完成了哪些操作我們會在後面分析到,這裡我們先來回憶一下PC的體系結構:PC機中的引導載入程式是由BIOS和位於硬碟MBR中的OS Boot Loader(比如LILO和GRUB等)一起組成的,BIOS在完成硬體檢測和資源分配後,將硬碟MBR中的Boot Loader讀到系統的RAM中,然後將控制權交給OS Boot Loader。Boot Loader的主要執行任務就是將核心映象從硬碟上讀到RAM中,然後跳轉到核心的入口點去執行,即開始啟動作業系統。在嵌入式系統中,通常並沒有像BIOS那樣的韌體程式(注:有的嵌入式cpu也會內嵌一段短小的啟動程式),因此整個系統的載入啟動任務就完全由Boot Loader來完成。比如在一個基於ARM7TDMI core的嵌入式系統中,系統在上電或復位時通常都從地址0x00000000處開始執行,而在這個地址處安排的通常就是系統的Boot Loader程式。(先想一下,通用PC和嵌入式系統為何會在此處存在如此的差異呢?)
Bootloader是基於特定硬體平臺來實現的,因此幾乎不可能為所有的嵌入式系統建立一個通用的Bootloader,不同的處理器架構都有不同的Bootloader,Bootloader不但依賴於cpu的體系結構,還依賴於嵌入式系統板級裝置的配置。對於2塊不同的板子而言,即使他們使用的是相同的處理器,要想讓執行在一塊板子上的Bootloader程式也能執行在另一塊板子上,一般也需要修改Bootloader的源程式。
Bootloader的啟動方式
Bootloader的啟動方式主要有網路啟動方式、磁碟啟動方式和Flash啟動方式。
1、網路啟動方式
圖1 Bootloader網路啟動方式示意圖
如圖1所示,裡面主機和目標板,他們中間通過網路來連線,首先目標板的DHCP/BIOS通過BOOTP服務來為Bootloader分配IP地址,配置網路引數,這樣才能支援網路傳輸功能。我們使用的u-boot可以直接設定網路引數,因此這裡就不用使用DHCP的方式動態分配IP了。接下來目標板的Bootloader通過TFTP服務將核心映像下載到目標板上,然後通過網路檔案系統來建立主機與目標板之間的檔案通訊過程,之後的系統更新通常也是使用Boot Loader的這種工作模式。工作於這種模式下的Boot Loader通常都會向它的終端使用者提供一個簡單的命令列介面。
2、磁碟啟動方式
這種方式主要是用在臺式機和伺服器上的,這些計算機都使用BIOS引導,並且使用磁碟作為儲存介質,這裡面兩個重要的用來啟動linux的有LILO和GRUB,這裡就不再具體說明了。
3、Flash啟動方式
這是我們最常用的方式。Flash有NOR Flash和NAND Flash兩種。NOR Flash可以支援隨機訪問,所以程式碼可以直接在Flash上執行,Bootloader一般是儲存在Flash晶片上的。另外Flash上還儲存著引數、核心映像和檔案系統。這種啟動方式與網路啟動方式之間的不同之處就在於,在網路啟動方式中,核心映像和檔案系統首先是放在主機上的,然後經過網路傳輸下載進目標板的,而這種啟動方式中核心映像和檔案系統則直接是放在Flash中的,這兩點在我們u-boot的使用過程中都用到了。
U-boot的定義
U-boot,全稱Universal Boot Loader,是由DENX小組的開發的遵循GPL條款的開放原始碼專案,它的主要功能是完成硬體裝置初始化、作業系統程式碼搬運,並提供一個控制檯及一個指令集在作業系統執行前操控硬體裝置。U-boot之所以這麼通用,原因是他具有很多特點:開放原始碼、支援多種嵌入式作業系統核心、支援多種處理器系列、較高的穩定性、高度靈活的功能設定、豐富的裝置驅動原始碼以及較為豐富的開發除錯文件與強大的網路技術支援。另外u-boot對作業系統和產品研發提供了靈活豐富的支援,主要表現在:可以引導壓縮或非壓縮系統核心,可以靈活設定/傳遞多個關鍵引數給作業系統,適合系統在不同開發階段的除錯要求與產品釋出,支援多種檔案系統,支援多種目標板環境引數儲存介質,採用CRC32校驗,可校驗核心及映象檔案是否完好,提供多種控制檯介面,使使用者可以在不需要ICE的情況下通過串列埠/乙太網/USB等介面下載資料並燒錄到儲存裝置中去(這個功能在實際的產品中是很實用的,尤其是在軟體現場升級的時候),以及提供豐富的裝置驅動等。
u-boot原始碼的目錄結構
1、board中存放於開發板相關的配置檔案,每一個開發板都以子資料夾的形式出現。
2、Commom資料夾實現u-boot行下支援的命令,每一個命令對應一個檔案。
3、cpu中存放特定cpu架構相關的目錄,每一款cpu架構都對應了一個子目錄。
4、Doc是文件目錄,有u-boot非常完善的文件。
5、Drivers中是u-boot支援的各種裝置的驅動程式。
6、Fs是支援的檔案系統,其中最常用的是JFFS2檔案系統。
7、Include資料夾是u-boot使用的標頭檔案,還有各種硬體平臺支援的彙編檔案,系統配置檔案和檔案系統支援的檔案。
8、Net是與網路協議相關的程式碼,bootp協議、TFTP協議、NFS檔案系統得實現。
9、Tooles是生成U-boot的工具。
對u-boot的目錄有了一些瞭解後,分析啟動程式碼的過程就方便多了,其中比較重要的目錄就是/board、/cpu、/drivers和/include目錄,如果想實現u-boot在一個平臺上的移植,就要對這些目錄進行深入的分析。
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
什麼是《編譯地址》?什麼是《執行地址》?
(一)編譯地址: 32位的處理器,它的每一條指令是4個位元組,以4個位元組儲存順序,進行順序執行,CPU是順序執行的,只要沒發生什麼跳轉,它會順序進行執行行, 編譯器會對每一條指令分配一個編譯地址,這是編譯器分配的,在編譯過程中分配的地址,我們稱之為編譯地址。
(二)執行地址:是指程式指令真正執行的地址,是由使用者指定的,使用者將執行地址燒錄到哪裡,哪裡就是執行的地址。
比如有一個指令的編譯地址是0x5,實際執行的地址是0x200,如果使用者將指令燒到0x200上,那麼這條指令的執行地址就是0x200,
當編譯地址和執行地址不同的時候會出現什麼結果?結果是不能跳轉,編譯後會產生跳轉地址,如果實際地址和編譯後產生的地址不相等,那麼就不能跳轉。
C語言編譯地址:都希望把編譯地址和實際執行地址放在一起的,但是彙編程式碼因為不需要做C語言到彙編的轉換,可以認為的去寫地址,所以直接寫的就是他的執行地址這就是為什麼任何bootloader剛開始會有一段彙編程式碼,因為起始程式碼編譯地址和實際地址不相等,這段程式碼和彙編無關,跳轉用的執行地址。
編譯地址和執行地址如何來算呢?
1. 假如有兩個編譯地址a=0x10,b=0x7,b的執行地址是0x300,那麼a的執行地址就是b的執行地址加上兩者編譯地址的差值,a-b=0x10-0x7=0x3,
a的執行地址就是0x300+0x3=0x303。
2. 假設uboot上兩條指令的編譯地址為a=0x33000007和b=0x33000001,這兩條指令都落在bank6上,現在要計算出他們對應的執行地址,要找出執行地址的始地址,這個是由使用者燒錄進去的,假設執行地址的首地址是0x0,則a的執行地址為0x7,b為0x1,就是這樣算出來的。
為什麼要分配編譯地址?這樣做有什麼好處,有什麼作用?
比如在函式a中定義了函式b,當執行到函式b時要進行指令跳轉,要跳轉到b函式所對應的起始地址上去,編譯時,編譯器給每條指令都分配了編譯地址,如果編譯器已經給分配了地址就可以直接進行跳轉,查詢b函式跳轉指令所對應的表,進行直接跳轉,因為有個編譯地址和指令對應的一個表,如果沒有分配,編譯器就查詢不到這個跳轉地址,要進行計算,非常麻煩。
什麼是《相對地址》?
以NOR Flash為例,NOR Falsh是對映到bank0上面,SDRAM是對映到bank6上面,uboot和核心最終是在SDRAM上面執行,最開始我們是從Nor Flash的零地址開始往後燒錄,uboot中至少有一段程式碼編譯地址和執行地址是不一樣的,編譯uboot或核心時,都會將編譯地址放入到SDRAM中,他們最終都會在SDRAM中執行,剛開始uboot在Nor
Flash中執行,執行地址是一個低端地址,是bank0中的一個地址,但編譯地址是bank6中的地址,這樣就會導致絕對跳轉指令執行的失敗,所以就引出了相對地址的概念。
那麼什麼是相對地址呢?
至少在bank0中uboot這段程式碼要知道不能用b+編譯地址這樣的方法去跳轉指令,因為這段程式碼的編譯地址和執行地址不一樣,那如何去做呢?
要去計算這個指令執行的真實地址,計算出來後再做跳轉,應該是b+執行地址,不能出現b+編譯地址,而是b+執行地址,而執行地址是算出來的。
_TEXT_BASE:
.word TEXT_BASE //0x33F80000,在board/config.mk中
這段話表示,使用者告訴編譯器編譯地址的起始地址
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
大多數 Boot Loader 都包含兩種不同的操作模式:"啟動載入"模式和"下載"模式,這種區別僅對於開發人員才有意義。
但從終端使用者的角度看,Boot Loader 的作用就是:用來載入作業系統,而並不存在所謂的啟動載入模式與下載工作模式的區別。
(一)啟動載入(Boot loading)模式:這種模式也稱為"自主"(Autonomous)模式。
也即 Boot Loader 從目標機上的某個固態儲存裝置上將作業系統載入到 RAM 中執行,整個過程並沒有使用者的介入。
這種模式是 Boot Loader 的正常工作模式,因此在嵌入式產品釋出的時侯,Boot Loader 顯然必須工作在這種模式下。
(二)下載(Downloading)模式:在這種模式下,目標機上的 Boot Loader 將通過串列埠連線或網路連線等通訊手段從主機(Host)下載檔案,比如:下載核心映像和根檔案系統映像等。從主機下載的檔案通常首先被 Boot Loader儲存到目標機的RAM 中,然後再被 BootLoader寫到目標機上的FLASH類固態儲存裝置中。Boot
Loader 的這種模式通常在第一次安裝核心與根檔案系統時被使用;此外,以後的系統更新也會使用 Boot Loader 的這種工作模式。工作於這種模式下的 Boot Loader 通常都會向它的終端使用者提供一個簡單的命令列介面。這種工作模式通常在第一次安裝核心與跟檔案系統時使用。或者在系統更新時使用。進行嵌入式系統除錯時一般也讓bootloader工作在這一模式下。
U-Boot 這樣功能強大的 Boot Loader 同時支援這兩種工作模式,而且允許使用者在這兩種工作模式之間進行切換。
大多數 bootloader 都分為階段 1(stage1)和階段 2(stage2)兩大部分,u-boot 也不例外。依賴於 CPU 體系結構的程式碼(如 CPU 初始化程式碼等)通常都放在階段 1 中且通常用匯編語言實現,而階段 2 則通常用 C 語言來實現,這樣可以實現複雜的功能,而且有更好的可讀性和移植性。
-------------------------------------------------------------------------------------------------------------------------------------------
第一、大概總結性得的分析
系統啟動的入口點。既然我們現在要分析u-boot的啟動過程,就必須先找到u-boot最先實現的是哪些程式碼,最先完成的是哪些任務。
另一方面一個可執行的image必須有一個入口點,並且只能有一個全域性入口點,所以要通知編譯器這個入口在哪裡。由此我們可以找到程式的入口點是在/board/lpc2210/u-boot.lds中指定的,其中ENTRY(_start)說明程式從_start開始執行,而他指向的是cpu/arm7tdmi/start.o檔案。
因為我們用的是ARM7TDMI的cpu架構,在復位後從地址0x00000000取它的第一條指令,所以我們將Flash對映到這個地址上,
這樣在系統加電後,cpu將首先執行u-boot程式。u-boot的啟動過程是多階段實現的,分了兩個階段。
依賴於cpu體系結構的程式碼(如裝置初始化程式碼等)通常都放在stage1中,而且通常都是用匯編語言來實現,以達到短小精悍的目的。
而stage2則通常是用C語言來實現的,這樣可以實現複雜的功能,而且程式碼具有更好的可讀性和可移植性。
下面我們先詳細分析下stage1中的程式碼,如圖2所示:
圖2 Start.s程式流程
程式碼真正開始是在_start,設定異常向量表,這樣在cpu發生異常時就跳轉到/cpu/arm7tdmi/interrupts中去執行相應得中斷程式碼。
在interrupts檔案中大部分的異常程式碼都沒有實現具體的功能,只是列印一些異常訊息,其中關鍵的是reset中斷程式碼,跳到reset入口地址。
reset復位入口之前有一些段的宣告。
1.在reset中,首先是將cpu設定為svc32模式下,並遮蔽所有irq和fiq。
2.在u-boot中除了定時器使用了中斷外,其他的基本上都不需要使用中斷,比如串列埠通訊和網路等通訊等,在u-boot中只要完成一些簡單的通訊就可以了,所以在這裡遮蔽掉了所有的中斷響應。
3.初始化外部匯流排。這部分首先設定了I/O口功能,包括串列埠、網路介面等的設定,其他I/O口都設定為GPIO。然後設定BCFG0~BCFG3,即外部匯流排控制器。這裡bank0對應Flash,設定為16位寬度,匯流排速度設為最慢,以實現穩定的操作;Bank1對應DRAM,設定和Flash相同;Bank2對應RTL8019。
4.接下來是cpu關鍵設定,包括系統重對映(告訴處理器在系統發生中斷的時候到外部儲存器中去讀取中斷向量表)和系統頻率。
5.lowlevel_init,設定RAM的時序,並將中斷控制器清零。這些部分和特定的平臺有關,但大致的流程都是一樣的。
下面就是程式碼的搬移階段了。為了獲得更快的執行速度,
通常把stage2載入到RAM空間中來執行,因此必須為載入Boot Loader的stage2準備好一段可用的RAM空間範圍。空間大小最好是memory page大小(通常是4KB)的倍數
一般而言,1M的RAM空間已經足夠了。
flash中儲存的u-boot可執行檔案中,程式碼段、資料段以及BSS段都是首尾相連儲存的,
所以在計算搬移大小的時候就是利用了用BSS段的首地址減去程式碼的首地址,這樣算出來的就是實際使用的空間。
程式用一個迴圈將程式碼搬移到0x81180000,即RAM底端1M空間用來儲存程式碼。
然後程式繼續將中斷向量表搬到RAM的頂端。由於stage2通常是C語言執行程式碼,所以還要建立堆疊去。
在堆疊區之前還要將malloc分配的空間以及全域性資料所需的空間空下來,他們的大小是由巨集定義給出的,可以在相應位置修改。
基本記憶體分佈圖:
圖3 搬移後記憶體分佈情況圖
下來是u-boot啟動的第二個階段,是用c程式碼寫的,
這部分是一些相對變化不大的部分,我們針對不同的板子改變它呼叫的一些初始化函式,並且通過設定一些巨集定義來改變初始化的流程,
所以這些程式碼在移植的過程中並不需要修改,也是錯誤相對較少出現的檔案。
在檔案的開始先是定義了一個函式指標陣列,通過這個陣列,程式通過一個迴圈來按順序進行常規的初始化,並在其後通過一些巨集定義來初始化一些特定的裝置。
在最後程式進入一個迴圈,main_loop。這個迴圈接收使用者輸入的命令,以設定引數或者進行啟動引導。
本篇文章將分析重點放在了前面的start.s上,是因為這部分無論在移植還是在除錯過程中都是最容易出問題的地方,要解決問題就需要程式設計師對程式碼進行修改,所以在這裡簡單介紹了一下start.s的基本流程,希望能對大家有所幫助
-------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------
第二、程式碼分析
2.2 階段 1 介紹
u-boot 的 stage1 程式碼通常放在 start.s 檔案中,它用匯編語言寫成,其主要程式碼部分如下:
2.2.1 定義入口
由於一個可執行的 Image 必須有一個入口點,並且只能有一個全域性入口,通常這個入口放在 ROM(Flash)的 0x0
地址,因此,必須通知編譯器以使其知道這個入口,該工作可通過修改聯結器指令碼來完成。
1. board/crane2410/u-boot.lds: ENTRY(_start) ==> cpu/arm920t/start.S: .globl _start
2. uboot 程式碼區(TEXT_BASE = 0x33F80000)定義在 board/crane2410/config.mk
U-Boot啟動核心的過程可以分為兩個階段,兩個階段的功能如下:
(1)第一階段的功能
? 硬體裝置初始化
? 載入U-Boot第二階段程式碼到RAM空間
? 設定好棧
? 跳轉到第二階段程式碼入口
(2)第二階段的功能
? 初始化本階段使用的硬體裝置
? 檢測系統記憶體對映
? 將核心從Flash讀取到RAM中
? 為核心設定啟動引數
? 呼叫核心
第一階段對應的檔案是cpu/arm920t/start.S和board/samsung/mini2440/lowlevel_init.S。
U-Boot啟動第一階段流程如下:
詳細分析
圖 2.1 U-Boot啟動第一階段流程
根據cpu/arm920t/u-boot.lds中指定的連線方式:
看一下uboot.lds檔案,在board/smdk2410目錄下面,uboot.lds是告訴編譯器這些段改怎麼劃分,GUN編譯過的段,最基本的三個段是RO,RW,ZI,RO表示只讀,對應於具體的指程式碼段,RW是資料段,ZI是歸零段,就是全域性變數的那段。Uboot程式碼這麼多,如何保證start.s會第一個執行,編譯在最開始呢?就是通過uboot.lds連結檔案進行
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
/*OUTPUT_FORMAT("elf32-arm", "elf32-arm", "elf32-arm")*/
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000; //起始地址
. = ALIGN(4); //4位元組對齊
.text : //test指程式碼段,上面3行標識是不佔用任何空間的
{
cpu/arm920t/start.o (.text) //這裡把start.o放在第一位就表示把start.s編
譯時放到最開始,這就是為什麼把uboot燒到起始地址上它肯定執行的是start.s
*(.text)
}
. = ALIGN(4); //前面的 “.” 代表當前值,是計算一個當前的值,是計算上
面佔用的整個空間,再加一個單元就表示它現在的位置
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
.got : { *(.got) }
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
. = ALIGN(4);
__bss_start = .; //bss表示歸零段
.bss : { *(.bss) }
_end = .;
}
第一個連結的是cpu/arm920t/start.o,因此u-boot.bin的入口程式碼在cpu/arm920t/start.o中,其原始碼在cpu/arm920t/start.S中。下面我們來分析cpu/arm920t/start.S的執行。
1. 硬體裝置初始化
(1)設定異常向量
下面程式碼是系統啟動後U-boot上電後執行的第一段程式碼,它是什麼意思?
u-boot對應的第一階段程式碼放在cpu/arm920t/start.S檔案中,入口程式碼如下:.
globl _startglobal /*宣告一個符號可被其它檔案引用,相當於聲明瞭一個全域性變數,.globl與.global相同*/
_start: b start_code /* 復位 */b是不帶返回的跳轉(bl是帶返回的跳轉),意思是無條件直接跳轉到start_code標號出執行程式
ldr pc, _undefined_instruction /* 未定義指令向量 l---dr相當於mov操作*/
ldr pc, _software_interrupt /* 軟體中斷向量 */
ldr pc, _prefetch_abort /* 預取指令異常向量 */
ldr pc, _data_abort /* 資料操作異常向量 */
ldr pc, _not_used /* 未使用 */
ldr pc, _irq /* irq中斷向量 */
ldr pc, _fiq /* fiq中斷向量 */
/* 中斷向量表入口地址 */
_undefined_instruction: .word undefined_instruction /*就是在當前地址,即_undefined_instruction 處存放 undefined_instruction*/
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_not_used: .word not_used
_irq: .word irq
_fiq: .word fiq
word偽操作用於分配一段字記憶體單元(分配的單元都是字對齊的),並用偽操作中的expr初始化
.balignl 16,0xdeadbeef
他們是系統定義的異常,一上電程式跳轉到start_code異常處執行相應的彙編指令,下面定義出的都是不同的異常,比如軟體發生軟中斷時,CPU就會去執行軟中斷的指令,這些異常中斷在CUP中地址是從0開始,每個異常佔4個位元組
ldr pc, _undefined_instruction表示把_undefined_instruction存放的數值存放到pc指標上
_undefined_instruction: .word undefined_instruction表示未定義的這個異常是由.word來定義的,它表示定義一個字,一個32位的數
. word後面的數:表示把該標識的編譯地址寫入當前地址,標識是不佔用任何指令的。把標識存放的數值copy到指標pc上面,那麼標識上存放的值是什麼?
是由.word undefined_instruction來指定的,pc就代表你執行程式碼的地址,她就實現了CPU要做一次跳轉時的工作。
以上程式碼設定了ARM異常向量表,各個異常向量介紹如下:
表 2.1 ARM異常向量表
地址 |
異常 |
進入模式 |
描述 |
0x00000000 |
復位 |
管理模式 |
復位電平有效時,產生復位異常,程式跳轉到復位處理程式處執行 |
0x00000004 |
未定義指令 |
未定義模式 |
遇到不能處理的指令時,產生未定義指令異常 |
0x00000008 |
軟體中斷 |
管理模式 |
執行SWI指令產生,用於使用者模式下的程式呼叫特權操作指令 |
0x0000000c |
預存指令 |
中止模式 |
處理器預取指令的地址不存在,或該地址不允許當前指令訪問,產生指令預取中止異常 |
0x00000010 |
資料操作 |
中止模式 |
處理器資料訪問指令的地址不存在,或該地址不允許當前指令訪問時,產生資料中止異常 |
0x00000014 |
未使用 |
未使用 |
未使用 |
0x00000018 |
IRQ |
IRQ |
外部中斷請求有效,且CPSR中的I位為0時,產生IRQ異常 |
0x0000001c |
FIQ |
FIQ |
快速中斷請求引腳有效,且CPSR中的F位為0時,產生FIQ異常 |
在cpu/arm920t/start.S中還有這些異常對應的異常處理程式。當一個異常產生時,CPU根據異常號在異常向量表中找到對應的異常向量,然後執行異常向量處的跳轉指令,CPU就跳轉到對應的異常處理程式執行。
其中復位異常向量的指令“b start_code”決定了U-Boot啟動後將自動跳轉到標號“start_code”處執行。
(2)CPU進入SVC模式
start_code:
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f /*工作模式位清零 */
orr r0, r0, #0xd3 /*工作模式位設定為“10011”(管理模式),並將中斷禁止位和快中斷禁止位置1 */
msr cpsr, r0
以上程式碼將CPU的工作模式位設定為管理模式,即設定相應的CPSR程式狀態字,並將中斷禁止位和快中斷禁止位置一,從而遮蔽了IRQ和FIQ中斷。
作業系統先註冊一個總的中斷,然後去查是由哪個中斷源產生的中斷,再去查使用者註冊的中斷表,查出來後就去執行使用者定義的使用者中斷處理函式。
(3)設定控制暫存器地址
# if defined(CONFIG_S3C2400) /*關閉看門狗*/
# define pWTCON 0x15300000 /*;看門狗暫存器*/
# define INTMSK 0x14400008 /*;中斷遮蔽暫存器*/
# define CLKDIVN 0x14800014 /*;時鐘分頻暫存器*/
#else /* s3c2410與s3c2440下面4個暫存器地址相同 */
# define pWTCON 0x53000000 /* WATCHDOG控制暫存器地址 */
# define INTMSK 0x4A000008 /* INTMSK暫存器地址 */
# define INTSUBMSK 0x4A00001C /* INTSUBMSK暫存器地址 次級中斷遮蔽暫存器*/
# define CLKDIVN 0x4C000014 /* CLKDIVN暫存器地址 ;時鐘分頻暫存器*/
# endif
對與s3c2440開發板,以上程式碼完成了WATCHDOG,INTMSK,INTSUBMSK,CLKDIVN四個暫存器的地址的設定。各個暫存器地址參見參考文獻[4] 。
(4)關閉看門狗
ldr r0, =pWTCON /*將pwtcon暫存器地址賦給R0*/
mov r1, #0x0 /*r1的內容為0*/
str r1, [r0] /* 看門狗控制器的最低位為0時,看門狗不輸出復位訊號 */
以上程式碼向看門狗控制暫存器寫入0,關閉看門狗。否則在U-Boot啟動過程中,CPU將不斷重啟。
為什麼要關看門狗?
就是防止,不同得兩個以上得CPU,進行喂狗的時間間隔問題:說白了,就是你執行的程式碼如果超出喂狗時間,而你不關狗,就會導致,你程式碼還沒執行完又得去喂狗,就這樣反覆得重啟CPU,那你程式碼永遠也執行不完,所以,得先關看門狗得原因,就是這樣。
關狗---詳細的原因:
關閉看門狗,關閉中斷,所謂的喂狗是每隔一段時間給某個暫存器置位而已,在實際中會專門啟動一個執行緒或程序會專門喂狗,當上層軟體出現故障時就會停止喂狗,
停止喂狗之後,cpu會自動復位,一般都在外部專門有一個看門狗,做一個外部的電路,不在cpu內部使用看門狗,cpu內部的看門狗是復位的cpu
當開發板很複雜時,有好幾個cpu時,就不能完全讓板子復位,但我們通常都讓整個板子復位。看門狗每隔短時間就會喂狗,問題是在兩次喂狗之間的時間間隔內,執行的程式碼的時間是否夠用,兩次喂狗之間的程式碼是否在兩次喂狗的時間延遲之內,如果在延遲之外的話,程式碼還沒改完就又進行喂狗,程式碼永遠也改不完
(5)遮蔽中斷
/*
* mask all IRQs by setting all bits in the INTMR - default
*/
mov r1, #0xffffffff /*遮蔽所有中斷, 某位被置1則對應的中斷被遮蔽 */ /*暫存器中的值*/
ldr r0, =INTMSK /*將管理中斷的暫存器地址賦給ro*/
str r1, [r0] /*將全r1的值賦給ro地址中的內容*/
INTMSK是主中斷遮蔽暫存器,每一位對應SRCPND(中斷源引腳暫存器)中的一位,表明SRCPND相應位代表的中斷請求是否被CPU所處理。
根據參考文獻4,INTMSK暫存器是一個32位的暫存器,每位對應一箇中斷,向其中寫入0xffffffff就將INTMSK暫存器全部位置一,從而遮蔽對應的中斷。
# if defined(CONFIG_S3C2440)
ldr r1, =0x7fff
ldr r0, =INTSUBMSK
str r1, [r0]
# endif
INTSUBMSK每一位對應SUBSRCPND中的一位,表明SUBSRCPND相應位代表的中斷請求是否被CPU所處理。
根據參考文獻4,INTSUBMSK暫存器是一個32位的暫存器,但是隻使用了低15位。向其中寫入0x7fff就是將INTSUBMSK暫存器全部有效位(低15位)置一,從而遮蔽對應的中斷。
遮蔽所有中斷,為什麼要關中斷?
中斷處理中ldr pc是將程式碼的編譯地址放在了指標上,而這段時間還沒有搬移程式碼,所以編譯地址上面沒有這個程式碼,如果進行跳轉就會跳轉到空指標上面
(6)設定MPLLCON,UPLLCON, CLKDIVN
# if defined(CONFIG_S3C2440)
#define MPLLCON 0x4C000004
#define UPLLCON 0x4C000008
ldr r0, =CLKDIVN ;設定時鐘
mov r1, #5
str r1, [r0]
ldr r0, =MPLLCON
ldr r1, =0x7F021
str r1, [r0]
ldr r0, =UPLLCON
ldr r1, =0x38022
str r1, [r0]
# else
/* FCLK:HCLK:PCLK = 1:2:4 */
/* default FCLK is 120 MHz ! */
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]
#endif
CPU上電幾毫秒後,晶振輸出穩定,FCLK=Fin(晶振頻率),CPU開始執行指令。但實際上,FCLK可以高於Fin,為了提高系統時鐘,需要用軟體來啟用PLL。這就需要設定CLKDIVN,MPLLCON,UPLLCON這3個暫存器。
CLKDIVN暫存器用於設定FCLK,HCLK,PCLK三者間的比例,可以根據表2.2來設定。
表 2.2 S3C2440 的CLKDIVN暫存器格式
CLKDIVN |
位 |
說明 |
初始值 |
HDIVN |
[2:1] |
00 : HCLK = FCLK/1. 01 : HCLK = FCLK/2. 10 : HCLK = FCLK/4 (當 CAMDIVN[9] = 0 時) HCLK= FCLK/8 (當 CAMDIVN[9] = 1 時) 11 : HCLK = FCLK/3 (當 CAMDIVN[8] = 0 時) HCLK = FCLK/6 (當 CAMDIVN[8] = 1時) |
00 |
PDIVN |
[0] |
0: PCLK = HCLK/1 1: PCLK = HCLK/2 |
0 |
設定CLKDIVN為5,就將HDIVN設定為二進位制的10,由於CAMDIVN[9]沒有被改變過,取預設值0,因此HCLK = FCLK/4。PDIVN被設定為1,因此PCLK= HCLK/2。因此分頻比FCLK:HCLK:PCLK = 1:4:8 。
MPLLCON暫存器用於設定FCLK與Fin的倍數。MPLLCON的位[19:12]稱為MDIV,位[9:4]稱為PDIV,位[1:0]稱為SDIV。
對於S3C2440,FCLK與Fin的關係如下面公式:
MPLL(FCLK) = (2×m×Fin)/(p× )
其中: m=MDIC+8,p=PDIV+2,s=SDIV
MPLLCON與UPLLCON的值可以根據參考文獻4中“PLL VALUE SELECTION TABLE”設定。該表部分摘錄如下:
表 2.3 推薦PLL值
輸入頻率 |
輸出頻率 |
MDIV |
PDIV |
SDIV |
12.0000MHz |
48.00 MHz |
56(0x38) |
2 |
2 |
12.0000MHz |
405.00 MHz |
127(0x7f) |
2 |
1 |
當mini2440系統主頻設定為405MHZ,USB時鐘頻率設定為48MHZ時,系統可以穩定執行,因此設定MPLLCON與UPLLCON為:
MPLLCON=(0x7f<<12) | (0x02<<4) | (0x01) = 0x7f021
UPLLCON=(0x38<<12) | (0x02<<4) | (0x02) = 0x38022
預設頻率為 FCLK:HCLK:PCLK = 1:2:4,預設 FCLK 的值為 120 MHz,該值為 S3C2410 手冊的推薦值。
設定時鐘分頻,為什麼要設定時鐘?
起始可以不設,系統能不能跑起來和頻率沒有任何關係,頻率的設定是要讓外圍的裝置能承受所設定的頻率,如果頻率過高則會導致cpu操作外圍裝置失敗
說白了:設定頻率,就為了CPU能去操作外圍裝置
(7)關閉MMU,cache ------(也就是做bank的設定)
接著往下看:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit /* ;跳轉並把轉移後面緊接的一條指令地址儲存到連結暫存器LR(R14)中,以此來完成子程式的呼叫*/
#endif
cpu_init_crit這段程式碼在U-Boot正常啟動時才需要執行,若將U-Boot從RAM中啟動則應該註釋掉這段程式碼。
下面分析一下cpu_init_crit到底做了什麼:
320 #ifndef CONFIG_SKIP_LOWLEVEL_INIT
321 cpu_init_crit:
322 /*
323 * 使資料cache與指令cache無效 */
324 */
325 mov r0, #0
326 mcr p15, 0, r0, c7, c7, 0 /* 向c7寫入0將使ICache與DCache無效*/
327 mcr p15, 0, r0, c8, c7, 0 /* 向c8寫入0將使TLB失效 ,協處理器*/
328
329 /*
330 * disable MMU stuff and caches
331 */
332 mrc p15, 0, r0, c1, c0, 0 /* 讀出控制暫存器到r0中 */
333 bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
334 bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
335 orr r0, r0, #0x00000002 @ set bit 2 (A) Align
336 orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
337 mcr p15, 0, r0, c1, c0, 0 /* 儲存r0到控制暫存器 */
338
339 /*
340 * before relocating, we have to setup RAM timing
341 * because memory timing is board-dependend, you will
342 * find a lowlevel_init.S in your board directory.
343 */
344 mov ip, lr
345
346 bl lowlevel_init
347
348 mov lr, ip
349 mov pc, lr
350 #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
程式碼中的c0,c1,c7,c8都是ARM920T的協處理器CP15的暫存器。其中c7是cache控制暫存器,c8是TLB控制暫存器。325~327行程式碼將0寫入c7、c8,使Cache,TLB內容無效。
第332~337行程式碼關閉了MMU。這是通過修改CP15的c1暫存器來實現的,先看CP15的c1暫存器的格式(僅列出程式碼中用到的位):
表 2.3 CP15的c1暫存器格式(部分)
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
. |
. |
V |
相關推薦【ARM-Linux開發】U-Boot啟動過程--詳細版的完全分析---------------------------------------------------------------------------------------------------------------------------------------- (一)U-Boot啟動過程--詳細版的完全分析-------------------------------------------------------------------------------------------------------------------------------------- 【ARM-Linux開發】嵌入式作業系統上的小型資料庫移植SQLite近段時間在學資料庫,因為自身需求,所以注重研究了點嵌入式sqlite資料庫,SQLite,是一款輕型的資料庫,是遵守ACID的關聯式資料庫管理系統,它的設計目標是嵌入式的,而且目前已經在很多嵌入式產品中使用了它,它佔用資源非常的低,在嵌入式裝置中,可能只需要幾百K的記憶體 【ARM-LInux開發】利用scp 遠端上傳下載檔案/資料夾scp [-1246BCpqrv] [-c cipher] [-F ssh_config] [-i identity_file] [-l limit] [-o ssh_option] [-P port] [-S program] [[[email protected]]host1:]file1 【ARM-Linux開發】【CUDA開發】【視訊開發】關於Linux下利用GPU對視訊進行硬體加速轉碼的方案最近一直在研究Linux下利用GPU進行硬體加速轉碼的方案,折騰了很久,至今沒有找到比較理想的硬加速轉碼方案。似乎網上討論這一方案的文章也特別少,這個過程中也進行了各種嘗試,遇到很多具體問題,以下便對之前所作的一些工作做一些總結和分享,省的時間長了自己也忘記了,也希望後來 【ARM-Linux開發】Gstreamer+QT+攝像頭 程式設計總結1,gstreamer開發手冊,gstreamer官網(這些都不用說了吧) 2,gst-launch的用法,這也不用說了吧。(白菜,雞蛋,西紅柿,磚頭,滑鼠……..) lqplayer--基於gstreamer和qt的Linux下的簡單播放器。 實現了基於QT 【ARM-Linux開發】【DSP開發】AM5728介紹AM5728 Sitara Processors 1.介紹 1.1AM572x概述 AM572x是高效能,Sitara器件、以28nm技術整合: 結構設計主要考慮嵌入式應用,包括工業通訊,人機介面(HMI),自動化控制,其它 嵌入式Linux開發——(十三)u-boot常用命令1、幫助命令help 執行help命令可以看到U-Boot中所有命令的作用,如“help bootm”可以用“?”來替代,比如“?Bootm”。 2、下載命令 Boot支援串列埠下載、網路下載,相關命令有:loadb、loads、loadx、loady和tftpboot、nfs。 & 【Java】【Flume】Flume-NG啟動過程源代碼分析(一)code extends fix tar top 依據 oid article gif 從bin/flume 這個shell腳本能夠看到Flume的起始於org.apache.flume.node.Application類,這是flume的main函數所在。 m U-Boot啟動過程分析總結做為嵌入式開發者,U-Boot的啟動是必須要熟悉的。下面分享U-Boot啟動流程。 U-Boot啟動核心的過程可以分為兩個階段,兩個階段的功能如下: 第一階段功能 硬體裝置初始化 載入U-Boot第二階段程式碼到RAM空間 設定好棧 跳轉到第二階段程式碼入口 第二階段功能 初始化本階 U-Boot啟動過程完全分析U-Boot啟動核心的過程可以分為兩個階段,兩個階段的功能如下: (1)第一階段的功能 Ø 硬體裝置初始化 Ø 載入U-Boot第二階段程式碼到RAM空間 Ø 設定好棧 Ø 跳轉到第二階段程式碼入口 (2)第二階段的功能 Ø 初始化 U-Boot啟動過程原始碼分析(2)-第二階段先總述:第一階段cpu/arm920t/start.S和board/smdk2410/lowlevel_init.S進行初始化,再跳到第二階段的入口點lib_arm/board.c中的start_armboot函式。 第二階段start_armboot函式需 Qemu搭建ARM vexpress開發環境(二)----通過u-boot啟動Linux核心Qemu搭建ARM vexpress開發環境(二)----通過u-boot啟動Linux核心 標籤(空格分隔): Qemu ARM Linux 在上文《Qemu搭建ARM vexpress開發環境(一)》中已經簡單講述了通過Qemu模擬直接啟動Linux核心,並掛在SD卡根檔案系統的方法,這種方法是直接啟動 【Android 系統開發】 編譯 Android檔案系統 u-boot 核心 並燒寫到 OK-6410A 開發板上本篇文章中用到的工具原始碼下載 : -- 光碟所含內容 : Android 引導 u-boot 原始碼, Android 核心 原始碼, Android 系統原始碼, 交叉編譯工具鏈;各項操作說明 : -- 編譯環境 : 編譯原始碼 (u-boot, 核心, Android 【Linux開發】Linux及Arm-Linux程式開發筆記(零基礎入門篇)Linux及Arm-Linux程式開發筆記(零基礎入門篇) 目錄 一、Arm-Linux程式開發平臺簡要介紹... 3 1.1程式開發所需系統及開發語言... 3 1.2系統平臺搭建方式... 4 二、Linux開發平臺搭建... 5 2.1安裝虛擬工作站... 【Linux開發】OpenCV在ARM-linux上的移植過程遇到的問題4---共享庫中巢狀庫帶路徑【已解決】【Linux開發】OpenCV在ARM-linux上的移植過程遇到的問題4—共享庫中巢狀庫帶路徑【已解決】 標籤:【Linux開發】 緊接著上一篇,我居然又嘗試了一下編譯opencv,主要是因為由於交叉編譯ARM-Linux,opencv,好像剛開始ma 【imx6ul】U-Boot 2016.03執行過程分析-ARM Cortex-A7uboot組織架構正在朝著linux架構方向發展,不同版本稍有不同,一下以U-Boot 2016.03為例。分析入口:以u-boot.lds(其決定了各個段的排布方式)開始:1、u-boot.lds://設定輸出檔案大小端格式 OUTPUT_FORMAT("elf32-lit 【Linux開發】CCS遠端除錯ARM,AM4378注意一點:CCS也是安裝在Linux主機上的,不是安裝在Windows上的,我在Windows上做出了很多嘗試,但最終也不沒明白究竟要用怎樣的格式去執行在ARM-Linux應用程式,out檔案ELF可 tomcat【Linux環境】安裝與啟動log 下載 linux環境 測試 $path rtu apache startup pro 一、安裝 1、下載tomcat安裝包 2、解壓安裝包 3、配置環境變量 打開~/.bash_profile文件,輸入一下兩句話: export TOMCAT_HOME=/User 【不用敲命令】如何正常啟動雙系統(windows win7 linux ubuntu ),避免任一系統啟動項丟失或啟動卡殼、卡頓!本說明針對正常安裝雙系統後,避免啟動任一系統困難。 【以Win7下安裝Ubuntu為例】 當在win7系統上安裝好Ubuntu系統後,進入系統時,發現可以看到win7的啟動條目,但選擇以後,有時候選擇了能進,多用幾次後就發現不行了,因此很惱火。 操作步驟: |