嵌入式Linux实验手册
基于ARM9 S3C2410 实验平台
1. 实验概述... 1
2. 实验环境配置... 1
2.1 开发主机配置... 1
2.2 实验板介绍... 1
2.3 实验板配置准备... 4
3. Linux预备知识... 5
3.1 Linux常用命令... 5
3.2 Makefile的使用常识... 8
3.3 嵌入式Linux系统引导过程... 12
3.4 U-boot启动过程... 13
3.5 Linux内核启动过程... 22
4. 实验内容... 31
4.1 建立交叉开发环境... 31
4.2 编译调试应用程序... 34
4.3 移植u-boot 36
4.4 编译Linux内核... 39
4.5 移植Linux内核... 47
4.6 调试Linux内核... 50
4.7 制作Linux文件系统... 52
4.8 部署Linux系统... 55
1. 实验概述
对于嵌入式系统,目标板一般只有很小的存储空间,处理器频率也很低。而且没有可以预装的Linux系统,直接在这样的硬件上建立Linux系统非常困难。嵌入式Linux交叉开发环境可以很好地解决这个难题。
所谓交叉开发,就是在开发主机上编辑编译源程序,在目标板上运行可执行程序。通常通过以太网接口传输Linux内核影像到目标板内存,让目标板的Linux挂接NFS的文件系统。这样的交叉开发环境可以非常方便地进行嵌入式Linux开发调试以及集成。
本实验以S3C2410 ARM920T处理器的实验板为例,建立嵌入式Linux交叉开发环境,完成嵌入式Linux开发的全过程。
通过本实验,可以掌握嵌入式Linux基本开发流程,熟悉u-boot、Linux内核、应用程序以及Linux文件系统的配置开发。从而能够在具体的工程项目中应用嵌入式Linux系统。
2. 实验环境配置
2.1 开发主机配置
建议开发主机硬件配置高一些,这样编译的速度快一些,尤其是编译Linux内核。
推荐使用X86 PC配置:
主频:>1GHz
内存:>256MB
安装Redhat Linux 9操作系统作为开发环境。可以在PC上安装Windows和Linux双操作系统,也可以在Windows安装vmware。
安装过程中,建议完全安装所有软件包,这样可以使用Redhat Linux提供的一些Linux工具和服务,方便开发。如果磁盘存储空间有限,安装过程可以附加选择一些软件包,或者Linux启动后再安装这些rpm包。
这些软件包包括:tftp, tftp-server等。
在开发时所需其他服务,请参考手册试验部分4.1内容:建立交叉开发环境。
实验板的硬件特点:
- SAMSUNG ARM9 S3C2410处理器,主频可达203MHz。
- 64MB SDRAM,有2片K4S561632构成,工作在32位模式
- 2MB NOR FLASH,型号为SST39VF1601,工作在16位模式
- 64MB NAND FLASH,型号为K9F1208,可以兼容16MB、32MB和128MB
- 通过跳线可以设置系统从NOR FLASH或者NAND FLASH启动。
- 10M以太网接口,采用CS8900Q3芯片,带传输和连接状态指示灯
- LCD和触摸屏接口。
- 二个USB HOST接口,遵守USB1.1标准。
- 一个USB Device接口,遵守USB1.1标准
- Audio音频接口:音频模块由S3C2410的IIS音频总线和UDA1341音频编解码器组成,板上带一个麦克。
- 2路RS232接口,波特率可达115200bps。
- RTC接3V锂电池供电
- SD卡接口,兼容SD Memory Card 1.0和SDIO Card Protocol 1.0
- I2C接口的EEPROM:可通过CPU的I2C接口实现对EEROM中的数据读写,数据掉电不丢失。
- 提供国际标准20针ICE JTAG接口,提供配套的Flash编程下载线。
- 16个按键
- 蜂鸣器,4个LED
- 开关电源
2.3 实验板配置准备
- 实验板与开发主机之间连接串口线,以太网线和JTAG接口线。
表2.3.1 连线接口说明
连接线
实验板
开发主机
串口线
UART1
串口
以太网线
10M网口
网口
JTAG电缆线
JTAG接口
并口
注意:切勿带电拔插JTAG电缆和并口线,否则很容易损坏芯片。
核心板上的跳线JP1:连接短路线插,设置为NAND FLASH启动; 断开短路插,设置为NOR FLASH启动。
注意:断开短路插时,要把短路插子插在一个脚上,不要取下,防止丢失。
使用sjf2410.exe程序把FS2410_BIOS_I.bin烧写到FLASH中:
1) 复制“Flash烧写工具”目录到主机上,双击“安装驱动.exe”,安装GIVEIO驱动。
2) 连接JTAG接口和并口线。20针扁平电缆连接JTAG接口和JTAG小板的JP3,并口连接主机和JTAG小板。确认核心板上的JP1调线断开。
3) 进入“Flash烧写工具”目录,双击执行sjf2410_bios.bat,显示烧写提示信息
4) 根据烧写提示信息,选择3:SST39VF160 Prog,再输入Input target offset: 0,开始烧写通过并口烧写到NOR FLASH
5)烧写完成后,复位即可启动BIOS
具体参考光盘提供的FS2410用户手册和烧写文档:
2.3节 FS2410的BIOS功能说明
2.6节 用SJF2410工具将BIOS烧写到NAND FLASH
2.7节 用SJF2410工具将BIOS烧写到NOR FLASH
FLASH烧写说明文档_sjf2410_v4.pdf
通过BIOS烧写u-boot.bin到NOR FLASH。
目标板上电,BIOS启动,在DNW显示启动信息并提示命令选项。
通过串口下载影像文件,选择1: Uart download file,输入1,显示等待串口接收传输文件。
打开DNW的Serial Port菜单,选择发送文件:Tx file
在对话框中浏览选择u-boot.bin的文件,确认后,开始传输,下载到目标板的SDRAM中缓存。
传输完成,提示是否立刻执行,输入n,不立刻执行
然后,回到显示命令选项,选择5: Write NOR FLASH with download file,SDRAM中内容就自动烧写到NOR FLASH的0地址了。
烧写完成后,复位实验板,串口终端应该显示出现u-boot的启动信息。
本课程要求学员对Linux基本操作命令有一定了解和掌握。下面列出的一些常用命令作为参考。最好针对每一个都能亲自练习、掌握。
----------------------------------------------------------------------
ls 以默认方式显示当前目录文件列表
ls –a 显示所有文件包括隐藏文件
ls –l 显示文件属性,包括大小,日期,符号连接,是否可读写及是否可执行
----------------------------------------------------------------------
cd dir 切换到当前目录下的dir目录
cd .. 切换到到上一级目录
cd ~ 切换到用户目录,比如是root用户,则切换到/root下
----------------------------------------------------------------------
rm file 删除某一个文件
rm -rf dir 删除当前目录下叫dir的整个目录
----------------------------------------------------------------------
cp source target 将文件source 复制为 target
cp –av soure_dir target_dir 将整个目录复制,两目录完全一样
cp –fr source_dir target_dir 将整个目录复制,并且是以非链接方式复制,当source目录带有符号链接时,两个目录不相同
----------------------------------------------------------------------
mv source target 将文件或者目录source更名为target
----------------------------------------------------------------------
diff dir1 dir2 比较目录1与目录2的文件列表是否相同,但不比较文件的实际内容,不同则列出
diff file1 file2 比较文件1与文件2的内容是否相同,如果是文本格式的文件,则将不相同的内容显示,如果是二进制代码则只表示两个文件是不同的
----------------------------------------------------------------------
echo message 显示一串字符
cat file 显示文件的内容,和DOS的type相同
cat file | more 显示文件的内容并传输到more程序实现分页显示,使用命令less file可实现相同的功能
more 分页命令,一般通过管道将内容传给它,如ls | more
----------------------------------------------------------------------
eject umout掉CDROM并将光碟弹出,但cdrom不能处于busy的状态,否则无效
----------------------------------------------------------------------
du 计算当前目录的容量
du -sm /root 计算/root目录的容量并以M为单位
find -name /path file 在/path目录下查找看是否有文件file
grep -ir “chars” 在当前目录的所有文件查找字串chars,并忽略大小写,-i为大小写,-r为下一级目录
----------------------------------------------------------------------
vi file 编辑文件file
vi原基本使用及命令:
vi分为编辑状态和命令状态。输入命令要先按ESC,退出编辑状态, 然后输入命令。
常用命令有:
:x(退出)
:x!(退出不保存)
:w(保存文件)
:w!(不询问方式写入文件)
:r file(读文件file)
:%s/oldchars/newchars/g(将所有字串oldchars换成newchars)
i进入编辑插入状态
ESC退出编辑状态
----------------------------------------------------------------------
man ls 读取关于ls命令的帮助
----------------------------------------------------------------------
reboot 重新启动计算机
halt 关闭计算机
init 0 关闭所有应用程序和服务,进入纯净的操作环境
init 1 重新启动应用及服务
init 6 重新启动计算机
----------------------------------------------------------------------
tar xfzv file.tgz 将文件file.tgz解压
tar -zcvf file.tgz
将文件或目录压缩为file.tgz
gzip directory.tar 将覆盖原文件生成压缩的 directory.tar.gz
gunzip directory.tar.gz 覆盖原文件解压生成不压缩的 directory.tar。
----------------------------------------------------------------------
dmesg 显示kernle启动及驱动装载信息
uname -a 显示操作系统的类型
----------------------------------------------------------------------
strings file 显示file文件中的ASCII字符内容
---------------------------------------------------------------------
rpm -ihv program.rpm 安装程序program并显示安装进程
----------------------------------------------------------------------
su root 切换到超级用户
chmod a+x file 将file文件设置为可执行,脚本类文件一定要这样设置一个,否则得用bash file才能执行
chmod 666 file 将文件file设置为可读写
chown user /dir 将/dir目录设置为user所有
----------------------------------------------------------------------
mknod /dev/hda1 b 3 1 创建块设备hda1,主设备号为3,从设备号为1,即master硬盘的的第一个分区
mknod /dev/tty1 c 4 1 创建字符设备tty1,主设备号为4,众设备号为1,即第一个tty终端
----------------------------------------------------------------------
touch /tmp/running 在/tmp下创建一个临时文件running,重新启动后消失
----------------------------------------------------------------------
fdisk /dev/hda 就像执行了dos的fdisk一样
mount -t ext2 /dev/hda1 /mnt 把/dev/hda1装载到 /mnt目录
df 显示文件系统装载的相关信息
mount -t nfs 192.168.1.1:/sharedir /mnt 将nfs服务的共享目录sharedir加载到/mnt/nfs目录
umount /mnt 将/mnt目录卸载,/mnt目录必须处于空闲状态
sync 刷新缓冲区,使内容与磁盘同步,
mkfs /dev/hda1 格式化/dev/hda1为ext2格式
----------------------------------------------------------------------
lilo 运行lilo程序,程序自动查找/etc/lilo.conf并按该配置生效
lilo -C /root/lilo.conf lilo程序按/root/lilo.conf配置生效
----------------------------------------------------------------------
dd if=/dev/fd0 of=floppy.fd 将软盘的内容复制成一个镜像
dd if=/dev/zero of=root.ram bs=1024,count=1024 生成一个大小为1M的块设备,可以把它当作硬盘的一个分区来用
----------------------------------------------------------------------
gcc hello.c -o hello 将hello.c编译成名为hello的二进制执行文件
ldd program 显示程序所使用了哪些库
----------------------------------------------------------------------
ps 显示当前系统进程信息
ps –ef 显示系统所有进程信息
kill -9 500 将进程编号为500的程序杀死
killall -9 netscape 将所有名字为netscape的程序杀死,kill不是万能的,对僵死的程序则无效。
top 显示系统进程的活动情况,按占CPU资源百分比来分
free 显示系统内存及swap使用情况
time program 在program程序结束后,将计算出program运行所使用的时间
----------------------------------------------------------------------
chroot . 将根目录切换至当前目录,调试新系统时使用
----------------------------------------------------------------------
ifconfig eth0 192.168.1.1 netmask 255.255.255.0 设置网卡1的地址192.168.1.1,掩码为255.255.255.0,不写netmask参数则默认为255.255.255.0
ifconfig eth0:0 192.168.1.2 捆绑网卡1的第二个地址为192.168.1.2
----------------------------------------------------------------------
telnet 192.168.1.1 登陆IP为192.168.1.1的telnet服务器
ftp 192.168.1.1 登陆到ftp服务器
3.1.2 GUN工具链常识
一般在完全安装的Redhat Linux上都会自动按照Linux GUN Toolchains。在/usr/bin目录下容易找到gcc,ld等工具。
为了对GUN工具链有一个初步印象,这里安排一个课余作业,在预习时完成。
课余作业:
通过编译一个简单的hello world对Linux编译开发环境有一个直观印象,学习使用编译工具。
1) 在主机上, 写好简单的“hello world” 例子程序。
# include
main(int argc, char **argv)
{
int i;
for ( i=0; i<10; I++) {
printf("hello i=%d
", i);
}
return 0;
}
2) 编译可执行程序.
$ gcc -o hello hello.c
3)使用如下命令运行程序:
# ./hello
可以看到控制台有”hello world”输出。
3.2 Makefile的使用常识
下面将介绍怎样编写、编译一个复杂点的应用程序。
多个代码文件的复杂应用可以通过Makefiel来进行管理。实际上内核就是一个由多个文件组成的程序,也是通过多级的Makefile进行管理,最终通过编译、链接,生成一个内核映象。在理解后面内核编译过程之前需要先了解Makefile的使用常识。
make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。
l Makefile里有什么?
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。
2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。
3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“#”。
还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。
l Makefile的文件名
默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。
当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。
l 引用其它的Makefile
在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:
include
filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)
在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:
include foo.make *.mk $(bar)
等价于:
include foo.make a.mk b.mk c.mk e.mk f.mk
make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:
1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
2、如果目录/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。
如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:
-include
其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。
l 环境变量 MAKEFILES
如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。
但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。
l make的工作方式
GNU的make工作时的执行步骤入下:(想来其它的make也是类似)
1、读入所有的Makefile。
2、读入被include的其它Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。
1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。
l 书写规则
规则包含两个部分,一个是依赖关系,一个是生成目标的方法。
在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。
书写规则举例:
foo.o : foo.c defs.h # foo模块
cc -c -g foo.c
看到这个例子,各位应该不是很陌生了,前面也已说过,foo.o是我们的目标,foo.c和defs.h是目标所依赖的源文件,而只有一个命令“cc -c -g foo.c”(以Tab键开头)。这个规则告诉我们两件事:
1、文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依赖关系发生。
2、如果生成(或更新)foo.o文件。也就是那个cc命令,其说明了,如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)
l 规则的语法
targets : prerequisites
command
...
command是命令行,如果其不与“target:prerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。
prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。
如果命令太长,你可以使用反斜框(‘’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。
一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。
l 在规则中使用通配符
如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三各通配符:“*”,“?”和“[...]”。这是和Unix的B-Shell是相同的。
波浪号(“~”)字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME目录下的test目录。而“~hchen/test”则表示用户hchen的宿主目录下的test目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是MS-DOS下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量“HOME”而定。
通配符代替了你一系列的文件,如“*.c”表示所以后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“”,如“*”来表示真实的“*”字符,而不是任意长度的字符串。
还是先来看几个例子吧:
clean:
rm -f *.o
上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令中的通配符。
objects = *.o
上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:
objects := $(wildcard *.o)
这种用法由关键字“wildcard”指出,wildcard是Makefile的关键字。
课余作业:
随便找一个Linux下的应用程序,提供源代码,tar.gz压缩格式的软件包。用上节介绍的命令解压。读该软件根目录下的Makefile文件。
3.3 嵌入式Linux系统引导过程
嵌入式Linux内核通常需要目标板上的固件引导。这些引导程序就是bootloader,在目标板上电的时候,完成板级初始化和Linux内核引导的任务。U-boot是一种常用的bootloader,本实验就是通过u-boot来引导的。
一般来说,嵌入式Linux系统启动过程都经过3个阶段,如下图所示:
嵌入式Linux系统启动过程
板子上电以后,首先执行bootloader,bootloader负责把Linux内核影像加载或者解压到RAM中。如果有ramdisk的话,也在这个阶段解压到RAM中。然后bootloader把控制权交给Linux内核。
Linux内核开始执行,初始化内存和硬件设备,挂接根文件系统,然后执行/sbin/init。启动第一个用户进程init,开始Linux用户空间的初始化过程。通常Linux内核影像也包含一段Linux bootloader的代码,完成zImage自解压功能。但是这不能完全替代bootloader固件。
Init进程根据inittab,执行系统初始化脚本,启动网络服务和X-windows等,并且管理用户登录。
这样Linux系统就完全启动起来了。
S3C2410EP实验板上电后,执行u-boot的第一条指令,顺序执行下列函数或者子程序,本手册中以u-boot-1.1.2为例:
_start: --> reset:
--> cpu_init_crit --> memsetup
--> relocate:
--> stack_setup:
--> start_armboot( ) --> init_sequence[]
--> …
--> main_loop()
这样u-boot就可以执行go或者bootm命令,引导Linux内核启动了。详细分析一下启动过程代码。
l cpu/arm920t/start.S
_start: b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
…
/* the actual reset code */
reset:
/* set the cpu to SVC32 mode */
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
/* turn off the watchdog */
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
#ifdef CONFIG_INIT_CRITICAL
bl cpu_init_crit
#endif
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_BASE /* test if we run from flash or RAM */
cmp r0, r1 /* don't reloc during debug */
beq stack_setup
ldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */
add r2, r0, r2 /* r2 <- source end address */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] */
stmia r1!, {r3-r10} /* copy to target address [r1] */
cmp r0, r2 /* until source end addreee [r2] */
ble copy_loop
/* Set up the stack */
stack_setup:
ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */
sub r0, r0, #CFG_MALLOC_LEN /* malloc area */
sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
clear_bss:
ldr r0, _bss_start /* find start of bss segment */
ldr r1, _bss_end /* stop here */
mov r2, #0x00000000 /* clear */
clbss_l:str r2, [r0] /* clear loop... */
add r0, r0, #4
cmp r0, r1
bne clbss_l
ldr pc, _start_armboot
_start_armboot: .word start_armboot
cpu_init_crit:
bl memsetup
l board/smdk2410/memsetup.S
.globl memsetup
memsetup:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, _TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
l lib_arm/board.c
void start_armboot (void)
{
DECLARE_GLOBAL_DATA_PTR;
ulong size;
init_fnc_t **init_fnc_ptr;
char *s;
/* Pointer is writable since we allocated a register for it */
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
/* compiler optimization barrier needed for GCC >= 3.4 */
__asm__ __volatile__("": : :"memory");
memset ((void*)gd, 0, sizeof (gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/* configure available FLASH banks */
size = flash_init ();
display_flash_config (size);
/* armboot_start is defined in the board-specific linker script */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
/* initialize environment */
env_relocate ();
/* IP Address */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* MAC Address */
…
devices_init (); /* get the devices list going. */
jumptable_init ();
console_init_r (); /* fully init console as a device */
/* enable exceptions */
enable_interrupts ();
/* Initialize from environment */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop() can return to retry autoboot, if so just run it again. */
for (;;) {
main_loop ();
}
/* NOTREACHED - no way out of command loop except booting */
}
init_fnc_t *init_sequence[] = {
cpu_init, /* basic cpu dependent setup */ --cpu/arm920t/cpu.c
board_init, /* basic board dependent setup */ --board/smdk2410/smdk2410.c
interrupt_init, /* set up exceptions */ --cpu/arm920t/s3c24x0/interrupt.c
env_init, /* initialize environment */ --common/cmd_flash.c
init_baudrate, /* initialze baudrate settings */ --lib_arm/board.c
serial_init, /* serial communications setup */ --cpu/arm920t/s3c24x0/serial.c
console_init_f, /* stage 1 init of console */ --common/console.c
display_banner, /* say that we are here */ --lib_arm/board.c
dram_init, /*configure available RAM banks */ --board/smdk2410/smdk2410.c
display_dram_config, --lib_arm/board.c
NULL,
};
main_loop() { } -- common/main.c
go 命令引导Linux内核的代码
l common/cmd_boot.c
int do_go (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong addr, rc;
int rcode = 0;
if (argc < 2) {
printf ("Usage:
%s
", cmdtp->usage);
return 1;
}
addr = simple_strtoul(argv[1], NULL, 16);
printf ("## Starting application at 0x%08lX ...
", addr);
/*
* pass address parameter as argv[0] (aka command name),
* and all remaining args
*/
rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);
if (rc != 0) rcode = 1;
printf ("## Application terminated, rc = 0x%lX
", rc);
return rcode;
}
U_BOOT_CMD(
go, CFG_MAXARGS, 1, do_go,
"go - start application at address 'addr'
",
"addr [arg ...]
- start application at address 'addr'
"
" passing 'arg' as arguments
"
);
bootm命令引导Linux内核的部分代码
l common/cmd_bootm.c
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong iflag;
ulong addr;
ulong data, len, checksum;
ulong *len_ptr;
uint unc_len = 0x400000;
int i, verify;
char *name, *s;
int (*appl)(int, char *[]);
image_header_t *hdr = &header;
s = getenv ("verify");
verify = (s && (*s == 'n')) ? 0 : 1;
if (argc < 2) {
addr = load_addr;
} else {
addr = simple_strtoul(argv[1], NULL, 16);
}
SHOW_BOOT_PROGRESS (1);
printf ("## Booting image at %08lx ...
", addr);
/* Copy header so we can blank CRC field for re-calculation */
memmove (&header, (char *)addr, sizeof(image_header_t));
if (ntohl(hdr->ih_magic) != IH_MAGIC)
{
puts ("Bad Magic Number
");
SHOW_BOOT_PROGRESS (-1);
return 1;
}
SHOW_BOOT_PROGRESS (2);
data = (ulong)&header;
len = sizeof(image_header_t);
checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (char *)data, len) != checksum) {
puts ("Bad Header Checksum
");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);
/* for multi-file images we need the data part, too */
print_image_hdr ((image_header_t *)addr);
data = addr + sizeof(image_header_t);
len = ntohl(hdr->ih_size);
if (verify) {
puts (" Verifying Checksum ... ");
if (crc32 (0, (char *)data, len) != ntohl(hdr->ih_dcrc)) {
printf ("Bad Data CRC
");
SHOW_BOOT_PROGRESS (-3);
return 1;
}
puts ("OK
");
}
SHOW_BOOT_PROGRESS (4);
len_ptr = (ulong *)data;
……
switch (hdr->ih_os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
do_bootm_linux (cmdtp, flag, argc, argv,
addr, len_ptr, verify);
break;
……
}
U_BOOT_CMD(
bootm, CFG_MAXARGS, 1, do_bootm,
"bootm - boot application image from memory
",
"[addr [arg ...]]
- boot application image stored in memory
"
" passing arguments 'arg ...'; when booting a Linux kernel,
"
" 'arg' can be the address of an initrd image
"
);
l lib_arm/armlinux.c
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
ulong addr, ulong *len_ptr, int verify)
{
DECLARE_GLOBAL_DATA_PTR;
ulong len = 0, checksum;
ulong initrd_start, initrd_end;
ulong data;
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
bd_t *bd = gd->bd;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
/*
* Check if there is an initrd image
*/
if (argc >= 3) {
SHOW_BOOT_PROGRESS (9);
addr = simple_strtoul (argv[2], NULL, 16);
printf ("## Loading Ramdisk Image at %08lx ...
", addr);
/* Copy header so we can blank CRC field for re-calculation */
memcpy (&header, (char *) addr, sizeof (image_header_t));
if (ntohl (hdr->ih_magic) != IH_MAGIC) {
printf ("Bad Magic Number
");
SHOW_BOOT_PROGRESS (-10);
do_reset (cmdtp, flag, argc, argv);
}
data = (ulong) & header;
len = sizeof (image_header_t);
checksum = ntohl (hdr->ih_hcrc);
hdr->ih_hcrc = 0;
if (crc32 (0, (char *) data, len) != checksum) {
printf ("Bad Header Checksum
");
SHOW_BOOT_PROGRESS (-11);
do_reset (cmdtp, flag, argc, argv);
}
SHOW_BOOT_PROGRESS (10);
print_image_hdr (hdr);
data = addr + sizeof (image_header_t);
len = ntohl (hdr->ih_size);
if (verify) {
ulong csum = 0;
printf (" Verifying Checksum ... ");
csum = crc32 (0, (char *) data, len);
if (csum != ntohl (hdr->ih_dcrc)) {
printf ("Bad Data CRC
");
SHOW_BOOT_PROGRESS (-12);
do_reset (cmdtp, flag, argc, argv);
}
printf ("OK
");
}
SHOW_BOOT_PROGRESS (11);
if ((hdr->ih_os != IH_OS_LINUX) ||
(hdr->ih_arch != IH_CPU_ARM) ||
(hdr->ih_type != IH_TYPE_RAMDISK)) {
printf ("No Linux ARM Ramdisk Image
");
SHOW_BOOT_PROGRESS (-13);
do_reset (cmdtp, flag, argc, argv);
}
/*
* Now check if we have a multifile image
*/
} else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
ulong tail = ntohl (len_ptr[0]) % 4;
int i;
SHOW_BOOT_PROGRESS (13);
/* skip kernel length and terminator */
data = (ulong) (&len_ptr[2]);
/* skip any additional image length fields */
for (i = 1; len_ptr[i]; ++i)
data += 4;
/* add kernel length, and align */
data += ntohl (len_ptr[0]);
if (tail) {
data += 4 - tail;
}
len = ntohl (len_ptr[1]);
} else {
/*
* no initrd image
*/
SHOW_BOOT_PROGRESS (14);
len = data = 0;
}
if (data) {
initrd_start = data;
initrd_end = initrd_start + len;
} else {
initrd_start = 0;
initrd_end = 0;
}
SHOW_BOOT_PROGRESS (15);
debug ("## Transferring control to Linux (at address %08lx) ...
",
(ulong) theKernel);
#if defined (CONFIG_SETUP_MEMORY_TAGS) ||
defined (CONFIG_CMDLINE_TAG) ||
defined (CONFIG_INITRD_TAG) ||
defined (CONFIG_SERIAL_TAG) ||
defined (CONFIG_REVISION_TAG) ||
defined (CONFIG_LCD) ||
defined (CONFIG_VFD)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd