【转】朱兆祺带你一步一步学习嵌入式(连载)

2019年11月12日 阅读数:180
这篇文章主要向大家介绍【转】朱兆祺带你一步一步学习嵌入式(连载),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

原文网址: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)。