天天看點

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

轉載:https://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/html/uboot_starts_analysis.html

版本:v1.9

Crifan Li

摘要

本文對Uboot中的Start.S的源碼的幾乎每一行,都進行了詳細的解析

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
本文提供多種格式供:
線上閱讀 HTML HTMLs PDF CHM TXT RTF WEBHELP
下載下傳(7zip壓縮包) HTML HTMLs PDF CHM TXT RTF WEBHELP

HTML版本的線上位址為:

http://www.crifan.com/files/doc/docbook/uboot_starts_analysis/release/html/uboot_starts_analysis.html

有任何意見,建議,送出bug等,都歡迎去讨論組發帖讨論:

http://www.crifan.com/bbs/categories/uboot_starts_analysis/

2013-09-04

修訂曆史
修訂 1.9 2013-09-04 crl
  1. 通過Docbook釋出
  2. 修正了一些瑣碎的筆誤,同時增添了些瑣碎内容
  3. 修正了0xdeadbeef的解釋
  4. 更新了所有的xml:id
修訂 1.6 2011-05-01 crl
  1. 添加彙編學習記錄
  2. 添加了如何檢視C或彙編的源代碼所對應的真正的彙編代碼
  3. 添加Start.S的總結
    1. Start.S的各個部分的總結
    2. Uboot中的記憶體的layout
  4. 更加詳細地解釋了為何ARM9中PC=PC+8
  5. 添加了一些其他的細節的内容
  6. 修正一些拼寫錯誤
修訂 1.0 2011-04-17 crl
  1. 詳細解釋了uboot的start.s中的每行代碼
  2. 添加了相關知識點的詳細解釋

版權 © 2013 Crifan, http://crifan.com

本文章遵從:署名-非商業性使用 2.5 中國大陸(CC BY-NC 2.5)

目錄

正文之前

1. 本文内容

2. 本文目标

3. 代碼來源

4. 閱讀此文所要具有的前提知識

5. 聲明

1. start.S詳解

1.1. 設定CPU模式

1.1.1. globl

1.1.2. _start

1.1.3. ldr

1.1.4. .word

1.1.5. .balignl

1.1.6. _TEXT_BASE _armboot_start

1.1.7. _bss_start _bss_end

1.1.8. FREE_RAM_END FREE_RAM_SIZE

1.1.9. IRQ_STACK_START FIQ_STACK_START

1.1.10. cpsr

1.1.11. bic

1.1.12. orr

1.1.13. msr

1.2. 關閉看門狗

1.2.1. pWTCON INTMOD INTMSK INTSUBMSK CLKDIVN

1.2.2. ldr pWTCON

1.2.3. mov

1.2.4. str

1.3. 關閉中斷

1.3.1. set INTMSK

1.3.2. set INTSUBMSK

1.3.3. set CLKDIVN

1.3.4. bl

1.4. 設定堆棧sp指針

1.4.1. stack_setup

1.4.2. calc stack

1.4.3. bl clock_init

1.4.4. adr

1.4.5. clear_bss

1.4.6. cal armboot size from _armboot_start

1.4.7. cal armboot size from CopyCode2Ram

1.5. 清除bss段

1.5.1. clear_bss

1.5.2. clear css loop

1.5.3. ldr pc

1.5.4. cpu_init_crit

1.5.5. disable MMU

1.5.6. clear bits

1.5.7. bl lowlevel_init

1.6. 異常中斷處理

1.6.1. macros stmia

1.6.2. cal reg value and store

1.6.3. irq_save_user_regs irq_restore_user_regs

1.6.4. exception handlers

1.6.5. Launch

1.6.6. int_return

2. start.S的總結

2.1. start.S各個部分的總結

2.2. Uboot中的記憶體的Layout

3. 相關知識點詳解

3.1. 如何檢視C或彙編的源代碼所對應的真正的彙編代碼

3.2. uboot初始化中,為何要設定CPU為SVC模式而不是設定為其他模式

3.3. 什麼是watchdog + 為何在要系統初始化的時候關閉watchdog

3.3.1. 什麼是watchdog

3.3.2. 為何在要系統初始化的時候關閉watchdog

3.4. 為何ARM7中PC=PC+8

3.4.1. 為何ARM9和ARM7一樣,也是PC=PC+8

3.5. AMR寄存器的别名 + APCS

3.5.1. ARM中的寄存器的别名

3.5.2. 什麼是APCS

3.6. 為何C語言(的函數調用)需要堆棧,而彙編語言卻不需要堆棧

3.6.1. 儲存現場/上下文

3.6.1.1. 什麼叫做上下文context

3.6.2. 傳遞參數

3.6.3. 舉例分析C語言函數調用是如何使用堆棧的

3.7. 關于為何不直接用mov指令,而非要用adr僞指令

3.8. mov指令的操作數的取值範圍到底是多少

3.9. 彙編學習總結記錄

3.9.1. 彙編中的标号=C中的标号

3.9.2. 彙編中的跳轉指令=C中的goto

3.9.3. 彙編中的.globl=C語言中的extern

3.9.4. 彙編中用bl指令和mov pc,lr來實作子函數調用和傳回

3.9.5. 彙編中的對應位置有存儲值的标号 = C語言中的指針變量

3.9.6. 彙編中的ldr+标号,來實作C中的函數調用

3.9.7. 彙編中設定某個寄存器的值或給某個位址指派

參考書目

插圖清單

1.1. LDR指令的文法

1.2. CPSR/SPSR的位域結構

1.3. pWTCON

1.4. INTMOD

1.5. INTMSK

1.6. INTSUBMSK

1.7. CLKDIVN

1.8. WTCON寄存器的位域

1.9. INTMSK寄存器的位域

1.10. INTSUBMSK寄存器的位域

1.11. INTSUBMSK寄存器的位域

1.12. macro的文法

1.13. LDM/STM的文法

1.14. 條件碼的含義

2.1. Uboot中的記憶體的Layout

3.1. AMR7三級流水線

3.2. ARM7三級流水線狀态

3.3. ARM7三級流水線示例

3.4. ARM7三級流水線 vs ARM9五級流水線

3.5. ARM7三級流水線到ARM9五級流水線的映射

3.6. ARM9的五級流水線示例

3.7. ARM9的五級流水線中為何PC=PC+8

3.8. ARM Application Procedure Call Standard (AAPCS)

3.9. 資料處理指令的指令格式

表格清單

1.1. global的文法

1.2. .word的文法

1.3. balignl的文法

1.4. CPSR Bitfield

1.5. CPSR=0xD3的位域及含義

1.6. 控制寄存器1的位域含義

1.7. 時鐘模式

1.8. 關于通路控制位在域通路控制寄存器中的含義

1.9. 關于通路允許(AP)位的含義

3.1. ARM中CPU的模式

3.2. ARM寄存器的别名

3.3. mov指令0xe3a00453的位域含義解析

範例清單

3.1. 彙編中的ldr加标号實作函數調用 示例

3.2.

3.3.

正文之前

目錄

1. 本文内容

2. 本文目标

3. 代碼來源

4. 閱讀此文所要具有的前提知識

5. 聲明

1. 本文内容

此文主要内容就是分析start.S這個彙編檔案的内容,即ARM上電後的最開始那一段的啟動過程。

2. 本文目标

本文的目标是,希望看完此文的讀者,可以達到:

  1. 微觀上,對此start.S的每一行,都有了基本的了解
  2. 宏觀上,對基于ARM核的S3C24X0的CPU的啟動過程,有更加清楚的概念

這樣的目的,是為了讀者看完本文後,再去看其他類似的啟動相關的源碼,能明白需要做什麼事情,然後再看别的系統是如何實作相關的内容的,達到一定程度的觸類旁通。

總體說就是,要做哪些,為何要這麼做,如何實作的,即英語中常說的:

  • do what
  • why do
  • how do

此三方面都清楚了解了,那麼也才能算真正懂了。

3. 代碼來源

所用代碼來自TQ2440官網,天嵌的bbs上下載下傳下來的uboot中的源碼:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\start.S

下載下傳位址為:2010年6月 最新TQ2440CD光牒下載下傳 (Linux核心,WinCE的eboot,uboot均有更新)

4. 閱讀此文所要具有的前提知識

閱讀此文之前,你至少要對TQ2440的闆子有個基本的了解,

以及要了解開發闆初始化的大概要做的事情,比如設定輸入頻率,設定堆棧等等。

另外,至少要有一定的C語言的基礎,這樣更利于了解彙編代碼。

5. 聲明

由于水準有限,難免有誤,歡迎指正:admin (at) crifan.com

歡迎轉載,但請注明作者。

第 1 章 start.S詳解

目錄

1.1. 設定CPU模式

1.1.1. globl

1.1.2. _start

1.1.3. ldr

1.1.4. .word

1.1.5. .balignl

1.1.6. _TEXT_BASE _armboot_start

1.1.7. _bss_start _bss_end

1.1.8. FREE_RAM_END FREE_RAM_SIZE

1.1.9. IRQ_STACK_START FIQ_STACK_START

1.1.10. cpsr

1.1.11. bic

1.1.12. orr

1.1.13. msr

1.2. 關閉看門狗

1.2.1. pWTCON INTMOD INTMSK INTSUBMSK CLKDIVN

1.2.2. ldr pWTCON

1.2.3. mov

1.2.4. str

1.3. 關閉中斷

1.3.1. set INTMSK

1.3.2. set INTSUBMSK

1.3.3. set CLKDIVN

1.3.4. bl

1.4. 設定堆棧sp指針

1.4.1. stack_setup

1.4.2. calc stack

1.4.3. bl clock_init

1.4.4. adr

1.4.5. clear_bss

1.4.6. cal armboot size from _armboot_start

1.4.7. cal armboot size from CopyCode2Ram

1.5. 清除bss段

1.5.1. clear_bss

1.5.2. clear css loop

1.5.3. ldr pc

1.5.4. cpu_init_crit

1.5.5. disable MMU

1.5.6. clear bits

1.5.7. bl lowlevel_init

1.6. 異常中斷處理

1.6.1. macros stmia

1.6.2. cal reg value and store

1.6.3. irq_save_user_regs irq_restore_user_regs

1.6.4. exception handlers

1.6.5. Launch

1.6.6. int_return

摘要

下面将詳細解釋uboot中的start.S中的每一行代碼。詳細到,每個指令的文法和含義,都進行詳細講解,使得此文讀者可以真正搞懂具體的含義,即what。

以及對于一些相關的問題,深入探究為何要這麼做,即why。

對于uboot的start.S,主要做的事情就是系統的各個方面的初始化。

從大的方面分,可以分成這幾個部分:

  • 設定CPU模式
  • 關閉看門狗
  • 關閉中斷
  • 設定堆棧sp指針
  • 清除bss段
  • 異常中斷處理

下面來對start.S進行詳細分析,看看每一個部分,是如何實作的。

1.1. 設定CPU模式

1.1.1. globl

/*
 *  armboot - Startup Code for ARM920 CPU-core
 *
 *  Copyright (c) 2001	Marius Gr鰃er <[email protected]>
 *  Copyright (c) 2002	Alex Z黳ke <[email protected]>
 *  Copyright (c) 2002	Gary Jennejohn <[email protected]>
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <config.h>
#include <version.h>


/*
 *************************************************************************
 *
 * Jump vector table as in table 3.1 in [1]
 *
 *************************************************************************
 */


.globl _start
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

globl是個關鍵字,對應含義為:

http://re-eject.gbadev.org/files/GasARMRef.pdf

表 1.1. global的文法

Directive Description Syntax Example
.global Makes symbol visible to the linker .global symbol .global MyAsmFunc
.globl Same as .global .globl symbol .globl MyOtherAsmFunc

是以,意思很簡單,就是相當于C語言中的Extern,聲明此變量,并且告訴連結器此變量是全局的,外部可以通路

是以,你可以看到

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\u-boot.lds

中,有用到此變量:

ENTRY(_start)

即指定入口為_start,而由下面的_start的含義可以得知,_start就是整個start.S的最開始,即整個uboot的代碼的開始。

1.1.2. _start

_start:	b       reset
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

_start後面加上一個冒号’:’,表示其是一個标号Label,類似于C語言goto後面的标号。

而同時,_start的值,也就是這個代碼的位置了,此處即為代碼的最開始,相對的0的位置。

而此處最開始的相對的0位置,在程式開始運作的時候,如果是從NorFlash啟動,那麼其位址是0,

_stat=0

如果是重新relocate代碼之後,就是我們定義的值了,即,在

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\config.mk

中的:

TEXT_BASE = 0x33D00000

表示是代碼段的基位址,即

_start=TEXT_BASE=0x33D00000

關于标号的文法解釋:

http://sourceware.org/binutils/docs-2.20/as/Labels.html#Labels

A label is written as a symbol immediately followed by a colon `:'. The symbol then represents the current value of the active location counter, and is, for example, a suitable instruction operand. You are warned if you use the same symbol to represent two different locations: the first definition overrides any other definitions.

而_start标号後面的:
b       reset      
就是跳轉到對應的标号為reset的位置。

1.1.3. ldr

ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
ldr指令的文法為:

http://infocenter.arm.com/help/topic/com.arm.doc.dui0206hc/DUI0206HC_rvct_linker_and_utilities_guide.pdf

圖 1.1. LDR指令的文法

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

http://wenku.baidu.com/view/f7cc280102020740be1e9bea.html

LDR指令的格式為:

LDR{條件} 目的寄存器,<存儲器位址>

LDR指令用于從存儲器中将一個32位的字資料傳送到目的寄存器中。該指令通常用于從存儲器中讀取32位的字資料到通用寄存器,然後對資料進行處理。當程式計數器PC作為目的寄存器時,指令從存儲器中讀取的字資料被當作目的位址,進而可以實作程式流程的跳轉。該指令在程式設計中比較常用,且尋址方式靈活多樣,請讀者認真掌握。

指令示例:

LDR R0,[R1] ;将存儲器位址為R1的字資料讀入寄存器R0。

LDR R0,[R1,R2] ;将存儲器位址為R1+R2的字資料讀入寄存器R0。

LDR R0,[R1,#8] ;将存儲器位址為R1+8的字資料讀入寄存器R0。

LDR R0,[R1,R2]! ;将存儲器位址為R1+R2的字資料讀入寄存器R0,并将新位址R1+R2寫入R1。

LDR R0,[R1,#8]! ;将存儲器位址為R1+8的字資料讀入寄存器R0,并将新位址R1+8寫入R1。

LDR R0,[R1],R2 ;将存儲器位址為R1的字資料讀入寄存器R0,并将新位址R1+R2寫入R1。

LDR R0,[R1,R2,LSL#2]! ;将存儲器位址為R1+R2×4的字資料讀入寄存器R0,并将新位址R1+R2×4寫入R1。

LDRR0,[R1],R2,LSL#2 ;将存儲器位址為R1的字資料讀入寄存器R0,并将新位址R1+R2×4寫入R1。”

http://www.pczpg.com/a/2010/0607/11062.html

ARM是RISC結構,資料從記憶體到CPU之間的移動隻能通過L/S指令來完成,也就是ldr/str指令。

比如想把資料從記憶體中某處讀取到寄存器中,隻能使用ldr

比如:

ldr r0, 0x12345678

就是把0x12345678這個位址中的值存放到r0中。

上面那些ldr的作用,以第一個_undefined_instruction為例,就是将位址為_undefined_instruction中的一個word的值,指派給pc。

1.1.4. .word

_undefined_instruction:	.word 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
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

http://blogold.chinaunix.net/u3/115924/showart_2280163.html

.word .word expr {,expr}… 配置設定一段字記憶體單元,并用expr初始化字記憶體單元(32bit)

http://re-eject.gbadev.org/files/GasARMRef.pdf
表 1.2. .word的文法
Directive Description Syntax Example
.word Define word expr (32bit numbers) .word expr {, …} .word 144511, 0x11223

是以上面的含義,以_undefined_instruction為例,就是,此處配置設定了一個word=32bit=4位元組的位址空間,裡面存放的值是undefined_instruction。

而此處_undefined_instruction也就是該位址空間的位址了。用C語言來表達就是:

_undefined_instruction = &undefined_instruction

*_undefined_instruction = undefined_instruction

在後面的代碼,我們可以看到,undefined_instruction也是一個标号,即一個位址值,對應着就是在發生“未定義指令”的時候,系統所要去執行的代碼。

(其他幾個對應的“軟體中斷”,“預取指錯誤”,“資料錯誤”,“未定義”,“(普通)中斷”,“快速中斷”,也是同樣的做法,跳轉到對應的位置執行對應的代碼。)

是以:

ldr pc, 标号1
......
标号1:.word 标号2
......
标号2:
......(具體要執行的代碼)
                      

的意思就是,将位址為标号1中内容載入到pc,而位址為标号1中的内容,正好裝的是标号2。

用C語言表達其實很簡單:

PC = *(标号1) = 标号2

對PC指派,即是實作代碼跳轉,是以整個這段彙編代碼的意思就是:

跳轉到标号2的位置,執行對應的代碼。

1.1.5. .balignl

.balignl 16,0xdeadbeef
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
balignl這個标号的文法及含義:

http://re-eject.gbadev.org/files/GasARMRef.pdf

表 1.3. balignl的文法

Directive Description Syntax Example
.balignl Word align the following code to alignment byte boundary (default=4). Fill skipped words with fill (default=0 or NOP). If the number of bytes skipped is greater than max, then don't align (default=alignment ). .balignl {alignment} {, fill} {, max} .balignl

是以意思就是,接下來的代碼,都要16位元組對齊,不足之處,用0xdeadbeef填充。

其中關于所要填充的内容0xdeadbeef,剛開始沒看懂是啥意思,後來終于搞懂了。

經過(

<[email protected]>

等)多位網友提示和糾正,覺得這樣解釋會更加合理些:

此處0xdeadbeef本身沒有真正的意義,但是很明顯,字面上的意思是,(壞)死的牛肉。

雖然其本身沒有實際意義,但是其是在十六進制下,能表示出來的,為數不多的,可讀的單詞之一了。

另外一個相對常見的是:0xbadc0de,意思是bad code,壞的代碼,注意其中的o是0,因為十六進制中是沒有o的。

這些“單詞”,相對的作用是,使得讀代碼的人,以及在檢視程式運作結果時,容易看懂,便于引起注意。

而關于自己之前,随意杜撰出來的,希望起到搞笑作用,表示good beef(好的牛肉)的0xgoodbeef,實際上,在十六進制下,會出錯的,因為十六進制下沒有o和 g這兩個字母。

1.1.6. _TEXT_BASE _armboot_start

/*
 *************************************************************************
 *
 * Startup Code (reset vector)
 *
 * do important init only if we don't start from memory!
 * relocate armboot to ram
 * setup stack
 * jump to second stage
 *
 *************************************************************************
 */

_TEXT_BASE:
	.word	TEXT_BASE

.globl _armboot_start
_armboot_start:
	.word _start
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處和上面的類似,_TEXT_BASE是一個标号位址,此位址中是一個word類型的變量,變量名是TEXT_BASE,此值見名知意,是text的base,即代碼的基位址,可以在

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\config.mk

中找到其定義:
TEXT_BASE = 0x33D00000      
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

同理,此含義可用C語言表示為:

*(_armboot_start) = _start

1.1.7. _bss_start _bss_end

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
	.word __bss_start

.globl _bss_end
_bss_end:
	.word _end
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

關于_bss_start和_bss_end都隻是兩個标号,對應着此處的位址。

而兩個位址裡面分别存放的值是__bss_start和_end,這兩個的值,根據注釋所說,是定義在開發闆相關的連結腳本裡面的,我們此處的開發闆相關的連結腳本是:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\u-boot.lds

其中可以找到__bss_start和_end的定義:
__bss_start = .;
	.bss : { *(.bss) }
	_end = .;
                      
而關于_bss_start和_bss_end定義為.glogl即全局變量,是因為uboot的其他源碼中要用到這兩個變量,詳情請自己去搜尋源碼。

1.1.8. FREE_RAM_END FREE_RAM_SIZE

.globl FREE_RAM_END
FREE_RAM_END:
	.word	0x0badc0de

.globl FREE_RAM_SIZE
FREE_RAM_SIZE:
	.word	0x0badc0de
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

關于FREE_RAM_END和FREE_RAM_SIZE,這裡隻是兩個标号,之是以也是聲明為全局變量,是因為uboot的源碼中會用到這兩個變量。

但是這裡有點特别的是,這兩個變量,将在本源碼start.S中的後面要用到,而在後面用到這兩個變量之前,uboot的C源碼中,會先去修改這兩個值,具體的邏輯是:

本檔案start.S中,後面有這兩句:

ldr	pc, _start_armboot

_start_armboot:	.word start_armboot
                      

意思很明顯,就是去調用start_armboot函數。

而start_armboot函數是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\lib_arm\board.c

中:
init_fnc_t *init_sequence[] = {
	cpu_init,		/* basic cpu dependent setup */
......
	NULL,
};

void start_armboot (void)
{
	init_fnc_t **init_fnc_ptr;
......

	for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
		if ((*init_fnc_ptr)() != 0) {
			hang ();
		}
	}
......

}
                      

即在start_armboot去調用了cpu_init。

cpu_init函數是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\cpu.c

中:

cpu_init源碼. 

int cpu_init (void)
{
    /*
     * setup up stacks if necessary
     */
#ifdef CONFIG_USE_IRQ
    IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
    FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
    FREE_RAM_END = FIQ_STACK_START - CONFIG_STACKSIZE_FIQ - CONFIG_STACKSIZE;
    FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1;
#else    
    FREE_RAM_END = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4 - CONFIG_STACKSIZE;
    FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1;
#endif
    return 0;
}
                          

在cpu_init中,根據我們的一些定義,比如堆棧大小等等,去修改了IRQ_STACK_START ,FIQ_STACK_START ,FREE_RAM_END和FREE_RAM_SIZE的值。

至于為何這麼修改,後面遇到的時候會具體再解釋。

1.1.9. IRQ_STACK_START FIQ_STACK_START

#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
	.word	0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
	.word 0x0badc0de
#endif
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

同上,IRQ_STACK_START和FIQ_STACK_START,也是在cpu_init中用到了。

不過此處,是隻有當定義了宏CONFIG_USE_IRQ的時候,才用到這兩個變量,其含義也很明顯,

隻有用到了中斷IRQ,才會用到中斷的堆棧,才有中端堆棧的起始位址。

快速中斷FIQ,同理。

1.1.10. cpsr

/*
 * the actual reset code
 */

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0,cpsr
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

CPSR 是目前的程式狀态寄存器(Current Program Status Register),

而 SPSR 是儲存的程式狀态寄存器(Saved Program Status Register)。

具體細節,可參考:

ARM7體系結構

http://www.csie.nctu.edu.tw/~wjtsai/EmbeddedSystemDesign/Ch2-bootloader.pdf

圖 1.2. CPSR/SPSR的位域結構

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
表 1.4. CPSR Bitfield
31 30 29 28 --- 7 6 - 4 3 2 1 說明
N Z C V I F M4 M3 M2 M1 M0
User26 模式
1 FIQ26 模式
1 IRQ26 模式
1 1 SVC26 模式
1 User 模式
1 1 FIQ 模式
1 1 IRQ 模式
1 1 1 SVC 模式
1 1 1 1 ABT 模式
1 1 1 1 UND 模式
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

MRS - Move From Status Register

MRS指令的文法為:

四、程式狀态寄存器通路指令

1、 MRS指令

MRS指令的格式為:

MRS{條件} 通用寄存器,程式狀态寄存器(CPSR或SPSR)

MRS指令用于将程式狀态寄存器的内容傳送到通用寄存器中。該指令一般用在以下兩種情況:

Ⅰ.當需要改變程式狀态寄存器的内容時,可用MRS将程式狀态寄存器的内容讀入通用寄存器,修改後再寫回程式狀态寄存器。

Ⅱ.當在異常處理或程序切換時,需要儲存程式狀态寄存器的值,可先用該指令讀出程式狀态寄存器的值,然後儲存。

指令示例:

MRS R0,CPSR ;傳送CPSR的内容到R0

MRS R0,SPSR ;傳送SPSR的内容到R0”

是以,上述彙編代碼含義為,将CPSR的值賦給R0寄存器。

1.1.11. bic

bic	r0,r0,#0x1f
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
bic指令的文法是:

16、BIC指令

BIC指令的格式為:

BIC{條件}{S} 目的寄存器,操作數1,操作數2

BIC指令用于清除操作數1的某些位,并把結果放置到目的寄存器中。操作數1應是一個寄存器,

操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。操作數2為32位的掩碼,如果在掩碼中設定了某一位,則清除這一位。未設定的掩碼位保持不變。

而0x1f=11111b

是以,此行代碼的含義就是,清除r0的bit[4:0]位。

1.1.12. orr

orr	r0,r0,#0xd3
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
orr指令的文法是:

14、ORR指令

ORR指令的格式為:

ORR{條件}{S} 目的寄存器,操作數1,操作數2

ORR指令用于在兩個操作數上進行邏輯或運算,并把結果放置到目的寄存器中。操作數1應是一個寄存器,操作數2可以是一個寄存器,被移位的寄存器,或一個立即數。該指令常用于設定操作數1的某些位。

指令示例:

ORR R0,R0,#3 ; 該指令設定R0的0、1位,其餘位保持不變。

是以此行彙編代碼的含義為:

而0xd3=1101 0111[4:0]位。

将r0與0xd3算數或運算,然後将結果給r0,即把r0的bit[7:6]和bit[4]和bit[2:0]置為1。

1.1.13. msr

msr	cpsr,r0
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

MSR - Move to Status Register

msr的指令格式是:

四、程式狀态寄存器通路指令

......

2、 MSR指令

MSR指令的格式為:

MSR{條件} 程式狀态寄存器(CPSR或SPSR)_<域>,操作數

MSR指令用于将操作數的内容傳送到程式狀态寄存器的特定域中。其中,操作數可以為通用寄存器或立即數。<域>用于設定程式狀态寄存器中需要操作的位,32位的程式狀态寄存器可分為4個域:

位[31:24]為條件标志位域,用f表示;

位[23:16]為狀态位域,用s表示;

位[15:8]為擴充位域,用x表示;

位[7:0]為控制位域,用c表示;

該指令通常用于恢複或改變程式狀态寄存器的内容,在使用時,一般要在MSR指令中指明将要操作的域。

指令示例:

MSR CPSR,R0 ;傳送R0的内容到CPSR

MSR SPSR,R0 ;傳送R0的内容到SPSR

MSR CPSR_c,R0 ;傳送R0的内容到SPSR,但僅僅修改CPSR中的控制位域

此行彙編代碼含義為,将r0的值賦給CPSR。

是以,上面四行彙編代碼的含義就很清楚了。

先是把CPSR的值放到r0寄存器中,然後清除bit[4:0],然後再或上

0xd3=11   0  10111b      

表 1.5. CPSR=0xD3的位域及含義

CPSR位域 7 6 5 4 3 2 1
位域含義 I F M4 M3 M2 M1 M0
0xD3 1 1 1 1 1
對應含義 關閉中斷IRQ 關閉快速中斷FIQ 設定CPU為SVC模式,這和上面代碼注釋中的“set the cpu to SVC32 mode”,也是一緻的。

關于為何設定CPU為SVC模式,而不是設定為其他模式,請參見本文檔後面的章節:第 3.2 節 “uboot初始化中,為何要設定CPU為SVC模式而不是設定為其他模式”

1.2. 關閉看門狗

1.2.1. pWTCON INTMOD INTMSK INTSUBMSK CLKDIVN

/* turn off the watchdog */
#if defined(CONFIG_S3C2400)
# define pWTCON		0x15300000
# define INTMSK		0x14400008	/* Interupt-Controller base addresses */
# define CLKDIVN	0x14800014	/* clock divisor register */
#elif defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)
# define pWTCON		0x53000000
# define INTMOD		0X4A000004
# define INTMSK		0x4A000008	/* Interupt-Controller base addresses */
# define INTSUBMSK	0x4A00001C
# define CLKDIVN	0x4C000014	/* clock divisor register */
#endif
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

上面幾個宏定義所對應的位址,都可以在對應的datasheet中找到對應的定義:

其中,S3C2410和TQ2440開發闆所用的CPU S3C2440,兩者在這部分的寄存器定義,都是一樣的,是以此處,采用CONFIG_S3C2410所對應的定義。

關于S3C2440相關的軟硬體資料,這個網站提供的很全面:

http://just4you.springnote.com/pages/1052612

其中有S3C2440的CPU的datasheet:

s3c2440a_um_rev014_040712.pdf

其中有對應的寄存器定義:

圖 1.3. pWTCON

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
圖 1.4. INTMOD
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
圖 1.5. INTMSK
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
圖 1.6. INTSUBMSK
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
圖 1.7. CLKDIVN
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
而關于每個寄存器的具體含義,見後面的分析。

1.2.2. ldr pWTCON

#if defined(CONFIG_S3C2400) || defined(CONFIG_S3C2410) || defined(CONFIG_S3C2440)
	ldr     r0, =pWTCON
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

這裡的ldr和前面介紹的ldr指令不是一個意思。

這裡的ldr是僞指令ldr。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
僞指令

僞指令,就是“僞”的指令,是針對“真”的指令而言的。

真的指令就是那些常見的指令,比如上面說的arm的ldr,bic,msr等等指令,是arm體系架構中真正存在的指令,你在arm彙編指令集中找得到對應的含義。

而僞指令是寫出來給彙程式設計式看的,彙程式設計式能看的僞指令具體表示的是啥意思,然後将其翻譯成真正的指令或者進行相應的處理。

僞指令ldr文法和含義:

http://blog.csdn.net/lihaoweiV/archive/2010/11/24/6033003.aspx

另外還有一個就是ldr僞指令,雖然ldr僞指令和ARM的ldr指令很像,但是作用不太一樣。ldr僞指令可以在立即數前加上=,以表示把一個位址寫到某寄存器中,比如:

ldr r0, =0x12345678

這樣,就把0x12345678這個位址寫到r0中了。是以,ldr僞指令和mov是比較相似的。

隻不過mov指令後面的立即數是有限制的,這個立即數,能夠必須由一個8位的二進制數,即屬于0x00-0xFF内的某個值,經過偶數次右移後得到,這樣才是合法資料,而ldr僞指令沒有這個限制。

那為何ldr僞指令的操作數沒有限制呢,那是因為其是僞指令,寫出來的僞指令,最終會被編譯器解釋成為真正的,合法的指令的,一般都是對應的mov指令。

這樣的話,寫彙程式設計式的時候,使用MOV指令是比較麻煩的,因為有些簡單的資料比較容易看出來,有些資料即不容易看出來是否是合法資料。是以,對此,ldr僞指令的出現,就是為了解決這個問題的,你隻管放心用ldr僞指令,不用關心操作數,而寫出的ldr僞指令,編譯器會幫你翻譯成對應的真正的彙編指令的。

而關于編譯器是如何将這些ldr僞指令翻譯成為真正的彙編指令的,我的了解是,其自動會去算出來對應的操作數,是否是合法的mov 的操作數,如果是,就将該ldr僞指令翻譯成mov指令,否則就用别的方式處理,我所觀察到的,其中一種方式就是,單獨申請一個4位元組的空間用于存放操作數,然後用ldr指令實作。

在uboot中,最後make完畢之後,會生産u-boot,

通過:

arm-linux-objdump –d u-boot > dump_u-boot.txt      
就可以把對應的彙編代碼輸出到該txt檔案了,其中就能找到僞指令:
ldr     r0, =0x53000000      
所對應的,真正的彙編代碼:
33d00068:	e3a00453 	mov	r0, #1392508928	; 0x53000000      

是以被翻譯成了mov指令。

而經過我的嘗試,故意将0x53000000改為0x53000010,對應的生産的彙編代碼為:

33d00068:	e59f0408 	ldr	r0, [pc, #1032]	; 33d00478 <fiq+0x58>
......
33d00478:	53000010 	.word	0x53000010
                          
其中可以看到,由于0x53000010不是有效的mov的操作數,沒法找到合适的0x00-0Xff去通過偶數次循環右移而得到,是以隻能換成此處這種方式,即在另外申請一個word的空間用于存放這個值:
33d00478:	53000010 	.word	0x53000010      

然後通過計算出相對目前PC的偏移,得到的位址,用ldr指令去除該位址中的值,即0x53000010,送給r0,比起mov指令,要複雜的多,也多消耗了一個word的空間。

對應地,其他的方式,個人了解,好像也可以通過MVN指令來實作,具體細節,有待進一步探索。

而這裡的:
ldr     r0, =pWTCON      

意思就很清楚了,就是把宏pWTCON的值指派給r0寄存器,即

r0=0x53000000

1.2.3. mov

mov     r1, #0x0
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
mov指令文法:

1、 MOV指令

MOV指令的格式為:

MOV{條件}{S} 目的寄存器,源操作數

MOV指令可完成從另一個寄存器、被移位的寄存器或将一個立即數加載到目的寄存器。其中S選項決定指令的操作是否影響CPSR中條件标志位的值,當沒有S時指令不更新CPSR中條件标志位的值。

指令示例:

MOV R1,R0 ;将寄存器R0的值傳送到寄存器R1

MOV PC,R14 ;将寄存器R14的值傳送到PC,常用于子程式傳回

MOV R1,R0,LSL#3 ;将寄存器R0的值左移3位後傳送到R1

不過對于MOV指令多說一句,那就是,一般可以用類似于:
MOV R0,R0      

的指令來實作NOP操作。

上面這句mov指令很簡單,就是把0x0指派給r1,即

r1=0x0

1.2.4. str

str     r1, [r0]
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
str指令文法:

4、STR指令

STR指令的格式為:

STR{條件} 源寄存器,<存儲器位址>

STR指令用于從源寄存器中将一個32位的字資料傳送到存儲器中。該指令在程式設計中比較常用,且尋址方式靈活多樣,使用方式可參考指令LDR。

指令示例:

STR R0,[R1],#8 ;将R0中的字資料寫入以R1為位址的存儲器中,并

将新位址R1+8寫入R1。

STR R0,[R1,#8] ;将R0中的字資料寫入以R1+8為位址的存儲器中。

是以這句str的作用也很簡單,那就是将r1寄存器的值,傳送到位址值為r0的(存儲器)記憶體中。

用C語言表示就是:

*r0 = r1

是以,上面幾行代碼意思也很清楚:

先是用r0寄存器存pWTCON的值,然後r1=0,再将r1中的0寫入到pWTCON中,其實就是

pWTCON = 0;      

而pWTCON寄存器的具體含義是什麼呢?下面就來了解其詳細含義:

圖 1.8. WTCON寄存器的位域

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

注意到bit[0]是Reset Enable/Disable,而設定為0的話,那就是關閉Watchdog的reset了,是以其他位的配置選項,就更不需要看了。

我們隻需要了解,在此處禁止了看門狗WatchDog(的複位功能),即可。

關于看門狗的作用,以及為何要在系統初始化的時候關閉看門狗,請參見本文檔後面的章節:第 3.3 節 “什麼是watchdog + 為何在要系統初始化的時候關閉watchdog”

1.3. 關閉中斷

1.3.1. set INTMSK

/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMSK
	str	r1, [r0])
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

上面這幾行代碼,和前面的很類似,作用很簡單,就是将INTMSK寄存器設定為0xffffffff,即,将所有的中端都mask了。

關于每一位的定義,其實可以不看的,反正此處都已mask了,不過還是貼出來,以備後用:

圖 1.9. INTMSK寄存器的位域

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處,關于mask這個詞,解釋一下。

mask這個單詞,是面具的意思,而中斷被mask了,就是中斷被掩蓋了,即雖然硬體上中斷發生了,但是此處被屏蔽了,是以從效果上來說,就相當于中斷被禁止了,硬體上即使發生了中斷,CPU也不會去執行對應中斷服務程式ISR了。

關于中斷的内容的詳細解釋,推薦看這個,解釋的很通俗易懂:【轉】ARM9 2410移植之ARM中斷原理, 中斷嵌套的誤區,中斷号的怎麼來的

1.3.2. set INTSUBMSK

# if defined(CONFIG_S3C2410)
	ldr	r1, =0x3ff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# elif defined(CONFIG_S3C2440)
	ldr	r1, =0x7fff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
# endif
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處是将2410的INTSUBMSK設定為0x3ff。

後經HateMath的提醒後,去查證,的确此處設定的0x3ff,是不嚴謹的。

因為,根據2410的datasheet中關于INTSUBMSK的解釋,bit[10:0]共11位,雖然預設reset的每一位都是1,但是此處對應的mask值,應該是11位全為1=0x7ff。

即寫成0x3ff,雖然是可以正常工作的,但是卻不夠嚴謹的。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處CPU是是S3C2440,是以用到0x7fff這段代碼。

其意思也很容易看懂,就是将INTSUBMSK寄存器的值設定為0x7fff。

先貼出對應每一位的含義:

圖 1.10. INTSUBMSK寄存器的位域

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

然後我們再來分析對應的0x7fff是啥含義。

其實也很簡單,意思就是:

0x7fff = bit[14:0]全是1 = 上表中的全部中斷都被屏蔽(mask)。

1.3.3. set CLKDIVN

#if 0
	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
#endif
#endif	/* CONFIG_S3C2400 || CONFIG_S3C2410 || CONFIG_S3C2440 */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處,關于CLKDIVN的具體含義,參見下表:

圖 1.11. INTSUBMSK寄存器的位域

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

而此處代碼被#if 0注釋掉了。

問:為何要注釋掉,難道想要使用其預設的值,即HDIVN和PDIVN上電後,預設值Initial State,都是0,對應的含義為,FCLK:HCLK:PCLK = 1:1:1 ???

答:不是,是因為我們在其他地方會去初始化時鐘,去設定對應的CLKDIVN,詳情參考後面的代碼第 1.4.3 節 “bl clock_init”的部分。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處是結束上面的#ifdef

1.3.4. bl

/*
	 * we do sys-critical inits only at reboot,
	 * not when booting from ram!
	 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_crit
#endif
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

關于bl指令的含義:

b指令,是單純的跳轉指令,即CPU直接跳轉到某位址繼續執行。

而BL是Branch with Link,帶分支的跳轉,而Link指的是Link Register,連結寄存器R14,即lr,是以,bl的含義是,除了包含b指令的單純的跳轉功能,在跳轉之前,還把r15寄存器=PC=cpu位址,指派給r14=lr,然後跳轉到對應位置,等要做的事情執行完畢之後,再用

mov pc, lr

使得cpu再跳轉回來,是以整個邏輯就是調用子程式的意思。

bl的文法為:

2、 BL指令

BL指令的格式為:

BL{條件} 目标位址

BL 是另一個跳轉指令,但跳轉之前,會在寄存器R14中儲存PC的目前内容,是以,可以通過将R14 的内容重新加載到PC中,來傳回到跳轉指令之後的那個指令處執行。該指令是實作子程式調用的一個基本但常用的手段。以下指令:

BL Label ;當程式無條件跳轉到标号Label處執行時,同時将目前的PC值儲存到R14中

對于上面的代碼來說,意思就很清晰了,就是當沒有定義CONFIG_SKIP_LOWLEVEL_INIT的時候,就掉轉到cpu_init_crit的位置,而在後面的代碼cpu_init_crit中,你可以看到最後一行彙編代碼就是

mov pc, lr,

又将PC跳轉回來,是以整個的含義就是,調用子程式cpu_init_crit,等cpu_init_crit執行完畢,再傳回此處繼續執行下面的代碼。

于此對應地b指令,就隻是單純的掉轉到某處繼續執行,而不能再通過mov pc, lr跳轉回來了。

1.4. 設定堆棧sp指針

1.4.1. stack_setup

/* Set up the stack						    */
stack_setup:
	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此句含義是,把位址為_TEXT_BASE的記憶體中的内容給r0,即,将所有的中斷都mask了。

而檢視前面的相關部分的代碼,即:

_TEXT_BASE:
	.word	TEXT_BASE
                      
得知,位址為_TEXT_BASE的記憶體中的内容,就是

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\config.mk

中的:

TEXT_BASE = 0x33D00000

是以,此處即:

r0

= TEXT_BASE

= 0x33D00000

而關于sub指令:

SUB : 減法

(Subtraction)

SUB{條件}{S} <dest>, <op 1>, <op 2>

dest = op_1 - op_2

SUB 用操作數 one 減去操作數 two,把結果放置到目的寄存器中。操作數 1 是一個寄存器,操作數 2 可以是一個寄存器,被移位的寄存器,或一個立即值:

SUB R0, R1, R2 ; R0 = R1 - R2

SUB R0, R1, #256 ; R0 = R1 - 256

SUB R0, R2, R3,LSL#1 ; R0 = R2 - (R3 << 1)

減法可以在有符号和無符号數上進行。

是以對應含義為:

r0 = r0 - #CFG_MALLOC_LEN

r0 = r0 - #CFG_GBL_DATA_SIZE

其中,對應的兩個宏的值是:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\configs\EmbedSky.h

中:
#define CONFIG_64MB_Nand		0		//添加了對64MB Nand Flash支援

/*
 * Size of malloc() pool
 */
#define CFG_MALLOC_LEN				(CFG_ENV_SIZE + 128*1024)
#define CFG_GBL_DATA_SIZE			128	/* size in bytes reserved for initial data */

#if(CONFIG_64MB_Nand == 1)
#define CFG_ENV_SIZE			0xc000	/* Total Size of Environment Sector */
#else
#define CFG_ENV_SIZE			0x20000	/* Total Size of Environment Sector */
#endif
                      

是以,從源碼中的宏定義中可以看出,

CFG_MALLOC_LEN

= (CFG_ENV_SIZE + 128*1024)

= 0x20000 + 128*1024

= 0x40000

= 256*1024

= 256KB

CFG_GBL_DATA_SIZE

= 128

是以,此三行的含義就是算出r0的值:

r0

= (r0 - #CFG_MALLOC_LEN) - #CFG_GBL_DATA_SIZE

= r0 - 0x40000 – 128

= r0 – 0x40080

= 33CBFF80

1.4.2. calc stack

#ifdef CONFIG_USE_IRQ
	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
	sub	sp, r0, #12		/* leave 3 words for abort-stack    */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

如果定義了CONFIG_USE_IRQ,即如果使用中斷的話,那麼再把r0的值減去IRQ和FIQ的堆棧的值,

而對應的宏的值也是在

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\configs\EmbedSky.h

中:
/*-------------------------------------------------------------------
 * Stack sizes
 *
 * The stack sizes are set up in start.S using the settings below
 */
#define CONFIG_STACKSIZE		(128*1024)	/* regular stack */
#ifdef CONFIG_USE_IRQ
#define CONFIG_STACKSIZE_IRQ		(4*1024)	/* IRQ stack */
#define CONFIG_STACKSIZE_FIQ		(4*1024)	/* FIQ stack */
#endif
                      

是以,此時r0的值就是:

#ifdef CONFIG_USE_IRQ

r0

= r0 - #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)

= r0 – (4*1024 + 4*1024)

= r0 – 8*1024

= 33CBFF80 – 8*1024

= 33CBDF80

#endif

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

最後,再減去終止異常所用到的堆棧大小,即12個位元組。

現在r0的值為:

#ifdef CONFIG_USE_IRQ

r0

= r0 – 12

= 33CBDF80 - 12

= 33CBDF74

#else

r0

= r0 – 12

= 33CBFF80 - 12

= 33CBFF74

#endif

然後将r0的值指派給sp,即堆棧指針。

關于:

sp代表stack pointer,堆棧指針;

和後面要提到的ip寄存器:

ip代表instruction pointer,指令指針。

更多詳情參見下面的解釋。

關于ARM的寄存器的别名和相關的APCS,參見本文後面的内容:第 3.5 節 “AMR寄存器的别名 + APCS”

1.4.3. bl clock_init

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

在上面,經過計算,算出了堆棧的位址,然後指派給了sp,此處,接着才去調用函數clock_init去初始化時鐘。

其中此函數是在C檔案:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\boot_init.c

中:
void clock_init(void)
{
...設定系統時鐘clock的相關代碼...
}
                      

看到這裡,讓我想起,關于其他人的關于此start.S代碼解釋中說到的,此處是先去設定好堆棧,即初始化sp指針,然後才去調用C語言的函數clock_init的。

而我們可以看到,前面那行代碼:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_crit
#endif
                      

就不需要先設定好堆棧,再去進行函數調用。

其中cpu_init_crit對應的代碼也在start.S中(詳見後面對應部分的代碼),是用彙編實作的。

而對于C語言,為何就需要堆棧,而彙編卻不需要堆棧的原因,請參見本文後面的内容:第 3.6 節 “為何C語言(的函數調用)需要堆棧,而彙編語言卻不需要堆棧”

1.4.4. adr

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:				/* relocate U-Boot to RAM	    */
	adr	r0, _start		/* r0 <- current position of code   */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
adr指令的文法和含義:

http://blog.mcuol.com/User/cdkfGao/article/8057_1.htm

1、ADR僞指令--- 小範圍的位址讀取

ADR僞指令将基于PC相對偏移的位址值或基于寄存器相對偏移的位址值讀取到寄存器中。

在彙編編譯器編譯源程式時,ADR僞指令被編譯器替換成一條合适的指令。通常,編譯器用一條ADD指令或SUB指令來實作該ADR僞指令的功能,若不能用一條指令實作,則産生錯誤,編譯失敗。

ADR僞指令格式 :ADR{cond} register, expr

位址表達式expr的取值範圍:

當位址值是位元組對齊時,其取指範圍為: +255 ~ 255B;

當位址值是字對齊時,其取指範圍為: -1020 ~ 1020B;

是以,上述:
adr r0, _start      

的意思其實很簡單,就是将_start的位址指派給r0.但是具體實作的方式就有點複雜了,對于用adr指令實作的話,說明_start這個位址,相對目前PC的偏移,應該是很小的,意思就是向前一段後者向後一段去找,肯定能找到_start這個标号位址的,此處,自己通過看代碼也可以看到_start,就是在目前指令的前面,距離很近,編譯後,對應彙編代碼,也可以猜得出,應該是上面所說的,用sub來實作,即目前PC減去某個值,得到_start的值,

參照前面介紹的内容,去:

arm-inux-objdump –d u-boot > dump_u-boot.txt      
然後打開dump_u-boot.txt,可以找到對應的彙編代碼,如下:
33d00000 <_start>:
33d00000:	ea000014 	b	33d00058 <reset>
。。。
33d000a4 <relocate>:
33d000a4:	e24f00ac 	sub	r0, pc, #172	; 0xac
                      

可以看到,這個相對目前PC的距離是0xac=172,細心的讀者可以看到,那條指令的位址減去0xac,卻并不等于_start的值,即

33d000a4 - 33d00000 = 0xa4 != 0xac

而0xac – 0xa4 = 8,

那是因為,由于ARM920T的五級流水線的緣故導緻指令執行那一時刻的PC的值等于該條指令PC的值加上8,即

sub r0, pc, #172中的PC的值是

sub r0, pc, #172

指令位址:33d000a4,再加上8,即33d000a4+8 = 33d000ac,

是以,33d000ac – 0xac,才等于我們看到的33d00000,才是_start的位址。

這個由于流水線導緻的PC的值和目前指令位址不同的現象,就是我們常說的,ARM中,PC=PC+8。

對于為何是PC=PC+8,請參見後面的内容:第 3.4 節 “為何ARM7中PC=PC+8”

對于此處為何不直接用mov指令,卻使用adr指令,請參見後面内容:第 3.7 節 “關于為何不直接用mov指令,而非要用adr僞指令”

對于mov指令的操作數的取值範圍,請參見後面内容:第 3.8 節 “mov指令的操作數的取值範圍到底是多少”

adr	r0, _start      

的僞代碼,被翻譯成實際彙編代碼為:

33d000a4:	e24f00ac 	sub	r0, pc, #172	; 0xac      

其含義就是,通過計算PC+8-172 ⇒ _start的位址,

而_start的位址,即相對代碼段的0位址,是這個位址在運作時刻的值,而當ARM920T加電啟動後,,此處是從Nor Flash啟動,對應的代碼,也是在Nor Flash中,對應的實體位址是0x0,是以,此時_start的值就是0,而不是0x33d00000。

是以,此時:

r0 = 0x0

1.4.5. clear_bss

ldr	r1, _TEXT_BASE		/* test if we run from flash or RAM */
	cmp     r0, r1                  /* don't reloc during debug         */
	beq     clear_bss
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
這裡的_TEXT_BASE的含義,前面已經說過了,那就是:
_TEXT_BASE:
	.word	TEXT_BASE
                          
得知,位址為_TEXT_BASE的記憶體中的内容,就是

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\config.mk

中的:
TEXT_BASE = 0x33D00000      

是以,此處就是

r1 = 0x33D00000

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

含義很簡單,就是比較r0和r1。而

r0 = 0x0

r1 = 0x33D00000

是以不相等

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
是以beq發現兩者不相等,就不會去跳轉到clear_bss,不會去執行對應的将bss段清零的動作了。

1.4.6. cal armboot size from _armboot_start

ldr	r2, _armboot_start
	ldr	r3, _bss_start
	sub	r2, r3, r2		/* r2 <- size of armboot            */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

這兩行代碼意思也很清楚,分别裝載_armboot_start和_bss_start位址中的值,指派給r2和r3

而_armboot_start和_bss_start的值,前面都已經提到過了,就是:

.globl _armboot_start
_armboot_start:
	.word _start

.globl _bss_start
_bss_start:
	.word __bss_start
                      
_TEXT_BASE:
	.word	TEXT_BASE
                          
而其中的_start,是我們uboot的代碼的最開始的位置,而__bss_start的值,是在

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\u-boot.lds

中的:
SECTIONS
{
	. = 0x00000000;

	. = ALIGN(4);
	.text      :
...
	. = ALIGN(4);
	.rodata : { *(.rodata) }

	. = ALIGN(4);
	.data : { *(.data) }

...
	. = ALIGN(4);
	__bss_start = .;
	.bss : { *(.bss) }
	_end = .;
}
                      

是以,可以看出,__bss_start的位置,是bss的start開始位置,同時也是text+rodata+data的結束位置,即代碼段,隻讀資料和已初始化的可寫的資料的最末尾的位置。

其實我們也可以通過前面的方法,objdump出來,看到對應的值:

33d00048 <_bss_start>:
33d00048:	33d339d4 	.word	0x33d339d4
                      
是0x33d339d4。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
注意

【總結】

r2 = _start = 0x33d00000

r3 =__bss_start = 0x33d339d4

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處的意思就很清楚了,就是r2 = r3-r2,計算出

text + rodata + data

的大小,即整個需要載入的資料量是多少,用于下面的函數去拷貝這麼多的資料到對應的記憶體的位置。

這裡的實際的值是

r2

= r3 –r2

= 0x33d339d4 - 0x33d00000

= 0x000339d4

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
注意

【總結】

到此刻位置,假定是從Nor Flash啟動的話:

r0 = 0x0 = 我們代碼此刻所在的位置

r1 = 0x33D00000 = 我們想要把我們的代碼放到哪裡

r2 = 0x000339d4 = 對應的代碼的大小(此處的代碼 = text + rodata + data)

1.4.7. cal armboot size from CopyCode2Ram

#if 1
	bl  CopyCode2Ram		/* r0: source, r1: dest, r2: size */
#else
	add	r2, r0, r2		/* r2 <- source end address         */

copy_loop:
	ldmia	r0!, {r3-r10}		/* copy from source address [r0]    */
	stmia	r1!, {r3-r10}		/* copy to   target address [r1]    */
	cmp	r0, r2			/* until source end addreee [r2]    */
	ble	copy_loop
#endif
#endif	/* CONFIG_SKIP_RELOCATE_UBOOT */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處,代碼很簡單,隻是注釋掉了原先的那些代碼,而單純的隻是去調用CopyCode2Ram這個函數。

CopyCode2Ram函數,前面也提到過了,是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\boot_init.c

中:
int CopyCode2Ram(unsigned long start_addr, unsigned char *buf, int size)
{
	unsigned int *pdwDest;
	unsigned int *pdwSrc;
	int i;

	if (bBootFrmNORFlash())
	{
		pdwDest = (unsigned int *)buf;
		pdwSrc  = (unsigned int *)start_addr;
		/* 從 NOR Flash啟動 */
		for (i = 0; i < size / 4; i++)
		{
			pdwDest[i] = pdwSrc[i];
		}
		return 0;
	}
	else
	{
		/* 初始化NAND Flash */
		nand_init_ll();

		/* 從 NAND Flash啟動 */
		if (NF_ReadID() == 0x76 )
			nand_read_ll(buf, start_addr, (size + NAND_BLOCK_MASK)&~(NAND_BLOCK_MASK));
		else
			nand_read_ll_lp(buf, start_addr, (size + NAND_BLOCK_MASK_LP)&~(NAND_BLOCK_MASK_LP));
		return 0;
	}
}
                      

可以看到,其有三個參數,start_addr,*buf和size,這三個參數,分别正好對應着我們剛才所總結的r0,r1和r2.

這些寄存器和參數的對應關系,也是APSC中定義的:

實際參數

APCS 沒有定義記錄、數組、和類似的格局。這樣語言可以自由的定義如何進行這些活動。但是,如果你自己的實作實際上不符合 APCS 的精神,那麼将不允許來自你的編譯器的代碼與來自其他編譯器的代碼連接配接在一起。典型的,使用 C 語言的慣例。

  • 前4個整數實參(或者更少!)被裝載到 a1 - a4
  • 前 4 個浮點實參(或者更少!)被裝載到 f0 - f3
  • 其他任何實參(如果有的話)存儲在記憶體中,用進入函數時緊接在 sp 的值上面的字來指向。換句話說,其餘的參數被壓入棧頂。是以要想簡單。最好定義接受 4 個或更少的參數的函數

上面說的a1-a4,就是寄存器r0-r3。

而CopyCode2Ram函數的邏輯也很清晰,就是先去判斷是從Nor Flash啟動還是從Nand Flash啟動,然後決定從哪裡拷貝所需要的代碼到對應的目标位址中。

1.5. 清除bss段

1.5.1. clear_bss

clear_bss:
	ldr	r0, _bss_start		/* find start of bss segment        */
	ldr	r1, _bss_end		/* stop here                        */
	mov 	r2, #0x00000000		/* clear                            */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處的_bss_start是:
.globl _bss_start
_bss_start:
	.word __bss_start
                      
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
而_bss_end,是:
.globl _bss_end
_bss_end:
	.word _end
                      
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
對應的,__bss_start和_end,都在前面提到過的那個連結腳本裡面:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\u-boot.lds

中的:
__bss_start = .;
	.bss : { *(.bss) }
	_end = .;
                      
即bss段的起始位址和結束位址。

1.5.2. clear css loop

clbss_l:str	r2, [r0]		/* clear loop...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此段代碼含義也很清晰,那就是,

先将r2,即0x0,存到位址為r0的記憶體中去,然後r0位址加上4,比較r0位址和r1位址,即比較目前位址是否到了bss段的結束位置,如果le,little or equal,小于或等于,那麼就跳到clbss_l,即接着這幾個步驟,直到位址超過了bss的_end位置,即實作了将整個bss段,都清零。

1.5.3. ldr pc

#if 0
	/* try doing this stuff after the relocation */
	ldr     r0, =pWTCON
	mov     r1, #0x0
	str     r1, [r0]

	/*
	 * mask all IRQs by setting all bits in the INTMR - default
	 */
	mov	r1, #0xffffffff
	ldr	r0, =INTMR
	str	r1, [r0]

	/* FCLK:HCLK:PCLK = 1:2:4 */
	/* default FCLK is 120 MHz ! */
	ldr	r0, =CLKDIVN
	mov	r1, #3
	str	r1, [r0]
	/* END stuff after relocation */
#endif

	ldr	pc, _start_armboot

_start_armboot:	.word start_armboot
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處忽略已經注釋掉的代碼
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

最後的那兩行,意思也很簡單,那就是将位址為_start_armboot中的内容,即

start_armboot,指派給PC,即調用start_armboot函數。

至此,彙編語言的start.S的整個工作,就完成了。

而start_armboot函數,在C檔案中:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\EmbedSky.c

中:
void start_armboot (void)
{
    ......
}
                      

這就是傳說中的,調用第二層次,即C語言級别的初始化了,去初始化各個裝置了。

其中包括了CPU,記憶體等,以及序列槽,正常初始化後,就可以從序列槽看到uboot的列印資訊了。

1.5.4. cpu_init_crit

/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
cpu_init_crit:
	/*
	 * flush v4 I/D caches
	 */
	mov	r0, #0
	mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */
	mcr	p15, 0, r0, c8, c7, 0	/* flush v4 TLB */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
關于mcr的來龍去脈:

http://apps.hi.baidu.com/share/detail/32319228

ARM 微處理器可支援多達 16 個協處理器,用于各種協處理操作,在程式執行的過程中,每個協處理器隻執行針對自身的協處理指令,忽略 ARM 處理器和其他協處理器的指令。ARM 的協處理器指令主要用于 ARM 處理器初始化 ARM 協處理器的資料處理操作,以及在ARM 處理器的寄存器和協處理器的寄存器之間傳送資料,和在 ARM 協處理器的寄存器和存儲器之間傳送資料。 ARM 協處理器指令包括以下 5 條:

  1. CDP 協處理器數操作指令
  2. LDC 協處理器資料加載指令
  3. STC 協處理器資料存儲指令
  4. MCR ARM 處理器寄存器到協處理器寄存器的資料傳送指令
  5. MRC 協處理器寄存器到ARM 處理器寄存器的資料傳送指令

......

CP15系統控制協處理器

CP15 —系統控制協處理器 (the system control coprocessor)他通過協處理器指令MCR和MRC提供具體的寄存器來配置和控制caches、MMU、保護系統、配置時鐘模式(在bootloader時鐘初始化用到)

CP15的寄存器隻能被MRC和MCR(Move to Coprocessor from ARM Register )指令通路

一些要說明的内容,見下::

http://infocenter.arm.com/help/topic/com.arm.doc.ddi0151c/ARM920T_TRM1_S.pdf

you can only access CP15 registers with MRC and MCR instructions in a privileged mode. The assembler for these instructions is:

MCR/MRC{cond} P15,opcode_1,Rd,CRn,CRm,opcode_2

The CRn field of MRC and MCR

instructions specifies the coprocessor register to access. The CRm field and opcode_2 fields specify a particular action when addressing registers. The L bit distinguishes between an MRC (L=1) and an MCR (L=0).

Note:

Attempting to read from a nonreadable register, or to write to a nonwritable register causes unpredictable results.

The opcode_1, opcode_2, and CRm fields should be zero, except when the values specified are used to select the desired operations, in all instructions that access CP15.

Using other values results in unpredictable behavior

CP15有很多個寄存器,分别叫做寄存器0(Register 0),到寄存器15(Register 15),

每個寄存器分别控制不同的功能,而且有的是隻讀,有的是隻寫,有的是可讀寫。

而且這些寄存器的含義,随着版本ARM核心版本變化而不斷擴充,詳情請參考:Processor setup via co-processor 15 and about co-processors

其中,根據我們此處關心的内容,摘錄部分内容如下:

ARM 710
  • Register 7 - IDC flush (write only)

    Any data written to this location will cause the IDC (Instruction/Data cache) to be flushed.

......

StrongARM SA110

......

  • Register 7 - Cache control (write only)

    Any data written to this location will cause the selected cache to be flushed.

    The OPC_2 and CRm co-processor fields select which cache

    operation should occur:

    Function OPC_2 CRm Data

    Flush I + D %0000 %0111 -

    Flush I %0000 %0101 -

    Flush D %0000 %0110 -

    Flush D single %0001 %0110 Virtual address

    Clean D entry %0001 %1010 Virtual address

    Drain write buf. %0100 %1010 -

  • Register 8 - TLB operations (write only)

    Any data written to this location will cause the selected TLB flush operation.

    The OPC_2 and CRm co-processor fields select which cache

    operation should occur:

    Function OPC_2 CRm Data

    Flush I + D %0000 %0111 -

    Flush I %0000 %0101 -

    Flush D %0000 %0110 -

    Flush D single %0001 %0110 Virtual address”

而MCR的詳細的文法為:

MCR指令

MCR指令将ARM處理器的寄存器中的資料傳送到協處理器寄存器中。如果協處理器不能成功地執行該操作,将産生未定義的指令異常中斷。

指令文法格式

MCR{<cond>} <p>,< opcode_1>,<Rd>,<CRn>,<CRm>{,<opcode_2>}

MCR{<cond>} p15,0,<Rd>,<CRn>,<CRm>{,<opcode_2>}

其中

  • <cond>

    指令執行的條件碼.當<cond>忽略時指令為無條件執行。

  • <opcode_1>

    協處理器将執行的操作的操作碼。對于CP15協處理器來說,<opcode_1>永遠為0b000,當<opcode_1>不為0b000時,該指令操作結果不可預知。

  • <Rd>

    作為源寄存器的ARM寄存器,其值将被傳送到協處理器寄存器中

  • <CRn>

    作為目标寄存器的協處理器寄存器,其編号可能是C0,C1,…,C15。

<CRm>和<opcode_2>兩者組合決定對協處理器寄存器進行所需要的操作,如果沒有指定,則将為<CRm>為C0,opcode_2為0
對照上面的那行代碼:
mcr	p15, 0, r0, c7, c7, 0	/* flush v3/v4 cache */      

可以看出,其中

rd為r0=0

CRn為C7

CRm為C7

對于這行代碼的作用,以此按照文法,來一點點解釋如下:

首先,mcr做的事情,其實很簡單,就是“ARM處理器的寄存器中的資料傳送到協處理器寄存器中”,

此處即是,将ARM的寄存器r0中的資料,此時r0=0,是以就是把0這個資料,傳送到協處理器CP15中。

而對應就是寫入到“<CRn>”這個“目标寄存器的協處理器寄存器”,此處CRn為C7,即将0寫入到寄存器7(Register 7)中去。

而上面關于Register 7的含義中也說了,“Any data written to this location will cause the selected cache to be flushed”,即你往這個寄存器7中寫入任何資料,都會導緻對應的緩存被清空。而到底那個緩存被清空呢,即我們這行指令

mcr	p15, 0, r0, c7, c7, 0      

起了什麼作用呢

那是由“<CRm>和<opcode_2>兩者組合決定”的。

而此處CRm為C7,opcode_2為0,而對于C7和0的組合的作用,參見上面的那個表中Register 7中的Flash I+D那一行,

當opcode_2為0,CRm為0111=7,就是我們要找的,其作用是“Flush I + D”,即清空指令緩存I Cache和資料緩存D Cache。

根據該表,同理,如果是opcode_2=0,而CRm=0101b=5,那麼對應的就是去“Flush I”,即隻清除指令緩存I Cache了。

而對應的指令也就是

mcr	p15, 0, r0, c7, c5, 0      
了。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此注釋說此行代碼的作用是,清理v3或v4的緩存

其中v4,我們很好了解,因為我們此處的CPU是ARM920T的核心,是屬于ARM V4的,而為何又說,也可以清除v3的cache呢?

那是因為,本身這些寄存器位域的定義,都是向下相容的,參見上面引用的内容,也寫到了:

ARM 710
  • Register 7 - IDC flush (write only)

    Any data written to this location will cause the IDC (Instruction/Data cache) to be flushed.

即,對于ARM7的話,你寫同樣的這行代碼
mcr	p15, 0, r0, c7, c7, 0      
也還是向register 7中寫入了資料0,這也同樣滿足了其所說的“Any data written to this location”,也會産生同樣的效果“cause the IDC (Instruction/Data cache) to be flushed”。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

同理,可以看出此行是去操作寄存器8,而對應的各個參數為:

rd為r0=0

CRn為C8

CRm為C7

opcode_2為0

對照寄存器8的表:

  • Register 8 - TLB operations (write only)

    Any data written to this location will cause the selected TLB flush operation.

    The OPC_2 and CRm co-processor fields select which cache

    operation should occur:

    Function OPC_2 CRm Data

    Flush I + D %0000 %0111 -

    Flush I %0000 %0101 -

    Flush D %0000 %0110 -

    Flush D single %0001 %0110 Virtual address”

其含義為:

向寄存器8中寫入資料,會導緻對應的TLB被清空。具體是哪個TLB,由opcode_2和CRm組合決定,

此處opcode_2為0,CRm為7=0111b,是以對應的作用是“Flush I + D”,即清空指令和資料的TLB。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
提示
上述兩行代碼,其實都可以ARM的官方網站上面找到:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdcfejb.html
Function Rd Instruction
Invalidate ICache and DCache SBZ MCR p15,0,Rd,c7,c7,0
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdifbjc.html
Function Rd Instruction
Invalidate TLB(s) SBZ MCR p15,0,Rd,c8,c7,0

1.5.5. disable MMU

/*
	 * disable MMU stuff and caches
	 */
	mrc	p15, 0, r0, c1, c0, 0
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處,對應的值為:

rd為r0=0

CRn為C1

CRm為C0

opcode_2為0

即,此行代碼是将r0的值,即0,寫入到CP15的寄存器1中。

寄存器1的相關的定義為:

http://www.heyrick.co.uk/assembler/coprocmnd.html

StrongARM SA110

  • Register 1 - Control (read/write)

    All values set to 0 at power-up.

    • Bit 0 - On-chip MMU turned off (0) or on (1)
    • Bit 1 - Address alignment fault disabled (0) or enabled (1)
    • Bit 2 - Data cache turned off (0) or on (1)
    • Bit 3 - Write buffer turned off (0) or on (1)
    • Bit 7 - Little-endian operation if 0, big-endian if 1
    • Bit 8 - System bit - controls the MMU permission system
    • Bit 9 - ROM bit - controls the MMU permission system
    • Bit 12 - Instruction cache turned off (0) or on (1)”
是以,對應内容就是,向bit[CRm]中寫入opcode_2,即向bit[0]寫入0,對應的作用為“On-chip MMU turned off”,即關閉MMU。

1.5.6. clear bits

bic	r0, r0, #0x00002300	@ clear bits 13, 9:8 (--V- --RS)
	bic	r0, r0, #0x00000087	@ clear bits 7, 2:0 (B--- -CAM)
	orr	r0, r0, #0x00000002	@ set bit 2 (A) Align
	orr	r0, r0, #0x00001000	@ set bit 12 (I) I-Cache
	mcr	p15, 0, r0, c1, c0, 0
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處幾行代碼,注釋中寫的也很清楚了,就是去清楚對應的位和設定對應的位,具體位域的含義見下:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0184b/Chdifbjc.html

表 1.6. 控制寄存器1的位域含義

Register bits Name Function Value
31 iA bit Asynchronous clock select See Table 2.11
30 nF bit notFastBus select See Table 2.11
29:15 - Reserved

Read = Unpredictable

Write = Should be zero

14 RR bit Round robin replacement

0 = Random replacement

1 = Round-robin replacement

13 V bit Base location of exception registers

0 = Low addresses = 0x00000000

1 = High addresses = 0xFFFF0000

12 I bit ICache enable

0 = ICache disabled

1 = ICache enabled

11:10 - Reserved

Read = 00

Write = 00

9 R bit ROM protection This bit modifies the MMU protection system. See Domain access control
8 S bit System protection This bit modifies the MMU protection system. See Domain access control
7 B bit Endianness

0 = Little-endian operation

1 = Big-endian operation

6:3 - Reserved

Read = 1111

Write = 1111

2 C bit DCache enable

0 = DCache disabled

1 = DCache enabled

1 A bit Alignment fault enable

Data address alignment fault checking

0 = Fault checking disabled

1 = Fault checking enabled

M bit MMU enable

0 = MMU disabled

1 = MMU enabled

表 1.7. 時鐘模式
Clocking mode iA nF
FastBus mode
Synchronous 1
Reserved 1
Asynchronous 1 1

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0151c/I273867.html

Domain access control

表 1.8. 關于通路控制位在域通路控制寄存器中的含義

Value Meaning Description
00 No access Any access generates a domain fault
01 Client Accesses are checked against the access permission bits in the section or page descriptor
10 Reserved Reserved. Currently behaves like the no access mode
11 Manager Accesses are not checked against the access permission bits so a permission fault cannot be generated

表 1.9 “關于通路允許(AP)位的含義”shows how to interpret the Access Permission (AP) bits and how their interpretation is dependent on the S and R bits (control register bits 8 and 9)

表 1.9. 關于通路允許(AP)位的含義

AP S R Supervisor permissions User permissions Description
00 No access No access Any access generates a permission fault
00 1 Read-only No access Only Supervisor read permitted
00 1 Read-only Read-only Any write generates a permission fault
00 1 1 Reserved - -
01 x x Read/write No access Access allowed only in Supervisor mode
10 x x Read/write Read-only Writes in User mode cause permission fault
11 x x Read/write Read/write All access types permitted in both modes
xx 1 1 Reserved -
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此行作用是:
  1. 清除bit[13]

    Base location of exception register(異常寄存器基位址)

    0 = Low address = 0x0000 0000

  2. 清除bit[9]和bit[8]

    此處不是很懂,待後續深入了解。

    目前的了解是:

    不論是Supervisor還是user,誰都不能通路,否則就出現權限錯誤“Any access generates a permission fault”

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此行作用是:
  1. 清除bit[7]

    使用little endian

  2. 清除bit[2-0]

    DCache disabled,關閉Dcache;

    Alignment Fault checking disabled,關閉位址對齊的錯誤檢查;

    MMU disabled,關閉MMU。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此行作用是:
  1. 設定bit[1]

    “Enable Data address alignment fault checking”打開資料位址對齊的錯誤檢查,即如果資料位址為非法(奇數?)位址,就報錯。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此行作用是:
  1. 設定bit[12]

    開啟指令緩存I cache。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
mcr指令,将剛才設定的r0的值,再寫入到寄存器1中。

1.5.7. bl lowlevel_init

/*
	 * before relocating, we have to setup RAM timing
	 * because memory timing is board-dependend, you will
	 * find a lowlevel_init.S in your board directory.
	 */
	mov	ip, lr
	bl	lowlevel_init
	mov	lr, ip
	mov	pc, lr
#endif /* CONFIG_SKIP_LOWLEVEL_INIT */
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

将lr的值給ip,即指令指針r12,此處之是以要儲存一下lr是因為此處是在子函數cpu_init_crit中,lr已經儲存了待會用于傳回主函數的位址,即上次調用時候的pc的值,而此處如果在子函數cpu_init_crit中繼續調用其他子函數lowlevel_init,而不儲存lr的話,那麼調用完lowlevel_init傳回來時候,就丢失了cpu_init_crit要傳回的位置。

說白了就是,每次你要調用函數之前,你自己要確定是否已經正确儲存了lr的值,要保證函數調用完畢後,也能正常傳回。當然,如果你此處根本不需要傳回,那麼就不用去儲存lr的值了。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
典型的子函數調用,通過将lr的值指派給pc,實作函數調用完成後而傳回的。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
這裡,其是和前面的代碼:
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
	bl	cpu_init_crit
#endif
                      
是對應的。

1.6. 異常中斷處理

摘要

1.6.1. macros stmia

/*
 *************************************************************************
 *
 * Interrupt handling
 *
 *************************************************************************
 */

@
@ IRQ stack frame.
@
#define S_FRAME_SIZE	72

#define S_OLD_R0	68
#define S_PSR		64
#define S_PC		60
#define S_LR		56
#define S_SP		52

#define S_IP		48
#define S_FP		44
#define S_R10		40
#define S_R9		36
#define S_R8		32
#define S_R7		28
#define S_R6		24
#define S_R5		20
#define S_R4		16
#define S_R3		12
#define S_R2		8
#define S_R1		4
#define S_R0		0

#define MODE_SVC 0x13
#define I_BIT	 0x80

/*
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */
	.macro	bad_save_user_regs

	sub	sp, sp, #S_FRAME_SIZE
	stmia	sp, {r0 - r12}			@ Calling r0-r12
	ldr	r2, _armboot_start    
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處很簡單,隻是一些宏定義而已。

後面用到的時候再解釋。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

.macro和後面的.endm相對應,其文法是:

圖 1.12. macro的文法

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
是以,此處就相當于一個無參數的宏bad_save_user_regs,也就相當于一個函數了。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

sp

= sp- S_FRAME_SIZE

= sp - 72

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

stmia的文法為:

圖 1.13. LDM/STM的文法

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

其中,條件域的具體含義如下:

圖 1.14. 條件碼的含義

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
更具體的含義:

六、批量資料加載/存儲指令ARM微處理器所支援批量資料加載/存儲指令可以一次在一片連續的存儲器單元和多個寄存器之間傳送資料,批量加載指令用于将一片連續的存儲器中的資料傳送到多個寄存器,批量資料存儲指令則完成相反的操作。常用的加載存儲指令如下:

LDM(或STM)指令

LDM(或STM)指令的格式為:

LDM(或STM){條件}{類型} 基址寄存器{!},寄存器清單{∧}

LDM(或STM)指令用于從由基址寄存器所訓示的一片連續存儲器到寄存器清單所訓示的多個寄存器之間傳送資料,該指令的常見用途是将多個寄存器的内容入棧或出棧。

其中,{類型}為以下幾種情況:

IA 每次傳送後位址加1;

IB 每次傳送前位址加1;

DA 每次傳送後位址減1;

DB 每次傳送前位址減1;

FD 滿遞減堆棧;

ED 空遞減堆棧;

FA 滿遞增堆棧;

EA 空遞增堆棧;

{!}為可選字尾,若選用該字尾,則當資料傳送完畢之後,将最後的位址寫入基址寄存器,否則基址寄存器的内容不改變。

基址寄存器不允許為R15,寄存器清單可以為R0~R15的任意組合。

{∧}為可選字尾,當指令為LDM且寄存器清單中包含R15,選用該字尾時表示:除了正常的資料傳送之外,還将SPSR複制到CPSR。同時,該字尾還表示傳入或傳出的是使用者模式下的寄存器,而不是目前模式下的寄存器。

指令示例:

STMFD R13!,{R0,R4-R12,LR} ;将寄存器清單中的寄存器(R0,R4到

R12,LR)存入堆棧。

LDMFD R13!,{R0,R4-R12,PC} ;将堆棧内容恢複到寄存器(R0,R4到

R12,LR)。

是以,此行的含義是,

将r0到r12的值,一個個地傳送到對應的位址上,基位址是sp的值,傳完一個,sp的值加4,一直到傳送完為止。

此處,可見,前面那行代碼:

sp = sp - 72

就是為此處傳送r0到r12,共13個寄存器,位址空間需要13*4=72個位元組,

即前面sp減去72,就是為了騰出空間,留此處将r0到r12的值,放到對應的位置的。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處的含義就是,将_armboot_start中的值,參考前面内容,即為_start,

而_start的值:

從 Nor Flash啟動時:_stat=0

relocate代碼之後為:_start=TEXT_BASE=0x33D00000

此處是已經relocate代碼了,是以應該了解為後者,即_start=0x33D00000

是以:

r2=0x33D00000

1.6.2. cal reg value and store

sub	r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
	sub	r2, r2, #(CFG_GBL_DATA_SIZE+8)  @ set base 2 words into abort stack
	ldmia	r2, {r2 - r3}			@ get pc, cpsr
	add	r0, sp, #S_FRAME_SIZE		@ restore sp_SVC
	add	r5, sp, #S_SP
	mov	r1, lr
	stmia	r5, {r0 - r3}			@ save sp_SVC, lr_SVC, pc, cpsr
	mov	r0, sp
	.endm
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處:

r2

= r2 - ( CONFIG_STACKSIZE+CFG_MALLOC_LEN)

= r2 – (128*1024 + 256*1024)

= 0x33D00000 - 384KB

= 0x33CA0000

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處:

r2

= r2 - (CFG_GBL_DATA_SIZE + 8)

= 0x33CA0000 – (128 + 8)

= 0x33C9FF78

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
分别将位址為r2和r2+4的内容,即位址為0x33C9FF78和0x33C9FF7C中的内容,load載入給r2和r3寄存器。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
将sp的值,加上72,送給r0
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
前面的定義是:
#define S_SP		52      
是以此處就是将sp的值,加上52,送給r5
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
将lr給r1
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
然後将r0到r3中的内容,存儲到位址為r5-r5+12中的位置去。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
将sp再指派給r0
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
結束宏bad_save_user_regs

此處雖然每行代碼基本看懂了,但是到底此bad_save_user_regs函數是做什麼的,還是不太清楚,有待以後慢慢深入了解。

1.6.3. irq_save_user_regs irq_restore_user_regs

.macro	irq_save_user_regs
	sub	sp, sp, #S_FRAME_SIZE
	stmia	sp, {r0 - r12}			@ Calling r0-r12
	add     r8, sp, #S_PC
	stmdb   r8, {sp, lr}^                   @ Calling SP, LR
	str     lr, [r8, #0]                    @ Save calling PC
	mrs     r6, spsr
	str     r6, [r8, #4]                    @ Save CPSR
	str     r0, [r8, #8]                    @ Save OLD_R0
	mov	r0, sp
	.endm

	.macro	irq_restore_user_regs
	ldmia	sp, {r0 - lr}^			@ Calling r0 - lr
	mov	r0, r0
	ldr	lr, [sp, #S_PC]			@ Get PC
	add	sp, sp, #S_FRAME_SIZE
	subs	pc, lr, #4			@ return & move spsr_svc into cpsr
	.endm

	.macro get_bad_stack
	ldr	r13, _armboot_start		@ setup our mode stack
	sub	r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
	sub	r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack
	str	lr, [r13]			@ save caller lr / spsr
	mrs	lr, spsr
	str     lr, [r13, #4] 
	mov	r13, #MODE_SVC			@ prepare SVC-Mode
	@ msr	spsr_c, r13
	msr	spsr, r13
	mov	lr, pc
	movs	pc, lr
	.endm

	.macro get_irq_stack			@ setup IRQ stack
	ldr	sp, IRQ_STACK_START
	.endm

	.macro get_fiq_stack			@ setup FIQ stack
	ldr	sp, FIQ_STACK_START
	.endm

              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
上面兩段代碼,基本上和前面很類似,雖然每一行都容易懂,但是整個兩個函數的意思,除了看其宏的名字irq_save_user_regs和irq_restore_user_regs,分别對應着中斷中,儲存和恢複使用者模式寄存器,之外,其他的,個人目前還是沒有太多了解。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處的get_bad_stack被後面undefined_instruction,software_interrupt等處調用,目前能了解的意思是,在出錯的時候,獲得對應的堆棧的值。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處的含義很好了解,就是把位址為IRQ_STACK_START中的值指派給sp。

即獲得IRQ的堆棧的起始位址。

而對于IRQ_STACK_START,是前面就提到過的cpu_init源碼

而此處,就是用到了,前面已經在cpu_init()中重新計算正确的值了。

即算出IRQ堆棧的起始位址,其算法很簡單,就是:

IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;      
即,先減去malloc預留的空間,和global data,即在

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\board.c

中定義的全局變量:

DECLARE_GLOBAL_DATA_PTR;

而此宏對應的值在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\include\asm-arm\global_data.h

中:
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")      
即,用一個固定的寄存器r8來存放此結構體的指針。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
提示
這也對應着編譯uboot的時候,你所看到的編譯參數-ffixed-r8

此gd_t的結構體,不同體系結構,用的不一樣。

而此處arm的平台中,gd_t的定義在同一檔案中:

typedef	struct	global_data {
	bd_t		*bd;
	unsigned long	flags;
	unsigned long	baudrate;
	unsigned long	have_console;	/* serial_init() was called */
	unsigned long	reloc_off;	/* Relocation Offset */
	unsigned long	env_addr;	/* Address  of Environment struct */
	unsigned long	env_valid;	/* Checksum of Environment valid? */
	unsigned long	fb_base;	/* base address of frame buffer */
#ifdef CONFIG_VFD
	unsigned char	vfd_type;	/* display type */
#endif
#if 0
	unsigned long	cpu_clk;	/* CPU clock in Hz!		*/
	unsigned long	bus_clk;
	unsigned long	ram_size;	/* RAM size */
	unsigned long	reset_status;	/* reset status register at boot */
#endif
	void		**jt;		/* jump table */
} gd_t;
                      
而此全局變量gd_t *gd會被其他很多檔案所引用,詳情自己去代碼中找。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處和上面類似,把位址為FIQ_STACK_START中的内容,給sp。

其中:

FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;      
即FIQ的堆棧起始位址,是IRQ堆棧起始位址減去IRQ堆棧的大小。

1.6.4. exception handlers

/*
 * exception handlers
 */
	.align  5
undefined_instruction:
	get_bad_stack
	bad_save_user_regs
	bl 	do_undefined_instruction
	.align	5
    

software_interrupt:
	get_bad_stack
	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

              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
如果發生未定義指令異常,CPU會掉轉到start.S開頭中對應的位置:
ldr	pc, _undefined_instruction      

即把位址為_undefined_instruction中的内容給pc,即跳轉到此處執行對應的代碼。

其做的事情依次是:

獲得出錯時候的堆棧

儲存使用者模式寄存器

跳轉到對應的函數:do_undefined_instruction

而do_undefined_instruction函數是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\interrupts.c

中:
void bad_mode (void)
{
	panic ("Resetting CPU ...\n");
	reset_cpu (0);
}

void do_undefined_instruction (struct pt_regs *pt_regs)
{
	printf ("undefined instruction\n");
	show_regs (pt_regs);
	bad_mode ();
}
                      
可以看到,此處起始啥事沒錯,隻是列印一下出錯時候的寄存器的值,然後跳轉到bad_mode中取reset CPU,直接重新開機系統了。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
以上幾個宏,和前面的do_undefined_instruction是類似的,就不多說了。

1.6.5. Launch

@ HJ
.globl Launch
    .align	4
Launch:    
    mov r7, r0
    @ diable interrupt
	@ disable watch dog timer
	mov	r1, #0x53000000
	mov	r2, #0x0
	str	r2, [r1]

    ldr r1,=INTMSK
    ldr r2,=0xffffffff  @ all interrupt disable
    str r2,[r1]

    ldr r1,=INTSUBMSK
    ldr r2,=0x7ff       @ all sub interrupt disable
    str r2,[r1]

    ldr     r1, = INTMOD
    mov r2, #0x0        @ set all interrupt as IRQ (not FIQ)
    str     r2, [r1]

    @ 
	mov	ip, #0
	mcr	p15, 0, ip, c13, c0, 0      @	/* zero PID */
	mcr	p15, 0, ip, c7, c7, 0       @	/* invalidate I,D caches */
	mcr	p15, 0, ip, c7, c10, 4      @	/* drain write buffer */
	mcr	p15, 0, ip, c8, c7, 0       @	/* invalidate I,D TLBs */
	mrc	p15, 0, ip, c1, c0, 0       @	/* get control register */
	bic	ip, ip, #0x0001             @	/* disable MMU */
	mcr	p15, 0, ip, c1, c0, 0       @	/* write control register */

    @ MMU_EnableICache
    @mrc p15,0,r1,c1,c0,0
    @orr r1,r1,#(1<<12)
    @mcr p15,0,r1,c1,c0,0

#ifdef CONFIG_SURPORT_WINCE
    bl Wince_Port_Init
#endif

    @ clear SDRAM: the end of free mem(has wince on it now) to the end of SDRAM
    ldr     r3, FREE_RAM_END
    ldr     r4, =PHYS_SDRAM_1+PHYS_SDRAM_1_SIZE    @ must clear all the memory unused to zero
    mov     r5, #0

    ldr     r1, _armboot_start
    ldr     r2, =On_Steppingstone
    sub     r2, r2, r1
    mov     pc, r2
On_Steppingstone:
2:  stmia   r3!, {r5}
    cmp     r3, r4
    bne     2b

    @ set sp = 0 on sys mode
    mov sp, #0

    @ add by HJ, switch to SVC mode
	msr	cpsr_c,	#0xdf	@ set the I-bit = 1, diable the IRQ interrupt
	msr	cpsr_c,	#0xd3	@ set the I-bit = 1, diable the IRQ interrupt
    ldr sp, =0x31ff5800	
    
    nop
	nop
    nop
	nop

	mov     pc, r7  @ Jump to PhysicalAddress
	nop
    mov pc, lr
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處相當于一個叫做Launch的函數,做了也是類似的系統初始化的動作。

但是沒找到此函數在哪裡被調用的。具體不太清楚。

1.6.6. int_return

#ifdef CONFIG_USE_IRQ
	.align	5
irq:
/* add by www.embedsky.net to use IRQ for USB and DMA */
	sub	lr, lr, #4			        @ the return address
	ldr	sp, IRQ_STACK_START	        @ the stack for irq
	stmdb	sp!, 	{ r0-r12,lr }	@ save registers
	
	ldr	lr,	=int_return		        @ set the return addr
	ldr	pc, =IRQ_Handle		        @ call the isr
int_return:
	ldmia	sp!, 	{ r0-r12,pc }^	@ return from interrupt
	.align	5
fiq:
	get_fiq_stack
	/* someone ought to write a more effiction 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
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處,做的事情,很容易看懂,就是中斷發生後,掉轉到這裡,然後儲存對應寄存器,然後跳轉到對應irq函數IRQ_Handle中去。

但是前面為何sp為何去減去4,原因不太懂。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
關于IRQ_Handle,是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\s3c24x0\interrupts.c

中:
void IRQ_Handle(void)
{
	unsigned long oft = intregs->INTOFFSET;
	S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

//	printk("IRQ_Handle: %d\n", oft);

	//清中斷
	if( oft == 4 ) gpio->EINTPEND = 1<<7;	
	intregs->SRCPND = 1<<oft;
	intregs->INTPND	= intregs->INTPND;

	/* run the isr */
	isr_handle_array[oft]();
}
                      
此處細節就不多解釋了,大體含義是,找到對應的中斷源,然後調用對應的之前已經注冊的中斷服務函數ISR。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處也很簡單,就是發生了快速中斷FIQ的時候,儲存IRQ的使用者模式寄存器,然後調用函數do_fiq,調用完畢後,再恢複IRQ的使用者模式寄存器。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
do_fiq()是在:

u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\interrupts.c

中:
void do_fiq (struct pt_regs *pt_regs)
{
	printf ("fast interrupt request\n");
	show_regs (pt_regs);
	bad_mode ();
}
                      
和前面提到過的do_undefined_instruction的一樣,就是列印寄存器資訊,然後跳轉到bad_mode()去重新開機CPU而已。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
此處就是,如果沒有定義CONFIG_USE_IRQ,那麼就用這段代碼,可以看到,都隻是直接調用do_irq和do_fiq,也沒做什麼實際工作。

第 2 章 start.S的總結

目錄

2.1. start.S各個部分的總結

2.2. Uboot中的記憶體的Layout

摘要

2.1. start.S各個部分的總結

其實關于start.S這個彙編檔案,主要做的事情就是系統的各個方面的初始化。

關于每個部分,上面具體的代碼實作,也都一行行的解釋過了,此處不再贅述。

此處,隻是簡單總結一下,其實作的方式,或者其他需要注意的地方。

  1. 設定CPU模式

    總的來說,就是将CPU設定為SVC模式。

    至于為何設定CPU是SVC模式,請參見後面章節的詳細解釋。

  2. 關閉看門狗

    就是去設定對應的寄存器,将看門狗關閉。

    至于為何關閉看門狗,請參見後面章節的詳細解釋。

  3. 關閉中斷

    關閉中斷,也是去設定對應的寄存器,即可。

  4. 設定堆棧sp指針

    所謂的設定堆棧sp指針,這樣的句子,之前聽到N次了,但是說實話,一直不太了解,到底更深一層的含義是什麼。

    後來,看了更多的代碼,才算有一點點了解。所謂的設定堆棧sp指針,就是設定堆棧,而所謂的設定堆棧,要做的事情,看起來很簡單,就隻是一個很簡單的動作:讓sp等于某個位址值,即可。

    但是背後的邏輯是:

    首先你自己要搞懂目前系統是如何使用堆棧的,堆棧是向上生長的還是向下生長的。

    然後知道系統如何使用堆棧之後,給sp指派之前,你要保證對應的位址空間,是專門配置設定好了,專門給堆棧用的,保證堆棧的大小相對合适,而不要太小以至于後期函數調用太多,導緻堆棧溢出,或者堆棧太大,浪費存儲空間,等等。

    所有這些背後的邏輯,都是要經過一定的程式設計經驗,才更加容易了解其中的含義的。

    此處,也隻是簡單說說,更多相關的内容,還是要靠每個人自己多實踐,慢慢的更加深入的了解。

  5. 清除bss段

    此處很簡單,就是将對應bss段,都設定為,0,即清零。

    其對應的位址空間,就是那些未初始化的全局變量之類的位址。

  6. 異常中斷處理

    異常中斷處理,就是實作對應的常見的那些進行中斷的部分内容。

    說白了就是實作一個個中斷函數。uboot在初始化的時候,主要目的隻是為了初始化系統,及引導系統,是以,此處的中斷處理部分的代碼,往往相對比較簡單,不是很複雜。

2.2. Uboot中的記憶體的Layout

總結了start.S做的事情之後,另外想在此總結一下,uboot中,初始化部分的代碼執行後,對應的記憶體空間,都是如何規劃,什麼地方放置了什麼内容。此部分内容,雖然和start.S沒有直接的關系,但是start.S中,堆棧sp的計算等,也和這部分内容有關。

下面這部分的uboot的記憶體的layout,主要是根據:

  1. start.S中關于設定堆棧指針的部分的代碼
    /* Set up the stack						    */
    stack_setup:
    	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
    	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
    	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
    
    #ifdef CONFIG_USE_IRQ
    	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
    #endif
    	sub	sp, r0, #12		/* leave 3 words for abort-stack    */
    
    	bl clock_init
                      
  2. u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\cpu\arm920t\cpu.c

    中的代碼
    int cpu_init (void)
    {
    	/*
    	 * setup up stacks if necessary
    	 */
    #ifdef CONFIG_USE_IRQ
    	IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
    	FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
        FREE_RAM_END = FIQ_STACK_START - CONFIG_STACKSIZE_FIQ - CONFIG_STACKSIZE;
        FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1;
    #else    
        FREE_RAM_END = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4 - CONFIG_STACKSIZE;
        FREE_RAM_SIZE = FREE_RAM_END - PHYS_SDRAM_1;
    #endif
    	return 0;
    }
                      
  3. u-boot-1.1.6_20100601\opt\EmbedSky\u-boot-1.1.6\board\EmbedSky\config.mk

    中的定義
    TEXT_BASE = 0x33D00000      

分析而得出的。

uboot的記憶體的layout,用圖表表示就是:

圖 2.1. Uboot中的記憶體的Layout

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

第 3 章 相關知識點詳解

目錄

3.1. 如何檢視C或彙編的源代碼所對應的真正的彙編代碼

3.2. uboot初始化中,為何要設定CPU為SVC模式而不是設定為其他模式

3.3. 什麼是watchdog + 為何在要系統初始化的時候關閉watchdog

3.3.1. 什麼是watchdog

3.3.2. 為何在要系統初始化的時候關閉watchdog

3.4. 為何ARM7中PC=PC+8

3.4.1. 為何ARM9和ARM7一樣,也是PC=PC+8

3.5. AMR寄存器的别名 + APCS

3.5.1. ARM中的寄存器的别名

3.5.2. 什麼是APCS

3.6. 為何C語言(的函數調用)需要堆棧,而彙編語言卻不需要堆棧

3.6.1. 儲存現場/上下文

3.6.1.1. 什麼叫做上下文context

3.6.2. 傳遞參數

3.6.3. 舉例分析C語言函數調用是如何使用堆棧的

3.7. 關于為何不直接用mov指令,而非要用adr僞指令

3.8. mov指令的操作數的取值範圍到底是多少

3.9. 彙編學習總結記錄

3.9.1. 彙編中的标号=C中的标号

3.9.2. 彙編中的跳轉指令=C中的goto

3.9.3. 彙編中的.globl=C語言中的extern

3.9.4. 彙編中用bl指令和mov pc,lr來實作子函數調用和傳回

3.9.5. 彙編中的對應位置有存儲值的标号 = C語言中的指針變量

3.9.6. 彙編中的ldr+标号,來實作C中的函數調用

3.9.7. 彙編中設定某個寄存器的值或給某個位址指派

摘要

3.1. 如何檢視C或彙編的源代碼所對應的真正的彙編代碼

首先解釋一下,由于彙編代碼中會存在一些僞指令等内容,是以,寫出來的彙編代碼,并不一定是真正可以執行的代碼,這些類似于僞指令的彙編代碼,經過彙編器,轉換或翻譯成真正的可以執行的彙編指令。是以,上面才會有将“彙編源代碼”轉換為“真正的彙編代碼”這一說。

然後,此處對于有些人不是很熟悉的,如何檢視源代碼真正對應的彙編代碼。

此處,對于彙編代碼,有兩種:

  1. 一種是隻是進過編譯階段,生成了對應的彙編代碼
  2. 另外一種是,編譯後的彙編代碼,經過連結器連結後,對應的彙編代碼。

總的來說,兩者差別很小,後者主要是更新了外部函數的位址等等,對于彙編代碼本身,至少對于我們一般所去檢視源代碼所對應的彙編來說,兩者可以視為沒差別。

在檢視源代碼所對應的真正的彙編代碼之前,先要做一些相關準備工作:

  1. 編譯uboot

    在Linux下,一般編譯uboot的方法是:

    1. make distclean      
      去清除之前配置,編譯等生成的一些檔案。
    2. make EmbedSky_config      
      去配置我們的uboot
    3. make      
      去執行編譯
  2. 檢視源碼所對應的彙編代碼

    對于我們此處的uboot的start.S來說:

    1. 對于編譯所生成的彙編的檢視方式是

      用交叉編譯器的dump工具去将彙編代碼都導出來:

      arm-linux-objdump –d cpu/arm920t/start.o > uboot_start.o_dump_result.txt      

      這樣就把start.o中的彙編代碼導出到uboot_start.o_dump_result.txt中了。

      然後檢視uboot_start.o_dump_result.txt,即可找到對應的彙編代碼。

      舉例來說,對于start.S中的彙編代碼:

      /* Set up the stack						    */
      stack_setup:
      	ldr	r0, _TEXT_BASE		/* upper 128 KiB: relocated uboot   */
      	sub	r0, r0, #CFG_MALLOC_LEN	/* malloc area                      */
      	sub	r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */
      
      #ifdef CONFIG_USE_IRQ
      	sub	r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
      #endif
      	sub	sp, r0, #12		/* leave 3 words for abort-stack    */
      
      	bl clock_init
                                
      去uboot_start.o_dump_result.txt中,搜尋stack_setup,即可找到對應部分的彙編代碼:
      00000090 <stack_setup>:
        90:	e51f0058 	ldr	r0, [pc, #-88]	; 40 <_TEXT_BASE>
        94:	e2400701 	sub	r0, r0, #262144	; 0x40000
        98:	e2400080 	sub	r0, r0, #128	; 0x80
        9c:	e240d00c 	sub	sp, r0, #12	; 0xc
        a0:	ebfffffe 	bl	0 <clock_init>
                                
    2. 對于連結所生成的彙編的檢視方式是

      和上面方法一樣,即:

      arm-linux-objdump –d u-boot > whole_uboot_dump_result.txt      
      然後打開該txt,找到stack_setup部分的代碼:
      33d00090 <stack_setup>:
      33d00090:	e51f0058 	ldr	r0, [pc, #-88]	; 33d00040 <_TEXT_BASE>
      33d00094:	e2400701 	sub	r0, r0, #262144	; 0x40000
      33d00098:	e2400080 	sub	r0, r0, #128	; 0x80
      33d0009c:	e240d00c 	sub	sp, r0, #12	; 0xc
      33d000a0:	eb000242 	bl	33d009b0 <clock_init>
                                
      兩者不一樣地方在于,我們uboot設定了text_base,即代碼段的基位址,上面編譯後的彙編代碼,經過連結後,更新了對應的基位址,是以看起來,是以代碼對應的位址,都變了,但是具體位址中的彙編代碼,除了個别調用函數的位址和跳轉到某個标号的位址之外,其他都還是一樣的。

對于C語言的源碼,也是同樣的方法,用對應的dump工具,去從該C語言的.o檔案中,dump出來彙編代碼。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
注意

【總結】

不論是C語言還是彙編語言的源檔案,想要檢視其對應的生成的彙編代碼的話,方法很簡單,就是用dump工具,從對應的.o目标檔案中,導出對應的彙編代碼,即可。

3.2. uboot初始化中,為何要設定CPU為SVC模式而不是設定為其他模式

在看Uboot的start.S檔案時候,發現其最開始初始化系統,做的第一件事情,就是将CPU設定為SVC模式,但是S3C2440的CPU的core是ARM920T,其有7種模式,為何非要設定為SVC模式,而不是設定為其他模式呢?對此,經過一些求證,得出如下原因:

首先,先要了解ARM的CPU的7種模式是哪些:

http://www.docin.com/p-73665362.html

表 3.1. ARM中CPU的模式

處理器模式 說明 備注
使用者(usr) 正常程式工作模式 此模式下程式不能夠通路一些受作業系統保護的系統資源,應用程式也不能直接進行處理器模式的切換。
系統(sys) 用于支援作業系統的特權任務等 與使用者模式類似,但具有可以直接切換到其它模式等特權
快中斷(fiq) 支援高速資料傳輸及通道處理 FIQ異常響應時進入此模式
中斷(irq) 用于通用中斷處理 IRQ異常響應時進入此模式
管理(svc) 作業系統保護代碼 系統複位和軟體中斷響應時進入此模式
中止(abt) 用于支援虛拟記憶體和/或存儲器保護 在ARM7TDMI沒有大用處
未定義(und) 支援硬體協處理器的軟體仿真 未定義指令異常響應時進入此模式

另外,7種模式中,除使用者usr模式外,其它模式均為特權模式。

對于為何此處是svc模式,而不是其他某種格式,其原因,可以從兩方面來看:

  1. 我們先簡單的來分析一下那7種模式:
    1. 中止abt和未定義und模式

      首先可以排除的是,中止abt和未定義und模式,那都是不太正常的模式,此處程式是正常運作的,是以不應該設定CPU為其中任何一種模式,是以可以排除。

    2. 快中斷fiq和中斷irq模式

      其次,對于快中斷fiq和中斷irq來說,此處uboot初始化的時候,也還沒啥中斷要處理和能夠處理,而且即使是注冊了終端服務程式後,能夠進行中斷,那麼這兩種模式,也是自動切換過去的,是以,此處也不應該設定為其中任何一種模式。

    3. 使用者usr模式

      雖然從理論上來說,可以設定CPU為使用者usr模式,但是由于此模式無法直接通路很多的硬體資源,而uboot初始化,就必須要去通路這類資源,是以此處可以排除,不能設定為使用者usr模式。

    4. 系統sys模式 vs 管理svc模式

      首先,sys模式和usr模式相比,所用的寄存器組,都是一樣的,但是增加了一些通路一些在usr模式下不能通路的資源。

      而svc模式本身就屬于特權模式,本身就可以通路那些受控資源,而且,比sys模式還多了些自己模式下的影子寄存器,是以,相對sys模式來說,可以通路資源的能力相同,但是擁有更多的硬體資源。

      是以,從理論上來說,雖然可以設定為sys和svc模式的任一種,但是從uboot方面考慮,其要做的事情是初始化系統相關硬體資源,需要擷取盡量多的權限,以友善操作硬體,初始化硬體。

    從uboot的目的是初始化硬體的角度來說,設定為svc模式,更有利于其工作。

    是以,此處将CPU設定為SVC模式。

  2. uboot作為一個bootloader來說,最終目的是為了啟動Linux的kernel,在做好準備工作(即初始化硬體,準備好kernel和rootfs等)跳轉到kernel之前,本身就要滿足一些條件,其中一個條件,就是要求CPU處于SVC模式的。

    是以,uboot在最初的初始化階段,就将CPU設定為SVC模式,也是最合适的。

    Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
    提示

    關于滿足哪些條件,詳情請參考

    ARM Linux Kernel Boot Requirements

    或者Linux核心文檔:

    kernel_source_root\documentation\arm\booting

    中也是同樣的解釋:
    The CPU must be in SVC mode
    是以,uboot在最初的初始化階段,就将CPU設定為SVC模式,也是最合适的。

綜上所述,uboot在初始化階段,就應該将CPU設定為SVC模式。

3.3. 什麼是watchdog + 為何在要系統初始化的時候關閉watchdog

關于Uboot初始化階段,在start.S中,為何要去關閉watchdog,下面解釋具體的原因:

3.3.1. 什麼是watchdog

參考嵌入式系統之WATCHDOG(看門狗)概述

簡要摘錄如下:

watchdog一般是一個硬體子產品,其作用是,在嵌入式作業系統中,很多應用情況是系統長期運作且無人看守,是以難免或者怕萬一出現系統當機,那就杯具了,這時,watchdog就會自動幫你重新開機系統。

那麼其是如何實作此功能的呢?那麼就要簡單解釋一下其實作原理了。

watchdog硬體的邏輯就是,其硬體上有個記錄逾時功能,然後要求使用者需要每隔一段時間(此時間可以根據自己需求而配置)去對其進行一定操作,比如往裡面寫一些固定的值,俗稱“喂狗”,那麼我發現逾時了,即過了這麼長時間你還不給偶喂食,那麼偶就認為你系統是當機了,出問題了,偶就幫你重新開機系統。

說白了就是弄個看家狗dog,你要定期給其喂食,如果逾時不喂食,那麼狗就認為你,他的主人,你的系統,當機了,就幫你reset重新開機系統。

3.3.2. 為何在要系統初始化的時候關閉watchdog

了解了watchdog的原理後,此問題就很容易了解了。

如果不禁用watchdog,那麼就要單獨寫程式去定期“喂狗”,那多麻煩,多無聊啊。

畢竟咱此處隻是去用uboot初始化必要的硬體資源和系統資源而已,完全用不到這個watchdog的機制。需要用到,那也是你linux核心跑起來了,是你系統關心的事情,和我uboot沒啥關系的,是以肯定此處要去關閉watchdog(的reset功能)了。

3.4. 為何ARM7中PC=PC+8

此處解釋為何ARM7中,CPU位址,即PC,為何有PC=PC+8這一說法:

衆所周知,AMR7,是三級流水線,其細節見圖:

圖 3.1. AMR7三級流水線

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

首先,對于ARM7對應的流水線的執行情況,如下面這個圖所示:

圖 3.2. ARM7三級流水線狀态

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

然後對于三級流水線舉例如下:

圖 3.3. ARM7三級流水線示例

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

從上圖,其實很容易看出,第一條指令:

add r0, r1,$5      

執行的時候,此時PC已經指向第三條指令:

cmp r2,#3      

的位址了,是以,是PC=PC+8.

3.4.1. 為何ARM9和ARM7一樣,也是PC=PC+8

ARM7的三條流水線,PC=PC+8,很好了解,但是AMR9中,是五級流水線,為何還是PC=PC+8,而不是

PC

=PC+(5-1)*4

=PC + 16,

呢?

下面就需要好好解釋一番了。

具體解釋之前,先貼上ARM7和ARM9的流水線的差別和聯系:

圖 3.4. ARM7三級流水線 vs ARM9五級流水線

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

圖 3.5. ARM7三級流水線到ARM9五級流水線的映射

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

下面開始對為何ARM9也是PC=PC+8進行解釋。

先列出ARM9的五級流水線的示例:

圖 3.6. ARM9的五級流水線示例

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

舉例分析為何PC=PC+8

然後我們以下面uboot中的start.S的最開始的彙編代碼為例來進行解釋:

00000000 <_start>:
   0:	ea000014 	b	58 <reset>
   4:	e59ff014 	ldr	pc, [pc, #20]	; 20 <_undefined_instruction>
   8:	e59ff014 	ldr	pc, [pc, #20]	; 24 <_software_interrupt>
   c:	e59ff014 	ldr	pc, [pc, #20]	; 28 <_prefetch_abort>
  10:	e59ff014 	ldr	pc, [pc, #20]	; 2c <_data_abort>
  14:	e59ff014 	ldr	pc, [pc, #20]	; 30 <_not_used>
  18:	e59ff014 	ldr	pc, [pc, #20]	; 34 <_irq>
  1c:	e59ff014 	ldr	pc, [pc, #20]	; 38 <_fiq>

00000020 <_undefined_instruction>:
  20:	00000120 	.word	0x00000120
              

下面對每一個指令周期,CPU做了哪些事情,分别詳細進行闡述:

在看下面具體解釋之前,有一句話要牢記,那就是:

PC不是指向你正在運作的指令,而是

PC始終指向你要取的指令的位址

認識清楚了這個前提,後面的舉例講解,就容易懂了。

  1. 指令周期Cycle1
    1. 取指

      PC總是指向将要讀取的指令的位址(即我們常說的,指向下一條指令的位址),而目前PC=4,

      是以去取實體位址為4對對應的指令

      ldr	pc, [pc, #20]      

      其對應二進制代碼為e59ff014。

      此處取指完之後,自動更新PC的值,即PC=PC+4(單個指令占4位元組,是以加4)=4+4=8

  2. 指令周期Cycle2
    1. 譯指

      翻譯指令e59ff014

    2. 同時再去取指

      PC總是指向将要讀取的指令的位址(即我們常說的,指向下一條指令的位址),而目前PC=8,

      是以去實體位址為8所對應的指令“ldr pc, [pc, #20]” 其對應二進制代碼為e59ff014。

      此處取指完之後,自動更新PC的值,即PC=PC+4=8+4=12=0xc

  3. 指令周期Cycle3
    1. 執行(指令)

      執行“e59ff014”,即

      ldr	pc, [pc, #20]      

      所對表達的含義,即PC

      = PC + 20

      = 12 + 20

      = 32

      = 0x20

      此處,隻是計算出待會要指派給PC的值是0x20,這個0x20還隻是放在執行單元中内部的緩沖中。

    2. 譯指

      翻譯e59ff014

    3. 取指

      此步驟由于是和上面(1)中的執行同步做的,是以,未受到影響,繼續取指,而取指的那一時刻,PC為上一Cycle更新後的值,即PC=0xc,是以是去取實體位址為0xc所對應的指令

      ldr	pc, [pc, #20]      
      對應二進制為e59ff014

其實,分析到這裡,大家就可以看出:

在Cycle3的時候,PC的值,剛好已經在Cycle1和Cycle2,分别加了4,是以Cycle3的時候,PC=PC+8,而同樣道理,對于任何一條指令的,都是在Cycle3,指令的Execute執行階段,如果用到PC的值,那麼PC那一時刻,就是PC=PC+8。

是以,此處雖然是五級流水線,但是卻不是PC=PC+16,而是PC=PC+8。

進一步地,我們發現,其實PC=PC+N的N,是和指令的執行階段所處于流水線的深度有關,即此處指令的執行Execute階段,是五級流水線中的第三個,而這個第三階段的Execute和指令的第一個階段的Fetch取指,相差的值是 3 -1 =2,即兩個CPU的Cycle,而每個Cycle都會導緻PC=+PC+4,是以,指令到了Execute階段,才會發現,此時PC已經變成PC=PC+8了。

回過頭來反觀ARM7的三級流水線,也是同樣的道理,指令的Execute執行階段,是處于指令的第三個階段,同理,在指令計算資料的時候,如果用到PC,就會發現此時PC=PC+8。

同理,假如ARM9的五級流水線,把指令的Execute執行階段,設計在了第四個階段,那麼就是PC=PC+(第4階段-1)*4個位元組 = PC= PC+12了。

用圖來說明PC=PC+8個過程

對于上面的文字的分析過程,可能看起來不是太容易了解,是以,下面這裡通過圖表來表示具體的流程,就更容易看懂了。其中,下圖,是以ARM9的五級流水線的内部架構圖為基礎,而編輯的出來用于說明為何ARM9的五級流水線,也是PC=PC+8:

圖 3.7. ARM9的五級流水線中為何PC=PC+8

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

對于上圖中的,第一個指令在執行的時候,是使用到了PC的值,其實,我們可以看到,

對于指令在執行中,不論是否用到PC的值,PC都會按照既定邏輯,沒一個cycle,自動增加4的,套用《非誠勿擾2》中的經典對白,即為:

你(指令執行的時候)用,

或者不用,

PC就在那裡,

自動增4

是以,經過兩個cycle的增4,就到了指令執行的時候,此時PC已經增加了8了,即使你指令執行的時候,沒有用到PC的值,其也還是已經加了8了。而一般來說,大多數的指令,肯定也都是沒有用到PC的,但是其實任何指令執行的那一時刻,也已經是PC=PC+8,而多數指令沒有用到,是以很多人沒有注意到這點罷了。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
PC(execute)=PC(fetch)+ 8

對于PC=PC+8中的兩個PC,其實含義不完全一樣.其更準确的表達,應該是這樣:

PC(execute)=PC(fetch)+ 8

其中:

PC(fetch):目前正在執行的指令,就是之前取該指令時候的PC的值

PC(execute):目前指令執行的計算中,如果用到PC,則此時PC的值。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
不同階段的PC值的關系

對應地,在ARM7的三級流水線(取指,譯指,執行)和ARM9的五級流水線(取指,譯指,執行,存儲,寫回)中,可以這麼說:

PC, 總是指向目前正在被取指的指令的位址,

PC-4,總是指向目前正在被譯指的指令的位址,

PC-8,總是指向目前的那條指令,即我們一般說的,正在被執行的指令的位址。

【總結】

ARM7的三級流水線,PC=PC+8,

ARM9的五級流水線,也是PC=PC+8,

根本的原因是,兩者的流水線設計中,指令的Execute執行階段,都是處于流水線的第三級。

是以使得PC=PC+8。

類似地,可以推導出:

假設,Execute階段處于流水線中的第E階段,每條指令是T個位元組,那麼

PC

= PC + N*T

= PC + (E - 1) * T

此處ARM7和ARM9:

Execute階段都是第3階段 ⇒ E=3

每條指令是4個位元組 ⇒ T=4

是以:

PC

=PC + N* T

=PC + (3 -1 ) * 4

= PC + 8

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
關于直接改變PC的值,會導緻流水線清空的解釋
把PC的值直接指派為0x20。而PC值更改,直接導緻流水線的清空,即導緻下一個cycle中的,對應的流水線中的其他幾個步驟,包括接下來的同一個Cycle中的取指的工作被取消。在PC跳轉到0x20的位置之後,流水線重新計算,重新一步步地按照流水線的邏輯,去一點點執行。當然要保證目前指令的執行完成,即執行之後,還有兩個cycle,分别做的Memory和Write,會繼續執行完成。

3.5. AMR寄存器的别名 + APCS

此處簡單介紹一下,ARM寄存器的别名,以及什麼是APCS。

用文字解釋之前,先看這個版本的解釋,顯得很直覺,很好了解:

圖 3.8. ARM Application Procedure Call Standard (AAPCS)

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

3.5.1. ARM中的寄存器的别名

預設的情況下,這些寄存器隻是叫做r0,r1,...,r14等,而APCS 對其起了不同的别名。

使用彙編器預處理器的功能,你可以定義 R0 等名字,但在你修改其他人寫的代碼的時候,最好還是學習使用 APCS 名字。

一般程式設計過程中,最好按照其約定,使用對應的名字,這樣使得程式可讀性更好。

關于不同寄存器所對應的名字,見下表:

表 3.2. ARM寄存器的别名

寄存器名字
Reg# APCS 意義
R0 a1 工作寄存器
R1 a2 "
R2 a3 "
R3 a4 "
R4 v1 必須保護
R5 v2 "
R6 v3 "
R7 v4 "
R8 v5 "
R9 v6 "
R10 sl 棧限制
R11 fp 桢指針
R12 ip 内部過程調用寄存器
R13 sp 棧指針
R14 lr 連接配接寄存器
R15 pc 程式計數器

更加詳細一點,見下:

Predeclared register names

The following register names are predeclared:

  1. r0-r15 and R0-R15
  2. a1-a4 (argument, result, or scratch registers, synonyms for r0 to r3)
  3. v1-v8 (variable registers, r4 to r11)
  4. sb and SB (static base, r9)
  5. ip and IP (intra-procedure-call scratch register, r12)
  6. sp and SP (stack pointer, r13)
  7. lr and LR (link register, r14)
  8. pc and PC (program counter, r15).

Predeclared extension register names

The following extension register names are predeclared:

  1. d0-d31 and D0-D31(VFP double-precision registers)
  2. s0-s31 and S0-S31(VFP single-precision registers)

Predeclared coprocessor names

The following coprocessor names and coprocessor register names are predeclared:

  1. p0-p15 (coprocessors 0-15)
  2. c0-c15 (coprocessor registers 0-15).

3.5.2. 什麼是APCS

APCS,ARM 過程調用标準(ARM Procedure Call Standard),提供了緊湊的編寫例程的一種機制,定義的例程可以與其他例程交織在一起。最顯著的一點是對這些例程來自哪裡沒有明确的限制。它們可以編譯自 C、 Pascal、也可以是用彙編語言寫成的。

APCS 定義了:

  • 對寄存器使用的限制。
  • 使用棧的慣例。
  • 在函數調用之間傳遞/傳回參數。
  • 可以被"回溯"的基于棧的結構的格式,用來提供從失敗點到程式入口的函數(和給予的參數)的清單。

3.6. 為何C語言(的函數調用)需要堆棧,而彙編語言卻不需要堆棧

之前看了很多關于uboot的分析,其中就有說要為C語言的運作,準備好堆棧。

而自己在Uboot的start.S彙編代碼中,關于系統初始化,也看到有堆棧指針初始化這個動作。但是,從來隻是看到有人說系統初始化要初始化堆棧,即正确給堆棧指針sp指派,但是卻從來沒有看到有人解釋,為何要初始化堆棧。是以,接下來的内容,就是經過一定的探究,試圖來解釋一下,為何要初始化堆棧,即:

為何C語言的函數調用要用到堆棧,而彙編卻不需要初始化堆棧。

要明白這個問題,首先要了解堆棧的作用。

關于堆棧的作用,要詳細講解的話,要很長的篇幅,是以此處隻是做簡略介紹。

總的來說,堆棧的作用就是:儲存現場/上下文,傳遞參數。

3.6.1. 儲存現場/上下文

現場,意思就相當于案發現場,總有一些現場的情況,要記錄下來的,否則被别人破壞掉之後,你就無法恢複現場了。而此處說的現場,就是指CPU運作的時候,用到了一些寄存器,比如r0,r1等等,對于這些寄存器的值,如果你不儲存而直接跳轉到子函數中去執行,那麼很可能就被其破壞了,因為其函數執行也要用到這些寄存器。

是以,在函數調用之前,應該将這些寄存器等現場,暫時保持起來,等調用函數執行完畢傳回後,再恢複現場。這樣CPU就可以正确的繼續執行了。

在計算機中,你常可以看到上下文這個詞,對應的英文是context。那麼:

3.6.1.1. 什麼叫做上下文context

儲存現場,也叫儲存上下文。

上下文,英文叫做context,就是上面的文章,和下面的文章,即與你此刻,目前CPU運作有關系的内容,即那些你用到寄存器。是以,和上面的現場,是一個意思。

儲存寄存器的值,一般用的是push指令,将對應的某些寄存器的值,一個個放到堆棧中,把對應的值壓入到堆棧裡面,即所謂的壓棧。

然後待被調用的子函數執行完畢的時候,再調用pop,把堆棧中的一個個的值,指派給對應的那些你剛開始壓棧時用到的寄存器,把對應的值從堆棧中彈出去,即所謂的出棧。

其中儲存的寄存器中,也包括lr的值(因為用bl指令進行跳轉的話,那麼之前的pc的值是存在lr中的),然後在子程式執行完畢的時候,再把堆棧中的lr的值pop出來,指派給pc,這樣就實作了子函數的正确的傳回。

3.6.2. 傳遞參數

C語言進行函數調用的時候,常常會傳遞給被調用的函數一些參數,對于這些C語言級别的參數,被編譯器翻譯成彙編語言的時候,就要找個地方存放一下,并且讓被調用的函數能夠通路,否則就沒發實作傳遞參數了。對于找個地方放一下,分兩種情況。

一種情況是,本身傳遞的參數就很少,就可以通過寄存器傳送參數。

因為在前面的儲存現場的動作中,已經儲存好了對應的寄存器的值,那麼此時,這些寄存器就是空閑的,可以供我們使用的了,那就可以放參數,而參數少的情況下,就足夠存放參數了,比如參數有2個,那麼就用r0和r1存放即可。(關于參數1和參數2,具體哪個放在r0,哪個放在r1,就是和APCS中的“在函數調用之間傳遞/傳回參數”相關了,APCS中會有詳細的約定。感興趣的自己去研究。)

但是如果參數太多,寄存器不夠用,那麼就得把多餘的參數堆棧中了。

即,可以用堆棧來傳遞所有的或寄存器放不下的那些多餘的參數。

3.6.3. 舉例分析C語言函數調用是如何使用堆棧的

對于上面的解釋的堆棧的作用顯得有些抽象,此處再用例子來簡單說明一下,就容易明白了:

用:

arm-inux-objdump –d u-boot > dump_u-boot.txt      

可以得到dump_u-boot.txt檔案。該檔案就是中,包含了u-boot中的程式的可執行的彙編代碼,其中我們可以看到C語言的函數的源代碼,到底對應着那些彙編代碼。

下面貼出兩個函數的彙編代碼,

一個是clock_init,另一個是與clock_init在同一C源檔案中的,另外一個函數CopyCode2Ram

33d0091c <CopyCode2Ram>:
33d0091c:	e92d4070 	push	{r4, r5, r6, lr}
33d00920:	e1a06000 	mov	r6, r0
33d00924:	e1a05001 	mov	r5, r1
33d00928:	e1a04002 	mov	r4, r2
33d0092c:	ebffffef 	bl	33d008f0 <bBootFrmNORFlash>
... ...
33d00984:	ebffff14 	bl	33d005dc <nand_read_ll>
... ...
33d009a8:	e3a00000 	mov	r0, #0	; 0x0
33d009ac:	e8bd8070 	pop	{r4, r5, r6, pc}

33d009b0 <clock_init>:
33d009b0:	e3a02313 	mov	r2, #1275068416	; 0x4c000000
33d009b4:	e3a03005 	mov	r3, #5	; 0x5
33d009b8:	e5823014 	str	r3, [r2, #20]
... ...
33d009f8:	e1a0f00e 	mov	pc, lr
                  
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

此處就是我們所期望的,用push指令,儲存了r4,r5,r以及lr。

用push去儲存r4,r5,r6,那是因為所謂的儲存現場,以後後續函數傳回時候再恢複現場,

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

上述用push去儲存lr,那是因為函數CopyCode2Ram裡面在此處調用了bBootFrmNORFlash

以及也調用了nand_read_ll:

33d00984:	ebffff14 	bl	33d005dc <nand_read_ll>      
也用到了bl指令,會改變我們最開始進入clock_init時候的lr的值,是以我們要用push也暫時儲存起來。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

把0指派給r0寄存器,這個就是我們所謂傳回值的傳遞,是通過r0寄存器的。

此處的傳回值是0,也對應着C語言的源碼中的“return 0”.

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
把之前push的值,給pop出來,還給對應的寄存器,其中最後一個是将開始push的lr的值,pop出來給賦給PC,因為實作了函數的傳回。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

可以看到此處是該函數第一行

其中沒有我們所期望的push指令,沒有去将一些寄存器的值放到堆棧中。這是因為,我們clock_init這部分的内容,所用到的r2,r3等寄存器,和前面調用clock_init之前所用到的寄存器r0,沒有沖突,是以此處可以不用push去儲存這類寄存器的值,不過有個寄存器要注意,那就是r14,即lr,其是在前面調用clock_init的時候,用的是bl指令,是以會自動把跳轉時候的pc的值指派給lr,是以也不需要push指令去将PC的值儲存到堆棧中。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

而此處是clock_init的代碼的最後一行

就是我們常見的mov pc, lr,把lr的值,即之前儲存的函數調用時候的PC值,指派給現在的PC,這樣就實作了函數的正确的傳回,即傳回到了函數調用時候下一個指令的位置。

這樣CPU就可以繼續執行原先函數内剩下那部分的代碼了。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
對于使用哪個寄存器來傳遞傳回值
當然你也可以用其他暫時空閑沒有用到的寄存器來傳遞傳回值,但是這些處理方式,本身是根據ARM的APCS的寄存器的使用的約定而設計的,你最好不要随便改變使用方式,最好還是按照其約定的來處理,這樣程式更加符合規範。

3.7. 關于為何不直接用mov指令,而非要用adr僞指令

在分析uboot的start.S中,看到一些指令,比如:

adr r0, _start      

覺得好像可以直接用mov指令實作即可,為啥還要這麼麻煩地,去用ldr去實作?

關于此處的代碼,為何要用adr指令:

adr r0, _start      
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
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 指令所允許的值,這也正是下面要詳細解釋的内容第 3.8 節 “mov指令的操作數的取值範圍到底是多少”

【總結】

之是以用adr而不用mov,主要是為了生成位址無關代碼,以及由于不友善判斷一個數,是否是有效的mov的操作數。

3.8. mov指令的操作數的取值範圍到底是多少

關于mov指令操作數的取值範圍,網上看到一些人說是0x00-0xFF,也有人說是其他的值的,但是經過一番求證,發現這些說法都不對。下面就是來詳細解釋,mov指令的操作數的取指範圍,到底是多少。

在看了我說的,關于這行代碼:

mov r0, 0x33d00000      

的源操作數0x33d0000,可能是mov指令所不允許的,這句話後,可能有人會說,我知道,那是因為mov的操作數的值,不允許大于255,至少網上很多人的資料介紹中,都是這麼說的。

對此,要說的是,你的回答是錯誤的。

關于mov操作數的真正的允許的取值範圍,還真的不是那麼容易就能搞懂的,下面就來詳細解釋解釋。

總的來說,我是從ARM 彙編的mov操作立即數的疑問

裡面,才算清楚mov的取值範圍,以及找了相應的datasheet,才最終看懂整個事情的來龍去脈的。

首先,mov的指令,是屬于ARM指令集中,資料處理(Data Process)分類中的其中一個指令,

而資料處理指令的具體格式是:ARM Processor Instruction Set

圖 3.9. 資料處理指令的指令格式

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目

對于此格式,我們可以拿:

arm-linux-objdump –d u-boot > dump_u-boot.txt      

中得到的彙編代碼中關于:

ldr     r0, =0x53000000      

所對應的,真正的彙編代碼:

33d00068:	e3a00453 	mov	r0, #1392508928	; 0x53000000      

來分析,就容易看懂了:

mov r0, #1392508928

= mov r0, #0x53000000

的作用就是,把0x53000000移動到r0中去。

其對應的二進制指令是上面的:

0xe3a00453 = 1110 0011 1010 0000 0000 0100 0101 0011 b

下面對照mov指令的格式,來分析這些位所對應的含義:

表 3.3. mov指令0xe3a00453的位域含義解析

31-28 27-26 25 24-21 20 19-16 15-12 11-0
Condition Field 00 I(Immediate Operand) OpCode(Operation Code) S(Set Condition Code) Rn(1st Operand Register) Rd(Destination Register)

Operand 2

1 = operand

2 is an immediate value

11-8:Rotate 7-0:Imm
1110 00 1 1101 0000 0000 0100 0101 0011
表明是立即數 1101對應的是MOV指令 MOV指令做的事情是: Rd:= Op2,和Rn無關,是以忽略這個Rn 表示0000号寄存器,即r0 0100=4,含義參見注釋1 0x53
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
注意
上述datasheet中寫到:

5.4.3 Immediate operand rotates

The immediate operand rotate field is a 4 bit unsigned integer which specifies a shift operation on the 8 bit immediate value. This value is zero extended to 32 bits, and then subject to a rotate right by twice the value in the rotate field. This enables many common constants to be generated, for example all powers of 2

意思是,對于bit[11:8]的值,是個4位,無符号的整型,其指定了bit[7:0]的8bit立即數值的位移操作。具體如何指定呢,那就是将bit[7:0]的值,循環右移2x bit[11:8]位。

對于我們的例子,就是,将bit[7:0]的值0x53,循環右移 2xbit[11:8]= 2 x 4 = 8位,

而0x53循環右移8位,就得到了0x53000000,就是我們要mov值,mov到目的寄存器rd,此處為r0中。

而上面英文最後一句說的是,通過将bit[7:0]的值,循環右移 2xbit[11:8]的方式,就可以産生出很多個數值了,即mov的操作數中,其中符合可以通過0x00-0xFF循環右移偶數位而産生的數值,都是合法的mov的操作數,而這樣的數,其實是很多的。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
總結mov取值範圍

是以,mov指令的操作數的真正的取指範圍,即不是0-0xFF(0-255),也不是隻有2的倍數,而是:

隻要該數,可以通過0x00-0xFF中某個數,循環右移偶數位而産生,就是合法的mov的操作數,否則就是非法的mov的操作數。

3.9. 彙編學習總結記錄

對于我們之前分析的start.S中,涉及到很多的彙編的語句,其中,可以看出,很多包含了很多種不同的文法,使用慣例等,下面,就對此進行一些總結,借以實作一定的舉一反三或者說觸類旁通,這樣,可以起到一定的借鑒功能,友善以後看其他類似彙編代碼, 容易看懂彙編代碼所要表達的含義。

3.9.1. 彙編中的标号=C中的标号

像前面彙編代碼中,有很多的,以點開頭,加上一個名字的形式的标号,比如:

reset:
	/*
	 * set the cpu to SVC32 mode
	 */
	mrs	r0,cpsr
              

中的reset,就是彙編中的标号,相對來說,比較容易了解,就相當于C語言的标号。

比如,C語言中定義一個标号ERR_NODEV:

ERR_NODEV: /* no device error */
    ... /* c code here */
              

然後對應在别處,使用goto去跳轉到這個标号ERR_NODEV:

if (something)
    goto ERR_NODEV ; 
              
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
【總結】
彙編中的标号 = C語言中的标号Label

3.9.2. 彙編中的跳轉指令=C中的goto

對應地,和上面的例子中的C語言中的編号和掉轉到标号的goto類似,彙編中,對于定義了标号,那麼也會有對應的指令,去跳轉到對應的彙編中的标号。

這些跳轉的指令,就是b指令,b是branch的縮寫。

b指令的格式是:

b{cond} label      

簡單說就是跳轉到label處。

用和上面的例子相關的代碼來舉例:

.globl _start
_start:	b       reset
              

就是用b指令跳轉到上面那個reset的标号。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
【總結】
彙編中的b跳轉指令 = C語言中的goto

3.9.3. 彙編中的.globl=C語言中的extern

對于上面例子中:

.globl _start
              

中的.global,就是聲明_start為全局變量/标号,可以供其他源檔案所通路。

即彙編器,在編譯此彙編代碼的時候,會将此變量記下來,知道其是個全局變量,遇到其他檔案是用到此變量的的時候,知道是通路這個全局變量的。

是以,從功能上來說,就相當于C語言用extern去生命一個變量,以實作本檔案外部通路此變量。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
【總結】
彙編中的.globl或.global = C語言中的extern

3.9.4. 彙編中用bl指令和mov pc,lr來實作子函數調用和傳回

和b指令類似的,另外還有一個bl指令,文法是:

BL{cond} label      

其作用是,除了b指令跳轉到label之外,在跳轉之前,先把下一條指令位址存到lr寄存器中,以友善跳轉到那邊執行完畢後,将lr再指派給pc,以實作函數傳回,繼續執行下面的指令的效果。

用下面這個start.S中的例子來說明:

bl	cpu_init_crit
......
cpu_init_crit:
......
	mov	pc, lr
              

其中,就是先調用bl掉轉到對應的标号cpu_init_crit,其實就是相當于一個函數了,

然後在cpu_init_crit部分,執行完畢後,最後調用 mov pc, lr,将lr中的值,賦給pc,即實作函數的傳回原先 bl cpu_init_crit下面那條代碼,繼續執行函數。

上面的整個過程,用C語言表示的話,就相當于

......
cpu_init_crit();
......

void cpu_init_crit(void)
{
......
}
              

而關于C語言中,函數的跳轉前後所要做的事情,都是C語言編譯器幫我們實作好了,會将此C語言中的函數調用,轉化為對應的彙編代碼的。

其中,此處所說的,函數掉轉前後所要做的事情,就是:

  • 函數跳轉前

    要将目前指令的下一條指令的位址,儲存到lr寄存器中

  • 函數調用完畢後

    将之前儲存的lr的值給pc,實作函數跳轉回來。繼續執行下一條指令。

而如果你本身自己寫彙編語言的話,那麼這些函數跳轉前後要做的事情,都是你程式員自己要關心,要實作的事情。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
總結彙編中的:bl + mov pc,lr
彙編中bl + mov pc,lr = C語言中的子函數調用和傳回

3.9.5. 彙編中的對應位置有存儲值的标号 = C語言中的指針變量

像前文所解析的代碼中類似于這樣的:

LABEL1:.word Value2      

比如:

_TEXT_BASE:
	.word	TEXT_BASE
              

所對應的含義是,有一個标号_TEXT_BASE

而該标号中對應的位置,所存放的是一個word的值,具體的數值是TEXT_BASE,此處的TEXT_BASE是在别處定義的一個宏,值是0x33D00000。

是以,即為:

有一個标号_TEXT_BASE,其對應的位置中,所存放的是一個word的值,值為

TEXT_BASE=0x33D00000      

總的來說,此種用法的含義,如果用C語言來表示,其實更加容易了解:

int *_TEXT_BASE = TEXT_BASE = 0x33D00000      

即:

int *_TEXT_BASE = 0x33D00000      
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
C語言中如何引用彙編中的标号
不過,對于這樣的類似于C語言中的指針的彙編中的标号,在C語言中調用到的話,卻是這樣引用的:
/* for the following variables, see start.S */
extern ulong _armboot_start;	/* code start */
extern ulong _bss_start;	/* code + data end == BSS start */
......
	IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
......
                  
而不是我原以為的,直接當做指針來引用該變量的方式:
*IRQ_STACK_START = *_armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
                  
其中,對應的彙編中的代碼為:
.globl _armboot_start
_armboot_start:
	.word _start
                  
是以,針對這點,還是需要注意一下的。至少以後如果自己寫代碼的時候,在C語言中引用彙編中的global的标号的時候,知道是如何引用該變量的。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
【總結】

彙編中類似這樣的代碼:

label1: .word value2

就相當于C語言中的:

int *label1 = value2

但是在C語言中引用該标号/變量的時候,卻是直接拿來用的,就像這樣:

label1 = other_value

其中label1就是個int型的變量。

3.9.6. 彙編中的ldr+标号,來實作C中的函數調用

接着上面的内容,繼續解釋,對于彙編中這樣的代碼:

第一種:

ldr pc, 标号1
......
标号1:.word 标号2
......
标号2:
	......(具體要執行的代碼)
              

或者是,

第二種:

ldr pc, 标号1
......
标号1:.word XXX(C語言中某個函數的函數名)
              

的意思就是,将位址為标号1中内容載入到pc中。

而位址為标号1中的内容,就是标号2。

TEXT_BASE=0x33D00000      

是以上面第一種的意思:

就很容易看出來,就是把标号2這個位址值,給pc,即實作了跳轉到标号2的位置執行代碼,

就相當于調用一個函數,該函數名為标号2.

第二種的意思,和上面類似,是将C語言中某個函數的函數名,即某個位址值,給pc,實作調用C中對應的那個函數。

兩種做法,其含義用C語言表達,其實很簡單:

PC = *(标号1) = 标号2

例 3.1. 彙編中的ldr加标号實作函數調用 示例

舉個例子就是:

第一種:

......
	ldr	pc, _software_interrupt
......
_software_interrupt:	.word software_interrupt
......
software_interrupt:
	get_bad_stack
	bad_save_user_regs
	bl 	do_software_interrupt
                  

就是實作了将标号1,_software_interrupt,對應的位置中的值,标号2,software_interrupt,給pc,即實作了将pc掉轉到software_interrupt的位置,即實作了調用函數software_interrupt的效果。

第二種:

ldr	pc, _start_armboot

_start_armboot:	.word start_armboot
                  

含義就是,将标号1,_start_armboot,所對應的位置中的值,start_armboot給pc,即實作了調用函數start_armboot的目的。

其中,start_armboot是C語言檔案中某個C語言的函數。

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
總結彙編中實作函數調用的方式
彙編中,實作函數調用的效果,有如下兩種方法:
  1. 方法1
    ldr pc, 标号1
    ......
    标号1:.word 标号2
    ......
    标号2:
    	......(具體要執行的代碼)
                              
  2. 方法2
    ldr pc, 标号1
    ......
    标号1:.word XXX(C語言中某個函數的函數名)
                              

3.9.7. 彙編中設定某個寄存器的值或給某個位址指派

在彙編代碼start.S中,看到不止一處, 類似于這樣的代碼:

形式1:

# define pWTCON		0x53000000
......
	ldr     r0, =pWTCON
	mov     r1, #0x0
	str     r1, [r0]
              

或者是,

形式2:

# define INTSUBMSK	0x4A00001C
......
	ldr	r1, =0x7fff
	ldr	r0, =INTSUBMSK
	str	r1, [r0]
              

其含義,都是将某個值,賦給某個位址,此處的位址,是用宏定義來定義的,對應着某個寄存器的位址。

其中,形式1是直接通過mov指令來将0這個值賦給r1寄存器,和形式2中的通過ldr僞指令來将0x3ff賦給r1寄存器,兩者差別是,前者是因為已經确定所要賦的值0x0是mov的有效操作數,而後者對于0x3ff不确定是否是mov的有效操作數

Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
警告
如果不是,則該指令無效,編譯的時候,也無法通過編譯,會出現類似于這樣的錯誤::
start.S: Assembler messages:
    start.S:149: Error: invalid constant -- 'mov r1,#0xFFEFDFFF'
    make[1]: *** [start.o] 錯誤 1
    make: *** [cpu/arm920t/start.o] 錯誤 2
                  

是以才用ldr僞指令,讓編譯器來幫你自動判斷:

  1. 如果該操作數是mov的有效操作數,那麼ldr僞指令就會被翻譯成對應的mov指令

    例 3.2. 

    舉例說明:

    彙編代碼:

    # define pWTCON		0x53000000
    ......
    	ldr     r0, =pWTCON
                              
    被翻譯後的真正的彙編代碼:
    33d00068:	e3a00453 	mov	r0, #1392508928	; 0x53000000
                              
  2. 如果該操作數不是mov的有效操作數,那麼ldr僞指令就會被翻譯成ldr指令

    例 3.3. 

    舉例說明:

    彙編代碼:

    ldr	r1, =0x7fff
                              
    被翻譯後的真正的彙編代碼:
    33d00080:	e59f13f8 	ldr	r1, [pc, #1016]	; 33d00480 <fiq+0x60>
    ......
    33d00480:	00007fff 	.word	0x00007fff
                              
    即把ldr僞指令翻譯成真正的ldr指令,并且另外配置設定了一個word的位址空間用于存放該數值,然後用ldr指令将對應位址中的值載入,指派給r1寄存器。
Uboot中start.S源碼的指令級的詳盡解析正文之前第 1 章 start.S詳解第 2 章 start.S的總結第 3 章 相關知識點詳解參考書目
總結彙編中給某個位址指派的方法
彙編中,一個常用的,用來給某個位址指派的方法,類似如下形式:
#define 宏的名字  寄存器位址
......
	ldr	r1, =要賦的值
	ldr	r0, =宏的名字
	str	r1, [r0]
                  

參考書目

[1] 2010年6月 最新TQ2440CD光牒下載下傳 (Linux核心,WinCE的eboot,uboot均有更新)

[2] .globl,.word,.balignl的文法

[3] label的解釋

[4] ldr的文法

[5] ldr指令

[6] ldr指令

[7] .word的文法

[8] ARM7體系結構

[9] bootloader

[10] S3C2440相關的軟硬體資料

[11] S3C2440的CPU的datasheet:s3c2440a_um_rev014_040712.pdf

[12] 僞指令ldr文法和含義

[13] ARM9 2410移植之ARM中斷原理, 中斷嵌套的誤區,中斷号的怎麼來的

[14] adr指令的文法和含義

[15] ARM協處理器

[16] ARM920T

[17] CP15的各個寄存器的含義解釋

[18] Invalidate ICache and DCache

[19] Invalidate TLB(s)

[20] Control register

[21] Domain access control

[22] ARM920T的CPU的7種模式

[23] ARM Linux Kernel Boot Requirements

[24] 嵌入式系統之WATCHDOG(看門狗)概述

[25] ARM流水線和program counter(PC)的增量

[26] Predeclared register names

[27] Predeclared extension register names

[28] Predeclared coprocessor names

[29] mov的操作數的取指範圍

[30] ARM Processor Instruction Set

[31] ARM9流水線PC=PC+8

[32] Strange behaviour of ldr [pc, #value]

繼續閱讀