s3c2410完全開發(轉)

作者:ploor 提交日期:2007-4-8 12:03:00 <script language="javascript" src="http://hot.tianyaclub.com/hot.js" type="text/javascript"></script>
一.簡介

 本書面向由傳統51單片機轉向ARM嵌入式開發的硬件工程師、由硬件轉嵌
入式軟件開發的工程師、沒有嵌入式開發經驗的軟件工程師。分9個部分:

1、開發環境建立
2、S3C2410功能部件介紹與實驗(含實驗代碼)
3、bootloader vivi詳細註釋
4、linux移植
5、linux驅動
6、yaffs文件系統詳解
7、調試工具
8、GUI開發簡介
9、UC/OS移植
通過學習第二部分,即可瞭解基於ARM CPU的嵌入式開發所需要的外圍器
件及其接口。對應的實驗代碼實現了對這些接口的操作,這可以讓硬件工程師形
成一個嵌入式硬件開發的概念。這部分也可以當作S3C2410的數據手冊來使用。

一個完整的嵌入式linux系統包含4部分內容:bootloader、parameters、kernel、
root file system。3、4、5、6部分詳細介紹了這4部分的內容,這是linux底層軟
件開發人員應該掌握的。通過學習這些章節,您可以詳細瞭解到如何在一個裸板
上裁減、移植linux,如何構造自己的根文件系統,如何編寫適合客戶需求的驅
動程序——驅動程序這章將結合幾個經典的驅動程序進行講解。您還可以瞭解到
在用在nand flash上的非常流行的yaffs文件系統是如何工作的,本書將結合yaffs
代碼詳細介紹yaffs文件系統。

第7部分介紹了嵌入式linux開發中使用gdb進行調試的詳細過程。






 此文檔目前完成了1、2、3部分,後面部分將陸續完成。希望能對各位在嵌
入式開發方面獻上棉力。



 歡迎來信指出文中的不足與錯誤,歡迎來信探討技術問題。



Email :[email protected]

MSN :[email protected]

QQ :17653039



二.建立開發環境

(1)編譯器arm-linux-gcc-3.4.1

下載地址:

ftp://ftp.handhelds.org/projects/toolchain/arm-linux-gcc-3.4.1.tar.bz2

執行如下命令安裝:

bunzip2 arm-linux-gcc-3.4.1.tar.bz2

 tar xvf arm-linux-gcc-3.4.1.tar -C /

生成的編譯工具在目錄/usr/local/arm/3.4.1/bin下,修改/etc/profile,
增加如下一行。這可以讓我們直接運行arm-linux-gcc,而不必將其絕對路徑都
寫出來,不過這得重新啓動後才生效:

pathmunge /usr/local/arm/3.4.1/bin

(2)Jflash-s3c2410:S3C2410芯片的JTAG工具

我們的第一個程序就是通過它下載到開發板上的nor flash或者nand flash
上去的。把它放到/usr/local/bin目錄下。

下載地址:e

ftp://ftp.mizi.com/pub/linuette/SDK/1.5/target/box/Jflash/Jflash-
s3c2410



 注意:步驟3您現在不必理會,可以等進行到“調試”部分時再回過頭來看。

(3)安裝gdb調試工具

下載地址:

http://www.gnu.org/software/gdb/download/

http://ftp.gnu.org/gnu/gdb/gdb-6.3.tar.gz

執行如下命令安裝:

 a.安裝在主機上運行的arm-linux-gdb工具:

tar xvzf gdb-6.3.tar.gz

 cd gdb6.3

./configure --target=arm-linux

 make

 make install

 此時,在/usr/local/bin中生成arm-linux-gdb等工具

 b.繼續上面的步驟,安裝gdbserver。需要將此工具下載到開發板上運
行,這在後面會詳細描述:

 cd gdbserver

 export CC=/usr/local/arm/3.4.1/bin/arm-linux-gcc

 ./configure arm-linux

 make

 此時在當前目錄中生成了gdbserver工具,當我們講到如何調試時,
會把這個文件下載到開發板上去。








三.S3C2410基礎實驗

本章將逐一介紹S3C2410各功能模塊,並結合簡單的程序進行上機實驗。您
不必將本章各節都看完,完全可以看了一、兩節,得到一個大概的印象之後,就
開始下一章。本章可以當作手冊來用。

注意:瞭解S3C2410各部件最好的參考資料是它的數據手冊。本文不打算翻
譯該手冊,在進行必要的講解後,進行實際實驗——這纔是本文的重點。

(1)實驗一:LED_ON

led_on.s只有7條指令,它只是簡單地點亮發光二極管LED1。本實驗的目
的是讓您對開發流程有個基本概念。



實驗步驟:

a.把PC並口和開發板JTAG接口連起來、確保插上“BOOT SEL”跳線、上
電(呵呵,廢話,如果以後實驗步驟中未特別指出,則本步驟省略)


b.進入LED_ON目錄後,執行如下命令生成可執行文件led_on:

 make

c.執行如下命令將led_on寫入nand flash:

 i. Jflash-s3c2410 led_on /t=5

 ii.當出現如下提示時,輸入0並回車:

iii.當出現如下提示時,輸入0並回車:



 iv.當再次出現與步驟ii相同的提示時,輸入2並回車

 d.按開發板上reset鍵後可看見LED1被點亮了



實驗步驟總地來說分3類:編寫源程序、編譯/連接程序、燒寫代碼。

先看看源程序led_on.s:

1 .text

2 .global _start

3 _start:

4 LDR R0,=0x56000010 @R0設爲GPBCON寄存器。此寄存器

@用於選擇端口B各引腳的功能:

@是輸出、是輸入、還是其他

5 MOV R1,#0x00004000

6 STR R1,[R0] @設置GPB7爲輸出口



7 LDR R0,=0x56000014 @R0設爲GPBDAT寄存器。此寄存器

@用於讀/寫端口B各引腳的數據


8 MOV R1,#0x00000000 @此值改爲0x00000080,

@可讓LED1熄滅

9 STR R1,[R0] @GPB7輸出0,LED1點亮



10 MAIN_LOOP:

11 B MAIN_LOOP

 對於程序中用到的寄存器GPBCON、GPBDAT,我稍作描述,具體寄存器的操作
可看實驗三:I/O PORTS。GPBCON用於選擇PORT B的11根引腳的功能:輸出、輸
入還是其他特殊功能。每根引腳用2位來設置:00表示輸入、01表示輸出、10表
示特殊功能、11保留不用。LED1-3的引腳是GPB7-GPB10,使用GPBCON中位[12:13]、
[13:14]、[15:16]、[17:18]來進行功能設置。GPBDAT用來讀/寫引腳:GPB0對應
位0、GPB1對應位1,諸如此類。當引腳設爲輸出時,寫入0或1可使相應引腳輸出
低電平或高電平。



 程序很簡單,第4、5、6行3條指令用於將LED1對應的引腳設成輸出引腳;
第7、8、9行3條指令讓這條引腳輸出0;第11行指令是個死循環。

 實驗步驟b中,指令“make”的作用就是編譯、連接led_on.s源程序。Makefile
的內容如下:

1 led_on:led_on.s

 2 arm-linux-gcc -g -c -o led_on.o led_on.s

 3 arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_tmp.o

 4 arm-linux-objcopy -O binary -S led_on_tmp.o led_on

5 clean:

 6 rm -f led_on

 7 rm -f led_on.o

 8 rm -f led_on_tmp.o

 make指令比較第1行中文件led_on和文件led_on.s的時間,如果led_on
的時間比led_on.s的時間舊(led_on未生成時,此條件默認成立),則執行第2、
3、4行的指令更新led_on。您也可以不用指令make,而直接一條一條地執行2、
3、4行的指令——但是這樣多累啊。第2行的指令是預編譯,第3行是連接,
第4行是把ELF格式的可執行文件led_on_tmp.o轉換成二進制格式文件led_on。
執行“make clean”時強制執行6、7、8行的刪除命令。

 注意:Makefile文件中相應的命令行前一定有一個製表符(TAB)





 彙編語言可讀性太差,現在請開始實驗二,我用C語言來實現了同樣的功能,
而以後的實驗,我也儘可能用C語言實現。

(2)實驗二:LED_ON_C

 C語言程序執行的第一條指令,並不在main函數中。當我們生成一個C程序
的可執行文件時,編譯器總是在我們的代碼前加一段固定的代碼——crt0.o,它
是編譯器自帶的一個文件。此段代碼設置C程序的堆棧等,然後調用main函數。
很可惜,在我們的裸板上,這段代碼無法執行,所以我們得自己寫一個。這段代
碼很簡單,只有3條指令。

 crt0.s代碼:


1 .text

2 .global _start

3 _start:

 4 ldr sp, =1024*4 @設置堆棧,注意:不能大於4k

 @nand flash中的代碼在復位後會

@移到內部ram中,它只有4k

5 bl main @調用C程序中的main函數

6 halt_loop:

 7 b halt_loop



 現在,我們可以很容易寫出控制LED的程序了,led_on_c.c代碼如下:



1 #define GPBCON (*(volatile unsigned long *)0x56000010)

2 #define GPBDAT (*(volatile unsigned long *)0x56000014)

3 int main()

4 {

 5 GPBCON = 0x00004000; //設置GPB7爲輸出口

 6 GPBDAT = 0x00000000; //令GPB7輸出0

 7 return 0;

8 }



最後,我們來看看Makefile:

1 led_on_c : crt0.s led_on_c.c

2 arm-linux-gcc -g -c -o crt0.o crt0.s

3 arm-linux-gcc -g -c -o led_on_c.o led_on_c.c

4 arm-linux-ld -Ttext 0x0000000 -g crt0.o led_on_c.o -o
led_on_c_tmp.o

5 arm-linux-objcopy -O binary -S led_on_c_tmp.o led_on_c

6 clean:

7 rm -f led_on_c

8 rm -f led_on_c.o

9 rm -f led_on_c_tmp.o

10 rm -f crt0.o



第2、3行分別對源程序crt0.s、led_on_c.c進行預編譯,第4行將預編譯
得到的結果連接起來,第5行把連接得到的ELF格式可執行文件led_on_c_tmp.o
轉換成二進制格式文件led_on_c。

好了,可以開始上機實驗了:

實驗步驟:

a.進入LED_ON_C目錄後,執行如下命令生成可執行文件led_on_c:

 make

b.執行如下命令將led_on_c寫入nand flash:

 i. Jflash-s3c2410 led_on_c /t=5

 ii.當出現如下提示時,輸入0並回車:


K9S1208 NAND Flash JTAG Programmer Ver 0.0

0:K9S1208 Program 1:K9S1208 Pr BlkPage 2: Exit

 Select the function to test :

 iii.當出現如下提示時,輸入0並回車:

 Input target block number:

 iv.當出現與步驟ii相同的提示時,輸入2並回車

 c.按開發板上reset鍵後可看見LED1被點亮了



目錄LEDS中的程序是使用4個LED從0到15輪流計數,您可以試試:

a.進入目錄後make
b.Jflash-s3c2410 leds /t=5
c.reset運行





另外,如果您有興趣,可以使用如下命令看看二進制可執行文件的反彙編碼:

 arm-linux-objdump -D -b binary -m arm xxxxx(二進制可執行文件名)



注意:本文的所有程序均在SOURCE目錄中,各程序所在目錄均爲大寫,其
可執行文件名爲相應目錄名的小寫,比如LEDS目錄下的可執行文件爲leds。以
後不再贅述如何燒寫程序:直接運行Jflash-s3c2410即可看到提示。





(3)實驗三:I/O PORTS

 請打開S3C2410數據手冊第9章IO/ PORTS,I/O PORTS含GPA、GPB、..、
GPH八個端口。它們的寄存器是相似的:GPxCON用於選擇引腳功能,GPxDAT用
於讀/寫引腳數據,GPxUP用於確定是否使用內部上拉電阻(x爲A、B、..、H,
沒有GPAUP寄存器)。

1、PORT A與PORT B-H在功能選擇方面有所不同,GPACON中每一位對應一根引
腳(共23根引腳)。當某位設爲0時,相應引腳爲輸出引腳,此時我們可以在
GPADAT中相應位寫入0或1讓此引腳輸出低電平或高電平;當某位設爲1時,
相應引腳爲地址線或用於地址控制,此時GPADAT無用。一般而言GPACON通
常設爲全1,以便訪問外部存儲器件。PORT A我們暫時不必理會。
2、PORT B-H在寄存器操作方面完全相同。GPxCON中每兩位控制一根引腳:00
表示輸入、01表示輸出、10表示特殊功能、11保留不用。GPxDAT用於讀/
寫引腳:當引腳設爲輸入時,讀此寄存器可知相應引腳的狀態是高是低;當
引腳設爲輸出時,寫此寄存器相應位可令此引腳輸出低電平或高電平。GpxUP:
某位爲0時,相應引腳無內部上拉;爲1時,相應引腳使用內部上拉。


其他寄存器的操作在後續相關章節使用到時再描述;PORT A-H中引腳的特殊
功能比如串口引腳、中斷引腳等,也在做相關實驗時再描述。



目錄KEY_LED中的程序功能爲:當K1-K4中某個按鍵按下時,LED1-LED4中
相應LED點亮。

key_led.c代碼:







1 #define GPBCON (*(volatile unsigned long *)0x56000010)

2 #define GPBDAT (*(volatile unsigned long *)0x56000014)



3 #define GPFCON (*(volatile unsigned long *)0x56000050)

4 #define GPFDAT (*(volatile unsigned long *)0x56000054)



/*

LED1-4對應GPB7-10

*/

5 #define GPB7_out (1<<(7*2))

6 #define GPB8_out (1<<(8*2))

7 #define GPB9_out (1<<(9*2))

8 #define GPB10_out (1<<(10*2))



/*

K1-K3對應GPF1-3

K4對應GPF7

*/

9 #define GPF1_in ~(3<<(1*2))

10 #define GPF2_in ~(3<<(2*2))

11 #define GPF3_in ~(3<<(3*2))

12 #define GPF7_in ~(3<<(7*2))





13 int main()

{

 //LED1-LED4對應的4根引腳設爲輸出

14 GPBCON =GPB7_out | GPB8_out | GPB9_out | GPB10_out ;



//K1-K4對應的4根引腳設爲輸入

 15 GPFCON &= GPF1_in & GPF2_in & GPF3_in & GPF7_in ;



 16 while(1){

 //若Kn爲0(表示按下),則令LEDn爲0(表示點亮)

 17 GPBDAT = ((GPFDAT & 0x0e)<<6) | ((GPFDAT & 0x80)<<3);
}



 18 return 0;

}



實驗步驟:


a.進入目錄KEY_LED,運行make命令生成key_led
b.燒寫key_led















(4)實驗四:arm-linux-ld

 在開始後續實驗之前,我們得了解一下arm-linux-ld連接命令的使用。在
上述實驗中,我們一直使用類似如下的命令進行連接:

arm-linux-ld -Ttext 0x00000000 crt0.o led_on_c.o -o led_on_c_tmp.o

我們看看它是什麼意思:-o選項設置輸出文件的名字爲led_on_c_tmp.o;
“--Ttext 0x00000000”設置代碼段的起始地址爲0x00000000;這條指令的作用就
是將crt0.o和led_on_c.o連接成led_on_c_mp.o可執行文件,此可執行文件的代
碼段起始地址爲0x00000000。

我們感興趣的就是“—Ttext”選項!進入LINK目錄,link.s代碼如下:

1 .text

2 .global _start

3 _start:

4 b step1

5 step1:

6 ldr pc, =step2

7 step2:

8 b step2





Makefile如下:

1 link:link.s

2 arm-linux-gcc -c -o link.o link.s

3 arm-linux-ld -Ttext 0x00000000 link.o -o link_tmp.o

4 # arm-linux-ld -Ttext 0x30000000 link.o -o link_tmp.o

5 arm-linux-objcopy -O binary -S link_tmp.o link

6 arm-linux-objdump -D -b binary -m arm link >ttt.s

7 # arm-linux-objdump -D -b binary -m arm link >ttt2.s

8 clean:

9 rm -f link

10 rm -f link.o

11 rm -f link_tmp.o



實驗步驟:

1.進入目錄LINK,運行make生成arm-linux-ld選項爲“-Ttext 0x00000000”
的反彙編碼ttt.s

2.make clean

3.修改Makefile:將第4、7行的“#”去掉,在第3、6行前加上“#”

4.運行make生成arm-linux-ld選項爲“-Ttext 0x30000000”的反彙編碼ttt2.s



link.s程序中用到兩種跳轉方法:b跳轉指令、直接向pc寄存器賦值。我們先
把在不同“—Ttext”選項下,生成的可執行文件的反彙編碼列出來,再詳細分析這
兩種不同指令帶來的差異。

ttt.s: ttt2.s

0: eaffffff b 0x4 0: eaffffff b 0x4


4: e59ff000 ldr pc, [pc, #0] ; 0xc 4: e59ff000 ldr pc, [pc, #0] ; 0xc

8: eafffffe b 0x8 8: eafffffe b 0x8

c: 00000008 andeq r0, r0, r8 c: 30000008 tsteq r0, #8 ; 0x8



先看看b跳轉指令:它是個相對跳轉指令,其機器碼格式如下:



[31:28]位是條件碼;[27:24]位爲“1010”時,表示B跳轉指令,爲“1011”時,表示BL
跳轉指令;[23:0]表示偏移地址。使用B或BL跳轉時,下一條指令的地址是這樣計算的:將指
令中24位帶符號的補碼立即數擴展爲32(擴展其符號位);將此32位數左移兩位;將得到的值
加到pc寄存器中,即得到跳轉的目標地址。我們看看第一條指令“b step1”的機器碼eaffffff:

1. 24位帶符號的補碼爲0xffffff,將它擴展爲32得到:0xffffffff
2.將此32位數左移兩位得到:0xfffffffc,其值就是-4
3.pc的值是當前指令的下兩條指令的地址,加上步驟2得到的-4,這恰好是第
二條指令step1的地址
各位不要被被反彙編代碼中的“b 0x4”給迷惑了,它可不是說跳到絕對地址0x4
處執行,絕對地址得像上述3個步驟那樣計算。您可以看到b跳轉指令是依賴於當
前pc寄存器的值的,這個特性使得使用b指令的程序不依賴於代碼存儲的位置——
即不管我們連接命令中“--Ttext”爲何,都可正確運行。



再看看第二條指令ldr pc, =step2:從反彙編碼“ldr pc, [pc, #0]”可以看出,
這條指令從內存中某個位置讀出數據,並賦給pc寄存器。這個位置的地址是當前
pc寄存器的值加上偏移值0,其中存放的值依賴於連接命令中的“--Ttext”選項。
執行這條指令後,對於ttt.s,pc=0x00000008;對於ttt2.s, pc=0x30000008。於
是執行第三條指令“b step2”時,它的絕對地址就不同了:對於ttt.s,絕對地址
爲0x00000008;對於ttt.s,絕對地址爲0x30000008。



ttt2.s上電後存放的位置也是0,但是它連接的地址是0x30000000。我們以後
會經常用到“存儲地址和連接地址不同”(術語上稱爲加載時域和運行時域)的特性:
大多機器上電時是從地址0開始運行的,但是從地址0運行程序在性能方面總有很
多限制,所以一般在開始的時候,使用與位置無關的指令將程序本身複製到它的連
接地址處,然後使用向pc寄存器賦值的方法跳到連接地址開始的內存上去執行剩下
的代碼。在實驗5、6中,我們將會作進一步介紹。

arm-linux-ld命令中選項“-Ttext”也可以使用選項“-Tfilexxx”來代替,在
文件filexxx中,我們可以寫出更復雜的參數來使用arm-linux-ld命令——在實驗
6中,我們就是使用這種方法來指定連接參數的。






(5)實驗五:MEMORY CONTROLLER

 S3C2410提供了外接ROM、SRAM、SDRAM、NOR Flash、NAND Flash的接口。S3C2410
外接存儲器的空間被分爲8 BANKS,每BANK容量爲128M:當訪問BANKx(x從0到7)
所對應的地址範圍(x*128M到(x+1)*128M-1,BANK6、7有稍微差別,請參考下面
第5點BANKSIZE寄存器的說明)時,片選信號nGCSx有效。本文所用的開發板,使
用了64M的NAND Flash和64M的SDRAM:NAND Flash不對應任何BANK,它是通過幾
組寄存器來訪問的,在上電後,NAND Flash開始的4k數據被自動地複製到芯片內


部一個被稱爲“Steppingstone”的RAM上。Steppingstone被映射爲地址0,上面
的4k程序完成必要的初始化;SDRAM使用BANK6,它的物理起始地址爲
6*128M=0x30000000。請您打開S3C2410數據手冊,第5章的圖“Figure 5-1.
S3C2410X Memory Map after Reset”可讓您一目瞭然。

 在開始下面內容前,如果您對SDRAM沒什麼概念,建議先看看這篇文章《高
手進階,終極內存技術指南——完整/進階版》。當然,不看也沒關係,照着做就
行了。此文鏈接地址:

http://bbs.cpcw.com/viewthread.php?tid=196978&fpage=1&highlight=

本實驗介紹如何使用SDRAM,這需要設置13個寄存器。呵呵,別擔心,這些
寄存器很多是類似的,並且由於我們只使用了BANK6,大部分的寄存器我們不必
理會:

1.BWSCON:對應BANK0-BANK7,每BANK使用4位。這4位分別表示:
a.STx:啓動/禁止SDRAM的數據掩碼引腳,對於SDRAM,此位爲0;對於
SRAM,此位爲1。
b.WSx:是否使用存儲器的WAIT信號,通常設爲0
c.DWx:使用兩位來設置存儲器的位寬:00-8位,01-16位,10-32位,
11-保留。
d.比較特殊的是BANK0對應的4位,它們由硬件跳線決定,只讀。




對於本開發板,使用兩片容量爲32Mbyte、位寬爲16的SDRAM組成容量爲
64Mbyte、位寬爲32的存儲器,所以其BWSCON相應位爲:0010。對於本開發板,
BWSCON可設爲0x22111110:其實我們只需要將BANK6對應的4位設爲0010即可,其
它的是什麼值沒什麼影響,這個值是參考手冊上給出的。

2.BANKCON0-BANKCON5:我們沒用到,使用默認值0x00000700即可
3.BANKCON6-BANKCON7:設爲0x00018005
在8個BANK中,只有BANK6和BANK7可以使用SRAM或SDRAM,所以BANKCON6-7
與BANKCON0-5有點不同:


a.MT([16:15]):用於設置本BANK外接的是SRAM還是SDRAM:SRAM-0b00,
SDRAM-0b11
b.當MT=0b11時,還需要設置兩個參數:
Trcd([3:2]):RAS to CAS delay,設爲推薦值0b01

SCAN([1:0]):SDRAM的列地址位數,對於本開發板使用的SDRAM
HY57V561620CT-H,列地址位數爲9,所以SCAN=0b01。如果使用其他
型號的SDRAM,您需要查看它的數據手冊來決定SCAN的取值:00-8位,
01-9位,10-10位




4.REFRESH(SDRAM refresh control register):設爲0x008e0000+ R_CNT
其中R_CNT用於控制SDRAM的刷新週期,佔用REFRESH寄存器的[10:0]位,
它的取值可如下計算(SDRAM時鐘頻率就是HCLK):




 R_CNT = 2^11 + 1 – SDRAM時鐘頻率(MHz) * SDRAM刷新週期(uS)

在未使用PLL時,SDRAM時鐘頻率等於晶振頻率12MHz;SDRAM的刷新週期
在SDRAM的數據手冊上有標明,在本開發板使用的SDRAM HY57V561620CT-H的
數據手冊上,可看見這麼一行“8192 refresh cycles / 64ms”:所以,刷
新週期=64ms/8192 = 7.8125 uS。

對於本實驗,R_CNT = 2^11 + 1 – 12 * 7.8125 = 1955,

REFRESH=0x008e0000 + 1955 = 0x008e07a3


5.BANKSIZE:0x000000b2
位[7]=1:Enable burst operation

位[5]=1:SDRAM power down mode enable

位[4]=1:SCLK is active only during the access (recommended)

位[2:1]=010:BANK6、BANK7對應的地址空間與BANK0-5不同。BANK0-5
的地址空間都是固定的128M,地址範圍是(x*128M)到(x+1)*128M-1,x
表示0到5。但是BANK7的起始地址是可變的,您可以從S3C2410數據手冊
第5章“Table 5-1. Bank 6/7 Addresses”中瞭解到BANK6、7的地址範
圍與地址空間的關係。本開發板僅使用BANK6的64M空間,我們可以令位
[2:1]=010(128M/128M)或001(64M/64M):這沒關係,多出來的空間程序
會檢測出來,不會發生使用不存在的內存的情況——後面介紹到的
bootloader和linux內核都會作內存檢測。

位[6]、位[3]沒有使用




6.MRSRB6、MRSRB7:0x00000030

 能讓我們修改的只有位[6:4](CL),SDRAM HY57V561620CT-H不支持CL=1
的情況,所以位[6:4]取值爲010(CL=2)或011(CL=3)。



 只要我們設置好了上述13個寄存器,往後SDRAM的使用就很簡單了。本實驗
先使用匯編語言設置好SDRAM,然後把程序本身從Steppingstone(還記得嗎?本
節開始的時候提到過,復位之後NAND Flash開頭的4k代碼會被自動地複製到這裏)
複製到SDRAM處,然後跳到SDRAM中執行。

 本實驗源代碼在SDRAM目錄中, head.s開頭的代碼如下:

 1 bl disable_watch_dog

 2 bl memsetup

 3 bl copy_steppingstone_to_sdram

 4 ldr pc, =set_sp @跳到SDRAM中繼續執行

5 set_sp:

 6 ldr sp, =0x34000000 @設置堆棧

 7 bl main @跳轉到C程序main函數

8 halt_loop:

 9 b halt_loop



 爲了讓程序結構簡單一點,我都使用函數調用的方式。第一條指令是禁止
WATCH DOG,您如果細心的話,一定會發現程序LEDS運行得有些不正常,那是因
爲WATCH DOG在不斷地重啓系統。以前爲了程序簡單,我沒有把這段程序加上去。
往WTCON寄存器(地址0x53000000)寫入0即可禁止WATCH DOG。第二條指令設置本
節開頭所描述的13個寄存器,以便使用SDRAM。請您翻看實驗四最後一段文字,
往下程序做的事情就是:將Steppingstone中的代碼複製到SDRAM中(起始地址爲
0x30000000),然後向pc寄存器直接賦值跳到SDRAM中執行下一條指令“ldr sp,
=0x34000000”。再往後的代碼就和實驗二、三一樣了。

 最後我們來看看SDRAM目錄下的Makefile:

 1 sdram : head.s sdram.c

 2 arm-linux-gcc -c -o head.o head.s

 3 arm-linux-gcc -c -o sdram.o sdram.c


 4 arm-linux-ld -Ttext 0x30000000 head.o sdram.o -o sdram_tmp.o

 5 arm-linux-objcopy -O binary -S sdram_tmp.o sdram

 請看第4句,是否和實驗四聯繫起來了呢?忘記的人請回頭複習,不再羅嗦。



 在目錄SDRAM下執行make指令生成可執行文件sdram後,下載到板子上運行,
可以發現與LEDS程序相比,LED閃爍得更慢:這就對了,外部SDRAM的性能比起內
部SRAM來說性能是差些。

 把程序從性能更好的內部SRAM移到外部SDRAM中去,是否多此一舉呢?內部
SRAM只有4k大小,如果我們的程序大於4k,那麼就不能指望利用內部SRAM來運行
了。所以得想辦法把存儲在NAND Flash中的代碼,複製到SDRAM中去。對於NAND
Flash中的前4k,芯片自動把它複製到內部SRAM中,我們可以很輕鬆地再把它復
制到SDRAM中(實驗五中函數copy_steppingstone_to_sdram就做這事)。但是對於
4k之後的代碼,複製它就不那麼輕鬆了,這正是實驗六的內容:

(6)實驗六:NAND FLASH CONTROLLER

 當OM1、OM0都是低電平(請看數據手冊198頁)——即開發板插上BOOT SEL跳
線時,S3C2410從NAND Flash啓動:NAND Flash的開始4k代碼會被自動地複製到
內部SRAM中。我們需要使用這4k代碼來把更多的代碼從NAND Flash中讀到SDRAM
中去。NAND Flash的操作通過NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT和NFECC
六個寄存器來完成。在開始下面內容前,請打開S3C2410數據手冊和NAND Flash
K9F1208U0M的數據手冊。

 在S3C2410數據手冊218頁,我們可以看到讀寫NAND Flash的操作次序:

1. Set NAND flash configuration by NFCONF register.

2. Write NAND flash command onto NFCMD register.

3. Write NAND flash address onto NFADDR register.

4. Read/Write data while checking NAND flash status by NFSTAT
register. R/nB signal should be checked before read operation or
after program operation.

 下面依次介紹:

 1、NFCONF:設爲0xf830——使能NAND Flash控制器、初始化ECC、NAND Flash
片選信號nFCE=1(inactive,真正使用時再讓它等於0)、設置TACLS、TWRPH0、
TWRPH1。需要指出的是TACLS、TWRPH0和TWRPH1,請打開S3C2410數據手冊218頁,
可以看到這三個參數控制的是NAND Flash信號線CLE/ALE與寫控制信號nWE的時
序關係。我們設的值爲TACLS=0,TWRPH0=3,TWRPH1=0,其含義爲:TACLS=1個HCLK
時鐘,TWRPH0=4個HCLK時鐘,TWRPH1=1個HCLK時鐘。請打開K9F1208U0M數據手冊
第13頁,在表“AC Timing Characteristics for Command / Address / Data
Input”中可以看到:

CLE setup Time = 0 ns,CLE Hold Time = 10 ns,

ALE setup Time = 0 ns,ALE Hold Time = 10 ns,

WE Pulse Width = 25 ns

可以計算,即使在HCLK=100MHz的情況下,TACLS+TWRPH0+TWRPH1=6/100 uS=60
ns,也是可以滿足NAND Flash K9F1208U0M的時序要求的。

2、NFCMD:

 對於不同型號的Flash,操作命令一般不一樣。對於本板使用的K9F1208U0M,
請打開其數據手冊第8頁“Table 1. Command Sets”,上面列得一清二楚。


 3、NFADDR:無話可說

 4、NFDATA:只用到低8位

 5、NFSTAT:只用到位0,0-busy,1-ready

 6、NFECC:待補



 現在來看一下如何從NAND Flash中讀出數據,請打開K9F1208U0M數據手冊第
29頁“PAGE READ”,跟本節的第2段是遙相呼應啊,提煉出來羅列如下(設讀
地址爲addr):

1、NFCONF = 0xf830
2、在第一次操作NAND Flash前,通常復位一下:
NFCONF &= ~0x800 (使能NAND Flash)

NFCMD = 0xff (reset命令)

循環查詢NFSTAT位0,直到它等於1




 3、NFCMD = 0 (讀命令)

 4、這步得稍微注意一下,請打開K9F1208U0M數據手冊第7頁,那個表格列出
了在地址操作的4個步驟對應的地址線,A8沒用到:

NFADDR = addr & 0xff

 NFADDR = (addr>>9) & 0xff (注意了,左移9位,不是8位)

 NFADDR = (addr>>17) & 0xff (左移17位,不是16位)

 NFADDR = (addr>>25) & 0xff (左移25位,不是24位)

 5、循環查詢NFSTAT位0,直到它等於1

 6、連續讀NFDATA寄存器512次,得到一頁數據(512字節)

 7、NFCONF |= 0x800 (禁止NAND Flash)



 本實驗代碼在NAND目錄下,源代碼爲head.s、init.c和main.c。head.s調用
init.c中的函數來關WATCH DOG、初始化SDRAM、初始化NAND Flash,然後將main.c
中的代碼從NAND Flash地址4096開始處複製到SDRAM中,最後,跳到main.c中的
main函數繼續執行。代碼本身沒什麼難度,與前面程序最大的差別就是“連接腳
本”的引入:實驗4最後一段提到過在arm-linux-ld命令中,選項“-Ttext”可
以使用選項“-Tfilexxx”來代替。在本實驗中,使用“-Tnand.lds”。nand.lds
內容如下:

1 SECTIONS {

2 firtst 0x00000000 : { head.o init.o }

3 second 0x30000000 : AT(4096) { main.o }

4 }



 完整的連接腳本文件形式如下:

SECTIONS {

...

secname start BLOCK(align) (NOLOAD) : AT ( ldadr )

 { contents } >region :phdr =fill

...

}

並非每個選項都是必須的,僅介紹nand.lds用到的:


1、secname:段名,對於nand.lds,段名爲first和second
2、start:本段運行時的地址,如果沒有使用AT(xxx),則本段存儲的地址
也是start
3、AT( ldadr ):定義本段存儲(加載)的地址
4、{ contents }:決定哪些內容放在本段,可以是整個目標文件,也可以
是目標文件中的某段(代碼段、數據段等)





nand.lds的含義是:head.o放在0x00000000地址開始處,init.o放在hean.o
後面,它們的運行地址是0x00000000;main.o放在地址4096(0x1000)開始處,但
是它的運行地址在0x30000000,在運行前需要從4096處複製到0x30000000處。爲
了更形象一點,您可以打開反彙編文件ttt.s,現摘取部分內容如下:

00000000 <.data>:

1 0: e3a0da01 mov sp, #4096 ; 0x1000

2 4: eb00000b bl 0x38

3 8: eb000011 bl 0x54

4 c: eb000042 bl 0x11c

 ...

5 1000: e1a0c00d mov ip, sp

6 1004: e92dd800 stmdb sp!, {fp, ip, lr, pc}

7 1008: e24cb004 sub fp, ip, #4 ; 0x4

8 100c: e59f1058 ldr r1, [pc, #88] ; 0x106c

 ...



上面的第1-4行與head.s中的前面4行代碼對應,第2-4行調用init.c中的函
數disable_watch_dog、memsetup、init_nand;再看看第5行,“1000”的得來
正是由於設置了“AT(4096)”,這行開始的是main.c中的第一個函數Rand()。

如果您想進一步瞭解連接腳本如何編寫,請參考《Using ld The GNU linker》
(在目錄“參考資料”下)。





上面的幾個程序都是在擺弄那幾個LED,現在來玩點有意思的:

(7)實驗七:UART

 UART的寄存器有11X3個(3個UART)之多,我選最簡單的方法來進行本實
驗,用到的寄存器也有8個。不過初始化就用去了5個寄存器,剩下的3個用於接
收、發送數據。如此一來,操作UART倒也不復雜。本板使用UART0:

 1、初始化:

把使用到的引腳GPH2、GPH3定義爲TXD0、RXD0:

GPHCON |= 0xa0

GPHUP |= 0x0c (上拉)

b.ULCON0 ( UART channel 0 line control register ):設爲0x03

此值含義爲:8個數據位,1個停止位,無校驗,正常操作模式(與之相對的
是Infra-Red Mode,此模式表示0、1的方式比較特殊)。

 c.UCON0 (UART channel 0 control register ):設爲0x05

除了位[3:0],其他位都使用默認值。位[3:0]=0b0101表示:發送、接收都


使用“中斷或查詢方式”——本實驗使用查詢查詢方式。

 d.UFCON0 (UART channel 0 FIFO control register ):設爲0x00

每個UART內部都有一個16字節的發送FIFO和接收FIFO,但是本實驗
不使用FIFO,設爲默認值0

 e.UMCON0 (UART channel 0 Modem control register ):設爲0x00

 本實驗不使用流控,設爲默認值0

 f.UBRDIV0 ( R/W Baud rate divisior register 0 ):設爲12

本實驗未使用PLL, PCLK=12MHz,設置波特率爲57600,則由公式

UBRDIVn = (int)(PCLK / (bps x 16) ) –1

可以計算得UBRDIV0 = 12,請使用S3C2410數據手冊第314頁的誤差公式
驗算一下此波特率是否在可容忍的誤差範圍之內,如果不在,則需要更換另
一個波特率(本實驗使用的57600是符合的)。

 2、發送數據:

a.UTRSTAT0 ( UART channel 0 Tx/Rx status register ):
位[2]:無數據發送時,自動設爲1。當我們要使用串口發送數據時,
先讀此位以判斷是否有數據正在佔用發送口。




 位[1]:發送FIFO是否爲空,本實驗未用此位

 位[0]:接收緩衝區是否有數據,若有,此位設爲1。本實驗中,需
要不斷查詢此位一判斷是否有數據已經被接收。

b.UTXH0 (UART channel 0 transmit buffer register ):
把要發送的數據寫入此寄存器。




3、接收數據:
a.UTRSTAT0:如同上述“2、發送數據”所列,我們用到位[0]

b.URXH0 (UART channel 0 receive buffer register ):




 當查詢到UTRSTAT0 位[0]=1時,讀此寄存器獲得串口接收到的數據。



 串口代碼在UART目錄下的serial.c文件中,包含三個函數:init_uart,putc,
getc。代碼如下:

1 void init_uart( )

2 {//初始化UART

3 GPHCON |= 0xa0; //GPH2,GPH3 used as TXD0,RXD0

4 GPHUP = 0x0c; //GPH2,GPH3內部上拉



5 ULCON0 = 0x03; //8N1(8個數據位,無校驗位,1個停止位)

6 UCON0 = 0x05; //查詢方式

7 UFCON0 = 0x00; //不使用FIFO

8 UMCON0 = 0x00; //不使用流控

9 UBRDIV0 = 12; //波特率爲57600

10 }

11 void putc(unsigned char c)

12 {

13 while( ! (UTRSTAT0 & TXD0READY) ); //不斷查詢,直到可以發送數據

14 UTXH0 = c; //發送數據

15 }


16 unsigned char getc( )

17 {

18 while( ! (UTRSTAT0 & RXD0READY) ); //不斷查詢,直到接收到了數據

19 return URXH0; //返回接收到的數據

20 }

 本實驗將串口輸入的數字、字母加1後再從串口輸出,比如輸入A就輸出B。

 實驗步驟:

1、進入UART目錄運行“make”命令生成可執行文件uart,下載到開發板上

2、在主機上運行串口工具minicom:

a.在終端上運行“minicom –s”啓動minicom,出來如下界面:




b.進入“Serial port setup”:





鍵入相應字母設置各項,比如:我用的是串口1,所以在A項設置爲
“/dev/ttyS0”;按“E”,設置波特率爲57600,8N1;按F、G,設置無流控。
最後回車退出,回到步驟a所示的界面。

c.可以選擇“Save setup as dfl”,這樣下次啓動minicom時可以不再進行
步驟b的設置。
d.選擇“Exit”退出設置界面。


3、復位開發板後,您可以在minicom上體驗一下本程序了。





(8)實驗八:printf、scanf

 本實驗利用串口實現兩個很常用的函數:printf和scanf。

 試驗代碼存放在stdio目錄下,其中lib目錄中包含了實現printf和scanf函
數的主要文件。大部分文件摘自linux2.6內核,本試驗主要在vsprintf.c文件的
基礎上,封裝了printf和scanf函數(print.c文件中)。在vsprintf.c文件中,需
要用到一些乘法和除法操作,文件lib1funcs.S實現除法、求模操作;div64.h
和div64.S實現64位的除法操作;muldi3.c實現乘法操作。另外,stdio目錄下的
serial.c文件實現了putc和getc函數,這兩個函數與具體板子的情況相關,所以
我沒把它們放入lib目錄下。

 此試驗的Makefile文件將lib目錄裏的文件生成靜態庫文件libc.a,現在,
你把stdio.h文件包含進你的代碼後,就可以非常方便地實現輸入、輸出了——
請參考本試驗代碼。

 實驗:與試驗7類似,以波特率57600 8N1打開串口,將make後生成的可執行
文件exe燒入開發板,可在minicom上觀察到結果:程序從minicom中接收字串,
從這些字串中檢出數字,分別以10進制和16進制方式打印出來。

 另外,您可以參考stdio_test_lib目錄下的代碼,寫出自己的測試程序。這
個目錄裏面的文件基本與本試驗一樣,只是在lib目錄下僅僅保留了libc.a庫文
件。

 最後,sys/lib/stdio目錄中存放的是標準輸入、輸出的代碼,其中的


Makefile可以生成libc.a文件並存放在sys/lib目錄下,以後本人編寫的庫函數
都也將存放在此目錄下。





(9)實驗九:INTERRUPT CONTROLLER

 S3C2410數據手冊354頁“Figure 14-1. Interrupt Process Diagram”非常簡潔地
概括了中斷處理的流程,我把這個圖搬過來,然後結合用到的寄存器用文字解釋
一下。

圖1 Interrupt Process Diagram

 SUBSRCPND和SRCPND寄存器表明有哪些中斷被觸發了,正在等待處理
(pending);SUBMASK(INTSUBMSK寄存器)和MASK(INTMSK寄存器)用於屏蔽某些中
斷。圖中的“Request sources(with sub -register)”表示的是INT_RXD0、
INT_TXD0等11箇中斷源,它們不同於“Request sources(without sub
-register)”:

1、“Request sources(without sub -register)”中的中斷源被觸發之後,SRCPND
寄存器中相應位被置1,如果此中斷沒有被INTMSK寄存器屏蔽、或者是快中斷(FIQ)
的話,它將被進一步處理

2、對於“Request sources(with sub -register)”中的中斷源被觸發之後,
SUBSRCPND寄存器中的相應位被置1,如果此中斷沒有被INTSUBMSK寄存器屏蔽的
話,它在SRCPND寄存器中的相應位也被置1,之後的處理過程就和“Request
sources(without sub -register)”一樣了

 繼續沿着圖1前進:在SRCPND寄存器中,被觸發的中斷的相應位被置1,等待
處理:

1、如果被觸發的中斷中有快中斷(FIQ)——MODE(INTMOD寄存器)中爲1的位對應
的中斷是FIQ,則CPU的FIQ中斷函數被調用。注意:FIQ只能分配一個,即INTMOD
中只能有一位設爲1。

2、對於一般中斷IRQ,可能同時有幾個中斷被觸發,未被INTMSK寄存器屏蔽的中
斷經過比較後,選出優先級最高的中斷——此中斷在INTPND寄存器中的相應位被
置1,然後CPU調用IRQ中斷處理函數。中斷處理函數可以通過讀取INTPND寄存器
來確定中斷源是哪個,也可以讀INTOFFSET寄存器來確定中斷源。

 請打開S3C2410數據手冊357頁,“Figure 14-2. Priority Generating Block”顯示
了各中斷源先經過6個一級優先級仲裁器選出各自優先級最高的中斷,然後再經
過二級優先級仲裁器選從中選出優先級最高的中斷。IRQ的中斷優先級由RIORITY


寄存器設定,請參考數據手冊365頁,RIORITY寄存器中ARB_SELn(n從0到6)用於
設定仲裁器n各輸入信號的中斷優先級,例如ARB_SEL6[20:19](0最高,其後各項
依次降低):

00 = REQ 0-1-2-3-4-5 01 = REQ 0-2-3-4-1-5

10 = REQ 0-3-4-1-2-5 11 = REQ 0-4-1-2-3-5

 RIORITY寄存器還有一項比較特殊的功能,如果ARB_MODEn設爲1,則仲裁器n
中輸入的中斷信號的優先級別將會輪換。例如ARB_MODE6設爲1,則仲裁器6的6
個輸入信號的優先級將如下輪換(見數據手冊358頁):

 If REQ0 or REQ5 is serviced, ARB_SEL bits are not changed at all.

If REQ1 is serviced, ARB_SEL bits are changed to 01b.

If REQ2 is serviced, ARB_SEL bits are changed to 10b.

If REQ3 is serviced, ARB_SEL bits are changed to 11b.

If REQ4 is serviced, ARB_SEL bits are changed to 00b.

意思即是:

 REQ0和REQ5的優先級不會改變

 當REQ1中斷被處理後,ARB_SEL6 = 0b01,即REQ1的優先級變成本仲裁器
中最低的(除去REQ5)

 當REQ2中斷被處理後,ARB_SEL6 = 0b10,即REQ2的優先級變成本仲裁器
中最低的(除去REQ5)

 當REQ3中斷被處理後,ARB_SEL6 = 0b11,即REQ3的優先級變成本仲裁器
中最低的(除去REQ5)

 當REQ4中斷被處理後,ARB_SEL6 = 0b00,即REQ4的優先級變成本仲裁器
中最低的(除去REQ5)



 現在來總結一下使用中斷的步驟:

1、當發生中斷IRQ時,CPU進入“中斷模式”,這時使用“中斷模式”下的堆棧;
當發生快中斷FIQ時,CPU進入“快中斷模式”,這時使用“快中斷模式”下的堆
棧。所以在使用中斷前,先設置好相應模式下的堆棧。

2、對於“Request sources(without sub -register)”中的中斷,將INTSUBMSK
寄存器中相應位設爲0

3、將INTMSK寄存器中相應位設爲0

4、確定使用此的方式:是FIQ還是IRQ。

a.如果是FIQ,則在INTMOD寄存器設置相應位爲1

 b.如果是IRQ,則在RIORITY寄存器中設置優先級

5、準備好中斷處理函數,

a.中斷向量:


在中斷向量設置好當FIQ或IRQ被觸發時的跳轉函數, IRQ、FIQ的中斷向量
地址分別爲0x00000018、0x0000001c(數據手冊79頁“Table 2-3. Exception
Vectors”)

b.對於IRQ,在跳轉函數中讀取INTPND寄存器或INTOFFSET寄存器的值來確
定中斷源,然後調用具體的處理函數

 c.對於FIQ,因爲只有一箇中斷可以設爲FIQ,無須判斷中斷源

 d.中斷處理函數進入和返回時需要花點心思:

 i.對於IRQ,進入和返回的代碼如下:


 sub lr, lr, #4 @計算返回地址

 stmdb sp!, { r0-r12,lr } @保存使用到的寄存器

 . .

 ldmia sp!, { r0-r12,pc }^ @中斷返回

 @^表示將spsr的值賦給cpsr

 ii.對於FIQ,進入和返回的代碼如下:

 sub lr, lr, #4 @計算返回地址

 stmdb sp!, { r0-r7,lr } @保存使用到的寄存器

 . .

 ldmia sp!, { r0-r7,pc }^ @快中斷返回,

@^表示將spsr的值賦給cpsr

 iii. 中斷返回之前需要清中斷:往SUBSRCPND(用到的話)、SRCPND、
INTPND中相應位寫1即可。對於INTPND,最簡單的方法就是“INTPND=INTPND”。

6、設置CPSR寄存器中的F-bit(對於FIQ)或I-bit(對於IRQ)爲0,開中斷



 本實驗使用按鍵K1-K4作爲4個外部中斷——EINT1-3、EINT7,當Kn按下時,
通過串口輸出“EINTn,Kn pressed!”,主程序讓4個LED輪流從0到15計數。對於
外部中斷,除了上面說的幾個寄存器外,還有幾個寄存器需要設置,請打開數據
手冊276頁“EXTERNAL INTERRUPT CONTROL REGISTER (EXTINTn)”:

1、EXTINT0-2:它們用於設置EINT0-24共25個外部中斷分別是低電平觸發、高電
平觸發、上升沿觸發、下降沿觸發或者“上升/下降沿觸發”(不知道確切的術語)。
對於本實驗,使用默認值:低電平觸發。

2、EINTFLT0-3:未用

3、EINTMASK、EINTPEND:呵呵,這對寄存器和上面的INTMSK、INTPND實在相似。
EINTMASK用於設置是否評比EINT4-23;EINTPEND表明有幾個外部中斷已經發生,
正在等待處理(pending),對某位寫入1可以讓此位清零。對於本實驗,EINTMASK
位[7]設爲0——使用EINT7。

4、GSTATUS0-4:未用



 本實驗代碼在INT目錄下,下面摘取與中斷相關的代碼:

(1)、head.s中:

 . .

1 msr cpsr_c, #0xd2 @進入中斷模式

2 ldr sp, =0x33000000 @設置中斷模式堆棧

3 msr cpsr_c, #0xdf @進入系統模式

4 ldr sp, =0x34000000 @設置系統模式堆棧

5 bl init_irq @調用中斷初始化函數,在init.c中

6 msr cpsr_c, #0x5f @設置I-bit=0,開IRQ中斷

 . .

7 HandleIRQ: @IRQ中斷向量跳轉函數

8 sub lr, lr, #4 @計算返回地址

9 stmdb sp!, { r0-r12,lr } @保存使用到的寄存器

10 ldr lr, =int_return @設置返回地址

11 ldr pc, =EINT_Handle @調用中斷處理函數EINT_Handle,


@在interrupt.c中

12 int_return:

13 ldmia sp!, { r0-r12,pc }^ @中斷返回,

@^表示將spsr的值複製到cpsr

(2)、init.c的中斷初始化函數init_irq:

1 #define EINT1 (2<<(1*2))

2 #define EINT2 (2<<(2*2))

3 #define EINT3 (2<<(3*2))

4 #define EINT7 (2<<(7*2))

5 void init_irq( )

6 {

7 GPFCON |= EINT1 | EINT2 | EINT3 | EINT7; //K1-K4對應

//EINT1-3和EINT7

8 GPFUP |= (1<<1) | (1<<2) | (1<<3) | (1<<7); //上拉



9 EINTMASK &= (~0x80); //EINT7使能,對於外部中斷EINT4-23,

//除下面的INTMSK外,還須設置EINTMASK

10 INTMSK &= (~0x1e); //EINT1-3、4-7使能(EINT4-7共用INTMSK[4])

11 PRIORITY &= (~0x03); //設定優先級

12 }



(3)、interrupt.c:

1 void EINT_Handle()

2 {

3 unsigned long oft = INTOFFSET;

4 switch( oft )

5 {

6 case 1: printk("EINT1, K1 pressed!/n/r"); break;

7 case 2: printk("EINT2, K2 pressed!/n/r"); break;

8 case 3: printk("EINT3, K3 pressed!/n/r"); break;

9 case 4: printk("EINT7, K4 pressed!/n/r"); break;

10 default: printk("Interrupt unknown!/n/r"); break;

11 }

 //以下清中斷

12 if( oft == 4 ) EINTPEND = 1<<7; //EINT4-7合用IRQ4,

//注意:EINTPEND[3:0]保留未用,

//向這些位寫入1可能導致未知結果



14 SRCPND = 1<
15 INTPND = INTPND; //清0

16 }



實驗步驟:

1、進入INT目錄運行“make”命令生成可執行文件int,下載到板子上


2、打開minicom(撥特率:57600,8N1)

3、按下按鈕即可看見串口輸出

4、您可以同時按住幾個按鈕觀察串口輸出,可以發現K1優先級最高,K4最低





(10)實驗十:TIMER

 本實驗利用TIMER0觸發中斷,使4個LED 1秒鐘閃一次。先簡述一下TIMER的
工作過程。請打開數據手冊284頁,我在“Figure 10-1. 16-bit PWM Timer Block
Diagram”上進行解說。摘錄此圖,見下面圖3。

 對於TIMER0,PCLK被“8-Bit Prescaler”(在TCFG0中定義)分頻後,進入“Clock
Divider”,“Clock Divider”有5種頻率輸出:從“8-Bit Prescaler”進來的
時鐘的2分頻、4分頻、8分頻、16分頻,或者外部時鐘TCLK0。後面的“5:1 MUX”
(5選1的選擇器)使用TCFG1來確定使用哪種頻率。“Control Logic0”在“5:1 MUX”
的輸出的頻率下工作。對於“Control Logic0”,我得畫圖說明:



圖2 Control Logic0內部結構

 “Control Logic0”的工作流程爲(請參考數據手冊287頁“TIMER
INITIALIZATION USING MANUAL UPDATE BIT AND INVERTER BIT”):

1、上電之後,設置TCMPB0和TCNTB0寄存器

2、設置TCON寄存器bit[1]爲1,這樣,TCMPB0和TCNTB0寄存器的值就被裝入TCMP0
和TCNT0寄存器中;設置bit[2]確定是否使用“Timer 0 output inverter”

3、設置TCON寄存器bit[0]啓動TIMER0,設置bit[3]確定是否使用“Timer 0 auto
reload”

4、當TIMER0啓動後,TCNT0在“5:1 MUX”輸出的時鐘下,進行減1計數。當TCNT0
等於TCMP0時:TOUT0輸出翻轉;當TCNT0等於0時:TOUT0輸出翻轉,TIMER0中斷
被觸發(如果此中斷使能了的話),TCMPB0和TCNTB0寄存器的值被自動裝入TCMP0
和TCNT0寄存器中(如果TCON寄存器bit[3]等於1的話)——此時下個計數流程開
始






圖3 16-bit PWM Timer Block Diagram

 現在介紹一下上面說到的幾個寄存器:

1、TCFG0和TCFG1:分別設爲119和0x03

 這連個寄存器用於設置“Control Logic”的時鐘,計算公式如下:

Timer input clock Frequency = PCLK / {prescaler value+1} / {divider value}

 對於TIMER0,prescaler value = TCFG0[7:0],divider value由TCFG[3:0]
確定(0b000:2,0b001:4,0b010:8,0b0011:16,0b01xx:使用外部TCLK0)。

 對於本實驗,TIMER0時鐘 = 12MHz/(119+1)/(16) = 6250Hz

2、TCNTB0:設爲3125

 在6250Hz的頻率下,此值對應的時間爲0.5S

3、TCON:

 TIMER0對應bit[3:0]:

 bit[3]用於確定在TCNT0計數到0時,是否自動將TCMPB0和TCNTB0寄存器
的值裝入TCMP0和TCNT0寄存器中

 bit[2]用於確定TOUT0是否反轉輸出(本實驗未用)


 bit[1]用於手動更新TCMP0和TCNT0寄存器:在第一次使用定時器前,此
位需要設爲1,此時TCMPB0和TCNTB0寄存器的值裝入TCMP0和TCNT0寄存器中

 bit[0]用於啓動TIMER0

4、TCONO0:只讀寄存器,用於讀取當前TCON0寄存器的值,本實驗未用



 本實驗的代碼在TIMER目錄下,init.c中的Timer0_init函數初始化並啓動
TIMER0:

1 void Timer0_init()

2 {

3 TCFG0 = 119; //Prescaler0 = 119

4 TCFG1 = 0x03; //Select MUX input for PWM Timer0:divider=16

5 TCNTB0 = 3125; //0.5秒鐘觸發一次中斷

6 TCON |= (1<<1); //Timer 0 manual update

7 TCON = 0x09; /*Timer 0 auto reload on

 Timer 0 output inverter off

 清"Timer 0 manual update"

 Timer 0 start */

8 }



 init.c中的init_irq函數使能TIEMR0中斷:

1 void init_irq( )

2 {

3 INTMSK &= (~(1<<10)); //INT_TIMER0中斷使能

4 }

 interrupt.c中的Timer0_Handle函數用於處理TIMER0中斷:每0.5s發生一次
中斷,中斷髮生時將4個LED的狀態反轉,即1s閃一次:

1 void Timer0_Handle()

2 {

3 if(INTOFFSET == 10){

4 GPBDAT = ~(GPBDAT & (0xf << 7));

5 }

6 //清中斷

7 SRCPND = 1 << INTOFFSET;

8 INTPND = INTPND;

9 }





(11)實驗十一:MMU

在理論上概括或解釋MMU,這不是我能勝任的。我僅基於爲了理解本實驗中
操作MMU的代碼而對MMU做些說明,現在先簡單地描述虛擬地址(VA)、變換後的虛
擬地址(MVA)、物理地址(PA)之間的關係:

啓動MMU後,S3C2410的CPU核看到的、用到的只是虛擬地址VA,至於VA如何
最終落實到物理地址PA上,CPU是不理會的。而caches和MMU也是看不見VA的,它
們利用VA變換得來的MVA去進行後續操作——轉換成PA去讀/寫實際內存芯片,


MVA是除CPU外的其他部分看見的虛擬地址。對於VA與MVA之間的變換關係,請打
開數據手冊551頁,我摘取了“Figure 2-8. Address Mapping Using CP15 Register 13”:



圖4 VA與MVA的關係

如果VA<32M,需要使用進程標識號PID(通過讀CP15的C13獲得)來轉換爲MVA。
VA與MVA的轉換方法如下(這是硬件自動完成的):

if(VA < 32M) then

MVA = VA | (PID << 25) //VA < 32M

else

 MVA = VA //VA >= 32M



利用PID生成MVA的目的是爲了減少切換進程時的代價:如果兩個進程佔用的
虛擬地址空間(VA)有重疊,不進行上述處理的話,當進行進程切換時必須進行虛
擬地址到物理地址的重新影射,這需要重建頁表、使無效caches和TLBS等等,代
價非常大。但是如果像上述那樣處理的話,進程切換就省事多了:假設兩個進程
1、2運行時的VA都是0-32M,則它們的MVA分別是(0x02000000-0x03ffffff)、
(0x04000000-0x05ffffff)——前面說過MMU、Caches使用MVA而不使用VA,這樣
就不必進行重建頁表等工作了。



現在來講講MVA到PA的變換過程:請打開數據手冊557頁,“Figure 3-1.
Translating Page Tables”(見下述圖5)非常精練地概括了對於不同類型的頁表,MVA
是如何轉換爲PA的。圖中的頁表“Translation table”起始地址爲“TTB base”,
在建立頁表後,寫入CP15的寄存器C2。

使用MVA[31:20]檢索頁表“Translation table”得到一個頁表項(entry,4
字節),根據此entry的低2位,可分爲以下4種:

1、0b00:無效

2、0b01:粗表(Coarse page)


 entry[31:10]爲粗表基址(Coarse page table base address),據此可以確
定一塊1K大小的內存——稱爲粗頁表(Coarse page table,見圖5)。

 粗頁表含256個頁表項,每個頁表項對應一塊4K大小的內存,每個頁表項又
可以分爲大頁描述符、小頁描述符。MVA[19:12]用來確定頁表項。一個大頁(64K)
對應16個大頁描述符,這16個大頁描述符相鄰且完全相同,entry[31:16]爲大頁
基址(Large page base)。MVA[15:0]是大頁內的偏移地址。一個小頁(4K)對應1
個小頁描述符,entry[31:12]爲小頁基址(Small page base)。MVA[11:0]是小頁
內的偏移地址。

3、0b10:段(Section)

 段的操作最爲簡單,entry[31:20]爲段基址(Section base),據此可以確定
一塊1M大小的內存(Section,見圖5),而MVA[19:0]則是塊內偏移地址

4、0b11:細表(Fine page)

 entry[31:12]爲細表基址(Fine page table base address),據此可以確定
一塊4K大小的內存——稱爲細頁表(Fine page table,見圖5)。

 細頁表含1024個頁表項,每個頁表項對應一塊1K大小的內存,每個頁表項又
可以分爲大頁描述符、小頁描述符、極小頁描述符。MVA[19:10]用來確定頁表項。
一個大頁(64K)對應64個大頁描述符,這64個大頁描述符相鄰且完全相同,
entry[31:16]爲大頁基址(Large page base)。MVA[15:0]是大頁內的偏移地址。
一個小頁(4K)對應4個小頁描述符,entry[31:12]爲小頁基址(Small page base)。
MVA[11:0]是小頁內的偏移地址。極小頁(1K)對應1個極小頁描述符,entry[31:10]
爲極小頁基址(Tiny page base)。MVA[9:0]是極小頁內的偏移地址。




圖5 Translating Page Tables



 訪問權限的檢查是MMU主要功能之一,它由描述符的AP和domain、CP15寄存
器C1的R/S/A位、CP15寄存器C3(域訪問控制)等聯合作用。本實驗不使用權限檢
查(令C3爲全1)。

 下面簡單介紹一下使用Cache和Write buffer:

1、“清空”(clean)的意思是把Cache或Write buffer中已經髒的(修改過,但未
寫入主存)數據寫入主存

2、“使無效”(invalidate):使之不能再使用,並不將髒的數據寫入主存

3、對於I/O影射的地址空間,不使用Cache和Write buffer

4、在使用MMU前,使無效Cache和drain write buffer

 與cache類似,在使用MMU前,使無效TLB。


 上面有些部分講得很簡略,除了作者水平不足之外,還在於本書的側重點—
—實驗。理論部分就麻煩各位自己想辦法了。不過這些內容也足以瞭解本實驗的
代碼了。本實驗與實驗9完成同樣的功能:使用按鍵K1-K4作爲4個外部中斷——
EINT1-3、EINT7,當Kn按下時,通過串口輸出“EINTn,Kn pressed!”,主程序
讓4個LED輪流從0到15計數。代碼在目錄MMU下。下面摘取與MMU相關的代碼詳細
說明。

先看看head.s代碼(將一些註釋去掉了):

1 b Reset

2 HandleUndef:

3 b HandleUndef

4 HandleSWI:

5 b HandleSWI

6 HandlePrefetchAbort:

7 b HandlePrefetchAbort

8 HandleDataAbort:

9 b HandleDataAbort

10 HandleNotUsed:

11 b HandleNotUsed

12 ldr pc, HandleIRQAddr

13 HandleFIQ:

14 b HandleFIQ



15 HandleIRQAddr:

16 .long HandleIRQ



17 Reset: @函數disable_watch_dog, memsetup, init_nand,

@nand_read_ll在init.c中定義

18 ldr sp, =4096 @設置堆棧

19 bl disable_watch_dog @關WATCH DOG

20 bl memsetup_2 @初始化SDRAM

21 bl init_nand @初始化NAND Flash



22 bl copy_vectors_from_nand_to_sdram @在init.c中

23 bl copy_process_from_nand_to_sdram @在init.c中



24 ldr sp, =0x30100000 @重新設置堆棧

@(因爲下面就要跳到SDRAM中執行了)

25 ldr pc, =run_on_sdram @跳到SDRAM中

26 run_on_sdram:

27 bl mmu_tlb_init @調用C函數mmu_tlb_init(mmu.c中),建立頁表

28 bl mmu_init @調用C函數mmu_init(mmu.c中),使能MMU



29 msr cpsr_c, #0xd2 @進入中斷模式

30 ldr sp, =0x33000000 @設置中斷模式堆棧


31 msr cpsr_c, #0xdf @進入系統模式

32 ldr sp, =0x30100000 @設置系統模式堆棧



33 bl init_irq @調用中斷初始化函數,在init.c中

34 msr cpsr_c, #0x5f @設置I-bit=0,開IRQ中斷



35 ldr lr, =halt_loop @設置返回地址

36 ldr pc, =main @b指令和bl指令只能前後跳轉32M的範圍,

@所以這裏使用向pc賦值的方法進行跳轉

37 halt_loop:

38 b halt_loop



39 HandleIRQ:

40 sub lr, lr, #4 @計算返回地址

41 stmdb sp!, { r0-r12,lr } @保存使用到的寄存器



42 ldr lr, =int_return @設置返回地址

43 ldr pc,=EINT_Handle @調用中斷處理函數,在interrupt.c中

44 int_return:

45 ldmia sp!, { r0-r12,pc }^ @中斷返回,

@^表示將spsr的值複製到cpsr



 請注意第12、15行,我們將IRQ中斷向量由以前的“b HandleIRQ”換成了:

 12 ldr pc, HandleIRQAddr

 15 HandleIRQAddr:

16 .long HandleIRQ

這是因爲b跳轉指令只能前後跳轉32M的範圍,而本實驗中中斷向量將重新放
在VA=0xffff0000開始處(而不是通常的0x00000000),到HandleIRQAddr的距離遠
遠超過了32M。將中斷向量重新定位在0xffff0000處,是因爲MMU使能後,中斷髮
生時:

1、如果中斷向量放在0x00000000處,則對於不同的進程(PID),中斷向量的MVA
將不同

2、如果中斷向量放在0xffff0000處,則對於不同的進程(PID),中斷向量的MVA
也相同

顯然,如果使用1,則帶來的麻煩非常大——對於每個進程,都得設置自己
的中斷向量。所以MMU使能後,處理中斷的方法應該是2。



第22行copy_vectors_from_nand_to_sdram函數將中斷向量複製到內存物理
地址0x33ff0000處,在mmu_tlb_init函數中會把0x33ff0000影射爲虛擬地址
0xffff0000。

第23行copy_process_from_nand_to_sdram函數將存在nand flash開頭的4K
代碼全部複製到0x30004000處(本實驗的連接地址爲0x30004000)。請注意SDRAM
起始地址爲0x30000000,前面的16K空間用來存放一級頁表(在mmu_tlb_init中設
置)。


第27行mmu_tlb_init函數設置頁表。本實驗以段的方式使用內存,所以僅使
用一級頁表,且頁表中所有頁表項均爲段描述符。

 mmu_tlb_init代碼(在mmu.c中)如下:

1 void mmu_tlb_init()

2 {

3 unsigned long entry_index;



4 /*SDRAM*/

5 for(entry_index = 0x30000000; entry_index < 0x34000000;

entry_index+=0x100000){

6 /*section table's entry:AP=0b11,domain=0,Cached,write-through mode(WT)*/

7 *(mmu_tlb_base+(entry_index>>20)) =

 entry_index |(0x03<<10)|(0<<5)|(1<<4)|(1<<3)|0x02;

8 }



9 /*SFR*/

10 for(entry_index = 0x48000000; entry_index < 0x60000000;

entry_index += 0x100000){

11 /*section table's entry:AP=0b11,domain=0,NCNB*/

12 *(mmu_tlb_base+(entry_index>>20)) =

 entry_index |(0x03<<10)|(0<<5)|(1<<4)| 0x02;

13 }



14 /*exception vector*/

15 /*section table's entry:AP=0b11,domain=0,Cached,write-through mode(WT)*/

16 *(mmu_tlb_base+(0xffff0000>>20)) =

 (VECTORS_PHY_BASE) |(0x03<<10)|(0<<5)|(1<<4)|(1<<3)|0x02;

17 }

 第4-8行令64M SDRAM的虛擬地址和物理地址相等——從0x30000000到
0x33ffffff,這樣可以使得在head.s中第28行調用mmu_init使能MMU前後的地址
一致。

 第9-13行設置特殊功能寄存器的虛擬地址,也讓它們的虛擬地址和物理地址
相等——從0x48000000到0x5fffffff。並且不使用cache和write buffer。

 第14-17行設置中斷向量的虛擬地址,虛擬地址0xfff00000對應物理地址
0x33f00000。



 回到head.s中第28行,調用mmu.c中的mmu_init函數使能MMU,此函數代碼入
下:

1 void mmu_init()

2 {

3 unsigned long ttb = MMU_TABLE_BASE;



4 __asm__(

5 "mov r0, #0/n"


6 /* invalidate I,D caches on v4 */

7 "mcr p15, 0, r0, c7, c7, 0/n"



8 /* drain write buffer on v4 */

9 "mcr p15, 0, r0, c7, c10, 4/n"



10 /* invalidate I,D TLBs on v4 */

11 "mcr p15, 0, r0, c8, c7, 0/n"



12 /* Load page table pointer */

13 "mov r4, %0/n"

14 "mcr p15, 0, r4, c2, c0, 0/n"



15 /* Write domain id (cp15_r3) */

16 "mvn r0, #0/n" /*0b11=Manager,不進行權限檢查*/

17 "mcr p15, 0, r0, c3, c0, 0/n"



18 /* Set control register v4 */

19 mrc p15, 0, r0, c1, c0, 0/n"



20 /* Clear out 'unwanted' bits */

21 "ldr r1, =0x1384/n"

22 "bic r0, r0, r1/n"



23 /* Turn on what we want */

24 /*Base location of exceptions = 0xffff0000*/

25 "orr r0, r0, #0x2000/n"

26 /* Fault checking enabled */

27 "orr r0, r0, #0x0002/n"

28 #ifdef CONFIG_CPU_D_CACHE_ON /*is not set*/

29 "orr r0, r0, #0x0004/n"

30 #endif

31 #ifdef CONFIG_CPU_I_CACHE_ON /*is not set*/

32 "orr r0, r0, #0x1000/n"

33 #endif

34 /* MMU enabled */

35 "orr r0, r0, #0x0001/n"



36 /* write control register *//*write control register P545*/

37 "mcr p15, 0, r0, c1, c0, 0/n"

38 : /* no outputs */

39 : "r" (ttb) );

40 }




 此函數使用嵌入彙編的方式,第29行的"r" (ttb)表示變量ttb的值賦給一個
寄存起作爲輸入參數,這個寄存器由編譯器自動分配;第13行的“%0”表示這個
寄存器。MMU控制寄存器C1中各位的含義(第18-37行),可以參考539頁“Table 2-10.
Control Register 1-bit Functions”。如果想詳細瞭解本函數用到的操作協處理器的
指令,可以參考數據手冊529頁“Appendix 2 PROGRAMMER'S MODEL”。

 本實驗代碼在CLOCK目錄下,運行make命令後將可執行文件mmu下載、運行。
然後將mmu.h文件中如下兩行的註釋去掉,重新make後下載運行mmu,可以發現LED
閃爍的速度變快了很多——這是因爲使用了cache(見上面代碼28-33行):

#define CONFIG_CPU_D_CACHE_ON 1

#define CONFIG_CPU_I_CACHE_ON 1





(12)實驗十二:CLOCK

 S3C2410 CPU主頻可以達到266MHz,前面的實驗都沒有使用PLL,CPU的頻率
只有12MHz。本實驗在實驗10的基礎上啓動PLL,使得FCLK=200MHz,HCLK=100MHz,
PCLK=50MHz。

 請打開數據手冊219頁第7章“CLOCK & POWER MANAGEMENT”。S3C2410有兩
個PLL:MPLL和UPLL,UPLL專用與USB設備,本實驗介紹的是MPLL——用於設置
FCLK、HCLK、PLCK。FCLK用於CPU核,HCLK用於AHB總線的設備(比如SDRAM),PCLK
用於APB總線的設備(比如UART)。請打開數據手冊224頁,“Figure 7-4. Power-On
Reset Sequence (when the external clock source is a crystal oscillator)”展示了上電
後, MPLL啓動的過程,摘錄此圖如下:



圖6 上電後MPLL的啓動過程

 請跟隨FCLK的圖像瞭解啓動過程:

1、上電幾毫秒後,晶振輸出穩定,FCLK=晶振頻率,nRESET信號恢復高電平後,


CPU開始執行指令。

2、我們可以在程序開頭啓動MPLL,在設置MPLL的幾個寄存器後,需要等待一段
時間(Lock Time),MPLL的輸出才穩定。在這段時間(Lock Time)內,FCLK停振,
CPU停止工作。Lock Time的長短由寄存器LOCKTIME設定。

3、Lock Time之後,MPLL輸出正常,CPU工作在新的FCLK下。

 本實驗的工作就是設置MPLL的幾個寄存器:

1、LOCKTIME:設爲0x00ffffff

前面說過,MPLL啓動後需要等待一段時間(Lock Time),使得其輸出穩定。
位[23:12]用於UPLL,位[11:0]用於MPLL。本實驗使用確省值0x00ffffff。

2、CLKDIVN:設爲0x03

 用於設置FCLK、HCLK、PCLK三者的比例:

bit[2]——HDIVN1,若爲1,則bit[1:0]必須設爲0b00,此時
FCLK:HCLK:PCLK=1:1/4:1/4;若爲0,三者比例由bit[1:0]確定

bit[1]——HDIVN,0:HCLK=FCLK;1:HCLK=FCLK/2

bit[0]——PDIVN,0:PCLK=HCLK;1:PCLK=HCLK/2

 本實驗設爲0x03,則FCLK:HCLK:PCLK=1:1/2:1/4

3、請翻到數據手冊226頁,有這麼一段:

If HDIVN = 1, the CPU bus mode has to be changed from the fast bus
mode to the asynchronous bus mode using following instructions:

MMU_SetAsyncBusMode

mrc p15, 0, r0, c1, c0, 0

orr r0, r0, #R1_nF:OR:R1_iA

mcr p15,0, r0, c1, c0, 0

 其中的“R1_nF:OR:R1_iA”等於0xc0000000。意思就是說,當HDIVN = 1時,
CPU bus mode需要從原來的“fast bus mode”改爲“asynchronous bus mode”。

4、MPLLCON:設爲(0x5c << 12)|(0x04 << 4)|(0x00),即0x5c0040

 對於MPLLCON寄存器,[19:12]爲MDIV,[9:4]爲PDIV,[1:0]爲SDIV。有如下
計算公式:

 MPLL(FCLK) = (m * Fin)/(p * 2^s)

 其中: m = MDIV + 8, p = PDIV + 2

對於本開發板,Fin = 12MHz,MPLLCON設爲0x5c0040,可以計算出
FCLK=200MHz,再由CLKDIVN的設置可知:HCLK=100MHz,PCLK=50MHz。

當設置MPLLCON之後——相當於圖6中的“PLL is configured by S/W first
time”,Lock Time就被自動插入,Lock Time之後,MPLL輸出穩定,CPU工作在
200MHz頻率下。



上面的4個步驟的工作在init.c中的clock_init函數中完成,代碼如下:

1 void clock_init()

2 {

3 LOCKTIME = 0x00ffffff;

4 CLKDIVN = 0x03; /*FCLK:HCLK:PCLK=1:2:4,

5 HDIVN1=0,HDIVN=1,PDIVN=1 */



6 /*If HDIVN = 1,the CPU bus mode has to be changed from


the fast bus mode to the asynchronous bus mod using

following instructions.*/

7 __asm__(

8 "mrc p15, 0, r1, c1, c0, 0/n" /* read ctrl register */

9 "orr r1, r1, #0xc0000000/n" /* Asynchronous */

10 "mcr p15, 0, r1, c1, c0, 0/n" /* write ctrl register */

11 );



12 MPLLCON = MPLL_200MHz; /*現在,

FCLK=200MHz,HCLK=100MHz,PCLK=50MHz*/

13 }

 在head.s中,在memsetup_2函數之前調用clock_init函數。



本實驗是在實驗10的基礎上修改得來,當工作頻率改變後,一些設備的初始
化參數需要調整,很幸運,只需要修改兩個地方:

1、SDRAM控制器的REFRESH寄存器(在init.c中):

 請翻看實驗五:MEMORY CONTROLLER中關於REFRESH寄存器的介紹,本實驗
HCLK=100MHz,REFRESH寄存器取值如下:

R_CNT = 2^11 + 1 – 100 * 7.8125 = 1268,

REFRESH=0x008e0000 + 1268 = 0x008e04f4

 在init.c中的memsetup_2函數,將原來的0x008e07a3換成0x008e04f4即可。

2、UART0的UBRDIV0寄存器(在serial.c中):

 請翻看實驗七:UART中關於UBRDIV0的介紹,本實驗PCLK=50MHz,設置波特
率爲57600時,UART0寄存器取值可由下式計算得53:

UBRDIVn = (int)(PCLK / (bps x 16) ) –1

 在serial.c中的init_uart函數,將UART0由原來的12換成53即可。



 本實驗代碼在CLOCK目錄下,運行make命令後將可執行文件clock下載、運行,
可以發現LED閃爍的速度變快了很多。然後將mmu.h文件中如下兩行的註釋去掉,
重新make後下載運行clock,可以發現LED閃爍得更快了,簡直分辨不出來了——
這是因爲使用了cache:

#define CONFIG_CPU_D_CACHE_ON 1

#define CONFIG_CPU_I_CACHE_ON 1
























四.Bootloader vivi

爲了將linux移植到ARM上,碰到的第一個程序就是bootloader,我選用韓國
mizi公司的vivi。您可以在以下地址下載:

http://www.mizi.com/developer/s3c2410x/download/vivi.html

如果您對bootloader沒有什麼概念,在學習VIVI的代碼之前,建議您閱讀一
篇文章《嵌入式系統 Boot Loader 技術內幕》(詹榮開著)。鏈接地址如下:

http://www-128.ibm.com/developerworks/cn/linux/l-btloader/

當您閱讀了上述文章後,我再企圖在理論上羅嗦什麼就不合適了(這篇文章
實在太好了)。vivi也可以分爲2個階段,階段1的代碼在arch/s3c2410/head.S
中,階段2的代碼從init/main.c的main函數開始。您可以跳到實驗部分先感受一
下vivi。

(1)階段1:arch/s3c2410/head.S

 沿着代碼執行的順序,head.S完成如下幾件事情:

1、關WATCH DOG:上電後,WATCH DOG默認是開着的

2、禁止所有中斷:vivi中沒用到中斷(不過這段代碼實在多餘,上電後中斷默認
是關閉的)

3、初始化系統時鐘:啓動MPLL,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz,“CPU
bus mode”改爲“Asynchronous bus mode”。請參考實驗十一:CLOCK

4、初始化內存控制寄存器:還記得那13個寄存器嗎?請複習實驗五:MEMORY
CONTROLLER

5、檢查是否從掉電模式喚醒,若是,則調用WakeupStart函數進行處理——這是
一段沒用上的代碼,vivi不可能進入掉電模式

6、點亮所有LED

7、初始化UART0:

a.設置GPIO,選擇UART0使用的引腳
b.初始化UART0,設置工作方式(不使用FIFO)、波特率115200 8N1、無流控
等,請參考實驗七:UART


8、將vivi所有代碼(包括階段1和階段2)從nand flash複製到SDRAM中:

a.設置nand flash控制寄存器
b.設置堆棧指針——調用C函數時必須先設置堆棧
c.設置即將調用的函數nand_read_ll的參數:r0=目的地址(SDRAM的地址),
r1=源地址(nand flash的地址),r2=複製的長度(以字節爲單位)
d.調用nand_read_ll進行復制
e.進行一些檢查工作:上電後nand flash最開始的4K代碼被自動複製到一
個稱爲“Steppingstone”的內部RAM中(地址爲0x00000000-0x00001000);
在執行nand_read_ll之後,這4K代碼同樣被複制到SDRAM中(地址爲
0x33f00000-0x33f01000)。比較這兩處的4K代碼,如果不同則表示出錯


9、跳到bootloader的階段2運行——就是調用init/main.c中的main函數:

a.重新設置堆棧
b.設置main函數的參數
c.調用main函數





如果您做了第二章的各個實驗,理解head.S就不會有任何困難——上面說到


的幾個步驟,都有相應的實驗。head.S有900多行,把它搬到這篇文章上來就太
不厚道了——白白浪費頁數而已。在vivi/arch/s3c2410/head.S上,我做了比較
詳細的註釋(其中的亂碼可能是韓文,不去管它)。

當執行完head.S的代碼後,內存的使用情況如下:



圖7 執行vivi stage1後內存的劃分情況



(2)階段2:init/main.c

 本階段從init/main.c中的main函數開始執行,它可以分爲8個步驟。我先把
main函數的代碼羅列如下(去掉了亂碼——可能是韓文,現在沒留下多少有用的
註釋了),然後逐個分析:

1 int main(int argc, char *argv[])

2 {

3 int ret;



4 /*Step 1*/

5 putstr("/r/n");

6 putstr(vivi_banner);



7 reset_handler();



8 /*Step 2*/

9 ret = board_init();

10 if (ret) {

11 putstr("Failed a board_init() procedure/r/n");

12 error();

13 }



14 /*Step 3*/

15 mem_map_init();


16 mmu_init();

17 putstr("Succeed memory mapping./r/n");

18 /* Now, vivi is running on the ram. MMU is enabled.*/



19 /*Step 4*/

20 /* initialize the heap area */

21 ret = heap_init();

22 if (ret) {

23 putstr("Failed initailizing heap region/r/n");

24 error();

25 }



26 /*Step 5*/

27 ret = mtd_dev_init();



28 /*Step 6*/

29 init_priv_data();



30 /*Step 7*/

31 misc();



32 init_builtin_cmds();



33 /*Step 8*/

34 boot_or_vivi();



35 return 0;

36 }



1、Step 1:reset_handler()

 reset_handler用於將內存清零,代碼在lib/reset_handle.c中。

[main(int argc, char *argv[]) > reset_handler()]

1 void

2 reset_handler(void)

3 {

4 int pressed;



5 pressed = is_pressed_pw_btn(); /*判斷是硬件復位還是軟件復位*/



6 if (pressed == PWBT_PRESS_LEVEL) {

7 DPRINTK("HARD RESET/r/n");

8 hard_reset_handle(); /*調用clear_mem對SDRAM清0*/

9 } else {

10 DPRINTK("SOFT RESET/r/n");


11 soft_reset_handle(); /*此函數爲空*/

12 }

13 }

 在上電後,reset_handler調用第8行的hard_reset_handle(),此函數在
lib/reset_handle.c中:

[main(int argc, char *argv[]) > reset_handler() > hard_reset_handle()]

1 static void

2 hard_reset_handle(void)

3 {

4 #if 0

5 clear_mem((unsigned long)(DRAM_BASE + VIVI_RAM_ABS_POS), /

6 (unsigned long)(DRAM_SIZE - VIVI_RAM_ABS_POS));

7 #endif



8 /*lib/memory.c,將起始地址爲USER_RAM_BASE,長度爲USER_RAM_SIZE的內存清0*/

9 clear_mem((unsigned long)USER_RAM_BASE, (unsigned long)USER_RAM_SIZE);

10 }

2、Step 2:board_init()

 board_init調用2個函數用於初始化定時器和設置各GPIO引腳功能,代碼在
arch/s3c2410/smdk.c中:

[main(int argc, char *argv[]) > board_init()]

1 int board_init(void)

2 {

3 init_time(); /*arch/s3c2410/proc.c*/

4 set_gpios(); /*arch/s3c2410/smdk.c*/

5 return 0;

6 }

 init_time()只是簡單的令寄存器TCFG0 = 0xf00,vivi未使用定時器,這個
函數可以忽略。

 set_gpios()用於選擇GPA-GPH端口各引腳的功能及是否使用各引腳的內部
上拉電阻,並設置外部中斷源寄存器EXTINT0-2(vivi中未使用外部中斷)。



3、Step 3:建立頁表和啓動MMU

 mem_map_init函數用於建立頁表,vivi使用段式頁表,只需要一級頁表。它
調用3個函數,代碼在arch/s3c2410/mmu.c中:

[main(int argc, char *argv[]) > mem_map_init(void)]

1 void mem_map_init(void)

2 {

3 #ifdef CONFIG_S3C2410_NAND_BOOT /* CONFIG_S3C2410_NAND_BOOT=y */

4 mem_map_nand_boot(); /* 最終調用mem_mepping_linear,建立頁表 */

5 #else

6 mem_map_nor();

7 #endif

8 cache_clean_invalidate(); /* 清空cache,使無效cache */


9 tlb_invalidate(); /* 使無效快表TLB */

10 }

 第9、10行的兩個函數可以不用管它,他們做的事情在下面的mmu_init函數
裏又重複了一遍。對於本開發板,在.config中定義了
CONFIG_S3C2410_NAND_BOOT。mem_map_nand_boot()函數調用
mem_mapping_linear()函數來最終完成建立頁表的工作。頁表存放在SDRAM物理
地址0x33dfc000開始處,共16K:一個頁表項4字節,共有4096個頁表項;每個頁
表項對應1M地址空間,共4G。mem_map_init先將4G虛擬地址映射到相同的物理地
址上,NCNB(不使用cache,不使用write buffer)——這樣,對寄存器的操作跟
未啓動MMU時是一樣的;再將SDRAM對應的64M空間的頁表項修改爲使用cache。
mem_mapping_linear函數的代碼在arch/s3c2410/mmu.c中:

[main(int argc, char *argv[]) > mem_map_init(void) > mem_map_nand_boot( )
> mem_mapping_linear(void)]

1 static inline void mem_mapping_linear(void)

2 {

3 unsigned long pageoffset, sectionNumber;

4 putstr_hex("MMU table base address = 0x", (unsigned long)mmu_tlb_base);

5 /* 4G 虛擬地址映射到相同的物理地址. not cacacheable, not bufferable */

6 /* mmu_tlb_base = 0x33dfc000*/

7 for (sectionNumber = 0; sectionNumber < 4096; sectionNumber++) {

8 pageoffset = (sectionNumber << 20);

9 *(mmu_tlb_base + (pageoffset >> 20)) = pageoffset | MMU_SECDESC;

10 }



11 /* make dram cacheable */

12 /* SDRAM物理地址0x3000000-0x33ffffff,

13 DRAM_BASE=0x30000000,DRAM_SIZE=64M

14 */

15 for (pageoffset = DRAM_BASE; pageoffset < (DRAM_BASE+DRAM_SIZE); /

16 pageoffset += SZ_1M) {

17 //DPRINTK(3, "Make DRAM section cacheable: 0x%08lx/n", pageoffset);

18 *(mmu_tlb_base + (pageoffset >> 20)) = /

pageoffset | MMU_SECDESC | MMU_CACHEABLE;

19 }

20 }



 mmu_init()函數用於啓動MMU,它直接調用arm920_setup()函數。
arm920_setup()的代碼在arch/s3c2410/mmu.c中:

[main(int argc, char *argv[]) > mmu_init( ) > arm920_setup( )]

1 static inline void arm920_setup(void)

2 {

3 unsigned long ttb = MMU_TABLE_BASE; /* MMU_TABLE_BASE = 0x 0x33dfc000 */

4 __asm__(

5 /* Invalidate caches */


6 "mov r0, #0/n"

7 "mcr p15, 0, r0, c7, c7, 0/n" /* invalidate I,D caches on v4 */

8 "mcr p15, 0, r0, c7, c10, 4/n" /* drain write buffer on v4 */

9 "mcr p15, 0, r0, c8, c7, 0/n" /* invalidate I,D TLBs on v4 */

10 /* Load page table pointer */

11 "mov r4, %0/n"

12 "mcr p15, 0, r4, c2, c0, 0/n" /* load page table pointer */



13 /* Write domain id (cp15_r3) */

14 "mvn r0, #0/n" /* Domains 0b01 = client, 0b11=Manager*/

15 "mcr p15, 0, r0, c3, c0, 0/n" /* load domain access register,

write domain 15:0, 數據手冊P548(access permissions)*/



16 /* Set control register v4 */

17 "mrc p15, 0, r0, c1, c0, 0/n" /* get control register v4 */

/*數據手冊P545:read control register */

18 /* Clear out 'unwanted' bits (then put them in if we need them) */

19 /* ..VI ..RS B... .CAM */ /*這些位的含義在數據手冊P546*/

20 "bic r0, r0, #0x3000/n" /* ..11 .... .... .... */

/*I(bit[12])=0 = Instruction cache disabled*/



21 /*V[bit[13]](Base location of exception registers)=0 = Low addresses =

0x0000 0000*/

22 "bic r0, r0, #0x0300/n" /* .... ..11 .... .... */



23 /*R(ROM protection bit[9])=0*/

 /*S(System protection bit[8])=0*/

 /*由於TTB中AP=0b11(line141),所以RS位不使用(P579)*/

24 "bic r0, r0, #0x0087/n" /* .... .... 1... .111 */

 /*M(bit[0])=0 = MMU disabled*/

 /*A(bit[1])=0 =Data address

 alignment fault checking disable*/

 /*C(bit[2])=0 = Data cache disabled*/

 /*B(bit[7])=0= Little-endian operation*/



25 /* Turn on what we want */

26 /* Fault checking enabled */

27 "orr r0, r0, #0x0002/n" /* .... .... .... ..1. */

/*A(bit[1])=1 = Data address

alignment fault checking enable*/



28 #ifdef CONFIG_CPU_D_CACHE_ON /*is not set*/

29 "orr r0, r0, #0x0004/n" /* .... .... .... .1.. */

/*C(bit[2])=1 = Data cache enabled*/


30 #endif

31 #ifdef CONFIG_CPU_I_CACHE_ON /*is not set*/

32 "orr r0, r0, #0x1000/n" /* ...1 .... .... .... */

/*I(bit[12])=1 = Instruction cache enabled*/

33 #endif



34 /* MMU enabled */

35 "orr r0, r0, #0x0001/n" /* .... .... .... ...1 */

/*M(bit[0])=1 = MMU enabled*/

36 "mcr p15, 0, r0, c1, c0, 0/n" /* write control register */

/*數據手冊P545*/

37 : /* no outputs */

38 : "r" (ttb) );

39 }



 至此,內存如下劃分:



圖8 創建頁表後內存的劃分情況





4、Step 4:heap_init()

 heap——堆,內存動態分配函數mmalloc就是從heap中劃出一塊空閒內存的,
mfree則將動態分配的某塊內存釋放回heap中。

heap_init函數在SDRAM中指定了一塊1M大小的內存作爲heap(起始地址
HEAP_BASE = 0x33e00000),並在heap的開頭定義了一個數據結構blockhead——
事實上,heap就是使用一系列的blockhead數據結構來描述和操作的。每個
blockhead數據結構對應着一塊heap內存,假設一個blockhead數據結構的存放位
置爲A,則它對應的可分配內存地址爲“A + sizeof(blockhead)”到“A +
sizeof(blockhead) + size - 1”。blockhead數據結構在lib/heap.c中定義:

1 typedef struct blockhead_t {


2 Int32 signature; //固定爲BLOCKHEAD_SIGNATURE

3 Bool allocated; //此區域是否已經分配出去:0-N,1-Y

4 unsigned long size; //此區域大小

5 struct blockhead_t *next; //鏈表指針

6 struct blockhead_t *prev; //鏈表指針

7 } blockhead;



現在來看看heap是如何運作的(如果您不關心heap實現的細節,這段可以跳
過)。vivi對heap的操作比較簡單,vivi中有一個全局變量static blockhead
*gHeapBase,它是heap的鏈表頭指針,通過它可以遍歷所有blockhead數據結構。
假設需要動態申請一塊sizeA大小的內存,則mmalloc函數從gHeapBase開始搜索
blockhead數據結構,如果發現某個blockhead滿足:

a. allocated = 0 //表示未分配
b. size > sizeA,


則找到了合適的blockhead,於是進行如下操作:

a.allocated設爲1


b.如果size – sizeA > sizeof(blockhead),則將剩下的內存組織成
一個新的blockhead,放入鏈表中

c.返回分配的內存的首地址

釋放內存的操作更簡單,直接將要釋放的內存對應的blockhead數據結構的
allocated設爲0即可。

 下面用圖來簡單演示先分配1K內存,再分配2K內存的過程:



圖9 在heap中連續分配1K和2K內存的示意圖


heap_init函數直接調用mmalloc_init函數進行初始化,此函數代碼在
lib/heap.c中,比較簡單,初始化gHeapBase即可:

[main(int argc, char *argv[]) > heap_init(void) > mmalloc_init(unsigned
char *heap, unsigned long size)]

1 static inline int mmalloc_init(unsigned char *heap, unsigned long size)

2 {

3 if (gHeapBase != NULL) return -1;



4 DPRINTK("malloc_init(): initialize heap area at 0x%08lx, size =
0x%08lx/n", heap, size);



5 gHeapBase = (blockhead *)(heap);

6 gHeapBase->allocated=FALSE;

7 gHeapBase->signature=BLOCKHEAD_SIGNATURE;

8 gHeapBase->next=NULL;

9 gHeapBase->prev=NULL;

10 gHeapBase->size = size - sizeof(blockhead);



11 return 0;

12 }



 分配heap區域後,內存劃分情況如下:



圖10 執行heap_init後內存的劃分情況





5、Step 5:mtd_dev_init()

 在分析代碼前先介紹一下MTD(Memory Technology Device)相關的技術。在
linux系統中,我們通常會用到不同的存儲設備,特別是FLASH設備。爲了在使用
新的存儲設備時,我們能更簡便地提供它的驅動程序,在上層應用和硬件驅動的


中間,抽象出MTD設備層。驅動層不必關心存儲的數據格式如何,比如是FAT32、
ETX2還是FFS2或其它。它僅僅提供一些簡單的接口,比如讀寫、擦除及查詢。如
何組織數據,則是上層應用的事情。MTD層將驅動層提供的函數封裝起來,向上
層提供統一的接口。這樣,上層即可專注於文件系統的實現,而不必關心存儲設
備的具體操作。

 在我們即將看到的代碼中,使用mtd_info數據結構表示一個MTD設備,使用
nand_chip數據結構表示一個nand flash芯片。在mtd_info結構中,對nand_flash
結構作了封裝,向上層提供統一的接口。比如,它根據nand_flash提供的
read_data(讀一個字節)、read_addr(發送要讀的扇區的地址)等函數,構造了一
個通用的讀函數read,將此函數的指針作爲自己的一個成員。而上層要讀寫flash
時,執行mtd_info中的read、write函數即可。

 下面分析代碼:

 mtd_dev_init()用來掃描所使用的NAND Flash的型號,構造MTD設備,即構
造一個mtd_info的數據結構。對於本開發板,它直接調用mtd_init(),mtd_init
又調用smc_init(),此函數在drivers/mtd/maps/s3c2410_flash.c中:

[main(int argc, char *argv[]) > mtd_dev_init() > mtd_init() > smc_init()]

1 static int

2 smc_init(void)

3 {

/*struct mtd_info *mymtd,數據類型在include/mtd/mtd.h*/

/*strcut nand_chip在include/mtd/nand.h中定義*/



4 struct nand_chip *this;

5 u_int16_t nfconf;



 /* Allocate memory for MTD device structure and private data */

6 mymtd = mmalloc(sizeof(struct mtd_info) + sizeof(struct
nand_chip));



7 if (!mymtd) {

8 printk("Unable to allocate S3C2410 NAND MTD device
structure./n");

9 return -ENOMEM;

10 }



 /* Get pointer to private data */

11 this = (struct nand_chip *)(&mymtd[1]);



 /* Initialize structures */

12 memset((char *)mymtd, 0, sizeof(struct mtd_info));

13 memset((char *)this, 0, sizeof(struct nand_chip));



 /* Link the private data with the MTD structure */

14 mymtd->priv = this;




 /* set NAND Flash controller */

15 nfconf = NFCONF;

 /* NAND Flash controller enable */

16 nfconf |= NFCONF_FCTRL_EN;



 /* Set flash memory timing */

17 nfconf &= ~NFCONF_TWRPH1; /* 0x0 */

18 nfconf |= NFCONF_TWRPH0_3; /* 0x3 */

19 nfconf &= ~NFCONF_TACLS; /* 0x0 */



20 NFCONF = nfconf;



 /* Set address of NAND IO lines */

21 this->hwcontrol = smc_hwcontrol;

22 this->write_cmd = write_cmd;

23 this->write_addr = write_addr;

24 this->read_data = read_data;

25 this->write_data = write_data;

26 this->wait_for_ready = wait_for_ready;



 /* Chip Enable -> RESET -> Wait for Ready -> Chip Disable */

27 this->hwcontrol(NAND_CTL_SETNCE);

28 this->write_cmd(NAND_CMD_RESET);

29 this->wait_for_ready();

30 this->hwcontrol(NAND_CTL_CLRNCE);



31 smc_insert(this);



32 return 0;

33 }



 6-14行構造了一個mtd_info結構和nand_flash結構,前者對應MTD設備,後
者對應nand flash芯片(如果您用的是其他類型的存儲器件,比如nor flash,這
裏的nand_flash結構應該換爲其他類型的數據結構)。MTD設備是具體存儲器件的
抽象,那麼在這些代碼中這種關係如何體現呢——第14行的代碼把兩者連結在一
起了。事實上,mtd_info結構中各成員的實現(比如read、write函數),正是由
priv變量所指向的nand_flash的各類操作函數(比如read_addr、read_data等)
來實現的。

 15-20行是初始化S3C2410上的NAND FLASH控制器,和我們在實驗六“NAND
FLASH CONTROLLER”裏面做的一樣。

 前面分配的nand_flash結構還是空的,現在當然就是填滿它的各類成員了,
這正是21-26行做的事情。

 27-30行對這塊nand flash作了一下復位操作。


 最後,也是最複雜的部分,根據剛纔填充的nand_flash結構,構造mtd_info
結構,這由31行的smc_insert函數調用smc_scan完成。先看看smc_insert函數:

[main(int argc, char *argv[]) > mtd_dev_init() > mtd_init() > smc_init()
> smc_insert(struct nand_chip *this)]

1 inline int

2 smc_insert(struct nand_chip *this) {

2 /* Scan to find existance of the device */



/*smc_scan defined at drivers/mtd/nand/smc_core.c*/

4 if (smc_scan(mymtd)) {

5 return -ENXIO;

6 }

 /* Allocate memory for internal data buffer */

7 this->data_buf = mmalloc(sizeof(u_char) *

8 (mymtd->oobblock + mymtd->oobsize));



9 if (!this->data_buf) {

10 printk("Unable to allocate NAND data buffer for S3C2410./n");

11 this->data_buf = NULL;

12 return -ENOMEM;

13 }



14 return 0;

15 }



 後面的7-13行,我也沒弄清楚,待查。

 現在,終於到重中之重了,請看smc_scan函數的代碼:

[main(int argc, char *argv[]) > mtd_dev_init() > mtd_init() > smc_init()
> smc_insert(struct nand_chip *this) > smc_scan(struct mtd_info *mtd)]

1 int smc_scan(struct mtd_info *mtd)

2 {

3 int i, nand_maf_id, nand_dev_id;

4 struct nand_chip *this = mtd->priv;



 /* Select the device */

5 nand_select();



 /* Send the command for reading device ID */

6 nand_command(mtd, NAND_CMD_READID, 0x00, -1);



7 this->wait_for_ready();



 /* Read manufacturer and device IDs */

8 nand_maf_id = this->read_data();


9 nand_dev_id = this->read_data();



 /* Print and sotre flash device information */

10 for (i = 0; nand_flash_ids[i].name != NULL; i++) {

11 if (nand_maf_id == nand_flash_ids[i].manufacture_id &&

12 nand_dev_id == nand_flash_ids[i].model_id) {

13 if (!(mtd->size) && !(nand_flash_ids[i].page256)) {

14 mtd->name = nand_flash_ids[i].name;

15 mtd->erasesize = nand_flash_ids[i].erasesize;

16 mtd->size = (1 << nand_flash_ids[i].chipshift);

17 mtd->eccsize = 256;

18 mtd->oobblock = 512;

19 mtd->oobsize = 16;

20 this->page_shift = 9;

21 this->dev =
&nand_smc_info[GET_DI_NUM(nand_flash_ids[i].chipshift)];

22 }



23 printk("NAND device: Manufacture ID:" /

24 " 0x%02x, Chip ID: 0x%02x (%s)/n",

25 nand_maf_id, nand_dev_id, mtd->name);

26 break;

27 }

28 }



 /* De-select the device */

29 nand_deselect();



30 /* Print warning message for no device */

31 if (!mtd->size) {

32 printk("No NAND device found!!!/n");

33 return 1;

34 }



35 /* Fill in remaining MTD driver data */

36 mtd->type = MTD_NANDFLASH;

37 mtd->flags = MTD_CAP_NANDFLASH | MTD_ECC;

38 mtd->module = NULL;

39 mtd->ecctype = MTD_ECC_SW;

40 mtd->erase = nand_erase;

41 mtd->point = NULL;

42 mtd->unpoint = NULL;

43 mtd->read = nand_read;

44 mtd->write = nand_write;


45 mtd->read_ecc = nand_read_ecc;

46 mtd->write_ecc = nand_write_ecc;

47 mtd->read_oob = nand_read_oob;

48 mtd->write_oob = nand_write_oob;

49 mtd->lock = NULL;

50 mtd->unlock = NULL;



51 /* Return happy */

52 return 0;

53 }



 5-9行,讀取nand flash得廠家ID和設備ID,下面將用這兩個ID號,在一個
預設的數組“nand_flash_ids”裏查找到與之相符的項。nand_flash_ids是
nand_flash_dev結構的數組,裏面存放的是世界上比較常用的nand flash型號的
一些特性(見下面的定義):

struct nand_flash_dev {

 char * name;

 int manufacture_id;

 int model_id;

 int chipshift;

 char page256;

 char pageadrlen;

 unsigned long erasesize;

};



 static struct nand_flash_dev nand_flash_ids[] = {

 {"Toshiba TC5816BDC", NAND_MFR_TOSHIBA, 0x64, 21, 1, 2, 0x1000},

 ..

 {NULL,}

};



10-28行根據找到的數組項,構造前面分配的mtd_info結構。

 35行之後的代碼,填充mtd_info結構的剩餘部分,大多是一些函數指針,比
如read、write函數等。



 執行完mtd_dev_init後,我們得到了一個mtd_info結構的全局變量(mymtd指
向它),以後對nand flash的操作,直接通過mymtd提供的接口進行。





6、Step 6:init_priv_data()

 此函數將啓動內核的命令參數取出,存放在內存特定的位置中。這些參數來
源有兩個:vivi預設的默認參數,用戶設置的參數(存放在nand flash上)。
init_priv_data先讀出默認參數,存放在“VIVI_PRIV_RAM_BASE”開始的內存上;
然後讀取用戶參數,若成功則用用戶參數覆蓋默認參數,否則使用默認參數。


 init_priv_data函數分別調用get_default_priv_data函數和
load_saved_priv_data函數來讀取默認參數和用戶參數。這些參數分爲3類:

 a.vivi自身使用的一些參數,比如傳輸文件時的使用的協議等

 b.linux啓動命令

 c.nand flash的分區參數



 get_default_priv_data函數比較簡單,它將vivi中存儲這些默認參數的變
量,複製到指定內存中。執行完後,此函數執行完畢後,內存使用情況如下:



圖11 執行init_priv_data後內存的劃分情況

 load_saved_priv_data函數讀取上述內存圖中分區參數的數據,找到
“param”分區,然後從中讀出上述3類參數,覆蓋掉默認參數(如果能成功讀出
的話)。





7、Step 7:misc()和init_builtin_cmds()

這兩個函數都是簡單地調用add_command函數,給一些命令增加相應的處理
函數。在vivi啓動後,可以進去操作界面,這些命令,就是供用戶使用的。它們
增加了如下命令:

a.add_command(&cpu_cmd)

b. add_command(&bon_cmd)

c. add_command(&reset_cmd)

d. add_command(¶m_cmd)

e. add_command(&part_cmd)

f. add_command(&mem_cmd)

g. add_command(&load_cmd)

h. add_command(&go_cmd)

i. add_command(&dump_cmd)

j. add_command(&call_cmd)

k. add_command(&boot_cmd)

l. add_command(&help_cmd)




現在來看看add_command函數是如何實現得:

1 void add_command(user_command_t *cmd)

2 {

3 if (head_cmd == NULL) {

4 head_cmd = tail_cmd = cmd;

5 } else {

6 tail_cmd->next_cmd = cmd;

7 tail_cmd = cmd;

8 }

9 /*printk("Registered '%s' command/n", cmd->name);*/

10 }



很簡單!把user_command_t結構放入一個鏈表即可。user_command_t結
構定義如下,呵呵,name是命令名稱,cmdfunc是這個命令的處理函數,helstr
是幫助信息。

typedef struct user_command {

 const char *name;

 void (*cmdfunc)(int argc, const char **);

 struct user_command *next_cmd;

 const char *helpstr;

} user_command_t;





8、Step 8:boot_or_vivi()

 此函數根據情況,或者啓動“vivi_shell”,進入與用戶進行交互的界面,
或者直接啓動linux內核。代碼如下:

[main(int argc, char *argv[]) > void boot_or_vivi(void)]

1 void boot_or_vivi(void)

2 {

3 char c;

4 int ret;

5 ulong boot_delay;



5 boot_delay = get_param_value("boot_delay", &ret);

7 if (ret) boot_delay = DEFAULT_BOOT_DELAY;

 /* If a value of boot_delay is zero,

 * unconditionally call vivi shell

*/

8 if (boot_delay == 0) vivi_shell();



 /*

 * wait for a keystroke (or a button press if you want.)

 */


9 printk("Press Return to start the LINUX now, any other key for
vivi/n");

10 c = awaitkey(boot_delay, NULL);

11 if (((c != '/r') && (c != '/n') && (c != '/0'))) {

12 printk("type /"help/" for help./n");

13 vivi_shell();

14 }

15 run_autoboot();



16 return;

17 }



 第10行等待鍵盤輸入,如果在一段時間內鍵盤無輸入,或者輸入了回車鍵,
則調用run_autoboot啓動內核;否則調用vivi_shell進入交互界面。

 vivi_shell等待用戶輸入命令(等待串口數據),然後根據命令查找“Step 7:
misc()和init_builtin_cmds()”設置的命令鏈表,運行找到的命令函數。
vivi_shell函數是通過調用serial_term函數來實現的,serial_term代碼如下:

[main(int argc, char *argv[]) > void boot_or_vivi(void) >
vivi_shell(void) > serial_term]

1 void serial_term(void)

2 {

3 char cmd_buf[MAX_CMDBUF_SIZE];



4 for (;;) {

5 printk("%s> ", prompt); /*prompt is defined upside*/



6 getcmd(cmd_buf, MAX_CMDBUF_SIZE);



 /* execute a user command */

7 if (cmd_buf[0])

8 exec_string(cmd_buf);

9 }

10 }



 第6行getcmd函數讀取串口數據,將返回的字符串作爲參數調用exec_string
函數。exec_string代碼如下:

[main(int argc, char *argv[]) > void boot_or_vivi(void) >
vivi_shell(void) > serial_term > void exec_string(char *buf)]

1 void exec_string(char *buf)

2 {

3 int argc;

4 char *argv[128];

5 char *resid;




6 while (*buf) {

7 memset(argv, 0, sizeof(argv));

8 parseargs(buf, &argc, argv, &resid);

9 if (argc > 0)

10 execcmd(argc, (const char **)argv);

11 buf = resid;

12 }

13 }



 它首先調用parseargs函數分析所傳入的字符串,確定參數個數及將這些參
數分別保存。比如對於字符串“abcd efgh ijklm 123”,parseargs函數返回的
結果是:argc = 4,argv[0]指向“abcd”字符串,argv[1]指向“efgh”字符
串,argv[2]指向“ijklm”字符串,argv[3]指向“123”字符串。然後,調用execmd
函數:

[main(int argc, char *argv[]) > void boot_or_vivi(void) >
vivi_shell(void) > serial_term > void execcmd(int argc, const char
**argv)]

1 void execcmd(int argc, const char **argv)

2 {

3 user_command_t *cmd = find_cmd(argv[0]);



4 if (cmd == NULL) {

5 printk("Could not found '%s' command/n", argv[0]);

6 printk("If you want to konw available commands, type
'help'/n");

7 return;

8 }

9 /*printk("execcmd: cmd=%s, argc=%d/n", argv[0], argc);*/



10 cmd->cmdfunc(argc, argv);

11 }



 首先,第3行的find_cmd函數根據命令(argv[0])找到對應的處理函數,然後
執行它(第10行)。命令名字和對應的處理函數指針,在上述“Step 7:misc()
和init_builtin_cmds()”中,使用一個命令鏈表存起來了。具體的命令處理函
數,這裏就不細說了,各位有興趣的可以自行閱讀源代碼。



 run_autoboot用於啓動內核,起始就是運行“boot”命令的處理函數
command_boot。我們看看它是如何啓動內核的,下面我們只列出用到的代碼:

[main(int argc, char *argv[]) > void boot_or_vivi(void) > void
run_autoboot(void) > exec_string("boot") > command_boot(1, 0)]

1 void command_boot(int argc, const char **argv)

2 {

 ..


3 media_type = get_param_value("media_type", &ret);

4 if (ret) {

5 printk("Can't get default 'media_type'/n");

6 return;

7 }

8 kernel_part = get_mtd_partition("kernel");

9 if (kernel_part == NULL) {

10 printk("Can't find default 'kernel' partition/n");

11 return;

12 }

13 from = kernel_part->offset;

14 size = kernel_part->size;

 ..

15 boot_kernel(from, size, media_type);

 ..

}



 第3行,獲得存儲內核代碼的器件類型;第8行獲取“kernel”分區信息,第
13、14行由此分區信息獲得內核存放的位置及大小;第15行的boot_kernel函數
將內核從其存儲的器件上(flash)複製到內存,然後執行它。boot_kernel代碼如
下:

[main(int argc, char *argv[]) > void boot_or_vivi(void) > void
run_autoboot(void) > exec_string("boot") > command_boot(1, 0) > int
boot_kernel(ulong from, size_t size, int media_type)]

1 int boot_kernel(ulong from, size_t size, int media_type)

2 {

3 int ret;

4 ulong boot_mem_base; /* base address of bootable memory */

5 ulong to;

6 ulong mach_type;



7 boot_mem_base = get_param_value("boot_mem_base", &ret);

8 if (ret) {

9 printk("Can't get base address of bootable memory/n");

10 printk("Get default DRAM address. (0x%08lx/n", DRAM_BASE);

11 boot_mem_base = DRAM_BASE;

12 }



 /* copy kerne image

* LINUX_KERNEL_OFFSET = 0x8000

*/

13 to = boot_mem_base + LINUX_KERNEL_OFFSET;

14 printk("Copy linux kernel from 0x%08lx to 0x%08lx, size =
0x%08lx ... ", from, to, size);


15 ret = copy_kernel_img(to, (char *)from, size, media_type);



16 if (ret) {

17 printk("failed/n");

18 return -1;

19 } else {

20 printk("done/n");

21 }



22 if (*(ulong *)(to + 9*4) != LINUX_ZIMAGE_MAGIC) {

23 printk("Warning: this binary is not compressed linux kernel
image/n");

24 printk("zImage magic = 0x%08lx/n", *(ulong *)(to + 9*4));

25 } else {

26 printk("zImage magic = 0x%08lx/n", *(ulong *)(to + 9*4));

27 }



 /* Setup linux parameters and linux command line

* LINUX_PARAM_OFFSET = 0x100

*/

28 setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET);



 /* Get machine type */

29 mach_type = get_param_value("mach_type", &ret);

30 printk("MACH_TYPE = %d/n", mach_type);



 /* Go Go Go */

31 printk("NOW, Booting Linux....../n");

32 call_linux(0, mach_type, to);



33 return 0;

34 }



 第7-13行用來確定在內核將存放在內存中的位置;15行調用nand_read_ll
函數(詳細介紹請看“實驗六:NAND FLASH CONTROLLER”)從flash芯片上將內核
讀出;28行設置內核啓動的參數,比如命令行等;29行獲取處理器型號;32行的
函數call_linux彙編代碼,跳到內核存放的地址處執行。值得細看的有兩個函數:
setup_linux_param和call_linux。

 setup_linux_param函數用於設置linux啓動時用到的各類參數:

[main( ) > boot_or_vivi( ) > run_autoboot( ) > exec_string("boot") >
command_boot(1, 0) > boot_kernel( ) > setup_linux_param( )]

1 static void setup_linux_param(ulong param_base)

2 {

3 struct param_struct *params = (struct param_struct *)param_base;


4 char *linux_cmd;



5 printk("Setup linux parameters at 0x%08lx/n", param_base);

6 memset(params, 0, sizeof(struct param_struct));

7 params->u1.s.page_size = LINUX_PAGE_SIZE;

8 params->u1.s.nr_pages = (DRAM_SIZE >> LINUX_PAGE_SHIFT);



 /* set linux command line */

9 linux_cmd = get_linux_cmd_line(); /* lib/priv_data/param.c */

10 if (linux_cmd == NULL) {

11 printk("Wrong magic: could not found linux command line/n");

12 } else {

13 memcpy(params->commandline, linux_cmd, strlen(linux_cmd) +
1);

14 printk("linux command line is: /"%s/"/n", linux_cmd);

15 }

16 }



 此函數在param_base地址存放linux啓動時用到的參數。對於本開發板,此
地址爲RAM_BASE + LINUX_PARAM_OFFSET = 0x3000_0000 + 0x100。第9行將命令
行參數從原來位置(上述Step 6:init_priv_data中將命令行等存儲在內存中)
讀出,複製到相應位置(第13行)。



 call_linux函數先對cache和tlb進行一些設置,然後將內核存放的位置直接
賦給pc寄存器,從而執行內核。call_linux函數代碼如下:

[main( ) > boot_or_vivi( ) > run_autoboot( ) > exec_string("boot") >
command_boot(1, 0) > boot_kernel( ) > call_linux( )]

1 void call_linux(long a0, long a1, long a2)

2 {

3 cache_clean_invalidate();

4 tlb_invalidate();



5 __asm__(

6 "mov r0, %0/n"

7 "mov r1, %1/n"

8 "mov r2, %2/n"

9 "mov ip, #0/n"

10 "mcr p15, 0, ip, c13, c0, 0/n" /* zero PID */

11 "mcr p15, 0, ip, c7, c7, 0/n" /* invalidate I,D caches */

12 "mcr p15, 0, ip, c7, c10, 4/n" /* drain write buffer */

13 "mcr p15, 0, ip, c8, c7, 0/n" /* invalidate I,D TLBs */

14 "mrc p15, 0, ip, c1, c0, 0/n" /* get control register */

15 "bic ip, ip, #0x0001/n" /* disable MMU */

16 "mcr p15, 0, ip, c1, c0, 0/n" /* write control register */


17 "mov pc, r2/n"

18 "nop/n"

19 "nop/n"

20 : /* no outpus */

21 : "r" (a0), "r" (a1), "r" (a2)

22 );

23 }



 對cache、tlb等的操作,您可以參考“實驗十一:MMU”。6、7、8行中的%0、
%1、%2對應3個寄存器,它們存放的值分別爲參數a0、a1和a2。這樣,執行完這3
條語句後,r0、r1、r2對應的值分別爲:0, mach_type, 0x3000_8000(內核在內
存中的存放地址)。第17行將跳到去執行內核。至此,linux內核終於開始運行了!

vivi執行完畢後,內存使用情況如下:



圖12 vivi啓動內核後內存的劃分情況 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章