最新日志

发表于:2007-12-16 23:58:50
标签:ARM  

1

转载:ARM开发板优惠活动:“天嵌ARM之旅”

学习ARM的朋友有好消息,SKY2440开发板优惠活动,现在买

S3C2440+3.5寸触摸屏ARM9开发板只需要999元,机会不可以错过啊。

需要的朋友就找我吧,我是负责他们的板子销售代理!

该文章转载自 ARM_SKY2440小组,以下是内容。

#1楼主:ARM开发板优惠活动:“天嵌ARM之旅”

文章发表于:2007-12-16 09:53

先解释一下这次活动的主题:天嵌是我们的公司名字,SKY也就是天嵌的意思,ARM之旅就是开发学习ARM的旅程,学完51单片机的朋友可以考虑学习ARM技术了,毕竟ARM是现在外面的主流!!

我举办这个活动的目的是为了推广ARM技术,让更多的朋友来一起学习。现在外面市场的ARM开发板价格非常昂贵,而且技术支持不足够,造成了很多ARM学习的朋友的误解,ARM很难学,开发板太贵了,一张开发板加上一个液晶屏都要1200以上,而且买了板子都不知道怎样学,没人教。

我以前做过一些ARM项目,争对开发过程的需要,我和朋友们一起研发了

天嵌 SKY2440 开发学习板,将所有ARM开发的功能都做在了板子上,而且我们工作室的成员,还编写了史上配套最详细的ARM9开发学习教程,有4百多页的资料和1G的板子配套学习资料,源代码,而且我们保持开发教程的不断更新,在我们的开发板上开发更多的功能。

SKY2440板子的详细介绍内容请到前面发的帖子查看,地址:

http://group.ednchina.com/550/7357.aspx

有兴趣的朋友可以到我们的公司网站下载这本开发教程:

下载网址:http://embedsky.net/

另外剑圣还做了一个板子的演示功能视频,包括SD卡,MP3,U盘的扩展,摄像头的采集,还有在开发板上实现上网功能,整个视频有20分钟。

SKY2440配套了3.5寸的三星触摸液晶屏,其实就是一台超级MP4,学ARM的朋友可以开发和娱乐两不误!

有兴趣的朋友可以到这个链接上看看我们的开发板的强大功能.

视频网址:http://www.tudou.com/programs/view/n-0sos0tK4U/

活动内容介绍我们的开发板采用S3C2440+3.5触摸屏,这一套开发板的定价本来是1200元一套,现在进行优惠活动,只要999元一套,不过这次数量有限,只能先报名订购的朋友才能享受这个价格。行动要快哦!!

报名条件只要是EDN的朋友都可以报名,数量有限制,只能一人买一套。

报名方式需要这套ARM开发板的朋友在这个帖子跟贴,然后到剑圣的淘宝拍下这个板子,付款之后,我们立刻发货,数量有限,卖完为止!

一定要在我的ARM_SKY2440小组跟贴,其他地方不起作用!

这个贴子的对外链接http://group.ednchina.com/550/post.aspx?id=7411

欢迎各位朋友转载给需要ARM的朋友,机会不可失!!

拍下板子的朋友,需要和剑圣联系,因为淘宝上的价格是1200,因为这个活动只是提供给EDN的朋友,为防止其他人在网上冒拍,需要修改价格后才能付款。

剑圣的淘宝地址

http://shop34991212.taobao.com/

剑圣的QQ85512015

剑圣的手机13516587191(有任何问题都可以打电话过去找他)

我们的服务承诺购买的板子提供技术支持,所有的问题都可以发到这个小组上询问,我一般是天天在线的,来问者必答!我们的板子提供3个月的包换,只要板子有任何质量问题都包换(不包括人为损坏),因为我的板子是经过多重测试,还有长时间的48小时拷机测试,绝对的稳定!

 

 

板子上电后显示的 wince 系统

点击看大图

 

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 原创

评论(6) | 阅读(1238)
发表于:2007-12-10 19:12:16
标签:无标签

1

上传一份虚拟机的详细使用说明文档

觉得上面内容写得非常详细,感谢这个文档的作者!!

rar

点击此处查看原文 >>

系统分类: ARM   |    用户分类:    |    来源: 原创

评论(5) | 阅读(996)
发表于:2007-12-10 19:06:05
标签:linux  

0

教你在虚拟机的linux上网,参数设置命令

在虚拟机装好了linux后,很多朋友都想实现在虚拟机里面上网,其实只要装好了linux。设置很简单。我说一说怎样设置吧。

先说说我的上网方式,我的电脑是用交换机上网的,用的是校园网。在交换机共享上网,每台机要设定好 IP,网关,掩码,还有DNS。所以我的虚拟机 linux也要设置好同样的参数。路由器我就不知道了,我想一般都是跟主机差不多的吧。

虚拟机的linux上网有两种方式,可以在虚拟机的网卡设置里设置,我设置的是桥接,不是外部连接。

在linux终端里面输入命令,可以执行一些功能设置。

比如  “ifconfig”  查看网卡设置情况。

“ifconfig eth0 192.168.0.230 netmask 255.255.255.0”设置网卡的IP 和掩码。

网卡的设置步骤:

1、检查本机IP和掩码,确定跟网关在同一个子网;
2、ping本机的IP地址(不是127.0.0.1),成功表示网卡工作正常;
3、关闭防火墙:iptables -F
4、ping网关的IP地址,成功表示网络正常;
5、检查缺省路由: route -n,看看其中的0.0.0.0是否是网关的IP;
6、ping一个外部的IP地址,成功表示网关工作正常;
7、检查DNS设置,试着解析一个域名: nslookup www.sina.com.cn
8、如果前面的全部通过,应该可以正常使用了。

在这里有一种最简单的设置方式,输入命令“netconfig -d eth0”就会弹出一个框,跟XP上的设置差不多一样的,就是设置IP,掩码,网关,还有DNS,4个参数,不过这时候是用键盘设置的,鼠标用不了!!

对LINUX有兴趣的朋友,我建议看看一些linux的教程,其实都是很简单的,你不去看就很难,命令打多了,就记住了!!!

希望各位朋友都能在虚拟机上上网!!

点击此处查看原文 >>

系统分类: ARM   |    用户分类:    |    来源: 原创

评论(0) | 阅读(1013)
发表于:2007-12-10 18:54:05
标签:linux  

0

如何在虚拟机中装好linux系统,有视频教程

这阵子在我的赛扬1.7机子上装上了VMWARE虚拟机软件,并顺利的安装了

linux red hat 9.0,这样就可以跟板子的linux连接了,方便以后的调试和编译。对学习 ARM来说非常的方便,现在很多朋友的电脑配置都很高了,我也打算换台双核的来玩。

这里给几个建议在虚拟机安装 linux的朋友,我自己走了好多弯路,所以希望能帮助一些朋友。

1. 首先你要下载 red hat 9.0 的三个ISO文件,网上很多地方有得下,我刚开始装了一个Federo版本的,觉得这个linux的内核比较新,装到最后很多东西都用不了。所以大家一定要装 red hat 9.0,不然以后就很多麻烦。

2. 下载的VMware虚拟机软件一定要下载完整板的,版本最好是5.5.  不要下载绿色版,简化板,因为到最后你装完了 linux后,要装工具时,会发现绿色板和简化板少了这个文件。自己很难在网上下载到 linux.iso这个文件。所以强烈建议下载 5.5 完整板。

3. 安装的时候要预留至少8G以上的硬盘空间,安装linux的时候,要选择安装所有的软件,linux的软件包很多,大概有4G多,完全安装那些软件有好处,省得你以后去下载,安装。linux的安装比windows的复杂多了,我到现在都还装不上QQ软件。

4. 推荐一个很多虚拟机的应用视频还有工具下载链接,虚拟机之家网络硬盘。

里面有个动画教程,包括了linux的安装,还有tools的安装,还有其他的很多动画教程,非常适合在虚拟机学习的朋友。

http://xuniji.ys168.com/

截个图片看看上面的内容:

放上一篇linux的安装说明,不过不是red hat 9.0的安装,仅供参考,具体内容自己去虚拟机之家硬盘里面下载。

虚拟机安装linux 
xp下虚拟机安装:
(一)软件环境:
win xp系统
vmware 5.0
Fedora Core linux3.0   下载:ftp://ftp.net.usf.edu/pub/fedora/linux/core/3/i386/iso/FC3-i386-DVD.iso
 
(二)虚拟机的安装:
  安装VMware,需要执行VMware-workstation-5.0.0-13124.exe,可以安装在任何路径,安
装过程中只需要下一步就可以了。安装完成之后会在桌面出现“VMware Workstation”的可
执行文件的快捷图标。
(三)创建虚拟机环境:
  运行VMware Workstation,双击图中的“New Virtual Machine”,依次单击下一步,直到出现要求选择操作系统的界面,选择“linux" Version 选择“Other Linux 2.6x kernel,”然后依次点击下一步,直到要求选择磁盘容量,把Disk size 修改为6GB,注意这里的“Allocate all disk space now”不要选中(选中的话,表示所分配的空间都被此虚拟机占用,WINDOWS 无法使用这6GB 的空间。不选中就表示虚拟机是动态调整的,例如:虚拟机只用到4GB,另外的2GB 还是可以被WINDOW 使用的)。选择“完成”后就完成虚拟机环境的创建。
(四)安装linux
在建立的虚拟机环境的主界面中选择“CD-ROM(IDE 1:0)”,然后选择选择“Use ISO image”导入“FC3-i386-DVD.iso”文件,选择OK。执行主界面中的“start this virtual machine”,然后在出现的界面,依次单击下一步,直到出现下图的语言选择界面,选择“简体中文”,依次点击“下一步”,直到出现要求选择分区的界面,选择用Disk druid手工分区,会出现"警告界面",选择“是”,出现磁盘设置界面,选择“新建”,设置/bootext3,100MB;SWAP,500MB;/,选择剩余空间,依次点击下一步,直到出现防火墙设置界面,选择“无防火墙”,依次点击下一步,直到出现软件安装包设置,选择“定制要安装的软件包”选择“定制要安装的软件包”,下一步,会出现有多个设置选项的界面,在默认设置的情况
下,需要有一些添加和删除选择:在桌面项目中添加KDE 和XFCE;在应用程序项目中去掉“办公/效率”和“视频和音频”;在服务器项目中添加选中服务器配置工具,网络服务器(细节中选择2,3),遗留网络服务器(细节中选3,4,6,8,9),在开发项目中全部选择。如果硬盘空间足够,也可以选择完全安装.......提示重新引导,依次下一步,中间会有系统用户的设置,输入用户名和密码继续,直到完
成安装。
(五)安装安装VMware Tools
安装VMware Tools 可以实现鼠标在虚拟机环境和WINDOWS 环境的方便切换(如果没有安装此工具,鼠标从虚拟机的Linux 环境中切换到WINDOWS 下,需要同时按Ctrl+Alt键,安装后鼠标可以直接移出);还可以使Linux 访问和操作WINDOWS 的目录。选择VM\Install VMware Tools…,选择之后,桌面中的光盘图标变为VMware Tools,双击此光盘图标, 会看到两个文件: VMwareTools-5.0.0-13124.i386.rpm 和
VMwareTools-5.0.0-13124.tar.gz。这里的安装有两种方法:
1、 直接双击VMwareTools-5.0.0-13124.i386.rpm 开始运行,完成之后打开终端,执行
vmware-config-tools.pl,开始安装,中间会出现一些确认选项,一直回车,直到出现设置屏幕大小的,选择一个然后回车(如选择3,1024×768),继续直到完成安装。
2、 打开终端, cd 到/media/cdrom/ ( 此处是光盘所在的位置, 在此位置下应该有
VMwareTools-5.0.0-13124.i386.rpm 和VMwareTools-5.0.0-13124.tar.gz 两个文件文件),
执行如下命令:
cp VMwareTools-5.0.0-13124.tar.gz /tmp //拷贝文件到tmp 目录下
cd /tmp //CD 到tmp 目录下
tar xzf VMwareTools-5.0.0-13124.tar.gz //解压文件
cd vmware-tools-distrib //CD到vmware-tools-distrib 目录下
./vmwre-install.pl //开始安装中间会出现一些确认项,一直回车
(六)设置共享文件
共享文件的设置有两种方法:
1、 打开VM\Setting,选择Options\Share Folders,添加一个共享文件,如在Linux 共享名share,WINDOWS 端的Host Folder 为:D:\os_share,打开终端,在/mnt/hgfs/share 目录下就可以访问到D:\os_share 目录下的文件了。
2、 mount 方式
把WINDOWS 系统下的某个文件共享,然后通过mount 命令把此文件共享到Linux 目录下。如:mount –t smbfs //192.168.2.110/test /mnt/share –o username="your"_user_name,workgroup=your_nt_domain这样就把共享的test 目录文件共享到Linux 目录下的/mnt/share 中了,在Linux 中可以操作此目录中的文件了。其中192.168.2.110 是本机的IP 地址。

进入linux后,运行命令装tools工具用到的命令
/sbin/telinit 3 进入文本模式
选择“VM”菜单的“install VMware Tools”
mount /dev/cdrom /mnt/cdrom 挂载光驱
cd /mnt/cdrom
cp vmware* /tmp/
cd /tmp
tar zvxf vmware*
cd vmware-t*
./*.pl
 
 

 

点击此处查看原文 >>

系统分类: ARM   |    用户分类:    |    来源: 原创

评论(0) | 阅读(1527)
发表于:2007-11-30 16:18:04
标签:单片机  ARM  

0

庆祝“佛山电子论坛”建立,包含ARM,51单片机,proteus技术学习

近来太多的朋友加入proteus仿真学习,还有一部分朋友已经在学习ARM了。为了让朋友们能有一个很好的交流空间,我和谢威潮大哥一起建立了这个“佛山电子论坛”,因为我们都是在佛山学习和工作,网站的名字“http://www.fsmcu.cn/”,      佛山mcu。很好记吧!!这个论坛是刚刚建立的,希望大家以后常来交流,我们会尽我们最大的努力把论坛建设得更好!!!

佛山电子论坛的网址是:http://www.fsmcu.cn/bbs/

我们开办了ARM,51单片机,还有proteus仿真3个栏目,每个栏目都有一些技术厉害的朋友来当版主,尽力答复朋友们的问题,和朋友们一起交流。特别是ARM栏目的版主,我们邀请了SKY2440开发学习板的ARM工程师来做我们的ARM版主,希望以后的朋友可以多上去交流。

我们办这个论坛的宗旨是:从51到ARM技术。让更多的朋友们在技术上有更多的提升。

我已经把博客上的proteus内容转移到论坛了,有兴趣的朋友可以去看看!

点击看大图

 

点击看大图

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 原创

评论(3) | 阅读(1190)
发表于:2007-11-30 12:12:06
标签:ARM  

0

关于JTAG并口延长线的购买

ARM板上的那条JTAG线比较短,一般需要并口线来延长。电脑城里面有很多种的并口线,有些朋友不懂需要买哪一种,现在我拍上图片,希望各位朋友不要买错了线哦!!并口的接头是一公一母,自己看图片拉!!

点击看大图

 

点击看大图

 

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 原创

评论(0) | 阅读(819)
发表于:2007-11-22 18:55:17
标签:ARM  

0

转载:U-BOOT移植到阳初2410的板子

这个好东西是从阳初论坛上下载的。我自己还没有研究,希望有兴趣的朋友可以研究一下。附件上有源代码,还有专门的word文档说明。以后研究了再放上更多的详细介绍,先发给大家玩玩!最近忙考试和制作简历,没时间!!呵呵!!!!!

以下是作者在论坛的讨论的内容:

晕死,发了半天,居然都没发上来,说我字数限制,我都没几个字。

只能传了。


写的匆忙,还没来得及改,有错的地方,望各位指出。^_^

 我的工作环境:

     RedHat 9

      arm-linux-gcc 4.1.1(glibc2.3.2)

      U-BOOT1.2.0

 目标板:阳初S3C2410开发板V2.3版

         买回来,板子上没有NOR FLASH,给的也只有VIVI,我想用U-BOOT(这个用的比较广),所以只能修改U-BOOT从NAND FLASH启动了。

我的内容包括:

      NAND FLASH启动;

      NAND FLASH驱动,让U-BOOT可以对NAND FLASH读写;

      环境变量保存到NAND FLASH。

      引导linux内核

 参照的主要是网上的文章,我只不过相当于拼凑了一下。呵呵。

 

我晕,大小上传也只有100KB。只能压缩传了。

顺便把U-BOOT中修改后的几个文件也发上来:

点击浏览该文件
 

编译后的U-BOOT.bin,可以利用JTAG烧写到NAND FLASH运行的:


点击浏览该文件

晕,之前做的U-BOOT不能正确阳初光盘编译好的linux映像内核。

下载到板子上0x30008000运行,不行,用bootm和go命令的结果都是和前面的结果一样,最后死在这里:

……

……

Uncompressing Linux....................................................... done, booting the kernel.

往下就没了。网上查了很多文章,说法不一,各人的遇到的特殊情况不同吧。

于是,我用2.6.20.3源码make s3c2410_defconfig默认配置,编译了一下,在板子上跑了一下,结果跑起来了,但是查看启动信息,发现U-BOOT的bootargs并没传递给内核:

U-BOOT的bootargs如下:

bootargs=root=/dev/ram rw initrd="0x308000",6000000 console="ttyS0",115200 mem="64M"

而内核启动显示如下:

Kernel command line: root="/dev/hda1" ro init="/bin/bash" console="ttySAC0"

说明U-BOOT并没有设置bootargs到参数里去。
再查看U-BOOT的include/configs/yangchu2410.h文件,发现没有定义如下:

#define CONFIG_CMDLINE_TAG  1   /* enable passing of ATAGs  */

#define CONFIG_SETUP_MEMORY_TAGS 1

#define CONFIG_INITRD_TAG   1

而#define CONFIG_CMDLINE_TAG    1是在传递cmdline时候必须设置的,所以在include/configs/yangchu2410.h文件中添加如上定义。

定义之后,重新编译U-BOOT,烧写到NAND FLASH中,启动板子。

再跑上面编译的2.6.20.3linux,内核启动信息中显示的参数信息和U-BOOT中设置的一致了。

设置U-BOOT的bootargs:noinitrd root="/dev/mtdblock/2" console="ttyS0" mac="00:01:5d:68:7a:0f",再跑阳初光盘带的linxu内核,往下看到兴奋的字眼了:

YANGCHU2410 # bootm 30008000

## Booting image at 30008000 ...

   Image Name:   yangchu_linux2.4.18

   Created:      2007-08-26   8:06:16 UTC

   Image Type:   ARM Linux Kernel Image (uncompressed)

   Data Size:    772272 Bytes = 754.2 kB

   Load Address: 30008000

   Entry Point:  30008040

   Verifying Checksum ... OK

   XIP Kernel Image ... OK


Starting kernel ...


Uncompressing Linux....................................................... done, booting the kernel.

Linux version 2.4.18-rmk7-pxa1 (fc@localhost.localdomain) (gcc version 2.95.3 20010315 (release)) #441  7 10 11:29:04 CST 2007

CPU: ARM/CIRRUS Arm920Tsid(wb) revision 0

Machine: Samsung-SMDK2410

On node 0 totalpages: 16384

zone(0): 16384 pages.

zone(1): 0 pages.

zone(2): 0 pages.

Kernel command line: noinitrd root="/dev/mtdblock/2" console="ttyS0" mac="00:01:5d:68:7a:0f"

……

……


楼顶的文章我已经改过了,上传了我更新过的文章。

相应要要修改的U-BOOT以及编译好的uboot.bin也重新上传了

UBOOT引导内核真是搞怪,前几天还能引导的,这几天又不能引导了,但是这些内核用VIVI都是能引导的,搞得头痛死了,总是死在下面这个地方:
Uncompressing Linux....................................................... done, booting the kernel.

后来发现是频率的问题。因为网上有人也遇到此问题,相关说法如下:

    之后用仿真器追踪内核结果发现:

    start_kernel函数的time_init()中程序进入死循环~~

    我随之进入time_init()

    继续进入        system_timer->init();
    
    结果发现,我的fclk,pclk都不能得到正确的值,积存器正确,我拿笔都能算出来,可是他就是总出错,所以我一气之下把他们全部写死.

    具体如下:
       在\arch\arm\mach_s3c2410\s3c2410.c 中 搜索 fclk ;

       fclk = s3c2410_get_pll(MPLLCON, xtal);   //这是他原来的,结果不是我们想要的200MHZ


            fclk =200*1000*1000 ; // 这是我加的

       在\arch\arm\mach_s3c2410\time.c 中搜索 pclk:

        pclk =   clk_get_rate(clk);         //这是原来的

        pclk = 50*1000*1000;           //这是我加的

       当然这都是根据2410的DATASHEET而定的
 
       我现在用的是FCLK : HCLK : PCLK  =  1 : 2 : 4 ,所以我分别写死为200M 100M 50M.

看来我的问题差不多也出在这边了,fclk得不到正确的值,由于没有仿真器,只能这么推测了。因为我终端上显示出来的传递参数都没有问题。

我再看了看VIVI,它里面用的就是200M。

smdk2410的U-BOOT原来运行频率是202.8M,后来把U-BOOT的频率改成200M,2.6.20.3的内核和阳初光盘的2.4.18的内核都能稳定地引导了。

修改文件如下:

"board/yangchu2410/smdk2410.c"

#define FCLK_SPEED 1

#if FCLK_SPEED==0  /* Fout = 203MHz, Fin = 12MHz for Audio */
#define M_MDIV 0xC3
#define M_PDIV 0x4
#define M_SDIV 0x1
#elif FCLK_SPEED==1  /* Fout = 202.8MHz */
//#define M_MDIV 0xA1
//#define M_PDIV 0x3
//#define M_SDIV 0x1
#define M_MDIV 0x5c  /* Fout = 200MHz */
#define M_PDIV 0x4
#define M_SDIV 0x0
#endif

源代码:

rar

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(1389)
发表于:2007-11-22 18:41:06
标签:ARM  

0

转:S3C2410 bootloader ----VIVI阅读笔记

建议读一读《嵌入式系统Boot Loader技术内幕》(詹荣开著),google一下就会找到一片。什么是Bootloader就不再这里废话了,看看上面的文章就明了了。 Bootloader有很多种,如本文将要阅读的vivi,除此之外还有uboot,redboot,lilo等等。Vivi 是韩国mizi公司专门为三星s3c2410芯片设计的Bootloader。
先来看看vivi的源码树:
vivi-+-arch-+-s3c2410
|-Documentation
|-drivers-+-serial
|           ‘-mtd-+-maps
|                  |-nor
|                  ‘-nand
|-include-+-platform
|           |-mtd
|           ‘-proc
|-init
|-lib-+-priv_data
|-scripts-+-lxdialog
|-test
|-util
可以google一下,搜到源码vivi.tar.gz。
前面提到的文件已经系统的分析了bootloader的,这里就按源代码来具体说事。vivi也可以分为2个阶段,阶段1的代码在arch/s3c2410/head.S中,阶段2的代码从init/main.c的main函数开始。

阶段1

阶段1从程序arch/s3c2410/head.S开始,按照head.S的代码执行顺序,一次完成了下面几个任务:
1、关WATCH DOG (disable watch dog timer)
上电后,WATCH DOG默认是开着的
2、禁止所有中断 (disable all interrupts)
vivi中不会用到中断,中断是系统的事,bootloader可不能去干这事的(不过这段代码实在多余,上电后中断默认是关闭的)
3、初始化系统时钟(initialise system clocks)
启动MPLL,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz,“CPU bus mode”改为“Asynchronous bus mode”。
4、初始化内存控制寄存器(memsetup)
S3c2410共有15个寄存器,在此开始初始化13个寄存器。
5、检查是否从掉电模式唤醒(Check if this is a wake-up from sleep)
若是,则调用WakeupStart函数进行处理。
6、点亮所有LED (All LED on)
点一下灯,通知外面的同志,告诉他们有情况发生。
7、初始化UART0 (set GPIO for UART & InitUART)
a.设置GPIO,选择UART0使用的引脚
b.初始化UART0,设置工作方式(使用FIFO)、波特率115200 8N1、无流控等。这可是使用串口与s3c2410通信的条件啊,在终端也要如此设置。
8、跳到内存测试函数(simple memory test to find some DRAM flaults)
当然要定义了CONFIG_BOOTUP_MEMTEST这个参数才会跳到内存测试。
9、如果定义了以Nand flash方式启动(#ifdef CONFIG_S3C2410_NAND_BOOT),则此时要将vivi所有代码(包括阶段1和阶段2)从Nand flash复制到SDRAM中(因为在Nand flash中是不能执行程序的,它只能做为程序和数据的存储器,而Nor flash可就不同了,Nor flash可以执行程序,但贵是它发展得瓶颈):
a.设置nand flash控制寄存器
b.设置堆栈指针
c.设置即将调用的函数nand_read_ll的参数:r0=目的地址(SDRAM的地址),r1=源地址(nand flash的地址),r2=复制的长度(以字节为单位)
d.调用nand_read_ll进行复制
  10、跳到bootloader的阶段2运行,亦即调用init/main.c中的main函数(get read to call C functions)
a.重新设置堆栈
b.设置main函数的参数
c.调用main函数
head.S有900多行,都是些arm汇编,看的云山雾罩,汇编看来是忘的差不多了,所以这部分代码也看的相当糙,只知道大概在干什么,至于个中缘由就不是很了解。先学学arm汇编再回来看。

阶段2

从init/main.c中的main函数开始,终于步入C语言的世界了。Main函数总共有8步(8 steps),先看看源代码:
int main(int argc, char *argv[])
{
        int ret;
       
/*
        * Step 1:
        */
        putstr("\r\n");
        putstr(vivi_banner);    //vivi_banner是vivi执行开始的显示信息,vivi_banner在文件version.c中定义
        reset_handler();

        /*
         * Step 2:
         */
        ret = board_init();
        if (ret) {
                putstr("Failed a board_init() procedure\r\n");
                error();
        }

        /*
         * Step 3:
         */
        mem_map_init();
        mmu_init();
        putstr("Succeed memory mapping.\r\n");

        /*
         * Now, vivi is running on the ram. MMU is enabled.
         * Step 4:
     */
        /* initialize the heap area*/
        ret = heap_init();
        if (ret) {
                putstr("Failed initailizing heap region\r\n");
                error();
        }

        /* Step 5:
         * MTD
         */
        ret = mtd_dev_init();

        /* Step 6:
         */
        init_priv_data();

        /* Step 7:
*/
        misc();
        init_builtin_cmds();

        /* Step 8:
         */
        boot_or_vivi();
        return 0;
}
下面按照上面的步骤逐步来分析一下。
1、Step 1:reset_handler()
reset_handler用于将内存清零,代码在lib/reset_handle.c中。
1  void
2  reset_handler(void)
3  {
4      int pressed;
5      pressed = is_pressed_pw_btn();  /*判断是硬件复位还是软件复位*/
6      if (pressed == PWBT_PRESS_LEVEL) {
7          DPRINTK("HARD RESET\r\n");
8          hard_reset_handle();        /*调用clear_mem对SDRAM清0*/
9      } else {
10         DPRINTK("SOFT RESET\r\n");
11         soft_reset_handle();        /*此函数为空*/
12     }
13  }
    在上电后,reset_handler调用第8行的hard_reset_handle(),此函数在lib/reset_handle.c中:
[main(int argc, char *argv[]) -> reset_handler() -> hard_reset_handle()]
1  static void
2  hard_reset_handle(void)
3  {
4  #if 0
5      clear_mem((unsigned long)(DRAM_BASE + VIVI_RAM_ABS_POS), \
6      (unsigned long)(DRAM_SIZE - VIVI_RAM_ABS_POS));
7  #endif
/*lib/memory.c,将起始地址为USER_RAM_BASE,长度为USER_RAM_SIZE的内存清0*/
8   clear_mem((unsigned long)USER_RAM_BASE, (unsigned long) USER_RAM_SIZE);
9  }
先写到这儿吧。
(未完待续)


S3C2410 bootloader ----VIVI阅读笔记2(续笔记1)

2、Step 2:board_init()    
board_init调用2个函数用于初始化定时器和设置各GPIO引脚功能,代码在arch/s3c2410/smdk.c中:
[main(int argc, char *argv[]) > board_init()]
1  int board_init(void)
2  {
3      init_time();  /*arch/s3c2410/proc.c*/
4      set_gpios();  /*arch/s3c2410/smdk.c */
5      return 0;
6  }
init_time() 这个函数对寄存器进行了简单的操作:
void init_time(void)
{
        TCFG0 = (TCFG0_DZONE(0) | TCFG0_PRE1(15) | TCFG0_PRE0(0));
        /*s3c2410 data sheet P298*/
        /*TCFG0 = 0 | 0xf00 | 0 */
}
寄存器TCFG0由三部分组成,prescaler0,prescaler1,deadzone和reserve四部分,前三部分分别对应 TCFG0_PRE0、TCFG0_PRE1、TCFG0_DZONE,TCFG0_PRE0(0)实际值为0x00,TCFG0_PRE1(15)实际值为0x0f00,而TCFG0_DZONE(0)实际值为 0x000000。实际中,vivi并未使用定时器,这个函数就可以忽略。set_gpios()用于选择GPA至GPH端口各引脚的功能及是否使用各引脚的内部上拉电阻,并设置外部中断源寄存器EXTINT0-2(vivi中未使用外部中断)。
1        void set_gpios(void)
2        {
3                GPACON  = vGPACON;
4                GPBCON  = vGPBCON;
5                GPBUP   = vGPBUP;
6                GPCCON  = vGPCCON;
7                GPCUP   = vGPCUP;
8                GPDCON  = vGPDCON;
9                GPDUP   = vGPDUP;
10                GPECON  = vGPECON;
11                GPEUP   = vGPEUP;
12                GPFCON  = vGPFCON;
13                GPFUP   = vGPFUP;
14                GPGCON  = vGPGCON;
15                GPGUP   = vGPGUP;
16                GPHCON  = vGPHCON;
17                GPHUP   = vGPHUP;
18                EXTINT0 = vEXTINT0;
19                EXTINT1 = vEXTINT1;
20                EXTINT2 = vEXTINT2;
21        }
        以第三行为例,vGPACON的值为0x007fffff,查找s3c2410用户手册可知,该参数将GPACON的23位全部置1。各位功能需察看s3c2410用户手册

3、Step 3:建立页表和启动MMU
          mem_map_init();
mmu_init();

mem_map_init函数用于建立页表,vivi使用段式页表,只需要一级页表。它调用3个函数,代码在arch/s3c2410/mmu.c中:
[main(int argc, char *argv[]) > mem_map_init(void)]
1        void mem_map_init(void)
2        {
3        #ifdef CONFIG_S3C2410_NAND_BOOT        
/*CONFIG_S3C2410_NAND_BOOT = y ,在文件include/autoconf.h中定义*/
4                mem_map_nand_boot();      
/* 最终调用mem_mepping_linear, 建立页表 */
5        #else
6                mem_map_nor();
7        #endif
8                cache_clean_invalidate();/* 清空cache,使无效cache */ 
9                tlb_invalidate();        /* 使无效快表TLB */
10        }
第9、10行的两个函数可以不用管它,他们做的事情在下面的mmu_init函数里又重复了一遍。对于本开发板,在.config中定义了 CONFIG_S3C2410_NAND_BOOT。mem_map_nand_boot()函数调用mem_mapping_linear()函数来最终完成建立页表的工作。页表存放在SDRAM物理地址0x33dfc000开始处,共16K:一个页表项4字节,共有4096个页表项;每个页表项对应 1M地址空间,共4G。mem_map_init先将4G虚拟地址映射到相同的物理地址上,NCNB(不使用cache,不使用write buffer)——这样,对寄存器的操作跟未启动MMU时是一样的;再将SDRAM对应的64M空间的页表项修改为使用cache。 mem_mapping_linear函数的代码在arch/s3c2410/mmu.c中:
[main(int argc, char *argv[]) > mem_map_init(void) > mem_map_nand_boot( ) > mem_mapping_linear(void)]
1  static inline void mem_mapping_linear(void)
2  { 
3      unsigned long pageoffset, sectionNumber;
4            putstr_hex("MMU table base address = 0x", (unsigned long)
mmu_tlb_base);
5       /* 4G 虚拟地址映射到相同的物理地址. not cacacheable, not bufferable */
6       /* mmu_tlb_base = 0x33dfc000*/
7       for (sectionNumber = 0; sectionNumber < 4096; sectionNumber++) {
8               pageoffset = (sectionNumber << 20);
9                 *(mmu_tlb_base + (pageoffset >> 20)) = pageoffset |
            MMU_SECDESC;
10      }
             
11      /* make dram cacheable */
12      /* SDRAM物理地址0x3000000-0x33ffffff,
13          DRAM_BASE=0x30000000,DRAM_SIZE=64M
14      */
15      for (pageoffset = DRAM_BASE; pageoffset < (DRAM_BASE+DRAM_SIZE); \
16          pageoffset += SZ_1M) {
17          //DPRINTK(3, "Make DRAM section cacheable: 0x%08lx\n",             pageoffset);
18          *(mmu_tlb_base + (pageoffset >> 20)) = \
pageoffset | MMU_SECDESC | MMU_CACHEABLE;
19      }
20 }
mmu_init()函数用于启动MMU,它直接调用arm920_setup()函数。arm920_setup()的代码在arch/s3c2410/mmu.c中:
[main(int argc, char *argv[]) > mmu_init( ) > arm920_setup( )]
1  static inline void arm920_setup(void)
2  {
3                  unsigned long ttb = MMU_TABLE_BASE;
/* MMU_TABLE_BASE = 0x33dfc000 */
4  __asm__(
5       /* Invalidate caches */
6       "mov    r0, #0\n"
7       "mcr    p15, 0, r0, c7, c7, 0\n"    /* invalidate I,D caches on v4 */
8       "mcr    p15, 0, r0, c7, c10, 4\n"   /* drain write buffer on v4 */
9       "mcr    p15, 0, r0, c8, c7, 0\n"    /* invalidate I,D TLBs on v4 */
           10      /* Load page table pointer */
11      "mov    r4, %0\n"
12      "mcr    p15, 0, r4, c2, c0, 0\n"    /* load page table pointer */
13      /* Write domain id (cp15_r3) */
          14      "mvn    r0, #0\n"    /* Domains 0b01 = client, 0b11=Manager*/
          15      "mcr    p15, 0, r0, c3, c0, 0\n"
/* load domain access register,write domain 15:0, 用户手册P548(access permissions)*/
16      /* Set control register v4 */
17      "mrc    p15, 0, r0, c1, c0, 0\n"    /* get control register v4 */
            /*数据手册P545:read control register */
18      /* Clear out 'unwanted' bits (then put them in if we need them) */
19      /* ..VI ..RS B... .CAM */   /*这些位的含义在数据手册P546*/
20      "bic r0, r0, #0x3000\n"  /* ..11 .... .... .... */
            /*I(bit[12])=0 = Instruction cache disabled*/
21      /*V[bit[13]](Base location of exception registers)=0 = Low addresses = 0x0000 0000*/
22      "bic r0, r0, #0x0300\n"      /* .... ..11 .... .... */
             
23      /*R(ROM protection bit[9])=0*/
                   /*S(System protection bit[8])=0*/
                   /*由于TTB中AP=0b11(line141),所以RS位不使用(P579)*/
24      "bic r0, r0, #0x0087\n"      /* 0x0000000010000111 */
                /*M(bit[0])=0 = MMU disabled*/
                /*A(bit[1])=0 =Data address alignment fault checking disable*/
                /*C(bit[2])=0 = Data cache disabled*/
                /*B(bit[7])=0= Little-endian operation*/
25      /* Turn on what we want */
26      /* Fault checking enabled */
27      "orr r0, r0, #0x0002\n"      /* .... .... .... ..10 */
            /*A(bit[1])=1 = Data address alignment fault checking enable*/
28                  #ifdef CONFIG_CPU_D_CACHE_ON     /*is not set*/
29      "orr    r0, r0, #0x0004\n"      /* .... .... .... .100 */
            /*C(bit[2])=1 = Data cache enabled*/
30 #endif 
31 #ifdef CONFIG_CPU_I_CACHE_ON     /*is not set*/
32      "orr    r0, r0, #0x1000\n"  /* ...1 .... .... .... */
            /*I(bit[12])=1 = Instruction cache enabled*/
33 #endif 
              
34          /* MMU enabled */
35                "orr    r0, r0, #0x0001\n"      /* .... .... .... ...1 */
            /*M(bit[0])=1 = MMU enabled*/
36                "mcr    p15, 0, r0, c1, c0, 0\n"    /* write control register */
            /*数据手册P545*/
37                : /* no outputs */
38                : "r" (ttb) );
39 }

[未完]


S3C2410 bootloader ----VIVI阅读笔记3

4、Step 4:heap_init()    
第4步调用了heap_init(void)函数,并返回值。该值是函数heap_init()调用的mmalloc_init()函数的返回值。其实,这步就是申请一块内存区域。
[lib/heap.c->heap_init(void)]
1        int heap_init(void)
2        {
3                return mmalloc_init((unsigned char *)(HEAP_BASE), HEAP_SIZE);      
4        }
内存动态分配函数mmalloc就是从heap(堆)中划出一块空闲内存。相应的mfree函数则将动态分配的某块内存释放回heap中。
heap_init函数在SDRAM中指定了一块1M大小的内存作为heap(起始地址HEAP_BASE = 0x33e00000),并在heap的开头定义了一个数据结构blockhead。事实上,heap就是使用一系列的blockhead数据结构来描述和操作的。每个blockhead数据结构对应着一块heap内存,假设一个blockhead数据结构的存放位置为A,则它对应的可分配内存地址为“A + sizeof(blockhead)”到“A + sizeof(blockhead) + size - 1”。blockhead数据结构在lib/heap.c中定义:
1  typedef struct blockhead_t {
2       int32 signature;     //固定为BLOCKHEAD_SIGNATURE
3       bool allocated;      //此区域是否已经分配出去:0-N,1-Y
4       unsigned long size;  //此区域大小
5       struct blockhead_t *next;   //链表指针
6       struct blockhead_t *prev;   //链表指针
7  } blockhead;
现在来看看heap是如何运作的(如果您不关心heap实现的细节,这段可以跳过)。vivi对heap的操作比较简单,vivi中有一个全局变量 static blockhead *gHeapBase,它是heap的链表头指针,通过它可以遍历所有blockhead数据结构。假设需要动态申请一块sizeA大小的内存,则 mmalloc函数从gHeapBase开始搜索blockhead数据结构,如果发现某个blockhead满足:
(1) allocated = 0  //表示未分配
(2) size > sizeA,则找到了合适的blockhead,
满足上述条件后,进行如下操作:
a.allocated设为1
b.如果size – sizeA > sizeof(blockhead),则将剩下的内存组织成一个新的blockhead,放入链表中
c.返回分配的内存的首地址释放内存的操作更简单,直接将要释放的内存对应的blockhead数据结构的allocated设为0即可。
heap_init函数直接调用mmalloc_init函数进行初始化,此函数代码在lib/heap.c中,比较简单,初始化gHeapBase即可:
[main(int argc, char *argv[]) > heap_init(void) > mmalloc_init(unsigned char *heap, unsigned long size)]
1         static inline int mmalloc_init(unsigned char *heap, unsigned long size)
2         {
3      if (gHeapBase != NULL) return -1;
    4    DPRINTK("malloc_init(): initialize heap area at 0x%08lx, size = 0x%08lx\n", heap, size);
5       gHeapBase = (blockhead *)(heap);
6       gHeapBase->allocated=FALSE;
7       gHeapBase->signature=BLOCKHEAD_SIGNATURE;
8       gHeapBase->next=NULL;
9       gHeapBase->prev=NULL;
10      gHeapBase->size = size - sizeof(blockhead);
11      return 0;
12         }
static blockhead *gHeapBase = NULL; 这个就是上面称赞的全局变量了,定义在lib/heap.c中。上面就是个链表操作,数据结构,看来搞这个也得好好学数据结构啊,不然内存搞的溢出、浪费可就哭都来不及了。

5、Step 5:mtd_dev_init()  
所谓MTD(Memory Technology Device)相关的技术。在linux系统中,我们通常会用到不同的存储设备,特别是FLASH设备。为了在使用新的存储设备时,我们能更简便地提供它的驱动程序,在上层应用和硬件驱动的中间,抽象出MTD设备层。驱动层不必关心存储的数据格式如何,比如是FAT32、ETX2还是FFS2或其它。它仅仅提供一些简单的接口,比如读写、擦除及查询。如何组织数据,则是上层应用的事情。MTD层将驱动层提供的函数封装起来,向上层提供统一的接口。这样,上层即可专注于文件系统的实现,而不必关心存储设备的具体操作。这段乱七八糟的话也许比较让人晕,也可以这样理解在设备驱动(此处指存储设备)和上层应用之间还存在着一层,共三层,这个中间层就是MTD技术的产物。通常可以将它视为驱动的一部分,叫做上层驱动,而那些实现设备的读、写操作的驱动称为下层驱动,上层驱动将下层驱动封装,并且留给其上层应用一些更加容易简单的接口。
在我们即将看到的代码中,使用mtd_info数据结构表示一个MTD设备,使用nand_chip数据结构表示一个nand flash芯片。在mtd_info结构中,对nand_flash结构作了封装,向上层提供统一的接口。比如,它根据nand_flash提供的 read_data(读一个字节)、read_addr(发送要读的扇区的地址)等函数,构造了一个通用的读函数read,将此函数的指针作为自己的一个成员。而上层要读写flash时,执行mtd_info中的read、write函数即可。
mtd_dev_init()用来扫描所使用的NAND Flash的型号,构造MTD设备,即构造一个mtd_info的数据结构。对于S3C2410来说,它直接调用mtd_init(),mtd_init 又调用smc_init(),此函数在drivers/mtd/maps/s3c2410_flash.c中:
[main(int argc,char *argv[])>mtd_dev_init()>mtd_init()]
1        int mtd_init(void)
2  {
3  int ret;

4  #ifdef CONFIG_MTD_CFI                /*is not set*/
5  ret = cfi_init();
6        #endif
7        #ifdef CONFIG_MTD_SMC9                /* =y */
8                ret = smc_init();
9        #endif
10        #ifdef CONFIG_S3C2410_AMD_BOOT        /*is not set*/
11                ret = amd_init();
12        #endif

13        if (ret) {
14                mymtd = NULL;
15                return ret;
16                 }
17        return 0;
18        }
显而易见,该函数应取第二项,这项在autoconf.h中定义了。
[main(int argc, char *argv[]) > mtd_dev_init() > mtd_init() > smc_init()]
1 static int
2 smc_init(void)
3 {
/*struct mtd_info *mymtd,数据类型在include/mtd/mtd.h*/
            /*strcut nand_chip在include/mtd/nand.h中定义*/
4       struct nand_chip *this;
5       u_int16_t nfconf;
            /* Allocate memory for MTD device structure and private data */
6       mymtd = mmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip));
7       if (!mymtd) {
8          printk("Unable to allocate S3C2410 NAND MTD device structure.\n");
9          return -ENOMEM;
10      }
            /* Get pointer to private data */
11      this = (struct nand_chip *)(&mymtd[1]);
            /* Initialize structures */
12      memset((char *)mymtd, 0, sizeof(struct mtd_info));
13      memset((char *)this, 0, sizeof(struct nand_chip));
/* Link the private data with the MTD structure */
14      mymtd->priv = this;
/* set NAND Flash  controller */
15      nfconf = NFCONF;
/* NAND Flash controller enable */
16      nfconf |= NFCONF_FCTRL_EN;
/* Set flash memory timing */
17      nfconf &= ~NFCONF_TWRPH1;   /* 0x0 */
    18      nfconf |= NFCONF_TWRPH0_3;  /* 0x3 */
    19      nfconf &= ~NFCONF_TACLS; /* 0x0 */
             
    20      NFCONF = nfconf;
             
            /* Set address of NAND IO lines */
    21      this->hwcontrol = smc_hwcontrol;
    22      this->write_cmd = write_cmd;
23      this->write_addr = write_addr;
24      this->read_data = read_data;
25      this->write_data = write_data;
26      this->wait_for_ready = wait_for_ready;
             
/* Chip Enable -> RESET -> Wait for Ready -> Chip Disable */
27      this->hwcontrol(NAND_CTL_SETNCE);
28      this->write_cmd(NAND_CMD_RESET);
29      this->wait_for_ready();
30      this->hwcontrol(NAND_CTL_CLRNCE);
             
31      smc_insert(this);
             
32      return 0;
33 }
6-14行构造了一个mtd_info结构和nand_flash结构,前者对应MTD设备,后者对应nand flash芯片(如果您用的是其他类型的存储器件,比如nor flash,这里的nand_flash结构应该换为其他类型的数据结构)。MTD设备是具体存储器件的抽象,那么在这些代码中这种关系如何体现呢——第 14行的代码把两者连结在一起了。事实上,mtd_info结构中各成员的实现(比如read、write函数),正是由priv变量所指向的 nand_flash的各类操作函数(比如read_addr、read_data等)来实现的。
15-20行是初始化S3C2410上的NAND FLASH控制器。前面分配的nand_flash结构还是空的,现在当然就是填满它的各类成员了,这正是21-26行做的事情。27-30行对这块 nand flash作了一下复位操作。最后,也是最复杂的部分,根据刚才填充的nand_flash结构,构造mtd_info结构,这由31行的 smc_insert函数调用smc_scan完成。

这才是VIVI启动的第5步,还有三步就完成了启动了,同时我的这篇阅读笔记也就OVER了。


Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1527713

点击此处查看原文 >>

系统分类: 单片机   |    用户分类:    |    来源: 转贴

评论(0) | 阅读(778)
发表于:2007-11-22 18:33:13
标签:ARM  

0

转载:嵌入式系统 Boot Loadre 技术内幕

嵌入式系统 Boot Loader 技术内幕

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项



级别: 初级

詹荣开 (mailto:zhanrk@sohu.com?subject=嵌入式系统 Boot Loader 技术内幕), Linux爱好者

2003 年 12 月 01 日

本文详细地介绍了基于嵌入式系统中的 OS 启动加载程序 ―― Boot Loader 的概念、软件设计的主要任务以及结构框架等内容。

1. 引言

在专用的嵌入式板子运行 GNU/Linux 系统已经变得越来越流行。一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次:

1. 引导加载程序。包括固化在固件(firmware)中的 boot 代码(可选),和 Boot Loader 两大部分。

2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。

3. 文件系统。包括根文件系统和建立于 Flash 内存设备之上文件系统。通常用 ram disk 来作为 root fs。

4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:MicroWindows 和 MiniGUI 懂。

引导加载程序是系统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道,PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘 MBR 中的 OS Boot Loader(比如,LILO 和 GRUB 等)一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。

而在嵌入式系统中,通常并没有像 BIOS 那样的固件程序(注,有的嵌入式 CPU 也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 ARM7TDMI core 的嵌入式系统中,系统在上电或复位时通常都从地址 0x00000000 处开始执行,而在这个地址处安排的通常就是系统的 Boot Loader 程序。

本文将从 Boot Loader 的概念、Boot Loader 的主要任务、Boot Loader 的框架结构以及 Boot Loader 的安装等四个方面来讨论嵌入式系统的 Boot Loader。


点击看大图


回页首


2. Boot Loader 的概念

简单地说,Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。

通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此,我们仍然可以对 Boot Loader 归纳出一些通用的概念来,以指导用户特定的 Boot Loader 设计与实现。

1. Boot Loader 所支持的 CPU 和嵌入式板

每种不同的 CPU 体系结构都有不同的 Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU 的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。

2. Boot Loader 的安装媒介(Installation Medium)

系统加电或复位后,所有的 CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0x00000000 取它的第一条指令。而基于 CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行 Boot Loader 程序。

下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图。


图1 固态存储设备的典型空间分配结构

3. 用来控制 Boot Loader 的设备或机制

主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。

4. Boot Loader 的启动过程是单阶段(Single Stage)还是多阶段(Multi-Stage)

通常多阶段的 Boot Loader 能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,也即启动过程可以分为 stage 1 和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面讨论。

5. Boot Loader 的操作模式 (Operation Mode)

大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。

启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。

下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。

像 Blob 或 U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,Blob 在启动时处于正常的启动加载模式,但是它会延时 10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如果在 10 秒内没有用户按键,则 blob 继续启动 Linux 内核。

6. BootLoader 与主机之间进行文件传输所用的通信设备及协议

最常见的情况就是,目标机上的 Boot Loader 通过串口与主机之间进行文件传输,传输协议通常是 xmodem/ymodem/zmodem 协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。

此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和 TFTP 协议来下载文件时,主机方必须有一个软件用来的提供 TFTP 服务。

在讨论了 BootLoader 的上述概念后,下面我们来具体看看 BootLoader 的应该完成哪些任务。


点击看大图


回页首


3. Boot Loader 的主要任务与典型结构框架

在继续本节的讨论之前,首先我们做一个假定,那就是:假定内核映像与根文件系统映像都被加载到 RAM 中运行。之所以提出这样一个假设前提是因为,在嵌入式系统中内核映像与根文件系统映像也可以直接在 ROM 或 Flash 这样的固态存储设备中直接运行。但这种做法无疑是以运行速度的牺牲为代价的。

从操作系统的角度看,Boot Loader 的总目标就是正确地调用内核来执行。

另外,由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。

Boot Loader 的 stage1 通常包括以下步骤(以执行的先后顺序):

  • 硬件设备初始化。

  • 为加载 Boot Loader 的 stage2 准备 RAM 空间。

  • 拷贝 Boot Loader 的 stage2 到 RAM 空间中。

  • 设置好堆栈。

  • 跳转到 stage2 的 C 入口点。

Boot Loader 的 stage2 通常包括以下步骤(以执行的先后顺序):

  • 初始化本阶段要使用到的硬件设备。

  • 检测系统内存映射(memory map)。

  • 将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中。

  • 为内核设置启动参数。

  • 调用内核。

3.1 Boot Loader 的 stage1

3.1.1 基本的硬件初始化

这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序):

1. 屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器(比如 ARM 的 CPSR 寄存器)来完成。

2. 设置 CPU 的速度和时钟频率。

3. RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。

4. 初始化 LED。典型地,通过 GPIO 来驱动 LED,其目的是表明系统的状态是 OK 还是 Error。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息来完成这一点。

5. 关闭 CPU 内部指令/数据 cache。

3.1.2 为加载 stage2 准备 RAM 空间

为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。

由于 stage2 通常是 C 语言执行代码,因此在考虑空间大小时,除了 stage2 可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言,1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排,比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是,将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) - RamEnd)是一种值得推荐的方法。

为了后面的叙述方便,这里把所安排的 RAM 空间范围的大小记为:stage2_size(字节),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此:

stage2_end=stage2_start+stage2_size

另外,还必须确保所安排的地址范围的的确确是可读写的 RAM 空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法,也即:以 memory page 为被测试单位,测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下:

1. 先保存 memory page 一开始两个字的内容。

2. 向这两个字中写入任意的数字。比如:向第一个字写入 0x55,第 2 个字写入 0xaa。

3. 然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0x55 和 0xaa。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。

4. 再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa,第 2 个字中写入 0x55。

5. 然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 和 0x55。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。

6. 恢复这两个字的原始内容。测试完毕。

为了得到一段干净的 RAM 空间范围,我们也可以将所安排的 RAM 空间范围进行清零操作。

3.1.3 拷贝 stage2 到 RAM 中

拷贝时要确定两点:(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址;(2) RAM 空间的起始地址。

3.1.4 设置堆栈指针 sp

堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),也即在 3.1.2 节所安排的那个 1MB 的 RAM 空间的最顶端(堆栈向下生长)。

此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2。

经过上述这些执行步骤后,系统的物理内存布局应该如下图2所示。

3.1.5 跳转到 stage2 的 C 入口点

在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。


图2 bootloader 的 stage2 可执行映象刚被拷贝到 RAM 空间时的系统内存布局
点击看大图

3.2 Boot Loader 的 stage2

正如前面所说,stage2 的代码通常用 C 语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是,在编译和链接 boot loader 这样的程序时,我们不能使用 glibc 库中的任何支持函数。其原因是显而易见的。这就给我们带来一个问题,那就是从那里跳转进 main() 函数呢?直接把 main() 函数的起始地址作为整个 stage2 执行映像的入口点或许是最直接的想法。但是这样做有两个缺点:1)无法通过main() 函数传递函数参数;2)无法处理 main() 函数返回的情况。一种更为巧妙的方法是利用 trampoline(弹簧床)的概念。也即,用汇编语言写一段trampoline 小程序,并将这段 trampoline 小程序来作为 stage2 可执行映象的执行入口点。然后我们可以在 trampoline 汇编小程序中用 CPU 跳转指令跳入 main() 函数中去执行;而当 main() 函数返回时,CPU 执行路径显然再次回到我们的 trampoline 程序。简而言之,这种方法的思想就是:用这段 trampoline 小程序来作为 main() 函数的外部包裹(external wrapper)。

下面给出一个简单的 trampoline 程序示例(来自blob):

.text
.globl _trampoline
_trampoline:
	bl	main
	/* if main ever returns we just call it again */
	b	_trampoline

可以看出,当 main() 函数返回后,我们又用一条跳转指令重新执行 trampoline 程序――当然也就重新执行 main() 函数,这也就是 trampoline(弹簧床)一词的意思所在。

3.2.1初始化本阶段要使用到的硬件设备

这通常包括:(1)初始化至少一个串口,以便和终端用户进行 I/O 输出信息;(2)初始化计时器等。

在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入 main() 函数执行。

设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。

3.2.2 检测系统的内存映射(memory map)

所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如,在 SA-1100 CPU 中,从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间,而在 Samsung S3C44B0X CPU 中,从 0x0c00,0000 到 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说,具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态。 由于上述这个事实,因此 Boot Loader 的 stage2 必须在它想干点什么 (比如,将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 "unused" 状态的。

(1) 内存映射的描述

可以用如下数据结构来描述 RAM 地址空间中的一段连续(continuous)的地址范围:

typedef struct memory_area_struct {
	u32 start; /* the base address of the memory region */
	u32 size; /* the byte number of the memory region */
	int used;
} memory_area_t;

这段 RAM 地址空间中的连续地址范围可以处于两种状态之一:(1)used=1,则说明这段连续的地址范围已被实现,也即真正地被映射到 RAM 单元上。(2)used=0,则说明这段连续的地址范围并未被系统所实现,而是处于未使用状态。

基于上述 memory_area_t 数据结构,整个 CPU 预留的 RAM 地址空间可以用一个 memory_area_t 类型的数组来表示,如下所示:

memory_area_t memory_map[NUM_MEM_AREAS] = {
	[0 ... (NUM_MEM_AREAS - 1)] = {
		.start = 0,
		.size = 0,
		.used = 0
	},
};

(2) 内存映射的检测

下面我们给出一个可用来检测整个 RAM 地址空间内存映射情况的简单而有效的算法:

/* 数组初始化 */
for(i = 0; i < NUM_MEM_AREAS; i++)
	memory_map[i].used = 0;
/* first write a 0 to all memory locations */
for(addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE)
	* (u32 *)addr = 0;
for(i = 0, addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) {
     /*
      * 检测从基地址 MEM_START+i*PAGE_SIZE 开始,大小为
* PAGE_SIZE 的地址空间是否是有效的RAM地址空间。
      */
     调用3.1.2节中的算法test_mempage();
     if ( current memory page isnot a valid ram page) {
		/* no RAM here */
		if(memory_map[i].used )
			i++;
		continue;
	}
	
	/*
	 * 当前页已经是一个被映射到 RAM 的有效地址范围
	 * 但是还要看看当前页是否只是 4GB 地址空间中某个地址页的别名?
	 */
	if(* (u32 *)addr != 0) { /* alias? */
		/* 这个内存页是 4GB 地址空间中某个地址页的别名 */
		if ( memory_map[i].used )
			i++;
		continue;
	}
	
	/*
	 * 当前页已经是一个被映射到 RAM 的有效地址范围
	 * 而且它也不是 4GB 地址空间中某个地址页的别名。
	 */
	if (memory_map[i].used == 0) {
		memory_map[i].start = addr;
		memory_map[i].size = PAGE_SIZE;
		memory_map[i].used = 1;
	} else {
		memory_map[i].size += PAGE_SIZE;
	}
} /* end of for (…) */

在用上述算法检测完系统的内存映射情况后,Boot Loader 也可以将内存映