【轉】朱兆祺帶你一步一步學習嵌入式(連載)

原文網址:http://bbs.elecfans.com/jishu_357014_2_1.html#comment_topphp

 從最初涉及嵌入式Linux開始到如今,深深的知道嵌入式的每一步學習都是舉步維艱。從去年11月份開始,我就着手整理各類學習資料,但願推進嵌入式學習的前進貢獻本身微不足道的一份力量。從去年到如今,將C語言的學習經驗整理成《攻破C語言筆試與機試陷阱及難點》(如今仍在更新),這份資料已經在電子發燒友論壇單片機論壇連載(http://bbs.elecfans.com/jishu_354666_1_1.html),這份資料也已經錄製了部分視頻;如今我一樣將嵌入式學習經驗進行整理進行連載,視頻我已經在加緊錄製,等錄製到一半將會掛載在網上以供嵌入式學習者免費下載。                        
    我從大一學習C語言到大二開始接觸ARM,從畢業工做到如今籌建明志電子科技工做室承接各種項目,一步步都是本身紮紮實實走過來,我整理的資料,每個圖都是根據個人思惟、適合學習者的角度親自畫出,每個程序都是親手敲入進行調試。
    2013年8月8日更新:
    爲了知足你們的學習,嵌入式Linux實用教程同步視頻以每5集形式上傳。


2014年4月8日:
     《嵌入式Linux開發實用教程》一書已經出版,本帖將會持續更新,後續更會有裸板視頻和項目視頻更新。敬請關注。
    《嵌入式Linux開發實用教程》購買地址:
http://item.taobao.com/item.htm?spm=a1z10.1.w6545579-4546600052.3.8xcC8H&id=36731267737
<ignore_js_op>
<ignore_js_op>

    




1.本書及視頻QQ羣:
嵌入式Linux開發實用教程1:284013595  (1000人)
嵌入式Linux開發實用教程2:271641475   (1000人,已滿)
嵌入式Linux開發實用教程3:301012138    (500人)
深圳市饅頭科技有限公司官方百度網盤(C語言、單片機、嵌入式視頻):
http://pan.baidu.com/share/link?shareid=3562495290&uk=3996269986



2.《嵌入式Linux開發實用教程》視頻清單:
第1課:Linux基本操做指令
第2課:Makefile
第3課:Linux經常使用軟件
第4課:U-Boot-2013.04搭建適合OK6410模板
第5課:初步編譯U-Boot-2013.04
第6課:U-Boot-2013.04啓動分析1
第7課:U-Boot-2013.04啓動分析2
第8課:SD卡啓動U-Boot原理
第9課:SD卡啓動U-Boot-2013.04移植1(解開衆多商家SD卡啓動機密)
第10課:SD卡啓動U-Boot-2013.04移植2(解開衆多商家SD卡啓動機密)
第11課:SD卡啓動
第12課:Signal # 8 caught錯誤
第13課:MMC驅動移植
第14課:FAT文件系統
第15課:U-Boot命令
第16課:NAND Flash移植(1)
第17課:NAND Flash移植(2)
第18課:NAND Flash移植(3)
第19課:DM9000網卡移植
第20課:Linux-3.8.3內核介紹
第21課:初步測試內核(1)
第22課:初步測試內核(2)
第23課:下載地址和入口地址
第24課: MTD分區
第25課:NAND Flash移植
第26課:DM9000網卡移植
第27課:使內核支持YAFFS2文件系統
第28課:製做YAFFS2文件系統
第29課:LCD移植
第30課:字符設備驅動之LED
第31課:字符設備驅動之ADC
第32課:塊驅動
第33課:tslib安裝
第34課:安裝Linux和embedded版本Qt-4.8.4
第35課:安裝QtCreator編譯環境
第36課:Qt初體驗之Hello
第37課:Qt之LED
第38課:Qt之ADC
第39課:項目拓展學習(1)
第40課:項目拓展學習(2)
第41—50課:裸板程序設計
3. 嵌入式Linux實用教程軟件:
Ubuntu10.04.4鏡像、VMware-7.0.1虛擬機、Source Insight3.5+註冊碼、SecureCRT_6.6.1_PiaoXu.net、SD_Writer、UltraEdit10c、USB轉串口驅動、等等嵌入式Linux經常使用軟件。
4. 嵌入式Linux實用教程資料
S3c6410相關手冊、Linux經常使用書籍、嵌入式Linux實用手冊初稿、等等。
5. 嵌入式Linux開發實用教程源碼
linux-3.8.三、u-boot-2013.04-rc一、自制編譯器、等等相關源碼
6. 嵌入式Linux實用教程程序
《嵌入式Linux開發實用教程》一書中涉及的U-Boot添加程序、Linux程序、Linux設備驅動程序、Qt程序等等
6. C語言技術公開課
在電子發燒友學院進行的一系列課程涉及的資料,相關連接以下:
下載說明:

百度網盤:

 
[url=]http://pan.baidu.com/share/link?shareid=3562495290&uk=3996269986[/url]




第一章第一節  Linux基本命令
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2111915&fromuid=222350

第一章第二節  Makefile基本知識
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2111927&fromuid=222350

第一章第三節  arm-linux交叉編譯鏈
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2111953&fromuid=222350

第一章第四節  映像文件的生產和運行
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2112037&fromuid=222350

第二章第一節  U-Boot-2013.04分析與移植之BootLoader概述
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2115821&fromuid=222350

第二章第三節  創建OK6410可用的U-Boot模板
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2116938&fromuid=222350

第二章第四節   編譯U-Boot模板
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2124889&fromuid=222350

第二章第五節  U-Boot-2013.04啓動分析(1)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2136058&fromuid=222350

第二章第六節  U-Boot-2013.04啓動分析(2)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2149112&fromuid=222350

第二章第七節  U-Boot-2013.04啓動分析(3)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2167921&fromuid=222350

第二章第八節  U-Boot-2013.04啓動分析(4)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2187418&fromuid=222350

第二章第九節  U-Boot-2013.04啓動分析(5)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2220235&fromuid=222350

第二章第十節   IROM啓動的概念
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2246925&fromuid=222350

第三章第一節   初步測試內核
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2273727&fromuid=222350


第三章第二節   mkimage工具 
http://bbs.elecfans.com/forum.ph ... 2860&fromuid=222350

第三章第三節    加載地址和入口地址
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2420732&fromuid=222350


第三章第四節    內核啓動分析
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2466792&fromuid=222350

第三章第五節    MTD分區
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2541575&fromuid=222350
第三章第六節     NAND Flash驅動移植

第三章第六節   DM9000 網卡驅動
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2676612&fromuid=222350

第三章第七節  YAFFS2根文件系統(1)

第三章第八節  LCD驅動移植

第三章第九節   LCD觸摸移植
 
第四章第二節  字符設備驅動
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2879175&fromuid=222350
 
 
第五章第一節  Qt編譯環境搭建

第五章第二節  安裝Linux/x11版Qt-4.8.4


 
第一章第一節  Linux基本命令
在進行嵌入式Linux學習開發的過程當中,將常用到Linux的操做命令。實際上,Linux系統中的命令也是爲實現特定的功能而編寫的程序,並且絕大數的命令是用C語言編寫的。有些實用性強的程序被普遍使用和傳播,逐漸地演變成Linux的標準命令。可是Linux的操做命令繁多,本節將在U-Boot、Linux移植過程當中經常使用到的Linux操做命令羅列出來進行講解,爲後續的學習作好良好的鋪墊。讀者不要認爲這是Linux簡單命令則不屑一顧,嵌入式Linux學習是一個漫長的過程,按部就班方能有所成就,這個過程是由每一小步累加而成的。天下難事,必做於易;天下大事,必做於細。因此讀者務必要對待學習的每個細節。
1.1.1   文件屬性查詢與修改 1.   文件屬性查詢
ls」命令在Linux目錄中佔據着重要地位,主要用於查看文件屬性、查看目錄下所包含的文件等。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/busybox-1.20.2/_install$ ls
bin              dev  home  linuxrc  proc  sbin  tmp  var
creat_yaffs2.sh  etc  lib   mnt      root  sys   usr
經過「ls」命令即查看_install目錄下有哪些東西。若是要進一步查看文件屬性,則使用「ll」命令或者「ls -al」命令,這兩個命令是等效的。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/busybox-1.20.2/_install$ ll
總用量 64
drwxr-xr-x 15 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 .
drwxr-xr-x 35 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 15:34 ..
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 15:34 bin
-rw-r--r--  1 zhuzhaoqi zhuzhaoqi  393 2013-03-17 16:32 creat_yaffs2.sh
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 dev
drwxr-xr-x  3 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 21:01 etc
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 home
drwxr-xr-x  3 zhuzhaoqi zhuzhaoqi 4096 2013-03-18 09:57 lib
lrwxrwxrwx  1 zhuzhaoqi zhuzhaoqi   11 2013-03-17 15:34 linuxrc -> bin/busybox
drwxr-xr-x  5 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 mnt
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 proc
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 root
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 15:34 sbin
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 sys
drwxrwxrwx  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 tmp
drwxr-xr-x  7 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 usr
drwxr-xr-x  2 zhuzhaoqi zhuzhaoqi 4096 2013-03-17 16:33 var
這樣每個文件的屬性將一目瞭然。而屬性中的每個數據都有特定的含義。如表1. 1所示。
drwxr-xr-x
2
zhuzhaoqi
zhuzhaoqi
4096
2013-03-17 15:34
bin
文件權限
鏈接數
文件全部者
文件所屬用戶組
文件大小
文件最後一次被修改的時間
文件名稱
而其中文件權限的10個字符含義如表1. 2所示。
文件類型
文件全部者的權限
文件所屬用戶組的權限
其餘人對此文件的權限
d
r
w
x
r
-
x
r
-
x
目錄
可讀
可寫
可執行
可讀
無權限
可執行
可讀
無權限
可執行
所以 /bin目錄的文件權限是:文件全部者對 /bin目錄可讀可寫可執行,文件所屬用戶組對 /bin目錄可讀不可寫可執行,其餘人對 /bin目錄可讀不可寫可執行。
當對某個文件進行操做,要特別注意這個文件是否具備將要進行操做的權限。若是咱們所在的用戶組沒有操做權限而又得進行操做,此時就得修改文件的權限。
1.   文件權限修改
chmod」命令是使得一個文件變動權限。
zhuzhaoqi@zhuzhaoqi-desktop:~/linux/include$ ll
總用量 8
drwxr-xr-x 2 zhuzhaoqi zhuzhaoqi 4096 2013-03-18 22:02 ./
drwxr-xr-x 3 zhuzhaoqi zhuzhaoqi 4096 2013-03-18 22:07 ../
-rw-r--r-- 1 zhuzhaoqi zhuzhaoqi    0 2013-03-18 22:02 s3c6410.h
從上一小節可知,「 drwxr-xr-x」除了「 d」是文件類型,剩下 9個字符劃分紅 3組,表示 3個用戶組的使用權限。而在 Linux系統中,每個用戶組的 3個字母分別可用數字進行描述其權限, r:4w:2x:1-:0,將每一組的數字進行相加,即獲得這組用戶的權限。例如上面 s3c6410.h的權限是: rw-r--r--,那麼每一用戶組權限分別是: 644,那麼組合起來即爲: 644。每一個文件的最高權限爲: 777
給予 s3c6410.h最高權限,以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/linux/include$ chmod 777 s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux/kernel/include$ ll
總用量 8
drwxr-xr-x 2 zhuzhaoqi zhuzhaoqi 4096 2013-03-18 22:02 ./
drwxr-xr-x 3 zhuzhaoqi zhuzhaoqi 4096 2013-03-18 22:07 ../
-rwxrwxrwx 1 zhuzhaoqi zhuzhaoqi    0 2013-03-18 22:02 s3c6410.h*
經過「 chmod」更改權限命令能夠看到 s3c6410.h的權限是最高權限。
1.1.2   目錄與路徑處理命令1.   切換目錄
cd」命令的做用是從當前目錄切換到另外一個目錄下。如從用戶根目錄進入 /linux目錄下,以下操做:
zhuzhaoqi@zhuzhaoqi-desktop:~$ cd linux/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$
2.   建立新目錄
mkdir」命令的做用是建立一個新的目錄,如在 /linux目錄下再建立一個 /linux-3.8.3子目錄,以下操做:
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ mkdir linux-3.8.3
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
linux-3.8.3
mkdir的用法不少,能夠從過輸入 mkdir –help查看,以下:
zhuzhaoqi@zhuzhaoqi-desktop:~$ mkdir --help
用法:mkdir [選項]... 目錄...
若指定目錄不存在則建立目錄。
 
長選項必須使用的參數對於短選項時也是必需使用的。
  -m, --mode=模式    設置權限模式(相似chmod),而不是rwxrwxrwx umask
  -p, --parents     須要時建立目標目錄的上層目錄,但即便這些目錄已存在也不看成錯誤處理
  -v, --verbose     每次建立新目錄都顯示信息
-Z, --context=CTX  將每一個建立的目錄的SELinux 安全環境設置爲CTX
--help      顯示此幫助信息並退出
--version       顯示版朩信並退出
mkdir –p這個指令在 U-BootLinux內核源碼中的 Makefile中的使用是至關頻繁的。
3.   刪除目錄
若是是刪除一個空目錄,則使用「 rmdir」命令便可;若是該目錄下有東西,則不能使用「 rmdir」命令刪除。
zhuzhaoqi@zhuzhaoqi-desktop:~/linux/linux-3.6.7$ ls
arch
zhuzhaoqi@zhuzhaoqi-desktop:~/linux/linux-3.6.7$ cd ..
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
linux-3.6.7  linux-3.8.3
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ cd linux-3.8.3/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux/linux-3.8.3$ ls
zhuzhaoqi@zhuzhaoqi-desktop:~/linux/linux-3.8.3$ cd ..
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
linux-3.6.7  linux-3.8.3
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ rmdir linux-3.8.3/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
linux-3.6.7
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ rmdir linux-3.6.7/
rmdir: 刪除 "linux-3.6.7/" 失敗:目錄非空
上面操做可知,因爲 /linux-3.8.3目錄爲空,則可以使用「 rmdir」刪除;可是 / linux-3.6.7目錄下有一個子目錄 /arch,則不能使用「 rmdir」刪除。此時則應該使用「 rm -r」命令刪除。
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
linux-3.6.7
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls linux-3.6.7/
arch
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ rm -r linux-3.6.7/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$
經過「 ls」命令可知, linux目錄下的 linux-3.6.7/目錄以及被刪除。
1.1.3   文件操做1.   新建文件
新建一個文件能夠使用「 vim」命令,可是使用「 vim」命令退出打開的文件時須要保存退出,不然會視爲沒有建立文件。
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ vim s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
s3c6410.h
2.   複製文件
複製文件命令爲「 cp」。以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
include  s3c6410.c  s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ cp s3c6410.h include/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
include  s3c6410.c  s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls include/
s3c6410.h
若是要複製而且重命名,以下操做:
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
include  kernel  s3c6410.c  s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ cp s3c6410.c include/s3c6400.c
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls include/
s3c6400.c  s3c6410.h
當複製目錄時,使用「 cp -r」命令。以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
include  kernel  s3c6410.c  s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls kernel/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ cp -r include/ kernel/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
include  kernel  s3c6410.c  s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls kernel/
include
3.   移動文件
移動一個文件則使用「 mv」命令,以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
include  kernel  s3c6410.c  s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ mv s3c6410.c kernel/
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls
include  kernel  s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ ls kernel/
include  s3c6410.c
編輯一個文件,做者提倡使用「 gedit」命令或者「 vim」命令。
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ gedit s3c6410.h
zhuzhaoqi@zhuzhaoqi-desktop:~/linux$ vim s3c6410.c
 
1.1.4   打包與解包、壓縮與解壓縮
熟悉打包與解包、壓縮與解壓縮的操做命令是能很好在 Linux操做文件的必備技能,而 Linux下的打包與解包、壓縮與解壓縮的操做命令也是種類繁多,本節截取經常使用的 8個格式進行講解。本節中, FileName是指打包、壓縮以後的文件名, DirName是指待打包、壓縮的文件名。
1)      .tar格式
單純的 tar功能其實僅僅是打包而已,也就是說將不少文件集結成一個文件,並無進行壓縮。
解包:tar xvf FileName.tar
打包:tar cvf FileName.tar DirName
2)      .gz格式
GZIP最先由 Jean-loup GaillyMark Adler建立,用於 UNIX系統的文件壓縮。在 Linux中常常會碰到後綴名爲 .gz的文件,它們的原型便是 GZIP格式。
解壓1gunzip FileName.gz
解壓2gzip -d FileName.gz
壓縮:gzip FileName
3)      .tar.gz格式和 .tgz格式
.tar.gz.tgz爲後綴名的壓縮文件在在 LinuxOSX下是很是常見的, LinuxOSX均可以直接解壓使用這種壓縮文件。
解壓:tar zxvf FileName.tar.gz
壓縮:tar zcvf FileName.tar.gz DirName
4)      .bz2格式
壓縮生成後綴名爲 .bz2的壓縮算法使用的是「 Burrows-Wheeler block sorting text」,這類算法壓縮比率比較高。
解壓1bzip2 -d FileName.bz2
解壓2bunzip2 FileName.bz2
壓縮: bzip2 -z DirName
這裏須要注意的是,當執行壓縮指令以後,將會生成 FileName.bz2壓縮文件,同時 DirName文件將會自動刪除。
5)      .tar.bz2格式
bzip2是一個壓縮能力很是強的壓縮程序,以 .bz2.tar.bz2爲後綴名的壓縮文件都是 bzip2壓縮的結果。
解壓:tar jxvf FileName.tar.bz2
壓縮:tar jcvf FileName.tar.bz2 DirName
6)      .Z格式
compress 是一個至關古老的  unix 壓縮指令,壓縮後的文件是以 .Z 做爲後綴名。
解壓:uncompress FileName.Z
壓縮:compress DirName
7)      .tar.Z格式
解壓:tar Zxvf FileName.tar.Z
壓縮:tar Zcvf FileName.tar.Z DirName
8)      .zip格式
ZIP由於格式開放並且免費,愈來愈多的軟件支持打開 Zip文件。
解壓:unzip FileName.zip
壓縮:zip FileName.zip DirName
以上8種打包壓縮算法都有所區別,最終致使的結果是壓縮時間和壓縮大小的不同。每一種壓縮格式都有其優點和不足,在何種場應該使用何種壓縮格式就得視實際狀況而定了。
在程序設計當中,空間換取時間、時間換取空間的現象是很是常見的一種方法。好比在單片機中LED跑馬燈中,常用數組中取出想要的花樣,這就是空間換取時間。
 
第一章第二節  Makefile基本知識
Makefile現在能得以普遍應用,這還得歸功於它被包含在Unix系統中。在make誕生以前,Unix系統的編譯系統主要由「make」「install」shell腳本程序和程序的源代碼組成。它能夠把不一樣目標的命令組成一個文件,並且能夠抽象化依賴關係的檢查和存檔。這是向現代編譯環境發展的重要一步。1977年,斯圖亞特·費爾德曼在貝爾實驗室裏製做了這個軟件。2003年,斯圖亞特·費爾德曼因發明了這樣一個重要的工具而接受了美國計算機協會(ACM)頒發的軟件系統獎。
Makefile文件是能夠實現自動化編譯,只須要一個「make」命令,整個工程就能徹底自動編譯,極大的提升了軟件開發的效率。目前雖有衆多依賴關係檢查工具,可是make是應用最普遍的一個。一個程序員會不會寫makefile,從一個側面說明了這個程序員是否具有完成大型工程的能力。
1.1.1   Makefile 規則
一個簡單的Makefile語句由目標、依賴條件、指令組成。
smdk6400_config  :    unconfig
     @mkdir -p $(obj)include $(obj)board/samsung/smdk6400
smdk6400_config:目標;
unconfig:先決條件;
@mkdir -p  $(obj) include $(obj)board/samsung/smdk6400 :指令。這裏特別注意,「@」前面是Tab鍵,而且必須是Tab鍵,而不能是空格。
目標和先決條件是依賴關係,目標是依賴於先決條件生成的。
 
1.1.2   Makefile 變量 1.   變量的引用方式
使用「$(OBJTREE)」或者「${ OBJTREE }」來引用OBJTREE這個變量的定義。這個引用方式彷佛很像C語言中的指針變量,使用*p來取存放在指針p中的值。
obj := $(OBJTREE)/
OBJTREE :=  $(if $( BUILD_DIR ),$(BUILD_DIR),$( CURDIR ))
export BUILD_DIR=/tmp/build
$(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))的含義:若是「BUILD_DIR」變量值不爲空,則將變量「BUILD_DIR」指定的目錄做爲一個子目錄;不然將目錄「CURDIR」做爲一個子目錄。
2.   遞歸展開式變量
這類變量的定義是經過「=」和「define」來定義的。
student = lilei
CLASS = $(student) $(teacher)
teacher = yang
 
all:
    @echo $(CLASS)
其優勢是:這種類型遞歸展開式的變量在定義時,能夠引用其它的以前沒有定義的變量,這個變量可能在後續部分定義,或者是經過make的命令行選項傳遞的變量。
其缺點是:其一,使用此風格的變量定義,可能會因爲出現變量的遞歸定義而致使make陷入到無限的變量展開過程當中,最終使make執行失敗。
x = $(y)
y = $(z)
z = $(x)
這樣的話會使得Makefile出錯,由於都最終引用了本身。
其二,這種風格的變量定義中若是使用了函數,那麼包含在變量值中的函數總會在變量被引用的地方執行。
3.   直接展開式變量
爲了不遞歸展開式變量存在的問題和不方便。GNU make支持另一種風格的變量,稱爲直接展開式變量。這種風格的變量使用「:=」定義。在使用「:=」定義變量時,變量值中對其餘量或者函數的引用在定義變量時被展開,也就是對變量進行替換。
X := student
Y := $(X)
X := teacher
all:
    @echo $(X) $(Y)
這裏的輸出是:teacher  student
這個直接展開式變量在定義時就完成了對所引用變量和函數的展開,所以不能實現對其後定義變量的引用。
4.   條件賦值
在對變量進行賦值以前,會對其進行判斷,只有在這個變量以前沒有進行賦值的狀況下才會對這個變量進行賦值。
X := student
X ?= teacher
all:
    @echo $(X)
因爲X在以前被賦值了,因此這裏的輸出是student
5.   變量的替換引用
對於一個已經定義的變量,能夠使用變量的替換引用將變量中的後綴字符使用指定的字符替換。格式爲「$(X:a=b)」(或者「${X:a=b}」),便是將變量「X」中全部「a」字符結尾的字替換爲「b」結尾的字。
X := fun.o main.o
Y := $(X: .o=.c)
all:
    @echo $(X) $(Y)
特別注意的是$(X: .o=.c)的「=」兩邊不能有空格。輸出是:fun.o main.o  fun.c main.c
6.   追加變量值
追加變量值是指一個通用變量在定義以後的其餘一個地方,能夠對其值進行追加。也就是說能夠在定義時(也能夠不定義而直接追加)給它賦一個基本值,後續根據須要可隨時對它的值進行追加(增長它的值)。在Makefile中使用「+=」(追加方式)來實現對一個變量值的追加操做。
X = fun.o main.o
X += sub.o
all:
    @echo $(x)
這裏輸出是:fun.o main.o sub.o
 
1.1.3   Makfile 經常使用關鍵字 1.   ifneq關鍵字
這個關鍵字是用來判斷兩個參數是否不相等。格式爲:
ifneq 「Value1」「Value2」
ifneq (Value1,Value2)
在判斷以前先要將Value1Value2的值進行展開和替換,如在U-Boot-2013.04的頂層目錄Makefile中,對U-Boot的版本參數就使用了ifneq關鍵字進行判斷。
VERSION      = 2013
PATCHLEVEL   = 04
SUBLEVEL     =
EXTRAVERSION =
 
ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif
先將SUBLEVEL使用$()展開和替換,若是SUBLEVEL的值不是空,則執行:
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
也就是說,若是$(SUBLEVEL) = 1的話,那麼U_BOOT_VERSION = 2013.04.1
若是SUBLEVEL的值是空,則是執行:
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
那麼此時U_BOOT_VERSION = 2013.04
2.   ifeq關鍵字
ifeq關鍵是和ifneq關鍵字相對而言,用來判斷兩個參數是否相等。格式爲:
ifeq 「Value1」「Value2」
ifeq (Value1,Value2)
ifneq同樣,先要將Value1Value2展開替換以後,再進行比較。
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE=/usr/local/arm/4.4.1/bin/arm-linux-
endif
若是HOSTARCH展開替換以後和ARCH展開替換以後相等,則:
CROSS_COMPILE=/usr/local/arm/4.4.1/bin/arm-linux-
不然CROSS_COMPILE不等於/usr/local/arm/4.4.1/bin/arm-linux-
3.   ifndef關鍵字
ifndef關鍵字用來判斷一個變量是否沒有進行定義。格式:
ifndef Value
因爲在Makefile中,沒有定義的變量的值爲空。
ifndef CONFIG_SANDBOX
SUBDIRS += $(SUBDIR_EXAMPLES)
endif
若是CONFIG_SANDBOX值爲空,條件成立,執行以下語句:
SUBDIRS += $(SUBDIR_EXAMPLES)
不然不執行。
4.   ifdef關鍵字
ifdef關鍵字用來判斷一個變量是否已經進行定義過。格式:
ifdef Value
如:
ifdef CONFIG_SYS_LDSCRIPT
        # need to strip off double quotes
        LDSCRIPT := $(subst ",,$(CONFIG_SYS_LDSCRIPT))
endif
若是CONFIG_SYS_LDSCRIPT定義過,則執行:
LDSCRIPT := $(subst ",,$(CONFIG_SYS_LDSCRIPT))
不然不執行。
 
1.1.4   Makefile 經常使用函數 1.   Makefile函數語法
在Makefile中,函數的調用和變量的調用相似,都是使用「$」進行標識。語法以下:
$(函數名 函數的參數)
${函數名 函數的參數}
函數名與函數的參數之間使用空格隔開,而函數的參數間使用逗號進行分隔。以上兩種寫法都是能夠的,可是爲了風格統一,請不要二者進行混合使用。
2.   shell函數
make能夠使用shell函數和外部通訊。shell函數自己的返回值是其參數的執行結果,沒有進行任何處理,對結果的處理是由make進行。當對函數的引用出如今規則的命令行中,命令行在執行時函數才被展開。展開時函數參數(shell命令)的執行是在另一個shell進程中完成的,所以須要對出如今規則命令行的多級「shell」函數引用須要謹慎處理,不然會影響效率(每一級的「shell」函數的參數都會有各自的shell進程)。
創建一個測試程序,Makefile的內容:
zhu := $(shell cat func)
 
all:
        @ echo $(zhu)
Func文件中的內容:
juxst zhuzhaoqi
執行完成Makefile以後:
zhuzhaoqi@zhuzhaoqi-desktop:~/u-boot/Makefile/shellfunction$ make
juxst zhuzhaoqi
U-BootLinux內核源碼中將會大量使用到shell函數。
3.   subst函數
subst函數是字符串替換函數,語法爲:
$(subst 被替換字串 替換字串 替換操做字符串)
執行subst函數以後,返回的是執行替換操做以後的字符串。以下:
name := zhu zhaoqi
Alphabet_befor := z
Alphabet_after := Z
Name := $(subst $(Alphabet_befor), $(Alphabet_after), $(name))
 
all:
        echo $(Name)
執行上面Makefile,輸出結果爲:
echo   Zhu  Zhaoqi
Zhu Zhaoqi
便是將「z」替換成「Z」.
4.   dir函數
dir函數做用爲取出該文件的目錄,其語法爲:
$(dir 文件名稱)
執行該函數以後返回文件目錄部分。
Makefile中經常使用函數較多,筆者就不一一例舉,讀者可參考相關文獻進行深刻了解。
 
第一章第三節  arm-linux交叉編譯鏈
日常咱們作的編譯叫本地編譯,也就是在當前平臺編譯,編譯獲得的程序也是在本地執行。相對而言的交叉編譯指的是在一個平臺上生成另外一個平臺的可執行代碼。
常見的交叉編譯有如下三種:
Windows PC上,利用 ADSARM  開發環境),使用 armcc 編譯器,編譯出針對 ARM CPU可執行代碼
Linux PC上,利用 arm-linux-gcc 編譯器,編譯出針對 Linux ARM平臺的 可執行代碼
Windows PC上,利用 cygwin環境,運行 arm-elf-gcc 編譯器,編譯出針對 ARM CPU可執行代碼
1.1.1   arm-linux交叉編譯工具鏈的製做方法
因爲通常嵌入式開發系統存儲大小是有限的,一般都要在性能優越的 PC上創建一個用於目標機的交叉編譯工具鏈,用該交叉編譯工具鏈在 PC上編譯目標機上要運行的程序,好比在 PC平臺( X86 CPU)上編譯出能運行在以 ARM爲內核的 CPU平臺上的程序。要生成在目標機上運行的程序,必需要用交叉編譯工具鏈完成。交叉編譯工具鏈是一個由編譯器、鏈接器和解釋器組成的綜合開發環境,交叉編譯工具鏈主要由 binutilsgccglibc 3個部分組成。有時出於減少 libc 庫大小的考慮,也能夠用別的  c 庫來代替  glibc,例如  uClibcdietlibc  newlib。創建交叉編譯工具鏈是一個至關複雜的過程,若是不想本身經歷複雜繁瑣的編譯過程,網上有一些編譯好的可用的交叉編譯工具鏈能夠下載,但就以學習爲目的來講讀者有必要學習本身製做一個交叉編譯工具鏈。本節經過具體的實例講述基於 ARM的嵌入式 Linux交叉編譯工具鏈的製做過程。
製做 arm-linux交叉編譯工具鏈的通常經過 crosstool工具或者 crosstool_NG,前者使用方便,可是製做會受到一些限制,使用 crosstool最多隻能編譯 gcc 4.1.1glibc 2.x的版本。 crosstool-NG是新的用來創建交叉工具鏈的工具,它是 crosstool的替換者,而 crosstool_NG則有更好的定製性,而且一直保持着更新,對新版本的編譯工具鏈的支持比較好,固然也帶來了一些麻煩,它並非下載下來就能夠使用,必須先配置安裝。咱們這裏選用 crosstool_NG來製做咱們的編譯鏈。
1.   安裝crosstool_NG
在crosstool_NG官網上下載最新版本,官網連接:http://crosstool-ng.org/
zhuzhaoqi@zhuzhaoqi-desktop:~$ mkdir arm-linux-tools
zhuzhaoqi@zhuzhaoqi-desktop:~$ cd arm-linux-tools/
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools$ ls
獲取源碼操做命令:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools$ wget http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.18.0.tar.bz2
--2013-03-26 21:34:34--  http://crosstool-ng.org/download/crosstool-ng/crosstool-ng-1.18.0.tar.bz2
正在解析主機 crosstool-ng.org... 140.211.15.107
正在鏈接 crosstool-ng.org|140.211.15.107|:80... 已鏈接。
已發出 HTTP 請求,正在等待迴應... 200 OK
長度: 1884219 (1.8M) [application/x-bzip]
正在保存至crosstool-ng-1.18.0.tar.bz2
 
100%[======================================>] 1,884,219    223K/s   花時8.8s
下載源碼成功以後解壓源碼:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools$ tar jxvf crosstool-ng-1.18.0.tar.bz2
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools$ ls
crosstool-ng-1.18.0  crosstool-ng-1.18.0.tar.bz2
考慮到後續將要使用到的各類目錄,在這裏先創建號後續所需目錄。
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools$ mkdir crosstool-build crosstool-install src  
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools$ ls
crosstool-build    crosstool-ng-1.18.0          src
crosstool-install  crosstool-ng-1.18.0.tar.bz2
因爲 ubuntu操做系統不少開發軟件都沒有安裝,所以要先安裝一些製做交叉編譯鏈必備的軟件。在 ubuntu下安裝軟件的命令爲:  sudo apt-get install ***
注:筆者建議 arm-linux交叉編譯工具鏈的製做最好在 CentOS系統中完成,由於 CentOS系統自帶較爲完善的開發軟件,對於初學者不會形成沒必要要的麻煩。
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools$ sudo apt-get install sed bash cut dpkg-dev patch texinfom4 libtool statwebsvn tar gzip bzip2 lzmabison flex texinfo automake libtool patchcvs cvsd gawk–y
配置整個工程而且進行依賴檢測:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ ./configure --prefix /home/zhuzhaoqi/arm-linux-tools/crosstool-install
在安裝過程當中,提示以下錯誤:
……
checking how to run the C preprocessor... gcc -E
checking for ranlib... ranlib
checking for objcopy... objcopy
checking for absolute path to objcopy... /usr/bin/objcopy
checking for objdump... objdump
checking for absolute path to objdump... /usr/bin/objdump
checking for readelf... readelf
checking for absolute path to readelf... /usr/bin/readelf
checking for bison... no
configure: error: missing required tool: bison
輸出錯誤提示確實 bison這個軟件,安裝:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ sudo apt-get install bison
安裝完成以後,再次進行配置:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ ./configure --prefix /home/zhuzhaoqi/arm-linux-tools/crosstool-install
又一次輸出錯誤:
……
checking for bison... bison
checking for flex... no
configure: error: missing required tool: flex
提示確實 flex這個軟件,進行安裝:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ sudo apt-get install flex
安裝完成以後,再一次進行配置:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ ./configure --prefix /home/zhuzhaoqi/arm-linux-tools/crosstool-install
又一次提示錯誤:
checking for bison... bison
checking for flex... flex
checking for gperf... no
configure: error: missing required tool: gperf
提示缺失 gperf這個軟件,進行安裝:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ sudo apt-get install gperf
安裝完成以後,再一次進行配置:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ ./configure --prefix /home/zhuzhaoqi/arm-linux-tools/crosstool-install
再一次提示出錯:
……
checking for bison... bison
checking for flex... flex
checking for gperf... gperf
checking for makeinfo... no
configure: error: missing required tool: makeinfo
缺失makeinfo軟件,進行安裝,若是安裝的是makeinfo,則會有以下提示:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ sudo apt-get install makeinfo
正在讀取軟件包列表... 完成
正在分析軟件包的依賴關係樹
E: 沒法找到軟件包makeinfo
此時應該安裝 texinfo軟件:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ sudo apt-get install makeinfo
安裝完成以後,再一次進行配置:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ ./configure --prefix /home/zhuzhaoqi/arm-linux-tools/crosstool-install
此次成功的配置成功,若是讀者操做還會報錯的話,依照上面方法找出其根源進行改正便可。成功配置以後會自動建立了咱們須要的 Makefile文件。
checking for library containing initscr... -lncursesw
configure: creating ./config.status
config.status: creating Makefile
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ ls
bootstrap      configure     ct-ng.comp  LICENSES     patches  steps.mk
config         configure.ac  ct-ng.in    licenses.d   README   TODO
config.log     contrib       docs        Makefile     samples
config.status  COPYING       kconfig     Makefile.in  scripts
執行 Makefile文件:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ make
  SED    'ct-ng'
  SED    'scripts/crosstool-NG.sh'
  SED    'scripts/saveSample.sh'
  SED    'scripts/showTuple.sh'
  GEN    'config/configure.in'
  GEN    'paths.mk'
  GEN    'paths.sh'
  DEP    'nconf.gui.dep'
  DEP    'nconf.dep'
  DEP    'lxdialog/yesno.dep'
  DEP    'lxdialog/util.dep'
  DEP    'lxdialog/textbox.dep'
  DEP    'lxdialog/menubox.dep'
  DEP    'lxdialog/inputbox.dep'
  DEP    'lxdialog/checklist.dep'
  DEP    'mconf.dep'
  DEP    'conf.dep'
  BISON  'zconf.tab.c'
  GPERF  'zconf.hash.c'
  LEX    'lex.zconf.c'
  DEP    'zconf.tab.dep'
  CC     'zconf.tab.o'
  CC     'conf.o'
  LD     'conf'
  CC     'lxdialog/checklist.o'
  CC     'lxdialog/inputbox.o'
  CC     'lxdialog/menubox.o'
  CC     'lxdialog/textbox.o'
  CC     'lxdialog/util.o'
  CC     'lxdialog/yesno.o'
  CC     'mconf.o'
  LD     'mconf'
  CC     'nconf.o'
  CC     'nconf.gui.o'
  LD     'nconf'
  SED    'docs/ct-ng.1'
  GZIP   'docs/ct-ng.1.gz'
編譯成功以後進行安裝:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ make install
成功安裝以後,能夠看到已經安裝到咱們指定的目錄下,最後輸出有這麼一句話:
……
For auto-completion, do not forget to install 'ct-ng.comp' into
your bash completion directory (usually /etc/bash_completion.d)
這是在提醒咱們不要忘記了配置環境變量,多麼人性化的提示。接下來配置環境變量。
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0$ sudo echo "PATH=$PATH:/home/zhuzhaoqi/arm-linux-tools/crosstool-install/bin" >> ~/.bashrc
執行使其生效:
zhuzhaoqi@zhuzhaoqi-desktop:~$ source /home/zhuzhaoqi/.bashrc
使用 ct-ng –v命令查看安裝結果:
zhuzhaoqi@zhuzhaoqi-desktop:~$ ct-ng -v
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
 
這個程序建立爲 i486-pc-linux-gnu
OKct-ng環境變量添加成功,也就意味着整個 crosstool-ng安裝成功。
 
2.   配置交叉編譯鏈
如今須要去作的就是配置要編譯的交叉編譯工具鏈,在 crosstool-ng已不少已經作好的默認配置(位於 crosstool-ng- X.Y.Z(crosstool-ng-1.18.0)/samples目錄下),這裏只要針對其進行修改就行了。對於編譯器組件部分的版本最好不要修改,由於那個配搭應該是通過測試後的最高本版了,但內核版本能夠修改。
能夠看到 samples目錄下的一些默認配置以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0/samples$ ls
alphaev56-unknown-linux-gnu           mips64el-n64-linux-uclibc
alphaev67-unknown-linux-gnu           mips-ar2315-linux-gnu
arm-bare_newlib_cortex_m3_nommu-eabi  mipsel-sde-elf
arm-cortex_a15-linux-gnueabi          mipsel-unknown-linux-gnu
arm-cortex_a8-linux-gnueabi           mips-malta-linux-gnu
arm-davinci-linux-gnueabi             mips-unknown-elf
armeb-unknown-eabi                    mips-unknown-linux-uclibc
armeb-unknown-linux-gnueabi           powerpc-405-linux-gnu
armeb-unknown-linux-uclibcgnueabi     powerpc64-unknown-linux-gnu
arm-unknown-eabi                      powerpc-860-linux-gnu
arm-unknown-linux-gnueabi             powerpc-e300c3-linux-gnu
arm-unknown-linux-uclibcgnueabi       powerpc-e500v2-linux-gnuspe
armv6-rpi-linux-gnueabi               powerpc-unknown-linux-gnu
avr32-unknown-none                    powerpc-unknown-linux-uclibc
bfin-unknown-linux-uclibc   powerpc-unknown_nofpu-linux-gnu
i586-geode-linux-uclibc               s390-ibm-linux-gnu
i586-mingw32msvc,i686-none-linux-gnu  s390x-ibm-linux-gnu
i686-nptl-linux-gnu                   samples.mk
i686-unknown-mingw32                  sh4-unknown-linux-gnu
m68k-unknown-elf                      x86_64-unknown-linux-gnu
m68k-unknown-uclinux-uclibc           x86_64-unknown-linux-uclibc
mips64el-n32-linux-uclibc             x86_64-unknown-mingw32
裏面有不少默認配置,有 armavr32mipspowerpc等硬件平臺。而 arm平臺有以下:
arm-unknown-eabi是基於裸板,也就是無操做系統。
arm-unknown-linux-gnueabi 是基於linux
arm-unknown-linux-uclibcgnueabi 這個應該能看出來了,是爲uclinux用的。
arm-cortex_a15-linux-gnueabi可從名字上看是爲cortex-a15用的。
arm-cortex_a8-linux-gnueabi 這個也可從名字上看是爲cortex-a8用的。
arm-xxx$&#*&還有幾個,這些暫且不去理會。
這裏是製做 arm-linux交叉編譯鏈,所以,咱們選擇 arm-unknown-linux-gnueabi進行配置。將 arm-unknown-linux-gnueabi文件夾複製到 crosstool-build/目錄下:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-ng-1.18.0/samples$ cp -r arm-unknown-linux-gnueabi/ ../../crosstool-build/
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-build$ ls
arm-unknown-linux-gnueabi
將默認配置文件拷貝到工做目錄( crosstool-build)下並更名爲 .config,由於默認的配置文件爲 .config這個名字,完成以後能夠加載須要的配置。
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-build$ cp arm-unknown-linux-gnueabi/crosstool.config .config
執行 ct-ng menuconfig進入配置界面進行配置:
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-build$ ct-ng menuconfig
  LN    config
  MKDIR config.gen
  IN    config.gen/arch.in
  IN    config.gen/kernel.in
  IN    config.gen/cc.in
  IN    config.gen/binutils.in
  IN    config.gen/libc.in
  IN    config.gen/debug.in
  CONF  config/config.in
#
# configuration saved
#
進入配置界面如圖1. 1所示。
 
下面就是設置源碼目錄和安裝目錄,這須要按照讀者依據本身實際設定的狀況來進行配置。
第一步,設定源碼包路徑和交叉編譯器的安裝路徑。
Paths and misc options --->
   (/home/zhuzhaoqi/arm-linux-tools/src) Local tarballs directory 保存源碼包路徑
     (/home/zhuzhaoqi/arm-linux-tools/tools) Prefix directory交叉編譯器的安裝路徑
配置以後的結構如圖1. 2所示。
 
第二步,修改交叉編譯器針對的構架。
由於本次是針對 OK6410製做編譯鏈,那就依據 s3c6410的硬件特性來製做。
Target options這是重點要修改的地方。 (如下配置均是基於已拷貝過來的配置。 )
        Target Architecture(arm) 這個不用管,已是arm了。
        Default instruction set mode (arm) 這個也無論,也已是arm了。
Architecture level() 這個須要進行修改。
經過查找資料,這個應該是指令集的架構,對於 S3C6410 ARM1176JZF-S核心使用的是 armv6zk架構,就選 armv6zk。那麼,具體都支持哪些架構呢?能夠用 man gcc來查詢,搜索 arm,再搜索 -march=就能夠找到本 gcc支持的處理器核心列表了:
-march=name
This specifies the name of the target ARM architecture.  GCC uses
this name to determine what kind of instructions it can emit when
generating assembly code.  This option can be used in conjunction
with or instead of the -mcpu= option.  Permissible names are:
armv2, armv2a, armv3, armv3m, armv4, armv4t, armv5, armv5t, armv5e,
armv5te, armv6, armv6j, armv6t2, armv6z, armv6zk, armv6-m, armv7,
armv7-a, armv7-r, armv7-m, iwmmxt, iwmmxt2, ep9312.     
Emit assembly for CPU() 這個須要進行修改。
這個對應的是 CPU的核心類型。一樣,也和上面的選項同樣,對應一個 GCC選項。 GCC中這樣描述。
-mcpu=name
This specifies the name of the target ARM processor.  GCC uses this
name to determine what kind of instructions it can emit when
generating assembly code.  Permissible names are: arm2, arm250,
arm3, arm6, arm60, arm600, arm610, arm620, arm7, arm7m, arm7d,
arm7dm, arm7di, arm7dmi, arm70, arm700, arm700i, arm710, arm710c,
arm7100, arm720, arm7500, arm7500fe, arm7tdmi,arm7tdmi-s, arm710t,
arm720t, arm740t, strongarm, strongarm110, strongarm1100,
strongarm1110, arm8, arm810, arm9, arm9e, arm920, arm920t, arm922t,
arm946e-s, arm966e-s, arm968e-s, arm926ej-s, arm940t, arm9tdmi,
arm10tdmi, arm1020t, arm1026ej-s, arm10e, arm1020e, arm1022e,
arm1136j-s, arm1136jf-s, mpcore, mpcorenovfp, arm1156t2-s,
arm1176jz-s, arm1176jzf-s, cortex-a8, cortex-a9, cortex-r4,
cortex-r4f, cortex-m3, cortex-m1, xscale, iwmmxt, iwmmxt2, ep9312.
這樣看簡單一些了若是是  S3C2410/S3C2440 就選  arm920t  若是是 s3c6410就選 arm1176jzf-s
Tune for CPU() ,對應的 GCC描述是這樣的:
-mtune=name
This option is very similar to the -mcpu= option, except that
instead of specifying the actual target processor type, and hence
restricting which instructions can be used, it specifies that GCC
should tune the performance of the code as if the target were of
the type specified in this option, but still choosing the
instructions that it will generate based on the cpu specified by a
-mcpu= option.  For some ARM implementations better performance can
be obtained by using this option.
意思是說這個選項和 -mcpu 很相似,這裏是指定真實的 CPU型號。不過有讀者是編譯 2440的工具鏈,這裏選擇的是 arm9tdmi,若是不是,那就空着。這裏的做用是若是 arm920t處理不了,就用 arm9tdmi的方式來編譯。
Floating point() 浮點相關的選項 s3c6410 有硬件 VFP,因此這裏選的  hardware FPU。這個是給有硬浮點的處理器強行選軟浮點用的。
Use specific FPU() 是跟浮點有關,這裏不選任何內容。至於怎麼組合,讀者能夠跟據本身的 CPU的實際狀況相應的進行配置。
C compiler  --->
      *** Additional supported languages: ***  
      [ ] Java //不用這個編譯器來編譯java
固然若是讀者須要用它來編譯 java那就不用去除。
其它選項不動 Save an Alternate Configuration File存盤 Exit 退出, OK,配置完了。
zhuzhaoqi@zhuzhaoqi-desktop:~/arm-linux-tools/crosstool-build$ ct-ng build
開始編譯,此編譯過程須要花費大約兩個小時,最終編譯出 arm-linux-gcc-4.4.1編譯鏈。
 
1.1.1   交叉編譯鏈在宿主機上安裝
交叉編譯鏈版本:arm-linux-gcc 4.4.1。交叉編譯鏈的版本不少,讀者能夠自行安裝版本更高的編譯鏈。
Linux系統環境:Ubuntu10.04.4。Ubuntu10.04.4發佈於2012年2月17日,做爲Ubuntu 10.04 LTS第四個也是最後一個版本,Ubuntu 10.04.4 修復了大量錯誤,提升了穩定性與兼容性。
1.         在/usr/local下面建立一個文件夾:mkdir arm,將arm-linux-gcc 4.4.1放在arm文件夾裏面。而後解壓縮,命令根據壓縮包的後綴不一樣而不一樣。
2.         添加環境變量,vim /etc/profile。
3.         在最後一行添加:export PATH=$PATH:/usr/local/arm/4.4.1/bin。
4.         退出執行命令:source /etc/profile。使其生效。
5.         檢測安裝是否成功:arm-linux-gcc -v ;若是成功,輸出最後一行則會提示:gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67)。以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/u-boot/Makefile/shellfunction$ arm-linux-gcc -v
Using built-in specs.
Target: arm-none-linux-gnueabi
Configured with: /scratch/julian/2009q3-respin-linux-lite/src/gcc-4.4/configure --build=i686-pc-linux-gnu --host=i686-pc-linux-gnu --target=arm-none-linux-gnueabi --enable-threads --disable-libmudflap --disable-libssp --disable-libstdcxx-pch --enable-extra-sgxxlite-multilibs --with-arch=armv5te --with-gnu-as --with-gnu-ld --with-specs='%{funwind-tables|fno-unwind-tables|mabi=*|ffreestanding|nostdlib:;:-funwind-tables} %{O2:%{!fno-remove-local-statics: -fremove-local-statics}} %{O*:%{O|O0|O1|O2|Os:;:%{!fno-remove-local-statics: -fremove-local-statics}}}' --enable-languages=c,c++ --enable-shared --disable-lto --enable-symvers=gnu --enable-__cxa_atexit --with-pkgversion='Sourcery G++ Lite 2009q3-67' --with-bugurl=https://support.codesourcery.com/GNUToolchain/ --disable-nls --prefix=/opt/codesourcery --with-sysroot=/opt/codesourcery/arm-none-linux-gnueabi/libc --with-build-sysroot=/scratch/julian/2009q3-respin-linux-lite/install/arm-none-linux-gnueabi/libc --with-gmp=/scratch/julian/2009q3-respin-linux-lite/obj/host-libs-2009q3-67-arm-none-linux-gnueabi-i686-pc-linux-gnu/usr --with-mpfr=/scratch/julian/2009q3-respin-linux-lite/obj/host-libs-2009q3-67-arm-none-linux-gnueabi-i686-pc-linux-gnu/usr --with-ppl=/scratch/julian/2009q3-respin-linux-lite/obj/host-libs-2009q3-67-arm-none-linux-gnueabi-i686-pc-linux-gnu/usr --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic -lm' --with-cloog=/scratch/julian/2009q3-respin-linux-lite/obj/host-libs-2009q3-67-arm-none-linux-gnueabi-i686-pc-linux-gnu/usr --disable-libgomp --enable-poison-system-directories --with-build-time-tools=/scratch/julian/2009q3-respin-linux-lite/install/arm-none-linux-gnueabi/bin --with-build-time-tools=/scratch/julian/2009q3-respin-linux-lite/install/arm-none-linux-gnueabi/bin
Thread model: posix
gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67)
筆者建議安裝最好不要在root用戶下進行安裝,不然使用交叉編譯鏈可能會存在權限限制。
 
第一章第四節  映像文件的生產和運行
德國罕見的科學大師萊布尼茨,在他的手跡裏留下這麼一句話:「1與0,一切數字的神奇淵源。這是造物的祕密美妙的典範,由於,一切無非都來自上帝。」二進制0和1兩個簡單的數字,構造了神奇的計算機世界,對人類的生產活動和社會活動產生了極其重要的影響,並以強大的生命力飛速發展。在嵌入式系統移植過程當中,無論文件數量多麼龐大的工程,通過編譯工具的層層處理後,最終生成一個能夠加載到存儲器內執行的二進制映像文件(.bin)。本節內容將會探討映像文件的生成過程,以及它在存儲設備的不一樣位置對程序運行產生的影響,爲本書後文嵌入式系統的移植打下堅決的基礎。
 
1.1.1   編譯過程
GNU提供的編譯工具包括彙編器as、C編譯器gcc、C++編譯器g++、連接器ld、二進制轉換工具objcopy和反彙編的工具objdump等。它們被稱做GNU編譯器集合,支持多種計算機體系類型。基於ARM平臺的工具分別爲arm-linux-gcc、arm-linux-g++、arm-linux-ld、arm-linux-objcopy和arm-linux-objdump。arm-linux交叉編譯編譯工具鏈的製做方法已經詳細介紹過,編譯程序直接使用前面製做好的工具鏈。
GNU編譯器的功能很是強大,程序能夠用C文件、彙編文件編寫,甚至是兩者的混合。如1. 3所示是程序編譯的大致流程,源文件通過預處理器、彙編器、編譯器、連接器處理後生成可執行文件,再由二進制轉換工具轉換爲可用於燒寫到Flash的二進制文件,同時爲了調試的方便還能夠用反彙編工具生成反彙編文件。圖中雙向箭頭的含義是,當gcc增長一些參數時能夠相互調用匯編器和連接器進行工做。例如輸入命令行「gcc –O  main.c」後,直接就獲得可執行文件a.out(elf)
 
 
 
 
 
程序編譯大致上能夠分爲編譯和連接兩個步驟:把源文件處理成中間目標文件.o(linux)、obj(windows)的動做稱爲編譯;把編譯造成的中間目標文件以及它們所須要的庫函數.a(linux) 、lib(windows)連接在一塊兒的動做稱爲連接。現用一個簡單的test工程來分析程序的編譯流程,麻雀雖小五臟俱全,它由啓動程序start.S、應用程序main.c、連接腳本test.lds和Makefile四個文件構成。test工程中的程序經過操做單板上的LED燈的狀態來斷定程序的運行結果,它除了用於理論研究以外,沒有其它的實用價值。
1.   編譯
在編譯階段,編譯器會檢查程序的語法、函數與變量的聲明狀況等。若是檢查到程序的語法有錯誤,編譯器當即中止編譯,並給出錯誤提示。若是程序調用的函數、變量沒有聲明原型,編譯器只會拋出一個警告,繼續編譯生成中間目標文件,待到連接階段進一步肯定調用的變量、函數是否存在。
程序清單1.  1start.S中彙編代碼
/*
*      This is a part of the test project
*      Author: LiQiang Date: 2013/04/01
*      Licensed under the GPL-2 or later.
*/
.globl _start
_start:
 
    #define REG32 0x70000000
    ldr r0, =REG32
    orr r0, r0, #0x13
    mcr p15,0,r0,c15,c2,4      
 
    /*關閉看門狗*/
    #define WATCHDOG 0x7E004000
    ldr r0, =WATCHDOG
    mov r1, #0
    str r1, [r0]
 
clean_bss:
    ldr r0, =bss_start
    ldr r1, =bss_end
    mov r3, #0
    cmp r0, r1
    beq clean_done
clean_loop:
    str r3, [r0], #4
    cmp r0, r1  
    bne clean_loop      
clean_done:
 
     /* 初始化棧 S3C6410 8KSRAM映射到0地址處*/
     ldr sp, =8*1024
     bl main
halt:
    b halt  
start.S文件的內容如程序清單1. 1,文件中的_start函數爲C語言運行環境作最低限度的初始化:將S3C6410處理外設端口的地址範圍告知ARM內核,關閉看門狗,清除bss段,初始化棧。初始化工做完畢後,跳轉到main()。start.S是用匯編語言編寫的代碼文件,文件中定義了一個WATCHDOG宏,用於寄存器的賦值。在彙編文件中出現#define宏定義語句,對於初學者可能會有些迷惑。
事實上,彙編文件有「.S」和「.s」兩種後綴,在以「.s」爲後綴的彙編文件中,程序徹底是由純粹的彙編代碼編寫。所謂的純粹是相對以「.S」爲後綴的彙編文件而言的,因爲現代彙編工具引入了預處理的概念,容許在彙編代碼(.S)中使用預處理命令。預處理命令以符號「#」開頭,包括 宏定義、文件包含和 條件編譯。在U-Boot和Linux內核源碼中,這種編程方式運用很是普遍
程序清單1.  2  main.c文件內容
/*
*  This is a part of the test project
* Author: LiQiang Date: 2013/04/01
* Licensed under the GPL-2 or later.
*/
#define GPMCON  *((volatile unsigned long*)0x7F008820)
#define GPMDAT  *((volatile unsigned long*)0x7F008824)
#define GPMPUD  *((volatile unsigned long*)0x7F008828)
int main()
{
    static int flag = 12;
    GPMCON = 0x1111; /* 輸出模式 */
    GPMPUD = 0x55;   /* 使能下拉 */
    GPMDAT = 0x0f;   /* 關閉LED */
    if(12 == flag)
        GPMDAT = 0x00;
    else
        GPMDAT = 0x0f;
    while(1);
    return 0;
}
main.c文件內容如程序清單1. 2所示,main.c中的main函數是運行完_start函數的跳轉點。main()中首先定義了一個靜態局部變量初值爲12,而後配置S3C6410處理器的GPM端口爲輸出、下拉模式,並將GPM低四位管腳的設爲高電平(單板上LED在管腳爲高電平的熄滅)。最後判斷是flag是否等於12,若是等於點亮LED,不然不點亮。從程序上看,這個判斷語句好像畫蛇添足、莫名其妙,由於flag期間並無做任何改變。其實,這個變量是爲講解程序的運行地址和加載地址的概念而定義的,它與程序運行的位置有關。
將上面兩個源碼文件處理成中間目標文件,分別輸入以下命令行:
arm-linux-gcc -o mian.o main.c –c
arm-linux-gcc -o start.o start.S –c
獲得main.o  Start.o兩個中間目標文件,供連接器使用。
 
2.   連接
連接是彙編階段生成的中間目標文件,相互查找本身所須要的函數與變量,重定向數據,完成符號解析的過程。包括對全部目標文件進行重定位、創建符號引用規則,同時爲變量、函數等分配運行地地址。函數與變量可能來源與其它中間文件或者庫文件,若是沒有找到所需的實現,連接器當即中止連接,給處錯誤提示。
利用一個連接腳本(.lds後綴)來指導連接連接器工做。控制輸出節在映像文件中的佈局。fortest.lds是一個簡單的連接腳本,指示了程序的運行地址(又稱連接地址)爲0x5000_0000以及text段、data段和bss段在映像文件中的空間排布順序。fortest.lds文件的內容以下:
ENTRY(_start)
SECTIONS
{
        . = 0x50000000;
        . = ALIGN(4);
        .text : {
                start.o (.text)
                * (.text)
        }
 
        .data : {
                * (.data)
        }
 
        bss_start = .;
        .bss : {
                * (.bss)
        }
        bss_end  = .;
}
1)        text段代碼段(text segment),一般是用來存放程序執行代碼的內存區域。這塊區域的大小在程序編譯時就已經肯定,而且內存區域一般屬於只讀,某些架構也容許代碼段爲可寫,即容許修改程序。在代碼段中,也有可能包含一些只讀的常數變量,例如字符串常量。
2)        data段數據段(data segment),數據段是存放已經初始化不爲0的靜態變量的內存區域,靜態變量包括全局變量和靜態局部變量,它們與程序有着相同的生存期。
3)        bss段bss segment,bbs段與data段相似,也是存放的靜態變量的內存區域。與data段不一樣的是,bbs段存放是沒有初始化或者初始化爲0的靜態變量,而且bbs段不在生成的可執行二進制文件內。bss_start表示這塊內存區域的起始地址,bss_end表示結束地址,它們由編譯系統計算獲得。未初始化的靜態變量默認爲0,所以程序開始執行的時候,在bss_start到bss_end內存中存儲的數據都必須是0。
4)        其餘段,上面三個段是編譯系統預約義的段名,用戶還能經過.section僞操做自定義段,在後面的移植過程咱們會發現,Linux內核源碼中爲了合理地排布數據實現特定的功能,定義了各類各樣的段。
在宿主機上輸入如下命令行,完成中間的目標文件的連接和可執行二進制文件的格式轉換。
arm-linux-ld –T test.lds -o test.elf start.o main.o
arm-linux-objcopy -O binary test.elf test.bin
arm-linux-objdump -D test.elf > test.dis
如圖1. 4所示是使用arm-linux-objcopy格式轉換工具獲得的二進制文件test.bin的內容,這些內容是處理器可以識別的機器碼,咱們每每難以直接閱讀、理解它們的含義。使用arm-linux-objdump工具生成便以咱們閱讀的反彙編文件test.dis。對比二進制文件test.bin的內容,耐心細緻地分析反彙編文件,如程序清單1. 3所示,能夠提煉出大量的信息。
程序清單1.  3text.dis文件內容
50000000 <_start>:  /* 代碼段起始位置程序的運行地址爲0x5000_0000*/
50000000:   e3a00207    mov r0, #1879048192 ; 0x70000000
50000004:   e3800013    orr r0, r0, #19 ; 0x13
50000008:   ee0f0f92    mcr 15, 0, r0, cr15, cr2, {4}
5000000c:   e59f0030    ldr r0, [pc, #48]   ; 50000044 <halt+0x4>
50000010:   e3a01000    mov r1, #0  ; 0x0
50000014:   e5801000    str r1, [r0]
 
50000018 <clean_bss>: /* 清除bss */
50000018:   e59f0028    ldr r0, [pc, #40]   ; 50000048 <halt+0x8>
5000001c:   e59f1028    ldr r1, [pc, #40]   ; 5000004c <halt+0xc>
50000020:   e3a03000    mov r3, #0  ; 0x0
50000024:   e1500001    cmp r0, r1
50000028:   0a000002    beq 50000038 <clean_done>
 
5000002c <clean_loop>:
5000002c:   e4803004    str r3, [r0], #4
50000030:   e1500001    cmp r0, r1
50000034:   1afffffc    bne 5000002c <clean_loop>
 
50000038 <clean_done>:
50000038:   e3a0da02    mov sp, #8192   ; 0x2000 /* 初始化sp */
5000003c:   eb000003    bl  50000050 <main> /* 跳轉至mian() */
 
50000040 <halt>:
50000040:   eafffffe    b   50000040 <halt>
50000044:   7e004000    .word   0x7e004000
50000048:   500000e0    .word   0x500000e0
5000004c:   500000e0    .word   0x500000e0
 
50000050 <main>: /* main()*/
50000050:   e52db004    push    {fp}        ; (str fp, [sp, #-4]!)
50000054:   e28db000    add fp, sp, #0  ; 0x0
50000058:   e3a0247f    mov r2, #2130706432 ; 0x7f000000
5000005c:   e2822b22    add r2, r2, #34816  ; 0x8800
50000060:   e2822020    add r2, r2, #32 ; 0x20
50000064:   e3a03c11    mov r3, #4352   ; 0x1100
50000068:   e2833011    add r3, r3, #17 ; 0x11
5000006c:   e5823000    str r3, [r2]  /*  GPMCON = 0x1111 */
50000070:   e3a0347f    mov r3, #2130706432 ; 0x7f000000
50000074:   e2833b22    add r3, r3, #34816  ; 0x8800
50000078:   e2833028    add r3, r3, #40 ; 0x28
5000007c:   e3a02055    mov r2, #85 ; 0x55
50000080:   e5832000    str r2, [r3]  /*  GPMPUD = 0x55 */
50000084:   e3a0347f    mov r3, #2130706432 ; 0x7f000000
50000088:   e2833b22    add r3, r3, #34816  ; 0x8800
5000008c:   e2833024    add r3, r3, #36 ; 0x24
50000090:   e3a0200f    mov r2, #15 ; 0xf
50000094:   e5832000    str r2, [r3]  /* GPMDAT = 0x0f */
50000098:   e59f3038    ldr r3, [pc, #56]   ; 500000d8 <main+0x88>  /* 讀取flag變量存儲地址 */
5000009c:   e5933000    ldr r3, [r3]/* 讀取flag變量的值 */
500000a0:   e353000c    cmp r3, #12 ; 0xc
500000a4:   1a000005    bne 500000c0 <main+0x70>
500000a8:   e3a0347f    mov r3, #2130706432 ; 0x7f000000
500000ac:   e2833b22    add r3, r3, #34816  ; 0x8800
500000b0:   e2833024    add r3, r3, #36 ; 0x24
500000b4:   e3a02000    mov r2, #0  ; 0x0
500000b8:   e5832000    str r2, [r3]
500000bc:   ea000004    b   500000d4 <main+0x84>
500000c0:   e3a0347f    mov r3, #2130706432 ; 0x7f000000
500000c4:   e2833b22    add r3, r3, #34816  ; 0x8800
500000c8:   e2833024    add r3, r3, #36 ; 0x24
500000cc:   e3a0200f    mov r2, #15 ; 0xf
500000d0:   e5832000    str r2, [r3]
500000d4:   eafffffe    b   500000d4 <main+0x84>
500000d8:   500000dc    .word   0x500000dc
Disassembly of section .data:
 
500000dc <flag.1245>: /* flag變量的地址爲0x5000_00dc,值爲12 */
500000dc:   0000000c    .word   0x0000000c
從test.dis反彙編文件中可知,test.bin包含了代碼段和數據段,並無包含bss段。咱們知道,bbs內存區域的數據初始值所有爲零,區域的起始位置和結束位置在程序編譯的時候預知。很容易想到在程序開始運行時,執行一小段代碼將這個區域的數據所有清零便可,不必在test.bin包含全爲0的bss段。編譯器的這種機制有效地減少了鏡像文件的大小、節約了磁盤容量。
main()函數的核心功能是驗證flag變量是否等於12,如今追蹤下這個操做的實現過程。要想讀取flag的值,必須知道它的存儲位置,首先執行指令「ldrr3, [pc, #56]」獲得flag變量的地址(指針)。pc與56相加合成一個地址,它是相對pc偏移56產生的。pc+56地址處存放了flag變量的指針0x5000_00dc,讀取出來存放到r3寄存器。而後執行指令「ldrr3, [r3]」將內存0x5000_00dc地址處的值讀出,這個值就是flag,並覆蓋r3寄存器。最後,判斷r3寄存器是否等於12。flag變量的地址在連接階段已經被分配好了,固定在0x5000_00dc處,可是從代碼中,咱們沒有找到對flag變量賦初值的語句,儘管在main函數已經用C語句「flag = 12」對它賦初值。
現提供一個驗證程序效果的簡單方法:將S3C6410處理器設置爲SD卡啓動方式,使用SD_Writer軟件將test.bin燒寫至SD卡中,而後將SD卡插入單板的卡槽,復位啓動便可。實際上,啓動的時候test.bin被加載到內部SRAM中,SRAM映射到0地址處。這個簡單方法能夠用來驗證一些裸板程序,方法實現的原理和SD_Writer軟件用法如今不展開討論,目前只要會使用便可。復位後,LED並無點亮。
若是每次編譯都要重複輸入編譯命令,操做起來很麻煩,爲此test工程中創建了一個Makefile文件,內容以下:
test.bin: start.o main.o
    arm-linux-ld -T fortest.lds -o test.elf start.o main.o
    arm-linux-objcopy -O binary test.elf test.bin
    arm-linux-objdump -D test.elf > test.dis
 
start.o : start.S
    arm-linux-gcc -o start.o start.S -c
main.o : main.c
    arm-linux-gcc -o main.o main.c -c
 
clean:
    rm *.o test.*
當將連接腳本中的運行地址修改成0時,進入test目錄,輸入「make clean」命令清除舊的文件,再輸入「make」從新編譯程序,驗證新生成的test.bin文件的效果,發現LED所有點亮,產生這個現象的緣由在下一個小節講述。
1.1.1   代碼搬運
當程序執行時,必須把代碼搬運到連接時所指定的運行地址空間,以保證程序在執行過程當中對變量、函數等符號的正確引用。在帶有操做系統中,這個過程由操做系統負責完成。而在裸機環境下,鏡像文件的運行地址由程序員根據具體平臺指定,加載地址又與處理器的設計密切相關。一般狀況下,啓動代碼最早執行一段位置無關碼,這段代碼實現程序從加載地址到運行地址的重定位,或者將程序從外部存儲介質直接拷貝至其運行地址。
1.   位置無關碼
位置無關碼必須具備位置無關的跳轉、位置無關的常量訪問等特色,不能訪問靜態變量,都是相對pc的偏移量來函數的跳轉或者常量的訪問。在ARM 體系中,使用相對跳轉指令b/bl實現程序跳轉。指令中所跳轉的目標地址用基於當前PC的偏移量來表示,與連接時分配給地址標號的絕對地址值無關,於是代碼能夠在任何位置正確的跳轉,實現位置無關性。
使用ldr僞指令將一個常量讀取到非pc的其餘通用寄存器中,可實現位置無關的常量訪問。例如:
ldr r0, =WATCHDOG
若是使用ldr僞指令將一個函數標號讀取到pc,這是一條與位置有關的跳轉指令,執行的結果是跳轉到函數的運行地址處。
2.   運行地址與加載地址
試想一下,當系統上電覆位的時候,若是test.bin恰好位於在0x5000_0000地址(flag的初值12位於0x5000_00dc),PC指向0x5000_0000地址,那麼這段代碼按照上述flag變量的讀取步驟,可以準確無誤的獲得結果。可是,若是test.bin位於0地址(flag的初值12位於0xdc,LED不亮時的狀況),PC指向0地址,程序依然從0x5000_00dc地址讀取flag變量,實際上它的初值位於0xdc。這時從C語言的角度看,出現一個flag不等於它的初值的現象(期間沒有改變flag)。出現錯誤的緣由是在程序中使用了位置相關的變量,但運行地址與加載地址不一致(加載地址爲0,運行地址爲0x5000_0000)。由此,可以容易理解運行地址和加載地址的含義:
加載地址是系統上電啓動時,程序被加載到可直接執行的存儲器的地址,也就是程序在RAM或者Flash ROM中的地址。由於有些存儲介質只能用來存儲數據不能執行程序,例如SD卡和NAND Flash等,必須把程序從這些存儲介質加載到能夠執行的地址處。運行地址就是程序在連接時候肯定的地址,好比fortest.lds連接腳本指定了程序的運行地址爲0x5000_0000,那麼連接器在爲變量、函數等分配地址的時候就會以0x5000_0000做爲參考。當加載地址和運行地址不相等時,必須使用與位置無關碼把程序代碼從它的加載地址搬運至運行地址,而後使用「ldr pc, =label」指令跳轉到運行地址處執行。
 
1.1.2   混合編程
在嵌入式系統底層編程中,C語言和彙編兩種編程語言的使用最普遍。C語言開發的程序具備可讀性高,容易修改、移植和開發週期短等特色。可是,C語言在一些場合很難或沒法實現特定的功能:底層程序須要直接與CPU內核打交道,一些特殊的指令在C語言中並無對應的成分,例如關閉看門狗、中斷的使能等;被系統頻繁調用的代碼段,對代碼的執行效率要求嚴格的時候。事實上,CPU體系結構並不一致,沒有對內部寄存器操做的通用指令。彙編語言與CPU的類型密切相關,提供的助記符指令可以方便直接地訪問硬件,但要求開發人員對CPU的體系結構十分熟悉。在早期的微處理器中,因爲處理器速度、存儲空間等硬件條件的限制,開發人員不得不選用匯編語言開發程序。隨着微處理器的發展,這些問題已經獲得很好的解決。若是依然徹底使用匯編語言編寫程序,工做量會很是大,系統很難維護升級。大多數狀況下,充分結合兩種語言的特色,彼此相互調用,以約定規則傳遞參數,共享數據。
1.   彙編函數與C語言函數相互調用
C程序函數與彙編函數相互調用時必須嚴格遵循ATPCS(ARMThumb Procedure Call Standard)。函數間約定R0、R1和R2爲傳入參數,函數的返回值放在R0中。GNU ARM編譯環境中,在彙編程序中要使用.global僞操做聲明改彙編程序爲全局的函數,可被外部函數調用。在C程序中要被彙編程序調用的C函數,一樣須要用關鍵字extern聲明。
程序清單1.  4代碼重定位函數
.globl  relocate_code
relocate_code:
    mov r4, r0  /* save addr_sp */
    mov r5, r1  /* save addr of gd */
    mov r6, r2  /* save addr of destination */
 
    /* Set up the stack */
stack_setup:
    mov sp, r4
 
    adr r0, _start
    cmp r0, r6
    beq clear_bss       /* skip relocation */
    mov r1, r6          /* r1 <- scratch for copy_loop */
    ldr r3, _bss_start_ofs
    add r2, r0, r3      /* r2 <- source end address     */
……
程序清單1. 4是從arch\arm\cpu\arm1176\start.S文件(U-Boot)中截取的代碼片斷,relocate_code函數用於重定位代碼。它在C程序中,經過relocate_code(addr_sp, id, addr)被調用。變量addr_sp、id和addr分別經過寄存器R0、R1和R3傳遞給彙編程序,實現了C函數和彙編函數數據的共享。
 
2.   C語言內嵌彙編
當須要在C語言程序中內嵌彙編代碼時,能夠使用gcc提供的asm語句功能。
程序清單1.  5整數原子加操做的實現
/*
* ARMv6 UP and SMP safe atomic ops.  We use load exclusive and
* store exclusive to ensure that these are atomic.  We may loop
* to ensure that the update happens.
*/
static inline void  atomic_add (int i, atomic_t *v)
{
    unsigned long tmp;
    int result;
 
    __asm__ __volatile__("@ atomic_add\n"
"1: ldrex   %0, [%3]\n"
"   add %0, %0, %4\n"
"   strex   %1, %0, [%3]\n"
"   teq %1, #0\n"
"   bne 1b"
    : "=&r" (result), "=&r" (tmp), "+Qo" ( v->counter )
    : "r" (&v->counter), "Ir" ( i )
    : "cc");
}
程序清單1. 5是從Linux源碼文件arch/arm/include/asm/atomic.h截取的一段代碼,本節內容不分析函數的具體實現。對於初學者,這段代碼看起來晦澀難懂,由於這不是標準C所定義的形式,而是gcc對C語言擴充的asm功能語句,用以在C語言程序中嵌入彙編代碼。asm語句最經常使用的格式爲:
__asm __  __volatile__(「 inst1 op1, op2, \n
「 inst2 op1, op2, \n」         /* 指令部分必選*/
...
「 instN op1, op2, \n
: output_operands   /* 輸出操做數可選 */
: input_operands        /* 輸入操做數可選 */
: clobbered_operands    /* 損壞描述部分可選*/
);
它由四個部分組成:指令部分,輸出部分,輸入部分,損壞描述部分。各部分使用「:」格開,指令部分必不可少,其餘三部分可選,可是若是使用了後面的部分,而前面部分爲空,也須要用「:」分隔,相應部份內容爲空。__asm__表示彙編語句的起始,__volatile__是一個可選項,加上它能夠防止編譯器優化時對彙編語句刪除、移動。
指令部分,指令之間使用「\n」(也能夠使用「;」或者「\n\t」)分隔。嵌入彙編指令的格式與標準彙編指令的格式大致相同,嵌入彙編指令的操做數使用佔位符「%」預留位置,用以引用C語言程序中的變量。操做數佔位符的數量取決於CPU中通用寄存器的總數量,佔位符的格式爲%0,%1,……,%n。
輸出、輸入部分,這兩部分用於描述操做數,不一樣的操做數描述語句之間用逗號分隔,每一個操做數描述符由限定字符串和C語言表達式組成,當限定字符串中帶有「=」時表示該操做數爲輸出操做數。限定字符串用於指示編譯器如何處理C語言表達式與指令操做數之間的關係,限定字符串中的限定字母有不少種,有些是通用的,有些跟特定的體系相關。在程序清單1. 5中:result、tmp和v->counter是輸出操做數,分別賦給%0、%1和%2;v->counter和i是輸入操做數,分別賦給%3和%4。其中,「r」:表示把變量放入通用寄存器中;「I」:表示0-31之間的常數。
第二章第一節   U-Boot-2013.04 分析與移植之 BootLoader 概述

   朱兆祺從本節開始,就帶領你們進入U-Boot-2013.04的移植,這是2013年4月份發佈的U-Boot源碼,是U-Boot版本中的重要分水嶺。從本節開始,若是你們要跟隨個人步伐,那就得準備好一塊OK6410開發板,由於我講解嵌入式Linux的學習是以OK6410爲載體進行的。
    從最終用戶的角度看,BootLoader(即啓動代碼)是處理器復位後進入操做系統以前執行的一段代碼,用以完成由硬件啓動到操做系統啓動的過渡,爲操做系統的運行提供基本的環境,如關閉看門狗、初始化時鐘和配置存儲器等。啓動代碼的最終目的是引導操做系統的啓動,但從開發人員的角度看,爲了開發和調試的方便,還會增長串口控制、以太網絡等功能。
    嵌入式系統與應用密切結合,它具備很強的專用性。實際系統的需求每每千差萬別,BootLoader代碼與CPU的類型、應用系統的配置及使用的操做系統等因素密切相關,這就註定了不可能有徹底通用的BootLoader,實際運用時必須根據具體狀況對啓動代碼進行移植。
表2.  1開發板配置
類別 
型號
規格
CPU
S3C6410
-
NAND Flash
K9GAG08U0D
2G
DRAM
K4X1G163PC
128M*2
Ethernet
DM9000A
-
LCD
WXCAT43
4.3寸
    本文所寫的內容都是基於表2. 1所示配置的單板(board):NAND芯片K9GAG08U0D共4096塊,每塊的包含128頁,每頁由4096字節的數據區和218字節的空閒區組成。兩片64 M×16 bit的Mobile DDR芯片K4X1G163PC,組合構成共256 MB的內存。儘管每一個人持有的單板配置各異,但分析、移植的原理相通。
    U-Boot,全稱爲Universal Boot Loader(通用bootloader),是遵循GPL條款的開放源碼項目。由德國DENX小組開發和維護的,其高超的技術使得U-Boot可以很是容易地被移植到多種嵌入式CPU中,支持多種嵌入式操做系統內核的引導。很多U-Boot源碼就是linux內核源碼的簡化,特別是一些設備的驅動程序,使得它在引導Linux Kernel方面尤其出色。本文選用當前最新版本的U-Boot,結合S3C6410處理器自身的特色,分析和探討它的移植要點。筆者認爲移植工做必須在充分理解源碼的組織方式、處理器特色、單板外圍器件原理等基礎上進行。充分利用源碼已經實現的功能,最小限度地破壞源碼的結構。
第二章第三節  創建OK6410可用的U-Boot模板
迄今爲止,U-Boot的最新版本 u-boot-2013.04-rc1.tar.bz2 U-Boot全部版本的下載地址爲:http://ftp.denx.de/pub/u-boot/rc(Release Candidate)表示正式發行候選版,1表明版本號,rc1即候選版的初版。rc版本發佈於軟件的正式定稿以前,期間不會再加入新的功能或模塊,這一版本的發佈主要是爲了讓開源社區經驗豐富的開發者,率先試用以及時反饋和修正源碼中存在的錯誤、漏洞,這個階段事後就會發布相對穩定的正式版。做者在移植該版本的時候,發現幾處較爲明顯的錯誤,並經過U-Boot的郵件列表反饋了這些錯誤信息。這些錯誤的位置以及修正方法將會在移植的時候逐一介紹。
讀者閱讀本書時,u-boot-2013.04正式版本可能已經發布,甚至有更新版本的源碼發佈。其實U-boot版本間的差異並非很大,較新版本僅僅在前面版本的基礎上,增長或者修改了一些驅動程序,對源碼的結構稍微的調整,但核心內容不會作太大的改變。但相比2010-03之前的版本,U-Boot後面的版本的源碼組織結構做了較大的調整,使得其源碼目錄、編譯形式與Linux Kernel源碼越發類似。
liqiang@ubuntu:~/work/forbook$ tar -jxvf u-boot-2013.04-rc1.tar.bz2 -C ./
輸入tar命令,解壓源碼到當前文件夾。進入u-boot-2013.04-rc1目錄,其中有個文件名爲「readme」的幫助文檔,通讀readme文件,咱們可以大致上瞭解U-Boot得到和尋求幫助的途徑,源碼目錄的組織結構,工程配置、編譯的方法等。U-Boot頂層目錄存在不少子目錄,下面介紹一些主要的目錄:
1)       arch對應不一樣構架的CPU,子目錄的名字就是所支持的CPU構架的名稱,如arch目錄下含有arm、avr32以及x86等,這些目錄能夠繼續細分。例如arm下含有cpu 、include 和lib等目錄,其中cpu用於進一步區分不一樣體系的arm內核。
2)       common該目錄存在的是一些公共的通用代碼文件,包括用於實現各類公共命令的cmd_*.c、env_*.c文件已經一些通用函數。它們獨立於處理器的體系結構,直接跟用戶打交道,是用戶與設備驅動之間溝通的紐帶,也是U-Boot的精髓所在。
3)       drivers 該目錄存放的是各種外設的驅動程序,如mmc、serial和net等。
4)       fs 支持文件系統。
5)       net 該目錄裏面的文件實現了各類網絡協議。
6)       nand_spl 實現U-Boot從NAND Flash中啓動的設備驅動。
7)       include 一些頭文件和單板配置文件,全部單板配置的文件位於include/configs目錄下。
8)       tools 經常使用工具,包括用於製做uImage的mkimage工具。
         9)Makefile文件控制着整個工程的編譯。

u-boot-2013-04-rc1中沒有對S3C6410處理器相關單板的支持,但支持帶有S3C6400處理器的SMDK6400單板。若是咱們要本身編寫全部的啓動代碼,工做量很大且很容易出錯。S3C6400S3C6410是三星公司推出的S3C64xx系列的處理器,都是基於16/32-bit RISC內核的低成本、低功耗、高性能微處理器解決方案。它們大體功能基本相同,硬件管腳兼容。若是用現有的SMDK6400做爲模板,就可以幫助咱們迅速地創建起一個大體的框架,後期再根據兩者的異同點填充、修改框架的內容。
事實上,就算U-Boot源碼中支持S3C6410相關的單板,因爲嵌入式系統應用的場合不同、需求不一樣,所以單板之間的配置不可能徹底一致,移植U-boot的工做也必須根據實際狀況進行。在本節內容中,將會帶領你們一步一步地創建一個可以經過編譯最小模板,修正一些源碼自身的錯誤,更多功能的移植再分章節具體闡述。
1.   修改頂層Makefile
liqiang@ubuntu:~/work/forbook$ cd u-boot-2013.04-rc1/
       輸入cd命令進入源碼目錄。
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ vim Makefile
       vim文本編輯器打開頂層Makefile文件。
792 smdk6400_noUSB_config   \
793 smdk6400_config :       unconfig
794         @mkdir -p $(obj)include $(obj)board/samsung/smdk6400
795         @mkdir -p $(obj)nand_spl/board/samsung/smdk6400
796         @echo "#define CONFIG_NAND_U_BOOT" > $(obj)include/config.h
797         @echo "CONFIG_NAND_U_BOOT = y" >> $(obj)include/config.mk
798         @if [ -z "$(findstring smdk6400_noUSB_config,$@)" ]; then      
                    \
799                 echo "RAM_TEXT = 0x57e00000" >> $(obj)board/samsung/smdk
    6400/config.tmp;\
800         else                                                            
                    \
801                 echo "RAM_TEXT = 0xc7e00000" >> $(obj)board/samsung/smdk
    6400/config.tmp;\
802         fi
803         @$(MKCONFIG) smdk6400 arm arm1176 smdk6400 samsung s3c64xx
804         @echo "CONFIG_NAND_U_BOOT = y" >> $(obj)include/config.mk
將這段內容複製,緊跟在這段內容的最後一行粘貼,而後修改爲以下內容(6400改爲6410):
806 smdk6410_noUSB_config   \
807 smdk6410_config :       unconfig
808         @mkdir -p $(obj)include $(obj)board/samsung/smdk6410
809         @mkdir -p $(obj)nand_spl/board/samsung/smdk6410
810         @echo "#define CONFIG_NAND_U_BOOT" > $(obj)include/config.h
811         @echo "CONFIG_NAND_U_BOOT = y" >> $(obj)include/config.mk
812         @if [ -z "$(findstring smdk6410_noUSB_config,$@)" ]; then      
                    \
813                 echo "RAM_TEXT = 0x57e00000" >> $(obj)board/samsung/smdk
    6410/config.tmp;\
814         else                                                            
                    \
815                 echo "RAM_TEXT = 0xc7e00000" >> $(obj)board/samsung/smdk
    6410/config.tmp;\
816         fi
817         @$(MKCONFIG) smdk6410 arm arm1176 smdk6410 samsung s3c64xx
818         @echo "CONFIG_NAND_U_BOOT = y" >> $(obj)include/config.mk
      
2.   建立SMDK6410單板信息
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ mkdir board/samsung/smdk6410
board/samsung目錄下建立smdk6410目錄。
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ cp board/samsung/smdk6400/* board/samsung/smdk6410
board/samsung/smdk6400目錄包含的全部文件複製到board/samsung/smdk6410目錄。
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ cd board/samsung/smdk6410/
進入board/samsung/smdk6410目錄。
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1/board/samsung/smdk6410$ mv smdk6400_nand_spl.c smdk6410_nand_spl.c
smdk6400_nand_spl.c文件重命名爲smdk6410_nand_spl.c
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1/board/samsung/smdk6410$ mv smdk6400.c smdk6410.c
一樣把smdk6400.c文件重命名爲smdk6410.c
95 int checkboard(void)
96 {
97         printf("Board:   SMDK6400\n");
98         return 0;
99 }
打開smdk6410.c文件,把以上單板信息打印函數修改成
95 int checkboard(void)
96 {
97         printf("Board:   SMDK6410\n");
98         return 0;
99 }
打開當前目錄下的Makefile文件,將COBJS-y := smdk6400.o修改成smdk6410.o
3.   創建nand_spl
nand_splU-Boot專門爲NAND Flash啓動而設計的,實現原理後文將會詳細闡述,如今只是簡單地創建模板。
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ mkdir nand_spl/board/samsung/smdk6410
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ cp nand_spl/board/samsung/smdk6400/* nand_spl/board/samsung/smdk6410/
與上述建立單板方法相似,建立nand_spl/board/samsung/smdk6410/目錄,並把nand_spl/board/samsung/smdk6400目錄中的全部文件拷貝到建立的目錄下。
進入nand_spl/board/samsung/smdk6410/目錄,打開當前目錄下的Makefile文件,將全部的「6400」字符串修改成「6410」,例如smdk6400_nand_spl.o修改成smdk6410_nand_spl.o
4.   建立s3c6410.h頭文件
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ cp arch/arm/include/asm/arch-s3c64xx/s3c6400.h arch/arm/include/asm/arch-s3c64xx/s3c6410.h
以頭文件s3c6400.h爲藍本,先簡單地創建S3C6410處理器的寄存器頭文件s3c6410.h
820 #define DMC1_MEM_CFG    0x00010012      /* burst 4, 13-bit row, 10-bit col */
821 #define DMC1_MEM_CFG2   0xB45
822 #define DMC1_CHIP0_CFG  0x150F8         /* 0x5000_0000~0x57ff_ffff (128 MiB) */
本書所用單板外接的DRAM大小爲256Mb,而源碼默認支持的128Mb,所以須要對其進行修改。打開s3c6400.h文件,將以上內容修改成:
820 #define DMC1_MEM_CFG    0x0001001a      /* burst 4, 14-bit row, 10-bit col */
821 #define DMC1_MEM_CFG2   0xB45
822 #define DMC1_CHIP0_CFG  0x150F0         /* 0x5000_0000~0x5fff_ffff (256MiB) */
5.   修改處理器Makefile
liqiang@liqiang-virtual-machine:~/work/forbook/u-boot-2013.04-rc1$ vim
arch/arm//cpu/arm1176/s3c64xx/Makefile
       打開s3c640xx目錄中的Makefile文件,增長對S3C6410處理器的的支持。
33 COBJS-$(CONFIG_S3C6400) += cpu_init.o speed.o
34 COBJS-$(CONFIG_S3C6410) += cpu_init.o speed.o # add here !
6.   建立SMDK6410頂層配置文件
liqiang@ubuntu:~/work/forbook/u-boot-2013.04-rc1$ cp include/configs/smdk6400.h  include/configs/smdk6410.h
SMDK6400的頂層配置文件smdk6400.h爲藍本建立SMDK6410單板的頂層配置文件smdk6410.h,並進行初步修改。
/*
* High Level Configuration Options
* (easy to change)
*/
#define CONFIG_S3C6400          1       /* in a SAMSUNG S3C6400 SoC     */
#define CONFIG_S3C64XX          1       /* in a SAMSUNG S3C64XX Family  */
#define CONFIG_SMDK6400        1       /* on a SAMSUNG SMDK6400 Board */
將以上內容修改成SMDK6410的配置選項。
/*
* High Level Configuration Options
* (easy to change)
*/
#define CONFIG_S3C6410          1       /* in a SAMSUNG S3C6410 SoC     */
#define CONFIG_S3C64XX          1       /* in a SAMSUNG S3C64XX Family  */
#define CONFIG_SMDK6410         1       /* on a SAMSUNG SMDK6410 Board  */
修改監控命令提示符,提示符能夠根據我的的喜愛修改。
131#define CONFIG_SYS_PROMPT               "SMDK6400 # "   /* Monitor Command Prompt    */
131#define CONFIG_SYS_PROMPT               "lq@u-boot#"   /* Monitor Command Prompt    */
修改識別字符串
196 #define CONFIG_IDENT_STRING     " for SMDK6400"
196 #define CONFIG_IDENT_STRING     " for SMDK6410"
修改DRAM的大小
166 #define PHYS_SDRAM_1_SIZE       0x08000000      /* 128 MB in Bank #1    */
166 #define PHYS_SDRAM_1_SIZE       0x10000000      /* 256 MB in Bank #1    */
7.   其餘修改
進入board/samsung/smdk6410/目錄,該目錄下的文件僅僅與單板相關,直接修改不會影響其它單板的編譯。打開smdk6410.clowlevel_init.S文件。
將這兩個文件中的#include <asm/arch/s3c6400.h>改成#include <asm/arch/s3c6410.h>,全部的宏CONFIG_S3C6400修改成CONFIG_S3C6410
cpu_init.S (arch\arm\cpu\arm1176\s3c64xx):#include <asm/arch/s3c6400.h>
reset.S (arch\arm\cpu\arm1176\s3c64xx):#include <asm/arch/s3c6400.h>
S3c64xx-hcd.c (drivers\usb\host):#include <asm/arch/s3c6400.h>
S3c64xx.c (drivers\mtd\nand):#include <asm/arch/s3c6400.h>
S3c64xx.c (drivers\serial):#include <asm/arch/s3c6400.h>
Speed.c (arch\arm\cpu\arm1176\s3c64xx):#include <asm/arch/s3c6400.h>
Timer.c (arch\arm\cpu\arm1176\s3c64xx):#include <asm/arch/s3c6400.h>
上面這些文件並非SMDK6410單板獨有的文件,若是直接將s3c6400.h改成s3c6410.h,會破壞SMDK6400的源碼結構,而編譯SMDK6410單板時必須包含s3c6410.h頭文件,能夠利用以下預處理命令解決這個問題。
#ifdef CONFIG_S3C6400
#include <asm/arch/s3c6400.h>
#else
#include <asm/arch/s3c6410.h>
#endif
打開arch/arm/cpu/arm1176/s3c64xx/speed.c文件,修改打印的CPU類型。
139 #ifdef CONFIG_S3C6400
140         printf("\nCPU:     S3C6400@%luMHz\n", get_ARMCLK() / 1000000);
141 #else
142         printf("\nCPU:     S3C6410@%luMHz\n", get_ARMCLK() / 1000000);
143 #endif
增長頂層控制宏。進入common.h(include)文件,用一樣的方式修改ohci-hcd.c (drivers/usb/host)
642 #if defined(CONFIG_S3C24X0) || \
643     defined(CONFIG_LH7A40X) || \
644     defined(CONFIG_S3C6400) || \
645     defined(CONFIG_EP93XX)
添加CONFIG_S3C6410宏後修改成:
642 #if defined(CONFIG_S3C24X0) || \
643     defined(CONFIG_LH7A40X) || \
644     defined(CONFIG_S3C6400) || \
645     defined(CONFIG_S3C6410) || \
646     defined(CONFIG_EP93XX)
第二章第四節   編譯U-Boot模板
U-Boot支持將編譯生成的文件與源碼文件分開放置,能夠經過兩種方式指定生成文件的目錄。
1)        在命令行參數添加中添加「O =」。
liqiang@liqiang-virtual-machine:~/work/forbook/u-boot-2013.04-rc1$ make O=build
2)        給環境參數變量BUILD_DIR賦值,這個值就是咱們指望中間文件存放的位置。
liqiang@liqiang-virtual-machine:~/work/forbook/u-boot-2013.04-rc1$ export BUILD_DIR=./build
liqiang@liqiang-virtual-machine:~/work/forbook/u-boot-2013.04-rc1$ make
爲了保持源代碼目錄的乾淨,推薦用以上方式將編譯生成的文件輸出到一個外部目錄。若是沒有指定生成文件的目錄,則默認爲源碼頂層目錄。
liqiang@liqiang-virtual-machine:~/work/forbook/u-boot-2013.04-rc1$ make O=../build smdk6410_config
liqiang@liqiang-virtual-machine:~/work/forbook/u-boot-2013.04-rc1$ make O=../build
輸入命令行,編譯後提示錯誤,打印出如下錯誤信息。
arm-linux-ld:/home/liqiang/work/forbook/build/u-boot.lds:19: syntax error
make: *** [/home/liqiang/work/forbook/build/u-boot] 錯誤 1
事實上,這是源碼出現的第一個bug,u-boot.lds連接腳本的語法有誤。u-boot.lds是在編譯的時候臨時生成的連接腳本,它生成的依據之一是 u-boot-nand.lds 連接文件,該文的位置爲board/samsung/smdk6410。打開u-boot-nand.lds,發現內存4的倍數對齊的描述與書寫有誤,必須大寫。
51         . = align(4);
52         .u_boot_list : {
53                 #include <u-boot.lst>
54         }
55
56         . = align(4);
修改成:
51         . = ALIGN(4);
52         .u_boot_list : {
53                 #include <u-boot.lst>
54         }
55
56         . = ALIGN (4);
       繼續輸入make O=../build編譯,出現錯誤提示信息:
start.o: In function `cpu_init_crit':
/home/liqiang/work/forbook/build/nand_spl/board/samsung/smdk6410/start.S:227: undefined reference to `_main'
make[1]: *** [/home/liqiang/work/forbook/build/nand_spl/u-boot-spl] 錯誤 1
make[1]:正在離開目錄 `/home/liqiang/work/forbook/u-boot-2013.04-rc1/nand_spl/board/samsung/smdk6410'
make: *** [nand_spl] 錯誤 2
事實上,這是源碼出現的第二個bug,修改方法以下:
打開Makefilenand_spl/board/samsung/smdk6410),添加crt0.S(arch/arm/lib/)文件編譯。
40 SOBJS   = start.o cpu_init.o lowlevel_init.o crt0.o
...
69 $(obj)start.S:
70         @rm -f $@
71         @ln -s $(TOPDIR)/arch/arm/cpu/arm1176/start.S $@
72 $(obj)crt0.S:
73         @rm -f $@
74         @ln -s $(TOPDIR)/arch/arm/lib/crt0.S $@
繼續編譯又出現下面的錯誤提示信息:
/home/liqiang/work/forbook/build/nand_spl/board/samsung/smdk6410/crt0.S:153: undefined reference to `coloured_LED_init'
/home/liqiang/work/forbook/build/nand_spl/board/samsung/smdk6410/crt0.S:154: undefined reference to `red_led_on'
make[1]: *** [/home/liqiang/work/forbook/build/nand_spl/u-boot-spl] 錯誤 1
make[1]:正在離開目錄 `/home/liqiang/work/forbook/u-boot-2013.04-rc1/nand_spl/board/samsung/smdk6410'
make: *** [nand_spl] 錯誤 2
這是源碼的第三個bug,打開arch/arm/lib/crt0.S文件,增長條件編譯。
152 #ifndef CONFIG_NAND_SPL
153         bl coloured_LED_init
154         bl red_led_on
155 #endif
       終於編譯過程順利經過,一個簡單的框架搭建完成。用ls命令列出build目錄中的全部文件,內容以下所示。
api      examples  net         u-boot.bin
arch     fs        post        u-boot.lds
board    include   System.map  u-boot.map
commoninclude2  test        u-boot-nand.bin
disk     lib       tools       u-boot.srec
drivers  nand_spl  u-boot
第一個bug是連接腳本語法有誤,很容易理解。第2、三個bug出現的緣由與nand_spl機制有關,將會在後文詳細介紹。儘管目前已經到了U-Boot編譯生成的u-boot.binu-boot-nand.bin二進制文件。單板並無配置能夠直接存儲和運行程序的NOR Flash,到目前爲止咱們依然沒法驗證移植是否成功。爲了加深對U-Boot移植要點的理解,本書移植U-Boot不借助第三方已經移植好的BootLoader燒寫程序,全部驅動程序自行編寫。事實上,要實現把編譯好的代碼在單板上運行試驗必須利用2.3.2小節的SD卡啓動方法。在此以前咱們先分析一下U-Boot的啓動流程,爲後續內容打好基礎。
第二章第五節  U-Boot啓動分析(1)

在BootLoader概述中,咱們已經知道BootLoader的實現依賴於處理器的體系結構,爲了移植的方便,大多數BootLoader能夠分爲兩個階段stage1和stage2。依賴於處理器體系結構的代碼,好比 CPU 初始化,通常都放在 stage1階段,一般多用匯編語言來實現,stage1必須是位置無關碼。stage2一般用C 語言來實現,這樣能夠實現給複雜的功能,並且代碼會具備更好的可讀性和可移植性。U-Boot也不例外,第一階段主要使用匯編語言編寫,程序的入口在start.s中。stage1在運行時,有可能不在其運行地址,這時不能使用靜態變量,必須利用位置無關碼進行編程。
U-Boot在stage1階段常常會出現CONFIG_NAND_SPL和CONFIG_SPL_BUILD兩個宏,用於控制程序的條件編譯。在編譯生成u-boot.bin時,它們爲假。去掉一些可有可無的過程和條件編譯斷定無效的代碼段,咱們經過分析u-boot.bin的生成過程來分析U-Boot的啓動流程。
1.   程序入口
.globl _start
_start: b   reset
.globl  若是一個符號沒有用.globl聲明,就表示這個符號不會被連接器用到。
b是跳轉指令,ARM的跳轉指令能夠從當前指令向前或者向後的32MB的地址空間跳轉(相對跳轉指令),是一種位置無關碼,這類跳轉指令有如下4種:
1)       B     跳轉指令
2)       BL    帶返回的跳轉指令
3)       BX    帶狀態切換的跳轉指令
4)       BLX   帶返回和狀態切換的跳轉指令
_start: b   reset
而這句跳轉時,PC寄存器的值將不會保存到LR寄存器中。
2.   設置ARM工做模式
reset:
    /*
     * set the cpu to SVC32 mode
     */
    mrs r0, cpsr
    bic r0, r0, #0x3f
    orr r0, r0, #0xd3
    msr cpsr, r0
ARM微處理器支持7種運行模式,分別爲:
用戶模式(usr):ARM處理器正常的程序執行狀態。
快速中斷模式(fiq):用於高速數據傳輸或通道處理。
外部中斷模式(irq):用於通用的中斷處理。
管理模式(svc):操做系統使用的保護模式。
數據訪問終止模式(abt):當數據或指令預取終止時進入該模式,可用於虛擬存儲及存儲保護。
系統模式(sys):運行具備特權的操做系統任務。
定義指令停止模式(und):當未定義的指令執行時進入該模式,可用於支持硬件協處理器的軟件仿真。
cpsr爲當前程序狀態寄存器,它包含了條件標誌位、中斷禁止位、當前處理器模式標誌以及其餘的一些控制和狀態位。cpsr能夠在任何處理器模式下被訪問。
N、Z、C、V這四位統稱爲條件標誌位。
cpsr的第八位統稱爲控制位。
返回上面代碼分析,mrs指令是讀狀態寄存器指令,以下所示:
mrs r0, cpsr
這行代碼的含義是將cpsr狀態寄存器讀取,保存到r0中。
bic指令是位清除指令,以下所示:
bic r0, r0, #0x3f
這行代碼的做用是將r0的低六位清零。
orr指令是或運算,以下所示:
    orr r0, r0, #0xd3
將r0與1101 0011進行或運算,因爲以前進行了位清零,那麼此時r0的低八位爲:1101 0011。
msr指令是寫狀態寄存器指令,以下所示:
    msr cpsr, r0
將r0數據寫入cpsr程序狀態寄存器。一樣cpsr的低八位即爲:1101 0011。那麼這八位的含義以下。
1)       第七位,即爲I位,當I=1時禁止IRQ中斷。
2)       第六位,即爲F位,當F=1時禁止FIQ中斷。
3)       第五位,即爲T位,當T=1時執行ARM指令;當T=0時執行Thumb指令。
4)       低五位,即爲M[4:0],當M[4:0] = 0x13,即爲管理模式。
接着進入cpu_init_crit,即cpu初始化階段。
3.   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 */
mov指令可完成從另外一個寄存器、被移位的寄存器或將一個當即數加載到目的寄存器。
mov r0, #0
這行代碼即表示將0這個當即數加載到r0寄存器中。
mcr指令將ARM處理器的寄存器中的數據傳遞到協處理器的寄存器中。若是協處理器不能成功地執行該操做,將產生未定義的指令異常中斷。
mcr p15, 0, r0, c7, c7, 0
指令從ARM寄存器中將數據傳送到協處理器p15的寄存器中,其中r0爲ARM寄存器,存放源操做數;c7和c7爲協處理器寄存器,爲目標寄存器;p15和r0之間的0爲操做碼1;最後0爲操做碼2。
上面這行代碼的做用是向c7寫入0將使ICache與DCache無效。
mcr p15, 0, r0, c8, c7, 0
而這行代碼的做用是向c8寫入0將使TLB失效。
4.   MMU初始化
    mrc p15, 0, r0, c1, c0, 0
    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
······
mmu_disable:
    mcr p15, 0, r0, c1, c0, 0
mrc指令將協處理器寄存器中的數值傳送到ARM處理器的寄存器中。若是協處理器不能成功地執行該操做,將產生未定義的指令異常中斷。
mrc p15, 0, r0, c1, c0, 0
指令將協處理器p15寄存器中的數據傳送到ARM寄存器中。其中,r0爲ARM寄存器,是目標寄存器;c1和c0爲協處理器寄存器,存放源操做數;p15和r0之間的0是操做碼1;最後0是操做碼2。
V :  表示異常向量表所在的位置,0:異常向量在0x00000000;1:異常向量在 0xFFFF0000。
I :  0 :關閉Icaches;1 :開啓Icaches。
R、S : 用來與頁表中的描述符一塊兒肯定內存的訪問權限。
B :  0 :CPU爲小字節序;1 : CPU爲大字節序。
C :  0:關閉DCaches;1:開啓Dcaches。
A :  0:數據訪問時不進行地址對齊檢查;1:數據訪問時進行地址對齊檢查。
M :  0:關閉MMU;1:開啓MMU。
到這裏,再逐句代碼分析。
bic r0, r0, #0x00002300
2300即爲0010 0011 0000 0000,便是將r0的第1三、九、8位清零。
bic r0, r0, #0x00000087
0087即爲0000 0000 1000 0111,便是將r0的第七、二、一、0位清零。
orr r0, r0, #0x00000002
5.   外設的基地址初始化
#ifdef CONFIG_PERIPORT_REMAP
    /* Peri port setup */
    ldr r0, =CONFIG_PERIPORT_BASE
    orr r0, r0, #CONFIG_PERIPORT_SIZE
     mcr p15,0,r0,c15,c2,4
#endif
       arm11把內存(memory)區間和外設(peripheral)區間地址分開,在CPU初始化的時候,須要經過協處理器指令CP15告訴CPU外設寄存器的地址範圍。若是沒有這樣作,CPU默認爲內存訪問,也就沒法訪問到外設區間的寄存器。
 
第二章第六節  U-Boot-2013.04啓動分析2
接下來是執行帶返回跳轉指令「bl lowlevel_init」。調用 lowlevel_init 函數(位於board\samsung\smdk6410\lowlevel_init.s)。lowlevel_init函數的工做是進行與單板相關的初始化工做,故名思議,這個初始化僅僅是最低限度(lowlevel)的,包括led燈配置(便於觀察現象)、關閉看門狗、設置中斷、配置系統時鐘、初始化串口、初始化內存和初始化喚醒復位。
1)      配置led
    ldr r0, =ELFIN_GPIO_BASE
    ldr r1, =0x55540000
    str r1, [r0, #GPNCON_OFFSET]
 
    ldr r1, =0x55555555
    str r1, [r0, #GPNPUD_OFFSET]
 
    ldr r1, =0xf000
    str r1, [r0, #GPNDAT_OFFSET]
這裏應該改爲與s3c6410相適應的配置,單板使用GPM0-GPM3管腳驅動led。根據s3c6410用戶手冊中的端口M控制寄存器章節能夠對程序做出以下修改。
    /* LED on only #8 */
    ldr r0, =ELFIN_GPIO_BASE
    ldr r1, =0x00111111
    str r1, [r0, #GPMCON_OFFSET]
 
    ldr r1, =0x00000555
    str r1, [r0, #GPMPUD_OFFSET]
     /* all of LEDs are power on */
    ldr r1, =0x000f
    str r1, [r0, #GPMDAT_OFFSET]
根據須要,LED測試自行修改:
    /* LED test */
    ldr r0, =ELFIN_GPIO_BASE
    ldr r1, =0x0003
    str r1, [r0, #GPMDAT_OFFSET]
2)      關閉看門狗
    ldr r0, =0x7e000000     @0x7e004000
    orr r0, r0, #0x4000
    mov r1, #0
    str r1, [r0]
大多數微處理器都帶有看門狗,當看門狗沒有被定時清零(喂狗)時,將引發復位,這可防止程序跑飛,也能夠防止程序運行時候出現死循環。設計者必須清楚看門狗的溢出時間以決定在合適的時候清除看門狗。在內核中一般用於防止出現死循環,U-Boot直接關閉看門狗。
3)      設置中斷
/* External interrupt pending clear */
    ldr r0, =(ELFIN_GPIO_BASE+EINTPEND_OFFSET)  /*EINTPEND*/
    ldr r1, [r0]
    str r1, [r0]
 
    ldr r0, =ELFIN_VIC0_BASE_ADDR   @0x71200000
    ldr r1, =ELFIN_VIC1_BASE_ADDR   @0x71300000
 
    /* Disable all interrupts (VIC0 and VIC1) */
    mvn r3, #0x0
    str r3, [r0, #oINTMSK]
    str r3, [r1, #oINTMSK]
 
    /* Set all interrupts as IRQ */
    mov r3, #0x0
    str r3, [r0, #oINTMOD]
    str r3, [r1, #oINTMOD]
 
    /* Pending Interrupt Clear */
    mov r3, #0x0
    str r3, [r0, #oVECTADDR]
    str r3, [r1, #oVECTADDR]
4)      配置系統時鐘
S3C6410有三個PLL(鎖相環),分別爲APLL、MPLL和EPLL。其中APLL產生ACLK,給CPU使用,MPLL產生HCLKX二、HCLK和PCLK,HCLKX2主要提供時鐘給DDR使用,最大能夠到266MHz。HCLK用做AXI\AHB總線時鐘,APB用做APB總線時鐘。接AXI和AHB總線的外設最大時鐘爲133MHz,接APB總線的外設最大時鐘爲66MHz。UART的時鐘能夠由MPLL或者EPLL提供。
系統時鐘初始化起始於:
system_clock_init:
    ldr r0, =ELFIN_CLOCK_POWER_BASE /* 0x7e00f000 */
S3C6400的時鐘系統與S3C6410有所差別,其中將
/* FOUT of EPLL is 96MHz */
    ldr r1, =0x200203
修改爲:
   ldr  r1, =0x80200203
5)      串口初始化
uart_asm_init:
    /* set GPIO to enable UART */
    ldr r0, =ELFIN_GPIO_BASE
    ldr r1, =0x220022
    str r1, [r0, #GPACON_OFFSET]
    mov pc, lr
6)      NAND Flash控制器初始化
nand_asm_init:
    ldr r0, =ELFIN_NAND_BASE
    ldr r1, [r0, #NFCONF_OFFSET]
    orr r1, r1, #0x70
    orr r1, r1, #0x7700
    str r1, [r0, #NFCONF_OFFSET]
 
    ldr r1, [r0, #NFCONT_OFFSET]
    orr r1, r1, #0x07
    str r1, [r0, #NFCONT_OFFSET]
 
    mov pc, lr
       簡單地對NAND Flash主機控制器的時間參數初始化。
7)      內存初始化
2.  3內存初始化流程
調用mem_ctrl_asm_init函數,跳入到arch/arm/cpu/arm1176/s3c64xx/ mem_ctrl_asm_init.s中。系統上電,在利用內存控制器訪問外部內存以前,須要進行一系列初始化工做,如圖2. 3。主要作兩件事情:配置內存控制器和初始化外部內存設備。配置內存控制器包括時間參數、位寬、片選和ID配置等。初始化外部內存設備,經過操做P1DIRECTCMD寄存器,發出初始化系列:「nop」命令,Prechargeall命令,Autorefresh命令,Autorefresh命令,EMRS命令,MRS命令。
S3C6410DRAM控制器是基於 ARM PrimeCell CP003 AXI DMC(PL340)S3C6410的存儲器端口0並不支持DRAM,因此只能選用存儲器端口1DMC1)。S3C6410DMC1基址ELFIN_DMC1_BASE的值爲0x7e00_1000。當DMC1使用32位數據線DRAM時,須要配置MEM_SYS_CFG寄存器,將芯片管腳Xm1DATA[31:16]設置爲DMC1的數據域。單板利用兩塊64M×16DDR SDRAM芯片K4X1G163PC組合成一塊大小爲64M×32的芯片,此時,MEM_SYS_CFG[7]必須清零。
DDR時間參數根據K4X1G163PC手冊獲得,並定義在s3c6410.h頭文件中,利用宏NS_TO_CLK(t)將時間參數轉化成時鐘週期,再寫入相應的寄存器中。一塊K4X1G163PC行地址爲A0 - A13,列地址爲A0 - A9BANK地址爲B0-B1。尋址範圍爲128Mb。特別注意的是,片選寄存器DMC1_CHIP0_CFG的值:P1_chip_0_cfg[16] = 1,選擇Bank-Row-Column組織結構。地址匹配值爲0x50,地址屏蔽位0xF0,屏蔽了總線的高八位。所以尋址範圍0x5xxxx_xxxx(0x5000_0000~0x5ff_ffff 256 MiB)
8)      喚醒復位初始化
/* Wakeup support. Don't know if it's going to be used, untested. */
    ldr r0, =(ELFIN_CLOCK_POWER_BASE + RST_STAT_OFFSET)
    ldr r1, [r0]
    bic r1, r1, #0xfffffff7
    cmp r1, #0x8
    beq wakeup_reset
 
第二章第七節  U-Boot-2013.04啓動分析3
1.   調用_main函數
_main函數的實現代碼位於arch/arm/lib/crt0.S文件中,用於創建C語言運行環境。crt0.S文件存放在arm處理器的lib庫目錄下,從文件的存放位置咱們能夠知道:_main函數和CPU的構架有關,而與單板的配置無關,即它支持全部的arm單板。編譯生成u-boot.bin二進制文件時,用於條件編譯的CONFIG_NAND_SPLCONFIG_SPL_BUILD宏爲假。_main函數是stage1stage2的過渡,它是一個彙編函數,但成分比較複雜:_main函數屢次調用C語言代碼,例如board_init_f board_init_r 等,彙編函數,如重定位函數relocate_codeboard_init_f函數和board_init_r函數的實現代碼均在arch/arm/lib/board.c文件中,由C語言編寫。
1)        聲明外部變量
.globl board_init_r
.globl __bss_start
.globl __bss_end__
聲明外部函數board_init_r,外部變量__bss_start__bss_end__
2)        爲調用board_init_f函數創建運行環境
.global _main
_main:
    ldr sp, =( CONFIG_SYS_INIT_SP_ADDR )
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    sub sp, # GD_SIZE     /* allocate one GD above SP */
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    mov r8, sp      /* GD is above SP */
    mov r0, #0
 
2.  4創建運行環境

如圖2. 4創建運行環境包括初始化堆棧指針sp和預留一個內存空間存儲gd_t類型的數據結GD,gd指向這個結構體的首地址。gd_t是關鍵字typedef爲global data數據結構定義的新名字,定義的原型位於文件 include/asm-generic/global_data.h。其成員主要是系統初始化的參數。
程序清單2. 1global_data結構
typedef struct global_data {
    bd_t *bd;
    unsigned long flags;
    unsigned long baudrate;
    unsigned long cpu_clk;  /* CPU clock in Hz!     */
    unsigned long bus_clk;
    /* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
    unsigned long pci_clk;
    unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
    unsigned long fb_base;  /* Base address of framebuffer mem */
#endif
#if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER)
    unsigned long post_log_word;  /* Record POST activities */
    unsigned long post_log_res; /* success of POST test */
    unsigned long post_init_f_time;  /* When post_init_f started */
#endif
#ifdef CONFIG_BOARD_TYPES
    unsigned long board_type;
#endif
    unsigned long have_console; /* serial_init() was called */
#ifdef CONFIG_PRE_CONSOLE_BUFFER
    unsigned long precon_buf_idx;   /* Pre-Console buffer index */
#endif
#ifdef CONFIG_MODEM_SUPPORT
    unsigned long do_mdm_init;
    unsigned long be_quiet;
#endif
    unsigned long env_addr; /* Address  of Environment struct */
    unsigned long env_valid;    /* Checksum of Environment valid? */
 
    /* TODO: is this the same as relocaddr, or something else? */
    unsigned long dest_addr;    /* Post-relocation address of U-Boot */
    unsigned long dest_addr_sp;
    unsigned long ram_top;  /* Top address of RAM used by U-Boot */
 
    unsigned long relocaddr;    /* Start address of U-Boot in RAM */
    phys_size_t ram_size;   /* RAM size */
    unsigned long mon_len;  /* monitor len */
    unsigned long irq_sp;       /* irq stack pointer */
    unsigned long start_addr_sp;    /* start_addr_stackpointer */
    unsigned long reloc_off;
    struct global_data *new_gd; /* relocated global data */
    const void *fdt_blob;   /* Our device tree, NULL if none */
    void **jt;      /* jump table */
    char env_buf[32];   /* buffer for getenv() before reloc. */
    struct arch_global_data arch;   /* architecture-specific data */
} gd_t;
在一個源碼文件中,訪問gd結構體前需用宏定義DECLARE_GLOBAL_DATA_PTR進行聲明,這個宏定義在文件arch/arm/include/asm/global_data.h。
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd  asm ("r8")
register是C語言中的一個關鍵字,除了一些特殊的場合,如要求變量高速地被調用,它通常不多被使用。若是一個變量被register修飾,就意味着該變量是一個寄存器變量,變量的值存放在寄存器中。固然,這裏的寄存器指的是CPU的內核寄存器,它獨立於內存沒有地址,因此沒法對寄存器變量進行取地址運算。DECLARE_GLOBAL_DATA_PTR定義了一個gd_t結構體指針變量gd, asm ("r8") 指定了gd值的存放位置r8。volatile是爲了防止變量被編譯器優化,要求每次都要去從新讀取變量的值。事實上,U-Boot中的這段代碼存在必定的缺陷。
在文件include/configs/sdmk6410.h中,CONFIG_SYS_INIT_SP_ADDR的計算過程以下:
#define CONFIG_SYS_IRAM_BASE    0x0c000000  /* Internal SRAM base address */
#define CONFIG_SYS_IRAM_SIZE    0x2000      /* 8 KB of internal SRAM memory */
#define CONFIG_SYS_IRAM_END     (CONFIG_SYS_IRAM_BASE + CONFIG_SYS_IRAM_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR (CONFIG_SYS_IRAM_END - GENERATED_GBL_DATA_SIZE)
其中,GENERATED_GBL_DATA_SIZE在編譯時,會自動生成
build/include/generated/generic-asm-offsets.h
#define GENERATED_GBL_DATA_SIZE (160) /* (sizeof(struct global_data) + 15) & ~15 */
由註釋可知,宏定義CONFIG_SYS_INIT_SP_ADDR已經爲gd在SRAM的頂部預留了160字節的空間,所以不必再將sp指針下調。固然,這樣作也並不會影響正常的啓動流程,可是偏離了設計者的本意,咱們只須要在crt0.S文件中,將下面部分代碼段註釋掉便可。
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
    sub sp, #GD_SIZE    /* allocate one GD above SP */
 
第二章第八節  U-Boot-2013.04啓動分析(4)
1)        調用 board_init_f 函數
    bl  board_init_f
實際上,board_init_f()函數是U-Boot執行的第一個C語言函數:void board_init_f(ulong bootflag),這個函數位於arch/arm/lib目錄下的board.c文件中。
void board_init_f()函數的主要工做是:清空gd指向的結構體、逐步填充結構體,執行init_fnc_ptr函數指針數組中的各個初始化函數和劃份內存區域等。結構體成員的初始化貫穿於board_init_f函數的整個過程,多數狀況下成員的值是根據頂層配置文件的宏肯定的。
    /* Pointer is writable since we allocated a register for it */
    gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
gd_t是一個結構體類型,其定義在arch/arm/include/asm目錄下的global_data.h文件中,前面已經詳細分析過。
memset((void *)gd, 0, sizeof(gd_t));
將gd所指向的結構體內全部變量清零,長度爲:sizeof(gd_t)。清空以後,在board_init_f()函中後面有不少代碼是對gd所指向的結構體的成員進行從新復賦值。
    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
        if ((*init_fnc_ptr)() != 0) {
            hang ();
        }
    }
在U-Boot中定義了一個init_sequence函數指針數組:
init_fnc_t * init_sequence [] = {
    arch_cpu_init,      /* basic arch cpu dependent setup */
    mark_bootstage,
#ifdef CONFIG_OF_CONTROL
    fdtdec_check_fdt,
#endif
#if defined(CONFIG_BOARD_EARLY_INIT_F)
    board_early_init_f,
#endif
    timer_init,     /* initialize timer */
#ifdef CONFIG_BOARD_POSTCLK_INIT
    board_postclk_init,
#endif
#ifdef CONFIG_FSL_ESDHC
    get_clocks,
#endif
    env_init,       /* initialize environment */
    init_baudrate,      /* initialze baudrate settings */
    serial_init,        /* serial communications setup */
    console_init_f,     /* stage 1 init of console */
    display_banner,     /* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
    print_cpuinfo,      /* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
    checkboard,     /* display board info */
#endif
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
    init_func_i2c,
#endif
    dram_init,      /* configure available RAM banks */
    NULL,
};
函數的類型爲init_fnc_t,init_fnc_t也是一個新定義的數據類型,這個數據類型是傳入參數爲空,返回值爲有符號整形的函數,函數用於初始化工做。以下:
typedef int (init_fnc_t) (void);
board_init_f 函數使用一個for循環語句來逐一執行數組中的初始化函數,若是初始化函數返回值不爲0,程序調用hang函數掛起,再也不繼續往下運行。
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
這行代碼告訴咱們SDRAM的末位物理地址爲0x5800 0000,即SDRAM的空間分佈爲0x5000 0000~0x57FF FFFF。說明SDRAM一共128MB的空間。
接下來的代碼程序就是對這128M內存進行劃分。
#ifdef CONFIG_PRAM
    /*
     * reserve protected RAM
     */
    reg = getenv_ulong("pram", 10, CONFIG_PRAM);
    addr -= (reg << 10);        /* size is in kB */
    debug("Reserving %ldk for protected RAM at %08lx\n", reg, addr);
#endif /* CONFIG_PRAM */
 
#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
    /* reserve TLB table */
    addr -= (4096 * 4);
    /* round down to next 64 kB limit */
    addr &= ~(0x10000 - 1);
    gd->tlb_addr = addr;
    debug("TLB table at: %08lx\n", addr);
#endif
 
    /* round down to next 4 kB limit */
    addr &= ~(4096 - 1);
    debug("Top of RAM usable for U-Boot at: %08lx\n", addr);
這裏告訴咱們是將SDRAM的最後64Kaddr &= ~(0x10000 - 1))分配給TLB,所分配的地址爲:0x57FF 0000~0x57FF FFFF
#ifdef CONFIG_LCD
#ifdef CONFIG_FB_ADDR
    gd->fb_base = CONFIG_FB_ADDR;
#else
    /* reserve memory for LCD display (always full pages) */
    addr = lcd_setmem(addr);
    gd->fb_base = addr;
#endif /* CONFIG_FB_ADDR */
#endif /* CONFIG_LCD */
 
    /*
     * reserve memory for U-Boot code, data & bss
     * round down to next 4 kB limit
     */
    addr -= gd->mon_len;
    addr &= ~(4096 - 1);
 
    debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr);
這段代碼是在SDRAM中從後往前給u-boot分配BSS、數據段、代碼段,分配地址爲:0x57F7 5000~0x57FE FFFF
/*
     * reserve memory for malloc() arena
     */
    addr_sp = addr - TOTAL_MALLOC_LEN;
    debug("Reserving %dk for malloc() at: %08lx\n",
            TOTAL_MALLOC_LEN >> 10, addr_sp);
從後往前緊挨着代碼段開闢一塊了malloc空間,給予的地址爲:0x57E6 D000~0x57E7 4FFF
    /*
     * (permanently) allocate a Board Info struct
     * and a permanent copy of the "global" data
     */
    addr_sp -= sizeof (bd_t);
    bd = (bd_t *) addr_sp;
    gd->bd = bd;
    debug("Reserving %zu Bytes for Board Info at: %08lx\n",
            sizeof (bd_t), addr_sp);
bd結構體分配空間,地址爲:0x57E6 CFD8~0x57E6 CFFF
    addr_sp -= sizeof (gd_t);
    id = (gd_t *) addr_sp;
    debug("Reserving %zu Bytes for Global Data at: %08lx\n",
            sizeof (gd_t), addr_sp);
這是給gd結構體分配空間,地址爲:0x57E6 CF60~0x57E6 CFD7
    /* setup stackpointer for exeptions */
    gd->irq_sp = addr_sp;
#ifdef CONFIG_USE_IRQ
    addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
    debug("Reserving %zu Bytes for IRQ stack at: %08lx\n",
        CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ, addr_sp);
#endif
    /* leave 3 words for abort-stack    */
    addr_sp -= 12;
 
    /* 8-byte alignment for ABI compliance */
    addr_sp &= ~0x07;
#else
    addr_sp += 128; /* leave 32 words for abort-stack   */
    gd->irq_sp = addr_sp;
#endif
 
    debug("New Stack Pointer is: %08lx\n", addr_sp);
分配異常中斷空間,地址:0x57E6 CF50~0x57E6 CF5F
1.  16  SDRAM內存劃分圖
綜合上面SDRAM的分配,那麼內存分配即爲如圖1. 16所示。SDRAM的空間大小爲128MB
其中在smdk6410.h中,有這麼一個宏定義:
#define CONFIG_SYS_SDRAM_BASE   0x50000000
這說明SDRAM的起始地址是:0x5000 0000
完成gd結構體的初始化和內存的劃分以後,執行board_init_f()函數的最後一行代碼:
relocate_code(addr_sp, id, addr);
這行代碼的意思很明顯是要跳回到start.S中,跳回start.S中緊接着的是下面這一段代碼。
    .globl  relocate_code
relocate_code:
    mov r4, r0  /* save addr_sp */
    mov r5, r1  /* save addr of gd */
    mov r6, r2  /* save addr of destination */
relocate_code()說帶回來的三個參數分別裝入r4r5r6寄存器中。
可是注意到,relocate_code這個函數的聲明實在commom.h中,以下:
void    relocate_code (ulong, gd_t *, ulong) __attribute__ ((noreturn));
relocate_code函數的三個參數分別棧頂地址、數據ID(即全局結構gd)在SDRAM中的起始地址和在SDRAM中存儲U-Boot的起始地址。
start.S中所接着進行的是設置堆棧指針,以下:
/* Set up the stack                         */
stack_setup:
    mov sp, r4
 
    adr r0, _start
    cmp r0, r6
    moveq   r9, #0      /* no relocation. relocation offset(r9) = 0 */
    beq clear_bss       /* skip relocation */
    mov r1, r6          /* r1 <- scratch for copy_loop */
    ldr r3, _bss_start_ofs
    add r2, r0, r3      /* r2 <- source end address    */
 
copy_loop:
    ldmia   r0!, {r9-r10}       /* copy from source address [r0]    */
    stmia   r1!, {r9-r10}       /* copy to   target address [r1]    */
    cmp r0, r2          /* until source end address [r2]    */
    blo copy_loop
r4是剛剛傳回來的堆棧指針,那麼將r4給sp,設定堆棧指針。
r6是在SDRAM中存儲u-boot的起始地址,將r0和r6進行比較。若是此時的u-boot已是在SDRAM中,則beq         clear_bss;若是不是,在NandFlash中,則要將u-boot複製到SDRAM中。
clear_bss:
#ifndef CONFIG_SPL_BUILD
    ldr r0, _bss_start_ofs
    ldr r1, _bss_end_ofs
    mov r4, r6          /* reloc addr */
    add r0, r0, r4
    add r1, r1, r4
    mov r2, #0x00000000     /* clear                */
 
clbss_l:cmp r0, r1          /* clear loop... */
    bhs clbss_e         /* if reached end of bss, exit */
    str r2, [r0]
    add r0, r0, #4
    b   clbss_l
clbss_e:
#ifndef CONFIG_NAND_SPL
    bl coloured_LED_init
    bl red_led_on
#endif
#endif
上面這段代碼是對BSS進行清零操做。
/*
* We are done. Do not return, instead branch to second part of board
* initialization, now running from RAM.
*/
#ifdef CONFIG_NAND_SPL
    ldr     pc, _nand_boot
 
_nand_boot: .word nand_boot
#else
    ldr r0, _board_init_r_ofs
    adr r1, _start
    add lr, r0, r1
    add     lr, lr, r9
    /* setup parameters for board_init_r */
    mov r0, r5      /* gd_t */
    mov r1, r6      /* dest_addr */
    /* jump to it ... */
    mov pc, lr
 
_board_init_r_ofs:
    .word board_init_r - _start
#endif
上面這段代碼若是是NAND 啓動的話,那麼就設置SP後跳到nand_boot()函數裏面進行復制代碼到SDRAM,而後跳到U-BootSDRAM 的起始地址開始運行。可是因爲CONFIG_NAND_SPL沒有宏定義,則是執行else。在進入board_init_r以前,給了兩個參數:r5r6
r5:數據ID(即全局結構gd)在SDRAM中的起始地址。
r6:在SDRAM中存儲U-Boot的起始地址。
第二章第九節  U-Boot-2013.04啓動分析(5)
board_init_r()函數一樣是位於arch/arm/lib目錄下的board.c文件中,而且是緊跟在board_init_f()函數後面。
board_init_r()函數的主要操做是:給標誌位賦值、清空malloc空間、初始化NAND Flash、初始化外設(I2CLCDVIDEOKEYBOARDUSBJTAG等)、跳轉表初始化、中斷初始化和中斷使能等。這裏但願讀者能舉一反三,完成這個函數的分析。
完成以前的操做,board_init_r()函數進入一個for循環,以下所示:
    /* main_loop() can return to retry autoboot, if so just run it again. */
    for (;;) {
        main_loop();
    }
main_loop()函數位於/commom目錄下的main.c文件中。以下:
void main_loop (void)
main_loop()函數既無入口參數也無返回值。Main_loop()函數的主要實現做用是:
1)       HUSH的相關初始化
#ifndef CONFIG_SYS_HUSH_PARSER
    static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
    int len;
    int rc = 1;
    int flag;
#endif
……
#ifdef CONFIG_SYS_HUSH_PARSER
    u_boot_hush_start ();
#endif
 
#if defined(CONFIG_HUSH_INIT_VAR)
    hush_init_var ();
#endif
2)       bootdelay的初始化
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    char *s;
    int bootdelay;
#endif
3)       啓動次數
#ifdef CONFIG_BOOTCOUNT_LIMIT
    bootcount = bootcount_load();
上面這行代碼的做用是加載保存的啓動次數。
    bootcount++;
啓動次數加1
    bootcount_store(bootcount);
更新啓動次數。
    sprintf (bcs_set, "%lu", bootcount);
將啓動次數經過串口輸出。
    setenv ("bootcount", bcs_set);
    bcs = getenv ("bootlimit");
    bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
#endif /* CONFIG_BOOTCOUNT_LIMIT */
 
這段代碼蘊含的東西較多。啓動次數限制功能,啓動次數限制能夠被用戶設置一個啓動次數,而後保存在Flash存儲器的特定位置,當到達啓動次數後,U-Boot沒法啓動。該功能適合一些商業產品,經過配置不一樣的License限制用戶從新啓動系統。
4)       Modem功能
#ifdef CONFIG_MODEM_SUPPORT
    debug ("DEBUG: main_loop:   do_mdm_init=%d\n", do_mdm_init);
    if (do_mdm_init) {
        char *str = strdup(getenv("mdm_cmd"));
        setenv ("preboot", str);  /* set or delete definition */
        if (str != NULL)
            free (str);
        mdm_init(); /* wait for modem connection */
    }
#endif  /* CONFIG_MODEM_SUPPORT */
 
若是系統中有Modem功能,打開其功能能夠接受其餘用戶經過電話網絡的撥號請求。Modem功能一般供一些遠程控制的系統使用
5)       設置U-Boot版本號
#ifdef CONFIG_VERSION_VARIABLE
    {
        setenv ("ver", version_string);  /* set version variable */
    }
#endif /* CONFIG_VERSION_VARIABLE */
 
打開動態版本支持功能後,u-boot在啓動的時候會顯示最新的版本號。
6)       啓動tftp功能
#if defined(CONFIG_UPDATE_TFTP)                    
    update_tftp (0UL);
#endif /* CONFIG_UPDATE_TFTP */
 
7)       打印啓動菜單
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    s = getenv ("bootdelay");
    bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
 
    debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
 
在進入主循環以前,若是配置了啓動延遲功能,須要等待用戶從串口或者網絡接口輸入。若是用戶按下任意鍵打斷,啓動流程,會向終端打印出一個啓動菜單。
#if defined(CONFIG_MENU_SHOW)
    bootdelay = menu_show(bootdelay);
#endif
 
向終端打印出一個啓動菜單。
# ifdef CONFIG_BOOT_RETRY_TIME
    init_cmd_timeout ();
# endif /* CONFIG_BOOT_RETRY_TIME */
 
初始化命令行超時機制。
#ifdef CONFIG_POST
    if (gd->flags & GD_FLG_POSTFAIL) {
        s = getenv("failbootcmd");
    }
    else
#endif /* CONFIG_POST */
#ifdef CONFIG_BOOTCOUNT_LIMIT
    if (bootlimit && (bootcount > bootlimit)) {
        printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
                (unsigned)bootlimit);
檢測是否超出啓動次數限制。
        s = getenv ("altbootcmd");
    }
    else
#endif /* CONFIG_BOOTCOUNT_LIMIT */
        s = getenv ("bootcmd");
 
獲取啓動命令參數。
main_loop()的主要做用便是U-Boot啓動管理。
[url=]到此爲止,我相信讀者應該對U-Boot的啓動原理有了大體的瞭解,在分析啓動原理,筆者但願讀者要有「刨根問底」的精神,不弄明白誓不罷休。
 
第二章第十節    IROM 啓動的概念
S3C6410由三星公司生產的ARM11應用處理器芯片,普遍用於移動電話和通用應用。市場上,不少公司紛紛推出本身的S3C6410學習開發板,風靡一時。處理器片內沒有供用戶存儲數據的Flash,用戶必須外接存儲器存儲數據。由表2. 1可知開發板惟一帶有的存儲介質是NAND Flash,若是不通過特殊方式,沒法直接將U-Boot鏡像文件燒寫到裏面。因爲公司間的競爭關係,防止競爭對手的抄襲,不少開發板相關的代碼並不開源。以本節涉及的內容爲例,被開發板生產商譽爲核心技術,商業機密。這對於以研究學習爲目的的購買者來講,無疑是巨大的阻礙。本節內容充分結合S3C6410支持SD卡啓動的特性,全面闡述利用SD卡燒寫、運行嵌入式系統的原理。
 
    在生活中,若是不合理操做計算機,計算機常常會出現沒法從硬盤中啓動的狀況。這時候能夠經過設置BIOS選擇從其它盤啓動,好比啓動CD、U盤等。在使用它們啓動系統以前,必須將其製做成啓動盤,把一個精簡的操做系統寫入其中。電腦啓動時就會識別啓動盤,加載存儲設備特定扇區的數據至內存,從而啓動系統,進行一些修復工做。
   
一樣,如圖2. 5所示,S3C6410有多種啓動模式,分別由XSELNAND,OM[4:O]管腳控制。把OM[4:1]管腳外部電平設置爲爲llll時,選擇IROM啓動。GPN[15:13]管腳的電平狀態用來選擇IROM啓動時的外部存儲設備,如SD/MMC(CH0和CH1)、OneNAND和NAND(數據大小不一樣的頁)。
三星公司在生產S3C6410芯片時,在地址爲0x8000_0000的IROM 區域固化了一段大小爲32KB的代碼,稱做BL0。處理器上電後,PC指向運行0x8000_0000,運行BL0,這種啓動方式稱做IROM啓動。啓動的大致流程以下:
1)        運行BL0進行一些初始化工做,如關閉看門狗,初始化TCM、系統時鐘、堆棧等
2)        而後根據GPN[15:13]管腳的電平狀態,判斷選定的存儲設備的類型,初始化存儲設備和它對應的控制器。從存儲設備(SD/MMC/OneNand/Nand)的特定區域讀取8KB的程序到SteppingStone中運行,被拷貝的這段代碼稱Bootloader1(BL1)
3)        BL1是用戶自行編寫的代碼,必須簡短精悍,運行與位置無關。BL1通常簡單地從新初始化系統,開闢更廣闊的內存空間,並將更加完善的Bootloader2(BL2)拷貝到SDRAM中。
4)        
類型
地址
用途
大小
IRAM
0x0C00_0000-0x0C00_1FFF
Stepping Stone (BL1)
8K
D-TCM0
0x0C00_2000-0x0C00_21FF 0x0C00_2200-0x0C00_2FFF 0x0C00_3000-0x0C00_3FFF
密鑰(512B) 保留(3.5k) 堆區,保存全局變量(4K)
8K
D-TCM1
0x0C00_4000-0x0C00_4018 0x0C00_4019-0x0C00_5FFF
存儲設備拷貝函數指針(24B) 棧區
8K
2. 2IROM啓動內存映射地址

跳轉到SDRAM中的BL2,繼續運行,BL2功能更增強大,把存儲設備中的內核和文件系統加載到SDRAM中,從而啓動系統。
 
 
 
 
 
 
 
 
 
S3C6410在0x0C00_0000至0x0C005FFF的地址空間內定義了三類內存區域,IRAM、D-TCM0和D-TCM1。IRAM用於加載運行BL1。當選定SD/MMC做爲IROM啓動的存儲設備時,D-TCM0保存了SD/MMC設備被IROM代碼檢測到的一些信息,如當前使用的SD/MMC控制器的基地址、SD/MMC卡的類別、設備的扇區總數等。它們被定義爲三個全局變量存放,其中扇區總數的存放地址爲0x0C00_3FFC。
2.  3設備拷貝函數
函數指針地址
函數參數及返回值
描述
0x0C00_4000
int NF8_ReadPage(uint32 blcok, uint32 page, uint8 *buffer) blcok:塊起始地址 page:須要拷貝的頁數 buffer:目標地址 返回值:失敗         1 成功
支持512字節每頁 8位硬件ECC校驗
0x0C00_4004
int NF8_ReadPage_Adv(uint32 blcok, uint32 page, uint8 *buffer) blcok:塊起始地址 page:須要拷貝的頁數 buffer:目標地址 返回值:失敗         1 成功
支持2K每頁 支持4K每頁 8位硬件ECC校驗
0x0C00_4008
bool CopyMMCtoMem(int channel, uint32StartBlkAddress uint16 blockSize, uint32*memoryPtr, bool with_init) channel:無效,取決於GPN15, GPN14 and GPN13管腳 StartBlkAddress:扇區起始地址 blockSize:須要拷貝的扇區數 memoryPtr:目標地址 with_init: :是否須要從新初始化 返回值:失敗         1 成功
支持SD/MMC 支持SDHC
0x0C00_400C
boolONENAND_ReadPage(uint32 Controller,uint32 uBlkAddr , uint8 uPageAddr, uint32* aData) Controller: OneNAND控制器編號,固定爲0 uBlkAddr:塊地址 uPageAddr:頁地址 aData:目標地址 返回值:失敗         1 成功
-
0x0C00_4010
bool ONENAND_ReadPage_4burst(uint32 Controller,uint32 uBlkAddr , uint8 uPageAddr, uint32* aData) Controller: OneNAND控制器編號,固定爲0 uBlkAddr:塊地址 uPageAddr:頁地址 aData:目標地址 返回值:失敗         1 成功
-
0x0C00_4014
bool ONENAND_ReadPage_8burst(uint32 Controller,uint32 uBlkAddr , uint8 uPageAddr, uint32* aData) Controller: OneNAND控制器編號,固定爲0 uBlkAddr:塊地址 uPageAddr:頁地址 aData:目標地址 返回值:失敗         1 成功
-
BL1的主要工做是從存儲設備中,拷貝更完善的BL2至DRAM,而且BL1大小不能超過8K。若是須要用戶自行編寫函數實現拷貝功能,開發難度很大。事實上,S3C6410已經在IROM中固化了6個用於從不一樣外部存儲設備拷貝數據到SDRAM中的函數,如表2. 3,這些函數的指針存放在D-TCM1的前24字節(每一個指針變量佔4字節)。用戶根據須要調用便可,有效地下降了開發難度。以CopyMMCtoMem函數爲例,能夠經過如下形式調用該函數。
#define CopyMMCtoMem(a,b,c,d,e) (((int(*)(int, uint, ushort, uint *, int))(*((uint *)(0x0C004000 + 0x8))))(a,b,c,d,e))
爲了更方便的闡釋IROM-SD/MMC的啓動原理,本書約定從IROM、以SD/MMC爲存儲設備的啓動方式爲SD卡啓動。
第三章第一節   初步測試內核 
內核的移植相對複雜,不可能一步到位,心急吃不了熱豆腐,咱們只有步步爲營,方能步步爲贏。本節的目的是修改內核,使得Linux-3.8.3內核適應於OK6410開發平臺。外設的移植,在接下來的章節會一步一步完成。
1.1.1   mkimage工具
製做Linux內核的壓縮鏡像文件,須要使用到mkimage工具。mkimage這個工具位於u-boot-2013. 04中的tools目錄下,它能夠用來製做不壓縮或者壓縮的多種可啓動鏡像文件。mkimage在製做鏡像文件的時候,是在原來的可執行鏡像文件的前面加上一個16byte0x40)的頭,用來記錄參數所指定的信息,這樣u-boot才能識別出製做出來的這個鏡像是針對哪個CPU體系結構、哪種OS、哪一種類型、加載到內存中的哪一個位置、入口點在內存的哪一個位置以及鏡像名是什麼等信息。在/u-boot-2013.04/tools目錄下執行./mkimage,輸出信息以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/u-boot/u-boot-2013.04/u-boot-2013.04/tools$ ./mkimage
Usage: ./mkimage -l image
          -l ==> list image header information
       ./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name-d data_file[:data_file...] image
          -A ==> set architecture to 'arch'
          -O ==> set operating system to 'os'
          -T ==> set image type to 'type'
          -C ==> set compression type 'comp'
          -a ==> set load address to 'addr' (hex)
          -e ==> set entry point to 'ep' (hex)
          -n ==> set image name to 'name'
          -d ==> use image data from 'datafile'
          -x ==> set XIP (execute in place)
       ./mkimage [-D dtc_options] -f fit-image.its fit-image
       ./mkimage -V ==> print version information and exit
3.  1  CPU體系結構
取值
表示的體系結構
取值
表示的體系結構
alpha
Alpha
arm
ARM
x86
Intel x86
ia64
IA64
mips
MIPS
mips64
MIPS 64 Bit
ppc
PowerPC
s390
IBM S390
sh
SuperH
sparc
SPARC
sparc64
SPARC 64 Bit
m68k
MC68000
針對上面的輸出信息,-A 指定CPU的體系結構,也就是說,arch的取值能夠是如表3. 1所示。
-O 指定操做系統類型,os能夠取:openbsdnetbsdfreebsd4_4bsdlinuxsvr4esixsolarisirixscodellncrlynxosvxworkspsosqnxu-bootrtemsartos
-T 指定鏡像類型,type能夠是:standalonekernelramdiskmultifirmwarescriptfilesystem
-C 指定鏡像壓縮方式,comp能夠是:none(不壓縮)、gzip gzip的壓縮方式)、bzip2(用bzip2的壓縮方式)。
-a 指定鏡像在內存中的加載地址,鏡像下載到內存中時,要按照用mkimage製做鏡像時,這個參數所指定的地址值來下載。
-e 指定鏡像運行的入口點地址,這個地址就是-a參數指定的值加上0x40(由於前面有個mkimage添加的0x40個字節的頭)。
-n 指定鏡像名。
-d 指定製做鏡像的源文件。
u-boot-2013.04下的tools這個文件夾下中的mkimage工具複製到ubuntu系統的/user/bin下,這樣能夠直接看成操做命令使用。
 
1.1.2   配置menuconfig
make menuconfig是基於文本選單的圖形化內核配置界面。
打開最頂層的Makefile,有這麼兩行代碼。
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
ARCH針對何種CPU體系結構,OK6410cpu是三星公司的S3C6410,爲arm,那麼這句就得修改爲armCROSS_COMPILE是編譯工具鏈,和u-boot配置同樣。則需修改爲:
ARCH ?= arm
CROSS_COMPILE ?= /usr/local/arm/4.4.1/bin/arm-linux-
進入arch/arm/mach-s3c64xx,有Kconfig文件,Kconfig做用是描述所屬目錄源文檔相關的內核配置菜單,在執行make menuconfig時,將從Kconfig文件中讀出菜單。打開Kconfig文件。其中:
# S3C6410 machine support
所支持的平臺有:
config MACH_ANW6410
config MACH_MINI6410
config MACH_REAL6410
config MACH_SMDK6410
可是沒有OK6410,這裏就須要進行修改文件,使得Linux-3.8.3能適合運行在OK6410開發平臺的內核,取以上的四種平臺中的一種做爲基礎進行修改,這裏就採用MINI6410
在當前arch/arm/mach-s3c64xx文件下,複製一份mach-mini6410.c而且重命名爲mach-ok6410.c。使用操做命令:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx$ cp mach-mini6410.c mach-ok6410.c
打開mach-ok6410.c文件,將mini6410MINI6410)修改成ok6410OK6410),打開mach-ok6410.c
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx$ gedit mach-ok6410.c
使用gedit最大的好處是能夠很好的進行文本操做,使用替換功能,將mini6410 MINI6410)替換成爲ok6410OK6410)。
arch/arm/mach-s3c64xx目錄下打開Makefile,找到以下:
obj-$(CONFIG_MACH_MINI6410)             += mach-mini6410.o
在其後面添加ok6410的配置:
obj-$(CONFIG_MACH_OK6410)               += mach-ok6410.o
添加這行代碼則是告訴編譯器要將ok6410.c編譯進內核。
回到arch/arm/mach-s3c64xx目錄下的Kconfig,打開文件,爲OK6410添加配置菜單。在以下:
config MACH_MINI6410
後面添加OK6410的配置:
config MACH_OK6410
    bool "OK6410"
    select CPU_S3C6410
    select SAMSUNG_DEV_ADC
    select S3C_DEV_HSMMC
    select S3C_DEV_HSMMC1
    select S3C_DEV_I2C1
select SAMSUNG_DEV_IDE
    select S3C_DEV_FB
    select S3C_DEV_RTC
    select SAMSUNG_DEV_TS
    select S3C_DEV_USB_HOST
#   select S3C_DEV_USB_HSOTG
    select S3C_DEV_WDT
    select SAMSUNG_DEV_KEYPAD
    select SAMSUNG_DEV_PWM
    select HAVE_S3C2410_WATCHDOG if WATCHDOG
    select S3C64XX_SETUP_SDHCI
    select S3C64XX_SETUP_I2C1
    select S3C64XX_SETUP_IDE
    select S3C64XX_SETUP_FB_24BPP
    select S3C64XX_SETUP_KEYPAD
    help
      Machine support for the feiling OK6410
添加以後執行make menuconfig就會有ok6410選項。
進入arch/arm/tools,打開mach-types文件:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/tools$ gedit mach-types
能夠看到以下:
machine_is_xxx    CONFIG_xxxx         MACH_TYPE_xxx           number
mini6410          MACH_MINI6410       MINI6410                2520
mini6410ID2520,可是OK6410ID1626,這個在u-boot也曾經出現過,這就如每個人都有本身相對應的ID,若是ID號不匹配,將致使u-boot沒法啓動內核,在mini6410後面添加以下。
ok6410          MACH_OK6410         OK6410                 1626
第三章第二節   mkimage工具


製做Linux內核的壓縮鏡像文件,須要使用到mkimage工具。mkimage這個工具位於u-boot-2013. 04中的tools目錄下,它能夠用來製做不壓縮或者壓縮的多種可啓動鏡像文件。mkimage在製做鏡像文件的時候,是在原來的可執行鏡像文件的前面加上一個16個byte(0x40)的頭,用來記錄參數所指定的信息,這樣u-boot才能識別出製做出來的這個鏡像是針對哪個CPU體系結構、哪種OS、哪一種類型、加載到內存中的哪一個位置、入口點在內存的哪一個位置以及鏡像名是什麼等信息。在/u-boot-2013.04/tools目錄下執行./mkimage,輸出信息以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/u-boot/u-boot-2013.04/u-boot-2013.04/tools$./mkimage
Usage: ./mkimage -l image
          -l==> list image header information
       ./mkimage[-x] -A arch -O os -T type -C comp -a addr -e ep -n name -ddata_file[:data_file...] image
          -A==> set architecture to 'arch'
          -O==> set operating system to 'os'
          -T==> set image type to 'type'
          -C==> set compression type 'comp'
          -a==> set load address to 'addr' (hex)
          -e==> set entry point to 'ep' (hex)
          -n==> set image name to 'name'
          -d==> use image data from 'datafile'
          -x==> set XIP (execute in place)
       ./mkimage[-D dtc_options] -f fit-image.its fit-image
       ./mkimage-V ==> print version information and exit
      
        
3. 1  CPU 體系結構
   
   
      
取值
      
表示的體系結構
取值
表示的體系結構
alpha
Alpha
arm
ARM
x86
Intel x86
ia64
IA64
mips
MIPS
mips64
MIPS 64 Bit
ppc
PowerPC
s390
IBM S390
sh
SuperH
sparc
SPARC
sparc64
SPARC 64 Bit
m68k
MC68000
   
   
 
    
   
   針對上面的輸出信息,-A 指定CPU的體系結構,也就是說,arch的取值能夠是如3. 1所示。
-O 指定操做系統類型,os能夠取:openbsd、netbsd、freebsd、4_4bsd、linux、svr四、esix、solaris、irix、sco、dell、ncr、lynxos、vxworks、psos、qnx、u-boot、rtems、artos。
-T 指定鏡像類型,type能夠是:standalone、kernel、ramdisk、multi、firmware、script、filesystem。
-C 指定鏡像壓縮方式,comp能夠是:none(不壓縮)、gzip( 用gzip的壓縮方式)、bzip2 (用bzip2的壓縮方式)。
-a 指定鏡像在內存中的加載地址,鏡像下載到內存中時,要按照用mkimage製做鏡像時,這個參數所指定的地址值來下載。
-e 指定鏡像運行的入口點地址,這個地址就是-a參數指定的值加上0x40(由於前面有個mkimage添加的0x40個字節的頭)。
-n 指定鏡像名。
-d 指定製做鏡像的源文件。
u-boot-2013.04下的tools這個文件夾下中的mkimage工具複製到ubuntu系統的/user/bin下,這樣能夠直接看成操做命令使用。
第三章第三節     加載地址和入口地址
在上一節中,沒法啓動內核,致使的緣由多是加載地址、入口地址等致使的。執行./mkimage以後以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/u-boot/u-boot-2013.04/u-boot-2013.04/tools$ ./mkimage
./mkimage [-x] -A arch -O os -T type -C comp -a addr -e ep -n name -ddata_file[:data_file...] image
其中-a addr指的就是鏡像在內存中的加載地址,鏡像下載到內存中時,要按照用mkimage製做鏡像時,這個參數所指定的地址值來下載。
-e ep 是指定鏡像運行的入口點地址。
還有兩個概念須要明白,便是bootm addresskernel運行地址。bootm address:經過ubootbootm命令,從address啓動kernelkernel運行地址:在具體mach目錄中的Makefile.boot中指定,是kernel啓動後實際運行的物理地址。
若是bootm addressLoad Address相等,在這種狀況下,bootm不會對uImage header後的zImage進行memory move的動做,而會直接goEntry Point開始執行。所以此時的Entry Point必須設置爲Load Address+ 0x40。若是kernel boot過程沒有到uncompressing the kernel,就多是這裏設置不對。它們之間的關係爲:boom address == Load Address == Entry Point - 0x40
若是bootm addressLoad Address不相等(但須要避免出現memory move時出現覆蓋致使zImage被破壞的狀況)。此種狀況下,bootm會把uImage header後的zImage文件moveLoad Address,而後goentry point開始執行。這段代碼在common/cmd_bootm.cbootm_load_os函數中,以下程序所示。由此知道此時的Load Address必須等於Entry Point。它們之間的關係則爲:boom address != Load Address == Entry Point
    case IH_COMP_NONE:
        if (load == blob_start || load == image_start)
{
            printf("   XIP %s ... ", type_name);
            no_overlap = 1;
        }
else
{
            printf("   Loading %s ... ", type_name);
            memmove_wd((void *)load, (void *)image_start,
                    image_len, CHUNKSZ);
        }
        *load_end = load + image_len;
        puts("OK\n");
        break;
zImage的頭部有地址無關的自解壓程序,所以剛開始執行的時候,zImage所在的內存地址(Entry Point)不須要同編譯kernel的地址相同。自解壓程序會把kernel解壓到編譯時指定的物理地址,而後開始地址相關代碼的執行。在開啓MMU以前,kernel都是直接使用物理地址(可參看內核符號映射表System.map)。
經過上面的分析,大概找出了問題的根源,因爲bootm addressLoad Address都爲50008000,屬於相等狀況,也就是說Entry Point:  50008000,這個地址須要修改,替換成50008040
找到Load AddressEntry Point這兩個地址的定義,存在於scripts/makefile.lib中,
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/scripts$ gedit Makefile.lib
打開以後能夠找到以下:
318    UIMAGE_LOADADDR ?= arch_must_set_this
319    UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR)
這裏就是說Entry Point等於Load Address,那麼應該修改爲爲Entry Point=Load Address+0x40,在GNU make中,有sed –e替換操做,如sed -e "s/..$$/40/",就是把輸出的字符串的最後兩個字符刪掉,而且用40來補充,也就是說把字符串最後兩個字符用40來替換。
那麼做以下修改:
318    UIMAGE_LOADADDR ?= arch_must_set_this
319    #UIMAGE_ENTRYADDR ?= $(UIMAGE_LOADADDR)
320    UIMAGE_ENTRYADDR  ?=$(shell echo $(UIMAGE_LOADADDR) |
sed -e "s/..$$/40/")
修改完成以後,回到linux-3.8.3根目錄下進行編譯,以下操做:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ make uImage
若是有以下報錯:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ make uImage
scripts/kconfig/conf --silentoldconfig Kconfig
 
*** Error during update of the configuration.
 
make[2]: *** [silentoldconfig] 錯誤 1
make[1]: *** [silentoldconfig] 錯誤 2
make: *** 沒有規則能夠建立「include/config/kernel.release」須要的目標「include/config/auto.conf」。中止。
那就是權限的問題,要麼修改文件權限,要麼在root下編譯。這樣便可:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ sudo make uImage
編譯成功以後輸出以下信息:
······
Image Name:   Linux-3.8.3
Created:      Sat Mar 16 10:38:47 2013
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    1664080 Bytes = 1625.08 kB = 1.59 MB
Load Address: 50008000
Entry Point:  50008040
  Image arch/arm/boot/uImage is ready
從串口輸出可知,Entry Point=Load Address+0x40,依舊按照SD燒寫方式進行測試,若是bootdelay延時過長,能夠修改bootdelay時間,以下操做:
Hit any key to stop autoboot:  0
zzq6410 >>> set bootdelay 3
zzq6410 >>> sav
Saving Environment to NAND...
Erasing Nand...
Erasing at 0x80000 -- 100% complete.
Writing to Nand... done
zzq6410 >>>
重啓OK6410開發平臺,測試結果以下:
NAND read: device 0 offset 0x100000, size 0x500000
5242880 bytes read: OK
## Booting kernel from Legacy Image at 50008000 ...
   Image Name:   Linux-3.8.3
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    1664080 Bytes = 1.6 MiB
   Load Address: 50008000
   Entry Point:  50008040
   Verifying Checksum ... OK
   XIP Kernel Image ... OK
OK
 
Starting kernel ...
 
Starting kernel ...
 
Uncompressing Linux... done, booting the kernel.
Booting Linux on physical CPU 0x0
Linux version 3.8.3 (zhuzhaoqi@zhuzhaoqi-desktop) (gcc version 4.4.1 (Sourcery G++ Lite 2009q3-67) ) #1 Fri Mar 15 12:56:52 CST 2013
CPU: ARMv6-compatible processor [410fb766] revision 6 (ARMv7), cr=00c5387d
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
Machine: OK6410
Memory policy: ECC disabled, Data cache writeback
CPU S3C6410 (id 0x36410101)
S3C24XX Clocks, Copyright 2004 Simtec Electronics
S3C64XX: PLL settings, A=533000000, M=533000000, E=24000000
S3C64XX: HCLK2=266500000, HCLK=133250000, PCLK=66625000
mout_apll: source is fout_apll (1), rate is 533000000
mout_epll: source is epll (1), rate is 24000000
mout_mpll: source is mpll (1), rate is 533000000
usb-bus-host: source is clk_48m (0), rate is 48000000
irda-bus: source is mout_epll (0), rate is 24000000
CPU: found DTCM0 8k @ 00000000, not enabled
CPU: moved DTCM0 8k to fffe8000, enabled
CPU: found DTCM1 8k @ 00000000, not enabled
CPU: moved DTCM1 8k to fffea000, enabled
CPU: found ITCM0 8k @ 00000000, not enabled
CPU: moved ITCM0 8k to fffe0000, enabled
CPU: found ITCM1 8k @ 00000000, not enabled
CPU: moved ITCM1 8k to fffe2000, enabled
Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 65024
Kernel command line: root=/dev/mtdblock2 rootfstype=cramfs console=ttySAC0,115200
PID hash table entries: 1024 (order: 0, 4096 bytes)
Dentry cache hash table entries: 32768 (order: 5, 131072 bytes)
Inode-cache hash table entries: 16384 (order: 4, 65536 bytes)
__ex_table already sorted, skipping sort
Memory: 256MB = 256MB total
Memory: 256532k/256532k available, 5612k reserved, 0K highmem
Virtual kernel memory layout:
    vector  : 0xffff0000 - 0xffff1000   (   4 kB)
    DTCM    : 0xfffe8000 - 0xfffec000   (  16 kB)
    ITCM    : 0xfffe0000 - 0xfffe4000   (  16 kB)
    fixmap  : 0xfff00000 - 0xfffe0000   ( 896 kB)
    vmalloc : 0xd0800000 - 0xff000000   ( 744 MB)
    lowmem  : 0xc0000000 - 0xd0000000   ( 256 MB)
    modules : 0xbf000000 - 0xc0000000   (  16 MB)
      .text : 0xc0008000 - 0xc02bed88   (2780 kB)
      .init : 0xc02bf000 - 0xc02da7a4   ( 110 kB)
      .data : 0xc02dc000 - 0xc03076a0   ( 174 kB)
       .bss : 0xc0308000 - 0xc0338ef8   ( 196 kB)
SLUB: Genslabs=13, HWalign=32, Order=0-3, MinObjects=0, CPUs=1, Nodes=1
NR_IRQS:246
VIC @f6000000: id 0x00041192, vendor 0x41
VIC @f6010000: id 0x00041192, vendor 0x41
sched_clock: 32 bits at 100 Hz, resolution 10000000ns, wraps every 4294967286ms
Console: colour dummy device 80x30
Calibrating delay loop... 353.89 BogoMIPS (lpj=1769472)
pid_max: default: 32768 minimum: 301
Mount-cache hash table entries: 512
CPU: Testing write buffer coherency: ok
Setting up static identity map for 0x502149a8 - 0x50214a04
DMA: preallocated 256 KiB pool for atomic coherent allocations
OK6410: Option string ok6410=0
OK6410: selected LCD display is 480x272
s3c64xx_dma_init: Registering DMA channels
PL080: IRQ 73, at d0846000, channels 0..8
PL080: IRQ 74, at d0848000, channels 8..16
S3C6410: Initialising architecture
bio: create slab <bio-0> at 0
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
ROMFS MTD (C) 2007 Red Hat, Inc.
io scheduler noop registered
io scheduler deadline registered
io scheduler cfq registered (default)
s3c-fb s3c-fb: window 0: fb
Serial: 8250/16550 driver, 4 ports, IRQ sharing disabled
s3c6400-uart.0: ttySAC0 at MMIO 0x7f005000 (irq = 69) is a S3C6400/10
console [ttySAC0] enabled
s3c6400-uart.1: ttySAC1 at MMIO 0x7f005400 (irq = 70) is a S3C6400/10
s3c6400-uart.2: ttySAC2 at MMIO 0x7f005800 (irq = 71) is a S3C6400/10
s3c6400-uart.3: ttySAC3 at MMIO 0x7f005c00 (irq = 72) is a S3C6400/10
brd: module loaded
loop: module loaded
s3c24xx-nand s3c6400-nand: Tacls=4, 30ns Twrph0=8 60ns, Twrph1=6 45ns
s3c24xx-nand s3c6400-nand: System booted from NAND
s3c24xx-nand s3c6400-nand: NAND soft ECC
NAND device: Manufacturer ID: 0xec, Chip ID: 0xd5 (Samsung NAND 2GiB 3,3V 8-bit), 2048MiB, page size: 4096, OOB size: 218
No oob scheme defined for oobsize 218
……
Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b
中間做者省去了不少信息,由於這些信息暫時對咱們是沒有太大關係,可是也給出了不少信息,由於能夠很好地和接下來的每一步移植做對比。從串口的輸出,能夠得知內核是啓動了。也就是說,此時u-boot已經成功將相關參數傳遞給linux3.8.3內核,完成了u-boot到內核的交接。而且內核已經識別了是OK6410開發平臺,控制CPUs3c6410等信息。
固然,讀者不只僅能夠經過修改Entry Point使得內核啓動,還能夠修改啓動內核的地址使得bootm addressLoad Address不相等,也就是修改U-Boot源碼中include/configs/目錄下的s3c6410.h文件中:
#ifdef CONFIG_ENABLE_MMU
#define CONFIG_SYS_MAPPED_RAM_BASE  0xc0000000
#define CONFIG_BOOTCOMMAND"nand read 0xc0018000 0x600000x1c0000;\"bootm 0xc0018000"
#else
 
#define CONFIG_SYS_MAPPED_RAM_BASE  CONFIG_SYS_SDRAM_BASE
#define CONFIG_BOOTCOMMAND"nand read 0x50018000 0x100000 0x500000;"\"bootm 0x50018000"
#endif
第三章第四節     內核啓動分析
    對於ARM處理器,內核啓動大致上能夠分爲兩個階段:與處理器相關的彙編啓動階段和與處理器無關的C代碼啓動階段。彙編啓動階段從head.S(arch/arm/kernel/head.S)文件開始,C代碼啓動階段從start_kernel函數(init/main.c)開始。固然,通過壓縮的內核鏡像文件zImage,在進入彙編啓動階段前還要運行一段自解壓代碼(arch/arm/boot/compressed/head.S)
    省略一些可有可無的過程和編譯後不運行的代碼,該過程的啓動流程如圖3. 7所示。相對早期linux-2.6.38的版本,linux-3.8.3在彙編啓動階段並無出現__lookup_machine_type,但這並不意味着內核再也不檢查bootloader傳入的machine_arch_type參數(R1),只是將檢查機制推遲到了C代碼階段。
   
1)     __lookup_processor_type
__lookup_processor_type函數的具體實現如程序清單3. 1。
程序清單3.  1查找處理器類型函數
__lookup_processor_type:
    adr r3, __lookup_processor_type_data
    ldmia r3, {r4 - r6}
    sub r3, r3, r4          @ get offset between virt&phys
    add r5, r5, r3          @ convert virt addresses to
    add r6, r6, r3          @ physical address space
1:  ldmia   r5, {r3, r4}            @ value, mask
    and r4, r4, r9          @ mask wanted bits
    teq r3, r4
    beq 2f
    add r5, r5, #PROC_INFO_SZ       @ sizeof(proc_info_list)
    cmp r5, r6
    blo 1b
    mov r5, #0              @ unknown processor
2:  mov pc, lr
ENDPROC(__lookup_processor_type)
    .align  2
    .type   __lookup_processor_type_data, %object
__lookup_processor_type_data:
    .long   .
    .long   __proc_info_begin
    .long   __proc_info_end
    .size   __lookup_processor_type_data, . - __lookup_processor_type_data
__lookup_processor_type函數的主要功能是將內核支持的全部CPU類型與經過程序實際讀取的cpu id進行查表匹配。若是匹配成功,將匹配到的proc_info_list的基地址存到r5,不然,r5爲0,程序將會進入一個死循環。函數傳入參數r9爲程序實際讀取的cpu id,傳出參數r5爲匹配到的proc_info_list指針的地址。同時爲了使C語言可以調用這個函數,根據APCS(ARM 過程調用標準)規則,簡單使用如下代碼就能包裝成一個C語言版本__lookup_processor_type的API函數,函數的原型爲struct proc_info_list *lookup_processor_type(unsigned int)。
ENTRY(lookup_processor_type)
    stmfd   sp!, {r4 - r6, r9, lr}
    mov r9, r0
    bl  __lookup_processor_type
    mov r0, r5
    ldmfd   sp!, {r4 - r6, r9, pc}
ENDPROC(lookup_processor_type)
ENTRY和ENDPROC宏的定義以下:
#define ENTRY(name)             \
    .globl name;                \
    name:
#define ENDPROC(name)
內核利用一個結構體proc_info_list來記錄處理器相關的信息,在文件arch/arm/include/asm/procinfo.h聲明瞭該結構體的類型,以下所示。
struct proc_info_list {
    unsigned int        cpu_val;
    unsigned int        cpu_mask;
    unsigned long       __cpu_mm_mmu_flags; /* used by head.S */
    unsigned long       __cpu_io_mmu_flags; /* used by head.S */
    unsigned long       __cpu_flush;        /* used by head.S */
    const char      *arch_name;
    const char      *elf_name;
    unsigned int        elf_hwcap;
    const char      *cpu_name;
    struct processor    *proc;
    struct cpu_tlb_fns  *tlb;
    struct cpu_user_fns *user;
    struct cpu_cache_fns    *cache;
};
事實上,在arch/arm/mm/proc-*.S這類文件中,程序才真正給內核所支持的arm處理器的proc_info_list分配了內存空間,例如linux/arch/arm/mm/proc-v6.S文件用匯編語言定義的__v6_proc_info結構體。.section指示符來指定這些結構體編譯到.proc.info段。.proc.info的起始地址爲 __proc_info_begin,終止位置爲__proc_info_end,把它們做爲全局變量保存在內存中,連接腳本arch/arm/kernel/vmlinux.lds部份內容參考以下:
.init.proc.info : {
  . = ALIGN(4);
__proc_info_begin = .;
*(.proc.info.init)
__proc_info_end = .;
}
2)       __vet_atags
在啓動內核時, bootloader會向內核傳遞一些參數。一般,bootloader 有兩種方法傳遞參數給內核:一種是舊的參數結構方式(parameter_struct)——主要是2.6 以前的內核使用的方式;另一種是如今的內核在用的參數列表(tagged list) 的方式。這些參數主要包括,系統的根設備標誌、頁面大小、內存的起始地址和大小、當前內核命令參數等。而這些參數是經過struct tag結構體組織,利用指針連接成一個按順序排放的參數列表。bootloader引導內核啓動時,就會把這個列表的首地址放入R2中,傳給內核,內核經過這個地址就分析出傳入的全部參數。
內核要求參數列表必須存放在RAM物理地址的頭16k位置,而且ATAG_CORE類型的參數須要放置在參數的列表的首位。__vet_atags的功能就是初步分析傳入的參數列表,判斷的方法也很簡單。若是這個列表起始參數是ATAG_CORE類型,則表示這是一個有效的參數列表。若是起始參數不是ATAG_CORE,就認爲bootloader沒有傳遞參數給內核或傳入的參數不正確。
1)       _ _create_page_tables
3.  8實際內存分佈圖

linux內核使用頁式內存管理,應用程序給出的內存地址是虛擬地址,它須要通過若干級頁表一級一級的變換,才變成真正的物理地址。32位CPU的虛擬地址大小從0x0000_0000到0xFFFF_FFFF共4G。以段(1 MB)的方式創建一級頁表,能夠將虛擬地址空間分割成4096個段條目(section entry)。條目也稱爲「描述符」(Descriptor),每個段描述符32位,所以一級頁表佔用16K(0x4000)內存空間。
 
 
 
s3c6410處理器DRAM的地址空間從0x5000_0000開始,上文提到bootloader傳遞給內核的參數列表存放在RAM物理地址的頭16K位置,頁表放置在內核的前16K,所以內核的偏移地址爲32K(0x8000),由此構成了如圖3. 8所示的實際內存分佈圖。
3. 9初步頁表創建流程

__create_page_tables函數初始化了一個很是簡單頁表,僅映射了使內核可以正常啓動的代碼空間,更加細緻的工做將會在後續階段完善。流程如所示,獲取頁表物理地址、清空頁表區和創建啓動參數頁表經過閱讀源碼很容易理解,不加分析。
__enable_mmu函數使能mmu後,CPU發出的地址是虛擬地址,程序正常運行須要映射獲得物理地址,爲了保障正常地配置mmu,須要對這段代碼1:1的絕對映射,映射範圍__turn_mmu_on至__turn_mmu_on_end。正常使能mmu後,不須要這段特定的映射了,在後續C代碼啓動階段時被paging_init()函數刪除。創建__enable_mmu函數區域的頁表代碼如程序清單 3. 2所示。
程序清單3.  2  __enable_mmu頁表的創建
//r4 =頁表物理地址
//獲取段描述符的默認配置flags
    ldr r7, [r10, #PROCINFO_MM_MMUFLAGS]
    adr r0, __turn_mmu_on_loc //獲得__turn_mmu_on_loc的物理地址
    ldmia   r0, {r3, r5, r6}
    sub r0, r0, r3      //計算獲得物理地址與虛擬地址的誤差
    add r5, r5, r0      //修正獲得__turn_mmu_on的物理地址
    add r6, r6, r0      //修正獲得__turn_mmu_on_end的物理地址
    mov r5, r5, lsr #SECTION_SHIFT //1M對齊
    mov r6, r6, lsr #SECTION_SHIFT //1M對齊
1:  orr r3, r7, r5, lsl #SECTION_SHIFT  //生成段描述符:flags + 段基址
    str r3, [r4, r5, lsl #PMD_ORDER]    //設置段描述絕對映射,物理地址等於虛擬地址。每一個段描述符佔4字節,PMD_ORDER = 2
    cmp r5, r6
    addlo   r5, r5, #1          //下一段,實際上__turn_mmu_on_end - __turn_mmu_on<  1M
    blo 1b
............................
__turn_mmu_on_loc:
    .long   .                   //__turn_mmu_on_loc當前位置的虛擬地址
    .long   __turn_mmu_on      //__turn_mmu_on的虛擬地址
    .long   __turn_mmu_on_end  //__turn_mmu_on_end的虛擬地址
創建內核的映射區頁表,分析見程序清單 3. 3
程序清單3.  3內核的映射區頁表的創建
//r4 =頁表物理地址
mov r3, pc                  //r3 = 當前物理地址
    mov r3, r3, lsr #SECTION_SHIFT    //物理地址轉化段基址
    orr r3, r7, r3, lsl #SECTION_SHIFT  //段基址 + flags = 段描述符
//KERNEL_START = 0xC000_8000  SECTION_SHIFT = 20  PMD_ORDER =  2
//因爲arm 的當即數只能是8位表示,全部用兩條指令實現了將r3存儲到對應的頁表項中
    add r0, r4,  #(KERNEL_START & 0xff000000) >> (SECTION_SHIFT - PMD_ORDER)
    str r3, [r0, #((KERNEL_START & 0x00f00000) >> SECTION_SHIFT) << PMD_ORDER]!
    ldr r6, =(KERNEL_END - 1)
    add r0, r0, #1 << PMD_ORDER
    add r6, r4, r6, lsr #(SECTION_SHIFT - PMD_ORDER)  //內核映射頁表結束的段基址
1:  cmp r0, r6
    add r3, r3, #1 << SECTION_SHIFT   //獲得段描述符
    strls   r3, [r0], #1 << PMD_ORDER    //設置段描述符
    bls 1b
1)        __v6_setup
__v6_setup 函數在 proc-v6.S 文件中,在頁表創建起來以後,此函數進行一些使能 MMU 以前的初始化操做。
2)       _ _enable_mmu
__v6_setup已經爲使能 MMU作好了必要的準備,爲了保證MMU啓動後程序順利返回,在進入__enable_mmu函數以前,已經將__mmap_switched的虛擬地址(連接地址)存儲在R13中。
3)        __mmap_switched
程序運行到這裏,MMU已經啓動,__mmap_switched函數爲內核進入C代碼階段作了一些準備工做:複製數據段,清楚BSS段,設置堆棧指針,保存processor ID、machine type(bootloader中傳入的)、atags pointer等。最後,終於跳轉到start_kernel函數,進入C代碼啓動階段。
第三章第五節  MTD分區
[url=] Memory Technology Device[/url] ,縮寫爲 MTD,即爲內存技術設備,是Linux系統中快閃存儲器轉換層。創造MTD子系統的主要目的是提供一個介於快閃存儲器硬件與上層應用之間的抽象接口。
由於具有如下特性,因此 MTD 設備和硬盤相較之下,處理起來要複雜許多:
1)        具備 eraseblocks 的[url=]特微[/url] ,而不是像硬盤同樣使用
2)        eraseblocks (32KiB ~ 128KiB) 跟硬盤的 sector size512  1024 bytes)比起來要大不少。
3)        操做上主要分做三個動做:從 eraseblock 讀取、寫入 eraseblock 、還有就是清除eraseblock 
4)        壞掉的 eraseblocks 沒法隱藏,須要軟件加以處理。
5)        eraseblocks 的壽命大約會在 104  105 的清除動做以後退出。
進入arch/arm/mach-s3c64xx目錄,打開mach-ok6410.c文件。能夠看到MTD分區信息以下:
static struct mtd_partition ok6410_nand_part[] = {
    [0] = {
        .name   = "uboot",
        .size   = SZ_1M,
        .offset = 0,
    },
    [1] = {
        .name   = "kernel",
        .size   = SZ_2M,
        .offset = SZ_1M,
    },
    [2] = {
        .name   = "rootfs",
        .size   = MTDPART_SIZ_FULL,
        .offset = SZ_1M + SZ_2M,
    },
};
Linux-3.8.3的源碼將NandFlash劃分爲3個分區:前1M用於存放u-boot1M後面的2M空間之間用於存放內核,3M以後的空間用來存放虛擬文件系統。rootfs文件系統是基於內存的文件系統,也是虛擬的文件系統,在系統啓動以後,隱藏在真正的根文件系統後面,不能被卸載。
這裏須要對NandFlash從新劃分分區,是的這個MTD分區適合OK6410開發平臺,也能適合當前的u-boot、內核、文件系統以及用戶。修改以下:
static struct mtd_partition ok6410_nand_part[] = {
        [0] = {
                .name       = "Bootloader",
                .offset     = 0,
                .size       = (1 * SZ_1M),
                .mask_flags = MTD_CAP_NANDFLASH,
        },
        [1] = {
                .name       = "Kernel",
                .offset     = (1 * SZ_1M),
                .size       = (5 * SZ_1M) ,
                .mask_flags = MTD_CAP_NANDFLASH,
        },
        [2] = {
                .name   = "File System",
                .offset = (6 * SZ_1M),
                .size   = (200 * SZ_1M) ,
        },
        [3] = {
                .name   = "User",
                .offset = MTDPART_OFS_APPEND,
                .size   = MTDPART_SIZ_FULL,
        },
};
NandFlash劃分紅了4MTD分區,其中0~1M之間的空間用來存放Bootloader,也就是u-boot1M~6M之間的空間用來存放linux內核,6M~206M之間的空間用來存放文件系統,剩下的空間提供給用戶使用。
修改完成以後,執行make uImage從新生成內核。將uImage重命名爲zImage,使用TFTP調試內核。
tftp 0x50008000 zImage
bootm 0x50008000
因爲尚未添加NandFlash驅動,因此串口輸出信息暫時沒法看到MTD分區信息。
注:本節配套視頻位於光盤中「嵌入式Linux實用教程視頻」目錄下第三章05課
第三章第五節    MTD分區
[url=] Memory Technology Device[/url] ,縮寫爲 MTD,即爲內存技術設備,是Linux系統中快閃存儲器轉換層。創造MTD子系統的主要目的是提供一個介於快閃存儲器硬件與上層應用之間的抽象接口。
由於具有如下特性,因此 MTD 設備和硬盤相較之下,處理起來要複雜許多:
1)        具備 eraseblocks 的[url=]特微[/url]   ,而不是像硬盤同樣使用
2)        eraseblocks (32KiB ~ 128KiB) 跟硬盤的 sector size512  1024 bytes)比起來要大不少。
3)        操做上主要分做三個動做:從 eraseblock 讀取、寫入 eraseblock 、還有就是清除eraseblock 
4)        壞掉的 eraseblocks 沒法隱藏,須要軟件加以處理。
5)        eraseblocks 的壽命大約會在 104  105 的清除動做以後退出。
進入arch/arm/mach-s3c64xx目錄,打開mach-ok6410.c文件。能夠看到MTD分區信息以下:
static struct mtd_partition ok6410_nand_part[] = {
    [0] = {
        .name   = "uboot",
        .size   = SZ_1M,
        .offset = 0,
    },
    [1] = {
        .name   = "kernel",
        .size   = SZ_2M,
        .offset = SZ_1M,
    },
    [2] = {
        .name   = "rootfs",
        .size   = MTDPART_SIZ_FULL,
        .offset = SZ_1M + SZ_2M,
    },
};
Linux-3.8.3的源碼將NandFlash劃分爲3個分區:前1M用於存放u-boot1M後面的2M空間之間用於存放內核,3M以後的空間用來存放虛擬文件系統。rootfs文件系統是基於內存的文件系統,也是虛擬的文件系統,在系統啓動以後,隱藏在真正的根文件系統後面,不能被卸載。
這裏須要對NandFlash從新劃分分區,是的這個MTD分區適合OK6410開發平臺,也能適合當前的u-boot、內核、文件系統以及用戶。修改以下:
static struct mtd_partition ok6410_nand_part[] = {
        [0] = {
                .name       = "Bootloader",
                .offset     = 0,
                .size       = (1 * SZ_1M),
                .mask_flags = MTD_CAP_NANDFLASH,
        },
        [1] = {
                .name       = "Kernel",
                .offset     = (1 * SZ_1M),
                .size       = (5 * SZ_1M) ,
                .mask_flags = MTD_CAP_NANDFLASH,
        },
        [2] = {
                .name   = "File System",
                .offset = (6 * SZ_1M),
                .size   = (200 * SZ_1M) ,
        },
        [3] = {
                .name   = "User",
                .offset = MTDPART_OFS_APPEND,
                .size   = MTDPART_SIZ_FULL,
        },
};
NandFlash劃分紅了4MTD分區,其中0~1M之間的空間用來存放Bootloader,也就是u-boot1M~6M之間的空間用來存放linux內核,6M~206M之間的空間用來存放文件系統,剩下的空間提供給用戶使用。
修改完成以後,執行make uImage從新生成內核。將uImage重命名爲zImage,使用TFTP調試內核。
tftp 0x50008000 zImage
bootm 0x50008000
因爲尚未添加NandFlash驅動,因此串口輸出信息暫時沒法看到MTD分區信息。
[url=] 注:本節配套視頻位於光盤中「嵌入式Linux實用教程視頻」目錄下第三章05課(MTD分區)。[/url]
第三章第六節      NAND Flash 驅動移植
    進入正文以前,先跟你們說聲抱歉,因爲年末待處理的事情太多,陸陸續續忙碌了大概一個月,昨天晚上剛剛回到深圳,這段時間,我一會在關注你們的學習進展。廢話咱們很少說,進入主題,go。
   
NAND Flash硬件原理在U-Boot章節已經講得很詳細,這裏就再也不累贅講解。直接進入Linux系統的NAND Flash驅動移植。
NAND Flash驅動不少工做linux內核已經完成,只須要稍做修改儘可以使用。從三星官網,下載s3c_nand.c文件,並將其放入drivers/mtd/nand/中。以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/mtd/nand$ ls s3c*
s3c2410.c  s3c2410.o  s3c_nand.c
須要將其編譯進入內核,那麼理所應當就該修改當前目錄下(drivers/mtd/nand/)的Makefile,以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/mtd/nand$ gedit Makefile
在文件任何一個位置添加以下語句:
obj-$(CONFIG_MTD_NAND_S3C)       += s3c_nand.o
添加NAND Flash的配置選項,這樣在make menuconfig時候即可隨意選擇是否須要NAND Flash驅動。涉及配置選項,須要修改的文件都是Kconfig。打開當前目錄下(drivers/mtd/nand/)的Kconfig,在config MTD_NAND_S3C2410後面添加NandFlash選項,以下所示:
config MTD_NAND_S3C
    tristate "NAND Flash support for S3C SoC"
    depends on (ARCH_S3C64XX || ARCH_S5P64XX || ARCH_S5PC1XX)&& MTD_NAND
    help
      This enables the NAND flash controller on the S3C.
      No board specfic support is done by this driver, eachboard
      must advertise a platform_device for the driver toattach.
 
config MTD_NAND_S3C_DEBUG
    bool "S3C NAND driver debug"
    depends on MTD_NAND_S3C
    help
      Enable debugging of the S3C NAND driver
 
config MTD_NAND_S3C_HWECC
    bool "S3C NAND Hardware ECC"
    depends on MTD_NAND_S3C
    help
      Enable the use of the S3C's internal ECC generatorwhen
      using NAND. Early versions of the chip have hadproblems with
      incorrect ECC generation, and if using these, the defaultof
      software ECC is preferable.
      If you lay down a device with the hardware ECC, thenyou will
      currently not be able to switch to software, as thereis no
      implementation for ECC method used by the S3C
因爲s3c_nand.c文件許多寄存器尚未定義,則須要在arch/arm/plat-samsung/include/plat/regs_nand.h文件中添加相關的寄存器定義,以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/plat-samsung/include/plat$ gedit regs-nand.h
在文件的最後添加以下相關定義:
#if 1
//zzq-> by 2013-3-16
/* for s3c_nand.c */
#define S3C_NFCONF S3C2410_NFREG(0x00)
#define S3C_NFCONT S3C2410_NFREG(0x04)
#define S3C_NFCMMD S3C2410_NFREG(0x08)
#define S3C_NFADDR S3C2410_NFREG(0x0c)
#define S3C_NFDATA8 S3C2410_NFREG(0x10)
#define S3C_NFDATA S3C2410_NFREG(0x10)
#define S3C_NFMECCDATA0 S3C2410_NFREG(0x14)
#define S3C_NFMECCDATA1 S3C2410_NFREG(0x18)
#define S3C_NFSECCDATA S3C2410_NFREG(0x1c)
#define S3C_NFSBLK S3C2410_NFREG(0x20)
#define S3C_NFEBLK S3C2410_NFREG(0x24)
#define S3C_NFSTAT S3C2410_NFREG(0x28)
#define S3C_NFMECCERR0 S3C2410_NFREG(0x2c)
#define S3C_NFMECCERR1 S3C2410_NFREG(0x30)
#define S3C_NFMECC0 S3C2410_NFREG(0x34)
#define S3C_NFMECC1 S3C2410_NFREG(0x38)
#define S3C_NFSECC S3C2410_NFREG(0x3c)
#define S3C_NFMLCBITPT S3C2410_NFREG(0x40)
#define S3C_NF8ECCERR0 S3C2410_NFREG(0x44)
#define S3C_NF8ECCERR1 S3C2410_NFREG(0x48)
#define S3C_NF8ECCERR2 S3C2410_NFREG(0x4c)
#define S3C_NFM8ECC0 S3C2410_NFREG(0x50)
#define S3C_NFM8ECC1 S3C2410_NFREG(0x54)
#define S3C_NFM8ECC2 S3C2410_NFREG(0x58)
#define S3C_NFM8ECC3 S3C2410_NFREG(0x5c)
#define S3C_NFMLC8BITPT0 S3C2410_NFREG(0x60)
#define S3C_NFMLC8BITPT1 S3C2410_NFREG(0x64)
 
#define S3C_NFCONF_NANDBOOT  (1<<31)
#define S3C_NFCONF_ECCCLKCON  (1<<30)
#define S3C_NFCONF_ECC_MLC  (1<<24)
#define S3C_NFCONF_ECC_1BIT  (0<<23)
#define S3C_NFCONF_ECC_4BIT  (2<<23)
#define S3C_NFCONF_ECC_8BIT  (1<<23)
#define S3C_NFCONF_TACLS(x)  ((x)<<12)
#define S3C_NFCONF_TWRPH0(x)  ((x)<<8)
#define S3C_NFCONF_TWRPH1(x)  ((x)<<4)
#define S3C_NFCONF_ADVFLASH  (1<<3)
#define S3C_NFCONF_PAGESIZE  (1<<2)
#define S3C_NFCONF_ADDRCYCLE  (1<<1)
#define S3C_NFCONF_BUSWIDTH  (1<<0)
 
#define S3C_NFCONT_ECC_ENC  (1<<18)
#define S3C_NFCONT_LOCKTGHT  (1<<17)
#define S3C_NFCONT_LOCKSOFT  (1<<16)
#define S3C_NFCONT_8BITSTOP  (1<<11)
#define S3C_NFCONT_MECCLOCK  (1<<7)
#define S3C_NFCONT_SECCLOCK  (1<<6)
#define S3C_NFCONT_INITMECC  (1<<5)
#define S3C_NFCONT_INITSECC  (1<<4)
#define S3C_NFCONT_nFCE1 (1<<2)
#define S3C_NFCONT_nFCE0 (1<<1)
#define S3C_NFCONT_INITECC  (S3C_NFCONT_INITSECC | S3C_NFCONT_INITMECC)
 
#define S3C_NFSTAT_ECCENCDONE  (1<<7)
#define S3C_NFSTAT_ECCDECDONE  (1<<6)
#define S3C_NFSTAT_BUSY (1<<0)
 
#define S3C_NFECCERR0_ECCBUSY  (1<<31)
 
//<-zzq
#endif
在這段宏定義中的#if……#endif,是爲了更好地添加與註釋代碼。
u-boot中的NAND Flash同樣,支持ECC校驗。因爲OK6410開發平臺使用的NAND Flash芯片是:K9GAG08U0D,那麼在nand_base.c文件中添加支持ECC校驗,操做以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/mtd/nand$ gedit nand_base.c
添加以下:
static struct nand_ecclayout nand_oob_218_128Bit = {
    .eccbytes = 104,
    .eccpos = {
        24,25,26,27,28,29,30,31,32,33,
        34,35,36,37,38,39,40,41,42,43,
        44,45,46,47,48,49,50,51,52,53,
        54,55,56,57,58,59,60,61,62,63,
        64,65,66,67,68,69,70,71,72,73,
        74,75,76,77,78,79,80,81,82,83,
        84,85,86,87,88,89,90,91,92,93,
        94,95,96,97,98,99,100,101,102,103,
        104,105,106,107,108,109,110,111,112,113,
        114,115,116,117,118,119,120,121,122,123,
        124,125,126,127},
    .oobfree =
    {
        {
            .offset = 2,
            .length = 22
        }
    }
};
ECC校驗位有104位,即24~127NAND Flash製造商規定0~12位爲檢測壞塊位,2~2322位爲用戶自定義檢測位。
int nand_scan_tail(struct mtd_info *mtd)函數中添加與之相對應的校驗,以下所示:
        case 218:
            chip->ecc.layout = &nand_oob_218_128Bit;
            break;
NAND Flash的驅動代碼添加完成,在linux-3.8.3/arch/arm/mach-s3c64xx添加NAND Flash初始化,以下所示:
static void __init ok6410_machine_init(void)
{
……
s3c_device_nand.name = "s3c6410-nand";
 
    s3c_nand_set_platdata(&ok6410_nand_info);
    s3c_fb_set_platdata(&ok6410_lcd_pdata[features.lcd_index]);
    s3c24xx_ts_set_platdata(NULL);
……
}
回到linux-3.8.3內核的根目錄下,進行內核配置,以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ make menuconfig
內核配置以下所示操做:                                                
Device Drivers -->  
Memory Technology Device (MTD) support --->
NAND Device Support
進入NAND Device Support以後,取消NAND Flash support for Samsung S3C SoCs,選擇 NAND Flash support for S3C SoC配置。以下所示:
<>   NAND Flash support for Samsung S3C SoCs
<*>   NAND Flash support for S3C SoC
  •      S3C NAND driver debug
  •      S3C NAND Hardware ECC  
完成配置以後從新編譯內核,經過TFTP進行調試,串口輸出有一個錯誤,以下:
S3C NAND Driver, (c) 2008 Samsung Electronics
dev_id == 0xd5 select s3c_nand_oob_mlc
****Nandflash:ChipType= MLC  ChipName=samsung-K9GAG08U0D************
S3C NAND Driver is using hardware ECC.
NAND device: Manufacturer ID: 0xec, Chip ID: 0xd5 (Samsung NAND 2GiB 3,3V 8-bit), page size: 4096, OOB size: 218
Driver must set ecc.strength when using hardware ECC
------------[ cut here ]------------
kernel BUG at drivers/mtd/nand/nand_base.c:3382!
Internal error: Oops - BUG: 0 [#1] ARM
從串口錯誤信息很容易能夠知道錯誤的根源在linux3.8.3內核的drivers/mtd/nand/nand_base.c:3382,進入文件,找到根源,以下所示:
if (mtd->writesize >= chip->ecc.size) {
if (!chip->ecc.strength) {
pr_warn("Driver must set ecc.strength when using hardware ECC\n");
BUG();
}
break;
}
由於在內核配置的時候選擇了硬件ECC校驗,在執行上面代碼的時候進入BUG(),而BUG()語句是一個死循環,內核進去以後沒法出來。爲了使調試經過,暫時先註釋掉BUG()語句,使得內核不進入死循環。
從新編譯內核,經過TFTP調試,串口輸出信息爲:
……
****Nandflash:ChipType= MLC  ChipName=samsung-K9GAG08U0D************
S3C NAND Driver is using hardware ECC.
NAND device: Manufacturer ID: 0xec, Chip ID: 0xd5 (Samsung NAND 2GiB 3,3V 8-bit), 2048MiB, page size: 4096, OOB size: 218
Driver must set ecc.strength when using hardware ECC
Creating 4 MTD partitions on "NAND 2GiB 3,3V 8-bit":
0x000000000000-0x000000100000 : "Bootloader"
0x000000100000-0x000000600000 : "Kernel"
0x000000600000-0x00000ce00000 : "File System"
0x00000ce00000-0x000080000000 : "User"
……
從串口的輸出,能夠看到MTD分區信息和NAND Flash的驅動信息,從信息輸出可知NAND Flash移植沒有問題。
第三章第六節   DM9000 網卡驅動
DM9000含有[url=]帶有[/url] 通用處理器接口、10M/100M物理層和16K的SRAM,DM9000詳細介紹和硬件原理[url=]在[/url] U-Boot的DM9000章節。
linux-3.8.3版本已經有DM9000的設備結構,在/linux-3.8.3/arch/arm/mach-s3c64xx/目錄下的mach-ok6410.c文件中,以下所示:
static struct resource ok6410_dm9k_resource[] = {
        [0] = DEFINE_RES_MEM(S3C64XX_PA_XM0CSN1, 2),
        [1] = DEFINE_RES_MEM(S3C64XX_PA_XM0CSN1 + 4, 2),
        [2] = DEFINE_RES_NAMED(S3C_EINT(7), 1, NULL, IORESOURCE_IRQ \
                                        | IORESOURCE_IRQ_HIGHLEVEL),
};
ok6410_dm9k_resource[0]和ok6410_dm9k_resource[1]是訪問DM9000時使用的地址,ok6410_dm9k_resource[2]定義了DM9000使用的中斷號。
static struct dm9000_plat_data ok6410_dm9k_pdata = {
     .flags   = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
};
宏定義在include/linux目錄下的dm9000.h中:
#define DM9000_PLATF_16BITONLY  (0x0002)
……
#define DM9000_PLATF_NO_EEPROM  (0x0010)
這裏定義了訪問DM9000時數據寬度是16位。
static struct platform_device ok6410_device_eth = {
        .name           = "dm9000",
        .id             = -1,
        .num_resources  = ARRAY_SIZE(ok6410_dm9k_resource),
        .resource       = ok6410_dm9k_resource,
        .dev            = {
                .platform_data  = &ok6410_dm9k_pdata,
        },
};
ok6410_device_eth結構體給出了DM9000的相關信息。
Linux-3.8.3版本對DM9000網卡的支持已經至關完美了,只須要在內核配置界面中添加DM9000的相應驅動便可。
第三章第七節   YAFFS2 根文件系統(1)
YAFFS的全稱是:Yet Another Flash File System,是由Aleph One公司所發展出來的NAND Flash 嵌入式文件系統。在YAFFS中,最小存儲單位爲一個page,文件內的數據是存儲在固定512 bytespage中,每個page亦會有一個對應的16 BytesSpare( OOB ,Out-Of-Band)
YAFFS2 Aleph1的工程師Charles Manning 開發的NAND Flash文件系統。YAFFS1YAFFS2 主要差別仍是在於page讀寫 size的大小,YAFFS2可支持到2K per page, 遠高於YAFFS512 Bytes, 所以對大容量NAND Flash更具優點。
 
使Linux-3.8.3內核支持YAFFS2文件系統
在線下載YAFFS2源碼,操做以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ git clone git://www.aleph1.co.uk/yaffs2
若是以前沒有安裝git-core,則會提示先得安裝git-core,安裝操做以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ sudo apt-get install git-core
安裝完成以後再次輸入下載源碼操做,下載過程以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ git clone git://www.aleph1.co.uk/yaffs2
Initialized empty Git repository in /home/zhuzhaoqi/Linux/linux-3.8.3/yaffs2/.git/
remote: Counting objects: 7476, done.
remote: Compressing objects: 100% (4574/4574), done.
remote: Total 7476 (delta 5920), reused 3625 (delta 2818)
Receiving objects: 100% (7476/7476), 3.54 MiB | 5 KiB/s, done.
Resolving deltas: 100% (5920/5920), done.
下載以後,在linux-3.8.3根目錄下會有yaffs2文件夾,以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ ls
arch           drivers   Kbuild       mm              scripts     virt
block          firmware  Kconfig     security    System.map
COPYING        fs        kernel       net             sound      
CREDITS        include   lib          README            yaffs2
crypto         init      MAINTAINERS  REPORTING-BUGS  tools
Documentation  ipc       Makefile     samples         usr
下載完成以後,進入yaffs2目錄下,有一個patch-ker.sh補丁腳本,這個腳本的功能是給內核添加yaffs2文件系統。yaffs2linux-3.8.3內核打好補丁,操做以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/yaffs2$ ./patch-ker.sh c m /home/zhuzhaoqi/Linux/linux-3.8.3
Updating /home/zhuzhaoqi/Linux/linux-3.8.3/fs/Kconfig
Updating /home/zhuzhaoqi/Linux/linux-3.8.3/fs/Makefile
補丁操做完成以後,進入linux-3.8.3/fs目錄下,若是有yaffs2文件夾,就說明yaffs2添加進入linux-3.8.3內核的補丁成功。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/fs$ ls
……   yaffs2   ……
接下來進行yaffs2的內核配置,回到linux-3.8.3的根目錄下,執行內核配置命令:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ make menuconfig
按照以下操做進行配置:
Device Drivers  --->
<*> Memory Technology Device (MTD) support  --->
<*>   Caching block device access to MTD devices
退回到和Device Drivers一個目錄下,完成以下操做配置:
File systems  --->
  • Miscellaneous filesystems  --->
<*>   yaffs2 file system support
此項yaffs2 file system support配置選項選擇」yes」是爲了告訴內核支持yaffs2文件系統。
完成以後從新編譯內核。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ make uImage
scripts/kconfig/conf --silentoldconfig Kconfig
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: include/generated/mach-types.h」是最新的。
  CALL    scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  CC      fs/yaffs2/yaffs_ecc.o
  CC      fs/yaffs2/yaffs_vfs.o
  CC      fs/yaffs2/yaffs_guts.o
  CC      fs/yaffs2/yaffs_checkptrw.o
  CC      fs/yaffs2/yaffs_packedtags1.o
  CC      fs/yaffs2/yaffs_packedtags2.o
  CC      fs/yaffs2/yaffs_nand.o
  CC      fs/yaffs2/yaffs_tagscompat.o
  CC      fs/yaffs2/yaffs_tagsmarshall.o
  CC      fs/yaffs2/yaffs_mtdif.o
  CC      fs/yaffs2/yaffs_nameval.o
  CC      fs/yaffs2/yaffs_attribs.o
  CC      fs/yaffs2/yaffs_allocator.o
  CC      fs/yaffs2/yaffs_yaffs1.o
  CC      fs/yaffs2/yaffs_yaffs2.o
  CC      fs/yaffs2/yaffs_bitmap.o
  CC      fs/yaffs2/yaffs_summary.o
  CC      fs/yaffs2/yaffs_verify.o
  LD      fs/yaffs2/yaffs.o
  LD      fs/yaffs2/built-in.o
  LD      fs/built-in.o
  CC      drivers/mtd/mtd_blkdevs.o
  CC      drivers/mtd/mtdblock.o
  LD      drivers/mtd/built-in.o
  LD      drivers/built-in.o
  LINK    vmlinux
  LD      vmlinux.o
  MODPOST vmlinux.o
  GEN     .version
  CHK     include/generated/compile.h
  UPD     include/generated/compile.h
  CC      init/version.o
  LD      init/built-in.o
  KSYM    .tmp_kallsyms1.o
  KSYM    .tmp_kallsyms2.o
  LD      vmlinux
  SORTEX  vmlinux
sort done marker at 2f3658
  SYSMAP  System.map
  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  GZIP    arch/arm/boot/compressed/piggy.gzip
  AS      arch/arm/boot/compressed/piggy.gzip.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage
  Kernel: arch/arm/boot/zImage is ready
  UIMAGE  arch/arm/boot/uImage
Image Name:   Linux-3.8.3
Created:      Sun Mar 17 10:15:12 2013
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    1723152 Bytes = 1682.77 kB = 1.64 MB
Load Address: 50008000
Entry Point:  50008040
  Image arch/arm/boot/uImage is ready
從輸出信息能夠看到,本次內核將yaffs2相關的一些文件編譯進去了內核。經過TFTP測試,串口信息輸出以下:
……
****Nandflash:ChipType= MLC  ChipName=samsung-K9GAG08U0D************
S3C NAND Driver is using hardware ECC.
NAND device: Manufacturer ID: 0xec, Chip ID: 0xd5 (Samsung NAND 2GiB 3,3V 8-bit), 2048MiB, page size: 4096, OOB size: 218
Driver must set ecc.strength when using hardware ECC
Creating 4 MTD partitions on "NAND 2GiB 3,3V 8-bit":
0x000000000000-0x000000100000 : "Bootloader"
0x000000100000-0x000000600000 : "Kernel"
0x000000600000-0x00000ce00000 : "File System"
0x00000ce00000-0x000080000000 : "User"
……
VFP support v0.3: implementor 41 architecture 1 part 20 variant b rev 5
drivers/rtc/hctosys.c: unable to open rtc device (rtc0)
List of all partitions:
1f00            1024 mtdblock0  (driver?)
1f01            5120 mtdblock1  (driver?)
1f02          204800 mtdblock2  (driver?)
1f03         1886208 mtdblock3  (driver?)
No filesystem could mount root, tried:  cramfs
Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(31,2)
……
從串口的輸出信息能夠知道內核已經支持yaffs2文件系統,下節將詳細講解制做根文件系統。
第三章第八節  LCD驅動移植
    OK6410的標準配置LCD型號爲WXCAT434.3寸電阻觸摸屏,分辨率爲480*272WXCAT43型號LCD屬於TFT,即薄膜晶體管。WXCAT43的屏幕響應時間小於80ms,其畫面色彩飽和度、真實效果和對比度都有較高優點,普遍應用與手機、MP4等移動產品中。
3.  10  LCD模塊接口原理圖
 

OK6410所標配的LCD已經集成爲一個模塊,實現了WXCAT43與單板之間接口的轉換。單板上與WXCAT43型號LCD模塊接口如圖3. 10所示。
一、2號引腳爲3.3V電源。
3~10號這8個引腳爲RED數據線;12~19號這8個引腳爲GREEN數據線;21~28號這8號引腳爲BLUE數據線。
目前大多數LCD顯示器都是採用RGB顏色模式,LCD屏幕上的全部顏色,都由這紅色、綠色、藍色三種色光按照不一樣的比例混合而成的。一組紅綠藍顏色就是一個最小的顯示單位。屏幕上的任何一個顏色均可以由一組RGB值來記錄和表達。前面已經提到WXCAT43的分辨率爲480*272,那也就是屏幕每一行有480個像素點、每一列有272個像素點,也就是說整個屏幕的像素點有130560。那麼這裏的每個像素點都是由一組RGB值。
RED的值用從0到255表示紅色由淺到深,同理GREEN和BLUE也是使用0到255表示綠色和藍色的由淺到深。那麼一組RGB值即由(R,G,B)構成。如單板給3~10的值爲1111 1111,則此刻RED的值255。GREEN和BLUE同理。
3一、32號引腳爲I2C總線控制。
3三、3四、3五、36這四個引腳爲LCD圖像使能頻率等控制。其中LVDEN引腳爲像素點使能,像素點是否顯示在屏幕上在於這個引腳是否使能;LVSYNC引腳爲垂直同步信號,該信號有效一次,則刷新一幀像素點;LHSYNC引腳爲行同步信號,該信號有效一次,刷新一行像素點;LVCLK爲刷新頻率。
3七、3八、3九、40號這四個引腳是「四線觸摸」控制信號線,都是ADC採樣。電阻式觸摸屏傳感器將矩形區域中觸摸點(X,Y)的物理位置轉換爲表明X座標和Y座標的電壓。電阻式觸摸屏能夠用四線產生屏幕偏置電壓,同時讀回觸摸點的電壓用以肯定觸摸點位置。
初步瞭解LCD原理以後,進入LCD驅動移植階段,移植分爲顯示和觸摸兩部分。
1.1.1   LCD顯示驅動
Linux內核中已經有相關的LCD驅動程序。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx$ gedit mach-ok6410.c
mach-ok6410.c文件中有:
static struct s3c_fb_pd_win ok6410_lcd_type0_fb_win = {
    .max_bpp    = 32,
    .default_bpp    = 16,
    .xres       = 480,
    .yres       = 272,
};
 
static struct fb_videomode ok6410_lcd_type0_timing = {
    /* 4.3" 480x272 */
    .left_margin    = 3,
    .right_margin   = 2,
    .upper_margin   = 1,
    .lower_margin   = 1,
    .hsync_len  = 40,
    .vsync_len  = 1,
    .xres       = 480,
    .yres       = 272,
};
標配4.3寸觸摸屏分辨率爲480*272,因此s3c_fb_pd_win ok6410_lcd_type0_fb_win結構體參數無需修改。
fb_videomode ok6410_lcd_type0_timing結構體中的相關參數聲明以下:
__u32 pixclock;       /*像素時鐘*/
__u32 left_margin;   /*行切換,從同步到繪圖之間的延遲*/
__u32 right_margin;  /*行切換,從繪圖到同步之間的延遲*/
__u32 upper_margin;  /*幀切換,從同步到繪圖之間的延遲*/
__u32 lower_margin;  /*幀切換,從繪圖到同步之間的延遲*/
__u32 hsync_len;      /*水平同步的長度*/
__u32 vsync_len;      /*垂直同步的長度*/
HBP(horizontal back porch):表示從水平同步信號開始到一行的有效數據開始之間的VCLK的個數,對應驅動中的left_margin
HFP(horizontal front porth):表示一行的有效數據結束到下一個水平同步信號開始之間的VCLK的個數,對應驅動中的right_margin
VBP(vertical back porch):表示在一幀圖像開始時,垂直同步信號之後的無效的行數,對應驅動中的upper_margin
VFB(vertical front porch):表示在一幀圖像結束後,垂直同步信號之前的無效的行數,對應驅動中的lower_margin
HSPW(horizontal sync pulse width):表示水平同步信號的寬度,用VCLK計算,對應驅動中的hsync_len
VSPW(vertical sync pulse width):表示垂直同步脈衝的寬度,用行數計算,對應驅動中的vsync_len
因爲啓動OK6410開發平臺會發現,LCD屏幕畫面向上移動了。
咱們先分析下LCD常見的幾種問題有:
1)        刷新頻率不正常
現象:屏幕象流動瀑布同樣有明顯向下刷新的光條。
緣由:LCD的時鐘頻率設置不對。
2)        總體顏色出現反色
現象:本來應該爲紅色卻變成藍色、本來爲藍色卻變成紅色。
緣由:三原色RGB數據中RGB排列順序有誤。
3)        圖象總體水平偏移
現象:LCD畫面總體左右偏移,與下一個圖像之間出現一個黑色豎條紋。
緣由:行同步信號的前間,後間等時序參數不對,須要調整。
4)        圖象總體上下偏移   
現象:LCD畫面總體上下偏移,與下一個圖像之間出現一個黑色橫條紋。
緣由:幀同步信號的前間,後間等時序參數不對,須要調整。
5)        圖像只顯示在上半部分
現象:LCD畫面只顯示上半部分,可是下半部分未顯示。
緣由:顯示緩衝區長度設置有誤,形成只顯示部分數據。
6)        顯示緩衝區數據錯誤
現象:LCD屏幕出現隨機的豎條紋。
緣由:顯示緩衝區是亂碼、或者顯存數據沒有及時更新。
fb_videomode ok6410_lcd_type0_timing結構體成員進行修改,以下所示:
static struct fb_videomode ok6410_lcd_type0_timing = {
        /* 4.3" 480x272 */
#if 0
        .left_margin    = 3,
        .right_margin   = 2,
        .upper_margin   = 1,
        .lower_margin   = 1,
        .hsync_len      = 40,
        .vsync_len      = 1,
        .xres           = 480,
        .yres           = 272,
#endif
 
        .left_margin    = 3,
        .right_margin   = 5,
        .upper_margin   = 3,
        .lower_margin   = 3,
        .hsync_len      = 42,
        .vsync_len      = 12,
        .xres           = 480,
        .yres           = 272,
 
};
一樣在mach-ok6410.c添加支持LCD驅動結構體,以下:
static struct map_desc ok6410_iodesc[] = {
        {
                /* LCD support */
                .virtual    = (unsigned long)S3C_VA_LCD,
                .pfn        = __phys_to_pfn(S3C_PA_FB),
                .length     = SZ_16K,
                .type       = MT_DEVICE,
        },
};
因爲S3C_VA_LCD沒有宏定義,在arch/arm/plat-samsung/include/plat/map-base.h中添加:
#define S3C_VA_LCD S3C_ADDR(0x01100000)     /* LCD */
mach-ok6410.cstatic void __init ok6410_map_io(void)函數中添加LCD初始化:
static void __init ok6410_map_io(void)
{
……
#if 0
        s3c64xx_init_io(NULL, 0);
#endif
        s3c64xx_init_io(ok6410_iodesc, ARRAY_SIZE(ok6410_iodesc));
……
}
/linux-3.8.3/drivers/video/目錄下添加samsung文件夾,samsung目錄下有:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/video/samsung$ ls
built-in.o     s3cfb_AT056.o  s3cfb_fimd4x.c  s3cfb.o         s3cfb_VGA800.o   s3cfb_WXCAT43.o
Kconfig        s3cfb_AT070.c  s3cfb_fimd4x.o  s3cfb_spi.c     s3cfb_WXCAT35.c  s3cfb_XGA1024.c
Makefile       s3cfb_AT070.o  s3cfb_fimd5x.c  s3cfb_spi.o     s3cfb_WXCAT35.o  s3cfb_XGA1024.o
s3cfb_AT056.c  s3cfb.c        s3cfb.h         s3cfb_VGA800.c  s3cfb_WXCAT43.c
regs-lcd.hregs-fb.h拷貝到/linux-3.8.3/arch/arm/mach-s3c64xx/include/mach目錄下,以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx/include/mach$ ls regs-fb.h regs-lcd.h
regs-fb.h  regs-lcd.h
將regs-fb-v4.h、regs-fb.h文件拷貝到/linux-3.8.3/arch/arm/plat-samsung/include/plat目錄下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/plat-samsung/include/plat$ lsregs-fb-v4.h
regs-fb-v4.h
在/linux-3.8.3/drivers/video/目錄下的Kconfig添加:
source "drivers/video/samsung/Kconfig"
而且在/linux-3.8.3/drivers/video/目錄下的添加:
obj-$(CONFIG_FB_S3C_EXT) += samsung/
若是此時編譯的話會,編譯信息輸出會提示drivers/video/samsung/s3cfb_fimd4x.c中的s3c6410_pm_do_save函數和s3c6410_pm_do_restore函數隱式聲明。做以下修改:
int s3cfb_suspend(struct platform_device *dev, pm_message_t state)
{
……
#if 0
s3c6410_pm_do_save(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save));
#endif
s3c_pm_do_save(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save));
……
}
int s3cfb_resume(struct platform_device *dev)
{
……
#if 0
s3c6410_pm_do_restore(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save));
#endif
s3c_pm_do_restore(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save));
……
}
修改完成以後執行make menuconfig,進行LCD顯示內核配置。
Device Drivers  --->
Graphics support  --->
<*> Support for frame buffer devices  --->
<>   Samsung S3C framebuffer support
 
Device Drivers  --->
Graphics support  --->
<*> S3C Framebuffer Support (eXtended)
Select LCD Type (4.3 inch 480x272 TFT LCD)  --->
(X) 4.3 inch 480x272 TFT LCD
 
Device Drivers  --->
Graphics support  --->
<*>   Advanced options for S3C Framebuffer
Select BPP(Bits Per Pixel) (16 BPP)  --->
(X) 16 BPP
 
Device Drivers  --->
Graphics support  --->
<*>   Advanced options for S3C Framebuffer
  •      Enable Double Buffering
 
Device Drivers  --->
Graphics support  --->
Console display driver support  --->
<*> Framebuffer Console support
[]   Map the console to the primary display device
這樣已經將LCD屏幕的顯示移植完成,接下須要完成LCD屏幕的觸摸移植。
第三章第九節   LCD觸摸移植
打開mach-ok6410.c:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx$ vim mach-ok6410.c
在mach-ok6410.c中添加ts.h頭文件:
#include <mach/ts.h>
將dev-ts.c文件拷貝至/linux-3.8.3/arch/arm/mach-s3c64xx/目錄下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx$ ll dev-ts.c
-rwxr-xr-x 1 zhuzhaoqi zhuzhaoqi 1552 2013-04-14 00:14 dev-ts.c*
同時在dev-ts.c文件中添加:
#include <linux/gfp.h>
將ts.h文件拷貝至/linux-3.8.3/arch/arm/mach-s3c64xx/include/mach目錄下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/mach-s3c64xx/include/mach$ ll ts.h
-rwxr-xr-x 1 zhuzhaoqi zhuzhaoqi 916 2013-04-14 00:15 ts.h*
在/linux-3.8.3/arch/arm/mach-s3c64xx/目錄下的Makefile文件中添加:
obj-$(CONFIG_TOUCHSCREEN_S3C)           += dev-ts.o
進入mach-ok6410.c文件添加觸摸初始化數據:
static struct s3c_ts_mach_info s3c_ts_platform __initdata = {
    .delay          = 10000,
    .presc          = 49,
    .oversampling_shift = 2,
    .resol_bit      = 12,
    .s3c_adc_con        = ADC_TYPE_2,
};
而且在mach-ok6410.c文件中添加初始化函數:
static void __init ok6410_machine_init(void)
{
……
#if 0
        s3c24xx_ts_set_platdata(NULL);
#endif
        s3c_ts_set_platdata(&s3c_ts_platform);
……
}
將s3c-ts.c文件拷貝至/linux-3.8.3/drivers/input/touchscreen/目錄下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/input/touchscreen$ ls s3c-ts.c
s3c-ts.c
在/linux-3.8.3/drivers/input/touchscreen/目錄下的Makefile添加:
obj-$(CONFIG_TOUCHSCREEN_S3C)           += s3c-ts.o
/linux-3.8.3/arch/arm/plat-samsung/include/plat/目錄下添加s3c-ts.c文件所需的ADC部分控制寄存器的宏定義:
/*----------------- Common definitions for S3C  ----------------*/
/* The following definitions will be applied to S3C24XX, S3C64XX, S5PC1XX.           
*/
/*-------------------------------------------------------------*/
 
#define S3C_ADCREG(x)                   (x)
 
#define S3C_ADCCON                      S3C_ADCREG(0x00)
#define S3C_ADCTSC                      S3C_ADCREG(0x04)
#define S3C_ADCDLY                      S3C_ADCREG(0x08)
#define S3C_ADCDAT0                     S3C_ADCREG(0x0C)
#define S3C_ADCDAT1                     S3C_ADCREG(0x10)
#define S3C_ADCUPDN                     S3C_ADCREG(0x14)
#define S3C_ADCCLRINT                   S3C_ADCREG(0x18)
#define S3C_ADCMUX                      S3C_ADCREG(0x1C)
#define S3C_ADCCLRWK                    S3C_ADCREG(0x20)
 
/* ADCCON Register Bits */
#define S3C_ADCCON_RESSEL_10BIT         (0x0<<16)
#define S3C_ADCCON_RESSEL_12BIT         (0x1<<16)
#define S3C_ADCCON_ECFLG                (1<<15)
#define S3C_ADCCON_PRSCEN               (1<<14)
#define S3C_ADCCON_PRSCVL(x)            (((x)&0xFF)<<6)
#define S3C_ADCCON_PRSCVLMASK           (0xFF<<6)
#define S3C_ADCCON_SELMUX(x)            (((x)&0x7)<<3)
#define S3C_ADCCON_SELMUX_1(x)          (((x)&0xF)<<0)
#define S3C_ADCCON_MUXMASK              (0x7<<3)
#define S3C_ADCCON_RESSEL_10BIT_1       (0x0<<3)
#define S3C_ADCCON_RESSEL_12BIT_1       (0x1<<3)
#define S3C_ADCCON_STDBM                (1<<2)
#define S3C_ADCCON_READ_START           (1<<1)
#define S3C_ADCCON_ENABLE_START         (1<<0)
#define S3C_ADCCON_STARTMASK            (0x3<<0)
 
/* ADCTSC Register Bits */
#define S3C_ADCTSC_UD_SEN               (1<<8)
#define S3C_ADCTSC_YM_SEN               (1<<7)
#define S3C_ADCTSC_YP_SEN               (1<<6)
#define S3C_ADCTSC_XM_SEN               (1<<5)
#define S3C_ADCTSC_XP_SEN               (1<<4)
#define S3C_ADCTSC_PULL_UP_DISABLE      (1<<3)
#define S3C_ADCTSC_AUTO_PST             (1<<2)
#define S3C_ADCTSC_XY_PST(x)            (((x)&0x3)<<0)
 
/* ADCDAT0 Bits */
#define S3C_ADCDAT0_UPDOWN              (1<<15)
#define S3C_ADCDAT0_AUTO_PST            (1<<14)
#define S3C_ADCDAT0_XY_PST              (0x3<<12)
#define S3C_ADCDAT0_XPDATA_MASK         (0x03FF)
#define S3C_ADCDAT0_XPDATA_MASK_12BIT   (0x0FFF)
 
/* ADCDAT1 Bits */
#define S3C_ADCDAT1_UPDOWN              (1<<15)
#define S3C_ADCDAT1_AUTO_PST            (1<<14)
#define S3C_ADCDAT1_XY_PST              (0x3<<12)
#define S3C_ADCDAT1_YPDATA_MASK         (0x03FF)
#define S3C_ADCDAT1_YPDATA_MASK_12BIT   (0x0FFF)
在/Linux/linux-3.8.3/include/linux目錄下的interrupt.h文件添加:
#define IRQF_SAMPLE_RANDOM      0x00000040
並在/linux-3.8.3/drivers/input/touchscreen/目錄下的Kconfig添加LCD觸摸配置:
config TOUCHSCREEN_S3C
        tristate "S3C touchscreen driver"
        depends on ARCH_S3C2410 || ARCH_S3C64XX || ARCH_S5P64XX || ARCH_S5PC1XX
        default y
        help
          Say Y here to enable the driver for the touchscreen on the
          S3C SMDK board.
 
          If unsure, say N.
 
          To compile this driver as a module, choose M here: the
          module will be called s3c_ts.
修改完成,執行make menuconfig,進行LCD觸摸配置:
Device Drivers  --->
Input device support  --->
  •    Touchscreens  --->
<*>   S3C touchscreen driver
 
System Type  --->
  • ADC common driver support
 
Device Drivers  --->
Input device support  --->
<*>   Event interface
 
LCD的顯示和觸摸配置完成以後執行make uImage命令:
……
  LD      vmlinux.o
arch/arm/plat-samsung/built-in.o:(.data+0x878): multiple definition of `s3c_device_ts'
arch/arm/mach-s3c64xx/built-in.o:(.data+0x34e0): first defined here
……
出現多重定義錯誤,則在/linux-3.8.3/arch/arm/plat-samsung目錄下的devs.c註釋掉s3c_device_ts便可:
//--->zzq
#undef CONFIG_SAMSUNG_DEV_TS
//<---zzq
#ifdef CONFIG_SAMSUNG_DEV_TS
若是還有錯誤,則可根據錯誤追溯源頭進行修改。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ make uImage
……
Image Name:   Linux-3.8.3
Created:      Sun Apr 14 01:47:28 2013
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    2156336 Bytes = 2105.80 kB = 2.06 MB
Load Address: 50008000
Entry Point:  50008040
  Image arch/arm/boot/uImage is ready
將生成的uImage拷貝至tftp目錄下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/arch/arm/boot$ cp uImage /tftpboot/
使用tftp進行燒寫、調試內核。
zhuzhaoqi@zhuzhaoqi-desktop:/tftpboot$ ls
uImage
zhuzhaoqi@zhuzhaoqi-desktop:/tftpboot$ tftp
tftp>
啓動ok6410開發平臺,停在U-Boot燒寫內核:
zzq6410 >>> tftp 0x50008000 uImage
dm9000 i/o: 0x18000300, id: 0x90000a46
DM9000: running in 16 bit mode
MAC: 00:40:5c:26:0a:5b
operating at 100M full duplex mode
Using dm9000 device
TFTP from server 192.168.1.187; our IP address is 192.168.1.100
Filename 'uImage'.
Load address: 0x50008000
Loading: ######################################################################################################################################################################################################################################################################################################################################################################################################################################
done
Bytes transferred = 2156400 (20e770 hex)
燒寫完成以後啓動內核:
zzq6410 >>>bootm 0x50008000
## Booting kernel from Legacy Image at 50008000 ...
   Image Name:   Linux-3.8.3
   Image Type:   ARM Linux Kernel Image (uncompressed)
   Data Size:    2156336 Bytes = 2.1 MiB
   Load Address: 50008000
   Entry Point:  50008040
   Verifying Checksum ... OK
   XIP Kernel Image ... OK
OK
Starting kernel ...
 
Starting kernel ...
Uncompressing Linux... done, booting the kernel.
……
S3C_LCD clock got enabled :: 133.250 Mhz
LCD TYPE :: LTE480WV will be initialized
……
S3C Touchscreen driver, (c) 2008 Samsung Electronics
S3C TouchScreen got loaded successfully : 12 bits
input: S3C TouchScreen as /devices/virtual/input/input0
……
從串口的信息輸出可知LCD的顯示和觸摸驅動成功。

 

第四章第二節  字符設備驅動

 

Linux操做系統將全部的設備都會當作是文件,所以當咱們須要訪問設備時,都是經過操做文件的方式進行訪問。對字符設備的讀寫是以字節爲單位進行的。
對字符設備驅動程序的學習過程,主要以兩個具備表明性且在OK6410開發平臺可實踐性的字符驅動展開分析,分別爲:LED驅動程序、ADC驅動程序。
1.1.1   LED 驅動程序設計
爲了展示LED的裸板程序和基於Linux系統的LED驅動程序區別與減小難度梯度,在寫LED驅動程序以前頗有必要先看一下LED的裸板程序是怎樣設計的。
1.  LED 裸板程序
                                                                     
  
圖4. 1  LED原理圖

OK6410開發平臺中有4個LED燈,原理圖如圖4. 1所示。
從圖4. 1中可知,4個LED是共陽鏈接,GPM0~GPM3分別控制着LED1~LED4。而GPMCON寄存器地址爲:0x7F008820;GPMDAT寄存器地址爲:0x7F008824。那麼GPM中3個寄存器宏定義爲:
/*===============================================================
**   基地址的定義
===============================================================*/
#define AHB_BASE        (0x7F000000)
/****************************************************************
**  GPX 的地址定義
****************************************************************/
#define GPX_BASE        (AHB_BASE+0x08000)
……
/****************************************************************
**      GPM 寄存器地址定義
****************************************************************/
#define GPMCON      (*(volatile unsigned long *)(GPX_BASE +0x0820))
#define GPMDAT      (*(volatile unsigned long *)(GPX_BASE +0x0824))
#define GPMPUD      (*(volatile unsigned long *)(GPX_BASE +0x0828))
將GPM0~GPM3設置爲輸出功能:
/* GPM0,1,2,3 設爲輸出引腳 */
/*
**   每個GPXCON 的引腳有 4 位二進制進行控制
**  0000- 輸入    0001- 輸出
*/
GPMCON = 0x1111;
點亮LED1,則是讓GPM3~GPM0輸出:1110。
GPMDAT = 0x0e;
點亮LED3,則是讓GPM3~GPM0輸出:1011。
GPMDAT = 0x0b;
 
2.  LED 驅動程序
有了LED裸板程序的基礎,移植到Linux系統LED驅動設備程序,難度也不會很大了。可是在Linux中,特別注意《s3c6410用戶手冊》提供的GPM寄存器地址不能直接用於Linux中。
     
  
4. 2 Linux 內存空間

通常狀況下,Linux系統中,進程的4GB( )內存空間被劃分紅爲兩個部分:用戶空間(3G)和內核空間(1G),大小分別爲0~3G和3~4G。如圖4. 2所示。
3~4G之間的內核空間中,從低地址到高地址依次爲:物理內存映射區、隔離帶、vmalloc虛擬內存分配區、隔離帶、高端內存映射區、專用頁面映射區。
用戶進程一般狀況下,只能訪問用戶空間的虛擬地址,不能訪問到內核空間。
每一個進程的用戶空間都是徹底獨立、互不相干的,用戶進程各自有不一樣的頁表。而內核空間是由內核負責映射,它並不會跟着進程改變,是固定的。內核空間地址有本身對應的頁表,內核的虛擬空間獨立於其餘程序。
在內核中,訪問IO內存以前,咱們只有IO內存的物理地址,這樣是沒法經過軟件直接訪問的,須要首先用ioremap()函數將設備所處的物理地址映射到內核虛擬地址空間(3GB~4GB)。而後,才能根據映射所獲得的內核虛擬地址範圍,經過訪問指令訪問這些IO內存資源。
通常來講,在系統運行時,外設的I/O內存資源的物理地址是已知的,由硬件的設計決定。可是CPU一般並無爲這些已知的外設I/O內存資源的物理地址預約義虛擬地址範圍,驅動程序並不能直接經過物理地址訪問I/O內存資源,而必須將它們映射到核心虛地址空間內(經過頁表),而後才能根據映射所獲得的核心虛地址範圍,經過訪內指令訪問這些I/O內存資源。Linux在io.h頭文件中聲明瞭函數ioremap(),用來將I/O內存資源的物理地址映射到核心虛地址空間(3GB-4GB)中,以下所示:
void * ioremap(unsigned long phys_addr, unsigned longsize,
unsignedlong flags);
iounmap函數用於取消ioremap()所作的映射,以下所示:
void iounmap(void *addr);
到這裏應該明白,像GPMCON(0x7F008820         )這個物理地址是不能直接操控的,必須經過映射到內核的虛擬地址中,才能進行操做。
如今開始設計第一個LED驅動程序。
字符驅動程序所要包含的頭文件主要位於include/linux及/arch/arm/mach-s3c64xx /include/mach目錄下,以下LED驅動程序所包含的頭文件:
/*
*  head file
*/
//moudle.h  包含了大量加載模塊須要的函數和符號的定義
#include <linux/module.h>
//kernel.h 以便使用printk() 等函數
#include <linux/kernel.h>
//fs.h 包含經常使用的數據結構,如struct file
#include <linux/fs.h>
//uaccess.h  包含copy_to_user() copy_from_user() 等函數
#include <linux/uaccess.h>
//io.h  包含inl() outl() readl() writel() IO 操做函數
#include <linux/io.h>
#include <linux/miscdevice.h>
#include <linux/pci.h>
//init.h 來指定你的初始化和清理函數,例如:module_init(init_function) module_exit(cleanup_function)
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
 
//irq.h 中斷與併發請求事件
#include <asm/irq.h>
// 下面這些頭文件是IO 口在內核的虛擬映射地址,涉及IO 口的操做所必須包含
//#include <mach/gpio.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/hardware.h>
#include <mach/map.h>
上面所列出的頭文件便是本次LED驅動程序說須要包含的頭文件。
#define DEVICE_NAME    "led"
#define LED_MAJOR        240                    /* 主設備號*/
這是LED驅動程序的驅動名稱和主設備號。
設備節點位於/dev目錄下,以下所示,例舉出了ubuntu系統/dev/vcs*的設備節點:
zhuzhaoqi@zhuzhaoqi-desktop:~$ ls -l /dev/vcs*
……
crw-rw---- 1 root tty 7,   7 2013-04-09 20:56 /dev/vcs7
crw-rw---- 1 root tty 7, 128 2013-04-09 20:56/dev/vcsa
……
/dev/vcs7設備節點的主設備號爲:7,次設備號爲:7;/dev/vcsa設備節點的主設備號爲:7,次設備號爲:128。
#define LED_ON           0
#define LED_OFF         1
這是LED燈打開或者關閉的宏定義,因爲OK6410開發平臺的4個LED是共陽鏈接,因此輸出1即爲熄滅LED,輸出0爲點亮LED。
字符驅動程序中實現了open、close、read、write等系統調用。
open函數指針的聲明位於fs.h的file_operations結構體中,以下所示:
struct file_operations {
     ……
     int (*open) (struct inode *, struct file *);
     ……
};
在open函數指針的回調函數led_open()完成的任務是設置GPM的輸出模式。
static int led_open(struct inode *inode,struct file*file)
{
    unsigned inti;
    /* 設置GPM0~GPM3 爲輸出模式*/
    for (i = 0;i < 4; i++)
    {
       s3c_gpio_cfgpin(S3C64XX_GPM(i),S3C_GPIO_OUTPUT);
printk("TheGPMCON %x is %x \n",i,s3c_gpio_getcfg(S3C64XX_GPM(i)) );
    }
   printk("Led open... \n");
    return 0;
}
s3c_gpio_cfgpin()函數原型位於gpio-cfg.h中,以下:
extern int s3c_gpio_cfgpin(unsigned int pin, unsignedint to);
內核對這個函數是這樣註釋的:s3c_gpio_cfgpin()函數用於改變引腳的GPIO功能。參數pin是GPIO的引腳名稱,參數to是須要將GPIO這個引腳設置成爲的功能。
GPIO的名稱在arch/arm/mach-s3c6400/include/mach/gpio.h進行了宏定義:
/* S3C64XX GPIO number definitions. */
 
#define S3C64XX_GPA(_nr)    (S3C64XX_GPIO_A_START + (_nr))
#define S3C64XX_GPB(_nr)    (S3C64XX_GPIO_B_START + (_nr))
#define S3C64XX_GPC(_nr)    (S3C64XX_GPIO_C_START + (_nr))
#define S3C64XX_GPD(_nr)    (S3C64XX_GPIO_D_START + (_nr))
#define S3C64XX_GPE(_nr)    (S3C64XX_GPIO_E_START + (_nr))
#define S3C64XX_GPF(_nr)    (S3C64XX_GPIO_F_START + (_nr))
#define S3C64XX_GPG(_nr)    (S3C64XX_GPIO_G_START + (_nr))
#define S3C64XX_GPH(_nr)    (S3C64XX_GPIO_H_START + (_nr))
#define S3C64XX_GPI(_nr)    (S3C64XX_GPIO_I_START + (_nr))
#define S3C64XX_GPJ(_nr)    (S3C64XX_GPIO_J_START + (_nr))
#define S3C64XX_GPK(_nr)    (S3C64XX_GPIO_K_START + (_nr))
#define S3C64XX_GPL(_nr)    (S3C64XX_GPIO_L_START + (_nr))
#define S3C64XX_GPM(_nr)    (S3C64XX_GPIO_M_START + (_nr))
#define S3C64XX_GPN(_nr)    (S3C64XX_GPIO_N_START + (_nr))
#define S3C64XX_GPO(_nr)    (S3C64XX_GPIO_O_START + (_nr))
#define S3C64XX_GPP(_nr)    (S3C64XX_GPIO_P_START + (_nr))
#define S3C64XX_GPQ(_nr)    (S3C64XX_GPIO_Q_START + (_nr))
S3C64XX_GPIO_M_START的定義以下:
enum s3c_gpio_number {
    S3C64XX_GPIO_A_START= 0,
    S3C64XX_GPIO_B_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_A),
    S3C64XX_GPIO_C_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_B),
    S3C64XX_GPIO_D_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_C),
    S3C64XX_GPIO_E_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_D),
    S3C64XX_GPIO_F_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_E),
    S3C64XX_GPIO_G_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_F),
    S3C64XX_GPIO_H_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_G),
    S3C64XX_GPIO_I_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_H),
    S3C64XX_GPIO_J_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_I),
    S3C64XX_GPIO_K_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_J),
    S3C64XX_GPIO_L_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_K),
     S3C64XX_GPIO_M_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_L),
    S3C64XX_GPIO_N_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_M),
    S3C64XX_GPIO_O_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_N),
    S3C64XX_GPIO_P_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_O),
    S3C64XX_GPIO_Q_START= S3C64XX_GPIO_NEXT(S3C64XX_GPIO_P),
};
S3C64XX_GPIO_NEXT的定義:
#define S3C64XX_GPIO_NEXT(__gpio) \
    ((__gpio##_START)+ (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
宏定義一層一層不少,可是經過這個設置,能夠很方便得選擇想要的任何一個GPIO口進行操做。
GPIO功能設置位於在gpio-cfg.h中:
#define S3C_GPIO_SPECIAL_MARK   (0xfffffff0)
#define S3C_GPIO_SPECIAL(x) (S3C_GPIO_SPECIAL_MARK | (x))
 
/* Defines for generic pin configurations */
#define S3C_GPIO_INPUT  (S3C_GPIO_SPECIAL(0))
#define S3C_GPIO_OUTPUT (S3C_GPIO_SPECIAL(1))
#define S3C_GPIO_SFN(x) (S3C_GPIO_SPECIAL(x))
經過上面宏定義可知,GPIO的引腳功能有輸入、輸出、和你想要的任何能夠實現的功能設置,S3C_GPIO_SFN(x)這個函數便是經過設定x的值,實現任何存在功能的設置。若是要設置GPM0~GPM3爲輸出功能,則:
for (i = 0; i < 4; i++) {
s3c_gpio_cfgpin(S3C64XX_GPM(i),S3C_GPIO_OUTPUT);
}
經過這樣的操做,設置就顯得比較簡潔實用。
s3c_gpio_getcfg(S3C64XX_GPM(i))
這行代碼的做用是獲取GMP(argv)的當前值。這個函數的原型在include/linux/gpio.h中:
static inline void gpio_get_value(unsigned int gpio)
{
    __gpio_get_value(gpio);
}
完成端口模式設定,接下來的程序是完成LED操做。在fs.h的file_operations結構體中,有unlocked_ioctl函數指針的聲明,以下:
struct file_operations {
……
long(*unlocked_ioctl) (struct file *,unsigned int,unsigned long);
……
};
unlocked_ioctl函數指針所要回調的函數led_ioctl()函數便是須要實現應用層對LED1~LED4的控制操做。
static long led_ioctl ( struct file *file, unsignedint cmd, \
unsigned long argv )
{
    if (argv> 4) {
        return-EINVAL;
    }
 
printk("LED ioctl...\n");
 
/*獲取應用層的操做 */
    switch(cmd){
 
/*若是是點亮LED(argv) */
    case LED_ON:
       gpio_set_value(S3C64XX_GPM(argv),0);
       printk("LED ON \n");
   printk("S3C64XX_GPM(i) = %x\n",gpio_get_value(S3C64XX_GPM(argv)) );
        return0;
 
/*若是是熄滅LED(argv) */
    caseLED_OFF:
       gpio_set_value(S3C64XX_GPM(argv),1);
       printk("LED OFF \n");
        printk("S3C64XX_GPM(i) = %x \n",gpio_get_value(S3C64XX_GPM(argv)) );
        return0;
    default:
        return-EINVAL;
    }
}
本函數調用了GPIO端口值設定函數。
gpio_set_value(S3C64XX_GPM(argv),1);
這是設定GMP(argv)輸出爲1。函數的原型位於include/linux/gpio.h中:
static inline void gpio_set_value(unsigned int gpio,int value)
{
    __gpio_set_value(gpio,value);
}
release函數指針所要回調的函數led_release()函數:
static int led_release(struct inode *inode,struct file*file)
{
       printk("zhuzhaoqi >>> s3c6410_led release \n");
        return0;
}
這是驅動程序的核心控制,各個函數指針所對應的回調函數:
struct file_operations led_fops = {
       .owner          = THIS_MODULE,
       .open           = led_open,
       .unlocked_ioctl = led_ioctl,
       .release        = led_release,
};
因爲Linux3.8.3內核中沒有ioctl函數指針,取而代之的是unlocked_ioctl函數指針實現對led_ioctl()函數的回調。
驅動程序的加載分爲靜態加載和動態加載,將驅動程序編譯進內核稱爲靜態加載,將驅動程序編譯成模塊,使用時再加載稱爲動態加載。動態加載模塊的擴展名爲:.ko,使用insmod命令進行加載,使用rmmod命令進行卸載。
static int __init led_init(void)
{
        int rc;
       printk("LEDinit... \n");
        rc =register_chrdev(LED_MAJOR,"led",&led_fops);
 
        if (rc< 0)
        {
               printk("register %s char dev error\n","led");
               return -1;
        }
 
       printk("OK!\n");
        return0;
}
__init修飾詞對內核是一種暗示,代表該初始化函數僅僅在初始化期間使用,在模塊裝載以後,模塊裝載器就會將初始化函數釋放掉,這樣就能將初始化函數所佔用的內存釋放出來以做他用。
當使用insmod命令加載LED驅動模塊時,led_init()初始化函數將被調用,向內核註冊LED驅動程序。
static void __exit led_exit(void)
{
       unregister_chrdev(LED_MAJOR,"led");
       printk("LED exit...\n");
}
 
__exit這個修飾詞告訴內核這個退出函數僅僅用於模塊卸載,而且僅僅能在模塊卸載或者系統關閉時被調用。
當使用rmmod命令卸載LED驅動模塊時,led_exit ()清除函數將被調用,向內核註冊LED驅動程序。
module_init(led_init);
module_exit(led_exit);
module_init和module_exit是強制性使用的,這個宏會在模塊的目標代碼中增長一個特殊的段,用於說明函數所在位置。若是沒有這個宏,則初始化函數和退出函數永遠不會被調用。
MODULE_LICENSE("GPL");
若是沒有聲明LICENSE,模塊被加載時,會給處理內核被污染(kernel taint)的警告。若是在zzq_led.c中沒有許可證(LICENSE),則會給出以下提示:
[YJR@zhuzhaoqi 3.8.3]# insmod zzq_led.ko
zzq_led: module license 'unspecified' taints kernel.
Disabling lock debugging due to kernel taint
Linux遵循GNU通用公共許可證(GPL),GPL是由自由軟件基金會爲GNU項目設計,它容許任何人對其從新發布甚至銷售。
固然,也許程序還會有驅動程序做者和描述信息:
MODULE_AUTHOR("zhuzhaoqi  jxlgzzq@163.com");
MODULE_DESCRIPTION("OK6410(S3C6410) LEDDriver");
完成驅動程序的設計以後,將zzq_led.c驅動程序放置於/drivers/char目錄下,打開Makefile文件:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/char$gedit Makefile
在Makefile中添加LED驅動:
obj-m                           += zzq_led.o
回到內核的根目錄執行make modules命令生成LED驅動模塊:
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3$ makemodules
……
  CC [M]  drivers/char/zzq_led.o
……
編譯完成以後在/drivers/char目錄下會生成zzq_led.ko模塊,將其拷貝到文件系統下面的/lib/modules/3.8.3(若是沒有3.8.3目錄,則創建)目錄下。
加載LED驅動模塊:
[YJR@zhuzhaoqi]\# cd lib/module/3.8.3/
[YJR@zhuzhaoqi]\# ls
zzq_led.ko
[YJR@zhuzhaoqi]\# insmod zzq_led.ko
LED init...
OK!
根據信息輸出可知加載zzq_led.ko驅動模塊成功。經過lsmod查看加載模塊:
[YJR@zhuzhaoqi]\# lsmod
zzq_led 1548 0 - Live 0xbf000000
在/dev目錄下創建設備文件,以下操做:
[YJR@zhuzhaoqi]\# mknod /dev/led c 240 0
是否創建成功,能夠查看/dev下的節點得知:
[YJR@zhuzhaoqi]\# ls /dev/l*
/dev/led          /dev/log          /dev/loop-control
說明LED設備文件已經成功創建。
 
3.  LED 應用程序
驅動程序須要應用程序對其操控。程序以下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
#define  LED_ON     0
#define  LED_OFF    1
 
/*
* LED  操做說明信息輸出
*/
void usage(char *exename)
{
   printf("How to use: \n");
   printf("    %s <LED Number><on/off>\n", exename);
   printf("    LED Number = 1,2, 3 or 4 \n");
}
 
/*  
*  應用程序主函數
*/
int main(int argc, char *argv[])
{
    unsigned intled_number;
 
    if (argc !=3) {
        gotoerr;
    }
 
    int fd =open("/dev/led",2,0777);
    if (fd <0) {
       printf("Can't open /dev/led \n");
        return-1;
    }
   printf("open /dev/led ok ... \n");
 
    led_number =strtoul(argv[1], 0, 0) - 1;
    if(led_number > 3) {
        gotoerr;
}
 
    /* LED ON */
    if(!strcmp(argv[2], "on")) {
       ioctl(fd, LED_ON, led_number);
    }
    /* LED OFF*/
    else if(!strcmp(argv[2], "off")) {
       ioctl(fd, LED_OFF, led_number);
    }
    else {
        gotoerr;
    }
 
    close(fd);
    return 0;
 
err:
    if (fd >0) {
       close(fd);
    }
   usage(argv[0]);
    return -1;
 
}
在main()函數中,涉及到了open()函數,其原型以下:
int open( const char* pathname,int flags, mode_t mode);
固然,不少open函數中的入口參數也是隻有2個,原型以下:
int open( const char* pathname, int flags);
第一個參數pathname是一個指向將要打開的設備文件途徑字符串。
第二個參數flags是打開文件所能使用的旗標,經常使用的幾種旗標有:
O_RDONLY 以只讀方式打開文件
O_WRONLY 以只寫方式打開文件
O_RDWR 以可讀寫方式打開文件。
上述三種經常使用的旗標是互斥使用,但可與其餘的旗標進行或運算符組合。
第三個參數mode是使用該文件的權限。好比777,755等。
經過這個應用程序實現對LED驅動程序的控制,爲了更加方便快捷編譯這個應用程序,爲其寫一個Makefile文件,以下所示:
# 交叉編譯鏈安裝路徑
CC = /usr/local/arm/4.4.1/bin/arm-linux-gcc
 
zzq_led_app:zzq_led_app.o
        $(CC) -ozzq_led_appzzq_led_app.o
 
zzq_led_app.o:zzq_led_app.c
        $(CC) -czzq_led_app.c
 
clean :
        rm zzq_led_app.ozzq_led_app
執行Makefile以後會生成zzq_led_app可執行應用文件,以下:
zhuzhaoqi@zhuzhaoqi-desktop:~/LDD/linux-3.8.3/zzq_led$make
/usr/local/arm/4.4.1/bin/arm-linux-gcc -czzq_led_app.c
/usr/local/arm/4.4.1/bin/arm-linux-gcc -o zzq_led_appzzq_led_app.o
zhuzhaoqi@zhuzhaoqi-desktop:~/LDD/linux-3.8.3/zzq_led$ls
Makefile zzq_led_app  zzq_led_app.c  zzq_led_app.o zzq_led.c
將生成的zzq_led_app可執行應用文件拷貝到根文件系統的/usr/bin目錄下,執行應用文件,以下操做:
[YJR@zhuzhaoqi]\# ./zzq_led_app
How to use:
   ./zzq_led_app <LED Number><on/off>
    LED Number =1, 2, 3 or 4
根據信息提示能夠進行對LED驅動程序的控制,點亮LED1,則以下:
[YJR@zhuzhaoqi]\# ./zzq_led_app 1 on
The GPMCON 0 is fffffff1
The GPMCON 1 is fffffff1
The GPMCON 2 is fffffff1
The GPMCON 3 is fffffff1
zhuzhaoqi >>> LED open...
LED ioctl...
LED ON
S3C64XX_GPM(i) = 0
LED release...
open /dev/led ok ...
此時能夠看到LED1點亮。
注:本節配套視頻位於光盤中「嵌入式Linux實用教程視頻」目錄下第四章01課(字符設備驅動之LED)。
第四章第三節 ADC驅動程序設計

    A/D轉換便是將模擬量轉換爲數字量,在物聯網迅速發展的今天,做爲物聯網的感知前端傳感器也隨之迅速更新,壓力、溫度、溼度等衆多模擬信號的處理都須要涉及到A/D轉換,所以A/D驅動程序學習在嵌入式佔據着重要地位。
1.        S3C6410的ADC控制寄存器簡介
S3C6410控制芯片自帶有4路獨立專用A/D轉換通道,如圖4. 3所示。

圖4. 3A/D轉換鏈接圖
透過三星公司提供的《s3c6410用戶手冊》可知,ADCCON爲ADC控制寄存器,地址爲:0x7E00 B0000。ADCCON的復位值爲:0x3FC4,即爲:0011  1111  1100  0100。
#define S3C_ADCREG(x)                         (x)
#define S3C_ADCCON                        S3C_ADCREG(0x00)
ADCCON控制寄存器具備16位,每一位都能經過賦值來實現其相對應的功能。
ADCCON[0]:ENABLE_START,A/D 轉換開始啓用。若是READ_START 啓用,這個值是無效的。ENABLE_START = 0,無行動;ENABLE_START = 1,A/D 轉換開始和該位被清理後開啓。ADCCON[0]的復位值爲0,即復位以後默認爲無行動。
#define S3C_ADCCON_NO_ENABLE_START                (0<<0)
#define S3C_ADCCON_ENABLE_START                (1<<0)
ADCCON[1]:READ_START,A/D 轉換開始讀取。READ_START = 0,禁用開始讀操做;READ_START = 1,啓動開始讀操做。ADCCON[1]的復位值爲0,禁用開始讀操做。
#define S3C_ADCCON_NO_READ_START                (0<<1)
#define S3C_ADCCON_READ_START                (1<<1)
ADCCON[2]:STDBM,待機模式選擇。STDBM = 0,正常運做模式;STDBM = 1,待機模式。ADCCON[2]的復位值爲1,待機模式。
#define S3C_ADCCON_RUN                (0<<2)
#define S3C_ADCCON_STDBM                (1<<2)
ADCCON[5:3]:SEL_MUX,模擬輸入通道選擇。SEL_MUX = 000,AIN0;SEL_MUX = 001,AIN1;SEL_MUX = 010,AIN2;SEL_MUX = 011,AIN3;SEL_MUX = 100,YM;SEL_MUX = 101,YP;SEL_MUX = 110,XM;SEL_MUX = 111,XP。ADCCON[5:3]的復位值爲000,選用AIN0通道。
#define S3C_ADCCON_RESSEL_10BIT_1        (0x0<<3)
#define S3C_ADCCON_RESSEL_12BIT_1        (0x1<<3)
#define S3C_ADCCON_MUXMASK                (0x7<<3)
#define S3C_ADCCON_SELMUX(x)                (((x)&0x7)<<3) //任意通道的選擇
ADCCON[13:6]:PRSCVL,ADC 預約標器值0xFF。數據值:5~255。ADCCON[13:6]的復位值爲1111 1111,即爲0xFF。
#define S3C_ADCCON_PRSCVL(x)                (((x)&0xFF)<<6) // 任意值設定
#define S3C_ADCCON_PRSCVLMASK                (0xFF<<6)  //復位值
ADCCON[14]:PRSCEN,ADC預約標器啓動。PRSCEN = 0,禁用;PRSCEN = 0,啓用。ADCCON[14]的復位值爲0,禁用ADC預約標器。
#define S3C_ADCCON_NO_PRSCEN                (0<<14)
#define S3C_ADCCON_PRSCEN                (1<<14)
ADCCON[15]:ECFLG,轉換的結束標記(只讀)。ECFLG = 0,A/D 轉換的過程當中;ECFLG = 1,A/D 轉換結束。ADCCON[15]的復位值爲0,A/D 轉換的過程當中。
#define S3C_ADCCON_ECFLG_ING                (0<<15)
#define S3C_ADCCON_ECFLG                (1<<15)
ADCDAT0寄存器爲ADC 的數據轉換寄存器。地址爲:0x7E00B00C。
ADCDAT0[9:0]:XPDATA,X 座標的數據轉換(包括正常的ADC 的轉換數據值)。數據值: 0x000~0x3FF。
ADCDAT0[11:10]:保留。當啓用12位AD時做爲轉換數據值使用。
#define S3C_ADCDAT0_XPDATA_MASK                (0x03FF)
#define S3C_ADCDAT0_XPDATA_MASK_12BIT        (0x0FFF)
上面所介紹的是專用A/D轉換通道經常使用寄存器,LCD觸摸屏A/D轉換有另外A/D通道

2.        ADC驅動程序
A/D轉化驅動因爲也屬於字符設備驅動,因此其程序設計流程和LED驅動大致一致。在linux-3.8.3/drivers/char目錄下新建zzqadc.c驅動文件,固然也可寫好以後在拷貝到linux-3.8.3/drivers/char目錄下。
zhuzhaoqi@zhuzhaoqi-desktop:~/Linux/linux-3.8.3/drivers/char$ vim zzqadc.c
頭文件是必不可少的,A/D驅動程序所要包含的頭文件以下所示:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <asm/uaccess.h>

#include <mach/map.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>

#include <plat/regs-adc.h>
與LED驅動程序所包含的頭文件相比較,多了ADC專用的頭文件,如regs-adc.h,這個頭文件位於linux-3.8.3/arch/arm/plat-samsung/include/plat目錄下。
static void __iomem *base_addr;
static struct clk *adc_clock;
#define   __ADCREG(name)   (*(unsigned long int *)(base_addr + name))
自從linux-2.6.9版本開始便把__iomem加入內核,__iomem是表示指向一個I/O的內存空間。將__iomem加入linux,主要是考慮到驅動程序的通用性。因爲不一樣的CPU體系結構對I/O空間的表示可能不一樣,可是當使用__iomem時,就會忽略對變量的檢查,由於__iomem使用的是void。
#define   S3C_ADCREG(x)      (x)
#define   S3C_ADCCON         S3C_ADCREG(0x00)
#define   S3C_ADCDAT0        S3C_ADCREG(0x0C)

/* ADC contrl */
#define   ADCCON             __ADCREG(S3C_ADCCON)
/* read the ADdata */
#define   ADCDAT0            __ADCREG(S3C_ADCCON)

聲明ADC控制寄存器的地址。
/* The set of ADCCON */
#define   S3C_ADCCON_ENABLE_START           (1 << 0)
#define   S3C_ADCCON_READ_START             (1 << 1)
#define   S3C_ADCCON_RUN                    (0 << 2)
#define   S3C_ADCCON_STDBM                  (1 << 2)
#define   S3C_ADCCON_SELMUX(x)              ( ((x)&0x7) << 3 )
#define   S3C_ADCCON_PRSCVL(x)              ( ((x)&0xFF) << 6 )
#define   S3C_ADCCON_PRSCEN                 (1 << 14)
#define   S3C_ADCCON_ECFLG                  (1 << 15)

/* The set of ADCDAT0 */
#define   S3C_ADCDAT0_XPDATA_MASK           (0x03FF)
#define   S3C_ADCDAT0_XPDATA_MASK_12BIT    (0x0FFF)
根據上一小節對ADCCON和ADCDAT0的介紹,能夠很容易寫出上面宏定義。
在使用ADC以前,先得對ADC進行初始化設置,因爲OK6410開發平臺自帶的A/D電壓採樣電路選用的是AIN0通道。則這裏須要對進行AIN0初始化,初始化階段須要完成的事情爲:A/D 轉換開始和該位被清理後開啓、正常運做模式、模擬輸入通道選擇AIN0、ADC 預約標器值0xFF、ADC預約標器啓動。
/*
*  AIN0 init  
*/
static int adc_init(void)
{

    ADCCON = S3C_ADCCON_PRSCEN | S3C_ADCCON_PRSCVL(0xFF) | \
  S3C_ADCCON_SELMUX(0x00) | S3C_ADCCON_RUN;
ADCCON |=S3C_ADCCON_ENABLE_START;

    return 0;
}
open函數指針的實現函數adc_open():
/*
*  open dev
*/
static int adc_open(struct inode *inode, struct file *filp)
{
    adc_init();
    return 0;
}
release函數指針的實現函數adc_release():
/*
*  release dev
*/
static int adc_release(struct inode *inode,struct file *filp)
{
    return 0;
}
read()函數指針的實現函數adc_read(),這個函數的做用是讀取ADC採樣數據。
/*
* adc_read
*/
static ssize_t adc_read(struct file *filp, char __user *buff, 
size_t size, loff_t *ppos)
{
    ADCCON |= S3C_ADCCON_READ_START;
    /* check the adc Enabled ,The [0] is low*/
    while(ADCCON & 0x01);
    /* check adc change end */
    while(!(ADCCON & 0x8000));

    /* return the data of adc */
    return (ADCDAT0 & S3C_ADCDAT0_XPDATA_MASK);
}
ADC驅動程序的核心控制部分:
static struct file_operations dev_fops =
{
    .owner   = THIS_MODULE,
    .open    = adc_open,
    .release = adc_release,
    .read    = adc_read,
};

static struct miscdevice misc =
{
    .minor = MISC_DYNAMIC_MINOR,
    .name  = 「zzqadc「,
    .fops  = &dev_fops,
};
加載insmod驅動程序以下所示:
static int __init dev_init()
{
    int ret;

    /* Address Mapping */
    base_addr = ioremap(SAMSUNG_PA_ADC,0X20);
    if(base_addr == NULL)
    {
        printk(KERN_ERR"failed to remap \n");
        return -ENOMEM;
    }

    /* Enabld acd clock */
    adc_clock = clk_get(NULL,"adc");
    if(!adc_clock)
    {
        printk(KERN_ERR"failed to get adc clock \n");
        return -ENOENT;
    }
    clk_enable(adc_clock);

    ret = misc_register(&misc);
    printk("dev_init return ret: %d \n", ret);

    return ret;
}
加載insmod驅動程序,這裏使用到了ioremap()函數,在內核驅動程序的初始化階段,經過ioremap()函數將物理地址映射到內核虛擬空間;在驅動程序的mmap系統調用中,使用remap_page_range()函數將該塊ROM映射到用戶虛擬空間。這樣內核空間和用戶空間都能訪問這段被映射後的虛擬地址。
ioremap()宏定義在asm/io.h內:
  #define ioremap(cookie,size)           __ioremap(cookie,size,0)
  __ioremap函數原型爲(arm/mm/ioremap.c):
  void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned longflags);
  phys_addr:要映射的起始的IO地址;
  size:要映射的空間的大小;
  flags:要映射的IO空間和權限有關的標誌。
該函數返回映射後的內核虛擬地址(3G-4G),接着即可以經過讀寫該返回的內核虛擬地址去訪問之這段I/O內存資源。
base_addr = ioremap(SAMSUNG_PA_ADC,0X20);
這行代碼便是將SAMSUNG_PA_ADC(0x7E00 B000)映射到內核,返回內核的虛擬地址給base_addr。
clk_get(NULL,"adc")能夠得到adc時鐘,每個外設都有本身的工做頻率,PRSCVL是A/D轉換器時鐘的預分頻功能時A/D時鐘的計算公式,A/D時鐘  = PCLK / (PRSCVL+1)。
注意:AD時鐘最大爲2.5MHZ而且應該小於PCLK的1/5。
    adc_clock = clk_get(NULL,"adc");
即爲獲取adc的工做時鐘頻率。
ret = misc_register(&misc);
建立雜項設備節點。這裏使用到了雜項設備,雜項設備也是在嵌入式系統中用得比較多的一種設備驅動。在 Linux 內核的include/linux目錄下有miscdevice.h文件,要把本身定義的misc device從設備定義在這裏。實際上是由於這些字符設備不符合預先肯定的字符設備範疇,全部這些設備採用主編號10 ,一塊兒歸於misc device,其實misc_register就是用主標號10調用register_chrdev()的。也就是說,misc設備其實也就是特殊的字符設備,可自動生成設備節點。
(雜項設備結構體分析)
卸載rmmod驅動程序:
static void __exit dev_exit()
{
    iounmap(base_addr);

    /* disable ths adc clock */
    if(adc_clock)
    {
        clk_disable(adc_clock);
        clk_put(adc_clock);
        adc_clock = NULL;
    }

    misc_deregister(&misc);
}

許可證聲明、做者信息、調用加載和卸載程序:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhuzhaoqi  jxlgzzq@163.com");

module_init(dev_init);
module_exit(dev_exit);
在/linux-3.8.3/drivers/char目錄下的Makefile中添加:
obj-m                           += zzqadc.o
回到/linux-3.8.3根目錄下:
/home/zhuzhaoqi/Linux/linux-3.8.3# make modules
將/linux-3.8.3/drivers/char目錄下生成的zzqadc.ko拷貝到文件系統的/lib/module/3.8.3目錄中。

3.        ADC應用程序
ADC應用程序也是相對簡單,打開設備驅動文件以後進行數據讀取便可。
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fp,adc_data,i;
    fp = open("/dev/zzqadc",O_RDWR);

    if (fp < 0)
    {
        printf("open failed! \n");
    }
    printf("opened ... \n");

    for ( ; ; i++)
    {
        adc_data = read(fp,NULL,0);
        printf("Begin the NO. %d test... \n",i);
        printf("adc_data = %d \n",adc_data);
        printf("The Value = %f V \n" , ( (float)adc_data )* 3.3 / 1024);
        printf("End the NO. %d test ...... \n \n",i);

        sleep(1);
    }

    close(fp);
    return 0;
}
因爲本次使用的A/D轉換是10位,數據轉換值即爲1024,而OK6410的參考電壓是3.3V,則A/D採集數據和電壓之間的轉換公式爲:(float)adc_data )* 3.3 / 1024。
爲ADC應用程序編寫Makefile:
CC = /usr/local/arm/4.4.1/bin/arm-linux-gcc

zzqadcapp:zzqadcapp.o
        $(CC) -o zzqadcapp zzqadcapp.o

zzqadcapp.o:zzqadcapp.c
        $(CC) -c zzqadcapp.c

clean :
        rm zzqadcapp.o zzqadcapp
將生成的zzqadcapp應用文件拷貝到到跟文件系統/usr/bin文件夾下。
加載zzqadc.ko設備:
[YJR@zhuzhaoqi 3.8.3]# insmod zzqadc.ko 
dev_init return ret: 0

[YJR@zhuzhaoqi]\# ls -l /dev/zzqadc
crw-rw----    1 root     root      10,  60 Jan  1 08:00 /dev/zzqadc
在/dev目錄下存在zzqadc設備節點,則說明ADC驅動加載成功。
執行ADC應用程序,電壓採樣以下所示:
[YJR@zhuzhaoqi]\# ./zzqadcapp 
opened ...
……
Begin the NO. 10 test... 
adc_data = 962 
The Value = 3.100195 V 
End the NO. 10 test ...... 
……
注:本節配套視頻位於光盤中「嵌入式Linux實用教程視頻」目錄下第四章02課(字符設備驅動之ADC)。
第五章第一節  Qt 編譯環境搭建

在進行Qt開發以前,創建Qt編譯環境、移植Qt是一個相當重要的步驟。
1.1.1   tslib 安裝
OK6410開發平臺在使用觸摸屏時,由於電磁噪聲的緣故,觸摸屏容易存在點擊不許確、有抖動等問題。tslib可以爲觸摸屏驅動得到的採樣提供諸如濾波、去抖、校準等功能,一般做爲觸摸屏驅動的適配層,爲上層的應用提供一個統一的接口。
在官方網站下載tslib-1.0.tar.bz2,地址:http://sourceforge.net/projects/tslib.berlios/files/。
將下載完成以後的tslib-1.0.tar.bz2存放在宿主機任意一個目錄下:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib$ ls
tslib-1.0.tar.bz2
將其解壓出來:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib$ tar jxvftslib-1.0.tar.bz2
解壓完成以後進入tslib-1.0目錄,以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$ls
acinclude.m4     autogen.sh    COPYING  m4          plugins  tests
AUTHORS          ChangeLog     etc     Makefile.am  tslib.pc.in
autogen-clean.sh configure.ac  INSTALL  NEWS        src
安裝autoconf、automake、libtool:
root@zhuzhaoqi-desktop:/home/zhuzhaoqi#apt-get installautoconf
root@zhuzhaoqi-desktop:/home/zhuzhaoqi#apt-get installautomake
root@zhuzhaoqi-desktop:/home/zhuzhaoqi#apt-get installlibtool
因爲open函數的語法不符合最新的gcc,在/tests/ts_calibrate.c中加入open的第三個參數:
if ((calfile = getenv("TSLIB_CALIBFILE")) !=NULL) {
cal_fd= open (calfile, O_CREAT | O_RDWR,0777);
} else {
cal_fd= open ("/etc/pointercal", O_CREAT | O_RDWR,0777);
}
執行編譯:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$./autogen.sh
……
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$./configure --prefix=/usr/local/tslib-1.0/ --host=arm-linux ac_cv_func_malloc_0_nonnull=yes--enable-inputapi=no
……
--prefix=/usr/local/tslib-1.0/這是安裝路徑,進行編譯、安裝。
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$make
……
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/tslib/tslib-1.0$sudomake install
安裝完成以後在/usr/local/目錄下有:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local$ ls
arm  etc    include lib  qwt-6.0.2      sbin  src
bin  games  info    man  qwt-6.0.2-arm  share  tslib-1.0
修改ts.conf:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/tslib-1.0/etc$vim ts.conf
去掉module_rawinput前面的#,注意前面這個空格也得刪除,以下:
# Uncomment if you wish to use the linux input layerevent interface
module_raw input
將/usr/local/tslib-1.0/目錄下的全部文件拷貝到開發板,筆者是存放在usr/local/。以下:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/tslib-1.0$ sudocp -r * /home/zhuzhaoqi/rootfs/usr/local/
在OK6410中設置tslib環境變量,在文件系統的/etc/profile中添加以下:
// 指定幀緩衝設備
export set TSLIB_FBDEVICE=/dev/fb0
// 指定觸摸屏設備節點
export set TSLIB_TSDEVICE=/dev/input/event0  
// 指定TSLIB  配置文件的位置
export set TSLIB_CONFFILE=/usr/local/etc/ts.conf
// 指定觸摸屏校準文件 pintercal  的存放位置
export set TSLIB_CALIBFILE=/etc/pointercal
// 指定觸摸屏插件所在路徑
export set TSLIB_PLUGINDIR=/usr/local/lib/ts
// 設定控制檯設備爲 none  ,不然默認爲 /dev/tty 
export TSLIB_CONSOLEDEVICE=none   
在測試觸摸屏以前,首先得保證在/dev目錄下有觸摸屏設備節點eventX:
[YJR@zhuzhaoqi]\# ls -l /dev/input/e*
crw-rw----    1 root     root     13,  64 Jan  1 08:00 /dev/input/event0
運行ts_calibrate:
[YJR@zhuzhaoqi]\# cd bin/
[YJR@zhuzhaoqi]\# ls
ts_calibrate ts_harvest    ts_print      ts_print_raw  ts_test
[YJR@zhuzhaoqi]\# ./ts_calibrate
運行/usr/local/bin中的ts_calibrate進行校準,成功的話會出現界面,並點擊十字符號,完成後會生成/etc/pointercal文件,這即是觸摸屏的校準配置文件。
或者能夠寫一個觸摸屏校準腳本calibrate,存放在/bin目錄下:
#!/bin/sh
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_TSDEVICE=/dev/input/event0
export TSLIB_CONFFILE=/usr/local/tslib/etc/ts.conf
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_PLUGINDIR=/usr/local/tslib/lib/ts
 
export TSLIB_TSEVENTTYPE=H3600
export TSLIB_CONSOLEDEVICE=none
export QWS_KEYBOARD="TTY:/dev/tty1"
 
if [ -c /dev/input/event0 ]; then
 
        if [ -e/etc/pointercal -a ! -s /etc/pointercal ] ; then
               rm /etc/pointercal
        fi
fi
 
export PATH=$QTDIR/bin:$PATH
exportLD_LIBRARY_PATH=$QTDIR/plugins/qtopialmigrate/:$QTDIR/qt_plugins/imageformats/:$QTDIR/lib:/root/tslib/build/lib:$LD_LIBRARY_PATH
 
exec /usr/local/tslib/bin/ts_calibrate  1>/dev/null 2>/dev/null
#exec /usr/local/tslib/bin/ts_test  1>/dev/null 2>/dev/null
執行calibrate:
[YJR@zhuzhaoqi]\# ./calibrate
若是執行的是ts_calibrate測試,效果如圖5. 1所示。
                                                                     
  
5. 1 ts_calibrate 測試效果

     
  
5. 2 ts_test 測試效果

若是執行的是ts_test測試,選擇draw畫圖選項,效果如圖5. 2所示。
注:本節配套視頻位於光盤中「嵌入式Linux實用教程視頻」目錄下第五章01課(tslib安裝)。
第五章第二節  安裝Linux/x11版Qt-4.8.4

在官方網站下載Qt libraries 4.8.4 for Linux/X11 (225 MB)(實際是:qt-everywhere-opensource-src-4.8.4.tar.gz)。
完成以後在ubuntu宿主機解壓:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4$ tarzxvf qt-everywhere-opensource-src-4.8.4.tar.gz
在裝有gold linker的系統裏,編譯腳本會加入-fuse-ld=gold選項,但這個選項gcc是不支持的。解決辦法是移除該選項,找到文件src/3rdparty/webkit/Source/common.pri,屏蔽QMAKE_LFLAGS+=-fuse-ld=gold。
linux-g++ {
isEmpty($$(SBOX_DPKG_INST_ARCH)):exists(/usr/bin/ld.gold){
   message(Using gold linker)
#    QMAKE_LFLAGS+=-fuse-ld=gold
}
}
配置安裝路徑:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$./configure --prefix=/usr/local/qt-4.8.4-x11
……
Type 'c' if you want to use the Commercial Edition.
Type 'o' if you want to use the Open Source Edition.
c 是商業,o 是開源,選擇o
……
Type 'yes' to accept this license offer.
Type 'no' to decline this license offer.
選擇yes
……
Qt is now configured for building. Just run 'make'.
Once everything is built, you must run 'make install'.
Qt will be installed into /usr/local/qt-4.8.4-x11
 
To reconfigure, run 'make confclean' and 'configure'.
 
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$
Qt輸出信息提示咱們進行make編譯:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$make
這個編譯過程比較久,依每一個人的電腦配置而定,大概須要1~3個小時。
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/qt-everywhere-opensource-src-4.8.4$make install
安裝好以後,在/usr/local目錄下面有:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local$ ls
arm  etc    include lib  qt-4.8.4-arm  qwt-6.0.2     sbin   src
bin  games  info    man  qt-4.8.4-x11  qwt-6.0.2-arm  tslib-1.0
 
1.1.2    安裝embedded Qt-4.8.4
Embedded版Qt4.8.4源碼和Linux/x11版Qt4.8.4是同樣的,將下載的源碼解壓在另外一個文件夾,配置embedded版配置:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/arm/qt-everywhere-opensource-src-4.8.4$./configure -prefix /usr/local/qt-4.8.4-arm/-shared -no-fast -no-largefile -no-exceptions -qt-sql-sqlite -qt3support-no-xmlpatterns -multimedia -no-svg -no-mmx -no-3dnow -no-sse -no-sse2 -qt-zlib-no-webkit -qt-libtiff -qt-libpng -qt-libjpeg -make libs -nomake examples-nomake docs -nomake demo -no-optimized-qmake -no-nis -no-cups -no-iconv-no-dbus -no-separate-debug-info -no-openssl -xplatform qws/linux-arm-g++-embedded arm -little-endian -no-freetype -depths 4,8,16,32 -qt-gfx-linuxfb-no-gfx-multiscreen -no-gfx-vnc -no-gfx-qvfb -qt-kbd-linuxinput -no-kbd-tty-no-glib -armfpa -no-mouse-qvfb -qt-mouse-pc -qt-mouse-tslib -I/usr/local/tslib-1.0/include-L/usr/local/tslib-1.0/lib
執行make進行編譯:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/arm/qt-everywhere-opensource-src-4.8.4$make
執行makeinstall進行安裝:
zhuzhaoqi@zhuzhaoqi-desktop:~/Qt-4.8.4/Qt-4.8.4/arm/qt-everywhere-opensource-src-4.8.4$make install
安裝好以後,在/usr/local目錄下面有:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local$ ls
arm  etc    include lib  qt-4.8.4-arm  qwt-6.0.2     sbin   src
bin  games  info    man  qt-4.8.4-x11  qwt-6.0.2-arm  tslib-1.0
在qt-4.8.4-arm目錄下有以下目錄:
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/qt-4.8.4-arm$ls
bin imports  include  lib mkspecs  plugins
在文件系統/opt/目錄下新建Qt-4.8.4目錄,以下所示:
zhuzhaoqi@zhuzhaoqi-desktop:~/rootfs/opt$ sudo mkdirQt-4.8.4/
將imports、lib、mkspecs、plugins拷貝至/opt/Qt-4.8.4/。
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/qt-4.8.3-arm$sudo cp -r importslibmkspecsplugins /home/zhuzhaoqi/rootfs/opt/Qt-4.8.4/
在開發板的文件系統/usr/目錄下新建/qt/目錄,將/qt-4.8.4-arm/lib/目錄下的全部文件拷貝到/usr/qt/目錄中。
zhuzhaoqi@zhuzhaoqi-desktop:~/rootfs/usr$ sudo mkdirqt
zhuzhaoqi@zhuzhaoqi-desktop:/usr/local/qt-4.8.4-arm/lib$sudo cp -r * /home/zhuzhaoqi/rootfs/usr/qt/
爲OK6410開發平臺添加Qt啓動環境參數,在/etc/profile中添加:
export QTDIR=/usr/qt
export QPEDIR=$QTDIR
export QT_PLUGIN_PATH=/usr/qt
export T_ROOT=/usr/local/tslib
export PATH=$QTDIR/:$PATH
export QWS_MOUSE_PROTO=Tslib:/dev/event0
export LD_LIBRARY_PATH=$T_ROOT/lib:$QTDIR
export QT_QWS_FONTDIR=/usr/qt
至此,Qt移植就完成。
注:本節配套視頻位於光盤中「嵌入式Linux實用教程視頻」目錄下第五章02課(安裝Linux和embedded版本Qt-4.8.4)。
相關文章
相關標籤/搜索