首页 > 代码库 > 【转】朱兆祺带你一步一步学习嵌入式(连载)

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

原文网址:http://bbs.elecfans.com/jishu_357014_2_1.html#comment_top

 从最初涉及嵌入式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.3、u-boot-2013.04-rc1、自制编译器、等等相关源码
6. 嵌入式Linux实用教程程序
《嵌入式Linux开发实用教程》一书中涉及的U-Boot添加程序、Linux程序、Linux设备驱动程序、Qt程序等等
6. C语言技术公开课
在电子发烧友学院进行的一系列课程涉及的资料,相关链接如下:
http://bbs.elecfans.com/jishu_361304_1_1.html
下载说明:

百度网盘:

 
[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驱动移植
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2625521&fromuid=222350

第三章第六节  DM9000网卡驱动
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2676612&fromuid=222350

第三章第七节  YAFFS2根文件系统(1)
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2678991&fromuid=222350

第三章第八节  LCD驱动移植
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2743024&fromuid=222350

第三章第九节   LCD触摸移植
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2798921&fromuid=222350
 
第四章第二节  字符设备驱动
http://bbs.elecfans.com/forum.php?mod=redirect&goto=findpost&ptid=357014&pid=2879175&fromuid=222350
 
第四章第三节 ADC驱动程序设计
 
第五章第一节  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.bz2U-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是链接脚本语法有误,很容易理解。第二、三个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的第13、9、8位清零。
bic r0, r0, #0x00000087
0087即为0000 0000 1000 0111,即是将r0的第7、2、1、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产生HCLKX2、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_fboard_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_21FF0x0C00_2200-0x0C00_2FFF0x0C00_3000-0x0C00_3FFF
密钥(512B)保留(3.5k)堆区,保存全局变量(4K)
8K
D-TCM1
0x0C00_4000-0x0C00_40180x0C00_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, uint32StartBlkAddressuint16 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控制器编号,固定为0uBlkAddr:块地址uPageAddr:页地址aData:目标地址返回值:失败        1 成功
-
0x0C00_4010
bool ONENAND_ReadPage_4burst(uint32 Controller,uint32 uBlkAddr , uint8 uPageAddr, uint32* aData)Controller: OneNAND控制器编号,固定为0uBlkAddr:块地址uPageAddr:页地址aData:目标地址返回值:失败        1 成功
-
0x0C00_4014
bool ONENAND_ReadPage_8burst(uint32 Controller,uint32 uBlkAddr , uint8 uPageAddr, uint32* aData)Controller: OneNAND控制器编号,固定为0uBlkAddr:块地址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、svr4、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 = http://www.mamicode.com/{
     .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所示。
1、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同理。
31、32号引脚为I2C总线控制。
33、34、35、36这四个引脚为LCD图像使能频率等控制。其中LVDEN引脚为像素点使能,像素点是否显示在屏幕上在于这个引脚是否使能;LVSYNC引脚为垂直同步信号,该信号有效一次,则刷新一帧像素点;LHSYNC引脚为行同步信号,该信号有效一次,刷新一行像素点;LVCLK为刷新频率。
37、38、39、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 = http://www.mamicode.com/{
    .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 = http://www.mamicode.com/read(fp,NULL,0);
        printf("Begin the NO. %d test... \n",i);
        printf("adc_data = http://www.mamicode.com/%d /n",adc_data);
        printf("The Value = http://www.mamicode.com/%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 = http://www.mamicode.com/962
The Value = http://www.mamicode.com/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   安装embeddedQt-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)。
 
 
 
 
 

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